@arthai/agents 1.0.5 → 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 (130) hide show
  1. package/README.md +33 -3
  2. package/VERSION +1 -1
  3. package/agents/troubleshooter.md +132 -0
  4. package/bin/cli.js +297 -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/qa-incident/SKILL.md +54 -0
  130. package/skills/restart/SKILL.md +187 -31
@@ -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
@@ -22,8 +22,9 @@ if [ ! -d "$AGENTS_DIR/.git" ]; then
22
22
  exit 0
23
23
  fi
24
24
 
25
- # Exit silently if no license
26
- if [ ! -f "$LICENSE_FILE" ]; then
25
+ # Exit silently if no license found in any path
26
+ ARTHAI_LICENSE_PATH="$REAL_HOME/.arthai/license"
27
+ if [ ! -f "$LICENSE_FILE" ] && [ ! -f "$ARTHAI_LICENSE_PATH" ] && [ -z "${ARTHAI_LICENSE_KEY:-}" ]; then
27
28
  exit 0
28
29
  fi
29
30
 
@@ -108,6 +109,27 @@ fi
108
109
 
109
110
  # --- Step 2: Check license (with revocation enforcement) ---
110
111
  NOW=$(date +%s)
112
+ WORKER_URL="https://license-worker.muddassar-shaikh.workers.dev"
113
+
114
+ # Resolve license key: ~/.claude-agents/.license takes priority (existing clone-install users),
115
+ # fall back to ~/.arthai/license (npm activate users).
116
+ ARTHAI_LICENSE_KEY_VALUE="${ARTHAI_LICENSE_KEY:-}"
117
+ RESOLVED_KEY=""
118
+ if [ -n "$ARTHAI_LICENSE_KEY_VALUE" ]; then
119
+ RESOLVED_KEY="$ARTHAI_LICENSE_KEY_VALUE"
120
+ elif [ -f "$LICENSE_FILE" ]; then
121
+ RESOLVED_KEY=$(cat "$LICENSE_FILE" 2>/dev/null | tr -d '[:space:]' || echo "")
122
+ elif [ -f "$REAL_HOME/.arthai/license" ]; then
123
+ RESOLVED_KEY=$(cat "$REAL_HOME/.arthai/license" 2>/dev/null | tr -d '[:space:]' || echo "")
124
+ fi
125
+
126
+ # Validate key format before using it in any JSON payload — prevents JSON injection
127
+ # via a crafted key value in the env var or license file.
128
+ KEY_FORMAT='^ARTH-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
129
+ if [ -n "$RESOLVED_KEY" ] && ! echo "$RESOLVED_KEY" | grep -qE "$KEY_FORMAT"; then
130
+ # Key has invalid format — don't use it for Worker call or local check
131
+ RESOLVED_KEY=""
132
+ fi
111
133
 
112
134
  # Detect if authorized-keys.txt changed in the pull — force re-check if so
113
135
  AK_HASH_FILE="$AGENTS_DIR/.ak-hash"
@@ -128,6 +150,49 @@ fi
128
150
  # Store current hash for next comparison
129
151
  [ -n "$AK_CURRENT_HASH" ] && echo "$AK_CURRENT_HASH" > "$AK_HASH_FILE" 2>/dev/null || true
130
152
 
153
+ # validate_with_worker: call Worker /validate with the resolved key.
154
+ # Sets WORKER_RESULT: "valid", "invalid" (definitive), or "network_error".
155
+ validate_with_worker() {
156
+ local key="$1"
157
+ if [ -z "$key" ]; then
158
+ WORKER_RESULT="invalid"
159
+ return
160
+ fi
161
+
162
+ local payload="{\"key\":\"$key\"}"
163
+ local response
164
+ local curl_exit
165
+
166
+ # Pass payload via stdin (--data @-) so the key is not visible in the process list
167
+ response=$(printf '%s' "$payload" | curl \
168
+ --silent \
169
+ --max-time 10 \
170
+ --connect-timeout 5 \
171
+ --request POST \
172
+ --header "Content-Type: application/json" \
173
+ --data "@-" \
174
+ "$WORKER_URL/validate" 2>/dev/null)
175
+ curl_exit=$?
176
+
177
+ if [ $curl_exit -ne 0 ] || [ -z "$response" ]; then
178
+ WORKER_RESULT="network_error"
179
+ return
180
+ fi
181
+
182
+ # Parse "valid" field from JSON — minimal grep-based parse, no jq dependency
183
+ local valid_field
184
+ valid_field=$(echo "$response" | grep -o '"valid"[[:space:]]*:[[:space:]]*[a-z]*' | head -1 | grep -o '[a-z]*$')
185
+
186
+ if [ "$valid_field" = "true" ]; then
187
+ WORKER_RESULT="valid"
188
+ elif [ "$valid_field" = "false" ]; then
189
+ WORKER_RESULT="invalid"
190
+ else
191
+ # Unexpected response format — treat as network error (fail safe)
192
+ WORKER_RESULT="network_error"
193
+ fi
194
+ }
195
+
131
196
  LICENSE_VALID=true
132
197
  if [ -f "$LICENSE_CACHE" ]; then
133
198
  LAST_CHECK=$(cat "$LICENSE_CACHE" 2>/dev/null || echo "0")
@@ -136,19 +201,41 @@ if [ -f "$LICENSE_CACHE" ]; then
136
201
  # Cache is fresh, skip validation
137
202
  :
138
203
  else
139
- # Re-validate
140
- if ! "$AGENTS_DIR/install.sh" --check-license-only 2>/dev/null; then
204
+ # Re-validate: try Worker first, fall back to local only on network error
205
+ validate_with_worker "$RESOLVED_KEY"
206
+ if [ "$WORKER_RESULT" = "valid" ]; then
207
+ echo "$NOW" > "$LICENSE_CACHE"
208
+ elif [ "$WORKER_RESULT" = "invalid" ]; then
209
+ # Worker says definitively invalid — no local fallback, clear cache
210
+ rm -f "$LICENSE_CACHE" 2>/dev/null || true
141
211
  LICENSE_VALID=false
142
212
  else
143
- echo "$NOW" > "$LICENSE_CACHE"
213
+ # Network error — fall back to local authorized-keys.txt check
214
+ echo "License server unavailable. Retrying with local validation..." >&2
215
+ if ! "$AGENTS_DIR/install.sh" --check-license-only 2>/dev/null; then
216
+ LICENSE_VALID=false
217
+ else
218
+ echo "$NOW" > "$LICENSE_CACHE"
219
+ fi
144
220
  fi
145
221
  fi
146
222
  else
147
- # First check (or cache was invalidated by AK change)
148
- if ! "$AGENTS_DIR/install.sh" --check-license-only 2>/dev/null; then
223
+ # First check (or cache was invalidated by AK change): try Worker first
224
+ validate_with_worker "$RESOLVED_KEY"
225
+ if [ "$WORKER_RESULT" = "valid" ]; then
226
+ echo "$NOW" > "$LICENSE_CACHE"
227
+ elif [ "$WORKER_RESULT" = "invalid" ]; then
228
+ # Worker says definitively invalid — no local fallback, ensure cache stays clear
229
+ rm -f "$LICENSE_CACHE" 2>/dev/null || true
149
230
  LICENSE_VALID=false
150
231
  else
151
- echo "$NOW" > "$LICENSE_CACHE"
232
+ # Network error — fall back to local authorized-keys.txt check
233
+ echo "License server unavailable. Retrying with local validation..." >&2
234
+ if ! "$AGENTS_DIR/install.sh" --check-license-only 2>/dev/null; then
235
+ LICENSE_VALID=false
236
+ else
237
+ echo "$NOW" > "$LICENSE_CACHE"
238
+ fi
152
239
  fi
153
240
  fi
154
241
 
@@ -182,12 +269,12 @@ if ! $LICENSE_VALID; then
182
269
 
183
270
  # Remove CLAUDE.md managed block
184
271
  CLAUDEMD="$PROJECT_DIR/CLAUDE.md"
185
- if [ -f "$CLAUDEMD" ] && grep -qF "<!-- >>> claude-agents toolkit -->" "$CLAUDEMD" 2>/dev/null; then
272
+ if [ -f "$CLAUDEMD" ] && grep -qF "<!-- >>> claude-agents toolkit (DO NOT EDIT THIS BLOCK) >>> -->" "$CLAUDEMD" 2>/dev/null; then
186
273
  tmp=$(mktemp)
187
274
  in_block=false
188
275
  skip_next_blank=false
189
276
  while IFS= read -r line; do
190
- if echo "$line" | grep -qF "<!-- >>> claude-agents toolkit -->"; then
277
+ if echo "$line" | grep -qF "<!-- >>> claude-agents toolkit (DO NOT EDIT THIS BLOCK) >>> -->"; then
191
278
  in_block=true
192
279
  continue
193
280
  fi
@@ -218,8 +305,8 @@ if ! $LICENSE_VALID; then
218
305
  rm -f "$LICENSE_CACHE" 2>/dev/null || true
219
306
  fi
220
307
 
221
- echo " License revoked. Toolkit has been disabled for this project."
222
- echo " Contact support to renew your license."
308
+ echo "Invalid license key. Toolkit has been disabled for this project."
309
+ echo " Check your key or get one at arthai.dev/pricing"
223
310
  exit 0
224
311
  fi
225
312
 
package/install.sh CHANGED
@@ -114,7 +114,7 @@ get_category_items() {
114
114
  echo "agents/architect.md agents/code-reviewer.md agents/design-studio-create.md agents/design-studio-critique.md agents/design-studio-think.md agents/gtm-expert.md agents/product-manager.md agents/stakeholder-reporter.md agents/meeting-prep.md agents/content-strategist.md agents/user-researcher.md agents/competitive-analyst.md"
115
115
  ;;
116
116
  development)
117
- echo "agents/frontend.md agents/python-backend.md agents/ops.md skills/planning skills/implement skills/fix skills/pr skills/precheck skills/review-pr skills/issue"
117
+ echo "agents/frontend.md agents/python-backend.md agents/ops.md agents/troubleshooter.md skills/planning skills/implement skills/fix skills/pr skills/precheck skills/review-pr skills/issue"
118
118
  ;;
119
119
  quality)
120
120
  echo "agents/qa.md agents/qa-e2e.md agents/qa-challenger.md agents/qa-test-promoter.md agents/qa-baseline-updater.md agents/qa-domain.md skills/qa skills/ci-fix skills/qa-incident skills/qa-learn"
@@ -126,7 +126,7 @@ get_category_items() {
126
126
  echo "hooks/triage-router.sh hooks/session-end.sh"
127
127
  ;;
128
128
  guardrails)
129
- echo "hooks/session-bootstrap.sh hooks/pre-bash-guard.sh hooks/pre-task-context.sh hooks/pre-edit-guard.sh hooks/post-test-summary.sh hooks/post-edit-lint.sh hooks/post-deploy-health.sh hooks/post-diff-test-compare.sh hooks/post-git-state.sh hooks/post-merge-cleanup.sh skills/onboard skills/welcome skills/share skills/templates skills/wizard skills/autopilot skills/continue"
129
+ echo "hooks/session-bootstrap.sh hooks/pre-bash-guard.sh hooks/pre-task-context.sh hooks/pre-edit-guard.sh hooks/post-test-summary.sh hooks/post-edit-lint.sh hooks/post-deploy-health.sh hooks/post-diff-test-compare.sh hooks/post-git-state.sh hooks/post-merge-cleanup.sh hooks/escalation-guard.sh hooks/project-setup.sh hooks/pre-server-port-guard.sh hooks/post-config-change-restart-reminder.sh hooks/post-server-crash-watch.sh skills/onboard skills/welcome skills/share skills/templates skills/wizard skills/autopilot skills/continue"
130
130
  ;;
131
131
  railway)
132
132
  echo "skills/railway/central-station skills/railway/database skills/railway/deploy skills/railway/deployment skills/railway/domain skills/railway/environment skills/railway/metrics skills/railway/new skills/railway/projects skills/railway/railway-docs skills/railway/service skills/railway/status skills/railway/templates"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arthai/agents",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "AI-powered development toolkit for Claude Code — agents, skills, and hooks",
5
5
  "bin": {
6
6
  "arthai": "bin/cli.js"
package/portable.manifest CHANGED
@@ -38,6 +38,7 @@ agent agents/content-strategist.md
38
38
  agent agents/user-researcher.md
39
39
  agent agents/competitive-analyst.md
40
40
  agent agents/ai-consultant.md
41
+ agent agents/troubleshooter.md
41
42
 
42
43
  # Portable skills (30)
43
44
  skill skills/explore.md
@@ -78,7 +79,8 @@ skill-dir skills/solution-architect
78
79
  skill-dir skills/deliverable-builder
79
80
  skill-dir skills/engagement-tracker
80
81
 
81
- # Portable hooks (13)
82
+ # Portable hooks (14)
83
+ hook hooks/project-setup.sh
82
84
  hook hooks/ensure-client-dir.sh
83
85
  hook hooks/check-deliverable.sh
84
86
  hook hooks/session-summary.sh
@@ -109,4 +111,8 @@ railway-skill-dir skills/railway/railway-docs
109
111
  railway-skill-dir skills/railway/service
110
112
  railway-skill-dir skills/railway/status
111
113
  railway-skill-dir skills/railway/templates
114
+ hook hooks/escalation-guard.sh
115
+ hook hooks/pre-server-port-guard.sh
116
+ hook hooks/post-config-change-restart-reminder.sh
117
+ hook hooks/post-server-crash-watch.sh
112
118
  hook hooks/session-end.sh
@@ -74,6 +74,26 @@ This goes far deeper than `/scan`. Read **actual source code** to understand HOW
74
74
 
75
75
  #### Step 1.1: Foundation Scan
76
76
 
77
+ **Managed block check (belt-and-suspenders — runs before anything else):**
78
+
79
+ Check if CLAUDE.md has the toolkit managed block. If missing, inject it before proceeding:
80
+
81
+ ```bash
82
+ MANAGED_START="<!-- >>> claude-agents toolkit (DO NOT EDIT THIS BLOCK) >>> -->"
83
+ if [ -f "$CLAUDE_PROJECT_DIR/CLAUDE.md" ]; then
84
+ grep -qF "$MANAGED_START" "$CLAUDE_PROJECT_DIR/CLAUDE.md" || echo "MISSING_BLOCK"
85
+ fi
86
+ ```
87
+
88
+ If the managed block is missing:
89
+ 1. Read `~/.claude-agents/templates/CLAUDE.md.managed-block` (or `$CLAUDE_PROJECT_DIR/.claude/hooks/../templates/CLAUDE.md.managed-block` if installed via plugin)
90
+ 2. Inject it at the end of CLAUDE.md using the markers:
91
+ - Start: `<!-- >>> claude-agents toolkit (DO NOT EDIT THIS BLOCK) >>> -->`
92
+ - End: `<!-- <<< claude-agents toolkit <<< -->`
93
+ 3. Report: "Injected toolkit managed block into CLAUDE.md (was missing)"
94
+
95
+ This catches any install path that missed the injection — clone installs, manual setups, or projects that predate the managed block feature.
96
+
77
97
  Run `/scan` first if CLAUDE.md has `<!-- TODO -->` placeholders or doesn't exist. This populates
78
98
  the basics (tech stack, services, test commands, infrastructure). Then proceed to deep scan.
79
99
 
@@ -162,6 +162,42 @@ gh run view <FAILED_RUN_ID> --log-failed 2>&1 | tail -200
162
162
  | **Build failures** | build errors | Read error, fix import/export/config |
163
163
  | **Migration** | Alembic/Django errors | Fix migration file |
164
164
  | **Dependency** | pip/npm install failures | Fix requirements/package.json |
165
+ | **Toolkit tests** | 15/20-skill-runtime-safety, manifest-coverage | See Toolkit Test Fixes below |
166
+
167
+ #### Toolkit-Specific Test Fixes (claude-agents repo)
168
+
169
+ When CI fails on the mechanical test suite (`tests/run.sh`), these are the common failures and auto-fixes:
170
+
171
+ | Test | Failure message | Root cause | Auto-fix |
172
+ |------|----------------|-----------|----------|
173
+ | `20-skill-runtime-safety` | "regex-unsafe [brackets] in descriptions" | SKILL.md `description:` or `arguments:` field contains `[text]` | Replace `[text]` with `<text>` in the frontmatter field. Brackets break regex matching in Claude Code. |
174
+ | `20-skill-runtime-safety` | "Skills missing required frontmatter fields" | SKILL.md missing `user-invocable: true` or `arguments:` | Add missing field to the YAML frontmatter between `---` markers. Check `git show HEAD~1:path/to/SKILL.md` for the original. |
175
+ | `15-manifest-coverage` | "entries mapped to categories" | New file in `portable.manifest` not listed in any `get_category_items()` category in `install.sh` | Add the manifest entry to the appropriate category in `install.sh:get_category_items()`. |
176
+ | `15-manifest-coverage` | "Install creates all expected symlinks" | New file in `portable.manifest` but install didn't create the symlink | Usually follows from the category mapping fix above. |
177
+ | `15-manifest-coverage` | "Entry counts are consistent" | Mismatch between manifest entries and installed files | Check that new manifest entries have matching source files. |
178
+ | `19-brownfield-assessment` | "classify_file returns IDENTICAL" | Agent fixture is stale after editing an agent `.md` file | Update fixture: `cp agents/{name}.md tests/fixtures/claude-setups/poweruser/.claude/agents/` |
179
+
180
+ **Auto-fix sequence for toolkit tests:**
181
+
182
+ ```bash
183
+ # 1. Get the exact failure
184
+ gh run view <ID> --log-failed 2>&1 | grep -E "FAIL|✗" | head -5
185
+
186
+ # 2. For bracket issues — find and fix ALL bracket descriptions
187
+ grep -rn 'description:.*\[' skills/*/SKILL.md
188
+ # Replace [text] with <text> in each match
189
+
190
+ # 3. For missing frontmatter — compare against last known good
191
+ git show HEAD~1:path/to/SKILL.md | head -6
192
+ # Restore missing fields
193
+
194
+ # 4. For manifest coverage — add to install.sh categories
195
+ grep "get_category_items" install.sh
196
+ # Add new entries to the right category
197
+
198
+ # 5. Verify locally before pushing
199
+ bash tests/run.sh --suite 15,20 --scenario a
200
+ ```
165
201
 
166
202
  **Attempt escalation:**
167
203
  - Attempt 1: Apply the obvious fix (auto-fix tools, direct code fix)
@@ -478,6 +478,29 @@ Select the right agent based on which layer the bug is in:
478
478
  If `.claude/project-profile.md` exists, read it to determine the platform and pick the right agent.
479
479
  If `/calibrate` generated custom agents (e.g., `ios-developer.md`), use those for platform-specific bugs.
480
480
 
481
+ **4.2b: Escalation protocol for fix agents**
482
+
483
+ Include this in the implementation agent's prompt:
484
+
485
+ ```
486
+ ## When Your Fix Doesn't Work (MANDATORY)
487
+
488
+ 1. After first failed attempt: re-read the root cause analysis from Step 1.
489
+ Is the root cause correct? If not, go back to Step 1.
490
+ 2. After second failed attempt: consult knowledge base:
491
+ - .claude/knowledge/qa-knowledge/ (error keywords)
492
+ - .claude/knowledge/shared/conventions.md (project gotchas)
493
+ - git log --all --grep="<error keyword>" --oneline -10
494
+ 3. After third failed attempt: STOP. Do not try another fix.
495
+ Generate a STUCK REPORT and send to team-lead:
496
+ - Error: [exact message]
497
+ - Root cause hypothesis: [from Step 1]
498
+ - Fix attempts: [1, 2, 3 with results]
499
+ - KB consultation results: [what you found]
500
+ - Recommendation: [re-investigate root cause / ask user for X / try different approach]
501
+ 4. If a troubleshooter agent is available, team-lead may spawn one.
502
+ ```
503
+
481
504
  **Agent prompt includes:**
482
505
  ```
483
506
  1. Root cause analysis from Step 1