Function bodies 337 total
NDJSONFormatter.stream method · javascript · L37-L42 (6 LOC)src/pipeline/formatters/NDJSONFormatter.js
async stream(input, writer) {
if (!writer || typeof writer !== 'function') {
throw new Error('NDJSONFormatter.stream requires a writer function');
}
await this._produce(input, (line) => writer(line + '\n'));
}NDJSONFormatter._produce method · javascript · L51-L155 (105 LOC)src/pipeline/formatters/NDJSONFormatter.js
async _produce(input, write) {
const files = (input.files || []).filter((f) => f !== null);
const totalSize = this.stage.calculateTotalSize(files);
const profileName = input.profile?.name || 'default';
// Metadata record
const metadata = {
type: 'metadata',
directory: input.basePath,
generated: new Date().toISOString(),
fileCount: files.length,
totalSize,
profile: profileName,
};
// Add git metadata if present
if (input.gitMetadata) {
metadata.git = {
branch: input.gitMetadata.branch || null,
lastCommit: input.gitMetadata.lastCommit
? {
hash: input.gitMetadata.lastCommit.hash,
message: input.gitMetadata.lastCommit.message,
}
: null,
filterType: input.gitMetadata.filterType || null,
hasUncommittedChanges: input.gitMetadata.hasUncommittedChanges || false,
};
}
// Add instructions if present
if (input.iSARIFFormatter.format method · javascript · L27-L101 (75 LOC)src/pipeline/formatters/SARIFFormatter.js
async format(input) {
const toolName = 'CopyTree';
const toolVersion = input.version || '0.0.0';
const informationUri = 'https://copytree.dev';
const files = (input.files || []).filter((f) => f !== null);
const results = files.map((file) => this._fileToResult(file, input));
const sarif = {
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
version: '2.1.0',
runs: [
{
tool: {
driver: {
name: toolName,
version: toolVersion,
informationUri,
rules: [
{
id: 'file-discovered',
name: 'FileDiscovered',
shortDescription: {
text: 'A file was discovered by CopyTree.',
},
fullDescription: {
text: 'CopyTree enumerated this file in the selected scope based on the configured profile and filters.',
},
SARIFFormatter._fileToResult method · javascript · L110-L169 (60 LOC)src/pipeline/formatters/SARIFFormatter.js
_fileToResult(file, input) {
// Calculate line count from content
let totalLines = 0;
if (typeof file.content === 'string' && !this.onlyTree && !file.isBinary) {
totalLines = file.content.split('\n').length;
}
const result = {
ruleId: 'file-discovered',
level: 'note',
message: {
text: `File discovered: ${file.path}`,
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: file.path,
uriBaseId: '%SRCROOT%',
},
},
},
],
properties: {
size: file.size || 0,
modified: file.modified || null,
isBinary: !!file.isBinary,
},
};
// Add region if we have content with line counts
if (totalLines > 0) {
result.locations[0].physicalLocation.region = {
startLine: 1,
endLine: Math.max(1, totalLines),
};
}
// Add optional properties
if (file.encoding)SARIFFormatter._toFileUri method · javascript · L177-L192 (16 LOC)src/pipeline/formatters/SARIFFormatter.js
_toFileUri(fileSystemPath) {
if (!fileSystemPath) {
return undefined;
}
if (fileSystemPath.startsWith('file://')) {
return fileSystemPath;
}
if (path.isAbsolute(fileSystemPath)) {
return pathToFileURL(fileSystemPath).href;
}
// Normalize relative Windows-style paths for SARIF consumers
return fileSystemPath.replace(/\\/g, '/');
}XMLFormatter.escapeCdata method · javascript · L17-L22 (6 LOC)src/pipeline/formatters/XMLFormatter.js
escapeCdata(content) {
// First sanitize to remove invalid XML control characters
const sanitized = sanitizeForXml(content.toString());
// Then escape the ]]> sequence
return sanitized.replaceAll(']]>', ']]]]><![CDATA[>');
}XMLFormatter.format method · javascript · L24-L151 (128 LOC)src/pipeline/formatters/XMLFormatter.js
async format(input) {
const chunks = [];
const generated = new Date().toISOString();
const totalSize = this.stage.calculateTotalSize(input.files);
const fileCount = input.files.filter((f) => f !== null).length;
// Manual XML construction to avoid any escaping
chunks.push('<?xml version="1.0" encoding="UTF-8"?>\n');
chunks.push(`<ct:directory xmlns:ct="urn:copytree" path="${input.basePath}">\n`);
// Add metadata
chunks.push(' <ct:metadata>\n');
chunks.push(` <ct:generated>${generated}</ct:generated>\n`);
chunks.push(` <ct:fileCount>${fileCount}</ct:fileCount>\n`);
chunks.push(` <ct:totalSize>${totalSize}</ct:totalSize>\n`);
if (input.profile) {
chunks.push(` <ct:profile>${input.profile.name || 'default'}</ct:profile>\n`);
}
// Add git metadata if present
if (input.gitMetadata) {
chunks.push(' <ct:git>\n');
if (input.gitMetadata.branch) {
chunks.push(` <ct:branch>${input.gRepobility — the code-quality scanner for AI-generated software · https://repobility.com
Pipeline.constructor method · javascript · L18-L62 (45 LOC)src/pipeline/Pipeline.js
constructor(options = {}) {
super();
this.stages = [];
this.stageInstances = []; // Track instantiated stages for lifecycle hooks
// Store config instance if provided, otherwise will be created during init
this._configInstance = options.config || null;
this.options = {
continueOnError: options.continueOnError, // Will be lazy-loaded from config if not provided
emitProgress: options.emitProgress, // Will be lazy-loaded from config if not provided
parallel: options.parallel ?? false,
maxConcurrency: options.maxConcurrency, // Will be lazy-loaded from config if not provided
...options,
};
this.stats = {
startTime: null,
endTime: null,
stagesCompleted: 0,
stagesFailed: 0,
errors: [],
perStageTimings: {},
perStageMetrics: {},
totalStageTime: 0,
averageStageTime: 0,
};
// Create pipeline context for stages
// Note: config will be populated during initializatiPipeline.through method · javascript · L69-L76 (8 LOC)src/pipeline/Pipeline.js
through(stages) {
if (!Array.isArray(stages)) {
stages = [stages];
}
this.stages.push(...stages);
return this;
}Pipeline._initializeStages method · javascript · L84-L127 (44 LOC)src/pipeline/Pipeline.js
async _initializeStages() {
if (this.stageInstances.length > 0) {
// Already initialized
return;
}
// Create isolated config instance if not provided
if (!this._configInstance) {
this._configInstance = await ConfigManager.create();
}
// Update context with config instance
this.context.config = this._configInstance;
// Instantiate all stages and call onInit hooks
for (let i = 0; i < this.stages.length; i++) {
const Stage = this.stages[i];
let stageInstance;
if (typeof Stage === 'function' && !Stage.prototype) {
// It's a plain function, use it directly
stageInstance = Stage;
} else if (typeof Stage === 'object' && Stage.process) {
// It's already an instance with a process method
stageInstance = Stage;
} else {
// It's a constructor, instantiate it with pipeline reference
stageInstance = new Stage({ ...this.options, pipeline: this });
}
thPipeline.process method · javascript · L134-L196 (63 LOC)src/pipeline/Pipeline.js
async process(input) {
// Initialize stages if not already done (also creates isolated config if needed)
await this._initializeStages();
// Refresh options from config now that we are async and likely fully loaded
// Only override if not explicitly provided in constructor options
// Use the isolated config instance, not the singleton
const cfg = this._configInstance;
if (this.options.continueOnError === undefined) {
this.options.continueOnError = cfg.get('pipeline.continueOnError', false);
}
if (this.options.emitProgress === undefined) {
this.options.emitProgress = cfg.get('pipeline.emitProgress', true);
}
if (this.options.maxConcurrency === undefined) {
this.options.maxConcurrency = cfg.get('app.maxConcurrency', 5);
}
// Update context options as well
this.context.options = this.options;
this.stats.startTime = Date.now();
this.stats.stagesCompleted = 0;
this.stats.stagesFailed = 0;
this.statsPipeline._processSequential method · javascript · L202-L376 (175 LOC)src/pipeline/Pipeline.js
async _processSequential(input) {
let result = input;
for (let i = 0; i < this.stageInstances.length; i++) {
const stageInstance = this.stageInstances[i];
const Stage = this.stages[i]; // For name resolution
const stageName = this._getStageName(Stage, i);
try {
this.emit('stage:start', {
stage: stageName,
index: i,
input: result,
});
const processMethod = stageInstance.process || stageInstance;
if (typeof processMethod !== 'function') {
throw new Error(`Stage ${stageName} does not have a process method`);
}
// Call beforeRun hook if it exists
if (typeof stageInstance.beforeRun === 'function') {
try {
await stageInstance.beforeRun(result);
} catch (hookError) {
this.context.logger.warn(
`Stage ${stageName} beforeRun hook failed: ${hookError.message}`,
);
// Continue processPipeline._processParallel method · javascript · L382-L407 (26 LOC)src/pipeline/Pipeline.js
async _processParallel(input) {
const chunks = Array.isArray(input) ? input : [input];
const results = [];
// Process chunks in batches
for (let i = 0; i < chunks.length; i += this.options.maxConcurrency) {
const batch = chunks.slice(i, i + this.options.maxConcurrency);
const batchResults = await Promise.all(
batch.map(async (chunk, _index) => {
try {
return await this._processSequential(chunk);
} catch (error) {
if (!this.options.continueOnError) {
throw error;
}
return null;
}
}),
);
results.push(...batchResults);
}
return Array.isArray(input) ? results : results[0];
}Pipeline._getStageName method · javascript · L413-L418 (6 LOC)src/pipeline/Pipeline.js
_getStageName(Stage, index) {
if (typeof Stage === 'string') return Stage;
if (Stage.name) return Stage.name;
if (Stage.constructor && Stage.constructor.name) return Stage.constructor.name;
return `Stage${index + 1}`;
}Pipeline.getStats method · javascript · L424-L438 (15 LOC)src/pipeline/Pipeline.js
getStats() {
// Calculate average stage time if we have completed stages
const totalStages = this.stats.stagesCompleted + this.stats.stagesFailed;
if (totalStages > 0 && this.stats.totalStageTime > 0) {
this.stats.averageStageTime = this.stats.totalStageTime / totalStages;
}
return {
...this.stats,
duration: this.stats.endTime
? this.stats.endTime - this.stats.startTime
: Date.now() - this.stats.startTime,
successRate: totalStages > 0 ? this.stats.stagesCompleted / totalStages : 1,
};
}Open data scored by Repobility · https://repobility.com
Pipeline.send method · javascript · L455-L474 (20 LOC)src/pipeline/Pipeline.js
send(passable) {
const self = this;
return {
through(stages) {
self.through(stages);
return {
async then(callback) {
const result = await self.process(passable);
if (callback) {
return callback(result);
}
return result;
},
async thenReturn() {
return await self.process(passable);
},
};
},
};
}Pipeline.through method · javascript · L458-L472 (15 LOC)src/pipeline/Pipeline.js
through(stages) {
self.through(stages);
return {
async then(callback) {
const result = await self.process(passable);
if (callback) {
return callback(result);
}
return result;
},
async thenReturn() {
return await self.process(passable);
},
};
},Pipeline.then method · javascript · L461-L467 (7 LOC)src/pipeline/Pipeline.js
async then(callback) {
const result = await self.process(passable);
if (callback) {
return callback(result);
}
return result;
},Stage.constructor method · javascript · L14-L25 (12 LOC)src/pipeline/Stage.js
constructor(options = {}) {
this.options = options;
// Config is now provided via the pipeline context in onInit()
// For backward compatibility, accept config in options, but prefer context
this._config = options.config || null;
this.name = this.constructor.name;
this.pipeline = options.pipeline; // Reference to parent pipeline for event emission
// Performance optimization: throttle file events
this.fileEventCount = 0;
this.lastFileEventTime = 0;
}Stage.onInit method · javascript · L102-L108 (7 LOC)src/pipeline/Stage.js
async onInit(context) {
// Set config from pipeline context if not already set
if (context?.config && !this._config) {
this._config = context.config;
}
// Default implementation - subclasses can override for custom initialization
}Stage.log method · javascript · L161-L193 (33 LOC)src/pipeline/Stage.js
log(message, level = 'info') {
// Emit event for UI if pipeline is available
if (this.pipeline) {
this.pipeline.emit('stage:log', {
stage: this.name,
message,
level,
timestamp: Date.now(),
});
}
// Route all terminal output through the central logger so that
// --log-level, --log-format, --no-color, and COPYTREE_LOG_LEVEL are obeyed.
const prefixedMessage = `[${this.name}] ${message}`;
switch (level) {
case 'error':
defaultLogger.error(prefixedMessage);
break;
case 'warn':
defaultLogger.warn(prefixedMessage);
break;
// Stage 'info' messages are internal pipeline progress/timing info.
// They were previously only visible in debug mode (app.debug=true), so
// we map them to debug level to preserve that backward-compatible behavior.
// Use --log-level debug (or COPYTREE_LOG_LEVEL=debug) to see them.
case 'info':
defaultLogger.debug(pStage.emitProgress method · javascript · L200-L209 (10 LOC)src/pipeline/Stage.js
emitProgress(progress, message) {
if (this.pipeline) {
this.pipeline.emit('stage:progress', {
stage: this.name,
progress,
message,
timestamp: Date.now(),
});
}
}Stage.emitFileEvent method · javascript · L216-L233 (18 LOC)src/pipeline/Stage.js
emitFileEvent(filePath, action = 'processed') {
if (!this.pipeline) return;
this.fileEventCount++;
const now = Date.now();
// Only emit every 20th file or every 100ms
if (this.fileEventCount % 20 === 0 || now - this.lastFileEventTime > 100) {
this.pipeline.emit('file:batch', {
stage: this.name,
count: this.fileEventCount,
lastFile: filePath,
action,
timestamp: now,
});
this.lastFileEventTime = now;
}
}Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
Stage.getElapsedTime method · javascript · L240-L250 (11 LOC)src/pipeline/Stage.js
getElapsedTime(startTime) {
const elapsed = Date.now() - startTime;
if (elapsed < 1000) {
return `${elapsed}ms`;
} else if (elapsed < 60000) {
return `${(elapsed / 1000).toFixed(1)}s`;
} else {
return `${(elapsed / 60000).toFixed(1)}m`;
}
}Stage.formatBytes method · javascript · L257-L265 (9 LOC)src/pipeline/Stage.js
formatBytes(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}AlwaysIncludeStage.process method · javascript · L19-L59 (41 LOC)src/pipeline/stages/AlwaysIncludeStage.js
async process(input) {
const { files } = input;
if (!this.alwaysPatterns || this.alwaysPatterns.length === 0) {
this.log('No always-include patterns configured', 'debug');
return input;
}
const startTime = Date.now();
let alwaysIncludeCount = 0;
this.log(`Processing ${this.alwaysPatterns.length} always-include pattern(s)`, 'info');
// Mark files that match always patterns
const processedFiles = files.map((file) => {
if (this.matchesAlwaysPatterns(file)) {
alwaysIncludeCount++;
logger.debug('File marked as always-include', {
path: file.path || file.relativePath,
patterns: this.alwaysPatterns,
});
return {
...file,
alwaysInclude: true,
};
}
return file;
});
const elapsed = this.getElapsedTime(startTime);
this.log(`Marked ${alwaysIncludeCount} file(s) as always-include in ${elapsed}`, 'info');
return {
...input,
AlwaysIncludeStage.matchesAlwaysPatterns method · javascript · L64-L94 (31 LOC)src/pipeline/stages/AlwaysIncludeStage.js
matchesAlwaysPatterns(file) {
const filePath = file.path || file.relativePath;
for (const pattern of this.alwaysPatterns) {
// Support both glob patterns and exact matches
if (pattern.includes('*') || pattern.includes('?') || pattern.includes('[')) {
// Glob pattern
if (
minimatch(filePath, pattern, {
matchBase: true,
dot: true,
nocase: process.platform === 'win32',
})
) {
return true;
}
} else {
// Exact match or path contains
if (filePath === pattern || filePath.includes(pattern)) {
return true;
}
// Also check basename for convenience
if (path.basename(filePath) === pattern) {
return true;
}
}
}
return false;
}AlwaysIncludeStage.validate method · javascript · L99-L109 (11 LOC)src/pipeline/stages/AlwaysIncludeStage.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');
}
return true;
}CharLimitStage.process method · javascript · L9-L67 (59 LOC)src/pipeline/stages/CharLimitStage.js
async process(input) {
this.log(`Applying character limit of ${this.limit.toLocaleString()} characters`, 'debug');
const startTime = Date.now();
let totalChars = 0;
const limitedFiles = [];
let truncatedFiles = 0;
let skippedFiles = 0;
for (const file of input.files) {
if (!file || !file.content) {
limitedFiles.push(file);
continue;
}
const contentLength = file.content.length;
if (totalChars + contentLength <= this.limit) {
// File fits entirely
limitedFiles.push(file);
totalChars += contentLength;
} else if (totalChars < this.limit) {
// File partially fits
const remainingChars = this.limit - totalChars;
const truncatedContent = file.content.substring(0, remainingChars);
limitedFiles.push({
...file,
content: truncatedContent + '\n\n... truncated due to character limit ...',
originalLength: contentLength,
truncaDeduplicateFilesStage.process method · javascript · L27-L99 (73 LOC)src/pipeline/stages/DeduplicateFilesStage.js
async process(files, context) {
if (!files || files.length === 0) {
return files;
}
const startTime = Date.now();
const contentHashes = new Map();
const duplicates = [];
const uniqueFiles = [];
this.log(`Checking ${files.length} files for duplicates`, 'info');
for (const file of files) {
// Skip files without content
if (!file.content && file.content !== '') {
uniqueFiles.push(file);
continue;
}
// Calculate content hash
const hash = this.calculateHash(file.content);
if (contentHashes.has(hash)) {
// Found a duplicate
const original = contentHashes.get(hash);
duplicates.push({
file: file.path || file.relativePath,
duplicateOf: original.path || original.relativePath,
size: file.size || file.stats?.size || 0,
});
// Emit deduplication event
if (context && context.emit) {
context.emit('file:deduplicated', DeduplicateFilesStage.calculateHash method · javascript · L104-L117 (14 LOC)src/pipeline/stages/DeduplicateFilesStage.js
calculateHash(content) {
const hash = crypto.createHash(this.hashAlgorithm);
if (Buffer.isBuffer(content)) {
hash.update(content);
} else if (typeof content === 'string') {
hash.update(content, 'utf8');
} else {
// For other types, convert to string
hash.update(String(content), 'utf8');
}
return hash.digest('hex');
}Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
DeduplicateFilesStage.validate method · javascript · L129-L139 (11 LOC)src/pipeline/stages/DeduplicateFilesStage.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');
}
return true;
}FileDiscoveryStage.constructor method · javascript · L23-L42 (20 LOC)src/pipeline/stages/FileDiscoveryStage.js
constructor(options = {}) {
super(options);
this.basePath = options.basePath || process.cwd();
this.patterns = options.patterns || ['**/*'];
this.respectGitignore = options.respectGitignore !== false;
this.forceInclude = options.forceInclude || [];
this.excludes = options.excludes || [];
// Convenience filter options
this.extFilter = options.extFilter || null; // e.g. ['.js', '.ts']
this.maxDepth =
options.maxDepth !== undefined && options.maxDepth !== null ? options.maxDepth : null;
this.minSizeBytes =
options.minSizeBytes !== undefined && options.minSizeBytes !== null
? options.minSizeBytes
: null;
this.maxSizeBytes =
options.maxSizeBytes !== undefined && options.maxSizeBytes !== null
? options.maxSizeBytes
: null;
}FileDiscoveryStage.loadGitignore method · javascript · L290-L314 (25 LOC)src/pipeline/stages/FileDiscoveryStage.js
async loadGitignore() {
const gitignorePath = path.join(this.basePath, '.gitignore');
if (await fs.pathExists(gitignorePath)) {
try {
const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
// Strip BOM if present
const cleaned =
gitignoreContent.charCodeAt(0) === 0xfeff ? gitignoreContent.slice(1) : gitignoreContent;
const ig = ignore().add(cleaned);
this.log('Loaded .gitignore rules', 'debug');
return {
base: this.basePath,
ig,
};
} catch (error) {
this.log(`Error loading .gitignore: ${error.message}`, 'debug');
return null;
}
}
return null;
}FileDiscoveryStage.loadCopytreeInclude method · javascript · L319-L361 (43 LOC)src/pipeline/stages/FileDiscoveryStage.js
async loadCopytreeInclude() {
const copytreeincludePath = path.join(this.basePath, '.copytreeinclude');
if (await fs.pathExists(copytreeincludePath)) {
const copytreeincludeContent = await fs.readFile(copytreeincludePath, 'utf8');
const rawPatterns = copytreeincludeContent
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'));
// Transform patterns following gitignore conventions
const transformedPatterns = rawPatterns.map((pattern) => {
// If pattern already has glob characters, keep it as-is
if (pattern.includes('*') || pattern.includes('?') || pattern.includes('[')) {
return pattern;
}
// If pattern doesn't start with /, it matches anywhere
if (!pattern.startsWith('/')) {
pattern = '**/' + pattern;
} else {
// Remove leading slash for relative matching
pattern = pattern.substring(1);
}
FileLoadingStage.constructor method · javascript · L8-L13 (6 LOC)src/pipeline/stages/FileLoadingStage.js
constructor(options = {}) {
super(options);
this.encoding = options.encoding || 'utf8';
// Config may not be available yet in constructor, but the proxy returns defaults
this.binaryAction = this.config.get('copytree.binaryFileAction', 'placeholder');
}FileLoadingStage.process method · javascript · L15-L29 (15 LOC)src/pipeline/stages/FileLoadingStage.js
async process(input) {
this.log(`Loading content for ${input.files.length} files`, 'debug');
const startTime = Date.now();
const filesWithContent = await Promise.all(
input.files.map((file) => this.loadFileContent(file)),
);
this.log(`Loaded file contents in ${this.getElapsedTime(startTime)}`, 'info');
return {
...input,
files: filesWithContent,
};
}FileLoadingStage.loadFileContent method · javascript · L31-L96 (66 LOC)src/pipeline/stages/FileLoadingStage.js
async loadFileContent(file) {
try {
// 1. Check for Structure Only patterns (Token saving)
const structureOnlyPatterns = this.config.get('copytree.structureOnlyPatterns', []);
const isStructureOnly = structureOnlyPatterns.some((pattern) =>
minimatch(file.path, pattern, { dot: true, nocase: process.platform === 'win32' }),
);
if (isStructureOnly) {
return {
...file,
content: '[Content skipped for AI context optimization]', // Token-efficient placeholder
isBinary: true, // Treat as binary to skip line numbering/processing
binaryCategory: 'structure-only',
};
}
// 2. Use centralized binary detector
const det = await detect(file.absolutePath, {
sampleBytes: this.config.get('copytree.binaryDetect.sampleBytes', 8192),
nonPrintableThreshold: this.config.get('copytree.binaryDetect.nonPrintableThreshold', 0.3),
});
// Get policy for this file's catFileLoadingStage.handleBinaryFile method · javascript · L98-L128 (31 LOC)src/pipeline/stages/FileLoadingStage.js
handleBinaryFile(file, det, policy) {
switch (policy) {
case 'skip':
return null;
case 'comment':
// Return stub for formatters to emit comments
return {
...file,
content: '',
isBinary: true,
excluded: true,
excludedReason: det.category || 'binary',
binaryCategory: det.category,
binaryName: det.name,
};
case 'base64':
return this.loadBinaryAsBase64(file, det);
case 'placeholder':
default:
return {
...file,
content: this.config.get('copytree.binaryPlaceholderText', '[Binary file not included]'),
isBinary: true,
binaryCategory: det.category,
binaryName: det.name,
};
}
}Repobility — the code-quality scanner for AI-generated software · https://repobility.com
FileLoadingStage.loadBinaryAsBase64 method · javascript · L130-L153 (24 LOC)src/pipeline/stages/FileLoadingStage.js
async loadBinaryAsBase64(file, det) {
try {
const buffer = await fs.readFile(file.absolutePath);
const base64 = buffer.toString('base64');
return {
...file,
content: base64,
isBinary: true,
encoding: 'base64',
binaryCategory: det?.category,
binaryName: det?.name,
};
} catch (error) {
return {
...file,
content: `[Error loading binary file: ${error.message}]`,
error: error.message,
isBinary: true,
binaryCategory: det?.category,
binaryName: det?.name,
};
}
}GitFilterStage.constructor method · javascript · L9-L17 (9 LOC)src/pipeline/stages/GitFilterStage.js
constructor(options = {}) {
super(options);
this.basePath = options.basePath || process.cwd();
this.modified = options.modified || false;
this.changed = options.changed || null;
this.includeGitStatus = options.withGitStatus || false;
this.gitUtils = new GitUtils(this.basePath);
}GitFilterStage.process method · javascript · L19-L108 (90 LOC)src/pipeline/stages/GitFilterStage.js
async process(input) {
// Use basePath from input if provided, otherwise fall back to constructor option
if (input.basePath) {
this.basePath = input.basePath;
this.gitUtils = new GitUtils(this.basePath);
}
// Skip if no git filtering is requested
if (!this.modified && !this.changed && !this.includeGitStatus) {
return input;
}
this.log('Applying git filters', 'debug');
const startTime = Date.now();
try {
// Check if we're in a git repository
if (!(await this.gitUtils.isGitRepository())) {
this.log('Not a git repository, skipping git filters', 'warn');
return input;
}
let filteredFiles = input.files;
let gitFiles = [];
// Get the appropriate file list based on options
if (this.modified) {
gitFiles = await this.gitUtils.getModifiedFiles();
this.log(`Found ${gitFiles.length} modified files`, 'debug');
} else if (this.changed) {
gitFiles = awaiInstructionsStage.process method · javascript · L14-L83 (70 LOC)src/pipeline/stages/InstructionsStage.js
async process(input) {
const startTime = Date.now();
// Check if instructions are disabled
if (input.options?.noInstructions || input.options?.instructions === false) {
this.log('Instructions disabled via --no-instructions', 'debug');
return input;
}
this.log('InstructionsStage processing started', 'debug');
try {
// Determine which instructions to load
let instructionsName = input.options?.instructions;
// If instructions is true but not a string, use default
if (instructionsName === true || !instructionsName) {
instructionsName = this.config.get('app.defaultInstructions', 'default');
}
this.log(`Loading instructions: ${instructionsName}`, 'debug');
// Load instructions
this.log(`Loading instructions: ${instructionsName}`, 'debug');
let instructionsContent;
try {
instructionsContent = await this.instructionsLoader.load(instructionsName);
} catch (error) {
InstructionsStage.validate method · javascript · L90-L112 (23 LOC)src/pipeline/stages/InstructionsStage.js
async validate(input) {
// Skip validation if instructions are disabled
if (input.options?.noInstructions || input.options?.instructions === false) {
return true;
}
let instructionsName = input.options?.instructions;
// If instructions is true but not a string, use default
if (instructionsName === true || !instructionsName) {
instructionsName = this.config.get('app.defaultInstructions', 'default');
}
// Check if instructions exist
const exists = await this.instructionsLoader.exists(instructionsName);
// Only throw error if a specific instructions set was requested (not true or default)
if (!exists && input.options?.instructions && input.options.instructions !== true) {
throw new Error(`Instructions '${instructionsName}' not found`);
}
return true;
}LimitStage.process method · javascript · L9-L30 (22 LOC)src/pipeline/stages/LimitStage.js
async process(input) {
this.log(`Limiting to first ${this.limit} files`, 'debug');
if (input.files.length <= this.limit) {
return input;
}
const limitedFiles = input.files.slice(0, this.limit);
const truncatedCount = input.files.length - this.limit;
this.log(`Limited output to ${this.limit} files (${truncatedCount} truncated)`, 'info');
return {
...input,
files: limitedFiles,
stats: {
...input.stats,
truncated: true,
truncatedCount,
},
};
}OutputFormattingStage.constructor method · javascript · L8-L19 (12 LOC)src/pipeline/stages/OutputFormattingStage.js
constructor(options = {}) {
super(options);
// Normalize and default format (xml is default)
const raw = (options.format || 'xml').toString().toLowerCase();
this.format = raw === 'md' ? 'markdown' : raw;
this.addLineNumbers =
options.addLineNumbers ??
options.withLineNumbers ??
this.config.get('copytree.addLineNumbers', false);
this.lineNumberFormat = this.config.get('copytree.lineNumberFormat', '%4d: ');
this.onlyTree = options.onlyTree || false;
}OutputFormattingStage.handleError method · javascript · L24-L33 (10 LOC)src/pipeline/stages/OutputFormattingStage.js
async handleError(error, input) {
this.log(`Output formatting failed: ${error.message}, returning raw data`, 'warn');
// Return a minimal valid output structure
return {
...input,
output: JSON.stringify({ error: error.message, files: input.files || [] }),
outputFormat: 'json',
outputSize: 0,
};
}Open data scored by Repobility · https://repobility.com
OutputFormattingStage.process method · javascript · L35-L95 (61 LOC)src/pipeline/stages/OutputFormattingStage.js
async process(input) {
this.log(`Formatting output as ${this.format}`, 'debug');
const startTime = Date.now();
let output;
switch (this.format) {
case 'xml': {
const formatter = new XMLFormatter({
stage: this,
addLineNumbers: this.addLineNumbers,
onlyTree: this.onlyTree,
});
output = await formatter.format(input);
break;
}
case 'json':
output = this.formatAsJSON(input);
break;
case 'tree':
output = this.formatAsTree(input);
break;
case 'markdown': {
const formatter = new MarkdownFormatter({
stage: this,
addLineNumbers: this.addLineNumbers,
onlyTree: this.onlyTree,
});
output = await formatter.format(input);
break;
}
case 'ndjson': {
const formatter = new NDJSONFormatter({
stage: this,
addLineNumbers: this.addLineNumbers,
onlyTree: thisOutputFormattingStage.formatAsJSON method · javascript · L97-L135 (39 LOC)src/pipeline/stages/OutputFormattingStage.js
formatAsJSON(input) {
const output = {
directory: input.basePath,
metadata: {
generated: new Date().toISOString(),
fileCount: input.files.filter((f) => f !== null).length,
totalSize: this.calculateTotalSize(input.files),
profile: input.profile?.name || 'default',
directoryStructure: this.generateDirectoryStructure(input.files),
...(input.instructions && {
instructions: input.instructions,
}),
},
files: input.files
.filter((f) => f !== null)
.map((file) => {
const fileObj = {
path: file.path,
size: file.size,
modified: file.modified,
isBinary: file.isBinary,
encoding: file.encoding,
};
// Add content unless --only-tree is set
if (!this.onlyTree) {
fileObj.content =
this.addLineNumbers && !file.isBinary
? this.addLineNumbersToContent(filOutputFormattingStage.formatAsTree method · javascript · L137-L159 (23 LOC)src/pipeline/stages/OutputFormattingStage.js
formatAsTree(input) {
const lines = [];
// Tree formatting options (currently using defaults)
// const indent = this.config.get('copytree.treeIndent', ' ');
// const connectors = this.config.get('copytree.treeConnectors');
lines.push(input.basePath);
lines.push('');
// Build tree structure
const tree = this.buildTreeStructure(input.files);
// Render tree
this.renderTree(tree, lines, '', true);
// Add summary
lines.push('');
lines.push(
`${input.files.filter((f) => f !== null).length} files, ${this.formatBytes(this.calculateTotalSize(input.files))}`,
);
return lines.join('\n');
}