@nurix/etna 0.20.10 → 0.20.11

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 (35) hide show
  1. package/data/catalog/rules/cloudflare.md +8 -0
  2. package/data/catalog/rules/database.md +10 -0
  3. package/data/catalog/rules/frontend.md +10 -0
  4. package/data/catalog/rules/pnpm.md +9 -0
  5. package/data/catalog/rules/react-native.md +10 -0
  6. package/data/catalog/rules/rust-desktop.md +8 -0
  7. package/data/catalog/rules/temporal.md +9 -0
  8. package/data/harness-index.json +1 -1
  9. package/data/harnesses/code/CLAUDE.md +6 -4
  10. package/data/harnesses/code/agents/polygraph-applier.md +24 -0
  11. package/data/harnesses/code/agents/polygraph-finder.md +31 -0
  12. package/data/harnesses/code/agents/polygraph-verifier.md +20 -0
  13. package/data/harnesses/code/codex/agents/polygraph-applier.toml +21 -0
  14. package/data/harnesses/code/codex/agents/polygraph-finder.toml +28 -0
  15. package/data/harnesses/code/codex/agents/polygraph-verifier.toml +17 -0
  16. package/data/harnesses/code/harness.json +2 -2
  17. package/data/harnesses/code/hooks/awareness.mjs +16 -2
  18. package/data/harnesses/code/hooks/lib/plans.mjs +11 -1
  19. package/data/harnesses/code/rules/cloudflare.md +8 -0
  20. package/data/harnesses/code/rules/database.md +10 -0
  21. package/data/harnesses/code/rules/frontend.md +10 -0
  22. package/data/harnesses/code/rules/pnpm.md +9 -0
  23. package/data/harnesses/code/skills/documentation/references/instruction-style.md +1 -1
  24. package/data/harnesses/code/skills/documentation/templates/plan.md +2 -2
  25. package/data/harnesses/code/skills/polygraph/SKILL.md +71 -0
  26. package/data/harnesses/code/skills/polygraph/references/dispatch.md +80 -0
  27. package/data/harnesses/code/skills/polygraph/references/rows.md +57 -0
  28. package/data/harnesses/code/skills/polygraph/scripts/audit.mjs +113 -0
  29. package/data/harnesses/delivery/harness.json +2 -2
  30. package/data/harnesses/delivery/skills/documentation/references/instruction-style.md +1 -1
  31. package/data/harnesses/delivery/skills/documentation/templates/plan.md +2 -2
  32. package/data/harnesses/etna/harness.json +2 -2
  33. package/data/harnesses/etna/skills/documentation/references/instruction-style.md +1 -1
  34. package/data/harnesses/etna/skills/documentation/templates/plan.md +2 -2
  35. package/package.json +2 -2
@@ -1,3 +1,11 @@
1
+ ---
2
+ paths:
3
+ - "**/wrangler.*"
4
+ - "**/.dev.vars*"
5
+ - "**/worker/**"
6
+ - "**/workers/**"
7
+ - "**/src/**/*.ts"
8
+ ---
1
9
  # Cloudflare Worker
2
10
 
3
11
  Conventions for Cloudflare Workers — `wrangler.jsonc` setup, vars and secrets, and code shape (dispatch, errors, modules, WebSockets). Schema design doctrine is the `schema-design` skill.
@@ -1,3 +1,13 @@
1
+ ---
2
+ paths:
3
+ - "**/*.sql"
4
+ - "**/migrations/**"
5
+ - "**/drizzle/**"
6
+ - "**/drizzle.config.*"
7
+ - "**/schema.ts"
8
+ - "**/schema/**"
9
+ - "**/db/**"
10
+ ---
1
11
  # Database
2
12
 
3
13
  Naming and the fixed column vocabulary for relational schemas in **every** Nurix project — one concept, one name, everywhere. Written for SQLite / Cloudflare D1 + Drizzle; a Postgres project keeps the same column names and semantics with native types (`uuid`, `timestamptz`, `boolean`). **Design doctrine — what earns a table, state vs. logs, junctions, FK policy, uniqueness traps — is the `schema-design` skill; consult it before any schema change.**
@@ -1,3 +1,13 @@
1
+ ---
2
+ paths:
3
+ - "**/*.tsx"
4
+ - "**/*.jsx"
5
+ - "**/*.ts"
6
+ - "**/*.css"
7
+ - "**/*.html"
8
+ - "**/tailwind.config.*"
9
+ - "**/vite.config.*"
10
+ ---
1
11
  # Frontend
2
12
 
3
13
  Tailwind v4 + shadcn-based UI conventions for **web** apps — styling, the `@nurix/components` library, the `design.md` token artifact, and design-skill routing — plus the TypeScript conventions every package compiles under.
@@ -1,3 +1,12 @@
1
+ ---
2
+ paths:
3
+ - "**/package.json"
4
+ - "**/pnpm-workspace.yaml"
5
+ - "**/pnpm-lock.yaml"
6
+ - "**/.npmrc"
7
+ - "**/tsconfig*.json"
8
+ - "**/turbo.json"
9
+ ---
1
10
  # pnpm — `@nurix/*` Workspaces: naming, boundaries, and consuming
2
11
 
3
12
  How a `@nurix/*` repo is **named**, **structured**, and how its packages are **consumed** — every repo takes the workspace shape, single-package frontends included. Publishing and CLI design live in the `nustack` skill, not here — no `nustack` skill in the runtime ⇒ ask before crossing the publish boundary.
@@ -1,3 +1,13 @@
1
+ ---
2
+ paths:
3
+ - "**/app.json"
4
+ - "**/app.config.*"
5
+ - "**/eas.json"
6
+ - "**/metro.config.*"
7
+ - "**/babel.config.*"
8
+ - "**/app/**"
9
+ - "**/*.tsx"
10
+ ---
1
11
  # React Native + Expo
2
12
 
3
13
  How mobile apps are built — distilled from a hand-crafted reference app, the gold standard for *style*. The rule is **scale-aware**: a few-screen app takes the lean form, a feature/auth-heavy app the full form; where a section offers both, the graduation trigger is stated.
@@ -1,3 +1,11 @@
1
+ ---
2
+ paths:
3
+ - "**/*.rs"
4
+ - "**/Cargo.toml"
5
+ - "**/Cargo.lock"
6
+ - "**/tauri.conf.json"
7
+ - "**/src-tauri/**"
8
+ ---
1
9
  # Rust Desktop Apps (Tauri 2)
2
10
 
3
11
  How native desktop apps are built: a **Rust core + web-frontend** shell — the cargo/JS dual workspace, the Rust↔TS contract seam, native-capability handling, and signed distribution. This rule governs everything on the **Rust and OS side of the webview boundary**; the webview UI is a normal web app, unchanged by being hosted in a webview. Scale-aware: a single-window tool with no native capabilities skips §2's workspace and §4's actor (graduation trigger in §2); a capability-rich app (audio, permissions, background work, sibling processes) takes the full shape.
@@ -1,3 +1,12 @@
1
+ ---
2
+ paths:
3
+ - "**/workflows/**"
4
+ - "**/activities/**"
5
+ - "**/*.workflow.ts"
6
+ - "**/*.activity.ts"
7
+ - "**/worker.ts"
8
+ - "**/temporal/**"
9
+ ---
1
10
  # Temporal
2
11
 
3
12
  Workflow and activity conventions for Temporal-backed services — the determinism boundary, file layout, registration, retry defaults, and starting workflows.
@@ -1,5 +1,5 @@
1
1
  {
2
- "fingerprint": "0fa183cce9ee",
2
+ "fingerprint": "7d6e2acfe663",
3
3
  "harnesses": {
4
4
  "code": {
5
5
  "plugin": "code",
@@ -33,14 +33,15 @@ Three peer modes, then a verification gate and a Completion step — enter where
33
33
 
34
34
  > Brainstorm entry needs a **basis** — a reference document or a one-shot brain dump; drip-sized asks (a bug fix, one button) are too small and get redirected. A **pillar** is a recorded strategic principle from a past brainstorm: in-progress sessions live under `docs/research/`, harvested ones under `docs/history/brainstorms/`, harvested doctrine under `docs/architecture/`. The LLD is the feature doc itself — `docs/features/{feature}.md`, design intent first, extended in place at Completion.
35
35
 
36
- > **Two stops, and only two.** The work between them runs without asking. The **plan pass** ends at a finished plan — written, boundary-checked, amendments folded, both ledgers in place — shown once; a plan that still owes a gate is not finished and is not shown as finished. The **build pass** ends at a discharged gate — the code written, driven, fixed, pinned, every ledger row in its Outcome cell — and stops before the freeze. What the user decides is the gated plan (stop 1) and the freeze-and-commit (stop 2); running a gate is never a question put to the user.
36
+ > **Two stops, and only two.** The work between them runs without asking. The **plan pass** ends at a finished plan — written, boundary-checked, amendments folded, both ledgers in place — shown once; a plan that still owes a gate is not finished and is not shown as finished. The **build pass** ends at a discharged gate — the code written, driven, fixed, pinned, every ledger row in its Outcome cell, the plan's documents certified (`Polygraph:` written) — and stops before the freeze. What the user decides is the gated plan (stop 1) and the freeze-and-commit (stop 2); running a gate is never a question put to the user.
37
37
 
38
38
  > **The lightweight lane:** a change touching one module, **at most ~10 files, none on a contract-heavy surface** (db schema, wire envelopes, auth, events, env/config — the `boundary` skill's list), and under about **three days'** work skips the modes above entirely — the ad-hoc regime of `.claude/rules/testing.md` applies: make the change, drive it, then pin it in the same unit of work if it alters promised behavior, cover it with a changelog row. The predicate is mechanical; when in doubt, the `boundary` diff scan is the tiebreak, not self-assessment.
39
39
 
40
40
  **On Plan Completion:**
41
41
 
42
- 1. **The verification gate is the tail of the build pass, never a separate ask** **[override]** — the moment the unit's code is written the session opens it: drives the real surface, fixes what driving finds, pins once, discharges. What is the user's call is the freeze and the commit — "commit" / "ship it" triggers step 2, not the gate, which has already run. This keeps the runtime's finish-the-task default inside the build and removes it from the freeze. The gate requires the plan's Test Obligations ledger to exist (proof the plan gates ran) and discharges every row into its **Outcome** cell, driving the real surface before pinning tests — `.claude/rules/testing.md`. A plan with outstanding obligations stays open, whatever the code does.
43
- 2. **Discharge, then freeze — in one edit.** Discharge the Doc Obligations ledger (every row into exactly one outcome: **updated** — the doc now states what the code does; **owes nothing**, with the reason; **blocked** — → a backlog item, so the debt outlives the frozen plan), fill the Completed date, and flip `Status: Complete` **in the same edit**: the flip freezes the file instantly, so anything left for a second edit is unreachable. For each discharged row's doc: flip `status: active` if it was `draft`, extend it in place, and confirm its `owns:` globs against the actual code — the ledger, not memory, says how many docs that is.
42
+ 1. **The verification gate is the tail of the build pass, never a separate ask** **[override]** — the moment the unit's code is written the session opens it: drives the real surface, fixes what driving finds, pins once, discharges. What is the user's call is the freeze and the commit — "commit" / "ship it" triggers step 3, not the gates, which have already run. This keeps the runtime's finish-the-task default inside the build and removes it from the freeze. The gate requires the plan's Test Obligations ledger to exist (proof the plan gates ran) and discharges every row into its **Outcome** cell, driving the real surface before pinning tests — `.claude/rules/testing.md`. A plan with outstanding obligations stays open, whatever the code does.
43
+ 2. **Certify the documents — the polygraph gate, still inside the build pass.** Once every Doc Obligations row has its outcome, run the `polygraph` skill in gate mode over the docs the plan touched (every row discharged **updated**, plus every `Spec:` doc): claim by claim against the code, corrections applied, and the plan's `Gates:` line rewritten from `Polygraph: pending` to `Polygraph: passed YYYY-MM-DD (…)`. A plan that touched no document writes `Polygraph: owes nothing — {reason}` by hand instead. A plan frozen while the segment still reads `pending` raises a `needs-session:` queue row and fails CI — the gate is a judgment obligation, enforced the same way as the doc ledger, never a wall.
44
+ 3. **Discharge, then freeze — in one edit.** Discharge the Doc Obligations ledger (every row into exactly one outcome: **updated** — the doc now states what the code does; **owes nothing**, with the reason; **blocked** — → a backlog item, so the debt outlives the frozen plan), fill the Completed date, and flip `Status: Complete` **in the same edit**: the flip freezes the file instantly, so anything left for a second edit is unreachable. For each discharged row's doc: flip `status: active` if it was `draft`, extend it in place, and confirm its `owns:` globs against the actual code — the ledger, not memory, says how many docs that is.
44
45
  3. Record the gate's results in the changelog entry, and confirm every section of `docs/tests/{feature}.md` carries its `Pinned:` trace — the checklist itself is authored at the gate (step 1), not written up afterwards.
45
46
 
46
47
  **On Brainstorm Close** (when a session ends, not when a plan completes): close + harvest is the skill's `stop` procedure — converge, a user-approved harvest manifest, apply, then a uselessness test; conclusions disperse by scope (architecture docs, decision records, backlog), the record moves from `docs/research/{topic}-brainstorm/` to `docs/history/brainstorms/{topic}/`, and the user decides whether the moved folder is deleted or committed.
@@ -78,6 +79,7 @@ Boundaries are **diagnosed, not documented** — the `boundary` skill derives th
78
79
 
79
80
  ### Documentation — close the loop
80
81
 
82
+ - **When a document's truth is in question — "is this doc still right?", a feature that drifted from its doc, a folder nobody has audited — invoke the `polygraph` skill (audit mode) on that target**: one doc, feature, folder, or code path, never the whole tree by reflex. Docs health (dead globs, lint, placement) is the `maintenance` agent; blast radius is `boundary`.
81
83
  - **Invoke the `documentation` skill when creating a doc, or when an edit changes a doc's structure or claims** — apply its templates and `references/style-guide.md`: truth documents stay present-tense; history lives only in `docs/history/` records. Records written from a template already in context (a changelog entry, a backlog row) skip the invocation.
82
84
  - **[override]** Keep `docs/` updated when code changes affect documented behavior. This file is the standing request for those docs: creating and updating them is part of the work, even if the runtime defaults to never creating documentation files unprompted.
83
85
  - After plan completion **[override]**: write the `docs/history/changelog/` entry as a **draft** immediately, while the context is fresh — no confirmation needed — and append its `docs/history/CHANGELOG.md` row; the commit fills what the draft left open and freezes it. Non-plan work needs no separate prompt: every non-exempt commit carries its own entry (the `changelog` rule).
@@ -130,7 +132,7 @@ The single policy for sub-agent dispatch — when to fan out, at what tier, and
130
132
  - **A dispatch is scoped to minutes and read before anything else starts.** One file, one sweep — never a build. The dispatching agent reads the result before moving on, and never leaves a sub-agent as the only thing running while it does something else: a sub-agent's state is invisible until someone looks, so an hour-long dispatch that dies is discovered an hour late, with nothing to show for the hour.
131
133
  - **Small or judgment work stays inline.** A task finishable in a handful of tool calls is faster done than briefed. **Never delegate judgment by reflex** — a second opinion is for a high-stakes call or an explicit request.
132
134
  - **A delegated result is a report, not verifiable ground truth** — verify load-bearing results directly.
133
- - **Tiers**: mechanical fan-out → the cheapest capable tier; judgment-heavy delegation → the session's standard tier; the strongest tier **only** on explicit user request. The session itself defaults to the standard tier, entering the strongest only by explicit user switch. Model names live in Runtime Notes.
135
+ - **Tiers**: mechanical fan-out → the cheapest capable tier; judgment-heavy delegation → the session's standard tier; the strongest tier **only** on explicit user request. The session itself defaults to the standard tier, entering the strongest only by explicit user switch. Model names live in Runtime Notes. **A rostered agent whose definition pins `model:` and `effort:` is dispatched at that pin** — the pin is the tier decision, made once at authoring; a skill that dispatches such an agent never re-picks per dispatch, and a session running hot cannot drag it up.
134
136
  - **An explicit user directive about tier, cost, or delegation overrides this section.**
135
137
 
136
138
  ---
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: polygraph-applier
3
+ description: Applies one packet of verified polygraph rows to exactly the documents the packet names, re-checking every citation first. Dispatched by the polygraph skill only; never on its own.
4
+ tools: Read, Edit, Write, Grep, Glob, Bash
5
+ model: sonnet
6
+ effort: medium
7
+ maxTurns: 15
8
+ ---
9
+
10
+ # Polygraph applier
11
+
12
+ You are an APPLIER in a truth audit. You receive the path of a **packet** and the path of its **context pack**. The packet names your editable docs (`doc:` lines) and your rows, with any bounce notes — read them; a bounced row's note says exactly what is wrong and where.
13
+
14
+ 1. Read the pack, then the packet. Each row is quoted in full inside the packet; never open the run's findings file to find your rows.
15
+ 2. **Re-check each row's citations against today's tree before editing.** This re-check is the audit's real gate: a claim that does not reproduce resolves `refuted-on-apply`, never as an edit that bends the doc to a stale claim.
16
+ 3. Edit **only** the docs your packet names. A fix that belongs in another doc is `needs-cross-doc`, naming that file precisely — your note is how the row is re-routed. Frozen records (completed plans, changelog entries, archived docs) are `exempt-frozen`. A subject awaiting an owner ruling is `blocked-on-question`, no edit.
17
+ 4. Style: present tense; no history narration; the repo's unbuilt-marker convention is the only sanctioned qualifier; preserve frontmatter and tooling metadata. Run the repo's doc lint over every doc touched, with repo-root-relative paths, and fix blocking findings.
18
+ 5. Write `apply-{doc}.psv` beside the packet — `id|outcome|note`, outcome ∈ `applied` · `needs-cross-doc` · `refuted-on-apply` · `exempt-frozen` · `blocked-on-question` · `skipped`.
19
+
20
+ **The audit never edits code.** A row whose fix is a source change — a comment, a rename, a bug — is `skipped` with a note for the register; markdown anywhere, package and root READMEs included, is yours. Rulings in the pack outrank every recorded decision.
21
+
22
+ Tree state: re-check every citation against today's tree before acting. READ-ONLY outside the packet's docs and your outcome file. Read budget: the pack and the packet are your reading list — grep before you read; never read a file over 20 KB without a line range; read a file once; one shell block per question.
23
+
24
+ Return **at most 120 words**: counts per outcome, docs touched, the lint result, and anything for the register.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: polygraph-finder
3
+ description: Sweeps one document against the code it describes for the polygraph skill — one row per claim, cited, into a partial file. Dispatched by the skill only; never on its own.
4
+ tools: Read, Grep, Glob, Bash, Write
5
+ model: sonnet
6
+ effort: medium
7
+ maxTurns: 25
8
+ ---
9
+
10
+ # Polygraph finder
11
+
12
+ You are a FINDER in a truth audit. You receive the path of a **context pack** and the path of your **partial file**; the pack is your whole reading list, and the partial is your one write.
13
+
14
+ Docs state the target design; code is presumed correct unless a standing decision **names** it for retirement — the pack's precedent section decides, and a match means the precedent names your subject (quote the naming words into evidence; a resembling thing is not a match; unsure → QUESTION).
15
+
16
+ 1. Read the pack in full. It names the document, its `owns:` globs resolved to today's files, the precedent quotations, the register rows already tracking this subject, and the rulings that bind.
17
+ 2. Read the document once, whole — it is the one file you always read in full. Then check each claim it makes against the code: grep for the mechanism, open the passage with a line range, cite `file:line`.
18
+ 3. Write one row per claim to the partial file, **no header**, exactly ten pipe-separated fields: `id|subject|claim|evidence|counter_evidence|verdict|confidence|depends_on|status|resolution`. A literal `|` becomes `¦`, a newline `; `. Verdicts: `COHERENT` (emit at the unit you audited, so coverage is countable) · `MISDESCRIBED` · `UNDOCUMENTED` · `UNBUILT` · `RESIDUE` · `CONTESTED` · `QUESTION`. `id` is `{doc-stem}-NNN`; `status` is `open`; `resolution` is empty; `confidence` is an honest 0–1.
19
+ 4. Sweep the other direction once: code inside the `owns:` globs that no claim in the document covers is an `UNDOCUMENTED` row — after the precedent test.
20
+
21
+ Rules that bind every row:
22
+
23
+ - **No citation, no row** — `file:line`, or the exact command and its result. Prove or refute; never confirm by plausibility.
24
+ - **A negative claim about a long document quotes the passage you read** — the sentence you believe wrong, or the section where the missing statement should live. Unsupported negative claims are the dominant false-positive class.
25
+ - **An already-tracked finding is skipped** unless your evidence adds something new; cite the register row as `already-registered: path:line` in `counter_evidence`.
26
+ - **A document that should not exist** — its subject gone, its authority absorbed by a sibling — is one `QUESTION` row proposing deletion or merge, with the inbound-reference count.
27
+ - **Rulings in the pack outrank every recorded decision.**
28
+
29
+ Tree state: re-check every citation against today's tree before writing it. **READ-ONLY everywhere except your partial file.** Read budget: the pack is your reading list — read nothing outside it without naming in your return why the pack was insufficient. Grep before you read; never read a file over 20 KB without a line range; read a file once. Compose one shell block per question, not one call per file — your turn cap is your tool budget.
30
+
31
+ Return **at most 120 words**: the partial's path, the row count, up to three findings, and limitations. Findings go in the file, never in the return.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: polygraph-verifier
3
+ description: Blind re-derivation of polygraph claims the primary doubts — claim and subject only, no prior evidence — into a verify file. Dispatched by the polygraph skill only; never on its own.
4
+ tools: Read, Grep, Glob, Bash, Write
5
+ model: sonnet
6
+ effort: low
7
+ maxTurns: 12
8
+ ---
9
+
10
+ # Polygraph verifier
11
+
12
+ You are a VERIFIER in a truth audit. You receive a **queue file** of items — `id`, `subject`, `claim` — deliberately with no prior evidence, reasoning, or verdict, and the path of your **output file**.
13
+
14
+ 1. Read the queue. For each item, re-derive the claim from the tree: find the mechanism, read the passage with a line range, decide.
15
+ 2. Judge each: `CONFIRMED` (true as stated) · `REFUTED` (false) · `ADJUSTED` (true in substance, wrong in a particular — say which). Cite exactly, `file:line`.
16
+ 3. Write the output file, one line per item: `id|check|confidence|citations|note`. A literal `|` becomes `¦`.
17
+
18
+ **A wrong confirmation is worse than an honest REFUTED.** Never infer from the claim's wording what the finder saw; derive from the tree. Read budget: grep before you read; never read a file over 20 KB without a line range; read a file once; one shell block per question. READ-ONLY everywhere except your output file.
19
+
20
+ Return **at most 120 words**: the file path and counts per check.
@@ -0,0 +1,21 @@
1
+ # dropped Claude tools: Read, Edit, Write, Grep, Glob, Bash
2
+ name = "polygraph-applier"
3
+ description = "Applies one packet of verified polygraph rows to exactly the documents the packet names, re-checking every citation first. Dispatched by the polygraph skill only; never on its own."
4
+ developer_instructions = '''
5
+
6
+ # Polygraph applier
7
+
8
+ You are an APPLIER in a truth audit. You receive the path of a **packet** and the path of its **context pack**. The packet names your editable docs (`doc:` lines) and your rows, with any bounce notes — read them; a bounced row's note says exactly what is wrong and where.
9
+
10
+ 1. Read the pack, then the packet. Each row is quoted in full inside the packet; never open the run's findings file to find your rows.
11
+ 2. **Re-check each row's citations against today's tree before editing.** This re-check is the audit's real gate: a claim that does not reproduce resolves `refuted-on-apply`, never as an edit that bends the doc to a stale claim.
12
+ 3. Edit **only** the docs your packet names. A fix that belongs in another doc is `needs-cross-doc`, naming that file precisely — your note is how the row is re-routed. Frozen records (completed plans, changelog entries, archived docs) are `exempt-frozen`. A subject awaiting an owner ruling is `blocked-on-question`, no edit.
13
+ 4. Style: present tense; no history narration; the repo's unbuilt-marker convention is the only sanctioned qualifier; preserve frontmatter and tooling metadata. Run the repo's doc lint over every doc touched, with repo-root-relative paths, and fix blocking findings.
14
+ 5. Write `apply-{doc}.psv` beside the packet — `id|outcome|note`, outcome ∈ `applied` · `needs-cross-doc` · `refuted-on-apply` · `exempt-frozen` · `blocked-on-question` · `skipped`.
15
+
16
+ **The audit never edits code.** A row whose fix is a source change — a comment, a rename, a bug — is `skipped` with a note for the register; markdown anywhere, package and root READMEs included, is yours. Rulings in the pack outrank every recorded decision.
17
+
18
+ Tree state: re-check every citation against today's tree before acting. READ-ONLY outside the packet's docs and your outcome file. Read budget: the pack and the packet are your reading list — grep before you read; never read a file over 20 KB without a line range; read a file once; one shell block per question.
19
+
20
+ Return **at most 120 words**: counts per outcome, docs touched, the lint result, and anything for the register.
21
+ '''
@@ -0,0 +1,28 @@
1
+ # dropped Claude tools: Read, Grep, Glob, Bash, Write
2
+ name = "polygraph-finder"
3
+ description = "Sweeps one document against the code it describes for the polygraph skill — one row per claim, cited, into a partial file. Dispatched by the skill only; never on its own."
4
+ developer_instructions = '''
5
+
6
+ # Polygraph finder
7
+
8
+ You are a FINDER in a truth audit. You receive the path of a **context pack** and the path of your **partial file**; the pack is your whole reading list, and the partial is your one write.
9
+
10
+ Docs state the target design; code is presumed correct unless a standing decision **names** it for retirement — the pack's precedent section decides, and a match means the precedent names your subject (quote the naming words into evidence; a resembling thing is not a match; unsure → QUESTION).
11
+
12
+ 1. Read the pack in full. It names the document, its `owns:` globs resolved to today's files, the precedent quotations, the register rows already tracking this subject, and the rulings that bind.
13
+ 2. Read the document once, whole — it is the one file you always read in full. Then check each claim it makes against the code: grep for the mechanism, open the passage with a line range, cite `file:line`.
14
+ 3. Write one row per claim to the partial file, **no header**, exactly ten pipe-separated fields: `id|subject|claim|evidence|counter_evidence|verdict|confidence|depends_on|status|resolution`. A literal `|` becomes `¦`, a newline `; `. Verdicts: `COHERENT` (emit at the unit you audited, so coverage is countable) · `MISDESCRIBED` · `UNDOCUMENTED` · `UNBUILT` · `RESIDUE` · `CONTESTED` · `QUESTION`. `id` is `{doc-stem}-NNN`; `status` is `open`; `resolution` is empty; `confidence` is an honest 0–1.
15
+ 4. Sweep the other direction once: code inside the `owns:` globs that no claim in the document covers is an `UNDOCUMENTED` row — after the precedent test.
16
+
17
+ Rules that bind every row:
18
+
19
+ - **No citation, no row** — `file:line`, or the exact command and its result. Prove or refute; never confirm by plausibility.
20
+ - **A negative claim about a long document quotes the passage you read** — the sentence you believe wrong, or the section where the missing statement should live. Unsupported negative claims are the dominant false-positive class.
21
+ - **An already-tracked finding is skipped** unless your evidence adds something new; cite the register row as `already-registered: path:line` in `counter_evidence`.
22
+ - **A document that should not exist** — its subject gone, its authority absorbed by a sibling — is one `QUESTION` row proposing deletion or merge, with the inbound-reference count.
23
+ - **Rulings in the pack outrank every recorded decision.**
24
+
25
+ Tree state: re-check every citation against today's tree before writing it. **READ-ONLY everywhere except your partial file.** Read budget: the pack is your reading list — read nothing outside it without naming in your return why the pack was insufficient. Grep before you read; never read a file over 20 KB without a line range; read a file once. Compose one shell block per question, not one call per file — your turn cap is your tool budget.
26
+
27
+ Return **at most 120 words**: the partial's path, the row count, up to three findings, and limitations. Findings go in the file, never in the return.
28
+ '''
@@ -0,0 +1,17 @@
1
+ # dropped Claude tools: Read, Grep, Glob, Bash, Write
2
+ name = "polygraph-verifier"
3
+ description = "Blind re-derivation of polygraph claims the primary doubts — claim and subject only, no prior evidence — into a verify file. Dispatched by the polygraph skill only; never on its own."
4
+ developer_instructions = '''
5
+
6
+ # Polygraph verifier
7
+
8
+ You are a VERIFIER in a truth audit. You receive a **queue file** of items — `id`, `subject`, `claim` — deliberately with no prior evidence, reasoning, or verdict, and the path of your **output file**.
9
+
10
+ 1. Read the queue. For each item, re-derive the claim from the tree: find the mechanism, read the passage with a line range, decide.
11
+ 2. Judge each: `CONFIRMED` (true as stated) · `REFUTED` (false) · `ADJUSTED` (true in substance, wrong in a particular — say which). Cite exactly, `file:line`.
12
+ 3. Write the output file, one line per item: `id|check|confidence|citations|note`. A literal `|` becomes `¦`.
13
+
14
+ **A wrong confirmation is worse than an honest REFUTED.** Never infer from the claim's wording what the finder saw; derive from the tree. Read budget: grep before you read; never read a file over 20 KB without a line range; read a file once; one shell block per question. READ-ONLY everywhere except your output file.
15
+
16
+ Return **at most 120 words**: the file path and counts per check.
17
+ '''
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "3.14.8",
4
+ "version": "3.15.1",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -25,5 +25,5 @@
25
25
  }
26
26
  ],
27
27
  "docsRoot": "docs",
28
- "digest": "5de54f6b1a71"
28
+ "digest": "b00642c1a47d"
29
29
  }
@@ -36,7 +36,7 @@ import {
36
36
  } from "./lib/state.mjs";
37
37
  import { readSafe, relIfInside, today, SOURCE_EXT, commandRoot, docsRoot } from "./lib/repo.mjs";
38
38
  import { sweepResolvedRows, docLint, hasHeaderComment } from "./lib/sweep.mjs";
39
- import { listPlans } from "./lib/plans.mjs";
39
+ import { listPlans, polygraphVerdict } from "./lib/plans.mjs";
40
40
  import { frozenStatusAtHead } from "./lib/frozen.mjs";
41
41
  import { undischargedDocRows } from "./lib/ledger.mjs";
42
42
  import { approvedPackages, rejectedPackages } from "./lib/approved.mjs";
@@ -312,7 +312,8 @@ function detectorForFile(projectDir, rel, meta, rows) {
312
312
  if (rel.startsWith(`${root}/history/plans/`)) {
313
313
  const planText = readSafe(path.join(projectDir, rel));
314
314
  resolveRows(projectDir, (row) => row.kind === "needs-session" && row.body.includes(rel));
315
- if (/^\*\*Status:\*\*\s*Complete\b/m.test(planText) && undischargedDocRows(planText).length > 0) {
315
+ const complete = /^\*\*Status:\*\*\s*Complete\b/m.test(planText);
316
+ if (complete && undischargedDocRows(planText).length > 0) {
316
317
  rows.push(
317
318
  formatNeedsSessionRow({
318
319
  date: meta.date,
@@ -321,6 +322,19 @@ function detectorForFile(projectDir, rel, meta, rows) {
321
322
  })
322
323
  );
323
324
  }
325
+ // Whether the plan's documents are TRUE is the polygraph skill's judgment, not a hook's — so
326
+ // the gate's trace (the `Polygraph:` segment of the Gates line) is raised the same way the doc
327
+ // ledger is: a `needs-session:` row while it reads `pending` on a Complete plan. A plan with no
328
+ // segment predates the gate or runs under a harness that ships no polygraph — out of contract.
329
+ if (complete && polygraphVerdict(planText) === "pending") {
330
+ rows.push(
331
+ formatNeedsSessionRow({
332
+ date: meta.date,
333
+ description: `${rel} reached Status: Complete with its documents uncertified — run the polygraph skill (gate mode) and write its Polygraph: segment`,
334
+ refs: rel,
335
+ })
336
+ );
337
+ }
324
338
  }
325
339
  if (rel.startsWith(`${root}/`)) {
326
340
  const findings = docLint(projectDir, rel);
@@ -28,7 +28,16 @@ export function inPlanScope(rel, scope) {
28
28
  });
29
29
  }
30
30
 
31
- // Every plan in the folder, parsed once: { file, status, gates, scope, hasLedger,
31
+ // The plan's Polygraph gate, read from its Gates line: 'pending' · 'passed' · 'owes nothing' ·
32
+ // null when the line carries no segment (a plan that predates the gate, or a repo whose harness
33
+ // ships no polygraph — both out of contract, both pass).
34
+ export function polygraphVerdict(text) {
35
+ const gates = (/^\*\*Gates:\*\*\s*(.+)$/m.exec(text || "") || [])[1] || "";
36
+ const m = /Polygraph:\s*(pending|passed|owes nothing)\b/i.exec(gates);
37
+ return m ? m[1].toLowerCase() : null;
38
+ }
39
+
40
+ // Every plan in the folder, parsed once: { file, status, gates, polygraph, scope, hasLedger,
32
41
  // hasDocLedger, ledgerRows }. `gates` is null on legacy plans (pre-Gates format).
33
42
  export function listPlans(projectDir) {
34
43
  const dir = path.join(projectDir, docsRoot(projectDir), "history", "plans");
@@ -47,6 +56,7 @@ export function listPlans(projectDir) {
47
56
  file,
48
57
  status,
49
58
  gates: (/^\*\*Gates:\*\*\s*(.+)$/m.exec(text) || [])[1]?.trim() || null,
59
+ polygraph: polygraphVerdict(text),
50
60
  scope: planScopePaths(text),
51
61
  hasLedger: /^##\s+Test Obligations\b/m.test(text),
52
62
  hasDocLedger: /^##\s+Doc Obligations\b/m.test(text),
@@ -1,3 +1,11 @@
1
+ ---
2
+ paths:
3
+ - "**/wrangler.*"
4
+ - "**/.dev.vars*"
5
+ - "**/worker/**"
6
+ - "**/workers/**"
7
+ - "**/src/**/*.ts"
8
+ ---
1
9
  # Cloudflare Worker
2
10
 
3
11
  Conventions for Cloudflare Workers — `wrangler.jsonc` setup, vars and secrets, and code shape (dispatch, errors, modules, WebSockets). Schema design doctrine is the `schema-design` skill.
@@ -1,3 +1,13 @@
1
+ ---
2
+ paths:
3
+ - "**/*.sql"
4
+ - "**/migrations/**"
5
+ - "**/drizzle/**"
6
+ - "**/drizzle.config.*"
7
+ - "**/schema.ts"
8
+ - "**/schema/**"
9
+ - "**/db/**"
10
+ ---
1
11
  # Database
2
12
 
3
13
  Naming and the fixed column vocabulary for relational schemas in **every** Nurix project — one concept, one name, everywhere. Written for SQLite / Cloudflare D1 + Drizzle; a Postgres project keeps the same column names and semantics with native types (`uuid`, `timestamptz`, `boolean`). **Design doctrine — what earns a table, state vs. logs, junctions, FK policy, uniqueness traps — is the `schema-design` skill; consult it before any schema change.**
@@ -1,3 +1,13 @@
1
+ ---
2
+ paths:
3
+ - "**/*.tsx"
4
+ - "**/*.jsx"
5
+ - "**/*.ts"
6
+ - "**/*.css"
7
+ - "**/*.html"
8
+ - "**/tailwind.config.*"
9
+ - "**/vite.config.*"
10
+ ---
1
11
  # Frontend
2
12
 
3
13
  Tailwind v4 + shadcn-based UI conventions for **web** apps — styling, the `@nurix/components` library, the `design.md` token artifact, and design-skill routing — plus the TypeScript conventions every package compiles under.
@@ -1,3 +1,12 @@
1
+ ---
2
+ paths:
3
+ - "**/package.json"
4
+ - "**/pnpm-workspace.yaml"
5
+ - "**/pnpm-lock.yaml"
6
+ - "**/.npmrc"
7
+ - "**/tsconfig*.json"
8
+ - "**/turbo.json"
9
+ ---
1
10
  # pnpm — `@nurix/*` Workspaces: naming, boundaries, and consuming
2
11
 
3
12
  How a `@nurix/*` repo is **named**, **structured**, and how its packages are **consumed** — every repo takes the workspace shape, single-package frontends included. Publishing and CLI design live in the `nustack` skill, not here — no `nustack` skill in the runtime ⇒ ask before crossing the publish boundary.
@@ -4,7 +4,7 @@ The shared style for every file an agent obeys — a `CLAUDE.md`, a rule, an age
4
4
 
5
5
  ## The test for every line
6
6
 
7
- An instruction file is paid for whenever it loads, scaled by its load class: `CLAUDE.md` and rules load on **every turn of every session** — the most expensive prose in a repo per word; an agent definition loads per dispatch, a skill per invocation. The bar is not "is this true?" but **"must this be in front of the agent whenever this file loads?"** Measure weight in tokens, not lines; trim biggest-first on every edit.
7
+ An instruction file is paid for whenever it loads, scaled by its load class: `CLAUDE.md` and unscoped rules load on **every turn of every session** — the most expensive prose in a repo per word; a `paths:`-scoped rule loads on those same terms from its first matching file read on; an agent definition loads per dispatch, a skill per invocation. The bar is not "is this true?" but **"must this be in front of the agent whenever this file loads?"** Measure weight in tokens, not lines; trim biggest-first on every edit.
8
8
 
9
9
  **Rewriting, the test is armor vs. narration.** A reason clause either blocks a move an agent would otherwise make — the failure is counter-intuitive, the wrong fix looks correct, or the rule reads as arbitrary tidiness and gets "improved away" — or it explains the self-evident. Ask of each: *does the forbidden move look correct to an agent mid-task?* Keep the first kind, cut the second. **A constraint may lose its explanation; it may never lose its existence** — compression is fewer words for the same constraints, so a passage that cannot shrink without dropping one stays long.
10
10
 
@@ -8,13 +8,13 @@ The `boundary` skill parses the **Proposed Changes** sections to check boundary
8
8
 
9
9
  **Doc Obligations is the one ledger the plan's author writes.** Deriving tests from behavior is judgment, which is why `testing` owns that section and it is marked never-hand-author; resolving Proposed Changes' file paths to the feature docs that own them is a **lookup** against the ownership index already injected at session start. Different work, different rule — fill it yourself, while the touch list is fresh.
10
10
 
11
- The **`Gates:`** line carries one machine-read segment. The compliance hook greps **`Boundary:`** — implementation edits inside the plan's scope are blocked while it reads `Boundary: pending`; the `boundary` skill rewrites it to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`. The other two gates are enforced by **section presence**: a plan with no `## Test Obligations` or no `## Doc Obligations` section blocks the same edits — the sections are the artifacts, and the gate derives from them, not from a claim about them. Never hand-edit the line to skip a gate.
11
+ The **`Gates:`** line carries two machine-read segments. The compliance hook greps **`Boundary:`** — implementation edits inside the plan's scope are blocked while it reads `Boundary: pending`; the `boundary` skill rewrites it to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`. The other two plan gates are enforced by **section presence**: a plan with no `## Test Obligations` or no `## Doc Obligations` section blocks the same edits — the sections are the artifacts, and the gate derives from them, not from a claim about them. **`Polygraph:`** is the completion gate's trace: it reads `Polygraph: pending` from the first draft until the `polygraph` skill (gate mode) certifies the plan's documents — every Doc Obligations row discharged **updated** plus every `Spec:` doc — and rewrites it to `Polygraph: passed YYYY-MM-DD (N docs · N claims · N corrected · N registered)`. A plan that touched no document writes `Polygraph: owes nothing — {reason}` by hand; nothing else on this segment is ever hand-written. A Complete plan still reading `pending` raises a `needs-session:` queue row and fails CI. Never hand-edit the line to skip a gate.
12
12
 
13
13
  ```
14
14
  # {Feature Name}
15
15
 
16
16
  **Status:** Draft | In Progress | Complete | Abandoned
17
- **Gates:** Boundary: pending
17
+ **Gates:** Boundary: pending · Polygraph: pending
18
18
  **Spec:** [docs/features/{feature}.md](../../features/{feature}.md) <!-- one line per feature doc this plan implements against; a plan spanning three features names three -->
19
19
  **Started:** YYYY-MM-DD
20
20
  **Completed:** YYYY-MM-DD <!-- fill in the SAME edit that flips Status to Complete — along with every ledger Outcome cell: the flip freezes the file instantly, so anything left for a second edit is unreachable (the frozen-record gate rightly blocks it) -->
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: polygraph
3
+ description: "Use when a document's truth is in question — audit a named doc, feature, folder, or code path against the code — and at every plan completion, where gate mode certifies the plan's docs before the freeze. Never the whole tree by reflex; not docs health, boundary, or the verification gate."
4
+ ---
5
+
6
+ # Polygraph — two witnesses, one verdict
7
+
8
+ Code and docs are two witnesses to the same product, and each lies in its own direction: documents describe designs the code never built, code runs behaviour no document learned. The audit checks a document's claims against the code, presumes the code correct unless a standing decision names it for retirement, corrects the document, registers the code-side findings, and puts to the owner what evidence cannot settle. It runs at two moments and nowhere else:
9
+
10
+ | Moment | Mode | Ends with |
11
+ | --- | --- | --- |
12
+ | Any time, on a named target — a doc, a feature, a folder of docs, or a code path resolved to its owning docs | **Audit** | the target corrected, code-side rows registered, verdict counts reported |
13
+ | Plan completion — after the verification gate has discharged and the Doc Obligations rows read **updated**, before the freeze | **Gate** | the same, over the plan's docs, plus the plan's `Polygraph:` segment |
14
+
15
+ You are the **primary**: you hold the conversation, compose every context pack and packet, judge every contested verdict, and write the segment. Agents sweep and apply; a verdict is never delegated, and neither is a word said to the owner.
16
+
17
+ References — read the routed file in full at the named moment:
18
+
19
+ | Reference | Load when |
20
+ | --- | --- |
21
+ | `references/rows.md` | writing or reading any row; judging CONTESTED, QUESTION, residue, or a document's right to exist |
22
+ | `references/dispatch.md` | composing a context pack, dispatching any role, packeting, recovering a dead or capped agent, pricing a run |
23
+
24
+ ## Standing rules
25
+
26
+ 1. **Code is presumed correct — unless a standing decision names it.** The precedent pack decides by **naming**: a decision condemns code only when it names the file, table, route, command, or surface by the product's own name for it, and the row quotes the naming words. A resembling thing is not a match; arguable → QUESTION. Documenting undocumented code is the mechanically easiest resolution and quietly canonizes a retiring lane; condemning on a resemblance retires code the product still ships. The full test is `references/rows.md` § The residue guard.
27
+ 2. **No citation, no row** — `file:line`, or the exact command and its result. Prove or refute, never confirm by plausibility.
28
+ **A documented behaviour the code lacks is UNBUILT, never MISDESCRIBED.** The document may be stating the target design; rewriting it to match the code silently abandons that design. Without the repo's unbuilt marker on the passage or a precedent naming the subject, the row is a QUESTION for the owner, and the passage stands until the answer.
29
+ 3. **Finders never edit; appliers never judge; the audit never edits code.** A code-side finding leaves as a register row for the planning workflow, however small and however directly it is asked for; a source fix asked for *inside* the audit is refused, registered, and answered with the one thing that would let it happen — a separate ask outside the audit. **Markdown is not source**: every document read may be edited under the applier discipline; frozen records are exempt.
30
+ 4. **The applier re-check is the gate.** Every row is re-derived against today's tree before its doc is touched; a claim that fails to reproduce dies as *did not reproduce* and never becomes an edit. This is what lets a row be applied by an agent that never saw the finder's reasoning.
31
+ 5. **Rows live on disk, never in your context.** Partials, packets, and outcomes land under `.claude/state/polygraph/{run}/`; you re-read files. The folder is machine-local and you delete it when the run closes — in the same turn as the report, **whatever is still open**: the reply carries every open row in full, an owner's answer starts a new run over those rows only, and the changelog entry of the commit is the record. Nothing under `.claude/state/polygraph/` outlives the reply.
32
+ 6. **The owner's answer is applied in the same pass** — verbatim in the doc it settles, quoted in the changelog entry's Ask line. There is no question ledger to park it in.
33
+
34
+ ## The run
35
+
36
+ Both modes run the same sequence; the modes differ only in how the target list and the closing write are derived.
37
+
38
+ 1. **Resolve the targets.** A doc path is a target; a feature name resolves to `docs/features/{feature}.md`; a folder yields every doc in it; a code path resolves through the injected ownership index to the docs whose `owns:` globs match — or, when no index is in context, by scanning the `owns:` frontmatter under the docs root. A code path with no owner is **reported, never refused** — say which paths are unowned and go on with the rest. A target with no document at all is the one refusal: there is nothing to audit.
39
+ 2. **Price it and say so** before dispatching when the target list exceeds ten documents: per-doc cost at the pinned tier, the wave count under the runtime's concurrency ceiling, and the total. An owner directive to run on a stronger tier is priced the same way — state the multiplier and record the directive in the changelog entry.
40
+ 3. **Write one context pack per doc** (`references/dispatch.md` § The context pack) — the doc's `owns:` globs, the code paths in question, the precedent quotations that name this subject, the register rows already tracking it, and the owner rulings that bind. The pack is the agent's whole reading list.
41
+ 4. **Dispatch one `polygraph-finder` per doc**, in waves under the concurrency ceiling. Each writes its partial to the run folder and returns at most 120 words. A finder that returns **partial** at its turn cap is resumed once from its partial on disk; a second partial is accepted as that doc's sweep and reported as such.
42
+ 5. **Merge and check.** Concatenate the partials into `findings.psv`, quarantine malformed lines into `malformed.txt` (kept, never silently repaired), run `scripts/audit.mjs {run}` and fix what it reports by hand.
43
+ 6. **Judge.** Read every non-COHERENT row's evidence yourself. CONTESTED rows you judge with both sets of evidence in hand; a row you doubt goes to a `polygraph-verifier` (claim and subject only) and comes back CONTESTED if the two disagree. QUESTION rows — and every proposed deletion, with its inbound count — go to the owner **now, inline**, batched and ranked by how many rows each answer unblocks; work proceeds on everything else while they are open.
44
+ 7. **Packet and apply.** One doc, one `polygraph-applier`, always — `audit.mjs` asserts no collision. MISDESCRIBED rows and in-scope UNDOCUMENTED rows that passed the residue guard go to the owning doc's packet; RESIDUE, UNBUILT-abandoned, and every code-side row go to the register (the backlog, in its existing grammar, deduped against what is already there); frozen records resolve `exempt-frozen`. Fold each applier's outcome file into row status by hand, then run `audit.mjs` again.
45
+ 8. **Run the repo's doc gates in your own seat** — lint, links, ownership — over every doc touched; a delegated gate that dies reports nothing, which reads exactly like a pass.
46
+ 9. **Report** in prose: docs audited, claims checked, corrected, registered, contested-and-judged, questions answered; the refuted-on-apply count is the quality signal, stated every time. Then delete the run folder — also when rows are still open for the owner; they are in the reply.
47
+
48
+ ## Gate mode — the closing write
49
+
50
+ Gate mode's target list is **every Doc Obligations row discharged as `updated` plus every `Spec:` doc**, and its code scope is the plan's Proposed Changes paths together with those docs' `owns:` globs. It runs after the verification gate and the Doc Obligations discharge, before `Status: Complete` is written — the freeze waits for it, and the harness's completion policy says so. The skill writes the `Polygraph:` segment and nothing else on the plan: the status flip is the author's, in the discharge-then-freeze edit.
51
+
52
+ Ends by rewriting your own segment of the plan's **`Gates:`** line, exactly:
53
+
54
+ - `Polygraph: passed YYYY-MM-DD (N docs · N claims · N corrected · N registered)` — every target swept, every non-QUESTION row resolved.
55
+ - `Polygraph: owes nothing — {reason}` — written by the plan's **author**, not by you, when the plan has no `Spec:` doc and its Doc Obligations row is `None`, or when the repo's harness ships no polygraph.
56
+
57
+ A QUESTION the owner has not answered leaves the segment at `pending`; the plan stays open, and the completion machinery says why. Never write `passed` over an open question.
58
+
59
+ ## Economy
60
+
61
+ The tier is pinned in the three agent definitions — the cheapest capable tier, low to medium effort, a turn cap — never chosen at dispatch. The strong tier's value concentrates in judgment, and every judgment in this skill is yours. Where a role agent is not installed (a runtime whose plugin channel carries skills only), dispatch an independent subagent on the cheapest capable tier with the role's body inline, and say so in the report.
62
+
63
+ ## What this skill is NOT
64
+
65
+ - **Routine docs health** — stale globs, broken links, structural drift: the `maintenance` agent.
66
+ - **A plan's contract check** — both sides of a boundary for a diff or plan: the `boundary` skill.
67
+ - **Test verification** — proving built behaviour: the `testing` skill.
68
+ - **A whole-tree audit by reflex.** The tree is audited one target at a time, and every plan certifies its own docs; a full sweep is an explicit ask, priced before it runs.
69
+ - **A code editor** — code-side findings exit as register rows (standing rule 3).
70
+
71
+ If the request is one of these, say so and redirect — don't adapt.
@@ -0,0 +1,80 @@
1
+ # Dispatch — packs, roles, packets, recovery, price
2
+
3
+ Load when: composing a context pack, dispatching any role, packeting rows, recovering a dead or turn-capped agent, or pricing a run.
4
+
5
+ ## The run folder
6
+
7
+ `.claude/state/polygraph/{YYYY-MM-DD-slug}/` — machine-local, never tracked, deleted when the run closes:
8
+
9
+ ```
10
+ partials/{doc}.psv one per finder — its whole output, no header
11
+ findings.psv the merge, header first — the working copy
12
+ malformed.txt quarantined lines, kept
13
+ packets/{doc}.txt one per applier: `doc: {path}` then row ids and bounce notes
14
+ verify-{group}.psv id|check|confidence|citations|note
15
+ apply-{doc}.psv id|outcome|note
16
+ packs/{doc}.md the context pack each finder and applier reads
17
+ ```
18
+
19
+ Work items pass through files, never through a dispatch call's arguments — a resumed agent may not carry arguments, and one that dies on a missing argument dies before it reads anything.
20
+
21
+ ## The context pack — the agent's whole reading list
22
+
23
+ One page per target document, written by you before the first dispatch. A brief that sends an agent to the charter, the backlog, the ownership index, or the findings file hands it a 500 KB read to extract 2 KB of fact — and hands the same read to every other agent in the wave. The pack carries the 2 KB:
24
+
25
+ | Section | Content |
26
+ | --- | --- |
27
+ | Target | the doc path; in gate mode, the plan path and the Proposed Changes paths that intersect this doc's `owns:` |
28
+ | Code | the doc's `owns:` globs, resolved to the files that exist today |
29
+ | Precedent | the quotations from guardrails, accepted decisions, and doctrine that **name** a subject this doc covers — the naming words only, with `path:line` |
30
+ | Already tracked | backlog and register rows that name a subject this doc covers, as `path:line` |
31
+ | Rulings | owner answers that bind this subject, verbatim |
32
+ | Read budget | the two clauses below, verbatim |
33
+
34
+ The two clauses every pack carries:
35
+
36
+ > Tree state: re-check every citation against today's tree before acting; a claim that fails to reproduce dies as *did not reproduce* — never becomes an edit. READ-ONLY everywhere except your one output file.
37
+
38
+ > Read budget: the pack is your reading list — read nothing outside it without naming in your return why the pack was insufficient. Grep before you read; never read a file over 20 KB without a line range; read a file once. Compose one shell block per question, not one call per file.
39
+
40
+ ## The roles
41
+
42
+ | Role | Agent | Receives | Writes | Returns |
43
+ | --- | --- | --- | --- | --- |
44
+ | Finder | `polygraph-finder` | the pack path, the partial path | `partials/{doc}.psv` — one row per claim, ten fields, no header | ≤ 120 words: the file path, row count, up to three findings, limitations |
45
+ | Verifier | `polygraph-verifier` | a queue of `id`, `subject`, `claim` — no evidence, no verdict | `verify-{group}.psv` | ≤ 120 words: the file path and counts per check |
46
+ | Applier | `polygraph-applier` | the packet path, the pack path | the packet's docs; `apply-{doc}.psv` | ≤ 120 words: counts per outcome, docs touched, lint result, anything for the register |
47
+
48
+ Each agent definition carries its role's doctrine and its turn cap; a dispatch names the target and the paths and nothing else. **Findings go in the file, never in the return** — every word returned is re-billed on every later turn of the session.
49
+
50
+ Where the runtime has no such agent installed, dispatch an independent subagent on the cheapest capable tier with the agent file's body pasted in as its brief, and say so in the report.
51
+
52
+ ## Packets — one doc, one applier
53
+
54
+ Routing is judgment, so you compose packets by hand; `audit.mjs` asserts the one mechanical invariant: **no document appears in two packets** — appliers run in parallel, and two agents in one file silently lose each other's edits.
55
+
56
+ **Packet by the doc the fix belongs in, not the doc a row's evidence cites.** A row's citations say where the *evidence* lives; the fix usually belongs elsewhere, and a bounced row's `needs-cross-doc` note says exactly where. A row with no derivable doc goes to the owner — never into a packet "to see what the agent makes of it".
57
+
58
+ By verdict: QUESTION and `depends_on` rows → the owner, inline. CONTESTED → you, with both sets of evidence. RESIDUE, and any row whose subject is a source file — a stale comment included — → the register. Everything else → the owning doc's packet.
59
+
60
+ ## Verification — budgeted by what it caught
61
+
62
+ - **The applier re-check is the law.** In the proving run it killed more false positives than every other mechanism combined.
63
+ - **Blind re-derivation is discretionary.** Send a row *you* doubt — claim and subject only. As a mandatory stage it refuted nothing: finders' self-doubt is conservative, and the errors live in the confident pile, which is where your own read of the evidence looks. Disagreement makes the row CONTESTED; a verification never overwrites a verdict.
64
+
65
+ ## Fold-back
66
+
67
+ After each applier returns, its outcomes become row state by your own edit, then `audit.mjs` checks the arithmetic:
68
+
69
+ - `applied` · `registered` · `exempt-frozen` → `resolved`, the resolution stamped with how.
70
+ - `refuted-on-apply` → `resolved`, resolution `did not reproduce`. A legitimate death; its count is the quality signal you report.
71
+ - `needs-cross-doc` · `blocked-on-question` · `skipped` → stays open — repacket, or hold for the answer.
72
+ - COHERENT rows close when recorded; they carry no work.
73
+
74
+ ## Recovery
75
+
76
+ **The file on disk is the recovery unit, never the wave.** A dead agent costs its remainder; read what landed and relaunch only that. A finder that returns **partial** at its turn cap is resumed once from its partial — a second partial is accepted as that doc's sweep and named as such in the report. Every dispatch names its output file and a time bound; an agent past its bound with no file is dead — relaunch it, never wait on it, and never let one runaway agent hold the run. Gates — lint, links, ownership — run in your own seat.
77
+
78
+ ## Price
79
+
80
+ State before dispatching, whenever a run exceeds ten documents or an owner directive names a tier: the per-doc cost at the pinned tier, the wave count under the concurrency ceiling, and the total; for a stronger tier, the multiplier and both totals. The directive and the estimate go in the changelog entry.
@@ -0,0 +1,57 @@
1
+ # Rows — the row, the verdicts, the residue guard
2
+
3
+ Load when: writing or reading any row, judging a CONTESTED or QUESTION row, deciding whether a subject is residue, or ruling on a document's right to exist.
4
+
5
+ ## The row
6
+
7
+ Pipe-delimited, one row per claim, **ten fields**, header line first. A literal `|` inside text is written `¦`; a newline becomes `; `.
8
+
9
+ ```
10
+ id|subject|claim|evidence|counter_evidence|verdict|confidence|depends_on|status|resolution
11
+ ```
12
+
13
+ | Field | Content |
14
+ | --- | --- |
15
+ | `id` | `{DOC}-{NNN}` — the target document's stem and a running number, unique within the run |
16
+ | `subject` | the stable key — a file, table, route, command, or surface |
17
+ | `claim` | one sentence: what is asserted true or false |
18
+ | `evidence` | verbose support — `file:line` citations or the exact command and its result. **No citation, no row.** A verdict leaning on a precedent quotes the precedent's naming words here |
19
+ | `counter_evidence` | what cuts the other way, including `already-registered: path:line` for a known item |
20
+ | `verdict` | one of the seven below |
21
+ | `confidence` | 0.0–1.0, honest — a signal that tells the primary where to look, never a routing key |
22
+ | `depends_on` | empty, or the owner question the row waits on, as the question's own words in brief |
23
+ | `status` | `open` · `resolved` · `reopened` |
24
+ | `resolution` | how it closed — `applied` · `registered` · `did not reproduce` · `answered` · `exempt-frozen` — empty while open |
25
+
26
+ **A negative claim about a long document quotes the passage read** — the sentence believed wrong, or the section where the missing statement should live. Unsupported negative claims are the dominant false-positive class, and they concentrate in the longest documents.
27
+
28
+ ## The seven verdicts
29
+
30
+ | Verdict | Meaning | Disposition |
31
+ | --- | --- | --- |
32
+ | `COHERENT` | doc and code agree — emitted at the audited unit so coverage is countable; closed when recorded | none |
33
+ | `MISDESCRIBED` | the doc is wrong about built behaviour | an applier fixes the doc |
34
+ | `UNDOCUMENTED` | code no doc claims — judged against the precedent pack **first**: condemned code is RESIDUE, never a documentation gap | an applier documents it in the owning doc |
35
+ | `UNBUILT` | a documented design with no code behind it — decided-unbuilt (the repo's marker convention), abandoned, or a document that should die; only the owner distinguishes the last two, so suspecting either makes the row a QUESTION | the marker |
36
+ | `RESIDUE` | code a standing decision condemns by name — running, even load-bearing, and still retiring | a register row, never a doc edit |
37
+ | `CONTESTED` | the evidence conflicts — a verifier and a finder disagree, or a claim has no code to arbitrate and a sibling document says otherwise | the primary judges with both sets of evidence in hand |
38
+ | `QUESTION` | only the owner can resolve it — intent, not evidence, is what is missing | asked inline, answered in the same pass |
39
+
40
+ A disposition is the verdict's default; where the primary overrides it, the resolution says so. **A verification never overwrites a verdict** — disagreement makes the row CONTESTED.
41
+
42
+ ## The residue guard
43
+
44
+ Before an UNDOCUMENTED row becomes a doc edit, its subject is tested against the precedent pack — the one place the audit can silently undo the repo's own decisions. The test is **naming**: a precedent matches only when it names the subject — the file, table, route, command, or surface by the product's own name for it — and the row quotes the naming words into `evidence`. A precedent describing a *resembling* thing is not a match; shared adjectives ("legacy", "old export lane") are not a match; a misread pronoun is not a match. Unsure → QUESTION, never a silent RESIDUE.
45
+
46
+ The asymmetry runs both ways. A missed match resurfaces: the register is owner-read, and an UNDOCUMENTED row that should have been RESIDUE is caught when the owner reads the doc it produced. A false match does not: it retires code the product still ships and withholds its documentation — worse than the disease. When in doubt, ask.
47
+
48
+ ## Frozen records and code
49
+
50
+ - **Frozen records are exempt** — a completed plan, a changelog entry, an archived document resolves `exempt-frozen`; its claims were true when frozen and the tree does not correct history.
51
+ - **The audit never edits code.** A code-side finding — a bug, a stale source comment, the unbuilt, residue — exits as a register row for the planning workflow, however small the edit and however directly it is asked for. **Markdown is not source**: every document read may be edited under the applier discipline, package and root READMEs included; the line is doc versus code, not proximity to `src/`.
52
+
53
+ ## A document's right to exist
54
+
55
+ A wholly stale document passes a sentence-by-sentence audit one true sentence at a time, so a finder that reads a document whose subject is gone, whose authority another doc has absorbed, or whose content belongs inside a sibling emits **one QUESTION row proposing deletion or merge**, carrying the inbound-reference count (who links here, which index rows list it, which ownership globs it claims). Deletion is owner-approved, always. An approved deletion removes the file, repoints every inbound reference to the surviving authority, and leaves **no redirect stub** — an annotation, a banner, or a status flag left in the reading path is not a source of truth however carefully the surviving sentences are hedged. A document whose subject survives but whose content is wrong is MISDESCRIBED, not a deletion candidate.
56
+
57
+ **Abandonment has no marker.** A design decided against is not owed: its claims come **out** of every marker that carried them rather than being reworded inside one, and the document survives as the record of a decision not to build, its status saying so. The tell that this went wrong: a reader finishes the paragraph still expecting the feature.
@@ -0,0 +1,113 @@
1
+ // skills/polygraph/scripts/audit.mjs - The audit's one script, and it is READ-ONLY: it validates
2
+ // the run's findings file, cross-checks verifier and applier outcome files against the rows, and
3
+ // asserts the packet-collision invariant. It never writes a byte — the primary holds the pen;
4
+ // this holds the arithmetic. A checker that is wrong emits a wrong warning; a script that acted
5
+ // on its own verdicts could destroy a refutation with no tally, which is the defect class this
6
+ // replaces.
7
+ //
8
+ // Usage: node audit.mjs <run-dir> (the run folder holding findings.psv)
9
+ // Exit: 0 clean · 1 findings to read · 2 unusable input
10
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+
13
+ const HEADER = 'id|subject|claim|evidence|counter_evidence|verdict|confidence|depends_on|status|resolution';
14
+ const FIELDS = HEADER.split('|').length; // 10
15
+ const VERDICTS = new Set(['COHERENT', 'MISDESCRIBED', 'UNDOCUMENTED', 'UNBUILT', 'RESIDUE', 'CONTESTED', 'QUESTION']);
16
+ const STATUSES = new Set(['open', 'resolved', 'reopened']);
17
+ const RESOLVING = new Set(['applied', 'exempt-frozen', 'refuted-on-apply']);
18
+ const STAYING = new Set(['needs-cross-doc', 'blocked-on-question', 'skipped']);
19
+ const CHECKS = new Set(['CONFIRMED', 'REFUTED', 'ADJUSTED']);
20
+
21
+ const run = process.argv[2];
22
+ if (!run || !existsSync(run) || !statSync(run).isDirectory()) { console.error('usage: node audit.mjs <run-dir>'); process.exit(2); }
23
+ const findingsPath = join(run, 'findings.psv');
24
+ if (!existsSync(findingsPath)) { console.error(`no findings file at ${findingsPath}`); process.exit(2); }
25
+
26
+ const problems = []; // schema violations and cross-check disagreements → exit 1
27
+ const notes = []; // informational — never fails the run
28
+
29
+ // ---- parse + validate the working file -------------------------------------------------------
30
+ const lines = readFileSync(findingsPath, 'utf8').split('\n');
31
+ if (lines[0].trim() !== HEADER) { console.error(`bad or missing header line in ${findingsPath}`); process.exit(2); }
32
+
33
+ const rows = new Map(); // id → { verdict, status, resolution, line }
34
+ for (let n = 1; n < lines.length; n++) {
35
+ const line = lines[n];
36
+ if (!line.trim()) continue;
37
+ const p = line.split('|');
38
+ const at = `line ${n + 1}`;
39
+ if (p.length !== FIELDS) { problems.push(`${at}: ${p.length} fields, expected ${FIELDS}`); continue; }
40
+ const [id, , claim, evidence, , verdict, confidence, , status, resolution] = p;
41
+ if (!id.trim()) { problems.push(`${at}: empty id`); continue; }
42
+ if (rows.has(id)) problems.push(`${at} (${id}): duplicate id — first seen at line ${rows.get(id).line}`);
43
+ if (!VERDICTS.has(verdict)) problems.push(`${at} (${id}): unknown verdict "${verdict}"`);
44
+ const conf = Number(confidence);
45
+ if (!(conf >= 0 && conf <= 1)) problems.push(`${at} (${id}): confidence "${confidence}" outside 0..1`);
46
+ if (!STATUSES.has(status)) problems.push(`${at} (${id}): unknown status "${status}"`);
47
+ if (status === 'resolved' && !resolution.trim())
48
+ problems.push(`${at} (${id}): resolved with an empty resolution — how did it close?`);
49
+ if (!claim.trim() || !evidence.trim())
50
+ problems.push(`${at} (${id}): empty ${!claim.trim() ? 'claim' : 'evidence'} — no citation, no row`);
51
+ rows.set(id, { verdict, status, resolution, line: n + 1 });
52
+ }
53
+
54
+ // ---- cross-check outcome files against row status --------------------------------------------
55
+ const files = readdirSync(run).filter((f) => f.endsWith('.psv'));
56
+
57
+ // apply-*.psv → id|outcome|note
58
+ for (const f of files.filter((f) => f.startsWith('apply-'))) {
59
+ for (const line of readFileSync(join(run, f), 'utf8').split('\n')) {
60
+ if (!line.trim()) continue;
61
+ const [id, outcome] = line.split('|').map((s) => (s ?? '').trim());
62
+ const row = rows.get(id);
63
+ if (!row) { problems.push(`${f}: outcome for unknown row "${id}"`); continue; }
64
+ if (!RESOLVING.has(outcome) && !STAYING.has(outcome)) { problems.push(`${f} (${id}): unknown outcome "${outcome}"`); continue; }
65
+ if (RESOLVING.has(outcome) && row.status === 'open')
66
+ problems.push(`${f} (${id}): outcome "${outcome}" but the row is still open — the fold missed it`);
67
+ }
68
+ }
69
+
70
+ // verify-*.psv → id|check|confidence|citations|note ; disagreement across files is a finding
71
+ const seenChecks = new Map(); // id → { check, file }
72
+ for (const f of files.filter((f) => f.startsWith('verify-'))) {
73
+ for (const line of readFileSync(join(run, f), 'utf8').split('\n')) {
74
+ if (!line.trim()) continue;
75
+ const [id, check] = line.split('|').map((s) => (s ?? '').trim());
76
+ const row = rows.get(id);
77
+ if (!row) { problems.push(`${f}: verification of unknown row "${id}"`); continue; }
78
+ if (!CHECKS.has(check)) { problems.push(`${f} (${id}): unknown check "${check}" — CONFIRMED/REFUTED/ADJUSTED only`); continue; }
79
+ const earlier = seenChecks.get(id);
80
+ if (earlier && earlier.check !== check)
81
+ problems.push(`${id}: two verifiers disagree — ${earlier.check} in ${earlier.file}, ${check} in ${f} → the row is CONTESTED, judge it`);
82
+ else seenChecks.set(id, { check, file: f });
83
+ if (check === 'REFUTED' && row.verdict !== 'CONTESTED' && row.status !== 'reopened')
84
+ notes.push(`${id}: a verifier REFUTED this row and it is neither CONTESTED nor reopened — judged already, or missed?`);
85
+ }
86
+ }
87
+
88
+ // packets/*.txt → `doc: path` lines; no doc in two packets
89
+ const packetsDir = join(run, 'packets');
90
+ if (existsSync(packetsDir)) {
91
+ const claims = new Map(); // doc → packet file
92
+ for (const f of readdirSync(packetsDir).filter((f) => f.endsWith('.txt'))) {
93
+ for (const line of readFileSync(join(packetsDir, f), 'utf8').split('\n')) {
94
+ const m = /^doc:\s*(.+)$/.exec(line.trim());
95
+ if (!m) continue;
96
+ const doc = m[1].trim();
97
+ if (claims.has(doc)) problems.push(`packets: "${doc}" claimed by both ${claims.get(doc)} and ${f} — two appliers in one file lose edits`);
98
+ else claims.set(doc, f);
99
+ }
100
+ }
101
+ }
102
+
103
+ // ---- report -----------------------------------------------------------------------------------
104
+ const total = rows.size;
105
+ const resolved = [...rows.values()].filter((r) => r.status === 'resolved').length;
106
+ const hist = {};
107
+ for (const r of rows.values()) hist[r.verdict] = (hist[r.verdict] || 0) + 1;
108
+ console.log(`rows: ${total} · resolved: ${resolved} · open: ${total - resolved}`);
109
+ console.log(`verdicts: ${Object.entries(hist).map(([k, v]) => `${k} ${v}`).join(' · ') || 'none'}`);
110
+ for (const n of notes) console.log(`~ ${n}`);
111
+ for (const p of problems) console.log(`✗ ${p}`);
112
+ console.log(problems.length ? `${problems.length} finding(s) to read` : 'clean');
113
+ process.exit(problems.length ? 1 : 0);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "delivery",
3
3
  "name": "delivery",
4
- "version": "0.2.29",
4
+ "version": "0.2.30",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -10,5 +10,5 @@
10
10
  "seeds": [],
11
11
  "tools": [],
12
12
  "docsRoot": "docs",
13
- "digest": "e7b3bec5e759"
13
+ "digest": "688776066c84"
14
14
  }
@@ -4,7 +4,7 @@ The shared style for every file an agent obeys — a `CLAUDE.md`, a rule, an age
4
4
 
5
5
  ## The test for every line
6
6
 
7
- An instruction file is paid for whenever it loads, scaled by its load class: `CLAUDE.md` and rules load on **every turn of every session** — the most expensive prose in a repo per word; an agent definition loads per dispatch, a skill per invocation. The bar is not "is this true?" but **"must this be in front of the agent whenever this file loads?"** Measure weight in tokens, not lines; trim biggest-first on every edit.
7
+ An instruction file is paid for whenever it loads, scaled by its load class: `CLAUDE.md` and unscoped rules load on **every turn of every session** — the most expensive prose in a repo per word; a `paths:`-scoped rule loads on those same terms from its first matching file read on; an agent definition loads per dispatch, a skill per invocation. The bar is not "is this true?" but **"must this be in front of the agent whenever this file loads?"** Measure weight in tokens, not lines; trim biggest-first on every edit.
8
8
 
9
9
  **Rewriting, the test is armor vs. narration.** A reason clause either blocks a move an agent would otherwise make — the failure is counter-intuitive, the wrong fix looks correct, or the rule reads as arbitrary tidiness and gets "improved away" — or it explains the self-evident. Ask of each: *does the forbidden move look correct to an agent mid-task?* Keep the first kind, cut the second. **A constraint may lose its explanation; it may never lose its existence** — compression is fewer words for the same constraints, so a passage that cannot shrink without dropping one stays long.
10
10
 
@@ -8,13 +8,13 @@ The `boundary` skill parses the **Proposed Changes** sections to check boundary
8
8
 
9
9
  **Doc Obligations is the one ledger the plan's author writes.** Deriving tests from behavior is judgment, which is why `testing` owns that section and it is marked never-hand-author; resolving Proposed Changes' file paths to the feature docs that own them is a **lookup** against the ownership index already injected at session start. Different work, different rule — fill it yourself, while the touch list is fresh.
10
10
 
11
- The **`Gates:`** line carries one machine-read segment. The compliance hook greps **`Boundary:`** — implementation edits inside the plan's scope are blocked while it reads `Boundary: pending`; the `boundary` skill rewrites it to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`. The other two gates are enforced by **section presence**: a plan with no `## Test Obligations` or no `## Doc Obligations` section blocks the same edits — the sections are the artifacts, and the gate derives from them, not from a claim about them. Never hand-edit the line to skip a gate.
11
+ The **`Gates:`** line carries two machine-read segments. The compliance hook greps **`Boundary:`** — implementation edits inside the plan's scope are blocked while it reads `Boundary: pending`; the `boundary` skill rewrites it to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`. The other two plan gates are enforced by **section presence**: a plan with no `## Test Obligations` or no `## Doc Obligations` section blocks the same edits — the sections are the artifacts, and the gate derives from them, not from a claim about them. **`Polygraph:`** is the completion gate's trace: it reads `Polygraph: pending` from the first draft until the `polygraph` skill (gate mode) certifies the plan's documents — every Doc Obligations row discharged **updated** plus every `Spec:` doc — and rewrites it to `Polygraph: passed YYYY-MM-DD (N docs · N claims · N corrected · N registered)`. A plan that touched no document writes `Polygraph: owes nothing — {reason}` by hand; nothing else on this segment is ever hand-written. A Complete plan still reading `pending` raises a `needs-session:` queue row and fails CI. Never hand-edit the line to skip a gate.
12
12
 
13
13
  ```
14
14
  # {Feature Name}
15
15
 
16
16
  **Status:** Draft | In Progress | Complete | Abandoned
17
- **Gates:** Boundary: pending
17
+ **Gates:** Boundary: pending · Polygraph: pending
18
18
  **Spec:** [docs/features/{feature}.md](../../features/{feature}.md) <!-- one line per feature doc this plan implements against; a plan spanning three features names three -->
19
19
  **Started:** YYYY-MM-DD
20
20
  **Completed:** YYYY-MM-DD <!-- fill in the SAME edit that flips Status to Complete — along with every ledger Outcome cell: the flip freezes the file instantly, so anything left for a second edit is unreachable (the frozen-record gate rightly blocks it) -->
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "etna",
3
3
  "name": "etna",
4
- "version": "1.5.18",
4
+ "version": "1.5.19",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -10,5 +10,5 @@
10
10
  "seeds": [],
11
11
  "tools": [],
12
12
  "docsRoot": "docs",
13
- "digest": "8627c15bbc62"
13
+ "digest": "4cf950471dd8"
14
14
  }
@@ -4,7 +4,7 @@ The shared style for every file an agent obeys — a `CLAUDE.md`, a rule, an age
4
4
 
5
5
  ## The test for every line
6
6
 
7
- An instruction file is paid for whenever it loads, scaled by its load class: `CLAUDE.md` and rules load on **every turn of every session** — the most expensive prose in a repo per word; an agent definition loads per dispatch, a skill per invocation. The bar is not "is this true?" but **"must this be in front of the agent whenever this file loads?"** Measure weight in tokens, not lines; trim biggest-first on every edit.
7
+ An instruction file is paid for whenever it loads, scaled by its load class: `CLAUDE.md` and unscoped rules load on **every turn of every session** — the most expensive prose in a repo per word; a `paths:`-scoped rule loads on those same terms from its first matching file read on; an agent definition loads per dispatch, a skill per invocation. The bar is not "is this true?" but **"must this be in front of the agent whenever this file loads?"** Measure weight in tokens, not lines; trim biggest-first on every edit.
8
8
 
9
9
  **Rewriting, the test is armor vs. narration.** A reason clause either blocks a move an agent would otherwise make — the failure is counter-intuitive, the wrong fix looks correct, or the rule reads as arbitrary tidiness and gets "improved away" — or it explains the self-evident. Ask of each: *does the forbidden move look correct to an agent mid-task?* Keep the first kind, cut the second. **A constraint may lose its explanation; it may never lose its existence** — compression is fewer words for the same constraints, so a passage that cannot shrink without dropping one stays long.
10
10
 
@@ -8,13 +8,13 @@ The `boundary` skill parses the **Proposed Changes** sections to check boundary
8
8
 
9
9
  **Doc Obligations is the one ledger the plan's author writes.** Deriving tests from behavior is judgment, which is why `testing` owns that section and it is marked never-hand-author; resolving Proposed Changes' file paths to the feature docs that own them is a **lookup** against the ownership index already injected at session start. Different work, different rule — fill it yourself, while the touch list is fresh.
10
10
 
11
- The **`Gates:`** line carries one machine-read segment. The compliance hook greps **`Boundary:`** — implementation edits inside the plan's scope are blocked while it reads `Boundary: pending`; the `boundary` skill rewrites it to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`. The other two gates are enforced by **section presence**: a plan with no `## Test Obligations` or no `## Doc Obligations` section blocks the same edits — the sections are the artifacts, and the gate derives from them, not from a claim about them. Never hand-edit the line to skip a gate.
11
+ The **`Gates:`** line carries two machine-read segments. The compliance hook greps **`Boundary:`** — implementation edits inside the plan's scope are blocked while it reads `Boundary: pending`; the `boundary` skill rewrites it to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`. The other two plan gates are enforced by **section presence**: a plan with no `## Test Obligations` or no `## Doc Obligations` section blocks the same edits — the sections are the artifacts, and the gate derives from them, not from a claim about them. **`Polygraph:`** is the completion gate's trace: it reads `Polygraph: pending` from the first draft until the `polygraph` skill (gate mode) certifies the plan's documents — every Doc Obligations row discharged **updated** plus every `Spec:` doc — and rewrites it to `Polygraph: passed YYYY-MM-DD (N docs · N claims · N corrected · N registered)`. A plan that touched no document writes `Polygraph: owes nothing — {reason}` by hand; nothing else on this segment is ever hand-written. A Complete plan still reading `pending` raises a `needs-session:` queue row and fails CI. Never hand-edit the line to skip a gate.
12
12
 
13
13
  ```
14
14
  # {Feature Name}
15
15
 
16
16
  **Status:** Draft | In Progress | Complete | Abandoned
17
- **Gates:** Boundary: pending
17
+ **Gates:** Boundary: pending · Polygraph: pending
18
18
  **Spec:** [docs/features/{feature}.md](../../features/{feature}.md) <!-- one line per feature doc this plan implements against; a plan spanning three features names three -->
19
19
  **Started:** YYYY-MM-DD
20
20
  **Completed:** YYYY-MM-DD <!-- fill in the SAME edit that flips Status to Complete — along with every ledger Outcome cell: the flip freezes the file instantly, so anything left for a second edit is unreachable (the frozen-record gate rightly blocks it) -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.20.10",
3
+ "version": "0.20.11",
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": "0fa183cce9ee"
45
+ "etnaFingerprint": "7d6e2acfe663"
46
46
  }