opencode-rules-md 0.6.5
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/LICENSE +21 -0
- package/README.md +650 -0
- package/dist/cli.mjs +374 -0
- package/dist/src/active-rules-state.d.ts +12 -0
- package/dist/src/active-rules-state.d.ts.map +1 -0
- package/dist/src/active-rules-state.js +118 -0
- package/dist/src/active-rules-state.js.map +1 -0
- package/dist/src/cli/config.d.ts +62 -0
- package/dist/src/cli/config.d.ts.map +1 -0
- package/dist/src/cli/config.js +246 -0
- package/dist/src/cli/config.js.map +1 -0
- package/dist/src/cli/install.d.ts +21 -0
- package/dist/src/cli/install.d.ts.map +1 -0
- package/dist/src/cli/install.js +63 -0
- package/dist/src/cli/install.js.map +1 -0
- package/dist/src/cli/main.d.ts +19 -0
- package/dist/src/cli/main.d.ts.map +1 -0
- package/dist/src/cli/main.js +135 -0
- package/dist/src/cli/main.js.map +1 -0
- package/dist/src/cli/real-fs.d.ts +26 -0
- package/dist/src/cli/real-fs.d.ts.map +1 -0
- package/dist/src/cli/real-fs.js +42 -0
- package/dist/src/cli/real-fs.js.map +1 -0
- package/dist/src/cli/status.d.ts +19 -0
- package/dist/src/cli/status.d.ts.map +1 -0
- package/dist/src/cli/status.js +58 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/debug.d.ts +3 -0
- package/dist/src/debug.d.ts.map +1 -0
- package/dist/src/debug.js +8 -0
- package/dist/src/debug.js.map +1 -0
- package/dist/src/git-branch.d.ts +2 -0
- package/dist/src/git-branch.d.ts.map +1 -0
- package/dist/src/git-branch.js +30 -0
- package/dist/src/git-branch.js.map +1 -0
- package/dist/src/index.d.ts +26 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +55 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp-tools.d.ts +8 -0
- package/dist/src/mcp-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools.js +18 -0
- package/dist/src/mcp-tools.js.map +1 -0
- package/dist/src/message-context.d.ts +55 -0
- package/dist/src/message-context.d.ts.map +1 -0
- package/dist/src/message-context.js +110 -0
- package/dist/src/message-context.js.map +1 -0
- package/dist/src/message-paths.d.ts +34 -0
- package/dist/src/message-paths.d.ts.map +1 -0
- package/dist/src/message-paths.js +112 -0
- package/dist/src/message-paths.js.map +1 -0
- package/dist/src/project-fingerprint.d.ts +2 -0
- package/dist/src/project-fingerprint.d.ts.map +1 -0
- package/dist/src/project-fingerprint.js +61 -0
- package/dist/src/project-fingerprint.js.map +1 -0
- package/dist/src/rule-discovery.d.ts +49 -0
- package/dist/src/rule-discovery.d.ts.map +1 -0
- package/dist/src/rule-discovery.js +143 -0
- package/dist/src/rule-discovery.js.map +1 -0
- package/dist/src/rule-filter.d.ts +61 -0
- package/dist/src/rule-filter.d.ts.map +1 -0
- package/dist/src/rule-filter.js +180 -0
- package/dist/src/rule-filter.js.map +1 -0
- package/dist/src/rule-metadata.d.ts +29 -0
- package/dist/src/rule-metadata.d.ts.map +1 -0
- package/dist/src/rule-metadata.js +100 -0
- package/dist/src/rule-metadata.js.map +1 -0
- package/dist/src/runtime-chat.d.ts +34 -0
- package/dist/src/runtime-chat.d.ts.map +1 -0
- package/dist/src/runtime-chat.js +39 -0
- package/dist/src/runtime-chat.js.map +1 -0
- package/dist/src/runtime-context.d.ts +25 -0
- package/dist/src/runtime-context.d.ts.map +1 -0
- package/dist/src/runtime-context.js +109 -0
- package/dist/src/runtime-context.js.map +1 -0
- package/dist/src/runtime.d.ts +31 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/src/runtime.js +238 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/session-store.d.ts +34 -0
- package/dist/src/session-store.d.ts.map +1 -0
- package/dist/src/session-store.js +90 -0
- package/dist/src/session-store.js.map +1 -0
- package/dist/src/utils.d.ts +15 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +19 -0
- package/dist/src/utils.js.map +1 -0
- package/dist/tui/data/rules.d.ts +63 -0
- package/dist/tui/data/rules.d.ts.map +1 -0
- package/dist/tui/data/rules.js +173 -0
- package/dist/tui/data/rules.js.map +1 -0
- package/dist/tui/index.d.ts +8 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +13 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/slots/sidebar-content.d.ts +11 -0
- package/dist/tui/slots/sidebar-content.d.ts.map +1 -0
- package/dist/tui/slots/sidebar-content.js +150 -0
- package/dist/tui/slots/sidebar-content.js.map +1 -0
- package/package.json +101 -0
- package/src/active-rules-state.ts +166 -0
- package/src/cli/config.ts +312 -0
- package/src/cli/install.ts +94 -0
- package/src/cli/main.ts +167 -0
- package/src/cli/real-fs.ts +65 -0
- package/src/cli/status.ts +80 -0
- package/src/debug.ts +9 -0
- package/src/git-branch.ts +37 -0
- package/src/index.ts +68 -0
- package/src/mcp-tools.ts +24 -0
- package/src/message-context.ts +139 -0
- package/src/message-paths.ts +153 -0
- package/src/project-fingerprint.ts +72 -0
- package/src/rule-discovery.ts +205 -0
- package/src/rule-filter.ts +269 -0
- package/src/rule-metadata.ts +161 -0
- package/src/runtime-chat.ts +68 -0
- package/src/runtime-context.ts +136 -0
- package/src/runtime.ts +369 -0
- package/src/session-store.ts +129 -0
- package/src/utils.ts +36 -0
- package/tui/.atl/.skill-registry.cache.json +3 -0
- package/tui/.atl/skill-registry.md +57 -0
- package/tui/data/rules.ts +230 -0
- package/tui/index.tsx +23 -0
- package/tui/slots/sidebar-content.tsx +364 -0
- package/tui/types/opencode-plugin-tui.d.ts +78 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
const SIMPLE_MARKERS = [
|
|
4
|
+
['package.json', 'node'],
|
|
5
|
+
['pyproject.toml', 'python'],
|
|
6
|
+
['go.mod', 'go'],
|
|
7
|
+
['Cargo.toml', 'rust'],
|
|
8
|
+
['pnpm-workspace.yaml', 'monorepo'],
|
|
9
|
+
['turbo.json', 'monorepo'],
|
|
10
|
+
];
|
|
11
|
+
const BROWSER_EXTENSION_SIGNAL_KEYS = [
|
|
12
|
+
'background',
|
|
13
|
+
'content_scripts',
|
|
14
|
+
'browser_action',
|
|
15
|
+
'page_action',
|
|
16
|
+
'action',
|
|
17
|
+
'permissions',
|
|
18
|
+
];
|
|
19
|
+
async function fileExists(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(filePath);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function isBrowserExtensionManifest(manifestPath) {
|
|
29
|
+
try {
|
|
30
|
+
const content = await fs.readFile(manifestPath, 'utf-8');
|
|
31
|
+
const parsed = JSON.parse(content);
|
|
32
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
33
|
+
return false;
|
|
34
|
+
const version = parsed.manifest_version;
|
|
35
|
+
if (version !== 2 && version !== 3)
|
|
36
|
+
return false;
|
|
37
|
+
return BROWSER_EXTENSION_SIGNAL_KEYS.some(key => key in parsed);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function detectProjectTags(projectDir) {
|
|
44
|
+
const tags = new Set();
|
|
45
|
+
const checks = SIMPLE_MARKERS.map(async ([marker, tag]) => {
|
|
46
|
+
const markerPath = path.join(projectDir, marker);
|
|
47
|
+
if (await fileExists(markerPath)) {
|
|
48
|
+
tags.add(tag);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
const manifestPath = path.join(projectDir, 'manifest.json');
|
|
52
|
+
checks.push((async () => {
|
|
53
|
+
if ((await fileExists(manifestPath)) &&
|
|
54
|
+
(await isBrowserExtensionManifest(manifestPath))) {
|
|
55
|
+
tags.add('browser-extension');
|
|
56
|
+
}
|
|
57
|
+
})());
|
|
58
|
+
await Promise.all(checks);
|
|
59
|
+
return [...tags].sort((a, b) => a.localeCompare(b));
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=project-fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-fingerprint.js","sourceRoot":"","sources":["../../src/project-fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,MAAM,cAAc,GAA4B;IAC9C,CAAC,cAAc,EAAE,MAAM,CAAC;IACxB,CAAC,gBAAgB,EAAE,QAAQ,CAAC;IAC5B,CAAC,QAAQ,EAAE,IAAI,CAAC;IAChB,CAAC,YAAY,EAAE,MAAM,CAAC;IACtB,CAAC,qBAAqB,EAAE,UAAU,CAAC;IACnC,CAAC,YAAY,EAAE,UAAU,CAAC;CAC3B,CAAC;AAEF,MAAM,6BAA6B,GAAG;IACpC,YAAY;IACZ,iBAAiB;IACjB,gBAAgB;IAChB,aAAa;IACb,QAAQ;IACR,aAAa;CACd,CAAC;AAEF,KAAK,UAAU,UAAU,CAAC,QAAgB;IACxC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,0BAA0B,CACvC,YAAoB;IAEpB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YACxE,OAAO,KAAK,CAAC;QACf,MAAM,OAAO,GAAG,MAAM,CAAC,gBAAgB,CAAC;QACxC,IAAI,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACjD,OAAO,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,MAAM,CAAC,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,UAAkB;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAAE;QACxD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACjD,IAAI,MAAM,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IAC5D,MAAM,CAAC,IAAI,CACT,CAAC,KAAK,IAAI,EAAE;QACV,IACE,CAAC,MAAM,UAAU,CAAC,YAAY,CAAC,CAAC;YAChC,CAAC,MAAM,0BAA0B,CAAC,YAAY,CAAC,CAAC,EAChD,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QAChC,CAAC;IACH,CAAC,CAAC,EAAE,CACL,CAAC;IAEF,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAE1B,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule file discovery utilities
|
|
3
|
+
*/
|
|
4
|
+
import { type RuleMetadata } from './rule-metadata';
|
|
5
|
+
/**
|
|
6
|
+
* Cached rule data for performance optimization
|
|
7
|
+
*/
|
|
8
|
+
interface CachedRule {
|
|
9
|
+
/** Raw file content */
|
|
10
|
+
content: string;
|
|
11
|
+
/** Parsed metadata from frontmatter */
|
|
12
|
+
metadata: RuleMetadata | undefined;
|
|
13
|
+
/** Content with frontmatter stripped */
|
|
14
|
+
strippedContent: string;
|
|
15
|
+
/** File modification time for cache invalidation */
|
|
16
|
+
mtime: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Clear the rule cache (useful for testing or manual invalidation)
|
|
20
|
+
*/
|
|
21
|
+
export declare function clearRuleCache(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Get cached rule data, refreshing from disk if file has changed.
|
|
24
|
+
* Uses mtime-based invalidation to detect file changes.
|
|
25
|
+
*
|
|
26
|
+
* @param filePath - Absolute path to the rule file
|
|
27
|
+
* @returns Cached rule data or undefined if file cannot be read
|
|
28
|
+
*/
|
|
29
|
+
export declare function getCachedRule(filePath: string): Promise<CachedRule | undefined>;
|
|
30
|
+
/**
|
|
31
|
+
* Discovered rule file with both absolute and relative paths
|
|
32
|
+
*/
|
|
33
|
+
export interface DiscoveredRule {
|
|
34
|
+
/** Absolute path to the rule file */
|
|
35
|
+
filePath: string;
|
|
36
|
+
/** Relative path from the rules directory root (for unique headings) */
|
|
37
|
+
relativePath: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Discover markdown rule files from standard directories
|
|
41
|
+
* Searches recursively in:
|
|
42
|
+
* - $OPENCODE_CONFIG_DIR/rules/ (highest priority)
|
|
43
|
+
* - $XDG_CONFIG_HOME/opencode/rules/ (or ~/.config/opencode/rules as fallback)
|
|
44
|
+
* - .opencode/rules/ (in project directory if provided)
|
|
45
|
+
* Finds all .md and .mdc files including nested subdirectories.
|
|
46
|
+
*/
|
|
47
|
+
export declare function discoverRuleFiles(projectDir?: string): Promise<DiscoveredRule[]>;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=rule-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-discovery.d.ts","sourceRoot":"","sources":["../../src/rule-discovery.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,iBAAiB,CAAC;AAIzB;;GAEG;AACH,UAAU,UAAU;IAClB,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,QAAQ,EAAE,YAAY,GAAG,SAAS,CAAC;IACnC,wCAAwC;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;CACf;AAOD;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAoCjC;AAmED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,cAAc,EAAE,CAAC,CA8B3B"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule file discovery utilities
|
|
3
|
+
*/
|
|
4
|
+
import { stat, readFile, readdir } from 'fs/promises';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import { createDebugLog } from './debug';
|
|
8
|
+
import { parseRuleMetadata, stripFrontmatter, } from './rule-metadata';
|
|
9
|
+
const debugLog = createDebugLog();
|
|
10
|
+
/**
|
|
11
|
+
* Rule cache keyed by absolute file path
|
|
12
|
+
*/
|
|
13
|
+
const ruleCache = new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Clear the rule cache (useful for testing or manual invalidation)
|
|
16
|
+
*/
|
|
17
|
+
export function clearRuleCache() {
|
|
18
|
+
ruleCache.clear();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get cached rule data, refreshing from disk if file has changed.
|
|
22
|
+
* Uses mtime-based invalidation to detect file changes.
|
|
23
|
+
*
|
|
24
|
+
* @param filePath - Absolute path to the rule file
|
|
25
|
+
* @returns Cached rule data or undefined if file cannot be read
|
|
26
|
+
*/
|
|
27
|
+
export async function getCachedRule(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
const stats = await stat(filePath);
|
|
30
|
+
const mtime = stats.mtimeMs;
|
|
31
|
+
// Check if we have a valid cached entry
|
|
32
|
+
const cached = ruleCache.get(filePath);
|
|
33
|
+
if (cached && cached.mtime === mtime) {
|
|
34
|
+
debugLog(`Cache hit: ${filePath}`);
|
|
35
|
+
return cached;
|
|
36
|
+
}
|
|
37
|
+
// Read and cache the file
|
|
38
|
+
debugLog(`Cache miss: ${filePath}`);
|
|
39
|
+
const content = await readFile(filePath, 'utf-8');
|
|
40
|
+
const metadata = parseRuleMetadata(content);
|
|
41
|
+
const strippedContent = stripFrontmatter(content);
|
|
42
|
+
const entry = {
|
|
43
|
+
content,
|
|
44
|
+
metadata,
|
|
45
|
+
strippedContent,
|
|
46
|
+
mtime,
|
|
47
|
+
};
|
|
48
|
+
ruleCache.set(filePath, entry);
|
|
49
|
+
return entry;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
// Remove stale cache entry if file no longer exists
|
|
53
|
+
ruleCache.delete(filePath);
|
|
54
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
+
console.warn(`[opencode-rules-md] Warning: Failed to read rule file ${filePath}: ${message}`);
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the global rules directory path
|
|
61
|
+
*/
|
|
62
|
+
function getGlobalRulesDir() {
|
|
63
|
+
const opencodeConfigDir = process.env.OPENCODE_CONFIG_DIR;
|
|
64
|
+
if (opencodeConfigDir) {
|
|
65
|
+
return path.join(opencodeConfigDir, 'rules');
|
|
66
|
+
}
|
|
67
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
68
|
+
if (xdgConfigHome) {
|
|
69
|
+
return path.join(xdgConfigHome, 'opencode', 'rules');
|
|
70
|
+
}
|
|
71
|
+
const homeDir = process.env.HOME || os.homedir();
|
|
72
|
+
return path.join(homeDir, '.config', 'opencode', 'rules');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Recursively scan a directory for markdown rule files
|
|
76
|
+
* Skips hidden files and directories (starting with .)
|
|
77
|
+
* @param dir - Directory to scan
|
|
78
|
+
* @param baseDir - Base directory for relative path calculation
|
|
79
|
+
* @returns Array of discovered file paths with their relative paths from baseDir
|
|
80
|
+
*/
|
|
81
|
+
async function scanDirectoryRecursively(dir, baseDir) {
|
|
82
|
+
const results = [];
|
|
83
|
+
try {
|
|
84
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
// Skip hidden files and directories
|
|
87
|
+
if (entry.name.startsWith('.')) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const fullPath = path.join(dir, entry.name);
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
// Recurse into subdirectory
|
|
93
|
+
results.push(...(await scanDirectoryRecursively(fullPath, baseDir)));
|
|
94
|
+
}
|
|
95
|
+
else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdc')) {
|
|
96
|
+
// Add markdown file
|
|
97
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
98
|
+
results.push({ filePath: fullPath, relativePath });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
// Treat ENOENT as benign (directory doesn't exist or was deleted)
|
|
104
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
105
|
+
return results;
|
|
106
|
+
}
|
|
107
|
+
// Log non-ENOENT directory read errors
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
console.warn(`[opencode-rules-md] Warning: Failed to read directory ${dir}: ${message}`);
|
|
110
|
+
}
|
|
111
|
+
return results;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Discover markdown rule files from standard directories
|
|
115
|
+
* Searches recursively in:
|
|
116
|
+
* - $OPENCODE_CONFIG_DIR/rules/ (highest priority)
|
|
117
|
+
* - $XDG_CONFIG_HOME/opencode/rules/ (or ~/.config/opencode/rules as fallback)
|
|
118
|
+
* - .opencode/rules/ (in project directory if provided)
|
|
119
|
+
* Finds all .md and .mdc files including nested subdirectories.
|
|
120
|
+
*/
|
|
121
|
+
export async function discoverRuleFiles(projectDir) {
|
|
122
|
+
const files = [];
|
|
123
|
+
// Discover global rules (recursively)
|
|
124
|
+
const globalRulesDir = getGlobalRulesDir();
|
|
125
|
+
if (globalRulesDir) {
|
|
126
|
+
const globalRules = await scanDirectoryRecursively(globalRulesDir, globalRulesDir);
|
|
127
|
+
for (const { filePath, relativePath } of globalRules) {
|
|
128
|
+
debugLog(`Discovered global rule: ${relativePath} (${filePath})`);
|
|
129
|
+
files.push({ filePath, relativePath });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Discover project-local rules (recursively) if project directory is provided
|
|
133
|
+
if (projectDir) {
|
|
134
|
+
const projectRulesDir = path.join(projectDir, '.opencode', 'rules');
|
|
135
|
+
const projectRules = await scanDirectoryRecursively(projectRulesDir, projectRulesDir);
|
|
136
|
+
for (const { filePath, relativePath } of projectRules) {
|
|
137
|
+
debugLog(`Discovered project rule: ${relativePath} (${filePath})`);
|
|
138
|
+
files.push({ filePath, relativePath });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return files;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=rule-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-discovery.js","sourceRoot":"","sources":["../../src/rule-discovery.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EACL,iBAAiB,EACjB,gBAAgB,GAEjB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;AAgBlC;;GAEG;AACH,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;AAEhD;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,SAAS,CAAC,KAAK,EAAE,CAAC;AACpB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB;IAEhB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC;QAE5B,wCAAwC;QACxC,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YACrC,QAAQ,CAAC,cAAc,QAAQ,EAAE,CAAC,CAAC;YACnC,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,0BAA0B;QAC1B,QAAQ,CAAC,eAAe,QAAQ,EAAE,CAAC,CAAC;QACpC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,eAAe,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAElD,MAAM,KAAK,GAAe;YACxB,OAAO;YACP,QAAQ;YACR,eAAe;YACf,KAAK;SACN,CAAC;QAEF,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC/B,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,oDAAoD;QACpD,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,CAAC,IAAI,CACV,yDAAyD,QAAQ,KAAK,OAAO,EAAE,CAChF,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB;IACxB,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IAC1D,IAAI,iBAAiB,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAClD,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;IACjD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,wBAAwB,CACrC,GAAW,EACX,OAAe;IAEf,MAAM,OAAO,GAAsD,EAAE,CAAC;IAEtE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,oCAAoC;YACpC,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/B,SAAS;YACX,CAAC;YAED,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAE5C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,4BAA4B;gBAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,wBAAwB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;YACvE,CAAC;iBAAM,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBACrE,oBAAoB;gBACpB,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;gBACtD,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,kEAAkE;QAClE,IAAI,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACzE,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,uCAAuC;QACvC,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,CAAC,IAAI,CACV,yDAAyD,GAAG,KAAK,OAAO,EAAE,CAC3E,CAAC;IACJ,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAYD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAmB;IAEnB,MAAM,KAAK,GAAqB,EAAE,CAAC;IAEnC,sCAAsC;IACtC,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAC;IAC3C,IAAI,cAAc,EAAE,CAAC;QACnB,MAAM,WAAW,GAAG,MAAM,wBAAwB,CAChD,cAAc,EACd,cAAc,CACf,CAAC;QACF,KAAK,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,WAAW,EAAE,CAAC;YACrD,QAAQ,CAAC,2BAA2B,YAAY,KAAK,QAAQ,GAAG,CAAC,CAAC;YAClE,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;QACpE,MAAM,YAAY,GAAG,MAAM,wBAAwB,CACjD,eAAe,EACf,eAAe,CAChB,CAAC;QACF,KAAK,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,YAAY,EAAE,CAAC;YACtD,QAAQ,CAAC,4BAA4B,YAAY,KAAK,QAAQ,GAAG,CAAC,CAAC;YACnE,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule filtering and matching utilities
|
|
3
|
+
*/
|
|
4
|
+
import { type DiscoveredRule } from './rule-discovery.js';
|
|
5
|
+
/**
|
|
6
|
+
* Check if a user prompt matches any of the given keywords.
|
|
7
|
+
* Uses case-insensitive word-boundary matching.
|
|
8
|
+
*
|
|
9
|
+
* @param prompt - The user's prompt text
|
|
10
|
+
* @param keywords - Array of keywords to match
|
|
11
|
+
* @returns true if any keyword matches the prompt
|
|
12
|
+
*/
|
|
13
|
+
export declare function promptMatchesKeywords(prompt: string, keywords: string[]): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Check if any of the required tools are available.
|
|
16
|
+
* Uses exact string matching (OR logic: any match returns true).
|
|
17
|
+
*
|
|
18
|
+
* @param availableToolIDs - Array of tool IDs currently available
|
|
19
|
+
* @param requiredTools - Array of tool IDs from rule metadata
|
|
20
|
+
* @returns true if any required tool is available
|
|
21
|
+
*/
|
|
22
|
+
export declare function toolsMatchAvailable(availableToolIDs: string[], requiredTools: string[]): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Result of reading and formatting rules
|
|
25
|
+
*/
|
|
26
|
+
export interface FilterResult {
|
|
27
|
+
formattedRules: string;
|
|
28
|
+
matchedPaths: string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Runtime filter context for conditional rule matching
|
|
32
|
+
*/
|
|
33
|
+
export interface RuleFilterContext {
|
|
34
|
+
/** File paths from conversation context (for glob matching) */
|
|
35
|
+
contextFilePaths?: string[];
|
|
36
|
+
/** User's prompt text (for keyword matching) */
|
|
37
|
+
userPrompt?: string;
|
|
38
|
+
/** Available tool IDs (for tool-based filtering) */
|
|
39
|
+
availableToolIDs?: string[];
|
|
40
|
+
/** Current model ID */
|
|
41
|
+
modelID?: string;
|
|
42
|
+
/** Current agent type */
|
|
43
|
+
agentType?: string;
|
|
44
|
+
/** Current slash command (e.g., /plan, /review) */
|
|
45
|
+
command?: string;
|
|
46
|
+
/** Detected project tags (e.g., node, python, monorepo) */
|
|
47
|
+
projectTags?: string[];
|
|
48
|
+
/** Current git branch name */
|
|
49
|
+
gitBranch?: string;
|
|
50
|
+
/** Current operating system (e.g., linux, darwin, win32) */
|
|
51
|
+
os?: string;
|
|
52
|
+
/** Whether running in CI environment */
|
|
53
|
+
ci?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read and format rule files for system prompt injection
|
|
57
|
+
* @param files - Array of discovered rule files with paths
|
|
58
|
+
* @param context - Optional RuleFilterContext for conditional rule matching
|
|
59
|
+
*/
|
|
60
|
+
export declare function readAndFormatRules(files: DiscoveredRule[], context?: RuleFilterContext): Promise<FilterResult>;
|
|
61
|
+
//# sourceMappingURL=rule-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-filter.d.ts","sourceRoot":"","sources":["../../src/rule-filter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAWzE;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAWT;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,MAAM,EAAE,EAC1B,aAAa,EAAE,MAAM,EAAE,GACtB,OAAO,CAOT;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,uBAAuB;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,EAAE,CAAC,EAAE,OAAO,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,YAAY,CAAC,CAqKvB"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule filtering and matching utilities
|
|
3
|
+
*/
|
|
4
|
+
import { minimatch } from 'minimatch';
|
|
5
|
+
import { createDebugLog } from './debug.js';
|
|
6
|
+
import { getCachedRule } from './rule-discovery.js';
|
|
7
|
+
const debugLog = createDebugLog();
|
|
8
|
+
/**
|
|
9
|
+
* Check if a file path matches any of the given glob patterns
|
|
10
|
+
*/
|
|
11
|
+
function fileMatchesGlobs(filePath, globs) {
|
|
12
|
+
return globs.some(glob => minimatch(filePath, glob, { matchBase: true }));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Check if a user prompt matches any of the given keywords.
|
|
16
|
+
* Uses case-insensitive word-boundary matching.
|
|
17
|
+
*
|
|
18
|
+
* @param prompt - The user's prompt text
|
|
19
|
+
* @param keywords - Array of keywords to match
|
|
20
|
+
* @returns true if any keyword matches the prompt
|
|
21
|
+
*/
|
|
22
|
+
export function promptMatchesKeywords(prompt, keywords) {
|
|
23
|
+
const lowerPrompt = prompt.toLowerCase();
|
|
24
|
+
return keywords.some(keyword => {
|
|
25
|
+
const lowerKeyword = keyword.toLowerCase();
|
|
26
|
+
// Escape special regex characters in the keyword
|
|
27
|
+
const escaped = lowerKeyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
28
|
+
// Word boundary at start, but allow continuation at end (e.g., "test" matches "testing")
|
|
29
|
+
const regex = new RegExp(`\\b${escaped}`, 'i');
|
|
30
|
+
return regex.test(lowerPrompt);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if any of the required tools are available.
|
|
35
|
+
* Uses exact string matching (OR logic: any match returns true).
|
|
36
|
+
*
|
|
37
|
+
* @param availableToolIDs - Array of tool IDs currently available
|
|
38
|
+
* @param requiredTools - Array of tool IDs from rule metadata
|
|
39
|
+
* @returns true if any required tool is available
|
|
40
|
+
*/
|
|
41
|
+
export function toolsMatchAvailable(availableToolIDs, requiredTools) {
|
|
42
|
+
if (requiredTools.length === 0) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// Create a Set for O(1) lookups
|
|
46
|
+
const availableSet = new Set(availableToolIDs);
|
|
47
|
+
return requiredTools.some(tool => availableSet.has(tool));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Read and format rule files for system prompt injection
|
|
51
|
+
* @param files - Array of discovered rule files with paths
|
|
52
|
+
* @param context - Optional RuleFilterContext for conditional rule matching
|
|
53
|
+
*/
|
|
54
|
+
export async function readAndFormatRules(files, context = {}) {
|
|
55
|
+
if (files.length === 0) {
|
|
56
|
+
return { formattedRules: '', matchedPaths: [] };
|
|
57
|
+
}
|
|
58
|
+
const ruleContents = [];
|
|
59
|
+
const matchedPaths = [];
|
|
60
|
+
const availableToolSet = context.availableToolIDs && context.availableToolIDs.length > 0
|
|
61
|
+
? new Set(context.availableToolIDs)
|
|
62
|
+
: undefined;
|
|
63
|
+
for (const { filePath, relativePath } of files) {
|
|
64
|
+
// Use cached rule data with mtime-based invalidation
|
|
65
|
+
const cachedRule = await getCachedRule(filePath);
|
|
66
|
+
if (!cachedRule) {
|
|
67
|
+
continue; // Error already logged by getCachedRule
|
|
68
|
+
}
|
|
69
|
+
const { metadata, strippedContent } = cachedRule;
|
|
70
|
+
// Check if rule has any conditional filters
|
|
71
|
+
const hasConditions = Boolean(metadata?.globs ||
|
|
72
|
+
metadata?.keywords ||
|
|
73
|
+
metadata?.tools ||
|
|
74
|
+
metadata?.model ||
|
|
75
|
+
metadata?.agent ||
|
|
76
|
+
metadata?.command ||
|
|
77
|
+
metadata?.project ||
|
|
78
|
+
metadata?.branch ||
|
|
79
|
+
metadata?.os ||
|
|
80
|
+
metadata?.ci !== undefined);
|
|
81
|
+
if (hasConditions && metadata) {
|
|
82
|
+
// Compute per-dimension match booleans (only for declared conditions)
|
|
83
|
+
const declaredChecks = [];
|
|
84
|
+
// Legacy: globs
|
|
85
|
+
if (metadata.globs) {
|
|
86
|
+
const globs = metadata.globs;
|
|
87
|
+
const globsMatch = context.contextFilePaths &&
|
|
88
|
+
context.contextFilePaths.length > 0 &&
|
|
89
|
+
context.contextFilePaths.some(contextPath => fileMatchesGlobs(contextPath, globs));
|
|
90
|
+
declaredChecks.push(Boolean(globsMatch));
|
|
91
|
+
}
|
|
92
|
+
// Legacy: keywords
|
|
93
|
+
if (metadata.keywords) {
|
|
94
|
+
const keywordsMatch = context.userPrompt &&
|
|
95
|
+
promptMatchesKeywords(context.userPrompt, metadata.keywords);
|
|
96
|
+
declaredChecks.push(Boolean(keywordsMatch));
|
|
97
|
+
}
|
|
98
|
+
// Legacy: tools
|
|
99
|
+
if (metadata.tools) {
|
|
100
|
+
const toolsMatch = availableToolSet &&
|
|
101
|
+
metadata.tools.some(tool => availableToolSet.has(tool));
|
|
102
|
+
declaredChecks.push(Boolean(toolsMatch));
|
|
103
|
+
}
|
|
104
|
+
// New: model
|
|
105
|
+
if (metadata.model) {
|
|
106
|
+
const modelMatch = context.modelID && metadata.model.includes(context.modelID);
|
|
107
|
+
declaredChecks.push(Boolean(modelMatch));
|
|
108
|
+
}
|
|
109
|
+
// New: agent
|
|
110
|
+
if (metadata.agent) {
|
|
111
|
+
const agentMatch = context.agentType && metadata.agent.includes(context.agentType);
|
|
112
|
+
declaredChecks.push(Boolean(agentMatch));
|
|
113
|
+
}
|
|
114
|
+
// New: command
|
|
115
|
+
if (metadata.command) {
|
|
116
|
+
const commandMatch = context.command && metadata.command.includes(context.command);
|
|
117
|
+
declaredChecks.push(Boolean(commandMatch));
|
|
118
|
+
}
|
|
119
|
+
// New: project
|
|
120
|
+
if (metadata.project) {
|
|
121
|
+
const projectTags = context.projectTags;
|
|
122
|
+
const projectMatch = projectTags &&
|
|
123
|
+
projectTags.length > 0 &&
|
|
124
|
+
metadata.project.some(tag => projectTags.includes(tag));
|
|
125
|
+
declaredChecks.push(Boolean(projectMatch));
|
|
126
|
+
}
|
|
127
|
+
// New: branch (supports glob patterns)
|
|
128
|
+
if (metadata.branch) {
|
|
129
|
+
const gitBranch = context.gitBranch;
|
|
130
|
+
const branchMatch = gitBranch &&
|
|
131
|
+
metadata.branch.some(pattern => {
|
|
132
|
+
// Exact match for non-glob patterns
|
|
133
|
+
if (pattern === gitBranch) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
// Only use glob matching if pattern contains glob characters
|
|
137
|
+
const hasGlobChars = /[*?\[{]/.test(pattern);
|
|
138
|
+
if (hasGlobChars) {
|
|
139
|
+
return minimatch(gitBranch, pattern);
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
});
|
|
143
|
+
declaredChecks.push(Boolean(branchMatch));
|
|
144
|
+
}
|
|
145
|
+
// New: os
|
|
146
|
+
if (metadata.os) {
|
|
147
|
+
const osMatch = context.os && metadata.os.includes(context.os);
|
|
148
|
+
declaredChecks.push(Boolean(osMatch));
|
|
149
|
+
}
|
|
150
|
+
// New: ci (strict boolean equality)
|
|
151
|
+
if (metadata.ci !== undefined) {
|
|
152
|
+
const ciMatch = context.ci === metadata.ci;
|
|
153
|
+
declaredChecks.push(ciMatch);
|
|
154
|
+
}
|
|
155
|
+
// Apply combinator: default 'any', or 'all' if specified
|
|
156
|
+
const mode = metadata.match ?? 'any';
|
|
157
|
+
const shouldInclude = mode === 'all'
|
|
158
|
+
? declaredChecks.every(Boolean)
|
|
159
|
+
: declaredChecks.some(Boolean);
|
|
160
|
+
if (!shouldInclude) {
|
|
161
|
+
debugLog(`Skipping conditional rule: ${relativePath} (match: ${mode}, checks: ${declaredChecks.join(', ')})`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
debugLog(`Including conditional rule: ${relativePath} (match: ${mode}, checks: ${declaredChecks.join(', ')})`);
|
|
165
|
+
}
|
|
166
|
+
// Use cached stripped content for output
|
|
167
|
+
// Use relativePath for unique headings instead of just filename
|
|
168
|
+
ruleContents.push(`## ${relativePath}\n\n${strippedContent}`);
|
|
169
|
+
matchedPaths.push(filePath);
|
|
170
|
+
}
|
|
171
|
+
if (ruleContents.length === 0) {
|
|
172
|
+
return { formattedRules: '', matchedPaths: [] };
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
formattedRules: `# OpenCode Rules\n\nPlease follow the following rules:\n\n` +
|
|
176
|
+
ruleContents.join('\n\n---\n\n'),
|
|
177
|
+
matchedPaths,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=rule-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-filter.js","sourceRoot":"","sources":["../../src/rule-filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule metadata parsing and frontmatter extraction
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Metadata extracted from .mdc file frontmatter
|
|
6
|
+
*/
|
|
7
|
+
export interface RuleMetadata {
|
|
8
|
+
globs?: string[];
|
|
9
|
+
keywords?: string[];
|
|
10
|
+
tools?: string[];
|
|
11
|
+
model?: string[];
|
|
12
|
+
agent?: string[];
|
|
13
|
+
command?: string[];
|
|
14
|
+
project?: string[];
|
|
15
|
+
branch?: string[];
|
|
16
|
+
os?: string[];
|
|
17
|
+
ci?: boolean;
|
|
18
|
+
match?: 'any' | 'all';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parse YAML metadata from rule file content using the yaml package.
|
|
22
|
+
* Extracts frontmatter (---) and returns metadata object.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseRuleMetadata(content: string): RuleMetadata | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Strip YAML frontmatter from rule content
|
|
27
|
+
*/
|
|
28
|
+
export declare function stripFrontmatter(content: string): string;
|
|
29
|
+
//# sourceMappingURL=rule-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-metadata.d.ts","sourceRoot":"","sources":["../../src/rule-metadata.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,KAAK,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;CACvB;AAiDD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAmE3E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAcxD"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule metadata parsing and frontmatter extraction
|
|
3
|
+
*/
|
|
4
|
+
const { parse: parseYaml } = await import('yaml');
|
|
5
|
+
/**
|
|
6
|
+
* Extract and normalize a string array from parsed frontmatter.
|
|
7
|
+
* Filters non-strings, trims whitespace, and removes empty values.
|
|
8
|
+
*
|
|
9
|
+
* @param value - Raw value from parsed YAML (may be array or undefined)
|
|
10
|
+
* @returns Normalized string array, or undefined if empty after filtering
|
|
11
|
+
*/
|
|
12
|
+
function extractStringArray(value) {
|
|
13
|
+
if (!Array.isArray(value)) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const result = value
|
|
17
|
+
.filter((v) => typeof v === 'string')
|
|
18
|
+
.map(v => v.trim())
|
|
19
|
+
.filter(v => v.length > 0);
|
|
20
|
+
return result.length > 0 ? result : undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse YAML metadata from rule file content using the yaml package.
|
|
24
|
+
* Extracts frontmatter (---) and returns metadata object.
|
|
25
|
+
*/
|
|
26
|
+
export function parseRuleMetadata(content) {
|
|
27
|
+
// Check if content starts with frontmatter
|
|
28
|
+
if (!content.startsWith('---')) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
// Find the closing --- marker
|
|
32
|
+
const endIndex = content.indexOf('---', 3);
|
|
33
|
+
if (endIndex === -1) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
// Extract the YAML frontmatter
|
|
37
|
+
const frontmatter = content.substring(3, endIndex).trim();
|
|
38
|
+
if (!frontmatter) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
// Parse YAML using the yaml package
|
|
43
|
+
const parsed = parseYaml(frontmatter);
|
|
44
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const metadata = {};
|
|
48
|
+
// Array fields to extract using shared helper
|
|
49
|
+
const arrayFields = [
|
|
50
|
+
'globs',
|
|
51
|
+
'keywords',
|
|
52
|
+
'tools',
|
|
53
|
+
'model',
|
|
54
|
+
'agent',
|
|
55
|
+
'command',
|
|
56
|
+
'project',
|
|
57
|
+
'branch',
|
|
58
|
+
'os',
|
|
59
|
+
];
|
|
60
|
+
for (const field of arrayFields) {
|
|
61
|
+
const extracted = extractStringArray(parsed[field]);
|
|
62
|
+
if (extracted) {
|
|
63
|
+
metadata[field] = extracted;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Extract ci boolean (only if strictly boolean)
|
|
67
|
+
if (typeof parsed.ci === 'boolean') {
|
|
68
|
+
metadata.ci = parsed.ci;
|
|
69
|
+
}
|
|
70
|
+
// Extract match (normalize to 'any' | 'all' only)
|
|
71
|
+
if (parsed.match === 'any' || parsed.match === 'all') {
|
|
72
|
+
metadata.match = parsed.match;
|
|
73
|
+
}
|
|
74
|
+
// Return metadata only if it has content
|
|
75
|
+
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
// Log warning for YAML parsing errors
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
console.warn(`[opencode-rules-md] Warning: Failed to parse YAML frontmatter: ${message}`);
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Strip YAML frontmatter from rule content
|
|
86
|
+
*/
|
|
87
|
+
export function stripFrontmatter(content) {
|
|
88
|
+
// Check if content starts with frontmatter
|
|
89
|
+
if (!content.startsWith('---')) {
|
|
90
|
+
return content;
|
|
91
|
+
}
|
|
92
|
+
// Find the closing --- marker
|
|
93
|
+
const endIndex = content.indexOf('---', 3);
|
|
94
|
+
if (endIndex === -1) {
|
|
95
|
+
return content;
|
|
96
|
+
}
|
|
97
|
+
// Return content after the closing marker, trimming leading newline
|
|
98
|
+
return content.substring(endIndex + 3).trimStart();
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=rule-metadata.js.map
|