From d6bcd7216dbd4d1befa2c81dff9aeef714a4de10 Mon Sep 17 00:00:00 2001 From: Rayan-and-beyond <263488867+Rayan-and-beyond@users.noreply.github.com> Date: Tue, 15 Sep 2026 20:52:19 +0000 Subject: [PATCH] fix: include symlinked files in scans --- CHANGELOG.md | 6 ++++++ package.json | 2 +- src/scan.js | 9 ++++++--- test/ctxtrim.test.js | 12 +++++++++++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9cb818..2e93381 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project are documented here, following [Keep a Changelog](https://keepachangelog.com/) and semantic versioning. +## [0.1.10] - 2026-09-15 + +### Fixed + +- Include symlinked files that resolve to regular files in scan results instead of silently omitting them. + ## [0.1.9] - 2026-09-12 ### Fixed diff --git a/package.json b/package.json index 3f9e1ad..7bea5cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ctxtrim", - "version": "0.1.9", + "version": "0.1.10", "description": "Trim what bloats your AI coding context. Scan a repo, find the high-cost/low-value files ballooning your Claude Code / Cursor / Codex context, and write ignore files to cut token cost. Zero dependencies.", "type": "module", "bin": { diff --git a/src/scan.js b/src/scan.js index e484a27..0579e90 100644 --- a/src/scan.js +++ b/src/scan.js @@ -45,11 +45,14 @@ export function scanRepo(target, opts = {}) { for (const e of entries) { if (ALWAYS_SKIP.has(e.name)) continue; const abs = join(dir, e.name); - if (e.isSymbolicLink()) continue; if (e.isDirectory()) { walk(abs); continue; } - if (!e.isFile()) continue; + if (!e.isFile() && !e.isSymbolicLink()) continue; let size = 0; - try { size = statSync(abs).size; } catch { continue; } + try { + const stat = statSync(abs); + if (!stat.isFile()) continue; + size = stat.size; + } catch { continue; } const rel = relative(root, abs).split(sep).join("/"); const pathClassification = classifyPath(rel); const info = pathClassification?.binary ? { tokens: 0, sample: "" } : fileInfo(abs, size); diff --git a/test/ctxtrim.test.js b/test/ctxtrim.test.js index da50d9f..76e9aa5 100644 --- a/test/ctxtrim.test.js +++ b/test/ctxtrim.test.js @@ -1,4 +1,4 @@ -import fs, { existsSync, mkdtempSync, rmSync, truncateSync, writeFileSync } from "node:fs"; +import fs, { existsSync, mkdtempSync, rmSync, symlinkSync, truncateSync, writeFileSync } from "node:fs"; import { spawnSync } from "node:child_process"; import { syncBuiltinESMExports } from "node:module"; import { tmpdir } from "node:os"; @@ -101,6 +101,16 @@ test("scan skips binary reads and retains content-dependent classification", (t) assert.equal(result.files.find((file) => file.rel === "large.json").category, "data"); }); +test("scan includes symlinked source files", (t) => { + const root = mkdtempSync(join(tmpdir(), "ctxtrim-symlink-")); + t.after(() => rmSync(root, { recursive: true, force: true })); + writeFileSync(join(root, "real.py"), "print(\"hello\")\n"); + symlinkSync("real.py", join(root, "linked.py")); + + const result = scanRepo(root); + assert.deepEqual(result.files.map((file) => file.rel).sort(), ["linked.py", "real.py"]); +}); + test("scan finds trimmable bloat and keeps source", () => { const s = scanRepo(repo); assert.ok(s.totals.trimTokens > 0);