@arthai/agents 1.0.4 → 1.0.6
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/README.md +55 -3
- package/VERSION +1 -1
- package/agents/troubleshooter.md +132 -0
- package/bin/cli.js +366 -0
- package/bundles/canvas.json +1 -1
- package/bundles/compass.json +1 -1
- package/bundles/counsel.json +1 -0
- package/bundles/cruise.json +1 -1
- package/bundles/forge.json +12 -1
- package/bundles/prism.json +1 -0
- package/bundles/scalpel.json +5 -2
- package/bundles/sentinel.json +8 -2
- package/bundles/shield.json +1 -0
- package/bundles/spark.json +1 -0
- package/compiler.sh +14 -0
- package/dist/plugins/canvas/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/canvas/VERSION +1 -0
- package/dist/plugins/canvas/commands/planning.md +100 -11
- package/dist/plugins/canvas/hooks/hooks.json +16 -0
- package/dist/plugins/canvas/hooks/project-setup.sh +109 -0
- package/dist/plugins/canvas/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/canvas/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/compass/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/compass/VERSION +1 -0
- package/dist/plugins/compass/commands/planning.md +100 -11
- package/dist/plugins/compass/hooks/hooks.json +16 -0
- package/dist/plugins/compass/hooks/project-setup.sh +109 -0
- package/dist/plugins/compass/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/compass/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/counsel/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/counsel/VERSION +1 -0
- package/dist/plugins/counsel/hooks/hooks.json +10 -0
- package/dist/plugins/counsel/hooks/project-setup.sh +109 -0
- package/dist/plugins/counsel/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/counsel/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/cruise/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/cruise/VERSION +1 -0
- package/dist/plugins/cruise/hooks/hooks.json +16 -0
- package/dist/plugins/cruise/hooks/project-setup.sh +109 -0
- package/dist/plugins/cruise/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/cruise/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/forge/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/forge/VERSION +1 -0
- package/dist/plugins/forge/agents/troubleshooter.md +132 -0
- package/dist/plugins/forge/commands/implement.md +99 -1
- package/dist/plugins/forge/commands/planning.md +100 -11
- package/dist/plugins/forge/hooks/escalation-guard.sh +177 -0
- package/dist/plugins/forge/hooks/hooks.json +22 -0
- package/dist/plugins/forge/hooks/project-setup.sh +109 -0
- package/dist/plugins/forge/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/forge/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/prime/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/prime/VERSION +1 -0
- package/dist/plugins/prime/agents/troubleshooter.md +132 -0
- package/dist/plugins/prime/commands/calibrate.md +20 -0
- package/dist/plugins/prime/commands/ci-fix.md +36 -0
- package/dist/plugins/prime/commands/fix.md +23 -0
- package/dist/plugins/prime/commands/implement.md +99 -1
- package/dist/plugins/prime/commands/planning.md +100 -11
- package/dist/plugins/prime/commands/qa-incident.md +54 -0
- package/dist/plugins/prime/commands/restart.md +186 -30
- package/dist/plugins/prime/hooks/escalation-guard.sh +177 -0
- package/dist/plugins/prime/hooks/hooks.json +60 -0
- package/dist/plugins/prime/hooks/post-config-change-restart-reminder.sh +86 -0
- package/dist/plugins/prime/hooks/post-server-crash-watch.sh +120 -0
- package/dist/plugins/prime/hooks/pre-server-port-guard.sh +110 -0
- package/dist/plugins/prime/hooks/project-setup.sh +109 -0
- package/dist/plugins/prime/hooks/sync-agents.sh +99 -12
- package/dist/plugins/prime/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/prime/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/prism/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/prism/VERSION +1 -0
- package/dist/plugins/prism/commands/qa-incident.md +54 -0
- package/dist/plugins/prism/hooks/hooks.json +12 -0
- package/dist/plugins/prism/hooks/project-setup.sh +109 -0
- package/dist/plugins/prism/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/prism/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/scalpel/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/scalpel/VERSION +1 -0
- package/dist/plugins/scalpel/agents/troubleshooter.md +132 -0
- package/dist/plugins/scalpel/commands/ci-fix.md +36 -0
- package/dist/plugins/scalpel/commands/fix.md +23 -0
- package/dist/plugins/scalpel/hooks/escalation-guard.sh +177 -0
- package/dist/plugins/scalpel/hooks/hooks.json +24 -0
- package/dist/plugins/scalpel/hooks/project-setup.sh +109 -0
- package/dist/plugins/scalpel/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/scalpel/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/sentinel/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/sentinel/VERSION +1 -0
- package/dist/plugins/sentinel/agents/troubleshooter.md +132 -0
- package/dist/plugins/sentinel/commands/restart.md +186 -30
- package/dist/plugins/sentinel/hooks/escalation-guard.sh +177 -0
- package/dist/plugins/sentinel/hooks/hooks.json +64 -0
- package/dist/plugins/sentinel/hooks/post-config-change-restart-reminder.sh +86 -0
- package/dist/plugins/sentinel/hooks/post-server-crash-watch.sh +120 -0
- package/dist/plugins/sentinel/hooks/pre-server-port-guard.sh +110 -0
- package/dist/plugins/sentinel/hooks/project-setup.sh +109 -0
- package/dist/plugins/sentinel/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/sentinel/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/shield/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/shield/VERSION +1 -0
- package/dist/plugins/shield/hooks/hooks.json +22 -12
- package/dist/plugins/shield/hooks/project-setup.sh +109 -0
- package/dist/plugins/shield/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/shield/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/spark/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/spark/VERSION +1 -0
- package/dist/plugins/spark/commands/calibrate.md +20 -0
- package/dist/plugins/spark/hooks/hooks.json +10 -0
- package/dist/plugins/spark/hooks/project-setup.sh +109 -0
- package/dist/plugins/spark/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/spark/templates/CLAUDE.md.template +111 -0
- package/hook-defs.json +31 -0
- package/hooks/escalation-guard.sh +177 -0
- package/hooks/post-config-change-restart-reminder.sh +86 -0
- package/hooks/post-server-crash-watch.sh +120 -0
- package/hooks/pre-server-port-guard.sh +110 -0
- package/hooks/project-setup.sh +109 -0
- package/hooks/sync-agents.sh +99 -12
- package/install.sh +2 -2
- package/package.json +1 -1
- package/portable.manifest +7 -1
- package/skills/calibrate/SKILL.md +20 -0
- package/skills/ci-fix/SKILL.md +36 -0
- package/skills/fix/SKILL.md +23 -0
- package/skills/implement/SKILL.md +99 -1
- package/skills/license/SKILL.md +159 -0
- package/skills/planning/SKILL.md +100 -11
- package/skills/publish/SKILL.md +3 -0
- package/skills/qa-incident/SKILL.md +54 -0
- package/skills/restart/SKILL.md +187 -31
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse hook (Bash): Circuit breaker for consecutive failures.
|
|
3
|
+
# Tracks failed commands by error signature. After 3 failures with the same
|
|
4
|
+
# signature, forces KB consultation and outputs a structured stuck report.
|
|
5
|
+
#
|
|
6
|
+
# Uses .claude/.escalation-state.json to persist state across tool calls.
|
|
7
|
+
# stdout is injected as context.
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
12
|
+
STATE_FILE="$PROJECT_DIR/.claude/.escalation-state.json"
|
|
13
|
+
CIRCUIT_BREAKER_THRESHOLD=3
|
|
14
|
+
|
|
15
|
+
# Extract the command and exit code
|
|
16
|
+
COMMAND="${CLAUDE_TOOL_INPUT_COMMAND:-}"
|
|
17
|
+
if [ -z "$COMMAND" ] && [ -n "${CLAUDE_TOOL_INPUT:-}" ]; then
|
|
18
|
+
COMMAND=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('command',''))" 2>/dev/null) || true
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
EXIT_CODE="${CLAUDE_TOOL_RESULT_EXIT_CODE:-0}"
|
|
22
|
+
|
|
23
|
+
[ -z "$COMMAND" ] && exit 0
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Skip tracking for non-failing commands and read-only commands
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
# Success — reset state if we had failures for this signature
|
|
30
|
+
if [ "$EXIT_CODE" = "0" ]; then
|
|
31
|
+
if [ -f "$STATE_FILE" ]; then
|
|
32
|
+
# Clear state on success — the issue is resolved
|
|
33
|
+
python3 -c "
|
|
34
|
+
import json, sys
|
|
35
|
+
try:
|
|
36
|
+
with open('$STATE_FILE', 'r') as f:
|
|
37
|
+
state = json.load(f)
|
|
38
|
+
# Reset consecutive failures
|
|
39
|
+
state['consecutive_failures'] = 0
|
|
40
|
+
state['last_error_signature'] = ''
|
|
41
|
+
state['attempts'] = []
|
|
42
|
+
with open('$STATE_FILE', 'w') as f:
|
|
43
|
+
json.dump(state, f, indent=2)
|
|
44
|
+
except:
|
|
45
|
+
pass
|
|
46
|
+
" 2>/dev/null || true
|
|
47
|
+
fi
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Skip tracking for read-only commands (ls, cat, grep, git status, etc.)
|
|
52
|
+
if echo "$COMMAND" | grep -qE '^\s*(ls|cat|head|tail|grep|rg|git\s+(status|log|diff|show|branch)|echo|pwd|which|type|file|wc)\b'; then
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# Compute error signature from command + exit code
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
# Get the last few lines of error output (from CLAUDE_TOOL_RESULT_STDERR or infer)
|
|
61
|
+
ERROR_OUTPUT="${CLAUDE_TOOL_RESULT_STDERR:-}"
|
|
62
|
+
if [ -z "$ERROR_OUTPUT" ] && [ -n "${CLAUDE_TOOL_RESULT_STDOUT:-}" ]; then
|
|
63
|
+
# Sometimes errors go to stdout (e.g., npm, python)
|
|
64
|
+
ERROR_OUTPUT=$(echo "${CLAUDE_TOOL_RESULT_STDOUT:-}" | tail -5)
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Compute signature: hash of (command_base + error_pattern)
|
|
68
|
+
# Strip variable parts (paths, timestamps, PIDs) for stable signatures
|
|
69
|
+
COMMAND_BASE=$(echo "$COMMAND" | awk '{print $1, $2}')
|
|
70
|
+
ERROR_PATTERN=$(echo "$ERROR_OUTPUT" | sed 's/[0-9]\{4,\}//g; s|/[^ ]*||g' | head -3)
|
|
71
|
+
SIGNATURE=$(echo "${COMMAND_BASE}:${ERROR_PATTERN}" | shasum -a 256 | cut -c1-16)
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Update state and check circuit breaker
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
RESULT=$(python3 -c "
|
|
78
|
+
import json, sys, time, os
|
|
79
|
+
|
|
80
|
+
state_file = '$STATE_FILE'
|
|
81
|
+
signature = '$SIGNATURE'
|
|
82
|
+
command = '''$COMMAND'''[:200]
|
|
83
|
+
error = '''$ERROR_OUTPUT'''[:500]
|
|
84
|
+
threshold = $CIRCUIT_BREAKER_THRESHOLD
|
|
85
|
+
|
|
86
|
+
# Load or create state
|
|
87
|
+
state = {'consecutive_failures': 0, 'last_error_signature': '', 'attempts': [], 'total_circuits_tripped': 0}
|
|
88
|
+
try:
|
|
89
|
+
if os.path.exists(state_file):
|
|
90
|
+
with open(state_file, 'r') as f:
|
|
91
|
+
state = json.load(f)
|
|
92
|
+
except:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
# Check if same signature as last failure
|
|
96
|
+
if state.get('last_error_signature') == signature:
|
|
97
|
+
state['consecutive_failures'] = state.get('consecutive_failures', 0) + 1
|
|
98
|
+
else:
|
|
99
|
+
# New error signature — reset counter
|
|
100
|
+
state['consecutive_failures'] = 1
|
|
101
|
+
state['attempts'] = []
|
|
102
|
+
|
|
103
|
+
state['last_error_signature'] = signature
|
|
104
|
+
state['attempts'] = (state.get('attempts', []) + [{'command': command, 'error': error[:200], 'time': time.time()}])[-5:]
|
|
105
|
+
|
|
106
|
+
# Check threshold
|
|
107
|
+
tripped = state['consecutive_failures'] >= threshold
|
|
108
|
+
|
|
109
|
+
if tripped:
|
|
110
|
+
state['total_circuits_tripped'] = state.get('total_circuits_tripped', 0) + 1
|
|
111
|
+
|
|
112
|
+
# Save state
|
|
113
|
+
os.makedirs(os.path.dirname(state_file), exist_ok=True)
|
|
114
|
+
with open(state_file, 'w') as f:
|
|
115
|
+
json.dump(state, f, indent=2)
|
|
116
|
+
|
|
117
|
+
# Output: tripped|count|attempts_summary
|
|
118
|
+
attempts_summary = ' / '.join([a.get('command', '')[:60] for a in state.get('attempts', [])])
|
|
119
|
+
print(f\"{'TRIPPED' if tripped else 'OK'}|{state['consecutive_failures']}|{attempts_summary}\")
|
|
120
|
+
" 2>/dev/null) || exit 0
|
|
121
|
+
|
|
122
|
+
STATUS=$(echo "$RESULT" | cut -d'|' -f1)
|
|
123
|
+
COUNT=$(echo "$RESULT" | cut -d'|' -f2)
|
|
124
|
+
ATTEMPTS=$(echo "$RESULT" | cut -d'|' -f3-)
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# If not tripped, show warning at count 2
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
if [ "$STATUS" = "OK" ] && [ "$COUNT" = "2" ]; then
|
|
131
|
+
echo "ESCALATION WARNING: 2 consecutive failures with same error pattern."
|
|
132
|
+
echo "One more failure triggers the circuit breaker."
|
|
133
|
+
echo "Before retrying: check .claude/knowledge/ for known solutions."
|
|
134
|
+
exit 0
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
# Circuit breaker tripped — inject structured stuck report
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
if [ "$STATUS" = "TRIPPED" ]; then
|
|
142
|
+
echo "CIRCUIT BREAKER TRIPPED — $COUNT consecutive failures with same error signature."
|
|
143
|
+
echo ""
|
|
144
|
+
echo "STOP. Do not attempt another fix without completing these steps:"
|
|
145
|
+
echo ""
|
|
146
|
+
echo "1. CONSULT KNOWLEDGE BASE:"
|
|
147
|
+
echo " - Read .claude/knowledge/shared/conventions.md"
|
|
148
|
+
echo " - Read .claude/knowledge/qa-knowledge/ (search for error keywords)"
|
|
149
|
+
echo " - Run: git log --all --grep='fix:' --oneline -10"
|
|
150
|
+
echo ""
|
|
151
|
+
echo "2. GATHER EVIDENCE (if not already done):"
|
|
152
|
+
echo " - Read full error output (not just the last line)"
|
|
153
|
+
echo " - Check environment: env vars, ports (lsof), processes (ps), disk (df)"
|
|
154
|
+
echo " - Check dependencies: node_modules, venv, Docker containers"
|
|
155
|
+
echo ""
|
|
156
|
+
echo "3. IF STILL STUCK — use this template to ask for help:"
|
|
157
|
+
echo " ┌─────────────────────────────────────────────────────────┐"
|
|
158
|
+
echo " │ STUCK REPORT │"
|
|
159
|
+
echo " │ │"
|
|
160
|
+
echo " │ Error: [exact error message] │"
|
|
161
|
+
echo " │ Context: [what I was doing] │"
|
|
162
|
+
echo " │ Attempts: │"
|
|
163
|
+
echo " │ 1. [what I tried] -> [result] │"
|
|
164
|
+
echo " │ 2. [what I tried] -> [result] │"
|
|
165
|
+
echo " │ 3. [what I tried] -> [result] │"
|
|
166
|
+
echo " │ Evidence: [logs, state, KB search results] │"
|
|
167
|
+
echo " │ What I need: [access/data/decision] │"
|
|
168
|
+
echo " │ My recommendation: [option A because X] │"
|
|
169
|
+
echo " └─────────────────────────────────────────────────────────┘"
|
|
170
|
+
echo ""
|
|
171
|
+
echo "4. IF ON A TEAM — escalate to troubleshooter agent or ask a teammate."
|
|
172
|
+
echo " If solo — present the stuck report to the user with options."
|
|
173
|
+
echo ""
|
|
174
|
+
echo "Previous attempts: $ATTEMPTS"
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
exit 0
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"hooks": {
|
|
3
3
|
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/project-setup.sh",
|
|
10
|
+
"timeout": 10
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
},
|
|
4
14
|
{
|
|
5
15
|
"matcher": "",
|
|
6
16
|
"hooks": [
|
|
@@ -102,6 +112,46 @@
|
|
|
102
112
|
"timeout": 5
|
|
103
113
|
}
|
|
104
114
|
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"matcher": "Bash",
|
|
118
|
+
"hooks": [
|
|
119
|
+
{
|
|
120
|
+
"type": "command",
|
|
121
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/escalation-guard.sh",
|
|
122
|
+
"timeout": 5
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"matcher": "Edit",
|
|
128
|
+
"hooks": [
|
|
129
|
+
{
|
|
130
|
+
"type": "command",
|
|
131
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/post-config-change-restart-reminder.sh",
|
|
132
|
+
"timeout": 5
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"matcher": "Write",
|
|
138
|
+
"hooks": [
|
|
139
|
+
{
|
|
140
|
+
"type": "command",
|
|
141
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/post-config-change-restart-reminder.sh",
|
|
142
|
+
"timeout": 5
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"matcher": "Bash",
|
|
148
|
+
"hooks": [
|
|
149
|
+
{
|
|
150
|
+
"type": "command",
|
|
151
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/post-server-crash-watch.sh",
|
|
152
|
+
"timeout": 15
|
|
153
|
+
}
|
|
154
|
+
]
|
|
105
155
|
}
|
|
106
156
|
],
|
|
107
157
|
"SessionEnd": [
|
|
@@ -178,6 +228,16 @@
|
|
|
178
228
|
"timeout": 5
|
|
179
229
|
}
|
|
180
230
|
]
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"matcher": "Bash",
|
|
234
|
+
"hooks": [
|
|
235
|
+
{
|
|
236
|
+
"type": "command",
|
|
237
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/pre-server-port-guard.sh",
|
|
238
|
+
"timeout": 5
|
|
239
|
+
}
|
|
240
|
+
]
|
|
181
241
|
}
|
|
182
242
|
]
|
|
183
243
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse hook (Edit/Write): Remind to restart after config file changes.
|
|
3
|
+
# Detects modifications to docker-compose, .env, Dockerfile, nginx, and port
|
|
4
|
+
# configs, then reminds the user that a /restart may be needed.
|
|
5
|
+
#
|
|
6
|
+
# stdout is injected as context.
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
# Extract the file path from tool input
|
|
11
|
+
FILE_PATH="${CLAUDE_TOOL_INPUT_FILE_PATH:-}"
|
|
12
|
+
if [ -z "$FILE_PATH" ] && [ -n "${CLAUDE_TOOL_INPUT:-}" ]; then
|
|
13
|
+
FILE_PATH=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('file_path',''))" 2>/dev/null) || true
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Check if the modified file is a config that affects running services
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
BASENAME=$(basename "$FILE_PATH")
|
|
23
|
+
NEEDS_RESTART=false
|
|
24
|
+
REASON=""
|
|
25
|
+
|
|
26
|
+
case "$BASENAME" in
|
|
27
|
+
docker-compose.yml|docker-compose.yaml|docker-compose.*.yml|docker-compose.*.yaml|compose.yml|compose.yaml)
|
|
28
|
+
NEEDS_RESTART=true
|
|
29
|
+
REASON="Docker Compose config changed — containers may need rebuilding"
|
|
30
|
+
;;
|
|
31
|
+
.env|.env.local|.env.development|.env.production)
|
|
32
|
+
NEEDS_RESTART=true
|
|
33
|
+
REASON="Environment variables changed — servers need restart to pick up new values"
|
|
34
|
+
;;
|
|
35
|
+
Dockerfile|Dockerfile.*)
|
|
36
|
+
NEEDS_RESTART=true
|
|
37
|
+
REASON="Dockerfile changed — container needs rebuilding"
|
|
38
|
+
;;
|
|
39
|
+
nginx.conf|nginx.*.conf)
|
|
40
|
+
NEEDS_RESTART=true
|
|
41
|
+
REASON="Nginx config changed — needs reload"
|
|
42
|
+
;;
|
|
43
|
+
next.config.js|next.config.mjs|next.config.ts)
|
|
44
|
+
NEEDS_RESTART=true
|
|
45
|
+
REASON="Next.js config changed — dev server needs restart"
|
|
46
|
+
;;
|
|
47
|
+
vite.config.js|vite.config.ts|vite.config.mjs)
|
|
48
|
+
NEEDS_RESTART=true
|
|
49
|
+
REASON="Vite config changed — dev server needs restart"
|
|
50
|
+
;;
|
|
51
|
+
tsconfig.json|tsconfig.*.json)
|
|
52
|
+
NEEDS_RESTART=true
|
|
53
|
+
REASON="TypeScript config changed — dev server may need restart"
|
|
54
|
+
;;
|
|
55
|
+
webpack.config.js|webpack.config.ts)
|
|
56
|
+
NEEDS_RESTART=true
|
|
57
|
+
REASON="Webpack config changed — dev server needs restart"
|
|
58
|
+
;;
|
|
59
|
+
package.json)
|
|
60
|
+
NEEDS_RESTART=true
|
|
61
|
+
REASON="package.json changed — may need npm install + restart"
|
|
62
|
+
;;
|
|
63
|
+
pyproject.toml|setup.py|setup.cfg|requirements.txt|requirements.*.txt)
|
|
64
|
+
NEEDS_RESTART=true
|
|
65
|
+
REASON="Python dependencies changed — may need pip install + restart"
|
|
66
|
+
;;
|
|
67
|
+
Procfile|Procfile.*)
|
|
68
|
+
NEEDS_RESTART=true
|
|
69
|
+
REASON="Procfile changed — process definitions updated"
|
|
70
|
+
;;
|
|
71
|
+
esac
|
|
72
|
+
|
|
73
|
+
# Also check for port-related changes in any file
|
|
74
|
+
if [ "$NEEDS_RESTART" = "false" ]; then
|
|
75
|
+
# Check if the file path contains common config directories
|
|
76
|
+
if echo "$FILE_PATH" | grep -qE '(config/|conf/|\.config)'; then
|
|
77
|
+
NEEDS_RESTART=true
|
|
78
|
+
REASON="Config file modified — services may need restart"
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
$NEEDS_RESTART || exit 0
|
|
83
|
+
|
|
84
|
+
echo "CONFIG CHANGED — $REASON. Run /restart to apply."
|
|
85
|
+
|
|
86
|
+
exit 0
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse hook (Bash): Detect server start commands and check for crash loops.
|
|
3
|
+
# After a server start is detected, waits briefly then verifies the process is
|
|
4
|
+
# still running and the port is responding. Catches services that start then die.
|
|
5
|
+
#
|
|
6
|
+
# stdout is injected as context.
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
COMMAND="${CLAUDE_TOOL_INPUT_COMMAND:-}"
|
|
11
|
+
if [ -z "$COMMAND" ] && [ -n "${CLAUDE_TOOL_INPUT:-}" ]; then
|
|
12
|
+
COMMAND=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('command',''))" 2>/dev/null) || true
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
[ -z "$COMMAND" ] && exit 0
|
|
16
|
+
|
|
17
|
+
# Only check commands that ran successfully
|
|
18
|
+
EXIT_CODE="${CLAUDE_TOOL_RESULT_EXIT_CODE:-0}"
|
|
19
|
+
[ "$EXIT_CODE" != "0" ] && exit 0
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Detect server start commands and extract ports
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
PORT=""
|
|
26
|
+
SERVICE=""
|
|
27
|
+
|
|
28
|
+
# npm/pnpm/yarn dev/start
|
|
29
|
+
if echo "$COMMAND" | grep -qE '\b(npm|pnpm|yarn)\s+run\s+(dev|start|serve)\b'; then
|
|
30
|
+
PORT=$(echo "$COMMAND" | grep -oE '\-\-port\s+([0-9]+)' | awk '{print $2}')
|
|
31
|
+
PORT="${PORT:-3000}"
|
|
32
|
+
SERVICE="Node.js dev server"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# next dev
|
|
36
|
+
if echo "$COMMAND" | grep -qE '\bnext\s+dev\b'; then
|
|
37
|
+
PORT=$(echo "$COMMAND" | grep -oE '\-\-port\s+([0-9]+)' | awk '{print $2}')
|
|
38
|
+
PORT="${PORT:-3000}"
|
|
39
|
+
SERVICE="Next.js dev server"
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# uvicorn
|
|
43
|
+
if echo "$COMMAND" | grep -qE '\buvicorn\b'; then
|
|
44
|
+
PORT=$(echo "$COMMAND" | grep -oE '\-\-port\s+([0-9]+)' | awk '{print $2}')
|
|
45
|
+
PORT="${PORT:-8000}"
|
|
46
|
+
SERVICE="Uvicorn"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# gunicorn
|
|
50
|
+
if echo "$COMMAND" | grep -qE '\bgunicorn\b'; then
|
|
51
|
+
PORT=$(echo "$COMMAND" | grep -oE '\-b\s+[^:]+:([0-9]+)' | grep -oE '[0-9]+$')
|
|
52
|
+
if [ -z "$PORT" ]; then
|
|
53
|
+
PORT=$(echo "$COMMAND" | grep -oE '\-\-bind\s+[^:]+:([0-9]+)' | grep -oE '[0-9]+$')
|
|
54
|
+
fi
|
|
55
|
+
PORT="${PORT:-8000}"
|
|
56
|
+
SERVICE="Gunicorn"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# flask run
|
|
60
|
+
if echo "$COMMAND" | grep -qE '\bflask\s+run\b'; then
|
|
61
|
+
PORT=$(echo "$COMMAND" | grep -oE '\-\-port\s+([0-9]+)' | awk '{print $2}')
|
|
62
|
+
PORT="${PORT:-5000}"
|
|
63
|
+
SERVICE="Flask"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# docker compose up
|
|
67
|
+
if echo "$COMMAND" | grep -qE '\bdocker\s+compose\s+up\b'; then
|
|
68
|
+
SERVICE="Docker Compose services"
|
|
69
|
+
# For docker compose, check container status instead of port
|
|
70
|
+
sleep 8
|
|
71
|
+
down_containers=$(docker compose ps --format '{{.Name}} {{.Status}}' 2>/dev/null | grep -viE 'Up|running' | head -5) || true
|
|
72
|
+
if [ -n "$down_containers" ]; then
|
|
73
|
+
echo "CRASH DETECTED — Docker containers not healthy after 8s:"
|
|
74
|
+
echo "$down_containers"
|
|
75
|
+
echo "Check logs: docker compose logs --tail=30"
|
|
76
|
+
fi
|
|
77
|
+
exit 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# rails server
|
|
81
|
+
if echo "$COMMAND" | grep -qE '\brails\s+s(erver)?\b'; then
|
|
82
|
+
PORT=$(echo "$COMMAND" | grep -oE '\-p\s+([0-9]+)' | awk '{print $2}')
|
|
83
|
+
PORT="${PORT:-3000}"
|
|
84
|
+
SERVICE="Rails server"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# No server start detected
|
|
88
|
+
[ -z "$SERVICE" ] && exit 0
|
|
89
|
+
[ -z "$PORT" ] && exit 0
|
|
90
|
+
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
# Wait and check for crash loop
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
sleep 8
|
|
96
|
+
|
|
97
|
+
# Check if process is still listening on the port
|
|
98
|
+
pid=$(lsof -ti:"$PORT" 2>/dev/null | head -1) || true
|
|
99
|
+
|
|
100
|
+
if [ -z "$pid" ]; then
|
|
101
|
+
echo "CRASH DETECTED — $SERVICE (port $PORT) is no longer running after 8s."
|
|
102
|
+
echo "The process started but has since exited. Check the terminal output for errors."
|
|
103
|
+
echo "Common causes: missing env vars, port conflict, dependency errors, syntax errors."
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Process is alive — try a quick HTTP health check
|
|
108
|
+
http_status=$(timeout 5 curl -sf -o /dev/null -w "%{http_code}" "http://localhost:$PORT/" 2>/dev/null) || http_status="no_response"
|
|
109
|
+
|
|
110
|
+
if [ "$http_status" = "no_response" ]; then
|
|
111
|
+
# Process exists but not responding to HTTP — might be starting up or non-HTTP
|
|
112
|
+
echo "$SERVICE (port $PORT): process alive (PID $pid) but not responding to HTTP yet. May still be starting."
|
|
113
|
+
elif echo "$http_status" | grep -qE '^[2345]'; then
|
|
114
|
+
# Any HTTP response means the server is up
|
|
115
|
+
: # Silent success — don't spam on healthy starts
|
|
116
|
+
else
|
|
117
|
+
echo "$SERVICE (port $PORT): process alive but returned HTTP $http_status."
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
exit 0
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PreToolUse hook (Bash): Check port availability before server start commands.
|
|
3
|
+
# Detects server start commands and warns if the target port is already occupied
|
|
4
|
+
# by an unexpected process.
|
|
5
|
+
#
|
|
6
|
+
# Exit 0 = allow the command
|
|
7
|
+
# Exit 2 = block the command (stdout shown as reason)
|
|
8
|
+
# stdout = injected as context (warnings)
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
COMMAND="${CLAUDE_TOOL_INPUT_COMMAND:-}"
|
|
13
|
+
if [ -z "$COMMAND" ] && [ -n "${CLAUDE_TOOL_INPUT:-}" ]; then
|
|
14
|
+
COMMAND=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('command',''))" 2>/dev/null) || true
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
[ -z "$COMMAND" ] && exit 0
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# Detect server start commands and extract ports
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
declare -a PORTS=()
|
|
24
|
+
|
|
25
|
+
# npm/pnpm/yarn dev/start — default port 3000 unless --port specified
|
|
26
|
+
if echo "$COMMAND" | grep -qE '\b(npm|pnpm|yarn)\s+run\s+(dev|start|serve)\b'; then
|
|
27
|
+
port=$(echo "$COMMAND" | grep -oE '\-\-port\s+([0-9]+)' | awk '{print $2}')
|
|
28
|
+
if [ -z "$port" ]; then
|
|
29
|
+
port=$(echo "$COMMAND" | grep -oE '\bp(ort)?\s*=?\s*([0-9]{4,5})' | grep -oE '[0-9]+')
|
|
30
|
+
fi
|
|
31
|
+
PORTS+=("${port:-3000}")
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# next dev — default port 3000
|
|
35
|
+
if echo "$COMMAND" | grep -qE '\bnext\s+dev\b'; then
|
|
36
|
+
port=$(echo "$COMMAND" | grep -oE '\-\-port\s+([0-9]+)' | awk '{print $2}')
|
|
37
|
+
PORTS+=("${port:-3000}")
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# vite — default port 5173
|
|
41
|
+
if echo "$COMMAND" | grep -qE '\bvite\b' && ! echo "$COMMAND" | grep -qE '\bvite\s+build\b'; then
|
|
42
|
+
port=$(echo "$COMMAND" | grep -oE '\-\-port\s+([0-9]+)' | awk '{print $2}')
|
|
43
|
+
PORTS+=("${port:-5173}")
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# uvicorn — default port 8000
|
|
47
|
+
if echo "$COMMAND" | grep -qE '\buvicorn\b'; then
|
|
48
|
+
port=$(echo "$COMMAND" | grep -oE '\-\-port\s+([0-9]+)' | awk '{print $2}')
|
|
49
|
+
PORTS+=("${port:-8000}")
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# gunicorn — default port 8000
|
|
53
|
+
if echo "$COMMAND" | grep -qE '\bgunicorn\b'; then
|
|
54
|
+
port=$(echo "$COMMAND" | grep -oE '\-b\s+[^:]+:([0-9]+)' | grep -oE '[0-9]+$')
|
|
55
|
+
if [ -z "$port" ]; then
|
|
56
|
+
port=$(echo "$COMMAND" | grep -oE '\-\-bind\s+[^:]+:([0-9]+)' | grep -oE '[0-9]+$')
|
|
57
|
+
fi
|
|
58
|
+
PORTS+=("${port:-8000}")
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# flask run — default port 5000
|
|
62
|
+
if echo "$COMMAND" | grep -qE '\bflask\s+run\b'; then
|
|
63
|
+
port=$(echo "$COMMAND" | grep -oE '\-\-port\s+([0-9]+)' | awk '{print $2}')
|
|
64
|
+
PORTS+=("${port:-5000}")
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# docker compose up — check compose file for ports
|
|
68
|
+
if echo "$COMMAND" | grep -qE '\bdocker\s+compose\s+up\b'; then
|
|
69
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
70
|
+
if [ -f "$PROJECT_DIR/docker-compose.yml" ]; then
|
|
71
|
+
# Extract host ports from ports: mappings (e.g., "3000:3000", "5432:5432")
|
|
72
|
+
compose_ports=$(grep -oE '^\s*-\s*"?([0-9]+):' "$PROJECT_DIR/docker-compose.yml" 2>/dev/null | grep -oE '[0-9]+' | head -5) || true
|
|
73
|
+
for p in $compose_ports; do
|
|
74
|
+
PORTS+=("$p")
|
|
75
|
+
done
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# rails server — default port 3000
|
|
80
|
+
if echo "$COMMAND" | grep -qE '\brails\s+s(erver)?\b'; then
|
|
81
|
+
port=$(echo "$COMMAND" | grep -oE '\-p\s+([0-9]+)' | awk '{print $2}')
|
|
82
|
+
PORTS+=("${port:-3000}")
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# go run with -addr or explicit port
|
|
86
|
+
if echo "$COMMAND" | grep -qE '\bgo\s+run\b'; then
|
|
87
|
+
port=$(echo "$COMMAND" | grep -oE ':([0-9]{4,5})' | head -1 | tr -d ':')
|
|
88
|
+
[ -n "$port" ] && PORTS+=("$port")
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# No server command detected
|
|
92
|
+
[ ${#PORTS[@]} -eq 0 ] && exit 0
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# Check each port for conflicts
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
for port in "${PORTS[@]}"; do
|
|
99
|
+
[ -z "$port" ] && continue
|
|
100
|
+
|
|
101
|
+
pid=$(lsof -ti:"$port" 2>/dev/null | head -1) || true
|
|
102
|
+
if [ -n "$pid" ]; then
|
|
103
|
+
proc_name=$(ps -p "$pid" -o comm= 2>/dev/null) || proc_name="unknown"
|
|
104
|
+
echo "PORT CONFLICT — port $port is already in use by $proc_name (PID $pid)."
|
|
105
|
+
echo "Kill it first: lsof -ti:$port | xargs kill -9"
|
|
106
|
+
echo "Or use a different port."
|
|
107
|
+
fi
|
|
108
|
+
done
|
|
109
|
+
|
|
110
|
+
exit 0
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# hooks/project-setup.sh — SessionStart hook: first-run project setup for marketplace installs.
|
|
3
|
+
#
|
|
4
|
+
# Runs on every SessionStart. Checks for marker file .claude/.toolkit-setup-done.
|
|
5
|
+
# If missing (or version is stale): creates CLAUDE.md from template, injects managed block,
|
|
6
|
+
# updates .gitignore with toolkit markers.
|
|
7
|
+
#
|
|
8
|
+
# Templates are read from ${CLAUDE_PLUGIN_ROOT}/templates/ (bundled with the plugin).
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
13
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-}"
|
|
14
|
+
MARKER_FILE="$PROJECT_DIR/.claude/.toolkit-setup-done"
|
|
15
|
+
|
|
16
|
+
# Exit silently if CLAUDE_PLUGIN_ROOT is not set (not running from a plugin context)
|
|
17
|
+
if [ -z "$PLUGIN_ROOT" ]; then
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
TEMPLATES_DIR="$PLUGIN_ROOT/templates"
|
|
22
|
+
TEMPLATE_CLAUDE="$TEMPLATES_DIR/CLAUDE.md.template"
|
|
23
|
+
TEMPLATE_BLOCK="$TEMPLATES_DIR/CLAUDE.md.managed-block"
|
|
24
|
+
|
|
25
|
+
# Exit silently if templates are not bundled
|
|
26
|
+
if [ ! -f "$TEMPLATE_CLAUDE" ] || [ ! -f "$TEMPLATE_BLOCK" ]; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Read toolkit version from VERSION file (bundled alongside plugin)
|
|
31
|
+
TOOLKIT_VERSION="0.0.0"
|
|
32
|
+
if [ -f "$PLUGIN_ROOT/VERSION" ]; then
|
|
33
|
+
TOOLKIT_VERSION=$(cat "$PLUGIN_ROOT/VERSION" 2>/dev/null | tr -d '[:space:]' || echo "0.0.0")
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
MANAGED_START="<!-- >>> claude-agents toolkit (DO NOT EDIT THIS BLOCK) >>> -->"
|
|
37
|
+
MANAGED_END="<!-- <<< claude-agents toolkit <<< -->"
|
|
38
|
+
GITIGNORE_START="# >>> claude-agents managed (DO NOT EDIT THIS BLOCK) >>>"
|
|
39
|
+
|
|
40
|
+
# Check marker file — skip if current version is already set up
|
|
41
|
+
if [ -f "$MARKER_FILE" ]; then
|
|
42
|
+
STORED_VERSION=$(cat "$MARKER_FILE" 2>/dev/null | tr -d '[:space:]' || echo "")
|
|
43
|
+
if [ "$STORED_VERSION" = "$TOOLKIT_VERSION" ]; then
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
CLAUDE_MD="$PROJECT_DIR/CLAUDE.md"
|
|
49
|
+
GITIGNORE="$PROJECT_DIR/.gitignore"
|
|
50
|
+
|
|
51
|
+
# 1. Create CLAUDE.md from template if it doesn't exist (never overwrite existing)
|
|
52
|
+
if [ ! -f "$CLAUDE_MD" ]; then
|
|
53
|
+
PROJECT_NAME=$(basename "$PROJECT_DIR")
|
|
54
|
+
sed "s/{{PROJECT_NAME}}/$PROJECT_NAME/g" "$TEMPLATE_CLAUDE" > "$CLAUDE_MD"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# 2. Inject managed block into CLAUDE.md if missing, or update if version is stale
|
|
58
|
+
if [ -f "$CLAUDE_MD" ]; then
|
|
59
|
+
BLOCK_CONTENT=$(cat "$TEMPLATE_BLOCK")
|
|
60
|
+
NEW_BLOCK=$(printf '%s\n<!-- version: %s -->\n%s\n%s' \
|
|
61
|
+
"$MANAGED_START" "$TOOLKIT_VERSION" "$BLOCK_CONTENT" "$MANAGED_END")
|
|
62
|
+
|
|
63
|
+
if ! grep -qF "$MANAGED_START" "$CLAUDE_MD"; then
|
|
64
|
+
# Block missing — append it
|
|
65
|
+
printf '\n\n%s\n' "$NEW_BLOCK" >> "$CLAUDE_MD"
|
|
66
|
+
else
|
|
67
|
+
# Block exists — update if version changed
|
|
68
|
+
EXISTING_VERSION=$(grep -o '<!-- version: [^>]* -->' "$CLAUDE_MD" 2>/dev/null | head -1 | sed 's/<!-- version: //;s/ -->//' | tr -d '[:space:]' || echo "")
|
|
69
|
+
if [ "$EXISTING_VERSION" != "$TOOLKIT_VERSION" ]; then
|
|
70
|
+
# Replace block contents between markers
|
|
71
|
+
tmp=$(mktemp)
|
|
72
|
+
in_block=false
|
|
73
|
+
wrote_block=false
|
|
74
|
+
while IFS= read -r line; do
|
|
75
|
+
if echo "$line" | grep -qF "$MANAGED_START"; then
|
|
76
|
+
in_block=true
|
|
77
|
+
printf '%s\n' "$NEW_BLOCK" >> "$tmp"
|
|
78
|
+
wrote_block=true
|
|
79
|
+
continue
|
|
80
|
+
fi
|
|
81
|
+
if echo "$line" | grep -qF "$MANAGED_END"; then
|
|
82
|
+
in_block=false
|
|
83
|
+
continue
|
|
84
|
+
fi
|
|
85
|
+
if ! $in_block; then
|
|
86
|
+
echo "$line" >> "$tmp"
|
|
87
|
+
fi
|
|
88
|
+
done < "$CLAUDE_MD"
|
|
89
|
+
mv "$tmp" "$CLAUDE_MD"
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# 3. Update .gitignore with toolkit marker block if missing
|
|
95
|
+
if [ ! -f "$GITIGNORE" ] || ! grep -qF "$GITIGNORE_START" "$GITIGNORE" 2>/dev/null; then
|
|
96
|
+
GITIGNORE_BLOCK=$(printf '%s\n.claude/.toolkit-last-seen-sha\n.claude/.toolkit-setup-done\n.claude/.claude-agents.conf\n%s' \
|
|
97
|
+
"$GITIGNORE_START" "# <<< claude-agents managed <<<")
|
|
98
|
+
if [ ! -f "$GITIGNORE" ]; then
|
|
99
|
+
printf '%s\n' "$GITIGNORE_BLOCK" > "$GITIGNORE"
|
|
100
|
+
else
|
|
101
|
+
printf '\n\n%s\n' "$GITIGNORE_BLOCK" >> "$GITIGNORE"
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# 4. Write marker file with current version
|
|
106
|
+
mkdir -p "$PROJECT_DIR/.claude"
|
|
107
|
+
printf '%s\n' "$TOOLKIT_VERSION" > "$MARKER_FILE"
|
|
108
|
+
|
|
109
|
+
exit 0
|