mirror of
https://github.com/paperclipai/paperclip
synced 2026-03-25 11:21:48 +00:00
Fix imported GitHub skill file paths
Normalize GitHub skill directories for blob/file imports and when reading legacy stored metadata so imported SKILL.md files resolve correctly. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
discoverProjectWorkspaceSkillDirectories,
|
||||
findMissingLocalSkillIds,
|
||||
normalizeGitHubSkillDirectory,
|
||||
parseSkillImportSourceInput,
|
||||
readLocalSkillImportFromDirectory,
|
||||
} from "../services/company-skills.js";
|
||||
@@ -86,6 +87,13 @@ describe("company skill import source parsing", () => {
|
||||
});
|
||||
|
||||
describe("project workspace skill discovery", () => {
|
||||
it("normalizes GitHub skill directories for blob imports and legacy metadata", () => {
|
||||
expect(normalizeGitHubSkillDirectory("retro/.", "retro")).toBe("retro");
|
||||
expect(normalizeGitHubSkillDirectory("retro/SKILL.md", "retro")).toBe("retro");
|
||||
expect(normalizeGitHubSkillDirectory("SKILL.md", "root-skill")).toBe("");
|
||||
expect(normalizeGitHubSkillDirectory("", "fallback-skill")).toBe("fallback-skill");
|
||||
});
|
||||
|
||||
it("finds bounded skill roots under supported workspace paths", async () => {
|
||||
const workspace = await makeTempDir("paperclip-skill-workspace-");
|
||||
await writeSkillDir(workspace, "Workspace Root");
|
||||
|
||||
@@ -190,6 +190,18 @@ function normalizeSkillKey(value: string | null | undefined) {
|
||||
return segments.length > 0 ? segments.join("/") : null;
|
||||
}
|
||||
|
||||
export function normalizeGitHubSkillDirectory(
|
||||
value: string | null | undefined,
|
||||
fallback: string,
|
||||
) {
|
||||
const normalized = normalizePortablePath(value ?? "");
|
||||
if (!normalized) return normalizePortablePath(fallback);
|
||||
if (path.posix.basename(normalized).toLowerCase() === "skill.md") {
|
||||
return normalizePortablePath(path.posix.dirname(normalized));
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function hashSkillValue(value: string) {
|
||||
return createHash("sha256").update(value).digest("hex").slice(0, 10);
|
||||
}
|
||||
@@ -1019,7 +1031,10 @@ async function readUrlSkillImports(
|
||||
repo: parsed.repo,
|
||||
ref: ref,
|
||||
trackingRef,
|
||||
repoSkillDir: basePrefix ? `${basePrefix}${skillDir}` : skillDir,
|
||||
repoSkillDir: normalizeGitHubSkillDirectory(
|
||||
basePrefix ? `${basePrefix}${skillDir}` : skillDir,
|
||||
slug,
|
||||
),
|
||||
};
|
||||
const inventory = filteredPaths
|
||||
.filter((entry) => entry === relativeSkillPath || entry.startsWith(`${skillDir}/`))
|
||||
@@ -1665,7 +1680,7 @@ export function companySkillService(db: Db) {
|
||||
const owner = asString(metadata.owner);
|
||||
const repo = asString(metadata.repo);
|
||||
const ref = skill.sourceRef ?? asString(metadata.ref) ?? "main";
|
||||
const repoSkillDir = normalizePortablePath(asString(metadata.repoSkillDir) ?? skill.slug);
|
||||
const repoSkillDir = normalizeGitHubSkillDirectory(asString(metadata.repoSkillDir), skill.slug);
|
||||
if (!owner || !repo) {
|
||||
throw unprocessable("Skill source metadata is incomplete.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user