feat: install.sh 模組化安裝(--wiki / --sdd / --all)

有時只需要 wiki 不需要 SDD。不 fork,改用同一入口模組選單:
- --wiki / --sdd / --all(預設),無參數 + 有 tty 則互動詢問
- curl|bash 無 tty 安全預設 --all
- settings.json 的 hooks 依選的模組自動組裝(不再下載單一靜態檔)

不 fork 的理由:使用者多半非工程背景,一個入口最友善;維護成本不翻倍。
模組邊界先劃好,未來功能達 3+ 個再演進成「模板組合器」。

同步 wishlist:§1 接關 / §2 hook 標記已完成(commit 39783cc),
新增 §3 機敏防護 / §4 模組化安裝並標完成。README 補三層防護與模組安裝說明。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-20 12:45:53 +08:00
parent 29e3636bd2
commit 9dcbe38021
3 changed files with 248 additions and 77 deletions
+174 -72
View File
@@ -1,21 +1,82 @@
#!/bin/bash
# system-dev-template installer
# 已有專案接入腳本——只建立缺少的東西,已有的一律不動
# 已有專案接入腳本——只建立缺少的東西,已有的一律不動
#
# 模組化安裝:
# --wiki 只裝 LLM Wiki(記憶系統 + 機敏防護)
# --sdd 只裝 SDD 系統(動 code 前必須有 design.md
# --all 兩個都裝(預設)
# 無參數 互動式詢問
#
# 為什麼留在同一個 repo 用參數選,而不是 fork:
# 使用者多半非專業,最怕「我要去哪個 repo」。一個入口 + 選單最友善。
# 等未來功能多到 3+ 個再演進成「模板組合器」。模組邊界先在這裡劃好。
set -e
set -euo pipefail
REPO_URL="https://raw.githubusercontent.com/uncle6me-web/system-dev-template/main/template"
CREATED=()
SKIPPED=()
# ── 解析模組參數 ──────────────────────────────────
MODULE=""
for arg in "$@"; do
case "$arg" in
--wiki|--wiki-only) MODULE="wiki" ;;
--sdd|--sdd-only) MODULE="sdd" ;;
--all) MODULE="all" ;;
-h|--help)
cat <<'HELP'
用法:install.sh [--wiki | --sdd | --all]
--wiki 只裝 LLM WikiCC 記憶系統 + 機敏防護)
--sdd 只裝 SDD 系統(動 code 前強制要有設計文件)
--all 兩個都裝(預設)
無參數 互動式詢問要裝哪個
HELP
exit 0 ;;
esac
done
echo ""
echo "🔧 system-dev-template installer"
echo "================================="
echo "只建立缺少的目錄和檔案,已有的不動。"
echo ""
# ── 目錄 ──────────────────────────────────────────
# ── 無參數 → 互動式詢問(給非專業使用者)──────────
if [ -z "$MODULE" ]; then
if [ -t 0 ]; then
echo "要安裝哪一塊?"
echo " 1) LLM Wiki —— 讓 CC 記住決策、不重複犯錯(含機敏防護)"
echo " 2) SDD —— 動 code 前強制先有設計文件"
echo " 3) 兩個都裝(推薦)"
echo ""
printf "請輸入 1 / 2 / 3 [預設 3]"
read -r choice || choice=3
case "$choice" in
1) MODULE="wiki" ;;
2) MODULE="sdd" ;;
*) MODULE="all" ;;
esac
else
# 非互動環境(如 curl | bash 無 tty)→ 預設全裝
MODULE="all"
fi
fi
WANT_WIKI=false
WANT_SDD=false
case "$MODULE" in
wiki) WANT_WIKI=true ;;
sdd) WANT_SDD=true ;;
all) WANT_WIKI=true; WANT_SDD=true ;;
esac
echo ""
echo "📦 安裝模組:$MODULE"
echo ""
# ── 工具函式 ──────────────────────────────────────
create_dir() {
if [ ! -d "$1" ]; then
mkdir -p "$1"
@@ -25,23 +86,10 @@ create_dir() {
fi
}
create_dir "docs/1-vision"
create_dir "docs/2-architecture/decisions"
create_dir "docs/3-specs"
create_dir "docs/4-guides"
create_dir "docs/5-records/incidents"
create_dir "docs/5-records/test-reports"
create_dir "docs/6-user"
create_dir ".claude/wiki"
create_dir ".claude/commands"
create_dir ".claude/hooks"
# ── 檔案(從 repo 下載,只在不存在時)──────────────
download_if_missing() {
local dest="$1"
local src="$2"
local dest="$1" src="$2"
if [ ! -f "$dest" ]; then
mkdir -p "$(dirname "$dest")"
curl -sSL "$src" -o "$dest"
CREATED+=("$dest")
else
@@ -49,67 +97,110 @@ download_if_missing() {
fi
}
# wiki 核心檔案
download_if_missing ".claude/wiki/INDEX.md" "$REPO_URL/.claude/wiki/INDEX.md"
download_if_missing ".claude/wiki/status.md" "$REPO_URL/.claude/wiki/status.md"
download_if_missing ".claude/wiki/mistakes.md" "$REPO_URL/.claude/wiki/mistakes.md"
download_if_missing ".claude/wiki/decisions-summary.md" "$REPO_URL/.claude/wiki/decisions-summary.md"
# slash commands
download_if_missing ".claude/commands/wiki-init.md" "$REPO_URL/.claude/commands/wiki-init.md"
download_if_missing ".claude/commands/wiki-capture.md" "$REPO_URL/.claude/commands/wiki-capture.md"
download_if_missing ".claude/commands/wiki-update.md" "$REPO_URL/.claude/commands/wiki-update.md"
download_if_missing ".claude/commands/wiki-recall.md" "$REPO_URL/.claude/commands/wiki-recall.md"
download_if_missing ".claude/commands/sdd-check.md" "$REPO_URL/.claude/commands/sdd-check.md"
# hooks(軟規範 → 硬攔截。下載後補執行權限)
download_if_missing ".claude/hooks/session-start-recall.sh" "$REPO_URL/.claude/hooks/session-start-recall.sh"
download_if_missing ".claude/hooks/sdd-guard.sh" "$REPO_URL/.claude/hooks/sdd-guard.sh"
download_if_missing ".claude/hooks/pre-write-guard.sh" "$REPO_URL/.claude/hooks/pre-write-guard.sh"
chmod +x .claude/hooks/*.sh 2>/dev/null || true
# docs/README.md(分類地圖)
# ── 共用結構(兩個模組都需要 docs 分類 + .claude)──
create_dir "docs/1-vision"
create_dir "docs/2-architecture/decisions"
create_dir "docs/4-guides"
create_dir "docs/5-records/incidents"
create_dir "docs/5-records/test-reports"
create_dir "docs/6-user"
create_dir ".claude/commands"
create_dir ".claude/hooks"
download_if_missing "docs/README.md" "$REPO_URL/docs/README.md"
# CLAUDE.md:只在完全不存在時建立
# ── WIKI 模組 ─────────────────────────────────────
if $WANT_WIKI; then
create_dir ".claude/wiki"
download_if_missing ".claude/wiki/INDEX.md" "$REPO_URL/.claude/wiki/INDEX.md"
download_if_missing ".claude/wiki/status.md" "$REPO_URL/.claude/wiki/status.md"
download_if_missing ".claude/wiki/mistakes.md" "$REPO_URL/.claude/wiki/mistakes.md"
download_if_missing ".claude/wiki/decisions-summary.md" "$REPO_URL/.claude/wiki/decisions-summary.md"
download_if_missing ".claude/wiki/.wikiignore" "$REPO_URL/.claude/wiki/.wikiignore"
download_if_missing ".claude/commands/wiki-init.md" "$REPO_URL/.claude/commands/wiki-init.md"
download_if_missing ".claude/commands/wiki-capture.md" "$REPO_URL/.claude/commands/wiki-capture.md"
download_if_missing ".claude/commands/wiki-update.md" "$REPO_URL/.claude/commands/wiki-update.md"
download_if_missing ".claude/commands/wiki-recall.md" "$REPO_URL/.claude/commands/wiki-recall.md"
# wiki 相關 hooks:接關 + 機敏掃描
download_if_missing ".claude/hooks/session-start-recall.sh" "$REPO_URL/.claude/hooks/session-start-recall.sh"
download_if_missing ".claude/hooks/wiki-secret-scan.sh" "$REPO_URL/.claude/hooks/wiki-secret-scan.sh"
fi
# ── SDD 模組 ──────────────────────────────────────
if $WANT_SDD; then
create_dir "docs/3-specs"
download_if_missing "docs/3-specs/TEMPLATE-sdd/design.md" "$REPO_URL/docs/3-specs/TEMPLATE-sdd/design.md"
download_if_missing "docs/3-specs/TEMPLATE-sdd/tasks.md" "$REPO_URL/docs/3-specs/TEMPLATE-sdd/tasks.md"
download_if_missing "docs/2-architecture/decisions/TEMPLATE-adr.md" "$REPO_URL/docs/2-architecture/decisions/TEMPLATE-adr.md"
download_if_missing ".claude/commands/sdd-check.md" "$REPO_URL/.claude/commands/sdd-check.md"
download_if_missing ".claude/hooks/sdd-guard.sh" "$REPO_URL/.claude/hooks/sdd-guard.sh"
fi
# ── 共用 hook:專案自訂禁令骨架(預設停用)────────
download_if_missing ".claude/hooks/pre-write-guard.sh" "$REPO_URL/.claude/hooks/pre-write-guard.sh"
chmod +x .claude/hooks/*.sh 2>/dev/null || true
# ── 依模組產生 settings.json 的 hooks 區塊 ────────
# settings.json 因模組而異,不能直接下載單一靜態檔,改條件組裝。
build_hooks_json() {
local session_hooks="" pretool_hooks=""
if $WANT_WIKI; then
session_hooks='{ "type": "command", "command": ".claude/hooks/session-start-recall.sh" }'
fi
# PreToolUse 依模組疊加
local pt=()
$WANT_SDD && pt+=('{ "type": "command", "command": ".claude/hooks/sdd-guard.sh" }')
pt+=('{ "type": "command", "command": ".claude/hooks/pre-write-guard.sh" }')
$WANT_WIKI && pt+=('{ "type": "command", "command": ".claude/hooks/wiki-secret-scan.sh" }')
local IFS=,
pretool_hooks="${pt[*]}"
printf '{\n "hooks": {\n'
if [ -n "$session_hooks" ]; then
printf ' "SessionStart": [\n { "matcher": "startup|resume|clear",\n "hooks": [ %s ] }\n ],\n' "$session_hooks"
fi
printf ' "PreToolUse": [\n { "matcher": "Write|Edit",\n "hooks": [ %s ] }\n ]\n' "$pretool_hooks"
printf ' }\n}\n'
}
if [ ! -f ".claude/settings.json" ]; then
build_hooks_json > .claude/settings.json
CREATED+=(".claude/settings.json (依 $MODULE 模組產生)")
else
SKIPPED+=(".claude/settings.json (已存在,請手動合併 hooks)")
fi
# ── CLAUDE.md:只在完全不存在時建立 ────────────────
if [ ! -f "CLAUDE.md" ]; then
download_if_missing "CLAUDE.md" "$REPO_URL/CLAUDE.md"
else
SKIPPED+=("CLAUDE.md (已存在,請手動加入 wiki 讀取順序區塊)")
fi
# .claude/settings.json:只在完全不存在時建立(比照 CLAUDE.md,不覆蓋既有設定)
if [ ! -f ".claude/settings.json" ]; then
download_if_missing ".claude/settings.json" "$REPO_URL/.claude/settings.json"
else
SKIPPED+=(".claude/settings.json (已存在,請手動加入 hooks 區塊)")
SKIPPED+=("CLAUDE.md (已存在,請手動加入對應區塊)")
fi
# ── 輸出結果 ──────────────────────────────────────
echo ""
echo "✅ 建立了:"
for item in "${CREATED[@]}"; do
echo " + $item"
done
for item in "${CREATED[@]}"; do echo " + $item"; done
if [ ${#SKIPPED[@]} -gt 0 ]; then
echo ""
echo "⚠️ 跳過(已存在):"
for item in "${SKIPPED[@]}"; do
echo " - $item"
done
for item in "${SKIPPED[@]}"; do echo " - $item"; done
fi
echo ""
echo "─────────────────────────────────"
# 如果 CLAUDE.md 已存在,提醒手動加入 wiki 區塊
# CLAUDE.md 已存在 → 依模組提醒手動加區塊
if [ -f "CLAUDE.md" ]; then
if ! grep -q "wiki/status.md" CLAUDE.md; then
if $WANT_WIKI && ! grep -q "wiki/status.md" CLAUDE.md; then
echo ""
echo "📌 CLAUDE.md 已存在但缺少 wiki 讀取順序"
echo " 請手動加入以下區塊:"
echo "📌 CLAUDE.md 已存在但缺少 wiki 讀取順序,請手動加入:"
echo ""
echo ' ## Wiki 讀取順序'
echo ' | 檔案 | 時機 | 用途 |'
@@ -118,26 +209,37 @@ if [ -f "CLAUDE.md" ]; then
echo ' | `.claude/wiki/mistakes.md` | 做新功能前 | 已知誤解 |'
echo ' | `.claude/wiki/decisions-summary.md` | 設計判斷時 | 架構決策 |'
fi
if $WANT_SDD && ! grep -q "docs/3-specs" CLAUDE.md; then
echo ""
echo "📌 CLAUDE.md 已存在但缺少 SDD 鐵律,請手動加入:"
echo ""
echo ' ## 絕對鐵律'
echo ' 1. 任何 code 變動前必須有對應 SDDdocs/3-specs/[子系統]/design.md'
echo ' 找不到 → 停手問負責人,不要自行建立。'
fi
fi
# 如果 settings.json 已存在,提醒手動加入 hooks 區塊
# settings.json 已存在 → 依模組提醒要合併哪些 hook
if [ -f ".claude/settings.json" ] && grep -q '"已存在"' <<<"${SKIPPED[*]}" 2>/dev/null; then :; fi
if [ -f ".claude/settings.json" ]; then
if ! grep -q "session-start-recall.sh" .claude/settings.json; then
MISSING_HOOKS=()
$WANT_WIKI && ! grep -q "session-start-recall.sh" .claude/settings.json && MISSING_HOOKS+=("SessionStart: session-start-recall.sh")
$WANT_WIKI && ! grep -q "wiki-secret-scan.sh" .claude/settings.json && MISSING_HOOKS+=("PreToolUse(Write|Edit): wiki-secret-scan.sh")
$WANT_SDD && ! grep -q "sdd-guard.sh" .claude/settings.json && MISSING_HOOKS+=("PreToolUse(Write|Edit): sdd-guard.sh")
if [ ${#MISSING_HOOKS[@]} -gt 0 ]; then
echo ""
echo "📌 .claude/settings.json 已存在但缺少 hooks。"
echo " 請手動把以下 hooks 合併進去(已有設定請保留):"
echo ""
echo ' "SessionStart": [{ "matcher": "startup|resume|clear",'
echo ' "hooks": [{ "type": "command", "command": ".claude/hooks/session-start-recall.sh" }] }],'
echo ' "PreToolUse": [{ "matcher": "Write|Edit",'
echo ' "hooks": [{ "type": "command", "command": ".claude/hooks/sdd-guard.sh" },'
echo ' { "type": "command", "command": ".claude/hooks/pre-write-guard.sh" }] }]'
echo "📌 .claude/settings.json 已存在,請手動把以下 hooks 合併進去(保留既有設定):"
for h in "${MISSING_HOOKS[@]}"; do echo " $h"; done
fi
fi
echo ""
echo "🚀 下一步:在 Claude Code 對話裡執行:"
echo " /wiki-init"
echo ""
echo " CC 會掃描現有文件、建立 wiki、整理 docs 結構。"
echo "🚀 下一步:"
if $WANT_WIKI; then
echo " 在 Claude Code 對話裡執行 /wiki-init"
echo " CC 會掃描現有文件、套用 .wikiignore、建立 wiki。"
fi
if $WANT_SDD; then
echo " 動 code 前先在 docs/3-specs/[子系統]/ 建 design.md(可用 /sdd-check 協助)"
fi
echo ""