polydeukes 0.8.0 → 0.10.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.
Files changed (32) hide show
  1. package/dist/covenant/shell-mod.d.ts +15 -4
  2. package/dist/covenant/shell-mod.js +63 -8
  3. package/dist/covenant/transcript-mod.js +8 -5
  4. package/dist/docs/README.ko.md +43 -18
  5. package/dist/docs/README.md +39 -17
  6. package/dist/docs/catalog.json +50 -162
  7. package/dist/docs/concepts/judgment.ko.md +2 -0
  8. package/dist/docs/concepts/judgment.md +2 -0
  9. package/dist/docs/how-to/connect-surfaces.ko.md +19 -7
  10. package/dist/docs/how-to/connect-surfaces.md +24 -12
  11. package/dist/docs/how-to/write-disciplines.ko.md +68 -31
  12. package/dist/docs/how-to/write-disciplines.md +69 -32
  13. package/dist/docs/index.json +577 -231
  14. package/dist/docs/reference/cli/init.ko.md +10 -7
  15. package/dist/docs/reference/cli/init.md +16 -11
  16. package/dist/docs/reference/configuration/index.ko.md +14 -20
  17. package/dist/docs/reference/configuration/index.md +16 -24
  18. package/dist/docs/reference/declaration-language/index.ko.md +280 -0
  19. package/dist/docs/reference/declaration-language/index.md +277 -0
  20. package/dist/docs/reference/packages/adapter-claude-code.ko.md +1 -1
  21. package/dist/docs/reference/packages/adapter-claude-code.md +1 -1
  22. package/dist/docs/reference/packages/adapter-codex.ko.md +34 -17
  23. package/dist/docs/reference/packages/adapter-codex.md +39 -21
  24. package/dist/docs/reference/packages/core.ko.md +2 -2
  25. package/dist/docs/reference/packages/core.md +2 -2
  26. package/dist/docs/reference/packages/polydeukes.ko.md +2 -2
  27. package/dist/docs/reference/packages/polydeukes.md +3 -3
  28. package/dist/docs/reference/packages/sdk-ts.ko.md +15 -18
  29. package/dist/docs/reference/packages/sdk-ts.md +14 -17
  30. package/dist/docs/troubleshooting.ko.md +18 -10
  31. package/dist/docs/troubleshooting.md +19 -11
  32. package/package.json +2 -2
@@ -97,23 +97,26 @@ npx pdks-codex init
97
97
  - `.codex/hooks/covenant-pretooluse.mjs`
98
98
  - `.codex/hooks.json`
99
99
 
100
- `hooks.json`은 덮어쓰지 않고 병합합니다. 다른 이벤트, 다른 matcher, 설치기가 모르는 키는
101
- 그대로 둡니다. 초기 설정은 기본적으로 `.codex/hooks`를 보호하므로, 이 설치기가 만드는 등록
102
- 파일은 같은 설치가 만든 설정이 지킵니다.
100
+ `hooks.json`에는 `PreToolUse`, `UserPromptSubmit`, `PostToolUse`, `SessionEnd` 항목이
101
+ 생깁니다. 파일은 덮어쓰지 않고 병합합니다. 사용자 항목, 같은 항목의 다른 handler, 다른
102
+ 이벤트, 설치기가 모르는 키는 그대로 둡니다. 초기 설정은 기본적으로 `.codex/hooks`를
103
+ 보호하므로, 이 설치기가 만드는 등록 파일은 같은 설치가 만든 설정이 지킵니다.
103
104
 
104
105
  **훅 승인까지가 설치입니다.** Codex는 훅 정의의 해시로 신뢰를 기록하므로, 생성된 훅은 검토
105
106
  대상으로 표시되고 `/hooks`에서 승인하기 전까지 건너뛰어집니다. 누군가 승인하기 전까지는
106
107
  아무것도 판정되지 않습니다. `init`이 실행할 때마다 바이트가 같은 명령 문자열을 쓰는 이유가
107
108
  이것입니다. 문자열이 바뀌면 다시 승인해야 합니다.
108
109
 
109
- Codex는 모든 파일 편집을 `apply_patch` 하나로 정규화하고, 경로 인자가 아니라 패치 텍스트를
110
+ Codex는 훅에 도달하는 모든 파일 편집을 `apply_patch` 하나로 정규화하고, 경로 인자가 아니라 패치 텍스트를
110
111
  보냅니다. `Edit`과 `Write`는 `.codex/hooks.json`에 적을 수 있는 matcher 별칭이며 도구 이름으로
111
112
  도착하지 않습니다. 패치 하나가 여러 파일을 건드리면 파일마다 IR 원소 하나가 실리고, 그중
112
113
  하나라도 차단되면 호출 전체가 차단됩니다.
113
114
 
114
- Codex에는 대화 기록 채널이 없어서 세션 증인(witness) 밸브가 읽을 사람 메시지 증거가
115
- 없습니다. 의도한 편집이 차단되면 자신의 터미널에서 수행하세요. 산출물별 동작은
116
- [표면 연결하기](../../how-to/connect-surfaces.ko.md#codex)에 있습니다.
115
+ 불안정한 Codex 대화 기록은 해석하지 않습니다. `UserPromptSubmit`은 시각을 붙인 사람
116
+ 메시지를, `PostToolUse`는 완료된 도구 호출을 공급하고, `SessionEnd`는 어댑터 소유 증거
117
+ 파일을 정리합니다. 따라서 설정된 증인 토큰으로 다시 시도한 보호 호출을 허용할 수 있습니다.
118
+ 프롬프트 증거가 기록되지 않았다면 복구 메시지가 안내하는 사용자 터미널을 사용합니다.
119
+ 산출물별 동작은 [표면 연결하기](../../how-to/connect-surfaces.ko.md#codex)에 있습니다.
117
120
 
118
121
  <a id="init-results"></a>
119
122
  ## 결과와 실패 조건
@@ -96,23 +96,28 @@ That command runs `pdks init` for the scaffold, then writes the Codex registrati
96
96
  - `.codex/hooks/covenant-pretooluse.mjs`
97
97
  - `.codex/hooks.json`
98
98
 
99
- `hooks.json` is merged rather than overwritten: other events, other matchers, and keys the
100
- installer does not know are left in place. The scaffold config protects `.codex/hooks` by
101
- default, so the registration this installer writes is covered by the config it writes.
99
+ `hooks.json` receives entries for `PreToolUse`, `UserPromptSubmit`, `PostToolUse`, and
100
+ `SessionEnd`. It is merged rather than overwritten: user entries, sibling handlers, other
101
+ events, and keys the installer does not know are left in place. The scaffold config protects
102
+ `.codex/hooks` by default, so the registration this installer writes is covered by the config
103
+ it writes.
102
104
 
103
105
  **Approving the hook is part of the install.** Codex records trust against the hash of a hook's
104
106
  definition, so the generated hook is listed for review and skipped until you approve it with
105
107
  `/hooks`. Until someone does, nothing is judged. This is why `init` writes a byte-identical
106
108
  command string on every run — a changed string needs approving again.
107
109
 
108
- Codex normalises every file edit into one tool, `apply_patch`, and delivers the patch text
109
- rather than a path argument. `Edit` and `Write` are matcher aliases you may write in
110
- `.codex/hooks.json`; they never arrive as the tool name. One patch that touches several files
111
- carries one IR element per file, and any one of them blocking blocks the whole call.
112
-
113
- Codex supplies no transcript channel, so the session witness valve has no human-message
114
- evidence to read. For an intentional blocked edit, use your own terminal. Details are in
115
- [Connect the surfaces](../../how-to/connect-surfaces.md#codex).
110
+ Codex normalises every file edit that reaches the hook into one tool, `apply_patch`, and
111
+ delivers the patch text rather than a path argument. `Edit` and `Write` are matcher aliases
112
+ you may write in `.codex/hooks.json`; they never arrive as the tool name. One patch that
113
+ touches several files carries one IR element per file, and any one of them blocking blocks
114
+ the whole call.
115
+
116
+ The unstable Codex transcript is never parsed. `UserPromptSubmit` supplies timestamped human
117
+ messages, `PostToolUse` supplies completed tool calls, and `SessionEnd` cleans up the
118
+ adapter-owned evidence file. A configured witness token can therefore release a retried
119
+ protected call. If no prompt evidence was recorded, use the user terminal named by the recovery
120
+ message. Details are in [Connect the surfaces](../../how-to/connect-surfaces.md#codex).
116
121
 
117
122
  <a id="init-results"></a>
118
123
  ## Results and failure conditions
@@ -8,6 +8,8 @@
8
8
  모습인지는 같은 문서의
9
9
  [권고와 차단 중 선택하기](../../how-to/configure-project.ko.md#choose-advise-or-block) 절에 있습니다.
10
10
 
11
+ 관계와 추출 연산의 전체 문법은 [선언 언어 참조](../declaration-language/index.ko.md)에서 확인할 수 있습니다.
12
+
11
13
  <a id="languages"></a>
12
14
  ## `languages`
13
15
 
@@ -107,18 +109,13 @@ witness:
107
109
  ttlMinutes: 10 # 그 메시지 시점부터의 유효 시간(분)
108
110
  ```
109
111
 
110
- 유효 시간이 있는 인간의 증인 밸브를 설정하며, 약속을 조립할 때 사용합니다. 이 밸브는
111
- 면제가 아니라 sudo입니다. 결정론 관문이 판정 사슬에 관해 계산할 수 있는 단 하나의
112
- 성질은 "책임질 인간이 지금 여기 있는가"이고, 증인(witness)은 그 통과 조건을 사람이
113
- 직접 공급하는 자리입니다. covenant가 정당한 편집을 막을 때 사람이 합의된 토큰을 대화에
114
- 입력하면, 그 메시지의 타임스탬프부터 `ttlMinutes` 동안 차단된 판정을 증언으로 통과시킬
115
- 수 있고, 만료되면 자동으로 다시 차단됩니다. 이 섹션을 쓰면 두 키 모두 필수입니다.
116
- 토큰은 공백을 걷어낸 뒤 비어 있을 수 없고, 시간 창은 0보다 큰 유한한 수여야 합니다.
112
+ 사람이 설정된 토큰을 대화에 입력하면 증인(witness) 밸브가 열립니다. 그 메시지의
113
+ 타임스탬프부터 `ttlMinutes` 동안 차단된 판정을 통과시킬 수 있으며, 시간이 지나면 차단이
114
+ 재개됩니다. 이 섹션을 쓰면 두 키 모두 필수입니다. `token`은 공백을 제거한 뒤에도 값이
115
+ 있어야 하고, `ttlMinutes`는 0보다 큰 유한한 수여야 합니다.
117
116
 
118
- **밸브는 판정 뒤에 확인하며 판정을 대신하지 않습니다.** 판정 본체는 언제나 실행됩니다.
119
- 정상 판정을 받을 호출에는 밸브를 적용하지 않으므로 유효 시간 중에도 그 결과는 바뀌지 않습니다.
120
- 따라서 `witnessed` 행은 실제 차단을 사람이 책임지고 허용했다는 뜻입니다.
121
- 차단되지 않은 호출에 형식적인 증언 기록을 남기지는 않습니다.
117
+ 판정기는 밸브를 확인하기 전에 판정을 실행합니다. 차단된 판정만 `witnessed`로 바뀌며,
118
+ 정상 판정과 권고는 기존 결과를 유지합니다.
122
119
 
123
120
  **토큰은 메시지 첫 줄에 단독으로 놓여야 합니다.** 증언을 발동하는 것과 증언을 이야기하는
124
121
  것은 다릅니다. 문장 안에서 토큰을 인용하거나 묻거나 설명하는 메시지는, 백틱으로 감싼
@@ -142,17 +139,14 @@ pdks witness
142
139
  토큰 값 자체는 자유입니다. 어떤 문구든 쓸 수 있고, 접두사나 명령 형태를 검사하지 않습니다.
143
140
  제약을 받는 것은 놓이는 자리뿐입니다.
144
141
 
145
- 토큰은 비밀이 아닙니다. 방어선은 비밀성이 아니라 출처 증명입니다. 세션 기록에서 사람이
146
- 직접 입력했다고 확인된 메시지로 토큰이 도착할 때만 증언이 성립하므로, 토큰을 아는
147
- AI도 증언을 위조할 수 없습니다. 증언으로 통과한 판정은 조용히 사라지지 않고 `witnessed`로
148
- 기록됩니다.
142
+ 어댑터가 토큰을 담은 메시지를 사람의 입력으로 확인해야 밸브가 열립니다.
143
+ 토큰을 아는 것만으로는 증언할 수 없습니다. 밸브로 통과한 판정은 모두 `witnessed`로 기록됩니다.
149
144
 
150
145
  <a id="three-lists"></a>
151
146
  ## 규율 목록 셋
152
147
 
153
- 규율은 세 목록 가운데 하나에 적으며, 어느 목록인지는 저자가 고르는 것이 아니라 선언 자체가
154
- 정하는 사실입니다. 선언은 자기가 읽는 증거 통로(channel)를 문법 안에서 이름 짓고, 표면마다
155
- 관측하는 통로가 다르므로, 목록은 그 통로에서 따라 나옵니다.
148
+ 선언이 읽는 증거 통로(channel)에 따라 목록을 선택합니다. 표면마다 공급하는 통로가 다르므로,
149
+ 해당 통로를 공급할 수 없는 표면의 목록에 항목을 적으면 로더가 거부합니다.
156
150
 
157
151
  | 목록 | 판정하는 표면 | 그 목록의 선언이 읽는 것 |
158
152
  |---|---|---|
@@ -196,8 +190,8 @@ AI도 증언을 위조할 수 없습니다. 증언으로 통과한 판정은 조
196
190
  disciplines[7] ('merge-is-the-users-call') reads transcript, command: it belongs in sessionDisciplines
197
191
  ```
198
192
 
199
- 댈 행선지가 없는 모양이 둘 있습니다. `changes`와 세션 통로를 함께 묶는 선언은 어느 표면도
200
- 그 둘을 한 번에 관측하지 못하므로 그대로 거부됩니다.
193
+ `changes`와 세션 통로를 함께 읽는 선언은 어느 목록에도 넣을 수 없습니다.
194
+ 두 통로를 동시에 관측하는 표면이 없기 때문입니다.
201
195
 
202
196
  ```text
203
197
  sessionDisciplines[2] ('pairs-across-a-session') reads transcript, changes: no surface observes both changes and a session channel
@@ -9,6 +9,8 @@ discipline fires is its
9
9
  [What enforcement looks like](../../how-to/configure-project.md#choose-advise-or-block)
10
10
  section.
11
11
 
12
+ For the complete syntax of relations and extraction steps, see the [Declaration language reference](../declaration-language/index.md).
13
+
12
14
  <a id="languages"></a>
13
15
  ## `languages`
14
16
 
@@ -112,20 +114,13 @@ witness:
112
114
  ttlMinutes: 10 # validity window, in minutes, from that message
113
115
  ```
114
116
 
115
- The values of the time-boxed human valve, consumed where the covenants are assembled.
116
- The valve is sudo, not an exemption: the one property a deterministic gate can compute
117
- about a judgment chain is "is an accountable human present, right now", and the witness
118
- is that human supplying the pass condition in person. When a covenant blocks a
119
- legitimate edit, a human types the agreed token into the conversation; blocked judgments
120
- can be witnessed open for `ttlMinutes` from that message's timestamp, then blocking
121
- resumes automatically. Both keys are required when the section is present: the token
122
- must be non-empty after trimming, the window a finite number greater than zero.
123
-
124
- **The valve stands after the verdict, never instead of it.** The judge body always runs.
125
- A call that would have passed anyway never consults the valve, so an open window changes
126
- nothing about clean work — and a `witnessed` telemetry row therefore always names a real
127
- block a human answered for, never a ritual. Only a judgment that actually blocked can be
128
- witnessed open.
117
+ A human can open the witness valve by typing the configured token in the conversation.
118
+ Blocked judgments may then proceed for `ttlMinutes` from that message's timestamp. After the
119
+ window expires, blocking resumes. Both keys are required when this section is present:
120
+ `token` must be non-empty after trimming, and `ttlMinutes` must be finite and greater than zero.
121
+
122
+ The judge runs before checking the valve. Only a blocked judgment can become `witnessed`;
123
+ passed and advised judgments keep their original result.
129
124
 
130
125
  **The token must stand alone on the message's first line.** Invoking the witness is
131
126
  distinct from talking about it: a message that quotes, questions, or explains the token
@@ -149,18 +144,15 @@ so when does `pdks witness` expire?
149
144
  The token's value is free — any phrase works, and it is never checked for a prefix or a
150
145
  command shape. Only its placement is constrained.
151
146
 
152
- The token is not a secret — the defense is provenance, not secrecy. A witness counts only
153
- when the token arrives in a message positively identified as human-typed in the session
154
- transcript, so an AI that knows the token still cannot forge one. Witnessed judgments are
155
- recorded as `witnessed`, never silent.
147
+ The adapter must identify the token's message as human input before it can open the valve.
148
+ Knowing the token alone is insufficient. Every judgment allowed through the valve is recorded
149
+ as `witnessed`.
156
150
 
157
151
  <a id="three-lists"></a>
158
152
  ## The three discipline lists
159
153
 
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.
154
+ Choose the list according to the evidence channels the declaration reads. Each surface supplies
155
+ different channels; the loader rejects an entry in a list whose surface cannot supply them.
164
156
 
165
157
  | List | Judged on | What its declarations read |
166
158
  |---|---|---|
@@ -205,8 +197,8 @@ the channels it reads, and the list it belongs in:
205
197
  disciplines[7] ('merge-is-the-users-call') reads transcript, command: it belongs in sessionDisciplines
206
198
  ```
207
199
 
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:
200
+ A declaration reading both `changes` and a session channel is invalid in every list,
201
+ because no surface observes both at once:
210
202
 
211
203
  ```text
212
204
  sessionDisciplines[2] ('pairs-across-a-session') reads transcript, changes: no surface observes both changes and a session channel
@@ -0,0 +1,280 @@
1
+ # 선언 언어 참조
2
+
3
+ [English](./index.md) · **한국어**
4
+
5
+ 규율(discipline)의 `declare` 블록을 작성할 때 사용하는 참조 문서입니다. 지원하는 소스,
6
+ 추출 단계, 조합 연산자, 관계, 기전(mechanism)을 모두 표로 정리했습니다. 설치와 실전 예제는
7
+ [규율 작성하기](../../how-to/write-disciplines.ko.md)를, 프로젝트 설정과 강제 수준은
8
+ [설정 참조](../configuration/index.ko.md)를 보세요.
9
+
10
+ <a id="declaration-shape"></a>
11
+ ## 선언 구조
12
+
13
+ 판정 항목에는 `id`와 `declare`가 필요하며, `why`와 `enforce`는 선택입니다.
14
+ 항목은 [소스를 관측할 수 있는 목록](../configuration/index.ko.md#three-lists)에 작성합니다.
15
+
16
+ | `declare` 안의 필드 | 필수 여부 | 의미 |
17
+ |---|---|---|
18
+ | `mechanism` | 필수 | 아래 기전 표의 이름입니다. 사용할 수 있는 증거 축과 관계를 제한합니다. |
19
+ | `scope` | 선택 | 소스 하나에 정규식을 적용해 판정할 관측을 선택합니다. |
20
+ | `sources` | 선택 | 파일이나 세션 증거에 이름을 붙입니다. |
21
+ | `supply` | 선택 | 소스가 없을 때의 처리를 정합니다. 지정하지 않은 소스는 `error`를 사용합니다. |
22
+ | `extract` | 필수 | 추출 이름마다 비어 있지 않은 단계 목록을 지정합니다. |
23
+ | `relate` | 필수 | 비교 조건과 진단 메시지의 목록입니다. 비어 있을 수 없습니다. |
24
+ | `witness` | 선택 | 위반을 허용할 수 있는 추가 비교 조건입니다. |
25
+
26
+ 바깥의 `id`가 규율 이름이므로 `declare` 안에 `discipline`을 다시 쓰지 않습니다.
27
+ 이름과 키는 대소문자를 구분하며, 지원하지 않는 선언 키는 거부됩니다.
28
+
29
+ 다음 항목은 `data/` 밖의 `.db` 경로를 알립니다.
30
+
31
+ ```yaml
32
+ disciplines:
33
+ - id: 'database-location'
34
+ why: '데이터베이스 파일은 data/ 아래에 둡니다.'
35
+ enforce: advise
36
+ declare:
37
+ mechanism: 'naming'
38
+ scope: { source: 'target.path', include: ['\.db$'] }
39
+ extract:
40
+ outside:
41
+ - { op: 'source', of: 'target.path' }
42
+ - { op: 'matches', re: '^(?!data/)' }
43
+ relate:
44
+ - id: 'location'
45
+ relation: { op: 'empty', of: 'outside' }
46
+ message: '{value}는 data/ 아래에 있어야 합니다'
47
+ ```
48
+
49
+ <a id="scope"></a>
50
+ ## 적용 범위
51
+
52
+ | 키 | 의미 |
53
+ |---|---|
54
+ | `source` | 필수입니다. `target.path`, `pre`, `post`, `command` 또는 이름 붙인 `file` 소스 중 하나입니다. |
55
+ | `include` | 정규식 문자열 목록입니다. 하나 이상 일치해야 합니다. 생략하거나 빈 목록이면 모든 문자열을 허용합니다. |
56
+ | `exclude` | 정규식 문자열 목록입니다. 하나라도 일치하면 제외합니다. 생략하거나 빈 목록이면 제외하지 않습니다. |
57
+ | `excludeIgnoreCase` | 선택인 불리언이며 기본값은 `false`입니다. `exclude`에만 적용됩니다. |
58
+
59
+ `scope`가 없으면 모든 관측이 대상입니다. `scope`를 지정했는데 그 소스가 없으면 대상에서
60
+ 제외됩니다. 패턴은 경로 glob이 아닌 정규식이며, `include`는 항상 대소문자를 구분합니다.
61
+
62
+ <a id="fixed-sources"></a>
63
+ ## 고정 소스
64
+
65
+ 각 관측은 확인할 수 있는 소스를 공급합니다. 파일 변경은 각각 판정하므로 `target.path`,
66
+ `pre`, `post`는 현재 판정하는 변경을 가리킵니다.
67
+
68
+ | 소스 | 값 | 공급되는 경우 |
69
+ |---|---|---|
70
+ | `target.path` | 저장소 기준 상대 경로 문자열 | 파일 대상이 있는 관측입니다. |
71
+ | `pre` | 변경 전 파일 텍스트 | 수정, 그리고 이전 텍스트를 읽을 수 있는 삭제입니다. 생성에는 없습니다. |
72
+ | `post` | 제안된 변경 후 파일 텍스트 | 생성과 수정입니다. 삭제에는 없습니다. |
73
+ | `state` | 변경 전후의 쌍 `{ pre, post }` | 수정입니다. 파이프라인을 양쪽에 각각 적용합니다. |
74
+ | `changes` | 관측한 변경 집합의 경로 배열 | `changeSetDisciplines`에서 읽습니다. `items`로 개별 경로를 추출합니다. |
75
+ | `command` | 셸 명령 텍스트 | 파일 대상이 없는 호출을 포함한 세션 셸 호출입니다. 실행되지 않는 stdin 리터럴 데이터는 제외합니다. |
76
+ | `actor` | 선택 필드 `agentType`을 가진 객체 | 주체를 확인할 수 있는 세션 호스트가 공급합니다. 주 세션은 `{}`이며, 호스트가 주체를 공급하지 않으면 소스 자체가 없습니다. |
77
+
78
+ 소스가 없는 상태는 빈 문자열이나 빈 배열이 있는 상태와 다릅니다. 부재 처리는 `supply`로
79
+ 정합니다. `state`는 호출 사이의 작업 진행 상태를 저장하지 않습니다. 쌍인 추출 결과를 받는
80
+ 관계는 `unchanged`뿐입니다. 셸 stdin 처리는
81
+ [명령 소스 예제](../configuration/index.ko.md#disciplines)에서 설명합니다.
82
+
83
+ <a id="source-kinds"></a>
84
+ ## 추가 소스 종류
85
+
86
+ `sources`에서 새 이름마다 바인딩 하나를 지정합니다. 고정 소스 이름을 덮어쓸 수 없습니다.
87
+
88
+ | 종류 | 바인딩 예제 | 공급되는 값 |
89
+ |---|---|---|
90
+ | `file` | `en: { file: 'locales/en.json' }` | 파일 텍스트입니다. 경로는 저장소 기준 상대 경로이며, 앞의 `/`와 `..` 경로 조각은 허용하지 않습니다. |
91
+ | `sidecar` | `spawns: { sidecar: true }` | 호스트의 에이전트 생성 기록을 담은 JSON 텍스트입니다. `agentType`이나 `items` 전에 `json`으로 파싱합니다. |
92
+ | `transcript` | `session: { transcript: true }` | `observedAtMs`, `toolCalls`, `userMessages`를 담은 세션 스냅샷입니다. |
93
+
94
+ 바인딩은 `{ op: 'source', of: 'en' }`으로 읽습니다. 현재 변경하는 파일은 제안된 `post`를
95
+ 사용하고, 나머지 파일은 해당 표면이 관측한 프로젝트 상태에서 읽습니다. `sidecar`와
96
+ `transcript`는 `sessionDisciplines`에 작성하며, 호스트가 그 통로를 공급해야 합니다.
97
+ 두 종류의 표식 값은 리터럴 `true`입니다.
98
+
99
+ <a id="supply-policies"></a>
100
+ ## 공급 정책
101
+
102
+ | 정책 | 소스가 없을 때의 동작 |
103
+ |---|---|
104
+ | `error` | 기본값입니다. 판정할 수 없는 관측으로 처리해 차단합니다. 강제 수준이 `advise`여도 같습니다. |
105
+ | `pass` | 사유 `supply-pass`와 함께 `skipped`를 기록하고 이 선언을 판정하지 않습니다. |
106
+ | `empty` | 빈 항목 목록으로 판정을 계속합니다. 단일 소스에만 쓸 수 있고 `state`에는 쓸 수 없습니다. |
107
+
108
+ `supply`의 키는 고정 소스나 선언에서 바인딩한 소스 이름이어야 합니다. 변경 전후 비교에서
109
+ 생성과 삭제를 건너뛰려면 `supply: { state: 'pass' }`를 씁니다. 새로 추가한 내용만 검사하려면
110
+ `supply: { pre: 'empty', post: 'empty' }`를 씁니다. 잘못된 JSON은 `pass`나 `empty`를
111
+ 지정해도 공급 오류입니다. 두 정책은 소스가 없는 경우에 적용됩니다.
112
+
113
+ <a id="items-and-pipelines"></a>
114
+ ## 항목과 파이프라인
115
+
116
+ 추출 결과는 순서가 있는 `{ key, value }` 항목 목록입니다. 키는 조합 연산과 키 비교에서
117
+ 항목을 식별하며, 값은 값 비교 관계가 대조하는 데이터입니다. 키를 바꿔도 값은 바뀌지 않습니다.
118
+
119
+ 파이프라인은 `source`나 조합 연산자로 시작합니다. 조합 연산자는 다른 추출 이름을 참조하며
120
+ 첫 단계에만 올 수 있습니다. 참조한 추출은 존재해야 하고 순환 참조는 허용하지 않습니다.
121
+ `state`에서 나온 쌍은 조합 연산자의 입력이 될 수 없습니다. 이후 단계는 결과를 차례로 변환합니다.
122
+
123
+ <a id="extract-steps"></a>
124
+ ## 추출 단계
125
+
126
+ 지원하는 단항 단계 17개입니다. 인자 예제에 표시한 키를 사용하며, 지원하지 않는 인자는
127
+ 컴파일 오류가 됩니다. 별도 설명이 없으면 항목 순서를 유지합니다.
128
+
129
+ | 단계 | 인자 | 결과 |
130
+ |---|---|---|
131
+ | `source` | `of: 'post'` 필수 | 해당 소스를 키 `'0'`인 항목 하나로 읽어 시작합니다. `state`는 쌍으로 추출합니다. |
132
+ | `json` | 없음 | 문자열 값을 JSON으로 파싱하고 키는 유지합니다. 잘못된 JSON은 공급 오류입니다. |
133
+ | `select` | `path: 'args.command'` 필수 | 객체의 점 경로를 따라 읽습니다. 없는 경로는 제외합니다. 결과가 배열이면 위치를 키로 원소를 나누고, 단일 값이면 키를 유지합니다. |
134
+ | `items` | 없음 | 각 배열을 한 단계 펼쳐 0부터 시작하는 위치를 키로 부여합니다. 배열이 아닌 값은 제외합니다. |
135
+ | `keyBy` | `field: 'id'` 필수 | 객체 필드의 문자열 표현을 키로 지정하고 원래 값은 유지합니다. 객체가 아니거나 필드가 없거나 null 또는 객체 값이면 제외합니다. |
136
+ | `keyByPattern` | `re: '^(.+)\.ts$'` 필수, `i: true` 선택·기본값 `false` | 첫 정규식 일치의 캡처 그룹 1을 키로 지정합니다. 일치하지 않거나 캡처가 없으면 제외하고, 원래 값은 유지합니다. |
137
+ | `field` | `name: 'version'` 필수 | 키를 유지하고 값을 객체의 해당 속성으로 바꿉니다. 속성이 없으면 `undefined`이며, 객체가 아니면 제외합니다. |
138
+ | `filter` | `when: [{ field: 'succeeded', eq: true }]` 필수 | 모든 조건을 만족하는 항목을 유지합니다. `when: []`는 모든 항목을 유지합니다. 아래 조건 표를 보세요. |
139
+ | `flattenKeys` | 없음 | `home.title`처럼 중첩된 말단 경로를 키와 값으로 나열합니다. 번역 문구는 결과에 포함하지 않습니다. |
140
+ | `sort` | 없음 | 값을 기준으로 안정적인 오름차순 정렬을 합니다. 모두 숫자면 수치로, 그 밖에는 문자열로 비교합니다. |
141
+ | `lines` | 없음 | 값을 문자열로 바꿔 줄바꿈으로 나누고, 각 줄의 앞뒤 공백과 빈 줄을 제거합니다. 키는 원래 줄 번호이며 1부터 시작합니다. |
142
+ | `matches` | `re: '^test:'` 필수, `i: true` 선택·기본값 `false` | 문자열로 바꾼 값이 정규식에 일치하는 항목을 유지합니다. 키와 값은 바뀌지 않습니다. |
143
+ | `toolUses` | `names: ['Bash']`, `subagentType: 'reviewer'` 모두 선택 | 세션 스냅샷에서 호출을 추출하고 관측 순번을 키로 씁니다. 지정한 필터는 모두 일치해야 합니다. 성공 여부는 자동으로 검사하지 않습니다. |
144
+ | `userTexts` | `re: '^approved$'` 필수, `i: true` 선택·기본값 `false` | 일치하는 사용자 메시지를 관측 순번 키로 추출합니다. 값에 스냅샷의 `observedAtMs`도 추가합니다. |
145
+ | `agentType` | `is: 'reviewer'` 필수 | 파싱한 생성 기록 중 종류가 일치하는 항목을 위치 키로 추출합니다. 기록 배열이나 객체 하나를 받습니다. |
146
+ | `first` | 없음 | 첫 항목과 그 키를 유지합니다. 빈 입력은 빈 상태로 남으며 정렬하지 않습니다. |
147
+ | `ageMs` | 없음 | 객체 값에 `ageMs = observedAtMs - timestampMs`를 추가합니다. 시간이 없거나 숫자가 아니거나 미래 관측이면 제외합니다. |
148
+
149
+ `items`와 배열을 반환하는 `select`는 배열마다 별도로 번호를 매깁니다. 여러 배열을 펼치면
150
+ 키가 겹칠 수 있으므로 객체 식별자로 비교해야 할 때는 `keyBy`를 사용하세요.
151
+ `flattenKeys`는 일반 객체의 속성을 따라 내려갑니다. 배열은 해당 속성 경로의 말단 값으로
152
+ 취급하며 배열 인덱스를 나열하지 않습니다. 빈 객체는 중첩된 경우에도 경로를 만들지 않습니다.
153
+
154
+ 정규식 단계는 JavaScript 정규식을 사용합니다. 지원하는 플래그 인자는 `i`이며 `g`나 `m`
155
+ 인자는 없습니다. 파일 전체 텍스트에서 `^`는 전체의 시작을 가리킵니다. 각 줄에 적용하려면
156
+ 먼저 `lines`를 쓰세요. `keyByPattern`에는 캡처 그룹이 필요하며 첫 일치만 사용합니다.
157
+
158
+ <a id="filter-predicates"></a>
159
+ ## 필터 조건
160
+
161
+ 조건 하나에는 `field`와 연산자 하나가 필요합니다. `field`는 점 경로가 아닌 객체의 직접
162
+ 속성 이름입니다. 객체가 아닌 값은 조건을 만족하지 못합니다. `when`의 모든 조건이 참이어야 합니다.
163
+
164
+ | 연산자 | 예제 | 조건 |
165
+ |---|---|---|
166
+ | `eq` | `{ field: 'succeeded', eq: true }` | 상수와 구조적으로 같습니다. |
167
+ | `ne` | `{ field: 'status', ne: 'draft' }` | 상수와 구조적으로 다릅니다. |
168
+ | `size` | `{ field: 'errors', size: 0 }` | 필드가 배열이고 원소 수가 지정한 수와 같습니다. |
169
+ | `notIn` | `{ field: 'status', notIn: ['draft', 'failed'] }` | 배열에 나열한 모든 상수와 필드 값이 다릅니다. |
170
+ | `lte` | `{ field: 'ageMs', lte: 600000 }` | 필드가 숫자이며 지정한 수 이하입니다. |
171
+ | `gte` | `{ field: 'count', gte: 1 }` | 필드가 숫자이며 지정한 수 이상입니다. |
172
+
173
+ `size`, `lte`, `gte`의 인자는 숫자이고 `notIn`의 인자는 배열입니다. 없는 속성은
174
+ `undefined`이므로 `ne`나 `notIn`을 만족할 수 있습니다. 두 연산자가 속성의 존재까지 확인하지는
175
+ 않습니다.
176
+
177
+ <a id="combinators"></a>
178
+ ## 조합 연산자
179
+
180
+ 피연산자는 서로 다른 추출 이름 둘입니다. `onlyIn`과 `intersect`는 **키**로 비교하고,
181
+ `union`은 두 목록을 이어 붙입니다. 세 연산 모두 항목의 값을 유지하며 정렬하거나 중복을
182
+ 제거하지 않습니다.
183
+
184
+ | 조합 연산자 | 구문 | 결과 |
185
+ |---|---|---|
186
+ | `union` | `{ op: 'union', of: ['a', 'b'] }` | `a`의 모든 항목 뒤에 `b`의 모든 항목을 붙입니다. 중복 키도 유지합니다. |
187
+ | `onlyIn` | `{ op: 'onlyIn', of: 'a', notIn: 'b' }` | `a` 중 키가 `b`에 없는 항목입니다. |
188
+ | `intersect` | `{ op: 'intersect', of: ['a', 'b'] }` | `a` 중 키가 `b`에도 있는 항목입니다. 값은 `a`의 것을 사용합니다. |
189
+
190
+ <a id="relations"></a>
191
+ ## 관계
192
+
193
+ 관계 7개는 모두 조건을 위반한 항목을 반환합니다. 반환 항목이 없으면 조건을 만족한 것입니다.
194
+ 아래의 `a`, `b`는 파일 이름이 아닌 추출 이름입니다.
195
+
196
+ | 관계 | 구문 | 조건 |
197
+ |---|---|---|
198
+ | `empty` | `{ op: 'empty', of: 'a' }` | `a`에 항목이 없어야 합니다. 실패하면 모든 항목을 보고합니다. |
199
+ | `nonEmpty` | `{ op: 'nonEmpty', of: 'a' }` | `a`에 항목이 하나 이상 있어야 합니다. 실패하면 추출 이름과 값 `null`을 보고합니다. |
200
+ | `equal` | `{ op: 'equal', of: ['a', 'b'] }` | 값의 집합이 양방향으로 같아야 합니다. 왼쪽에만 있는 항목, 오른쪽에만 있는 항목 순으로 보고합니다. |
201
+ | `subset` | `{ op: 'subset', of: 'a', in: 'b' }` | `a`의 모든 값이 `b`에 있어야 합니다. 일치하지 않는 `a`의 항목을 보고합니다. |
202
+ | `implies` | `{ op: 'implies', of: 'a', requires: 'b' }` | `a`의 모든 키가 `b`에 있어야 합니다. 필요한 키가 없는 `a`의 항목을 보고합니다. |
203
+ | `ordered` | `{ op: 'ordered', of: 'a', strict: false }` | 값이 오름차순이어야 합니다. `strict` 기본값은 `false`이며, `true`이면 이웃한 동일 값도 거부합니다. 위반한 쌍의 뒤 항목을 보고합니다. |
204
+ | `unchanged` | `{ op: 'unchanged', of: 'a' }` | `state`에서 추출한 쌍의 공통 키에서 변경 전후 값이 같아야 합니다. 추가되거나 삭제된 키는 위반으로 세지 않습니다. |
205
+
206
+ `equal`과 `subset`은 값을 구조적으로 비교하며 항목의 키, 목록 순서, 중복 횟수는 무시합니다.
207
+ 값 *안에* 있는 배열은 순서를 비교합니다. `implies`는 키로 비교하며 값을 무시합니다.
208
+ 예를 들어 `{ key: 'en', value: 'home' }`과 `{ key: 'ko', value: 'home' }`은 `equal`을
209
+ 만족하지만, 키가 다르므로 앞 항목이 뒤 항목을 `implies`로 요구하면 위반입니다.
210
+
211
+ `ordered`는 모든 값이 숫자면 수치로, 그 밖에는 문자열 표현으로 비교합니다. 정렬을 수행하지
212
+ 않으며 빈 입력과 항목 하나인 입력은 통과합니다. 바로 앞에서 정렬하면 원래 입력의 순서가
213
+ 올바랐는지는 확인할 수 없습니다.
214
+
215
+ 쌍을 받는 관계는 `unchanged`뿐이고, 나머지는 단일 추출 결과를 받습니다. `equal`, `subset`,
216
+ `implies`에 지정하는 두 추출 이름은 서로 달라야 합니다.
217
+
218
+ <a id="messages-and-witness"></a>
219
+ ## 메시지와 선언의 증인
220
+
221
+ 각 `relate` 항목에는 고유한 `id`, `relation`, 그리고 아래 메시지 형태 중 하나가 필요합니다.
222
+
223
+ | 필드 | 용도 |
224
+ |---|---|
225
+ | `message` | 모든 관계에서 쓸 수 있는 진단 템플릿 하나입니다. |
226
+ | `messageBySide` | `{ left: '…', right: '…' }`이며 `equal`에만 허용됩니다. |
227
+
228
+ 템플릿의 `{key}`와 `{value}`는 첫 위반 항목의 값으로 치환합니다. `{before}`는
229
+ `unchanged`의 변경 전 값이며, 없으면 빈 문자열입니다. 위반이 여러 개면 나머지 개수를
230
+ 덧붙입니다. 객체는 JavaScript 문자열 표현으로 출력하므로 표시하려는 필드를 먼저 추출하세요.
231
+
232
+ 선언의 선택 블록 `witness`에는 선택인 `extract`와 필수인 `relate`가 있습니다. 본체의
233
+ 추출을 참조할 수 있지만 본체는 증인의 추출을 참조할 수 없고, 증인의 추출 이름으로 본체의
234
+ 이름을 덮어쓸 수도 없습니다. 본체가 위반하고 증인의 모든 비교가 성립하면 증언으로 허용됩니다.
235
+ 증인의 공급 오류는 위반을 허용하지 않습니다.
236
+ 최상위의 [인간 증인(witness) 설정](../configuration/index.ko.md#witness)은 별도로 지정합니다.
237
+
238
+ <a id="mechanisms"></a>
239
+ ## 기전
240
+
241
+ 선언마다 기전 하나를 지정합니다. 컴파일러는 `source` 단계에서 축을 도출합니다.
242
+ `actor`를 뺀 고정 소스는 `change`, `actor`는 `actor`, 파일과 sidecar 바인딩은 `world`,
243
+ 대화 기록 바인딩은 `history`입니다. 적용 범위만으로 축이 추가되지는 않습니다. 본체의 관계와
244
+ 도출한 축이 선택한 기전의 허용 범위에 들어가야 합니다. 증인의 추출 소스도 축에 포함합니다.
245
+ 기전이 비교 조건을 만들어 주지는 않으므로 추출과 관계는 직접 작성해야 합니다.
246
+
247
+ | 기전 | 허용하는 축 | 허용하는 본체 관계 | 용도 또는 필수 구조 |
248
+ |---|---|---|---|
249
+ | `pairing` | `world` | `equal`, `subset` | 공급한 파일에서 대응하는 데이터를 비교합니다. |
250
+ | `companion` | `change`, `world` | `implies` | 다른 추출에 대응하는 키가 있어야 합니다. |
251
+ | `monotonic-order` | `change`, `world` | `ordered` | 나열된 값의 순서를 확인합니다. |
252
+ | `fingerprint-sync` | `world` | `equal` | 추출한 지문 값을 비교합니다. |
253
+ | `producer-owned` | `actor` | `empty`, `nonEmpty` | 관측된 작업 주체를 확인합니다. |
254
+ | `self-absolution-ban` | `change` | `unchanged`, `empty` | 파일 자체 내용의 변경을 검사합니다. |
255
+ | `actor-scope` | `actor` | `empty`, `nonEmpty` | 관측된 주체에 따라 작업을 제한합니다. |
256
+ | `precedent` | `history`, `world` | `nonEmpty` | 선행 증거를 요구합니다. |
257
+ | `phase-order` | `history` | `ordered` | 추출한 관측 순번을 비교합니다. |
258
+ | `turn-locality` | `history` | `nonEmpty` | 지정한 시간 범위 안의 증거를 요구합니다. |
259
+ | `stated-ground` | `history` | `nonEmpty` | 패턴에 일치하는 사용자 발언을 요구합니다. |
260
+ | `controlled-vocabulary` | `change`, `world` | `subset` | 추출한 값을 허용 집합과 비교합니다. |
261
+ | `naming` | `change` | `empty`, `nonEmpty` | `scope.source`는 `target.path`여야 합니다. |
262
+ | `added-only` | `change` | `empty` | 주로 `post`와 `pre`의 차이를 비교합니다. |
263
+ | `one-way-marker` | `change` | `subset` | 선택한 값이 계속 존재하는지 확인합니다. |
264
+ | `delegated-scope` | — | — | 예약 이름이며 로드 시점에 거부됩니다. |
265
+ | `scoped-valve` | `change`, `actor`, `world`, `history` | 관계 7개 모두 | 선언의 `witness` 블록이 필수입니다. |
266
+ | `forbidden-command` | `change` | `empty` | `scope.source`는 `command`여야 합니다. |
267
+
268
+ 이름 18개에는 예약 이름 하나가 포함되므로 사용할 수 있는 것은 17개입니다. 기전 제약과
269
+ [규율 목록 배치](../configuration/index.ko.md#placement-rule)는 별도로 검사합니다.
270
+
271
+ <a id="validation"></a>
272
+ ## 선언 검증하기
273
+
274
+ `pnpm exec pdks explain`을 실행해 의도한 표면에 항목이 `declare`로 등록됐는지 확인하세요.
275
+ `config-fault`인 `skip` 등록은 컴파일 실패이므로 오류 위치와 사유를 읽어야 합니다.
276
+ 지원하지 않는 키나 잘못된 소스·목록 조합은 설정 로드 단계에서 실패할 수도 있습니다.
277
+
278
+ 이후 해당 표면에서 위반 입력과 정상 입력을 각각 실행하세요.
279
+ [번역 파일 예제](../../how-to/write-disciplines.ko.md#locale-key-pairing)를 참고할 수 있습니다.
280
+ `advised`와 `skipped`도 종료 코드 0일 수 있으므로 종료 코드만으로 판단하지 마세요.