@hasna/hooks 0.4.1 → 0.6.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 (35) hide show
  1. package/README.md +96 -12
  2. package/bin/index.js +2089 -470
  3. package/bin/serve.js +5248 -0
  4. package/dist/cf/provision.d.ts +24 -0
  5. package/dist/config.d.ts +18 -0
  6. package/dist/db/legacy-import.d.ts +1 -1
  7. package/dist/db/migrations/004_hooks_table.d.ts +9 -0
  8. package/dist/db/pg-migrations.d.ts +1 -1
  9. package/dist/db/storage-sync.d.ts +26 -6
  10. package/dist/index.d.ts +18 -2
  11. package/dist/index.js +5341 -286
  12. package/dist/lib/custom-install.d.ts +19 -0
  13. package/dist/lib/manifest.d.ts +70 -0
  14. package/dist/lib/resolve.d.ts +19 -0
  15. package/dist/lib/run.d.ts +37 -0
  16. package/dist/lib/store.d.ts +69 -0
  17. package/dist/lib/sync.d.ts +35 -0
  18. package/dist/serve.d.ts +36 -0
  19. package/dist/storage.d.ts +2 -2
  20. package/dist/storage.js +133 -42
  21. package/hooks/codewith-native-common.test.ts +1521 -3
  22. package/hooks/codewith-native-common.ts +1627 -37
  23. package/hooks/hook-scanoutput/README.md +151 -0
  24. package/hooks/hook-scanoutput/package.json +12 -0
  25. package/hooks/hook-scanoutput/src/hook.test.ts +217 -0
  26. package/hooks/hook-scanoutput/src/hook.ts +319 -0
  27. package/hooks/mention-context/README.md +109 -0
  28. package/hooks/mention-context/package.json +9 -0
  29. package/hooks/mention-context/src/hasna-mention-context.py +1218 -0
  30. package/hooks/mention-context/src/hasna-mention-warm.py +521 -0
  31. package/hooks/mention-context/src/hook.test.ts +68 -0
  32. package/hooks/mention-context/src/test_run_capture.py +345 -0
  33. package/hooks/pre-bash/README.md +72 -2
  34. package/hooks/worktree-guard/README.md +9 -2
  35. package/package.json +9 -5
package/README.md CHANGED
@@ -67,10 +67,21 @@ hooks install session-start prompt-guard pre-bash worktree-guard stop-sync knowl
67
67
  ```
68
68
 
69
69
  The scoped destructive-operation guard does not block every cleanup command. It
70
- blocks resolved shell/file-tool targets that threaten `~/.hasna`, configured
71
- workspace roots, Hasna division/scope roots, or active repo/worktree roots,
72
- including recursive `rm`, `rsync --delete`, destructive `find`, and destructive
73
- `git clean` / `git reset --hard` forms.
70
+ blocks resolved shell/file-tool targets that threaten `/` or a system root
71
+ (`/usr`, `/etc`, `/bin`, `/lib`, `/var`, `/boot`, `/home`, `/Users`, and the
72
+ other FHS and macOS equivalents), `~/.hasna`, configured workspace roots, Hasna
73
+ division/scope roots, or active repo/worktree roots, including recursive `rm`,
74
+ `rsync --delete`, destructive `find`, and destructive `git clean` / `git reset
75
+ --hard` forms.
76
+
77
+ It also blocks by *shape*: a destructive target containing a command
78
+ substitution or variable expansion immediately followed by `/` is checked as the
79
+ shell would render it if that expansion returned empty, so
80
+ `rm -rf "$(anything)"/*` and `rm -rf "$VAR"/*` are refused whatever the
81
+ expansion is. Wrapped forms (`bash -c`, `su -c`, `eval`, `ssh host '…'`) are
82
+ unwrapped first. See [`hooks/pre-bash/README.md`](hooks/pre-bash/README.md) for
83
+ the full rules, the deliberate exemptions (`${VAR:?}`, bare `"$(cmd)"` with no
84
+ trailing separator), and the recommended safe form.
74
85
 
75
86
  Apply that fragment through `open-configs` or the managed config renderer. A
76
87
  direct write path exists only for explicit local/test use:
@@ -79,14 +90,67 @@ direct write path exists only for explicit local/test use:
79
90
  hooks install knowledge-context --target codewith --apply-codewith --codewith-config /tmp/codewith-config.toml
80
91
  ```
81
92
 
93
+ ## Custom and remote hooks
94
+
95
+ A hook is defined by a manifest — `{ name, version, description, events, script, args?, timeout_ms? }` — where `script` is a relative path or inline content. Hooks come from three sources: the bundled registry, a user custom directory, or a remote registry.
96
+
97
+ **Install custom hooks** from a local directory, a git URL, or a manifest URL:
98
+
99
+ ```bash
100
+ hooks install ./my-hook # directory with manifest.json
101
+ hooks install git@github.com:org/hook-repo.git
102
+ hooks install https://example.com/hooks/my-hook/manifest.json
103
+ ```
104
+
105
+ Custom hooks land in `~/.hasna/hooks/hooks/<name>/`. A custom hook with the same name as a bundled hook takes precedence (visible in `hooks info <name>`).
106
+
107
+ **Trust model.** Every hook script is pinned by sha256 in `~/.hasna/hooks/hooks.lock` and the SQLite `hooks` table. `hooks run` verifies the script hash before executing; if the script changed, the run is refused:
108
+
109
+ ```bash
110
+ hooks trust <name> # re-pin the current script content
111
+ hooks update # re-register hooks and refresh pins
112
+ ```
113
+
114
+ **Registry server.** `hooks serve` exposes the local store over HTTP — catalog, artifacts, and the published lock:
115
+
116
+ ```bash
117
+ hooks serve --port 39428 --api-key "$HASNA_HOOKS_API_KEY"
118
+ # GET /health, GET /api/v1/catalog, GET /api/v1/hooks/:name/:version,
119
+ # PUT /api/v1/hooks (publish, requires the key), GET /api/v1/lock
120
+ ```
121
+
122
+ **Cloudflare registry (opt-in).** Presence of an API URL selects the remote registry; absence means local. There is no mode concept.
123
+
124
+ ```bash
125
+ hooks init --cloudflare --api-url https://registry.example.com --api-key <vault-key-name>
126
+ hooks sync # fetch catalog + lock from the API, verify sha256, update the local store
127
+ hooks sync --dry-run # print the plan without changing anything
128
+ ```
129
+
130
+ `hooks init --cloudflare` stores the API URL and a vault key NAME in `~/.hasna/hooks/config.json` — never the key value. Serve with the key resolved from the vault:
131
+
132
+ ```bash
133
+ secrets exec <vault-key-name> --as HASNA_HOOKS_API_KEY -- hooks serve
134
+ ```
135
+
136
+ **Cloudflare provisioning.** `hooks cf deploy` creates the D1 database and R2 bucket via the Cloudflare API, then prints the exact wrangler commands for the worker upload (the worker needs the workerd target, which only wrangler can bundle):
137
+
138
+ ```bash
139
+ export CF_API_TOKEN=... # resolve from the vault, never paste the value
140
+ hooks cf deploy --account-id <id> --dry-run # plan first
141
+ hooks cf deploy --account-id <id>
142
+ ```
143
+
144
+ The worker (`src/cf/worker.ts`) implements the same API routes against D1 + R2, with artifacts at `hook_artifacts/<name>/<version>.json`. See `src/cf/wrangler.toml.example`.
145
+
82
146
  ## Storage
83
147
 
84
148
  Hooks stores data locally by default in `~/.hasna/hooks/` and uses SQLite
85
149
  directly for hook event history. The package owns its database schema and
86
150
  migrations; it does not depend on the deprecated shared runtime or its CLI.
87
- The repo includes its own PostgreSQL migration definitions for optional remote
88
- storage deployments. Use the `hooks log` commands to inspect local hook event
89
- data.
151
+ The repo includes its own PostgreSQL migration definitions for the optional
152
+ `hooks storage push|pull|sync` commands. Use the `hooks log` commands to inspect
153
+ local hook event data.
90
154
 
91
155
  ```bash
92
156
  hooks storage status --json
@@ -96,14 +160,34 @@ hooks storage sync --json
96
160
  ```
97
161
 
98
162
  Configure database storage with `HASNA_HOOKS_DATABASE_URL` or fallback
99
- `HOOKS_DATABASE_URL`. Optional storage mode env vars are
100
- `HASNA_HOOKS_STORAGE_MODE` and `HOOKS_STORAGE_MODE`, with `local`, `hybrid`, or
101
- `remote` values.
163
+ `HOOKS_DATABASE_URL`.
164
+
165
+ ### Storage backend
166
+
167
+ Hooks storage has one setting with two values: **which data backend**, not where
168
+ anything is deployed.
169
+
170
+ | `HASNA_HOOKS_STORAGE_BACKEND` (fallback `HOOKS_STORAGE_BACKEND`) | meaning |
171
+ | --- | --- |
172
+ | `sqlite` | the on-box SQLite file in `~/.hasna/hooks/` (default) |
173
+ | `postgresql` | the PostgreSQL database named by `HASNA_HOOKS_DATABASE_URL` |
174
+
175
+ Leave it unset and the backend is inferred exactly as before: `postgresql` when a
176
+ database URL is configured, `sqlite` otherwise. An unrecognised value is an
177
+ error, not a silent fall back to SQLite.
178
+
179
+ The former deployment-mode variables `HASNA_HOOKS_STORAGE_MODE` and
180
+ `HOOKS_STORAGE_MODE`, and their `local` / `hybrid` / `remote` / `self-hosted` /
181
+ `cloud` values, are **retired**. They are not read; setting one raises an error
182
+ naming the replacement variable and the backend to use (`local` became `sqlite`,
183
+ everything else became `postgresql`). Deployment location was never a property of
184
+ the data layer, so it is no longer expressed as one.
102
185
 
103
186
  ## Runtime model
104
187
 
105
- This package is an npm/local CLI, MCP server, and static dashboard package. It
106
- does not require a deployed cloud or self-hosted runtime to install or run hooks.
188
+ This package is an npm CLI, MCP server, and static dashboard package. Installing
189
+ and running hooks needs nothing deployed anywhere — the SQLite backend is the
190
+ default and requires no server.
107
191
 
108
192
  ## Data Directory
109
193