fix(context-bill): --exact resolves a billed path one link at a time, so a shared deep chain keeps its measurements

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-26 03:01:29 +00:00
1 parent 398ca092df
commit 2b68acb2d5
2 files changed
+43 -1

No files matched your search

+16 -1
View File
@@ -243,6 +243,21 @@ function entryKind(p: string, e: fs.Dirent): "file" | "dir" | null {
* followed directory symlink that contains the tree being walked (`..`, `/`,
* `$HOME`) would climb out of that tree, so walkers refuse it.
*/
/**
* The realpath of `p`, resolved one component at a time the way the walk
* reads directories, so a lexical path that crosses more links than the
* kernel's limit still resolves.
*/
function realpathStepwise(p: string): string {
const abs = path.resolve(p);
const { root } = path.parse(abs);
let real = fs.realpathSync(root);
for (const segment of abs.slice(root.length).split(path.sep)) {
if (segment) real = fs.realpathSync(path.join(real, segment));
}
return real;
}
function encloses(outer: string, inner: string): boolean {
const rel = path.relative(outer, inner);
return rel === "" || (rel !== ".." && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel));
@@ -1019,7 +1034,7 @@ export async function measureExactTokens(
const isFm = key.endsWith(fmSuffix);
let real: string;
try {
real = fs.realpathSync(isFm ? key.slice(0, -fmSuffix.length) : key);
real = realpathStepwise(isFm ? key.slice(0, -fmSuffix.length) : key);
} catch {
return undefined;
}
+27
View File
@@ -1406,6 +1406,33 @@ describe("--exact (opt-in measurement; offline here via an injected fetch)", ()
expect(rows).toBe(depth + 1);
});
it("prices a second skill's links into a chain deeper than the kernel's limit from the one measurement", async () => {
if (process.platform === "win32") return;
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "context-bill-exact-shared-deep-"));
const levels = path.join(tmp, "levels");
const depth = 50;
for (let i = 0; i < depth; i++) {
fs.mkdirSync(path.join(levels, `L${i}`), { recursive: true });
fs.writeFileSync(path.join(levels, `L${i}`, `f${i}.md`), `F${i}-${"x".repeat(30)}`);
}
for (let i = 0; i < depth - 1; i++) fs.symlinkSync(path.join(levels, `L${i + 1}`), path.join(levels, `L${i}`, "next"));
markCheckout(levels);
const root = path.join(tmp, "skills");
for (const name of ["qa", "qb"]) {
fs.mkdirSync(path.join(root, name), { recursive: true });
fs.writeFileSync(path.join(root, name, "SKILL.md"), `---\nname: ${name}\ndescription: d\n---\n# ${name}\n`);
fs.symlinkSync(path.join(levels, "L0"), path.join(root, name, "next"));
}
fs.symlinkSync(levels, path.join(root, "gstack"));
const measured = await measureExactTokens(root, { model: "m", apiKey: "sk-test", fetchImpl: fakeFetch(), egressHome });
const bill = buildBill(root, { tokensOf: measured.tokensOf, tokenSource: measured.tokenSource });
fs.rmSync(tmp, { recursive: true, force: true });
expect([...measured.missedKeys]).toEqual([]);
const [qa, qb] = ["qa", "qb"].map((n) => bill.skills.find((s) => s.name === n)!);
expect(qb.totalMdBytes).toBe(qa.totalMdBytes);
expect(qb.totalMdTokens).toBe(qa.totalMdTokens);
});
it("names a missing tree, without a raw ENOENT or any request", async () => {
const parent = fs.mkdtempSync(path.join(os.tmpdir(), "context-bill-missing-"));
const out = capture();