feat(W2 Phase 2-3): JDD 文件範本+八條封路 hook+還清兩件舊債
SDD: docs/3-specs/jdd-dual-profile(active)。編號 task 26/33 完成,Phase 4-5 未開工。 ■ Phase 2 JDD 文件範本(orchestrator profile) 範本形狀對齊「實際跑出來的那兩份」(總管已寫的 root.md 15 卡、journeys.md J-1 九站), 不是照規格憑空造: - 卡片是巢狀 bullet(`- **P1** 🟢 …` + 子項放來源/對帳),非規格畫的平行文字行 - 站點索引**巢狀 bullet 不用表格**(表格會把層級壓平,看不出從屬) - 兩份都保留「這卷還缺什麼(誠實記)」收尾段——規格沒有,但那是防假綠的地方 新增:root.md / journeys.md / sprint.md / triage-map.md 四範本(add-if-missing, 填了就永不覆蓋)+ plugin-load-order.md(W3 插槽,框架不發明平行外掛格式) ■ Phase 3 封路 hook(八條規則落六支檔) - role-guard(J1+J2+J3)★命門:考生不能改考卷。六組實測含「考題藏在別的 md 裡」也擋 - jdd-format-guard(J4+J5+J8):紅卡缺對帳日/任務缺站號/PM 文件混技術名詞 - station-done-guard(J6):收工判準是站的考題全綠,不是任務全關 - regression-scope(J7):動實作 → 列出要重考哪幾題(只提醒不擋) - install-artifact-guard(S1):實例不改機制 - orchestrator-scope-guard(S4):總管不進成員 repo 動實作(從實例上收進框架, 路徑清單改由實例自填,範本零專名) 掛載鏈依「範圍大的擋在前」:改機制 → 角色 → 位置 → 格式 → 既有三支 ■ 還清兩件舊債 - update.sh 檔案清單改讀 manifest(舊硬編降為抓不到來源時的 fallback) ——install/update 兩份手抄清單漂移的根因全修 - CLAUDE.md 界標補植:舊實例全文原封包進本地區、框架區重鋪、原檔備份、冪等 ——解開「沒界標⇒不敢覆蓋⇒框架改的憲法永遠送不到既有實例」這個死結 ■ 修掉三個自己造的問題(實測抓出來的,不是想出來的) - jdd-format-guard 誤擋真實 journeys.md 的「這卷還缺什麼」自述段 → 排除法改**正面圈定**(只掃卡片本體與站內文),說明區/自述段/索引自然不在範圍 - install-artifact-guard 把 pre-write-guard.sh 也擋了——而它的錯誤訊息正叫人去改那支 → 使用者自訂插槽列為最優先放行 - check-legacy-paths 用 HEAD 當基準會**自我弱化**:改成清單驅動後保護範圍 35→29 條 → 基準改指最後一次真正發佈的版本 ■ 實測(全部貼過輸出) - G2 考生改考卷:6/6,含 orchestrator 寫 code/engineer 改考題/考題藏別處 - G4 憲法分流:兩環境重裝,總管版技術軌關鍵字 0、成員版上游指針 8,界標 4/4 - G6 實例改機制:4/4,含框架開發標記放行與自訂插槽放行 - G7 CI 擋實例名:注入違規 → 指出檔案行號 exit 1 - G3 進度以站計量:起牀推「J-1 已點亮 2/9 站」、收工列未亮站並禁用任務數當理由 - 回歸考、界標補植冪等、orchestrator-scope-guard 四組:全通 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
#!/bin/bash
|
||||
# role-guard.sh — 角色封路:誰能寫什麼(JDD 規則 J1+J2+J3)
|
||||
#
|
||||
# 掛 PreToolUse(matcher: Write|Edit|MultiEdit)。
|
||||
#
|
||||
# ── 為什麼三條規則合成一支 hook ─────────────────────────
|
||||
# J1 orchestrator 不准寫 code
|
||||
# J2 orchestrator 不准寫技術軌文件(任務池/規格)
|
||||
# J3 engineer 不准改考卷(旅程/根文件/任何考題)
|
||||
# 三條都是「同一個 hook 事件 × 同一份身分判定 × 同一張路徑表」。
|
||||
# 拆三支=解析三次 JSON、三處維護同一張表,改一條規則要記得改三個檔。
|
||||
# 規則編號保留在程式碼與錯誤訊息裡,追溯不會少。
|
||||
#
|
||||
# ── J3 是命門 ───────────────────────────────────────
|
||||
# 考生不能改考卷。這條沒守住,整套 PM 軌就是裝飾品:
|
||||
# 考題沒過的人,只要改一下考題就「過了」,而且沒有人會發現。
|
||||
# 所以 J3 攔的不只是檔案,還包括 md 檔裡的 Gherkin 區塊——
|
||||
# 考題常常就住在別的文件裡。
|
||||
#
|
||||
# 誠實限制:擋的是「直接寫檔」這個語法層動作。
|
||||
# 用 bash 繞道(sed -i / cat > / python 改檔)擋不到。
|
||||
# 價值是「想跳過會被抓到 + 留痕可審」,不是技術防偽。絕不聲稱不可能繞過。
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/role-lib.sh
|
||||
. "$HOOK_DIR/lib/role-lib.sh" 2>/dev/null || exit 0 # 函式庫不在就放行(容錯)
|
||||
|
||||
INPUT="$(cat)"
|
||||
FILE_PATH="$(sdt_file_path "$INPUT")"
|
||||
[ -z "$FILE_PATH" ] && exit 0 # 拿不到路徑 → 放行,寧可漏擋不誤殺
|
||||
|
||||
REL="$(sdt_rel_path "$FILE_PATH")"
|
||||
ROLE="$(sdt_role)"
|
||||
|
||||
# 身分矩陣先驗(成員 repo × orchestrator 這格不存在)
|
||||
sdt_assert_identity || exit 2
|
||||
|
||||
# ── 路徑分類 ───────────────────────────────────────
|
||||
is_code_path() {
|
||||
case "$1" in
|
||||
src/*|*/src/*) return 0 ;;
|
||||
*.py|*.ts|*.tsx|*.js|*.jsx|*.go|*.rs|*.java|*.rb|*.c|*.h|*.cpp|*.sh|*.bash|*.zsh) return 0 ;;
|
||||
*.sql|*.vue|*.svelte|*.swift|*.kt|*.php) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
is_tech_track_doc() { # 技術軌文件:任務池與規格
|
||||
case "$1" in
|
||||
*tasks.md|*requirements.md|*design.md) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
is_exam_paper() { # 考卷:旅程/根文件/獨立考題檔
|
||||
case "$1" in
|
||||
*journeys.md|*root.md|*.feature) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# md 檔內含 Gherkin 區塊?(考題常寄住在別的文件裡)
|
||||
content_has_gherkin() {
|
||||
local c
|
||||
c="$(sdt_write_content "$INPUT")"
|
||||
[ -z "$c" ] && return 1
|
||||
# ```gherkin 圍籬,或 journeys.md 的考題行式(- **G-1.1**),或 Given/When/Then 三件成組
|
||||
printf '%s' "$c" | grep -qiE '```[[:space:]]*gherkin' && return 0
|
||||
printf '%s' "$c" | grep -qE '^\s*-?\s*\*\*G-[0-9]+\.[0-9]+\*\*' && return 0
|
||||
if printf '%s' "$c" | grep -qE '^\s*-?\s*Given ' \
|
||||
&& printf '%s' "$c" | grep -qE '^\s*-?\s*When ' \
|
||||
&& printf '%s' "$c" | grep -qE '^\s*-?\s*Then '; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
block() { # $1=規則編號 $2=標題 $3=正確做法(多行)
|
||||
cat >&2 <<EOF
|
||||
❌ BLOCKED by role-guard(規則 $1)
|
||||
|
||||
$2
|
||||
|
||||
身分:role=$ROLE(來源:AGENT_ROLE 環境變數/依安裝位置推定)
|
||||
路徑:$REL
|
||||
|
||||
$3
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
# ── J1/J2:orchestrator 的禁區 ─────────────────────
|
||||
if [ "$ROLE" = "orchestrator" ]; then
|
||||
if is_code_path "$REL"; then
|
||||
block "J1" "總管(PM)不寫 code。" \
|
||||
" 你的工作是決定「要點亮哪幾站、考題是什麼」,不是自己下去寫。
|
||||
|
||||
正確做法:
|
||||
· 把它變成一個掛站號的任務,交給該 repo 的 engineer
|
||||
· 你要的結果寫成考題(Gherkin),不是寫成實作
|
||||
|
||||
為什麼擋:PM 自己下去寫 code,就沒有人在看「零件之間有沒有人掉進縫裡」了
|
||||
——那正是這個角色唯一不能被取代的價值。"
|
||||
fi
|
||||
if is_tech_track_doc "$REL"; then
|
||||
block "J2" "總管(PM)不寫技術軌文件(任務池/規格)。" \
|
||||
" 任務池與規格屬於 engineer。你對它們**只讀**。
|
||||
|
||||
正確做法:
|
||||
· 要調整優先順序 → 改 sprint.md 的指定站,讓認領流程自己去挑任務
|
||||
· 覺得少了任務 → 說出「哪一站的考題會掛」,由 engineer 認領或新增
|
||||
· 要改驗收標準 → 改 journeys.md 的考題(那是你的檔)
|
||||
|
||||
為什麼擋:你一旦動手改任務清單,就等於繞過「先認領→不足才新增」的順序,
|
||||
很快會長出第二份平行的任務池,兩邊不同步。"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── J3:engineer 不准改考卷(命門)─────────────────
|
||||
if [ "$ROLE" = "engineer" ]; then
|
||||
if is_exam_paper "$REL"; then
|
||||
block "J3" "考生不能改考卷。" \
|
||||
" journeys.md/root.md/考題檔是 PM 的檔案,engineer 只能讀當期指定站的段落。
|
||||
|
||||
正確做法:
|
||||
· 考題沒過 → 去把東西做對,不是把題目改掉
|
||||
· 真的認為題目出錯了(判準不合理、站定義有誤)→ **回報 PM**,由他決定改不改
|
||||
· 你需要的是「這站到底要什麼」→ 讀,不要寫
|
||||
|
||||
為什麼這條是命門:考題沒過的人只要改一下考題就「過了」,
|
||||
而且沒有任何人會發現。這樣整套驗收就是裝飾品。"
|
||||
fi
|
||||
# md 檔本身不是考卷,但內容含考題 → 一樣擋(考題常寄住在別的文件裡)
|
||||
case "$REL" in
|
||||
*.md)
|
||||
if content_has_gherkin; then
|
||||
block "J3" "考生不能改考卷(這次寫入的內容含考題)。" \
|
||||
" 這個檔名不是考卷,但你要寫進去的內容裡有 Gherkin 考題(Given/When/Then 或 G-x.y)。
|
||||
|
||||
正確做法:
|
||||
· 要記錄「我做到哪了」→ 寫 wiki/status,不要在文件裡複寫考題
|
||||
· 要提出新考題 → 回報 PM,由他寫進 journeys.md
|
||||
|
||||
為什麼擋:考題散落成兩份就會不同步,
|
||||
而不同步的那一刻起,「全綠」代表什麼就沒人說得準了。"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user