claude-queue 1.3.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Makefile +37 -0
- package/README.md +141 -0
- package/claude-queue.sh +539 -0
- package/package.json +13 -49
- package/dist/cli.js +0 -686
- package/dist/mcp/index.d.ts +0 -1
- package/dist/mcp/index.js +0 -577
- package/dist/mcp.js +0 -8
- package/dist/server/index.js +0 -1379
- package/dist/skills/queue/SKILL.md +0 -153
- package/dist/ui/assets/index-CGuz4rUk.css +0 -1
- package/dist/ui/assets/index-yyLY0y4s.js +0 -90
- package/dist/ui/index.html +0 -15
- package/dist/ui/sounds/complete.mp3 +0 -0
- package/dist/ui/sounds/question.mp3 +0 -0
- package/dist/ui/sounds/start.mp3 +0 -0
package/claude-queue.sh
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# claude-queue - Automated GitHub issue solver
|
|
4
|
+
#
|
|
5
|
+
# Fetches all open issues from the current repo, uses Claude Code CLI
|
|
6
|
+
# to solve each one, and opens a PR in the morning with everything.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# claude-queue [options]
|
|
10
|
+
#
|
|
11
|
+
# Options:
|
|
12
|
+
# --max-retries N Max retries per issue (default: 3)
|
|
13
|
+
# --max-turns N Max Claude turns per attempt (default: 50)
|
|
14
|
+
# --label LABEL Only process issues with this label
|
|
15
|
+
# --model MODEL Claude model to use
|
|
16
|
+
# -v, --version Show version
|
|
17
|
+
# -h, --help Show this help message
|
|
18
|
+
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
|
|
21
|
+
VERSION=$(node -p "require('$(dirname "$0")/package.json').version" 2>/dev/null || echo "unknown")
|
|
22
|
+
|
|
23
|
+
MAX_RETRIES=3
|
|
24
|
+
MAX_TURNS=50
|
|
25
|
+
ISSUE_FILTER=""
|
|
26
|
+
MODEL_FLAG=""
|
|
27
|
+
DATE=$(date +%Y-%m-%d)
|
|
28
|
+
TIMESTAMP=$(date +%H%M%S)
|
|
29
|
+
BRANCH="claude-queue/${DATE}"
|
|
30
|
+
LOG_DIR="/tmp/claude-queue-${DATE}-${TIMESTAMP}"
|
|
31
|
+
|
|
32
|
+
LABEL_PROGRESS="claude-queue:in-progress"
|
|
33
|
+
LABEL_SOLVED="claude-queue:solved"
|
|
34
|
+
LABEL_FAILED="claude-queue:failed"
|
|
35
|
+
|
|
36
|
+
RED='\033[0;31m'
|
|
37
|
+
GREEN='\033[0;32m'
|
|
38
|
+
YELLOW='\033[1;33m'
|
|
39
|
+
BLUE='\033[0;34m'
|
|
40
|
+
DIM='\033[2m'
|
|
41
|
+
BOLD='\033[1m'
|
|
42
|
+
NC='\033[0m'
|
|
43
|
+
|
|
44
|
+
declare -a SOLVED_ISSUES=()
|
|
45
|
+
declare -a FAILED_ISSUES=()
|
|
46
|
+
declare -a SKIPPED_ISSUES=()
|
|
47
|
+
CURRENT_ISSUE=""
|
|
48
|
+
START_TIME=$(date +%s)
|
|
49
|
+
|
|
50
|
+
while [[ $# -gt 0 ]]; do
|
|
51
|
+
case $1 in
|
|
52
|
+
--max-retries) MAX_RETRIES="$2"; shift 2 ;;
|
|
53
|
+
--max-turns) MAX_TURNS="$2"; shift 2 ;;
|
|
54
|
+
--label) ISSUE_FILTER="$2"; shift 2 ;;
|
|
55
|
+
--model) MODEL_FLAG="--model $2"; shift 2 ;;
|
|
56
|
+
-v|--version) echo "claude-queue v${VERSION}"; exit 0 ;;
|
|
57
|
+
-h|--help) head -16 "$0" | tail -14; exit 0 ;;
|
|
58
|
+
*) echo "Unknown option: $1"; exit 1 ;;
|
|
59
|
+
esac
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
log() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${BLUE}[claude-queue]${NC} $1"; }
|
|
63
|
+
log_success() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${GREEN}[claude-queue]${NC} $1"; }
|
|
64
|
+
log_warn() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${YELLOW}[claude-queue]${NC} $1"; }
|
|
65
|
+
log_error() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${RED}[claude-queue]${NC} $1"; }
|
|
66
|
+
log_header() { echo -e "\n${BOLD}═══ $1 ═══${NC}\n"; }
|
|
67
|
+
|
|
68
|
+
cleanup() {
|
|
69
|
+
local exit_code=$?
|
|
70
|
+
|
|
71
|
+
if [ -n "$CURRENT_ISSUE" ]; then
|
|
72
|
+
log_warn "Interrupted while working on issue #${CURRENT_ISSUE}"
|
|
73
|
+
gh issue edit "$CURRENT_ISSUE" --remove-label "$LABEL_PROGRESS" 2>/dev/null || true
|
|
74
|
+
gh issue edit "$CURRENT_ISSUE" --add-label "$LABEL_FAILED" 2>/dev/null || true
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [ $exit_code -ne 0 ] && [ ${#SOLVED_ISSUES[@]} -gt 0 ]; then
|
|
78
|
+
log_warn "Script interrupted but ${#SOLVED_ISSUES[@]} issue(s) were solved."
|
|
79
|
+
log_warn "Branch '${BRANCH}' has your commits. Push manually if needed."
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
log "Logs saved to: ${LOG_DIR}"
|
|
83
|
+
}
|
|
84
|
+
trap cleanup EXIT
|
|
85
|
+
|
|
86
|
+
preflight() {
|
|
87
|
+
log_header "Preflight Checks"
|
|
88
|
+
|
|
89
|
+
local failed=false
|
|
90
|
+
|
|
91
|
+
for cmd in gh claude git jq; do
|
|
92
|
+
if command -v "$cmd" &>/dev/null; then
|
|
93
|
+
log " $cmd ... found"
|
|
94
|
+
else
|
|
95
|
+
log_error " $cmd ... NOT FOUND"
|
|
96
|
+
failed=true
|
|
97
|
+
fi
|
|
98
|
+
done
|
|
99
|
+
|
|
100
|
+
if ! gh auth status &>/dev/null; then
|
|
101
|
+
log_error " gh auth ... not authenticated"
|
|
102
|
+
failed=true
|
|
103
|
+
else
|
|
104
|
+
log " gh auth ... ok"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
|
|
108
|
+
log_error " git repo ... not inside a git repository"
|
|
109
|
+
failed=true
|
|
110
|
+
else
|
|
111
|
+
log " git repo ... ok"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
|
|
115
|
+
log_error " working tree ... dirty (commit or stash changes first)"
|
|
116
|
+
failed=true
|
|
117
|
+
else
|
|
118
|
+
log " working tree ... clean"
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
if [ "$failed" = true ]; then
|
|
122
|
+
log_error "Preflight failed. Aborting."
|
|
123
|
+
exit 1
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
mkdir -p "$LOG_DIR"
|
|
127
|
+
log " log dir ... ${LOG_DIR}"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
ensure_labels() {
|
|
131
|
+
log "Creating labels (if missing)..."
|
|
132
|
+
|
|
133
|
+
gh label create "$LABEL_PROGRESS" --color "fbca04" --description "claude-queue is working on this" --force 2>/dev/null || true
|
|
134
|
+
gh label create "$LABEL_SOLVED" --color "0e8a16" --description "Solved by claude-queue" --force 2>/dev/null || true
|
|
135
|
+
gh label create "$LABEL_FAILED" --color "d93f0b" --description "claude-queue could not solve this" --force 2>/dev/null || true
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
setup_branch() {
|
|
139
|
+
log_header "Branch Setup"
|
|
140
|
+
|
|
141
|
+
local default_branch
|
|
142
|
+
default_branch=$(gh repo view --json defaultBranchRef -q '.defaultBranchRef.name')
|
|
143
|
+
log "Default branch: ${default_branch}"
|
|
144
|
+
|
|
145
|
+
git fetch origin "$default_branch" --quiet
|
|
146
|
+
|
|
147
|
+
if git show-ref --verify --quiet "refs/heads/${BRANCH}"; then
|
|
148
|
+
log_warn "Branch ${BRANCH} already exists, adding timestamp suffix"
|
|
149
|
+
BRANCH="${BRANCH}-${TIMESTAMP}"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
git checkout -b "$BRANCH" "origin/${default_branch}" --quiet
|
|
153
|
+
log_success "Created branch: ${BRANCH}"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fetch_issues() {
|
|
157
|
+
local args=(--state open --json "number,title,body,labels" --limit 200)
|
|
158
|
+
|
|
159
|
+
if [ -n "$ISSUE_FILTER" ]; then
|
|
160
|
+
args+=(--label "$ISSUE_FILTER")
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
gh issue list "${args[@]}"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
process_issue() {
|
|
167
|
+
local issue_number=$1
|
|
168
|
+
local issue_title="$2"
|
|
169
|
+
local attempt=0
|
|
170
|
+
local solved=false
|
|
171
|
+
local issue_log="${LOG_DIR}/issue-${issue_number}.md"
|
|
172
|
+
local checkpoint
|
|
173
|
+
checkpoint=$(git rev-parse HEAD)
|
|
174
|
+
|
|
175
|
+
CURRENT_ISSUE="$issue_number"
|
|
176
|
+
|
|
177
|
+
log_header "Issue #${issue_number}: ${issue_title}"
|
|
178
|
+
|
|
179
|
+
gh issue edit "$issue_number" \
|
|
180
|
+
--remove-label "$LABEL_SOLVED" \
|
|
181
|
+
--remove-label "$LABEL_FAILED" \
|
|
182
|
+
2>/dev/null || true
|
|
183
|
+
gh issue edit "$issue_number" --add-label "$LABEL_PROGRESS"
|
|
184
|
+
|
|
185
|
+
{
|
|
186
|
+
echo "# Issue #${issue_number}: ${issue_title}"
|
|
187
|
+
echo ""
|
|
188
|
+
echo "**Started:** $(date)"
|
|
189
|
+
echo ""
|
|
190
|
+
} > "$issue_log"
|
|
191
|
+
|
|
192
|
+
while [ "$attempt" -lt "$MAX_RETRIES" ] && [ "$solved" = false ]; do
|
|
193
|
+
attempt=$((attempt + 1))
|
|
194
|
+
log "Attempt ${attempt}/${MAX_RETRIES}"
|
|
195
|
+
|
|
196
|
+
git reset --hard "$checkpoint" --quiet 2>/dev/null || true
|
|
197
|
+
git clean -fd --quiet 2>/dev/null || true
|
|
198
|
+
|
|
199
|
+
echo "## Attempt ${attempt}" >> "$issue_log"
|
|
200
|
+
echo "" >> "$issue_log"
|
|
201
|
+
|
|
202
|
+
local custom_instructions=""
|
|
203
|
+
if [ -f ".claude-queue" ]; then
|
|
204
|
+
custom_instructions="
|
|
205
|
+
|
|
206
|
+
Additional project-specific instructions:
|
|
207
|
+
$(cat .claude-queue)"
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
local prompt
|
|
211
|
+
prompt="You are an automated assistant solving a GitHub issue in this repository.
|
|
212
|
+
|
|
213
|
+
First, read the full issue details by running:
|
|
214
|
+
gh issue view ${issue_number}
|
|
215
|
+
|
|
216
|
+
Then:
|
|
217
|
+
1. Explore the codebase to understand the project structure and conventions
|
|
218
|
+
2. Implement a complete, correct fix for the issue
|
|
219
|
+
3. Run any existing tests to verify your fix doesn't break anything
|
|
220
|
+
4. If tests fail because of your changes, fix them
|
|
221
|
+
|
|
222
|
+
Rules:
|
|
223
|
+
- Do NOT create any git commits
|
|
224
|
+
- Do NOT push anything
|
|
225
|
+
- Match the existing code style exactly
|
|
226
|
+
- Only change what is necessary to solve the issue
|
|
227
|
+
${custom_instructions}
|
|
228
|
+
If this issue does NOT require code changes (e.g. it's a question, a request for external action,
|
|
229
|
+
a finding, or something that can't be solved with code), output a line that says CLAUDE_QUEUE_NO_CODE
|
|
230
|
+
followed by an explanation of what needs to be done instead.
|
|
231
|
+
|
|
232
|
+
Otherwise, when you are done, output a line that says CLAUDE_QUEUE_SUMMARY followed by a 2-3 sentence
|
|
233
|
+
description of what you changed and why."
|
|
234
|
+
|
|
235
|
+
local attempt_log="${LOG_DIR}/issue-${issue_number}-attempt-${attempt}.log"
|
|
236
|
+
local claude_exit=0
|
|
237
|
+
|
|
238
|
+
# shellcheck disable=SC2086
|
|
239
|
+
claude -p "$prompt" \
|
|
240
|
+
--dangerously-skip-permissions \
|
|
241
|
+
--max-turns "$MAX_TURNS" \
|
|
242
|
+
$MODEL_FLAG \
|
|
243
|
+
> "$attempt_log" 2>&1 || claude_exit=$?
|
|
244
|
+
|
|
245
|
+
if [ "$claude_exit" -ne 0 ]; then
|
|
246
|
+
log_warn "Claude exited with code ${claude_exit}"
|
|
247
|
+
echo "**Claude exited with code ${claude_exit}**" >> "$issue_log"
|
|
248
|
+
echo "" >> "$issue_log"
|
|
249
|
+
continue
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
local no_code_reason
|
|
253
|
+
no_code_reason=$(grep -A 20 "CLAUDE_QUEUE_NO_CODE" "$attempt_log" 2>/dev/null | tail -n +2 | head -10 || echo "")
|
|
254
|
+
|
|
255
|
+
if [ -n "$no_code_reason" ]; then
|
|
256
|
+
log "Issue does not require code changes"
|
|
257
|
+
{
|
|
258
|
+
echo "### No Code Changes Required"
|
|
259
|
+
echo "$no_code_reason"
|
|
260
|
+
echo ""
|
|
261
|
+
} >> "$issue_log"
|
|
262
|
+
solved=true
|
|
263
|
+
log_success "Issue #${issue_number} handled (no code changes needed)"
|
|
264
|
+
break
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
local changed_files
|
|
268
|
+
changed_files=$(git diff --name-only 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null)
|
|
269
|
+
|
|
270
|
+
if [ -z "$changed_files" ]; then
|
|
271
|
+
log_warn "No file changes detected"
|
|
272
|
+
echo "**No file changes detected**" >> "$issue_log"
|
|
273
|
+
echo "" >> "$issue_log"
|
|
274
|
+
continue
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
log_success "Changes detected in:"
|
|
278
|
+
echo "$changed_files" | while IFS= read -r f; do
|
|
279
|
+
log " ${f}"
|
|
280
|
+
done
|
|
281
|
+
|
|
282
|
+
local summary
|
|
283
|
+
summary=$(grep -A 20 "CLAUDE_QUEUE_SUMMARY" "$attempt_log" 2>/dev/null | tail -n +2 | head -10 || echo "No summary provided.")
|
|
284
|
+
|
|
285
|
+
{
|
|
286
|
+
echo "### Summary"
|
|
287
|
+
echo "$summary"
|
|
288
|
+
echo ""
|
|
289
|
+
echo "### Changed Files"
|
|
290
|
+
echo "$changed_files" | while IFS= read -r f; do echo "- \`${f}\`"; done
|
|
291
|
+
echo ""
|
|
292
|
+
} >> "$issue_log"
|
|
293
|
+
|
|
294
|
+
git add -A
|
|
295
|
+
git commit -m "fix: resolve #${issue_number} - ${issue_title}
|
|
296
|
+
|
|
297
|
+
Automated fix by claude-queue.
|
|
298
|
+
Closes #${issue_number}" --quiet
|
|
299
|
+
|
|
300
|
+
solved=true
|
|
301
|
+
|
|
302
|
+
log_success "Solved issue #${issue_number} on attempt ${attempt}"
|
|
303
|
+
done
|
|
304
|
+
|
|
305
|
+
gh issue edit "$issue_number" --remove-label "$LABEL_PROGRESS" 2>/dev/null || true
|
|
306
|
+
|
|
307
|
+
{
|
|
308
|
+
echo "**Finished:** $(date)"
|
|
309
|
+
echo "**Status:** $([ "$solved" = true ] && echo "SOLVED" || echo "FAILED after ${MAX_RETRIES} attempts")"
|
|
310
|
+
} >> "$issue_log"
|
|
311
|
+
|
|
312
|
+
if [ "$solved" = true ]; then
|
|
313
|
+
gh issue edit "$issue_number" --add-label "$LABEL_SOLVED"
|
|
314
|
+
gh issue comment "$issue_number" --body-file "$issue_log" 2>/dev/null || true
|
|
315
|
+
SOLVED_ISSUES+=("${issue_number}|${issue_title}")
|
|
316
|
+
else
|
|
317
|
+
gh issue edit "$issue_number" --add-label "$LABEL_FAILED"
|
|
318
|
+
gh issue comment "$issue_number" --body "claude-queue failed to solve this issue after ${MAX_RETRIES} attempts." 2>/dev/null || true
|
|
319
|
+
FAILED_ISSUES+=("${issue_number}|${issue_title}")
|
|
320
|
+
git reset --hard "$checkpoint" --quiet 2>/dev/null || true
|
|
321
|
+
git clean -fd --quiet 2>/dev/null || true
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
CURRENT_ISSUE=""
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
review_and_fix() {
|
|
328
|
+
log_header "Final Review & Fix Pass"
|
|
329
|
+
|
|
330
|
+
local review_log="${LOG_DIR}/review.md"
|
|
331
|
+
|
|
332
|
+
local prompt
|
|
333
|
+
prompt="You are doing a final review pass on automated code changes in this repository.
|
|
334
|
+
|
|
335
|
+
Look at all uncommitted and recently committed changes on this branch. For each file that was modified:
|
|
336
|
+
1. Read the full file
|
|
337
|
+
2. Check for bugs, incomplete implementations, lazy code, missed edge cases, or style inconsistencies
|
|
338
|
+
3. Fix anything you find
|
|
339
|
+
|
|
340
|
+
Rules:
|
|
341
|
+
- Do NOT create any git commits
|
|
342
|
+
- Do NOT push anything
|
|
343
|
+
- Only fix real problems, don't refactor for style preferences
|
|
344
|
+
- Match the existing code style exactly
|
|
345
|
+
|
|
346
|
+
When you are done, output a line that says CLAUDE_QUEUE_REVIEW followed by a brief summary of what you fixed. If nothing needed fixing, say so."
|
|
347
|
+
|
|
348
|
+
# shellcheck disable=SC2086
|
|
349
|
+
claude -p "$prompt" \
|
|
350
|
+
--dangerously-skip-permissions \
|
|
351
|
+
--max-turns "$MAX_TURNS" \
|
|
352
|
+
$MODEL_FLAG \
|
|
353
|
+
> "$review_log" 2>&1 || true
|
|
354
|
+
|
|
355
|
+
local changed_files
|
|
356
|
+
changed_files=$(git diff --name-only 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null)
|
|
357
|
+
|
|
358
|
+
if [ -n "$changed_files" ]; then
|
|
359
|
+
log_success "Review pass made fixes:"
|
|
360
|
+
echo "$changed_files" | while IFS= read -r f; do
|
|
361
|
+
log " ${f}"
|
|
362
|
+
done
|
|
363
|
+
|
|
364
|
+
git add -A
|
|
365
|
+
git commit -m "chore: final review pass
|
|
366
|
+
|
|
367
|
+
Automated review and fixes by claude-queue." --quiet
|
|
368
|
+
else
|
|
369
|
+
log "Review pass found nothing to fix"
|
|
370
|
+
fi
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
create_pr() {
|
|
374
|
+
log_header "Creating Pull Request"
|
|
375
|
+
|
|
376
|
+
local default_branch
|
|
377
|
+
default_branch=$(gh repo view --json defaultBranchRef -q '.defaultBranchRef.name')
|
|
378
|
+
local elapsed=$(( $(date +%s) - START_TIME ))
|
|
379
|
+
local duration
|
|
380
|
+
duration="$(( elapsed / 3600 ))h $(( (elapsed % 3600) / 60 ))m $(( elapsed % 60 ))s"
|
|
381
|
+
local pr_body="${LOG_DIR}/pr-body.md"
|
|
382
|
+
local total_processed=$(( ${#SOLVED_ISSUES[@]} + ${#FAILED_ISSUES[@]} ))
|
|
383
|
+
|
|
384
|
+
{
|
|
385
|
+
echo "## claude-queue Run Summary"
|
|
386
|
+
echo ""
|
|
387
|
+
echo "| Metric | Value |"
|
|
388
|
+
echo "|--------|-------|"
|
|
389
|
+
echo "| Date | ${DATE} |"
|
|
390
|
+
echo "| Duration | ${duration} |"
|
|
391
|
+
echo "| Issues processed | ${total_processed} |"
|
|
392
|
+
echo "| Solved | ${#SOLVED_ISSUES[@]} |"
|
|
393
|
+
echo "| Failed | ${#FAILED_ISSUES[@]} |"
|
|
394
|
+
echo "| Skipped | ${#SKIPPED_ISSUES[@]} |"
|
|
395
|
+
echo ""
|
|
396
|
+
|
|
397
|
+
if [ ${#SOLVED_ISSUES[@]} -gt 0 ]; then
|
|
398
|
+
echo "### Solved Issues"
|
|
399
|
+
echo ""
|
|
400
|
+
echo "| Issue | Title |"
|
|
401
|
+
echo "|-------|-------|"
|
|
402
|
+
for entry in "${SOLVED_ISSUES[@]}"; do
|
|
403
|
+
local num="${entry%%|*}"
|
|
404
|
+
local title="${entry#*|}"
|
|
405
|
+
echo "| #${num} | ${title} |"
|
|
406
|
+
done
|
|
407
|
+
echo ""
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
if [ ${#FAILED_ISSUES[@]} -gt 0 ]; then
|
|
411
|
+
echo "### Failed Issues"
|
|
412
|
+
echo ""
|
|
413
|
+
echo "| Issue | Title |"
|
|
414
|
+
echo "|-------|-------|"
|
|
415
|
+
for entry in "${FAILED_ISSUES[@]}"; do
|
|
416
|
+
local num="${entry%%|*}"
|
|
417
|
+
local title="${entry#*|}"
|
|
418
|
+
echo "| #${num} | ${title} |"
|
|
419
|
+
done
|
|
420
|
+
echo ""
|
|
421
|
+
fi
|
|
422
|
+
|
|
423
|
+
echo "---"
|
|
424
|
+
echo ""
|
|
425
|
+
echo "### Chain Logs"
|
|
426
|
+
echo ""
|
|
427
|
+
|
|
428
|
+
for log_file in "${LOG_DIR}"/issue-*.md; do
|
|
429
|
+
if [ ! -f "$log_file" ]; then
|
|
430
|
+
continue
|
|
431
|
+
fi
|
|
432
|
+
|
|
433
|
+
local issue_num
|
|
434
|
+
issue_num=$(basename "$log_file" | grep -oE '[0-9]+')
|
|
435
|
+
|
|
436
|
+
echo "<details>"
|
|
437
|
+
echo "<summary>Issue #${issue_num} Log</summary>"
|
|
438
|
+
echo ""
|
|
439
|
+
head -c 40000 "$log_file"
|
|
440
|
+
echo ""
|
|
441
|
+
echo "</details>"
|
|
442
|
+
echo ""
|
|
443
|
+
done
|
|
444
|
+
} > "$pr_body"
|
|
445
|
+
|
|
446
|
+
local body_size
|
|
447
|
+
body_size=$(wc -c < "$pr_body")
|
|
448
|
+
if [ "$body_size" -gt 60000 ]; then
|
|
449
|
+
log_warn "PR body is ${body_size} bytes, truncating to fit GitHub limits"
|
|
450
|
+
head -c 59000 "$pr_body" > "${pr_body}.tmp"
|
|
451
|
+
{
|
|
452
|
+
echo ""
|
|
453
|
+
echo ""
|
|
454
|
+
echo "---"
|
|
455
|
+
echo "*Log truncated. Full logs available at: ${LOG_DIR}*"
|
|
456
|
+
} >> "${pr_body}.tmp"
|
|
457
|
+
mv "${pr_body}.tmp" "$pr_body"
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
git push origin "$BRANCH" --quiet
|
|
461
|
+
log_success "Pushed branch to origin"
|
|
462
|
+
|
|
463
|
+
local pr_url
|
|
464
|
+
pr_url=$(gh pr create \
|
|
465
|
+
--base "$default_branch" \
|
|
466
|
+
--head "$BRANCH" \
|
|
467
|
+
--title "claude-queue: Automated fixes (${DATE})" \
|
|
468
|
+
--body-file "$pr_body")
|
|
469
|
+
|
|
470
|
+
log_success "Pull request created: ${pr_url}"
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
main() {
|
|
474
|
+
echo -e "${BOLD}"
|
|
475
|
+
echo ' _ _ '
|
|
476
|
+
echo ' ___| | __ _ _ _ __| | ___ __ _ _ _ ___ _ _ ___'
|
|
477
|
+
echo ' / __| |/ _` | | | |/ _` |/ _ \_____ / _` | | | |/ _ \ | | |/ _ \'
|
|
478
|
+
echo ' | (__| | (_| | |_| | (_| | __/_____| (_| | |_| | __/ |_| | __/'
|
|
479
|
+
echo ' \___|_|\__,_|\__,_|\__,_|\___| \__, |\__,_|\___|\__,_|\___|'
|
|
480
|
+
echo ' |_| '
|
|
481
|
+
echo -e "${NC}"
|
|
482
|
+
echo -e " ${DIM}Automated GitHub issue solver${NC}"
|
|
483
|
+
echo ""
|
|
484
|
+
|
|
485
|
+
preflight
|
|
486
|
+
ensure_labels
|
|
487
|
+
setup_branch
|
|
488
|
+
|
|
489
|
+
log_header "Fetching Issues"
|
|
490
|
+
|
|
491
|
+
local issues
|
|
492
|
+
issues=$(fetch_issues)
|
|
493
|
+
local total
|
|
494
|
+
total=$(echo "$issues" | jq length)
|
|
495
|
+
|
|
496
|
+
if [ "$total" -eq 0 ]; then
|
|
497
|
+
log "No open issues found. Going back to sleep."
|
|
498
|
+
exit 0
|
|
499
|
+
fi
|
|
500
|
+
|
|
501
|
+
log "Found ${total} open issue(s)"
|
|
502
|
+
|
|
503
|
+
for i in $(seq 0 $((total - 1))); do
|
|
504
|
+
local number title labels
|
|
505
|
+
number=$(echo "$issues" | jq -r ".[$i].number")
|
|
506
|
+
title=$(echo "$issues" | jq -r ".[$i].title")
|
|
507
|
+
labels=$(echo "$issues" | jq -r "[.[$i].labels[].name] | join(\",\")" 2>/dev/null || echo "")
|
|
508
|
+
|
|
509
|
+
if echo "$labels" | grep -q "claude-queue:"; then
|
|
510
|
+
log "Skipping #${number} (already has a claude-queue label)"
|
|
511
|
+
SKIPPED_ISSUES+=("${number}|${title}")
|
|
512
|
+
continue
|
|
513
|
+
fi
|
|
514
|
+
|
|
515
|
+
process_issue "$number" "$title" || true
|
|
516
|
+
done
|
|
517
|
+
|
|
518
|
+
if [ ${#SOLVED_ISSUES[@]} -gt 0 ]; then
|
|
519
|
+
review_and_fix
|
|
520
|
+
create_pr
|
|
521
|
+
else
|
|
522
|
+
log_warn "No issues were solved. No PR created."
|
|
523
|
+
fi
|
|
524
|
+
|
|
525
|
+
log_header "claude-queue Complete"
|
|
526
|
+
|
|
527
|
+
local elapsed=$(( $(date +%s) - START_TIME ))
|
|
528
|
+
log "Duration: $(( elapsed / 3600 ))h $(( (elapsed % 3600) / 60 ))m $(( elapsed % 60 ))s"
|
|
529
|
+
log_success "Solved: ${#SOLVED_ISSUES[@]}"
|
|
530
|
+
if [ ${#FAILED_ISSUES[@]} -gt 0 ]; then
|
|
531
|
+
log_error "Failed: ${#FAILED_ISSUES[@]}"
|
|
532
|
+
fi
|
|
533
|
+
if [ ${#SKIPPED_ISSUES[@]} -gt 0 ]; then
|
|
534
|
+
log_warn "Skipped: ${#SKIPPED_ISSUES[@]}"
|
|
535
|
+
fi
|
|
536
|
+
log "Logs: ${LOG_DIR}"
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
main "$@"
|
package/package.json
CHANGED
|
@@ -1,63 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-queue",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
5
|
-
"type": "module",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Automated GitHub issue solver powered by Claude Code",
|
|
6
5
|
"bin": {
|
|
7
|
-
"claude-queue": "./
|
|
8
|
-
"claude-queue-mcp": "./dist/mcp.js"
|
|
9
|
-
},
|
|
10
|
-
"main": "./dist/server.js",
|
|
11
|
-
"exports": {
|
|
12
|
-
".": "./dist/server.js",
|
|
13
|
-
"./mcp": "./dist/mcp.js"
|
|
14
|
-
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist"
|
|
17
|
-
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsx scripts/build.ts",
|
|
20
|
-
"build:cli": "tsup src/index.ts --format esm --out-dir dist --clean --entry.cli=src/index.ts",
|
|
21
|
-
"typecheck": "tsc --noEmit",
|
|
22
|
-
"prepublishOnly": "npm run build"
|
|
6
|
+
"claude-queue": "./claude-queue.sh"
|
|
23
7
|
},
|
|
24
8
|
"keywords": [
|
|
9
|
+
"github",
|
|
10
|
+
"issues",
|
|
11
|
+
"automation",
|
|
25
12
|
"claude",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"task-management",
|
|
30
|
-
"ai-assistant"
|
|
13
|
+
"ai",
|
|
14
|
+
"claude-queue",
|
|
15
|
+
"devtools"
|
|
31
16
|
],
|
|
32
|
-
"author": "Kamran Ahmed
|
|
17
|
+
"author": "Kamran Ahmed",
|
|
33
18
|
"license": "MIT",
|
|
34
|
-
"homepage": "https://github.com/kamranahmedse/claude-queue#readme",
|
|
35
19
|
"repository": {
|
|
36
20
|
"type": "git",
|
|
37
|
-
"url": "
|
|
38
|
-
},
|
|
39
|
-
"bugs": {
|
|
40
|
-
"url": "https://github.com/kamranahmedse/claude-queue/issues"
|
|
21
|
+
"url": "https://github.com/kamranahmedse/claude-queue"
|
|
41
22
|
},
|
|
23
|
+
"homepage": "https://github.com/kamranahmedse/claude-queue#readme",
|
|
42
24
|
"engines": {
|
|
43
|
-
"node": ">=
|
|
44
|
-
},
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
47
|
-
"better-sqlite3": "^12.5.0",
|
|
48
|
-
"commander": "^14.0.2",
|
|
49
|
-
"cors": "^2.8.5",
|
|
50
|
-
"express": "^5.2.1",
|
|
51
|
-
"http-proxy-middleware": "^3.0.5",
|
|
52
|
-
"nanoid": "^5.1.6"
|
|
53
|
-
},
|
|
54
|
-
"devDependencies": {
|
|
55
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
56
|
-
"@types/cors": "^2.8.19",
|
|
57
|
-
"@types/express": "^5.0.6",
|
|
58
|
-
"@types/node": "^25.0.3",
|
|
59
|
-
"tsup": "^8.5.1",
|
|
60
|
-
"tsx": "^4.21.0",
|
|
61
|
-
"typescript": "^5.9.3"
|
|
25
|
+
"node": ">=16"
|
|
62
26
|
}
|
|
63
27
|
}
|