@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
@@ -0,0 +1,111 @@
1
+ # CLAUDE.md — {{PROJECT_NAME}}
2
+
3
+ <!-- Generated by claude-agents install.sh --init -->
4
+ <!-- TODO: Replace {{placeholders}} with your project details -->
5
+
6
+ ## Project Overview
7
+
8
+ {{PROJECT_NAME}} is a {{DESCRIPTION}}.
9
+
10
+ ## Tech Stack
11
+
12
+ - **Frontend**: <!-- TODO: e.g., Next.js 14, React 18, TypeScript, Tailwind -->
13
+ - **Backend**: <!-- TODO: e.g., FastAPI, SQLAlchemy, PostgreSQL -->
14
+ - **Auth**: <!-- TODO: e.g., Stytch, Auth0, Clerk -->
15
+ - **Deploy**: <!-- TODO: e.g., Railway, Vercel, AWS -->
16
+
17
+ ## Project Structure
18
+
19
+ ```
20
+ {{PROJECT_NAME}}/
21
+ ├── frontend/ <!-- TODO: Frontend directory -->
22
+ ├── backend/ <!-- TODO: Backend directory -->
23
+ └── ...
24
+ ```
25
+
26
+ ## Key Architecture
27
+
28
+ <!-- TODO: Describe your auth flow, API patterns, database schema, etc. -->
29
+
30
+ ## Local Dev Services
31
+
32
+ <!-- TODO: Auto-populated by /scan or fill manually -->
33
+
34
+ | Service | Port | Directory | Start Command |
35
+ |----------|------|-----------|---------------|
36
+ | Frontend | <!-- TODO --> | frontend/ | <!-- TODO: e.g., npm run dev --> |
37
+ | Backend | <!-- TODO --> | backend/ | <!-- TODO: e.g., uvicorn app.main:app --reload --> |
38
+
39
+ ## Test Commands
40
+
41
+ <!-- TODO: Auto-populated by /scan or fill manually -->
42
+
43
+ | What | Command | Directory |
44
+ |------|---------|-----------|
45
+ | Backend tests | <!-- TODO: e.g., pytest --> | backend/ |
46
+ | Backend lint | <!-- TODO: e.g., ruff check . --> | backend/ |
47
+ | Frontend tests | <!-- TODO: e.g., npm test --> | frontend/ |
48
+ | Frontend lint | <!-- TODO: e.g., npm run lint --> | frontend/ |
49
+ | Type check | <!-- TODO: e.g., npx tsc --noEmit --> | frontend/ |
50
+ | E2E tests | <!-- TODO: e.g., npx playwright test --> | frontend/ |
51
+
52
+ ## Infrastructure
53
+
54
+ <!-- TODO: Auto-populated by /scan or fill manually -->
55
+
56
+ | Platform | Service | Domain |
57
+ |----------|---------|--------|
58
+ | <!-- TODO: e.g., Railway --> | <!-- TODO --> | <!-- TODO --> |
59
+
60
+ Health endpoints: <!-- TODO: e.g., /health, /api/health -->
61
+
62
+ ## Environments
63
+
64
+ <!-- TODO: Auto-populated by /scan environments or /calibrate -->
65
+
66
+ | Name | Type | URL | Health | Deploy | Branch |
67
+ |------|------|-----|--------|--------|--------|
68
+ | local | development | <!-- TODO --> | <!-- TODO: e.g., /health --> | manual | — |
69
+ | <!-- TODO --> | <!-- TODO: staging/production/preview/canary --> | <!-- TODO --> | <!-- TODO --> | <!-- TODO --> | <!-- TODO --> |
70
+
71
+ Access notes: <!-- TODO: e.g., Railway MCP for staging/prod. Env vars: .env.local, .env.staging -->
72
+
73
+ ## Domain
74
+
75
+ <!-- TODO: Auto-populated by /scan or fill manually -->
76
+ <!-- Describe what this app does, its core entities, and business rules. -->
77
+ <!-- Used by qa-domain agent for domain-aware testing. -->
78
+
79
+ ## Running Locally
80
+
81
+ ```bash
82
+ # TODO: Add your local development commands
83
+ # Frontend
84
+ cd frontend && npm run dev
85
+
86
+ # Backend
87
+ cd backend && source .venv/bin/activate && uvicorn app.main:app --reload
88
+ ```
89
+
90
+ ## Critical Rules
91
+
92
+ <!-- TODO: Add project-specific rules, e.g.: -->
93
+ - Never push to main directly — always create a PR
94
+ - Secrets in .env.local only — never committed
95
+
96
+ ## Agent Customization
97
+
98
+ The following agents/skills are managed by `claude-agents` (symlinked):
99
+ - Run `~/.claude-agents/install.sh --status` to see what's linked
100
+ - To override any portable file, replace the symlink with a regular file
101
+ - Your override won't be touched by future syncs
102
+
103
+ ### Project-Specific Agents
104
+
105
+ Add project-specific agents as regular files in `.claude/agents/`:
106
+ - See `~/.claude-agents/examples/agents/` for templates (frontend, backend, ops, sre, qa)
107
+
108
+ ### Project-Specific Skills
109
+
110
+ Add project-specific skills as regular directories in `.claude/skills/`:
111
+ - See `~/.claude-agents/examples/skills/` for templates (ci-fix, qa, restart)
package/hook-defs.json CHANGED
@@ -100,5 +100,36 @@
100
100
  "entries": [
101
101
  {"event": "PostToolUse", "matcher": "Bash", "timeout": 5}
102
102
  ]
103
+ },
104
+ "pre-server-port-guard": {
105
+ "script": "pre-server-port-guard.sh",
106
+ "entries": [
107
+ {"event": "PreToolUse", "matcher": "Bash", "timeout": 5}
108
+ ]
109
+ },
110
+ "post-config-change-restart-reminder": {
111
+ "script": "post-config-change-restart-reminder.sh",
112
+ "entries": [
113
+ {"event": "PostToolUse", "matcher": "Edit", "timeout": 5},
114
+ {"event": "PostToolUse", "matcher": "Write", "timeout": 5}
115
+ ]
116
+ },
117
+ "post-server-crash-watch": {
118
+ "script": "post-server-crash-watch.sh",
119
+ "entries": [
120
+ {"event": "PostToolUse", "matcher": "Bash", "timeout": 15}
121
+ ]
122
+ },
123
+ "project-setup": {
124
+ "script": "project-setup.sh",
125
+ "entries": [
126
+ {"event": "SessionStart", "matcher": "", "timeout": 10}
127
+ ]
128
+ },
129
+ "escalation-guard": {
130
+ "script": "escalation-guard.sh",
131
+ "entries": [
132
+ {"event": "PostToolUse", "matcher": "Bash", "timeout": 5}
133
+ ]
103
134
  }
104
135
  }
@@ -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
@@ -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