Skip to content

Commit 8bdf36a

Browse files
committed
refactor: use existing symlink resolution pattern for AGENTS.md
- Extracted resolveSymlinkPath function to handle symlink resolution - Removed duplicate safeReadFileFollowingSymlinks function - Updated loadAgentRulesFile to use resolveSymlinkPath + safeReadFile - Updated tests to match new implementation - Maintains same functionality while reusing existing patterns
1 parent abdbb45 commit 8bdf36a

File tree

2 files changed

+36
-20
lines changed

2 files changed

+36
-20
lines changed

src/core/prompts/sections/__tests__/custom-instructions.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,11 @@ describe("addCustomInstructions", () => {
674674
return Promise.resolve({
675675
isSymbolicLink: vi.fn().mockReturnValue(true),
676676
})
677+
} else if (pathStr.endsWith("actual-agents-file.md")) {
678+
// The resolved target is not a symlink
679+
return Promise.resolve({
680+
isSymbolicLink: vi.fn().mockReturnValue(false),
681+
})
677682
}
678683
return Promise.reject({ code: "ENOENT" })
679684
})

src/core/prompts/sections/custom-instructions.ts

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,38 @@ async function safeReadFile(filePath: string): Promise<string> {
2727
}
2828

2929
/**
30-
* Safely read a file and follow symlinks if necessary
30+
* Resolve a symlink to its target path
3131
*/
32-
async function safeReadFileFollowingSymlinks(filePath: string): Promise<string> {
32+
async function resolveSymlinkPath(symlinkPath: string, depth: number = 0): Promise<string> {
33+
// Avoid cyclic symlinks
34+
if (depth > MAX_DEPTH) {
35+
throw new Error(`Maximum symlink depth exceeded for ${symlinkPath}`)
36+
}
37+
3338
try {
3439
// Check if the path is a symlink
35-
const stats = await fs.lstat(filePath)
36-
if (stats.isSymbolicLink()) {
37-
// Resolve the symlink to get the actual file path
38-
const linkTarget = await fs.readlink(filePath)
39-
const resolvedPath = path.resolve(path.dirname(filePath), linkTarget)
40-
// Read from the resolved path
41-
const content = await fs.readFile(resolvedPath, "utf-8")
42-
return content.trim()
43-
} else {
44-
// Not a symlink, read normally
45-
const content = await fs.readFile(filePath, "utf-8")
46-
return content.trim()
40+
const stats = await fs.lstat(symlinkPath)
41+
if (!stats.isSymbolicLink()) {
42+
// Not a symlink, return the original path
43+
return symlinkPath
4744
}
48-
} catch (err) {
49-
const errorCode = (err as NodeJS.ErrnoException).code
50-
if (!errorCode || !["ENOENT", "EISDIR"].includes(errorCode)) {
51-
throw err
45+
46+
// Get the symlink target
47+
const linkTarget = await fs.readlink(symlinkPath)
48+
// Resolve the target path (relative to the symlink location)
49+
const resolvedTarget = path.resolve(path.dirname(symlinkPath), linkTarget)
50+
51+
// Check if the resolved target is also a symlink (handle nested symlinks)
52+
const targetStats = await fs.lstat(resolvedTarget)
53+
if (targetStats.isSymbolicLink()) {
54+
// Recursively resolve nested symlinks
55+
return resolveSymlinkPath(resolvedTarget, depth + 1)
5256
}
53-
return ""
57+
58+
return resolvedTarget
59+
} catch (err) {
60+
// If we can't resolve the symlink, return the original path
61+
return symlinkPath
5462
}
5563
}
5664

@@ -250,7 +258,10 @@ export async function loadRuleFiles(cwd: string): Promise<string> {
250258
async function loadAgentRulesFile(cwd: string): Promise<string> {
251259
try {
252260
const agentsPath = path.join(cwd, "AGENTS.md")
253-
const content = await safeReadFileFollowingSymlinks(agentsPath)
261+
// Resolve symlinks if necessary
262+
const resolvedPath = await resolveSymlinkPath(agentsPath)
263+
// Read the file content
264+
const content = await safeReadFile(resolvedPath)
254265
if (content) {
255266
return `# Agent Rules Standard (AGENTS.md):\n${content}`
256267
}

0 commit comments

Comments
 (0)