#!/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 "" # ── 偵測 vault 類型 → 決定 raw source(原始文件)路徑 ────────── # 為什麼:這個模板原本假設「原始文件在 docs/」,但 Logseq / Obsidian # 這種 PKM vault 有自己的目錄慣例,整理時不能照 docs/ 那套搬動, # 否則會破壞 vault 結構、讓筆記變不可讀。 # 偵測結果寫進 CLAUDE.md,讓 CC 和未來的 Cowork skill 都知道 # 「該讀/該整理哪裡」而不是亂動。 # 必須在建立 CLAUDE.md 之前跑完。 VAULT_TYPE="" RAW_SOURCE="" if [ -d "logseq" ]; then VAULT_TYPE="logseq" RAW_SOURCE="pages/, journals/" elif [ -d ".obsidian" ]; then VAULT_TYPE="obsidian" RAW_SOURCE="./ (整個 vault 根目錄的 .md)" else VAULT_TYPE="docs" RAW_SOURCE="docs/" fi echo "🗂️ 偵測到 vault 類型:$VAULT_TYPE → raw source:$RAW_SOURCE" echo "" # 把「raw source 宣告區塊」吐出來,給新建的 CLAUDE.md append 或 # 給已存在的 CLAUDE.md 當手動補貼的提示。內容對 CC / Cowork 都是 # 機器可讀的指令(明確路徑 + 不可破壞 vault 結構的約束)。 emit_raw_source_block() { cat < 安裝時偵測到的 vault 類型:**$VAULT_TYPE** > CC 與 Cowork 整理/讀取「人寫的原始文件」時,**只在這裡找、只在這裡動**。 | 項目 | 值 | |------|----| | vault 類型 | \`$VAULT_TYPE\` | | raw source | \`$RAW_SOURCE\` | **約束(CC 與 Cowork 都必須遵守)** - 整理 wiki/知識時,原始文件**一律從上方 raw source 路徑讀取**,不要假設是 \`docs/\`。 BLOCK if [ "$VAULT_TYPE" != "docs" ]; then cat </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:只在完全不存在時建立 ──────────────── # 新建時把偵測到的 raw source 宣告 append 進去(在建立的當下寫入, # 不回頭改使用者既有的 CLAUDE.md,維持「已有不覆蓋」原則)。 if [ ! -f "CLAUDE.md" ]; then download_if_missing "CLAUDE.md" "$REPO_URL/CLAUDE.md" if [ -f "CLAUDE.md" ]; then emit_raw_source_block >> CLAUDE.md CREATED+=("CLAUDE.md ← 已寫入 raw source 宣告($VAULT_TYPE)") fi 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 ! grep -q "raw source" CLAUDE.md; then echo "" echo "📌 CLAUDE.md 已存在但缺少 raw source 宣告(偵測到 vault 類型:$VAULT_TYPE)。" echo " 請手動把以下區塊貼進去,讓 CC 與 Cowork 知道原始文件在哪、不要亂動 vault:" emit_raw_source_block | sed 's/^/ /' fi 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 ""