@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.
Files changed (319) hide show
  1. package/README.md +4 -4
  2. package/dist/apply.d.ts.map +1 -1
  3. package/dist/apply.js.map +1 -1
  4. package/dist/cache/prefix.d.ts.map +1 -1
  5. package/dist/cache/prefix.js.map +1 -1
  6. package/dist/cli/args.d.ts +16 -59
  7. package/dist/cli/args.d.ts.map +1 -1
  8. package/dist/cli/args.js +18 -277
  9. package/dist/cli/args.js.map +1 -1
  10. package/dist/cli/bin.d.ts.map +1 -1
  11. package/dist/cli/bin.js.map +1 -1
  12. package/dist/cli/config.d.ts +62 -0
  13. package/dist/cli/config.d.ts.map +1 -1
  14. package/dist/cli/config.js +87 -2
  15. package/dist/cli/config.js.map +1 -1
  16. package/dist/cli/hooks.d.ts +148 -0
  17. package/dist/cli/hooks.d.ts.map +1 -0
  18. package/dist/cli/hooks.js +835 -0
  19. package/dist/cli/hooks.js.map +1 -0
  20. package/dist/cli/init.d.ts +30 -13
  21. package/dist/cli/init.d.ts.map +1 -1
  22. package/dist/cli/init.js +173 -38
  23. package/dist/cli/init.js.map +1 -1
  24. package/dist/cli/report.d.ts.map +1 -1
  25. package/dist/cli/report.js +2 -2
  26. package/dist/cli/report.js.map +1 -1
  27. package/dist/cli/run.d.ts +25 -88
  28. package/dist/cli/run.d.ts.map +1 -1
  29. package/dist/cli/run.js +32 -322
  30. package/dist/cli/run.js.map +1 -1
  31. package/dist/cli/shell.d.ts +106 -0
  32. package/dist/cli/shell.d.ts.map +1 -0
  33. package/dist/cli/shell.js +100 -0
  34. package/dist/cli/shell.js.map +1 -0
  35. package/dist/cli/subcommands/flags.d.ts +141 -0
  36. package/dist/cli/subcommands/flags.d.ts.map +1 -0
  37. package/dist/cli/subcommands/flags.js +208 -0
  38. package/dist/cli/subcommands/flags.js.map +1 -0
  39. package/dist/cli/subcommands/hooks.d.ts +18 -0
  40. package/dist/cli/subcommands/hooks.d.ts.map +1 -0
  41. package/dist/cli/subcommands/hooks.js +64 -0
  42. package/dist/cli/subcommands/hooks.js.map +1 -0
  43. package/dist/cli/subcommands/init.d.ts +19 -0
  44. package/dist/cli/subcommands/init.d.ts.map +1 -0
  45. package/dist/cli/subcommands/init.js +44 -0
  46. package/dist/cli/subcommands/init.js.map +1 -0
  47. package/dist/cli/subcommands/map.d.ts +75 -0
  48. package/dist/cli/subcommands/map.d.ts.map +1 -0
  49. package/dist/cli/subcommands/map.js +132 -0
  50. package/dist/cli/subcommands/map.js.map +1 -0
  51. package/dist/cli/subcommands/registry.d.ts +80 -0
  52. package/dist/cli/subcommands/registry.d.ts.map +1 -0
  53. package/dist/cli/subcommands/registry.js +138 -0
  54. package/dist/cli/subcommands/registry.js.map +1 -0
  55. package/dist/cli/subcommands/retrieve.d.ts +74 -0
  56. package/dist/cli/subcommands/retrieve.d.ts.map +1 -0
  57. package/dist/cli/subcommands/retrieve.js +84 -0
  58. package/dist/cli/subcommands/retrieve.js.map +1 -0
  59. package/dist/cli/subcommands/smelt.d.ts +107 -0
  60. package/dist/cli/subcommands/smelt.d.ts.map +1 -0
  61. package/dist/cli/subcommands/smelt.js +304 -0
  62. package/dist/cli/subcommands/smelt.js.map +1 -0
  63. package/dist/cli/subcommands/stats.d.ts +43 -0
  64. package/dist/cli/subcommands/stats.d.ts.map +1 -0
  65. package/dist/cli/subcommands/stats.js +62 -0
  66. package/dist/cli/subcommands/stats.js.map +1 -0
  67. package/dist/cli/subcommands/subcommand.d.ts +113 -0
  68. package/dist/cli/subcommands/subcommand.d.ts.map +1 -0
  69. package/dist/cli/subcommands/subcommand.js +3 -0
  70. package/dist/cli/subcommands/subcommand.js.map +1 -0
  71. package/dist/cli/usage.d.ts +3 -0
  72. package/dist/cli/usage.d.ts.map +1 -0
  73. package/dist/cli/usage.js +95 -0
  74. package/dist/cli/usage.js.map +1 -0
  75. package/dist/detect.d.ts.map +1 -1
  76. package/dist/detect.js.map +1 -1
  77. package/dist/errors.d.ts +21 -1
  78. package/dist/errors.d.ts.map +1 -1
  79. package/dist/errors.js +33 -1
  80. package/dist/errors.js.map +1 -1
  81. package/dist/harness/aider.d.ts +10 -0
  82. package/dist/harness/aider.d.ts.map +1 -0
  83. package/dist/harness/aider.js +21 -0
  84. package/dist/harness/aider.js.map +1 -0
  85. package/dist/harness/claude-code.d.ts +3 -0
  86. package/dist/harness/claude-code.d.ts.map +1 -0
  87. package/dist/harness/claude-code.js +68 -0
  88. package/dist/harness/claude-code.js.map +1 -0
  89. package/dist/harness/cline.d.ts +3 -0
  90. package/dist/harness/cline.d.ts.map +1 -0
  91. package/dist/harness/cline.js +54 -0
  92. package/dist/harness/cline.js.map +1 -0
  93. package/dist/harness/codex.d.ts +3 -0
  94. package/dist/harness/codex.d.ts.map +1 -0
  95. package/dist/harness/codex.js +91 -0
  96. package/dist/harness/codex.js.map +1 -0
  97. package/dist/harness/cursor.d.ts +3 -0
  98. package/dist/harness/cursor.d.ts.map +1 -0
  99. package/dist/harness/cursor.js +54 -0
  100. package/dist/harness/cursor.js.map +1 -0
  101. package/dist/harness/gemini.d.ts +3 -0
  102. package/dist/harness/gemini.d.ts.map +1 -0
  103. package/dist/harness/gemini.js +59 -0
  104. package/dist/harness/gemini.js.map +1 -0
  105. package/dist/harness/grok.d.ts +3 -0
  106. package/dist/harness/grok.d.ts.map +1 -0
  107. package/dist/harness/grok.js +46 -0
  108. package/dist/harness/grok.js.map +1 -0
  109. package/dist/harness/hermes.d.ts +3 -0
  110. package/dist/harness/hermes.d.ts.map +1 -0
  111. package/dist/harness/hermes.js +71 -0
  112. package/dist/harness/hermes.js.map +1 -0
  113. package/dist/harness/kilocode.d.ts +3 -0
  114. package/dist/harness/kilocode.d.ts.map +1 -0
  115. package/dist/harness/kilocode.js +35 -0
  116. package/dist/harness/kilocode.js.map +1 -0
  117. package/dist/harness/opencode.d.ts +3 -0
  118. package/dist/harness/opencode.d.ts.map +1 -0
  119. package/dist/harness/opencode.js +97 -0
  120. package/dist/harness/opencode.js.map +1 -0
  121. package/dist/harness/paths.d.ts +17 -0
  122. package/dist/harness/paths.d.ts.map +1 -0
  123. package/dist/harness/paths.js +45 -0
  124. package/dist/harness/paths.js.map +1 -0
  125. package/dist/harness/profile.d.ts +184 -0
  126. package/dist/harness/profile.d.ts.map +1 -0
  127. package/dist/harness/profile.js +26 -0
  128. package/dist/harness/profile.js.map +1 -0
  129. package/dist/harness/registry.d.ts +51 -0
  130. package/dist/harness/registry.d.ts.map +1 -0
  131. package/dist/harness/registry.js +87 -0
  132. package/dist/harness/registry.js.map +1 -0
  133. package/dist/harness/snippet.d.ts +19 -0
  134. package/dist/harness/snippet.d.ts.map +1 -0
  135. package/dist/harness/snippet.js +41 -0
  136. package/dist/harness/snippet.js.map +1 -0
  137. package/dist/hash.d.ts.map +1 -1
  138. package/dist/hash.js.map +1 -1
  139. package/dist/hooks/guard-core.d.ts +164 -0
  140. package/dist/hooks/guard-core.d.ts.map +1 -0
  141. package/dist/hooks/guard-core.js +491 -0
  142. package/dist/hooks/guard-core.js.map +1 -0
  143. package/dist/hooks/shim.d.ts +176 -0
  144. package/dist/hooks/shim.d.ts.map +1 -0
  145. package/dist/hooks/shim.js +192 -0
  146. package/dist/hooks/shim.js.map +1 -0
  147. package/dist/hooks/shims/claude-code.d.ts +10 -0
  148. package/dist/hooks/shims/claude-code.d.ts.map +1 -0
  149. package/dist/hooks/shims/claude-code.js +14 -0
  150. package/dist/hooks/shims/claude-code.js.map +1 -0
  151. package/dist/hooks/shims/cline.d.ts +10 -0
  152. package/dist/hooks/shims/cline.d.ts.map +1 -0
  153. package/dist/hooks/shims/cline.js +14 -0
  154. package/dist/hooks/shims/cline.js.map +1 -0
  155. package/dist/hooks/shims/codex.d.ts +10 -0
  156. package/dist/hooks/shims/codex.d.ts.map +1 -0
  157. package/dist/hooks/shims/codex.js +14 -0
  158. package/dist/hooks/shims/codex.js.map +1 -0
  159. package/dist/hooks/shims/cursor.d.ts +10 -0
  160. package/dist/hooks/shims/cursor.d.ts.map +1 -0
  161. package/dist/hooks/shims/cursor.js +14 -0
  162. package/dist/hooks/shims/cursor.js.map +1 -0
  163. package/dist/hooks/shims/gemini.d.ts +10 -0
  164. package/dist/hooks/shims/gemini.d.ts.map +1 -0
  165. package/dist/hooks/shims/gemini.js +14 -0
  166. package/dist/hooks/shims/gemini.js.map +1 -0
  167. package/dist/hooks/shims/grok.d.ts +10 -0
  168. package/dist/hooks/shims/grok.d.ts.map +1 -0
  169. package/dist/hooks/shims/grok.js +14 -0
  170. package/dist/hooks/shims/grok.js.map +1 -0
  171. package/dist/hooks/shims/hermes.d.ts +10 -0
  172. package/dist/hooks/shims/hermes.d.ts.map +1 -0
  173. package/dist/hooks/shims/hermes.js +14 -0
  174. package/dist/hooks/shims/hermes.js.map +1 -0
  175. package/dist/index.d.ts +33 -76
  176. package/dist/index.d.ts.map +1 -1
  177. package/dist/index.js +13 -67
  178. package/dist/index.js.map +1 -1
  179. package/dist/lang/bash.d.ts.map +1 -1
  180. package/dist/lang/bash.js.map +1 -1
  181. package/dist/lang/c.d.ts.map +1 -1
  182. package/dist/lang/c.js.map +1 -1
  183. package/dist/lang/c_sharp.d.ts.map +1 -1
  184. package/dist/lang/c_sharp.js.map +1 -1
  185. package/dist/lang/cpp.d.ts.map +1 -1
  186. package/dist/lang/cpp.js.map +1 -1
  187. package/dist/lang/go.d.ts.map +1 -1
  188. package/dist/lang/go.js.map +1 -1
  189. package/dist/lang/java.d.ts.map +1 -1
  190. package/dist/lang/java.js.map +1 -1
  191. package/dist/lang/javascript.d.ts.map +1 -1
  192. package/dist/lang/javascript.js.map +1 -1
  193. package/dist/lang/kotlin.d.ts.map +1 -1
  194. package/dist/lang/kotlin.js.map +1 -1
  195. package/dist/lang/php.d.ts.map +1 -1
  196. package/dist/lang/php.js.map +1 -1
  197. package/dist/lang/profile.d.ts.map +1 -1
  198. package/dist/lang/profile.js.map +1 -1
  199. package/dist/lang/python.d.ts.map +1 -1
  200. package/dist/lang/python.js.map +1 -1
  201. package/dist/lang/registry.d.ts.map +1 -1
  202. package/dist/lang/registry.js.map +1 -1
  203. package/dist/lang/ruby.d.ts.map +1 -1
  204. package/dist/lang/ruby.js.map +1 -1
  205. package/dist/lang/rust.d.ts.map +1 -1
  206. package/dist/lang/rust.js.map +1 -1
  207. package/dist/lang/swift.d.ts.map +1 -1
  208. package/dist/lang/swift.js.map +1 -1
  209. package/dist/lang/tsx.d.ts.map +1 -1
  210. package/dist/lang/tsx.js.map +1 -1
  211. package/dist/lang/typescript.d.ts.map +1 -1
  212. package/dist/lang/typescript.js.map +1 -1
  213. package/dist/net/policy.d.ts +18 -1
  214. package/dist/net/policy.d.ts.map +1 -1
  215. package/dist/net/policy.js +7 -4
  216. package/dist/net/policy.js.map +1 -1
  217. package/dist/ops/index.d.ts +39 -0
  218. package/dist/ops/index.d.ts.map +1 -0
  219. package/dist/ops/index.js +37 -0
  220. package/dist/ops/index.js.map +1 -0
  221. package/dist/ops/inputs.d.ts +170 -0
  222. package/dist/ops/inputs.d.ts.map +1 -0
  223. package/dist/ops/inputs.js +142 -0
  224. package/dist/ops/inputs.js.map +1 -0
  225. package/dist/ops/verbs.d.ts +136 -0
  226. package/dist/ops/verbs.d.ts.map +1 -0
  227. package/dist/ops/verbs.js +76 -0
  228. package/dist/ops/verbs.js.map +1 -0
  229. package/dist/plan/auto.d.ts +69 -0
  230. package/dist/plan/auto.d.ts.map +1 -0
  231. package/dist/plan/auto.js +70 -0
  232. package/dist/plan/auto.js.map +1 -0
  233. package/dist/plan/budget.d.ts +21 -0
  234. package/dist/plan/budget.d.ts.map +1 -0
  235. package/dist/plan/budget.js +26 -0
  236. package/dist/plan/budget.js.map +1 -0
  237. package/dist/plan/grammar.d.ts +14 -0
  238. package/dist/plan/grammar.d.ts.map +1 -1
  239. package/dist/plan/grammar.js +47 -6
  240. package/dist/plan/grammar.js.map +1 -1
  241. package/dist/plan/lexical.d.ts.map +1 -1
  242. package/dist/plan/lexical.js +1 -7
  243. package/dist/plan/lexical.js.map +1 -1
  244. package/dist/plan/planners.d.ts +35 -2
  245. package/dist/plan/planners.d.ts.map +1 -1
  246. package/dist/plan/planners.js +35 -2
  247. package/dist/plan/planners.js.map +1 -1
  248. package/dist/plan/structural.d.ts +19 -5
  249. package/dist/plan/structural.d.ts.map +1 -1
  250. package/dist/plan/structural.js +180 -33
  251. package/dist/plan/structural.js.map +1 -1
  252. package/dist/repomap/cache.d.ts +48 -0
  253. package/dist/repomap/cache.d.ts.map +1 -1
  254. package/dist/repomap/cache.js +112 -13
  255. package/dist/repomap/cache.js.map +1 -1
  256. package/dist/repomap/io.d.ts +24 -0
  257. package/dist/repomap/io.d.ts.map +1 -0
  258. package/dist/repomap/io.js +34 -0
  259. package/dist/repomap/io.js.map +1 -0
  260. package/dist/repomap/map.d.ts +69 -6
  261. package/dist/repomap/map.d.ts.map +1 -1
  262. package/dist/repomap/map.js +104 -22
  263. package/dist/repomap/map.js.map +1 -1
  264. package/dist/repomap/rank.d.ts +26 -2
  265. package/dist/repomap/rank.d.ts.map +1 -1
  266. package/dist/repomap/rank.js +20 -0
  267. package/dist/repomap/rank.js.map +1 -1
  268. package/dist/repomap/reader.d.ts +73 -0
  269. package/dist/repomap/reader.d.ts.map +1 -0
  270. package/dist/repomap/reader.js +30 -0
  271. package/dist/repomap/reader.js.map +1 -0
  272. package/dist/repomap/tags.d.ts.map +1 -1
  273. package/dist/repomap/tags.js.map +1 -1
  274. package/dist/retrieve.d.ts +8 -0
  275. package/dist/retrieve.d.ts.map +1 -1
  276. package/dist/retrieve.js +12 -1
  277. package/dist/retrieve.js.map +1 -1
  278. package/dist/smelter.d.ts +79 -0
  279. package/dist/smelter.d.ts.map +1 -0
  280. package/dist/smelter.js +65 -0
  281. package/dist/smelter.js.map +1 -0
  282. package/dist/stages.d.ts.map +1 -1
  283. package/dist/stages.js +2 -2
  284. package/dist/stages.js.map +1 -1
  285. package/dist/stats.d.ts +2 -1
  286. package/dist/stats.d.ts.map +1 -1
  287. package/dist/stats.js +2 -1
  288. package/dist/stats.js.map +1 -1
  289. package/dist/store-dir.d.ts +43 -4
  290. package/dist/store-dir.d.ts.map +1 -1
  291. package/dist/store-dir.js +48 -5
  292. package/dist/store-dir.js.map +1 -1
  293. package/dist/store.d.ts +6 -0
  294. package/dist/store.d.ts.map +1 -1
  295. package/dist/store.js +6 -0
  296. package/dist/store.js.map +1 -1
  297. package/dist/types.d.ts +35 -3
  298. package/dist/types.d.ts.map +1 -1
  299. package/dist/types.js.map +1 -1
  300. package/grammars/tree-sitter-bash.wasm +0 -0
  301. package/grammars/tree-sitter-c.wasm +0 -0
  302. package/grammars/tree-sitter-c_sharp.wasm +0 -0
  303. package/grammars/tree-sitter-cpp.wasm +0 -0
  304. package/grammars/tree-sitter-go.wasm +0 -0
  305. package/grammars/tree-sitter-java.wasm +0 -0
  306. package/grammars/tree-sitter-javascript.wasm +0 -0
  307. package/grammars/tree-sitter-kotlin.wasm +0 -0
  308. package/grammars/tree-sitter-php.wasm +0 -0
  309. package/grammars/tree-sitter-python.wasm +0 -0
  310. package/grammars/tree-sitter-ruby.wasm +0 -0
  311. package/grammars/tree-sitter-rust.wasm +0 -0
  312. package/grammars/tree-sitter-swift.wasm +0 -0
  313. package/grammars/tree-sitter-tsx.wasm +0 -0
  314. package/grammars/tree-sitter-typescript.wasm +0 -0
  315. package/package.json +22 -14
  316. package/dist/cli/resolve.d.ts +0 -103
  317. package/dist/cli/resolve.d.ts.map +0 -1
  318. package/dist/cli/resolve.js +0 -106
  319. 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