@minakoto00/skills 0.1.0
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/LICENSE +21 -0
- package/README.md +50 -0
- package/bin/skills.js +58 -0
- package/install-skill.sh +213 -0
- package/package.json +16 -0
- package/skills/review-pr/README.md +163 -0
- package/skills/review-pr/SKILL.md +180 -0
- package/skills/review-pr/docs/examples.md +106 -0
- package/skills/review-pr/scripts/cluster_review_issues.sh +110 -0
- package/skills/review-pr/scripts/common.sh +101 -0
- package/skills/review-pr/scripts/detect_platform.sh +55 -0
- package/skills/review-pr/scripts/fetch_review_comments.sh +301 -0
- package/skills/review-pr/scripts/plan_review_validation_dispatch.sh +74 -0
- package/skills/review-pr/scripts/post_review_comment.sh +89 -0
- package/skills/review-pr/scripts/repo_policy.sh +132 -0
- package/skills/review-pr/scripts/resolve_review_target.sh +160 -0
- package/skills/review-pr/scripts/worktree_sync.sh +118 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
5
|
+
# shellcheck source=./common.sh
|
|
6
|
+
source "$SCRIPT_DIR/common.sh"
|
|
7
|
+
|
|
8
|
+
input_path=
|
|
9
|
+
|
|
10
|
+
usage() {
|
|
11
|
+
cat <<'USAGE'
|
|
12
|
+
Usage: plan_review_validation_dispatch.sh [--input <path>]
|
|
13
|
+
|
|
14
|
+
Read clustered review issues and emit a bounded subagent dispatch plan.
|
|
15
|
+
USAGE
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
while [[ $# -gt 0 ]]; do
|
|
19
|
+
case "$1" in
|
|
20
|
+
--input)
|
|
21
|
+
input_path=$2
|
|
22
|
+
shift 2
|
|
23
|
+
;;
|
|
24
|
+
--help|-h)
|
|
25
|
+
usage
|
|
26
|
+
exit 0
|
|
27
|
+
;;
|
|
28
|
+
*)
|
|
29
|
+
die "unknown argument: $1"
|
|
30
|
+
;;
|
|
31
|
+
esac
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
require_cmd jq
|
|
35
|
+
|
|
36
|
+
if [[ -n "$input_path" ]]; then
|
|
37
|
+
input_json=$(cat "$input_path")
|
|
38
|
+
else
|
|
39
|
+
input_json=$(cat)
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
jq -cn --argjson payload "$input_json" '
|
|
43
|
+
def cluster_items:
|
|
44
|
+
$payload.issue_clusters.items // [];
|
|
45
|
+
|
|
46
|
+
def planned_subagents($cluster_count):
|
|
47
|
+
if $cluster_count <= 0 then 0
|
|
48
|
+
elif $cluster_count <= 2 then 1
|
|
49
|
+
elif $cluster_count <= 4 then 2
|
|
50
|
+
elif $cluster_count <= 6 then 3
|
|
51
|
+
else 4
|
|
52
|
+
end;
|
|
53
|
+
|
|
54
|
+
cluster_items as $clusters
|
|
55
|
+
| ($clusters | length) as $cluster_count
|
|
56
|
+
| planned_subagents($cluster_count) as $subagent_count
|
|
57
|
+
| {
|
|
58
|
+
issue_cluster_count: $cluster_count,
|
|
59
|
+
subagent_count: $subagent_count,
|
|
60
|
+
assignments: [
|
|
61
|
+
range(0; $subagent_count)
|
|
62
|
+
| . as $index
|
|
63
|
+
| {
|
|
64
|
+
subagent_id: ("validation-" + (($index + 1) | tostring)),
|
|
65
|
+
cluster_ids: [
|
|
66
|
+
range(0; $cluster_count)
|
|
67
|
+
| select(. % $subagent_count == $index)
|
|
68
|
+
| $clusters[.].cluster_id
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
| .cluster_count = (.cluster_ids | length)
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
'
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
5
|
+
# shellcheck source=./common.sh
|
|
6
|
+
source "$SCRIPT_DIR/common.sh"
|
|
7
|
+
|
|
8
|
+
repo=.
|
|
9
|
+
platform=
|
|
10
|
+
number=
|
|
11
|
+
body_file=
|
|
12
|
+
dry_run=false
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
cat <<'USAGE'
|
|
16
|
+
Usage: post_review_comment.sh --repo <path> --number <id> --body-file <file> [--platform <github|gitlab>] [--dry-run]
|
|
17
|
+
|
|
18
|
+
Post a change plan and patch guidance comment to a GitHub PR or GitLab MR.
|
|
19
|
+
USAGE
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
while [[ $# -gt 0 ]]; do
|
|
23
|
+
case "$1" in
|
|
24
|
+
--repo)
|
|
25
|
+
repo=$2
|
|
26
|
+
shift 2
|
|
27
|
+
;;
|
|
28
|
+
--platform)
|
|
29
|
+
platform=$2
|
|
30
|
+
shift 2
|
|
31
|
+
;;
|
|
32
|
+
--number)
|
|
33
|
+
number=$2
|
|
34
|
+
shift 2
|
|
35
|
+
;;
|
|
36
|
+
--body-file)
|
|
37
|
+
body_file=$2
|
|
38
|
+
shift 2
|
|
39
|
+
;;
|
|
40
|
+
--dry-run)
|
|
41
|
+
dry_run=true
|
|
42
|
+
shift
|
|
43
|
+
;;
|
|
44
|
+
--help|-h)
|
|
45
|
+
usage
|
|
46
|
+
exit 0
|
|
47
|
+
;;
|
|
48
|
+
*)
|
|
49
|
+
die "unknown argument: $1"
|
|
50
|
+
;;
|
|
51
|
+
esac
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
repo=$(resolve_repo_root "$repo")
|
|
55
|
+
[[ -n "$number" ]] || die "--number is required"
|
|
56
|
+
[[ -n "$body_file" ]] || die "--body-file is required"
|
|
57
|
+
[[ -f "$body_file" ]] || die "body file not found: $body_file"
|
|
58
|
+
|
|
59
|
+
if [[ -z "$platform" ]]; then
|
|
60
|
+
platform=$("$SCRIPT_DIR/detect_platform.sh" --repo "$repo")
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
if [[ "$dry_run" == true ]]; then
|
|
64
|
+
print_kv platform "$platform"
|
|
65
|
+
print_kv repo_root "$repo"
|
|
66
|
+
print_kv number "$number"
|
|
67
|
+
print_kv body_file "$body_file"
|
|
68
|
+
exit 0
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
cd "$repo"
|
|
72
|
+
|
|
73
|
+
case "$platform" in
|
|
74
|
+
github)
|
|
75
|
+
require_cmd gh
|
|
76
|
+
gh pr comment "$number" --body-file "$body_file"
|
|
77
|
+
;;
|
|
78
|
+
gitlab)
|
|
79
|
+
require_cmd glab
|
|
80
|
+
glab mr note "$number" --message "$(cat "$body_file")"
|
|
81
|
+
;;
|
|
82
|
+
*)
|
|
83
|
+
die "unsupported platform: $platform"
|
|
84
|
+
;;
|
|
85
|
+
esac
|
|
86
|
+
|
|
87
|
+
print_kv platform "$platform"
|
|
88
|
+
print_kv number "$number"
|
|
89
|
+
print_kv body_file "$body_file"
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
5
|
+
# shellcheck source=./common.sh
|
|
6
|
+
source "$SCRIPT_DIR/common.sh"
|
|
7
|
+
|
|
8
|
+
repo=.
|
|
9
|
+
|
|
10
|
+
usage() {
|
|
11
|
+
cat <<'USAGE'
|
|
12
|
+
Usage: repo_policy.sh [--repo <path>]
|
|
13
|
+
|
|
14
|
+
Inspect AGENTS.md and CLAUDE.md, if present, and print the resolved worktree root policy.
|
|
15
|
+
USAGE
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
while [[ $# -gt 0 ]]; do
|
|
19
|
+
case "$1" in
|
|
20
|
+
--repo)
|
|
21
|
+
repo=$2
|
|
22
|
+
shift 2
|
|
23
|
+
;;
|
|
24
|
+
--help|-h)
|
|
25
|
+
usage
|
|
26
|
+
exit 0
|
|
27
|
+
;;
|
|
28
|
+
*)
|
|
29
|
+
die "unknown argument: $1"
|
|
30
|
+
;;
|
|
31
|
+
esac
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
repo=$(resolve_repo_root "$repo")
|
|
35
|
+
|
|
36
|
+
extract_from_line() {
|
|
37
|
+
local line=$1
|
|
38
|
+
local token=
|
|
39
|
+
local root_token=
|
|
40
|
+
|
|
41
|
+
while IFS= read -r token; do
|
|
42
|
+
[[ -n "$token" ]] || continue
|
|
43
|
+
root_token=$token
|
|
44
|
+
if [[ "$token" == ../*/* ]]; then
|
|
45
|
+
root_token="../${token#../}"
|
|
46
|
+
root_token="${root_token%%/*}"
|
|
47
|
+
fi
|
|
48
|
+
if [[ "$root_token" == *worktree* || "$root_token" == *worktrees* ]]; then
|
|
49
|
+
printf '%s\n' "$(expand_path "$root_token" "$repo")"
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
52
|
+
done < <(printf '%s\n' "$line" | grep -oE '\.\./[^[:space:]`]+|\./[^[:space:]`]+|/[^[:space:]`]+' || true)
|
|
53
|
+
|
|
54
|
+
while IFS= read -r token; do
|
|
55
|
+
token=${token#\`}
|
|
56
|
+
token=${token%\`}
|
|
57
|
+
if [[ "$token" == *worktree* || "$token" == *worktrees* ]]; then
|
|
58
|
+
printf '%s\n' "$(expand_path "$token" "$repo")"
|
|
59
|
+
return 0
|
|
60
|
+
fi
|
|
61
|
+
done < <(printf '%s\n' "$line" | grep -oE '\`[^\`]+\`' || true)
|
|
62
|
+
|
|
63
|
+
if [[ "$line" =~ ([A-Za-z0-9._-]+worktree[A-Za-z0-9._-]*)[[:space:]]+folder ]]; then
|
|
64
|
+
printf '%s\n' "$(expand_path "../${BASH_REMATCH[1]}" "$repo")"
|
|
65
|
+
return 0
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if [[ "$line" =~ \.[/]?worktrees ]]; then
|
|
69
|
+
printf '%s\n' "$(expand_path .worktrees "$repo")"
|
|
70
|
+
return 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
if [[ "$line" =~ (^|[^.[:alnum:]_])worktrees([^[:alnum:]_]|$) ]]; then
|
|
74
|
+
printf '%s\n' "$(expand_path worktrees "$repo")"
|
|
75
|
+
return 0
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
return 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
extract_policy() {
|
|
82
|
+
local file=$1
|
|
83
|
+
local match
|
|
84
|
+
while IFS= read -r line; do
|
|
85
|
+
match=$(extract_from_line "$line") || continue
|
|
86
|
+
printf '%s\n' "$match"
|
|
87
|
+
return 0
|
|
88
|
+
done < <(grep -i 'worktree' "$file" || true)
|
|
89
|
+
return 1
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
agents_file="$repo/AGENTS.md"
|
|
93
|
+
claude_file="$repo/CLAUDE.md"
|
|
94
|
+
agents_root=
|
|
95
|
+
claude_root=
|
|
96
|
+
|
|
97
|
+
if [[ -f "$agents_file" ]]; then
|
|
98
|
+
agents_root=$(extract_policy "$agents_file" || true)
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
if [[ -f "$claude_file" ]]; then
|
|
102
|
+
claude_root=$(extract_policy "$claude_file" || true)
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
if [[ -n "$agents_root" && -n "$claude_root" && "$agents_root" != "$claude_root" ]]; then
|
|
106
|
+
die "conflicting worktree roots: AGENTS.md=$agents_root CLAUDE.md=$claude_root"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
policy_root=${agents_root:-$claude_root}
|
|
110
|
+
policy_source=default
|
|
111
|
+
|
|
112
|
+
if [[ -n "$agents_root" ]]; then
|
|
113
|
+
policy_source=AGENTS.md
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
if [[ -n "$claude_root" && -z "$agents_root" ]]; then
|
|
117
|
+
policy_source=CLAUDE.md
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [[ -n "$claude_root" && -n "$agents_root" ]]; then
|
|
121
|
+
policy_source=AGENTS.md+CLAUDE.md
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
if [[ -z "$policy_root" ]]; then
|
|
125
|
+
policy_root=$(expand_path .worktrees "$repo")
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
print_kv repo_root "$repo"
|
|
129
|
+
print_kv policy_worktree_root "$policy_root"
|
|
130
|
+
print_kv policy_source "$policy_source"
|
|
131
|
+
print_kv agents_file "$agents_file"
|
|
132
|
+
print_kv claude_file "$claude_file"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
5
|
+
# shellcheck source=./common.sh
|
|
6
|
+
source "$SCRIPT_DIR/common.sh"
|
|
7
|
+
|
|
8
|
+
repo=.
|
|
9
|
+
platform=
|
|
10
|
+
number=
|
|
11
|
+
mode=
|
|
12
|
+
json_output=false
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
cat <<'USAGE'
|
|
16
|
+
Usage: resolve_review_target.sh --repo <path> (--latest | --number <id>) [--platform <github|gitlab>] [--json]
|
|
17
|
+
|
|
18
|
+
Resolve the latest open MR/PR or a specific MR/PR number and print normalized metadata.
|
|
19
|
+
Default output is shell-safe key=value lines.
|
|
20
|
+
USAGE
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
while [[ $# -gt 0 ]]; do
|
|
24
|
+
case "$1" in
|
|
25
|
+
--repo)
|
|
26
|
+
repo=$2
|
|
27
|
+
shift 2
|
|
28
|
+
;;
|
|
29
|
+
--platform)
|
|
30
|
+
platform=$2
|
|
31
|
+
shift 2
|
|
32
|
+
;;
|
|
33
|
+
--number)
|
|
34
|
+
number=$2
|
|
35
|
+
mode=number
|
|
36
|
+
shift 2
|
|
37
|
+
;;
|
|
38
|
+
--latest)
|
|
39
|
+
mode=latest
|
|
40
|
+
shift
|
|
41
|
+
;;
|
|
42
|
+
--json)
|
|
43
|
+
json_output=true
|
|
44
|
+
shift
|
|
45
|
+
;;
|
|
46
|
+
--help|-h)
|
|
47
|
+
usage
|
|
48
|
+
exit 0
|
|
49
|
+
;;
|
|
50
|
+
*)
|
|
51
|
+
die "unknown argument: $1"
|
|
52
|
+
;;
|
|
53
|
+
esac
|
|
54
|
+
done
|
|
55
|
+
|
|
56
|
+
repo=$(resolve_repo_root "$repo")
|
|
57
|
+
require_cmd jq
|
|
58
|
+
|
|
59
|
+
if [[ -z "$mode" ]]; then
|
|
60
|
+
die "choose exactly one of --latest or --number"
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
if [[ -z "$platform" ]]; then
|
|
64
|
+
platform=$("$SCRIPT_DIR/detect_platform.sh" --repo "$repo")
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
remote_url=$(origin_url "$repo")
|
|
68
|
+
repository=$(repository_slug_from_remote "$remote_url")
|
|
69
|
+
|
|
70
|
+
select_latest_number() {
|
|
71
|
+
local field=$1
|
|
72
|
+
jq -r --arg field "$field" 'sort_by(.updatedAt // .updated_at) | last | .[$field] // empty'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
emit_kv() {
|
|
76
|
+
local number_value=$1
|
|
77
|
+
local title=$2
|
|
78
|
+
local source_branch=$3
|
|
79
|
+
local target_branch=$4
|
|
80
|
+
local author=$5
|
|
81
|
+
local head_sha=$6
|
|
82
|
+
local updated_at=$7
|
|
83
|
+
local web_url=$8
|
|
84
|
+
|
|
85
|
+
print_kv platform "$platform"
|
|
86
|
+
print_kv repository "$repository"
|
|
87
|
+
print_kv number "$number_value"
|
|
88
|
+
print_kv title "$title"
|
|
89
|
+
print_kv source_branch "$source_branch"
|
|
90
|
+
print_kv target_branch "$target_branch"
|
|
91
|
+
print_kv author "$author"
|
|
92
|
+
print_kv head_sha "$head_sha"
|
|
93
|
+
print_kv updated_at "$updated_at"
|
|
94
|
+
print_kv web_url "$web_url"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
cd "$repo"
|
|
98
|
+
|
|
99
|
+
case "$platform" in
|
|
100
|
+
github)
|
|
101
|
+
require_cmd gh
|
|
102
|
+
|
|
103
|
+
if [[ "$mode" == latest ]]; then
|
|
104
|
+
prs=$(gh pr list --state open --limit 100 --json number,updatedAt)
|
|
105
|
+
number=$(printf '%s' "$prs" | select_latest_number number)
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
[[ -n "$number" ]] || die "no open GitHub pull requests found"
|
|
109
|
+
|
|
110
|
+
view=$(gh pr view "$number" --json number,title,headRefName,baseRefName,author,headRefOid,updatedAt,url)
|
|
111
|
+
number_value=$(printf '%s' "$view" | jq -r '.number')
|
|
112
|
+
title=$(printf '%s' "$view" | jq -r '.title')
|
|
113
|
+
source_branch=$(printf '%s' "$view" | jq -r '.headRefName')
|
|
114
|
+
target_branch=$(printf '%s' "$view" | jq -r '.baseRefName')
|
|
115
|
+
author=$(printf '%s' "$view" | jq -r '.author.login // .author.name // "unknown"')
|
|
116
|
+
head_sha=$(printf '%s' "$view" | jq -r '.headRefOid')
|
|
117
|
+
updated_at=$(printf '%s' "$view" | jq -r '.updatedAt')
|
|
118
|
+
web_url=$(printf '%s' "$view" | jq -r '.url')
|
|
119
|
+
;;
|
|
120
|
+
gitlab)
|
|
121
|
+
require_cmd glab
|
|
122
|
+
|
|
123
|
+
if [[ "$mode" == latest ]]; then
|
|
124
|
+
mrs=$(glab mr list --state opened --per-page 100 --output json)
|
|
125
|
+
number=$(printf '%s' "$mrs" | select_latest_number iid)
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
[[ -n "$number" ]] || die "no open GitLab merge requests found"
|
|
129
|
+
|
|
130
|
+
view=$(glab mr view "$number" --output json)
|
|
131
|
+
number_value=$(printf '%s' "$view" | jq -r '.iid')
|
|
132
|
+
title=$(printf '%s' "$view" | jq -r '.title')
|
|
133
|
+
source_branch=$(printf '%s' "$view" | jq -r '.source_branch')
|
|
134
|
+
target_branch=$(printf '%s' "$view" | jq -r '.target_branch')
|
|
135
|
+
author=$(printf '%s' "$view" | jq -r '.author.username // .author.name // "unknown"')
|
|
136
|
+
head_sha=$(printf '%s' "$view" | jq -r '.sha')
|
|
137
|
+
updated_at=$(printf '%s' "$view" | jq -r '.updated_at')
|
|
138
|
+
web_url=$(printf '%s' "$view" | jq -r '.web_url')
|
|
139
|
+
;;
|
|
140
|
+
*)
|
|
141
|
+
die "unsupported platform: $platform"
|
|
142
|
+
;;
|
|
143
|
+
esac
|
|
144
|
+
|
|
145
|
+
if [[ "$json_output" == true ]]; then
|
|
146
|
+
jq -cn \
|
|
147
|
+
--arg platform "$platform" \
|
|
148
|
+
--arg repository "$repository" \
|
|
149
|
+
--arg number "$number_value" \
|
|
150
|
+
--arg title "$title" \
|
|
151
|
+
--arg source_branch "$source_branch" \
|
|
152
|
+
--arg target_branch "$target_branch" \
|
|
153
|
+
--arg author "$author" \
|
|
154
|
+
--arg head_sha "$head_sha" \
|
|
155
|
+
--arg updated_at "$updated_at" \
|
|
156
|
+
--arg web_url "$web_url" \
|
|
157
|
+
'{platform:$platform,repository:$repository,number:$number,title:$title,source_branch:$source_branch,target_branch:$target_branch,author:$author,head_sha:$head_sha,updated_at:$updated_at,web_url:$web_url}'
|
|
158
|
+
else
|
|
159
|
+
emit_kv "$number_value" "$title" "$source_branch" "$target_branch" "$author" "$head_sha" "$updated_at" "$web_url"
|
|
160
|
+
fi
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
5
|
+
# shellcheck source=./common.sh
|
|
6
|
+
source "$SCRIPT_DIR/common.sh"
|
|
7
|
+
|
|
8
|
+
repo=.
|
|
9
|
+
remote=origin
|
|
10
|
+
source_branch=
|
|
11
|
+
head_sha=
|
|
12
|
+
dry_run=false
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
cat <<'USAGE'
|
|
16
|
+
Usage: worktree_sync.sh --repo <path> --source-branch <branch> [--head-sha <sha>] [--remote <name>] [--dry-run]
|
|
17
|
+
|
|
18
|
+
Reuse or create a worktree for the source branch, using AGENTS.md / CLAUDE.md policy when present.
|
|
19
|
+
USAGE
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
while [[ $# -gt 0 ]]; do
|
|
23
|
+
case "$1" in
|
|
24
|
+
--repo)
|
|
25
|
+
repo=$2
|
|
26
|
+
shift 2
|
|
27
|
+
;;
|
|
28
|
+
--source-branch)
|
|
29
|
+
source_branch=$2
|
|
30
|
+
shift 2
|
|
31
|
+
;;
|
|
32
|
+
--head-sha)
|
|
33
|
+
head_sha=$2
|
|
34
|
+
shift 2
|
|
35
|
+
;;
|
|
36
|
+
--remote)
|
|
37
|
+
remote=$2
|
|
38
|
+
shift 2
|
|
39
|
+
;;
|
|
40
|
+
--dry-run)
|
|
41
|
+
dry_run=true
|
|
42
|
+
shift
|
|
43
|
+
;;
|
|
44
|
+
--help|-h)
|
|
45
|
+
usage
|
|
46
|
+
exit 0
|
|
47
|
+
;;
|
|
48
|
+
*)
|
|
49
|
+
die "unknown argument: $1"
|
|
50
|
+
;;
|
|
51
|
+
esac
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
[[ -n "$source_branch" ]] || die "--source-branch is required"
|
|
55
|
+
repo=$(resolve_repo_root "$repo")
|
|
56
|
+
|
|
57
|
+
policy_output=$("$SCRIPT_DIR/repo_policy.sh" --repo "$repo")
|
|
58
|
+
load_kv_output "$policy_output"
|
|
59
|
+
|
|
60
|
+
worktree_name=$(sanitize_branch_name "$source_branch")
|
|
61
|
+
worktree_path="$policy_worktree_root/$worktree_name"
|
|
62
|
+
action=reused
|
|
63
|
+
|
|
64
|
+
worktree_exists=false
|
|
65
|
+
if git -C "$repo" worktree list --porcelain | grep -F "worktree $worktree_path" >/dev/null 2>&1; then
|
|
66
|
+
worktree_exists=true
|
|
67
|
+
elif [[ -d "$worktree_path" && -e "$worktree_path/.git" ]]; then
|
|
68
|
+
worktree_exists=true
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
if [[ "$worktree_exists" == true ]]; then
|
|
72
|
+
if [[ -n "$(git -C "$worktree_path" status --porcelain)" ]]; then
|
|
73
|
+
die "existing worktree has uncommitted changes: $worktree_path"
|
|
74
|
+
fi
|
|
75
|
+
else
|
|
76
|
+
action=created
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if [[ "$dry_run" == true ]]; then
|
|
80
|
+
print_kv repo_root "$repo"
|
|
81
|
+
print_kv worktree_path "$worktree_path"
|
|
82
|
+
print_kv worktree_action "$action"
|
|
83
|
+
print_kv source_branch "$source_branch"
|
|
84
|
+
print_kv remote "$remote"
|
|
85
|
+
print_kv head_sha "$head_sha"
|
|
86
|
+
exit 0
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
mkdir -p "$policy_worktree_root"
|
|
90
|
+
|
|
91
|
+
git -C "$repo" fetch "$remote" "$source_branch"
|
|
92
|
+
|
|
93
|
+
git -C "$repo" rev-parse --verify "$remote/$source_branch" >/dev/null 2>&1 || die "remote branch not found: $remote/$source_branch"
|
|
94
|
+
|
|
95
|
+
if [[ "$worktree_exists" == true ]]; then
|
|
96
|
+
git -C "$worktree_path" checkout "$source_branch"
|
|
97
|
+
git -C "$worktree_path" pull --ff-only "$remote" "$source_branch"
|
|
98
|
+
else
|
|
99
|
+
if git -C "$repo" show-ref --verify --quiet "refs/heads/$source_branch"; then
|
|
100
|
+
git -C "$repo" worktree add "$worktree_path" "$source_branch"
|
|
101
|
+
else
|
|
102
|
+
git -C "$repo" worktree add -b "$source_branch" "$worktree_path" "$remote/$source_branch"
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [[ -n "$head_sha" ]]; then
|
|
107
|
+
git -C "$worktree_path" fetch "$remote" "$source_branch"
|
|
108
|
+
git -C "$worktree_path" cat-file -e "$head_sha^{commit}" >/dev/null 2>&1 || die "head sha not available after fetch: $head_sha"
|
|
109
|
+
git -C "$worktree_path" reset --hard "$head_sha"
|
|
110
|
+
else
|
|
111
|
+
git -C "$worktree_path" reset --hard "$remote/$source_branch"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
print_kv repo_root "$repo"
|
|
115
|
+
print_kv worktree_path "$worktree_path"
|
|
116
|
+
print_kv worktree_action "$action"
|
|
117
|
+
print_kv source_branch "$source_branch"
|
|
118
|
+
print_kv current_head "$(git -C "$worktree_path" rev-parse HEAD)"
|