@webpieces/ai-hook-rules 0.3.182 → 0.3.183

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/ai-hook-rules",
3
- "version": "0.3.182",
3
+ "version": "0.3.183",
4
4
  "description": "Pluggable write-time validation framework for AI coding agents (@webpieces/ai-hook-rules). Claude Code PreToolUse + openclaw before_tool_call adapters share one rule engine.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -36,7 +36,7 @@
36
36
  "directory": "packages/tooling/ai-hook-rules"
37
37
  },
38
38
  "dependencies": {
39
- "@webpieces/rules-config": "0.3.182"
39
+ "@webpieces/rules-config": "0.3.183"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public"
@@ -3,6 +3,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const fs_1 = require("fs");
4
4
  const path_1 = require("path");
5
5
  const child_process_1 = require("child_process");
6
+ /**
7
+ * Walk up from `startDir` until we find the directory containing `.git`, which marks
8
+ * the root of the repo. Claude can `cd` into a sub-package before running a Bash tool,
9
+ * which moves process.cwd() away from the repo root; without this traversal the hook
10
+ * would look for `.webpieces/skiphooks` and `node_modules/.bin/wp-ai-hook` in the wrong
11
+ * place and wrongly report webpieces as "not installed". `.git` can be a directory
12
+ * (normal clone) or a file (worktree / submodule), so we accept either.
13
+ * Falls back to `startDir` when no `.git` is found anywhere up the tree.
14
+ */
15
+ function findRepoRoot(startDir) {
16
+ let dir = startDir;
17
+ const fsRoot = (0, path_1.parse)(dir).root;
18
+ while (true) {
19
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, '.git')))
20
+ return dir;
21
+ if (dir === fsRoot)
22
+ return startDir;
23
+ dir = (0, path_1.dirname)(dir);
24
+ }
25
+ }
6
26
  function readSkipHooks(cwd) {
7
27
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
8
28
  try {
@@ -17,7 +37,7 @@ function readSkipHooks(cwd) {
17
37
  }
18
38
  }
19
39
  function run(rawInput) {
20
- const cwd = process.cwd();
40
+ const cwd = findRepoRoot(process.cwd());
21
41
  let payload = null;
22
42
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
23
43
  try {
@@ -47,6 +67,7 @@ function run(rawInput) {
47
67
  const result = (0, child_process_1.spawnSync)(localHook, [], {
48
68
  input: Buffer.from(rawInput),
49
69
  encoding: 'buffer',
70
+ cwd,
50
71
  });
51
72
  if (result.stdout?.length)
52
73
  process.stdout.write(result.stdout);
@@ -1 +1 @@
1
- {"version":3,"file":"global-hook.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/global-hook.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { spawnSync } from 'child_process';\n\ninterface SkipHooksFile {\n expires: number | null;\n reason?: string;\n}\n\ninterface HookPayload {\n tool_name?: string;\n tool_input?: {\n file_path?: string;\n };\n}\n\nfunction readSkipHooks(cwd: string): SkipHooksFile | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const skipPath = join(cwd, '.webpieces', 'skiphooks');\n if (!existsSync(skipPath)) return null;\n return JSON.parse(readFileSync(skipPath, 'utf8')) as SkipHooksFile;\n } catch (err: unknown) {\n // eslint-disable-next-line @webpieces/catch-error-pattern -- intentionally discard; malformed .skiphooks must not crash global hook\n return null;\n }\n}\n\nfunction run(rawInput: string): void {\n const cwd = process.cwd();\n\n let payload: HookPayload | null = null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n if (rawInput.trim()) {\n payload = JSON.parse(rawInput) as HookPayload;\n }\n } catch (err: unknown) {\n //const error = toError(err); -- malformed stdin is intentionally ignored; must not crash global hook\n }\n\n // 1. Check .skiphooks — if valid and not expired, allow everything\n const skipHooks = readSkipHooks(cwd);\n if (skipHooks !== null) {\n const nowSeconds = Date.now() / 1000;\n if (skipHooks.expires === null || nowSeconds < skipHooks.expires) {\n process.exit(0);\n }\n }\n\n // 2. Always allow writing .webpieces/skiphooks (this is the escape hatch)\n const filePath = payload?.tool_input?.file_path ?? '';\n if (payload?.tool_name === 'Write' && filePath.endsWith('.webpieces/skiphooks')) {\n process.exit(0);\n }\n\n // 3. Delegate to per-repo hook if present\n const localHook = join(cwd, 'node_modules', '.bin', 'wp-ai-hook');\n if (existsSync(localHook)) {\n const result = spawnSync(localHook, [], {\n input: Buffer.from(rawInput),\n encoding: 'buffer',\n });\n if (result.stdout?.length) process.stdout.write(result.stdout);\n if (result.stderr?.length) process.stderr.write(result.stderr);\n process.exit(result.status ?? 0);\n }\n\n // 4. No local hook found — block and instruct AI\n process.stderr.write(\n 'STOP. You MUST ask the human to run one of 3 commands to continue.\\n' +\n '\\n' +\n 'This project is missing the webpieces AI hook (./node_modules/.bin/wp-ai-hook not found).\\n' +\n '\\n' +\n 'Present these THREE options to the human verbatim and wait for them to pick one.\\n' +\n 'Each option is a command the human can copy-paste into their own terminal:\\n' +\n '\\n' +\n ' A) PREFERRED — install the hook.\\n' +\n ' If this is a pnpm workspace, run from the monorepo root:\\n' +\n ' pnpm add -Dw @webpieces/ai-hook-rules\\n' +\n ' Otherwise, run from the project root:\\n' +\n ' npm install --save-dev @webpieces/ai-hook-rules\\n' +\n '\\n' +\n ' B) Skip hooks temporarily (auto-expires). Ask the human how long, then have them run\\n' +\n ' ONE of these from the project root:\\n' +\n ' 1 hour: mkdir -p .webpieces && printf \\'{\"expires\": %s, \"reason\": \"temporary skip\"}\\' \"$(($(date +%s) + 3600))\" > .webpieces/skiphooks\\n' +\n ' 1 week: mkdir -p .webpieces && printf \\'{\"expires\": %s, \"reason\": \"temporary skip\"}\\' \"$(($(date +%s) + 604800))\" > .webpieces/skiphooks\\n' +\n '\\n' +\n ' C) Disable permanently (NEVER expires — hooks stay off until this file is deleted).\\n' +\n ' Only choose this if the human explicitly wants it. Have them run from the project root:\\n' +\n ' mkdir -p .webpieces && printf \\'{\"expires\": null, \"reason\": \"permanently disabled\"}\\' > .webpieces/skiphooks\\n' +\n ' To re-enable later: rm .webpieces/skiphooks\\n' +\n '\\n' +\n 'You are BLOCKED. Ask the human now and wait for their response.\\n',\n );\n process.exit(2);\n}\n\nlet stdinData = '';\nprocess.stdin.resume();\nprocess.stdin.setEncoding('utf8');\nprocess.stdin.on('data', (chunk: string) => {\n stdinData += chunk;\n});\nprocess.stdin.on('end', () => {\n run(stdinData);\n});\n"]}
1
+ {"version":3,"file":"global-hook.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/global-hook.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'fs';\nimport { join, dirname, parse } from 'path';\nimport { spawnSync } from 'child_process';\n\ninterface SkipHooksFile {\n expires: number | null;\n reason?: string;\n}\n\n/**\n * Walk up from `startDir` until we find the directory containing `.git`, which marks\n * the root of the repo. Claude can `cd` into a sub-package before running a Bash tool,\n * which moves process.cwd() away from the repo root; without this traversal the hook\n * would look for `.webpieces/skiphooks` and `node_modules/.bin/wp-ai-hook` in the wrong\n * place and wrongly report webpieces as \"not installed\". `.git` can be a directory\n * (normal clone) or a file (worktree / submodule), so we accept either.\n * Falls back to `startDir` when no `.git` is found anywhere up the tree.\n */\nfunction findRepoRoot(startDir: string): string {\n let dir = startDir;\n const fsRoot = parse(dir).root;\n while (true) {\n if (existsSync(join(dir, '.git'))) return dir;\n if (dir === fsRoot) return startDir;\n dir = dirname(dir);\n }\n}\n\ninterface HookPayload {\n tool_name?: string;\n tool_input?: {\n file_path?: string;\n };\n}\n\nfunction readSkipHooks(cwd: string): SkipHooksFile | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const skipPath = join(cwd, '.webpieces', 'skiphooks');\n if (!existsSync(skipPath)) return null;\n return JSON.parse(readFileSync(skipPath, 'utf8')) as SkipHooksFile;\n } catch (err: unknown) {\n // eslint-disable-next-line @webpieces/catch-error-pattern -- intentionally discard; malformed .skiphooks must not crash global hook\n return null;\n }\n}\n\nfunction run(rawInput: string): void {\n const cwd = findRepoRoot(process.cwd());\n\n let payload: HookPayload | null = null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n if (rawInput.trim()) {\n payload = JSON.parse(rawInput) as HookPayload;\n }\n } catch (err: unknown) {\n //const error = toError(err); -- malformed stdin is intentionally ignored; must not crash global hook\n }\n\n // 1. Check .skiphooks — if valid and not expired, allow everything\n const skipHooks = readSkipHooks(cwd);\n if (skipHooks !== null) {\n const nowSeconds = Date.now() / 1000;\n if (skipHooks.expires === null || nowSeconds < skipHooks.expires) {\n process.exit(0);\n }\n }\n\n // 2. Always allow writing .webpieces/skiphooks (this is the escape hatch)\n const filePath = payload?.tool_input?.file_path ?? '';\n if (payload?.tool_name === 'Write' && filePath.endsWith('.webpieces/skiphooks')) {\n process.exit(0);\n }\n\n // 3. Delegate to per-repo hook if present\n const localHook = join(cwd, 'node_modules', '.bin', 'wp-ai-hook');\n if (existsSync(localHook)) {\n const result = spawnSync(localHook, [], {\n input: Buffer.from(rawInput),\n encoding: 'buffer',\n cwd,\n });\n if (result.stdout?.length) process.stdout.write(result.stdout);\n if (result.stderr?.length) process.stderr.write(result.stderr);\n process.exit(result.status ?? 0);\n }\n\n // 4. No local hook found — block and instruct AI\n process.stderr.write(\n 'STOP. You MUST ask the human to run one of 3 commands to continue.\\n' +\n '\\n' +\n 'This project is missing the webpieces AI hook (./node_modules/.bin/wp-ai-hook not found).\\n' +\n '\\n' +\n 'Present these THREE options to the human verbatim and wait for them to pick one.\\n' +\n 'Each option is a command the human can copy-paste into their own terminal:\\n' +\n '\\n' +\n ' A) PREFERRED — install the hook.\\n' +\n ' If this is a pnpm workspace, run from the monorepo root:\\n' +\n ' pnpm add -Dw @webpieces/ai-hook-rules\\n' +\n ' Otherwise, run from the project root:\\n' +\n ' npm install --save-dev @webpieces/ai-hook-rules\\n' +\n '\\n' +\n ' B) Skip hooks temporarily (auto-expires). Ask the human how long, then have them run\\n' +\n ' ONE of these from the project root:\\n' +\n ' 1 hour: mkdir -p .webpieces && printf \\'{\"expires\": %s, \"reason\": \"temporary skip\"}\\' \"$(($(date +%s) + 3600))\" > .webpieces/skiphooks\\n' +\n ' 1 week: mkdir -p .webpieces && printf \\'{\"expires\": %s, \"reason\": \"temporary skip\"}\\' \"$(($(date +%s) + 604800))\" > .webpieces/skiphooks\\n' +\n '\\n' +\n ' C) Disable permanently (NEVER expires — hooks stay off until this file is deleted).\\n' +\n ' Only choose this if the human explicitly wants it. Have them run from the project root:\\n' +\n ' mkdir -p .webpieces && printf \\'{\"expires\": null, \"reason\": \"permanently disabled\"}\\' > .webpieces/skiphooks\\n' +\n ' To re-enable later: rm .webpieces/skiphooks\\n' +\n '\\n' +\n 'You are BLOCKED. Ask the human now and wait for their response.\\n',\n );\n process.exit(2);\n}\n\nlet stdinData = '';\nprocess.stdin.resume();\nprocess.stdin.setEncoding('utf8');\nprocess.stdin.on('data', (chunk: string) => {\n stdinData += chunk;\n});\nprocess.stdin.on('end', () => {\n run(stdinData);\n});\n"]}