@dyyz1993/pi-coding-agent 0.69.17 → 0.69.23
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/dist/core/agent-session.d.ts +12 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +208 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/client-channel.d.ts +61 -0
- package/dist/core/extensions/client-channel.d.ts.map +1 -0
- package/dist/core/extensions/client-channel.js +67 -0
- package/dist/core/extensions/client-channel.js.map +1 -0
- package/dist/core/extensions/index.d.ts +3 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -0
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +13 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +8 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +49 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/include-resolver.d.ts +18 -0
- package/dist/core/include-resolver.d.ts.map +1 -0
- package/dist/core/include-resolver.js +304 -0
- package/dist/core/include-resolver.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +17 -4
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/session-manager.d.ts +8 -4
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +29 -6
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/storage.d.ts +24 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +55 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/tools/path-security.d.ts +15 -0
- package/dist/core/tools/path-security.d.ts.map +1 -0
- package/dist/core/tools/path-security.js +76 -0
- package/dist/core/tools/path-security.js.map +1 -0
- package/dist/core/tools/strip-markdown.d.ts +2 -0
- package/dist/core/tools/strip-markdown.d.ts.map +1 -0
- package/dist/core/tools/strip-markdown.js +8 -0
- package/dist/core/tools/strip-markdown.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client-types.d.ts +1 -0
- package/dist/modes/rpc/rpc-client-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client-types.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +1 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +3 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +5 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +9 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +9 -5
- package/dist/rules-engine/cache.d.ts +0 -4
- package/dist/rules-engine/cache.d.ts.map +0 -1
- package/dist/rules-engine/cache.js +0 -32
- package/dist/rules-engine/cache.js.map +0 -1
- package/dist/rules-engine/config.d.ts +0 -8
- package/dist/rules-engine/config.d.ts.map +0 -1
- package/dist/rules-engine/config.js +0 -56
- package/dist/rules-engine/config.js.map +0 -1
- package/dist/rules-engine/index.d.ts +0 -10
- package/dist/rules-engine/index.d.ts.map +0 -1
- package/dist/rules-engine/index.js +0 -393
- package/dist/rules-engine/index.js.map +0 -1
- package/dist/rules-engine/injector.d.ts +0 -5
- package/dist/rules-engine/injector.d.ts.map +0 -1
- package/dist/rules-engine/injector.js +0 -57
- package/dist/rules-engine/injector.js.map +0 -1
- package/dist/rules-engine/loader.d.ts +0 -8
- package/dist/rules-engine/loader.d.ts.map +0 -1
- package/dist/rules-engine/loader.js +0 -190
- package/dist/rules-engine/loader.js.map +0 -1
- package/dist/rules-engine/matcher.d.ts +0 -3
- package/dist/rules-engine/matcher.d.ts.map +0 -1
- package/dist/rules-engine/matcher.js +0 -48
- package/dist/rules-engine/matcher.js.map +0 -1
- package/dist/rules-engine/types.d.ts +0 -150
- package/dist/rules-engine/types.d.ts.map +0 -1
- package/dist/rules-engine/types.js +0 -2
- package/dist/rules-engine/types.js.map +0 -1
- package/examples/extensions/auto-session-title.ts +0 -82
- package/examples/extensions/file-snapshot.ts +0 -417
- package/examples/extensions/subagent/README.md +0 -172
- package/examples/extensions/subagent/agents/planner.md +0 -37
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/examples/extensions/subagent/agents/scout.md +0 -50
- package/examples/extensions/subagent/agents/worker.md +0 -24
- package/examples/extensions/subagent/agents.ts +0 -126
- package/examples/extensions/subagent/index.ts +0 -987
- package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/examples/extensions/subagent/prompts/implement.md +0 -10
- package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
- package/examples/extensions/subagent-v2/index.ts +0 -849
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
import { Type } from "@dyyz1993/pi-ai";
|
|
2
|
-
import { defineTool } from "@dyyz1993/pi-coding-agent";
|
|
3
|
-
import { ServerChannel } from "../core/extensions/server-channel.js";
|
|
4
|
-
import { getRules, invalidateCache } from "./cache.js";
|
|
5
|
-
import { loadConfig } from "./config.js";
|
|
6
|
-
import { buildSystemPromptSection, buildToolContextSection } from "./injector.js";
|
|
7
|
-
import { matchesAnyGlob } from "./matcher.js";
|
|
8
|
-
export { ServerChannel } from "../core/extensions/server-channel.js";
|
|
9
|
-
export { getRules, invalidateCache } from "./cache.js";
|
|
10
|
-
export { loadConfig, resolveDirs } from "./config.js";
|
|
11
|
-
export { buildCompactContext, buildSystemPromptSection, buildToolContextSection } from "./injector.js";
|
|
12
|
-
export { loadRules, parseFrontmatter, parseRuleFile } from "./loader.js";
|
|
13
|
-
export { matchesAnyGlob, matchGlob } from "./matcher.js";
|
|
14
|
-
const READ_TOOLS = new Set(["read", "grep", "glob"]);
|
|
15
|
-
export default function rulesEnginePlugin(pi) {
|
|
16
|
-
let config = null;
|
|
17
|
-
let rules = [];
|
|
18
|
-
let cachedMatchHash = "";
|
|
19
|
-
let hasSentSnapshot = false;
|
|
20
|
-
let _lastCwd = "";
|
|
21
|
-
let lastMessages = [];
|
|
22
|
-
function rebuildMatchHistory(messages) {
|
|
23
|
-
const history = [];
|
|
24
|
-
for (const msg of messages) {
|
|
25
|
-
if (msg.role !== "toolResult")
|
|
26
|
-
continue;
|
|
27
|
-
const details = msg.details;
|
|
28
|
-
if (!details?.rulesMatched)
|
|
29
|
-
continue;
|
|
30
|
-
const rulesMatched = details.rulesMatched;
|
|
31
|
-
history.push({
|
|
32
|
-
filePath: details.matchedFilePath || "",
|
|
33
|
-
ruleNames: rulesMatched.map((r) => r.name),
|
|
34
|
-
toolName: msg.toolName || "",
|
|
35
|
-
toolCallId: msg.toolCallId || "",
|
|
36
|
-
severity: rulesMatched.some((r) => r.severity === "critical" || r.severity === "high") ? "warning" : "info",
|
|
37
|
-
timestamp: msg.timestamp || 0,
|
|
38
|
-
matchedRuleDetails: rulesMatched,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
return history;
|
|
42
|
-
}
|
|
43
|
-
const rawChannel = pi.registerChannel("rules-engine");
|
|
44
|
-
const channel = new ServerChannel(rawChannel);
|
|
45
|
-
channel.handle("getSnapshot", (params) => {
|
|
46
|
-
const unconditional = getUnconditionalRules();
|
|
47
|
-
const conditional = getConditionalRules();
|
|
48
|
-
const matchHistory = rebuildMatchHistory(lastMessages);
|
|
49
|
-
return {
|
|
50
|
-
type: "snapshot",
|
|
51
|
-
rules: rules.map(toRuleDetail),
|
|
52
|
-
injectedRuleNames: unconditional.map((r) => r.name),
|
|
53
|
-
totalRules: rules.length,
|
|
54
|
-
unconditionalCount: unconditional.length,
|
|
55
|
-
conditionalCount: conditional.length,
|
|
56
|
-
matchHistory,
|
|
57
|
-
lifecycleLog: [],
|
|
58
|
-
loadedAt: Date.now(),
|
|
59
|
-
cacheTTL: config?.cacheTTL || 30000,
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
function getUnconditionalRules() {
|
|
63
|
-
return rules.filter((r) => r.isUnconditional);
|
|
64
|
-
}
|
|
65
|
-
function getConditionalRules() {
|
|
66
|
-
return rules.filter((r) => !r.isUnconditional);
|
|
67
|
-
}
|
|
68
|
-
function getMatchingRules(targetPath) {
|
|
69
|
-
return getConditionalRules().filter((rule) => {
|
|
70
|
-
const globs = rule.frontmatter.paths;
|
|
71
|
-
if (!globs || globs.length === 0)
|
|
72
|
-
return false;
|
|
73
|
-
return matchesAnyGlob(globs, targetPath);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
async function refreshRules(cwd) {
|
|
77
|
-
config = await loadConfig(cwd);
|
|
78
|
-
rules = await getRules(cwd, config);
|
|
79
|
-
}
|
|
80
|
-
function toRuleDetail(r) {
|
|
81
|
-
return {
|
|
82
|
-
name: r.name,
|
|
83
|
-
title: r.title,
|
|
84
|
-
filePath: r.filePath,
|
|
85
|
-
scope: r.scope,
|
|
86
|
-
source: r.source,
|
|
87
|
-
severity: r.frontmatter.severity || "medium",
|
|
88
|
-
isUnconditional: r.isUnconditional,
|
|
89
|
-
paths: r.frontmatter.paths || [],
|
|
90
|
-
description: r.frontmatter.description,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
function buildSnapshot(matchHistory, lifecycleLog) {
|
|
94
|
-
const unconditional = getUnconditionalRules();
|
|
95
|
-
const conditional = getConditionalRules();
|
|
96
|
-
return {
|
|
97
|
-
type: "snapshot",
|
|
98
|
-
rules: rules.map(toRuleDetail),
|
|
99
|
-
injectedRuleNames: unconditional.map((r) => r.name),
|
|
100
|
-
totalRules: rules.length,
|
|
101
|
-
unconditionalCount: unconditional.length,
|
|
102
|
-
conditionalCount: conditional.length,
|
|
103
|
-
matchHistory,
|
|
104
|
-
lifecycleLog,
|
|
105
|
-
loadedAt: Date.now(),
|
|
106
|
-
cacheTTL: config?.cacheTTL || 30000,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
function extractTargetPath(args) {
|
|
110
|
-
if ("filePath" in args && typeof args.filePath === "string")
|
|
111
|
-
return args.filePath;
|
|
112
|
-
if ("path" in args && typeof args.path === "string")
|
|
113
|
-
return args.path;
|
|
114
|
-
if ("pattern" in args && typeof args.pattern === "string")
|
|
115
|
-
return args.pattern;
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
pi.registerTool(defineTool({
|
|
119
|
-
name: "rules_list",
|
|
120
|
-
label: "List Rules",
|
|
121
|
-
description: "List all discovered rules from all configured directories across all scopes",
|
|
122
|
-
parameters: Type.Object({}),
|
|
123
|
-
async execute() {
|
|
124
|
-
const unconditional = getUnconditionalRules();
|
|
125
|
-
const conditional = getConditionalRules();
|
|
126
|
-
const byScope = {};
|
|
127
|
-
for (const r of rules) {
|
|
128
|
-
byScope[r.scope] = (byScope[r.scope] || 0) + 1;
|
|
129
|
-
}
|
|
130
|
-
let output = `# Loaded Rules (${rules.length})\n\n`;
|
|
131
|
-
output += `Scopes: ${Object.entries(byScope)
|
|
132
|
-
.map(([k, v]) => `${k}: ${v}`)
|
|
133
|
-
.join(", ")}\n\n`;
|
|
134
|
-
output += `**Unconditional** (${unconditional.length}):\n`;
|
|
135
|
-
for (const rule of unconditional) {
|
|
136
|
-
output += `- ${rule.title} (${rule.source})\n`;
|
|
137
|
-
}
|
|
138
|
-
output += `\n**Conditional** (${conditional.length}):\n`;
|
|
139
|
-
for (const rule of conditional) {
|
|
140
|
-
output += `- ${rule.title} [${rule.frontmatter.paths?.join(", ")}] (${rule.source})\n`;
|
|
141
|
-
}
|
|
142
|
-
return { content: [{ type: "text", text: output }], details: undefined };
|
|
143
|
-
},
|
|
144
|
-
}));
|
|
145
|
-
pi.registerTool(defineTool({
|
|
146
|
-
name: "rules_match",
|
|
147
|
-
label: "Match Rules",
|
|
148
|
-
description: "Find conditional rules that match a given file path by glob pattern",
|
|
149
|
-
parameters: Type.Object({
|
|
150
|
-
filePath: Type.String({ description: "File path to match" }),
|
|
151
|
-
}),
|
|
152
|
-
async execute(_id, params) {
|
|
153
|
-
const matching = getMatchingRules(params.filePath);
|
|
154
|
-
const unconditional = getUnconditionalRules();
|
|
155
|
-
let output = `# Rule Match: ${params.filePath}\n\n`;
|
|
156
|
-
output += `**Unconditional** (always active, ${unconditional.length}):\n`;
|
|
157
|
-
for (const r of unconditional) {
|
|
158
|
-
output += `- ${r.title}\n`;
|
|
159
|
-
}
|
|
160
|
-
output += `\n**Conditional matches** (${matching.length}):\n`;
|
|
161
|
-
for (const r of matching) {
|
|
162
|
-
const sev = r.frontmatter.severity || "medium";
|
|
163
|
-
output += `- [${sev}] ${r.title} (${r.frontmatter.paths?.join(", ")})\n`;
|
|
164
|
-
}
|
|
165
|
-
return { content: [{ type: "text", text: output }], details: undefined };
|
|
166
|
-
},
|
|
167
|
-
}));
|
|
168
|
-
pi.registerTool(defineTool({
|
|
169
|
-
name: "rules_reload",
|
|
170
|
-
label: "Reload Rules",
|
|
171
|
-
description: "Force reload all rules from disk (clears cache and re-reads config)",
|
|
172
|
-
parameters: Type.Object({}),
|
|
173
|
-
async execute(_id, _params, _signal, _onUpdate, ctx) {
|
|
174
|
-
invalidateCache();
|
|
175
|
-
await refreshRules(ctx.cwd);
|
|
176
|
-
return {
|
|
177
|
-
content: [
|
|
178
|
-
{
|
|
179
|
-
type: "text",
|
|
180
|
-
text: `Rules reloaded: ${rules.length} total (${getUnconditionalRules().length} unconditional, ${getConditionalRules().length} conditional)`,
|
|
181
|
-
},
|
|
182
|
-
],
|
|
183
|
-
details: undefined,
|
|
184
|
-
};
|
|
185
|
-
},
|
|
186
|
-
}));
|
|
187
|
-
pi.registerTool(defineTool({
|
|
188
|
-
name: "rules_show",
|
|
189
|
-
label: "Show Rule",
|
|
190
|
-
description: "Show the full content of a specific rule by name",
|
|
191
|
-
parameters: Type.Object({
|
|
192
|
-
name: Type.String({ description: "Rule name (filename without .md)" }),
|
|
193
|
-
}),
|
|
194
|
-
async execute(_id, params) {
|
|
195
|
-
const rule = rules.find((r) => r.name === params.name);
|
|
196
|
-
if (!rule) {
|
|
197
|
-
return {
|
|
198
|
-
content: [{ type: "text", text: `Rule '${params.name}' not found` }],
|
|
199
|
-
isError: true,
|
|
200
|
-
details: undefined,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
let output = `# ${rule.title}\n\n`;
|
|
204
|
-
output += `- **Name**: ${rule.name}\n`;
|
|
205
|
-
output += `- **Scope**: ${rule.scope}\n`;
|
|
206
|
-
output += `- **Source**: ${rule.source}\n`;
|
|
207
|
-
output += `- **File**: ${rule.filePath}\n`;
|
|
208
|
-
if (rule.frontmatter.paths?.length) {
|
|
209
|
-
output += `- **Paths**: ${rule.frontmatter.paths.join(", ")}\n`;
|
|
210
|
-
}
|
|
211
|
-
if (rule.frontmatter.description) {
|
|
212
|
-
output += `- **Description**: ${rule.frontmatter.description}\n`;
|
|
213
|
-
}
|
|
214
|
-
output += `\n${rule.content}`;
|
|
215
|
-
return { content: [{ type: "text", text: output }], details: undefined };
|
|
216
|
-
},
|
|
217
|
-
}));
|
|
218
|
-
pi.registerCommand("rules", {
|
|
219
|
-
description: "Rules management (list, reload, check <path>, active)",
|
|
220
|
-
handler: async (args, ctx) => {
|
|
221
|
-
const parts = args.trim().split(/\s+/);
|
|
222
|
-
const sub = parts[0] || "list";
|
|
223
|
-
if (sub === "list" || sub === "ls") {
|
|
224
|
-
ctx.ui.notify(`${rules.length} rules loaded (${getUnconditionalRules().length} unconditional, ${getConditionalRules().length} conditional)`, "info");
|
|
225
|
-
}
|
|
226
|
-
else if (sub === "reload") {
|
|
227
|
-
invalidateCache();
|
|
228
|
-
await refreshRules(ctx.cwd);
|
|
229
|
-
ctx.ui.notify(`Rules reloaded: ${rules.length} total`, "info");
|
|
230
|
-
}
|
|
231
|
-
else if (sub === "check" && parts[1]) {
|
|
232
|
-
const target = parts.slice(1).join(" ");
|
|
233
|
-
const matching = getMatchingRules(target);
|
|
234
|
-
ctx.ui.notify(matching.length > 0
|
|
235
|
-
? `${matching.length} rules match ${target}: ${matching.map((r) => r.title).join(", ")}`
|
|
236
|
-
: `No conditional rules match ${target}`, "info");
|
|
237
|
-
}
|
|
238
|
-
else if (sub === "active") {
|
|
239
|
-
const active = getUnconditionalRules();
|
|
240
|
-
ctx.ui.notify(`Active: ${active.length} unconditional (in system prompt), ${getConditionalRules().length} conditional (on file match)`, "info");
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
ctx.ui.notify("Usage: /rules [list|reload|check <path>|active]", "info");
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
});
|
|
247
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
248
|
-
_lastCwd = ctx.cwd;
|
|
249
|
-
await refreshRules(ctx.cwd);
|
|
250
|
-
ctx.ui.setStatus("rules-engine", `Rules: ${rules.length}`);
|
|
251
|
-
const unconditional = getUnconditionalRules();
|
|
252
|
-
const conditional = getConditionalRules();
|
|
253
|
-
if (!hasSentSnapshot) {
|
|
254
|
-
hasSentSnapshot = true;
|
|
255
|
-
const scopeGroups = new Map();
|
|
256
|
-
for (const r of rules) {
|
|
257
|
-
const list = scopeGroups.get(r.scope) || [];
|
|
258
|
-
list.push(r);
|
|
259
|
-
scopeGroups.set(r.scope, list);
|
|
260
|
-
}
|
|
261
|
-
const scannedDirs = [...scopeGroups.entries()].map(([scope, scopeRules]) => ({
|
|
262
|
-
dir: scopeRules[0]?.source || scope,
|
|
263
|
-
fileCount: scopeRules.length,
|
|
264
|
-
ruleNames: scopeRules.map((r) => r.name),
|
|
265
|
-
}));
|
|
266
|
-
channel.emit("snapshot", buildSnapshot([], [
|
|
267
|
-
{
|
|
268
|
-
event: "loaded",
|
|
269
|
-
message: `Loaded ${rules.length} rules (${unconditional.length} unconditional, ${conditional.length} conditional)`,
|
|
270
|
-
ruleCount: rules.length,
|
|
271
|
-
timestamp: Date.now(),
|
|
272
|
-
details: {
|
|
273
|
-
scannedDirs,
|
|
274
|
-
configSource: config ? ".rules-config.json" : "default",
|
|
275
|
-
cacheHit: false,
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
]));
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
pi.on("before_agent_start", async (event) => {
|
|
282
|
-
const unconditional = getUnconditionalRules();
|
|
283
|
-
if (unconditional.length === 0) {
|
|
284
|
-
channel.emit("injected", {
|
|
285
|
-
type: "injected",
|
|
286
|
-
ruleNames: [],
|
|
287
|
-
systemPromptLength: event.systemPrompt.length,
|
|
288
|
-
});
|
|
289
|
-
return undefined;
|
|
290
|
-
}
|
|
291
|
-
const sources = [...new Set(unconditional.map((r) => r.source))];
|
|
292
|
-
const section = buildSystemPromptSection(unconditional, sources);
|
|
293
|
-
const newPrompt = event.systemPrompt + section;
|
|
294
|
-
channel.emit("injected", {
|
|
295
|
-
type: "injected",
|
|
296
|
-
ruleNames: unconditional.map((r) => r.name),
|
|
297
|
-
systemPromptLength: newPrompt.length,
|
|
298
|
-
});
|
|
299
|
-
return {
|
|
300
|
-
systemPrompt: newPrompt,
|
|
301
|
-
};
|
|
302
|
-
});
|
|
303
|
-
pi.on("tool_result", async (event) => {
|
|
304
|
-
if (!READ_TOOLS.has(event.toolName))
|
|
305
|
-
return undefined;
|
|
306
|
-
const targetPath = extractTargetPath(event.input);
|
|
307
|
-
if (!targetPath)
|
|
308
|
-
return undefined;
|
|
309
|
-
const matching = getMatchingRules(targetPath);
|
|
310
|
-
if (matching.length === 0)
|
|
311
|
-
return undefined;
|
|
312
|
-
const matchedRuleDetails = matching.map((r) => ({
|
|
313
|
-
name: r.name,
|
|
314
|
-
title: r.title,
|
|
315
|
-
severity: r.frontmatter.severity || "medium",
|
|
316
|
-
matchedGlob: r.frontmatter.paths?.find((p) => matchesAnyGlob([p], targetPath)) || r.frontmatter.paths?.[0] || "",
|
|
317
|
-
}));
|
|
318
|
-
const contextSection = buildToolContextSection(matching, targetPath);
|
|
319
|
-
const hasCritical = matching.some((r) => r.frontmatter.severity === "critical");
|
|
320
|
-
const hasHigh = matching.some((r) => r.frontmatter.severity === "high");
|
|
321
|
-
channel.emit("matched", {
|
|
322
|
-
type: "matched",
|
|
323
|
-
filePath: targetPath,
|
|
324
|
-
matchedRules: matchedRuleDetails,
|
|
325
|
-
toolName: event.toolName,
|
|
326
|
-
toolCallId: event.toolCallId,
|
|
327
|
-
severity: hasCritical ? "warning" : hasHigh ? "warning" : "info",
|
|
328
|
-
timestamp: Date.now(),
|
|
329
|
-
});
|
|
330
|
-
return {
|
|
331
|
-
content: [...event.content, { type: "text", text: `\n\n${contextSection}` }],
|
|
332
|
-
details: {
|
|
333
|
-
...(event.details || {}),
|
|
334
|
-
rulesMatched: matchedRuleDetails,
|
|
335
|
-
matchedFilePath: targetPath,
|
|
336
|
-
},
|
|
337
|
-
};
|
|
338
|
-
});
|
|
339
|
-
pi.on("context", async (event) => {
|
|
340
|
-
lastMessages = event.messages;
|
|
341
|
-
const matchHistory = rebuildMatchHistory(event.messages);
|
|
342
|
-
const hash = JSON.stringify(matchHistory.map((r) => `${r.toolCallId}:${r.filePath}`));
|
|
343
|
-
if (hash !== cachedMatchHash) {
|
|
344
|
-
cachedMatchHash = hash;
|
|
345
|
-
const unconditional = getUnconditionalRules();
|
|
346
|
-
const conditional = getConditionalRules();
|
|
347
|
-
channel.emit("snapshot", {
|
|
348
|
-
type: "snapshot",
|
|
349
|
-
rules: rules.map(toRuleDetail),
|
|
350
|
-
injectedRuleNames: unconditional.map((r) => r.name),
|
|
351
|
-
totalRules: rules.length,
|
|
352
|
-
unconditionalCount: unconditional.length,
|
|
353
|
-
conditionalCount: conditional.length,
|
|
354
|
-
matchHistory,
|
|
355
|
-
lifecycleLog: [],
|
|
356
|
-
loadedAt: Date.now(),
|
|
357
|
-
cacheTTL: config?.cacheTTL || 30000,
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
return undefined;
|
|
361
|
-
});
|
|
362
|
-
pi.on("session_compact", async (_event, ctx) => {
|
|
363
|
-
cachedMatchHash = "";
|
|
364
|
-
lastMessages = [];
|
|
365
|
-
ctx.ui.setStatus("rules-engine", `Rules: ${rules.length} (re-injected after compact)`);
|
|
366
|
-
});
|
|
367
|
-
pi.on("turn_end", async () => {
|
|
368
|
-
if (lastMessages.length === 0 && rules.length > 0)
|
|
369
|
-
return;
|
|
370
|
-
const matchHistory = rebuildMatchHistory(lastMessages);
|
|
371
|
-
const hash = JSON.stringify(matchHistory.map((r) => `${r.toolCallId}:${r.filePath}`));
|
|
372
|
-
if (hash !== cachedMatchHash) {
|
|
373
|
-
cachedMatchHash = hash;
|
|
374
|
-
channel.emit("snapshot", {
|
|
375
|
-
type: "snapshot",
|
|
376
|
-
rules: rules.map(toRuleDetail),
|
|
377
|
-
injectedRuleNames: getUnconditionalRules().map((r) => r.name),
|
|
378
|
-
totalRules: rules.length,
|
|
379
|
-
unconditionalCount: getUnconditionalRules().length,
|
|
380
|
-
conditionalCount: getConditionalRules().length,
|
|
381
|
-
matchHistory,
|
|
382
|
-
lifecycleLog: [],
|
|
383
|
-
loadedAt: Date.now(),
|
|
384
|
-
cacheTTL: config?.cacheTTL || 30000,
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
pi.on("session_shutdown", async (_event, ctx) => {
|
|
389
|
-
channel.emit("unloaded", { type: "unloaded", reason: "session_shutdown" });
|
|
390
|
-
ctx.ui.setStatus("rules-engine", undefined);
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules-engine/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { Type } from \"@dyyz1993/pi-ai\";\nimport { defineTool, type ExtensionAPI } from \"@dyyz1993/pi-coding-agent\";\nimport { ServerChannel } from \"../core/extensions/server-channel.js\";\nimport { getRules, invalidateCache } from \"./cache.js\";\nimport { loadConfig } from \"./config.js\";\nimport { buildSystemPromptSection, buildToolContextSection } from \"./injector.js\";\nimport { matchesAnyGlob } from \"./matcher.js\";\nimport type {\n\tInjectedPayload,\n\tLifecycleEntry,\n\tMatchedRuleDetail,\n\tMatchRecord,\n\tParsedRule,\n\tRuleDetail,\n\tRuleSeverity,\n\tRulesChannelContract,\n\tRulesChannelEvent,\n\tRulesConfig,\n\tScannedDir,\n\tSnapshotPayload,\n} from \"./types.js\";\n\nexport { ServerChannel } from \"../core/extensions/server-channel.js\";\nexport { getRules, invalidateCache } from \"./cache.js\";\nexport { loadConfig, resolveDirs } from \"./config.js\";\nexport { buildCompactContext, buildSystemPromptSection, buildToolContextSection } from \"./injector.js\";\nexport { loadRules, parseFrontmatter, parseRuleFile } from \"./loader.js\";\nexport { matchesAnyGlob, matchGlob } from \"./matcher.js\";\nexport type {\n\tInjectedPayload,\n\tLifecycleEntry,\n\tMatchedPayload,\n\tMatchRecord,\n\tParsedRule,\n\tReloadedPayload,\n\tRuleDetail,\n\tRuleFrontmatter,\n\tRuleScope,\n\tRuleSeverity,\n\tRulesChannelContract,\n\tRulesChannelEvent,\n\tRulesConfig,\n\tScannedDir,\n\tSnapshotPayload,\n\tUnloadedPayload,\n} from \"./types.js\";\n\nconst READ_TOOLS = new Set([\"read\", \"grep\", \"glob\"]);\n\nexport default function rulesEnginePlugin(pi: ExtensionAPI) {\n\tlet config: RulesConfig | null = null;\n\tlet rules: ParsedRule[] = [];\n\tlet cachedMatchHash = \"\";\n\tlet hasSentSnapshot = false;\n\tlet _lastCwd = \"\";\n\tlet lastMessages: unknown[] = [];\n\n\tfunction rebuildMatchHistory(messages: unknown[]): MatchRecord[] {\n\t\tconst history: MatchRecord[] = [];\n\t\tfor (const msg of messages) {\n\t\t\tif ((msg as Record<string, unknown>).role !== \"toolResult\") continue;\n\t\t\tconst details = (msg as { details?: Record<string, unknown> }).details;\n\t\t\tif (!details?.rulesMatched) continue;\n\t\t\tconst rulesMatched = details.rulesMatched as MatchedRuleDetail[];\n\t\t\thistory.push({\n\t\t\t\tfilePath: (details.matchedFilePath as string) || \"\",\n\t\t\t\truleNames: rulesMatched.map((r) => r.name),\n\t\t\t\ttoolName: (msg as { toolName?: string }).toolName || \"\",\n\t\t\t\ttoolCallId: (msg as { toolCallId?: string }).toolCallId || \"\",\n\t\t\t\tseverity: rulesMatched.some((r) => r.severity === \"critical\" || r.severity === \"high\") ? \"warning\" : \"info\",\n\t\t\t\ttimestamp: (msg as { timestamp?: number }).timestamp || 0,\n\t\t\t\tmatchedRuleDetails: rulesMatched,\n\t\t\t});\n\t\t}\n\t\treturn history;\n\t}\n\n\tconst rawChannel = pi.registerChannel(\"rules-engine\");\n\tconst channel = new ServerChannel<RulesChannelContract>(rawChannel);\n\n\tchannel.handle(\"getSnapshot\", (params) => {\n\t\tconst unconditional = getUnconditionalRules();\n\t\tconst conditional = getConditionalRules();\n\t\tconst matchHistory = rebuildMatchHistory(lastMessages);\n\t\treturn {\n\t\t\ttype: \"snapshot\" as const,\n\t\t\trules: rules.map(toRuleDetail),\n\t\t\tinjectedRuleNames: unconditional.map((r) => r.name),\n\t\t\ttotalRules: rules.length,\n\t\t\tunconditionalCount: unconditional.length,\n\t\t\tconditionalCount: conditional.length,\n\t\t\tmatchHistory,\n\t\t\tlifecycleLog: [] as LifecycleEntry[],\n\t\t\tloadedAt: Date.now(),\n\t\t\tcacheTTL: config?.cacheTTL || 30000,\n\t\t};\n\t});\n\n\tfunction getUnconditionalRules(): ParsedRule[] {\n\t\treturn rules.filter((r) => r.isUnconditional);\n\t}\n\n\tfunction getConditionalRules(): ParsedRule[] {\n\t\treturn rules.filter((r) => !r.isUnconditional);\n\t}\n\n\tfunction getMatchingRules(targetPath: string): ParsedRule[] {\n\t\treturn getConditionalRules().filter((rule) => {\n\t\t\tconst globs = rule.frontmatter.paths;\n\t\t\tif (!globs || globs.length === 0) return false;\n\t\t\treturn matchesAnyGlob(globs, targetPath);\n\t\t});\n\t}\n\n\tasync function refreshRules(cwd: string): Promise<void> {\n\t\tconfig = await loadConfig(cwd);\n\t\trules = await getRules(cwd, config);\n\t}\n\n\tfunction toRuleDetail(r: ParsedRule): RuleDetail {\n\t\treturn {\n\t\t\tname: r.name,\n\t\t\ttitle: r.title,\n\t\t\tfilePath: r.filePath,\n\t\t\tscope: r.scope,\n\t\t\tsource: r.source,\n\t\t\tseverity: r.frontmatter.severity || (\"medium\" as RuleSeverity),\n\t\t\tisUnconditional: r.isUnconditional,\n\t\t\tpaths: r.frontmatter.paths || [],\n\t\t\tdescription: r.frontmatter.description,\n\t\t};\n\t}\n\n\tfunction buildSnapshot(matchHistory: MatchRecord[], lifecycleLog: LifecycleEntry[]): RulesChannelEvent {\n\t\tconst unconditional = getUnconditionalRules();\n\t\tconst conditional = getConditionalRules();\n\t\treturn {\n\t\t\ttype: \"snapshot\",\n\t\t\trules: rules.map(toRuleDetail),\n\t\t\tinjectedRuleNames: unconditional.map((r) => r.name),\n\t\t\ttotalRules: rules.length,\n\t\t\tunconditionalCount: unconditional.length,\n\t\t\tconditionalCount: conditional.length,\n\t\t\tmatchHistory,\n\t\t\tlifecycleLog,\n\t\t\tloadedAt: Date.now(),\n\t\t\tcacheTTL: config?.cacheTTL || 30000,\n\t\t};\n\t}\n\n\tfunction extractTargetPath(args: Record<string, unknown>): string | undefined {\n\t\tif (\"filePath\" in args && typeof args.filePath === \"string\") return args.filePath;\n\t\tif (\"path\" in args && typeof args.path === \"string\") return args.path;\n\t\tif (\"pattern\" in args && typeof args.pattern === \"string\") return args.pattern;\n\t\treturn undefined;\n\t}\n\n\tpi.registerTool(\n\t\tdefineTool({\n\t\t\tname: \"rules_list\",\n\t\t\tlabel: \"List Rules\",\n\t\t\tdescription: \"List all discovered rules from all configured directories across all scopes\",\n\t\t\tparameters: Type.Object({}),\n\t\t\tasync execute() {\n\t\t\t\tconst unconditional = getUnconditionalRules();\n\t\t\t\tconst conditional = getConditionalRules();\n\n\t\t\t\tconst byScope: Record<string, number> = {};\n\t\t\t\tfor (const r of rules) {\n\t\t\t\t\tbyScope[r.scope] = (byScope[r.scope] || 0) + 1;\n\t\t\t\t}\n\n\t\t\t\tlet output = `# Loaded Rules (${rules.length})\\n\\n`;\n\t\t\t\toutput += `Scopes: ${Object.entries(byScope)\n\t\t\t\t\t.map(([k, v]) => `${k}: ${v}`)\n\t\t\t\t\t.join(\", \")}\\n\\n`;\n\n\t\t\t\toutput += `**Unconditional** (${unconditional.length}):\\n`;\n\t\t\t\tfor (const rule of unconditional) {\n\t\t\t\t\toutput += `- ${rule.title} (${rule.source})\\n`;\n\t\t\t\t}\n\n\t\t\t\toutput += `\\n**Conditional** (${conditional.length}):\\n`;\n\t\t\t\tfor (const rule of conditional) {\n\t\t\t\t\toutput += `- ${rule.title} [${rule.frontmatter.paths?.join(\", \")}] (${rule.source})\\n`;\n\t\t\t\t}\n\n\t\t\t\treturn { content: [{ type: \"text\", text: output }], details: undefined };\n\t\t\t},\n\t\t}),\n\t);\n\n\tpi.registerTool(\n\t\tdefineTool({\n\t\t\tname: \"rules_match\",\n\t\t\tlabel: \"Match Rules\",\n\t\t\tdescription: \"Find conditional rules that match a given file path by glob pattern\",\n\t\t\tparameters: Type.Object({\n\t\t\t\tfilePath: Type.String({ description: \"File path to match\" }),\n\t\t\t}),\n\t\t\tasync execute(_id, params) {\n\t\t\t\tconst matching = getMatchingRules(params.filePath);\n\t\t\t\tconst unconditional = getUnconditionalRules();\n\n\t\t\t\tlet output = `# Rule Match: ${params.filePath}\\n\\n`;\n\t\t\t\toutput += `**Unconditional** (always active, ${unconditional.length}):\\n`;\n\t\t\t\tfor (const r of unconditional) {\n\t\t\t\t\toutput += `- ${r.title}\\n`;\n\t\t\t\t}\n\t\t\t\toutput += `\\n**Conditional matches** (${matching.length}):\\n`;\n\t\t\t\tfor (const r of matching) {\n\t\t\t\t\tconst sev = r.frontmatter.severity || \"medium\";\n\t\t\t\t\toutput += `- [${sev}] ${r.title} (${r.frontmatter.paths?.join(\", \")})\\n`;\n\t\t\t\t}\n\n\t\t\t\treturn { content: [{ type: \"text\", text: output }], details: undefined };\n\t\t\t},\n\t\t}),\n\t);\n\n\tpi.registerTool(\n\t\tdefineTool({\n\t\t\tname: \"rules_reload\",\n\t\t\tlabel: \"Reload Rules\",\n\t\t\tdescription: \"Force reload all rules from disk (clears cache and re-reads config)\",\n\t\t\tparameters: Type.Object({}),\n\t\t\tasync execute(_id, _params, _signal, _onUpdate, ctx) {\n\t\t\t\tinvalidateCache();\n\t\t\t\tawait refreshRules(ctx.cwd);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Rules reloaded: ${rules.length} total (${getUnconditionalRules().length} unconditional, ${getConditionalRules().length} conditional)`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t},\n\t\t}),\n\t);\n\n\tpi.registerTool(\n\t\tdefineTool({\n\t\t\tname: \"rules_show\",\n\t\t\tlabel: \"Show Rule\",\n\t\t\tdescription: \"Show the full content of a specific rule by name\",\n\t\t\tparameters: Type.Object({\n\t\t\t\tname: Type.String({ description: \"Rule name (filename without .md)\" }),\n\t\t\t}),\n\t\t\tasync execute(_id, params) {\n\t\t\t\tconst rule = rules.find((r) => r.name === params.name);\n\t\t\t\tif (!rule) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Rule '${params.name}' not found` }],\n\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tlet output = `# ${rule.title}\\n\\n`;\n\t\t\t\toutput += `- **Name**: ${rule.name}\\n`;\n\t\t\t\toutput += `- **Scope**: ${rule.scope}\\n`;\n\t\t\t\toutput += `- **Source**: ${rule.source}\\n`;\n\t\t\t\toutput += `- **File**: ${rule.filePath}\\n`;\n\t\t\t\tif (rule.frontmatter.paths?.length) {\n\t\t\t\t\toutput += `- **Paths**: ${rule.frontmatter.paths.join(\", \")}\\n`;\n\t\t\t\t}\n\t\t\t\tif (rule.frontmatter.description) {\n\t\t\t\t\toutput += `- **Description**: ${rule.frontmatter.description}\\n`;\n\t\t\t\t}\n\t\t\t\toutput += `\\n${rule.content}`;\n\n\t\t\t\treturn { content: [{ type: \"text\", text: output }], details: undefined };\n\t\t\t},\n\t\t}),\n\t);\n\n\tpi.registerCommand(\"rules\", {\n\t\tdescription: \"Rules management (list, reload, check <path>, active)\",\n\t\thandler: async (args, ctx) => {\n\t\t\tconst parts = args.trim().split(/\\s+/);\n\t\t\tconst sub = parts[0] || \"list\";\n\n\t\t\tif (sub === \"list\" || sub === \"ls\") {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`${rules.length} rules loaded (${getUnconditionalRules().length} unconditional, ${getConditionalRules().length} conditional)`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t} else if (sub === \"reload\") {\n\t\t\t\tinvalidateCache();\n\t\t\t\tawait refreshRules(ctx.cwd);\n\t\t\t\tctx.ui.notify(`Rules reloaded: ${rules.length} total`, \"info\");\n\t\t\t} else if (sub === \"check\" && parts[1]) {\n\t\t\t\tconst target = parts.slice(1).join(\" \");\n\t\t\t\tconst matching = getMatchingRules(target);\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\tmatching.length > 0\n\t\t\t\t\t\t? `${matching.length} rules match ${target}: ${matching.map((r) => r.title).join(\", \")}`\n\t\t\t\t\t\t: `No conditional rules match ${target}`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t} else if (sub === \"active\") {\n\t\t\t\tconst active = getUnconditionalRules();\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`Active: ${active.length} unconditional (in system prompt), ${getConditionalRules().length} conditional (on file match)`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tctx.ui.notify(\"Usage: /rules [list|reload|check <path>|active]\", \"info\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\t_lastCwd = ctx.cwd;\n\t\tawait refreshRules(ctx.cwd);\n\t\tctx.ui.setStatus(\"rules-engine\", `Rules: ${rules.length}`);\n\n\t\tconst unconditional = getUnconditionalRules();\n\t\tconst conditional = getConditionalRules();\n\n\t\tif (!hasSentSnapshot) {\n\t\t\thasSentSnapshot = true;\n\n\t\t\tconst scopeGroups = new Map<string, ParsedRule[]>();\n\t\t\tfor (const r of rules) {\n\t\t\t\tconst list = scopeGroups.get(r.scope) || [];\n\t\t\t\tlist.push(r);\n\t\t\t\tscopeGroups.set(r.scope, list);\n\t\t\t}\n\n\t\t\tconst scannedDirs: ScannedDir[] = [...scopeGroups.entries()].map(([scope, scopeRules]) => ({\n\t\t\t\tdir: scopeRules[0]?.source || scope,\n\t\t\t\tfileCount: scopeRules.length,\n\t\t\t\truleNames: scopeRules.map((r) => r.name),\n\t\t\t}));\n\n\t\t\tchannel.emit(\n\t\t\t\t\"snapshot\",\n\t\t\t\tbuildSnapshot(\n\t\t\t\t\t[],\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tevent: \"loaded\",\n\t\t\t\t\t\t\tmessage: `Loaded ${rules.length} rules (${unconditional.length} unconditional, ${conditional.length} conditional)`,\n\t\t\t\t\t\t\truleCount: rules.length,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\tscannedDirs,\n\t\t\t\t\t\t\t\tconfigSource: config ? \".rules-config.json\" : \"default\",\n\t\t\t\t\t\t\t\tcacheHit: false,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t});\n\n\tpi.on(\"before_agent_start\", async (event) => {\n\t\tconst unconditional = getUnconditionalRules();\n\n\t\tif (unconditional.length === 0) {\n\t\t\tchannel.emit(\"injected\", {\n\t\t\t\ttype: \"injected\",\n\t\t\t\truleNames: [],\n\t\t\t\tsystemPromptLength: event.systemPrompt.length,\n\t\t\t});\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst sources = [...new Set(unconditional.map((r) => r.source))];\n\t\tconst section = buildSystemPromptSection(unconditional, sources);\n\t\tconst newPrompt = event.systemPrompt + section;\n\n\t\tchannel.emit(\"injected\", {\n\t\t\ttype: \"injected\",\n\t\t\truleNames: unconditional.map((r) => r.name),\n\t\t\tsystemPromptLength: newPrompt.length,\n\t\t});\n\n\t\treturn {\n\t\t\tsystemPrompt: newPrompt,\n\t\t};\n\t});\n\n\tpi.on(\"tool_result\", async (event) => {\n\t\tif (!READ_TOOLS.has(event.toolName)) return undefined;\n\n\t\tconst targetPath = extractTargetPath(event.input);\n\t\tif (!targetPath) return undefined;\n\n\t\tconst matching = getMatchingRules(targetPath);\n\t\tif (matching.length === 0) return undefined;\n\n\t\tconst matchedRuleDetails: MatchedRuleDetail[] = matching.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttitle: r.title,\n\t\t\tseverity: r.frontmatter.severity || (\"medium\" as RuleSeverity),\n\t\t\tmatchedGlob:\n\t\t\t\tr.frontmatter.paths?.find((p) => matchesAnyGlob([p], targetPath)) || r.frontmatter.paths?.[0] || \"\",\n\t\t}));\n\n\t\tconst contextSection = buildToolContextSection(matching, targetPath);\n\n\t\tconst hasCritical = matching.some((r) => r.frontmatter.severity === \"critical\");\n\t\tconst hasHigh = matching.some((r) => r.frontmatter.severity === \"high\");\n\n\t\tchannel.emit(\"matched\", {\n\t\t\ttype: \"matched\",\n\t\t\tfilePath: targetPath,\n\t\t\tmatchedRules: matchedRuleDetails,\n\t\t\ttoolName: event.toolName,\n\t\t\ttoolCallId: event.toolCallId,\n\t\t\tseverity: hasCritical ? \"warning\" : hasHigh ? \"warning\" : \"info\",\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\treturn {\n\t\t\tcontent: [...event.content, { type: \"text\" as const, text: `\\n\\n${contextSection}` }],\n\t\t\tdetails: {\n\t\t\t\t...((event.details as Record<string, unknown>) || {}),\n\t\t\t\trulesMatched: matchedRuleDetails,\n\t\t\t\tmatchedFilePath: targetPath,\n\t\t\t},\n\t\t};\n\t});\n\n\tpi.on(\"context\", async (event) => {\n\t\tlastMessages = event.messages;\n\t\tconst matchHistory = rebuildMatchHistory(event.messages);\n\n\t\tconst hash = JSON.stringify(matchHistory.map((r) => `${r.toolCallId}:${r.filePath}`));\n\t\tif (hash !== cachedMatchHash) {\n\t\t\tcachedMatchHash = hash;\n\t\t\tconst unconditional = getUnconditionalRules();\n\t\t\tconst conditional = getConditionalRules();\n\t\t\tchannel.emit(\"snapshot\", {\n\t\t\t\ttype: \"snapshot\",\n\t\t\t\trules: rules.map(toRuleDetail),\n\t\t\t\tinjectedRuleNames: unconditional.map((r) => r.name),\n\t\t\t\ttotalRules: rules.length,\n\t\t\t\tunconditionalCount: unconditional.length,\n\t\t\t\tconditionalCount: conditional.length,\n\t\t\t\tmatchHistory,\n\t\t\t\tlifecycleLog: [],\n\t\t\t\tloadedAt: Date.now(),\n\t\t\t\tcacheTTL: config?.cacheTTL || 30000,\n\t\t\t});\n\t\t}\n\n\t\treturn undefined;\n\t});\n\n\tpi.on(\"session_compact\", async (_event, ctx) => {\n\t\tcachedMatchHash = \"\";\n\t\tlastMessages = [];\n\t\tctx.ui.setStatus(\"rules-engine\", `Rules: ${rules.length} (re-injected after compact)`);\n\t});\n\n\tpi.on(\"turn_end\", async () => {\n\t\tif (lastMessages.length === 0 && rules.length > 0) return;\n\t\tconst matchHistory = rebuildMatchHistory(lastMessages);\n\t\tconst hash = JSON.stringify(matchHistory.map((r) => `${r.toolCallId}:${r.filePath}`));\n\t\tif (hash !== cachedMatchHash) {\n\t\t\tcachedMatchHash = hash;\n\t\t\tchannel.emit(\"snapshot\", {\n\t\t\t\ttype: \"snapshot\",\n\t\t\t\trules: rules.map(toRuleDetail),\n\t\t\t\tinjectedRuleNames: getUnconditionalRules().map((r) => r.name),\n\t\t\t\ttotalRules: rules.length,\n\t\t\t\tunconditionalCount: getUnconditionalRules().length,\n\t\t\t\tconditionalCount: getConditionalRules().length,\n\t\t\t\tmatchHistory,\n\t\t\t\tlifecycleLog: [],\n\t\t\t\tloadedAt: Date.now(),\n\t\t\t\tcacheTTL: config?.cacheTTL || 30000,\n\t\t\t});\n\t\t}\n\t});\n\n\tpi.on(\"session_shutdown\", async (_event, ctx) => {\n\t\tchannel.emit(\"unloaded\", { type: \"unloaded\", reason: \"session_shutdown\" });\n\t\tctx.ui.setStatus(\"rules-engine\", undefined);\n\t});\n}\n"]}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { ParsedRule } from "./types.js";
|
|
2
|
-
export declare function buildSystemPromptSection(rules: ParsedRule[], sources: string[]): string;
|
|
3
|
-
export declare function buildToolContextSection(rules: ParsedRule[], targetPath: string): string;
|
|
4
|
-
export declare function buildCompactContext(rules: ParsedRule[]): string;
|
|
5
|
-
//# sourceMappingURL=injector.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"injector.d.ts","sourceRoot":"","sources":["../../src/rules-engine/injector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAU7C,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAuBvF;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAkBvF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAW/D","sourcesContent":["import type { ParsedRule } from \"./types.js\";\n\nconst SEVERITY_ICONS: Record<string, string> = {\n\tcritical: \"🔴\",\n\thigh: \"🟠\",\n\tmedium: \"🟡\",\n\tlow: \"🔵\",\n\thint: \"💡\",\n};\n\nexport function buildSystemPromptSection(rules: ParsedRule[], sources: string[]): string {\n\tif (rules.length === 0) return \"\";\n\n\tconst lines: string[] = [\n\t\t\"\",\n\t\t\"---\",\n\t\t`## Project Rules (auto-loaded from ${sources.join(\", \")})`,\n\t\t\"The following rules are always active. Follow them strictly.\",\n\t\t\"---\",\n\t\t\"\",\n\t];\n\n\tfor (const rule of rules) {\n\t\tlines.push(`## Rule: ${rule.name} — ${rule.title}`);\n\t\tif (rule.frontmatter.description) {\n\t\t\tlines.push(`> ${rule.frontmatter.description}`);\n\t\t}\n\t\tlines.push(\"\");\n\t\tlines.push(rule.content);\n\t\tlines.push(\"\");\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildToolContextSection(rules: ParsedRule[], targetPath: string): string {\n\tif (rules.length === 0) return \"\";\n\n\tconst lines: string[] = [`[rules-engine] Conditional rules matched for ${targetPath}:`, \"\"];\n\n\tfor (const rule of rules) {\n\t\tconst severity = rule.frontmatter.severity || \"medium\";\n\t\tconst icon = SEVERITY_ICONS[severity] || \"🟡\";\n\t\tlines.push(`## ${icon} ${rule.title} [${severity}]`);\n\t\tif (rule.frontmatter.description) {\n\t\t\tlines.push(`> ${rule.frontmatter.description}`);\n\t\t}\n\t\tlines.push(\"\");\n\t\tlines.push(rule.content);\n\t\tlines.push(\"\");\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildCompactContext(rules: ParsedRule[]): string {\n\tif (rules.length === 0) return \"\";\n\n\tconst lines: string[] = [\"## Active Rules (persist across compaction)\", \"\"];\n\n\tfor (const rule of rules) {\n\t\tconst desc = rule.frontmatter.description || rule.content.split(\"\\n\")[0];\n\t\tlines.push(`- **${rule.title}** (${rule.name}): ${desc}`);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n"]}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const SEVERITY_ICONS = {
|
|
2
|
-
critical: "🔴",
|
|
3
|
-
high: "🟠",
|
|
4
|
-
medium: "🟡",
|
|
5
|
-
low: "🔵",
|
|
6
|
-
hint: "💡",
|
|
7
|
-
};
|
|
8
|
-
export function buildSystemPromptSection(rules, sources) {
|
|
9
|
-
if (rules.length === 0)
|
|
10
|
-
return "";
|
|
11
|
-
const lines = [
|
|
12
|
-
"",
|
|
13
|
-
"---",
|
|
14
|
-
`## Project Rules (auto-loaded from ${sources.join(", ")})`,
|
|
15
|
-
"The following rules are always active. Follow them strictly.",
|
|
16
|
-
"---",
|
|
17
|
-
"",
|
|
18
|
-
];
|
|
19
|
-
for (const rule of rules) {
|
|
20
|
-
lines.push(`## Rule: ${rule.name} — ${rule.title}`);
|
|
21
|
-
if (rule.frontmatter.description) {
|
|
22
|
-
lines.push(`> ${rule.frontmatter.description}`);
|
|
23
|
-
}
|
|
24
|
-
lines.push("");
|
|
25
|
-
lines.push(rule.content);
|
|
26
|
-
lines.push("");
|
|
27
|
-
}
|
|
28
|
-
return lines.join("\n");
|
|
29
|
-
}
|
|
30
|
-
export function buildToolContextSection(rules, targetPath) {
|
|
31
|
-
if (rules.length === 0)
|
|
32
|
-
return "";
|
|
33
|
-
const lines = [`[rules-engine] Conditional rules matched for ${targetPath}:`, ""];
|
|
34
|
-
for (const rule of rules) {
|
|
35
|
-
const severity = rule.frontmatter.severity || "medium";
|
|
36
|
-
const icon = SEVERITY_ICONS[severity] || "🟡";
|
|
37
|
-
lines.push(`## ${icon} ${rule.title} [${severity}]`);
|
|
38
|
-
if (rule.frontmatter.description) {
|
|
39
|
-
lines.push(`> ${rule.frontmatter.description}`);
|
|
40
|
-
}
|
|
41
|
-
lines.push("");
|
|
42
|
-
lines.push(rule.content);
|
|
43
|
-
lines.push("");
|
|
44
|
-
}
|
|
45
|
-
return lines.join("\n");
|
|
46
|
-
}
|
|
47
|
-
export function buildCompactContext(rules) {
|
|
48
|
-
if (rules.length === 0)
|
|
49
|
-
return "";
|
|
50
|
-
const lines = ["## Active Rules (persist across compaction)", ""];
|
|
51
|
-
for (const rule of rules) {
|
|
52
|
-
const desc = rule.frontmatter.description || rule.content.split("\n")[0];
|
|
53
|
-
lines.push(`- **${rule.title}** (${rule.name}): ${desc}`);
|
|
54
|
-
}
|
|
55
|
-
return lines.join("\n");
|
|
56
|
-
}
|
|
57
|
-
//# sourceMappingURL=injector.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"injector.js","sourceRoot":"","sources":["../../src/rules-engine/injector.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ParsedRule } from \"./types.js\";\n\nconst SEVERITY_ICONS: Record<string, string> = {\n\tcritical: \"🔴\",\n\thigh: \"🟠\",\n\tmedium: \"🟡\",\n\tlow: \"🔵\",\n\thint: \"💡\",\n};\n\nexport function buildSystemPromptSection(rules: ParsedRule[], sources: string[]): string {\n\tif (rules.length === 0) return \"\";\n\n\tconst lines: string[] = [\n\t\t\"\",\n\t\t\"---\",\n\t\t`## Project Rules (auto-loaded from ${sources.join(\", \")})`,\n\t\t\"The following rules are always active. Follow them strictly.\",\n\t\t\"---\",\n\t\t\"\",\n\t];\n\n\tfor (const rule of rules) {\n\t\tlines.push(`## Rule: ${rule.name} — ${rule.title}`);\n\t\tif (rule.frontmatter.description) {\n\t\t\tlines.push(`> ${rule.frontmatter.description}`);\n\t\t}\n\t\tlines.push(\"\");\n\t\tlines.push(rule.content);\n\t\tlines.push(\"\");\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildToolContextSection(rules: ParsedRule[], targetPath: string): string {\n\tif (rules.length === 0) return \"\";\n\n\tconst lines: string[] = [`[rules-engine] Conditional rules matched for ${targetPath}:`, \"\"];\n\n\tfor (const rule of rules) {\n\t\tconst severity = rule.frontmatter.severity || \"medium\";\n\t\tconst icon = SEVERITY_ICONS[severity] || \"🟡\";\n\t\tlines.push(`## ${icon} ${rule.title} [${severity}]`);\n\t\tif (rule.frontmatter.description) {\n\t\t\tlines.push(`> ${rule.frontmatter.description}`);\n\t\t}\n\t\tlines.push(\"\");\n\t\tlines.push(rule.content);\n\t\tlines.push(\"\");\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildCompactContext(rules: ParsedRule[]): string {\n\tif (rules.length === 0) return \"\";\n\n\tconst lines: string[] = [\"## Active Rules (persist across compaction)\", \"\"];\n\n\tfor (const rule of rules) {\n\t\tconst desc = rule.frontmatter.description || rule.content.split(\"\\n\")[0];\n\t\tlines.push(`- **${rule.title}** (${rule.name}): ${desc}`);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n"]}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ParsedRule, RuleCache } from "./types.js";
|
|
2
|
-
export declare function parseFrontmatter(content: string): {
|
|
3
|
-
data: Record<string, unknown>;
|
|
4
|
-
body: string;
|
|
5
|
-
};
|
|
6
|
-
export declare function parseRuleFile(filePath: string, content: string): ParsedRule;
|
|
7
|
-
export declare function loadRules(rulesDir: string): RuleCache;
|
|
8
|
-
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/rules-engine/loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAmB,MAAM,YAAY,CAAC;AAuBzE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA4EjG;AAwBD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CA6B3E;AAgBD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAuBrD","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { ParsedRule, RuleCache, RuleFrontmatter } from \"./types.js\";\n\nfunction splitComma(val: string): string[] {\n\tconst result: string[] = [];\n\tlet depth = 0;\n\tlet current = \"\";\n\tfor (const ch of val) {\n\t\tif (ch === \"{\" || ch === \"(\" || ch === \"[\") depth++;\n\t\telse if (ch === \"}\" || ch === \")\" || ch === \"]\") depth--;\n\n\t\tif (ch === \",\" && depth === 0) {\n\t\t\tconst trimmed = current.trim().replace(/^[\"']|[\"']$/g, \"\");\n\t\t\tif (trimmed) result.push(trimmed);\n\t\t\tcurrent = \"\";\n\t\t} else {\n\t\t\tcurrent += ch;\n\t\t}\n\t}\n\tconst trimmed = current.trim().replace(/^[\"']|[\"']$/g, \"\");\n\tif (trimmed) result.push(trimmed);\n\treturn result;\n}\n\nexport function parseFrontmatter(content: string): { data: Record<string, unknown>; body: string } {\n\tconst frontmatterRegex = /^---\\r?\\n([\\s\\S]*?)\\n?---\\r?\\n([\\s\\S]*)$/;\n\tconst match = content.match(frontmatterRegex);\n\n\tif (!match) {\n\t\treturn { data: {}, body: content };\n\t}\n\n\tconst [, frontmatterStr, body] = match;\n\tconst data: Record<string, unknown> = {};\n\n\tconst lines = frontmatterStr.split(\"\\n\");\n\tlet i = 0;\n\twhile (i < lines.length) {\n\t\tconst line = lines[i];\n\t\tconst colonIndex = line.indexOf(\":\");\n\t\tif (colonIndex === -1) {\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst rawKey = line.slice(0, colonIndex).trim();\n\t\tlet value: string | string[] | null = line.slice(colonIndex + 1).trim();\n\n\t\tif (value === \"\" || value === \"null\" || value === \"undefined\") {\n\t\t\tconst listItems: string[] = [];\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < lines.length) {\n\t\t\t\tconst subLine = lines[j];\n\t\t\t\tif (subLine.match(/^\\s*-\\s+/)) {\n\t\t\t\t\tlistItems.push(\n\t\t\t\t\t\tsubLine\n\t\t\t\t\t\t\t.replace(/^\\s*-\\s+/, \"\")\n\t\t\t\t\t\t\t.trim()\n\t\t\t\t\t\t\t.replace(/^[\"']|[\"']$/g, \"\"),\n\t\t\t\t\t);\n\t\t\t\t\tj++;\n\t\t\t\t} else if (subLine.trim() === \"\" || subLine.match(/^\\s+/)) {\n\t\t\t\t\tj++;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (listItems.length > 0) {\n\t\t\t\tconst camelKey = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());\n\t\t\t\tdata[camelKey] = listItems;\n\t\t\t\ti = j;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvalue = null;\n\t\t} else if (value.startsWith(\"[\") && value.endsWith(\"]\")) {\n\t\t\ttry {\n\t\t\t\tvalue = JSON.parse(value.replace(/'/g, '\"'));\n\t\t\t} catch {\n\t\t\t\tvalue = (value as string)\n\t\t\t\t\t.slice(1, -1)\n\t\t\t\t\t.split(\",\")\n\t\t\t\t\t.map((v: string) => v.trim().replace(/^[\"']|[\"']$/g, \"\"));\n\t\t\t}\n\t\t} else if (value.startsWith('\"') && value.endsWith('\"')) {\n\t\t\tvalue = value.slice(1, -1);\n\t\t} else if (value.startsWith(\"'\") && value.endsWith(\"'\")) {\n\t\t\tvalue = value.slice(1, -1);\n\t\t}\n\n\t\tconst camelKey = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());\n\n\t\tif (camelKey === \"paths\" && typeof value === \"string\") {\n\t\t\tdata[camelKey] = splitComma(value);\n\t\t} else {\n\t\t\tdata[camelKey] = value;\n\t\t}\n\t\ti++;\n\t}\n\n\treturn { data, body: body.trim() };\n}\n\nfunction extractTitle(body: string): string {\n\tfor (const line of body.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (trimmed) {\n\t\t\treturn trimmed.replace(/^#+\\s*/, \"\").replace(/\\*\\*/g, \"\");\n\t\t}\n\t}\n\treturn \"Untitled Rule\";\n}\n\nfunction parsePaths(raw: unknown): string[] {\n\tif (!raw) return [];\n\tif (typeof raw === \"string\") {\n\t\treturn raw\n\t\t\t.split(\",\")\n\t\t\t.map((g) => g.trim())\n\t\t\t.filter(Boolean);\n\t}\n\tif (Array.isArray(raw)) return raw as string[];\n\treturn [];\n}\n\nexport function parseRuleFile(filePath: string, content: string): ParsedRule {\n\tconst { data, body } = parseFrontmatter(content);\n\tconst paths = parsePaths(data.paths);\n\tconst isUnconditional = paths.length === 0 || (paths.length === 1 && paths[0] === \"**\");\n\n\tconst frontmatter: RuleFrontmatter = {};\n\tif (data.description && typeof data.description === \"string\") frontmatter.description = data.description;\n\tif (data.severity && typeof data.severity === \"string\")\n\t\tfrontmatter.severity = data.severity as ParsedRule[\"frontmatter\"][\"severity\"];\n\tif (data.paths) frontmatter.paths = paths;\n\tif (data.allowedTools)\n\t\tfrontmatter.allowedTools =\n\t\t\ttypeof data.allowedTools === \"string\" ? [data.allowedTools] : (data.allowedTools as string[]);\n\tif (data.whenToUse && typeof data.whenToUse === \"string\") frontmatter.whenToUse = data.whenToUse;\n\tif (data.notifyOnMatch !== undefined)\n\t\tfrontmatter.notifyOnMatch = data.notifyOnMatch === \"true\" || data.notifyOnMatch === true;\n\tif (data.skipInPrompt !== undefined)\n\t\tfrontmatter.skipInPrompt = data.skipInPrompt === \"true\" || data.skipInPrompt === true;\n\n\treturn {\n\t\tname: path.basename(filePath, \".md\"),\n\t\tfilePath,\n\t\ttitle: extractTitle(body),\n\t\tcontent: body.trim(),\n\t\tscope: \"project\",\n\t\tsource: \"\",\n\t\tfrontmatter,\n\t\tisUnconditional,\n\t};\n}\n\nfunction scanDir(dir: string, files: string[] = []): string[] {\n\tif (!fs.existsSync(dir)) return files;\n\tconst entries = fs.readdirSync(dir, { withFileTypes: true });\n\tfor (const entry of entries) {\n\t\tconst fullPath = path.join(dir, entry.name);\n\t\tif (entry.isDirectory()) {\n\t\t\tscanDir(fullPath, files);\n\t\t} else if (entry.isFile() && (entry.name.endsWith(\".md\") || entry.name.endsWith(\".mdc\"))) {\n\t\t\tfiles.push(fullPath);\n\t\t}\n\t}\n\treturn files;\n}\n\nexport function loadRules(rulesDir: string): RuleCache {\n\tconst rules: ParsedRule[] = [];\n\tconst unconditional: ParsedRule[] = [];\n\tconst conditional: ParsedRule[] = [];\n\n\tif (!fs.existsSync(rulesDir)) {\n\t\treturn { rules, unconditional, conditional, loadedAt: Date.now() };\n\t}\n\n\tconst files = scanDir(rulesDir);\n\n\tfor (const filePath of files) {\n\t\tconst content = fs.readFileSync(filePath, \"utf-8\");\n\t\tconst rule = parseRuleFile(filePath, content);\n\t\trules.push(rule);\n\t\tif (rule.isUnconditional) {\n\t\t\tunconditional.push(rule);\n\t\t} else {\n\t\t\tconditional.push(rule);\n\t\t}\n\t}\n\n\treturn { rules, unconditional, conditional, loadedAt: Date.now() };\n}\n"]}
|