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