From 27fc96c9974e690f8ae8d8bcd7dde3aad754c063 Mon Sep 17 00:00:00 2001 From: eunwoo song Date: Mon, 7 Sep 2026 05:32:21 +0900 Subject: [PATCH] fix: repair unclosed managed blocks --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- src/ignore.js | 8 +++++++- test/ctxtrim.test.js | 16 ++++++++++++++++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc00af..de55dc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project are documented here, following [Keep a Changelog](https://keepachangelog.com/) and semantic versioning. +## [0.1.4] - 2026-09-07 + +### Fixed + +- Repair an unclosed managed ignore block in place instead of appending a + duplicate block on every run. + ## [0.1.3] - 2026-09-06 ### Fixed diff --git a/package-lock.json b/package-lock.json index f51e515..c70f8c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ctxtrim", - "version": "0.1.3", + "version": "0.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ctxtrim", - "version": "0.1.3", + "version": "0.1.4", "license": "MIT", "bin": { "ctxtrim": "bin/ctxtrim.js" diff --git a/package.json b/package.json index ec62996..fe13b85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ctxtrim", - "version": "0.1.3", + "version": "0.1.4", "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/ignore.js b/src/ignore.js index 6934c5e..e6b7e01 100644 --- a/src/ignore.js +++ b/src/ignore.js @@ -21,9 +21,15 @@ export function block(patterns) { /** Insert or replace the managed block in existing content (keeps the user's own lines). */ export function merge(existing, patterns) { const b = block(patterns); - if (existing.includes(START) && existing.includes(END)) { + const startIndex = existing.indexOf(START); + const endIndex = existing.indexOf(END, startIndex + START.length); + if (startIndex !== -1 && endIndex !== -1) { return existing.replace(new RegExp(escape(START) + "[\\s\\S]*?" + escape(END)), b).trimEnd() + "\n"; } + if (startIndex !== -1) { + const base = existing.slice(0, startIndex).trim(); + return (base ? base + "\n\n" : "") + b + "\n"; + } const base = existing.trim(); return (base ? base + "\n\n" : "") + b + "\n"; } diff --git a/test/ctxtrim.test.js b/test/ctxtrim.test.js index b03da1b..ca72059 100644 --- a/test/ctxtrim.test.js +++ b/test/ctxtrim.test.js @@ -130,6 +130,22 @@ test("ignore block is idempotent (managed block replaced, user lines kept)", () assert.ok(!second.includes("package-lock.json"), "old managed pattern should be gone"); }); +test("ignore merge repairs a managed block with no end marker", () => { + const existing = [ + "# my own rule", + "*.log", + "# >>> ctxtrim (managed) >>>", + "old-generated-pattern/", + ].join("\n"); + + const repaired = merge(existing, ["dist/", "coverage/"]); + + assert.ok(repaired.includes("# my own rule\n*.log")); + assert.ok(repaired.includes(block(["dist/", "coverage/"]))); + assert.ok(!repaired.includes("old-generated-pattern/")); + assert.equal((repaired.match(/ctxtrim \(managed\)/g) || []).length, 2); +}); + test("clean repo (only source) reports nothing to trim", () => { // scanning the src subdir alone = only source const s = scanRepo(join(repo, "src"));