diff --git a/lib/context-bill.ts b/lib/context-bill.ts index 0cd882868..342b95e39 100644 --- a/lib/context-bill.ts +++ b/lib/context-bill.ts @@ -210,6 +210,14 @@ function parseFrontmatter(text: string): { bytes: number; keys: string[]; block: * Dirent type check sees none of them. Dangling links and anything that is * neither a file nor a directory are null. */ +function isFile(p: string): boolean { + try { + return fs.statSync(p).isFile(); + } catch { + return false; + } +} + function entryKind(p: string, e: fs.Dirent): "file" | "dir" | null { if (e.isFile()) return "file"; if (e.isDirectory()) return "dir"; @@ -289,7 +297,7 @@ export function walkMd(dir: string, opts: { billedRoot?: string; skipNestedSkill } else if ( kind !== "dir" || fs.existsSync(path.join(p, ".git")) || - (opts.skipNestedSkills && current === dir && fs.existsSync(path.join(p, "SKILL.md"))) + (opts.skipNestedSkills && current === dir && isFile(path.join(p, "SKILL.md"))) ) { continue; } diff --git a/test/context-bill.test.ts b/test/context-bill.test.ts index 024e4cd06..304d0efae 100644 --- a/test/context-bill.test.ts +++ b/test/context-bill.test.ts @@ -391,6 +391,17 @@ describe("upstream fix b: installed-tree layout (repo-checkout subdir skip)", () fs.rmSync(root, { recursive: true, force: true }); }); + it("bills a subdirectory whose SKILL.md is a directory to its container, as discovery does not count it as a skill", () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "context-bill-skilldir-")); + fs.writeFileSync(path.join(root, "SKILL.md"), "---\nname: c\ndescription: d\n---\n# c\n"); + fs.mkdirSync(path.join(root, "sub", "SKILL.md"), { recursive: true }); + fs.writeFileSync(path.join(root, "sub", "notes.md"), "n".repeat(500)); + const bill = buildBill(root); + fs.rmSync(root, { recursive: true, force: true }); + expect(bill.skills.map((s) => s.name)).toEqual([path.basename(root)]); + expect(bill.skills[0].totalMdBytes).toBe(bill.skills[0].skillMdBytes + 500); + }); + it("refuses a parent link when the root's own name starts with '..'", () => { if (process.platform === "win32") return; const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "context-bill-dotdot-"));