Repository navigation
Expand file tree
/
Copy pathspec-rocket
More file actions
executable file
·588 lines (557 loc) · 26.5 KB
/
Copy pathspec-rocket
File metadata and controls
executable file
·588 lines (557 loc) · 26.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
#!/usr/bin/env bash
# ============================================================
# SpecRocket CLI — init / update / migrate
# 规格驱动开发(SDD)框架的命令入口 (v3.5.0)
# Usage:
# ./spec-rocket init [项目名] # 初始化新项目(兼容 init.sh)
# ./spec-rocket update # 一键更新本地 skill(工具感知)
# ./spec-rocket migrate [路径] # 项目重构:介入 → 理解 → 保持 → 重构(不区分是否 SpecRocket 项目)
# ./spec-rocket help # 帮助
# ============================================================
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VERSION="3.5.0"
TEMPLATE="$SCRIPT_DIR/template"
GH_REPO="https://github.com/Toketec/SpecRocket.git"
USER_BASE="${SPECROCKET_USER_HOME:-$HOME}"
# ---------- 颜色 ----------
C_GREEN='\033[0;32m'; C_YELLOW='\033[1;33m'; C_RED='\033[0;31m'; C_CYAN='\033[0;36m'; C_NC='\033[0m'
info() { echo -e "${C_CYAN}[SpecRocket]${C_NC} $*"; }
ok() { echo -e "${C_GREEN}✅ $*${C_NC}"; }
warn() { echo -e "${C_YELLOW}⚠️ $*${C_NC}"; }
err() { echo -e "${C_RED}❌ $*${C_NC}" >&2; }
# ============================================================
# init — 初始化新项目(逻辑与 init.sh 一致)
# ============================================================
cmd_init() {
if [ ! -d "$TEMPLATE" ]; then
err "找不到 template/ 目录。请先 clone 本仓库:git clone $GH_REPO"
exit 1
fi
if [ $# -eq 0 ]; then
TARGET_DIR="."
info "在当前目录初始化骨架 ... ($(pwd))"
else
NAME="$1"
TARGET_DIR="${2:-$NAME}"
if [ -d "$TARGET_DIR" ]; then
err "目录 '$TARGET_DIR' 已存在"
exit 1
fi
info "创建 $TARGET_DIR ..."
mkdir -p "$TARGET_DIR"
fi
info "复制骨架 ..."
shopt -s dotglob
cp -r "$TEMPLATE"/* "$TARGET_DIR/"
shopt -u dotglob
cd "$TARGET_DIR"
if [ ! -d .git ]; then
info "初始化 Git ..."
git init >/dev/null
git add . >/dev/null
git commit -m "init: SpecRocket project skeleton" >/dev/null 2>&1 || warn "git commit 失败(可能未配置用户信息),可稍后手动提交"
fi
ok "项目已初始化(v$VERSION 结构:docs/ + sprints/ + adrs/ + apps/businesses/tools + assets/)"
echo " 下一步:运行 /spec-rocket brainstorm 引导写产品文档"
}
# ============================================================
# update — 一键更新本地 skill(工具感知)
# ============================================================
cmd_update() {
info "SpecRocket update v$VERSION — 检测本地安装的 AI 工具并同步 skill ..."
echo ""
# ① 更新本地仓库(保证模板源头最新)
if [ "${SPECROCKET_OFFLINE:-0}" = "1" ]; then
info "① 离线模式,跳过仓库更新"
elif git rev-parse --git-dir >/dev/null 2>&1 && git remote get-url origin 2>/dev/null | grep -qi specrocket; then
info "① 更新本地 SpecRocket 仓库 (git pull) ..."
git pull --ff-only 2>&1 | sed 's/^/ /' || warn "git pull 失败,跳过(可稍后手动 git pull)"
else
warn "① 当前不在 SpecRocket 仓库内,跳过仓库更新"
fi
echo ""
# ② 检测 AI 工具
info "② 检测 AI 工具 ..."
FOUND=()
for tool in hermes claude codex cursor windsurf cline aider; do
if command -v "$tool" >/dev/null 2>&1; then
FOUND+=("$tool")
echo " 已检测: $tool"
fi
done
# 无 CLI 命令但存在配置目录也算
[ -d "$USER_BASE/.hermes" ] && echo " 已检测: Hermes Agent ($USER_BASE/.hermes)"
[ -d "$USER_BASE/.claude" ] && echo " 已检测: Claude Code ($USER_BASE/.claude)"
[ -d "$USER_BASE/.codex" ] && echo " 已检测: Codex ($USER_BASE/.codex)"
[ -d "$USER_BASE/.cursor" ] && echo " 已检测: Cursor ($USER_BASE/.cursor)"
[ -d "$USER_BASE/.windsurf" ] && echo " 已检测: Windsurf ($USER_BASE/.windsurf)"
if [ ${#FOUND[@]} -eq 0 ]; then
warn "未检测到常见 AI 工具 CLI。仍将同步 Hermes skill 目录(若存在)。"
fi
echo ""
# ③ 按工具同步 skill / 规则文件
info "③ 按工具同步 skill ..."
# Hermes Agent → ~/.hermes/skills/spec-rocket/
if [ -d "$USER_BASE/.hermes/skills" ]; then
SYNC_HERMES=1
if [ ! -f "$SCRIPT_DIR/SKILL.md" ]; then
err "仓库内缺少 SKILL.md,无法同步 Hermes skill"
SYNC_HERMES=0
fi
if [ "$SYNC_HERMES" = "1" ]; then
mkdir -p "$USER_BASE/.hermes/skills/spec-rocket"
cp "$SCRIPT_DIR/SKILL.md" "$USER_BASE/.hermes/skills/spec-rocket/SKILL.md"
ok "Hermes skill 已同步 → $USER_BASE/.hermes/skills/spec-rocket/ (v$VERSION)"
fi
else
info " 未发现 ~/.hermes/skills,跳过 Hermes 同步"
fi
# spec-rocket-light(若本地存在)
if [ -d "$USER_BASE/.hermes/skills/spec-rocket-light" ]; then
LIGHT_SYNCED=0
for light_src in "$SCRIPT_DIR/../SpecRocket-light/SKILL.md" "$SCRIPT_DIR/../SpecRocketLight/SKILL.md"; do
if [ -f "$light_src" ]; then
cp "$light_src" "$USER_BASE/.hermes/skills/spec-rocket-light/SKILL.md"
ok "spec-rocket-light skill 已同步 (来自 $light_src)"
LIGHT_SYNCED=1
break
fi
done
[ "$LIGHT_SYNCED" = "0" ] && warn "本地有 spec-rocket-light skill,但未找到 light 源仓库(SpecRocketLight/),跳过"
else
info " spec-rocket-light 未安装,跳过"
fi
# Codex → ~/.codex/AGENTS.md(全局指令,谨慎:只更新已有 SpecRocket 片段,不强制写入)
if [ -f "$USER_BASE/.codex/AGENTS.md" ] && grep -q "SpecRocket" "$USER_BASE/.codex/AGENTS.md" 2>/dev/null; then
warn "检测到 ~/.codex/AGENTS.md 含 SpecRocket 内容——请手动更新该文件中的版本说明,或删除旧片段后由项目级 AGENTS.md 接管"
else
info " Codex: 未发现全局 SpecRocket 配置(推荐用项目级 AGENTS.md,由 init/migrate 注入)"
fi
# Claude Code / Cursor / Windsurf / Cline → 项目级规则文件
info " Claude Code / Cursor / Windsurf / Cline: 规则文件在项目内(CLAUDE.md / .cursorrules / .windsurf/rules),"
info " 由每个项目的 /spec-rocket migrate 或 init 注入最新版本,无需全局更新。"
echo ""
ok "update 完成"
echo " 版本: v$VERSION | 变更摘要见 GitHub Release 或 README"
echo " 若本项目使用的模板已过时,请对项目执行 /spec-rocket migrate 重构"
}
# ============================================================
# migrate — 项目重构:介入 → 理解 → 保持 → 重构(结构层由脚本执行)
# 重构原则:转移 → 收敛 → 保留 → 删除;最终目录结构零残留
# 内容层(理解项目/转移语义内容/保持原有设计)由 AI 斜杠命令按 SKILL.md 规则执行
# ============================================================
cmd_migrate() {
TARGET="${1:-.}"
if [ ! -d "$TARGET" ]; then
err "目标目录不存在: $TARGET"
exit 1
fi
cd "$TARGET"
info "SpecRocket migrate v$VERSION — 目标: $(pwd)"
echo ""
REPORT_NEEDED=0
LEGACY_NOTES=() # 保留待确认项(内容已转移,确认后删除)
STRAY_DOCS=() # 规则1:游离内容文档 → 收敛 docs/
ROOT_STRAY_R1=() # 规则1:根目录内容型文档 → 收敛 docs/
ROOT_STRAY_R2=() # 规则2:根目录非规范项 → 归位 assets/
ASSETS_STRAY=()
# ═══════════════════════════════════════════════
# 1. 迁移旧 sprint 容器:docs/sprints/sprint-NNN_xxx/ → sprints/sp-NNN-xxx/docs/
# ═══════════════════════════════════════════════
if [ -d docs/sprints ]; then
info "① 检测到旧结构 docs/sprints/,迁移到顶层 sprints/ ..."
mkdir -p sprints
# 旧 sprint 文件(v2.1 ~ v2.3)检测
while IFS= read -r f; do
LEGACY_NOTES+=("$f(旧版文件,AI 按迁移映射表转移内容)")
REPORT_NEEDED=1
done < <(find docs -name "ux-flows.md" -o -name "ui-wireframes.md" -o -name "user-journey-flows.md" 2>/dev/null || true)
# 处理具体 sprint:sprint-NNN_xxx/ → sp-NNN-xxx/docs/
for sprint in docs/sprints/sprint-*; do
[ -d "$sprint" ] || continue
base=$(basename "$sprint")
newname=$(printf '%s' "$base" | sed 's/^sprint-/sp-/; s/_/-/g')
if [ -d "sprints/$newname" ]; then
warn " 目标 sprints/$newname 已存在,跳过(内容请 AI 合并)"
continue
fi
mkdir -p "sprints/$newname/docs"
# 转移内容(不删除源,保留 .legacy 供 AI 确认)
cp -r "$sprint"/* "sprints/$newname/docs/" 2>/dev/null || true
# sprint 内含 specs/ 的情况(v2.12 结构异常)→ 移到新 specs/
if [ -d "$sprint/specs" ]; then
cp -r "$sprint/specs" "sprints/$newname/specs" 2>/dev/null || true
fi
ok " 已转移: $base → sprints/$newname/docs/"
mv "$sprint" "$sprint.legacy"
LEGACY_NOTES+=("$sprint.legacy → 内容已转移至 sprints/$newname/,AI 确认后删除")
REPORT_NEEDED=1
done
# 处理 sprint-000(v2.5 废弃)
if [ -d docs/sprints/sprint-000_initial ]; then
HAS_REAL=$(grep -L "{" docs/sprints/sprint-000_initial/*.md 2>/dev/null | head -1)
if [ -z "$HAS_REAL" ]; then
rm -rf docs/sprints/sprint-000_initial
ok " 已删除废弃目录: docs/sprints/sprint-000_initial/(纯模板副本)"
else
mv docs/sprints/sprint-000_initial docs/sprints/sprint-000_initial.legacy
warn " sprint-000_initial 含真实内容 → 保留为 .legacy(AI 转移基线设计到 sp-001-*/docs/ 后删除)"
LEGACY_NOTES+=("docs/sprints/sprint-000_initial.legacy → 基线设计转移至 sp-001-*/docs/ 后删除")
REPORT_NEEDED=1
fi
fi
# 转移 _template(若有旧模板文件散落)——新模板由下方统一补齐
rm -rf docs/sprints 2>/dev/null || true
if [ -z "$(ls -A sprints 2>/dev/null)" ]; then
rmdir sprints 2>/dev/null || true
fi
fi
# ═══════════════════════════════════════════════
# 2. 迁移 ADR/ → adrs/(旧单文件 → 新 adr 文件夹)
# ═══════════════════════════════════════════════
if [ -d ADR ]; then
info "② 检测到旧结构 ADR/,迁移到 adrs/(一次大型变动 = 一个 adr 文件夹)..."
mkdir -p adrs
for f in ADR/ADR-*.md; do
[ -f "$f" ] || continue
base=$(basename "$f")
# adr-名称 → adr-YYYYMMDD-名称(用迁移当天日期)
name=$(printf '%s' "$base" | sed 's/^ADR-//; s/\.md$//; s/_/-/g')
newdir="adrs/adr-$(date +%Y%m%d)-$name"
if [ ! -d "$newdir" ]; then
mkdir -p "$newdir"
cp "$f" "$newdir/architecture.md"
# 补齐另两份占位,提示 AI 按新语义收敛
printf '# 数据模型\n\n> 待 AI 按新语义补充(一次大型变动 = 一个 adr 文件夹)。\n' > "$newdir/data-model.md"
printf '# 影响与注意事项\n\n> 待 AI 按新语义补充(关联模块/部署/回滚)。\n' > "$newdir/impact.md"
ok " 已转移: $base → $newdir/(架构变动设计文件夹)"
LEGACY_NOTES+=("$f → 已转移至 $newdir/,旧决策记录已并入 architecture.md,data-model/impact 待 AI 收敛")
REPORT_NEEDED=1
else
warn " $newdir 已存在,跳过(内容请 AI 合并)"
fi
done
# ADR 内其他游离 .md → 报告
while IFS= read -r f; do
case "$f" in ADR/_template/*|ADR/ADR-*.md) continue;; esac
LEGACY_NOTES+=("$f(ADR 内游离文档,AI 判断归属)")
REPORT_NEEDED=1
done < <(find ADR -type f -name "*.md" 2>/dev/null || true)
fi
# 2b. 收敛 v3.0 单文件时代遗留的 adrs/*.md(非 _template)→ 文件夹
if [ -d adrs ]; then
while IFS= read -r f; do
case "$f" in adrs/_template/*|adrs/*/) continue;; esac
[ -f "$f" ] || continue
base=$(basename "$f")
name=$(printf '%s' "$base" | sed 's/^adr-//; s/\.md$//; s/_/-/g')
newdir="adrs/adr-$(date +%Y%m%d)-$name"
if [ ! -d "$newdir" ]; then
mkdir -p "$newdir"
mv "$f" "$newdir/architecture.md"
printf '# 数据模型\n\n> 待 AI 按新语义补充(一次大型变动 = 一个 adr 文件夹)。\n' > "$newdir/data-model.md"
printf '# 影响与注意事项\n\n> 待 AI 按新语义补充(关联模块/部署/回滚)。\n' > "$newdir/impact.md"
ok " 已收敛: $base → $newdir/(单文件 → 文件夹,3 份文档)"
LEGACY_NOTES+=("$base → 已收敛至 $newdir/,内容并入 architecture.md,其余待 AI 整合")
REPORT_NEEDED=1
fi
done < <(find adrs -maxdepth 1 -type f -name "*.md" 2>/dev/null || true)
fi
# ═══════════════════════════════════════════════
# 3. 检测模块内 specs/(v2.x 模块维度规格)→ 报告 AI 迁移到迭代容器
# ═══════════════════════════════════════════════
MODULE_SPECS=()
for dir in apps businesses tools; do
[ -d "$dir" ] || continue
while IFS= read -r sp; do
MODULE_SPECS+=("$sp")
done < <(find "$dir" -type d -name specs 2>/dev/null || true)
done
if [ ${#MODULE_SPECS[@]} -gt 0 ]; then
warn "③ 检测到模块内 specs/(v2.x 结构)——需迁移到迭代容器 sprints/sp-NNN-*/specs/:"
for sp in "${MODULE_SPECS[@]}"; do
echo " - $sp/(AI 按 spec 头部「基于冲刺」字段归入对应冲刺,重新编号 spec-NNN-xxx)"
done
REPORT_NEEDED=1
fi
# ═══════════════════════════════════════════════
# 4. 根目录残留 ssot-convention(方法论本体,仅应存在于主仓库)
# ═══════════════════════════════════════════════
if ls ssot-convention*.md >/dev/null 2>&1; then
mkdir -p archive
for f in ssot-convention*.md; do
mv "$f" "archive/$f"
ok " 已归档: $f → archive/$f(方法论本体文档,最新版见 SpecRocket GitHub 主仓库)"
done
fi
# ═══════════════════════════════════════════════
# 4b. LICENSE 处理(v3.2 起模板不再预设证书)
# ═══════════════════════════════════════════════
if [ -f LICENSE ]; then
if head -1 LICENSE | grep -q "^MIT License$" && grep -q "Toketec" LICENSE; then
mkdir -p archive
mv LICENSE archive/LICENSE
ok " 已归档: LICENSE → archive/LICENSE(v3.2 起模板不再预设证书,旧模板默认 MIT 已移除,如需证书请自行添加)"
REPORT_NEEDED=1
else
warn " 保留: LICENSE(项目自定义证书,v3.2 起模板不再预设证书,此证书由项目自行持有)"
fi
fi
# ═══════════════════════════════════════════════
# 5. 补齐新骨架(docs 四件套 + sprints/_template/ + adrs/_template/ + assets/)
# ═══════════════════════════════════════════════
# docs/ 四件套
mkdir -p docs
for f in product-overview.md non-functional-reqs.md visual-design.md whitepaper.md; do
if [ ! -f "docs/$f" ] && [ -f "$TEMPLATE/docs/$f" ]; then
cp "$TEMPLATE/docs/$f" "docs/$f"
ok " 补齐: docs/$f"
fi
done
# 已有 sprint 容器补齐 prototypes.md(UI 设计文档,v3.2.1)
if [ -d sprints ]; then
while IFS= read -r pd; do
if [ ! -f "$pd/prototypes.md" ] && [ -f "$TEMPLATE/sprints/_template/docs/prototypes/prototypes.md" ]; then
cp "$TEMPLATE/sprints/_template/docs/prototypes/prototypes.md" "$pd/prototypes.md"
ok " 补齐: $pd/prototypes.md(UI 设计文档)"
fi
done < <(find sprints -maxdepth 5 -type d -name prototypes 2>/dev/null || true)
fi
# sprints/_template/
if [ ! -d sprints/_template ] && [ -d "$TEMPLATE/sprints/_template" ]; then
mkdir -p sprints
cp -r "$TEMPLATE/sprints/_template" sprints/_template
ok " 补齐: sprints/_template/(docs/ + specs/ 模板)"
fi
# adrs/_template/
if [ ! -d adrs/_template ] && [ -d "$TEMPLATE/adrs/_template" ]; then
mkdir -p adrs
cp -r "$TEMPLATE/adrs/_template" adrs/_template
ok " 补齐: adrs/_template/"
fi
# assets/ 骨架(v2.9 沿用)
if [ ! -d assets ]; then
if [ -d "$TEMPLATE/assets" ]; then
cp -r "$TEMPLATE/assets" assets
ok " 补齐: assets/ 运营资产骨架(configs/interfaces/standards/manuals)"
fi
else
for sub in configs interfaces standards manuals; do
if [ ! -d "assets/$sub" ]; then
mkdir -p "assets/$sub"
ok " 补齐: assets/$sub/"
fi
done
[ ! -f assets/README.md ] && [ -f "$TEMPLATE/assets/README.md" ] && cp "$TEMPLATE/assets/README.md" assets/README.md
while IFS= read -r f; do
base=$(basename "$f")
[ "$base" = "README.md" ] && continue
ASSETS_STRAY+=("$f")
done < <(find assets -maxdepth 1 -mindepth 1 ! -name "configs" ! -name "interfaces" ! -name "standards" ! -name "manuals" 2>/dev/null || true)
fi
# 运营资产目录自动归位(规则2)
move_ops_dir() { # $1=源目录 $2=目标子目录
local src="$1" dst="assets/$2"
[ -d "$src" ] || return 0
if [ ! -d "$dst" ]; then
mv "$src" "$dst"
ok " 已转移: $src/ → $dst/(规则2:运营资产收敛 assets/)"
REPORT_NEEDED=1
elif [ -z "$(ls -A "$dst" | grep -v '^\.gitkeep$' 2>/dev/null)" ]; then
mv "$src"/* "$dst"/ 2>/dev/null || true
rmdir "$src" 2>/dev/null || true
ok " 已转移: $src/ → $dst/(合并入骨架)"
REPORT_NEEDED=1
else
warn " $src/ → $dst/(目标已存在,内容请 AI 合并)"
REPORT_NEEDED=1
fi
}
move_ops_dir config configs
move_ops_dir api interfaces
move_ops_dir manual manuals
move_ops_dir docs-manual manuals
move_ops_dir standards standards
# ═══════════════════════════════════════════════
# 6. 游离文档扫描(规则1:收敛 docs/;规则2:归位 assets/)
# ═══════════════════════════════════════════════
if [ -d docs ]; then
while IFS= read -r f; do
base=$(basename "$f")
case "$base" in
product-overview.md|non-functional-reqs.md|visual-design.md|whitepaper.md|README.md|preview.html) continue ;;
*.legacy.md) continue ;;
esac
STRAY_DOCS+=("$f")
done < <(find docs -type f \( -name "*.md" -o -name "*.html" \) 2>/dev/null || true)
fi
while IFS= read -r f; do
base=$(basename "$f")
case "$base" in
AGENTS.md|CLAUDE.md|README.md|README.en.md|MIGRATION-REPORT.md|ssot-convention*.md|LICENSE) continue ;;
*.legacy.md) continue ;;
esac
case "$base" in
*.md|*.txt|*.docx|*.doc|*.pdf|*.rst|*.tex) ROOT_STRAY_R1+=("$f") ;;
*) ROOT_STRAY_R2+=("$f") ;;
esac
done < <(find . -maxdepth 1 -type f ! -name ".*" 2>/dev/null || true)
while IFS= read -r d; do
base=$(basename "$d")
case "$base" in
docs|sprints|adrs|apps|businesses|tools|assets|archive) continue ;;
.*) continue ;;
esac
ROOT_STRAY_R2+=("$d")
done < <(find . -maxdepth 1 -type d ! -name "." 2>/dev/null || true)
# ═══════════════════════════════════════════════
# 6b. visual-design 混入业务设计扫描(v3.2.1)
# ═══════════════════════════════════════════════
VD_SCAN=0
VD_HITS=""
if [ -f docs/visual-design.md ]; then
VD_HITS=$(grep -nE "首页|列表页|详情页|登录页|注册页|购物车|导航栏|线框|框线图|页面结构|业务字段" docs/visual-design.md 2>/dev/null | grep -vE ":[|>]" | head -10 || true)
if [ -n "$VD_HITS" ]; then
VD_SCAN=1
REPORT_NEEDED=1
fi
fi
# ═══════════════════════════════════════════════
# 7. 报告输出
# ═══════════════════════════════════════════════
if [ "${#LEGACY_NOTES[@]}" -gt 0 ]; then
warn "检测到旧版结构,已完成结构层迁移(详见下方报告):"
for n in "${LEGACY_NOTES[@]}"; do
echo " - $n"
done
fi
if [ "${#STRAY_DOCS[@]}" -gt 0 ] || [ "${#ROOT_STRAY_R1[@]}" -gt 0 ]; then
warn "检测到不符合模板的游离内容文档(规则1:优先收敛到 docs/ 对应文档,由 AI 判断归属并合并):"
for f in "${STRAY_DOCS[@]}" "${ROOT_STRAY_R1[@]}"; do
echo " - $f"
done
REPORT_NEEDED=1
fi
if [ "${#ROOT_STRAY_R2[@]}" -gt 0 ]; then
warn "检测到根目录非规范项(规则2:转移并收敛到 assets/ 对应子目录,由 AI 确认归属):"
for f in "${ROOT_STRAY_R2[@]}"; do
echo " - $f"
done
REPORT_NEEDED=1
fi
if [ "$VD_SCAN" = "1" ]; then
warn "docs/visual-design.md 疑似混入业务设计(关键词命中;含模板示例词的行可忽略,请 AI 判断):"
echo "$VD_HITS" | sed 's/^/ /'
echo " → 确为业务设计时:转移到 sprints/*/docs/prototypes/prototypes.md,visual-design.md 只留审美边界"
fi
if [ "${#ASSETS_STRAY[@]}" -gt 0 ]; then
warn "检测到 assets/ 内散落项(规则2:归位到 configs/interfaces/standards/manuals):"
for f in "${ASSETS_STRAY[@]}"; do
echo " - $f"
done
REPORT_NEEDED=1
fi
if [ "${#MODULE_SPECS[@]}" -gt 0 ]; then
REPORT_NEEDED=1
fi
# 生成 MIGRATION-REPORT.md
if [ "$REPORT_NEEDED" = "1" ] || [ ${#LEGACY_NOTES[@]} -gt 0 ]; then
REPORT="MIGRATION-REPORT.md"
{
echo "# 迁移报告 — $(date +%Y-%m-%d)"
echo ""
echo "> 由 /spec-rocket migrate v$VERSION 生成。重构原则:**转移 → 收敛 → 保留 → 删除**。"
echo "> 请由 AI 按 SKILL.md 流程处理:②理解项目 → ③保持原有设计 → ④重构 → ⑤验收,完成后目录结构完全符合最新规范,零残留。"
echo ""
echo "## 迁移映射表(旧 → 新)"
echo ""
echo "| 旧结构 | 新位置 | 策略 |"
echo "|:--|:--|:--|"
echo "| \`docs/sprints/sprint-NNN_xxx/\` 文档 | \`sprints/sp-NNN-xxx/docs/\` | 转移(脚本已执行,.legacy 待删) |"
echo "| \`{apps|businesses|tools}/*/specs/SPEC-{APP|BIZ|TOOL}-NNN_xxx/\` | \`sprints/sp-NNN-xxx/specs/spec-NNN-xxx/\` | AI 按「基于冲刺」字段归入,重新编号 |"
echo "| \`ADR/ADR-NNN_xxx.md\` | \`adrs/adr-NNN-xxx.md\` | 转移(脚本已执行,原件待删) |"
echo "| \`ux-flows.md\` | 旅程 → \`user-scenarios.md\`;流程 → \`business-flows.md\` | AI 转移 |"
echo "| \`ui-wireframes.md\` | 布局/交互 → \`prototypes/\`;系统图表 → \`uml-pack.md\` | AI 转移 |"
echo "| \`user-journey-flows.md\` | 旅程总览 → \`user-scenarios.md\`;流程图 → \`business-flows.md\` | AI 转移 |"
echo ""
echo "## 待 AI 处理项"
echo ""
if [ ${#LEGACY_NOTES[@]} -gt 0 ]; then
echo "### 已转移待确认(内容已复制,确认后删除原件)"
echo ""
for n in "${LEGACY_NOTES[@]}"; do
echo "- $n"
done
echo ""
fi
if [ ${#MODULE_SPECS[@]} -gt 0 ]; then
echo "### 模块内 specs/(需迁移到迭代容器)"
echo ""
for sp in "${MODULE_SPECS[@]}"; do
echo "- $sp/"
done
echo ""
fi
if [ "$VD_SCAN" = "1" ]; then
echo "### visual-design.md 疑似业务设计(AI 判断后转移到 prototypes.md)"
echo ""
echo "$VD_HITS" | sed 's/^/- /'
echo ""
fi
if [ "${#STRAY_DOCS[@]}" -gt 0 ] || [ "${#ROOT_STRAY_R1[@]}" -gt 0 ]; then
echo "### 游离内容文档(规则1,AI 收敛到 docs/ 对应文档)"
echo ""
for f in "${STRAY_DOCS[@]}" "${ROOT_STRAY_R1[@]}"; do
echo "- $f"
done
echo ""
fi
if [ "${#ROOT_STRAY_R2[@]}" -gt 0 ]; then
echo "### 根目录非规范项(规则2,AI 归位 assets/)"
echo ""
for f in "${ROOT_STRAY_R2[@]}"; do
echo "- $f"
done
echo ""
fi
if [ "${#ASSETS_STRAY[@]}" -gt 0 ]; then
echo "### assets/ 待归位(规则2)"
echo ""
for f in "${ASSETS_STRAY[@]}"; do
echo "- $f"
done
echo ""
fi
} > "$REPORT"
ok "已生成迁移报告: $REPORT(AI 处理完成后删除本文件)"
fi
echo ""
info "migrate v$VERSION 介入完成(结构层)。"
if [ "$REPORT_NEEDED" = "1" ]; then
echo " 1. AI 读取 MIGRATION-REPORT.md 并先理解项目(②理解)"
echo " 2. 按映射表保持原有设计并转移(③保持)"
echo " 3. 处理完成后确认目录结构完全符合最新规范,删除报告与 *.legacy"
else
echo " 项目结构已是最新。下一步:运行 /spec-rocket brainstorm 引导写产品文档(或按需完善 assets/)"
fi
}
# ============================================================
# main
# ============================================================
case "${1:-help}" in
init) shift; cmd_init "$@" ;;
update) shift; cmd_update "$@" ;;
migrate) shift; cmd_migrate "$@" ;;
help|-h|--help|"")
echo "SpecRocket CLI v$VERSION"
echo "Usage:"
echo " ./spec-rocket init [项目名] # 初始化新项目(无参=当前目录)"
echo " ./spec-rocket update # 一键更新本地 skill(自动检测 AI 工具)"
echo " ./spec-rocket migrate [路径] # 项目重构(介入 → 理解 → 保持 → 重构,零残留)"
;;
*)
err "未知命令: $1(可用: init / update / migrate / help)"
exit 1
;;
esac