@nurix/etna 0.19.70 → 0.19.71
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/agents/maintenance.md +1 -1
- package/data/harnesses/code/codex/agents/maintenance.toml +1 -1
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/hooks/check-ownership.mjs +47 -12
- package/data/harnesses/code/hooks/lib/ownership.mjs +57 -0
- package/data/harnesses/delivery/agents/maintenance.md +1 -1
- package/data/harnesses/delivery/codex/agents/maintenance.toml +1 -1
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/etna/agents/maintenance.md +1 -1
- package/data/harnesses/etna/codex/agents/maintenance.toml +1 -1
- package/data/harnesses/etna/harness.json +2 -2
- package/package.json +2 -2
package/data/harness-index.json
CHANGED
|
@@ -59,7 +59,7 @@ The ownership source of truth is the **union of every feature doc's `owns:` fron
|
|
|
59
59
|
For each `owns:` glob across the feature docs:
|
|
60
60
|
|
|
61
61
|
- Verify the glob matches existing files. Report globs matching nothing, deleted paths, and orphaned docs.
|
|
62
|
-
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). Surface as a question.
|
|
62
|
+
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). `node .claude/hooks/check-ownership.mjs` reports these mechanically alongside dead globs (`overlaps()` in `lib/ownership.mjs`); a glob at strictly higher specificity carving out of a broader `**` is the designed layering and is never flagged. Surface a real overlap as a question — narrowing someone's glob is an author's call, not remediation.
|
|
63
63
|
- **Stamp `type:` where missing.** Every doc carries a `type:` genre key (enum = the `documentation` skill's template names); docs predating the key are stamped mechanically — the genre is readable from placement and shape (a `docs/features/` doc is `type: feature`, an AD is `type: architecture-decision`). Stamping is remediation, not a question; an ambiguous genre is backlogged instead.
|
|
64
64
|
|
|
65
65
|
For each code symbol or path a doc explicitly **names** (function/export/route/type):
|
|
@@ -57,7 +57,7 @@ The ownership source of truth is the **union of every feature doc's `owns:` fron
|
|
|
57
57
|
For each `owns:` glob across the feature docs:
|
|
58
58
|
|
|
59
59
|
- Verify the glob matches existing files. Report globs matching nothing, deleted paths, and orphaned docs.
|
|
60
|
-
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). Surface as a question.
|
|
60
|
+
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). `node .claude/hooks/check-ownership.mjs` reports these mechanically alongside dead globs (`overlaps()` in `lib/ownership.mjs`); a glob at strictly higher specificity carving out of a broader `**` is the designed layering and is never flagged. Surface a real overlap as a question — narrowing someone's glob is an author's call, not remediation.
|
|
61
61
|
- **Stamp `type:` where missing.** Every doc carries a `type:` genre key (enum = the `documentation` skill's template names); docs predating the key are stamped mechanically — the genre is readable from placement and shape (a `docs/features/` doc is `type: feature`, an AD is `type: architecture-decision`). Stamping is remediation, not a question; an ambiguous genre is backlogged instead.
|
|
62
62
|
|
|
63
63
|
For each code symbol or path a doc explicitly **names** (function/export/route/type):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "code",
|
|
3
3
|
"name": "code",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.9.1",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -15,5 +15,5 @@
|
|
|
15
15
|
"docs/memory/main.md",
|
|
16
16
|
".mcp.json"
|
|
17
17
|
],
|
|
18
|
-
"digest": "
|
|
18
|
+
"digest": "bc889c51aa02"
|
|
19
19
|
}
|
|
@@ -1,22 +1,57 @@
|
|
|
1
|
-
// harness/code/hooks/check-ownership.mjs — CI backstop
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// harness/code/hooks/check-ownership.mjs — CI backstop for docs/features/ ownership. Two checks,
|
|
2
|
+
// one walk of the tree:
|
|
3
|
+
//
|
|
4
|
+
// 1. dead-glob — every owns: glob matches at least one file
|
|
5
|
+
// 2. ownership-overlap — no two feature docs claim a common file at EQUAL specificity, which
|
|
6
|
+
// leaves "most specific wins" unable to pick an owner
|
|
7
|
+
//
|
|
8
|
+
// Either is silenced by an unexpired `waived:` row in docs/operations/bookkeeping.md naming its
|
|
9
|
+
// kind plus the offending glob (dead-glob) or the shared path (ownership-overlap) verbatim.
|
|
10
|
+
// Exit 1 on anything unwaived.
|
|
11
|
+
//
|
|
12
|
+
// A glob at strictly HIGHER specificity carving a sub-path out of a broader `**` is the designed
|
|
13
|
+
// layering, not a conflict — only an exact tie is reported.
|
|
4
14
|
|
|
5
|
-
import { deadGlobs } from "./lib/ownership.mjs";
|
|
15
|
+
import { deadGlobs, listRepoFiles, overlaps } from "./lib/ownership.mjs";
|
|
6
16
|
import { readQueue, unexpiredWaivers } from "./lib/state.mjs";
|
|
7
17
|
|
|
8
18
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
9
19
|
const today = new Date().toISOString().slice(0, 10);
|
|
10
20
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const unwaived = dead.filter((d) => !waivers.some((w) => w.body.includes(d.glob)));
|
|
21
|
+
const files = listRepoFiles(projectDir);
|
|
22
|
+
const queue = readQueue(projectDir);
|
|
14
23
|
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
const dead = deadGlobs(projectDir, files);
|
|
25
|
+
const deadWaivers = unexpiredWaivers(queue, "dead-glob", today);
|
|
26
|
+
const unwaivedDead = dead.filter((d) => !deadWaivers.some((w) => w.body.includes(d.glob)));
|
|
27
|
+
|
|
28
|
+
const conflicts = overlaps(projectDir, files);
|
|
29
|
+
const overlapWaivers = unexpiredWaivers(queue, "ownership-overlap", today);
|
|
30
|
+
const unwaivedOverlaps = conflicts.filter((c) => !overlapWaivers.some((w) => w.body.includes(c.example)));
|
|
31
|
+
|
|
32
|
+
if (unwaivedDead.length === 0 && unwaivedOverlaps.length === 0) {
|
|
33
|
+
console.log(
|
|
34
|
+
`check-ownership: OK (${dead.length} dead glob(s), ${conflicts.length} equal-specificity overlap(s), all waived or none)`,
|
|
35
|
+
);
|
|
17
36
|
process.exit(0);
|
|
18
37
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.error(
|
|
38
|
+
|
|
39
|
+
if (unwaivedDead.length > 0) {
|
|
40
|
+
console.error(`check-ownership: ${unwaivedDead.length} unwaived dead owns: glob(s):`);
|
|
41
|
+
for (const d of unwaivedDead) console.error(` ${d.glob} → ${d.doc}`);
|
|
42
|
+
console.error("Fix the owning doc's owns: frontmatter, or add a `waived:` dead-glob row with reason + expires=.");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (unwaivedOverlaps.length > 0) {
|
|
46
|
+
console.error(`check-ownership: ${unwaivedOverlaps.length} unwaived equal-specificity overlap(s):`);
|
|
47
|
+
for (const c of unwaivedOverlaps) {
|
|
48
|
+
console.error(` ${c.example}`);
|
|
49
|
+
console.error(` ${c.a.glob} → ${c.a.doc}`);
|
|
50
|
+
console.error(` ${c.b.glob} → ${c.b.doc}`);
|
|
51
|
+
}
|
|
52
|
+
console.error(
|
|
53
|
+
"Narrow one doc's glob so a single owner wins, or add a `waived:` ownership-overlap row naming the shared path with reason + expires=.",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
22
57
|
process.exit(1);
|
|
@@ -242,3 +242,60 @@ export function deadGlobs(projectDir, repoFiles) {
|
|
|
242
242
|
}
|
|
243
243
|
return dead;
|
|
244
244
|
}
|
|
245
|
+
|
|
246
|
+
// Specificity of an owns: glob as a (literal, wildcard) segment tuple. The ownership rule is
|
|
247
|
+
// "most specific wins", so a glob at strictly higher specificity legitimately carves a sub-path
|
|
248
|
+
// out of a broader `**` — that is the designed layering, never a conflict. Only an exact tie
|
|
249
|
+
// leaves the owner ambiguous, which is what `overlaps()` reports.
|
|
250
|
+
export function specificity(glob) {
|
|
251
|
+
let literal = 0;
|
|
252
|
+
let wildcard = 0;
|
|
253
|
+
for (const segment of String(glob).split("/")) {
|
|
254
|
+
if (segment === "") continue;
|
|
255
|
+
if (segment.includes("*") || segment.includes("?")) wildcard++;
|
|
256
|
+
else literal++;
|
|
257
|
+
}
|
|
258
|
+
return { literal, wildcard };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Two globs are equally specific when both tuple components match.
|
|
262
|
+
export function sameSpecificity(a, b) {
|
|
263
|
+
return a.literal === b.literal && a.wildcard === b.wildcard;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Cross-doc equal-specificity ownership conflicts: two globs from DIFFERENT feature docs that
|
|
267
|
+
// match a common file at the same specificity, so "most specific wins" cannot pick an owner and
|
|
268
|
+
// the path is doubly owned. Returns [{ a: {doc, glob}, b: {doc, glob}, example }].
|
|
269
|
+
//
|
|
270
|
+
// This is the mechanical half of a rule the harness has always stated and never checked — the
|
|
271
|
+
// `documentation` skill's frontmatter reference calls an equal-specificity overlap "an ownership
|
|
272
|
+
// conflict to resolve", and the `maintenance` agent is told to flag one. Without this, both had
|
|
273
|
+
// to be enforced by eye.
|
|
274
|
+
export function overlaps(projectDir, repoFiles) {
|
|
275
|
+
const files = repoFiles || listRepoFiles(projectDir);
|
|
276
|
+
const owners = [];
|
|
277
|
+
for (const entry of scanFeatureDocs(projectDir)) {
|
|
278
|
+
if (entry.status === "deprecated") continue;
|
|
279
|
+
for (const glob of entry.owns) {
|
|
280
|
+
const re = globToRegExp(glob);
|
|
281
|
+
owners.push({ doc: entry.doc, glob, spec: specificity(glob), files: files.filter((f) => re.test(f)) });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return pairOverlaps(owners);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// The pure core of `overlaps()`, split out so tests can feed synthetic owners without a repo tree.
|
|
288
|
+
export function pairOverlaps(owners) {
|
|
289
|
+
const found = [];
|
|
290
|
+
for (let i = 0; i < owners.length; i++) {
|
|
291
|
+
for (let j = i + 1; j < owners.length; j++) {
|
|
292
|
+
const a = owners[i];
|
|
293
|
+
const b = owners[j];
|
|
294
|
+
if (a.doc === b.doc) continue;
|
|
295
|
+
if (!sameSpecificity(a.spec, b.spec)) continue;
|
|
296
|
+
const example = a.files.find((f) => b.files.includes(f));
|
|
297
|
+
if (example) found.push({ a: { doc: a.doc, glob: a.glob }, b: { doc: b.doc, glob: b.glob }, example });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return found;
|
|
301
|
+
}
|
|
@@ -59,7 +59,7 @@ The ownership source of truth is the **union of every feature doc's `owns:` fron
|
|
|
59
59
|
For each `owns:` glob across the feature docs:
|
|
60
60
|
|
|
61
61
|
- Verify the glob matches existing files. Report globs matching nothing, deleted paths, and orphaned docs.
|
|
62
|
-
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). Surface as a question.
|
|
62
|
+
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). `node .claude/hooks/check-ownership.mjs` reports these mechanically alongside dead globs (`overlaps()` in `lib/ownership.mjs`); a glob at strictly higher specificity carving out of a broader `**` is the designed layering and is never flagged. Surface a real overlap as a question — narrowing someone's glob is an author's call, not remediation.
|
|
63
63
|
- **Stamp `type:` where missing.** Every doc carries a `type:` genre key (enum = the `documentation` skill's template names); docs predating the key are stamped mechanically — the genre is readable from placement and shape (a `docs/features/` doc is `type: feature`, an AD is `type: architecture-decision`). Stamping is remediation, not a question; an ambiguous genre is backlogged instead.
|
|
64
64
|
|
|
65
65
|
For each code symbol or path a doc explicitly **names** (function/export/route/type):
|
|
@@ -57,7 +57,7 @@ The ownership source of truth is the **union of every feature doc's `owns:` fron
|
|
|
57
57
|
For each `owns:` glob across the feature docs:
|
|
58
58
|
|
|
59
59
|
- Verify the glob matches existing files. Report globs matching nothing, deleted paths, and orphaned docs.
|
|
60
|
-
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). Surface as a question.
|
|
60
|
+
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). `node .claude/hooks/check-ownership.mjs` reports these mechanically alongside dead globs (`overlaps()` in `lib/ownership.mjs`); a glob at strictly higher specificity carving out of a broader `**` is the designed layering and is never flagged. Surface a real overlap as a question — narrowing someone's glob is an author's call, not remediation.
|
|
61
61
|
- **Stamp `type:` where missing.** Every doc carries a `type:` genre key (enum = the `documentation` skill's template names); docs predating the key are stamped mechanically — the genre is readable from placement and shape (a `docs/features/` doc is `type: feature`, an AD is `type: architecture-decision`). Stamping is remediation, not a question; an ambiguous genre is backlogged instead.
|
|
62
62
|
|
|
63
63
|
For each code symbol or path a doc explicitly **names** (function/export/route/type):
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "delivery",
|
|
3
3
|
"name": "delivery",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.9",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
8
8
|
".vscode/settings.json"
|
|
9
9
|
],
|
|
10
10
|
"seeds": [],
|
|
11
|
-
"digest": "
|
|
11
|
+
"digest": "cebc16bda479"
|
|
12
12
|
}
|
|
@@ -59,7 +59,7 @@ The ownership source of truth is the **union of every feature doc's `owns:` fron
|
|
|
59
59
|
For each `owns:` glob across the feature docs:
|
|
60
60
|
|
|
61
61
|
- Verify the glob matches existing files. Report globs matching nothing, deleted paths, and orphaned docs.
|
|
62
|
-
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). Surface as a question.
|
|
62
|
+
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). `node .claude/hooks/check-ownership.mjs` reports these mechanically alongside dead globs (`overlaps()` in `lib/ownership.mjs`); a glob at strictly higher specificity carving out of a broader `**` is the designed layering and is never flagged. Surface a real overlap as a question — narrowing someone's glob is an author's call, not remediation.
|
|
63
63
|
- **Stamp `type:` where missing.** Every doc carries a `type:` genre key (enum = the `documentation` skill's template names); docs predating the key are stamped mechanically — the genre is readable from placement and shape (a `docs/features/` doc is `type: feature`, an AD is `type: architecture-decision`). Stamping is remediation, not a question; an ambiguous genre is backlogged instead.
|
|
64
64
|
|
|
65
65
|
For each code symbol or path a doc explicitly **names** (function/export/route/type):
|
|
@@ -57,7 +57,7 @@ The ownership source of truth is the **union of every feature doc's `owns:` fron
|
|
|
57
57
|
For each `owns:` glob across the feature docs:
|
|
58
58
|
|
|
59
59
|
- Verify the glob matches existing files. Report globs matching nothing, deleted paths, and orphaned docs.
|
|
60
|
-
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). Surface as a question.
|
|
60
|
+
- Flag **equal-specificity overlaps** — two feature docs whose `owns:` globs claim the same paths at the same specificity (an ambiguous owner). `node .claude/hooks/check-ownership.mjs` reports these mechanically alongside dead globs (`overlaps()` in `lib/ownership.mjs`); a glob at strictly higher specificity carving out of a broader `**` is the designed layering and is never flagged. Surface a real overlap as a question — narrowing someone's glob is an author's call, not remediation.
|
|
61
61
|
- **Stamp `type:` where missing.** Every doc carries a `type:` genre key (enum = the `documentation` skill's template names); docs predating the key are stamped mechanically — the genre is readable from placement and shape (a `docs/features/` doc is `type: feature`, an AD is `type: architecture-decision`). Stamping is remediation, not a question; an ambiguous genre is backlogged instead.
|
|
62
62
|
|
|
63
63
|
For each code symbol or path a doc explicitly **names** (function/export/route/type):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.71",
|
|
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": "6492994f8d44"
|
|
46
46
|
}
|