oh-my-claude-sisyphus 3.6.0 → 3.6.2
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.
- package/commands/omc-setup.md +3 -3
- package/commands/psm.md +180 -0
- package/commands/release.md +1 -1
- package/dist/__tests__/analytics/transcript-scanner.test.js +69 -27
- package/dist/__tests__/analytics/transcript-scanner.test.js.map +1 -1
- package/dist/__tests__/installer.test.js +2 -1
- package/dist/__tests__/installer.test.js.map +1 -1
- package/dist/analytics/session-manager.d.ts +24 -0
- package/dist/analytics/session-manager.d.ts.map +1 -1
- package/dist/analytics/session-manager.js +98 -9
- package/dist/analytics/session-manager.js.map +1 -1
- package/dist/analytics/transcript-scanner.d.ts +9 -8
- package/dist/analytics/transcript-scanner.d.ts.map +1 -1
- package/dist/analytics/transcript-scanner.js +146 -16
- package/dist/analytics/transcript-scanner.js.map +1 -1
- package/dist/hooks/empty-message-sanitizer/__tests__/index.test.d.ts +2 -0
- package/dist/hooks/empty-message-sanitizer/__tests__/index.test.d.ts.map +1 -0
- package/dist/hooks/empty-message-sanitizer/__tests__/index.test.js +416 -0
- package/dist/hooks/empty-message-sanitizer/__tests__/index.test.js.map +1 -0
- package/dist/hooks/keyword-detector/__tests__/index.test.d.ts +2 -0
- package/dist/hooks/keyword-detector/__tests__/index.test.d.ts.map +1 -0
- package/dist/hooks/keyword-detector/__tests__/index.test.js +427 -0
- package/dist/hooks/keyword-detector/__tests__/index.test.js.map +1 -0
- package/dist/hooks/subagent-tracker/index.d.ts +83 -0
- package/dist/hooks/subagent-tracker/index.d.ts.map +1 -0
- package/dist/hooks/subagent-tracker/index.js +207 -0
- package/dist/hooks/subagent-tracker/index.js.map +1 -0
- package/dist/hooks/think-mode/__tests__/index.test.d.ts +2 -0
- package/dist/hooks/think-mode/__tests__/index.test.d.ts.map +1 -0
- package/dist/hooks/think-mode/__tests__/index.test.js +556 -0
- package/dist/hooks/think-mode/__tests__/index.test.js.map +1 -0
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +8 -0
- package/dist/installer/index.js.map +1 -1
- package/docs/design/project-session-manager.md +1033 -0
- package/package.json +1 -1
- package/skills/omc-setup/SKILL.md +27 -3
- package/skills/project-session-manager/SKILL.md +410 -0
- package/skills/project-session-manager/lib/config.sh +86 -0
- package/skills/project-session-manager/lib/parse.sh +121 -0
- package/skills/project-session-manager/lib/session.sh +132 -0
- package/skills/project-session-manager/lib/tmux.sh +103 -0
- package/skills/project-session-manager/lib/worktree.sh +171 -0
- package/skills/project-session-manager/psm.sh +629 -0
- package/skills/project-session-manager/templates/feature.md +56 -0
- package/skills/project-session-manager/templates/issue-fix.md +57 -0
- package/skills/project-session-manager/templates/pr-review.md +65 -0
- package/skills/project-session-manager/templates/projects.json +19 -0
- package/skills/release/SKILL.md +1 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PSM Reference Parser
|
|
3
|
+
|
|
4
|
+
# Parse a reference string into components
|
|
5
|
+
# Supports:
|
|
6
|
+
# omc#123 -> alias=omc, number=123
|
|
7
|
+
# owner/repo#123 -> repo=owner/repo, number=123
|
|
8
|
+
# https://... -> parsed from URL
|
|
9
|
+
# #123 -> number=123 (use current repo)
|
|
10
|
+
#
|
|
11
|
+
# Usage: psm_parse_ref "omc#123"
|
|
12
|
+
# Returns: type|alias|repo|number|local_path|base
|
|
13
|
+
psm_parse_ref() {
|
|
14
|
+
local ref="$1"
|
|
15
|
+
local type=""
|
|
16
|
+
local alias=""
|
|
17
|
+
local repo=""
|
|
18
|
+
local number=""
|
|
19
|
+
local local_path=""
|
|
20
|
+
local base="main"
|
|
21
|
+
|
|
22
|
+
# GitHub PR URL
|
|
23
|
+
if [[ "$ref" =~ ^https://github\.com/([^/]+)/([^/]+)/pull/([0-9]+) ]]; then
|
|
24
|
+
repo="${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
|
|
25
|
+
number="${BASH_REMATCH[3]}"
|
|
26
|
+
type="pr"
|
|
27
|
+
# Try to find alias for this repo
|
|
28
|
+
alias=$(psm_find_alias_for_repo "$repo")
|
|
29
|
+
if [[ -n "$alias" ]]; then
|
|
30
|
+
IFS='|' read -r _ local_path base <<< "$(psm_get_project "$alias")"
|
|
31
|
+
fi
|
|
32
|
+
echo "pr|${alias:-}|$repo|$number|${local_path:-}|$base"
|
|
33
|
+
return 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# GitHub Issue URL
|
|
37
|
+
if [[ "$ref" =~ ^https://github\.com/([^/]+)/([^/]+)/issues/([0-9]+) ]]; then
|
|
38
|
+
repo="${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
|
|
39
|
+
number="${BASH_REMATCH[3]}"
|
|
40
|
+
type="issue"
|
|
41
|
+
alias=$(psm_find_alias_for_repo "$repo")
|
|
42
|
+
if [[ -n "$alias" ]]; then
|
|
43
|
+
IFS='|' read -r _ local_path base <<< "$(psm_get_project "$alias")"
|
|
44
|
+
fi
|
|
45
|
+
echo "issue|${alias:-}|$repo|$number|${local_path:-}|$base"
|
|
46
|
+
return 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# alias#number format (e.g., omc#123)
|
|
50
|
+
if [[ "$ref" =~ ^([a-zA-Z][a-zA-Z0-9_-]*)#([0-9]+)$ ]]; then
|
|
51
|
+
alias="${BASH_REMATCH[1]}"
|
|
52
|
+
number="${BASH_REMATCH[2]}"
|
|
53
|
+
|
|
54
|
+
local project_info
|
|
55
|
+
project_info=$(psm_get_project "$alias")
|
|
56
|
+
if [[ $? -eq 0 ]]; then
|
|
57
|
+
IFS='|' read -r repo local_path base <<< "$project_info"
|
|
58
|
+
# Determine type from context (default to issue, caller specifies)
|
|
59
|
+
echo "ref|$alias|$repo|$number|$local_path|$base"
|
|
60
|
+
return 0
|
|
61
|
+
else
|
|
62
|
+
echo "error|Unknown project alias: $alias|||"
|
|
63
|
+
return 1
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# owner/repo#number format
|
|
68
|
+
if [[ "$ref" =~ ^([a-zA-Z0-9_-]+)/([a-zA-Z0-9_.-]+)#([0-9]+)$ ]]; then
|
|
69
|
+
repo="${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
|
|
70
|
+
number="${BASH_REMATCH[3]}"
|
|
71
|
+
alias=$(psm_find_alias_for_repo "$repo")
|
|
72
|
+
if [[ -n "$alias" ]]; then
|
|
73
|
+
IFS='|' read -r _ local_path base <<< "$(psm_get_project "$alias")"
|
|
74
|
+
fi
|
|
75
|
+
echo "ref|${alias:-}|$repo|$number|${local_path:-}|$base"
|
|
76
|
+
return 0
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Just #number (use current repo)
|
|
80
|
+
if [[ "$ref" =~ ^#([0-9]+)$ ]]; then
|
|
81
|
+
number="${BASH_REMATCH[1]}"
|
|
82
|
+
# Detect repo from current directory
|
|
83
|
+
if git rev-parse --git-dir > /dev/null 2>&1; then
|
|
84
|
+
local remote_url=$(git remote get-url origin 2>/dev/null)
|
|
85
|
+
if [[ "$remote_url" =~ github\.com[:/]([^/]+)/([^/.]+) ]]; then
|
|
86
|
+
repo="${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
|
|
87
|
+
local_path=$(git rev-parse --show-toplevel)
|
|
88
|
+
alias=$(psm_find_alias_for_repo "$repo")
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
echo "ref|${alias:-}|${repo:-}|$number|${local_path:-}|$base"
|
|
92
|
+
return 0
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
echo "error|Cannot parse reference: $ref|||"
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Find project alias for a given repo
|
|
100
|
+
psm_find_alias_for_repo() {
|
|
101
|
+
local target_repo="$1"
|
|
102
|
+
if [[ ! -f "$PSM_PROJECTS" ]]; then
|
|
103
|
+
return 1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
jq -r ".aliases | to_entries[] | select(.value.repo == \"$target_repo\") | .key" "$PSM_PROJECTS" | head -1
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Sanitize a string for use in filenames/session names
|
|
110
|
+
psm_sanitize() {
|
|
111
|
+
local input="$1"
|
|
112
|
+
# Remove path traversal, convert to lowercase, replace spaces with dashes
|
|
113
|
+
echo "$input" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g' | sed 's/--*/-/g' | head -c 30
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Generate a slug from title
|
|
117
|
+
psm_slugify() {
|
|
118
|
+
local title="$1"
|
|
119
|
+
local max_len="${2:-30}"
|
|
120
|
+
echo "$title" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//' | head -c "$max_len"
|
|
121
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PSM Session Registry Management
|
|
3
|
+
|
|
4
|
+
# Add session to registry
|
|
5
|
+
# Usage: psm_add_session <id> <type> <project> <ref> <branch> <base> <tmux> <worktree> <source_repo> <metadata_json>
|
|
6
|
+
psm_add_session() {
|
|
7
|
+
local id="$1"
|
|
8
|
+
local type="$2"
|
|
9
|
+
local project="$3"
|
|
10
|
+
local ref="$4"
|
|
11
|
+
local branch="$5"
|
|
12
|
+
local base="$6"
|
|
13
|
+
local tmux_session="$7"
|
|
14
|
+
local worktree="$8"
|
|
15
|
+
local source_repo="$9"
|
|
16
|
+
local metadata="${10:-{}}"
|
|
17
|
+
|
|
18
|
+
local now=$(date -Iseconds)
|
|
19
|
+
|
|
20
|
+
local tmp=$(mktemp)
|
|
21
|
+
jq --arg id "$id" \
|
|
22
|
+
--arg type "$type" \
|
|
23
|
+
--arg project "$project" \
|
|
24
|
+
--arg ref "$ref" \
|
|
25
|
+
--arg branch "$branch" \
|
|
26
|
+
--arg base "$base" \
|
|
27
|
+
--arg tmux "$tmux_session" \
|
|
28
|
+
--arg worktree "$worktree" \
|
|
29
|
+
--arg source "$source_repo" \
|
|
30
|
+
--arg now "$now" \
|
|
31
|
+
--argjson meta "$metadata" \
|
|
32
|
+
'.sessions[$id] = {
|
|
33
|
+
"id": $id,
|
|
34
|
+
"type": $type,
|
|
35
|
+
"project": $project,
|
|
36
|
+
"ref": $ref,
|
|
37
|
+
"branch": $branch,
|
|
38
|
+
"base": $base,
|
|
39
|
+
"tmux": $tmux,
|
|
40
|
+
"worktree": $worktree,
|
|
41
|
+
"source_repo": $source,
|
|
42
|
+
"created_at": $now,
|
|
43
|
+
"last_accessed": $now,
|
|
44
|
+
"state": "active",
|
|
45
|
+
"metadata": $meta
|
|
46
|
+
} | .stats.total_created += 1' \
|
|
47
|
+
"$PSM_SESSIONS" > "$tmp" && mv "$tmp" "$PSM_SESSIONS"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Get session by ID
|
|
51
|
+
# Usage: psm_get_session <id>
|
|
52
|
+
psm_get_session() {
|
|
53
|
+
local id="$1"
|
|
54
|
+
jq -r ".sessions[\"$id\"] // empty" "$PSM_SESSIONS"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Update session state
|
|
58
|
+
# Usage: psm_update_session_state <id> <state>
|
|
59
|
+
psm_update_session_state() {
|
|
60
|
+
local id="$1"
|
|
61
|
+
local state="$2"
|
|
62
|
+
local now=$(date -Iseconds)
|
|
63
|
+
|
|
64
|
+
local tmp=$(mktemp)
|
|
65
|
+
jq --arg id "$id" \
|
|
66
|
+
--arg state "$state" \
|
|
67
|
+
--arg now "$now" \
|
|
68
|
+
'.sessions[$id].state = $state | .sessions[$id].last_accessed = $now' \
|
|
69
|
+
"$PSM_SESSIONS" > "$tmp" && mv "$tmp" "$PSM_SESSIONS"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Remove session from registry
|
|
73
|
+
# Usage: psm_remove_session <id>
|
|
74
|
+
psm_remove_session() {
|
|
75
|
+
local id="$1"
|
|
76
|
+
|
|
77
|
+
local tmp=$(mktemp)
|
|
78
|
+
jq --arg id "$id" \
|
|
79
|
+
'del(.sessions[$id]) | .stats.total_cleaned += 1' \
|
|
80
|
+
"$PSM_SESSIONS" > "$tmp" && mv "$tmp" "$PSM_SESSIONS"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# List all sessions
|
|
84
|
+
# Usage: psm_list_sessions [project]
|
|
85
|
+
psm_list_sessions() {
|
|
86
|
+
local project="$1"
|
|
87
|
+
|
|
88
|
+
if [[ -n "$project" ]]; then
|
|
89
|
+
jq -r ".sessions | to_entries[] | select(.value.project == \"$project\") | .value | \"\(.id)|\(.type)|\(.state)|\(.worktree)\"" "$PSM_SESSIONS"
|
|
90
|
+
else
|
|
91
|
+
jq -r '.sessions | to_entries[] | .value | "\(.id)|\(.type)|\(.state)|\(.worktree)"' "$PSM_SESSIONS"
|
|
92
|
+
fi
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Get sessions by state
|
|
96
|
+
psm_get_sessions_by_state() {
|
|
97
|
+
local state="$1"
|
|
98
|
+
jq -r ".sessions | to_entries[] | select(.value.state == \"$state\") | .value.id" "$PSM_SESSIONS"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Get session count
|
|
102
|
+
psm_session_count() {
|
|
103
|
+
jq -r '.sessions | length' "$PSM_SESSIONS"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Write session metadata file in worktree
|
|
107
|
+
# Usage: psm_write_session_metadata <worktree_path> <session_json>
|
|
108
|
+
psm_write_session_metadata() {
|
|
109
|
+
local worktree_path="$1"
|
|
110
|
+
local session_json="$2"
|
|
111
|
+
|
|
112
|
+
echo "$session_json" > "${worktree_path}/.psm-session.json"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Read session metadata from worktree
|
|
116
|
+
psm_read_session_metadata() {
|
|
117
|
+
local worktree_path="$1"
|
|
118
|
+
local meta_file="${worktree_path}/.psm-session.json"
|
|
119
|
+
|
|
120
|
+
if [[ -f "$meta_file" ]]; then
|
|
121
|
+
cat "$meta_file"
|
|
122
|
+
fi
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Get all session IDs for cleanup check
|
|
126
|
+
psm_get_review_sessions() {
|
|
127
|
+
jq -r '.sessions | to_entries[] | select(.value.type == "review") | "\(.value.id)|\(.value.metadata.pr_number // empty)|\(.value.project)"' "$PSM_SESSIONS"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
psm_get_fix_sessions() {
|
|
131
|
+
jq -r '.sessions | to_entries[] | select(.value.type == "fix") | "\(.value.id)|\(.value.metadata.issue_number // empty)|\(.value.project)"' "$PSM_SESSIONS"
|
|
132
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PSM Tmux Session Management
|
|
3
|
+
|
|
4
|
+
# Check if tmux is available
|
|
5
|
+
psm_has_tmux() {
|
|
6
|
+
command -v tmux &> /dev/null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
# Create a tmux session
|
|
10
|
+
# Usage: psm_create_tmux_session <session_name> <working_dir>
|
|
11
|
+
psm_create_tmux_session() {
|
|
12
|
+
local session_name="$1"
|
|
13
|
+
local working_dir="$2"
|
|
14
|
+
|
|
15
|
+
if ! psm_has_tmux; then
|
|
16
|
+
echo "error|tmux not found"
|
|
17
|
+
return 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Check if session already exists
|
|
21
|
+
if tmux has-session -t "$session_name" 2>/dev/null; then
|
|
22
|
+
echo "exists|$session_name"
|
|
23
|
+
return 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Create detached session
|
|
27
|
+
tmux new-session -d -s "$session_name" -c "$working_dir" 2>/dev/null || {
|
|
28
|
+
echo "error|Failed to create tmux session"
|
|
29
|
+
return 1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
echo "created|$session_name"
|
|
33
|
+
return 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Launch Claude Code in tmux session
|
|
37
|
+
# Usage: psm_launch_claude <session_name>
|
|
38
|
+
psm_launch_claude() {
|
|
39
|
+
local session_name="$1"
|
|
40
|
+
|
|
41
|
+
if ! tmux has-session -t "$session_name" 2>/dev/null; then
|
|
42
|
+
echo "error|Session not found: $session_name"
|
|
43
|
+
return 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Send claude command to the session
|
|
47
|
+
tmux send-keys -t "$session_name" "claude" Enter
|
|
48
|
+
|
|
49
|
+
echo "launched|$session_name"
|
|
50
|
+
return 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Kill a tmux session
|
|
54
|
+
# Usage: psm_kill_tmux_session <session_name>
|
|
55
|
+
psm_kill_tmux_session() {
|
|
56
|
+
local session_name="$1"
|
|
57
|
+
|
|
58
|
+
if ! tmux has-session -t "$session_name" 2>/dev/null; then
|
|
59
|
+
echo "not_found|$session_name"
|
|
60
|
+
return 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
tmux kill-session -t "$session_name" 2>/dev/null || {
|
|
64
|
+
echo "error|Failed to kill session"
|
|
65
|
+
return 1
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
echo "killed|$session_name"
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# List all PSM tmux sessions
|
|
73
|
+
psm_list_tmux_sessions() {
|
|
74
|
+
if ! psm_has_tmux; then
|
|
75
|
+
return 0
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
tmux list-sessions -F "#{session_name}|#{session_created}|#{session_attached}" 2>/dev/null | grep "^psm:" || true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Check if a tmux session exists
|
|
82
|
+
# Usage: psm_tmux_session_exists <session_name>
|
|
83
|
+
psm_tmux_session_exists() {
|
|
84
|
+
local session_name="$1"
|
|
85
|
+
tmux has-session -t "$session_name" 2>/dev/null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Get current tmux session name
|
|
89
|
+
psm_current_tmux_session() {
|
|
90
|
+
if [[ -n "$TMUX" ]]; then
|
|
91
|
+
tmux display-message -p "#{session_name}" 2>/dev/null
|
|
92
|
+
fi
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Generate tmux session name
|
|
96
|
+
# Usage: psm_tmux_session_name <alias> <type> <id>
|
|
97
|
+
psm_tmux_session_name() {
|
|
98
|
+
local alias="$1"
|
|
99
|
+
local type="$2"
|
|
100
|
+
local id="$3"
|
|
101
|
+
|
|
102
|
+
echo "psm:${alias}:${type}-${id}"
|
|
103
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PSM Worktree Management
|
|
3
|
+
|
|
4
|
+
# Create a worktree for PR review
|
|
5
|
+
# Usage: psm_create_pr_worktree <local_repo> <alias> <pr_number> <pr_branch>
|
|
6
|
+
psm_create_pr_worktree() {
|
|
7
|
+
local local_repo="$1"
|
|
8
|
+
local alias="$2"
|
|
9
|
+
local pr_number="$3"
|
|
10
|
+
local pr_branch="$4"
|
|
11
|
+
|
|
12
|
+
local worktree_root=$(psm_get_worktree_root)
|
|
13
|
+
local worktree_path="${worktree_root}/${alias}/pr-${pr_number}"
|
|
14
|
+
|
|
15
|
+
# Check if worktree already exists
|
|
16
|
+
if [[ -d "$worktree_path" ]]; then
|
|
17
|
+
echo "exists|$worktree_path"
|
|
18
|
+
return 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Ensure parent directory exists
|
|
22
|
+
mkdir -p "${worktree_root}/${alias}"
|
|
23
|
+
|
|
24
|
+
# Fetch the PR branch
|
|
25
|
+
cd "$local_repo" || return 1
|
|
26
|
+
git fetch origin "pull/${pr_number}/head:psm-pr-${pr_number}-review" 2>/dev/null || {
|
|
27
|
+
echo "error|Failed to fetch PR #${pr_number}"
|
|
28
|
+
return 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Create worktree
|
|
32
|
+
git worktree add "$worktree_path" "psm-pr-${pr_number}-review" 2>/dev/null || {
|
|
33
|
+
echo "error|Failed to create worktree"
|
|
34
|
+
return 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
echo "created|$worktree_path"
|
|
38
|
+
return 0
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Create a worktree for issue fix
|
|
42
|
+
# Usage: psm_create_issue_worktree <local_repo> <alias> <issue_number> <slug> <base_branch>
|
|
43
|
+
psm_create_issue_worktree() {
|
|
44
|
+
local local_repo="$1"
|
|
45
|
+
local alias="$2"
|
|
46
|
+
local issue_number="$3"
|
|
47
|
+
local slug="$4"
|
|
48
|
+
local base_branch="${5:-main}"
|
|
49
|
+
|
|
50
|
+
local worktree_root=$(psm_get_worktree_root)
|
|
51
|
+
local worktree_path="${worktree_root}/${alias}/issue-${issue_number}"
|
|
52
|
+
local branch_name="fix/${issue_number}-${slug}"
|
|
53
|
+
|
|
54
|
+
# Check if worktree already exists
|
|
55
|
+
if [[ -d "$worktree_path" ]]; then
|
|
56
|
+
echo "exists|$worktree_path|$branch_name"
|
|
57
|
+
return 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
mkdir -p "${worktree_root}/${alias}"
|
|
61
|
+
|
|
62
|
+
cd "$local_repo" || return 1
|
|
63
|
+
|
|
64
|
+
# Fetch latest from origin
|
|
65
|
+
git fetch origin "$base_branch" 2>/dev/null || {
|
|
66
|
+
echo "error|Failed to fetch $base_branch"
|
|
67
|
+
return 1
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Create and checkout new branch
|
|
71
|
+
git branch "$branch_name" "origin/$base_branch" 2>/dev/null || {
|
|
72
|
+
# Branch might already exist
|
|
73
|
+
true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Create worktree
|
|
77
|
+
git worktree add "$worktree_path" "$branch_name" 2>/dev/null || {
|
|
78
|
+
echo "error|Failed to create worktree"
|
|
79
|
+
return 1
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
echo "created|$worktree_path|$branch_name"
|
|
83
|
+
return 0
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Create a worktree for feature development
|
|
87
|
+
# Usage: psm_create_feature_worktree <local_repo> <alias> <feature_name> <base_branch>
|
|
88
|
+
psm_create_feature_worktree() {
|
|
89
|
+
local local_repo="$1"
|
|
90
|
+
local alias="$2"
|
|
91
|
+
local feature_name="$3"
|
|
92
|
+
local base_branch="${4:-main}"
|
|
93
|
+
|
|
94
|
+
local worktree_root=$(psm_get_worktree_root)
|
|
95
|
+
local safe_name=$(psm_sanitize "$feature_name")
|
|
96
|
+
local worktree_path="${worktree_root}/${alias}/feat-${safe_name}"
|
|
97
|
+
local branch_name="feature/${safe_name}"
|
|
98
|
+
|
|
99
|
+
# Check if worktree already exists
|
|
100
|
+
if [[ -d "$worktree_path" ]]; then
|
|
101
|
+
echo "exists|$worktree_path|$branch_name"
|
|
102
|
+
return 1
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
mkdir -p "${worktree_root}/${alias}"
|
|
106
|
+
|
|
107
|
+
cd "$local_repo" || return 1
|
|
108
|
+
|
|
109
|
+
# Fetch latest
|
|
110
|
+
git fetch origin "$base_branch" 2>/dev/null || {
|
|
111
|
+
echo "error|Failed to fetch $base_branch"
|
|
112
|
+
return 1
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Create branch
|
|
116
|
+
git branch "$branch_name" "origin/$base_branch" 2>/dev/null || true
|
|
117
|
+
|
|
118
|
+
# Create worktree
|
|
119
|
+
git worktree add "$worktree_path" "$branch_name" 2>/dev/null || {
|
|
120
|
+
echo "error|Failed to create worktree"
|
|
121
|
+
return 1
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
echo "created|$worktree_path|$branch_name"
|
|
125
|
+
return 0
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Remove a worktree
|
|
129
|
+
# Usage: psm_remove_worktree <local_repo> <worktree_path>
|
|
130
|
+
psm_remove_worktree() {
|
|
131
|
+
local local_repo="$1"
|
|
132
|
+
local worktree_path="$2"
|
|
133
|
+
|
|
134
|
+
if [[ ! -d "$worktree_path" ]]; then
|
|
135
|
+
echo "not_found|$worktree_path"
|
|
136
|
+
return 1
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Check for uncommitted changes
|
|
140
|
+
if [[ -d "$worktree_path/.git" ]] || [[ -f "$worktree_path/.git" ]]; then
|
|
141
|
+
cd "$worktree_path" || return 1
|
|
142
|
+
if [[ -n $(git status --porcelain 2>/dev/null) ]]; then
|
|
143
|
+
echo "dirty|$worktree_path"
|
|
144
|
+
return 1
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
cd "$local_repo" || return 1
|
|
149
|
+
git worktree remove "$worktree_path" --force 2>/dev/null || {
|
|
150
|
+
# Force remove the directory if git worktree remove fails
|
|
151
|
+
rm -rf "$worktree_path"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
echo "removed|$worktree_path"
|
|
155
|
+
return 0
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# List all PSM worktrees
|
|
159
|
+
psm_list_worktrees() {
|
|
160
|
+
local worktree_root=$(psm_get_worktree_root)
|
|
161
|
+
|
|
162
|
+
if [[ ! -d "$worktree_root" ]]; then
|
|
163
|
+
return 0
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
find "$worktree_root" -mindepth 2 -maxdepth 2 -type d 2>/dev/null | while read -r dir; do
|
|
167
|
+
local alias=$(basename "$(dirname "$dir")")
|
|
168
|
+
local name=$(basename "$dir")
|
|
169
|
+
echo "${alias}:${name}|${dir}"
|
|
170
|
+
done
|
|
171
|
+
}
|