@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.
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/hooks/awareness.mjs +1 -1
- package/data/harnesses/code/hooks/lib/sweep.mjs +1 -1
- package/data/harnesses/code/skills/design/references/emission.md +2 -0
- package/data/harnesses/code/skills/design/scripts/validate-design-md.mjs +45 -32
- package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +3 -2
- package/data/harnesses/code/skills/documentation/scripts/source-lint.mjs +5 -8
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +3 -2
- package/data/harnesses/delivery/skills/documentation/scripts/source-lint.mjs +5 -8
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +3 -2
- package/data/harnesses/etna/skills/documentation/scripts/source-lint.mjs +5 -8
- package/package.json +2 -2
package/data/harness-index.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "code",
|
|
3
3
|
"name": "code",
|
|
4
|
-
"version": "3.16.
|
|
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": "
|
|
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
|
|
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
|
|
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
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
// -
|
|
114
|
-
//
|
|
115
|
-
//
|
|
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
|
-
|
|
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)) {
|
|
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
|
-
|
|
1001
|
-
out.push("
|
|
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
|
-
|
|
1017
|
-
out.push(` --text-${step}
|
|
1018
|
-
out.push(` --text-${step}-
|
|
1019
|
-
|
|
1020
|
-
if (r.tracking != null) out.push(` --text-${step}-
|
|
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}-
|
|
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
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const
|
|
1052
|
-
|
|
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
|
|
26
|
-
|
|
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
|
-
//
|
|
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.
|
|
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": "
|
|
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
|
|
26
|
-
|
|
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
|
-
//
|
|
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.
|
|
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": "
|
|
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
|
|
26
|
-
|
|
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
|
-
//
|
|
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.
|
|
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": "
|
|
45
|
+
"etnaFingerprint": "993a4863fa1f"
|
|
46
46
|
}
|