pi-shepherd 0.1.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 (55) hide show
  1. package/README.en.md +136 -0
  2. package/README.md +136 -0
  3. package/index.ts +229 -0
  4. package/node_modules/@pi-atelier/shared-utils/README.en.md +182 -0
  5. package/node_modules/@pi-atelier/shared-utils/README.md +182 -0
  6. package/node_modules/@pi-atelier/shared-utils/package.json +51 -0
  7. package/node_modules/@pi-atelier/shared-utils/src/__tests__/agents.test.ts +120 -0
  8. package/node_modules/@pi-atelier/shared-utils/src/__tests__/ephemeral.test.ts +100 -0
  9. package/node_modules/@pi-atelier/shared-utils/src/__tests__/file-lock.test.ts +152 -0
  10. package/node_modules/@pi-atelier/shared-utils/src/__tests__/filter-match.test.ts +187 -0
  11. package/node_modules/@pi-atelier/shared-utils/src/__tests__/memory-parser.test.ts +170 -0
  12. package/node_modules/@pi-atelier/shared-utils/src/__tests__/paths.test.ts +126 -0
  13. package/node_modules/@pi-atelier/shared-utils/src/__tests__/project-config-edge.test.ts +138 -0
  14. package/node_modules/@pi-atelier/shared-utils/src/__tests__/project-config.test.ts +257 -0
  15. package/node_modules/@pi-atelier/shared-utils/src/__tests__/project-tools-mcp.test.ts +189 -0
  16. package/node_modules/@pi-atelier/shared-utils/src/__tests__/project-tools.test.ts +204 -0
  17. package/node_modules/@pi-atelier/shared-utils/src/__tests__/settings-backup-advanced.test.ts +269 -0
  18. package/node_modules/@pi-atelier/shared-utils/src/__tests__/settings-backup-array.test.ts +267 -0
  19. package/node_modules/@pi-atelier/shared-utils/src/__tests__/settings-backup.test.ts +520 -0
  20. package/node_modules/@pi-atelier/shared-utils/src/__tests__/settings-read.test.ts +116 -0
  21. package/node_modules/@pi-atelier/shared-utils/src/__tests__/settings-write.test.ts +119 -0
  22. package/node_modules/@pi-atelier/shared-utils/src/__tests__/tool-output.test.ts +145 -0
  23. package/node_modules/@pi-atelier/shared-utils/src/agents.ts +39 -0
  24. package/node_modules/@pi-atelier/shared-utils/src/ephemeral.ts +42 -0
  25. package/node_modules/@pi-atelier/shared-utils/src/file-lock.ts +62 -0
  26. package/node_modules/@pi-atelier/shared-utils/src/filter-match.ts +100 -0
  27. package/node_modules/@pi-atelier/shared-utils/src/index.ts +71 -0
  28. package/node_modules/@pi-atelier/shared-utils/src/memory-parser.ts +96 -0
  29. package/node_modules/@pi-atelier/shared-utils/src/paths.ts +23 -0
  30. package/node_modules/@pi-atelier/shared-utils/src/project-config.ts +241 -0
  31. package/node_modules/@pi-atelier/shared-utils/src/project-tools.ts +191 -0
  32. package/node_modules/@pi-atelier/shared-utils/src/settings-array.ts +73 -0
  33. package/node_modules/@pi-atelier/shared-utils/src/settings-backup-rollback.ts +104 -0
  34. package/node_modules/@pi-atelier/shared-utils/src/settings-backup-utils.ts +75 -0
  35. package/node_modules/@pi-atelier/shared-utils/src/settings-backup.ts +172 -0
  36. package/node_modules/@pi-atelier/shared-utils/src/settings.ts +104 -0
  37. package/node_modules/@pi-atelier/shared-utils/src/tool-output.ts +149 -0
  38. package/node_modules/@pi-atelier/shared-utils/tsconfig.json +9 -0
  39. package/node_modules/@pi-atelier/shared-utils/vitest.config.ts +24 -0
  40. package/package.json +49 -0
  41. package/rules.json +516 -0
  42. package/shepherd/ephemeral-shared.ts +14 -0
  43. package/shepherd/ephemeral.ts +52 -0
  44. package/shepherd/index.ts +39 -0
  45. package/shepherd/line-count.ts +86 -0
  46. package/shepherd/rules-editor.ts +135 -0
  47. package/shepherd/rules-tool.ts +99 -0
  48. package/shepherd/rules-validate.ts +44 -0
  49. package/shepherd/rules.ts +283 -0
  50. package/shepherd/state-tracker.ts +119 -0
  51. package/shepherd/tool-event-types.ts +31 -0
  52. package/shepherd/tool-hooks.ts +176 -0
  53. package/shepherd/worktree-check.ts +130 -0
  54. package/tsconfig.json +14 -0
  55. package/vitest.config.ts +13 -0
package/rules.json ADDED
@@ -0,0 +1,516 @@
1
+ [
2
+ {
3
+ "comment": "[rtk] 自动代理高频 bash 命令",
4
+ "tool": "bash",
5
+ "action": "rewrite",
6
+ "pattern": "^(git\\s+(status|log|diff)|cargo\\s+(test|build|clippy)|pytest)\\b",
7
+ "flags": "",
8
+ "reason": "rtk command rewrite:自动加 rtk 前缀压缩输出",
9
+ "enabled": true
10
+ },
11
+ {
12
+ "comment": "[Python] 编辑后必须格式检查 + 跑测试",
13
+ "hook": "tool_result",
14
+ "tool": "edit",
15
+ "action": "notify",
16
+ "conditions": [
17
+ {
18
+ "field": "path",
19
+ "pattern": "\\.py$",
20
+ "flags": ""
21
+ }
22
+ ],
23
+ "reason": "编辑了 Python 文件,必须:1) 运行 ruff check <file> 做格式检查;2) 跑覆盖该代码的单元测试(如无测试则先补充),修复所有测试问题确保通过。",
24
+ "enabled": true
25
+ },
26
+ {
27
+ "comment": "[settings] 禁止 bash 写入 settings.json",
28
+ "hook": "tool_result",
29
+ "tool": "write",
30
+ "action": "notify",
31
+ "conditions": [
32
+ {
33
+ "field": "command",
34
+ "pattern": "(>>|>|tee|sed\\s+-i|cp\\s|mv\\s).*settings\\.json|settings\\.json\\s*(>>|>)"
35
+ }
36
+ ],
37
+ "reason": "直接编辑 settings.json 曾导致配置丢失(119行变3行)。必须使用 patchSettingsSectionWithBackup 或 settings_rollback。",
38
+ "enabled": true
39
+ },
40
+ {
41
+ "comment": "[Rust] 编辑后必须格式检查 + 跑测试",
42
+ "hook": "tool_result",
43
+ "tool": "edit",
44
+ "action": "notify",
45
+ "conditions": [
46
+ {
47
+ "field": "path",
48
+ "pattern": "\\.rs$",
49
+ "flags": ""
50
+ }
51
+ ],
52
+ "reason": "编辑了 Rust 文件,必须:1) 运行 cargo clippy 做格式和 lint 检查;2) 跑覆盖该代码的单元测试(如无测试则先补充),修复所有测试问题确保通过。",
53
+ "enabled": true
54
+ },
55
+ {
56
+ "comment": "[Rust][write] 写入后必须格式检查 + 跑测试",
57
+ "hook": "tool_result",
58
+ "tool": "write",
59
+ "action": "notify",
60
+ "conditions": [
61
+ {
62
+ "field": "path",
63
+ "pattern": "\\.rs$",
64
+ "flags": ""
65
+ }
66
+ ],
67
+ "reason": "写入了 Rust 文件,必须:1) 运行 cargo clippy 做格式和 lint 检查;2) 跑覆盖该代码的单元测试(如无测试则先补充),修复所有测试问题确保通过。",
68
+ "enabled": true
69
+ },
70
+ {
71
+ "comment": "[TypeScript] 编辑后必须跑测试",
72
+ "hook": "tool_result",
73
+ "tool": "edit",
74
+ "action": "notify",
75
+ "conditions": [
76
+ {
77
+ "field": "path",
78
+ "pattern": "\\.ts$",
79
+ "flags": ""
80
+ }
81
+ ],
82
+ "reason": "编辑了 TypeScript 文件,必须跑覆盖该代码的单元测试(如无测试则先补充),修复所有测试问题确保通过。",
83
+ "enabled": true
84
+ },
85
+ {
86
+ "comment": "[TypeScript][write] 写入后必须跑测试",
87
+ "hook": "tool_result",
88
+ "tool": "write",
89
+ "action": "notify",
90
+ "conditions": [
91
+ {
92
+ "field": "path",
93
+ "pattern": "\\.ts$",
94
+ "flags": ""
95
+ }
96
+ ],
97
+ "reason": "写入了 TypeScript 文件,必须跑覆盖该代码的单元测试(如无测试则先补充),修复所有测试问题确保通过。",
98
+ "enabled": true
99
+ },
100
+ {
101
+ "comment": "收尾规则 — agent_end 时提醒 commit + 记忆 + 总结,commit 必须带 session ID",
102
+ "hook": "agent_end",
103
+ "action": "notify",
104
+ "check": "has_edits",
105
+ "reason": "⚠️ shepherd: 检测到文件编辑,执行收尾工作:\n1️⃣ **Git commit**:如有未提交的 git 改动 → git add + git commit。**⚠️ commit message 末尾必须加 `session:${PI_SESSION_ID}`** — 这是硬性要求,不是建议。session ID 用于会话分析和 roadmap 追踪,缺少会导致无法追溯。格式示例:`feat: xxx session:019e71c9-b340-7fae-a330-98d837e72099`\n2️⃣ **记忆更新**:如有值得记住的知识 → memory_update\n3️⃣ **会话总结**:简要总结本次会话做了什么",
106
+ "stopReason": [
107
+ "stop"
108
+ ],
109
+ "enabled": true
110
+ },
111
+ {
112
+ "comment": "[memory] git commit 后提醒更新记忆",
113
+ "hook": "tool_result",
114
+ "tool": "bash",
115
+ "pattern": "git\\s+commit",
116
+ "action": "steer",
117
+ "reason": "📝 刚执行了 git commit。检查本轮是否有值得跨会话保留的知识(架构决策、踩坑教训、结论),如果有 → 用 memory_update 工具写入。用法:①先调 memory_index 看已有文件,判断重复/合并/新建;②调 memory_update(fileName, content, scope) 一步写入文件+更新索引。fileName 格式:topic--kw1,kw2,kw3.md。scope:L1=跨项目通用,L2=项目级(默认)。单文件不超 200 行。如果没有新知识则忽略。",
118
+ "subagent": false,
119
+ "requireSuccess": true,
120
+ "enabled": true
121
+ },
122
+ {
123
+ "comment": "[arch] enforce-read-over-bash-cat:bash 中 cat|head/tail 读文件时强制用 read 工具",
124
+ "enabled": true,
125
+ "tool": "bash",
126
+ "pattern": "\\bcat\\b.*\\|\\s*(\\bhead\\b|\\btail\\b)",
127
+ "flags": "",
128
+ "action": "notify",
129
+ "reason": "bash cat|head/tail 读文件不如用 read 工具——read 支持 offset/limit 参数精确读取指定行范围,还保留 Tab 字符、自动标准化 CRLF,比管道拼接更可靠。",
130
+ "hook": "tool_call"
131
+ },
132
+ {
133
+ "comment": "[debug] 工具反复出错时提醒翻记忆",
134
+ "hook": "tool_result",
135
+ "action": "steer",
136
+ "state": {
137
+ "countKind": "errors",
138
+ "gte": 5
139
+ },
140
+ "reason": "🔍 **工具反复出错**:连续失败 2 次,翻看 .pi/memory/ 目录下的记忆文件,看是否已有踩坑记录。",
141
+ "enabled": true,
142
+ "subagent": false
143
+ },
144
+ {
145
+ "comment": "[dev] 编辑扩展代码时提醒参考规范",
146
+ "hook": "tool_result",
147
+ "tool": "edit",
148
+ "conditions": [
149
+ {
150
+ "field": "path",
151
+ "pattern": "extensions/.*\\.ts$"
152
+ }
153
+ ],
154
+ "action": "steer",
155
+ "reason": "📝 编辑了扩展代码。如果尚未阅读扩展开发规范,请先 read 根目录或项目目录下 extensions/README.md;如果已阅读且有新知识,更新到该文件。已阅读且无新知识则无视。",
156
+ "requireSuccess": true,
157
+ "subagent": false,
158
+ "enabled": true
159
+ },
160
+ {
161
+ "comment": "[dev] 写入扩展代码时提醒参考规范",
162
+ "hook": "tool_result",
163
+ "tool": "write",
164
+ "conditions": [
165
+ {
166
+ "field": "path",
167
+ "pattern": "extensions/.*\\.ts$"
168
+ }
169
+ ],
170
+ "action": "steer",
171
+ "reason": "📝 写入了扩展代码。如果尚未阅读扩展开发规范,请先 read 根目录或项目目录下 extensions/README.md;如果已阅读且有新知识,更新到该文件。已阅读且无新知识则无视。",
172
+ "requireSuccess": true,
173
+ "subagent": false,
174
+ "enabled": true
175
+ },
176
+ {
177
+ "comment": "[dev] 编辑扩展代码后提醒跑 typecheck",
178
+ "hook": "tool_result",
179
+ "tool": "edit",
180
+ "conditions": [
181
+ {
182
+ "field": "path",
183
+ "pattern": "extensions/.*\\.ts$"
184
+ }
185
+ ],
186
+ "action": "steer",
187
+ "reason": "📝 编辑了扩展 TypeScript 代码。请运行 `cd extensions && bash check.sh <扩展名> --typecheck` 确认无类型错误。如果已跑过则无视。",
188
+ "requireSuccess": true,
189
+ "subagent": false,
190
+ "enabled": true
191
+ },
192
+ {
193
+ "comment": "[dev] 写入扩展代码后提醒跑 typecheck",
194
+ "hook": "tool_result",
195
+ "tool": "write",
196
+ "conditions": [
197
+ {
198
+ "field": "path",
199
+ "pattern": "extensions/.*\\.ts$"
200
+ }
201
+ ],
202
+ "action": "steer",
203
+ "reason": "📝 写入了扩展 TypeScript 代码。请运行 `cd extensions && bash check.sh <扩展名> --typecheck` 确认无类型错误。如果已跑过则无视。",
204
+ "requireSuccess": true,
205
+ "subagent": false,
206
+ "enabled": true
207
+ },
208
+ {
209
+ "comment": "[TS] 禁止空格缩进 — TS 文件必须用 Tab",
210
+ "hook": "tool_call",
211
+ "tool": "edit",
212
+ "action": "notify",
213
+ "conditions": [
214
+ {
215
+ "field": "path",
216
+ "pattern": "\\.ts$"
217
+ },
218
+ {
219
+ "field": "text",
220
+ "pattern": "\\n [\\S ]"
221
+ }
222
+ ],
223
+ "reason": "❌ TS 文件要求 Tab 缩进,不是空格。请用 Tab 缩进重写代码。",
224
+ "enabled": true
225
+ },
226
+ {
227
+ "comment": "[TS][write] 禁止空格缩进 — TS 文件必须用 Tab",
228
+ "hook": "tool_call",
229
+ "tool": "write",
230
+ "action": "notify",
231
+ "conditions": [
232
+ {
233
+ "field": "path",
234
+ "pattern": "\\.ts$"
235
+ },
236
+ {
237
+ "field": "text",
238
+ "pattern": "\\n [\\S ]"
239
+ }
240
+ ],
241
+ "reason": "❌ TS 文件要求 Tab 缩进,不是空格。请用 Tab 缩进重写代码。",
242
+ "enabled": true
243
+ },
244
+ {
245
+ "comment": "[Python] 禁止 Tab 缩进 — Python 必须用 4 空格",
246
+ "hook": "tool_call",
247
+ "tool": "edit",
248
+ "action": "notify",
249
+ "conditions": [
250
+ {
251
+ "field": "path",
252
+ "pattern": "\\.py$"
253
+ },
254
+ {
255
+ "field": "text",
256
+ "pattern": "\\n\\t"
257
+ }
258
+ ],
259
+ "reason": "❌ Python 文件禁止 Tab 缩进,必须用 4 空格(PEP 8)。",
260
+ "enabled": true
261
+ },
262
+ {
263
+ "comment": "[Python][write] 禁止 Tab 缩进 — Python 必须用 4 空格",
264
+ "hook": "tool_call",
265
+ "tool": "write",
266
+ "action": "notify",
267
+ "conditions": [
268
+ {
269
+ "field": "path",
270
+ "pattern": "\\.py$"
271
+ },
272
+ {
273
+ "field": "text",
274
+ "pattern": "\\n\\t"
275
+ }
276
+ ],
277
+ "reason": "❌ Python 文件禁止 Tab 缩进,必须用 4 空格(PEP 8)。",
278
+ "enabled": true
279
+ },
280
+ {
281
+ "comment": "[Rust] 禁止 Tab 缩进 — Rust 必须用 4 空格",
282
+ "hook": "tool_call",
283
+ "tool": "edit",
284
+ "action": "notify",
285
+ "conditions": [
286
+ {
287
+ "field": "path",
288
+ "pattern": "\\.rs$"
289
+ },
290
+ {
291
+ "field": "text",
292
+ "pattern": "\\n\\t"
293
+ }
294
+ ],
295
+ "reason": "❌ Rust 文件禁止 Tab 缩进,必须用 4 空格(rustfmt)。",
296
+ "enabled": true
297
+ },
298
+ {
299
+ "comment": "[Rust][write] 禁止 Tab 缩进 — Rust 必须用 4 空格",
300
+ "hook": "tool_call",
301
+ "tool": "write",
302
+ "action": "notify",
303
+ "conditions": [
304
+ {
305
+ "field": "path",
306
+ "pattern": "\\.rs$"
307
+ },
308
+ {
309
+ "field": "text",
310
+ "pattern": "\\n\\t"
311
+ }
312
+ ],
313
+ "reason": "❌ Rust 文件禁止 Tab 缩进,必须用 4 空格(rustfmt)。",
314
+ "enabled": true
315
+ },
316
+ {
317
+ "comment": "[code-graph] grep 搜代码符号时推荐用 code-graph",
318
+ "hook": "tool_result",
319
+ "tool": "grep",
320
+ "action": "notify",
321
+ "pattern": ".",
322
+ "flags": "s",
323
+ "reason": "推荐用 code-graph 替代 grep 搜代码——code-graph 理解 AST 语义,能按符号名、调用链、引用关系精准定位,比 grep 逐行匹配快得多且不漏不误报。选工具方法:①模糊搜索 → semantic_code_search(默认首选);②精确符号名 → get_ast_node;③引用追踪 → find_references;④调用链 → get_call_graph;⑤模块结构 → module_overview。grep 仅适合搜字面量字符串(TODO、配置值、错误消息)。如果当前搜索的就是字面量字符串则忽略此提醒。如果目标目录没有被 code-graph 索引(搜索返回空结果),调用 setup_codegraph({ directory: \"目标目录路径\" }) 一键建索引。",
324
+ "requiresTools": [
325
+ "code_graph_semantic_code_search"
326
+ ],
327
+ "enabled": true
328
+ },
329
+ {
330
+ "comment": "[code-graph] bash grep/rg 搜代码文件时推荐用 pi grep 或 code-graph",
331
+ "hook": "tool_result",
332
+ "tool": "bash",
333
+ "action": "notify",
334
+ "pattern": "\\b(grep|rg)\\b.*\\.(py|rs|ts|js|toml)(?=[\\s'\"|)]|$)",
335
+ "flags": "",
336
+ "reason": "💡 bash grep 搜代码文件不如用 pi 内置 grep 工具或 code-graph MCP 工具——code-graph 理解 AST 语义,能精准匹配符号名和调用关系。选工具:①模糊搜索 → semantic_code_search;②精确符号 → get_ast_node;③引用追踪 → find_references;④调用链 → get_call_graph。如果搜的是字面量字符串(TODO、配置值)则忽略。如果目标目录没有被 code-graph 索引,调用 setup_codegraph({ directory: \"目标目录路径\" }) 一键建索引。",
337
+ "enabled": true
338
+ },
339
+ {
340
+ "comment": "[code-graph] 工具报索引错误时提醒重建索引",
341
+ "hook": "tool_result",
342
+ "tool": "grep",
343
+ "action": "notify",
344
+ "conditions": [
345
+ {
346
+ "field": "path",
347
+ "pattern": "\\.(ts|js|py|rs)$"
348
+ }
349
+ ],
350
+ "reason": "💡 搜代码文件推荐用 code-graph。如果 code-graph 返回空结果,可能是目标目录没有被索引。调用 setup_codegraph({ directory: \"目标目录路径\" }) 一键建索引(软链接+增量索引,当前会话即刻可搜)。用法参考 code-graph 技能:semantic_code_search(模糊搜索)、get_ast_node(精确符号)、get_call_graph(调用链)、module_overview(模块结构)。",
351
+ "enabled": true
352
+ },
353
+ {
354
+ "comment": "[deps] 编辑 package.json 时提醒依赖合规检查",
355
+ "hook": "tool_result",
356
+ "tool": "edit",
357
+ "action": "notify",
358
+ "conditions": [
359
+ {
360
+ "field": "path",
361
+ "pattern": "package\\.json$"
362
+ }
363
+ ],
364
+ "reason": "📦 编辑了 package.json 依赖字段。请对照 DEPENDENCY-GUIDE.md 检查合规性:① pi SDK 必须放 peerDependencies + optionalDependencies,禁止放 dependencies/devDependencies;② shared-utils 运行时引用放 dependencies(开发放 devDependencies);③ 检查 files 白名单是否存在;④ 添加新依赖后运行 scan-deps.sh 验证无嵌套/版本冲突。",
365
+ "enabled": true
366
+ },
367
+ {
368
+ "comment": "[deps][write] 写入 package.json 时提醒依赖合规检查",
369
+ "hook": "tool_result",
370
+ "tool": "write",
371
+ "action": "notify",
372
+ "conditions": [
373
+ {
374
+ "field": "path",
375
+ "pattern": "package\\.json$"
376
+ }
377
+ ],
378
+ "reason": "📦 写入了 package.json 依赖字段。请对照 DEPENDENCY-GUIDE.md 检查合规性:① pi SDK 必须放 peerDependencies + optionalDependencies,禁止放 dependencies/devDependencies;② shared-utils 运行时引用放 dependencies(开发放 devDependencies);③ 检查 files 白名单是否存在;④ 添加新依赖后运行 scan-deps.sh 验证无嵌套/版本冲突。",
379
+ "enabled": true
380
+ },
381
+ {
382
+ "comment": "[通用] 脚本执行失败时提醒先 --help",
383
+ "hook": "tool_result",
384
+ "tool": "bash",
385
+ "action": "steer",
386
+ "pattern": "(python|uv run python) .+(scripts|tools|bin)/",
387
+ "state": {
388
+ "countKind": "errors",
389
+ "gte": 1
390
+ },
391
+ "reason": "💡 脚本执行失败。在盲目调参重试之前,先运行 `uv run python <脚本路径> --help` 查看用法说明和参数格式,避免反复试错浪费时间。",
392
+ "subagent": false,
393
+ "requireSuccess": false,
394
+ "enabled": true
395
+ },
396
+ {
397
+ "comment": "[roadmap] 禁止直接编辑 roadmap JSON — 必须用 roadmap 工具",
398
+ "hook": "tool_call",
399
+ "tool": "edit",
400
+ "action": "block",
401
+ "conditions": [
402
+ {
403
+ "field": "path",
404
+ "pattern": "\\.roadmap\\.json$|\\broadmap/roadmap\\.json$"
405
+ }
406
+ ],
407
+ "reason": "⛔ 禁止直接编辑 roadmap JSON!请用 roadmap 工具操作(自动处理时间戳/ID分配/状态级联/归档/doing同步):\n查看:roadmap_list(列表)、roadmap_show(详情)、roadmap_next(待办)\n创建:roadmap_plan(完整JSON创建/更新)、roadmap_create + roadmap_add_epic/story/task(逐步构建)\n修改:roadmap_update(roadmapId, item_id, {status/title/description/priority}) — 更新单个项\n完成:roadmap_done(roadmapId, taskId) — 标记完成并级联更新\n归档:roadmap_archive(roadmapId) — 归档已完成项\n直接改 JSON 会绕过这些机制导致数据不一致。",
408
+ "enabled": true
409
+ },
410
+ {
411
+ "comment": "[roadmap] 禁止直接写入 roadmap JSON — 必须用 roadmap 工具",
412
+ "hook": "tool_call",
413
+ "tool": "write",
414
+ "action": "block",
415
+ "conditions": [
416
+ {
417
+ "field": "path",
418
+ "pattern": "\\.roadmap\\.json$|\\broadmap/roadmap\\.json$"
419
+ }
420
+ ],
421
+ "reason": "⛔ 禁止直接写入 roadmap JSON!请用 roadmap 工具操作(自动处理时间戳/ID分配/状态级联/归档/doing同步):\n查看:roadmap_list(列表)、roadmap_show(详情)、roadmap_next(待办)\n创建:roadmap_plan(完整JSON创建/更新)、roadmap_create + roadmap_add_epic/story/task(逐步构建)\n修改:roadmap_update(roadmapId, item_id, {status/title/description/priority}) — 更新单个项\n完成:roadmap_done(roadmapId, taskId) — 标记完成并级联更新\n归档:roadmap_archive(roadmapId) — 归档已完成项\n直接改 JSON 会绕过这些机制导致数据不一致。",
422
+ "enabled": true
423
+ },
424
+ {
425
+ "comment": "[roadmap] 禁止通过 bash/python 脚本直接读写 roadmap JSON — 必须用 roadmap 工具",
426
+ "hook": "tool_call",
427
+ "tool": "bash",
428
+ "action": "block",
429
+ "conditions": [
430
+ {
431
+ "field": "command",
432
+ "pattern": "((roadmap\\.json|\\.roadmap\\.json)[\\s\\S]*(open\\(|json\\.load|json\\.dump|write|sed|awk|jq|cat))|((open\\(|json\\.load|json\\.dump|write|sed|awk|jq|cat)[\\s\\S]*(roadmap\\.json|\\.roadmap\\.json))"
433
+ }
434
+ ],
435
+ "reason": "⛔ 禁止通过 bash/python 脚本直接读写 roadmap JSON!请用 roadmap 工具操作(自动处理时间戳/ID分配/状态级联/归档/doing同步):\n查看:roadmap_list(列表)、roadmap_show(详情)、roadmap_next(待办)\n创建:roadmap_plan(完整JSON创建/更新)、roadmap_create + roadmap_add_epic/story/task(逐步构建)\n修改:roadmap_update(roadmapId, item_id, {status/title/description/priority}) — 更新单个项\n完成:roadmap_done(roadmapId, taskId) — 标记完成并级联更新\n归档:roadmap_archive(roadmapId) — 归档已完成项\n直接碰底层 JSON 会绕过这些机制导致数据不一致。",
436
+ "enabled": true
437
+ },
438
+ {
439
+ "comment": "[GDScript] 编辑 .gd 文件后提醒写/跑 GUT 测试",
440
+ "reason": "跟 Python/Rust/TS 规则一致——编辑代码后提醒补充单元测试。触发条件:edit 工具的 file 参数匹配 .gd 扩展名。提醒内容:是否有对应的 GUT 测试?如果没有,建议补写。如果有,提醒跑 run_unit_tests.sh 验证。",
441
+ "trigger": {
442
+ "tool": "edit",
443
+ "event": "tool_result",
444
+ "match": "\\.gd$"
445
+ },
446
+ "action": "notify"
447
+ },
448
+ {
449
+ "action": "notify",
450
+ "comment": "[GDScript][write] 写入 .gd 文件后提醒写/跑 GUT 测试",
451
+ "reason": "跟 edit 规则配对。新建 .gd 文件时提醒创建对应的测试文件。",
452
+ "trigger": {
453
+ "event": "tool_result",
454
+ "match": "\\.gd$",
455
+ "tool": "write"
456
+ }
457
+ },
458
+ {
459
+ "comment": "[settings] 禁止 edit settings.json — 必须用 patchSettingsSectionWithBackup 或 settings_rollback",
460
+ "reason": "直接编辑 settings.json 曾导致配置丢失(119 行变 3 行)。必须使用 patchSettingsSectionWithBackup 或 settings_rollback。",
461
+ "tool": "edit",
462
+ "hook": "tool_call",
463
+ "action": "block",
464
+ "message": "⛔ 禁止直接 edit settings.json!使用 patchSettingsSectionWithBackup() 安全修改,或 settings_rollback 回滚。",
465
+ "conditions": [
466
+ {
467
+ "field": "path",
468
+ "pattern": "settings\\.json$|settings\\.json\\.bak"
469
+ }
470
+ ]
471
+ },
472
+ {
473
+ "action": "block",
474
+ "comment": "[settings] 禁止 write settings.json — 必须用 patchSettingsSectionWithBackup 或 settings_rollback",
475
+ "conditions": [
476
+ {
477
+ "field": "path",
478
+ "pattern": "settings\\.json$|settings\\.json\\.bak"
479
+ }
480
+ ],
481
+ "hook": "tool_call",
482
+ "message": "⛔ 禁止直接 write settings.json!使用 patchSettingsSectionWithBackup() 安全修改,或 settings_rollback 回滚。",
483
+ "reason": "直接编辑 settings.json 曾导致配置丢失(119 行变 3 行)。必须使用 patchSettingsSectionWithBackup 或 settings_rollback。",
484
+ "tool": "write"
485
+ },
486
+ {
487
+ "action": "block",
488
+ "comment": "[settings] 禁止 bash 直接修改 settings.json — 必须用 patchSettingsSectionWithBackup 或 settings_rollback",
489
+ "conditions": [
490
+ {
491
+ "field": "command",
492
+ "pattern": "settings\\.json"
493
+ }
494
+ ],
495
+ "hook": "tool_call",
496
+ "message": "⛔ 禁止通过 bash 修改 settings.json!使用 patchSettingsSectionWithBackup() 安全修改,或 settings_rollback 回滚。",
497
+ "reason": "禁止 bash 直接修改 settings.json — 必须用 patchSettingsSectionWithBackup 或 settings_rollback",
498
+ "tool": "bash"
499
+ },
500
+ {
501
+ "comment": "[git] commit 成功后提醒 push",
502
+ "reason": "项目已有远程仓库 origin,commit 后应同步推送避免本地积压",
503
+ "trigger": "bash",
504
+ "event": "tool_result",
505
+ "condition": "bash command contains 'git commit' and exit code is 0",
506
+ "action": "notify",
507
+ "message": "commit 成功。检查是否有远程仓库(git remote),有的话立即 git push 同步到远程。"
508
+ },
509
+ {
510
+ "comment": "UI 布局改动提醒",
511
+ "reason": "编辑 GameScene.gd 或 UI 相关代码时,先查 debug_summary().ui_layout 确认位置,改完用 debug_toggle_borders() 验证无重叠",
512
+ "pattern": "GameScene\\.gd",
513
+ "type": "notify",
514
+ "action": "notify"
515
+ }
516
+ ]
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shepherd 临时提示 re-export
3
+ *
4
+ * 缓冲区操作来自 @pi-atelier/shared-utils(单扩展内使用)。
5
+ * 跨扩展传递请用 pi.events.emit("ephemeral:hint")。
6
+ */
7
+
8
+ export {
9
+ drainHints,
10
+ hasHints,
11
+ peekHints,
12
+ peekLabels,
13
+ pushHint,
14
+ } from "@pi-atelier/shared-utils";
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Shepherd 专用提示缓冲区
3
+ *
4
+ * shepherd 规则触发的 steer/notify 提示通过 pushWarning 推入,
5
+ * 由共享的 ephemeral 注入机制在 before_provider_request 时发送。
6
+ * 提示只对当前请求生效,不写入 session 历史。
7
+ */
8
+
9
+ /** 替换 ${ENV_VAR} 格式的环境变量 */
10
+ function expandEnvVars(text: string): string {
11
+ return text.replace(/\$\{(\w+)\}/g, (_match, name: string) => process.env[name] ?? `\${${name}}`);
12
+ }
13
+
14
+ /** 推入一条 shepherd 提示(自动加 ⚠️ shepherd: 前缀) */
15
+ export function pushWarning(reason: string, label?: string): void {
16
+ // shepherd 前缀用于通知气泡识别,注入时由 injectHints 统一处理
17
+ pushShepherdHint(expandEnvVars(reason), label);
18
+ }
19
+
20
+ /** 生成通知气泡用的摘要(优先用规则名列表,fallback 截断 reason) */
21
+ export function notifySummary(text: string, labels?: string[]): string {
22
+ // 优先使用规则名(comment)列表
23
+ if (labels && labels.length > 0) {
24
+ const joined = labels.join("、");
25
+ return joined.length > 120 ? joined.slice(0, 117) + "..." : joined;
26
+ }
27
+ // fallback:截取第一个 --- 之前的内容
28
+ const idx = text.indexOf("\n---");
29
+ if (idx > 0) return text.slice(0, idx);
30
+ if (text.length > 120) return text.slice(0, 117) + "...";
31
+ return text;
32
+ }
33
+
34
+ // ── 内部:shepherd 前缀推入共享缓冲区 ──
35
+
36
+ import { pushHint as _pushShared, hasHints } from "./ephemeral-shared.js";
37
+
38
+ const SHEPHERD_PREFIX = "⚠️ shepherd: ";
39
+
40
+ function pushShepherdHint(reason: string, label?: string): void {
41
+ _pushShared(`${SHEPHERD_PREFIX}${reason}`, label);
42
+ }
43
+
44
+ /** 推入规则格式错误提示(加 ❌ 前缀区别于普通 warning) */
45
+ export function pushRuleError(msg: string): void {
46
+ _pushShared(`❌ shepherd 规则格式错误: ${msg}`);
47
+ }
48
+
49
+ /** shepherd 是否有待发送的提示 */
50
+ export function hasWarnings(): boolean {
51
+ return hasHints();
52
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @pi-atelier/shepherd — barrel export
3
+ */
4
+
5
+ export { hasWarnings, notifySummary, pushWarning } from "./ephemeral";
6
+ export {
7
+ drainHints,
8
+ hasHints,
9
+ peekHints,
10
+ peekLabels,
11
+ pushHint,
12
+ } from "./ephemeral-shared";
13
+ export { checkLineCount } from "./line-count";
14
+ export {
15
+ CODE_EXT_RE,
16
+ type Condition,
17
+ compileRules,
18
+ getMatchTargets,
19
+ hasGitUncommittedChanges,
20
+ isInWorktree,
21
+ isSubagent,
22
+ loadRules,
23
+ loadRulesFromFile,
24
+ type Rule,
25
+ ruleMatches,
26
+ } from "./rules";
27
+ export {
28
+ type ResettableRule,
29
+ type StateCondition,
30
+ StateTracker,
31
+ } from "./state-tracker";
32
+ export {
33
+ getAvailableTools,
34
+ registerToolCall,
35
+ registerToolResult,
36
+ type ToolState,
37
+ toolsAvailable,
38
+ } from "./tool-hooks";
39
+ export { checkWorktrees } from "./worktree-check";