@webpieces/ai-hook-rules 0.4.622 → 0.4.624
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 +49 -19
- package/package.json +2 -2
- package/src/adapters/hook-core.js +13 -14
- package/src/adapters/hook-core.js.map +1 -1
- package/src/bin/hook-registration.d.ts +160 -50
- package/src/bin/hook-registration.js +227 -96
- package/src/bin/hook-registration.js.map +1 -1
- package/src/bin/managed-env.d.ts +46 -0
- package/src/bin/managed-env.js +50 -0
- package/src/bin/managed-env.js.map +1 -0
- package/src/bin/setup.d.ts +1 -2
- package/src/bin/setup.js +38 -46
- package/src/bin/setup.js.map +1 -1
- package/src/bin/shim-audit-log.js +12 -1
- package/src/bin/shim-audit-log.js.map +1 -1
- package/src/bin/shim-deny-reason.d.ts +6 -0
- package/src/bin/shim-deny-reason.js +82 -0
- package/src/bin/shim-deny-reason.js.map +1 -0
- package/src/bin/shim.d.ts +0 -1
- package/src/bin/shim.js +3 -57
- package/src/bin/shim.js.map +1 -1
- package/src/bin/upgrade-shim.js +140 -30
- package/src/bin/upgrade-shim.js.map +1 -1
- package/src/core/decision-log.d.ts +3 -3
- package/src/core/decision-log.js +8 -8
- package/src/core/decision-log.js.map +1 -1
- package/src/core/effective-tree.d.ts +5 -2
- package/src/core/effective-tree.js +1 -1
- package/src/core/effective-tree.js.map +1 -1
- package/src/core/l0-matrix.js +15 -13
- package/src/core/l0-matrix.js.map +1 -1
- package/src/core/l1-doc.js +29 -68
- package/src/core/l1-doc.js.map +1 -1
- package/src/core/l1-rows.d.ts +17 -9
- package/src/core/l1-rows.js +18 -13
- package/src/core/l1-rows.js.map +1 -1
- package/src/core/log-stream.d.ts +6 -4
- package/src/core/log-stream.js +6 -4
- package/src/core/log-stream.js.map +1 -1
- package/src/core/log-streams.d.ts +13 -3
- package/src/core/log-streams.js +15 -5
- package/src/core/log-streams.js.map +1 -1
- package/src/core/runner.d.ts +1 -2
- package/src/core/runner.js +29 -24
- package/src/core/runner.js.map +1 -1
- package/src/core/version-sync.d.ts +67 -0
- package/src/core/version-sync.js +148 -0
- package/src/core/version-sync.js.map +1 -0
- package/src/core/webpieces-versions.d.ts +83 -0
- package/src/core/webpieces-versions.js +169 -0
- package/src/core/webpieces-versions.js.map +1 -0
- package/templates/ai-hook.sh +15 -4
- package/templates/claude-settings-hook.json +6 -3
- package/src/bin/guarantee-root.d.ts +0 -95
- package/src/bin/guarantee-root.js +0 -297
- package/src/bin/guarantee-root.js.map +0 -1
- package/src/core/coordinator-worktree.d.ts +0 -61
- package/src/core/coordinator-worktree.js +0 -94
- package/src/core/coordinator-worktree.js.map +0 -1
- package/templates/guarantee-root.sh +0 -113
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# webpieces L-1 hook — GUARANTEE ROOT. Generated by renderGuaranteeRoot(); do not hand-edit.
|
|
3
|
-
#
|
|
4
|
-
# Registered ABSOLUTE in .claude/settings.json, matcher "Bash":
|
|
5
|
-
# sh "$CLAUDE_PROJECT_DIR/.claude/webpieces/guarantee-root.sh"
|
|
6
|
-
#
|
|
7
|
-
# It exists because the GUARD hooks beside it are registered RELATIVE, so that each git tree is
|
|
8
|
-
# governed by its own @webpieces release. A relative hook that cannot resolve does not block — the
|
|
9
|
-
# harness logs it and lets the tool call proceed UNGUARDED. This file makes that unreachable by
|
|
10
|
-
# refusing any cd that would park the shell where the relative hooks cannot launch.
|
|
11
|
-
#
|
|
12
|
-
# Three tests, no config, no binary, no network:
|
|
13
|
-
# 1. destination holds .git -> ALLOW (tree root, worktree, or foreign clone)
|
|
14
|
-
# 2. destination is outside $CLAUDE_PROJECT_DIR -> ALLOW (the harness resets the cwd next call)
|
|
15
|
-
# 3. otherwise -> DENY (sticky AND unguarded)
|
|
16
|
-
#
|
|
17
|
-
# A denied cd never runs, so the shell never leaves the root and there is nothing to recover from.
|
|
18
|
-
|
|
19
|
-
PAYLOAD="$(cat)"
|
|
20
|
-
CWD="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"cwd"[[:space:]]*:[[:space:]]*"\([^"\\]*\)".*/\1/p')"
|
|
21
|
-
TOOL="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"tool_name"[[:space:]]*:[[:space:]]*"\([^"\\]*\)".*/\1/p')"
|
|
22
|
-
|
|
23
|
-
# The command PREFIX, not the whole command — note there is no closing " in this pattern.
|
|
24
|
-
# WHY: a JSON payload escapes an embedded double quote as \", and `[^"\\]*` stops dead at that
|
|
25
|
-
# backslash, so the usual "capture between quotes" form yields the EMPTY STRING for any command
|
|
26
|
-
# containing a quote at all (measured: `cd /a/b && echo "hi"` -> ''). An empty CMD here would mean
|
|
27
|
-
# "no cd found" -> ALLOW, i.e. this guard would fail OPEN for every quoted command — the exact hazard
|
|
28
|
-
# it exists to close. Capturing only up to the first quote/backslash is enough, because everything
|
|
29
|
-
# L-1 needs (is the FIRST word a cd, and what is its target) lives in the prefix; a quote can only
|
|
30
|
-
# appear later, in the part we do not need.
|
|
31
|
-
CMD="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"\\]*\).*/\1/p')"
|
|
32
|
-
|
|
33
|
-
# Only Bash can move the shell. Anything else, and any payload we cannot read, is not ours.
|
|
34
|
-
[ "$TOOL" = "Bash" ] || exit 0
|
|
35
|
-
[ -n "$CMD" ] || exit 0
|
|
36
|
-
|
|
37
|
-
# --- cd audit (best-effort; never blocks, never touches stdout) --------------------------------
|
|
38
|
-
SID="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"session_id"[[:space:]]*:[[:space:]]*"\([^"\\]*\)".*/\1/p')"
|
|
39
|
-
AID="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"agent_id"[[:space:]]*:[[:space:]]*"\([^"\\]*\)".*/\1/p')"
|
|
40
|
-
# Untrusted payload values are used as path segments, so anything outside [A-Za-z0-9._-] collapses to _
|
|
41
|
-
# and a leading dot is neutralised — ../../etc can never escape the logs directory.
|
|
42
|
-
clean() { printf '%s' "$1" | tr -c 'A-Za-z0-9._-' '_' \
|
|
43
|
-
| sed -e 's/\.\{2,\}/_/g' -e 's/^\.\{1,\}/_/' | cut -c1-64; }
|
|
44
|
-
wp_cd_log() { # $1 = verdict, $2 = destination (may be empty)
|
|
45
|
-
{
|
|
46
|
-
[ -n "$CLAUDE_PROJECT_DIR" ] || return 0
|
|
47
|
-
_d="$CLAUDE_PROJECT_DIR/.webpieces/logs/L-1-cd"
|
|
48
|
-
mkdir -p "$_d" 2>/dev/null || return 0
|
|
49
|
-
# The LAYER is the directory; this is the WRITER, keyed exactly like LogStream.writerFile():
|
|
50
|
-
# <session>-<agent|coordinator>-guarantee-root.log. ALWAYS keyed; a missing session_id renders as
|
|
51
|
-
# 'unknown'. No bare-name branch anywhere.
|
|
52
|
-
_p="$(clean "${SID:-unknown}")-$(clean "${AID:-coordinator}")-guarantee-root"
|
|
53
|
-
_f="$_d/${_p}.log"
|
|
54
|
-
_sz="$(wc -c < "$_f" 2>/dev/null | tr -d ' ')"
|
|
55
|
-
case "$_sz" in ''|*[!0-9]*) _sz=0 ;; esac
|
|
56
|
-
[ "$_sz" -gt 524288 ] && mv -f "$_f" "$_d/${_p}.1.log" 2>/dev/null
|
|
57
|
-
# fault=- is a constant here: L-1 detects no L0 fault. It is present so ONE grep spans every
|
|
58
|
-
# hook-written stream rather than needing a different field list per layer.
|
|
59
|
-
printf '%s\t%s\tfault=-\tdest=%s\tcwd=%s\t%s\n' \
|
|
60
|
-
"$(date '+%Y-%m-%dT%H:%M:%S%z' 2>/dev/null)" "$1" "$2" "$CWD" "$CMD" >> "$_f"
|
|
61
|
-
} 2>/dev/null || true
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# Does the command OPEN with cd/pushd? Only a LEADING cd counts — the same rule effective-tree.ts
|
|
66
|
-
# enforces, because a later cd cannot retroactively move a command that has already run.
|
|
67
|
-
FIRST="$(printf '%s' "$CMD" | sed -n 's/^[[:space:]]*\([^[:space:]]\{1,\}\).*/\1/p')"
|
|
68
|
-
case "$FIRST" in
|
|
69
|
-
cd|pushd) ;;
|
|
70
|
-
*) exit 0 ;; # no leading cd: nothing to audit, nothing to judge
|
|
71
|
-
esac
|
|
72
|
-
|
|
73
|
-
# The target: a single-quoted path first (that is how a path with spaces is spelled), else a bare word.
|
|
74
|
-
DEST="$(printf '%s' "$CMD" | sed -n "s/^[[:space:]]*[a-z]\{2,5\}[[:space:]]\{1,\}'\([^']*\)'.*/\1/p")"
|
|
75
|
-
[ -n "$DEST" ] || DEST="$(printf '%s' "$CMD" | sed -n 's/^[[:space:]]*[a-z]\{2,5\}[[:space:]]\{1,\}\([^[:space:];&|]\{1,\}\).*/\1/p')"
|
|
76
|
-
|
|
77
|
-
REASON=""
|
|
78
|
-
if [ -z "$DEST" ] || [ "$DEST" = "-" ]; then
|
|
79
|
-
REASON='A bare cd (or cd -) moves the shell somewhere the guards cannot predict - a bare cd goes to your home directory, where the webpieces hooks do not exist and every later tool call would run UNGUARDED. Name the directory: cd /abs/path && <your command>.'
|
|
80
|
-
else
|
|
81
|
-
# A target the guard cannot expand is a target it cannot judge. sh has no regex here, so test the
|
|
82
|
-
# four unexpandable shapes directly.
|
|
83
|
-
case "$DEST" in
|
|
84
|
-
*'$'*|*'`'*|'~'|'~/'*) REASON='This cd target is not a literal path, so the guards cannot tell where the shell will end up. Use a literal absolute path: cd /abs/path && <your command>. A $VAR, ~, $(...) or backtick is never expanded by the guard.' ;;
|
|
85
|
-
esac
|
|
86
|
-
fi
|
|
87
|
-
|
|
88
|
-
if [ -z "$REASON" ]; then
|
|
89
|
-
# Resolve against the shell's real cwd. A destination that does not exist needs no verdict: the cd
|
|
90
|
-
# itself will fail and the shell stays exactly where it is.
|
|
91
|
-
ABS="$(CDPATH= cd -- "${CWD:-.}" 2>/dev/null && CDPATH= cd -- "$DEST" 2>/dev/null && pwd)"
|
|
92
|
-
if [ -z "$ABS" ]; then wp_cd_log ALLOW-NO-SUCH-DIR "$DEST"; exit 0; fi
|
|
93
|
-
|
|
94
|
-
# TEST 1 — a git tree of any kind. A worktree's .git is a FILE, a clone's is a DIR; -e covers both.
|
|
95
|
-
if [ -e "$ABS/.git" ]; then wp_cd_log ALLOW-GIT-TREE "$ABS"; exit 0; fi
|
|
96
|
-
|
|
97
|
-
# TEST 2 — outside the governed project. The harness resets the cwd before the next call, so at most
|
|
98
|
-
# one command runs there, on paths we do not govern anyway.
|
|
99
|
-
case "$ABS/" in
|
|
100
|
-
"$CLAUDE_PROJECT_DIR"/*) ;;
|
|
101
|
-
*) wp_cd_log ALLOW-OUTSIDE "$ABS"; exit 0 ;;
|
|
102
|
-
esac
|
|
103
|
-
|
|
104
|
-
# TEST 3 — inside a governed tree with no shim beside it: sticky AND unguarded.
|
|
105
|
-
REASON="$(printf 'The webpieces guard hooks are registered RELATIVE (.claude/webpieces/ai-hook.sh) so that each git tree is governed by its own release. %s has no .claude/webpieces/ai-hook.sh, so a shell parked there launches NO hooks at all and every later tool call runs UNGUARDED - and a cd that stays inside the project PERSISTS to your next call. Run it from the tree root instead: cd %s && <your command>. Tools that take their own directory (git -C, pnpm -C, pnpm --filter, nx) need no cd at all.' "$ABS" "$CLAUDE_PROJECT_DIR")"
|
|
106
|
-
fi
|
|
107
|
-
|
|
108
|
-
wp_cd_log DENY "${ABS:-$DEST}"
|
|
109
|
-
|
|
110
|
-
BS='\' # one literal backslash, so the \u001b escape never sits in this source
|
|
111
|
-
ESC="${BS}u001b" # the 6 chars: backslash u 0 0 1 b — Claude Code parses \u001b -> ESC
|
|
112
|
-
printf '{"systemMessage":"%s🛑 %s%s","hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"%s"}}\n' "${ESC}[31;1m" "$REASON" "${ESC}[0m" "$REASON"
|
|
113
|
-
exit 0 # the decision is carried by permissionDecision deny, not the exit code
|