@boyingliu01/xp-gate 0.8.2 → 0.8.5
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/bin/xp-gate.js +111 -129
- package/hooks/pre-commit +5 -4
- package/lib/__tests__/baseline.test.js +123 -0
- package/lib/__tests__/migrate.test.js +20 -0
- package/lib/__tests__/uninstall.test.js +36 -0
- package/lib/__tests__/update-skill.test.js +11 -0
- package/lib/baseline.js +392 -0
- package/lib/doctor.js +127 -141
- package/lib/gate-audit.ts +25 -8
- package/lib/init.js +13 -2
- package/lib/migrate.js +74 -73
- package/lib/uninstall.js +63 -31
- package/lib/update-skill.js +32 -34
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/sprint-flow/SKILL.md +31 -0
- package/plugins/opencode/README.md +1 -1
- package/plugins/opencode/package.json +29 -5
- package/plugins/opencode/scripts/prepack.cjs +85 -0
- package/plugins/opencode/skills/sprint-flow/SKILL.md +31 -0
- package/skills/sprint-flow/SKILL.md +31 -0
package/lib/uninstall.js
CHANGED
|
@@ -260,6 +260,68 @@ function buildPlan(mode) {
|
|
|
260
260
|
return plan;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Remove a single file item with ownership verification.
|
|
265
|
+
* @returns {boolean} true on success, false on skip (not xp-gate owned)
|
|
266
|
+
*/
|
|
267
|
+
function removeFile(item, config) {
|
|
268
|
+
const manifestEntry = config.manifest && config.manifest.files
|
|
269
|
+
? config.manifest.files[item.manifestKey]
|
|
270
|
+
: null;
|
|
271
|
+
|
|
272
|
+
if (!verifyFileOwnership(item.path, manifestEntry, item.signature)) {
|
|
273
|
+
if (fs.existsSync(item.path)) {
|
|
274
|
+
console.warn(` Warning: ${item.label} does not contain xp-gate signature — skipping`);
|
|
275
|
+
}
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
fs.unlinkSync(item.path);
|
|
280
|
+
console.log(` Removed ${item.label}`);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Remove a directory item.
|
|
286
|
+
* @returns {boolean} true if dir existed and was removed, false otherwise
|
|
287
|
+
*/
|
|
288
|
+
function removeDir(item) {
|
|
289
|
+
if (!fs.existsSync(item.path)) return false;
|
|
290
|
+
fs.rmSync(item.path, { recursive: true, force: true });
|
|
291
|
+
console.log(` Removed ${item.label}`);
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Unset core.hooksPath if it matches the expected xp-gate path.
|
|
297
|
+
* @returns {boolean} true if unset was performed or already unset, false on mismatch
|
|
298
|
+
*/
|
|
299
|
+
function unsetGitConfigIfMatch(item) {
|
|
300
|
+
const currentPath = getCurrentHooksPath();
|
|
301
|
+
if (currentPath === null || currentPath === '') {
|
|
302
|
+
console.log(` ${item.label} — not set`);
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
if (currentPath === item.expectedPath) {
|
|
306
|
+
unsetHooksPath();
|
|
307
|
+
console.log(` Unset ${item.label}`);
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
console.warn(` Warning: core.hooksPath (${currentPath}) does not match xp-gate path — skipping unset`);
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Execute a single plan item (file/dir/gitconfig).
|
|
316
|
+
* @returns {boolean} true on success, false on skip/failure
|
|
317
|
+
*/
|
|
318
|
+
function executePlanItem(item, config) {
|
|
319
|
+
if (item.type === 'file') return removeFile(item, config);
|
|
320
|
+
if (item.type === 'dir') return removeDir(item);
|
|
321
|
+
if (item.type === 'gitconfig' && item.action === 'unset-hooks-path') return unsetGitConfigIfMatch(item);
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
|
|
263
325
|
/**
|
|
264
326
|
* @param {string[]} args CLI arguments
|
|
265
327
|
* @returns {number} exit code (0 = success, 1 = error)
|
|
@@ -337,37 +399,7 @@ async function uninstall(args) {
|
|
|
337
399
|
|
|
338
400
|
for (const item of plan) {
|
|
339
401
|
try {
|
|
340
|
-
|
|
341
|
-
const manifestEntry = config.manifest && config.manifest.files
|
|
342
|
-
? config.manifest.files[item.manifestKey]
|
|
343
|
-
: null;
|
|
344
|
-
|
|
345
|
-
if (!verifyFileOwnership(item.path, manifestEntry, item.signature)) {
|
|
346
|
-
if (fs.existsSync(item.path)) {
|
|
347
|
-
console.warn(` Warning: ${item.label} does not contain xp-gate signature — skipping`);
|
|
348
|
-
}
|
|
349
|
-
continue;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
fs.unlinkSync(item.path);
|
|
353
|
-
console.log(` Removed ${item.label}`);
|
|
354
|
-
} else if (item.type === 'dir') {
|
|
355
|
-
if (fs.existsSync(item.path)) {
|
|
356
|
-
fs.rmSync(item.path, { recursive: true, force: true });
|
|
357
|
-
console.log(` Removed ${item.label}`);
|
|
358
|
-
}
|
|
359
|
-
} else if (item.type === 'gitconfig' && item.action === 'unset-hooks-path') {
|
|
360
|
-
const currentPath = getCurrentHooksPath();
|
|
361
|
-
if (currentPath === null || currentPath === '') {
|
|
362
|
-
// Already unset
|
|
363
|
-
console.log(` ${item.label} — not set`);
|
|
364
|
-
} else if (currentPath === item.expectedPath) {
|
|
365
|
-
unsetHooksPath();
|
|
366
|
-
console.log(` Unset ${item.label}`);
|
|
367
|
-
} else {
|
|
368
|
-
console.warn(` Warning: core.hooksPath (${currentPath}) does not match xp-gate path — skipping unset`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
402
|
+
hadErrors = !executePlanItem(item, config) || hadErrors;
|
|
371
403
|
} catch (e) {
|
|
372
404
|
console.warn(` Warning: Could not remove ${item.label}: ${e.message}`);
|
|
373
405
|
hadErrors = true;
|
package/lib/update-skill.js
CHANGED
|
@@ -8,57 +8,55 @@ const HOME = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
|
8
8
|
const CONFIG_DIR = path.join(HOME, '.config', 'xp-gate');
|
|
9
9
|
const SKILLS_DIR = path.join(HOME, '.config', 'opencode', 'skills');
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const skills = config.installedSkills || {};
|
|
18
|
-
|
|
19
|
-
for (const [skillName, info] of Object.entries(skills)) {
|
|
20
|
-
console.log(` ${skillName}: ${info.version || 'unknown'}`);
|
|
21
|
-
}
|
|
22
|
-
console.log('Update check complete');
|
|
23
|
-
return 0;
|
|
11
|
+
function handleCheckMode() {
|
|
12
|
+
console.log('Checking for updates...');
|
|
13
|
+
const config = getConfig();
|
|
14
|
+
const skills = config.installedSkills || {};
|
|
15
|
+
for (const [skillName, info] of Object.entries(skills)) {
|
|
16
|
+
console.log(` ${skillName}: ${info.version || 'unknown'}`);
|
|
24
17
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
18
|
+
console.log('Update check complete');
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function handleAllMode(verbose) {
|
|
23
|
+
const config = getConfig();
|
|
24
|
+
const skills = config.installedSkills || {};
|
|
25
|
+
console.log('Updating all skills...');
|
|
26
|
+
let hasErrors = false;
|
|
27
|
+
for (const skillName of Object.keys(skills)) {
|
|
28
|
+
try {
|
|
29
|
+
await updateSingleSkill(skillName, verbose);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(`Failed to update ${skillName}: ${err.message}`);
|
|
32
|
+
hasErrors = true;
|
|
40
33
|
}
|
|
41
|
-
|
|
42
|
-
return hasErrors ? 1 : 0;
|
|
43
34
|
}
|
|
44
|
-
|
|
35
|
+
return hasErrors ? 1 : 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handleSingleMode(name, verbose) {
|
|
45
39
|
if (!name) {
|
|
46
40
|
console.error('Error: Skill name required');
|
|
47
41
|
console.error('Usage: xp-gate update-skill <name> or --all');
|
|
48
42
|
return 1;
|
|
49
43
|
}
|
|
50
|
-
|
|
51
44
|
const config = getConfig();
|
|
52
45
|
const skills = config.installedSkills || {};
|
|
53
|
-
|
|
54
46
|
if (!skills[name]) {
|
|
55
47
|
console.error(`Error: ${name} is not installed`);
|
|
56
48
|
return 1;
|
|
57
49
|
}
|
|
58
|
-
|
|
59
50
|
return updateSingleSkill(name, verbose);
|
|
60
51
|
}
|
|
61
52
|
|
|
53
|
+
async function updateSkill(name, options = {}) {
|
|
54
|
+
const { all = false, check = false, verbose = false } = options;
|
|
55
|
+
if (check) return handleCheckMode();
|
|
56
|
+
if (all) return handleAllMode(verbose);
|
|
57
|
+
return handleSingleMode(name, verbose);
|
|
58
|
+
}
|
|
59
|
+
|
|
62
60
|
async function updateSingleSkill(name, verbose) {
|
|
63
61
|
console.log(`Updating ${name}...`);
|
|
64
62
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 6 quality gates, Sprint Flow, and Delphi multi-expert review.",
|
|
6
6
|
"author": {
|
|
@@ -12,6 +12,7 @@ description: >
|
|
|
12
12
|
- "start sprint"
|
|
13
13
|
- "一键开发"
|
|
14
14
|
- "/sprint-flow"
|
|
15
|
+
触发后第一行输出: `Sprint Flow: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → USER ACCEPTANCE → FEEDBACK → SHIP → LAND → CLEANUP`
|
|
15
16
|
用法: /sprint-flow "[需求描述]"
|
|
16
17
|
示例: /sprint-flow "开发访谈机器人,支持多轮对话"
|
|
17
18
|
可选参数:
|
|
@@ -236,6 +237,36 @@ ISOLATE → AUTO-ESTIMATE → THINK → PLAN → [GITHOOKS-GATE] → BUILD → R
|
|
|
236
237
|
|
|
237
238
|
## 各 Phase 调用的 Skills
|
|
238
239
|
|
|
240
|
+
### ⚠️ 强制输出格式规范(Mandatory Output Format)
|
|
241
|
+
|
|
242
|
+
**执行每个 Phase 时,必须以以下固定格式输出阶段标题**,不可省略、不可合并、不可替换:
|
|
243
|
+
|
|
244
|
+
```markdown
|
|
245
|
+
## Phase -1: ISOLATE (隔离)
|
|
246
|
+
## Phase -0.5: AUTO-ESTIMATE (规模评估)
|
|
247
|
+
## Phase 0: THINK (思考)
|
|
248
|
+
## Phase 1: PLAN (规划)
|
|
249
|
+
## Phase 2: BUILD (构建)
|
|
250
|
+
## Phase 3: REVIEW (评审)
|
|
251
|
+
## Phase 4: USER ACCEPTANCE (用户验收)
|
|
252
|
+
## Phase 5: FEEDBACK (反馈)
|
|
253
|
+
## Phase 6: SHIP (发布)
|
|
254
|
+
## Phase 7: LAND (部署)
|
|
255
|
+
## Phase 8: CLEANUP (清理)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**规则**:
|
|
259
|
+
1. 每个 Phase **开始执行时必须首先输出**对应的 `## Phase X: NAME` 标题行(作为该 Phase 输出的第一行)
|
|
260
|
+
2. **禁止省略** "Phase" 关键词(如不能只写 "ISOLATE" 或 "## -1")
|
|
261
|
+
3. **禁止合并**多个 Phase 的输出(每个 Phase 必须有独立标题)
|
|
262
|
+
4. **格式必须精确匹配**:`## Phase ` + 数字 + `: ` + 大写英文名 + ` (中文名)`
|
|
263
|
+
5. 跳过某个 Phase(如 `--resume-from build` 跳过了 -1, -0.5, 0, 1)时,不输出被跳过 Phase 的标题
|
|
264
|
+
6. 触发 `/sprint-flow` 后,**第一行输出应包含工作流阶段概览**:
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
Sprint Flow: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → USER ACCEPTANCE → FEEDBACK → SHIP → LAND → CLEANUP
|
|
268
|
+
```
|
|
269
|
+
|
|
239
270
|
### Phase -1: ISOLATE(git worktree 隔离)
|
|
240
271
|
|
|
241
272
|
**执行时机**: `/sprint-flow` 启动后、Phase 0 THINK 之前。**自动执行**。
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "@
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"private": true,
|
|
2
|
+
"name": "@boyingliu01/opencode-plugin",
|
|
3
|
+
"version": "0.8.5",
|
|
5
4
|
"type": "module",
|
|
6
5
|
"main": "index.ts",
|
|
7
6
|
"description": "XP-Gate quality gates + AI workflow skills for OpenCode",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/boyingliu01/xp-gate",
|
|
10
|
+
"directory": "plugins/opencode"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"registry": "https://registry.npmjs.org",
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"index.ts",
|
|
18
|
+
"skills/",
|
|
19
|
+
"tsconfig.json",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
8
22
|
"scripts": {
|
|
9
|
-
"
|
|
23
|
+
"prepack": "node scripts/prepack.cjs",
|
|
10
24
|
"check": "tsc --noEmit"
|
|
11
25
|
},
|
|
12
26
|
"dependencies": {
|
|
@@ -14,5 +28,15 @@
|
|
|
14
28
|
},
|
|
15
29
|
"devDependencies": {
|
|
16
30
|
"typescript": "^5.4.0"
|
|
17
|
-
}
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"opencode",
|
|
34
|
+
"plugin",
|
|
35
|
+
"xp-gate",
|
|
36
|
+
"quality-gates",
|
|
37
|
+
"code-review",
|
|
38
|
+
"sprint-flow"
|
|
39
|
+
],
|
|
40
|
+
"author": "boyingliu01",
|
|
41
|
+
"license": "MIT"
|
|
18
42
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* prepack.js — Bundle skills into @boyingliu01/opencode-plugin before npm publish.
|
|
6
|
+
*
|
|
7
|
+
* Skills live in repo-root `skills/` and are gitignored in `plugins/opencode/skills/`.
|
|
8
|
+
* This script copies them into the plugin package so the published tarball is self-contained.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const PLUGIN_ROOT = path.resolve(__dirname, '..');
|
|
15
|
+
const REPO_ROOT = path.resolve(PLUGIN_ROOT, '..', '..');
|
|
16
|
+
|
|
17
|
+
const CORE_SKILLS = [
|
|
18
|
+
'admin-template-guidelines',
|
|
19
|
+
'delphi-review',
|
|
20
|
+
'improve-codebase-architecture',
|
|
21
|
+
'ralph-loop',
|
|
22
|
+
'sprint-flow',
|
|
23
|
+
'test-driven-development',
|
|
24
|
+
'test-specification-alignment',
|
|
25
|
+
'to-issues',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'coverage', '.opencode']);
|
|
29
|
+
const SKIP_FILES = new Set(['package-lock.json']);
|
|
30
|
+
const SKIP_FILE_SUFFIXES = ['.lock', '.js.map'];
|
|
31
|
+
|
|
32
|
+
function shouldSkipFile(name) {
|
|
33
|
+
if (SKIP_FILES.has(name)) return true;
|
|
34
|
+
return SKIP_FILE_SUFFIXES.some(suffix => name.endsWith(suffix));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function copyDir(src, dest) {
|
|
38
|
+
if (!fs.existsSync(src)) {
|
|
39
|
+
console.error(`[prepack] SKIP (missing): ${src}`);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
43
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
46
|
+
const srcPath = path.join(src, entry.name);
|
|
47
|
+
const destPath = path.join(dest, entry.name);
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
copyDir(srcPath, destPath);
|
|
50
|
+
} else if (entry.isFile()) {
|
|
51
|
+
if (shouldSkipFile(entry.name)) continue;
|
|
52
|
+
fs.copyFileSync(srcPath, destPath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function main() {
|
|
59
|
+
const skillsDest = path.join(PLUGIN_ROOT, 'skills');
|
|
60
|
+
|
|
61
|
+
// Clean existing skills
|
|
62
|
+
if (fs.existsSync(skillsDest)) {
|
|
63
|
+
fs.rmSync(skillsDest, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
fs.mkdirSync(skillsDest, { recursive: true });
|
|
66
|
+
|
|
67
|
+
let copied = 0;
|
|
68
|
+
for (const name of CORE_SKILLS) {
|
|
69
|
+
const src = path.join(REPO_ROOT, 'skills', name);
|
|
70
|
+
const dest = path.join(skillsDest, name);
|
|
71
|
+
if (copyDir(src, dest)) {
|
|
72
|
+
copied += 1;
|
|
73
|
+
console.error(`[prepack] skills/${name}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (copied !== CORE_SKILLS.length) {
|
|
78
|
+
console.error(`[prepack] ERROR: expected ${CORE_SKILLS.length} skills, copied ${copied}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.error(`[prepack] done: ${copied} skills bundled for @boyingliu01/opencode-plugin`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
main();
|
|
@@ -12,6 +12,7 @@ description: >
|
|
|
12
12
|
- "start sprint"
|
|
13
13
|
- "一键开发"
|
|
14
14
|
- "/sprint-flow"
|
|
15
|
+
触发后第一行输出: `Sprint Flow: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → USER ACCEPTANCE → FEEDBACK → SHIP → LAND → CLEANUP`
|
|
15
16
|
用法: /sprint-flow "[需求描述]"
|
|
16
17
|
示例: /sprint-flow "开发访谈机器人,支持多轮对话"
|
|
17
18
|
可选参数:
|
|
@@ -236,6 +237,36 @@ ISOLATE → AUTO-ESTIMATE → THINK → PLAN → [GITHOOKS-GATE] → BUILD → R
|
|
|
236
237
|
|
|
237
238
|
## 各 Phase 调用的 Skills
|
|
238
239
|
|
|
240
|
+
### ⚠️ 强制输出格式规范(Mandatory Output Format)
|
|
241
|
+
|
|
242
|
+
**执行每个 Phase 时,必须以以下固定格式输出阶段标题**,不可省略、不可合并、不可替换:
|
|
243
|
+
|
|
244
|
+
```markdown
|
|
245
|
+
## Phase -1: ISOLATE (隔离)
|
|
246
|
+
## Phase -0.5: AUTO-ESTIMATE (规模评估)
|
|
247
|
+
## Phase 0: THINK (思考)
|
|
248
|
+
## Phase 1: PLAN (规划)
|
|
249
|
+
## Phase 2: BUILD (构建)
|
|
250
|
+
## Phase 3: REVIEW (评审)
|
|
251
|
+
## Phase 4: USER ACCEPTANCE (用户验收)
|
|
252
|
+
## Phase 5: FEEDBACK (反馈)
|
|
253
|
+
## Phase 6: SHIP (发布)
|
|
254
|
+
## Phase 7: LAND (部署)
|
|
255
|
+
## Phase 8: CLEANUP (清理)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**规则**:
|
|
259
|
+
1. 每个 Phase **开始执行时必须首先输出**对应的 `## Phase X: NAME` 标题行(作为该 Phase 输出的第一行)
|
|
260
|
+
2. **禁止省略** "Phase" 关键词(如不能只写 "ISOLATE" 或 "## -1")
|
|
261
|
+
3. **禁止合并**多个 Phase 的输出(每个 Phase 必须有独立标题)
|
|
262
|
+
4. **格式必须精确匹配**:`## Phase ` + 数字 + `: ` + 大写英文名 + ` (中文名)`
|
|
263
|
+
5. 跳过某个 Phase(如 `--resume-from build` 跳过了 -1, -0.5, 0, 1)时,不输出被跳过 Phase 的标题
|
|
264
|
+
6. 触发 `/sprint-flow` 后,**第一行输出应包含工作流阶段概览**:
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
Sprint Flow: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → USER ACCEPTANCE → FEEDBACK → SHIP → LAND → CLEANUP
|
|
268
|
+
```
|
|
269
|
+
|
|
239
270
|
### Phase -1: ISOLATE(git worktree 隔离)
|
|
240
271
|
|
|
241
272
|
**执行时机**: `/sprint-flow` 启动后、Phase 0 THINK 之前。**自动执行**。
|
|
@@ -12,6 +12,7 @@ description: >
|
|
|
12
12
|
- "start sprint"
|
|
13
13
|
- "一键开发"
|
|
14
14
|
- "/sprint-flow"
|
|
15
|
+
触发后第一行输出: `Sprint Flow: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → USER ACCEPTANCE → FEEDBACK → SHIP → LAND → CLEANUP`
|
|
15
16
|
用法: /sprint-flow "[需求描述]"
|
|
16
17
|
示例: /sprint-flow "开发访谈机器人,支持多轮对话"
|
|
17
18
|
可选参数:
|
|
@@ -236,6 +237,36 @@ ISOLATE → AUTO-ESTIMATE → THINK → PLAN → [GITHOOKS-GATE] → BUILD → R
|
|
|
236
237
|
|
|
237
238
|
## 各 Phase 调用的 Skills
|
|
238
239
|
|
|
240
|
+
### ⚠️ 强制输出格式规范(Mandatory Output Format)
|
|
241
|
+
|
|
242
|
+
**执行每个 Phase 时,必须以以下固定格式输出阶段标题**,不可省略、不可合并、不可替换:
|
|
243
|
+
|
|
244
|
+
```markdown
|
|
245
|
+
## Phase -1: ISOLATE (隔离)
|
|
246
|
+
## Phase -0.5: AUTO-ESTIMATE (规模评估)
|
|
247
|
+
## Phase 0: THINK (思考)
|
|
248
|
+
## Phase 1: PLAN (规划)
|
|
249
|
+
## Phase 2: BUILD (构建)
|
|
250
|
+
## Phase 3: REVIEW (评审)
|
|
251
|
+
## Phase 4: USER ACCEPTANCE (用户验收)
|
|
252
|
+
## Phase 5: FEEDBACK (反馈)
|
|
253
|
+
## Phase 6: SHIP (发布)
|
|
254
|
+
## Phase 7: LAND (部署)
|
|
255
|
+
## Phase 8: CLEANUP (清理)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**规则**:
|
|
259
|
+
1. 每个 Phase **开始执行时必须首先输出**对应的 `## Phase X: NAME` 标题行(作为该 Phase 输出的第一行)
|
|
260
|
+
2. **禁止省略** "Phase" 关键词(如不能只写 "ISOLATE" 或 "## -1")
|
|
261
|
+
3. **禁止合并**多个 Phase 的输出(每个 Phase 必须有独立标题)
|
|
262
|
+
4. **格式必须精确匹配**:`## Phase ` + 数字 + `: ` + 大写英文名 + ` (中文名)`
|
|
263
|
+
5. 跳过某个 Phase(如 `--resume-from build` 跳过了 -1, -0.5, 0, 1)时,不输出被跳过 Phase 的标题
|
|
264
|
+
6. 触发 `/sprint-flow` 后,**第一行输出应包含工作流阶段概览**:
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
Sprint Flow: ISOLATE → AUTO-ESTIMATE → THINK → PLAN → BUILD → REVIEW → USER ACCEPTANCE → FEEDBACK → SHIP → LAND → CLEANUP
|
|
268
|
+
```
|
|
269
|
+
|
|
239
270
|
### Phase -1: ISOLATE(git worktree 隔离)
|
|
240
271
|
|
|
241
272
|
**执行时机**: `/sprint-flow` 启动后、Phase 0 THINK 之前。**自动执行**。
|