shiftblame 0.4.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.claude/agents/{backend-engineer.md → DEV-be.md} +5 -5
  2. package/.claude/agents/DEV-db.md +78 -0
  3. package/.claude/agents/{frontend-engineer.md → DEV-fe.md} +5 -5
  4. package/.claude/agents/{feature-developer.md → DEV.md} +30 -28
  5. package/.claude/agents/MIS-cicd.md +140 -0
  6. package/.claude/agents/MIS-cloud.md +100 -0
  7. package/.claude/agents/MIS-infra.md +107 -0
  8. package/.claude/agents/MIS.md +132 -0
  9. package/.claude/agents/PRD-arch.md +71 -0
  10. package/.claude/agents/PRD-market.md +70 -0
  11. package/.claude/agents/PRD-plan.md +75 -0
  12. package/.claude/agents/PRD.md +109 -0
  13. package/.claude/agents/{e2e-test-engineer.md → QA-e2e.md} +5 -5
  14. package/.claude/agents/{integration-test-engineer.md → QA-integ.md} +5 -5
  15. package/.claude/agents/{unit-test-engineer.md → QA-unit.md} +5 -5
  16. package/.claude/agents/{quality-assurance.md → QA.md} +26 -24
  17. package/.claude/agents/QC-test.md +96 -0
  18. package/.claude/agents/QC-uni.md +108 -0
  19. package/.claude/agents/QC-user.md +131 -0
  20. package/.claude/agents/QC.md +138 -0
  21. package/.claude/agents/SEC-blue.md +112 -0
  22. package/.claude/agents/SEC-red.md +90 -0
  23. package/.claude/agents/SEC-white.md +103 -0
  24. package/.claude/agents/SEC.md +164 -0
  25. package/.claude/agents/SECRETARY.md +44 -0
  26. package/.claude/commands/secretary.md +9 -0
  27. package/.claude/skills/blame-init/SKILL.md +135 -0
  28. package/.claude/skills/blame-reflect/SKILL.md +87 -0
  29. package/README.md +91 -130
  30. package/package.json +3 -2
  31. package/scripts/postinstall.js +72 -8
  32. package/scripts/preuninstall.js +81 -0
  33. package/.claude/agents/administrative-clerk.md +0 -132
  34. package/.claude/agents/audit-reviewer.md +0 -164
  35. package/.claude/agents/infra-engineer.md +0 -66
  36. package/.claude/agents/operations-engineer.md +0 -140
  37. package/.claude/agents/product-planner.md +0 -77
  38. package/.claude/agents/project-manager.md +0 -79
  39. package/.claude/agents/quality-control.md +0 -120
  40. package/.claude/agents/system-architect.md +0 -81
  41. package/.claude/commands/shiftblame-link.md +0 -34
  42. package/.claude/commands/shiftblame-reflect.md +0 -80
  43. package/.claude/skills/secretary/SKILL.md +0 -417
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- # 🍲 shiftblame
3
+ # shiftblame
4
4
 
5
5
  ### 推鍋
6
6
 
@@ -8,7 +8,7 @@ _一套明確責任歸屬的 Agents 開發框架_
8
8
 
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
10
10
  [![Claude Code](https://img.shields.io/badge/Claude%20Code-compatible-8a2be2.svg)](https://claude.com/claude-code)
11
- [![Agents](https://img.shields.io/badge/agents-8-blue.svg)](#這是什麼)
11
+ [![Agents](https://img.shields.io/badge/agents-25-blue.svg)](#資源供給機制)
12
12
  [![Language](https://img.shields.io/badge/lang-繁體中文-red.svg)](#)
13
13
 
14
14
  > _「這不是我的鍋。」_
@@ -19,51 +19,44 @@ _一套明確責任歸屬的 Agents 開發框架_
19
19
 
20
20
  ---
21
21
 
22
- 需求從企劃推到上線,經過 **8 層 agent**。每一層該誰負責、出了事該找誰,白紙黑字記在鍋紀錄裡。
22
+ 秘書動態掃描 agents 目錄,把正確的需求推給正確的部門。每個部門該誰負責、出了事該找誰,白紙黑字記在鍋紀錄裡。
23
23
 
24
24
  還沒想清楚?秘書也能幫你**釐清方向**——用結構化問答收斂需求,確認後再推鍋。
25
25
 
26
26
  ---
27
27
 
28
- ## 誰的鍋?
28
+ ## 資源供給機制
29
29
 
30
- 每個角色都有自己的 `~/.shiftblame/blame/<role>/BLAME.md`,犯錯就記,下次避雷。
30
+ 模型分配**按角色類型**,不按部門層級:
31
31
 
32
- ### 老闆的鍋
32
+ | 角色類型 | 模型 | 對象 |
33
+ |----------|------|------|
34
+ | **管理層**(調度 sub-agent) | **haiku** | PRD、DEV、QA、QC、SEC、MIS |
35
+ | **執行職級**(sub-agent) | **sonnet**(預設)/**opus**(複雜度 ≥ 80) | 由主管按任務複雜度分配 |
33
36
 
34
- | 情境 | 為什麼是老闆的鍋 | 回退方式 |
35
- |------|------------------|----------|
36
- | **大環境問題**:缺 API key、缺套件、缺權限⋯⋯ | 「換更強的 agent 在同一環境裡做得了嗎?」做不了 = 環境的鍋 = 老闆的鍋 | 老闆補好環境條件後重啟 |
37
- | **老闆明示直接修改**:親口說「直接改」「不用跑流程」 | 老闆下令跳過流程,改壞了自己扛。commit 以 `BOSS-HOTFIX:` 為前綴 | `git revert` |
37
+ ---
38
+
39
+ ## 誰的鍋
40
+
41
+ 每個部門都有自己的 `~/.shiftblame/blame/<DEPT>/<role>/BLAME.md`,犯錯就記,下次避雷。
38
42
 
39
43
  ### 秘書的鍋
40
44
 
41
45
  | 情境 | 為什麼是秘書的鍋 |
42
46
  |------|------------------|
43
- | **判鍋判錯**:起點層判斷錯誤導致重工 | 秘書職責就是判斷該從哪層開始 |
44
- | **退回判錯**:老闆說不 OK 時退回了錯誤的層 | 秘書職責就是判斷根因在哪層 |
45
- | **合併出包**:rebase / merge --squash 過程出錯 | 合併是秘書親自執行的 |
47
+ | **路由判錯**:需求推給了錯誤的部門導致重工 | 秘書職責就是判斷該推給誰 |
48
+ | **退回判錯**:老闆說不 OK 時推給了錯誤的部門 | 秘書職責就是判斷根因在哪 |
46
49
 
47
- ### 各層 agent 的鍋
50
+ ### 各部門的鍋
48
51
 
49
- | 角色 | 典型犯錯情境 |
52
+ | 部門 | 典型犯錯情境 |
50
53
  |------|-------------|
51
- | `product-planner` | PRD 漏掉老闆明確提到的需求、自作主張加了老闆沒說的東西 |
52
- | `system-architect` | 技術選型不可行、模組拆分導致後續無法實作 |
53
- | `project-manager` | 驗收條件與 PRD 矛盾、任務依賴排錯導致卡關 |
54
- | `quality-assurance` | **測試主管**:拆分任務不當導致測試工程師產出衝突、收合不完整、測試涵蓋度不足。底下三個測試工程師(unit-test-engineer / integration-test-engineer / e2e-test-engineer)的鍋也由主管扛 |
55
- | `feature-developer` | **開發主管**:拆分任務不當導致職能工程師產出衝突、收合不完整、實作偏離 spec、引入新 bug。底下三個職能工程師(frontend-engineer / backend-engineer / infra-engineer)的鍋也由主管扛 |
56
- | `quality-control` | e2e 場景遺漏關鍵流程、環境設定錯誤導致假綠 |
57
- | `audit-reviewer` | 該抓的沒抓到(放水)、退回理由不具體導致重工 |
58
- | `operations-engineer` | 部署步驟與 dag 不符、上線後 smoke test 沒跑或漏驗 |
59
-
60
- ### 行政文書的鍋
61
-
62
- | 情境 | 為什麼是行政文書的鍋 |
63
- |------|---------------------|
64
- | **聚合遺漏**:未掃描到應聚合的舊文件 | 文件聚合是行政文書唯一職責 |
65
- | **REPO.md 格式錯亂**:聚合後內容不完整或格式錯誤 | 聚合品質由行政文書負責 |
66
- | **誤刪 STM 檔案**:刪掉應保留的最新 3 筆 | 保留規則由行政文書執行 |
54
+ | PRD | PRD 漏掉老闆明確提到的需求、自作主張加了老闆沒說的東西、技術選型不可行、市調資料不準確 |
55
+ | QA | 測試涵蓋度不足、拆分任務不當導致測試工程師產出衝突 |
56
+ | DEV | 實作偏離 spec、引入新 bug、拆分任務不當導致工程師產出衝突 |
57
+ | QC | 品管場景遺漏關鍵流程、邊緣/模糊測試不足、一致性遺漏、使用者體驗問題 |
58
+ | SEC | 該抓的沒抓到(放水)、退回理由不具體、安全掃描遺漏 |
59
+ | MIS | 環境盤點遺漏、CI/CD pipeline 配置錯誤、合併出包、部署失敗 |
67
60
 
68
61
  ---
69
62
 
@@ -82,103 +75,78 @@ _一套明確責任歸屬的 Agents 開發框架_
82
75
  明確 不確定
83
76
  │ │
84
77
  ▼ ▼
85
- 老闆原話 ┌──────────────────────┐
86
- 留底 │ 諮詢模式 │
87
- │ 結構化問答釐清方向 │
88
- │ 確認後才推鍋 │
89
- └──────────┬───────────┘
90
-
91
- │◄──────────────────────┘
78
+ 原話留底 ┌──────────────────────┐
79
+ │ 諮詢模式 │
80
+ │ 結構化問答釐清方向 │
81
+ │ 確認後才推鍋 │
82
+ └──────────┬───────────┘
83
+
84
+ │◄─────────────────────┘
92
85
 
93
86
  ┌─────────────────────────┐
94
- 秘書預審(翻成人話) │ 每一層啟動前都要過這個閘門
95
- 老闆只回「OK / 不 OK」 老闆不需要知道內部有哪些角色
87
+ 掃描 agents 目錄 │
88
+ 判斷該推給哪個部門
96
89
  └─────────┬───────────────┘
97
90
 
98
91
  ┌───────┴────────┐
99
92
  │ │
100
- OK 不 OK(附原因或新需求)
93
+ 預審 OK 不 OK
101
94
  │ │
102
95
  ▼ ▼
103
- 啟動該層 秘書判斷根因在哪層,
104
- agent 把鍋丟給正確的人重跑
105
-
96
+ 啟動該部門 秘書判斷根因,
97
+ │ 推給正確的部門重做
106
98
 
107
- 產出文件 + commit
99
+ 收回傳 判斷下一步
108
100
 
109
101
 
110
- 下一層繼續(再走一次預審)
102
+ 完成 → 秘書對照原話 → 呈報老闆
111
103
  ```
112
104
 
113
- ### 8 層推鍋鏈
105
+ ### 需求路由
106
+
107
+ 秘書根據需求性質,把鍋推給對的部門:
114
108
 
115
- | # | 角色 | 產出 | 主要工作 |
116
- |---|------|------|---------|
117
- | 1 | product-planner | prd | 把老闆原話轉 PRD |
118
- | 2 | system-architect | dag | 技術選型、模組拓撲、檔案結構、介面簽章、部署方案 |
119
- | 3 | project-manager | spec | 功能拆解、驗收條件、任務依賴 |
120
- | 4 | quality-assurance | basis | dag + spec 寫測試(TDD 紅)= **QA 定規則** |
121
- | 5 | feature-developer | devlog | 寫最小實作讓測試全綠(TDD 綠) |
122
- | 6 | quality-control | e2e | 使用者視角 e2e 測試並實際執行 = **QC 依規則驗收** |
123
- | 7 | audit-reviewer | audit | 整條鏈路驗收,回傳 ACCEPTED / REJECTED |
124
- | 8 | operations-engineer| ops | main 依 dag 方案實際上線 |
109
+ | 需求性質 | 推給 |
110
+ |---|---|
111
+ | 全新功能 / 方向性變更 | PRD |
112
+ | 技術選型 / 工具比較 | PRD |
113
+ | 架構調整 / 技術遷移 | PRD |
114
+ | 加細節 / 改驗收條件 | DEV |
115
+ | 測試不足 / 要補測試 | QA |
116
+ | 已知 bug / 程式修正 | DEV |
117
+ | 使用者體驗問題 | QC |
118
+ | 部署 / 上線方式調整 | MIS |
119
+ | 環境 / 工具問題 | MIS |
120
+ | CI/CD / 自動化調整 | MIS |
125
121
 
126
- **行政文書**不參與 8 層鏈路,只在鏈路完成後進行文件聚合。
122
+ 全新功能的典型路徑:`PRD MIS(環境) → QA → DEV → QC → SEC(安全) → MIS(合併+部署)`,但秘書可依實際需求動態跳過或新增步驟。
127
123
 
128
124
  ### 檔案結構
129
125
 
130
126
  ```
131
- ~/.shiftblame/ # 推鍋產物(家目錄)
132
- ├── blame/ # 鍋紀錄(所有 repo 共用)
133
- │ ├── product-planner/BLAME.md
134
- │ ├── system-architect/BLAME.md
135
- │ ├── project-manager/BLAME.md
136
- │ ├── quality-assurance/
137
- ├── BLAME.md # 測試主管的鍋
138
- ├── unit-test-engineer/BLAME.md # 單元測試工程師的鍋
139
- │ ├── integration-test-engineer/BLAME.md # 整合測試工程師的鍋
140
- │ │ └── e2e-test-engineer/BLAME.md # E2E 測試工程師的鍋
141
- │ ├── feature-developer/
142
- │ │ ├── BLAME.md # 開發主管的鍋
143
- │ │ ├── frontend-engineer/BLAME.md # 前端工程師的鍋
144
- │ │ ├── backend-engineer/BLAME.md # 後端工程師的鍋
145
- │ │ └── infra-engineer/BLAME.md # 基建工程師的鍋
146
- │ ├── quality-control/BLAME.md
147
- │ ├── audit-reviewer/BLAME.md
148
- │ ├── operations-engineer/BLAME.md
149
- │ ├── secretary/BLAME.md
150
- │ ├── administrative-clerk/BLAME.md # 行政文書的鍋
151
- │ └── boss/BLAME.md
152
- └── <repo>/ # 每個 repo 各自一個目錄
153
- ├── docs/
154
- │ ├── prd/<slug>.md
155
- │ ├── dag/<slug>.md
156
- │ ├── spec/<slug>.md
157
- │ ├── basis/<slug>/
158
- │ │ ├── unit/... # unit-test-engineer 產出
159
- │ │ ├── integration/... # integration-test-engineer 產出
160
- │ │ └── e2e/... # e2e-test-engineer 產出
161
- │ ├── devlog/<slug>/
162
- │ │ ├── frontend/... # frontend-engineer 產出
163
- │ │ ├── backend/... # backend-engineer 產出
164
- │ │ └── infra/... # infra-engineer 產出
165
- │ ├── e2e/<slug>.md
166
- │ ├── audit/<slug>.md
167
- │ └── ops/<slug>.md
168
- ├── REPO.md # 文件聚合檔(長期記憶)
169
- └── report/
170
- └── <YYYY-MM-DD_HHMMSS>-<slug>.md # 秘書最終對照報告
171
-
172
- ~/.worktree/ # 共享 worktree(家目錄)
127
+ ~/.shiftblame/
128
+ ├── blame/ # 鍋紀錄(所有 repo 共用)
129
+ │ ├── DEV/{fe,be,db}/BLAME.md
130
+ │ ├── MIS/{infra,cicd,cloud}/BLAME.md
131
+ │ ├── PRD/{plan,arch,market}/BLAME.md
132
+ │ ├── QA/{unit,integ,e2e}/BLAME.md
133
+ │ ├── QC/{test,uni,user}/BLAME.md
134
+ │ ├── SEC/{red,white,blue}/BLAME.md
135
+ └── SECRETARY/BLAME.md
173
136
  └── <repo>/
174
- └── <slug>/ # 前 7 棒的工作目錄
137
+ ├── {MIS}/<slug>.md
138
+ ├── {DEV,QA}/<slug>.md
139
+ ├── {PRD,QC,SEC}/<slug>.md
140
+ └── REPO.md
175
141
 
176
- <repo>/ # repo 根目錄
177
- ├── .shiftblame/ # symlink 目錄
178
- │ ├── <repo> → ~/.shiftblame/<repo>/ # 本 repo 的 docs + report
179
- │ └── blame → ~/.shiftblame/blame/ # 鍋紀錄
142
+ ~/.worktree/<repo>/<slug>/ # 共享 worktree
143
+
144
+ <repo>/
145
+ ├── .shiftblame/ # symlink 目錄
146
+ │ ├── <repo> → ~/.shiftblame/<repo>/
147
+ │ └── blame → ~/.shiftblame/blame/
180
148
  └── .worktree/
181
- └── <slug> → ~/.worktree/<repo>/<slug>/ # worktree symlink
149
+ └── <slug> → ~/.worktree/<repo>/<slug>/
182
150
  ```
183
151
 
184
152
  ---
@@ -198,21 +166,21 @@ cd /path/to/your/project
198
166
  npm install shiftblame
199
167
  ```
200
168
 
201
- ### 初始化鍋目錄
169
+ ### 初始化
202
170
 
203
171
  安裝完成後,在目標 repo 中執行:
204
172
 
205
173
  ```
206
- /shiftblame-link
174
+ /blame-init
207
175
  ```
208
176
 
209
- 這會在 repo 根目錄建立 `.shiftblame/` symlink 指向 `~/.shiftblame/`。每個要用推鍋的 repo 都要跑一次。
177
+ 建立 `~/.shiftblame/` 完整目錄結構、repo symlink、檢查 `.gitignore`。秘書在首次推鍋時也會自動偵測並執行初始化。
210
178
 
211
179
  ---
212
180
 
213
181
  ## 使用
214
182
 
215
- 在 Claude Code 中用 `/secretary` 顯式呼叫秘書:
183
+ 在 Claude Code 中用 `/secretary` 呼叫秘書:
216
184
 
217
185
  ```
218
186
  /secretary 幫我做一個 Markdown 轉 HTML 的 CLI
@@ -222,43 +190,36 @@ npm install shiftblame
222
190
  /secretary 我想要一個可以記錄每日心情的終端小工具
223
191
  ```
224
192
 
225
- 還沒想清楚?也可以直接跟秘書諮詢:
193
+ 還沒想清楚?也可以諮詢:
226
194
 
227
195
  ```
228
196
  /secretary 我在猶豫要用 REST 還是 GraphQL,你覺得呢
229
197
  ```
230
198
 
231
- ```
232
- /secretary 我不確定這個功能該怎麼做比較好,幫我想想
233
- ```
234
-
235
199
  秘書會用結構化問答幫你收斂方向,確認後才開始推鍋。
236
200
 
237
201
  ### 聚合鍋紀錄
238
202
 
239
- 當各角色的 BLAME.md 累積了一定歷史後,可以執行:
203
+ 當各部門的 BLAME.md 累積了一定歷史後,可以執行:
240
204
 
241
205
  ```
242
- /shiftblame-reflect
206
+ /blame-reflect
243
207
  ```
244
208
 
245
- 這會掃描所有角色的鍋紀錄,將「下次怎麼避免」提煉成**常識(規則)**,將「背後的機制」與「為什麼這條規則有效」提煉成**認知(模型)**,重新寫回各角色的 BLAME.md 檔頭。
246
-
247
- > **為什麼要用 `/secretary`?** Claude Code 不保證每次都能正確判斷該觸發哪個 skill,顯式呼叫最可靠。
209
+ 掃描所有部門的鍋紀錄,將「下次怎麼避免」提煉成**常識(規則)**,將「背後的機制」提煉成**認知(模型)**,寫回各 BLAME.md 檔頭。
248
210
 
249
- 秘書接手後會:
211
+ ### 秘書接手後
250
212
 
251
- 1. 保存你的**原話逐字稿**
252
- 2. 取 slug、建立共享 worktree
253
- 3. 在每一層啟動前先用人話告訴你「接下來要做的事」,你回 OK 才繼續
254
- 4. 全鏈路跑完後,親自對照原話產出**秘書最終確認報告**
255
- 5. 呈報「完全達成 X / 部分達成 Y / 未達成 Z」
256
- 6. 通知**行政文書**進行文件聚合——各部門目錄只保留最新 3 筆,其餘聚合至 `REPO.md`
213
+ 1. 掃描 `.claude/agents/` 取得可用部門清單
214
+ 2. 保存你的**原話逐字稿**
215
+ 3. 每個部門啟動前先用人話告訴你「接下來要做的事」,你回 OK 才繼續
216
+ 4. 完成後親自對照原話,呈報「完全達成 X / 部分達成 Y / 未達成 Z」
217
+ 5. 完成後親自執行文件聚合
257
218
 
258
219
  你在過程中只需要:
259
220
 
260
- - **OK**:繼續推
261
- - **不 OK + 原因**:秘書會自己判斷該退回哪一層重跑
221
+ - **OK**:繼續推
222
+ - **不 OK + 原因**:秘書會判斷該推給哪個部門重做
262
223
 
263
224
  ---
264
225
 
@@ -266,4 +227,4 @@ npm install shiftblame
266
227
 
267
228
  > _「倉庫已經發出來了,接下來怎麼用就不是我的鍋了。」_
268
229
 
269
- MIT
230
+ MIT
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "shiftblame",
3
- "version": "0.4.0",
3
+ "version": "1.2.0",
4
4
  "description": "推鍋",
5
5
  "scripts": {
6
- "postinstall": "node scripts/postinstall.js"
6
+ "postinstall": "node scripts/postinstall.js",
7
+ "preuninstall": "node scripts/preuninstall.js"
7
8
  },
8
9
  "license": "MIT",
9
10
  "repository": {
@@ -1,26 +1,81 @@
1
1
  const { execSync } = require("child_process");
2
2
  const path = require("path");
3
3
  const fs = require("fs");
4
+ const pkg = require("../package.json");
4
5
 
5
6
  const src = path.join(__dirname, "..", ".claude");
7
+ const MANIFEST_NAME = ".shiftblame-manifest.json";
8
+
6
9
  const isGlobal = Boolean(
7
10
  process.env.npm_config_global === "true" ||
8
11
  process.env.npm_config_prefix ===
9
12
  execSync("npm config get prefix -g", { encoding: "utf8" }).trim()
10
13
  );
11
14
 
15
+ let dest, installType;
12
16
  if (isGlobal) {
13
- const dest = path.join(require("os").homedir(), ".claude");
14
- copyRecursive(src, dest);
15
- console.log("shiftblame: user 級別安裝完成 → ~/.claude/");
17
+ dest = path.join(require("os").homedir(), ".claude");
18
+ installType = "global";
16
19
  } else {
17
- const projectDir = findProjectRoot(process.cwd());
18
- const dest = path.join(projectDir, ".claude");
19
- copyRecursive(src, dest);
20
- console.log(`shiftblame: repo 級別安裝完成 → ${dest}/`);
20
+ dest = path.join(findProjectRoot(process.cwd()), ".claude");
21
+ installType = "local";
22
+ }
23
+
24
+ // 1. 讀舊 manifest
25
+ const manifestPath = path.join(dest, MANIFEST_NAME);
26
+ let oldFiles = [];
27
+ if (fs.existsSync(manifestPath)) {
28
+ try {
29
+ const old = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
30
+ oldFiles = old.files || [];
31
+ } catch (_) {}
32
+ }
33
+
34
+ // 2. 掃描 src 建立新檔案清單
35
+ const newFiles = [];
36
+ function collectFiles(dir, base) {
37
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
38
+ const rel = base ? base + "/" + entry.name : entry.name;
39
+ if (entry.name === MANIFEST_NAME) continue;
40
+ if (entry.isDirectory()) {
41
+ collectFiles(path.join(dir, entry.name), rel);
42
+ } else {
43
+ newFiles.push(rel);
44
+ }
45
+ }
46
+ }
47
+ if (fs.existsSync(src)) {
48
+ collectFiles(src, "");
49
+ }
50
+
51
+ // 3. 清理舊版殘留(在新版中已不存在的檔案)
52
+ const newSet = new Set(newFiles);
53
+ const staleFiles = oldFiles.filter((f) => !newSet.has(f));
54
+ for (const file of staleFiles) {
55
+ const filePath = path.join(dest, file);
56
+ if (fs.existsSync(filePath)) {
57
+ fs.unlinkSync(filePath);
58
+ }
21
59
  }
60
+ // 清理空目錄
61
+ cleanEmptyDirs(dest, dest);
62
+
63
+ // 4. 複製新檔案
64
+ copyRecursive(src, dest);
22
65
 
23
- console.log("shiftblame: 請在目標 repo 中執行 /shiftblame-link 初始化鍋目錄");
66
+ // 5. 寫入新 manifest
67
+ const manifest = { version: pkg.version, type: installType, files: newFiles };
68
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
69
+
70
+ // 6. 輸出
71
+ const label = isGlobal ? "user 級別" : "repo 級別";
72
+ console.log(`shiftblame: ${label}安裝完成 → ${dest}/`);
73
+ if (staleFiles.length > 0) {
74
+ console.log(`shiftblame: 已清除 ${staleFiles.length} 個舊版殘留檔案`);
75
+ }
76
+ console.log("shiftblame: 請在目標 repo 中執行 /blame-init 初始化鍋目錄");
77
+
78
+ // --- helpers ---
24
79
 
25
80
  function findProjectRoot(dir) {
26
81
  let current = dir;
@@ -49,3 +104,12 @@ function copyRecursive(src, dest) {
49
104
  fs.copyFileSync(src, dest);
50
105
  }
51
106
  }
107
+
108
+ function cleanEmptyDirs(dir, stopAt) {
109
+ if (dir === stopAt || !fs.existsSync(dir)) return;
110
+ const entries = fs.readdirSync(dir);
111
+ if (entries.length === 0) {
112
+ fs.rmdirSync(dir);
113
+ cleanEmptyDirs(path.dirname(dir), stopAt);
114
+ }
115
+ }
@@ -0,0 +1,81 @@
1
+ const { execSync } = require("child_process");
2
+ const path = require("path");
3
+ const fs = require("fs");
4
+
5
+ const MANIFEST_NAME = ".shiftblame-manifest.json";
6
+
7
+ const isGlobal = Boolean(
8
+ process.env.npm_config_global === "true" ||
9
+ process.env.npm_config_prefix ===
10
+ execSync("npm config get prefix -g", { encoding: "utf8" }).trim()
11
+ );
12
+
13
+ let dest;
14
+ if (isGlobal) {
15
+ dest = path.join(require("os").homedir(), ".claude");
16
+ } else {
17
+ dest = path.join(findProjectRoot(process.cwd()), ".claude");
18
+ }
19
+
20
+ const manifestPath = path.join(dest, MANIFEST_NAME);
21
+
22
+ if (!fs.existsSync(manifestPath)) {
23
+ console.log("shiftblame: 未找到安裝紀錄,跳過清理");
24
+ process.exit(0);
25
+ }
26
+
27
+ let manifest;
28
+ try {
29
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
30
+ } catch (_) {
31
+ console.log("shiftblame: 安裝紀錄損毀,跳過清理");
32
+ process.exit(0);
33
+ }
34
+
35
+ // 1. 刪除所有追蹤的檔案
36
+ let removed = 0;
37
+ for (const file of manifest.files || []) {
38
+ const filePath = path.join(dest, file);
39
+ if (fs.existsSync(filePath)) {
40
+ fs.unlinkSync(filePath);
41
+ removed++;
42
+ }
43
+ }
44
+
45
+ // 2. 清理空目錄(不刪除 .claude/ 本身)
46
+ cleanEmptyDirs(dest, dest);
47
+
48
+ // 3. 刪除 manifest
49
+ if (fs.existsSync(manifestPath)) {
50
+ fs.unlinkSync(manifestPath);
51
+ }
52
+
53
+ console.log(`shiftblame: 已移除 ${removed} 個檔案`);
54
+ console.log("shiftblame: 反安裝完成(已保留 .claude/ 目錄)");
55
+
56
+ // --- helpers ---
57
+
58
+ function findProjectRoot(dir) {
59
+ let current = dir;
60
+ while (current !== path.dirname(current)) {
61
+ if (
62
+ fs.existsSync(path.join(current, "package.json")) &&
63
+ current !== path.join(__dirname, "..")
64
+ ) {
65
+ return current;
66
+ }
67
+ current = path.dirname(current);
68
+ }
69
+ return process.env.INIT_CWD || process.cwd();
70
+ }
71
+
72
+ function cleanEmptyDirs(dir, stopAt) {
73
+ if (dir === stopAt || !fs.existsSync(dir)) return;
74
+ try {
75
+ const entries = fs.readdirSync(dir);
76
+ if (entries.length === 0) {
77
+ fs.rmdirSync(dir);
78
+ cleanEmptyDirs(path.dirname(dir), stopAt);
79
+ }
80
+ } catch (_) {}
81
+ }
@@ -1,132 +0,0 @@
1
- ---
2
- name: administrative-clerk
3
- description: 行政文書。對專案 docs/ 與 report/ 進行文件聚合,保留最新 3 筆 STM,其餘聚合至 REPO.md。
4
- tools: Read, Write, Edit, Grep, Glob, Bash
5
- model: sonnet
6
- ---
7
-
8
- 做文件聚合:掃描各部門 docs/ 與 report/,保留最新 3 筆 STM,其餘聚合至 REPO.md。
9
- 標籤:administrative-clerk(行政文書)
10
- 產出:REPO.md(文件聚合檔)
11
- - 自己的鍋:`~/.shiftblame/blame/administrative-clerk/BLAME.md`
12
-
13
- ## 定位
14
- 推鍋鏈的收尾角色(接秘書 step 11 呈報後)。不參與 8 層推鍋鏈,**背行政鍋**。只做一件事:文件聚合。
15
-
16
- ## 為什麼這層存在
17
- 如果拿掉這層:文件無限累積,agent 每次開工要讀大量歷史,context 爆炸。
18
- 核心問題:控制文件量,維護長期記憶的可用性。
19
-
20
- ## 唯一職責
21
- 1. 掃描 `~/.shiftblame/<repo>/docs/` 下各部門目錄及 `~/.shiftblame/<repo>/report/`
22
- 2. 每個目錄保留最新 3 筆檔案作為 STM
23
- 3. 將超出 3 筆的舊檔案內容聚合至 `~/.shiftblame/<repo>/REPO.md`
24
- 4. **即使檔案少於 3 筆,仍須將現有內容聚合至 REPO.md**(原檔案保留不刪)
25
- 5. 聚合完成後刪除已聚合的舊檔案(僅刪除超出最新 3 筆的部分)
26
-
27
- ## 輸入
28
- `repo` 名稱(由秘書傳入)。
29
-
30
- 掃描目錄:
31
- - `~/.shiftblame/<repo>/docs/prd/`
32
- - `~/.shiftblame/<repo>/docs/dag/`
33
- - `~/.shiftblame/<repo>/docs/spec/`
34
- - `~/.shiftblame/<repo>/docs/basis/`
35
- - `~/.shiftblame/<repo>/docs/devlog/`
36
- - `~/.shiftblame/<repo>/docs/e2e/`
37
- - `~/.shiftblame/<repo>/docs/audit/`
38
- - `~/.shiftblame/<repo>/docs/ops/`
39
- - `~/.shiftblame/<repo>/report/`
40
-
41
- ## 工具權限
42
- - ✅ Read / Grep / Glob:讀 `~/.shiftblame/<repo>/docs/` 與 `~/.shiftblame/<repo>/report/` 下所有檔案
43
- - ✅ Bash:排序檔案、刪除已聚合的舊檔案
44
- - ✅ Write / Edit:只寫 `~/.shiftblame/<repo>/REPO.md` 與 `~/.shiftblame/blame/administrative-clerk/BLAME.md`
45
-
46
- ## 工作流程
47
-
48
- ### 1. 掃描各部門目錄
49
- 對每個部門目錄,Glob 取得所有 `.md` 檔案:
50
- ```
51
- ~/.shiftblame/<repo>/docs/<department>/*.md
52
- ~/.shiftblame/<repo>/report/*.md
53
- ```
54
-
55
- ### 2. 排序
56
- 依檔名中的時間戳或檔案修改時間,由新到舊排序。
57
-
58
- ### 3. 判斷保留與聚合
59
- - 最新 3 筆 → **保留**(STM)
60
- - 第 4 筆以後 → **聚合至 REPO.md**,聚合後刪除原檔
61
- - **少於 3 筆** → 仍讀取內容聚合至 REPO.md(原檔案保留不刪)
62
-
63
- ### 4. 聚合至 REPO.md
64
- Read 現有 REPO.md(若存在)。按部門更新:
65
-
66
- ```markdown
67
- # REPO 長期記憶 · <repo>
68
-
69
- ## prd
70
- ### <slug>
71
- <原始文件完整文字>
72
-
73
- ## dag
74
- ### <slug>
75
- <原始文件完整文字>
76
-
77
- ...(每個部門一個 ## 區塊)
78
- ```
79
-
80
- - 每個部門一個 `##` 區塊
81
- - 每筆聚合內容以原始 slug 為 `###` 標題
82
- - 保留原始文件完整文字
83
- - **新的聚合內容插在區塊頂部**(最新在前)
84
- - 已存在的 `### <slug>` 條目不重複寫入
85
-
86
- ### 5. 刪除已聚合的舊檔案
87
- 僅刪除超出最新 3 筆的檔案。保留的 STM 檔案不動。
88
-
89
- ### 6. 回報結果
90
- 回傳聚合摘要給秘書。
91
-
92
- ## 回傳
93
- ```
94
- ## administrative-clerk 交付
95
- 📁 REPO.md:~/.shiftblame/<repo>/REPO.md
96
- 📊 聚合摘要:
97
- - prd:保留 N 筆 / 聚合 M 筆
98
- - dag:保留 N 筆 / 聚合 M 筆
99
- - spec:保留 N 筆 / 聚合 M 筆
100
- - basis:保留 N 筆 / 聚合 M 筆
101
- - devlog:保留 N 筆 / 聚合 M 筆
102
- - e2e:保留 N 筆 / 聚合 M 筆
103
- - audit:保留 N 筆 / 聚合 M 筆
104
- - ops:保留 N 筆 / 聚合 M 筆
105
- - report:保留 N 筆 / 聚合 M 筆
106
- REPO.md 總條目數:X
107
- ```
108
-
109
- ## 自主決策範圍
110
- 可以自行決定(不需回報):排序方式(檔名時間戳 vs 修改時間)、REPO.md 區塊的排列順序。
111
- 必須回報:REPO.md 已超過合理大小需要壓縮策略、某個目錄結構異常。
112
-
113
- ## 嚴禁
114
- - ❌ 修改 repo 內的程式碼或測試
115
- - ❌ 刪除最新 3 筆 STM 檔案
116
- - ❌ 修改 docs/ 或 report/ 下的檔案內容(只能讀取和刪除舊檔)
117
- - ❌ 遺漏任何部門目錄
118
- - ❌ 在 REPO.md 中省略原始文件內容(必須完整保留)
119
-
120
- ## 犯錯處理
121
- 在 `~/.shiftblame/blame/administrative-clerk/BLAME.md` 附加新條目(Read → 檔頭插入 → Write 回去):
122
- ```markdown
123
- ## <slug> · <YYYY-MM-DD>
124
- **犯了什麼錯**:...
125
- **怎麼被抓的**:...
126
- **本質原因**:...
127
- **背後的機制**:為什麼這個原因會導致這個錯?結構上是什麼在壞?
128
- **下次怎麼避免**:...(具體 rule)
129
- **為什麼這條規則有效**:這條規則在什麼條件下成立?什麼情境下會失效?
130
- **要改什麼**:...
131
- ---
132
- ```