pi-shepherd 0.1.2 → 0.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/README.en.md +205 -84
- package/README.md +207 -88
- package/index.ts +262 -229
- package/package.json +60 -55
- package/rules.json +196 -33
- package/shepherd/compaction.ts +76 -0
- package/shepherd/conditions.ts +98 -0
- package/shepherd/ephemeral.ts +55 -52
- package/shepherd/git.ts +64 -0
- package/shepherd/index.ts +13 -0
- package/shepherd/line-count.ts +86 -86
- package/shepherd/message-end.ts +120 -0
- package/shepherd/rules-editor.ts +250 -215
- package/shepherd/rules-tool-helpers.ts +119 -120
- package/shepherd/rules-tool-list.ts +124 -126
- package/shepherd/rules-tool.ts +182 -142
- package/shepherd/rules-validate.ts +89 -44
- package/shepherd/rules.ts +366 -295
- package/shepherd/tool-event-types.ts +27 -14
- package/shepherd/tool-hooks.ts +165 -177
- package/shepherd/worktree-check.ts +130 -130
- package/tsconfig.json +21 -14
package/index.ts
CHANGED
|
@@ -1,229 +1,262 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shepherd — 通用 Hook 规则引擎
|
|
3
|
-
*
|
|
4
|
-
* 规则驱动的事件 hook,支持多种动作:
|
|
5
|
-
* - tool_call: 工具调用前(可 block 拦截 / notify 提醒 / rewrite 重写)
|
|
6
|
-
* - tool_result: 工具执行后(可 notify 提醒 / steer 向 LLM 注入 + 行数检查)
|
|
7
|
-
* - agent_end: AI 正常完成时(可 notify 提醒,支持 stopReason 过滤)
|
|
8
|
-
* - session_shutdown: 会话结束时(可 notify 提醒)
|
|
9
|
-
*
|
|
10
|
-
* steer/notify 提示通过 before_provider_request 临时注入到 LLM payload,
|
|
11
|
-
* 不写入 session 历史,不占用后续上下文。
|
|
12
|
-
*
|
|
13
|
-
* 规则配置文件:
|
|
14
|
-
* 全局: ~/.pi/agent/extensions/shepherd/rules.json
|
|
15
|
-
* 项目级: <cwd>/.pi/extensions/shepherd-rules-*.json(自动扫描,叠加加载)
|
|
16
|
-
*
|
|
17
|
-
* 修改规则文件后 /reload 即可生效,无需重启 pi。
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
/** pi payload 消息结构的最小类型 */
|
|
25
|
-
interface PayloadMessage {
|
|
26
|
-
role: string;
|
|
27
|
-
content?: unknown;
|
|
28
|
-
[key: string]: unknown;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** pi provider payload 的最小类型 */
|
|
32
|
-
interface ProviderPayload {
|
|
33
|
-
messages?: PayloadMessage[];
|
|
34
|
-
[key: string]: unknown;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
38
|
-
const RULES_DIR = __dirname;
|
|
39
|
-
|
|
40
|
-
import { getEffectiveConfig } from "@pi-atelier/shared-utils";
|
|
41
|
-
import {
|
|
42
|
-
checkWorktrees,
|
|
43
|
-
drainHints,
|
|
44
|
-
hasGitUncommittedChanges,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// ──
|
|
91
|
-
pi.on("
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
.map((h) => h.text)
|
|
110
|
-
.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shepherd — 通用 Hook 规则引擎
|
|
3
|
+
*
|
|
4
|
+
* 规则驱动的事件 hook,支持多种动作:
|
|
5
|
+
* - tool_call: 工具调用前(可 block 拦截 / notify 提醒 / rewrite 重写)
|
|
6
|
+
* - tool_result: 工具执行后(可 notify 提醒 / steer 向 LLM 注入 + 行数检查)
|
|
7
|
+
* - agent_end: AI 正常完成时(可 notify 提醒,支持 stopReason 过滤)
|
|
8
|
+
* - session_shutdown: 会话结束时(可 notify 提醒)
|
|
9
|
+
*
|
|
10
|
+
* steer/notify 提示通过 before_provider_request 临时注入到 LLM payload,
|
|
11
|
+
* 不写入 session 历史,不占用后续上下文。
|
|
12
|
+
*
|
|
13
|
+
* 规则配置文件:
|
|
14
|
+
* 全局: ~/.pi/agent/extensions/shepherd/rules.json
|
|
15
|
+
* 项目级: <cwd>/.pi/extensions/shepherd-rules-*.json(自动扫描,叠加加载)
|
|
16
|
+
*
|
|
17
|
+
* 修改规则文件后 /reload 即可生效,无需重启 pi。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { dirname } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
23
|
+
|
|
24
|
+
/** pi payload 消息结构的最小类型 */
|
|
25
|
+
interface PayloadMessage {
|
|
26
|
+
role: string;
|
|
27
|
+
content?: unknown;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** pi provider payload 的最小类型 */
|
|
32
|
+
interface ProviderPayload {
|
|
33
|
+
messages?: PayloadMessage[];
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
const RULES_DIR = __dirname;
|
|
39
|
+
|
|
40
|
+
import { getEffectiveConfig } from "@pi-atelier/shared-utils";
|
|
41
|
+
import {
|
|
42
|
+
checkWorktrees,
|
|
43
|
+
drainHints,
|
|
44
|
+
hasGitUncommittedChanges,
|
|
45
|
+
hasGitUntracked,
|
|
46
|
+
hasWarnings,
|
|
47
|
+
isSubagent,
|
|
48
|
+
loadRules,
|
|
49
|
+
notifySummary,
|
|
50
|
+
pushWarning,
|
|
51
|
+
registerMessageEnd,
|
|
52
|
+
registerSessionCompact,
|
|
53
|
+
registerToolCall,
|
|
54
|
+
registerToolResult,
|
|
55
|
+
resetMessageEndState,
|
|
56
|
+
StateTracker,
|
|
57
|
+
type ToolState,
|
|
58
|
+
} from "./shepherd";
|
|
59
|
+
import { isQuestionEnding, ruleMatches } from "./shepherd/rules";
|
|
60
|
+
import type { BuiltinContext } from "./shepherd/conditions";
|
|
61
|
+
import { registerRulesEditorTool } from "./shepherd/rules-tool";
|
|
62
|
+
|
|
63
|
+
/** 本地 hints 缓冲区(收集 pi.events.emit("ephemeral:hint") 的数据) */
|
|
64
|
+
const _localHints: { text: string; short?: string }[] = [];
|
|
65
|
+
|
|
66
|
+
/** 可变状态:跨 hook 共享 */
|
|
67
|
+
let _aborted = false;
|
|
68
|
+
let _wasDirty = false;
|
|
69
|
+
const _agentEndFired = new Set<string>();
|
|
70
|
+
const _toolState: ToolState = {
|
|
71
|
+
hasEdits: false,
|
|
72
|
+
tracker: new StateTracker(),
|
|
73
|
+
cachedTools: null,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default function shepherdExtension(pi: ExtensionAPI) {
|
|
77
|
+
// ── 读取配置(三层合并:defaults → 全局 settings → 项目 settings)──
|
|
78
|
+
const { config: shepherdConfig } = getEffectiveConfig<{
|
|
79
|
+
projectRulesPattern: string;
|
|
80
|
+
maxWarnings: number;
|
|
81
|
+
}>(
|
|
82
|
+
"shepherd",
|
|
83
|
+
{
|
|
84
|
+
projectRulesPattern: "shepherd-rules-",
|
|
85
|
+
maxWarnings: 5,
|
|
86
|
+
},
|
|
87
|
+
process.cwd(),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// ── 监听跨扩展 hints(通过 pi.events 绕过 jiti 多实例) ──
|
|
91
|
+
pi.events.on("ephemeral:hint", (data) => {
|
|
92
|
+
const { text, short } = data as { text: string; short?: string };
|
|
93
|
+
_localHints.push({ text, short });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// ── before_provider_request:注入临时提示 ──────────────────
|
|
97
|
+
pi.on("before_provider_request", async (event, ctx) => {
|
|
98
|
+
// shepherd 规则 hints
|
|
99
|
+
const shepherdText = drainHints();
|
|
100
|
+
if (shepherdText) {
|
|
101
|
+
_localHints.unshift({ text: shepherdText });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 通知摘要:short 优先,fallback 到 notifySummary 截断
|
|
105
|
+
const shortParts = _localHints
|
|
106
|
+
.map((h) => h.short)
|
|
107
|
+
.filter(Boolean) as string[];
|
|
108
|
+
const longParts = _localHints
|
|
109
|
+
.map((h) => (h.short ? null : h.text))
|
|
110
|
+
.filter(Boolean) as string[];
|
|
111
|
+
const notifyText = [...shortParts, ...longParts].join("\n\n");
|
|
112
|
+
|
|
113
|
+
const allHints = _localHints
|
|
114
|
+
.splice(0)
|
|
115
|
+
.map((h) => h.text)
|
|
116
|
+
.join("\n\n");
|
|
117
|
+
let payload = event.payload as ProviderPayload;
|
|
118
|
+
|
|
119
|
+
if (allHints) {
|
|
120
|
+
const text = allHints;
|
|
121
|
+
payload = { ...payload };
|
|
122
|
+
payload.messages = [...(payload.messages ?? [])];
|
|
123
|
+
payload.messages.push({
|
|
124
|
+
role: "user",
|
|
125
|
+
content: [{ type: "text", text }],
|
|
126
|
+
});
|
|
127
|
+
ctx.ui.notify?.(notifySummary(notifyText), "warning");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return payload;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ── session_start ──────────────────────────────────────────
|
|
134
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
135
|
+
checkWorktrees(ctx.ui);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ── agent_start ────────────────────────────────────────────
|
|
139
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
140
|
+
_aborted = ctx.signal?.aborted ?? false;
|
|
141
|
+
_toolState.hasEdits = false;
|
|
142
|
+
_toolState.cachedTools = null;
|
|
143
|
+
resetMessageEndState();
|
|
144
|
+
if (ctx.signal && !ctx.signal.aborted) {
|
|
145
|
+
ctx.signal.addEventListener("abort", () => {
|
|
146
|
+
_aborted = true;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
_wasDirty = hasGitUncommittedChanges();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
pi.on("input", async (_event) => {
|
|
153
|
+
/* 用户输入新消息 — 允许 agent_end 规则再次触发 */
|
|
154
|
+
_agentEndFired.clear();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ── agent_end ──────────────────────────────────────────────
|
|
158
|
+
pi.on("agent_end", async (event, _ctx) => {
|
|
159
|
+
if (isSubagent() || _aborted) return;
|
|
160
|
+
const rules = loadRules(RULES_DIR, {
|
|
161
|
+
projectRulesPattern: shepherdConfig.projectRulesPattern,
|
|
162
|
+
}).filter((r) => r.hook === "agent_end");
|
|
163
|
+
if (rules.length === 0) return;
|
|
164
|
+
|
|
165
|
+
const lastAssistant = [...event.messages]
|
|
166
|
+
.reverse()
|
|
167
|
+
.find((m) => m.role === "assistant");
|
|
168
|
+
const stopReason: string | undefined = (
|
|
169
|
+
lastAssistant as unknown as PayloadMessage | undefined
|
|
170
|
+
)?.stopReason as string | undefined;
|
|
171
|
+
|
|
172
|
+
// 提取最后一条 assistant 消息的文本内容(用于 not_question_ending 条件)
|
|
173
|
+
let lastAssistantText = "";
|
|
174
|
+
if (lastAssistant) {
|
|
175
|
+
const parts = (lastAssistant as unknown as PayloadMessage).content;
|
|
176
|
+
if (Array.isArray(parts)) {
|
|
177
|
+
lastAssistantText = parts
|
|
178
|
+
.filter((p: { type?: string }) => p.type === "text")
|
|
179
|
+
.map((p: { text?: string }) => p.text ?? "")
|
|
180
|
+
.join("");
|
|
181
|
+
} else if (typeof parts === "string") {
|
|
182
|
+
lastAssistantText = parts;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
let pushed = false;
|
|
186
|
+
|
|
187
|
+
// 如果 AI 在问用户问题(等待回复),跳过所有 agent_end 提醒
|
|
188
|
+
const isQuestion = isQuestionEnding(lastAssistantText);
|
|
189
|
+
|
|
190
|
+
for (const rule of rules) {
|
|
191
|
+
const allowedReasons = rule.stopReason ?? ["stop"];
|
|
192
|
+
if (!allowedReasons.includes((stopReason ?? "") as never)) continue;
|
|
193
|
+
if (_agentEndFired.has(rule.comment)) continue;
|
|
194
|
+
if (isQuestion) continue; // AI 在问问题,跳过收尾提醒
|
|
195
|
+
|
|
196
|
+
// 统一条件匹配:通过 ruleMatches 检查 conditions(含 builtin)
|
|
197
|
+
const ctx: BuiltinContext = {
|
|
198
|
+
hasEdits: _toolState.hasEdits,
|
|
199
|
+
gitDirty: hasGitUncommittedChanges(),
|
|
200
|
+
gitUntracked: hasGitUntracked(),
|
|
201
|
+
lastAssistantText,
|
|
202
|
+
};
|
|
203
|
+
const shouldNotify = ruleMatches(rule, {}, ctx);
|
|
204
|
+
if (ctx.gitDirty) _wasDirty = true;
|
|
205
|
+
|
|
206
|
+
if (shouldNotify && rule.action === "notify") {
|
|
207
|
+
_agentEndFired.add(rule.comment);
|
|
208
|
+
pushWarning(rule.reason, rule.comment);
|
|
209
|
+
pushed = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 统一发送唯一的 sendMessage(triggerTurn):
|
|
214
|
+
// - pushed:agent_end 自己推入了 warning
|
|
215
|
+
// - hasWarnings():message_end 或 tool_result 推入了 warning(它们不再自己发 sendMessage)
|
|
216
|
+
// 只发一次,避免多个 hook 各发一次导致第二个 drainHints 拿空 → 空回复
|
|
217
|
+
if (pushed || hasWarnings()) {
|
|
218
|
+
setTimeout(() => {
|
|
219
|
+
try {
|
|
220
|
+
pi.sendMessage(
|
|
221
|
+
{ customType: "shepherd-agent-end", display: false, content: "" },
|
|
222
|
+
{ triggerTurn: true },
|
|
223
|
+
);
|
|
224
|
+
} catch {
|
|
225
|
+
/* session 已替换 */
|
|
226
|
+
}
|
|
227
|
+
}, 0);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// ── session_shutdown ───────────────────────────────────────
|
|
232
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
233
|
+
const rules = loadRules(RULES_DIR, {
|
|
234
|
+
projectRulesPattern: shepherdConfig.projectRulesPattern,
|
|
235
|
+
}).filter((r) => r.hook === "session_shutdown");
|
|
236
|
+
if (rules.length === 0) return;
|
|
237
|
+
for (const rule of rules) {
|
|
238
|
+
// 统一条件匹配
|
|
239
|
+
const builtinCtx: BuiltinContext = {
|
|
240
|
+
hasEdits: false,
|
|
241
|
+
gitDirty: hasGitUncommittedChanges(),
|
|
242
|
+
gitUntracked: hasGitUntracked(),
|
|
243
|
+
};
|
|
244
|
+
const shouldNotify = ruleMatches(rule, {}, builtinCtx);
|
|
245
|
+
if (shouldNotify && rule.action === "notify") {
|
|
246
|
+
ctx.ui?.notify?.(`⚠️ shepherd: ${rule.reason}`, "warning");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// ── tool_call + tool_result(提取到 tool-hooks.ts)────────
|
|
252
|
+
const _rulesOpts = {
|
|
253
|
+
projectRulesPattern: shepherdConfig.projectRulesPattern,
|
|
254
|
+
};
|
|
255
|
+
registerToolCall(pi, _toolState, RULES_DIR, _rulesOpts);
|
|
256
|
+
registerToolResult(pi, _toolState, RULES_DIR, _rulesOpts);
|
|
257
|
+
registerMessageEnd(pi, _toolState, RULES_DIR, _rulesOpts);
|
|
258
|
+
registerSessionCompact(pi, _toolState, RULES_DIR, _rulesOpts);
|
|
259
|
+
|
|
260
|
+
// ── shepherd_rules 工具:规则文件安全编辑 ───────────────────
|
|
261
|
+
registerRulesEditorTool(pi, RULES_DIR, process.cwd());
|
|
262
|
+
}
|
package/package.json
CHANGED
|
@@ -1,55 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-shepherd",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Line count guard and behavior rules extension for pi-coding-agent",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "vitest run",
|
|
8
|
+
"lint": "biome check lib tests index.ts tool-hooks.ts",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@pi-atelier/shared-utils": "^1.0.0",
|
|
13
|
+
"ignore": "^7.0.5"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@earendil-works/pi-coding-agent": ">=0.9.0"
|
|
17
|
+
},
|
|
18
|
+
"peerDependenciesMeta": {
|
|
19
|
+
"@earendil-works/pi-coding-agent": {
|
|
20
|
+
"optional": true
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"optionalDependencies": {
|
|
24
|
+
"@earendil-works/pi-coding-agent": "^0.76.0"
|
|
25
|
+
},
|
|
26
|
+
"pi": {
|
|
27
|
+
"extensions": [
|
|
28
|
+
"."
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@biomejs/biome": "^2.4.15",
|
|
33
|
+
"@types/node": "^25.9.2",
|
|
34
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
35
|
+
"typescript": "^6.0.3",
|
|
36
|
+
"vitest": "^3.0.0"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"index.ts",
|
|
40
|
+
"shepherd/",
|
|
41
|
+
"rules.json",
|
|
42
|
+
"README.md",
|
|
43
|
+
"README.en.md",
|
|
44
|
+
"tsconfig.json",
|
|
45
|
+
"vitest.config.ts",
|
|
46
|
+
"package.json"
|
|
47
|
+
],
|
|
48
|
+
"keywords": [
|
|
49
|
+
"pi-package"
|
|
50
|
+
],
|
|
51
|
+
"homepage": "https://catlain.github.io/pi-atelier/",
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+ssh://git@github.com/catlain/pi-shepherd.git"
|
|
55
|
+
},
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/catlain/pi-shepherd/issues"
|
|
58
|
+
},
|
|
59
|
+
"license": "MIT"
|
|
60
|
+
}
|