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
36 changes: 36 additions & 0 deletions .claude/hooks/session-start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash
# Canonical Tap House SessionStart hook for Claude Code on the web.
#
# Fresh web-session containers clone the repo bare: no submodules, no
# pre-commit hook installed — so `git commit` runs unformatted and the
# clang-format CI gate fails on code a local clone would have fixed at commit
# time. This hook closes that gap at session start:
#
# 1. init submodules recursively (kernels, SDKs, vendored test harnesses)
# 2. install pre-commit and register the repo's canonical hook
# (.pre-commit-config.yaml — the Tap-wide pinned clang-format)
# 3. warm the pinned clang-format binary so the first commit doesn't
# pay the download (the container snapshot caches it)
#
# Canonical copy: taphouse (distributed by scripts/sync.sh alongside
# .clang-format / .clang-tidy / .pre-commit-config.yaml; drift-guarded where
# present). Web-only; local clones are untouched.
set -euo pipefail

if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
exit 0
fi

cd "$CLAUDE_PROJECT_DIR"

echo "session-start: initializing submodules ..."
git submodule update --init --recursive

echo "session-start: installing pre-commit ..."
if ! python3 -m pre_commit --version >/dev/null 2>&1; then
python3 -m pip install --quiet pre-commit
fi
python3 -m pre_commit install
python3 -m pre_commit install-hooks

echo "session-start: done."
14 changes: 14 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh"
}
]
}
]
}
}
11 changes: 11 additions & 0 deletions .github/workflows/drift-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,16 @@ jobs:
fail=1
fi
fi
# The Claude Code web SessionStart hook is guarded only when BOTH
# sides carry it: older pinned TapHouse refs predate the hook, and
# .claude/settings.json is deliberately unguarded (repos may extend
# their settings with repo-specific hooks).
if [ -f ".claude/hooks/session-start.sh" ] && [ -f ".taphouse/.claude/hooks/session-start.sh" ]; then
if ! diff -q ".taphouse/.claude/hooks/session-start.sh" ".claude/hooks/session-start.sh" >/dev/null 2>&1; then
echo "::error file=.claude/hooks/session-start.sh::session-start.sh differs from TapHouse canonical (ref ${{ inputs.ref }}). Re-run taphouse/scripts/sync.sh and commit."
diff -u ".taphouse/.claude/hooks/session-start.sh" ".claude/hooks/session-start.sh" || true
fail=1
fi
fi
if [ "$fail" -eq 0 ]; then echo "All shared configs match TapHouse@${{ inputs.ref }}."; fi
exit $fail
38 changes: 38 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

**TapHouse** — the single source of truth for the Tap family's shared style and setup files.
Nothing here is consumed at build time; the files are *distributed as copies* into each consumer
repo's root by `scripts/sync.sh`, and consumer CI runs the reusable
`.github/workflows/drift-check.yml` against a pinned tag to guarantee the copies never silently
diverge. `README.md` is the authoritative description of the mechanism; read it before changing
anything.

## The rules that keep this working

- **Never hand-edit a synced copy in a consumer repo.** Change the canonical file *here*, tag,
then re-run `scripts/sync.sh` into each consumer. A hand-edited consumer copy fails that repo's
drift job — that is the mechanism working, not a nuisance.
- **Adding a file to the canonical set touches three places:** the file itself, the copy step in
`scripts/sync.sh`, and (if it should be guarded) a comparison in `drift-check.yml`. Files that
not every consumer carries — `scripts/tidy.sh`, `.claude/hooks/session-start.sh` — are guarded
*conditionally* (only when present; the hook additionally only when the pinned TapHouse ref
carries it, so older pins don't break). `.claude/settings.json` is deliberately unguarded and
synced only-if-missing: repos may extend their settings with repo-specific hooks.
- **Two version knobs, both living here** (see README "Updating the rules"): the TapHouse tag
(consumers pin it in their `style.yml` `ref:`) and the clang-format version (the mirror `rev:`
inside `.pre-commit-config.yaml`). Tagging is a deliberate release act — bump consumer `ref:`s
in the same sweep as the re-sync, and don't move existing tags.
- **Consumer rollout order matters:** merge the TapHouse change first, then sync + commit in
consumers. The conditional guards make the in-between state safe, but a guarded unconditional
file (the core four) must exist at the pinned ref before consumers reference it.

## Conventions recorded here but not yet drift-enforced

The family C++ namespace convention lives in README.md (one `tap::<library>` sub-namespace per
repo) rather than in the drift-checked `STYLE.md`, so recording it didn't force a family-wide
re-sync. Promote such conventions into `STYLE.md` (a tagged release) only once every consumer
complies.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Canonical **Tap House Rules** — the shared C++ style for the Tap family of
libraries (AmbiTap, SampleRateTap, OscTap, AmbiTap-Pd, AmbiTap-Max, …).

This repo is the single source of truth for four root-level config files, plus
one distributed helper script:
two distributed helper scripts:

| File | Enforces |
|------|----------|
Expand All @@ -13,6 +13,7 @@ one distributed helper script:
| `STYLE.md` | The human-readable rules and rationale |
| `.pre-commit-config.yaml` | Local git-hook wiring: formats staged C/C++ **before** each commit, so the clang-format CI gate can't fail on something a developer could have caught locally |
| `scripts/tidy.sh` | Local mirror of the CI **clang-tidy** gate (naming + mandatory braces) over a repo's own TUs. Distributed to C++ repos that run the clang-tidy gate; kept a single copy here so it can't fork per-repo (it was, briefly). Repo-agnostic — no project name is baked in. |
| `.claude/hooks/session-start.sh` | Claude Code **web** sessions: fresh containers clone bare (no submodules, no pre-commit hook), so agent commits could bypass the local format layer entirely. The hook initializes submodules, installs the pinned pre-commit hook, and warms its clang-format at session start. Registered per-repo by `.claude/settings.json` (created by sync only if missing — repos may extend their settings, so only the hook *script* is drift-guarded). |

`clang-format` and `clang-tidy` discover their config by walking **up** the
directory tree from each source file, so those config files (and the
Expand Down Expand Up @@ -75,7 +76,9 @@ consumer has migrated and it can be enforced.
```
Now `git commit` reformats staged C/C++ with TapHouse's pinned clang-format
before the commit lands. First run fetches the pinned hook; thereafter it is
cached and offline.
cached and offline. (Claude Code **web** sessions do this automatically via
the synced SessionStart hook, once it's merged on the repo's default
branch — no per-clone step there.)

3. **Run the same hook in CI** instead of a hand-installed clang-format, so the
version can never skew from local:
Expand Down
15 changes: 15 additions & 0 deletions scripts/sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,19 @@ cp "$here/scripts/tidy.sh" "$target/scripts/tidy.sh"
chmod +x "$target/scripts/tidy.sh"
echo "synced scripts/tidy.sh -> $target/scripts/tidy.sh"

# Claude Code web SessionStart hook — initializes submodules and installs the
# pinned pre-commit hook in fresh web-session containers, so the clang-format
# gate can never fail on code a local clone would have formatted at commit
# time. The hook script is canonical (drift-guarded where present); the
# .claude/settings.json registration is created only if missing, because repos
# may legitimately extend their settings with repo-specific hooks.
mkdir -p "$target/.claude/hooks"
cp "$here/.claude/hooks/session-start.sh" "$target/.claude/hooks/session-start.sh"
chmod +x "$target/.claude/hooks/session-start.sh"
echo "synced .claude/hooks/session-start.sh -> $target/.claude/hooks/session-start.sh"
if [ ! -f "$target/.claude/settings.json" ]; then
cp "$here/.claude/settings.json" "$target/.claude/settings.json"
echo "created $target/.claude/settings.json (registers the hook; repo-specific settings merge there)"
fi

echo "Done. Review and commit the updated files in: $target"