@polydeukes/adapter-claude-code 0.4.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.
- package/dist/file-changes.d.ts +7 -9
- package/dist/file-changes.js +8 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/precedent.d.ts +5 -5
- package/dist/precedent.js +5 -5
- package/dist/run-adapter-path.d.ts +5 -6
- package/dist/run-adapter-path.js +7 -8
- package/dist/session-vocabulary.d.ts +9 -4
- package/dist/session-vocabulary.js +9 -4
- package/dist/transcript.d.ts +6 -6
- package/dist/transcript.js +14 -14
- package/dist/up-translate.d.ts +11 -12
- package/dist/up-translate.js +8 -9
- package/dist/virtual-post-state.d.ts +5 -5
- package/dist/virtual-post-state.js +5 -5
- package/package.json +2 -2
package/dist/file-changes.d.ts
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* File-change evidence computation
|
|
3
|
-
*
|
|
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
|
|
9
|
-
*
|
|
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
|
|
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
|
|
18
|
-
*
|
|
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.
|
package/dist/file-changes.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* File-change evidence computation
|
|
3
|
-
*
|
|
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
|
|
9
|
-
*
|
|
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
|
|
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
|
|
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
|
|
96
|
-
*
|
|
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,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @polydeukes/adapter-claude-code — up-translates Claude Code PreToolUse hook
|
|
3
|
-
* payloads into the agent-neutral covenant input IR
|
|
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
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @polydeukes/adapter-claude-code — up-translates Claude Code PreToolUse hook
|
|
3
|
-
* payloads into the agent-neutral covenant input IR
|
|
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
|
package/dist/precedent.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Adapter-owned precedent evidence evaluator
|
|
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
|
|
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
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
|
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
|
|
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
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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. */
|
package/dist/run-adapter-path.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `runAdapterPath` — the adapter path's single wiring entry point
|
|
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
|
|
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';
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
61
|
-
//
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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) {
|
package/dist/transcript.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSONL-backed `CanonicalTranscript` provider
|
|
3
|
-
*
|
|
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
|
|
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}
|
|
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
|
|
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
|
|
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.
|
package/dist/transcript.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSONL-backed `CanonicalTranscript` provider
|
|
3
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}
|
|
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
|
|
161
|
-
//
|
|
162
|
-
//
|
|
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
|
|
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
|
|
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.
|
package/dist/up-translate.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* up-translate — Claude Code PreToolUse payloads into the agent-neutral IR
|
|
3
|
-
* PRD §4.1–§4.2).
|
|
2
|
+
* up-translate — Claude Code PreToolUse payloads into the agent-neutral IR.
|
|
4
3
|
*
|
|
5
4
|
* Pure translation only — no I/O, no process spawning. Agent and tool literals live in
|
|
6
5
|
* this package by design: it is the boundary where Claude Code's vocabulary is
|
|
@@ -8,12 +7,12 @@
|
|
|
8
7
|
*
|
|
9
8
|
* These live outside the barrel because `run-adapter-path.ts` consumes
|
|
10
9
|
* `buildCovenantInput` and the barrel re-exports `run-adapter-path`: importing through
|
|
11
|
-
* the barrel is an initialization cycle
|
|
10
|
+
* the barrel is an initialization cycle. The barrel re-exports this
|
|
12
11
|
* module, so every consumer outside the package still reaches it at the same path.
|
|
13
12
|
*/
|
|
14
13
|
import { type CovenantInput } from '@polydeukes/core';
|
|
15
14
|
/**
|
|
16
|
-
* `ClaudePreToolUsePayload` — a Claude Code PreToolUse hook payload
|
|
15
|
+
* `ClaudePreToolUsePayload` — a Claude Code PreToolUse hook payload.
|
|
17
16
|
*
|
|
18
17
|
* Only `tool_name` and `tool_input` are required; the rest is preserved when present.
|
|
19
18
|
* The agent/tool literals this package interprets live in the *values* of these fields,
|
|
@@ -28,12 +27,12 @@ export type ClaudePreToolUsePayload = {
|
|
|
28
27
|
tool_input: Record<string, unknown>;
|
|
29
28
|
};
|
|
30
29
|
/**
|
|
31
|
-
* `TranslatedEvent` — the result of up-translating one payload
|
|
30
|
+
* `TranslatedEvent` — the result of up-translating one payload.
|
|
32
31
|
*
|
|
33
32
|
* Success carries the IR fragment; failure carries a human-readable `reason`. A `Task`
|
|
34
33
|
* with a string `subagent_type` becomes a `subagentSpawn`; every other tool becomes a
|
|
35
34
|
* `toolCall`. Classification failure never demotes to a `toolCall` — losing spawn
|
|
36
|
-
* evidence is a bypass vector
|
|
35
|
+
* evidence is a bypass vector.
|
|
37
36
|
*/
|
|
38
37
|
export type TranslatedEvent = {
|
|
39
38
|
ok: true;
|
|
@@ -53,20 +52,20 @@ export type TranslatedEvent = {
|
|
|
53
52
|
reason: string;
|
|
54
53
|
};
|
|
55
54
|
/**
|
|
56
|
-
* Up-translate one Claude Code payload into an IR fragment (pure
|
|
55
|
+
* Up-translate one Claude Code payload into an IR fragment (pure).
|
|
57
56
|
*
|
|
58
57
|
* Never throws: a payload that cannot be classified — a non-object, a missing/invalid
|
|
59
58
|
* `tool_name` or `tool_input`, or a `Task` lacking a string `subagent_type` — resolves
|
|
60
|
-
* to `{ ok: false, reason }
|
|
59
|
+
* to `{ ok: false, reason }`, fail-closed.
|
|
61
60
|
*/
|
|
62
61
|
export declare function translateEvent(payload: unknown): TranslatedEvent;
|
|
63
62
|
/**
|
|
64
|
-
* Fold a sequence of payloads into one {@link CovenantInput} (pure
|
|
63
|
+
* Fold a sequence of payloads into one {@link CovenantInput} (pure).
|
|
65
64
|
*
|
|
66
65
|
* Preserves observation order into `toolCalls` / `subagentSpawns`; `userMessages` is
|
|
67
|
-
* always `[]`
|
|
68
|
-
* classification the whole build fails closed with the blocking exit
|
|
69
|
-
* drop would be a bypass vector
|
|
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.
|
|
70
69
|
*/
|
|
71
70
|
export declare function buildCovenantInput(payloads: unknown[]): {
|
|
72
71
|
ok: true;
|
package/dist/up-translate.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* up-translate — Claude Code PreToolUse payloads into the agent-neutral IR
|
|
3
|
-
* PRD §4.1–§4.2).
|
|
2
|
+
* up-translate — Claude Code PreToolUse payloads into the agent-neutral IR.
|
|
4
3
|
*
|
|
5
4
|
* Pure translation only — no I/O, no process spawning. Agent and tool literals live in
|
|
6
5
|
* this package by design: it is the boundary where Claude Code's vocabulary is
|
|
@@ -8,17 +7,17 @@
|
|
|
8
7
|
*
|
|
9
8
|
* These live outside the barrel because `run-adapter-path.ts` consumes
|
|
10
9
|
* `buildCovenantInput` and the barrel re-exports `run-adapter-path`: importing through
|
|
11
|
-
* the barrel is an initialization cycle
|
|
10
|
+
* the barrel is an initialization cycle. The barrel re-exports this
|
|
12
11
|
* module, so every consumer outside the package still reaches it at the same path.
|
|
13
12
|
*/
|
|
14
13
|
import { EXIT_BREAK_BLOCKING } from '@polydeukes/core';
|
|
15
14
|
import { parsePayloadEnvelope } from './payload-envelope.js';
|
|
16
15
|
/**
|
|
17
|
-
* Up-translate one Claude Code payload into an IR fragment (pure
|
|
16
|
+
* Up-translate one Claude Code payload into an IR fragment (pure).
|
|
18
17
|
*
|
|
19
18
|
* Never throws: a payload that cannot be classified — a non-object, a missing/invalid
|
|
20
19
|
* `tool_name` or `tool_input`, or a `Task` lacking a string `subagent_type` — resolves
|
|
21
|
-
* to `{ ok: false, reason }
|
|
20
|
+
* to `{ ok: false, reason }`, fail-closed.
|
|
22
21
|
*/
|
|
23
22
|
export function translateEvent(payload) {
|
|
24
23
|
const envelope = parsePayloadEnvelope(payload);
|
|
@@ -35,12 +34,12 @@ export function translateEvent(payload) {
|
|
|
35
34
|
return { ok: true, kind: 'toolCall', value: { name: toolName, args: toolInput } };
|
|
36
35
|
}
|
|
37
36
|
/**
|
|
38
|
-
* Fold a sequence of payloads into one {@link CovenantInput} (pure
|
|
37
|
+
* Fold a sequence of payloads into one {@link CovenantInput} (pure).
|
|
39
38
|
*
|
|
40
39
|
* Preserves observation order into `toolCalls` / `subagentSpawns`; `userMessages` is
|
|
41
|
-
* always `[]`
|
|
42
|
-
* classification the whole build fails closed with the blocking exit
|
|
43
|
-
* drop would be a bypass vector
|
|
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.
|
|
44
43
|
*/
|
|
45
44
|
export function buildCovenantInput(payloads) {
|
|
46
45
|
const input = { toolCalls: [], subagentSpawns: [], userMessages: [] };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Virtual post-state parser
|
|
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
|
|
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"
|
|
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
|
|
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 }
|
|
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
|
|
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
|
|
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
|
|
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 }
|
|
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
|
|
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
|
+
"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.
|
|
32
|
+
"@polydeukes/core": "^0.5.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^24.0.0",
|