start-vibing-stacks 2.14.0 → 2.16.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.
@@ -1,56 +1,104 @@
1
1
  ---
2
2
  name: docs-tracker
3
- version: 1.0.0
3
+ version: 2.0.0
4
+ description: "File → domain mapping rules and changelog templates consumed by `documenter` v2.0.0. Detects modified files via `git diff-tree`, classifies them via `.claude/config/domain-mapping.json`, and emits the actions documenter must apply (Edit known anchors, never Write over existing files; bidirectional connections; cap commit log at 20)."
4
5
  ---
5
6
 
6
- # Docs Tracker — Automatic Documentation System
7
+ # Docs Tracker — File Domain Mapping Rules (v2.0.0)
7
8
 
8
- **ALWAYS invoke AFTER implementation completes.**
9
+ **ALWAYS invoke AFTER `commit-manager` succeeds (so the commit hash is real).**
9
10
 
10
- ## Execution Flow
11
+ This skill is the **rule set** consumed by `documenter` v2.0.0. It does not write files itself — it tells documenter what to do with each changed file.
11
12
 
13
+ ## Detection (one git call, not many)
14
+
15
+ ```bash
16
+ # Files in the LAST commit (after commit-manager pushed)
17
+ git diff-tree --no-commit-id --name-status -r HEAD
12
18
  ```
13
- 1. DETECT CHANGES → git diff --name-status
14
-
15
- 2. CLASSIFY → A=Added, M=Modified, D=Deleted
16
-
17
- 3. CHECK EXISTING DOCS → codebase-knowledge/domains/
18
-
19
- 4. CREATE/UPDATE/REMOVE as needed
20
-
21
- 5. UPDATE CLAUDE.md "Last Change" section
22
- ```
23
19
 
24
- ## What to Document
20
+ Status codes: `A` = added, `M` = modified, `D` = deleted, `R` = renamed, `C` = copied. Treat `R` and `C` as `M` for mapping purposes plus a "renamed from" note in the destination domain.
21
+
22
+ ## File → Domain mapping
23
+
24
+ Source of truth: `.claude/config/domain-mapping.json` (shipped with this CLI; project may override). A path may map to ≥1 domain. Unmatched paths → `general`.
25
+
26
+ Skip the entire pass if every changed path matches one of:
25
27
 
26
- | Change Type | Action |
28
+ | Skip pattern | Reason |
27
29
  |---|---|
28
- | New file | Create domain entry if new domain |
29
- | Modified file | Update domain "Last Update" + "Recent Commits" |
30
- | Deleted file | Remove from domain "Files" table |
31
- | New connection | Add to "Connections" in BOTH domains |
32
- | Bug fix | Add to "Problems & Solutions" |
30
+ | `.claude/**` | meta does not belong to any product domain |
31
+ | `docs/**` | docs commit no code drift |
32
+ | `.github/**` | CI handled by `infrastructure` domain only if pattern is added |
33
+ | `CHANGELOG*`, `*.md` (root only) | release docs |
34
+ | `package.json`, `composer.json`, `pyproject.toml` (deps only not scripts) | bumps tracked elsewhere |
35
+
36
+ ## Action matrix (applied by `documenter`)
37
+
38
+ | Git status | Domain file state | Action |
39
+ |---|---|---|
40
+ | `A` | exists | `Edit`: append row to `## Files`, prepend row to `## Recent Commits`, increment `files_count` in frontmatter |
41
+ | `A` | missing | `Write`: create from `TEMPLATE.md`, fill TL;DR, add the file row, add the commit row |
42
+ | `M` | exists | `Edit`: prepend row to `## Recent Commits`; if file's role description changed, update its row in `## Files` |
43
+ | `M` | missing | unusual — investigate; usually means mapping rule was added but file existed before. Treat as `A` |
44
+ | `D` | exists | `Edit`: strike-through (`~~path~~`) row in `## Files`, decrement `files_count`. Prune at the next pass |
45
+ | `R src→dst` | exists | `Edit`: replace `src` row with `dst` row, add note `(renamed from src in <short-sha>)` to Attention Points |
46
+
47
+ ## Bidirectional connections (mandatory)
48
+
49
+ When a session adds `auth → api` to `auth.md`, **also** add `api ← auth` to `api.md`. The two edits must succeed atomically — if the second fails, roll back the first. The `security-auditor` flags dangling links as a MEDIUM finding.
50
+
51
+ ## Cap and archive (size guard)
33
52
 
34
- ## Detection Commands
53
+ After any edit, check the live file:
35
54
 
36
55
  ```bash
37
- # What changed?
38
- git diff --name-status HEAD~1
56
+ [ "$(wc -c < domains/<slug>.md)" -gt 8192 ] && trigger_archive
57
+ ```
58
+
59
+ When triggered, move the **oldest 5 rows** of `## Recent Commits` (and oldest 2 Problems & Solutions, oldest 3 Attention Points) into `<slug>.archive.md`. Live file must stay ≤ 8 KB.
39
60
 
40
- # What's staged?
41
- git diff --name-only --cached
61
+ ## Index regeneration (after every pass)
42
62
 
43
- # What's unstaged?
44
- git diff --name-only
63
+ ```bash
64
+ # Regenerate _index.json from frontmatter of every domain file
65
+ for f in .claude/skills/codebase-knowledge/domains/*.md; do
66
+ # parse YAML frontmatter, compute summary_sha = sha256(TL;DR block), emit one record
67
+ done | jq -s '{schema_version:1, generated_at:(now|todate), domain_count:length, domains:.}' \
68
+ > .claude/skills/codebase-knowledge/_index.json
45
69
 
46
- # What's untracked?
47
- git ls-files --others --exclude-standard
70
+ # Then derive _INDEX.md from _index.json
71
+ ```
72
+
73
+ `_index.json` is the source of truth — `_INDEX.md` is human-readable derivative.
74
+
75
+ ## Pre-commit hook (CI integration)
76
+
77
+ A repository can add this check to fail builds when documenter forgot to run:
78
+
79
+ ```bash
80
+ HEAD_SHA=$(git rev-parse --short HEAD)
81
+ LAST_INDEXED=$(jq -r '.domains | map(.last_commit) | unique[]' \
82
+ .claude/skills/codebase-knowledge/_index.json)
83
+ echo "$LAST_INDEXED" | grep -qx "$HEAD_SHA" || {
84
+ echo "ERROR: _index.json is stale. Run documenter before pushing."
85
+ exit 1
86
+ }
48
87
  ```
49
88
 
50
89
  ## Rules
51
90
 
52
- 1. **RUN AFTER EVERY IMPLEMENTATION** no exceptions
53
- 2. **DETECT VIA GIT** — don't guess, use git diff
54
- 3. **UPDATE DOMAINS** — not just CLAUDE.md
55
- 4. **BIDIRECTIONAL** — if A↔B connection, update both
56
- 5. **INCLUDE COMMIT HASH** — traceability
91
+ 1. **AFTER `commit-manager`**never before; the hash must be real.
92
+ 2. **DETECT VIA GIT** — `git diff-tree -r HEAD`, never guess from session memory.
93
+ 3. **EDIT KNOWN ANCHORS** — `documenter` uses `Edit` / `StrReplace` on existing domain files; never `Write` over them.
94
+ 4. **BIDIRECTIONAL** — both ends of every connection updated atomically.
95
+ 5. **CAP AT 8 KB / 20 commits / 10 attention / 5 P&S** — archive overflow into `<slug>.archive.md`.
96
+ 6. **REGENERATE `_index.json` EVERY PASS** — derived; never hand-edited.
97
+ 7. **SKIP META PATHS** — `.claude/**`, `docs/**`, `.github/**` do not get domain entries.
98
+
99
+ ## See Also
100
+
101
+ - `documenter` v2.0.0 — applies the actions defined here
102
+ - `codebase-knowledge` v2.0.0 — reads what documenter wrote
103
+ - `domain-updater` v2.0.0 — appends session wisdom AFTER documenter
104
+ - `.claude/config/domain-mapping.json` — pattern → domain rules
@@ -1,40 +1,163 @@
1
1
  ---
2
2
  name: git-workflow
3
- version: 1.0.0
3
+ version: 2.0.0
4
+ description: "Git workflow conventions used by commit-manager v2.0.0+: Conventional Commits (no AI-attribution footers), branch naming, direct-to-main vs feature-branch flow, end-on-main rule, push policy, never force-push main, signed commits encouraged. Invoke when starting a feature, creating commits, pushing, or wiring git-related hooks."
4
5
  ---
5
6
 
6
7
  # Git Workflow
7
8
 
8
- ## Branch Naming
9
+ **ALWAYS invoke when starting work, creating commits, pushing, or wiring `pre-commit` / `Stop` hooks that touch git.**
10
+
11
+ > The git history is documentation. A reader six months from now should understand *why*, not just *what*. The commit-manager agent enforces the message format below; this skill defines what counts as compliant.
12
+
13
+ ## 1. Branch naming
9
14
 
10
15
  ```
11
- feature/short-description
12
- fix/bug-description
13
- refactor/what-changed
14
- chore/maintenance-task
16
+ feature/<short-kebab-description>
17
+ fix/<bug-id-or-description>
18
+ refactor/<area-changed>
19
+ chore/<maintenance-task>
20
+ docs/<scope>
21
+ ci/<workflow-area>
15
22
  ```
16
23
 
17
- ## Commit Format (Conventional)
24
+ Rules:
25
+ - Always kebab-case, never spaces or underscores.
26
+ - ≤ 50 chars; longer goes in the PR description.
27
+ - One feature per branch.
28
+
29
+ ## 2. Conventional Commits — required format
30
+
31
+ ```
32
+ <type>(<scope>): <subject>
33
+
34
+ <body — wrapped at ~72 cols; bullets when ≥ 3 files affected>
35
+ ```
36
+
37
+ | Type | When |
38
+ |---|---|
39
+ | `feat` | New user-visible capability |
40
+ | `fix` | Bug fix |
41
+ | `refactor` | Internal change, no behavior change |
42
+ | `perf` | Performance improvement |
43
+ | `test` | Tests added/changed only |
44
+ | `docs` | Documentation only |
45
+ | `chore` | Build, deps, tooling — nothing user-visible |
46
+ | `ci` | CI/CD config |
47
+ | `style` | Formatting only |
48
+ | `revert` | Reverts a prior commit |
49
+ | `build` | Build system / bundler config |
50
+
51
+ Subject:
52
+ - ≤ 50 chars
53
+ - imperative ("add login retry", not "added", not "adds")
54
+ - no trailing period
55
+ - lowercase first word (after the type/scope prefix)
56
+
57
+ Body (only when ≥ 3 files or non-obvious *why*):
58
+ - explain motivation, trade-offs, links to issues
59
+ - bullet list when summarizing multiple changes
60
+
61
+ ### Example
18
62
 
19
63
  ```
20
- type(scope): description
64
+ feat(auth): add session refresh with rotating cookies
21
65
 
22
- Body explaining what and why.
66
+ - Issue rotated session id on each login + every 7 days
67
+ - Persist previous id for 30s grace window to avoid races
68
+ - Drop legacy cookie name `sid` from response set; honor on read
69
+ ```
23
70
 
71
+ ### **No AI-attribution footers** *(commit-manager v2.0.0+ rule)*
72
+
73
+ Do **not** append:
74
+ ```
24
75
  Generated with Claude Code
25
76
  Co-Authored-By: Claude <noreply@anthropic.com>
26
77
  ```
27
78
 
28
- ## Flow
79
+ The commit message describes the change. Tooling provenance lives in CI artifacts, signed commits, or release metadata — not in every commit body.
80
+
81
+ ## 3. Two valid flows
82
+
83
+ The repo's CLAUDE.md (or `git-workflow` overlay) declares which is in effect.
84
+
85
+ ### Flow A — direct-to-main (small repos, single contributor, fast iteration)
86
+
87
+ ```
88
+ 1. Pull main, ensure clean tree
89
+ 2. Make changes
90
+ 3. Quality gate green
91
+ 4. Commit → push to main
92
+ ```
93
+
94
+ ### Flow B — feature branch + merge (multi-contributor, CI gates required)
29
95
 
30
- 1. Create branch from main: `git checkout -b feature/name`
96
+ ```
97
+ 1. From clean main, create branch: git checkout -b feature/<name>
31
98
  2. Work, commit incrementally
32
- 3. When done: merge to main, delete branch
33
- 4. Push main to remote
99
+ 3. Quality gate green
100
+ 4. Push branch: git push -u origin HEAD
101
+ 5. Open PR; CI must pass
102
+ 6. Merge (squash or rebase — never merge commit on main)
103
+ 7. Delete branch local + remote
104
+ 8. Checkout main, pull
105
+ ```
106
+
107
+ End state in **both flows**: clean tree, on `main`, in sync with `origin/main`. The Stop validator enforces this.
108
+
109
+ ## 4. Push policy
110
+
111
+ | Operation | Allowed | Notes |
112
+ |---|---|---|
113
+ | `git push` to feature branch | ✅ Always | Force-push only if branch is yours and not yet reviewed |
114
+ | `git push` to main | ✅ When tree clean and CI green locally | Branch protection should reject otherwise |
115
+ | `git push --force-with-lease` to feature branch | ✅ Preferred over `--force` | Refuses if remote moved since your last fetch |
116
+ | `git push --force` to main | ❌ Never | Re-writes shared history |
117
+ | `git push --no-verify` | ❌ Never | Bypasses hooks intentionally configured to gate |
118
+
119
+ ## 5. Pre-flight checklist (commit-manager runs this internally)
120
+
121
+ - [ ] Working tree status reviewed (`git status -sb`)
122
+ - [ ] Diff reviewed (`git diff --stat HEAD` then `git diff` if needed)
123
+ - [ ] Quality gate passed (typecheck / lint / tests / build)
124
+ - [ ] Security gate passed (`security-auditor` clean)
125
+ - [ ] No secrets in diff (gitleaks)
126
+ - [ ] CLAUDE.md `Last Change` updated when scope warrants it
127
+ - [ ] Conventional commit message drafted
128
+ - [ ] Push target confirmed (`origin main` vs `origin feature/*`)
129
+
130
+ ## 6. Tags & releases
131
+
132
+ ```bash
133
+ # Annotated tags only — lightweight tags are stripped by some hosts
134
+ git tag -a v1.2.3 -m "Release v1.2.3"
135
+ git push origin v1.2.3
136
+ ```
137
+
138
+ Semver: bump `MAJOR.MINOR.PATCH` per breaking/feature/fix.
139
+
140
+ ## 7. Recovering from mistakes
141
+
142
+ | Situation | Fix |
143
+ |---|---|
144
+ | Bad commit not pushed | `git reset --soft HEAD~1`, fix, re-commit |
145
+ | Bad commit pushed to feature branch | `git commit --amend` then `git push --force-with-lease` |
146
+ | Bad commit pushed to main | `git revert <sha>` and push the revert (NEVER force-push main) |
147
+ | Pushed a secret | Revoke at provider FIRST, then `git filter-repo` + force-push (one-time, document it) |
148
+ | Wrong branch | `git stash`, `git checkout <correct>`, `git stash pop` |
34
149
 
35
150
  ## Rules
36
151
 
37
- - NEVER force push main
38
- - ALWAYS conventional commits
39
- - ALWAYS end on main branch
40
- - ONE feature per branch
152
+ - **NEVER** force-push `main` / `master`
153
+ - **NEVER** `--no-verify` to bypass hooks
154
+ - **ALWAYS** Conventional Commits, no AI-attribution footers
155
+ - **ALWAYS** end on `main` with a clean tree (Stop validator enforces)
156
+ - **ONE** feature per branch (or per direct-to-main commit batch)
157
+
158
+ ## See Also
159
+
160
+ - `commit-manager` agent — drives the message + push pipeline
161
+ - `quality-gate` — the gate the commit-manager waits for
162
+ - `secrets-management` — gitleaks 3-layer (pre-commit, CI, push protection)
163
+ - `ci-pipelines` — branch-protection rules that mirror this skill
@@ -1,93 +1,271 @@
1
1
  ---
2
2
  name: hook-development
3
- version: 1.0.0
3
+ version: 2.0.0
4
+ description: "Claude Code hook development for 2026. Covers all 9 supported events (UserPromptSubmit, PreToolUse, PostToolUse, Notification, Stop, SubagentStop, SessionStart, SessionEnd, PreCompact), stdin/stdout JSON contract, decision/continue/systemMessage outputs, matchers (tool patterns), exit-code semantics, cycle detection (stop_hook_active), settings.json registration, run-hook.sh dispatcher (bun → tsx → node fallback). Invoke when creating or modifying any .claude/hooks/* file or settings.json hook entry."
4
5
  ---
5
6
 
6
- # Hook Development — Claude Code Hooks
7
+ # Hook Development — Claude Code Hooks (2026)
7
8
 
8
9
  **ALWAYS invoke when creating or modifying Claude Code hooks.**
9
10
 
10
- ## Hook Types
11
+ > Hooks are user-controlled deterministic gates around the model. Anything you want **enforced**, not asked nicely — put in a hook. The agent cannot disable a hook from inside the conversation.
11
12
 
12
- | Event | When | Use Case |
13
- |-------|------|----------|
14
- | `UserPromptSubmit` | Before prompt is sent | Inject workflow, validate input |
15
- | `Stop` | Before task completion | Validate state, block if dirty |
16
- | `PreToolUse` | Before tool execution | Approve/block dangerous tools |
17
- | `PostToolUse` | After tool execution | Log, validate output |
13
+ ## 1. Supported events (Claude Code, 2026)
18
14
 
19
- ## File Structure
15
+ | Event | Fires | Common use |
16
+ |---|---|---|
17
+ | `SessionStart` | When a session begins (new chat or resume) | Inject onboarding context, restore state |
18
+ | `SessionEnd` | When a session ends cleanly | Flush logs, persist state, run cleanup |
19
+ | `UserPromptSubmit` | After user submits, before model receives | Inject workflow rules, block forbidden phrases |
20
+ | `PreToolUse` | Before any tool is invoked | Approve / block / rewrite tool calls (security gate) |
21
+ | `PostToolUse` | After tool completes | Log, validate output, react to specific tool results |
22
+ | `Notification` | When the agent is idle and notifies (e.g. waiting for input) | Desktop notifications, status forwarding |
23
+ | `Stop` | Before the agent ends its turn | Validate completion (clean tree, tests pass, docs updated) |
24
+ | `SubagentStop` | When a Task() subagent finishes | Validate subagent output, persist artifacts |
25
+ | `PreCompact` | Before transcript compaction is triggered | Snapshot/export full transcript first |
26
+
27
+ `Stop` and `Subagent` events are the most commonly customized. `PreToolUse` is the security workhorse.
28
+
29
+ ## 2. File layout
20
30
 
21
31
  ```
22
- .claude/hooks/
23
- ├── run-hook.sh # Entry point (bash → bun/tsx fallback)
24
- ├── user-prompt-submit.ts # Prompt injection
25
- └── stop-validator.ts # Task completion gate
32
+ .claude/
33
+ ├── hooks/
34
+ ├── run-hook.sh # Entry point: bun → tsx → node fallback
35
+ │ ├── session-start.ts
36
+ │ ├── session-end.ts
37
+ │ ├── user-prompt-submit.ts
38
+ │ ├── pre-tool-use.ts
39
+ │ ├── post-tool-use.ts
40
+ │ ├── stop-validator.ts
41
+ │ ├── subagent-stop.ts
42
+ │ ├── pre-compact.ts
43
+ │ └── lib/ # shared helpers (cmd(), readStdin(), etc.)
44
+ └── settings.json # registers hooks
26
45
  ```
27
46
 
28
- ## Hook Input (stdin JSON)
47
+ ## 3. Stdin contract (per event)
48
+
49
+ All hook input arrives as **a single JSON object on stdin**. Read with a timeout — never hang.
29
50
 
30
51
  ```typescript
31
- // UserPromptSubmit
32
- interface PromptInput {
33
- user_prompt: string;
52
+ // Common envelope (most events)
53
+ interface HookInput {
34
54
  session_id: string;
55
+ cwd: string;
56
+ transcript_path?: string;
57
+ hook_event_name: string; // e.g. "Stop"
35
58
  }
36
59
 
37
- // Stop
38
- interface StopInput {
39
- stop_hook_active?: boolean; // Cycle detection
40
- transcript?: string;
60
+ interface UserPromptSubmitInput extends HookInput {
61
+ user_prompt: string;
41
62
  }
42
- ```
43
63
 
44
- ## Hook Output (stdout JSON)
64
+ interface PreToolUseInput extends HookInput {
65
+ tool_name: string; // "Bash" | "Edit" | "Write" | ...
66
+ tool_input: Record<string, unknown>;
67
+ }
45
68
 
46
- ```typescript
47
- // UserPromptSubmit — inject system message
48
- { "continue": true, "systemMessage": "WORKFLOW: ..." }
69
+ interface PostToolUseInput extends PreToolUseInput {
70
+ tool_response: unknown;
71
+ tool_error?: string;
72
+ }
49
73
 
50
- // Stop approve or block
51
- { "continue": false, "decision": "approve", "reason": "All checks passed" }
52
- { "continue": true, "decision": "block", "reason": "Uncommitted files" }
74
+ interface StopInput extends HookInput {
75
+ stop_hook_active?: boolean; // TRUE if a previous Stop hook already blocked — don't loop
76
+ }
77
+
78
+ interface SubagentStopInput extends StopInput {
79
+ subagent_id: string;
80
+ }
81
+
82
+ interface PreCompactInput extends HookInput {
83
+ trigger: "manual" | "auto";
84
+ }
53
85
  ```
54
86
 
55
- ## Template: Stop Validator
87
+ ## 4. Stdout contract (per event)
56
88
 
57
- ```typescript
58
- #!/usr/bin/env node
59
- import { execSync } from 'child_process';
89
+ Output a **single JSON object on stdout**. Anything else (or invalid JSON) is treated as advisory only.
60
90
 
61
- function cmd(c: string): string {
62
- try { return execSync(c, { encoding: 'utf8' }).trim(); } catch { return ''; }
91
+ ```typescript
92
+ // Universal fields
93
+ interface HookOutput {
94
+ continue?: boolean; // false = stop the agent now (Stop family)
95
+ decision?: "approve" | "block"; // gate verdict
96
+ reason?: string; // shown to user / logged
97
+ systemMessage?: string; // injected as system message (UserPromptSubmit, SessionStart)
98
+ hookSpecificOutput?: Record<string, unknown>;
63
99
  }
100
+ ```
64
101
 
65
- const branch = cmd('git rev-parse --abbrev-ref HEAD');
66
- const dirty = cmd('git status --porcelain');
102
+ PreToolUse block / approve / rewrite a tool call:
103
+ ```json
104
+ { "decision": "approve", "reason": "Edit on src/ allowed" }
105
+ { "decision": "block", "reason": "Write outside src/ rejected" }
106
+ ```
67
107
 
68
- const result = (!dirty && (branch === 'main' || branch === 'master'))
69
- ? { continue: false, decision: 'approve', reason: 'Clean main branch' }
70
- : { continue: true, decision: 'block', reason: `Branch: ${branch}, dirty: ${!!dirty}` };
108
+ Stop block prevents finalization until issues are fixed:
109
+ ```json
110
+ { "continue": true, "decision": "block", "reason": "Uncommitted files" }
111
+ ```
71
112
 
72
- console.log(JSON.stringify(result));
113
+ SessionStart — inject context:
114
+ ```json
115
+ { "systemMessage": "ROUTINE: research → plan → implement → test → commit" }
73
116
  ```
74
117
 
75
- ## settings.json Registration
118
+ ## 5. Exit-code semantics
119
+
120
+ | Exit | Meaning |
121
+ |---|---|
122
+ | `0` | Success — JSON output applied if present |
123
+ | `2` | **Blocking** — message printed to stderr is shown to the user (alternative to `decision: "block"`) |
124
+ | anything else | Non-blocking error — logged, agent continues |
125
+
126
+ **Never** exit non-zero on a transient error (e.g. network) unless you intend to block. Hooks must be deterministic.
127
+
128
+ ## 6. `settings.json` registration
76
129
 
77
130
  ```json
78
131
  {
79
132
  "hooks": {
80
- "Stop": [{ "hooks": [{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" stop-validator", "timeout": 30 }] }],
81
- "UserPromptSubmit": [{ "matcher": "", "hooks": [{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" user-prompt-submit", "timeout": 10 }] }]
133
+ "SessionStart": [
134
+ {
135
+ "hooks": [{
136
+ "type": "command",
137
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" session-start",
138
+ "timeout": 10
139
+ }]
140
+ }
141
+ ],
142
+ "UserPromptSubmit": [
143
+ {
144
+ "matcher": "",
145
+ "hooks": [{
146
+ "type": "command",
147
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" user-prompt-submit",
148
+ "timeout": 10
149
+ }]
150
+ }
151
+ ],
152
+ "PreToolUse": [
153
+ {
154
+ "matcher": "Bash|Write|Edit",
155
+ "hooks": [{
156
+ "type": "command",
157
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" pre-tool-use",
158
+ "timeout": 10
159
+ }]
160
+ }
161
+ ],
162
+ "Stop": [
163
+ {
164
+ "hooks": [{
165
+ "type": "command",
166
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" stop-validator",
167
+ "timeout": 30
168
+ }]
169
+ }
170
+ ]
82
171
  }
83
172
  }
84
173
  ```
85
174
 
86
- ## Rules
175
+ `matcher` is a regex against `tool_name` (PreToolUse / PostToolUse) or empty for "all". Multiple entries per event are run in registration order.
176
+
177
+ ## 7. `run-hook.sh` dispatcher
178
+
179
+ Survives whichever runtime the user has installed. Always-zero exit so hook errors don't kill the session.
180
+
181
+ ```bash
182
+ #!/usr/bin/env bash
183
+ # .claude/hooks/run-hook.sh
184
+ set -uo pipefail
185
+ HOOK_NAME="${1:?missing hook name}"
186
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
187
+ HOOK_FILE="$HOOK_DIR/${HOOK_NAME}.ts"
188
+
189
+ [[ -f "$HOOK_FILE" ]] || { echo '{"continue":true}'; exit 0; }
190
+
191
+ if command -v bun >/dev/null 2>&1; then bun "$HOOK_FILE"
192
+ elif command -v tsx >/dev/null 2>&1; then tsx "$HOOK_FILE"
193
+ elif command -v node >/dev/null 2>&1; then npx --yes tsx "$HOOK_FILE"
194
+ else echo '{"continue":true}'; exit 0
195
+ fi
196
+ ```
197
+
198
+ ## 8. Stop validator template (gate-aware, with cycle detection)
199
+
200
+ Use `execFileSync` (not `execSync` with shell strings) — passes args as an array, immune to shell injection.
201
+
202
+ ```typescript
203
+ #!/usr/bin/env node
204
+ import { execFileSync } from 'node:child_process';
205
+ import { readFileSync, existsSync } from 'node:fs';
206
+
207
+ function git(...args: string[]): string {
208
+ try { return execFileSync('git', args, { encoding: 'utf8' }).trim(); } catch { return ''; }
209
+ }
210
+
211
+ const input = JSON.parse(readFileSync(0, 'utf8')); // stdin
212
+
213
+ // Cycle detection — if we already blocked once, approve to avoid infinite loop
214
+ if (input.stop_hook_active) {
215
+ console.log(JSON.stringify({ continue: false, decision: 'approve' }));
216
+ process.exit(0);
217
+ }
218
+
219
+ const dirty = git('status', '--porcelain');
220
+ const issues: string[] = [];
221
+
222
+ if (dirty) issues.push(`GIT_TREE_NOT_CLEAN: ${dirty.split('\n').length} file(s)`);
223
+ if (existsSync('CLAUDE.md') &&
224
+ !readFileSync('CLAUDE.md', 'utf8').includes('Last Change')) issues.push('CLAUDE_MD_NOT_UPDATED');
225
+
226
+ const result = issues.length === 0
227
+ ? { continue: false, decision: 'approve', reason: 'All checks passed' }
228
+ : { continue: true, decision: 'block', reason: issues.join(' | ') };
229
+
230
+ console.log(JSON.stringify(result));
231
+ process.exit(0);
232
+ ```
233
+
234
+ ## 9. Performance & safety rules
235
+
236
+ 1. **Timeouts:** 10s for `UserPromptSubmit` / `PreToolUse` / `SessionStart`; 30s for `Stop` / `SubagentStop`.
237
+ 2. **Always exit 0** unless intentionally blocking with exit 2 — non-zero kills the session.
238
+ 3. **Read stdin with timeout** — `readFileSync(0, 'utf8')` is fine (it returns immediately when EOF).
239
+ 4. **Output valid JSON** — invalid JSON is treated as advisory and silently dropped.
240
+ 5. **Cycle detection** — check `stop_hook_active` in `Stop` / `SubagentStop`. Without this, a buggy hook can lock the agent in an infinite block-fix-block loop.
241
+ 6. **No network calls** in hot-path hooks (`PreToolUse`, `UserPromptSubmit`) — they fire on every tool call / prompt.
242
+ 7. **Idempotent** — hooks may be invoked twice (retries, restarts). Don't write to immutable state without a guard.
243
+ 8. **Use `execFileSync`, never `execSync` with a shell string** — the latter is shell-injection-prone if any input ever flows in.
244
+
245
+ ## 10. Common patterns
246
+
247
+ | Goal | Event | Pattern |
248
+ |---|---|---|
249
+ | "Inject the current routine on every prompt" | `UserPromptSubmit` | `{ continue: true, systemMessage: "ROUTINE: ..." }` |
250
+ | "Block writes outside `src/`" | `PreToolUse` | match `Write\|Edit`, inspect `tool_input.path`, `decision: "block"` if outside |
251
+ | "Run quality gate before finishing" | `Stop` | run typecheck/lint/tests, `decision: "block"` if any fail |
252
+ | "Persist subagent output to disk" | `SubagentStop` | parse `tool_response`, write to `.claude/subagent-outputs/{id}.json` |
253
+ | "Snapshot transcript before compaction" | `PreCompact` | copy `transcript_path` to `.claude/transcripts/{session_id}.jsonl` |
254
+
255
+ ## FORBIDDEN
256
+
257
+ | Pattern | Why |
258
+ |---|---|
259
+ | `process.exit(1)` on transient error | Kills the session |
260
+ | Hook output not valid JSON | Dropped silently — no enforcement |
261
+ | Network call in `PreToolUse` | Adds latency to every tool call |
262
+ | Forgetting `stop_hook_active` check | Infinite block loop |
263
+ | Writing to `CLAUDE.md` from `Stop` hook | Triggers another `Stop` cycle |
264
+ | Hardcoded user paths (`~/Users/me/...`) | Won't run on other machines |
265
+ | Shell-string `execSync` for git/fs commands | Shell-injection surface |
266
+
267
+ ## See Also
87
268
 
88
- 1. **Always use `run-hook.sh` as entry** handles bun/tsx fallback
89
- 2. **Read stdin with timeout** hooks must not hang
90
- 3. **Exit 0 always** non-zero kills the session
91
- 4. **Output valid JSON to stdout** — Claude Code parses it
92
- 5. **Cycle detection** — check `stop_hook_active` flag in Stop hooks
93
- 6. **Keep hooks fast** — timeout applies (10s for prompt, 30s for stop)
269
+ - `quality-gate` the Stop validator's typical payload
270
+ - `git-workflow`branch / dirty-tree checks
271
+ - `commit-manager` pairs with Stop validator (validator detects, commit-manager fixes)