mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 16:34:00 +01:00
fix(context-bill): a root container skips a nested skill reached through a directory link at any depth
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
c44548413d
commit
b67d9f288f
2 files changed
+38
-5
No files matched your search
+12
-5
@@ -284,7 +284,11 @@ export interface WalkedFile {
|
||||
*
|
||||
* With `skipNestedSkills`, a top-level subdirectory holding its own SKILL.md is
|
||||
* not entered, so a container skill keeps the files it shares with a nested
|
||||
* skill instead of losing them to whichever path sorted first.
|
||||
* skill instead of losing them to whichever path sorted first. The root
|
||||
* container also skips a nested skill reached through a directory link at any
|
||||
* depth: discovery bills that skill on its own, and the plain walk this
|
||||
* replaced never followed directory links, so counting it here too would bill
|
||||
* it twice.
|
||||
*
|
||||
* Every directory is read through its realpath, so a lookup crosses at most
|
||||
* one link however deep the lexical path runs: a chain of links never hits
|
||||
@@ -301,8 +305,9 @@ export function walkMdFiles(dir: string, opts: { billedRoot?: string; skipNested
|
||||
return out;
|
||||
}
|
||||
const seen = new Set<string>([rootReal]);
|
||||
const container = rootReal === billedReal;
|
||||
let entryCount = 0;
|
||||
const walk = (current: string, currentReal: string) => {
|
||||
const walk = (current: string, currentReal: string, viaLink: boolean) => {
|
||||
let entries: fs.Dirent[];
|
||||
try {
|
||||
entries = fs.readdirSync(currentReal, { withFileTypes: true });
|
||||
@@ -320,7 +325,9 @@ export function walkMdFiles(dir: string, opts: { billedRoot?: string; skipNested
|
||||
} else if (
|
||||
kind !== "dir" ||
|
||||
fs.existsSync(path.join(viaReal, ".git")) ||
|
||||
(opts.skipNestedSkills && current === dir && isFile(path.join(viaReal, "SKILL.md")))
|
||||
(opts.skipNestedSkills &&
|
||||
(current === dir || (container && (viaLink || e.isSymbolicLink()))) &&
|
||||
isFile(path.join(viaReal, "SKILL.md")))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -347,10 +354,10 @@ export function walkMdFiles(dir: string, opts: { billedRoot?: string; skipNested
|
||||
}
|
||||
if (encloses(real, rootReal) || encloses(real, billedReal)) continue;
|
||||
seen.add(real);
|
||||
walk(p, real);
|
||||
walk(p, real, viaLink || e.isSymbolicLink());
|
||||
}
|
||||
};
|
||||
walk(dir, rootReal);
|
||||
walk(dir, rootReal, false);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -398,6 +398,32 @@ describe("upstream fix b: installed-tree layout (repo-checkout subdir skip)", ()
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("keeps a root container from billing a nested skill that a deeper directory link reaches", () => {
|
||||
if (process.platform === "win32") return;
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "context-bill-container-deep-"));
|
||||
fs.writeFileSync(path.join(root, "SKILL.md"), "---\nname: c\ndescription: d\n---\n# c\n");
|
||||
fs.mkdirSync(path.join(root, "sub"));
|
||||
fs.writeFileSync(path.join(root, "sub", "SKILL.md"), "---\nname: s\ndescription: d\n---\n# s\n");
|
||||
fs.writeFileSync(path.join(root, "sub", "big.md"), "b".repeat(10_000));
|
||||
fs.mkdirSync(path.join(root, "refs"));
|
||||
fs.writeFileSync(path.join(root, "refs", "note.md"), "n");
|
||||
fs.symlinkSync(path.join(root, "sub"), path.join(root, "refs", "alias"));
|
||||
const outside = fs.mkdtempSync(path.join(os.tmpdir(), "context-bill-container-outside-"));
|
||||
fs.mkdirSync(path.join(outside, "deep"));
|
||||
fs.writeFileSync(path.join(outside, "deep", "SKILL.md"), "---\nname: o\ndescription: d\n---\n# o\n");
|
||||
fs.writeFileSync(path.join(outside, "deep", "o.md"), "o".repeat(5_000));
|
||||
fs.mkdirSync(path.join(root, "linked"));
|
||||
fs.symlinkSync(outside, path.join(root, "linked", "via"));
|
||||
|
||||
const bill = buildBill(root);
|
||||
const byName = Object.fromEntries(bill.skills.map((s) => [s.name, s]));
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
fs.rmSync(outside, { recursive: true, force: true });
|
||||
expect(Object.keys(byName).sort()).toEqual([path.basename(root), path.join("linked", "via", "deep"), path.join("refs", "alias"), "sub"].sort());
|
||||
expect(byName[path.basename(root)].totalMdBytes).toBe("---\nname: c\ndescription: d\n---\n# c\n".length + 1);
|
||||
expect(byName[path.join("refs", "alias")].totalMdBytes).toBe(byName.sub.totalMdBytes);
|
||||
});
|
||||
|
||||
it("walks a chain of links deeper than the kernel's link limit without losing files", () => {
|
||||
if (process.platform === "win32") return;
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "context-bill-deep-chain-"));
|
||||
|
||||
Reference in new issue
Block a user