25cb4d1f63
issue #1:新增 /issue-handle slash command,CC 處理 GitHub issue 的 普世指引——讀回自己 repo 直接做、跨 repo 發要先問人、禁掛 Actions/cron 自動輪詢(會觸發 GitHub 異常偵測)。屬共用指引,install/update 不分模組裝。 issue #2:pre-write-guard.sh 釐清定位為「按需手填的空插槽」,非裝上就 生效的警察。檔頭明講有 CC 在場時直接叫 CC 寫貼合的 guard 更好;install/ update 安裝時提示「預設不攔,要手填才生效」,消除安全錯覺。hook 執行維持 安靜不洗版。 另:repo 自己裝了一套開發用 wiki(內部記錄),與 template/ 成品範本實體 隔離,.gitignore 排除 .claude/wiki/、.claude/hooks/、.claude/VERSION, 不推 GitHub。 升版 1.2.0,CHANGELOG 記錄。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
256 lines
10 KiB
Bash
256 lines
10 KiB
Bash
#!/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 Wiki(CC 記憶系統 + 機敏防護)
|
||
--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"
|
||
|
||
# ── 共用指引:GitHub issue 處理(讀/回普世,跨 repo 發要先問,禁自動輪詢)──
|
||
download_if_missing ".claude/commands/issue-handle.md" "$REPO_URL/.claude/commands/issue-handle.md"
|
||
|
||
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 變動前必須有對應 SDD(docs/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
|
||
|
||
# pre-write-guard 是空殼,提醒它預設不攔(避免「以為有保護其實沒有」的安全錯覺)
|
||
echo ""
|
||
echo "ℹ️ .claude/hooks/pre-write-guard.sh 是「按需手填的空插槽」,預設不攔任何東西。"
|
||
echo " 需要專案禁令?最簡單是叫你的 CC 寫一支貼合的 guard hook(比範本表達力強);"
|
||
echo " 或自己填 FORBIDDEN_PATTERNS 並到 settings.json 掛上才會生效。"
|
||
|
||
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 " GitHub issue:CC 可直接 /issue-handle 讀回自己 repo 的 issue(禁自動輪詢)"
|
||
echo ""
|