← back to gregpriday__copytree

Function bodies 337 total

All specs Real LLM only Function bodies
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', '    ')
          : th
OutputFormattingStage.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 (minim
SecretsGuardStage.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, filePa
SecretsGuardStage._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);
      transformStr
StreamingOutputStage.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: ${escapeYaml
Want 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) {
        strea
StreamingOutputStage.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.gitMetad
StreamingOutputStage.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.cache
TransformStage.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.cache
TransformStage.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 file
CacheService.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,
     
‹ prevpage 3 / 7next ›