@nurix/etna 0.20.29 → 0.20.31
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/harness-index.json +1 -1
- package/data/harnesses/code/CLAUDE.md +4 -4
- package/data/harnesses/code/agents/bookkeeper.md +2 -2
- package/data/harnesses/code/agents/change-review.md +3 -3
- package/data/harnesses/code/agents/lld-mode.md +3 -3
- package/data/harnesses/code/agents/maintenance.md +22 -19
- package/data/harnesses/code/agents/polygraph-finder.md +3 -3
- package/data/harnesses/code/agents/polygraph.md +4 -4
- package/data/harnesses/code/agents/retro.md +1 -1
- package/data/harnesses/code/codex/agents/bookkeeper.toml +7 -3
- package/data/harnesses/code/codex/agents/change-review.toml +3 -3
- package/data/harnesses/code/codex/agents/lld-mode.toml +3 -3
- package/data/harnesses/code/codex/agents/maintenance.toml +60 -37
- package/data/harnesses/code/codex/agents/polygraph-finder.toml +6 -2
- package/data/harnesses/code/codex/agents/polygraph.toml +4 -4
- package/data/harnesses/code/codex/agents/retro.toml +1 -1
- package/data/harnesses/code/docs/structure.md +15 -15
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/hooks/lib/ownership.mjs +12 -13
- package/data/harnesses/code/hooks/lib/sweep.mjs +1 -1
- package/data/harnesses/code/hooks/mount.mjs +17 -3
- package/data/harnesses/code/skills/documentation/SKILL.md +17 -27
- package/data/harnesses/code/skills/documentation/references/frontmatter.md +56 -62
- package/data/harnesses/code/skills/documentation/references/style-guide.md +2 -2
- package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +1 -1
- package/data/harnesses/code/skills/documentation/templates/Readme.md +1 -1
- package/data/harnesses/code/skills/documentation/templates/docs-readme.md +1 -1
- package/data/harnesses/code/skills/documentation/templates/doctrine.md +2 -2
- package/data/harnesses/code/skills/documentation/templates/feature.md +3 -4
- package/data/harnesses/code/skills/documentation/templates/package.md +2 -3
- package/data/harnesses/code/skills/documentation/templates/root-readme.md +1 -1
- package/data/harnesses/code/skills/documentation/templates/system.md +2 -4
- package/data/harnesses/code/skills/documentation/templates/ux-behavior.md +4 -4
- package/data/harnesses/delivery/agents/lld-mode.md +3 -3
- package/data/harnesses/delivery/agents/maintenance.md +22 -19
- package/data/harnesses/delivery/agents/retro.md +1 -1
- package/data/harnesses/delivery/codex/agents/lld-mode.toml +3 -3
- package/data/harnesses/delivery/codex/agents/maintenance.toml +60 -37
- package/data/harnesses/delivery/codex/agents/retro.toml +1 -1
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/skills/documentation/SKILL.md +17 -27
- package/data/harnesses/delivery/skills/documentation/references/frontmatter.md +56 -62
- package/data/harnesses/delivery/skills/documentation/references/style-guide.md +2 -2
- package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +1 -1
- package/data/harnesses/delivery/skills/documentation/templates/Readme.md +1 -1
- package/data/harnesses/delivery/skills/documentation/templates/docs-readme.md +1 -1
- package/data/harnesses/delivery/skills/documentation/templates/doctrine.md +2 -2
- package/data/harnesses/delivery/skills/documentation/templates/feature.md +3 -4
- package/data/harnesses/delivery/skills/documentation/templates/package.md +2 -3
- package/data/harnesses/delivery/skills/documentation/templates/root-readme.md +1 -1
- package/data/harnesses/delivery/skills/documentation/templates/system.md +2 -4
- package/data/harnesses/delivery/skills/documentation/templates/ux-behavior.md +4 -4
- package/data/harnesses/etna/agents/change-review.md +3 -3
- package/data/harnesses/etna/agents/lld-mode.md +3 -3
- package/data/harnesses/etna/agents/maintenance.md +22 -19
- package/data/harnesses/etna/agents/retro.md +1 -1
- package/data/harnesses/etna/codex/agents/change-review.toml +3 -3
- package/data/harnesses/etna/codex/agents/lld-mode.toml +3 -3
- package/data/harnesses/etna/codex/agents/maintenance.toml +60 -37
- package/data/harnesses/etna/codex/agents/retro.toml +1 -1
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/skills/documentation/SKILL.md +17 -27
- package/data/harnesses/etna/skills/documentation/references/frontmatter.md +56 -62
- package/data/harnesses/etna/skills/documentation/references/style-guide.md +2 -2
- package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +1 -1
- package/data/harnesses/etna/skills/documentation/templates/Readme.md +1 -1
- package/data/harnesses/etna/skills/documentation/templates/docs-readme.md +1 -1
- package/data/harnesses/etna/skills/documentation/templates/doctrine.md +2 -2
- package/data/harnesses/etna/skills/documentation/templates/feature.md +3 -4
- package/data/harnesses/etna/skills/documentation/templates/package.md +2 -3
- package/data/harnesses/etna/skills/documentation/templates/root-readme.md +1 -1
- package/data/harnesses/etna/skills/documentation/templates/system.md +2 -4
- package/data/harnesses/etna/skills/documentation/templates/ux-behavior.md +4 -4
- package/lib/install/mount.mjs +17 -3
- 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
51
|
-
3.
|
|
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
|
|
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
|
-
|
|
58
|
+
Across the header pointers and the transitional globs:
|
|
58
59
|
|
|
59
|
-
-
|
|
60
|
-
-
|
|
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
|
|
79
|
-
- If
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 + `
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
20
|
-
│ │ #
|
|
21
|
-
│ ├── systems/ # One `{machine}.md` per machine spanning packages.
|
|
22
|
-
│ │ #
|
|
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 `
|
|
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
|
|
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:
|
|
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
|
|
74
|
-
- **The docs tree is a graph, and
|
|
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
|
|
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 — `
|
|
103
|
-
| A package's behavior document | `architecture/packages/<package>.md`, one per package | Yes — `type: package`, `objects:`, `owns:`
|
|
104
|
-
| A system's behavior document | `architecture/systems/{system}.md`, one per machine | Yes — `type: system`, `objects:`, `
|
|
105
|
-
| Cross-cutting design / steering | `architecture/common/` |
|
|
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
|
|
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.
|
|
4
|
+
"version": "3.20.2",
|
|
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": "
|
|
32
|
+
"digest": "535a7aa34dfe"
|
|
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"
|
|
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
|
|
225
|
-
// the
|
|
226
|
-
//
|
|
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"
|
|
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
|
|
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
|
|
260
|
-
//
|
|
261
|
-
//
|
|
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
|
|
285
|
-
"# Declared once at the consuming end (verifies
|
|
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
|
|
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
|
//
|
|
@@ -206,7 +206,7 @@ function removeEmptyParents(projectDir, rel) {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
//
|
|
209
|
+
// Fetches the manifest's mounted files that differ on disk, writes them deepest path first, then deletes owned names the manifest omits.
|
|
210
210
|
async function mountFromManifest({ projectDir, manifest, origin, cacheDir, fetchImpl, timeoutMs, priorOwned }) {
|
|
211
211
|
const all = mountedEntries(manifest);
|
|
212
212
|
const entries = admittedEntries(projectDir, all);
|
|
@@ -218,14 +218,28 @@ async function mountFromManifest({ projectDir, manifest, origin, cacheDir, fetch
|
|
|
218
218
|
const dest = join(projectDir, entry.path);
|
|
219
219
|
return !(existsSync(dest) && sha256Of(readFileSync(dest)) === entry.sha256);
|
|
220
220
|
});
|
|
221
|
+
// Booby trap: every live session in the checkout runs these hooks while the mount rewrites them.
|
|
222
|
+
// Writing as each fetch lands let `hooks/compliance.mjs` arrive seconds before a new `hooks/lib/`
|
|
223
|
+
// module it imports, and every hook call in that window died with ERR_MODULE_NOT_FOUND (3.20.1
|
|
224
|
+
// rollout, 2026-10-05). Fetch everything, then write locally, imports (deeper paths) before importers.
|
|
225
|
+
const fetched = [];
|
|
221
226
|
await eachLimited(stale, async (entry) => {
|
|
222
227
|
try {
|
|
223
|
-
|
|
224
|
-
written.push(entry.path);
|
|
228
|
+
fetched.push({ entry, bytes: await readObject({ origin, entry, cacheDir, fetchImpl, timeoutMs }) });
|
|
225
229
|
} catch {
|
|
226
230
|
failed.push(entry.path);
|
|
227
231
|
}
|
|
228
232
|
});
|
|
233
|
+
const depth = (path) => path.split('/').length;
|
|
234
|
+
fetched.sort((a, b) => depth(b.entry.path) - depth(a.entry.path) || a.entry.path.localeCompare(b.entry.path));
|
|
235
|
+
for (const { entry, bytes } of fetched) {
|
|
236
|
+
try {
|
|
237
|
+
writeAtomic(join(projectDir, entry.path), bytes);
|
|
238
|
+
written.push(entry.path);
|
|
239
|
+
} catch {
|
|
240
|
+
failed.push(entry.path);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
229
243
|
for (const rel of prunableFiles(projectDir, unionOwned(manifest.owned, priorOwned))) {
|
|
230
244
|
if (listed.has(rel)) continue;
|
|
231
245
|
rmSync(join(projectDir, rel), { force: true });
|