@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.
Files changed (158) hide show
  1. package/dist/index.js +55 -58
  2. package/dist/init/hook-packs/manifest-claude-code.d.ts +1 -1
  3. package/dist/init/hook-packs/manifest-claude-code.d.ts.map +1 -1
  4. package/dist/init/hook-packs/manifest-claude-code.js +260 -2
  5. package/dist/init/hook-packs/manifest-claude-code.js.map +1 -1
  6. package/dist/shell/binding/resolve-binding.d.ts.map +1 -1
  7. package/dist/shell/binding/resolve-binding.js +105 -1
  8. package/dist/shell/binding/resolve-binding.js.map +1 -1
  9. package/dist/shell/binding/types.d.ts +47 -3
  10. package/dist/shell/binding/types.d.ts.map +1 -1
  11. package/dist/shell/command-metadata.d.ts +93 -0
  12. package/dist/shell/command-metadata.d.ts.map +1 -0
  13. package/dist/shell/command-metadata.js +687 -0
  14. package/dist/shell/command-metadata.js.map +1 -0
  15. package/dist/shell/commands/agents.d.ts +1 -2
  16. package/dist/shell/commands/agents.d.ts.map +1 -1
  17. package/dist/shell/commands/claim.d.ts +16 -0
  18. package/dist/shell/commands/claim.d.ts.map +1 -1
  19. package/dist/shell/commands/claim.js +85 -26
  20. package/dist/shell/commands/claim.js.map +1 -1
  21. package/dist/shell/commands/events.d.ts +106 -0
  22. package/dist/shell/commands/events.d.ts.map +1 -0
  23. package/dist/shell/commands/events.js +510 -0
  24. package/dist/shell/commands/events.js.map +1 -0
  25. package/dist/shell/commands/gates.d.ts +2 -2
  26. package/dist/shell/commands/gates.d.ts.map +1 -1
  27. package/dist/shell/commands/gates.js +106 -25
  28. package/dist/shell/commands/gates.js.map +1 -1
  29. package/dist/shell/commands/init.d.ts.map +1 -1
  30. package/dist/shell/commands/init.js +26 -0
  31. package/dist/shell/commands/init.js.map +1 -1
  32. package/dist/shell/commands/prepush.d.ts +26 -0
  33. package/dist/shell/commands/prepush.d.ts.map +1 -0
  34. package/dist/shell/commands/prepush.js +373 -0
  35. package/dist/shell/commands/prepush.js.map +1 -0
  36. package/dist/shell/commands/scope.d.ts.map +1 -1
  37. package/dist/shell/commands/scope.js +31 -1
  38. package/dist/shell/commands/scope.js.map +1 -1
  39. package/dist/shell/commands/specs.d.ts +44 -3
  40. package/dist/shell/commands/specs.d.ts.map +1 -1
  41. package/dist/shell/commands/specs.js +411 -15
  42. package/dist/shell/commands/specs.js.map +1 -1
  43. package/dist/shell/commands/worktree.d.ts.map +1 -1
  44. package/dist/shell/commands/worktree.js +51 -1
  45. package/dist/shell/commands/worktree.js.map +1 -1
  46. package/dist/shell/gates/disposition.d.ts.map +1 -1
  47. package/dist/shell/gates/disposition.js +43 -2
  48. package/dist/shell/gates/disposition.js.map +1 -1
  49. package/dist/shell/index.d.ts +10 -4
  50. package/dist/shell/index.d.ts.map +1 -1
  51. package/dist/shell/index.js +22 -2
  52. package/dist/shell/index.js.map +1 -1
  53. package/dist/shell/legacy-command-map.js +832 -0
  54. package/dist/shell/push-range/classify-range.d.ts +99 -0
  55. package/dist/shell/push-range/classify-range.d.ts.map +1 -0
  56. package/dist/shell/push-range/classify-range.js +155 -0
  57. package/dist/shell/push-range/classify-range.js.map +1 -0
  58. package/dist/shell/push-range/scope-match.d.ts +13 -0
  59. package/dist/shell/push-range/scope-match.d.ts.map +1 -0
  60. package/dist/shell/push-range/scope-match.js +53 -0
  61. package/dist/shell/push-range/scope-match.js.map +1 -0
  62. package/dist/shell/register.d.ts.map +1 -1
  63. package/dist/shell/register.js +263 -228
  64. package/dist/shell/register.js.map +1 -1
  65. package/dist/shell/registered-command-groups.js +48 -0
  66. package/dist/shell/rules.d.ts +19 -0
  67. package/dist/shell/rules.d.ts.map +1 -1
  68. package/dist/shell/rules.js +27 -0
  69. package/dist/shell/rules.js.map +1 -1
  70. package/dist/shell/session/resolve-session.d.ts +29 -1
  71. package/dist/shell/session/resolve-session.d.ts.map +1 -1
  72. package/dist/shell/session/resolve-session.js +817 -11
  73. package/dist/shell/session/resolve-session.js.map +1 -1
  74. package/dist/shell/session/types.d.ts +127 -1
  75. package/dist/shell/session/types.d.ts.map +1 -1
  76. package/dist/shell/session/types.js +10 -4
  77. package/dist/shell/session/types.js.map +1 -1
  78. package/dist/store/doctor-snapshot.d.ts.map +1 -1
  79. package/dist/store/doctor-snapshot.js +26 -0
  80. package/dist/store/doctor-snapshot.js.map +1 -1
  81. package/dist/store/events-migration.d.ts +207 -0
  82. package/dist/store/events-migration.d.ts.map +1 -0
  83. package/dist/store/events-migration.js +358 -0
  84. package/dist/store/events-migration.js.map +1 -0
  85. package/dist/store/events-store.d.ts +47 -1
  86. package/dist/store/events-store.d.ts.map +1 -1
  87. package/dist/store/events-store.js +278 -0
  88. package/dist/store/events-store.js.map +1 -1
  89. package/dist/store/git-autocommit.d.ts +46 -0
  90. package/dist/store/git-autocommit.d.ts.map +1 -0
  91. package/dist/store/git-autocommit.js +198 -0
  92. package/dist/store/git-autocommit.js.map +1 -0
  93. package/dist/store/index.d.ts +4 -1
  94. package/dist/store/index.d.ts.map +1 -1
  95. package/dist/store/index.js +7 -1
  96. package/dist/store/index.js.map +1 -1
  97. package/dist/store/leases-store.d.ts.map +1 -1
  98. package/dist/store/leases-store.js +58 -0
  99. package/dist/store/leases-store.js.map +1 -1
  100. package/dist/store/rules.d.ts +53 -0
  101. package/dist/store/rules.d.ts.map +1 -1
  102. package/dist/store/rules.js +54 -0
  103. package/dist/store/rules.js.map +1 -1
  104. package/dist/store/specs-migration.d.ts +128 -0
  105. package/dist/store/specs-migration.d.ts.map +1 -0
  106. package/dist/store/specs-migration.js +481 -0
  107. package/dist/store/specs-migration.js.map +1 -0
  108. package/dist/store/specs-store.d.ts.map +1 -1
  109. package/dist/store/specs-store.js +14 -2
  110. package/dist/store/specs-store.js.map +1 -1
  111. package/dist/store/specs-writer.d.ts +130 -3
  112. package/dist/store/specs-writer.d.ts.map +1 -1
  113. package/dist/store/specs-writer.js +941 -102
  114. package/dist/store/specs-writer.js.map +1 -1
  115. package/dist/store/types.d.ts +6 -0
  116. package/dist/store/types.d.ts.map +1 -1
  117. package/dist/store/waivers-store.d.ts.map +1 -1
  118. package/dist/store/waivers-store.js +8 -1
  119. package/dist/store/waivers-store.js.map +1 -1
  120. package/dist/store/worktrees-writer.d.ts +28 -0
  121. package/dist/store/worktrees-writer.d.ts.map +1 -1
  122. package/dist/store/worktrees-writer.js +110 -12
  123. package/dist/store/worktrees-writer.js.map +1 -1
  124. package/package.json +5 -2
  125. package/templates/hook-packs/claude-code/CLAUDE.md +7 -1
  126. package/templates/hook-packs/claude-code/agent-heartbeat.sh +1 -1
  127. package/templates/hook-packs/claude-code/agent-register.sh +1 -1
  128. package/templates/hook-packs/claude-code/agent-stop.sh +1 -1
  129. package/templates/hook-packs/claude-code/audit.sh +1 -1
  130. package/templates/hook-packs/claude-code/block-dangerous.sh +1 -1
  131. package/templates/hook-packs/claude-code/classify_command.py +1 -1
  132. package/templates/hook-packs/claude-code/cwd-guard.sh +30 -0
  133. package/templates/hook-packs/claude-code/dispatch/post_tool_use.sh +15 -4
  134. package/templates/hook-packs/claude-code/dispatch/pre_tool_use.sh +10 -2
  135. package/templates/hook-packs/claude-code/dispatch/session_start.sh +1 -1
  136. package/templates/hook-packs/claude-code/dispatch/stop.sh +2 -2
  137. package/templates/hook-packs/claude-code/duplicate-export-check.sh +156 -0
  138. package/templates/hook-packs/claude-code/god-object-check.sh +102 -0
  139. package/templates/hook-packs/claude-code/guard-strikes.sh +1 -1
  140. package/templates/hook-packs/claude-code/lib/parse-input.sh +115 -1
  141. package/templates/hook-packs/claude-code/lib/run-handlers.sh +1 -1
  142. package/templates/hook-packs/claude-code/loc-delta-check.sh +91 -0
  143. package/templates/hook-packs/claude-code/naming-check.sh +128 -0
  144. package/templates/hook-packs/claude-code/plan-transcript-finalize.sh +59 -0
  145. package/templates/hook-packs/claude-code/plan-transcript-snapshot.sh +86 -0
  146. package/templates/hook-packs/claude-code/protected-paths.sh +59 -0
  147. package/templates/hook-packs/claude-code/quiet-merge.sh +68 -0
  148. package/templates/hook-packs/claude-code/reset-danger-latch.sh +1 -1
  149. package/templates/hook-packs/claude-code/reset-strikes.sh +1 -1
  150. package/templates/hook-packs/claude-code/runtime-paths.sh +1 -1
  151. package/templates/hook-packs/claude-code/scan-secrets.sh +98 -0
  152. package/templates/hook-packs/claude-code/scope-guard.sh +47 -65
  153. package/templates/hook-packs/claude-code/session-caws-status.sh +1 -1
  154. package/templates/hook-packs/claude-code/session-log.sh +1 -1
  155. package/templates/hook-packs/claude-code/session_log_renderer.py +956 -0
  156. package/templates/hook-packs/claude-code/shortcut-language-check.sh +147 -0
  157. package/templates/hook-packs/claude-code/worktree-guard.sh +1 -1
  158. 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: 5
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
  }
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
  # CAWS-MANAGED-HOOK
3
3
  # hook_pack: claude-code
4
- # hook_pack_version: 5
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`
@@ -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
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
  # CAWS-MANAGED-HOOK
3
3
  # hook_pack: claude-code
4
- # hook_pack_version: 5
4
+ # hook_pack_version: 11
5
5
  # caws_min_major: 11
6
6
  # lineage_refs: 17
7
7
  # do_not_edit_directly: update via `caws init --agent-surface claude-code`
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
  # CAWS-MANAGED-HOOK
3
3
  # hook_pack: claude-code
4
- # hook_pack_version: 5
4
+ # hook_pack_version: 11
5
5
  # caws_min_major: 11
6
6
  # lineage_refs: 17
7
7
  # do_not_edit_directly: update via `caws init --agent-surface claude-code`
@@ -1,7 +1,7 @@
1
1
  #!/bin/bash
2
2
  # CAWS-MANAGED-HOOK
3
3
  # hook_pack: claude-code
4
- # hook_pack_version: 5
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`