refactor: modularize kugetsu shell script

Split the monolithic kugetsu script into modular components:

Modules created:
- kugetsu-config.sh - Config/env loading and global variables
- kugetsu-index.sh - Index.json read/write via JSON
- kugetsu-worktree.sh - Git worktree operations
- kugetsu-log.sh - Structured logging and notifications
- kugetsu-session.sh - Session create/fork/destroy logic
- kugetsu-queue-daemon.sh - Queue daemon subprocess

Main script (kugetsu) is now a thin dispatcher that sources all modules.

Acceptance Criteria:
- All existing commands work exactly as before
- Main script sources modules
- Each module is independently testable

Fixes #116
This commit is contained in:
shokollm
2026-04-05 10:17:25 +00:00
parent da0fa302de
commit f61fbd6dd5
7 changed files with 1301 additions and 1879 deletions

View File

@@ -0,0 +1,135 @@
#!/bin/bash
set -euo pipefail
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.get('issues', {}).get('$issue_ref') or 'null')"
}
set_base_in_index() {
local session_id="$1"
local index=$(read_index)
local pm_agent=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('pm_agent') or 'null')")
local issues=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d.get('issues', {})))")
if [ "$session_id" = "null" ]; then
write_index "null" "$pm_agent" "$issues"
else
write_index "\"$session_id\"" "$pm_agent" "$issues"
fi
}
set_pm_agent_in_index() {
local session_id="$1"
local index=$(read_index)
local base=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('base') or 'null')")
local issues=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d.get('issues', {})))")
if [ "$session_id" = "null" ]; then
write_index "$base" "null" "$issues"
else
write_index "$base" "\"$session_id\"" "$issues"
fi
}
add_issue_to_index() {
local issue_ref="$1"
local session_file="$2"
local index=$(read_index)
local base=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('base') or 'null')")
local pm_agent=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('pm_agent') or 'null')")
local issues=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d.get('issues', {})))")
issues=$(python3 -c "import sys, json; d=json.load(sys.stdin); d['$issue_ref']='$session_file'; print(json.dumps(d))" <<< "$issues")
write_index "$base" "$pm_agent" "$issues"
}
remove_issue_from_index() {
local issue_ref="$1"
local index=$(read_index)
local base=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('base') or 'null')")
local pm_agent=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('pm_agent') or 'null')")
local issues=$(echo "$index" | python3 -c "import sys, json; d=json.load(sys.stdin); print(json.dumps(d.get('issues', {})))")
issues=$(python3 -c "import sys, json; d=json.load(sys.stdin); d.pop('$issue_ref', None); print(json.dumps(d))" <<< "$issues")
write_index "$base" "$pm_agent" "$issues"
}
validate_issue_ref() {
local issue_ref="$1"
if [[ ! "$issue_ref" =~ ^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+#[0-9]+$ ]]; then
echo "Error: Invalid issue ref format: '$issue_ref'" >&2
echo "Expected format: instance/user/repo#number" >&2
echo "Example: github.com/shoko/kugetsu#14" >&2
exit 1
fi
}
update_session_pr_url() {
local issue_ref="$1"
local pr_url="$2"
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 for $issue_ref: $pr_url")
PYEOF
}