Function bodies 337 total
OutputFormattingStage.buildTreeStructure method · javascript · L161-L191 (31 LOC)src/pipeline/stages/OutputFormattingStage.js
buildTreeStructure(files) {
const tree = {};
for (const file of files) {
if (file === null) continue;
const parts = file.path.split('/');
let current = tree;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (i === parts.length - 1) {
// It's a file
current[part] = {
isFile: true,
size: file.size,
content: file.content,
};
} else {
// It's a directory
if (!current[part]) {
current[part] = {};
}
current = current[part];
}
}
}
return tree;
}OutputFormattingStage.renderTree method · javascript · L193-L224 (32 LOC)src/pipeline/stages/OutputFormattingStage.js
renderTree(node, lines, prefix, _isLast, showSizes = true) {
const entries = Object.entries(node).sort(([a], [b]) => {
// Directories first, then files
const aIsFile = node[a].isFile;
const bIsFile = node[b].isFile;
if (aIsFile && !bIsFile) return 1;
if (!aIsFile && bIsFile) return -1;
return a.localeCompare(b);
});
entries.forEach(([name, value], index) => {
const isLastEntry = index === entries.length - 1;
const connector = isLastEntry
? this.config.get('copytree.treeConnectors.last', '└── ')
: this.config.get('copytree.treeConnectors.middle', '├── ');
if (value.isFile) {
const sizeStr = showSizes ? ` (${this.formatBytes(value.size)})` : '';
lines.push(`${prefix}${connector}${name}${sizeStr}`);
} else {
lines.push(`${prefix}${connector}${name}/`);
const extension = isLastEntry
? this.config.get('copytree.treeConnectors.empty', ' ')
: thOutputFormattingStage.addLineNumbersToContent method · javascript · L226-L239 (14 LOC)src/pipeline/stages/OutputFormattingStage.js
addLineNumbersToContent(content) {
if (!content) return content;
const lines = content.split('\n');
return lines
.map((line, index) => {
const lineNumber = (index + 1).toString();
const formatted = this.lineNumberFormat
.replace('%d', lineNumber)
.replace('%4d', lineNumber.padStart(4));
return formatted + line;
})
.join('\n');
}OutputFormattingStage.generateDirectoryStructure method · javascript · L247-L259 (13 LOC)src/pipeline/stages/OutputFormattingStage.js
generateDirectoryStructure(files) {
const validFiles = files.filter((f) => f !== null);
if (validFiles.length === 0) return '';
// Build tree structure
const tree = this.buildTreeStructure(validFiles);
// Render tree to string (reuse renderTree with showSizes=false)
const lines = [];
this.renderTree(tree, lines, '', true, false);
return lines.join('\n');
}ProfileFilterStage.process method · javascript · L11-L66 (56 LOC)src/pipeline/stages/ProfileFilterStage.js
async process(input) {
this.log('Applying filters', 'debug');
const startTime = Date.now();
const originalCount = input.files.length;
// Filter files
const filteredFiles = input.files.filter((file) => {
// Always include files marked as force-include (highest priority)
if (file.alwaysInclude) {
return true;
}
// Check if file should be always included
if (this.filter.length > 0) {
let matched = false;
for (const pattern of this.filter) {
if (minimatch(file.path, pattern, { dot: true, nocase: process.platform === 'win32' })) {
matched = true;
break;
}
}
// If filter patterns exist but file doesn't match any, exclude it
if (!matched) {
this.log(`Excluding ${file.path} (no filter match)`, 'debug');
return false;
}
}
// Check exclusion patterns
for (const pattern of this.exclude) {
if (minimSecretsGuardStage.constructor method · javascript · L41-L55 (15 LOC)src/pipeline/stages/SecretsGuardStage.js
constructor(options = {}) {
super(options);
this.enabled = options.enabled ?? this.config.get('secretsGuard.enabled', true);
this.excludeGlobs =
options.excludeGlobs || this.config.get('secretsGuard.exclude', SECRET_FILE_PATTERNS);
this.redactInline = options.redactInline ?? this.config.get('secretsGuard.redactInline', true);
this.redactionMode =
options.redactionMode || this.config.get('secretsGuard.redactionMode', 'typed');
this.maxFileBytes =
options.maxFileBytes || this.config.get('secretsGuard.maxFileBytes', 5_000_000);
this.failOnSecrets =
options.failOnSecrets ?? this.config.get('secretsGuard.failOnSecrets', false);
this.gitleaks = new GitleaksAdapter(options.gitleaks || {});
this.useGitleaks = false;
}SecretsGuardStage.onInit method · javascript · L57-L67 (11 LOC)src/pipeline/stages/SecretsGuardStage.js
async onInit() {
if (!this.enabled) return;
this.useGitleaks = await this.gitleaks.isAvailable();
if (this.useGitleaks) {
const version = await this.gitleaks.getVersion();
this.log(`Secrets Guard: using Gitleaks ${version || 'unknown'}`, 'info');
} else {
this.log('Secrets Guard: using basic regex scanning (Gitleaks not found)', 'info');
}
}Open data scored by Repobility · https://repobility.com
SecretsGuardStage.process method · javascript · L69-L156 (88 LOC)src/pipeline/stages/SecretsGuardStage.js
async process(input) {
if (!this.enabled) {
return input;
}
const files = input.files || [];
const processedFiles = [];
const findings = [];
let redactionCount = 0;
for (const file of files) {
if (!file) {
processedFiles.push(file);
continue;
}
const filePath = file.relativePath || file.path || '';
if (this._isExcluded(filePath)) {
this.log(`Excluding secret-prone file: ${filePath}`, 'debug');
processedFiles.push(null);
continue;
}
if (!file.content) {
processedFiles.push(file);
continue;
}
if (Buffer.byteLength(file.content, 'utf8') > this.maxFileBytes) {
this.log(`Skipping secret scan for ${filePath} (too large)`, 'debug');
processedFiles.push(file);
continue;
}
let fileFindings = [];
if (this.useGitleaks) {
try {
fileFindings = await this.gitleaks.scanString(file.content, filePaSecretsGuardStage._basicScan method · javascript · L164-L183 (20 LOC)src/pipeline/stages/SecretsGuardStage.js
_basicScan(file) {
const results = [];
for (const pattern of BASIC_PATTERNS) {
const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
let match;
while ((match = regex.exec(file.content)) !== null) {
const { line, column } = this._positionFromIndex(file.content, match.index);
results.push({
RuleID: pattern.id,
StartLine: line,
EndLine: line,
StartColumn: column,
EndColumn: column + match[0].length,
Match: match[0],
File: file.relativePath || file.path,
});
}
}
return results;
}SecretsGuardStage._positionFromIndex method · javascript · L185-L191 (7 LOC)src/pipeline/stages/SecretsGuardStage.js
_positionFromIndex(content, index) {
const snippet = content.slice(0, index);
const lines = snippet.split('\n');
const line = lines.length;
const column = (lines[lines.length - 1] || '').length + 1;
return { line, column };
}SortFilesStage.constructor method · javascript · L8-L21 (14 LOC)src/pipeline/stages/SortFilesStage.js
constructor(sortBy = 'path', order = 'asc') {
// Handle options object from Pipeline (new architecture)
if (typeof sortBy === 'object' && sortBy !== null) {
const options = sortBy;
super(options);
this.sortBy = options.sortBy || 'path';
this.order = options.order || 'asc';
} else {
// Handle individual parameters (legacy)
super();
this.sortBy = sortBy; // 'path', 'size', 'modified', 'name', 'extension'
this.order = order; // 'asc' or 'desc'
}
}SortFilesStage.process method · javascript · L26-L78 (53 LOC)src/pipeline/stages/SortFilesStage.js
async process(input) {
const { files } = input;
if (!files || files.length === 0) {
return input;
}
const startTime = Date.now();
this.log(`Sorting ${files.length} files by ${this.sortBy} (${this.order})`, 'info');
// Create a copy to avoid mutating the original array
const sorted = [...files].sort((a, b) => {
let compareValue = 0;
switch (this.sortBy) {
case 'size':
compareValue = this.compareBySize(a, b);
break;
case 'modified':
compareValue = this.compareByModified(a, b);
break;
case 'name':
compareValue = this.compareByName(a, b);
break;
case 'extension':
compareValue = this.compareByExtension(a, b);
break;
case 'depth':
compareValue = this.compareByDepth(a, b);
break;
case 'path':
default:
compareValue = this.compareByPath(a, b);
break;
}
SortFilesStage.compareByPath method · javascript · L92-L99 (8 LOC)src/pipeline/stages/SortFilesStage.js
compareByPath(a, b) {
const pathA = a.relativePath || a.path || '';
const pathB = b.relativePath || b.path || '';
return pathA.localeCompare(pathB, undefined, {
numeric: true,
sensitivity: 'base',
});
}SortFilesStage.compareBySize method · javascript · L104-L114 (11 LOC)src/pipeline/stages/SortFilesStage.js
compareBySize(a, b) {
const sizeA = a.stats?.size || 0;
const sizeB = b.stats?.size || 0;
if (sizeA === sizeB) {
// Secondary sort by path if sizes are equal
return this.compareByPath(a, b);
}
return sizeA - sizeB;
}SortFilesStage.compareByModified method · javascript · L119-L129 (11 LOC)src/pipeline/stages/SortFilesStage.js
compareByModified(a, b) {
const timeA = a.stats?.mtime ? new Date(a.stats.mtime).getTime() : 0;
const timeB = b.stats?.mtime ? new Date(b.stats.mtime).getTime() : 0;
if (timeA === timeB) {
// Secondary sort by path if times are equal
return this.compareByPath(a, b);
}
return timeA - timeB;
}Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
SortFilesStage.compareByName method · javascript · L134-L142 (9 LOC)src/pipeline/stages/SortFilesStage.js
compareByName(a, b) {
const nameA = path.basename(a.relativePath || a.path || '');
const nameB = path.basename(b.relativePath || b.path || '');
return nameA.localeCompare(nameB, undefined, {
numeric: true,
sensitivity: 'base',
});
}SortFilesStage.compareByExtension method · javascript · L147-L157 (11 LOC)src/pipeline/stages/SortFilesStage.js
compareByExtension(a, b) {
const extA = path.extname(a.relativePath || a.path || '').toLowerCase();
const extB = path.extname(b.relativePath || b.path || '').toLowerCase();
if (extA === extB) {
// Secondary sort by path if extensions are equal
return this.compareByPath(a, b);
}
return extA.localeCompare(extB);
}SortFilesStage.compareByDepth method · javascript · L162-L172 (11 LOC)src/pipeline/stages/SortFilesStage.js
compareByDepth(a, b) {
const depthA = (a.relativePath || a.path || '').split('/').length;
const depthB = (b.relativePath || b.path || '').split('/').length;
if (depthA === depthB) {
// Secondary sort by path if depths are equal
return this.compareByPath(a, b);
}
return depthA - depthB;
}SortFilesStage.validate method · javascript · L177-L201 (25 LOC)src/pipeline/stages/SortFilesStage.js
validate(input) {
if (!input || typeof input !== 'object') {
throw new Error('Input must be an object');
}
if (!Array.isArray(input.files)) {
throw new Error('Input must have a files array');
}
const validSortOptions = ['path', 'size', 'modified', 'name', 'extension', 'depth'];
if (!validSortOptions.includes(this.sortBy)) {
throw new Error(
`Invalid sortBy option: ${this.sortBy}. Must be one of: ${validSortOptions.join(', ')}`,
);
}
const validOrderOptions = ['asc', 'desc'];
if (!validOrderOptions.includes(this.order)) {
throw new Error(
`Invalid order option: ${this.order}. Must be one of: ${validOrderOptions.join(', ')}`,
);
}
return true;
}StreamingOutputStage.constructor method · javascript · L20-L31 (12 LOC)src/pipeline/stages/StreamingOutputStage.js
constructor(options = {}) {
super(options);
const raw = (options.format || 'xml').toString().toLowerCase();
this.format = raw === 'md' ? 'markdown' : raw;
this.outputStream = options.outputStream || process.stdout;
this.addLineNumbers =
options.addLineNumbers ??
options.withLineNumbers ??
this.config.get('copytree.addLineNumbers', false);
this.lineNumberFormat = this.config.get('copytree.lineNumberFormat', '%4d: ');
this.prettyPrint = options.prettyPrint ?? true;
}StreamingOutputStage.process method · javascript · L33-L74 (42 LOC)src/pipeline/stages/StreamingOutputStage.js
async process(input) {
this.log(`Streaming output as ${this.format}`, 'debug');
const startTime = Date.now();
// Create transform stream
const transformStream = this.createTransformStream(input);
// For file outputs, wait until the destination stream flushes to disk.
const shouldWaitForOutputStream =
this.outputStream &&
this.outputStream !== process.stdout &&
this.outputStream !== process.stderr;
const outputStreamFinished = shouldWaitForOutputStream
? new Promise((resolve, reject) => {
this.outputStream.once('finish', resolve);
this.outputStream.once('error', reject);
})
: Promise.resolve();
// Connect to output stream
transformStream.pipe(this.outputStream);
// Process files through stream
await this.streamFiles(input, transformStream);
// Wait for stream to finish
await new Promise((resolve, reject) => {
transformStream.on('finish', resolve);
transformStrStreamingOutputStage.createTransformStream method · javascript · L76-L92 (17 LOC)src/pipeline/stages/StreamingOutputStage.js
createTransformStream(input) {
if (this.format === 'xml') {
return this.createXMLStream(input);
} else if (this.format === 'json') {
return this.createJSONStream(input);
} else if (this.format === 'tree') {
return this.createTreeStream(input);
} else if (this.format === 'markdown') {
return this.createMarkdownStream(input);
} else if (this.format === 'ndjson') {
return this.createNDJSONStream(input);
} else if (this.format === 'sarif') {
return this.createSARIFStream(input);
}
throw new Error(`Unknown streaming format: ${this.format}`);
}StreamingOutputStage.createMarkdownStream method · javascript · L94-L243 (150 LOC)src/pipeline/stages/StreamingOutputStage.js
createMarkdownStream(input) {
const stream = new Transform({
writableObjectMode: true,
transform: (chunk, _encoding, callback) => callback(null, chunk),
});
const files = input.files || [];
const nonNullFiles = files.filter((f) => f !== null);
const fileCount = nonNullFiles.length;
const totalSize = this.calculateTotalSize(nonNullFiles);
const includeGitStatus = !!input.options?.withGitStatus;
const includeLineNumbers = !!(this.addLineNumbers || input.options?.withLineNumbers);
const onlyTree = !!input.options?.onlyTree;
const charLimitApplied = !!(
input.options?.charLimit ||
input.stats?.truncatedFiles > 0 ||
nonNullFiles.some((f) => f?.truncated)
);
// Header and front matter
stream.write('---\n');
stream.write('format: copytree-md@1\n');
stream.write('tool: copytree\n');
stream.write(`generated: ${escapeYamlScalar(new Date().toISOString())}\n`);
stream.write(`base_path: ${escapeYamlWant this analysis on your repo? https://repobility.com/scan/
StreamingOutputStage.createXMLStream method · javascript · L245-L340 (96 LOC)src/pipeline/stages/StreamingOutputStage.js
createXMLStream(input) {
const stream = new Transform({
writableObjectMode: true,
transform: (chunk, encoding, callback) => {
callback(null, chunk);
},
});
// Write XML header and metadata
const header = '<?xml version="1.0" encoding="UTF-8"?>\n';
const rootStart = `<ct:directory xmlns:ct="urn:copytree" path="${input.basePath}">\n`;
stream.write(header);
stream.write(rootStart);
// Write metadata
stream.write(' <ct:metadata>\n');
stream.write(` <ct:generated>${new Date().toISOString()}</ct:generated>\n`);
stream.write(` <ct:fileCount>${input.files.length}</ct:fileCount>\n`);
stream.write(` <ct:totalSize>${this.calculateTotalSize(input.files)}</ct:totalSize>\n`);
if (input.profile) {
stream.write(` <ct:profile>${input.profile.name || 'default'}</ct:profile>\n`);
}
if (input.gitMetadata) {
stream.write(' <ct:git>\n');
if (input.gitMetadata.branch) {
streaStreamingOutputStage.createJSONStream method · javascript · L342-L411 (70 LOC)src/pipeline/stages/StreamingOutputStage.js
createJSONStream(input) {
const stream = new Transform({
writableObjectMode: true,
transform: (chunk, encoding, callback) => {
callback(null, chunk);
},
});
let isFirst = true;
// Write JSON header
stream.write('{\n');
stream.write(` "directory": ${JSON.stringify(input.basePath)},\n`);
stream.write(' "metadata": {\n');
stream.write(` "generated": "${new Date().toISOString()}",\n`);
stream.write(` "fileCount": ${input.files.length},\n`);
stream.write(` "totalSize": ${this.calculateTotalSize(input.files)}`);
if (input.profile) {
stream.write(',\n');
stream.write(` "profile": ${JSON.stringify(input.profile.name || 'default')}`);
}
stream.write('\n },\n');
stream.write(' "files": [\n');
// Transform for individual files
stream._transform = (file, encoding, callback) => {
if (!file || file === null) {
callback();
return;
}
let json =StreamingOutputStage.createTreeStream method · javascript · L413-L450 (38 LOC)src/pipeline/stages/StreamingOutputStage.js
createTreeStream(input) {
const stream = new Transform({
writableObjectMode: true,
transform: (chunk, encoding, callback) => {
callback(null, chunk);
},
});
// For tree format, we need to collect all files first
// So we'll buffer them and output at the end
const files = [];
stream._transform = (file, encoding, callback) => {
if (file && file !== null) {
files.push(file);
}
callback();
};
// Add _final handler to output the tree
stream._final = (callback) => {
// Build and render tree
const lines = [];
lines.push(input.basePath);
lines.push('');
const tree = this.buildTreeStructure(files);
this.renderTree(tree, lines, '', true);
lines.push('');
lines.push(`${files.length} files, ${this.formatBytes(this.calculateTotalSize(files))}`);
stream.push(lines.join('\n') + '\n');
callback();
};
return stream;
}StreamingOutputStage.createNDJSONStream method · javascript · L452-L583 (132 LOC)src/pipeline/stages/StreamingOutputStage.js
createNDJSONStream(input) {
const files = input.files || [];
const totalSize = this.calculateTotalSize(files);
const profileName = input.profile?.name || 'default';
let metadataWritten = false;
const stream = new Transform({
writableObjectMode: true,
transform: (chunk, _encoding, callback) => callback(null, chunk),
});
stream._transform = (file, _encoding, callback) => {
// Write metadata as first line if not yet written
if (!metadataWritten) {
metadataWritten = true;
const metadata = {
type: 'metadata',
directory: input.basePath,
generated: new Date().toISOString(),
fileCount: files.length,
totalSize,
profile: profileName,
};
if (input.gitMetadata) {
metadata.git = {
branch: input.gitMetadata.branch || null,
lastCommit: input.gitMetadata.lastCommit
? {
hash: input.gitMetadStreamingOutputStage.createSARIFStream method · javascript · L585-L729 (145 LOC)src/pipeline/stages/StreamingOutputStage.js
createSARIFStream(input) {
const stream = new Transform({
writableObjectMode: true,
transform: (chunk, _encoding, callback) => callback(null, chunk),
});
// SARIF requires all data before outputting, so we buffer files
const files = [];
stream._transform = (file, _encoding, callback) => {
if (file && file !== null) {
files.push(file);
}
callback();
};
// Add _final handler to output complete SARIF document
stream._final = (callback) => {
const toolName = 'CopyTree';
const toolVersion = input.version || '0.0.0';
const informationUri = 'https://copytree.dev';
const results = files.map((file) => {
const totalLines =
typeof file.content === 'string' && !file.isBinary ? file.content.split('\n').length : 0;
const result = {
ruleId: 'file-discovered',
level: 'note',
message: { text: `File discovered: ${file.path}` },
locations: StreamingOutputStage.streamFiles method · javascript · L731-L750 (20 LOC)src/pipeline/stages/StreamingOutputStage.js
async streamFiles(input, transformStream) {
// Process files one at a time to manage memory
let processed = 0;
for (const file of input.files) {
if (file !== null) {
transformStream.write(file);
// Small delay to prevent overwhelming the stream
if (processed % 100 === 0) {
await new Promise((resolve) => setTimeout(resolve, 0));
}
processed++;
}
}
// Signal end of data
transformStream.end();
}StreamingOutputStage.addLineNumbersToContent method · javascript · L754-L767 (14 LOC)src/pipeline/stages/StreamingOutputStage.js
addLineNumbersToContent(content) {
if (!content) return content;
const lines = content.split('\n');
return lines
.map((line, index) => {
const lineNumber = (index + 1).toString();
const formatted = this.lineNumberFormat
.replace('%d', lineNumber)
.replace('%4d', lineNumber.padStart(4));
return formatted + line;
})
.join('\n');
}StreamingOutputStage.buildTreeStructure method · javascript · L775-L802 (28 LOC)src/pipeline/stages/StreamingOutputStage.js
buildTreeStructure(files) {
const tree = {};
for (const file of files) {
if (file === null) continue;
const parts = file.path.split('/');
let current = tree;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (i === parts.length - 1) {
current[part] = {
isFile: true,
size: file.size,
};
} else {
if (!current[part]) {
current[part] = {};
}
current = current[part];
}
}
}
return tree;
}Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
StreamingOutputStage.renderTree method · javascript · L804-L828 (25 LOC)src/pipeline/stages/StreamingOutputStage.js
renderTree(node, lines, prefix, _isLast) {
const entries = Object.entries(node).sort(([a], [b]) => {
const aIsFile = node[a].isFile;
const bIsFile = node[b].isFile;
if (aIsFile && !bIsFile) return 1;
if (!aIsFile && bIsFile) return -1;
return a.localeCompare(b);
});
entries.forEach(([name, value], index) => {
const isLastEntry = index === entries.length - 1;
const connector = isLastEntry ? '└── ' : '├── ';
if (value.isFile) {
lines.push(`${prefix}${connector}${name} (${this.formatBytes(value.size)})`);
} else {
lines.push(`${prefix}${connector}${name}/`);
const extension = isLastEntry ? ' ' : '│ ';
this.renderTree(value, lines, prefix + extension, false);
}
});
}TransformStage.constructor method · javascript · L10-L26 (17 LOC)src/pipeline/stages/TransformStage.js
constructor(options = {}) {
super(options);
this.registry = options.registry;
this.transformerConfig = options.transformers || {};
this.maxConcurrency = options.maxConcurrency || appConfig.maxConcurrency || 5;
this.noCache = options.noCache;
// Initialize cache for transformations (disabled if noCache is true)
// Only create cache if not disabled
this.cache = options.noCache
? null
: options.cache ||
CacheService.create('transformations', {
enabled: options.cacheEnabled ?? true,
defaultTtl: 86400, // 24 hours
});
}TransformStage.processWithDisplay method · javascript · L34-L223 (190 LOC)src/pipeline/stages/TransformStage.js
async processWithDisplay(input) {
const { files } = input;
const startTime = Date.now();
let transformCount = 0;
let errorCount = 0;
// logger is already imported at the top
// Import p-limit dynamically (v7+ uses default export)
const { default: pLimit } = await import('p-limit');
const limit = pLimit(this.maxConcurrency);
// First pass: identify files that need transformation
const filesToTransform = [];
const cachedResults = new Map();
let hasHeavyTransformers = false;
for (const file of files) {
const transformer = this.getTransformerForFile(file);
if (!transformer) continue;
const transformerName = transformer.constructor.name;
const cacheKey = generateTransformCacheKey(
file,
transformerName,
this.transformerConfig[transformerName],
);
// Only check cache for heavy transformers
if (transformer.isHeavy && this.cache) {
const cached = await this.cacheTransformStage.limit method · javascript · L89-L152 (64 LOC)src/pipeline/stages/TransformStage.js
limit(async () => {
// Check if this file is cached
if (cachedResults.has(file)) {
return cachedResults.get(file);
}
// Find the transform info for this file using O(1) Map lookup
const transformInfo = transformMap.get(file);
if (!transformInfo) {
return file; // No transformation needed
}
const { transformer, cacheKey } = transformInfo;
const filename = path.basename(file.path);
try {
// Update display if showing multi-line
if (showMultiLine) {
activeFiles.push(filename);
updateTransformDisplay();
}
// Perform transformation
const transformed = await transformer.transform(file);
if (transformed) {
if (transformed.transformed) {
transformCount++;
}
// Cache the result only for heavy transformers
if (transformer.isHeavy && this.cacheTransformStage.handleError method · javascript · L235-L258 (24 LOC)src/pipeline/stages/TransformStage.js
async handleError(error, input) {
this.log(`Transform stage encountered error: ${error.message}`, 'warn');
// Check if this is a recoverable error type
const isRecoverable = this._isRecoverableError(error);
if (isRecoverable && input && input.files) {
this.log('Attempting recovery by skipping transformation for affected files', 'info');
// Return input with transformation skipped but files preserved
return {
...input,
stats: {
...input.stats,
transformedCount: 0,
transformErrors: input.files.length,
recoveredFromError: true,
},
};
}
// If not recoverable, rethrow the error
throw error;
}TransformStage._isRecoverableError method · javascript · L264-L276 (13 LOC)src/pipeline/stages/TransformStage.js
_isRecoverableError(error) {
// Recoverable errors include transformation failures, network issues, etc.
const recoverableTypes = [
'TransformError',
'ENOTFOUND', // Network errors
'ETIMEDOUT', // Timeout errors
'ECONNRESET', // Connection reset
];
return recoverableTypes.some(
(type) => error.name === type || error.code === type || error.message.includes(type),
);
}TransformStage.getTransformerForFile method · javascript · L278-L307 (30 LOC)src/pipeline/stages/TransformStage.js
getTransformerForFile(file) {
if (!this.registry) {
return null;
}
try {
const transformer = this.registry.getForFile(file);
// Check if transformer is enabled in config
const transformerName = transformer.constructor.name;
const config =
this.transformerConfig[transformerName] ||
this.transformerConfig[transformerName.toLowerCase()] ||
this.transformerConfig[transformerName.replace(/Transformer$/, '').toLowerCase()];
if (config && config.enabled === false) {
return null;
}
// Set noCache option on transformer if specified
if (this.noCache) {
transformer.cacheEnabled = false;
}
return transformer;
} catch (_error) {
// No transformer found, return null
return null;
}
}CacheService.constructor method · javascript · L13-L57 (45 LOC)src/services/CacheService.js
constructor(options = {}) {
this.enabled = options.enabled ?? config().get('cache.enabled', true);
this.driver = options.driver || config().get('cache.driver', 'file');
this.prefix = options.prefix || config().get('cache.prefix', 'copytree_');
this.defaultTtl = options.defaultTtl || config().get('cache.defaultTtl', 3600);
// File cache settings
let configPath = options.cachePath || config().get('cache.file.path');
// If no path from config, use home directory cache
if (!configPath) {
configPath = path.join(os.homedir(), '.copytree', 'cache');
}
// Ensure cache path is absolute
this.cachePath = path.isAbsolute(configPath) ? configPath : path.resolve(configPath);
this.extension = config().get('cache.file.extension', '.cache');
this.gcProbability = config().get('cache.file.gcProbability', 0.01);
this.maxCacheAge =
options.maxCacheAge || config().get('cache.file.maxAge', 7 * 24 * 60 * 60 * 1000); // 7 days in ms
Open data scored by Repobility · https://repobility.com
CacheService.generateKey method · javascript · L65-L75 (11 LOC)src/services/CacheService.js
generateKey(key, data = null) {
let fullKey = this.prefix + key;
if (data) {
const hash = crypto.createHash('sha256');
hash.update(JSON.stringify(data, Object.keys(data).sort()));
fullKey += '_' + hash.digest('hex').substring(0, 8);
}
return fullKey;
}CacheService.get method · javascript · L83-L127 (45 LOC)src/services/CacheService.js
async get(key, defaultValue = null) {
if (!this.enabled) {
return defaultValue;
}
const fullKey = this.generateKey(key);
try {
// Check memory cache first
if (this.memoryCache.has(fullKey)) {
const item = this.memoryCache.get(fullKey);
if (item.expires > Date.now()) {
this.logger.debug(`Cache hit (memory): ${key}`);
return item.value;
} else {
this.memoryCache.delete(fullKey);
}
}
// Check file cache
if (this.driver === 'file') {
const filePath = this.getCacheFilePath(fullKey);
if (await fs.pathExists(filePath)) {
const data = await fs.readJson(filePath);
if (data.expires > Date.now()) {
this.logger.debug(`Cache hit (file): ${key}`);
// Store in memory cache for faster access
this.memoryCache.set(fullKey, data);
return data.value;
} else {
// Expired, remove fileCacheService.set method · javascript · L136-L173 (38 LOC)src/services/CacheService.js
async set(key, value, ttl = null) {
if (!this.enabled) {
return false;
}
const fullKey = this.generateKey(key);
const expires = Date.now() + (ttl || this.defaultTtl) * 1000;
const cacheItem = {
key: fullKey,
value,
expires,
created: Date.now(),
};
try {
// Store in memory cache
this.memoryCache.set(fullKey, cacheItem);
// Store in file cache
if (this.driver === 'file') {
const filePath = this.getCacheFilePath(fullKey);
await fs.ensureDir(path.dirname(filePath));
await fs.writeJson(filePath, cacheItem, { spaces: 2 });
// Garbage collection
if (Math.random() < this.gcProbability) {
this.runGarbageCollection().catch(() => {});
}
}
this.logger.debug(`Cache set: ${key} (TTL: ${ttl || this.defaultTtl}s)`);
return true;
} catch (error) {
this.logger.error(`Cache set error: ${error.message}`);
return false;
}
CacheService.forget method · javascript · L190-L215 (26 LOC)src/services/CacheService.js
async forget(key) {
if (!this.enabled) {
return false;
}
const fullKey = this.generateKey(key);
try {
// Remove from memory cache
this.memoryCache.delete(fullKey);
// Remove from file cache
if (this.driver === 'file') {
const filePath = this.getCacheFilePath(fullKey);
if (await fs.pathExists(filePath)) {
await fs.remove(filePath);
}
}
this.logger.debug(`Cache forget: ${key}`);
return true;
} catch (error) {
this.logger.error(`Cache forget error: ${error.message}`);
return false;
}
}CacheService.clear method · javascript · L222-L267 (46 LOC)src/services/CacheService.js
async clear(pattern = null) {
if (!this.enabled) {
return 0;
}
let cleared = 0;
try {
// Clear memory cache
if (pattern) {
const regex = new RegExp(pattern);
for (const key of this.memoryCache.keys()) {
if (regex.test(key)) {
this.memoryCache.delete(key);
cleared++;
}
}
} else {
cleared = this.memoryCache.size;
this.memoryCache.clear();
}
// Clear file cache
if (this.driver === 'file' && (await fs.pathExists(this.cachePath))) {
const files = await fs.readdir(this.cachePath);
for (const file of files) {
if (!file.endsWith(this.extension)) continue;
if (pattern) {
const regex = new RegExp(pattern);
if (!regex.test(file)) continue;
}
await fs.remove(path.join(this.cachePath, file));
cleared++;
}
}
this.logger.info(`Cache cleared: ${CacheService.runGarbageCollection method · javascript · L274-L346 (73 LOC)src/services/CacheService.js
async runGarbageCollection(_options = {}) {
if (!this.enabled || this.driver !== 'file') {
return 0;
}
let removed = 0;
let totalSize = 0;
try {
this.logger.debug('Running cache garbage collection...');
const files = await fs.readdir(this.cachePath);
const now = Date.now();
for (const file of files) {
if (!file.endsWith(this.extension)) continue;
const filePath = path.join(this.cachePath, file);
try {
const stats = await fs.stat(filePath);
const data = await fs.readJson(filePath);
// Remove if expired
if (data.expires < now) {
await fs.remove(filePath);
removed++;
totalSize += stats.size;
continue;
}
// Remove if older than max cache age
const fileAge = now - stats.mtimeMs;
if (fileAge > this.maxCacheAge) {
await fs.remove(filePath);
removed++;
CacheService.create method · javascript · L364-L369 (6 LOC)src/services/CacheService.js
static create(namespace, options = {}) {
return new CacheService({
prefix: `copytree_${namespace}_`,
...options,
});
}GitHubUrlHandler.parseUrl method · javascript · L29-L41 (13 LOC)src/services/GitHubUrlHandler.js
parseUrl() {
const pattern = /^https:\/\/github\.com\/([^/]+\/[^/]+)(?:\/tree\/([^/]+))?(?:\/(.*?))?$/;
const matches = this.url.match(pattern);
if (!matches) {
throw new CommandError('Invalid GitHub URL format', 'github-url');
}
this.repoUrl = `https://github.com/${matches[1]}.git`;
this.branch = matches[2] || '';
this.subPath = matches[3] || '';
this.updateCacheKey();
}Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
GitHubUrlHandler.updateCacheKey method · javascript · L46-L52 (7 LOC)src/services/GitHubUrlHandler.js
updateCacheKey() {
const identifier = this.repoUrl.replace('https://github.com/', '').replace('.git', '');
this.cacheKey = crypto
.createHash('md5')
.update(`${identifier}/${this.branch || 'default'}`)
.digest('hex');
}GitHubUrlHandler.setupCacheDirectory method · javascript · L57-L70 (14 LOC)src/services/GitHubUrlHandler.js
setupCacheDirectory() {
this.cacheDir = path.join(os.homedir(), '.copytree', 'repos');
try {
fs.ensureDirSync(this.cacheDir);
} catch (error) {
throw new CommandError(
`Failed to create repository cache directory at ${this.cacheDir}: ${error.message}. ` +
'Ensure HOME is writable or set a custom cache path via configuration.',
'GitHubUrlHandler',
{ originalError: error },
);
}
this.repoDir = path.join(this.cacheDir, this.cacheKey);
}GitHubUrlHandler.getFiles method · javascript · L75-L105 (31 LOC)src/services/GitHubUrlHandler.js
async getFiles() {
try {
// Check if repository already exists with .git folder
if (fs.existsSync(this.repoDir) && fs.existsSync(path.join(this.repoDir, '.git'))) {
await this.updateRepository();
} else {
// Clean up any partial directory if it exists without .git
if (fs.existsSync(this.repoDir)) {
logger.warn('Found incomplete repository cache, removing...', {
cacheDir: this.repoDir,
});
fs.rmSync(this.repoDir, { recursive: true, force: true });
}
await this.cloneRepository();
}
const targetPath = this.subPath ? path.join(this.repoDir, this.subPath) : this.repoDir;
if (!fs.existsSync(targetPath)) {
throw new CommandError(`Path '${this.subPath}' not found in repository`, 'github-path');
}
return targetPath;
} catch (error) {
logger.error('Failed to get files from GitHub', {
url: this.url,
error: error.message,