feat: 接關 hook + SDD 強制 hook(實作 wishlist 兩項)
§1 接關機制(雙保險): - session-start-recall.sh:SessionStart 自動注入 status 重點 + 快照核實提醒 - /wiki-recall:fallback 命令,hook 失效時手動接關 §2 軟規範 → 硬攔截: - sdd-guard.sh:動 code 檔但無 SDD → exit 2 擋(/sdd-check 自動版) - pre-write-guard.sh:專案自訂禁令骨架(預設停用) - settings.json:掛 SessionStart + PreToolUse 配套:install.sh 下載 hooks/settings(settings 比照 CLAUDE.md 不覆蓋); README/CLAUDE.md 補文件 + 誠實限制聲明。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -63,7 +63,9 @@ system-dev-template/
|
||||
│ ├── docs/ ← 文件結構(六層分類)
|
||||
│ └── .claude/
|
||||
│ ├── wiki/ ← CC 的記憶空間
|
||||
│ └── commands/ ← Slash commands
|
||||
│ ├── commands/ ← Slash commands
|
||||
│ ├── hooks/ ← 硬攔截(SessionStart 接關 + SDD 協議)
|
||||
│ └── settings.json ← 掛 hooks
|
||||
│
|
||||
├── skills/
|
||||
│ └── llm-wiki/ ← 複製到 Legacy-Workspace/.claude/skills/
|
||||
@@ -86,10 +88,28 @@ system-dev-template/
|
||||
| Command | 做什麼 |
|
||||
|---------|-------|
|
||||
| `/wiki-init` | 初始化 wiki(新專案或接入已有專案)|
|
||||
| `/wiki-recall` | Session 開始,手動接關(hook 沒啟動時的 fallback)|
|
||||
| `/wiki-capture` | 把這次對話的結論存進 wiki |
|
||||
| `/wiki-update` | Session 結束,更新 status.md |
|
||||
| `/sdd-check` | 確認當前任務有沒有對應 SDD |
|
||||
|
||||
命名閉環:init(建) → update(存,session 末) ↔ recall(接,session 初) → capture(隨時存結論)。
|
||||
|
||||
---
|
||||
|
||||
## Hooks(軟規範 → 硬攔截)
|
||||
|
||||
規範不再只是 CLAUDE.md 的軟提醒,加了底線機制:
|
||||
|
||||
| Hook | 角色 |
|
||||
|------|------|
|
||||
| `session-start-recall.sh` | 開 session 自動注入 status 重點,不靠 CC 自覺 |
|
||||
| `sdd-guard.sh` | 動 code 檔但沒有任何 SDD → 攔(exit 2),對應 `/sdd-check` 的自動版 |
|
||||
| `pre-write-guard.sh` | 專案自訂禁令範本骨架(預設停用,填 pattern 才生效)|
|
||||
|
||||
> 誠實限制:hook 擋語法層明顯違規(直接寫檔),擋不了藏在 helper / bash 裡的繞道。
|
||||
> 價值是「想跳過會被抓到 + 留痕可審」,不是技術防偽——文檔(mindset)+ hook(底線)都不可省。
|
||||
|
||||
---
|
||||
|
||||
## 設計原則
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Wishlist
|
||||
|
||||
system-dev-template 自身要補的功能。
|
||||
|
||||
---
|
||||
|
||||
## 1. 接關機制(開新對話自動恢復進度)
|
||||
|
||||
**問題**:template 現在「接關」只靠 `CLAUDE.md` 的軟提醒「Wiki 讀取順序:status.md = session 開始第一件事」,期待 CC 開新對話自己去讀。但軟提醒擋不住「CC 讀了 CLAUDE.md 卻沒真讀 status」,使用者也無法確定它讀了沒。
|
||||
|
||||
**設計:雙保險(hook 自動 + 命令兜底)**
|
||||
|
||||
| 機制 | 角色 | 何時 |
|
||||
|---|---|---|
|
||||
| **SessionStart hook** | 主路徑:開 session 自動注入 status 重點,不靠 CC 自覺、不用人說 | 每次 startup/resume/clear 自動 |
|
||||
| **`/wiki-recall` 命令** | Fallback:hook 沒啟動時手動接關;要完整脈絡時也用 | 使用者見沒自動接關 → 打一句 |
|
||||
|
||||
主路徑不靠人;命令應對 hook 失效。沒有 fallback,hook 一失效就靜默回到「全靠 CC 自覺」(正是要解決的問題)。
|
||||
|
||||
**要做**:
|
||||
- `template/.claude/hooks/session-start-recall.sh`:注入 status.md 的「正在做 / 下次第一件事」。
|
||||
- `template/.claude/settings.json`:掛 `SessionStart`(matcher `startup|resume|clear`)。
|
||||
- `template/.claude/commands/wiki-recall.md`:手動接關命令(讀 status → decisions → wishlist → HANDOFF)。
|
||||
- `install.sh`:偵測已有 settings.json 則不覆蓋,提示手動加 SessionStart(比照 CLAUDE.md 處理)。
|
||||
|
||||
**命名閉環**:init(建) → update(存,session 末) ↔ recall(接,session 初) → capture(隨時存結論)。
|
||||
|
||||
**配套鐵律(寫進 wiki-recall + 文件)**:status/wiki 是 **point-in-time 快照非即時狀態**。接關=讀快照 **+ 核實快照**,不盲信。實例:某專案 status 曾寫「待 A 收尾 X」,實際 X 早完成,照舊資訊會催已完成的事。
|
||||
|
||||
---
|
||||
|
||||
## 2. Hook 強制機制(軟規範 → 硬攔截)
|
||||
|
||||
**問題**:template 現在**完全沒有 hook**,所有規範(SDD 協議、wiki 維護)都是 CLAUDE.md 的軟提醒。CC 想跳過就跳過,沒有東西抓得到。
|
||||
|
||||
**對照 arcrun(值得 template 借鑑)**:arcrun 有一套 hook 把鐵律變強制——
|
||||
- `pre-write-guard.sh` / `pre-bash-guard.sh`:違反禁令(沒讀 SDD 就動 code、寫違規檔)→ **exit 2 直接 block**,CC 跳不過。
|
||||
- `session-start-load-sdd.sh`:開 session 強制注入進度 + 禁令。
|
||||
- 效果:「想跳過 SDD 協議就會被抓到」,不靠自覺。
|
||||
|
||||
**要做(template 標配一個最小 hook 集)**:
|
||||
- **SDD 協議 hook**:動 code(.ts/.go/...)前若沒對應 SDD / 沒在回覆宣告已讀 → 擋或警告。對應 template 既有的 `/sdd-check`,但從「命令要人打」升級成「hook 自動攔」。
|
||||
- **SessionStart hook**:見上 §1(接關 + 注入規範)。
|
||||
- **可選 pre-write guard**:讓使用者自訂專案禁令(如本 InkStoneCo 的「KBDB 禁動表」hook),template 給範本骨架。
|
||||
|
||||
**設計原則(抄 arcrun 的誠實限制)**:hook 擋語法層明顯違規,擋不了藏在 helper 裡的繞道 → **文檔(mindset)+ hook(底線)都不可省**,絕不在文件聲稱「不可能繞過」。hook 的價值是「想跳過會被抓到」+ 留痕可審,不是技術防偽。
|
||||
|
||||
**為何重要**:template 的兩大賣點(SDD 強制先設計 + wiki 持久記憶)現在都只是「請你照做」。加 hook 才從「建議」變「機制」——這正是 template 區別於「就是寫幾個 markdown」的關鍵。
|
||||
@@ -34,6 +34,7 @@ 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 下載,只在不存在時)──────────────
|
||||
|
||||
@@ -58,8 +59,15 @@ download_if_missing ".claude/wiki/decisions-summary.md" "$REPO_URL/.claude/wiki/
|
||||
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(分類地圖)
|
||||
download_if_missing "docs/README.md" "$REPO_URL/docs/README.md"
|
||||
|
||||
@@ -70,6 +78,13 @@ 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 區塊)")
|
||||
fi
|
||||
|
||||
# ── 輸出結果 ──────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
@@ -105,6 +120,21 @@ if [ -f "CLAUDE.md" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# 如果 settings.json 已存在,提醒手動加入 hooks 區塊
|
||||
if [ -f ".claude/settings.json" ]; then
|
||||
if ! grep -q "session-start-recall.sh" .claude/settings.json; 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" }] }]'
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🚀 下一步:在 Claude Code 對話裡執行:"
|
||||
echo " /wiki-init"
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# /wiki-recall — Session 開始,手動接關
|
||||
|
||||
開新對話時接上次進度。**Fallback 命令**:SessionStart hook 沒啟動時手動接關;要完整脈絡時也用。
|
||||
|
||||
> 主路徑是 SessionStart hook 自動注入 status 重點,不靠你打命令。
|
||||
> 這支命令應對 hook 失效,以及需要比「status 重點」更完整脈絡的時候。
|
||||
|
||||
---
|
||||
|
||||
## 命名閉環
|
||||
|
||||
init(建) → update(存,session 末) ↔ **recall(接,session 初)** → capture(隨時存結論)
|
||||
|
||||
---
|
||||
|
||||
## 執行流程
|
||||
|
||||
### 第一步:讀 status.md(當前進度)
|
||||
|
||||
讀 `.claude/wiki/status.md`,掌握:
|
||||
- 正在做什麼、阻擋點
|
||||
- 下次 session 第一件事
|
||||
- 待負責人確認、已知問題
|
||||
|
||||
### 第二步:讀 decisions-summary.md(為什麼這樣做)
|
||||
|
||||
讀 `.claude/wiki/decisions-summary.md`,掌握相關的架構決策——避免重新討論已定案的事。
|
||||
|
||||
### 第三步:讀 mistakes.md(別重犯)
|
||||
|
||||
讀 `.claude/wiki/mistakes.md`,掌握已知誤解 + 快速檢查清單。
|
||||
|
||||
### 第四步:掃 wishlist / HANDOFF(如果有)
|
||||
|
||||
- `docs/wishlist.md`:待補功能
|
||||
- 任何 `HANDOFF.md` / 交接note:上一棒留下的脈絡
|
||||
|
||||
### 第五步:回報接關結果
|
||||
|
||||
```
|
||||
📍 接關完成
|
||||
🔄 上次正在做:[status 的「正在做」]
|
||||
🎯 下次第一件事:[status 的「下次 session 第一件事」]
|
||||
⚠️ 待確認:[如有]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 鐵律:快照非即時狀態
|
||||
|
||||
status / wiki 是 **point-in-time 快照,不是即時狀態**。
|
||||
|
||||
接關 = 讀快照 **+ 核實快照**,**不盲信**。
|
||||
|
||||
> 實例:某專案 status 曾寫「待 A 收尾 X」,實際 X 早已完成。
|
||||
> 照舊資訊行動會去催一件已完成的事。
|
||||
|
||||
動手前,先用當前 code / git / 檔案核實快照寫的事項是否仍成立。發現落差 → 先更新 status,再動手。
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
# PreToolUse hook 範本骨架 —— 專案自訂禁令
|
||||
# wishlist §2 可選:讓使用者自訂專案禁令(例:「KBDB 禁動表」「某目錄唯讀」)。
|
||||
#
|
||||
# 預設不啟用。要用時:
|
||||
# 1. 在下面 FORBIDDEN_PATTERNS 填入禁改的路徑/檔名 pattern
|
||||
# 2. 到 .claude/settings.json 的 PreToolUse 加掛這支
|
||||
#
|
||||
# 掛在 PreToolUse(matcher: Write|Edit)。stdin 收到 JSON:{ tool_name, tool_input: { file_path } }
|
||||
# 命中禁令 → exit 2 擋。
|
||||
#
|
||||
# 誠實限制:只擋直接寫檔。bash 繞道、helper 間接改動擋不到。留痕可審 ≠ 技術防偽。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── 專案自訂:禁改的 pattern(一行一個,case glob 語法)──────
|
||||
# 範例(已註解,啟用前請改成自己的):
|
||||
# "*/db/schema.sql" # 禁手改 schema
|
||||
# "*/migrations/*" # migration 一旦建立不可改
|
||||
FORBIDDEN_PATTERNS=(
|
||||
# "*/your/protected/path/*"
|
||||
)
|
||||
|
||||
# 沒設任何禁令 → 直接放行(骨架預設狀態)
|
||||
[ ${#FORBIDDEN_PATTERNS[@]} -eq 0 ] && exit 0
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
else
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
||||
fi
|
||||
|
||||
[ -z "$FILE_PATH" ] && exit 0
|
||||
|
||||
for pattern in "${FORBIDDEN_PATTERNS[@]}"; do
|
||||
# shellcheck disable=SC2254
|
||||
case "$FILE_PATH" in
|
||||
$pattern)
|
||||
cat >&2 <<EOF
|
||||
🚫 專案禁令攔截:$FILE_PATH 命中禁改規則($pattern)。
|
||||
|
||||
這是本專案 .claude/hooks/pre-write-guard.sh 設定的硬底線。
|
||||
要動 → 先和負責人確認,並更新禁令設定。
|
||||
EOF
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
exit 0
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
# PreToolUse hook — 動 code 前檢查有沒有對應 SDD
|
||||
# wishlist §2:把 /sdd-check 從「命令要人打」升級成「hook 自動攔」。
|
||||
#
|
||||
# 掛在 settings.json 的 PreToolUse(matcher: Write|Edit)。
|
||||
# stdin 收到 JSON:{ tool_name, tool_input: { file_path, ... } }
|
||||
# 行為:動到 code 檔(.ts/.go/...)但 docs/3-specs/ 下沒有任何 SDD → 警告(exit 2 擋)。
|
||||
#
|
||||
# 誠實限制(抄 arcrun):只擋語法層明顯違規(直接寫 code 檔)。
|
||||
# 藏在 helper 裡、用 bash 繞道的改動擋不到。
|
||||
# 價值是「想跳過會被抓到 + 留痕可審」,不是技術防偽。絕不聲稱「不可能繞過」。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
# 解析 file_path。優先用 jq,沒有 jq 退回 grep(容錯)。
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||
else
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
||||
fi
|
||||
|
||||
# 拿不到路徑 → 不擋(容錯,寧可放過也不誤殺)
|
||||
[ -z "$FILE_PATH" ] && exit 0
|
||||
|
||||
# 只管 code 檔。docs/markdown/設定檔等放行。
|
||||
case "$FILE_PATH" in
|
||||
*.ts|*.tsx|*.js|*.jsx|*.go|*.py|*.rs|*.java|*.rb|*.php|*.c|*.cpp|*.h|*.hpp|*.swift|*.kt) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
# 改 SDD 自己 / 測試檔 → 放行
|
||||
case "$FILE_PATH" in
|
||||
*docs/3-specs/*) exit 0 ;;
|
||||
*_test.*|*.test.*|*.spec.*|*/tests/*|*/test/*) exit 0 ;;
|
||||
esac
|
||||
|
||||
# docs/3-specs/ 下完全沒有 design.md → 攔
|
||||
SDD_COUNT=0
|
||||
if [ -d "docs/3-specs" ]; then
|
||||
SDD_COUNT=$(find docs/3-specs -name 'design.md' -not -path '*TEMPLATE*' 2>/dev/null | wc -l | tr -d ' ')
|
||||
fi
|
||||
|
||||
if [ "$SDD_COUNT" -eq 0 ]; then
|
||||
cat >&2 <<EOF
|
||||
🚫 SDD 協議攔截:要動 code 檔 ($FILE_PATH),但 docs/3-specs/ 下找不到任何 SDD。
|
||||
|
||||
絕對鐵律:任何 code 變動前必須有對應 SDD(design.md)。
|
||||
|
||||
請先:
|
||||
1. 確認這個改動屬於哪個子系統
|
||||
2. 在 docs/3-specs/[子系統]/ 建立 design.md(可用 /sdd-check 協助)
|
||||
3. 在回覆開頭宣告已讀 SDD + 對應 task
|
||||
|
||||
小修改(修 bug、改文字)若確定豁免,請明確說明範圍後由人放行。
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# 有 SDD:放行,但留痕提醒要宣告(stderr 警告,不擋)
|
||||
echo "📋 提醒:docs/3-specs/ 下有 SDD。動手前請確認已讀對應 design.md 並在回覆宣告。" >&2
|
||||
exit 0
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
# SessionStart hook — 開 session 自動注入 status.md 重點
|
||||
# wishlist §1 主路徑:不靠 CC 自覺、不用人說,開 session 就把進度推到眼前。
|
||||
#
|
||||
# 掛在 settings.json 的 SessionStart(matcher: startup|resume|clear)。
|
||||
# stdout 會被當成 context 注入給 CC。
|
||||
#
|
||||
# 鐵律:status 是 point-in-time 快照,非即時狀態。
|
||||
# 這個 hook 只負責「把快照推到眼前」,核實快照是 CC 的責任——下面的提醒就是要它別盲信。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
STATUS_FILE=".claude/wiki/status.md"
|
||||
|
||||
# 沒有 wiki 就安靜退出(exit 0),不干擾還沒 /wiki-init 的專案
|
||||
if [ ! -f "$STATUS_FILE" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "════════════════════════════════════════════════"
|
||||
echo "📍 接關:上次進度(來自 $STATUS_FILE 快照)"
|
||||
echo "════════════════════════════════════════════════"
|
||||
echo ""
|
||||
cat "$STATUS_FILE"
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────"
|
||||
echo "⚠️ 以上是 point-in-time 快照,非即時狀態。"
|
||||
echo " 動手前先核實:快照寫的事項是否真的還沒做完?"
|
||||
echo " 需要完整脈絡(decisions / mistakes / SDD)→ 執行 /wiki-recall"
|
||||
echo "════════════════════════════════════════════════"
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup|resume|clear",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": ".claude/hooks/session-start-recall.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": ".claude/hooks/sdd-guard.sh"
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": ".claude/hooks/pre-write-guard.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,9 @@
|
||||
| `.claude/wiki/mistakes.md` | 做新功能前 | 已知誤解 + 快速檢查清單 |
|
||||
| `.claude/wiki/decisions-summary.md` | 遇到設計判斷時 | 架構決策快速查 |
|
||||
|
||||
> 開 session 由 `SessionStart` hook 自動注入 status 重點。沒自動接關 → 打 `/wiki-recall`。
|
||||
> status/wiki 是 **快照非即時狀態**:讀快照 **+ 核實快照**,不盲信。
|
||||
|
||||
---
|
||||
|
||||
## 規範索引
|
||||
|
||||
Reference in New Issue
Block a user