@webpieces/ai-hook-rules 0.4.558 → 0.4.560
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/README.md +1 -1
- package/package.json +2 -2
- package/src/adapters/claude-code-response.d.ts +1 -1
- package/src/adapters/claude-code-response.js +9 -1
- package/src/adapters/claude-code-response.js.map +1 -1
- package/src/adapters/hook-core.js +27 -10
- package/src/adapters/hook-core.js.map +1 -1
- package/src/bin/shim-audit-log.d.ts +73 -0
- package/src/bin/shim-audit-log.js +144 -0
- package/src/bin/shim-audit-log.js.map +1 -0
- package/src/bin/shim.d.ts +1 -0
- package/src/bin/shim.js +45 -18
- package/src/bin/shim.js.map +1 -1
- package/src/core/coordinator-worktree.d.ts +61 -0
- package/src/core/coordinator-worktree.js +94 -0
- package/src/core/coordinator-worktree.js.map +1 -0
- package/src/core/decision-log.d.ts +50 -9
- package/src/core/decision-log.js +118 -34
- package/src/core/decision-log.js.map +1 -1
- package/src/core/effective-tree.js.map +1 -1
- package/src/core/l0-matrix.js +2 -2
- package/src/core/l0-matrix.js.map +1 -1
- package/src/core/main-sync-log.d.ts +1 -1
- package/src/core/main-sync-log.js +9 -9
- package/src/core/main-sync-log.js.map +1 -1
- package/src/core/main-sync-refresh.d.ts +1 -1
- package/src/core/main-sync-refresh.js +2 -2
- package/src/core/main-sync-refresh.js.map +1 -1
- package/src/core/rejection-log.d.ts +7 -0
- package/src/core/rejection-log.js +21 -6
- package/src/core/rejection-log.js.map +1 -1
- package/src/core/runner.d.ts +2 -1
- package/src/core/runner.js +33 -7
- package/src/core/runner.js.map +1 -1
- package/templates/ai-hook.sh +69 -16
package/templates/ai-hook.sh
CHANGED
|
@@ -80,6 +80,58 @@ fi
|
|
|
80
80
|
# Read the tool payload ONCE, up front. The shim no longer exec's the bin (see RUN_BIN_SH), so it must
|
|
81
81
|
# forward stdin to the bin itself — and it needs the payload again on the fail-closed path below.
|
|
82
82
|
PAYLOAD="$(cat)"
|
|
83
|
+
CMD="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"\\]*\)".*/\1/p')"
|
|
84
|
+
TOOL="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"tool_name"[[:space:]]*:[[:space:]]*"\([^"\\]*\)".*/\1/p')"
|
|
85
|
+
FILE="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"\\]*\)".*/\1/p')"
|
|
86
|
+
WP_CWD="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"cwd"[[:space:]]*:[[:space:]]*"\([^"\\]*\)".*/\1/p')"
|
|
87
|
+
[ -n "$WP_CWD" ] || WP_CWD="$ROOT" # no cwd in the payload (older client, or a hand-run) → the shim's own tree
|
|
88
|
+
# Best-effort AUDIT TRAIL of what L0 did with this call — every call, not just the broken ones. One
|
|
89
|
+
# tab-separated line per invocation into this TREE's own logs/ai-hook-shim.log (gitignored), so the
|
|
90
|
+
# observed behaviour can be diffed against the matrix in guards/L0-tooling.md. NEVER breaks or blocks the
|
|
91
|
+
# hook: every write is swallowed, and nothing ever goes to stdout (stdout is the PreToolUse decision
|
|
92
|
+
# channel — a stray byte there would corrupt allow/deny).
|
|
93
|
+
WP_TREE=""
|
|
94
|
+
WP_LOG_DIR=""
|
|
95
|
+
wp_resolve_log_dir() {
|
|
96
|
+
_wp_rp="$(git -C "$WP_CWD" rev-parse --git-dir --git-common-dir 2>/dev/null)"
|
|
97
|
+
_wp_gd="$(printf '%s\n' "$_wp_rp" | sed -n 1p)"
|
|
98
|
+
_wp_cd="$(printf '%s\n' "$_wp_rp" | sed -n 2p)"
|
|
99
|
+
if [ -z "$_wp_gd" ] || [ -z "$_wp_cd" ]; then
|
|
100
|
+
WP_TREE=primary; WP_LOG_DIR="$WP_CWD/.webpieces/logs"; return 0
|
|
101
|
+
fi
|
|
102
|
+
# git prints a BARE .git from the primary clone and an absolute path from a linked worktree; the TS
|
|
103
|
+
# twin runs path.resolve(cwd, printed), so do the same before comparing or taking a basename.
|
|
104
|
+
case "$_wp_gd" in /*) : ;; *) _wp_gd="$WP_CWD/$_wp_gd" ;; esac
|
|
105
|
+
case "$_wp_cd" in /*) : ;; *) _wp_cd="$WP_CWD/$_wp_cd" ;; esac
|
|
106
|
+
# The primary clone's root is the parent of the SHARED git dir — declining any layout whose shared
|
|
107
|
+
# dir is not named .git (a bare repo, --separate-git-dir), same test as primaryRoot().
|
|
108
|
+
_wp_primary="$WP_CWD"
|
|
109
|
+
case "$_wp_cd" in
|
|
110
|
+
*/.git) [ -d "${_wp_cd%/*}" ] && _wp_primary="${_wp_cd%/*}" ;;
|
|
111
|
+
esac
|
|
112
|
+
if [ "$_wp_gd" = "$_wp_cd" ]; then
|
|
113
|
+
WP_TREE=primary
|
|
114
|
+
WP_LOG_DIR="$_wp_primary/.webpieces/logs"
|
|
115
|
+
else
|
|
116
|
+
# git's OWN name for the worktree (the basename of <primary>/.git/worktrees/<name>), not the
|
|
117
|
+
# directory's basename — two worktrees under different parents may share a directory name.
|
|
118
|
+
WP_TREE="${_wp_gd##*/}"
|
|
119
|
+
WP_LOG_DIR="$_wp_primary/.webpieces/worktrees/$WP_TREE/logs"
|
|
120
|
+
fi
|
|
121
|
+
}
|
|
122
|
+
wp_log() { # $1 = L0 fault code (D|X|K|-), $2 = verdict label
|
|
123
|
+
{
|
|
124
|
+
[ -n "$WP_LOG_DIR" ] || wp_resolve_log_dir
|
|
125
|
+
mkdir -p "$WP_LOG_DIR" 2>/dev/null || return 0
|
|
126
|
+
_wp_f="$WP_LOG_DIR/ai-hook-shim.log"
|
|
127
|
+
# Rotate at the SAME 512 KB into the SAME .1.log sibling as every JS-side webpieces log. This runs
|
|
128
|
+
# on every tool call, so it is one wc and no more; a size we cannot read counts as 0 (no rotation).
|
|
129
|
+
_wp_sz="$(wc -c < "$_wp_f" 2>/dev/null | tr -d ' ')"
|
|
130
|
+
case "$_wp_sz" in ''|*[!0-9]*) _wp_sz=0 ;; esac
|
|
131
|
+
[ "$_wp_sz" -gt 524288 ] && mv -f "$_wp_f" "$WP_LOG_DIR/ai-hook-shim.1.log" 2>/dev/null
|
|
132
|
+
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z' 2>/dev/null)" "$BIN_NAME" "$TOOL" "tree=$WP_TREE" "fault=$1" "$2" "$CMD" >> "$_wp_f"
|
|
133
|
+
} 2>/dev/null || true
|
|
134
|
+
}
|
|
83
135
|
BROKEN_BIN=""
|
|
84
136
|
CRASH_MSG=""
|
|
85
137
|
if [ -x "$BIN" ] && [ -z "$DRIFT_PKG" ]; then
|
|
@@ -91,6 +143,14 @@ if [ -x "$BIN" ] && [ -z "$DRIFT_PKG" ]; then
|
|
|
91
143
|
cat "$OUT_FILE" # the guard's real decision — verbatim
|
|
92
144
|
cat "$ERR_FILE" >&2
|
|
93
145
|
rm -f "$OUT_FILE" "$ERR_FILE" 2>/dev/null
|
|
146
|
+
# THE HEALTHY CALL, which this log used to be silent about (see WP_LOG_SH's header). No sh-side
|
|
147
|
+
# fault, the bin ran, and its own exit code says how it ended — 0 allow, 2 block. Logged AFTER the
|
|
148
|
+
# decision bytes are already on stdout, so the audit trail never sits between the guard and Claude
|
|
149
|
+
# Code. Without this line, "no entry" meant either healthy or never-ran, and those are the two
|
|
150
|
+
# answers a reader most needs to tell apart.
|
|
151
|
+
WP_VERDICT=PASS-BIN-ALLOW
|
|
152
|
+
[ "$RC" = 2 ] && WP_VERDICT=PASS-BIN-BLOCK
|
|
153
|
+
wp_log - "$WP_VERDICT"
|
|
94
154
|
exit "$RC"
|
|
95
155
|
fi
|
|
96
156
|
# Crashed. Keep the most useful stderr line for the human. Strip " and backslash so the text stays a
|
|
@@ -107,18 +167,11 @@ fi
|
|
|
107
167
|
# through: the assistant's own Bash tool routes through this hook too, so blocking everything would
|
|
108
168
|
# deadlock the very commands (pnpm install / rm -rf node_modules && pnpm install) that re-enable the
|
|
109
169
|
# guards. A silent exit 0 = "allow" in the PreToolUse protocol; the guards resume once the tree is sane.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# tab-separated line per call → <root>/.webpieces/logs/ai-hook-shim.log (gitignored). NEVER breaks or
|
|
116
|
-
# blocks the hook: all writes are best-effort (|| true) and go to a file, never to stdout (stdout is
|
|
117
|
-
# the PreToolUse decision channel — a stray byte there would corrupt allow/deny).
|
|
118
|
-
LOG_DIR="$ROOT/.webpieces/logs"
|
|
119
|
-
wp_log() { # $1 = label (ALLOW-CURE|ALLOW-READ|ALLOW-CONFIG|DENY|DENY-STALE|DENY-BROKEN)
|
|
120
|
-
{ mkdir -p "$LOG_DIR" 2>/dev/null && printf '%s\t%s\t%s\t%s\t%s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z' 2>/dev/null)" "$BIN_NAME" "$TOOL" "$1" "$CMD" >> "$LOG_DIR/ai-hook-shim.log"; } 2>/dev/null || true
|
|
121
|
-
}
|
|
170
|
+
# WHICH of the six guards/L0-tooling.md faults fired, in the doc's own letters. Only the three sh-side
|
|
171
|
+
# codes can be decided here; S/C/Y live in the binary, which never got to run on this path.
|
|
172
|
+
WP_FAULT=X # X — bin missing (fresh clone, new worktree)
|
|
173
|
+
[ -n "$DRIFT_PKG" ] && WP_FAULT=D # D — version drift; D and K are mutually exclusive
|
|
174
|
+
[ -n "$BROKEN_BIN" ] && WP_FAULT=K # K — bin present but CRASHED (corrupt node_modules)
|
|
122
175
|
DENY_LABEL="DENY"
|
|
123
176
|
[ -n "$DRIFT_PKG" ] && DENY_LABEL="DENY-STALE" # version drift, not a missing bin
|
|
124
177
|
[ -n "$BROKEN_BIN" ] && DENY_LABEL="DENY-BROKEN" # bin present but CRASHED (corrupt node_modules)
|
|
@@ -126,19 +179,19 @@ DENY_LABEL="DENY"
|
|
|
126
179
|
# help a given fault also cannot hurt it, and gating each entry on a fault is what produced the four
|
|
127
180
|
# defects recorded above L0_ALLOW_ERE.
|
|
128
181
|
if [ "$TOOL" = "Read" ]; then
|
|
129
|
-
wp_log ALLOW-READ
|
|
182
|
+
wp_log "$WP_FAULT" ALLOW-READ # you must be able to read to work out how to fix this
|
|
130
183
|
exit 0
|
|
131
184
|
fi
|
|
132
185
|
case "$FILE" in
|
|
133
186
|
*/webpieces.config.json|webpieces.config.json)
|
|
134
|
-
wp_log ALLOW-CONFIG
|
|
187
|
+
wp_log "$WP_FAULT" ALLOW-CONFIG # the always-allowed recovery target — every guard is configured from it
|
|
135
188
|
exit 0 ;;
|
|
136
189
|
esac
|
|
137
190
|
if printf '%s' "$CMD" | grep -Eq '^(cd[[:space:]]+([A-Za-z0-9._/@~+-]+|'\''[^'\'']+'\'')[[:space:]]*&&[[:space:]]*)?((pnpm|npm)[[:space:]]+(install|i)([[:space:]]+--[A-Za-z][A-Za-z0-9=._/@:-]*)*|rm[[:space:]]+-rf[[:space:]]+(\./)?node_modules/?([[:space:]]*&&[[:space:]]*(pnpm|npm)[[:space:]]+(install|i)([[:space:]]+--[A-Za-z][A-Za-z0-9=._/@:-]*)*)?|git[[:space:]]+(pull|fetch)([[:space:]]+(--)?[A-Za-z0-9][A-Za-z0-9=._/@:-]*)*|(pnpm|npm|npx)([[:space:]]+(exec|run))?[[:space:]]+wp-upgrade-shim|cp[[:space:]]+(\./)?node_modules/@webpieces/ai-hook-rules/templates/ai-hook\.sh[[:space:]]+(\./)?\.claude/webpieces/ai-hook\.sh|(pnpm|npm|npx)([[:space:]]+(exec|run))?[[:space:]]+wp-install-ai-hooks([[:space:]]+--[A-Za-z][A-Za-z0-9=._/@:-]*)*|(pwd|git[[:space:]]+(status|log|diff|show|branch|rev-parse)|git[[:space:]]+worktree[[:space:]]+list)([[:space:]]+(--)?[A-Za-z0-9][A-Za-z0-9=._/@:-]*)*)([[:space:]]+2>(&1|/dev/null))?([[:space:]]*\|[[:space:]]*(tail|head)([[:space:]]+-(n[[:space:]]+)?[0-9]+)?)?[[:space:]]*$'; then
|
|
138
|
-
wp_log ALLOW-CURE
|
|
191
|
+
wp_log "$WP_FAULT" ALLOW-CURE # record the self-heal we let through (re-enables the guards)
|
|
139
192
|
exit 0 # allow the cure so the assistant can break the deadlock
|
|
140
193
|
fi
|
|
141
|
-
wp_log "$DENY_LABEL"
|
|
194
|
+
wp_log "$WP_FAULT" "$DENY_LABEL" # every fail-closed block, with the fault that caused it
|
|
142
195
|
if [ -n "$BROKEN_BIN" ]; then
|
|
143
196
|
# Report (do NOT auto-clean) the orphaned pnpm staging dirs — a package pnpm was mid-way through
|
|
144
197
|
# writing is left behind as <name>_<pid>_<hash>. Their presence is the fingerprint of an install that
|