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
+33 -3
View File
@@ -42,6 +42,17 @@ curl -sSL https://raw.githubusercontent.com/uncle6me-web/system-dev-template/mai
腳本只建立缺少的東西,**已有的檔案一律不動**。
**只要一塊?** 兩套系統可分開裝(有時只需要 wiki,不需要 SDD):
```bash
# 下載後帶參數執行
curl -sSL .../install.sh -o install.sh && bash install.sh --wiki # 只裝 LLM Wiki
bash install.sh --sdd # 只裝 SDD
bash install.sh --all # 兩個都裝(預設)
```
在終端機直接跑(有 tty)時,不帶參數會**互動詢問**要裝哪一塊——對非工程背景的使用者最友善。`curl | bash` 無 tty 則安全預設為 `--all`。settings.json 的 hooks 會依選的模組自動組裝。
安裝完後在 CC 對話裡:
```
/wiki-init
@@ -62,10 +73,10 @@ system-dev-template/
│ ├── CLAUDE.md ← 填空版導航牌
│ ├── docs/ ← 文件結構(六層分類)
│ └── .claude/
│ ├── wiki/ ← CC 的記憶空間
│ ├── wiki/ ← CC 的記憶空間(含 .wikiignore 機敏排除)
│ ├── commands/ ← Slash commands
│ ├── hooks/ ← 硬攔截(SessionStart 接關 + SDD 協議)
│ └── settings.json ← 掛 hooks
│ ├── hooks/ ← 硬攔截(接關 + SDD 協議 + wiki 機敏掃描
│ └── settings.json ← 掛 hooksinstall.sh 依模組組裝)
├── skills/
│ └── llm-wiki/ ← 複製到 Legacy-Workspace/.claude/skills/
@@ -105,8 +116,27 @@ system-dev-template/
|------|------|
| `session-start-recall.sh` | 開 session 自動注入 status 重點,不靠 CC 自覺 |
| `sdd-guard.sh` | 動 code 檔但沒有任何 SDD → 攔(exit 2),對應 `/sdd-check` 的自動版 |
| `wiki-secret-scan.sh` | 機敏值要寫進 `.claude/wiki/` → 攔(exit 2),密碼/金鑰/個資的機械底線 |
| `pre-write-guard.sh` | 專案自訂禁令範本骨架(預設停用,填 pattern 才生效)|
---
## Wiki 機敏防護(不想被編入的內容)
`.gitignore` 一樣,讓不想進 wiki 的內容(密碼、金鑰、個資)不被編入。三層:
| 層 | 機制 | 擋什麼 | 性質 |
|----|------|--------|------|
| **L1** | `.claude/wiki/.wikiignore`(glob) | 整個機敏檔不編入 | 協議(CC 遵守)|
| **L2** | 行內標記 `<!-- wiki:ignore -->``<!-- wiki:end -->` | 檔案內某段不編入 | 協議(CC 遵守)|
| **L3** | `wiki-secret-scan.sh` hook | 機敏值真的寫進 wiki → exit 2 擋 | **硬攔截(機械偵測)**|
L1/L2 是你主動標記,L3 是自動兜底——萬一 CC 漏掉前兩層,寫進 wiki 的那一刻會被 regex 攔下(密碼賦值、PEM 私鑰、AWS/GitHub/Slack 金鑰、JWT、連線字串帳密、身分證、信用卡號)。
> 誠實限制:L1/L2 靠 CC 自律,L3 靠特徵比對(有偽陽/偽陰)。
> 這是「減少**意外**外洩」的機制,不是保險箱——真正的密鑰本就不該進版控。
> 誤判時:該行尾加 `wiki-secret-ok` 放行;整檔機敏則加進 `.wikiignore`。
> 誠實限制:hook 擋語法層明顯違規(直接寫檔),擋不了藏在 helper / bash 裡的繞道。
> 價值是「想跳過會被抓到 + 留痕可審」,不是技術防偽——文檔(mindset)+ hook(底線)都不可省。
+41 -2
View File
@@ -4,7 +4,7 @@ system-dev-template 自身要補的功能。
---
## 1. 接關機制(開新對話自動恢復進度)
## 1. 接關機制(開新對話自動恢復進度) ✅ 已完成(commit 39783cc
**問題**template 現在「接關」只靠 `CLAUDE.md` 的軟提醒「Wiki 讀取順序:status.md = session 開始第一件事」,期待 CC 開新對話自己去讀。但軟提醒擋不住「CC 讀了 CLAUDE.md 卻沒真讀 status」,使用者也無法確定它讀了沒。
@@ -29,7 +29,7 @@ system-dev-template 自身要補的功能。
---
## 2. Hook 強制機制(軟規範 → 硬攔截)
## 2. Hook 強制機制(軟規範 → 硬攔截) ✅ 已完成(commit 39783cc
**問題**template 現在**完全沒有 hook**,所有規範(SDD 協議、wiki 維護)都是 CLAUDE.md 的軟提醒。CC 想跳過就跳過,沒有東西抓得到。
@@ -46,3 +46,42 @@ system-dev-template 自身要補的功能。
**設計原則(抄 arcrun 的誠實限制)**:hook 擋語法層明顯違規,擋不了藏在 helper 裡的繞道 → **文檔(mindset+ hook(底線)都不可省**,絕不在文件聲稱「不可能繞過」。hook 的價值是「想跳過會被抓到」+ 留痕可審,不是技術防偽。
**為何重要**template 的兩大賣點(SDD 強制先設計 + wiki 持久記憶)現在都只是「請你照做」。加 hook 才從「建議」變「機制」——這正是 template 區別於「就是寫幾個 markdown」的關鍵。
---
## 3. Wiki 機敏內容防護(不想被編入的內容) ✅ 已完成
**問題**:wiki 是會被 CC 反覆讀取、可能進版控的記憶空間。有些內容不該被編入——密碼、API 金鑰、私鑰、個資。只靠口頭約束太危險:密碼/個資外洩是**不可逆**後果,光提醒 CC「別記機敏資訊」擋不住意外。
**設計:三層防護(協議 + 機械底線)**
| 層 | 機制 | 擋什麼 | 性質 |
|---|---|---|---|
| **L1** | `.claude/wiki/.wikiignore`glob,像 .gitignore | 整個機敏檔不編入 | 協議(CC 遵守) |
| **L2** | 行內標記 `<!-- wiki:ignore -->``<!-- wiki:end -->` | 檔案內某段不編入 | 協議(CC 遵守) |
| **L3** | `wiki-secret-scan.sh` hookPreToolUse | 機敏值真寫進 `.claude/wiki/` → exit 2 擋 | **硬攔截(機械偵測)** |
L1/L2 是使用者主動標記,L3 是自動兜底——前兩層漏掉時,寫進 wiki 那一刻 regex 攔下(密碼賦值、PEM 私鑰、AWS/GitHub/Slack/Google/Stripe 金鑰、JWT、連線字串帳密、台灣身分證、信用卡號)。
**已做**
- `template/.claude/wiki/.wikiignore`L1 範本(預設排除 `.env`/`*.pem`/`*secret*` 等)。
- `template/.claude/hooks/wiki-secret-scan.sh`L3 hook,只掃 `.claude/wiki/**` 寫入,`wiki-secret-ok` 行尾標記可豁免誤判。
- `settings.json`PreToolUse(Write|Edit) 加掛。
- `wiki-init.md` / `wiki-capture.md` / `SKILL.md`:寫入 L1+L2 協議。
**設計原則(抄 §2 的誠實限制)**L1/L2 靠 CC 自律、L3 靠 regex 特徵(有偽陽/偽陰)。這是「減少**意外**外洩」的機制,不是保險箱——真正的密鑰本就不該進版控。絕不聲稱「不可能繞過」。
---
## 4. 模組化安裝(Wiki / SDD 可分開裝) ✅ 已完成
**問題**:有時只需要 wiki,不需要 SDD(反之亦然)。原 install.sh 一律全裝。
**決策(為何不 fork**:使用者多半非工程背景,最怕「我要去哪個 repo」。一個入口 + 模組選單最友善。fork 成獨立 repo 維護成本翻倍、現在只有 2 個功能略嫌早。等未來功能多到 3+ 個再演進成「模板組合器」(meta-template 把子模板拉進來)——模組邊界先在 install.sh 劃好,當作拆分前置。
**已做**
- `install.sh` 支援 `--wiki` / `--sdd` / `--all`(預設),無參數 + 有 tty 則互動詢問 1/2/3`curl|bash` 無 tty 安全預設 `--all`
- settings.json 的 hooks 依選的模組**自動組裝**(不再下載單一靜態檔)。
- README 補上模組安裝與 `.wikiignore` 三層防護說明。
**未來演進**:功能達 3+ 個 → 拆成獨立模板 repo,本 repo 轉「組合器」把它們拉進來。
+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 ""