superpowers-zh 1.3.0 → 1.5.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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +2 -2
- package/CLAUDE.md +1 -1
- package/README.md +25 -12
- package/RELEASE-NOTES.zh.md +129 -0
- package/bin/superpowers-zh.js +52 -0
- package/docs/README.openclaw.md +7 -4
- package/docs/README.qoder.md +95 -0
- package/gemini-extension.json +1 -1
- package/hooks/hooks-cursor.json +1 -1
- package/package.json +4 -3
- package/skills/brainstorming/scripts/server.cjs +33 -17
- package/skills/brainstorming/scripts/start-server.sh +9 -14
- package/skills/brainstorming/scripts/stop-server.sh +9 -8
- package/skills/finishing-a-development-branch/SKILL.md +107 -32
- package/skills/requesting-code-review/SKILL.md +8 -10
- package/skills/requesting-code-review/code-reviewer.md +124 -104
- package/skills/using-git-worktrees/SKILL.md +114 -97
- package/skills/using-superpowers/SKILL.md +1 -1
- package/skills/using-superpowers/references/qoder-tools.md +43 -0
|
@@ -76,8 +76,10 @@ function decodeFrame(buffer) {
|
|
|
76
76
|
const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383));
|
|
77
77
|
const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1';
|
|
78
78
|
const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST);
|
|
79
|
-
const
|
|
80
|
-
const
|
|
79
|
+
const SESSION_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm';
|
|
80
|
+
const CONTENT_DIR = path.join(SESSION_DIR, 'content');
|
|
81
|
+
const STATE_DIR = path.join(SESSION_DIR, 'state');
|
|
82
|
+
let ownerPid = process.env.BRAINSTORM_OWNER_PID ? Number(process.env.BRAINSTORM_OWNER_PID) : null;
|
|
81
83
|
|
|
82
84
|
const MIME_TYPES = {
|
|
83
85
|
'.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript',
|
|
@@ -112,10 +114,10 @@ function wrapInFrame(content) {
|
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
function getNewestScreen() {
|
|
115
|
-
const files = fs.readdirSync(
|
|
117
|
+
const files = fs.readdirSync(CONTENT_DIR)
|
|
116
118
|
.filter(f => f.endsWith('.html'))
|
|
117
119
|
.map(f => {
|
|
118
|
-
const fp = path.join(
|
|
120
|
+
const fp = path.join(CONTENT_DIR, f);
|
|
119
121
|
return { path: fp, mtime: fs.statSync(fp).mtime.getTime() };
|
|
120
122
|
})
|
|
121
123
|
.sort((a, b) => b.mtime - a.mtime);
|
|
@@ -142,7 +144,7 @@ function handleRequest(req, res) {
|
|
|
142
144
|
res.end(html);
|
|
143
145
|
} else if (req.method === 'GET' && req.url.startsWith('/files/')) {
|
|
144
146
|
const fileName = req.url.slice(7);
|
|
145
|
-
const filePath = path.join(
|
|
147
|
+
const filePath = path.join(CONTENT_DIR, path.basename(fileName));
|
|
146
148
|
if (!fs.existsSync(filePath)) {
|
|
147
149
|
res.writeHead(404);
|
|
148
150
|
res.end('Not found');
|
|
@@ -230,7 +232,7 @@ function handleMessage(text) {
|
|
|
230
232
|
touchActivity();
|
|
231
233
|
console.log(JSON.stringify({ source: 'user-event', ...event }));
|
|
232
234
|
if (event.choice) {
|
|
233
|
-
const eventsFile = path.join(
|
|
235
|
+
const eventsFile = path.join(STATE_DIR, 'events');
|
|
234
236
|
fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n');
|
|
235
237
|
}
|
|
236
238
|
}
|
|
@@ -258,32 +260,33 @@ const debounceTimers = new Map();
|
|
|
258
260
|
// ========== Server Startup ==========
|
|
259
261
|
|
|
260
262
|
function startServer() {
|
|
261
|
-
if (!fs.existsSync(
|
|
263
|
+
if (!fs.existsSync(CONTENT_DIR)) fs.mkdirSync(CONTENT_DIR, { recursive: true });
|
|
264
|
+
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
262
265
|
|
|
263
266
|
// Track known files to distinguish new screens from updates.
|
|
264
267
|
// macOS fs.watch reports 'rename' for both new files and overwrites,
|
|
265
268
|
// so we can't rely on eventType alone.
|
|
266
269
|
const knownFiles = new Set(
|
|
267
|
-
fs.readdirSync(
|
|
270
|
+
fs.readdirSync(CONTENT_DIR).filter(f => f.endsWith('.html'))
|
|
268
271
|
);
|
|
269
272
|
|
|
270
273
|
const server = http.createServer(handleRequest);
|
|
271
274
|
server.on('upgrade', handleUpgrade);
|
|
272
275
|
|
|
273
|
-
const watcher = fs.watch(
|
|
276
|
+
const watcher = fs.watch(CONTENT_DIR, (eventType, filename) => {
|
|
274
277
|
if (!filename || !filename.endsWith('.html')) return;
|
|
275
278
|
|
|
276
279
|
if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename));
|
|
277
280
|
debounceTimers.set(filename, setTimeout(() => {
|
|
278
281
|
debounceTimers.delete(filename);
|
|
279
|
-
const filePath = path.join(
|
|
282
|
+
const filePath = path.join(CONTENT_DIR, filename);
|
|
280
283
|
|
|
281
284
|
if (!fs.existsSync(filePath)) return; // file was deleted
|
|
282
285
|
touchActivity();
|
|
283
286
|
|
|
284
287
|
if (!knownFiles.has(filename)) {
|
|
285
288
|
knownFiles.add(filename);
|
|
286
|
-
const eventsFile = path.join(
|
|
289
|
+
const eventsFile = path.join(STATE_DIR, 'events');
|
|
287
290
|
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
|
|
288
291
|
console.log(JSON.stringify({ type: 'screen-added', file: filePath }));
|
|
289
292
|
} else {
|
|
@@ -297,10 +300,10 @@ function startServer() {
|
|
|
297
300
|
|
|
298
301
|
function shutdown(reason) {
|
|
299
302
|
console.log(JSON.stringify({ type: 'server-stopped', reason }));
|
|
300
|
-
const infoFile = path.join(
|
|
303
|
+
const infoFile = path.join(STATE_DIR, 'server-info');
|
|
301
304
|
if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile);
|
|
302
305
|
fs.writeFileSync(
|
|
303
|
-
path.join(
|
|
306
|
+
path.join(STATE_DIR, 'server-stopped'),
|
|
304
307
|
JSON.stringify({ reason, timestamp: Date.now() }) + '\n'
|
|
305
308
|
);
|
|
306
309
|
watcher.close();
|
|
@@ -309,8 +312,8 @@ function startServer() {
|
|
|
309
312
|
}
|
|
310
313
|
|
|
311
314
|
function ownerAlive() {
|
|
312
|
-
if (!
|
|
313
|
-
try { process.kill(
|
|
315
|
+
if (!ownerPid) return true;
|
|
316
|
+
try { process.kill(ownerPid, 0); return true; } catch (e) { return e.code === 'EPERM'; }
|
|
314
317
|
}
|
|
315
318
|
|
|
316
319
|
// Check every 60s: exit if owner process died or idle for 30 minutes
|
|
@@ -320,14 +323,27 @@ function startServer() {
|
|
|
320
323
|
}, 60 * 1000);
|
|
321
324
|
lifecycleCheck.unref();
|
|
322
325
|
|
|
326
|
+
// Validate owner PID at startup. If it's already dead, the PID resolution
|
|
327
|
+
// was wrong (common on WSL, Tailscale SSH, and cross-user scenarios).
|
|
328
|
+
// Disable monitoring and rely on the idle timeout instead.
|
|
329
|
+
if (ownerPid) {
|
|
330
|
+
try { process.kill(ownerPid, 0); }
|
|
331
|
+
catch (e) {
|
|
332
|
+
if (e.code !== 'EPERM') {
|
|
333
|
+
console.log(JSON.stringify({ type: 'owner-pid-invalid', pid: ownerPid, reason: 'dead at startup' }));
|
|
334
|
+
ownerPid = null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
323
339
|
server.listen(PORT, HOST, () => {
|
|
324
340
|
const info = JSON.stringify({
|
|
325
341
|
type: 'server-started', port: Number(PORT), host: HOST,
|
|
326
342
|
url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT,
|
|
327
|
-
screen_dir:
|
|
343
|
+
screen_dir: CONTENT_DIR, state_dir: STATE_DIR
|
|
328
344
|
});
|
|
329
345
|
console.log(info);
|
|
330
|
-
fs.writeFileSync(path.join(
|
|
346
|
+
fs.writeFileSync(path.join(STATE_DIR, 'server-info'), info + '\n');
|
|
331
347
|
});
|
|
332
348
|
}
|
|
333
349
|
|
|
@@ -78,16 +78,17 @@ fi
|
|
|
78
78
|
SESSION_ID="$$-$(date +%s)"
|
|
79
79
|
|
|
80
80
|
if [[ -n "$PROJECT_DIR" ]]; then
|
|
81
|
-
|
|
81
|
+
SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}"
|
|
82
82
|
else
|
|
83
|
-
|
|
83
|
+
SESSION_DIR="/tmp/brainstorm-${SESSION_ID}"
|
|
84
84
|
fi
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
STATE_DIR="${SESSION_DIR}/state"
|
|
87
|
+
PID_FILE="${STATE_DIR}/server.pid"
|
|
88
|
+
LOG_FILE="${STATE_DIR}/server.log"
|
|
88
89
|
|
|
89
|
-
# Create fresh session directory
|
|
90
|
-
mkdir -p "$
|
|
90
|
+
# Create fresh session directory with content and state peers
|
|
91
|
+
mkdir -p "${SESSION_DIR}/content" "$STATE_DIR"
|
|
91
92
|
|
|
92
93
|
# Kill any existing server
|
|
93
94
|
if [[ -f "$PID_FILE" ]]; then
|
|
@@ -106,22 +107,16 @@ if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then
|
|
|
106
107
|
OWNER_PID="$PPID"
|
|
107
108
|
fi
|
|
108
109
|
|
|
109
|
-
# On Windows/MSYS2, the MSYS2 PID namespace is invisible to Node.js.
|
|
110
|
-
# Skip owner-PID monitoring — the 30-minute idle timeout prevents orphans.
|
|
111
|
-
case "${OSTYPE:-}" in
|
|
112
|
-
msys*|cygwin*|mingw*) OWNER_PID="" ;;
|
|
113
|
-
esac
|
|
114
|
-
|
|
115
110
|
# Foreground mode for environments that reap detached/background processes.
|
|
116
111
|
if [[ "$FOREGROUND" == "true" ]]; then
|
|
117
112
|
echo "$$" > "$PID_FILE"
|
|
118
|
-
env BRAINSTORM_DIR="$
|
|
113
|
+
env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs
|
|
119
114
|
exit $?
|
|
120
115
|
fi
|
|
121
116
|
|
|
122
117
|
# Start server, capturing output to log file
|
|
123
118
|
# Use nohup to survive shell exit; disown to remove from job table
|
|
124
|
-
nohup env BRAINSTORM_DIR="$
|
|
119
|
+
nohup env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs > "$LOG_FILE" 2>&1 &
|
|
125
120
|
SERVER_PID=$!
|
|
126
121
|
disown "$SERVER_PID" 2>/dev/null
|
|
127
122
|
echo "$SERVER_PID" > "$PID_FILE"
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Stop the brainstorm server and clean up
|
|
3
|
-
# Usage: stop-server.sh <
|
|
3
|
+
# Usage: stop-server.sh <session_dir>
|
|
4
4
|
#
|
|
5
5
|
# Kills the server process. Only deletes session directory if it's
|
|
6
6
|
# under /tmp (ephemeral). Persistent directories (.superpowers/) are
|
|
7
7
|
# kept so mockups can be reviewed later.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
SESSION_DIR="$1"
|
|
10
10
|
|
|
11
|
-
if [[ -z "$
|
|
12
|
-
echo '{"error": "Usage: stop-server.sh <
|
|
11
|
+
if [[ -z "$SESSION_DIR" ]]; then
|
|
12
|
+
echo '{"error": "Usage: stop-server.sh <session_dir>"}'
|
|
13
13
|
exit 1
|
|
14
14
|
fi
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
STATE_DIR="${SESSION_DIR}/state"
|
|
17
|
+
PID_FILE="${STATE_DIR}/server.pid"
|
|
17
18
|
|
|
18
19
|
if [[ -f "$PID_FILE" ]]; then
|
|
19
20
|
pid=$(cat "$PID_FILE")
|
|
@@ -42,11 +43,11 @@ if [[ -f "$PID_FILE" ]]; then
|
|
|
42
43
|
exit 1
|
|
43
44
|
fi
|
|
44
45
|
|
|
45
|
-
rm -f "$PID_FILE" "${
|
|
46
|
+
rm -f "$PID_FILE" "${STATE_DIR}/server.log"
|
|
46
47
|
|
|
47
48
|
# Only delete ephemeral /tmp directories
|
|
48
|
-
if [[ "$
|
|
49
|
-
rm -rf "$
|
|
49
|
+
if [[ "$SESSION_DIR" == /tmp/* ]]; then
|
|
50
|
+
rm -rf "$SESSION_DIR"
|
|
50
51
|
fi
|
|
51
52
|
|
|
52
53
|
echo '{"status": "stopped"}'
|
|
@@ -9,7 +9,7 @@ description: 当实现完成、所有测试通过、需要决定如何集成工
|
|
|
9
9
|
|
|
10
10
|
通过提供清晰的选项并执行所选工作流来引导开发工作的收尾。
|
|
11
11
|
|
|
12
|
-
**核心原则:** 验证测试 → 展示选项 → 执行选择 → 清理。
|
|
12
|
+
**核心原则:** 验证测试 → 检测环境 → 展示选项 → 执行选择 → 清理。
|
|
13
13
|
|
|
14
14
|
**开始时宣布:** "我正在使用 finishing-a-development-branch 技能来完成这项工作。"
|
|
15
15
|
|
|
@@ -25,6 +25,7 @@ npm test / cargo test / pytest / go test ./...
|
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
**如果测试失败:**
|
|
28
|
+
|
|
28
29
|
```
|
|
29
30
|
测试失败(<N> 个失败)。必须先修复才能继续:
|
|
30
31
|
|
|
@@ -37,7 +38,24 @@ npm test / cargo test / pytest / go test ./...
|
|
|
37
38
|
|
|
38
39
|
**如果测试通过:** 继续步骤 2。
|
|
39
40
|
|
|
40
|
-
### 步骤 2
|
|
41
|
+
### 步骤 2:检测环境
|
|
42
|
+
|
|
43
|
+
**在展示选项之前,先确定工作区状态:**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
|
47
|
+
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
这决定了展示哪种菜单、以及清理方式:
|
|
51
|
+
|
|
52
|
+
| 状态 | 菜单 | 清理 |
|
|
53
|
+
|------|------|------|
|
|
54
|
+
| `GIT_DIR == GIT_COMMON`(普通仓库) | 标准 4 个选项 | 无 worktree 可清理 |
|
|
55
|
+
| `GIT_DIR != GIT_COMMON`,命名分支 | 标准 4 个选项 | 按来源判断(见步骤 6) |
|
|
56
|
+
| `GIT_DIR != GIT_COMMON`,分离 HEAD | 收敛 3 个选项(无合并) | 无清理(由外部管理) |
|
|
57
|
+
|
|
58
|
+
### 步骤 3:确定基础分支
|
|
41
59
|
|
|
42
60
|
```bash
|
|
43
61
|
# 尝试常见的基础分支
|
|
@@ -46,9 +64,9 @@ git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null
|
|
|
46
64
|
|
|
47
65
|
或者询问:"这个分支是从 main 分出来的——对吗?"
|
|
48
66
|
|
|
49
|
-
### 步骤
|
|
67
|
+
### 步骤 4:展示选项
|
|
50
68
|
|
|
51
|
-
|
|
69
|
+
**普通仓库和命名分支 worktree —— 准确展示以下 4 个选项:**
|
|
52
70
|
|
|
53
71
|
```
|
|
54
72
|
实现已完成。你想怎么做?
|
|
@@ -61,30 +79,45 @@ git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null
|
|
|
61
79
|
选哪个?
|
|
62
80
|
```
|
|
63
81
|
|
|
64
|
-
|
|
82
|
+
**分离 HEAD —— 准确展示以下 3 个选项:**
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
实现已完成。你在分离 HEAD 上(由外部管理的工作区)。
|
|
86
|
+
|
|
87
|
+
1. 作为新分支推送并创建 Pull Request
|
|
88
|
+
2. 保持现状(我稍后处理)
|
|
89
|
+
3. 丢弃这项工作
|
|
90
|
+
|
|
91
|
+
选哪个?
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**不要添加解释** —— 保持选项简洁。
|
|
65
95
|
|
|
66
|
-
### 步骤
|
|
96
|
+
### 步骤 5:执行选择
|
|
67
97
|
|
|
68
98
|
#### 选项 1:本地合并
|
|
69
99
|
|
|
70
100
|
```bash
|
|
71
|
-
#
|
|
72
|
-
git
|
|
101
|
+
# 切到主仓库根目录,保证 CWD 安全
|
|
102
|
+
MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel)
|
|
103
|
+
cd "$MAIN_ROOT"
|
|
73
104
|
|
|
74
|
-
#
|
|
105
|
+
# 先合并 —— 在删除任何东西之前先验证合并成功
|
|
106
|
+
git checkout <base-branch>
|
|
75
107
|
git pull
|
|
76
|
-
|
|
77
|
-
# 合并功能分支
|
|
78
108
|
git merge <feature-branch>
|
|
79
109
|
|
|
80
110
|
# 在合并结果上验证测试
|
|
81
111
|
<test command>
|
|
82
112
|
|
|
83
|
-
#
|
|
84
|
-
git branch -d <feature-branch>
|
|
113
|
+
# 合并成功之后再:清理 worktree(步骤 6),然后删除分支
|
|
85
114
|
```
|
|
86
115
|
|
|
87
|
-
|
|
116
|
+
然后:清理 worktree(步骤 6),再删除分支:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
git branch -d <feature-branch>
|
|
120
|
+
```
|
|
88
121
|
|
|
89
122
|
#### 选项 2:推送并创建 PR
|
|
90
123
|
|
|
@@ -103,7 +136,7 @@ EOF
|
|
|
103
136
|
)"
|
|
104
137
|
```
|
|
105
138
|
|
|
106
|
-
|
|
139
|
+
**不要清理 worktree** —— 用户在 PR 反馈迭代时还需要它存活。
|
|
107
140
|
|
|
108
141
|
#### 选项 3:保持现状
|
|
109
142
|
|
|
@@ -114,6 +147,7 @@ EOF
|
|
|
114
147
|
#### 选项 4:丢弃
|
|
115
148
|
|
|
116
149
|
**先确认:**
|
|
150
|
+
|
|
117
151
|
```
|
|
118
152
|
这将永久删除:
|
|
119
153
|
- 分支 <name>
|
|
@@ -126,28 +160,40 @@ EOF
|
|
|
126
160
|
等待精确的确认。
|
|
127
161
|
|
|
128
162
|
确认后:
|
|
163
|
+
|
|
129
164
|
```bash
|
|
130
|
-
git
|
|
131
|
-
|
|
165
|
+
MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel)
|
|
166
|
+
cd "$MAIN_ROOT"
|
|
132
167
|
```
|
|
133
168
|
|
|
134
|
-
|
|
169
|
+
然后:清理 worktree(步骤 6),再强制删除分支:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
git branch -D <feature-branch>
|
|
173
|
+
```
|
|
135
174
|
|
|
136
|
-
### 步骤
|
|
175
|
+
### 步骤 6:清理工作区
|
|
137
176
|
|
|
138
|
-
|
|
177
|
+
**只对选项 1 和 4 执行。** 选项 2 和 3 始终保留 worktree。
|
|
139
178
|
|
|
140
|
-
检查是否在工作树中:
|
|
141
179
|
```bash
|
|
142
|
-
|
|
180
|
+
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
|
181
|
+
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
|
182
|
+
WORKTREE_PATH=$(git rev-parse --show-toplevel)
|
|
143
183
|
```
|
|
144
184
|
|
|
145
|
-
|
|
185
|
+
**如果 `GIT_DIR == GIT_COMMON`:** 普通仓库,无 worktree 可清理。结束。
|
|
186
|
+
|
|
187
|
+
**如果 worktree 路径在 `.worktrees/`、`worktrees/` 或 `~/.config/superpowers/worktrees/` 之下:** 这是 Superpowers 创建的 worktree —— 我们负责清理。
|
|
188
|
+
|
|
146
189
|
```bash
|
|
147
|
-
git
|
|
190
|
+
MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel)
|
|
191
|
+
cd "$MAIN_ROOT"
|
|
192
|
+
git worktree remove "$WORKTREE_PATH"
|
|
193
|
+
git worktree prune # 自愈:清理任何过期的注册记录
|
|
148
194
|
```
|
|
149
195
|
|
|
150
|
-
|
|
196
|
+
**否则:** 这个工作区由宿主环境(harness)管理。**不要**移除它。如果你的平台提供了工作区退出工具,用它。否则原样保留工作区。
|
|
151
197
|
|
|
152
198
|
## 快速参考
|
|
153
199
|
|
|
@@ -161,40 +207,69 @@ git worktree remove <worktree-path>
|
|
|
161
207
|
## 常见错误
|
|
162
208
|
|
|
163
209
|
**跳过测试验证**
|
|
210
|
+
|
|
164
211
|
- **问题:** 合并损坏的代码、创建失败的 PR
|
|
165
212
|
- **修复:** 在提供选项前始终验证测试
|
|
166
213
|
|
|
167
214
|
**开放式问题**
|
|
215
|
+
|
|
168
216
|
- **问题:** "接下来该做什么?" → 含糊不清
|
|
169
|
-
- **修复:** 准确展示 4
|
|
217
|
+
- **修复:** 准确展示 4 个结构化选项(分离 HEAD 时是 3 个)
|
|
218
|
+
|
|
219
|
+
**为选项 2 清理 worktree**
|
|
170
220
|
|
|
171
|
-
|
|
172
|
-
- **问题:** 在可能还需要工作树时就删除了(选项 2、3)
|
|
221
|
+
- **问题:** 删掉用户 PR 迭代还需要的 worktree
|
|
173
222
|
- **修复:** 只在选项 1 和 4 时清理
|
|
174
223
|
|
|
224
|
+
**先删分支再删 worktree**
|
|
225
|
+
|
|
226
|
+
- **问题:** `git branch -d` 失败,因为 worktree 还引用着该分支
|
|
227
|
+
- **修复:** 先合并,再删 worktree,最后删分支
|
|
228
|
+
|
|
229
|
+
**在 worktree 内部跑 `git worktree remove`**
|
|
230
|
+
|
|
231
|
+
- **问题:** 当 CWD 在被删除的 worktree 内时,命令静默失败
|
|
232
|
+
- **修复:** 跑 `git worktree remove` 前先 `cd` 到主仓库根目录
|
|
233
|
+
|
|
234
|
+
**清理 harness 拥有的 worktree**
|
|
235
|
+
|
|
236
|
+
- **问题:** 移除 harness 创建的 worktree 会造成幻影状态
|
|
237
|
+
- **修复:** 只清理 `.worktrees/`、`worktrees/` 或 `~/.config/superpowers/worktrees/` 下的 worktree
|
|
238
|
+
|
|
175
239
|
**丢弃时不确认**
|
|
240
|
+
|
|
176
241
|
- **问题:** 意外删除工作成果
|
|
177
|
-
- **修复:** 要求输入
|
|
242
|
+
- **修复:** 要求输入 'discard' 确认
|
|
178
243
|
|
|
179
244
|
## 红线
|
|
180
245
|
|
|
181
246
|
**绝不:**
|
|
247
|
+
|
|
182
248
|
- 在测试失败时继续
|
|
183
|
-
-
|
|
249
|
+
- 合并前不验证合并结果上的测试
|
|
184
250
|
- 不确认就删除工作成果
|
|
185
251
|
- 未经明确请求就强制推送
|
|
252
|
+
- 在确认合并成功之前移除 worktree
|
|
253
|
+
- 清理不是你创建的 worktree(按来源判断)
|
|
254
|
+
- 在 worktree 内部跑 `git worktree remove`
|
|
186
255
|
|
|
187
256
|
**始终:**
|
|
257
|
+
|
|
188
258
|
- 在提供选项前验证测试
|
|
189
|
-
-
|
|
259
|
+
- 展示菜单前检测环境
|
|
260
|
+
- 准确展示 4 个选项(分离 HEAD 时是 3 个)
|
|
190
261
|
- 选项 4 要求输入确认
|
|
191
|
-
- 只在选项 1 和 4
|
|
262
|
+
- 只在选项 1 和 4 时清理 worktree
|
|
263
|
+
- 移除 worktree 前 `cd` 到主仓库根目录
|
|
264
|
+
- 移除后跑 `git worktree prune`
|
|
192
265
|
|
|
193
266
|
## 集成
|
|
194
267
|
|
|
195
268
|
**被以下技能调用:**
|
|
269
|
+
|
|
196
270
|
- **subagent-driven-development**(步骤 7)- 所有任务完成后
|
|
197
271
|
- **executing-plans**(步骤 5)- 所有批次完成后
|
|
198
272
|
|
|
199
273
|
**配合使用:**
|
|
274
|
+
|
|
200
275
|
- **using-git-worktrees** - 清理由该技能创建的工作树
|
|
@@ -5,7 +5,7 @@ description: 完成任务、实现重要功能或合并前使用,用于验证
|
|
|
5
5
|
|
|
6
6
|
# 请求代码审查
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
派遣代码审查子代理,在问题扩散之前发现它们。审查者获得的是精心组织的评估上下文——绝不是你的会话历史。这样可以让审查者专注于工作成果而非你的思考过程,同时保留你自己的上下文以便继续工作。
|
|
9
9
|
|
|
10
10
|
**核心原则:** 早审查,勤审查。
|
|
11
11
|
|
|
@@ -29,16 +29,15 @@ BASE_SHA=$(git rev-parse HEAD~1) # 或 origin/main
|
|
|
29
29
|
HEAD_SHA=$(git rev-parse HEAD)
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
**2.
|
|
32
|
+
**2. 派遣代码审查子代理:**
|
|
33
33
|
|
|
34
|
-
使用 Task 工具,指定
|
|
34
|
+
使用 Task 工具,指定 `general-purpose` 类型,填写 `code-reviewer.md` 中的模板
|
|
35
35
|
|
|
36
36
|
**占位符说明:**
|
|
37
|
-
- `{
|
|
37
|
+
- `{DESCRIPTION}` - 你刚完成的内容简要说明
|
|
38
38
|
- `{PLAN_OR_REQUIREMENTS}` - 预期功能
|
|
39
39
|
- `{BASE_SHA}` - 起始提交
|
|
40
40
|
- `{HEAD_SHA}` - 结束提交
|
|
41
|
-
- `{DESCRIPTION}` - 简要说明
|
|
42
41
|
|
|
43
42
|
**3. 处理反馈:**
|
|
44
43
|
- Critical 问题立即修复
|
|
@@ -56,12 +55,11 @@ HEAD_SHA=$(git rev-parse HEAD)
|
|
|
56
55
|
BASE_SHA=$(git log --oneline | grep "Task 1" | head -1 | awk '{print $1}')
|
|
57
56
|
HEAD_SHA=$(git rev-parse HEAD)
|
|
58
57
|
|
|
59
|
-
[
|
|
60
|
-
|
|
58
|
+
[派遣代码审查子代理]
|
|
59
|
+
DESCRIPTION: 添加了 verifyIndex() 和 repairIndex(),支持 4 种问题类型
|
|
61
60
|
PLAN_OR_REQUIREMENTS: docs/superpowers/plans/deployment-plan.md 中的任务 2
|
|
62
61
|
BASE_SHA: a7981ec
|
|
63
62
|
HEAD_SHA: 3df7661
|
|
64
|
-
DESCRIPTION: 添加了 verifyIndex() 和 repairIndex(),支持 4 种问题类型
|
|
65
63
|
|
|
66
64
|
[子代理返回]:
|
|
67
65
|
优点:架构清晰,测试真实
|
|
@@ -82,8 +80,8 @@ HEAD_SHA=$(git rev-parse HEAD)
|
|
|
82
80
|
- 修复后再进入下一个任务
|
|
83
81
|
|
|
84
82
|
**执行计划:**
|
|
85
|
-
-
|
|
86
|
-
-
|
|
83
|
+
- 每个任务完成后或在自然 checkpoint 审查
|
|
84
|
+
- 获取反馈,应用,继续
|
|
87
85
|
|
|
88
86
|
**临时开发:**
|
|
89
87
|
- 合并前审查
|