Compare commits

..

1 Commits

Author SHA1 Message Date
shokollm
3a0983c28a fix: add PR merge conflict check to dev agent workflow
Dev agent should check if PR has merge conflicts before asking for review.
This prevents approving a PR that has conflicts, which could introduce
new errors after rebase.

Changes:
- Add conflict check step in workflow for both with/without user message cases
- Use mergeable field from PR API to detect conflicts
- Instructions to rebase and resolve conflicts if mergeable: false
2026-04-08 02:15:37 +00:00
7 changed files with 163 additions and 400 deletions

1
.gitignore vendored
View File

@@ -4,4 +4,3 @@ results/
*/results/
*.pyc
.kugetsu/

View File

@@ -17,122 +17,77 @@ usage() {
kugetsu - OpenCode Session Manager (Issue-Driven)
Usage:
kugetsu <command> [subcommand] [options]
Commands:
init [--force] Initialize base + pm-agent sessions (requires TTY)
start <issue-ref> <message> [--debug] Start task for issue (forks base session)
continue <issue-ref> [message] [--debug] Continue existing task for issue
delegate <message> Send message to PM agent (fire-and-forget)
logs [n] Show recent delegation logs (default: 10)
status Check kugetsu initialization status
doctor [--fix] Diagnose and fix kugetsu issues
notify [list|clear] Show or clear notifications
list List all tracked sessions
prune [--force] Remove orphaned sessions
destroy <target> [-y] Delete session (issue, pm-agent, or base)
set-pr <issue-ref> <pr-url> Set PR URL for session
context <issue-ref> Show context for issue
queue [subcommand] Queue management
queue-daemon [subcommand] Queue daemon management
env [subcommand] Environment variable management
server [subcommand] Git server configuration
help [command] Show help for a command
Use 'kugetsu <command> help' for subcommand help.
Example: kugetsu queue help, kugetsu queue-daemon help
kugetsu init [--force] Initialize base + pm-agent sessions (requires TTY)
kugetsu start <issue-ref> <message> [--debug] Start task for issue (forks base session)
kugetsu continue <issue-ref> [message] [--debug] Continue existing task for issue
kugetsu delegate <message> Send message to PM agent (fire-and-forget)
kugetsu logs [n] Show recent delegation logs (default: 10)
kugetsu status Check kugetsu initialization status
kugetsu doctor [--fix] Diagnose and fix kugetsu issues
kugetsu notify [list|clear] Show or clear notifications
kugetsu list List all tracked sessions
kugetsu prune [--force] Remove orphaned sessions (keeps base + pm-agent)
kugetsu destroy <issue-ref> [-y] Delete session for issue
kugetsu destroy --pm-agent [-y] Delete pm-agent session (not recommended)
kugetsu destroy --base [-y] Delete base session
kugetsu set-pr <issue-ref> <pr-url> Set PR URL for session (for PR tracking)
kugetsu context <issue-ref> Show context for issue
kugetsu queue [list|stats|clear] Show queue status or statistics
kugetsu queue enqueue <issue-ref> <message> Enqueue a task (normally via delegate)
kugetsu queue-daemon [start|stop|restart|status|logs] Manage queue daemon
kugetsu env [get|set|list] Manage agent environment variables
kugetsu server [list|add|remove|default|get] Manage git server configurations
kugetsu help Show this help
Issue Ref Format:
instance/user/repo#number
Example: github.com/shoko/kugetsu#14
EOF
}
usage_queue() {
cat << 'EOF'
kugetsu queue - Queue management
Commands:
init Create base + pm-agent sessions via TUI. Requires terminal access.
Use --force to reinitialize if sessions exist.
start Fork new session from base for specific issue.
Requires pm-agent to be running (created by init).
continue Continue work on existing issue session.
delegate Send message to PM agent for task coordination.
Fire-and-forget: returns immediately, runs in background.
Use 'kugetsu logs' to check output.
logs Show recent delegation logs.
Default: 10 most recent. Use 'kugetsu logs 20' for more.
status Check if kugetsu is initialized and PM agent is active.
doctor Diagnose kugetsu issues. Use --fix to attempt repairs.
notify Show or clear notifications from PM agent.
Use 'kugetsu notify list' to see unread notifications.
list Show all sessions (base + pm-agent + forked issues).
prune Remove sessions not in index (orphaned from opencode).
Use --force to skip confirmation.
destroy Delete specific issue, pm-agent, or base session.
Usage:
kugetsu queue [subcommand]
Options:
--debug Show real-time debug output and capture to debug.log
Subcommands:
list Show pending tasks (default)
stats Show queue statistics
clear Clear all queue items
enqueue <issue-ref> <message> Enqueue a task
help Show this help
PM Context:
kugetsu reads ~/.kugetsu/pm-agent.md (if exists) and injects it
into the PM agent session at init time. This allows customizing PM
behavior without recreating the session.
Notifications:
PM Agent writes task completion notifications to ~/.kugetsu/notifications.json
Use 'kugetsu notify list' to see unread notifications.
Examples:
kugetsu queue list
kugetsu queue stats
kugetsu queue clear
kugetsu queue enqueue github.com/shoko/kugetsu#14 "fix bug"
EOF
}
usage_queue_daemon() {
cat << 'EOF'
kugetsu queue-daemon - Queue daemon management
Usage:
kugetsu queue-daemon [subcommand]
Subcommands:
start Start the queue daemon
stop Stop the queue daemon
restart Restart the queue daemon
status Check daemon status
logs Show recent daemon logs
help Show this help
Examples:
kugetsu queue-daemon start
kugetsu queue-daemon status
kugetsu queue-daemon logs
EOF
}
usage_env() {
cat << 'EOF'
kugetsu env - Environment variable management
Usage:
kugetsu env [subcommand]
Subcommands:
list List all environment variables
get <key> Get a specific variable
set <key> <value> Set a variable
rm <key> Remove a variable
help Show this help
Examples:
kugetsu env list
kugetsu env get GITEA_TOKEN
kugetsu env set CUSTOM_VAR "value"
kugetsu env rm CUSTOM_VAR
EOF
}
usage_server() {
cat << 'EOF'
kugetsu server - Git server configuration
Usage:
kugetsu server [subcommand]
Subcommands:
list List all configured servers (default)
add <name> <url> Add a new server
remove <name> Remove a server
default [<name>] Get or set default server
get [<name>] Get server URL
help Show this help
Examples:
kugetsu server list
kugetsu server add github.com https://github.com
kugetsu server default github.com
kugetsu init
kugetsu status
kugetsu delegate "work on issue #5"
kugetsu logs
kugetsu logs 20
kugetsu doctor
kugetsu doctor --fix
kugetsu notify list
kugetsu notify clear
kugetsu start github.com/shoko/kugetsu#14 "fix bug"
kugetsu continue github.com/shoko/kugetsu#14 "add tests"
kugetsu list
EOF
}
@@ -910,15 +865,10 @@ find_sessions_by_issue_number() {
}
cmd_queue() {
local action="${1:-}"
local action="${1:-list}"
shift
case "$action" in
""|help|--help|-h)
usage_queue
;;
help|--help|-h)
usage_queue
;;
list)
local pending_tasks=$(get_pending_tasks 10)
if [ "$pending_tasks" = "[]" ]; then
@@ -948,8 +898,8 @@ cmd_queue() {
echo "Queue cleared."
;;
enqueue)
local issue_ref="${2:-}"
local message="${3:-}"
local issue_ref="${1:-}"
local message="${2:-}"
if [ -z "$issue_ref" ] || [ -z "$message" ]; then
echo "Usage: kugetsu queue enqueue <issue-ref> <message>" >&2
exit 1
@@ -960,20 +910,16 @@ cmd_queue() {
check_task_timeouts
;;
*)
echo "Unknown queue subcommand: $action" >&2
usage_queue
echo "Usage: kugetsu queue [list|stats|clear|enqueue]" >&2
exit 1
;;
esac
}
cmd_queue_daemon() {
local action="${1:-}"
local action="${1:-status}"
case "$action" in
""|help|--help|-h)
usage_queue_daemon
;;
start)
if [ -f "$QUEUE_DAEMON_PID_FILE" ]; then
local old_pid=$(cat "$QUEUE_DAEMON_PID_FILE")
@@ -1032,8 +978,7 @@ cmd_queue_daemon() {
fi
;;
*)
echo "Unknown queue-daemon subcommand: $action" >&2
usage_queue_daemon
echo "Usage: kugetsu queue-daemon [start|stop|restart|status|logs]" >&2
exit 1
;;
esac
@@ -1114,12 +1059,10 @@ set_debug_mode() {
}
cmd_env() {
local action="${1:-}"
local action="${1:-list}"
shift
case "$action" in
""|help|--help|-h)
usage_env
;;
list)
echo "Agent environment variables:"
if [ -d "$ENV_DIR" ]; then
@@ -1137,7 +1080,7 @@ cmd_env() {
fi
;;
get)
local key="${2:-}"
local key="${1:-}"
if [ -z "$key" ]; then
echo "Usage: kugetsu env get <key>" >&2
exit 1
@@ -1152,8 +1095,8 @@ cmd_env() {
fi
;;
set)
local key="${2:-}"
local value="${3:-}"
local key="${1:-}"
local value="${2:-}"
if [ -z "$key" ] || [ -z "$value" ]; then
echo "Usage: kugetsu env set <key> <value>" >&2
exit 1
@@ -1162,8 +1105,8 @@ cmd_env() {
echo "${key}=${value}" >> "$ENV_DIR/default.env"
echo "Set $key in $ENV_DIR/default.env"
;;
rm|remove)
local key="${2:-}"
rm)
local key="${1:-}"
if [ -z "$key" ]; then
echo "Usage: kugetsu env rm <key>" >&2
exit 1
@@ -1174,8 +1117,7 @@ cmd_env() {
fi
;;
*)
echo "Unknown env subcommand: $action" >&2
usage_env
echo "Usage: kugetsu env [list|get|set|rm]" >&2
exit 1
;;
esac
@@ -1185,10 +1127,7 @@ cmd_server() {
local action="${1:-}"
case "$action" in
""|help|--help|-h)
usage_server
;;
"list")
""|"list")
if [ -z "${GIT_SERVERS+x}" ]; then
echo "No git servers configured"
return
@@ -1265,8 +1204,14 @@ cmd_server() {
fi
;;
*)
echo "Unknown server subcommand: $action" >&2
usage_server
echo "Usage: kugetsu server <list|add|remove|default|get>" >&2
echo "" >&2
echo "Commands:" >&2
echo " list List all configured git servers" >&2
echo " add <name> <url> Add a new git server" >&2
echo " remove <name> Remove a git server" >&2
echo " default [<name>] Get or set default server" >&2
echo " get [<name>] Get URL for a server (default: current default)" >&2
exit 1
;;
esac
@@ -1376,29 +1321,7 @@ main() {
case "$command" in
help|--help|-h)
local subcommand="${1:-}"
case "$subcommand" in
queue|"")
usage_queue
;;
queue-daemon)
usage_queue_daemon
;;
env)
usage_env
;;
server)
usage_server
;;
"")
usage
;;
*)
echo "Help not available for '$subcommand'" >&2
usage
exit 1
;;
esac
usage
;;
init)
cmd_init "$@"

View File

@@ -26,9 +26,6 @@ QUEUE_DAEMON_INTERVAL_MINUTES="${QUEUE_DAEMON_INTERVAL_MINUTES:-5}"
QUEUE_CLEANUP_AGE_DAYS="${QUEUE_CLEANUP_AGE_DAYS:-7}"
TASK_TIMEOUT_HOURS="${TASK_TIMEOUT_HOURS:-1}"
NETWORK_RETRY_ATTEMPTS="${NETWORK_RETRY_ATTEMPTS:-3}"
NETWORK_RETRY_DELAY_SECONDS="${NETWORK_RETRY_DELAY_SECONDS:-5}"
# Load user config overrides (~/.kugetsu/config)
if [ -f "$KUGETSU_DIR/config" ]; then
source "$KUGETSU_DIR/config"
@@ -90,24 +87,3 @@ set_debug_mode() {
echo "${filtered_args[@]}"
}
retry_with_backoff() {
local max_attempts="${1:-$NETWORK_RETRY_ATTEMPTS}"
local delay_seconds="${2:-$NETWORK_RETRY_DELAY_SECONDS}"
local command="$3"
local remaining_attempts=$max_attempts
while [ $remaining_attempts -gt 0 ]; do
if eval "$command"; then
return 0
fi
remaining_attempts=$((remaining_attempts - 1))
if [ $remaining_attempts -gt 0 ]; then
log "warn" "retry_with_backoff" "Command failed, $remaining_attempts retries remaining. Waiting ${delay_seconds}s..."
sleep "$delay_seconds"
delay_seconds=$((delay_seconds * 2))
fi
done
log "error" "retry_with_backoff" "Command failed after $max_attempts attempts"
return 1
}

View File

@@ -139,77 +139,6 @@ validate_issue_ref() {
fi
}
read_json_file() {
local file_path="$1"
if [ -f "$file_path" ]; then
cat "$file_path"
else
echo "{}"
fi
}
write_json_file() {
local file_path="$1"
local json_content="$2"
local temp_file="$file_path.tmp.$$"
printf '%s' "$json_content" > "$temp_file"
if ! python3 -c "import json; json.load(open('$temp_file'))" 2>/dev/null; then
echo "Error: write_json_file would create malformed JSON: $file_path" >&2
rm -f "$temp_file"
return 1
fi
mv "$temp_file" "$file_path"
}
get_json_value() {
local file_path="$1"
local key="$2"
local default="${3:-}"
if [ ! -f "$file_path" ]; then
echo "$default"
return
fi
python3 -c "import json; print(json.load(open('$file_path')).get('$key', '$default'))" 2>/dev/null || echo "$default"
}
set_json_value() {
local file_path="$1"
local key="$2"
local value="$3"
if [ ! -f "$file_path" ]; then
printf '{"%s": "%s"}\n' "$key" "$value" > "$file_path"
return
fi
python3 << PYEOF
import json
import sys
file_path = "$file_path"
key = "$key"
value = "$value"
try:
with open(file_path, 'r') as f:
data = json.load(f)
except:
data = {}
data[key] = value
with open(file_path, 'w') as f:
json.dump(data, f, indent=2)
print(f"Set $key = $value in $file_path")
PYEOF
}
update_session_pr_url() {
local issue_ref="$1"
local pr_url="$2"

View File

@@ -34,8 +34,6 @@ release_lock() {
check_task_completion() {
local item="$1"
local queue_id=$(basename "$item" .json)
local item_data=$(read_json_file "$item")
local state=$(python3 -c "import json; print(json.load(open('$item')).get('state', ''))" 2>/dev/null)
[ "$state" = "notified" ] || return 0
@@ -43,31 +41,6 @@ check_task_completion() {
local session_id=$(python3 -c "import json; print(json.load(open('$item')).get('opencode_session_id', ''))" 2>/dev/null)
local issue_ref=$(python3 -c "import json; print(json.load(open('$item')).get('issue_ref', ''))" 2>/dev/null)
local pid=$(python3 -c "import json; print(json.load(open('$item')).get('pid', ''))" 2>/dev/null)
local notified_at=$(python3 -c "import json; print(json.load(open('$item')).get('notified_at', ''))" 2>/dev/null)
local timed_out=false
if [ -n "$notified_at" ]; then
local notified_epoch=$(date -d "$notified_at" +%s 2>/dev/null || echo "0")
local now_epoch=$(date +%s)
local hours_elapsed=$(( (now_epoch - notified_epoch) / 3600 ))
if [ "$hours_elapsed" -ge "${TASK_TIMEOUT_HOURS:-1}" ]; then
timed_out=true
log_warn "queue-daemon" "Task $queue_id ($issue_ref) timed out after ${hours_elapsed}h"
fi
fi
if [ "$timed_out" = true ]; then
if [ -n "$pid" ] && [ "$pid" != "None" ]; then
kill "$pid" 2>/dev/null || true
fi
if [ -n "$session_id" ]; then
opencode session stop "$session_id" 2>/dev/null || true
fi
update_queue_item_state "$queue_id" "error"
log_error "queue-daemon" "Task $queue_id ($issue_ref) marked error — timeout after ${hours_elapsed}h"
release_lock "$issue_ref"
return
fi
if [ -n "$pid" ] && [ "$pid" != "None" ]; then
if ! kill -0 "$pid" 2>/dev/null; then

View File

@@ -13,8 +13,7 @@ count_active_dev_sessions() {
if [ -d "$SESSIONS_DIR" ]; then
for session_file in "$SESSIONS_DIR"/*.json; do
if [ -f "$session_file" ]; then
local filename
filename=$(basename "$session_file")
local filename=$(basename "$session_file")
if [ "$filename" != "base.json" ] && [ "$filename" != "pm-agent.json" ]; then
count=$((count + 1))
fi
@@ -240,55 +239,23 @@ create_session() {
return 1
fi
local before_file
before_file="$KUGETSU_DIR/sessions/before$$.json"
local after_file
after_file="$KUGETSU_DIR/sessions/after$$.json"
local before_json=$(opencode session list --format=json 2>/dev/null)
local before_set=$(echo "$before_json" | python3 -c "import sys,json; sessions=json.load(sys.stdin); print('|'.join(s['id'] for s in sessions))" 2>/dev/null || echo "|")
opencode session list --format=json > "$before_file" 2>/dev/null || printf '{}' > "$before_file"
local fork_success=false
local attempt=0
local max_attempts="${NETWORK_RETRY_ATTEMPTS:-3}"
while [ $attempt -lt $max_attempts ] && [ "$fork_success" = false ]; do
attempt=$((attempt + 1))
if opencode run --fork --session "$base_session" "new session" >/dev/null 2>&1; then
fork_success=true
elif [ $attempt -lt $max_attempts ]; then
log "warn" "create_session" "Fork attempt $attempt failed, retrying..."
sleep "$((attempt * 2))"
fi
done
if [ "$fork_success" = false ]; then
log "error" "create_session" "Failed to fork session after $max_attempts attempts"
rm -f "$before_file" "$after_file"
return 1
fi
opencode run --fork --session "$base_session" "new session" >/dev/null 2>&1
sleep 1
opencode session list --format=json > "$after_file" 2>/dev/null || printf '{}' > "$after_file"
local after_json=$(opencode session list --format=json 2>/dev/null)
local after_sessions=$(echo "$after_json" | python3 -c "import sys,json; sessions=json.load(sys.stdin); [print(s['id']) for s in sessions]" 2>/dev/null || true)
local new_session_id
new_session_id=$(python3 << PYEOF
import json
with open("$before_file", 'r') as f:
before = json.load(f)
with open("$after_file", 'r') as f:
after = json.load(f)
before_ids = set(s['id'] for s in before)
for s in after:
if s['id'] not in before_ids:
print(s['id'])
break
PYEOF
)
rm -f "$before_file" "$after_file"
local new_session_id=""
while IFS= read -r sess; do
if [[ -n "$sess" ]] && [[ ! "$before_set" =~ \|${sess}\| ]]; then
new_session_id="$sess"
break
fi
done <<< "$after_sessions"
echo "$new_session_id"
}
@@ -303,37 +270,21 @@ build_dev_agent_message() {
local number=$(echo "$issue_ref" | grep -oE '#[0-9]+$' | tr -d '#')
local worktree_path=$(issue_ref_to_worktree_path "$issue_ref")
local conflict_check=""
local review_notes=""
local delegator_header=""
local delegator_footer=""
if [ -n "$user_message" ]; then
conflict_check=" - CRITICAL: Check if PR has merge conflicts before asking for review:
- Use: curl -s \"https://$instance/api/v1/repos/$owner/$repo/pulls/$number\" -H \"Authorization: Bearer \$GITEA_TOKEN\"
- If \"mergeable\": false, there ARE conflicts - you MUST resolve them FIRST
- To resolve: cd to worktree, git fetch origin, git rebase origin/main, resolve conflicts, git rebase --continue, git push --force-with-lease
- Only after resolving conflicts (mergeable: true) can you ask for review"
delegator_header="IMPORTANT: Follow the workflow below as your guideline, but prioritize the delegator's message.
Workflow:"
delegator_footer="
Delegator's message:
$user_message"
else
review_notes=" - IMPORTANT: After listing reviews, READ the review comments and incorporate feedback
- Check for review state: \"APPROVED\" means ready to merge, \"COMMENT\" means feedback to address"
delegator_header="Workflow:"
fi
cat <<EOF
cat <<EOF
You are assigned to work on $issue_ref.
$delegator_header
IMPORTANT: Follow the workflow below as your guideline, but prioritize the delegator's message.
Workflow:
1. Read the issue at $instance/$owner/$repo/issues/$number AND all comments on that issue
2. Check if a PR already exists for this issue
- If PR exists and is open, review it and learn from it
$conflict_check
- CRITICAL: Check if PR has merge conflicts before asking for review:
- Use: curl -s "https://$instance/api/v1/repos/$owner/$repo/pulls/$number" -H "Authorization: Bearer \$GITEA_TOKEN"
- If "mergeable": false, there ARE conflicts - you MUST resolve them FIRST
- To resolve: cd to worktree, git fetch origin, git rebase origin/main, resolve conflicts, git rebase --continue, git push --force-with-lease
- Only after resolving conflicts (mergeable: true) can you ask for review
- If PR makes sense to continue, work on it instead
- If PR is not worth continuing, create a new branch/PR but explain in PR description why you're creating a new one instead of continuing the existing PR
3. Read README.md (if exists) to understand the general concept of this repository
@@ -351,14 +302,52 @@ Tools for PR interaction:
- Post issue/PR comment: curl -X POST "https://$instance/api/v1/repos/$owner/$repo/issues/$number/comments" -H "Authorization: Bearer \$GITEA_TOKEN" -H "Content-Type: application/json" -d '{"body":"Your comment"}'
- List PR comments: curl -s "https://$instance/api/v1/repos/$owner/$repo/issues/$number/comments" -H "Authorization: Bearer \$GITEA_TOKEN"
- List PR reviews: curl -s "https://$instance/api/v1/repos/$owner/$repo/pulls/$number/reviews" -H "Authorization: Bearer \$GITEA_TOKEN"
$review_notes
- Merge PR (only with approval): tea pr merge --repo $owner/$repo $number --style merge
- MERGING requires approval first! Check for: approval in reviews, OR "lgtm"/"approved" in comments
- If no approval, ask reviewer to approve first before merging
$delegator_footer
Delegator's message:
$user_message
Work directory: $worktree_path
EOF
else
cat <<EOF
You are assigned to work on $issue_ref.
Workflow:
1. Read the issue at $instance/$owner/$repo/issues/$number AND all comments on that issue
2. Check if a PR already exists for this issue
- If PR exists and is open, review it and learn from it
- CRITICAL: Check if PR has merge conflicts before asking for review:
- Use: curl -s "https://$instance/api/v1/repos/$owner/$repo/pulls/$number" -H "Authorization: Bearer \$GITEA_TOKEN"
- If "mergeable": false, there ARE conflicts - you MUST resolve them FIRST
- To resolve: cd to worktree, git fetch origin, git rebase origin/main, resolve conflicts, git rebase --continue, git push --force-with-lease
- Only after resolving conflicts (mergeable: true) can you ask for review
- If PR makes sense to continue, work on it instead
- If PR is not worth continuing, create a new branch/PR but explain in PR description why you're creating a new one instead of continuing the existing PR
3. Read README.md (if exists) to understand the general concept of this repository
4. Read CONTRIBUTING.md (if exists) to understand how to contribute
- If CONTRIBUTING.md doesn't exist, follow steps 5-9 as your guideline
5. Explore the repository to understand the codebase
6. If anything is unclear, post a comment on the issue asking for clarification before implementing
7. Implement the solution
8. Create a branch named fix/issue-$number and implement the fix
9. Create a PR when the implementation is complete using: tea pr create --repo $owner/$repo --title "Your PR title" --body "PR description"
- Make sure you are logged in with: tea login add --name gitea --token \$GITEA_TOKEN --url https://$instance
- If tea is not available, use: curl -X POST "https://$instance/api/v1/repos/$owner/$repo/pulls" -H "Authorization: Bearer \$GITEA_TOKEN" -H "Content-Type: application/json" -d '{"title":"PR Title","head":"branch-name","base":"main","body":"PR description"}'
Tools for PR interaction:
- Post issue/PR comment: curl -X POST "https://$instance/api/v1/repos/$owner/$repo/issues/$number/comments" -H "Authorization: Bearer \$GITEA_TOKEN" -H "Content-Type: application/json" -d '{"body":"Your comment"}'
- List PR comments: curl -s "https://$instance/api/v1/repos/$owner/$repo/issues/$number/comments" -H "Authorization: Bearer \$GITEA_TOKEN"
- List PR reviews: curl -s "https://$instance/api/v1/repos/$owner/$repo/pulls/$number/reviews" -H "Authorization: Bearer \$GITEA_TOKEN"
- Merge PR (only with approval): tea pr merge --repo $owner/$repo $number --style merge
- MERGING requires approval first! Check for: approval in reviews, OR "lgtm"/"approved" in comments
- If no approval, ask reviewer to approve first before merging
Work directory: $worktree_path
EOF
fi
}
ensure_worktree() {
@@ -411,20 +400,20 @@ ensure_session() {
session_exists=true
fi
if [ "$worktree_exists" = true ] && [ "$session_exists" = true ]; then
if $worktree_exists && $session_exists; then
log "info" "ensure_session" "Session already exists for $issue_ref"
echo "continued"
return 0
fi
if [ "$worktree_exists" = false ] && [ "$session_exists" = true ]; then
if ! $worktree_exists && $session_exists; then
log "warn" "ensure_session" "Session exists but worktree is missing. Removing stale session..."
rm -f "$session_path"
remove_issue_from_index "$issue_ref"
session_exists=false
fi
if [ "$worktree_exists" = false ]; then
if ! $worktree_exists; then
local wt_status=$(ensure_worktree "$issue_ref")
if [ "$wt_status" != "created" ] && [ "$wt_status" != "existed" ]; then
log "error" "ensure_session" "Failed to ensure worktree for $issue_ref"
@@ -522,8 +511,6 @@ cmd_continue() {
exit 1
fi
kugetsu_context_dump "$issue_ref" "$message" "$(issue_ref_to_branch_name "$issue_ref")"
local session_file=$(issue_ref_to_filename "$issue_ref")
local session_path="$SESSIONS_DIR/$session_file"
local opencode_session_id=$(python3 -c "import json; print(json.load(open('$session_path')).get('opencode_session_id', ''))" 2>/dev/null || echo "")

View File

@@ -76,8 +76,7 @@ create_worktree() {
exit 1
fi
local worktree_parent_dir
worktree_parent_dir=$(dirname "$worktree_path")
local worktree_parent_dir=$(dirname "$worktree_path")
mkdir -p "$worktree_parent_dir"
if worktree_exists "$issue_ref" "$parent_dir"; then
@@ -86,36 +85,15 @@ create_worktree() {
fi
echo "Creating worktree at '$worktree_path'..."
local clone_success=false
local attempt=0
local max_attempts="${NETWORK_RETRY_ATTEMPTS:-3}"
while [ $attempt -lt $max_attempts ] && [ "$clone_success" = false ]; do
attempt=$((attempt + 1))
if [ $attempt -gt 1 ]; then
echo "Clone attempt $attempt of $max_attempts..."
sleep "$((attempt * 2))"
fi
if git clone "$repo_url" "$worktree_path" 2>/dev/null; then
clone_success=true
fi
done
if [ "$clone_success" = false ]; then
echo "Error: Failed to clone repository after $max_attempts attempts" >&2
git clone "$repo_url" "$worktree_path" 2>/dev/null || {
echo "Error: Failed to clone repository" >&2
exit 1
fi
}
echo "Creating branch '$branch_name'..."
if git -C "$worktree_path" checkout -b "$branch_name" origin/main 2>/dev/null; then
:
elif git -C "$worktree_path" checkout -b "$branch_name" main 2>/dev/null; then
:
else
(cd "$worktree_path" && git checkout -b "$branch_name" origin/main 2>/dev/null || git checkout -b "$branch_name" main 2>/dev/null) || {
echo "Warning: Could not checkout branch (may need to run from within worktree after session)" >&2
fi
}
echo "Worktree created at: $worktree_path"
}
@@ -167,17 +145,15 @@ check_pr_status() {
token="${GITEA_TOKEN:-}"
fi
local response_file="$KUGETSU_DIR/.pr_status_response_$$.json"
local response
if [ -n "$token" ]; then
curl -s -H "Authorization: token $token" "$api_url" > "$response_file" 2>/dev/null || printf '{}' > "$response_file"
response=$(curl -s -H "Authorization: token $token" "$api_url" 2>/dev/null || echo "{}")
else
curl -s "$api_url" > "$response_file" 2>/dev/null || printf '{}' > "$response_file"
response=$(curl -s "$api_url" 2>/dev/null || echo "{}")
fi
local state=$(python3 -c "import json; print(json.load(open('$response_file')).get('state', 'unknown'))" 2>/dev/null || echo "unknown")
local merged=$(python3 -c "import json; print('true' if json.load(open('$response_file')).get('merged', False) else 'false')" 2>/dev/null || echo "false")
rm -f "$response_file"
local state=$(echo "$response" | python3 -c "import json, sys; d=json.load(sys.stdin); print(d.get('state', 'unknown'))" 2>/dev/null || echo "unknown")
local merged=$(echo "$response" | python3 -c "import json, sys; d=json.load(sys.stdin); print('true' if d.get('merged', False) else 'false')" 2>/dev/null || echo "false")
if [ "$merged" = "true" ]; then
echo "merged"