@weotro/dx 0.1.4 → 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 +13 -1
- package/agent-references/ship-issue-pr-core.md +502 -0
- package/lib/codex-initial.js +64 -0
- package/lib/env-profile.js +98 -2
- 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,92 @@
|
|
|
1
|
+
# Codex Runtime Contract
|
|
2
|
+
|
|
3
|
+
仅在共享流程进入外部专家或 follow-up 分支时读取。
|
|
4
|
+
|
|
5
|
+
## Claude 专家通道
|
|
6
|
+
|
|
7
|
+
### 可用性探测与会话级熔断
|
|
8
|
+
|
|
9
|
+
第一次需要外部专家时运行 `command -v claude` 和 `claude --version`,分别保留退出码、stdout 和 stderr。该探测不是交付硬门禁:任一命令非零、不可执行或超时,都把 Claude 通道标记为“本次不可用”,立即由 fresh Codex subagent 接管当前阶段;不得退出、输出阻塞或停止共享流程。
|
|
10
|
+
|
|
11
|
+
“本次不可用”状态持续到当前顶层 `oo-ship-issue-pr` 调用结束,覆盖后续方案讨论、实现、review、修复和回收阶段。状态一旦设置,后续不再探测或调用 Claude,每个需要外部专家的阶段都直接创建 fresh Codex subagent;subagent 也不可用时由 Codex 主进程降级接管。新的顶层 `oo-ship-issue-pr` 调用才重新探测 Claude。
|
|
12
|
+
|
|
13
|
+
探测通过后,所有调用从当前 worktree 根目录执行,固定使用 `--effort medium --no-session-persistence`。首选模型是 Claude Fable 5(`--model fable`);仅在该次调用明确因 Fable 额度不足失败时,以完全相同的 prompt、权限模式和工具集合重试一次 Claude Opus 5(`--model opus`)。Claude 只写工作树;Git 历史、GitHub、权威验证和最终事实核验由 Codex 主进程负责。
|
|
14
|
+
|
|
15
|
+
### 模型额度回退
|
|
16
|
+
|
|
17
|
+
Claude 通道未被标记为“本次不可用”时,每次外部专家调用都重新从 Fable 开始,不因上一轮曾回退而直接选择 Opus。保留每次尝试的退出码、stdout 和 stderr,并按以下顺序处理:
|
|
18
|
+
|
|
19
|
+
1. Fable 返回 0:接受结果,不调用 Opus。
|
|
20
|
+
2. Fable 非零,且输出明确表示当前模型的使用额度、配额或 credits 已耗尽(例如 `hit your limit`、`usage limit`、`quota exceeded`、`credit balance is too low`、`insufficient credits` 或带重置时间的额度提示):只重试一次 Opus。
|
|
21
|
+
3. Fable 的失败属于未登录、认证、网络、超时、无效模型、参数、权限、工具或其他非额度错误:保留原错误,不调用 Opus,把 Claude 通道标记为“本次不可用”,立即进入 fresh subagent 回退。
|
|
22
|
+
4. Opus 返回 0:接受 Opus 结果,并在阶段报告中记录本轮实际模型为 `Claude Opus 5`。
|
|
23
|
+
5. Opus 失败:记录 Fable 与 Opus 两次证据,把 Claude 通道标记为“本次不可用”,停止 Claude 重试并立即进入 fresh subagent 回退。不得继续循环或换用其他 Claude 模型。
|
|
24
|
+
|
|
25
|
+
下面命令块中的 `fable` 是首选尝试。命令非零时同时检查 stdout 与 stderr;只有命中上述额度回退条件,才将 `--model fable` 替换为 `--model opus` 原样重跑一次。重试前清空或更换 `RESULT_FILE`,避免把 Fable 的部分输出混入 Opus 结果。
|
|
26
|
+
|
|
27
|
+
### 只读讨论、review 与 adversarial review
|
|
28
|
+
|
|
29
|
+
把 Issue、验收标准、base、方案或已确认问题写入本次任务唯一的 `PROMPT_FILE`:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
claude -p \
|
|
33
|
+
--model fable \
|
|
34
|
+
--effort medium \
|
|
35
|
+
--permission-mode plan \
|
|
36
|
+
--tools "Read,Grep,Glob,Bash" \
|
|
37
|
+
--no-session-persistence \
|
|
38
|
+
< "$PROMPT_FILE" > "$RESULT_FILE"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
prompt 要求只读、不修改文件、不执行 Git/GitHub 写操作、不递归委派,并直接读取源码和 `git diff <base>...HEAD` 取证。
|
|
42
|
+
|
|
43
|
+
- 普通 review 以 findings 为主,按严重级排序;每项包含严重级、`文件:行号`、证据、影响和具体修法。
|
|
44
|
+
- adversarial review 先挑战方案假设、构造反例和失败路径,再输出同样格式的 findings;没有证据的问题不进入报告。
|
|
45
|
+
|
|
46
|
+
### 重改动实现与修复
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
claude -p \
|
|
50
|
+
--model fable \
|
|
51
|
+
--effort medium \
|
|
52
|
+
--permission-mode acceptEdits \
|
|
53
|
+
--tools "Read,Grep,Glob,Edit,Write,Bash" \
|
|
54
|
+
--no-session-persistence \
|
|
55
|
+
< "$PROMPT_FILE" > "$RESULT_FILE"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
prompt 必须包含 Issue、逐条验收标准、最终方案、涉及目录和 `AGENTS.md` 路由,并要求只改验收范围、不 commit、不 push、不碰 GitHub、不递归委派、保护已有未提交改动,最后报告修改路径和未运行验证。
|
|
59
|
+
|
|
60
|
+
## Claude 不可用时的 fresh subagent
|
|
61
|
+
|
|
62
|
+
可用性探测失败、当前调用在模型额度回退后仍无有效结果,或因安装、登录、认证、网络、超时、参数、权限、工具等原因无法使用 Claude 时,主进程立即调用 Codex `spawn_agent` 创建一个全新的 `worker` subagent 接管当前外部专家阶段。Claude 通道同时进入“本次不可用”状态,后续外部专家阶段直接走本节,不再尝试 Claude。每次回退都新建 agent,不复用此前 agent;使用 `fork_turns: "none"`,在 prompt 中完整提供任务上下文。
|
|
63
|
+
|
|
64
|
+
fallback prompt 必须包含:
|
|
65
|
+
|
|
66
|
+
- 当前 worktree 的绝对路径,以及只在该 worktree 工作。
|
|
67
|
+
- 当前阶段是只读方案讨论、只读 review、adversarial review、实现还是修复;只承接这一阶段,不运行完整 `oo-ship-issue-pr`。
|
|
68
|
+
- Issue 原文、逐条验收标准、base、主进程初步方案或最终方案,以及该阶段所需的 diff、已确认问题和验证上下文。
|
|
69
|
+
- 先读取根与涉及目录的 `AGENTS.md`;它不是代码库中唯一执行者,保护已有未提交改动,不回退其他人的修改。
|
|
70
|
+
- 只读阶段保持工作树不变;实现或修复阶段的所有权限定为 prompt 列出的验收范围和涉及文件,改动留在工作树。
|
|
71
|
+
- 不 commit、不 push、不操作 GitHub、不调用 Claude、不递归委派;最后返回证据、结论、修改路径和未运行验证。
|
|
72
|
+
|
|
73
|
+
主进程等待 fresh subagent 返回后按外部专家的同一标准逐条复核,再继续共享流程。`spawn_agent` 本身不可用或派发失败时,主进程直接完成同一阶段并记录 `Codex 主进程降级`;Claude 通道不可用和 subagent 派发失败都不得单独中断交付链。
|
|
74
|
+
|
|
75
|
+
## 结果、等待与补跑
|
|
76
|
+
|
|
77
|
+
Claude CLI 是前台同步调用。命令返回、成功模型的 `RESULT_FILE` 可完整读取且工作树稳定即算返回;非零退出按模型额度回退和会话级熔断规则处理,卡住时记录命令与原因,把 Claude 通道标记为“本次不可用”,然后进入 fresh subagent 回退。
|
|
78
|
+
|
|
79
|
+
Claude 通道不可用时由 fresh subagent 继续当前阶段;subagent 也不可用时由 Codex 主进程直接接管。Claude 或 subagent 执行过的验证只作参考,阶段五及后续轮次要求的全部权威验证由 Codex 主进程重新执行并阅读最新输出。
|
|
80
|
+
|
|
81
|
+
Claude 或 fresh subagent 返回后,Codex 主进程逐条直读源码复核,合并重复项并记录拒绝依据。
|
|
82
|
+
|
|
83
|
+
## 阶段与报告映射
|
|
84
|
+
|
|
85
|
+
- 阶段四方案讨论:只读外部专家,记录实际使用的 Fable 5、Opus 5、fresh Codex subagent 或 Codex 主进程降级。
|
|
86
|
+
- 阶段四实现与阶段 7.4 重修复:可写外部专家,记录实际使用的 Fable 5、Opus 5、fresh Codex subagent 或 Codex 主进程降级。
|
|
87
|
+
- 阶段 7.3:复杂度门禁命中且本 PR 尚未用过外部专家审查时执行 review;需要挑战方案时执行 adversarial review。成功返回的 fresh Codex subagent 计入本 PR 唯一一次外部专家审查,Codex 主进程降级不计入。
|
|
88
|
+
- reviewer:`Codex 主进程自审 / Claude Fable 5 review / Claude Fable 5 adversarial review / Claude Opus 5 review / Claude Opus 5 adversarial review / fresh Codex subagent review / fresh Codex subagent adversarial review / Codex 主进程降级 review / Codex 主进程降级 adversarial review`,按本轮实际执行者填写。
|
|
89
|
+
|
|
90
|
+
## Follow-up
|
|
91
|
+
|
|
92
|
+
使用 Codex `spawn_agent` 的 `worker` 角色,prompt 指定 worktree 绝对路径并要求调用 `oo-ship-issue-pr`。明确告知 worker 它不是代码库中唯一执行者,不回退其他人的改动,只在指定 worktree 工作。派发深度只允许一层;worker 发现的新 follow-up 只建 Issue。
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prune-git-repository
|
|
3
|
+
description: 仅在用户显式调用 $prune-git-repository、/prune-git-repository 或明确要求使用 prune-git-repository 技能时使用;不要通过关键词、任务类型或上下文自动触发。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Prune Git Repository
|
|
7
|
+
|
|
8
|
+
Use the bundled scripts instead of rebuilding shell loops. Always run them from the target repository.
|
|
9
|
+
|
|
10
|
+
## Authorization gate
|
|
11
|
+
|
|
12
|
+
Treat every execute mode as destructive.
|
|
13
|
+
|
|
14
|
+
1. Require explicit current-turn authorization for the exact cleanup categories and thresholds.
|
|
15
|
+
2. Run dry-run mode first and report counts, cutoff, keep sets, protected refs, and occupied branches.
|
|
16
|
+
3. Pass `--execute` only after the authorization covers the previewed deletions.
|
|
17
|
+
4. Never delete the base branch, remote default branch, current worktree, or current branch.
|
|
18
|
+
5. Preserve unrelated working-tree changes. Do not infer permission to push recent local-only branches.
|
|
19
|
+
|
|
20
|
+
Read [pitfalls.md](references/pitfalls.md) before any execute run.
|
|
21
|
+
|
|
22
|
+
## Workflow
|
|
23
|
+
|
|
24
|
+
When multiple categories are authorized, use this order:
|
|
25
|
+
|
|
26
|
+
1. Inspect `git status`, remotes, default branch, `gh auth status`, and worktrees.
|
|
27
|
+
2. Preview all selected scripts.
|
|
28
|
+
3. Remove extra worktrees first so checked-out candidate branches can be deleted.
|
|
29
|
+
4. Prune branches.
|
|
30
|
+
5. Prune Releases and tags.
|
|
31
|
+
6. Fetch/prune and run the scripts again without `--execute`; require zero remaining candidates.
|
|
32
|
+
7. Verify `main...origin/main`, a clean current worktree, server-side refs through `ls-remote`, and matching local/remote tag sets.
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
35
|
+
|
|
36
|
+
Resolve the installed script directory once:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
PRUNE_SKILL_ROOT="${AGENTS_HOME:-$HOME/.agents}/skills/prune-git-repository"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Preview branch cleanup:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
"$PRUNE_SKILL_ROOT/scripts/prune-branches.sh" \
|
|
46
|
+
--remote origin --base main --stale-days 20
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Execute the authorized branch cleanup:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
"$PRUNE_SKILL_ROOT/scripts/prune-branches.sh" \
|
|
53
|
+
--remote origin --base main --stale-days 20 --execute
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Preview or remove all linked worktrees except the current worktree:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
"$PRUNE_SKILL_ROOT/scripts/prune-worktrees.sh"
|
|
60
|
+
"$PRUNE_SKILL_ROOT/scripts/prune-worktrees.sh" --execute
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Preview or retain only the newest tags and GitHub Releases:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
"$PRUNE_SKILL_ROOT/scripts/prune-tags-releases.sh" \
|
|
67
|
+
--remote origin --keep 10
|
|
68
|
+
"$PRUNE_SKILL_ROOT/scripts/prune-tags-releases.sh" \
|
|
69
|
+
--remote origin --keep 10 --execute
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Use `--tags-only` only when the user explicitly excludes GitHub Releases. The combined mode requires the retained tag names and retained Release tag names to match; stop for user direction if they differ.
|
|
73
|
+
|
|
74
|
+
## Reporting
|
|
75
|
+
|
|
76
|
+
Report:
|
|
77
|
+
|
|
78
|
+
- effective cutoff and keep set;
|
|
79
|
+
- deleted local/remote branch counts by reason;
|
|
80
|
+
- deleted local/remote tag and GitHub Release counts;
|
|
81
|
+
- removed and pruned worktree counts;
|
|
82
|
+
- any protected, occupied, failed, or deliberately retained items;
|
|
83
|
+
- final local/server counts, set diffs, `main` ahead/behind, and worktree status.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "清理 Git 仓库"
|
|
3
|
+
short_description: "安全清理旧分支、标签、Release 与 worktree"
|
|
4
|
+
default_prompt: "Use $prune-git-repository to preview and safely clean stale Git branches, old tags and Releases, and extra worktrees."
|
|
5
|
+
policy:
|
|
6
|
+
allow_implicit_invocation: false
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Failure modes and guardrails
|
|
2
|
+
|
|
3
|
+
Read this file before execute mode.
|
|
4
|
+
|
|
5
|
+
## Raw output and `rtk`
|
|
6
|
+
|
|
7
|
+
- Use `rtk proxy git ...` for machine-readable Git output. Plain `rtk git worktree list --porcelain` may render human-friendly paths and remove `worktree ` prefixes.
|
|
8
|
+
- Use `rtk proxy gh ...` and `rtk proxy jq ...` when complete JSON or line lists are required. Plain `rtk` can replace the middle of a long list with a truncation marker; never feed rendered output back into deletion commands.
|
|
9
|
+
- Verify remote deletion with `rtk proxy git ls-remote`, not only local remote-tracking refs.
|
|
10
|
+
- `rtk rm -rf` may be protected/no-op. Remove registered worktrees with Git first. Use the narrowly scoped `find ... -exec /bin/rm -rf` fallback only after Git removal fails and the path is proven to be a non-current registered worktree.
|
|
11
|
+
|
|
12
|
+
## Shell hazards
|
|
13
|
+
|
|
14
|
+
- Use Bash arrays for batches. Do not rely on scalar word splitting; zsh does not split scalar variables by default and can turn a whole batch into one invalid refspec.
|
|
15
|
+
- Never use `path` as a zsh loop variable; it is tied to `PATH` and can make `git`, `sed`, and `rtk` disappear mid-script. Use `wt_path`.
|
|
16
|
+
- Under `set -e`, avoid standalone arithmetic tests or `((count++))`; a zero result exits with status 1. Put arithmetic comparisons inside `if` and use `count=$((count + 1))`.
|
|
17
|
+
- Use exact refspecs (`:refs/heads/...`, `:refs/tags/...`) to avoid branch/tag ambiguity.
|
|
18
|
+
|
|
19
|
+
## Branch classification
|
|
20
|
+
|
|
21
|
+
- Fetch with `--prune` before classifying.
|
|
22
|
+
- Determine merge state with `git merge-base --is-ancestor <tip> <remote/base>`; branch names and PR state are not topology evidence.
|
|
23
|
+
- Classify local and remote tips independently. A recent local branch can track an already-deleted or stale remote ref; do not publish it unless the user explicitly authorizes pushing it.
|
|
24
|
+
- Exclude the exact remote HEAD symref and base ref. `origin/HEAD` may render as `origin`, so filtering only the short name `HEAD` is unsafe.
|
|
25
|
+
- Protect branches checked out by any worktree. Remove authorized extra worktrees first or report the branch as occupied.
|
|
26
|
+
- Calculate the cutoff portably: macOS `date -v-<N>d`, GNU `date -d '<N> days ago'`.
|
|
27
|
+
|
|
28
|
+
## Tags and Releases
|
|
29
|
+
|
|
30
|
+
- Use `git ls-remote --tags --refs`; without `--refs`, annotated tags add `^{}` pseudo-refs and corrupt counts.
|
|
31
|
+
- Fetch tags before sorting by `creatordate`. Compare local-only and remote-only tags explicitly.
|
|
32
|
+
- When tags and GitHub Releases are cleaned together, require the newest tag set and newest Release tag-name set to match before execution. Otherwise stopping is safer than leaving retained Releases without their tags.
|
|
33
|
+
- Retrieve all Releases with `gh release list --limit 1000 --json ...` through `rtk proxy`; re-query after deletion.
|
|
34
|
+
- Delete old Releases before their old tags, then synchronize and compare the retained tag sets.
|
|
35
|
+
|
|
36
|
+
## Worktrees
|
|
37
|
+
|
|
38
|
+
- Read paths only from raw `git worktree list --porcelain` output.
|
|
39
|
+
- Canonicalize the current repository root and compare exact absolute paths. Never compare against a display path containing `~`.
|
|
40
|
+
- Use `git worktree remove --force --force` and wait for it to finish; dependency directories can make deletion slow.
|
|
41
|
+
- Run `git worktree prune --expire now` for missing or partially removed worktrees.
|
|
42
|
+
- If Git says a registered directory is not a working tree, delete only that already-validated registered directory, then prune. Never broaden the fallback to a parent directory shared with the current checkout.
|
|
43
|
+
- After cleanup, raw porcelain output must contain exactly the current worktree.
|
|
@@ -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.
|