(cwd: string)
| 5 | const PROJECT_RULE_FILES = ['QODEX.md', 'CLAUDE.md', 'AGENTS.md', '.cursorrules', '.windsurfrules', 'AI.md']; |
| 6 | |
| 7 | export async function loadProjectRules(cwd: string): Promise<{ content: string; sourcePath: string } | null> { |
| 8 | // Walk up from cwd looking for project rule files |
| 9 | let dir = path.resolve(cwd); |
| 10 | const root = path.parse(dir).root; |
| 11 | |
| 12 | while (dir !== root) { |
| 13 | for (const name of PROJECT_RULE_FILES) { |
| 14 | const filePath = path.join(dir, name); |
| 15 | try { |
| 16 | const content = await fs.readFile(filePath, 'utf-8'); |
| 17 | return { content: content.trim(), sourcePath: filePath }; |
| 18 | } catch (err: any) { |
| 19 | // ENOENT just means this rule file isn't present here — keep walking. |
| 20 | // Any other error (e.g. EACCES) means an EXISTING rule file failed to |
| 21 | // read; surface it so the user's project rules aren't silently dropped. |
| 22 | if (err?.code !== 'ENOENT') { |
| 23 | logger.warn(`Failed to read project rule file ${filePath}: ${err?.message ?? err}`); |
| 24 | } |
| 25 | } |
| 26 | } |
| 27 | const parent = path.dirname(dir); |
| 28 | if (parent === dir) break; |
| 29 | dir = parent; |
| 30 | } |
| 31 | |
| 32 | // Also check home directory for global rules |
| 33 | try { |
| 34 | const home = path.join(process.env.HOME ?? '', '.qodex', 'QODEX.md'); |
| 35 | const content = await fs.readFile(home, 'utf-8'); |
| 36 | return { content: content.trim(), sourcePath: home }; |
| 37 | } catch {} |
| 38 | |
| 39 | return null; |
| 40 | } |
no test coverage detected