opencode-rules-md 0.8.2 → 0.8.4
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/tui/index.js +846 -11
- package/dist/tui/index.js.map +16 -1
- package/package.json +5 -14
- package/dist/tui/tui/index.js +0 -7869
- package/dist/tui/tui/index.js.map +0 -88
package/dist/tui/index.js
CHANGED
|
@@ -1,13 +1,848 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
4
|
+
// tui/index.tsx
|
|
5
|
+
import { createComponent as _$createComponent2 } from "@opentui/solid";
|
|
6
|
+
|
|
7
|
+
// tui/slots/sidebar-content.tsx
|
|
8
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
9
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
10
|
+
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
11
|
+
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
12
|
+
import { insert as _$insert } from "@opentui/solid";
|
|
13
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
14
|
+
import { setProp as _$setProp } from "@opentui/solid";
|
|
15
|
+
import { createElement as _$createElement } from "@opentui/solid";
|
|
16
|
+
import { createSignal, createEffect, createMemo, onCleanup, Show, For } from "solid-js";
|
|
17
|
+
|
|
18
|
+
// src/rule-discovery.ts
|
|
19
|
+
import { stat, readFile, readdir } from "fs/promises";
|
|
20
|
+
import path from "path";
|
|
21
|
+
import os from "os";
|
|
22
|
+
|
|
23
|
+
// src/debug.ts
|
|
24
|
+
function createDebugLog(prefix = "[opencode-rules-md]") {
|
|
25
|
+
return (message) => {
|
|
26
|
+
if (process.env.OPENCODE_RULES_DEBUG) {
|
|
27
|
+
console.debug(`${prefix} ${message}`);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/rule-metadata.ts
|
|
33
|
+
var { parse: parseYaml } = await import("yaml");
|
|
34
|
+
function extractStringArray(value) {
|
|
35
|
+
if (!Array.isArray(value)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const result = value.filter((v) => typeof v === "string").map((v) => v.trim()).filter((v) => v.length > 0);
|
|
39
|
+
return result.length > 0 ? result : undefined;
|
|
40
|
+
}
|
|
41
|
+
function parseRuleMetadata(content) {
|
|
42
|
+
if (!content.startsWith("---")) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const endIndex = content.indexOf("---", 3);
|
|
46
|
+
if (endIndex === -1) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const frontmatter = content.substring(3, endIndex).trim();
|
|
50
|
+
if (!frontmatter) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const parsed = parseYaml(frontmatter);
|
|
55
|
+
if (!parsed || typeof parsed !== "object") {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const metadata = {};
|
|
59
|
+
const arrayFields = [
|
|
60
|
+
"globs",
|
|
61
|
+
"keywords",
|
|
62
|
+
"tools",
|
|
63
|
+
"model",
|
|
64
|
+
"agent",
|
|
65
|
+
"command",
|
|
66
|
+
"project",
|
|
67
|
+
"branch",
|
|
68
|
+
"os"
|
|
69
|
+
];
|
|
70
|
+
for (const field of arrayFields) {
|
|
71
|
+
const extracted = extractStringArray(parsed[field]);
|
|
72
|
+
if (extracted) {
|
|
73
|
+
metadata[field] = extracted;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (typeof parsed.ci === "boolean") {
|
|
77
|
+
metadata.ci = parsed.ci;
|
|
78
|
+
}
|
|
79
|
+
if (parsed.match === "any" || parsed.match === "all") {
|
|
80
|
+
metadata.match = parsed.match;
|
|
81
|
+
}
|
|
82
|
+
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
console.warn(`[opencode-rules-md] Warning: Failed to parse YAML frontmatter: ${message}`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function stripFrontmatter(content) {
|
|
90
|
+
if (!content.startsWith("---")) {
|
|
91
|
+
return content;
|
|
92
|
+
}
|
|
93
|
+
const endIndex = content.indexOf("---", 3);
|
|
94
|
+
if (endIndex === -1) {
|
|
95
|
+
return content;
|
|
96
|
+
}
|
|
97
|
+
return content.substring(endIndex + 3).trimStart();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/rule-discovery.ts
|
|
101
|
+
var debugLog = createDebugLog();
|
|
102
|
+
var ruleCache = new Map;
|
|
103
|
+
async function getCachedRule(filePath) {
|
|
104
|
+
try {
|
|
105
|
+
const stats = await stat(filePath);
|
|
106
|
+
const mtime = stats.mtimeMs;
|
|
107
|
+
const cached = ruleCache.get(filePath);
|
|
108
|
+
if (cached && cached.mtime === mtime) {
|
|
109
|
+
debugLog(`Cache hit: ${filePath}`);
|
|
110
|
+
return cached;
|
|
111
|
+
}
|
|
112
|
+
debugLog(`Cache miss: ${filePath}`);
|
|
113
|
+
const content = await readFile(filePath, "utf-8");
|
|
114
|
+
const metadata = parseRuleMetadata(content);
|
|
115
|
+
const strippedContent = stripFrontmatter(content);
|
|
116
|
+
const entry = {
|
|
117
|
+
content,
|
|
118
|
+
metadata,
|
|
119
|
+
strippedContent,
|
|
120
|
+
mtime
|
|
121
|
+
};
|
|
122
|
+
ruleCache.set(filePath, entry);
|
|
123
|
+
return entry;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
ruleCache.delete(filePath);
|
|
126
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
127
|
+
console.warn(`[opencode-rules-md] Warning: Failed to read rule file ${filePath}: ${message}`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function getGlobalRulesDir() {
|
|
132
|
+
const opencodeConfigDir = process.env.OPENCODE_CONFIG_DIR;
|
|
133
|
+
if (opencodeConfigDir) {
|
|
134
|
+
return path.join(opencodeConfigDir, "rules");
|
|
135
|
+
}
|
|
136
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
137
|
+
if (xdgConfigHome) {
|
|
138
|
+
return path.join(xdgConfigHome, "opencode", "rules");
|
|
139
|
+
}
|
|
140
|
+
const homeDir = process.env.HOME || os.homedir();
|
|
141
|
+
return path.join(homeDir, ".config", "opencode", "rules");
|
|
142
|
+
}
|
|
143
|
+
async function scanDirectoryRecursively(dir, baseDir) {
|
|
144
|
+
const results = [];
|
|
145
|
+
try {
|
|
146
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
147
|
+
for (const entry of entries) {
|
|
148
|
+
if (entry.name.startsWith(".")) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const fullPath = path.join(dir, entry.name);
|
|
152
|
+
if (entry.isDirectory()) {
|
|
153
|
+
results.push(...await scanDirectoryRecursively(fullPath, baseDir));
|
|
154
|
+
} else if (entry.name.endsWith(".md") || entry.name.endsWith(".mdc")) {
|
|
155
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
156
|
+
results.push({ filePath: fullPath, relativePath });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
161
|
+
return results;
|
|
162
|
+
}
|
|
163
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
164
|
+
console.warn(`[opencode-rules-md] Warning: Failed to read directory ${dir}: ${message}`);
|
|
165
|
+
}
|
|
166
|
+
return results;
|
|
167
|
+
}
|
|
168
|
+
async function discoverRuleFiles(projectDir) {
|
|
169
|
+
const files = [];
|
|
170
|
+
const globalRulesDir = getGlobalRulesDir();
|
|
171
|
+
if (globalRulesDir) {
|
|
172
|
+
const globalRules = await scanDirectoryRecursively(globalRulesDir, globalRulesDir);
|
|
173
|
+
for (const { filePath, relativePath } of globalRules) {
|
|
174
|
+
debugLog(`Discovered global rule: ${relativePath} (${filePath})`);
|
|
175
|
+
files.push({ filePath, relativePath });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (projectDir) {
|
|
179
|
+
const projectRulesDir = path.join(projectDir, ".opencode", "rules");
|
|
180
|
+
const projectRules = await scanDirectoryRecursively(projectRulesDir, projectRulesDir);
|
|
181
|
+
for (const { filePath, relativePath } of projectRules) {
|
|
182
|
+
debugLog(`Discovered project rule: ${relativePath} (${filePath})`);
|
|
183
|
+
files.push({ filePath, relativePath });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return files;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/active-rules-state.ts
|
|
190
|
+
import * as fs from "fs/promises";
|
|
191
|
+
import * as os2 from "os";
|
|
192
|
+
import * as path2 from "path";
|
|
193
|
+
var debugLog2 = createDebugLog();
|
|
194
|
+
var writeQueues = new Map;
|
|
195
|
+
var stateDirOverride = null;
|
|
196
|
+
var SAFE_SESSION_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
197
|
+
function isValidSessionId(sessionId) {
|
|
198
|
+
return SAFE_SESSION_ID_PATTERN.test(sessionId);
|
|
199
|
+
}
|
|
200
|
+
function resolveStateDir() {
|
|
201
|
+
if (stateDirOverride !== null) {
|
|
202
|
+
return stateDirOverride;
|
|
203
|
+
}
|
|
204
|
+
return path2.join(os2.homedir(), ".opencode", "state", "opencode-rules-md");
|
|
205
|
+
}
|
|
206
|
+
function getStateFilePath(sessionId) {
|
|
207
|
+
if (!isValidSessionId(sessionId)) {
|
|
208
|
+
throw new Error(`Invalid sessionId: ${sessionId}`);
|
|
209
|
+
}
|
|
210
|
+
return path2.join(resolveStateDir(), `${sessionId}.json`);
|
|
211
|
+
}
|
|
212
|
+
async function readActiveRulesState(sessionId) {
|
|
213
|
+
if (!isValidSessionId(sessionId)) {
|
|
214
|
+
debugLog2(`Invalid sessionId rejected: ${sessionId}`);
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const filePath = getStateFilePath(sessionId);
|
|
218
|
+
try {
|
|
219
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
220
|
+
const parsed = JSON.parse(content);
|
|
221
|
+
if (!isValidActiveRulesState(parsed)) {
|
|
222
|
+
debugLog2(`Invalid active rules state format for session ${sessionId}`);
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return parsed;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
debugLog2(`Failed to read active rules state for session ${sessionId}: ${error}`);
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function isValidActiveRulesState(value) {
|
|
232
|
+
if (typeof value !== "object" || value === null) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
const obj = value;
|
|
236
|
+
if (typeof obj["sessionId"] !== "string") {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
if (typeof obj["evaluatedAt"] !== "number") {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
if (!Array.isArray(obj["matchedRulePaths"])) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
for (const item of obj["matchedRulePaths"]) {
|
|
246
|
+
if (typeof item !== "string") {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// tui/data/rules.ts
|
|
254
|
+
import path3 from "path";
|
|
255
|
+
async function loadSidebarRules(projectDir, sessionId) {
|
|
256
|
+
const discovered = await discoverRuleFiles(projectDir ?? undefined);
|
|
257
|
+
const activeState = sessionId ? await readActiveRulesState(sessionId) : null;
|
|
258
|
+
const hasEvaluationState = activeState !== null;
|
|
259
|
+
const matchedPathsSet = hasEvaluationState ? new Set(activeState.matchedRulePaths) : null;
|
|
260
|
+
const entries = [];
|
|
261
|
+
let skippedCount = 0;
|
|
262
|
+
for (const rule of discovered) {
|
|
263
|
+
const cached = await getCachedRule(rule.filePath);
|
|
264
|
+
if (!cached) {
|
|
265
|
+
skippedCount++;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const meta = cached.metadata;
|
|
269
|
+
const source = ruleSource(rule.filePath, projectDir);
|
|
270
|
+
const isConditional = hasConditions(meta);
|
|
271
|
+
const conditionSummary = isConditional ? formatConditionSummary(meta) : "always active";
|
|
272
|
+
let isActive;
|
|
273
|
+
if (matchedPathsSet !== null) {
|
|
274
|
+
isActive = matchedPathsSet.has(rule.filePath);
|
|
275
|
+
} else {
|
|
276
|
+
isActive = isConditional ? null : true;
|
|
277
|
+
}
|
|
278
|
+
entries.push({
|
|
279
|
+
name: "",
|
|
280
|
+
path: rule.relativePath,
|
|
281
|
+
source,
|
|
282
|
+
isConditional,
|
|
283
|
+
conditionSummary,
|
|
284
|
+
metadata: meta ?? {},
|
|
285
|
+
isActive
|
|
10
286
|
});
|
|
287
|
+
}
|
|
288
|
+
disambiguateNames(entries);
|
|
289
|
+
const activeOrder = (v) => v === true ? 0 : v === null ? 1 : 2;
|
|
290
|
+
entries.sort((a, b) => {
|
|
291
|
+
if (a.source !== b.source)
|
|
292
|
+
return a.source === "project" ? -1 : 1;
|
|
293
|
+
const activeCmp = activeOrder(a.isActive) - activeOrder(b.isActive);
|
|
294
|
+
if (activeCmp !== 0)
|
|
295
|
+
return activeCmp;
|
|
296
|
+
const nameCompare = a.name.localeCompare(b.name);
|
|
297
|
+
if (nameCompare !== 0)
|
|
298
|
+
return nameCompare;
|
|
299
|
+
return a.path.localeCompare(b.path);
|
|
300
|
+
});
|
|
301
|
+
return { rules: entries, skippedCount, hasEvaluationState };
|
|
302
|
+
}
|
|
303
|
+
function ruleSource(filePath, projectDir) {
|
|
304
|
+
if (!projectDir)
|
|
305
|
+
return "global";
|
|
306
|
+
const projectRulesPrefix = path3.join(projectDir, ".opencode", "rules") + path3.sep;
|
|
307
|
+
return filePath.startsWith(projectRulesPrefix) ? "project" : "global";
|
|
308
|
+
}
|
|
309
|
+
function hasConditions(meta) {
|
|
310
|
+
if (!meta)
|
|
311
|
+
return false;
|
|
312
|
+
return !!(meta.globs || meta.keywords || meta.tools || meta.model || meta.agent || meta.command || meta.project || meta.branch || meta.os || meta.ci !== undefined);
|
|
313
|
+
}
|
|
314
|
+
function formatConditionSummary(meta) {
|
|
315
|
+
const parts = [];
|
|
316
|
+
const arrayFields = [
|
|
317
|
+
["globs", "globs"],
|
|
318
|
+
["keywords", "keywords"],
|
|
319
|
+
["tools", "tools"],
|
|
320
|
+
["model", "model"],
|
|
321
|
+
["agent", "agent"],
|
|
322
|
+
["command", "command"],
|
|
323
|
+
["project", "project"],
|
|
324
|
+
["branch", "branch"],
|
|
325
|
+
["os", "os"]
|
|
326
|
+
];
|
|
327
|
+
for (const [field, label] of arrayFields) {
|
|
328
|
+
const value = meta[field];
|
|
329
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
330
|
+
parts.push(`${label}: ${value.join(", ")}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (meta.ci !== undefined) {
|
|
334
|
+
parts.push(`ci: ${String(meta.ci)}`);
|
|
335
|
+
}
|
|
336
|
+
if (meta.match) {
|
|
337
|
+
parts.push(`match: ${meta.match}`);
|
|
338
|
+
}
|
|
339
|
+
return parts.join(", ");
|
|
340
|
+
}
|
|
341
|
+
function disambiguateNames(entries) {
|
|
342
|
+
for (const entry of entries) {
|
|
343
|
+
const basename = path3.basename(entry.path);
|
|
344
|
+
const dotIndex = basename.lastIndexOf(".");
|
|
345
|
+
entry.name = dotIndex > 0 ? basename.substring(0, dotIndex) : basename;
|
|
346
|
+
}
|
|
347
|
+
const stemCounts = new Map;
|
|
348
|
+
for (const entry of entries) {
|
|
349
|
+
stemCounts.set(entry.name, (stemCounts.get(entry.name) ?? 0) + 1);
|
|
350
|
+
}
|
|
351
|
+
for (const entry of entries) {
|
|
352
|
+
if ((stemCounts.get(entry.name) ?? 0) <= 1)
|
|
353
|
+
continue;
|
|
354
|
+
const dir = path3.dirname(entry.path);
|
|
355
|
+
if (dir && dir !== ".") {
|
|
356
|
+
const parent = path3.basename(dir);
|
|
357
|
+
entry.name = `${parent}/${entry.name}`;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const nameCounts = new Map;
|
|
361
|
+
for (const entry of entries) {
|
|
362
|
+
nameCounts.set(entry.name, (nameCounts.get(entry.name) ?? 0) + 1);
|
|
363
|
+
}
|
|
364
|
+
for (const entry of entries) {
|
|
365
|
+
if ((nameCounts.get(entry.name) ?? 0) > 1) {
|
|
366
|
+
entry.name = entry.path;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// tui/slots/sidebar-content.tsx
|
|
372
|
+
function RuleSection(props) {
|
|
373
|
+
const activeCount = createMemo(() => props.rules.filter((r) => r.isActive === true).length);
|
|
374
|
+
const headerCount = createMemo(() => {
|
|
375
|
+
if (props.hasEvaluationState) {
|
|
376
|
+
return `(${activeCount()}/${props.rules.length})`;
|
|
377
|
+
}
|
|
378
|
+
return `(${props.rules.length})`;
|
|
379
|
+
});
|
|
380
|
+
const bulletColor = (rule) => {
|
|
381
|
+
return rule.isActive === true ? props.theme.success : props.theme.textMuted;
|
|
382
|
+
};
|
|
383
|
+
return _$createComponent(Show, {
|
|
384
|
+
get when() {
|
|
385
|
+
return props.rules.length > 0;
|
|
386
|
+
},
|
|
387
|
+
get children() {
|
|
388
|
+
var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("text");
|
|
389
|
+
_$insertNode(_el$, _el$2);
|
|
390
|
+
_$insertNode(_el$2, _el$3);
|
|
391
|
+
_$insertNode(_el$2, _el$4);
|
|
392
|
+
_$setProp(_el$2, "flexDirection", "row");
|
|
393
|
+
_$setProp(_el$2, "gap", 1);
|
|
394
|
+
_$setProp(_el$2, "onMouseDown", () => props.onToggle());
|
|
395
|
+
_$insert(_el$3, () => props.open ? "\u25BC" : "\u25B6");
|
|
396
|
+
_$insert(_el$4, () => props.title, null);
|
|
397
|
+
_$insert(_el$4, _$createComponent(Show, {
|
|
398
|
+
get when() {
|
|
399
|
+
return !props.open;
|
|
400
|
+
},
|
|
401
|
+
get children() {
|
|
402
|
+
var _el$5 = _$createElement("span"), _el$6 = _$createTextNode(` `);
|
|
403
|
+
_$insertNode(_el$5, _el$6);
|
|
404
|
+
_$insert(_el$5, headerCount, null);
|
|
405
|
+
_$effect((_$p) => _$setProp(_el$5, "style", {
|
|
406
|
+
fg: props.theme.textMuted
|
|
407
|
+
}, _$p));
|
|
408
|
+
return _el$5;
|
|
409
|
+
}
|
|
410
|
+
}), null);
|
|
411
|
+
_$insert(_el$, _$createComponent(Show, {
|
|
412
|
+
get when() {
|
|
413
|
+
return props.open;
|
|
414
|
+
},
|
|
415
|
+
get children() {
|
|
416
|
+
return _$createComponent(For, {
|
|
417
|
+
get each() {
|
|
418
|
+
return props.rules;
|
|
419
|
+
},
|
|
420
|
+
children: (rule, localIndex) => {
|
|
421
|
+
const globalIndex = () => props.globalOffset + localIndex();
|
|
422
|
+
return (() => {
|
|
423
|
+
var _el$7 = _$createElement("box"), _el$8 = _$createElement("box"), _el$9 = _$createElement("text"), _el$1 = _$createElement("text");
|
|
424
|
+
_$insertNode(_el$7, _el$8);
|
|
425
|
+
_$setProp(_el$7, "flexDirection", "column");
|
|
426
|
+
_$setProp(_el$7, "onMouseDown", () => props.onExpandToggle(globalIndex()));
|
|
427
|
+
_$insertNode(_el$8, _el$9);
|
|
428
|
+
_$insertNode(_el$8, _el$1);
|
|
429
|
+
_$setProp(_el$8, "flexDirection", "row");
|
|
430
|
+
_$setProp(_el$8, "gap", 1);
|
|
431
|
+
_$insertNode(_el$9, _$createTextNode(`\u2022`));
|
|
432
|
+
_$insert(_el$1, () => rule.name);
|
|
433
|
+
_$insert(_el$7, _$createComponent(Show, {
|
|
434
|
+
get when() {
|
|
435
|
+
return props.expandedIndex === globalIndex();
|
|
436
|
+
},
|
|
437
|
+
get children() {
|
|
438
|
+
var _el$10 = _$createElement("box"), _el$11 = _$createElement("text");
|
|
439
|
+
_$insertNode(_el$10, _el$11);
|
|
440
|
+
_$setProp(_el$10, "flexDirection", "column");
|
|
441
|
+
_$setProp(_el$10, "paddingLeft", 4);
|
|
442
|
+
_$insert(_el$11, () => rule.path);
|
|
443
|
+
_$insert(_el$10, _$createComponent(Show, {
|
|
444
|
+
get when() {
|
|
445
|
+
return (rule.metadata.globs?.length ?? 0) > 0;
|
|
446
|
+
},
|
|
447
|
+
get children() {
|
|
448
|
+
var _el$12 = _$createElement("text"), _el$13 = _$createTextNode(`Globs: `);
|
|
449
|
+
_$insertNode(_el$12, _el$13);
|
|
450
|
+
_$insert(_el$12, () => rule.metadata.globs.join(", "), null);
|
|
451
|
+
_$effect((_$p) => _$setProp(_el$12, "fg", props.theme.textMuted, _$p));
|
|
452
|
+
return _el$12;
|
|
453
|
+
}
|
|
454
|
+
}), null);
|
|
455
|
+
_$insert(_el$10, _$createComponent(Show, {
|
|
456
|
+
get when() {
|
|
457
|
+
return (rule.metadata.keywords?.length ?? 0) > 0;
|
|
458
|
+
},
|
|
459
|
+
get children() {
|
|
460
|
+
var _el$14 = _$createElement("text"), _el$15 = _$createTextNode(`Keywords: `);
|
|
461
|
+
_$insertNode(_el$14, _el$15);
|
|
462
|
+
_$insert(_el$14, () => rule.metadata.keywords.join(", "), null);
|
|
463
|
+
_$effect((_$p) => _$setProp(_el$14, "fg", props.theme.textMuted, _$p));
|
|
464
|
+
return _el$14;
|
|
465
|
+
}
|
|
466
|
+
}), null);
|
|
467
|
+
_$insert(_el$10, _$createComponent(Show, {
|
|
468
|
+
get when() {
|
|
469
|
+
return (rule.metadata.tools?.length ?? 0) > 0;
|
|
470
|
+
},
|
|
471
|
+
get children() {
|
|
472
|
+
var _el$16 = _$createElement("text"), _el$17 = _$createTextNode(`Tools: `);
|
|
473
|
+
_$insertNode(_el$16, _el$17);
|
|
474
|
+
_$insert(_el$16, () => rule.metadata.tools.join(", "), null);
|
|
475
|
+
_$effect((_$p) => _$setProp(_el$16, "fg", props.theme.textMuted, _$p));
|
|
476
|
+
return _el$16;
|
|
477
|
+
}
|
|
478
|
+
}), null);
|
|
479
|
+
_$insert(_el$10, _$createComponent(Show, {
|
|
480
|
+
get when() {
|
|
481
|
+
return (rule.metadata.model?.length ?? 0) > 0;
|
|
482
|
+
},
|
|
483
|
+
get children() {
|
|
484
|
+
var _el$18 = _$createElement("text"), _el$19 = _$createTextNode(`Model: `);
|
|
485
|
+
_$insertNode(_el$18, _el$19);
|
|
486
|
+
_$insert(_el$18, () => rule.metadata.model.join(", "), null);
|
|
487
|
+
_$effect((_$p) => _$setProp(_el$18, "fg", props.theme.textMuted, _$p));
|
|
488
|
+
return _el$18;
|
|
489
|
+
}
|
|
490
|
+
}), null);
|
|
491
|
+
_$insert(_el$10, _$createComponent(Show, {
|
|
492
|
+
get when() {
|
|
493
|
+
return (rule.metadata.agent?.length ?? 0) > 0;
|
|
494
|
+
},
|
|
495
|
+
get children() {
|
|
496
|
+
var _el$20 = _$createElement("text"), _el$21 = _$createTextNode(`Agent: `);
|
|
497
|
+
_$insertNode(_el$20, _el$21);
|
|
498
|
+
_$insert(_el$20, () => rule.metadata.agent.join(", "), null);
|
|
499
|
+
_$effect((_$p) => _$setProp(_el$20, "fg", props.theme.textMuted, _$p));
|
|
500
|
+
return _el$20;
|
|
501
|
+
}
|
|
502
|
+
}), null);
|
|
503
|
+
_$insert(_el$10, _$createComponent(Show, {
|
|
504
|
+
get when() {
|
|
505
|
+
return (rule.metadata.command?.length ?? 0) > 0;
|
|
506
|
+
},
|
|
507
|
+
get children() {
|
|
508
|
+
var _el$22 = _$createElement("text"), _el$23 = _$createTextNode(`Command: `);
|
|
509
|
+
_$insertNode(_el$22, _el$23);
|
|
510
|
+
_$insert(_el$22, () => rule.metadata.command.join(", "), null);
|
|
511
|
+
_$effect((_$p) => _$setProp(_el$22, "fg", props.theme.textMuted, _$p));
|
|
512
|
+
return _el$22;
|
|
513
|
+
}
|
|
514
|
+
}), null);
|
|
515
|
+
_$insert(_el$10, _$createComponent(Show, {
|
|
516
|
+
get when() {
|
|
517
|
+
return (rule.metadata.project?.length ?? 0) > 0;
|
|
518
|
+
},
|
|
519
|
+
get children() {
|
|
520
|
+
var _el$24 = _$createElement("text"), _el$25 = _$createTextNode(`Project: `);
|
|
521
|
+
_$insertNode(_el$24, _el$25);
|
|
522
|
+
_$insert(_el$24, () => rule.metadata.project.join(", "), null);
|
|
523
|
+
_$effect((_$p) => _$setProp(_el$24, "fg", props.theme.textMuted, _$p));
|
|
524
|
+
return _el$24;
|
|
525
|
+
}
|
|
526
|
+
}), null);
|
|
527
|
+
_$insert(_el$10, _$createComponent(Show, {
|
|
528
|
+
get when() {
|
|
529
|
+
return (rule.metadata.branch?.length ?? 0) > 0;
|
|
530
|
+
},
|
|
531
|
+
get children() {
|
|
532
|
+
var _el$26 = _$createElement("text"), _el$27 = _$createTextNode(`Branch: `);
|
|
533
|
+
_$insertNode(_el$26, _el$27);
|
|
534
|
+
_$insert(_el$26, () => rule.metadata.branch.join(", "), null);
|
|
535
|
+
_$effect((_$p) => _$setProp(_el$26, "fg", props.theme.textMuted, _$p));
|
|
536
|
+
return _el$26;
|
|
537
|
+
}
|
|
538
|
+
}), null);
|
|
539
|
+
_$insert(_el$10, _$createComponent(Show, {
|
|
540
|
+
get when() {
|
|
541
|
+
return (rule.metadata.os?.length ?? 0) > 0;
|
|
542
|
+
},
|
|
543
|
+
get children() {
|
|
544
|
+
var _el$28 = _$createElement("text"), _el$29 = _$createTextNode(`OS: `);
|
|
545
|
+
_$insertNode(_el$28, _el$29);
|
|
546
|
+
_$insert(_el$28, () => rule.metadata.os.join(", "), null);
|
|
547
|
+
_$effect((_$p) => _$setProp(_el$28, "fg", props.theme.textMuted, _$p));
|
|
548
|
+
return _el$28;
|
|
549
|
+
}
|
|
550
|
+
}), null);
|
|
551
|
+
_$insert(_el$10, _$createComponent(Show, {
|
|
552
|
+
get when() {
|
|
553
|
+
return rule.metadata.ci !== undefined;
|
|
554
|
+
},
|
|
555
|
+
get children() {
|
|
556
|
+
var _el$30 = _$createElement("text"), _el$31 = _$createTextNode(`CI: `);
|
|
557
|
+
_$insertNode(_el$30, _el$31);
|
|
558
|
+
_$insert(_el$30, () => String(rule.metadata.ci), null);
|
|
559
|
+
_$effect((_$p) => _$setProp(_el$30, "fg", props.theme.textMuted, _$p));
|
|
560
|
+
return _el$30;
|
|
561
|
+
}
|
|
562
|
+
}), null);
|
|
563
|
+
_$insert(_el$10, _$createComponent(Show, {
|
|
564
|
+
get when() {
|
|
565
|
+
return rule.metadata.match;
|
|
566
|
+
},
|
|
567
|
+
get children() {
|
|
568
|
+
var _el$32 = _$createElement("text"), _el$33 = _$createTextNode(`Match: `);
|
|
569
|
+
_$insertNode(_el$32, _el$33);
|
|
570
|
+
_$insert(_el$32, () => rule.metadata.match, null);
|
|
571
|
+
_$effect((_$p) => _$setProp(_el$32, "fg", props.theme.textMuted, _$p));
|
|
572
|
+
return _el$32;
|
|
573
|
+
}
|
|
574
|
+
}), null);
|
|
575
|
+
_$effect((_$p) => _$setProp(_el$11, "fg", props.theme.textMuted, _$p));
|
|
576
|
+
return _el$10;
|
|
577
|
+
}
|
|
578
|
+
}), null);
|
|
579
|
+
_$effect((_p$) => {
|
|
580
|
+
var _v$3 = bulletColor(rule), _v$4 = props.theme.text;
|
|
581
|
+
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$9, "fg", _v$3, _p$.e));
|
|
582
|
+
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$1, "fg", _v$4, _p$.t));
|
|
583
|
+
return _p$;
|
|
584
|
+
}, {
|
|
585
|
+
e: undefined,
|
|
586
|
+
t: undefined
|
|
587
|
+
});
|
|
588
|
+
return _el$7;
|
|
589
|
+
})();
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}), null);
|
|
594
|
+
_$effect((_p$) => {
|
|
595
|
+
var _v$ = props.theme.text, _v$2 = props.theme.text;
|
|
596
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$3, "fg", _v$, _p$.e));
|
|
597
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$4, "fg", _v$2, _p$.t));
|
|
598
|
+
return _p$;
|
|
599
|
+
}, {
|
|
600
|
+
e: undefined,
|
|
601
|
+
t: undefined
|
|
602
|
+
});
|
|
603
|
+
return _el$;
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
function SidebarContent(props) {
|
|
608
|
+
const [rules, setRules] = createSignal([]);
|
|
609
|
+
const [status, setStatus] = createSignal("loading");
|
|
610
|
+
const [skippedCount, setSkippedCount] = createSignal(0);
|
|
611
|
+
const [hasEvaluationState, setHasEvaluationState] = createSignal(false);
|
|
612
|
+
const [expandedIndex, setExpandedIndex] = createSignal(null);
|
|
613
|
+
const [lastDir, setLastDir] = createSignal(undefined);
|
|
614
|
+
const [lastSessionId, setLastSessionId] = createSignal(undefined);
|
|
615
|
+
const [projectOpen, setProjectOpen] = createSignal(false);
|
|
616
|
+
const [globalOpen, setGlobalOpen] = createSignal(false);
|
|
617
|
+
const [refreshCounter, setRefreshCounter] = createSignal(0);
|
|
618
|
+
const theme = () => props.theme.current;
|
|
619
|
+
const resolveProjectDir = () => {
|
|
620
|
+
return props.api.state.path.directory ?? null;
|
|
621
|
+
};
|
|
622
|
+
let requestId = 0;
|
|
623
|
+
let debounceTimer = null;
|
|
624
|
+
const loadRulesInitial = async () => {
|
|
625
|
+
const thisRequest = ++requestId;
|
|
626
|
+
const dir = resolveProjectDir();
|
|
627
|
+
const sessionId = props.sessionId;
|
|
628
|
+
setLastDir(dir);
|
|
629
|
+
setLastSessionId(sessionId);
|
|
630
|
+
setStatus("loading");
|
|
631
|
+
try {
|
|
632
|
+
const result = await loadSidebarRules(dir, sessionId);
|
|
633
|
+
if (requestId !== thisRequest)
|
|
634
|
+
return;
|
|
635
|
+
setRules(result.rules);
|
|
636
|
+
setSkippedCount(result.skippedCount);
|
|
637
|
+
setHasEvaluationState(result.hasEvaluationState);
|
|
638
|
+
setStatus("loaded");
|
|
639
|
+
} catch (err) {
|
|
640
|
+
if (requestId !== thisRequest)
|
|
641
|
+
return;
|
|
642
|
+
console.error("[opencode-rules-md] Failed to load rules:", err);
|
|
643
|
+
setStatus("error");
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
const loadRulesRefresh = async () => {
|
|
647
|
+
if (status() === "loading")
|
|
648
|
+
return;
|
|
649
|
+
const thisRequest = ++requestId;
|
|
650
|
+
const dir = resolveProjectDir();
|
|
651
|
+
const sessionId = props.sessionId;
|
|
652
|
+
try {
|
|
653
|
+
const result = await loadSidebarRules(dir, sessionId);
|
|
654
|
+
if (requestId !== thisRequest)
|
|
655
|
+
return;
|
|
656
|
+
setRules(result.rules);
|
|
657
|
+
setSkippedCount(result.skippedCount);
|
|
658
|
+
setHasEvaluationState(result.hasEvaluationState);
|
|
659
|
+
setStatus("loaded");
|
|
660
|
+
} catch (err) {
|
|
661
|
+
if (requestId !== thisRequest)
|
|
662
|
+
return;
|
|
663
|
+
console.error("[opencode-rules-md] Failed to refresh rules:", err);
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
createEffect(() => {
|
|
667
|
+
const currentSessionId = props.sessionId;
|
|
668
|
+
const currentDir = resolveProjectDir();
|
|
669
|
+
if (currentSessionId !== lastSessionId() || currentDir !== lastDir()) {
|
|
670
|
+
if (debounceTimer !== null) {
|
|
671
|
+
clearTimeout(debounceTimer);
|
|
672
|
+
debounceTimer = null;
|
|
673
|
+
}
|
|
674
|
+
setExpandedIndex(null);
|
|
675
|
+
setProjectOpen(false);
|
|
676
|
+
setGlobalOpen(false);
|
|
677
|
+
loadRulesInitial();
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
createEffect(() => {
|
|
681
|
+
const counter = refreshCounter();
|
|
682
|
+
if (counter > 0) {
|
|
683
|
+
loadRulesRefresh();
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
const triggerRefresh = (event) => {
|
|
687
|
+
const eventSessionID = event.properties.sessionID;
|
|
688
|
+
if (typeof eventSessionID === "string" && eventSessionID !== props.sessionId) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (debounceTimer !== null) {
|
|
692
|
+
clearTimeout(debounceTimer);
|
|
693
|
+
}
|
|
694
|
+
debounceTimer = setTimeout(() => {
|
|
695
|
+
debounceTimer = null;
|
|
696
|
+
setRefreshCounter((c) => c + 1);
|
|
697
|
+
}, 150);
|
|
698
|
+
};
|
|
699
|
+
const unsubMessageUpdated = props.api.event.on("message.updated", triggerRefresh);
|
|
700
|
+
const unsubSessionStatus = props.api.event.on("session.status", triggerRefresh);
|
|
701
|
+
onCleanup(() => {
|
|
702
|
+
if (debounceTimer !== null) {
|
|
703
|
+
clearTimeout(debounceTimer);
|
|
704
|
+
}
|
|
705
|
+
unsubMessageUpdated();
|
|
706
|
+
unsubSessionStatus();
|
|
707
|
+
});
|
|
708
|
+
const toggleExpand = (index) => {
|
|
709
|
+
setExpandedIndex((prev) => prev === index ? null : index);
|
|
710
|
+
};
|
|
711
|
+
const projectRules = createMemo(() => rules().filter((r) => r.source === "project"));
|
|
712
|
+
const globalRules = createMemo(() => rules().filter((r) => r.source === "global"));
|
|
713
|
+
return (() => {
|
|
714
|
+
var _el$34 = _$createElement("box"), _el$35 = _$createElement("text"), _el$36 = _$createElement("b");
|
|
715
|
+
_$insertNode(_el$34, _el$35);
|
|
716
|
+
_$insertNode(_el$35, _el$36);
|
|
717
|
+
_$insertNode(_el$36, _$createTextNode(`Rules`));
|
|
718
|
+
_$insert(_el$34, _$createComponent(Show, {
|
|
719
|
+
get when() {
|
|
720
|
+
return status() === "loading";
|
|
721
|
+
},
|
|
722
|
+
get children() {
|
|
723
|
+
var _el$38 = _$createElement("text");
|
|
724
|
+
_$insertNode(_el$38, _$createTextNode(`Loading...`));
|
|
725
|
+
_$effect((_$p) => _$setProp(_el$38, "fg", theme().textMuted, _$p));
|
|
726
|
+
return _el$38;
|
|
727
|
+
}
|
|
728
|
+
}), null);
|
|
729
|
+
_$insert(_el$34, _$createComponent(Show, {
|
|
730
|
+
get when() {
|
|
731
|
+
return status() === "error";
|
|
732
|
+
},
|
|
733
|
+
get children() {
|
|
734
|
+
var _el$40 = _$createElement("text");
|
|
735
|
+
_$insertNode(_el$40, _$createTextNode(`Failed to load rules`));
|
|
736
|
+
_$effect((_$p) => _$setProp(_el$40, "fg", theme().textMuted, _$p));
|
|
737
|
+
return _el$40;
|
|
738
|
+
}
|
|
739
|
+
}), null);
|
|
740
|
+
_$insert(_el$34, _$createComponent(Show, {
|
|
741
|
+
get when() {
|
|
742
|
+
return status() === "loaded";
|
|
743
|
+
},
|
|
744
|
+
get children() {
|
|
745
|
+
return [_$createComponent(Show, {
|
|
746
|
+
get when() {
|
|
747
|
+
return rules().length > 0;
|
|
748
|
+
},
|
|
749
|
+
get fallback() {
|
|
750
|
+
return (() => {
|
|
751
|
+
var _el$44 = _$createElement("text");
|
|
752
|
+
_$insertNode(_el$44, _$createTextNode(`No rules found`));
|
|
753
|
+
_$effect((_$p) => _$setProp(_el$44, "fg", theme().textMuted, _$p));
|
|
754
|
+
return _el$44;
|
|
755
|
+
})();
|
|
756
|
+
},
|
|
757
|
+
get children() {
|
|
758
|
+
return [_$createComponent(RuleSection, {
|
|
759
|
+
title: "Project",
|
|
760
|
+
get rules() {
|
|
761
|
+
return projectRules();
|
|
762
|
+
},
|
|
763
|
+
get theme() {
|
|
764
|
+
return theme();
|
|
765
|
+
},
|
|
766
|
+
get open() {
|
|
767
|
+
return projectOpen();
|
|
768
|
+
},
|
|
769
|
+
onToggle: () => setProjectOpen((x) => !x),
|
|
770
|
+
get expandedIndex() {
|
|
771
|
+
return expandedIndex();
|
|
772
|
+
},
|
|
773
|
+
globalOffset: 0,
|
|
774
|
+
onExpandToggle: toggleExpand,
|
|
775
|
+
get hasEvaluationState() {
|
|
776
|
+
return hasEvaluationState();
|
|
777
|
+
}
|
|
778
|
+
}), _$createComponent(RuleSection, {
|
|
779
|
+
title: "Global",
|
|
780
|
+
get rules() {
|
|
781
|
+
return globalRules();
|
|
782
|
+
},
|
|
783
|
+
get theme() {
|
|
784
|
+
return theme();
|
|
785
|
+
},
|
|
786
|
+
get open() {
|
|
787
|
+
return globalOpen();
|
|
788
|
+
},
|
|
789
|
+
onToggle: () => setGlobalOpen((x) => !x),
|
|
790
|
+
get expandedIndex() {
|
|
791
|
+
return expandedIndex();
|
|
792
|
+
},
|
|
793
|
+
get globalOffset() {
|
|
794
|
+
return projectRules().length;
|
|
795
|
+
},
|
|
796
|
+
onExpandToggle: toggleExpand,
|
|
797
|
+
get hasEvaluationState() {
|
|
798
|
+
return hasEvaluationState();
|
|
799
|
+
}
|
|
800
|
+
})];
|
|
801
|
+
}
|
|
802
|
+
}), _$createComponent(Show, {
|
|
803
|
+
get when() {
|
|
804
|
+
return skippedCount() > 0;
|
|
805
|
+
},
|
|
806
|
+
get children() {
|
|
807
|
+
var _el$42 = _$createElement("text"), _el$43 = _$createTextNode(` rules skipped (unreadable)`);
|
|
808
|
+
_$insertNode(_el$42, _el$43);
|
|
809
|
+
_$insert(_el$42, skippedCount, _el$43);
|
|
810
|
+
_$effect((_$p) => _$setProp(_el$42, "fg", theme().textMuted, _$p));
|
|
811
|
+
return _el$42;
|
|
812
|
+
}
|
|
813
|
+
})];
|
|
814
|
+
}
|
|
815
|
+
}), null);
|
|
816
|
+
_$effect((_$p) => _$setProp(_el$35, "fg", theme().text, _$p));
|
|
817
|
+
return _el$34;
|
|
818
|
+
})();
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// tui/index.tsx
|
|
822
|
+
var id = "opencode-rules-md";
|
|
823
|
+
var tui = async (api) => {
|
|
824
|
+
api.slots.register({
|
|
825
|
+
order: 350,
|
|
826
|
+
slots: {
|
|
827
|
+
sidebar_content: (ctx, props) => _$createComponent2(SidebarContent, {
|
|
828
|
+
get sessionId() {
|
|
829
|
+
return props.session_id;
|
|
830
|
+
},
|
|
831
|
+
api,
|
|
832
|
+
get theme() {
|
|
833
|
+
return ctx.theme;
|
|
834
|
+
}
|
|
835
|
+
})
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
};
|
|
839
|
+
var tui_default = {
|
|
840
|
+
id,
|
|
841
|
+
tui
|
|
842
|
+
};
|
|
843
|
+
export {
|
|
844
|
+
tui_default as default
|
|
11
845
|
};
|
|
12
|
-
|
|
13
|
-
//#
|
|
846
|
+
|
|
847
|
+
//# debugId=ED0F9EF9A8B9A40664756E2164756E21
|
|
848
|
+
//# sourceMappingURL=index.js.map
|