@webpieces/ai-hook-rules 0.3.159 → 0.3.161

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 (43) hide show
  1. package/bin/wp-ai-hook.js +16 -0
  2. package/bin/wp-dev-hook-install.js +16 -0
  3. package/bin/wp-dev-hook-uninstall.js +16 -0
  4. package/bin/wp-setup-ai-hooks.js +1 -1
  5. package/bin/wp-setup-global-ai-hooks.js +16 -0
  6. package/package.json +6 -5
  7. package/src/adapters/claude-code-hook.js +8 -4
  8. package/src/adapters/claude-code-hook.js.map +1 -1
  9. package/src/adapters/global-hook.d.ts +1 -0
  10. package/src/adapters/global-hook.js +76 -0
  11. package/src/adapters/global-hook.js.map +1 -0
  12. package/src/adapters/openclaw-plugin.js +4 -2
  13. package/src/adapters/openclaw-plugin.js.map +1 -1
  14. package/src/bin/{postinstall.d.ts → dev-hook-install.d.ts} +0 -1
  15. package/src/bin/dev-hook-install.js +54 -0
  16. package/src/bin/dev-hook-install.js.map +1 -0
  17. package/src/bin/dev-hook-uninstall.d.ts +1 -0
  18. package/src/bin/dev-hook-uninstall.js +34 -0
  19. package/src/bin/dev-hook-uninstall.js.map +1 -0
  20. package/src/bin/global-setup.d.ts +1 -0
  21. package/src/bin/global-setup.js +60 -0
  22. package/src/bin/global-setup.js.map +1 -0
  23. package/src/bin/setup.d.ts +1 -0
  24. package/src/bin/setup.js +102 -0
  25. package/src/bin/setup.js.map +1 -0
  26. package/src/core/load-rules.js +5 -3
  27. package/src/core/load-rules.js.map +1 -1
  28. package/src/core/rules/index.js +1 -0
  29. package/src/core/rules/index.js.map +1 -1
  30. package/src/core/rules/no-js-files.d.ts +3 -0
  31. package/src/core/rules/no-js-files.js +29 -0
  32. package/src/core/rules/no-js-files.js.map +1 -0
  33. package/src/core/rules/validate-ts-in-src.js +0 -1
  34. package/src/core/rules/validate-ts-in-src.js.map +1 -1
  35. package/src/core/runner.js +40 -50
  36. package/src/core/runner.js.map +1 -1
  37. package/src/core/types.d.ts +1 -1
  38. package/src/core/types.js +2 -1
  39. package/src/core/types.js.map +1 -1
  40. package/templates/claude-settings-hook.json +1 -1
  41. package/bin/postinstall.js +0 -15
  42. package/src/bin/postinstall.js +0 -106
  43. package/src/bin/postinstall.js.map +0 -1
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // Plain JS shim — delegates to compiled TypeScript.
3
+ // Must NOT be converted to TypeScript (needs to exist pre-build for pnpm bin symlinks).
4
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
5
+ 'use strict';
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const compiled = path.join(__dirname, '..', 'src', 'adapters', 'claude-code-hook.js');
10
+
11
+ if (fs.existsSync(compiled)) {
12
+ require(compiled).main();
13
+ } else {
14
+ console.error(' [ai-hook-rules] Package not built yet. Run the build first, or install from npm.');
15
+ process.exit(1);
16
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // Plain JS shim — delegates to compiled TypeScript.
3
+ // Must NOT be converted to TypeScript (needs to exist pre-build for pnpm bin symlinks).
4
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
5
+ 'use strict';
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const compiled = path.join(__dirname, '..', 'src', 'bin', 'dev-hook-install.js');
10
+
11
+ if (fs.existsSync(compiled)) {
12
+ require(compiled).main();
13
+ } else {
14
+ console.error(' [ai-hook-rules] Package not built yet. Run `nx build ai-hook-rules` first.');
15
+ process.exit(1);
16
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // Plain JS shim — delegates to compiled TypeScript.
3
+ // Must NOT be converted to TypeScript (needs to exist pre-build for pnpm bin symlinks).
4
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
5
+ 'use strict';
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const compiled = path.join(__dirname, '..', 'src', 'bin', 'dev-hook-uninstall.js');
10
+
11
+ if (fs.existsSync(compiled)) {
12
+ require(compiled).main();
13
+ } else {
14
+ console.error(' [ai-hook-rules] Package not built yet. Run `nx build ai-hook-rules` first.');
15
+ process.exit(1);
16
+ }
@@ -6,7 +6,7 @@
6
6
 
7
7
  const path = require('path');
8
8
  const fs = require('fs');
9
- const compiled = path.join(__dirname, '..', 'src', 'bin', 'postinstall.js');
9
+ const compiled = path.join(__dirname, '..', 'src', 'bin', 'setup.js');
10
10
 
11
11
  if (fs.existsSync(compiled)) {
12
12
  require(compiled).main();
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ // Plain JS shim — delegates to compiled TypeScript.
3
+ // Must NOT be converted to TypeScript (needs to exist pre-build for pnpm bin symlinks).
4
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
5
+ 'use strict';
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const compiled = path.join(__dirname, '..', 'src', 'bin', 'global-setup.js');
10
+
11
+ if (fs.existsSync(compiled)) {
12
+ require(compiled).main();
13
+ } else {
14
+ console.error(' [ai-hook-rules] Package not built yet. Run the build first, or install from npm.');
15
+ process.exit(1);
16
+ }
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@webpieces/ai-hook-rules",
3
- "version": "0.3.159",
3
+ "version": "0.3.161",
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",
7
- "scripts": {
8
- "postinstall": "node bin/postinstall.js"
9
- },
10
7
  "bin": {
8
+ "wp-ai-hook": "./bin/wp-ai-hook.js",
11
9
  "wp-setup-ai-hooks": "./bin/wp-setup-ai-hooks.js",
10
+ "wp-setup-global-ai-hooks": "./bin/wp-setup-global-ai-hooks.js",
11
+ "wp-dev-hook-install": "./bin/wp-dev-hook-install.js",
12
+ "wp-dev-hook-uninstall": "./bin/wp-dev-hook-uninstall.js",
12
13
  "wp-git-update": "./src/scripts/git-updateFromMain.js",
13
14
  "wp-git-gather": "./src/scripts/git-gatherInfo.js",
14
15
  "wp-git-merge-complete": "./src/scripts/git-mergeComplete.js"
@@ -35,7 +36,7 @@
35
36
  "directory": "packages/tooling/ai-hook-rules"
36
37
  },
37
38
  "dependencies": {
38
- "@webpieces/rules-config": "0.3.159"
39
+ "@webpieces/rules-config": "0.3.161"
39
40
  },
40
41
  "publishConfig": {
41
42
  "access": "public"
@@ -26,8 +26,7 @@ function safeParse(raw) {
26
26
  }
27
27
  catch (err) {
28
28
  const error = (0, to_error_1.toError)(err);
29
- void error;
30
- return null;
29
+ throw new types_1.InformAiError(`Malformed hook input from Claude Code stdin: ${error.message}`);
31
30
  }
32
31
  }
33
32
  function normalizeToolKind(toolName) {
@@ -102,8 +101,13 @@ async function main() {
102
101
  }
103
102
  catch (err) {
104
103
  const error = (0, to_error_1.toError)(err);
105
- process.stderr.write(`[ai-hooks] claude-code adapter crashed (failing open): ${error.message}\n`);
106
- process.exit(0);
104
+ if (err instanceof types_1.InformAiError) {
105
+ process.stderr.write(error.message + '\n');
106
+ }
107
+ else {
108
+ process.stderr.write(`[ai-hooks] hook crashed unexpectedly — failing closed: ${error.message}\n`);
109
+ }
110
+ process.exit(2);
107
111
  }
108
112
  }
109
113
  if (require.main === module) {
@@ -1 +1 @@
1
- {"version":3,"file":"claude-code-hook.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/claude-code-hook.ts"],"names":[],"mappings":";;AA4EA,oBAoCC;AAhHD,2CAA8C;AAC9C,yDAAqD;AACrD,yCAA8E;AAC9E,+CAA2C;AAE3C,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;AAqBnE,SAAS,SAAS;IACd,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC3B,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7C,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK;YAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC1B,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC3C,8DAA8D;IAC9D,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAC;IAChD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,KAAK,KAAK,CAAC;QACX,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB;IACvC,IAAI,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAoB,CAAC;IAClE,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,kBAAkB,CAAC,QAAkB,EAAE,SAA8B;IAC1E,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,IAAI,2BAAmB,CAAC,QAAQ,EAAE;YACrC,IAAI,sBAAc,CAAC,EAAE,EAAE,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC;SAClD,CAAC,CAAC;IACP,CAAC;IACD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,IAAI,2BAAmB,CAAC,QAAQ,EAAE;YACrC,IAAI,sBAAc,CAAC,SAAS,CAAC,UAAU,IAAI,EAAE,EAAE,SAAS,CAAC,UAAU,IAAI,EAAE,CAAC;SAC7E,CAAC,CAAC;IACP,CAAC;IACD,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,sBAAc,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC;QACzF,OAAO,IAAI,2BAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAEM,KAAK,UAAU,IAAI;IACtB,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;YAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAAC,OAAO;QAAC,CAAC;QAE1C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAE1B,IAAI,OAAO,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;YAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;YAC3C,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YACnE,MAAM,MAAM,GAAG,IAAA,gBAAO,EAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACrC,IAAI,CAAC,MAAM,EAAE,CAAC;gBAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YACzC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChB,OAAO;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACtD,IAAI,CAAC,QAAQ,EAAE,CAAC;YAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAAC,OAAO;QAAC,CAAC;QAE3C,MAAM,KAAK,GAAG,kBAAkB,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK,EAAE,CAAC;YAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAAC,OAAO;QAAC,CAAC;QAExC,MAAM,MAAM,GAAG,IAAA,YAAG,EAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM,EAAE,CAAC;YAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAAC,OAAO;QAAC,CAAC;QAEzC,IAAA,4BAAY,EAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;QAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0DAA0D,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;QAClG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,CAAC;AACX,CAAC","sourcesContent":["import { run, runBash } from '../core/runner';\nimport { logRejection } from '../core/rejection-log';\nimport { NormalizedToolInput, NormalizedEdit, ToolKind } from '../core/types';\nimport { toError } from '../core/to-error';\n\nconst HANDLED_FILE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit']);\n\ninterface ClaudeCodePayload {\n tool_name: string;\n tool_input: ClaudeCodeToolInput;\n}\n\ninterface ClaudeCodeToolInput {\n file_path?: string;\n content?: string;\n old_string?: string;\n new_string?: string;\n edits?: ClaudeCodeEditEntry[];\n command?: string;\n}\n\ninterface ClaudeCodeEditEntry {\n old_string?: string;\n new_string?: string;\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => { data += chunk; });\n process.stdin.on('end', () => resolve(data));\n process.stdin.on('error', () => resolve(''));\n if (process.stdin.isTTY) resolve('');\n });\n}\n\nfunction safeParse(raw: string): ClaudeCodePayload | null {\n if (!raw || raw.trim() === '') return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(raw) as ClaudeCodePayload;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n}\n\nfunction normalizeToolKind(toolName: string): ToolKind | null {\n if (HANDLED_FILE_TOOLS.has(toolName)) return toolName as ToolKind;\n return null;\n}\n\nfunction normalizeToolInput(toolKind: ToolKind, toolInput: ClaudeCodeToolInput): NormalizedToolInput | null {\n const filePath = toolInput.file_path;\n if (!filePath) return null;\n\n if (toolKind === 'Write') {\n return new NormalizedToolInput(filePath, [\n new NormalizedEdit('', toolInput.content || ''),\n ]);\n }\n if (toolKind === 'Edit') {\n return new NormalizedToolInput(filePath, [\n new NormalizedEdit(toolInput.old_string || '', toolInput.new_string || ''),\n ]);\n }\n if (toolKind === 'MultiEdit') {\n const raw = Array.isArray(toolInput.edits) ? toolInput.edits : [];\n const edits = raw.map((e) => new NormalizedEdit(e.old_string || '', e.new_string || ''));\n return new NormalizedToolInput(filePath, edits);\n }\n return null;\n}\n\nexport async function main(): Promise<void> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = await readStdin();\n const payload = safeParse(raw);\n if (!payload) { process.exit(0); return; }\n\n const cwd = process.cwd();\n\n if (payload.tool_name === 'Bash') {\n const command = payload.tool_input.command;\n if (!command || command.trim() === '') { process.exit(0); return; }\n const result = runBash(command, cwd);\n if (!result) { process.exit(0); return; }\n process.stderr.write(result.report);\n process.exit(2);\n return;\n }\n\n const toolKind = normalizeToolKind(payload.tool_name);\n if (!toolKind) { process.exit(0); return; }\n\n const input = normalizeToolInput(toolKind, payload.tool_input);\n if (!input) { process.exit(0); return; }\n\n const result = run(toolKind, input, cwd);\n if (!result) { process.exit(0); return; }\n\n logRejection(toolKind, input, result, cwd);\n process.stderr.write(result.report);\n process.exit(2);\n } catch (err: unknown) {\n const error = toError(err);\n process.stderr.write(`[ai-hooks] claude-code adapter crashed (failing open): ${error.message}\\n`);\n process.exit(0);\n }\n}\n\nif (require.main === module) {\n main();\n}\n"]}
1
+ {"version":3,"file":"claude-code-hook.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/claude-code-hook.ts"],"names":[],"mappings":"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","sourcesContent":["import { run, runBash } from '../core/runner';\nimport { logRejection } from '../core/rejection-log';\nimport { NormalizedToolInput, NormalizedEdit, ToolKind, InformAiError } from '../core/types';\nimport { toError } from '../core/to-error';\n\nconst HANDLED_FILE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit']);\n\ninterface ClaudeCodePayload {\n tool_name: string;\n tool_input: ClaudeCodeToolInput;\n}\n\ninterface ClaudeCodeToolInput {\n file_path?: string;\n content?: string;\n old_string?: string;\n new_string?: string;\n edits?: ClaudeCodeEditEntry[];\n command?: string;\n}\n\ninterface ClaudeCodeEditEntry {\n old_string?: string;\n new_string?: string;\n}\n\nfunction readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let data = '';\n process.stdin.setEncoding('utf8');\n process.stdin.on('data', (chunk: string) => { data += chunk; });\n process.stdin.on('end', () => resolve(data));\n process.stdin.on('error', () => resolve(''));\n if (process.stdin.isTTY) resolve('');\n });\n}\n\nfunction safeParse(raw: string): ClaudeCodePayload | null {\n if (!raw || raw.trim() === '') return null;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return JSON.parse(raw) as ClaudeCodePayload;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Malformed hook input from Claude Code stdin: ${error.message}`);\n }\n}\n\nfunction normalizeToolKind(toolName: string): ToolKind | null {\n if (HANDLED_FILE_TOOLS.has(toolName)) return toolName as ToolKind;\n return null;\n}\n\nfunction normalizeToolInput(toolKind: ToolKind, toolInput: ClaudeCodeToolInput): NormalizedToolInput | null {\n const filePath = toolInput.file_path;\n if (!filePath) return null;\n\n if (toolKind === 'Write') {\n return new NormalizedToolInput(filePath, [\n new NormalizedEdit('', toolInput.content || ''),\n ]);\n }\n if (toolKind === 'Edit') {\n return new NormalizedToolInput(filePath, [\n new NormalizedEdit(toolInput.old_string || '', toolInput.new_string || ''),\n ]);\n }\n if (toolKind === 'MultiEdit') {\n const raw = Array.isArray(toolInput.edits) ? toolInput.edits : [];\n const edits = raw.map((e) => new NormalizedEdit(e.old_string || '', e.new_string || ''));\n return new NormalizedToolInput(filePath, edits);\n }\n return null;\n}\n\nexport async function main(): Promise<void> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = await readStdin();\n const payload = safeParse(raw);\n if (!payload) { process.exit(0); return; }\n\n const cwd = process.cwd();\n\n if (payload.tool_name === 'Bash') {\n const command = payload.tool_input.command;\n if (!command || command.trim() === '') { process.exit(0); return; }\n const result = runBash(command, cwd);\n if (!result) { process.exit(0); return; }\n process.stderr.write(result.report);\n process.exit(2);\n return;\n }\n\n const toolKind = normalizeToolKind(payload.tool_name);\n if (!toolKind) { process.exit(0); return; }\n\n const input = normalizeToolInput(toolKind, payload.tool_input);\n if (!input) { process.exit(0); return; }\n\n const result = run(toolKind, input, cwd);\n if (!result) { process.exit(0); return; }\n\n logRejection(toolKind, input, result, cwd);\n process.stderr.write(result.report);\n process.exit(2);\n } catch (err: unknown) {\n const error = toError(err);\n if (err instanceof InformAiError) {\n process.stderr.write(error.message + '\\n');\n } else {\n process.stderr.write(`[ai-hooks] hook crashed unexpectedly — failing closed: ${error.message}\\n`);\n }\n process.exit(2);\n }\n}\n\nif (require.main === module) {\n main();\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const fs_1 = require("fs");
4
+ const path_1 = require("path");
5
+ const child_process_1 = require("child_process");
6
+ function readSkipHooks(cwd) {
7
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
8
+ try {
9
+ const skipPath = (0, path_1.join)(cwd, '.webpieces', 'skiphooks');
10
+ if (!(0, fs_1.existsSync)(skipPath))
11
+ return null;
12
+ return JSON.parse((0, fs_1.readFileSync)(skipPath, 'utf8'));
13
+ }
14
+ catch (err) {
15
+ // eslint-disable-next-line @webpieces/catch-error-pattern -- intentionally discard; malformed .skiphooks must not crash global hook
16
+ return null;
17
+ }
18
+ }
19
+ function run(rawInput) {
20
+ const cwd = process.cwd();
21
+ let payload = null;
22
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
23
+ try {
24
+ if (rawInput.trim()) {
25
+ payload = JSON.parse(rawInput);
26
+ }
27
+ }
28
+ catch (err) {
29
+ //const error = toError(err); -- malformed stdin is intentionally ignored; must not crash global hook
30
+ }
31
+ // 1. Check .skiphooks — if valid and not expired, allow everything
32
+ const skipHooks = readSkipHooks(cwd);
33
+ if (skipHooks !== null) {
34
+ const nowSeconds = Date.now() / 1000;
35
+ if (skipHooks.expires === null || nowSeconds < skipHooks.expires) {
36
+ process.exit(0);
37
+ }
38
+ }
39
+ // 2. Always allow writing .webpieces/skiphooks (this is the escape hatch)
40
+ const filePath = payload?.tool_input?.file_path ?? '';
41
+ if (payload?.tool_name === 'Write' && filePath.endsWith('.webpieces/skiphooks')) {
42
+ process.exit(0);
43
+ }
44
+ // 3. Delegate to per-repo hook if present
45
+ const localHook = (0, path_1.join)(cwd, 'node_modules', '.bin', 'wp-ai-hook');
46
+ if ((0, fs_1.existsSync)(localHook)) {
47
+ const result = (0, child_process_1.spawnSync)(localHook, [], {
48
+ input: rawInput,
49
+ encoding: 'buffer',
50
+ });
51
+ if (result.stdout?.length)
52
+ process.stdout.write(result.stdout);
53
+ if (result.stderr?.length)
54
+ process.stderr.write(result.stderr);
55
+ process.exit(result.status ?? 0);
56
+ }
57
+ // 4. No local hook found — block and instruct AI
58
+ process.stdout.write('This project is missing the webpieces AI hook (./node_modules/.bin/wp-ai-hook not found).\n' +
59
+ 'Ask the human one of:\n' +
60
+ ' A) Install webpieces: run `npm install @webpieces/ai-hook-rules` then retry\n' +
61
+ ' B) Skip hooks: ask how long (1 hour / 1 day / forever), then write .webpieces/skiphooks:\n' +
62
+ ' {"expires": <unix epoch seconds>, "reason": "..."}\n' +
63
+ ' or for forever: {"expires": null, "reason": "..."}\n' +
64
+ ' Note: writing .webpieces/skiphooks is ALWAYS allowed even while blocked.\n');
65
+ process.exit(2);
66
+ }
67
+ let stdinData = '';
68
+ process.stdin.resume();
69
+ process.stdin.setEncoding('utf8');
70
+ process.stdin.on('data', (chunk) => {
71
+ stdinData += chunk;
72
+ });
73
+ process.stdin.on('end', () => {
74
+ run(stdinData);
75
+ });
76
+ //# sourceMappingURL=global-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-hook.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/global-hook.ts"],"names":[],"mappings":";;AAAA,2BAA8C;AAC9C,+BAA4B;AAC5B,iDAA0C;AAc1C,SAAS,aAAa,CAAC,GAAW;IAC9B,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,IAAA,WAAI,EAAC,GAAG,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;QACtD,IAAI,CAAC,IAAA,eAAU,EAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QACvC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAkB,CAAC;IACvE,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,oIAAoI;QACpI,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED,SAAS,GAAG,CAAC,QAAgB;IACzB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAE1B,IAAI,OAAO,GAAuB,IAAI,CAAC;IACvC,8DAA8D;IAC9D,IAAI,CAAC;QACD,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;YAClB,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAgB,CAAC;QAClD,CAAC;IACL,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,qGAAqG;IACzG,CAAC;IAED,mEAAmE;IACnE,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACrB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QACrC,IAAI,SAAS,CAAC,OAAO,KAAK,IAAI,IAAI,UAAU,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACL,CAAC;IAED,0EAA0E;IAC1E,MAAM,QAAQ,GAAG,OAAO,EAAE,UAAU,EAAE,SAAS,IAAI,EAAE,CAAC;IACtD,IAAI,OAAO,EAAE,SAAS,KAAK,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,CAAC;QAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,0CAA0C;IAC1C,MAAM,SAAS,GAAG,IAAA,WAAI,EAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;IAClE,IAAI,IAAA,eAAU,EAAC,SAAS,CAAC,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,IAAA,yBAAS,EAAC,SAAS,EAAE,EAAE,EAAE;YACpC,KAAK,EAAE,QAAQ;YACf,QAAQ,EAAE,QAAQ;SACrB,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/D,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/D,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,iDAAiD;IACjD,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,6FAA6F;QACzF,yBAAyB;QACzB,iFAAiF;QACjF,8FAA8F;QAC9F,6DAA6D;QAC7D,2DAA2D;QAC3D,8EAA8E,CACrF,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,IAAI,SAAS,GAAG,EAAE,CAAC;AACnB,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;AACvB,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;AAClC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;IACvC,SAAS,IAAI,KAAK,CAAC;AACvB,CAAC,CAAC,CAAC;AACH,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;IACzB,GAAG,CAAC,SAAS,CAAC,CAAC;AACnB,CAAC,CAAC,CAAC","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: 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.stdout.write(\n 'This project is missing the webpieces AI hook (./node_modules/.bin/wp-ai-hook not found).\\n' +\n 'Ask the human one of:\\n' +\n ' A) Install webpieces: run `npm install @webpieces/ai-hook-rules` then retry\\n' +\n ' B) Skip hooks: ask how long (1 hour / 1 day / forever), then write .webpieces/skiphooks:\\n' +\n ' {\"expires\": <unix epoch seconds>, \"reason\": \"...\"}\\n' +\n ' or for forever: {\"expires\": null, \"reason\": \"...\"}\\n' +\n ' Note: writing .webpieces/skiphooks is ALWAYS allowed even while blocked.\\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"]}
@@ -67,8 +67,10 @@ async function handler(event, _context) {
67
67
  }
68
68
  catch (err) {
69
69
  const error = (0, to_error_1.toError)(err);
70
- console.error(`[ai-hooks] openclaw adapter crashed (failing open): ${error.message}`);
71
- return undefined;
70
+ const msg = err instanceof types_1.InformAiError
71
+ ? error.message
72
+ : `[ai-hooks] openclaw adapter crashed — failing closed: ${error.message}`;
73
+ return new OpenclawHandlerResult('rejected', msg);
72
74
  }
73
75
  }
74
76
  //# sourceMappingURL=openclaw-plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"openclaw-plugin.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/openclaw-plugin.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\nimport * as fs from 'fs';\n\nimport { CONFIG_FILENAME } from '@webpieces/rules-config';\nimport { run } from '../core/runner';\nimport { NormalizedToolInput, NormalizedEdit, ToolKind } from '../core/types';\nimport { toError } from '../core/to-error';\n\ninterface ToolCallEvent {\n toolName: string;\n // webpieces-disable no-any-unknown -- openclaw SDK passes opaque tool arguments\n arguments: Record<string, unknown>;\n}\n\ninterface HookContext {\n // webpieces-disable no-any-unknown -- openclaw SDK context shape is opaque\n [key: string]: unknown;\n}\n\nclass OpenclawHandlerResult {\n readonly status: 'approved' | 'rejected';\n readonly reason: string | undefined;\n\n constructor(status: 'approved' | 'rejected', reason?: string) {\n this.status = status;\n this.reason = reason;\n }\n}\n\nconst TOOL_MAP: Record<string, ToolKind> = {\n 'write': 'Write',\n 'edit': 'Edit',\n};\n\nfunction mapToolName(openclawName: string): ToolKind | null {\n return TOOL_MAP[openclawName] || null;\n}\n\n// webpieces-disable no-any-unknown -- openclaw SDK passes opaque tool arguments\nfunction mapToolInput(toolName: string, args: Record<string, unknown>): NormalizedToolInput | null {\n const filePath = typeof args['path'] === 'string' ? args['path'] as string : null;\n if (!filePath) return null;\n\n if (toolName === 'write') {\n const content = typeof args['content'] === 'string' ? args['content'] as string : '';\n return new NormalizedToolInput(filePath, [new NormalizedEdit('', content)]);\n }\n if (toolName === 'edit') {\n const oldStr = typeof args['old_string'] === 'string' ? args['old_string'] as string : '';\n const newStr = typeof args['new_string'] === 'string' ? args['new_string'] as string : '';\n return new NormalizedToolInput(filePath, [new NormalizedEdit(oldStr, newStr)]);\n }\n return null;\n}\n\nfunction findWorkspaceRoot(filePath: string): string | null {\n let dir = path.dirname(filePath);\n while (true) {\n if (fs.existsSync(path.join(dir, CONFIG_FILENAME))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport default async function handler(\n event: ToolCallEvent,\n _context: HookContext,\n): Promise<OpenclawHandlerResult | undefined> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const toolKind = mapToolName(event.toolName);\n if (!toolKind) return undefined;\n\n const input = mapToolInput(event.toolName, event.arguments);\n if (!input) return undefined;\n\n const wsRoot = findWorkspaceRoot(input.filePath);\n if (!wsRoot) return undefined;\n\n const result = run(toolKind, input, wsRoot);\n if (!result) return new OpenclawHandlerResult('approved');\n return new OpenclawHandlerResult('rejected', result.report);\n } catch (err: unknown) {\n const error = toError(err);\n console.error(`[ai-hooks] openclaw adapter crashed (failing open): ${error.message}`);\n return undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"openclaw-plugin.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/openclaw-plugin.ts"],"names":[],"mappings":";;AAiEA,0BAyBC;;AA1FD,mDAA6B;AAC7B,+CAAyB;AAEzB,0DAA0D;AAC1D,2CAAqC;AACrC,yCAA6F;AAC7F,+CAA2C;AAa3C,MAAM,qBAAqB;IAIvB,YAAY,MAA+B,EAAE,MAAe;QACxD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;CACJ;AAED,MAAM,QAAQ,GAA6B;IACvC,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,MAAM;CACjB,CAAC;AAEF,SAAS,WAAW,CAAC,YAAoB;IACrC,OAAO,QAAQ,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC;AAC1C,CAAC;AAED,gFAAgF;AAChF,SAAS,YAAY,CAAC,QAAgB,EAAE,IAA6B;IACjE,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAW,CAAC,CAAC,CAAC,IAAI,CAAC;IAClF,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QACrF,OAAO,IAAI,2BAAmB,CAAC,QAAQ,EAAE,CAAC,IAAI,sBAAc,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1F,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1F,OAAO,IAAI,2BAAmB,CAAC,QAAQ,EAAE,CAAC,IAAI,sBAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB;IACvC,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjC,OAAO,IAAI,EAAE,CAAC;QACV,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,8BAAe,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAChC,GAAG,GAAG,MAAM,CAAC;IACjB,CAAC;AACL,CAAC;AAEc,KAAK,UAAU,OAAO,CACjC,KAAoB,EACpB,QAAqB;IAErB,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QAEhC,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAE7B,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QAE9B,MAAM,MAAM,GAAG,IAAA,YAAG,EAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,qBAAqB,CAAC,UAAU,CAAC,CAAC;QAC1D,OAAO,IAAI,qBAAqB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAChE,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,GAAG,GAAG,GAAG,YAAY,qBAAa;YACpC,CAAC,CAAC,KAAK,CAAC,OAAO;YACf,CAAC,CAAC,yDAAyD,KAAK,CAAC,OAAO,EAAE,CAAC;QAC/E,OAAO,IAAI,qBAAqB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACtD,CAAC;AACL,CAAC","sourcesContent":["import * as path from 'path';\nimport * as fs from 'fs';\n\nimport { CONFIG_FILENAME } from '@webpieces/rules-config';\nimport { run } from '../core/runner';\nimport { NormalizedToolInput, NormalizedEdit, ToolKind, InformAiError } from '../core/types';\nimport { toError } from '../core/to-error';\n\ninterface ToolCallEvent {\n toolName: string;\n // webpieces-disable no-any-unknown -- openclaw SDK passes opaque tool arguments\n arguments: Record<string, unknown>;\n}\n\ninterface HookContext {\n // webpieces-disable no-any-unknown -- openclaw SDK context shape is opaque\n [key: string]: unknown;\n}\n\nclass OpenclawHandlerResult {\n readonly status: 'approved' | 'rejected';\n readonly reason: string | undefined;\n\n constructor(status: 'approved' | 'rejected', reason?: string) {\n this.status = status;\n this.reason = reason;\n }\n}\n\nconst TOOL_MAP: Record<string, ToolKind> = {\n 'write': 'Write',\n 'edit': 'Edit',\n};\n\nfunction mapToolName(openclawName: string): ToolKind | null {\n return TOOL_MAP[openclawName] || null;\n}\n\n// webpieces-disable no-any-unknown -- openclaw SDK passes opaque tool arguments\nfunction mapToolInput(toolName: string, args: Record<string, unknown>): NormalizedToolInput | null {\n const filePath = typeof args['path'] === 'string' ? args['path'] as string : null;\n if (!filePath) return null;\n\n if (toolName === 'write') {\n const content = typeof args['content'] === 'string' ? args['content'] as string : '';\n return new NormalizedToolInput(filePath, [new NormalizedEdit('', content)]);\n }\n if (toolName === 'edit') {\n const oldStr = typeof args['old_string'] === 'string' ? args['old_string'] as string : '';\n const newStr = typeof args['new_string'] === 'string' ? args['new_string'] as string : '';\n return new NormalizedToolInput(filePath, [new NormalizedEdit(oldStr, newStr)]);\n }\n return null;\n}\n\nfunction findWorkspaceRoot(filePath: string): string | null {\n let dir = path.dirname(filePath);\n while (true) {\n if (fs.existsSync(path.join(dir, CONFIG_FILENAME))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport default async function handler(\n event: ToolCallEvent,\n _context: HookContext,\n): Promise<OpenclawHandlerResult | undefined> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const toolKind = mapToolName(event.toolName);\n if (!toolKind) return undefined;\n\n const input = mapToolInput(event.toolName, event.arguments);\n if (!input) return undefined;\n\n const wsRoot = findWorkspaceRoot(input.filePath);\n if (!wsRoot) return undefined;\n\n const result = run(toolKind, input, wsRoot);\n if (!result) return new OpenclawHandlerResult('approved');\n return new OpenclawHandlerResult('rejected', result.report);\n } catch (err: unknown) {\n const error = toError(err);\n const msg = err instanceof InformAiError\n ? error.message\n : `[ai-hooks] openclaw adapter crashed failing closed: ${error.message}`;\n return new OpenclawHandlerResult('rejected', msg);\n }\n}\n"]}
@@ -1,2 +1 @@
1
- #!/usr/bin/env node
2
1
  export declare function main(): void;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ const fs_1 = require("fs");
5
+ const os_1 = require("os");
6
+ const path_1 = require("path");
7
+ function main() {
8
+ const cwd = process.cwd();
9
+ const distHookPath = (0, path_1.join)(cwd, 'dist', 'packages', 'tooling', 'ai-hook-rules', 'src', 'adapters', 'claude-code-hook.js');
10
+ if (!(0, fs_1.existsSync)(distHookPath)) {
11
+ console.error(`[wp-dev-hook-install] Local build not found at: ${distHookPath}`);
12
+ console.error(' Run `nx build ai-hook-rules` first.');
13
+ process.exit(1);
14
+ }
15
+ const homeDir = (0, os_1.homedir)();
16
+ const webpiecesDir = (0, path_1.join)(homeDir, '.webpieces');
17
+ const backupPath = (0, path_1.join)(webpiecesDir, 'dev-hook-backup.json');
18
+ const claudeSettingsPath = (0, path_1.join)(homeDir, '.claude', 'settings.json');
19
+ if ((0, fs_1.existsSync)(backupPath)) {
20
+ console.error('[wp-dev-hook-install] Dev hook is already installed (backup file exists).');
21
+ console.error(' Run `wp-dev-hook-uninstall` first to remove the current dev hook.');
22
+ process.exit(1);
23
+ }
24
+ let settings = {};
25
+ if ((0, fs_1.existsSync)(claudeSettingsPath)) {
26
+ settings = JSON.parse((0, fs_1.readFileSync)(claudeSettingsPath, 'utf8'));
27
+ }
28
+ // Save whatever hooks exist now (may be undefined/null) so uninstall can restore them
29
+ const backup = { previousHooks: settings.hooks ?? null };
30
+ if (!(0, fs_1.existsSync)(webpiecesDir)) {
31
+ (0, fs_1.mkdirSync)(webpiecesDir, { recursive: true });
32
+ }
33
+ (0, fs_1.writeFileSync)(backupPath, JSON.stringify(backup, null, 4) + '\n');
34
+ // Replace hooks with a single entry pointing at the local dist build
35
+ const hookCommand = `node ${distHookPath}`;
36
+ settings.hooks = {
37
+ PreToolUse: [
38
+ {
39
+ matcher: 'Write|Edit|MultiEdit|Bash',
40
+ hooks: [{ type: 'command', command: hookCommand }],
41
+ },
42
+ ],
43
+ };
44
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(claudeSettingsPath), { recursive: true });
45
+ (0, fs_1.writeFileSync)(claudeSettingsPath, JSON.stringify(settings, null, 4) + '\n');
46
+ console.log(` Dev hook installed → ${hookCommand}`);
47
+ console.log('');
48
+ console.log(' RESTART Claude Code to activate the local build hook.');
49
+ console.log(' Run `wp-dev-hook-uninstall` when done testing.');
50
+ }
51
+ if (require.main === module) {
52
+ main();
53
+ }
54
+ //# sourceMappingURL=dev-hook-install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-hook-install.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/dev-hook-install.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { homedir } from 'os';\nimport { join, dirname } from 'path';\n\ninterface HookEntry {\n matcher: string;\n hooks: Array<{ type: string; command: string }>;\n}\n\ninterface ClaudeSettings {\n hooks?: {\n PreToolUse?: HookEntry[];\n };\n // webpieces-disable no-any-unknown -- opaque settings bag; arbitrary keys allowed\n [key: string]: unknown;\n}\n\ninterface DevHookBackup {\n previousHooks: ClaudeSettings['hooks'] | null;\n}\n\nexport function main(): void {\n const cwd = process.cwd();\n const distHookPath = join(cwd, 'dist', 'packages', 'tooling', 'ai-hook-rules', 'src', 'adapters', 'claude-code-hook.js');\n\n if (!existsSync(distHookPath)) {\n console.error(`[wp-dev-hook-install] Local build not found at: ${distHookPath}`);\n console.error(' Run `nx build ai-hook-rules` first.');\n process.exit(1);\n }\n\n const homeDir = homedir();\n const webpiecesDir = join(homeDir, '.webpieces');\n const backupPath = join(webpiecesDir, 'dev-hook-backup.json');\n const claudeSettingsPath = join(homeDir, '.claude', 'settings.json');\n\n if (existsSync(backupPath)) {\n console.error('[wp-dev-hook-install] Dev hook is already installed (backup file exists).');\n console.error(' Run `wp-dev-hook-uninstall` first to remove the current dev hook.');\n process.exit(1);\n }\n\n let settings: ClaudeSettings = {};\n if (existsSync(claudeSettingsPath)) {\n settings = JSON.parse(readFileSync(claudeSettingsPath, 'utf8')) as ClaudeSettings;\n }\n\n // Save whatever hooks exist now (may be undefined/null) so uninstall can restore them\n const backup: DevHookBackup = { previousHooks: settings.hooks ?? null };\n if (!existsSync(webpiecesDir)) {\n mkdirSync(webpiecesDir, { recursive: true });\n }\n writeFileSync(backupPath, JSON.stringify(backup, null, 4) + '\\n');\n\n // Replace hooks with a single entry pointing at the local dist build\n const hookCommand = `node ${distHookPath}`;\n settings.hooks = {\n PreToolUse: [\n {\n matcher: 'Write|Edit|MultiEdit|Bash',\n hooks: [{ type: 'command', command: hookCommand }],\n },\n ],\n };\n\n mkdirSync(dirname(claudeSettingsPath), { recursive: true });\n writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 4) + '\\n');\n\n console.log(` Dev hook installed → ${hookCommand}`);\n console.log('');\n console.log(' RESTART Claude Code to activate the local build hook.');\n console.log(' Run `wp-dev-hook-uninstall` when done testing.');\n}\n\nif (require.main === module) {\n main();\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function main(): void;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ const fs_1 = require("fs");
5
+ const os_1 = require("os");
6
+ const path_1 = require("path");
7
+ function main() {
8
+ const homeDir = (0, os_1.homedir)();
9
+ const backupPath = (0, path_1.join)(homeDir, '.webpieces', 'dev-hook-backup.json');
10
+ const claudeSettingsPath = (0, path_1.join)(homeDir, '.claude', 'settings.json');
11
+ if (!(0, fs_1.existsSync)(backupPath)) {
12
+ console.error('[wp-dev-hook-uninstall] No dev hook backup found — dev hook was not installed.');
13
+ process.exit(1);
14
+ }
15
+ const backup = JSON.parse((0, fs_1.readFileSync)(backupPath, 'utf8'));
16
+ let settings = {};
17
+ if ((0, fs_1.existsSync)(claudeSettingsPath)) {
18
+ settings = JSON.parse((0, fs_1.readFileSync)(claudeSettingsPath, 'utf8'));
19
+ }
20
+ if (backup.previousHooks === null) {
21
+ delete settings.hooks;
22
+ }
23
+ else {
24
+ settings.hooks = backup.previousHooks;
25
+ }
26
+ (0, fs_1.writeFileSync)(claudeSettingsPath, JSON.stringify(settings, null, 4) + '\n');
27
+ (0, fs_1.rmSync)(backupPath);
28
+ console.log(' Dev hook removed. Previous hook configuration restored.');
29
+ console.log(' RESTART Claude Code to return to normal hook.');
30
+ }
31
+ if (require.main === module) {
32
+ main();
33
+ }
34
+ //# sourceMappingURL=dev-hook-uninstall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-hook-uninstall.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/dev-hook-uninstall.ts"],"names":[],"mappings":";;AAqBA,oBA4BC;AAjDD,2BAAqE;AACrE,2BAA6B;AAC7B,+BAA4B;AAmB5B,SAAgB,IAAI;IAChB,MAAM,OAAO,GAAG,IAAA,YAAO,GAAE,CAAC;IAC1B,MAAM,UAAU,GAAG,IAAA,WAAI,EAAC,OAAO,EAAE,YAAY,EAAE,sBAAsB,CAAC,CAAC;IACvE,MAAM,kBAAkB,GAAG,IAAA,WAAI,EAAC,OAAO,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IAErE,IAAI,CAAC,IAAA,eAAU,EAAC,UAAU,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,gFAAgF,CAAC,CAAC;QAChG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,UAAU,EAAE,MAAM,CAAC,CAAkB,CAAC;IAE7E,IAAI,QAAQ,GAAmB,EAAE,CAAC;IAClC,IAAI,IAAA,eAAU,EAAC,kBAAkB,CAAC,EAAE,CAAC;QACjC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,kBAAkB,EAAE,MAAM,CAAC,CAAmB,CAAC;IACtF,CAAC;IAED,IAAI,MAAM,CAAC,aAAa,KAAK,IAAI,EAAE,CAAC;QAChC,OAAO,QAAQ,CAAC,KAAK,CAAC;IAC1B,CAAC;SAAM,CAAC;QACJ,QAAQ,CAAC,KAAK,GAAG,MAAM,CAAC,aAAa,CAAC;IAC1C,CAAC;IAED,IAAA,kBAAa,EAAC,kBAAkB,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5E,IAAA,WAAM,EAAC,UAAU,CAAC,CAAC;IAEnB,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;AACnE,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,CAAC;AACX,CAAC","sourcesContent":["import { existsSync, readFileSync, writeFileSync, rmSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\n\ninterface HookEntry {\n matcher: string;\n hooks: Array<{ type: string; command: string }>;\n}\n\ninterface ClaudeSettings {\n hooks?: {\n PreToolUse?: HookEntry[];\n };\n // webpieces-disable no-any-unknown -- opaque settings bag; arbitrary keys allowed\n [key: string]: unknown;\n}\n\ninterface DevHookBackup {\n previousHooks: ClaudeSettings['hooks'] | null;\n}\n\nexport function main(): void {\n const homeDir = homedir();\n const backupPath = join(homeDir, '.webpieces', 'dev-hook-backup.json');\n const claudeSettingsPath = join(homeDir, '.claude', 'settings.json');\n\n if (!existsSync(backupPath)) {\n console.error('[wp-dev-hook-uninstall] No dev hook backup found — dev hook was not installed.');\n process.exit(1);\n }\n\n const backup = JSON.parse(readFileSync(backupPath, 'utf8')) as DevHookBackup;\n\n let settings: ClaudeSettings = {};\n if (existsSync(claudeSettingsPath)) {\n settings = JSON.parse(readFileSync(claudeSettingsPath, 'utf8')) as ClaudeSettings;\n }\n\n if (backup.previousHooks === null) {\n delete settings.hooks;\n } else {\n settings.hooks = backup.previousHooks;\n }\n\n writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 4) + '\\n');\n rmSync(backupPath);\n\n console.log(' Dev hook removed. Previous hook configuration restored.');\n console.log(' RESTART Claude Code to return to normal hook.');\n}\n\nif (require.main === module) {\n main();\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function main(): void;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ const fs_1 = require("fs");
5
+ const os_1 = require("os");
6
+ const path_1 = require("path");
7
+ function main() {
8
+ const homeDir = (0, os_1.homedir)();
9
+ const webpiecesDir = (0, path_1.join)(homeDir, '.webpieces');
10
+ const globalHookDest = (0, path_1.join)(webpiecesDir, 'global-hook.js');
11
+ const claudeSettingsPath = (0, path_1.join)(homeDir, '.claude', 'settings.json');
12
+ // Find compiled global-hook.js relative to this compiled file (src/bin/ → src/adapters/)
13
+ const shimSource = (0, path_1.join)(__dirname, '..', 'adapters', 'global-hook.js');
14
+ if (!(0, fs_1.existsSync)(shimSource)) {
15
+ console.error(`[wp-setup-global-ai-hooks] Cannot find compiled hook at: ${shimSource}`);
16
+ console.error(' Make sure the package is built first.');
17
+ process.exit(1);
18
+ }
19
+ // Create ~/.webpieces/ if needed
20
+ if (!(0, fs_1.existsSync)(webpiecesDir)) {
21
+ (0, fs_1.mkdirSync)(webpiecesDir, { recursive: true });
22
+ }
23
+ (0, fs_1.copyFileSync)(shimSource, globalHookDest);
24
+ console.log(` Installed global hook → ${globalHookDest}`);
25
+ // Wire into ~/.claude/settings.json using absolute path (~ is not expanded by Claude Code)
26
+ const hookCommand = `node ${globalHookDest}`;
27
+ let settings = {};
28
+ if ((0, fs_1.existsSync)(claudeSettingsPath)) {
29
+ settings = JSON.parse((0, fs_1.readFileSync)(claudeSettingsPath, 'utf8'));
30
+ }
31
+ if (!settings.hooks)
32
+ settings.hooks = {};
33
+ if (!Array.isArray(settings.hooks.PreToolUse))
34
+ settings.hooks.PreToolUse = [];
35
+ const alreadyWired = settings.hooks.PreToolUse.some((e) => e.hooks.some((h) => h.command.includes('global-hook.js')));
36
+ if (alreadyWired) {
37
+ console.log(' ~/.claude/settings.json already has the global hook — skipping.');
38
+ }
39
+ else {
40
+ settings.hooks.PreToolUse.push({
41
+ matcher: 'Write|Edit|MultiEdit|Bash',
42
+ hooks: [{ type: 'command', command: hookCommand }],
43
+ });
44
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(claudeSettingsPath), { recursive: true });
45
+ (0, fs_1.writeFileSync)(claudeSettingsPath, JSON.stringify(settings, null, 4) + '\n');
46
+ console.log(` Wired global hook into ~/.claude/settings.json`);
47
+ }
48
+ console.log('');
49
+ console.log('✅ Global webpieces hook installed.');
50
+ console.log(' IMPORTANT: Remove any per-project hook entries from .claude/settings.json files.');
51
+ console.log(' The global hook delegates to each repo\'s ./node_modules/.bin/wp-ai-hook automatically.');
52
+ console.log('');
53
+ console.log(' If a project lacks webpieces, the hook will ask AI to warn you and offer:');
54
+ console.log(' A) Install webpieces in that project');
55
+ console.log(' B) Write a .skiphooks file to bypass temporarily or forever');
56
+ }
57
+ if (require.main === module) {
58
+ main();
59
+ }
60
+ //# sourceMappingURL=global-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-setup.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/global-setup.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync } from 'fs';\nimport { homedir } from 'os';\nimport { join, dirname } from 'path';\n\ninterface HookEntry {\n matcher: string;\n hooks: Array<{ type: string; command: string }>;\n}\n\ninterface ClaudeSettings {\n hooks?: {\n PreToolUse?: HookEntry[];\n };\n // webpieces-disable no-any-unknown -- opaque settings bag; arbitrary keys allowed\n [key: string]: unknown;\n}\n\nexport function main(): void {\n const homeDir = homedir();\n const webpiecesDir = join(homeDir, '.webpieces');\n const globalHookDest = join(webpiecesDir, 'global-hook.js');\n const claudeSettingsPath = join(homeDir, '.claude', 'settings.json');\n\n // Find compiled global-hook.js relative to this compiled file (src/bin/ → src/adapters/)\n const shimSource = join(__dirname, '..', 'adapters', 'global-hook.js');\n\n if (!existsSync(shimSource)) {\n console.error(`[wp-setup-global-ai-hooks] Cannot find compiled hook at: ${shimSource}`);\n console.error(' Make sure the package is built first.');\n process.exit(1);\n }\n\n // Create ~/.webpieces/ if needed\n if (!existsSync(webpiecesDir)) {\n mkdirSync(webpiecesDir, { recursive: true });\n }\n\n copyFileSync(shimSource, globalHookDest);\n console.log(` Installed global hook → ${globalHookDest}`);\n\n // Wire into ~/.claude/settings.json using absolute path (~ is not expanded by Claude Code)\n const hookCommand = `node ${globalHookDest}`;\n\n let settings: ClaudeSettings = {};\n if (existsSync(claudeSettingsPath)) {\n settings = JSON.parse(readFileSync(claudeSettingsPath, 'utf8')) as ClaudeSettings;\n }\n\n if (!settings.hooks) settings.hooks = {};\n if (!Array.isArray(settings.hooks.PreToolUse)) settings.hooks.PreToolUse = [];\n\n const alreadyWired = settings.hooks.PreToolUse.some((e) =>\n e.hooks.some((h) => h.command.includes('global-hook.js')),\n );\n\n if (alreadyWired) {\n console.log(' ~/.claude/settings.json already has the global hook — skipping.');\n } else {\n settings.hooks.PreToolUse.push({\n matcher: 'Write|Edit|MultiEdit|Bash',\n hooks: [{ type: 'command', command: hookCommand }],\n });\n mkdirSync(dirname(claudeSettingsPath), { recursive: true });\n writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 4) + '\\n');\n console.log(` Wired global hook into ~/.claude/settings.json`);\n }\n\n console.log('');\n console.log('✅ Global webpieces hook installed.');\n console.log(' IMPORTANT: Remove any per-project hook entries from .claude/settings.json files.');\n console.log(' The global hook delegates to each repo\\'s ./node_modules/.bin/wp-ai-hook automatically.');\n console.log('');\n console.log(' If a project lacks webpieces, the hook will ask AI to warn you and offer:');\n console.log(' A) Install webpieces in that project');\n console.log(' B) Write a .skiphooks file to bypass temporarily or forever');\n}\n\nif (require.main === module) {\n main();\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function main(): void;
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const index_1 = require("../core/rules/index");
8
+ const HOOK_COMMAND = './node_modules/.bin/wp-ai-hook';
9
+ const CONFIG_FILENAME = 'webpieces.config.json';
10
+ function settingsAlreadyHasHook(settingsPath) {
11
+ if (!fs.existsSync(settingsPath))
12
+ return false;
13
+ const content = fs.readFileSync(settingsPath, 'utf8');
14
+ return content.includes('wp-ai-hook');
15
+ }
16
+ function wireSettings(projectRoot) {
17
+ const claudeDir = path.join(projectRoot, '.claude');
18
+ if (!fs.existsSync(claudeDir)) {
19
+ console.log(' [ai-hook-rules] No .claude/ directory found — add the hook manually:');
20
+ console.log(' In .claude/settings.json under hooks.PreToolUse, add:');
21
+ console.log(` { "matcher": "Write|Edit|MultiEdit|Bash", "hooks": [{ "type": "command", "command": "${HOOK_COMMAND}" }] }`);
22
+ return;
23
+ }
24
+ const settingsPath = path.join(claudeDir, 'settings.json');
25
+ if (settingsAlreadyHasHook(settingsPath)) {
26
+ console.log(' [ai-hook-rules] .claude/settings.json already has the hook — skipping.');
27
+ return;
28
+ }
29
+ let settings = {};
30
+ if (fs.existsSync(settingsPath)) {
31
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
32
+ }
33
+ if (!settings.hooks)
34
+ settings.hooks = {};
35
+ if (!Array.isArray(settings.hooks.PreToolUse))
36
+ settings.hooks.PreToolUse = [];
37
+ settings.hooks.PreToolUse.push({
38
+ matcher: 'Write|Edit|MultiEdit|Bash',
39
+ hooks: [{ type: 'command', command: HOOK_COMMAND }],
40
+ });
41
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 4) + '\n');
42
+ console.log(` [ai-hook-rules] Wired ${HOOK_COMMAND} into .claude/settings.json`);
43
+ }
44
+ function seedConfig(projectRoot) {
45
+ const configPath = path.join(projectRoot, CONFIG_FILENAME);
46
+ if (fs.existsSync(configPath)) {
47
+ console.log(` [ai-hook-rules] ${CONFIG_FILENAME} already exists — run with --sync to add missing rules.`);
48
+ return;
49
+ }
50
+ const rules = {};
51
+ for (const name of index_1.builtInRuleNames) {
52
+ rules[name] = { mode: 'OFF' };
53
+ }
54
+ const config = { rules, rulesDir: [] };
55
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 4) + '\n');
56
+ console.log(` [ai-hook-rules] Created ${CONFIG_FILENAME} with all rules set to OFF.`);
57
+ console.log(' Review and enable the rules you want by changing "mode" to "ON" or "MODIFIED_CODE" etc.');
58
+ }
59
+ function syncConfig(projectRoot) {
60
+ const configPath = path.join(projectRoot, CONFIG_FILENAME);
61
+ let config = { rules: {}, rulesDir: [] };
62
+ if (fs.existsSync(configPath)) {
63
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
64
+ }
65
+ if (!config.rules)
66
+ config.rules = {};
67
+ const added = [];
68
+ for (const name of index_1.builtInRuleNames) {
69
+ if (!Object.prototype.hasOwnProperty.call(config.rules, name)) {
70
+ config.rules[name] = { mode: 'OFF' };
71
+ added.push(name);
72
+ }
73
+ }
74
+ if (added.length === 0) {
75
+ console.log(` [ai-hook-rules] ${CONFIG_FILENAME} is already up to date — no new rules to add.`);
76
+ return;
77
+ }
78
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 4) + '\n');
79
+ console.log(` [ai-hook-rules] Added ${added.length} new rule(s) to ${CONFIG_FILENAME} (set to OFF):`);
80
+ for (const name of added) {
81
+ console.log(` - ${name}`);
82
+ }
83
+ console.log(' Review each new rule and set "mode" to ON/MODIFIED_CODE/etc. as desired.');
84
+ }
85
+ function main() {
86
+ const args = process.argv.slice(2);
87
+ const isSync = args.includes('--sync');
88
+ const projectRoot = process.cwd();
89
+ if (isSync) {
90
+ syncConfig(projectRoot);
91
+ }
92
+ else {
93
+ seedConfig(projectRoot);
94
+ console.log('');
95
+ console.log(' To install the global Claude Code hook (one-time, per machine):');
96
+ console.log(' ./node_modules/.bin/wp-setup-global-ai-hooks');
97
+ }
98
+ }
99
+ if (require.main === module) {
100
+ main();
101
+ }
102
+ //# sourceMappingURL=setup.js.map