claudient 0.1.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/.claude-plugin/plugin.json +42 -0
- package/CONTEXT.md +58 -0
- package/README.md +165 -0
- package/agents/build-resolvers/de/python-resolver.md +64 -0
- package/agents/build-resolvers/de/typescript-resolver.md +65 -0
- package/agents/build-resolvers/es/python-resolver.md +64 -0
- package/agents/build-resolvers/es/typescript-resolver.md +65 -0
- package/agents/build-resolvers/fr/python-resolver.md +64 -0
- package/agents/build-resolvers/fr/typescript-resolver.md +65 -0
- package/agents/build-resolvers/nl/python-resolver.md +64 -0
- package/agents/build-resolvers/nl/typescript-resolver.md +65 -0
- package/agents/build-resolvers/python-resolver.md +62 -0
- package/agents/build-resolvers/typescript-resolver.md +63 -0
- package/agents/core/architect.md +64 -0
- package/agents/core/code-reviewer.md +78 -0
- package/agents/core/de/architect.md +66 -0
- package/agents/core/de/code-reviewer.md +80 -0
- package/agents/core/de/planner.md +63 -0
- package/agents/core/de/security-reviewer.md +93 -0
- package/agents/core/es/architect.md +66 -0
- package/agents/core/es/code-reviewer.md +80 -0
- package/agents/core/es/planner.md +63 -0
- package/agents/core/es/security-reviewer.md +93 -0
- package/agents/core/fr/architect.md +66 -0
- package/agents/core/fr/code-reviewer.md +80 -0
- package/agents/core/fr/planner.md +63 -0
- package/agents/core/fr/security-reviewer.md +93 -0
- package/agents/core/nl/architect.md +66 -0
- package/agents/core/nl/code-reviewer.md +80 -0
- package/agents/core/nl/planner.md +63 -0
- package/agents/core/nl/security-reviewer.md +93 -0
- package/agents/core/planner.md +61 -0
- package/agents/core/security-reviewer.md +91 -0
- package/guides/agent-orchestration.md +231 -0
- package/guides/de/agent-orchestration.md +174 -0
- package/guides/de/getting-started.md +164 -0
- package/guides/de/hooks-cookbook.md +160 -0
- package/guides/de/memory-management.md +153 -0
- package/guides/de/security.md +180 -0
- package/guides/de/skill-authoring.md +214 -0
- package/guides/de/token-optimization.md +156 -0
- package/guides/es/agent-orchestration.md +174 -0
- package/guides/es/getting-started.md +164 -0
- package/guides/es/hooks-cookbook.md +160 -0
- package/guides/es/memory-management.md +153 -0
- package/guides/es/security.md +180 -0
- package/guides/es/skill-authoring.md +214 -0
- package/guides/es/token-optimization.md +156 -0
- package/guides/fr/agent-orchestration.md +174 -0
- package/guides/fr/getting-started.md +164 -0
- package/guides/fr/hooks-cookbook.md +227 -0
- package/guides/fr/memory-management.md +169 -0
- package/guides/fr/security.md +180 -0
- package/guides/fr/skill-authoring.md +214 -0
- package/guides/fr/token-optimization.md +158 -0
- package/guides/getting-started.md +164 -0
- package/guides/hooks-cookbook.md +423 -0
- package/guides/memory-management.md +192 -0
- package/guides/nl/agent-orchestration.md +174 -0
- package/guides/nl/getting-started.md +164 -0
- package/guides/nl/hooks-cookbook.md +160 -0
- package/guides/nl/memory-management.md +153 -0
- package/guides/nl/security.md +180 -0
- package/guides/nl/skill-authoring.md +214 -0
- package/guides/nl/token-optimization.md +156 -0
- package/guides/security.md +229 -0
- package/guides/skill-authoring.md +226 -0
- package/guides/token-optimization.md +169 -0
- package/hooks/lifecycle/cost-tracker.md +49 -0
- package/hooks/lifecycle/cost-tracker.sh +59 -0
- package/hooks/lifecycle/pre-compact-save.md +56 -0
- package/hooks/lifecycle/pre-compact-save.sh +37 -0
- package/hooks/lifecycle/session-start.md +50 -0
- package/hooks/lifecycle/session-start.sh +47 -0
- package/hooks/post-tool-use/audit-log.md +53 -0
- package/hooks/post-tool-use/audit-log.sh +53 -0
- package/hooks/post-tool-use/prettier.md +53 -0
- package/hooks/post-tool-use/prettier.sh +49 -0
- package/hooks/pre-tool-use/block-dangerous.md +48 -0
- package/hooks/pre-tool-use/block-dangerous.sh +76 -0
- package/hooks/pre-tool-use/git-push-confirm.md +46 -0
- package/hooks/pre-tool-use/git-push-confirm.sh +36 -0
- package/mcp/configs/github.json +11 -0
- package/mcp/configs/postgres.json +11 -0
- package/mcp/de/recommended-servers.md +170 -0
- package/mcp/es/recommended-servers.md +170 -0
- package/mcp/fr/recommended-servers.md +170 -0
- package/mcp/nl/recommended-servers.md +170 -0
- package/mcp/recommended-servers.md +168 -0
- package/package.json +45 -0
- package/prompts/project-starters/de/fastapi-project.md +62 -0
- package/prompts/project-starters/de/nextjs-project.md +82 -0
- package/prompts/project-starters/es/fastapi-project.md +62 -0
- package/prompts/project-starters/es/nextjs-project.md +82 -0
- package/prompts/project-starters/fastapi-project.md +60 -0
- package/prompts/project-starters/fr/fastapi-project.md +62 -0
- package/prompts/project-starters/fr/nextjs-project.md +82 -0
- package/prompts/project-starters/nextjs-project.md +80 -0
- package/prompts/project-starters/nl/fastapi-project.md +62 -0
- package/prompts/project-starters/nl/nextjs-project.md +82 -0
- package/prompts/system-prompts/ai-product.md +80 -0
- package/prompts/system-prompts/data-pipeline.md +76 -0
- package/prompts/system-prompts/de/ai-product.md +82 -0
- package/prompts/system-prompts/de/data-pipeline.md +78 -0
- package/prompts/system-prompts/de/saas-backend.md +71 -0
- package/prompts/system-prompts/es/ai-product.md +82 -0
- package/prompts/system-prompts/es/data-pipeline.md +78 -0
- package/prompts/system-prompts/es/saas-backend.md +71 -0
- package/prompts/system-prompts/fr/ai-product.md +82 -0
- package/prompts/system-prompts/fr/data-pipeline.md +78 -0
- package/prompts/system-prompts/fr/saas-backend.md +71 -0
- package/prompts/system-prompts/nl/ai-product.md +82 -0
- package/prompts/system-prompts/nl/data-pipeline.md +78 -0
- package/prompts/system-prompts/nl/saas-backend.md +71 -0
- package/prompts/system-prompts/saas-backend.md +69 -0
- package/prompts/task-specific/changelog.md +81 -0
- package/prompts/task-specific/de/changelog.md +83 -0
- package/prompts/task-specific/de/debugging.md +78 -0
- package/prompts/task-specific/de/pr-description.md +69 -0
- package/prompts/task-specific/debugging.md +76 -0
- package/prompts/task-specific/es/changelog.md +83 -0
- package/prompts/task-specific/es/debugging.md +78 -0
- package/prompts/task-specific/es/pr-description.md +69 -0
- package/prompts/task-specific/fr/changelog.md +83 -0
- package/prompts/task-specific/fr/debugging.md +78 -0
- package/prompts/task-specific/fr/pr-description.md +69 -0
- package/prompts/task-specific/nl/changelog.md +83 -0
- package/prompts/task-specific/nl/debugging.md +78 -0
- package/prompts/task-specific/nl/pr-description.md +69 -0
- package/prompts/task-specific/pr-description.md +67 -0
- package/rules/common/coding-style.md +45 -0
- package/rules/common/de/coding-style.md +47 -0
- package/rules/common/de/git.md +48 -0
- package/rules/common/de/performance.md +40 -0
- package/rules/common/de/security.md +45 -0
- package/rules/common/de/testing.md +45 -0
- package/rules/common/es/coding-style.md +47 -0
- package/rules/common/es/git.md +48 -0
- package/rules/common/es/performance.md +40 -0
- package/rules/common/es/security.md +45 -0
- package/rules/common/es/testing.md +45 -0
- package/rules/common/fr/coding-style.md +47 -0
- package/rules/common/fr/git.md +48 -0
- package/rules/common/fr/performance.md +40 -0
- package/rules/common/fr/security.md +45 -0
- package/rules/common/fr/testing.md +45 -0
- package/rules/common/git.md +46 -0
- package/rules/common/nl/coding-style.md +47 -0
- package/rules/common/nl/git.md +48 -0
- package/rules/common/nl/performance.md +40 -0
- package/rules/common/nl/security.md +45 -0
- package/rules/common/nl/testing.md +45 -0
- package/rules/common/performance.md +38 -0
- package/rules/common/security.md +43 -0
- package/rules/common/testing.md +43 -0
- package/rules/language-specific/de/go.md +48 -0
- package/rules/language-specific/de/python.md +38 -0
- package/rules/language-specific/de/typescript.md +51 -0
- package/rules/language-specific/es/go.md +48 -0
- package/rules/language-specific/es/python.md +38 -0
- package/rules/language-specific/es/typescript.md +51 -0
- package/rules/language-specific/fr/go.md +48 -0
- package/rules/language-specific/fr/python.md +38 -0
- package/rules/language-specific/fr/typescript.md +51 -0
- package/rules/language-specific/go.md +46 -0
- package/rules/language-specific/nl/go.md +48 -0
- package/rules/language-specific/nl/python.md +38 -0
- package/rules/language-specific/nl/typescript.md +51 -0
- package/rules/language-specific/python.md +36 -0
- package/rules/language-specific/typescript.md +49 -0
- package/scripts/cli.js +161 -0
- package/scripts/link-skills.sh +35 -0
- package/scripts/list-skills.sh +34 -0
- package/skills/ai-engineering/agent-construction.md +285 -0
- package/skills/ai-engineering/claude-api.md +248 -0
- package/skills/ai-engineering/de/agent-construction.md +287 -0
- package/skills/ai-engineering/de/claude-api.md +250 -0
- package/skills/ai-engineering/es/agent-construction.md +287 -0
- package/skills/ai-engineering/es/claude-api.md +250 -0
- package/skills/ai-engineering/fr/agent-construction.md +287 -0
- package/skills/ai-engineering/fr/claude-api.md +250 -0
- package/skills/ai-engineering/nl/agent-construction.md +287 -0
- package/skills/ai-engineering/nl/claude-api.md +250 -0
- package/skills/backend/dotnet/csharp.md +304 -0
- package/skills/backend/dotnet/de/csharp.md +306 -0
- package/skills/backend/dotnet/es/csharp.md +306 -0
- package/skills/backend/dotnet/fr/csharp.md +306 -0
- package/skills/backend/dotnet/nl/csharp.md +306 -0
- package/skills/backend/go/de/go.md +307 -0
- package/skills/backend/go/es/go.md +307 -0
- package/skills/backend/go/fr/go.md +307 -0
- package/skills/backend/go/go.md +305 -0
- package/skills/backend/go/nl/go.md +307 -0
- package/skills/backend/nodejs/de/nestjs.md +274 -0
- package/skills/backend/nodejs/de/nextjs.md +222 -0
- package/skills/backend/nodejs/es/nestjs.md +274 -0
- package/skills/backend/nodejs/es/nextjs.md +222 -0
- package/skills/backend/nodejs/fr/nestjs.md +274 -0
- package/skills/backend/nodejs/fr/nextjs.md +222 -0
- package/skills/backend/nodejs/nestjs.md +272 -0
- package/skills/backend/nodejs/nextjs.md +220 -0
- package/skills/backend/nodejs/nl/nestjs.md +274 -0
- package/skills/backend/nodejs/nl/nextjs.md +222 -0
- package/skills/backend/python/de/django.md +285 -0
- package/skills/backend/python/de/fastapi.md +244 -0
- package/skills/backend/python/django.md +283 -0
- package/skills/backend/python/es/django.md +285 -0
- package/skills/backend/python/es/fastapi.md +244 -0
- package/skills/backend/python/fastapi.md +242 -0
- package/skills/backend/python/fr/django.md +285 -0
- package/skills/backend/python/fr/fastapi.md +244 -0
- package/skills/backend/python/nl/django.md +285 -0
- package/skills/backend/python/nl/fastapi.md +244 -0
- package/skills/data-ml/dbt-data-pipelines.md +155 -0
- package/skills/data-ml/de/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/de/pandas-polars.md +147 -0
- package/skills/data-ml/de/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/es/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/es/pandas-polars.md +147 -0
- package/skills/data-ml/es/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/fr/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/fr/pandas-polars.md +147 -0
- package/skills/data-ml/fr/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/nl/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/nl/pandas-polars.md +147 -0
- package/skills/data-ml/nl/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/pandas-polars.md +145 -0
- package/skills/data-ml/pytorch-tensorflow.md +169 -0
- package/skills/database/de/graphql.md +181 -0
- package/skills/database/es/graphql.md +181 -0
- package/skills/database/fr/graphql.md +181 -0
- package/skills/database/graphql.md +179 -0
- package/skills/database/nl/graphql.md +181 -0
- package/skills/devops-infra/de/docker.md +133 -0
- package/skills/devops-infra/de/github-actions.md +179 -0
- package/skills/devops-infra/de/kubernetes.md +129 -0
- package/skills/devops-infra/de/terraform.md +130 -0
- package/skills/devops-infra/docker.md +131 -0
- package/skills/devops-infra/es/docker.md +133 -0
- package/skills/devops-infra/es/github-actions.md +179 -0
- package/skills/devops-infra/es/kubernetes.md +129 -0
- package/skills/devops-infra/es/terraform.md +130 -0
- package/skills/devops-infra/fr/docker.md +133 -0
- package/skills/devops-infra/fr/github-actions.md +179 -0
- package/skills/devops-infra/fr/kubernetes.md +129 -0
- package/skills/devops-infra/fr/terraform.md +130 -0
- package/skills/devops-infra/github-actions.md +177 -0
- package/skills/devops-infra/kubernetes.md +127 -0
- package/skills/devops-infra/nl/docker.md +133 -0
- package/skills/devops-infra/nl/github-actions.md +179 -0
- package/skills/devops-infra/nl/kubernetes.md +129 -0
- package/skills/devops-infra/nl/terraform.md +130 -0
- package/skills/devops-infra/terraform.md +128 -0
- package/skills/finance-payments/de/stripe.md +187 -0
- package/skills/finance-payments/es/stripe.md +187 -0
- package/skills/finance-payments/fr/stripe.md +187 -0
- package/skills/finance-payments/nl/stripe.md +187 -0
- package/skills/finance-payments/stripe.md +185 -0
- package/workflows/code-review.md +151 -0
- package/workflows/de/code-review.md +153 -0
- package/workflows/de/debugging-session.md +146 -0
- package/workflows/de/feature-development.md +155 -0
- package/workflows/de/new-project-bootstrap.md +175 -0
- package/workflows/de/refactor-safely.md +150 -0
- package/workflows/debugging-session.md +144 -0
- package/workflows/es/code-review.md +153 -0
- package/workflows/es/debugging-session.md +146 -0
- package/workflows/es/feature-development.md +155 -0
- package/workflows/es/new-project-bootstrap.md +175 -0
- package/workflows/es/refactor-safely.md +150 -0
- package/workflows/feature-development.md +153 -0
- package/workflows/fr/code-review.md +153 -0
- package/workflows/fr/debugging-session.md +146 -0
- package/workflows/fr/feature-development.md +155 -0
- package/workflows/fr/new-project-bootstrap.md +175 -0
- package/workflows/fr/refactor-safely.md +150 -0
- package/workflows/new-project-bootstrap.md +173 -0
- package/workflows/nl/code-review.md +153 -0
- package/workflows/nl/debugging-session.md +146 -0
- package/workflows/nl/feature-development.md +155 -0
- package/workflows/nl/new-project-bootstrap.md +175 -0
- package/workflows/nl/refactor-safely.md +150 -0
- package/workflows/refactor-safely.md +148 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# cost-tracker.sh — estimates and logs token costs per session
|
|
3
|
+
#
|
|
4
|
+
# Install: add to settings.json hooks.PostToolUse
|
|
5
|
+
# {
|
|
6
|
+
# "matcher": "",
|
|
7
|
+
# "hooks": [{
|
|
8
|
+
# "type": "command",
|
|
9
|
+
# "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/cost-tracker.sh",
|
|
10
|
+
# "async": true
|
|
11
|
+
# }]
|
|
12
|
+
# }
|
|
13
|
+
#
|
|
14
|
+
# Log location: .claude/logs/costs.log
|
|
15
|
+
# Add .claude/logs/ to .gitignore
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
INPUT=$(cat)
|
|
20
|
+
LOG_FILE="${CLAUDE_PROJECT_DIR:-$(pwd)}/.claude/logs/costs.log"
|
|
21
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
22
|
+
SESSION_ID="${CLAUDE_SESSION_ID:-unknown}"
|
|
23
|
+
|
|
24
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
25
|
+
|
|
26
|
+
# Extract token usage if available in the event payload
|
|
27
|
+
USAGE=$(echo "$INPUT" | python3 -c "
|
|
28
|
+
import sys, json
|
|
29
|
+
try:
|
|
30
|
+
d = json.load(sys.stdin)
|
|
31
|
+
usage = d.get('usage', {})
|
|
32
|
+
inp_tokens = usage.get('input_tokens', 0)
|
|
33
|
+
out_tokens = usage.get('output_tokens', 0)
|
|
34
|
+
cache_read = usage.get('cache_read_input_tokens', 0)
|
|
35
|
+
cache_write = usage.get('cache_creation_input_tokens', 0)
|
|
36
|
+
|
|
37
|
+
# Approximate costs (Sonnet 4.6 pricing as baseline — update as needed)
|
|
38
|
+
# Input: \$3/MTok, Output: \$15/MTok, Cache read: \$0.30/MTok, Cache write: \$3.75/MTok
|
|
39
|
+
cost = (
|
|
40
|
+
(inp_tokens / 1_000_000) * 3.0 +
|
|
41
|
+
(out_tokens / 1_000_000) * 15.0 +
|
|
42
|
+
(cache_read / 1_000_000) * 0.30 +
|
|
43
|
+
(cache_write / 1_000_000) * 3.75
|
|
44
|
+
)
|
|
45
|
+
print(f'input={inp_tokens} output={out_tokens} cache_read={cache_read} est_cost_usd={cost:.6f}')
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f'parse_error={e}')
|
|
48
|
+
" 2>/dev/null || echo "usage=unavailable")
|
|
49
|
+
|
|
50
|
+
TOOL_NAME=$(echo "$INPUT" | python3 -c "
|
|
51
|
+
import sys, json
|
|
52
|
+
try:
|
|
53
|
+
d = json.load(sys.stdin)
|
|
54
|
+
print(d.get('tool_name', 'unknown'))
|
|
55
|
+
except:
|
|
56
|
+
print('unknown')
|
|
57
|
+
" 2>/dev/null || echo "unknown")
|
|
58
|
+
|
|
59
|
+
echo "${TIMESTAMP} | session=${SESSION_ID} | tool=${TOOL_NAME} | ${USAGE}" >> "$LOG_FILE"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Hook: Pre-Compact Save
|
|
2
|
+
|
|
3
|
+
Saves session state to a temporary file before Claude Code compacts the conversation history, preserving context that would otherwise be lost.
|
|
4
|
+
|
|
5
|
+
## Event
|
|
6
|
+
`PreCompact` — fires before the context compaction step
|
|
7
|
+
|
|
8
|
+
## settings.json entry
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"hooks": {
|
|
13
|
+
"PreCompact": [
|
|
14
|
+
{
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/pre-compact-save.sh",
|
|
19
|
+
"timeout": 10
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`PreCompact` has no `matcher` field — it always fires before compaction.
|
|
29
|
+
|
|
30
|
+
## What it does
|
|
31
|
+
|
|
32
|
+
Before Claude compacts the conversation, this hook:
|
|
33
|
+
1. Writes the current working directory, open files, and recent bash commands to `.claude/tmp/session-state.md`
|
|
34
|
+
2. The file is included in the compaction summary prompt, helping Claude retain what it was working on
|
|
35
|
+
|
|
36
|
+
Output format:
|
|
37
|
+
```markdown
|
|
38
|
+
# Session State (pre-compact snapshot)
|
|
39
|
+
**Time:** 2026-05-13T10:45:00Z
|
|
40
|
+
**CWD:** /Users/dev/myproject
|
|
41
|
+
**Recent files touched:** src/api/users.py, tests/test_users.py
|
|
42
|
+
**Last command:** pytest tests/ -x
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Why this matters
|
|
46
|
+
|
|
47
|
+
Without this hook, compaction can cause Claude to lose track of which files it was editing, what tests were passing, and what the current task was. This hook gives the compaction model a structured anchor.
|
|
48
|
+
|
|
49
|
+
## Setup
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cp hooks/lifecycle/pre-compact-save.sh .claude/hooks/
|
|
53
|
+
chmod +x .claude/hooks/pre-compact-save.sh
|
|
54
|
+
mkdir -p .claude/tmp
|
|
55
|
+
echo ".claude/tmp/" >> .gitignore
|
|
56
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pre-compact-save.sh — saves session state before compaction fires
|
|
3
|
+
#
|
|
4
|
+
# Install: add to settings.json hooks.PreCompact
|
|
5
|
+
# {
|
|
6
|
+
# "matcher": "",
|
|
7
|
+
# "hooks": [{
|
|
8
|
+
# "type": "command",
|
|
9
|
+
# "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/pre-compact-save.sh"
|
|
10
|
+
# }]
|
|
11
|
+
# }
|
|
12
|
+
#
|
|
13
|
+
# Also add to CLAUDE.md:
|
|
14
|
+
# "When PreCompact fires, summarize: current task state, files modified,
|
|
15
|
+
# open decisions, next steps. Append to .claude/memory/session-state.md
|
|
16
|
+
# with a timestamp header."
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
MEMORY_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}/.claude/memory"
|
|
21
|
+
STATE_FILE="${MEMORY_DIR}/session-state.md"
|
|
22
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
23
|
+
|
|
24
|
+
mkdir -p "$MEMORY_DIR"
|
|
25
|
+
|
|
26
|
+
# Create or append a timestamped section for Claude to fill in
|
|
27
|
+
cat >> "$STATE_FILE" << EOF
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
## Session snapshot — ${TIMESTAMP}
|
|
31
|
+
|
|
32
|
+
*[Claude: append your summary here — current task, files modified, open decisions, next steps]*
|
|
33
|
+
|
|
34
|
+
EOF
|
|
35
|
+
|
|
36
|
+
# Output to stdout so Claude sees the instruction
|
|
37
|
+
echo "PreCompact triggered at ${TIMESTAMP}. Append your session summary to .claude/memory/session-state.md before compaction."
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Hook: Session Start
|
|
2
|
+
|
|
3
|
+
Runs setup tasks when a Claude Code session begins — prints a welcome banner, checks for uncommitted changes, and reminds Claude of the project context.
|
|
4
|
+
|
|
5
|
+
## Event
|
|
6
|
+
`Notification` — fires on session start (and other lifecycle notifications)
|
|
7
|
+
|
|
8
|
+
## settings.json entry
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"hooks": {
|
|
13
|
+
"Notification": [
|
|
14
|
+
{
|
|
15
|
+
"matcher": "session_start",
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/session-start.sh",
|
|
20
|
+
"timeout": 5
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
30
|
+
|
|
31
|
+
On session start:
|
|
32
|
+
1. Prints project name and current branch to stdout (visible in Claude's context)
|
|
33
|
+
2. Runs `git status --short` — if there are uncommitted changes, outputs a summary
|
|
34
|
+
3. Checks for a `.claude/session-notes.md` file and prints it if present (use this for per-project reminders)
|
|
35
|
+
|
|
36
|
+
Example output injected into context:
|
|
37
|
+
```
|
|
38
|
+
=== Session Start: myproject (branch: feature/auth) ===
|
|
39
|
+
Uncommitted changes: 3 files (M src/auth.py, M tests/test_auth.py, ?? .env.local)
|
|
40
|
+
Session notes: Working on JWT refresh token implementation. Tests failing on line 42.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Setup
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cp hooks/lifecycle/session-start.sh .claude/hooks/
|
|
47
|
+
chmod +x .claude/hooks/session-start.sh
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Optionally create `.claude/session-notes.md` with notes for Claude at the start of each session. Update it as context changes.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# session-start.sh — loads context reminders at the start of each session
|
|
3
|
+
#
|
|
4
|
+
# Install: add to settings.json hooks.SessionStart
|
|
5
|
+
# {
|
|
6
|
+
# "matcher": "",
|
|
7
|
+
# "hooks": [{
|
|
8
|
+
# "type": "command",
|
|
9
|
+
# "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/session-start.sh"
|
|
10
|
+
# }]
|
|
11
|
+
# }
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
16
|
+
|
|
17
|
+
REMINDERS=()
|
|
18
|
+
|
|
19
|
+
# Check for previous session state
|
|
20
|
+
if [ -f "${PROJECT_DIR}/.claude/memory/session-state.md" ]; then
|
|
21
|
+
REMINDERS+=("Previous session state found at .claude/memory/session-state.md — read it before starting work.")
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Check for current task file
|
|
25
|
+
if [ -f "${PROJECT_DIR}/.claude/memory/current-task.md" ]; then
|
|
26
|
+
REMINDERS+=("Active task context found at .claude/memory/current-task.md — read it to understand what's in progress.")
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Check for CONTEXT.md
|
|
30
|
+
if [ -f "${PROJECT_DIR}/CONTEXT.md" ]; then
|
|
31
|
+
REMINDERS+=("Domain glossary available at CONTEXT.md — use it for project terminology.")
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Check for ADRs
|
|
35
|
+
if [ -d "${PROJECT_DIR}/docs/adr" ] && [ "$(ls -A "${PROJECT_DIR}/docs/adr" 2>/dev/null)" ]; then
|
|
36
|
+
ADR_COUNT=$(find "${PROJECT_DIR}/docs/adr" -name "*.md" | wc -l | tr -d ' ')
|
|
37
|
+
REMINDERS+=("${ADR_COUNT} architectural decisions recorded in docs/adr/ — check before making architectural choices.")
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Output reminders
|
|
41
|
+
if [ ${#REMINDERS[@]} -gt 0 ]; then
|
|
42
|
+
echo "=== Session context ==="
|
|
43
|
+
for reminder in "${REMINDERS[@]}"; do
|
|
44
|
+
echo "• $reminder"
|
|
45
|
+
done
|
|
46
|
+
echo "======================"
|
|
47
|
+
fi
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Hook: Audit Log
|
|
2
|
+
|
|
3
|
+
Records every tool call to a structured JSONL log file for review, compliance, and debugging.
|
|
4
|
+
|
|
5
|
+
## Event
|
|
6
|
+
`PostToolUse` — fires after every tool call completes
|
|
7
|
+
|
|
8
|
+
## settings.json entry
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"hooks": {
|
|
13
|
+
"PostToolUse": [
|
|
14
|
+
{
|
|
15
|
+
"matcher": "",
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-log.sh",
|
|
20
|
+
"async": true
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Empty `matcher` means it fires for every tool, not just Bash. `async: true` means it runs in the background without blocking Claude.
|
|
30
|
+
|
|
31
|
+
## What it does
|
|
32
|
+
|
|
33
|
+
Appends a JSON line to `.claude/logs/audit.log` for every tool call:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{"timestamp":"2026-05-13T10:23:45Z","tool":"Write","input":{"file_path":"/src/app.py"},"exit_code":0}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Fields logged: `timestamp`, `tool_name`, `tool_input` (sanitized — secrets stripped), `exit_code`, `duration_ms`.
|
|
40
|
+
|
|
41
|
+
Useful for:
|
|
42
|
+
- Auditing what Claude changed during a session
|
|
43
|
+
- Debugging unexpected file modifications
|
|
44
|
+
- Compliance requirements that mandate change logging
|
|
45
|
+
|
|
46
|
+
## Setup
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cp hooks/post-tool-use/audit-log.sh .claude/hooks/
|
|
50
|
+
chmod +x .claude/hooks/audit-log.sh
|
|
51
|
+
mkdir -p .claude/logs
|
|
52
|
+
echo ".claude/logs/" >> .gitignore
|
|
53
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# audit-log.sh — logs every tool call with timestamp and tool name
|
|
3
|
+
#
|
|
4
|
+
# Install: add to settings.json hooks.PostToolUse
|
|
5
|
+
# {
|
|
6
|
+
# "matcher": "",
|
|
7
|
+
# "hooks": [{
|
|
8
|
+
# "type": "command",
|
|
9
|
+
# "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/audit-log.sh",
|
|
10
|
+
# "async": true
|
|
11
|
+
# }]
|
|
12
|
+
# }
|
|
13
|
+
#
|
|
14
|
+
# Log location: .claude/logs/audit.log
|
|
15
|
+
# Add .claude/logs/ to .gitignore
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
INPUT=$(cat)
|
|
20
|
+
LOG_FILE="${CLAUDE_PROJECT_DIR:-$(pwd)}/.claude/logs/audit.log"
|
|
21
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
22
|
+
|
|
23
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
24
|
+
|
|
25
|
+
TOOL_NAME=$(echo "$INPUT" | python3 -c "
|
|
26
|
+
import sys, json
|
|
27
|
+
try:
|
|
28
|
+
d = json.load(sys.stdin)
|
|
29
|
+
print(d.get('tool_name', 'unknown'))
|
|
30
|
+
except:
|
|
31
|
+
print('unknown')
|
|
32
|
+
" 2>/dev/null || echo "unknown")
|
|
33
|
+
|
|
34
|
+
TOOL_SUMMARY=$(echo "$INPUT" | python3 -c "
|
|
35
|
+
import sys, json
|
|
36
|
+
try:
|
|
37
|
+
d = json.load(sys.stdin)
|
|
38
|
+
inp = d.get('tool_input', {})
|
|
39
|
+
# Summarize without exposing sensitive values
|
|
40
|
+
if 'command' in inp:
|
|
41
|
+
print(f\"cmd: {str(inp['command'])[:120]}\")
|
|
42
|
+
elif 'file_path' in inp:
|
|
43
|
+
print(f\"file: {inp['file_path']}\")
|
|
44
|
+
elif 'url' in inp:
|
|
45
|
+
print(f\"url: {inp['url']}\")
|
|
46
|
+
else:
|
|
47
|
+
keys = list(inp.keys())[:3]
|
|
48
|
+
print(f\"keys: {keys}\")
|
|
49
|
+
except:
|
|
50
|
+
print('(parse error)')
|
|
51
|
+
" 2>/dev/null || echo "(error)")
|
|
52
|
+
|
|
53
|
+
echo "${TIMESTAMP} | ${TOOL_NAME} | ${TOOL_SUMMARY}" >> "$LOG_FILE"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Hook: Auto-Prettier
|
|
2
|
+
|
|
3
|
+
Runs Prettier on any file written or edited by Claude, keeping formatting consistent without a separate format step.
|
|
4
|
+
|
|
5
|
+
## Event
|
|
6
|
+
`PostToolUse` — fires after `Write` and `Edit` tool calls
|
|
7
|
+
|
|
8
|
+
## settings.json entry
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"hooks": {
|
|
13
|
+
"PostToolUse": [
|
|
14
|
+
{
|
|
15
|
+
"matcher": "Write|Edit",
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/prettier.sh",
|
|
20
|
+
"async": true
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Matcher `Write|Edit` limits this to file-writing tools only — no overhead on Bash or Read calls.
|
|
30
|
+
|
|
31
|
+
## What it does
|
|
32
|
+
|
|
33
|
+
Extracts the `file_path` from the tool input JSON, checks if Prettier supports that file extension (`.ts`, `.tsx`, `.js`, `.jsx`, `.json`, `.css`, `.md`), and runs:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx prettier --write "$FILE_PATH"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Runs async so it doesn't block Claude's next action. If Prettier isn't installed or fails, the hook exits silently — it never blocks Claude.
|
|
40
|
+
|
|
41
|
+
## Requirements
|
|
42
|
+
|
|
43
|
+
- Node.js ≥ 18
|
|
44
|
+
- `prettier` in the project's `node_modules` or globally installed
|
|
45
|
+
- A `.prettierrc` or `prettier.config.js` in the project root (optional — uses Prettier defaults otherwise)
|
|
46
|
+
|
|
47
|
+
## Setup
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cp hooks/post-tool-use/prettier.sh .claude/hooks/
|
|
51
|
+
chmod +x .claude/hooks/prettier.sh
|
|
52
|
+
npm install --save-dev prettier # if not already installed
|
|
53
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# prettier.sh — auto-formats files after Claude writes or edits them
|
|
3
|
+
#
|
|
4
|
+
# Install: add to settings.json hooks.PostToolUse
|
|
5
|
+
# {
|
|
6
|
+
# "matcher": "Write|Edit",
|
|
7
|
+
# "hooks": [{
|
|
8
|
+
# "type": "command",
|
|
9
|
+
# "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/prettier.sh",
|
|
10
|
+
# "async": true
|
|
11
|
+
# }]
|
|
12
|
+
# }
|
|
13
|
+
#
|
|
14
|
+
# Requires: prettier installed (npx prettier or local node_modules)
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
INPUT=$(cat)
|
|
19
|
+
|
|
20
|
+
FILE_PATH=$(echo "$INPUT" | python3 -c "
|
|
21
|
+
import sys, json
|
|
22
|
+
try:
|
|
23
|
+
d = json.load(sys.stdin)
|
|
24
|
+
print(d.get('tool_input', {}).get('file_path', ''))
|
|
25
|
+
except:
|
|
26
|
+
print('')
|
|
27
|
+
" 2>/dev/null || echo "")
|
|
28
|
+
|
|
29
|
+
if [ -z "$FILE_PATH" ]; then
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Only format files prettier supports
|
|
34
|
+
SUPPORTED_EXTENSIONS=("js" "jsx" "ts" "tsx" "json" "css" "scss" "html" "md" "yaml" "yml")
|
|
35
|
+
EXTENSION="${FILE_PATH##*.}"
|
|
36
|
+
|
|
37
|
+
for ext in "${SUPPORTED_EXTENSIONS[@]}"; do
|
|
38
|
+
if [ "$EXTENSION" = "$ext" ]; then
|
|
39
|
+
# Use local prettier if available, fall back to npx
|
|
40
|
+
if [ -f "${CLAUDE_PROJECT_DIR:-$(pwd)}/node_modules/.bin/prettier" ]; then
|
|
41
|
+
"${CLAUDE_PROJECT_DIR:-$(pwd)}/node_modules/.bin/prettier" --write "$FILE_PATH" 2>/dev/null || true
|
|
42
|
+
else
|
|
43
|
+
npx prettier --write "$FILE_PATH" 2>/dev/null || true
|
|
44
|
+
fi
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
exit 0
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Hook: Block Dangerous Commands
|
|
2
|
+
|
|
3
|
+
Intercepts `Bash` tool calls before they execute and blocks or warns on destructive shell patterns.
|
|
4
|
+
|
|
5
|
+
## Event
|
|
6
|
+
`PreToolUse` — fires before every tool call, matcher set to `Bash`
|
|
7
|
+
|
|
8
|
+
## settings.json entry
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"hooks": {
|
|
13
|
+
"PreToolUse": [
|
|
14
|
+
{
|
|
15
|
+
"matcher": "Bash",
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-dangerous.sh",
|
|
20
|
+
"timeout": 5
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
30
|
+
|
|
31
|
+
Reads the `tool_input.command` field from the JSON payload on stdin and matches it against two pattern lists:
|
|
32
|
+
|
|
33
|
+
- **Blocked patterns** (exit 2 — hard block): `rm -rf /`, `rm -rf ~`, `mkfs`, `dd if=/dev/zero`, fork bomb, `chmod -R 777 /`
|
|
34
|
+
- **Warning patterns** (exit 1 — Claude sees warning, user decides): `rm -rf`, pipe to `bash`/`sh`, `sudo`, `git push --force`, `git reset --hard`, `DROP TABLE`, `TRUNCATE`
|
|
35
|
+
|
|
36
|
+
Exit code semantics:
|
|
37
|
+
- `0` — allow
|
|
38
|
+
- `1` — warn (Claude reports the warning and asks for confirmation)
|
|
39
|
+
- `2` — block (Claude refuses to run the command)
|
|
40
|
+
|
|
41
|
+
## Setup
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cp hooks/pre-tool-use/block-dangerous.sh .claude/hooks/
|
|
45
|
+
chmod +x .claude/hooks/block-dangerous.sh
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then add the `settings.json` entry above to `.claude/settings.json` or `~/.claude/settings.json`.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# block-dangerous.sh — blocks destructive shell commands before they execute
|
|
3
|
+
#
|
|
4
|
+
# Install: add to settings.json hooks.PreToolUse
|
|
5
|
+
# {
|
|
6
|
+
# "matcher": "Bash",
|
|
7
|
+
# "hooks": [{
|
|
8
|
+
# "type": "command",
|
|
9
|
+
# "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-dangerous.sh",
|
|
10
|
+
# "timeout": 5
|
|
11
|
+
# }]
|
|
12
|
+
# }
|
|
13
|
+
#
|
|
14
|
+
# Exit codes: 0=allow, 1=warn, 2=block
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
INPUT=$(cat)
|
|
19
|
+
COMMAND=$(echo "$INPUT" | python3 -c "
|
|
20
|
+
import sys, json
|
|
21
|
+
try:
|
|
22
|
+
d = json.load(sys.stdin)
|
|
23
|
+
print(d.get('tool_input', {}).get('command', ''))
|
|
24
|
+
except:
|
|
25
|
+
print('')
|
|
26
|
+
" 2>/dev/null || echo "")
|
|
27
|
+
|
|
28
|
+
if [ -z "$COMMAND" ]; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Patterns that are always blocked
|
|
33
|
+
BLOCKED_PATTERNS=(
|
|
34
|
+
"rm -rf /"
|
|
35
|
+
"rm -rf ~"
|
|
36
|
+
"rm -rf \$HOME"
|
|
37
|
+
"> /dev/sda"
|
|
38
|
+
"mkfs"
|
|
39
|
+
"dd if=/dev/zero"
|
|
40
|
+
":(){ :|:& };:" # Fork bomb
|
|
41
|
+
"chmod -R 777 /"
|
|
42
|
+
"chown -R"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
for pattern in "${BLOCKED_PATTERNS[@]}"; do
|
|
46
|
+
if echo "$COMMAND" | grep -qF "$pattern" 2>/dev/null; then
|
|
47
|
+
echo "BLOCKED: '${pattern}' is not permitted." >&2
|
|
48
|
+
exit 2
|
|
49
|
+
fi
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
# Patterns that require warning (exit 1 — Claude sees warning, user decides)
|
|
53
|
+
WARN_PATTERNS=(
|
|
54
|
+
"rm -rf"
|
|
55
|
+
"| bash"
|
|
56
|
+
"| sh"
|
|
57
|
+
"| zsh"
|
|
58
|
+
"curl.*| "
|
|
59
|
+
"wget.*| "
|
|
60
|
+
"sudo "
|
|
61
|
+
"git push --force"
|
|
62
|
+
"git reset --hard"
|
|
63
|
+
"git clean -f"
|
|
64
|
+
"DROP TABLE"
|
|
65
|
+
"DROP DATABASE"
|
|
66
|
+
"TRUNCATE"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
for pattern in "${WARN_PATTERNS[@]}"; do
|
|
70
|
+
if echo "$COMMAND" | grep -qi "$pattern" 2>/dev/null; then
|
|
71
|
+
echo "WARNING: command matches potentially dangerous pattern '${pattern}'. Proceed only if intentional." >&2
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
exit 0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Hook: Git Push Confirmation
|
|
2
|
+
|
|
3
|
+
Intercepts `git push` calls and requires explicit confirmation before proceeding, preventing accidental pushes to remote branches.
|
|
4
|
+
|
|
5
|
+
## Event
|
|
6
|
+
`PreToolUse` — fires before every Bash tool call, checks for `git push` in the command
|
|
7
|
+
|
|
8
|
+
## settings.json entry
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"hooks": {
|
|
13
|
+
"PreToolUse": [
|
|
14
|
+
{
|
|
15
|
+
"matcher": "Bash",
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/git-push-confirm.sh",
|
|
20
|
+
"timeout": 10
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
30
|
+
|
|
31
|
+
When a `Bash` command contains `git push`, the hook:
|
|
32
|
+
1. Extracts the remote and branch from the command
|
|
33
|
+
2. Checks if the target is `main` or `master` — exits 2 (hard block) for force pushes to protected branches
|
|
34
|
+
3. Exits 1 (warning) for all other `git push` commands, giving Claude a message to surface to the user
|
|
35
|
+
|
|
36
|
+
Regular `git push` to feature branches: user sees a warning and can confirm.
|
|
37
|
+
Force push to main/master: always blocked.
|
|
38
|
+
|
|
39
|
+
## Setup
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cp hooks/pre-tool-use/git-push-confirm.sh .claude/hooks/
|
|
43
|
+
chmod +x .claude/hooks/git-push-confirm.sh
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Add the `settings.json` entry above. Can be combined with `block-dangerous.sh` — both run as separate entries in the `PreToolUse` array.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# git-push-confirm.sh — warns before any git push operation
|
|
3
|
+
#
|
|
4
|
+
# Install: add to settings.json hooks.PreToolUse
|
|
5
|
+
# {
|
|
6
|
+
# "matcher": "Bash",
|
|
7
|
+
# "hooks": [{
|
|
8
|
+
# "type": "command",
|
|
9
|
+
# "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/git-push-confirm.sh",
|
|
10
|
+
# "timeout": 5
|
|
11
|
+
# }]
|
|
12
|
+
# }
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
INPUT=$(cat)
|
|
17
|
+
COMMAND=$(echo "$INPUT" | python3 -c "
|
|
18
|
+
import sys, json
|
|
19
|
+
try:
|
|
20
|
+
d = json.load(sys.stdin)
|
|
21
|
+
print(d.get('tool_input', {}).get('command', ''))
|
|
22
|
+
except:
|
|
23
|
+
print('')
|
|
24
|
+
" 2>/dev/null || echo "")
|
|
25
|
+
|
|
26
|
+
if echo "$COMMAND" | grep -q "git push"; then
|
|
27
|
+
# Extract branch info if possible
|
|
28
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
29
|
+
REMOTE=$(echo "$COMMAND" | grep -oP '(?<=git push )\S+' 2>/dev/null || echo "origin")
|
|
30
|
+
|
|
31
|
+
echo "⚠️ About to push branch '${BRANCH}' to '${REMOTE}'." >&2
|
|
32
|
+
echo "Confirm this is intentional before proceeding." >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
exit 0
|