@nurix/etna 0.19.58 → 0.19.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/harness-index.json +1 -1
- package/data/harnesses/apollo/agents/bookkeeper.md +1 -1
- package/data/harnesses/apollo/codex/agents/bookkeeper.toml +1 -1
- package/data/harnesses/apollo/harness.json +1 -1
- package/data/harnesses/apollo/hooks/awareness.mjs +25 -0
- package/data/harnesses/apollo/hooks/check-completion-integrity.mjs +6 -3
- package/data/harnesses/apollo/hooks/compliance.mjs +6 -21
- package/data/harnesses/apollo/hooks/lib/state.mjs +8 -1
- package/data/harnesses/apollo/skills/documentation/templates/bookkeeping.md +1 -1
- package/data/harnesses/code/agents/bookkeeper.md +1 -1
- package/data/harnesses/code/codex/agents/bookkeeper.toml +1 -1
- package/data/harnesses/code/harness.json +1 -1
- package/data/harnesses/code/hooks/awareness.mjs +25 -0
- package/data/harnesses/code/hooks/check-completion-integrity.mjs +6 -3
- package/data/harnesses/code/hooks/compliance.mjs +6 -21
- package/data/harnesses/code/hooks/lib/state.mjs +8 -1
- package/data/harnesses/code/skills/documentation/templates/bookkeeping.md +1 -1
- package/data/harnesses/delivery/harness.json +1 -1
- package/data/harnesses/delivery/skills/documentation/templates/bookkeeping.md +1 -1
- package/data/harnesses/etna/harness.json +1 -1
- package/data/harnesses/etna/skills/documentation/templates/bookkeeping.md +1 -1
- package/package.json +2 -2
package/data/harness-index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bookkeeper
|
|
3
|
-
description: Drains docs/operations/bookkeeping.md — performs mechanical fixes (owns: globs, header comments, doc-lint findings, index rows), expands note: rows into doc edits,
|
|
3
|
+
description: Drains docs/operations/bookkeeping.md — performs mechanical fixes (owns: globs, header comments, doc-lint findings, index rows), expands note: rows into doc edits, deletes each row it drains. Dispatched by the main session at turn end or commit time; never self-triggers. Triggers on "drain the bookkeeping queue", "dispatch the bookkeeper", "/bookkeeper".
|
|
4
4
|
tools: Read, Glob, Grep, Edit, Write, Bash, Skill
|
|
5
5
|
model: sonnet
|
|
6
6
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dropped Claude tools: Read, Glob, Grep, Edit, Write, Bash, Skill
|
|
2
2
|
name = "bookkeeper"
|
|
3
|
-
description = "Drains docs/operations/bookkeeping.md — performs mechanical fixes (owns: globs, header comments, doc-lint findings, index rows), expands note: rows into doc edits,
|
|
3
|
+
description = "Drains docs/operations/bookkeeping.md — performs mechanical fixes (owns: globs, header comments, doc-lint findings, index rows), expands note: rows into doc edits, deletes each row it drains. Dispatched by the main session at turn end or commit time; never self-triggers. Triggers on \"drain the bookkeeping queue\", \"dispatch the bookkeeper\", \"/bookkeeper\"."
|
|
4
4
|
developer_instructions = '''
|
|
5
5
|
|
|
6
6
|
# Bookkeeper Agent
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
readQueue,
|
|
24
24
|
countRows,
|
|
25
25
|
formatAutoRow,
|
|
26
|
+
formatNeedsSessionRow,
|
|
26
27
|
appendRows,
|
|
27
28
|
resolveRows,
|
|
28
29
|
unexpiredWaivers,
|
|
@@ -245,6 +246,30 @@ function detectorForFile(projectDir, rel, meta, rows) {
|
|
|
245
246
|
if (rel.endsWith(".md")) {
|
|
246
247
|
// An edited doc resolves any doc-implicated row that pointed at it.
|
|
247
248
|
resolveRows(projectDir, (row) => row.kind === "auto" && row.body.includes("doc-implicated") && row.body.includes(rel));
|
|
249
|
+
|
|
250
|
+
// A plan's Doc Obligations ledger names docs whose stated behavior the plan falsifies. Whether
|
|
251
|
+
// one is discharged is a judgment, never a mechanical fix — so it becomes a `needs-session:`
|
|
252
|
+
// row, the one kind the bookkeeper leaves untouched, rather than an `auto:` row a sub-agent
|
|
253
|
+
// would tick without reading the code. Raising it here rather than walling it at Stop is what
|
|
254
|
+
// gives it the queue's three existing walls (Stop, commit, CI) instead of a fourth special case.
|
|
255
|
+
// Resolve-then-append keeps exactly one row per plan, so re-editing never stacks duplicates.
|
|
256
|
+
if (rel.startsWith("docs/history/plans/")) {
|
|
257
|
+
const planText = readSafe(path.join(projectDir, rel));
|
|
258
|
+
resolveRows(projectDir, (row) => row.kind === "needs-session" && row.body.includes(rel));
|
|
259
|
+
if (
|
|
260
|
+
/^\*\*Status:\*\*\s*Complete\b/m.test(planText) &&
|
|
261
|
+
/^##\s+Doc Obligations\b/m.test(planText) &&
|
|
262
|
+
/^\s*-\s*\[ \]\s*Every Doc Obligations row discharged/m.test(planText)
|
|
263
|
+
) {
|
|
264
|
+
rows.push(
|
|
265
|
+
formatNeedsSessionRow({
|
|
266
|
+
date: meta.date,
|
|
267
|
+
description: `${rel} reached Status: Complete with its Doc Obligations rows undischarged`,
|
|
268
|
+
refs: rel,
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
248
273
|
if (rel.startsWith("docs/")) {
|
|
249
274
|
const findings = docLint(projectDir, rel);
|
|
250
275
|
if (findings.length) {
|
|
@@ -41,13 +41,16 @@ for (const file of plans) {
|
|
|
41
41
|
const unchecked = (text.match(/^- \[ \]/gm) || []).length;
|
|
42
42
|
if (unchecked > 0) fail(`${file} is Complete with ${unchecked} unchecked Done Criteria box(es)`);
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// EVERY Spec line, not the first. A plan may implement against several feature docs, and a
|
|
45
|
+
// single-match parse silently exempts all but one — the same singular assumption the Doc
|
|
46
|
+
// Obligations ledger exists to break, reintroduced here where nothing would surface it.
|
|
47
|
+
const specs = [...text.matchAll(/^\*\*Spec:\*\*\s*\[[^\]]*\]\(([^)]+)\)/gm)].map((m) => m[1]);
|
|
48
|
+
for (const spec of specs) {
|
|
46
49
|
const specPath = path.resolve(plansDir, spec);
|
|
47
50
|
const specText = readSafe(specPath);
|
|
48
51
|
const specStatus = (field(frontmatter(specText), "status") || "").toString();
|
|
49
52
|
if (specText && specStatus !== "active") {
|
|
50
|
-
fail(`${file} is Complete but its Spec LLD is status: ${specStatus || "unknown"} (expected active)`);
|
|
53
|
+
fail(`${file} is Complete but its Spec LLD ${spec} is status: ${specStatus || "unknown"} (expected active)`);
|
|
51
54
|
}
|
|
52
55
|
const feature = path.basename(specPath, ".md");
|
|
53
56
|
if (!fs.existsSync(path.join(projectDir, "docs", "tests", `${feature}.md`))) {
|
|
@@ -308,15 +308,6 @@ function plansCompletedThisSession(projectDir, sessionId) {
|
|
|
308
308
|
return out;
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
// A completed plan whose Doc Obligations ledger was never discharged. Discharge is a judgment the
|
|
312
|
-
// hook cannot make — whether a doc was actually updated well is a human read — so what it checks is
|
|
313
|
-
// the one mechanical trace the act leaves: the Done Criteria checkbox that says every row was
|
|
314
|
-
// examined. Presence of the section is gated at the implementation-edit wall; this is the other end.
|
|
315
|
-
function docLedgerUndischarged(text) {
|
|
316
|
-
if (!/^##\s+Doc Obligations\b/m.test(text)) return false;
|
|
317
|
-
return /^\s*-\s*\[ \]\s*Every Doc Obligations row discharged/m.test(text);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
311
|
// Whether some changelog entry already accounts for this plan.
|
|
321
312
|
//
|
|
322
313
|
// COMMITTED COUNTS. The test used to be `git status --porcelain docs/history/changelog/` — i.e. "is
|
|
@@ -397,19 +388,13 @@ function stopGate(projectDir, payload) {
|
|
|
397
388
|
};
|
|
398
389
|
}
|
|
399
390
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
391
|
+
// An undischarged Doc Obligations ledger is not walled here — the detector raises it as a
|
|
392
|
+
// `needs-session:` row, which the queue wall above already blocks on, and which outlives this
|
|
393
|
+
// stop through the commit gate and CI. A second wall keyed to one checkbox would add a special
|
|
394
|
+
// case beside a general rule that already covers it.
|
|
395
|
+
const undocumented = plansCompletedThisSession(projectDir, payload.session_id).filter(
|
|
396
|
+
(rel) => !planHasChangelogEntry(projectDir, rel)
|
|
404
397
|
);
|
|
405
|
-
if (docsOpen.length > 0) {
|
|
406
|
-
return {
|
|
407
|
-
decision: "block",
|
|
408
|
-
reason: `Plan \`${docsOpen[0]}\` reached Status: Complete with its Doc Obligations rows undischarged. Walk each row — update the doc, or mark it owes nothing with the reason, or send it to the backlog as blocked — then tick the Done Criteria line. The feature docs are the only artifact the next session inherits.`,
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const undocumented = completed.filter((rel) => !planHasChangelogEntry(projectDir, rel));
|
|
413
398
|
if (undocumented.length > 0) {
|
|
414
399
|
return {
|
|
415
400
|
decision: "block",
|
|
@@ -14,7 +14,7 @@ export const QUEUE_PATH = ["docs", "operations", "bookkeeping.md"];
|
|
|
14
14
|
|
|
15
15
|
export const QUEUE_HEADER = `# Bookkeeping
|
|
16
16
|
|
|
17
|
-
The shared work queue keeping documentation and code in sync —
|
|
17
|
+
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.
|
|
18
18
|
`;
|
|
19
19
|
|
|
20
20
|
export function queueFile(projectDir) {
|
|
@@ -63,6 +63,13 @@ export function formatAutoRow({ date, kind, description, action, sessionId }) {
|
|
|
63
63
|
return `- auto: ${date} · ${kind} · ${description} → ${action} | sid=${sessionId || "-"}`;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Format one needs-session: row — a finding detected mechanically that only a session can resolve.
|
|
67
|
+
// It carries no `sid=`: unlike an auto: row it is never drained by the bookkeeper, so the session
|
|
68
|
+
// that raised it is not the one that must answer it.
|
|
69
|
+
export function formatNeedsSessionRow({ date, description, refs }) {
|
|
70
|
+
return `- needs-session: ${date} · ${description} → main session resolves | refs=${refs}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
// Append rows to the queue, creating the file with its genre header when absent.
|
|
67
74
|
// Dedupes on the row text before the `| sid=` tail, so a re-fired detector never stacks duplicates.
|
|
68
75
|
export function appendRows(projectDir, rowLines) {
|
|
@@ -7,7 +7,7 @@ The row grammar is machine-read by the hooks and CI; keep it exact. A `waived:`
|
|
|
7
7
|
```
|
|
8
8
|
# Bookkeeping
|
|
9
9
|
|
|
10
|
-
The shared work queue keeping documentation and code in sync —
|
|
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.
|
|
11
11
|
|
|
12
12
|
- auto: YYYY-MM-DD · {finding-kind} · {description} → {action} | sid={session_id} tp={transcript_path} ts={ISO}
|
|
13
13
|
- note: YYYY-MM-DD · {area} · {one-line context only the session holds} → lands-in: {owning doc | backlog | memory} | sid=… tp=… ts=…
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bookkeeper
|
|
3
|
-
description: Drains docs/operations/bookkeeping.md — performs mechanical fixes (owns: globs, header comments, doc-lint findings, index rows), expands note: rows into doc edits,
|
|
3
|
+
description: Drains docs/operations/bookkeeping.md — performs mechanical fixes (owns: globs, header comments, doc-lint findings, index rows), expands note: rows into doc edits, deletes each row it drains. Dispatched by the main session at turn end or commit time; never self-triggers. Triggers on "drain the bookkeeping queue", "dispatch the bookkeeper", "/bookkeeper".
|
|
4
4
|
tools: Read, Glob, Grep, Edit, Write, Bash, Skill
|
|
5
5
|
model: sonnet
|
|
6
6
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dropped Claude tools: Read, Glob, Grep, Edit, Write, Bash, Skill
|
|
2
2
|
name = "bookkeeper"
|
|
3
|
-
description = "Drains docs/operations/bookkeeping.md — performs mechanical fixes (owns: globs, header comments, doc-lint findings, index rows), expands note: rows into doc edits,
|
|
3
|
+
description = "Drains docs/operations/bookkeeping.md — performs mechanical fixes (owns: globs, header comments, doc-lint findings, index rows), expands note: rows into doc edits, deletes each row it drains. Dispatched by the main session at turn end or commit time; never self-triggers. Triggers on \"drain the bookkeeping queue\", \"dispatch the bookkeeper\", \"/bookkeeper\"."
|
|
4
4
|
developer_instructions = '''
|
|
5
5
|
|
|
6
6
|
# Bookkeeper Agent
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
readQueue,
|
|
24
24
|
countRows,
|
|
25
25
|
formatAutoRow,
|
|
26
|
+
formatNeedsSessionRow,
|
|
26
27
|
appendRows,
|
|
27
28
|
resolveRows,
|
|
28
29
|
unexpiredWaivers,
|
|
@@ -245,6 +246,30 @@ function detectorForFile(projectDir, rel, meta, rows) {
|
|
|
245
246
|
if (rel.endsWith(".md")) {
|
|
246
247
|
// An edited doc resolves any doc-implicated row that pointed at it.
|
|
247
248
|
resolveRows(projectDir, (row) => row.kind === "auto" && row.body.includes("doc-implicated") && row.body.includes(rel));
|
|
249
|
+
|
|
250
|
+
// A plan's Doc Obligations ledger names docs whose stated behavior the plan falsifies. Whether
|
|
251
|
+
// one is discharged is a judgment, never a mechanical fix — so it becomes a `needs-session:`
|
|
252
|
+
// row, the one kind the bookkeeper leaves untouched, rather than an `auto:` row a sub-agent
|
|
253
|
+
// would tick without reading the code. Raising it here rather than walling it at Stop is what
|
|
254
|
+
// gives it the queue's three existing walls (Stop, commit, CI) instead of a fourth special case.
|
|
255
|
+
// Resolve-then-append keeps exactly one row per plan, so re-editing never stacks duplicates.
|
|
256
|
+
if (rel.startsWith("docs/history/plans/")) {
|
|
257
|
+
const planText = readSafe(path.join(projectDir, rel));
|
|
258
|
+
resolveRows(projectDir, (row) => row.kind === "needs-session" && row.body.includes(rel));
|
|
259
|
+
if (
|
|
260
|
+
/^\*\*Status:\*\*\s*Complete\b/m.test(planText) &&
|
|
261
|
+
/^##\s+Doc Obligations\b/m.test(planText) &&
|
|
262
|
+
/^\s*-\s*\[ \]\s*Every Doc Obligations row discharged/m.test(planText)
|
|
263
|
+
) {
|
|
264
|
+
rows.push(
|
|
265
|
+
formatNeedsSessionRow({
|
|
266
|
+
date: meta.date,
|
|
267
|
+
description: `${rel} reached Status: Complete with its Doc Obligations rows undischarged`,
|
|
268
|
+
refs: rel,
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
248
273
|
if (rel.startsWith("docs/")) {
|
|
249
274
|
const findings = docLint(projectDir, rel);
|
|
250
275
|
if (findings.length) {
|
|
@@ -41,13 +41,16 @@ for (const file of plans) {
|
|
|
41
41
|
const unchecked = (text.match(/^- \[ \]/gm) || []).length;
|
|
42
42
|
if (unchecked > 0) fail(`${file} is Complete with ${unchecked} unchecked Done Criteria box(es)`);
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// EVERY Spec line, not the first. A plan may implement against several feature docs, and a
|
|
45
|
+
// single-match parse silently exempts all but one — the same singular assumption the Doc
|
|
46
|
+
// Obligations ledger exists to break, reintroduced here where nothing would surface it.
|
|
47
|
+
const specs = [...text.matchAll(/^\*\*Spec:\*\*\s*\[[^\]]*\]\(([^)]+)\)/gm)].map((m) => m[1]);
|
|
48
|
+
for (const spec of specs) {
|
|
46
49
|
const specPath = path.resolve(plansDir, spec);
|
|
47
50
|
const specText = readSafe(specPath);
|
|
48
51
|
const specStatus = (field(frontmatter(specText), "status") || "").toString();
|
|
49
52
|
if (specText && specStatus !== "active") {
|
|
50
|
-
fail(`${file} is Complete but its Spec LLD is status: ${specStatus || "unknown"} (expected active)`);
|
|
53
|
+
fail(`${file} is Complete but its Spec LLD ${spec} is status: ${specStatus || "unknown"} (expected active)`);
|
|
51
54
|
}
|
|
52
55
|
const feature = path.basename(specPath, ".md");
|
|
53
56
|
if (!fs.existsSync(path.join(projectDir, "docs", "tests", `${feature}.md`))) {
|
|
@@ -308,15 +308,6 @@ function plansCompletedThisSession(projectDir, sessionId) {
|
|
|
308
308
|
return out;
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
// A completed plan whose Doc Obligations ledger was never discharged. Discharge is a judgment the
|
|
312
|
-
// hook cannot make — whether a doc was actually updated well is a human read — so what it checks is
|
|
313
|
-
// the one mechanical trace the act leaves: the Done Criteria checkbox that says every row was
|
|
314
|
-
// examined. Presence of the section is gated at the implementation-edit wall; this is the other end.
|
|
315
|
-
function docLedgerUndischarged(text) {
|
|
316
|
-
if (!/^##\s+Doc Obligations\b/m.test(text)) return false;
|
|
317
|
-
return /^\s*-\s*\[ \]\s*Every Doc Obligations row discharged/m.test(text);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
311
|
// Whether some changelog entry already accounts for this plan.
|
|
321
312
|
//
|
|
322
313
|
// COMMITTED COUNTS. The test used to be `git status --porcelain docs/history/changelog/` — i.e. "is
|
|
@@ -397,19 +388,13 @@ function stopGate(projectDir, payload) {
|
|
|
397
388
|
};
|
|
398
389
|
}
|
|
399
390
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
391
|
+
// An undischarged Doc Obligations ledger is not walled here — the detector raises it as a
|
|
392
|
+
// `needs-session:` row, which the queue wall above already blocks on, and which outlives this
|
|
393
|
+
// stop through the commit gate and CI. A second wall keyed to one checkbox would add a special
|
|
394
|
+
// case beside a general rule that already covers it.
|
|
395
|
+
const undocumented = plansCompletedThisSession(projectDir, payload.session_id).filter(
|
|
396
|
+
(rel) => !planHasChangelogEntry(projectDir, rel)
|
|
404
397
|
);
|
|
405
|
-
if (docsOpen.length > 0) {
|
|
406
|
-
return {
|
|
407
|
-
decision: "block",
|
|
408
|
-
reason: `Plan \`${docsOpen[0]}\` reached Status: Complete with its Doc Obligations rows undischarged. Walk each row — update the doc, or mark it owes nothing with the reason, or send it to the backlog as blocked — then tick the Done Criteria line. The feature docs are the only artifact the next session inherits.`,
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const undocumented = completed.filter((rel) => !planHasChangelogEntry(projectDir, rel));
|
|
413
398
|
if (undocumented.length > 0) {
|
|
414
399
|
return {
|
|
415
400
|
decision: "block",
|
|
@@ -14,7 +14,7 @@ export const QUEUE_PATH = ["docs", "operations", "bookkeeping.md"];
|
|
|
14
14
|
|
|
15
15
|
export const QUEUE_HEADER = `# Bookkeeping
|
|
16
16
|
|
|
17
|
-
The shared work queue keeping documentation and code in sync —
|
|
17
|
+
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.
|
|
18
18
|
`;
|
|
19
19
|
|
|
20
20
|
export function queueFile(projectDir) {
|
|
@@ -63,6 +63,13 @@ export function formatAutoRow({ date, kind, description, action, sessionId }) {
|
|
|
63
63
|
return `- auto: ${date} · ${kind} · ${description} → ${action} | sid=${sessionId || "-"}`;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Format one needs-session: row — a finding detected mechanically that only a session can resolve.
|
|
67
|
+
// It carries no `sid=`: unlike an auto: row it is never drained by the bookkeeper, so the session
|
|
68
|
+
// that raised it is not the one that must answer it.
|
|
69
|
+
export function formatNeedsSessionRow({ date, description, refs }) {
|
|
70
|
+
return `- needs-session: ${date} · ${description} → main session resolves | refs=${refs}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
// Append rows to the queue, creating the file with its genre header when absent.
|
|
67
74
|
// Dedupes on the row text before the `| sid=` tail, so a re-fired detector never stacks duplicates.
|
|
68
75
|
export function appendRows(projectDir, rowLines) {
|
|
@@ -7,7 +7,7 @@ The row grammar is machine-read by the hooks and CI; keep it exact. A `waived:`
|
|
|
7
7
|
```
|
|
8
8
|
# Bookkeeping
|
|
9
9
|
|
|
10
|
-
The shared work queue keeping documentation and code in sync —
|
|
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.
|
|
11
11
|
|
|
12
12
|
- auto: YYYY-MM-DD · {finding-kind} · {description} → {action} | sid={session_id} tp={transcript_path} ts={ISO}
|
|
13
13
|
- note: YYYY-MM-DD · {area} · {one-line context only the session holds} → lands-in: {owning doc | backlog | memory} | sid=… tp=… ts=…
|
|
@@ -7,7 +7,7 @@ The row grammar is machine-read by the hooks and CI; keep it exact. A `waived:`
|
|
|
7
7
|
```
|
|
8
8
|
# Bookkeeping
|
|
9
9
|
|
|
10
|
-
The shared work queue keeping documentation and code in sync —
|
|
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.
|
|
11
11
|
|
|
12
12
|
- auto: YYYY-MM-DD · {finding-kind} · {description} → {action} | sid={session_id} tp={transcript_path} ts={ISO}
|
|
13
13
|
- note: YYYY-MM-DD · {area} · {one-line context only the session holds} → lands-in: {owning doc | backlog | memory} | sid=… tp=… ts=…
|
|
@@ -7,7 +7,7 @@ The row grammar is machine-read by the hooks and CI; keep it exact. A `waived:`
|
|
|
7
7
|
```
|
|
8
8
|
# Bookkeeping
|
|
9
9
|
|
|
10
|
-
The shared work queue keeping documentation and code in sync —
|
|
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.
|
|
11
11
|
|
|
12
12
|
- auto: YYYY-MM-DD · {finding-kind} · {description} → {action} | sid={session_id} tp={transcript_path} ts={ISO}
|
|
13
13
|
- 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.
|
|
3
|
+
"version": "0.19.59",
|
|
4
4
|
"description": "Bootstrap a project with a Nurix harness — copies the harness's CLAUDE.md, skills, agents, rules, and hooks directly into the project from a bundled payload (no marketplace fetch, no network).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"agent",
|
|
43
43
|
"plugin"
|
|
44
44
|
],
|
|
45
|
-
"etnaFingerprint": "
|
|
45
|
+
"etnaFingerprint": "44a8ddcfae78"
|
|
46
46
|
}
|