claude-queue 1.4.0 → 1.5.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.
Files changed (3) hide show
  1. package/README.md +61 -4
  2. package/claude-queue.sh +435 -20
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # claude-queue
2
2
 
3
- Automated GitHub issue solver. Queue up issues, let Claude Code solve them, wake up to a PR.
3
+ Automated GitHub issue solver & creator. Create well-structured issues from a text dump or interactive interview, then let Claude Code solve them overnight.
4
4
 
5
- claude-queue fetches all open issues from your repo, uses [Claude Code](https://docs.anthropic.com/en/docs/claude-code) to solve each one, and opens a pull request with a summary of what was solved, what failed, and the full changelog.
5
+ claude-queue has two modes:
6
+ - **Solve** (default) — fetches open issues, uses [Claude Code](https://docs.anthropic.com/en/docs/claude-code) to solve each one, and opens a pull request
7
+ - **Create** — decomposes a description into well-structured GitHub issues, either from inline text or via an interactive interview
6
8
 
7
9
  ## Prerequisites
8
10
 
@@ -24,13 +26,15 @@ 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
37
+ #### Solve options
34
38
 
35
39
  | Flag | Default | Description |
36
40
  |------|---------|-------------|
@@ -40,7 +44,7 @@ claude-queue
40
44
  | `--model MODEL` | CLI default | Claude model to use (e.g. `claude-sonnet-4-5-20250929`) |
41
45
  | `-h, --help` | | Show help |
42
46
 
43
- ### Examples
47
+ #### Solve examples
44
48
 
45
49
  ```bash
46
50
  # Solve all open issues
@@ -53,6 +57,59 @@ claude-queue --label bug
53
57
  claude-queue --max-retries 5 --model claude-sonnet-4-5-20250929
54
58
  ```
55
59
 
60
+ ### Creating issues
61
+
62
+ Generate well-structured GitHub issues from a text description or an interactive interview with Claude.
63
+
64
+ ```bash
65
+ claude-queue create "Add dark mode and fix the login bug"
66
+ ```
67
+
68
+ There are three ways to provide input:
69
+
70
+ 1. **Inline text** — pass your description as an argument
71
+ 2. **Stdin** — run `claude-queue create` with no arguments, type or paste your text, then press Ctrl+D
72
+ 3. **Interactive** — run `claude-queue create -i` and Claude will ask clarifying questions before generating issues
73
+
74
+ In all modes, Claude decomposes the input into individual, actionable issues with titles, markdown bodies, and labels (reusing existing repo labels where possible). You get a preview before anything is created.
75
+
76
+ #### Create options
77
+
78
+ | Flag | Default | Description |
79
+ |------|---------|-------------|
80
+ | `-i, --interactive` | off | Interview mode — Claude asks clarifying questions first |
81
+ | `--label LABEL` | none | Add this label to every created issue |
82
+ | `--model MODEL` | CLI default | Claude model to use |
83
+ | `-h, --help` | | Show help for create |
84
+
85
+ #### Create examples
86
+
87
+ ```bash
88
+ # Create issues from a text description
89
+ claude-queue create "Add user avatars, implement search, and fix the 404 on /settings"
90
+
91
+ # Interactive mode — Claude asks questions first
92
+ claude-queue create -i
93
+
94
+ # Paste a longer description via stdin
95
+ claude-queue create
96
+
97
+ # Add a label to all created issues (useful with --label on solve)
98
+ claude-queue create --label backlog "Refactor the auth module and add rate limiting"
99
+ ```
100
+
101
+ ### Workflow: create then solve
102
+
103
+ The `--label` flag on both commands lets you create a workflow where `create` plans the issues and bare `claude-queue` solves them:
104
+
105
+ ```bash
106
+ # Plan: create issues tagged "nightshift"
107
+ claude-queue create --label nightshift "Add dark mode and fix the login bug"
108
+
109
+ # Solve: only process those issues
110
+ claude-queue --label nightshift
111
+ ```
112
+
56
113
  ## Configuration
57
114
 
58
115
  Create a `.claude-queue` file in your repo root to add custom instructions to every issue prompt:
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
- # 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.
5
+ # Commands:
6
+ # claude-queue [options] Solve open issues (default)
7
+ # claude-queue create [options] Create issues from text or interactively
7
8
  #
8
- # Usage:
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
 
@@ -47,17 +51,38 @@ declare -a SKIPPED_ISSUES=()
47
51
  CURRENT_ISSUE=""
48
52
  START_TIME=$(date +%s)
49
53
 
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
54
+ show_help() {
55
+ echo "claude-queue v${VERSION} — Automated GitHub issue solver & creator"
56
+ echo ""
57
+ echo "Usage:"
58
+ echo " claude-queue [options] Solve open issues (default)"
59
+ echo " claude-queue create [options] [text] Create issues from text or interactively"
60
+ echo ""
61
+ echo "Solve options:"
62
+ echo " --max-retries N Max retries per issue (default: 3)"
63
+ echo " --max-turns N Max Claude turns per attempt (default: 50)"
64
+ echo " --label LABEL Only process issues with this label"
65
+ echo " --model MODEL Claude model to use"
66
+ echo " -v, --version Show version"
67
+ echo " -h, --help Show this help message"
68
+ echo ""
69
+ echo "Run 'claude-queue create --help' for create options."
70
+ }
71
+
72
+ show_create_help() {
73
+ echo "claude-queue create — Generate GitHub issues from text or an interactive interview"
74
+ echo ""
75
+ echo "Usage:"
76
+ echo " claude-queue create \"description\" Create issues from inline text"
77
+ echo " claude-queue create Prompt for text input (Ctrl+D to finish)"
78
+ echo " claude-queue create -i Interactive interview mode"
79
+ echo ""
80
+ echo "Options:"
81
+ echo " -i, --interactive Interview mode (Claude asks clarifying questions first)"
82
+ echo " --label LABEL Add this label to every created issue"
83
+ echo " --model MODEL Claude model to use"
84
+ echo " -h, --help Show this help message"
85
+ }
61
86
 
62
87
  log() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${BLUE}[claude-queue]${NC} $1"; }
63
88
  log_success() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${GREEN}[claude-queue]${NC} $1"; }
@@ -79,7 +104,9 @@ cleanup() {
79
104
  log_warn "Branch '${BRANCH}' has your commits. Push manually if needed."
80
105
  fi
81
106
 
82
- log "Logs saved to: ${LOG_DIR}"
107
+ if [ -d "$LOG_DIR" ]; then
108
+ log "Logs saved to: ${LOG_DIR}"
109
+ fi
83
110
  }
84
111
  trap cleanup EXIT
85
112
 
@@ -536,4 +563,392 @@ main() {
536
563
  log "Logs: ${LOG_DIR}"
537
564
  }
538
565
 
539
- main "$@"
566
+ create_preflight() {
567
+ log_header "Preflight Checks"
568
+
569
+ local failed=false
570
+
571
+ for cmd in gh claude jq; do
572
+ if command -v "$cmd" &>/dev/null; then
573
+ log " $cmd ... found"
574
+ else
575
+ log_error " $cmd ... NOT FOUND"
576
+ failed=true
577
+ fi
578
+ done
579
+
580
+ if ! gh auth status &>/dev/null; then
581
+ log_error " gh auth ... not authenticated"
582
+ failed=true
583
+ else
584
+ log " gh auth ... ok"
585
+ fi
586
+
587
+ if ! git rev-parse --is-inside-work-tree &>/dev/null; then
588
+ log_error " git repo ... not inside a git repository"
589
+ failed=true
590
+ else
591
+ log " git repo ... ok"
592
+ fi
593
+
594
+ if [ "$failed" = true ]; then
595
+ log_error "Preflight failed. Aborting."
596
+ exit 1
597
+ fi
598
+ }
599
+
600
+ get_repo_labels() {
601
+ gh label list --json name -q '.[].name' 2>/dev/null | paste -sd ',' -
602
+ }
603
+
604
+ extract_json() {
605
+ local input="$1"
606
+ local json
607
+
608
+ json=$(echo "$input" | sed -n '/^```\(json\)\?$/,/^```$/{ /^```/d; p; }')
609
+ if [ -z "$json" ]; then
610
+ json="$input"
611
+ fi
612
+
613
+ if echo "$json" | jq empty 2>/dev/null; then
614
+ echo "$json"
615
+ return 0
616
+ fi
617
+
618
+ json=$(echo "$input" | grep -o '\[.*\]' | head -1)
619
+ if [ -n "$json" ] && echo "$json" | jq empty 2>/dev/null; then
620
+ echo "$json"
621
+ return 0
622
+ fi
623
+
624
+ return 1
625
+ }
626
+
627
+ create_from_text() {
628
+ local user_text="$1"
629
+ local repo_labels
630
+ repo_labels=$(get_repo_labels)
631
+
632
+ log "Analyzing text and generating issues..."
633
+
634
+ local prompt
635
+ prompt="You are a GitHub issue planner. The user wants to create issues for a repository.
636
+
637
+ Existing labels in the repo: ${repo_labels}
638
+
639
+ The user's description:
640
+ ${user_text}
641
+
642
+ Decompose this into a JSON array of well-structured GitHub issues. Each issue should have:
643
+ - \"title\": a clear, concise issue title
644
+ - \"body\": a detailed issue body in markdown (include acceptance criteria where appropriate)
645
+ - \"labels\": an array of label strings (reuse existing repo labels when they fit, or suggest new ones)
646
+
647
+ Rules:
648
+ - Create separate issues for logically distinct tasks
649
+ - Each issue should be independently actionable
650
+ - Use clear, imperative titles (e.g. \"Add dark mode toggle to settings page\")
651
+ - If the description is vague, make reasonable assumptions and note them in the body
652
+
653
+ Output ONLY the JSON array, no other text."
654
+
655
+ local output
656
+ # shellcheck disable=SC2086
657
+ output=$(claude -p "$prompt" $MODEL_FLAG 2>/dev/null)
658
+
659
+ local json
660
+ if ! json=$(extract_json "$output"); then
661
+ log_error "Failed to parse Claude's response as JSON"
662
+ log_error "Raw output:"
663
+ echo "$output"
664
+ exit 1
665
+ fi
666
+
667
+ local count
668
+ count=$(echo "$json" | jq length)
669
+ if [ "$count" -eq 0 ]; then
670
+ log_error "No issues were generated"
671
+ exit 1
672
+ fi
673
+
674
+ echo "$json"
675
+ }
676
+
677
+ create_interactive() {
678
+ local repo_labels
679
+ repo_labels=$(get_repo_labels)
680
+ local conversation=""
681
+ local max_turns=10
682
+ local turn=0
683
+
684
+ local system_prompt="You are a GitHub issue planner conducting an interview to understand what issues to create for a repository.
685
+
686
+ Existing labels in the repo: ${repo_labels}
687
+
688
+ Your job:
689
+ 1. Ask focused questions to understand what the user wants to build or fix
690
+ 2. Ask about priorities, scope, and acceptance criteria
691
+ 3. When you have enough information, output the marker CLAUDE_QUEUE_READY on its own line, followed by a JSON array of issues
692
+
693
+ Each issue in the JSON array should have:
694
+ - \"title\": a clear, concise issue title
695
+ - \"body\": a detailed issue body in markdown
696
+ - \"labels\": an array of label strings (reuse existing repo labels when they fit)
697
+
698
+ Rules:
699
+ - Ask one question at a time
700
+ - Keep questions short and specific
701
+ - After 2-3 questions you should have enough context — don't over-interview
702
+ - If the user says \"done\", immediately generate the issues with what you know
703
+ - Output ONLY your question text (no JSON) until you're ready to generate issues
704
+ - When ready, output CLAUDE_QUEUE_READY on its own line followed by ONLY the JSON array"
705
+
706
+ echo -e "${BOLD}Interactive issue creation${NC}"
707
+ echo -e "${DIM}Answer Claude's questions. Type 'done' to generate issues at any time.${NC}"
708
+ echo ""
709
+
710
+ while [ "$turn" -lt "$max_turns" ]; do
711
+ turn=$((turn + 1))
712
+
713
+ local prompt
714
+ if [ -z "$conversation" ]; then
715
+ prompt="${system_prompt}
716
+
717
+ Start by asking your first question."
718
+ else
719
+ prompt="${system_prompt}
720
+
721
+ Conversation so far:
722
+ ${conversation}
723
+
724
+ Continue the interview or, if you have enough information, output CLAUDE_QUEUE_READY followed by the JSON array."
725
+ fi
726
+
727
+ local output
728
+ # shellcheck disable=SC2086
729
+ output=$(claude -p "$prompt" $MODEL_FLAG 2>/dev/null)
730
+
731
+ if echo "$output" | grep -q "CLAUDE_QUEUE_READY"; then
732
+ local json_part
733
+ json_part=$(echo "$output" | sed -n '/CLAUDE_QUEUE_READY/,$ p' | tail -n +2)
734
+
735
+ local json
736
+ if ! json=$(extract_json "$json_part"); then
737
+ log_error "Failed to parse generated issues as JSON"
738
+ exit 1
739
+ fi
740
+
741
+ echo "$json"
742
+ return 0
743
+ fi
744
+
745
+ echo -e "${BLUE}Claude:${NC} ${output}"
746
+ echo ""
747
+
748
+ local user_input
749
+ read -r -p "You: " user_input
750
+
751
+ if [ "$user_input" = "done" ]; then
752
+ conversation="${conversation}
753
+ Claude: ${output}
754
+ User: Please generate the issues now with what you know."
755
+
756
+ local final_prompt="${system_prompt}
757
+
758
+ Conversation so far:
759
+ ${conversation}
760
+
761
+ The user wants you to generate the issues now. Output CLAUDE_QUEUE_READY followed by the JSON array."
762
+
763
+ local final_output
764
+ # shellcheck disable=SC2086
765
+ final_output=$(claude -p "$final_prompt" $MODEL_FLAG 2>/dev/null)
766
+
767
+ local final_json_part
768
+ final_json_part=$(echo "$final_output" | sed -n '/CLAUDE_QUEUE_READY/,$ p' | tail -n +2)
769
+ if [ -z "$final_json_part" ]; then
770
+ final_json_part="$final_output"
771
+ fi
772
+
773
+ local json
774
+ if ! json=$(extract_json "$final_json_part"); then
775
+ log_error "Failed to parse generated issues as JSON"
776
+ exit 1
777
+ fi
778
+
779
+ echo "$json"
780
+ return 0
781
+ fi
782
+
783
+ conversation="${conversation}
784
+ Claude: ${output}
785
+ User: ${user_input}"
786
+ done
787
+
788
+ log_warn "Reached maximum interview turns, generating issues with current information..."
789
+
790
+ local final_prompt="${system_prompt}
791
+
792
+ Conversation so far:
793
+ ${conversation}
794
+
795
+ You've reached the maximum number of questions. Output CLAUDE_QUEUE_READY followed by the JSON array now."
796
+
797
+ local final_output
798
+ # shellcheck disable=SC2086
799
+ final_output=$(claude -p "$final_prompt" $MODEL_FLAG 2>/dev/null)
800
+
801
+ local final_json_part
802
+ final_json_part=$(echo "$final_output" | sed -n '/CLAUDE_QUEUE_READY/,$ p' | tail -n +2)
803
+ if [ -z "$final_json_part" ]; then
804
+ final_json_part="$final_output"
805
+ fi
806
+
807
+ local json
808
+ if ! json=$(extract_json "$final_json_part"); then
809
+ log_error "Failed to parse generated issues as JSON"
810
+ exit 1
811
+ fi
812
+
813
+ echo "$json"
814
+ }
815
+
816
+ preview_issues() {
817
+ local json="$1"
818
+ local count
819
+ count=$(echo "$json" | jq length)
820
+
821
+ echo ""
822
+ echo -e "${BOLD}═══ Issue Preview ═══${NC}"
823
+ echo ""
824
+
825
+ for i in $(seq 0 $((count - 1))); do
826
+ local title labels body
827
+ title=$(echo "$json" | jq -r ".[$i].title")
828
+ labels=$(echo "$json" | jq -r ".[$i].labels // [] | join(\", \")")
829
+ body=$(echo "$json" | jq -r ".[$i].body" | head -3)
830
+
831
+ echo -e " ${BOLD}$((i + 1)). ${title}${NC}"
832
+ if [ -n "$labels" ]; then
833
+ echo -e " ${DIM}Labels: ${labels}${NC}"
834
+ fi
835
+ echo -e " ${DIM}$(echo "$body" | head -1)${NC}"
836
+ echo ""
837
+ done
838
+ }
839
+
840
+ confirm_and_create() {
841
+ local json="$1"
842
+ local extra_label="$2"
843
+ local count
844
+ count=$(echo "$json" | jq length)
845
+
846
+ local prompt_text="Create ${count} issue(s)? [y/N] "
847
+ read -r -p "$prompt_text" confirm
848
+
849
+ if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
850
+ log "Cancelled."
851
+ exit 0
852
+ fi
853
+
854
+ echo ""
855
+
856
+ for i in $(seq 0 $((count - 1))); do
857
+ local title body
858
+ title=$(echo "$json" | jq -r ".[$i].title")
859
+ body=$(echo "$json" | jq -r ".[$i].body")
860
+
861
+ local label_args=()
862
+ local issue_labels
863
+ issue_labels=$(echo "$json" | jq -r ".[$i].labels // [] | .[]")
864
+ while IFS= read -r lbl; do
865
+ if [ -n "$lbl" ]; then
866
+ label_args+=(--label "$lbl")
867
+ fi
868
+ done <<< "$issue_labels"
869
+
870
+ if [ -n "$extra_label" ]; then
871
+ label_args+=(--label "$extra_label")
872
+ fi
873
+
874
+ local issue_url
875
+ issue_url=$(gh issue create --title "$title" --body "$body" "${label_args[@]}" 2>&1)
876
+ log_success "Created: ${issue_url}"
877
+ done
878
+
879
+ echo ""
880
+ log_success "Created ${count} issue(s)"
881
+ }
882
+
883
+ cmd_create() {
884
+ local interactive=false
885
+ local extra_label=""
886
+ local user_text=""
887
+
888
+ while [[ $# -gt 0 ]]; do
889
+ case $1 in
890
+ -i|--interactive) interactive=true; shift ;;
891
+ --label) extra_label="$2"; shift 2 ;;
892
+ --model) MODEL_FLAG="--model $2"; shift 2 ;;
893
+ -h|--help) show_create_help; exit 0 ;;
894
+ -*) echo "Unknown option: $1"; echo ""; show_create_help; exit 1 ;;
895
+ *) user_text="$1"; shift ;;
896
+ esac
897
+ done
898
+
899
+ create_preflight
900
+
901
+ local json
902
+
903
+ if [ "$interactive" = true ]; then
904
+ json=$(create_interactive)
905
+ elif [ -n "$user_text" ]; then
906
+ json=$(create_from_text "$user_text")
907
+ else
908
+ echo -e "${BOLD}Describe what issues you want to create.${NC}"
909
+ echo -e "${DIM}Type or paste your text, then press Ctrl+D when done.${NC}"
910
+ echo ""
911
+ user_text=$(cat)
912
+ if [ -z "$user_text" ]; then
913
+ log_error "No input provided"
914
+ exit 1
915
+ fi
916
+ json=$(create_from_text "$user_text")
917
+ fi
918
+
919
+ preview_issues "$json"
920
+ confirm_and_create "$json" "$extra_label"
921
+ }
922
+
923
+ # --- Subcommand routing ---
924
+
925
+ SUBCOMMAND=""
926
+ if [[ $# -gt 0 ]] && [[ "$1" != -* ]]; then
927
+ SUBCOMMAND="$1"; shift
928
+ fi
929
+
930
+ case "$SUBCOMMAND" in
931
+ "")
932
+ while [[ $# -gt 0 ]]; do
933
+ case $1 in
934
+ --max-retries) MAX_RETRIES="$2"; shift 2 ;;
935
+ --max-turns) MAX_TURNS="$2"; shift 2 ;;
936
+ --label) ISSUE_FILTER="$2"; shift 2 ;;
937
+ --model) MODEL_FLAG="--model $2"; shift 2 ;;
938
+ -v|--version) echo "claude-queue v${VERSION}"; exit 0 ;;
939
+ -h|--help) show_help; exit 0 ;;
940
+ *) echo "Unknown option: $1"; exit 1 ;;
941
+ esac
942
+ done
943
+ main
944
+ ;;
945
+ create)
946
+ cmd_create "$@"
947
+ ;;
948
+ *)
949
+ echo "Unknown command: $SUBCOMMAND"
950
+ echo ""
951
+ show_help
952
+ exit 1
953
+ ;;
954
+ esac
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-queue",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Automated GitHub issue solver powered by Claude Code",
5
5
  "bin": {
6
6
  "claude-queue": "./claude-queue.sh"