@nurix/etna 0.19.67 → 0.19.68

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": "546f4bccb873",
2
+ "fingerprint": "d5b676c55c3a",
3
3
  "harnesses": {
4
4
  "code": {
5
5
  "plugin": "code",
@@ -149,6 +149,7 @@ docs/
149
149
  │ ├── plans/ # Implementation plans (Status field required)
150
150
  │ ├── migrations/ # One-shot migration/upgrade runbooks (YYYY-MM-DD-{slug}.md; outcome-stamped then frozen)
151
151
  │ ├── journal.md # Append-only mid-execution knowledge capture (one line per fact; resolves by landing in its owning doc) — author-created, never scaffolded
152
+ │ ├── sessions/ # Per-session gate logs (YYYY-MM-DD-{sid}.md) — machinery-written records, never drained, never edited down; created by the first gate event, never scaffolded
152
153
  │ ├── brainstorms/ # Harvested brainstorm sessions ({topic}/) — user decides kept (committed) or deleted; Readme.md is the index
153
154
  │ ├── research/ # Deep-research records ({topic}/ — README.md synthesis + findings.md + sources.md); frozen, cited, owns no code
154
155
  │ └── backlog.md ✱ # Tracked work items by priority (P0-P3)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "3.8.1",
4
+ "version": "3.8.2",
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": "f120a269e99c"
18
+ "digest": "2dcea7420200"
19
19
  }
@@ -30,6 +30,8 @@ import {
30
30
  stateDir,
31
31
  recordTouched,
32
32
  } from "./lib/state.mjs";
33
+ import { frozenStatus } from "./lib/frozen.mjs";
34
+ import { approvedPackages } from "./lib/approved.mjs";
33
35
 
34
36
  const today = () => new Date().toISOString().slice(0, 10);
35
37
 
@@ -360,6 +362,68 @@ function detectRenames(projectDir, command, meta, rows) {
360
362
  }
361
363
  }
362
364
 
365
+ // Working-tree paths git sees as changed or untracked — the outcome detector's ground truth.
366
+ // Fails open: no git, or a git error, reads as an empty list.
367
+ function dirtyPaths(projectDir) {
368
+ const res = spawnSync("git", ["status", "--porcelain"], { cwd: projectDir, encoding: "utf8", timeout: 5000 });
369
+ if (res.status !== 0) return [];
370
+ return (res.stdout || "")
371
+ .split("\n")
372
+ .filter(Boolean)
373
+ .map((line) => {
374
+ const p = line.slice(3);
375
+ return p.includes(" -> ") ? p.split(" -> ")[1] : p; // a rename reports both sides; the new path is the live one
376
+ })
377
+ .map((p) => p.replace(/^"|"$/g, ""));
378
+ }
379
+
380
+ // OUTCOME RECOMPUTATION — the gates deny wrong actions, but an action can be routed around by
381
+ // any tool that writes bytes (a manifest written by a script, a frozen record touched by sed).
382
+ // State cannot be routed around: recompute the two outcomes the gates guard and raise what
383
+ // changed as rows. Dedupe against the queue so a retry loop raises one row, not ten.
384
+ function detectOutcomes(projectDir, meta, rows) {
385
+ const existing = readQueue(projectDir);
386
+ const raised = (needle) => existing.some((r) => r.body.includes(needle)) || rows.some((r) => r.includes(needle));
387
+ for (const rel of dirtyPaths(projectDir)) {
388
+ if (rel.includes("node_modules/") || rel.includes(".bkp.")) continue;
389
+ if (path.posix.basename(rel) === "package.json") {
390
+ const approved = approvedPackages(projectDir);
391
+ if (approved.size === 0) continue; // no table installed — nothing to enforce
392
+ let manifest;
393
+ try {
394
+ manifest = JSON.parse(readSafe(path.join(projectDir, rel)));
395
+ } catch (e) {
396
+ continue;
397
+ }
398
+ const names = [...Object.keys(manifest.dependencies || {}), ...Object.keys(manifest.devDependencies || {})];
399
+ for (const name of names) {
400
+ if (approved.has(name) || raised(`\`${name}\` (package-off-catalog`)) continue;
401
+ rows.push(
402
+ formatAutoRow({
403
+ ...meta,
404
+ kind: "package-off-catalog",
405
+ description: `${rel} declares \`${name}\` (package-off-catalog), absent from the approved table`,
406
+ action: "remove the dependency, or add it to .claude/hooks/approved-packages.md by explicit user decision",
407
+ })
408
+ );
409
+ }
410
+ }
411
+ if (rel.startsWith("docs/history/")) {
412
+ const frozen = frozenStatus(projectDir, rel);
413
+ if (frozen.frozen && !raised(`${rel} is a frozen record`)) {
414
+ rows.push(
415
+ formatAutoRow({
416
+ ...meta,
417
+ kind: "frozen-record-edited",
418
+ description: `${rel} is a frozen record and carries uncommitted modifications (${frozen.reason})`,
419
+ action: "revert the edit — a correction is a new dated entry that links back",
420
+ })
421
+ );
422
+ }
423
+ }
424
+ }
425
+ }
426
+
363
427
  function postToolUse(projectDir, payload) {
364
428
  const meta = {
365
429
  date: today(),
@@ -371,11 +435,16 @@ function postToolUse(projectDir, payload) {
371
435
  const rows = [];
372
436
  if (payload.tool_name === "Bash") {
373
437
  detectRenames(projectDir, input.command, meta, rows);
438
+ detectOutcomes(projectDir, meta, rows);
374
439
  } else {
375
440
  const rel = relIfInside(projectDir, input.file_path || input.notebook_path);
376
441
  if (rel) detectorForFile(projectDir, rel, meta, rows);
377
442
  // Editing a feature doc's frontmatter can orphan globs the same way a rename can.
378
443
  if (rel && rel.startsWith("docs/features/")) detectRenames(projectDir, "git mv", meta, rows);
444
+ // A manifest or history file written by a non-Bash tool is the same evasion surface.
445
+ if (rel && (path.posix.basename(rel) === "package.json" || rel.startsWith("docs/history/"))) {
446
+ detectOutcomes(projectDir, meta, rows);
447
+ }
379
448
  }
380
449
  appendRows(projectDir, rows);
381
450
  }
@@ -13,6 +13,7 @@ import { readQueue, countRows, askedThisSession, recordAsked } from "./lib/state
13
13
  import { frozenStatus } from "./lib/frozen.mjs";
14
14
  import { undischargedRows } from "./lib/ledger.mjs";
15
15
  import { appendEvent, unresolvedDenials, sessionLogPath } from "./lib/session-log.mjs";
16
+ import { approvedPackages } from "./lib/approved.mjs";
16
17
 
17
18
  function readSafe(file) {
18
19
  try {
@@ -240,20 +241,6 @@ function commitGate(projectDir, sessionId) {
240
241
  return null;
241
242
  }
242
243
 
243
- // Package names approved in .claude/hooks/approved-packages.md (plus testing.md frameworks and general.md).
244
- function approvedPackages(projectDir) {
245
- const names = new Set();
246
- const sources = [
247
- path.join(projectDir, ".claude", "hooks", "approved-packages.md"),
248
- path.join(projectDir, ".claude", "rules", "general.md"),
249
- path.join(projectDir, ".claude", "rules", "testing.md"),
250
- ];
251
- for (const src of sources) {
252
- const text = readSafe(src);
253
- for (const m of text.matchAll(/`(@?[a-z0-9][\w./-]*)`/g)) names.add(m[1]);
254
- }
255
- return names;
256
- }
257
244
 
258
245
  // A token must parse as an npm package spec — name or @scope/name, optional @range tail —
259
246
  // before it may be judged against the table. Everything else on an install line (redirections
@@ -0,0 +1,30 @@
1
+ // harness/code/hooks/lib/approved.mjs — the approved-package set, one reader shared by the
2
+ // PreToolUse install gate (compliance) and the PostToolUse outcome detector (awareness), so
3
+ // "blocked at install" and "flagged in the manifest" can never disagree about the table.
4
+
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+
8
+ function readSafe(file) {
9
+ try {
10
+ return fs.readFileSync(file, "utf8");
11
+ } catch (e) {
12
+ return "";
13
+ }
14
+ }
15
+
16
+ // Package names approved in .claude/hooks/approved-packages.md (plus testing.md frameworks and
17
+ // general.md). Empty set ⇒ no rules installed ⇒ nothing to enforce.
18
+ export function approvedPackages(projectDir) {
19
+ const names = new Set();
20
+ const sources = [
21
+ path.join(projectDir, ".claude", "hooks", "approved-packages.md"),
22
+ path.join(projectDir, ".claude", "rules", "general.md"),
23
+ path.join(projectDir, ".claude", "rules", "testing.md"),
24
+ ];
25
+ for (const src of sources) {
26
+ const text = readSafe(src);
27
+ for (const m of text.matchAll(/`(@?[a-z0-9][\w./-]*)`/g)) names.add(m[1]);
28
+ }
29
+ return names;
30
+ }
@@ -12,7 +12,7 @@ When creating or updating a feature doc, derive its **`owns:` frontmatter** —
12
12
 
13
13
  ## How to derive the frontmatter
14
14
 
15
- A feature doc's ownership frontmatter is `owns:` (required), `module:` (optional), `keywords:` (required), and `status:` (required). An optional `last-reviewed:` date (stamped whenever the doc is substantively reviewed or updated) gives the `maintenance` agent a staleness signal to rank its audits by — it is tooling metadata, never narrated in the body. `type:` (required — the genre, enum = the template names) and `rev:` (generator-written generation stamp) are the same class: external tooling reads them, sessions **preserve them on edits** and never narrate or inject them. The `maintenance` agent stamps `type:` on docs that predate the key.
15
+ A feature doc's ownership frontmatter is `owns:` (required), `module:` (optional), `keywords:` (required), and `status:` (required). An optional `last-reviewed:` date (stamped whenever the doc is substantively reviewed or updated) gives the `maintenance` agent a staleness signal to rank its audits by — it is tooling metadata, never narrated in the body. `type:` (required — the genre, enum = the template names) and `rev:` (generator-written generation stamp) are the same class: external tooling reads them, sessions **preserve them on edits** and never narrate or inject them. The `maintenance` agent stamps `type:` on docs that predate the key. `slug:` completes the set on generated docs: the identity key, minted once at creation and never re-derived from a display name — in the repo the filename is the slug, and the `maintenance` agent re-aligns the two when they diverge.
16
16
 
17
17
  **1. `owns:`** (the path scope)
18
18
 
@@ -30,6 +30,8 @@ One file per template. Every template is either a **truth document** (states cur
30
30
  | Backlog | [backlog.md](backlog.md) | Record (append-only) |
31
31
  | Memory (main agent) | [memory-main.md](memory-main.md) | Record (append-only ledger) |
32
32
 
33
+ **The `type:` enum lives here.** A doc's `type:` frontmatter takes its value from this table — one genre per template (`feature`, `architecture-decision`, `non-goals`, …). External generators consume this list rather than defining their own; a type this table does not name defaults to `docs/architecture/` placement and is never added to the protected-files list.
34
+
33
35
  **Baseline scaffolds** — files that must exist after every baseline; the `maintenance` agent (baseline mode) creates any that are missing: [docs-readme.md](docs-readme.md), [structure.md](structure.md), [changelog-index.md](changelog-index.md), [backlog.md](backlog.md), [memory-main.md](memory-main.md), and [root-readme.md](root-readme.md) (never overwriting an existing root README).
34
36
 
35
37
  **Ownership.** A feature doc is the canonical reference for the code paths in its **`owns:` frontmatter** — the only ownership key. To find what a doc owns, read its `owns:` globs; to find the doc for a path, match the path against the injected ownership index (the union of every feature's `owns:` globs), narrower glob wins. See [`../references/frontmatter.md`](../references/frontmatter.md) for turning code into `owns:` globs.
@@ -31,6 +31,7 @@ Each topic has one canonical location. Read from here, not from plans or changel
31
31
  | Historical plans | `docs/history/plans/` | YYYY-MM-DD-name.md |
32
32
  | Change records | `docs/history/changelog/` | `YYYY-MM-DD-{three-words}.md` — one per unit of work, written at commit time |
33
33
  | Migration runbooks | `docs/history/migrations/` | YYYY-MM-DD-name.md |
34
+ | Session gate logs | `docs/history/sessions/` | `YYYY-MM-DD-{sid}.md` — machinery-written; denials and their resolutions, appended, never edited down |
34
35
  | Deep-research artifacts | `docs/history/research/{topic}/` | README.md + findings.md + sources.md |
35
36
  | Brainstorm design records | `docs/research/{topic}-brainstorm/` (active; legacy `-brainstorming/`) → `docs/history/brainstorms/{topic}/` (harvested; user deletes or commits) | thesis.md + forks.md + ledger.md + state.md |
36
37
  | Backlog | `docs/history/` | backlog.md |
@@ -149,6 +149,7 @@ docs/
149
149
  │ ├── plans/ # Implementation plans (Status field required)
150
150
  │ ├── migrations/ # One-shot migration/upgrade runbooks (YYYY-MM-DD-{slug}.md; outcome-stamped then frozen)
151
151
  │ ├── journal.md # Append-only mid-execution knowledge capture (one line per fact; resolves by landing in its owning doc) — author-created, never scaffolded
152
+ │ ├── sessions/ # Per-session gate logs (YYYY-MM-DD-{sid}.md) — machinery-written records, never drained, never edited down; created by the first gate event, never scaffolded
152
153
  │ ├── brainstorms/ # Harvested brainstorm sessions ({topic}/) — user decides kept (committed) or deleted; Readme.md is the index
153
154
  │ ├── research/ # Deep-research records ({topic}/ — README.md synthesis + findings.md + sources.md); frozen, cited, owns no code
154
155
  │ └── backlog.md ✱ # Tracked work items by priority (P0-P3)
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "harness": "delivery",
3
3
  "name": "delivery",
4
- "version": "0.2.5",
4
+ "version": "0.2.6",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
8
8
  ".vscode/settings.json"
9
9
  ],
10
10
  "seeds": [],
11
- "digest": "99afecb2c42f"
11
+ "digest": "5f761b31c27a"
12
12
  }
@@ -12,7 +12,7 @@ When creating or updating a feature doc, derive its **`owns:` frontmatter** —
12
12
 
13
13
  ## How to derive the frontmatter
14
14
 
15
- A feature doc's ownership frontmatter is `owns:` (required), `module:` (optional), `keywords:` (required), and `status:` (required). An optional `last-reviewed:` date (stamped whenever the doc is substantively reviewed or updated) gives the `maintenance` agent a staleness signal to rank its audits by — it is tooling metadata, never narrated in the body. `type:` (required — the genre, enum = the template names) and `rev:` (generator-written generation stamp) are the same class: external tooling reads them, sessions **preserve them on edits** and never narrate or inject them. The `maintenance` agent stamps `type:` on docs that predate the key.
15
+ A feature doc's ownership frontmatter is `owns:` (required), `module:` (optional), `keywords:` (required), and `status:` (required). An optional `last-reviewed:` date (stamped whenever the doc is substantively reviewed or updated) gives the `maintenance` agent a staleness signal to rank its audits by — it is tooling metadata, never narrated in the body. `type:` (required — the genre, enum = the template names) and `rev:` (generator-written generation stamp) are the same class: external tooling reads them, sessions **preserve them on edits** and never narrate or inject them. The `maintenance` agent stamps `type:` on docs that predate the key. `slug:` completes the set on generated docs: the identity key, minted once at creation and never re-derived from a display name — in the repo the filename is the slug, and the `maintenance` agent re-aligns the two when they diverge.
16
16
 
17
17
  **1. `owns:`** (the path scope)
18
18
 
@@ -30,6 +30,8 @@ One file per template. Every template is either a **truth document** (states cur
30
30
  | Backlog | [backlog.md](backlog.md) | Record (append-only) |
31
31
  | Memory (main agent) | [memory-main.md](memory-main.md) | Record (append-only ledger) |
32
32
 
33
+ **The `type:` enum lives here.** A doc's `type:` frontmatter takes its value from this table — one genre per template (`feature`, `architecture-decision`, `non-goals`, …). External generators consume this list rather than defining their own; a type this table does not name defaults to `docs/architecture/` placement and is never added to the protected-files list.
34
+
33
35
  **Baseline scaffolds** — files that must exist after every baseline; the `maintenance` agent (baseline mode) creates any that are missing: [docs-readme.md](docs-readme.md), [structure.md](structure.md), [changelog-index.md](changelog-index.md), [backlog.md](backlog.md), [memory-main.md](memory-main.md), and [root-readme.md](root-readme.md) (never overwriting an existing root README).
34
36
 
35
37
  **Ownership.** A feature doc is the canonical reference for the code paths in its **`owns:` frontmatter** — the only ownership key. To find what a doc owns, read its `owns:` globs; to find the doc for a path, match the path against the injected ownership index (the union of every feature's `owns:` globs), narrower glob wins. See [`../references/frontmatter.md`](../references/frontmatter.md) for turning code into `owns:` globs.
@@ -31,6 +31,7 @@ Each topic has one canonical location. Read from here, not from plans or changel
31
31
  | Historical plans | `docs/history/plans/` | YYYY-MM-DD-name.md |
32
32
  | Change records | `docs/history/changelog/` | `YYYY-MM-DD-{three-words}.md` — one per unit of work, written at commit time |
33
33
  | Migration runbooks | `docs/history/migrations/` | YYYY-MM-DD-name.md |
34
+ | Session gate logs | `docs/history/sessions/` | `YYYY-MM-DD-{sid}.md` — machinery-written; denials and their resolutions, appended, never edited down |
34
35
  | Deep-research artifacts | `docs/history/research/{topic}/` | README.md + findings.md + sources.md |
35
36
  | Brainstorm design records | `docs/research/{topic}-brainstorm/` (active; legacy `-brainstorming/`) → `docs/history/brainstorms/{topic}/` (harvested; user deletes or commits) | thesis.md + forks.md + ledger.md + state.md |
36
37
  | Backlog | `docs/history/` | backlog.md |
@@ -149,6 +149,7 @@ docs/
149
149
  │ ├── plans/ # Implementation plans (Status field required)
150
150
  │ ├── migrations/ # One-shot migration/upgrade runbooks (YYYY-MM-DD-{slug}.md; outcome-stamped then frozen)
151
151
  │ ├── journal.md # Append-only mid-execution knowledge capture (one line per fact; resolves by landing in its owning doc) — author-created, never scaffolded
152
+ │ ├── sessions/ # Per-session gate logs (YYYY-MM-DD-{sid}.md) — machinery-written records, never drained, never edited down; created by the first gate event, never scaffolded
152
153
  │ ├── brainstorms/ # Harvested brainstorm sessions ({topic}/) — user decides kept (committed) or deleted; Readme.md is the index
153
154
  │ ├── research/ # Deep-research records ({topic}/ — README.md synthesis + findings.md + sources.md); frozen, cited, owns no code
154
155
  │ └── backlog.md ✱ # Tracked work items by priority (P0-P3)
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "harness": "etna",
3
3
  "name": "etna",
4
- "version": "1.4.4",
4
+ "version": "1.4.5",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
8
8
  ".vscode/settings.json"
9
9
  ],
10
10
  "seeds": [],
11
- "digest": "dca4815bd3d5"
11
+ "digest": "e0669fcb3dcf"
12
12
  }
@@ -12,7 +12,7 @@ When creating or updating a feature doc, derive its **`owns:` frontmatter** —
12
12
 
13
13
  ## How to derive the frontmatter
14
14
 
15
- A feature doc's ownership frontmatter is `owns:` (required), `module:` (optional), `keywords:` (required), and `status:` (required). An optional `last-reviewed:` date (stamped whenever the doc is substantively reviewed or updated) gives the `maintenance` agent a staleness signal to rank its audits by — it is tooling metadata, never narrated in the body. `type:` (required — the genre, enum = the template names) and `rev:` (generator-written generation stamp) are the same class: external tooling reads them, sessions **preserve them on edits** and never narrate or inject them. The `maintenance` agent stamps `type:` on docs that predate the key.
15
+ A feature doc's ownership frontmatter is `owns:` (required), `module:` (optional), `keywords:` (required), and `status:` (required). An optional `last-reviewed:` date (stamped whenever the doc is substantively reviewed or updated) gives the `maintenance` agent a staleness signal to rank its audits by — it is tooling metadata, never narrated in the body. `type:` (required — the genre, enum = the template names) and `rev:` (generator-written generation stamp) are the same class: external tooling reads them, sessions **preserve them on edits** and never narrate or inject them. The `maintenance` agent stamps `type:` on docs that predate the key. `slug:` completes the set on generated docs: the identity key, minted once at creation and never re-derived from a display name — in the repo the filename is the slug, and the `maintenance` agent re-aligns the two when they diverge.
16
16
 
17
17
  **1. `owns:`** (the path scope)
18
18
 
@@ -30,6 +30,8 @@ One file per template. Every template is either a **truth document** (states cur
30
30
  | Backlog | [backlog.md](backlog.md) | Record (append-only) |
31
31
  | Memory (main agent) | [memory-main.md](memory-main.md) | Record (append-only ledger) |
32
32
 
33
+ **The `type:` enum lives here.** A doc's `type:` frontmatter takes its value from this table — one genre per template (`feature`, `architecture-decision`, `non-goals`, …). External generators consume this list rather than defining their own; a type this table does not name defaults to `docs/architecture/` placement and is never added to the protected-files list.
34
+
33
35
  **Baseline scaffolds** — files that must exist after every baseline; the `maintenance` agent (baseline mode) creates any that are missing: [docs-readme.md](docs-readme.md), [structure.md](structure.md), [changelog-index.md](changelog-index.md), [backlog.md](backlog.md), [memory-main.md](memory-main.md), and [root-readme.md](root-readme.md) (never overwriting an existing root README).
34
36
 
35
37
  **Ownership.** A feature doc is the canonical reference for the code paths in its **`owns:` frontmatter** — the only ownership key. To find what a doc owns, read its `owns:` globs; to find the doc for a path, match the path against the injected ownership index (the union of every feature's `owns:` globs), narrower glob wins. See [`../references/frontmatter.md`](../references/frontmatter.md) for turning code into `owns:` globs.
@@ -31,6 +31,7 @@ Each topic has one canonical location. Read from here, not from plans or changel
31
31
  | Historical plans | `docs/history/plans/` | YYYY-MM-DD-name.md |
32
32
  | Change records | `docs/history/changelog/` | `YYYY-MM-DD-{three-words}.md` — one per unit of work, written at commit time |
33
33
  | Migration runbooks | `docs/history/migrations/` | YYYY-MM-DD-name.md |
34
+ | Session gate logs | `docs/history/sessions/` | `YYYY-MM-DD-{sid}.md` — machinery-written; denials and their resolutions, appended, never edited down |
34
35
  | Deep-research artifacts | `docs/history/research/{topic}/` | README.md + findings.md + sources.md |
35
36
  | Brainstorm design records | `docs/research/{topic}-brainstorm/` (active; legacy `-brainstorming/`) → `docs/history/brainstorms/{topic}/` (harvested; user deletes or commits) | thesis.md + forks.md + ledger.md + state.md |
36
37
  | Backlog | `docs/history/` | backlog.md |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.19.67",
3
+ "version": "0.19.68",
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": "546f4bccb873"
45
+ "etnaFingerprint": "d5b676c55c3a"
46
46
  }