Merge origin/main into fix/issue-116-modularize-script
This commit is contained in:
@@ -447,6 +447,534 @@ check_task_timeouts() {
|
||||
done
|
||||
}
|
||||
|
||||
cleanup_old_queue_items() {
|
||||
local days="${QUEUE_CLEANUP_AGE_DAYS:-7}"
|
||||
|
||||
if [ ! -d "$QUEUE_ITEMS_DIR" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
find "$QUEUE_ITEMS_DIR" -name "*.json" -type f -mtime "+$days" 2>/dev/null | while read -r file; do
|
||||
local state=$(python3 -c "import json; print(json.load(open('$file')).get('state', ''))" 2>/dev/null || echo "")
|
||||
if [ "$state" = "completed" ] || [ "$state" = "error" ]; then
|
||||
rm -f "$file"
|
||||
echo "Cleaned up: $(basename "$file")"
|
||||
fi
|
||||
done
|
||||
}
|
||||
update_session_pr_url() {
|
||||
local issue_ref="$1"
|
||||
local pr_url="$2"
|
||||
|
||||
if [ -z "$issue_ref" ] || [ -z "$pr_url" ]; then
|
||||
echo "Error: update_session_pr_url requires <issue-ref> and <pr-url>" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local session_file=$(get_session_for_issue "$issue_ref")
|
||||
if [ -z "$session_file" ] || [ "$session_file" = "null" ]; then
|
||||
echo "Error: No session found for '$issue_ref'" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local session_path="$SESSIONS_DIR/$session_file"
|
||||
|
||||
if [ ! -f "$session_path" ]; then
|
||||
echo "Error: Session file not found: $session_path" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
python3 << PYEOF
|
||||
import json
|
||||
|
||||
session_path = "$session_path"
|
||||
pr_url = "$pr_url"
|
||||
|
||||
with open(session_path, 'r') as f:
|
||||
session = json.load(f)
|
||||
|
||||
session['pr_url'] = pr_url
|
||||
|
||||
with open(session_path, 'w') as f:
|
||||
json.dump(session, f, indent=2)
|
||||
|
||||
print(f"Updated pr_url to: {pr_url}")
|
||||
PYEOF
|
||||
}
|
||||
|
||||
read_index() {
|
||||
if [ -f "$INDEX_FILE" ]; then
|
||||
cat "$INDEX_FILE"
|
||||
else
|
||||
echo '{"base": null, "pm_agent": null, "issues": {}}'
|
||||
fi
|
||||
}
|
||||
|
||||
write_index() {
|
||||
local base="$1"
|
||||
local pm_agent="$2"
|
||||
local issues_json="$3"
|
||||
local temp_file="$INDEX_FILE.tmp.$$"
|
||||
printf '{"base": %s, "pm_agent": %s, "issues": %s}\n' "$base" "$pm_agent" "$issues_json" > "$temp_file"
|
||||
mv "$temp_file" "$INDEX_FILE"
|
||||
}
|
||||
|
||||
get_base_session_id() {
|
||||
local index=$(read_index)
|
||||
echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('base') or '')"
|
||||
}
|
||||
|
||||
get_pm_agent_session_id() {
|
||||
local index=$(read_index)
|
||||
echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('pm_agent') or '')"
|
||||
}
|
||||
|
||||
get_session_for_issue() {
|
||||
local issue_ref="$1"
|
||||
local index=$(read_index)
|
||||
echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d['issues'].get('$issue_ref') or '')"
|
||||
}
|
||||
|
||||
set_base_in_index() {
|
||||
local base_session_id="$1"
|
||||
local pm_agent=$(get_pm_agent_session_id)
|
||||
local issues_json=$(read_index | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d['issues']))")
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ]; then
|
||||
write_index "\"$base_session_id\"" "null" "$issues_json"
|
||||
else
|
||||
write_index "\"$base_session_id\"" "\"$pm_agent\"" "$issues_json"
|
||||
fi
|
||||
}
|
||||
|
||||
set_pm_agent_in_index() {
|
||||
local pm_agent_session_id="$1"
|
||||
local base=$(get_base_session_id)
|
||||
local issues_json=$(read_index | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d['issues']))")
|
||||
if [ -z "$base" ] || [ "$base" = "null" ]; then
|
||||
write_index "null" "\"$pm_agent_session_id\"" "$issues_json"
|
||||
else
|
||||
write_index "\"$base\"" "\"$pm_agent_session_id\"" "$issues_json"
|
||||
fi
|
||||
}
|
||||
|
||||
add_issue_to_index() {
|
||||
local issue_ref="$1"
|
||||
local session_file="$2"
|
||||
local index=$(read_index)
|
||||
local base=$(get_base_session_id)
|
||||
local pm_agent=$(get_pm_agent_session_id)
|
||||
local issues=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d['issues']))")
|
||||
local new_issues=$(echo "$issues" | python3 -c "import sys, json; d=json.load(sys.stdin); d['$issue_ref']='$session_file'; print(json.dumps(d))")
|
||||
if [ -z "$base" ] || [ "$base" = "null" ]; then
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ]; then
|
||||
write_index "null" "null" "$new_issues"
|
||||
else
|
||||
write_index "null" "\"$pm_agent\"" "$new_issues"
|
||||
fi
|
||||
else
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ]; then
|
||||
write_index "\"$base\"" "null" "$new_issues"
|
||||
else
|
||||
write_index "\"$base\"" "\"$pm_agent\"" "$new_issues"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
remove_issue_from_index() {
|
||||
local issue_ref="$1"
|
||||
local index=$(read_index)
|
||||
local base=$(get_base_session_id)
|
||||
local pm_agent=$(get_pm_agent_session_id)
|
||||
local new_issues=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); d['issues'].pop('$issue_ref', None); print(json.dumps(d['issues']))")
|
||||
if [ -z "$base" ] || [ "$base" = "null" ]; then
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ]; then
|
||||
write_index "null" "null" "$new_issues"
|
||||
else
|
||||
write_index "null" "\"$pm_agent\"" "$new_issues"
|
||||
fi
|
||||
else
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ]; then
|
||||
write_index "\"$base\"" "null" "$new_issues"
|
||||
else
|
||||
write_index "\"$base\"" "\"$pm_agent\"" "$new_issues"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
validate_issue_ref() {
|
||||
local issue_ref="$1"
|
||||
if [[ ! "$issue_ref" =~ ^[^/]+/[^/]+/[^#]+#[0-9]+$ ]]; then
|
||||
echo "Error: invalid issue ref format" >&2
|
||||
echo "Expected: instance/user/repo#number" >&2
|
||||
echo "Example: github.com/shoko/kugetsu#14" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_opencode_session_exists() {
|
||||
local session_id="$1"
|
||||
opencode session list --format json 2>/dev/null | grep -q "\"$session_id\""
|
||||
}
|
||||
|
||||
kugetsu_get_pm_context() {
|
||||
local user_pm_context="${KUGETSU_DIR}/pm-agent.md"
|
||||
local skill_pm_context="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../pm/SKILL.md"
|
||||
|
||||
if [ -f "$user_pm_context" ]; then
|
||||
cat "$user_pm_context"
|
||||
elif [ -f "$skill_pm_context" ]; then
|
||||
cat "$skill_pm_context"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
kugetsu_get_fork_context() {
|
||||
local issue_ref="$1"
|
||||
local context=""
|
||||
|
||||
context="## IMPORTANT WORKING RULES
|
||||
|
||||
1. You are working on issue: $issue_ref
|
||||
2. If you encounter ANY error, blocker, or cannot complete the task:
|
||||
- STOP immediately
|
||||
- Log what happened and why you cannot proceed
|
||||
- Do NOT switch to other work or try alternative approaches
|
||||
3. Do NOT work on other issues or PRs unless explicitly asked
|
||||
4. Environment variables are available in ~/.kugetsu/env/
|
||||
|
||||
"
|
||||
|
||||
if [ -f "$REPOS_CONFIG" ]; then
|
||||
context="${context}
|
||||
## REPOSITORIES CONFIG
|
||||
$(cat "$REPOS_CONFIG")
|
||||
|
||||
"
|
||||
fi
|
||||
|
||||
if [ -f "$ENV_DIR/default.env" ]; then
|
||||
context="${context}
|
||||
## ENVIRONMENT (available at ~/.kugetsu/env/)
|
||||
Environment file exists at: $ENV_DIR/default.env
|
||||
Source it with: source ~/.kugetsu/env/default.env
|
||||
"
|
||||
fi
|
||||
|
||||
echo "$context"
|
||||
}
|
||||
|
||||
kugetsu_add_notification() {
|
||||
local type="$1"
|
||||
local message="$2"
|
||||
local issue_ref="${3:-}"
|
||||
local gitea_url="${4:-}"
|
||||
|
||||
mkdir -p "$(dirname "$NOTIFICATIONS_FILE")"
|
||||
|
||||
local notification=$(python3 << PYEOF
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
notification = {
|
||||
"type": "$type",
|
||||
"message": "$message",
|
||||
"issue_ref": "$issue_ref" if "$issue_ref" else None,
|
||||
"gitea_url": "$gitea_url" if "$gitea_url" else None,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"read": False
|
||||
}
|
||||
|
||||
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
|
||||
notifications = []
|
||||
|
||||
if os.path.exists(file_path):
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
notifications = json.load(f)
|
||||
except:
|
||||
notifications = []
|
||||
|
||||
notifications.append(notification)
|
||||
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(notifications, f, indent=2)
|
||||
|
||||
print("Notification added")
|
||||
PYEOF
|
||||
)
|
||||
echo "$notification"
|
||||
}
|
||||
|
||||
kugetsu_get_notifications() {
|
||||
local limit="${1:-10}"
|
||||
|
||||
if [ ! -f "$NOTIFICATIONS_FILE" ]; then
|
||||
echo "[]"
|
||||
return
|
||||
fi
|
||||
|
||||
python3 << PYEOF
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
print("[]")
|
||||
exit(0)
|
||||
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
notifications = json.load(f)
|
||||
|
||||
unread = [n for n in notifications if not n.get("read", False)]
|
||||
unread.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
|
||||
|
||||
for n in unread[:$limit]:
|
||||
ts = n.get("timestamp", "unknown")
|
||||
ntype = n.get("type", "info")
|
||||
msg = n.get("message", "")
|
||||
issue = n.get("issue_ref", "")
|
||||
gitea = n.get("gitea_url", "")
|
||||
|
||||
print(f"[{ts}] {ntype}: {msg}")
|
||||
if issue:
|
||||
print(f" Issue: {issue}")
|
||||
if gitea:
|
||||
print(f" Link: {gitea}")
|
||||
print()
|
||||
|
||||
if not unread:
|
||||
print("No unread notifications.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error reading notifications: {e}")
|
||||
PYEOF
|
||||
}
|
||||
|
||||
kugetsu_clear_notifications() {
|
||||
if [ ! -f "$NOTIFICATIONS_FILE" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
python3 << PYEOF
|
||||
import json
|
||||
import os
|
||||
|
||||
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
exit(0)
|
||||
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
notifications = json.load(f)
|
||||
|
||||
for n in notifications:
|
||||
n["read"] = True
|
||||
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(notifications, f, indent=2)
|
||||
|
||||
print("Notifications marked as read")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
PYEOF
|
||||
}
|
||||
|
||||
cmd_notify() {
|
||||
local action="${1:-}"
|
||||
|
||||
case "$action" in
|
||||
""|"list"|"show")
|
||||
kugetsu_get_notifications 10
|
||||
;;
|
||||
"clear")
|
||||
kugetsu_clear_notifications
|
||||
;;
|
||||
*)
|
||||
echo "Usage: kugetsu notify [list|clear]"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
if [ ! -f "$INDEX_FILE" ]; then
|
||||
echo "kugetsu_not_initialized"
|
||||
return
|
||||
fi
|
||||
|
||||
local base=$(get_base_session_id)
|
||||
local pm_agent=$(get_pm_agent_session_id)
|
||||
|
||||
if [ -z "$base" ] || [ "$base" = "null" ]; then
|
||||
echo "base_session_missing"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -z "$pm_agent" ] || [ "$pm_agent" = "null" ] || [ "$pm_agent" = "None" ]; then
|
||||
echo "pm_agent_missing"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "ok"
|
||||
}
|
||||
|
||||
get_verbosity_context() {
|
||||
local verbosity="${KUGETSU_VERBOSITY:-default}"
|
||||
local verbosity_file="$VERBOSITY_DIR/${verbosity}.md"
|
||||
|
||||
if [ -f "$verbosity_file" ]; then
|
||||
cat "$verbosity_file"
|
||||
else
|
||||
echo "## Verbosity: $verbosity"
|
||||
fi
|
||||
}
|
||||
|
||||
init_verbosity_templates() {
|
||||
mkdir -p "$VERBOSITY_DIR"
|
||||
|
||||
if [ ! -f "$VERBOSITY_DIR/verbose.md" ]; then
|
||||
cat > "$VERBOSITY_DIR/verbose.md" << 'EOF'
|
||||
## Verbosity: Verbose
|
||||
|
||||
You are operating in HIGH verbosity mode. Include ALL available context:
|
||||
- Full command outputs and their results
|
||||
- Detailed reasoning and thinking process
|
||||
- All file changes with diffs when relevant
|
||||
- Complete log excerpts
|
||||
- Comprehensive status updates
|
||||
- Ask clarifying questions when uncertain
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [ ! -f "$VERBOSITY_DIR/default.md" ]; then
|
||||
cat > "$VERBOSITY_DIR/default.md" << 'EOF'
|
||||
## Verbosity: Default
|
||||
|
||||
You are operating in NORMAL verbosity mode. Provide balanced output:
|
||||
- Standard command outputs and key results
|
||||
- Moderate reasoning detail
|
||||
- Important file changes summarized
|
||||
- Regular status updates
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [ ! -f "$VERBOSITY_DIR/quiet.md" ]; then
|
||||
cat > "$VERBOSITY_DIR/quiet.md" << 'EOF'
|
||||
## Verbosity: Quiet
|
||||
|
||||
You are operating in QUIET verbosity mode. Keep output minimal:
|
||||
- Only essential information
|
||||
- Brief status updates (1-2 sentences)
|
||||
- Final decisions only
|
||||
- Yes/No answers when appropriate
|
||||
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-Z]{2,}/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+#[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)
|
||||
owner=$(echo "$gitserver" | cut -d'/' -f2)
|
||||
repo=$(echo "$gitserver" | cut -d'/' -f3)
|
||||
issue_number=$(echo "$message" | grep -oE '#[0-9]+' | 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"
|
||||
}
|
||||
|
||||
>>>>>>> origin/main
|
||||
cmd_queue() {
|
||||
local action="${1:-list}"
|
||||
shift
|
||||
|
||||
Reference in New Issue
Block a user