@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.
Files changed (131) hide show
  1. package/README.md +55 -3
  2. package/VERSION +1 -1
  3. package/agents/troubleshooter.md +132 -0
  4. package/bin/cli.js +366 -0
  5. package/bundles/canvas.json +1 -1
  6. package/bundles/compass.json +1 -1
  7. package/bundles/counsel.json +1 -0
  8. package/bundles/cruise.json +1 -1
  9. package/bundles/forge.json +12 -1
  10. package/bundles/prism.json +1 -0
  11. package/bundles/scalpel.json +5 -2
  12. package/bundles/sentinel.json +8 -2
  13. package/bundles/shield.json +1 -0
  14. package/bundles/spark.json +1 -0
  15. package/compiler.sh +14 -0
  16. package/dist/plugins/canvas/.claude-plugin/plugin.json +1 -1
  17. package/dist/plugins/canvas/VERSION +1 -0
  18. package/dist/plugins/canvas/commands/planning.md +100 -11
  19. package/dist/plugins/canvas/hooks/hooks.json +16 -0
  20. package/dist/plugins/canvas/hooks/project-setup.sh +109 -0
  21. package/dist/plugins/canvas/templates/CLAUDE.md.managed-block +123 -0
  22. package/dist/plugins/canvas/templates/CLAUDE.md.template +111 -0
  23. package/dist/plugins/compass/.claude-plugin/plugin.json +1 -1
  24. package/dist/plugins/compass/VERSION +1 -0
  25. package/dist/plugins/compass/commands/planning.md +100 -11
  26. package/dist/plugins/compass/hooks/hooks.json +16 -0
  27. package/dist/plugins/compass/hooks/project-setup.sh +109 -0
  28. package/dist/plugins/compass/templates/CLAUDE.md.managed-block +123 -0
  29. package/dist/plugins/compass/templates/CLAUDE.md.template +111 -0
  30. package/dist/plugins/counsel/.claude-plugin/plugin.json +1 -1
  31. package/dist/plugins/counsel/VERSION +1 -0
  32. package/dist/plugins/counsel/hooks/hooks.json +10 -0
  33. package/dist/plugins/counsel/hooks/project-setup.sh +109 -0
  34. package/dist/plugins/counsel/templates/CLAUDE.md.managed-block +123 -0
  35. package/dist/plugins/counsel/templates/CLAUDE.md.template +111 -0
  36. package/dist/plugins/cruise/.claude-plugin/plugin.json +1 -1
  37. package/dist/plugins/cruise/VERSION +1 -0
  38. package/dist/plugins/cruise/hooks/hooks.json +16 -0
  39. package/dist/plugins/cruise/hooks/project-setup.sh +109 -0
  40. package/dist/plugins/cruise/templates/CLAUDE.md.managed-block +123 -0
  41. package/dist/plugins/cruise/templates/CLAUDE.md.template +111 -0
  42. package/dist/plugins/forge/.claude-plugin/plugin.json +1 -1
  43. package/dist/plugins/forge/VERSION +1 -0
  44. package/dist/plugins/forge/agents/troubleshooter.md +132 -0
  45. package/dist/plugins/forge/commands/implement.md +99 -1
  46. package/dist/plugins/forge/commands/planning.md +100 -11
  47. package/dist/plugins/forge/hooks/escalation-guard.sh +177 -0
  48. package/dist/plugins/forge/hooks/hooks.json +22 -0
  49. package/dist/plugins/forge/hooks/project-setup.sh +109 -0
  50. package/dist/plugins/forge/templates/CLAUDE.md.managed-block +123 -0
  51. package/dist/plugins/forge/templates/CLAUDE.md.template +111 -0
  52. package/dist/plugins/prime/.claude-plugin/plugin.json +1 -1
  53. package/dist/plugins/prime/VERSION +1 -0
  54. package/dist/plugins/prime/agents/troubleshooter.md +132 -0
  55. package/dist/plugins/prime/commands/calibrate.md +20 -0
  56. package/dist/plugins/prime/commands/ci-fix.md +36 -0
  57. package/dist/plugins/prime/commands/fix.md +23 -0
  58. package/dist/plugins/prime/commands/implement.md +99 -1
  59. package/dist/plugins/prime/commands/planning.md +100 -11
  60. package/dist/plugins/prime/commands/qa-incident.md +54 -0
  61. package/dist/plugins/prime/commands/restart.md +186 -30
  62. package/dist/plugins/prime/hooks/escalation-guard.sh +177 -0
  63. package/dist/plugins/prime/hooks/hooks.json +60 -0
  64. package/dist/plugins/prime/hooks/post-config-change-restart-reminder.sh +86 -0
  65. package/dist/plugins/prime/hooks/post-server-crash-watch.sh +120 -0
  66. package/dist/plugins/prime/hooks/pre-server-port-guard.sh +110 -0
  67. package/dist/plugins/prime/hooks/project-setup.sh +109 -0
  68. package/dist/plugins/prime/hooks/sync-agents.sh +99 -12
  69. package/dist/plugins/prime/templates/CLAUDE.md.managed-block +123 -0
  70. package/dist/plugins/prime/templates/CLAUDE.md.template +111 -0
  71. package/dist/plugins/prism/.claude-plugin/plugin.json +1 -1
  72. package/dist/plugins/prism/VERSION +1 -0
  73. package/dist/plugins/prism/commands/qa-incident.md +54 -0
  74. package/dist/plugins/prism/hooks/hooks.json +12 -0
  75. package/dist/plugins/prism/hooks/project-setup.sh +109 -0
  76. package/dist/plugins/prism/templates/CLAUDE.md.managed-block +123 -0
  77. package/dist/plugins/prism/templates/CLAUDE.md.template +111 -0
  78. package/dist/plugins/scalpel/.claude-plugin/plugin.json +1 -1
  79. package/dist/plugins/scalpel/VERSION +1 -0
  80. package/dist/plugins/scalpel/agents/troubleshooter.md +132 -0
  81. package/dist/plugins/scalpel/commands/ci-fix.md +36 -0
  82. package/dist/plugins/scalpel/commands/fix.md +23 -0
  83. package/dist/plugins/scalpel/hooks/escalation-guard.sh +177 -0
  84. package/dist/plugins/scalpel/hooks/hooks.json +24 -0
  85. package/dist/plugins/scalpel/hooks/project-setup.sh +109 -0
  86. package/dist/plugins/scalpel/templates/CLAUDE.md.managed-block +123 -0
  87. package/dist/plugins/scalpel/templates/CLAUDE.md.template +111 -0
  88. package/dist/plugins/sentinel/.claude-plugin/plugin.json +1 -1
  89. package/dist/plugins/sentinel/VERSION +1 -0
  90. package/dist/plugins/sentinel/agents/troubleshooter.md +132 -0
  91. package/dist/plugins/sentinel/commands/restart.md +186 -30
  92. package/dist/plugins/sentinel/hooks/escalation-guard.sh +177 -0
  93. package/dist/plugins/sentinel/hooks/hooks.json +64 -0
  94. package/dist/plugins/sentinel/hooks/post-config-change-restart-reminder.sh +86 -0
  95. package/dist/plugins/sentinel/hooks/post-server-crash-watch.sh +120 -0
  96. package/dist/plugins/sentinel/hooks/pre-server-port-guard.sh +110 -0
  97. package/dist/plugins/sentinel/hooks/project-setup.sh +109 -0
  98. package/dist/plugins/sentinel/templates/CLAUDE.md.managed-block +123 -0
  99. package/dist/plugins/sentinel/templates/CLAUDE.md.template +111 -0
  100. package/dist/plugins/shield/.claude-plugin/plugin.json +1 -1
  101. package/dist/plugins/shield/VERSION +1 -0
  102. package/dist/plugins/shield/hooks/hooks.json +22 -12
  103. package/dist/plugins/shield/hooks/project-setup.sh +109 -0
  104. package/dist/plugins/shield/templates/CLAUDE.md.managed-block +123 -0
  105. package/dist/plugins/shield/templates/CLAUDE.md.template +111 -0
  106. package/dist/plugins/spark/.claude-plugin/plugin.json +1 -1
  107. package/dist/plugins/spark/VERSION +1 -0
  108. package/dist/plugins/spark/commands/calibrate.md +20 -0
  109. package/dist/plugins/spark/hooks/hooks.json +10 -0
  110. package/dist/plugins/spark/hooks/project-setup.sh +109 -0
  111. package/dist/plugins/spark/templates/CLAUDE.md.managed-block +123 -0
  112. package/dist/plugins/spark/templates/CLAUDE.md.template +111 -0
  113. package/hook-defs.json +31 -0
  114. package/hooks/escalation-guard.sh +177 -0
  115. package/hooks/post-config-change-restart-reminder.sh +86 -0
  116. package/hooks/post-server-crash-watch.sh +120 -0
  117. package/hooks/pre-server-port-guard.sh +110 -0
  118. package/hooks/project-setup.sh +109 -0
  119. package/hooks/sync-agents.sh +99 -12
  120. package/install.sh +2 -2
  121. package/package.json +1 -1
  122. package/portable.manifest +7 -1
  123. package/skills/calibrate/SKILL.md +20 -0
  124. package/skills/ci-fix/SKILL.md +36 -0
  125. package/skills/fix/SKILL.md +23 -0
  126. package/skills/implement/SKILL.md +99 -1
  127. package/skills/license/SKILL.md +159 -0
  128. package/skills/planning/SKILL.md +100 -11
  129. package/skills/publish/SKILL.md +3 -0
  130. package/skills/qa-incident/SKILL.md +54 -0
  131. package/skills/restart/SKILL.md +187 -31
@@ -1,68 +1,224 @@
1
1
  ---
2
2
  name: restart
3
- description: "Kill and restart local dev servers. Reads service config from CLAUDE.md. Usage: /restart [service]"
3
+ description: "Discover, restart, and validate local dev servers. Auto-detects Docker vs native, checks health, catches crash loops. Usage: /restart <service> <--preflight>"
4
4
  ---
5
5
 
6
6
  # Restart Servers Skill
7
7
 
8
- Kill and restart local dev servers using the configuration from CLAUDE.md.
8
+ Discover how services run, restart them safely, and verify they stay healthy.
9
9
 
10
- ## Instructions
10
+ ## Phase 1: Discover Services
11
11
 
12
- 1. **Read CLAUDE.md** in the project root. Look for the **Local Dev Services** table:
12
+ **First, check CLAUDE.md** for a `Local Dev Services` table:
13
13
 
14
14
  ```markdown
15
15
  ## Local Dev Services
16
16
 
17
- | Service | Port | Directory | Start Command |
18
- |----------|------|-----------|---------------|
19
- | Frontend | 3000 | frontend/ | npm run dev |
20
- | Backend | 8000 | backend/ | uvicorn app.main:app --reload --port 8000 |
17
+ | Service | Type | Port | Directory | Start Command | Health Check | Depends On |
18
+ |----------|--------|------|-----------|---------------------------------------|-----------------|------------|
19
+ | Postgres | docker | 5432 | — | docker compose up -d postgres | pg_isready | Docker |
20
+ | Backend | native | 8000 | backend/ | uvicorn app.main:app --reload | /api/health | Postgres |
21
+ | Frontend | native | 3000 | frontend/ | npm run dev | /health | Backend |
21
22
  ```
22
23
 
23
- If CLAUDE.md doesn't have this section, scan the project:
24
- - `package.json` scripts → find dev/start commands
25
- - `requirements.txt` / `pyproject.toml` look for uvicorn/gunicorn/flask
26
- - `docker-compose.yml` → extract service ports
27
- - Common ports: frontend 3000, backend 8000
24
+ **If the table is missing or incomplete, auto-discover** by scanning the repo. Check these files in order:
25
+
26
+ | File | What to extract |
27
+ |------|-----------------|
28
+ | `docker-compose.yml` / `docker-compose.*.yml` | Service names, ports, images, healthcheck configs, volume mounts. These are **docker** type services. |
29
+ | `Dockerfile` / `Dockerfile.*` | What gets containerized — cross-reference with compose to understand which services are Docker-managed. |
30
+ | `package.json` (root + subdirs) | `scripts.dev`, `scripts.start`, `scripts.serve` → these are **native** Node services. Check `proxy` field for backend port. |
31
+ | `Makefile` / `Procfile` / `Justfile` | Process definitions, often with ports and dependency ordering. |
32
+ | `pyproject.toml` / `requirements.txt` | Python services — look for uvicorn/gunicorn/flask/django in deps. |
33
+ | `.env` / `.env.local` / `.env.example` | Port assignments (`PORT=`, `DATABASE_URL=`, `REDIS_URL=`), required env vars. |
34
+ | `turbo.json` / `nx.json` / `pnpm-workspace.yaml` | Monorepo structure — maps workspace packages to services. |
35
+
36
+ For each discovered service, determine:
37
+ - **Name**: human-readable (e.g., "Backend API", "Postgres")
38
+ - **Type**: `docker` (managed by docker compose) or `native` (runs directly on host)
39
+ - **Port**: what port it listens on
40
+ - **Directory**: where to `cd` before running the start command
41
+ - **Start command**: exact command to launch it
42
+ - **Health check**: endpoint or command to verify it's running (look for `/health`, `/api/health`, `pg_isready`, `redis-cli ping`, etc.)
43
+ - **Depends on**: other services that must be running first (DB before backend, backend before frontend)
44
+
45
+ **If discovery is ambiguous, ASK the user.** Specifically ask when:
46
+ - Multiple compose files exist and it's unclear which to use (dev vs prod vs test)
47
+ - A service could be Docker OR native (e.g., Postgres has both a compose entry and a local install)
48
+ - No health check endpoint is obvious — ask what URL or command confirms the service is healthy
49
+ - Port conflicts or unusual port assignments are detected
50
+ - You find services but can't determine the dependency order
51
+
52
+ Present what you found and ask for confirmation:
53
+ ```
54
+ Found 3 services:
55
+ 1. Postgres (docker, port 5432) — via docker-compose.yml
56
+ 2. Backend (native, port 8000) — via backend/pyproject.toml
57
+ 3. Frontend (native, port 3000) — via frontend/package.json
58
+
59
+ Dependencies: Frontend → Backend → Postgres
60
+
61
+ Health checks:
62
+ - Postgres: pg_isready -h localhost -p 5432
63
+ - Backend: curl localhost:8000/api/health
64
+ - Frontend: curl localhost:3000
65
+
66
+ Does this look right? Any corrections?
67
+ ```
68
+
69
+ After confirmation (or if CLAUDE.md table already exists), proceed to Phase 2.
70
+
71
+ **Update CLAUDE.md**: If services were auto-discovered and the user confirmed, write/update the `Local Dev Services` table in CLAUDE.md so future runs skip discovery.
28
72
 
29
- 2. **Kill existing processes** for the target service(s):
73
+ ## Phase 2: Pre-flight Validation
30
74
 
75
+ Before touching any running processes, validate the environment is ready:
76
+
77
+ ### 2a. Docker check (if any service type is `docker`)
78
+ ```bash
79
+ # Is Docker daemon running?
80
+ docker info > /dev/null 2>&1 && echo "Docker: OK" || echo "Docker: NOT RUNNING"
81
+ ```
82
+ If Docker is not running, **stop and tell the user**. Don't proceed.
83
+
84
+ ### 2b. Dependency check (for native services)
31
85
  ```bash
32
- # Kill by port
86
+ # Node services — are node_modules installed?
87
+ [ -d "<directory>/node_modules" ] && echo "node_modules: OK" || echo "node_modules: MISSING — run npm install"
88
+
89
+ # Python services — is the venv active / deps installed?
90
+ [ -f "<directory>/.venv/bin/python" ] && echo "venv: OK" || echo "venv: MISSING"
91
+ ```
92
+
93
+ ### 2c. Environment variables
94
+ ```bash
95
+ # Check critical env vars exist (read from .env.example or known requirements)
96
+ [ -f "<directory>/.env" ] || [ -f "<directory>/.env.local" ] && echo "env file: OK" || echo "env file: MISSING"
97
+ ```
98
+
99
+ ### 2d. Port availability
100
+ ```bash
101
+ # Check if port is already in use by a DIFFERENT process than expected
102
+ lsof -ti:<port> 2>/dev/null
103
+ ```
104
+ If a port is occupied by an unexpected process, warn the user before killing it.
105
+
106
+ ### 2e. Pre-flight only mode
107
+ If the user ran `/restart --preflight`, **stop here** and report results. Don't restart anything.
108
+
109
+ ## Phase 3: Restart Services (dependency order)
110
+
111
+ Restart in dependency order — infrastructure first, then backends, then frontends.
112
+
113
+ ### 3a. Stop services (reverse dependency order)
114
+ ```bash
115
+ # For native services — kill by port
33
116
  lsof -ti:<port> | xargs kill -9 2>/dev/null
34
117
 
118
+ # For docker services — stop the specific service
119
+ docker compose stop <service_name>
120
+
35
121
  # Wait for ports to free
36
122
  sleep 2
37
- ```
38
123
 
39
- 3. **Start each server** in background using the configured start command:
124
+ # Verify port is actually free
125
+ lsof -ti:<port> 2>/dev/null && echo "WARNING: port <port> still occupied" || echo "Port <port>: free"
126
+ ```
40
127
 
128
+ ### 3b. Start services (dependency order)
41
129
  ```bash
42
- # Run in background
43
- <start_command> &
130
+ # Docker services first
131
+ docker compose up -d <service_name>
132
+
133
+ # Then native services — run in background
134
+ cd <directory> && <start_command>
44
135
  ```
45
136
 
46
- Use `run_in_background: true` on the Bash tool.
137
+ Use `run_in_background: true` on the Bash tool for native services.
138
+
139
+ **Wait between dependency layers** — don't start the backend until the DB health check passes. Don't start the frontend until the backend health check passes.
140
+
141
+ ## Phase 4: Post-restart Health Validation
142
+
143
+ This is the critical phase. A single health check is not enough — services can start and then crash seconds later.
47
144
 
48
- 4. **Health check** after startup:
145
+ ### 4a. Initial health check (per service, in dependency order)
49
146
 
50
147
  ```bash
51
- # Check each service
52
- curl -sf http://localhost:<port>/health > /dev/null 2>&1 && echo "<service>:UP" || echo "<service>:DOWN"
148
+ # HTTP services
149
+ curl -sf http://localhost:<port><health_path> > /dev/null 2>&1 && echo "<service>: UP" || echo "<service>: DOWN"
150
+
151
+ # Postgres
152
+ pg_isready -h localhost -p <port> 2>/dev/null && echo "Postgres: UP" || echo "Postgres: DOWN"
153
+
154
+ # Redis
155
+ redis-cli -p <port> ping 2>/dev/null | grep -q PONG && echo "Redis: UP" || echo "Redis: DOWN"
156
+
157
+ # Docker services
158
+ docker compose ps <service_name> --format '{{.Status}}' | grep -q "Up" && echo "<service>: UP" || echo "<service>: DOWN"
159
+ ```
160
+
161
+ ### 4b. Crash loop detection (wait 8 seconds, check again)
162
+
163
+ ```bash
164
+ sleep 8
165
+
166
+ # Re-check each service
167
+ curl -sf http://localhost:<port><health_path> > /dev/null 2>&1 && echo "<service>: STABLE" || echo "<service>: CRASHED"
168
+
169
+ # For native services — is the process still running?
170
+ lsof -ti:<port> > /dev/null 2>&1 && echo "<service> process: alive" || echo "<service> process: GONE"
53
171
  ```
54
172
 
55
- 5. **Report** status of all servers.
173
+ If a service is down on the second check:
174
+ 1. **Check logs** — read the last 30 lines of output for error messages
175
+ 2. **Report the failure clearly** with the error
176
+ 3. **Do NOT retry automatically** — tell the user what went wrong
177
+
178
+ ### 4c. Final status report
179
+
180
+ ```
181
+ ✅ Restart complete
182
+
183
+ | Service | Type | Port | Status |
184
+ |----------|--------|------|---------|
185
+ | Postgres | docker | 5432 | STABLE |
186
+ | Backend | native | 8000 | STABLE |
187
+ | Frontend | native | 3000 | STABLE |
188
+
189
+ All services healthy after 10s stability check.
190
+ ```
191
+
192
+ Or if something failed:
193
+
194
+ ```
195
+ ⚠️ Restart partial — 1 service unhealthy
196
+
197
+ | Service | Type | Port | Status |
198
+ |----------|--------|------|---------|
199
+ | Postgres | docker | 5432 | STABLE |
200
+ | Backend | native | 8000 | CRASHED |
201
+ | Frontend | native | 3000 | SKIPPED |
202
+
203
+ Backend crashed after startup. Last error:
204
+ ModuleNotFoundError: No module named 'sqlalchemy'
205
+
206
+ Fix: Run `pip install -r requirements.txt` in backend/ and retry.
207
+ Frontend was skipped because it depends on Backend.
208
+ ```
56
209
 
57
210
  ## Argument Patterns
58
211
 
59
212
  | User Input | Action |
60
213
  |-----------|--------|
61
- | `/restart` (no args) | Kill and restart all configured servers |
62
- | `/restart backend` | Kill and restart only the backend service |
63
- | `/restart frontend` | Kill and restart only the frontend service |
64
- | `/restart <service>` | Kill and restart the named service |
214
+ | `/restart` (no args) | Discover validate → restart all services |
215
+ | `/restart backend` | Restart only the named service (and its dependencies if they're down) |
216
+ | `/restart --preflight` | Discover and validate only don't restart anything |
65
217
 
66
- ## If CLAUDE.md Is Missing Service Config
218
+ ## Key Principles
67
219
 
68
- Report: "No Local Dev Services table found in CLAUDE.md. Add a table with Service, Port, Directory, and Start Command columns. Or specify the services manually."
220
+ 1. **Never guess** if you can't determine how a service runs, ask
221
+ 2. **Dependency order matters** — always start infra → backend → frontend
222
+ 3. **One health check isn't enough** — check twice with a gap to catch crash loops
223
+ 4. **Report errors with context** — show the actual log output, not just "DOWN"
224
+ 5. **Don't retry blindly** — if a service crashes, diagnose and report, don't loop
@@ -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,5 +1,17 @@
1
1
  {
2
2
  "hooks": {
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
+ }
14
+ ],
3
15
  "PostToolUse": [
4
16
  {
5
17
  "matcher": "Bash",
@@ -20,6 +32,58 @@
20
32
  "timeout": 5
21
33
  }
22
34
  ]
35
+ },
36
+ {
37
+ "matcher": "Edit",
38
+ "hooks": [
39
+ {
40
+ "type": "command",
41
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/post-config-change-restart-reminder.sh",
42
+ "timeout": 5
43
+ }
44
+ ]
45
+ },
46
+ {
47
+ "matcher": "Write",
48
+ "hooks": [
49
+ {
50
+ "type": "command",
51
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/post-config-change-restart-reminder.sh",
52
+ "timeout": 5
53
+ }
54
+ ]
55
+ },
56
+ {
57
+ "matcher": "Bash",
58
+ "hooks": [
59
+ {
60
+ "type": "command",
61
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/post-server-crash-watch.sh",
62
+ "timeout": 15
63
+ }
64
+ ]
65
+ },
66
+ {
67
+ "matcher": "Bash",
68
+ "hooks": [
69
+ {
70
+ "type": "command",
71
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/escalation-guard.sh",
72
+ "timeout": 5
73
+ }
74
+ ]
75
+ }
76
+ ],
77
+ "PreToolUse": [
78
+ {
79
+ "matcher": "Bash",
80
+ "hooks": [
81
+ {
82
+ "type": "command",
83
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/pre-server-port-guard.sh",
84
+ "timeout": 5
85
+ }
86
+ ]
23
87
  }
24
88
  ]
25
89
  }
@@ -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