polydeukes 0.3.0 → 0.5.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.
@@ -2,15 +2,19 @@
2
2
 
3
3
  **English** · [한국어](./configuration.ko.md)
4
4
 
5
- > Alpha. This reference describes the config surface as shipped today (schema v2,
6
- > loader, and the four built-in discipline predicates). Fields and predicates will grow;
7
- > what is written here is tested and enforced now.
5
+ > Alpha. This guide covers the config surface as shipped today (schema v2, loader, and
6
+ > the four built-in discipline predicates). Fields and predicates will grow; what is
7
+ > written here is tested and enforced now.
8
8
 
9
9
  `polydeukes.config.yaml` is the one file where a project declares its disciplines — the
10
10
  promises the human and the AI partner both agree to be bound by. It is **data, not code**:
11
11
  nothing in it can compute, so nothing in it can lie. The core validates it, the covenant
12
12
  package enforces it, and every judgment it causes is measured.
13
13
 
14
+ This is the guide layer: the file itself, how discovery fails, the IDE wiring, and what
15
+ enforcement looks like. Every key, with its full rules and pitfalls, lives in
16
+ [the configuration reference](./reference/configuration.md).
17
+
14
18
  ## The file
15
19
 
16
20
  Put exactly one of these at the project root:
@@ -37,325 +41,63 @@ Discovery is deliberately strict, and every failure refuses loudly instead of gu
37
41
  leaves that adapter on its defaults — check the name against the adapter's own reference.
38
42
  Inside a namespace the vocabulary belongs to that adapter: the core passes contents
39
43
  through verbatim, and the adapter's own validator rejects what it does not recognise,
40
- naming the full field path (see [`adapters`](#adapters-optional)).
44
+ naming the full field path (see
45
+ [the `adapters` reference](./reference/configuration.md#adapters)).
41
46
 
42
47
  ## IDE support
43
48
 
44
- The published JSON Schema gives autocompletion and validation in editors. The schema file
45
- ships inside `@polydeukes/core` — a *transitive* dependency of the umbrella — and under
46
- pnpm's default strict layout transitive dependencies are not exposed at your project's
47
- top-level `node_modules` (measured 2026-08-03: the file resolves only under
48
- `node_modules/.pnpm/…`). A `node_modules`-relative `$schema` line therefore does not
49
- resolve in a default pnpm install. `pdks init claude-code` writes no `$schema` line into
50
- the generated config for the same reason: no single *file-path* spelling resolves in every
51
- consumer layout, and a wrong line loses editor validation silently.
52
-
53
- Two forms work. The version-pinned public URL is independent of any install layout
54
- (verified against the `v0.2.0` tag and `main` — swap the tag for the release you
55
- installed):
49
+ The JSON Schema gives autocompletion and validation in editors. It ships inside the
50
+ `polydeukes` package, so the line names a path into your own `node_modules`:
56
51
 
57
52
  ```yaml
58
- # yaml-language-server: $schema=https://raw.githubusercontent.com/huskyhoochu/polydeukes/v0.2.0/packages/core/schema/polydeukes.schema.json
53
+ # yaml-language-server: $schema=node_modules/polydeukes/dist/schema/polydeukes.schema.json
59
54
  ```
60
55
 
61
- The relative path works only in a layout where `@polydeukes/core` is present at the top
62
- level of `node_modules`. Whether yours is one is a one-look check — if
63
- `node_modules/@polydeukes/core/schema/` exists in your project, the line resolves:
64
-
65
- ```yaml
66
- # yaml-language-server: $schema=node_modules/@polydeukes/core/schema/polydeukes.schema.json
67
- ```
68
-
69
- For a JSON config, use the standard top-level key instead — it is accepted and ignored by
70
- the loader, with the same two values:
56
+ For a JSON config, use the standard top-level key instead. The loader accepts it and drops
57
+ it from the resolved config:
71
58
 
72
59
  ```json
73
- { "$schema": "https://raw.githubusercontent.com/huskyhoochu/polydeukes/v0.2.0/packages/core/schema/polydeukes.schema.json" }
74
- ```
75
-
76
- ## Reference
77
-
78
- ### `languages` (required)
79
-
80
- The language axis, first-class. Keys are your values (`typescript`, `python`, …) — the
81
- core ships no language names and never interprets the command string.
82
-
83
- ```yaml
84
- languages:
85
- typescript:
86
- productionGlob: 'packages/*/src/**/*.ts' # what counts as production source
87
- testCmd: 'pnpm --filter {scope} test' # {scope} is substituted at resolve time
88
- ```
89
-
90
- `testCmd` is a template string, not a function. Every `{scope}` occurrence is replaced;
91
- all other braces (`${VAR}`, `{a,b}`, `awk '{print}'`) pass through untouched. A command
92
- that ignores scope (`pnpm test`) is equally valid.
93
-
94
- ### `protectedPaths` (optional)
95
-
96
- Raw path patterns whose files the covenants protect from modification — by editor tools
97
- and by shell commands alike (`sed -i`, `tee`, redirects, heredocs, parent-directory
98
- moves). Entries are normalized (trimmed, deduplicated) at resolve time.
99
-
100
- ```yaml
101
- protectedPaths:
102
- - 'packages/core/src'
103
- - '.claude/hooks'
104
- ```
105
-
106
- **The config file protects itself.** The discovered config file is automatically appended
107
- to `protectedPaths` — an edit that would lower your own gates goes through the same judge
108
- as everything else. If the file that declares the disciplines were not itself under the
109
- disciplines, the whole chain would be decoration.
110
-
111
- ### `adapters` (optional)
112
-
113
- Adapter namespaces. One config file, one namespace per adapter: each key names an
114
- adapter, and its value is that adapter's own settings object. The core validates the
115
- container shape only — the keys and the contents belong to each adapter, which ships
116
- its own validator for its own vocabulary. An unknown key *inside* a namespace is
117
- rejected by that adapter's validator, with the full field path in the error.
118
-
119
- ```yaml
120
- adapters:
121
- git:
122
- enforce: advise
123
- protectedPaths:
124
- - 'packages/core/src'
125
- ```
126
-
127
- #### `adapters.git` — the git commit adapter
128
-
129
- | Key | Values | Default | Meaning |
130
- |---|---|---|---|
131
- | `enforce` | `block` \| `advise` | `block` | Enforcement level of the commit surface |
132
- | `protectedPaths` | string array | `[]` | Additive protection scope judged by the commit surface only |
133
-
134
- - **`block`** — a staged change that breaks a covenant blocks the commit (exit 2). The
135
- only way through is the witness valve: a human answering the TTY prompt with the full
136
- token. The prompt names what it asks the human to witness — the broken registration,
137
- the matched entry, and the fact that the one answer covers the whole commit. An absent
138
- namespace, an absent `adapters` map, or an absent `enforce` key all mean `block` — not
139
- writing the key selects the strictest level.
140
- - **`advise`** — the commit surface becomes a backstop without a block: a verdict on a
141
- staged change is recorded as an `advised` telemetry event and the commit proceeds
142
- (exit 0) with one advisory line on stderr. No TTY prompt fires. Only the verdict is
143
- relaxed — a run that cannot judge (missing or invalid config, an unresolvable judge
144
- body) still fails closed at exit 2, at either level.
145
-
146
- **`protectedPaths` here is an additive scope.** The commit surface judges the union of the
147
- top-level `protectedPaths` and this list — concatenated (common first) and normalized as one,
148
- so spelling and dedupe rules are identical for both. The session surface never reads it: the
149
- list exists for paths whose edit is legitimate work during a session but must pass a judged
150
- checkpoint when it is promoted into repository history — a judgment chain's own sources are
151
- the canonical tenant. As the enforcement level is the observer's setting, so is the
152
- additional scope. There is no subtractive vocabulary: a config line can widen a surface's
153
- scope, never quietly strip one.
154
-
155
- The session surface (the editor-time hook) has no level setting here; it always blocks.
156
-
157
- **Context-family disciplines skip on the commit surface.** A commit has no session to look
158
- at, so a `requirePrecedent` entry cannot be judged there — demanding evidence a commit
159
- cannot carry would block every matching commit with no legitimate way through.
160
-
161
- They are not filtered out, though. They assemble like any other discipline and become
162
- *skip registrations*: routing intact, no judge body. When one matches a staged change it
163
- records a `skipped` telemetry event and lets the commit proceed. The record carries the
164
- entry's `id` and the change it would have judged, so a gate that did nothing says so in
165
- the data — and it appears **only when the entry's scope actually matched**, so a commit
166
- touching nothing the entry cares about records nothing at all.
167
-
168
- This is the same disposition the session surface uses whenever it has no transcript to
169
- read. One rule, both surfaces: evidence that cannot be evaluated is skipped and measured,
170
- never blocked and never silent.
171
-
172
- ### `telemetry` (optional)
173
-
174
- ```yaml
175
- telemetry:
176
- logPath: '.polydeukes/roi.log' # default when omitted; keep it gitignored
177
- ```
178
-
179
- Every judgment — passed, blocked, witnessed, advised, or skipped — appends one record.
180
- Telemetry is fail-open by design: a logging failure never changes a verdict.
181
-
182
- ### `witness` (optional)
183
-
184
- ```yaml
185
- witness:
186
- token: 'covenant witness' # the phrase a human types in the conversation
187
- ttlMinutes: 10 # validity window, in minutes, from that message
188
- ```
189
-
190
- The values of the time-boxed human valve, consumed where the covenants are assembled.
191
- The valve is sudo, not an exemption: the one property a deterministic gate can compute
192
- about a judgment chain is "is an accountable human present, right now", and the witness
193
- is that human supplying the pass condition in person. When a covenant blocks a
194
- legitimate edit, a human types the agreed token into the conversation; blocked judgments
195
- can be witnessed open for `ttlMinutes` from that message's timestamp, then blocking
196
- resumes automatically. Both keys are required when the section is present: the token
197
- must be non-empty after trimming, the window a finite number greater than zero.
198
-
199
- **The valve stands after the verdict, never instead of it.** The judge body always runs.
200
- A call that would have passed anyway never consults the valve, so an open window changes
201
- nothing about clean work — and a `witnessed` telemetry row therefore always names a real
202
- block a human answered for, never a ritual. Only a judgment that actually blocked can be
203
- witnessed open.
204
-
205
- **The token must stand alone on the message's first line.** Invoking the witness is
206
- distinct from talking about it: a message that quotes, questions, or explains the token
207
- mid-sentence — or wraps it in backticks — does not open the valve, while a first line
208
- carrying the token alone does, with any following lines free for the work itself.
209
-
210
- A message that invokes — the token alone on the first line, the rest free:
211
-
212
- ```text
213
- covenant witness
214
-
215
- now fix the hook file
216
- ```
217
-
218
- A message that merely mentions — the valve stays shut:
219
-
220
- ```text
221
- so when does `covenant witness` expire?
222
- ```
223
-
224
- The token's value is free — any phrase works, and it is never checked for a prefix or a
225
- command shape. Only its placement is constrained.
226
-
227
- The token is not a secret — the defense is provenance, not secrecy. A witness counts only
228
- when the token arrives in a message positively identified as human-typed in the session
229
- transcript, so an AI that knows the token still cannot forge one. Witnessed judgments are
230
- recorded as `witnessed`, never silent.
231
-
232
- ### `disciplines` (optional)
233
-
234
- Each entry is one discipline: a practice the team imposes on itself, declared as data.
235
- An entry carries exactly **one** predicate (zero or two is rejected), an `id` (the
236
- telemetry label), and optionally a `why` (the reason, kept next to the rule) plus, on a
237
- `forbid` or `requirePrecedent` entry, `in` (the file globs it judges) and `except` (globs
238
- carved out of that scope).
239
-
240
- **`forbid` — content delta.** Blocks an edit that *adds* a new match of the pattern.
241
- Existing occurrences are forgiven: adopting a discipline never blocks a legacy codebase,
242
- because the judgment direction is "what did this edit add", not "what does the file
243
- contain".
244
-
245
- ```yaml
246
- disciplines:
247
- - id: 'covenant-vocabulary'
248
- why: 'control-framing vocabulary is banned in package sources.'
249
- in:
250
- - 'packages/*/src/**'
251
- forbid: '\b(guard|harness|kb)\b'
252
- ```
253
-
254
- **`immutable` — path family.** Blocks modification of existing files that match; creating
255
- new files is allowed.
256
-
257
- ```yaml
258
- - id: 'archived-records-stay-frozen'
259
- why: 'an archive that can be edited is not an archive.'
260
- immutable: 'records/archive/**'
60
+ { "$schema": "node_modules/polydeukes/dist/schema/polydeukes.schema.json" }
261
61
  ```
262
62
 
263
- **`forbidCommand` — command family.** Blocks shell commands matching the pattern, even
264
- when the command mentions no protected path. This is how gate-disarming commands are
265
- caught.
63
+ **The path is resolved against the directory your config sits in**, not against a project
64
+ root the editor infers. The spelling above is right when the two are the same place. When
65
+ they are not — a config in a monorepo sub-package whose dependencies installed at the
66
+ workspace root — count the levels up yourself:
266
67
 
267
68
  ```yaml
268
- - id: 'hooks-stay-armed'
269
- why: 'a command that disarms or reroutes the git gate is a gate bypass in itself.'
270
- forbidCommand: 'LEFTHOOK=(0|false|no|off)\b|core\.hooksPath'
69
+ # yaml-language-server: $schema=../../node_modules/polydeukes/dist/schema/polydeukes.schema.json
271
70
  ```
272
71
 
273
- **`requirePrecedent` — context family.** Blocks a change that arrives without a required
274
- step having happened earlier in the session. The other three families all ask "is this
275
- change itself bad"; this one asks something else. The change is legitimate — what is
276
- missing is the procedure in front of it, so what gets judged is not the mutation but the
277
- session history.
278
-
279
- Evidence means an **execution**, not a request. A call the covenant blocked, one a human
280
- refused, and one that simply failed all leave the same trace in a session, and none of
281
- them is precedent — the transcript is read for what actually ran and reported success.
282
- That is what keeps the cheapest way through the gate being the thing the discipline
283
- asks for.
72
+ `pdks init claude-code` writes the line only when the schema is where the plain spelling
73
+ names it. If the generated config has no such line, that is the case above, and the prefix
74
+ is yours to add — an unresolvable path costs you validation without reporting anything.
284
75
 
285
- Two consequences are worth knowing before you write one. The outcome is read per command
286
- LINE, so a chain where the required command ran but a later step failed does not count.
287
- And the pattern is matched at the start of a simple command, so the same words in an
288
- argument or a comment do not count either. **In both cases running the command on its own
289
- opens the gate** — the block message says so.
76
+ If you installed `@polydeukes/core` directly rather than the umbrella, name its own copy:
290
77
 
291
78
  ```yaml
292
- - id: 'dependency-needs-npm-view'
293
- why: 'a dependency version must be measured before it is written.'
294
- in:
295
- - 'package.json'
296
- - 'packages/*/package.json'
297
- when: '(^|\n)\s*"[^"]+"\s*:\s*"[~^]?\d[^"]*"'
298
- requirePrecedent:
299
- command: 'npm view '
79
+ # yaml-language-server: $schema=node_modules/@polydeukes/core/schema/polydeukes.schema.json
300
80
  ```
301
81
 
302
- The evidence vocabulary is layered. `command` is the core's own key — a shell call is a surface
303
- every agent shares — and the core validates it fully, rejecting an empty string or a pattern that
304
- does not compile. It is matched **at the start of a simple command**, not anywhere in the command
305
- line, so `echo "npm view yaml"` and a mention parked behind a `#` are not evidence while `cd pkg &&
306
- npm view yaml` is. Every other key belongs to an adapter: the core checks the container only (a flat
307
- object carrying exactly one evidence key) and passes the value through verbatim, and the adapter
308
- that owns the word validates and judges it. The Claude Code adapter brings two: `subagent` (exact
309
- match on a spawn kind) and `tool` (a regex over tool names) — so "query the docs tool before
310
- touching this" is expressible today. Both follow the same execution rule as `command`. An evidence
311
- key no assembled adapter recognizes cannot be judged, so the entry compiles to a skip registration:
312
- routing stays, the body is dropped, assembly names the fault once on stderr, and every matching
313
- change afterwards records `skipped` rather than a verdict. A typo therefore never passes itself off
314
- as adapter vocabulary — but it does leave the discipline inert, and the `skipped` rows are where
315
- that shows.
316
-
317
- `when` (optional) is the trigger: an added-direction delta regex, combinable with
318
- `requirePrecedent` and with nothing else. When it is absent, every change inside `in`
319
- scope triggers the discipline. The two keys divide the work — `in` says which files are
320
- watched, `when` says which change in them demands the precedent.
321
-
322
- **A caution on line anchors.** These patterns are matched against the file's whole content
323
- as a single string, and the config schema takes a regex string with no flags. `^` therefore
324
- anchors to the start of the *file*, not the start of a line, so a line-shaped pattern
325
- written with `^` matches only the first line and the discipline silently stops firing —
326
- the regex still compiles, the judgment still runs, and the verdict is `passed`. Write
327
- `(^|\n)` when you mean the start of a line. This is why the example above carries
328
- `(^|\n)\s*"[^"]+"…` rather than `^\s*"[^"]+"…`.
329
-
330
- **And a caution on match length.** The delta keys on the matched *text*: a change is only
331
- seen as added when the matched string itself differs between the file's before and after.
332
- A pattern that stops mid-value — say at the first digit of a version — produces the same
333
- match text for `4.0.5` and `4.0.6`, so a version bump adds nothing to the delta and the
334
- discipline silently passes. Make the pattern span the whole value that can change; the
335
- example above runs through the closing quote (`\d[^"]*"`) for exactly this reason. Both
336
- failure shapes are the same class: the regex compiles, the verdict says `passed`, and
337
- nothing tells you the discipline is inert — so when you add an entry, measure it against
338
- a real file and a realistic edit, not a one-line snippet.
339
-
340
- The kind of change matters at the trigger. With `when` present, a deletion never triggers
341
- — deleting adds no content. With `when` absent, deletion triggers like any other change in
342
- scope, since the declared scope is the whole mutation.
343
-
344
- **The cheap way through is the honest one.** Unlike the witness, this evidence lives on the
345
- AI's own surface, so it is not forgery-proof. It does not need to be: the least effortful
346
- way to open this gate is to actually call the tool, and that is exactly the behaviour the
347
- discipline exists to induce.
348
-
349
- Adding a discipline is a data edit — no code, no plumbing. Custom judge bodies remain the
350
- escape layer for the few rules data cannot express.
82
+ Every value here is a **file path**, not a module specifier: `$schema` is a static string an
83
+ editor reads, so no module resolver runs on it. Code that reads the schema at runtime uses
84
+ the package subpath `polydeukes/schema.json` instead.
351
85
 
352
86
  ## What enforcement looks like
353
87
 
354
- A violating tool call or shell command is **blocked (exit 2)** before it runs, with the
355
- discipline's `id` in the telemetry record. The sanctioned valve is the witness — a human
356
- supplying the pass condition on a judgment that actually blocked, recorded as
357
- `witnessed` — never silent. On the commit surface under
358
- `adapters.git.enforce: advise`, a verdict is recorded as `advised` and the commit
359
- proceeds — a backstop that measures instead of blocking. A missing, ambiguous, or
360
- invalid config blocks every call until it is fixed: the system fails closed, because a
361
- dead gate that waves things through is the cheapest bypass of all.
88
+ A `disciplines:` entry lands at **advise** by default: a break is recorded as `advised`
89
+ with the discipline's `id` in the telemetry record, the break message with its `why` goes
90
+ to stderr, and the call proceeds (exit 0) — the judgment measures instead of stopping.
91
+ Writing `enforce: block` on an entry is the promotion: that entry then **blocks (exit 2)**
92
+ before the call runs. The sanctioned valve on a block is the witness — a human supplying
93
+ the pass condition on a judgment that actually blocked, recorded as `witnessed` — never
94
+ silent.
95
+
96
+ What blocks without being asked is the judging chain's own protection, a finite list: the
97
+ `protectedPaths` entries (tool-axis and shell-axis mutations, and mentions without a
98
+ read-only head), the session transcript, and the assembly itself — a missing, ambiguous, or
99
+ invalid config, an unbuilt judge, an unparseable payload, or a routing that could not
100
+ answer. At either level the system fails closed on these, because a dead gate that waves
101
+ things through is the cheapest bypass of all. On the commit surface `adapters.git.enforce: advise` relaxes
102
+ the protected-path verdicts to `advised` as well — it is the observer's setting — while an
103
+ assembly that cannot judge still fails closed.
@@ -5,6 +5,8 @@
5
5
  > Alpha. This guide covers the install paths that ship today, and everything here is the
6
6
  > measured behaviour of the published packages.
7
7
 
8
+ This is the getting-started layer: from an empty project to a first judged call.
9
+
8
10
  One devDependency, one command per surface. The umbrella package `polydeukes` is the only
9
11
  thing you install — it carries the core, the judge, and the adapters as its own
10
12
  dependencies, and `pdks` is its CLI (an alias of `polydeukes`).
@@ -49,8 +51,8 @@ package resolves there **before writing anything** — if it does not (say, the
49
51
  was skipped), it prints the install command and exits 2 with zero files written, never a
50
52
  half-wired tree.
51
53
 
52
- Five artifacts, none ever overwritten. What exists is reported and kept — the hook, the
53
- config, and the discipline file are left alone, the settings file is merged, and
54
+ Six artifacts, none ever overwritten. What exists is reported and kept — the hook, the
55
+ config, and the discipline files are left alone, the settings file is merged, and
54
56
  `.gitignore` is only ever appended to — so re-running is always safe:
55
57
 
56
58
  | Artifact | What it is |
@@ -59,6 +61,7 @@ config, and the discipline file are left alone, the settings file is merged, and
59
61
  | `.claude/settings.json` | The PreToolUse registration for editing tools and shell calls. **Merged, never replaced** — your other hooks and permissions stay. |
60
62
  | `polydeukes.config.yaml` | The starter protection policy: a placeholder `languages` block, a minimum `protectedPaths` list, and the witness block. The comments in the file explain why each entry is there. |
61
63
  | `.claude/rules/polydeukes.md` | A scoped discipline file telling your AI partner that `pdks docs` exists and which topic answers what. It carries `paths` frontmatter, so it loads when a Polydeukes path is in play rather than sitting in every session's context. |
64
+ | `.claude/skills/discipline-draft/SKILL.md` | The classification procedure. Describe a recurring problem to your AI partner and it lands as a config entry — judged at advise when a current family can express it, a `draft: true` entry otherwise — and the same file tells the agent to consult `advised` rows in the telemetry log at task boundaries. |
62
65
  | `.gitignore` | An appended ignore rule for `.polydeukes/`, with its comment line — telemetry is local observation data and never belongs in history. |
63
66
 
64
67
  ## First edit — `languages`
@@ -203,6 +206,7 @@ Every judgment appends exactly one record — `passed`, `blocked`, `witnessed`,
203
206
  `skipped` — so the block you just caused is the last line. A gate you have watched block
204
207
  once is a gate you know is wired.
205
208
 
206
- From here: [the configuration reference](./configuration.md) for every field and for
209
+ From here: [the configuration guide](./configuration.md) for the file and its wiring,
210
+ [the configuration reference](./reference/configuration.md) for every field and for
207
211
  writing your own disciplines, and [troubleshooting](./troubleshooting.md) when something
208
212
  blocks and you don't know why.
@@ -25,16 +25,27 @@ This is a pure library. It knows the staged-diff shape and nothing about install
25
25
  runners, or valves — wiring it into a pre-commit hook is a deployment act that lives in the
26
26
  umbrella.
27
27
 
28
- ## Staged collection and the `adapters.git` namespace
28
+ ## Collection and the `adapters.git` namespace
29
+
30
+ **Three collectors, one shape.** `collectStagedChanges`, `collectWorktreeChanges`, and
31
+ `collectRangeChanges(repoRoot, '<base>..<head>' | '<base>...<head>')` each return the same
32
+ `StagedChange[]`, so the translator and everything after it is one path.
33
+
34
+ | Collector | `pre` | `post` | Also |
35
+ |---|---|---|---|
36
+ | staged | HEAD blob | The **staged** blob — never the worktree, which may have diverged after `git add` | |
37
+ | worktree | HEAD blob | The bytes on disk | Untracked, non-ignored files join as `added`; a file missing from disk is `deleted`, whether HEAD held it or only the index did; an unreadable path (a dangling symlink) yields null content and is judged on its path |
38
+ | range | base blob | head blob | `...` resolves the base to `git merge-base`; a ref git cannot resolve, or two refs with no merge-base, throws |
29
39
 
30
40
  **Collection is deliberately narrow about what it trusts.**
31
41
 
32
42
  | Decision | Why |
33
43
  |---|---|
34
- | `--no-renames` forced on | A rename is judged as a deletion plus an addition. A `git mv` of a protected file must not slip through as one opaque rename entry |
35
- | `pre` from the HEAD blob, `post` from the **staged** blob | Never the worktree, which may have diverged after `git add` |
36
- | A binary blob yields null content | Rather than lossily decoded bytes |
37
- | The unborn first commit narrows to all-added | Rather than throwing |
44
+ | `--no-renames` forced on, in every collector | A rename is judged as a deletion plus an addition. A `git mv` of a protected file must not slip through as one opaque rename entry |
45
+ | A binary blob or file yields null content | Rather than lossily decoded bytes |
46
+ | The unborn first commit narrows to all-added | Rather than throwing — staged and worktree alike |
47
+ | A type change (`T`) keeps its `pre` side | A symlink replaced by a file is a modification, so a delta judgment still sees what was removed |
48
+ | Every listing ends with `--` | A branch that shares its name with a file is a ref, never an ambiguous argument |
38
49
 
39
50
  Translation produces one tool call per change, under the adapter-owned names `staged-write`
40
51
  and `staged-delete`. A deletion always carries its evidence. A write carries it unless the
@@ -54,7 +65,7 @@ vocabulary, its validator, and its defaults all live here.
54
65
 
55
66
  An unknown key, an `enforce` outside the two values, or a `protectedPaths` that is not an
56
67
  array of strings each fail fast with the full field path. The writing reference is
57
- [configuration.md's `adapters` section](../configuration.md#adapters-optional).
68
+ [the configuration reference's `adapters` section](./configuration.md#adapters).
58
69
 
59
70
  **The additive scope is additive for a reason.** The level belongs to the observer, and so
60
71
  does the scope: entries listed here are judged when work becomes history, and the session