@smeltjs/core 0.1.0 → 0.3.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/README.md +4 -4
- package/dist/apply.d.ts.map +1 -1
- package/dist/apply.js.map +1 -1
- package/dist/cache/prefix.d.ts.map +1 -1
- package/dist/cache/prefix.js.map +1 -1
- package/dist/cli/args.d.ts +16 -59
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +18 -277
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/bin.d.ts.map +1 -1
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/config.d.ts +62 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +87 -2
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/hooks.d.ts +148 -0
- package/dist/cli/hooks.d.ts.map +1 -0
- package/dist/cli/hooks.js +835 -0
- package/dist/cli/hooks.js.map +1 -0
- package/dist/cli/init.d.ts +30 -13
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +173 -38
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/report.d.ts.map +1 -1
- package/dist/cli/report.js +2 -2
- package/dist/cli/report.js.map +1 -1
- package/dist/cli/run.d.ts +25 -88
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +32 -322
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/shell.d.ts +106 -0
- package/dist/cli/shell.d.ts.map +1 -0
- package/dist/cli/shell.js +100 -0
- package/dist/cli/shell.js.map +1 -0
- package/dist/cli/subcommands/flags.d.ts +141 -0
- package/dist/cli/subcommands/flags.d.ts.map +1 -0
- package/dist/cli/subcommands/flags.js +208 -0
- package/dist/cli/subcommands/flags.js.map +1 -0
- package/dist/cli/subcommands/hooks.d.ts +18 -0
- package/dist/cli/subcommands/hooks.d.ts.map +1 -0
- package/dist/cli/subcommands/hooks.js +64 -0
- package/dist/cli/subcommands/hooks.js.map +1 -0
- package/dist/cli/subcommands/init.d.ts +19 -0
- package/dist/cli/subcommands/init.d.ts.map +1 -0
- package/dist/cli/subcommands/init.js +44 -0
- package/dist/cli/subcommands/init.js.map +1 -0
- package/dist/cli/subcommands/map.d.ts +75 -0
- package/dist/cli/subcommands/map.d.ts.map +1 -0
- package/dist/cli/subcommands/map.js +132 -0
- package/dist/cli/subcommands/map.js.map +1 -0
- package/dist/cli/subcommands/registry.d.ts +80 -0
- package/dist/cli/subcommands/registry.d.ts.map +1 -0
- package/dist/cli/subcommands/registry.js +138 -0
- package/dist/cli/subcommands/registry.js.map +1 -0
- package/dist/cli/subcommands/retrieve.d.ts +74 -0
- package/dist/cli/subcommands/retrieve.d.ts.map +1 -0
- package/dist/cli/subcommands/retrieve.js +84 -0
- package/dist/cli/subcommands/retrieve.js.map +1 -0
- package/dist/cli/subcommands/smelt.d.ts +107 -0
- package/dist/cli/subcommands/smelt.d.ts.map +1 -0
- package/dist/cli/subcommands/smelt.js +304 -0
- package/dist/cli/subcommands/smelt.js.map +1 -0
- package/dist/cli/subcommands/stats.d.ts +43 -0
- package/dist/cli/subcommands/stats.d.ts.map +1 -0
- package/dist/cli/subcommands/stats.js +62 -0
- package/dist/cli/subcommands/stats.js.map +1 -0
- package/dist/cli/subcommands/subcommand.d.ts +113 -0
- package/dist/cli/subcommands/subcommand.d.ts.map +1 -0
- package/dist/cli/subcommands/subcommand.js +3 -0
- package/dist/cli/subcommands/subcommand.js.map +1 -0
- package/dist/cli/usage.d.ts +3 -0
- package/dist/cli/usage.d.ts.map +1 -0
- package/dist/cli/usage.js +95 -0
- package/dist/cli/usage.js.map +1 -0
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js.map +1 -1
- package/dist/errors.d.ts +21 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +33 -1
- package/dist/errors.js.map +1 -1
- package/dist/harness/aider.d.ts +10 -0
- package/dist/harness/aider.d.ts.map +1 -0
- package/dist/harness/aider.js +21 -0
- package/dist/harness/aider.js.map +1 -0
- package/dist/harness/claude-code.d.ts +3 -0
- package/dist/harness/claude-code.d.ts.map +1 -0
- package/dist/harness/claude-code.js +68 -0
- package/dist/harness/claude-code.js.map +1 -0
- package/dist/harness/cline.d.ts +3 -0
- package/dist/harness/cline.d.ts.map +1 -0
- package/dist/harness/cline.js +54 -0
- package/dist/harness/cline.js.map +1 -0
- package/dist/harness/codex.d.ts +3 -0
- package/dist/harness/codex.d.ts.map +1 -0
- package/dist/harness/codex.js +91 -0
- package/dist/harness/codex.js.map +1 -0
- package/dist/harness/cursor.d.ts +3 -0
- package/dist/harness/cursor.d.ts.map +1 -0
- package/dist/harness/cursor.js +54 -0
- package/dist/harness/cursor.js.map +1 -0
- package/dist/harness/gemini.d.ts +3 -0
- package/dist/harness/gemini.d.ts.map +1 -0
- package/dist/harness/gemini.js +59 -0
- package/dist/harness/gemini.js.map +1 -0
- package/dist/harness/grok.d.ts +3 -0
- package/dist/harness/grok.d.ts.map +1 -0
- package/dist/harness/grok.js +46 -0
- package/dist/harness/grok.js.map +1 -0
- package/dist/harness/hermes.d.ts +3 -0
- package/dist/harness/hermes.d.ts.map +1 -0
- package/dist/harness/hermes.js +71 -0
- package/dist/harness/hermes.js.map +1 -0
- package/dist/harness/kilocode.d.ts +3 -0
- package/dist/harness/kilocode.d.ts.map +1 -0
- package/dist/harness/kilocode.js +35 -0
- package/dist/harness/kilocode.js.map +1 -0
- package/dist/harness/opencode.d.ts +3 -0
- package/dist/harness/opencode.d.ts.map +1 -0
- package/dist/harness/opencode.js +97 -0
- package/dist/harness/opencode.js.map +1 -0
- package/dist/harness/paths.d.ts +17 -0
- package/dist/harness/paths.d.ts.map +1 -0
- package/dist/harness/paths.js +45 -0
- package/dist/harness/paths.js.map +1 -0
- package/dist/harness/profile.d.ts +184 -0
- package/dist/harness/profile.d.ts.map +1 -0
- package/dist/harness/profile.js +26 -0
- package/dist/harness/profile.js.map +1 -0
- package/dist/harness/registry.d.ts +51 -0
- package/dist/harness/registry.d.ts.map +1 -0
- package/dist/harness/registry.js +87 -0
- package/dist/harness/registry.js.map +1 -0
- package/dist/harness/snippet.d.ts +19 -0
- package/dist/harness/snippet.d.ts.map +1 -0
- package/dist/harness/snippet.js +41 -0
- package/dist/harness/snippet.js.map +1 -0
- package/dist/hash.d.ts.map +1 -1
- package/dist/hash.js.map +1 -1
- package/dist/hooks/guard-core.d.ts +164 -0
- package/dist/hooks/guard-core.d.ts.map +1 -0
- package/dist/hooks/guard-core.js +491 -0
- package/dist/hooks/guard-core.js.map +1 -0
- package/dist/hooks/shim.d.ts +176 -0
- package/dist/hooks/shim.d.ts.map +1 -0
- package/dist/hooks/shim.js +192 -0
- package/dist/hooks/shim.js.map +1 -0
- package/dist/hooks/shims/claude-code.d.ts +10 -0
- package/dist/hooks/shims/claude-code.d.ts.map +1 -0
- package/dist/hooks/shims/claude-code.js +14 -0
- package/dist/hooks/shims/claude-code.js.map +1 -0
- package/dist/hooks/shims/cline.d.ts +10 -0
- package/dist/hooks/shims/cline.d.ts.map +1 -0
- package/dist/hooks/shims/cline.js +14 -0
- package/dist/hooks/shims/cline.js.map +1 -0
- package/dist/hooks/shims/codex.d.ts +10 -0
- package/dist/hooks/shims/codex.d.ts.map +1 -0
- package/dist/hooks/shims/codex.js +14 -0
- package/dist/hooks/shims/codex.js.map +1 -0
- package/dist/hooks/shims/cursor.d.ts +10 -0
- package/dist/hooks/shims/cursor.d.ts.map +1 -0
- package/dist/hooks/shims/cursor.js +14 -0
- package/dist/hooks/shims/cursor.js.map +1 -0
- package/dist/hooks/shims/gemini.d.ts +10 -0
- package/dist/hooks/shims/gemini.d.ts.map +1 -0
- package/dist/hooks/shims/gemini.js +14 -0
- package/dist/hooks/shims/gemini.js.map +1 -0
- package/dist/hooks/shims/grok.d.ts +10 -0
- package/dist/hooks/shims/grok.d.ts.map +1 -0
- package/dist/hooks/shims/grok.js +14 -0
- package/dist/hooks/shims/grok.js.map +1 -0
- package/dist/hooks/shims/hermes.d.ts +10 -0
- package/dist/hooks/shims/hermes.d.ts.map +1 -0
- package/dist/hooks/shims/hermes.js +14 -0
- package/dist/hooks/shims/hermes.js.map +1 -0
- package/dist/index.d.ts +33 -76
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -67
- package/dist/index.js.map +1 -1
- package/dist/lang/bash.d.ts.map +1 -1
- package/dist/lang/bash.js.map +1 -1
- package/dist/lang/c.d.ts.map +1 -1
- package/dist/lang/c.js.map +1 -1
- package/dist/lang/c_sharp.d.ts.map +1 -1
- package/dist/lang/c_sharp.js.map +1 -1
- package/dist/lang/cpp.d.ts.map +1 -1
- package/dist/lang/cpp.js.map +1 -1
- package/dist/lang/go.d.ts.map +1 -1
- package/dist/lang/go.js.map +1 -1
- package/dist/lang/java.d.ts.map +1 -1
- package/dist/lang/java.js.map +1 -1
- package/dist/lang/javascript.d.ts.map +1 -1
- package/dist/lang/javascript.js.map +1 -1
- package/dist/lang/kotlin.d.ts.map +1 -1
- package/dist/lang/kotlin.js.map +1 -1
- package/dist/lang/php.d.ts.map +1 -1
- package/dist/lang/php.js.map +1 -1
- package/dist/lang/profile.d.ts.map +1 -1
- package/dist/lang/profile.js.map +1 -1
- package/dist/lang/python.d.ts.map +1 -1
- package/dist/lang/python.js.map +1 -1
- package/dist/lang/registry.d.ts.map +1 -1
- package/dist/lang/registry.js.map +1 -1
- package/dist/lang/ruby.d.ts.map +1 -1
- package/dist/lang/ruby.js.map +1 -1
- package/dist/lang/rust.d.ts.map +1 -1
- package/dist/lang/rust.js.map +1 -1
- package/dist/lang/swift.d.ts.map +1 -1
- package/dist/lang/swift.js.map +1 -1
- package/dist/lang/tsx.d.ts.map +1 -1
- package/dist/lang/tsx.js.map +1 -1
- package/dist/lang/typescript.d.ts.map +1 -1
- package/dist/lang/typescript.js.map +1 -1
- package/dist/net/policy.d.ts +18 -1
- package/dist/net/policy.d.ts.map +1 -1
- package/dist/net/policy.js +7 -4
- package/dist/net/policy.js.map +1 -1
- package/dist/ops/index.d.ts +39 -0
- package/dist/ops/index.d.ts.map +1 -0
- package/dist/ops/index.js +37 -0
- package/dist/ops/index.js.map +1 -0
- package/dist/ops/inputs.d.ts +170 -0
- package/dist/ops/inputs.d.ts.map +1 -0
- package/dist/ops/inputs.js +142 -0
- package/dist/ops/inputs.js.map +1 -0
- package/dist/ops/verbs.d.ts +136 -0
- package/dist/ops/verbs.d.ts.map +1 -0
- package/dist/ops/verbs.js +76 -0
- package/dist/ops/verbs.js.map +1 -0
- package/dist/plan/auto.d.ts +69 -0
- package/dist/plan/auto.d.ts.map +1 -0
- package/dist/plan/auto.js +70 -0
- package/dist/plan/auto.js.map +1 -0
- package/dist/plan/budget.d.ts +21 -0
- package/dist/plan/budget.d.ts.map +1 -0
- package/dist/plan/budget.js +26 -0
- package/dist/plan/budget.js.map +1 -0
- package/dist/plan/grammar.d.ts +14 -0
- package/dist/plan/grammar.d.ts.map +1 -1
- package/dist/plan/grammar.js +47 -6
- package/dist/plan/grammar.js.map +1 -1
- package/dist/plan/lexical.d.ts.map +1 -1
- package/dist/plan/lexical.js +1 -7
- package/dist/plan/lexical.js.map +1 -1
- package/dist/plan/planners.d.ts +35 -2
- package/dist/plan/planners.d.ts.map +1 -1
- package/dist/plan/planners.js +35 -2
- package/dist/plan/planners.js.map +1 -1
- package/dist/plan/structural.d.ts +19 -5
- package/dist/plan/structural.d.ts.map +1 -1
- package/dist/plan/structural.js +180 -33
- package/dist/plan/structural.js.map +1 -1
- package/dist/repomap/cache.d.ts +48 -0
- package/dist/repomap/cache.d.ts.map +1 -1
- package/dist/repomap/cache.js +112 -13
- package/dist/repomap/cache.js.map +1 -1
- package/dist/repomap/io.d.ts +24 -0
- package/dist/repomap/io.d.ts.map +1 -0
- package/dist/repomap/io.js +34 -0
- package/dist/repomap/io.js.map +1 -0
- package/dist/repomap/map.d.ts +69 -6
- package/dist/repomap/map.d.ts.map +1 -1
- package/dist/repomap/map.js +104 -22
- package/dist/repomap/map.js.map +1 -1
- package/dist/repomap/rank.d.ts +26 -2
- package/dist/repomap/rank.d.ts.map +1 -1
- package/dist/repomap/rank.js +20 -0
- package/dist/repomap/rank.js.map +1 -1
- package/dist/repomap/reader.d.ts +73 -0
- package/dist/repomap/reader.d.ts.map +1 -0
- package/dist/repomap/reader.js +30 -0
- package/dist/repomap/reader.js.map +1 -0
- package/dist/repomap/tags.d.ts.map +1 -1
- package/dist/repomap/tags.js.map +1 -1
- package/dist/retrieve.d.ts +8 -0
- package/dist/retrieve.d.ts.map +1 -1
- package/dist/retrieve.js +12 -1
- package/dist/retrieve.js.map +1 -1
- package/dist/smelter.d.ts +79 -0
- package/dist/smelter.d.ts.map +1 -0
- package/dist/smelter.js +65 -0
- package/dist/smelter.js.map +1 -0
- package/dist/stages.d.ts.map +1 -1
- package/dist/stages.js +2 -2
- package/dist/stages.js.map +1 -1
- package/dist/stats.d.ts +2 -1
- package/dist/stats.d.ts.map +1 -1
- package/dist/stats.js +2 -1
- package/dist/stats.js.map +1 -1
- package/dist/store-dir.d.ts +43 -4
- package/dist/store-dir.d.ts.map +1 -1
- package/dist/store-dir.js +48 -5
- package/dist/store-dir.js.map +1 -1
- package/dist/store.d.ts +6 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +6 -0
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +35 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +22 -14
- package/dist/cli/resolve.d.ts +0 -103
- package/dist/cli/resolve.d.ts.map +0 -1
- package/dist/cli/resolve.js +0 -106
- package/dist/cli/resolve.js.map +0 -1
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { decide, isMainModule } from './guard-core.ts';
|
|
2
|
+
import type { GuardDecision, GuardRequest, GuardSettings } from './guard-core.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The shim runtime — the one loop every per-harness shim runs.
|
|
5
|
+
*
|
|
6
|
+
* A shim is a thin adapter: it maps its harness's native PreToolUse stdin schema
|
|
7
|
+
* onto a {@link GuardRequest}, hands that to the guard core, and renders the
|
|
8
|
+
* {@link GuardDecision} back in the harness's native output schema. All policy —
|
|
9
|
+
* threshold, windows, suggestion rendering, enforcement mode — lives in
|
|
10
|
+
* `guard-core.ts`; a shim that grew an opinion of its own would be a second guard
|
|
11
|
+
* that drifts from the first.
|
|
12
|
+
*
|
|
13
|
+
* Most of a shim is a *table*, not code: which tool names mean read and shell, which
|
|
14
|
+
* payload keys carry them, and the two documents the harness's schema wants back. That
|
|
15
|
+
* table is a {@link HarnessHookSchema}, `shimFromSchema` turns one into a
|
|
16
|
+
* {@link ShimAdapter}, and every harness's schema lives in its profile
|
|
17
|
+
* (`src/harness/<id>.ts`). {@link ShimAdapter} stays public as the escape hatch for a
|
|
18
|
+
* harness a table cannot express.
|
|
19
|
+
*
|
|
20
|
+
* The enforcement gate lives here, once, because it is the same for every harness:
|
|
21
|
+
*
|
|
22
|
+
* - **deny mode (the default)**: a deny is rendered as the harness's deny, with the
|
|
23
|
+
* guard's steering reason. The transcript stays truthful — the model sees what it
|
|
24
|
+
* asked for refused and why, and learns to run the replacement (and
|
|
25
|
+
* `smelt retrieve`) itself.
|
|
26
|
+
* - **rewrite mode (`hooks.enforcement: "rewrite"` in smelt.config.json)**: when the
|
|
27
|
+
* denied call is a Bash command, the guard produced a faithful executable
|
|
28
|
+
* `suggestion`, and this harness can modify tool input, the shim substitutes the
|
|
29
|
+
* suggestion instead of denying. A harness that cannot rewrite falls back to the
|
|
30
|
+
* deny — with the exact command in the reason — so rewrite mode never silently
|
|
31
|
+
* weakens into nothing. A substitution is always announced: in the decision
|
|
32
|
+
* reason where the harness's rewrite schema carries one (Claude Code, Codex),
|
|
33
|
+
* and on stderr where it does not (Gemini, Cursor, Hermes, the opencode
|
|
34
|
+
* plugin) — a rewrite nobody can see would change what the model asked for
|
|
35
|
+
* without telling anyone. And a shim never rewrites in deny mode: a silent
|
|
36
|
+
* rewrite changes what the model asked for without telling it, which is this
|
|
37
|
+
* project's signature failure shape applied to the harness.
|
|
38
|
+
*/
|
|
39
|
+
/** What one harness shim supplies. Everything else is {@link runShim}'s. */
|
|
40
|
+
export interface ShimAdapter {
|
|
41
|
+
/** Map the harness's raw stdin JSON to a guard request; `undefined` = pass through. */
|
|
42
|
+
readonly toRequest: (raw: unknown) => GuardRequest | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* The working directory tool-relative paths resolve against, read from the raw
|
|
45
|
+
* payload when the harness sends one (Claude Code's hook stdin carries `cwd`: the
|
|
46
|
+
* *session's* Bash cwd, which after a `cd` differs from this hook process's own).
|
|
47
|
+
* `undefined` falls back to the shim process's cwd.
|
|
48
|
+
*/
|
|
49
|
+
readonly cwd?: (raw: unknown) => string | undefined;
|
|
50
|
+
/** Render an allow. Most harnesses: empty stdout, exit 0. */
|
|
51
|
+
readonly pass: () => ShimOutput;
|
|
52
|
+
/** Render a deny, in the harness's schema. */
|
|
53
|
+
readonly deny: (raw: unknown, request: GuardRequest, decision: GuardDecision) => ShimOutput;
|
|
54
|
+
/**
|
|
55
|
+
* Render an input rewrite (only ever called for a Bash-shaped request whose
|
|
56
|
+
* decision carries a faithful `suggestion`, and only in rewrite mode). Omit on
|
|
57
|
+
* harnesses whose hook schema cannot modify tool input — they deny instead. A
|
|
58
|
+
* schema with no reason channel must announce the substitution via `stderr`.
|
|
59
|
+
*/
|
|
60
|
+
readonly rewrite?: (raw: unknown, request: GuardRequest, decision: GuardDecision) => ShimOutput;
|
|
61
|
+
}
|
|
62
|
+
/** What a shim writes and how it exits. `stdout` is a complete JSON line or ''. */
|
|
63
|
+
export interface ShimOutput {
|
|
64
|
+
readonly stdout: string;
|
|
65
|
+
/** Written to the shim process's stderr — the announcement channel for harnesses
|
|
66
|
+
* whose rewrite schema has no reason field. Never a decision by itself. */
|
|
67
|
+
readonly stderr?: string;
|
|
68
|
+
readonly exitCode: number;
|
|
69
|
+
}
|
|
70
|
+
/** The pure half of the shim loop, for tests: raw harness input → rendered output. */
|
|
71
|
+
export declare function renderShimDecision(adapter: ShimAdapter, raw: unknown, settings: GuardSettings, cwd: string, statFile?: Parameters<typeof decide>[3]): ShimOutput;
|
|
72
|
+
/**
|
|
73
|
+
* The whole shim as a process: read the harness's stdin, decide, render, exit.
|
|
74
|
+
* Fail open exactly like the guard core — unparseable stdin passes with a stderr
|
|
75
|
+
* warning, and an unexpected error passes too. A shim crash must never read as a
|
|
76
|
+
* policy decision.
|
|
77
|
+
*/
|
|
78
|
+
export declare function runShimMain(adapter: ShimAdapter): void;
|
|
79
|
+
/**
|
|
80
|
+
* Map one `{tool name, tool input}` pair — the shape every surveyed harness feeds its
|
|
81
|
+
* pre-tool hook, under varying key spellings — onto a {@link GuardRequest}.
|
|
82
|
+
* `undefined` for tools the guard has no opinion on: pass through.
|
|
83
|
+
*/
|
|
84
|
+
export declare function toolCallRequest(toolName: unknown, toolInput: unknown, names: {
|
|
85
|
+
readonly read: readonly string[];
|
|
86
|
+
readonly bash: readonly string[];
|
|
87
|
+
}): GuardRequest | undefined;
|
|
88
|
+
/** The raw stdin as a keyed record, or an empty record when it is not object-shaped. */
|
|
89
|
+
export declare function asRecord(raw: unknown): Record<string, unknown>;
|
|
90
|
+
/**
|
|
91
|
+
* One harness's pre-tool hook schema — everything that varies between shims, as a
|
|
92
|
+
* table. Everything that does not vary (the tool mapping, the rewrite-input splice,
|
|
93
|
+
* the announcement, the deny fallback) belongs to {@link shimFromSchema} and exists
|
|
94
|
+
* exactly once.
|
|
95
|
+
*
|
|
96
|
+
* A schema states capability, never invents it: a harness whose hook cannot modify
|
|
97
|
+
* tool input simply omits {@link rewrite}, and `renderShimDecision` then falls back to
|
|
98
|
+
* the deny — whose reason still carries the exact replacement command.
|
|
99
|
+
*/
|
|
100
|
+
export interface HarnessHookSchema {
|
|
101
|
+
/** Tool names this harness spells a file read with. */
|
|
102
|
+
readonly readTools: readonly string[];
|
|
103
|
+
/** Tool names this harness spells a shell command with. */
|
|
104
|
+
readonly bashTools: readonly string[];
|
|
105
|
+
/**
|
|
106
|
+
* Where the tool name lives in the raw payload, in priority order — harnesses
|
|
107
|
+
* disagree about the spelling (`tool_name`, `toolName`), and some nest the call.
|
|
108
|
+
* A dotted key reads through one level of nesting (`preToolUse.toolName`).
|
|
109
|
+
*/
|
|
110
|
+
readonly toolNameKeys: readonly string[];
|
|
111
|
+
/** Where the tool's input object lives, same rules as {@link toolNameKeys}. */
|
|
112
|
+
readonly toolInputKeys: readonly string[];
|
|
113
|
+
/**
|
|
114
|
+
* The payload key carrying the working directory relative paths resolve against,
|
|
115
|
+
* for the harnesses that send one (Claude Code's hook stdin carries the *session's*
|
|
116
|
+
* cwd, which after a `cd` differs from the hook process's own). Absent = the shim
|
|
117
|
+
* process's cwd.
|
|
118
|
+
*/
|
|
119
|
+
readonly cwdKey?: string;
|
|
120
|
+
/** The harness's deny document, with the guard's steering reason in its one slot. */
|
|
121
|
+
readonly deny: (reason: string) => unknown;
|
|
122
|
+
/** The harness's input-rewrite document. Absent on a deny-only harness. */
|
|
123
|
+
readonly rewrite?: HarnessRewriteSchema;
|
|
124
|
+
}
|
|
125
|
+
/** What a rewrite renders, and where the substitution is announced. */
|
|
126
|
+
export interface HarnessRewriteSchema {
|
|
127
|
+
/** The rewrite document this harness's schema wants back. */
|
|
128
|
+
readonly document: (rewrite: RewriteSlots) => unknown;
|
|
129
|
+
/**
|
|
130
|
+
* Where the substitution is announced. `'reason'` when the document carries a
|
|
131
|
+
* reason channel the model reads (the template puts {@link RewriteSlots.announcement}
|
|
132
|
+
* there); `'stderr'` when it does not — a rewrite nobody can see would change what
|
|
133
|
+
* the model asked for without telling anyone.
|
|
134
|
+
*/
|
|
135
|
+
readonly announce: 'reason' | 'stderr';
|
|
136
|
+
}
|
|
137
|
+
/** What a rewrite document is built from. Filled in by {@link shimFromSchema}. */
|
|
138
|
+
export interface RewriteSlots {
|
|
139
|
+
/**
|
|
140
|
+
* The tool's own input with `command` replaced by the suggestion — the splice every
|
|
141
|
+
* rewriting harness needs, and which four shims used to carry verbatim. Harnesses
|
|
142
|
+
* whose rewrite is a shallow merge send only the changed key instead.
|
|
143
|
+
*/
|
|
144
|
+
readonly input: Record<string, unknown>;
|
|
145
|
+
/** The replacement command itself. */
|
|
146
|
+
readonly suggestion: string;
|
|
147
|
+
/** The announcement, for a document with a reason channel. */
|
|
148
|
+
readonly announcement: string;
|
|
149
|
+
}
|
|
150
|
+
/** What a deny says when the decision carries no reason — never an empty message. */
|
|
151
|
+
export declare const DENIED_WITHOUT_REASON = "denied by the smelt guard";
|
|
152
|
+
/**
|
|
153
|
+
* The announcement for a rewrite whose document carries a reason channel the model
|
|
154
|
+
* reads (Claude Code, Codex): the model is told, in its own transcript, that the
|
|
155
|
+
* command it asked for is not the command that ran.
|
|
156
|
+
*/
|
|
157
|
+
export declare function rewroteReason(suggestion: string): string;
|
|
158
|
+
/**
|
|
159
|
+
* The stderr announcement, in two halves so a *generated* consumer can splice values
|
|
160
|
+
* between them: the opencode plugin this installer writes is JavaScript source, and
|
|
161
|
+
* its copy of this sentence used to be hand-typed inside a string template where
|
|
162
|
+
* nothing could see it drift from the shims'. Both halves are pinned by
|
|
163
|
+
* `test/guards/harness-registry.test.ts`.
|
|
164
|
+
*/
|
|
165
|
+
export declare const REWRITE_ANNOUNCEMENT_OPENING = "smelt guard (rewrite mode): substituted the command in-flight with `";
|
|
166
|
+
export declare const REWRITE_ANNOUNCEMENT_JOIN = "`. ";
|
|
167
|
+
/** The one stderr announcement, for a rewrite schema with no reason channel. */
|
|
168
|
+
export declare function rewriteAnnouncement(suggestion: string, reason: string): string;
|
|
169
|
+
/**
|
|
170
|
+
* The adapter a {@link HarnessHookSchema} describes. Everything shared lives here
|
|
171
|
+
* once: the tool mapping, the rewrite-input splice, both announcements, the deny
|
|
172
|
+
* fallback, and the rule that a schema with no `rewrite` renders a deny instead.
|
|
173
|
+
*/
|
|
174
|
+
export declare function shimFromSchema(schema: HarnessHookSchema): ShimAdapter;
|
|
175
|
+
export { isMainModule };
|
|
176
|
+
//# sourceMappingURL=shim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shim.d.ts","sourceRoot":"","sources":["../../src/hooks/shim.ts"],"names":[],"mappings":"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","sourcesContent":["import process from 'node:process';\n\nimport { decide, isMainModule, readAllOfStdin, readGuardSettings } from './guard-core.ts';\nimport type { GuardDecision, GuardRequest, GuardSettings } from './guard-core.ts';\n\n/**\n * The shim runtime — the one loop every per-harness shim runs.\n *\n * A shim is a thin adapter: it maps its harness's native PreToolUse stdin schema\n * onto a {@link GuardRequest}, hands that to the guard core, and renders the\n * {@link GuardDecision} back in the harness's native output schema. All policy —\n * threshold, windows, suggestion rendering, enforcement mode — lives in\n * `guard-core.ts`; a shim that grew an opinion of its own would be a second guard\n * that drifts from the first.\n *\n * Most of a shim is a *table*, not code: which tool names mean read and shell, which\n * payload keys carry them, and the two documents the harness's schema wants back. That\n * table is a {@link HarnessHookSchema}, `shimFromSchema` turns one into a\n * {@link ShimAdapter}, and every harness's schema lives in its profile\n * (`src/harness/<id>.ts`). {@link ShimAdapter} stays public as the escape hatch for a\n * harness a table cannot express.\n *\n * The enforcement gate lives here, once, because it is the same for every harness:\n *\n * - **deny mode (the default)**: a deny is rendered as the harness's deny, with the\n * guard's steering reason. The transcript stays truthful — the model sees what it\n * asked for refused and why, and learns to run the replacement (and\n * `smelt retrieve`) itself.\n * - **rewrite mode (`hooks.enforcement: \"rewrite\"` in smelt.config.json)**: when the\n * denied call is a Bash command, the guard produced a faithful executable\n * `suggestion`, and this harness can modify tool input, the shim substitutes the\n * suggestion instead of denying. A harness that cannot rewrite falls back to the\n * deny — with the exact command in the reason — so rewrite mode never silently\n * weakens into nothing. A substitution is always announced: in the decision\n * reason where the harness's rewrite schema carries one (Claude Code, Codex),\n * and on stderr where it does not (Gemini, Cursor, Hermes, the opencode\n * plugin) — a rewrite nobody can see would change what the model asked for\n * without telling anyone. And a shim never rewrites in deny mode: a silent\n * rewrite changes what the model asked for without telling it, which is this\n * project's signature failure shape applied to the harness.\n */\n\n/** What one harness shim supplies. Everything else is {@link runShim}'s. */\nexport interface ShimAdapter {\n /** Map the harness's raw stdin JSON to a guard request; `undefined` = pass through. */\n readonly toRequest: (raw: unknown) => GuardRequest | undefined;\n /**\n * The working directory tool-relative paths resolve against, read from the raw\n * payload when the harness sends one (Claude Code's hook stdin carries `cwd`: the\n * *session's* Bash cwd, which after a `cd` differs from this hook process's own).\n * `undefined` falls back to the shim process's cwd.\n */\n readonly cwd?: (raw: unknown) => string | undefined;\n /** Render an allow. Most harnesses: empty stdout, exit 0. */\n readonly pass: () => ShimOutput;\n /** Render a deny, in the harness's schema. */\n readonly deny: (raw: unknown, request: GuardRequest, decision: GuardDecision) => ShimOutput;\n /**\n * Render an input rewrite (only ever called for a Bash-shaped request whose\n * decision carries a faithful `suggestion`, and only in rewrite mode). Omit on\n * harnesses whose hook schema cannot modify tool input — they deny instead. A\n * schema with no reason channel must announce the substitution via `stderr`.\n */\n readonly rewrite?: (raw: unknown, request: GuardRequest, decision: GuardDecision) => ShimOutput;\n}\n\n/** What a shim writes and how it exits. `stdout` is a complete JSON line or ''. */\nexport interface ShimOutput {\n readonly stdout: string;\n /** Written to the shim process's stderr — the announcement channel for harnesses\n * whose rewrite schema has no reason field. Never a decision by itself. */\n readonly stderr?: string;\n readonly exitCode: number;\n}\n\n/** The pure half of the shim loop, for tests: raw harness input → rendered output. */\nexport function renderShimDecision(\n adapter: ShimAdapter,\n raw: unknown,\n settings: GuardSettings,\n cwd: string,\n statFile?: Parameters<typeof decide>[3],\n): ShimOutput {\n const request = adapter.toRequest(raw);\n if (request === undefined) return adapter.pass();\n const effectiveCwd = adapter.cwd?.(raw) ?? cwd;\n const decision =\n statFile === undefined\n ? decide(request, settings, effectiveCwd)\n : decide(request, settings, effectiveCwd, statFile);\n if (decision.action === 'allow') return adapter.pass();\n if (\n settings.enforcement === 'rewrite' &&\n adapter.rewrite !== undefined &&\n request.tool === 'Bash' &&\n decision.suggestion !== undefined\n ) {\n return adapter.rewrite(raw, request, decision);\n }\n return adapter.deny(raw, request, decision);\n}\n\n/**\n * The whole shim as a process: read the harness's stdin, decide, render, exit.\n * Fail open exactly like the guard core — unparseable stdin passes with a stderr\n * warning, and an unexpected error passes too. A shim crash must never read as a\n * policy decision.\n */\nexport function runShimMain(adapter: ShimAdapter): void {\n let output: ShimOutput;\n try {\n const text = readAllOfStdin();\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch {\n process.stderr.write(\n `smelt shim: stdin was not JSON — allowing the call. Fix the hook wiring.\\n`,\n );\n raw = undefined;\n }\n if (raw === undefined) {\n output = adapter.pass();\n } else {\n const settings = readGuardSettings(process.cwd(), (text_) =>\n process.stderr.write(`${text_}\\n`),\n );\n output = renderShimDecision(adapter, raw, settings, process.cwd());\n }\n } catch (error) {\n process.stderr.write(\n `smelt shim: unexpected error — allowing the call. ` +\n `${error instanceof Error ? (error.stack ?? error.message) : String(error)}\\n`,\n );\n output = adapter.pass();\n }\n if (output.stderr !== undefined && output.stderr !== '') process.stderr.write(output.stderr);\n if (output.stdout !== '') process.stdout.write(output.stdout);\n process.exitCode = output.exitCode;\n}\n\n/**\n * Map one `{tool name, tool input}` pair — the shape every surveyed harness feeds its\n * pre-tool hook, under varying key spellings — onto a {@link GuardRequest}.\n * `undefined` for tools the guard has no opinion on: pass through.\n */\nexport function toolCallRequest(\n toolName: unknown,\n toolInput: unknown,\n names: { readonly read: readonly string[]; readonly bash: readonly string[] },\n): GuardRequest | undefined {\n if (typeof toolName !== 'string') return undefined;\n const input =\n typeof toolInput === 'object' && toolInput !== null && !Array.isArray(toolInput)\n ? (toolInput as Record<string, unknown>)\n : {};\n\n if (names.read.includes(toolName)) {\n const path = firstString(input, ['file_path', 'path', 'absolute_path', 'filePath']);\n if (path === undefined) return undefined;\n const offsetLimited =\n firstDefined(input, ['offset', 'limit', 'start_line', 'end_line']) !== undefined;\n return { tool: 'Read', input: { path, offsetLimited } };\n }\n if (names.bash.includes(toolName)) {\n const command = firstString(input, ['command']);\n if (command === undefined) return undefined;\n return { tool: 'Bash', input: { command } };\n }\n return undefined;\n}\n\nfunction firstString(input: Record<string, unknown>, keys: readonly string[]): string | undefined {\n for (const key of keys) {\n const value = input[key];\n if (typeof value === 'string' && value !== '') return value;\n }\n return undefined;\n}\n\nfunction firstDefined(input: Record<string, unknown>, keys: readonly string[]): unknown {\n for (const key of keys) {\n if (input[key] !== undefined && input[key] !== null) return input[key];\n }\n return undefined;\n}\n\n/** The raw stdin as a keyed record, or an empty record when it is not object-shaped. */\nexport function asRecord(raw: unknown): Record<string, unknown> {\n return typeof raw === 'object' && raw !== null && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n}\n\n/* ------------------------------------------------------------------------------------\n * The hook schema, as data\n * ---------------------------------------------------------------------------------- */\n\n/**\n * One harness's pre-tool hook schema — everything that varies between shims, as a\n * table. Everything that does not vary (the tool mapping, the rewrite-input splice,\n * the announcement, the deny fallback) belongs to {@link shimFromSchema} and exists\n * exactly once.\n *\n * A schema states capability, never invents it: a harness whose hook cannot modify\n * tool input simply omits {@link rewrite}, and `renderShimDecision` then falls back to\n * the deny — whose reason still carries the exact replacement command.\n */\nexport interface HarnessHookSchema {\n /** Tool names this harness spells a file read with. */\n readonly readTools: readonly string[];\n /** Tool names this harness spells a shell command with. */\n readonly bashTools: readonly string[];\n /**\n * Where the tool name lives in the raw payload, in priority order — harnesses\n * disagree about the spelling (`tool_name`, `toolName`), and some nest the call.\n * A dotted key reads through one level of nesting (`preToolUse.toolName`).\n */\n readonly toolNameKeys: readonly string[];\n /** Where the tool's input object lives, same rules as {@link toolNameKeys}. */\n readonly toolInputKeys: readonly string[];\n /**\n * The payload key carrying the working directory relative paths resolve against,\n * for the harnesses that send one (Claude Code's hook stdin carries the *session's*\n * cwd, which after a `cd` differs from the hook process's own). Absent = the shim\n * process's cwd.\n */\n readonly cwdKey?: string;\n /** The harness's deny document, with the guard's steering reason in its one slot. */\n readonly deny: (reason: string) => unknown;\n /** The harness's input-rewrite document. Absent on a deny-only harness. */\n readonly rewrite?: HarnessRewriteSchema;\n}\n\n/** What a rewrite renders, and where the substitution is announced. */\nexport interface HarnessRewriteSchema {\n /** The rewrite document this harness's schema wants back. */\n readonly document: (rewrite: RewriteSlots) => unknown;\n /**\n * Where the substitution is announced. `'reason'` when the document carries a\n * reason channel the model reads (the template puts {@link RewriteSlots.announcement}\n * there); `'stderr'` when it does not — a rewrite nobody can see would change what\n * the model asked for without telling anyone.\n */\n readonly announce: 'reason' | 'stderr';\n}\n\n/** What a rewrite document is built from. Filled in by {@link shimFromSchema}. */\nexport interface RewriteSlots {\n /**\n * The tool's own input with `command` replaced by the suggestion — the splice every\n * rewriting harness needs, and which four shims used to carry verbatim. Harnesses\n * whose rewrite is a shallow merge send only the changed key instead.\n */\n readonly input: Record<string, unknown>;\n /** The replacement command itself. */\n readonly suggestion: string;\n /** The announcement, for a document with a reason channel. */\n readonly announcement: string;\n}\n\n/** What a deny says when the decision carries no reason — never an empty message. */\nexport const DENIED_WITHOUT_REASON = 'denied by the smelt guard';\n\n/**\n * The announcement for a rewrite whose document carries a reason channel the model\n * reads (Claude Code, Codex): the model is told, in its own transcript, that the\n * command it asked for is not the command that ran.\n */\nexport function rewroteReason(suggestion: string): string {\n return `smelt rewrote this command (hooks.enforcement: \"rewrite\"): ${suggestion}`;\n}\n\n/**\n * The stderr announcement, in two halves so a *generated* consumer can splice values\n * between them: the opencode plugin this installer writes is JavaScript source, and\n * its copy of this sentence used to be hand-typed inside a string template where\n * nothing could see it drift from the shims'. Both halves are pinned by\n * `test/guards/harness-registry.test.ts`.\n */\nexport const REWRITE_ANNOUNCEMENT_OPENING =\n 'smelt guard (rewrite mode): substituted the command in-flight with `';\nexport const REWRITE_ANNOUNCEMENT_JOIN = '`. ';\n\n/** The one stderr announcement, for a rewrite schema with no reason channel. */\nexport function rewriteAnnouncement(suggestion: string, reason: string): string {\n return `${REWRITE_ANNOUNCEMENT_OPENING}${suggestion}${REWRITE_ANNOUNCEMENT_JOIN}${reason}`;\n}\n\n/**\n * The first of `keys` the raw payload carries, `undefined` when it carries none — the\n * `a ?? b ?? c` every shim used to spell by hand, with dotted keys for the harnesses\n * that nest the tool call one level down.\n */\nfunction payloadValue(raw: unknown, keys: readonly string[]): unknown {\n const fields = asRecord(raw);\n for (const key of keys) {\n const dot = key.indexOf('.');\n const value =\n dot === -1 ? fields[key] : asRecord(fields[key.slice(0, dot)])[key.slice(dot + 1)];\n if (value !== undefined && value !== null) return value;\n }\n return undefined;\n}\n\n/**\n * The adapter a {@link HarnessHookSchema} describes. Everything shared lives here\n * once: the tool mapping, the rewrite-input splice, both announcements, the deny\n * fallback, and the rule that a schema with no `rewrite` renders a deny instead.\n */\nexport function shimFromSchema(schema: HarnessHookSchema): ShimAdapter {\n const { cwdKey, rewrite } = schema;\n return {\n toRequest: (raw) =>\n toolCallRequest(\n payloadValue(raw, schema.toolNameKeys),\n payloadValue(raw, schema.toolInputKeys),\n { read: schema.readTools, bash: schema.bashTools },\n ),\n ...(cwdKey === undefined\n ? {}\n : {\n cwd: (raw: unknown): string | undefined => {\n const cwd = asRecord(raw)[cwdKey];\n return typeof cwd === 'string' && cwd !== '' ? cwd : undefined;\n },\n }),\n pass: () => ({ stdout: '', exitCode: 0 }),\n deny: (_raw, _request, decision) => ({\n stdout: `${JSON.stringify(schema.deny(decision.reason ?? DENIED_WITHOUT_REASON))}\\n`,\n exitCode: 0,\n }),\n ...(rewrite === undefined\n ? {}\n : {\n rewrite: (raw: unknown, _request: GuardRequest, decision: GuardDecision): ShimOutput => {\n const suggestion = decision.suggestion ?? '';\n const input = {\n ...asRecord(payloadValue(raw, schema.toolInputKeys)),\n command: decision.suggestion,\n };\n const document = rewrite.document({\n input,\n suggestion,\n announcement: rewroteReason(suggestion),\n });\n return {\n stdout: `${JSON.stringify(document)}\\n`,\n ...(rewrite.announce === 'stderr'\n ? { stderr: `${rewriteAnnouncement(suggestion, decision.reason ?? '')}\\n` }\n : {}),\n exitCode: 0,\n };\n },\n }),\n };\n}\n\nexport { isMainModule };\n"]}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { decide, isMainModule, readAllOfStdin, readGuardSettings } from './guard-core.js';
|
|
3
|
+
/** The pure half of the shim loop, for tests: raw harness input → rendered output. */
|
|
4
|
+
export function renderShimDecision(adapter, raw, settings, cwd, statFile) {
|
|
5
|
+
const request = adapter.toRequest(raw);
|
|
6
|
+
if (request === undefined)
|
|
7
|
+
return adapter.pass();
|
|
8
|
+
const effectiveCwd = adapter.cwd?.(raw) ?? cwd;
|
|
9
|
+
const decision = statFile === undefined
|
|
10
|
+
? decide(request, settings, effectiveCwd)
|
|
11
|
+
: decide(request, settings, effectiveCwd, statFile);
|
|
12
|
+
if (decision.action === 'allow')
|
|
13
|
+
return adapter.pass();
|
|
14
|
+
if (settings.enforcement === 'rewrite' &&
|
|
15
|
+
adapter.rewrite !== undefined &&
|
|
16
|
+
request.tool === 'Bash' &&
|
|
17
|
+
decision.suggestion !== undefined) {
|
|
18
|
+
return adapter.rewrite(raw, request, decision);
|
|
19
|
+
}
|
|
20
|
+
return adapter.deny(raw, request, decision);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The whole shim as a process: read the harness's stdin, decide, render, exit.
|
|
24
|
+
* Fail open exactly like the guard core — unparseable stdin passes with a stderr
|
|
25
|
+
* warning, and an unexpected error passes too. A shim crash must never read as a
|
|
26
|
+
* policy decision.
|
|
27
|
+
*/
|
|
28
|
+
export function runShimMain(adapter) {
|
|
29
|
+
let output;
|
|
30
|
+
try {
|
|
31
|
+
const text = readAllOfStdin();
|
|
32
|
+
let raw;
|
|
33
|
+
try {
|
|
34
|
+
raw = JSON.parse(text);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
process.stderr.write(`smelt shim: stdin was not JSON — allowing the call. Fix the hook wiring.\n`);
|
|
38
|
+
raw = undefined;
|
|
39
|
+
}
|
|
40
|
+
if (raw === undefined) {
|
|
41
|
+
output = adapter.pass();
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const settings = readGuardSettings(process.cwd(), (text_) => process.stderr.write(`${text_}\n`));
|
|
45
|
+
output = renderShimDecision(adapter, raw, settings, process.cwd());
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
process.stderr.write(`smelt shim: unexpected error — allowing the call. ` +
|
|
50
|
+
`${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
|
|
51
|
+
output = adapter.pass();
|
|
52
|
+
}
|
|
53
|
+
if (output.stderr !== undefined && output.stderr !== '')
|
|
54
|
+
process.stderr.write(output.stderr);
|
|
55
|
+
if (output.stdout !== '')
|
|
56
|
+
process.stdout.write(output.stdout);
|
|
57
|
+
process.exitCode = output.exitCode;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Map one `{tool name, tool input}` pair — the shape every surveyed harness feeds its
|
|
61
|
+
* pre-tool hook, under varying key spellings — onto a {@link GuardRequest}.
|
|
62
|
+
* `undefined` for tools the guard has no opinion on: pass through.
|
|
63
|
+
*/
|
|
64
|
+
export function toolCallRequest(toolName, toolInput, names) {
|
|
65
|
+
if (typeof toolName !== 'string')
|
|
66
|
+
return undefined;
|
|
67
|
+
const input = typeof toolInput === 'object' && toolInput !== null && !Array.isArray(toolInput)
|
|
68
|
+
? toolInput
|
|
69
|
+
: {};
|
|
70
|
+
if (names.read.includes(toolName)) {
|
|
71
|
+
const path = firstString(input, ['file_path', 'path', 'absolute_path', 'filePath']);
|
|
72
|
+
if (path === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
const offsetLimited = firstDefined(input, ['offset', 'limit', 'start_line', 'end_line']) !== undefined;
|
|
75
|
+
return { tool: 'Read', input: { path, offsetLimited } };
|
|
76
|
+
}
|
|
77
|
+
if (names.bash.includes(toolName)) {
|
|
78
|
+
const command = firstString(input, ['command']);
|
|
79
|
+
if (command === undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
return { tool: 'Bash', input: { command } };
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
function firstString(input, keys) {
|
|
86
|
+
for (const key of keys) {
|
|
87
|
+
const value = input[key];
|
|
88
|
+
if (typeof value === 'string' && value !== '')
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
function firstDefined(input, keys) {
|
|
94
|
+
for (const key of keys) {
|
|
95
|
+
if (input[key] !== undefined && input[key] !== null)
|
|
96
|
+
return input[key];
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
/** The raw stdin as a keyed record, or an empty record when it is not object-shaped. */
|
|
101
|
+
export function asRecord(raw) {
|
|
102
|
+
return typeof raw === 'object' && raw !== null && !Array.isArray(raw)
|
|
103
|
+
? raw
|
|
104
|
+
: {};
|
|
105
|
+
}
|
|
106
|
+
/** What a deny says when the decision carries no reason — never an empty message. */
|
|
107
|
+
export const DENIED_WITHOUT_REASON = 'denied by the smelt guard';
|
|
108
|
+
/**
|
|
109
|
+
* The announcement for a rewrite whose document carries a reason channel the model
|
|
110
|
+
* reads (Claude Code, Codex): the model is told, in its own transcript, that the
|
|
111
|
+
* command it asked for is not the command that ran.
|
|
112
|
+
*/
|
|
113
|
+
export function rewroteReason(suggestion) {
|
|
114
|
+
return `smelt rewrote this command (hooks.enforcement: "rewrite"): ${suggestion}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The stderr announcement, in two halves so a *generated* consumer can splice values
|
|
118
|
+
* between them: the opencode plugin this installer writes is JavaScript source, and
|
|
119
|
+
* its copy of this sentence used to be hand-typed inside a string template where
|
|
120
|
+
* nothing could see it drift from the shims'. Both halves are pinned by
|
|
121
|
+
* `test/guards/harness-registry.test.ts`.
|
|
122
|
+
*/
|
|
123
|
+
export const REWRITE_ANNOUNCEMENT_OPENING = 'smelt guard (rewrite mode): substituted the command in-flight with `';
|
|
124
|
+
export const REWRITE_ANNOUNCEMENT_JOIN = '`. ';
|
|
125
|
+
/** The one stderr announcement, for a rewrite schema with no reason channel. */
|
|
126
|
+
export function rewriteAnnouncement(suggestion, reason) {
|
|
127
|
+
return `${REWRITE_ANNOUNCEMENT_OPENING}${suggestion}${REWRITE_ANNOUNCEMENT_JOIN}${reason}`;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The first of `keys` the raw payload carries, `undefined` when it carries none — the
|
|
131
|
+
* `a ?? b ?? c` every shim used to spell by hand, with dotted keys for the harnesses
|
|
132
|
+
* that nest the tool call one level down.
|
|
133
|
+
*/
|
|
134
|
+
function payloadValue(raw, keys) {
|
|
135
|
+
const fields = asRecord(raw);
|
|
136
|
+
for (const key of keys) {
|
|
137
|
+
const dot = key.indexOf('.');
|
|
138
|
+
const value = dot === -1 ? fields[key] : asRecord(fields[key.slice(0, dot)])[key.slice(dot + 1)];
|
|
139
|
+
if (value !== undefined && value !== null)
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The adapter a {@link HarnessHookSchema} describes. Everything shared lives here
|
|
146
|
+
* once: the tool mapping, the rewrite-input splice, both announcements, the deny
|
|
147
|
+
* fallback, and the rule that a schema with no `rewrite` renders a deny instead.
|
|
148
|
+
*/
|
|
149
|
+
export function shimFromSchema(schema) {
|
|
150
|
+
const { cwdKey, rewrite } = schema;
|
|
151
|
+
return {
|
|
152
|
+
toRequest: (raw) => toolCallRequest(payloadValue(raw, schema.toolNameKeys), payloadValue(raw, schema.toolInputKeys), { read: schema.readTools, bash: schema.bashTools }),
|
|
153
|
+
...(cwdKey === undefined
|
|
154
|
+
? {}
|
|
155
|
+
: {
|
|
156
|
+
cwd: (raw) => {
|
|
157
|
+
const cwd = asRecord(raw)[cwdKey];
|
|
158
|
+
return typeof cwd === 'string' && cwd !== '' ? cwd : undefined;
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
pass: () => ({ stdout: '', exitCode: 0 }),
|
|
162
|
+
deny: (_raw, _request, decision) => ({
|
|
163
|
+
stdout: `${JSON.stringify(schema.deny(decision.reason ?? DENIED_WITHOUT_REASON))}\n`,
|
|
164
|
+
exitCode: 0,
|
|
165
|
+
}),
|
|
166
|
+
...(rewrite === undefined
|
|
167
|
+
? {}
|
|
168
|
+
: {
|
|
169
|
+
rewrite: (raw, _request, decision) => {
|
|
170
|
+
const suggestion = decision.suggestion ?? '';
|
|
171
|
+
const input = {
|
|
172
|
+
...asRecord(payloadValue(raw, schema.toolInputKeys)),
|
|
173
|
+
command: decision.suggestion,
|
|
174
|
+
};
|
|
175
|
+
const document = rewrite.document({
|
|
176
|
+
input,
|
|
177
|
+
suggestion,
|
|
178
|
+
announcement: rewroteReason(suggestion),
|
|
179
|
+
});
|
|
180
|
+
return {
|
|
181
|
+
stdout: `${JSON.stringify(document)}\n`,
|
|
182
|
+
...(rewrite.announce === 'stderr'
|
|
183
|
+
? { stderr: `${rewriteAnnouncement(suggestion, decision.reason ?? '')}\n` }
|
|
184
|
+
: {}),
|
|
185
|
+
exitCode: 0,
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
export { isMainModule };
|
|
192
|
+
//# sourceMappingURL=shim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shim.js","sourceRoot":"","sources":["../../src/hooks/shim.ts"],"names":[],"mappings":"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","sourcesContent":["import process from 'node:process';\n\nimport { decide, isMainModule, readAllOfStdin, readGuardSettings } from './guard-core.ts';\nimport type { GuardDecision, GuardRequest, GuardSettings } from './guard-core.ts';\n\n/**\n * The shim runtime — the one loop every per-harness shim runs.\n *\n * A shim is a thin adapter: it maps its harness's native PreToolUse stdin schema\n * onto a {@link GuardRequest}, hands that to the guard core, and renders the\n * {@link GuardDecision} back in the harness's native output schema. All policy —\n * threshold, windows, suggestion rendering, enforcement mode — lives in\n * `guard-core.ts`; a shim that grew an opinion of its own would be a second guard\n * that drifts from the first.\n *\n * Most of a shim is a *table*, not code: which tool names mean read and shell, which\n * payload keys carry them, and the two documents the harness's schema wants back. That\n * table is a {@link HarnessHookSchema}, `shimFromSchema` turns one into a\n * {@link ShimAdapter}, and every harness's schema lives in its profile\n * (`src/harness/<id>.ts`). {@link ShimAdapter} stays public as the escape hatch for a\n * harness a table cannot express.\n *\n * The enforcement gate lives here, once, because it is the same for every harness:\n *\n * - **deny mode (the default)**: a deny is rendered as the harness's deny, with the\n * guard's steering reason. The transcript stays truthful — the model sees what it\n * asked for refused and why, and learns to run the replacement (and\n * `smelt retrieve`) itself.\n * - **rewrite mode (`hooks.enforcement: \"rewrite\"` in smelt.config.json)**: when the\n * denied call is a Bash command, the guard produced a faithful executable\n * `suggestion`, and this harness can modify tool input, the shim substitutes the\n * suggestion instead of denying. A harness that cannot rewrite falls back to the\n * deny — with the exact command in the reason — so rewrite mode never silently\n * weakens into nothing. A substitution is always announced: in the decision\n * reason where the harness's rewrite schema carries one (Claude Code, Codex),\n * and on stderr where it does not (Gemini, Cursor, Hermes, the opencode\n * plugin) — a rewrite nobody can see would change what the model asked for\n * without telling anyone. And a shim never rewrites in deny mode: a silent\n * rewrite changes what the model asked for without telling it, which is this\n * project's signature failure shape applied to the harness.\n */\n\n/** What one harness shim supplies. Everything else is {@link runShim}'s. */\nexport interface ShimAdapter {\n /** Map the harness's raw stdin JSON to a guard request; `undefined` = pass through. */\n readonly toRequest: (raw: unknown) => GuardRequest | undefined;\n /**\n * The working directory tool-relative paths resolve against, read from the raw\n * payload when the harness sends one (Claude Code's hook stdin carries `cwd`: the\n * *session's* Bash cwd, which after a `cd` differs from this hook process's own).\n * `undefined` falls back to the shim process's cwd.\n */\n readonly cwd?: (raw: unknown) => string | undefined;\n /** Render an allow. Most harnesses: empty stdout, exit 0. */\n readonly pass: () => ShimOutput;\n /** Render a deny, in the harness's schema. */\n readonly deny: (raw: unknown, request: GuardRequest, decision: GuardDecision) => ShimOutput;\n /**\n * Render an input rewrite (only ever called for a Bash-shaped request whose\n * decision carries a faithful `suggestion`, and only in rewrite mode). Omit on\n * harnesses whose hook schema cannot modify tool input — they deny instead. A\n * schema with no reason channel must announce the substitution via `stderr`.\n */\n readonly rewrite?: (raw: unknown, request: GuardRequest, decision: GuardDecision) => ShimOutput;\n}\n\n/** What a shim writes and how it exits. `stdout` is a complete JSON line or ''. */\nexport interface ShimOutput {\n readonly stdout: string;\n /** Written to the shim process's stderr — the announcement channel for harnesses\n * whose rewrite schema has no reason field. Never a decision by itself. */\n readonly stderr?: string;\n readonly exitCode: number;\n}\n\n/** The pure half of the shim loop, for tests: raw harness input → rendered output. */\nexport function renderShimDecision(\n adapter: ShimAdapter,\n raw: unknown,\n settings: GuardSettings,\n cwd: string,\n statFile?: Parameters<typeof decide>[3],\n): ShimOutput {\n const request = adapter.toRequest(raw);\n if (request === undefined) return adapter.pass();\n const effectiveCwd = adapter.cwd?.(raw) ?? cwd;\n const decision =\n statFile === undefined\n ? decide(request, settings, effectiveCwd)\n : decide(request, settings, effectiveCwd, statFile);\n if (decision.action === 'allow') return adapter.pass();\n if (\n settings.enforcement === 'rewrite' &&\n adapter.rewrite !== undefined &&\n request.tool === 'Bash' &&\n decision.suggestion !== undefined\n ) {\n return adapter.rewrite(raw, request, decision);\n }\n return adapter.deny(raw, request, decision);\n}\n\n/**\n * The whole shim as a process: read the harness's stdin, decide, render, exit.\n * Fail open exactly like the guard core — unparseable stdin passes with a stderr\n * warning, and an unexpected error passes too. A shim crash must never read as a\n * policy decision.\n */\nexport function runShimMain(adapter: ShimAdapter): void {\n let output: ShimOutput;\n try {\n const text = readAllOfStdin();\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch {\n process.stderr.write(\n `smelt shim: stdin was not JSON — allowing the call. Fix the hook wiring.\\n`,\n );\n raw = undefined;\n }\n if (raw === undefined) {\n output = adapter.pass();\n } else {\n const settings = readGuardSettings(process.cwd(), (text_) =>\n process.stderr.write(`${text_}\\n`),\n );\n output = renderShimDecision(adapter, raw, settings, process.cwd());\n }\n } catch (error) {\n process.stderr.write(\n `smelt shim: unexpected error — allowing the call. ` +\n `${error instanceof Error ? (error.stack ?? error.message) : String(error)}\\n`,\n );\n output = adapter.pass();\n }\n if (output.stderr !== undefined && output.stderr !== '') process.stderr.write(output.stderr);\n if (output.stdout !== '') process.stdout.write(output.stdout);\n process.exitCode = output.exitCode;\n}\n\n/**\n * Map one `{tool name, tool input}` pair — the shape every surveyed harness feeds its\n * pre-tool hook, under varying key spellings — onto a {@link GuardRequest}.\n * `undefined` for tools the guard has no opinion on: pass through.\n */\nexport function toolCallRequest(\n toolName: unknown,\n toolInput: unknown,\n names: { readonly read: readonly string[]; readonly bash: readonly string[] },\n): GuardRequest | undefined {\n if (typeof toolName !== 'string') return undefined;\n const input =\n typeof toolInput === 'object' && toolInput !== null && !Array.isArray(toolInput)\n ? (toolInput as Record<string, unknown>)\n : {};\n\n if (names.read.includes(toolName)) {\n const path = firstString(input, ['file_path', 'path', 'absolute_path', 'filePath']);\n if (path === undefined) return undefined;\n const offsetLimited =\n firstDefined(input, ['offset', 'limit', 'start_line', 'end_line']) !== undefined;\n return { tool: 'Read', input: { path, offsetLimited } };\n }\n if (names.bash.includes(toolName)) {\n const command = firstString(input, ['command']);\n if (command === undefined) return undefined;\n return { tool: 'Bash', input: { command } };\n }\n return undefined;\n}\n\nfunction firstString(input: Record<string, unknown>, keys: readonly string[]): string | undefined {\n for (const key of keys) {\n const value = input[key];\n if (typeof value === 'string' && value !== '') return value;\n }\n return undefined;\n}\n\nfunction firstDefined(input: Record<string, unknown>, keys: readonly string[]): unknown {\n for (const key of keys) {\n if (input[key] !== undefined && input[key] !== null) return input[key];\n }\n return undefined;\n}\n\n/** The raw stdin as a keyed record, or an empty record when it is not object-shaped. */\nexport function asRecord(raw: unknown): Record<string, unknown> {\n return typeof raw === 'object' && raw !== null && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n}\n\n/* ------------------------------------------------------------------------------------\n * The hook schema, as data\n * ---------------------------------------------------------------------------------- */\n\n/**\n * One harness's pre-tool hook schema — everything that varies between shims, as a\n * table. Everything that does not vary (the tool mapping, the rewrite-input splice,\n * the announcement, the deny fallback) belongs to {@link shimFromSchema} and exists\n * exactly once.\n *\n * A schema states capability, never invents it: a harness whose hook cannot modify\n * tool input simply omits {@link rewrite}, and `renderShimDecision` then falls back to\n * the deny — whose reason still carries the exact replacement command.\n */\nexport interface HarnessHookSchema {\n /** Tool names this harness spells a file read with. */\n readonly readTools: readonly string[];\n /** Tool names this harness spells a shell command with. */\n readonly bashTools: readonly string[];\n /**\n * Where the tool name lives in the raw payload, in priority order — harnesses\n * disagree about the spelling (`tool_name`, `toolName`), and some nest the call.\n * A dotted key reads through one level of nesting (`preToolUse.toolName`).\n */\n readonly toolNameKeys: readonly string[];\n /** Where the tool's input object lives, same rules as {@link toolNameKeys}. */\n readonly toolInputKeys: readonly string[];\n /**\n * The payload key carrying the working directory relative paths resolve against,\n * for the harnesses that send one (Claude Code's hook stdin carries the *session's*\n * cwd, which after a `cd` differs from the hook process's own). Absent = the shim\n * process's cwd.\n */\n readonly cwdKey?: string;\n /** The harness's deny document, with the guard's steering reason in its one slot. */\n readonly deny: (reason: string) => unknown;\n /** The harness's input-rewrite document. Absent on a deny-only harness. */\n readonly rewrite?: HarnessRewriteSchema;\n}\n\n/** What a rewrite renders, and where the substitution is announced. */\nexport interface HarnessRewriteSchema {\n /** The rewrite document this harness's schema wants back. */\n readonly document: (rewrite: RewriteSlots) => unknown;\n /**\n * Where the substitution is announced. `'reason'` when the document carries a\n * reason channel the model reads (the template puts {@link RewriteSlots.announcement}\n * there); `'stderr'` when it does not — a rewrite nobody can see would change what\n * the model asked for without telling anyone.\n */\n readonly announce: 'reason' | 'stderr';\n}\n\n/** What a rewrite document is built from. Filled in by {@link shimFromSchema}. */\nexport interface RewriteSlots {\n /**\n * The tool's own input with `command` replaced by the suggestion — the splice every\n * rewriting harness needs, and which four shims used to carry verbatim. Harnesses\n * whose rewrite is a shallow merge send only the changed key instead.\n */\n readonly input: Record<string, unknown>;\n /** The replacement command itself. */\n readonly suggestion: string;\n /** The announcement, for a document with a reason channel. */\n readonly announcement: string;\n}\n\n/** What a deny says when the decision carries no reason — never an empty message. */\nexport const DENIED_WITHOUT_REASON = 'denied by the smelt guard';\n\n/**\n * The announcement for a rewrite whose document carries a reason channel the model\n * reads (Claude Code, Codex): the model is told, in its own transcript, that the\n * command it asked for is not the command that ran.\n */\nexport function rewroteReason(suggestion: string): string {\n return `smelt rewrote this command (hooks.enforcement: \"rewrite\"): ${suggestion}`;\n}\n\n/**\n * The stderr announcement, in two halves so a *generated* consumer can splice values\n * between them: the opencode plugin this installer writes is JavaScript source, and\n * its copy of this sentence used to be hand-typed inside a string template where\n * nothing could see it drift from the shims'. Both halves are pinned by\n * `test/guards/harness-registry.test.ts`.\n */\nexport const REWRITE_ANNOUNCEMENT_OPENING =\n 'smelt guard (rewrite mode): substituted the command in-flight with `';\nexport const REWRITE_ANNOUNCEMENT_JOIN = '`. ';\n\n/** The one stderr announcement, for a rewrite schema with no reason channel. */\nexport function rewriteAnnouncement(suggestion: string, reason: string): string {\n return `${REWRITE_ANNOUNCEMENT_OPENING}${suggestion}${REWRITE_ANNOUNCEMENT_JOIN}${reason}`;\n}\n\n/**\n * The first of `keys` the raw payload carries, `undefined` when it carries none — the\n * `a ?? b ?? c` every shim used to spell by hand, with dotted keys for the harnesses\n * that nest the tool call one level down.\n */\nfunction payloadValue(raw: unknown, keys: readonly string[]): unknown {\n const fields = asRecord(raw);\n for (const key of keys) {\n const dot = key.indexOf('.');\n const value =\n dot === -1 ? fields[key] : asRecord(fields[key.slice(0, dot)])[key.slice(dot + 1)];\n if (value !== undefined && value !== null) return value;\n }\n return undefined;\n}\n\n/**\n * The adapter a {@link HarnessHookSchema} describes. Everything shared lives here\n * once: the tool mapping, the rewrite-input splice, both announcements, the deny\n * fallback, and the rule that a schema with no `rewrite` renders a deny instead.\n */\nexport function shimFromSchema(schema: HarnessHookSchema): ShimAdapter {\n const { cwdKey, rewrite } = schema;\n return {\n toRequest: (raw) =>\n toolCallRequest(\n payloadValue(raw, schema.toolNameKeys),\n payloadValue(raw, schema.toolInputKeys),\n { read: schema.readTools, bash: schema.bashTools },\n ),\n ...(cwdKey === undefined\n ? {}\n : {\n cwd: (raw: unknown): string | undefined => {\n const cwd = asRecord(raw)[cwdKey];\n return typeof cwd === 'string' && cwd !== '' ? cwd : undefined;\n },\n }),\n pass: () => ({ stdout: '', exitCode: 0 }),\n deny: (_raw, _request, decision) => ({\n stdout: `${JSON.stringify(schema.deny(decision.reason ?? DENIED_WITHOUT_REASON))}\\n`,\n exitCode: 0,\n }),\n ...(rewrite === undefined\n ? {}\n : {\n rewrite: (raw: unknown, _request: GuardRequest, decision: GuardDecision): ShimOutput => {\n const suggestion = decision.suggestion ?? '';\n const input = {\n ...asRecord(payloadValue(raw, schema.toolInputKeys)),\n command: decision.suggestion,\n };\n const document = rewrite.document({\n input,\n suggestion,\n announcement: rewroteReason(suggestion),\n });\n return {\n stdout: `${JSON.stringify(document)}\\n`,\n ...(rewrite.announce === 'stderr'\n ? { stderr: `${rewriteAnnouncement(suggestion, decision.reason ?? '')}\\n` }\n : {}),\n exitCode: 0,\n };\n },\n }),\n };\n}\n\nexport { isMainModule };\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ShimAdapter } from '../shim.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code shim — VERIFIED tier. The runnable front door only: every fact about this
|
|
4
|
+
* harness, its hook schema included, lives in its profile
|
|
5
|
+
* (`src/harness/claude-code.ts`), and `shimFromSchema` turns that schema into the
|
|
6
|
+
* adapter below. The installer wires `node dist/hooks/shims/claude-code.js` as the
|
|
7
|
+
* harness's hook command; this file is what that runs.
|
|
8
|
+
*/
|
|
9
|
+
export declare const adapter: ShimAdapter;
|
|
10
|
+
//# sourceMappingURL=claude-code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../../src/hooks/shims/claude-code.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,WAAuC,CAAC","sourcesContent":["import { claudeCode } from '../../harness/claude-code.ts';\nimport { shimAdapterOf } from '../../harness/profile.ts';\nimport { isMainModule, runShimMain } from '../shim.ts';\nimport type { ShimAdapter } from '../shim.ts';\n\n/**\n * Claude Code shim — VERIFIED tier. The runnable front door only: every fact about this\n * harness, its hook schema included, lives in its profile\n * (`src/harness/claude-code.ts`), and `shimFromSchema` turns that schema into the\n * adapter below. The installer wires `node dist/hooks/shims/claude-code.js` as the\n * harness's hook command; this file is what that runs.\n */\nexport const adapter: ShimAdapter = shimAdapterOf(claudeCode);\n\nif (isMainModule(import.meta.url)) runShimMain(adapter);\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { claudeCode } from '../../harness/claude-code.js';
|
|
2
|
+
import { shimAdapterOf } from '../../harness/profile.js';
|
|
3
|
+
import { isMainModule, runShimMain } from '../shim.js';
|
|
4
|
+
/**
|
|
5
|
+
* Claude Code shim — VERIFIED tier. The runnable front door only: every fact about this
|
|
6
|
+
* harness, its hook schema included, lives in its profile
|
|
7
|
+
* (`src/harness/claude-code.ts`), and `shimFromSchema` turns that schema into the
|
|
8
|
+
* adapter below. The installer wires `node dist/hooks/shims/claude-code.js` as the
|
|
9
|
+
* harness's hook command; this file is what that runs.
|
|
10
|
+
*/
|
|
11
|
+
export const adapter = shimAdapterOf(claudeCode);
|
|
12
|
+
if (isMainModule(import.meta.url))
|
|
13
|
+
runShimMain(adapter);
|
|
14
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../../src/hooks/shims/claude-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGvD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAgB,aAAa,CAAC,UAAU,CAAC,CAAC;AAE9D,IAAI,YAAY,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC;IAAE,WAAW,CAAC,OAAO,CAAC,CAAC","sourcesContent":["import { claudeCode } from '../../harness/claude-code.ts';\nimport { shimAdapterOf } from '../../harness/profile.ts';\nimport { isMainModule, runShimMain } from '../shim.ts';\nimport type { ShimAdapter } from '../shim.ts';\n\n/**\n * Claude Code shim — VERIFIED tier. The runnable front door only: every fact about this\n * harness, its hook schema included, lives in its profile\n * (`src/harness/claude-code.ts`), and `shimFromSchema` turns that schema into the\n * adapter below. The installer wires `node dist/hooks/shims/claude-code.js` as the\n * harness's hook command; this file is what that runs.\n */\nexport const adapter: ShimAdapter = shimAdapterOf(claudeCode);\n\nif (isMainModule(import.meta.url)) runShimMain(adapter);\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ShimAdapter } from '../shim.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Cline shim — EXPERIMENTAL tier. The runnable front door only: every fact about this
|
|
4
|
+
* harness, its hook schema included, lives in its profile
|
|
5
|
+
* (`src/harness/cline.ts`), and `shimFromSchema` turns that schema into the
|
|
6
|
+
* adapter below. The installer wires `node dist/hooks/shims/cline.js` as the
|
|
7
|
+
* harness's hook command; this file is what that runs.
|
|
8
|
+
*/
|
|
9
|
+
export declare const adapter: ShimAdapter;
|
|
10
|
+
//# sourceMappingURL=cline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cline.d.ts","sourceRoot":"","sources":["../../../src/hooks/shims/cline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,WAAkC,CAAC","sourcesContent":["import { cline } from '../../harness/cline.ts';\nimport { shimAdapterOf } from '../../harness/profile.ts';\nimport { isMainModule, runShimMain } from '../shim.ts';\nimport type { ShimAdapter } from '../shim.ts';\n\n/**\n * Cline shim — EXPERIMENTAL tier. The runnable front door only: every fact about this\n * harness, its hook schema included, lives in its profile\n * (`src/harness/cline.ts`), and `shimFromSchema` turns that schema into the\n * adapter below. The installer wires `node dist/hooks/shims/cline.js` as the\n * harness's hook command; this file is what that runs.\n */\nexport const adapter: ShimAdapter = shimAdapterOf(cline);\n\nif (isMainModule(import.meta.url)) runShimMain(adapter);\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { cline } from '../../harness/cline.js';
|
|
2
|
+
import { shimAdapterOf } from '../../harness/profile.js';
|
|
3
|
+
import { isMainModule, runShimMain } from '../shim.js';
|
|
4
|
+
/**
|
|
5
|
+
* Cline shim — EXPERIMENTAL tier. The runnable front door only: every fact about this
|
|
6
|
+
* harness, its hook schema included, lives in its profile
|
|
7
|
+
* (`src/harness/cline.ts`), and `shimFromSchema` turns that schema into the
|
|
8
|
+
* adapter below. The installer wires `node dist/hooks/shims/cline.js` as the
|
|
9
|
+
* harness's hook command; this file is what that runs.
|
|
10
|
+
*/
|
|
11
|
+
export const adapter = shimAdapterOf(cline);
|
|
12
|
+
if (isMainModule(import.meta.url))
|
|
13
|
+
runShimMain(adapter);
|
|
14
|
+
//# sourceMappingURL=cline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cline.js","sourceRoot":"","sources":["../../../src/hooks/shims/cline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGvD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAgB,aAAa,CAAC,KAAK,CAAC,CAAC;AAEzD,IAAI,YAAY,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC;IAAE,WAAW,CAAC,OAAO,CAAC,CAAC","sourcesContent":["import { cline } from '../../harness/cline.ts';\nimport { shimAdapterOf } from '../../harness/profile.ts';\nimport { isMainModule, runShimMain } from '../shim.ts';\nimport type { ShimAdapter } from '../shim.ts';\n\n/**\n * Cline shim — EXPERIMENTAL tier. The runnable front door only: every fact about this\n * harness, its hook schema included, lives in its profile\n * (`src/harness/cline.ts`), and `shimFromSchema` turns that schema into the\n * adapter below. The installer wires `node dist/hooks/shims/cline.js` as the\n * harness's hook command; this file is what that runs.\n */\nexport const adapter: ShimAdapter = shimAdapterOf(cline);\n\nif (isMainModule(import.meta.url)) runShimMain(adapter);\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ShimAdapter } from '../shim.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Codex CLI shim — VERIFIED tier. The runnable front door only: every fact about this
|
|
4
|
+
* harness, its hook schema included, lives in its profile
|
|
5
|
+
* (`src/harness/codex.ts`), and `shimFromSchema` turns that schema into the
|
|
6
|
+
* adapter below. The installer wires `node dist/hooks/shims/codex.js` as the
|
|
7
|
+
* harness's hook command; this file is what that runs.
|
|
8
|
+
*/
|
|
9
|
+
export declare const adapter: ShimAdapter;
|
|
10
|
+
//# sourceMappingURL=codex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../../src/hooks/shims/codex.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,WAAkC,CAAC","sourcesContent":["import { codex } from '../../harness/codex.ts';\nimport { shimAdapterOf } from '../../harness/profile.ts';\nimport { isMainModule, runShimMain } from '../shim.ts';\nimport type { ShimAdapter } from '../shim.ts';\n\n/**\n * Codex CLI shim — VERIFIED tier. The runnable front door only: every fact about this\n * harness, its hook schema included, lives in its profile\n * (`src/harness/codex.ts`), and `shimFromSchema` turns that schema into the\n * adapter below. The installer wires `node dist/hooks/shims/codex.js` as the\n * harness's hook command; this file is what that runs.\n */\nexport const adapter: ShimAdapter = shimAdapterOf(codex);\n\nif (isMainModule(import.meta.url)) runShimMain(adapter);\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { codex } from '../../harness/codex.js';
|
|
2
|
+
import { shimAdapterOf } from '../../harness/profile.js';
|
|
3
|
+
import { isMainModule, runShimMain } from '../shim.js';
|
|
4
|
+
/**
|
|
5
|
+
* Codex CLI shim — VERIFIED tier. The runnable front door only: every fact about this
|
|
6
|
+
* harness, its hook schema included, lives in its profile
|
|
7
|
+
* (`src/harness/codex.ts`), and `shimFromSchema` turns that schema into the
|
|
8
|
+
* adapter below. The installer wires `node dist/hooks/shims/codex.js` as the
|
|
9
|
+
* harness's hook command; this file is what that runs.
|
|
10
|
+
*/
|
|
11
|
+
export const adapter = shimAdapterOf(codex);
|
|
12
|
+
if (isMainModule(import.meta.url))
|
|
13
|
+
runShimMain(adapter);
|
|
14
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../../src/hooks/shims/codex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGvD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAgB,aAAa,CAAC,KAAK,CAAC,CAAC;AAEzD,IAAI,YAAY,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC;IAAE,WAAW,CAAC,OAAO,CAAC,CAAC","sourcesContent":["import { codex } from '../../harness/codex.ts';\nimport { shimAdapterOf } from '../../harness/profile.ts';\nimport { isMainModule, runShimMain } from '../shim.ts';\nimport type { ShimAdapter } from '../shim.ts';\n\n/**\n * Codex CLI shim — VERIFIED tier. The runnable front door only: every fact about this\n * harness, its hook schema included, lives in its profile\n * (`src/harness/codex.ts`), and `shimFromSchema` turns that schema into the\n * adapter below. The installer wires `node dist/hooks/shims/codex.js` as the\n * harness's hook command; this file is what that runs.\n */\nexport const adapter: ShimAdapter = shimAdapterOf(codex);\n\nif (isMainModule(import.meta.url)) runShimMain(adapter);\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ShimAdapter } from '../shim.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Cursor shim — EXPERIMENTAL tier. The runnable front door only: every fact about this
|
|
4
|
+
* harness, its hook schema included, lives in its profile
|
|
5
|
+
* (`src/harness/cursor.ts`), and `shimFromSchema` turns that schema into the
|
|
6
|
+
* adapter below. The installer wires `node dist/hooks/shims/cursor.js` as the
|
|
7
|
+
* harness's hook command; this file is what that runs.
|
|
8
|
+
*/
|
|
9
|
+
export declare const adapter: ShimAdapter;
|
|
10
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../../src/hooks/shims/cursor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,WAAmC,CAAC","sourcesContent":["import { cursor } from '../../harness/cursor.ts';\nimport { shimAdapterOf } from '../../harness/profile.ts';\nimport { isMainModule, runShimMain } from '../shim.ts';\nimport type { ShimAdapter } from '../shim.ts';\n\n/**\n * Cursor shim — EXPERIMENTAL tier. The runnable front door only: every fact about this\n * harness, its hook schema included, lives in its profile\n * (`src/harness/cursor.ts`), and `shimFromSchema` turns that schema into the\n * adapter below. The installer wires `node dist/hooks/shims/cursor.js` as the\n * harness's hook command; this file is what that runs.\n */\nexport const adapter: ShimAdapter = shimAdapterOf(cursor);\n\nif (isMainModule(import.meta.url)) runShimMain(adapter);\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { cursor } from '../../harness/cursor.js';
|
|
2
|
+
import { shimAdapterOf } from '../../harness/profile.js';
|
|
3
|
+
import { isMainModule, runShimMain } from '../shim.js';
|
|
4
|
+
/**
|
|
5
|
+
* Cursor shim — EXPERIMENTAL tier. The runnable front door only: every fact about this
|
|
6
|
+
* harness, its hook schema included, lives in its profile
|
|
7
|
+
* (`src/harness/cursor.ts`), and `shimFromSchema` turns that schema into the
|
|
8
|
+
* adapter below. The installer wires `node dist/hooks/shims/cursor.js` as the
|
|
9
|
+
* harness's hook command; this file is what that runs.
|
|
10
|
+
*/
|
|
11
|
+
export const adapter = shimAdapterOf(cursor);
|
|
12
|
+
if (isMainModule(import.meta.url))
|
|
13
|
+
runShimMain(adapter);
|
|
14
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../../src/hooks/shims/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGvD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAgB,aAAa,CAAC,MAAM,CAAC,CAAC;AAE1D,IAAI,YAAY,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC;IAAE,WAAW,CAAC,OAAO,CAAC,CAAC","sourcesContent":["import { cursor } from '../../harness/cursor.ts';\nimport { shimAdapterOf } from '../../harness/profile.ts';\nimport { isMainModule, runShimMain } from '../shim.ts';\nimport type { ShimAdapter } from '../shim.ts';\n\n/**\n * Cursor shim — EXPERIMENTAL tier. The runnable front door only: every fact about this\n * harness, its hook schema included, lives in its profile\n * (`src/harness/cursor.ts`), and `shimFromSchema` turns that schema into the\n * adapter below. The installer wires `node dist/hooks/shims/cursor.js` as the\n * harness's hook command; this file is what that runs.\n */\nexport const adapter: ShimAdapter = shimAdapterOf(cursor);\n\nif (isMainModule(import.meta.url)) runShimMain(adapter);\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ShimAdapter } from '../shim.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Gemini CLI shim — EXPERIMENTAL tier. The runnable front door only: every fact about this
|
|
4
|
+
* harness, its hook schema included, lives in its profile
|
|
5
|
+
* (`src/harness/gemini.ts`), and `shimFromSchema` turns that schema into the
|
|
6
|
+
* adapter below. The installer wires `node dist/hooks/shims/gemini.js` as the
|
|
7
|
+
* harness's hook command; this file is what that runs.
|
|
8
|
+
*/
|
|
9
|
+
export declare const adapter: ShimAdapter;
|
|
10
|
+
//# sourceMappingURL=gemini.d.ts.map
|