diff --git a/CHANGELOG.md b/CHANGELOG.md index f911098..3a62d49 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.7] - 2026-09-11 + +### Fixed + +- Reject multiple positional paths with exit code 2 before scanning or writing ignore files. + ## [0.1.6] - 2026-09-08 ### Fixed diff --git a/README.md b/README.md index 93cc266..ac951bf 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,9 @@ npx ctxtrim --format json # machine-readable npx ctxtrim --fail-on-waste 40 # CI: exit 1 if 40%+ of context is junk ``` +At most one path may be supplied; omitting it uses the current directory. Extra paths +are a usage error (exit code 2), detected before scanning or writing files. + | Flag | Default | Meaning | | --- | --- | --- | | `--write` | off | create/update the ignore files (otherwise report only) | diff --git a/package-lock.json b/package-lock.json index f9fd278..73288a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ctxtrim", - "version": "0.1.6", + "version": "0.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ctxtrim", - "version": "0.1.6", + "version": "0.1.7", "license": "MIT", "bin": { "ctxtrim": "bin/ctxtrim.js" diff --git a/package.json b/package.json index 0c68064..82de463 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ctxtrim", - "version": "0.1.6", + "version": "0.1.7", "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/cli.js b/src/cli.js index 75972fe..7062698 100644 --- a/src/cli.js +++ b/src/cli.js @@ -47,7 +47,10 @@ function parse(argv) { else if (has("--top")) o.top = Number(val()); else if (has("--format")) o.format = val(); else if (has("--fail-on-waste")) o.failOnWaste = Number(val()); - else if (!a.startsWith("-")) o.path = a; + else if (!a.startsWith("-")) { + if (o.path !== null) return { ...o, error: "ctxtrim: only one path may be supplied\n" }; + o.path = a; + } } return o; } @@ -66,6 +69,7 @@ function numericOptionError(o) { export function run(argv, { version }) { const o = parse(argv); + if (o.error) { process.stderr.write(o.error); return 2; } if (o.help) { process.stdout.write(HELP); return 0; } if (o.version) { process.stdout.write(version + "\n"); return 0; } const target = o.path || "."; diff --git a/test/multiple-paths.test.js b/test/multiple-paths.test.js new file mode 100644 index 0000000..cb1cad1 --- /dev/null +++ b/test/multiple-paths.test.js @@ -0,0 +1,38 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const cli = fileURLToPath(new URL("../bin/ctxtrim.js", import.meta.url)); + +test("multiple paths fail before scanning or writing either directory", (t) => { + const root = mkdtempSync(join(tmpdir(), "ctxtrim-paths-")); + t.after(() => rmSync(root, { recursive: true, force: true })); + const paths = [join(root, "a"), join(root, "b")]; + for (const path of paths) { + mkdirSync(path); + writeFileSync(join(path, "package-lock.json"), "{}\n"); + } + for (const args of [ + [paths[0], paths[1], "--write"], + [paths[0], "--format", "json", paths[1], "--write"], + [paths[0], paths[0]], + ["", paths[1]], + ]) { + const result = spawnSync(process.execPath, [cli, ...args], { encoding: "utf8" }); + assert.equal(result.status, 2); + assert.match(result.stderr, /only one path/i); + assert.equal(result.stdout, ""); + } + for (const path of paths) { + assert.equal(existsSync(join(path, ".cursorignore")), false); + assert.equal(existsSync(join(path, ".aiexclude")), false); + } + for (const args of [[paths[0], "--format", "json"], ["--format=json", paths[0]], []]) { + const result = spawnSync(process.execPath, [cli, ...args], { cwd: paths[0], encoding: "utf8" }); + assert.equal(result.status, 0, result.stderr); + } +});