@paths.design/caws-cli 11.1.7 → 11.1.8
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/dist/index.js +55 -58
- package/dist/init/hook-packs/manifest-claude-code.d.ts +1 -1
- package/dist/init/hook-packs/manifest-claude-code.d.ts.map +1 -1
- package/dist/init/hook-packs/manifest-claude-code.js +260 -2
- package/dist/init/hook-packs/manifest-claude-code.js.map +1 -1
- package/dist/shell/binding/resolve-binding.d.ts.map +1 -1
- package/dist/shell/binding/resolve-binding.js +105 -1
- package/dist/shell/binding/resolve-binding.js.map +1 -1
- package/dist/shell/binding/types.d.ts +47 -3
- package/dist/shell/binding/types.d.ts.map +1 -1
- package/dist/shell/command-metadata.d.ts +93 -0
- package/dist/shell/command-metadata.d.ts.map +1 -0
- package/dist/shell/command-metadata.js +687 -0
- package/dist/shell/command-metadata.js.map +1 -0
- package/dist/shell/commands/agents.d.ts +1 -2
- package/dist/shell/commands/agents.d.ts.map +1 -1
- package/dist/shell/commands/claim.d.ts +16 -0
- package/dist/shell/commands/claim.d.ts.map +1 -1
- package/dist/shell/commands/claim.js +85 -26
- package/dist/shell/commands/claim.js.map +1 -1
- package/dist/shell/commands/events.d.ts +106 -0
- package/dist/shell/commands/events.d.ts.map +1 -0
- package/dist/shell/commands/events.js +510 -0
- package/dist/shell/commands/events.js.map +1 -0
- package/dist/shell/commands/gates.d.ts +2 -2
- package/dist/shell/commands/gates.d.ts.map +1 -1
- package/dist/shell/commands/gates.js +106 -25
- package/dist/shell/commands/gates.js.map +1 -1
- package/dist/shell/commands/init.d.ts.map +1 -1
- package/dist/shell/commands/init.js +26 -0
- package/dist/shell/commands/init.js.map +1 -1
- package/dist/shell/commands/prepush.d.ts +26 -0
- package/dist/shell/commands/prepush.d.ts.map +1 -0
- package/dist/shell/commands/prepush.js +373 -0
- package/dist/shell/commands/prepush.js.map +1 -0
- package/dist/shell/commands/scope.d.ts.map +1 -1
- package/dist/shell/commands/scope.js +31 -1
- package/dist/shell/commands/scope.js.map +1 -1
- package/dist/shell/commands/specs.d.ts +44 -3
- package/dist/shell/commands/specs.d.ts.map +1 -1
- package/dist/shell/commands/specs.js +411 -15
- package/dist/shell/commands/specs.js.map +1 -1
- package/dist/shell/commands/worktree.d.ts.map +1 -1
- package/dist/shell/commands/worktree.js +51 -1
- package/dist/shell/commands/worktree.js.map +1 -1
- package/dist/shell/gates/disposition.d.ts.map +1 -1
- package/dist/shell/gates/disposition.js +43 -2
- package/dist/shell/gates/disposition.js.map +1 -1
- package/dist/shell/index.d.ts +10 -4
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +22 -2
- package/dist/shell/index.js.map +1 -1
- package/dist/shell/legacy-command-map.js +832 -0
- package/dist/shell/push-range/classify-range.d.ts +99 -0
- package/dist/shell/push-range/classify-range.d.ts.map +1 -0
- package/dist/shell/push-range/classify-range.js +155 -0
- package/dist/shell/push-range/classify-range.js.map +1 -0
- package/dist/shell/push-range/scope-match.d.ts +13 -0
- package/dist/shell/push-range/scope-match.d.ts.map +1 -0
- package/dist/shell/push-range/scope-match.js +53 -0
- package/dist/shell/push-range/scope-match.js.map +1 -0
- package/dist/shell/register.d.ts.map +1 -1
- package/dist/shell/register.js +263 -228
- package/dist/shell/register.js.map +1 -1
- package/dist/shell/registered-command-groups.js +48 -0
- package/dist/shell/rules.d.ts +19 -0
- package/dist/shell/rules.d.ts.map +1 -1
- package/dist/shell/rules.js +27 -0
- package/dist/shell/rules.js.map +1 -1
- package/dist/shell/session/resolve-session.d.ts +29 -1
- package/dist/shell/session/resolve-session.d.ts.map +1 -1
- package/dist/shell/session/resolve-session.js +817 -11
- package/dist/shell/session/resolve-session.js.map +1 -1
- package/dist/shell/session/types.d.ts +127 -1
- package/dist/shell/session/types.d.ts.map +1 -1
- package/dist/shell/session/types.js +10 -4
- package/dist/shell/session/types.js.map +1 -1
- package/dist/store/doctor-snapshot.d.ts.map +1 -1
- package/dist/store/doctor-snapshot.js +26 -0
- package/dist/store/doctor-snapshot.js.map +1 -1
- package/dist/store/events-migration.d.ts +207 -0
- package/dist/store/events-migration.d.ts.map +1 -0
- package/dist/store/events-migration.js +358 -0
- package/dist/store/events-migration.js.map +1 -0
- package/dist/store/events-store.d.ts +47 -1
- package/dist/store/events-store.d.ts.map +1 -1
- package/dist/store/events-store.js +278 -0
- package/dist/store/events-store.js.map +1 -1
- package/dist/store/git-autocommit.d.ts +46 -0
- package/dist/store/git-autocommit.d.ts.map +1 -0
- package/dist/store/git-autocommit.js +198 -0
- package/dist/store/git-autocommit.js.map +1 -0
- package/dist/store/index.d.ts +4 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +7 -1
- package/dist/store/index.js.map +1 -1
- package/dist/store/leases-store.d.ts.map +1 -1
- package/dist/store/leases-store.js +58 -0
- package/dist/store/leases-store.js.map +1 -1
- package/dist/store/rules.d.ts +53 -0
- package/dist/store/rules.d.ts.map +1 -1
- package/dist/store/rules.js +54 -0
- package/dist/store/rules.js.map +1 -1
- package/dist/store/specs-migration.d.ts +128 -0
- package/dist/store/specs-migration.d.ts.map +1 -0
- package/dist/store/specs-migration.js +481 -0
- package/dist/store/specs-migration.js.map +1 -0
- package/dist/store/specs-store.d.ts.map +1 -1
- package/dist/store/specs-store.js +14 -2
- package/dist/store/specs-store.js.map +1 -1
- package/dist/store/specs-writer.d.ts +130 -3
- package/dist/store/specs-writer.d.ts.map +1 -1
- package/dist/store/specs-writer.js +941 -102
- package/dist/store/specs-writer.js.map +1 -1
- package/dist/store/types.d.ts +6 -0
- package/dist/store/types.d.ts.map +1 -1
- package/dist/store/waivers-store.d.ts.map +1 -1
- package/dist/store/waivers-store.js +8 -1
- package/dist/store/waivers-store.js.map +1 -1
- package/dist/store/worktrees-writer.d.ts +28 -0
- package/dist/store/worktrees-writer.d.ts.map +1 -1
- package/dist/store/worktrees-writer.js +110 -12
- package/dist/store/worktrees-writer.js.map +1 -1
- package/package.json +5 -2
- package/templates/hook-packs/claude-code/CLAUDE.md +7 -1
- package/templates/hook-packs/claude-code/agent-heartbeat.sh +1 -1
- package/templates/hook-packs/claude-code/agent-register.sh +1 -1
- package/templates/hook-packs/claude-code/agent-stop.sh +1 -1
- package/templates/hook-packs/claude-code/audit.sh +1 -1
- package/templates/hook-packs/claude-code/block-dangerous.sh +1 -1
- package/templates/hook-packs/claude-code/classify_command.py +1 -1
- package/templates/hook-packs/claude-code/cwd-guard.sh +30 -0
- package/templates/hook-packs/claude-code/dispatch/post_tool_use.sh +15 -4
- package/templates/hook-packs/claude-code/dispatch/pre_tool_use.sh +10 -2
- package/templates/hook-packs/claude-code/dispatch/session_start.sh +1 -1
- package/templates/hook-packs/claude-code/dispatch/stop.sh +2 -2
- package/templates/hook-packs/claude-code/duplicate-export-check.sh +156 -0
- package/templates/hook-packs/claude-code/god-object-check.sh +102 -0
- package/templates/hook-packs/claude-code/guard-strikes.sh +1 -1
- package/templates/hook-packs/claude-code/lib/parse-input.sh +115 -1
- package/templates/hook-packs/claude-code/lib/run-handlers.sh +1 -1
- package/templates/hook-packs/claude-code/loc-delta-check.sh +91 -0
- package/templates/hook-packs/claude-code/naming-check.sh +128 -0
- package/templates/hook-packs/claude-code/plan-transcript-finalize.sh +59 -0
- package/templates/hook-packs/claude-code/plan-transcript-snapshot.sh +86 -0
- package/templates/hook-packs/claude-code/protected-paths.sh +59 -0
- package/templates/hook-packs/claude-code/quiet-merge.sh +68 -0
- package/templates/hook-packs/claude-code/reset-danger-latch.sh +1 -1
- package/templates/hook-packs/claude-code/reset-strikes.sh +1 -1
- package/templates/hook-packs/claude-code/runtime-paths.sh +1 -1
- package/templates/hook-packs/claude-code/scan-secrets.sh +98 -0
- package/templates/hook-packs/claude-code/scope-guard.sh +47 -65
- package/templates/hook-packs/claude-code/session-caws-status.sh +1 -1
- package/templates/hook-packs/claude-code/session-log.sh +1 -1
- package/templates/hook-packs/claude-code/session_log_renderer.py +956 -0
- package/templates/hook-packs/claude-code/shortcut-language-check.sh +147 -0
- package/templates/hook-packs/claude-code/worktree-guard.sh +1 -1
- package/templates/hook-packs/claude-code/worktree-write-guard.sh +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# CAWS-MANAGED-HOOK
|
|
3
3
|
# hook_pack: claude-code
|
|
4
|
-
# hook_pack_version:
|
|
4
|
+
# hook_pack_version: 11
|
|
5
5
|
# caws_min_major: 11
|
|
6
6
|
# lineage_refs: 8,16
|
|
7
7
|
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
@@ -124,4 +124,118 @@ for k, v in fields.items():
|
|
|
124
124
|
HOOK_STOP_HOOK_ACTIVE="${HOOK_STOP_HOOK_ACTIVE:-0}" \
|
|
125
125
|
HOOK_TOOL_INPUT_JSON="${HOOK_TOOL_INPUT_JSON:-{\}}" \
|
|
126
126
|
HOOK_TOOL_RESPONSE_JSON="${HOOK_TOOL_RESPONSE_JSON:-{\}}"
|
|
127
|
+
|
|
128
|
+
# CAWS-SESSION-ID-DURABLE-HOOK-ENVELOPE-001: write/refresh the
|
|
129
|
+
# durable session envelope so agent-Bash CLI invocations (which
|
|
130
|
+
# don't inherit HOOK_SESSION_ID) can recover the harness session id
|
|
131
|
+
# via the on-disk bridge. Skipped when session id is missing/unknown
|
|
132
|
+
# (the resolver refuses literal "unknown" anyway). Non-fatal on any
|
|
133
|
+
# error — the hook must never block on this cache write.
|
|
134
|
+
_write_durable_session_envelope
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# CAWS-SESSION-ID-DURABLE-HOOK-ENVELOPE-001
|
|
138
|
+
# Write/refresh `<repo_root>/tmp/<session_id>/.session-envelope.json`.
|
|
139
|
+
# Called from parse_hook_input after exports are set. Idempotent.
|
|
140
|
+
# Preserves created_at across refreshes; updates last_seen_at to now.
|
|
141
|
+
# All failures are silently swallowed; hooks MUST NOT block on cache.
|
|
142
|
+
_write_durable_session_envelope() {
|
|
143
|
+
# Refuse missing/unknown/empty session id. The resolver refuses the
|
|
144
|
+
# literal "unknown" so writing it would just produce a stale file
|
|
145
|
+
# that gets skipped on read.
|
|
146
|
+
local sid="${HOOK_SESSION_ID:-}"
|
|
147
|
+
if [[ -z "$sid" || "$sid" == "unknown" ]]; then
|
|
148
|
+
return 0
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# Repo root via git. If HOOK_CWD is empty or git fails, skip — we
|
|
152
|
+
# can't write a repo-keyed envelope without one.
|
|
153
|
+
local cwd="${HOOK_CWD:-$PWD}"
|
|
154
|
+
local repo_root
|
|
155
|
+
repo_root=$(cd "$cwd" 2>/dev/null && git rev-parse --show-toplevel 2>/dev/null) || return 0
|
|
156
|
+
[[ -z "$repo_root" ]] && return 0
|
|
157
|
+
|
|
158
|
+
local envelope_dir="$repo_root/tmp/$sid"
|
|
159
|
+
local envelope_path="$envelope_dir/.session-envelope.json"
|
|
160
|
+
mkdir -p "$envelope_dir" 2>/dev/null || return 0
|
|
161
|
+
|
|
162
|
+
# Preserve created_at from existing envelope (refresh semantics).
|
|
163
|
+
# Use python for JSON read; if parse fails, treat as new envelope.
|
|
164
|
+
local now created_at
|
|
165
|
+
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
166
|
+
created_at="$now"
|
|
167
|
+
if [[ -f "$envelope_path" ]]; then
|
|
168
|
+
local existing_created
|
|
169
|
+
existing_created=$(python3 -c '
|
|
170
|
+
import json, sys
|
|
171
|
+
try:
|
|
172
|
+
with open(sys.argv[1]) as f:
|
|
173
|
+
d = json.load(f)
|
|
174
|
+
v = d.get("created_at")
|
|
175
|
+
if isinstance(v, str) and v:
|
|
176
|
+
print(v)
|
|
177
|
+
except Exception:
|
|
178
|
+
pass
|
|
179
|
+
' "$envelope_path" 2>/dev/null)
|
|
180
|
+
if [[ -n "$existing_created" ]]; then
|
|
181
|
+
created_at="$existing_created"
|
|
182
|
+
fi
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# Atomic write: temp file + rename. tmpfile is in the same dir to
|
|
186
|
+
# guarantee same-filesystem rename atomicity.
|
|
187
|
+
local tmpfile="$envelope_dir/.session-envelope.tmp.$$"
|
|
188
|
+
python3 -c '
|
|
189
|
+
import json, sys
|
|
190
|
+
payload = {
|
|
191
|
+
"session_id": sys.argv[1],
|
|
192
|
+
"repo_root": sys.argv[2],
|
|
193
|
+
"created_at": sys.argv[3],
|
|
194
|
+
"last_seen_at": sys.argv[4],
|
|
195
|
+
"hook_event": sys.argv[5],
|
|
196
|
+
}
|
|
197
|
+
with open(sys.argv[6], "w") as f:
|
|
198
|
+
json.dump(payload, f)
|
|
199
|
+
f.write("\n")
|
|
200
|
+
' "$sid" "$repo_root" "$created_at" "$now" "${HOOK_EVENT_NAME:-unknown}" "$tmpfile" 2>/dev/null || {
|
|
201
|
+
rm -f "$tmpfile" 2>/dev/null
|
|
202
|
+
return 0
|
|
203
|
+
}
|
|
204
|
+
mv -f "$tmpfile" "$envelope_path" 2>/dev/null || {
|
|
205
|
+
rm -f "$tmpfile" 2>/dev/null
|
|
206
|
+
return 0
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# CAWS-WORKTREE-OWNERSHIP-HARNESS-ID-001: also write/refresh the per-repo
|
|
210
|
+
# caller-session pointer at `<repo_root>/tmp/.caller-session.json`. In
|
|
211
|
+
# agent-Bash, HOOK_SESSION_ID is not in the env, so the resolver cannot
|
|
212
|
+
# tell which of several fresh sibling envelopes is the caller's. This
|
|
213
|
+
# pointer names the session that most recently fired a hook in this repo
|
|
214
|
+
# — the actively-working caller — so the resolver can disambiguate the
|
|
215
|
+
# >=2-fresh-envelope case to the caller's own envelope. Evidence only:
|
|
216
|
+
# the resolver treats absent/stale/non-matching pointers as "refuse",
|
|
217
|
+
# never as a guess. Reuses sid / repo_root / now from above.
|
|
218
|
+
local pointer_dir="$repo_root/tmp"
|
|
219
|
+
local pointer_path="$pointer_dir/.caller-session.json"
|
|
220
|
+
local pointer_tmp="$pointer_dir/.caller-session.tmp.$$"
|
|
221
|
+
mkdir -p "$pointer_dir" 2>/dev/null || return 0
|
|
222
|
+
python3 -c '
|
|
223
|
+
import json, sys
|
|
224
|
+
payload = {
|
|
225
|
+
"session_id": sys.argv[1],
|
|
226
|
+
"repo_root": sys.argv[2],
|
|
227
|
+
"last_seen_at": sys.argv[3],
|
|
228
|
+
}
|
|
229
|
+
with open(sys.argv[4], "w") as f:
|
|
230
|
+
json.dump(payload, f)
|
|
231
|
+
f.write("\n")
|
|
232
|
+
' "$sid" "$repo_root" "$now" "$pointer_tmp" 2>/dev/null || {
|
|
233
|
+
rm -f "$pointer_tmp" 2>/dev/null
|
|
234
|
+
return 0
|
|
235
|
+
}
|
|
236
|
+
mv -f "$pointer_tmp" "$pointer_path" 2>/dev/null || {
|
|
237
|
+
rm -f "$pointer_tmp" 2>/dev/null
|
|
238
|
+
return 0
|
|
239
|
+
}
|
|
240
|
+
return 0
|
|
127
241
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CAWS-MANAGED-HOOK
|
|
3
|
+
# hook_pack: claude-code
|
|
4
|
+
# hook_pack_version: 11
|
|
5
|
+
# caws_min_major: 11
|
|
6
|
+
# lineage_refs: 31
|
|
7
|
+
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
|
+
#
|
|
9
|
+
# CAWS LOC-Delta Advisory Check (QG-HOOKS-EXTRACT-001)
|
|
10
|
+
#
|
|
11
|
+
# Advisory-only PostToolUse hook firing on Edit. Flags a single edit that adds
|
|
12
|
+
# more than a configurable number of lines to one file — a refactoring-budget
|
|
13
|
+
# signal (CLAUDE.md "Ask first for risky changes ... >300 LOC"). It NEVER
|
|
14
|
+
# blocks, NEVER mutates, and does not depend on any quality-gates module.
|
|
15
|
+
#
|
|
16
|
+
# Delta source (priority order):
|
|
17
|
+
# 1. Edit payload — newline-count(new_string) - newline-count(old_string).
|
|
18
|
+
# Exact, synchronous, works on untracked files and in worktrees where a
|
|
19
|
+
# git diff would be misleading. This is the primary source.
|
|
20
|
+
# 2. If the payload lacks old_string/new_string (e.g. a Write or a multi-edit
|
|
21
|
+
# batch shape this hook doesn't model), exit 0 silently — an advisory
|
|
22
|
+
# hook must never produce a false positive from missing data.
|
|
23
|
+
#
|
|
24
|
+
# env:
|
|
25
|
+
# CAWS_LOC_DELTA_WARN_THRESHOLD added-line threshold (default 300)
|
|
26
|
+
|
|
27
|
+
set -uo pipefail
|
|
28
|
+
|
|
29
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
30
|
+
# shellcheck source=lib/parse-input.sh
|
|
31
|
+
source "$SCRIPT_DIR/lib/parse-input.sh" 2>/dev/null || exit 0
|
|
32
|
+
parse_hook_input || exit 0
|
|
33
|
+
|
|
34
|
+
FILE_PATH="$HOOK_FILE_PATH"
|
|
35
|
+
TOOL_NAME="$HOOK_TOOL_NAME"
|
|
36
|
+
|
|
37
|
+
# Edit only — the LOC-delta signal is about growing an existing file.
|
|
38
|
+
[[ "$TOOL_NAME" != "Edit" ]] && exit 0
|
|
39
|
+
[[ -z "$FILE_PATH" ]] && exit 0
|
|
40
|
+
|
|
41
|
+
# Skip generated / vendored / build output.
|
|
42
|
+
case "$FILE_PATH" in
|
|
43
|
+
*/node_modules/* | */dist/* | */build/* | */coverage/* | */.next/* | */out/* | */vendor/*)
|
|
44
|
+
exit 0
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
47
|
+
case "$(basename "$FILE_PATH")" in
|
|
48
|
+
*.min.js | *.bundle.js | *.map | *.lock | *-lock.json | package-lock.json)
|
|
49
|
+
exit 0
|
|
50
|
+
;;
|
|
51
|
+
esac
|
|
52
|
+
|
|
53
|
+
THRESHOLD="${CAWS_LOC_DELTA_WARN_THRESHOLD:-300}"
|
|
54
|
+
[[ "$THRESHOLD" =~ ^[0-9]+$ ]] || THRESHOLD=300
|
|
55
|
+
|
|
56
|
+
# Need jq + the payload to read old_string/new_string.
|
|
57
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
58
|
+
[[ -n "${HOOK_TOOL_INPUT_JSON:-}" ]] || exit 0
|
|
59
|
+
|
|
60
|
+
# Presence check: only proceed if BOTH fields exist in the payload. A missing
|
|
61
|
+
# field => exit silently (priority-2 rule: no false positives from missing data).
|
|
62
|
+
HAS_OLD=$(printf '%s' "$HOOK_TOOL_INPUT_JSON" | jq -r 'has("old_string")' 2>/dev/null || printf 'false')
|
|
63
|
+
HAS_NEW=$(printf '%s' "$HOOK_TOOL_INPUT_JSON" | jq -r 'has("new_string")' 2>/dev/null || printf 'false')
|
|
64
|
+
[[ "$HAS_OLD" == "true" && "$HAS_NEW" == "true" ]] || exit 0
|
|
65
|
+
|
|
66
|
+
# Line counts via jq: number of lines in each string. A string with N
|
|
67
|
+
# newlines spans N+1 lines, but for an added-LOC delta the newline count is
|
|
68
|
+
# the right comparator (it counts line-breaks introduced).
|
|
69
|
+
OLD_LINES=$(printf '%s' "$HOOK_TOOL_INPUT_JSON" | jq -r '(.old_string // "") | (. / "\n" | length) - 1' 2>/dev/null || printf '0')
|
|
70
|
+
NEW_LINES=$(printf '%s' "$HOOK_TOOL_INPUT_JSON" | jq -r '(.new_string // "") | (. / "\n" | length) - 1' 2>/dev/null || printf '0')
|
|
71
|
+
|
|
72
|
+
# Normalize to integers; an empty string yields -1 from the formula above,
|
|
73
|
+
# clamp to 0.
|
|
74
|
+
[[ "$OLD_LINES" =~ ^-?[0-9]+$ ]] || OLD_LINES=0
|
|
75
|
+
[[ "$NEW_LINES" =~ ^-?[0-9]+$ ]] || NEW_LINES=0
|
|
76
|
+
(( OLD_LINES < 0 )) && OLD_LINES=0
|
|
77
|
+
(( NEW_LINES < 0 )) && NEW_LINES=0
|
|
78
|
+
|
|
79
|
+
DELTA=$(( NEW_LINES - OLD_LINES ))
|
|
80
|
+
|
|
81
|
+
if (( DELTA > THRESHOLD )); then
|
|
82
|
+
MSG="LOC-delta advisory: this edit to ${FILE_PATH} adds ~${DELTA} lines (> ${THRESHOLD} threshold). Large single edits are hard to review and often signal that the change should be split into smaller, focused commits or that a new module is warranted. (Advisory only — set CAWS_LOC_DELTA_WARN_THRESHOLD to tune; this never blocks.)"
|
|
83
|
+
jq -n --arg msg "$MSG" '{
|
|
84
|
+
hookSpecificOutput: {
|
|
85
|
+
hookEventName: "PostToolUse",
|
|
86
|
+
additionalContext: $msg
|
|
87
|
+
}
|
|
88
|
+
}'
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
exit 0
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CAWS-MANAGED-HOOK
|
|
3
|
+
# hook_pack: claude-code
|
|
4
|
+
# hook_pack_version: 11
|
|
5
|
+
# caws_min_major: 11
|
|
6
|
+
# lineage_refs: 25
|
|
7
|
+
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
|
+
#
|
|
9
|
+
# CAWS Naming Convention Check Hook for Claude Code
|
|
10
|
+
#
|
|
11
|
+
# Advisory-only PostToolUse hook that fires on Write (new file creation
|
|
12
|
+
# only). Flags filenames that violate the "no shadow files" doctrine
|
|
13
|
+
# stated in CLAUDE.md:
|
|
14
|
+
#
|
|
15
|
+
# - Banned modifier suffixes (enhanced, unified, simplified, new,
|
|
16
|
+
# next, final, copy, revamp, improved, alt, tmp, scratch, wip,
|
|
17
|
+
# temp, old, backup, plus test-variant equivalents)
|
|
18
|
+
# - Version suffixes (-v2., _v3., etc.) — git is the version
|
|
19
|
+
# control surface, not filenames
|
|
20
|
+
# - Date stamps (YYYY-MM-DD) — same reason
|
|
21
|
+
#
|
|
22
|
+
# Test files with canonical extensions (.test.js, .spec.ts, etc.)
|
|
23
|
+
# are explicitly exempted from the test-related modifier checks.
|
|
24
|
+
#
|
|
25
|
+
# Promoted from Sterling per CAWS-HOOK-PACK-PROMOTE-001. The advisory
|
|
26
|
+
# messages have been genericized: removed Sterling-era references to
|
|
27
|
+
# the v10 `caws naming check` CLI command (which v11 does not ship)
|
|
28
|
+
# and to `.caws/canonical-map.yaml` (a v10 artifact). The doctrine
|
|
29
|
+
# the hook enforces remains: CLAUDE.md key rule "No shadow files —
|
|
30
|
+
# edit in place, never create `*-enhanced.*`, `*-new.*`, `*-v2.*`
|
|
31
|
+
# copies".
|
|
32
|
+
|
|
33
|
+
set -euo pipefail
|
|
34
|
+
|
|
35
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
36
|
+
# shellcheck source=lib/parse-input.sh
|
|
37
|
+
source "$SCRIPT_DIR/lib/parse-input.sh"
|
|
38
|
+
parse_hook_input
|
|
39
|
+
|
|
40
|
+
FILE_PATH="$HOOK_FILE_PATH"
|
|
41
|
+
TOOL_NAME="$HOOK_TOOL_NAME"
|
|
42
|
+
|
|
43
|
+
# Only check Write tool (new files)
|
|
44
|
+
if [[ "$TOOL_NAME" != "Write" ]]; then
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if [[ -z "$FILE_PATH" ]]; then
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Get filename
|
|
53
|
+
FILENAME=$(basename "$FILE_PATH")
|
|
54
|
+
|
|
55
|
+
# Banned modifiers that indicate incomplete/temporary naming
|
|
56
|
+
BANNED_MODIFIERS=(
|
|
57
|
+
"enhanced"
|
|
58
|
+
"unified"
|
|
59
|
+
"simplified"
|
|
60
|
+
"better"
|
|
61
|
+
"new"
|
|
62
|
+
"next"
|
|
63
|
+
"final"
|
|
64
|
+
"copy"
|
|
65
|
+
"revamp"
|
|
66
|
+
"improved"
|
|
67
|
+
"alt"
|
|
68
|
+
"tmp"
|
|
69
|
+
"scratch"
|
|
70
|
+
"wip"
|
|
71
|
+
"test-"
|
|
72
|
+
"-test"
|
|
73
|
+
"_test"
|
|
74
|
+
"temp"
|
|
75
|
+
"old"
|
|
76
|
+
"backup"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Convert filename to lowercase for checking
|
|
80
|
+
FILENAME_LOWER=$(echo "$FILENAME" | tr '[:upper:]' '[:lower:]')
|
|
81
|
+
|
|
82
|
+
# Check for banned modifiers (word-boundary aware)
|
|
83
|
+
for modifier in "${BANNED_MODIFIERS[@]}"; do
|
|
84
|
+
# Use word-boundary matching to avoid false positives (e.g., "old" in "gold_oracle")
|
|
85
|
+
# Match modifier preceded by start-of-string, hyphen, or underscore
|
|
86
|
+
# and followed by end-of-string, hyphen, underscore, or dot
|
|
87
|
+
if [[ "$FILENAME_LOWER" =~ (^|[-_.])"$modifier"([-_.]|$) ]]; then
|
|
88
|
+
# Special case: allow test files that follow conventions
|
|
89
|
+
if [[ "$modifier" == "test-" ]] || [[ "$modifier" == "-test" ]] || [[ "$modifier" == "_test" ]]; then
|
|
90
|
+
if [[ "$FILENAME_LOWER" =~ \.(test|spec)\.(js|ts|jsx|tsx|py|go|rs)$ ]]; then
|
|
91
|
+
continue
|
|
92
|
+
fi
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
echo '{
|
|
96
|
+
"hookSpecificOutput": {
|
|
97
|
+
"hookEventName": "PostToolUse",
|
|
98
|
+
"additionalContext": "Warning: The filename '\'''"$FILENAME"''\'' contains the modifier '\'''"$modifier"''\'' which may indicate temporary or non-canonical naming. Per the CAWS doctrine (CLAUDE.md key rule \"No shadow files\"), edit existing files in place rather than creating *-enhanced.*, *-new.*, *-v2.* copies. Consider using a more descriptive, permanent name."
|
|
99
|
+
}
|
|
100
|
+
}'
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
done
|
|
104
|
+
|
|
105
|
+
# Check for version suffixes (e.g., file-v2.js, file_v3.ts)
|
|
106
|
+
if [[ "$FILENAME_LOWER" =~ [-_]v[0-9]+\. ]]; then
|
|
107
|
+
echo '{
|
|
108
|
+
"hookSpecificOutput": {
|
|
109
|
+
"hookEventName": "PostToolUse",
|
|
110
|
+
"additionalContext": "Warning: The filename '\'''"$FILENAME"''\'' contains a version suffix. Version control should be handled by git, not file names. Consider removing the version suffix."
|
|
111
|
+
}
|
|
112
|
+
}'
|
|
113
|
+
exit 0
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Check for date stamps (e.g., file-2024-01-15.js)
|
|
117
|
+
if [[ "$FILENAME_LOWER" =~ [0-9]{4}[-_][0-9]{2}[-_][0-9]{2} ]]; then
|
|
118
|
+
echo '{
|
|
119
|
+
"hookSpecificOutput": {
|
|
120
|
+
"hookEventName": "PostToolUse",
|
|
121
|
+
"additionalContext": "Warning: The filename '\'''"$FILENAME"''\'' contains a date stamp. Version control should be handled by git, not file names. Consider removing the date."
|
|
122
|
+
}
|
|
123
|
+
}'
|
|
124
|
+
exit 0
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# File naming is OK
|
|
128
|
+
exit 0
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CAWS-MANAGED-HOOK
|
|
3
|
+
# hook_pack: claude-code
|
|
4
|
+
# hook_pack_version: 11
|
|
5
|
+
# caws_min_major: 11
|
|
6
|
+
# lineage_refs: 27
|
|
7
|
+
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
|
+
#
|
|
9
|
+
# Plan Transcript Finalize — overwrite each pending plan-transcript
|
|
10
|
+
# snapshot with the final turn-end transcript.
|
|
11
|
+
#
|
|
12
|
+
# Wired into: Stop dispatch.
|
|
13
|
+
#
|
|
14
|
+
# At hook time:
|
|
15
|
+
# - The agent's turn is ending. The transcript at $TRANSCRIPT_PATH
|
|
16
|
+
# now includes everything that happened: the agent's plan,
|
|
17
|
+
# ExitPlanMode presentation, user approval/rejection, and any
|
|
18
|
+
# subsequent reasoning or tool calls.
|
|
19
|
+
# - $HOME/.claude/.pending-plan-snapshots lists snapshot paths that
|
|
20
|
+
# plan-transcript-snapshot.sh registered earlier in this turn.
|
|
21
|
+
# - For each pending snapshot, overwrite with the final transcript
|
|
22
|
+
# so the file co-located next to the plan represents the FULL
|
|
23
|
+
# conversation context that produced + reviewed the plan.
|
|
24
|
+
# - Drain the pending list (the snapshots are now finalized; future
|
|
25
|
+
# plan presentations in future turns get fresh entries).
|
|
26
|
+
#
|
|
27
|
+
# Idempotency: this hook is safe to run multiple times. If the pending
|
|
28
|
+
# list is empty or doesn't exist, the hook is a no-op.
|
|
29
|
+
#
|
|
30
|
+
# Companion: plan-transcript-snapshot.sh (PostToolUse on ExitPlanMode)
|
|
31
|
+
# is the producer. Both ship together per CAWS-HOOK-PACK-PROMOTE-001.
|
|
32
|
+
|
|
33
|
+
set -euo pipefail
|
|
34
|
+
|
|
35
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
36
|
+
# shellcheck source=lib/parse-input.sh
|
|
37
|
+
source "$SCRIPT_DIR/lib/parse-input.sh"
|
|
38
|
+
parse_hook_input
|
|
39
|
+
|
|
40
|
+
TRANSCRIPT_PATH="$HOOK_TRANSCRIPT_PATH"
|
|
41
|
+
[ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ] || exit 0
|
|
42
|
+
|
|
43
|
+
PENDING="$HOME/.claude/.pending-plan-snapshots"
|
|
44
|
+
[ -f "$PENDING" ] || exit 0
|
|
45
|
+
|
|
46
|
+
# Process each pending snapshot. Order doesn't matter — each is just
|
|
47
|
+
# a file copy. Skip empty lines defensively.
|
|
48
|
+
while IFS= read -r snapshot; do
|
|
49
|
+
[ -z "$snapshot" ] && continue
|
|
50
|
+
snapshot_dir=$(dirname "$snapshot")
|
|
51
|
+
[ -d "$snapshot_dir" ] || continue
|
|
52
|
+
cp "$TRANSCRIPT_PATH" "$snapshot" 2>/dev/null || true
|
|
53
|
+
done < "$PENDING"
|
|
54
|
+
|
|
55
|
+
# Drain the pending list. New ExitPlanMode calls in subsequent turns
|
|
56
|
+
# will re-populate it via plan-transcript-snapshot.sh.
|
|
57
|
+
> "$PENDING" 2>/dev/null || true
|
|
58
|
+
|
|
59
|
+
exit 0
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CAWS-MANAGED-HOOK
|
|
3
|
+
# hook_pack: claude-code
|
|
4
|
+
# hook_pack_version: 11
|
|
5
|
+
# caws_min_major: 11
|
|
6
|
+
# lineage_refs: 27
|
|
7
|
+
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
|
+
#
|
|
9
|
+
# Plan Transcript Snapshot — capture conversation context at the moment
|
|
10
|
+
# the agent presents a plan via ExitPlanMode.
|
|
11
|
+
#
|
|
12
|
+
# Wired into: PostToolUse with matcher "ExitPlanMode" (self-filters on
|
|
13
|
+
# $HOOK_TOOL_NAME at the top).
|
|
14
|
+
#
|
|
15
|
+
# At hook time:
|
|
16
|
+
# - The agent has finished building the plan (Write/Edit calls done).
|
|
17
|
+
# - ExitPlanMode has just been called to present the plan to the user.
|
|
18
|
+
# - The transcript at $TRANSCRIPT_PATH contains everything up to this
|
|
19
|
+
# moment: exploration, design, tool results, the plan-write itself,
|
|
20
|
+
# and the ExitPlanMode tool_use record.
|
|
21
|
+
# - The user's approval/rejection and any subsequent reasoning are NOT
|
|
22
|
+
# yet in the transcript — those come later in the turn and are
|
|
23
|
+
# captured by plan-transcript-finalize.sh on Stop.
|
|
24
|
+
#
|
|
25
|
+
# Output:
|
|
26
|
+
# - <plan-path>.transcript.jsonl — co-located transcript snapshot.
|
|
27
|
+
# - $HOME/.claude/.pending-plan-snapshots — newline-separated list of
|
|
28
|
+
# snapshot paths awaiting Stop-hook finalization (overwrite with
|
|
29
|
+
# the final transcript at turn end).
|
|
30
|
+
#
|
|
31
|
+
# Privacy note: The transcript is unfiltered. It includes Bash command
|
|
32
|
+
# outputs, Read results, and any content that crossed the conversation.
|
|
33
|
+
# Sharing the .transcript.jsonl file is equivalent to sharing your full
|
|
34
|
+
# session for the turns leading up to the plan. Treat accordingly.
|
|
35
|
+
#
|
|
36
|
+
# Companion: plan-transcript-finalize.sh (Stop hook) drains the pending
|
|
37
|
+
# list and finalizes each snapshot with the turn-end transcript.
|
|
38
|
+
# Promoted from Sterling per CAWS-HOOK-PACK-PROMOTE-001.
|
|
39
|
+
|
|
40
|
+
set -euo pipefail
|
|
41
|
+
|
|
42
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
43
|
+
# shellcheck source=lib/parse-input.sh
|
|
44
|
+
source "$SCRIPT_DIR/lib/parse-input.sh"
|
|
45
|
+
parse_hook_input
|
|
46
|
+
|
|
47
|
+
# Hook gates — bail silently if anything is wrong rather than fail visibly.
|
|
48
|
+
# A snapshot hook should never block the agent's flow.
|
|
49
|
+
TOOL_NAME="$HOOK_TOOL_NAME"
|
|
50
|
+
[ "$TOOL_NAME" = "ExitPlanMode" ] || exit 0
|
|
51
|
+
|
|
52
|
+
TRANSCRIPT_PATH="$HOOK_TRANSCRIPT_PATH"
|
|
53
|
+
[ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ] || exit 0
|
|
54
|
+
|
|
55
|
+
# Find the most recent plan file Write in the transcript. The transcript
|
|
56
|
+
# is a JSONL stream of conversation events; grep is cheaper and more
|
|
57
|
+
# robust than jq for this lookup since field nesting can vary across
|
|
58
|
+
# Claude Code versions. Match: "file_path":"/.../.claude/plans/<name>.md"
|
|
59
|
+
#
|
|
60
|
+
# `|| true` swallows grep's exit-1 on no-match so set -e doesn't abort
|
|
61
|
+
# the script before our bail-out check runs.
|
|
62
|
+
PLAN_FILE=$(grep -oE '"file_path":"[^"]*/\.claude/plans/[^"]*\.md"' "$TRANSCRIPT_PATH" 2>/dev/null \
|
|
63
|
+
| tail -1 \
|
|
64
|
+
| sed -E 's/^"file_path":"//; s/"$//' \
|
|
65
|
+
|| true)
|
|
66
|
+
|
|
67
|
+
[ -n "$PLAN_FILE" ] && [ -f "$PLAN_FILE" ] || exit 0
|
|
68
|
+
|
|
69
|
+
# Snapshot at this moment (plan finalized + presented).
|
|
70
|
+
SNAPSHOT="${PLAN_FILE%.md}.transcript.jsonl"
|
|
71
|
+
cp "$TRANSCRIPT_PATH" "$SNAPSHOT" 2>/dev/null || exit 0
|
|
72
|
+
|
|
73
|
+
# Mark for Stop-hook finalization. The finalize hook will overwrite the
|
|
74
|
+
# snapshot with the FINAL turn-end transcript (which includes user
|
|
75
|
+
# approval, any subsequent reasoning, and the rest of the turn).
|
|
76
|
+
PENDING="$HOME/.claude/.pending-plan-snapshots"
|
|
77
|
+
mkdir -p "$(dirname "$PENDING")" 2>/dev/null || true
|
|
78
|
+
|
|
79
|
+
# Idempotent append: don't duplicate if already pending. Multiple
|
|
80
|
+
# ExitPlanMode calls in one session targeting the same plan file
|
|
81
|
+
# overwrite the snapshot but only register the snapshot path once.
|
|
82
|
+
if [ ! -f "$PENDING" ] || ! grep -qxF "$SNAPSHOT" "$PENDING" 2>/dev/null; then
|
|
83
|
+
echo "$SNAPSHOT" >> "$PENDING"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
exit 0
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CAWS-MANAGED-HOOK
|
|
3
|
+
# hook_pack: claude-code
|
|
4
|
+
# hook_pack_version: 11
|
|
5
|
+
# caws_min_major: 11
|
|
6
|
+
# lineage_refs: 8,16,23
|
|
7
|
+
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
|
+
#
|
|
9
|
+
# CAWS Protected Paths Guard for Claude Code
|
|
10
|
+
#
|
|
11
|
+
# Blocks direct Write/Edit access to:
|
|
12
|
+
# - hook scripts under .claude/hooks/* (no agent-side hook editing)
|
|
13
|
+
# - strike-state files .claude/logs/guard-strikes-*.json (no manual
|
|
14
|
+
# manipulation of progressive-strike counters)
|
|
15
|
+
#
|
|
16
|
+
# This is the structural enforcement of the doctrine that hooks
|
|
17
|
+
# "may not be removed or weakened by an agent's local judgment"
|
|
18
|
+
# (templates/hook-packs/claude-code/CLAUDE.md). Promoted from Sterling
|
|
19
|
+
# per CAWS-HOOK-PACK-PROMOTE-001.
|
|
20
|
+
#
|
|
21
|
+
# If you are reading this because a write was blocked, do not edit
|
|
22
|
+
# hook files or strike-state files to bypass a guard. Switch into the
|
|
23
|
+
# correct worktree, fix the active spec scope, or ask the user if the
|
|
24
|
+
# guard itself is wrong.
|
|
25
|
+
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
# shellcheck source=lib/parse-input.sh
|
|
30
|
+
source "$SCRIPT_DIR/lib/parse-input.sh"
|
|
31
|
+
parse_hook_input
|
|
32
|
+
|
|
33
|
+
case "$HOOK_TOOL_NAME" in
|
|
34
|
+
Write|Edit) ;;
|
|
35
|
+
*) exit 0 ;;
|
|
36
|
+
esac
|
|
37
|
+
|
|
38
|
+
if [[ -z "$HOOK_FILE_PATH" ]]; then
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
case "$HOOK_FILE_PATH" in
|
|
43
|
+
*/.claude/hooks/*)
|
|
44
|
+
echo "BLOCKED: $HOOK_FILE_PATH is protected." >&2
|
|
45
|
+
echo "Ask the user for permission before editing Claude hook scripts." >&2
|
|
46
|
+
exit 1
|
|
47
|
+
;;
|
|
48
|
+
*/.claude/logs/guard-strikes-*.json)
|
|
49
|
+
echo "BLOCKED: $HOOK_FILE_PATH is protected guard state." >&2
|
|
50
|
+
echo "Do not edit strike counters by hand to bypass enforcement." >&2
|
|
51
|
+
echo "If the scope was legitimately corrected and prior strikes are stale, ask the user to run:" >&2
|
|
52
|
+
echo " bash .claude/hooks/reset-strikes.sh --current" >&2
|
|
53
|
+
echo "(or --session <uuid> / --worktree <name> / --all --confirm; resets are logged)." >&2
|
|
54
|
+
echo "Otherwise switch into the correct worktree, update the active CAWS spec scope, or ask the user for direction." >&2
|
|
55
|
+
exit 2
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
|
|
59
|
+
exit 0
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CAWS-MANAGED-HOOK
|
|
3
|
+
# hook_pack: claude-code
|
|
4
|
+
# hook_pack_version: 11
|
|
5
|
+
# caws_min_major: 11
|
|
6
|
+
# lineage_refs: 22,26
|
|
7
|
+
# do_not_edit_directly: update via `caws init --agent-surface claude-code`
|
|
8
|
+
#
|
|
9
|
+
# Quiet merge hook: suppress verbose output AND fix CWD safety
|
|
10
|
+
#
|
|
11
|
+
# Two problems solved:
|
|
12
|
+
# 1. `caws worktree merge` produces verbose output that can overflow context.
|
|
13
|
+
# 2. When a subagent's CWD is inside the worktree being destroyed, the process
|
|
14
|
+
# loses its CWD and crashes (posix_spawn ENOENT on PostToolUse hooks).
|
|
15
|
+
#
|
|
16
|
+
# The fix: rewrite merge/destroy commands to:
|
|
17
|
+
# cd <repo-root> && <command> 2>/dev/null | tail -3
|
|
18
|
+
# This moves CWD to safety BEFORE the directory is destroyed, and suppresses
|
|
19
|
+
# verbose output.
|
|
20
|
+
#
|
|
21
|
+
# IMPORTANT: This hook MUST be the last PreToolUse hook for Bash commands
|
|
22
|
+
# that intercepts input. It emits updatedInput which replaces any prior
|
|
23
|
+
# hook's updatedInput. Order in dispatch/pre_tool_use.sh: after the
|
|
24
|
+
# blocking guards (so a real refusal still fires), before scan-secrets
|
|
25
|
+
# (which is advisory-only and emits additionalContext, not updatedInput).
|
|
26
|
+
#
|
|
27
|
+
# Promoted from Sterling per CAWS-HOOK-PACK-PROMOTE-001 and
|
|
28
|
+
# docs/reports/sterling_hook_port_audit_001.md. Companion to cwd-guard.sh
|
|
29
|
+
# (entry 22) for the worktree-destroyed-while-inside class.
|
|
30
|
+
|
|
31
|
+
set -euo pipefail
|
|
32
|
+
|
|
33
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
34
|
+
# shellcheck source=lib/parse-input.sh
|
|
35
|
+
source "$SCRIPT_DIR/lib/parse-input.sh"
|
|
36
|
+
parse_hook_input
|
|
37
|
+
|
|
38
|
+
TOOL_NAME="$HOOK_TOOL_NAME"
|
|
39
|
+
COMMAND="$HOOK_COMMAND"
|
|
40
|
+
|
|
41
|
+
# Only intercept Bash tool
|
|
42
|
+
if [[ "$TOOL_NAME" != "Bash" ]] || [[ -z "$COMMAND" ]]; then
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Resolve repo root (may differ from CLAUDE_PROJECT_DIR in worktrees)
|
|
47
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
48
|
+
if command -v git >/dev/null 2>&1; then
|
|
49
|
+
GIT_COMMON_DIR=$(cd "$PROJECT_DIR" && git rev-parse --git-common-dir 2>/dev/null || echo "")
|
|
50
|
+
if [[ -n "$GIT_COMMON_DIR" ]] && [[ "$GIT_COMMON_DIR" != ".git" ]]; then
|
|
51
|
+
CANDIDATE=$(cd "$PROJECT_DIR" && cd "$GIT_COMMON_DIR/.." 2>/dev/null && pwd || echo "")
|
|
52
|
+
if [[ -n "$CANDIDATE" ]] && [[ -d "$CANDIDATE/.caws" ]]; then
|
|
53
|
+
PROJECT_DIR="$CANDIDATE"
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Match: caws worktree merge|destroy <name> [options]
|
|
59
|
+
# Skip if already piped/redirected (user already handling output)
|
|
60
|
+
if echo "$COMMAND" | grep -qE 'caws\s+worktree\s+(merge|destroy)\b' && ! echo "$COMMAND" | grep -qE '[|>]'; then
|
|
61
|
+
# Always prepend cd to repo root for CWD safety (critical for subagents
|
|
62
|
+
# whose CWD is inside the worktree being destroyed)
|
|
63
|
+
QUIET_CMD="cd \"$PROJECT_DIR\" && $COMMAND 2>/dev/null | tail -3; echo '---'; git log --oneline -1"
|
|
64
|
+
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","updatedInput":{"command":%s}}}' "$(printf '%s' "$QUIET_CMD" | jq -Rs .)"
|
|
65
|
+
exit 0
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
exit 0
|