fix: resolve symlinks in skill config source discovery and test paths

Use fs.realpath() in config-source-discovery to resolve symlinks before
loading skills, preventing duplicate/mismatched paths on systems where
tmpdir() returns a symlink (e.g., macOS /var → /private/var). Also adds
agents-config-dir utility for ~/.agents path resolution.
This commit is contained in:
YeonGyu-Kim
2026-02-14 16:19:18 +09:00
parent 3de05f6442
commit e9c9cb696d
4 changed files with 31 additions and 9 deletions

View File

@@ -53,26 +53,28 @@ async function loadSourcePath(options: {
const stat = await fs.stat(absolutePath).catch(() => null)
if (!stat) return []
const realBasePath = await fs.realpath(absolutePath).catch(() => absolutePath)
if (stat.isFile()) {
if (!isMarkdownPath(absolutePath)) return []
if (!isMarkdownPath(realBasePath)) return []
const loaded = await loadSkillFromPath({
skillPath: absolutePath,
resolvedPath: dirname(absolutePath),
defaultName: inferSkillNameFromFileName(absolutePath),
skillPath: realBasePath,
resolvedPath: dirname(realBasePath),
defaultName: inferSkillNameFromFileName(realBasePath),
scope: "config",
})
if (!loaded) return []
return filterByGlob([loaded], dirname(absolutePath), options.globPattern)
return filterByGlob([loaded], dirname(realBasePath), options.globPattern)
}
if (!stat.isDirectory()) return []
const directorySkills = await loadSkillsFromDir({
skillsDir: absolutePath,
skillsDir: realBasePath,
scope: "config",
maxDepth: options.recursive ? MAX_RECURSIVE_DEPTH : 0,
})
return filterByGlob(directorySkills, absolutePath, options.globPattern)
return filterByGlob(directorySkills, realBasePath, options.globPattern)
}
export async function discoverConfigSourceSkills(options: {

View File

@@ -0,0 +1,14 @@
import { describe, test, expect } from "bun:test"
import { getAgentsConfigDir } from "./agents-config-dir"
describe("getAgentsConfigDir", () => {
test("returns path ending with .agents", () => {
// given agents config dir is requested
// when getAgentsConfigDir is called
const result = getAgentsConfigDir()
// then returns path ending with .agents
expect(result.endsWith(".agents")).toBe(true)
})
})

View File

@@ -0,0 +1,6 @@
import { homedir } from "node:os"
import { join } from "node:path"
export function getAgentsConfigDir(): string {
return join(homedir(), ".agents")
}

View File

@@ -1,10 +1,10 @@
import { describe, it, expect, beforeAll, afterAll } from "bun:test"
import { mkdirSync, writeFileSync, symlinkSync, rmSync } from "fs"
import { mkdirSync, writeFileSync, symlinkSync, rmSync, realpathSync } from "fs"
import { join } from "path"
import { tmpdir } from "os"
import { resolveSymlink, resolveSymlinkAsync, isSymbolicLink } from "./file-utils"
const testDir = join(tmpdir(), "file-utils-test-" + Date.now())
const testDir = join(realpathSync(tmpdir()), "file-utils-test-" + Date.now())
// Create a directory structure that mimics the real-world scenario:
//