6a49f25aef
SDD: docs/3-specs/jdd-dual-profile(draft → active,leo 2026-08-05 回「開工」)
範圍:總管指定的「防炸兩件 → Phase 0 → Phase 1」,Phase 2 以後未開工。
■ 防炸(排在所有 task 之前,因為它們炸的是既有的東西)
- check-no-instance-names.sh + instance-names.txt:框架範本不得混入實例專名
基線實測 22 行命中(非先前誤報的 20)→ 9 行無損泛化改寫、13 行檔級豁免記帳待 W3 搬走
拒絕假性清理(把專名換成模糊詞=資訊消失、分層問題還在)
- check-legacy-paths.sh:已發佈腳本引用的 35 條遠端路徑只增不移
舊實例跑的是舊腳本、路徑寫死;搬檔=整排 404 且不會有下一次更新來修它(1.16.0 前科)
■ Phase 0 地基
- template/manifest/{common,repo,orchestrator}.tsv:安裝清單單一真相源
修好 install/update 兩份硬編清單的既有漂移——install 從不裝 wiki-first-search /
subagent-wiki-guard / publish-lag-check / decisions-summary,但 update 會
⇒ 乾淨安裝反而拿不到 1.16/1.17/1.18 的招牌功能
- .claude/hooks/lib/role-lib.sh:scope×role 兩軸機械判定,零自陳
身分矩陣六組實測全通過,含「成員 repo × orchestrator」不存在的格子擋下
- .sdt-framework-dev:框架開發標記(官方沒有 --framework-dev 這個參數,實查非記憶)
■ Phase 1 雙 profile
- profiles/{repo,orchestrator}/CLAUDE.md 兩部憲法
- install.sh:--profile + 自動偵測+寫檔前確認、manifest 驅動、
CLAUDE.md 三段組裝(框架區/本地補充區界標+sha256)、.profile、.template-manifest、
settings.json 寫入 env.AGENT_ROLE 預設
- update.sh:漂移偵測(不覆蓋手改檔、另存 .new、白話清單)+ 基準快照隨更新前進
- template/CLAUDE.md 原路徑凍結留底(相容)
■ 順手修掉兩個舊 bug(都在本次要動的函式裡)
- add_if_missing 少了 mkdir -p ⇒ 新目錄的檔 curl 失敗但 VERSION 照升(2026-07 記「待回報」至今未修)
- 下載健全性只用 [ -s ]=非空即接受 ⇒ 404 頁面會無聲覆寫好檔
(SKILL.md 260→1 行的機制;同一支腳本的版本號那條路早就防了,檔案這條沒防)
■ 實測(非推論)
- G4 憲法分流:兩個乾淨環境各裝一次,orchestrator 版含 SDD 三件式關鍵字 0 次、
repo 版含上游指針 8 次;界標 4/4;sha 宣告與實算相符
- G7 CI 擋實例名:注入違規行 → fail 並指出 sdd-check.md:77,exit 1;還原後 exit 0
- 漂移偵測:手改兩支 hook → 正確報 2 支、手改內容保住、產 .new;
解掉後歸零;連跑三輪冪等
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
446 lines
29 KiB
Markdown
446 lines
29 KiB
Markdown
---
|
||
status: active # active | draft | paused | closed(生命週期鐵律見 ../SDD-LIFECYCLE.md)
|
||
superseded_by: "" # closed 且被取代時填接替的 SDD 資料夾名
|
||
---
|
||
|
||
# jdd-dual-profile — Design
|
||
|
||
> 建立:2026-08-05 | 最後更新:2026-08-05
|
||
> 負責人:system-dev-template CC
|
||
> 來源:InkStoneCo 總管交辦 **W2**
|
||
> **狀態:active(2026-08-05 leo 回「開工」升活性)**
|
||
>
|
||
> 升活性前實查:全 repo 帶 frontmatter 的只有 `TEMPLATE-sdd`(draft,範本不算數)
|
||
> ⇒ active 數 = 0,**無衝突、無未完成任務需搬移**(D35 ④ a 步驟空集合)。
|
||
>
|
||
> 施工順序由總管指定:**發現①、發現⑦ 的防炸工程 → Phase 0 → Phase 1 → 停下驗 Gherkin**。
|
||
> Phase 2 以後未經確認不得開工。
|
||
|
||
---
|
||
|
||
## 一句話說明
|
||
|
||
把 system-dev-template 從「單一 repo 級框架」改造成 **雙 profile 框架(總管級/repo 級)**,
|
||
並在其上裝入 **JDD PM 軌**(root.md/journeys.md/站號 sprint/角色權限封路),
|
||
讓「憲法分流」「角色權限」「實例不改機制」三件事全部由**檔案結構與 hook 機械決定**,
|
||
沒有任何一格靠 agent 自我判斷。
|
||
|
||
---
|
||
|
||
## 0. 現況實查(動手前先搞清楚現在長什麼樣)
|
||
|
||
> 本節是設計的事實基礎,也是「與規格假設不符」的清單來源。每條都是本次實測,不是回憶。
|
||
|
||
### 0.1 SDD 現況
|
||
|
||
- `docs/3-specs/` 下 5 個資料夾,**帶 frontmatter 的只有 `TEMPLATE-sdd`(status: draft)**。
|
||
`cross-repo-signing` / `install-layout` / `wiki-architecture` / `tasks-project-projection`
|
||
四份都只在正文寫「狀態:已採納/已結案」,**沒有 frontmatter** ⇒ 機器查得到的 `active` 數 = **0**。
|
||
- 本 repo 的 SDD 住 `docs/3-specs/`,但它發給別人的 `sdd-guard.sh` 與 `sdd-active-check.sh`
|
||
預設路徑是 `system-dev/docs/3-specs` ⇒ **框架 repo 的 D35 閘從來沒對自己開過火**。
|
||
- 現有 SDD 的實際慣例是 **design.md + tasks.md 兩件式**(`TEMPLATE-sdd` 也只有這兩支),
|
||
`requirements.md` 在本 repo **沒有先例**。本案依交辦要求補齊三件式。
|
||
|
||
### 0.2 安裝機制現況
|
||
|
||
| 事實 | 影響本設計的地方 |
|
||
|---|---|
|
||
| `install.sh` 逐檔 `download_if_missing "<dest>" "$REPO_URL/<path>"`,**沒有 manifest**,檔案清單硬編在腳本裡 | 加 profile ⇒ 清單要分四份(common/repo/orchestrator/模組交叉),硬編必然漂移 → **必須先做 manifest** |
|
||
| `update.sh` 另有一份**幾乎重複**的清單(update_file/keep_file/add_if_missing/keep_with_template 四類) | 同上;manifest 的「類別」欄正好就是這四類 |
|
||
| `REPO_URL = $TEMPLATE_SOURCE/template`,所有安裝產物的遠端路徑都掛在 `template/` 底下 | 分離 §三把 `common/`、`profiles/` 畫在 repo 根 ⇒ 會變成第二個 base URL;本設計改掛 `template/` 底下(見決策 D2) |
|
||
| `update.sh` 的**自我更新在腳本尾端**(先跑完所有下載才更新自己) | 若這一版搬動既有檔案路徑,舊實例這一輪會整排 404;1.16.0 已被同型問題咬過(來源改動=自動更新死掉)⇒ **既有檔一律不搬**(決策 D3) |
|
||
| `CLAUDE.md` 是整份下載 + `emit_raw_source_block` append,**沒有任何區段界標** | 「本地補充區」目前不存在邊界,漂移偵測無從談起 ⇒ 必須先立界標(設計 §2) |
|
||
| `build_hooks_json()` 依模組(wiki/sdd)條件組裝 settings.json | profile 只是加第三個維度,**沿用同一支函式**,不另造 |
|
||
|
||
### 0.3 hook 現況(template 內共 7 支)
|
||
|
||
`pre-write-guard.sh`(空殼,`FORBIDDEN_PATTERNS` 為空=不攔任何東西)、`publish-lag-check.sh`、
|
||
`sdd-guard.sh`、`session-start-recall.sh`、`subagent-wiki-guard.sh`、`wiki-first-search.sh`、
|
||
`wiki-secret-scan.sh`。
|
||
|
||
- **`AGENT_ROLE` 在整個 repo 與 InkStoneCo 實例中 0 次出現** ⇒ 角色軸完全從零開始。
|
||
- **`guard-cross-project.sh` 不在 template 裡**——它只存在於 InkStoneCo 實例的 `.claude/hooks/`。
|
||
分離 §六.4 標它 `[修改]`,實際動作是「**從實例上收進框架**」,不是就地改(決策 D6)。
|
||
- 同理,`delivery-police.sh`/`self-drive-police.sh`/`unpushed-police.sh`/`history-first-guard.sh`
|
||
等 **11 支都是實例自行發明的**,框架不知道它們存在。
|
||
|
||
### 0.4 漂移基線(機械閘 #3 的今日實測值)
|
||
|
||
比對 InkStoneCo 實例的 `.claude/hooks/` 與本 repo `template/.claude/hooks/`:
|
||
|
||
| 狀態 | 數量 | 檔 |
|
||
|---|---|---|
|
||
| 與框架一致 | 2 | `session-start-recall.sh`、`wiki-secret-scan.sh` |
|
||
| **已被手改(漂移)** | **4** | `pre-write-guard.sh`、`sdd-guard.sh`、`subagent-wiki-guard.sh`、`wiki-first-search.sh` |
|
||
| 實例自行發明 | 11 | 見 §0.3 |
|
||
|
||
⇒ **今天沒有任何機制知道這 4 支已經漂移**。這就是分離 §八 預測②(「漂移數歸零」)的基線值 = 4。
|
||
|
||
### 0.5 實例專名基線(機械閘 #2 的今日實測值)
|
||
|
||
`template/` 底下命中 `arcrun|mira|leo21c|inkstone|uncle6|polaris`:**8 檔 20 行**。分三類:
|
||
|
||
| 類 | 內容 | 處置 |
|
||
|---|---|---|
|
||
| (a) 註解/舉例(6 行) | `sdd-guard.sh`「誠實限制(抄 arcrun)」、`publish-lag-check.sh` 的 jsDelivr 例、`logseq-markers.md` 的 TODO 例句、`issue-handle.md` 的 repo 名清單 | **本波清理**(改寫成通用敘述) |
|
||
| (b) 政策內容混進框架(3 行) | `subagent-wiki-guard.sh`「有 Arcrun RAG MCP 就用它」、`wiki-extract.md`「下游 Arcrun ingest」 | **標逐行豁免,W3 隨 policy pack 搬走** |
|
||
| (c) 整支是 L2 產物(13 行) | `system-dev/workflows/tasks-project-sync.{yaml,local.sh}`(本體就是 arcrun workflow) | **整組標豁免,W3 移入 policy pack** |
|
||
|
||
⇒ 閘 #2 **不能一上線就全紅**。必須配「逐行豁免標記 + 基線報表」,否則為了讓 CI 綠會出現
|
||
假性清理(把 arcrun 換成「某工作流引擎」=資訊消失但問題還在)。
|
||
|
||
---
|
||
|
||
## 範圍
|
||
|
||
### 包含(In Scope)
|
||
|
||
- template 的雙 profile 化(宣告式,非搬檔):manifest、`--profile`、profile 專屬產物
|
||
- CLAUDE.md 生成、框架區/本地補充區界標、update 漂移偵測
|
||
- JDD 文件範本:`root.md`、`journeys.md`(含站點索引表)、站號 sprint 範本、術語表
|
||
- 兩軸身分(scope × role)的機械判定函式庫
|
||
- JDD §六 8 條封路規則 + 分離 §六 4 條防糾纏閘的實作
|
||
- policy plugin 的**插槽**:載入順序文件化 + profile 憲法 must-read 注入點
|
||
- CHANGELOG + VERSION bump + 乾淨環境雙 profile 安裝實測
|
||
|
||
### 不包含(Out of Scope)
|
||
|
||
- **arcrun-policy plugin 本體**(W3):`plugin.json`、marketplace 發行、白名單 hook 內容、
|
||
primer/dispatch-checklist 搬遷、`.mcp.json`
|
||
- **實例側落地**(W4):InkStoneCo 的 root.md/journeys.md 實填、routine 取任務源改站號、
|
||
實例 11 支自製 hook 的退役與上收
|
||
- **雲端側**(W5):LLM Wiki 總編輯管線、儀表 job、`arch_baseline`/`arch_iteration` 卡
|
||
- **記憶階梯改序(KBDB-first)**:藍圖 §7,屬另一波
|
||
- **既有 SDD 的 frontmatter 補齊**(§0.1 撿到的斷層):本波只回報,不順手改——
|
||
那會動到四份別的 SDD 的生命週期狀態,屬規格層,另走 pending-changes
|
||
|
||
---
|
||
|
||
## 1. 架構總覽
|
||
|
||
```
|
||
system-dev-template/ ← 框架 repo(本 repo)
|
||
├─ scripts/install.sh --profile=repo|orchestrator ← 改造:讀 manifest
|
||
├─ scripts/update.sh ← 改造:讀 manifest + 漂移偵測
|
||
├─ scripts/check-no-instance-names.sh ← 新增:機械閘 #2(CI)
|
||
├─ scripts/instance-names.txt ← 新增:黑名單設定檔
|
||
└─ template/ ← 所有安裝產物的遠端根($REPO_URL)
|
||
├─ manifest/
|
||
│ ├─ common.tsv ← 新增:兩 profile 共裝
|
||
│ ├─ repo.tsv ← 新增:repo profile 專屬
|
||
│ └─ orchestrator.tsv ← 新增:orchestrator profile 專屬
|
||
├─ .claude/hooks/… ← 既有 7 支【原地不動】= common 的實體
|
||
│ ├─ lib/role-lib.sh ← 新增:兩軸判定函式庫(被 source)
|
||
│ ├─ role-guard.sh ← 新增:J1+J2+J3
|
||
│ ├─ jdd-format-guard.sh ← 新增:J4+J5+J8
|
||
│ ├─ station-done-guard.sh ← 新增:J6
|
||
│ ├─ regression-scope.sh ← 新增:J7
|
||
│ └─ install-artifact-guard.sh ← 新增:S1
|
||
├─ profiles/
|
||
│ ├─ repo/
|
||
│ │ ├─ CLAUDE.md ← repo 憲法範本(=現行 template/CLAUDE.md 演進)
|
||
│ │ └─ (SDD 三件式、repo wiki 規範:沿用 common 既有產物,manifest 宣告即可)
|
||
│ └─ orchestrator/
|
||
│ ├─ CLAUDE.md ← 總管憲法範本(藍圖 v3 指針+術語表)
|
||
│ ├─ docs/root.md.template
|
||
│ ├─ docs/journeys.md.template
|
||
│ ├─ docs/sprint.md.template ← 站號 sprint
|
||
│ ├─ docs/triage-map.md.template ← 格式範本,內容留實例
|
||
│ ├─ docs/plugin-load-order.md ← 載入順序文件(W3 插槽)
|
||
│ └─ hooks/orchestrator-scope-guard.sh ← 新增:S4(上收 guard-cross-project)
|
||
└─ system-dev/… ← 既有【原地不動】
|
||
|
||
實例安裝後:
|
||
CLAUDE.md ← 框架區(profile 範本)+ 本地補充區(界標分隔)
|
||
system-dev/.profile ← 單行:repo | orchestrator(scope 軸唯一來源)
|
||
system-dev/.template-manifest ← 每個產物的 path / version / sha256(漂移偵測依據)
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 設計答案①:雙 profile 的 CLAUDE.md 怎麼生成、本地補充區邊界、漂移怎麼偵測
|
||
|
||
### 2.1 生成:組裝而非下載
|
||
|
||
現行是「整份下載 `template/CLAUDE.md` + append raw source 區塊」。改為三段組裝:
|
||
|
||
```
|
||
<!-- sdt:framework begin profile=<repo|orchestrator> version=1.19.0 sha256=<前12碼> -->
|
||
(profiles/<profile>/CLAUDE.md 的完整內容,一字不改)
|
||
<!-- sdt:framework end -->
|
||
|
||
<!-- sdt:local begin — 這一區是你的,update 永遠不會動它 -->
|
||
(install 產生的 raw source 宣告;之後由使用者/CC 自由追加)
|
||
<!-- sdt:local end -->
|
||
```
|
||
|
||
- 界標用 HTML 註解:md 渲染看不見、CC 讀得到、`grep -n` 定位得到。
|
||
- `sha256` 記的是**框架區內容本身**(不含界標行),是漂移偵測的比對基準。
|
||
- 兩區順序固定:框架區在上(agent 先讀到憲法),本地區在下。
|
||
|
||
### 2.2 兩份 profile 憲法的內容分界(G4 的判準)
|
||
|
||
| | `profiles/repo/CLAUDE.md` | `profiles/orchestrator/CLAUDE.md` |
|
||
|---|---|---|
|
||
| 效忠文件 | `requirements.md`(SDD 技術軌) | `root.md` + `journeys.md`(PM 軌) |
|
||
| 含 SDD 三件式細節 | ✅ 含(現行內容) | ❌ **不含**(G4 明文:總管版無 SDD 三件式細節) |
|
||
| 上游指針 | ✅ 一行(指向總管 repo 的憲法位置,**不寫死專案名**,由 install 問一次或留空) | ❌ 無(它自己就是上游) |
|
||
| JDD 術語表 | 只放「站號怎麼標在 task 上」一段 | 全表(Journey/Station/通關/點亮/對帳/完備) |
|
||
| sprint 機制 | 「認領 loop」段(engineer 的動作) | 全流程(指定站 → 認領 → 新增必掛站 → 收尾判準) |
|
||
| 角色 | engineer(可寫 code/tasks/requirements/design;禁改考卷) | orchestrator(可寫 root/journeys/sprint;禁寫 code、禁改 tasks) |
|
||
|
||
> G4 的機械驗法:`grep -c "SDD 三件式\|requirements.md" CLAUDE.md`
|
||
> 在 orchestrator 實例上 = 0;`grep -c "上游" CLAUDE.md` 在 repo 實例上 ≥ 1。
|
||
|
||
### 2.3 邊界規則(寫進兩份憲法,並由 hook 兌現)
|
||
|
||
1. **框架區唯讀**:任何內容變更走框架 repo 提案 → bump → update 拉下來。
|
||
2. **本地補充區隨便寫**:update 永不讀、永不寫、永不比對。
|
||
3. 實例要覆寫框架區的某條規則 → 不准就地改,**在本地補充區寫「例外聲明 + 理由 + 日期」**。
|
||
這樣 diff 永遠乾淨,而例外仍然留痕可審。
|
||
|
||
### 2.4 漂移偵測:manifest + sha256
|
||
|
||
`system-dev/.template-manifest`(install 產生,update 維護),TSV 一行一產物:
|
||
|
||
```
|
||
<dest 路徑> <class> <安裝時 version> <安裝時 sha256>
|
||
```
|
||
|
||
`class` 沿用 update.sh 既有四類語意:`overwrite`(模板/邏輯檔)/`keep`(使用者資料檔)/
|
||
`add-if-missing`(新資料檔)/`keep-with-template`(使用者會手填的客製檔)。
|
||
|
||
update 對每個 `overwrite` 類產物跑三態判定:
|
||
|
||
| 實檔 sha vs manifest sha | 遠端 sha vs manifest sha | 判定 | 動作 |
|
||
|---|---|---|---|
|
||
| 相同 | 相同 | 沒變 | **no-op**(不下載、不列報表)← 兌現 EARS-1.2.2 |
|
||
| 相同 | 不同 | 乾淨、有新版 | 覆蓋,列「已更新」 |
|
||
| **不同** | 任意 | **漂移** | **不覆蓋**;新版另存 `<檔>.new`;列入 ⚠️ 漂移清單 |
|
||
|
||
漂移清單的輸出格式(白話,leo 讀得懂):
|
||
|
||
```
|
||
⚠️ 下列 3 個檔被手改過,這一版沒有覆蓋它們:
|
||
.claude/hooks/sdd-guard.sh → 新版已放在 sdd-guard.sh.new,請 diff
|
||
你可以:① 把你的改動寫成框架提案(推薦,一次修全家)
|
||
② 放棄本地改動:mv sdd-guard.sh.new sdd-guard.sh
|
||
```
|
||
|
||
**舊實例遷移(沒有 manifest 的 1.18.x)**:update 偵測到無 manifest → 進「一次性補植」:
|
||
以本版遠端內容為基準建 manifest,**凡當下與遠端不一致者一律先標成漂移**(保守:寧可多報不漏報),
|
||
並把現有 `CLAUDE.md` 整份包進 `sdt:local` 區、框架區從 profile 重鋪,
|
||
輸出「你的舊 CLAUDE.md 已完整保留在本地補充區,請自行搬移重複段落」。整段冪等,重跑不再動。
|
||
|
||
---
|
||
|
||
## 3. 設計答案②:AGENT_ROLE 兩軸身分在 hook 裡怎麼機械判定
|
||
|
||
### 3.1 兩個來源,零自陳
|
||
|
||
| 軸 | 來源 | 誰寫 | 讀不到時 |
|
||
|---|---|---|---|
|
||
| **scope** | `system-dev/.profile`(單行 `repo`/`orchestrator`) | install.sh(`--profile` 或偵測+人確認一次) | 視為 `repo`(多數實例;且此時 orchestrator 專屬閘不觸發,仍有 common 閘在) |
|
||
| **role** | 環境變數 `AGENT_ROLE`(`orchestrator`/`engineer`) | ① install 依 profile 寫進 `.claude/settings.json` 的 `env` 當預設<br>② 派工端 spawn subagent 時注入<br>③ 人工 override | **依 scope 推定**:orchestrator profile → `orchestrator`;repo profile → `engineer` |
|
||
|
||
> 為什麼 scope 不放 `settings.json`:settings.json 是「使用者資料檔」,update 永不覆蓋,
|
||
> 而且 CI/獨立腳本也要讀得到。`system-dev/.profile` 與 `VERSION` 同層,一致且好找。
|
||
|
||
### 3.2 身分矩陣(含那個不存在的格子)
|
||
|
||
| | `AGENT_ROLE=orchestrator` | `AGENT_ROLE=engineer` |
|
||
|---|---|---|
|
||
| **orchestrator profile** | PM 本尊 ✅ | 總管 repo 裡的技術 subagent ✅ |
|
||
| **repo profile** | **❌ 不存在** → exit 2,要求修正環境 | 寫 code 的 subagent ✅ |
|
||
|
||
「repo profile × orchestrator」被攔的訊息要說清楚:成員 repo 沒有 PM——
|
||
要 PM 的動作請回總管 repo 做(分離 §二「空格也是封路」)。
|
||
|
||
### 3.3 `lib/role-lib.sh`(common,被 source 不獨立掛)
|
||
|
||
```bash
|
||
sdt_repo_root() # 由 ${BASH_SOURCE} 往上找,不假設 CLAUDE_PROJECT_DIR(雲端可跑)
|
||
sdt_rel_path "$f" # 絕對/相對 → repo 相對路徑(抄 guard-cross-project 的 case 寫法)
|
||
sdt_scope() # 讀 system-dev/.profile,trim;讀不到回 repo
|
||
sdt_role() # 讀 $AGENT_ROLE;空 → 依 sdt_scope 推定
|
||
sdt_assert_identity() # 檢查矩陣空格,命中 → 印訊息 exit 2
|
||
sdt_file_path_from_stdin # 統一的 JSON 解析(jq → python3 → grep 三段 fallback)
|
||
```
|
||
|
||
**為什麼是函式庫不是 hook**:六支新 hook 都要做同樣四件事(解析 JSON、算相對路徑、判 scope、判 role)。
|
||
各寫一份=四處維護同一條規則,正是分離 §六.4 要避免的病。
|
||
|
||
### 3.4 為什麼角色 hook 屬 common,不按規格放進各自 profile
|
||
|
||
分離 §三把 `engineer 角色 hooks` 畫在 `profiles/repo/`、`orchestrator 角色 hooks` 畫在
|
||
`profiles/orchestrator/`。**照做會漏一格**:總管 repo 裡也會 spawn engineer subagent
|
||
(矩陣右上角),若 engineer 的封路 hook 只裝在 repo profile,那顆 subagent 在總管 repo 裡
|
||
**改得動 journeys.md** ⇒ G2「考生改考卷被攔截」在最該生效的地方失效。
|
||
|
||
⇒ 本設計改為:**role 軸的 hook 全部屬 common(兩 profile 都裝),scope 軸的 hook 才按 profile 分**。
|
||
唯一 scope 專屬的是 `orchestrator-scope-guard.sh`(總管禁入成員 repo 寫實作)。
|
||
|
||
---
|
||
|
||
## 4. 設計答案③:12 條規則落成哪些 hook(改既有 vs 新增)
|
||
|
||
> **規則數 ≠ 檔案數**。J1/J2/J3 都是「PreToolUse Write|Edit 依 role×path 判定」,
|
||
> 拆三支=三次解析同一包 JSON、三處維護同一張路徑表。合併為一支、規則編號保留在程式碼註解與訊息裡。
|
||
|
||
### 4.1 JDD §六 八條
|
||
|
||
| 規則 | 內容 | 落點 | 既有/新增 |
|
||
|---|---|---|---|
|
||
| J1 | orchestrator 寫 `src/**`、`*.py`、`*.ts`… → 攔 | `role-guard.sh` | **新增**(規格說「既有 hook 已涵蓋則跳過」——已確認 `pre-write-guard.sh` 是空殼且不認 role,**不涵蓋**) |
|
||
| J2 | orchestrator 寫 `tasks.md`/`requirements.md`/`design.md` → 攔 | `role-guard.sh` | 新增(同上) |
|
||
| J3 | engineer 寫 `journeys.md`/`root.md`/`*.feature`/md 內 Gherkin 區塊 → 攔(**命門**) | `role-guard.sh` | 新增 |
|
||
| J4 | `root.md` 🔴 卡缺【要驗證+對帳日】→ 攔 | `jdd-format-guard.sh` | 新增 |
|
||
| J5 | `tasks.md` **新增**的 task 缺站號 → 攔 | `jdd-format-guard.sh` | 新增 |
|
||
| J6 | sprint 收尾判準:tasks 全關 → **指定站 Gherkin 全過** | `station-done-guard.sh` | **新增**(規格標 `[修改]`,但 template 內**沒有**任何 sprint 收尾 hook——`delivery-police.sh` 只存在於 InkStoneCo 實例 ⇒ 對框架而言是新增,見決策 D6) |
|
||
| J7 | 站相關實作變動 → 查站點索引表 → 列重考清單 | `regression-scope.sh` | 新增(提醒不擋) |
|
||
| J8 | `root.md`/`journeys.md` 出現技術名詞 → 警告/攔 | `jdd-format-guard.sh` | 新增 |
|
||
|
||
### 4.2 分離 §六 四條
|
||
|
||
| 閘 | 內容 | 落點 | 既有/新增 |
|
||
|---|---|---|---|
|
||
| S1 | 實例寫入安裝產物區 → 攔;`--framework-dev` 例外 | `install-artifact-guard.sh` | 新增 |
|
||
| S2 | 框架範本混入實例專名 → CI fail | `scripts/check-no-instance-names.sh`(**非 hook**) | 新增 |
|
||
| S3 | update 漂移偵測 | `update.sh` + `.template-manifest` | **改既有腳本** |
|
||
| S4 | guard-cross-project 職責不重疊 | `profiles/orchestrator/hooks/orchestrator-scope-guard.sh` | **新增於框架**(實體改寫自實例那支,見 D6) |
|
||
|
||
### 4.3 既有檔改動清單
|
||
|
||
| 檔 | 改什麼 | 為什麼 |
|
||
|---|---|---|
|
||
| `template/.claude/hooks/session-start-recall.sh` | 依 `sdt_scope()` 分流注入:orchestrator → root/journeys 摘要 + 本 sprint **未點亮**站;repo → 現行 principles/status/mistakes | 唯一真正的「改既有 hook」;藍圖 §0「routine 起牀讀站號」的落點 |
|
||
| `scripts/install.sh` | 加 `--profile`、自動偵測+人確認、改讀 manifest、產 `.profile`/`.template-manifest`、CLAUDE.md 三段組裝、`build_hooks_json()` 加 profile 維度與 `env.AGENT_ROLE` | E1 主體 |
|
||
| `scripts/update.sh` | 改讀 manifest、三態判定、漂移報表、舊實例 marker 補植遷移 | S3 + EARS-1.2.2 |
|
||
| `template/CLAUDE.md` | 演進為 `template/profiles/repo/CLAUDE.md`;**原路徑保留一份轉址說明**,避免舊 update.sh 404 | D3 向下相容 |
|
||
| §0.5 (a) 類 6 行註解 | 改寫成通用敘述 | S2 前置清潔 |
|
||
|
||
**合計:新增 hook 6 支**(`role-guard`、`jdd-format-guard`、`station-done-guard`、
|
||
`regression-scope`、`install-artifact-guard`、`orchestrator-scope-guard`)
|
||
+ **共用函式庫 1 支**(`lib/role-lib.sh`,不獨立掛);
|
||
**改既有 hook 1 支**(`session-start-recall.sh`);**改既有腳本 2 支**(install/update);
|
||
**新增非 hook 腳本 1 支**(`check-no-instance-names.sh`)。
|
||
|
||
### 4.4 settings.json 掛載順序(install 依 profile 組裝)
|
||
|
||
```
|
||
SessionStart: session-start-recall.sh(profile 分流)
|
||
[W3 插槽:policy plugin 的 SessionStart hook 自動排在框架 hook 之後]
|
||
PreToolUse(Write|Edit|MultiEdit):
|
||
1. install-artifact-guard.sh ← 先擋「改機制」,最外層
|
||
2. role-guard.sh ← 再判角色
|
||
3. jdd-format-guard.sh ← 再驗格式
|
||
4. orchestrator-scope-guard.sh ← 僅 orchestrator profile
|
||
5. sdd-guard.sh(既有)
|
||
6. pre-write-guard.sh(既有空殼)
|
||
7. wiki-secret-scan.sh(既有)
|
||
PreToolUse(Grep|Glob|Read|Bash): wiki-first-search.sh(既有)
|
||
PreToolUse(Task): subagent-wiki-guard.sh(既有)
|
||
Stop / TaskCompleted: station-done-guard.sh、regression-scope.sh
|
||
```
|
||
|
||
順序原則:**範圍大的擋在前**(改機制 → 角色 → 格式),讓錯誤訊息指向最根本的那條規則。
|
||
|
||
---
|
||
|
||
## 5. W3 插槽(本波只留座位,不做 plugin)
|
||
|
||
分離 §五 載入順序原樣文件化到 `profiles/*/docs/plugin-load-order.md`:
|
||
|
||
```
|
||
SessionStart
|
||
1. profile 憲法(scope 軸:這個資料夾的 CLAUDE.md)
|
||
2. framework hooks 上鏈(common + profile)
|
||
3. 已安裝 policy plugin 注入(primer 推到眼前、白名單 hook 排入鏈尾、skill 就緒)
|
||
4. session-start-recall(wiki status 快照,現行機制)
|
||
```
|
||
|
||
框架要做的只有兩件(分離 §四明文):
|
||
1. 文件化「政策包= Claude Code 官方 plugin」的約定與載入順序——**框架不發明平行外掛格式**;
|
||
2. 在兩份 profile 憲法留 must-read 注入點(一段標題 + 一行說明「政策包的必讀會出現在這裡」),
|
||
讓 plugin 的 SessionStart hook 有地方推內容。
|
||
|
||
⚠️ **W3 動手前必讀官方文件**(`code.claude.com/docs/en/plugins.md`、`plugins-reference.md`),
|
||
不憑記憶寫 `plugin.json`。本 SDD **不**預先規定 plugin 的 schema。
|
||
|
||
---
|
||
|
||
## 關鍵決策
|
||
|
||
| # | 決策 | 選擇 | 原因 | 放棄的選項 |
|
||
|---|---|---|---|---|
|
||
| D1 | 安裝清單怎麼管 | **manifest TSV**(common/repo/orchestrator 各一份),install 與 update 共讀 | 現行清單硬編在兩支腳本裡已經重複;加 profile 會變四份必然漂移。manifest 讓「加一個產物」=加一行資料 | 繼續硬編(四份清單手動同步) |
|
||
| D2 | `common/`、`profiles/` 放哪 | 放 **`template/` 底下** | 所有安裝產物的遠端根是 `$TEMPLATE_SOURCE/template`;放 repo 根會開第二個 base URL,publish-exclude 與 mirror 規則也要跟著改。分離 §三的樹是示意(它把 install.sh 也畫在根,實際在 `scripts/`) | 照規格字面放 repo 根 |
|
||
| D3 | 既有檔要不要實體搬進 `common/` | **不搬**。既有檔原地不動,靠 manifest **宣告**它屬 common | `update.sh` 的自我更新在腳本尾端 ⇒ 舊實例跑的是舊腳本、路徑寫死,搬檔=這一輪整排 404。1.16.0 已經被「來源改動=自動更新死掉」咬過一次 | 照規格字面實體搬移(斷所有舊實例的更新) |
|
||
| D4 | 「本地補充區」怎麼劃 | **HTML 註解界標** + 框架區 sha256 | md 渲染不可見、grep 定位得到、CC 讀得到;sha 讓漂移可機械判定 | 靠檔尾約定(無邊界=無法偵測)/另開 `CLAUDE.local.md`(agent 不保證會讀) |
|
||
| D5 | 角色 hook 屬 common 還是各 profile | **屬 common** | 總管 repo 裡也會 spawn engineer;照規格分裝會讓那顆 subagent 改得動 journeys.md,G2 在最該生效處失效(§3.4) | 照規格分裝進各 profile |
|
||
| D6 | `guard-cross-project` 怎麼「修改」 | **上收進框架** `orchestrator-scope-guard.sh`,實例版於 W4 退役 | 它根本不在 template 裡,是實例自己發明的。就地改=在實例改機制=違反本案要立的第一條鐵律 | 在實例上就地改(自打嘴巴) |
|
||
| D7 | J1/J2/J3 三條規則的檔案數 | **合成一支 `role-guard.sh`** | 同一個 hook 事件、同一份身分判定、同一張路徑表;拆三支=三處維護一條規則 | 一條一支(規格字面「逐條實作」) |
|
||
| D8 | 閘 #2(實例專名)怎麼上線 | **腳本 + 逐行豁免標記 + 基線報表**,(a) 類本波清、(b)(c) 類標豁免待 W3 | 現況 8 檔 20 行命中;一上線全紅會逼出假性清理(把 arcrun 換成「某工作流引擎」=資訊沒了問題還在) | 硬上線(CI 立刻全紅)/先全部改寫(假性清理) |
|
||
| D9 | `--framework-dev` 怎麼實作 | **repo 根的 `.sdt-framework-dev` 檔**(框架 repo 自帶並 commit),輔以 env `SDT_FRAMEWORK_DEV=1` | Claude Code **沒有** `--framework-dev` 這個官方 flag(規格假設不成立)。用檔案=框架 repo 天生就有,零記憶負擔 | 造一個 CLI flag(做不到)/純靠環境變數(每次要記得設) |
|
||
| D10 | 本 SDD 的 status | **draft**,confirm 後升 active | D35 ②③;本 repo 現有 0 份 active,升活性不需搬移任何任務 | 直接寫 active(搶活性) |
|
||
|
||
---
|
||
|
||
## 技術限制
|
||
|
||
- 相容 macOS bash 3.2(`set -u` 下空陣列展開要先判長度——install.sh 既有踩過)。
|
||
- 不假設有 `jq`:JSON 解析走 `jq → python3 → grep` 三段 fallback(沿既有 hook 慣例)。
|
||
- 不假設 `CLAUDE_PROJECT_DIR` 存在:路徑一律由 `${BASH_SOURCE}` 往上推(雲端可跑)。
|
||
- hook 一律「解析失敗即放行」,寧可漏擋不誤殺;每支頂部寫誠實限制。
|
||
- 遠端檔案路徑(`$REPO_URL/...`)對既有實例是**契約**,本波只增不移。
|
||
|
||
---
|
||
|
||
## 驗收標準
|
||
|
||
以 requirements.md §四 的 **G1–G7** 為唯一驗收線。收工時每題必須附**實測輸出**,
|
||
狀態只有三種:`✅ 通(附證據)` / `◐ 半通(標明缺什麼)` / `❌ 斷`。
|
||
|
||
- G5 本波上限為 `◐ 半通`(插槽就位、政策包在 W3)——**不得標 ✅**。
|
||
- 另加兩條非功能驗收:
|
||
- 乾淨環境雙 profile 安裝各 < 10 分鐘(分離 §八 預測①,實測計時)
|
||
- `bash scripts/update.sh` 在 InkStoneCo 實例上跑出的漂移清單 = **4 支**(§0.4 基線,
|
||
對得上=偵測正確;對不上=偵測有偽陰/偽陽)
|
||
|
||
---
|
||
|
||
## 附錄 A:三個 marker 檔的格式規格(task 0.3)
|
||
|
||
| 檔 | 位置 | 內容 | 誰寫 | 誰讀 |
|
||
|---|---|---|---|---|
|
||
| `.profile` | 實例 `system-dev/.profile` | 單行:`repo` 或 `orchestrator`(前後空白會被 trim) | `install.sh --profile` | `role-lib.sh` 的 `sdt_scope()`、`update.sh`、CI |
|
||
| `.template-manifest` | 實例 `system-dev/.template-manifest` | TSV:`dest class version sha256`(安裝當下的雜湊) | install/update | `update.sh` 的三態判定 |
|
||
| `.sdt-framework-dev` | **框架 repo 根**(實例不該有) | 任意文字,存在即生效 | 框架 repo 自帶並 commit | `install-artifact-guard.sh` |
|
||
|
||
- 三者都是「機器可讀的事實」,不是設定選項——agent 不得靠它們表達意圖,只能讀。
|
||
- `.profile` 讀不到 → 視為 `repo`(代價寫在 `role-lib.sh` 註解裡,不假裝沒有)。
|
||
- `.sdt-framework-dev` 出現在實例裡 = 有人在關掉自己的封路,**git diff 看得見**。
|
||
|
||
---
|
||
|
||
## 附錄 B:本波實作的兩支機械閘(先於一切 task,防炸用)
|
||
|
||
| 閘 | 檔 | 擋什麼 | 實測 |
|
||
|---|---|---|---|
|
||
| 實例專名 | `scripts/check-no-instance-names.sh` + `scripts/instance-names.txt` | 框架範本混入實例專名 | 基線 22 行 → 處置後 0 違規/13 行檔級豁免;注入違規行可 fail 並指出檔案:行號 |
|
||
| 相容路徑 | `scripts/check-legacy-paths.sh` | 搬檔/改名導致舊實例 update 整排 404 | 追蹤 35 條已發佈路徑;移走 `template/CLAUDE.md` 可正確 fail |
|
||
|
||
> 為什麼這兩支要排在所有 task 之前(總管裁定):它們**炸的是既有的東西**,不是新功能沒做好。
|
||
> 專名閘晚做 → 之後每加一個範本檔都可能再混進實例名,債只會更大;
|
||
> 相容閘晚做 → Phase 1 一搬 `CLAUDE.md` 就把所有舊實例的自動更新弄死,而且**沒有下一次更新能修它**。
|
||
|
||
---
|
||
|
||
## 相關文件
|
||
|
||
- `requirements.md`(本卷)/`tasks.md`(本卷)
|
||
- `docs/3-specs/SDD-LIFECYCLE.md`(D35 生命週期)
|
||
- `docs/3-specs/install-layout/design.md`(安裝產物佈局的既有決策,本案沿用其「不污染用戶根目錄」原則)
|
||
- 上游提案:`InkStoneCo/system-dev/docs/3-specs/pending-changes.md` §三 W2
|
||
- 需求輸入:`~/Desktop/總管/{JDD-template-upgrade,分離導入規格,總管系統藍圖v3}.md`
|