polydeukes 0.5.0 → 0.6.1
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/README.ko.md +54 -74
- package/README.md +55 -86
- package/dist/baseline.d.ts +82 -0
- package/dist/baseline.js +166 -0
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +26 -6
- package/dist/claude-code-hook.d.ts +7 -5
- package/dist/claude-code-hook.js +69 -17
- package/dist/claude-code.d.ts +6 -0
- package/dist/claude-code.js +6 -0
- package/dist/covenant-check.d.ts +10 -17
- package/dist/covenant-check.js +44 -12
- package/dist/covenant-module.d.ts +2 -2
- package/dist/covenant-module.js +9 -1
- package/dist/docs/README.ko.md +60 -0
- package/dist/docs/README.md +64 -0
- package/dist/docs/catalog.json +464 -0
- package/dist/docs/concepts/judgment.ko.md +113 -0
- package/dist/docs/concepts/judgment.md +113 -0
- package/dist/docs/how-to/configure-project.ko.md +99 -0
- package/dist/docs/how-to/configure-project.md +95 -0
- package/dist/docs/how-to/connect-surfaces.ko.md +115 -0
- package/dist/docs/how-to/connect-surfaces.md +118 -0
- package/dist/docs/how-to/write-disciplines.ko.md +124 -0
- package/dist/docs/how-to/write-disciplines.md +125 -0
- package/dist/docs/index.json +2046 -0
- package/dist/docs/reference/cli/covenant-check.ko.md +101 -0
- package/dist/docs/reference/cli/covenant-check.md +98 -0
- package/dist/docs/reference/cli/docs.ko.md +97 -0
- package/dist/docs/reference/cli/docs.md +95 -0
- package/dist/docs/reference/cli/explain.ko.md +79 -0
- package/dist/docs/reference/cli/explain.md +84 -0
- package/dist/docs/reference/cli/init.ko.md +119 -0
- package/dist/docs/reference/cli/init.md +131 -0
- package/dist/docs/reference/configuration/index.ko.md +448 -0
- package/dist/docs/reference/configuration/index.md +474 -0
- package/dist/docs/reference/packages/adapter-claude-code.ko.md +83 -0
- package/dist/docs/reference/{adapter-claude-code.md → packages/adapter-claude-code.md} +16 -10
- package/dist/docs/reference/packages/adapter-git.ko.md +101 -0
- package/dist/docs/reference/{adapter-git.md → packages/adapter-git.md} +25 -15
- package/dist/docs/reference/packages/core.ko.md +128 -0
- package/dist/docs/reference/{core.md → packages/core.md} +32 -13
- package/dist/docs/reference/packages/covenant.ko.md +115 -0
- package/dist/docs/reference/{covenant.md → packages/covenant.md} +41 -26
- package/dist/docs/reference/packages/polydeukes.ko.md +134 -0
- package/dist/docs/reference/packages/polydeukes.md +139 -0
- package/dist/docs/troubleshooting.ko.md +142 -0
- package/dist/docs/troubleshooting.md +97 -122
- package/dist/docs/tutorials/first-judgment.ko.md +82 -0
- package/dist/docs/tutorials/first-judgment.md +81 -0
- package/dist/docs-catalog.d.ts +25 -0
- package/dist/docs-catalog.js +450 -0
- package/dist/docs-library.d.ts +23 -0
- package/dist/docs-library.js +347 -0
- package/dist/docs-markdown.d.ts +32 -0
- package/dist/docs-markdown.js +150 -0
- package/dist/docs-query.d.ts +11 -40
- package/dist/docs-query.js +28 -122
- package/dist/docs-types.d.ts +105 -0
- package/dist/docs-types.js +2 -0
- package/dist/explain.d.ts +3 -5
- package/dist/explain.js +48 -47
- package/dist/index.d.ts +2 -3
- package/dist/index.js +1 -2
- package/dist/init-claude-code.d.ts +5 -3
- package/dist/init-claude-code.js +226 -63
- package/dist/init-grok.d.ts +51 -0
- package/dist/init-grok.js +242 -0
- package/dist/load-config.d.ts +5 -1
- package/dist/load-config.js +2 -1
- package/dist/pre-state-reader.d.ts +22 -0
- package/dist/pre-state-reader.js +32 -0
- package/dist/scaffold-project.js +48 -8
- package/dist/schema/polydeukes.schema.json +38 -91
- package/package.json +7 -7
- package/dist/docs/configuration.md +0 -103
- package/dist/docs/installation.md +0 -212
- package/dist/docs/reference/configuration.md +0 -338
- package/dist/docs/reference/polydeukes.md +0 -287
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# Configuration reference
|
|
2
|
+
|
|
3
|
+
**English** · [한국어](index.ko.md)
|
|
4
|
+
|
|
5
|
+
Every key of `polydeukes.config.yaml`, one section per key. The guide — what the file is,
|
|
6
|
+
how discovery fails, and the IDE wiring — is
|
|
7
|
+
[Configuring Polydeukes](../../how-to/configure-project.md), and what a verdict looks like when a
|
|
8
|
+
discipline fires is its
|
|
9
|
+
[What enforcement looks like](../../how-to/configure-project.md#choose-advise-or-block)
|
|
10
|
+
section.
|
|
11
|
+
|
|
12
|
+
<a id="languages"></a>
|
|
13
|
+
## `languages`
|
|
14
|
+
|
|
15
|
+
Required. The language axis, first-class. Keys are your values (`typescript`, `python`, …) —
|
|
16
|
+
the core ships no language names and never interprets the command string.
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
languages:
|
|
20
|
+
typescript:
|
|
21
|
+
productionGlob: 'packages/*/src/**/*.ts' # what counts as production source
|
|
22
|
+
testCmd: 'pnpm --filter {scope} test' # {scope} is substituted at resolve time
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`testCmd` is a template string, not a function. Every `{scope}` occurrence is replaced;
|
|
26
|
+
all other braces (`${VAR}`, `{a,b}`, `awk '{print}'`) pass through untouched. A command
|
|
27
|
+
that ignores scope (`pnpm test`) is equally valid.
|
|
28
|
+
|
|
29
|
+
<a id="protectedpaths"></a>
|
|
30
|
+
## `protectedPaths`
|
|
31
|
+
|
|
32
|
+
Optional. Raw path patterns whose files the covenants protect from modification — by
|
|
33
|
+
editor tools and by shell commands alike (`sed -i`, `tee`, redirects, heredocs,
|
|
34
|
+
parent-directory moves). Entries are normalized (trimmed, deduplicated) at resolve time.
|
|
35
|
+
An empty-string entry is rejected at load time — it carries no path meaning.
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
protectedPaths:
|
|
39
|
+
- 'packages/core/src'
|
|
40
|
+
- '.claude/hooks'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**The config file protects itself.** The discovered config file is automatically appended
|
|
44
|
+
to `protectedPaths` — an edit that would lower your own gates goes through the same judge
|
|
45
|
+
as everything else. If the file that declares the disciplines were not itself under the
|
|
46
|
+
disciplines, the whole chain would be decoration.
|
|
47
|
+
|
|
48
|
+
<a id="adapters"></a>
|
|
49
|
+
## `adapters`
|
|
50
|
+
|
|
51
|
+
Optional. Adapter namespaces. One config file, one namespace per adapter: each key names an
|
|
52
|
+
adapter, and its value is that adapter's own settings object. The core validates the
|
|
53
|
+
container shape only — the keys and the contents belong to each adapter, which ships
|
|
54
|
+
its own validator for its own vocabulary. An unknown key *inside* a namespace is
|
|
55
|
+
rejected by that adapter's validator, with the full field path in the error.
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
adapters:
|
|
59
|
+
git:
|
|
60
|
+
enforce: advise
|
|
61
|
+
protectedPaths:
|
|
62
|
+
- 'packages/core/src'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
<a id="adapters-git"></a>
|
|
66
|
+
### `adapters.git` — the git commit adapter
|
|
67
|
+
|
|
68
|
+
| Key | Values | Default | Meaning |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| `enforce` | `block` \| `advise` | `block` | Enforcement level of the commit surface |
|
|
71
|
+
| `protectedPaths` | string array | `[]` | Additive protection scope judged by the commit surface only |
|
|
72
|
+
|
|
73
|
+
- **`block`** — a staged change that breaks a covenant judged at block level blocks the
|
|
74
|
+
commit (exit 2): a protected path, or an entry promoted with `enforce: block`. An ordinary
|
|
75
|
+
entry keeps its own default `advise` under this setting (see `enforce` below). The
|
|
76
|
+
only way through is the witness valve: a human answering the TTY prompt with the full
|
|
77
|
+
token. The prompt names what it asks the human to witness — the broken registration,
|
|
78
|
+
the matched entry, and the fact that the one answer covers the whole commit. An absent
|
|
79
|
+
namespace, an absent `adapters` map, or an absent `enforce` key all mean `block` — not
|
|
80
|
+
writing the key selects the strictest level.
|
|
81
|
+
- **`advise`** — the commit surface becomes a backstop without a block: a verdict on a
|
|
82
|
+
staged change is recorded as an `advised` telemetry event and the commit proceeds
|
|
83
|
+
(exit 0) with one advisory line on stderr. No TTY prompt fires. The judgment criteria stay
|
|
84
|
+
the same; violations no longer block. A run that cannot judge (missing or invalid config,
|
|
85
|
+
an unresolvable judge body) still fails closed at exit 2, at either level.
|
|
86
|
+
|
|
87
|
+
**`protectedPaths` here is an additive scope.** The commit surface judges the union of the
|
|
88
|
+
top-level `protectedPaths` and this list — concatenated (common first) and normalized as one,
|
|
89
|
+
so spelling and dedupe rules are identical for both. The session surface never reads it: the
|
|
90
|
+
list exists for paths whose edit is legitimate work during a session but must pass a judged
|
|
91
|
+
checkpoint when it is promoted into repository history — a judgment chain's own sources are
|
|
92
|
+
the canonical tenant. As the enforcement level is the observer's setting, so is the
|
|
93
|
+
additional scope. There is no subtractive vocabulary: a config line can widen a surface's
|
|
94
|
+
scope, never quietly strip one.
|
|
95
|
+
|
|
96
|
+
The session surface (the editor-time hook) has no level setting here. What it blocks is the
|
|
97
|
+
judging chain's own protection — `protectedPaths` mutations and mentions on the tool and
|
|
98
|
+
shell axes, the session transcript, an assembly that cannot judge (missing or invalid
|
|
99
|
+
config, unbuilt judge, unparseable payload, a routing that could not answer) — plus any
|
|
100
|
+
entry promoted with `enforce: block`. Every other discipline entry lands `advised` there.
|
|
101
|
+
|
|
102
|
+
**Declarations that read the session skip on the commit surface when their supply policy is `pass`.**
|
|
103
|
+
A commit has no session to look at, so a declaration whose `sources` bind the transcript — a `precedent`,
|
|
104
|
+
`phase-order`, `turn-locality` or `stated-ground` entry — cannot be judged there; demanding
|
|
105
|
+
evidence a commit cannot carry would block every matching commit with no legitimate way
|
|
106
|
+
through. The declaration's own `supply: { session: 'pass' }` disposes of the absence: when
|
|
107
|
+
its scope matches a staged change it records a `skipped` telemetry event carrying the reason
|
|
108
|
+
token `supply-pass` and lets the commit proceed. The record carries the entry's `id` and the
|
|
109
|
+
change it would have judged, so a gate that did nothing says so in the data — and it appears
|
|
110
|
+
**only when the entry's scope actually matched**. A declaration scoped on the `command`
|
|
111
|
+
source records nothing at all there: a staged diff carries no command line, so no world it
|
|
112
|
+
observes is ever admitted.
|
|
113
|
+
|
|
114
|
+
This is the same disposition the session surface uses whenever it has no transcript to
|
|
115
|
+
read **and** the declaration's `supply` is `pass`. Without that policy a missing source is
|
|
116
|
+
unjudgeable (exit 2), not an automatic skip.
|
|
117
|
+
|
|
118
|
+
<a id="telemetry"></a>
|
|
119
|
+
## `telemetry`
|
|
120
|
+
|
|
121
|
+
Optional.
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
telemetry:
|
|
125
|
+
logPath: '.polydeukes/roi.log' # default when omitted; keep it gitignored
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Every judgment — passed, blocked, witnessed, advised, or skipped — appends one record.
|
|
129
|
+
Telemetry is fail-open by design: a logging failure never changes a verdict. The path
|
|
130
|
+
itself is still validated at load time — an empty or whitespace-only `logPath` is
|
|
131
|
+
rejected.
|
|
132
|
+
|
|
133
|
+
<a id="witness"></a>
|
|
134
|
+
## `witness`
|
|
135
|
+
|
|
136
|
+
Optional.
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
witness:
|
|
140
|
+
token: 'covenant witness' # the phrase a human types in the conversation
|
|
141
|
+
ttlMinutes: 10 # validity window, in minutes, from that message
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The values of the time-boxed human valve, consumed where the covenants are assembled.
|
|
145
|
+
The valve is sudo, not an exemption: the one property a deterministic gate can compute
|
|
146
|
+
about a judgment chain is "is an accountable human present, right now", and the witness
|
|
147
|
+
is that human supplying the pass condition in person. When a covenant blocks a
|
|
148
|
+
legitimate edit, a human types the agreed token into the conversation; blocked judgments
|
|
149
|
+
can be witnessed open for `ttlMinutes` from that message's timestamp, then blocking
|
|
150
|
+
resumes automatically. Both keys are required when the section is present: the token
|
|
151
|
+
must be non-empty after trimming, the window a finite number greater than zero.
|
|
152
|
+
|
|
153
|
+
**The valve stands after the verdict, never instead of it.** The judge body always runs.
|
|
154
|
+
A call that would have passed anyway never consults the valve, so an open window changes
|
|
155
|
+
nothing about clean work — and a `witnessed` telemetry row therefore always names a real
|
|
156
|
+
block a human answered for, never a ritual. Only a judgment that actually blocked can be
|
|
157
|
+
witnessed open.
|
|
158
|
+
|
|
159
|
+
**The token must stand alone on the message's first line.** Invoking the witness is
|
|
160
|
+
distinct from talking about it: a message that quotes, questions, or explains the token
|
|
161
|
+
mid-sentence — or wraps it in backticks — does not open the valve, while a first line
|
|
162
|
+
carrying the token alone does, with any following lines free for the work itself.
|
|
163
|
+
|
|
164
|
+
A message that invokes — the token alone on the first line, the rest free:
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
covenant witness
|
|
168
|
+
|
|
169
|
+
now fix the hook file
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
A message that merely mentions — the valve stays shut:
|
|
173
|
+
|
|
174
|
+
```text
|
|
175
|
+
so when does `covenant witness` expire?
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The token's value is free — any phrase works, and it is never checked for a prefix or a
|
|
179
|
+
command shape. Only its placement is constrained.
|
|
180
|
+
|
|
181
|
+
The token is not a secret — the defense is provenance, not secrecy. A witness counts only
|
|
182
|
+
when the token arrives in a message positively identified as human-typed in the session
|
|
183
|
+
transcript, so an AI that knows the token still cannot forge one. Witnessed judgments are
|
|
184
|
+
recorded as `witnessed`, never silent.
|
|
185
|
+
|
|
186
|
+
<a id="disciplines"></a>
|
|
187
|
+
## `disciplines`
|
|
188
|
+
|
|
189
|
+
Optional. Each entry is one discipline: a practice the team imposes on itself, declared as
|
|
190
|
+
data. A judged entry carries a `declare` block — the one judged form, a declaration whose
|
|
191
|
+
`scope` lives inside the block — an `id` (the telemetry label), and optionally a `why` (the
|
|
192
|
+
reason, which travels with the block message the agent reads) and an `enforce` level. The
|
|
193
|
+
closed key set is `id` · `why` · `enforce` · `declare`; any other key is refused.
|
|
194
|
+
|
|
195
|
+
**`draft` — an unpromoted entry.** The one shape that carries no predicate:
|
|
196
|
+
`{ id, why, draft: true }` and nothing else. A draft registers a practice as prose ahead of
|
|
197
|
+
its promotion — it makes no judgment and no telemetry record on either surface, and
|
|
198
|
+
`pdks explain` shows it as `unpromoted`. `why` is required here (the prose is the entry's
|
|
199
|
+
whole body), and the marker must be the literal `true` — a draft is declared, never
|
|
200
|
+
inferred, so an entry with neither a predicate nor `draft: true` is still a validation
|
|
201
|
+
error, and `draft: false` is rejected as dead data.
|
|
202
|
+
|
|
203
|
+
```yaml
|
|
204
|
+
disciplines:
|
|
205
|
+
- id: 'benchmark-supports-performance-claim'
|
|
206
|
+
why: 'a performance claim must be supported by a fresh benchmark run during judgment.'
|
|
207
|
+
draft: true
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
A bilingual mirror pair is not a draft. The engine already judges that shape as `companion`
|
|
211
|
+
(this repository's `docs-stay-bilingual` entry). Use `draft: true` only for a promise the
|
|
212
|
+
current grammar cannot express.
|
|
213
|
+
|
|
214
|
+
A `why` is never judged — it changes no verdict. It is appended to the break message once a
|
|
215
|
+
verdict has blocked, so whoever reads the block gets the rationale in the same line instead
|
|
216
|
+
of having to open this file. A `why` spanning several lines is folded to spaces: the message
|
|
217
|
+
is one line.
|
|
218
|
+
|
|
219
|
+
**`enforce` — the entry's own level.** Optional on any judged entry: `block` or `advise`.
|
|
220
|
+
**Absent means `advise`.** Under `advise` a break is recorded as an `advised` telemetry
|
|
221
|
+
event and the call proceeds (exit 0), with the break message still written to stderr;
|
|
222
|
+
`block` is the promotion — it pins the entry at block. The entry's level composes with the
|
|
223
|
+
surface's (`adapters.git.enforce` on the commit surface; the session surface has none) and
|
|
224
|
+
the lenient side wins — an `advise` on either axis makes the entry advise, and an explicit
|
|
225
|
+
`block` never raises a surface the observer set to advise. An unjudgeable body (never
|
|
226
|
+
built, or one that cannot be loaded) still blocks whatever the level. A draft carries no
|
|
227
|
+
`enforce`; any
|
|
228
|
+
other value is rejected at load time. `pdks explain` prints the level an entry declares
|
|
229
|
+
(`enforce: block` or `enforce: advise`) on both surfaces and leaves an absent one unmarked;
|
|
230
|
+
the session header states the default.
|
|
231
|
+
|
|
232
|
+
```yaml
|
|
233
|
+
- id: 'hooks-stay-armed'
|
|
234
|
+
why: 'a command that disarms or reroutes the git gate is a gate bypass in itself.'
|
|
235
|
+
enforce: advise
|
|
236
|
+
declare:
|
|
237
|
+
mechanism: 'forbidden-command'
|
|
238
|
+
scope: { source: 'command' }
|
|
239
|
+
extract:
|
|
240
|
+
hits:
|
|
241
|
+
- { op: 'source', of: 'command' }
|
|
242
|
+
- { op: 'lines' }
|
|
243
|
+
- { op: 'matches', re: 'LEFTHOOK=(0|false|no|off)\b|core\.hooksPath' }
|
|
244
|
+
relate:
|
|
245
|
+
- { id: 'gates-armed', relation: { op: 'empty', of: 'hits' }, message: '{value}' }
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Added-direction content is a declaration.** A promise about what an edit *adds* — a
|
|
249
|
+
banned word, a stray `.only`, a citation that resolves nowhere — is written as an
|
|
250
|
+
`added-only` declaration: `pre` and `post` are each cut into lines and keyed by the match,
|
|
251
|
+
`onlyIn` keeps what `post` has and `pre` lacks, and `empty` over that difference is the
|
|
252
|
+
verdict. Existing occurrences are forgiven, so adopting the discipline never blocks a legacy
|
|
253
|
+
codebase. `supply: empty` is what lets a file creation (no `pre`) count as all-added and a
|
|
254
|
+
deletion (no `post`) as adding nothing; the `scope` block replaces `in`/`except` with
|
|
255
|
+
regular expressions over the path.
|
|
256
|
+
|
|
257
|
+
```yaml
|
|
258
|
+
disciplines:
|
|
259
|
+
- id: 'no-focused-tests-in-src'
|
|
260
|
+
why: 'a focused test must not land in shared source.'
|
|
261
|
+
declare:
|
|
262
|
+
mechanism: 'added-only'
|
|
263
|
+
scope: { source: 'target.path', include: ['^src/', '^test/'] }
|
|
264
|
+
supply: { pre: 'empty', post: 'empty' }
|
|
265
|
+
extract:
|
|
266
|
+
before:
|
|
267
|
+
- { op: 'source', of: 'pre' }
|
|
268
|
+
- { op: 'lines' }
|
|
269
|
+
- { op: 'keyByPattern', re: '(\.only\()' }
|
|
270
|
+
after:
|
|
271
|
+
- { op: 'source', of: 'post' }
|
|
272
|
+
- { op: 'lines' }
|
|
273
|
+
- { op: 'keyByPattern', re: '(\.only\()' }
|
|
274
|
+
added:
|
|
275
|
+
- { op: 'onlyIn', of: 'after', notIn: 'before' }
|
|
276
|
+
relate:
|
|
277
|
+
- id: 'nothing-added'
|
|
278
|
+
relation: { op: 'empty', of: 'added' }
|
|
279
|
+
message: 'adds {key}: {value}'
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
The key is the match text, so a line carrying a word the file already has anywhere is
|
|
283
|
+
forgiven. A line carrying two new words surfaces only the first match; the second appears
|
|
284
|
+
on a later judgment **after that first match is removed**. Re-judging the same input yields
|
|
285
|
+
the same first match.
|
|
286
|
+
|
|
287
|
+
**A frozen path is a declaration too.** A file that may be created once and never modified
|
|
288
|
+
or deleted: `pre` present means a modification, `post` absent means a deletion, and either
|
|
289
|
+
breaks. Creating the file, including with empty contents (`post: ''`), passes.
|
|
290
|
+
|
|
291
|
+
```yaml
|
|
292
|
+
- id: 'archived-records-stay-frozen'
|
|
293
|
+
why: 'an archive that can be edited is not an archive.'
|
|
294
|
+
declare:
|
|
295
|
+
mechanism: 'self-absolution-ban'
|
|
296
|
+
scope: { source: 'target.path', include: ['^records/archive/'] }
|
|
297
|
+
supply: { pre: 'empty', post: 'empty' }
|
|
298
|
+
extract:
|
|
299
|
+
prior: [{ op: 'source', of: 'pre' }]
|
|
300
|
+
here: [{ op: 'source', of: 'target.path' }]
|
|
301
|
+
after: [{ op: 'source', of: 'post' }]
|
|
302
|
+
deleted: [{ op: 'onlyIn', of: 'here', notIn: 'after' }]
|
|
303
|
+
touched: [{ op: 'union', of: ['prior', 'deleted'] }]
|
|
304
|
+
relate:
|
|
305
|
+
- { id: 'frozen', relation: { op: 'empty', of: 'touched' }, message: '{value} is frozen' }
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**A command line is a source.** On the session surface a shell call carries its command
|
|
309
|
+
line as the fixed source `command`, and a call that changes no file is still one
|
|
310
|
+
observation — it is judged as a world of its own, with subject `-`. A `forbidden-command`
|
|
311
|
+
declaration reads that source, cuts it into lines, keeps the lines a pattern matches, and
|
|
312
|
+
requires the result to be `empty`. It scopes on `command` so that only shell calls are
|
|
313
|
+
admitted: an Edit carries no command line, and a declaration reading a source its world
|
|
314
|
+
lacks is unjudgeable. A multi-line command is judged line by line, so `^` means the start
|
|
315
|
+
of a line; a pattern that would span a line boundary does not match.
|
|
316
|
+
|
|
317
|
+
```yaml
|
|
318
|
+
- id: 'hooks-stay-armed'
|
|
319
|
+
why: 'a command that disarms or reroutes the git gate is a gate bypass in itself.'
|
|
320
|
+
declare:
|
|
321
|
+
mechanism: 'forbidden-command'
|
|
322
|
+
scope: { source: 'command' }
|
|
323
|
+
extract:
|
|
324
|
+
hits:
|
|
325
|
+
- { op: 'source', of: 'command' }
|
|
326
|
+
- { op: 'lines' }
|
|
327
|
+
- { op: 'matches', re: 'LEFTHOOK=(0|false|no|off)\b|core\.hooksPath' }
|
|
328
|
+
relate:
|
|
329
|
+
- { id: 'gates-armed', relation: { op: 'empty', of: 'hits' }, message: '{value}' }
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**A precedent is a declaration over the session.** Most declarations ask "is this change
|
|
333
|
+
itself bad"; a `precedent` asks whether a required step happened earlier in the session.
|
|
334
|
+
The change is legitimate — what is missing is the procedure in front of it, so what gets
|
|
335
|
+
judged is the session history: `sources: { session: { transcript: true } }` hands the
|
|
336
|
+
declaration the user turns and tool calls as one snapshot, `toolUses` picks the calls,
|
|
337
|
+
`filter` keeps the ones that ran and **succeeded**, `select` reaches the command line, and
|
|
338
|
+
`matches` finds the required one; `nonEmpty` is the verdict. A call the covenant blocked,
|
|
339
|
+
one a human refused, and one that simply failed are not precedent. The pattern is matched
|
|
340
|
+
anywhere in a command line — a line that merely mentions the command counts, a declared
|
|
341
|
+
limit. `supply: { session: 'pass' }` is what makes the commit surface record `skipped`
|
|
342
|
+
instead of blocking every matching commit.
|
|
343
|
+
|
|
344
|
+
```yaml
|
|
345
|
+
- id: 'dependency-needs-npm-view'
|
|
346
|
+
why: 'a dependency version must be measured before it is written.'
|
|
347
|
+
declare:
|
|
348
|
+
mechanism: 'precedent'
|
|
349
|
+
scope: { source: 'target.path', include: ['^(packages/[^/]+/)?package\.json$'] }
|
|
350
|
+
sources: { session: { transcript: true } }
|
|
351
|
+
supply: { session: 'pass' }
|
|
352
|
+
extract:
|
|
353
|
+
npmView:
|
|
354
|
+
- { op: 'source', of: 'session' }
|
|
355
|
+
- { op: 'toolUses', names: ['Bash'] }
|
|
356
|
+
- { op: 'filter', when: [{ field: 'succeeded', eq: true }] }
|
|
357
|
+
- { op: 'select', path: 'args.command' }
|
|
358
|
+
- { op: 'matches', re: '\bnpm view ' }
|
|
359
|
+
relate:
|
|
360
|
+
- { id: 'npm-view', relation: { op: 'nonEmpty', of: 'npmView' }, message: 'no successful npm view precedes this manifest edit' }
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
A tool call is evidence the same way: `toolUses` without `names`, then `field name` and
|
|
364
|
+
`matches` over the tool's name, or `toolUses` with `subagentType` for a spawn of one agent
|
|
365
|
+
kind. The other history mechanisms read the same snapshot — `phase-order` relates two
|
|
366
|
+
spawn ordinals with `ordered`, `turn-locality` keeps the user turns inside a time window
|
|
367
|
+
(`userTexts → ageMs → filter lte`), and `stated-ground` requires a user turn matching a
|
|
368
|
+
pattern; the last two are usually scoped on `command`, so that only the shell call they
|
|
369
|
+
apply to is judged.
|
|
370
|
+
|
|
371
|
+
**A caution on line anchors.** A declaration's `lines` step splits the text first, so `^`
|
|
372
|
+
inside `keyByPattern` or `matches` after it is the start of a line. A pattern that stops
|
|
373
|
+
mid-value — say at the first digit of a version — keys `4.0.5` and `4.0.6` alike, so a bump
|
|
374
|
+
adds nothing to an added-only difference and the discipline silently passes: make the
|
|
375
|
+
pattern span the whole value that can change. Both failure shapes compile, run, and answer
|
|
376
|
+
`passed`, so measure a new entry against a real file and a realistic edit.
|
|
377
|
+
|
|
378
|
+
**Witnesses and precedent evidence are different.** A pattern seeking precedent evidence must
|
|
379
|
+
distinguish the required action from a mere mention. Session evidence lives on the AI's own
|
|
380
|
+
surface, so it is not forgery-proof; the design relies on the least effortful way to satisfy
|
|
381
|
+
the check being to actually run the command, which is the behaviour the discipline exists to
|
|
382
|
+
induce. A pattern alone does not eliminate the possibility of forged evidence. Test both valid
|
|
383
|
+
and violating cases.
|
|
384
|
+
|
|
385
|
+
**`declare` — declaration family.** One judgment written as data, in the algebra grammar
|
|
386
|
+
the core publishes as `algebra-declaration.schema.json`: `judge = relate ∘ extract`. The
|
|
387
|
+
block carries the declaration's `scope`, `sources`, `supply`, `extract`, `relate`, and optional
|
|
388
|
+
`witness`; the entry's `id` is the declaration's name, so the block never carries a
|
|
389
|
+
`discipline` key, and `in`/`except`/`when` are refused — the `scope` block is the scope.
|
|
390
|
+
|
|
391
|
+
```yaml
|
|
392
|
+
- id: 'db-files-only-under-data'
|
|
393
|
+
why: 'a *.db file may exist only under data/'
|
|
394
|
+
declare:
|
|
395
|
+
mechanism: 'naming'
|
|
396
|
+
scope: { source: 'target.path', include: ['\.db$'] }
|
|
397
|
+
extract:
|
|
398
|
+
outside:
|
|
399
|
+
- { op: 'source', of: 'target.path' }
|
|
400
|
+
- { op: 'matches', re: '^(?!data/)' }
|
|
401
|
+
relate:
|
|
402
|
+
- id: 'placed'
|
|
403
|
+
relation: { op: 'empty', of: 'outside' }
|
|
404
|
+
message: '{value} is outside data/'
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
This repository's live config uses the same mechanism with `_docs/knowledge/` as
|
|
408
|
+
`sqlite-only-under-knowledge`.
|
|
409
|
+
|
|
410
|
+
Each observation is judged as one **world** with seven source names: `target.path` (the
|
|
411
|
+
repo-relative path), `pre` and `post` (the file's text on the side the change carries —
|
|
412
|
+
a creation has no `pre`, a deletion no `post`), `state` (`{ pre, post }`, present only
|
|
413
|
+
on a modification), and `changes` (every path the observation changes — the one call on the
|
|
414
|
+
session surface, the whole staged set on the commit surface), and `command` (the shell
|
|
415
|
+
call's command line — present on a shell call only, and a shell call that changes no file
|
|
416
|
+
is one world of its own, so a declaration scoped on `command` sees it while one scoped on
|
|
417
|
+
`target.path` does not), and `actor` (the observation's actor — described below). A
|
|
418
|
+
declaration that reads `changes` is judged only where the whole change set is observed: the
|
|
419
|
+
session surface records it `skipped`, the same disposition the commit surface gives a
|
|
420
|
+
declaration that reads the session, because one call can never carry the other half of a
|
|
421
|
+
pair. This repository's live config
|
|
422
|
+
carries one — `docs-stay-bilingual`, an `implies` over the `.md`/`.ko.md` pair, advised
|
|
423
|
+
on the commit surface when one side is staged without the other. A declaration that needs a
|
|
424
|
+
file outside the target names it in a `sources` block, `sources: { en: { file:
|
|
425
|
+
'locales/en.json' } }`, and reads it as `{ op: 'source', of: 'en' }`; the path is
|
|
426
|
+
repo-relative (no leading `/`, no `..` segment) and the name may not be one of the seven. The
|
|
427
|
+
surface reads the file the way it observes the tree — the disk in a session, the index for a
|
|
428
|
+
staged commit, the `<to>` commit for a range — except that a named file the change itself
|
|
429
|
+
touches is read from the change's `post`, so both surfaces judge the same text. A second
|
|
430
|
+
kind, `sources: { spawns: { sidecar: true } }`, names the session's spawn-record channel
|
|
431
|
+
instead of a path — the subagent records the host keeps beside the transcript, supplied as
|
|
432
|
+
one JSON array; where the channel lives is the surface's fact, so the value is the marker
|
|
433
|
+
`true`, and on the commit surface (which has no session) the channel is always absent. A
|
|
434
|
+
third kind, `sources: { session: { transcript: true } }`, names the session's own
|
|
435
|
+
conversation history — the user turns and tool calls the surface reads, handed to the
|
|
436
|
+
declaration as one snapshot whose entries carry their
|
|
437
|
+
observation ordinal; the history steps (`toolUses`, `userTexts`, `first`, `ageMs`) read it, and
|
|
438
|
+
`agentType` reads the parsed sidecar. This repository's live config carries one —
|
|
439
|
+
`tests-before-implementation`, an `ordered` over the ordinals of two subagent spawns, which
|
|
440
|
+
the commit surface (no session) records `skipped`. The seventh fixed name, `actor`, is the
|
|
441
|
+
observation's actor — `{ agentType }` inside a subagent, `{}` in the main session, absent
|
|
442
|
+
where the surface proves none (the commit surface) — read as `{ op: 'source', of: 'actor' }`
|
|
443
|
+
followed by `select` on `agentType`; it derives the `actor` axis the `producer-owned` and
|
|
444
|
+
`actor-scope` mechanisms require, and this repository's live config carries one of each
|
|
445
|
+
(`tests-are-the-writers`, `commits-come-from-the-main-session`). A `supply` key must name
|
|
446
|
+
one of the seven fixed sources or one of the declaration's own `sources`; any other key is
|
|
447
|
+
refused. A source the change does not carry is absent, and the declaration's
|
|
448
|
+
`supply` block says what that means: `error` (the default) makes the call unjudgeable —
|
|
449
|
+
recorded `blocked` at either enforce level — `pass` leaves it unjudged, and `empty` reads
|
|
450
|
+
the absent side as an empty item list and judges on. `empty` is what lets an added-only
|
|
451
|
+
declaration see a creation as all-added and a deletion as adding nothing; it does not apply
|
|
452
|
+
to `state`, the paired source. A declaration comparing before with after therefore needs
|
|
453
|
+
`supply: { state: pass }` to let a file creation through.
|
|
454
|
+
|
|
455
|
+
A break is recorded like any other family's, with one addition: the telemetry row carries a
|
|
456
|
+
fifth field naming the elements the relation failed on (at most eight per relate entry, with
|
|
457
|
+
the true count beside them). A `skipped` row uses the same field for a reason token instead —
|
|
458
|
+
`no-observation` (the surface has no channel for what the entry reads), `config-fault` (the
|
|
459
|
+
block could not be assembled), or `supply-pass` (the declaration's own `supply: pass` let an
|
|
460
|
+
absent source through). Every declaration also names its `mechanism` — one of eighteen
|
|
461
|
+
catalogue names such as `naming`, `companion`, or `pairing` — and the validator refuses a
|
|
462
|
+
name whose shape the declaration does not match: the axes its sources derive (`change` for
|
|
463
|
+
the fixed names other than `actor`, `actor` for `actor`, `world` for a `file` or `sidecar`
|
|
464
|
+
source, `history` for a `transcript` source) and the relations it relates must fall inside
|
|
465
|
+
what that name admits. A block the compiler cannot resolve — a step name outside the
|
|
466
|
+
registry, an argument outside a step's keys — becomes a skip registration that names its
|
|
467
|
+
location on stderr and routes nothing. A shell write into the declaration's scope whose
|
|
468
|
+
result the judge can compute (a redirect, a heredoc, an append) is judged as the file change
|
|
469
|
+
it makes; one it cannot compute (`sed -i`, an opaque command) records `skipped`. The
|
|
470
|
+
declaration's own `witness` block joins the
|
|
471
|
+
human's witness as a second way to open a blocked verdict.
|
|
472
|
+
|
|
473
|
+
Adding a discipline is a data edit — no code, no plumbing. Custom judge bodies remain the
|
|
474
|
+
escape layer for the few rules data cannot express.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# `@polydeukes/adapter-claude-code`
|
|
2
|
+
|
|
3
|
+
[English](adapter-claude-code.md) · **한국어**
|
|
4
|
+
|
|
5
|
+
> **세션 표면의 번역기**입니다. PreToolUse 페이로드가 약속(covenant) 입력 IR이 되고,
|
|
6
|
+
> 판정기가 읽는 파일 변경 증거와 대화 기록(transcript) 통로가 함께 실립니다.
|
|
7
|
+
>
|
|
8
|
+
> 알파입니다. 통합 패키지의 의존성으로 설치되므로 일반 사용자가 따로 설치하거나 불러올 필요는 없습니다. 세션 표면은
|
|
9
|
+
> [`polydeukes/claude-code`](polydeukes.ko.md#session-export)를 통해 닿습니다.
|
|
10
|
+
|
|
11
|
+
<a id="ownership"></a>
|
|
12
|
+
## 담당하는 기능
|
|
13
|
+
|
|
14
|
+
Claude Code의 입력을 공통 형식으로 번역합니다. 에이전트와 도구의 구체적인 이름은 이 패키지에서 처리하고 코어에는 넣지 않습니다. 코어가 특정 에이전트에 의존하지 않는다는
|
|
15
|
+
제약은 테스트로 확인합니다.
|
|
16
|
+
|
|
17
|
+
| 단위 | 하는 일 |
|
|
18
|
+
|---|---|
|
|
19
|
+
| 페이로드 상향 번역 | 원본 PreToolUse 페이로드가 `CovenantInput`이 됩니다 |
|
|
20
|
+
| 예상 변경 후 상태 | 편집이 적용되면 파일이 무엇을 담을지 디스크를 건드리지 않고 계산합니다 |
|
|
21
|
+
| 파일 변경 증거 | 디스크의 변경 전 상태와 예상 변경 후 상태를 짝지어 변경 증거를 만듭니다 |
|
|
22
|
+
| 대화 기록(transcript) 제공자 | 세션 JSONL 파일을 `CanonicalTranscript`로 바꿉니다 |
|
|
23
|
+
| 텔레메트리 배선 | 전체 처리 경로에서 호출이 기록되도록 연결합니다 |
|
|
24
|
+
|
|
25
|
+
이 패키지는 약속(covenant) 패키지를 결코 불러오지 않습니다. 판정 전달 함수는 통합 패키지가
|
|
26
|
+
**주입**하고, 그래서 의존은 코어를 통해 한 방향으로만 흐릅니다. `@polydeukes/core`는
|
|
27
|
+
`peerDependency`로 부릅니다. 판정기와 같은 어휘를 공유하며 별도 사본을 설치하지 않습니다.
|
|
28
|
+
|
|
29
|
+
<a id="translation"></a>
|
|
30
|
+
## 페이로드 번역과 세 축
|
|
31
|
+
|
|
32
|
+
**판정기에 증거를 전달하는 축은 셋**이며, 축마다 제공할 수 있는 증거가 다릅니다.
|
|
33
|
+
|
|
34
|
+
| 축 | 싣는 것 | 결과 |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| 도구 | 증명된 `fileChange`입니다. 도구가 실행되기 전에 계산된 변형 대상입니다 | 증명된 대상만 판정합니다. 편집 **내용** 안의 보호 경로는 언급이고 통과합니다 |
|
|
37
|
+
| 셸 | 실행 전에는 대상을 계산할 수 없는 경우가 많은 명령줄입니다 | 계산되는 쓰기는 편집처럼 판정하고, 나머지는 추측하지 않고 기록합니다 |
|
|
38
|
+
| 대화 기록 | 세션 자신의 기록입니다 | 전체 경로 등가로 판정하고 보호 조상으로는 보지 않습니다 |
|
|
39
|
+
|
|
40
|
+
번역 과정에서 실패하면 어느 단계에서든 호출을 차단합니다(fail-closed).
|
|
41
|
+
서브에이전트 종류가 지정된 `Task` 호출은 스폰에 대응시킵니다. 분류할 수 없는 페이로드는 번역 **실패**이고, `blocked` 한 행을 기록하고
|
|
42
|
+
종료 `2`를 냅니다. 추측으로 물러서지 않습니다. 봉투 최상위의 `agent_type`은 IR의 `actor`가
|
|
43
|
+
됩니다. 서브에이전트 안에서는 `{ agentType }`, 그 밖에는 `{}`입니다. `tool_input`은 에이전트
|
|
44
|
+
자신의 텍스트이므로 여기서 읽지 않습니다.
|
|
45
|
+
|
|
46
|
+
**변경 후의 증거는 파일을 다시 읽는 대신 계산합니다.** `Edit`, `Write`, `MultiEdit`을
|
|
47
|
+
메모리에서 적용해 예상 변경 후 상태를 구합니다. 다중 편집도 순서대로 적용합니다. 내용을
|
|
48
|
+
확인하는 규율(discipline)은 현재 파일이 아니라 **제안된** 결과를 판정합니다.
|
|
49
|
+
변경 후 상태를 계산할 수 없으면 증거를 만들지 않습니다. 실제 도구도 같은 편집을 거부하기
|
|
50
|
+
때문입니다. 파일을 바꾸지 않는 호출에 변경 증거를 만들어 넣지도 않습니다.
|
|
51
|
+
|
|
52
|
+
**대화 기록은 사람이 친 것으로 확정된 메시지만 받아들입니다.** 증인 밸브가 사람 전용인
|
|
53
|
+
근거가 이것입니다. AI는 자기 증인을 합성할 수 없습니다. 읽기 실패는 빈 대화 기록이 아니라
|
|
54
|
+
`undefined`로 답합니다. 빈 세션은 아직 아무 말도 하지 않은 것이라 판정 대상이고, 읽을 수
|
|
55
|
+
없는 세션은 증거 통로 자체가 없는 것이라 건너뜁니다. 어느 경우에도 이 부재만으로 증인 밸브가 작업을 허용하지는 않습니다.
|
|
56
|
+
|
|
57
|
+
**선행(`precedent`)은 선언이지 어댑터 평가기가 아닙니다.** 이 어댑터는 대화 기록
|
|
58
|
+
스냅샷을 공급합니다. 성공한 `toolUses`를 추출해 대조하는 것은 선언 엔진입니다. 이
|
|
59
|
+
패키지에 별도 선례 평가기는 없습니다. 문법은
|
|
60
|
+
[설정 — 규율](../configuration/index.ko.md#disciplines)에 있습니다.
|
|
61
|
+
|
|
62
|
+
<a id="consumer-contract"></a>
|
|
63
|
+
## 사용자와의 접점
|
|
64
|
+
|
|
65
|
+
- **생성된 훅.** 통합 패키지의 `claude-code` 서브패스를 통해 이 어댑터를 불러옵니다. 패키지를 갱신하면 실행할 구현도 바뀌지만 훅 파일은 그대로입니다.
|
|
66
|
+
|
|
67
|
+
일반 사용자가 직접 불러올 필요는 없으며 별도 설정 네임스페이스도 없습니다.
|
|
68
|
+
|
|
69
|
+
<a id="limits"></a>
|
|
70
|
+
## 선언된 한계
|
|
71
|
+
|
|
72
|
+
- **자식 프로세스의 쓰기는 관측 밖입니다.** 이 표면이 판정하는 것은 **선언된 도구
|
|
73
|
+
호출**입니다. 프로세스를 띄우고 그 프로세스가 파일을 쓰는 명령은, 테스트 러너든 빌드든,
|
|
74
|
+
명령에 대해 판정되지 자식이 한 일에 대해 판정되지 않습니다. 추적되는 파일에 대해서는
|
|
75
|
+
커밋 표면에서 결과를 별도로 관측할 수 있습니다.
|
|
76
|
+
- **증거는 후상태를 계산할 수 있는 곳에만 있습니다.** 변형 도구 넷이 전부 증거를 내고
|
|
77
|
+
노트북도 그렇습니다. `NotebookEdit`는 셀 단위 `modify` 증거를 냅니다. 증거가 없는 것은
|
|
78
|
+
이 어댑터가 해소하지 못하는 페이로드입니다. 읽을 수 없거나 파싱되지 않는 노트북, 이름을
|
|
79
|
+
붙일 수 없는 셀, 알지 못하는 편집 모드가 그렇습니다.
|
|
80
|
+
- **증거 없는 호출은 보수적으로 판정합니다.** 증명된 대상이 없으므로 호출 인자에 보호
|
|
81
|
+
경로가 언급됐는지 대조합니다.
|
|
82
|
+
- **저장소 밖 조상은 범위 밖입니다.** 프로젝트 루트 위의 경로는 여기서 관측하지 않습니다.
|
|
83
|
+
그 범위는 에이전트 실행 환경의 권한 정책으로 제한해야 합니다.
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# `@polydeukes/adapter-claude-code`
|
|
2
2
|
|
|
3
|
-
**English** · [한국어](
|
|
3
|
+
**English** · [한국어](adapter-claude-code.ko.md)
|
|
4
4
|
|
|
5
5
|
> **The session surface's translator** — PreToolUse payloads become the covenant input IR,
|
|
6
6
|
> with the file-change evidence and the transcript channel the judge reads.
|
|
7
7
|
>
|
|
8
8
|
> Alpha. A transitive dependency of the umbrella: you do not install it and you do not
|
|
9
9
|
> import it. The session surface reaches it through
|
|
10
|
-
> [`polydeukes/claude-code`](
|
|
10
|
+
> [`polydeukes/claude-code`](polydeukes.md#session-export).
|
|
11
11
|
|
|
12
|
+
<a id="ownership"></a>
|
|
12
13
|
## What this package owns
|
|
13
14
|
|
|
14
15
|
The boundary where Claude Code's vocabulary is translated away. Agent and tool literals
|
|
@@ -21,12 +22,14 @@ agent-neutrality a claim a test can check rather than a slogan.
|
|
|
21
22
|
| Virtual post-state | Computes what a file *would* contain after an edit applies, without touching disk |
|
|
22
23
|
| File-change evidence | Pairs the disk pre-state with the virtual post-state into union evidence |
|
|
23
24
|
| Transcript provider | Turns a session JSONL file into a `CanonicalTranscript` |
|
|
24
|
-
| Precedent evaluator | This adapter's own evidence vocabulary for the context family |
|
|
25
25
|
| Telemetry wiring | Drives the full funnel so exactly one row lands per call |
|
|
26
26
|
|
|
27
27
|
This package never imports the covenant package. The dispatch seam is *injected* by the
|
|
28
|
-
umbrella, which keeps dependencies one-way, through the core alone.
|
|
28
|
+
umbrella, which keeps dependencies one-way, through the core alone. It names
|
|
29
|
+
`@polydeukes/core` as a `peerDependency`: the vocabulary is shared with the judge, not
|
|
30
|
+
installed a second time here.
|
|
29
31
|
|
|
32
|
+
<a id="translation"></a>
|
|
30
33
|
## Payload translation and the three axes
|
|
31
34
|
|
|
32
35
|
**Three axes reach the judge**, and they differ in what evidence they can carry.
|
|
@@ -39,7 +42,9 @@ umbrella, which keeps dependencies one-way, through the core alone.
|
|
|
39
42
|
|
|
40
43
|
Translation is fail-closed at every step. A `Task` call carrying a subagent type maps to a
|
|
41
44
|
spawn; a payload that cannot be classified is a translation *failure* that logs one
|
|
42
|
-
`blocked` record and exits `2`, rather than degrading into a guess.
|
|
45
|
+
`blocked` record and exits `2`, rather than degrading into a guess. The envelope's top-level
|
|
46
|
+
`agent_type` becomes the IR's `actor` — `{ agentType }` inside a subagent, `{}` otherwise;
|
|
47
|
+
`tool_input` is never read for it, since that is the agent's own text.
|
|
43
48
|
|
|
44
49
|
**Evidence is computed, never read back.** The virtual post-state applies `Edit`, `Write`,
|
|
45
50
|
and `MultiEdit` in memory — sequential multi-edit application included — so a content-aware
|
|
@@ -53,19 +58,20 @@ witness valve human-only: an AI cannot synthesize its own witness. A read failur
|
|
|
53
58
|
judged, an unreadable one is no evidence channel at all and is skipped. Either way the
|
|
54
59
|
valve turns off, never open.
|
|
55
60
|
|
|
56
|
-
**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
**Precedent is a declaration, not an adapter evaluator.** This adapter supplies the
|
|
62
|
+
transcript snapshot. The declaration engine extracts succeeded `toolUses` and matches them;
|
|
63
|
+
there is no separate precedent evaluator in this package. The grammar is in
|
|
64
|
+
[Configuration — disciplines](../configuration/index.md#disciplines).
|
|
60
65
|
|
|
66
|
+
<a id="consumer-contract"></a>
|
|
61
67
|
## Where the consumer touches it
|
|
62
68
|
|
|
63
69
|
- **The generated hook**, which loads this adapter through the umbrella's `claude-code`
|
|
64
70
|
subpath. Upgrading the package upgrades what runs; the hook file itself never changes.
|
|
65
|
-
- **`requirePrecedent` entries** using the `subagent` or `tool` evidence keys.
|
|
66
71
|
|
|
67
72
|
No import, and no configuration namespace of its own.
|
|
68
73
|
|
|
74
|
+
<a id="limits"></a>
|
|
69
75
|
## Declared limits
|
|
70
76
|
|
|
71
77
|
- **A child process's writes are outside observation.** This surface judges *declared tool
|