@nurix/etna 0.19.43 → 0.19.44

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.
Files changed (48) hide show
  1. package/bin/etna.js +33 -6
  2. package/data/harness-index.json +1 -1
  3. package/data/harnesses/apollo/agents/maintenance.md +3 -1
  4. package/data/harnesses/apollo/harness.json +2 -2
  5. package/data/harnesses/apollo/skills/boundary/SKILL.md +3 -1
  6. package/data/harnesses/apollo/skills/documentation/SKILL.md +2 -0
  7. package/data/harnesses/apollo/skills/documentation/templates/Readme.md +2 -0
  8. package/data/harnesses/apollo/skills/documentation/templates/bookkeeping.md +18 -0
  9. package/data/harnesses/apollo/skills/documentation/templates/journal.md +13 -0
  10. package/data/harnesses/apollo/skills/documentation/templates/plan.md +3 -0
  11. package/data/harnesses/apollo/skills/test-plan/SKILL.md +2 -0
  12. package/data/harnesses/code/agents/bookkeeper.md +27 -0
  13. package/data/harnesses/code/agents/maintenance.md +3 -1
  14. package/data/harnesses/code/harness.json +2 -2
  15. package/data/harnesses/code/hooks/awareness.mjs +370 -0
  16. package/data/harnesses/code/hooks/check-bookkeeping-clean.mjs +19 -0
  17. package/data/harnesses/code/hooks/check-changelog-presence.mjs +48 -0
  18. package/data/harnesses/code/hooks/check-completion-integrity.mjs +68 -0
  19. package/data/harnesses/code/hooks/check-ownership.mjs +22 -0
  20. package/data/harnesses/code/hooks/compliance.mjs +332 -0
  21. package/data/harnesses/code/hooks/hooks.json +16 -10
  22. package/data/harnesses/code/hooks/lib/frozen.mjs +58 -0
  23. package/data/harnesses/code/hooks/lib/ownership.mjs +223 -0
  24. package/data/harnesses/code/hooks/lib/state.mjs +156 -0
  25. package/data/harnesses/code/skills/boundary/SKILL.md +3 -1
  26. package/data/harnesses/code/skills/documentation/SKILL.md +2 -0
  27. package/data/harnesses/code/skills/documentation/templates/Readme.md +2 -0
  28. package/data/harnesses/code/skills/documentation/templates/bookkeeping.md +18 -0
  29. package/data/harnesses/code/skills/documentation/templates/journal.md +13 -0
  30. package/data/harnesses/code/skills/documentation/templates/plan.md +3 -0
  31. package/data/harnesses/code/skills/test-plan/SKILL.md +2 -0
  32. package/data/harnesses/delivery/agents/maintenance.md +3 -1
  33. package/data/harnesses/delivery/harness.json +2 -2
  34. package/data/harnesses/delivery/skills/documentation/SKILL.md +2 -0
  35. package/data/harnesses/delivery/skills/documentation/templates/Readme.md +2 -0
  36. package/data/harnesses/delivery/skills/documentation/templates/bookkeeping.md +18 -0
  37. package/data/harnesses/delivery/skills/documentation/templates/journal.md +13 -0
  38. package/data/harnesses/delivery/skills/documentation/templates/plan.md +3 -0
  39. package/data/harnesses/etna/agents/maintenance.md +3 -1
  40. package/data/harnesses/etna/harness.json +2 -2
  41. package/data/harnesses/etna/skills/boundary/SKILL.md +3 -1
  42. package/data/harnesses/etna/skills/documentation/SKILL.md +2 -0
  43. package/data/harnesses/etna/skills/documentation/templates/Readme.md +2 -0
  44. package/data/harnesses/etna/skills/documentation/templates/bookkeeping.md +18 -0
  45. package/data/harnesses/etna/skills/documentation/templates/journal.md +13 -0
  46. package/data/harnesses/etna/skills/documentation/templates/plan.md +3 -0
  47. package/package.json +2 -2
  48. package/data/harnesses/code/hooks/generate-ownership-index.mjs +0 -148
@@ -0,0 +1,156 @@
1
+ // harness/code/hooks/lib/state.mjs — the bookkeeping queue (docs/operations/bookkeeping.md) and the
2
+ // machine-local state dir (.claude/state/). The row grammar is the contract between the awareness
3
+ // detector (writer), the bookkeeper agent (drain), the compliance gates (walls), and CI (backstop):
4
+ // - auto: {YYYY-MM-DD} · {finding-kind} · {description} → {action} | sid={session_id} tp={transcript_path} ts={ISO}
5
+ // - note: {YYYY-MM-DD} · {area} · {context} → lands-in: {owning doc | backlog | memory} | sid=… tp=… ts=…
6
+ // - needs-session: {YYYY-MM-DD} · {what could not be grounded} → main session resolves | refs={ts}
7
+ // - waived: {YYYY-MM-DD} · {what is deferred} · reason: {why} → expires={YYYY-MM-DD}
8
+ // Unresolved = auto + note + needs-session. waived: rows are resolved history until expires= passes.
9
+
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+
13
+ export const QUEUE_PATH = ["docs", "operations", "bookkeeping.md"];
14
+
15
+ export const QUEUE_HEADER = `# Bookkeeping
16
+
17
+ The shared work queue keeping documentation and code in sync — 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 resolved or waived. Row grammar and lifecycle: the behavioral-hooks feature doc. A \`waived:\` row carries \`expires=\` and is PR-reviewable history.
18
+ `;
19
+
20
+ export function queueFile(projectDir) {
21
+ return path.join(projectDir, ...QUEUE_PATH);
22
+ }
23
+
24
+ const ROW_RE = /^-\s+(auto|note|needs-session|waived):\s+(.*)$/;
25
+
26
+ // Parse the queue text into rows: { kind, date, body, raw }. Non-row lines are ignored.
27
+ export function parseQueue(text) {
28
+ const rows = [];
29
+ for (const line of String(text || "").split(/\r?\n/)) {
30
+ const m = ROW_RE.exec(line.trim());
31
+ if (!m) continue;
32
+ const body = m[2].trim();
33
+ const date = (/^(\d{4}-\d{2}-\d{2})/.exec(body) || [])[1] || "";
34
+ rows.push({ kind: m[1], date, body, raw: line });
35
+ }
36
+ return rows;
37
+ }
38
+
39
+ export function readQueue(projectDir) {
40
+ try {
41
+ return parseQueue(fs.readFileSync(queueFile(projectDir), "utf8"));
42
+ } catch (e) {
43
+ return [];
44
+ }
45
+ }
46
+
47
+ // Counts by kind; `unresolved` is the number the walls act on.
48
+ export function countRows(rows) {
49
+ const c = { auto: 0, note: 0, needsSession: 0, waived: 0 };
50
+ for (const row of rows) {
51
+ if (row.kind === "auto") c.auto += 1;
52
+ else if (row.kind === "note") c.note += 1;
53
+ else if (row.kind === "needs-session") c.needsSession += 1;
54
+ else if (row.kind === "waived") c.waived += 1;
55
+ }
56
+ c.unresolved = c.auto + c.note + c.needsSession;
57
+ return c;
58
+ }
59
+
60
+ // Format one auto: row per the grammar; sid/tp/ts come from the hook payload.
61
+ export function formatAutoRow({ date, kind, description, action, sessionId, transcriptPath, ts }) {
62
+ return `- auto: ${date} · ${kind} · ${description} → ${action} | sid=${sessionId || "-"} tp=${transcriptPath || "-"} ts=${ts || "-"}`;
63
+ }
64
+
65
+ // Append rows to the queue, creating the file with its genre header when absent.
66
+ // Dedupes on the row text before the `| sid=` tail, so a re-fired detector never stacks duplicates.
67
+ export function appendRows(projectDir, rowLines) {
68
+ if (!rowLines || rowLines.length === 0) return 0;
69
+ const file = queueFile(projectDir);
70
+ let text = "";
71
+ try {
72
+ text = fs.readFileSync(file, "utf8");
73
+ } catch (e) {
74
+ fs.mkdirSync(path.dirname(file), { recursive: true });
75
+ text = QUEUE_HEADER + "\n";
76
+ }
77
+ const key = (line) => line.split("| sid=")[0].trim();
78
+ const existing = new Set(parseQueue(text).map((r) => key(r.raw)));
79
+ const fresh = rowLines.filter((line) => !existing.has(key(line)));
80
+ if (fresh.length === 0) return 0;
81
+ if (!text.endsWith("\n")) text += "\n";
82
+ fs.writeFileSync(file, text + fresh.join("\n") + "\n");
83
+ return fresh.length;
84
+ }
85
+
86
+ // Delete queue rows matching the predicate (used by the detector to auto-resolve
87
+ // rows whose finding the session has since addressed). Returns the count removed.
88
+ export function resolveRows(projectDir, predicate) {
89
+ const file = queueFile(projectDir);
90
+ let text;
91
+ try {
92
+ text = fs.readFileSync(file, "utf8");
93
+ } catch (e) {
94
+ return 0;
95
+ }
96
+ let removed = 0;
97
+ const kept = text.split(/\r?\n/).filter((line) => {
98
+ const m = ROW_RE.exec(line.trim());
99
+ if (!m) return true;
100
+ const row = { kind: m[1], body: m[2].trim(), raw: line };
101
+ if (predicate(row)) {
102
+ removed += 1;
103
+ return false;
104
+ }
105
+ return true;
106
+ });
107
+ if (removed > 0) fs.writeFileSync(file, kept.join("\n"));
108
+ return removed;
109
+ }
110
+
111
+ // Unexpired waived rows for a finding kind (the dead-glob allowlist CI honors).
112
+ // A waived row covers a glob when its body names it verbatim.
113
+ export function unexpiredWaivers(rows, kind, today) {
114
+ return rows.filter((row) => {
115
+ if (row.kind !== "waived") return false;
116
+ if (kind && !row.body.includes(kind)) return false;
117
+ const expires = (/expires=(\d{4}-\d{2}-\d{2})/.exec(row.body) || [])[1];
118
+ if (!expires) return false;
119
+ return expires >= today;
120
+ });
121
+ }
122
+
123
+ // Ensure .claude/state/ exists and is self-ignoring (a `*` .gitignore inside it),
124
+ // so consumers never see machine-local state as untracked noise. Returns the dir.
125
+ export function stateDir(projectDir) {
126
+ const dir = path.join(projectDir, ".claude", "state");
127
+ fs.mkdirSync(dir, { recursive: true });
128
+ const ignore = path.join(dir, ".gitignore");
129
+ if (!fs.existsSync(ignore)) fs.writeFileSync(ignore, "*\n");
130
+ return dir;
131
+ }
132
+
133
+ // Session-scoped ask memory: an `ask` gate fires once per (session, class); after the user has
134
+ // answered it once, repeats in the same session pass silently (the runtime's own permission
135
+ // prompts still apply). Records live in .claude/state/asked.tsv — machine-local, self-ignored.
136
+ export function askedThisSession(projectDir, sessionId, key) {
137
+ if (!sessionId) return false;
138
+ try {
139
+ const text = fs.readFileSync(path.join(projectDir, ".claude", "state", "asked.tsv"), "utf8");
140
+ return text.split("\n").includes(`${sessionId}\t${key}`);
141
+ } catch (e) {
142
+ return false;
143
+ }
144
+ }
145
+
146
+ export function recordAsked(projectDir, sessionId, key) {
147
+ if (!sessionId) return;
148
+ fs.appendFileSync(path.join(stateDir(projectDir), "asked.tsv"), `${sessionId}\t${key}\n`);
149
+ }
150
+
151
+ // Append one tab-separated line to .claude/state/touched.tsv (file → owning doc map for the session).
152
+ export function recordTouched(projectDir, relPath, docs) {
153
+ const dir = stateDir(projectDir);
154
+ const line = `${new Date().toISOString()}\t${relPath}\t${docs.join(",") || "-"}\n`;
155
+ fs.appendFileSync(path.join(dir, "touched.tsv"), line);
156
+ }
@@ -61,9 +61,11 @@ The packs are a floor, not a fence: report any contract that passes the ships-gr
61
61
 
62
62
  **BLOCK** = any CRITICAL. HIGH findings yield **PASS WITH AMENDMENTS** — fold the amendments into the plan before coding. Order amendments sequencing-first: many violations dissolve by reordering steps (expand-then-contract, migrate-before-code) rather than changing what ships — prefer that fix, drawn from the entry's safe-change sequence.
63
63
 
64
+ 6. **Record the verdict in the plan's `Gates:` line.** Rewrite only your own segment, to exactly `Boundary: passed YYYY-MM-DD` (PASS, or PASS WITH AMENDMENTS once the amendments are folded) or `Boundary: blocked YYYY-MM-DD` (BLOCK). The compliance hook greps these exact forms — implementation edits stay blocked while the segment reads `pending`. A plan with no `Gates:` line predates the format; add the line rather than skipping the write-back.
65
+
64
66
  ## Rules
65
67
 
66
- - **Read-only.** You never edit the plan, the code, or any doc. The user folds your amendments into the plan.
68
+ - **Read-only, one exception.** You never edit the plan's content, the code, or any doc — the user folds your amendments into the plan. The single write you own is the plan's `Gates:` line (step 6), your segment only.
67
69
  - **Verify in source.** Never flag from plan text alone — read both sides of every finding and cite them: code as `file:line`, doctrine as the doc path.
68
70
  - **No other side, no violation.** An intersection with no traced consumer and no doctrine behind it isn't a violation — it's a **new edge**: report it in its own section with the hardening its mechanism prescribes (version the cache key now, make the enum append-only now), so the fragile edge is born safe.
69
71
  - **A clean report is valid.** Zero violations means say so — the coverage table is the evidence it was checked, not skipped. Don't pad findings.
@@ -73,6 +73,8 @@ The two guarantees are complementary and both break on mixing: a reader holding
73
73
  | Testing strategy | [`templates/testing-strategy.md`](templates/testing-strategy.md) |
74
74
  | Behavioral checklist for one feature (`docs/tests/{feature}.md`) | [`templates/behavioral-checklist.md`](templates/behavioral-checklist.md) — the verification gate's driving script, and the source its suite tests are drawn from |
75
75
  | Recurring procedure/setup | [`templates/operations-procedure.md`](templates/operations-procedure.md) |
76
+ | Capture mid-execution knowledge (`docs/history/journal.md`) | [`templates/journal.md`](templates/journal.md) (record — append-only; a row resolves by landing in its owning doc) |
77
+ | Bookkeeping work queue (`docs/operations/bookkeeping.md`) | [`templates/bookkeeping.md`](templates/bookkeeping.md) — machine-read row grammar; appended by hooks and sessions, drained by the `bookkeeper` agent, walled at Stop and commit |
76
78
  | One-shot migration/upgrade | [`templates/migration-runbook.md`](templates/migration-runbook.md) (record) |
77
79
  | Add to protected files | [`templates/protected-files.md`](templates/protected-files.md) |
78
80
  | Create/update root README | [`templates/root-readme.md`](templates/root-readme.md) |
@@ -16,6 +16,7 @@ One file per template. Every template is either a **truth document** (states cur
16
16
  | Testing strategy | [testing-strategy.md](testing-strategy.md) | Truth |
17
17
  | Behavioral checklist | [behavioral-checklist.md](behavioral-checklist.md) | Truth |
18
18
  | Operations procedure | [operations-procedure.md](operations-procedure.md) | Truth |
19
+ | Bookkeeping ledger | [bookkeeping.md](bookkeeping.md) | Truth (work queue) |
19
20
  | Protected files | [protected-files.md](protected-files.md) | Truth |
20
21
  | Project root README | [root-readme.md](root-readme.md) | Truth |
21
22
  | docs/README.md (doc index) | [docs-readme.md](docs-readme.md) | Truth |
@@ -23,6 +24,7 @@ One file per template. Every template is either a **truth document** (states cur
23
24
  | Plan | [plan.md](plan.md) | Record |
24
25
  | Changelog entry | [changelog-entry.md](changelog-entry.md) | Record |
25
26
  | Migration runbook | [migration-runbook.md](migration-runbook.md) | Record |
27
+ | Journal | [journal.md](journal.md) | Record (append-only) |
26
28
  | CHANGELOG master index | [changelog-index.md](changelog-index.md) | Record index (append-only) |
27
29
  | Backlog | [backlog.md](backlog.md) | Record (append-only) |
28
30
  | Memory (main agent) | [memory-main.md](memory-main.md) | Record (append-only ledger) |
@@ -0,0 +1,18 @@
1
+ # Bookkeeping ledger (docs/operations/bookkeeping.md)
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.
4
+
5
+ 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
+
7
+ ```
8
+ # Bookkeeping
9
+
10
+ The shared work queue keeping documentation and code in sync — 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 resolved or waived. Row grammar and lifecycle: the behavioral-hooks feature doc. A `waived:` row carries `expires=` and is PR-reviewable history.
11
+
12
+ - auto: YYYY-MM-DD · {finding-kind} · {description} → {action} | sid={session_id} tp={transcript_path} ts={ISO}
13
+ - note: YYYY-MM-DD · {area} · {one-line context only the session holds} → lands-in: {owning doc | backlog | memory} | sid=… tp=… ts=…
14
+ - needs-session: YYYY-MM-DD · {what the bookkeeper could not ground} → main session resolves | refs={original row ts}
15
+ - waived: YYYY-MM-DD · {what is deferred} · reason: {why} → expires=YYYY-MM-DD
16
+ ```
17
+
18
+ Finding kinds for `auto:`: `dead-glob` · `doc-implicated` · `doc-lint` · `header-missing` · `index-row-missing` · `boundary-scan-suggested` · `doc-placement`.
@@ -0,0 +1,13 @@
1
+ # Journal (docs/history/journal.md)
2
+
3
+ Record — append-only. Use for: durable mid-execution knowledge — a fact discovered while building that some owning doc will eventually carry, captured the moment it exists so it survives the session. A row is resolved by landing in its owning doc: the row is deleted and the changelog entry cites it. The file stays short by construction; a journal that only grows is a queue nobody drains.
4
+
5
+ One line per fact. Never narrative, never progress notes (progress lives in the plan), never user preferences (those go to the runtime's memory store).
6
+
7
+ ```
8
+ # Journal
9
+
10
+ Append-only capture of mid-execution knowledge — one line per fact, written the moment it exists, resolved by landing in its owning doc (delete the row, cite it in the changelog entry). Format: `YYYY-MM-DD · <area> · <one-line fact> → lands-in: <owning doc | backlog | memory>`
11
+
12
+ - YYYY-MM-DD · {area} · {one-line fact} → lands-in: {owning doc | backlog | memory}
13
+ ```
@@ -4,10 +4,13 @@ Record. Use for: every implementation plan. Plans are created here directly with
4
4
 
5
5
  The `boundary` skill parses the **Proposed Changes** sections to check boundary impact before execution, and the `test-plan` skill writes a **Test Obligations** section into the plan at the same gate. Keep that structure stable — section headings and bullet shape are load-bearing for the parsers, and the Test Obligations table is the ledger the verification gate discharges before the plan may reach `Status: Complete`.
6
6
 
7
+ The **`Gates:`** line is machine-read: the compliance hook greps exactly the forms below, and implementation edits are blocked while a segment reads `pending`. Each gate skill rewrites only its own segment when it runs — `boundary` to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`; `test-plan` to `Test-plan: written YYYY-MM-DD (N rows)`. Never hand-edit the line to skip a gate.
8
+
7
9
  ```
8
10
  # {Feature Name}
9
11
 
10
12
  **Status:** Draft | In Progress | Complete | Abandoned
13
+ **Gates:** Boundary: pending · Test-plan: pending
11
14
  **Spec:** [docs/features/{feature}.md](../../features/{feature}.md)
12
15
  **Started:** YYYY-MM-DD
13
16
  **Completed:** YYYY-MM-DD <!-- fill when status flips to Complete -->
@@ -73,6 +73,8 @@ An e2e obligation (a scenario in the project's e2e tree per `testing.md`) is ear
73
73
 
74
74
  A behavior with no obligation is stated too, with the reason (compiler-guarded, covered by an existing test) — an empty cell reads as "not yet analyzed"; an explicit "none — compiler-guarded" reads as "checked."
75
75
 
76
+ 6. **Record the write in the plan's `Gates:` line.** Rewrite only your own segment, to exactly `Test-plan: written YYYY-MM-DD (N rows)` where N is the ledger's row count. The compliance hook greps this exact form. A plan with no `Gates:` line predates the format; add the line rather than skipping the write-back.
77
+
76
78
  ## Rules
77
79
 
78
80
  - **Manifest only.** You write the Test Obligations section into the plan and a chat summary. You write **no** test files and **no** `docs/tests/` checklist — those are verification-gate-time and Completion-time, owned by others.
@@ -115,7 +115,7 @@ For unowned code paths:
115
115
 
116
116
  ### Step 6: Report & update tracking
117
117
 
118
- 1. Update `docs/structure.md` Health Log with today's date and summary — **append to the end**, never date-sorted into the middle. Rows are positional: one may say "the row above is…", and an inserted row silently breaks that reference.
118
+ 1. Update `docs/structure.md` Health Log with today's date and summary — **append to the end**, never date-sorted into the middle. Rows are positional: one may say "the row above is…", and an inserted row silently breaks that reference. A Health Log entry is a **generated row, not a narrative essay**: one line — date · counts (stale docs fixed, dead globs, unowned paths, backlog items created) · links to anything that needs a human.
119
119
  2. Update `docs/memory/maintenance.md` with last review date, stale docs fixed, unowned paths, backlog items created.
120
120
  3. Report findings: stale docs, broken ownership, unowned code, architecture drift, backlog health.
121
121
 
@@ -137,6 +137,7 @@ docs/
137
137
  ├── features/ # Feature specs — the only first-class doc unit (flat; ownership in each doc's `owns:` frontmatter)
138
138
  │ └── {group}/ # browse-only subfolder for grouping; NEVER a module; may carry a Readme.md
139
139
  ├── operations/ # Setup, development, deployment, CI/CD
140
+ │ └── bookkeeping.md # the shared doc-sync work queue (hook-appended, bookkeeper-drained, walled at commit) — author-created, never scaffolded; treat rows as live work, never as drift
140
141
  ├── research/ # In-progress brainstorm sessions ({topic}-brainstorm/; legacy -brainstorming/) + standing research; Readme.md is the index
141
142
  │ └── {topic}-brainstorm/ # created by the `brainstorm` skill; moves to history/brainstorms/ on a passed harvest
142
143
  ├── history/
@@ -144,6 +145,7 @@ docs/
144
145
  │ ├── changelog/ # Individual entries (YYYY-MM-DD-{three-words}.md; written at commit time)
145
146
  │ ├── plans/ # Implementation plans (Status field required)
146
147
  │ ├── migrations/ # One-shot migration/upgrade runbooks (YYYY-MM-DD-{slug}.md; outcome-stamped then frozen)
148
+ │ ├── journal.md # Append-only mid-execution knowledge capture (one line per fact; resolves by landing in its owning doc) — author-created, never scaffolded
147
149
  │ ├── brainstorms/ # Harvested brainstorm sessions ({topic}/) — user decides kept (committed) or deleted; Readme.md is the index
148
150
  │ ├── research/ # Deep-research records ({topic}/ — README.md synthesis + findings.md + sources.md); frozen, cited, owns no code
149
151
  │ └── 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.1.5",
4
+ "version": "0.1.6",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
8
8
  ".vscode/settings.json"
9
9
  ],
10
10
  "seeds": [],
11
- "digest": "ee08c0d0162a"
11
+ "digest": "dd6fdce1e2aa"
12
12
  }
@@ -73,6 +73,8 @@ The two guarantees are complementary and both break on mixing: a reader holding
73
73
  | Testing strategy | [`templates/testing-strategy.md`](templates/testing-strategy.md) |
74
74
  | Behavioral checklist for one feature (`docs/tests/{feature}.md`) | [`templates/behavioral-checklist.md`](templates/behavioral-checklist.md) — the verification gate's driving script, and the source its suite tests are drawn from |
75
75
  | Recurring procedure/setup | [`templates/operations-procedure.md`](templates/operations-procedure.md) |
76
+ | Capture mid-execution knowledge (`docs/history/journal.md`) | [`templates/journal.md`](templates/journal.md) (record — append-only; a row resolves by landing in its owning doc) |
77
+ | Bookkeeping work queue (`docs/operations/bookkeeping.md`) | [`templates/bookkeeping.md`](templates/bookkeeping.md) — machine-read row grammar; appended by hooks and sessions, drained by the `bookkeeper` agent, walled at Stop and commit |
76
78
  | One-shot migration/upgrade | [`templates/migration-runbook.md`](templates/migration-runbook.md) (record) |
77
79
  | Add to protected files | [`templates/protected-files.md`](templates/protected-files.md) |
78
80
  | Create/update root README | [`templates/root-readme.md`](templates/root-readme.md) |
@@ -16,6 +16,7 @@ One file per template. Every template is either a **truth document** (states cur
16
16
  | Testing strategy | [testing-strategy.md](testing-strategy.md) | Truth |
17
17
  | Behavioral checklist | [behavioral-checklist.md](behavioral-checklist.md) | Truth |
18
18
  | Operations procedure | [operations-procedure.md](operations-procedure.md) | Truth |
19
+ | Bookkeeping ledger | [bookkeeping.md](bookkeeping.md) | Truth (work queue) |
19
20
  | Protected files | [protected-files.md](protected-files.md) | Truth |
20
21
  | Project root README | [root-readme.md](root-readme.md) | Truth |
21
22
  | docs/README.md (doc index) | [docs-readme.md](docs-readme.md) | Truth |
@@ -23,6 +24,7 @@ One file per template. Every template is either a **truth document** (states cur
23
24
  | Plan | [plan.md](plan.md) | Record |
24
25
  | Changelog entry | [changelog-entry.md](changelog-entry.md) | Record |
25
26
  | Migration runbook | [migration-runbook.md](migration-runbook.md) | Record |
27
+ | Journal | [journal.md](journal.md) | Record (append-only) |
26
28
  | CHANGELOG master index | [changelog-index.md](changelog-index.md) | Record index (append-only) |
27
29
  | Backlog | [backlog.md](backlog.md) | Record (append-only) |
28
30
  | Memory (main agent) | [memory-main.md](memory-main.md) | Record (append-only ledger) |
@@ -0,0 +1,18 @@
1
+ # Bookkeeping ledger (docs/operations/bookkeeping.md)
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.
4
+
5
+ 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
+
7
+ ```
8
+ # Bookkeeping
9
+
10
+ The shared work queue keeping documentation and code in sync — 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 resolved or waived. Row grammar and lifecycle: the behavioral-hooks feature doc. A `waived:` row carries `expires=` and is PR-reviewable history.
11
+
12
+ - auto: YYYY-MM-DD · {finding-kind} · {description} → {action} | sid={session_id} tp={transcript_path} ts={ISO}
13
+ - note: YYYY-MM-DD · {area} · {one-line context only the session holds} → lands-in: {owning doc | backlog | memory} | sid=… tp=… ts=…
14
+ - needs-session: YYYY-MM-DD · {what the bookkeeper could not ground} → main session resolves | refs={original row ts}
15
+ - waived: YYYY-MM-DD · {what is deferred} · reason: {why} → expires=YYYY-MM-DD
16
+ ```
17
+
18
+ Finding kinds for `auto:`: `dead-glob` · `doc-implicated` · `doc-lint` · `header-missing` · `index-row-missing` · `boundary-scan-suggested` · `doc-placement`.
@@ -0,0 +1,13 @@
1
+ # Journal (docs/history/journal.md)
2
+
3
+ Record — append-only. Use for: durable mid-execution knowledge — a fact discovered while building that some owning doc will eventually carry, captured the moment it exists so it survives the session. A row is resolved by landing in its owning doc: the row is deleted and the changelog entry cites it. The file stays short by construction; a journal that only grows is a queue nobody drains.
4
+
5
+ One line per fact. Never narrative, never progress notes (progress lives in the plan), never user preferences (those go to the runtime's memory store).
6
+
7
+ ```
8
+ # Journal
9
+
10
+ Append-only capture of mid-execution knowledge — one line per fact, written the moment it exists, resolved by landing in its owning doc (delete the row, cite it in the changelog entry). Format: `YYYY-MM-DD · <area> · <one-line fact> → lands-in: <owning doc | backlog | memory>`
11
+
12
+ - YYYY-MM-DD · {area} · {one-line fact} → lands-in: {owning doc | backlog | memory}
13
+ ```
@@ -4,10 +4,13 @@ Record. Use for: every implementation plan. Plans are created here directly with
4
4
 
5
5
  The `boundary` skill parses the **Proposed Changes** sections to check boundary impact before execution, and the `test-plan` skill writes a **Test Obligations** section into the plan at the same gate. Keep that structure stable — section headings and bullet shape are load-bearing for the parsers, and the Test Obligations table is the ledger the verification gate discharges before the plan may reach `Status: Complete`.
6
6
 
7
+ The **`Gates:`** line is machine-read: the compliance hook greps exactly the forms below, and implementation edits are blocked while a segment reads `pending`. Each gate skill rewrites only its own segment when it runs — `boundary` to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`; `test-plan` to `Test-plan: written YYYY-MM-DD (N rows)`. Never hand-edit the line to skip a gate.
8
+
7
9
  ```
8
10
  # {Feature Name}
9
11
 
10
12
  **Status:** Draft | In Progress | Complete | Abandoned
13
+ **Gates:** Boundary: pending · Test-plan: pending
11
14
  **Spec:** [docs/features/{feature}.md](../../features/{feature}.md)
12
15
  **Started:** YYYY-MM-DD
13
16
  **Completed:** YYYY-MM-DD <!-- fill when status flips to Complete -->
@@ -115,7 +115,7 @@ For unowned code paths:
115
115
 
116
116
  ### Step 6: Report & update tracking
117
117
 
118
- 1. Update `docs/structure.md` Health Log with today's date and summary — **append to the end**, never date-sorted into the middle. Rows are positional: one may say "the row above is…", and an inserted row silently breaks that reference.
118
+ 1. Update `docs/structure.md` Health Log with today's date and summary — **append to the end**, never date-sorted into the middle. Rows are positional: one may say "the row above is…", and an inserted row silently breaks that reference. A Health Log entry is a **generated row, not a narrative essay**: one line — date · counts (stale docs fixed, dead globs, unowned paths, backlog items created) · links to anything that needs a human.
119
119
  2. Update `docs/memory/maintenance.md` with last review date, stale docs fixed, unowned paths, backlog items created.
120
120
  3. Report findings: stale docs, broken ownership, unowned code, architecture drift, backlog health.
121
121
 
@@ -137,6 +137,7 @@ docs/
137
137
  ├── features/ # Feature specs — the only first-class doc unit (flat; ownership in each doc's `owns:` frontmatter)
138
138
  │ └── {group}/ # browse-only subfolder for grouping; NEVER a module; may carry a Readme.md
139
139
  ├── operations/ # Setup, development, deployment, CI/CD
140
+ │ └── bookkeeping.md # the shared doc-sync work queue (hook-appended, bookkeeper-drained, walled at commit) — author-created, never scaffolded; treat rows as live work, never as drift
140
141
  ├── research/ # In-progress brainstorm sessions ({topic}-brainstorm/; legacy -brainstorming/) + standing research; Readme.md is the index
141
142
  │ └── {topic}-brainstorm/ # created by the `brainstorm` skill; moves to history/brainstorms/ on a passed harvest
142
143
  ├── history/
@@ -144,6 +145,7 @@ docs/
144
145
  │ ├── changelog/ # Individual entries (YYYY-MM-DD-{three-words}.md; written at commit time)
145
146
  │ ├── plans/ # Implementation plans (Status field required)
146
147
  │ ├── migrations/ # One-shot migration/upgrade runbooks (YYYY-MM-DD-{slug}.md; outcome-stamped then frozen)
148
+ │ ├── journal.md # Append-only mid-execution knowledge capture (one line per fact; resolves by landing in its owning doc) — author-created, never scaffolded
147
149
  │ ├── brainstorms/ # Harvested brainstorm sessions ({topic}/) — user decides kept (committed) or deleted; Readme.md is the index
148
150
  │ ├── research/ # Deep-research records ({topic}/ — README.md synthesis + findings.md + sources.md); frozen, cited, owns no code
149
151
  │ └── 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.3.8",
4
+ "version": "1.3.9",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
8
8
  ".vscode/settings.json"
9
9
  ],
10
10
  "seeds": [],
11
- "digest": "91acf558021c"
11
+ "digest": "12d918b0c33e"
12
12
  }
@@ -61,9 +61,11 @@ The packs are a floor, not a fence: report any contract that passes the ships-gr
61
61
 
62
62
  **BLOCK** = any CRITICAL. HIGH findings yield **PASS WITH AMENDMENTS** — fold the amendments into the plan before coding. Order amendments sequencing-first: many violations dissolve by reordering steps (expand-then-contract, migrate-before-code) rather than changing what ships — prefer that fix, drawn from the entry's safe-change sequence.
63
63
 
64
+ 6. **Record the verdict in the plan's `Gates:` line.** Rewrite only your own segment, to exactly `Boundary: passed YYYY-MM-DD` (PASS, or PASS WITH AMENDMENTS once the amendments are folded) or `Boundary: blocked YYYY-MM-DD` (BLOCK). The compliance hook greps these exact forms — implementation edits stay blocked while the segment reads `pending`. A plan with no `Gates:` line predates the format; add the line rather than skipping the write-back.
65
+
64
66
  ## Rules
65
67
 
66
- - **Read-only.** You never edit the plan, the code, or any doc. The user folds your amendments into the plan.
68
+ - **Read-only, one exception.** You never edit the plan's content, the code, or any doc — the user folds your amendments into the plan. The single write you own is the plan's `Gates:` line (step 6), your segment only.
67
69
  - **Verify in source.** Never flag from plan text alone — read both sides of every finding and cite them: code as `file:line`, doctrine as the doc path.
68
70
  - **No other side, no violation.** An intersection with no traced consumer and no doctrine behind it isn't a violation — it's a **new edge**: report it in its own section with the hardening its mechanism prescribes (version the cache key now, make the enum append-only now), so the fragile edge is born safe.
69
71
  - **A clean report is valid.** Zero violations means say so — the coverage table is the evidence it was checked, not skipped. Don't pad findings.
@@ -73,6 +73,8 @@ The two guarantees are complementary and both break on mixing: a reader holding
73
73
  | Testing strategy | [`templates/testing-strategy.md`](templates/testing-strategy.md) |
74
74
  | Behavioral checklist for one feature (`docs/tests/{feature}.md`) | [`templates/behavioral-checklist.md`](templates/behavioral-checklist.md) — the verification gate's driving script, and the source its suite tests are drawn from |
75
75
  | Recurring procedure/setup | [`templates/operations-procedure.md`](templates/operations-procedure.md) |
76
+ | Capture mid-execution knowledge (`docs/history/journal.md`) | [`templates/journal.md`](templates/journal.md) (record — append-only; a row resolves by landing in its owning doc) |
77
+ | Bookkeeping work queue (`docs/operations/bookkeeping.md`) | [`templates/bookkeeping.md`](templates/bookkeeping.md) — machine-read row grammar; appended by hooks and sessions, drained by the `bookkeeper` agent, walled at Stop and commit |
76
78
  | One-shot migration/upgrade | [`templates/migration-runbook.md`](templates/migration-runbook.md) (record) |
77
79
  | Add to protected files | [`templates/protected-files.md`](templates/protected-files.md) |
78
80
  | Create/update root README | [`templates/root-readme.md`](templates/root-readme.md) |
@@ -16,6 +16,7 @@ One file per template. Every template is either a **truth document** (states cur
16
16
  | Testing strategy | [testing-strategy.md](testing-strategy.md) | Truth |
17
17
  | Behavioral checklist | [behavioral-checklist.md](behavioral-checklist.md) | Truth |
18
18
  | Operations procedure | [operations-procedure.md](operations-procedure.md) | Truth |
19
+ | Bookkeeping ledger | [bookkeeping.md](bookkeeping.md) | Truth (work queue) |
19
20
  | Protected files | [protected-files.md](protected-files.md) | Truth |
20
21
  | Project root README | [root-readme.md](root-readme.md) | Truth |
21
22
  | docs/README.md (doc index) | [docs-readme.md](docs-readme.md) | Truth |
@@ -23,6 +24,7 @@ One file per template. Every template is either a **truth document** (states cur
23
24
  | Plan | [plan.md](plan.md) | Record |
24
25
  | Changelog entry | [changelog-entry.md](changelog-entry.md) | Record |
25
26
  | Migration runbook | [migration-runbook.md](migration-runbook.md) | Record |
27
+ | Journal | [journal.md](journal.md) | Record (append-only) |
26
28
  | CHANGELOG master index | [changelog-index.md](changelog-index.md) | Record index (append-only) |
27
29
  | Backlog | [backlog.md](backlog.md) | Record (append-only) |
28
30
  | Memory (main agent) | [memory-main.md](memory-main.md) | Record (append-only ledger) |
@@ -0,0 +1,18 @@
1
+ # Bookkeeping ledger (docs/operations/bookkeeping.md)
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.
4
+
5
+ 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
+
7
+ ```
8
+ # Bookkeeping
9
+
10
+ The shared work queue keeping documentation and code in sync — 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 resolved or waived. Row grammar and lifecycle: the behavioral-hooks feature doc. A `waived:` row carries `expires=` and is PR-reviewable history.
11
+
12
+ - auto: YYYY-MM-DD · {finding-kind} · {description} → {action} | sid={session_id} tp={transcript_path} ts={ISO}
13
+ - note: YYYY-MM-DD · {area} · {one-line context only the session holds} → lands-in: {owning doc | backlog | memory} | sid=… tp=… ts=…
14
+ - needs-session: YYYY-MM-DD · {what the bookkeeper could not ground} → main session resolves | refs={original row ts}
15
+ - waived: YYYY-MM-DD · {what is deferred} · reason: {why} → expires=YYYY-MM-DD
16
+ ```
17
+
18
+ Finding kinds for `auto:`: `dead-glob` · `doc-implicated` · `doc-lint` · `header-missing` · `index-row-missing` · `boundary-scan-suggested` · `doc-placement`.
@@ -0,0 +1,13 @@
1
+ # Journal (docs/history/journal.md)
2
+
3
+ Record — append-only. Use for: durable mid-execution knowledge — a fact discovered while building that some owning doc will eventually carry, captured the moment it exists so it survives the session. A row is resolved by landing in its owning doc: the row is deleted and the changelog entry cites it. The file stays short by construction; a journal that only grows is a queue nobody drains.
4
+
5
+ One line per fact. Never narrative, never progress notes (progress lives in the plan), never user preferences (those go to the runtime's memory store).
6
+
7
+ ```
8
+ # Journal
9
+
10
+ Append-only capture of mid-execution knowledge — one line per fact, written the moment it exists, resolved by landing in its owning doc (delete the row, cite it in the changelog entry). Format: `YYYY-MM-DD · <area> · <one-line fact> → lands-in: <owning doc | backlog | memory>`
11
+
12
+ - YYYY-MM-DD · {area} · {one-line fact} → lands-in: {owning doc | backlog | memory}
13
+ ```
@@ -4,10 +4,13 @@ Record. Use for: every implementation plan. Plans are created here directly with
4
4
 
5
5
  The `boundary` skill parses the **Proposed Changes** sections to check boundary impact before execution, and the `test-plan` skill writes a **Test Obligations** section into the plan at the same gate. Keep that structure stable — section headings and bullet shape are load-bearing for the parsers, and the Test Obligations table is the ledger the verification gate discharges before the plan may reach `Status: Complete`.
6
6
 
7
+ The **`Gates:`** line is machine-read: the compliance hook greps exactly the forms below, and implementation edits are blocked while a segment reads `pending`. Each gate skill rewrites only its own segment when it runs — `boundary` to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`; `test-plan` to `Test-plan: written YYYY-MM-DD (N rows)`. Never hand-edit the line to skip a gate.
8
+
7
9
  ```
8
10
  # {Feature Name}
9
11
 
10
12
  **Status:** Draft | In Progress | Complete | Abandoned
13
+ **Gates:** Boundary: pending · Test-plan: pending
11
14
  **Spec:** [docs/features/{feature}.md](../../features/{feature}.md)
12
15
  **Started:** YYYY-MM-DD
13
16
  **Completed:** YYYY-MM-DD <!-- fill when status flips to Complete -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.19.43",
3
+ "version": "0.19.44",
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": {
@@ -41,5 +41,5 @@
41
41
  "agent",
42
42
  "plugin"
43
43
  ],
44
- "etnaFingerprint": "4d6e06e37895"
44
+ "etnaFingerprint": "0140c843638d"
45
45
  }