@dyyz1993/pi-coding-agent 0.69.18 → 0.69.24
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/CHANGELOG.md +19 -0
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +12 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +15 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +216 -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 +21 -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 +31 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +86 -34
- 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/main.d.ts.map +1 -1
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +4 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +54 -12
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts +2 -0
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +8 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client-types.d.ts +2 -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 +2 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +6 -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 +6 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +10 -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,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-with-deps",
|
|
3
|
-
"version": "0.69.
|
|
3
|
+
"version": "0.69.20",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-with-deps",
|
|
9
|
-
"version": "0.69.
|
|
9
|
+
"version": "0.69.20",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dyyz1993/pi-coding-agent",
|
|
3
|
-
"version": "0.69.
|
|
3
|
+
"version": "0.69.24",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -36,15 +36,15 @@
|
|
|
36
36
|
"copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
|
|
37
37
|
"copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
|
|
38
38
|
"test": "vitest --run",
|
|
39
|
+
"lint:extensions": "eslint extensions/",
|
|
39
40
|
"prepublishOnly": "npm run clean && npm run build"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
43
|
+
"@dyyz1993/pi-agent-core": "^0.69.24",
|
|
44
|
+
"@dyyz1993/pi-ai": "^0.69.24",
|
|
45
|
+
"@dyyz1993/pi-tui": "^0.69.24",
|
|
42
46
|
"@mariozechner/jiti": "^2.6.2",
|
|
43
|
-
"@dyyz1993/pi-agent-core": "^0.69.17",
|
|
44
|
-
"@dyyz1993/pi-ai": "^0.69.17",
|
|
45
|
-
"@dyyz1993/pi-tui": "^0.69.17",
|
|
46
47
|
"@silvia-odwyer/photon-node": "^0.3.4",
|
|
47
|
-
"typebox": "^1.1.24",
|
|
48
48
|
"chalk": "^5.5.0",
|
|
49
49
|
"cli-highlight": "^2.1.11",
|
|
50
50
|
"diff": "^8.0.2",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"minimatch": "^10.2.3",
|
|
58
58
|
"proper-lockfile": "^4.1.2",
|
|
59
59
|
"strip-ansi": "^7.1.0",
|
|
60
|
+
"typebox": "^1.1.24",
|
|
60
61
|
"undici": "^7.19.1",
|
|
61
62
|
"uuid": "^11.1.0",
|
|
62
63
|
"yaml": "^2.8.2"
|
|
@@ -71,13 +72,16 @@
|
|
|
71
72
|
"@mariozechner/clipboard": "^0.3.2"
|
|
72
73
|
},
|
|
73
74
|
"devDependencies": {
|
|
75
|
+
"@eslint/js": "^10.0.1",
|
|
74
76
|
"@types/diff": "^7.0.2",
|
|
75
77
|
"@types/hosted-git-info": "^3.0.5",
|
|
76
78
|
"@types/ms": "^2.1.0",
|
|
77
79
|
"@types/node": "^24.3.0",
|
|
78
80
|
"@types/proper-lockfile": "^4.1.4",
|
|
81
|
+
"eslint": "^10.3.0",
|
|
79
82
|
"shx": "^0.4.0",
|
|
80
83
|
"typescript": "^5.7.3",
|
|
84
|
+
"typescript-eslint": "^8.59.1",
|
|
81
85
|
"vitest": "^3.2.4"
|
|
82
86
|
},
|
|
83
87
|
"keywords": [
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/rules-engine/cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI1D,wBAAsB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQ7F;AAED,wBAAgB,eAAe,IAAI,IAAI,CAEtC","sourcesContent":["import { resolveDirs } from \"./config.js\";\nimport { loadRules } from \"./loader.js\";\nimport type { ParsedRule, RulesConfig } from \"./types.js\";\n\nlet cache: { rules: ParsedRule[]; loadedAt: number } | null = null;\n\nexport async function getRules(projectDir: string, config: RulesConfig): Promise<ParsedRule[]> {\n\tif (cache && Date.now() - cache.loadedAt < config.cacheTTL) {\n\t\treturn cache.rules;\n\t}\n\n\tconst rules = await loadAllRules(projectDir, config);\n\tcache = { rules, loadedAt: Date.now() };\n\treturn rules;\n}\n\nexport function invalidateCache(): void {\n\tcache = null;\n}\n\nasync function loadAllRules(projectDir: string, config: RulesConfig): Promise<ParsedRule[]> {\n\tconst sources = resolveDirs(projectDir, config);\n\tconst result: ParsedRule[] = [];\n\tconst seen = new Set<string>();\n\n\tfor (const { scope, dir, source } of sources) {\n\t\tconst ruleCache = loadRules(dir);\n\t\tfor (const rule of ruleCache.rules) {\n\t\t\tif (seen.has(rule.filePath)) continue;\n\t\t\tseen.add(rule.filePath);\n\t\t\trule.scope = scope as ParsedRule[\"scope\"];\n\t\t\trule.source = source;\n\t\t\tresult.push(rule);\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { resolveDirs } from "./config.js";
|
|
2
|
-
import { loadRules } from "./loader.js";
|
|
3
|
-
let cache = null;
|
|
4
|
-
export async function getRules(projectDir, config) {
|
|
5
|
-
if (cache && Date.now() - cache.loadedAt < config.cacheTTL) {
|
|
6
|
-
return cache.rules;
|
|
7
|
-
}
|
|
8
|
-
const rules = await loadAllRules(projectDir, config);
|
|
9
|
-
cache = { rules, loadedAt: Date.now() };
|
|
10
|
-
return rules;
|
|
11
|
-
}
|
|
12
|
-
export function invalidateCache() {
|
|
13
|
-
cache = null;
|
|
14
|
-
}
|
|
15
|
-
async function loadAllRules(projectDir, config) {
|
|
16
|
-
const sources = resolveDirs(projectDir, config);
|
|
17
|
-
const result = [];
|
|
18
|
-
const seen = new Set();
|
|
19
|
-
for (const { scope, dir, source } of sources) {
|
|
20
|
-
const ruleCache = loadRules(dir);
|
|
21
|
-
for (const rule of ruleCache.rules) {
|
|
22
|
-
if (seen.has(rule.filePath))
|
|
23
|
-
continue;
|
|
24
|
-
seen.add(rule.filePath);
|
|
25
|
-
rule.scope = scope;
|
|
26
|
-
rule.source = source;
|
|
27
|
-
result.push(rule);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
|
-
//# sourceMappingURL=cache.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/rules-engine/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,IAAI,KAAK,GAAqD,IAAI,CAAC;AAEnE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,UAAkB,EAAE,MAAmB,EAAyB;IAC9F,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC5D,OAAO,KAAK,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACrD,KAAK,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IACxC,OAAO,KAAK,CAAC;AAAA,CACb;AAED,MAAM,UAAU,eAAe,GAAS;IACvC,KAAK,GAAG,IAAI,CAAC;AAAA,CACb;AAED,KAAK,UAAU,YAAY,CAAC,UAAkB,EAAE,MAAmB,EAAyB;IAC3F,MAAM,OAAO,GAAG,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAChD,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QAC9C,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;YACpC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACtC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxB,IAAI,CAAC,KAAK,GAAG,KAA4B,CAAC;YAC1C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;YACrB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["import { resolveDirs } from \"./config.js\";\nimport { loadRules } from \"./loader.js\";\nimport type { ParsedRule, RulesConfig } from \"./types.js\";\n\nlet cache: { rules: ParsedRule[]; loadedAt: number } | null = null;\n\nexport async function getRules(projectDir: string, config: RulesConfig): Promise<ParsedRule[]> {\n\tif (cache && Date.now() - cache.loadedAt < config.cacheTTL) {\n\t\treturn cache.rules;\n\t}\n\n\tconst rules = await loadAllRules(projectDir, config);\n\tcache = { rules, loadedAt: Date.now() };\n\treturn rules;\n}\n\nexport function invalidateCache(): void {\n\tcache = null;\n}\n\nasync function loadAllRules(projectDir: string, config: RulesConfig): Promise<ParsedRule[]> {\n\tconst sources = resolveDirs(projectDir, config);\n\tconst result: ParsedRule[] = [];\n\tconst seen = new Set<string>();\n\n\tfor (const { scope, dir, source } of sources) {\n\t\tconst ruleCache = loadRules(dir);\n\t\tfor (const rule of ruleCache.rules) {\n\t\t\tif (seen.has(rule.filePath)) continue;\n\t\t\tseen.add(rule.filePath);\n\t\t\trule.scope = scope as ParsedRule[\"scope\"];\n\t\t\trule.source = source;\n\t\t\tresult.push(rule);\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { RulesConfig } from "./types.js";
|
|
2
|
-
export declare function loadConfig(projectDir: string): Promise<RulesConfig>;
|
|
3
|
-
export declare function resolveDirs(projectDir: string, config: RulesConfig): Array<{
|
|
4
|
-
scope: string;
|
|
5
|
-
dir: string;
|
|
6
|
-
source: string;
|
|
7
|
-
}>;
|
|
8
|
-
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/rules-engine/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAmB9C,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAwBzE;AAED,wBAAgB,WAAW,CAC1B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,WAAW,GACjB,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAevD","sourcesContent":["import * as fs from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport * as path from \"node:path\";\nimport type { RulesConfig } from \"./types.js\";\n\nconst DEFAULT_CACHE_TTL = 30_000;\n\nconst DEFAULT_DIRS = {\n\tmanaged: [\"/etc/claude-code/.claude/rules\"],\n\tuser: [path.join(homedir(), \".claude\", \"rules\"), path.join(homedir(), \".config\", \"opencode\", \"rules\")],\n\tpi: [\".pi/rules\"],\n\tproject: [\".claude/rules\", \".opencode/rules\", \".trae/rules\"],\n};\n\nfunction defaultConfig(): RulesConfig {\n\treturn {\n\t\tcacheTTL: DEFAULT_CACHE_TTL,\n\t\tnotifyOnLoad: true,\n\t\tnotifyOnMatch: true,\n\t};\n}\n\nexport async function loadConfig(projectDir: string): Promise<RulesConfig> {\n\tconst configFiles = [\n\t\t\".rules-config.json\",\n\t\t\".pi/rules-config.json\",\n\t\t\".claude/rules-config.json\",\n\t\t\".opencode/rules-config.json\",\n\t];\n\n\tfor (const name of configFiles) {\n\t\tconst fp = path.resolve(projectDir, name);\n\t\ttry {\n\t\t\tconst raw = fs.readFileSync(fp, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw);\n\t\t\treturn {\n\t\t\t\t...defaultConfig(),\n\t\t\t\t...parsed,\n\t\t\t\tcacheTTL: parsed.cacheTTL ?? DEFAULT_CACHE_TTL,\n\t\t\t\tnotifyOnLoad: parsed.notifyOnLoad ?? true,\n\t\t\t\tnotifyOnMatch: parsed.notifyOnMatch ?? true,\n\t\t\t};\n\t\t} catch {}\n\t}\n\n\treturn defaultConfig();\n}\n\nexport function resolveDirs(\n\tprojectDir: string,\n\tconfig: RulesConfig,\n): Array<{ scope: string; dir: string; source: string }> {\n\tconst sources = config.dirs || DEFAULT_DIRS;\n\tconst result: Array<{ scope: string; dir: string; source: string }> = [];\n\n\tfor (const [scope, paths] of Object.entries(sources)) {\n\t\tfor (const p of paths) {\n\t\t\tresult.push({\n\t\t\t\tscope,\n\t\t\t\tdir: p.startsWith(\"/\") || p.startsWith(\"~\") ? p.replace(/^~/, homedir()) : path.resolve(projectDir, p),\n\t\t\t\tsource: p,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
const DEFAULT_CACHE_TTL = 30_000;
|
|
5
|
-
const DEFAULT_DIRS = {
|
|
6
|
-
managed: ["/etc/claude-code/.claude/rules"],
|
|
7
|
-
user: [path.join(homedir(), ".claude", "rules"), path.join(homedir(), ".config", "opencode", "rules")],
|
|
8
|
-
pi: [".pi/rules"],
|
|
9
|
-
project: [".claude/rules", ".opencode/rules", ".trae/rules"],
|
|
10
|
-
};
|
|
11
|
-
function defaultConfig() {
|
|
12
|
-
return {
|
|
13
|
-
cacheTTL: DEFAULT_CACHE_TTL,
|
|
14
|
-
notifyOnLoad: true,
|
|
15
|
-
notifyOnMatch: true,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export async function loadConfig(projectDir) {
|
|
19
|
-
const configFiles = [
|
|
20
|
-
".rules-config.json",
|
|
21
|
-
".pi/rules-config.json",
|
|
22
|
-
".claude/rules-config.json",
|
|
23
|
-
".opencode/rules-config.json",
|
|
24
|
-
];
|
|
25
|
-
for (const name of configFiles) {
|
|
26
|
-
const fp = path.resolve(projectDir, name);
|
|
27
|
-
try {
|
|
28
|
-
const raw = fs.readFileSync(fp, "utf-8");
|
|
29
|
-
const parsed = JSON.parse(raw);
|
|
30
|
-
return {
|
|
31
|
-
...defaultConfig(),
|
|
32
|
-
...parsed,
|
|
33
|
-
cacheTTL: parsed.cacheTTL ?? DEFAULT_CACHE_TTL,
|
|
34
|
-
notifyOnLoad: parsed.notifyOnLoad ?? true,
|
|
35
|
-
notifyOnMatch: parsed.notifyOnMatch ?? true,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
catch { }
|
|
39
|
-
}
|
|
40
|
-
return defaultConfig();
|
|
41
|
-
}
|
|
42
|
-
export function resolveDirs(projectDir, config) {
|
|
43
|
-
const sources = config.dirs || DEFAULT_DIRS;
|
|
44
|
-
const result = [];
|
|
45
|
-
for (const [scope, paths] of Object.entries(sources)) {
|
|
46
|
-
for (const p of paths) {
|
|
47
|
-
result.push({
|
|
48
|
-
scope,
|
|
49
|
-
dir: p.startsWith("/") || p.startsWith("~") ? p.replace(/^~/, homedir()) : path.resolve(projectDir, p),
|
|
50
|
-
source: p,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
56
|
-
//# sourceMappingURL=config.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/rules-engine/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEjC,MAAM,YAAY,GAAG;IACpB,OAAO,EAAE,CAAC,gCAAgC,CAAC;IAC3C,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IACtG,EAAE,EAAE,CAAC,WAAW,CAAC;IACjB,OAAO,EAAE,CAAC,eAAe,EAAE,iBAAiB,EAAE,aAAa,CAAC;CAC5D,CAAC;AAEF,SAAS,aAAa,GAAgB;IACrC,OAAO;QACN,QAAQ,EAAE,iBAAiB;QAC3B,YAAY,EAAE,IAAI;QAClB,aAAa,EAAE,IAAI;KACnB,CAAC;AAAA,CACF;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB,EAAwB;IAC1E,MAAM,WAAW,GAAG;QACnB,oBAAoB;QACpB,uBAAuB;QACvB,2BAA2B;QAC3B,6BAA6B;KAC7B,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAChC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,OAAO;gBACN,GAAG,aAAa,EAAE;gBAClB,GAAG,MAAM;gBACT,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,iBAAiB;gBAC9C,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,IAAI;gBACzC,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,IAAI;aAC3C,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACX,CAAC;IAED,OAAO,aAAa,EAAE,CAAC;AAAA,CACvB;AAED,MAAM,UAAU,WAAW,CAC1B,UAAkB,EAClB,MAAmB,EACqC;IACxD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,YAAY,CAAC;IAC5C,MAAM,MAAM,GAA0D,EAAE,CAAC;IAEzE,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACtD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC;gBACX,KAAK;gBACL,GAAG,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;gBACtG,MAAM,EAAE,CAAC;aACT,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["import * as fs from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport * as path from \"node:path\";\nimport type { RulesConfig } from \"./types.js\";\n\nconst DEFAULT_CACHE_TTL = 30_000;\n\nconst DEFAULT_DIRS = {\n\tmanaged: [\"/etc/claude-code/.claude/rules\"],\n\tuser: [path.join(homedir(), \".claude\", \"rules\"), path.join(homedir(), \".config\", \"opencode\", \"rules\")],\n\tpi: [\".pi/rules\"],\n\tproject: [\".claude/rules\", \".opencode/rules\", \".trae/rules\"],\n};\n\nfunction defaultConfig(): RulesConfig {\n\treturn {\n\t\tcacheTTL: DEFAULT_CACHE_TTL,\n\t\tnotifyOnLoad: true,\n\t\tnotifyOnMatch: true,\n\t};\n}\n\nexport async function loadConfig(projectDir: string): Promise<RulesConfig> {\n\tconst configFiles = [\n\t\t\".rules-config.json\",\n\t\t\".pi/rules-config.json\",\n\t\t\".claude/rules-config.json\",\n\t\t\".opencode/rules-config.json\",\n\t];\n\n\tfor (const name of configFiles) {\n\t\tconst fp = path.resolve(projectDir, name);\n\t\ttry {\n\t\t\tconst raw = fs.readFileSync(fp, \"utf-8\");\n\t\t\tconst parsed = JSON.parse(raw);\n\t\t\treturn {\n\t\t\t\t...defaultConfig(),\n\t\t\t\t...parsed,\n\t\t\t\tcacheTTL: parsed.cacheTTL ?? DEFAULT_CACHE_TTL,\n\t\t\t\tnotifyOnLoad: parsed.notifyOnLoad ?? true,\n\t\t\t\tnotifyOnMatch: parsed.notifyOnMatch ?? true,\n\t\t\t};\n\t\t} catch {}\n\t}\n\n\treturn defaultConfig();\n}\n\nexport function resolveDirs(\n\tprojectDir: string,\n\tconfig: RulesConfig,\n): Array<{ scope: string; dir: string; source: string }> {\n\tconst sources = config.dirs || DEFAULT_DIRS;\n\tconst result: Array<{ scope: string; dir: string; source: string }> = [];\n\n\tfor (const [scope, paths] of Object.entries(sources)) {\n\t\tfor (const p of paths) {\n\t\t\tresult.push({\n\t\t\t\tscope,\n\t\t\t\tdir: p.startsWith(\"/\") || p.startsWith(\"~\") ? p.replace(/^~/, homedir()) : path.resolve(projectDir, p),\n\t\t\t\tsource: p,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type ExtensionAPI } from "@dyyz1993/pi-coding-agent";
|
|
2
|
-
export { ServerChannel } from "../core/extensions/server-channel.js";
|
|
3
|
-
export { getRules, invalidateCache } from "./cache.js";
|
|
4
|
-
export { loadConfig, resolveDirs } from "./config.js";
|
|
5
|
-
export { buildCompactContext, buildSystemPromptSection, buildToolContextSection } from "./injector.js";
|
|
6
|
-
export { loadRules, parseFrontmatter, parseRuleFile } from "./loader.js";
|
|
7
|
-
export { matchesAnyGlob, matchGlob } from "./matcher.js";
|
|
8
|
-
export type { InjectedPayload, LifecycleEntry, MatchedPayload, MatchRecord, ParsedRule, ReloadedPayload, RuleDetail, RuleFrontmatter, RuleScope, RuleSeverity, RulesChannelContract, RulesChannelEvent, RulesConfig, ScannedDir, SnapshotPayload, UnloadedPayload, } from "./types.js";
|
|
9
|
-
export default function rulesEnginePlugin(pi: ExtensionAPI): void;
|
|
10
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules-engine/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAqB1E,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzD,YAAY,EACX,eAAe,EACf,cAAc,EACd,cAAc,EACd,WAAW,EACX,UAAU,EACV,eAAe,EACf,UAAU,EACV,eAAe,EACf,SAAS,EACT,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,eAAe,EACf,eAAe,GACf,MAAM,YAAY,CAAC;AAIpB,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EAAE,EAAE,YAAY,QAqbzD","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"]}
|