@webpieces/ai-hook-rules 0.3.178 → 0.3.179

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.178",
3
+ "version": "0.3.179",
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.178"
39
+ "@webpieces/rules-config": "0.3.179"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public"
@@ -55,18 +55,29 @@ function run(rawInput) {
55
55
  process.exit(result.status ?? 0);
56
56
  }
57
57
  // 4. No local hook found — block and instruct AI
58
- process.stderr.write('STOP. DO NOT TAKE ANY ACTION. You MUST ask the human and wait for their answer.\n' +
58
+ process.stderr.write('STOP. You MUST ask the human to run one of 3 commands to continue.\n' +
59
59
  '\n' +
60
60
  'This project is missing the webpieces AI hook (./node_modules/.bin/wp-ai-hook not found).\n' +
61
61
  '\n' +
62
- 'Present these options to the human and wait for their choice:\n' +
63
- ' A) PREFERRED: Run from the monorepo root: `pnpm add -Dw @webpieces/ai-hook-rules`\n' +
64
- ' (the -D flag makes it a devDependency, -w adds it to the workspace root)\n' +
65
- ' If not a pnpm workspace, run from the project root: `npm install --save-dev @webpieces/ai-hook-rules`\n' +
66
- ' B) Skip hooks temporarily: ask human how long (1 hour / 1 day / forever), then write\n' +
67
- ' .webpieces/skiphooks with {"expires": <unix epoch seconds>, "reason": "..."}\n' +
62
+ 'Present these THREE options to the human verbatim and wait for them to pick one.\n' +
63
+ 'Each option is a command the human can copy-paste into their own terminal:\n' +
64
+ '\n' +
65
+ ' A) PREFERRED install the hook.\n' +
66
+ ' If this is a pnpm workspace, run from the monorepo root:\n' +
67
+ ' pnpm add -Dw @webpieces/ai-hook-rules\n' +
68
+ ' Otherwise, run from the project root:\n' +
69
+ ' npm install --save-dev @webpieces/ai-hook-rules\n' +
70
+ '\n' +
71
+ ' B) Skip hooks temporarily (auto-expires). Ask the human how long, then have them run\n' +
72
+ ' ONE of these from the project root:\n' +
73
+ ' 1 hour: mkdir -p .webpieces && printf \'{"expires": %s, "reason": "temporary skip"}\' "$(($(date +%s) + 3600))" > .webpieces/skiphooks\n' +
74
+ ' 1 week: mkdir -p .webpieces && printf \'{"expires": %s, "reason": "temporary skip"}\' "$(($(date +%s) + 604800))" > .webpieces/skiphooks\n' +
75
+ '\n' +
76
+ ' C) Disable permanently (NEVER expires — hooks stay off until this file is deleted).\n' +
77
+ ' Only choose this if the human explicitly wants it. Have them run from the project root:\n' +
78
+ ' mkdir -p .webpieces && printf \'{"expires": null, "reason": "permanently disabled"}\' > .webpieces/skiphooks\n' +
79
+ ' To re-enable later: rm .webpieces/skiphooks\n' +
68
80
  '\n' +
69
- 'WARNING: DO NOT disable permanently (expires: null) without explicit human permission.\n' +
70
81
  'You are BLOCKED. Ask the human now and wait for their response.\n');
71
82
  process.exit(2);
72
83
  }
@@ -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. DO NOT TAKE ANY ACTION. You MUST ask the human and wait for their answer.\\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 options to the human and wait for their choice:\\n' +\n ' A) PREFERRED: Run from the monorepo root: `pnpm add -Dw @webpieces/ai-hook-rules`\\n' +\n ' (the -D flag makes it a devDependency, -w adds it to the workspace root)\\n' +\n ' If not a pnpm workspace, run from the project root: `npm install --save-dev @webpieces/ai-hook-rules`\\n' +\n ' B) Skip hooks temporarily: ask human how long (1 hour / 1 day / forever), then write\\n' +\n ' .webpieces/skiphooks with {\"expires\": <unix epoch seconds>, \"reason\": \"...\"}\\n' +\n '\\n' +\n 'WARNING: DO NOT disable permanently (expires: null) without explicit human permission.\\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 } 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"]}