@weotro/dx 0.1.5 → 0.1.6
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/README.md +2 -0
- package/agent-references/ship-issue-pr-core.md +502 -0
- package/lib/codex-initial.js +64 -0
- package/package.json +2 -1
- package/skills/cc-ship-issue-pr/SKILL.md +40 -0
- package/skills/cc-ship-issue-pr/agents/openai.yaml +2 -0
- package/skills/cc-ship-issue-pr/references/runtime.md +96 -0
- package/skills/git-release/SKILL.md +19 -3
- package/skills/git-release/agents/openai.yaml +2 -2
- package/skills/git-release/references/post-release-follow-up.md +158 -0
- package/skills/oo-ship-issue-pr/SKILL.md +44 -0
- package/skills/oo-ship-issue-pr/agents/openai.yaml +2 -0
- package/skills/oo-ship-issue-pr/references/runtime.md +92 -0
- package/skills/prune-git-repository/SKILL.md +83 -0
- package/skills/prune-git-repository/agents/openai.yaml +6 -0
- package/skills/prune-git-repository/references/pitfalls.md +43 -0
- package/skills/prune-git-repository/scripts/prune-branches.sh +136 -0
- package/skills/prune-git-repository/scripts/prune-tags-releases.sh +117 -0
- package/skills/prune-git-repository/scripts/prune-worktrees.sh +71 -0
- package/skills/create-issue/SKILL.md +0 -90
- package/skills/ship-issue-pr/SKILL.md +0 -742
- package/skills/third-party-review/SKILL.md +0 -64
- package/skills/third-party-review/agents/openai.yaml +0 -6
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
remote=origin
|
|
5
|
+
base=main
|
|
6
|
+
stale_days=20
|
|
7
|
+
execute=false
|
|
8
|
+
|
|
9
|
+
while [[ $# -gt 0 ]]; do
|
|
10
|
+
case "$1" in
|
|
11
|
+
--remote) remote=$2; shift 2 ;;
|
|
12
|
+
--base) base=$2; shift 2 ;;
|
|
13
|
+
--stale-days) stale_days=$2; shift 2 ;;
|
|
14
|
+
--execute) execute=true; shift ;;
|
|
15
|
+
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
|
16
|
+
esac
|
|
17
|
+
done
|
|
18
|
+
|
|
19
|
+
[[ "$stale_days" =~ ^[0-9]+$ ]] || { echo "--stale-days must be a non-negative integer" >&2; exit 2; }
|
|
20
|
+
command -v rtk >/dev/null || { echo "rtk is required" >&2; exit 1; }
|
|
21
|
+
git_raw() { rtk proxy git "$@"; }
|
|
22
|
+
|
|
23
|
+
git_raw rev-parse --is-inside-work-tree >/dev/null
|
|
24
|
+
git_raw fetch "$remote" --prune
|
|
25
|
+
base_ref="refs/remotes/$remote/$base"
|
|
26
|
+
git_raw show-ref --verify --quiet "$base_ref" || { echo "missing $base_ref" >&2; exit 1; }
|
|
27
|
+
|
|
28
|
+
if cutoff=$(date -v-"${stale_days}"d +%s 2>/dev/null); then
|
|
29
|
+
cutoff_iso=$(date -r "$cutoff" '+%Y-%m-%d %H:%M:%S %z')
|
|
30
|
+
else
|
|
31
|
+
cutoff=$(date -d "$stale_days days ago" +%s)
|
|
32
|
+
cutoff_iso=$(date -d "@$cutoff" '+%Y-%m-%d %H:%M:%S %z')
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
tmp=$(mktemp -d)
|
|
36
|
+
trap 'rm -rf "$tmp"' EXIT
|
|
37
|
+
: > "$tmp/local"
|
|
38
|
+
: > "$tmp/remote"
|
|
39
|
+
: > "$tmp/protected"
|
|
40
|
+
current=$(git_raw branch --show-current)
|
|
41
|
+
git_raw worktree list --porcelain | sed -n 's/^branch refs\/heads\///p' > "$tmp/checked-out"
|
|
42
|
+
|
|
43
|
+
git_raw for-each-ref --format='%(refname)|%(refname:short)|%(committerdate:unix)' refs/heads > "$tmp/local-refs"
|
|
44
|
+
while IFS='|' read -r ref short ts; do
|
|
45
|
+
[[ "$short" == "$base" ]] && continue
|
|
46
|
+
reason=
|
|
47
|
+
if git_raw merge-base --is-ancestor "$ref" "$base_ref"; then
|
|
48
|
+
reason=merged
|
|
49
|
+
elif [[ "$ts" -le "$cutoff" ]]; then
|
|
50
|
+
reason=stale-unmerged
|
|
51
|
+
fi
|
|
52
|
+
if [[ -n "$reason" ]] && grep -Fqx "$short" "$tmp/checked-out"; then
|
|
53
|
+
printf 'local|%s|%s\n' "$short" "$reason" >> "$tmp/protected"
|
|
54
|
+
elif [[ -n "$reason" ]]; then
|
|
55
|
+
printf '%s|%s|%s\n' "$short" "$reason" "$ts" >> "$tmp/local"
|
|
56
|
+
fi
|
|
57
|
+
done < "$tmp/local-refs"
|
|
58
|
+
|
|
59
|
+
git_raw for-each-ref --format='%(refname)|%(committerdate:unix)' "refs/remotes/$remote" > "$tmp/remote-refs"
|
|
60
|
+
while IFS='|' read -r ref ts; do
|
|
61
|
+
[[ "$ref" == "refs/remotes/$remote/HEAD" || "$ref" == "$base_ref" ]] && continue
|
|
62
|
+
name=${ref#"refs/remotes/$remote/"}
|
|
63
|
+
[[ "$name" == "$ref" || -z "$name" ]] && continue
|
|
64
|
+
reason=
|
|
65
|
+
if git_raw merge-base --is-ancestor "$ref" "$base_ref"; then
|
|
66
|
+
reason=merged
|
|
67
|
+
elif [[ "$ts" -le "$cutoff" ]]; then
|
|
68
|
+
reason=stale-unmerged
|
|
69
|
+
fi
|
|
70
|
+
if [[ -n "$reason" ]] && grep -Fqx "$name" "$tmp/checked-out"; then
|
|
71
|
+
printf 'remote|%s|%s\n' "$name" "$reason" >> "$tmp/protected"
|
|
72
|
+
elif [[ -n "$reason" ]]; then
|
|
73
|
+
printf '%s|%s|%s\n' "$name" "$reason" "$ts" >> "$tmp/remote"
|
|
74
|
+
fi
|
|
75
|
+
done < "$tmp/remote-refs"
|
|
76
|
+
|
|
77
|
+
echo "cutoff=$cutoff_iso"
|
|
78
|
+
echo "local_candidates=$(wc -l < "$tmp/local" | tr -d ' ')"
|
|
79
|
+
awk -F'|' '{print " " $1 " [" $2 "]"}' "$tmp/local"
|
|
80
|
+
echo "remote_candidates=$(wc -l < "$tmp/remote" | tr -d ' ')"
|
|
81
|
+
awk -F'|' '{print " " $1 " [" $2 "]"}' "$tmp/remote"
|
|
82
|
+
echo "protected_checked_out=$(wc -l < "$tmp/protected" | tr -d ' ')"
|
|
83
|
+
awk -F'|' '{print " " $1 ":" $2 " [" $3 "]"}' "$tmp/protected"
|
|
84
|
+
|
|
85
|
+
if ! $execute; then
|
|
86
|
+
echo "dry_run=true"
|
|
87
|
+
exit 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
failures=0
|
|
91
|
+
|
|
92
|
+
batch=()
|
|
93
|
+
flush_remote_batch() {
|
|
94
|
+
[[ ${#batch[@]} -eq 0 ]] && return 0
|
|
95
|
+
if ! git_raw push "$remote" "${batch[@]}"; then
|
|
96
|
+
failures=$((failures + 1))
|
|
97
|
+
fi
|
|
98
|
+
batch=()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
while IFS='|' read -r name reason ts; do
|
|
102
|
+
[[ -z "$name" ]] && continue
|
|
103
|
+
batch+=(":refs/heads/$name")
|
|
104
|
+
if [[ ${#batch[@]} -ge 30 ]]; then flush_remote_batch; fi
|
|
105
|
+
done < "$tmp/remote"
|
|
106
|
+
flush_remote_batch
|
|
107
|
+
|
|
108
|
+
local_deleted=0
|
|
109
|
+
while IFS='|' read -r name reason ts; do
|
|
110
|
+
[[ -z "$name" ]] && continue
|
|
111
|
+
if git_raw branch -D -- "$name"; then
|
|
112
|
+
local_deleted=$((local_deleted + 1))
|
|
113
|
+
else
|
|
114
|
+
failures=$((failures + 1))
|
|
115
|
+
fi
|
|
116
|
+
done < "$tmp/local"
|
|
117
|
+
|
|
118
|
+
git_raw fetch "$remote" --prune
|
|
119
|
+
remote_left=0
|
|
120
|
+
while IFS='|' read -r name reason ts; do
|
|
121
|
+
[[ -z "$name" ]] && continue
|
|
122
|
+
if git_raw ls-remote --exit-code --heads "$remote" "refs/heads/$name" >/dev/null 2>&1; then
|
|
123
|
+
remote_left=$((remote_left + 1))
|
|
124
|
+
fi
|
|
125
|
+
done < "$tmp/remote"
|
|
126
|
+
|
|
127
|
+
local_left=0
|
|
128
|
+
while IFS='|' read -r name reason ts; do
|
|
129
|
+
[[ -z "$name" ]] && continue
|
|
130
|
+
if git_raw show-ref --verify --quiet "refs/heads/$name"; then
|
|
131
|
+
local_left=$((local_left + 1))
|
|
132
|
+
fi
|
|
133
|
+
done < "$tmp/local"
|
|
134
|
+
|
|
135
|
+
echo "local_deleted=$local_deleted local_left=$local_left remote_left=$remote_left failures=$failures"
|
|
136
|
+
[[ "$local_left" -eq 0 && "$remote_left" -eq 0 && "$failures" -eq 0 ]]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
remote=origin
|
|
5
|
+
keep=10
|
|
6
|
+
execute=false
|
|
7
|
+
tags_only=false
|
|
8
|
+
|
|
9
|
+
while [[ $# -gt 0 ]]; do
|
|
10
|
+
case "$1" in
|
|
11
|
+
--remote) remote=$2; shift 2 ;;
|
|
12
|
+
--keep) keep=$2; shift 2 ;;
|
|
13
|
+
--execute) execute=true; shift ;;
|
|
14
|
+
--tags-only) tags_only=true; shift ;;
|
|
15
|
+
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
|
16
|
+
esac
|
|
17
|
+
done
|
|
18
|
+
|
|
19
|
+
[[ "$keep" =~ ^[1-9][0-9]*$ ]] || { echo "--keep must be a positive integer" >&2; exit 2; }
|
|
20
|
+
command -v rtk >/dev/null || { echo "rtk is required" >&2; exit 1; }
|
|
21
|
+
git_raw() { rtk proxy git "$@"; }
|
|
22
|
+
gh_raw() { rtk proxy gh "$@"; }
|
|
23
|
+
jq_raw() { rtk proxy jq "$@"; }
|
|
24
|
+
|
|
25
|
+
git_raw rev-parse --is-inside-work-tree >/dev/null
|
|
26
|
+
git_raw fetch "$remote" --tags --prune
|
|
27
|
+
tmp=$(mktemp -d)
|
|
28
|
+
trap 'rm -rf "$tmp"' EXIT
|
|
29
|
+
|
|
30
|
+
git_raw for-each-ref --sort=-creatordate --format='%(refname:short)' refs/tags | sed -n "1,${keep}p" > "$tmp/keep-tags"
|
|
31
|
+
sort "$tmp/keep-tags" > "$tmp/keep-tags-sorted"
|
|
32
|
+
git_raw tag --list | sort > "$tmp/local-tags"
|
|
33
|
+
git_raw ls-remote --tags --refs "$remote" | awk '{sub("refs/tags/", "", $2); print $2}' | sort > "$tmp/remote-tags"
|
|
34
|
+
comm -23 "$tmp/local-tags" "$tmp/keep-tags-sorted" > "$tmp/delete-local"
|
|
35
|
+
comm -23 "$tmp/remote-tags" "$tmp/keep-tags-sorted" > "$tmp/delete-remote"
|
|
36
|
+
|
|
37
|
+
echo "keep_tags=$(wc -l < "$tmp/keep-tags" | tr -d ' ')"
|
|
38
|
+
sed 's/^/ /' "$tmp/keep-tags"
|
|
39
|
+
echo "delete_local_tags=$(wc -l < "$tmp/delete-local" | tr -d ' ')"
|
|
40
|
+
echo "delete_remote_tags=$(wc -l < "$tmp/delete-remote" | tr -d ' ')"
|
|
41
|
+
|
|
42
|
+
if ! $tags_only; then
|
|
43
|
+
command -v gh >/dev/null || { echo "gh is required for Release cleanup" >&2; exit 1; }
|
|
44
|
+
command -v jq >/dev/null || { echo "jq is required for Release cleanup" >&2; exit 1; }
|
|
45
|
+
gh_raw auth status >/dev/null
|
|
46
|
+
gh_raw release list --limit 1000 --json tagName,publishedAt,createdAt,isDraft,isPrerelease,name > "$tmp/releases.json"
|
|
47
|
+
jq_raw -r 'sort_by(.publishedAt // .createdAt) | reverse | .[].tagName' "$tmp/releases.json" | sed -n "1,${keep}p" > "$tmp/keep-releases"
|
|
48
|
+
jq_raw -r 'sort_by(.publishedAt // .createdAt) | reverse | .[].tagName' "$tmp/releases.json" | sed -n "$((keep + 1)),\$p" > "$tmp/delete-releases"
|
|
49
|
+
sort "$tmp/keep-releases" > "$tmp/keep-releases-sorted"
|
|
50
|
+
echo "keep_releases=$(wc -l < "$tmp/keep-releases" | tr -d ' ')"
|
|
51
|
+
sed 's/^/ /' "$tmp/keep-releases"
|
|
52
|
+
echo "delete_releases=$(wc -l < "$tmp/delete-releases" | tr -d ' ')"
|
|
53
|
+
if ! cmp -s "$tmp/keep-tags-sorted" "$tmp/keep-releases-sorted"; then
|
|
54
|
+
echo "retained tag and Release sets differ; refusing combined cleanup" >&2
|
|
55
|
+
diff -u "$tmp/keep-tags-sorted" "$tmp/keep-releases-sorted" || true
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
if ! $execute; then
|
|
61
|
+
echo "dry_run=true"
|
|
62
|
+
exit 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
if ! $tags_only; then
|
|
66
|
+
while IFS= read -r tag_name; do
|
|
67
|
+
[[ -z "$tag_name" ]] && continue
|
|
68
|
+
gh_raw release delete "$tag_name" --yes
|
|
69
|
+
done < "$tmp/delete-releases"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
batch=()
|
|
73
|
+
flush_tag_batch() {
|
|
74
|
+
[[ ${#batch[@]} -eq 0 ]] && return 0
|
|
75
|
+
git_raw push "$remote" "${batch[@]}"
|
|
76
|
+
batch=()
|
|
77
|
+
}
|
|
78
|
+
while IFS= read -r tag_name; do
|
|
79
|
+
[[ -z "$tag_name" ]] && continue
|
|
80
|
+
batch+=(":refs/tags/$tag_name")
|
|
81
|
+
if [[ ${#batch[@]} -ge 40 ]]; then flush_tag_batch; fi
|
|
82
|
+
done < "$tmp/delete-remote"
|
|
83
|
+
flush_tag_batch
|
|
84
|
+
|
|
85
|
+
batch=()
|
|
86
|
+
while IFS= read -r tag_name; do
|
|
87
|
+
[[ -z "$tag_name" ]] && continue
|
|
88
|
+
batch+=("$tag_name")
|
|
89
|
+
if [[ ${#batch[@]} -ge 80 ]]; then
|
|
90
|
+
git_raw tag -d "${batch[@]}"
|
|
91
|
+
batch=()
|
|
92
|
+
fi
|
|
93
|
+
done < "$tmp/delete-local"
|
|
94
|
+
[[ ${#batch[@]} -eq 0 ]] || git_raw tag -d "${batch[@]}"
|
|
95
|
+
|
|
96
|
+
# Synchronize retained local-only tags after old remote tags are removed.
|
|
97
|
+
while IFS= read -r tag_name; do
|
|
98
|
+
[[ -z "$tag_name" ]] && continue
|
|
99
|
+
if ! git_raw ls-remote --exit-code --tags --refs "$remote" "refs/tags/$tag_name" >/dev/null 2>&1; then
|
|
100
|
+
git_raw push "$remote" "refs/tags/$tag_name:refs/tags/$tag_name"
|
|
101
|
+
fi
|
|
102
|
+
done < "$tmp/keep-tags"
|
|
103
|
+
|
|
104
|
+
git_raw fetch "$remote" --prune --prune-tags
|
|
105
|
+
git_raw tag --list | sort > "$tmp/final-local"
|
|
106
|
+
git_raw ls-remote --tags --refs "$remote" | awk '{sub("refs/tags/", "", $2); print $2}' | sort > "$tmp/final-remote"
|
|
107
|
+
cmp -s "$tmp/final-local" "$tmp/final-remote" || { diff -u "$tmp/final-local" "$tmp/final-remote"; exit 1; }
|
|
108
|
+
[[ $(wc -l < "$tmp/final-local" | tr -d ' ') -eq $(wc -l < "$tmp/keep-tags" | tr -d ' ') ]]
|
|
109
|
+
|
|
110
|
+
if ! $tags_only; then
|
|
111
|
+
gh_raw release list --limit 1000 --json tagName,publishedAt,createdAt > "$tmp/final-releases.json"
|
|
112
|
+
jq_raw -r '.[].tagName' "$tmp/final-releases.json" | sort > "$tmp/final-release-tags"
|
|
113
|
+
cmp -s "$tmp/final-remote" "$tmp/final-release-tags" || { diff -u "$tmp/final-remote" "$tmp/final-release-tags"; exit 1; }
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
echo "final_tag_count=$(wc -l < "$tmp/final-local" | tr -d ' ')"
|
|
117
|
+
if ! $tags_only; then echo "final_release_count=$(jq_raw 'length' "$tmp/final-releases.json")"; fi
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
execute=false
|
|
5
|
+
if [[ "${1:-}" == "--execute" ]]; then
|
|
6
|
+
execute=true
|
|
7
|
+
elif [[ $# -ne 0 ]]; then
|
|
8
|
+
echo "usage: $0 [--execute]" >&2
|
|
9
|
+
exit 2
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
command -v rtk >/dev/null || { echo "rtk is required" >&2; exit 1; }
|
|
13
|
+
git_raw() { rtk proxy git "$@"; }
|
|
14
|
+
|
|
15
|
+
repo_root=$(git_raw rev-parse --show-toplevel)
|
|
16
|
+
repo_root=$(cd "$repo_root" && pwd -P)
|
|
17
|
+
tmp=$(mktemp -d)
|
|
18
|
+
trap 'rm -rf "$tmp"' EXIT
|
|
19
|
+
|
|
20
|
+
git_raw worktree list --porcelain > "$tmp/worktrees"
|
|
21
|
+
sed -n 's/^worktree //p' "$tmp/worktrees" | while IFS= read -r wt_path; do
|
|
22
|
+
[[ "$wt_path" == "$repo_root" ]] && continue
|
|
23
|
+
printf '%s\n' "$wt_path"
|
|
24
|
+
done > "$tmp/candidates"
|
|
25
|
+
|
|
26
|
+
count=$(wc -l < "$tmp/candidates" | tr -d ' ')
|
|
27
|
+
echo "current_worktree=$repo_root"
|
|
28
|
+
echo "candidate_worktrees=$count"
|
|
29
|
+
sed 's/^/ /' "$tmp/candidates"
|
|
30
|
+
|
|
31
|
+
if ! $execute; then
|
|
32
|
+
echo "dry_run=true"
|
|
33
|
+
exit 0
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
removed=0
|
|
37
|
+
failed=0
|
|
38
|
+
while IFS= read -r wt_path; do
|
|
39
|
+
[[ -z "$wt_path" ]] && continue
|
|
40
|
+
[[ "$wt_path" == "$repo_root" || "$wt_path" == "/" ]] && { echo "refusing unsafe path: $wt_path" >&2; exit 1; }
|
|
41
|
+
|
|
42
|
+
if [[ ! -e "$wt_path" ]]; then
|
|
43
|
+
continue
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if git_raw worktree remove --force --force -- "$wt_path"; then
|
|
47
|
+
removed=$((removed + 1))
|
|
48
|
+
continue
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
echo "git removal failed; using registered-path fallback: $wt_path" >&2
|
|
52
|
+
if rtk proxy /usr/bin/find "$wt_path" -mindepth 1 -maxdepth 1 -exec /bin/rm -rf -- '{}' + \
|
|
53
|
+
&& rtk proxy /usr/bin/find "$wt_path" -depth -delete >/dev/null 2>&1; then
|
|
54
|
+
removed=$((removed + 1))
|
|
55
|
+
else
|
|
56
|
+
failed=$((failed + 1))
|
|
57
|
+
fi
|
|
58
|
+
done < "$tmp/candidates"
|
|
59
|
+
|
|
60
|
+
git_raw worktree prune --expire now
|
|
61
|
+
git_raw worktree list --porcelain > "$tmp/final"
|
|
62
|
+
sed -n 's/^worktree //p' "$tmp/final" | awk -v current="$repo_root" '$0 != current' > "$tmp/remaining"
|
|
63
|
+
remaining=$(wc -l < "$tmp/remaining" | tr -d ' ')
|
|
64
|
+
|
|
65
|
+
echo "removed=$removed failed=$failed remaining=$remaining"
|
|
66
|
+
if [[ "$remaining" -ne 0 || "$failed" -ne 0 ]]; then
|
|
67
|
+
sed 's/^/ remaining: /' "$tmp/remaining" >&2
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
cat "$tmp/final"
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: create-issue
|
|
3
|
-
description: 仅在用户显式调用 $create-issue、/create-issue 或明确要求使用 create-issue 技能时使用;不要通过关键词、任务类型或上下文自动触发。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Create Issue
|
|
7
|
-
|
|
8
|
-
## Overview
|
|
9
|
-
|
|
10
|
-
Turn a converged discussion into issue(s) that read like **an architect handing a spec to a programmer**: the programmer can locate the work, understand the plan without asking back, and verify done objectively — without the user re-issuing instructions each time.
|
|
11
|
-
|
|
12
|
-
**Core principle — write it as a task dispatch, not a bug note.** Every issue must let a programmer execute end-to-end on first read. That means three things per finding:
|
|
13
|
-
1. **Stable location** — anchor to a durable identifier (`file` + function/class/symbol/section name, or a short quoted snippet). Line numbers are a *navigation hint only*, always approximate, never the anchor — code shifts and a hard line pin rots immediately and over-constrains the fix.
|
|
14
|
-
2. **An unambiguous plan** — what to change and why, with no decision left dangling that forces the programmer to come back and ask.
|
|
15
|
-
3. **An objective acceptance check** — verifiable by behavior/text/data-state plus a runnable command where applicable.
|
|
16
|
-
|
|
17
|
-
An issue that says "the button is wrong" forces the developer to re-investigate what you already know. An issue pinned to `Button.tsx:42` sends them to the wrong line after one edit. Anchor by symbol, plan the work, define done.
|
|
18
|
-
|
|
19
|
-
**Manual-only:** this skill NEVER auto-triggers on keywords (提issue / file an issue / etc.). Run it only when the user explicitly invokes `/create-issue` or names the skill.
|
|
20
|
-
|
|
21
|
-
## When to Use
|
|
22
|
-
|
|
23
|
-
Only on explicit invocation. Appropriate when a review/investigation has converged — you've already located code and compared against a source of truth, and actionable items emerged.
|
|
24
|
-
|
|
25
|
-
**Not for:** still-exploring discussions, trivial one-line fixes the user is about to do themselves, or vague ideas with no verifiable outcome.
|
|
26
|
-
|
|
27
|
-
## Workflow
|
|
28
|
-
|
|
29
|
-
1. **Probe the repo's issue conventions first.** Read `CONTRIBUTING`, issue templates (`.github/`), or any `git-workflow`/ruler docs; skim a recent `gh issue list`. Match their template, labels, title style. Never hardcode another repo's format.
|
|
30
|
-
2. **Group issues:**
|
|
31
|
-
- **3+ issues → always auto-create one parent/umbrella tracking issue + child issues. Do NOT ask, do NOT discuss.** Children link back with `Refs: #<parent>`; the parent lists children as a checklist and holds the cross-issue map.
|
|
32
|
-
- **Fewer than 3 → apply the Merge vs. Split table.** Confirm via AskUserQuestion only when genuinely ambiguous; otherwise state the default and proceed.
|
|
33
|
-
3. **Anchor every finding to a stable location.** Anchor by `file` + durable identifier (function/class/method/symbol name, or a short quoted snippet), NOT by a hard line number. A line number may ride along as an approximate hint (`Button.tsx · onSubmit() (~L42)`), but the symbol is the anchor — it survives edits, a line pin doesn't. Pair each with its source-of-truth location (design section, spec criterion). Present as a table: `维度 | 期望(真源) | 现状(代码) | 位置(符号/锚点)`.
|
|
34
|
-
4. **Plan the work like an architect.** State the intended approach so a programmer can execute without coming back to ask. Resolve the decisions you can already resolve; for genuinely open ones, say so explicitly and give the constraint/tradeoff rather than leaving a silent gap. Name the shared root cause once. Avoid ambiguity: if a phrasing could be read two ways, rewrite it.
|
|
35
|
-
5. **Draw the boundary.** Explicitly list what's OUT of scope, what stays unchanged, and any knock-on effects on other files/modules — so the developer doesn't "fix" intentional things or miss a ripple. Call out adjacent code the change must NOT break.
|
|
36
|
-
6. **Make acceptance criteria objective.** Each checkbox judges ONE thing, verifiable by behavior/text/data-state PLUS a runnable command (lint/build/test) where applicable. No "code is cleaner" subjective items.
|
|
37
|
-
7. **Create via heredoc**, not `-m`/`--body` single-line (literal `\n` won't become newlines). Title in Conventional Commits style. For plain ordinals use `A1`/`第1项`/`` `#1` `` — never bare `#1`/`#2` (GitHub resolves them to issue links).
|
|
38
|
-
8. **Review created issues — both plan AND code (MANDATORY).** After ALL issues are created, dispatch a subagent to audit them (see section below). The audit has TWO jobs: (a) verify claims match the codebase, and (b) read each issue as the programmer who'll execute it and flag any plan-level defect — ambiguity, missing decision, wrong/risky approach, undefined scope. Fix EVERY problem it reports via `gh issue edit` before reporting done.
|
|
39
|
-
9. **Report back** the created URL(s) + one-line grouping/boundary summary + what the review found and fixed.
|
|
40
|
-
|
|
41
|
-
## Post-Creation Review (mandatory)
|
|
42
|
-
|
|
43
|
-
After creating every issue, dispatch a subagent (Explore or general-purpose). Hand it the issue numbers/URLs and have it run BOTH passes below per issue. Reading code consistency alone is not enough — a factually-correct issue can still be unexecutable.
|
|
44
|
-
|
|
45
|
-
**Pass A — Code consistency (does it match reality?):**
|
|
46
|
-
- Every location anchor (file + symbol) actually exists and points at the claimed code. (Don't fail an issue over a stale line-number hint — anchors are symbols; line numbers are approximate by design.)
|
|
47
|
-
- Each acceptance criterion is objectively verifiable (a real command / path / behavior), not aspirational.
|
|
48
|
-
- Claims about current state (bugs, missing permissions/config, signatures) are true in the code TODAY.
|
|
49
|
-
- No finding contradicts the codebase or describes work already done.
|
|
50
|
-
|
|
51
|
-
**Pass B — Plan soundness (read it as the assigned programmer):** Ask "could I execute this start-to-finish without coming back to ask a question?"
|
|
52
|
-
- **Ambiguity:** any sentence open to two readings; vague pronouns; "fix the handling" with no defined target behavior.
|
|
53
|
-
- **Missing decision:** the issue defers a choice it should have made (which API, which file, which pattern) and leaves the programmer guessing.
|
|
54
|
-
- **Wrong/risky approach:** the proposed plan would break adjacent code, fight an existing convention (check ruler/CLAUDE.md), or pick a clearly worse path.
|
|
55
|
-
- **Undefined scope/boundary:** no out-of-scope statement, or knock-on effects on other modules not called out.
|
|
56
|
-
- **Acceptance ≠ goal:** the checkboxes, even if objective, don't actually prove the stated goal is met.
|
|
57
|
-
- **Structural:** `Refs:`/parent links correct; no bare `#n` ordinal mis-links; 3+ issues have an umbrella.
|
|
58
|
-
|
|
59
|
-
The subagent returns a per-issue defect list spanning both passes. **You MUST fix ALL reported problems** via `gh issue edit` (heredoc), then re-verify. Do not report the task complete while any reported problem stands unfixed.
|
|
60
|
-
|
|
61
|
-
## Merge vs. Split (only when <3 issues)
|
|
62
|
-
|
|
63
|
-
| Signal | Action |
|
|
64
|
-
|--------|--------|
|
|
65
|
-
| Same component / source-of-truth target / change cycle, mutually non-blocking | One issue, internal groups (A/B/C) |
|
|
66
|
-
| Different host (e.g. web vs. mobile/Flutter), independent tracking/rollback, one item far heavier | Split, link with `Refs:` |
|
|
67
|
-
| User explicitly asks to split/merge | Follow user, link related ones |
|
|
68
|
-
|
|
69
|
-
## Common Mistakes
|
|
70
|
-
|
|
71
|
-
- **Pinning to a hard line number** → after one edit the anchor points at the wrong code and over-constrains the fix. Anchor by file + symbol; line numbers are an approximate hint only.
|
|
72
|
-
- **Filename with no symbol anchor at all** → developer re-hunts. Always name the function/class/section.
|
|
73
|
-
- **Writing a bug note, not a task spec** → "X is wrong" with no plan leaves the programmer to redesign. State the intended approach.
|
|
74
|
-
- **Leaving a decision dangling** → "handle this better" forces a round-trip question. Resolve it, or state the constraint explicitly if genuinely open.
|
|
75
|
-
- **Acceptance criteria all subjective/visual** → not verifiable. Attach a command or observable behavior.
|
|
76
|
-
- **Acceptance that doesn't prove the goal** → objective but checks the wrong thing. Tie each checkbox to the stated goal.
|
|
77
|
-
- **Asking about grouping for 3+ issues** → don't. Auto-create umbrella + children.
|
|
78
|
-
- **Post-creation review only checks code, not the plan** → ships factually-correct but unexecutable/ambiguous issues. Run BOTH passes (code consistency + plan soundness).
|
|
79
|
-
- **No out-of-scope section** → developer changes intentional things or misses a ripple. Always state the boundary and knock-on effects.
|
|
80
|
-
- **`-m "...\n..."`** → literal `\n` in the issue body. Use heredoc `--body-file -`.
|
|
81
|
-
- **Bare `#1`/`#2` for ordinals** → GitHub mis-links them. Use `A1` / backticks.
|
|
82
|
-
|
|
83
|
-
## Red Flags — stop and fix before reporting done
|
|
84
|
-
|
|
85
|
-
- About to anchor a finding to a bare line number, or with no symbol/source-of-truth reference at all.
|
|
86
|
-
- The issue describes what's wrong but not what to do about it — a programmer would have to design the fix from scratch.
|
|
87
|
-
- A sentence in the issue could be read two ways, or defers a decision the issue should have made.
|
|
88
|
-
- An acceptance checkbox you can't verify by running something or observing a concrete state — or that doesn't actually prove the goal.
|
|
89
|
-
- 3+ issues created without an umbrella, or related issues without `Refs:`.
|
|
90
|
-
- Reported done without running BOTH review passes (code + plan) and fixing every finding.
|