hookgate 0.0.2

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.
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "hookgate",
3
+ "owner": {
4
+ "name": "Allan Nava",
5
+ "url": "https://github.com/Allan-Nava"
6
+ },
7
+ "metadata": {
8
+ "description": "Calibrated decision gates for Claude Code hooks.",
9
+ "version": "0.0.2"
10
+ },
11
+ "plugins": [
12
+ {
13
+ "name": "hookgate",
14
+ "source": "./",
15
+ "description": "PreToolUse and Stop gates decided by TypeSafe's Jev with calibrated confidence — allow, ask or deny in about 100 ms, fail-open when the API is unreachable."
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "hookgate",
3
+ "version": "0.0.2",
4
+ "description": "Calibrated, sub-second decisions in Claude Code's hooks: a PreToolUse gate on shell commands and a Stop gate on unverified completion, answered by TypeSafe's Jev with a confidence score, escalating to the human when unsure.",
5
+ "author": {
6
+ "name": "Allan Nava",
7
+ "url": "https://github.com/Allan-Nava"
8
+ },
9
+ "homepage": "https://github.com/Allan-Nava/hookgate",
10
+ "repository": "https://github.com/Allan-Nava/hookgate",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "hooks",
14
+ "guardrails",
15
+ "typesafe",
16
+ "system-one",
17
+ "calibrated-confidence",
18
+ "agent-safety"
19
+ ]
20
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Allan Nava
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/Allan-Nava/hookgate/main/assets/logo.svg" width="72" height="72" alt="hookgate">
3
+ </p>
4
+ <p align="center">
5
+ <a href="https://allan-nava.github.io/hookgate/"><img src="https://img.shields.io/badge/docs-allan--nava.github.io%2Fhookgate-2f5d8a?labelColor=1b1a18" alt="Documentation"></a>
6
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2f5d8a?labelColor=1b1a18" alt="MIT licence"></a>
7
+ </p>
8
+
9
+ # hookgate
10
+
11
+ **Calibrated, sub-second decisions inside Claude Code's hooks.** A `PreToolUse` gate
12
+ on shell commands and a `Stop` gate on unverified claims of completion, answered by
13
+ [TypeSafe's Jev](https://typesafe.ai/) — a model that returns typed decisions with a
14
+ confidence score instead of text — in about 100 ms. Confident: act. Unsure: ask the
15
+ human. Unreachable: get out of the way.
16
+
17
+ > **Status: scaffold.** The plugin installs and does nothing yet. The two gates are
18
+ > being designed in `thoughts/HG-1-jev-gates/` through the
19
+ > [QRSPI](https://github.com/Allan-Nava/qrspi) workflow; the first release ships them
20
+ > with the benchmark below filled in. Until then every handler falls through.
21
+
22
+ ## Why in the hooks
23
+
24
+ A coding agent's harness takes hundreds of small decisions per session: is this
25
+ `rm -rf` aimed at the repo or at `~`, does "done, all tests pass" match `git status`,
26
+ should this be allowed, asked about or refused. Today those are taken by a static
27
+ allowlist, by a full LLM call (`type: prompt` hooks — seconds and cents each), or by
28
+ nobody. A System One model is built for exactly this shape: unstructured state in,
29
+ a typed answer plus a calibrated probability out, no text to parse, no hallucination
30
+ to catch. TypeSafe's own [plugin](https://github.com/typesafe-ai/skills) and
31
+ [cookbooks](https://docs.typesafe.ai/cookbooks.md) show how to build *applications*
32
+ on Jev. Nobody has put it inside the agent harness. That is the gap this fills.
33
+
34
+ ## What ships
35
+
36
+ | Gate | Hook | Question to Jev | Effect |
37
+ |---|---|---|---|
38
+ | Command risk | `PreToolUse` on `Bash` | `Choice{allow, ask, deny}` + `Noul` "destroys data or state outside the repo?" | `permissionDecision` with a reason; below the confidence threshold it is always `ask`, never `allow` |
39
+ | Unverified completion | `Stop` | `Noul` "does the last message claim a completion the visible state does not support?" on the message plus `git status` | `block` with the reason, so the agent verifies before stopping |
40
+
41
+ Thresholds live in `.claude/hookgate.json` and scale with risk, as
42
+ [TypeSafe's confidence guide](https://docs.typesafe.ai/confidence.md) recommends.
43
+
44
+ **Fail-open, always.** No `TYPESAFE_API_KEY`, no network, a timeout, a 5xx or a bug
45
+ in this plugin means *no decision*: exit 0, empty stdout, and Claude Code's normal
46
+ permission flow applies as if hookgate were not installed. A gate that stalls the
47
+ agent is worse than none. Fail-closed will be an explicit opt-in.
48
+
49
+ **State never carries secrets.** Commands can contain tokens; anything that looks
50
+ like one is redacted before it leaves the machine, and state is truncated to Jev's
51
+ 32k-token limit.
52
+
53
+ ## Install
54
+
55
+ Not published yet. Once it is:
56
+
57
+ ```
58
+ /plugin marketplace add Allan-Nava/hookgate
59
+ /plugin install hookgate@hookgate
60
+ ```
61
+
62
+ and `TYPESAFE_API_KEY` in the environment Claude Code runs in. Zero dependencies,
63
+ Node 18 or later, one `fetch` to `POST https://api.typesafe.ai/v1/systemone`.
64
+
65
+ ## Benchmark
66
+
67
+ The first release carries, in `evals/`, at least fifty shell commands labelled by
68
+ hand as safe, ask or dangerous, run against both gates and against a `type: prompt`
69
+ hook on `claude-opus-5`, and reports agreement with the labels, p50 and p95 latency,
70
+ cost per decision and the share of `ask` per confidence threshold. One run, one
71
+ model version, dated — a data point, not a benchmark suite. If agreement stays under
72
+ about 90%, the command gate ships `ask`-only.
73
+
74
+ ## Two design notes
75
+
76
+ **Why not teach Jev?** TypeSafe already does, well: official Python and JavaScript
77
+ SDKs, an MIT plugin with the patterns, thirteen cookbooks. Duplicating that would be
78
+ noise. hookgate has one job the harness can feel.
79
+
80
+ **Why zero dependencies?** A hook starts on every tool call. Start-up cost *is* the
81
+ cost, so there is nothing to install, nothing to resolve, one file to read.
82
+
83
+ ## Prior art
84
+
85
+ - [typesafe-ai/skills](https://github.com/typesafe-ai/skills) — the official agent
86
+ skill: API, primitives, patterns. Read it to build with Jev.
87
+ - [Claude Code hooks](https://code.claude.com/docs/en/hooks) — `type: prompt` hooks
88
+ are the incumbent for judged decisions; this is the faster, calibrated alternative.
89
+ - [qrspi](https://github.com/Allan-Nava/qrspi) — the sibling project this one is
90
+ modelled on: a Markdown-first plugin, a dependency-free installer, a site generated
91
+ from the README, releases by tag.
92
+
93
+ ## License
94
+
95
+ MIT.
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ // hookgate — calibrated decision gates for Claude Code hooks.
3
+ //
4
+ // hookgate check validate the manifests, hooks.json and this file
5
+ // hookgate pre-tool-use PreToolUse handler (reads the hook JSON on stdin)
6
+ // hookgate stop Stop handler (reads the hook JSON on stdin)
7
+ // hookgate help
8
+ //
9
+ // Every handler FAILS OPEN. Exit 0 with no JSON on stdout means "no decision":
10
+ // Claude Code's ordinary permission flow applies, exactly as if the plugin were not
11
+ // installed. A missing TYPESAFE_API_KEY, a timeout, a 5xx or a bug in this file must
12
+ // never block the user's work. Fail-closed is an explicit opt-in for later.
13
+ //
14
+ // Zero dependencies, Node 18+: a hook starts on every tool call, so start-up cost
15
+ // is the cost. The API is one POST with fetch, which Node 18 has.
16
+
17
+ import { existsSync, readFileSync } from 'node:fs'
18
+ import { dirname, join, resolve } from 'node:path'
19
+ import { fileURLToPath } from 'node:url'
20
+
21
+ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
22
+ const read = (p) => readFileSync(join(ROOT, p), 'utf8')
23
+ const json = (p) => JSON.parse(read(p))
24
+
25
+ const [cmd = 'help'] = process.argv.slice(2)
26
+
27
+ // --- hook handlers ----------------------------------------------------------
28
+ //
29
+ // Not implemented yet: the gates are being designed under thoughts/HG-1-jev-gates/
30
+ // through the QRSPI workflow. Until then both handlers fall through — the plugin is
31
+ // installable and inert, which is what fail-open means.
32
+
33
+ async function readStdin() {
34
+ let s = ''
35
+ for await (const chunk of process.stdin) s += chunk
36
+ try {
37
+ return JSON.parse(s)
38
+ } catch {
39
+ return null
40
+ }
41
+ }
42
+
43
+ async function handler(name) {
44
+ const input = await readStdin()
45
+ if (!input || !process.env.TYPESAFE_API_KEY) process.exit(0)
46
+ process.stderr.write(`hookgate: ${name} gate not implemented yet — falling through\n`)
47
+ process.exit(0)
48
+ }
49
+
50
+ // --- check ------------------------------------------------------------------
51
+
52
+ const HOOK_EVENTS = new Set(['PreToolUse', 'PostToolUse', 'Stop', 'SubagentStop', 'UserPromptSubmit', 'PermissionRequest'])
53
+
54
+ function check() {
55
+ const errors = []
56
+ const fail = (m) => errors.push(m)
57
+
58
+ const pkg = json('package.json')
59
+ const plugin = json('.claude-plugin/plugin.json')
60
+ const market = json('.claude-plugin/marketplace.json')
61
+
62
+ const versions = { 'package.json': pkg.version, 'plugin.json': plugin.version, 'marketplace.json': market.metadata?.version }
63
+ if (new Set(Object.values(versions)).size !== 1) fail(`versions differ: ${JSON.stringify(versions)}`)
64
+ if (pkg.name !== 'hookgate' || plugin.name !== 'hookgate') fail('package.json and plugin.json must both be named hookgate')
65
+ if (!(market.plugins ?? []).some((p) => p.name === 'hookgate' && p.source === './')) fail('marketplace.json must list the hookgate plugin with source "./"')
66
+ if (!/github\.com[/:]Allan-Nava\/hookgate/.test(pkg.repository?.url ?? '')) fail('package.json#repository must name the GitHub repo')
67
+ if (pkg.dependencies && Object.keys(pkg.dependencies).length) fail('no runtime dependencies — a hook runs on every tool call')
68
+ for (const f of ['bin', 'hooks', '.claude-plugin', 'README.md', 'LICENSE']) if (!pkg.files?.includes(f)) fail(`package.json#files is missing ${f}`)
69
+
70
+ const hooks = json('hooks/hooks.json')
71
+ for (const [event, entries] of Object.entries(hooks.hooks ?? {})) {
72
+ if (!HOOK_EVENTS.has(event)) fail(`hooks.json: unknown event ${event}`)
73
+ for (const entry of entries) {
74
+ for (const h of entry.hooks ?? []) {
75
+ if (h.type !== 'command') fail(`${event}: only command hooks are used here (got ${h.type})`)
76
+ if (h.command !== '${CLAUDE_PLUGIN_ROOT}/bin/hookgate.mjs') fail(`${event}: command must be \${CLAUDE_PLUGIN_ROOT}/bin/hookgate.mjs, got ${h.command}`)
77
+ if (!Array.isArray(h.args) || !['pre-tool-use', 'stop'].includes(h.args[0])) fail(`${event}: args must name a handler this file implements`)
78
+ if (typeof h.timeout !== 'number' || h.timeout > 10) fail(`${event}: timeout must be set and at most 10 s — a gate that stalls the agent is worse than none`)
79
+ }
80
+ }
81
+ }
82
+ if (!hooks.hooks?.PreToolUse?.some((e) => e.matcher === 'Bash')) fail('hooks.json: the PreToolUse gate must match Bash')
83
+
84
+ for (const f of ['README.md', 'CONTRIBUTING.md', 'CLAUDE.md', 'LICENSE']) if (!existsSync(join(ROOT, f))) fail(`${f} is missing`)
85
+ if (!/fail-open|fails open/i.test(read('README.md'))) fail('README.md must state the fail-open rule')
86
+
87
+ if (errors.length) {
88
+ for (const e of errors) console.error(`✗ ${e}`)
89
+ process.exit(1)
90
+ }
91
+ const n = Object.values(hooks.hooks).reduce((a, es) => a + es.reduce((b, e) => b + e.hooks.length, 0), 0)
92
+ console.log(`ok — ${n} hooks, manifests in sync at ${pkg.version}`)
93
+ }
94
+
95
+ function help() {
96
+ console.log(read('bin/hookgate.mjs').split('\n').slice(1, 6).map((l) => l.replace(/^\/\/ ?/, '')).join('\n'))
97
+ }
98
+
99
+ switch (cmd) {
100
+ case 'check':
101
+ check()
102
+ break
103
+ case 'pre-tool-use':
104
+ case 'stop':
105
+ await handler(cmd)
106
+ break
107
+ default:
108
+ help()
109
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "description": "hookgate — calibrated gates. Every handler fails open: no key, no network or an API error means no decision, and Claude Code's normal permission flow takes over.",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Bash",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "${CLAUDE_PLUGIN_ROOT}/bin/hookgate.mjs",
11
+ "args": ["pre-tool-use"],
12
+ "timeout": 5,
13
+ "statusMessage": "hookgate: judging the command"
14
+ }
15
+ ]
16
+ }
17
+ ],
18
+ "Stop": [
19
+ {
20
+ "hooks": [
21
+ {
22
+ "type": "command",
23
+ "command": "${CLAUDE_PLUGIN_ROOT}/bin/hookgate.mjs",
24
+ "args": ["stop"],
25
+ "timeout": 5,
26
+ "statusMessage": "hookgate: checking the claim of completion"
27
+ }
28
+ ]
29
+ }
30
+ ]
31
+ }
32
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "hookgate",
3
+ "version": "0.0.2",
4
+ "description": "Calibrated, sub-second decisions in Claude Code's hooks: a PreToolUse gate on shell commands and a Stop gate on unverified completion, answered by TypeSafe's Jev with a confidence score, escalating to the human when unsure.",
5
+ "type": "module",
6
+ "bin": {
7
+ "hookgate": "bin/hookgate.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "hooks",
12
+ ".claude-plugin",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "scripts": {
20
+ "build:site": "node site/build.mjs",
21
+ "test": "node bin/hookgate.mjs check",
22
+ "backlog": "node scripts/backlog.mjs check && node scripts/backlog_test.mjs"
23
+ },
24
+ "keywords": [
25
+ "claude",
26
+ "claude-code",
27
+ "claude-code-plugin",
28
+ "hooks",
29
+ "guardrails",
30
+ "typesafe",
31
+ "system-one",
32
+ "calibrated-confidence",
33
+ "agent-safety"
34
+ ],
35
+ "author": "Allan Nava (https://github.com/Allan-Nava)",
36
+ "license": "MIT",
37
+ "homepage": "https://github.com/Allan-Nava/hookgate",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/Allan-Nava/hookgate.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/Allan-Nava/hookgate/issues"
44
+ },
45
+ "devDependencies": {
46
+ "marked": "^18.0.0"
47
+ }
48
+ }