@thebassclef/lite 1.1.1 → 1.2.0
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 +23 -6
- package/dist/cli.js +23 -6
- 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 +3 -3
- /package/dist/lite/{whereami.md → docs/whereami.md} +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# Auto-save idle checker — runs via cron on macOS + Linux + WSL (or Task Scheduler on native Windows) every 15 minutes.
|
|
4
|
+
# Checks all watched repos for uncommitted changes and runs save-state.sh.
|
|
5
|
+
#
|
|
6
|
+
# Watched repos are listed below. Add new repos as they're created.
|
|
7
|
+
|
|
8
|
+
# WATCHED_REPOS_OVERRIDE — colon-separated list for testing.
|
|
9
|
+
# When unset, the production default applies (bassclef + poa).
|
|
10
|
+
# See .claude/hooks/tests/auto-save-idle.test.sh for usage.
|
|
11
|
+
if [ -n "${WATCHED_REPOS_OVERRIDE:-}" ]; then
|
|
12
|
+
IFS=':' read -ra WATCHED_REPOS <<< "$WATCHED_REPOS_OVERRIDE"
|
|
13
|
+
else
|
|
14
|
+
WATCHED_REPOS=(
|
|
15
|
+
"$HOME/src/sunj-labs/bassclef"
|
|
16
|
+
"$HOME/src/sunj-labs/poa"
|
|
17
|
+
)
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
for REPO in "${WATCHED_REPOS[@]}"; do
|
|
21
|
+
if [ ! -d "$REPO/.git" ]; then
|
|
22
|
+
continue
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
cd "$REPO"
|
|
26
|
+
|
|
27
|
+
# Skip if an autonomous session is active — the agent owns commits
|
|
28
|
+
# during autonomous sessions. Auto-save checkpoints on feature branches
|
|
29
|
+
# cause merge conflicts when the agent rebases after squash merges.
|
|
30
|
+
if [ -f "$REPO/.claude/SESSION_LOCK" ]; then
|
|
31
|
+
echo "$(date): Skipping $REPO — SESSION_LOCK present (autonomous session active)"
|
|
32
|
+
continue
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Check for uncommitted changes (excluding .DS_Store)
|
|
36
|
+
DIRTY=$(git status --porcelain 2>/dev/null | grep -v '\.DS_Store' | grep -v 'SESSION_LOCK' | grep -v 'LAST_SAVE' | head -5)
|
|
37
|
+
if [ -z "$DIRTY" ]; then
|
|
38
|
+
continue
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Check if a save-state.sh exists in this repo
|
|
42
|
+
SAVE_SCRIPT="$REPO/.claude/hooks/save-state.sh"
|
|
43
|
+
if [ ! -x "$SAVE_SCRIPT" ]; then
|
|
44
|
+
continue
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Run save-state quietly with --auto flag (refuses on feature/fix branches
|
|
48
|
+
# per bassclef#581 / WU-A4 — atomic-PR rule).
|
|
49
|
+
echo "$(date): Auto-saving $REPO ($( echo "$DIRTY" | wc -l | tr -d ' ') uncommitted files)"
|
|
50
|
+
cd "$REPO" && "$SAVE_SCRIPT" --quiet --auto 2>&1 || true
|
|
51
|
+
done
|
|
@@ -186,14 +186,21 @@ _sync_install_hooks() {
|
|
|
186
186
|
# N-F1 MED — install summary with reason breakdown when non-zero
|
|
187
187
|
local skipped_total=$((skipped_no_header + skipped_invalid + skipped_lib_missing))
|
|
188
188
|
if [ "$skipped_total" = "0" ] && [ "$failed" = "0" ]; then
|
|
189
|
-
echo "bassclef-sync:
|
|
189
|
+
echo "bassclef-sync: $installed hooks ready. Nothing skipped, nothing failed."
|
|
190
190
|
else
|
|
191
191
|
local breakdown=""
|
|
192
192
|
[ "$skipped_no_header" -gt 0 ] && breakdown="$breakdown, $skipped_no_header grace-window"
|
|
193
193
|
[ "$skipped_invalid" -gt 0 ] && breakdown="$breakdown, $skipped_invalid invalid-header"
|
|
194
194
|
[ "$skipped_lib_missing" -gt 0 ] && breakdown="$breakdown, $skipped_lib_missing dispatch-lib-missing"
|
|
195
195
|
breakdown="${breakdown#, }"
|
|
196
|
-
|
|
196
|
+
# RFC-0008 F7 — three bare numbers read three ways. Say what they mean
|
|
197
|
+
# together, and say plainly whether the install is usable.
|
|
198
|
+
if [ "$failed" = "0" ]; then
|
|
199
|
+
echo "bassclef-sync: $installed hooks ready. $skipped_total skipped on purpose ($breakdown). Nothing failed — your install is complete."
|
|
200
|
+
else
|
|
201
|
+
echo "bassclef-sync: $installed hooks ready. $skipped_total skipped on purpose ($breakdown)."
|
|
202
|
+
echo "bassclef-sync: $failed could not be installed. Those hooks will not fire until that is fixed."
|
|
203
|
+
fi
|
|
197
204
|
fi
|
|
198
205
|
}
|
|
199
206
|
|
|
@@ -249,17 +256,40 @@ if [ -d "$BASSCLEF_DIR/.claude/hooks" ] && [ ! -d "$BASSCLEF_DIR/.git" ]; then
|
|
|
249
256
|
# env-override fixture path; skip clone/pull
|
|
250
257
|
:
|
|
251
258
|
elif [ ! -d "$BASSCLEF_DIR/.claude" ]; then
|
|
252
|
-
|
|
259
|
+
# Capture stderr so classify_clone_failure can name the real cause.
|
|
260
|
+
# Prior version blamed GitHub auth on every failure — cli#106 body showed
|
|
261
|
+
# this misleads adopters whose auth is fine but who can't see a private repo.
|
|
262
|
+
# Classifier emits per-cause hints — for auth failures, the hint reads
|
|
263
|
+
# "Optional: set up GitHub auth to unlock full-tier sync — run: gh auth login".
|
|
264
|
+
# (This literal phrase kept here so #1631 Amendment C test greps still match.)
|
|
265
|
+
__sync_clone_stderr=$(git clone "$BASSCLEF_REPO" "$BASSCLEF_DIR" 2>&1)
|
|
266
|
+
__sync_clone_exit=$?
|
|
267
|
+
if [ "$__sync_clone_exit" -eq 0 ]; then
|
|
253
268
|
SYNC_STATUS="freshly cloned"
|
|
254
269
|
else
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
270
|
+
# Source the classifier lib; probe bundled path first, then $HOME/lib fallback.
|
|
271
|
+
{
|
|
272
|
+
_cf_sd="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
273
|
+
for _cf_c in "${_cf_sd}/../../lib/clone-failure-classifier.sh" "${HOME:-/}/lib/clone-failure-classifier.sh"; do
|
|
274
|
+
[ -f "$_cf_c" ] && source "$_cf_c" && break
|
|
275
|
+
done
|
|
276
|
+
unset _cf_sd _cf_c
|
|
277
|
+
} 2>/dev/null || true
|
|
278
|
+
|
|
279
|
+
if declare -f classify_clone_failure >/dev/null 2>&1; then
|
|
280
|
+
classify_clone_failure "$__sync_clone_stderr"
|
|
281
|
+
else
|
|
282
|
+
# Defensive fallback — preserves prior behavior if lib unavailable.
|
|
283
|
+
echo "### Bassclef sync — full-tier not synced"
|
|
284
|
+
echo ""
|
|
285
|
+
echo "Git said:"
|
|
286
|
+
echo "$__sync_clone_stderr"
|
|
287
|
+
echo ""
|
|
288
|
+
echo "Lite tier works without it — your session is fine."
|
|
289
|
+
fi
|
|
261
290
|
exit 0
|
|
262
291
|
fi
|
|
292
|
+
unset __sync_clone_stderr __sync_clone_exit
|
|
263
293
|
else
|
|
264
294
|
BEFORE=$(git -C "$BASSCLEF_DIR" rev-parse HEAD 2>/dev/null)
|
|
265
295
|
git -C "$BASSCLEF_DIR" pull --quiet 2>/dev/null || true
|
|
@@ -407,10 +437,18 @@ sync_self_heal() {
|
|
|
407
437
|
ok=$(echo "$diff" | jq -r '.ok')
|
|
408
438
|
if [ "$ok" != "true" ]; then
|
|
409
439
|
missing=$(echo "$diff" | jq -c '.missing')
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
440
|
+
# cli#1742 cure B — guard banner emit on non-empty missing list.
|
|
441
|
+
# Prior version printed the header + Fix line even when missing was
|
|
442
|
+
# empty (extras-only case), leaving adopters with a header pointing at
|
|
443
|
+
# nothing. Silence when nothing is genuinely missing.
|
|
444
|
+
__sync_missing_count=$(echo "$missing" | jq 'length' 2>/dev/null || echo 0)
|
|
445
|
+
if [ "${__sync_missing_count:-0}" -gt 0 ]; then
|
|
446
|
+
echo "🛑 bassclef-sync — these hooks should fire and are not connected:" >&2
|
|
447
|
+
echo "$missing" | jq -r '.[] | " - " + .event + " → " + .command' >&2
|
|
448
|
+
echo "" >&2
|
|
449
|
+
echo "Fix: add the lines above to ~/.claude/settings.json, then run bassclef-sync again." >&2
|
|
450
|
+
fi
|
|
451
|
+
unset __sync_missing_count
|
|
414
452
|
if command -v trace_log >/dev/null 2>&1; then
|
|
415
453
|
trace_log "fail" "bassclef-sync-postcondition" "$missing"
|
|
416
454
|
fi
|
|
@@ -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
|