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.
- package/.claude/agents/{backend-engineer.md → DEV-be.md} +5 -5
- package/.claude/agents/DEV-db.md +78 -0
- package/.claude/agents/{frontend-engineer.md → DEV-fe.md} +5 -5
- package/.claude/agents/{feature-developer.md → DEV.md} +30 -28
- package/.claude/agents/MIS-cicd.md +140 -0
- package/.claude/agents/MIS-cloud.md +100 -0
- package/.claude/agents/MIS-infra.md +107 -0
- package/.claude/agents/MIS.md +132 -0
- package/.claude/agents/PRD-arch.md +71 -0
- package/.claude/agents/PRD-market.md +70 -0
- package/.claude/agents/PRD-plan.md +75 -0
- package/.claude/agents/PRD.md +109 -0
- package/.claude/agents/{e2e-test-engineer.md → QA-e2e.md} +5 -5
- package/.claude/agents/{integration-test-engineer.md → QA-integ.md} +5 -5
- package/.claude/agents/{unit-test-engineer.md → QA-unit.md} +5 -5
- package/.claude/agents/{quality-assurance.md → QA.md} +26 -24
- package/.claude/agents/QC-test.md +96 -0
- package/.claude/agents/QC-uni.md +108 -0
- package/.claude/agents/QC-user.md +131 -0
- package/.claude/agents/QC.md +138 -0
- package/.claude/agents/SEC-blue.md +112 -0
- package/.claude/agents/SEC-red.md +90 -0
- package/.claude/agents/SEC-white.md +103 -0
- package/.claude/agents/SEC.md +164 -0
- package/.claude/agents/SECRETARY.md +44 -0
- package/.claude/commands/secretary.md +9 -0
- package/.claude/skills/blame-init/SKILL.md +135 -0
- package/.claude/skills/blame-reflect/SKILL.md +87 -0
- package/README.md +91 -130
- package/package.json +3 -2
- package/scripts/postinstall.js +72 -8
- package/scripts/preuninstall.js +81 -0
- package/.claude/agents/administrative-clerk.md +0 -132
- package/.claude/agents/audit-reviewer.md +0 -164
- package/.claude/agents/infra-engineer.md +0 -66
- package/.claude/agents/operations-engineer.md +0 -140
- package/.claude/agents/product-planner.md +0 -77
- package/.claude/agents/project-manager.md +0 -79
- package/.claude/agents/quality-control.md +0 -120
- package/.claude/agents/system-architect.md +0 -81
- package/.claude/commands/shiftblame-link.md +0 -34
- package/.claude/commands/shiftblame-reflect.md +0 -80
- package/.claude/skills/secretary/SKILL.md +0 -417
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# shiftblame
|
|
4
4
|
|
|
5
5
|
### 推鍋
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ _一套明確責任歸屬的 Agents 開發框架_
|
|
|
8
8
|
|
|
9
9
|
[](./LICENSE)
|
|
10
10
|
[](https://claude.com/claude-code)
|
|
11
|
-
[](#資源供給機制)
|
|
12
12
|
[](#)
|
|
13
13
|
|
|
14
14
|
> _「這不是我的鍋。」_
|
|
@@ -19,51 +19,44 @@ _一套明確責任歸屬的 Agents 開發框架_
|
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
秘書動態掃描 agents 目錄,把正確的需求推給正確的部門。每個部門該誰負責、出了事該找誰,白紙黑字記在鍋紀錄裡。
|
|
23
23
|
|
|
24
24
|
還沒想清楚?秘書也能幫你**釐清方向**——用結構化問答收斂需求,確認後再推鍋。
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## 資源供給機制
|
|
29
29
|
|
|
30
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
###
|
|
50
|
+
### 各部門的鍋
|
|
48
51
|
|
|
49
|
-
|
|
|
52
|
+
| 部門 | 典型犯錯情境 |
|
|
50
53
|
|------|-------------|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
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
|
-
│
|
|
87
|
+
│ 掃描 agents 目錄 │
|
|
88
|
+
│ 判斷該推給哪個部門 │
|
|
96
89
|
└─────────┬───────────────┘
|
|
97
90
|
│
|
|
98
91
|
┌───────┴────────┐
|
|
99
92
|
│ │
|
|
100
|
-
|
|
93
|
+
預審 OK 不 OK
|
|
101
94
|
│ │
|
|
102
95
|
▼ ▼
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
│
|
|
96
|
+
啟動該部門 秘書判斷根因,
|
|
97
|
+
│ 推給正確的部門重做
|
|
106
98
|
▼
|
|
107
|
-
|
|
99
|
+
收回傳 → 判斷下一步
|
|
108
100
|
│
|
|
109
101
|
▼
|
|
110
|
-
|
|
102
|
+
完成 → 秘書對照原話 → 呈報老闆
|
|
111
103
|
```
|
|
112
104
|
|
|
113
|
-
###
|
|
105
|
+
### 需求路由
|
|
106
|
+
|
|
107
|
+
秘書根據需求性質,把鍋推給對的部門:
|
|
114
108
|
|
|
115
|
-
|
|
|
116
|
-
|
|
117
|
-
|
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
|
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
|
-
|
|
122
|
+
全新功能的典型路徑:`PRD → MIS(環境) → QA → DEV → QC → SEC(安全) → MIS(合併+部署)`,但秘書可依實際需求動態跳過或新增步驟。
|
|
127
123
|
|
|
128
124
|
### 檔案結構
|
|
129
125
|
|
|
130
126
|
```
|
|
131
|
-
~/.shiftblame/
|
|
132
|
-
├── blame/
|
|
133
|
-
│ ├──
|
|
134
|
-
│ ├──
|
|
135
|
-
│ ├──
|
|
136
|
-
│ ├──
|
|
137
|
-
│
|
|
138
|
-
│
|
|
139
|
-
│
|
|
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
|
-
|
|
137
|
+
├── {MIS}/<slug>.md
|
|
138
|
+
├── {DEV,QA}/<slug>.md
|
|
139
|
+
├── {PRD,QC,SEC}/<slug>.md
|
|
140
|
+
└── REPO.md
|
|
175
141
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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>/
|
|
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
|
-
/
|
|
174
|
+
/blame-init
|
|
207
175
|
```
|
|
208
176
|
|
|
209
|
-
|
|
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
|
-
|
|
203
|
+
當各部門的 BLAME.md 累積了一定歷史後,可以執行:
|
|
240
204
|
|
|
241
205
|
```
|
|
242
|
-
/
|
|
206
|
+
/blame-reflect
|
|
243
207
|
```
|
|
244
208
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
> **為什麼要用 `/secretary`?** Claude Code 不保證每次都能正確判斷該觸發哪個 skill,顯式呼叫最可靠。
|
|
209
|
+
掃描所有部門的鍋紀錄,將「下次怎麼避免」提煉成**常識(規則)**,將「背後的機制」提煉成**認知(模型)**,寫回各 BLAME.md 檔頭。
|
|
248
210
|
|
|
249
|
-
|
|
211
|
+
### 秘書接手後
|
|
250
212
|
|
|
251
|
-
1.
|
|
252
|
-
2.
|
|
253
|
-
3.
|
|
254
|
-
4.
|
|
255
|
-
5.
|
|
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
|
-
-
|
|
261
|
-
-
|
|
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": "
|
|
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": {
|
package/scripts/postinstall.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
console.log("shiftblame: user 級別安裝完成 → ~/.claude/");
|
|
17
|
+
dest = path.join(require("os").homedir(), ".claude");
|
|
18
|
+
installType = "global";
|
|
16
19
|
} else {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
```
|