@polydeukes/adapter-claude-code 0.3.0 → 0.5.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.
@@ -1,21 +1,19 @@
1
1
  /**
2
- * File-change evidence computation (COVENANT-10 §4.3) — turns one Edit/Write/MultiEdit/
3
- * NotebookEdit payload into the agent-neutral `FileChange` evidence the discipline layer
4
- * judges.
2
+ * File-change evidence computation — turns one Edit/Write/MultiEdit/NotebookEdit
3
+ * payload into the agent-neutral `FileChange` evidence the discipline layer judges.
5
4
  *
6
5
  * Pure translation: pre-state comes through an injected reader (disk is the caller's
7
6
  * choice), post-state through `virtualPostState`. An unresolvable post-state OMITS the
8
- * element (the specified disposition, PRD §4.3 — the real tool rejects the same edit,
9
- * so there is no change to judge); non-mutating tools and unparseable envelopes yield
10
- * an empty array, never an error.
7
+ * element — the real tool rejects the same edit, so there is no change to judge;
8
+ * non-mutating tools and unparseable envelopes yield an empty array, never an error.
11
9
  */
12
10
  import type { FileChange } from '@polydeukes/core';
13
11
  /**
14
- * Collect the file-change evidence of one raw PreToolUse payload (COVENANT-10 §4.3).
12
+ * Collect the file-change evidence of one raw PreToolUse payload.
15
13
  *
16
14
  * `readPreState` returns the target file's current content, `null` when it does not
17
- * exist — that absence IS the union discriminant (CORE-06 §4.2): no pre-state tags a
18
- * `create`, an existing one a `modify` (these tools cannot delete). Evidence is
15
+ * exist — that absence IS the union discriminant: no pre-state tags a `create`, an
16
+ * existing one a `modify` (these tools cannot delete). Evidence is
19
17
  * singular like its IR home (`toolCall.fileChange`): one payload proves at most one
20
18
  * change, and `null` means nothing provable. NotebookEdit names its target differently
21
19
  * and proves a cell rather than a file, so it branches into its own computation.
@@ -1,13 +1,11 @@
1
1
  /**
2
- * File-change evidence computation (COVENANT-10 §4.3) — turns one Edit/Write/MultiEdit/
3
- * NotebookEdit payload into the agent-neutral `FileChange` evidence the discipline layer
4
- * judges.
2
+ * File-change evidence computation — turns one Edit/Write/MultiEdit/NotebookEdit
3
+ * payload into the agent-neutral `FileChange` evidence the discipline layer judges.
5
4
  *
6
5
  * Pure translation: pre-state comes through an injected reader (disk is the caller's
7
6
  * choice), post-state through `virtualPostState`. An unresolvable post-state OMITS the
8
- * element (the specified disposition, PRD §4.3 — the real tool rejects the same edit,
9
- * so there is no change to judge); non-mutating tools and unparseable envelopes yield
10
- * an empty array, never an error.
7
+ * element — the real tool rejects the same edit, so there is no change to judge;
8
+ * non-mutating tools and unparseable envelopes yield an empty array, never an error.
11
9
  */
12
10
  import { isPlainObject } from '@polydeukes/core';
13
11
  import { parsePayloadEnvelope } from './payload-envelope.js';
@@ -44,7 +42,7 @@ function cellSource(cells, cellId) {
44
42
  return Array.isArray(source) ? source.join('') : null;
45
43
  }
46
44
  /**
47
- * Cell-level evidence for one NotebookEdit payload (COVENANT-10b §2-d).
45
+ * Cell-level evidence for one NotebookEdit payload.
48
46
  *
49
47
  * The judged quantity is the target CELL's source, not the notebook's serialization:
50
48
  * `path` names the notebook while `pre`/`post` carry the cell's text, so a banned word
@@ -89,11 +87,11 @@ function collectNotebookChange(toolInput, readPreState) {
89
87
  };
90
88
  }
91
89
  /**
92
- * Collect the file-change evidence of one raw PreToolUse payload (COVENANT-10 §4.3).
90
+ * Collect the file-change evidence of one raw PreToolUse payload.
93
91
  *
94
92
  * `readPreState` returns the target file's current content, `null` when it does not
95
- * exist — that absence IS the union discriminant (CORE-06 §4.2): no pre-state tags a
96
- * `create`, an existing one a `modify` (these tools cannot delete). Evidence is
93
+ * exist — that absence IS the union discriminant: no pre-state tags a `create`, an
94
+ * existing one a `modify` (these tools cannot delete). Evidence is
97
95
  * singular like its IR home (`toolCall.fileChange`): one payload proves at most one
98
96
  * change, and `null` means nothing provable. NotebookEdit names its target differently
99
97
  * and proves a cell rather than a file, so it branches into its own computation.
package/dist/index.d.ts CHANGED
@@ -1,79 +1,15 @@
1
1
  /**
2
2
  * @polydeukes/adapter-claude-code — up-translates Claude Code PreToolUse hook
3
- * payloads into the agent-neutral covenant input IR (ADAPTER-01).
3
+ * payloads into the agent-neutral covenant input IR.
4
4
  *
5
5
  * Pre-alpha. Pure translation only — no I/O, no process spawning. Agent and tool
6
6
  * literals live here by design: this package is the boundary where Claude Code's
7
7
  * vocabulary is translated away before it reaches the core.
8
8
  */
9
- import { type CovenantInput } from '@polydeukes/core';
10
9
  export { collectFileChanges } from './file-changes.js';
11
10
  export { evaluatePrecedent } from './precedent.js';
12
11
  export { type DispatchOutcome, runAdapterPath } from './run-adapter-path.js';
13
12
  export { COMMAND_ARGS, MUTATING_TOOLS, SHELL_TOOLS, transcriptPathFromPayload, } from './session-vocabulary.js';
14
13
  export { transcriptFromJsonl, transcriptFromJsonlFile } from './transcript.js';
14
+ export { buildCovenantInput, type ClaudePreToolUsePayload, type TranslatedEvent, translateEvent, } from './up-translate.js';
15
15
  export { type VirtualPostState, virtualPostState } from './virtual-post-state.js';
16
- /**
17
- * `ClaudePreToolUsePayload` — a Claude Code PreToolUse hook payload (PRD §4.1).
18
- *
19
- * Only `tool_name` and `tool_input` are required; the rest is preserved when present.
20
- * The agent/tool literals this package interprets live in the *values* of these fields,
21
- * never in core — this boundary is the package's reason to exist.
22
- */
23
- export type ClaudePreToolUsePayload = {
24
- hook_event_name?: string;
25
- session_id?: string;
26
- transcript_path?: string;
27
- cwd?: string;
28
- tool_name: string;
29
- tool_input: Record<string, unknown>;
30
- };
31
- /**
32
- * `TranslatedEvent` — the result of up-translating one payload (PRD §4.2).
33
- *
34
- * Success carries the IR fragment; failure carries a human-readable `reason`. A `Task`
35
- * with a string `subagent_type` becomes a `subagentSpawn`; every other tool becomes a
36
- * `toolCall`. Classification failure never demotes to a `toolCall` — losing spawn
37
- * evidence is a bypass vector (PRD §4.1).
38
- */
39
- export type TranslatedEvent = {
40
- ok: true;
41
- kind: 'toolCall';
42
- value: {
43
- name: string;
44
- args?: Record<string, unknown>;
45
- };
46
- } | {
47
- ok: true;
48
- kind: 'subagentSpawn';
49
- value: {
50
- kind: string;
51
- };
52
- } | {
53
- ok: false;
54
- reason: string;
55
- };
56
- /**
57
- * Up-translate one Claude Code payload into an IR fragment (pure, PRD §4.2).
58
- *
59
- * Never throws: a payload that cannot be classified — a non-object, a missing/invalid
60
- * `tool_name` or `tool_input`, or a `Task` lacking a string `subagent_type` — resolves
61
- * to `{ ok: false, reason }` (fail-closed, PRD §5.2).
62
- */
63
- export declare function translateEvent(payload: unknown): TranslatedEvent;
64
- /**
65
- * Fold a sequence of payloads into one {@link CovenantInput} (pure, PRD §4.2).
66
- *
67
- * Preserves observation order into `toolCalls` / `subagentSpawns`; `userMessages` is
68
- * always `[]` (ADAPTER-04 supplies witness evidence later). If any payload fails
69
- * classification the whole build fails closed with the blocking exit code — a silent
70
- * drop would be a bypass vector (PRD §5.2/§7).
71
- */
72
- export declare function buildCovenantInput(payloads: unknown[]): {
73
- ok: true;
74
- value: CovenantInput;
75
- } | {
76
- ok: false;
77
- exitCode: 2;
78
- reason: string;
79
- };
package/dist/index.js CHANGED
@@ -1,65 +1,15 @@
1
1
  /**
2
2
  * @polydeukes/adapter-claude-code — up-translates Claude Code PreToolUse hook
3
- * payloads into the agent-neutral covenant input IR (ADAPTER-01).
3
+ * payloads into the agent-neutral covenant input IR.
4
4
  *
5
5
  * Pre-alpha. Pure translation only — no I/O, no process spawning. Agent and tool
6
6
  * literals live here by design: this package is the boundary where Claude Code's
7
7
  * vocabulary is translated away before it reaches the core.
8
8
  */
9
- import { EXIT_BREAK_BLOCKING } from '@polydeukes/core';
10
- import { parsePayloadEnvelope } from './payload-envelope.js';
11
9
  export { collectFileChanges } from './file-changes.js';
12
10
  export { evaluatePrecedent } from './precedent.js';
13
11
  export { runAdapterPath } from './run-adapter-path.js';
14
12
  export { COMMAND_ARGS, MUTATING_TOOLS, SHELL_TOOLS, transcriptPathFromPayload, } from './session-vocabulary.js';
15
13
  export { transcriptFromJsonl, transcriptFromJsonlFile } from './transcript.js';
14
+ export { buildCovenantInput, translateEvent, } from './up-translate.js';
16
15
  export { virtualPostState } from './virtual-post-state.js';
17
- /**
18
- * Up-translate one Claude Code payload into an IR fragment (pure, PRD §4.2).
19
- *
20
- * Never throws: a payload that cannot be classified — a non-object, a missing/invalid
21
- * `tool_name` or `tool_input`, or a `Task` lacking a string `subagent_type` — resolves
22
- * to `{ ok: false, reason }` (fail-closed, PRD §5.2).
23
- */
24
- export function translateEvent(payload) {
25
- const envelope = parsePayloadEnvelope(payload);
26
- if (envelope.ok !== true) {
27
- return { ok: false, reason: envelope.reason };
28
- }
29
- const { toolName, toolInput } = envelope;
30
- if (toolName === 'Task') {
31
- if (typeof toolInput.subagent_type !== 'string') {
32
- return { ok: false, reason: 'Task payload is missing a string subagent_type' };
33
- }
34
- return { ok: true, kind: 'subagentSpawn', value: { kind: toolInput.subagent_type } };
35
- }
36
- return { ok: true, kind: 'toolCall', value: { name: toolName, args: toolInput } };
37
- }
38
- /**
39
- * Fold a sequence of payloads into one {@link CovenantInput} (pure, PRD §4.2).
40
- *
41
- * Preserves observation order into `toolCalls` / `subagentSpawns`; `userMessages` is
42
- * always `[]` (ADAPTER-04 supplies witness evidence later). If any payload fails
43
- * classification the whole build fails closed with the blocking exit code — a silent
44
- * drop would be a bypass vector (PRD §5.2/§7).
45
- */
46
- export function buildCovenantInput(payloads) {
47
- const input = { toolCalls: [], subagentSpawns: [], userMessages: [] };
48
- for (let index = 0; index < payloads.length; index++) {
49
- const translated = translateEvent(payloads[index]);
50
- if (translated.ok !== true) {
51
- return {
52
- ok: false,
53
- exitCode: EXIT_BREAK_BLOCKING,
54
- reason: `payload at index ${index} failed classification: ${translated.reason}`,
55
- };
56
- }
57
- if (translated.kind === 'toolCall') {
58
- input.toolCalls.push(translated.value);
59
- }
60
- else {
61
- input.subagentSpawns.push(translated.value);
62
- }
63
- }
64
- return { ok: true, value: input };
65
- }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Adapter-owned precedent evidence evaluator (COVENANT-13 §4.4).
2
+ * Adapter-owned precedent evidence evaluator.
3
3
  *
4
4
  * The core owns the `command` evidence vocabulary and validates only the container
5
5
  * shape of everything else; ecosystem values — spawn kinds and tool names — are this
@@ -10,16 +10,16 @@
10
10
  */
11
11
  import type { CanonicalTranscript } from '@polydeukes/core';
12
12
  /**
13
- * Judge one `requirePrecedent` evidence object against the session (PRD §4.4).
13
+ * Judge one `requirePrecedent` evidence object against the session.
14
14
  *
15
15
  * - `subagent`: exact spawn-kind equality (a kind is a value, not a pattern).
16
16
  * - `tool`: the observed tool names matched as a regular expression.
17
17
  * - anything else: `undefined` — outside this adapter's vocabulary, including the core's
18
18
  * own `command` key, which the covenant compiler evaluates itself.
19
19
  *
20
- * Both vocabularies require the call to have RUN and reported success (COVENANT-13b
21
- * §4.4): a call the covenant blocked, one the human refused, and one that simply failed
22
- * carry the same outcome, and none of them did the work the discipline demands. The spawn
20
+ * Both vocabularies require the call to have RUN and reported success: a call the
21
+ * covenant blocked, one the human refused, and one that simply failed carry the same
22
+ * outcome, and none of them did the work the discipline demands. The spawn
23
23
  * axis therefore reads the joined tool calls — the spawn query carries no outcome —
24
24
  * identifying a spawn by the same field the transcript provider does.
25
25
  *
package/dist/precedent.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Adapter-owned precedent evidence evaluator (COVENANT-13 §4.4).
2
+ * Adapter-owned precedent evidence evaluator.
3
3
  *
4
4
  * The core owns the `command` evidence vocabulary and validates only the container
5
5
  * shape of everything else; ecosystem values — spawn kinds and tool names — are this
@@ -9,16 +9,16 @@
9
9
  * adapter recognizes.
10
10
  */
11
11
  /**
12
- * Judge one `requirePrecedent` evidence object against the session (PRD §4.4).
12
+ * Judge one `requirePrecedent` evidence object against the session.
13
13
  *
14
14
  * - `subagent`: exact spawn-kind equality (a kind is a value, not a pattern).
15
15
  * - `tool`: the observed tool names matched as a regular expression.
16
16
  * - anything else: `undefined` — outside this adapter's vocabulary, including the core's
17
17
  * own `command` key, which the covenant compiler evaluates itself.
18
18
  *
19
- * Both vocabularies require the call to have RUN and reported success (COVENANT-13b
20
- * §4.4): a call the covenant blocked, one the human refused, and one that simply failed
21
- * carry the same outcome, and none of them did the work the discipline demands. The spawn
19
+ * Both vocabularies require the call to have RUN and reported success: a call the
20
+ * covenant blocked, one the human refused, and one that simply failed carry the same
21
+ * outcome, and none of them did the work the discipline demands. The spawn
22
22
  * axis therefore reads the joined tool calls — the spawn query carries no outcome —
23
23
  * identifying a spawn by the same field the transcript provider does.
24
24
  *
@@ -1,13 +1,12 @@
1
1
  /**
2
- * `runAdapterPath` — the adapter path's single wiring entry point (ADAPTER-03).
2
+ * `runAdapterPath` — the adapter path's single wiring entry point.
3
3
  *
4
4
  * Composes translation → injected dispatch → funnel-supplement recording so every
5
5
  * adapter-path call leaves exactly one telemetry row when summed with downstream
6
- * records (PRD §4.3 table is canonical). I/O lives here and only here — the
7
- * translate layer (index.ts) stays pure.
6
+ * records. I/O lives here and only here — the translate layer (index.ts) stays pure.
8
7
  */
9
8
  /**
10
- * `DispatchOutcome` — structural mirror of the dispatcher's return (PRD §4.2).
9
+ * `DispatchOutcome` — structural mirror of the dispatcher's return.
11
10
  *
12
11
  * Deliberately declared here instead of imported: dependencies are one-way (adapter →
13
12
  * core only), so the covenant package is never imported. Contract drift is caught by
@@ -21,14 +20,14 @@ export type DispatchOutcome = {
21
20
  }[];
22
21
  };
23
22
  /**
24
- * Run one PreToolUse payload through the adapter path (PRD §4.1).
23
+ * Run one PreToolUse payload through the adapter path.
25
24
  *
26
25
  * Fail-closed on the verdict axis: an unparseable payload, a classification failure,
27
26
  * or a rejecting dispatch all resolve to `{ exitCode: 2 }` with one adapter `blocked`
28
27
  * record — never a thrown error (an unhandled rejection would exit the hook
29
28
  * non-blocking, a bypass vector). The funnel supplement is the exact rule
30
29
  * `exitCode 0 && results.length 0 → one adapter passed record`; every other outcome
31
- * appends nothing because downstream already recorded (PRD §4.3, no double counting).
30
+ * appends nothing because downstream already recorded, so nothing double-counts.
32
31
  */
33
32
  export declare function runAdapterPath(spec: {
34
33
  /** Raw hook stdin — one PreToolUse payload as a JSON string. */
@@ -1,15 +1,14 @@
1
1
  /**
2
- * `runAdapterPath` — the adapter path's single wiring entry point (ADAPTER-03).
2
+ * `runAdapterPath` — the adapter path's single wiring entry point.
3
3
  *
4
4
  * Composes translation → injected dispatch → funnel-supplement recording so every
5
5
  * adapter-path call leaves exactly one telemetry row when summed with downstream
6
- * records (PRD §4.3 table is canonical). I/O lives here and only here — the
7
- * translate layer (index.ts) stays pure.
6
+ * records. I/O lives here and only here — the translate layer (index.ts) stays pure.
8
7
  */
9
8
  import { readFileSync } from 'node:fs';
10
9
  import { appendRecordFailOpen, EXIT_BREAK_BLOCKING, EXIT_UPHOLD } from '@polydeukes/core';
11
10
  import { collectFileChanges } from './file-changes.js';
12
- import { buildCovenantInput } from './index.js';
11
+ import { buildCovenantInput } from './up-translate.js';
13
12
  /** Default label for adapter-level telemetry records. */
14
13
  const DEFAULT_ADAPTER_LABEL = 'adapter-claude-code';
15
14
  /**
@@ -18,7 +17,7 @@ const DEFAULT_ADAPTER_LABEL = 'adapter-claude-code';
18
17
  * Any other read failure (permissions, a directory target, fd exhaustion) throws:
19
18
  * `null` is the IR's creation sentinel, and a poisoned `pre: null` on an existing
20
19
  * file would let a path-family discipline uphold the overwrite (fail-open). The
21
- * caller converts the throw into one adapter `blocked` record (PR #23 review).
20
+ * caller converts the throw into one adapter `blocked` record.
22
21
  */
23
22
  function readPreStateFromDisk(filePath) {
24
23
  try {
@@ -31,14 +30,14 @@ function readPreStateFromDisk(filePath) {
31
30
  }
32
31
  }
33
32
  /**
34
- * Run one PreToolUse payload through the adapter path (PRD §4.1).
33
+ * Run one PreToolUse payload through the adapter path.
35
34
  *
36
35
  * Fail-closed on the verdict axis: an unparseable payload, a classification failure,
37
36
  * or a rejecting dispatch all resolve to `{ exitCode: 2 }` with one adapter `blocked`
38
37
  * record — never a thrown error (an unhandled rejection would exit the hook
39
38
  * non-blocking, a bypass vector). The funnel supplement is the exact rule
40
39
  * `exitCode 0 && results.length 0 → one adapter passed record`; every other outcome
41
- * appends nothing because downstream already recorded (PRD §4.3, no double counting).
40
+ * appends nothing because downstream already recorded, so nothing double-counts.
42
41
  */
43
42
  export async function runAdapterPath(spec) {
44
43
  const label = spec.adapterLabel ?? DEFAULT_ADAPTER_LABEL;
@@ -57,8 +56,8 @@ export async function runAdapterPath(spec) {
57
56
  if (built.ok !== true) {
58
57
  return blockAndRecord();
59
58
  }
60
- // Attach pre/post evidence to the call it belongs to (CORE-06 §4.2) — this path
61
- // translates exactly one payload, so the one evidence rides toolCalls[0]. Attached
59
+ // Attach pre/post evidence to the call it belongs to — this path translates
60
+ // exactly one payload, so the one evidence rides toolCalls[0]. Attached
62
61
  // only when provable: a non-mutating payload leaves its call unproven. A pre-state
63
62
  // read failure that is not absence blocks: evidence that cannot be gathered must not
64
63
  // dispatch a shape that reads as creation.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Session-surface tool vocabulary (DIST-01 §3-b).
2
+ * Session-surface tool vocabulary.
3
3
  *
4
4
  * The PreToolUse surface's tool names, owned by the adapter that speaks them — the
5
5
  * `STAGED_WRITE`/`STAGED_DELETE` precedent on the git side. Assembly consumes these
@@ -7,20 +7,25 @@
7
7
  * get them. The transcript reader lives here for the same reason: `transcript_path`
8
8
  * is a Claude Code envelope key, and reading it is translation, not assembly.
9
9
  */
10
- /** Tool names whose calls mutate a file and carry `fileChange` evidence. */
10
+ /**
11
+ * Tool names whose calls mutate a file and carry `fileChange` evidence.
12
+ *
13
+ * A tool absent from this list is judged by nobody. Removing an entry that looks
14
+ * unused silently stops judging that tool, and no test in this repository catches it.
15
+ */
11
16
  export declare const MUTATING_TOOLS: string[];
12
17
  /** Tool names that carry a shell command line instead of a file target. */
13
18
  export declare const SHELL_TOOLS: string[];
14
19
  /** `tool_input` keys a shell tool's command line travels in. */
15
20
  export declare const COMMAND_ARGS: string[];
16
21
  /**
17
- * Read the live transcript path out of a raw PreToolUse payload (DIST-01 §3-b).
22
+ * Read the live transcript path out of a raw PreToolUse payload.
18
23
  *
19
24
  * The transcript path travels in the raw payload only — up-translation drops it, so it
20
25
  * is read from the string and nowhere else. Every failure narrows to `undefined`
21
26
  * (unparseable JSON, a non-object payload, a non-string field), never a throw: lost
22
27
  * evidence leaves the dispatcher on its `noopTranscript` default, which shuts the
23
- * witness valve rather than opening it (ADAPTER-04 §4.4). A payload this function
28
+ * witness valve rather than opening it. A payload this function
24
29
  * cannot parse is still dispatched — `runAdapterPath` owns that verdict.
25
30
  */
26
31
  export declare function transcriptPathFromPayload(rawPayload: string): string | undefined;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Session-surface tool vocabulary (DIST-01 §3-b).
2
+ * Session-surface tool vocabulary.
3
3
  *
4
4
  * The PreToolUse surface's tool names, owned by the adapter that speaks them — the
5
5
  * `STAGED_WRITE`/`STAGED_DELETE` precedent on the git side. Assembly consumes these
@@ -8,20 +8,25 @@
8
8
  * is a Claude Code envelope key, and reading it is translation, not assembly.
9
9
  */
10
10
  import { isPlainObject } from '@polydeukes/core';
11
- /** Tool names whose calls mutate a file and carry `fileChange` evidence. */
11
+ /**
12
+ * Tool names whose calls mutate a file and carry `fileChange` evidence.
13
+ *
14
+ * A tool absent from this list is judged by nobody. Removing an entry that looks
15
+ * unused silently stops judging that tool, and no test in this repository catches it.
16
+ */
12
17
  export const MUTATING_TOOLS = ['Edit', 'Write', 'MultiEdit', 'NotebookEdit'];
13
18
  /** Tool names that carry a shell command line instead of a file target. */
14
19
  export const SHELL_TOOLS = ['Bash'];
15
20
  /** `tool_input` keys a shell tool's command line travels in. */
16
21
  export const COMMAND_ARGS = ['command'];
17
22
  /**
18
- * Read the live transcript path out of a raw PreToolUse payload (DIST-01 §3-b).
23
+ * Read the live transcript path out of a raw PreToolUse payload.
19
24
  *
20
25
  * The transcript path travels in the raw payload only — up-translation drops it, so it
21
26
  * is read from the string and nowhere else. Every failure narrows to `undefined`
22
27
  * (unparseable JSON, a non-object payload, a non-string field), never a throw: lost
23
28
  * evidence leaves the dispatcher on its `noopTranscript` default, which shuts the
24
- * witness valve rather than opening it (ADAPTER-04 §4.4). A payload this function
29
+ * witness valve rather than opening it. A payload this function
25
30
  * cannot parse is still dispatched — `runAdapterPath` owns that verdict.
26
31
  */
27
32
  export function transcriptPathFromPayload(rawPayload) {
@@ -1,15 +1,15 @@
1
1
  /**
2
- * JSONL-backed `CanonicalTranscript` provider (ADAPTER-04) — parses a Claude Code
3
- * session transcript into the agent-neutral query seam the witness valve judges over.
2
+ * JSONL-backed `CanonicalTranscript` provider — parses a Claude Code session
3
+ * transcript into the agent-neutral query seam the witness valve judges over.
4
4
  *
5
5
  * JSONL vocabulary (`origin`, `subagent_type`, ISO timestamps) stays confined to this
6
6
  * package, never the core. Parsing happens once (a snapshot); the queries are pure
7
7
  * reads. Every failure — unreadable file, broken line, shape mismatch — reduces
8
- * evidence instead of throwing (fail-closed, PRD §4.4).
8
+ * evidence instead of throwing, fail-closed.
9
9
  */
10
10
  import { type CanonicalTranscript } from '@polydeukes/core';
11
11
  /**
12
- * Parse JSONL transcript text into a {@link CanonicalTranscript} (PRD §4.2–4.4).
12
+ * Parse JSONL transcript text into a {@link CanonicalTranscript}.
13
13
  *
14
14
  * One pass over the lines builds an immutable snapshot; the queries only read it.
15
15
  * Unparseable lines, non-object lines, and shape-mismatched entries are skipped
@@ -17,7 +17,7 @@ import { type CanonicalTranscript } from '@polydeukes/core';
17
17
  */
18
18
  export declare function transcriptFromJsonl(text: string): CanonicalTranscript;
19
19
  /**
20
- * Read a transcript file and parse it (PRD §5.4).
20
+ * Read a transcript file and parse it.
21
21
  *
22
22
  * ANY read failure — missing file, permission, directory — answers `undefined`, never a
23
23
  * throw. It is deliberately NOT an empty transcript: the two are different facts, and
@@ -25,7 +25,7 @@ export declare function transcriptFromJsonl(text: string): CanonicalTranscript;
25
25
  * nothing yet, and judging against it is correct. An unreadable one is no evidence channel
26
26
  * at all, so the context family must skip rather than demand evidence from a session
27
27
  * nobody can read — while the witness valve reads the same absence and stays shut, leaving
28
- * a dead end with no message naming the cause (COVENANT-13 §4.5).
28
+ * a dead end with no message naming the cause.
29
29
  *
30
30
  * Either way the valve turns off, never open: `undefined` leaves the dispatcher on its
31
31
  * `noopTranscript` default.
@@ -1,16 +1,16 @@
1
1
  /**
2
- * JSONL-backed `CanonicalTranscript` provider (ADAPTER-04) — parses a Claude Code
3
- * session transcript into the agent-neutral query seam the witness valve judges over.
2
+ * JSONL-backed `CanonicalTranscript` provider — parses a Claude Code session
3
+ * transcript into the agent-neutral query seam the witness valve judges over.
4
4
  *
5
5
  * JSONL vocabulary (`origin`, `subagent_type`, ISO timestamps) stays confined to this
6
6
  * package, never the core. Parsing happens once (a snapshot); the queries are pure
7
7
  * reads. Every failure — unreadable file, broken line, shape mismatch — reduces
8
- * evidence instead of throwing (fail-closed, PRD §4.4).
8
+ * evidence instead of throwing, fail-closed.
9
9
  */
10
10
  import { readFileSync } from 'node:fs';
11
11
  import { isPlainObject, } from '@polydeukes/core';
12
12
  /**
13
- * Extract a human utterance from one entry, or `undefined` (PRD §4.2).
13
+ * Extract a human utterance from one entry, or `undefined`.
14
14
  *
15
15
  * The allowlist is positive identification: `type === 'user'`, `origin.kind === 'human'`,
16
16
  * and a plain-string `message.content`. Anything else — tool_result blocks,
@@ -35,7 +35,7 @@ function toUserMessage(entry) {
35
35
  };
36
36
  }
37
37
  /**
38
- * Extract subagent invocations from one entry (PRD §4.3).
38
+ * Extract subagent invocations from one entry.
39
39
  *
40
40
  * Detection keys on the *field*, not the tool name (the real tool has been renamed
41
41
  * Task → Agent): any `tool_use` block whose `input.subagent_type` is a string is an
@@ -61,7 +61,7 @@ function toSubagentInvocations(entry) {
61
61
  return invocations;
62
62
  }
63
63
  /**
64
- * Extract tool calls from one entry (COVENANT-13 §4.3).
64
+ * Extract tool calls from one entry.
65
65
  *
66
66
  * Positive identification on the *name*: any `tool_use` block with a string `name` is a
67
67
  * call, in observation order. A non-plain `input` empties the args but keeps the block —
@@ -91,12 +91,12 @@ function toToolCalls(entry) {
91
91
  return calls;
92
92
  }
93
93
  /**
94
- * Extract the outcomes reported by one entry's result blocks (COVENANT-13b §4.2).
94
+ * Extract the outcomes reported by one entry's result blocks.
95
95
  *
96
96
  * Results ride `user` entries and reference the call they answer; only that reference and
97
97
  * the error marker are read, never the result body. Success is ENUMERATED — no marker, or
98
98
  * a marker of exactly `false` — so every other value, boolean or not, reads as a failure
99
- * and a shape mismatch can only ever reduce evidence (§7). A block that cannot prove a
99
+ * and a shape mismatch can only ever reduce evidence. A block that cannot prove a
100
100
  * string reference is dropped alone.
101
101
  */
102
102
  function toToolResults(entry) {
@@ -121,7 +121,7 @@ function toToolResults(entry) {
121
121
  return results;
122
122
  }
123
123
  /**
124
- * Parse JSONL transcript text into a {@link CanonicalTranscript} (PRD §4.2–4.4).
124
+ * Parse JSONL transcript text into a {@link CanonicalTranscript}.
125
125
  *
126
126
  * One pass over the lines builds an immutable snapshot; the queries only read it.
127
127
  * Unparseable lines, non-object lines, and shape-mismatched entries are skipped
@@ -157,9 +157,9 @@ export function transcriptFromJsonl(text) {
157
157
  outcomes.set(result.id, result.succeeded);
158
158
  }
159
159
  }
160
- // The join (COVENANT-13b §4.2). No result found is not ignorance: this provider CAN
161
- // read the result channel, so silence is success it failed to prove — `undefined` stays
162
- // reserved for a provider that cannot see results at all.
160
+ // The join. No result found is not ignorance: this provider CAN read the result
161
+ // channel, so silence is success it failed to prove — `undefined` stays reserved
162
+ // for a provider that cannot see results at all.
163
163
  const toolCalls = observedCalls.map((call) => ({
164
164
  name: call.name,
165
165
  args: call.args,
@@ -179,7 +179,7 @@ export function transcriptFromJsonl(text) {
179
179
  };
180
180
  }
181
181
  /**
182
- * Read a transcript file and parse it (PRD §5.4).
182
+ * Read a transcript file and parse it.
183
183
  *
184
184
  * ANY read failure — missing file, permission, directory — answers `undefined`, never a
185
185
  * throw. It is deliberately NOT an empty transcript: the two are different facts, and
@@ -187,7 +187,7 @@ export function transcriptFromJsonl(text) {
187
187
  * nothing yet, and judging against it is correct. An unreadable one is no evidence channel
188
188
  * at all, so the context family must skip rather than demand evidence from a session
189
189
  * nobody can read — while the witness valve reads the same absence and stays shut, leaving
190
- * a dead end with no message naming the cause (COVENANT-13 §4.5).
190
+ * a dead end with no message naming the cause.
191
191
  *
192
192
  * Either way the valve turns off, never open: `undefined` leaves the dispatcher on its
193
193
  * `noopTranscript` default.
@@ -0,0 +1,77 @@
1
+ /**
2
+ * up-translate — Claude Code PreToolUse payloads into the agent-neutral IR.
3
+ *
4
+ * Pure translation only — no I/O, no process spawning. Agent and tool literals live in
5
+ * this package by design: it is the boundary where Claude Code's vocabulary is
6
+ * translated away before it reaches the core.
7
+ *
8
+ * These live outside the barrel because `run-adapter-path.ts` consumes
9
+ * `buildCovenantInput` and the barrel re-exports `run-adapter-path`: importing through
10
+ * the barrel is an initialization cycle. The barrel re-exports this
11
+ * module, so every consumer outside the package still reaches it at the same path.
12
+ */
13
+ import { type CovenantInput } from '@polydeukes/core';
14
+ /**
15
+ * `ClaudePreToolUsePayload` — a Claude Code PreToolUse hook payload.
16
+ *
17
+ * Only `tool_name` and `tool_input` are required; the rest is preserved when present.
18
+ * The agent/tool literals this package interprets live in the *values* of these fields,
19
+ * never in core — this boundary is the package's reason to exist.
20
+ */
21
+ export type ClaudePreToolUsePayload = {
22
+ hook_event_name?: string;
23
+ session_id?: string;
24
+ transcript_path?: string;
25
+ cwd?: string;
26
+ tool_name: string;
27
+ tool_input: Record<string, unknown>;
28
+ };
29
+ /**
30
+ * `TranslatedEvent` — the result of up-translating one payload.
31
+ *
32
+ * Success carries the IR fragment; failure carries a human-readable `reason`. A `Task`
33
+ * with a string `subagent_type` becomes a `subagentSpawn`; every other tool becomes a
34
+ * `toolCall`. Classification failure never demotes to a `toolCall` — losing spawn
35
+ * evidence is a bypass vector.
36
+ */
37
+ export type TranslatedEvent = {
38
+ ok: true;
39
+ kind: 'toolCall';
40
+ value: {
41
+ name: string;
42
+ args?: Record<string, unknown>;
43
+ };
44
+ } | {
45
+ ok: true;
46
+ kind: 'subagentSpawn';
47
+ value: {
48
+ kind: string;
49
+ };
50
+ } | {
51
+ ok: false;
52
+ reason: string;
53
+ };
54
+ /**
55
+ * Up-translate one Claude Code payload into an IR fragment (pure).
56
+ *
57
+ * Never throws: a payload that cannot be classified — a non-object, a missing/invalid
58
+ * `tool_name` or `tool_input`, or a `Task` lacking a string `subagent_type` — resolves
59
+ * to `{ ok: false, reason }`, fail-closed.
60
+ */
61
+ export declare function translateEvent(payload: unknown): TranslatedEvent;
62
+ /**
63
+ * Fold a sequence of payloads into one {@link CovenantInput} (pure).
64
+ *
65
+ * Preserves observation order into `toolCalls` / `subagentSpawns`; `userMessages` is
66
+ * always `[]` — witness evidence arrives through the transcript, not this path. If any
67
+ * payload fails classification the whole build fails closed with the blocking exit
68
+ * code — a silent drop would be a bypass vector.
69
+ */
70
+ export declare function buildCovenantInput(payloads: unknown[]): {
71
+ ok: true;
72
+ value: CovenantInput;
73
+ } | {
74
+ ok: false;
75
+ exitCode: 2;
76
+ reason: string;
77
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * up-translate — Claude Code PreToolUse payloads into the agent-neutral IR.
3
+ *
4
+ * Pure translation only — no I/O, no process spawning. Agent and tool literals live in
5
+ * this package by design: it is the boundary where Claude Code's vocabulary is
6
+ * translated away before it reaches the core.
7
+ *
8
+ * These live outside the barrel because `run-adapter-path.ts` consumes
9
+ * `buildCovenantInput` and the barrel re-exports `run-adapter-path`: importing through
10
+ * the barrel is an initialization cycle. The barrel re-exports this
11
+ * module, so every consumer outside the package still reaches it at the same path.
12
+ */
13
+ import { EXIT_BREAK_BLOCKING } from '@polydeukes/core';
14
+ import { parsePayloadEnvelope } from './payload-envelope.js';
15
+ /**
16
+ * Up-translate one Claude Code payload into an IR fragment (pure).
17
+ *
18
+ * Never throws: a payload that cannot be classified — a non-object, a missing/invalid
19
+ * `tool_name` or `tool_input`, or a `Task` lacking a string `subagent_type` — resolves
20
+ * to `{ ok: false, reason }`, fail-closed.
21
+ */
22
+ export function translateEvent(payload) {
23
+ const envelope = parsePayloadEnvelope(payload);
24
+ if (envelope.ok !== true) {
25
+ return { ok: false, reason: envelope.reason };
26
+ }
27
+ const { toolName, toolInput } = envelope;
28
+ if (toolName === 'Task') {
29
+ if (typeof toolInput.subagent_type !== 'string') {
30
+ return { ok: false, reason: 'Task payload is missing a string subagent_type' };
31
+ }
32
+ return { ok: true, kind: 'subagentSpawn', value: { kind: toolInput.subagent_type } };
33
+ }
34
+ return { ok: true, kind: 'toolCall', value: { name: toolName, args: toolInput } };
35
+ }
36
+ /**
37
+ * Fold a sequence of payloads into one {@link CovenantInput} (pure).
38
+ *
39
+ * Preserves observation order into `toolCalls` / `subagentSpawns`; `userMessages` is
40
+ * always `[]` — witness evidence arrives through the transcript, not this path. If any
41
+ * payload fails classification the whole build fails closed with the blocking exit
42
+ * code — a silent drop would be a bypass vector.
43
+ */
44
+ export function buildCovenantInput(payloads) {
45
+ const input = { toolCalls: [], subagentSpawns: [], userMessages: [] };
46
+ for (let index = 0; index < payloads.length; index++) {
47
+ const translated = translateEvent(payloads[index]);
48
+ if (translated.ok !== true) {
49
+ return {
50
+ ok: false,
51
+ exitCode: EXIT_BREAK_BLOCKING,
52
+ reason: `payload at index ${index} failed classification: ${translated.reason}`,
53
+ };
54
+ }
55
+ if (translated.kind === 'toolCall') {
56
+ input.toolCalls.push(translated.value);
57
+ }
58
+ else {
59
+ input.subagentSpawns.push(translated.value);
60
+ }
61
+ }
62
+ return { ok: true, value: input };
63
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Virtual post-state parser (ADAPTER-02) — computes the file content *after* an
2
+ * Virtual post-state parser — computes the file content *after* an
3
3
  * Edit/Write/MultiEdit payload would be applied, from `tool_input` alone.
4
4
  *
5
5
  * Pure computation only — no I/O, no process spawning. Reading the pre-state from
@@ -7,11 +7,11 @@
7
7
  * `replace_all`) stays confined to this package, never the core.
8
8
  */
9
9
  /**
10
- * `VirtualPostState` — the result of computing one payload's post-state (PRD §3.1).
10
+ * `VirtualPostState` — the result of computing one payload's post-state.
11
11
  *
12
12
  * Success carries the virtual file `{ filePath, content }`; failure carries a
13
13
  * human-readable `reason`. A failure is never silently replaced by the pre-state —
14
- * that would disguise the change as "no change" (a bypass vector, PRD §6).
14
+ * that would disguise the change as "no change", a bypass vector.
15
15
  */
16
16
  export type VirtualPostState = {
17
17
  ok: true;
@@ -24,12 +24,12 @@ export type VirtualPostState = {
24
24
  reason: string;
25
25
  };
26
26
  /**
27
- * Compute the virtual post-state of one Claude Code payload (pure, PRD §3.1).
27
+ * Compute the virtual post-state of one Claude Code payload (pure).
28
28
  *
29
29
  * `preState` is the target file's current content, `null` when the file does not
30
30
  * exist. Never throws: any input that cannot be classified — a non-object payload,
31
31
  * a missing `tool_name`/`tool_input`/`file_path`, an unsatisfiable Edit, a partial
32
32
  * MultiEdit, or any tool other than Write/Edit/MultiEdit — resolves to
33
- * `{ ok: false, reason }` (fail-closed, PRD §4.2).
33
+ * `{ ok: false, reason }`, fail-closed.
34
34
  */
35
35
  export declare function virtualPostState(payload: unknown, preState: string | null): VirtualPostState;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Virtual post-state parser (ADAPTER-02) — computes the file content *after* an
2
+ * Virtual post-state parser — computes the file content *after* an
3
3
  * Edit/Write/MultiEdit payload would be applied, from `tool_input` alone.
4
4
  *
5
5
  * Pure computation only — no I/O, no process spawning. Reading the pre-state from
@@ -9,7 +9,7 @@
9
9
  import { isPlainObject } from '@polydeukes/core';
10
10
  import { parsePayloadEnvelope } from './payload-envelope.js';
11
11
  /**
12
- * Apply one `old_string` → `new_string` substitution to `content` (PRD §3.2).
12
+ * Apply one `old_string` → `new_string` substitution to `content`.
13
13
  *
14
14
  * Preconditions mirror the Edit tool's own acceptance rules: non-empty `old_string`,
15
15
  * `old_string !== new_string`, and occurrence count exactly 1 (or ≥1 with
@@ -44,13 +44,13 @@ function applyEdit(content, edit) {
44
44
  return { ok: true, content: content.replace(oldString, () => newString) };
45
45
  }
46
46
  /**
47
- * Compute the virtual post-state of one Claude Code payload (pure, PRD §3.1).
47
+ * Compute the virtual post-state of one Claude Code payload (pure).
48
48
  *
49
49
  * `preState` is the target file's current content, `null` when the file does not
50
50
  * exist. Never throws: any input that cannot be classified — a non-object payload,
51
51
  * a missing `tool_name`/`tool_input`/`file_path`, an unsatisfiable Edit, a partial
52
52
  * MultiEdit, or any tool other than Write/Edit/MultiEdit — resolves to
53
- * `{ ok: false, reason }` (fail-closed, PRD §4.2).
53
+ * `{ ok: false, reason }`, fail-closed.
54
54
  */
55
55
  export function virtualPostState(payload, preState) {
56
56
  const envelope = parsePayloadEnvelope(payload);
@@ -106,7 +106,7 @@ export function virtualPostState(payload, preState) {
106
106
  content = preState;
107
107
  }
108
108
  // Sequential application: edit N targets the result of edit N-1. Any failure
109
- // fails the whole call — a partial result must never leak (PRD §6).
109
+ // fails the whole call — a partial result must never leak.
110
110
  for (let index = startIndex; index < edits.length; index++) {
111
111
  const edit = edits[index];
112
112
  if (!isPlainObject(edit)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polydeukes/adapter-claude-code",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Polydeukes adapter for Claude Code — up-translates PreToolUse hook payloads into the agent-neutral covenant input IR. Alpha.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@polydeukes/core": "^0.3.0"
32
+ "@polydeukes/core": "^0.5.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^24.0.0",