@webpieces/ai-hook-rules 0.3.284 → 0.3.285

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.
@@ -12,5 +12,6 @@ if (fs.existsSync(compiled)) {
12
12
  require(compiled).main();
13
13
  } else {
14
14
  console.error(' [ai-hook-rules] Package not built yet. Run the build first, or install from npm.');
15
+ // eslint-disable-next-line @webpieces/no-process-exit-outside-main -- plain-JS bin shim entry point (pre-build launcher); exits when the compiled package is missing.
15
16
  process.exit(1);
16
17
  }
package/bin/wp-ai-hook.js CHANGED
@@ -12,5 +12,6 @@ if (fs.existsSync(compiled)) {
12
12
  require(compiled).main();
13
13
  } else {
14
14
  console.error(' [ai-hook-rules] Package not built yet. Run the build first, or install from npm.');
15
+ // eslint-disable-next-line @webpieces/no-process-exit-outside-main -- plain-JS bin shim entry point (pre-build launcher); exits when the compiled package is missing.
15
16
  process.exit(1);
16
17
  }
@@ -12,5 +12,6 @@ if (fs.existsSync(compiled)) {
12
12
  require(compiled).main();
13
13
  } else {
14
14
  console.error(' [ai-hook-rules] Package not built yet. Run the build first, or install from npm.');
15
+ // eslint-disable-next-line @webpieces/no-process-exit-outside-main -- plain-JS bin shim entry point (pre-build launcher); exits when the compiled package is missing.
15
16
  process.exit(1);
16
17
  }
@@ -12,5 +12,6 @@ if (fs.existsSync(compiled)) {
12
12
  require(compiled).main();
13
13
  } else {
14
14
  console.error(' [ai-hook-rules] Package not built yet. Run `nx build ai-hook-rules` first.');
15
+ // eslint-disable-next-line @webpieces/no-process-exit-outside-main -- plain-JS bin shim entry point (pre-build launcher); exits when the compiled package is missing.
15
16
  process.exit(1);
16
17
  }
@@ -12,5 +12,6 @@ if (fs.existsSync(compiled)) {
12
12
  require(compiled).main();
13
13
  } else {
14
14
  console.error(' [ai-hook-rules] Package not built yet. Run `nx build ai-hook-rules` first.');
15
+ // eslint-disable-next-line @webpieces/no-process-exit-outside-main -- plain-JS bin shim entry point (pre-build launcher); exits when the compiled package is missing.
15
16
  process.exit(1);
16
17
  }
@@ -12,5 +12,6 @@ if (fs.existsSync(compiled)) {
12
12
  require(compiled).main();
13
13
  } else {
14
14
  console.error(' [ai-hook-rules] Package not built yet. Run the build first, or install from npm.');
15
+ // eslint-disable-next-line @webpieces/no-process-exit-outside-main -- plain-JS bin shim entry point (pre-build launcher); exits when the compiled package is missing.
15
16
  process.exit(1);
16
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/ai-hook-rules",
3
- "version": "0.3.284",
3
+ "version": "0.3.285",
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",
@@ -35,7 +35,7 @@
35
35
  "directory": "packages/tooling/ai-hook-rules"
36
36
  },
37
37
  "dependencies": {
38
- "@webpieces/rules-config": "0.3.284"
38
+ "@webpieces/rules-config": "0.3.285"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public"
@@ -51,16 +51,16 @@ function denyJson(reason, toolName) {
51
51
  }
52
52
  // Block the tool call and surface `reason` to both the user (terminal UI) and the model. `toolName`
53
53
  // selects whether the red `systemMessage` is added (Bash) or omitted (file tools) — see denyJson.
54
- // NOTE: emitDeny/emitAllow are the hook's designated terminal boundary — the exit code IS the
55
- // Claude Code PreToolUse protocol (exit 0 + JSON = the contract). When the
56
- // @webpieces/no-process-exit-outside-main rule is activated repo-wide (after the tooling release
57
- // carrying it), add its eslint-disable-next-line above each process.exit below.
54
+ // emitDeny/emitAllow are the hook's designated terminal boundary — the exit code IS the Claude Code
55
+ // PreToolUse protocol (exit 0 + JSON = the contract), so the process.exit stays and is allowlisted.
58
56
  function emitDeny(reason, toolName) {
59
57
  process.stdout.write(denyJson(reason, toolName) + '\n');
58
+ // eslint-disable-next-line @webpieces/no-process-exit-outside-main -- hook exit-code IS the Claude Code PreToolUse protocol (exit 0 + JSON = the contract); designated terminal boundary.
60
59
  process.exit(0);
61
60
  }
62
61
  // Allow the tool call. No JSON needed — a silent exit 0 is "allow" in the PreToolUse protocol.
63
62
  function emitAllow() {
63
+ // eslint-disable-next-line @webpieces/no-process-exit-outside-main -- hook exit-code IS the Claude Code PreToolUse protocol (silent exit 0 = "allow"); designated terminal boundary.
64
64
  process.exit(0);
65
65
  }
66
66
  //# sourceMappingURL=claude-code-response.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"claude-code-response.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/claude-code-response.ts"],"names":[],"mappings":";AAAA,+FAA+F;AAC/F,sFAAsF;AACtF,yGAAyG;AACzG,EAAE;AACF,gGAAgG;AAChG,kGAAkG;AAClG,uGAAuG;AACvG,oDAAoD;AACpD,EAAE;AACF,oGAAoG;AACpG,oDAAoD;AACpD,EAAE;AACF,8GAA8G;AAC9G,8GAA8G;AAC9G,8GAA8G;AAC9G,8GAA8G;AAC9G,8GAA8G;AAC9G,EAAE;AACF,mGAAmG;AACnG,qFAAqF;AACrF,qGAAqG;AACrG,mGAAmG;AACnG,mGAAmG;AACnG,oGAAoG;AACpG,8FAA8F;AAC9F,yCAAyC;AACzC,6FAA6F;;AAS7F,4BAYC;AAQD,4BAGC;AAGD,8BAEC;AAnCD,qGAAqG;AACrG,uGAAuG;AACvG,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;AACtC,SAAS,gBAAgB,CAAC,MAAc;IACpC,OAAO,GAAG,GAAG,YAAY,MAAM,GAAG,GAAG,KAAK,CAAC;AAC/C,CAAC;AAED,SAAgB,QAAQ,CAAC,MAAc,EAAE,QAAgB;IACrD,MAAM,kBAAkB,GAAG;QACvB,aAAa,EAAE,YAAY;QAC3B,kBAAkB,EAAE,MAAM;QAC1B,wBAAwB,EAAE,MAAM;KACnC,CAAC;IACF,yFAAyF;IACzF,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC3F,CAAC;IACD,2FAA2F;IAC3F,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,kBAAkB,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,oGAAoG;AACpG,kGAAkG;AAClG,8FAA8F;AAC9F,2EAA2E;AAC3E,iGAAiG;AACjG,gFAAgF;AAChF,SAAgB,QAAQ,CAAC,MAAc,EAAE,QAAgB;IACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,+FAA+F;AAC/F,SAAgB,SAAS;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC","sourcesContent":["// The single place that knows Claude Code's PreToolUse decision protocol, so every deny in the\n// Claude Code adapter is emitted identically — and identically to the checked-in shim\n// (.claude/webpieces/ai-hook.sh, rendered by renderShim() in ../bin/shim.ts), which emits the same JSON.\n//\n// A block is signalled by `permissionDecision: \"deny\"` JSON on STDOUT with exit 0 — NOT exit 2.\n// Claude Code only parses the JSON on exit 0; exit 2 would ignore stdout and the reason would not\n// surface in the terminal UI. \"deny\" still blocks the tool, so this remains fail-closed: it is not the\n// silent-allow a bare exit 0 with no JSON would be.\n//\n// WHY the tool-conditional `systemMessage` (verified by live tests against Claude Code v2.1.x — the\n// docs are wrong here; do NOT re-derive from them):\n//\n// | deny field | Bash tool | Write/Edit/MultiEdit tool |\n// |-----------------------------------|-----------------------------------|-------------------------------|\n// | permissionDecisionReason (plain) | model sees it; USER SEES NOTHING | model + RED \"Error:\" block ok |\n// | systemMessage | ONLY user-visible field; grey | grey extra line (redundant) |\n// | systemMessage wrapped in ANSI red | RED + visible to the user (fix) | redundant 2nd red line |\n//\n// So: on a **Bash** deny we ALSO emit a top-level `systemMessage` wrapped in ANSI red (ESC[31;1m …\n// ESC[0m) — it is the only field a Bash deny shows the human, and it honors ANSI. On\n// Write/Edit/MultiEdit we add NO `systemMessage` (the reason already renders red natively — a second\n// line is just noise). `permissionDecisionReason` is always plain text (never ANSI): it's what the\n// model reads and what Write/Edit renders red. JSON.stringify serializes the ESC char as the valid\n// \\u escape, so the payload stays valid JSON — we build the ESC via String.fromCharCode(0x1b) so no\n// raw ESC (0x1b) byte ever lives in this source file. Do NOT use exit 2 (stdout JSON ignored;\n// stderr invisible to the user on Bash).\n// Refs: Claude Code GitHub issues #31592, #40380, #17356 (asymmetry \"closed / not planned\").\n\n// ANSI escape (0x1b) built at runtime so no raw ESC byte sits in source. ANSI red is a *bonus* — the\n// 🛑 prefix + reason stay meaningful if a future/CI renderer strips the color. One place = one escape.\nconst ESC = String.fromCharCode(0x1b);\nfunction redSystemMessage(reason: string): string {\n return `${ESC}[31;1m🛑 ${reason}${ESC}[0m`;\n}\n\nexport function denyJson(reason: string, toolName: string): string {\n const hookSpecificOutput = {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: reason,\n };\n // Bash only: permissionDecisionReason is NOT user-visible, so add the red systemMessage.\n if (toolName === 'Bash') {\n return JSON.stringify({ systemMessage: redSystemMessage(reason), hookSpecificOutput });\n }\n // Write/Edit/MultiEdit (and anything else): reason renders red natively; no systemMessage.\n return JSON.stringify({ hookSpecificOutput });\n}\n\n// Block the tool call and surface `reason` to both the user (terminal UI) and the model. `toolName`\n// selects whether the red `systemMessage` is added (Bash) or omitted (file tools) — see denyJson.\n// NOTE: emitDeny/emitAllow are the hook's designated terminal boundary — the exit code IS the\n// Claude Code PreToolUse protocol (exit 0 + JSON = the contract). When the\n// @webpieces/no-process-exit-outside-main rule is activated repo-wide (after the tooling release\n// carrying it), add its eslint-disable-next-line above each process.exit below.\nexport function emitDeny(reason: string, toolName: string): never {\n process.stdout.write(denyJson(reason, toolName) + '\\n');\n process.exit(0);\n}\n\n// Allow the tool call. No JSON needed — a silent exit 0 is \"allow\" in the PreToolUse protocol.\nexport function emitAllow(): never {\n process.exit(0);\n}\n"]}
1
+ {"version":3,"file":"claude-code-response.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/adapters/claude-code-response.ts"],"names":[],"mappings":";AAAA,+FAA+F;AAC/F,sFAAsF;AACtF,yGAAyG;AACzG,EAAE;AACF,gGAAgG;AAChG,kGAAkG;AAClG,uGAAuG;AACvG,oDAAoD;AACpD,EAAE;AACF,oGAAoG;AACpG,oDAAoD;AACpD,EAAE;AACF,8GAA8G;AAC9G,8GAA8G;AAC9G,8GAA8G;AAC9G,8GAA8G;AAC9G,8GAA8G;AAC9G,EAAE;AACF,mGAAmG;AACnG,qFAAqF;AACrF,qGAAqG;AACrG,mGAAmG;AACnG,mGAAmG;AACnG,oGAAoG;AACpG,8FAA8F;AAC9F,yCAAyC;AACzC,6FAA6F;;AAS7F,4BAYC;AAMD,4BAIC;AAGD,8BAGC;AAnCD,qGAAqG;AACrG,uGAAuG;AACvG,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;AACtC,SAAS,gBAAgB,CAAC,MAAc;IACpC,OAAO,GAAG,GAAG,YAAY,MAAM,GAAG,GAAG,KAAK,CAAC;AAC/C,CAAC;AAED,SAAgB,QAAQ,CAAC,MAAc,EAAE,QAAgB;IACrD,MAAM,kBAAkB,GAAG;QACvB,aAAa,EAAE,YAAY;QAC3B,kBAAkB,EAAE,MAAM;QAC1B,wBAAwB,EAAE,MAAM;KACnC,CAAC;IACF,yFAAyF;IACzF,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC3F,CAAC;IACD,2FAA2F;IAC3F,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,kBAAkB,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,oGAAoG;AACpG,kGAAkG;AAClG,oGAAoG;AACpG,oGAAoG;AACpG,SAAgB,QAAQ,CAAC,MAAc,EAAE,QAAgB;IACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC;IACxD,0LAA0L;IAC1L,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,+FAA+F;AAC/F,SAAgB,SAAS;IACrB,qLAAqL;IACrL,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC","sourcesContent":["// The single place that knows Claude Code's PreToolUse decision protocol, so every deny in the\n// Claude Code adapter is emitted identically — and identically to the checked-in shim\n// (.claude/webpieces/ai-hook.sh, rendered by renderShim() in ../bin/shim.ts), which emits the same JSON.\n//\n// A block is signalled by `permissionDecision: \"deny\"` JSON on STDOUT with exit 0 — NOT exit 2.\n// Claude Code only parses the JSON on exit 0; exit 2 would ignore stdout and the reason would not\n// surface in the terminal UI. \"deny\" still blocks the tool, so this remains fail-closed: it is not the\n// silent-allow a bare exit 0 with no JSON would be.\n//\n// WHY the tool-conditional `systemMessage` (verified by live tests against Claude Code v2.1.x — the\n// docs are wrong here; do NOT re-derive from them):\n//\n// | deny field | Bash tool | Write/Edit/MultiEdit tool |\n// |-----------------------------------|-----------------------------------|-------------------------------|\n// | permissionDecisionReason (plain) | model sees it; USER SEES NOTHING | model + RED \"Error:\" block ok |\n// | systemMessage | ONLY user-visible field; grey | grey extra line (redundant) |\n// | systemMessage wrapped in ANSI red | RED + visible to the user (fix) | redundant 2nd red line |\n//\n// So: on a **Bash** deny we ALSO emit a top-level `systemMessage` wrapped in ANSI red (ESC[31;1m …\n// ESC[0m) — it is the only field a Bash deny shows the human, and it honors ANSI. On\n// Write/Edit/MultiEdit we add NO `systemMessage` (the reason already renders red natively — a second\n// line is just noise). `permissionDecisionReason` is always plain text (never ANSI): it's what the\n// model reads and what Write/Edit renders red. JSON.stringify serializes the ESC char as the valid\n// \\u escape, so the payload stays valid JSON — we build the ESC via String.fromCharCode(0x1b) so no\n// raw ESC (0x1b) byte ever lives in this source file. Do NOT use exit 2 (stdout JSON ignored;\n// stderr invisible to the user on Bash).\n// Refs: Claude Code GitHub issues #31592, #40380, #17356 (asymmetry \"closed / not planned\").\n\n// ANSI escape (0x1b) built at runtime so no raw ESC byte sits in source. ANSI red is a *bonus* — the\n// 🛑 prefix + reason stay meaningful if a future/CI renderer strips the color. One place = one escape.\nconst ESC = String.fromCharCode(0x1b);\nfunction redSystemMessage(reason: string): string {\n return `${ESC}[31;1m🛑 ${reason}${ESC}[0m`;\n}\n\nexport function denyJson(reason: string, toolName: string): string {\n const hookSpecificOutput = {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: reason,\n };\n // Bash only: permissionDecisionReason is NOT user-visible, so add the red systemMessage.\n if (toolName === 'Bash') {\n return JSON.stringify({ systemMessage: redSystemMessage(reason), hookSpecificOutput });\n }\n // Write/Edit/MultiEdit (and anything else): reason renders red natively; no systemMessage.\n return JSON.stringify({ hookSpecificOutput });\n}\n\n// Block the tool call and surface `reason` to both the user (terminal UI) and the model. `toolName`\n// selects whether the red `systemMessage` is added (Bash) or omitted (file tools) — see denyJson.\n// emitDeny/emitAllow are the hook's designated terminal boundary — the exit code IS the Claude Code\n// PreToolUse protocol (exit 0 + JSON = the contract), so the process.exit stays and is allowlisted.\nexport function emitDeny(reason: string, toolName: string): never {\n process.stdout.write(denyJson(reason, toolName) + '\\n');\n // eslint-disable-next-line @webpieces/no-process-exit-outside-main -- hook exit-code IS the Claude Code PreToolUse protocol (exit 0 + JSON = the contract); designated terminal boundary.\n process.exit(0);\n}\n\n// Allow the tool call. No JSON needed — a silent exit 0 is \"allow\" in the PreToolUse protocol.\nexport function emitAllow(): never {\n // eslint-disable-next-line @webpieces/no-process-exit-outside-main -- hook exit-code IS the Claude Code PreToolUse protocol (silent exit 0 = \"allow\"); designated terminal boundary.\n process.exit(0);\n}\n"]}