Files
system-dev-template/docs/3-specs/jdd-dual-profile/design.md
T
Leo 6a49f25aef feat(W2 Phase 0-1): 雙 profile 地基+JDD 兩軸身分+兩支防炸閘
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>
2026-08-05 23:56:48 +08:00

446 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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**
> **狀態:active2026-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.mdjourneys.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 ⇒ 清單要分四份(commonrepoorchestrator/模組交叉),硬編必然漂移 → **必須先做 manifest** |
| `update.sh` 另有一份**幾乎重複**的清單(update_filekeep_fileadd_if_missingkeep_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 內容、
primerdispatch-checklist 搬遷、`.mcp.json`
- **實例側落地**W4):InkStoneCo 的 root.mdjourneys.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 | orchestratorscope 軸唯一來源)
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(可寫 codetasksrequirementsdesign;禁改考卷) | orchestrator(可寫 rootjourneyssprint;禁寫 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/.profiletrim;讀不到回 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.shprofile 分流)
[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-recallwiki 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 URLpublish-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.mdG2 在最該生效處失效(§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 §四 的 **G1G7** 為唯一驗收線。收工時每題必須附**實測輸出**,
狀態只有三種:`✅ 通(附證據)` / `◐ 半通(標明缺什麼)` / `❌ 斷`
- 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`(安裝當下的雜湊) | installupdate | `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`