@nurix/etna 0.20.24 → 0.20.25

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "fingerprint": "73da5d8c28fc",
2
+ "fingerprint": "c24bc6f1af10",
3
3
  "harnesses": {
4
4
  "code": {
5
5
  "plugin": "code",
@@ -157,7 +157,7 @@ Defaults — ❌ prohibitions hold unless the user explicitly asks otherwise;
157
157
  - ✅ **Verification** — batched at the gate, never continuous, and the gate is the build's last step, never a separate ask; the regimes and the gate are `.claude/rules/testing.md`, the procedure the `testing` skill (gate mode).
158
158
  - ❌ **Dependencies** – Never upgrade pinned packages without asking; security patches and test-tooling installs at `latest` (`testing.md`) are exempt.
159
159
  - ❌ **Compatibility** – Never maintain backward compatibility unless specified.
160
- - ❌ **Branches & Worktrees [override]** – Never self-create git branches or worktrees; commit straight to the checked-out branch. This replaces any built-in "branch first on the default branch" default. Exception: when the user asks for a pull request, create the branch the PR requires before committing its changes.
160
+ - ❌ **Branches & Worktrees [override]** – `dev` and `production` are merge-only. Work on a short-lived branch cut from `dev` (`feat/<slug>`, `fix/<slug>`, `docs/<slug>`; `hotfix/<slug>` off `production`), commit there, and open a pull request into `dev` (or `production` for a hotfix) — never commit to either long-lived branch, and never create a worktree. The branch is created when the first commit is due, not at session start. This replaces any built-in "branch first on the default branch" default: the branch is required, and it is never `main`.
161
161
  - ❌ **Protected Files** – Once per session, if `docs/operations/protected-files.md` exists, read it and never modify any path it lists; never modify `AGENTS.md` (harness-regenerated) or `protected-files.md` itself.
162
162
  - ✅ **Browser Automation** – If the runtime provides browser automation, use it freely; no need to ask first. If it doesn't — or the executing agent can't hold the browser connection (see Runtime Notes) — return a browser-verification checklist (URLs, actions, expected outcomes) for the orchestrating session or the user to run.
163
163
  - ✅ **Bookkeeping [override]** — dispatching outranks a runtime default that reserves sub-agents for explicit requests (§ Delegation), and `hooks/compliance.mjs` enforces it: Stop is blocked on unresolved rows unless the transcript carries a real `subagent_type: bookkeeper` record. Hooks append doc-debt rows to `docs/operations/bookkeeping.md`; three walls read it at three widths (Stop = your session's rows · commit = rows implicating staged files · CI = all), each re-verifying conditions at gate time. Dispatch the `bookkeeper` for your rows — **never drain `auto:`/`note:` rows in your own loop**, whichever agent you are: the drain is dispatched work at every nesting level (a sub-agent dispatches its own `bookkeeper` — § Delegation), and only `needs-session:` rows are yours to resolve directly. A drained row is **deleted**. The full grammar arrives in the deny message when a wall fires.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "3.19.2",
4
+ "version": "3.19.3",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -25,5 +25,5 @@
25
25
  }
26
26
  ],
27
27
  "docsRoot": "docs",
28
- "digest": "42dd88faa621"
28
+ "digest": "5181bf532ddd"
29
29
  }
@@ -8,16 +8,16 @@
8
8
  },
9
9
  "hooks": {
10
10
  "SessionStart": [
11
- { "matcher": "", "hooks": [ { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/awareness.mjs\"", "timeout": 10 } ] }
11
+ { "matcher": "", "hooks": [ { "type": "command", "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/awareness.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"", "timeout": 10 } ] }
12
12
  ],
13
13
  "PostToolUse": [
14
- { "matcher": "Edit|Write|MultiEdit|Bash", "hooks": [ { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/awareness.mjs\"", "timeout": 10 }, { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/build-graph.mjs\"", "timeout": 10 } ] }
14
+ { "matcher": "Edit|Write|MultiEdit|Bash", "hooks": [ { "type": "command", "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/awareness.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"", "timeout": 10 }, { "type": "command", "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/build-graph.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"", "timeout": 10 } ] }
15
15
  ],
16
16
  "PreToolUse": [
17
- { "matcher": "Edit|Write|MultiEdit|Bash", "hooks": [ { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/compliance.mjs\"", "timeout": 10 } ] }
17
+ { "matcher": "Edit|Write|MultiEdit|Bash", "hooks": [ { "type": "command", "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/compliance.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"", "timeout": 10 } ] }
18
18
  ],
19
19
  "Stop": [
20
- { "matcher": "", "hooks": [ { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/compliance.mjs\"", "timeout": 15 } ] }
20
+ { "matcher": "", "hooks": [ { "type": "command", "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/compliance.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"", "timeout": 15 } ] }
21
21
  ]
22
22
  }
23
23
  }
@@ -1,6 +1,6 @@
1
1
  # Component catalog — the fused index
2
2
 
3
- > **Generated folder — never hand-edit.** Everything under `skills/design/components/` is emitted by the components-repo regen instruction set (`docs/playbooks/skills/case-studies/components-regen-spec.md`, run against the `@nurix/components` library checkout). Regeneration may reshape any file beneath this one; the design skill's only seam with this folder is this index.
3
+ > **Generated folder — never hand-edit.** Everything under `skills/design/components/` is emitted by the components-repo regen instruction set (`docs/skills/design/components-regen-spec.md`, run against the `@nurix/components` library checkout). Regeneration may reshape any file beneath this one; the design skill's only seam with this folder is this index.
4
4
 
5
5
  **Load contract.** This index loads in **both** positions of the project's `primitives` switch (`design.md`, `primitives: nurix | native`) — it is how the skill knows what chrome *exists* to compose with. The per-category deep references beside it ([inputs](inputs.md), [overlays](overlays.md), [collections](collections.md), [navigation](navigation.md), [layout](layout.md), [feedback](feedback.md), [charts](charts.md), [typography](typography.md), [media](media.md), [motion](motion.md), [hooks](hooks.md), [utils](utils.md), plus [setup](setup.md) for install/Tailwind wiring) load **only under `primitives: nurix`**, and only when that category is touched.
6
6
 
@@ -113,7 +113,7 @@ The two guarantees are complementary and both break on mixing: a reader holding
113
113
  | Derive/update an owning doc's `owns:` (feature, package, system) | [`references/frontmatter.md`](references/frontmatter.md) |
114
114
  | Update existing doc | § Editing an existing doc (below) |
115
115
  | Instruction file (`CLAUDE.md`, a rule, an agent, skill prose) | [`references/instruction-style.md`](references/instruction-style.md) — not these templates; every-load prose stays lean, and the present-tense rule still applies |
116
- | Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`10`. **No etna checkout available ⇒ ask the user for access to the etna repo, or proceed from first principles** with [`references/instruction-style.md`](references/instruction-style.md) governing the prose. This row is the only place the playbook is referenced |
116
+ | Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`13`. **No etna checkout available ⇒ ask the user for access to the etna repo, or proceed from first principles** with [`references/instruction-style.md`](references/instruction-style.md) governing the prose. This row is the only place the playbook is referenced |
117
117
  | **No template matches** | [`references/untemplated.md`](references/untemplated.md) — shape decisions + the co-authoring workflow |
118
118
 
119
119
  ---
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "delivery",
3
3
  "name": "delivery",
4
- "version": "0.2.35",
4
+ "version": "0.2.36",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -10,5 +10,5 @@
10
10
  "seeds": [],
11
11
  "tools": [],
12
12
  "docsRoot": "docs",
13
- "digest": "f4807d25316f"
13
+ "digest": "c2d4fb33502b"
14
14
  }
@@ -6,7 +6,7 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/generate-ownership-index.mjs\"",
9
+ "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/generate-ownership-index.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"",
10
10
  "timeout": 10
11
11
  }
12
12
  ]
@@ -113,7 +113,7 @@ The two guarantees are complementary and both break on mixing: a reader holding
113
113
  | Derive/update an owning doc's `owns:` (feature, package, system) | [`references/frontmatter.md`](references/frontmatter.md) |
114
114
  | Update existing doc | § Editing an existing doc (below) |
115
115
  | Instruction file (`CLAUDE.md`, a rule, an agent, skill prose) | [`references/instruction-style.md`](references/instruction-style.md) — not these templates; every-load prose stays lean, and the present-tense rule still applies |
116
- | Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`10`. **No etna checkout available ⇒ ask the user for access to the etna repo, or proceed from first principles** with [`references/instruction-style.md`](references/instruction-style.md) governing the prose. This row is the only place the playbook is referenced |
116
+ | Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`13`. **No etna checkout available ⇒ ask the user for access to the etna repo, or proceed from first principles** with [`references/instruction-style.md`](references/instruction-style.md) governing the prose. This row is the only place the playbook is referenced |
117
117
  | **No template matches** | [`references/untemplated.md`](references/untemplated.md) — shape decisions + the co-authoring workflow |
118
118
 
119
119
  ---
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "discussion",
3
3
  "name": "discussion",
4
- "version": "0.6.1",
4
+ "version": "0.6.2",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -10,5 +10,5 @@
10
10
  "seeds": [],
11
11
  "tools": [],
12
12
  "docsRoot": "docs",
13
- "digest": "37b98928394e"
13
+ "digest": "ed9548ee592a"
14
14
  }
@@ -5,7 +5,7 @@
5
5
  "hooks": [
6
6
  {
7
7
  "type": "command",
8
- "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/update-topics.sh",
8
+ "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/update-topics.sh\"; [ -f \"$f\" ] || exit 0; bash \"$f\"",
9
9
  "timeout": 15,
10
10
  "statusMessage": "Syncing workspace metadata"
11
11
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "etna",
3
3
  "name": "etna",
4
- "version": "1.5.25",
4
+ "version": "1.5.26",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -10,5 +10,5 @@
10
10
  "seeds": [],
11
11
  "tools": [],
12
12
  "docsRoot": "docs",
13
- "digest": "d84075a6eda5"
13
+ "digest": "6448aac5edb5"
14
14
  }
@@ -6,7 +6,7 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/generate-ownership-index.mjs\"",
9
+ "command": "f=\"$CLAUDE_PROJECT_DIR/.claude/hooks/generate-ownership-index.mjs\"; [ -f \"$f\" ] || exit 0; node \"$f\"",
10
10
  "timeout": 10
11
11
  }
12
12
  ]
@@ -113,7 +113,7 @@ The two guarantees are complementary and both break on mixing: a reader holding
113
113
  | Derive/update an owning doc's `owns:` (feature, package, system) | [`references/frontmatter.md`](references/frontmatter.md) |
114
114
  | Update existing doc | § Editing an existing doc (below) |
115
115
  | Instruction file (`CLAUDE.md`, a rule, an agent, skill prose) | [`references/instruction-style.md`](references/instruction-style.md) — not these templates; every-load prose stays lean, and the present-tense rule still applies |
116
- | Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`10`. **No etna checkout available ⇒ ask the user for access to the etna repo, or proceed from first principles** with [`references/instruction-style.md`](references/instruction-style.md) governing the prose. This row is the only place the playbook is referenced |
116
+ | Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`13`. **No etna checkout available ⇒ ask the user for access to the etna repo, or proceed from first principles** with [`references/instruction-style.md`](references/instruction-style.md) governing the prose. This row is the only place the playbook is referenced |
117
117
  | **No template matches** | [`references/untemplated.md`](references/untemplated.md) — shape decisions + the co-authoring workflow |
118
118
 
119
119
  ---
@@ -227,10 +227,19 @@ function groupHasCommand(group, command) {
227
227
  return (group.hooks || []).some((h) => h.command === command);
228
228
  }
229
229
 
230
+ // The harness script a hook command runs, by its `.claude/hooks/<name>` path — the identity
231
+ // that survives a change to the command's shape. Null for a command that runs no such script.
232
+ function hookScript(command) {
233
+ const found = /\.claude\/hooks\/([\w.-]+)/.exec(command || '');
234
+ return found ? found[1] : null;
235
+ }
236
+
230
237
  // Deep-merge the bundle's hooks.json object into <project>/.claude/settings.json under
231
238
  // "hooks", preserving every existing key. Idempotent: for each event, an incoming entry
232
239
  // merges into the matcher-group of the same matcher, skipping any hook whose command
233
- // already exists there; a new matcher creates a new group. Returns count of hooks added.
240
+ // already exists there; a hook already registered for the same script under an older command
241
+ // shape is replaced in place, never doubled; a new matcher creates a new group. Returns the
242
+ // count of hooks added or replaced.
234
243
  function mergeHooks(targetDir, bundledHooksJson) {
235
244
  if (!existsSync(bundledHooksJson)) return 0;
236
245
  const bundled = readJson(bundledHooksJson);
@@ -261,7 +270,10 @@ function mergeHooks(targetDir, bundledHooksJson) {
261
270
  match.hooks = match.hooks || [];
262
271
  for (const hook of group.hooks || []) {
263
272
  if (groupHasCommand(match, hook.command)) continue;
264
- match.hooks.push(hook);
273
+ const script = hookScript(hook.command);
274
+ const staleIndex = script ? match.hooks.findIndex((h) => hookScript(h.command) === script) : -1;
275
+ if (staleIndex === -1) match.hooks.push(hook);
276
+ else match.hooks[staleIndex] = hook;
265
277
  added += 1;
266
278
  }
267
279
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.20.24",
3
+ "version": "0.20.25",
4
4
  "description": "Bootstrap a project with a Nurix harness — copies the harness's CLAUDE.md, skills, agents, rules, and hooks directly into the project from a bundled payload (no marketplace fetch, no network).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,5 +42,5 @@
42
42
  "agent",
43
43
  "plugin"
44
44
  ],
45
- "etnaFingerprint": "73da5d8c28fc"
45
+ "etnaFingerprint": "c24bc6f1af10"
46
46
  }