@polydeukes/adapter-claude-code 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 CHANGED
@@ -1,40 +1,49 @@
1
- # @polydeukes/adapter-claude-code
2
-
3
- **한국어** · [English](./README.md)
4
-
5
- > Claude Code 어휘가 번역되어 사라지는 경계입니다. PreToolUse 페이로드는 코어에 닿기 전에 에이전트 중립적인 약속(covenant) 입력 IR로 바뀝니다.
6
-
7
- **알파(alpha) 단계입니다.** 에이전트와 도구의 리터럴은 의도적으로 이 패키지 안에만 삽니다. 이 패키지가 존재하는 이유가 리터럴이 코어에 닿지 않게 하는 것이고,
8
- 그래서 코어의 에이전트 중립성은 구호가 아니라 시험 가능한 주장으로 남습니다.
9
-
10
- ## 여기 담긴
11
-
12
- - **페이로드 번역.** 날것의 PreToolUse 페이로드가 `CovenantInput`(도구 호출과 서브에이전트 스폰)으로 바뀝니다. 서브에이전트 유형을 실은 `Task`
13
- 호출은 스폰으로 대응되고, 분류할 수 없는 페이로드는 번역 실패입니다. 실패는 추측으로 대체되는 대신 차단됩니다(fail-closed).
14
- - **가상 사후 상태(virtual post-state).** `Edit`·`Write`·`MultiEdit`가 적용되면 파일이 어떤 내용이 될지를 디스크를 건드리지 않고
15
- 계산합니다. 내용을 보는 약속은 지금의 파일이 아니라 제안된 결과를 판정합니다. 여러 편집의 순차 적용과 파일 생성 관례까지 계산에 들어갑니다.
16
- - **파일 변경 증거.** 디스크의 편집 전 상태와 가상 사후 상태를 짝지어 판별 유니온(discriminated union) 증거(편집 전 상태가 없으면 `create`,
17
- 있으면 `modify`)로 만들고, 변이 도구 호출 자신의 `fileChange` 필드에 싣습니다. 규율(discipline) 판정이 소비하는 증거가 이것입니다. 사후 상태가
18
- 성립하지 않으면 아무것도 내지 않고(같은 편집은 실제 도구도 거부합니다), 비변이 호출에 증거를 날조하지 않습니다.
19
- - **대화 기록 공급자.** `transcriptFromJsonlFile`이 세션 JSONL 파일을 `CanonicalTranscript`로 바꿉니다. 시간제 증인(TTL
20
- witness)의 실제 데이터 원천이며, 사람이 직접 친 것으로 확인된 메시지만 받아들이므로 AI가 자기 증언을 합성할 길이 없습니다. `findToolCalls`에도 답해
21
- `tool_use` 블록에서 도구 호출을 뽑아냅니다. 호출의 `input`이 평면 객체가 아니면 블록은 남기고 `args`만 비우는데, 호출이 있었다는 사실 자체가 증거이기
22
- 때문입니다. 읽기에 실패하면 빈 대화 기록이 아니라 `undefined`로 답합니다. 빈 세션은 아직 아무 말도 하지 않은 세션이라 판정 대상이고, 읽히지 않는 세션은 증거
23
- 통로 자체가 없어서 건너뜁니다. 어느 쪽이든 밸브는 닫히지, 열리지 않습니다.
24
- - **세션 공급 리더.** `sessionSourceReader({ repoRoot })`는 이 표면의 파일 리더입니다(작업 트리 위의 `SourceReader` —
25
- 항목 없음 · 디렉터리 · NUL 바이트 같은 부재는 `undefined`로 답하고, 권한 거부는 던집니다).
26
- `sessionChannelReader({ transcriptPath })`는 이 표면의 스폰 사이드카 채널입니다. 이 호스트가 대화 기록 옆
27
- `<dir>/<세션ID>/subagents/agent-*.meta.json`에 남기는 서브에이전트 기록을 파일명 정렬 순서의 JSON 배열 하나로 답합니다.
28
- `'[]'`는 스폰을 관측하지 못한 채널이고 `undefined`는 채널 자체가 없는 것 — 서로 다른 두 사실이며, 선언의 `supply` 정책이
29
- 처분하는 것은 둘째뿐입니다.
30
- 표면이 파싱하는 대화 기록(transcript)은 선언의 소스이기도 합니다(`{ transcript: true }`). 약속(covenant) 패키지가
31
- 공급 시점에 스냅샷으로 펴므로 이 어댑터는 그것을 위한 리더를 더하지 않습니다.
32
- - **텔레메트리 배선.** `runAdapterPath`가 깔때기 전체를 끝까지 실행합니다. 날 페이로드에서 번역(실패는 차단 1행을 기록하고 `2`로 종료), 주입된 이음새를
33
- 통한 판정, 깔때기 완결까지 이어져 호출마다 정확히 1행이 남습니다. 증언(witnessed)도 예외가 아닙니다. 판정 이음새를 주입으로 받는 이유는 이 패키지가
34
- covenant 패키지를 직접 가져오지(import) 않기 때문입니다. 의존은 코어를 통해서만, 단방향으로 흐릅니다.
35
-
36
- 아키텍처 청사진과 설계 근거는 [프로젝트 저장소](https://github.com/huskyhoochu/polydeukes)에 있습니다.
37
-
38
- ## 라이선스
39
-
40
- MIT
1
+ # `@polydeukes/adapter-claude-code`
2
+
3
+ [English](./README.md) · **한국어**
4
+
5
+ 어댑터는 Claude Code 세션 표면의 설치 단위입니다. Claude Code PreToolUse 페이로드를
6
+ 약속(covenant) 입력 IR로 변환하고, 판정기를 스폰하며, 프로젝트에 세션 표면을 등록하는
7
+ `pdks-claude-code` 실행 파일을 제공합니다. 우산 패키지가 세션 표면에 주입할 읽기 함수를
8
+ 만드는 팩터리도 제공합니다.
9
+
10
+ `polydeukes`와 함께 설치합니다. `polydeukes`는 이 패키지의 `peerDependency`입니다.
11
+
12
+ ```sh
13
+ npm install --save-dev polydeukes @polydeukes/adapter-claude-code
14
+ npx pdks-claude-code init
15
+ ```
16
+
17
+ <a id="overview"></a>
18
+ ## 개요
19
+
20
+ 공개 계약 심볼은 다음과 같습니다.
21
+
22
+ - `runHook`
23
+ - `sessionSourceReader`
24
+ - `sessionChannelReader`
25
+ - `sessionEvidenceFromPayload`
26
+ - `transcriptPathFromPayload`
27
+ - `transcriptFromJsonlFile`
28
+ - `COMMAND_ARGS`
29
+ - `MUTATING_TOOLS`
30
+ - `SHELL_TOOLS`
31
+
32
+ <a id="examples"></a>
33
+ ## 예제
34
+
35
+ ```ts
36
+ import { runHook } from '@polydeukes/adapter-claude-code';
37
+
38
+ // 표준 입력에서 페이로드를 읽어 repoRoot에서 `pdks covenant check --enforce block`을 스폰하고
39
+ // 그 자식 프로세스의 종료 코드를 돌려줍니다. 생성된 훅 위임자가 부르는 함수입니다.
40
+ const { exitCode } = runHook({ repoRoot: process.cwd() });
41
+ ```
42
+
43
+ <a id="see-also"></a>
44
+ ## 같이 보기
45
+
46
+ - [`@polydeukes/adapter-claude-code` 패키지
47
+ 레퍼런스](../../docs/reference/packages/adapter-claude-code.ko.md)
48
+ - [우산의 진입점](../../docs/reference/packages/polydeukes.ko.md#polydeukes-entry-points)
49
+ - [판정기(`covenant` 모듈)](../../docs/reference/packages/polydeukes.ko.md#covenant-module)
package/README.md CHANGED
@@ -1,55 +1,49 @@
1
- # @polydeukes/adapter-claude-code
2
-
3
- **English** ·
4
- [한국어](https://github.com/huskyhoochu/polydeukes/blob/main/packages/adapter-claude-code/README.ko.md)
5
-
6
- > The boundary where Claude Code's vocabulary is translated away. PreToolUse hook payloads become
7
- the agent-neutral covenant input IR before anything reaches the core.
8
-
9
- **Alpha.** Agent and tool literals live *here* by design — this package exists so they never reach
10
- the core, which is how the core's agent-neutrality stays a testable claim rather than a slogan.
11
-
12
- ## What lives here
13
-
14
- - **Payload up-translation** — a raw PreToolUse payload becomes a `CovenantInput` (tool calls and
15
- subagent spawns). A `Task` call carrying a subagent type maps to a spawn; a payload that cannot be
16
- classified is a translation *failure*, and failures block (fail-closed) instead of degrading into
17
- a guess.
18
- - **Virtual post-state** — computes what a file *would* contain after an `Edit`/`Write`/`MultiEdit`
19
- applies, without touching disk. Content-aware covenants judge the proposed result, not the file as
20
- it currently is — including sequential multi-edit application and file-creation conventions.
21
- - **File-change evidence** — the disk pre-state pairs with the virtual post-state into union
22
- evidence (`create` when no pre-state exists, `modify` otherwise) attached to the mutating tool
23
- call's own `fileChange` field — the evidence discipline judgments consume. An unresolvable
24
- post-state yields nothing (the real tool rejects the same edit), and evidence is never fabricated
25
- for non-mutating calls.
26
- - **Transcript provider** — `transcriptFromJsonlFile` turns a session JSONL file into a
27
- `CanonicalTranscript`, the TTL witness's real data source: only positively-identified human-typed
28
- messages are admitted, so an AI can never synthesize its own witness. It answers `findToolCalls`
29
- too, extracting calls from `tool_use` blocks — when a call's `input` is not a flat object the
30
- block still yields a call with empty `args`, because the existence of the call is itself the
31
- evidence. A read failure answers `undefined` rather than an empty transcript — an empty session
32
- has said nothing yet and is judged, an unreadable one is no evidence channel at all and is
33
- skipped. Either way the valve turns off, never open.
34
- - **Session supply readers** — `sessionSourceReader({ repoRoot })` is this surface's file
35
- reader (a `SourceReader` over the working tree: absence — no entry, a directory, NUL bytes —
36
- answers `undefined`, a permission refusal throws), and `sessionChannelReader({ transcriptPath })`
37
- is its spawn-sidecar channel: the subagent records this host keeps at
38
- `<dir>/<sessionId>/subagents/agent-*.meta.json` beside the transcript, answered as one JSON
39
- array in filename order. `'[]'` is a channel that observed no spawn; `undefined` is no
40
- channel at all two different facts, and the declaration's `supply` policy disposes only of
41
- the second. The transcript this surface parses is also a declaration source (`{ transcript:
42
- true }`): the covenant flattens it into a snapshot at supply time, so this adapter adds no
43
- reader for it.
44
- - **Telemetry wiring** — `runAdapterPath` drives the full funnel: raw payload → translation (a
45
- failure logs one blocked record and exits `2`) → dispatch through an *injected* seam → funnel
46
- completion, so exactly one record lands per call, witnessed rows included. The dispatch seam is
47
- injected because this package never imports the covenant package — dependencies stay one-way,
48
- through the core only.
49
-
50
- See the [project repository](https://github.com/huskyhoochu/polydeukes) for the architecture
51
- blueprint and design rationale.
52
-
53
- ## License
54
-
55
- MIT
1
+ # `@polydeukes/adapter-claude-code`
2
+
3
+ **English** · [한국어](./README.ko.md)
4
+
5
+ This adapter is the install unit for the Claude Code session surface. It translates Claude Code
6
+ PreToolUse payloads into covenant input IR, spawns the judge, and ships the `pdks-claude-code`
7
+ bin that registers the surface in a project. It also provides the session-side reader factories
8
+ the umbrella injects.
9
+
10
+ Install it next to `polydeukes`, which it names as a `peerDependency`:
11
+
12
+ ```sh
13
+ npm install --save-dev polydeukes @polydeukes/adapter-claude-code
14
+ npx pdks-claude-code init
15
+ ```
16
+
17
+ <a id="overview"></a>
18
+ ## Overview
19
+
20
+ Public contract symbols include:
21
+
22
+ - `runHook`
23
+ - `sessionSourceReader`
24
+ - `sessionChannelReader`
25
+ - `sessionEvidenceFromPayload`
26
+ - `transcriptPathFromPayload`
27
+ - `transcriptFromJsonlFile`
28
+ - `COMMAND_ARGS`
29
+ - `MUTATING_TOOLS`
30
+ - `SHELL_TOOLS`
31
+
32
+ <a id="examples"></a>
33
+ ## Examples
34
+
35
+ ```ts
36
+ import { runHook } from '@polydeukes/adapter-claude-code';
37
+
38
+ // Reads the payload from stdin, spawns `pdks covenant check --enforce block` in repoRoot,
39
+ // and returns that child's exit code. This is what the generated hook delegator calls.
40
+ const { exitCode } = runHook({ repoRoot: process.cwd() });
41
+ ```
42
+
43
+ <a id="see-also"></a>
44
+ ## See also
45
+
46
+ - [`@polydeukes/adapter-claude-code` package
47
+ reference](../../docs/reference/packages/adapter-claude-code.md)
48
+ - [The umbrella's entry points](../../docs/reference/packages/polydeukes.md#polydeukes-entry-points)
49
+ - [The judge (`covenant` module)](../../docs/reference/packages/polydeukes.md#covenant-module)
package/dist/bin.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `pdks-claude-code` — this adapter's bin.
4
+ *
5
+ * A thin argv shim over one subcommand. Anything else prints usage and exits 2: an unknown
6
+ * argument must never be read as `init`, because a typo would then install into whatever
7
+ * directory the user happened to be in.
8
+ */
9
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `pdks-claude-code` — this adapter's bin.
4
+ *
5
+ * A thin argv shim over one subcommand. Anything else prints usage and exits 2: an unknown
6
+ * argument must never be read as `init`, because a typo would then install into whatever
7
+ * directory the user happened to be in.
8
+ */
9
+ import { initClaudeCode } from './init.js';
10
+ const args = process.argv.slice(2);
11
+ if (args.length === 1 && args[0] === 'init') {
12
+ try {
13
+ const { created, skipped } = initClaudeCode({ projectRoot: process.cwd() });
14
+ for (const path of created) {
15
+ process.stdout.write(`created ${path}\n`);
16
+ }
17
+ for (const path of skipped) {
18
+ process.stdout.write(`skipped ${path} (already present)\n`);
19
+ }
20
+ process.exit(0);
21
+ }
22
+ catch (error) {
23
+ // A precondition failure leaves zero files; the message names what the user has to do
24
+ // before running this again.
25
+ process.stderr.write(`pdks-claude-code init failed: ${error instanceof Error ? error.message : String(error)}\n`);
26
+ process.exit(2);
27
+ }
28
+ }
29
+ process.stderr.write('usage: pdks-claude-code init\n');
30
+ process.exit(2);
package/dist/hook.d.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `runHook` — the session surface's entry point: one PreToolUse payload in, one
3
+ * `pdks covenant check` process out, its status back as the exit code.
4
+ *
5
+ * This package judges nothing and writes no telemetry row. It builds the agent-neutral IR
6
+ * — the translated payload, this host's tool roster, the file-change evidence, the session
7
+ * evidence — and hands it to the umbrella's bin on stdin. Every verdict, and every row, is
8
+ * the child's.
9
+ *
10
+ * A failure before the spawn travels IN the spawn: the failure sentence replaces the IR on
11
+ * stdin, the child fails closed on it as non-JSON, and the one row that call earns is
12
+ * written by the one writer. Only an unresolvable `polydeukes` skips the spawn, because
13
+ * then there is no writer at all.
14
+ */
15
+ /** The spawn seam's parameters — what the child is asked to run, where, and on stdin. */
16
+ export type RunHookSpawnSpec = {
17
+ command: string;
18
+ args: string[];
19
+ cwd: string;
20
+ stdin: string;
21
+ };
22
+ /** {@link runHook} input — the project being judged, the payload, and the spawn seam. */
23
+ export type RunHookSpec = {
24
+ /**
25
+ * The project root: the config the child discovers, and the install graph `polydeukes`
26
+ * is located in. A delegator derives it from its own location, never from a cwd a host
27
+ * chose.
28
+ */
29
+ repoRoot: string;
30
+ /** Raw hook stdin — one PreToolUse payload as JSON. Absent reads fd 0. */
31
+ rawPayload?: string;
32
+ /**
33
+ * Injected spawn seam. Absent spawns node on the located bin with stdout and stderr
34
+ * inherited, so the child's break reason reaches the host.
35
+ */
36
+ spawn?: (spec: RunHookSpawnSpec) => {
37
+ status: number | null;
38
+ };
39
+ };
40
+ /** {@link runHook} result — the exit code the hook process leaves with. */
41
+ export type RunHookOutcome = {
42
+ exitCode: 0 | 2;
43
+ };
44
+ /**
45
+ * Judge one PreToolUse payload by spawning the umbrella's judge over its IR.
46
+ *
47
+ * Only a child status of 0 passes through as 0. A crashed child (1) or a signalled one
48
+ * (null) is not a verdict, and forwarding either as-is would let the host read a non-2 as
49
+ * "not blocked".
50
+ */
51
+ export declare function runHook(spec: RunHookSpec): RunHookOutcome;
package/dist/hook.js ADDED
@@ -0,0 +1,142 @@
1
+ /**
2
+ * `runHook` — the session surface's entry point: one PreToolUse payload in, one
3
+ * `pdks covenant check` process out, its status back as the exit code.
4
+ *
5
+ * This package judges nothing and writes no telemetry row. It builds the agent-neutral IR
6
+ * — the translated payload, this host's tool roster, the file-change evidence, the session
7
+ * evidence — and hands it to the umbrella's bin on stdin. Every verdict, and every row, is
8
+ * the child's.
9
+ *
10
+ * A failure before the spawn travels IN the spawn: the failure sentence replaces the IR on
11
+ * stdin, the child fails closed on it as non-JSON, and the one row that call earns is
12
+ * written by the one writer. Only an unresolvable `polydeukes` skips the spawn, because
13
+ * then there is no writer at all.
14
+ */
15
+ import { spawnSync } from 'node:child_process';
16
+ import { readFileSync } from 'node:fs';
17
+ import { EXIT_BREAK_BLOCKING, EXIT_UPHOLD } from '@polydeukes/core';
18
+ import { collectFileChanges } from './file-changes.js';
19
+ import { findUmbrellaBin, UMBRELLA_PACKAGE } from './resolve-umbrella.js';
20
+ import { sessionEvidenceFromPayload } from './session-evidence.js';
21
+ import { COMMAND_ARGS, MUTATING_TOOLS, SHELL_TOOLS } from './session-vocabulary.js';
22
+ import { buildCovenantInput } from './up-translate.js';
23
+ /** The subcommand and posture the session surface always spawns with. */
24
+ const CHECK_ARGS = ['covenant', 'check', '--enforce', 'block'];
25
+ /** The prefix a pre-spawn failure travels under, so an operator can find it in the log. */
26
+ const FAILURE_PREFIX = 'adapter-claude-code failed before spawn:';
27
+ /**
28
+ * Real-fs pre-state reader for file changes — `null` only for true absence (ENOENT).
29
+ *
30
+ * Any other read failure (permissions, a directory target, fd exhaustion) throws: `null` is
31
+ * the IR's creation sentinel, and a poisoned `pre: null` on an existing file would let a
32
+ * path-family discipline uphold the overwrite. The caller turns the throw into a failure
33
+ * sentence the child fails closed on.
34
+ */
35
+ function readPreStateFromDisk(filePath) {
36
+ try {
37
+ return readFileSync(filePath, 'utf-8');
38
+ }
39
+ catch (error) {
40
+ if (error.code === 'ENOENT')
41
+ return null;
42
+ throw error;
43
+ }
44
+ }
45
+ /** A pre-spawn failure carrying the step that produced it — the operator's only trace. */
46
+ class PreSpawnFailure extends Error {
47
+ }
48
+ /**
49
+ * Build the IR one payload proves, as the JSON text the child reads from stdin.
50
+ *
51
+ * Throws {@link PreSpawnFailure} naming the step that failed. The four steps fail for
52
+ * different reasons and need different repairs, so each names itself rather than sharing
53
+ * one sentence.
54
+ */
55
+ function buildStdin(rawPayload) {
56
+ let payload;
57
+ try {
58
+ payload = JSON.parse(rawPayload);
59
+ }
60
+ catch (error) {
61
+ throw new PreSpawnFailure(`the payload is not JSON (${error instanceof Error ? error.message : String(error)})`);
62
+ }
63
+ const built = buildCovenantInput([payload]);
64
+ if (built.ok !== true) {
65
+ throw new PreSpawnFailure('the payload is not a PreToolUse envelope this adapter translates');
66
+ }
67
+ // Attached to the call it belongs to: this path translates exactly one payload, so the
68
+ // one evidence rides toolCalls[0]. Left ABSENT rather than null when unprovable — the
69
+ // protocol refuses a null fileChange, and a call the judge should fall back on would
70
+ // fail closed instead.
71
+ let evidence;
72
+ try {
73
+ evidence = collectFileChanges(payload, readPreStateFromDisk);
74
+ }
75
+ catch (error) {
76
+ throw new PreSpawnFailure(`the pre-state of the target could not be read (${error instanceof Error ? error.message : String(error)})`);
77
+ }
78
+ let session;
79
+ try {
80
+ session = sessionEvidenceFromPayload({ rawPayload });
81
+ }
82
+ catch (error) {
83
+ throw new PreSpawnFailure(`the session evidence could not be gathered (${error instanceof Error ? error.message : String(error)})`);
84
+ }
85
+ return JSON.stringify({
86
+ ...built.value,
87
+ toolCalls: evidence === null
88
+ ? built.value.toolCalls
89
+ : built.value.toolCalls.map((call, index) => index === 0 ? { ...call, fileChange: evidence } : call),
90
+ tools: { mutating: MUTATING_TOOLS, shell: SHELL_TOOLS, commandArgs: COMMAND_ARGS },
91
+ ...(session === undefined ? {} : { session }),
92
+ });
93
+ }
94
+ /** The default seam: node on the located bin, the child's own output reaching the host. */
95
+ function spawnCovenantCheck(spec) {
96
+ return spawnSync(spec.command, spec.args, {
97
+ cwd: spec.cwd,
98
+ input: spec.stdin,
99
+ stdio: ['pipe', 'inherit', 'inherit'],
100
+ });
101
+ }
102
+ /**
103
+ * Judge one PreToolUse payload by spawning the umbrella's judge over its IR.
104
+ *
105
+ * Only a child status of 0 passes through as 0. A crashed child (1) or a signalled one
106
+ * (null) is not a verdict, and forwarding either as-is would let the host read a non-2 as
107
+ * "not blocked".
108
+ */
109
+ export function runHook(spec) {
110
+ const bin = findUmbrellaBin(spec.repoRoot);
111
+ if (bin === undefined) {
112
+ // The one outcome with no row anywhere: nothing to spawn means no writer exists. The
113
+ // line names the package because installing it is the operator's next action.
114
+ process.stderr.write(`covenant hook failed closed: cannot use '${UMBRELLA_PACKAGE}' from ${spec.repoRoot} — ` +
115
+ 'install or update it there, then try again\n');
116
+ return { exitCode: EXIT_BREAK_BLOCKING };
117
+ }
118
+ const rawPayload = spec.rawPayload ?? readFileSync(0, 'utf-8');
119
+ let stdin;
120
+ try {
121
+ stdin = buildStdin(rawPayload);
122
+ }
123
+ catch (error) {
124
+ stdin = `${FAILURE_PREFIX} ${error instanceof Error ? error.message : String(error)}\n`;
125
+ // The child fails closed on this line as non-JSON, but its own stderr names only the
126
+ // parse failure. The reason reaches the operator from here.
127
+ process.stderr.write(stdin);
128
+ }
129
+ const spawn = spec.spawn ?? spawnCovenantCheck;
130
+ const { status } = spawn({
131
+ command: process.execPath,
132
+ args: [bin, ...CHECK_ARGS],
133
+ cwd: spec.repoRoot,
134
+ stdin,
135
+ });
136
+ if (status !== EXIT_UPHOLD && status !== EXIT_BREAK_BLOCKING) {
137
+ // Not a verdict: the judge crashed or was signalled, so no row was written. Exit 2 is
138
+ // still right for the host; the line is what tells the operator this was not a break.
139
+ process.stderr.write(`covenant hook failed closed: the judge exited with status ${String(status)} before a verdict\n`);
140
+ }
141
+ return { exitCode: status === EXIT_UPHOLD ? EXIT_UPHOLD : EXIT_BREAK_BLOCKING };
142
+ }
package/dist/index.d.ts CHANGED
@@ -7,8 +7,9 @@
7
7
  * this package is the boundary where Claude Code's vocabulary is translated away before it
8
8
  * reaches the core.
9
9
  */
10
- export { type AdapterPathOutcome, type DispatchAdapterView, type RunAdapterPathSpec, runAdapterPath, } from './run-adapter-path.ts';
10
+ export { type RunHookOutcome, type RunHookSpec, runHook } from './hook.ts';
11
11
  export { type SessionChannelReaderSpec, sessionChannelReader, } from './session-channel-reader.ts';
12
+ export { type SessionEvidenceFromPayloadSpec, type SessionEvidenceOutcome, sessionEvidenceFromPayload, } from './session-evidence.ts';
12
13
  export { type SessionSourceReaderSpec, sessionSourceReader } from './session-source-reader.ts';
13
14
  export { COMMAND_ARGS, MUTATING_TOOLS, SHELL_TOOLS, type TranscriptPathFromPayloadSpec, transcriptPathFromPayload, } from './session-vocabulary.ts';
14
15
  export { type TranscriptFromJsonlFileSpec, transcriptFromJsonlFile } from './transcript.ts';
package/dist/index.js CHANGED
@@ -7,8 +7,9 @@
7
7
  * this package is the boundary where Claude Code's vocabulary is translated away before it
8
8
  * reaches the core.
9
9
  */
10
- export { runAdapterPath, } from './run-adapter-path.js';
10
+ export { runHook } from './hook.js';
11
11
  export { sessionChannelReader, } from './session-channel-reader.js';
12
+ export { sessionEvidenceFromPayload, } from './session-evidence.js';
12
13
  export { sessionSourceReader } from './session-source-reader.js';
13
14
  export { COMMAND_ARGS, MUTATING_TOOLS, SHELL_TOOLS, transcriptPathFromPayload, } from './session-vocabulary.js';
14
15
  export { transcriptFromJsonlFile } from './transcript.js';
package/dist/init.d.ts ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `initClaudeCode` — the Claude Code session-surface installer.
3
+ *
4
+ * One command wires a project into the session surface: prove `polydeukes` resolves, spawn
5
+ * its agent-neutral scaffold (`pdks init`, which writes the config and the telemetry ignore
6
+ * line), then add what this package owns — the delegator hook file, its
7
+ * `.claude/settings.json` registration, the discipline file that tells an agent the docs
8
+ * query exists, and the classification skill that turns a described problem into a config
9
+ * entry.
10
+ *
11
+ * Preflight comes first and nothing is written before it clears. A generated hook whose
12
+ * import can never resolve blocks every call through its own fail-closed catch, and a tree
13
+ * that also has no config and no valve to open cannot be edited back into shape from inside
14
+ * the session.
15
+ *
16
+ * Nothing existing is overwritten. The settings file in particular is merged, never
17
+ * replaced: a consumer's other PreToolUse registrations and permissions are live
18
+ * configuration, and replacing them would disarm every other tool they wired.
19
+ */
20
+ /** What one install left behind, per artifact. */
21
+ export type InitClaudeCodeReport = {
22
+ created: string[];
23
+ skipped: string[];
24
+ };
25
+ /** {@link initClaudeCode} input — the target tree and its two seams. */
26
+ export type InitClaudeCodeSpec = {
27
+ /** Project root to install into — every write below is relative to it. */
28
+ projectRoot: string;
29
+ /**
30
+ * Preflight seam: the absolute path of the umbrella's `pdks` bin as reachable from the
31
+ * given root, or a throw when the umbrella cannot be resolved there. ABSENT uses the real
32
+ * resolution, anchored at that root and nowhere else — anchoring it at the installer's
33
+ * own module would answer for the installer's install graph rather than the target
34
+ * project's, which is precisely the case that must fail.
35
+ */
36
+ resolvePolydeukes?: (projectRoot: string) => string;
37
+ /**
38
+ * The `pdks init` spawn seam. ABSENT spawns node on the located bin with the child's
39
+ * output inherited.
40
+ */
41
+ spawnScaffold?: (spec: {
42
+ command: string;
43
+ args: string[];
44
+ cwd: string;
45
+ }) => {
46
+ status: number | null;
47
+ };
48
+ };
49
+ /**
50
+ * Install the Claude Code session surface into `spec.projectRoot`, skipping whatever is
51
+ * already there and reporting both halves per artifact.
52
+ *
53
+ * Throws before any write when the umbrella cannot be resolved from that root, when the
54
+ * settings file cannot be parsed, or when the scaffold refuses — all three leave zero files.
55
+ * Translating a throw into exit 2 with the install command is the bin's job.
56
+ */
57
+ export declare function initClaudeCode(spec: InitClaudeCodeSpec): InitClaudeCodeReport;