catport 1.0.0

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.
Files changed (56) hide show
  1. package/ARCHITECTURE.md +94 -0
  2. package/CONTRIBUTING.md +133 -0
  3. package/LICENSE +21 -0
  4. package/README.md +414 -0
  5. package/bin/catport +8 -0
  6. package/package.json +48 -0
  7. package/src/cli/args.js +133 -0
  8. package/src/cli/main.js +78 -0
  9. package/src/cli/parser.js +152 -0
  10. package/src/cli/ui.js +78 -0
  11. package/src/config/constants.js +62 -0
  12. package/src/config/ignores.js +119 -0
  13. package/src/config/loader.js +15 -0
  14. package/src/config/options.js +181 -0
  15. package/src/core/analyzer.js +23 -0
  16. package/src/core/bundler.js +165 -0
  17. package/src/core/extractor.js +76 -0
  18. package/src/core/ignore.js +65 -0
  19. package/src/core/processor.js +59 -0
  20. package/src/core/scanner.js +184 -0
  21. package/src/formatters/index.js +78 -0
  22. package/src/formatters/json.js +284 -0
  23. package/src/formatters/markdown.js +164 -0
  24. package/src/formatters/multipart.js +127 -0
  25. package/src/formatters/xml.js +221 -0
  26. package/src/formatters/yaml.js +147 -0
  27. package/src/index.js +11 -0
  28. package/src/optimizers/definitions.js +79 -0
  29. package/src/optimizers/index.js +96 -0
  30. package/src/optimizers/langs/batch.js +3 -0
  31. package/src/optimizers/langs/c_family.js +3 -0
  32. package/src/optimizers/langs/clojure.js +3 -0
  33. package/src/optimizers/langs/css.js +3 -0
  34. package/src/optimizers/langs/go.js +5 -0
  35. package/src/optimizers/langs/haskell.js +4 -0
  36. package/src/optimizers/langs/html.js +4 -0
  37. package/src/optimizers/langs/ini.js +4 -0
  38. package/src/optimizers/langs/javascript.js +11 -0
  39. package/src/optimizers/langs/lua.js +4 -0
  40. package/src/optimizers/langs/markdown.js +3 -0
  41. package/src/optimizers/langs/perl.js +3 -0
  42. package/src/optimizers/langs/php.js +4 -0
  43. package/src/optimizers/langs/powershell.js +5 -0
  44. package/src/optimizers/langs/python.js +5 -0
  45. package/src/optimizers/langs/ruby.js +4 -0
  46. package/src/optimizers/langs/rust.js +3 -0
  47. package/src/optimizers/langs/shell.js +4 -0
  48. package/src/optimizers/langs/sql.js +4 -0
  49. package/src/optimizers/langs/xml.js +3 -0
  50. package/src/optimizers/langs/yaml.js +3 -0
  51. package/src/optimizers/tokenizer.js +444 -0
  52. package/src/utils/git.js +35 -0
  53. package/src/utils/io.js +79 -0
  54. package/src/utils/logger.js +25 -0
  55. package/src/utils/path.js +59 -0
  56. package/src/utils/style.js +59 -0
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "catport",
3
+ "version": "1.0.0",
4
+ "description": "A high-performance, minimalist, and dependency-free CLI tool for bundling file contents or extracting files from a bundle.",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "catport": "bin/catport"
8
+ },
9
+ "files": [
10
+ "src",
11
+ "bin",
12
+ "README.md",
13
+ "LICENSE",
14
+ "CONTRIBUTING.md",
15
+ "ARCHITECTURE.md"
16
+ ],
17
+ "scripts": {
18
+ "test": "node --test tests/**/*.test.js",
19
+ "test:watch": "node --test --watch tests/**/*.test.js",
20
+ "lint": "npx eslint src/ tests/ --max-warnings 0"
21
+ },
22
+ "keywords": [
23
+ "llm",
24
+ "context",
25
+ "bundle",
26
+ "devtools",
27
+ "serializer",
28
+ "ai",
29
+ "prompt-engineering",
30
+ "multipart",
31
+ "yaml",
32
+ "git",
33
+ "minifier",
34
+ "zero-dependency",
35
+ "cli"
36
+ ],
37
+ "author": "Reza Jelodar",
38
+ "license": "MIT",
39
+ "engines": {
40
+ "node": ">=20.0.0"
41
+ },
42
+ "type": "module",
43
+ "devDependencies": {
44
+ "@eslint/js": "^9.0.0",
45
+ "eslint": "^9.0.0",
46
+ "globals": "^15.0.0"
47
+ }
48
+ }
@@ -0,0 +1,133 @@
1
+ import { DEFAULTS, LOG, OPTIMIZE } from '../config/constants.js';
2
+ import { OPTIONS } from '../config/options.js';
3
+ import { Parser } from './parser.js';
4
+
5
+ const parseSize = (input) => {
6
+ if (typeof input === 'number') {
7
+ return input;
8
+ }
9
+ if (!input) {
10
+ return 0;
11
+ }
12
+
13
+ const s = String(input).trim();
14
+ const match = s.match(/^(\d+(?:\.\d+)?)\s*([kmg]b?)?$/i);
15
+
16
+ if (!match) {
17
+ return parseInt(s, 10) || 0;
18
+ }
19
+
20
+ const val = parseFloat(match[1]);
21
+ const suffix = (match[2] || '').toLowerCase();
22
+
23
+ if (suffix.startsWith('k')) {
24
+ return Math.floor(val * 1024);
25
+ }
26
+ if (suffix.startsWith('m')) {
27
+ return Math.floor(val * 1024 * 1024);
28
+ }
29
+ if (suffix.startsWith('g')) {
30
+ return Math.floor(val * 1024 * 1024 * 1024);
31
+ }
32
+
33
+ return Math.floor(val);
34
+ };
35
+
36
+ const normalize = (config) => {
37
+ if (config.verbose) {
38
+ config.logLevel = LOG.INFO;
39
+ }
40
+
41
+ if (config.optimize !== undefined) {
42
+ const val = String(config.optimize).trim();
43
+ const lowerVal = val.toLowerCase();
44
+ const validModes = new Set(Object.values(OPTIMIZE));
45
+
46
+ if (validModes.has(lowerVal)) {
47
+ config.optimize = lowerVal;
48
+ } else {
49
+ config.optimizeCmd = val;
50
+ }
51
+ }
52
+
53
+ if (config.noInstruct) {
54
+ config.instruct = false;
55
+ }
56
+
57
+ if (config.noStructure) {
58
+ config.structure = false;
59
+ }
60
+
61
+ if (config.unsafe) {
62
+ config.safeMode = false;
63
+ }
64
+
65
+ if (config.extensions) {
66
+ config.extSet = new Set(
67
+ config.extensions.split(',').map(e => e.trim().replace(/^\./, '').toLowerCase())
68
+ );
69
+ }
70
+
71
+ // Merge Ignore Patterns
72
+ const baseIgnore = config.noIgnore ? [] : DEFAULTS.IGNORE;
73
+ const userIgnore = config.ignore || [];
74
+ config.ignore = [...baseIgnore, ...userIgnore];
75
+
76
+ if (config.priority) {
77
+ config.priorityRules = config.priority.map(p => {
78
+ if (typeof p === 'object') {
79
+ return p;
80
+ }
81
+ const idx = p.lastIndexOf(':');
82
+ if (idx > 0) {
83
+ const score = parseInt(p.slice(idx + 1), 10);
84
+ if (!Number.isNaN(score)) {
85
+ return {
86
+ pattern: p.slice(0, idx),
87
+ score
88
+ };
89
+ }
90
+ }
91
+ return null;
92
+ }).filter(Boolean);
93
+ }
94
+
95
+ if (config.gitDiff === true) {
96
+ config.gitDiff = 'HEAD';
97
+ }
98
+
99
+ if (config.maxSize !== undefined) {
100
+ config.maxSize = parseSize(config.maxSize);
101
+ }
102
+
103
+ return config;
104
+ };
105
+
106
+ export const ArgParser = {
107
+ process(argv, fileConfig = {}, schema = OPTIONS) {
108
+ const defaults = Parser.getDefaults(schema);
109
+ const explicit = Parser.parse(argv, schema);
110
+
111
+ const rawConfig = {
112
+ logLevel: LOG.WARN,
113
+ instruct: DEFAULTS.INSTRUCT,
114
+ structure: DEFAULTS.STRUCTURE,
115
+ listDirs: DEFAULTS.LIST_DIRS,
116
+ optimize: DEFAULTS.OPTIMIZE,
117
+ safeMode: DEFAULTS.SAFE_MODE,
118
+ charsPerToken: DEFAULTS.CHARS_PER_TOKEN,
119
+ maxSize: DEFAULTS.MAX_SIZE,
120
+ ...defaults,
121
+ ...fileConfig
122
+ };
123
+
124
+ Object.keys(explicit).forEach(k => {
125
+ if (k === 'paths' && explicit.paths.length === 0) {
126
+ return;
127
+ }
128
+ rawConfig[k] = explicit[k];
129
+ });
130
+
131
+ return normalize(rawConfig);
132
+ }
133
+ };
@@ -0,0 +1,78 @@
1
+ import { ArgParser } from './args.js';
2
+ import { UI } from './ui.js';
3
+ import { Bundler } from '../core/bundler.js';
4
+ import { Extractor } from '../core/extractor.js';
5
+ import { NodeIO } from '../utils/io.js';
6
+ import { Git } from '../utils/git.js';
7
+ import { Logger } from '../utils/logger.js';
8
+ import { EXIT, APP } from '../config/constants.js';
9
+ import { OPTIONS } from '../config/options.js';
10
+ import { Optimizer } from '../optimizers/index.js';
11
+ import { Formatter } from '../formatters/index.js';
12
+ import { Scanner } from '../core/scanner.js';
13
+ import { Analyzer } from '../core/analyzer.js';
14
+ import { Processor } from '../core/processor.js';
15
+ import { ConfigLoader } from '../config/loader.js';
16
+
17
+ export const CLI = {
18
+ async run(argv, io = NodeIO) {
19
+ const ui = UI(io);
20
+ try {
21
+ const fileConfig = await ConfigLoader.load(io);
22
+ const config = ArgParser.process(argv, fileConfig);
23
+
24
+ if (config.help) {
25
+ ui.printHeader(APP);
26
+ ui.printHelp(APP, OPTIONS);
27
+ return;
28
+ }
29
+
30
+ if (config.version) {
31
+ io.writeStdout(`${APP.NAME} v${APP.VERSION}\n`);
32
+ return;
33
+ }
34
+
35
+ const logger = Logger(config.logLevel, io);
36
+
37
+ if (config.extract) {
38
+ await Extractor.run(config, io, {
39
+ formatter: Formatter,
40
+ logger: logger
41
+ });
42
+ } else {
43
+ if (config.gitDiff) {
44
+ const changes = await Git.getChangedFiles(io, config.gitDiff, io.cwd());
45
+ if (!changes) {
46
+ ui.printError('Not a git repository or git command failed.');
47
+ io.exit(EXIT.ERROR);
48
+ return;
49
+ }
50
+
51
+ if (changes.size === 0) {
52
+ ui.printError('No changes detected.');
53
+ io.exit(EXIT.SUCCESS);
54
+ return;
55
+ }
56
+
57
+ config.gitFiles = changes;
58
+ }
59
+
60
+ const stats = await Bundler.run(config, io, {
61
+ scanner: Scanner,
62
+ analyzer: Analyzer,
63
+ processor: Processor,
64
+ optimizer: Optimizer,
65
+ formatter: Formatter,
66
+ logger: logger
67
+ });
68
+
69
+ if (config.output && stats) {
70
+ ui.printUsageReport(stats, config.output);
71
+ }
72
+ }
73
+ } catch (e) {
74
+ ui.printError(e.message);
75
+ io.exit(EXIT.ERROR);
76
+ }
77
+ }
78
+ };
@@ -0,0 +1,152 @@
1
+
2
+ /**
3
+ * Map CLI flags to schema keys.
4
+ */
5
+ const buildMaps = (schema) => {
6
+ const flags = new Map();
7
+ const shorts = new Map();
8
+
9
+ for (const [key, opt] of Object.entries(schema)) {
10
+ const long = '--' + key.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
11
+ flags.set(long, key);
12
+ if (opt.short) {
13
+ shorts.set('-' + opt.short, key);
14
+ }
15
+ }
16
+ return { flags, shorts };
17
+ };
18
+
19
+ const parseNumber = (key, val) => {
20
+ const n = Number(val);
21
+ if (Number.isNaN(n)) {
22
+ throw new Error(`Expected number for --${key}, got "${val}"`);
23
+ }
24
+ return n;
25
+ };
26
+
27
+ export const Parser = {
28
+ getDefaults(schema) {
29
+ const defaults = { paths: [] };
30
+ for (const [key, opt] of Object.entries(schema)) {
31
+ if (opt.default !== undefined) {
32
+ defaults[key] = opt.default;
33
+ }
34
+ }
35
+ return defaults;
36
+ },
37
+
38
+ /**
39
+ * Parse argv into an explicit configuration object.
40
+ * Does NOT apply defaults. Returns only what is present in argv.
41
+ */
42
+ parse(argv, schema) {
43
+ const { flags, shorts } = buildMaps(schema);
44
+ const explicit = { paths: [] };
45
+ const args = argv.slice(2);
46
+
47
+ for (let i = 0; i < args.length; i++) {
48
+ const arg = args[i];
49
+
50
+ if (arg === '--') {
51
+ explicit.paths.push(...args.slice(i + 1));
52
+ break;
53
+ }
54
+
55
+ if (!arg.startsWith('-') || arg === '-') {
56
+ explicit.paths.push(arg);
57
+ continue;
58
+ }
59
+
60
+ // Grouped Short Flags (e.g. -xvu)
61
+ if (!arg.startsWith('--') && arg.length > 2) {
62
+ const chars = arg.slice(1).split('');
63
+ for (const c of chars) {
64
+ const key = shorts.get('-' + c);
65
+ if (!key) {
66
+ throw new Error(`Unknown flag: -${c}`);
67
+ }
68
+ if (schema[key].type !== 'boolean') {
69
+ throw new Error(`Flag -${c} requires a value and cannot be grouped.`);
70
+ }
71
+ explicit[key] = true;
72
+ }
73
+ continue;
74
+ }
75
+
76
+ // Normal Flags
77
+ let key = null;
78
+ let explicitVal = null;
79
+
80
+ if (arg.startsWith('--')) {
81
+ if (arg.includes('=')) {
82
+ const parts = arg.split('=');
83
+ key = flags.get(parts[0]);
84
+ explicitVal = parts.slice(1).join('=');
85
+ } else {
86
+ key = flags.get(arg);
87
+ }
88
+ } else {
89
+ key = shorts.get(arg);
90
+ }
91
+
92
+ if (!key) {
93
+ throw new Error(`Unknown argument: ${arg}`);
94
+ }
95
+
96
+ const opt = schema[key];
97
+ const isBool = opt.type === 'boolean';
98
+
99
+ if (isBool) {
100
+ if (explicitVal) {
101
+ throw new Error(`Boolean flag ${key} does not accept a value.`);
102
+ }
103
+ explicit[key] = true;
104
+ } else {
105
+ let val = explicitVal;
106
+
107
+ if (val === null) {
108
+ const next = args[i + 1];
109
+
110
+ // Value detection logic
111
+ let isValidValue = false;
112
+ if (next !== undefined) {
113
+ const isFlag = next.startsWith('-');
114
+ const isStdin = next === '-';
115
+ const isNumberValue = isFlag && opt.type === 'number' && !Number.isNaN(Number(next));
116
+
117
+ if (!isFlag || isStdin || isNumberValue) {
118
+ isValidValue = true;
119
+ }
120
+ }
121
+
122
+ if (isValidValue) {
123
+ val = next;
124
+ i++;
125
+ } else {
126
+ if (opt.optional) {
127
+ val = true;
128
+ } else {
129
+ throw new Error(`Flag ${key} requires a value.`);
130
+ }
131
+ }
132
+ }
133
+
134
+ if (val === true && opt.optional) {
135
+ explicit[key] = true;
136
+ } else {
137
+ const parsed = opt.type === 'number' ? parseNumber(key, val) : val;
138
+ if (opt.type === 'array') {
139
+ if (!explicit[key]) {
140
+ explicit[key] = [];
141
+ }
142
+ explicit[key].push(parsed);
143
+ } else {
144
+ explicit[key] = parsed;
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ return explicit;
151
+ }
152
+ };
package/src/cli/ui.js ADDED
@@ -0,0 +1,78 @@
1
+ import { Style } from '../utils/style.js';
2
+
3
+ export const UI = (io) => ({
4
+ printHeader: (app) => {
5
+ io.writeStdout(`\n${Style.bold(Style.cyan(app.NAME))} v${app.VERSION}\n`);
6
+ io.writeStdout(`${Style.dim(app.DESC)}\n`);
7
+ },
8
+
9
+ printHelp: (app, schema) => {
10
+ const pad = (str, len) => str.padEnd(len, ' ');
11
+ const row = (short, long, desc) => ` ${Style.yellow(short)}, ${Style.green(pad(long, 24))} ${desc}`;
12
+ const toKebab = (str) => '--' + str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
13
+
14
+ io.writeStdout(`\n${Style.bold('USAGE')}\n ${app.NAME} [options] [path...]\n`);
15
+
16
+ const categories = {};
17
+
18
+ Object.entries(schema).forEach(([key, opt]) => {
19
+ const cat = opt.category || 'General';
20
+ if (!categories[cat]) {
21
+ categories[cat] = [];
22
+ }
23
+
24
+ let flagStr = toKebab(key);
25
+ if (opt.type !== 'boolean') {
26
+ const mv = Style.italic(opt.metavar);
27
+ if (opt.optional) {
28
+ flagStr += ` [${mv}]`;
29
+ } else {
30
+ flagStr += ` <${mv}>`;
31
+ }
32
+ }
33
+
34
+ categories[cat].push(row(`-${opt.short}`, flagStr, opt.desc));
35
+ });
36
+
37
+ const order = [
38
+ 'General',
39
+ 'Bundling',
40
+ 'Extraction'
41
+ ];
42
+
43
+ order.forEach(name => {
44
+ if (categories[name]) {
45
+ io.writeStdout(`\n${Style.bold(Style.underline(name.toUpperCase()))}\n${categories[name].join('\n')}\n`);
46
+ }
47
+ });
48
+
49
+ const cmt = (s) => Style.dim('# ' + s);
50
+
51
+ io.writeStdout(`\n${Style.bold('EXAMPLES')}
52
+ ${cmt('Bundle project with task')}
53
+ ${app.NAME} -T "Refactor this" -o bundle.md
54
+
55
+ ${cmt('Bundle git changes with optimized output (no comments)')}
56
+ ${app.NAME} -g HEAD -O comments
57
+
58
+ ${cmt('Extract code (safe by default)')}
59
+ ${app.NAME} -x bundle.md -d ./src
60
+
61
+ ${cmt('Extract code (unsafe/allow traversal)')}
62
+ ${app.NAME} -x bundle.md -d ./src -U
63
+ `);
64
+ },
65
+
66
+ printError: (msg) => {
67
+ const badge = Style.bgRed(Style.white(' FATAL '));
68
+ io.writeStderr(`\n${badge} ${msg}\n`);
69
+ },
70
+
71
+ printUsageReport: (stats, path) => {
72
+ if (path) {
73
+ const files = stats.files !== undefined ? stats.files : stats;
74
+ const tokens = stats.tokens !== undefined ? ` (~${stats.tokens} tokens)` : '';
75
+ io.writeStdout(`\n${Style.green('✔ Success')} Bundled ${Style.bold(files)} files${tokens} to ${Style.cyan(path)}\n`);
76
+ }
77
+ }
78
+ });
@@ -0,0 +1,62 @@
1
+ import { DEFAULT_IGNORES } from './ignores.js';
2
+
3
+ export const APP = {
4
+ NAME: 'catport',
5
+ VERSION: '1.0.0',
6
+ DESC: 'Deterministic filesystem serializer for LLM contexts'
7
+ };
8
+
9
+ export const EXIT = {
10
+ SUCCESS: 0,
11
+ ERROR: 1
12
+ };
13
+
14
+ export const LOG = {
15
+ SILENT: 0,
16
+ ERROR: 1,
17
+ WARN: 2,
18
+ INFO: 3,
19
+ DEBUG: 4
20
+ };
21
+
22
+ export const FORMAT = {
23
+ MD: 'md',
24
+ XML: 'xml',
25
+ JSON: 'json',
26
+ YAML: 'yaml',
27
+ MULTIPART: 'multipart'
28
+ };
29
+
30
+ export const XML_MODE = {
31
+ AUTO: 'auto',
32
+ CDATA: 'cdata',
33
+ ESCAPE: 'escape'
34
+ };
35
+
36
+ export const OPTIMIZE = {
37
+ NONE: 'none',
38
+ WHITESPACE: 'whitespace',
39
+ COMMENTS: 'comments',
40
+ MINIFY: 'minify'
41
+ };
42
+
43
+ export const XML_TAGS = {
44
+ CDATA_OPEN: '<![' + 'CDATA[',
45
+ CDATA_CLOSE: ']]' + '>'
46
+ };
47
+
48
+ export const DEFAULTS = {
49
+ BUDGET: 0,
50
+ CHARS_PER_TOKEN: 4.2,
51
+ PRIORITY: 1,
52
+ EXTRACT_DIR: '.',
53
+ FORMAT: FORMAT.MD,
54
+ INSTRUCT: true,
55
+ STRUCTURE: true,
56
+ LIST_DIRS: false,
57
+ SAFE_MODE: true,
58
+ OPTIMIZE: OPTIMIZE.NONE,
59
+ CONCURRENCY: 32,
60
+ MAX_SIZE: 10 * 1024 * 1024,
61
+ IGNORE: DEFAULT_IGNORES
62
+ };
@@ -0,0 +1,119 @@
1
+ export const DEFAULT_IGNORES = [
2
+ // --- Version Control & Metadata ---
3
+ '.git/', '.svn/', '.hg/', '.bzr/', '.cvs/', '.gitattributes', '.gitmodules',
4
+
5
+ // --- OS & System Files ---
6
+ '.DS_Store', '.DS_Store?', '._*', '.Spotlight-V100', '.Trashes',
7
+ 'Thumbs.db', 'ehthumbs.db', 'desktop.ini', '$RECYCLE.BIN/',
8
+ '.AppleDouble/', '.LSOverride/',
9
+ '.directory', '.fuse_hidden*', '.nfs*',
10
+
11
+ // --- Editors & IDEs ---
12
+ '.idea/', '.vscode/', '.vs/', '.settings/', '.classpath', '.project',
13
+ '*.sublime-workspace', '*.sublime-project',
14
+ '*.iml', '*.ipr', '*.iws', '*.suo', '*.ntvs*', '*.njsproj', '*.sln',
15
+ '.factorypath', '.checkstyle', '.metadata/',
16
+ 'nbproject/', 'build.xml', // NetBeans / Ant
17
+ '.history/', '.ionide/', // VS Code history / Ionide
18
+ 'Session.vim', '.netrwhist', // Vim
19
+ '*~', '#*#', '.#*', // Emacs / Vim
20
+
21
+ // --- Logs, Temp, Backups ---
22
+ '*.log', 'logs/', 'log/', '*.log.*',
23
+ '*.backup', '*.bak', '*.tmp', '*.temp',
24
+ '*.swp', '*.swo', '*.swn',
25
+ '*.old', '*.orig', '*.rej',
26
+ '*.dump', '*.stackdump', 'core',
27
+ '*.pid', '*.seed',
28
+
29
+ // --- Secrets, Credentials, Local Config (Security) ---
30
+ '.env', '.env.*', '!.env.example', '!.env.sample', '!.env.template', '!.env.defaults',
31
+ '*.pem', '*.key', '*.crt', '*.pfx', '*.p12', '*.der', '*.cer',
32
+ 'id_rsa', 'id_rsa.pub', 'known_hosts',
33
+ 'config.secret.*', 'secrets.*', 'credentials.*',
34
+ '.npmrc', '.netrc', '.ssh/', '.aws/', '.gcloud/', '.azure/',
35
+
36
+ // --- Build Outputs & Artifacts (General) ---
37
+ 'dist/', 'build/', 'out/', 'target/', 'obj/', 'bin/',
38
+ 'release/', 'debug/',
39
+ '_build/', 'deps/',
40
+
41
+ // --- Archives & Binary Assets ---
42
+ '*.zip', '*.tar', '*.gz', '*.tgz', '*.rar', '*.7z', '*.iso', '*.dmg',
43
+ '*.exe', '*.dll', '*.so', '*.so.*', '*.dylib', '*.bin', '*.msi', '*.app',
44
+ '*.deb', '*.rpm', '*.apk', '*.jar', '*.war', '*.ear', '*.nar',
45
+ '*.png', '*.jpg', '*.jpeg', '*.gif', '*.ico', '*.svg', '*.webp', '*.bmp', '*.tiff',
46
+ '*.mp4', '*.mp3', '*.wav', '*.mov', '*.avi', '*.mkv', '*.webm', '*.flv',
47
+ '*.pdf', '*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx',
48
+ '*.ttf', '*.otf', '*.woff', '*.woff2', '*.eot',
49
+ '*.psd', '*.ai', '*.hex', '*.out',
50
+
51
+ // --- Node.js / JS / TS ---
52
+ 'node_modules/', 'bower_components/', 'jspm_packages/', 'web_modules/',
53
+ '.npm/', '.yarn/', '.pnp.*', '.pnpm-store/',
54
+ '.cache/', '.parcel-cache/', '.eslintcache/', '.stylelintcache/',
55
+ 'coverage/', '.nyc_output/', 'lib-cov/',
56
+ '.grunt/', '.gulp/',
57
+ '.next/', '.nuxt/', '.output/', '.docusaurus/', '.svelte-kit/', '.astro/',
58
+ '*.min.js', '*.min.css', '*.map', 'report.[0-9]*.*', '*.tsbuildinfo',
59
+
60
+ // --- Python ---
61
+ '__pycache__/', '*.py[cod]', '*$py.class',
62
+ 'venv/', '.venv/', 'env/', 'ENV/',
63
+ '.pytest_cache/', '.mypy_cache/', '.ruff_cache/', '.tox/', '.nox/', '.coverage',
64
+ 'htmlcov/', 'pip-wheel-metadata/',
65
+ '*.egg-info/', '.eggs/', '*.egg', 'instance/',
66
+ 'jupyter_cache/', '.ipynb_checkpoints/', 'celerybeat-schedule',
67
+
68
+ // --- Java / JVM / Kotlin ---
69
+ '*.class', '.gradle/', 'gradle-app.setting',
70
+ 'hs_err_pid*',
71
+
72
+ // --- C / C++ / Obj-C ---
73
+ '*.o', '*.obj', '*.lo', '*.la', '*.lai', '*.slo', '*.sl',
74
+ '*.a', '*.lib',
75
+ '*.gch', '*.pch', '*.d',
76
+ '.make', 'cmake-build-*/', 'CMakeFiles/', 'CMakeCache.txt',
77
+ '*.profraw', '*.gcda', '*.gcno',
78
+ 'DerivedData/', 'compile_commands.json',
79
+
80
+ // --- .NET / C# ---
81
+ '*.pdb', 'packages/', 'TestResult/',
82
+
83
+ // --- Go ---
84
+ 'go.work', 'go.test', '_testmain.go',
85
+
86
+ // --- Rust ---
87
+ '*.rs.bk',
88
+
89
+ // --- Ruby ---
90
+ '.bundle/', 'lib/bundler/man/', '*.gem', '.ruby-version',
91
+
92
+ // --- PHP ---
93
+ 'vendor/', 'composer.phar', '*.phar', '.phpunit.result.cache',
94
+
95
+ // --- Swift ---
96
+ '.swiftpm',
97
+
98
+ // --- Dart / Flutter ---
99
+ '.dart_tool/', '.pub-cache/', '.pub/',
100
+
101
+ // --- Terraform ---
102
+ '.terraform/', '*.tfstate', '.terraform.lock.hcl',
103
+
104
+ // --- Serverless ---
105
+ '.serverless/', '.aws-sam/', '.elasticbeanstalk/',
106
+
107
+ // --- Virtualization ---
108
+ '.vagrant/',
109
+
110
+ // --- Databases ---
111
+ '*.sqlite', '*.sqlite3', '*.db', '*.db3', '*.s3db',
112
+ '*.db-journal', '*.mdb', '*.accdb',
113
+ 'dump.rdb',
114
+
115
+ // --- Lock Files (Noise reduction) ---
116
+ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb',
117
+ 'Cargo.lock', 'Gemfile.lock', 'composer.lock', 'poetry.lock', 'Pipfile.lock',
118
+ 'go.sum', 'mix.lock', 'pubspec.lock', 'flake.lock'
119
+ ];
@@ -0,0 +1,15 @@
1
+ import { resolve } from 'node:path';
2
+
3
+ export const ConfigLoader = {
4
+ load: async (io) => {
5
+ try {
6
+ const path = resolve(io.cwd(), '.catport.json');
7
+ const content = await io.readText(path);
8
+ return JSON.parse(content);
9
+ } catch {
10
+ // Fail silently if config file is missing or invalid,
11
+ // relying on defaults handled by ArgParser.
12
+ return {};
13
+ }
14
+ }
15
+ };