@dyyz1993/pi-coding-agent 0.74.24 → 0.74.25
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.map +1 -1
- package/dist/core/agent-session.js +3 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/extensions/agent-permissions/index.ts +235 -0
- package/dist/extensions/ask-tools/index.ts +115 -0
- package/dist/extensions/auto-memory/contract.d.ts +51 -0
- package/dist/extensions/auto-memory/contract.d.ts.map +1 -0
- package/dist/extensions/auto-memory/contract.js +2 -0
- package/dist/extensions/auto-memory/contract.js.map +1 -0
- package/dist/extensions/auto-memory/contract.ts +56 -0
- package/dist/extensions/auto-memory/index.ts +969 -0
- package/dist/extensions/auto-memory/prompts.ts +202 -0
- package/dist/extensions/auto-memory/skip-rules.ts +297 -0
- package/dist/extensions/auto-memory/utils.ts +208 -0
- package/dist/extensions/auto-session-title/index.ts +83 -0
- package/dist/extensions/bash-ext/contract.d.ts +79 -0
- package/dist/extensions/bash-ext/contract.d.ts.map +1 -0
- package/dist/extensions/bash-ext/contract.js +2 -0
- package/dist/extensions/bash-ext/contract.js.map +1 -0
- package/dist/extensions/bash-ext/contract.ts +69 -0
- package/dist/extensions/bash-ext/index.ts +858 -0
- package/dist/extensions/claude-hooks-compat/config-loader.ts +49 -0
- package/dist/extensions/claude-hooks-compat/handler-runner.ts +377 -0
- package/dist/extensions/claude-hooks-compat/if-parser.ts +53 -0
- package/dist/extensions/claude-hooks-compat/index.ts +178 -0
- package/dist/extensions/claude-hooks-compat/matcher.ts +17 -0
- package/dist/extensions/claude-hooks-compat/stdin-builder.ts +27 -0
- package/dist/extensions/claude-hooks-compat/types.ts +77 -0
- package/dist/extensions/compaction-manager/config.ts +47 -0
- package/dist/extensions/compaction-manager/context-fold.ts +63 -0
- package/dist/extensions/compaction-manager/index.ts +151 -0
- package/dist/extensions/compaction-manager/microcompact.ts +49 -0
- package/dist/extensions/compaction-manager/reactive.ts +9 -0
- package/dist/extensions/compaction-manager/session-memory.ts +48 -0
- package/dist/extensions/coordinator/INTEGRATION.md +376 -0
- package/dist/extensions/coordinator/handler.test.ts +277 -0
- package/dist/extensions/coordinator/handler.ts +189 -0
- package/dist/extensions/coordinator/index.ts +261 -0
- package/dist/extensions/coordinator/types.d.ts +100 -0
- package/dist/extensions/coordinator/types.d.ts.map +1 -0
- package/dist/extensions/coordinator/types.js +2 -0
- package/dist/extensions/coordinator/types.js.map +1 -0
- package/dist/extensions/coordinator/types.ts +72 -0
- package/dist/extensions/file-snapshot/index.ts +131 -0
- package/dist/extensions/file-time-guard/README.md +133 -0
- package/dist/extensions/file-time-guard/config.ts +13 -0
- package/dist/extensions/file-time-guard/index.ts +171 -0
- package/dist/extensions/hooks-engine/index.ts +117 -0
- package/dist/extensions/lsp/lsp/client/file-tracker.ts +70 -0
- package/dist/extensions/lsp/lsp/client/registry.ts +305 -0
- package/dist/extensions/lsp/lsp/client/runtime.ts +832 -0
- package/dist/extensions/lsp/lsp/config/resolver.ts +573 -0
- package/dist/extensions/lsp/lsp/contract.d.ts +101 -0
- package/dist/extensions/lsp/lsp/contract.d.ts.map +1 -0
- package/dist/extensions/lsp/lsp/contract.js +2 -0
- package/dist/extensions/lsp/lsp/contract.js.map +1 -0
- package/dist/extensions/lsp/lsp/contract.ts +103 -0
- package/dist/extensions/lsp/lsp/hooks/agent-end.ts +169 -0
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.d.ts +10 -0
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.d.ts.map +1 -0
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.js +30 -0
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.js.map +1 -0
- package/dist/extensions/lsp/lsp/hooks/diagnostics-mode.ts +41 -0
- package/dist/extensions/lsp/lsp/hooks/writethrough.ts +342 -0
- package/dist/extensions/lsp/lsp/index.ts +307 -0
- package/dist/extensions/lsp/lsp/lsp.test.ts +684 -0
- package/dist/extensions/lsp/lsp/monitoring/server-metrics.ts +176 -0
- package/dist/extensions/lsp/lsp/tools/lsp-tool.ts +402 -0
- package/dist/extensions/lsp/lsp/utils/dependency-resolver.ts +147 -0
- package/dist/extensions/lsp/lsp/utils/diagnostics-wait.ts +41 -0
- package/dist/extensions/lsp/lsp/utils/lsp-helpers.d.ts +20 -0
- package/dist/extensions/lsp/lsp/utils/lsp-helpers.d.ts.map +1 -0
- package/dist/extensions/lsp/lsp/utils/lsp-helpers.js +64 -0
- package/dist/extensions/lsp/lsp/utils/lsp-helpers.js.map +1 -0
- package/dist/extensions/lsp/lsp/utils/lsp-helpers.ts +76 -0
- package/dist/extensions/message-bridge/GUIDE.md +210 -0
- package/dist/extensions/message-bridge/index.ts +222 -0
- package/dist/extensions/output-guard/index.ts +384 -0
- package/dist/extensions/preview/index.ts +278 -0
- package/dist/extensions/rules-engine/MATCH_HISTORY_RECONCILIATION.md +111 -0
- package/dist/extensions/rules-engine/RULES-ENGINE-GUIDE.md +470 -0
- package/dist/extensions/rules-engine/cache.js +232 -0
- package/dist/extensions/rules-engine/cache.ts +38 -0
- package/dist/extensions/rules-engine/config.js +63 -0
- package/dist/extensions/rules-engine/config.ts +70 -0
- package/dist/extensions/rules-engine/index.js +1530 -0
- package/dist/extensions/rules-engine/index.ts +552 -0
- package/dist/extensions/rules-engine/injector.js +68 -0
- package/dist/extensions/rules-engine/injector.ts +74 -0
- package/dist/extensions/rules-engine/loader.js +179 -0
- package/dist/extensions/rules-engine/loader.ts +205 -0
- package/dist/extensions/rules-engine/matcher.js +534 -0
- package/dist/extensions/rules-engine/matcher.ts +52 -0
- package/dist/extensions/rules-engine/types.d.ts +156 -0
- package/dist/extensions/rules-engine/types.d.ts.map +1 -0
- package/dist/extensions/rules-engine/types.js +2 -0
- package/dist/extensions/rules-engine/types.js.map +1 -0
- package/dist/extensions/rules-engine/types.ts +169 -0
- package/dist/extensions/session-supervisor/checker.ts +116 -0
- package/dist/extensions/session-supervisor/config.ts +45 -0
- package/dist/extensions/session-supervisor/index.ts +726 -0
- package/dist/extensions/session-supervisor/prompts.ts +132 -0
- package/dist/extensions/session-supervisor/scheduler.ts +69 -0
- package/dist/extensions/session-supervisor/types.ts +215 -0
- package/dist/extensions/subagent/README.md +172 -0
- package/dist/extensions/subagent/agents/explorer.md +25 -0
- package/dist/extensions/subagent/agents/guide.md +27 -0
- package/dist/extensions/subagent/agents/planner.md +37 -0
- package/dist/extensions/subagent/agents/reviewer.md +35 -0
- package/dist/extensions/subagent/agents/scout.md +50 -0
- package/dist/extensions/subagent/agents/verification.md +35 -0
- package/dist/extensions/subagent/agents/worker.md +24 -0
- package/dist/extensions/subagent/agents.ts +25 -0
- package/dist/extensions/subagent/index.ts +987 -0
- package/dist/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/dist/extensions/subagent/prompts/implement.md +10 -0
- package/dist/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/dist/extensions/subagent-ext/contract.d.ts +2 -0
- package/dist/extensions/subagent-ext/contract.d.ts.map +1 -0
- package/dist/extensions/subagent-ext/contract.js +2 -0
- package/dist/extensions/subagent-ext/contract.js.map +1 -0
- package/dist/extensions/subagent-ext/contract.ts +1 -0
- package/dist/extensions/subagent-ext/index.ts +347 -0
- package/dist/extensions/subagent-shared/contract.d.ts +25 -0
- package/dist/extensions/subagent-shared/contract.d.ts.map +1 -0
- package/dist/extensions/subagent-shared/contract.js +2 -0
- package/dist/extensions/subagent-shared/contract.js.map +1 -0
- package/dist/extensions/subagent-shared/contract.ts +28 -0
- package/dist/extensions/subagent-shared/index.ts +4 -0
- package/dist/extensions/subagent-shared/render.ts +166 -0
- package/dist/extensions/subagent-shared/types.ts +35 -0
- package/dist/extensions/subagent-shared/utils.ts +112 -0
- package/dist/extensions/subagent-v2/contract.d.ts +2 -0
- package/dist/extensions/subagent-v2/contract.d.ts.map +1 -0
- package/dist/extensions/subagent-v2/contract.js +2 -0
- package/dist/extensions/subagent-v2/contract.js.map +1 -0
- package/dist/extensions/subagent-v2/contract.ts +1 -0
- package/dist/extensions/subagent-v2/index.ts +599 -0
- package/dist/extensions/todo-ext/contract.d.ts +27 -0
- package/dist/extensions/todo-ext/contract.d.ts.map +1 -0
- package/dist/extensions/todo-ext/contract.js +2 -0
- package/dist/extensions/todo-ext/contract.js.map +1 -0
- package/dist/extensions/todo-ext/contract.ts +30 -0
- package/dist/extensions/todo-ext/index.ts +419 -0
- package/package.json +6 -5
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// packages/coding-agent/extensions/rules-engine/loader.ts
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
function splitComma(val) {
|
|
5
|
+
const result = [];
|
|
6
|
+
let depth = 0;
|
|
7
|
+
let current = "";
|
|
8
|
+
for (const ch of val) {
|
|
9
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
10
|
+
else if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
11
|
+
if (ch === "," && depth === 0) {
|
|
12
|
+
const trimmed2 = current.trim().replace(/^["']|["']$/g, "");
|
|
13
|
+
if (trimmed2) result.push(trimmed2);
|
|
14
|
+
current = "";
|
|
15
|
+
} else {
|
|
16
|
+
current += ch;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const trimmed = current.trim().replace(/^["']|["']$/g, "");
|
|
20
|
+
if (trimmed) result.push(trimmed);
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
function parseFrontmatter(content) {
|
|
24
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\n?---\r?\n([\s\S]*)$/;
|
|
25
|
+
const match = content.match(frontmatterRegex);
|
|
26
|
+
if (!match) {
|
|
27
|
+
return { data: {}, body: content };
|
|
28
|
+
}
|
|
29
|
+
const [, frontmatterStr, body] = match;
|
|
30
|
+
const data = {};
|
|
31
|
+
const lines = frontmatterStr.split("\n");
|
|
32
|
+
let i = 0;
|
|
33
|
+
while (i < lines.length) {
|
|
34
|
+
const line = lines[i];
|
|
35
|
+
const colonIndex = line.indexOf(":");
|
|
36
|
+
if (colonIndex === -1) {
|
|
37
|
+
i++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const rawKey = line.slice(0, colonIndex).trim();
|
|
41
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
42
|
+
if (value === "" || value === "null" || value === "undefined") {
|
|
43
|
+
const listItems = [];
|
|
44
|
+
let j = i + 1;
|
|
45
|
+
while (j < lines.length) {
|
|
46
|
+
const subLine = lines[j];
|
|
47
|
+
if (subLine.match(/^\s*-\s+/)) {
|
|
48
|
+
listItems.push(
|
|
49
|
+
subLine.replace(/^\s*-\s+/, "").trim().replace(/^["']|["']$/g, "")
|
|
50
|
+
);
|
|
51
|
+
j++;
|
|
52
|
+
} else if (subLine.trim() === "" || subLine.match(/^\s+/)) {
|
|
53
|
+
j++;
|
|
54
|
+
} else {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (listItems.length > 0) {
|
|
59
|
+
const camelKey2 = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
60
|
+
data[camelKey2] = listItems;
|
|
61
|
+
i = j;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
value = null;
|
|
65
|
+
} else if (value.startsWith("[") && value.endsWith("]")) {
|
|
66
|
+
try {
|
|
67
|
+
value = JSON.parse(value.replace(/'/g, '"'));
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.debug("[rules-engine] JSON array parse failed:", err instanceof Error ? err.message : err);
|
|
70
|
+
value = value.slice(1, -1).split(",").map((v) => v.trim().replace(/^["']|["']$/g, ""));
|
|
71
|
+
}
|
|
72
|
+
} else if (value.startsWith('"') && value.endsWith('"')) {
|
|
73
|
+
value = value.slice(1, -1);
|
|
74
|
+
} else if (value.startsWith("'") && value.endsWith("'")) {
|
|
75
|
+
value = value.slice(1, -1);
|
|
76
|
+
}
|
|
77
|
+
const camelKey = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
78
|
+
if ((camelKey === "paths" || camelKey === "globs") && typeof value === "string") {
|
|
79
|
+
data[camelKey] = splitComma(value);
|
|
80
|
+
} else {
|
|
81
|
+
data[camelKey] = value;
|
|
82
|
+
}
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
return { data, body: body.trim() };
|
|
86
|
+
}
|
|
87
|
+
function extractTitle(body) {
|
|
88
|
+
for (const line of body.split("\n")) {
|
|
89
|
+
const trimmed = line.trim();
|
|
90
|
+
if (trimmed) {
|
|
91
|
+
return trimmed.replace(/^#+\s*/, "").replace(/\*\*/g, "");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return "Untitled Rule";
|
|
95
|
+
}
|
|
96
|
+
function parsePaths(raw) {
|
|
97
|
+
if (!raw) return [];
|
|
98
|
+
if (typeof raw === "string") {
|
|
99
|
+
return raw.split(",").map((g) => g.trim()).filter(Boolean);
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(raw)) return raw;
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
function parseRuleFile(filePath, content) {
|
|
105
|
+
const { data, body } = parseFrontmatter(content);
|
|
106
|
+
const rawGlobs = data.globs ?? data.paths;
|
|
107
|
+
const globs = parsePaths(rawGlobs);
|
|
108
|
+
const rawPaths = data.paths;
|
|
109
|
+
const paths = parsePaths(rawPaths);
|
|
110
|
+
const isUnconditional = globs.length === 0 || globs.length === 1 && globs[0] === "**";
|
|
111
|
+
const frontmatter = {};
|
|
112
|
+
if (rawGlobs) {
|
|
113
|
+
frontmatter.globs = globs;
|
|
114
|
+
if (rawPaths) {
|
|
115
|
+
frontmatter.paths = paths;
|
|
116
|
+
} else {
|
|
117
|
+
frontmatter.paths = globs;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (data.description && typeof data.description === "string") frontmatter.description = data.description;
|
|
121
|
+
if (data.severity && typeof data.severity === "string")
|
|
122
|
+
frontmatter.severity = data.severity;
|
|
123
|
+
if (data.allowedTools)
|
|
124
|
+
frontmatter.allowedTools = typeof data.allowedTools === "string" ? [data.allowedTools] : data.allowedTools;
|
|
125
|
+
if (data.whenToUse && typeof data.whenToUse === "string") frontmatter.whenToUse = data.whenToUse;
|
|
126
|
+
if (data.notifyOnMatch !== void 0)
|
|
127
|
+
frontmatter.notifyOnMatch = data.notifyOnMatch === "true" || data.notifyOnMatch === true;
|
|
128
|
+
if (data.skipInPrompt !== void 0)
|
|
129
|
+
frontmatter.skipInPrompt = data.skipInPrompt === "true" || data.skipInPrompt === true;
|
|
130
|
+
return {
|
|
131
|
+
name: path.basename(filePath, path.extname(filePath)),
|
|
132
|
+
filePath,
|
|
133
|
+
title: extractTitle(body),
|
|
134
|
+
content: body.trim(),
|
|
135
|
+
scope: "project",
|
|
136
|
+
source: "",
|
|
137
|
+
frontmatter,
|
|
138
|
+
isUnconditional
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function scanDir(dir, files = []) {
|
|
142
|
+
if (!fs.existsSync(dir)) return files;
|
|
143
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
const fullPath = path.join(dir, entry.name);
|
|
146
|
+
if (entry.isDirectory()) {
|
|
147
|
+
scanDir(fullPath, files);
|
|
148
|
+
} else if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".mdc"))) {
|
|
149
|
+
files.push(fullPath);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return files;
|
|
153
|
+
}
|
|
154
|
+
function loadRules(rulesDir) {
|
|
155
|
+
const rules = [];
|
|
156
|
+
const unconditional = [];
|
|
157
|
+
const conditional = [];
|
|
158
|
+
if (!fs.existsSync(rulesDir)) {
|
|
159
|
+
return { rules, unconditional, conditional, loadedAt: Date.now() };
|
|
160
|
+
}
|
|
161
|
+
const files = scanDir(rulesDir);
|
|
162
|
+
for (const filePath of files) {
|
|
163
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
164
|
+
const rule = parseRuleFile(filePath, content);
|
|
165
|
+
rules.push(rule);
|
|
166
|
+
if (rule.isUnconditional) {
|
|
167
|
+
unconditional.push(rule);
|
|
168
|
+
} else {
|
|
169
|
+
conditional.push(rule);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { rules, unconditional, conditional, loadedAt: Date.now() };
|
|
173
|
+
}
|
|
174
|
+
export {
|
|
175
|
+
loadRules,
|
|
176
|
+
parseFrontmatter,
|
|
177
|
+
parseRuleFile
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsibG9hZGVyLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyJpbXBvcnQgKiBhcyBmcyBmcm9tIFwibm9kZTpmc1wiO1xuaW1wb3J0ICogYXMgcGF0aCBmcm9tIFwibm9kZTpwYXRoXCI7XG5pbXBvcnQgdHlwZSB7IFBhcnNlZFJ1bGUsIFJ1bGVDYWNoZSwgUnVsZUZyb250bWF0dGVyIH0gZnJvbSBcIi4vdHlwZXMuanNcIjtcblxuZnVuY3Rpb24gc3BsaXRDb21tYSh2YWw6IHN0cmluZyk6IHN0cmluZ1tdIHtcblx0Y29uc3QgcmVzdWx0OiBzdHJpbmdbXSA9IFtdO1xuXHRsZXQgZGVwdGggPSAwO1xuXHRsZXQgY3VycmVudCA9IFwiXCI7XG5cdGZvciAoY29uc3QgY2ggb2YgdmFsKSB7XG5cdFx0aWYgKGNoID09PSBcIntcIiB8fCBjaCA9PT0gXCIoXCIgfHwgY2ggPT09IFwiW1wiKSBkZXB0aCsrO1xuXHRcdGVsc2UgaWYgKGNoID09PSBcIn1cIiB8fCBjaCA9PT0gXCIpXCIgfHwgY2ggPT09IFwiXVwiKSBkZXB0aC0tO1xuXG5cdFx0aWYgKGNoID09PSBcIixcIiAmJiBkZXB0aCA9PT0gMCkge1xuXHRcdFx0Y29uc3QgdHJpbW1lZCA9IGN1cnJlbnQudHJpbSgpLnJlcGxhY2UoL15bXCInXXxbXCInXSQvZywgXCJcIik7XG5cdFx0XHRpZiAodHJpbW1lZCkgcmVzdWx0LnB1c2godHJpbW1lZCk7XG5cdFx0XHRjdXJyZW50ID0gXCJcIjtcblx0XHR9IGVsc2Uge1xuXHRcdFx0Y3VycmVudCArPSBjaDtcblx0XHR9XG5cdH1cblx0Y29uc3QgdHJpbW1lZCA9IGN1cnJlbnQudHJpbSgpLnJlcGxhY2UoL15bXCInXXxbXCInXSQvZywgXCJcIik7XG5cdGlmICh0cmltbWVkKSByZXN1bHQucHVzaCh0cmltbWVkKTtcblx0cmV0dXJuIHJlc3VsdDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlRnJvbnRtYXR0ZXIoY29udGVudDogc3RyaW5nKTogeyBkYXRhOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjsgYm9keTogc3RyaW5nIH0ge1xuXHRjb25zdCBmcm9udG1hdHRlclJlZ2V4ID0gL14tLS1cXHI/XFxuKFtcXHNcXFNdKj8pXFxuPy0tLVxccj9cXG4oW1xcc1xcU10qKSQvO1xuXHRjb25zdCBtYXRjaCA9IGNvbnRlbnQubWF0Y2goZnJvbnRtYXR0ZXJSZWdleCk7XG5cblx0aWYgKCFtYXRjaCkge1xuXHRcdHJldHVybiB7IGRhdGE6IHt9LCBib2R5OiBjb250ZW50IH07XG5cdH1cblxuXHRjb25zdCBbLCBmcm9udG1hdHRlclN0ciwgYm9keV0gPSBtYXRjaDtcblx0Y29uc3QgZGF0YTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPSB7fTtcblxuXHRjb25zdCBsaW5lcyA9IGZyb250bWF0dGVyU3RyLnNwbGl0KFwiXFxuXCIpO1xuXHRsZXQgaSA9IDA7XG5cdHdoaWxlIChpIDwgbGluZXMubGVuZ3RoKSB7XG5cdFx0Y29uc3QgbGluZSA9IGxpbmVzW2ldO1xuXHRcdGNvbnN0IGNvbG9uSW5kZXggPSBsaW5lLmluZGV4T2YoXCI6XCIpO1xuXHRcdGlmIChjb2xvbkluZGV4ID09PSAtMSkge1xuXHRcdFx0aSsrO1xuXHRcdFx0Y29udGludWU7XG5cdFx0fVxuXG5cdFx0Y29uc3QgcmF3S2V5ID0gbGluZS5zbGljZSgwLCBjb2xvbkluZGV4KS50cmltKCk7XG5cdFx0bGV0IHZhbHVlOiBzdHJpbmcgfCBzdHJpbmdbXSB8IG51bGwgPSBsaW5lLnNsaWNlKGNvbG9uSW5kZXggKyAxKS50cmltKCk7XG5cblx0XHRpZiAodmFsdWUgPT09IFwiXCIgfHwgdmFsdWUgPT09IFwibnVsbFwiIHx8IHZhbHVlID09PSBcInVuZGVmaW5lZFwiKSB7XG5cdFx0XHRjb25zdCBsaXN0SXRlbXM6IHN0cmluZ1tdID0gW107XG5cdFx0XHRsZXQgaiA9IGkgKyAxO1xuXHRcdFx0d2hpbGUgKGogPCBsaW5lcy5sZW5ndGgpIHtcblx0XHRcdFx0Y29uc3Qgc3ViTGluZSA9IGxpbmVzW2pdO1xuXHRcdFx0XHRpZiAoc3ViTGluZS5tYXRjaCgvXlxccyotXFxzKy8pKSB7XG5cdFx0XHRcdFx0bGlzdEl0ZW1zLnB1c2goXG5cdFx0XHRcdFx0XHRzdWJMaW5lXG5cdFx0XHRcdFx0XHRcdC5yZXBsYWNlKC9eXFxzKi1cXHMrLywgXCJcIilcblx0XHRcdFx0XHRcdFx0LnRyaW0oKVxuXHRcdFx0XHRcdFx0XHQucmVwbGFjZSgvXltcIiddfFtcIiddJC9nLCBcIlwiKSxcblx0XHRcdFx0XHQpO1xuXHRcdFx0XHRcdGorKztcblx0XHRcdFx0fSBlbHNlIGlmIChzdWJMaW5lLnRyaW0oKSA9PT0gXCJcIiB8fCBzdWJMaW5lLm1hdGNoKC9eXFxzKy8pKSB7XG5cdFx0XHRcdFx0aisrO1xuXHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdGJyZWFrO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0XHRpZiAobGlzdEl0ZW1zLmxlbmd0aCA+IDApIHtcblx0XHRcdFx0Y29uc3QgY2FtZWxLZXkgPSByYXdLZXkucmVwbGFjZSgvLShbYS16XSkvZywgKF8sIGxldHRlcikgPT4gbGV0dGVyLnRvVXBwZXJDYXNlKCkpO1xuXHRcdFx0XHRkYXRhW2NhbWVsS2V5XSA9IGxpc3RJdGVtcztcblx0XHRcdFx0aSA9IGo7XG5cdFx0XHRcdGNvbnRpbnVlO1xuXHRcdFx0fVxuXHRcdFx0dmFsdWUgPSBudWxsO1xuXHRcdH0gZWxzZSBpZiAodmFsdWUuc3RhcnRzV2l0aChcIltcIikgJiYgdmFsdWUuZW5kc1dpdGgoXCJdXCIpKSB7XG5cdFx0XHR0cnkge1xuXHRcdFx0XHR2YWx1ZSA9IEpTT04ucGFyc2UodmFsdWUucmVwbGFjZSgvJy9nLCAnXCInKSk7XG5cdFx0XHR9IGNhdGNoIChlcnIpIHtcblx0XHRcdFx0Y29uc29sZS5kZWJ1ZyhcIltydWxlcy1lbmdpbmVdIEpTT04gYXJyYXkgcGFyc2UgZmFpbGVkOlwiLCBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogZXJyKTtcblx0XHRcdFx0dmFsdWUgPSAodmFsdWUgYXMgc3RyaW5nKVxuXHRcdFx0XHRcdC5zbGljZSgxLCAtMSlcblx0XHRcdFx0XHQuc3BsaXQoXCIsXCIpXG5cdFx0XHRcdFx0Lm1hcCgodjogc3RyaW5nKSA9PiB2LnRyaW0oKS5yZXBsYWNlKC9eW1wiJ118W1wiJ10kL2csIFwiXCIpKTtcblx0XHRcdH1cblx0XHR9IGVsc2UgaWYgKHZhbHVlLnN0YXJ0c1dpdGgoJ1wiJykgJiYgdmFsdWUuZW5kc1dpdGgoJ1wiJykpIHtcblx0XHRcdHZhbHVlID0gdmFsdWUuc2xpY2UoMSwgLTEpO1xuXHRcdH0gZWxzZSBpZiAodmFsdWUuc3RhcnRzV2l0aChcIidcIikgJiYgdmFsdWUuZW5kc1dpdGgoXCInXCIpKSB7XG5cdFx0XHR2YWx1ZSA9IHZhbHVlLnNsaWNlKDEsIC0xKTtcblx0XHR9XG5cblx0XHRjb25zdCBjYW1lbEtleSA9IHJhd0tleS5yZXBsYWNlKC8tKFthLXpdKS9nLCAoXywgbGV0dGVyKSA9PiBsZXR0ZXIudG9VcHBlckNhc2UoKSk7XG5cblx0XHRpZiAoKGNhbWVsS2V5ID09PSBcInBhdGhzXCIgfHwgY2FtZWxLZXkgPT09IFwiZ2xvYnNcIikgJiYgdHlwZW9mIHZhbHVlID09PSBcInN0cmluZ1wiKSB7XG5cdFx0XHRkYXRhW2NhbWVsS2V5XSA9IHNwbGl0Q29tbWEodmFsdWUpO1xuXHRcdH0gZWxzZSB7XG5cdFx0XHRkYXRhW2NhbWVsS2V5XSA9IHZhbHVlO1xuXHRcdH1cblx0XHRpKys7XG5cdH1cblxuXHRyZXR1cm4geyBkYXRhLCBib2R5OiBib2R5LnRyaW0oKSB9O1xufVxuXG5mdW5jdGlvbiBleHRyYWN0VGl0bGUoYm9keTogc3RyaW5nKTogc3RyaW5nIHtcblx0Zm9yIChjb25zdCBsaW5lIG9mIGJvZHkuc3BsaXQoXCJcXG5cIikpIHtcblx0XHRjb25zdCB0cmltbWVkID0gbGluZS50cmltKCk7XG5cdFx0aWYgKHRyaW1tZWQpIHtcblx0XHRcdHJldHVybiB0cmltbWVkLnJlcGxhY2UoL14jK1xccyovLCBcIlwiKS5yZXBsYWNlKC9cXCpcXCovZywgXCJcIik7XG5cdFx0fVxuXHR9XG5cdHJldHVybiBcIlVudGl0bGVkIFJ1bGVcIjtcbn1cblxuZnVuY3Rpb24gcGFyc2VQYXRocyhyYXc6IHVua25vd24pOiBzdHJpbmdbXSB7XG5cdGlmICghcmF3KSByZXR1cm4gW107XG5cdGlmICh0eXBlb2YgcmF3ID09PSBcInN0cmluZ1wiKSB7XG5cdFx0cmV0dXJuIHJhd1xuXHRcdFx0LnNwbGl0KFwiLFwiKVxuXHRcdFx0Lm1hcCgoZykgPT4gZy50cmltKCkpXG5cdFx0XHQuZmlsdGVyKEJvb2xlYW4pO1xuXHR9XG5cdGlmIChBcnJheS5pc0FycmF5KHJhdykpIHJldHVybiByYXcgYXMgc3RyaW5nW107XG5cdHJldHVybiBbXTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlUnVsZUZpbGUoZmlsZVBhdGg6IHN0cmluZywgY29udGVudDogc3RyaW5nKTogUGFyc2VkUnVsZSB7XG5cdGNvbnN0IHsgZGF0YSwgYm9keSB9ID0gcGFyc2VGcm9udG1hdHRlcihjb250ZW50KTtcblx0Y29uc3QgcmF3R2xvYnMgPSBkYXRhLmdsb2JzID8/IGRhdGEucGF0aHM7XG5cdGNvbnN0IGdsb2JzID0gcGFyc2VQYXRocyhyYXdHbG9icyk7XG5cdGNvbnN0IHJhd1BhdGhzID0gZGF0YS5wYXRocztcblx0Y29uc3QgcGF0aHMgPSBwYXJzZVBhdGhzKHJhd1BhdGhzKTtcblx0Y29uc3QgaXNVbmNvbmRpdGlvbmFsID0gZ2xvYnMubGVuZ3RoID09PSAwIHx8IChnbG9icy5sZW5ndGggPT09IDEgJiYgZ2xvYnNbMF0gPT09IFwiKipcIik7XG5cblx0Y29uc3QgZnJvbnRtYXR0ZXI6IFJ1bGVGcm9udG1hdHRlciA9IHt9O1xuXHRpZiAocmF3R2xvYnMpIHtcblx0XHRmcm9udG1hdHRlci5nbG9icyA9IGdsb2JzO1xuXHRcdGlmIChyYXdQYXRocykge1xuXHRcdFx0ZnJvbnRtYXR0ZXIucGF0aHMgPSBwYXRocztcblx0XHR9IGVsc2Uge1xuXHRcdFx0ZnJvbnRtYXR0ZXIucGF0aHMgPSBnbG9icztcblx0XHR9XG5cdH1cblx0aWYgKGRhdGEuZGVzY3JpcHRpb24gJiYgdHlwZW9mIGRhdGEuZGVzY3JpcHRpb24gPT09IFwic3RyaW5nXCIpIGZyb250bWF0dGVyLmRlc2NyaXB0aW9uID0gZGF0YS5kZXNjcmlwdGlvbjtcblx0aWYgKGRhdGEuc2V2ZXJpdHkgJiYgdHlwZW9mIGRhdGEuc2V2ZXJpdHkgPT09IFwic3RyaW5nXCIpXG5cdFx0ZnJvbnRtYXR0ZXIuc2V2ZXJpdHkgPSBkYXRhLnNldmVyaXR5IGFzIFBhcnNlZFJ1bGVbXCJmcm9udG1hdHRlclwiXVtcInNldmVyaXR5XCJdO1xuXHRpZiAoZGF0YS5hbGxvd2VkVG9vbHMpXG5cdFx0ZnJvbnRtYXR0ZXIuYWxsb3dlZFRvb2xzID1cblx0XHRcdHR5cGVvZiBkYXRhLmFsbG93ZWRUb29scyA9PT0gXCJzdHJpbmdcIiA/IFtkYXRhLmFsbG93ZWRUb29sc10gOiAoZGF0YS5hbGxvd2VkVG9vbHMgYXMgc3RyaW5nW10pO1xuXHRpZiAoZGF0YS53aGVuVG9Vc2UgJiYgdHlwZW9mIGRhdGEud2hlblRvVXNlID09PSBcInN0cmluZ1wiKSBmcm9udG1hdHRlci53aGVuVG9Vc2UgPSBkYXRhLndoZW5Ub1VzZTtcblx0aWYgKGRhdGEubm90aWZ5T25NYXRjaCAhPT0gdW5kZWZpbmVkKVxuXHRcdGZyb250bWF0dGVyLm5vdGlmeU9uTWF0Y2ggPSBkYXRhLm5vdGlmeU9uTWF0Y2ggPT09IFwidHJ1ZVwiIHx8IGRhdGEubm90aWZ5T25NYXRjaCA9PT0gdHJ1ZTtcblx0aWYgKGRhdGEuc2tpcEluUHJvbXB0ICE9PSB1bmRlZmluZWQpXG5cdFx0ZnJvbnRtYXR0ZXIuc2tpcEluUHJvbXB0ID0gZGF0YS5za2lwSW5Qcm9tcHQgPT09IFwidHJ1ZVwiIHx8IGRhdGEuc2tpcEluUHJvbXB0ID09PSB0cnVlO1xuXG5cdHJldHVybiB7XG5cdFx0bmFtZTogcGF0aC5iYXNlbmFtZShmaWxlUGF0aCwgcGF0aC5leHRuYW1lKGZpbGVQYXRoKSksXG5cdFx0ZmlsZVBhdGgsXG5cdFx0dGl0bGU6IGV4dHJhY3RUaXRsZShib2R5KSxcblx0XHRjb250ZW50OiBib2R5LnRyaW0oKSxcblx0XHRzY29wZTogXCJwcm9qZWN0XCIsXG5cdFx0c291cmNlOiBcIlwiLFxuXHRcdGZyb250bWF0dGVyLFxuXHRcdGlzVW5jb25kaXRpb25hbCxcblx0fTtcbn1cblxuZnVuY3Rpb24gc2NhbkRpcihkaXI6IHN0cmluZywgZmlsZXM6IHN0cmluZ1tdID0gW10pOiBzdHJpbmdbXSB7XG5cdGlmICghZnMuZXhpc3RzU3luYyhkaXIpKSByZXR1cm4gZmlsZXM7XG5cdGNvbnN0IGVudHJpZXMgPSBmcy5yZWFkZGlyU3luYyhkaXIsIHsgd2l0aEZpbGVUeXBlczogdHJ1ZSB9KTtcblx0Zm9yIChjb25zdCBlbnRyeSBvZiBlbnRyaWVzKSB7XG5cdFx0Y29uc3QgZnVsbFBhdGggPSBwYXRoLmpvaW4oZGlyLCBlbnRyeS5uYW1lKTtcblx0XHRpZiAoZW50cnkuaXNEaXJlY3RvcnkoKSkge1xuXHRcdFx0c2NhbkRpcihmdWxsUGF0aCwgZmlsZXMpO1xuXHRcdH0gZWxzZSBpZiAoZW50cnkuaXNGaWxlKCkgJiYgKGVudHJ5Lm5hbWUuZW5kc1dpdGgoXCIubWRcIikgfHwgZW50cnkubmFtZS5lbmRzV2l0aChcIi5tZGNcIikpKSB7XG5cdFx0XHRmaWxlcy5wdXNoKGZ1bGxQYXRoKTtcblx0XHR9XG5cdH1cblx0cmV0dXJuIGZpbGVzO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gbG9hZFJ1bGVzKHJ1bGVzRGlyOiBzdHJpbmcpOiBSdWxlQ2FjaGUge1xuXHRjb25zdCBydWxlczogUGFyc2VkUnVsZVtdID0gW107XG5cdGNvbnN0IHVuY29uZGl0aW9uYWw6IFBhcnNlZFJ1bGVbXSA9IFtdO1xuXHRjb25zdCBjb25kaXRpb25hbDogUGFyc2VkUnVsZVtdID0gW107XG5cblx0aWYgKCFmcy5leGlzdHNTeW5jKHJ1bGVzRGlyKSkge1xuXHRcdHJldHVybiB7IHJ1bGVzLCB1bmNvbmRpdGlvbmFsLCBjb25kaXRpb25hbCwgbG9hZGVkQXQ6IERhdGUubm93KCkgfTtcblx0fVxuXG5cdGNvbnN0IGZpbGVzID0gc2NhbkRpcihydWxlc0Rpcik7XG5cblx0Zm9yIChjb25zdCBmaWxlUGF0aCBvZiBmaWxlcykge1xuXHRcdGNvbnN0IGNvbnRlbnQgPSBmcy5yZWFkRmlsZVN5bmMoZmlsZVBhdGgsIFwidXRmLThcIik7XG5cdFx0Y29uc3QgcnVsZSA9IHBhcnNlUnVsZUZpbGUoZmlsZVBhdGgsIGNvbnRlbnQpO1xuXHRcdHJ1bGVzLnB1c2gocnVsZSk7XG5cdFx0aWYgKHJ1bGUuaXNVbmNvbmRpdGlvbmFsKSB7XG5cdFx0XHR1bmNvbmRpdGlvbmFsLnB1c2gocnVsZSk7XG5cdFx0fSBlbHNlIHtcblx0XHRcdGNvbmRpdGlvbmFsLnB1c2gocnVsZSk7XG5cdFx0fVxuXHR9XG5cblx0cmV0dXJuIHsgcnVsZXMsIHVuY29uZGl0aW9uYWwsIGNvbmRpdGlvbmFsLCBsb2FkZWRBdDogRGF0ZS5ub3coKSB9O1xufVxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUFBLFlBQVksUUFBUTtBQUNwQixZQUFZLFVBQVU7QUFHdEIsU0FBUyxXQUFXLEtBQXVCO0FBQzFDLFFBQU0sU0FBbUIsQ0FBQztBQUMxQixNQUFJLFFBQVE7QUFDWixNQUFJLFVBQVU7QUFDZCxhQUFXLE1BQU0sS0FBSztBQUNyQixRQUFJLE9BQU8sT0FBTyxPQUFPLE9BQU8sT0FBTyxJQUFLO0FBQUEsYUFDbkMsT0FBTyxPQUFPLE9BQU8sT0FBTyxPQUFPLElBQUs7QUFFakQsUUFBSSxPQUFPLE9BQU8sVUFBVSxHQUFHO0FBQzlCLFlBQU1BLFdBQVUsUUFBUSxLQUFLLEVBQUUsUUFBUSxnQkFBZ0IsRUFBRTtBQUN6RCxVQUFJQSxTQUFTLFFBQU8sS0FBS0EsUUFBTztBQUNoQyxnQkFBVTtBQUFBLElBQ1gsT0FBTztBQUNOLGlCQUFXO0FBQUEsSUFDWjtBQUFBLEVBQ0Q7QUFDQSxRQUFNLFVBQVUsUUFBUSxLQUFL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LEtBQUssaUJBQWlCLFdBQVcsQ0FBQyxLQUFLLFlBQVksSUFBSyxLQUFLO0FBQ3RFLE1BQUksS0FBSyxhQUFhLE9BQU8sS0FBSyxjQUFjLFNBQVUsYUFBWSxZQUFZLEtBQUs7QUFDdkYsTUFBSSxLQUFLLGtCQUFrQjtBQUMxQixnQkFBWSxnQkFBZ0IsS0FBSyxrQkFBa0IsVUFBVSxLQUFLLGtCQUFrQjtBQUNyRixNQUFJLEtBQUssaUJBQWlCO0FBQ3pCLGdCQUFZLGVBQWUsS0FBSyxpQkFBaUIsVUFBVSxLQUFLLGlCQUFpQjtBQUVsRixTQUFPO0FBQUEsSUFDTixNQUFXLGNBQVMsVUFBZSxhQUFRLFFBQVEsQ0FBQztBQUFBLElBQ3BEO0FBQUEsSUFDQSxPQUFPLGFBQWEsSUFBSTtBQUFBLElBQ3hCLFNBQVMsS0FBSyxLQUFLO0FBQUEsSUFDbkIsT0FBTztBQUFBLElBQ1AsUUFBUTtBQUFBLElBQ1I7QUFBQSxJQUNBO0FBQUEsRUFDRDtBQUNEO0FBRUEsU0FBUyxRQUFRLEtBQWEsUUFBa0IsQ0FBQyxHQUFhO0FBQzdELE1BQUksQ0FBSSxjQUFXLEdBQUcsRUFBRyxRQUFPO0FBQ2hDLFFBQU0sVUFBYSxlQUFZLEtBQUssRUFBRSxlQUFlLEtBQUssQ0FBQztBQUMzRCxhQUFXLFNBQVMsU0FBUztBQUM1QixVQUFNLFdBQWdCLFVBQUssS0FBSyxNQUFNLElBQUk7QUFDMUMsUUFBSSxNQUFNLFlBQVksR0FBRztBQUN4QixjQUFRLFVBQVUsS0FBSztBQUFBLElBQ3hCLFdBQVcsTUFBTSxPQUFPLE1BQU0sTUFBTSxLQUFLLFNBQVMsS0FBSyxLQUFLLE1BQU0sS0FBSyxTQUFTLE1BQU0sSUFBSTtBQUN6RixZQUFNLEtBQUssUUFBUTtBQUFBLElBQ3BCO0FBQUEsRUFDRDtBQUNBLFNBQU87QUFDUjtBQUVPLFNBQVMsVUFBVSxVQUE2QjtBQUN0RCxRQUFNLFFBQXNCLENBQUM7QUFDN0IsUUFBTSxnQkFBOEIsQ0FBQztBQUNyQyxRQUFNLGNBQTRCLENBQUM7QUFFbkMsTUFBSSxDQUFJLGNBQVcsUUFBUSxHQUFHO0FBQzdCLFdBQU8sRUFBRSxPQUFPLGVBQWUsYUFBYSxVQUFVLEtBQUssSUFBSSxFQUFFO0FBQUEsRUFDbEU7QUFFQSxRQUFNLFFBQVEsUUFBUSxRQUFRO0FBRTlCLGFBQVcsWUFBWSxPQUFPO0FBQzdCLFVBQU0sVUFBYSxnQkFBYSxVQUFVLE9BQU87QUFDakQsVUFBTSxPQUFPLGNBQWMsVUFBVSxPQUFPO0FBQzVDLFVBQU0sS0FBSyxJQUFJO0FBQ2YsUUFBSSxLQUFLLGlCQUFpQjtBQUN6QixvQkFBYyxLQUFLLElBQUk7QUFBQSxJQUN4QixPQUFPO0FBQ04sa0JBQVksS0FBSyxJQUFJO0FBQUEsSUFDdEI7QUFBQSxFQUNEO0FBRUEsU0FBTyxFQUFFLE9BQU8sZUFBZSxhQUFhLFVBQVUsS0FBSyxJQUFJLEVBQUU7QUFDbEU7IiwKICAibmFtZXMiOiBbInRyaW1tZWQiLCAiY2FtZWxLZXkiXQp9Cg==
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { ParsedRule, RuleCache, RuleFrontmatter } from "./types.js";
|
|
4
|
+
|
|
5
|
+
function splitComma(val: string): string[] {
|
|
6
|
+
const result: string[] = [];
|
|
7
|
+
let depth = 0;
|
|
8
|
+
let current = "";
|
|
9
|
+
for (const ch of val) {
|
|
10
|
+
if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
11
|
+
else if (ch === "}" || ch === ")" || ch === "]") depth--;
|
|
12
|
+
|
|
13
|
+
if (ch === "," && depth === 0) {
|
|
14
|
+
const trimmed = current.trim().replace(/^["']|["']$/g, "");
|
|
15
|
+
if (trimmed) result.push(trimmed);
|
|
16
|
+
current = "";
|
|
17
|
+
} else {
|
|
18
|
+
current += ch;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const trimmed = current.trim().replace(/^["']|["']$/g, "");
|
|
22
|
+
if (trimmed) result.push(trimmed);
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function parseFrontmatter(content: string): { data: Record<string, unknown>; body: string } {
|
|
27
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\n?---\r?\n([\s\S]*)$/;
|
|
28
|
+
const match = content.match(frontmatterRegex);
|
|
29
|
+
|
|
30
|
+
if (!match) {
|
|
31
|
+
return { data: {}, body: content };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const [, frontmatterStr, body] = match;
|
|
35
|
+
const data: Record<string, unknown> = {};
|
|
36
|
+
|
|
37
|
+
const lines = frontmatterStr.split("\n");
|
|
38
|
+
let i = 0;
|
|
39
|
+
while (i < lines.length) {
|
|
40
|
+
const line = lines[i];
|
|
41
|
+
const colonIndex = line.indexOf(":");
|
|
42
|
+
if (colonIndex === -1) {
|
|
43
|
+
i++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const rawKey = line.slice(0, colonIndex).trim();
|
|
48
|
+
let value: string | string[] | null = line.slice(colonIndex + 1).trim();
|
|
49
|
+
|
|
50
|
+
if (value === "" || value === "null" || value === "undefined") {
|
|
51
|
+
const listItems: string[] = [];
|
|
52
|
+
let j = i + 1;
|
|
53
|
+
while (j < lines.length) {
|
|
54
|
+
const subLine = lines[j];
|
|
55
|
+
if (subLine.match(/^\s*-\s+/)) {
|
|
56
|
+
listItems.push(
|
|
57
|
+
subLine
|
|
58
|
+
.replace(/^\s*-\s+/, "")
|
|
59
|
+
.trim()
|
|
60
|
+
.replace(/^["']|["']$/g, ""),
|
|
61
|
+
);
|
|
62
|
+
j++;
|
|
63
|
+
} else if (subLine.trim() === "" || subLine.match(/^\s+/)) {
|
|
64
|
+
j++;
|
|
65
|
+
} else {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (listItems.length > 0) {
|
|
70
|
+
const camelKey = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
71
|
+
data[camelKey] = listItems;
|
|
72
|
+
i = j;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
value = null;
|
|
76
|
+
} else if (value.startsWith("[") && value.endsWith("]")) {
|
|
77
|
+
try {
|
|
78
|
+
value = JSON.parse(value.replace(/'/g, '"'));
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.debug("[rules-engine] JSON array parse failed:", err instanceof Error ? err.message : err);
|
|
81
|
+
value = (value as string)
|
|
82
|
+
.slice(1, -1)
|
|
83
|
+
.split(",")
|
|
84
|
+
.map((v: string) => v.trim().replace(/^["']|["']$/g, ""));
|
|
85
|
+
}
|
|
86
|
+
} else if (value.startsWith('"') && value.endsWith('"')) {
|
|
87
|
+
value = value.slice(1, -1);
|
|
88
|
+
} else if (value.startsWith("'") && value.endsWith("'")) {
|
|
89
|
+
value = value.slice(1, -1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const camelKey = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
93
|
+
|
|
94
|
+
if ((camelKey === "paths" || camelKey === "globs") && typeof value === "string") {
|
|
95
|
+
data[camelKey] = splitComma(value);
|
|
96
|
+
} else {
|
|
97
|
+
data[camelKey] = value;
|
|
98
|
+
}
|
|
99
|
+
i++;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { data, body: body.trim() };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function extractTitle(body: string): string {
|
|
106
|
+
for (const line of body.split("\n")) {
|
|
107
|
+
const trimmed = line.trim();
|
|
108
|
+
if (trimmed) {
|
|
109
|
+
return trimmed.replace(/^#+\s*/, "").replace(/\*\*/g, "");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return "Untitled Rule";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parsePaths(raw: unknown): string[] {
|
|
116
|
+
if (!raw) return [];
|
|
117
|
+
if (typeof raw === "string") {
|
|
118
|
+
return raw
|
|
119
|
+
.split(",")
|
|
120
|
+
.map((g) => g.trim())
|
|
121
|
+
.filter(Boolean);
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(raw)) return raw as string[];
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function parseRuleFile(filePath: string, content: string): ParsedRule {
|
|
128
|
+
const { data, body } = parseFrontmatter(content);
|
|
129
|
+
const rawGlobs = data.globs ?? data.paths;
|
|
130
|
+
const globs = parsePaths(rawGlobs);
|
|
131
|
+
const rawPaths = data.paths;
|
|
132
|
+
const paths = parsePaths(rawPaths);
|
|
133
|
+
const isUnconditional = globs.length === 0 || (globs.length === 1 && globs[0] === "**");
|
|
134
|
+
|
|
135
|
+
const frontmatter: RuleFrontmatter = {};
|
|
136
|
+
if (rawGlobs) {
|
|
137
|
+
frontmatter.globs = globs;
|
|
138
|
+
if (rawPaths) {
|
|
139
|
+
frontmatter.paths = paths;
|
|
140
|
+
} else {
|
|
141
|
+
frontmatter.paths = globs;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (data.description && typeof data.description === "string") frontmatter.description = data.description;
|
|
145
|
+
if (data.severity && typeof data.severity === "string")
|
|
146
|
+
frontmatter.severity = data.severity as ParsedRule["frontmatter"]["severity"];
|
|
147
|
+
if (data.allowedTools)
|
|
148
|
+
frontmatter.allowedTools =
|
|
149
|
+
typeof data.allowedTools === "string" ? [data.allowedTools] : (data.allowedTools as string[]);
|
|
150
|
+
if (data.whenToUse && typeof data.whenToUse === "string") frontmatter.whenToUse = data.whenToUse;
|
|
151
|
+
if (data.notifyOnMatch !== undefined)
|
|
152
|
+
frontmatter.notifyOnMatch = data.notifyOnMatch === "true" || data.notifyOnMatch === true;
|
|
153
|
+
if (data.skipInPrompt !== undefined)
|
|
154
|
+
frontmatter.skipInPrompt = data.skipInPrompt === "true" || data.skipInPrompt === true;
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
name: path.basename(filePath, path.extname(filePath)),
|
|
158
|
+
filePath,
|
|
159
|
+
title: extractTitle(body),
|
|
160
|
+
content: body.trim(),
|
|
161
|
+
scope: "project",
|
|
162
|
+
source: "",
|
|
163
|
+
frontmatter,
|
|
164
|
+
isUnconditional,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function scanDir(dir: string, files: string[] = []): string[] {
|
|
169
|
+
if (!fs.existsSync(dir)) return files;
|
|
170
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
const fullPath = path.join(dir, entry.name);
|
|
173
|
+
if (entry.isDirectory()) {
|
|
174
|
+
scanDir(fullPath, files);
|
|
175
|
+
} else if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".mdc"))) {
|
|
176
|
+
files.push(fullPath);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return files;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function loadRules(rulesDir: string): RuleCache {
|
|
183
|
+
const rules: ParsedRule[] = [];
|
|
184
|
+
const unconditional: ParsedRule[] = [];
|
|
185
|
+
const conditional: ParsedRule[] = [];
|
|
186
|
+
|
|
187
|
+
if (!fs.existsSync(rulesDir)) {
|
|
188
|
+
return { rules, unconditional, conditional, loadedAt: Date.now() };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const files = scanDir(rulesDir);
|
|
192
|
+
|
|
193
|
+
for (const filePath of files) {
|
|
194
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
195
|
+
const rule = parseRuleFile(filePath, content);
|
|
196
|
+
rules.push(rule);
|
|
197
|
+
if (rule.isUnconditional) {
|
|
198
|
+
unconditional.push(rule);
|
|
199
|
+
} else {
|
|
200
|
+
conditional.push(rule);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { rules, unconditional, conditional, loadedAt: Date.now() };
|
|
205
|
+
}
|