treezip 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -108,7 +108,34 @@ my-project/
108
108
 
109
109
  ### Default ignores
110
110
 
111
- `node_modules`, `.git`, `.DS_Store`, `__pycache__`, `.pnpm`
111
+ treezip hardcodes an aggressive noise filter tuned for LLM context — build
112
+ artifacts, caches, and OS junk never reach the output:
113
+
114
+ - **VCS**: `.git` (dir and worktree pointer file), `.hg`, `.svn`, `.jj`, …
115
+ - **OS**: `.DS_Store`, `Thumbs.db`, `desktop.ini`, `._*` AppleDouble files, …
116
+ - **JS/TS**: `node_modules`, `.next`, `.nuxt`, `.turbo`, `.vite`, `.svelte-kit`,
117
+ `.astro`, `coverage`, `.wrangler`, `.vercel`, `*.tsbuildinfo`, …
118
+ - **Python**: `__pycache__`, `.venv`/`venv`, `.pytest_cache`, `.mypy_cache`,
119
+ `.ruff_cache`, `*.egg-info`, `*.pyc`, …
120
+ - **Swift/Xcode**: `DerivedData`, `.build`, `.swiftpm`, `xcuserdata`,
121
+ `Index.noindex` and friends, …
122
+ - **Others**: `.gradle`, `zig-out`, `_build` (Elixir), `.dart_tool`,
123
+ `CMakeFiles`, `cmake-build-*`, `.terraform`, `.idea`, `.vs`, …
124
+
125
+ Three smarter rules on top of the basename list:
126
+
127
+ 1. **Content markers** — a directory containing `pyvenv.cfg` is a venv, one
128
+ containing `Index.noindex` is an Xcode DerivedData root; both are skipped
129
+ *whatever they're named* (catches custom `-derivedDataPath` dirs).
130
+ 2. **Sibling confirmation** — generic names are only skipped when the
131
+ ecosystem is proven: `target/` next to `Cargo.toml`/`pom.xml`, `build/`
132
+ next to `build.gradle`, `bin`/`obj` next to a `.csproj`. A docs folder
133
+ named `build/` stays visible.
134
+ 3. **Scoped rules** — `.claude/worktrees` is hidden but `.claude/skills`
135
+ stays; `.yarn/cache` is hidden but `.yarn/patches` stays.
136
+
137
+ Meaningful files are never hidden: lockfiles (`package-lock.json`,
138
+ `Package.resolved`, `uv.lock`, …), `.env`, `.envrc`, `.vscode`, `.github`.
112
139
 
113
140
  ## Output header
114
141
 
package/dist/cli.js CHANGED
File without changes
@@ -0,0 +1,10 @@
1
+ /** Directory basenames that are always noise. */
2
+ export declare const NOISE_DIRS: Set<string>;
3
+ /** File basenames that are always noise. */
4
+ export declare const NOISE_FILES: Set<string>;
5
+ /** True if a directory entry should be skipped, judged by name + context. */
6
+ export declare function isNoiseDir(name: string, parentName: string, siblings: string[]): boolean;
7
+ /** True if a file entry should be skipped. */
8
+ export declare function isNoiseFile(name: string, parentName: string): boolean;
9
+ /** True if a directory's own entry list marks the whole dir as noise. */
10
+ export declare function hasNoiseMarker(entries: string[]): boolean;
package/dist/ignore.js ADDED
@@ -0,0 +1,205 @@
1
+ // Hardcoded noise filtering — build artifacts, caches, and OS junk that
2
+ // should never appear in an LLM-context tree. Sources: github/gitignore
3
+ // templates, vendor docs, and the default-ignore lists shipped by
4
+ // repomix/gitingest (the closest prior art).
5
+ //
6
+ // Design rules:
7
+ // 1. Only distinctive basenames are ignored unconditionally. Generic
8
+ // names (dist, build, out, bin, obj, vendor, Pods) are kept visible
9
+ // unless corroborated by a sibling marker (e.g. target + Cargo.toml).
10
+ // 2. Content markers catch arbitrarily-named build roots: a dir that
11
+ // directly contains pyvenv.cfg is a venv; one containing
12
+ // Index.noindex / ModuleCache.noindex is an Xcode DerivedData root
13
+ // (custom -derivedDataPath dirs can be named anything).
14
+ // 3. Lockfiles (package-lock.json, Package.resolved, uv.lock, ...) and
15
+ // meaningful dotfiles (.env, .envrc, .vscode) are never hidden.
16
+ /** Directory basenames that are always noise. */
17
+ export const NOISE_DIRS = new Set([
18
+ // VCS internals
19
+ ".git",
20
+ ".hg",
21
+ ".svn",
22
+ ".bzr",
23
+ ".jj",
24
+ ".sapling",
25
+ // OS junk
26
+ ".Spotlight-V100",
27
+ ".Trashes",
28
+ ".fseventsd",
29
+ ".TemporaryItems",
30
+ ".DocumentRevisions-V100",
31
+ "$RECYCLE.BIN",
32
+ "__MACOSX",
33
+ // JavaScript / TypeScript
34
+ "node_modules",
35
+ ".pnpm",
36
+ ".pnpm-store",
37
+ ".next",
38
+ ".nuxt",
39
+ ".output",
40
+ ".turbo",
41
+ ".parcel-cache",
42
+ ".cache",
43
+ ".vite",
44
+ ".svelte-kit",
45
+ ".astro",
46
+ ".angular",
47
+ ".docusaurus",
48
+ ".expo",
49
+ ".expo-shared",
50
+ ".wrangler",
51
+ ".vercel",
52
+ ".netlify",
53
+ ".serverless",
54
+ ".rpt2_cache",
55
+ ".sass-cache",
56
+ "coverage",
57
+ ".nyc_output",
58
+ "storybook-static",
59
+ // Python
60
+ "__pycache__",
61
+ ".venv",
62
+ "venv",
63
+ ".tox",
64
+ ".nox",
65
+ ".pytest_cache",
66
+ ".mypy_cache",
67
+ ".ruff_cache",
68
+ ".hypothesis",
69
+ ".ipynb_checkpoints",
70
+ ".eggs",
71
+ "site-packages",
72
+ "htmlcov",
73
+ "__pypackages__",
74
+ ".pytype",
75
+ ".pyre",
76
+ "cython_debug",
77
+ // Swift / Xcode
78
+ "DerivedData",
79
+ ".build",
80
+ ".swiftpm",
81
+ "xcuserdata",
82
+ "Carthage",
83
+ "SourcePackages",
84
+ "Index.noindex",
85
+ "ModuleCache.noindex",
86
+ "Intermediates.noindex",
87
+ "CompilationCache.noindex",
88
+ "SDKStatCaches.noindex",
89
+ // JVM
90
+ ".gradle",
91
+ // Zig
92
+ ".zig-cache",
93
+ "zig-cache",
94
+ "zig-out",
95
+ // Elixir
96
+ "_build",
97
+ ".elixir_ls",
98
+ // Dart / Flutter
99
+ ".dart_tool",
100
+ // C / CMake
101
+ "CMakeFiles",
102
+ "_deps",
103
+ // .NET / IDE caches (.vscode/.idea configs are often committed — .vs never is)
104
+ ".vs",
105
+ ".idea",
106
+ // Ruby
107
+ ".bundle",
108
+ ".yardoc",
109
+ // Infra
110
+ ".terraform",
111
+ ".terragrunt-cache",
112
+ ".vagrant",
113
+ ".direnv",
114
+ ]);
115
+ /** File basenames that are always noise. */
116
+ export const NOISE_FILES = new Set([
117
+ ".DS_Store",
118
+ "Thumbs.db",
119
+ "desktop.ini",
120
+ ".directory",
121
+ ".git", // worktree/submodule pointer file
122
+ ".eslintcache",
123
+ ".stylelintcache",
124
+ ".node_repl_history",
125
+ ".yarn-integrity",
126
+ ".coverage",
127
+ "coverage.xml",
128
+ ".dmypy.json",
129
+ "dmypy.json",
130
+ "nosetests.xml",
131
+ "Package.pins",
132
+ ]);
133
+ const NOISE_FILE_PREFIXES = [
134
+ "._", // AppleDouble resource forks
135
+ "npm-debug.log",
136
+ "yarn-debug.log",
137
+ "yarn-error.log",
138
+ "lerna-debug.log",
139
+ ];
140
+ const NOISE_FILE_SUFFIXES = [".pyc", ".pyo", ".tsbuildinfo"];
141
+ const NOISE_DIR_PREFIXES = ["cmake-build-"];
142
+ const NOISE_DIR_SUFFIXES = [".egg-info"];
143
+ /**
144
+ * Generic dir names that are only noise when a sibling file proves the
145
+ * ecosystem (predicate runs against each sibling basename).
146
+ */
147
+ const SIBLING_CONFIRMED_DIRS = {
148
+ target: (s) => s === "Cargo.toml" || s === "pom.xml",
149
+ build: (s) => s === "build.gradle" ||
150
+ s === "build.gradle.kts" ||
151
+ s === "settings.gradle" ||
152
+ s === "settings.gradle.kts",
153
+ deps: (s) => s === "mix.exs",
154
+ bin: (s) => /\.(cs|fs|vb)proj$|\.sln$/.test(s),
155
+ obj: (s) => /\.(cs|fs|vb)proj$|\.sln$/.test(s),
156
+ };
157
+ /** Child names that are noise only inside a specific parent dir. */
158
+ const SCOPED_NOISE = {
159
+ ".claude": new Set(["worktrees"]),
160
+ ".yarn": new Set(["cache", "unplugged", "install-state.gz", "build-state.yml"]),
161
+ };
162
+ /**
163
+ * If a directory directly contains any of these entries, the entire
164
+ * directory is noise regardless of its name.
165
+ */
166
+ const CONTENT_MARKERS = new Set([
167
+ "pyvenv.cfg", // Python venv (catches env/, myenv/, any name)
168
+ "Index.noindex", // Xcode DerivedData root (default or custom -derivedDataPath)
169
+ "ModuleCache.noindex",
170
+ "SDKStatCaches.noindex",
171
+ "CompilationCache.noindex",
172
+ ]);
173
+ /** True if a directory entry should be skipped, judged by name + context. */
174
+ export function isNoiseDir(name, parentName, siblings) {
175
+ if (NOISE_DIRS.has(name))
176
+ return true;
177
+ if (NOISE_DIR_PREFIXES.some((p) => name.startsWith(p)))
178
+ return true;
179
+ if (NOISE_DIR_SUFFIXES.some((s) => name.endsWith(s)))
180
+ return true;
181
+ const scoped = SCOPED_NOISE[parentName];
182
+ if (scoped?.has(name))
183
+ return true;
184
+ const confirm = SIBLING_CONFIRMED_DIRS[name];
185
+ if (confirm && siblings.some(confirm))
186
+ return true;
187
+ return false;
188
+ }
189
+ /** True if a file entry should be skipped. */
190
+ export function isNoiseFile(name, parentName) {
191
+ if (NOISE_FILES.has(name))
192
+ return true;
193
+ if (NOISE_FILE_PREFIXES.some((p) => name.startsWith(p)))
194
+ return true;
195
+ if (NOISE_FILE_SUFFIXES.some((s) => name.endsWith(s)))
196
+ return true;
197
+ const scoped = SCOPED_NOISE[parentName];
198
+ if (scoped?.has(name))
199
+ return true;
200
+ return false;
201
+ }
202
+ /** True if a directory's own entry list marks the whole dir as noise. */
203
+ export function hasNoiseMarker(entries) {
204
+ return entries.some((e) => CONTENT_MARKERS.has(e));
205
+ }
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export type { TreeNode } from "./scanner.js";
3
3
  export { compress } from "./compress.js";
4
4
  export type { CompressedItem } from "./compress.js";
5
5
  export { render } from "./renderer.js";
6
+ export { NOISE_DIRS, NOISE_FILES, isNoiseDir, isNoiseFile, hasNoiseMarker } from "./ignore.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { scan, countNodes } from "./scanner.js";
2
2
  export { compress } from "./compress.js";
3
3
  export { render } from "./renderer.js";
4
+ export { NOISE_DIRS, NOISE_FILES, isNoiseDir, isNoiseFile, hasNoiseMarker } from "./ignore.js";
package/dist/scanner.d.ts CHANGED
@@ -3,7 +3,7 @@ export interface TreeNode {
3
3
  isDir: boolean;
4
4
  children: TreeNode[];
5
5
  }
6
- export declare function scan(rootPath: string, ignores?: Set<string>): TreeNode;
6
+ export declare function scan(rootPath: string): TreeNode;
7
7
  /** Count total files and directories in a TreeNode (excludes root) */
8
8
  export declare function countNodes(node: TreeNode): {
9
9
  dirs: number;
package/dist/scanner.js CHANGED
@@ -1,25 +1,23 @@
1
- import { readdirSync, statSync, lstatSync } from "node:fs";
2
- import { join } from "node:path";
3
- const DEFAULT_IGNORES = new Set([
4
- "node_modules",
5
- ".git",
6
- ".DS_Store",
7
- ".Spotlight-V100",
8
- ".Trashes",
9
- "__pycache__",
10
- ".pnpm",
11
- ]);
12
- export function scan(rootPath, ignores = DEFAULT_IGNORES) {
1
+ import { readdirSync, lstatSync, statSync } from "node:fs";
2
+ import { join, basename } from "node:path";
3
+ import { isNoiseDir, isNoiseFile, hasNoiseMarker } from "./ignore.js";
4
+ export function scan(rootPath) {
13
5
  const stat = statSync(rootPath);
14
6
  const name = rootPath.split("/").filter(Boolean).pop() ?? rootPath;
15
7
  if (!stat.isDirectory()) {
16
8
  return { name, isDir: false, children: [] };
17
9
  }
18
- const entries = readdirSync(rootPath).filter((e) => !ignores.has(e)).sort();
10
+ // Never bail on the root itself, even if it looks like a build dir —
11
+ // the user explicitly asked for it.
12
+ return scanDir(rootPath, name);
13
+ }
14
+ function scanDir(dirPath, name) {
15
+ const entries = readdirSync(dirPath).sort();
16
+ const parentName = basename(dirPath);
19
17
  const files = [];
20
18
  const dirs = [];
21
19
  for (const entry of entries) {
22
- const fullPath = join(rootPath, entry);
20
+ const fullPath = join(dirPath, entry);
23
21
  let entryStat;
24
22
  try {
25
23
  entryStat = lstatSync(fullPath);
@@ -31,9 +29,24 @@ export function scan(rootPath, ignores = DEFAULT_IGNORES) {
31
29
  if (entryStat.isSymbolicLink())
32
30
  continue;
33
31
  if (entryStat.isDirectory()) {
34
- dirs.push(scan(fullPath, ignores));
32
+ if (isNoiseDir(entry, parentName, entries))
33
+ continue;
34
+ // Content-marker check: a dir whose entries reveal it as a venv or
35
+ // DerivedData root is noise regardless of what it's named.
36
+ let childEntries;
37
+ try {
38
+ childEntries = readdirSync(fullPath);
39
+ }
40
+ catch {
41
+ continue;
42
+ }
43
+ if (hasNoiseMarker(childEntries))
44
+ continue;
45
+ dirs.push(scanDir(fullPath, entry));
35
46
  }
36
47
  else if (entryStat.isFile()) {
48
+ if (isNoiseFile(entry, parentName))
49
+ continue;
37
50
  files.push({ name: entry, isDir: false, children: [] });
38
51
  }
39
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "treezip",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Compressed tree output for AI-optimized codebase summaries",
5
5
  "type": "module",
6
6
  "bin": {