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:
2026-08-06 00:26:56 +08:00
parent 6a49f25aef
commit 2f5d9f3bb2
18 changed files with 1246 additions and 83 deletions
@@ -0,0 +1,128 @@
# journeys.md — PM 軌驗收([專案/組織名])
> **根**[root.md](root.md)J-1 對應根卡 **P?**)。本文件**只從人的角度寫**,禁止出現系統/模組名詞。
> **誰能寫**:只有總管(orchestrator)。engineersubagent **禁改本檔與任何考題**——考生不能改考卷。
> **標記**:🟢 考過即關 / 🔴 考過轉「對帳中」(站上附真實世界判準與對帳日)
> **站全域編號**,跨 Journey 共享;同一站在別條旅程重複出現只寫引用,不重抄。
> **考題(Gherkin)的 Then 只准寫「使用者看得到/感覺到什麼」**——「回傳 200」「部署成功」一律不准入題。
>
> 立卷 [YYYY-MM-DD]。取代「數任務完成幾條」當進度語言:
> 從今天起回報形式是「**J-x 已點亮 n/m 站**」。
---
## A1 [角色名]
- 一句話描述:**[這個角色是誰、他想幹嘛。用他自己會講的話寫,不要用你的話。]**
- 他不想知道我們內部長什麼樣。判準:
- 每一站都要再問一次——「**用戶需不需要為了過這關,去理解一個屬於我們內部的概念?**」
- 需要 ⇒ 這站沒過。就算對象是工程師也一樣。
### J-1 [旅程名:用第一人稱寫這個角色的一條情境]
- 這條旅程的頭尾:**[起點] → [中間] → [終點]**。
- **驗收只認頭尾**。中間任何一環「做完了」都不算通關。
#### S1 [站名:用戶拿到什麼] 🟢
- [一句話解釋這站在幹嘛。]
- **G-1.1**
- Given [我是誰/什麼狀況]
- When [我做了什麼]
- Then [我看到/感覺到什麼]
#### S2 [站名] 🟢
- [一句解釋。]
- **G-2.1**
- Given [...]
- When [...]
- Then [...]
- **G-2.2**
- Given [邊界情況——想一個「安靜地什麼都沒發生」的可能]
- When [...]
- Then [...]——**不准安靜地什麼都沒發生**
#### S3 [站名] 🔴
- 【對帳:**[真實世界的判準,要能用數字或事實判真假]**——[低於多少就代表這個設計錯了,要改成什麼]|對帳日 [YYYY-MM-DD]】
- **G-3.1**
- Given [...]
- When [...]
- Then [...]
### J-2 [第二條旅程]
> 站全域共享:同一站重複出現只寫引用,不重抄。
#### S1 →(引用,見 J-1
#### S7 [這條旅程才有的新站] 🟢
- **G-7.1**
- Given [...]
- When [...]
- Then [...]
---
## 附:站點索引(回歸考觸發表)
> 用途:某一站相關的東西被改動時,查這裡就知道**要重考哪些題**。
> ⚠️ 用巢狀 bullet,**不要用表格**——表格會把層級壓平,讀的人看不出「站 → 被誰經過 → 重考什麼」的從屬關係。
- **S1** [站名]
- 被經過:J-1
- 改動時重考:G-1.1
- **S2** [站名]
- 被經過:J-1、J-2
- 改動時重考:G-2.1、G-2.2
- **S3** [站名]
- 被經過:J-1
- 改動時重考:G-3.1 + 下游 S4~S9(這站變了,後面全部要重考)
---
## 這卷還缺什麼(誠實記,別假裝完備)
> 這一段是**防假綠的裝置**,不是免責聲明。刪掉它,這卷看起來就會比實際完整。
- **每一站現在點亮了沒有,刻意留白**。理由:站的狀態要靠實測填,
不是靠對著舊文件推測——那正是「假綠」的來源。第一次點亮由 sprint 收尾時實考填入。
- **目前只有 J-1**。第二條旅程等 J-1 通關再立——規矩是「**需要新站才提案新站**」,
不是先把表格畫滿。
- [其他你知道還缺、但這一版先不做的東西。寫出來,別讓下一個人以為這卷是完整的。]
---
<!-- ════════ 寫這份文件的規矩(給總管看,不是內容的一部分)════════
【三層結構】角色(A)→ 旅程(J)→ 站(S)→ 考題(G)
與技術軌的 Epic → Story → EARS 對稱,但**切法正交**:
技術軌沿系統結構切(保證每個零件是對的),
PM 軌沿人的經歷切(保證零件之間沒有人掉進縫裡)。
【站的編號是全域的】
S 不隸屬於某條 J。同一個能力被兩條旅程經過,就是同一個 S、同一組考題。
重複出現只寫「S1 →(引用,見 J-1)」,**不重抄**——抄第二份就會有兩份不同步的考題。
【Gherkin 的 Then 只能寫使用者感受得到的事】
✅ 「我在信箱收到一組號碼」「我看得到它是從我哪個檔案來的」
❌ 「回傳 200」「部署成功」「資料寫入成功」
這條就是「HTTP 200 不算驗過」的正式化——技術上通了但使用者沒感覺到,等於沒通。
【紅站的對帳行寫在站上,不寫進考題】
法條(Gherkin)管「做到沒」,對帳管「賭對沒」,物理分離。
🔴 站缺對帳行 格式錯誤。
【定 Journey 之前】
先寫一篇該角色的**敘事故事**給人讀一次,驗「完整性」(讀完找不找得到斷裂)。
驗完歸檔到 docs/archive/stories/,不進日常維護。
【誰能寫】
只有 orchestrator。engineersubagent 寫這個檔會被 hook 擋下——考生不能改考卷。
考題不過就去把東西做對,不是去改考題。
════════════════════════════════════════════════════════ -->
@@ -0,0 +1,58 @@
# 政策包(policy pack)= 官方 plugin,以及它什麼時候被載入
> 這份是**約定**,不是實作。它回答一件事:機制是怎麼在 agent 醒來之前就已經在場的。
---
## 鐵律:政策包一律做成 Claude Code 官方 plugin
- 框架**不發明平行的外掛格式**。官方 plugin 已經涵蓋 hooksskillsagentscommands
MCP server 的打包、安裝與版本分發——自造一套等於重寫一個市場上活得好好的輪子。
- 要寫政策包的人,**動手前先讀官方文件**,照現行 schema 實作,
不要憑記憶寫設定檔格式。官方會改,記憶不會跟著改。
---
## 三層是什麼(別把層搞混,混了就回不去)
- **框架(L1)**:方法論——雙軌、wiki、封路、階梯、儀表
- 判準:**換一家公司照樣成立**
- 住這裡:本 template
- **政策包(L2)**:一家之言的技術棧政策
- 判準:**換一家公司就不成立**
- 住這裡:獨立的 plugin repo
- **實例(L3)**:資料——卡片內容、wiki、sprint、指標
- 判準:**換一個 repo 就不成立**
- 住這裡:各實例自己
> 框架範本裡出現具體專案名 = 格式錯誤(有 CI 擋)。政策包不受此限——專名是它的內容。
---
## 載入順序(agent 醒來時世界已就位)
1. **profile 憲法**(scope 軸:這個資料夾的 CLAUDE.md——總管版或成員版)
2. **框架 hooks 上鏈**common profile
3. **已安裝的政策包注入**——必讀推到眼前、白名單 hook 排入鏈尾、skill 就緒
4. **接關**wiki 快照)
- 執行期的總管/engineer **不需要知道** plugin 是官方機制還是框架機制——
hooks 在鏈上就會攔、skill 在庫裡就會觸發。
- **機制不靠認知,靠結構在場。** 唯一需要懂 plugin 規格的角色,是「改框架/寫政策包的人」。
---
## 框架這一側只做兩件事
1. 文件化上面這個約定與順序(就是這份檔)
2. 在兩部憲法裡留 **must-read 注入點**——政策包的 SessionStart hook 有地方把必讀推進來
注入點長這樣(兩部憲法末尾都有):
```markdown
## 政策包必讀(policy pack must-read
<!-- policy-pack:must-read -->
```
- 沒裝政策包時它是空的,不影響任何事。
- 框架**不需要為此新造機制**——官方 hook 就做得到。
@@ -0,0 +1,76 @@
# root.md — 我們到底在做什麼(需求根文件)
> 這是**兩軌共享的唯一錨點**PM 軌([journeys.md](journeys.md))和技術軌(各專案自己的規格)都從這裡長出來。
> **規矩**:一張卡一句白話 + 來源標記 + 紅綠燈。**禁止出現任何技術名詞**——技術是手段,寫進各專案自己的規格,不寫進根。
> **紅綠怎麼判**:能指著市場上活得好好的先例說「照這個做」=🟢;不能、解法是猜的=🔴。**紅卡必附對帳判準與對帳日。**
> **誰能寫**:只有總管。**誰來勾**:出資/決策的那個人——卡片真偽是他的第一道閘,這份文件的價值全在他點頭或搖頭。
>
> 立卷 [YYYY-MM-DD]。素材=[列出你是從哪些文件/對話讀出這些卡的]。
---
## 一、[分節標題:例如「關於我怎麼工作」]
> 分節是為了讓人一眼看出「這幾張是同一類的事」。沒把握就先不分,卡多了再分。
- **P1** 🟢 [一句白話。不懂技術的人讀了能點頭或搖頭。]
- ——(你說的:[出處]
- **P2** 🔴 [一句白話。]
- 【要驗證:[一句話判準,要能用真實世界的數字或事實判真假] | 對帳日 [YYYY-MM-DD]】
- **P3** 🟢 [一句白話。]
- ——(你舉的例子:[出處]
## 二、[分節標題:例如「關於我在賣什麼」]
- **P4** 🔴 [一句白話。]
- 【要驗證:[判準] 對帳日 [YYYY-MM-DD]】
- **P5** 🟢 [一句白話。]
- ——(從你的抱怨反推:[出處])
## 三、[分節標題:例如「關於產品要給誰」]
- **P6** 🟢 [一句白話。]
- ——(我猜的,請確認)
- 這張卡就是 [journeys.md](journeys.md) 的 **J-1**。 ← 卡片對應到某條旅程時這樣標
---
## 這份文件現在的狀態
> 這一段不是客套,是**交接資訊**。刪掉它,讀的人就不知道這份文件可信到什麼程度。
- **全部 N 張卡都還沒被勾過**(或:已勾 N/M)。紅綠燈是總管依「有沒有市場先例」判的,來源標記寫的是從哪裡讀到的。
- **請你做的只有一件事**:掃過去,看有沒有哪張卡「不是我的意思」或「這根本不重要」。搖頭的拿掉,你補的加上。
- ⚠️ 標明有沒有「我猜的」卡。凡是 PM/AI 推測補完、而非本人明示的內容,**必須帶「請確認」類標記**,不得混充原意。
- 紅卡的對帳日到了,會拿真實數據來對帳:**承諾成立,或換一個承諾**。賭錯不丟臉,賭了不認才是。
---
<!-- ════════ 寫這份文件的規矩(給總管看,不是內容的一部分)════════
【卡片格式】
- **P<編號>** <🟢或🔴> <一句白話>。
- ——(來源標記) ← 🟢 卡用這行
- 【要驗證:<判準> 對帳日 <日期>】 ← 🔴 卡改用這行,**缺了就是格式錯誤,會被擋**
【來源標記怎麼寫】
常見形式:(你說的)(你舉的例子:X)(從你的抱怨反推:X)(我猜的,請確認)
形式不限於此,可依實際來源自由描述。
唯一鐵律:**凡是推測補完、而非本人明示的內容,必須帶「請確認」類標記。**
【禁止技術名詞】
不准出現:API、DB、資料庫、WASM、MCP、endpoint、schema、SDK、CLI…
技術是達成手段,寫進各專案自己的規格,不寫進根。
⚠️ 自檢只掃**卡片本體**(`- **P...` 開頭那些行及其子項),
不掃這段說明區與 <!-- --> 註解——否則你在解釋規矩時提到的詞會被自己抓到。
【紅綠怎麼判】
能指著市場上活得好好的先例說「照這個做」=🟢(輪子卡,考過即關)
不能、解法是猜的=🔴(賭注卡,考過轉「對帳中」,等對帳日拿真實數據判決)
【誰能寫】
只有 orchestrator。engineersubagent 寫這個檔會被 hook 擋下——考生不能改考卷。
════════════════════════════════════════════════════════ -->
@@ -0,0 +1,61 @@
# sprint.md — 本期要點亮哪幾站
> **單位是站號,不是一批任務。** 這份檔的存在理由:讓自動起牀的排程/新開的 session
> 一睜眼就有明確的「離通關還缺什麼」,而不是讀到一堆過期任務只能空轉收工。
> **誰能寫**:只有總管(orchestrator)。
---
## 本期
- **期間**[YYYY-MM-DD] → [YYYY-MM-DD]
- **交付**[J-x] 的 **[S-a]、[S-b]**
- **考題**G-a.1、G-a.2、G-b.1
- **這期不做**:[明寫哪些站這期不碰。沒寫,別人就會自己加戲。]
### 現在幾站亮了
- [J-x] 已點亮 **n/m** 站
- ✅ 已亮:[S-?]([實測證據在哪])
- ◐ 半通:[S-?]([缺什麼])
- ❌ 未亮:[S-?]、[S-?]
---
## 認領流程(順序鐵律,不准跳)
> **先認領 → 認領不足才新增 → 新增必掛站。** 這個順序是為了防止重造一份任務清單。
1. **先認領**:對現有任務池**逐項**問——
「這個任務不做,[S-a]/[S-b] 的考題會掛嗎?」
- 會掛 → 認領進本期
- 不會 → **留在池子裡,這期不准碰**
2. **認領完考題還是過不了** → 池子真的缺東西 → **此時才准新增任務**
- 新任務**必須標注它服務哪一站**(無站號=格式錯誤,hook 會擋)
3. **收尾判準**=指定站的考題**全綠**,不是任務全關
- 🟢 站 → 點亮、關閉
- 🔴 站 → 點亮 + 轉「對帳中」,等 root.md 上的對帳日
---
## 認領清單
- [S-a] [站名]
- 認領:[任務編號/描述](在哪個 repo)
- 認領:[...]
- 新增:[...] ← 標明是新增的,以及為什麼池子裡沒有
- [S-b] [站名]
- 認領:[...]
---
## 到期結算(換檔機制)
> 期間一到就**強制結算**,不准無聲延期——過期的 sprint 檔正是「起牀讀到殘骸」的來源。
- 到期日 [YYYY-MM-DD] 當天做三件事:
- 亮了的站 → 在 journeys.md 標記,附**實測證據**
- 沒亮的站 → **搬移**到下一期,並寫一行「為什麼沒亮」
- 沒人認領也沒亮的任務 → **作廢或退回池子**,不准留在這裡假裝還活著
- 狀態只有三種:**✅ 通(附實測證據)/◐ 半通(標明缺什麼)/❌ 斷**
- 「程式碼寫完了」不是狀態
@@ -0,0 +1,34 @@
# triage-map.md — 能力域 → 誰做
> 新需求進來時查這張表:這件事該落在哪個成員 repo。
> **表上沒有 ⇒ 不准自己開新 repo**,走人閘提案。
> **誰能寫**:只有總管(orchestrator)。
---
## 對照
- **[能力域名稱]**
- 落在:`[成員 repo 路徑]`
- 邊界:[什麼算它的、什麼不算——寫清楚才不會兩個 repo 搶同一件事或互推]
- **[能力域名稱]**
- 落在:`[成員 repo 路徑]`
- 邊界:[...]
---
## 分診的第二問(別漏)
查完「誰做」,還要問**這個需求動到哪條旅程的哪些站**:
- 動到既有的站 → 查 journeys.md 的**站點索引**,決定重考範圍
- 需要新的站 → 走 journeys.md 提案(只有總管能寫)
- 連角色都是新的 → **先寫一篇敘事故事**給人驗完整性,再立新旅程
---
## 表上沒有的怎麼辦
- 先確認真的沒有——多數「新能力」其實是既有能力域的延伸,硬開新 repo 只是把邊界問題往後推。
- 真的沒有 → 提案開新 repo,**這是人閘**(動到大家共用的結構)。
- 提案要寫:這個能力域的邊界在哪、為什麼塞不進既有任何一個、誰維護。
@@ -0,0 +1,97 @@
#!/bin/bash
# orchestrator-scope-guard.sh — 總管不進成員 repo 動實作(分離規格 防糾纏閘 S4)
#
# 只裝在 orchestrator profile。掛 PreToolUseWrite|Edit|MultiEdit)。
#
# ── 與 role-guard 的分工(刻意分兩支,不重疊)────────────
# role-guard 管「這個**角色**能寫什麼**類型**的檔」(code?考卷?任務池?)
# orchestrator-scope-guard 管「總管能不能進這個**位置**」(成員 repo 的地盤)
# 兩件事正交:總管在自己家寫 md 沒問題,進成員 repo 寫 md 也沒問題(那是交辦),
# 但進成員 repo 寫實作檔就是越界——那是該 repo 的 engineer 的事。
#
# ── 為什麼「只擋非 .md」──────────────────────────
# .md 放行是刻意的:總管要能在成員 repo 裡留交辦文件、筆記、規格討論。
# 擋的是「總管自己下去改人家的實作」——那會讓該 repo 的 CC 完全不知道發生什麼事,
# 而且繞過了該 repo 自己的規格與 wiki 紀律。
#
# ── 自動派工放行 ────────────────────────────────
# 當「總管派出去的 subagent 已經戴著該 repo 的人格」時,它寫該 repo 的 code 是合理的
# (心智已經在那個 repo 裡,context 是隔離的)。雙重夾:
# ① 環境變數 CLAUDE_CODE_CHILD_SESSION=1subagent 標籤;總管主 session 沒有)
# ② 路徑在下方白名單內
# ⚠️ 隔離保證是 prompt reset(軟的,靠監測),不是結構級。要絕對純淨走獨立 session。
#
# 誠實限制:擋的是路徑語法層。「把實作偽裝成 .md」或「該交辦卻判斷成可直改」
# 這種語意層越界擋不到。它是底線,不是萬能。
set -uo pipefail
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$HOOK_DIR/lib/role-lib.sh" 2>/dev/null || exit 0
# 只在總管實例生效(裝錯地方就安靜退出)
[ "$(sdt_scope)" = "orchestrator" ] || exit 0
INPUT="$(cat)"
FILE_PATH="$(sdt_file_path "$INPUT")"
[ -z "$FILE_PATH" ] && exit 0
ROOT="$(sdt_repo_root)"
REL="$(sdt_rel_path "$FILE_PATH")"
# ── 成員 repo 目錄怎麼認 ─────────────────────────
# 不寫死任何專案名(框架不含實例資料)。兩種來源:
# ① 實例自填的清單:system-dev/.member-dirs(一行一個目錄前綴)
# ② 沒有該檔 → 動態偵測:頂層下「自己帶 .git 的子目錄」就是成員 repo
MEMBER_DIRS_FILE="$ROOT/system-dev/.member-dirs"
top="${REL%%/*}"
[ "$top" = "$REL" ] && exit 0 # 不在任何子目錄裡=總管自己的檔,放行
in_member_repo=1
if [ -f "$MEMBER_DIRS_FILE" ]; then
while IFS= read -r d; do
case "$d" in ''|'#'*) continue ;; esac
d="${d%/}"
case "$REL" in "$d"/*) in_member_repo=0; break ;; esac
done < "$MEMBER_DIRS_FILE"
else
[ -d "$ROOT/$top/.git" ] && in_member_repo=0
fi
[ "$in_member_repo" -eq 0 ] || exit 0
# ── 自動派工白名單(實例自填,框架不預設任何路徑)────
ALLOW_FILE="$ROOT/system-dev/.autodispatch-allow"
if [ "${CLAUDE_CODE_CHILD_SESSION:-}" = "1" ] && [ -f "$ALLOW_FILE" ]; then
while IFS= read -r a; do
case "$a" in ''|'#'*) continue ;; esac
a="${a%/}"
case "$REL" in
"$a"/*) echo "🤝 [orchestrator-scope-guard] subagent 放行:$a(自動派工)" >&2; exit 0 ;;
esac
done < "$ALLOW_FILE"
fi
# ── 成員 repo 內:.md 放行(交辦/文件),其餘擋 ────
case "$REL" in
*.md|*.MD|*.markdown) exit 0 ;;
esac
cat >&2 <<EOF
❌ BLOCKED by orchestrator-scope-guard(防糾纏閘 S4
總管不進成員 repo 動實作。
成員 repo$top
路徑:$REL
正確做法:
· 要那個 repo 改什麼 → 在**那個 repo** 開一張交辦(.md 放行),
給判準與考題,成品由該 repo 的 CC 按它自己的規格產出
· 你能派 subagent 去做 → 那才是正解,不是自己下去改,也不是叫人去開另一個 session
· 真要放行 subagent 自動寫某個 repo → 把路徑加進 system-dev/.autodispatch-allow
(⚠️ 加=放權,想清楚它影響誰)
為什麼擋:你直接改人家的實作,該 repo 的 CC 完全不知道發生過什麼事——
它的規格、wiki、驗收全部被繞過,下次它照自己的紀律工作時就會把你的改動蓋掉。
EOF
exit 2