@nurix/etna 0.20.16 → 0.20.18

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": "6b7b8d3d3e8f",
2
+ "fingerprint": "993a4863fa1f",
3
3
  "harnesses": {
4
4
  "code": {
5
5
  "plugin": "code",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "3.16.4",
4
+ "version": "3.16.6",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -25,5 +25,5 @@
25
25
  }
26
26
  ],
27
27
  "docsRoot": "docs",
28
- "digest": "5e98d9973eb1"
28
+ "digest": "79daa8edeb53"
29
29
  }
@@ -343,7 +343,7 @@ function detectorForFile(projectDir, rel, meta, rows) {
343
343
  formatAutoRow({
344
344
  ...meta,
345
345
  kind: "doc-lint",
346
- description: `${rel} has ${findings.length} doc-lint finding(s)`,
346
+ description: `${rel} has doc-lint findings`,
347
347
  action: `fix or doc-lint-disable with reason`,
348
348
  })
349
349
  );
@@ -132,7 +132,7 @@ export const ROW_VERIFIERS = {
132
132
  return now !== then;
133
133
  },
134
134
 
135
- // "<rel> has N doc-lint finding(s)" — dead once a re-run comes back clean (or the doc is gone).
135
+ // "<rel> has doc-lint findings" — dead once a re-run comes back clean (or the doc is gone).
136
136
  "doc-lint": (projectDir, body) => {
137
137
  const m = /·\s+doc-lint\s+·\s+(\S+)\s+has/.exec(body);
138
138
  if (!m) return false;
@@ -36,6 +36,8 @@ personality:
36
36
  Each prevents a known silent-failure class:
37
37
 
38
38
  - **Specificity armor.** Theme blocks are `:root:not(.dark)` and `:root.dark` — both specificity (0,2,0) — keyed on the `.dark` class the library compiles into its own stylesheet. A `data-theme` attribute keys none of the library's `dark:` variants, its theme toggle, or its toaster: an app on the attribute inverts its tokens and renders mixed library chrome.
39
+ - **Theme-invariant tokens sit in a bare `:root`.** Only the colour roles are themed. Font stacks, the type ramp, radii, shadows, motion and layout emit into a bare `:root` block ahead of the two theme blocks. Behind `:root:not(.dark)` they match nothing once `.dark` is on the root, and every `font-size: var(--text-…)` collapses to an inherited 16px in dark mode while the colours still look right.
40
+ - **The ramp emits Tailwind's composite sub-keys.** `--text-<step>` with `--line-height`, `--font-weight` and `--letter-spacing` sub-keys are exactly what the `text-<step>` utility applies. A `-size`/`-line`/`-weight` suffix form generates junk utilities and leaves `text-<step>` undefined. The `--font-feature-settings` and `--font-family` sub-keys are inert for the utility and are read through `var()`. The dense operator tier's steps are optional: absent steps emit nothing and warn once per package (`dmw-08`).
39
41
  - **The tone trio.** `success`, `warning`, and `info` reach the library as `--tone-success|warning|info` — what a status dot (`<Chip prefix="dot" color="var(--tone-success)">`) and the copy button's check read, falling back to the library's own hue when a role is absent. Declare all three, in both themes — a missing `info` (`dmw-06`) puts the library's blue beside the package's green and amber.
40
42
  - **Font binding by derivation.** Font stacks bind to installed packages under **registered family names** (`"Inter Variable"`, not `"Inter"`), each with its declared fallback stack — otherwise the flagship font silently never renders. Install the font packages the design.md declares (e.g. Fontsource) and verify the name matches what the package registers. The library's `font-sans` / `font-mono` / `font-display` utilities compile to `inherit` on purpose (a ramp, never a face): the app declares its own theme keys in a plain `@theme` pointing at the emitted slots — `--font-body: var(--font-sans)`, `--font-heading: var(--font-display)`, `--font-code: var(--font-mono)` — and sets the ambient face on `body` in `@layer base`.
41
43
  - **Every authored token group reaches a named consumer.** An authored ramp with no emitter vanishes without a trace — wire each group (colors → theme classes/utilities, ramp → text styles, elevation → radius/shadow utilities, motion → duration/easing vars) and let the ship gate's reach check prove it. The personality families reach the library by construction; the app's `--duration-*` / `--ease-*` reach app-owned animation code, or they are unused.
@@ -105,14 +105,15 @@
105
105
  // background (checked only where chroma ≥ 0.01 — hue is noise near gray).
106
106
  // dmw-07 accent chroma delta between themes > 0.06 (identity likely not held across themes).
107
107
  //
108
- // Emitted artifact shape (deterministic, byte-stable): a `:root:not(.dark)` block and a
109
- // `:root.dark` block (specificity armor — both selectors reach 0,2,0 via the attribute
110
- // form, so a component library's later bare `:root` cannot silently defeat a theme). The light block
111
- // carries the 36 roles as --<role> plus every theme-invariant group: --font-display/sans/mono (registered
112
- // names quoted, declared fallback stacks appended), --text-<step>-size/-line/-weight (+ -family/-tracking/
113
- // -features where authored), --radius-*, --shadow-*, --duration-*, --ease-*, and the layout vars
114
- // (--space-base, --sidebar-width[-collapsed], --topbar-height, --side-panel-width[-wide],
115
- // --content-max-width; unmapped keys emit as --layout-<key>). The dark block carries the 36 dark roles.
108
+ // dmw-08 the dense operator tier is unadopted: typography.ramp omits optional steps (one warn per package).
109
+ //
110
+ // Emitted artifact shape (deterministic, byte-stable): THREE blocks. A bare `:root` carries every
111
+ // theme-invariant group — --font-display/sans/mono (registered names quoted, fallback stacks appended),
112
+ // the ramp as Tailwind composite keys --text-<step> + --line-height/--font-weight/--letter-spacing
113
+ // sub-keys, --radius-*, --shadow-*, --duration-*, --ease-*, and the layout vars (unmapped keys emit as
114
+ // --layout-<key>). Then `:root:not(.dark)` and `:root.dark` (both 0,2,0) carry the colour roles.
115
+ // Theme-invariant tokens must never move behind `:root:not(.dark)`: it matches nothing once `.dark` is on
116
+ // the root, and the whole ramp collapses to inherited 16px in dark mode (references/emission.md).
116
117
 
117
118
  import { readFileSync, writeFileSync, existsSync, statSync, readdirSync, realpathSync } from "node:fs";
118
119
  import { join, resolve, basename, dirname, relative, sep } from "node:path";
@@ -140,7 +141,11 @@ const SECTIONS = [
140
141
  "11 Responsive", "12 Agent Prompt Guide",
141
142
  ];
142
143
 
143
- const RAMP_STEPS = ["display", "h1", "h2", "body", "small", "caption", "data"];
144
+ // The dense operator tier is optional: every catalog package authors only the seven required steps, and
145
+ // each new value would be a taste decision per identity. Absent optional steps warn once (dmw-08) and emit nothing.
146
+ const RAMP_STEPS_REQUIRED = ["display", "h1", "h2", "body", "small", "caption", "data"];
147
+ const RAMP_STEPS_OPTIONAL = ["title", "h3", "prose", "control-lg", "dense", "control", "meta", "micro", "code", "ident"];
148
+ const RAMP_STEPS = [...RAMP_STEPS_REQUIRED, ...RAMP_STEPS_OPTIONAL];
144
149
  const FONT_SLOTS = ["display", "sans", "mono"];
145
150
  // Optional color roles: emitted only when declared (both themes or neither). `info` completes the
146
151
  // success/warning/info trio @nurix/components' status dots read through --tone-*.
@@ -688,9 +693,17 @@ function checkCoverage(values, report) {
688
693
  for (const slot of FONT_SLOTS) if (!isObj(typo[slot])) missing(`typography.${slot}`);
689
694
  if (!isObj(typo.ramp)) missing("typography.ramp");
690
695
  else {
696
+ // One warn per package, not one per step, so the taste warns stay readable across the catalog.
697
+ const unadopted = RAMP_STEPS_OPTIONAL.filter((s) => !isObj(typo.ramp[s]));
698
+ if (unadopted.length) {
699
+ warn(report, "dmw-08", `dense operator tier unadopted — typography.ramp missing ${unadopted.length}/${RAMP_STEPS_OPTIONAL.length}: ${unadopted.join(", ")}`);
700
+ }
691
701
  for (const step of RAMP_STEPS) {
692
702
  const r = typo.ramp[step];
693
- if (!isObj(r)) { missing(`typography.ramp.${step}`); continue; }
703
+ if (!isObj(r)) {
704
+ if (!RAMP_STEPS_OPTIONAL.includes(step)) missing(`typography.ramp.${step}`);
705
+ continue;
706
+ }
694
707
  for (const k of ["size", "line", "weight"]) if (r[k] == null) missing(`typography.ramp.${step}.${k}`);
695
708
  if (r.family == null) missing(`typography.ramp.${step}.family`);
696
709
  else if (!FONT_SLOTS.includes(r.family)) {
@@ -997,30 +1010,27 @@ export function emitTokens(front, values) {
997
1010
  const out = [];
998
1011
  out.push(`/* design.tokens.css — compiled from ${id} (design-md/v2) by validate-design-md.mjs — derived artifact: edit the design.md, never this file */`);
999
1012
  out.push("");
1000
- out.push(':root:not(.dark) {');
1001
- out.push(" /* color roles */");
1002
- for (const role of COLOR_ROLES) out.push(` --${role}: ${cssValue(req(values, ["color", "light", role]))};`);
1003
- for (const role of OPTIONAL_COLOR_ROLES) {
1004
- const v = values.color.light[role];
1005
- if (v != null) out.push(` --${role}: ${cssValue(v)};`);
1006
- }
1007
- out.push("");
1013
+ // Theme-invariant tokens sit in a bare `:root`; behind `:root:not(.dark)` they would not exist in dark mode.
1014
+ out.push(":root {");
1008
1015
  out.push(" /* typography */");
1009
1016
  for (const slot of FONT_SLOTS) {
1010
1017
  const f = req(values, ["typography", slot]);
1011
1018
  if (f.family == null || f.fallback == null) throw new Error(`cannot emit: typography.${slot} family/fallback missing`);
1012
1019
  out.push(` --font-${slot}: "${f.family}", ${f.fallback};`);
1013
1020
  }
1021
+ const ramp = req(values, ["typography", "ramp"]);
1014
1022
  for (const step of RAMP_STEPS) {
1023
+ if (ramp[step] == null && RAMP_STEPS_OPTIONAL.includes(step)) continue;
1015
1024
  const r = req(values, ["typography", "ramp", step]);
1016
- out.push(` --text-${step}-size: ${cssValue(req(r, ["size"]))};`);
1017
- out.push(` --text-${step}-line: ${cssValue(req(r, ["line"]))};`);
1018
- out.push(` --text-${step}-weight: ${cssValue(req(r, ["weight"]))};`);
1019
- if (r.family != null) out.push(` --text-${step}-family: var(--font-${r.family});`);
1020
- if (r.tracking != null) out.push(` --text-${step}-tracking: ${cssValue(r.tracking)};`);
1025
+ // Tailwind's `text-*` utility reads exactly --line-height, --letter-spacing and --font-weight sub-keys.
1026
+ out.push(` --text-${step}: ${cssValue(req(r, ["size"]))};`);
1027
+ out.push(` --text-${step}--line-height: ${cssValue(req(r, ["line"]))};`);
1028
+ out.push(` --text-${step}--font-weight: ${cssValue(req(r, ["weight"]))};`);
1029
+ if (r.tracking != null) out.push(` --text-${step}--letter-spacing: ${cssValue(r.tracking)};`);
1021
1030
  if (Array.isArray(r.features) && r.features.length) {
1022
- out.push(` --text-${step}-features: ${r.features.map((x) => `"${x}"`).join(", ")};`);
1031
+ out.push(` --text-${step}--font-feature-settings: ${r.features.map((x) => `"${x}"`).join(", ")}; /* inert for text-*; read via var() */`);
1023
1032
  }
1033
+ if (r.family != null) out.push(` --text-${step}--font-family: var(--font-${r.family}); /* inert for text-*; read via var() */`);
1024
1034
  }
1025
1035
  out.push("");
1026
1036
  out.push(" /* elevation */");
@@ -1043,15 +1053,18 @@ export function emitTokens(front, values) {
1043
1053
  }
1044
1054
  }
1045
1055
  out.push("}");
1046
- out.push("");
1047
- out.push(':root.dark {');
1048
- out.push(" /* color roles */");
1049
- for (const role of COLOR_ROLES) out.push(` --${role}: ${cssValue(req(values, ["color", "dark", role]))};`);
1050
- for (const role of OPTIONAL_COLOR_ROLES) {
1051
- const v = values.color.dark[role];
1052
- if (v != null) out.push(` --${role}: ${cssValue(v)};`);
1056
+ // Both colour blocks reach (0,2,0), one notch above a library's unlayered `:root`/`.dark`, whatever the import order.
1057
+ for (const [selector, theme] of [[":root:not(.dark)", "light"], [":root.dark", "dark"]]) {
1058
+ out.push("");
1059
+ out.push(`${selector} {`);
1060
+ out.push(` /* color roles — ${theme} */`);
1061
+ for (const role of COLOR_ROLES) out.push(` --${role}: ${cssValue(req(values, ["color", theme, role]))};`);
1062
+ for (const role of OPTIONAL_COLOR_ROLES) {
1063
+ const v = values.color[theme][role];
1064
+ if (v != null) out.push(` --${role}: ${cssValue(v)};`);
1065
+ }
1066
+ out.push("}");
1053
1067
  }
1054
- out.push("}");
1055
1068
  return out.join("\n") + "\n";
1056
1069
  }
1057
1070
 
@@ -22,8 +22,9 @@ const RULES = [
22
22
 
23
23
  // Sections inside a truth doc where dated history is sanctioned (the two bridges).
24
24
  const BRIDGE_HEADINGS = /^#{1,4}\s+(decision changelog|health log)\b/i;
25
- // Instruction files and generated trees are out of scope; the authoring roots are anchored to the repo root.
26
- const SKIP = /^(\.claude|skills|rules|agents|harness)\/|(^|\/)(node_modules|packages\/[^/]+\/data)\/|(^|\/)(CLAUDE|AGENTS)\.md$|\.bkp\./;
25
+ // Instruction files, the three harness-installed mirrors and generated trees are out of scope; the authoring
26
+ // roots are anchored to the repo root.
27
+ const SKIP = /^(\.claude|\.agents|\.codex|skills|rules|agents|harness)\/|(^|\/)(node_modules|packages\/[^/]+\/data)\/|(^|\/)(CLAUDE|AGENTS)\.md$|\.bkp\./;
27
28
  // `operations/issues/YYYY-MM-DD-{slug}.md` is a sanctioned record genre (`type: issue`, the
28
29
  // `issue-bridge` skill's local pack) living outside the record folders, so it is named here too —
29
30
  // graded as a truth document it fails the date rule for carrying the date its filename already does.
@@ -2,10 +2,7 @@
2
2
  // Usage: node source-lint.mjs <file | dir ...> | node source-lint.mjs --changed (git-changed + untracked source files)
3
3
  // Exit 1 when any block-severity finding survives. Escape hatch: a `source-lint-disable: id[, id]` comment,
4
4
  // file-wide in the first 20 lines or on the offending line; the reason goes in a second comment beside it.
5
- //
6
- // The convention is the harness `CLAUDE.md` § File Structure and is not restated here: a repo-relative path
7
- // header of two or three present-tense sentences, one third-person sentence above each non-obvious function,
8
- // rationale in the owning doc. This script checks only what is mechanical; voice and placement stay judgement.
5
+ // The convention itself is the harness `CLAUDE.md` § File Structure; this checks only what is mechanical.
9
6
 
10
7
  import { readFileSync, existsSync, statSync, readdirSync } from 'node:fs';
11
8
  import { execSync, spawnSync } from 'node:child_process';
@@ -21,10 +18,10 @@ const DIALECTS = {
21
18
  '.py': '#', '.sh': '#', '.zsh': '#', '.bash': '#', '.rb': '#',
22
19
  };
23
20
 
24
- // Generated trees, vendored copies, and formats with no comment syntax are a different genre. `.claude/`
25
- // is harness-owned in every consumer — its files are overwritten on the next roll, so a finding there
26
- // names work nobody in that repo can land. The forge lints its own copies at `harness/`, not here.
27
- const SKIP = /(^|\/)(node_modules|dist|build|target|\.git|\.claude|coverage|__screenshots__|data\/harnesses)\/|\.min\.|\.d\.ts$|(^|\/)(pnpm-lock|package-lock)\./;
21
+ // Generated trees, vendored copies, and formats with no comment syntax are a different genre. `.claude/`,
22
+ // `.agents/` and `.codex/` are harness-owned in every consumer — overwritten on the next roll, so a finding
23
+ // there names work nobody in that repo can land. The forge lints its own copies at `harness/`, not here.
24
+ const SKIP = /(^|\/)(node_modules|dist|build|target|\.git|\.claude|\.agents|\.codex|coverage|__screenshots__|data\/harnesses)\/|\.min\.|\.d\.ts$|(^|\/)(pnpm-lock|package-lock)\./;
28
25
  // A generated file says so in its own first lines; the convention exempts it.
29
26
  const GENERATED = /@generated|DO NOT EDIT|AUTO-GENERATED|This file was generated/i;
30
27
  // Below this a file is too small for a density ratio to mean anything.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "delivery",
3
3
  "name": "delivery",
4
- "version": "0.2.31",
4
+ "version": "0.2.32",
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": "2b8abfd5bb5f"
13
+ "digest": "94b63ce8cf37"
14
14
  }
@@ -22,8 +22,9 @@ const RULES = [
22
22
 
23
23
  // Sections inside a truth doc where dated history is sanctioned (the two bridges).
24
24
  const BRIDGE_HEADINGS = /^#{1,4}\s+(decision changelog|health log)\b/i;
25
- // Instruction files and generated trees are out of scope; the authoring roots are anchored to the repo root.
26
- const SKIP = /^(\.claude|skills|rules|agents|harness)\/|(^|\/)(node_modules|packages\/[^/]+\/data)\/|(^|\/)(CLAUDE|AGENTS)\.md$|\.bkp\./;
25
+ // Instruction files, the three harness-installed mirrors and generated trees are out of scope; the authoring
26
+ // roots are anchored to the repo root.
27
+ const SKIP = /^(\.claude|\.agents|\.codex|skills|rules|agents|harness)\/|(^|\/)(node_modules|packages\/[^/]+\/data)\/|(^|\/)(CLAUDE|AGENTS)\.md$|\.bkp\./;
27
28
  // `operations/issues/YYYY-MM-DD-{slug}.md` is a sanctioned record genre (`type: issue`, the
28
29
  // `issue-bridge` skill's local pack) living outside the record folders, so it is named here too —
29
30
  // graded as a truth document it fails the date rule for carrying the date its filename already does.
@@ -2,10 +2,7 @@
2
2
  // Usage: node source-lint.mjs <file | dir ...> | node source-lint.mjs --changed (git-changed + untracked source files)
3
3
  // Exit 1 when any block-severity finding survives. Escape hatch: a `source-lint-disable: id[, id]` comment,
4
4
  // file-wide in the first 20 lines or on the offending line; the reason goes in a second comment beside it.
5
- //
6
- // The convention is the harness `CLAUDE.md` § File Structure and is not restated here: a repo-relative path
7
- // header of two or three present-tense sentences, one third-person sentence above each non-obvious function,
8
- // rationale in the owning doc. This script checks only what is mechanical; voice and placement stay judgement.
5
+ // The convention itself is the harness `CLAUDE.md` § File Structure; this checks only what is mechanical.
9
6
 
10
7
  import { readFileSync, existsSync, statSync, readdirSync } from 'node:fs';
11
8
  import { execSync, spawnSync } from 'node:child_process';
@@ -21,10 +18,10 @@ const DIALECTS = {
21
18
  '.py': '#', '.sh': '#', '.zsh': '#', '.bash': '#', '.rb': '#',
22
19
  };
23
20
 
24
- // Generated trees, vendored copies, and formats with no comment syntax are a different genre. `.claude/`
25
- // is harness-owned in every consumer — its files are overwritten on the next roll, so a finding there
26
- // names work nobody in that repo can land. The forge lints its own copies at `harness/`, not here.
27
- const SKIP = /(^|\/)(node_modules|dist|build|target|\.git|\.claude|coverage|__screenshots__|data\/harnesses)\/|\.min\.|\.d\.ts$|(^|\/)(pnpm-lock|package-lock)\./;
21
+ // Generated trees, vendored copies, and formats with no comment syntax are a different genre. `.claude/`,
22
+ // `.agents/` and `.codex/` are harness-owned in every consumer — overwritten on the next roll, so a finding
23
+ // there names work nobody in that repo can land. The forge lints its own copies at `harness/`, not here.
24
+ const SKIP = /(^|\/)(node_modules|dist|build|target|\.git|\.claude|\.agents|\.codex|coverage|__screenshots__|data\/harnesses)\/|\.min\.|\.d\.ts$|(^|\/)(pnpm-lock|package-lock)\./;
28
25
  // A generated file says so in its own first lines; the convention exempts it.
29
26
  const GENERATED = /@generated|DO NOT EDIT|AUTO-GENERATED|This file was generated/i;
30
27
  // Below this a file is too small for a density ratio to mean anything.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "etna",
3
3
  "name": "etna",
4
- "version": "1.5.21",
4
+ "version": "1.5.22",
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": "a3f92ccefc2e"
13
+ "digest": "1fcc2f8a0d2a"
14
14
  }
@@ -22,8 +22,9 @@ const RULES = [
22
22
 
23
23
  // Sections inside a truth doc where dated history is sanctioned (the two bridges).
24
24
  const BRIDGE_HEADINGS = /^#{1,4}\s+(decision changelog|health log)\b/i;
25
- // Instruction files and generated trees are out of scope; the authoring roots are anchored to the repo root.
26
- const SKIP = /^(\.claude|skills|rules|agents|harness)\/|(^|\/)(node_modules|packages\/[^/]+\/data)\/|(^|\/)(CLAUDE|AGENTS)\.md$|\.bkp\./;
25
+ // Instruction files, the three harness-installed mirrors and generated trees are out of scope; the authoring
26
+ // roots are anchored to the repo root.
27
+ const SKIP = /^(\.claude|\.agents|\.codex|skills|rules|agents|harness)\/|(^|\/)(node_modules|packages\/[^/]+\/data)\/|(^|\/)(CLAUDE|AGENTS)\.md$|\.bkp\./;
27
28
  // `operations/issues/YYYY-MM-DD-{slug}.md` is a sanctioned record genre (`type: issue`, the
28
29
  // `issue-bridge` skill's local pack) living outside the record folders, so it is named here too —
29
30
  // graded as a truth document it fails the date rule for carrying the date its filename already does.
@@ -2,10 +2,7 @@
2
2
  // Usage: node source-lint.mjs <file | dir ...> | node source-lint.mjs --changed (git-changed + untracked source files)
3
3
  // Exit 1 when any block-severity finding survives. Escape hatch: a `source-lint-disable: id[, id]` comment,
4
4
  // file-wide in the first 20 lines or on the offending line; the reason goes in a second comment beside it.
5
- //
6
- // The convention is the harness `CLAUDE.md` § File Structure and is not restated here: a repo-relative path
7
- // header of two or three present-tense sentences, one third-person sentence above each non-obvious function,
8
- // rationale in the owning doc. This script checks only what is mechanical; voice and placement stay judgement.
5
+ // The convention itself is the harness `CLAUDE.md` § File Structure; this checks only what is mechanical.
9
6
 
10
7
  import { readFileSync, existsSync, statSync, readdirSync } from 'node:fs';
11
8
  import { execSync, spawnSync } from 'node:child_process';
@@ -21,10 +18,10 @@ const DIALECTS = {
21
18
  '.py': '#', '.sh': '#', '.zsh': '#', '.bash': '#', '.rb': '#',
22
19
  };
23
20
 
24
- // Generated trees, vendored copies, and formats with no comment syntax are a different genre. `.claude/`
25
- // is harness-owned in every consumer — its files are overwritten on the next roll, so a finding there
26
- // names work nobody in that repo can land. The forge lints its own copies at `harness/`, not here.
27
- const SKIP = /(^|\/)(node_modules|dist|build|target|\.git|\.claude|coverage|__screenshots__|data\/harnesses)\/|\.min\.|\.d\.ts$|(^|\/)(pnpm-lock|package-lock)\./;
21
+ // Generated trees, vendored copies, and formats with no comment syntax are a different genre. `.claude/`,
22
+ // `.agents/` and `.codex/` are harness-owned in every consumer — overwritten on the next roll, so a finding
23
+ // there names work nobody in that repo can land. The forge lints its own copies at `harness/`, not here.
24
+ const SKIP = /(^|\/)(node_modules|dist|build|target|\.git|\.claude|\.agents|\.codex|coverage|__screenshots__|data\/harnesses)\/|\.min\.|\.d\.ts$|(^|\/)(pnpm-lock|package-lock)\./;
28
25
  // A generated file says so in its own first lines; the convention exempts it.
29
26
  const GENERATED = /@generated|DO NOT EDIT|AUTO-GENERATED|This file was generated/i;
30
27
  // Below this a file is too small for a density ratio to mean anything.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.20.16",
3
+ "version": "0.20.18",
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": "6b7b8d3d3e8f"
45
+ "etnaFingerprint": "993a4863fa1f"
46
46
  }