@nurix/etna 0.18.10 → 0.18.12
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 +13 -13
- package/bin/etna.js +98 -2
- package/data/harness-index.json +9 -1
- package/data/harnesses/code/CLAUDE.md +9 -10
- package/data/harnesses/code/agents/brainstorm.md +3 -3
- package/data/harnesses/code/agents/change-review.md +4 -4
- package/data/harnesses/code/agents/maintenance.md +177 -59
- package/data/harnesses/code/agents/spec-mode.md +3 -3
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/hooks/generate-ownership-index.mjs +148 -0
- package/data/harnesses/code/hooks/hooks.json +12 -0
- package/data/harnesses/code/skills/boundary/references/boundary-taxonomy.md +3 -3
- package/data/harnesses/code/skills/brainstorm/README.md +11 -9
- package/data/harnesses/code/skills/brainstorm/SKILL.md +2 -2
- package/data/harnesses/code/skills/brainstorm/harvest.md +5 -6
- package/data/harnesses/code/skills/distiller/SKILL.md +3 -3
- package/data/harnesses/code/skills/distiller/references/docs-mode.md +7 -7
- package/data/harnesses/code/skills/documentation/SKILL.md +23 -23
- package/data/harnesses/code/skills/documentation/references/templates.md +26 -64
- package/data/harnesses/etna/.prettierignore +12 -0
- package/data/harnesses/etna/.prettierrc.json +3 -0
- package/data/harnesses/etna/.vscode/settings.json +10 -0
- package/data/harnesses/etna/CLAUDE.md +151 -0
- package/data/harnesses/etna/agents/brainstorm.md +35 -0
- package/data/harnesses/etna/agents/change-review.md +251 -0
- package/data/harnesses/etna/agents/maintenance.md +240 -0
- package/data/harnesses/etna/agents/performance-review.md +223 -0
- package/data/harnesses/etna/agents/spec-mode.md +56 -0
- package/data/harnesses/etna/harness.json +11 -0
- package/data/harnesses/etna/hooks/generate-ownership-index.mjs +148 -0
- package/data/harnesses/etna/hooks/hooks.json +39 -0
- package/data/harnesses/etna/hooks/lib/redact.mjs +35 -0
- package/data/harnesses/etna/hooks/prompt-recall.mjs +155 -0
- package/data/harnesses/etna/hooks/session-reflect.mjs +285 -0
- package/data/harnesses/etna/rules/general.md +86 -0
- package/data/harnesses/etna/rules/kb.md +13 -0
- package/data/harnesses/etna/rules/nomenclature.md +41 -0
- package/data/harnesses/etna/rules/npm-cli.md +88 -0
- package/data/harnesses/etna/skills/boundary/SKILL.md +49 -0
- package/data/harnesses/etna/skills/boundary/references/boundary-taxonomy.md +796 -0
- package/data/harnesses/etna/skills/brainstorm/README.md +138 -0
- package/data/harnesses/etna/skills/brainstorm/SKILL.md +135 -0
- package/data/harnesses/etna/skills/brainstorm/harvest.md +25 -0
- package/data/harnesses/etna/skills/brainstorm/templates/backlog.md +39 -0
- package/data/harnesses/etna/skills/brainstorm/templates/build.md +37 -0
- package/data/harnesses/etna/skills/brainstorm/templates/decisions.md +29 -0
- package/data/harnesses/etna/skills/brainstorm/templates/doctrine.md +31 -0
- package/data/harnesses/etna/skills/brainstorm/templates/domain-model.md +25 -0
- package/data/harnesses/etna/skills/brainstorm/templates/glossary.md +20 -0
- package/data/harnesses/etna/skills/brainstorm/templates/milestones.md +43 -0
- package/data/harnesses/etna/skills/brainstorm/templates/non-goals.md +17 -0
- package/data/harnesses/etna/skills/brainstorm/templates/plan-doc.md +61 -0
- package/data/harnesses/etna/skills/brainstorm/templates/questions.md +36 -0
- package/data/harnesses/etna/skills/brainstorm/templates/references.md +28 -0
- package/data/harnesses/etna/skills/brainstorm/templates/rejected.md +29 -0
- package/data/harnesses/etna/skills/brainstorm/templates/state.md +1 -0
- package/data/harnesses/etna/skills/brainstorm/templates/tech.md +23 -0
- package/data/harnesses/etna/skills/brainstorm/templates/thesis.md +21 -0
- package/data/harnesses/etna/skills/distiller/SKILL.md +90 -0
- package/data/harnesses/etna/skills/distiller/references/docs-mode.md +299 -0
- package/data/harnesses/etna/skills/distiller/references/method.md +94 -0
- package/data/harnesses/etna/skills/documentation/SKILL.md +124 -0
- package/data/harnesses/etna/skills/documentation/references/examples.md +162 -0
- package/data/harnesses/etna/skills/documentation/references/style-guide.md +308 -0
- package/data/harnesses/etna/skills/documentation/references/templates.md +853 -0
- package/data/harnesses/etna/skills/workflow-mapper/SKILL.md +451 -0
- package/data/harnesses/etna/skills/workflow-mapper/references/patterns.md +214 -0
- package/data/harnesses/hr/harness.json +1 -1
- package/data/harnesses/hr/skills/evaluator/criteria/testing.md +4 -4
- package/data/harnesses/mobile/.prettierignore +12 -0
- package/data/harnesses/mobile/.prettierrc.json +3 -0
- package/data/harnesses/mobile/.vscode/settings.json +10 -0
- package/data/harnesses/mobile/CLAUDE.md +128 -0
- package/data/harnesses/mobile/agents/brainstorm.md +35 -0
- package/data/harnesses/mobile/agents/change-review.md +251 -0
- package/data/harnesses/mobile/agents/maintenance.md +240 -0
- package/data/harnesses/mobile/agents/performance-review.md +223 -0
- package/data/harnesses/mobile/agents/spec-mode.md +56 -0
- package/data/harnesses/mobile/harness.json +11 -0
- package/data/harnesses/mobile/hooks/generate-ownership-index.mjs +148 -0
- package/data/harnesses/mobile/hooks/hooks.json +39 -0
- package/data/harnesses/mobile/hooks/lib/redact.mjs +35 -0
- package/data/harnesses/mobile/hooks/prompt-recall.mjs +155 -0
- package/data/harnesses/mobile/hooks/session-reflect.mjs +285 -0
- package/data/harnesses/mobile/rules/general.md +86 -0
- package/data/harnesses/mobile/rules/nomenclature.md +41 -0
- package/data/harnesses/mobile/rules/react-native.md +132 -0
- package/data/harnesses/mobile/rules/typescript.md +40 -0
- package/data/harnesses/mobile/skills/boundary/SKILL.md +49 -0
- package/data/harnesses/mobile/skills/boundary/references/boundary-taxonomy.md +796 -0
- package/data/harnesses/mobile/skills/brainstorm/README.md +138 -0
- package/data/harnesses/mobile/skills/brainstorm/SKILL.md +135 -0
- package/data/harnesses/mobile/skills/brainstorm/harvest.md +25 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/backlog.md +39 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/build.md +37 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/decisions.md +29 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/doctrine.md +31 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/domain-model.md +25 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/glossary.md +20 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/milestones.md +43 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/non-goals.md +17 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/plan-doc.md +61 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/questions.md +36 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/references.md +28 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/rejected.md +29 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/state.md +1 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/tech.md +23 -0
- package/data/harnesses/mobile/skills/brainstorm/templates/thesis.md +21 -0
- package/data/harnesses/mobile/skills/distiller/SKILL.md +90 -0
- package/data/harnesses/mobile/skills/distiller/references/docs-mode.md +299 -0
- package/data/harnesses/mobile/skills/distiller/references/method.md +94 -0
- package/data/harnesses/mobile/skills/documentation/SKILL.md +124 -0
- package/data/harnesses/mobile/skills/documentation/references/examples.md +162 -0
- package/data/harnesses/mobile/skills/documentation/references/style-guide.md +308 -0
- package/data/harnesses/mobile/skills/documentation/references/templates.md +853 -0
- package/data/harnesses/mobile/skills/workflow-mapper/SKILL.md +451 -0
- package/data/harnesses/mobile/skills/workflow-mapper/references/patterns.md +214 -0
- package/package.json +1 -1
- package/data/harnesses/code/agents/harness-manager.md +0 -184
package/README.md
CHANGED
|
@@ -28,9 +28,9 @@ your-project/
|
|
|
28
28
|
├── .vscode/settings.json ← editor settings (markdown formatter on save)
|
|
29
29
|
└── .claude/
|
|
30
30
|
├── skills/ ← documentation, boundary, distiller, brainstorm, workflow-mapper
|
|
31
|
-
├── agents/ ←
|
|
31
|
+
├── agents/ ← spec-mode, brainstorm, maintenance, change-review, performance-review
|
|
32
32
|
├── rules/ ← general.md, nomenclature.md
|
|
33
|
-
├── hooks/ ←
|
|
33
|
+
├── hooks/ ← generate-ownership-index (SessionStart), session-reflect (SessionEnd), prompt-recall (UserPromptSubmit) + lib/redact
|
|
34
34
|
├── settings.json ← the hooks are deep-merged in here (your existing keys preserved)
|
|
35
35
|
└── harness.json ← {version, digest} — the installed-harness sentinel
|
|
36
36
|
```
|
|
@@ -43,20 +43,19 @@ Re-run `npx @nurix/etna --name=code` anytime to refresh the harness content with
|
|
|
43
43
|
|
|
44
44
|
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):
|
|
45
45
|
|
|
46
|
-
1. **"baseline this repo"** (or "reconcile the harness") — runs the **`
|
|
47
|
-
2. If it
|
|
48
|
-
3. **"reconcile the harness"** again — `harness-manager` now proposes **modules** from those boundaries.
|
|
46
|
+
1. **"baseline this repo"** (or "reconcile the harness") — runs the **`maintenance`** agent, which auto-selects **baseline mode** from the missing/behind `harness-version`. It builds the `docs/` tree to the harness shape, stamps `docs/structure.md` with the installed `harness-version`, proposes **modules** (as `module:` labels, derived from your code's bounded contexts), then hands back.
|
|
47
|
+
2. If it hands back with a follow-up (typically the module proposal), re-invoke it to finish.
|
|
49
48
|
|
|
50
|
-
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.
|
|
49
|
+
Then work normally. Boundaries aren't generated as files — the `boundary` skill diagnoses cross-cutting contracts from source at plan time. This brownfield path reads your _actual_ code, not a blank slate, so it's safe to run on an existing project with real history.
|
|
51
50
|
|
|
52
51
|
Two things worth knowing on an existing repo:
|
|
53
52
|
|
|
54
53
|
- **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.
|
|
55
|
-
- **
|
|
54
|
+
- **Memory capture starts on your next session.** The `session-reflect` hook (SessionEnd) distils each session's lessons into the per-actor inbox `docs/memory/inbox/`; once enough entries accrue, the `maintenance` agent _proposes_ a `distiller` run that distils them into provenance-linked fact-cards under `docs/memory/facts/` and, eventually, proposed `docs/` updates. Nothing runs on its own — it proposes, you approve.
|
|
56
55
|
|
|
57
56
|
### Optional: semantic retrieval (qmd)
|
|
58
57
|
|
|
59
|
-
If the [`qmd`](https://www.npmjs.com/package/@tobilu/qmd) binary is on your `PATH`, `
|
|
58
|
+
If the [`qmd`](https://www.npmjs.com/package/@tobilu/qmd) binary is on your `PATH`, the `maintenance` agent (baseline mode) registers per-subtree search collections so the distiller and the doc anchors get semantic retrieval. It's strictly optional — absent, discovery rides the generated ownership index + `keywords:` frontmatter and everything still works on a filename/grep fallback. Install it whenever.
|
|
60
59
|
|
|
61
60
|
## Migrating from 0.1.x
|
|
62
61
|
|
|
@@ -77,25 +76,26 @@ Listed by `npx @nurix/etna` (no args) using the bundled `data/harness-index.json
|
|
|
77
76
|
|
|
78
77
|
Each harness has two halves in the etna repo:
|
|
79
78
|
|
|
80
|
-
- `plugins/<name>/` — the **plugin** (what a Claude Code plugin natively bundles): `definition.md` (manifest — identity + `skills:`/`rules:` lists), `
|
|
79
|
+
- `plugins/<name>/` — the **plugin** (what a Claude Code plugin natively bundles): `definition.md` (manifest — identity + `skills:`/`rules:`/`agents:` lists), `hooks/`, and any unlisted, harness-specific `agents/`.
|
|
81
80
|
- `harness/<name>/` — the **policy**: `CLAUDE.md` (pure policy, no frontmatter — installed as a verbatim copy) plus `index.md`, whose `plugin:` key names the plugin this harness pairs with. All harness-folder frontmatter lives in `index.md` alone.
|
|
82
81
|
|
|
83
82
|
Two kinds of membership:
|
|
84
83
|
|
|
85
|
-
- **Shared, listed** — skills and
|
|
84
|
+
- **Shared, listed** — skills, rules, and agents live in the flat top-level `skills/`/`rules/`/`agents/` catalogs, reusable across plugins. The manifest names the ones it pulls in:
|
|
86
85
|
|
|
87
86
|
```yaml
|
|
88
87
|
# plugins/code/definition.md
|
|
89
88
|
---
|
|
90
89
|
name: code
|
|
91
90
|
skills: [boundary, brainstorm, distiller, documentation, workflow-mapper]
|
|
92
|
-
rules: [general, nomenclature]
|
|
91
|
+
rules: [general, nomenclature, typescript]
|
|
92
|
+
agents: [brainstorm, change-review, maintenance, performance-review, spec-mode]
|
|
93
93
|
---
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
- **Owned, by location** —
|
|
96
|
+
- **Owned, by location** — hooks (and any unlisted, harness-specific agents) live inside the plugin (`plugins/<name>/{agents,hooks}/`). No list — membership is presence in the folder, exactly how a Claude Code plugin ships them. Shared governance agents are listed instead (above) and resolved from the `agents/` catalog, local-first; a plugin can still drop its own copy beside it to override one by name.
|
|
97
97
|
|
|
98
|
-
`scripts/build-data.js` (run by `prepack`) resolves the listed skills/rules from the catalogs
|
|
98
|
+
`scripts/build-data.js` (run by `prepack`) resolves the listed skills/rules/agents from the catalogs (local-first), copies any unlisted plugin-owned agents plus the plugin's `hooks/` wholesale into `data/harnesses/<folder>/`; a listed skill/rule/agent that doesn't exist is a hard error. The marketplace channel (`.github/scripts/publish.mjs`) reads the same plugin folder, so both channels resolve identical membership — and both ship the plugin's hooks. The installer (`bin/etna.js`) reads from the bundle at install time; no GitHub fetch at runtime.
|
|
99
99
|
|
|
100
100
|
## Building / contributing
|
|
101
101
|
|
package/bin/etna.js
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
// prompts you to pick one. Drops the harness's CLAUDE.md + project-root config
|
|
6
6
|
// files (.prettierrc.json, .vscode/settings.json, …) at the project root, and
|
|
7
7
|
// copies its skills, agents, rules, scripts, and hooks into <project>/.claude/.
|
|
8
|
+
// Agents — the brainstorm facilitator plus the generic governance agents
|
|
9
|
+
// (spec-mode, maintenance, change-review, performance-review) —
|
|
10
|
+
// are vendored per-project into <project>/.claude/agents/, the same lifecycle as
|
|
11
|
+
// skills and rules. The CLI writes only inside the target folder; nothing global.
|
|
8
12
|
// No marketplace, no plugin enablement — files live in the project,
|
|
9
13
|
// version-controlled and editable per project. Re-run `etna` to refresh.
|
|
10
14
|
// Harnesses are addressed by their harness/<folder> name; several harnesses may
|
|
@@ -23,6 +27,7 @@ import {
|
|
|
23
27
|
} from 'node:fs';
|
|
24
28
|
import { join, dirname, resolve } from 'node:path';
|
|
25
29
|
import { fileURLToPath } from 'node:url';
|
|
30
|
+
import { spawnSync } from 'node:child_process';
|
|
26
31
|
import * as p from '@clack/prompts';
|
|
27
32
|
|
|
28
33
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -180,6 +185,51 @@ function retireOldHooks(targetDir) {
|
|
|
180
185
|
return removed;
|
|
181
186
|
}
|
|
182
187
|
|
|
188
|
+
// Component subdirs whose membership is tracked so a removed-from-the-harness component
|
|
189
|
+
// can be pruned on refresh. Hooks are excluded — retireOldHooks owns them (it must also
|
|
190
|
+
// prune the matching settings.json entry, which a name-only diff can't reach).
|
|
191
|
+
const PRUNABLE_SUBDIRS = ['skills', 'agents', 'rules', 'scripts'];
|
|
192
|
+
|
|
193
|
+
// The set of component names the bundle ships, per prunable subdir — the manifest of what
|
|
194
|
+
// THIS harness owns. Written into the installed harness.json so the next refresh can diff.
|
|
195
|
+
function bundledOwned(bundledHarnessDir) {
|
|
196
|
+
const owned = {};
|
|
197
|
+
for (const sub of PRUNABLE_SUBDIRS) {
|
|
198
|
+
const dir = join(bundledHarnessDir, sub);
|
|
199
|
+
owned[sub] = existsSync(dir) ? readdirSync(dir).sort() : [];
|
|
200
|
+
}
|
|
201
|
+
return owned;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Read the `owned` manifest the PREVIOUS install recorded in the project's harness.json.
|
|
205
|
+
// Absent (first install, or pre-pruning version) ⇒ {} ⇒ nothing is pruned this round.
|
|
206
|
+
function readInstalledOwned(targetDir) {
|
|
207
|
+
const path = join(targetDir, '.claude', 'harness.json');
|
|
208
|
+
if (!existsSync(path)) return {};
|
|
209
|
+
const owned = readJson(path).owned;
|
|
210
|
+
return owned && typeof owned === 'object' ? owned : {};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Remove vendored components a prior harness version owned but this one no longer ships
|
|
214
|
+
// (e.g. a retired agent like harness-manager). Only names present in the PRIOR `owned`
|
|
215
|
+
// manifest are eligible, so a project's own components — never in that manifest — survive.
|
|
216
|
+
function pruneOrphanedComponents(targetDir, priorOwned, newOwned) {
|
|
217
|
+
const removed = [];
|
|
218
|
+
for (const sub of PRUNABLE_SUBDIRS) {
|
|
219
|
+
const had = priorOwned[sub] || [];
|
|
220
|
+
const has = new Set(newOwned[sub] || []);
|
|
221
|
+
for (const name of had) {
|
|
222
|
+
if (has.has(name)) continue;
|
|
223
|
+
const target = join(targetDir, '.claude', sub, name);
|
|
224
|
+
if (existsSync(target)) {
|
|
225
|
+
rmSync(target, { recursive: true, force: true });
|
|
226
|
+
removed.push(`orphaned ${sub.replace(/s$/, '')} ${name}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return removed;
|
|
231
|
+
}
|
|
232
|
+
|
|
183
233
|
// True if an event's matcher-group already carries a hook with this command string.
|
|
184
234
|
function groupHasCommand(group, command) {
|
|
185
235
|
return (group.hooks || []).some((h) => h.command === command);
|
|
@@ -299,16 +349,28 @@ async function installHarness(harnessName, harnesses) {
|
|
|
299
349
|
cpSync(bundledClaudeMd, claudeMdDest, { force: true });
|
|
300
350
|
installed.push('CLAUDE.md → ./');
|
|
301
351
|
|
|
352
|
+
// What a prior install recorded as harness-owned — read before we overwrite the marker,
|
|
353
|
+
// so we can prune anything this bundle has since dropped.
|
|
354
|
+
const priorOwned = readInstalledOwned(targetDir);
|
|
355
|
+
|
|
302
356
|
for (const sub of PLUGIN_SUBDIRS) {
|
|
303
357
|
const count = copyHarnessSubdir(join(bundledHarnessDir, sub), join(targetDir, '.claude', sub));
|
|
304
358
|
if (count > 0) installed.push(`${count} ${sub} → .claude/${sub}/`);
|
|
305
359
|
}
|
|
306
360
|
|
|
307
|
-
//
|
|
361
|
+
// Prune components a prior harness version owned but this bundle no longer ships (e.g. a
|
|
362
|
+
// retired agent). Only prior-owned names are eligible, so the user's own files survive.
|
|
363
|
+
const newOwned = bundledOwned(bundledHarnessDir);
|
|
364
|
+
for (const r of pruneOrphanedComponents(targetDir, priorOwned, newOwned)) notices.push(`removed ${r}`);
|
|
365
|
+
|
|
366
|
+
// Write the harness marker — plus the `owned` manifest the next refresh diffs against —
|
|
367
|
+
// so the maintenance agent (baseline mode) can resolve the installed version.
|
|
308
368
|
if (existsSync(bundledHarnessJson)) {
|
|
309
369
|
const harnessJsonDest = join(targetDir, '.claude', 'harness.json');
|
|
310
370
|
mkdirSync(dirname(harnessJsonDest), { recursive: true });
|
|
311
|
-
|
|
371
|
+
const marker = readJson(bundledHarnessJson);
|
|
372
|
+
marker.owned = newOwned;
|
|
373
|
+
writeJson(harnessJsonDest, marker);
|
|
312
374
|
}
|
|
313
375
|
|
|
314
376
|
// Verbatim project-root files bundled outside the plugin route (.prettierrc.json,
|
|
@@ -364,6 +426,36 @@ function parseArgs(args) {
|
|
|
364
426
|
return { name, help, positional };
|
|
365
427
|
}
|
|
366
428
|
|
|
429
|
+
// npx caches aggressively: a bare `npx @nurix/etna` can re-run a STALE cached copy
|
|
430
|
+
// long after a newer version is published (npm's packument cache resolves an old
|
|
431
|
+
// `latest`, and npx reuses the matching unpacked install). To make `npx @nurix/etna`
|
|
432
|
+
// always run the latest WITHOUT the caller remembering flags, check the registry on
|
|
433
|
+
// startup and, if this copy is behind, re-exec the latest — `--prefer-online` defeats
|
|
434
|
+
// the metadata cache and npx fetches the fresh tarball.
|
|
435
|
+
//
|
|
436
|
+
// Guards: ETNA_SELFUPDATED=1 is set on the child to stop an infinite re-exec loop;
|
|
437
|
+
// ETNA_NO_SELFUPDATE lets a caller opt out (CI, offline, or running an intentionally
|
|
438
|
+
// pinned/local-dev build). Degrades gracefully — any failure (offline, unauthed, npm/
|
|
439
|
+
// npx missing) falls through to running THIS copy. @nurix/etna is restricted, so the
|
|
440
|
+
// `npm view` reuses the caller's existing npm auth — the same auth that installed it.
|
|
441
|
+
function selfUpdateOrReexec() {
|
|
442
|
+
if (process.env.ETNA_SELFUPDATED || process.env.ETNA_NO_SELFUPDATE) return;
|
|
443
|
+
const here = readJson(join(pkgRoot, 'package.json')).version;
|
|
444
|
+
if (!here) return;
|
|
445
|
+
const view = spawnSync('npm', ['view', '@nurix/etna', 'version', '--prefer-online'], {
|
|
446
|
+
encoding: 'utf8',
|
|
447
|
+
});
|
|
448
|
+
const latest = view.status === 0 ? (view.stdout || '').trim() : null;
|
|
449
|
+
if (!latest || latest === here) return; // current, offline, or unauthed → run this copy
|
|
450
|
+
const child = spawnSync(
|
|
451
|
+
'npx',
|
|
452
|
+
['-y', '--prefer-online', `@nurix/etna@${latest}`, ...process.argv.slice(2)],
|
|
453
|
+
{ stdio: 'inherit', env: { ...process.env, ETNA_SELFUPDATED: '1' } },
|
|
454
|
+
);
|
|
455
|
+
if (!child.error) process.exit(child.status ?? 0); // re-exec ran → hand off complete
|
|
456
|
+
// npx unavailable (child.error) → fall through and run this (older) copy.
|
|
457
|
+
}
|
|
458
|
+
|
|
367
459
|
async function main(args) {
|
|
368
460
|
const idx = loadHarnessIndex();
|
|
369
461
|
const harnesses = idx.harnesses || {};
|
|
@@ -405,6 +497,10 @@ async function main(args) {
|
|
|
405
497
|
await installHarness(harnessName, harnesses);
|
|
406
498
|
}
|
|
407
499
|
|
|
500
|
+
// Before anything else: if a newer @nurix/etna is published, re-exec it (so a stale
|
|
501
|
+
// npx-cached run still installs the latest harness). No-op when already current.
|
|
502
|
+
selfUpdateOrReexec();
|
|
503
|
+
|
|
408
504
|
main(process.argv.slice(2)).catch((e) => {
|
|
409
505
|
p.log.error(e?.stack || String(e));
|
|
410
506
|
process.exit(1);
|
package/data/harness-index.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"fingerprint": "
|
|
2
|
+
"fingerprint": "acbb6d687e95",
|
|
3
3
|
"harnesses": {
|
|
4
4
|
"code": {
|
|
5
5
|
"plugin": "code",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"plugin": "discover",
|
|
10
10
|
"description": "Product Discovery harness"
|
|
11
11
|
},
|
|
12
|
+
"etna": {
|
|
13
|
+
"plugin": "etna",
|
|
14
|
+
"description": "the master store's own harness"
|
|
15
|
+
},
|
|
12
16
|
"hr": {
|
|
13
17
|
"plugin": "hr",
|
|
14
18
|
"description": "Hiring / AI-Steering Evaluation harness"
|
|
@@ -16,6 +20,10 @@
|
|
|
16
20
|
"kb-agi": {
|
|
17
21
|
"plugin": "kb-agi",
|
|
18
22
|
"description": "self-improving knowledge-base harness"
|
|
23
|
+
},
|
|
24
|
+
"mobile": {
|
|
25
|
+
"plugin": "mobile",
|
|
26
|
+
"description": "Nurix React Native + Expo harness"
|
|
19
27
|
}
|
|
20
28
|
}
|
|
21
29
|
}
|
|
@@ -6,9 +6,9 @@ This file defines how AI agents (Claude, GPT, etc.) should work on this project.
|
|
|
6
6
|
|
|
7
7
|
## Bootstrap
|
|
8
8
|
|
|
9
|
-
> A missing `docs/structure.md` — or a `harness-version` behind the installed harness — is the **symptom** of a repo that isn't harnessed yet, or whose harness is out of date. Don't reconcile it by hand. Drive the **reconcile chain** — you (the main session) own it, because `
|
|
9
|
+
> A missing `docs/structure.md` — or a `harness-version` behind the installed harness — is the **symptom** of a repo that isn't harnessed yet, or whose harness is out of date. Don't reconcile it by hand. Drive the **reconcile chain** — you (the main session) own it, because `maintenance` is a sub-agent that can't run skills or spawn sub-agents:
|
|
10
10
|
>
|
|
11
|
-
> 1. Invoke **`
|
|
11
|
+
> 1. Invoke **`maintenance`** (`"baseline this repo"` / `"reconcile the harness"`). It auto-selects **baseline mode** from the missing/behind `harness-version`, reconciles `docs/`, stamps the version, then hands back.
|
|
12
12
|
> 2. If it hands back with a follow-up (typically the module proposal), re-invoke it to finish.
|
|
13
13
|
>
|
|
14
14
|
> Then continue. Each component's internals live in the component, not here.
|
|
@@ -36,7 +36,7 @@ Three peer modes plus a Completion step — enter wherever the user's thinking a
|
|
|
36
36
|
| **Spec** | new feature work | `spec-mode` agent + `documentation` skill | Plan |
|
|
37
37
|
| **Plan** | a spec is approved | `documentation` skill → `docs/history/plans/YYYY-MM-DD-{slug}.md` | Completion |
|
|
38
38
|
|
|
39
|
-
On **Completion**: freeze the plan (`Status: Complete`) and flip the feature spec to `
|
|
39
|
+
On **Completion**: freeze the plan (`Status: Complete`) and flip the feature spec to `status: active`, extending it with how-it-works sections and confirming its `owns:` globs against the actual code; then distil the plan's trail — if the memory inbox (`docs/memory/inbox/`) holds entries, run `distiller facts` (inbox→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 gates every approved plan before coding — it reads the source the plan touches and reports what would break (see **Boundaries** below); a brainstorm session is written to a **kept** record at `docs/brainstorm/{topic}/` (committed, one subfolder per topic), and on close harvest distils its conclusions by scope — doctrine, glossary, non-goals, and information-architecture changes → `docs/architecture/`, decisions → `docs/architecture/adr/`, backlog → `docs/history/backlog.md` — leaving the `docs/brainstorm/{topic}/` record in place as the provenance. Open questions block close.
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
@@ -44,11 +44,10 @@ On **Completion**: freeze the plan (`Status: Complete`) and flip the feature spe
|
|
|
44
44
|
|
|
45
45
|
Before modifying code in an unfamiliar area:
|
|
46
46
|
|
|
47
|
-
1.
|
|
48
|
-
2. If `qmd` is installed, `qmd query --format json -c <collection> "<topic>"` (repeat `-c` to span this repo's collections; `qmd collection list` names them)
|
|
47
|
+
1. Consult the **injected ownership index** — the generated `glob → doc · module · keywords · status` map, surfaced into context at session start (the union of every feature doc's `owns:` frontmatter). Match your path or topic (its `keywords:`) to the owning feature doc and read it before changes. (If no ownership index appears in context, the repo isn't reconciled — see Bootstrap.)
|
|
48
|
+
2. If `qmd` is installed, run `qmd query --format json -c <collection> "<topic>"` (repeat `-c` to span this repo's collections; `qmd collection list` names them) for semantic recall over the indexed docs — prefer it when entering an unfamiliar area; absent ⇒ skip.
|
|
49
49
|
3. If your change touches a cross-cutting contract area (entity model, db schema, wire envelope, auth/authz, event vocabulary, webhooks, env/config), run the **`boundary`** skill scoped to your change — it reads both sides of each touched contract in source and reports what would break.
|
|
50
|
-
4. If a `docs/memory/` file exists for the domain you're working in, read it for domain knowledge and failure modes
|
|
51
|
-
5. 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 1.
|
|
50
|
+
4. If a `docs/memory/` file exists for the domain you're working in, read it for domain knowledge and failure modes.
|
|
52
51
|
|
|
53
52
|
For the protocol on _writing_ to memory (which file, what belongs there, what doesn't), see **Project Memory** below.
|
|
54
53
|
|
|
@@ -82,9 +81,9 @@ When implementing a plan, if you discover work that is important but outside the
|
|
|
82
81
|
|
|
83
82
|
## Doc Ownership
|
|
84
83
|
|
|
85
|
-
Which doc is canonical for which code paths
|
|
84
|
+
Which doc is canonical for which code paths is the **feature doc's own frontmatter**. A feature doc (flat in `docs/features/`) carries `owns:` (code-path globs — the only ownership key), `module:` (a bounded-context label, omitted when cross-cutting), `keywords:` (discovery), and `status:`. Code → doc ownership is the union of every feature doc's `owns:` globs, surfaced as the generated ownership index at session start. A module is tied to code by the `owns:` globs of the docs carrying its `module:` label — not a folder, not a separate map. Architecture docs describe cross-cutting patterns and don't own specific files; operations docs describe procedures. A living doc may additionally carry an optional `anchors:` block (a qmd drift tripwire, not ownership).
|
|
86
85
|
|
|
87
|
-
The spec-mode agent checks
|
|
86
|
+
The spec-mode agent checks for ownership conflicts during research — feature docs whose `owns:` globs overlap at equal specificity. If conflicts are found, address them in the spec before proceeding.
|
|
88
87
|
|
|
89
88
|
---
|
|
90
89
|
|
|
@@ -120,4 +119,4 @@ Unless explicitly requested:
|
|
|
120
119
|
Maintenance triggers (act on these without re-reading `structure.md`):
|
|
121
120
|
|
|
122
121
|
- After plan completion: add a `docs/history/changelog/` entry + update `docs/history/CHANGELOG.md` (no confirmation; include `Commits:` if multi-commit). Non-plan changes (refactors, fixes, API changes): prompt first.
|
|
123
|
-
- When you
|
|
122
|
+
- When you move or rename source files: update the `owns:` globs of the feature doc that owns them so the generated ownership index stays accurate. A new doc that owns code declares its paths in its own `owns:` frontmatter — there is no separate map to update.
|
|
@@ -14,7 +14,7 @@ You are a **thin orchestrator.** Every mechanic — the constant loop, the four
|
|
|
14
14
|
## When to offer a session
|
|
15
15
|
|
|
16
16
|
- 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`.
|
|
17
|
-
- 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
|
|
17
|
+
- 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 its `adr/` decision titles. The steering layer lives there; there are no `docs/modules/` or `docs/pillars/` folders. 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`.
|
|
18
18
|
|
|
19
19
|
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.
|
|
20
20
|
|
|
@@ -28,8 +28,8 @@ Four modes exist — **dump / interview / probe / converge** — and they change
|
|
|
28
28
|
- **Suggest, never seize.** Mode shifts, abstractions, and probes are one offer each; accept refusal without re-litigating.
|
|
29
29
|
- **Never write production code** while a session is open — load-bearing snippets are illustration only, inside the session folder.
|
|
30
30
|
|
|
31
|
-
You don't perform the harvest yourself, and you don't presume a destination. On close the
|
|
31
|
+
You don't perform the harvest yourself, and you don't presume a destination. On close the skill **offers** the harvest, distilling the pillars' conclusions into wherever the harness routes them. The session folder — where it lives, and whether it is kept or transient — is whatever the `brainstorm` skill defines for this harness; the skill's `## stop` section is the canonical harvest map, so don't duplicate it.
|
|
32
32
|
|
|
33
33
|
## What you may write
|
|
34
34
|
|
|
35
|
-
The
|
|
35
|
+
The session folder only (its pillars and `state.md`), wherever the `brainstorm` skill places it for this harness. Never edit the live docs directly — the harvest does that.
|
|
@@ -33,7 +33,7 @@ The heatmap needs rolling 1w / 1m / 3m windows, so default `since` to 3 months e
|
|
|
33
33
|
|
|
34
34
|
1. If `./docs/memory/change-review.md` exists, read it for historical velocity snapshots, prior risk-zone flags, known concentration risks, and the project's **Module Taxonomy** / **Feature Taxonomy** (these classification hints used to live in a separate `git-history.md` memory file — they now live here).
|
|
35
35
|
2. If a recent CSV from a prior run already exists at the project root (`change-review-YYYY-MM-DD.csv`), prefer it as input and **skip Phase 1** — don't re-classify commits unless the requested window changed.
|
|
36
|
-
3.
|
|
36
|
+
3. Build the ownership map from feature `owns:` frontmatter — consult the injected ownership index (`glob → doc · module · keywords · status`), or glob `docs/features/**/*.md` and read each doc's `owns:` globs — to map churn to owning feature docs (and their `module:` labels).
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
@@ -207,8 +207,8 @@ Trend column: ↑ rising, ↓ falling, → steady, ↑↑ sharply rising, ↓↓
|
|
|
207
207
|
For each module with high churn (top quartile in 1m window), check how contract-heavy and how covered it is:
|
|
208
208
|
|
|
209
209
|
- Does its code own cross-cutting contract surfaces (schema/data access, wire handlers, auth/authz, event dispatch, env/config readers)? Skim the module's files to judge — contract-heavy churn is where silent breakage lives.
|
|
210
|
-
- Is the module's
|
|
211
|
-
-
|
|
210
|
+
- Is the module's owning feature doc up to date? (cross-reference with the maintenance agent's last drift check if available)
|
|
211
|
+
- Do the module's code paths appear under any feature doc's `owns:` globs?
|
|
212
212
|
|
|
213
213
|
Flag risk zones: high churn × contract-heavy × low doc coverage. These are where silent breakage is most likely.
|
|
214
214
|
|
|
@@ -231,7 +231,7 @@ Per module, list which authors made the most commits in the 3-month window. Surf
|
|
|
231
231
|
- **Risk zones**: top 5 high-churn modules that own contract-heavy surfaces, with the specific contracts at risk named
|
|
232
232
|
- **Stabilization signals**: modules where review overhead can decrease
|
|
233
233
|
- **Concentration risks**: bus-factor and review-coverage flags
|
|
234
|
-
- **Recommendations**: prioritized list — typically "run the `boundary` skill on changes touching X", "
|
|
234
|
+
- **Recommendations**: prioritized list — typically "run the `boundary` skill on changes touching X", "add `owns:` coverage for Y to a feature doc", "spread reviews of Z across more authors"
|
|
235
235
|
|
|
236
236
|
### Update tracking
|
|
237
237
|
|