@nurix/etna 0.19.45 → 0.19.46
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/harness.json +1 -1
- package/data/harnesses/apollo/hooks/awareness.mjs +10 -0
- package/data/harnesses/apollo/hooks/compliance.mjs +9 -3
- package/data/harnesses/apollo/hooks/lib/state.mjs +6 -5
- package/data/harnesses/code/harness.json +1 -1
- package/data/harnesses/code/hooks/awareness.mjs +10 -0
- package/data/harnesses/code/hooks/compliance.mjs +9 -3
- package/data/harnesses/code/hooks/lib/state.mjs +6 -5
- package/package.json +3 -2
package/data/harness-index.json
CHANGED
|
@@ -123,6 +123,14 @@ function driftBanner(projectDir) {
|
|
|
123
123
|
const waivers = unexpiredWaivers(queueRows, "dead-glob", today());
|
|
124
124
|
const unwaivedDead = dead.filter((d) => !waivers.some((w) => w.body.includes(d.glob)));
|
|
125
125
|
|
|
126
|
+
// A queued dead-glob row covered by an unexpired waiver is resolved history — drop it,
|
|
127
|
+
// so waiving a finding class actually empties the rows it supersedes.
|
|
128
|
+
resolveRows(projectDir, (row) => {
|
|
129
|
+
if (row.kind !== "auto" || !row.body.includes("dead-glob")) return false;
|
|
130
|
+
const m = /`([^`]+)`/.exec(row.body);
|
|
131
|
+
return !!m && waivers.some((w) => w.body.includes(`\`${m[1]}\``));
|
|
132
|
+
});
|
|
133
|
+
|
|
126
134
|
// Docs older than the code they own (doc mtime < newest owned-file mtime).
|
|
127
135
|
const staleDocs = [];
|
|
128
136
|
for (const entry of scanFeatureDocs(projectDir)) {
|
|
@@ -313,7 +321,9 @@ function detectorForFile(projectDir, rel, meta, rows) {
|
|
|
313
321
|
|
|
314
322
|
function detectRenames(projectDir, command, meta, rows) {
|
|
315
323
|
if (!/\bgit mv\b|\bmv\b/.test(command || "")) return;
|
|
324
|
+
const waivers = unexpiredWaivers(readQueue(projectDir), "dead-glob", today());
|
|
316
325
|
for (const { glob, doc } of deadGlobs(projectDir)) {
|
|
326
|
+
if (waivers.some((w) => w.body.includes(`\`${glob}\``))) continue;
|
|
317
327
|
rows.push(
|
|
318
328
|
formatAutoRow({
|
|
319
329
|
...meta,
|
|
@@ -232,7 +232,8 @@ function bashGate(projectDir, command, sessionId) {
|
|
|
232
232
|
return commitGate(projectDir);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
// `git branch <name>` creates; flag-led forms (--show-current, --list, -d, -vv) query or manage
|
|
236
|
+
if (statement(String.raw`git\s+(checkout\s+-b|switch\s+-c|branch\s+(?!-)\S+|worktree\s+add)`).test(command)) {
|
|
236
237
|
return askOnce(
|
|
237
238
|
projectDir,
|
|
238
239
|
sessionId,
|
|
@@ -290,12 +291,17 @@ function plansCompletedThisSession(projectDir) {
|
|
|
290
291
|
function stopGate(projectDir, payload) {
|
|
291
292
|
if (payload.stop_hook_active) return null; // the runtime's infinite-loop guard — never re-block
|
|
292
293
|
|
|
293
|
-
|
|
294
|
+
// The Stop wall is scoped to this session's own rows (plus sid-less ones) — a concurrent
|
|
295
|
+
// session's open work must not block this one's stop. The commit wall stays global.
|
|
296
|
+
const mine = readQueue(projectDir).filter(
|
|
297
|
+
(row) => !/\bsid=/.test(row.body) || row.body.includes(`sid=${payload.session_id}`)
|
|
298
|
+
);
|
|
299
|
+
const counts = countRows(mine);
|
|
294
300
|
if (counts.unresolved > 0 && !bookkeeperDispatched(payload.transcript_path)) {
|
|
295
301
|
return {
|
|
296
302
|
decision: "block",
|
|
297
303
|
reason:
|
|
298
|
-
`${counts.unresolved} unresolved bookkeeping rows (${counts.auto} auto, ${counts.note} note, ${counts.needsSession} needs-session) in docs/operations/bookkeeping.md. ` +
|
|
304
|
+
`${counts.unresolved} unresolved bookkeeping rows from this session (${counts.auto} auto, ${counts.note} note, ${counts.needsSession} needs-session) in docs/operations/bookkeeping.md. ` +
|
|
299
305
|
"Dispatch the `bookkeeper` agent to drain them, resolve them now, or convert to `waived:` rows with reason + expiry. " +
|
|
300
306
|
"If implementation of the open plan is complete, also report it and offer the verification gate — never run it unbidden.",
|
|
301
307
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// harness/code/hooks/lib/state.mjs — the bookkeeping queue (docs/operations/bookkeeping.md) and the
|
|
2
2
|
// machine-local state dir (.claude/state/). The row grammar is the contract between the awareness
|
|
3
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}
|
|
5
|
-
// - note: {YYYY-MM-DD} · {area} · {context} → lands-in: {owning doc | backlog | memory} | sid=…
|
|
4
|
+
// - auto: {YYYY-MM-DD} · {finding-kind} · {description} → {action} | sid={session_id}
|
|
5
|
+
// - note: {YYYY-MM-DD} · {area} · {context} → lands-in: {owning doc | backlog | memory} | sid=…
|
|
6
6
|
// - needs-session: {YYYY-MM-DD} · {what could not be grounded} → main session resolves | refs={ts}
|
|
7
7
|
// - waived: {YYYY-MM-DD} · {what is deferred} · reason: {why} → expires={YYYY-MM-DD}
|
|
8
8
|
// Unresolved = auto + note + needs-session. waived: rows are resolved history until expires= passes.
|
|
@@ -57,9 +57,10 @@ export function countRows(rows) {
|
|
|
57
57
|
return c;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// Format one auto: row per the grammar; sid
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
// Format one auto: row per the grammar; the sid tail is the only provenance kept —
|
|
61
|
+
// a transcript path or timestamp is machine noise in a PR-reviewed queue.
|
|
62
|
+
export function formatAutoRow({ date, kind, description, action, sessionId }) {
|
|
63
|
+
return `- auto: ${date} · ${kind} · ${description} → ${action} | sid=${sessionId || "-"}`;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
// Append rows to the queue, creating the file with its genre header when absent.
|
|
@@ -123,6 +123,14 @@ function driftBanner(projectDir) {
|
|
|
123
123
|
const waivers = unexpiredWaivers(queueRows, "dead-glob", today());
|
|
124
124
|
const unwaivedDead = dead.filter((d) => !waivers.some((w) => w.body.includes(d.glob)));
|
|
125
125
|
|
|
126
|
+
// A queued dead-glob row covered by an unexpired waiver is resolved history — drop it,
|
|
127
|
+
// so waiving a finding class actually empties the rows it supersedes.
|
|
128
|
+
resolveRows(projectDir, (row) => {
|
|
129
|
+
if (row.kind !== "auto" || !row.body.includes("dead-glob")) return false;
|
|
130
|
+
const m = /`([^`]+)`/.exec(row.body);
|
|
131
|
+
return !!m && waivers.some((w) => w.body.includes(`\`${m[1]}\``));
|
|
132
|
+
});
|
|
133
|
+
|
|
126
134
|
// Docs older than the code they own (doc mtime < newest owned-file mtime).
|
|
127
135
|
const staleDocs = [];
|
|
128
136
|
for (const entry of scanFeatureDocs(projectDir)) {
|
|
@@ -313,7 +321,9 @@ function detectorForFile(projectDir, rel, meta, rows) {
|
|
|
313
321
|
|
|
314
322
|
function detectRenames(projectDir, command, meta, rows) {
|
|
315
323
|
if (!/\bgit mv\b|\bmv\b/.test(command || "")) return;
|
|
324
|
+
const waivers = unexpiredWaivers(readQueue(projectDir), "dead-glob", today());
|
|
316
325
|
for (const { glob, doc } of deadGlobs(projectDir)) {
|
|
326
|
+
if (waivers.some((w) => w.body.includes(`\`${glob}\``))) continue;
|
|
317
327
|
rows.push(
|
|
318
328
|
formatAutoRow({
|
|
319
329
|
...meta,
|
|
@@ -232,7 +232,8 @@ function bashGate(projectDir, command, sessionId) {
|
|
|
232
232
|
return commitGate(projectDir);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
// `git branch <name>` creates; flag-led forms (--show-current, --list, -d, -vv) query or manage
|
|
236
|
+
if (statement(String.raw`git\s+(checkout\s+-b|switch\s+-c|branch\s+(?!-)\S+|worktree\s+add)`).test(command)) {
|
|
236
237
|
return askOnce(
|
|
237
238
|
projectDir,
|
|
238
239
|
sessionId,
|
|
@@ -290,12 +291,17 @@ function plansCompletedThisSession(projectDir) {
|
|
|
290
291
|
function stopGate(projectDir, payload) {
|
|
291
292
|
if (payload.stop_hook_active) return null; // the runtime's infinite-loop guard — never re-block
|
|
292
293
|
|
|
293
|
-
|
|
294
|
+
// The Stop wall is scoped to this session's own rows (plus sid-less ones) — a concurrent
|
|
295
|
+
// session's open work must not block this one's stop. The commit wall stays global.
|
|
296
|
+
const mine = readQueue(projectDir).filter(
|
|
297
|
+
(row) => !/\bsid=/.test(row.body) || row.body.includes(`sid=${payload.session_id}`)
|
|
298
|
+
);
|
|
299
|
+
const counts = countRows(mine);
|
|
294
300
|
if (counts.unresolved > 0 && !bookkeeperDispatched(payload.transcript_path)) {
|
|
295
301
|
return {
|
|
296
302
|
decision: "block",
|
|
297
303
|
reason:
|
|
298
|
-
`${counts.unresolved} unresolved bookkeeping rows (${counts.auto} auto, ${counts.note} note, ${counts.needsSession} needs-session) in docs/operations/bookkeeping.md. ` +
|
|
304
|
+
`${counts.unresolved} unresolved bookkeeping rows from this session (${counts.auto} auto, ${counts.note} note, ${counts.needsSession} needs-session) in docs/operations/bookkeeping.md. ` +
|
|
299
305
|
"Dispatch the `bookkeeper` agent to drain them, resolve them now, or convert to `waived:` rows with reason + expiry. " +
|
|
300
306
|
"If implementation of the open plan is complete, also report it and offer the verification gate — never run it unbidden.",
|
|
301
307
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// harness/code/hooks/lib/state.mjs — the bookkeeping queue (docs/operations/bookkeeping.md) and the
|
|
2
2
|
// machine-local state dir (.claude/state/). The row grammar is the contract between the awareness
|
|
3
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}
|
|
5
|
-
// - note: {YYYY-MM-DD} · {area} · {context} → lands-in: {owning doc | backlog | memory} | sid=…
|
|
4
|
+
// - auto: {YYYY-MM-DD} · {finding-kind} · {description} → {action} | sid={session_id}
|
|
5
|
+
// - note: {YYYY-MM-DD} · {area} · {context} → lands-in: {owning doc | backlog | memory} | sid=…
|
|
6
6
|
// - needs-session: {YYYY-MM-DD} · {what could not be grounded} → main session resolves | refs={ts}
|
|
7
7
|
// - waived: {YYYY-MM-DD} · {what is deferred} · reason: {why} → expires={YYYY-MM-DD}
|
|
8
8
|
// Unresolved = auto + note + needs-session. waived: rows are resolved history until expires= passes.
|
|
@@ -57,9 +57,10 @@ export function countRows(rows) {
|
|
|
57
57
|
return c;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// Format one auto: row per the grammar; sid
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
// Format one auto: row per the grammar; the sid tail is the only provenance kept —
|
|
61
|
+
// a transcript path or timestamp is machine noise in a PR-reviewed queue.
|
|
62
|
+
export function formatAutoRow({ date, kind, description, action, sessionId }) {
|
|
63
|
+
return `- auto: ${date} · ${kind} · ${description} → ${action} | sid=${sessionId || "-"}`;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
// Append rows to the queue, creating the file with its genre header when absent.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.46",
|
|
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": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"node": ">=18.0.0"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
+
"test": "node --test 'tests/**/*.test.mjs'",
|
|
22
23
|
"build:data": "node scripts/build-data.js",
|
|
23
24
|
"prepack": "node scripts/build-data.js",
|
|
24
25
|
"validate:codex-agents": "node lib/codex/validate-codex-agents.mjs"
|
|
@@ -41,5 +42,5 @@
|
|
|
41
42
|
"agent",
|
|
42
43
|
"plugin"
|
|
43
44
|
],
|
|
44
|
-
"etnaFingerprint": "
|
|
45
|
+
"etnaFingerprint": "efebb48c460d"
|
|
45
46
|
}
|