diff --git a/README.md b/README.md index a299e71..3ddf365 100644 --- a/README.md +++ b/README.md @@ -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 ← 掛 hooks(install.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** | 行內標記 ``…`` | 檔案內某段不編入 | 協議(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(底線)都不可省。 diff --git a/docs/wishlist.md b/docs/wishlist.md index e8e8180..bb45875 100644 --- a/docs/wishlist.md +++ b/docs/wishlist.md @@ -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** | 行內標記 ``…`` | 檔案內某段不編入 | 協議(CC 遵守) | +| **L3** | `wiki-secret-scan.sh` hook(PreToolUse) | 機敏值真寫進 `.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 轉「組合器」把它們拉進來。 diff --git a/scripts/install.sh b/scripts/install.sh index 496f521..6c3e4bd 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -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 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" @@ -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 變動前必須有對應 SDD(docs/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 ""