@hasna/hooks 0.8.0 → 0.9.1

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 (63) hide show
  1. package/README.md +33 -5
  2. package/bin/hooks-mcp.js +4862 -0
  3. package/bin/index.js +592 -170
  4. package/bin/serve.js +33 -10
  5. package/dist/db/index.d.ts +8 -0
  6. package/dist/index.d.ts +4 -1
  7. package/dist/index.js +2870 -2709
  8. package/dist/lib/db-writer.d.ts +30 -1
  9. package/dist/lib/installer.d.ts +40 -1
  10. package/dist/lib/local-opt-in.d.ts +26 -0
  11. package/dist/lib/registration.d.ts +75 -0
  12. package/dist/lib/registry.d.ts +14 -0
  13. package/dist/lib/sync.d.ts +17 -1
  14. package/dist/sdk/index.d.ts +103 -0
  15. package/dist/sdk/index.js +1050 -0
  16. package/dist/storage.js +15 -1
  17. package/hooks/hook-agent-rules-version-check/README.md +1 -1
  18. package/hooks/hook-trash-guard/README.md +147 -0
  19. package/hooks/hook-trash-guard/package.json +12 -0
  20. package/hooks/hook-trash-guard/src/hook.ts +1142 -0
  21. package/package.json +10 -3
  22. package/scripts/validate-package.ts +31 -4
  23. package/hooks/codewith-native-common.test.ts +0 -1935
  24. package/hooks/hook-affected-tests/tsconfig.json +0 -25
  25. package/hooks/hook-agent-rules-version-check/src/hook.test.ts +0 -104
  26. package/hooks/hook-agent-rules-version-check/tsconfig.json +0 -25
  27. package/hooks/hook-announce-start/tsconfig.json +0 -25
  28. package/hooks/hook-announce-stop/tsconfig.json +0 -25
  29. package/hooks/hook-autoformat/tsconfig.json +0 -25
  30. package/hooks/hook-branchprotect/tsconfig.json +0 -25
  31. package/hooks/hook-checkbugs/tsconfig.json +0 -15
  32. package/hooks/hook-checkdocs/tsconfig.json +0 -15
  33. package/hooks/hook-checkfiles/tsconfig.json +0 -15
  34. package/hooks/hook-checklint/tsconfig.json +0 -15
  35. package/hooks/hook-checkpoint/tsconfig.json +0 -25
  36. package/hooks/hook-checksecurity/tsconfig.json +0 -15
  37. package/hooks/hook-checktasks/tsconfig.json +0 -20
  38. package/hooks/hook-checktests/tsconfig.json +0 -15
  39. package/hooks/hook-conflict-detect/tsconfig.json +0 -25
  40. package/hooks/hook-contextrefresh/tsconfig.json +0 -25
  41. package/hooks/hook-desktopnotify/tsconfig.json +0 -25
  42. package/hooks/hook-dm-inject/tsconfig.json +0 -25
  43. package/hooks/hook-envsetup/tsconfig.json +0 -25
  44. package/hooks/hook-failure-to-task/tsconfig.json +0 -25
  45. package/hooks/hook-filelock/tsconfig.json +0 -25
  46. package/hooks/hook-fleet-blockers-gate/src/hook.test.ts +0 -302
  47. package/hooks/hook-fleet-blockers-gate/tsconfig.json +0 -25
  48. package/hooks/hook-fleet-catchup/src/hook.test.ts +0 -156
  49. package/hooks/hook-fleet-catchup/tsconfig.json +0 -25
  50. package/hooks/hook-gitguard/tsconfig.json +0 -25
  51. package/hooks/hook-knowledge-context/src/hook.test.ts +0 -379
  52. package/hooks/hook-packageage/tsconfig.json +0 -25
  53. package/hooks/hook-permissionguard/tsconfig.json +0 -25
  54. package/hooks/hook-phonenotify/tsconfig.json +0 -25
  55. package/hooks/hook-precompact/tsconfig.json +0 -25
  56. package/hooks/hook-protectfiles/tsconfig.json +0 -25
  57. package/hooks/hook-scanoutput/src/hook.test.ts +0 -217
  58. package/hooks/hook-spiral-detector/src/hook.test.ts +0 -72
  59. package/hooks/hook-stylescheck/tsconfig.json +0 -25
  60. package/hooks/hook-typecheck-gate/tsconfig.json +0 -25
  61. package/hooks/hook-workspace-repos-guard/src/hook.test.ts +0 -466
  62. package/hooks/hook-workspace-repos-guard/tsconfig.json +0 -21
  63. package/hooks/mention-context/src/hook.test.ts +0 -68
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@hasna/hooks",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "Open source hooks library for AI coding agents - Install safety, quality, and automation hooks with a single command",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "hooks": "bin/index.js",
8
+ "hooks-mcp": "bin/hooks-mcp.js",
8
9
  "hooks-serve": "bin/serve.js"
9
10
  },
10
11
  "exports": {
@@ -12,6 +13,10 @@
12
13
  "types": "./dist/index.d.ts",
13
14
  "import": "./dist/index.js"
14
15
  },
16
+ "./sdk": {
17
+ "types": "./dist/sdk/index.d.ts",
18
+ "import": "./dist/sdk/index.js"
19
+ },
15
20
  "./storage": {
16
21
  "types": "./dist/storage.d.ts",
17
22
  "import": "./dist/storage.js"
@@ -20,7 +25,7 @@
20
25
  "main": "./dist/index.js",
21
26
  "types": "./dist/index.d.ts",
22
27
  "scripts": {
23
- "build": "rm -rf dist bin && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external pg --external ink --external react --external chalk --external conf --external @modelcontextprotocol/sdk --external zod && bun build ./src/serve.ts --outdir ./bin --target bun --external pg && bun build ./src/index.ts ./src/storage.ts --outdir ./dist --target bun --external pg && bun run build:types",
28
+ "build": "rm -rf dist bin && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external pg --external ink --external react --external chalk --external conf --external @modelcontextprotocol/sdk --external zod && bun build ./src/serve.ts --outdir ./bin --target bun --external pg && bun build ./src/mcp/hooks-mcp.ts --outdir ./bin --target bun --external pg --external chalk --external conf --external @modelcontextprotocol/sdk --external zod && bun build ./src/sdk/index.ts --outdir ./dist/sdk --target bun && bun build ./src/index.ts ./src/storage.ts --outdir ./dist --target bun --external pg && bun run build:types",
24
29
  "build:types": "tsc -p tsconfig.build.json",
25
30
  "dev": "bun run ./src/cli/index.tsx",
26
31
  "test": "bun test",
@@ -80,7 +85,9 @@
80
85
  "!hooks/*/dist/",
81
86
  "scripts/",
82
87
  "README.md",
83
- "LICENSE"
88
+ "LICENSE",
89
+ "!hooks/**/*.test.ts",
90
+ "!hooks/**/tsconfig.json"
84
91
  ],
85
92
  "homepage": "https://github.com/hasna/hooks#readme",
86
93
  "bugs": {
@@ -173,15 +173,27 @@ async function validateExtractedSmoke(pkg: PackageJson): Promise<void> {
173
173
  const dataDir = join(workspace, "data");
174
174
  const binIndex = join(packageDir, "bin", "index.js");
175
175
  const binServe = join(packageDir, "bin", "serve.js");
176
+ const binMcp = join(packageDir, "bin", "hooks-mcp.js");
177
+ const sdkBundle = join(packageDir, "dist", "sdk", "index.js");
176
178
  if (!existsSync(binIndex)) throw new Error(`packed tarball is missing bin/index.js (CLI bin)`);
177
179
  if (!existsSync(binServe)) throw new Error(`packed tarball is missing bin/serve.js (serve bin)`);
180
+ if (!existsSync(binMcp)) throw new Error(`packed tarball is missing bin/hooks-mcp.js (MCP bin)`);
181
+ if (!existsSync(sdkBundle)) throw new Error(`packed tarball is missing dist/sdk/index.js (./sdk export)`);
178
182
 
183
+ // Hermetic route: the smoke lanes below exercise the on-box store on
184
+ // purpose, so they opt in explicitly (hasna/apps#1720 — without the
185
+ // opt-in `hooks run` and `hooks-mcp` fail closed, which the fail-closed
186
+ // lane checks separately) and keep the station's Keychain out of it.
179
187
  const env = {
180
188
  ...process.env,
189
+ HASNA_STATION: "no-such-station",
190
+ HASNA_HOOKS_LOCAL: "1",
181
191
  HASNA_HOOKS_DATA_DIR: dataDir,
182
192
  HASNA_HOOKS_DB_PATH: join(dataDir, "hooks.db"),
183
193
  NO_COLOR: "1",
184
194
  };
195
+ const failClosedEnv = { ...env };
196
+ delete (failClosedEnv as Record<string, string | undefined>).HASNA_HOOKS_LOCAL;
185
197
 
186
198
  // 1. CLI help.
187
199
  const help = spawnSync("bun", ["run", binIndex, "--help"], { cwd: smokeDir, env, encoding: "utf8" });
@@ -210,8 +222,22 @@ async function validateExtractedSmoke(pkg: PackageJson): Promise<void> {
210
222
  await new Promise((r) => setTimeout(r, 100));
211
223
  }
212
224
 
213
- // 3. MCP stdio startup: an initialize handshake must get a response.
214
- const mcpProc = spawn("bun", ["run", binIndex, "mcp", "--stdio"], {
225
+ // 2b. Fail-closed lane (hasna/apps#1720): with nothing configured the
226
+ // packed CLI and the packed hooks-mcp bin exit non-zero, name the
227
+ // credential tiers + the opt-in on stderr, and create no local store.
228
+ const failClosedHome = join(workspace, "failclosed-home");
229
+ const fcEnv = { ...failClosedEnv, HOME: failClosedHome, HASNA_HOOKS_DATA_DIR: join(failClosedHome, "data"), HASNA_HOOKS_DB_PATH: join(failClosedHome, "data", "hooks.db") };
230
+ for (const [label, argv] of [["packed CLI `hooks categories`", [binIndex, "categories"]], ["packed hooks-mcp", [binMcp]]] as Array<[string, string[]]>) {
231
+ const fc = spawnSync("bun", ["run", ...argv], { cwd: smokeDir, env: fcEnv, encoding: "utf8", input: "", timeout: 20000 });
232
+ if (fc.status === 0) throw new Error(`${label} exited 0 with nothing configured (must fail closed)`);
233
+ if (!/HASNA_HOOKS_LOCAL=1/.test(fc.stderr) || !/hasna\.credentials\.hooks\.api-key/.test(fc.stderr)) {
234
+ throw new Error(`${label} refusal did not name the tiers + opt-in: ${fc.stderr.slice(0, 300)}`);
235
+ }
236
+ if (existsSync(join(failClosedHome, "data", "hooks.db"))) throw new Error(`${label} created hooks.db while failing closed`);
237
+ }
238
+
239
+ // 3. MCP stdio startup (explicit local opt-in): an initialize handshake must get a response.
240
+ const mcpProc = spawn("bun", ["run", binMcp], {
215
241
  cwd: smokeDir,
216
242
  env,
217
243
  stdio: ["pipe", "pipe", "pipe"],
@@ -252,11 +278,12 @@ async function validateExtractedSmoke(pkg: PackageJson): Promise<void> {
252
278
  // pg link is best-effort; the import below will fail loudly if needed.
253
279
  }
254
280
  const sdkSmoke = join(smokeDir, "sdk-smoke.ts");
255
- await writeFile(sdkSmoke, `import { HOOKS, getStorageStatus } from "@hasna/hooks";\nimport { getStorageStatus as ss } from "@hasna/hooks/storage";\nconsole.log(JSON.stringify({ count: HOOKS.length, backend: getStorageStatus().backend, ss: ss().backend }));\n`);
281
+ await writeFile(sdkSmoke, `import { HOOKS, getStorageStatus } from "@hasna/hooks";\nimport { getStorageStatus as ss } from "@hasna/hooks/storage";\nimport { HooksClient, createHooksClient } from "@hasna/hooks/sdk";\nlet sdk = "threw";\ntry { createHooksClient({ env: { HASNA_STATION: "no-such-station" }, credentials: { keychain: { enabled: false } } }); sdk = "returned"; } catch (e) { sdk = /REMOTE_API_/.test(String(e)) ? "fail-closed" : "threw"; }\nconsole.log(JSON.stringify({ count: HOOKS.length, backend: getStorageStatus().backend, ss: ss().backend, sdk, hasClient: typeof HooksClient === "function" }));\n`);
256
282
  const sdk = spawnSync("bun", ["run", sdkSmoke], { cwd: smokeDir, env, encoding: "utf8", timeout: 20000 });
257
283
  if (sdk.status !== 0) throw new Error(`packed SDK import failed: ${sdk.stderr}`);
258
- const sdkOut = JSON.parse(sdk.stdout.trim()) as { count: number; backend: string };
284
+ const sdkOut = JSON.parse(sdk.stdout.trim()) as { count: number; backend: string; sdk: string; hasClient: boolean };
259
285
  if (typeof sdkOut.count !== "number" || sdkOut.count <= 0) throw new Error(`packed SDK import returned count ${sdkOut.count}`);
286
+ if (sdkOut.sdk !== "fail-closed" || !sdkOut.hasClient) throw new Error(`packed ./sdk export did not fail closed with nothing configured: ${JSON.stringify(sdkOut)}`);
260
287
 
261
288
  // 5. One bundled-hook run from the packed artifact (isolated data dir;
262
289
  // first run self-trusts, then executes).