polydeukes 0.6.0 → 0.7.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 +30 -70
- package/README.md +31 -84
- package/dist/baseline.d.ts +37 -1
- package/dist/baseline.js +68 -1
- package/dist/bin.d.ts +3 -4
- package/dist/bin.js +79 -93
- package/dist/covenant/bash-line.d.ts +130 -0
- package/dist/covenant/bash-line.js +566 -0
- package/dist/covenant/declaration-engine.d.ts +108 -0
- package/dist/covenant/declaration-engine.js +415 -0
- package/dist/covenant/discipline.d.ts +103 -0
- package/dist/covenant/discipline.js +667 -0
- package/dist/covenant/dispatch.d.ts +153 -0
- package/dist/covenant/dispatch.js +237 -0
- package/dist/covenant/extract-steps.d.ts +122 -0
- package/dist/covenant/extract-steps.js +0 -0
- package/dist/covenant/mention.d.ts +117 -0
- package/dist/covenant/mention.js +209 -0
- package/dist/covenant/module.d.ts +25 -0
- package/dist/covenant/module.js +23 -0
- package/dist/covenant/mutation-rules.d.ts +40 -0
- package/dist/covenant/mutation-rules.js +146 -0
- package/dist/covenant/relations.d.ts +46 -0
- package/dist/covenant/relations.js +68 -0
- package/dist/covenant/run-covenant.d.ts +95 -0
- package/dist/covenant/run-covenant.js +164 -0
- package/dist/covenant/self-mod.d.ts +55 -0
- package/dist/covenant/self-mod.js +84 -0
- package/dist/covenant/shell-evidence.d.ts +38 -0
- package/dist/covenant/shell-evidence.js +247 -0
- package/dist/covenant/shell-mod.d.ts +84 -0
- package/dist/covenant/shell-mod.js +213 -0
- package/dist/covenant/supply.d.ts +54 -0
- package/dist/covenant/supply.js +50 -0
- package/dist/covenant/transcript-mod.d.ts +60 -0
- package/dist/covenant/transcript-mod.js +254 -0
- package/dist/covenant/ttl-witness.d.ts +47 -0
- package/dist/covenant/ttl-witness.js +80 -0
- package/dist/covenant-check.d.ts +72 -39
- package/dist/covenant-check.js +260 -121
- package/dist/diff-ir.d.ts +28 -0
- package/dist/diff-ir.js +310 -0
- package/dist/docs/README.ko.md +60 -0
- package/dist/docs/README.md +64 -0
- package/dist/docs/catalog.json +480 -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 +98 -0
- package/dist/docs/how-to/connect-surfaces.ko.md +120 -0
- package/dist/docs/how-to/connect-surfaces.md +121 -0
- package/dist/docs/how-to/write-disciplines.ko.md +179 -0
- package/dist/docs/how-to/write-disciplines.md +181 -0
- package/dist/docs/index.json +2280 -0
- package/dist/docs/reference/cli/covenant-check.ko.md +148 -0
- package/dist/docs/reference/cli/covenant-check.md +153 -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 +84 -0
- package/dist/docs/reference/cli/explain.md +89 -0
- package/dist/docs/reference/cli/init.ko.md +119 -0
- package/dist/docs/reference/cli/init.md +119 -0
- package/dist/docs/reference/configuration/index.ko.md +553 -0
- package/dist/docs/reference/{configuration.md → configuration/index.md} +215 -90
- package/dist/docs/reference/packages/adapter-claude-code.ko.md +110 -0
- package/dist/docs/reference/{adapter-claude-code.md → packages/adapter-claude-code.md} +47 -19
- package/dist/docs/reference/packages/adapter-grok.ko.md +77 -0
- package/dist/docs/reference/packages/adapter-grok.md +79 -0
- package/dist/docs/reference/packages/core.ko.md +174 -0
- package/dist/docs/reference/{core.md → packages/core.md} +74 -12
- package/dist/docs/reference/packages/polydeukes.ko.md +195 -0
- package/dist/docs/reference/packages/polydeukes.md +204 -0
- package/dist/docs/reference/packages/sdk-ts.ko.md +161 -0
- package/dist/docs/reference/packages/sdk-ts.md +164 -0
- package/dist/docs/troubleshooting.ko.md +144 -0
- package/dist/docs/troubleshooting.md +103 -151
- 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 +1 -1
- package/dist/explain.js +37 -22
- package/dist/load-config.d.ts +1 -1
- package/dist/load-config.js +1 -1
- package/dist/pre-state-reader.d.ts +1 -1
- package/dist/pre-state-reader.js +1 -1
- package/dist/scaffold-project.d.ts +2 -2
- package/dist/scaffold-project.js +9 -3
- package/dist/schema/polydeukes.schema.json +12 -0
- package/dist/worktree-reader.d.ts +19 -0
- package/dist/worktree-reader.js +30 -0
- package/package.json +3 -17
- package/dist/claude-code-hook.d.ts +0 -79
- package/dist/claude-code-hook.js +0 -372
- package/dist/claude-code.d.ts +0 -6
- package/dist/claude-code.js +0 -6
- package/dist/covenant-module.d.ts +0 -25
- package/dist/covenant-module.js +0 -42
- package/dist/docs/configuration.md +0 -103
- package/dist/docs/installation.md +0 -241
- package/dist/docs/reference/adapter-git.md +0 -100
- package/dist/docs/reference/covenant.md +0 -116
- package/dist/docs/reference/polydeukes.md +0 -315
- package/dist/index.d.ts +0 -22
- package/dist/index.js +0 -21
- package/dist/init-claude-code.d.ts +0 -52
- package/dist/init-claude-code.js +0 -469
- package/dist/init-grok.d.ts +0 -51
- package/dist/init-grok.js +0 -242
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Configuration reference
|
|
2
2
|
|
|
3
|
-
**English** · [한국어](
|
|
3
|
+
**English** · [한국어](index.ko.md)
|
|
4
4
|
|
|
5
5
|
Every key of `polydeukes.config.yaml`, one section per key. The guide — what the file is,
|
|
6
6
|
how discovery fails, and the IDE wiring — is
|
|
7
|
-
[Configuring Polydeukes](
|
|
7
|
+
[Configuring Polydeukes](../../how-to/configure-project.md), and what a verdict looks like when a
|
|
8
8
|
discipline fires is its
|
|
9
|
-
[What enforcement looks like](
|
|
9
|
+
[What enforcement looks like](../../how-to/configure-project.md#choose-advise-or-block)
|
|
10
|
+
section.
|
|
10
11
|
|
|
12
|
+
<a id="languages"></a>
|
|
11
13
|
## `languages`
|
|
12
14
|
|
|
13
15
|
Required. The language axis, first-class. Keys are your values (`typescript`, `python`, …) —
|
|
@@ -24,6 +26,7 @@ languages:
|
|
|
24
26
|
all other braces (`${VAR}`, `{a,b}`, `awk '{print}'`) pass through untouched. A command
|
|
25
27
|
that ignores scope (`pnpm test`) is equally valid.
|
|
26
28
|
|
|
29
|
+
<a id="protectedpaths"></a>
|
|
27
30
|
## `protectedPaths`
|
|
28
31
|
|
|
29
32
|
Optional. Raw path patterns whose files the covenants protect from modification — by
|
|
@@ -42,6 +45,7 @@ to `protectedPaths` — an edit that would lower your own gates goes through the
|
|
|
42
45
|
as everything else. If the file that declares the disciplines were not itself under the
|
|
43
46
|
disciplines, the whole chain would be decoration.
|
|
44
47
|
|
|
48
|
+
<a id="adapters"></a>
|
|
45
49
|
## `adapters`
|
|
46
50
|
|
|
47
51
|
Optional. Adapter namespaces. One config file, one namespace per adapter: each key names an
|
|
@@ -52,62 +56,37 @@ rejected by that adapter's validator, with the full field path in the error.
|
|
|
52
56
|
|
|
53
57
|
```yaml
|
|
54
58
|
adapters:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
protectedPaths:
|
|
58
|
-
- 'packages/core/src'
|
|
59
|
+
example:
|
|
60
|
+
someKey: 'a value the example adapter defines'
|
|
59
61
|
```
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
token. The prompt names what it asks the human to witness — the broken registration,
|
|
71
|
-
the matched entry, and the fact that the one answer covers the whole commit. An absent
|
|
72
|
-
namespace, an absent `adapters` map, or an absent `enforce` key all mean `block` — not
|
|
73
|
-
writing the key selects the strictest level.
|
|
74
|
-
- **`advise`** — the commit surface becomes a backstop without a block: a verdict on a
|
|
75
|
-
staged change is recorded as an `advised` telemetry event and the commit proceeds
|
|
76
|
-
(exit 0) with one advisory line on stderr. No TTY prompt fires. Only the verdict is
|
|
77
|
-
relaxed — a run that cannot judge (missing or invalid config, an unresolvable judge
|
|
78
|
-
body) still fails closed at exit 2, at either level.
|
|
79
|
-
|
|
80
|
-
**`protectedPaths` here is an additive scope.** The commit surface judges the union of the
|
|
81
|
-
top-level `protectedPaths` and this list — concatenated (common first) and normalized as one,
|
|
82
|
-
so spelling and dedupe rules are identical for both. The session surface never reads it: the
|
|
83
|
-
list exists for paths whose edit is legitimate work during a session but must pass a judged
|
|
84
|
-
checkpoint when it is promoted into repository history — a judgment chain's own sources are
|
|
85
|
-
the canonical tenant. As the enforcement level is the observer's setting, so is the
|
|
86
|
-
additional scope. There is no subtractive vocabulary: a config line can widen a surface's
|
|
87
|
-
scope, never quietly strip one.
|
|
88
|
-
|
|
89
|
-
The session surface (the editor-time hook) has no level setting here. What it blocks is the
|
|
63
|
+
**`protectedPaths` is one list for both surfaces.** There is no additive change-set-only scope
|
|
64
|
+
and no surface-level enforcement key in the config. The change-set surface judges the same
|
|
65
|
+
normalized list the session surface does. A violation exits 2 on the session surface; on the
|
|
66
|
+
change-set surface it lands `advised` at exit 0 unless the check runs with `--enforce block`. The
|
|
67
|
+
judge emits only that exit code: whether a commit stops is the user's hook wiring, so a
|
|
68
|
+
telemetry row records the verdict, not the commit's fate — a `blocked` row can sit beside a
|
|
69
|
+
commit that proceeded because the wiring ignored the exit code.
|
|
70
|
+
|
|
71
|
+
The session surface (the editor-time hook) has no level setting either. What it blocks is the
|
|
90
72
|
judging chain's own protection — `protectedPaths` mutations and mentions on the tool and
|
|
91
73
|
shell axes, the session transcript, an assembly that cannot judge (missing or invalid
|
|
92
74
|
config, unbuilt judge, unparseable payload, a routing that could not answer) — plus any
|
|
93
75
|
entry promoted with `enforce: block`. Every other discipline entry lands `advised` there.
|
|
94
76
|
|
|
95
|
-
**
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
This is the same disposition the session surface uses whenever it has no transcript to
|
|
108
|
-
read. One rule, both surfaces: evidence that cannot be evaluated is skipped and measured,
|
|
109
|
-
never blocked and never silent.
|
|
77
|
+
**A declaration reaches only the surface that observes its channels.** A transcript reader is
|
|
78
|
+
written in `sessionDisciplines` and is never compiled for a change set; a `changes` reader is
|
|
79
|
+
written in `changeSetDisciplines` and is never compiled for one call. The three lists and the
|
|
80
|
+
rule that places an entry in one of them are in [the three discipline
|
|
81
|
+
lists](#three-lists).
|
|
82
|
+
|
|
83
|
+
A declaration's `supply` policy still governs a channel the surface registers but the host
|
|
84
|
+
does not prove. A host that carries no session (the Grok adapter) leaves a
|
|
85
|
+
`sources: { session: { transcript: true } }` entry's source absent, and `supply: { session: 'pass' }`
|
|
86
|
+
records a `skipped` telemetry event carrying the reason token `supply-pass` and lets the call
|
|
87
|
+
proceed. Without that policy a missing source is unjudgeable (exit 2), not an automatic skip.
|
|
110
88
|
|
|
89
|
+
<a id="telemetry"></a>
|
|
111
90
|
## `telemetry`
|
|
112
91
|
|
|
113
92
|
Optional.
|
|
@@ -122,6 +101,7 @@ Telemetry is fail-open by design: a logging failure never changes a verdict. The
|
|
|
122
101
|
itself is still validated at load time — an empty or whitespace-only `logPath` is
|
|
123
102
|
rejected.
|
|
124
103
|
|
|
104
|
+
<a id="witness"></a>
|
|
125
105
|
## `witness`
|
|
126
106
|
|
|
127
107
|
Optional.
|
|
@@ -174,11 +154,141 @@ when the token arrives in a message positively identified as human-typed in the
|
|
|
174
154
|
transcript, so an AI that knows the token still cannot forge one. Witnessed judgments are
|
|
175
155
|
recorded as `witnessed`, never silent.
|
|
176
156
|
|
|
157
|
+
<a id="three-lists"></a>
|
|
158
|
+
## The three discipline lists
|
|
159
|
+
|
|
160
|
+
A discipline is written in one of three lists, and which one is a fact about the declaration
|
|
161
|
+
rather than a choice the author makes. A declaration names the evidence channels it reads in
|
|
162
|
+
its own syntax, and a surface observes some channels and not others, so the list follows from
|
|
163
|
+
the channels.
|
|
164
|
+
|
|
165
|
+
| List | Judged on | What its declarations read |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `disciplines` | both surfaces | the changed file itself (`target.path` · `pre` · `post` · `state`) and `file` sources — nothing else. Drafts live here too |
|
|
168
|
+
| `sessionDisciplines` | the session surface only | at least one of `command` · `transcript` · the `sidecar` channel · `actor`, and never `changes` |
|
|
169
|
+
| `changeSetDisciplines` | the change-set surface only | `changes`, and no session channel |
|
|
170
|
+
|
|
171
|
+
The session surface is one call a host observed before it ran — an adapter hook, or a program
|
|
172
|
+
calling the SDK with an input IR. It carries the command line, the conversation history, the
|
|
173
|
+
spawn-record channel, and the actor, and it does not carry the finished change set. The
|
|
174
|
+
change-set surface is `pdks covenant check --diff` over a change set some producer finished.
|
|
175
|
+
It carries `changes`, and it carries no command line, no history, and no actor.
|
|
176
|
+
|
|
177
|
+
<a id="channel-to-list"></a>
|
|
178
|
+
### Channel to list
|
|
179
|
+
|
|
180
|
+
Each channel appears in a declaration in a fixed syntactic position, and the loader reads
|
|
181
|
+
those positions to derive the list.
|
|
182
|
+
|
|
183
|
+
| Channel | How it appears in a declaration | List |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| `transcript` | `sources: { session: { transcript: true } }` | `sessionDisciplines` |
|
|
186
|
+
| `channel` | `sources: { spawns: { sidecar: true } }` | `sessionDisciplines` |
|
|
187
|
+
| `command` | `scope: { source: 'command' }`, or `{ op: 'source', of: 'command' }` in any pipeline | `sessionDisciplines` |
|
|
188
|
+
| `actor` | `{ op: 'source', of: 'actor' }` in any pipeline | `sessionDisciplines` |
|
|
189
|
+
| `changes` | `{ op: 'source', of: 'changes' }` in any pipeline | `changeSetDisciplines` |
|
|
190
|
+
| none of the five | the declaration reads the changed file and `file` sources alone | `disciplines` |
|
|
191
|
+
|
|
192
|
+
A `witness` block's own `extract` is walked with the body's, so a valve reading the transcript
|
|
193
|
+
puts its entry in `sessionDisciplines` like any other transcript reader.
|
|
194
|
+
|
|
195
|
+
<a id="placement-rule"></a>
|
|
196
|
+
### The placement rule
|
|
197
|
+
|
|
198
|
+
`disciplines` takes an entry binding none of the five channels, plus every draft.
|
|
199
|
+
`sessionDisciplines` takes an entry binding at least one session channel and not `changes`.
|
|
200
|
+
`changeSetDisciplines` takes an entry binding `changes` and no session channel. Anything else
|
|
201
|
+
is a `ConfigValidationError` at load time, on both surfaces, and the message names the entry,
|
|
202
|
+
the channels it reads, and the list it belongs in:
|
|
203
|
+
|
|
204
|
+
```text
|
|
205
|
+
disciplines[7] ('merge-is-the-users-call') reads transcript, command: it belongs in sessionDisciplines
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Two shapes have no destination to name. A declaration binding both `changes` and a session
|
|
209
|
+
channel is refused outright, because no surface observes both at once:
|
|
210
|
+
|
|
211
|
+
```text
|
|
212
|
+
sessionDisciplines[2] ('pairs-across-a-session') reads transcript, changes: no surface observes both changes and a session channel
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
A draft binds nothing — it carries no declaration — so it belongs in `disciplines`, and a
|
|
216
|
+
draft written in either surface list is refused with `a draft belongs in disciplines`.
|
|
217
|
+
|
|
218
|
+
Ids are unique across all three lists and the three meta-covenant labels (`self-mod` ·
|
|
219
|
+
`shell-mod` · `transcript-mod`): the telemetry label space is one space, and `pdks explain`
|
|
220
|
+
and every label-keyed reader index by the label alone.
|
|
221
|
+
|
|
222
|
+
<a id="lists-in-practice"></a>
|
|
223
|
+
### What each surface compiles
|
|
224
|
+
|
|
225
|
+
`pdks covenant check` reading an input IR on stdin compiles `disciplines` followed by
|
|
226
|
+
`sessionDisciplines`; the same command with `--diff` compiles `disciplines` followed by
|
|
227
|
+
`changeSetDisciplines`. `pdks explain` prints both surfaces with each list's name and count in
|
|
228
|
+
the surface header, so the placement of every entry is readable without running a judgment.
|
|
229
|
+
This repository's live config places 11 judged entries and 1 draft in `disciplines`, 13 in
|
|
230
|
+
`sessionDisciplines`, and 1 (`docs-stay-bilingual`) in `changeSetDisciplines` — 24
|
|
231
|
+
registrations on the session surface and 12 on the change-set surface.
|
|
232
|
+
|
|
233
|
+
```yaml
|
|
234
|
+
disciplines: # both surfaces
|
|
235
|
+
- id: 'covenant-vocabulary'
|
|
236
|
+
declare:
|
|
237
|
+
mechanism: 'added-only'
|
|
238
|
+
scope: { source: 'target.path', include: ['^packages/'] }
|
|
239
|
+
supply: { pre: 'empty', post: 'empty' }
|
|
240
|
+
extract:
|
|
241
|
+
before: [{ op: 'source', of: 'pre' }, { op: 'lines' }]
|
|
242
|
+
after: [{ op: 'source', of: 'post' }, { op: 'lines' }]
|
|
243
|
+
added: [{ op: 'onlyIn', of: 'after', notIn: 'before' }]
|
|
244
|
+
relate:
|
|
245
|
+
- { id: 'nothing-added', relation: { op: 'empty', of: 'added' }, message: 'adds {value}' }
|
|
246
|
+
|
|
247
|
+
sessionDisciplines: # the session surface only — reads command
|
|
248
|
+
- id: 'pnpm-only'
|
|
249
|
+
declare:
|
|
250
|
+
mechanism: 'forbidden-command'
|
|
251
|
+
scope: { source: 'command' }
|
|
252
|
+
extract:
|
|
253
|
+
hits:
|
|
254
|
+
- { op: 'source', of: 'command' }
|
|
255
|
+
- { op: 'lines' }
|
|
256
|
+
- { op: 'matches', re: '\bnpm install\b' }
|
|
257
|
+
relate:
|
|
258
|
+
- { id: 'no-npm', relation: { op: 'empty', of: 'hits' }, message: '{value}' }
|
|
259
|
+
|
|
260
|
+
changeSetDisciplines: # the change-set surface only — reads changes
|
|
261
|
+
- id: 'docs-stay-bilingual'
|
|
262
|
+
declare:
|
|
263
|
+
mechanism: 'companion'
|
|
264
|
+
scope: { source: 'target.path', include: ['\.md$'] }
|
|
265
|
+
extract:
|
|
266
|
+
en:
|
|
267
|
+
- { op: 'source', of: 'target.path' }
|
|
268
|
+
- { op: 'keyByPattern', re: '^(.+?)(?<!\.ko)\.md$' }
|
|
269
|
+
koChanged:
|
|
270
|
+
- { op: 'source', of: 'changes' }
|
|
271
|
+
- { op: 'items' }
|
|
272
|
+
- { op: 'keyByPattern', re: '^(.+)\.ko\.md$' }
|
|
273
|
+
relate:
|
|
274
|
+
- id: 'ko-follows'
|
|
275
|
+
relation: { op: 'implies', of: 'en', requires: 'koChanged' }
|
|
276
|
+
message: '{value} changed without {key}.ko.md'
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Moving an entry between lists is the whole edit: the entry's body is unchanged by the move,
|
|
280
|
+
and the loader is what says whether the new place is the right one.
|
|
281
|
+
|
|
282
|
+
<a id="disciplines"></a>
|
|
177
283
|
## `disciplines`
|
|
178
284
|
|
|
179
285
|
Optional. Each entry is one discipline: a practice the team imposes on itself, declared as
|
|
180
|
-
data.
|
|
181
|
-
|
|
286
|
+
data. Everything in this section applies to an entry in any of the three lists; which list an
|
|
287
|
+
entry is written in is decided by [the placement rule](#placement-rule), and
|
|
288
|
+
`sessionDisciplines` and `changeSetDisciplines` take the same entry shape as `disciplines`
|
|
289
|
+
minus the draft form. A judged entry carries a `declare` block — the one judged form, a
|
|
290
|
+
declaration whose `scope` lives inside the block — an `id` (the telemetry label), and
|
|
291
|
+
optionally a `why` (the
|
|
182
292
|
reason, which travels with the block message the agent reads) and an `enforce` level. The
|
|
183
293
|
closed key set is `id` · `why` · `enforce` · `declare`; any other key is refused.
|
|
184
294
|
|
|
@@ -192,11 +302,15 @@ error, and `draft: false` is rejected as dead data.
|
|
|
192
302
|
|
|
193
303
|
```yaml
|
|
194
304
|
disciplines:
|
|
195
|
-
- id: '
|
|
196
|
-
why: '
|
|
305
|
+
- id: 'benchmark-supports-performance-claim'
|
|
306
|
+
why: 'a performance claim must be supported by a fresh benchmark run during judgment.'
|
|
197
307
|
draft: true
|
|
198
308
|
```
|
|
199
309
|
|
|
310
|
+
A bilingual mirror pair is not a draft. The engine already judges that shape as `companion`
|
|
311
|
+
(this repository's `docs-stay-bilingual` entry). Use `draft: true` only for a promise the
|
|
312
|
+
current grammar cannot express.
|
|
313
|
+
|
|
200
314
|
A `why` is never judged — it changes no verdict. It is appended to the break message once a
|
|
201
315
|
verdict has blocked, so whoever reads the block gets the rationale in the same line instead
|
|
202
316
|
of having to open this file. A `why` spanning several lines is folded to spaces: the message
|
|
@@ -205,10 +319,10 @@ is one line.
|
|
|
205
319
|
**`enforce` — the entry's own level.** Optional on any judged entry: `block` or `advise`.
|
|
206
320
|
**Absent means `advise`.** Under `advise` a break is recorded as an `advised` telemetry
|
|
207
321
|
event and the call proceeds (exit 0), with the break message still written to stderr;
|
|
208
|
-
`block` is the promotion — it pins the entry at block. The
|
|
209
|
-
|
|
210
|
-
the
|
|
211
|
-
|
|
322
|
+
`block` is the promotion — it pins the entry at block. The config carries no surface-level
|
|
323
|
+
enforcement key: absent or `advise` means `advised` on both surfaces, `block` means exit 2 on
|
|
324
|
+
the session surface and on a change-set check run with `--enforce block` (the change-set
|
|
325
|
+
surface's default posture is advise for every verdict). An unjudgeable body (never
|
|
212
326
|
built, or one that cannot be loaded) still blocks whatever the level. A draft carries no
|
|
213
327
|
`enforce`; any
|
|
214
328
|
other value is rejected at load time. `pdks explain` prints the level an entry declares
|
|
@@ -242,21 +356,21 @@ regular expressions over the path.
|
|
|
242
356
|
|
|
243
357
|
```yaml
|
|
244
358
|
disciplines:
|
|
245
|
-
- id: '
|
|
246
|
-
why: '
|
|
359
|
+
- id: 'no-focused-tests-in-src'
|
|
360
|
+
why: 'a focused test must not land in shared source.'
|
|
247
361
|
declare:
|
|
248
362
|
mechanism: 'added-only'
|
|
249
|
-
scope: { source: 'target.path', include: ['^
|
|
363
|
+
scope: { source: 'target.path', include: ['^src/', '^test/'] }
|
|
250
364
|
supply: { pre: 'empty', post: 'empty' }
|
|
251
365
|
extract:
|
|
252
366
|
before:
|
|
253
367
|
- { op: 'source', of: 'pre' }
|
|
254
368
|
- { op: 'lines' }
|
|
255
|
-
- { op: 'keyByPattern', re: '\
|
|
369
|
+
- { op: 'keyByPattern', re: '(\.only\()' }
|
|
256
370
|
after:
|
|
257
371
|
- { op: 'source', of: 'post' }
|
|
258
372
|
- { op: 'lines' }
|
|
259
|
-
- { op: 'keyByPattern', re: '\
|
|
373
|
+
- { op: 'keyByPattern', re: '(\.only\()' }
|
|
260
374
|
added:
|
|
261
375
|
- { op: 'onlyIn', of: 'after', notIn: 'before' }
|
|
262
376
|
relate:
|
|
@@ -266,12 +380,13 @@ disciplines:
|
|
|
266
380
|
```
|
|
267
381
|
|
|
268
382
|
The key is the match text, so a line carrying a word the file already has anywhere is
|
|
269
|
-
forgiven
|
|
270
|
-
|
|
383
|
+
forgiven. A line carrying two new words surfaces only the first match; the second appears
|
|
384
|
+
on a later judgment **after that first match is removed**. Re-judging the same input yields
|
|
385
|
+
the same first match.
|
|
271
386
|
|
|
272
387
|
**A frozen path is a declaration too.** A file that may be created once and never modified
|
|
273
388
|
or deleted: `pre` present means a modification, `post` absent means a deletion, and either
|
|
274
|
-
breaks.
|
|
389
|
+
breaks. Creating the file, including with empty contents (`post: ''`), passes.
|
|
275
390
|
|
|
276
391
|
```yaml
|
|
277
392
|
- id: 'archived-records-stay-frozen'
|
|
@@ -297,7 +412,14 @@ declaration reads that source, cuts it into lines, keeps the lines a pattern mat
|
|
|
297
412
|
requires the result to be `empty`. It scopes on `command` so that only shell calls are
|
|
298
413
|
admitted: an Edit carries no command line, and a declaration reading a source its world
|
|
299
414
|
lacks is unjudgeable. A multi-line command is judged line by line, so `^` means the start
|
|
300
|
-
of a line; a pattern that would span a line boundary does not match.
|
|
415
|
+
of a line; a pattern that would span a line boundary does not match. Heredoc bodies and
|
|
416
|
+
herestring words are not part of the source: bash passes them to the command as stdin
|
|
417
|
+
data rather than executing them, so a banned token quoted inside one is not a match, and
|
|
418
|
+
whether that data becomes a file write is judged by the shell-evidence path instead. Text
|
|
419
|
+
bash expands first stays in the source — a body under an unquoted delimiter that carries
|
|
420
|
+
`$` or a backtick, and a herestring word holding a substitution — as does a line the
|
|
421
|
+
tokenizer could not finish reading. What the receiving command does with its stdin is not
|
|
422
|
+
judged: a script fed to an interpreter is data to bash and a program to the interpreter.
|
|
301
423
|
|
|
302
424
|
```yaml
|
|
303
425
|
- id: 'hooks-stay-armed'
|
|
@@ -323,8 +445,9 @@ declaration the user turns and tool calls as one snapshot, `toolUses` picks the
|
|
|
323
445
|
`matches` finds the required one; `nonEmpty` is the verdict. A call the covenant blocked,
|
|
324
446
|
one a human refused, and one that simply failed are not precedent. The pattern is matched
|
|
325
447
|
anywhere in a command line — a line that merely mentions the command counts, a declared
|
|
326
|
-
limit. `supply: { session: 'pass' }` is what
|
|
327
|
-
|
|
448
|
+
limit. `supply: { session: 'pass' }` is what disposes of an absent session on a host that proves
|
|
449
|
+
none; an entry reading the transcript is written in `sessionDisciplines`, so a change set never
|
|
450
|
+
compiles it at all.
|
|
328
451
|
|
|
329
452
|
```yaml
|
|
330
453
|
- id: 'dependency-needs-npm-view'
|
|
@@ -360,10 +483,12 @@ adds nothing to an added-only difference and the discipline silently passes: mak
|
|
|
360
483
|
pattern span the whole value that can change. Both failure shapes compile, run, and answer
|
|
361
484
|
`passed`, so measure a new entry against a real file and a realistic edit.
|
|
362
485
|
|
|
363
|
-
**
|
|
364
|
-
the
|
|
365
|
-
|
|
366
|
-
behaviour the discipline exists to
|
|
486
|
+
**Witnesses and precedent evidence are different.** A pattern seeking precedent evidence must
|
|
487
|
+
distinguish the required action from a mere mention. Session evidence lives on the AI's own
|
|
488
|
+
surface, so it is not forgery-proof; the design relies on the least effortful way to satisfy
|
|
489
|
+
the check being to actually run the command, which is the behaviour the discipline exists to
|
|
490
|
+
induce. A pattern alone does not eliminate the possibility of forged evidence. Test both valid
|
|
491
|
+
and violating cases.
|
|
367
492
|
|
|
368
493
|
**`declare` — declaration family.** One judgment written as data, in the algebra grammar
|
|
369
494
|
the core publishes as `algebra-declaration.schema.json`: `judge = relate ∘ extract`. The
|
|
@@ -372,37 +497,37 @@ block carries the declaration's `scope`, `sources`, `supply`, `extract`, `relate
|
|
|
372
497
|
`discipline` key, and `in`/`except`/`when` are refused — the `scope` block is the scope.
|
|
373
498
|
|
|
374
499
|
```yaml
|
|
375
|
-
- id: 'db-only-under-
|
|
376
|
-
why: 'a *.db file may exist only under
|
|
500
|
+
- id: 'db-files-only-under-data'
|
|
501
|
+
why: 'a *.db file may exist only under data/'
|
|
377
502
|
declare:
|
|
378
503
|
mechanism: 'naming'
|
|
379
504
|
scope: { source: 'target.path', include: ['\.db$'] }
|
|
380
505
|
extract:
|
|
381
506
|
outside:
|
|
382
507
|
- { op: 'source', of: 'target.path' }
|
|
383
|
-
- { op: 'matches', re: '^(?!
|
|
508
|
+
- { op: 'matches', re: '^(?!data/)' }
|
|
384
509
|
relate:
|
|
385
510
|
- id: 'placed'
|
|
386
511
|
relation: { op: 'empty', of: 'outside' }
|
|
387
|
-
message: '{value} is outside
|
|
512
|
+
message: '{value} is outside data/'
|
|
388
513
|
```
|
|
389
514
|
|
|
390
|
-
This repository's live config
|
|
515
|
+
This repository's live config uses the same mechanism with `_docs/knowledge/` as
|
|
516
|
+
`sqlite-only-under-knowledge`.
|
|
391
517
|
|
|
392
518
|
Each observation is judged as one **world** with seven source names: `target.path` (the
|
|
393
519
|
repo-relative path), `pre` and `post` (the file's text on the side the change carries —
|
|
394
520
|
a creation has no `pre`, a deletion no `post`), `state` (`{ pre, post }`, present only
|
|
395
521
|
on a modification), and `changes` (every path the observation changes — the one call on the
|
|
396
|
-
session surface, the whole staged set on the
|
|
522
|
+
session surface, the whole staged set on the change-set surface), and `command` (the shell
|
|
397
523
|
call's command line — present on a shell call only, and a shell call that changes no file
|
|
398
524
|
is one world of its own, so a declaration scoped on `command` sees it while one scoped on
|
|
399
525
|
`target.path` does not), and `actor` (the observation's actor — described below). A
|
|
400
|
-
declaration that reads `changes` is
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
on the commit surface when one side is staged without the other. A declaration that needs a
|
|
526
|
+
declaration that reads `changes` is written in `changeSetDisciplines` and compiled only where
|
|
527
|
+
the whole change set is observed, because one call never carries the other half of a pair.
|
|
528
|
+
This repository's live config carries one — `docs-stay-bilingual`, an `implies` over the
|
|
529
|
+
`.md`/`.ko.md` pair, advised on the change-set surface when one side is staged without the
|
|
530
|
+
other. A declaration that needs a
|
|
406
531
|
file outside the target names it in a `sources` block, `sources: { en: { file:
|
|
407
532
|
'locales/en.json' } }`, and reads it as `{ op: 'source', of: 'en' }`; the path is
|
|
408
533
|
repo-relative (no leading `/`, no `..` segment) and the name may not be one of the seven. The
|
|
@@ -412,16 +537,16 @@ touches is read from the change's `post`, so both surfaces judge the same text.
|
|
|
412
537
|
kind, `sources: { spawns: { sidecar: true } }`, names the session's spawn-record channel
|
|
413
538
|
instead of a path — the subagent records the host keeps beside the transcript, supplied as
|
|
414
539
|
one JSON array; where the channel lives is the surface's fact, so the value is the marker
|
|
415
|
-
`true`, and on the
|
|
540
|
+
`true`, and on the change-set surface (which has no session) the channel is always absent. A
|
|
416
541
|
third kind, `sources: { session: { transcript: true } }`, names the session's own
|
|
417
542
|
conversation history — the user turns and tool calls the surface reads, handed to the
|
|
418
543
|
declaration as one snapshot whose entries carry their
|
|
419
544
|
observation ordinal; the history steps (`toolUses`, `userTexts`, `first`, `ageMs`) read it, and
|
|
420
545
|
`agentType` reads the parsed sidecar. This repository's live config carries one —
|
|
421
|
-
`tests-before-implementation`, an `ordered` over the ordinals of two subagent spawns,
|
|
422
|
-
|
|
546
|
+
`tests-before-implementation`, an `ordered` over the ordinals of two subagent spawns, written
|
|
547
|
+
in `sessionDisciplines`. The seventh fixed name, `actor`, is the
|
|
423
548
|
observation's actor — `{ agentType }` inside a subagent, `{}` in the main session, absent
|
|
424
|
-
where the surface proves none (the
|
|
549
|
+
where the surface proves none (the change-set surface) — read as `{ op: 'source', of: 'actor' }`
|
|
425
550
|
followed by `select` on `agentType`; it derives the `actor` axis the `producer-owned` and
|
|
426
551
|
`actor-scope` mechanisms require, and this repository's live config carries one of each
|
|
427
552
|
(`tests-are-the-writers`, `commits-come-from-the-main-session`). A `supply` key must name
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# `@polydeukes/adapter-claude-code`
|
|
2
|
+
|
|
3
|
+
[English](adapter-claude-code.md) · **한국어**
|
|
4
|
+
|
|
5
|
+
> **Claude Code의 설치 단위**입니다. PreToolUse 페이로드가 약속(covenant) 입력 IR이 되고,
|
|
6
|
+
> 판정기가 읽는 파일 변경 증거와 대화 기록(transcript) 통로가 함께 실립니다. 세션 표면을
|
|
7
|
+
> 프로젝트에 설치하는 것도 이 패키지가 합니다.
|
|
8
|
+
>
|
|
9
|
+
> 알파입니다. `polydeukes`와 함께 설치하며, `polydeukes`는 이 패키지의 `peerDependency`입니다.
|
|
10
|
+
|
|
11
|
+
<a id="ownership"></a>
|
|
12
|
+
## 담당하는 기능
|
|
13
|
+
|
|
14
|
+
Claude Code의 입력을 공통 형식으로 번역합니다. 에이전트와 도구의 구체적인 이름은 이 패키지에서 처리하고 코어에는 넣지 않습니다. 코어가 특정 에이전트에 의존하지 않는다는
|
|
15
|
+
제약은 테스트로 확인합니다.
|
|
16
|
+
|
|
17
|
+
| 단위 | 하는 일 |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `pdks-claude-code` 실행 파일 | 하위 명령 하나 `pdks-claude-code init`으로 프로젝트에 세션 표면을 등록합니다 |
|
|
20
|
+
| `runHook` | PreToolUse 페이로드 하나를 입력 IR로 바꾸고 판정기를 스폰합니다 |
|
|
21
|
+
| 페이로드 상향 번역 | 원본 PreToolUse 페이로드가 `CovenantInput`이 됩니다 |
|
|
22
|
+
| 예상 변경 후 상태 | 편집이 적용되면 파일이 무엇을 담을지 디스크를 건드리지 않고 계산합니다 |
|
|
23
|
+
| 파일 변경 증거 | 디스크의 변경 전 상태와 예상 변경 후 상태를 짝지어 변경 증거를 만듭니다 |
|
|
24
|
+
| 대화 기록(transcript) 제공자 | 세션 JSONL 파일을 `CanonicalTranscript`로 바꿉니다 |
|
|
25
|
+
|
|
26
|
+
생성된 훅 위임자가 불러오는 것이 `runHook({ repoRoot })`입니다. `tools`와 `session` 증거를
|
|
27
|
+
실은 입력 IR을 만든 뒤 `repoRoot`에서 `pdks covenant check --enforce block`을 스폰하고 그
|
|
28
|
+
자식 프로세스의 종료 코드를 그대로 돌려줍니다. 판정은 그 자식 프로세스가 하며, 이 패키지에는
|
|
29
|
+
판정 코드가 없습니다.
|
|
30
|
+
|
|
31
|
+
**이 패키지는 텔레메트리 행을 쓰지 않습니다.** 스폰 전에 실패하면 그 사실을 한 줄로 만들어
|
|
32
|
+
`pdks`의 표준 입력으로 보내고, `pdks`가 fail-closed 행을 기록합니다. 호출 하나에 행 하나는
|
|
33
|
+
그대로입니다. 판정기를 불러오지도 않습니다. `polydeukes`와 `@polydeukes/core` 모두
|
|
34
|
+
`peerDependencies`이므로 어휘와 판정기를 공유할 뿐 사본을 따로 설치하지 않습니다.
|
|
35
|
+
|
|
36
|
+
<a id="translation"></a>
|
|
37
|
+
## 페이로드 번역과 세 축
|
|
38
|
+
|
|
39
|
+
**판정기에 증거를 전달하는 축은 셋**이며, 축마다 제공할 수 있는 증거가 다릅니다.
|
|
40
|
+
|
|
41
|
+
| 축 | 싣는 것 | 결과 |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| 도구 | 증명된 `fileChange`입니다. 도구가 실행되기 전에 계산된 변형 대상입니다 | 증명된 대상만 판정합니다. 편집 **내용** 안의 보호 경로는 언급이고 통과합니다 |
|
|
44
|
+
| 셸 | 실행 전에는 대상을 계산할 수 없는 경우가 많은 명령줄입니다 | 계산되는 쓰기는 편집처럼 판정하고, 나머지는 추측하지 않고 기록합니다 |
|
|
45
|
+
| 대화 기록 | 세션 자신의 기록입니다 | 전체 경로 등가로 판정하고 보호 조상으로는 보지 않습니다 |
|
|
46
|
+
|
|
47
|
+
번역 과정에서 실패하면 어느 단계에서든 호출을 차단합니다(fail-closed).
|
|
48
|
+
서브에이전트 종류가 지정된 `Task` 호출은 스폰에 대응시킵니다. 분류할 수 없는 페이로드는 번역 **실패**이고, `blocked` 한 행을 기록하고
|
|
49
|
+
종료 `2`를 냅니다. 추측으로 물러서지 않습니다. 봉투 최상위의 `agent_type`은 IR의 `actor`가
|
|
50
|
+
됩니다. 서브에이전트 안에서는 `{ agentType }`, 그 밖에는 `{}`입니다. `tool_input`은 에이전트
|
|
51
|
+
자신의 텍스트이므로 여기서 읽지 않습니다.
|
|
52
|
+
|
|
53
|
+
**변경 후의 증거는 파일을 다시 읽는 대신 계산합니다.** `Edit`, `Write`, `MultiEdit`을
|
|
54
|
+
메모리에서 적용해 예상 변경 후 상태를 구합니다. 다중 편집도 순서대로 적용합니다. 내용을
|
|
55
|
+
확인하는 규율(discipline)은 현재 파일이 아니라 **제안된** 결과를 판정합니다.
|
|
56
|
+
변경 후 상태를 계산할 수 없으면 증거를 만들지 않습니다. 실제 도구도 같은 편집을 거부하기
|
|
57
|
+
때문입니다. 파일을 바꾸지 않는 호출에 변경 증거를 만들어 넣지도 않습니다.
|
|
58
|
+
|
|
59
|
+
**대화 기록은 사람이 친 것으로 확정된 메시지만 받아들입니다.** 증인 밸브가 사람 전용인
|
|
60
|
+
근거가 이것입니다. AI는 자기 증인을 합성할 수 없습니다. 읽기 실패는 빈 대화 기록이 아니라
|
|
61
|
+
`undefined`로 답합니다. 빈 세션은 아직 아무 말도 하지 않은 것이라 판정 대상이고, 읽을 수
|
|
62
|
+
없는 세션은 증거 통로 자체가 없는 것이라 건너뜁니다. 어느 경우에도 이 부재만으로 증인 밸브가 작업을 허용하지는 않습니다.
|
|
63
|
+
|
|
64
|
+
**선행(`precedent`)은 선언이지 어댑터 평가기가 아닙니다.** 이 어댑터는 대화 기록
|
|
65
|
+
스냅샷을 공급합니다. 성공한 `toolUses`를 추출해 대조하는 것은 선언 엔진입니다. 이
|
|
66
|
+
패키지에 별도 선례 평가기는 없습니다. 문법은
|
|
67
|
+
[설정 — 규율](../configuration/index.ko.md#disciplines)에 있습니다.
|
|
68
|
+
|
|
69
|
+
<a id="consumer-contract"></a>
|
|
70
|
+
## 사용자와의 접점
|
|
71
|
+
|
|
72
|
+
프로젝트 루트에서 두 줄이면 Claude Code 세션 표면이 섭니다.
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
npm install --save-dev polydeukes @polydeukes/adapter-claude-code
|
|
76
|
+
npx pdks-claude-code init
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`pdks-claude-code init`은 프로젝트에서 `polydeukes`를 찾고, 에이전트와 무관한 초기 파일을
|
|
80
|
+
만드는 `pdks init`을 스폰한 뒤, Claude Code 산출물 넷을 기존 파일을 덮지 않고 씁니다.
|
|
81
|
+
다시 실행하면 이미 있는 산출물마다 `skipped`로 보고하고 아무것도 덮어쓰지 않습니다.
|
|
82
|
+
산출물 전체 목록은 [`pdks init`](../cli/init.ko.md#init-claude-code)에 있습니다.
|
|
83
|
+
|
|
84
|
+
- **생성된 훅.** 이 패키지의 `runHook`을 불러옵니다. 패키지를 갱신하면 실행할 구현도 바뀌지만
|
|
85
|
+
훅 파일은 그대로입니다.
|
|
86
|
+
|
|
87
|
+
별도 설정 네임스페이스는 없습니다.
|
|
88
|
+
|
|
89
|
+
<a id="limits"></a>
|
|
90
|
+
## 선언된 한계
|
|
91
|
+
|
|
92
|
+
- **자식 프로세스의 쓰기는 관측 밖입니다.** 이 표면이 판정하는 것은 **선언된 도구
|
|
93
|
+
호출**입니다. 프로세스를 띄우고 그 프로세스가 파일을 쓰는 명령은, 테스트 러너든 빌드든,
|
|
94
|
+
명령에 대해 판정되지 자식이 한 일에 대해 판정되지 않습니다. 추적되는 파일에 대해서는
|
|
95
|
+
변경 집합 표면에서 결과를 별도로 관측할 수 있습니다.
|
|
96
|
+
- **증거는 후상태를 계산할 수 있는 곳에만 있습니다.** 변형 도구 넷이 전부 증거를 내고
|
|
97
|
+
노트북도 그렇습니다. `NotebookEdit`는 셀 단위 `modify` 증거를 냅니다. 증거가 없는 것은
|
|
98
|
+
이 어댑터가 해소하지 못하는 페이로드입니다. 읽을 수 없거나 파싱되지 않는 노트북, 이름을
|
|
99
|
+
붙일 수 없는 셀, 알지 못하는 편집 모드가 그렇습니다.
|
|
100
|
+
- **증거 없는 호출은 보수적으로 판정합니다.** 증명된 대상이 없으므로 호출 인자에 보호
|
|
101
|
+
경로가 언급됐는지 대조합니다.
|
|
102
|
+
- **저장소 밖 조상은 범위 밖입니다.** 프로젝트 루트 위의 경로는 여기서 관측하지 않습니다.
|
|
103
|
+
그 범위는 에이전트 실행 환경의 권한 정책으로 제한해야 합니다.
|
|
104
|
+
- **`polydeukes`를 찾지 못하면 행이 남지 않습니다.** 프로젝트에서 우산 패키지를 찾지 못하면
|
|
105
|
+
스폰할 프로세스도, 기록할 로그 경로도 없습니다. 훅은 stderr에 한 줄을 남기고 종료 코드
|
|
106
|
+
`2`를 내며 텔레메트리 로그에는 아무것도 추가되지 않습니다. 스폰 전 실패 가운데 이 경우만
|
|
107
|
+
그렇고, 나머지는 모두 `pdks`에 도달해 행을 남깁니다.
|
|
108
|
+
- **Grok는 `@polydeukes/adapter-grok`로 설치합니다.** 그 패키지가 자기 위임자와 명부를
|
|
109
|
+
씁니다. 두 세션 어댑터를 한 프로젝트에 함께 설치하면 호출마다 판정기가 두 번 실행될 수
|
|
110
|
+
있습니다.
|