polydeukes 0.6.1 → 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.
Files changed (106) hide show
  1. package/README.ko.md +14 -28
  2. package/README.md +14 -28
  3. package/dist/baseline.d.ts +37 -1
  4. package/dist/baseline.js +68 -1
  5. package/dist/bin.d.ts +3 -4
  6. package/dist/bin.js +73 -90
  7. package/dist/covenant/bash-line.d.ts +130 -0
  8. package/dist/covenant/bash-line.js +566 -0
  9. package/dist/covenant/declaration-engine.d.ts +108 -0
  10. package/dist/covenant/declaration-engine.js +415 -0
  11. package/dist/covenant/discipline.d.ts +103 -0
  12. package/dist/covenant/discipline.js +667 -0
  13. package/dist/covenant/dispatch.d.ts +153 -0
  14. package/dist/covenant/dispatch.js +237 -0
  15. package/dist/covenant/extract-steps.d.ts +122 -0
  16. package/dist/covenant/extract-steps.js +0 -0
  17. package/dist/covenant/mention.d.ts +117 -0
  18. package/dist/covenant/mention.js +209 -0
  19. package/dist/covenant/module.d.ts +25 -0
  20. package/dist/covenant/module.js +23 -0
  21. package/dist/covenant/mutation-rules.d.ts +40 -0
  22. package/dist/covenant/mutation-rules.js +146 -0
  23. package/dist/covenant/relations.d.ts +46 -0
  24. package/dist/covenant/relations.js +68 -0
  25. package/dist/covenant/run-covenant.d.ts +95 -0
  26. package/dist/covenant/run-covenant.js +164 -0
  27. package/dist/covenant/self-mod.d.ts +55 -0
  28. package/dist/covenant/self-mod.js +84 -0
  29. package/dist/covenant/shell-evidence.d.ts +38 -0
  30. package/dist/covenant/shell-evidence.js +247 -0
  31. package/dist/covenant/shell-mod.d.ts +84 -0
  32. package/dist/covenant/shell-mod.js +213 -0
  33. package/dist/covenant/supply.d.ts +54 -0
  34. package/dist/covenant/supply.js +50 -0
  35. package/dist/covenant/transcript-mod.d.ts +60 -0
  36. package/dist/covenant/transcript-mod.js +254 -0
  37. package/dist/covenant/ttl-witness.d.ts +47 -0
  38. package/dist/covenant/ttl-witness.js +80 -0
  39. package/dist/covenant-check.d.ts +72 -39
  40. package/dist/covenant-check.js +260 -121
  41. package/dist/diff-ir.d.ts +28 -0
  42. package/dist/diff-ir.js +310 -0
  43. package/dist/docs/README.ko.md +8 -8
  44. package/dist/docs/README.md +8 -8
  45. package/dist/docs/catalog.json +44 -28
  46. package/dist/docs/concepts/judgment.ko.md +2 -2
  47. package/dist/docs/concepts/judgment.md +2 -2
  48. package/dist/docs/how-to/configure-project.ko.md +16 -16
  49. package/dist/docs/how-to/configure-project.md +15 -12
  50. package/dist/docs/how-to/connect-surfaces.ko.md +39 -34
  51. package/dist/docs/how-to/connect-surfaces.md +36 -33
  52. package/dist/docs/how-to/write-disciplines.ko.md +61 -6
  53. package/dist/docs/how-to/write-disciplines.md +62 -6
  54. package/dist/docs/index.json +607 -373
  55. package/dist/docs/reference/cli/covenant-check.ko.md +112 -65
  56. package/dist/docs/reference/cli/covenant-check.md +114 -59
  57. package/dist/docs/reference/cli/docs.ko.md +2 -2
  58. package/dist/docs/reference/cli/docs.md +2 -2
  59. package/dist/docs/reference/cli/explain.ko.md +14 -9
  60. package/dist/docs/reference/cli/explain.md +15 -10
  61. package/dist/docs/reference/cli/init.ko.md +52 -52
  62. package/dist/docs/reference/cli/init.md +43 -55
  63. package/dist/docs/reference/configuration/index.ko.md +171 -66
  64. package/dist/docs/reference/configuration/index.md +178 -71
  65. package/dist/docs/reference/packages/adapter-claude-code.ko.md +38 -11
  66. package/dist/docs/reference/packages/adapter-claude-code.md +38 -14
  67. package/dist/docs/reference/packages/adapter-grok.ko.md +77 -0
  68. package/dist/docs/reference/packages/adapter-grok.md +79 -0
  69. package/dist/docs/reference/packages/core.ko.md +49 -3
  70. package/dist/docs/reference/packages/core.md +54 -5
  71. package/dist/docs/reference/packages/polydeukes.ko.md +138 -77
  72. package/dist/docs/reference/packages/polydeukes.md +146 -81
  73. package/dist/docs/reference/packages/sdk-ts.ko.md +161 -0
  74. package/dist/docs/reference/packages/sdk-ts.md +164 -0
  75. package/dist/docs/troubleshooting.ko.md +17 -15
  76. package/dist/docs/troubleshooting.md +18 -14
  77. package/dist/docs/tutorials/first-judgment.ko.md +2 -2
  78. package/dist/docs/tutorials/first-judgment.md +2 -2
  79. package/dist/explain.d.ts +1 -1
  80. package/dist/explain.js +37 -22
  81. package/dist/load-config.d.ts +1 -1
  82. package/dist/load-config.js +1 -1
  83. package/dist/pre-state-reader.d.ts +1 -1
  84. package/dist/pre-state-reader.js +1 -1
  85. package/dist/scaffold-project.d.ts +2 -2
  86. package/dist/scaffold-project.js +9 -3
  87. package/dist/schema/polydeukes.schema.json +12 -0
  88. package/dist/worktree-reader.d.ts +19 -0
  89. package/dist/worktree-reader.js +30 -0
  90. package/package.json +3 -17
  91. package/dist/claude-code-hook.d.ts +0 -79
  92. package/dist/claude-code-hook.js +0 -372
  93. package/dist/claude-code.d.ts +0 -6
  94. package/dist/claude-code.js +0 -6
  95. package/dist/covenant-module.d.ts +0 -25
  96. package/dist/covenant-module.js +0 -42
  97. package/dist/docs/reference/packages/adapter-git.ko.md +0 -101
  98. package/dist/docs/reference/packages/adapter-git.md +0 -108
  99. package/dist/docs/reference/packages/covenant.ko.md +0 -115
  100. package/dist/docs/reference/packages/covenant.md +0 -123
  101. package/dist/index.d.ts +0 -22
  102. package/dist/index.js +0 -21
  103. package/dist/init-claude-code.d.ts +0 -52
  104. package/dist/init-claude-code.js +0 -586
  105. package/dist/init-grok.d.ts +0 -51
  106. package/dist/init-grok.js +0 -242
@@ -2,63 +2,61 @@
2
2
 
3
3
  **English** · [한국어](./polydeukes.ko.md)
4
4
 
5
- The umbrella package is the only package a consumer installs. It owns the CLI bin, the config
6
- loader, the commit-surface runner, the session-surface runner subpath, and the bundled schema
7
- artifact.
5
+ The umbrella package is the only package a consumer installs. It ships the `pdks` bin, the
6
+ judge, both surfaces' composition roots, the session-surface runner subpath, and the bundled
7
+ schema artifact.
8
8
 
9
9
  <a id="polydeukes-entry-points"></a>
10
10
  ## Entry points
11
11
 
12
- | Specifier | What it exports |
12
+ | Specifier | What it is |
13
13
  |---|---|
14
- | `polydeukes` | `loadConfig`, `runCovenantCheck`, and `ResolvedConfig` |
15
- | `polydeukes/claude-code` | `runClaudeCodeHook` and its spec/outcome types |
14
+ | `pdks` / `polydeukes` | The executable. One CLI under two names in `bin` |
16
15
  | `polydeukes/schema.json` | The bundled config JSON Schema |
17
16
 
18
- The package has one executable name in `bin`: `pdks`, with `polydeukes` as an alias.
17
+ There is no `.` entry point. `import 'polydeukes'` fails with `ERR_PACKAGE_PATH_NOT_EXPORTED`;
18
+ what a consumer reaches is the bin and the schema file. A session surface reaches this
19
+ package the same way a shell does — by spawning `pdks covenant check` — so an agent adapter
20
+ takes it as a peer dependency rather than importing it.
19
21
 
20
22
  <a id="polydeukes-bin"></a>
21
23
  ## CLI surface
22
24
 
23
25
  | Command | Purpose |
24
26
  |---|---|
25
- | `pdks covenant check` | Judge the staged diff, the working tree, or a ref range |
26
- | `pdks init claude-code` | Install the Claude Code session surface |
27
- | `pdks init grok` | Install the Grok session surface |
27
+ | `pdks covenant check` | Judge an input IR (default, the session surface) or a unified diff (`--diff`, the change-set surface) from stdin |
28
+ | `pdks init` | Create the project scaffold: config file and telemetry ignore line |
28
29
  | `pdks explain` | Render the assembled registration table without judging |
29
30
  | `pdks docs [topic]` | Read a bundled topic |
30
31
  | `pdks docs search <query>` | Search the bundled docs |
31
32
  | `pdks docs show <document-id>` | Show one bundled document or section |
32
33
 
33
- `pdks docs` is offline. It reads the installed package, not the network. Flags, JSON, and
34
- exit codes are in [`pdks docs`](../cli/docs.md).
34
+ Session-surface installers live on the adapters: `pdks-claude-code init` and `pdks-grok init`.
35
35
 
36
- <a id="polydeukes-export-map"></a>
37
- ## Export map
36
+ <a id="surface-from-input-mode"></a>
37
+ ### The input mode is the surface
38
38
 
39
- <a id="root-export"></a>
40
- ### `.` root export
39
+ `covenant check` reads its surface off how the input arrived, never off the input's own keys.
40
+ An IR on stdin is one call a host observed before it ran, so the run is the **session surface**;
41
+ `--diff` is a finished change set from some producer, so the run is the **change-set surface**.
42
+ Each surface compiles the shared list plus its own:
41
43
 
42
- | Symbol | Kind | Notes |
44
+ | Input | Surface | Lists compiled |
43
45
  |---|---|---|
44
- | `loadConfig` | function | Discovers exactly one `polydeukes.config.*` file under `rootDir`; throws on missing, ambiguous, parse, or validation failure. |
45
- | `runCovenantCheck` | function | Runs the commit-surface runner and resolves to `{ exitCode: 0 \| 2 }`. |
46
- | `ResolvedConfig` | type | Re-export from `@polydeukes/core`. |
47
- | `LoadConfigSpec`, `LoadedConfig` | types | Configuration loader input and result. |
48
- | `CovenantCheckSpec`, `CovenantCheckOutcome`, `CheckDomain` | types | Commit runner input, result, and observation selection. |
46
+ | an input IR on stdin | `session` | `disciplines`, then `sessionDisciplines` |
47
+ | a unified diff on stdin under `--diff` | `changeSet` | `disciplines`, then `changeSetDisciplines` |
49
48
 
50
- <a id="session-export"></a>
51
- ### `./claude-code`
49
+ The IR's `session` key says something else: what the host proved about the call — the witness
50
+ valve's human turns, the transcript axis, and the pre-state channel shell-write evidence needs.
51
+ A host that carries no session (the Grok adapter) is still judged on the session surface, and
52
+ what it lacks is the evidence those three read, not the surface. `pdks explain` prints each
53
+ surface with its two list names and counts.
52
54
 
53
- | Symbol | Kind | Notes |
54
- |---|---|---|
55
- | `runClaudeCodeHook` | function | Runs the session-surface runner and resolves to `{ exitCode: 0 \| 2 }`. |
56
- | `ClaudeCodeHookSpec` | type | Input for the session runner. |
57
- | `ClaudeCodeHookOutcome` | type | Session runner result. |
55
+ `pdks docs` is offline. It reads the installed package, not the network. Flags, JSON, and
56
+ exit codes are in [`pdks docs`](../cli/docs.md).
58
57
 
59
- The generated hook imports this subpath, not the barrel. ESM imports are eager, so importing the
60
- barrel would load the commit-surface runner — and the git adapter behind it — on every session tool
61
- call that will never use them.
58
+ <a id="polydeukes-export-map"></a>
59
+ ## Export map
62
60
 
63
61
  <a id="schema-export"></a>
64
62
  ### `./schema.json`
@@ -67,67 +65,134 @@ call that will never use them.
67
65
  |---|---|
68
66
  | `polydeukes.schema.json` | The config schema copy that ships with the umbrella package. |
69
67
 
70
- <a id="polydeukes-signatures"></a>
71
- ## Signatures and examples
68
+ <a id="covenant-module"></a>
69
+ ## The judge (`covenant` module)
72
70
 
73
- ```ts
74
- function loadConfig(spec: LoadConfigSpec): LoadedConfig;
71
+ The judge is the umbrella's `src/covenant/` module. Everything that turns a declared promise
72
+ into a verdict lives there, and both composition roots plus `pdks explain` import it directly.
73
+ There is nothing to install, nothing to import, and no peer to satisfy: what you reach is its
74
+ behaviour, through the `disciplines:` block in your config and the rows it writes to
75
+ `.polydeukes/roi.log`.
75
76
 
76
- type LoadConfigSpec = { rootDir: string };
77
+ <a id="ownership"></a>
78
+ ### What the module owns
77
79
 
78
- type LoadedConfig = {
79
- config: ResolvedConfig;
80
- configPath: string;
81
- };
82
-
83
- function runCovenantCheck(spec: CovenantCheckSpec): Promise<CovenantCheckOutcome>;
84
-
85
- type CovenantCheckSpec = {
86
- repoRoot: string;
87
- telemetryPath?: string;
88
- covenantDist?: string;
89
- ttyPrompt?: (prompt: string) => string | null;
90
- domain?: CheckDomain;
91
- };
92
-
93
- type CheckDomain =
94
- | { kind: 'staged' }
95
- | { kind: 'worktree' }
96
- | { kind: 'range'; base: string; head: string; ancestry?: 'merge-base' };
97
-
98
- function runClaudeCodeHook(spec: ClaudeCodeHookSpec): Promise<ClaudeCodeHookOutcome>;
99
-
100
- type ClaudeCodeHookSpec = {
101
- repoRoot: string;
102
- rawPayload?: string;
103
- telemetryPath?: string;
104
- covenantDist?: string;
105
- };
106
- ```
107
-
108
- `ancestry: 'merge-base'` is the `<base>...<head>` reading. `rawPayload` absent means the hook reads
109
- fd 0. The hook types live on `polydeukes/claude-code`, not on the root barrel.
80
+ | Unit | What it does |
81
+ |---|---|
82
+ | `runCovenant` wrapper | Runs a judge body, translates its non-blocking `1` into the blocking `2`, and logs every call. No covenant runs unmeasured |
83
+ | Path-routing dispatcher | Registers covenants against protected paths and runs *every* matching one — no short-circuit, so the telemetry never under-counts |
84
+ | Meta-covenants | Three registrations that protect the judging chain itself |
85
+ | TTL witness | The time-boxed human valve, consulted only after a verdict blocked |
86
+ | Delta layer | New-violation-only judgment over a file's before/after pair |
87
+ | Discipline library | Config `disciplines:` entries become enforcement without a line of code |
88
+
89
+ <a id="disciplines-and-meta-covenants"></a>
90
+ ### Discipline families and meta-covenants
91
+
92
+ **A `disciplines:` entry is one declaration** — `judge = relate ∘ extract` over the
93
+ observation as a world. What the declaration's sources bind decides what evidence the
94
+ judgment needs, which is also what decides whether it can be judged on a given surface.
95
+
96
+ | Sources | Judges | Evidence needed | List |
97
+ |---|---|---|---|
98
+ | the fixed names `target.path` · `pre` · `post` · `state` | The change itself | A file change | `disciplines` |
99
+ | the fixed name `changes` | The observation's whole change set | A finished change set | `changeSetDisciplines` |
100
+ | the fixed name `command` | The shell call's command line | A shell call — an Edit carries none | `sessionDisciplines` |
101
+ | the fixed name `actor` | Who made the observation | An actor the host proved | `sessionDisciplines` |
102
+ | `{ transcript: true }` | Session history — was a qualifying call actually executed *before* this one | A session | `sessionDisciplines` |
103
+ | `{ file: … }` | Another file | The surface's reader for it | `disciplines` |
104
+ | `{ sidecar: true }` | The spawn-record channel | The surface's channel reader | `sessionDisciplines` |
105
+
106
+ The writing guide for these entries is [the configuration reference's `disciplines`
107
+ section](../configuration/index.md#disciplines); the declaration grammar is the core's
108
+ `algebra-declaration.schema.json`.
109
+
110
+ **Three meta-covenants** protect the judging chain. They are covenants like any other; the
111
+ vocabulary below applies to them unchanged.
112
+
113
+ | Registration | Axis | Judges |
114
+ |---|---|---|
115
+ | self-mod | Tool | Mutations to protected paths through editing tools. Only the call's proven mutation target is compared — a protected path inside an edit's *content* is a mention and passes |
116
+ | shell-mod | Shell | The same, through a command line. A command mentioning a protected path passes only if its leading word proves it read-only |
117
+ | transcript-mod | Transcript | Writes to the live session transcript, judged by whole-path **equality** — never as a protected ancestor |
110
118
 
111
- ```ts
112
- import { loadConfig, runCovenantCheck } from 'polydeukes';
113
- import { runClaudeCodeHook } from 'polydeukes/claude-code';
119
+ **Six words** are the telemetry contract — five verdicts and one observation. A row in
120
+ `.polydeukes/roi.log` carries exactly one of them, and the CLI, the docs, and the tests use
121
+ the same word for the same event. How to read a row is in
122
+ [troubleshooting](../../troubleshooting.md#reading-a-verdict).
114
123
 
115
- const { configPath } = loadConfig({ rootDir: process.cwd() });
116
- const check = await runCovenantCheck({ repoRoot: process.cwd() });
117
- const hook = await runClaudeCodeHook({ repoRoot: process.cwd(), rawPayload: '{}' });
118
- ```
124
+ | Verdict | Means |
125
+ |---|---|
126
+ | `passed` | The call was judged and upheld the covenant |
127
+ | `blocked` | The call was judged and broke it |
128
+ | `witnessed` | A **blocked** verdict a human opened in person. Never silent, never a clean call |
129
+ | `advised` | A break recorded without stopping the call — the default for every discipline entry on both surfaces, unless the entry itself says `enforce: block` |
130
+ | `skipped` | The call reached a registration that could not judge it. **Not a pass** — the recorded absence of a judgment |
131
+ | `unattributed` | A protected entry's on-disk state moved and no judgment row explains it. **Not a verdict** — no call is blocked or passed by it; the session surface writes it after comparing state against a stored baseline |
132
+
133
+ `unattributed` answers a question the other five cannot. They are all written by a judge
134
+ about a call it was handed, so a write that arrives without a declared call — through an
135
+ interpreter, a test runner's child process, a script that assembles the path from its own
136
+ arguments — leaves no row at all. The comparison observes the result rather than the
137
+ spelling, so it records that write after the fact. It never blocks: the write already
138
+ happened, and the comparison fails open on both sides of the verdict.
139
+
140
+ <a id="consumer-contract"></a>
141
+ ### Where the consumer touches it
142
+
143
+ - **The `disciplines:` block** in your config. One entry compiles into one registration,
144
+ carrying its own telemetry label.
145
+ - **`protectedPaths`**, which the path-routing dispatcher matches against.
146
+ - **The `witness` block**, which arms the TTL valve.
147
+ - **`.polydeukes/roi.log`**, where every judgment lands as one row.
148
+
149
+ No import. The umbrella assembles the module for both surfaces.
150
+
151
+ <a id="limits"></a>
152
+ ### Declared limits
153
+
154
+ - **The shell axis leaves `skipped` rows, and that row is the contract.** Predicting a
155
+ shell command's target from its text is undecidable, so the invariant this axis holds is
156
+ not "nothing gets through" — it is that **no call passes unrecorded**. A new spelling
157
+ landing in `skipped` is the declared limit showing itself. A pass with no row at all, or
158
+ one recorded `passed` without a judgment, is the defect class.
159
+ - **A session-only declaration on a host that proves no session records `skipped`.** The
160
+ entry is compiled — it is on the session surface — but the transcript the declaration binds
161
+ is absent, and the entry's own `supply: pass` disposes of that with a `skipped` row carrying
162
+ the reason `supply-pass`. With no policy the missing session is unjudgeable (exit 2), not an
163
+ automatic skip.
164
+ - **A surface never registers the other surface's list.** A `command` or transcript
165
+ declaration is not compiled for a change set, and a `changes` declaration is not compiled for
166
+ one call, so neither leaves a row on the surface that does not observe it. Which entries a
167
+ surface carries is read from `pdks explain` and from the config's own list names, not from
168
+ the telemetry log.
169
+ - **A declaration the compiler cannot resolve compiles to a skip registration** — routing
170
+ intact, no body: a step outside the registry, an argument outside a step's keys, a pattern
171
+ that does not compile, a paired/single mismatch. Assembly therefore never throws: one
172
+ unresolvable entry cannot take down its siblings, the meta-covenants, and the valve, which
173
+ would leave no way to fix the config that caused it. A reserved mechanism, or one whose
174
+ axes and relations the catalogue refuses, is a different stage: config loading rejects the
175
+ file (exit 2), and no skip row is written. A source the world lacks at judgment time is a
176
+ third case — the declaration's own `supply` policy disposes of it, and with no policy the
177
+ body answers unjudgeable (exit 2), never upheld.
178
+ - **Complete containment is a non-goal.** There are no blocklists here — enumerating bypass
179
+ spellings is always one step behind, so the logic is inverted: a mention of a protected
180
+ path blocks unless proven safe. Residual vectors such as indirect path computation are
181
+ telemetry targets, not block targets. The two friction valves — the read-only allowlist
182
+ and the TTL witness — both leave a measurable trace.
183
+ - **The valve stands after the verdict.** Only a judgment that actually blocked can be
184
+ witnessed open, a mid-sentence mention of the token does not arm it, and an AI can never
185
+ open the valve for itself.
119
186
 
120
187
  <a id="polydeukes-failure-boundaries"></a>
121
188
  ## Failure boundaries
122
189
 
123
- - `loadConfig()` throws on missing, ambiguous, parse, or validation failure.
124
- - `runCovenantCheck()` and `runClaudeCodeHook()` never throw; they resolve to `{ exitCode: 0 \| 2
125
- }`.
190
+ - `runCovenantCheck()` never throws; it resolves to `{ exitCode: 0 \| 2 }`.
126
191
  - The numeric codes are `EXIT_UPHOLD` (`0`), `EXIT_BREAK_NON_BLOCKING` (`1`), and
127
192
  `EXIT_BREAK_BLOCKING` (`2`) from `@polydeukes/core`. The umbrella runners expose only `0` or
128
193
  `2`; they never return `1`.
129
- - `pdks covenant check` prompts for a witness token only for the staged domain, never for
130
- `--worktree` or `--range`.
194
+ - `pdks covenant check` never prompts. It reads stdin and exits 0 or 2; the caller decides what
195
+ that exit code means.
131
196
  - `pdks docs` and `pdks explain` print nothing partial on failure.
132
197
 
133
198
  <a id="polydeukes-see-also"></a>
@@ -0,0 +1,161 @@
1
+ # `@polydeukes/sdk-ts`
2
+
3
+ [English](sdk-ts.md) · **한국어**
4
+
5
+ > **TypeScript에서 판정기로 가는 동사 하나**입니다. 약속(covenant) 입력 IR을
6
+ > `pdks covenant check`에 건네고 판정 결과를 값으로 돌려받습니다.
7
+ >
8
+ > 알파입니다. `polydeukes` · `@polydeukes/core`와 함께 설치하며, 둘 다 이 패키지의
9
+ > `peerDependencies`입니다.
10
+
11
+ <a id="ownership"></a>
12
+ ## 담당하는 기능
13
+
14
+ 스폰과, 종료 상태를 값으로 옮기는 일이 전부입니다. 판정받는 프로젝트의 `polydeukes` 설치를
15
+ 찾고, 그 실행 파일에 입력을 표준 입력으로 넣어 돌린 뒤, 자식이 답한 것을 돌려줍니다. 판정
16
+ 코드는 여기에 없습니다. 분기는 우산 패키지를 찾았는지와 자식이 어떤 상태로 끝났는지뿐입니다.
17
+
18
+ | 단위 | 하는 일 |
19
+ |---|---|
20
+ | `checkCovenant` | 판정받는 프로젝트에서 `pdks covenant check`를 스폰하고 판정 결과를 돌려줍니다 |
21
+ | 우산 해소 | `repoRoot`의 설치 그래프에서 `polydeukes`를 찾아 `pdks` 실행 파일을 읽습니다 |
22
+ | 판정 결과 변환 | 종료 코드 `0`은 `upheld`, `2`는 `blocked`, 그 밖은 모두 `unjudged`입니다 |
23
+
24
+ 이 패키지는 텔레메트리 행을 쓰지 않습니다. 실행이 남기는 행은 모두 판정이 일어난 자식
25
+ 프로세스가 쓰므로, 호출 하나에 행 하나는 그대로입니다.
26
+
27
+ <a id="install"></a>
28
+ ## 설치
29
+
30
+ ```sh
31
+ pnpm add @polydeukes/sdk-ts polydeukes @polydeukes/core
32
+ ```
33
+
34
+ 실행 파일도 설치 단계도 없습니다. 우산 패키지가 SDK가 스폰할 판정기를 공급하고, 코어가
35
+ 호출자가 채우는 `CovenantInput` 타입을 공급합니다.
36
+
37
+ <a id="verb"></a>
38
+ ## 동사
39
+
40
+ ```ts
41
+ import { checkCovenant } from '@polydeukes/sdk-ts';
42
+
43
+ const verdict = await checkCovenant({
44
+ repoRoot: '/path/to/the/project',
45
+ input: {
46
+ toolCalls: [
47
+ {
48
+ name: 'writeFile',
49
+ args: { path: 'src/index.ts', content: 'export const answer = 42;\n' },
50
+ fileChange: {
51
+ kind: 'modify',
52
+ path: 'src/index.ts',
53
+ pre: 'export const answer = 41;\n',
54
+ post: 'export const answer = 42;\n',
55
+ },
56
+ },
57
+ ],
58
+ subagentSpawns: [],
59
+ userMessages: [],
60
+ tools: { mutating: ['writeFile', 'rm'], shell: ['exec'], commandArgs: ['command'] },
61
+ },
62
+ });
63
+ ```
64
+
65
+ IR은 호출자의 것입니다. 이 패키지는 IR을 읽지도 채우지도 않습니다. `session`도 `actor`도
66
+ 자기 명부도 더하지 않으며, 위의 `tools` 값도 호출자 자신의 도구 이름입니다. `subagentSpawns`와
67
+ `userMessages`는 필수 배열이므로 둘 다 없는 호출자는 빈 배열을 보냅니다. `world` 키는 판정기가
68
+ 거부합니다. 러너가 세계를 디스크의 프로젝트에서 읽으며, 클라이언트가 세계를 고르면 무엇을
69
+ 판정할지를 고르는 것이 되기 때문입니다.
70
+
71
+ <a id="spec"></a>
72
+ ## 스펙
73
+
74
+ ```ts
75
+ type CheckCovenantSpec = {
76
+ repoRoot: string;
77
+ input: CovenantInput;
78
+ enforce?: 'advise' | 'block';
79
+ spawn?: (spec: CheckCovenantSpawnSpec) => Promise<{ status: number | null; stderr: string }>;
80
+ };
81
+
82
+ type CheckCovenantSpawnSpec = { command: string; args: string[]; cwd: string; stdin: string };
83
+ ```
84
+
85
+ | 필드 | 무엇인가 |
86
+ |---|---|
87
+ | `repoRoot` | 판정받는 프로젝트입니다. 설정 발견, 세계 축, 자식의 cwd, 우산 패키지를 찾는 설치 그래프가 모두 여기 걸립니다 |
88
+ | `input` | 호출자 자신의 IR이며 자식의 표준 입력으로 원문 그대로 갑니다 |
89
+ | `enforce` | 실행 전체에 대한 관측자의 기본 자세입니다. **적지 않으면 `block`입니다** |
90
+ | `spawn` | 주입하는 스폰 이음매입니다. 없으면 이 프로세스의 node 실행 파일로 자식을 띄웁니다 |
91
+
92
+ **`enforce`의 기본값은 `block`입니다.** 이것은 표면의 강제 수준이지 항목의 것이 아닙니다.
93
+ 보호 경로와 `enforce: block`을 단 항목이 호출을 멈추고, 나머지 위반은 종료 코드 0에
94
+ `advised`로 기록됩니다. 항목 자신의 강제 수준은 다른 표면에서와 같이 느슨한 쪽이 이기도록
95
+ 조합됩니다. `@polydeukes/adapter-claude-code`와 `@polydeukes/adapter-grok`도 같은 수준으로
96
+ 판정기를 스폰합니다.
97
+
98
+ 기본 스폰은 파일 서술자를 하나도 상속하지 않습니다. 호출자가 자기 서술자를 갖지 않을 수 있고,
99
+ 상속한 stdout이 닫혀 있으면 자식이 답하기 전에 EPIPE로 죽기 때문입니다. stderr는 모아서
100
+ 돌려주고, 판정기가 stdout에는 판정 결과를 쓰지 않으므로 stdout은 흘려보내고 버립니다.
101
+
102
+ <a id="verdicts"></a>
103
+ ## 판정 결과 셋
104
+
105
+ ```ts
106
+ type CheckCovenantVerdict =
107
+ | { verdict: 'upheld'; advisories: string }
108
+ | { verdict: 'blocked'; reason: string }
109
+ | { verdict: 'unjudged'; reason: string };
110
+ ```
111
+
112
+ | 판정 결과 | 자식의 상태 | 호출자에게 뜻하는 것 |
113
+ |---|---|---|
114
+ | `upheld` | `0` | 호출이 판정을 받았고 아무것도 막지 않았습니다. `advisories`는 자식의 stderr 원문이며 그 실행이 낸 권고 줄을 싣습니다. 진행하면 됩니다 |
115
+ | `blocked` | `2` | 호출이 판정을 받았고 무언가 막았습니다. `reason`은 자식의 stderr 원문입니다. 진행하지 않습니다 |
116
+ | `unjudged` | 그 밖의 상태이거나 우산 패키지가 없음 | 판정이 일어나지 않았습니다. `reason`이 어느 쪽인지 말합니다. 이것을 통과로 읽으면 판정기가 설치되지 않은 프로젝트에서 모든 호출이 지나갑니다 |
117
+
118
+ **밸브가 없고 그 자리를 사유가 대신합니다.** 무인 실시간 호출자에게는 TTY도 사람의 턴도
119
+ 없으므로, 이 패키지는 증인 인자를 받지 않고 세션을 지어내지도 않습니다. 대신
120
+ `blocked.reason`과 `upheld.advisories`가 데이터로 돌아오고, 그 텍스트를 이슈나 로그에 적을지
121
+ 모델에게 돌려줄지는 소비자가 정합니다. 설정 저자와 소비자를 위한 기본 자세 규칙은
122
+ [규율 작성하기](../../how-to/write-disciplines.ko.md#posture)에 있습니다.
123
+
124
+ <a id="failure"></a>
125
+ ## 실패 예제
126
+
127
+ 프로젝트에 `polydeukes`가 설치돼 있지 않으면 스폰할 것이 없고, 동사는 `upheld`로 답하는 대신
128
+ 그 사실을 말합니다.
129
+
130
+ ```ts
131
+ const verdict = await checkCovenant({ repoRoot: '/tmp/project-without-polydeukes', input });
132
+
133
+ // {
134
+ // verdict: 'unjudged',
135
+ // reason: 'no polydeukes in the install graph of /tmp/project-without-polydeukes:
136
+ // install it to have this input judged',
137
+ // }
138
+ ```
139
+
140
+ 자식 프로세스는 돌지 않고 텔레메트리 로그에도 아무것도 더해지지 않습니다. 행은 판정이
141
+ 일어나는 자리에 쓰이는데, 판정이 일어나지 않았기 때문입니다.
142
+
143
+ <a id="limits"></a>
144
+ ## 선언된 한계
145
+
146
+ - **IR은 호출자가 만듭니다.** 도구 명부와 변경 전 상태와 봉투는 호스트가 아는 사실이므로,
147
+ 그것을 아는 소비자가 채웁니다. 이 패키지는 그중 무엇도 공급하지 않습니다.
148
+ - **SDK가 여는 것은 세션 표면뿐입니다.** 입력이 표준 입력의 IR로 가고, 그것이 이 실행을 세션
149
+ 표면 판정으로 만듭니다. 끝난 변경 집합을 가진 호출자는 대신 셸에서
150
+ `pdks covenant check --diff`에 통합 diff를 파이프합니다.
151
+ - **여기서는 텔레메트리 행을 쓰지 않습니다.** 행은 모두 자식이 씁니다.
152
+ - **`unjudged`는 통과가 아닙니다.** 판정기가 답하지 않았다는 사실을 기록하며, 판정기가 없는
153
+ 프로젝트에서 무엇을 허용할지는 소비자가 정합니다.
154
+
155
+ <a id="see-also"></a>
156
+ ## 함께 보기
157
+
158
+ - [`pdks covenant check`](../cli/covenant-check.ko.md)
159
+ - [`polydeukes`](polydeukes.ko.md)
160
+ - [`@polydeukes/core`](core.ko.md)
161
+ - [설정 참조](../configuration/index.ko.md)
@@ -0,0 +1,164 @@
1
+ # `@polydeukes/sdk-ts`
2
+
3
+ **English** · [한국어](sdk-ts.ko.md)
4
+
5
+ > **One verb from TypeScript to the judge** — hand a covenant input IR to
6
+ > `pdks covenant check` and read the verdict back as a value.
7
+ >
8
+ > Alpha. Install it next to `polydeukes` and `@polydeukes/core`, which it names as
9
+ > `peerDependencies`.
10
+
11
+ <a id="ownership"></a>
12
+ ## What this package owns
13
+
14
+ The spawn and the translation of an exit status into a value, and nothing else. It locates the
15
+ `polydeukes` install of the project being judged, runs its bin with the input on stdin, and
16
+ returns what the child answered. No judgment logic lives here: the branches are whether the
17
+ umbrella resolved and what status the child left with.
18
+
19
+ | Unit | What it does |
20
+ |---|---|
21
+ | `checkCovenant` | Spawns `pdks covenant check` in the judged project and returns the verdict |
22
+ | Umbrella resolution | Finds `polydeukes` in the install graph of `repoRoot` and reads its `pdks` bin |
23
+ | Verdict translation | Exit `0` is `upheld`, exit `2` is `blocked`, everything else is `unjudged` |
24
+
25
+ This package writes no telemetry row. Every row a run leaves is written by the child process,
26
+ where the judgment happened, so one call still leaves one row.
27
+
28
+ <a id="install"></a>
29
+ ## Install
30
+
31
+ ```sh
32
+ pnpm add @polydeukes/sdk-ts polydeukes @polydeukes/core
33
+ ```
34
+
35
+ There is no bin and no install step. The umbrella supplies the judge the SDK spawns, and the
36
+ core supplies the `CovenantInput` type the caller fills in.
37
+
38
+ <a id="verb"></a>
39
+ ## The verb
40
+
41
+ ```ts
42
+ import { checkCovenant } from '@polydeukes/sdk-ts';
43
+
44
+ const verdict = await checkCovenant({
45
+ repoRoot: '/path/to/the/project',
46
+ input: {
47
+ toolCalls: [
48
+ {
49
+ name: 'writeFile',
50
+ args: { path: 'src/index.ts', content: 'export const answer = 42;\n' },
51
+ fileChange: {
52
+ kind: 'modify',
53
+ path: 'src/index.ts',
54
+ pre: 'export const answer = 41;\n',
55
+ post: 'export const answer = 42;\n',
56
+ },
57
+ },
58
+ ],
59
+ subagentSpawns: [],
60
+ userMessages: [],
61
+ tools: { mutating: ['writeFile', 'rm'], shell: ['exec'], commandArgs: ['command'] },
62
+ },
63
+ });
64
+ ```
65
+
66
+ The IR is the caller's. This package neither reads it nor completes it: it adds no `session`,
67
+ no `actor`, and no roster of its own, and the `tools` values above are the caller's own tool
68
+ names. `subagentSpawns` and `userMessages` are required collections, so a caller with neither
69
+ sends the empty arrays. A `world` key is refused by the judge — the runner reads the world from
70
+ the project on disk, and a client choosing the world would be choosing what is judged.
71
+
72
+ <a id="spec"></a>
73
+ ## The spec
74
+
75
+ ```ts
76
+ type CheckCovenantSpec = {
77
+ repoRoot: string;
78
+ input: CovenantInput;
79
+ enforce?: 'advise' | 'block';
80
+ spawn?: (spec: CheckCovenantSpawnSpec) => Promise<{ status: number | null; stderr: string }>;
81
+ };
82
+
83
+ type CheckCovenantSpawnSpec = { command: string; args: string[]; cwd: string; stdin: string };
84
+ ```
85
+
86
+ | Field | What it is |
87
+ |---|---|
88
+ | `repoRoot` | The project being judged: config discovery, the world axis, the child's cwd, and the install graph the umbrella is found in |
89
+ | `input` | The caller's own IR, sent verbatim as the child's stdin |
90
+ | `enforce` | The observer's posture for the whole run. **Absent is `block`** |
91
+ | `spawn` | An injected spawn seam. Absent, the child runs under this process's node executable |
92
+
93
+ **`enforce` defaults to `block`.** That is the surface's level, not an entry's: protected paths
94
+ and entries carrying `enforce: block` stop the call, and every other break is recorded
95
+ `advised` at exit 0. An entry's own level composes with it lenient-side-wins, as on every other
96
+ surface. `@polydeukes/adapter-claude-code` and `@polydeukes/adapter-grok` spawn the judge at
97
+ the same level.
98
+
99
+ The default spawn inherits no file descriptor. A caller may hold none of its own, and an
100
+ inherited stdout that is closed would kill the child with EPIPE before it answered. stderr is
101
+ collected and returned; stdout is drained and dropped, because the judge writes no verdict
102
+ there.
103
+
104
+ <a id="verdicts"></a>
105
+ ## The three verdicts
106
+
107
+ ```ts
108
+ type CheckCovenantVerdict =
109
+ | { verdict: 'upheld'; advisories: string }
110
+ | { verdict: 'blocked'; reason: string }
111
+ | { verdict: 'unjudged'; reason: string };
112
+ ```
113
+
114
+ | Verdict | Child status | What it means for the caller |
115
+ |---|---|---|
116
+ | `upheld` | `0` | The call was judged and nothing blocked it. `advisories` is the child's stderr verbatim, carrying any advisory lines the run produced. Proceed |
117
+ | `blocked` | `2` | The call was judged and something blocked it. `reason` is the child's stderr verbatim. Do not proceed |
118
+ | `unjudged` | anything else, or no umbrella | No judgment happened. `reason` says which. Reading it as an uphold would let an uninstalled judge pass every call |
119
+
120
+ **There is no valve, and the reason is the substitute.** An unattended real-time caller has no
121
+ TTY and no human turn, so this package takes no witness argument and invents no session.
122
+ `blocked.reason` and `upheld.advisories` come back as data instead, and the consumer decides
123
+ where the text goes — an issue, a log, or back to the model. The posture rules for a config
124
+ author and for a consumer are in [write disciplines](../../how-to/write-disciplines.md#posture).
125
+
126
+ <a id="failure"></a>
127
+ ## A failure example
128
+
129
+ When the project has no `polydeukes` installed there is nothing to spawn, and the verb says so
130
+ rather than answering `upheld`:
131
+
132
+ ```ts
133
+ const verdict = await checkCovenant({ repoRoot: '/tmp/project-without-polydeukes', input });
134
+
135
+ // {
136
+ // verdict: 'unjudged',
137
+ // reason: 'no polydeukes in the install graph of /tmp/project-without-polydeukes:
138
+ // install it to have this input judged',
139
+ // }
140
+ ```
141
+
142
+ No child process runs, and the telemetry log gains nothing: the row is written where the
143
+ judgment happens, and no judgment happened.
144
+
145
+ <a id="limits"></a>
146
+ ## Declared limits
147
+
148
+ - **The caller builds the IR.** The tool roster, the pre-state, and the envelope are the
149
+ host's facts, so a consumer that knows them fills them in. This package supplies none of
150
+ them.
151
+ - **The SDK exposes the session surface only.** The input travels as an IR on stdin, which is
152
+ what makes the run a session-surface judgment. A caller that has a finished change set pipes
153
+ a unified diff to `pdks covenant check --diff` from its shell instead.
154
+ - **No telemetry row is written here.** Every row comes from the child.
155
+ - **An `unjudged` verdict is not a pass.** It records that the judge did not answer, and the
156
+ consumer decides what a project without a judge is allowed to do.
157
+
158
+ <a id="see-also"></a>
159
+ ## See also
160
+
161
+ - [`pdks covenant check`](../cli/covenant-check.md)
162
+ - [`polydeukes`](polydeukes.md)
163
+ - [`@polydeukes/core`](core.md)
164
+ - [Configuration reference](../configuration/index.md)