@nurix/etna 0.15.7 → 0.16.0
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/bin/etna.js +64 -13
- package/data/harness-index.json +7 -5
- package/data/harnesses/code/CLAUDE.md +3 -7
- package/data/harnesses/code/agents/harness-manager.md +4 -4
- package/data/harnesses/code/agents/maintenance.md +1 -1
- package/data/harnesses/code/harness.json +4 -3
- package/data/harnesses/code/hooks/hooks.json +3 -17
- package/data/harnesses/code/hooks/prompt-recall.js +157 -0
- package/data/harnesses/code/hooks/session-reflect.js +283 -0
- package/data/harnesses/code/skills/distiller/SKILL.md +62 -60
- package/data/harnesses/code/skills/distiller/references/docs-mode.md +21 -21
- package/data/harnesses/code/skills/distiller/references/method.md +41 -41
- package/data/harnesses/code/skills/documentation/references/templates.md +20 -27
- package/data/harnesses/code-fable/CLAUDE.md +138 -0
- package/data/harnesses/code-fable/agents/brainstorm.md +35 -0
- package/data/harnesses/code-fable/agents/change-review.md +249 -0
- package/data/harnesses/code-fable/agents/harness-manager.md +193 -0
- package/data/harnesses/code-fable/agents/maintenance.md +123 -0
- package/data/harnesses/code-fable/agents/performance-review.md +218 -0
- package/data/harnesses/code-fable/agents/spec-mode.md +56 -0
- package/data/harnesses/code-fable/harness.json +7 -0
- package/data/harnesses/code-fable/hooks/hooks.json +27 -0
- package/data/harnesses/code-fable/hooks/lib/redact.js +37 -0
- package/data/harnesses/code-fable/hooks/prompt-recall.js +157 -0
- package/data/harnesses/code-fable/hooks/session-reflect.js +283 -0
- package/data/harnesses/code-fable/rules/general.md +96 -0
- package/data/harnesses/code-fable/rules/nomenclature.md +34 -0
- package/data/harnesses/code-fable/skills/boundary/SKILL.md +262 -0
- package/data/harnesses/code-fable/skills/boundary/references/boundary-taxonomy.md +734 -0
- package/data/harnesses/code-fable/skills/brainstorm/README.md +136 -0
- package/data/harnesses/code-fable/skills/brainstorm/SKILL.md +135 -0
- package/data/harnesses/code-fable/skills/brainstorm/harvest.md +26 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/backlog.md +39 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/build.md +37 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/decisions.md +29 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/doctrine.md +31 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/domain-model.md +25 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/glossary.md +19 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/milestones.md +43 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/non-goals.md +21 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/plan-doc.md +61 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/questions.md +35 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/references.md +28 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/rejected.md +29 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/state.md +8 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/tech.md +23 -0
- package/data/harnesses/code-fable/skills/brainstorm/templates/thesis.md +21 -0
- package/data/harnesses/code-fable/skills/distiller/SKILL.md +219 -0
- package/data/harnesses/code-fable/skills/distiller/references/docs-mode.md +526 -0
- package/data/harnesses/code-fable/skills/distiller/references/method.md +166 -0
- package/data/harnesses/code-fable/skills/documentation/SKILL.md +127 -0
- package/data/harnesses/code-fable/skills/documentation/references/examples.md +193 -0
- package/data/harnesses/code-fable/skills/documentation/references/style-guide.md +288 -0
- package/data/harnesses/code-fable/skills/documentation/references/templates.md +942 -0
- package/data/harnesses/code-fable/skills/workflow-mapper/SKILL.md +430 -0
- package/data/harnesses/code-fable/skills/workflow-mapper/references/patterns.md +214 -0
- package/data/harnesses/{re → discover}/CLAUDE.md +48 -33
- package/data/harnesses/{re → discover}/agents/api.md +3 -3
- package/data/harnesses/{re → discover}/agents/codebase.md +3 -3
- package/data/harnesses/{re → discover}/agents/community.md +3 -3
- package/data/harnesses/{re → discover}/agents/deployed-client-bundle.md +5 -5
- package/data/harnesses/{re → discover}/agents/distribution-artifacts.md +4 -4
- package/data/harnesses/{re → discover}/agents/docs.md +3 -3
- package/data/harnesses/{re → discover}/agents/infra-backend-fingerprint.md +2 -2
- package/data/harnesses/{re → discover}/agents/packages.md +3 -3
- package/data/harnesses/{re → discover}/agents/session.md +19 -11
- package/data/harnesses/{re → discover}/agents/website.md +3 -3
- package/data/harnesses/{re → discover}/agents/wire-capture.md +9 -7
- package/data/harnesses/discover/harness.json +7 -0
- package/data/harnesses/{re/rules/re-discovery.md → discover/rules/discovery.md} +54 -17
- package/data/harnesses/{re/rules/re-evaluation.md → discover/rules/evaluation.md} +12 -1
- package/data/harnesses/{re/rules/re-ingestion.md → discover/rules/ingestion.md} +192 -23
- package/data/harnesses/discover/rules/self-correction.md +177 -0
- package/data/harnesses/discover/rules/tradecraft.md +532 -0
- package/data/harnesses/hr/CLAUDE.md +91 -0
- package/data/harnesses/hr/harness.json +7 -0
- package/data/harnesses/hr/skills/interview-eval/SKILL.md +118 -0
- package/data/harnesses/hr/skills/interview-eval/TEMPLATE.md +124 -0
- package/data/harnesses/hr/skills/interview-eval/extract_session.py +418 -0
- package/data/harnesses/hr/skills/interview-eval/rubric.md +219 -0
- package/data/harnesses/kb-agi/harness.json +1 -0
- package/package.json +1 -1
- package/data/harnesses/code/hooks/post-commit.js +0 -136
- package/data/harnesses/code/hooks/post-tool-failure.js +0 -136
- package/data/harnesses/code/hooks/session-stop.js +0 -207
- package/data/harnesses/re/harness.json +0 -6
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
# distiller · mode `docs` — facts → documentation deltas
|
|
2
|
+
|
|
3
|
+
The shared knowledge root for this mode is still [`references/method.md`](method.md) —
|
|
4
|
+
**read it first, in this mode too.** `docs` mode reuses its surprisal, EDU-decomposition,
|
|
5
|
+
reconciliation (ADD/UPDATE/SUPERSEDE/NOOP), and bi-temporal vocabulary unchanged. This
|
|
6
|
+
file is the *procedure* that sequences those mechanisms one hop further down the gradient:
|
|
7
|
+
from `docs/memory/facts/` (warm, distilled cards) into proposed edits to `docs/` (the cold,
|
|
8
|
+
human-owned SSoT). It NEVER writes a doc on its own — it **drafts and presents**; the human
|
|
9
|
+
disposes (P7).
|
|
10
|
+
|
|
11
|
+
> `facts` mode = inbox → cards. `docs` mode = cards → *presented* doc deltas. One component,
|
|
12
|
+
> two modes, one algorithm root. distill② is the greenfield hop no surveyed tool has built
|
|
13
|
+
> (prior art stops at index/retrieval); the discipline below is what keeps it from corrupting
|
|
14
|
+
> documentation.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## The audit-trail invariant (the spine)
|
|
19
|
+
|
|
20
|
+
Every rule here serves one invariant, because the load-bearing moment in this mode is the
|
|
21
|
+
**human-approval moment**, and that moment is only trustworthy if the reviewer can
|
|
22
|
+
mechanically check every claim:
|
|
23
|
+
|
|
24
|
+
> **Two-way traceability.** Every drafted delta *line* names the fact-card(s) it decomposes
|
|
25
|
+
> from (forward: delta → card). Every consumed card names the doc(s) it was digested into
|
|
26
|
+
> (backward: card → delta). Every card already names the sessions/commits it came from
|
|
27
|
+
> (`provenance`, from `facts` mode). The chain **doc-line → card → session/commit**
|
|
28
|
+
> must resolve end to end with no orphan link. A delta line with no card, or a card marked
|
|
29
|
+
> digested into a doc that does not contain its content, is a **defect**: the run fails its
|
|
30
|
+
> acceptance check, the line is not presented.
|
|
31
|
+
|
|
32
|
+
This mode introduces **no prose that is not a decomposition of a card** — decomposition-with-
|
|
33
|
+
citation, exactly as `facts` mode decomposes inbox entries into cards. If the agent cannot
|
|
34
|
+
cite a card for a sentence, that sentence is not written.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Design lens: the six ways facts→docs corrupts documentation
|
|
39
|
+
|
|
40
|
+
Designed **failure-modes-outward.** Every step below names the hole it closes; if a step
|
|
41
|
+
seems heavy, trace it to the failure it prevents.
|
|
42
|
+
|
|
43
|
+
| # | Failure mode | What it looks like | The protection that closes it |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| **F1** | **Invention** | The draft asserts something no card says — interpolated, generalized, or hallucinated prose. | Decomposition-never-invention: every line carries `[from: fact-…]`; a line with no card behind it is illegal and dropped (Step 3, Acceptance A2). |
|
|
46
|
+
| **F2** | **Stale-card application** | A superseded/invalidated card is written into a doc as if current. | Bi-temporal gate: only `invalid_from: null` cards are eligible; superseded cards are skipped and named in the report (Step 2). |
|
|
47
|
+
| **F3** | **Double digestion** | The same card is written into the same doc twice across runs. | `digested_into:` on the card is the idempotency mark — a (card, doc) pair already recorded is not re-drafted (Resolution 3, Step 2). |
|
|
48
|
+
| **F4** | **Conflict steamrolling** | A card contradicts existing prose and the draft silently overwrites it. | Card-vs-prose conflicts become **D7 questions**, never edits (Resolution 2, Step 4). |
|
|
49
|
+
| **F5** | **Pillar-drift absorption** | A card contradicting a doctrine/decision headline is quietly written into a doc, laundering a belief change. | Pillar escape valve: such a card emits a *propose-Brainstorming-Mode* item, never a doc edit (Resolution 4, Step 2.4). |
|
|
50
|
+
| **F6** | **Partial-approval limbo** | Human approves 3 of 5 lines; the card is marked fully digested (losing 2) or fully un-digested (re-proposing 3). | Partial digestion: `digested_into:` records only the (card → doc) pairs *actually written*; declined lines → backlog (D10); the card recirculates only its un-applied content (Resolution 3, Step 6). |
|
|
51
|
+
|
|
52
|
+
Two structural failures sit above these: **frozen-record corruption** (editing a pillar, a
|
|
53
|
+
`Complete`/`Abandoned` plan, or changelog history — forbidden outright) and **axis merge**
|
|
54
|
+
(folding distill② into maintenance — refused: reconcile owns *structure*, maintenance owns
|
|
55
|
+
*docs-vs-code fidelity*, distill② owns the *facts→docs distillation* hop; three distinct axes,
|
|
56
|
+
P19).
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Execution model
|
|
61
|
+
|
|
62
|
+
Runs **in the main session.** No standing sub-agent, no daemon, no cron, no hook self-trigger
|
|
63
|
+
(P6). The harness proposes; the user disposes. Invoked one of three ways, all human-gated:
|
|
64
|
+
|
|
65
|
+
1. The user asks (`distiller docs`, "distill the facts into docs", "draft pending doc deltas").
|
|
66
|
+
2. The **Completion flow** points at it (one router line in the kit `CLAUDE.md` — Resolution 5a).
|
|
67
|
+
3. The user accepts the **maintenance threshold proposal** (≥N undigested cards map to one doc — Resolution 5b).
|
|
68
|
+
|
|
69
|
+
In every path the output is **a presented draft, never a written doc.** Nothing mutates a doc
|
|
70
|
+
without an explicit human "yes" on that specific draft.
|
|
71
|
+
|
|
72
|
+
### Invocation shapes
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
distiller docs # Completion default: resolve target from the active plan/spec
|
|
76
|
+
distiller docs --feature <slug> # just-completed feature → resolve to its owning doc
|
|
77
|
+
distiller docs --doc <path> # threshold path / direct: digest cards touching one named doc
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`docs` mode runs against **one target doc at a time** — the unit of a delta is a single doc.
|
|
81
|
+
The Completion caller uses the bare/`--feature` form (it knows what just shipped); the
|
|
82
|
+
threshold caller (maintenance) uses `--doc` (it knows which doc the cards piled up against).
|
|
83
|
+
A card-first run may surface several single-doc deltas; each is presented and approved
|
|
84
|
+
independently.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Procedure (executable, numbered)
|
|
89
|
+
|
|
90
|
+
> Each step names the failure mode(s) it closes. Ordering is load-bearing: the pillar check
|
|
91
|
+
> precedes drafting (a belief-changing card never reaches the draft); the digestion mark is
|
|
92
|
+
> written **last** (a crash re-presents rather than silently swallows — the mirror of `facts`
|
|
93
|
+
> mode's "consume the digested entries last").
|
|
94
|
+
|
|
95
|
+
### 0. Read the roots
|
|
96
|
+
|
|
97
|
+
Read [`method.md`](method.md) (surprisal / EDU / reconciliation / bi-temporal vocabulary) and
|
|
98
|
+
this file. Confirm the target doc exists, or note that none does (Edge Case A).
|
|
99
|
+
|
|
100
|
+
### 1. Resolve the target doc, read the three roots in order
|
|
101
|
+
|
|
102
|
+
1. `method.md` (algorithm vocabulary).
|
|
103
|
+
2. The **target doc** — full current prose + its `owns`/`touches` frontmatter. This is the
|
|
104
|
+
SSoT you are proposing to revise; you read it before you draft so conflicts surface as
|
|
105
|
+
questions, not silent overwrites.
|
|
106
|
+
- `--doc <path>` → that doc.
|
|
107
|
+
- `--feature <slug>` / bare → the spec that just flipped to `Active` (Completion names it),
|
|
108
|
+
or the feature doc whose `owns` glob covers the plan's Proposed-Changes paths. Glob
|
|
109
|
+
`docs/{features,modules/*/features}/**/*.md`, match `owns` against changed paths.
|
|
110
|
+
Architecture/operations docs have no `owns` — for those, fall to entity/semantic match.
|
|
111
|
+
- No doc resolves → Edge Case A.
|
|
112
|
+
3. The **pillar headlines** — the doctrine pillar (`## Pn —` headlines) and the decisions
|
|
113
|
+
pillar (`## Dn —` headlines) plus each one's one-line Principle/Decision statement. Read
|
|
114
|
+
**every** decision headline except those marked superseded: a decision carrying a
|
|
115
|
+
`Reversed by D-M` marker (or a legacy `Status: tentative`) is NOT a binding pillar headline
|
|
116
|
+
and is skipped; absence of any status marker means the decision is live (the new
|
|
117
|
+
append-only model has no `Status: committed` field — a live decision is simply one nothing
|
|
118
|
+
has reversed). Headlines only, never the bodies — this is the conservative
|
|
119
|
+
contradiction-check substrate (Resolution 4).
|
|
120
|
+
|
|
121
|
+
### 2. INPUT SELECTION — gather the cards "touching" the target (Resolution 1) — *closes F2, F3, F5*
|
|
122
|
+
|
|
123
|
+
1. **Structural retriever (always runs, qmd-independent).** Read every card in
|
|
124
|
+
`docs/memory/facts/*.md`. A card *structurally touches* the target when its `provenance`
|
|
125
|
+
paths or `entities` intersect the target's `owns`/`touches` globs — match defined precisely
|
|
126
|
+
in Resolution 1.
|
|
127
|
+
2. **Semantic retriever (qmd, optional).** If `command -v qmd` succeeds, for the target's
|
|
128
|
+
identity (title + each `owns` glob stem + any `anchors:` query) run
|
|
129
|
+
`qmd query --format json -c {repo}-facts "<the doc's subject>"`. `{repo}` = repo dir name;
|
|
130
|
+
scope with `-c` always; repeat `-c` per relevant `{repo}-*` facts collection (the set
|
|
131
|
+
`qmd collection list` names). qmd absent ⇒ skip silently, note in the report that selection
|
|
132
|
+
ran structural-only (changes recall, not correctness).
|
|
133
|
+
3. **Bi-temporal + digestion gate (F2, F3).** From the union, drop:
|
|
134
|
+
- any card with `invalid_from` ≠ `null` (superseded — list as "skipped: superseded");
|
|
135
|
+
- any card whose `digested_into:` already lists the target doc path (already consumed here);
|
|
136
|
+
- any card whose `superseded_by:` chain leads to a live card already in the set (the live
|
|
137
|
+
card wins; keep the superseded one out of drafting, keep its trail — Edge Case D).
|
|
138
|
+
What remains is the **working card-set**.
|
|
139
|
+
4. **Partition the working card-set (F5):**
|
|
140
|
+
- **pillar-conflicting** — cards in explicit headline-level conflict with a pillar/decision
|
|
141
|
+
(Resolution 4). These NEVER become a doc edit; they route to the escape valve **before**
|
|
142
|
+
drafting, so a belief-changing fact can never be laundered into prose.
|
|
143
|
+
- **doc-bound** — everything else (most cards).
|
|
144
|
+
A card can be both: handle both arms; do not let one suppress the other.
|
|
145
|
+
|
|
146
|
+
### 3. DELTA DRAFTING — classify, then decompose doc-bound cards (Resolution 2) — *closes F1, F4*
|
|
147
|
+
|
|
148
|
+
First **surprisal-gate** each doc-bound card against the *doc* (method §1, retargeted): a card
|
|
149
|
+
whose content the doc already states is predictable ⇒ **NOOP** — draft nothing, but still mark
|
|
150
|
+
it digested on accept (Step 5; it is absorbed, so it must not re-surface forever). This is the
|
|
151
|
+
only retention signal; no importance scores.
|
|
152
|
+
|
|
153
|
+
For each surviving card, classify against existing prose — the card-vs-doc decision (method §3,
|
|
154
|
+
retargeted), exactly one of:
|
|
155
|
+
|
|
156
|
+
- **EXTEND** — adds behavior/rationale the doc lacks, no conflict → draft an additive line.
|
|
157
|
+
- **REFINE** — sharpens an existing doc claim that is incomplete, not wrong → draft an in-place
|
|
158
|
+
refinement (additive; never deletes prose).
|
|
159
|
+
- **CONFLICT** — card and prose disagree on a matter of fact → **do not resolve.** Emit a D7
|
|
160
|
+
question; draft the line only *conditionally*, gated on the answer (F4).
|
|
161
|
+
- **ESCALATE** — contradicts a pillar headline → already routed to the valve in Step 2.4
|
|
162
|
+
(Resolution 4); never drafted here.
|
|
163
|
+
- **NOOP** — doc already states it → mark digested, draft nothing.
|
|
164
|
+
|
|
165
|
+
For each EXTEND/REFINE card, decide which **allowed section** it informs (Resolution 2 fixes
|
|
166
|
+
the allow-list) and draft the smallest faithful prose change. Every drafted line carries an
|
|
167
|
+
inline provenance marker `[from: fact-<slug>]` for each contributing card. These markers are
|
|
168
|
+
**scaffolding for the review moment, not final doc text** — stripped on accept (Step 5.1), so
|
|
169
|
+
the human-owned prose stays clean. The durable forward-link survives as the card's
|
|
170
|
+
`digested_into:` back-pointer.
|
|
171
|
+
|
|
172
|
+
The output of this step is a **single presented delta document** (in chat, not written to
|
|
173
|
+
disk):
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
TARGET: docs/features/memory-capture.md
|
|
177
|
+
|
|
178
|
+
── PROPOSED DELTA ────────────────────────────────────────
|
|
179
|
+
§ Current Behavior (EXTEND)
|
|
180
|
+
+ The qmd retrieval lens is organized into per-subtree collections, with the
|
|
181
|
+
memory inbox excluded from indexing by construction. [from: fact-qmd-optional-retrieval-lens]
|
|
182
|
+
|
|
183
|
+
§ Changelog entry (new — docs/history/changelog/2026-06-04-...md)
|
|
184
|
+
### Modified
|
|
185
|
+
- capture-hook merge is now idempotent across re-install [from: fact-etna-hook-merge-idempotent]
|
|
186
|
+
|
|
187
|
+
── QUESTIONS (D7 phrasing — answer before accept) ────────
|
|
188
|
+
Q1. docs/features/memory-capture.md §Current Behavior says the index spans all of
|
|
189
|
+
docs/memory; fact-qmd-optional-retrieval-lens says the memory inbox is excluded.
|
|
190
|
+
Confirm the doc is stale before I revise it — I will not overwrite it silently.
|
|
191
|
+
|
|
192
|
+
── PILLAR-CONFLICT ESCAPE (NOT a doc edit) ───────────────
|
|
193
|
+
(see Resolution 4 — emitted as a propose-Brainstorming-Mode item, if any)
|
|
194
|
+
|
|
195
|
+
── DIGESTION PLAN (applied only on accept) ───────────────
|
|
196
|
+
On accept I will set `digested_into:` on: fact-qmd-..., fact-etna-hook-..., and the
|
|
197
|
+
NOOP'd cards already stated by the doc: fact-...
|
|
198
|
+
──────────────────────────────────────────────────────────
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 4. PRESENT and await disposition — never auto-write (locked default #4) — *the gate itself*
|
|
202
|
+
|
|
203
|
+
Present the delta document in chat and **stop.** The agent does not edit the target doc, write
|
|
204
|
+
the changelog, or touch any card frontmatter at this point. The human disposes:
|
|
205
|
+
|
|
206
|
+
- **Accept (whole or per-line)** → Step 5.
|
|
207
|
+
- **Accept with edits** → the human's edits are authored prose; apply them as doc text, but
|
|
208
|
+
digestion-marking (Step 5) still records the contributing cards as evidence consumed (the
|
|
209
|
+
human edited the *abstraction*; the *evidence* is unchanged). Lines the human deletes
|
|
210
|
+
entirely → their sole-source cards are **not** marked digested (nothing landed for them).
|
|
211
|
+
- **Decline** → Step 6 (backlog at par, D10).
|
|
212
|
+
- **Blocked-on-question (CONFLICT)** → leave undigested; the question re-surfaces next run if
|
|
213
|
+
unanswered. Not backlogged unless the user explicitly declines to engage it.
|
|
214
|
+
|
|
215
|
+
### 5. ON ACCEPT — write doc, write changelog, mark digestion LAST (Resolution 3) — *closes F3, F6*
|
|
216
|
+
|
|
217
|
+
Apply in this order so a crash leaves the audit trail consistent (doc content exists before
|
|
218
|
+
any card claims to have landed in it):
|
|
219
|
+
|
|
220
|
+
1. **Write the accepted prose into the target doc**, stripping the `[from: ...]` markers from
|
|
221
|
+
the final text (review scaffolding; the doc's prose is the human-owned abstraction).
|
|
222
|
+
Prefer handing approved lines to the **`documentation` skill**, which owns the
|
|
223
|
+
feature/changelog templates — this mode carries no copy of them.
|
|
224
|
+
2. **Write the changelog entry** if the delta included one (via the `documentation` skill's
|
|
225
|
+
Changelog template, `docs/history/changelog/`). Append-only; never edit an existing entry.
|
|
226
|
+
3. **Mark digestion LAST:** on each card that contributed an *accepted* line (and each NOOP'd
|
|
227
|
+
card the doc already stated), append the target doc path (and changelog path, if it
|
|
228
|
+
contributed there) to `digested_into:`. Marking last means a crash mid-write re-presents
|
|
229
|
+
the delta rather than falsely claiming digestion.
|
|
230
|
+
|
|
231
|
+
Cards STAY — `digested_into:` is the only mutation; bi-temporal rules from `method.md` still
|
|
232
|
+
apply (a card is never deleted; supersession is `facts` mode's surface). The card is now both
|
|
233
|
+
evidence-for-the-doc and a queryable audit record of *where its content went*.
|
|
234
|
+
|
|
235
|
+
### 6. ON DECLINE — backlog at par, deduped (D10) — *closes F6*
|
|
236
|
+
|
|
237
|
+
Append the declined delta to `docs/history/backlog.md` at the finding's own P0–P3 severity
|
|
238
|
+
(same scale as code work), deduped:
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
- **[P2]** distiller-docs proposed updating docs/features/memory-capture.md §Current Behavior from fact-qmd-optional-retrieval-lens (declined) — discovered by distiller docs
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Dedupe by (target doc § section, contributing card-ids): if an identical pending item exists,
|
|
245
|
+
leave it. Never a segregated section, never auto-lowest-tier (D10). Never block Completion.
|
|
246
|
+
The declined cards stay undigested, so they re-surface next pass.
|
|
247
|
+
|
|
248
|
+
### 7. Report (in chat)
|
|
249
|
+
|
|
250
|
+
Target doc; working-set cards; lines drafted and their cards; cards classified EXTEND / REFINE
|
|
251
|
+
/ NOOP / CONFLICT-question / ESCALATE / skipped-as-superseded; questions raised; pillar-conflict
|
|
252
|
+
items verbatim; on accept — doc + changelog written and cards marked digested; on decline — the
|
|
253
|
+
backlog line. If qmd was absent, the one advisory line that selection ran structural-only. A
|
|
254
|
+
run where no undigested card touches the target is **valid**: say "no undigested cards touch
|
|
255
|
+
{target}," do not pad — and **touch nothing**.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## The five resolutions
|
|
260
|
+
|
|
261
|
+
### Resolution 1 — INPUT SELECTION: what "touching" means, precisely
|
|
262
|
+
|
|
263
|
+
A card **touches** the target doc iff it satisfies **either** retriever, and is not removed by
|
|
264
|
+
the Step-2.3 gates. "Touching" is a **union of retrievers, intersected with eligibility** —
|
|
265
|
+
broad capture, conservative gating.
|
|
266
|
+
|
|
267
|
+
**(a) Structural intersection (exact, always evaluated).** True when ANY of:
|
|
268
|
+
|
|
269
|
+
1. **Provenance match (STRONG signal).** A `commit <sha>` entry in the card's `provenance`
|
|
270
|
+
resolves to changed files (`git show --name-only <sha>` when the commit is local); a changed
|
|
271
|
+
file under the target's `owns`/`touches` globs is a match. (`session <id>` entries carry no
|
|
272
|
+
paths and do not participate; an ADR/brainstorm path matches the globs directly.) This is
|
|
273
|
+
strong because the card derives from a commit that edited code the doc
|
|
274
|
+
owns.
|
|
275
|
+
2. **Entity match (SUPPORTING signal).** A normalized name in the card's `entities` equals
|
|
276
|
+
(case-insensitively, after kebab/camel normalization) a module/feature stem in an `owns`
|
|
277
|
+
glob, the doc title, or a symbol the doc explicitly names. **Bare prose mentions do NOT
|
|
278
|
+
count** — only declared ownership, so "touching" can't be triggered by a doc that merely
|
|
279
|
+
discusses an entity it doesn't own.
|
|
280
|
+
|
|
281
|
+
**(b) Semantic neighborhood (qmd, optional, recall-only).** A card returned by
|
|
282
|
+
`qmd query -c {repo}-facts "<doc subject>"` within qmd's default result window. Semantic hits
|
|
283
|
+
with **no** structural intersection are still candidates but flagged `[semantic-only]` in the
|
|
284
|
+
delta — the link is associative, not provenance-grounded, so the reviewer treats it as
|
|
285
|
+
lower-trust and may reject on link-strength alone.
|
|
286
|
+
|
|
287
|
+
The candidate set is the **union** of (a) and (b); correctness rests on (a) (exact,
|
|
288
|
+
deterministic, qmd-independent), (b) only raises recall. This is why the mode runs fully
|
|
289
|
+
without qmd, and why the maintenance threshold (Resolution 5b) counts on (a) alone.
|
|
290
|
+
|
|
291
|
+
A card may touch **several** docs (its entities span modules) — expected, handled by partial
|
|
292
|
+
digestion (Resolution 3): the card is processed once per target doc, and `digested_into:`
|
|
293
|
+
accumulates each doc it actually fed.
|
|
294
|
+
|
|
295
|
+
### Resolution 2 — DELTA DRAFTING RULES: what a fact may update, with provenance
|
|
296
|
+
|
|
297
|
+
**Allow-list of writable targets** (a card may inform ONLY these):
|
|
298
|
+
|
|
299
|
+
| Target | What a card may do | Why bounded here |
|
|
300
|
+
|---|---|---|
|
|
301
|
+
| **`## Current Behavior`** (feature doc) | Add/modify a sentence describing how the system works *today*, when a card reports a now-true behavior the section omits or contradicts. The primary target. | The "how it works now" section cards are evidence for. |
|
|
302
|
+
| **`## Background`** — why-built metadata (feature doc) | Add a sentence of *why* a decision was made, when a card records a decision/constraint/reversal Background omits. | Cards carry the "why" the loop captured; Background is its home. |
|
|
303
|
+
| **`## Architecture` / `## Key Functions`** (feature doc) | Additive correction only when a card names a concrete, code-anchored fact (a new function, a moved file). Prefer Current Behavior unless the card is structural. | Code-anchored structure, not prose. |
|
|
304
|
+
| **A new changelog entry** in `docs/history/changelog/` (via `documentation` skill's Changelog template, `### Added`/`### Modified`/`### Removed`) | Draft a changelog line for a code change a card witnesses, when a plan just completed. | Append-only history; a *new* file, never an edit. |
|
|
305
|
+
|
|
306
|
+
**Never writable by distill②** (hard stops): `## Goal`, `## Goals & Non-Goals`, `## Approach`,
|
|
307
|
+
`## Open Questions`, `## State`, frontmatter `owns`/`touches`/`anchors` (maintenance/spec axis
|
|
308
|
+
— P19), any **pillar** file, any **plan** (frozen), any **existing** changelog entry (frozen
|
|
309
|
+
history), any **boundary** file. A card implying one of these needs a change becomes a
|
|
310
|
+
**question** (below) or, for pillars, an **escape item** (Resolution 4).
|
|
311
|
+
|
|
312
|
+
**Per-line provenance (the invariant, mechanized).** Every drafted line carries
|
|
313
|
+
`[from: fact-<slug>]` for each contributing card. A line with no such tag is a **defect** — not
|
|
314
|
+
presented (Acceptance A2). On accept the tag is stripped from the final doc prose (it was
|
|
315
|
+
review scaffolding); the durable forward-link survives as `digested_into:` (Resolution 3) — so
|
|
316
|
+
traceability is preserved without polluting human-owned prose.
|
|
317
|
+
|
|
318
|
+
**Decomposition, never invention.** As in `facts` mode: one card may yield several doc lines;
|
|
319
|
+
the agent rephrases the card's atomic statement into doc-fit prose but **adds no claim the card
|
|
320
|
+
does not support.** When two cards are needed for one line, both ids attach. If doc-fit prose
|
|
321
|
+
would require a fact in no card, that fact is *missing* — raise it as a gap in the report, do
|
|
322
|
+
not invent it.
|
|
323
|
+
|
|
324
|
+
**Conflict → question (D7 phrasing), never silent resolution.** When a card contradicts an
|
|
325
|
+
existing sentence, the agent does NOT overwrite. It emits a D7 question — *"docs/x.md §S says
|
|
326
|
+
A; fact-<slug> (from <provenance>) says B. Confirm the doc is stale before I revise it."* —
|
|
327
|
+
and drafts the conflicting line only as a *conditional* proposal gated on the answer. Docs stay
|
|
328
|
+
SSoT: the card is evidence for revision, the human decides.
|
|
329
|
+
|
|
330
|
+
### Resolution 3 — DIGESTION MARKING: `digested_into:` semantics
|
|
331
|
+
|
|
332
|
+
On accept, each contributing (and each NOOP'd) card gains a `digested_into:` frontmatter list:
|
|
333
|
+
|
|
334
|
+
```yaml
|
|
335
|
+
---
|
|
336
|
+
id: fact-qmd-optional-retrieval-lens
|
|
337
|
+
valid_from: 2026-06-04
|
|
338
|
+
invalid_from: null
|
|
339
|
+
superseded_by: null
|
|
340
|
+
digested_into: # ← added by docs mode, on accept, never on draft
|
|
341
|
+
- docs/features/memory-capture.md
|
|
342
|
+
- docs/history/changelog/2026-06-04-qmd-lens.md
|
|
343
|
+
entities: [qmd, retrieval-lens, per-subtree-collections, inbox]
|
|
344
|
+
provenance: [session e3c4a623, commit d262e18]
|
|
345
|
+
---
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
A **flat list of doc paths** — the brief's shape. Track *which doc* a card landed in, not which
|
|
349
|
+
sentence; whole-card-vs-target is the granularity, which keeps selection (Step 2.3) a simple
|
|
350
|
+
list-membership test.
|
|
351
|
+
|
|
352
|
+
Semantics:
|
|
353
|
+
|
|
354
|
+
- **Absent / empty** ⇒ undigested ⇒ eligible for selection and counted by the maintenance
|
|
355
|
+
threshold. The default state of a fresh `facts`-mode card.
|
|
356
|
+
- **A list of doc paths** ⇒ the card's content has been decomposed into each listed doc — the
|
|
357
|
+
backward audit link completing the two-way trace.
|
|
358
|
+
- **Partial digestion (F6), keyed on whether THE CURRENT TARGET is listed.** A card may
|
|
359
|
+
contribute to doc X yet have residual facts not placed elsewhere. Mark it
|
|
360
|
+
`digested_into: [docs/X.md]` — digested into X, but **still selectable** for other targets,
|
|
361
|
+
because selection drops a card only when the list already contains *the current target*. A
|
|
362
|
+
card stays in circulation until every doc it touches has consumed it. No separate flag.
|
|
363
|
+
Lines the human declined are not represented in the list (their cards recirculate); only
|
|
364
|
+
*applied* content is recorded.
|
|
365
|
+
- **One card, several docs** ⇒ the list grows **append-only across runs.** Each entry is
|
|
366
|
+
independently meaningful; order is run-order. **Never rewrite or remove an entry** — a doc
|
|
367
|
+
later reverted does not un-digest the card (the card *was* digested there at that transaction
|
|
368
|
+
time; the doc's own git history records the revert). This mirrors `method.md`'s bi-temporal
|
|
369
|
+
stance: the trail only grows.
|
|
370
|
+
- **Supersession interaction (F2 + F4 seam).** If a digested card is later superseded by a
|
|
371
|
+
`facts`-mode run, the *old* card keeps its `digested_into:` (it documents what the doc once
|
|
372
|
+
said and why) AND gains `invalid_from:`/`superseded_by:` from the normal bi-temporal path.
|
|
373
|
+
The *new* card is undigested → on the next `docs` run its content surfaces the now-stale doc
|
|
374
|
+
prose as a **CONFLICT question** (Resolution 2), never an auto-rewrite. The trail shows: doc
|
|
375
|
+
prose ← old card (now invalid) ← superseded_by → new card (pending). No link is ever broken.
|
|
376
|
+
|
|
377
|
+
`digested_into:` is the **only** field `docs` mode writes on a card. It never edits the card's
|
|
378
|
+
body, `provenance`, `entities`, or bi-temporal fields — those are `facts` mode's surface.
|
|
379
|
+
|
|
380
|
+
### Resolution 4 — PILLAR ESCAPE VALVE: "contradicts a pillar," conservatively
|
|
381
|
+
|
|
382
|
+
**Definition (deliberately narrow).** A card *contradicts a pillar* iff its atomic statement is
|
|
383
|
+
in **explicit, headline-level conflict** with a doctrine principle (`## Pn —` headline) or a
|
|
384
|
+
live decision (`## Dn —` headline) — i.e. the card asserts X and the headline (or its one-line
|
|
385
|
+
Principle/Decision statement) asserts not-X about the *same* subject. A decision counts as a
|
|
386
|
+
binding headline unless it has been superseded: a `Reversed by D-M` marker (or a legacy
|
|
387
|
+
`Status: tentative`) drops it from the substrate, and the reversing decision is the live
|
|
388
|
+
headline in its place. The operative test: **could you point to the exact pillar headline the
|
|
389
|
+
card negates?** If not, it is not a pillar contradiction.
|
|
390
|
+
|
|
391
|
+
- ✅ Escapes: a card stating "the harness auto-runs the distiller on a cron" vs P6's "build no
|
|
392
|
+
enforcement machinery / no cron" — direct negation of a headline claim. A card recording a
|
|
393
|
+
reversal that negates a live decision headline — e.g. "modules auto-create at a
|
|
394
|
+
flat→modules threshold" vs a live D-headline "the user sets module granularity."
|
|
395
|
+
- ❌ Does NOT escape: a card that merely *touches a topic a pillar discusses*; adds nuance,
|
|
396
|
+
scope, or an exception not contemplated by a pillar (normal doc evolution); is "in tension
|
|
397
|
+
with the spirit"; or conflicts only with a pillar's **body** but not its headline (that is a
|
|
398
|
+
normal doc CONFLICT question — D7 — or nothing). **Vibes do not escape.**
|
|
399
|
+
|
|
400
|
+
If uncertain whether a conflict is headline-level, **do not escape** — treat it as a doc CONFLICT
|
|
401
|
+
question or drop it. Rationale (the decision rule under uncertainty): a false-negative — a real
|
|
402
|
+
conflict slips through as a question — is **recoverable** by the human in the review moment; a
|
|
403
|
+
false-positive — a spurious brainstorm proposal — costs the operator a mode-entry decision and
|
|
404
|
+
**erodes the valve's signal.** When unsure, the cheaper miss is the question.
|
|
405
|
+
|
|
406
|
+
**Emission (P7 mode-entry instance — propose, never enter).** A pillar-conflicting card
|
|
407
|
+
produces a **propose-Brainstorming-Mode item** in the PILLAR-CONFLICT block, never a doc edit
|
|
408
|
+
and never an auto-entry into Brainstorming Mode:
|
|
409
|
+
|
|
410
|
+
```
|
|
411
|
+
── PILLAR-CONFLICT ESCAPE (NOT a doc edit) ───────────────
|
|
412
|
+
fact-<slug> (from <provenance>) appears to contradict doctrine headline
|
|
413
|
+
"P6 — the harness builds no enforcement machinery":
|
|
414
|
+
card says: <the card's atomic statement>
|
|
415
|
+
P6 headline: <the headline claim>
|
|
416
|
+
This is a belief-level conflict, not a doc revision. Propose entering Brainstorming
|
|
417
|
+
Mode to reconcile it? (I will not edit the pillar, and I have NOT digested this card
|
|
418
|
+
into any doc.) [from: fact-<slug>, contradicts: P6]
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The conflicting card is **NOT marked digested** (it was not absorbed — it escaped) and **NOT
|
|
422
|
+
backlogged** as a doc-delta (it awaits the brainstorm disposition). It stays undigested so the
|
|
423
|
+
conflict re-surfaces until brainstorm resolves it (the recurrence *is* the dedupe). Distillation
|
|
424
|
+
never quietly rewrites belief — the pillars are the human-owned headwater that *steers*;
|
|
425
|
+
distill② may not silently change what they steer. (If a brainstorm later lands a pillar change
|
|
426
|
+
plus a downstream doc edit, that doc edit re-enters this mode normally.)
|
|
427
|
+
|
|
428
|
+
### Resolution 5 — TRIGGER WIRING (P10: at most one line each)
|
|
429
|
+
|
|
430
|
+
**(a) Completion-flow pointer — ONE router-grade line in the kit `CLAUDE.md` Completion step.**
|
|
431
|
+
Added to the "On **Completion**" flow in `harness/code/CLAUDE.md` §Workflows. Terse, points,
|
|
432
|
+
does not re-teach (the procedure lives in this file). Verbatim:
|
|
433
|
+
|
|
434
|
+
> On Completion, after flipping the spec to `Active`, consult `distiller docs` for the just-completed feature's doc — it drafts any pending fact-card deltas for your approval (presented, never auto-written).
|
|
435
|
+
|
|
436
|
+
**(b) Maintenance threshold — ONE line in `agents/maintenance.md` Step 7**, sibling to the
|
|
437
|
+
existing inbox-distillation threshold (the `facts` analogue: ≥5 undigested *inbox entries*
|
|
438
|
+
proposes a `facts` run). Verbatim:
|
|
439
|
+
|
|
440
|
+
> **Docs-distillation threshold.** Per target doc, count undigested fact-cards (`invalid_from: null`, the doc's path not in their `digested_into:`) that **touch** it by Resolution 1's structural intersection (exact, qmd-independent). If **≥10** map to **one** doc, add one line to the report **proposing** a `distiller docs --doc <path>` run on that doc — propose only, never auto-run, no cron. Declined/ignored ⇒ the count keeps growing and the proposal reappears next pass; that recurrence is the dedupe, so never backlog the proposal itself.
|
|
441
|
+
|
|
442
|
+
`N = 10` default (vs the inbox threshold's 5 — inbox entries are rare and rich; cards accrue per doc). "Touch" uses the
|
|
443
|
+
**structural** retriever only (deterministic, no qmd dependency in the maintenance loop).
|
|
444
|
+
Per-doc count, not repo-wide: the proposal names the one doc, so the run is already scoped. This
|
|
445
|
+
lands as a proposal emitter only — maintenance does not run the distiller (P19: distinct axis).
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Edge cases
|
|
450
|
+
|
|
451
|
+
**A. No target doc exists yet for a completed feature.** distill② does **not** create the
|
|
452
|
+
feature doc — that is the Completion step's job (flip spec to `Active`) via the `documentation`
|
|
453
|
+
skill. If the invocation finds no doc, the mode reports *"no target doc yet for {feature};
|
|
454
|
+
create it via the spec flow first, then re-invoke `distiller docs`"* and stops. It never
|
|
455
|
+
scaffolds a doc from cards alone — a doc born purely from distilled facts would have no
|
|
456
|
+
human-authored abstraction (Background/Goal/Approach) and would violate docs-as-SSoT +
|
|
457
|
+
decomposition-never-invention. Cards stay undigested; they'll be drafted once the doc exists. If
|
|
458
|
+
the gap persists, backlog at par: `- **[P2]** Feature {slug} has {N} undigested cards but no owning doc — discovered by distiller docs`.
|
|
459
|
+
|
|
460
|
+
**B. A card touching zero docs.** Not selected for any target — it stays undigested, queryable,
|
|
461
|
+
counted toward no threshold. Normal and valid: not every fact belongs in a doc (a transient
|
|
462
|
+
observation, a fact about an unowned path). **Never** force-fit into the nearest doc (that is
|
|
463
|
+
invention). If maintenance reports many undigested cards touching *no* doc, that is an
|
|
464
|
+
**unowned-code** signal for maintenance's existing unowned-path step, not a distill② action.
|
|
465
|
+
Report it as a count (*"{N} cards touch no current doc"*); do not absorb it. No doc is touched.
|
|
466
|
+
|
|
467
|
+
**C. A card touching a FROZEN record (a plan, an existing changelog entry, a pillar).** Never
|
|
468
|
+
edited (hard stop). A card whose only relevant target is frozen:
|
|
469
|
+
- if it *contradicts* a pillar headline → escape valve (Resolution 4);
|
|
470
|
+
- if it reports a code change worth recording → draft a **new** changelog entry (append-only is
|
|
471
|
+
allowed; editing an existing one is not);
|
|
472
|
+
- otherwise → it touches a writable doc section instead, or touches zero docs (Edge Case B).
|
|
473
|
+
The frozen file is never the write target.
|
|
474
|
+
|
|
475
|
+
**D. Conflicting cards (two live cards disagree about the target's area).** distill② does not
|
|
476
|
+
adjudicate which is true — that is `facts`-mode reconciliation's job (ADD/UPDATE/SUPERSEDE/NOOP,
|
|
477
|
+
`method.md` §3). If two *live* (non-superseded) cards make contradictory claims about the same
|
|
478
|
+
doc sentence, surface **both** as a single D7 question — *"fact-A says X, fact-B says Y about §S;
|
|
479
|
+
these disagree — which holds? I will not draft a revision until the cards reconcile"* — and draft
|
|
480
|
+
**neither** line. Suggest the user run `distiller facts` to reconcile first. If one card is
|
|
481
|
+
already `superseded_by` the other, the live card wins and the superseded one is excluded from
|
|
482
|
+
drafting (Step 2.3) but its trail is preserved. distill② never resolves a fact-vs-fact conflict
|
|
483
|
+
by picking one — that crosses into fabricating belief.
|
|
484
|
+
|
|
485
|
+
**E. A card hand-edited into the doc (accept-with-edits), then re-surfaces.** Because
|
|
486
|
+
`digested_into:` was set on accept (Step 5.3) listing the target, selection (Step 2.3) drops the
|
|
487
|
+
card on the next run — it is digested into that doc regardless of whether the landed prose is the
|
|
488
|
+
agent's draft or the human's edit (the human edited the *abstraction*; the *evidence* is recorded
|
|
489
|
+
either way). The card does not re-surface for that doc. Only if the human *deleted* a line
|
|
490
|
+
entirely (so nothing landed for its sole-source card) does that card stay undigested and re-
|
|
491
|
+
surface — which is correct: its content never reached the doc.
|
|
492
|
+
|
|
493
|
+
**F. Disambiguation — prose-conflict vs pillar-conflict.** If a card collides with *both* a doc
|
|
494
|
+
sentence and a pillar headline, the **pillar escape wins** (the heavier, more conservative path):
|
|
495
|
+
emit the propose-Brainstorming-Mode item and do not draft the doc line, because the doc prose may
|
|
496
|
+
itself be downstream of the belief now in question. Resolve the belief first, then let any
|
|
497
|
+
resulting doc edit re-enter normally.
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
## Acceptance checks
|
|
502
|
+
|
|
503
|
+
1. **Presented, never auto-written.** A run produces a chat-presented draft and waits; no doc
|
|
504
|
+
file changes until the human approves (locked default #4).
|
|
505
|
+
2. **Per-line provenance.** Every drafted prose line / changelog bullet carries a `[from: ...]`
|
|
506
|
+
tag naming ≥1 source card; a line with no card behind it does not exist in the draft (F1).
|
|
507
|
+
3. **Digestion recorded + cards survive.** On approval, every consumed (incl. NOOP'd) card gains
|
|
508
|
+
the target path in `digested_into:` and the file remains in `docs/memory/facts/` (F3); marking
|
|
509
|
+
happens last, after the doc write.
|
|
510
|
+
4. **Pillar-contradicting card → brainstorm proposal, not a doc edit.** A planted card negating a
|
|
511
|
+
doctrine/decision headline produces a propose-Brainstorming-Mode item and zero doc/pillar
|
|
512
|
+
edits; it is neither digested nor backlogged (Resolution 4, F5).
|
|
513
|
+
5. **Conflict surfaces as a question.** A card disagreeing with doc prose yields a D7-phrased
|
|
514
|
+
QUESTION, never a silent rewrite (Resolution 2, F4).
|
|
515
|
+
6. **Declined delta → backlog at par, deduped.** Declining produces one P-tiered backlog line
|
|
516
|
+
interleaved with code items; a second decline adds nothing (D10).
|
|
517
|
+
7. **Docs without relevant cards untouched.** A doc no card touches is never opened or modified;
|
|
518
|
+
a run with an empty working set reports "no undigested cards touch {target}" and changes
|
|
519
|
+
nothing.
|
|
520
|
+
8. **Idempotent via the digestion mark.** An immediate re-run on the same doc finds its touching
|
|
521
|
+
cards already `digested_into:` that doc and is a clean NOOP — the digestion mark is to `docs`
|
|
522
|
+
mode what inbox consumption is to `facts` mode (F3).
|
|
523
|
+
9. **Trigger budget.** Exactly one line added to `CLAUDE.md` Completion; exactly one line added to
|
|
524
|
+
maintenance Step 7. No procedure inlined into either (P10).
|
|
525
|
+
10. **Not autonomous.** No cron/daemon/hook; invoked by the Completion step, by accepting
|
|
526
|
+
maintenance's threshold proposal, or by direct user request only (P6).
|