@webpieces/ai-hook-rules 0.3.191 → 0.3.192

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.
@@ -1,114 +0,0 @@
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
- /**
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
- }
26
- function readSkipHooks(cwd) {
27
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
28
- try {
29
- const skipPath = (0, path_1.join)(cwd, '.webpieces', 'skiphooks');
30
- if (!(0, fs_1.existsSync)(skipPath))
31
- return null;
32
- return JSON.parse((0, fs_1.readFileSync)(skipPath, 'utf8'));
33
- }
34
- catch (err) {
35
- // eslint-disable-next-line @webpieces/catch-error-pattern -- intentionally discard; malformed .skiphooks must not crash global hook
36
- return null;
37
- }
38
- }
39
- function run(rawInput) {
40
- const cwd = findRepoRoot(process.cwd());
41
- let payload = null;
42
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
43
- try {
44
- if (rawInput.trim()) {
45
- payload = JSON.parse(rawInput);
46
- }
47
- }
48
- catch (err) {
49
- //const error = toError(err); -- malformed stdin is intentionally ignored; must not crash global hook
50
- }
51
- // 1. Check .skiphooks — if valid and not expired, allow everything
52
- const skipHooks = readSkipHooks(cwd);
53
- if (skipHooks !== null) {
54
- const nowSeconds = Date.now() / 1000;
55
- if (skipHooks.expires === null || nowSeconds < skipHooks.expires) {
56
- process.exit(0);
57
- }
58
- }
59
- // 2. Always allow writing .webpieces/skiphooks (this is the escape hatch)
60
- const filePath = payload?.tool_input?.file_path ?? '';
61
- if (payload?.tool_name === 'Write' && filePath.endsWith('.webpieces/skiphooks')) {
62
- process.exit(0);
63
- }
64
- // 3. Delegate to per-repo hook if present
65
- const localHook = (0, path_1.join)(cwd, 'node_modules', '.bin', 'wp-ai-hook');
66
- if ((0, fs_1.existsSync)(localHook)) {
67
- const result = (0, child_process_1.spawnSync)(localHook, [], {
68
- input: Buffer.from(rawInput),
69
- encoding: 'buffer',
70
- cwd,
71
- });
72
- if (result.stdout?.length)
73
- process.stdout.write(result.stdout);
74
- if (result.stderr?.length)
75
- process.stderr.write(result.stderr);
76
- process.exit(result.status ?? 0);
77
- }
78
- // 4. No local hook found — block and instruct AI
79
- process.stderr.write('STOP. You MUST ask the human to run one of 3 commands to continue.\n' +
80
- '\n' +
81
- 'This project is missing the webpieces AI hook (./node_modules/.bin/wp-ai-hook not found).\n' +
82
- '\n' +
83
- 'Present these THREE options to the human verbatim and wait for them to pick one.\n' +
84
- 'Each option is a command the human can copy-paste into their own terminal:\n' +
85
- '\n' +
86
- ' A) PREFERRED — install the webpieces rules bundle (one package: hook + all rules + pr-gate).\n' +
87
- ' If this is a pnpm workspace, run from the monorepo root:\n' +
88
- ' pnpm add -Dw @webpieces/nx-webpieces-rules\n' +
89
- ' Otherwise, run from the project root:\n' +
90
- ' npm install --save-dev @webpieces/nx-webpieces-rules\n' +
91
- '\n' +
92
- ' B) Skip hooks temporarily (auto-expires). Ask the human how long, then have them run\n' +
93
- ' ONE of these from the project root:\n' +
94
- ' 1 hour: mkdir -p .webpieces && printf \'{"expires": %s, "reason": "temporary skip"}\' "$(($(date +%s) + 3600))" > .webpieces/skiphooks\n' +
95
- ' 1 week: mkdir -p .webpieces && printf \'{"expires": %s, "reason": "temporary skip"}\' "$(($(date +%s) + 604800))" > .webpieces/skiphooks\n' +
96
- '\n' +
97
- ' C) Disable permanently (NEVER expires — hooks stay off until this file is deleted).\n' +
98
- ' Only choose this if the human explicitly wants it. Have them run from the project root:\n' +
99
- ' mkdir -p .webpieces && printf \'{"expires": null, "reason": "permanently disabled"}\' > .webpieces/skiphooks\n' +
100
- ' To re-enable later: rm .webpieces/skiphooks\n' +
101
- '\n' +
102
- 'You are BLOCKED. Ask the human now and wait for their response.\n');
103
- process.exit(2);
104
- }
105
- let stdinData = '';
106
- process.stdin.resume();
107
- process.stdin.setEncoding('utf8');
108
- process.stdin.on('data', (chunk) => {
109
- stdinData += chunk;
110
- });
111
- process.stdin.on('end', () => {
112
- run(stdinData);
113
- });
114
- //# sourceMappingURL=global-hook.js.map
@@ -1 +0,0 @@
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 webpieces rules bundle (one package: hook + all rules + pr-gate).\\n' +\n ' If this is a pnpm workspace, run from the monorepo root:\\n' +\n ' pnpm add -Dw @webpieces/nx-webpieces-rules\\n' +\n ' Otherwise, run from the project root:\\n' +\n ' npm install --save-dev @webpieces/nx-webpieces-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,85 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.main = main;
4
- const fs_1 = require("fs");
5
- const readline_1 = require("readline");
6
- const os_1 = require("os");
7
- const path_1 = require("path");
8
- function prompt(question) {
9
- return new Promise((resolve) => {
10
- const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
11
- rl.question(question, (answer) => {
12
- rl.close();
13
- resolve(answer.trim().toLowerCase());
14
- });
15
- });
16
- }
17
- function isWired(settings) {
18
- return (settings.hooks?.PreToolUse ?? []).some((e) => e.hooks.some((h) => h.command.includes('global-hook.js')));
19
- }
20
- function installHook(settings, shimSource, globalHookDest, claudeSettingsPath) {
21
- (0, fs_1.mkdirSync)((0, path_1.dirname)(globalHookDest), { recursive: true });
22
- (0, fs_1.copyFileSync)(shimSource, globalHookDest);
23
- const hookCommand = `node ${globalHookDest}`;
24
- if (!settings.hooks)
25
- settings.hooks = {};
26
- if (!Array.isArray(settings.hooks.PreToolUse))
27
- settings.hooks.PreToolUse = [];
28
- settings.hooks.PreToolUse.push({
29
- matcher: 'Write|Edit|MultiEdit|Bash',
30
- hooks: [{ type: 'command', command: hookCommand }],
31
- });
32
- (0, fs_1.mkdirSync)((0, path_1.dirname)(claudeSettingsPath), { recursive: true });
33
- (0, fs_1.writeFileSync)(claudeSettingsPath, JSON.stringify(settings, null, 4) + '\n');
34
- console.log(` Installed global hook → ${globalHookDest}`);
35
- console.log(` Wired into ~/.claude/settings.json`);
36
- console.log('');
37
- console.log('✅ Global webpieces hook installed.');
38
- console.log(' The global hook delegates to each repo\'s ./node_modules/.bin/wp-ai-hook automatically.');
39
- }
40
- function uninstallHook(settings, globalHookDest, claudeSettingsPath) {
41
- if (settings.hooks?.PreToolUse) {
42
- settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((e) => !e.hooks.some((h) => h.command.includes('global-hook.js')));
43
- }
44
- (0, fs_1.writeFileSync)(claudeSettingsPath, JSON.stringify(settings, null, 4) + '\n');
45
- if ((0, fs_1.existsSync)(globalHookDest)) {
46
- (0, fs_1.rmSync)(globalHookDest);
47
- }
48
- console.log('✅ Global webpieces hook removed.');
49
- }
50
- async function main() {
51
- const homeDir = (0, os_1.homedir)();
52
- const globalHookDest = (0, path_1.join)(homeDir, '.webpieces', 'global-hook.js');
53
- const claudeSettingsPath = (0, path_1.join)(homeDir, '.claude', 'settings.json');
54
- const shimSource = (0, path_1.join)(__dirname, '..', 'adapters', 'global-hook.js');
55
- if (!(0, fs_1.existsSync)(shimSource)) {
56
- console.error(`[wp-setup-global-ai-hooks] Cannot find compiled hook at: ${shimSource}`);
57
- process.exit(1);
58
- }
59
- let settings = {};
60
- if ((0, fs_1.existsSync)(claudeSettingsPath)) {
61
- settings = JSON.parse((0, fs_1.readFileSync)(claudeSettingsPath, 'utf8'));
62
- }
63
- if (isWired(settings)) {
64
- const answer = await prompt('Global hook is already installed. Uninstall? [y/N]: ');
65
- if (answer === 'y') {
66
- uninstallHook(settings, globalHookDest, claudeSettingsPath);
67
- }
68
- else {
69
- console.log(' No changes made.');
70
- }
71
- }
72
- else {
73
- const answer = await prompt('Install global webpieces hook into ~/.claude/settings.json? [Y/n]: ');
74
- if (answer !== 'n') {
75
- installHook(settings, shimSource, globalHookDest, claudeSettingsPath);
76
- }
77
- else {
78
- console.log(' No changes made.');
79
- }
80
- }
81
- }
82
- if (require.main === module) {
83
- void main();
84
- }
85
- //# sourceMappingURL=global-setup.js.map
@@ -1 +0,0 @@
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, rmSync } from 'fs';\nimport { createInterface } from 'readline';\nimport { homedir } from 'os';\nimport { join, dirname } from 'path';\n\ninterface HookCommand {\n type: string;\n command: string;\n}\n\ninterface HookEntry {\n matcher: string;\n hooks: Array<HookCommand>;\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\nfunction prompt(question: string): Promise<string> {\n return new Promise((resolve: (answer: string) => void) => {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n rl.question(question, (answer: string) => {\n rl.close();\n resolve(answer.trim().toLowerCase());\n });\n });\n}\n\nfunction isWired(settings: ClaudeSettings): boolean {\n return (settings.hooks?.PreToolUse ?? []).some((e: HookEntry) =>\n e.hooks.some((h: HookCommand) => h.command.includes('global-hook.js')),\n );\n}\n\nfunction installHook(settings: ClaudeSettings, shimSource: string, globalHookDest: string, claudeSettingsPath: string): void {\n mkdirSync(dirname(globalHookDest), { recursive: true });\n copyFileSync(shimSource, globalHookDest);\n\n const hookCommand = `node ${globalHookDest}`;\n if (!settings.hooks) settings.hooks = {};\n if (!Array.isArray(settings.hooks.PreToolUse)) settings.hooks.PreToolUse = [];\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\n console.log(` Installed global hook → ${globalHookDest}`);\n console.log(` Wired into ~/.claude/settings.json`);\n console.log('');\n console.log('✅ Global webpieces hook installed.');\n console.log(' The global hook delegates to each repo\\'s ./node_modules/.bin/wp-ai-hook automatically.');\n}\n\nfunction uninstallHook(settings: ClaudeSettings, globalHookDest: string, claudeSettingsPath: string): void {\n if (settings.hooks?.PreToolUse) {\n settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((e: HookEntry) =>\n !e.hooks.some((h: HookCommand) => h.command.includes('global-hook.js')),\n );\n }\n writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 4) + '\\n');\n if (existsSync(globalHookDest)) {\n rmSync(globalHookDest);\n }\n console.log('✅ Global webpieces hook removed.');\n}\n\nexport async function main(): Promise<void> {\n const homeDir = homedir();\n const globalHookDest = join(homeDir, '.webpieces', 'global-hook.js');\n const claudeSettingsPath = join(homeDir, '.claude', 'settings.json');\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 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 if (isWired(settings)) {\n const answer = await prompt('Global hook is already installed. Uninstall? [y/N]: ');\n if (answer === 'y') {\n uninstallHook(settings, globalHookDest, claudeSettingsPath);\n } else {\n console.log(' No changes made.');\n }\n } else {\n const answer = await prompt('Install global webpieces hook into ~/.claude/settings.json? [Y/n]: ');\n if (answer !== 'n') {\n installHook(settings, shimSource, globalHookDest, claudeSettingsPath);\n } else {\n console.log(' No changes made.');\n }\n }\n}\n\nif (require.main === module) {\n void main();\n}\n"]}