@nurix/etna 0.14.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -7
- package/bin/etna.js +58 -1
- package/data/harnesses/code/CLAUDE.md +11 -16
- package/data/harnesses/code/agents/brainstorm.md +13 -20
- package/data/harnesses/code/agents/change-review.md +5 -5
- package/data/harnesses/code/agents/harness-manager.md +49 -9
- package/data/harnesses/code/agents/maintenance.md +42 -5
- package/data/harnesses/code/agents/spec-mode.md +1 -1
- package/data/harnesses/code/harness.json +6 -0
- package/data/harnesses/code/hooks/hooks.json +41 -0
- package/data/harnesses/code/hooks/lib/redact.js +37 -0
- package/data/harnesses/code/hooks/post-commit.js +136 -0
- package/data/harnesses/code/hooks/post-tool-failure.js +136 -0
- package/data/harnesses/code/hooks/session-stop.js +207 -0
- package/data/harnesses/code/skills/boundary/SKILL.md +21 -3
- package/data/harnesses/code/skills/boundary/references/boundary-taxonomy.md +229 -161
- package/data/harnesses/code/skills/brainstorm/README.md +76 -227
- package/data/harnesses/code/skills/brainstorm/SKILL.md +78 -566
- package/data/harnesses/code/skills/brainstorm/harvest.md +26 -0
- package/data/harnesses/code/skills/brainstorm/templates/build.md +37 -0
- package/data/harnesses/code/skills/brainstorm/templates/decisions.md +12 -25
- package/data/harnesses/code/skills/brainstorm/templates/domain-model.md +25 -0
- package/data/harnesses/code/skills/brainstorm/templates/non-goals.md +2 -2
- package/data/harnesses/code/skills/brainstorm/templates/questions.md +18 -67
- package/data/harnesses/code/skills/brainstorm/templates/state.md +5 -5
- package/data/harnesses/code/skills/brainstorm/templates/tech.md +23 -0
- package/data/harnesses/code/skills/brainstorm/templates/thesis.md +21 -0
- package/data/harnesses/code/skills/distiller/SKILL.md +218 -0
- package/data/harnesses/code/skills/distiller/references/docs-mode.md +526 -0
- package/data/harnesses/code/skills/distiller/references/method.md +166 -0
- package/data/harnesses/code/skills/documentation/references/templates.md +78 -5
- package/data/harnesses/kb-agi/CLAUDE.md +1 -1
- package/data/harnesses/kb-agi/agents/kb-agi-init.md +2 -2
- package/data/harnesses/kb-agi/agents/kb-catch-stale.md +1 -1
- package/data/harnesses/kb-agi/agents/kb-find-gaps.md +2 -2
- package/data/harnesses/kb-agi/agents/kb-fix-links.md +1 -1
- package/data/harnesses/kb-agi/agents/kb-refresh-frontdoor.md +1 -1
- package/data/harnesses/kb-agi/agents/kb-refresh-summaries.md +1 -1
- package/data/harnesses/kb-agi/agents/kb-sync-metadata.md +1 -1
- package/data/harnesses/kb-agi/harness.json +6 -0
- package/package.json +1 -1
- package/data/harnesses/code/skills/brainstorm/greenfield.md +0 -99
- package/data/harnesses/code/skills/brainstorm/templates/inventory.md +0 -44
package/README.md
CHANGED
|
@@ -16,17 +16,38 @@ After this completes:
|
|
|
16
16
|
|
|
17
17
|
```
|
|
18
18
|
your-project/
|
|
19
|
-
├── CLAUDE.md
|
|
19
|
+
├── CLAUDE.md ← harness policy (your existing file → CLAUDE.md.bak.<ISO>)
|
|
20
20
|
└── .claude/
|
|
21
|
-
├── skills/
|
|
22
|
-
├── agents/
|
|
23
|
-
├── rules/
|
|
24
|
-
|
|
21
|
+
├── skills/ ← documentation, boundary, distiller, brainstorm, workflow-mapper
|
|
22
|
+
├── agents/ ← harness-manager, spec-mode, brainstorm, maintenance, change-review, performance-review
|
|
23
|
+
├── rules/ ← general.md, nomenclature.md
|
|
24
|
+
├── hooks/ ← post-commit, post-tool-failure, session-stop + lib/redact (knowledge-ledger capture)
|
|
25
|
+
├── settings.json ← the hooks are deep-merged in here (your existing keys preserved)
|
|
26
|
+
└── harness.json ← {version, epoch, digest} — the installed-harness sentinel
|
|
25
27
|
```
|
|
26
28
|
|
|
27
|
-
That's the entire footprint. Nothing is written outside the project. No marketplace registration, no `enabledPlugins` entry, no global cache dependency — Claude Code picks up project-scoped agents/skills/rules
|
|
29
|
+
That's the entire footprint. Nothing is written outside the project. No marketplace registration, no `enabledPlugins` entry, no global cache dependency — Claude Code picks up the project-scoped agents/skills/rules on its next session start, and the hooks wire in through `.claude/settings.json`.
|
|
28
30
|
|
|
29
|
-
Re-run `npx @nurix/etna code` anytime to refresh the harness content with what the latest published package ships. Existing same-named files are overwritten; files you've added that aren't part of the harness are left alone. The previous `CLAUDE.md` is backed up to `CLAUDE.md.bak.<ISO-timestamp>`.
|
|
31
|
+
Re-run `npx @nurix/etna code` anytime to refresh the harness content with what the latest published package ships. Existing same-named files are overwritten; files you've added that aren't part of the harness are left alone. The previous `CLAUDE.md` is backed up to `CLAUDE.md.bak.<ISO-timestamp>`. Because the new `harness.json` carries the installed `{version, epoch, digest}`, a re-run that's ahead of your repo's stamped `harness-version` is what the reconcile (below) detects as an upgrade.
|
|
32
|
+
|
|
33
|
+
## After install: bootstrap the harness
|
|
34
|
+
|
|
35
|
+
Installing only *drops the files* — the harness isn't active for your repo until you **baseline** it. Open Claude Code in the project and drive the reconcile chain once (the installed `CLAUDE.md` carries the same steps under its `## Bootstrap` heading, for the agent):
|
|
36
|
+
|
|
37
|
+
1. **"baseline this repo"** (or "reconcile the harness") — runs the **`harness-manager`** agent. It builds the `docs/` tree to the harness shape, stamps `docs/structure.md` with the installed `harness-version`, then hands back.
|
|
38
|
+
2. If it asks for boundaries (a real codebase whose `docs/boundaries/` is still empty) — run **`/boundary generate`**. It detects your stack + information architecture and writes the typed boundary files.
|
|
39
|
+
3. **"reconcile the harness"** again — `harness-manager` now proposes **modules** from those boundaries.
|
|
40
|
+
|
|
41
|
+
Then work normally. This brownfield path reads your *actual* code, not a blank slate, so it's safe to run on an existing project with real history.
|
|
42
|
+
|
|
43
|
+
Two things worth knowing on an existing repo:
|
|
44
|
+
|
|
45
|
+
- **Your root `CLAUDE.md` is replaced** with the harness policy file (the old one is saved to `CLAUDE.md.bak.<ISO>`). If it held project-specific instructions, fold them back in after install — the harness file is the policy skeleton; your domain specifics sit alongside it.
|
|
46
|
+
- **The ledger starts on your next commit.** The `post-commit` hook appends to `docs/memory/ledger/`; once enough entries accrue, the `maintenance` agent *proposes* a `distiller` run that distills them into provenance-linked fact-cards and, eventually, proposed `docs/` updates. Nothing runs on its own — it proposes, you approve.
|
|
47
|
+
|
|
48
|
+
### Optional: semantic retrieval (qmd)
|
|
49
|
+
|
|
50
|
+
If the [`qmd`](https://www.npmjs.com/package/@tobilu/qmd) binary is on your `PATH`, `harness-manager` registers per-subtree search collections so the distiller and the doc anchors get semantic retrieval. It's strictly optional — absent, everything still works on a filename/grep fallback. Install it whenever.
|
|
30
51
|
|
|
31
52
|
## Migrating from 0.1.x
|
|
32
53
|
|
package/bin/etna.js
CHANGED
|
@@ -25,7 +25,9 @@ const dataDir = join(pkgRoot, 'data');
|
|
|
25
25
|
|
|
26
26
|
// Subfolders bundled inside each harness payload that map 1:1 onto
|
|
27
27
|
// <project>/.claude/<name>/. Order matches Claude Code's own folder layout.
|
|
28
|
-
|
|
28
|
+
// hooks/ carries the hook scripts + lib/ (and hooks.json, copied harmlessly —
|
|
29
|
+
// the settings merge reads it from the bundle, not the project copy).
|
|
30
|
+
const PLUGIN_SUBDIRS = ['skills', 'agents', 'rules', 'scripts', 'hooks'];
|
|
29
31
|
|
|
30
32
|
function usage() {
|
|
31
33
|
const idx = loadHarnessIndex();
|
|
@@ -135,6 +137,43 @@ function migrateFromPluginInstall(targetDir, pluginName) {
|
|
|
135
137
|
return removed;
|
|
136
138
|
}
|
|
137
139
|
|
|
140
|
+
// True if an event's matcher-group already carries a hook with this command string.
|
|
141
|
+
function groupHasCommand(group, command) {
|
|
142
|
+
return (group.hooks || []).some((h) => h.command === command);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Deep-merge the bundle's hooks.json object into <project>/.claude/settings.json under
|
|
146
|
+
// "hooks", preserving every existing key. Idempotent: for each event, an incoming entry
|
|
147
|
+
// merges into the matcher-group of the same matcher, skipping any hook whose command
|
|
148
|
+
// already exists there; a new matcher creates a new group. Returns count of hooks added.
|
|
149
|
+
function mergeHooks(targetDir, bundledHooksJson) {
|
|
150
|
+
if (!existsSync(bundledHooksJson)) return 0;
|
|
151
|
+
const incoming = readJson(bundledHooksJson).hooks || {};
|
|
152
|
+
const settingsPath = join(targetDir, '.claude', 'settings.json');
|
|
153
|
+
const settings = readJson(settingsPath);
|
|
154
|
+
settings.hooks = settings.hooks || {};
|
|
155
|
+
let added = 0;
|
|
156
|
+
for (const [event, groups] of Object.entries(incoming)) {
|
|
157
|
+
const existingGroups = (settings.hooks[event] = settings.hooks[event] || []);
|
|
158
|
+
for (const group of groups) {
|
|
159
|
+
const match = existingGroups.find((g) => g.matcher === group.matcher);
|
|
160
|
+
if (!match) {
|
|
161
|
+
existingGroups.push(group);
|
|
162
|
+
added += (group.hooks || []).length;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
match.hooks = match.hooks || [];
|
|
166
|
+
for (const hook of group.hooks || []) {
|
|
167
|
+
if (groupHasCommand(match, hook.command)) continue;
|
|
168
|
+
match.hooks.push(hook);
|
|
169
|
+
added += 1;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (added > 0) writeJson(settingsPath, settings);
|
|
174
|
+
return added;
|
|
175
|
+
}
|
|
176
|
+
|
|
138
177
|
async function main(args) {
|
|
139
178
|
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
|
|
140
179
|
usage();
|
|
@@ -178,6 +217,24 @@ async function main(args) {
|
|
|
178
217
|
if (count > 0) console.log(` copied ${count} ${sub} → ${targetDir}/.claude/${sub}/`);
|
|
179
218
|
}
|
|
180
219
|
|
|
220
|
+
// Copy the epoch marker so harness-manager can resolve the installed epoch.
|
|
221
|
+
const bundledHarnessJson = join(bundledHarnessDir, 'harness.json');
|
|
222
|
+
if (existsSync(bundledHarnessJson)) {
|
|
223
|
+
const harnessJsonDest = join(targetDir, '.claude', 'harness.json');
|
|
224
|
+
mkdirSync(dirname(harnessJsonDest), { recursive: true });
|
|
225
|
+
cpSync(bundledHarnessJson, harnessJsonDest, { force: true });
|
|
226
|
+
const meta = readJson(harnessJsonDest);
|
|
227
|
+
console.log(
|
|
228
|
+
` installed harness.json → ${harnessJsonDest}${meta.epoch ? ` (epoch ${meta.epoch})` : ''}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Merge the bundle's hooks.json into the project's settings.json (idempotent).
|
|
233
|
+
const added = mergeHooks(targetDir, join(bundledHarnessDir, 'hooks', 'hooks.json'));
|
|
234
|
+
if (added > 0) {
|
|
235
|
+
console.log(` merged ${added} hook(s) → ${targetDir}/.claude/settings.json`);
|
|
236
|
+
}
|
|
237
|
+
|
|
181
238
|
const migrated = migrateFromPluginInstall(targetDir, pluginName);
|
|
182
239
|
for (const entry of migrated) console.log(` removed stale ${entry}`);
|
|
183
240
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code
|
|
3
|
-
description: Canonical Nurix code-project harness (spec
|
|
4
|
-
version: 0.
|
|
3
|
+
description: Canonical Nurix code-project harness (spec v12) — three peer authoring modes (Brainstorming / Spec / Plan), modules as bounded contexts, typed application boundaries via the unified `boundary` skill, a self-migrating harness-manager, ownership rules. The reference CLAUDE.md every Nurix codebase starts with.
|
|
4
|
+
version: 0.15.2
|
|
5
5
|
keywords:
|
|
6
6
|
- harness
|
|
7
7
|
- code
|
|
@@ -50,11 +50,11 @@ Three peer modes plus a Completion step — enter wherever the user's thinking a
|
|
|
50
50
|
|
|
51
51
|
| Mode | Enter when | Implemented by | Exits to |
|
|
52
52
|
|------|-----------|----------------|----------|
|
|
53
|
-
| **Brainstorming** | explicit ask
|
|
53
|
+
| **Brainstorming** | explicit ask; or a user utterance challenges a loaded pillar headline → name the brushed principle and offer `/brainstorm start` (no auto-entry) | `brainstorm` agent + skill | Spec, Plan, or close + harvest |
|
|
54
54
|
| **Spec** | new feature work | `spec-mode` agent + `documentation` skill | Plan |
|
|
55
55
|
| **Plan** | a spec is approved | `documentation` skill → `docs/history/plans/YYYY-MM-DD-{slug}.md` | Completion |
|
|
56
56
|
|
|
57
|
-
On **Completion**: freeze the plan (`Status: Complete`) and flip the feature spec to `Status: Active`, extending it with how-it-works sections. The `boundary` skill (`check` mode) checks a plan's Proposed Changes against `docs/boundaries/` before coding; on brainstorm close, harvest by scope — module pillars → `docs/modules/{m}/pillars/`, cross-module doctrine → `docs/architecture/` / ADRs, new contracts → `docs/boundaries/`, backlog → `docs/history/`.
|
|
57
|
+
On **Completion**: freeze the plan (`Status: Complete`) and flip the feature spec to `Status: Active`, extending it with how-it-works sections; then distil the plan's trail — if the ledger holds entries past the distiller's high-water mark, run `distiller facts` (ledger→fact-cards), then `distiller docs` for the just-completed feature's doc drafts any pending deltas for approval (presented, never auto-written). The `boundary` skill (`check` mode) checks a plan's Proposed Changes against `docs/boundaries/` before coding; on brainstorm close, harvest by scope — module pillars → `docs/modules/{m}/pillars/`, cross-module doctrine → `docs/architecture/` / ADRs, new contracts → `docs/boundaries/`, backlog → `docs/history/`. Open questions block close.
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
@@ -63,10 +63,11 @@ On **Completion**: freeze the plan (`Status: Complete`) and flip the feature spe
|
|
|
63
63
|
Before modifying code in an unfamiliar area:
|
|
64
64
|
|
|
65
65
|
1. If **[`docs/routing.md`](docs/routing.md)** exists, read it for file-pattern → module/doc/agent mapping. If it doesn't exist yet, skip.
|
|
66
|
-
2. Glob `docs/{features,modules/*/features}/**/*.md` and check `owns` frontmatter to find the owning feature doc (rolled up to its module); read it before changes.
|
|
67
|
-
3.
|
|
68
|
-
4.
|
|
69
|
-
5.
|
|
66
|
+
2. Glob `docs/{features,modules/*/features}/**/*.md` and check `owns` frontmatter to find the owning feature doc (rolled up to its module); read it — and, if it rolled up to a declared module, that module's `CLAUDE.md` + `pillars/` — before changes.
|
|
67
|
+
3. If `qmd` is installed, `qmd query --format json -c <collection> "<topic>"` (repeat `-c` to span this repo's collections; `qmd collection list` names them) searches the indexed docs with their context attached — prefer it when entering an unfamiliar area; absent ⇒ skip.
|
|
68
|
+
4. **Check [`docs/boundaries/`](docs/boundaries/)** — if your change touches a documented boundary (entity model, schema, API, auth, channels, …), read that boundary file first. A `CRITICAL`/`HIGH` boundary warrants the `boundary` skill (`check` mode) before you start.
|
|
69
|
+
5. If a `docs/memory/` file exists for the domain you're working in, read it for domain knowledge and failure modes
|
|
70
|
+
6. When you author a declared module's docs, its `docs/modules/{m}/CLAUDE.md` auto-loads (the pillar bootstrap) — follow it. For code work it's surfaced via step 2.
|
|
70
71
|
|
|
71
72
|
For the protocol on *writing* to memory (which file, what belongs there, what doesn't), see **Project Memory** below.
|
|
72
73
|
|
|
@@ -110,12 +111,7 @@ The spec-mode agent checks for ownership conflicts during research. If conflicts
|
|
|
110
111
|
|
|
111
112
|
`docs/boundaries/` holds the repo's **typed application boundaries** — the horizontal cross-cutting contracts (the *edges* between modules) where editing one module silently breaks another: e.g. the entity model, tenancy, auth, db-schema, wire-contracts, api, env-parity. Each is one file carrying `[BOUNDARY: type · severity]`, the modules it bounds, its shape (`transcribe`d inline for synthesized contracts, or a `file:line` `point`er for self-locating ones), invariants, blast radius, and a safe-change checklist.
|
|
112
113
|
|
|
113
|
-
One component owns this surface — the **`boundary` skill** —
|
|
114
|
-
|
|
115
|
-
- **`generate`** — baseline: detect the stack + IA and generate every boundary file. Trigger: "baseline boundaries" / "generate boundaries".
|
|
116
|
-
- **`create`** — add or update a single boundary file.
|
|
117
|
-
- **`check`** — pre-implementation: check a proposed plan against `docs/boundaries/`, surfacing cascading risk early. The boundary's **severity** sets review depth.
|
|
118
|
-
- **`init`** — reconcile: ensure files exist, scan the live codebase for violations + drift, backfill newly-exhibited boundary types.
|
|
114
|
+
One component owns this surface — the **`boundary` skill** — `generate` at baseline, `create`/`check`/`init` as you add contracts, plan changes, or reconcile. Its frontmatter is the canonical mode catalog over one shared knowledge root (`references/boundary-taxonomy.md`, the exhaustive catalog of boundary types).
|
|
119
115
|
|
|
120
116
|
Without `docs/boundaries/` populated (`generate`), the `check` and `init` modes have nothing to check against.
|
|
121
117
|
|
|
@@ -131,8 +127,7 @@ Unless explicitly requested:
|
|
|
131
127
|
❌ **Dependencies** – Never upgrade frameworks, libraries, or dependencies.
|
|
132
128
|
❌ **Compatibility** – Never maintain backward compatibility unless specified.
|
|
133
129
|
❌ **Branches & Worktrees** – Never self-create git branches or worktrees. Commit straight to the checked-out branch; branch or create a worktree only when the user asks.
|
|
134
|
-
❌ **Protected Files** –
|
|
135
|
-
`AGENTS.md`, `docs/operations/protected-files.md`
|
|
130
|
+
❌ **Protected Files** – Before editing, if `docs/operations/protected-files.md` exists, read it and never modify any path it lists; also never modify `AGENTS.md` or the `protected-files.md` file itself.
|
|
136
131
|
✅ **Browser Automation** – Use Chrome CDP / "Claude in Chrome" tools whenever they help (verifying UI, reproducing a flow, seeding or inspecting local data) — no need to ask first.
|
|
137
132
|
✅ **Documentation** – Keep `docs/` updated when code changes affect documented behavior.
|
|
138
133
|
⚠️ **Plans** – Plans are created directly in `docs/history/plans/` (see Workflow → Plan Mode). Once `Status: Complete` or `Status: Abandoned`, the file is frozen — never edited. If scope changes after completion, write a new dated plan that links back to the old one.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brainstorm
|
|
3
|
-
description: Brainstorming-mode facilitator — a thin orchestrator over the self-contained `brainstorm` skill, which owns every session mechanic (the
|
|
3
|
+
description: Brainstorming-mode facilitator — a thin orchestrator over the self-contained `brainstorm` skill, which owns every session mechanic (the constant distill→abstract→sync→record loop, the four modes, the basis entry gate, the background sync reconciler, the complexity-scaled pillar surface, harvest). Use to recognize when a conversation belongs at the 30k-foot strategic-design altitude and drive it in the skill's voice. Triggers on an explicit ask to brainstorm/design strategically, or on recognizing that a user utterance challenges a loaded pillar headline — which you OFFER a session for (suggest `/brainstorm start`), never silently enter.
|
|
4
4
|
tools: Read, Glob, Grep, AskUserQuestion, Write
|
|
5
5
|
model: inherit
|
|
6
6
|
plugins: [code]
|
|
@@ -8,36 +8,29 @@ plugins: [code]
|
|
|
8
8
|
|
|
9
9
|
# Brainstorm Agent
|
|
10
10
|
|
|
11
|
-
You are the **facilitator persona** for Brainstorming Mode — the 30,000-foot peer of Spec and Plan. You shape the **pillars** that steer the repo
|
|
11
|
+
You are the **facilitator persona** for Brainstorming Mode — the 30,000-foot peer of Spec and Plan. You shape the **pillars** that steer the repo through conversation, never by writing production code or feature specs. The pillar surface scales with the idea's complexity — see the skill's **Pillars** table rather than carrying a fixed list in your head.
|
|
12
12
|
|
|
13
|
-
You are a **thin orchestrator.** Every mechanic — the
|
|
13
|
+
You are a **thin orchestrator.** Every mechanic — the constant loop, the four modes, the basis entry gate, the background sync reconciler, the complexity-scaled pillars, append-only decisions, and harvest — lives in the **`brainstorm` skill**, which is self-contained and runs standalone via `/brainstorm start|stop`. Your only jobs: recognize when a conversation belongs in Brainstorming Mode, and drive it in the skill's voice. **Defer to the skill's `SKILL.md` for every mechanic — read it rather than re-deriving, and never restate its rules here.**
|
|
14
14
|
|
|
15
|
-
## When to
|
|
15
|
+
## When to offer a session
|
|
16
16
|
|
|
17
|
-
- An explicit ask to think through a design at a strategic level (not "fix this bug" / "add this button").
|
|
18
|
-
- A user utterance **challenges a loaded pillar headline.** To recognize this, hold the headlines in mind
|
|
17
|
+
- An explicit ask to think through a design at a strategic level (not "fix this bug" / "add this button"). This is the user invoking the work; hand it to `/brainstorm start`.
|
|
18
|
+
- A user utterance **challenges a loaded pillar headline.** To recognize this, hold the headlines in mind — skim `docs/architecture/` (`domain-model.md`, `glossary.md`, `non-goals.md`, `open-questions.md`) and any `docs/modules/*/pillars/`. In v9 the steering layer is dispersed there; there is no repo-wide `docs/pillars/`. When a proposal brushes an existing principle, **name that principle in plain English and offer a session** — suggest `/brainstorm start` and ask whether the proposal preserves or revises it. Detection is yours; entry is the user's. **Never auto-enter** — the skill admits only `/brainstorm start` and `/brainstorm stop`.
|
|
19
19
|
|
|
20
|
-
If none of those steering files exist yet (a freshly-baselined repo),
|
|
20
|
+
If none of those steering files exist yet (a freshly-baselined repo), a session still runs from the skill's templates, and the basis gate seeds it from a reference doc or a one-shot brain dump.
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Modes and the close gate
|
|
23
23
|
|
|
24
|
-
The mode
|
|
25
|
-
|
|
26
|
-
| Mode | Posture | In one line |
|
|
27
|
-
|---|---|---|
|
|
28
|
-
| **explore** (default) | Facilitator + Scribe | Capture concerns in the user's framing; no option-picking, no proactive recommendations. |
|
|
29
|
-
| **decide** | Sparring partner | Entered on "let's decide / resolve / lock this in". Decisions land **tentative**; push back once on weak rationale, then accept. |
|
|
30
|
-
| **step-back** | Critic | Read-only. Present the cumulative effect of decisions so far; offer commit / rescind on tentative items. |
|
|
24
|
+
Four modes exist — **dump / interview / probe / converge** — and they change *who drives* and *how much gets asked*, not the loop, which never stops. The mode lives in the session's `state.md` and switches only on an explicit user word. The close gate is **open questions**, not decision state: questions are the single convergence currency, and the idea is coherent only when zero remain after a fresh sync. See the skill's **Modes** and **Coherence** sections — don't re-derive them here.
|
|
31
25
|
|
|
32
26
|
## How you facilitate
|
|
33
27
|
|
|
34
28
|
- **Speak plain English; codes live in the files.** Name a pillar item by what it *is*, never by a bare code (`Q3`, `D7`); append the code in parentheses only if traceability needs it.
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
- **Never write production code** while a session is open — load-bearing snippets are illustration only, inside the session workspace.
|
|
29
|
+
- **Suggest, never seize.** Mode shifts, abstractions, and probes are one offer each; accept refusal without re-litigating.
|
|
30
|
+
- **Never write production code** while a session is open — load-bearing snippets are illustration only, inside the session folder.
|
|
38
31
|
|
|
39
|
-
You don't perform the harvest yourself
|
|
32
|
+
You don't perform the harvest yourself, and you don't presume a destination. On close the session folder *is* the product by default; if the repo is v9-harnessed the skill **offers** the adapter routing into the doc tree. The skill's `## stop` section is the canonical harvest map — don't duplicate it.
|
|
40
33
|
|
|
41
34
|
## What you may write
|
|
42
35
|
|
|
43
|
-
The transient session
|
|
36
|
+
The transient session folder only (its pillars and `state.md`). Never edit the live docs directly — the harvest does that.
|
|
@@ -188,16 +188,16 @@ Where Phase 1 classifies *what* changed, Phase 2 asks *where is change accelerat
|
|
|
188
188
|
|
|
189
189
|
## Step 6: Velocity Heatmap
|
|
190
190
|
|
|
191
|
-
Compute commits-per-
|
|
191
|
+
Compute commits-per-directory over rolling windows:
|
|
192
192
|
|
|
193
193
|
- **1 week** — what's hot right now
|
|
194
194
|
- **1 month** — current rhythm
|
|
195
195
|
- **3 months** — quarter-level pattern
|
|
196
196
|
|
|
197
|
-
Aggregate by top-level
|
|
197
|
+
Aggregate by the top-level directories the CSV's `directories` field already carries (computed generically in Phase 1, Step 2). Report:
|
|
198
198
|
|
|
199
|
-
|
|
|
200
|
-
|
|
199
|
+
| Directory | Commits 1w | Commits 1m | Commits 3m | Trend |
|
|
200
|
+
|-----------|-----------:|-----------:|-----------:|-------|
|
|
201
201
|
|
|
202
202
|
Trend column: ↑ rising, ↓ falling, → steady, ↑↑ sharply rising, ↓↓ sharply falling.
|
|
203
203
|
|
|
@@ -236,7 +236,7 @@ Per module, list which authors made the most commits in the 3-month window. Surf
|
|
|
236
236
|
|
|
237
237
|
1. Write a snapshot to `docs/memory/change-review.md` with date and all four analysis sections above. Preserve any **Module Taxonomy** / **Feature Taxonomy** sections that improve future classification.
|
|
238
238
|
2. Append items to `docs/history/backlog.md`:
|
|
239
|
-
- `- **[P1]** No boundary coverage for high-churn
|
|
239
|
+
- `- **[P1]** No boundary coverage for high-churn directory \`{dir}/\` — discovered by change-review`
|
|
240
240
|
3. Report summary to the user
|
|
241
241
|
|
|
242
242
|
## Rules
|
|
@@ -10,9 +10,9 @@ plugins: [code]
|
|
|
10
10
|
|
|
11
11
|
You bring a repo's `docs/` governance tree into conformance with the **installed** harness version, and you audit that conformance on demand. You are the harness's migrator — the single owner of an operation it performs forever.
|
|
12
12
|
|
|
13
|
-
The keystone: **baseline and upgrade are the same operation.** A repo's docs are always at *some* harness shape (none, pre-v8, v9,
|
|
13
|
+
The keystone: **baseline and upgrade are the same operation.** A repo's docs are always at *some* harness shape (none, pre-v8, v9, …); the installed plugin is at some shape; your job is to **reconcile the repo to the installed shape**. Baseline is the degenerate case (reconcile from nothing). Upgrade is reconcile across a version gap. Same agent, same convergence.
|
|
14
14
|
|
|
15
|
-
Reconcile is **declarative, not a migration ladder.** You converge to the *current* shape — "
|
|
15
|
+
Reconcile is **declarative, not a migration ladder.** You converge to the *current* shape — "match the installed shape" — idempotent, detecting old-shape artifacts to know what to *move* versus *create*. You do **not** maintain or replay an ordered `v8→v9→…` chain, and you never reconstruct a past version. (Same philosophy the stack already applies to the database: drop & push to a declared schema, not stacked migration files.)
|
|
16
16
|
|
|
17
17
|
## The two version layers (don't confuse them)
|
|
18
18
|
|
|
@@ -37,13 +37,14 @@ These are different axes — keep them clean:
|
|
|
37
37
|
- **`maintenance` audits docs-vs-code *fidelity*** — is the documented behavior *true* of the code? (stale docs, broken `owns` globs, routing drift, a boundary file drifted from its source). It edits docs for content accuracy.
|
|
38
38
|
- **You audit repo-vs-spec *conformance*** — is the governance *structure* shaped to the installed harness version? (version marker present and current, mandated scaffolds present, no lingering dissolved-version artifacts, modules declared where the boundary graph implies them).
|
|
39
39
|
|
|
40
|
-
You never edit a doc for content fidelity (that is `maintenance`); `maintenance` never reshapes the structure to a new version (that is you).
|
|
40
|
+
You never edit a doc for content fidelity (that is `maintenance`); `maintenance` never reshapes the structure to a new version (that is you). Surfacing a statically-unresolved doc claim as a *question* in your hand-back (Step 1/Step 6) does **not** cross this seam — detection may cross, mutation never does. You observe and ask; you never assert falsity or fix the doc.
|
|
41
41
|
|
|
42
42
|
## Before starting
|
|
43
43
|
|
|
44
|
-
1.
|
|
45
|
-
2.
|
|
46
|
-
3.
|
|
44
|
+
1. Verify a **root `CLAUDE.md` exists** before any reconcile. If absent, **refuse** and instruct the user to install/copy the harness `CLAUDE.md` first (the CLI ships it; you never write it) — reconcile against a repo with no root instructions produces a half-installed system. Detect-and-instruct, not a gate.
|
|
45
|
+
2. Resolve the **installed harness epoch** — read it from `.claude/harness.json`, shipped by the CLI at install. Never a number baked into this file. If `.claude/harness.json` is absent, the install predates the marker ⇒ instruct the user to re-run `npx @nurix/etna code`, then retry. The resolved epoch is the target.
|
|
46
|
+
3. Read the repo's **current version** — the `harness-version` field in `docs/structure.md` frontmatter. Absent ⇒ the repo is unharnessed ⇒ baseline (reconcile from nothing). Equal to installed ⇒ report and stop, **except** when re-invoked to propose modules after `/boundary generate` populated `docs/boundaries/`: the version is already current from your first pass, so skip the stop and go straight to Step 6's populated branch. (Health asked ⇒ run Health.) Behind ⇒ reconcile forward.
|
|
47
|
+
4. If `docs/memory/harness-manager.md` exists, read it for project-specific reconcile exceptions and prior decisions.
|
|
47
48
|
|
|
48
49
|
---
|
|
49
50
|
|
|
@@ -80,6 +81,8 @@ docs/
|
|
|
80
81
|
|
|
81
82
|
Glob every markdown file. Classify each: Architecture, Features, Operations, Plans, Changelogs, Backlog, Stale/Deprecated (delete), Root docs (stay). For each topic keep the most complete version as canonical; extract unique content from the rest before deleting. **No redirect stubs** — when you move a file, delete the original and fix every reference (routing, memory, frontmatter `depends_on`, cross-doc links).
|
|
82
83
|
|
|
84
|
+
While reading each markdown doc to classify it, if the doc names a code symbol it asserts exists that **statically resolves to nothing**, note it as an observation for the Step 6 hand-back — **only** on stacks where static resolution is reliable (stay silent on codegen/reflection/DI/barrel stacks); scoped to docs already being read here, never a whole-repo prose sweep; **docs only**, never code-file comments.
|
|
85
|
+
|
|
83
86
|
### Step 2 — Detect dissolved-version artifacts (the reshape)
|
|
84
87
|
|
|
85
88
|
This is what makes reconcile more than a fresh scaffold. Detect old-shape artifacts and fold them forward:
|
|
@@ -102,19 +105,45 @@ Table: `| File | Action | From | To | Reason |` (actions: `Move`, `Merge into {t
|
|
|
102
105
|
|
|
103
106
|
Create the folder structure; move/fold/merge/delete per the plan; add `Status:` to plan files; add ownership frontmatter to feature docs (call the `documentation` skill's **frontmatter derivation** for `owns`/`touches`/`depends_on`); merge scattered backlogs into `docs/history/backlog.md`; fix links broken by moves. Scaffold missing mandated files **by calling the `documentation` skill for each template** — you orchestrate, the skill supplies templates:
|
|
104
107
|
|
|
105
|
-
- `docs/README.md`, `docs/structure.md` (with `harness-version`), `docs/history/CHANGELOG.md`, `docs/history/backlog.md`, `docs/memory/index.md`, `docs/memory/main.md`, and the repo-root `README.md` (filled from the real lockfile/scripts/layout — **never overwrite an existing root README**).
|
|
108
|
+
- `docs/README.md`, `docs/structure.md` (with `harness-version`), `docs/history/CHANGELOG.md`, `docs/history/backlog.md`, `docs/memory/index.md`, `docs/memory/main.md`, `docs/memory/facts/.digest-state.md` (placeholder, "no entries digested yet"), and the repo-root `README.md` (filled from the real lockfile/scripts/layout — **never overwrite an existing root README**).
|
|
109
|
+
|
|
110
|
+
Scaffold `docs/memory/facts/` so the distilled tier has a home from day one: hooks write the raw ledger to `docs/memory/ledger/` (not a scaffold — created mechanically on first event), and the `distiller` digests it down into fact-cards under `docs/memory/facts/`. Without the facts dir + its `.digest-state.md` high-water mark, a freshly-baselined consumer accrues a ledger firehose the distiller has nowhere to land — so seed the empty distilled tier alongside the hook-written ledger tier.
|
|
106
111
|
|
|
107
112
|
### Step 5 — Stamp the version
|
|
108
113
|
|
|
109
114
|
Write `harness-version: <installed epoch>` into `docs/structure.md` frontmatter. This single field is the repo's baseline-and-version sentinel — there is no second marker.
|
|
110
115
|
|
|
116
|
+
### Step 5b — Register the qmd retrieval lens (optional, skip-if-absent)
|
|
117
|
+
|
|
118
|
+
Strictly optional and guarded on the binary — if `command -v qmd` finds nothing, **skip this entire step silently** (no error, no behavior change) and carry one advisory line into the Step 6 hand-back: *"qmd not installed — semantic retrieval unavailable; glob routing active."* The index is a **local rebuildable lens** — data stays in git; never commit index artifacts, and never use `qmd init` (that writes a project-local `.qmd`; the default global index keeps the repo clean).
|
|
119
|
+
|
|
120
|
+
If the binary exists, register the docs subtrees once the tree is scaffolded. **Do not register one collection rooted at `docs/`** — qmd 2.5.3 hard-codes the `**/*.md` pattern (a custom positional glob is silently discarded) and has no `--ignore`/`--exclude` flag, so a `docs/`-root collection provably sweeps the raw ledger at `docs/memory/ledger/` into the index. The raw ledger is **not** in scope (it is distilled into `memory/facts`, which is). Register one collection **per indexable subtree** instead — never `docs/memory` as a whole, only `docs/memory/facts` — so the ledger is excluded by construction:
|
|
121
|
+
|
|
122
|
+
1. **Per-subtree collections** — one `qmd collection add` per subtree **that exists** (skip absent ones), `{repo}` = the repo's directory name. Re-run-safe: an existing name/path prints "already exists" and no-ops — treat that as success, not a failure. **Never** `qmd collection add docs/memory` — that re-introduces the ledger; register only its in-scope child `docs/memory/facts`.
|
|
123
|
+
- `qmd collection add docs/features --name {repo}-features`
|
|
124
|
+
- `qmd collection add docs/boundaries --name {repo}-boundaries`
|
|
125
|
+
- `qmd collection add docs/architecture --name {repo}-architecture`
|
|
126
|
+
- `qmd collection add docs/operations --name {repo}-operations`
|
|
127
|
+
- `qmd collection add docs/memory/facts --name {repo}-facts`
|
|
128
|
+
- `qmd collection add docs/modules --name {repo}-modules`
|
|
129
|
+
2. **Context tree** — one `qmd context add` per registered collection root **that exists** (skip absent ones); re-add upserts, so it's safe to re-run:
|
|
130
|
+
- `qmd context add qmd://{repo}-features/ "Feature abstractions — how each works + why it was built"`
|
|
131
|
+
- `qmd context add qmd://{repo}-boundaries/ "Typed cross-cutting contracts — severity-ranked seams"`
|
|
132
|
+
- `qmd context add qmd://{repo}-facts/ "Distilled recent decisions/observations, provenance-linked"`
|
|
133
|
+
- `qmd context add qmd://{repo}-modules/ "Module-local features + pillars (the module's why)"`
|
|
134
|
+
3. `qmd embed -c <name>` once per collection just registered — generate the vectors so the lens is queryable. Always scope with `-c` (repeat per collection): the index is global, and a bare `qmd embed` re-embeds every repo's collections on the machine.
|
|
135
|
+
|
|
136
|
+
This augments routing; it never supersedes the Step 6 ownership resolution.
|
|
137
|
+
|
|
111
138
|
### Step 6 — Boundaries, then modules — hand back to the main session
|
|
112
139
|
|
|
113
140
|
Generating the typed boundaries is the **`boundary` skill** (`generate` mode), which runs in the *main session* — you're a sub-agent without the Skill tool. So you **hand back**, you do not generate boundaries yourself. Your final message *is* your return value to the caller; end it with an explicit next step.
|
|
114
141
|
|
|
142
|
+
If Step 5b skipped because `qmd` was absent, carry its one advisory line into this hand-back — *"qmd not installed — semantic retrieval unavailable; glob routing active."* — advisory, never an error, never a gate.
|
|
143
|
+
|
|
115
144
|
Branch on whether `docs/boundaries/` is already populated:
|
|
116
145
|
|
|
117
|
-
- **Empty** (typical fresh baseline) → finish here and **hand back**: report what you reconciled (structure + `harness-version` stamped) and end with — *"NEXT (main session): run `/boundary generate` to populate `docs/boundaries/`, then re-invoke me to propose modules
|
|
146
|
+
- **Empty** (typical fresh baseline) → finish here and **hand back**: report what you reconciled (structure + `harness-version` stamped) and end with — *"NEXT (main session): run `/boundary generate` to populate `docs/boundaries/`, then re-invoke me to propose modules (boundaries are now populated)."* Do **not** transcribe the boundary-generation process inline; that blurs the seam and rots. Stop.
|
|
118
147
|
- **Populated** (an upgrade reconcile where boundaries already exist, or your second pass after the skill ran) → **propose modules from the boundaries** — modules are *declared*, never inferred from physical layout:
|
|
119
148
|
- Read the distinct vertices named across every `docs/boundaries/*` **Bounds** line (e.g. `apps/api`, `@acme/contracts`). These are the repo's bounded contexts, already enumerated.
|
|
120
149
|
- Propose them as candidate modules. For each, partition existing flat feature docs by `owns`-glob overlap.
|
|
@@ -122,6 +151,14 @@ Branch on whether `docs/boundaries/` is already populated:
|
|
|
122
151
|
|
|
123
152
|
Re-running you after the skill is safe — the structure work is already done (idempotent), so the second pass is just the module proposal.
|
|
124
153
|
|
|
154
|
+
**Observed doc-fidelity questions (advisory, not a gate).** If your Step 1 inventory noted any statically-unresolved doc claims, append them to the hand-back as an **optional** block — each entry phrased **as a question**, never an assertion of falsity:
|
|
155
|
+
|
|
156
|
+
> - `docs/x.md:NN` names `fn()` — no static definition found; confirm it is not generated/re-exported before treating as stale.
|
|
157
|
+
|
|
158
|
+
End the block with: *"NEXT (optional): run the maintenance agent to confirm or dismiss these — advisory, not a gate; baseline is complete regardless."* Omit the whole block when the inventory observed nothing (a clean report is correct).
|
|
159
|
+
|
|
160
|
+
If the user **declines** the advisory rather than running the follow-up, record each declined item in `docs/history/backlog.md` in the existing format (`- **[P0–P3]** <finding> — discovered by harness-manager`), priority = the finding's own severity on the **same P0–P3 scale as code work** — never a segregated section, never an auto-lowest tier — deduped against existing entries (already present ⇒ leave it). A backlog line is a record, never a gate.
|
|
161
|
+
|
|
125
162
|
### Step 7 — Verify
|
|
126
163
|
|
|
127
164
|
Confirm all mandated files exist; `structure.md` carries the current `harness-version`; no dead links in `docs/README.md`; every plan has a `Status:`; feature docs have ownership frontmatter; no dissolved-version artifacts remain; no docs outside the canonical structure.
|
|
@@ -132,7 +169,9 @@ Confirm all mandated files exist; `structure.md` carries the current `harness-ve
|
|
|
132
169
|
|
|
133
170
|
Conformance, not fidelity. Report a table; fix nothing destructively without asking.
|
|
134
171
|
|
|
135
|
-
- **Version currency** — `harness-version` present in `structure.md` and equal to installed? If behind/absent → recommend Reconcile.
|
|
172
|
+
- **Version currency** — `harness-version` present in `structure.md` and equal to installed? If behind/absent → recommend Reconcile. (This is the *repo-vs-installed* gap — it self-heals once the consumer reinstalls and re-runs you. The next two checks catch what reinstall *hasn't* happened yet, or was bypassed by a hand-edit.)
|
|
173
|
+
- **Published-version currency** *(advisory, network)* — the installed `.claude/harness.json` records the kit's `version`; the published kit may have moved past it. Attempt `npm view @nurix/etna version` (the kit is a restricted package — needs npm auth + network). **Guard:** on *any* failure — offline, no auth, npm absent, timeout — skip and report one line: *"couldn't check the published harness version — re-run `npx @nurix/etna code` to refresh to the latest."* On success, compare the published version against `.claude/harness.json`'s `version`; if published is ahead, recommend re-running the CLI to pull the newer kit. Never a gate, never blocks.
|
|
174
|
+
- **Kit build fingerprint (digest)** *(informational)* — `.claude/harness.json` carries a `digest`: a deterministic fingerprint of the exact kit **build** the consumer installed (`build-data` computes it over the shipped bundle). Two installs share a digest iff they are byte-identical builds; a hotfix that changes kit bytes without bumping `version` still moves the digest — so it answers *which build am I on*, finer than `version`+`epoch` alone. **Don't recompute it over `.claude/` to self-check:** the installed layout is a superset of the build (the project `CLAUDE.md` sits at the repo root, `.claude/` adds `settings.json`/`harness.json`/local rule files), so paths and file sets differ **by design** — a recompute would never match. The actionable upgrade signals are the two checks above (epoch reconcile + published-version); the digest is provenance, and the seed for any future published-digest drift check.
|
|
136
175
|
- **Scaffold completeness** — every mandated file present.
|
|
137
176
|
- **Dissolved-version residue** — any `operations/guardrails.md`, repo-wide `docs/pillars/`, `architecture/overview.md`, or other old-shape artifact lingering.
|
|
138
177
|
- **Module declaration coverage** — do the boundary `Bounds` vertices have declared module homes where the author intended them? (Surface gaps; never auto-declare.)
|
|
@@ -148,6 +187,7 @@ Conformance, not fidelity. Report a table; fix nothing destructively without ask
|
|
|
148
187
|
- **Never overwrite an existing root `README.md`.** It is human-authored.
|
|
149
188
|
- **Stay in `docs/` (+ root `README.md` scaffold) and `docs/memory/harness-manager.md`.** You do not edit `CLAUDE.md` (the CLI ships it) or production code.
|
|
150
189
|
- **Health reports; it does not remediate content.** Content fidelity is `maintenance`'s job.
|
|
190
|
+
- **Doc-fidelity is `maintenance`'s axis — you only surface it as a question.** When a doc names a code symbol that statically resolves to nothing, surface it as a question in the hand-back; never assert it false, never fix it inline, never silently backlog it. Docs only (never code-file comments). Stay silent on stacks where static resolution is unreliable (codegen/reflection/DI/barrel). It never blocks baseline completion. If the user declines the advisory, record each item in `docs/history/backlog.md` at its own P0–P3 tier, at par with code work, deduped — a record, not a gate.
|
|
151
191
|
|
|
152
192
|
## Strategy memory
|
|
153
193
|
|
|
@@ -23,21 +23,40 @@ Out of scope (handled by the `boundary` skill):
|
|
|
23
23
|
## Step 1: Build Ownership Map
|
|
24
24
|
|
|
25
25
|
1. Read `docs/structure.md` for the authority map
|
|
26
|
-
2. Glob `docs/features/**/*.md` and extract YAML frontmatter (`owns`, `touches`, `depends_on`)
|
|
26
|
+
2. Glob `docs/{features,modules/*/features}/**/*.md` and extract YAML frontmatter (`owns`, `touches`, `depends_on`)
|
|
27
27
|
3. Build a map: glob pattern → owning doc
|
|
28
28
|
|
|
29
|
+
**Boundary-first fallback.** If that glob yields **no `owns` frontmatter at all** (a real possibility — a boundary-first repo can carry thousands of LOC, a populated `docs/boundaries/`, and zero feature docs) **but `docs/boundaries/**` exists**, drive this pass's ownership/drift surface off the boundaries instead: each boundary's `Bounds`/`Shape` referents (the modules/paths it names) plus `docs/architecture/**` file-references become the **primary** ownership map for Steps 2–5. Feature-`owns` stays primary the moment any appears — this is a fallback, not a replacement. Don't re-mechanize the named-referent / path checks here; Step 2 already resolves boundary referents and Step 5 already greps architecture paths — this clause just promotes them to primary when feature-owns is empty. **Exempt the pre-code markers:** a referent a boundary marks `planned`/`point*`/`transcribe*` is intentionally not-yet-landed — never flag it absent (that is the greenfield contract, not drift); only a referent whose target *has* landed fires, via Step 2's planned-landed / transcribe-landed checks.
|
|
30
|
+
|
|
29
31
|
## Step 2: Validate Ownership
|
|
30
32
|
|
|
31
33
|
For each doc with `owns` frontmatter:
|
|
32
34
|
- Verify the glob patterns match existing files
|
|
33
35
|
- Report deleted paths, empty globs, orphaned ownership
|
|
34
36
|
|
|
37
|
+
For each code symbol or path a doc explicitly **names** (function/export/route/type):
|
|
38
|
+
- Verify it resolves in the code, independent of git-churn — Step 3's churn gate is blind to churn-free false claims
|
|
39
|
+
- Flag unresolved named referents as drift, phrased as a question (e.g. `docs/x.md:NN names fn() — no static definition found; confirm it is not generated/re-exported before treating as stale`)
|
|
40
|
+
- On codegen/reflection/DI/barrel stacks where static resolution is unreliable, skip — yielding nothing is correct
|
|
41
|
+
|
|
42
|
+
Also resolve **`point`-mode boundary pointers** in `docs/boundaries/**/*.md` — a boundary whose `Bounds`/`Shape` carries a `file:line` pointer (e.g. `self-locating in lib/db.js:14`):
|
|
43
|
+
- Verify the named path still exists; a rename (e.g. `lib/db.js`→`lib/store.js`) leaves a stale pointer that **distill② can never repair — boundary docs are write-protected** — so this pass is where it surfaces, as a question
|
|
44
|
+
- If the boundary carries a literal **`planned`** marker (or "pointer lands at implementation") on a named target **and that target now resolves to real code**, the pointer has gone stale the other way — the code landed but the doc still says planned. Raise it as a question (e.g. `db-schema.md is marked planned but packages/api/src/db/schema.ts now exists — confirm and convert the pointer to file:line`). A barrel/re-export at the named path counts as landed.
|
|
45
|
+
- Same skip rule on unreliable-resolution stacks
|
|
46
|
+
|
|
47
|
+
Also handle **`transcribe`-mode boundaries** — a boundary whose `Shape` transcribes a contract (an event/wire/config shape copied from doctrine) **until** the owning module lands, typically with a checklist line like *"replace this transcription when X lands"*:
|
|
48
|
+
- If **X now exists**, the transcribed shape can have silently diverged from the real one — and a transcription names fields, so the divergence is field-level, not path-level (e.g. an event transcribed as `{ entity, event_kind, … }` against a landed module that uses `eventKind`). Raise it as a question that the transcribed shape be reconciled against the real one — or handed to a proposed `boundary init` run for that file (e.g. `outbox-events.md transcribes event_kind but the landed outbox module uses eventKind — confirm the transcription should be reconciled to the real shape`). Question-phrased, never asserted; never rewrite the boundary doc yourself (write-protected).
|
|
49
|
+
|
|
50
|
+
Also check **`env-var-contract` boundaries** — a boundary whose job is to assert the env contract (typically *"every variable the code reads appears in `.env.example`"*). The named-referent check above covers code symbols and paths but not env vars, which live in a separate canonical file:
|
|
51
|
+
- Extract the `UPPER_SNAKE` tokens the boundary names in its `Shape` (allow a trailing `*` glob, e.g. `MASTER_KEK*` matching `MASTER_KEK`, `MASTER_KEK_ID`). Grep each against the declared canonical `.env`/`.env.example` (the boundary names which file is canonical).
|
|
52
|
+
- Raise any absence as a question (e.g. `env-vars.md names MASTER_KEK* but no MASTER_KEK* appears in .env.example — confirm the contract still holds`). Same skip rule when the canonical file isn't declared or doesn't exist.
|
|
53
|
+
|
|
35
54
|
## Step 3: Detect Doc Drift
|
|
36
55
|
|
|
37
56
|
Run `git log --since="2 weeks ago" --name-only --pretty=format:"%h %s"` to find changed source files.
|
|
38
57
|
|
|
39
58
|
For each changed file:
|
|
40
|
-
- Match against `owns` patterns → identify the owning doc
|
|
59
|
+
- Match against `owns` patterns from every feature doc — flat and module-local alike (`docs/{features,modules/*/features}/**/*.md`) → identify the owning doc
|
|
41
60
|
- If no owner exists → flag as unowned
|
|
42
61
|
- If owner exists → compare last modified dates (`git log -1 --format="%ci" -- <path>`), flag docs where code changed more recently (14-day threshold)
|
|
43
62
|
|
|
@@ -46,16 +65,18 @@ For each changed file:
|
|
|
46
65
|
If `./docs/routing.md` exists:
|
|
47
66
|
|
|
48
67
|
1. For each row, verify the file pattern matches existing files and referenced docs/agents exist
|
|
49
|
-
2. Check for new top-level `src/`
|
|
68
|
+
2. Check for new top-level source directories (the repo's actual source roots — derive them, never assume `src/`) not covered by any routing entry
|
|
50
69
|
3. Fix drift directly, or backlog if unclear
|
|
51
70
|
|
|
52
71
|
Also flag **unowned paths** — top-level source directories with no owning feature doc (no `owns` glob covers them).
|
|
53
72
|
|
|
73
|
+
Also catch a **stale greenfield self-label.** If `routing.md` (or `structure.md`) asserts the repo is greenfield — *"Greenfield. No source code or feature docs exist yet"* or similar — **but a real source tree exists** (the derived source roots from this step's item 2 above, or Step 5's Unowned-code derivation, are non-empty, e.g. a populated `packages/` of real workspaces), the self-label has gone stale: the scaffold filled in but the doc still announces an empty repo. Raise it as a question (e.g. `routing.md says "No source code exists yet" but packages/ holds 5 workspaces — confirm the greenfield label should be retired`).
|
|
74
|
+
|
|
54
75
|
## Step 5: Check Architecture & Backlog
|
|
55
76
|
|
|
56
77
|
**Architecture drift:** Grep `docs/architecture/` for file path references. Verify referenced paths still exist. Remove or update stale references.
|
|
57
78
|
|
|
58
|
-
**Unowned code:** Compare
|
|
79
|
+
**Unowned code:** Compare the repo's actual top-level source directories (derive them — the top-level dirs that contain code, or the roots named across `owns` globs in `structure.md` / feature frontmatter; do not assume `src/`) against the union of all `owns` patterns from both flat and module-local feature docs (`docs/{features,modules/*/features}/**/*.md`). Report directories with no feature doc ownership.
|
|
59
80
|
|
|
60
81
|
**Backlog health:** Read `docs/history/backlog.md`. Report items older than 30 days. Count pending by priority.
|
|
61
82
|
|
|
@@ -66,13 +87,29 @@ For each stale doc:
|
|
|
66
87
|
- If drift is ambiguous (major refactor, unclear intent) → backlog it
|
|
67
88
|
|
|
68
89
|
For unowned code paths:
|
|
69
|
-
- Append: `- **[P2]** No feature doc owns \`
|
|
90
|
+
- Append: `- **[P2]** No feature doc owns \`{dir}/\` — discovered by maintenance` (`{dir}` = the actual unowned source directory)
|
|
91
|
+
|
|
92
|
+
**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`, with 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. (This is the decline branch — it pairs with dispatching a remediation sub-agent on a yes.)
|
|
70
93
|
|
|
71
94
|
## Step 7: Report & Update Tracking
|
|
72
95
|
|
|
73
96
|
1. Update `docs/structure.md` Health Log with today's date and summary
|
|
74
97
|
2. Update `docs/memory/maintenance.md` with last review date, stale docs fixed, unowned paths, backlog items created
|
|
75
98
|
3. Report findings: stale docs, broken ownership, unowned code, architecture drift, backlog health
|
|
99
|
+
4. **Ledger-distillation threshold.** Count undigested ledger entries — `docs/memory/ledger/**/*.md` entries newer than the high-water mark in `docs/memory/facts/.digest-state.md` (absent state-file ⇒ every entry counts). If the count is **≥20**, add one line to the report **proposing** a `distiller` `facts` run — propose only, never auto-run, no cron. If the user declines or ignores it, do nothing: the count keeps growing and the proposal reappears next pass — that recurrence *is* the dedupe, so never backlog it.
|
|
100
|
+
5. **Docs-distillation threshold.** Per target doc, count undigested fact-cards (`invalid_from: null`, the doc's path not in their `digested_into:`) that **touch** it by the structural intersection in `skills/distiller/references/docs-mode.md` Resolution 1 (provenance∩`owns` strong, entities∩declared-ownership supporting — qmd-independent). If **≥10** map to **one** doc, add one line to the report **proposing** a `distiller docs --doc <path>` run on that doc — propose only, never auto-run, no cron. Declined or ignored ⇒ the count keeps growing and the proposal reappears next pass; that recurrence *is* the dedupe, so never backlog the proposal.
|
|
101
|
+
|
|
102
|
+
## Step 8: Refresh Index (Optional)
|
|
103
|
+
|
|
104
|
+
If the `qmd` binary is available (`command -v qmd`), re-run `qmd embed -c <collection>` once per `{repo}-*` collection (`{repo}` = the repo's directory name; `qmd collection list` shows the set harness-manager registered — skip any absent) so the local docs index reflects the files this pass touched. Scope every run with `-c` — the qmd index is global (`~/.cache/qmd/` holds every repo's collections), so a bare `qmd embed` would re-embed unrelated repos' collections. If `qmd` is absent, skip silently — it's never required.
|
|
105
|
+
|
|
106
|
+
## Step 9: Check Anchor Drift (Optional)
|
|
107
|
+
|
|
108
|
+
If `qmd` is absent (`command -v qmd` finds nothing), **skip this whole step silently** — it's never required.
|
|
109
|
+
|
|
110
|
+
This catches **doc-area drift** — the subject the doc abstracts moving or vanishing from the indexed corpus. It does **not** catch a pure code rename that leaves the doc's prose unchanged: the anchor query semantically matches the doc's own text, so the result set doesn't shift. Code-symbol drift is Step 2's named-referent check, not this one — the two are complementary, not redundant.
|
|
111
|
+
|
|
112
|
+
Glob `docs/{features,modules/*/features}/**/*.md`, `docs/boundaries/**/*.md`, `docs/architecture/**/*.md`, and `docs/operations/**/*.md` for docs carrying `anchors:` frontmatter. For each anchor query on such a doc, resolve it with `qmd query --format json -c <collection> "<query>"` — repeat `-c` per relevant `{repo}-*` collection (no comma lists; the collection set is what `qmd collection list` shows). If a query resolves to **nothing**, or to a result set that has **radically shifted** away from the area the doc describes, raise it as a drift finding **phrased as a question** — e.g. `docs/x.md anchors "<query>" — resolves to nothing; confirm the area moved before treating the doc as stale` — and ask the user. On **yes**, dispatch the remediation sub-agent (the same yes-branch as Step 6). On **decline or no action**, append it to `docs/history/backlog.md` at the finding's own P0–P3 severity per the **Declined realignment** branch in Step 6 — same scale, deduped, never a gate.
|
|
76
113
|
|
|
77
114
|
---
|
|
78
115
|
|
|
@@ -19,7 +19,7 @@ You do NOT write the spec — you return structured research that the main agent
|
|
|
19
19
|
First, understand the documentation landscape:
|
|
20
20
|
|
|
21
21
|
1. Read `./docs/README.md` to understand the documentation structure
|
|
22
|
-
2.
|
|
22
|
+
2. If `./docs/features/README.md` exists, read it to see existing feature specs and where the new spec fits; otherwise glob the feature docs directly
|
|
23
23
|
3. If `./docs/memory/spec-mode.md` exists, read it for feature doc inventory, ownership conflict zones, and lessons from previous research
|
|
24
24
|
4. Read 1-2 existing specs in `./docs/features/` to calibrate format and depth
|
|
25
25
|
5. Glob `./docs/{features,modules/*/features}/**/*.md` and read YAML frontmatter (`owns`, `touches`, `depends_on`) from each to build the ownership map.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionEnd": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/session-stop.js\"",
|
|
10
|
+
"async": true
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"PostToolUseFailure": [
|
|
16
|
+
{
|
|
17
|
+
"matcher": "",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-failure.js\"",
|
|
22
|
+
"async": true
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"PostToolUse": [
|
|
28
|
+
{
|
|
29
|
+
"matcher": "Bash",
|
|
30
|
+
"if": "Bash(git commit *)",
|
|
31
|
+
"hooks": [
|
|
32
|
+
{
|
|
33
|
+
"type": "command",
|
|
34
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-commit.js\"",
|
|
35
|
+
"async": true
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|