@thebassclef/lite 1.1.0 → 1.2.0-alpha.1
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/cli.cjs +258 -63
- package/dist/cli.js +258 -63
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lite/.claude/bassclef-configs.jsonc +56 -0
- package/dist/lite/.claude/bassclef-orientation.md +120 -0
- package/dist/lite/.claude/hooks/adr-discipline-check.sh +132 -0
- package/dist/lite/.claude/hooks/atomic-pr-check.sh +169 -0
- package/dist/lite/.claude/hooks/auto-save-idle.sh +51 -0
- package/dist/lite/.claude/hooks/bassclef-sync.sh +51 -13
- package/dist/lite/.claude/hooks/bet-doc-gate.sh +283 -0
- package/dist/lite/.claude/hooks/bug-diagnosis.sh +64 -0
- package/dist/lite/.claude/hooks/destructive-command-guard.sh +136 -0
- package/dist/lite/.claude/hooks/file-rename-discipline-check.sh +147 -0
- package/dist/lite/.claude/hooks/hook-classifier-canary.sh +158 -0
- package/dist/lite/.claude/hooks/kiss-default-check.sh +129 -0
- package/dist/lite/.claude/hooks/multi-tenancy-check.sh +83 -0
- package/dist/lite/.claude/hooks/oo-ad-entry-point-check.sh +216 -0
- package/dist/lite/.claude/hooks/operator-private-path-check.sh +203 -0
- package/dist/lite/.claude/hooks/pattern-annotation-validate.sh +142 -0
- package/dist/lite/.claude/hooks/plain-english-steering.sh +15 -1
- package/dist/lite/.claude/hooks/post-write-trace.sh +66 -0
- package/dist/lite/.claude/hooks/pr-body-scrub-check.sh +446 -0
- package/dist/lite/.claude/hooks/pre-commit-identifier-leak-scrub.sh +137 -0
- package/dist/lite/.claude/hooks/pre-gh-pr-body-scrub.sh +192 -0
- package/dist/lite/.claude/hooks/pre-gh-pr-title-scrub.sh +172 -0
- package/dist/lite/.claude/hooks/role-entry-gate.sh +115 -0
- package/dist/lite/.claude/hooks/save-state.sh +268 -0
- package/dist/lite/.claude/hooks/session-reflection.d/08-settings-drift.sh +8 -1
- package/dist/lite/.claude/hooks/session-reflection.d/10-abrupt-stop-recovery.sh +52 -0
- package/dist/lite/.claude/hooks/session-reflection.d/55-orientation-gate.sh +40 -4
- package/dist/lite/.claude/hooks/session-reflection.d/80-hook-heartbeat-check.sh +12 -5
- package/dist/lite/.claude/hooks/session-reflection.d/81-hook-manifest-staleness.sh +18 -2
- package/dist/lite/.claude/hooks/session-reflection.d/91-npm-version-drift.sh +54 -0
- package/dist/lite/.claude/hooks/session-reflection.d/tests/10-abrupt-stop-recovery.test.sh +95 -0
- package/dist/lite/.claude/hooks/session-reflection.sh +62 -15
- package/dist/lite/.claude/hooks/tool-failure-diagnosis.sh +50 -0
- package/dist/lite/.claude/luminaries/andrej-karpathy.md +165 -0
- package/dist/lite/.claude/luminaries/dave-gray.md +155 -0
- package/dist/lite/.claude/luminaries/gary-klein.md +166 -0
- package/dist/lite/.claude/luminaries/kaoru-ishikawa.md +162 -0
- package/dist/lite/.claude/luminaries/lean-product-canvas.md +33 -0
- package/dist/lite/.claude/luminaries/luke-wroblewski.md +224 -0
- package/dist/lite/.claude/luminaries/ryan-singer.md +113 -0
- package/dist/lite/.claude/luminaries/steve-krug.md +168 -0
- package/dist/lite/.claude/luminaries/tony-ulwick.md +134 -0
- package/dist/lite/.claude/rules/arc-level-orientation-before-leaf.md +60 -0
- package/dist/lite/.claude/rules/architect-review-discipline.md +71 -0
- package/dist/lite/.claude/rules/auto-fix-criteria.md +126 -0
- package/dist/lite/.claude/rules/bassclef-hook-drift-blocker.md +144 -0
- package/dist/lite/.claude/rules/bassclef-models-discovery.md +107 -0
- package/dist/lite/.claude/rules/bassclef-workflow-parity.md +104 -0
- package/dist/lite/.claude/rules/cost-governance.md +154 -0
- package/dist/lite/.claude/rules/file-rename-discipline.md +95 -0
- package/dist/lite/.claude/rules/mechanism-fidelity.md +88 -0
- package/dist/lite/.claude/rules/prototype-production-coupling.md +125 -0
- package/dist/lite/.claude/rules/quick-fix-criteria.md +90 -0
- package/dist/lite/.claude/rules/session-artifacts.md +30 -0
- package/dist/lite/.claude/rules/sibling-smoke-after-substrate-change.md +107 -0
- package/dist/lite/.claude/rules/substrate-as-system.md +67 -0
- package/dist/lite/.claude/rules/usability.md +22 -0
- package/dist/lite/.claude/settings.json +20 -0
- package/dist/lite/.claude/skills/agent-research-spawn/SKILL.md +100 -0
- package/dist/lite/.claude/skills/architect-review/SKILL.md +201 -0
- package/dist/lite/.claude/skills/auto-fix/SKILL.md +297 -0
- package/dist/lite/.claude/skills/autonomous/SKILL.md +300 -0
- package/dist/lite/.claude/skills/bind-subdomain/SKILL.md +232 -0
- package/dist/lite/.claude/skills/canvas/SKILL.md +41 -0
- package/dist/lite/.claude/skills/deploy-prod/SKILL.md +106 -0
- package/dist/lite/.claude/skills/docs-sync/SKILL.md +188 -0
- package/dist/lite/.claude/skills/eisenhower/SKILL.md +273 -0
- package/dist/lite/.claude/skills/empathy-map/SKILL.md +168 -0
- package/dist/lite/.claude/skills/frontend-design/SKILL.md +53 -0
- package/dist/lite/.claude/skills/interview-me/SKILL.md +171 -0
- package/dist/lite/.claude/skills/journal-export/SKILL.md +293 -0
- package/dist/lite/.claude/skills/jtbd-tasks/SKILL.md +203 -0
- package/dist/lite/.claude/skills/objectory-decompose/SKILL.md +256 -0
- package/dist/lite/.claude/skills/pick-luminaries/SKILL.md +100 -0
- package/dist/lite/.claude/skills/pre-mortem/SKILL.md +180 -0
- package/dist/lite/.claude/skills/prototype-gallery/SKILL.md +264 -0
- package/dist/lite/.claude/skills/provision-deploy-host/SKILL.md +257 -0
- package/dist/lite/.claude/skills/quick-fix/SKILL.md +273 -0
- package/dist/lite/.claude/skills/release/SKILL.md +311 -0
- package/dist/lite/.claude/skills/release-notes/SKILL.md +202 -0
- package/dist/lite/.claude/skills/rfc/SKILL.md +222 -0
- package/dist/lite/.claude/skills/riff-prototypes/SKILL.md +637 -0
- package/dist/lite/.claude/skills/secrets-bootstrap/SKILL.md +209 -0
- package/dist/lite/.claude/skills/skill/SKILL.md +304 -0
- package/dist/lite/.claude/skills/smoke-test/SKILL.md +100 -0
- package/dist/lite/.claude/skills/state/SKILL.md +204 -0
- package/dist/lite/.claude/skills/substrate-check/SKILL.md +218 -0
- package/dist/lite/.claude/skills/synthetic-user/SKILL.md +381 -0
- package/dist/lite/.claude/skills/task-scenarios/SKILL.md +105 -0
- package/dist/lite/.claude/skills/tier-dependency-audit/SKILL.md +139 -0
- package/dist/lite/.claude/skills/ux-migration/SKILL.md +424 -0
- package/dist/lite/.claude/skills/value-prop-canvas/SKILL.md +297 -0
- package/dist/lite/README.md +5 -1
- package/dist/lite/gitignore +6 -0
- package/dist/lite/lib/bassclef-dir-resolver.sh +85 -0
- package/dist/lite/lib/clone-failure-classifier.sh +59 -0
- package/dist/lite/lib/fixture-builder.sh +190 -0
- package/dist/lite/lib/fresh-install-check.sh +49 -0
- package/dist/lite/lib/hook-heartbeat.sh +0 -1
- package/dist/lite/lib/mechanism-fidelity.sh +569 -0
- package/dist/lite/lib/npm-version-check.sh +112 -0
- package/dist/lite/lib/preview-state.sh +229 -0
- package/dist/lite/lib/session-rescue.sh +0 -1
- package/dist/lite/scripts/generate-lite-manifest.sh +145 -4
- package/dist/lite/scripts/lite-manifest-drift-check.sh +5 -1
- package/dist/lite/standards/architect-review-discipline.md +222 -0
- package/dist/lite/standards/bassclef-platform.md +249 -0
- package/dist/lite/standards/bassclef-test-repos.md +97 -0
- package/dist/lite/standards/bassclef-wiring-manifest.json +25 -1
- package/dist/lite/standards/cold-adopter-harness.md +205 -0
- package/dist/lite/standards/data-management.md +135 -0
- package/dist/lite/standards/frontend-stack/nextjs-prisma.md +124 -0
- package/dist/lite/standards/lite-manifest-schema-changes.md +27 -0
- package/dist/lite/standards/lite-manifest.json +1456 -14
- package/dist/lite/standards/mechanism-fidelity.md +205 -0
- package/dist/lite/standards/operator-private-discipline.md +200 -0
- package/dist/lite/standards/operator-to-public-flow.md +195 -0
- package/dist/lite/standards/prototype-gallery.md +251 -0
- package/dist/lite/standards/pseudonym-key-schema.md +109 -0
- package/dist/lite/standards/sibling-validation-checklist.md +181 -0
- package/dist/lite/standards/slack-notifications.md +161 -0
- package/dist/lite/standards/synthetic-user-testing.md +173 -0
- package/dist/lite/standards/vendor-bindings.md +165 -0
- package/dist/lite/standards/vendor-plugins/frontend-design.md +66 -0
- package/dist/lite/standards/vpc-fit-validation.md +167 -0
- package/dist/lite/templates/gallery-yml.md +113 -0
- package/dist/lite/templates/product-canvas.md +169 -0
- package/dist/lite/templates/value-prop-canvas.md +180 -0
- package/package.json +12 -5
- /package/dist/lite/{whereami.md → docs/whereami.md} +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# bet-doc-gate.sh — PreToolUse Edit|Write|MultiEdit on docs/iteration-bets/*.md
|
|
4
|
+
#
|
|
5
|
+
# Composes two substrate disciplines at the bet-doc artifact-edit surface:
|
|
6
|
+
#
|
|
7
|
+
# 1. Thread-walk gate (per .claude/rules/arc-level-orientation-before-leaf.md)
|
|
8
|
+
# — marker required at state/markers/arc-walk/<goal-slug>.marker
|
|
9
|
+
# — closes bassclef-upstream#300
|
|
10
|
+
#
|
|
11
|
+
# 2. Produces/consumes substantive check (per .claude/rules/wu-sequencing-compounds.md)
|
|
12
|
+
# — Consumes cells in steps table must name concrete artifacts
|
|
13
|
+
# (file paths, marker paths, decision names, backtick-quoted refs)
|
|
14
|
+
# OR explicitly carry "parallel-safe (no consumer)"
|
|
15
|
+
# OR contain "session-start" / "union of" (prep / closeout shapes)
|
|
16
|
+
# — WARN on vague-prose ("WU-N's stable enforcement", "wired hook", etc.)
|
|
17
|
+
# — closes bassclef-upstream#21 (V2 mechanical layer of wu-sequencing-compounds)
|
|
18
|
+
#
|
|
19
|
+
# Closes the V1-methodology-only debt for both rules. Per memory
|
|
20
|
+
# feedback_hooks_beat_methodology_for_substrate_quality.
|
|
21
|
+
#
|
|
22
|
+
# Behavior:
|
|
23
|
+
# - Fires on Edit|Write|MultiEdit PreToolUse when target matches docs/iteration-bets/*.md
|
|
24
|
+
# - Reads tool_name + tool_input.file_path from stdin JSON (PreToolUse contract)
|
|
25
|
+
# OR from BASSCLEF_BET_DOC_GATE_TEST_TOOL / BASSCLEF_BET_DOC_GATE_TEST_FILE env vars (testability)
|
|
26
|
+
# - Silent on non-matching paths or non-Edit tool calls
|
|
27
|
+
# - BLOCK (exit 2) when thread-walk marker missing or stale
|
|
28
|
+
# - WARN (exit 0 with stderr) when produces/consumes table has vague-prose cells (advisory V1; V3 escalates if drift persists)
|
|
29
|
+
# - Allowlist baseline at .claude/hooks/bet-doc-gate-allowlist.txt grandfathers existing un-walked goal docs
|
|
30
|
+
#
|
|
31
|
+
# Overrides (all logged via trace-helper):
|
|
32
|
+
# SKIP_BET_DOC_GATE=1 — bypass both checks
|
|
33
|
+
# SKIP_ARC_WALK_GATE=1 — bypass thread-walk only (P/C still runs)
|
|
34
|
+
# SKIP_PRODUCES_CONSUMES_CHECK=1 — bypass P/C only (thread-walk still runs)
|
|
35
|
+
|
|
36
|
+
set -euo pipefail
|
|
37
|
+
# Saltzer-Schroeder Discipline 1 (strict mode at the top) per .claude/rules/defensive-bash.md.
|
|
38
|
+
# Non-fatal commands carry `|| true` per-line where their non-zero exit is the expected path.
|
|
39
|
+
|
|
40
|
+
# === Override (logged) ===
|
|
41
|
+
if [ "${SKIP_BET_DOC_GATE:-0}" = "1" ]; then
|
|
42
|
+
if [ -x ".claude/hooks/trace-helper.sh" ]; then
|
|
43
|
+
bash .claude/hooks/trace-helper.sh "bet-doc-gate" "skipped via SKIP_BET_DOC_GATE=1" 2>/dev/null || true
|
|
44
|
+
fi
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# === Parse input: test env vars first, else stdin JSON ===
|
|
49
|
+
TOOL_NAME="${BASSCLEF_BET_DOC_GATE_TEST_TOOL:-}"
|
|
50
|
+
FILE_PATH="${BASSCLEF_BET_DOC_GATE_TEST_FILE:-}"
|
|
51
|
+
|
|
52
|
+
if [ -z "$TOOL_NAME" ] && [ -z "$FILE_PATH" ]; then
|
|
53
|
+
if [ ! -t 0 ]; then
|
|
54
|
+
INPUT=$(cat)
|
|
55
|
+
if [ -n "$INPUT" ] && command -v jq >/dev/null 2>&1; then
|
|
56
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || echo "")
|
|
57
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null || echo "")
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# === Tool matcher ===
|
|
63
|
+
case "$TOOL_NAME" in
|
|
64
|
+
Edit|Write|MultiEdit) ;;
|
|
65
|
+
*) exit 0 ;;
|
|
66
|
+
esac
|
|
67
|
+
|
|
68
|
+
# === Path matcher (absolute or relative; both shapes accepted) ===
|
|
69
|
+
case "$FILE_PATH" in
|
|
70
|
+
*/docs/iteration-bets/*.md) ;;
|
|
71
|
+
docs/iteration-bets/*.md) ;;
|
|
72
|
+
*) exit 0 ;;
|
|
73
|
+
esac
|
|
74
|
+
|
|
75
|
+
# === Extract goal slug (basename without extension) ===
|
|
76
|
+
BET_BASE=$(basename "$FILE_PATH" .md)
|
|
77
|
+
|
|
78
|
+
# === Allowlist check ===
|
|
79
|
+
ALLOWLIST=".claude/hooks/bet-doc-gate-allowlist.txt"
|
|
80
|
+
if [ -f "$ALLOWLIST" ]; then
|
|
81
|
+
if grep -Fxq "$BET_BASE" "$ALLOWLIST" 2>/dev/null; then
|
|
82
|
+
exit 0
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# === Arc-walk check (unless SKIP_ARC_WALK_GATE=1) ===
|
|
87
|
+
ARC_MARKER="state/markers/arc-walk/${BET_BASE}.marker"
|
|
88
|
+
if [ "${SKIP_ARC_WALK_GATE:-0}" != "1" ]; then
|
|
89
|
+
if [ ! -f "$ARC_MARKER" ]; then
|
|
90
|
+
cat >&2 <<EOF
|
|
91
|
+
|
|
92
|
+
============================================
|
|
93
|
+
🛑 BLOCKED: bet-doc-gate (thread-walk)
|
|
94
|
+
============================================
|
|
95
|
+
|
|
96
|
+
You are editing a goal doc without prior parent-goal walk evidence in this session.
|
|
97
|
+
|
|
98
|
+
Goal: ${BET_BASE}
|
|
99
|
+
Missing marker: state/markers/thread-walk/${BET_BASE}.marker
|
|
100
|
+
(writes resolve to state/markers/arc-walk/ per compat
|
|
101
|
+
window through 2026-10-31; both paths accepted)
|
|
102
|
+
|
|
103
|
+
Per .claude/rules/arc-level-orientation-before-leaf.md — walk the parent_bet
|
|
104
|
+
chain up to the root before editing. Surface drivers plus open acceptance plus
|
|
105
|
+
out-of-scope from each ancestor.
|
|
106
|
+
|
|
107
|
+
To resolve:
|
|
108
|
+
1. Walk the chain: read docs/iteration-bets/${BET_BASE}.md frontmatter,
|
|
109
|
+
then follow parent_bet up to the root
|
|
110
|
+
2. Surface drivers plus open acceptance plus out_of_scope per ancestor
|
|
111
|
+
3. Touch the marker:
|
|
112
|
+
mkdir -p state/markers/thread-walk/
|
|
113
|
+
touch state/markers/thread-walk/${BET_BASE}.marker
|
|
114
|
+
|
|
115
|
+
Overrides (logged via trace-helper):
|
|
116
|
+
SKIP_ARC_WALK_GATE=1 — skip the parent-goal walk check only
|
|
117
|
+
SKIP_BET_DOC_GATE=1 — skip both checks
|
|
118
|
+
|
|
119
|
+
Closes bassclef-upstream#300 + #21 (composed).
|
|
120
|
+
============================================
|
|
121
|
+
EOF
|
|
122
|
+
if [ -x ".claude/hooks/trace-helper.sh" ]; then
|
|
123
|
+
bash .claude/hooks/trace-helper.sh "bet-doc-gate" "BLOCKED thread-walk on ${BET_BASE}" 2>/dev/null || true
|
|
124
|
+
fi
|
|
125
|
+
exit 2
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# === Produces/consumes substantive check (unless SKIP_PRODUCES_CONSUMES_CHECK=1) ===
|
|
130
|
+
if [ "${SKIP_PRODUCES_CONSUMES_CHECK:-0}" = "1" ]; then
|
|
131
|
+
exit 0
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if [ ! -f "$FILE_PATH" ]; then
|
|
135
|
+
# New file (Write before content lands); nothing to scan
|
|
136
|
+
exit 0
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Find the steps table header line.
|
|
140
|
+
# Accepts two shapes (per bassclef-upstream#688):
|
|
141
|
+
# 3-col: | WU | Produces | Consumes (from prior WU) |
|
|
142
|
+
# 6-col: | WU | Problem + value prop | Produces | Consumes (from prior WU) | How this step builds on the prior | Risk |
|
|
143
|
+
# The optional group before "Produces" swallows the "Problem + value prop"
|
|
144
|
+
# column when the six-column shape is used.
|
|
145
|
+
HEADER_INFO=$(grep -nE '^\| WU \|( [^|]* \|)? Produces \| Consumes \(from prior WU\) \|' "$FILE_PATH" 2>/dev/null | head -1 || true)
|
|
146
|
+
if [ -z "$HEADER_INFO" ]; then
|
|
147
|
+
# No steps table; nothing to scan
|
|
148
|
+
exit 0
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
HEADER_LNUM=$(echo "$HEADER_INFO" | cut -d: -f1)
|
|
152
|
+
# Detect column shape by counting pipes in the header line.
|
|
153
|
+
# 3-col header has 4 pipes; 6-col header has 7 pipes.
|
|
154
|
+
# Consumes cell is field 4 in 3-col; field 5 in 6-col (awk splits on '|'
|
|
155
|
+
# and prepends an empty field before the first pipe).
|
|
156
|
+
HEADER_LINE=$(sed -n "${HEADER_LNUM}p" "$FILE_PATH")
|
|
157
|
+
PIPE_COUNT=$(echo "$HEADER_LINE" | tr -cd '|' | wc -c | tr -d ' ')
|
|
158
|
+
if [ "$PIPE_COUNT" -ge 7 ]; then
|
|
159
|
+
CONSUMES_FIELD=5
|
|
160
|
+
else
|
|
161
|
+
CONSUMES_FIELD=4
|
|
162
|
+
fi
|
|
163
|
+
# Skip header + separator (lines header_lnum and header_lnum+1)
|
|
164
|
+
START=$((HEADER_LNUM + 2))
|
|
165
|
+
|
|
166
|
+
# Scan rows until empty line or non-pipe line
|
|
167
|
+
VAGUE_HITS=""
|
|
168
|
+
LNUM=$START
|
|
169
|
+
while IFS= read -r line; do
|
|
170
|
+
case "$line" in
|
|
171
|
+
"|"*)
|
|
172
|
+
# Pipe-separated row. Extract Consumes cell at CONSUMES_FIELD
|
|
173
|
+
# (field 4 in 3-col shape, field 5 in 6-col shape).
|
|
174
|
+
CONSUMES=$(echo "$line" | awk -F'|' -v f="$CONSUMES_FIELD" '{print $f}' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
175
|
+
if [ -z "$CONSUMES" ]; then
|
|
176
|
+
LNUM=$((LNUM+1)); continue
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# PASS patterns: explicit parallel-safe, session-start, union-of-closeout,
|
|
180
|
+
# backtick-quoted reference, slash-path, .sh/.md/.json/.yml extension
|
|
181
|
+
if echo "$CONSUMES" | grep -qE 'parallel-safe \(no consumer\)|session-start|union of|`[^`]+`|/[a-zA-Z]|\.sh\b|\.md\b|\.json\b|\.yml\b'; then
|
|
182
|
+
LNUM=$((LNUM+1)); continue
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# WARN pattern: starts with WU-N or WU-N's followed by vague prose without
|
|
186
|
+
# any concrete artifact reference
|
|
187
|
+
if echo "$CONSUMES" | grep -qE "^WU-[0-9]+'?s?\b"; then
|
|
188
|
+
VAGUE_HITS="${VAGUE_HITS} line ${LNUM}: ${CONSUMES}
|
|
189
|
+
"
|
|
190
|
+
fi
|
|
191
|
+
;;
|
|
192
|
+
"") break ;;
|
|
193
|
+
*) break ;;
|
|
194
|
+
esac
|
|
195
|
+
LNUM=$((LNUM+1))
|
|
196
|
+
done < <(tail -n +"$START" "$FILE_PATH")
|
|
197
|
+
|
|
198
|
+
if [ -n "$VAGUE_HITS" ]; then
|
|
199
|
+
cat >&2 <<EOF
|
|
200
|
+
|
|
201
|
+
============================================
|
|
202
|
+
⚠️ WARN: bet-doc-gate (produces/consumes)
|
|
203
|
+
============================================
|
|
204
|
+
|
|
205
|
+
Bet: ${BET_BASE}
|
|
206
|
+
|
|
207
|
+
The following Consumes cells contain vague-prose references that may not
|
|
208
|
+
name concrete artifacts the next WU's source actually reads:
|
|
209
|
+
|
|
210
|
+
${VAGUE_HITS}
|
|
211
|
+
Per .claude/rules/wu-sequencing-compounds.md — each Consumes cell should
|
|
212
|
+
name a concrete artifact (file path, marker path, decision name) OR
|
|
213
|
+
explicitly carry "parallel-safe (no consumer)" when the step is independent.
|
|
214
|
+
|
|
215
|
+
This is advisory (V1). Flips to BLOCK if observable drift continues
|
|
216
|
+
(V3 follow-on per feedback_hooks_beat_methodology_for_substrate_quality).
|
|
217
|
+
|
|
218
|
+
Overrides (logged):
|
|
219
|
+
SKIP_PRODUCES_CONSUMES_CHECK=1 — skip P/C only
|
|
220
|
+
SKIP_BET_DOC_GATE=1 — skip both
|
|
221
|
+
|
|
222
|
+
Closes bassclef-upstream#21.
|
|
223
|
+
============================================
|
|
224
|
+
EOF
|
|
225
|
+
if [ -x ".claude/hooks/trace-helper.sh" ]; then
|
|
226
|
+
bash .claude/hooks/trace-helper.sh "bet-doc-gate" "WARN produces/consumes on ${BET_BASE}" 2>/dev/null || true
|
|
227
|
+
fi
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# === Closeout enforcement check (unless SKIP_CLOSEOUT_ENFORCE=1) ===
|
|
231
|
+
# Fires when goal frontmatter marks the goal as complete AND no ## Closeout
|
|
232
|
+
# section exists. Prevents the 103-orphan class from recurring — per
|
|
233
|
+
# whereami operator_recap_2026-08-04e Phase 1b, 103 of 107 goal docs closed
|
|
234
|
+
# without a Closeout section; metrics undercounted by 96% until backfilled.
|
|
235
|
+
# V1 ADVISORY (WARN via stderr; exit 0 continues). V2 flips to BLOCK after
|
|
236
|
+
# allowlist baseline clears. Per bassclef-upstream#1081 + .claude/rules/session-artifacts.md.
|
|
237
|
+
if [ "${SKIP_CLOSEOUT_ENFORCE:-0}" != "1" ]; then
|
|
238
|
+
# Read status field from YAML frontmatter (first `status: X` line inside first --- block)
|
|
239
|
+
BET_STATUS=$(awk '/^---$/{c++; next} c==1 && /^status:/{sub(/^status: */, ""); sub(/[[:space:]]*$/, ""); print; exit}' "$FILE_PATH" 2>/dev/null || echo "")
|
|
240
|
+
|
|
241
|
+
case "$BET_STATUS" in
|
|
242
|
+
complete|shipped|closed|done)
|
|
243
|
+
# Bet marks itself complete; check for closeout section
|
|
244
|
+
if ! grep -qiE "^## Closeout" "$FILE_PATH" 2>/dev/null; then
|
|
245
|
+
cat >&2 <<EOF
|
|
246
|
+
|
|
247
|
+
============================================
|
|
248
|
+
⚠️ WARN: bet-doc-gate (closeout section)
|
|
249
|
+
============================================
|
|
250
|
+
|
|
251
|
+
Bet: ${BET_BASE}
|
|
252
|
+
Status: ${BET_STATUS}
|
|
253
|
+
|
|
254
|
+
The goal frontmatter marks this as complete but no "## Closeout" section
|
|
255
|
+
exists in the body. Metrics velocity view undercounts closed work when the
|
|
256
|
+
section is missing.
|
|
257
|
+
|
|
258
|
+
Per .claude/rules/session-artifacts.md — completed goals ship a Closeout
|
|
259
|
+
section documenting what landed + what deferred + retro links.
|
|
260
|
+
|
|
261
|
+
This is advisory (V1). Flips to BLOCK after allowlist baseline clears
|
|
262
|
+
(V2 per feedback_hooks_beat_methodology_for_substrate_quality).
|
|
263
|
+
|
|
264
|
+
Overrides (logged):
|
|
265
|
+
SKIP_CLOSEOUT_ENFORCE=1 — skip closeout check only
|
|
266
|
+
SKIP_BET_DOC_GATE=1 — skip all bet-doc gate checks
|
|
267
|
+
|
|
268
|
+
Closes bassclef-upstream#1081.
|
|
269
|
+
============================================
|
|
270
|
+
EOF
|
|
271
|
+
if [ -x ".claude/hooks/trace-helper.sh" ]; then
|
|
272
|
+
bash .claude/hooks/trace-helper.sh "bet-doc-gate" "WARN closeout missing on ${BET_BASE}" 2>/dev/null || true
|
|
273
|
+
fi
|
|
274
|
+
fi
|
|
275
|
+
;;
|
|
276
|
+
*)
|
|
277
|
+
# Bet not marked complete (or status unset) — skip; only fires on complete bets
|
|
278
|
+
:
|
|
279
|
+
;;
|
|
280
|
+
esac
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
exit 0
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
# Bug Diagnosis hook — fires on UserPromptSubmit.
|
|
6
|
+
# Safety net: keyword detection injects diagnosis reminder.
|
|
7
|
+
#
|
|
8
|
+
# IMPORTANT: This hook is a SAFETY NET, not the process.
|
|
9
|
+
# The agent is responsible for self-triggering diagnosis when
|
|
10
|
+
# it recognizes a failure in any form — including screenshots,
|
|
11
|
+
# implicit phrasing, and logs that this keyword matcher cannot detect.
|
|
12
|
+
|
|
13
|
+
INPUT=$(cat)
|
|
14
|
+
# Per bassclef#1125: fail-soft on jq parse failure (malformed stdin JSON
|
|
15
|
+
# returns jq exit 5; set -e would crash the hook).
|
|
16
|
+
PROMPT=$(echo "$INPUT" | jq -r '.prompt // ""' 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "")
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
19
|
+
|
|
20
|
+
# Strong match: explicit defect/failure keywords → full diagnosis block
|
|
21
|
+
if echo "$PROMPT" | grep -qiE '\b(bug|defect|regress(ion)?|flaky|intermittent)\b|\b(crash|panic|abort|sigkill|oom|segfault)\b|\b(broken|corrupt|stale|stuck|hung|hanging|frozen|deadlock|infinite.loop)\b|\b(404|500|502|503|504|redirect.loop|cors.error)\b|\b(timeout|timed.out|memory.leak|high.cpu)\b|\b(unreachable|not.responding|connection.refused)\b|\b(not.working|doesn.t.work|won.t.start|stopped.working|blank.page|white.screen)\b'; then
|
|
22
|
+
source "$SCRIPT_DIR/trace-helper.sh"
|
|
23
|
+
trace_log "bug-diagnosis" "defect keywords detected"
|
|
24
|
+
cat <<'DIAG'
|
|
25
|
+
============================================
|
|
26
|
+
BUG/FAILURE DETECTED — RUN /diagnose FIRST
|
|
27
|
+
============================================
|
|
28
|
+
|
|
29
|
+
The user's message describes a bug, error, or failure.
|
|
30
|
+
Run the /diagnose skill BEFORE making ANY code changes.
|
|
31
|
+
|
|
32
|
+
Do not Edit, Write, or run fix commands until you have written:
|
|
33
|
+
1. Is / Is Not
|
|
34
|
+
2. Five Whys
|
|
35
|
+
3. Hypothesis + Test
|
|
36
|
+
|
|
37
|
+
If this is actually a design discussion (not a bug), state that
|
|
38
|
+
and proceed normally.
|
|
39
|
+
============================================
|
|
40
|
+
DIAG
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Weak match: phrases that often accompany failures — lighter reminder
|
|
45
|
+
if echo "$PROMPT" | grep -qiE '(fix|re-run|re.run|how do we|throws|error|issue|wrong|problem|diagnose|screenshot|logs show|FAIL)'; then
|
|
46
|
+
cat <<'REMIND'
|
|
47
|
+
============================================
|
|
48
|
+
POSSIBLE FAILURE — CHECK BEFORE FIXING
|
|
49
|
+
============================================
|
|
50
|
+
|
|
51
|
+
This message may describe a failure (screenshot, log, or implicit).
|
|
52
|
+
The hook cannot always detect failures — YOU must judge:
|
|
53
|
+
|
|
54
|
+
- Is the user reporting something that isn't working as expected?
|
|
55
|
+
- Are they showing a screenshot with errors or unexpected output?
|
|
56
|
+
- Are they asking "how do we fix/re-run X"?
|
|
57
|
+
|
|
58
|
+
If YES → run /diagnose BEFORE writing any fix.
|
|
59
|
+
If NO → proceed normally.
|
|
60
|
+
============================================
|
|
61
|
+
REMIND
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
exit 0
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# Destructive Command Guard — fires on Bash PreToolUse.
|
|
4
|
+
# Detects destructive SIGNALS in any command, not specific tools.
|
|
5
|
+
# Forces a temperance pause before proceeding.
|
|
6
|
+
#
|
|
7
|
+
# Design: signal-based, not command-specific. Instead of hardcoding
|
|
8
|
+
# "prisma migrate reset", we detect the PATTERN of destruction:
|
|
9
|
+
# destructive verbs + force flags + amplifiers. This catches tools
|
|
10
|
+
# we haven't seen yet.
|
|
11
|
+
#
|
|
12
|
+
# Origin: operator manually invoked /temperance to prevent a prisma
|
|
13
|
+
# migrate reset that would have destroyed dev data. A single DELETE
|
|
14
|
+
# on a stale migration row was the actual fix.
|
|
15
|
+
|
|
16
|
+
INPUT=$(cat)
|
|
17
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
|
|
18
|
+
|
|
19
|
+
# Skip empty commands
|
|
20
|
+
if [ -z "$COMMAND" ]; then
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# === Safe command allowlist (skip quickly for common operations) ===
|
|
25
|
+
# These are high-frequency, never destructive — skip to avoid latency.
|
|
26
|
+
if echo "$COMMAND" | grep -qE '^(git (status|diff|log|show|branch --show)|ls |cat |head |tail |wc |echo |which |test |pwd|cd )'; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# === Signal-based destructive detection ===
|
|
31
|
+
#
|
|
32
|
+
# Three signal types, scored by severity:
|
|
33
|
+
# VERB: destructive action word (reset, drop, delete, destroy, purge, etc.)
|
|
34
|
+
# FORCE: flag that bypasses confirmation (--force, --hard, -f, -rf, --yes)
|
|
35
|
+
# AMPLIFIER: scope expansion (CASCADE, --all, --volumes, recursive, -r)
|
|
36
|
+
#
|
|
37
|
+
# Scoring:
|
|
38
|
+
# VERB alone = MEDIUM (warn)
|
|
39
|
+
# VERB + FORCE = HIGH (strong warn)
|
|
40
|
+
# VERB + AMPLIFIER = HIGH
|
|
41
|
+
# VERB + FORCE + AMPLIFIER = CRITICAL
|
|
42
|
+
|
|
43
|
+
VERB=""
|
|
44
|
+
FORCE=""
|
|
45
|
+
AMPLIFIER=""
|
|
46
|
+
|
|
47
|
+
# --- Detect destructive verbs ---
|
|
48
|
+
# These words signal intent to destroy state
|
|
49
|
+
if echo "$COMMAND" | grep -qiE '\b(reset|drop|delete|destroy|purge|prune|wipe|clean|truncate|obliterate|nuke|flush|drain|evict|overwrite|revert|rollback|format|erase|clear|demolish|discard|abort|terminate|revoke|invalidate|expire|uninstall|deregister|detach)\b'; then
|
|
50
|
+
VERB=$(echo "$COMMAND" | grep -oiE '\b(reset|drop|delete|destroy|purge|prune|wipe|clean|truncate|obliterate|nuke|flush|drain|evict|overwrite|revert|rollback|format|erase|clear|demolish|discard|abort|terminate|revoke|invalidate|expire|uninstall|deregister|detach)\b' | head -1)
|
|
51
|
+
fi
|
|
52
|
+
# "remove" only counts with a force/recursive flag (otherwise it's just rm file.txt)
|
|
53
|
+
if [ -z "$VERB" ] && echo "$COMMAND" | grep -qiE '\bremove\b' && echo "$COMMAND" | grep -qE '\-(r|f|rf|fr)\b|--force|--recursive'; then
|
|
54
|
+
VERB="remove"
|
|
55
|
+
fi
|
|
56
|
+
# "rm" only counts with a force/recursive flag (otherwise it's just rm file.txt)
|
|
57
|
+
# Per bassclef goal 2026-06-17a Phase Z-2: hardening discovered the rm gap.
|
|
58
|
+
if [ -z "$VERB" ] && echo "$COMMAND" | grep -qE '\brm\b' && echo "$COMMAND" | grep -qE '\-(r|f|rf|fr)\b|--force|--recursive'; then
|
|
59
|
+
VERB="rm"
|
|
60
|
+
fi
|
|
61
|
+
# "push" only counts with --force (otherwise it's just git push)
|
|
62
|
+
if [ -z "$VERB" ] && echo "$COMMAND" | grep -qE 'push.*--force|push.*-f\b'; then
|
|
63
|
+
VERB="force-push"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# No destructive verb → exit early
|
|
67
|
+
if [ -z "$VERB" ]; then
|
|
68
|
+
exit 0
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# --- Detect force flags ---
|
|
72
|
+
if echo "$COMMAND" | grep -qE '\-\-(force|hard|yes|no-verify|skip-seed|no-prompt)\b|\s-f\b|\s-rf\b|\s-fr\b'; then
|
|
73
|
+
FORCE=$(echo "$COMMAND" | grep -oE '\-\-(force|hard|yes|no-verify|skip-seed|no-prompt)\b|\s-f\b|\s-rf\b' | head -1 | tr -d ' ')
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# --- Detect amplifiers ---
|
|
77
|
+
if echo "$COMMAND" | grep -qiE 'CASCADE|--all\b|--volumes\b|--recursive\b|\s-r\b|--include-untracked|system\b'; then
|
|
78
|
+
AMPLIFIER=$(echo "$COMMAND" | grep -oiE 'CASCADE|--all|--volumes|--recursive|-r\b|--include-untracked|system' | head -1)
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# === Score severity ===
|
|
82
|
+
if [ -n "$VERB" ] && [ -n "$FORCE" ] && [ -n "$AMPLIFIER" ]; then
|
|
83
|
+
SEVERITY="CRITICAL — forced + amplified destruction"
|
|
84
|
+
elif [ -n "$VERB" ] && ([ -n "$FORCE" ] || [ -n "$AMPLIFIER" ]); then
|
|
85
|
+
SEVERITY="HIGH — forced or amplified destruction"
|
|
86
|
+
else
|
|
87
|
+
SEVERITY="MEDIUM — destructive verb detected"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# === Classify the domain ===
|
|
91
|
+
CATEGORY="DESTRUCTIVE COMMAND"
|
|
92
|
+
if echo "$COMMAND" | grep -qiE 'prisma|psql|mysql|sqlite|mongo|DROP|TRUNCATE|migrate'; then
|
|
93
|
+
CATEGORY="DATABASE DESTRUCTION"
|
|
94
|
+
elif echo "$COMMAND" | grep -qiE 'docker|compose|container|volume|image'; then
|
|
95
|
+
CATEGORY="CONTAINER DESTRUCTION"
|
|
96
|
+
elif echo "$COMMAND" | grep -qiE '^git |^gh '; then
|
|
97
|
+
CATEGORY="GIT DESTRUCTION"
|
|
98
|
+
elif echo "$COMMAND" | grep -qiE '^rm |^find.*-delete|xargs.*rm'; then
|
|
99
|
+
CATEGORY="FILE DESTRUCTION"
|
|
100
|
+
elif echo "$COMMAND" | grep -qiE 'node_modules|\.next|dist/|build/|cache'; then
|
|
101
|
+
CATEGORY="BUILD CACHE WIPE"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# === Output temperance prompt ===
|
|
105
|
+
echo ""
|
|
106
|
+
echo "============================================"
|
|
107
|
+
echo "⚠️ DESTRUCTIVE COMMAND — TEMPERANCE REQUIRED"
|
|
108
|
+
echo "============================================"
|
|
109
|
+
echo ""
|
|
110
|
+
echo "Category: $CATEGORY"
|
|
111
|
+
echo "Severity: $SEVERITY"
|
|
112
|
+
echo "Signals: verb=$VERB${FORCE:+ force=$FORCE}${AMPLIFIER:+ amplifier=$AMPLIFIER}"
|
|
113
|
+
echo "Command: $COMMAND"
|
|
114
|
+
echo ""
|
|
115
|
+
echo "BEFORE PROCEEDING, answer:"
|
|
116
|
+
echo ""
|
|
117
|
+
echo "1. ROOT CAUSE — What specific problem requires destruction?"
|
|
118
|
+
echo " (Not 'it's broken' — what exactly is wrong?)"
|
|
119
|
+
echo ""
|
|
120
|
+
echo "2. SIMPLER FIX — Is there a targeted alternative?"
|
|
121
|
+
echo " Common traps:"
|
|
122
|
+
echo " • DB checksum mismatch → fix the row, don't reset the DB"
|
|
123
|
+
echo " • Schema drift → create a migration, don't db push"
|
|
124
|
+
echo " • Dirty git state → commit or stash, don't reset --hard"
|
|
125
|
+
echo " • Failing tests → diagnose the test, don't wipe state"
|
|
126
|
+
echo " • Container issues → restart the service, don't prune all"
|
|
127
|
+
echo " • Build errors → clear the specific cache, don't rm -rf"
|
|
128
|
+
echo ""
|
|
129
|
+
echo "3. RECOVERY — If you proceed, can you undo it?"
|
|
130
|
+
echo " (Backup? Seed data? Git reflog? Docker image cache?)"
|
|
131
|
+
echo ""
|
|
132
|
+
echo "The simplest fix is almost never the most destructive one."
|
|
133
|
+
echo "============================================"
|
|
134
|
+
echo ""
|
|
135
|
+
|
|
136
|
+
exit 0
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# install-class: project
|
|
4
|
+
# file-rename-discipline-check — PreToolUse hook that blocks `git commit`
|
|
5
|
+
# Bash invocations when the staged changes delete a file under the
|
|
6
|
+
# rule's scope without a matching entry in standards/file-renames.json.
|
|
7
|
+
#
|
|
8
|
+
# Per .claude/rules/file-rename-discipline.md — every file rename in
|
|
9
|
+
# bassclef registers a map entry AND ships a fixture test. The rule's
|
|
10
|
+
# methodology layer says do it; this hook is the mechanical layer that
|
|
11
|
+
# fires on every commit.
|
|
12
|
+
#
|
|
13
|
+
# Why this exists: the 2026-06-21 rename of canonical-source-config-validate.sh
|
|
14
|
+
# shipped without registering itself anywhere. POA's session on 2026-06-29
|
|
15
|
+
# surfaced the gap eight days later. Bet 29e WU-9 shipped the cure; this
|
|
16
|
+
# hook prevents the next rename from repeating the failure.
|
|
17
|
+
#
|
|
18
|
+
# Exit codes:
|
|
19
|
+
# 0 — allow (non-matching Bash, no scoped deletions, all deletions registered, override)
|
|
20
|
+
# 2 — block (one or more scoped deletions missing from the rename map)
|
|
21
|
+
#
|
|
22
|
+
# Override: SKIP_FILE_RENAME_DISCIPLINE=1 (logged via trace-helper)
|
|
23
|
+
|
|
24
|
+
set +e
|
|
25
|
+
|
|
26
|
+
INPUT=$(cat 2>/dev/null)
|
|
27
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
|
|
28
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null)
|
|
29
|
+
|
|
30
|
+
# Only fire on Bash tool calls
|
|
31
|
+
[ "$TOOL_NAME" = "Bash" ] || exit 0
|
|
32
|
+
|
|
33
|
+
# Only fire on `git commit` invocations (not git status, not git diff, etc)
|
|
34
|
+
case "$COMMAND" in
|
|
35
|
+
*"git commit"*) : ;;
|
|
36
|
+
*) exit 0 ;;
|
|
37
|
+
esac
|
|
38
|
+
|
|
39
|
+
# Skip via override (logged)
|
|
40
|
+
if [ "${SKIP_FILE_RENAME_DISCIPLINE:-0}" = "1" ]; then
|
|
41
|
+
if [ -x ".claude/hooks/trace-helper.sh" ]; then
|
|
42
|
+
bash .claude/hooks/trace-helper.sh "file-rename-discipline" "skipped via SKIP_FILE_RENAME_DISCIPLINE=1" 2>/dev/null || true
|
|
43
|
+
fi
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Resolve the rename map (env-var override for tests + curl-pipe scenarios)
|
|
48
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
49
|
+
RENAME_MAP="${BASSCLEF_RENAME_MAP:-$REPO_ROOT/standards/file-renames.json}"
|
|
50
|
+
|
|
51
|
+
# If the rename map does not exist on this repo, the hook is dormant.
|
|
52
|
+
# (Adopter repos don't ship the map; only bassclef-upstream does.)
|
|
53
|
+
[ -f "$RENAME_MAP" ] || exit 0
|
|
54
|
+
|
|
55
|
+
# Read staged deletions. `git diff --cached --diff-filter=D --name-only`
|
|
56
|
+
# lists files deleted in the staged change.
|
|
57
|
+
DELETIONS=$(git diff --cached --diff-filter=D --name-only 2>/dev/null)
|
|
58
|
+
[ -n "$DELETIONS" ] || exit 0
|
|
59
|
+
|
|
60
|
+
# Scope filter — only check deletions under the rule's path list.
|
|
61
|
+
SCOPED_PATHS=(
|
|
62
|
+
".claude/hooks/"
|
|
63
|
+
".claude/skills/"
|
|
64
|
+
".claude/rules/"
|
|
65
|
+
".claude/luminaries/"
|
|
66
|
+
".claude/agents/"
|
|
67
|
+
"scripts/"
|
|
68
|
+
"lib/"
|
|
69
|
+
"standards/"
|
|
70
|
+
"presence/"
|
|
71
|
+
"architecture/decisions/"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Build the list of registered old_names from the rename map.
|
|
75
|
+
# Both the renames array AND the compat_shims_preserved list are
|
|
76
|
+
# considered "registered" — the rule allows both shapes.
|
|
77
|
+
REGISTERED_NAMES=$(jq -r '
|
|
78
|
+
((.renames // [])[]?.old_name),
|
|
79
|
+
((.compat_shims_preserved // [])[]?.old_name)
|
|
80
|
+
' "$RENAME_MAP" 2>/dev/null | sort -u)
|
|
81
|
+
|
|
82
|
+
# Walk the deletions; flag any scoped deletion whose basename is missing
|
|
83
|
+
# from the registered list.
|
|
84
|
+
UNREGISTERED=""
|
|
85
|
+
while IFS= read -r path; do
|
|
86
|
+
[ -n "$path" ] || continue
|
|
87
|
+
# Scope filter: skip deletions outside the rule's paths
|
|
88
|
+
in_scope=0
|
|
89
|
+
for scope in "${SCOPED_PATHS[@]}"; do
|
|
90
|
+
case "$path" in
|
|
91
|
+
"$scope"*) in_scope=1; break ;;
|
|
92
|
+
esac
|
|
93
|
+
done
|
|
94
|
+
[ "$in_scope" = "1" ] || continue
|
|
95
|
+
|
|
96
|
+
base=$(basename "$path")
|
|
97
|
+
if ! echo "$REGISTERED_NAMES" | grep -qFx "$base"; then
|
|
98
|
+
UNREGISTERED="$UNREGISTERED - $path (basename: $base)"$'\n'
|
|
99
|
+
fi
|
|
100
|
+
done <<< "$DELETIONS"
|
|
101
|
+
|
|
102
|
+
# No unregistered scoped deletions → allow
|
|
103
|
+
[ -z "$UNREGISTERED" ] && exit 0
|
|
104
|
+
|
|
105
|
+
# Block with a structured remediation message
|
|
106
|
+
cat <<MSG >&2
|
|
107
|
+
============================================
|
|
108
|
+
🛑 FILE-RENAME DISCIPLINE — BLOCKED 🛑
|
|
109
|
+
============================================
|
|
110
|
+
|
|
111
|
+
The staged commit deletes one or more files under the rule's scope without
|
|
112
|
+
a matching entry in standards/file-renames.json. Per .claude/rules/file-rename-discipline.md
|
|
113
|
+
every file rename registers a map entry plus a fixture test.
|
|
114
|
+
|
|
115
|
+
Unregistered deletions:
|
|
116
|
+
$UNREGISTERED
|
|
117
|
+
|
|
118
|
+
Three required moves:
|
|
119
|
+
|
|
120
|
+
1. Add an entry under \`.renames\` in standards/file-renames.json:
|
|
121
|
+
|
|
122
|
+
{
|
|
123
|
+
"old_name": "<basename-being-deleted>",
|
|
124
|
+
"new_name": "<basename-of-the-successor-file>",
|
|
125
|
+
"renamed_at": "$(date +%Y-%m-%d)",
|
|
126
|
+
"category": "<hook|skill|rule|agent|luminary|script|lib|standard|adr>"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
2. Ship a fixture under .claude/hooks/tests/fixtures/adopter-$(date +%Y-%m-%d)-rename/
|
|
130
|
+
that mirrors the adopter state after the rename ships. The migrate
|
|
131
|
+
script's test suite reads the fixture; assertions pin the cure.
|
|
132
|
+
|
|
133
|
+
3. Cite the rename in the goal doc or chronicle for this commit.
|
|
134
|
+
|
|
135
|
+
Compat-shim case — when the old name keeps working via a different
|
|
136
|
+
mechanism (e.g., dispatcher's canonical-sync.sh via ADR-032 two-stage rocket):
|
|
137
|
+
add the entry under \`.compat_shims_preserved\` instead, with a reason field.
|
|
138
|
+
|
|
139
|
+
Override (logged via trace-helper): SKIP_FILE_RENAME_DISCIPLINE=1 git commit ...
|
|
140
|
+
|
|
141
|
+
Use only for migrating bulk content or emergency rescue; routine renames
|
|
142
|
+
register the entry.
|
|
143
|
+
|
|
144
|
+
============================================
|
|
145
|
+
MSG
|
|
146
|
+
|
|
147
|
+
exit 2
|