@nurix/etna 0.19.91 → 0.19.92

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": "b52694cf6295",
2
+ "fingerprint": "a1cf8eb35f57",
3
3
  "harnesses": {
4
4
  "code": {
5
5
  "plugin": "code",
@@ -139,7 +139,7 @@ Defaults — ❌ prohibitions hold unless the user explicitly asks otherwise;
139
139
  - ❌ **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.
140
140
  - ❌ **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.
141
141
  - ✅ **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.
142
- - ✅ **Bookkeeping** — hooks silently append doc-debt rows to `docs/operations/bookkeeping.md`; the commit hook blocks while unresolved rows remain — dispatch the `bookkeeper` agent to drain the queue. The queue is a queue, never an archive: a drained row is **deleted**, so a fully drained file holds no rows at all. **Every row is a cache of a condition, not a record of an event** — the session-start sweep re-tests each row against the repo and deletes the ones whose condition has died, `waived:` rows included, so the file empties itself as the work lands. What the sweep cannot re-test ages out instead: **a `waived:` row is unresolved once `expires=` passes**, which returns the wall and re-summons the bookkeeper to re-judge the reason. A waiver defers a finding; it never makes one immortal.
142
+ - ✅ **Bookkeeping** — hooks silently append doc-debt rows to `docs/operations/bookkeeping.md`; the commit hook blocks while any unresolved row **implicates a file the commit stages** — dispatch the `bookkeeper` agent to drain those rows. Rows about files the commit does not touch are whoever raised them's, and are not yours to drain; a row naming no path, a `needs-session:` row, an expired waiver, and a commit staging nothing block regardless. The queue is a queue, never an archive: a drained row is **deleted**, so a fully drained file holds no rows at all. **Every row is a cache of a condition, not a record of an event** — the session-start sweep re-tests each row against the repo and deletes the ones whose condition has died, `waived:` rows included, so the file empties itself as the work lands. What the sweep cannot re-test ages out instead: **a `waived:` row is unresolved once `expires=` passes**, which returns the wall and re-summons the bookkeeper to re-judge the reason. A waiver defers a finding; it never makes one immortal.
143
143
  - ✅ **Cleanup removes the cause, not the record of it.** When the user asks for something to be deleted or cleaned up, the ask is for the underlying condition to be gone. Deleting the record while the condition survives is worse than doing nothing — it destroys the only evidence the debt exists. The order is fixed: **read what the record is masking** (a `waived:` row, an `UNBUILT:` marker, a `status:` flag, a lint suppression — each asserts something is fine; check whether that is still true), **fix the condition**, **then delete the record**. Reversing it backfires concretely: a deleted waiver's finding is re-appended by the detector as an `auto:` row, which *fails* the commit wall the `waived:` row passed.
144
144
  - ✅ **Delegation** — when to fan out, tiers, and trust are § Delegation.
145
145
  - ⚠️ **Plans** – Plans are created directly in `docs/history/plans/` (see Workflows). Once `Status: Complete` or `Status: Abandoned`, the file is frozen — never edited. Abandoning a plan whose code was committed first converts its undischarged Test Obligations into backlog items (the `testing` rule). If scope changes after completion, write a new dated plan that links back to the old one.
@@ -7,7 +7,7 @@ model: sonnet
7
7
 
8
8
  # Bookkeeper Agent
9
9
 
10
- You drain the shared work queue at `docs/operations/bookkeeping.md`. Each row is one finding; your job is to resolve it — by doing the work it names — and delete the row. You are the asynchronous half of the harness's enforcement loop: the detector appends rows silently, you drain them, and the Stop/commit walls hold until the queue is clean or waived.
10
+ You drain the shared work queue at `docs/operations/bookkeeping.md`. Each row is one finding; your job is to resolve it — by doing the work it names — and delete the row. You are the asynchronous half of the harness's enforcement loop: the detector appends rows silently, you drain them, and the walls hold until the rows each one reads are clean or waived — Stop reads the ending session's rows, the commit gate reads the rows implicating that commit's staged files, CI reads them all.
11
11
 
12
12
  ## Source-of-truth order — the repo first, the transcript last
13
13
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "3.10.14",
4
+ "version": "3.10.15",
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": "98eb07166e9f"
18
+ "digest": "f07032c97db1"
19
19
  }
@@ -1,6 +1,10 @@
1
1
  // harness/code/hooks/check-bookkeeping-clean.mjs — CI backstop: no unresolved bookkeeping rows
2
2
  // (auto:/note:/needs-session:) may reach the remote. waived: rows pass — they are the recorded,
3
3
  // PR-reviewable exceptions. Exit 1 when unresolved rows exist.
4
+ //
5
+ // THIS CHECK IS GLOBAL AND STAYS GLOBAL. The commit gate scopes to one commit's staged set so
6
+ // concurrent agents in one checkout do not wall each other; CI judges the branch as a whole, where
7
+ // there is no staged set to scope by and every row has had every commit to be drained in.
4
8
 
5
9
  import { readQueue, countRows } from "./lib/state.mjs";
6
10
 
@@ -11,7 +11,7 @@ import { readStdin, resolveProjectDir } from "./lib/ownership.mjs";
11
11
  import { readSafe, relIfInside, git, commandRoot, backtickTokens, SOURCE_EXT } from "./lib/repo.mjs";
12
12
  import { openGatedPlans, inPlanScope } from "./lib/plans.mjs";
13
13
  import { stagedExempt } from "./lib/exempt.mjs";
14
- import { readQueue, countRows, askedThisSession, recordAsked } from "./lib/state.mjs";
14
+ import { readQueue, countRows, rowsBlockingCommit, askedThisSession, recordAsked } from "./lib/state.mjs";
15
15
  import { frozenStatus } from "./lib/frozen.mjs";
16
16
  import { undischargedRows } from "./lib/ledger.mjs";
17
17
  import { appendEvent, unresolvedDenials, sessionLogPath, malformedEventLines } from "./lib/session-log.mjs";
@@ -161,14 +161,29 @@ function commitGate(projectDir, sessionId) {
161
161
  );
162
162
  }
163
163
  }
164
- const counts = countRows(readQueue(projectDir));
165
- if (counts.unresolved > 0) {
164
+ // The bookkeeping wall is scoped to the staged set — the grammar and its fail-closed cases are
165
+ // `lib/state.mjs` § What a row implicates. Rows about files this commit does not touch are
166
+ // somebody else's, and holding a commit behind rows its session may not drain is a wait with no
167
+ // action that clears it.
168
+ const { blocking, unrelated } = rowsBlockingCommit(readQueue(projectDir), staged);
169
+ if (blocking.length > 0) {
170
+ const shown = blocking.slice(0, 8);
171
+ const lines = shown.map(({ row, hits }) =>
172
+ hits.length > 0
173
+ ? ` - ${row.kind}: ${row.body}\n ↳ implicates staged ${hits.join(", ")}`
174
+ : ` - ${row.kind}: ${row.body}\n ↳ names no staged path — a row like this blocks every commit`
175
+ );
176
+ if (blocking.length > shown.length) lines.push(` - …and ${blocking.length - shown.length} more`);
166
177
  return deny(
167
178
  projectDir,
168
179
  sessionId,
169
180
  "bookkeeping",
170
181
  "git commit",
171
- `BLOCKED — bookkeeping. ${counts.unresolved} unresolved row(s) in docs/operations/bookkeeping.md. Dispatch the bookkeeper, resolve, or convert to \`waived:\` with a reason and expiry.`
182
+ `BLOCKED — bookkeeping. ${blocking.length} unresolved row(s) in docs/operations/bookkeeping.md implicate this commit:\n${lines.join("\n")}\n` +
183
+ "Dispatch the bookkeeper, resolve them, or convert to `waived:` with a reason and expiry." +
184
+ (unrelated > 0
185
+ ? ` (${unrelated} further unresolved row(s) implicate nothing staged here — another session's work, and not yours to drain.)`
186
+ : "")
172
187
  );
173
188
  }
174
189
 
@@ -423,7 +438,8 @@ function stopGate(projectDir, payload) {
423
438
  if (payload.stop_hook_active) return null; // the runtime's infinite-loop guard — never re-block
424
439
 
425
440
  // The Stop wall is scoped to this session's own rows (plus sid-less ones) — a concurrent
426
- // session's open work must not block this one's stop. The commit wall stays global.
441
+ // session's open work must not block this one's stop. End-of-session accountability is per
442
+ // session and stays that way; the commit wall scopes by staged path instead, and CI by nothing.
427
443
  const mine = readQueue(projectDir).filter(
428
444
  (row) => !/\bsid=/.test(row.body) || row.body.includes(`sid=${payload.session_id}`)
429
445
  );
@@ -9,15 +9,20 @@
9
9
  // only while its expires= date is in the future; past it, the deferral is over and the wall returns.
10
10
  // Rows are also re-verified against the repo before the walls read them (awareness.mjs's sweep),
11
11
  // so a row whose condition died is deleted rather than waiting for its date.
12
+ //
13
+ // The three walls read the queue at three widths: the commit wall takes only the rows implicating
14
+ // the commit's staged set (§ What a row implicates), the Stop wall takes the ending session's own
15
+ // rows, and CI takes every row. Accountability is per session; a commit is per set of files.
12
16
 
13
17
  import fs from "node:fs";
14
18
  import path from "node:path";
19
+ import { globToRegExp } from "./ownership.mjs";
15
20
 
16
21
  export const QUEUE_PATH = ["docs", "operations", "bookkeeping.md"];
17
22
 
18
23
  export const QUEUE_HEADER = `# Bookkeeping
19
24
 
20
- The shared work queue keeping documentation and code in sync — a queue, never an archive. Rows are appended mechanically (auto:) and by working sessions (note:), drained by the bookkeeper agent, and walled at commit: a commit is possible only when every row is either **resolved, which deletes it**, or converted to \`waived:\`. A drained row leaves no trace here; what it closed is recorded in the doc it corrected, and git history holds the row itself. Row grammar and lifecycle: the \`documentation\` skill's \`templates/bookkeeping.md\`.\n\n**Every row is a cache of a condition, not a record of an event.** A session-start sweep re-tests each row against the repo and deletes the ones whose condition has died — \`waived:\` rows included, because a waiver defers a finding and never makes one immortal. What the sweep cannot mechanically re-test ages out instead: a \`waived:\` row carries \`expires=\`, is PR-reviewable history until that date, and counts as **unresolved** past it — which returns the wall and re-summons the bookkeeper to re-judge whether the reason still holds.
25
+ The shared work queue keeping documentation and code in sync — a queue, never an archive. Rows are appended mechanically (auto:) and by working sessions (note:), drained by the bookkeeper agent, and walled at commit: a commit goes through only when every row **implicating one of its staged files** is either **resolved, which deletes it**, or converted to \`waived:\`. A row about files the commit does not touch belongs to whoever raised it; a row naming no file, and a commit staging none, block everything. A drained row leaves no trace here; what it closed is recorded in the doc it corrected, and git history holds the row itself. Row grammar and lifecycle: the \`documentation\` skill's \`templates/bookkeeping.md\`.\n\n**Every row is a cache of a condition, not a record of an event.** A session-start sweep re-tests each row against the repo and deletes the ones whose condition has died — \`waived:\` rows included, because a waiver defers a finding and never makes one immortal. What the sweep cannot mechanically re-test ages out instead: a \`waived:\` row carries \`expires=\`, is PR-reviewable history until that date, and counts as **unresolved** past it — which returns the wall and re-summons the bookkeeper to re-judge whether the reason still holds.
21
26
  `;
22
27
 
23
28
  export function queueFile(projectDir) {
@@ -143,6 +148,85 @@ export function countRows(rows, today = new Date().toISOString().slice(0, 10)) {
143
148
  return c;
144
149
  }
145
150
 
151
+ // The unresolved rows themselves — the same set countRows() tallies, as rows rather than a number.
152
+ export function unresolvedRows(rows, today = new Date().toISOString().slice(0, 10)) {
153
+ return rows.filter((row) => {
154
+ if (row.kind !== "waived") return true;
155
+ const expires = waiverExpiry(row);
156
+ return !expires || expires < today;
157
+ });
158
+ }
159
+
160
+ // --- What a row implicates -------------------------------------------------------------------
161
+ //
162
+ // THE COMMIT WALL IS SCOPED BY PATH. Every mechanical finding names the file it is about, so a
163
+ // commit can be walled by the rows implicating ITS staged set rather than by the whole queue.
164
+ // A global wall is unattributable the moment agents share a checkout: sibling agents also share a
165
+ // session id, so an agent ready to commit was held behind rows a concurrent sibling raised — rows
166
+ // the ownership etiquette (drain only what your own edits raised) forbids it from draining, which
167
+ // makes the wall a wait with no action that clears it. Measured on the fleet: a 15-minute stall
168
+ // behind a sibling's ten rows, and two agents in 5–6-minute mutual politeness loops.
169
+ //
170
+ // The shapes below mirror the ones awareness.mjs's ROW_VERIFIERS re-test — one grammar, two
171
+ // readers. Each rule captures every path the row names: the file the finding is about AND the doc
172
+ // that owns or records it, since staging either one is a commit answering for that row.
173
+ //
174
+ // A ROW THIS CANNOT PARSE IMPLICATES EVERYTHING. Scoping may narrow a wall on evidence, never on
175
+ // ignorance — so an unrecognized row, a judgment row, and a commit with nothing staged to compare
176
+ // against all keep the old repo-wide block.
177
+ const ROW_PATH_RULES = [
178
+ /·\s*doc-implicated\s*·\s*(\S+)\s+changed;\s+owning doc\s+(\S+?)\s+may need/,
179
+ /·\s*doc-lint\s*·\s*(\S+)\s+has\s/,
180
+ /·\s*header-missing\s*·\s*(\S+)\s+lacks\s/,
181
+ /·\s*index-row-missing\s*·\s*(\S+)\s+is not linked from\s+(\S+)/,
182
+ /·\s*package-rejected\s*·\s*(\S+)\s+declares\s/,
183
+ /·\s*frozen-record-edited\s*·\s*(\S+)\s+is a frozen record/,
184
+ /·\s*dead-glob\s*·\s*owns: glob\s+`([^`]+)`\s+in\s+(\S+)\s+matches/,
185
+ /→\s*lands-in:\s*(\S+\/\S+)/, // a note: row names the doc its context lands in
186
+ ];
187
+
188
+ // The paths a row implicates, or null when its grammar yields none.
189
+ export function rowPaths(row) {
190
+ for (const re of ROW_PATH_RULES) {
191
+ const m = re.exec(row.body);
192
+ if (!m) continue;
193
+ const paths = m.slice(1).filter(Boolean);
194
+ if (paths.length > 0) return paths;
195
+ }
196
+ return null;
197
+ }
198
+
199
+ // True when one of a row's tokens names a staged file. A token carrying `*` — the glob a dead-glob
200
+ // row quotes — compiles through the ownership index's engine; a plain token matches a staged file
201
+ // exactly, or as the directory it sits in.
202
+ function tokenHitsStaged(token, staged) {
203
+ if (token.includes("*")) {
204
+ const re = globToRegExp(token);
205
+ return staged.some((f) => re.test(f));
206
+ }
207
+ return staged.some((f) => f === token || f.startsWith(token + "/"));
208
+ }
209
+
210
+ // The unresolved rows a commit staging `staged` must answer for, each with the staged paths it
211
+ // implicates. `needs-session:` rows and expired waivers are judgments rather than mechanical
212
+ // findings — they carry no reliable path and stay global by kind, which is the semantics they had
213
+ // before scoping. Returns { blocking, unrelated }: `unrelated` counts the rows this commit is
214
+ // walked past, which the block message reports so scoping is visible rather than silent.
215
+ export function rowsBlockingCommit(rows, staged, today = new Date().toISOString().slice(0, 10)) {
216
+ const blocking = [];
217
+ let unrelated = 0;
218
+ for (const row of unresolvedRows(rows, today)) {
219
+ const paths = row.kind === "auto" || row.kind === "note" ? rowPaths(row) : null;
220
+ const hits = paths && staged.length > 0 ? paths.filter((p) => tokenHitsStaged(p, staged)) : null;
221
+ if (hits && hits.length === 0) {
222
+ unrelated += 1;
223
+ continue;
224
+ }
225
+ blocking.push({ row, hits: hits || [] });
226
+ }
227
+ return { blocking, unrelated };
228
+ }
229
+
146
230
  // Format one auto: row per the grammar; the sid tail is the only provenance kept —
147
231
  // a transcript path or timestamp is machine noise in a PR-reviewed queue.
148
232
  export function formatAutoRow({ date, kind, description, action, sessionId }) {
@@ -1,13 +1,15 @@
1
1
  # Bookkeeping ledger (docs/operations/bookkeeping.md)
2
2
 
3
- Truth document with an unusual lifecycle: a **work queue**, not an archive. Rows are appended mechanically by the awareness hook (`auto:`) and deliberately by working sessions (`note:`), drained by the `bookkeeper` agent, and walled at Stop and commit — a commit is possible only when every row is resolved (deleted) or converted to `waived:`. What survives in git — `waived:` and `needs-session:` rows — is exactly what a PR reviewer should see.
3
+ Truth document with an unusual lifecycle: a **work queue**, not an archive. Rows are appended mechanically by the awareness hook (`auto:`) and deliberately by working sessions (`note:`), drained by the `bookkeeper` agent, and walled at Stop, at commit, and in CI. What survives in git — `waived:` and `needs-session:` rows — is exactly what a PR reviewer should see.
4
+
5
+ **Each wall reads the queue at its own width.** Stop takes the ending session's own rows, because accountability for an arc is per session. The commit gate takes only the rows **implicating that commit's staged files** — a row names the file its finding is about, and holding a commit behind rows raised by somebody else's edits is a wait nothing the committer may do can clear. CI takes every row on the branch, where there is no staged set to scope by. Fail-closed cases stay repo-wide at the commit gate: a row whose grammar names no path, a `needs-session:` row, an expired waiver, and a commit staging nothing at all.
4
6
 
5
7
  The row grammar is machine-read by the hooks and CI; keep it exact. A `waived:` row's `expires=` date is enforced: CI honors the waiver until the date, then fails. `waived:` rows whose finding kind is `dead-glob` double as the dead-glob allowlist.
6
8
 
7
9
  ```
8
10
  # Bookkeeping
9
11
 
10
- The shared work queue keeping documentation and code in sync — a queue, never an archive. Rows are appended mechanically (auto:) and by working sessions (note:), drained by the bookkeeper agent, and walled at commit: a commit is possible only when every row is either **resolved, which deletes it**, or converted to `waived:`. A drained row leaves no trace here; what it closed is recorded in the doc it corrected, and git history holds the row itself. Row grammar and lifecycle: the `documentation` skill's `templates/bookkeeping.md`. A `waived:` row carries `expires=` and is PR-reviewable history.
12
+ The shared work queue keeping documentation and code in sync — a queue, never an archive. Rows are appended mechanically (auto:) and by working sessions (note:), drained by the bookkeeper agent, and walled at commit: a commit goes through only when every row **implicating one of its staged files** is either **resolved, which deletes it**, or converted to `waived:`. A row about files the commit does not touch belongs to whoever raised it; a row naming no file, and a commit staging none, block everything. A drained row leaves no trace here; what it closed is recorded in the doc it corrected, and git history holds the row itself. Row grammar and lifecycle: the `documentation` skill's `templates/bookkeeping.md`. A `waived:` row carries `expires=` and is PR-reviewable history.
11
13
 
12
14
  - auto: YYYY-MM-DD · {finding-kind} · {description} → {action} | sid={session_id} tp={transcript_path} ts={ISO}
13
15
  - note: YYYY-MM-DD · {area} · {one-line context only the session holds} → lands-in: {owning doc | backlog | memory} | sid=… tp=… ts=…
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "harness": "delivery",
3
3
  "name": "delivery",
4
- "version": "0.2.19",
4
+ "version": "0.2.20",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
8
8
  ".vscode/settings.json"
9
9
  ],
10
10
  "seeds": [],
11
- "digest": "7358b638bff2"
11
+ "digest": "3bb0c6640019"
12
12
  }
@@ -1,13 +1,15 @@
1
1
  # Bookkeeping ledger (docs/operations/bookkeeping.md)
2
2
 
3
- Truth document with an unusual lifecycle: a **work queue**, not an archive. Rows are appended mechanically by the awareness hook (`auto:`) and deliberately by working sessions (`note:`), drained by the `bookkeeper` agent, and walled at Stop and commit — a commit is possible only when every row is resolved (deleted) or converted to `waived:`. What survives in git — `waived:` and `needs-session:` rows — is exactly what a PR reviewer should see.
3
+ Truth document with an unusual lifecycle: a **work queue**, not an archive. Rows are appended mechanically by the awareness hook (`auto:`) and deliberately by working sessions (`note:`), drained by the `bookkeeper` agent, and walled at Stop, at commit, and in CI. What survives in git — `waived:` and `needs-session:` rows — is exactly what a PR reviewer should see.
4
+
5
+ **Each wall reads the queue at its own width.** Stop takes the ending session's own rows, because accountability for an arc is per session. The commit gate takes only the rows **implicating that commit's staged files** — a row names the file its finding is about, and holding a commit behind rows raised by somebody else's edits is a wait nothing the committer may do can clear. CI takes every row on the branch, where there is no staged set to scope by. Fail-closed cases stay repo-wide at the commit gate: a row whose grammar names no path, a `needs-session:` row, an expired waiver, and a commit staging nothing at all.
4
6
 
5
7
  The row grammar is machine-read by the hooks and CI; keep it exact. A `waived:` row's `expires=` date is enforced: CI honors the waiver until the date, then fails. `waived:` rows whose finding kind is `dead-glob` double as the dead-glob allowlist.
6
8
 
7
9
  ```
8
10
  # Bookkeeping
9
11
 
10
- The shared work queue keeping documentation and code in sync — a queue, never an archive. Rows are appended mechanically (auto:) and by working sessions (note:), drained by the bookkeeper agent, and walled at commit: a commit is possible only when every row is either **resolved, which deletes it**, or converted to `waived:`. A drained row leaves no trace here; what it closed is recorded in the doc it corrected, and git history holds the row itself. Row grammar and lifecycle: the `documentation` skill's `templates/bookkeeping.md`. A `waived:` row carries `expires=` and is PR-reviewable history.
12
+ The shared work queue keeping documentation and code in sync — a queue, never an archive. Rows are appended mechanically (auto:) and by working sessions (note:), drained by the bookkeeper agent, and walled at commit: a commit goes through only when every row **implicating one of its staged files** is either **resolved, which deletes it**, or converted to `waived:`. A row about files the commit does not touch belongs to whoever raised it; a row naming no file, and a commit staging none, block everything. A drained row leaves no trace here; what it closed is recorded in the doc it corrected, and git history holds the row itself. Row grammar and lifecycle: the `documentation` skill's `templates/bookkeeping.md`. A `waived:` row carries `expires=` and is PR-reviewable history.
11
13
 
12
14
  - auto: YYYY-MM-DD · {finding-kind} · {description} → {action} | sid={session_id} tp={transcript_path} ts={ISO}
13
15
  - note: YYYY-MM-DD · {area} · {one-line context only the session holds} → lands-in: {owning doc | backlog | memory} | sid=… tp=… ts=…
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "harness": "etna",
3
3
  "name": "etna",
4
- "version": "1.5.8",
4
+ "version": "1.5.9",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
8
8
  ".vscode/settings.json"
9
9
  ],
10
10
  "seeds": [],
11
- "digest": "f2edee92fac2"
11
+ "digest": "269767b3abdf"
12
12
  }
@@ -1,13 +1,15 @@
1
1
  # Bookkeeping ledger (docs/operations/bookkeeping.md)
2
2
 
3
- Truth document with an unusual lifecycle: a **work queue**, not an archive. Rows are appended mechanically by the awareness hook (`auto:`) and deliberately by working sessions (`note:`), drained by the `bookkeeper` agent, and walled at Stop and commit — a commit is possible only when every row is resolved (deleted) or converted to `waived:`. What survives in git — `waived:` and `needs-session:` rows — is exactly what a PR reviewer should see.
3
+ Truth document with an unusual lifecycle: a **work queue**, not an archive. Rows are appended mechanically by the awareness hook (`auto:`) and deliberately by working sessions (`note:`), drained by the `bookkeeper` agent, and walled at Stop, at commit, and in CI. What survives in git — `waived:` and `needs-session:` rows — is exactly what a PR reviewer should see.
4
+
5
+ **Each wall reads the queue at its own width.** Stop takes the ending session's own rows, because accountability for an arc is per session. The commit gate takes only the rows **implicating that commit's staged files** — a row names the file its finding is about, and holding a commit behind rows raised by somebody else's edits is a wait nothing the committer may do can clear. CI takes every row on the branch, where there is no staged set to scope by. Fail-closed cases stay repo-wide at the commit gate: a row whose grammar names no path, a `needs-session:` row, an expired waiver, and a commit staging nothing at all.
4
6
 
5
7
  The row grammar is machine-read by the hooks and CI; keep it exact. A `waived:` row's `expires=` date is enforced: CI honors the waiver until the date, then fails. `waived:` rows whose finding kind is `dead-glob` double as the dead-glob allowlist.
6
8
 
7
9
  ```
8
10
  # Bookkeeping
9
11
 
10
- The shared work queue keeping documentation and code in sync — a queue, never an archive. Rows are appended mechanically (auto:) and by working sessions (note:), drained by the bookkeeper agent, and walled at commit: a commit is possible only when every row is either **resolved, which deletes it**, or converted to `waived:`. A drained row leaves no trace here; what it closed is recorded in the doc it corrected, and git history holds the row itself. Row grammar and lifecycle: the `documentation` skill's `templates/bookkeeping.md`. A `waived:` row carries `expires=` and is PR-reviewable history.
12
+ The shared work queue keeping documentation and code in sync — a queue, never an archive. Rows are appended mechanically (auto:) and by working sessions (note:), drained by the bookkeeper agent, and walled at commit: a commit goes through only when every row **implicating one of its staged files** is either **resolved, which deletes it**, or converted to `waived:`. A row about files the commit does not touch belongs to whoever raised it; a row naming no file, and a commit staging none, block everything. A drained row leaves no trace here; what it closed is recorded in the doc it corrected, and git history holds the row itself. Row grammar and lifecycle: the `documentation` skill's `templates/bookkeeping.md`. A `waived:` row carries `expires=` and is PR-reviewable history.
11
13
 
12
14
  - auto: YYYY-MM-DD · {finding-kind} · {description} → {action} | sid={session_id} tp={transcript_path} ts={ISO}
13
15
  - note: YYYY-MM-DD · {area} · {one-line context only the session holds} → lands-in: {owning doc | backlog | memory} | sid=… tp=… ts=…
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.19.91",
3
+ "version": "0.19.92",
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": "b52694cf6295"
45
+ "etnaFingerprint": "a1cf8eb35f57"
46
46
  }