claude-queue 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -48
- package/claude-queue.sh +455 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# claude-queue
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A CLI tool that solves GitHub issues using [Claude Code](https://docs.anthropic.com/en/docs/claude-code). It picks up open issues from your repo, solves them one by one, and opens a pull request with all the fixes. It can also create well-structured GitHub issues from a text description or interactive interview.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The typical workflow is: open issues for whatever you need done, run `claude-queue`, and come back to a pull request with everything solved. I usually do this at night and review the PR in the morning.
|
|
6
|
+
|
|
7
|
+
Issues don't have to be code changes — they can be investigative tasks like "figure out why the API is slow and document what you find" or "audit the codebase for accessibility issues". Claude will research, document findings, and commit whatever it produces.
|
|
6
8
|
|
|
7
9
|
## Prerequisites
|
|
8
10
|
|
|
@@ -16,7 +18,7 @@ claude-queue fetches all open issues from your repo, uses [Claude Code](https://
|
|
|
16
18
|
npm install -g claude-queue
|
|
17
19
|
```
|
|
18
20
|
|
|
19
|
-
Or run directly
|
|
21
|
+
Or run directly:
|
|
20
22
|
|
|
21
23
|
```bash
|
|
22
24
|
npx claude-queue
|
|
@@ -24,23 +26,20 @@ npx claude-queue
|
|
|
24
26
|
|
|
25
27
|
## Usage
|
|
26
28
|
|
|
29
|
+
### Solving issues
|
|
30
|
+
|
|
27
31
|
Run from inside any git repository with GitHub issues:
|
|
28
32
|
|
|
29
33
|
```bash
|
|
30
34
|
claude-queue
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
### Options
|
|
34
|
-
|
|
35
37
|
| Flag | Default | Description |
|
|
36
38
|
|------|---------|-------------|
|
|
37
39
|
| `--max-retries N` | `3` | Max retry attempts per issue before marking it failed |
|
|
38
|
-
| `--max-turns N` | `50` | Max Claude Code turns per attempt
|
|
39
|
-
| `--label LABEL` | all issues | Only process issues
|
|
40
|
+
| `--max-turns N` | `50` | Max Claude Code turns per attempt |
|
|
41
|
+
| `--label LABEL` | all issues | Only process issues with this label |
|
|
40
42
|
| `--model MODEL` | CLI default | Claude model to use (e.g. `claude-sonnet-4-5-20250929`) |
|
|
41
|
-
| `-h, --help` | | Show help |
|
|
42
|
-
|
|
43
|
-
### Examples
|
|
44
43
|
|
|
45
44
|
```bash
|
|
46
45
|
# Solve all open issues
|
|
@@ -53,6 +52,48 @@ claude-queue --label bug
|
|
|
53
52
|
claude-queue --max-retries 5 --model claude-sonnet-4-5-20250929
|
|
54
53
|
```
|
|
55
54
|
|
|
55
|
+
### Creating issues
|
|
56
|
+
|
|
57
|
+
Generate GitHub issues from a text description or an interactive interview with Claude.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
claude-queue create "Add dark mode and fix the login bug"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
There are three ways to provide input:
|
|
64
|
+
|
|
65
|
+
1. **Inline text** — pass your description as an argument
|
|
66
|
+
2. **Stdin** — run `claude-queue create` with no arguments, type or paste your text, then press Ctrl+D
|
|
67
|
+
3. **Interactive** — run `claude-queue create -i` and Claude will ask clarifying questions before generating issues
|
|
68
|
+
|
|
69
|
+
Claude decomposes the input into individual issues with titles, markdown bodies, and labels (reusing existing repo labels where possible). You get a preview before anything is created.
|
|
70
|
+
|
|
71
|
+
| Flag | Default | Description |
|
|
72
|
+
|------|---------|-------------|
|
|
73
|
+
| `-i, --interactive` | off | Interview mode — Claude asks clarifying questions first |
|
|
74
|
+
| `--label LABEL` | none | Add this label to every created issue |
|
|
75
|
+
| `--model MODEL` | CLI default | Claude model to use |
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Interactive mode
|
|
79
|
+
claude-queue create -i
|
|
80
|
+
|
|
81
|
+
# Add a label to all created issues
|
|
82
|
+
claude-queue create --label backlog "Refactor the auth module and add rate limiting"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Create then solve workflow
|
|
86
|
+
|
|
87
|
+
The `--label` flag on both commands lets you create a pipeline where `create` plans the issues and `claude-queue` solves them:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Plan
|
|
91
|
+
claude-queue create --label nightshift "Add dark mode and fix the login bug"
|
|
92
|
+
|
|
93
|
+
# Solve
|
|
94
|
+
claude-queue --label nightshift
|
|
95
|
+
```
|
|
96
|
+
|
|
56
97
|
## Configuration
|
|
57
98
|
|
|
58
99
|
Create a `.claude-queue` file in your repo root to add custom instructions to every issue prompt:
|
|
@@ -63,64 +104,58 @@ Use TypeScript strict mode.
|
|
|
63
104
|
Never modify files in the src/legacy/ directory.
|
|
64
105
|
```
|
|
65
106
|
|
|
66
|
-
These instructions are appended to the prompt Claude receives for each issue.
|
|
107
|
+
These instructions are appended to the prompt Claude receives for each issue. Useful for project-specific conventions that aren't in `CLAUDE.md`.
|
|
67
108
|
|
|
68
109
|
## How It Works
|
|
69
110
|
|
|
70
|
-
###
|
|
111
|
+
### Preflight
|
|
71
112
|
|
|
72
|
-
Verifies all dependencies
|
|
113
|
+
Verifies all dependencies (`gh`, `claude`, `git`, `jq`), checks that `gh` is authenticated, and ensures the working tree is clean.
|
|
73
114
|
|
|
74
|
-
###
|
|
115
|
+
### Label setup
|
|
75
116
|
|
|
76
117
|
Creates three labels on the repo (skips if they already exist):
|
|
77
118
|
|
|
78
|
-
| Label |
|
|
79
|
-
|
|
80
|
-
| `claude-queue:in-progress` |
|
|
81
|
-
| `claude-queue:solved` |
|
|
82
|
-
| `claude-queue:failed` |
|
|
119
|
+
| Label | Meaning |
|
|
120
|
+
|-------|---------|
|
|
121
|
+
| `claude-queue:in-progress` | Currently being worked on |
|
|
122
|
+
| `claude-queue:solved` | Successfully fixed |
|
|
123
|
+
| `claude-queue:failed` | Could not be solved after all retries |
|
|
83
124
|
|
|
84
|
-
|
|
125
|
+
### Branching
|
|
85
126
|
|
|
86
|
-
|
|
127
|
+
Creates a branch `claude-queue/YYYY-MM-DD` off your default branch. All fixes go into this one branch. If the branch already exists, a timestamp suffix is added.
|
|
87
128
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
### 4. Issue Processing
|
|
129
|
+
### Issue processing
|
|
91
130
|
|
|
92
131
|
For each open issue (up to 200, oldest first):
|
|
93
132
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
133
|
+
1. **Skip** — issues with any `claude-queue:*` label are skipped. Remove the label to re-process.
|
|
134
|
+
2. **Label** — marks the issue `claude-queue:in-progress`.
|
|
135
|
+
3. **Solve** — launches Claude Code with a prompt to read the issue, explore the codebase, implement a fix, and run tests.
|
|
136
|
+
4. **Evaluate** — if Claude produced file changes, they are committed. If not, the attempt is retried.
|
|
137
|
+
5. **Retry** — on failure, the working tree is reset and Claude gets a fresh context. Up to 3 attempts (configurable with `--max-retries`).
|
|
138
|
+
6. **Result** — marks the issue `claude-queue:solved` or `claude-queue:failed`.
|
|
139
|
+
|
|
140
|
+
Issues are solved sequentially so later fixes build on earlier ones within a single branch.
|
|
141
|
+
|
|
142
|
+
### Review pass
|
|
104
143
|
|
|
105
|
-
|
|
144
|
+
After all issues are processed, Claude does a second pass reviewing all committed changes for bugs, incomplete implementations, and style issues — fixing anything it finds.
|
|
106
145
|
|
|
107
|
-
###
|
|
146
|
+
### Pull request
|
|
108
147
|
|
|
109
|
-
Once
|
|
148
|
+
Once done, the branch is pushed and a PR is opened with:
|
|
110
149
|
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
- **Chain logs** — collapsible per-issue logs showing Claude's full output for each attempt
|
|
150
|
+
- Summary table with solved/failed/skipped counts and run duration
|
|
151
|
+
- Tables of solved and failed issues with links
|
|
152
|
+
- Collapsible per-issue logs showing Claude's full output
|
|
115
153
|
|
|
116
|
-
|
|
154
|
+
No PR is created if nothing was solved.
|
|
117
155
|
|
|
118
|
-
### Interruption
|
|
156
|
+
### Interruption handling
|
|
119
157
|
|
|
120
|
-
If
|
|
121
|
-
- Removes the `claude-queue:in-progress` label from the current issue
|
|
122
|
-
- Marks it as `claude-queue:failed`
|
|
123
|
-
- Prints where your commits and logs are so nothing is lost
|
|
158
|
+
If interrupted (Ctrl+C, SIGTERM), the script removes the `claude-queue:in-progress` label from the current issue, marks it as failed, and prints where your commits and logs are.
|
|
124
159
|
|
|
125
160
|
## Logs
|
|
126
161
|
|
|
@@ -133,7 +168,7 @@ Full logs for each run are saved to `/tmp/claude-queue-DATE-TIMESTAMP/`:
|
|
|
133
168
|
├── issue-42-attempt-2.log # Raw Claude output, attempt 2
|
|
134
169
|
├── issue-57.md
|
|
135
170
|
├── issue-57-attempt-1.log
|
|
136
|
-
└── pr-body.md #
|
|
171
|
+
└── pr-body.md # Generated PR description
|
|
137
172
|
```
|
|
138
173
|
|
|
139
174
|
## License
|
package/claude-queue.sh
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
|
-
# claude-queue - Automated GitHub issue solver
|
|
3
|
+
# claude-queue - Automated GitHub issue solver & creator
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# Commands:
|
|
6
|
+
# claude-queue [options] Solve open issues (default)
|
|
7
|
+
# claude-queue create [options] Create issues from text or interactively
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
# claude-queue [options]
|
|
10
|
-
#
|
|
11
|
-
# Options:
|
|
9
|
+
# Solve options:
|
|
12
10
|
# --max-retries N Max retries per issue (default: 3)
|
|
13
11
|
# --max-turns N Max Claude turns per attempt (default: 50)
|
|
14
12
|
# --label LABEL Only process issues with this label
|
|
15
13
|
# --model MODEL Claude model to use
|
|
16
14
|
# -v, --version Show version
|
|
17
15
|
# -h, --help Show this help message
|
|
16
|
+
#
|
|
17
|
+
# Create options:
|
|
18
|
+
# -i, --interactive Interview mode (Claude asks questions)
|
|
19
|
+
# --label LABEL Add this label to all created issues
|
|
20
|
+
# --model MODEL Claude model to use
|
|
21
|
+
# -h, --help Show help for create
|
|
18
22
|
|
|
19
23
|
set -euo pipefail
|
|
20
24
|
|
|
@@ -45,19 +49,41 @@ declare -a SOLVED_ISSUES=()
|
|
|
45
49
|
declare -a FAILED_ISSUES=()
|
|
46
50
|
declare -a SKIPPED_ISSUES=()
|
|
47
51
|
CURRENT_ISSUE=""
|
|
52
|
+
CHILD_PID=""
|
|
48
53
|
START_TIME=$(date +%s)
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
show_help() {
|
|
56
|
+
echo "claude-queue v${VERSION} — Automated GitHub issue solver & creator"
|
|
57
|
+
echo ""
|
|
58
|
+
echo "Usage:"
|
|
59
|
+
echo " claude-queue [options] Solve open issues (default)"
|
|
60
|
+
echo " claude-queue create [options] [text] Create issues from text or interactively"
|
|
61
|
+
echo ""
|
|
62
|
+
echo "Solve options:"
|
|
63
|
+
echo " --max-retries N Max retries per issue (default: 3)"
|
|
64
|
+
echo " --max-turns N Max Claude turns per attempt (default: 50)"
|
|
65
|
+
echo " --label LABEL Only process issues with this label"
|
|
66
|
+
echo " --model MODEL Claude model to use"
|
|
67
|
+
echo " -v, --version Show version"
|
|
68
|
+
echo " -h, --help Show this help message"
|
|
69
|
+
echo ""
|
|
70
|
+
echo "Run 'claude-queue create --help' for create options."
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
show_create_help() {
|
|
74
|
+
echo "claude-queue create — Generate GitHub issues from text or an interactive interview"
|
|
75
|
+
echo ""
|
|
76
|
+
echo "Usage:"
|
|
77
|
+
echo " claude-queue create \"description\" Create issues from inline text"
|
|
78
|
+
echo " claude-queue create Prompt for text input (Ctrl+D to finish)"
|
|
79
|
+
echo " claude-queue create -i Interactive interview mode"
|
|
80
|
+
echo ""
|
|
81
|
+
echo "Options:"
|
|
82
|
+
echo " -i, --interactive Interview mode (Claude asks clarifying questions first)"
|
|
83
|
+
echo " --label LABEL Add this label to every created issue"
|
|
84
|
+
echo " --model MODEL Claude model to use"
|
|
85
|
+
echo " -h, --help Show this help message"
|
|
86
|
+
}
|
|
61
87
|
|
|
62
88
|
log() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${BLUE}[claude-queue]${NC} $1"; }
|
|
63
89
|
log_success() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${GREEN}[claude-queue]${NC} $1"; }
|
|
@@ -79,8 +105,21 @@ cleanup() {
|
|
|
79
105
|
log_warn "Branch '${BRANCH}' has your commits. Push manually if needed."
|
|
80
106
|
fi
|
|
81
107
|
|
|
82
|
-
|
|
108
|
+
if [ -d "$LOG_DIR" ]; then
|
|
109
|
+
log "Logs saved to: ${LOG_DIR}"
|
|
110
|
+
fi
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
handle_interrupt() {
|
|
114
|
+
if [ -n "$CHILD_PID" ] && kill -0 "$CHILD_PID" 2>/dev/null; then
|
|
115
|
+
kill -TERM "$CHILD_PID" 2>/dev/null || true
|
|
116
|
+
wait "$CHILD_PID" 2>/dev/null || true
|
|
117
|
+
fi
|
|
118
|
+
CHILD_PID=""
|
|
119
|
+
exit 130
|
|
83
120
|
}
|
|
121
|
+
|
|
122
|
+
trap handle_interrupt INT TERM
|
|
84
123
|
trap cleanup EXIT
|
|
85
124
|
|
|
86
125
|
preflight() {
|
|
@@ -240,7 +279,10 @@ description of what you changed and why."
|
|
|
240
279
|
--dangerously-skip-permissions \
|
|
241
280
|
--max-turns "$MAX_TURNS" \
|
|
242
281
|
$MODEL_FLAG \
|
|
243
|
-
> "$attempt_log" 2>&1
|
|
282
|
+
> "$attempt_log" 2>&1 &
|
|
283
|
+
CHILD_PID=$!
|
|
284
|
+
wait "$CHILD_PID" || claude_exit=$?
|
|
285
|
+
CHILD_PID=""
|
|
244
286
|
|
|
245
287
|
if [ "$claude_exit" -ne 0 ]; then
|
|
246
288
|
log_warn "Claude exited with code ${claude_exit}"
|
|
@@ -350,7 +392,10 @@ When you are done, output a line that says CLAUDE_QUEUE_REVIEW followed by a bri
|
|
|
350
392
|
--dangerously-skip-permissions \
|
|
351
393
|
--max-turns "$MAX_TURNS" \
|
|
352
394
|
$MODEL_FLAG \
|
|
353
|
-
> "$review_log" 2>&1
|
|
395
|
+
> "$review_log" 2>&1 &
|
|
396
|
+
CHILD_PID=$!
|
|
397
|
+
wait "$CHILD_PID" 2>/dev/null || true
|
|
398
|
+
CHILD_PID=""
|
|
354
399
|
|
|
355
400
|
local changed_files
|
|
356
401
|
changed_files=$(git diff --name-only 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null)
|
|
@@ -536,4 +581,392 @@ main() {
|
|
|
536
581
|
log "Logs: ${LOG_DIR}"
|
|
537
582
|
}
|
|
538
583
|
|
|
539
|
-
|
|
584
|
+
create_preflight() {
|
|
585
|
+
log_header "Preflight Checks"
|
|
586
|
+
|
|
587
|
+
local failed=false
|
|
588
|
+
|
|
589
|
+
for cmd in gh claude jq; do
|
|
590
|
+
if command -v "$cmd" &>/dev/null; then
|
|
591
|
+
log " $cmd ... found"
|
|
592
|
+
else
|
|
593
|
+
log_error " $cmd ... NOT FOUND"
|
|
594
|
+
failed=true
|
|
595
|
+
fi
|
|
596
|
+
done
|
|
597
|
+
|
|
598
|
+
if ! gh auth status &>/dev/null; then
|
|
599
|
+
log_error " gh auth ... not authenticated"
|
|
600
|
+
failed=true
|
|
601
|
+
else
|
|
602
|
+
log " gh auth ... ok"
|
|
603
|
+
fi
|
|
604
|
+
|
|
605
|
+
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
|
|
606
|
+
log_error " git repo ... not inside a git repository"
|
|
607
|
+
failed=true
|
|
608
|
+
else
|
|
609
|
+
log " git repo ... ok"
|
|
610
|
+
fi
|
|
611
|
+
|
|
612
|
+
if [ "$failed" = true ]; then
|
|
613
|
+
log_error "Preflight failed. Aborting."
|
|
614
|
+
exit 1
|
|
615
|
+
fi
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
get_repo_labels() {
|
|
619
|
+
gh label list --json name -q '.[].name' 2>/dev/null | paste -sd ',' -
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
extract_json() {
|
|
623
|
+
local input="$1"
|
|
624
|
+
local json
|
|
625
|
+
|
|
626
|
+
json=$(echo "$input" | sed -n '/^```\(json\)\?$/,/^```$/{ /^```/d; p; }')
|
|
627
|
+
if [ -z "$json" ]; then
|
|
628
|
+
json="$input"
|
|
629
|
+
fi
|
|
630
|
+
|
|
631
|
+
if echo "$json" | jq empty 2>/dev/null; then
|
|
632
|
+
echo "$json"
|
|
633
|
+
return 0
|
|
634
|
+
fi
|
|
635
|
+
|
|
636
|
+
json=$(echo "$input" | grep -o '\[.*\]' | head -1)
|
|
637
|
+
if [ -n "$json" ] && echo "$json" | jq empty 2>/dev/null; then
|
|
638
|
+
echo "$json"
|
|
639
|
+
return 0
|
|
640
|
+
fi
|
|
641
|
+
|
|
642
|
+
return 1
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
create_from_text() {
|
|
646
|
+
local user_text="$1"
|
|
647
|
+
local repo_labels
|
|
648
|
+
repo_labels=$(get_repo_labels)
|
|
649
|
+
|
|
650
|
+
log "Analyzing text and generating issues..."
|
|
651
|
+
|
|
652
|
+
local prompt
|
|
653
|
+
prompt="You are a GitHub issue planner. The user wants to create issues for a repository.
|
|
654
|
+
|
|
655
|
+
Existing labels in the repo: ${repo_labels}
|
|
656
|
+
|
|
657
|
+
The user's description:
|
|
658
|
+
${user_text}
|
|
659
|
+
|
|
660
|
+
Decompose this into a JSON array of well-structured GitHub issues. Each issue should have:
|
|
661
|
+
- \"title\": a clear, concise issue title
|
|
662
|
+
- \"body\": a detailed issue body in markdown (include acceptance criteria where appropriate)
|
|
663
|
+
- \"labels\": an array of label strings (reuse existing repo labels when they fit, or suggest new ones)
|
|
664
|
+
|
|
665
|
+
Rules:
|
|
666
|
+
- Create separate issues for logically distinct tasks
|
|
667
|
+
- Each issue should be independently actionable
|
|
668
|
+
- Use clear, imperative titles (e.g. \"Add dark mode toggle to settings page\")
|
|
669
|
+
- If the description is vague, make reasonable assumptions and note them in the body
|
|
670
|
+
|
|
671
|
+
Output ONLY the JSON array, no other text."
|
|
672
|
+
|
|
673
|
+
local output
|
|
674
|
+
# shellcheck disable=SC2086
|
|
675
|
+
output=$(claude -p "$prompt" $MODEL_FLAG 2>/dev/null)
|
|
676
|
+
|
|
677
|
+
local json
|
|
678
|
+
if ! json=$(extract_json "$output"); then
|
|
679
|
+
log_error "Failed to parse Claude's response as JSON"
|
|
680
|
+
log_error "Raw output:"
|
|
681
|
+
echo "$output"
|
|
682
|
+
exit 1
|
|
683
|
+
fi
|
|
684
|
+
|
|
685
|
+
local count
|
|
686
|
+
count=$(echo "$json" | jq length)
|
|
687
|
+
if [ "$count" -eq 0 ]; then
|
|
688
|
+
log_error "No issues were generated"
|
|
689
|
+
exit 1
|
|
690
|
+
fi
|
|
691
|
+
|
|
692
|
+
echo "$json"
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
create_interactive() {
|
|
696
|
+
local repo_labels
|
|
697
|
+
repo_labels=$(get_repo_labels)
|
|
698
|
+
local conversation=""
|
|
699
|
+
local max_turns=10
|
|
700
|
+
local turn=0
|
|
701
|
+
|
|
702
|
+
local system_prompt="You are a GitHub issue planner conducting an interview to understand what issues to create for a repository.
|
|
703
|
+
|
|
704
|
+
Existing labels in the repo: ${repo_labels}
|
|
705
|
+
|
|
706
|
+
Your job:
|
|
707
|
+
1. Ask focused questions to understand what the user wants to build or fix
|
|
708
|
+
2. Ask about priorities, scope, and acceptance criteria
|
|
709
|
+
3. When you have enough information, output the marker CLAUDE_QUEUE_READY on its own line, followed by a JSON array of issues
|
|
710
|
+
|
|
711
|
+
Each issue in the JSON array should have:
|
|
712
|
+
- \"title\": a clear, concise issue title
|
|
713
|
+
- \"body\": a detailed issue body in markdown
|
|
714
|
+
- \"labels\": an array of label strings (reuse existing repo labels when they fit)
|
|
715
|
+
|
|
716
|
+
Rules:
|
|
717
|
+
- Ask one question at a time
|
|
718
|
+
- Keep questions short and specific
|
|
719
|
+
- After 2-3 questions you should have enough context — don't over-interview
|
|
720
|
+
- If the user says \"done\", immediately generate the issues with what you know
|
|
721
|
+
- Output ONLY your question text (no JSON) until you're ready to generate issues
|
|
722
|
+
- When ready, output CLAUDE_QUEUE_READY on its own line followed by ONLY the JSON array"
|
|
723
|
+
|
|
724
|
+
echo -e "${BOLD}Interactive issue creation${NC}"
|
|
725
|
+
echo -e "${DIM}Answer Claude's questions. Type 'done' to generate issues at any time.${NC}"
|
|
726
|
+
echo ""
|
|
727
|
+
|
|
728
|
+
while [ "$turn" -lt "$max_turns" ]; do
|
|
729
|
+
turn=$((turn + 1))
|
|
730
|
+
|
|
731
|
+
local prompt
|
|
732
|
+
if [ -z "$conversation" ]; then
|
|
733
|
+
prompt="${system_prompt}
|
|
734
|
+
|
|
735
|
+
Start by asking your first question."
|
|
736
|
+
else
|
|
737
|
+
prompt="${system_prompt}
|
|
738
|
+
|
|
739
|
+
Conversation so far:
|
|
740
|
+
${conversation}
|
|
741
|
+
|
|
742
|
+
Continue the interview or, if you have enough information, output CLAUDE_QUEUE_READY followed by the JSON array."
|
|
743
|
+
fi
|
|
744
|
+
|
|
745
|
+
local output
|
|
746
|
+
# shellcheck disable=SC2086
|
|
747
|
+
output=$(claude -p "$prompt" $MODEL_FLAG 2>/dev/null)
|
|
748
|
+
|
|
749
|
+
if echo "$output" | grep -q "CLAUDE_QUEUE_READY"; then
|
|
750
|
+
local json_part
|
|
751
|
+
json_part=$(echo "$output" | sed -n '/CLAUDE_QUEUE_READY/,$ p' | tail -n +2)
|
|
752
|
+
|
|
753
|
+
local json
|
|
754
|
+
if ! json=$(extract_json "$json_part"); then
|
|
755
|
+
log_error "Failed to parse generated issues as JSON"
|
|
756
|
+
exit 1
|
|
757
|
+
fi
|
|
758
|
+
|
|
759
|
+
echo "$json"
|
|
760
|
+
return 0
|
|
761
|
+
fi
|
|
762
|
+
|
|
763
|
+
echo -e "${BLUE}Claude:${NC} ${output}"
|
|
764
|
+
echo ""
|
|
765
|
+
|
|
766
|
+
local user_input
|
|
767
|
+
read -r -p "You: " user_input
|
|
768
|
+
|
|
769
|
+
if [ "$user_input" = "done" ]; then
|
|
770
|
+
conversation="${conversation}
|
|
771
|
+
Claude: ${output}
|
|
772
|
+
User: Please generate the issues now with what you know."
|
|
773
|
+
|
|
774
|
+
local final_prompt="${system_prompt}
|
|
775
|
+
|
|
776
|
+
Conversation so far:
|
|
777
|
+
${conversation}
|
|
778
|
+
|
|
779
|
+
The user wants you to generate the issues now. Output CLAUDE_QUEUE_READY followed by the JSON array."
|
|
780
|
+
|
|
781
|
+
local final_output
|
|
782
|
+
# shellcheck disable=SC2086
|
|
783
|
+
final_output=$(claude -p "$final_prompt" $MODEL_FLAG 2>/dev/null)
|
|
784
|
+
|
|
785
|
+
local final_json_part
|
|
786
|
+
final_json_part=$(echo "$final_output" | sed -n '/CLAUDE_QUEUE_READY/,$ p' | tail -n +2)
|
|
787
|
+
if [ -z "$final_json_part" ]; then
|
|
788
|
+
final_json_part="$final_output"
|
|
789
|
+
fi
|
|
790
|
+
|
|
791
|
+
local json
|
|
792
|
+
if ! json=$(extract_json "$final_json_part"); then
|
|
793
|
+
log_error "Failed to parse generated issues as JSON"
|
|
794
|
+
exit 1
|
|
795
|
+
fi
|
|
796
|
+
|
|
797
|
+
echo "$json"
|
|
798
|
+
return 0
|
|
799
|
+
fi
|
|
800
|
+
|
|
801
|
+
conversation="${conversation}
|
|
802
|
+
Claude: ${output}
|
|
803
|
+
User: ${user_input}"
|
|
804
|
+
done
|
|
805
|
+
|
|
806
|
+
log_warn "Reached maximum interview turns, generating issues with current information..."
|
|
807
|
+
|
|
808
|
+
local final_prompt="${system_prompt}
|
|
809
|
+
|
|
810
|
+
Conversation so far:
|
|
811
|
+
${conversation}
|
|
812
|
+
|
|
813
|
+
You've reached the maximum number of questions. Output CLAUDE_QUEUE_READY followed by the JSON array now."
|
|
814
|
+
|
|
815
|
+
local final_output
|
|
816
|
+
# shellcheck disable=SC2086
|
|
817
|
+
final_output=$(claude -p "$final_prompt" $MODEL_FLAG 2>/dev/null)
|
|
818
|
+
|
|
819
|
+
local final_json_part
|
|
820
|
+
final_json_part=$(echo "$final_output" | sed -n '/CLAUDE_QUEUE_READY/,$ p' | tail -n +2)
|
|
821
|
+
if [ -z "$final_json_part" ]; then
|
|
822
|
+
final_json_part="$final_output"
|
|
823
|
+
fi
|
|
824
|
+
|
|
825
|
+
local json
|
|
826
|
+
if ! json=$(extract_json "$final_json_part"); then
|
|
827
|
+
log_error "Failed to parse generated issues as JSON"
|
|
828
|
+
exit 1
|
|
829
|
+
fi
|
|
830
|
+
|
|
831
|
+
echo "$json"
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
preview_issues() {
|
|
835
|
+
local json="$1"
|
|
836
|
+
local count
|
|
837
|
+
count=$(echo "$json" | jq length)
|
|
838
|
+
|
|
839
|
+
echo ""
|
|
840
|
+
echo -e "${BOLD}═══ Issue Preview ═══${NC}"
|
|
841
|
+
echo ""
|
|
842
|
+
|
|
843
|
+
for i in $(seq 0 $((count - 1))); do
|
|
844
|
+
local title labels body
|
|
845
|
+
title=$(echo "$json" | jq -r ".[$i].title")
|
|
846
|
+
labels=$(echo "$json" | jq -r ".[$i].labels // [] | join(\", \")")
|
|
847
|
+
body=$(echo "$json" | jq -r ".[$i].body" | head -3)
|
|
848
|
+
|
|
849
|
+
echo -e " ${BOLD}$((i + 1)). ${title}${NC}"
|
|
850
|
+
if [ -n "$labels" ]; then
|
|
851
|
+
echo -e " ${DIM}Labels: ${labels}${NC}"
|
|
852
|
+
fi
|
|
853
|
+
echo -e " ${DIM}$(echo "$body" | head -1)${NC}"
|
|
854
|
+
echo ""
|
|
855
|
+
done
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
confirm_and_create() {
|
|
859
|
+
local json="$1"
|
|
860
|
+
local extra_label="$2"
|
|
861
|
+
local count
|
|
862
|
+
count=$(echo "$json" | jq length)
|
|
863
|
+
|
|
864
|
+
local prompt_text="Create ${count} issue(s)? [y/N] "
|
|
865
|
+
read -r -p "$prompt_text" confirm
|
|
866
|
+
|
|
867
|
+
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
|
868
|
+
log "Cancelled."
|
|
869
|
+
exit 0
|
|
870
|
+
fi
|
|
871
|
+
|
|
872
|
+
echo ""
|
|
873
|
+
|
|
874
|
+
for i in $(seq 0 $((count - 1))); do
|
|
875
|
+
local title body
|
|
876
|
+
title=$(echo "$json" | jq -r ".[$i].title")
|
|
877
|
+
body=$(echo "$json" | jq -r ".[$i].body")
|
|
878
|
+
|
|
879
|
+
local label_args=()
|
|
880
|
+
local issue_labels
|
|
881
|
+
issue_labels=$(echo "$json" | jq -r ".[$i].labels // [] | .[]")
|
|
882
|
+
while IFS= read -r lbl; do
|
|
883
|
+
if [ -n "$lbl" ]; then
|
|
884
|
+
label_args+=(--label "$lbl")
|
|
885
|
+
fi
|
|
886
|
+
done <<< "$issue_labels"
|
|
887
|
+
|
|
888
|
+
if [ -n "$extra_label" ]; then
|
|
889
|
+
label_args+=(--label "$extra_label")
|
|
890
|
+
fi
|
|
891
|
+
|
|
892
|
+
local issue_url
|
|
893
|
+
issue_url=$(gh issue create --title "$title" --body "$body" "${label_args[@]}" 2>&1)
|
|
894
|
+
log_success "Created: ${issue_url}"
|
|
895
|
+
done
|
|
896
|
+
|
|
897
|
+
echo ""
|
|
898
|
+
log_success "Created ${count} issue(s)"
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
cmd_create() {
|
|
902
|
+
local interactive=false
|
|
903
|
+
local extra_label=""
|
|
904
|
+
local user_text=""
|
|
905
|
+
|
|
906
|
+
while [[ $# -gt 0 ]]; do
|
|
907
|
+
case $1 in
|
|
908
|
+
-i|--interactive) interactive=true; shift ;;
|
|
909
|
+
--label) extra_label="$2"; shift 2 ;;
|
|
910
|
+
--model) MODEL_FLAG="--model $2"; shift 2 ;;
|
|
911
|
+
-h|--help) show_create_help; exit 0 ;;
|
|
912
|
+
-*) echo "Unknown option: $1"; echo ""; show_create_help; exit 1 ;;
|
|
913
|
+
*) user_text="$1"; shift ;;
|
|
914
|
+
esac
|
|
915
|
+
done
|
|
916
|
+
|
|
917
|
+
create_preflight
|
|
918
|
+
|
|
919
|
+
local json
|
|
920
|
+
|
|
921
|
+
if [ "$interactive" = true ]; then
|
|
922
|
+
json=$(create_interactive)
|
|
923
|
+
elif [ -n "$user_text" ]; then
|
|
924
|
+
json=$(create_from_text "$user_text")
|
|
925
|
+
else
|
|
926
|
+
echo -e "${BOLD}Describe what issues you want to create.${NC}"
|
|
927
|
+
echo -e "${DIM}Type or paste your text, then press Ctrl+D when done.${NC}"
|
|
928
|
+
echo ""
|
|
929
|
+
user_text=$(cat)
|
|
930
|
+
if [ -z "$user_text" ]; then
|
|
931
|
+
log_error "No input provided"
|
|
932
|
+
exit 1
|
|
933
|
+
fi
|
|
934
|
+
json=$(create_from_text "$user_text")
|
|
935
|
+
fi
|
|
936
|
+
|
|
937
|
+
preview_issues "$json"
|
|
938
|
+
confirm_and_create "$json" "$extra_label"
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
# --- Subcommand routing ---
|
|
942
|
+
|
|
943
|
+
SUBCOMMAND=""
|
|
944
|
+
if [[ $# -gt 0 ]] && [[ "$1" != -* ]]; then
|
|
945
|
+
SUBCOMMAND="$1"; shift
|
|
946
|
+
fi
|
|
947
|
+
|
|
948
|
+
case "$SUBCOMMAND" in
|
|
949
|
+
"")
|
|
950
|
+
while [[ $# -gt 0 ]]; do
|
|
951
|
+
case $1 in
|
|
952
|
+
--max-retries) MAX_RETRIES="$2"; shift 2 ;;
|
|
953
|
+
--max-turns) MAX_TURNS="$2"; shift 2 ;;
|
|
954
|
+
--label) ISSUE_FILTER="$2"; shift 2 ;;
|
|
955
|
+
--model) MODEL_FLAG="--model $2"; shift 2 ;;
|
|
956
|
+
-v|--version) echo "claude-queue v${VERSION}"; exit 0 ;;
|
|
957
|
+
-h|--help) show_help; exit 0 ;;
|
|
958
|
+
*) echo "Unknown option: $1"; exit 1 ;;
|
|
959
|
+
esac
|
|
960
|
+
done
|
|
961
|
+
main
|
|
962
|
+
;;
|
|
963
|
+
create)
|
|
964
|
+
cmd_create "$@"
|
|
965
|
+
;;
|
|
966
|
+
*)
|
|
967
|
+
echo "Unknown command: $SUBCOMMAND"
|
|
968
|
+
echo ""
|
|
969
|
+
show_help
|
|
970
|
+
exit 1
|
|
971
|
+
;;
|
|
972
|
+
esac
|