From 91505345a285c321d6380ff5f59107a27783976a Mon Sep 17 00:00:00 2001 From: shokollm <270575765+shokollm@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:20:48 +0000 Subject: [PATCH] feat(kugetsu): smart delegate with worktree awareness - Parse issue refs from message (gitserver.com/owner/repo/issues/123 or owner/repo#123) - Find existing worktrees/sessions by issue number - Ask user to confirm which worktree to use, or delegate anyway - Inject missing info context to PM agent - Inject selected worktree context to PM agent Fixes #128 --- skills/kugetsu/scripts/kugetsu | 159 ++++++++++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 1 deletion(-) diff --git a/skills/kugetsu/scripts/kugetsu b/skills/kugetsu/scripts/kugetsu index 79bc0c4..c3248c6 100755 --- a/skills/kugetsu/scripts/kugetsu +++ b/skills/kugetsu/scripts/kugetsu @@ -641,6 +641,99 @@ EOF fi } +parse_issue_ref_from_message() { + local message="$1" + + local gitserver="" + local owner="" + local repo="" + local issue_number="" + + if echo "$message" | grep -qE '[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(issues|pull)/[0-9]+'; then + gitserver=$(echo "$message" | grep -oE '[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+' | head -1 | sed 's/\/[^/]*\/[^/]*$//') + local full_path=$(echo "$message" | grep -oE '[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(issues|pull)/[0-9]+' | head -1) + owner=$(echo "$full_path" | cut -d'/' -f2) + repo=$(echo "$full_path" | cut -d'/' -f3) + issue_number=$(echo "$full_path" | grep -oE '[0-9]+$' | head -1) + elif echo "$message" | grep -qE '[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+#([0-9]+)'; then + owner=$(echo "$message" | grep -oE '[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+#' | sed 's/#$//' | cut -d'/' -f1) + repo=$(echo "$message" | grep -oE '[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+#' | sed 's/#$//' | cut -d'/' -f2) + issue_number=$(echo "$message" | grep -oE '#[0-9]+' | grep -oE '[0-9]+' | head -1) + fi + + echo "${gitserver}|${owner}|${repo}|${issue_number}" +} + +get_missing_info() { + local parsed="$1" + local gitserver=$(echo "$parsed" | cut -d'|' -f1) + local owner=$(echo "$parsed" | cut -d'|' -f2) + local repo=$(echo "$parsed" | cut -d'|' -f3) + local issue_number=$(echo "$parsed" | cut -d'|' -f4) + + local missing="" + [ -z "$gitserver" ] && missing="${missing}git server, " + [ -z "$owner" ] && missing="${missing}owner, " + [ -z "$repo" ] && missing="${missing}repository, " + [ -z "$issue_number" ] && missing="${missing}issue number, " + + echo "$missing" | sed 's/, $//' +} + +build_missing_info_context() { + local missing="$1" + if [ -n "$missing" ]; then + echo "" + echo "NOTE: This task delegation has no information about: ${missing}." + echo "We need them if user wants to work on a specific issue. Otherwise we don't need it." + fi +} + +find_worktrees_by_issue_number() { + local issue_number="$1" + local results="" + + if [ ! -d "$WORKTREES_DIR/.kugetsu-worktrees" ]; then + echo "" + return + fi + + for wt in "$WORKTREES_DIR/.kugetsu-worktrees"/*; do + if [ -d "$wt" ]; then + local wt_issue_number=$(echo "$wt" | grep -oE '#?[0-9]+$' | grep -oE '[0-9]+' | head -1) + if [ "$wt_issue_number" = "$issue_number" ]; then + results="${results}${wt}:worktree +" + fi + fi + done + + echo "$results" +} + +find_sessions_by_issue_number() { + local issue_number="$1" + local results="" + + if [ ! -d "$SESSIONS_DIR" ]; then + echo "" + return + fi + + for session_file in "$SESSIONS_DIR"/*.json; do + if [ -f "$session_file" ]; then + local session_issue_ref=$(basename "$session_file" .json | sed 's/_/\//g') + local session_issue_number=$(echo "$session_issue_ref" | grep -oE '#?[0-9]+$' | grep -oE '[0-9]+' | head -1) + if [ "$session_issue_number" = "$issue_number" ]; then + results="${results}${session_file}:session +" + fi + fi + done + + echo "$results" +} + cmd_delegate() { local message="${1:-}" local verbosity="${KUGETSU_VERBOSITY:-default}" @@ -660,6 +753,70 @@ cmd_delegate() { mkdir -p "$LOGS_DIR" local log_file="$LOGS_DIR/delegate-$(date +%s).log" + local parsed=$(parse_issue_ref_from_message "$message") + local gitserver=$(echo "$parsed" | cut -d'|' -f1) + local owner=$(echo "$parsed" | cut -d'|' -f2) + local repo=$(echo "$parsed" | cut -d'|' -f3) + local issue_number=$(echo "$parsed" | cut -d'|' -f4) + + local missing_info=$(get_missing_info "$parsed") + local context_injection="" + if [ -n "$missing_info" ]; then + context_injection=$(build_missing_info_context "$missing_info") + echo "NOTE: Delegation missing information: ${missing_info}" + fi + + local candidates="" + local candidate_count=0 + + if [ -n "$issue_number" ]; then + local worktrees=$(find_worktrees_by_issue_number "$issue_number") + local sessions=$(find_sessions_by_issue_number "$issue_number") + + while IFS=: read -r path type; do + if [ -n "$path" ]; then + candidate_count=$((candidate_count + 1)) + candidates="${candidates}${candidate_count}) ${path} (${type}) +" + fi + done <<< "$worktrees" + + while IFS=: read -r path type; do + if [ -n "$path" ]; then + candidate_count=$((candidate_count + 1)) + candidates="${candidates}${candidate_count}) ${path} (${type}) +" + fi + done <<< "$sessions" + fi + + local use_worktree="" + if [ $candidate_count -gt 0 ]; then + echo "Found $candidate_count existing worktree(s)/session(s) for issue #${issue_number}:" + echo "$candidates" + echo "r) Delegate anyway (without routing)" + echo "Which one to use? [1-${candidate_count}/r]: " + read -r choice + + if [ "$choice" = "r" ] || [ -z "$choice" ]; then + use_worktree="" + elif [ "$choice" -ge 1 ] && [ "$choice" -le "$candidate_count" ]; then + local selected=$(echo "$candidates" | sed -n "${choice}p") + use_worktree=$(echo "$selected" | sed 's/) .*//') + fi + fi + + local final_message="${message}${context_injection}" + + if [ -n "$use_worktree" ]; then + if [ -d "$use_worktree" ]; then + echo "Using worktree: $use_worktree" + final_message="${final_message} + +NOTE: Worktree selected: ${use_worktree}" + fi + fi + local temp_dir="${KUGETSU_TEMP_DIR:-$HOME/.local/share/opencode/tool-output}" mkdir -p "$ENV_DIR" @@ -671,7 +828,7 @@ cmd_delegate() { fi env_sh="${env_sh}set +a; " - nohup sh -c "${env_sh}opencode run '$message' --continue --session '$pm_session' >> '$log_file' 2>&1" > /dev/null 2>&1 & + nohup sh -c "${env_sh}opencode run '${final_message}' --continue --session '$pm_session' >> '$log_file' 2>&1" > /dev/null 2>&1 & disown echo "Delegated to PM agent (logged to $(basename "$log_file"))" echo "Verbosity: $verbosity"