@nurix/etna 0.20.29 → 0.20.30

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.
Files changed (73) hide show
  1. package/data/harness-index.json +1 -1
  2. package/data/harnesses/code/CLAUDE.md +4 -4
  3. package/data/harnesses/code/agents/bookkeeper.md +2 -2
  4. package/data/harnesses/code/agents/change-review.md +3 -3
  5. package/data/harnesses/code/agents/lld-mode.md +3 -3
  6. package/data/harnesses/code/agents/maintenance.md +22 -19
  7. package/data/harnesses/code/agents/polygraph-finder.md +3 -3
  8. package/data/harnesses/code/agents/polygraph.md +4 -4
  9. package/data/harnesses/code/agents/retro.md +1 -1
  10. package/data/harnesses/code/codex/agents/bookkeeper.toml +7 -3
  11. package/data/harnesses/code/codex/agents/change-review.toml +3 -3
  12. package/data/harnesses/code/codex/agents/lld-mode.toml +3 -3
  13. package/data/harnesses/code/codex/agents/maintenance.toml +60 -37
  14. package/data/harnesses/code/codex/agents/polygraph-finder.toml +6 -2
  15. package/data/harnesses/code/codex/agents/polygraph.toml +4 -4
  16. package/data/harnesses/code/codex/agents/retro.toml +1 -1
  17. package/data/harnesses/code/docs/structure.md +15 -15
  18. package/data/harnesses/code/harness.json +2 -2
  19. package/data/harnesses/code/hooks/lib/ownership.mjs +12 -13
  20. package/data/harnesses/code/hooks/lib/sweep.mjs +1 -1
  21. package/data/harnesses/code/skills/documentation/SKILL.md +17 -27
  22. package/data/harnesses/code/skills/documentation/references/frontmatter.md +56 -62
  23. package/data/harnesses/code/skills/documentation/references/style-guide.md +2 -2
  24. package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +1 -1
  25. package/data/harnesses/code/skills/documentation/templates/Readme.md +1 -1
  26. package/data/harnesses/code/skills/documentation/templates/docs-readme.md +1 -1
  27. package/data/harnesses/code/skills/documentation/templates/doctrine.md +2 -2
  28. package/data/harnesses/code/skills/documentation/templates/feature.md +3 -4
  29. package/data/harnesses/code/skills/documentation/templates/package.md +2 -3
  30. package/data/harnesses/code/skills/documentation/templates/root-readme.md +1 -1
  31. package/data/harnesses/code/skills/documentation/templates/system.md +2 -4
  32. package/data/harnesses/code/skills/documentation/templates/ux-behavior.md +4 -4
  33. package/data/harnesses/delivery/agents/lld-mode.md +3 -3
  34. package/data/harnesses/delivery/agents/maintenance.md +22 -19
  35. package/data/harnesses/delivery/agents/retro.md +1 -1
  36. package/data/harnesses/delivery/codex/agents/lld-mode.toml +3 -3
  37. package/data/harnesses/delivery/codex/agents/maintenance.toml +60 -37
  38. package/data/harnesses/delivery/codex/agents/retro.toml +1 -1
  39. package/data/harnesses/delivery/harness.json +2 -2
  40. package/data/harnesses/delivery/skills/documentation/SKILL.md +17 -27
  41. package/data/harnesses/delivery/skills/documentation/references/frontmatter.md +56 -62
  42. package/data/harnesses/delivery/skills/documentation/references/style-guide.md +2 -2
  43. package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +1 -1
  44. package/data/harnesses/delivery/skills/documentation/templates/Readme.md +1 -1
  45. package/data/harnesses/delivery/skills/documentation/templates/docs-readme.md +1 -1
  46. package/data/harnesses/delivery/skills/documentation/templates/doctrine.md +2 -2
  47. package/data/harnesses/delivery/skills/documentation/templates/feature.md +3 -4
  48. package/data/harnesses/delivery/skills/documentation/templates/package.md +2 -3
  49. package/data/harnesses/delivery/skills/documentation/templates/root-readme.md +1 -1
  50. package/data/harnesses/delivery/skills/documentation/templates/system.md +2 -4
  51. package/data/harnesses/delivery/skills/documentation/templates/ux-behavior.md +4 -4
  52. package/data/harnesses/etna/agents/change-review.md +3 -3
  53. package/data/harnesses/etna/agents/lld-mode.md +3 -3
  54. package/data/harnesses/etna/agents/maintenance.md +22 -19
  55. package/data/harnesses/etna/agents/retro.md +1 -1
  56. package/data/harnesses/etna/codex/agents/change-review.toml +3 -3
  57. package/data/harnesses/etna/codex/agents/lld-mode.toml +3 -3
  58. package/data/harnesses/etna/codex/agents/maintenance.toml +60 -37
  59. package/data/harnesses/etna/codex/agents/retro.toml +1 -1
  60. package/data/harnesses/etna/harness.json +2 -2
  61. package/data/harnesses/etna/skills/documentation/SKILL.md +17 -27
  62. package/data/harnesses/etna/skills/documentation/references/frontmatter.md +56 -62
  63. package/data/harnesses/etna/skills/documentation/references/style-guide.md +2 -2
  64. package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +1 -1
  65. package/data/harnesses/etna/skills/documentation/templates/Readme.md +1 -1
  66. package/data/harnesses/etna/skills/documentation/templates/docs-readme.md +1 -1
  67. package/data/harnesses/etna/skills/documentation/templates/doctrine.md +2 -2
  68. package/data/harnesses/etna/skills/documentation/templates/feature.md +3 -4
  69. package/data/harnesses/etna/skills/documentation/templates/package.md +2 -3
  70. package/data/harnesses/etna/skills/documentation/templates/root-readme.md +1 -1
  71. package/data/harnesses/etna/skills/documentation/templates/system.md +2 -4
  72. package/data/harnesses/etna/skills/documentation/templates/ux-behavior.md +4 -4
  73. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  # dropped Claude tools: Read, Glob, Grep, Edit, Write, Bash
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 structural reconcile on top: when the repo's `harness-version` marker is missing or behind, it converges `docs/` to the installed harness shape (scaffold the standard tree, fold dissolved-shape artifacts forward, 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\"."
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
  developer_instructions = '''
5
5
 
6
6
  # Maintenance Agent
@@ -26,9 +26,9 @@ Decide the mode once, up front, from the version marker — then run.
26
26
  | behind on minor/patch | **probe first**, below |
27
27
  | equal to installed | **audit only** (the default spine) |
28
28
 
29
- **The probe.** Run the Step B2 detection pass read-only: if any old-shape artifact in the fold table is present, **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`), the shape *did* change ⇒ **baseline mode**. Only when the probe finds nothing ⇒ re-stamp the version (no reshape), then audit.
29
+ **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.
30
30
 
31
- > **The version digit is a hint; the fold table is the authority.** A shape change ships in a minor whenever the capability behind it is additive — `docs/history/ledger/` dissolving into `docs/history/changelog/` arrived in a minor, as did `docs/history/migrations/` — so "minor ⇒ nothing to reshape" silently strands dissolved-shape artifacts in the repo forever.
31
+ **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
32
 
33
33
  > **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
34
  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.
@@ -42,24 +42,35 @@ Decide the mode once, up front, from the version marker — then run.
42
42
 
43
43
  ## Part A — Coherence audit (always runs)
44
44
 
45
- The ownership source of truth is the **union of every doc's `owns:` frontmatter** — the same source the SessionStart ownership index derives from. There is no `docs/routing.md` and no precomputed file to regenerate; you rebuild the map by globbing `docs/features/`.
45
+ Ownership is **computed, never typed**, per the `documentation` skill's `references/frontmatter.md`: a document names the objects it designs (`objects:`), and each source file's path header names the document that speaks for it. Until the harness's ownership scan switches to those edges, it reads a **transitional `owns:` region glob** on each owning document — the same source the SessionStart ownership index derives from today. There is no `docs/routing.md` and no precomputed file to regenerate; you rebuild the map from the source headers and the transitional globs.
46
46
 
47
47
  ### Step 1: Build the ownership map
48
48
 
49
49
  1. Read `docs/structure.md` for the authority map.
50
- 2. Glob the docs tree for markdown carrying `owns:` frontmatter, ignoring `*.bkp.*` files and anything under `docs/history/`. **Ownership is declared, not positional** — the standard shape files these under `docs/features/`, but a repo may declare ownership on any doc. For each, parse the `owns:` globs and `module:` label.
51
- 3. Build a map: glob → owning doc + module. Narrower glob wins on overlap. **This IS the source of truth.**
50
+ 2. Read each source file's path header for the `docs/…` path it names — the header pointer, the authored edge on the code's side.
51
+ 3. Glob the docs tree for markdown carrying frontmatter, ignoring `*.bkp.*` files (installer backups awaiting the fold) and anything under `docs/history/` (frozen records quote retired globs as provenance). For each, parse `objects:`, `module:` and any transitional `owns:` region globs.
52
+ 4. Build the map: path → the document its header names; where a header names none, the narrowest transitional glob that matches. Where the two disagree, report both — never pick one silently.
52
53
 
53
- **Architecture-first fallback.** If no doc carries `owns:` yet (a fresh-baseline repo can carry thousands of LOC and zero feature docs), drive this pass's ownership/drift surface off `docs/architecture/**` file-references instead — they become the **primary** ownership map for Steps 2–5. The `owns:` map becomes primary the moment any feature doc declares ownership — this is a fallback, not a replacement.
54
+ **Architecture-first fallback.** If no source header names a document and no doc carries `owns:` yet (a fresh-baseline repo can carry thousands of LOC and zero feature docs), drive this pass's ownership/drift surface off `docs/architecture/**` file-references instead — they become the **primary** ownership map for Steps 2–5. The computed map becomes primary the moment any header or feature doc names ownership — this is a fallback, not a replacement.
54
55
 
55
56
  ### Step 2: Validate ownership
56
57
 
57
- For each `owns:` glob across the feature docs:
58
+ Across the header pointers and the transitional globs:
58
59
 
59
- - Verify the glob matches existing files. Report globs matching nothing, deleted paths, and orphaned docs.
60
- - 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.
60
+ - **Header pointers:** a header naming a document that does not exist is remediation — correct the path. A source file whose header names no document, inside a region an owning document speaks for, is reported as a header to add, never invented.
61
+ - **Transitional globs:** verify each matches existing files; report globs matching nothing, deleted paths, and orphaned docs. A file-level `owns:` list is reduced to its region globs (remediation, not a question); the files it dropped are reported where their headers name no document.
62
+ - **Retired keys:** `applies:` and `keywords:` are removed wherever found (remediation); a doctrine the removed `applies:` named stays named in the document's Scope section.
63
+ - Flag **equal-specificity overlaps** — two feature docs whose transitional `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
64
  - **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
65
 
66
+ **Then validate the doc→doc edge.** `node .claude/hooks/check-ownership.mjs` and the session-injected **doc edge index** carry `verifies:` and its generated inverse; read the edge index and check two things:
67
+
68
+ - **Dangling edges.** A `verifies:` 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`.
69
+ - **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.
70
+ - **Doctrine with no `objects:`.** A `docs/architecture/common/` document that names no objects is reachable by nothing: the closure that links a doctrine to the surfaces it governs runs through the objects both name. Report the list; naming a doctrine's objects is its author's call.
71
+
72
+ **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.
73
+
63
74
  For each code symbol or path a doc explicitly **names** (function/export/route/type):
64
75
 
65
76
  - Verify it resolves in the code, independent of git-churn — Step 3's churn gate is blind to churn-free false claims.
@@ -75,17 +86,15 @@ Also check the **env contract** when the repo declares a canonical `.env.example
75
86
 
76
87
  Run `git log --since="2 weeks ago" --name-only --pretty=format:"%h %s"` to find changed source files. For each changed file:
77
88
 
78
- - Match against the `owns:` map (narrower glob wins) → identify the owning doc.
79
- - If no glob matches → flag as **unowned**.
89
+ - Match against the map from Step 1 (the header pointer first, then the narrowest transitional glob) → identify the owning doc.
90
+ - If neither names a document → flag as **unowned**.
80
91
  - If an owner exists → compare last-modified dates (`git log -1 --format="%ci" -- <path>`), flag docs where code changed more recently (14-day threshold).
81
92
 
82
93
  **Audit priority.** Rank the docs to inspect by their `last-reviewed:` frontmatter (optional key): never-stamped first, then oldest-first. A doc reviewed last week rarely needs a re-read; one never stamped is the likeliest to have drifted.
83
94
 
84
95
  ### Step 4: Check architecture & backlog
85
96
 
86
- **Unowned paths.** Compare the repo's actual top-level source directories (derive them — the top-level dirs that contain code, or the roots named across the `owns:` globs; do not assume `src/`) against the `owns:` map. Report directories no `owns:` glob covers.
87
-
88
- **Stale greenfield self-label.** If `structure.md` asserts the repo is greenfield — _"No source code or feature docs exist yet"_ or similar — **but a real source tree exists** (the derived source roots above are non-empty, e.g. a populated `packages/` of real workspaces), the self-label has gone stale. Raise it as a question (e.g. `structure.md says "No source code exists yet" but packages/ holds 5 workspaces — confirm the greenfield label should be retired`).
97
+ **Unowned paths.** Compare the repo's actual top-level source directories (derive them — the top-level dirs that contain code, or the roots named across the headers and the transitional globs; do not assume `src/`) against the map. Report directories whose files no header and no transitional glob reaches.
89
98
 
90
99
  **Architecture drift.** Grep `docs/architecture/` for file-path references. Verify referenced paths still exist. Remove or update stale references.
91
100
 
@@ -99,7 +108,7 @@ For each stale doc: update the doc directly when the drift is clear (renamed fun
99
108
 
100
109
  For each feature doc you verified accurate or updated this pass, stamp `last-reviewed:` in its frontmatter with today's date (add the key if absent). Never write review dates into the body — it's frontmatter metadata only.
101
110
 
102
- For unowned code paths, append: `- **[P2]** No feature doc owns \`{dir}/\` — discovered by maintenance` (`{dir}` = the actual unowned source directory).
111
+ For an unowned code **region**, append: `- **[P2]** No feature doc owns \`{dir}/\` — discovered by maintenance` (`{dir}` = the actual unowned source directory). The target is **every region has an owner**, never every file: an individual shared file that several features import is expected to be unowned, because its authority is the contract it exposes rather than any one consumer. Report a directory that no doc's region covers; never mint a row for a shared file, and never propose closing a gap by adding a file glob to whichever doc happens to import it.
103
112
 
104
113
  **Declined realignment.** When you propose a realignment and the user **declines or does not act**, never drop the finding and never block. Append it to `docs/history/backlog.md` as `- **[P0–P3]** <finding> — discovered by maintenance`, priority = the finding's own severity **on the same P0–P3 scale as code items** — never a segregated "compliance" section, never auto-relegated to the lowest tier. Record once: dedupe against existing entries; if already present, leave it.
105
114
 
@@ -107,7 +116,7 @@ For unowned code paths, append: `- **[P2]** No feature doc owns \`{dir}/\` — d
107
116
 
108
117
  ### Step 6: Report & update tracking
109
118
 
110
- 1. Update `docs/structure.md` Health Log with today's date and summary — **append to the end**, never date-sorted into the middle. Rows are positional: one may say "the row above is…", and an inserted row silently breaks that reference. A Health Log entry is a **generated row, not a narrative essay**: one line — date · counts (stale docs fixed, dead globs, unowned paths, backlog items created) · links to anything that needs a human.
119
+ 1. Append today's row to `docs/history/health-log.md` — **append to the end**, never date-sorted into the middle. Rows are positional: one may say "the row above is…", and an inserted row silently breaks that reference. A Health Log entry is a **generated row, not a narrative essay**: one line — date · counts (stale docs fixed, dead globs, unowned paths, backlog items created) · links to anything that needs a human.
111
120
  2. Update `docs/memory/maintenance.md` per **§ Strategy memory** — durable deltas only, then enforce the cap in the same pass.
112
121
  3. Report findings: stale docs, broken ownership, unowned code, architecture drift, backlog health.
113
122
 
@@ -122,11 +131,11 @@ Baseline mode converges the repo's `docs/` to the **installed** harness shape, t
122
131
  ```
123
132
  docs/
124
133
  ├── README.md ✱ # Documentation root — quick links, section index
125
- ├── structure.md ✱ # Authority map + frontmatter `harness-version` (you own this field)
134
+ ├── structure.md # Harness-owned authority map — the installer writes it on every refresh; you stamp only `harness-version`
126
135
  ├── architecture/ # Cross-module design + steering layer
127
136
  │ │ # domain-model, glossary, non-goals, references, open-questions
128
137
  │ └── decisions/ # Architecture Decisions (ADs — one per subject, updated in place; journey in each AD's Decision Changelog)
129
- ├── features/ # Feature specs — the only first-class doc unit (flat; ownership in each doc's `owns:` frontmatter)
138
+ ├── features/ # Feature specs — the only first-class doc unit (flat; ownership computed from each doc's `objects:` and the source headers)
130
139
  │ └── {group}/ # browse-only subfolder for grouping; NEVER a module; may carry a Readme.md
131
140
  ├── operations/ # Setup, development, deployment, CI/CD
132
141
  │ └── bookkeeping.md # the shared doc-sync work queue (hook-appended, bookkeeper-drained, walled at commit) — author-created, never scaffolded; treat rows as live work, never as drift
@@ -138,7 +147,6 @@ docs/
138
147
  │ ├── plans/ # Implementation plans (Status field required)
139
148
  │ ├── migrations/ # One-shot migration/upgrade runbooks (YYYY-MM-DD-{slug}.md; outcome-stamped then frozen)
140
149
  │ ├── journal.md # Append-only mid-execution knowledge capture (one line per fact; resolves by landing in its owning doc) — author-created, never scaffolded
141
- │ ├── sessions/ # Per-session gate logs (YYYY-MM-DD-{sid}.md) — machinery-written records, never drained, never edited down; created by the first gate event, never scaffolded
142
150
  │ ├── brainstorms/ # Harvested brainstorm sessions ({topic}/) — user decides kept (committed) or deleted; Readme.md is the index
143
151
  │ ├── research/ # Deep-research records ({topic}/ — README.md synthesis + findings.md + sources.md); frozen, cited, owns no code
144
152
  │ └── backlog.md ✱ # Tracked work items by priority (P0-P3)
@@ -147,9 +155,9 @@ docs/
147
155
  └── tests/ # Behavioral test checklists ({feature}.md, step → **Assert**), derived at plan Completion via the `documentation` skill; author-maintained
148
156
  ```
149
157
 
150
- **✱ marks the five scaffolded files — the only paths a baseline creates, and the only shape signals.** Every **folder** in this tree is created on first use and is simply absent until it holds a file; git cannot track an empty directory, so a missing folder is never evidence of drift and is never scaffolded to satisfy the shape. Record its role in `docs/structure.md` instead.
158
+ **✱ marks the scaffolded files — the only paths a baseline creates, and the only shape signals.** `docs/structure.md` is not one of them: the installer owns it and rewrites it on every install or refresh, so a missing or hand-edited `structure.md` means the harness needs refreshing (`npx @nurix/etna`), never a scaffold from a template — and its body is never converged, only its `harness-version` field stamped. Every **folder** in this tree is created on first use and is simply absent until it holds a file; git cannot track an empty directory, so a missing folder is never evidence of drift and is never scaffolded to satisfy the shape. Its role is already in `docs/structure.md`; a repo's own notes about legacy folders go in `docs/README.md` § Notes for this repository.
151
159
 
152
- There is **no `docs/routing.md`**, **no `docs/modules/{m}/`**, **no `docs/pillars/`**, **no `docs/boundaries/`**. Code ownership lives in feature `owns:` frontmatter; a module is a `module:` label, not a folder; boundaries are diagnosed from source; brainstorm records live in `docs/research/` (active) and `docs/history/brainstorms/` (harvested).
160
+ There is **no `docs/routing.md`**, **no `docs/modules/{m}/`**, **no `docs/pillars/`**, **no `docs/boundaries/`**. Code ownership is computed from `objects:` and source headers, with a transitional `owns:` region glob; a module is a `module:` label, not a folder; boundaries are diagnosed from source; brainstorm records live in `docs/research/` (active) and `docs/history/brainstorms/` (harvested).
153
161
 
154
162
  > Brainstorm records are **author-created, never scaffolded**; legacy active folders carry the older `-brainstorming/` suffix — treat both spellings as canonical. **Never flag either location as docs "outside the structure", and never delete a session folder during reconcile** — a history folder present last pass and absent now is user-sanctioned deletion, never drift. Keep each folder's `Readme.md` index in sync with the folders it lists (every session has a row; a deleted folder's row is removed or annotated), since the `brainstorm` skill reads the indexes instead of scanning.
155
163
 
@@ -165,39 +173,51 @@ Detecting and folding old-shape artifacts is what makes a baseline more than a f
165
173
 
166
174
  | If you find (old shape) | Fold to (current shape) |
167
175
  | --- | --- |
168
- | a persisted `docs/routing.md` ownership map | migrate each File-Pattern row's ownership into the owning feature doc's `owns:` frontmatter (Pattern → an `owns:` glob; Module column → the doc's `module:` label), then **delete `docs/routing.md`** |
169
- | feature docs carrying `owns`/`touches`/`depends_on` legacy frontmatter | collapse to the single `owns:` key (drop `touches`/`depends_on`; their paths fold into `owns:` if the doc genuinely owns them, else drop) |
170
- | a `docs/modules/{m}/` tree | move each module's `features/` flat into `docs/features/` with the module name as a `module:` tag on each doc; fold module-local `pillars/`/prose into `docs/architecture/`; delete the `docs/modules/` tree |
171
- | repo-wide `docs/pillars/` | cross-module doctrine → `docs/architecture/`; delete the folder |
172
- | `docs/boundaries/` (legacy persisted boundaries) | delete — boundary analysis is a plan-time diagnostic (the `boundary` skill reads contracts fresh from source); fold any human-authored knowledge (external consumers, intent, accepted exceptions) into `docs/architecture/` |
173
- | `docs/OWNERSHIP_INDEX.md` (legacy derived ownership index) | delete — ownership is the union of feature `owns:` frontmatter, surfaced by the generated index |
174
- | `docs/history/ledger/YYYY-MM.md` (the Refinement Ledger, now merged into the changelog) | split each `### ` block into its own `docs/history/changelog/YYYY-MM-DD-{three-words}.md` entry — content preserved **verbatim**, heading promoted to `#`, three words naming the work; append one row per entry to `docs/history/CHANGELOG.md`; delete the `docs/history/ledger/` folder. Records are moved, never rewritten |
175
- | `docs/operations/guardrails.md` | fold durable contract notes into `docs/architecture/` |
176
- | `docs/architecture/overview.md` | split into `domain-model.md` / `glossary.md` / `non-goals.md` / `references.md` / `open-questions.md` |
177
- | `docs/architecture/adr/` (legacy folder name), or `ADR-NNN-*.md` records anywhere under `docs/architecture/` | **rename to the AD genre.** The folder becomes `docs/architecture/decisions/`; each record file `ADR-NNN-{slug}.md` becomes `AD-NNN-{slug}.md`, keeping its number and slug. Inside each record, update the retired vocabulary — "ADR", "Architecture Decision Record", and any supersession framing ("once accepted, an ADR is never edited; a later ADR supersedes it") — to the AD model: one AD per subject, **updated in place**, its journey confined to a `Decision Changelog` tail. A record's **reasoning is never rewritten** by a reconcile; only its genre vocabulary, its own title/id, and paths it cites. The folder's `Readme.md` is an **index, not a record** — reframe its genre description while preserving authored content such as a candidate-decision list verbatim. Repoint live references **inside `docs/`**, including one AD citing another; leave frozen records' links as point-in-time. A live reference **outside** your write scope — most often the root `README.md`, which you never overwrite — cannot be repointed: record it as a backlog item naming the file and line rather than leaving the residue silent |
178
- | a legacy `docs/brainstorm/{topic}/`, a singular `docs/history/brainstorm/`, or a root `brainstorm/`/`brainstorming/` folder | move each **active** record to `docs/research/{topic}-brainstorm/` and each **harvested** one to `docs/history/brainstorms/{topic}/` (dropping the suffix); create/refresh both `Readme.md` indexes; delete the emptied folder |
179
176
  | `*.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 |
180
177
  | `*.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 |
181
178
  | no `harness-version` in `structure.md` | stamp it (Step B4) |
179
+ | `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 |
180
+ | docs carrying a retired `applies:` or `keywords:` key, or a file-level `owns:` list | **remove the retired key; reduce `owns:` to its region globs.** Mechanical: a doctrine the removed `applies:` named stays named in the document's Scope; the files a reduced `owns:` dropped are reported where their headers name no document (`documentation` skill, `references/frontmatter.md`) |
181
+
182
+ **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.
183
+
184
+ ### Step B2b — Converge documents to their genre's current schema
185
+
186
+ 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.
187
+
188
+ **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.
189
+
190
+ **The two natures split the treatment, and the split is absolute.**
191
+
192
+ | Nature | Genres | Treatment |
193
+ | --- | --- | --- |
194
+ | **Truth document** | features, ux, architecture, operations, `docs/tests/` checklists, READMEs (never `structure.md`, which the installer owns) | **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 |
195
+ | **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) |
196
+
197
+ **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.
198
+
199
+ **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.
182
200
 
183
201
  ### Step B3 — Present the reconcile plan, then execute
184
202
 
185
203
  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).
186
204
 
187
- 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.
205
+ 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 + `objects:`) 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.
188
206
 
189
- 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**).
207
+ Scaffold missing mandated files **by calling the `documentation` skill for each template** — you orchestrate, the skill supplies templates: `docs/history/CHANGELOG.md`, `docs/history/backlog.md`, `docs/memory/main.md`, `docs/operations/setup.md` (when the repo has a lockfile and a runnable root script; **never a second first-run doc** — propose a rename with a `note:` row if one exists under another name), and the repo-root `README.md` (an outward opening in a user's terms from the charter and vision documents, then orientation and routing filled from the manifests, the ownership index and the root scripts — **never overwrite an existing root README**).
190
208
 
191
209
  ### Step B4 — Stamp the version
192
210
 
193
211
  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).
194
212
 
213
+ **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.
214
+
195
215
  ### Step B5 — Module proposal
196
216
 
197
217
  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.
198
218
 
199
- - Propose the derived contexts as candidate `module:` labels. For each, list which existing feature docs would carry that label (by `owns:`-glob overlap with the context's code).
200
- - **Propose — don't force.** Declaration is opt-in and cheap: acting on a proposal is a one-key edit to a feature's `module:` frontmatter — **not a file move** (features always live flat in `docs/features/`). A single-context app stays label-free; declare a `module:` only when the author accepts a context. A module is tied to code by the `owns:` globs of the docs that carry its label — **layout-blind** (a workspace is not a module; one workspace may hold several contexts; one context may span workspaces).
219
+ - Propose the derived contexts as candidate `module:` labels. For each, list which existing feature docs would carry that label (by which of the context's files name them in their headers, or fall under their transitional globs).
220
+ - **Propose — don't force.** Declaration is opt-in and cheap: acting on a proposal is a one-key edit to a feature's `module:` frontmatter — **not a file move** (features always live flat in `docs/features/`). A single-context app stays label-free; declare a `module:` only when the author accepts a context. A module is tied to code through the documents that carry its label — the files their headers and transitional globs reach — **layout-blind** (a workspace is not a module; one workspace may hold several contexts; one context may span workspaces).
201
221
 
202
222
  ### Step B6 — Compact the agent memory
203
223
 
@@ -216,6 +236,9 @@ Apply the **§ Strategy memory** retirement procedure to `docs/memory/maintenanc
216
236
  - **Enforce the memory cap on every run** (§ Strategy memory) — you are its only writer, so no one else can do it.
217
237
  - **A clean report is valid** — do not invent issues.
218
238
  - **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.
239
+ - **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: `objects:` · a transitional `owns:` region glob · `module:` · `status:` · `last-reviewed:` · `type: feature` · `derivation: "extracted (baseline YYYY-MM-DD, nustack graph + source read)"`), plus the architecture, tests, and operations layers. Use the `nustack-graph` 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.
240
+ - **Diagrams ride the baseline (operator-ruled 2026-08-24).** Whenever baseline mode produces or converges the architecture layer, draw two ASCII diagrams from the real source, each in its own fenced code block: (1) an **architecture diagram** in the system-design/architecture doc — the actual processes, entry points, and wire surfaces as boxes, the calls/queues/files between them as labelled arrows; (2) a **DB ER diagram** wherever the repo declares a relational schema (DDL, migrations, an ORM schema) — one box per table listing its key columns, FK edges drawn to the table they reference, cardinality on the edge; it lands in the domain-model doc, or `docs/architecture/data-model.md` when none exists. Box-drawing characters (`┌─┐│└┘├┤`), width ≤ 100 columns. Derived only from what the code declares — a diagram never invents a component the source does not show, and a repo with no relational schema gets **no** ER diagram rather than an imagined one. Diagrams are truth-document sections: later baselines and audits converge them in place like any other section.
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: nustack 0.9.0`): use it — localize every question through `nustack graph` before reading source, and never run an index step first: the family re-indexes a stale store before it answers, so the only freshness decision left is the deliberate `--no-refresh`. **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: nustack 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.
219
242
  - **One sentinel.** `docs/structure.md` `harness-version` is the only baseline/version marker.
220
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.
221
244
  - **You orchestrate docs; the `documentation` skill supplies templates.** It is a library — never expect it to baseline.
@@ -9,10 +9,14 @@ You are a FINDER in a truth audit. You receive the path of a **context pack** an
9
9
 
10
10
  Docs state the target design; code is presumed correct unless a standing decision **names** it for retirement — the pack's precedent section decides, and a match means the precedent names your subject (quote the naming words into evidence; a resembling thing is not a match; unsure → QUESTION).
11
11
 
12
- 1. Read the pack in full. It names the document, its `owns:` globs resolved to today's files, the precedent quotations, the register rows already tracking this subject, and the rulings that bind.
12
+ 1. Read the pack in full. It names the document, the files it speaks for (the source files whose headers name it, and its transitional `owns:` region globs) resolved to today's files, the precedent quotations, the register rows already tracking this subject, and the rulings that bind.
13
13
  2. Read the document once, whole — it is the one file you always read in full. Then check each claim it makes against the code: grep for the mechanism, open the passage with a line range, cite `file:line`.
14
14
  3. Write one row per claim to the partial file, **no header**, exactly ten pipe-separated fields: `id|subject|claim|evidence|counter_evidence|verdict|confidence|depends_on|status|resolution`. A literal `|` becomes `¦`, a newline `; `. Verdicts: `COHERENT` (emit at the unit you audited, so coverage is countable) · `MISDESCRIBED` · `UNDOCUMENTED` · `UNBUILT` · `RESIDUE` · `CONTESTED` · `QUESTION`. `id` is `{doc-stem}-NNN`; `status` is `open`; `resolution` is empty; `confidence` is an honest 0–1.
15
- 4. Sweep the other direction once: code inside the `owns:` globs that no claim in the document covers is an `UNDOCUMENTED` row — after the precedent test.
15
+ 4. Sweep the other direction once: code in the files the document speaks for that no claim in the document covers is an `UNDOCUMENTED` row — after the precedent test.
16
+ 5. Sweep the comments in the files the document speaks for once, against the shape in the repo's `CLAUDE.md` § File Structure — the authority, never restated by you. A comment is not a claim about the code, it is a claim *in* the code, and it is the one artifact nothing else audits. Two things come out of this sweep, and a claim worth keeping produces **both**:
17
+ - **The claim, as an `UNDOCUMENTED` row against the document** — design rationale, the alternatives weighed, the measurement behind a constant, the reason an obvious-looking edit is wrong. The applier folds it into the doc, which is what makes cutting the comment safe later. Cite the comment's `file:line` as evidence and state the claim in the document's voice, not the comment's.
18
+ - **The comment itself, as a code-side row** naming the file and the lines — `MISDESCRIBED` where it states something the code does not do, `RESIDUE` where it is history, a ticket or decision-record reference, a dated note, a divider, a per-field block, or a restatement of what the doc now carries. Code-side rows leave as register rows; **you never edit a comment**, whatever the row says.
19
+ Shape breaches are one `RESIDUE` row per file, counted rather than enumerated: no path header, a header naming a path other than its own, a header past three sentences, a function comment past two. **The exception you must not report is the booby trap** — a comment whose subject is a danger on an edit that looks safe, naming the version, measurement, or condition that makes it true. It earns its length where it sits.
16
20
 
17
21
  Rules that bind every row:
18
22
 
@@ -13,7 +13,7 @@ Two kinds of target, one sequence:
13
13
 
14
14
  | Target | Resolves to | Ends with |
15
15
  | --- | --- | --- |
16
- | A doc, a feature, a folder of docs, or a code path | the named doc; `docs/features/{feature}.md`; every doc in the folder; the docs whose `owns:` globs match the path | the target corrected, code-side rows registered, verdict counts reported |
16
+ | A doc, a feature, a folder of docs, or a code path | the named doc; `docs/features/{feature}.md`; every doc in the folder; the doc the path's source header names, else the docs whose transitional `owns:` globs match | the target corrected, code-side rows registered, verdict counts reported |
17
17
  | A plan (`docs/history/plans/…`) | every Doc Obligations row discharged **updated** plus every `Spec:` doc, with the plan's Proposed Changes paths as the code scope | the same, plus the plan's `Polygraph:` segment (§ Auditing a plan) |
18
18
 
19
19
  References — read the routed file in full at the named moment. They live beside the `/polygraph` command: `.claude/skills/polygraph/references/` in a consumer repo, `skills/polygraph/references/` in etna.
@@ -35,9 +35,9 @@ References — read the routed file in full at the named moment. They live besid
35
35
 
36
36
  ## The run
37
37
 
38
- 1. **Resolve the targets.** A doc path is a target; a feature name resolves to `docs/features/{feature}.md`; a folder yields every doc in it; a code path resolves through the ownership index in the repo's session context — or, when none was handed to you, by scanning the `owns:` frontmatter under the docs root — to the docs whose `owns:` globs match; a plan resolves as the table above says. A code path with no owner is **reported, never refused** — say which paths are unowned and go on with the rest. A target with no document at all is the one refusal: there is nothing to audit.
38
+ 1. **Resolve the targets.** A doc path is a target; a feature name resolves to `docs/features/{feature}.md`; a folder yields every doc in it; a code path resolves through the ownership index in the repo's session context — or, when none was handed to you, by reading the path's source header and, failing that, the transitional `owns:` globs under the docs root — to the docs that speak for it; a plan resolves as the table above says. A code path with no owner is **reported, never refused** — say which paths are unowned and go on with the rest. A target with no document at all is the one refusal: there is nothing to audit.
39
39
  2. **Price it and say so** in your report's first line when the target list exceeds ten documents, and stop there before dispatching when the ask did not already accept that size: per-doc cost at the pinned tier, the wave count under the runtime's concurrency ceiling, and the total. An owner directive to run on a stronger tier is priced the same way — state the multiplier and name the directive so the changelog entry records it.
40
- 3. **Write one context pack per doc** (`references/dispatch.md` § The context pack) — the doc's `owns:` globs, the code paths in question, the precedent quotations that name this subject, the register rows already tracking it, and the owner rulings that bind. The pack is the agent's whole reading list.
40
+ 3. **Write one context pack per doc** (`references/dispatch.md` § The context pack) — the files it speaks for (the source files whose headers name it, and its transitional `owns:` region globs), the code paths in question, the precedent quotations that name this subject, the register rows already tracking it, and the owner rulings that bind. The pack is the agent's whole reading list.
41
41
  4. **Dispatch one `polygraph-finder` per doc**, in waves under the concurrency ceiling. Each writes its partial to the run folder and returns at most 120 words. A finder that returns **partial** at its turn cap is resumed once from its partial on disk; a second partial is accepted as that doc's sweep and reported as such.
42
42
  5. **Merge and check.** Concatenate the partials into `findings.psv`, quarantine malformed lines into `malformed.txt` (kept, never silently repaired), run `scripts/audit.mjs {run}` (beside the references) and fix what it reports by hand.
43
43
  6. **Judge.** Read every non-COHERENT row's evidence yourself. CONTESTED rows you judge with both sets of evidence in hand; a row you doubt goes to a `polygraph-verifier` (claim and subject only) and comes back CONTESTED if the two disagree. QUESTION rows — and every proposed deletion, with its inbound count — go into your report **in full**, batched and ranked by how many rows each answer unblocks; work proceeds on everything else while they are open.
@@ -47,7 +47,7 @@ References — read the routed file in full at the named moment. They live besid
47
47
 
48
48
  ## Auditing a plan
49
49
 
50
- When the target is a plan, the docs are the rows its Doc Obligations ledger discharged **updated** plus every `Spec:` doc, and the code scope is the plan's Proposed Changes paths together with those docs' `owns:` globs. The run is the same; it ends by writing your own segment of the plan's **`Gates:`** line — appended when the line carries none — and nothing else on the plan:
50
+ When the target is a plan, the docs are the rows its Doc Obligations ledger discharged **updated** plus every `Spec:` doc, and the code scope is the plan's Proposed Changes paths together with the files those docs speak for (the source files whose headers name them, and their transitional `owns:` region globs). The run is the same; it ends by writing your own segment of the plan's **`Gates:`** line — appended when the line carries none — and nothing else on the plan:
51
51
 
52
52
  - `Polygraph: passed YYYY-MM-DD (N docs · N claims · N corrected · N registered)` — every target swept, every non-QUESTION row resolved.
53
53
  - `Polygraph: pending` — a QUESTION the owner has not answered is open; the report says which. Never write `passed` over an open question.
@@ -19,7 +19,7 @@ You distill refinement history into documentation and harness feedback. Your inp
19
19
  Classify every finding into exactly one bucket. Work from the entries — recurrence and explicit fields, never vibes.
20
20
 
21
21
  1. **Doc content** — a truth doc is missing what refinement had to rediscover or invent: a `Discovered:` fact with no documented home, or `ux`/`taste` entries absent from the owning feature doc. Name the doc, the missing content, the evidencing entries.
22
- 2. **Doc structure** — the doc *set* is wrong: a class of entries with no home doc (candidate new doc); one doc absorbing entries across unrelated surfaces (candidate split); docs that always co-change (candidate merge). Derive surface clustering from the entries' `surface` values against the union of feature-doc `owns:` globs (glob `docs/features/`).
22
+ 2. **Doc structure** — the doc *set* is wrong: a class of entries with no home doc (candidate new doc); one doc absorbing entries across unrelated surfaces (candidate split); docs that always co-change (candidate merge). Derive surface clustering from the entries' `surface` values against the files each feature doc speaks for (source headers naming it, and its transitional `owns:` region globs; glob `docs/features/`).
23
23
  3. **Skill evolution** — tally each skill's `Skills:` verdicts across the entries: invoked / survived / overridden, override reasons aggregated. A skill overridden in most of its invocations is a finding. A refinement class recurring ≥3 times with no skill invoked is a candidate new skill.
24
24
  4. **Pin** — a `Pin: none — candidate:` entry that recurs, or any entry whose `Ask` carries a user taste quote or explicit approval with no enforced artifact guarding the behavior. Output the exact pin (test, token, guardrail, schema) and where it goes.
25
25
  5. **Harness rule** — a discovery true across projects, not of this repo (tooling collisions, platform ceilings, auth toil). Route to the shared rule file it belongs in.
@@ -16,10 +16,10 @@ docs/
16
16
  │ # own notes. Describes directories, never files or a tree — `ls` is the roster
17
17
  ├── structure.md # This file — harness-owned authority map; `harness-version` stamped after baseline
18
18
  ├── architecture/ # The design layer, in four folders; Readme.md is its summary and names folders only
19
- │ ├── packages/ # One `{package}.md` per project package: what it is, how it behaves. Carries
20
- │ │ # `owns:` over `packages/<pkg>/**` RESIDUALLY — whatever no system or feature doc claims
21
- │ ├── systems/ # One `{machine}.md` per machine spanning packages. Carries `owns:` over its
22
- │ │ # subtrees; more specific than a package's glob
19
+ │ ├── packages/ # One `{package}.md` per project package: what it is, how it behaves. Speaks
20
+ │ │ # RESIDUALLY for `packages/<pkg>/**` — every file no narrower document speaks for
21
+ │ ├── systems/ # One `{machine}.md` per machine spanning packages. Speaks for its subtrees,
22
+ │ │ # more specific than a package's claim
23
23
  │ ├── common/ # The enterprise plane — one `{concern}.md` per concern the platform handles
24
24
  │ │ # the same way everywhere. Owns no code
25
25
  │ └── decisions/ # ADs — `AD-NNN-{slug}.md`, one per subject, updated in place; the journey lives in
@@ -27,7 +27,7 @@ docs/
27
27
  │ # decision per subject and whether it is built
28
28
  ├── features/ # Feature docs — `{feature}.md`, one per thing a person points at (an address, a command
29
29
  │ # family, a named control), cut by its objects (flat, no subfolders). One of three owning genres, beside
30
- │ # `architecture/packages/` and `architecture/systems/`; ownership is `owns:` frontmatter
30
+ │ # `architecture/packages/` and `architecture/systems/`; ownership is computed from `objects:` and source headers
31
31
  ├── acceptance/ # Acceptance criteria — `{capability}.md`, stable `AC-n` assert lines. Owns no code
32
32
  ├── operations/ # Procedures — `setup.md` (the one-time first run, incl. the stack), then development, deployment, signing, rotation, diagnostics;
33
33
  │ │ # one topic-named `{procedure}.md` each. Also holds bookkeeping.md, the shared
@@ -61,19 +61,19 @@ docs/
61
61
  ## Doctrine every genre follows
62
62
 
63
63
  - **`docs/architecture/`** is the design layer in four folders: **`packages/`** (one per project package — what it is and how it behaves), **`systems/`** (one per machine spanning packages), **`common/`** (the enterprise plane) and **`decisions/`** (the ADs, one per subject, updated in place, each journey confined to that file's Decision Changelog tail).
64
- **`common/` and `decisions/` own no code; `packages/` and `systems/` do.** Ownership is a frontmatter fact, not a folder fact: it is the union of every `owns:` glob in `features/`, `architecture/packages/` and `architecture/systems/`, and the more specific glob wins — a feature's beats a system's, a system's beats a package's. **A package document is the residual owner of its tree**, so a package's exhaustive behavior spec is that document, not a feature doc.
64
+ **`common/` and `decisions/` own no code; `packages/` and `systems/` do.** Ownership is computed, not positional and not typed: a document names the objects it designs in `objects:`, each source file's path header names the document that speaks for it, and the object closure links the two; the more specific claim wins — a feature's beats a system's, a system's beats a package's. Until the ownership scan switches to those edges it reads a transitional `owns:` region glob on each of the three owning genres (the `documentation` skill's `references/frontmatter.md`). **A package document is the residual owner of its tree**, so a package's exhaustive behavior spec is that document, not a feature doc.
65
65
  **What earns which genre.** A project package earns a `packages/` document — one, always. A machine whose parts sit in several packages, which a reader asks about as one thing, earns a `systems/` document. A **feature** document exists for something a person points at — an address, a command family, a named control — and is cut by the objects it designs, never by the code's routers, tables, plans or packages; a machine nobody navigates to is a `systems/` document even when it owns tables, and a fragment whose central object is declared in a sibling merges into it. A concern handled the same way everywhere is `common/` and owns nothing — including the governing-decisions register (fixed product decisions `F-###`, baseline defaults `B-###`, external boundaries, out-of-scope), which is authored doctrine the `boundary` skill never persists to.
66
66
  - **`history/research/` is not legacy — it is the installed deep-research genre**, one `{topic}/` folder per research pass in the three-file shape. A record is frozen and cited, owns no code, and is never edited to stay current; a new question gets a new topic folder. Entries that predate the three-file convention — a single file or a single-report folder — are kept as authored.
67
- - **`docs/features/` is flat — every feature authority is a file, and no subfolder exists.** A subject too large for one file is two files, not a folder: the `owns:` frontmatter already states authority, and a nested doc is one the ownership scan and the index rows reach last.
67
+ - **`docs/features/` is flat — every feature authority is a file, and no subfolder exists.** A subject too large for one file is two files, not a folder: `objects:` and the source headers already state authority, and a nested doc is one the ownership scan and the index rows reach last.
68
68
  - **`docs/research/`** holds records, not authorities: its standing-research folders and loose notes ground plans and decisions, and its `Readme.md` lists every folder so the `brainstorm` skill reads it instead of scanning.
69
69
  - **`docs/tests/`** holds behavioral test checklists — one `{feature}.md` per surface, written as `- [ ] step` → `- [ ] **Assert**` (how to verify a feature's promised behavior). A checklist is written or updated **at the verification gate, before the driving** — it is the script the real surface is driven against and the menu the pinned suite tests are drawn from, so each behavior section closes with a `> Pinned:` line naming the test that guards it, `driven only` with the reason, or `none yet` with the test it owes. **No section carries a date** — the *event* of driving (its date, environment, evidence, a `FAIL` and the backlog item it raised) belongs to the changelog entry of the unit that drove it. A section a drive cannot reach says `not reachable — {what blocks it}` on its trace line until the blocker is gone. **The gate that drives a checklist maintains it** — retiring dead sections and repairing traces is part of the run, because a checklist owns no code and no ownership glob implicates it. The cross-system testing strategy is doctrine in `architecture/common/`. A checklist carries `type: checklist` plus `verifies:`, the doc(s) whose promises it proves; a target is legitimately a feature, a package or a system document, and several values are correct where one checklist proves promises two docs decide.
70
70
  **`verifies:` is the load-bearing key, and the filename convention is not a substitute for it.** A same-named file is a coincidence no resolver can trust — it breaks the moment a checklist spans two docs or a feature is renamed. The inverse — which checklists cover a feature — is **generated, never listed in the feature doc**.
71
71
  - **`docs/operations/bookkeeping.md`** is the shared doc-sync work queue — hooks append rows, the `bookkeeper` agent drains them, and the commit wall blocks while rows remain. It is **author-created, never scaffolded**, and its rows are **live work, never drift**: a drained row is deleted, so a fully drained file holds no rows at all. Read a waiver's referent before deleting it; deleting the record while the condition survives re-appends the finding as an `auto:` row that fails the wall the waiver passed.
72
72
  - **`type:` is a required frontmatter key** on every doc that carries frontmatter — the genre stamp, its enum being the `documentation` skill's template names (`feature`, `architecture-decision`, `project-memory`, `ux`, `acceptance-criteria`, …). It is tooling metadata like `last-reviewed:` and `status:`: external tooling reads it, sessions **preserve it on edits** and never narrate it in the body. The `maintenance` agent stamps it on docs that lack it.
73
- **No genre is frontmatter-less by policy; the gaps are the templates.** `architecture/common/**` carries `type: doctrine` with `status:` and `objects:` and no `owns:`, from the documentation skill's doctrine template. `architecture/Readme.md` carries no block, because its template ships none and a block is never manufactured on a doc whose template has none. `architecture/packages/**` and `architecture/systems/**` carry `type: package` / `type: system` with `objects:`, `owns:`, `status:` and optional `applies:`, because those genres own code and the ownership scan reads frontmatter. The `operations/` procedures carry a hand-authored `type: operations` block; the bookkeeping queue carries none. Docs written by a skill that ships a block (`issue-bridge`'s `type: issue` / `type: triage`) carry it.
74
- - **The docs tree is a graph, and every edge is declared at the consuming end.** Three frontmatter keys carry it: `owns:` (doc → code), **`verifies:`** (checklist → the doc whose promises it proves), and **`applies:`** (a feature, package or system doc → the doctrine it is *bound by* — an obligation, deliberately not a bibliography, so a key that collected everything consulted would stop meaning anything). Every inverse is **generated** — `buildEdgeIndex()` in `.claude/hooks/lib/ownership.mjs`, injected at SessionStart beside the ownership index — and authoring one by hand is a defect: a parent that hand-lists its children doubles the staleness surface instead of halving it. `check-ownership` fails on an edge naming a doc that does not exist, waivable as `dangling-edge`. **`applies:` is reported, never inferred** — an obligation is an assertion only the consuming doc's author can make.
73
+ **No genre is frontmatter-less by policy; the gaps are the templates.** `architecture/common/**` carries `type: doctrine` with `status:` and `objects:` and no `owns:`, from the documentation skill's doctrine template. `architecture/Readme.md` carries no block, because its template ships none and a block is never manufactured on a doc whose template has none. `architecture/packages/**` and `architecture/systems/**` carry `type: package` / `type: system` with `objects:` and `status:`, plus the transitional `owns:` region glob the ownership scan reads until it switches to source headers. The `operations/` procedures carry a hand-authored `type: operations` block; the bookkeeping queue carries none. Docs written by a skill that ships a block (`issue-bridge`'s `type: issue` / `type: triage`) carry it.
74
+ - **The docs tree is a graph, and nothing in it that can be computed is typed.** A document names the objects it designs (`objects:`); a source file names the document that speaks for it in its path header; the object closure computes the rest — which files a document speaks for, and which surfaces a doctrine governs. One doc → doc edge is authored, because nothing computes it: **`verifies:`** (checklist → the doc whose promises it proves). Its inverse is **generated** — `buildEdgeIndex()` in `.claude/hooks/lib/ownership.mjs`, injected at SessionStart beside the ownership index — and authoring an inverse by hand is a defect. `applies:` is retired; `owns:` is a transitional region glob (the `documentation` skill's `references/frontmatter.md`).
75
75
  - **`docs/acceptance/` holds the acceptance-criteria genre** — one `kebab-case.md` per capability, written at specification time, before implementation, holding stable `AC-n` assert lines that verification surfaces parse as claims to grade the built product against; each file is keyed to the checklist sections that grade it. The folder is created on first use and never scaffolded. The `AC-n:` line grammar is load-bearing: the key sits at line start, exactly `AC-<number>:`, followed by the assert sentence — tables, emphasis-wrapped keys, and nested bullets break the parse.
76
- - **The `ux` genre decides observable behavior; `docs/tests/` only verifies it.** A feature doc states what a feature is and which contracts it honours — all of which stay plausibly true through a rewrite of the surface — so a UX-behavior doc states what a person *observes*, and is written **before** the code. This repo holds no standalone `ux` document: what a person observes on a surface is a section of the feature doc that owns that surface's components and wire. A surface needing the genre gets `features/{surface}-ux.md` with real `owns:` over its unclaimed component globs — an `owns: []` doc nothing implicates is one no change reaches.
76
+ - **The `ux` genre decides observable behavior; `docs/tests/` only verifies it.** A feature doc states what a feature is and which contracts it honours — all of which stay plausibly true through a rewrite of the surface — so a UX-behavior doc states what a person *observes*, and is written **before** the code. This repo holds no standalone `ux` document: what a person observes on a surface is a section of the feature doc that owns that surface's components and wire. A surface needing the genre gets `features/{surface}-ux.md`, named in the headers of the component files no other document speaks for; where a feature document already speaks for them, the UX document is named in its coordinated-edits row.
77
77
  - **`docs/history/journal.md` is the temporary lane, and `docs/memory/` is not.** The journal holds mid-execution facts one line each, each resolved by landing in its owning doc; memory holds only per-agent strategy and project knowledge no single doc owns. A fact an owning doc will eventually carry goes to the journal, never to memory — memory has no drain, so what lands there stays until somebody distils it.
78
78
  - **There is no `docs/history/sessions/`.** Per-session gate logs are machine-local, written by `.claude/hooks/lib/session-log.mjs` into `.claude/state/sessions/` (self-ignored), so they never enter `docs/` or git. A `docs/history/sessions/` folder is residue, not shape.
79
79
  - **The repo-root `README.md` opens outward and turns inward.** What the product does, for whom and where it runs, in a user's terms; then, for the team and agents, how it works, a Map of top-level units with their owning doc, the rules that bite, where to go by intent, and at most four everyday commands. It carries no install, stack or tree — install and stack are `operations/setup.md`, the tree is this file's. Template: the `documentation` skill's `templates/root-readme.md`.
@@ -99,17 +99,17 @@ docs/
99
99
 
100
100
  | You are adding… | It goes in… | Needs frontmatter? |
101
101
  | --- | --- | --- |
102
- | A feature document — something a person points at (an address, a command family, a named control), cut by the objects it designs | `features/` (flat) | Yes — `objects:`, `owns:`, `status:`, `type: feature` (+ optional `module:`, `applies:`, `last-reviewed:`) |
103
- | A package's behavior document | `architecture/packages/<package>.md`, one per package | Yes — `type: package`, `objects:`, `owns:` (its tree, residually), `status:`, optional `applies:` |
104
- | A system's behavior document | `architecture/systems/{system}.md`, one per machine | Yes — `type: system`, `objects:`, `owns:` (the machine's subtrees), `status:`, optional `applies:` |
105
- | Cross-cutting design / steering | `architecture/common/` | No |
102
+ | A feature document — something a person points at (an address, a command family, a named control), cut by the objects it designs | `features/` (flat) | Yes — `type: feature`, `objects:`, `status:` (+ optional `module:`, `last-reviewed:`; a transitional `owns:` region glob) |
103
+ | A package's behavior document | `architecture/packages/<package>.md`, one per package | Yes — `type: package`, `objects:`, `status:` (+ the transitional `owns:` over its tree, residually) |
104
+ | A system's behavior document | `architecture/systems/{system}.md`, one per machine | Yes — `type: system`, `objects:`, `status:` (+ the transitional `owns:` over the machine's subtrees) |
105
+ | Cross-cutting design / steering (doctrine) | `architecture/common/` | Yes — `type: doctrine`, `objects:`, `status:` |
106
106
  | An Architecture Decision (AD) | `architecture/decisions/` — one file per subject, updated in place; the journey lives in that file's Decision Changelog | Yes — `type: architecture-decision` (+ `title:`, `status:`, `created:`, `last-updated:`, `decision-owner:`, `tags:`) |
107
107
  | One-time machine setup, first run, stack, prerequisites, env files | `operations/setup.md` — fixed name, one per repo, linked from the root README; a diverging package gets a `### {unit}` subsection, never a second first-run doc | Yes — `type: operations` |
108
108
  | A recurring procedure (develop, deploy, rotate, diagnose, CI) | `operations/{procedure}.md` | Yes — `type: operations` |
109
109
  | A tracker issue held in the repo | `operations/issues/YYYY-MM-DD-{slug}.md` (via the `issue-bridge` skill's local pack) | Yes — `type: issue`, `id:`, `status:`, `labels:`, `author:`, `created:`, `github:` |
110
110
  | A triage dossier for an investigated issue | `operations/triage/YYYY-MM-DD-{slug}.md` (via the `issue-bridge` skill) | Yes — `type: triage`, `tracker:`, `issue:`, `date:`, `confidence:`. A dated working artifact: it never claims `owns:` and never masquerades as a feature doc |
111
111
  | A behavioral test checklist | `tests/{feature}.md` | Yes — `type: checklist`, `verifies:` (the doc(s) whose promises it proves; several values are legitimate). No `owns:` — it owns no code. Truth doc, present-tense; written at the verification gate before the driving, each section carrying a `Pinned:` trace |
112
- | What a person sees and can do on a surface (UX behavior) | `features/{surface}-ux.md` with `owns:` over that surface's unclaimed component globs; a section of the owning feature doc where the surface has one owner; `architecture/common/` when it is cross-cutting | Yes — `type: ux` (+ the feature-doc keys when it lives in `features/`) |
112
+ | What a person sees and can do on a surface (UX behavior) | `features/{surface}-ux.md`, named in the headers of the component files no other document speaks for; a section of the owning feature doc where the surface has one owner; `architecture/common/` when it is cross-cutting | Yes — `type: ux` (+ the feature-doc keys when it lives in `features/`) |
113
113
  | Acceptance criteria for one capability | `acceptance/{capability}.md` — stable `AC-n` assert lines verification surfaces parse as claims; keys are never renumbered or reused, a retired criterion is marked retired in place | Yes — `type: acceptance-criteria`, `status:`; no `owns:` (owns no code) |
114
114
  | An in-progress brainstorm session | `research/{topic}-brainstorm/` (via the `brainstorm` skill) | No |
115
115
  | A source-of-truth audit | Nowhere in `docs/` — the `polygraph` skill (audit mode) corrects the target doc in place and keeps its rows under `.claude/state/polygraph/{run}/`, deleted with the report | No |
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "3.19.6",
4
+ "version": "3.20.1",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -29,5 +29,5 @@
29
29
  "origin": "https://cdn.nustack.io",
30
30
  "track": "latest"
31
31
  },
32
- "digest": "71c048868428"
32
+ "digest": "f52d24285ab6"
33
33
  }
@@ -219,11 +219,12 @@ export function buildIndex(projectDir) {
219
219
  }
220
220
 
221
221
  // Scan every doc that declares a doc→doc edge and return the edges, resolved.
222
- // Shape: { from, kind ("verifies"|"applies"), to, missing (the target file is absent) }.
222
+ // Shape: { from, kind ("verifies"), to, missing (the target file is absent) }.
223
223
  //
224
- // EVERY EDGE IS DECLARED AT THE CONSUMING END — a checklist names what it verifies, a feature names
225
- // the doctrine it obeys — so this walks the consumers and never looks for a hand-authored inverse.
226
- // A doc listing its own dependents is a defect the graph is built to make unnecessary.
224
+ // EVERY EDGE IS DECLARED AT THE CONSUMING END — a checklist names what it verifies — so this walks
225
+ // the consumers and never looks for a hand-authored inverse. `applies:` is a retired key and is not
226
+ // read: a surface's governing doctrine is computed through the objects both documents name
227
+ // (documentation skill, references/frontmatter.md; knowledge-layer § Documents and code).
227
228
  export function scanDocEdges(projectDir) {
228
229
  const docsDir = path.join(projectDir, docsRoot(projectDir));
229
230
  const files = listMarkdown(docsDir).filter((f) => !f.includes(".bkp."));
@@ -238,7 +239,7 @@ export function scanDocEdges(projectDir) {
238
239
  const block = frontmatter(text);
239
240
  if (!block) continue;
240
241
  const from = path.relative(projectDir, file).split(path.sep).join("/");
241
- for (const kind of ["verifies", "applies"]) {
242
+ for (const kind of ["verifies"]) {
242
243
  for (const to of asList(field(block, kind))) {
243
244
  const target = to.toString().trim();
244
245
  if (!target) continue;
@@ -254,11 +255,11 @@ export function scanDocEdges(projectDir) {
254
255
  return edges;
255
256
  }
256
257
 
257
- // Build the inverse-edge index: what each doc is verified by, and what obeys it.
258
+ // Build the inverse-edge index: what each doc is verified by.
258
259
  //
259
- // THE INVERSE IS GENERATED, NEVER AUTHORED. Read this to answer "if I change this doctrine, what
260
- // is bound by it?" and "if I change this surface, which checklist proves it?" — the two questions
261
- // a prose link cannot answer, and the reason the edges are frontmatter rather than sentences.
260
+ // THE INVERSE IS GENERATED, NEVER AUTHORED. Read this to answer "if I change this surface, which
261
+ // checklist proves it?" — a question a prose link cannot answer, and the reason the edge is
262
+ // frontmatter rather than a sentence.
262
263
  // A doc with no inbound edge is reachable by nothing: no change implicates it, so it rots without
263
264
  // the rot being detectable. The footer counts those, because that count IS the health metric.
264
265
  export function buildEdgeIndex(projectDir) {
@@ -272,17 +273,15 @@ export function buildEdgeIndex(projectDir) {
272
273
  const lines = [];
273
274
  for (const [to, list] of [...inverse].sort((a, b) => a[0].localeCompare(b[0]))) {
274
275
  const verifiedBy = list.filter((e) => e.kind === "verifies").map((e) => e.from);
275
- const appliedBy = list.filter((e) => e.kind === "applies").map((e) => e.from);
276
276
  const parts = [];
277
277
  if (verifiedBy.length) parts.push(`verified by ${verifiedBy.sort().join(", ")}`);
278
- if (appliedBy.length) parts.push(`applied by ${appliedBy.sort().join(", ")}`);
279
278
  const absent = list.some((e) => e.missing) ? " ← TARGET MISSING" : "";
280
279
  lines.push(`${to} ← ${parts.join(" · ")}${absent}`);
281
280
  }
282
281
  const dangling = edges.filter((e) => e.missing).length;
283
282
  const header =
284
- "# Doc edge index (generated) — doc ← who verifies it · who is bound by it\n" +
285
- "# Declared once at the consuming end (verifies:/applies: frontmatter); this inverse is derived.\n" +
283
+ "# Doc edge index (generated) — doc ← who verifies it\n" +
284
+ "# Declared once at the consuming end (verifies: frontmatter); this inverse is derived.\n" +
286
285
  "# Before changing a doc, read what points AT it — those are the docs the change implicates.\n";
287
286
  const footer = dangling > 0 ? `\n# (${dangling} edge(s) name a doc that does not exist — fix the frontmatter)` : "";
288
287
  return header + lines.join("\n") + footer;
@@ -100,7 +100,7 @@ export const ROW_VERIFIERS = {
100
100
  // silently swept a waived row whose plan was still open.
101
101
  //
102
102
  // NOT FRONTMATTER EITHER, and that follows from the harness's own doctrine rather than taste:
103
- // `type:`/`owns:`/`applies:` and their siblings are tooling metadata, "preserved on edits, never
103
+ // `type:`/`objects:`/`owns:` and their siblings are tooling metadata, "preserved on edits, never
104
104
  // narrated" — a key that by definition states nothing about behavior cannot answer a claim that
105
105
  // documented behavior may have moved. So the bodies are compared with the frontmatter stripped.
106
106
  //