@@ -1,7 +1,7 @@
# SDD × Gitea 治理規範
# SDD × Gitea 治理規範
```
```
version: 0.7 .0
version: 0.10 .0
status: 現行(本檔的修改依 §9 走 issue)
status: 現行(本檔的修改依 §9 走 issue)
scope: 所有安裝 ISEP 的環境(本機 CC 與雲端 CC)
scope: 所有安裝 ISEP 的環境(本機 CC 與雲端 CC)
distribution: 隨 ISEP 發佈,ISEP repo 是唯一編輯點(§11)
distribution: 隨 ISEP 發佈,ISEP repo 是唯一編輯點(§11)
@@ -9,6 +9,12 @@ lineage: v0.5.0 draft by claude.ai(存檔 _draft-claude-ai-v0.5.0.md)
→ v0.6.0 由總管對照現場實況修訂,逐條分歧見 DIVERGENCE-v0.5.0-to-v0.6.0.md
→ v0.6.0 由總管對照現場實況修訂,逐條分歧見 DIVERGENCE-v0.5.0-to-v0.6.0.md
→ v0.7.0 併入全局遍歷(14 repo/ 156 open/45 張管理票分六群,§13)與新舊票 mapping(§14);
→ v0.7.0 併入全局遍歷(14 repo/ 156 open/45 張管理票分六群,§13)與新舊票 mapping(§14);
依 leo 指正:規劃不另立文件,修正本檔就是新版
依 leo 指正:規劃不另立文件,修正本檔就是新版
→ v0.8.0 三源整合定案(§15):claude.ai 兩份規劃 × 舊票需求 × 既有機制,
逐領域對照、三處矛盾解法、動工順序——leo 2026-08-20 核准
→ v0.9.0 leo 裁定全面 PR-only(§8.1):所有 subagent、地端與雲端總管一律走 PR,
推翻總管原本「只有 ISEP 走 PR-only」的提案
→ v0.10.0 M4.0 補上「里程碑怎麼組成」(目標→遍歷票池→缺口才補票→定下不改);
M4.3 改寫成「定下就不改,尤其不准因為做不完而打折」——leo 2026-08-20 訂正
```
```
---
---
@@ -121,23 +127,36 @@ s/triage ──驗傷通過──▶ s/backlog ──排進 milestone──▶ s
## 4. Milestone 規則(= sprint = 可測試版本)
## 4. Milestone 規則(= sprint = 可測試版本)
- **M4.1** 命名 `vX.Y` ,必設 due date。
- **M4.0(怎麼組成一個 milestone) ** 🔴 **順序是:leo 給目標 → 遍歷票池找出「達成它需要哪些票」→
池裡沒有的才補開 → 定下,之後不改。**
```
leo 說要達成什麼
↓
總管遍歷 issues 池(跨 repo),找出達成這個目標需要關掉哪些票
↓
某一塊沒有票承接 ⇒ 那才是真缺口 ⇒ 補開一張(不是在里程碑裡編任務)
↓
組成定下 ← 從這裡開始,內容不再變動
` ``
- **不是**「總管想做什麼就放什麼」,也**不是**「先訂五件事再看做得完幾件」。
- 里程碑的名字寫**這個里程碑在做什麼事**,不是版本號(leo 2026-08-20)。
- **M4.1** 必設 due date。
- **M4.2** Deliverable = **一個可測的新版本號**。所有掛入 leaf 關閉後,可從預設分支打出通過驗收的版本。
- **M4.2** Deliverable = **一個可測的新版本號**。所有掛入 leaf 關閉後,可從預設分支打出通過驗收的版本。
- **M4.3** **Timebox 到期不自動關、不自動打 tag。 ** 到期強制做一次降 scope 對帳(未完成 leaf 搬下一 milestone)並通知 leo。
- **M4.3(定下就不改,尤其不准打折)** 🔴 **組成一經定下,不因為做不完而縮減。**
🔴 自動打 tag 會製造假交付——tag 永遠只在「驗過了」之後發生。
> leo 2026-08-20:「**milestone 確定後怎麼可以再把東西移除?定下工作自己刪掉是什麼意思?
> 根本就沒有什麼降**⋯⋯而不是要做 5 件事,做不到就改成 2 件,**自己打折**。」
- **Timebox 到期:不自動關、不自動打 tag、不把票移出。** 到期只做一件事:通知 leo。
- 做不完就是**還沒完成**,里程碑保持開著,百分比就顯示真實的完成度——
那個數字本來就是要拿來看「還差多少」的。把分母改小只是讓它說謊。
- 自動打 tag 會製造假交付——tag 永遠只在「驗過了」之後發生。
- ⚠️ 唯一的例外是**目標本身變了**(leo 改了要達成什麼)⇒ 那是重新走一次 M4.0,不是打折。
- **M4.4** Milestone 關閉 = release tag,一對一。「已交付」唯一合法形式是 **tag 存在且裝得起來**;打 tag 前置 open issues = 0(§8 E12)。
- **M4.4** Milestone 關閉 = release tag,一對一。「已交付」唯一合法形式是 **tag 存在且裝得起來**;打 tag 前置 open issues = 0(§8 E12)。
- **M4.5** Description 只寫版本目標一句 + tracking 連結。討論回 tracking issue。
- **M4.5** Description 只寫版本目標一句 + tracking 連結。討論回 tracking issue。
- **M4.7(降 scope 必須留痕)** 🔴 **把票移出里程碑,必須同時寫進該里程碑的 description。 **
格式:原本幾張、移走哪幾張、為什麼、移去哪裡。
- **禁的不是降 scope**——卡人閘時降 scope 就是 M4.3 要的。禁的是**降得無聲無息**。
- 為什麼寫在 description 不是寫在票裡:**leo 看的是百分比那個畫面**,
他不會為了確認 100% 是不是真的而去逐張點票。**痕跡要留在他會經過的地方。**
- 來由(2026-08-20 實犯):`v0.2.0` 本來 6 張,`#5` 卡人閘被移出 ⇒ 分母 6 變 5 ⇒ 顯示 100%。
leo 當場問「為什麼還有很多 issues 開放中」。
🔴 這與 §3.4(審核完沒關票 ⇒ 數字偏低)是**同一個病的兩面**:
畫面上的數字不等於實際狀態,而 leo 只看得到畫面。
- 機械閘:`inkstone/ISEP#24` 。
- **M4.6** 🔴 **release note 寫在 Gitea Releases 裡,不寫在 README。**( leo 2026-08-20:「release 不是寫在 readme,要放在 release 裡」)
- **M4.6** 🔴 **release note 寫在 Gitea Releases 裡,不寫在 README。**( leo 2026-08-20:「release 不是寫在 readme,要放在 release 裡」)
README 不得自行宣稱版本號——那會產生第二份會漂移的版本真相。
README 不得自行宣稱版本號——那會產生第二份會漂移的版本真相。
@@ -244,11 +263,11 @@ s/triage ──驗傷通過──▶ s/backlog ──排進 milestone──▶ s
| # | 封什麼路 | 用什麼封 | 現況 |
| # | 封什麼路 | 用什麼封 | 現況 |
|---|---|---|---|
|---|---|---|---|
| E1 | 直接 push 預設分支 | branch protection: PR-only | ◐ 現有 `main-and-prod-push-guard.sh` 語意相反( 擋 subagent、放行總管)。**本版只對 ISEP 開 PR-only**,見 §8.1 |
| E1 | 直接 push 預設分支 | branch protection: PR-only,**全部 repo、全部角色**(§8.1) | ◐ 現有 ` main-and-prod-push-guard.sh` 只 擋 subagent、放行總管 ⇒ **與本條不符,要改**:總管與雲端一律同擋 |
| E2 | PR 不關聯 issue | PR 模板必含 ` closes #`,缺漏即 fail | ❌ 待建 |
| E2 | PR 不關聯 issue | PR 模板必含 ` closes #`,缺漏即 fail | ❌ 待建 |
| E3 | 手動關 leaf | hook 攔截;只放行 ` close/*` 與 §6.2 人執路徑 | ❌ 待建 |
| E3 | 手動關 leaf | hook 攔截;只放行 ` close/*` 與 §6.2 人執路徑 | ❌ 待建 |
| E4 | 被 block 的票先關 | Gitea issue dependency | ✅ 平台原生,已驗證可用 |
| E4 | 被 block 的票先關 | Gitea issue dependency | ✅ 平台原生,已驗證可用 |
| E5 | Milestone 悄悄過期 | job:到期做降 scope 對帳 + 通知 ( **不自動關、不自動 打 tag** ) | ❌ 待建(先做成手動腳本,見 §8.2) |
| E5 | Milestone 悄悄過期 | job:到期只通知 leo ( **不自動關、不打 tag、不移票**, M4.3 ) | ❌ 待建(先做成手動腳本,見 §8.2) |
| E6 | 代理越過人閘 | PreToolUse hook | ◐ 現有 ` irreversible-dispatch-guard.sh`/ ` prod-write-guard.sh` 覆蓋一部分 |
| E6 | 代理越過人閘 | PreToolUse hook | ◐ 現有 ` irreversible-dispatch-guard.sh`/ ` prod-write-guard.sh` 覆蓋一部分 |
| E7 | SDD 內出現 leaf 連結 | pre-commit lint | ❌ 待建 |
| E7 | SDD 內出現 leaf 連結 | pre-commit lint | ❌ 待建 |
| E8 | Tracking issue 掛 milestone | job 摘除並告警 | ❌ 待建 |
| E8 | Tracking issue 掛 milestone | job 摘除並告警 | ❌ 待建 |
@@ -261,13 +280,24 @@ s/triage ──驗傷通過──▶ s/backlog ──排進 milestone──▶ s
| E15 | 代理硬做只有人能做的事 | 代理端無對應 tool 或 token;唯一出口是開 ` human/exec` 票 | ◐ 部分(credential 機制已擋一部分,D36) |
| E15 | 代理硬做只有人能做的事 | 代理端無對應 tool 或 token;唯一出口是開 ` human/exec` 票 | ◐ 部分(credential 機制已擋一部分,D36) |
| E16 | 標籤漂移 | 走 Gitea labels API 依 ` labels.yaml` 校正:缺的補、改的還原、多的**只告警不刪** | ❌ 待建(` ISEP#4 `) |
| E16 | 標籤漂移 | 走 Gitea labels API 依 ` labels.yaml` 校正:缺的補、改的還原、多的**只告警不刪** | ❌ 待建(` ISEP#4 `) |
### 8.1 E1 的適用範圍(裁決記錄 )
### 8.1 E1 的適用範圍:**全部 repo、全部角色,一律 PR-only**( leo 2026-08-20 裁定 )
**只 有 ISEP 走 PR- only;其他 repo 維持現行「總管推 main 自己裁」。 **
> leo 原話:「 **所 有 subagent 都是 PR only,在雲端地端總管所做的都是 PR-only。**」
- 理由:ISEP 壞掉 = **所有 session 一起壞 ** ,值得多一道摩擦;其他 repo 壞掉只影響自己。
` ``
- 一刀切到 14 個 repo 會在 leo 最忙的時候把整條線卡在「等總管開 PR」。
subagent(地端/雲端) → 只能推自己的分支,開 PR ← 擋
- 這條命中四題公式的「跨專案結構」⇒ 已記為裁決題(`DIVERGENCE` §E1),leo 可打回。
總管 (地端/雲端) → 只能推自己的分支,開 PR ← 擋
leo → 他自己的操作不在此限
` ``
🔴 **沒有例外,不分 repo。** 這條推翻了總管原本的判斷(原提案:只有 ISEP 走 PR-only,
其他 repo 維持「總管推 main 自己裁」,理由是怕卡住 leo)。
leo 直接裁定全面適用——**歷史紀錄留在 ` DIVERGENCE-v0.5.0-to-v0.6.0.md` §E1,該段已被本條推翻。**
**因此要改的**: ` main-and-prod-push-guard.sh` 目前的判準是
「` CLAUDE_CODE_CHILD_SESSION=1` 才擋」⇒ 只擋 subagent、放行總管,**與本條不符**。
改成不分角色一律擋推預設分支;` /tmp/.main-push-ok` 那套「總管看過」的戳記機制隨之作廢
(PR 的 review 就是那道確認,不需要第二套)。排在群 4「派工與交付紀律」。
### 8.2 所有 job 第一版都不依賴 Gitea Actions runner
### 8.2 所有 job 第一版都不依賴 Gitea Actions runner
@@ -582,6 +612,58 @@ leo 08-17 診斷見公理 3; 08-17 實測:文字層的閘 **8 次誤攔、0
---
---
## 15. 三源整合定案(leo 2026-08-20 核准)
三源=claude.ai 兩份規劃(v0.5.0 draft+ ` InkStoneCo#40 ` forge-discipline)× 45 張舊票 × 既有機制。
### 15.1 方向(四句,其餘全是它們的展開)
1. **機制只有一份。** 環境、標籤、規範、版本號都是單一真相源;本機與雲端裝同一個 plugin,沒有子集。
2. **閘封動作,不封措辭。** 伺服器端擋不可逆;手寫 hook 擋高代價動作(block);
hookify+ additionalContext 只提醒(永遠 warn);skill 管 regex 表達不了的判斷。
3. **一切要留痕、可量測。** 閘的動作、開場全局、里程碑百分比——leo 看到的畫面就是實況。
4. **進度=舊票關掉幾張。** 版本只是讓閘生效的載具,release note 寫明關了哪張票。
### 15.2 工作順序(六個里程碑,各 repo 同名,內容一經確定不增不減)
| 順 | 里程碑 | 來源票(由目標遍歷票池而來,M4.0) |
|---|---|---|
| 0 | 讓閘擋對東西 | ` InkStoneCo#56 #23 #22 #36 #55 #1 ` |
| 1 | 讓規則的效果看得見 | ` InkStoneCo#48 #17 #20 #18 `、` Arcrun#86 #142 #81 ` |
| 2 | 同一件事只留一份 | ` InkStoneCo#14 #57 #37 #11 `、` arcrun-rag#116 #123 #122 #88 `、` Arcrun#109 `、` system-dev-template#2 #3 ` |
| 3 | 不要卡在 leo 身上 | ` InkStoneCo#15 #38 #34 #31 #52 ` |
| 4 | 派工與交付紀律 | ` InkStoneCo#12 #28 `、` arcrun-rag#47 #124 `、` Arcrun#93 #143 #131 `、` system-dev-template#7 ` |
| 5 | 票與文件歸位 | ` InkStoneCo#9 #49 #10 #8 #5 `、` system-dev-template#1 #4 ` |
這六群的組成是照 M4.0 走出來的:leo 給的目標是「把管理這條線做對」,
總管遍歷 14 repo/ 156 張 open 票,挑出 44 張達成它需要關掉的,**沒有補開任何新任務票**。
排序判準=「什麼擋住什麼」:閘不修好,其他群的成果會被誤攔咬到;沒有留痕,修了也量不出變好。
` #40 ` 是憲法,貫穿全部;` #48 ` 是 ` #40 ` warn 校準的資料前提,所以排群 1 之首。
### 15.3 三處矛盾的定案
1. ` #40 `「hook 只減不增」vs E 清單要 ~11 支新閘 ⇒ 新封路優先做在**伺服器端**或 **warn 層**;
要新增 ` .sh` 必須在 PR 說明為何前兩層做不到。
2. warn-first vs hookify 的 warn 不進 AI ⇒ warn 一律用 ` additionalContext`;
新規則從 warn 出生,憑 ` #48 ` 的留痕數據才升 block。
3. milestone 到期自動關+打 tag ⇒ 否決(M4.3)。
### 15.4 待 leo 裁(不擋群 0 開工)
` #37 ` 兩份 repo 哪份為準(建議 ` inkstone/*`)/` #5 ` ` #8 ` 轉 hub 或作廢/
08-20 兩支未經 warn 期的 block 閘,隨盤點表(` docs/hooks-inventory.md`)一起審。
**已裁**: PR-only 全面適用(§8.1, 2026-08-20)。
### 15.5 跨 repo 的載體:ISEP 的六張群票
Gitea 的 milestone 只管得到同一個 repo,所以六個群在 ` inkstone/ISEP` 各有一張 ` hub` 票
( ` ISEP#30 `– ` #35 `),別的 repo 的舊票用 **Gitea 原生 dependency** 指到它
( leo 2026-08-20;跨 repo dependency 已實測可用)。
⇒ **群票的相依全關,群票才關得掉**;六張群票的狀態就是六群的進度。
---
## 附:Label 全集(快照;唯一真相為 ` labels.yaml`)
## 附:Label 全集(快照;唯一真相為 ` labels.yaml`)
` ``
` ``