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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +650 -0
  3. package/dist/cli.mjs +374 -0
  4. package/dist/src/active-rules-state.d.ts +12 -0
  5. package/dist/src/active-rules-state.d.ts.map +1 -0
  6. package/dist/src/active-rules-state.js +118 -0
  7. package/dist/src/active-rules-state.js.map +1 -0
  8. package/dist/src/cli/config.d.ts +62 -0
  9. package/dist/src/cli/config.d.ts.map +1 -0
  10. package/dist/src/cli/config.js +246 -0
  11. package/dist/src/cli/config.js.map +1 -0
  12. package/dist/src/cli/install.d.ts +21 -0
  13. package/dist/src/cli/install.d.ts.map +1 -0
  14. package/dist/src/cli/install.js +63 -0
  15. package/dist/src/cli/install.js.map +1 -0
  16. package/dist/src/cli/main.d.ts +19 -0
  17. package/dist/src/cli/main.d.ts.map +1 -0
  18. package/dist/src/cli/main.js +135 -0
  19. package/dist/src/cli/main.js.map +1 -0
  20. package/dist/src/cli/real-fs.d.ts +26 -0
  21. package/dist/src/cli/real-fs.d.ts.map +1 -0
  22. package/dist/src/cli/real-fs.js +42 -0
  23. package/dist/src/cli/real-fs.js.map +1 -0
  24. package/dist/src/cli/status.d.ts +19 -0
  25. package/dist/src/cli/status.d.ts.map +1 -0
  26. package/dist/src/cli/status.js +58 -0
  27. package/dist/src/cli/status.js.map +1 -0
  28. package/dist/src/debug.d.ts +3 -0
  29. package/dist/src/debug.d.ts.map +1 -0
  30. package/dist/src/debug.js +8 -0
  31. package/dist/src/debug.js.map +1 -0
  32. package/dist/src/git-branch.d.ts +2 -0
  33. package/dist/src/git-branch.d.ts.map +1 -0
  34. package/dist/src/git-branch.js +30 -0
  35. package/dist/src/git-branch.js.map +1 -0
  36. package/dist/src/index.d.ts +26 -0
  37. package/dist/src/index.d.ts.map +1 -0
  38. package/dist/src/index.js +55 -0
  39. package/dist/src/index.js.map +1 -0
  40. package/dist/src/mcp-tools.d.ts +8 -0
  41. package/dist/src/mcp-tools.d.ts.map +1 -0
  42. package/dist/src/mcp-tools.js +18 -0
  43. package/dist/src/mcp-tools.js.map +1 -0
  44. package/dist/src/message-context.d.ts +55 -0
  45. package/dist/src/message-context.d.ts.map +1 -0
  46. package/dist/src/message-context.js +110 -0
  47. package/dist/src/message-context.js.map +1 -0
  48. package/dist/src/message-paths.d.ts +34 -0
  49. package/dist/src/message-paths.d.ts.map +1 -0
  50. package/dist/src/message-paths.js +112 -0
  51. package/dist/src/message-paths.js.map +1 -0
  52. package/dist/src/project-fingerprint.d.ts +2 -0
  53. package/dist/src/project-fingerprint.d.ts.map +1 -0
  54. package/dist/src/project-fingerprint.js +61 -0
  55. package/dist/src/project-fingerprint.js.map +1 -0
  56. package/dist/src/rule-discovery.d.ts +49 -0
  57. package/dist/src/rule-discovery.d.ts.map +1 -0
  58. package/dist/src/rule-discovery.js +143 -0
  59. package/dist/src/rule-discovery.js.map +1 -0
  60. package/dist/src/rule-filter.d.ts +61 -0
  61. package/dist/src/rule-filter.d.ts.map +1 -0
  62. package/dist/src/rule-filter.js +180 -0
  63. package/dist/src/rule-filter.js.map +1 -0
  64. package/dist/src/rule-metadata.d.ts +29 -0
  65. package/dist/src/rule-metadata.d.ts.map +1 -0
  66. package/dist/src/rule-metadata.js +100 -0
  67. package/dist/src/rule-metadata.js.map +1 -0
  68. package/dist/src/runtime-chat.d.ts +34 -0
  69. package/dist/src/runtime-chat.d.ts.map +1 -0
  70. package/dist/src/runtime-chat.js +39 -0
  71. package/dist/src/runtime-chat.js.map +1 -0
  72. package/dist/src/runtime-context.d.ts +25 -0
  73. package/dist/src/runtime-context.d.ts.map +1 -0
  74. package/dist/src/runtime-context.js +109 -0
  75. package/dist/src/runtime-context.js.map +1 -0
  76. package/dist/src/runtime.d.ts +31 -0
  77. package/dist/src/runtime.d.ts.map +1 -0
  78. package/dist/src/runtime.js +238 -0
  79. package/dist/src/runtime.js.map +1 -0
  80. package/dist/src/session-store.d.ts +34 -0
  81. package/dist/src/session-store.d.ts.map +1 -0
  82. package/dist/src/session-store.js +90 -0
  83. package/dist/src/session-store.js.map +1 -0
  84. package/dist/src/utils.d.ts +15 -0
  85. package/dist/src/utils.d.ts.map +1 -0
  86. package/dist/src/utils.js +19 -0
  87. package/dist/src/utils.js.map +1 -0
  88. package/dist/tui/data/rules.d.ts +63 -0
  89. package/dist/tui/data/rules.d.ts.map +1 -0
  90. package/dist/tui/data/rules.js +173 -0
  91. package/dist/tui/data/rules.js.map +1 -0
  92. package/dist/tui/index.d.ts +8 -0
  93. package/dist/tui/index.d.ts.map +1 -0
  94. package/dist/tui/index.js +13 -0
  95. package/dist/tui/index.js.map +1 -0
  96. package/dist/tui/slots/sidebar-content.d.ts +11 -0
  97. package/dist/tui/slots/sidebar-content.d.ts.map +1 -0
  98. package/dist/tui/slots/sidebar-content.js +150 -0
  99. package/dist/tui/slots/sidebar-content.js.map +1 -0
  100. package/package.json +101 -0
  101. package/src/active-rules-state.ts +166 -0
  102. package/src/cli/config.ts +312 -0
  103. package/src/cli/install.ts +94 -0
  104. package/src/cli/main.ts +167 -0
  105. package/src/cli/real-fs.ts +65 -0
  106. package/src/cli/status.ts +80 -0
  107. package/src/debug.ts +9 -0
  108. package/src/git-branch.ts +37 -0
  109. package/src/index.ts +68 -0
  110. package/src/mcp-tools.ts +24 -0
  111. package/src/message-context.ts +139 -0
  112. package/src/message-paths.ts +153 -0
  113. package/src/project-fingerprint.ts +72 -0
  114. package/src/rule-discovery.ts +205 -0
  115. package/src/rule-filter.ts +269 -0
  116. package/src/rule-metadata.ts +161 -0
  117. package/src/runtime-chat.ts +68 -0
  118. package/src/runtime-context.ts +136 -0
  119. package/src/runtime.ts +369 -0
  120. package/src/session-store.ts +129 -0
  121. package/src/utils.ts +36 -0
  122. package/tui/.atl/.skill-registry.cache.json +3 -0
  123. package/tui/.atl/skill-registry.md +57 -0
  124. package/tui/data/rules.ts +230 -0
  125. package/tui/index.tsx +23 -0
  126. package/tui/slots/sidebar-content.tsx +364 -0
  127. 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":"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"}
@@ -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