@scopebond/hook 0.6.0 → 0.8.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 (50) hide show
  1. package/README.md +123 -13
  2. package/dist/cli.d.ts +2 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +825 -92
  5. package/dist/cli.js.map +1 -1
  6. package/dist/cloud.d.ts +2 -0
  7. package/dist/cloud.d.ts.map +1 -1
  8. package/dist/cloud.js +1 -1
  9. package/dist/cloud.js.map +1 -1
  10. package/dist/explain.d.ts +44 -0
  11. package/dist/explain.d.ts.map +1 -0
  12. package/dist/explain.js +75 -0
  13. package/dist/explain.js.map +1 -0
  14. package/dist/index.d.ts +11 -4
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/init.d.ts +41 -4
  19. package/dist/init.d.ts.map +1 -1
  20. package/dist/init.js +104 -21
  21. package/dist/init.js.map +1 -1
  22. package/dist/install.d.ts +70 -2
  23. package/dist/install.d.ts.map +1 -1
  24. package/dist/install.js +262 -10
  25. package/dist/install.js.map +1 -1
  26. package/dist/map.d.ts +6 -0
  27. package/dist/map.d.ts.map +1 -1
  28. package/dist/map.js +4 -1
  29. package/dist/map.js.map +1 -1
  30. package/dist/minimize.d.ts +11 -3
  31. package/dist/minimize.d.ts.map +1 -1
  32. package/dist/minimize.js +35 -6
  33. package/dist/minimize.js.map +1 -1
  34. package/dist/rules.d.ts +51 -0
  35. package/dist/rules.d.ts.map +1 -0
  36. package/dist/rules.js +216 -0
  37. package/dist/rules.js.map +1 -0
  38. package/dist/runtime-install.d.ts +25 -0
  39. package/dist/runtime-install.d.ts.map +1 -0
  40. package/dist/runtime-install.js +106 -0
  41. package/dist/runtime-install.js.map +1 -0
  42. package/dist/runtime.d.ts +13 -0
  43. package/dist/runtime.d.ts.map +1 -1
  44. package/dist/runtime.js +52 -7
  45. package/dist/runtime.js.map +1 -1
  46. package/dist/version.d.ts +5 -1
  47. package/dist/version.d.ts.map +1 -1
  48. package/dist/version.js +6 -2
  49. package/dist/version.js.map +1 -1
  50. package/package.json +2 -2
package/README.md CHANGED
@@ -55,16 +55,99 @@ npx @scopebond/hook init --codex # Codex, then approve it once with /hooks
55
55
 
56
56
  `init` scaffolds `.scopebond/` in the current project (a machine signing key, a
57
57
  countersigning key, a starter policy — "protect main and production paths" — and a
58
- `.gitignore` so none of it is committed) and configures `.claude/settings.json`,
59
- `.cursor/hooks.json`, or `.codex/hooks.json`. Then run one safe command in the agent and see the receipt in
60
- `.scopebond/receipts.db`. `init`, `trust` and `uninstall` are meant for a person at a
61
- terminal: in a script or CI, pass `--yes`.
58
+ `.gitignore` so none of it is committed) and wires your agent to the hook. Then run one
59
+ safe command in the agent and see the receipt in `.scopebond/receipts.db`. `init`, `trust`
60
+ and `uninstall` are meant for a person at a terminal: in a script or CI, pass `--yes`.
61
+ `npx @scopebond/hook init --dry-run` shows what it would write, and changes nothing.
62
+
63
+ **Where the hook goes, and why.** `init` pins a copy of the hook on this machine so it
64
+ starts fast on every tool call. That command names paths that exist only here, so it
65
+ never goes into a file your team shares: for Claude Code it goes into
66
+ `.claude/settings.local.json`, which `init` keeps out of git for this clone; for Cursor
67
+ and Codex it goes into `.cursor/hooks.json` or `.codex/hooks.json` only while git does not
68
+ already track that file, and a tracked file gets the portable command instead. A hook
69
+ command that cannot start is treated by the agent as a non-blocking error — the agent
70
+ carries on with no check — so a machine-specific path in a committed file would leave
71
+ every teammate unprotected while the file says otherwise. `doctor` reports that case, and
72
+ running `init` again moves an entry an older version wrote into `.claude/settings.json`.
73
+
74
+ To give everyone who clones the project the hook, use `init --shared`: it writes the
75
+ portable command (`npx -y @scopebond/hook@<version> claude`) to `.claude/settings.json`,
76
+ `.cursor/hooks.json` or `.codex/hooks.json`. It starts on any machine, more slowly, and
77
+ until a teammate runs `init` themselves it blocks their agent's actions with a message
78
+ saying how to set it up.
79
+
80
+ Check what it did with `npx @scopebond/hook status` (which agents are configured, in
81
+ which scope) and `npx @scopebond/hook doctor` (whether each configured command can
82
+ actually start).
83
+
84
+ ### Changing the rules
85
+
86
+ `.scopebond/policy.json` is **generated**. The thing you edit is `.scopebond/rules.json`,
87
+ a short readable list, and `policy.json` is compiled from it — so a limit is a line in a
88
+ list, not a 700-character lookahead:
89
+
90
+ ```
91
+ npx @scopebond/hook rules # what is blocked, in plain English
92
+ npx @scopebond/hook rules allow dd # stop blocking a program
93
+ npx @scopebond/hook rules protect infra/ # never write there
94
+ npx @scopebond/hook rules protect-branch production
95
+ npx @scopebond/hook rules apply # recompile after editing rules.json by hand
96
+ ```
97
+
98
+ The compiled patterns are identical to the ones this package has always shipped — there
99
+ is a test that pins them against the starter policy — so the readable front end cannot
100
+ change what is enforced. Each clause description is generated from the list too, so it
101
+ stays true after an edit, and a block message quotes it.
102
+
103
+ ### The hook command `init` installs
104
+
105
+ The hook runs once per tool call, so the command has to start fast. `init` copies this
106
+ package into `~/.scopebond/runtime/<version>/` once per machine and points your agent
107
+ at that absolute path. Measured on one Windows machine, through a shell, warm cache:
108
+
109
+ | Command in the agent config | Median per tool call |
110
+ |---|---|
111
+ | pinned absolute path (the default) | **151 ms** |
112
+ | `npx -y @scopebond/hook@<version>` | 1053 ms |
113
+
114
+ That ~900 ms is npm re-resolving a package already on disk, on every action. Pass
115
+ `--npx` to `init` if you would rather have the portable command, and `init` falls back
116
+ to it automatically when it cannot make a durable copy — slow beats broken. `doctor`
117
+ re-checks that the pinned paths still resolve, so a cleared home or a switched Node
118
+ version shows up as a problem rather than as a hook that silently cannot start.
119
+
120
+ ### What each agent can actually stop
121
+
122
+ | | Claude Code | Cursor | Codex |
123
+ |---|---|---|---|
124
+ | Shell commands | prevented | prevented | prevented |
125
+ | File reads | prevented | prevented | prevented |
126
+ | MCP tool calls | prevented | prevented | prevented |
127
+ | File writes / edits | prevented | **recorded, not prevented** | prevented |
128
+
129
+ Cursor reports a file edit only *after* it is written (`afterFileEdit`; it has no
130
+ before-edit hook), so an out-of-policy edit there is signed and flagged, not blocked —
131
+ and the message says so rather than claiming otherwise. For edits that must be stopped
132
+ before they land, make `@scopebond/github-action` a required check on pull requests.
62
133
 
63
134
  ## Connect it to your workspace (optional)
64
135
 
65
- To see the receipts in your hosted Scopebond workspace, create a connection from
66
- the portal's **Connect** step (it gives you a one-use enrollment bundle), save it
67
- as `scopebond-enrollment.json`, then:
136
+ To see the receipts in your hosted Scopebond workspace, sign this computer in:
137
+
138
+ ```
139
+ npx @scopebond/hook login https://<your-workspace>
140
+ ```
141
+
142
+ It prints a short code and a link. Someone who manages the workspace opens the link,
143
+ checks that the code matches, and approves it for an environment and agent. The
144
+ command then finishes connecting on its own: nothing is copied or pasted, and the
145
+ code expires after 10 minutes if nobody approves it. Add `--cursor` or `--codex`
146
+ for those agents, or `--no-install` to leave the agent's settings alone.
147
+
148
+ If your workspace does not offer sign-in codes, create a connection from the
149
+ portal's **Connect** step (it gives you a one-use enrollment bundle), save it as
150
+ `scopebond-enrollment.json`, then:
68
151
 
69
152
  ```
70
153
  npx @scopebond/hook connect https://<your-workspace> scopebond-enrollment.json
@@ -82,8 +165,8 @@ can hand you a single copy-paste command with nothing to save.
82
165
 
83
166
  From then on every receipt is mirrored to the workspace through a **durable outbox**:
84
167
  delivery is best-effort and never blocks a tool call, and receipts are retained
85
- locally and retried if the workspace is unreachable. `scopebond-hook flush` delivers
86
- anything still queued — run it on a session-end hook (and set
168
+ locally and retried if the workspace is unreachable. `npx @scopebond/hook flush`
169
+ delivers anything still queued — run it on a session-end hook (and set
87
170
  `SCOPEBOND_HOOK_FLUSH_MS=0`) if you want zero per-call latency.
88
171
 
89
172
  ## How it works
@@ -91,14 +174,41 @@ anything still queued — run it on a session-end hook (and set
91
174
  Each tool call is mapped to a normalized [Action Taxonomy](https://github.com/avouro-com/scopebond)
92
175
  action (`shell.exec`, `git.push`, `file.write`, `mcp.tool.call`, …), signed by the
93
176
  machine key and decided against your policy by an in-process check-only gateway.
94
- An allowed action is recorded and left to the coding agent's normal permission
95
- prompt; the hook never auto-approves it. A denied action is blocked. Unknown tools are
96
- recorded as *not evaluated* and grant nothing. Anything unexpected fails closed
97
- (deny) with a repair message.
177
+ An allowed action is recorded and the agent proceeds. A denied action is blocked.
178
+ Unknown tools are recorded as *not evaluated* and grant nothing — they fall through to
179
+ the coding agent's own permission prompt, so the hook never turns an unrecognised
180
+ action into an approval. Anything unexpected fails closed (deny) with a repair message.
181
+
182
+ **What a block says.** A denial names the action, the rule that decided, why that rule
183
+ exists (the clause's own description), the engine's technical detail and where to change
184
+ it. The same text goes to the agent, so it can choose another approach instead of
185
+ retrying a blocked call:
186
+
187
+ ```
188
+ Scopebond blocked git.push origin main — rule "protect-branches" (enforce).
189
+ Why: Deny pushes to main, master and release/* (any case, any refspec spelling), …
190
+ Detail: param ref fails pattern
191
+ Change the rule: edit clause "protect-branches" in /repo/.scopebond/policy.json
192
+ ```
98
193
 
99
194
  Commands are stored as a scrubbed head plus a digest; file contents are never
100
195
  stored; common secret shapes are removed before signing.
101
196
 
197
+ **Where receipts live, and how much room they take.** `.scopebond/receipts.db` in the
198
+ project, roughly 25 KiB per tool call. Nothing is ever deleted automatically — these are
199
+ your evidence — so `status` reports the count and size, and `prune` bounds it when you
200
+ choose to:
201
+
202
+ ```
203
+ npx @scopebond/hook prune # report the footprint
204
+ npx @scopebond/hook prune --before 90d --yes # archive, then remove, anything older
205
+ ```
206
+
207
+ `prune` writes the receipts it will remove to a JSONL file beside the database first, so
208
+ they stay verifiable, and it refuses outright once the log has been anchored — a receipt's
209
+ position is its anchor leaf index, so removing one would make an existing anchor
210
+ unverifiable.
211
+
102
212
  **What a shell command is checked for.** A command line is split into every command it
103
213
  runs (`&&`, `;`, pipes, `$( )` and backticks — also inside double quotes — `bash -c`,
104
214
  `eval`, `trap`, `su -c`, `script -c`, `watch`, `parallel`, `cmd /c`, `pwsh -Command`
package/dist/cli.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ /** A `--since` value: a duration (`7d`, `24h`, `30m`) or an ISO-ish date. */
3
+ export declare function parseSince(value: string | undefined, now?: number): number | null;
3
4
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAojBA,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,GAAG,IAAI,CAS7F"}