feat: auto-resolve @path references in skill templates to absolute paths

Skill loaders previously only told agents that @path references are
relative to the skill directory, but agents often failed to resolve
them. Now @path/with/slash patterns are automatically expanded to
absolute paths during template construction.
This commit is contained in:
YeonGyu-Kim
2026-02-09 11:37:57 +09:00
parent 8dd07973a9
commit 70ac962fca
6 changed files with 133 additions and 4 deletions
@@ -5,6 +5,7 @@ import yaml from "js-yaml"
import { parseFrontmatter } from "../../shared/frontmatter"
import { sanitizeModelField } from "../../shared/model-sanitizer"
import { resolveSymlink, isMarkdownFile } from "../../shared/file-utils"
import { resolveSkillPathReferences } from "../../shared/skill-path-resolver"
import type { CommandDefinition } from "../claude-code-command-loader/types"
import type { SkillScope, SkillMetadata, LoadedSkill } from "./types"
import type { SkillMcpConfig } from "../skill-mcp-manager/types"
@@ -90,11 +91,12 @@ export async function loadSkillFromPathAsync(
const isOpencodeSource = scope === "opencode" || scope === "opencode-project"
const formattedDescription = `(${scope} - Skill) ${originalDescription}`
const resolvedBody = resolveSkillPathReferences(body.trim(), resolvedPath)
const wrappedTemplate = `<skill-instruction>
Base directory for this skill: ${resolvedPath}/
File references (@path) in this skill are relative to this directory.
${body.trim()}
${resolvedBody}
</skill-instruction>
<user-request>
+3 -1
View File
@@ -4,6 +4,7 @@ import yaml from "js-yaml"
import { parseFrontmatter } from "../../shared/frontmatter"
import { sanitizeModelField } from "../../shared/model-sanitizer"
import { resolveSymlinkAsync, isMarkdownFile } from "../../shared/file-utils"
import { resolveSkillPathReferences } from "../../shared/skill-path-resolver"
import { getClaudeConfigDir } from "../../shared"
import { getOpenCodeConfigDir } from "../../shared/opencode-config-dir"
import type { CommandDefinition } from "../claude-code-command-loader/types"
@@ -84,11 +85,12 @@ async function loadSkillFromPath(
const isOpencodeSource = scope === "opencode" || scope === "opencode-project"
const formattedDescription = `(${scope} - Skill) ${originalDescription}`
const resolvedBody = resolveSkillPathReferences(body.trim(), resolvedPath)
const templateContent = `<skill-instruction>
Base directory for this skill: ${resolvedPath}/
File references (@path) in this skill are relative to this directory.
${body.trim()}
${resolvedBody}
</skill-instruction>
<user-request>
+3 -1
View File
@@ -8,6 +8,7 @@ import { homedir } from "os"
import { parseFrontmatter } from "../../shared/frontmatter"
import { sanitizeModelField } from "../../shared/model-sanitizer"
import { deepMerge } from "../../shared/deep-merge"
import { resolveSkillPathReferences } from "../../shared/skill-path-resolver"
function parseAllowedToolsFromMetadata(allowedTools: string | string[] | undefined): string[] | undefined {
if (!allowedTools) return undefined
@@ -105,11 +106,12 @@ function configEntryToLoaded(
const description = entry.description || fileMetadata.description || ""
const resolvedPath = entry.from ? dirname(resolveFilePath(entry.from, configDir)) : configDir || process.cwd()
const resolvedTemplate = resolveSkillPathReferences(template.trim(), resolvedPath)
const wrappedTemplate = `<skill-instruction>
Base directory for this skill: ${resolvedPath}/
File references (@path) in this skill are relative to this directory.
${template.trim()}
${resolvedTemplate}
</skill-instruction>
<user-request>