@webpieces/ai-hook-rules 0.3.190 → 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.
- package/bin/wp-ai-guards-hook.js +16 -0
- package/bin/{wp-setup-global-ai-hooks.js → wp-ai-rules-hook.js} +1 -1
- package/package.json +6 -3
- package/src/adapters/claude-code-hook.js +8 -117
- package/src/adapters/claude-code-hook.js.map +1 -1
- package/src/adapters/guards-hook.js +13 -0
- package/src/adapters/guards-hook.js.map +1 -0
- package/src/adapters/hook-core.d.ts +7 -0
- package/src/adapters/hook-core.js +140 -0
- package/src/adapters/hook-core.js.map +1 -0
- package/src/adapters/rules-hook.d.ts +1 -0
- package/src/adapters/rules-hook.js +13 -0
- package/src/adapters/rules-hook.js.map +1 -0
- package/src/bin/setup.d.ts +52 -1
- package/src/bin/setup.js +259 -87
- package/src/bin/setup.js.map +1 -1
- package/src/core/rules/merge-in-progress-guard.d.ts +3 -2
- package/src/core/rules/merge-in-progress-guard.js +18 -11
- package/src/core/rules/merge-in-progress-guard.js.map +1 -1
- package/src/core/rules/pr-creation-guard.js +9 -8
- package/src/core/rules/pr-creation-guard.js.map +1 -1
- package/templates/claude-settings-hook.json +12 -2
- package/templates/webpieces.config.seed.json +17 -1
- package/src/adapters/global-hook.d.ts +0 -1
- package/src/adapters/global-hook.js +0 -114
- package/src/adapters/global-hook.js.map +0 -1
- package/src/bin/global-setup.js +0 -85
- package/src/bin/global-setup.js.map +0 -1
- /package/src/{bin/global-setup.d.ts → adapters/guards-hook.d.ts} +0 -0
|
@@ -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', 'guards-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
|
+
}
|
|
@@ -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', '
|
|
9
|
+
const compiled = path.join(__dirname, '..', 'src', 'adapters', 'rules-hook.js');
|
|
10
10
|
|
|
11
11
|
if (fs.existsSync(compiled)) {
|
|
12
12
|
require(compiled).main();
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/ai-hook-rules",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.192",
|
|
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
7
|
"bin": {
|
|
8
8
|
"wp-ai-hook": "./bin/wp-ai-hook.js",
|
|
9
|
+
"wp-ai-rules-hook": "./bin/wp-ai-rules-hook.js",
|
|
10
|
+
"wp-ai-guards-hook": "./bin/wp-ai-guards-hook.js",
|
|
9
11
|
"wp-setup-ai-hooks": "./bin/wp-setup-ai-hooks.js",
|
|
10
|
-
"wp-setup-global-ai-hooks": "./bin/wp-setup-global-ai-hooks.js",
|
|
11
12
|
"wp-dev-hook-install": "./bin/wp-dev-hook-install.js",
|
|
12
13
|
"wp-dev-hook-uninstall": "./bin/wp-dev-hook-uninstall.js"
|
|
13
14
|
},
|
|
@@ -15,6 +16,8 @@
|
|
|
15
16
|
".": "./src/index.js",
|
|
16
17
|
"./package.json": "./package.json",
|
|
17
18
|
"./claude-code": "./src/adapters/claude-code-hook.js",
|
|
19
|
+
"./claude-code-rules": "./src/adapters/rules-hook.js",
|
|
20
|
+
"./claude-code-guards": "./src/adapters/guards-hook.js",
|
|
18
21
|
"./openclaw-plugin": "./src/adapters/openclaw-plugin.js"
|
|
19
22
|
},
|
|
20
23
|
"files": [
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
"directory": "packages/tooling/ai-hook-rules"
|
|
33
36
|
},
|
|
34
37
|
"dependencies": {
|
|
35
|
-
"@webpieces/rules-config": "0.3.
|
|
38
|
+
"@webpieces/rules-config": "0.3.192"
|
|
36
39
|
},
|
|
37
40
|
"publishConfig": {
|
|
38
41
|
"access": "public"
|
|
@@ -1,124 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.main = main;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const HANDLED_FILE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit']);
|
|
12
|
-
function readStdin() {
|
|
13
|
-
return new Promise((resolve) => {
|
|
14
|
-
let data = '';
|
|
15
|
-
process.stdin.setEncoding('utf8');
|
|
16
|
-
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
17
|
-
process.stdin.on('end', () => resolve(data));
|
|
18
|
-
process.stdin.on('error', () => resolve(''));
|
|
19
|
-
if (process.stdin.isTTY)
|
|
20
|
-
resolve('');
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
function safeParse(raw) {
|
|
24
|
-
if (!raw || raw.trim() === '')
|
|
25
|
-
return null;
|
|
26
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
27
|
-
try {
|
|
28
|
-
return JSON.parse(raw);
|
|
29
|
-
}
|
|
30
|
-
catch (err) {
|
|
31
|
-
const error = (0, to_error_1.toError)(err);
|
|
32
|
-
throw new types_1.InformAiError(`Malformed hook input from Claude Code stdin: ${error.message}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function normalizeToolKind(toolName) {
|
|
36
|
-
if (HANDLED_FILE_TOOLS.has(toolName))
|
|
37
|
-
return toolName;
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
function normalizeToolInput(toolKind, toolInput) {
|
|
41
|
-
const filePath = toolInput.file_path;
|
|
42
|
-
if (!filePath)
|
|
43
|
-
return null;
|
|
44
|
-
if (toolKind === 'Write') {
|
|
45
|
-
return new types_1.NormalizedToolInput(filePath, [
|
|
46
|
-
new types_1.NormalizedEdit('', toolInput.content || ''),
|
|
47
|
-
]);
|
|
48
|
-
}
|
|
49
|
-
if (toolKind === 'Edit') {
|
|
50
|
-
return new types_1.NormalizedToolInput(filePath, [
|
|
51
|
-
new types_1.NormalizedEdit(toolInput.old_string || '', toolInput.new_string || ''),
|
|
52
|
-
]);
|
|
53
|
-
}
|
|
54
|
-
if (toolKind === 'MultiEdit') {
|
|
55
|
-
const raw = Array.isArray(toolInput.edits) ? toolInput.edits : [];
|
|
56
|
-
const edits = raw.map((e) => new types_1.NormalizedEdit(e.old_string || '', e.new_string || ''));
|
|
57
|
-
return new types_1.NormalizedToolInput(filePath, edits);
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
async function main() {
|
|
62
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
63
|
-
try {
|
|
64
|
-
const raw = await readStdin();
|
|
65
|
-
const payload = safeParse(raw);
|
|
66
|
-
if (!payload) {
|
|
67
|
-
process.exit(0);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const cwd = process.cwd();
|
|
71
|
-
if (payload.tool_name === 'Bash') {
|
|
72
|
-
const command = payload.tool_input.command;
|
|
73
|
-
if (!command || command.trim() === '') {
|
|
74
|
-
process.exit(0);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const result = (0, runner_1.runBash)(command, cwd);
|
|
78
|
-
if (!result) {
|
|
79
|
-
process.exit(0);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
process.stderr.write(result.report);
|
|
83
|
-
process.exit(2);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
const toolKind = normalizeToolKind(payload.tool_name);
|
|
87
|
-
if (!toolKind) {
|
|
88
|
-
process.exit(0);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const input = normalizeToolInput(toolKind, payload.tool_input);
|
|
92
|
-
if (!input) {
|
|
93
|
-
process.exit(0);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
// Always allow edits to webpieces.config.json — it's the fix target when the config is broken
|
|
97
|
-
if (path.basename(input.filePath) === load_config_1.CONFIG_FILENAME) {
|
|
98
|
-
process.exit(0);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const result = (0, runner_1.run)(toolKind, input, cwd);
|
|
102
|
-
if (!result) {
|
|
103
|
-
process.exit(0);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
(0, rejection_log_1.logRejection)(toolKind, input, result, cwd);
|
|
107
|
-
process.stderr.write(result.report);
|
|
108
|
-
process.exit(2);
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
const error = (0, to_error_1.toError)(err);
|
|
112
|
-
if (err instanceof types_1.InformAiError) {
|
|
113
|
-
process.stderr.write(error.message + '\n');
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
process.stderr.write(`[ai-hooks] hook crashed unexpectedly — failing closed: ${error.message}\n`);
|
|
117
|
-
}
|
|
118
|
-
process.exit(2);
|
|
119
|
-
}
|
|
4
|
+
// Combined Claude Code PreToolUse adapter (matcher Write|Edit|MultiEdit|Bash) — runs BOTH the
|
|
5
|
+
// code-style rules and the git/PR/branch guards. Kept for the back-compat `wp-ai-hook` bin and the
|
|
6
|
+
// `./claude-code` package export. New installs wire the two split hooks (rules-hook / guards-hook)
|
|
7
|
+
// independently; this combined entry simply runs everything.
|
|
8
|
+
const hook_core_1 = require("./hook-core");
|
|
9
|
+
function main() {
|
|
10
|
+
return (0, hook_core_1.runMain)('all');
|
|
120
11
|
}
|
|
121
12
|
if (require.main === module) {
|
|
122
|
-
main();
|
|
13
|
+
void main();
|
|
123
14
|
}
|
|
124
15
|
//# sourceMappingURL=claude-code-hook.js.map
|
|
@@ -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":";;
|
|
1
|
+
{"version":3,"file":"claude-code-hook.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/claude-code-hook.ts"],"names":[],"mappings":";;AAMA,oBAEC;AARD,8FAA8F;AAC9F,mGAAmG;AACnG,mGAAmG;AACnG,6DAA6D;AAC7D,2CAAsC;AAEtC,SAAgB,IAAI;IAChB,OAAO,IAAA,mBAAO,EAAC,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC1B,KAAK,IAAI,EAAE,CAAC;AAChB,CAAC","sourcesContent":["// Combined Claude Code PreToolUse adapter (matcher Write|Edit|MultiEdit|Bash) — runs BOTH the\n// code-style rules and the git/PR/branch guards. Kept for the back-compat `wp-ai-hook` bin and the\n// `./claude-code` package export. New installs wire the two split hooks (rules-hook / guards-hook)\n// independently; this combined entry simply runs everything.\nimport { runMain } from './hook-core';\n\nexport function main(): Promise<void> {\n return runMain('all');\n}\n\nif (require.main === module) {\n void main();\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.main = main;
|
|
4
|
+
// Claude Code PreToolUse adapter for the GIT/PR/BRANCH GUARDS hook (matcher Bash).
|
|
5
|
+
// File-edit payloads pass through untouched — code-style validation is the separate rules hook.
|
|
6
|
+
const hook_core_1 = require("./hook-core");
|
|
7
|
+
function main() {
|
|
8
|
+
return (0, hook_core_1.runMain)('guards');
|
|
9
|
+
}
|
|
10
|
+
if (require.main === module) {
|
|
11
|
+
void main();
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=guards-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guards-hook.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/guards-hook.ts"],"names":[],"mappings":";;AAIA,oBAEC;AAND,mFAAmF;AACnF,gGAAgG;AAChG,2CAAsC;AAEtC,SAAgB,IAAI;IAChB,OAAO,IAAA,mBAAO,EAAC,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC1B,KAAK,IAAI,EAAE,CAAC;AAChB,CAAC","sourcesContent":["// Claude Code PreToolUse adapter for the GIT/PR/BRANCH GUARDS hook (matcher Bash).\n// File-edit payloads pass through untouched — code-style validation is the separate rules hook.\nimport { runMain } from './hook-core';\n\nexport function main(): Promise<void> {\n return runMain('guards');\n}\n\nif (require.main === module) {\n void main();\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type HookMode = 'rules' | 'guards' | 'all';
|
|
2
|
+
/**
|
|
3
|
+
* Shared entry point for all three Claude Code PreToolUse adapters. `mode` selects which tool kinds
|
|
4
|
+
* to validate; payloads outside the mode's scope pass through (exit 0). Fails CLOSED (exit 2) on any
|
|
5
|
+
* unexpected crash so a broken hook never silently lets an edit through.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runMain(mode: HookMode): Promise<void>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runMain = runMain;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const path = tslib_1.__importStar(require("path"));
|
|
6
|
+
const runner_1 = require("../core/runner");
|
|
7
|
+
const rejection_log_1 = require("../core/rejection-log");
|
|
8
|
+
const load_config_1 = require("../core/load-config");
|
|
9
|
+
const types_1 = require("../core/types");
|
|
10
|
+
const to_error_1 = require("../core/to-error");
|
|
11
|
+
const HANDLED_FILE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit']);
|
|
12
|
+
function readStdin() {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
let data = '';
|
|
15
|
+
process.stdin.setEncoding('utf8');
|
|
16
|
+
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
17
|
+
process.stdin.on('end', () => resolve(data));
|
|
18
|
+
process.stdin.on('error', () => resolve(''));
|
|
19
|
+
if (process.stdin.isTTY)
|
|
20
|
+
resolve('');
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function safeParse(raw) {
|
|
24
|
+
if (!raw || raw.trim() === '')
|
|
25
|
+
return null;
|
|
26
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(raw);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const error = (0, to_error_1.toError)(err);
|
|
32
|
+
throw new types_1.InformAiError(`Malformed hook input from Claude Code stdin: ${error.message}`, { cause: error });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function normalizeToolKind(toolName) {
|
|
36
|
+
if (HANDLED_FILE_TOOLS.has(toolName))
|
|
37
|
+
return toolName;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function normalizeToolInput(toolKind, toolInput) {
|
|
41
|
+
const filePath = toolInput.file_path;
|
|
42
|
+
if (!filePath)
|
|
43
|
+
return null;
|
|
44
|
+
if (toolKind === 'Write') {
|
|
45
|
+
return new types_1.NormalizedToolInput(filePath, [
|
|
46
|
+
new types_1.NormalizedEdit('', toolInput.content || ''),
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
if (toolKind === 'Edit') {
|
|
50
|
+
return new types_1.NormalizedToolInput(filePath, [
|
|
51
|
+
new types_1.NormalizedEdit(toolInput.old_string || '', toolInput.new_string || ''),
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
if (toolKind === 'MultiEdit') {
|
|
55
|
+
const raw = Array.isArray(toolInput.edits) ? toolInput.edits : [];
|
|
56
|
+
const edits = raw.map((e) => new types_1.NormalizedEdit(e.old_string || '', e.new_string || ''));
|
|
57
|
+
return new types_1.NormalizedToolInput(filePath, edits);
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function handleBash(payload, cwd) {
|
|
62
|
+
const command = payload.tool_input.command;
|
|
63
|
+
if (!command || command.trim() === '') {
|
|
64
|
+
process.exit(0);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const result = (0, runner_1.runBash)(command, cwd);
|
|
68
|
+
if (!result) {
|
|
69
|
+
process.exit(0);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
process.stderr.write(result.report);
|
|
73
|
+
process.exit(2);
|
|
74
|
+
}
|
|
75
|
+
function handleFileTool(payload, cwd) {
|
|
76
|
+
const toolKind = normalizeToolKind(payload.tool_name);
|
|
77
|
+
if (!toolKind) {
|
|
78
|
+
process.exit(0);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const input = normalizeToolInput(toolKind, payload.tool_input);
|
|
82
|
+
if (!input) {
|
|
83
|
+
process.exit(0);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Always allow edits to webpieces.config.json — it's the fix target when the config is broken
|
|
87
|
+
if (path.basename(input.filePath) === load_config_1.CONFIG_FILENAME) {
|
|
88
|
+
process.exit(0);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const result = (0, runner_1.run)(toolKind, input, cwd);
|
|
92
|
+
if (!result) {
|
|
93
|
+
process.exit(0);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
(0, rejection_log_1.logRejection)(toolKind, input, result, cwd);
|
|
97
|
+
process.stderr.write(result.report);
|
|
98
|
+
process.exit(2);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Shared entry point for all three Claude Code PreToolUse adapters. `mode` selects which tool kinds
|
|
102
|
+
* to validate; payloads outside the mode's scope pass through (exit 0). Fails CLOSED (exit 2) on any
|
|
103
|
+
* unexpected crash so a broken hook never silently lets an edit through.
|
|
104
|
+
*/
|
|
105
|
+
async function runMain(mode) {
|
|
106
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
107
|
+
try {
|
|
108
|
+
const raw = await readStdin();
|
|
109
|
+
const payload = safeParse(raw);
|
|
110
|
+
if (!payload) {
|
|
111
|
+
process.exit(0);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const cwd = process.cwd();
|
|
115
|
+
if (payload.tool_name === 'Bash') {
|
|
116
|
+
if (mode === 'rules') {
|
|
117
|
+
process.exit(0);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
handleBash(payload, cwd);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (mode === 'guards') {
|
|
124
|
+
process.exit(0);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
handleFileTool(payload, cwd);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const error = (0, to_error_1.toError)(err);
|
|
131
|
+
if (err instanceof types_1.InformAiError) {
|
|
132
|
+
process.stderr.write(error.message + '\n');
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
process.stderr.write(`[ai-hooks] hook crashed unexpectedly — failing closed: ${error.message}\n`);
|
|
136
|
+
}
|
|
137
|
+
process.exit(2);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=hook-core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-core.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/hook-core.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\n\nimport { run, runBash } from '../core/runner';\nimport { logRejection } from '../core/rejection-log';\nimport { CONFIG_FILENAME } from '../core/load-config';\nimport { NormalizedToolInput, NormalizedEdit, ToolKind, InformAiError } from '../core/types';\nimport { toError } from '../core/to-error';\n\n// Which tool kinds this hook invocation should validate. The hook is split into two independently\n// installable PreToolUse hooks so they can live in different settings files / locations:\n// - 'rules' → code-style rules, matcher Write|Edit|MultiEdit (Bash payloads pass through).\n// - 'guards' → git/PR/branch guards, matcher Bash (file payloads pass through).\n// - 'all' → both, for the combined back-compat `wp-ai-hook` bin.\nexport type HookMode = 'rules' | 'guards' | 'all';\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: (value: string) => void) => {\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}`, { cause: error });\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: ClaudeCodeEditEntry) => new NormalizedEdit(e.old_string || '', e.new_string || ''));\n return new NormalizedToolInput(filePath, edits);\n }\n return null;\n}\n\nfunction handleBash(payload: ClaudeCodePayload, cwd: string): void {\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}\n\nfunction handleFileTool(payload: ClaudeCodePayload, cwd: string): void {\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 // Always allow edits to webpieces.config.json — it's the fix target when the config is broken\n if (path.basename(input.filePath) === CONFIG_FILENAME) { 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}\n\n/**\n * Shared entry point for all three Claude Code PreToolUse adapters. `mode` selects which tool kinds\n * to validate; payloads outside the mode's scope pass through (exit 0). Fails CLOSED (exit 2) on any\n * unexpected crash so a broken hook never silently lets an edit through.\n */\nexport async function runMain(mode: HookMode): 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 if (mode === 'rules') { process.exit(0); return; }\n handleBash(payload, cwd);\n return;\n }\n\n if (mode === 'guards') { process.exit(0); return; }\n handleFileTool(payload, cwd);\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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(): Promise<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.main = main;
|
|
4
|
+
// Claude Code PreToolUse adapter for the CODE-STYLE RULES hook (matcher Write|Edit|MultiEdit).
|
|
5
|
+
// Bash payloads pass through untouched — branch/PR/merge protection is the separate guards hook.
|
|
6
|
+
const hook_core_1 = require("./hook-core");
|
|
7
|
+
function main() {
|
|
8
|
+
return (0, hook_core_1.runMain)('rules');
|
|
9
|
+
}
|
|
10
|
+
if (require.main === module) {
|
|
11
|
+
void main();
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=rules-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules-hook.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/rules-hook.ts"],"names":[],"mappings":";;AAIA,oBAEC;AAND,+FAA+F;AAC/F,iGAAiG;AACjG,2CAAsC;AAEtC,SAAgB,IAAI;IAChB,OAAO,IAAA,mBAAO,EAAC,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC1B,KAAK,IAAI,EAAE,CAAC;AAChB,CAAC","sourcesContent":["// Claude Code PreToolUse adapter for the CODE-STYLE RULES hook (matcher Write|Edit|MultiEdit).\n// Bash payloads pass through untouched — branch/PR/merge protection is the separate guards hook.\nimport { runMain } from './hook-core';\n\nexport function main(): Promise<void> {\n return runMain('rules');\n}\n\nif (require.main === module) {\n void main();\n}\n"]}
|
package/src/bin/setup.d.ts
CHANGED
|
@@ -1 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
declare class HookSpec {
|
|
2
|
+
readonly key: string;
|
|
3
|
+
readonly label: string;
|
|
4
|
+
readonly matcher: string;
|
|
5
|
+
readonly bin: string;
|
|
6
|
+
constructor(key: string, label: string, matcher: string, bin: string);
|
|
7
|
+
commandFor(target: InstallTarget, projectRoot: string): string;
|
|
8
|
+
}
|
|
9
|
+
declare class InstallTarget {
|
|
10
|
+
readonly choice: string;
|
|
11
|
+
readonly label: string;
|
|
12
|
+
readonly settingsPath: string;
|
|
13
|
+
readonly absolute: boolean;
|
|
14
|
+
constructor(choice: string, label: string, settingsPath: string, absolute: boolean);
|
|
15
|
+
}
|
|
16
|
+
export declare const RULES_HOOK: HookSpec;
|
|
17
|
+
export declare const GUARDS_HOOK: HookSpec;
|
|
18
|
+
export declare function installTargets(projectRoot: string, homeDir?: string): InstallTarget[];
|
|
19
|
+
type Json = Record<string, unknown>;
|
|
20
|
+
type RuleEntry = Json;
|
|
21
|
+
type Section = Record<string, RuleEntry>;
|
|
22
|
+
interface ConfigFile {
|
|
23
|
+
extends?: string;
|
|
24
|
+
rules: Section;
|
|
25
|
+
hookGuards: Section;
|
|
26
|
+
commands: Json;
|
|
27
|
+
rulesDir: string[];
|
|
28
|
+
}
|
|
29
|
+
interface MigrateResult {
|
|
30
|
+
config: ConfigFile;
|
|
31
|
+
changes: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function migrate(existing: Json): MigrateResult;
|
|
34
|
+
interface HookCommand {
|
|
35
|
+
type: string;
|
|
36
|
+
command: string;
|
|
37
|
+
}
|
|
38
|
+
interface HookEntry {
|
|
39
|
+
matcher: string;
|
|
40
|
+
hooks: HookCommand[];
|
|
41
|
+
}
|
|
42
|
+
interface ClaudeSettings {
|
|
43
|
+
hooks?: {
|
|
44
|
+
PreToolUse?: HookEntry[];
|
|
45
|
+
};
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
export declare function readSettings(settingsPath: string): ClaudeSettings;
|
|
49
|
+
export declare function hasHook(settings: ClaudeSettings, bin: string): boolean;
|
|
50
|
+
export declare function applyHook(hook: HookSpec, chosen: InstallTarget | null, targets: InstallTarget[], projectRoot: string): void;
|
|
51
|
+
export declare function main(): Promise<void>;
|
|
52
|
+
export {};
|