Files
system-dev-template/scripts/install.sh
T
Leo 9dcbe38021 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>
2026-06-20 12:45:53 +08:00

246 lines
9.6 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# system-dev-template installer
# 已有專案接入腳本——只建立缺少的東西,已有的一律不動。
#
# 模組化安裝:
# --wiki 只裝 LLM Wiki(記憶系統 + 機敏防護)
# --sdd 只裝 SDD 系統(動 code 前必須有 design.md
# --all 兩個都裝(預設)
# 無參數 互動式詢問
#
# 為什麼留在同一個 repo 用參數選,而不是 fork:
# 使用者多半非專業,最怕「我要去哪個 repo」。一個入口 + 選單最友善。
# 等未來功能多到 3+ 個再演進成「模板組合器」。模組邊界先在這裡劃好。
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"
CREATED+=("$1/")
else
SKIPPED+=("$1/ (已存在)")
fi
}
download_if_missing() {
local dest="$1" src="$2"
if [ ! -f "$dest" ]; then
mkdir -p "$(dirname "$dest")"
curl -sSL "$src" -o "$dest"
CREATED+=("$dest")
else
SKIPPED+=("$dest (已存在,跳過)")
fi
}
# ── 共用結構(兩個模組都需要 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"
# ── 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 (已存在,請手動加入對應區塊)")
fi
# ── 輸出結果 ──────────────────────────────────────
echo ""
echo "✅ 建立了:"
for item in "${CREATED[@]}"; do echo " + $item"; done
if [ ${#SKIPPED[@]} -gt 0 ]; then
echo ""
echo "⚠️ 跳過(已存在):"
for item in "${SKIPPED[@]}"; do echo " - $item"; done
fi
echo ""
echo "─────────────────────────────────"
# CLAUDE.md 已存在 → 依模組提醒手動加區塊
if [ -f "CLAUDE.md" ]; then
if $WANT_WIKI && ! grep -q "wiki/status.md" CLAUDE.md; then
echo ""
echo "📌 CLAUDE.md 已存在但缺少 wiki 讀取順序,請手動加入:"
echo ""
echo ' ## Wiki 讀取順序'
echo ' | 檔案 | 時機 | 用途 |'
echo ' |------|------|------|'
echo ' | `.claude/wiki/status.md` | session 開始第一件事 | 當前進度 |'
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 已存在 → 依模組提醒要合併哪些 hook
if [ -f ".claude/settings.json" ] && grep -q '"已存在"' <<<"${SKIPPED[*]}" 2>/dev/null; then :; fi
if [ -f ".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 合併進去(保留既有設定):"
for h in "${MISSING_HOOKS[@]}"; do echo " • $h"; done
fi
fi
echo ""
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 ""