Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/hooks/branch-pr-discipline.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions .claude/hooks/stop-validator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions .claude/rules/core-directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | <correction, one line> | <surface it contradicts: rules/<file> | skill:<name> | 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.
5 changes: 5 additions & 0 deletions .claude/skills/land-the-plane/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ 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)
- `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

Expand Down
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<!-- Future v3 migration notes appended here. -->
6 changes: 6 additions & 0 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion docs/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
58 changes: 58 additions & 0 deletions scripts/hook-tests.d/40-self-improvement.sh
Original file line number Diff line number Diff line change
@@ -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"
29 changes: 29 additions & 0 deletions scripts/hook-tests.d/50-discipline.sh
Original file line number Diff line number Diff line change
@@ -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"
Loading