From b8a458be354b1ecc92753ee5224943534f7ab6d8 Mon Sep 17 00:00:00 2001 From: Jordan Winters Date: Fri, 24 Jul 2026 11:33:54 -0500 Subject: [PATCH 1/3] =?UTF-8?q?feat(loop):=20correction=20capture=20?= =?UTF-8?q?=E2=80=94=20log,=20retro=20promotion,=20stop=20nudge=20(U16/O14?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements research option O14 (artifacts/research_ai_coding_frustrations.md Part 3) — the standing correction-capture loop so mid-session corrections stop getting silently repeated instead of retained. - core-directives.md: new "Correction Capture" subsection — a user correction that contradicts standing rules/skills/instructions gets appended as one line to scratchpad/corrections.log; ordinary task instructions are not logged. - land-the-plane/SKILL.md: Handoff section gains a Retro step — map each non-empty log entry to the strongest enforcement rung it can support (rule/skill/hook/CI, same discipline as postmortem's Prevention step), promote via a small PR or filed issue, then remove the processed line. - stop-validator.sh: compact, position-independent reminder block — when scratchpad/corrections.log is non-empty, name the pending count and point at the retro step. Silent when absent or zero-byte. Kept minimal per the note that PR #45 also touches this file. - New scripts/hook-tests.d/40-self-improvement.sh: hermetic harness cases for the 2-line, absent, and zero-byte log states. - CHANGELOG.md (Unreleased/Added) and MIGRATION.md entries. rules-lines budget: 419 -> 423 lines (budget 500; core-directives.md only touched rule file, +4 lines). Co-Authored-By: Claude Fable 5 --- .claude/hooks/stop-validator.sh | 7 +++ .claude/rules/core-directives.md | 4 ++ .claude/skills/land-the-plane/SKILL.md | 5 ++ CHANGELOG.md | 1 + MIGRATION.md | 10 ++++ scripts/hook-tests.d/40-self-improvement.sh | 58 +++++++++++++++++++++ 6 files changed, 85 insertions(+) create mode 100755 scripts/hook-tests.d/40-self-improvement.sh diff --git a/.claude/hooks/stop-validator.sh b/.claude/hooks/stop-validator.sh index a6e0f2c..3870c60 100755 --- a/.claude/hooks/stop-validator.sh +++ b/.claude/hooks/stop-validator.sh @@ -85,4 +85,11 @@ if [ -d "$PROJECT_DIR/.git" ]; then fi fi +# Correction-capture reminder (O14): unresolved log entries must be promoted, not silently dropped +CORRECTIONS_LOG="$PROJECT_DIR/scratchpad/corrections.log" +if [ -s "$CORRECTIONS_LOG" ]; then + CORRECTIONS_COUNT=$(wc -l < "$CORRECTIONS_LOG" | tr -d ' ') + echo "[CORRECTIONS PENDING] $CORRECTIONS_COUNT correction(s) in scratchpad/corrections.log — run land-the-plane's retro step (promote or file an issue) before ending." +fi + exit 0 diff --git a/.claude/rules/core-directives.md b/.claude/rules/core-directives.md index 596dc5b..06efc87 100644 --- a/.claude/rules/core-directives.md +++ b/.claude/rules/core-directives.md @@ -68,3 +68,7 @@ Write atomic, descriptive commit messages. Each commit should represent one comp ### Artifacts, Scratchpad, Handoffs See Rules 4, 5, and 7 above. + +### Correction Capture + +When a user correction contradicts a current rule, skill, or standing instruction ("no, we don't do X here"), append one line to `scratchpad/corrections.log`: `YYYY-MM-DD | | | skill: | none-yet>`. Log only contradictions of standing guidance — never ordinary task instructions. The log is ephemeral capture; `land-the-plane`'s retro step is what promotes an entry to a durable rule/skill/hook/CI change. diff --git a/.claude/skills/land-the-plane/SKILL.md b/.claude/skills/land-the-plane/SKILL.md index bcf4ff6..1b4bd41 100644 --- a/.claude/skills/land-the-plane/SKILL.md +++ b/.claude/skills/land-the-plane/SKILL.md @@ -71,6 +71,11 @@ git status # MUST show "up to date with origin" - Release working state: drop stashes, remove temp files, release any locks, and keep `./scratchpad/` references out of anything you hand off — scratchpad is ephemeral and disposable, not a citable deliverable. +- **Retro**: if `scratchpad/corrections.log` is non-empty, map each entry + to the strongest enforcement rung it can support (rule edit < skill edit + < hook < permissions.deny/CI — same discipline as `postmortem`'s + Prevention step), promote it via a small PR or filed issue, then remove + the processed lines. Never end a session by silently discarding the log. ## Failure Branches diff --git a/CHANGELOG.md b/CHANGELOG.md index e9aabff..0306fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `pre-tool-use-validator.sh` (unit U4): asks (never silently proceeds) before a direct Write/Edit to `.claude/settings.json`, `.claude/rules/*`, or root `CLAUDE.md` — the paths `/tailor` proposes changes to rather than writing directly — backing that propose-only contract mechanically instead of leaving it to convention (removes the now-contradicting "user-configurable" comment). Extends its 6 secret-shape regexes to also scan `Bash` commands that redirect or heredoc content into a file, closing the blind spot where a heredoc'd `.env` write bypassed Write/Edit-only detection; fixes a latent bug (pre-existing, not introduced here) where the private-key pattern's leading `-----` was parsed as a grep option on BSD/macOS grep, silently never matching — `-e` on all 6 patterns now - `pre-tool-use-validator.sh` is now also registered under the PreToolUse `Bash` matcher in `settings.json`, activating U4's redirect/heredoc secret scan for real Bash tool calls (it was previously wired to Write/Edit-family tools only) - `.claude/hooks/gate-lib.sh`: new shared stack-detection library — one `gate_lib_detect` function replaces `pre-commit-verification.sh`'s inline per-stack block, emitting an INVOCABLE command (e.g. `pnpm run lint`, `uv run pytest`, `go test ./...`, `cargo clippy`) alongside the exact human label the pre-commit advisory already showed, per detected TS/JS (package-manager-aware)/Python/Go/Rust gate. `pre-commit-verification.sh` now sources the lib and reconstructs its advisory text from the labels — verified byte-identical stdout across TS/npm/Python/Go/Rust fixtures pre- and post-refactor; pure structure, zero behavior change (Two Hats). Lib's `test(pnpm)` command smoke-tested against `scripts/fixtures/failing-project/`, confirmed nonzero (unit U5a) +- Correction-capture loop (O14): `.claude/rules/core-directives.md` gains a compact "Correction Capture" subsection — when a user correction contradicts a standing rule/skill/instruction, append one line to `scratchpad/corrections.log` (`YYYY-MM-DD | correction | surface`); `land-the-plane`'s Handoff section gains a Retro step that maps each logged entry to the strongest enforcement rung it can support (rule/skill/hook/CI — the same discipline `postmortem`'s Prevention step already applies), promotes it via a small PR or filed issue, then removes the line; `stop-validator.sh` now emits a one-line session-end reminder naming the pending count when the log is non-empty. New `scripts/hook-tests.d/40-self-improvement.sh` harness cases cover the 2-line, absent, and zero-byte log states (unit U16) ## [4.0.0] - 2026-07-23 diff --git a/MIGRATION.md b/MIGRATION.md index 493ab7d..fc57c59 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -275,5 +275,15 @@ and triggers unchanged). If your own agents preloaded `designing-apis` or `appli **What breaks:** nothing breaks outright — both cases now pause for an explicit confirmation (`ask`, not `deny`) instead of proceeding silently. Fully automated/unattended pipelines that do either will need to handle the prompt. **Action required:** confirm the prompt to proceed as before. The config-file gate closes a mechanical-backing gap in `/tailor`'s "proposes only, never silently writes" contract (`.claude/skills/tailor/SKILL.md`); the Bash scan closes a blind spot where a heredoc'd `.env` write bypassed the existing Write/Edit secret detection entirely. Neither is a complete guarantee — see `docs/hooks.md`'s Secret Detection section for the standing limitation and the Trivy CI backstop. +### `stop-validator.sh` gains a correction-capture reminder +Session end now checks `scratchpad/corrections.log` (written when a user correction contradicts +standing rules/skills, per `core-directives.md`'s new Correction Capture convention). If the log is +non-empty, `stop-validator.sh` prints a one-line reminder naming the pending count and pointing at +`land-the-plane`'s new retro step — promote each entry to a rule/skill/hook/CI change, or file an +issue, before ending the session. The hook only reads the log; it never blocks the session or edits +the file itself. +**Action required:** none. The log only exists if you (or an agent) are already following the +Correction Capture convention; adopters who aren't will never see this reminder, and an absent or +empty log stays exactly as silent as before. diff --git a/scripts/hook-tests.d/40-self-improvement.sh b/scripts/hook-tests.d/40-self-improvement.sh new file mode 100755 index 0000000..456e845 --- /dev/null +++ b/scripts/hook-tests.d/40-self-improvement.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# scripts/hook-tests.d/40-self-improvement.sh — correction-capture reminder +# (O14/U16): stop-validator.sh's scratchpad/corrections.log nudge. Hermetic: +# each case gets its own throwaway CLAUDE_PROJECT_DIR; no git repo is needed +# since the reminder is independent of the hook's separate uncommitted- +# changes check (see 00-baseline.sh's "silent on a clean tree" case for that). + +_fresh_corrections_dir() { mktemp -d "${TMPDIR:-/tmp}/hook-test-corrections.XXXXXX"; } + +# --- corrections.log with 2 lines: reminder names the count and the surface +two_line_dir=$(_fresh_corrections_dir) +mkdir -p "$two_line_dir/scratchpad" +printf '2026-07-24 | example correction one | none-yet\n2026-07-24 | example correction two | none-yet\n' \ + > "$two_line_dir/scratchpad/corrections.log" +run_case \ + "stop-validator: corrections.log with 2 lines names the count" \ + ".claude/hooks/stop-validator.sh" \ + "$(cat <<'JSON' +{"session_id":"test-session","stop_hook_active":false} +JSON +)" \ + "CLAUDE_PROJECT_DIR=$two_line_dir" \ + "stdout-contains:2" +run_case \ + "stop-validator: corrections.log reminder names 'corrections' for promotion" \ + ".claude/hooks/stop-validator.sh" \ + "$(cat <<'JSON' +{"session_id":"test-session","stop_hook_active":false} +JSON +)" \ + "CLAUDE_PROJECT_DIR=$two_line_dir" \ + "stdout-contains:corrections" + +# --- absent corrections.log: silent ------------------------------------------ +absent_dir=$(_fresh_corrections_dir) +run_case \ + "stop-validator: silent (exit 0, no output) when corrections.log is absent" \ + ".claude/hooks/stop-validator.sh" \ + "$(cat <<'JSON' +{"session_id":"test-session","stop_hook_active":false} +JSON +)" \ + "CLAUDE_PROJECT_DIR=$absent_dir" \ + "exit0-silent" + +# --- empty (zero-byte) corrections.log: silent ------------------------------- +empty_dir=$(_fresh_corrections_dir) +mkdir -p "$empty_dir/scratchpad" +: > "$empty_dir/scratchpad/corrections.log" +run_case \ + "stop-validator: silent (exit 0, no output) when corrections.log is zero-byte" \ + ".claude/hooks/stop-validator.sh" \ + "$(cat <<'JSON' +{"session_id":"test-session","stop_hook_active":false} +JSON +)" \ + "CLAUDE_PROJECT_DIR=$empty_dir" \ + "exit0-silent" From 5134875a14361955595d0b8f61cd515acf1f773a Mon Sep 17 00:00:00 2001 From: Jordan Winters Date: Fri, 24 Jul 2026 11:35:00 -0500 Subject: [PATCH 2/3] docs(loop): auto-memory vs rules guidance + re-audit cadence (U16/O14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes O14 (artifacts/research_ai_coding_frustrations.md Part 3 / "The standing iteration loop") alongside the prior mechanics commit. - docs/customization.md: new "Session Learning: Auto-Memory vs. Repo Rules" section — Claude Code's per-user auto-memory is for personal preferences and machine quirks (ride the platform feature, don't rebuild it); team-shared, reviewed, CI-checked guidance belongs in .claude/rules//.claude/skills//.claude/hooks/, and corrections.log promotions land there, never only in memory. Cross-links core-directives.md's Correction Capture convention. - CONTRIBUTING.md: new "Standing self-improvement loop" section extending the eval-first/retirement policies with the full capture -> escalate -> verify -> re-audit cycle, including re-running the failure-taxonomy coverage audit from artifacts/research_ai_coding_frustrations.md at each model-generation bump. - CHANGELOG.md (Unreleased/Added) entry. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 1 + CONTRIBUTING.md | 14 ++++++++++++++ docs/customization.md | 6 ++++++ 3 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0306fdc..957d68f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `pre-tool-use-validator.sh` is now also registered under the PreToolUse `Bash` matcher in `settings.json`, activating U4's redirect/heredoc secret scan for real Bash tool calls (it was previously wired to Write/Edit-family tools only) - `.claude/hooks/gate-lib.sh`: new shared stack-detection library — one `gate_lib_detect` function replaces `pre-commit-verification.sh`'s inline per-stack block, emitting an INVOCABLE command (e.g. `pnpm run lint`, `uv run pytest`, `go test ./...`, `cargo clippy`) alongside the exact human label the pre-commit advisory already showed, per detected TS/JS (package-manager-aware)/Python/Go/Rust gate. `pre-commit-verification.sh` now sources the lib and reconstructs its advisory text from the labels — verified byte-identical stdout across TS/npm/Python/Go/Rust fixtures pre- and post-refactor; pure structure, zero behavior change (Two Hats). Lib's `test(pnpm)` command smoke-tested against `scripts/fixtures/failing-project/`, confirmed nonzero (unit U5a) - Correction-capture loop (O14): `.claude/rules/core-directives.md` gains a compact "Correction Capture" subsection — when a user correction contradicts a standing rule/skill/instruction, append one line to `scratchpad/corrections.log` (`YYYY-MM-DD | correction | surface`); `land-the-plane`'s Handoff section gains a Retro step that maps each logged entry to the strongest enforcement rung it can support (rule/skill/hook/CI — the same discipline `postmortem`'s Prevention step already applies), promotes it via a small PR or filed issue, then removes the line; `stop-validator.sh` now emits a one-line session-end reminder naming the pending count when the log is non-empty. New `scripts/hook-tests.d/40-self-improvement.sh` harness cases cover the 2-line, absent, and zero-byte log states (unit U16) +- `docs/customization.md`'s new "Session Learning: Auto-Memory vs. Repo Rules" section draws the line between Claude Code's personal, unreviewed auto-memory and this repo's team-shared, reviewed `.claude/rules/`/`.claude/skills/`/`.claude/hooks/` layer, cross-linking the Correction Capture convention; `CONTRIBUTING.md`'s new "Standing self-improvement loop" section names the full capture → escalate → verify → re-audit cycle the eval-first and retirement policies feed into, including re-running `artifacts/research_ai_coding_frustrations.md`'s failure-taxonomy coverage audit at each model-generation bump (unit U16) ## [4.0.0] - 2026-07-23 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c7164f..9b65074 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,6 +100,20 @@ passes unaided, that's the redundancy test from the eval-first policy catching u reality, not a flaw in the skill: open a retirement PR citing the run, the same way you'd cite evidence to add a skill in the first place. +## Standing self-improvement loop + +This is the loop the eval-first and retirement policies above feed into, not a separate +program: **capture** — a mid-session correction that contradicts standing guidance lands +in `scratchpad/corrections.log` (`core-directives.md`'s Correction Capture convention); +**escalate** — `land-the-plane`'s retro step maps each entry to the strongest enforcement +rung it can support (rule edit / skill edit / new hook / CI check); **verify** — promoted +skills go through the eval-first policy above, promoted hooks/CI checks get a harness case +(`scripts/hook-tests.d/`) that demonstrates the failure they now block; **re-audit** — at +each model-generation bump, alongside the retirement re-evals above, re-run the +failure-taxonomy coverage audit from `artifacts/research_ai_coding_frustrations.md` against +the then-current framework and diff against its coverage matrix. This is how the framework +compounds instead of re-discovering the same failure twice. + ## Getting started - Read [CLAUDE.md](CLAUDE.md) and `.claude/rules/` — they define the standards this repo diff --git a/docs/customization.md b/docs/customization.md index 1e5629c..5438212 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -90,6 +90,12 @@ Only loads when a file under src/api/ is read or edited. The framework intentionally ships no stack-specific frontend rule (React, Vue, etc.) — that choice belongs to the adopter, not the template. Add your own under `.claude/rules/` using `.claude/templates/rule.template.md` as the starting point. +## Session Learning: Auto-Memory vs. Repo Rules + +Claude Code's own auto-memory is personal, per-user, and unreviewed — the right home for individual preferences and machine-specific quirks. Ride that platform feature; don't rebuild it here. + +Team-shared guidance is reviewed and enforced instead: `.claude/rules/`, `.claude/skills/`, and `.claude/hooks/` are read by every collaborator and checked in CI. A correction that should bind the whole team, not just your own sessions, gets logged via `core-directives.md`'s Correction Capture convention and promoted through `land-the-plane`'s retro step — it never lives only in memory. + ## Artifact Templates Skills that produce planning artifacts bundle their templates inside the owning skill's `resources/` directory, so each skill stays self-contained (e.g. `.claude/skills/architecture/designing-systems/resources/adr.template.md`, `.claude/skills/product/planning-artifacts/resources/prd.template.md`). Three artifact types have no owning library skill; starter templates for those live in `.claude/templates/artifacts/`: `plan`, `design_spec`, `security_audit`. (The postmortem template moved into the `postmortem` skill's `resources/` when that skill was added.) Output naming for all artifact types is defined in CLAUDE.md's artifact table. From 9de6315660bf77f7c81950a5c4c2f1b0ddd9f480 Mon Sep 17 00:00:00 2001 From: Jordan Winters Date: Fri, 24 Jul 2026 11:39:59 -0500 Subject: [PATCH 3/3] fix(hooks): BSD sed fallback in branch-pr-discipline + docs row (landing follow-through) - sed -E in the no-jq command extraction: the BRE alternation form silently never matched on BSD/macOS sed, so every warning this hook gives was inert whenever jq was absent (task #26; same bug class the U1 rewrite fixed in the push blocker). Hermetic regression cases pin the fallback via GIT_DIR/GIT_WORK_TREE fixtures. - docs/hooks.md stop-validator row now mentions the corrections-log reminder (flagged by the U16 builder as outside its file list). Co-Authored-By: Claude Fable 5 --- .claude/hooks/branch-pr-discipline.sh | 4 +++- CHANGELOG.md | 1 + docs/hooks.md | 2 +- scripts/hook-tests.d/50-discipline.sh | 29 +++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 scripts/hook-tests.d/50-discipline.sh diff --git a/.claude/hooks/branch-pr-discipline.sh b/.claude/hooks/branch-pr-discipline.sh index 1c923d0..a653fe2 100755 --- a/.claude/hooks/branch-pr-discipline.sh +++ b/.claude/hooks/branch-pr-discipline.sh @@ -45,7 +45,9 @@ cmd="" if command -v jq >/dev/null 2>&1; then cmd="$(printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null)" else - cmd="$(printf '%s' "$payload" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(\([^"\\]\|\\.\)*\)".*/\1/p' | head -n1)" + # sed -E: the old BRE \| alternation silently never matches on BSD/macOS sed + # (same bug fixed in pre-push-main-blocker.sh's U1 rewrite). + cmd="$(printf '%s' "$payload" | sed -nE 's/.*"command"[[:space:]]*:[[:space:]]*"(([^"\\]|\\.)*)".*/\1/p' | head -n1)" fi [ -z "${cmd:-}" ] && exit 0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 957d68f..9a2b9f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `.claude/hooks/gate-lib.sh`: new shared stack-detection library — one `gate_lib_detect` function replaces `pre-commit-verification.sh`'s inline per-stack block, emitting an INVOCABLE command (e.g. `pnpm run lint`, `uv run pytest`, `go test ./...`, `cargo clippy`) alongside the exact human label the pre-commit advisory already showed, per detected TS/JS (package-manager-aware)/Python/Go/Rust gate. `pre-commit-verification.sh` now sources the lib and reconstructs its advisory text from the labels — verified byte-identical stdout across TS/npm/Python/Go/Rust fixtures pre- and post-refactor; pure structure, zero behavior change (Two Hats). Lib's `test(pnpm)` command smoke-tested against `scripts/fixtures/failing-project/`, confirmed nonzero (unit U5a) - Correction-capture loop (O14): `.claude/rules/core-directives.md` gains a compact "Correction Capture" subsection — when a user correction contradicts a standing rule/skill/instruction, append one line to `scratchpad/corrections.log` (`YYYY-MM-DD | correction | surface`); `land-the-plane`'s Handoff section gains a Retro step that maps each logged entry to the strongest enforcement rung it can support (rule/skill/hook/CI — the same discipline `postmortem`'s Prevention step already applies), promotes it via a small PR or filed issue, then removes the line; `stop-validator.sh` now emits a one-line session-end reminder naming the pending count when the log is non-empty. New `scripts/hook-tests.d/40-self-improvement.sh` harness cases cover the 2-line, absent, and zero-byte log states (unit U16) - `docs/customization.md`'s new "Session Learning: Auto-Memory vs. Repo Rules" section draws the line between Claude Code's personal, unreviewed auto-memory and this repo's team-shared, reviewed `.claude/rules/`/`.claude/skills/`/`.claude/hooks/` layer, cross-linking the Correction Capture convention; `CONTRIBUTING.md`'s new "Standing self-improvement loop" section names the full capture → escalate → verify → re-audit cycle the eval-first and retirement policies feed into, including re-running `artifacts/research_ai_coding_frustrations.md`'s failure-taxonomy coverage audit at each model-generation bump (unit U16) +- `branch-pr-discipline.sh`'s no-jq sed fallback switched to `sed -E` — the old BRE alternation silently never matched on BSD/macOS sed, disabling the hook's warnings whenever jq was absent (same bug class fixed in `pre-push-main-blocker.sh` by U1); regression cases added in `scripts/hook-tests.d/50-discipline.sh` ## [4.0.0] - 2026-07-23 diff --git a/docs/hooks.md b/docs/hooks.md index 32d37ca..9059cb9 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -12,7 +12,7 @@ Hooks run automatically at key points in Claude Code's lifecycle. | `pre-push-main-blocker.sh` | PreToolUse (Bash) | Block direct pushes to main/master branch | | `pre-commit-verification.sh` | PreToolUse (Bash) | Pre-commit quality checks | | `post-tool-use-tracker.sh` | PostToolUse | Track file changes | -| `stop-validator.sh` | Stop | Release file locks, cleanup session state, warn about uncommitted and unpushed work | +| `stop-validator.sh` | Stop | Release file locks, cleanup session state, warn about uncommitted/unpushed work and unprocessed `scratchpad/corrections.log` entries | | `subagent-stop-validator.sh` | SubagentStop | Log swarm worker completion | | `post-edit-lint.sh` | PostToolUse | Auto-format after edits; surfaces only unfixable issues | | `branch-pr-discipline.sh` | PreToolUse (Bash) | Warn-only branch/PR hygiene checks | diff --git a/scripts/hook-tests.d/50-discipline.sh b/scripts/hook-tests.d/50-discipline.sh new file mode 100644 index 0000000..b12a382 --- /dev/null +++ b/scripts/hook-tests.d/50-discipline.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Cases for branch-pr-discipline.sh — specifically its no-jq sed fallback. +# Regression coverage for the BSD/macOS sed -E fix: with the old BRE \| +# alternation the fallback extracted nothing on macOS, silently disabling +# every warning this hook exists to give. GIT_DIR/GIT_WORK_TREE pin the +# hook's bare `git rev-parse` calls to a fixture repo regardless of the +# harness's own checkout (hermetic on any runner/branch). + +bpd_repo=$(_fresh_dir) +git -C "$bpd_repo" init -q >/dev/null 2>&1 +git -C "$bpd_repo" -c user.email=test@example.com -c user.name=test \ + commit -q --allow-empty -m init >/dev/null 2>&1 +git -C "$bpd_repo" checkout -q -b feature/one >/dev/null 2>&1 + +bpd_nojq=$(path_without_jq) + +run_case \ + "branch-pr-discipline: no-jq sed fallback extracts the command (stacking warning fires)" \ + ".claude/hooks/branch-pr-discipline.sh" \ + '{"tool_name":"Bash","tool_input":{"command":"git checkout -b feature/two"}}' \ + "PATH=$bpd_nojq GIT_DIR=$bpd_repo/.git GIT_WORK_TREE=$bpd_repo" \ + "stdout-contains:stacks work on an unmerged branch" + +run_case \ + "branch-pr-discipline: non-branching command stays silent (no-jq path)" \ + ".claude/hooks/branch-pr-discipline.sh" \ + '{"tool_name":"Bash","tool_input":{"command":"git status"}}' \ + "PATH=$bpd_nojq GIT_DIR=$bpd_repo/.git GIT_WORK_TREE=$bpd_repo" \ + "exit0-silent"