This file is a merged representation of the entire codebase, combined into a single document by Repomix.

<file_summary>
This section contains a summary of this file.

<purpose>
This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.
</purpose>

<file_format>
The content is organized as follows:
1. This summary section
2. Repository information
3. Directory structure
4. Repository files, each consisting of:
  - File path as an attribute
  - Full contents of the file
</file_format>

<usage_guidelines>
- This file should be treated as read-only. Any changes should be made to the
  original repository files, not this packed version.
- When processing this file, use the file path to distinguish
  between different files in the repository.
- Be aware that this file may contain sensitive information. Handle it with
  the same level of security as you would the original repository.
- Pay special attention to the Repository Description. These contain important context and guidelines specific to this project.
- Pay special attention to the Repository Instruction. These contain important context and guidelines specific to this project.
</usage_guidelines>

<notes>
- Some files may have been excluded based on .gitignore rules and Repomix's configuration
- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
- Files matching patterns in .gitignore are excluded
- Files matching default ignore patterns are excluded
- Files are sorted by Git change count (files with more changes are at the bottom)
</notes>

<additional_info>
<user_provided_header>
This repository contains the source code for the Repomix tool.
Repomix is designed to pack repository contents into a single file,
making it easier for AI systems to analyze and process the codebase.

Key Features:
- Configurable ignore patterns
- Custom header text support
- Efficient file processing and packing

Please refer to the README.md file for more detailed information on usage and configuration.

</user_provided_header>

</additional_info>

</file_summary>

<directory_structure>
.devcontainer/
  devcontainer.json
.github/
  workflows/
    ci.yml
    codeql.yml
    docker.yml
    homebrew.yml
  CODEOWNERS
  copilot-instructions.md
  dependabot.yml
  FUNDING.yml
  pull_request_template.md
  renovate.json5
.memo/
  memo.md
.repomix/
bin/
  repomix.cjs
src/
  cli/
    actions/
      defaultAction.ts
      initAction.ts
      mcpAction.ts
      migrationAction.ts
      remoteAction.ts
      versionAction.ts
    cliPrint.ts
    cliRun.ts
    cliSpinner.ts
    types.ts
  config/
    configLoad.ts
    configSchema.ts
    defaultIgnore.ts
    globalDirectory.ts
  core/
    file/
      workers/
        fileCollectWorker.ts
        fileProcessWorker.ts
      fileCollect.ts
      fileManipulate.ts
      filePathSort.ts
      fileProcess.ts
      fileSearch.ts
      fileTreeGenerate.ts
      fileTypes.ts
      gitCommand.ts
      packageJsonParse.ts
      permissionCheck.ts
    metrics/
      workers/
        fileMetricsWorker.ts
        outputMetricsWorker.ts
        types.ts
      calculateAllFileMetrics.ts
      calculateMetrics.ts
      calculateOutputMetrics.ts
    output/
      outputStyles/
        markdownStyle.ts
        plainStyle.ts
        xmlStyle.ts
      outputGenerate.ts
      outputGeneratorTypes.ts
      outputSort.ts
      outputStyleDecorate.ts
    packager/
      copyToClipboardIfEnabled.ts
      writeOutputToDisk.ts
    security/
      workers/
        securityCheckWorker.ts
      filterOutUntrustedFiles.ts
      securityCheck.ts
      validateFileSafety.ts
    tokenCount/
      tokenCount.ts
    treeSitter/
      parseStrategies/
        CssParseStrategy.ts
        DefaultParseStrategy.ts
        GoParseStrategy.ts
        ParseStrategy.ts
        PythonParseStrategy.ts
        TypeScriptParseStrategy.ts
        VueParseStrategy.ts
      queries/
        queryC.ts
        queryCpp.ts
        queryCSharp.ts
        queryCss.ts
        queryGo.ts
        queryJava.ts
        queryJavascript.ts
        queryPhp.ts
        queryPython.ts
        queryRuby.ts
        queryRust.ts
        querySolidity.ts
        querySwift.ts
        queryTypescript.ts
        queryVue.ts
        README.md
      ext2Lang.ts
      lang2Query.ts
      languageParser.ts
      loadLanguage.ts
      parseFile.ts
    packager.ts
  mcp/
    prompts/
      packRemoteRepositoryPrompts.ts
    tools/
      fileSystemReadDirectoryTool.ts
      fileSystemReadFileTool.ts
      mcpToolRuntime.ts
      packCodebaseTool.ts
      packRemoteRepositoryTool.ts
      readRepomixOutputTool.ts
    mcpServer.ts
  shared/
    constants.ts
    errorHandle.ts
    logger.ts
    processConcurrency.ts
    types.ts
  index.ts
test-css/
tests/
  cli/
    actions/
      defaultAction.test.ts
      initAction.test.ts
      mcpAction.test.ts
      migrationAction.test.ts
      remoteAction.test.ts
      versionAction.test.ts
    cliPrint.test.ts
    cliRun.test.ts
  config/
    configLoad.test.ts
    configSchema.test.ts
    globalDirectory.test.ts
  core/
    file/
      fileCollect.test.ts
      fileManipulate.test.ts
      filePathSort.test.ts
      fileProcess.test.ts
      fileSearch.test.ts
      gitCommand.test.ts
      packageJsonParse.test.ts
      permissionCheck.test.ts
    metrics/
      calculateAllFileMetrics.test.ts
      calculateMetrics.test.ts
      calculateOutputMetrics.test.ts
    output/
      outputStyles/
        markdownStyle.test.ts
        plainStyle.test.ts
        xmlStyle.test.ts
      outputGenerate.test.ts
      outputSort.test.ts
      outputStyleDecorate.test.ts
    packager/
      copyToClipboardIfEnabled.test.ts
      writeOutputToDisk.test.ts
    security/
      workers/
        securityCheckWorker.test.ts
      filterOutUntrustedFiles.test.ts
      securityCheck.test.ts
      validateFileSafety.test.ts
    tokenCount/
      tokenCount.test.ts
    treeSitter/
      LanguageParser.test.ts
      loadLanguage.test.ts
      parseFile.c.test.ts
      parseFile.comments.test.ts
      parseFile.cpp.test.ts
      parseFile.csharp.test.ts
      parseFile.css.test.ts
      parseFile.go.test.ts
      parseFile.java.test.ts
      parseFile.javascript.test.ts
      parseFile.php.test.ts
      parseFile.python.test.ts
      parseFile.ruby.test.ts
      parseFile.rust.test.ts
      parseFile.solidity.test.ts
      parseFile.swift.test.ts
      parseFile.test.ts
      parseFile.typescript.test.ts
      parseFile.vue.test.ts
    packager.test.ts
  integration-tests/
    packager.test.ts
  mcp/
    tools/
      fileSystemReadDirectoryTool.test.ts
      fileSystemReadFileTool.test.ts
      packCodebaseTool.test.ts
    mcpServer.test.ts
  shared/
    logger.test.ts
    processConcurrency.test.ts
  testing/
    testUtils.ts
website/
  client/
    .vitepress/
      config/
        configDe.ts
        configEnUs.ts
        configEs.ts
        configFr.ts
        configJa.ts
        configKo.ts
        configPtBr.ts
        configShard.ts
        configZhCn.ts
      theme/
        component.d.ts
        custom.css
        index.ts
        style.css
      config.ts
    components/
      api/
        client.ts
      Home/
        Hero.vue
        PackButton.vue
        TryIt.vue
        TryItFileUpload.vue
        TryItFolderUpload.vue
        TryItPackOptions.vue
        TryItResult.vue
        TryItResultContent.vue
        TryItResultErrorContent.vue
        TryItUrlInput.vue
      utils/
        analytics.ts
        requestHandlers.ts
        resultViewer.ts
        validation.ts
      Home.vue
    src/
      de/
        guide/
          development/
            index.md
            setup.md
          tips/
            best-practices.md
          code-compress.md
          command-line-options.md
          comment-removal.md
          configuration.md
          custom-instructions.md
          index.md
          installation.md
          mcp-server.md
          output.md
          prompt-examples.md
          remote-repository-processing.md
          security.md
          usage.md
        index.md
      en/
        guide/
          development/
            index.md
            setup.md
          tips/
            best-practices.md
          code-compress.md
          command-line-options.md
          comment-removal.md
          configuration.md
          custom-instructions.md
          index.md
          installation.md
          mcp-server.md
          output.md
          prompt-examples.md
          remote-repository-processing.md
          security.md
          usage.md
        index.md
      es/
        guide/
          development/
            index.md
            setup.md
          tips/
            best-practices.md
          code-compress.md
          command-line-options.md
          comment-removal.md
          configuration.md
          custom-instructions.md
          index.md
          installation.md
          mcp-server.md
          output.md
          prompt-examples.md
          remote-repository-processing.md
          security.md
          usage.md
        index.md
      fr/
        guide/
          development/
            index.md
            setup.md
          tips/
            best-practices.md
          code-compress.md
          command-line-options.md
          comment-removal.md
          configuration.md
          custom-instructions.md
          index.md
          installation.md
          mcp-server.md
          output.md
          prompt-examples.md
          remote-repository-processing.md
          security.md
          usage.md
        index.md
      ja/
        guide/
          development/
            index.md
            setup.md
          tips/
            best-practices.md
          code-compress.md
          command-line-options.md
          comment-removal.md
          configuration.md
          custom-instructions.md
          index.md
          installation.md
          mcp-server.md
          output.md
          prompt-examples.md
          remote-repository-processing.md
          security.md
          usage.md
        index.md
      ko/
        guide/
          development/
            index.md
            setup.md
          tips/
            best-practices.md
          code-compress.md
          command-line-options.md
          comment-removal.md
          configuration.md
          custom-instructions.md
          index.md
          installation.md
          mcp-server.md
          output.md
          prompt-examples.md
          remote-repository-processing.md
          security.md
          usage.md
        index.md
      pt-br/
        guide/
          development/
            index.md
            setup.md
          tips/
            best-practices.md
          code-compress.md
          command-line-options.md
          comment-removal.md
          configuration.md
          custom-instructions.md
          index.md
          installation.md
          mcp-server.md
          output.md
          prompt-examples.md
          remote-repository-processing.md
          security.md
          usage.md
        index.md
      public/
        images/
          repomix-logo.svg
      zh-cn/
        guide/
          development/
            index.md
            setup.md
          tips/
            best-practices.md
          code-compress.md
          command-line-options.md
          comment-removal.md
          configuration.md
          custom-instructions.md
          index.md
          installation.md
          mcp-server.md
          output.md
          prompt-examples.md
          remote-repository-processing.md
          security.md
          usage.md
        index.md
    .gitignore
    .tool-versions
    Dockerfile
    package.json
    tsconfig.json
    tsconfig.node.json
  server/
    src/
      schemas/
        request.ts
      utils/
        cache.ts
        errorHandler.ts
        fileUtils.ts
        logger.ts
        network.ts
        processConcurrency.ts
        rateLimit.ts
        sharedInstance.ts
        time.ts
        validation.ts
      constants.ts
      index.ts
      processZipFile.ts
      remoteRepo.ts
      types.ts
    .dockerignore
    .gitignore
    cloudbuild.yaml
    Dockerfile
    package.json
    tsconfig.json
  compose.yml
  README.md
.clinerules
.codecov.yml
.cursorrules
.dockerignore
.editorconfig
.gitignore
.node-version
.npmignore
.repomixignore
.secretlintrc.json
.tool-versions
biome.json
CLAUDE.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
Dockerfile
LICENSE
llms-install.md
package.json
README.md
repomix-instruction.md
repomix.config.json
SECURITY.md
tsconfig.build.json
tsconfig.json
typos.toml
vitest.config.ts
</directory_structure>

<files>
This section contains the contents of the repository's files.

<file path=".devcontainer/devcontainer.json">
{
  "name": "Repomix",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bullseye",
  "runArgs": ["--name", "repomix-devcontainer"],
  "postCreateCommand": "npm install"
}
</file>

<file path=".github/workflows/ci.yml">
name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  workflow_dispatch:

jobs:
  lint-biome:
    name: Lint Biome
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version-file: .tool-versions
        cache: npm
    - run: npm ci
    - run: npm run lint-biome && git diff --exit-code

  lint-ts:
    name: Lint TypeScript
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version-file: .tool-versions
        cache: npm
    - run: npm ci
    - run: npm run lint-ts

  lint-secretlint:
    name: Lint Secretlint
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version-file: .tool-versions
        cache: npm
    - run: npm ci
    - run: npm run lint-secretlint

  lint-action:
    name: Lint GitHub Actions
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker://rhysd/actionlint:latest
        with:
          args: "-color"

  check-typos:
    name: Check typos
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: crate-ci/typos@master

  test:
    name: Test
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18.0.0, 18.x, 19.x, 20.x, 21.x, 22.x, 23.x]
    runs-on: ${{ matrix.os }}
    steps:
    - uses: actions/checkout@v4
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v4
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm run test --reporter=verbose
      env:
        CI_OS: ${{ runner.os }}

  test-coverage:
    name: Test coverage
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version-file: .tool-versions
        cache: npm
    - run: npm ci
    - run: npm run test-coverage -- --reporter=verbose
      env:
        CI_OS: ${{ runner.os }}
    - uses: actions/upload-artifact@v4
      with:
        name: test-coverage
        path: coverage/
    - uses: codecov/codecov-action@v5
      with:
        fail_ci_if_error: true
        directory: ./coverage
      env:
        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

  build-and-run:
    name: Build and run
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18.0.0, 18.x, 19.x, 20.x, 21.x, 22.x, 23.x]
    runs-on: ${{ matrix.os }}
    steps:
    - uses: actions/checkout@v4
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v4
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm run build
    - name: Install only production dependencies
      run: npm ci --omit=dev
    - run: node bin/repomix.cjs
    - run: node bin/repomix.cjs --version
    - run: node bin/repomix.cjs --help
    - name: Upload build artifact
      uses: actions/upload-artifact@v4
      with:
        name: repomix-output-${{ matrix.os }}-${{ matrix.node-version }}.txt
        path: repomix-output.txt
</file>

<file path=".github/workflows/codeql.yml">
name: "CodeQL"

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]
  schedule:
    - cron: '25 11 * * 0'

jobs:
  analyze:
    name: Analyze (${{ matrix.language }})
    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
    permissions:
      security-events: write
      packages: read
      actions: read
      contents: read

    strategy:
      fail-fast: false
      matrix:
        include:
        - language: javascript-typescript
          build-mode: none
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v3
      with:
        languages: ${{ matrix.language }}
        build-mode: ${{ matrix.build-mode }}

    - if: matrix.build-mode == 'manual'
      shell: bash
      run: |
        echo 'If you are using a "manual" build mode for one or more of the' \
          'languages you are analyzing, replace this with the commands to build' \
          'your code, for example:'
        echo '  make bootstrap'
        echo '  make release'
        exit 1

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v3
      with:
        category: "/language:${{matrix.language}}"
</file>

<file path=".github/workflows/docker.yml">
name: Docker

on:
  push:
    branches:
      - "main"
    paths-ignore:
      - "**.md"
      - LICENSE
  pull_request:
    branches:
      - "*"
    paths:
      - "Dockerfile"
      - ".dockerignore"
      - ".github/workflows/docker.yml"
  workflow_dispatch:
  release:
    types: [published, edited]

permissions:
  contents: read
  packages: write

jobs:
  build-and-publish-image:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Docker metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            ghcr.io/yamadashy/repomix
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}
            type=semver,pattern={{major}}.{{minor}}
            type=raw,value=latest,enable=${{ github.event_name == 'release' }}

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GitHub Container Registry
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and Publish Docker Image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          platforms: linux/amd64,linux/arm64,linux/arm/v7
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
</file>

<file path=".github/workflows/homebrew.yml">
name: Homebrew

on:
  release:
    types:
      - created

jobs:
  homebrew:
    runs-on: macos-latest
    steps:
      - name: Set up Homebrew
        uses: Homebrew/actions/setup-homebrew@master
        with:
          test-bot: false

      - name: Configure Git user
        uses: Homebrew/actions/git-user-config@master

      - name: Bump packages
        uses: Homebrew/actions/bump-packages@master
        with:
          token: ${{ secrets.COMMITTER_TOKEN }}
          formulae: repomix
</file>

<file path=".github/dependabot.yml">
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"
</file>

<file path=".github/FUNDING.yml">
github: yamadashy
</file>

<file path=".github/pull_request_template.md">
<!-- Please include a summary of the changes -->

## Checklist

- [ ] Run `npm run test`
- [ ] Run `npm run lint`
</file>

<file path=".github/renovate.json5">
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended",
    "schedule:weekly",
    'group:allNonMajor'
  ],
  "rangeStrategy": "bump",
  "dependencyDashboard": false,
  "labels": ["dependencies", "renovate"],
  "packageRules": [
    {
      matchDepTypes: ['peerDependencies'],
      enabled: false,
    },
  ],
  "ignoreDeps": [
    "node",
  ]
}
</file>

<file path=".memo/memo.md">

</file>

<file path="bin/repomix.cjs">
#!/usr/bin/env node

const nodeVersion = process.versions.node;
const [major] = nodeVersion.split('.').map(Number);

const EXIT_CODES = {
  SUCCESS: 0,
  ERROR: 1,
};

if (major < 16) {
  console.error(`Repomix requires Node.js version 16 or higher. Current version: ${nodeVersion}\n`);
  process.exit(EXIT_CODES.ERROR);
}

function setupErrorHandlers() {
  process.on('uncaughtException', (error) => {
    console.error('Uncaught Exception:', error);
    process.exit(EXIT_CODES.ERROR);
  });

  process.on('unhandledRejection', (reason) => {
    console.error('Unhandled Promise Rejection:', reason);
    process.exit(EXIT_CODES.ERROR);
  });

  function shutdown() {
    process.exit(EXIT_CODES.SUCCESS);
  }

  process.on('SIGINT', () => {
    console.log('\nReceived SIGINT. Shutting down...');
    shutdown();
  });
  process.on('SIGTERM', shutdown);
}

(async () => {
  try {
    setupErrorHandlers();

    const { run } = await import('../lib/cli/cliRun.js');
    await run();
  } catch (error) {
    if (error instanceof Error) {
      console.error('Fatal Error:', {
        name: error.name,
        message: error.message,
        stack: error.stack,
      });
    } else {
      console.error('Fatal Error:', error);
    }

    process.exit(EXIT_CODES.ERROR);
  }
})();
</file>

<file path="src/cli/actions/migrationAction.ts">
import * as fs from 'node:fs/promises';
import path from 'node:path';
import * as prompts from '@clack/prompts';
import pc from 'picocolors';
import { getGlobalDirectory } from '../../config/globalDirectory.js';
import { logger } from '../../shared/logger.js';

interface MigrationPaths {
  oldConfigPath: string;
  newConfigPath: string;
  oldIgnorePath: string;
  newIgnorePath: string;
  oldInstructionPath: string;
  newInstructionPath: string;
  oldOutputPaths: string[];
  newOutputPaths: string[];
  oldGlobalConfigPath: string;
  newGlobalConfigPath: string;
}

interface MigrationResult {
  configMigrated: boolean;
  ignoreMigrated: boolean;
  instructionMigrated: boolean;
  outputFilesMigrated: string[];
  globalConfigMigrated: boolean;
  error?: Error;
}

/**
 * Check if a file exists at the given path
 */
const fileExists = async (filePath: string): Promise<boolean> => {
  try {
    await fs.access(filePath);
    return true;
  } catch {
    return false;
  }
};

/**
 * Replace all occurrences of 'repopack' with 'repomix' in a string
 */
const replaceRepopackString = (content: string): string => {
  return content.replace(/repopack/g, 'repomix').replace(/Repopack/g, 'Repomix');
};

/**
 * Update file content by replacing 'repopack' with 'repomix'
 */
const updateFileContent = async (filePath: string): Promise<boolean> => {
  const content = await fs.readFile(filePath, 'utf8');
  const updatedContent = replaceRepopackString(content);

  // Check if content needs to be updated
  if (content !== updatedContent) {
    await fs.writeFile(filePath, updatedContent, 'utf8');
    const relativePath = path.relative(process.cwd(), filePath);
    logger.log(`Updated repopack references in ${pc.cyan(relativePath)}`);
    return true;
  }

  return false;
};

/**
 * Parse JSON content, update instructionFilePath if exists
 */
const updateInstructionPath = (content: string): string => {
  try {
    const config = JSON.parse(content);
    if (config.output?.instructionFilePath) {
      config.output.instructionFilePath = config.output.instructionFilePath.replace('repopack', 'repomix');
    }
    // Also update output.filePath if it exists
    if (config.output?.filePath) {
      config.output.filePath = config.output.filePath.replace('repopack', 'repomix');
    }
    return JSON.stringify(config, null, 2);
  } catch {
    return content;
  }
};

/**
 * Get output file paths pairs
 */
const getOutputFilePaths = (rootDir: string): { oldPaths: string[]; newPaths: string[] } => {
  const extensions = ['.txt', '.xml', '.md'];
  const oldPaths = extensions.map((ext) => path.join(rootDir, `repopack-output${ext}`));
  const newPaths = extensions.map((ext) => path.join(rootDir, `repomix-output${ext}`));
  return { oldPaths, newPaths };
};

/**
 * Migrate a single file from old path to new path
 */
const migrateFile = async (
  oldPath: string,
  newPath: string,
  description: string,
  isConfig = false,
): Promise<boolean> => {
  if (!(await fileExists(oldPath))) {
    return false;
  }

  const exists = await fileExists(newPath);
  if (exists) {
    const shouldOverwrite = await prompts.confirm({
      message: `${description} already exists at ${newPath}. Do you want to overwrite it?`,
    });

    if (prompts.isCancel(shouldOverwrite) || !shouldOverwrite) {
      logger.info(`Skipping migration of ${description}`);
      return false;
    }
  }

  try {
    // Read and update content
    let content = await fs.readFile(oldPath, 'utf8');
    content = replaceRepopackString(content);

    // For config files, also update instructionFilePath and output.filePath
    if (isConfig) {
      content = updateInstructionPath(content);
    }

    // Ensure the target directory exists
    await fs.mkdir(path.dirname(newPath), { recursive: true });

    // Write to new file
    await fs.writeFile(newPath, content, 'utf8');

    // Remove old file
    await fs.unlink(oldPath);

    const relativeOldPath = path.relative(process.cwd(), oldPath);
    const relativeNewPath = path.relative(process.cwd(), newPath);

    logger.log(`Renamed ${description} from ${relativeOldPath} to ${relativeNewPath}`);
    return true;
  } catch (error) {
    logger.error(`Failed to migrate ${description}:`, error);
    return false;
  }
};

/**
 * Update content of gitignore and repomixignore files
 */
const updateIgnoreFiles = async (rootDir: string): Promise<void> => {
  const gitignorePath = path.join(rootDir, '.gitignore');
  const repomixignorePath = path.join(rootDir, '.repomixignore');

  if (await fileExists(gitignorePath)) {
    const updated = await updateFileContent(gitignorePath);
    if (!updated) {
      logger.debug('No changes needed in .gitignore');
    }
  }

  if (await fileExists(repomixignorePath)) {
    const updated = await updateFileContent(repomixignorePath);
    if (!updated) {
      logger.debug('No changes needed in .repomixignore');
    }
  }
};

/**
 * Get all migration related file paths
 */
const getMigrationPaths = (rootDir: string): MigrationPaths => {
  const { oldPaths: oldOutputPaths, newPaths: newOutputPaths } = getOutputFilePaths(rootDir);
  const oldGlobalDirectory = path.join(process.env.HOME || '', '.config', 'repopack');
  const newGlobalDirectory = getGlobalDirectory();

  return {
    oldConfigPath: path.join(rootDir, 'repopack.config.json'),
    newConfigPath: path.join(rootDir, 'repomix.config.json'),
    oldIgnorePath: path.join(rootDir, '.repopackignore'),
    newIgnorePath: path.join(rootDir, '.repomixignore'),
    oldInstructionPath: path.join(rootDir, 'repopack-instruction.md'),
    newInstructionPath: path.join(rootDir, 'repomix-instruction.md'),
    oldOutputPaths,
    newOutputPaths,
    oldGlobalConfigPath: path.join(oldGlobalDirectory, 'repopack.config.json'),
    newGlobalConfigPath: path.join(newGlobalDirectory, 'repomix.config.json'),
  };
};

/**
 * Migrate output files
 */
const migrateOutputFiles = async (oldPaths: string[], newPaths: string[]): Promise<string[]> => {
  const migratedFiles: string[] = [];

  for (let i = 0; i < oldPaths.length; i++) {
    const oldPath = oldPaths[i];
    const newPath = newPaths[i];
    const ext = path.extname(oldPath);

    if (await migrateFile(oldPath, newPath, `Output file (${ext})`)) {
      migratedFiles.push(newPath);
    }
  }

  return migratedFiles;
};

export const runMigrationAction = async (rootDir: string): Promise<MigrationResult> => {
  const result: MigrationResult = {
    configMigrated: false,
    ignoreMigrated: false,
    instructionMigrated: false,
    outputFilesMigrated: [],
    globalConfigMigrated: false,
  };

  try {
    const paths = getMigrationPaths(rootDir);

    // Check if migration is needed
    const hasOldConfig = await fileExists(paths.oldConfigPath);
    const hasOldIgnore = await fileExists(paths.oldIgnorePath);
    const hasOldInstruction = await fileExists(paths.oldInstructionPath);
    const hasOldGlobalConfig = await fileExists(paths.oldGlobalConfigPath);
    const hasOldOutput = await Promise.all(paths.oldOutputPaths.map(fileExists)).then((results) =>
      results.some((exists) => exists),
    );

    if (!hasOldConfig && !hasOldIgnore && !hasOldInstruction && !hasOldOutput && !hasOldGlobalConfig) {
      logger.debug('No Repopack files found to migrate.');
      return result;
    }

    // Show migration notice based on what needs to be migrated
    let migrationMessage = `Found ${pc.green('Repopack')} `;
    const items = [];
    if (hasOldConfig || hasOldIgnore || hasOldInstruction || hasOldOutput) items.push('local configuration');
    if (hasOldGlobalConfig) items.push('global configuration');
    migrationMessage += `${items.join(' and ')}. Would you like to migrate to ${pc.green('Repomix')}?`;

    // Confirm migration with user
    const shouldMigrate = await prompts.confirm({
      message: migrationMessage,
    });

    if (prompts.isCancel(shouldMigrate) || !shouldMigrate) {
      logger.info('Migration cancelled.');
      return result;
    }

    // Show migration notice
    logger.info(pc.cyan('\nMigrating from Repopack to Repomix...'));
    logger.log('');

    // Migrate config file
    if (hasOldConfig) {
      result.configMigrated = await migrateFile(paths.oldConfigPath, paths.newConfigPath, 'Configuration file', true);
    }

    // Migrate global config file
    if (hasOldGlobalConfig) {
      result.globalConfigMigrated = await migrateFile(
        paths.oldGlobalConfigPath,
        paths.newGlobalConfigPath,
        'Global configuration file',
        true,
      );
    }

    // Migrate ignore file
    if (hasOldIgnore) {
      result.ignoreMigrated = await migrateFile(paths.oldIgnorePath, paths.newIgnorePath, 'Ignore file');
    }

    // Migrate instruction file
    if (hasOldInstruction) {
      result.instructionMigrated = await migrateFile(
        paths.oldInstructionPath,
        paths.newInstructionPath,
        'Instruction file',
      );
    }

    // Migrate output files
    if (hasOldOutput) {
      result.outputFilesMigrated = await migrateOutputFiles(paths.oldOutputPaths, paths.newOutputPaths);
    }

    // Update content in gitignore and repomixignore
    await updateIgnoreFiles(rootDir);

    // Show success message
    if (
      result.configMigrated ||
      result.ignoreMigrated ||
      result.instructionMigrated ||
      result.outputFilesMigrated.length > 0 ||
      result.globalConfigMigrated
    ) {
      logger.log('');
      logger.success('✔ Migration completed successfully!');
      logger.log('');
      logger.info(
        'You can now use Repomix commands as usual. The old Repopack files have been migrated to the new format.',
      );
      logger.log('');
    }

    return result;
  } catch (error) {
    if (error instanceof Error) {
      result.error = error;
    } else {
      result.error = new Error(String(error));
    }
    logger.error('An error occurred during migration:', error);
    return result;
  }
};
</file>

<file path="src/cli/actions/versionAction.ts">
import { getVersion } from '../../core/file/packageJsonParse.js';
import { logger } from '../../shared/logger.js';

export const runVersionAction = async (): Promise<void> => {
  const version = await getVersion();
  logger.log(version);
};
</file>

<file path="src/cli/cliPrint.ts">
import path from 'node:path';
import pc from 'picocolors';
import type { RepomixConfigMerged } from '../config/configSchema.js';
import type { SuspiciousFileResult } from '../core/security/securityCheck.js';
import { logger } from '../shared/logger.js';

export const printSummary = (
  totalFiles: number,
  totalCharacters: number,
  totalTokens: number,
  outputPath: string,
  suspiciousFilesResults: SuspiciousFileResult[],
  config: RepomixConfigMerged,
) => {
  let securityCheckMessage = '';
  if (config.security.enableSecurityCheck) {
    if (suspiciousFilesResults.length > 0) {
      securityCheckMessage = pc.yellow(
        `${suspiciousFilesResults.length.toLocaleString()} suspicious file(s) detected and excluded`,
      );
    } else {
      securityCheckMessage = pc.white('✔ No suspicious files detected');
    }
  } else {
    securityCheckMessage = pc.dim('Security check disabled');
  }

  logger.log(pc.white('📊 Pack Summary:'));
  logger.log(pc.dim('────────────────'));
  logger.log(`${pc.white('  Total Files:')} ${pc.white(totalFiles.toLocaleString())} files`);
  logger.log(`${pc.white('  Total Chars:')} ${pc.white(totalCharacters.toLocaleString())} chars`);
  logger.log(`${pc.white(' Total Tokens:')} ${pc.white(totalTokens.toLocaleString())} tokens`);
  logger.log(`${pc.white('       Output:')} ${pc.white(outputPath)}`);
  logger.log(`${pc.white('     Security:')} ${pc.white(securityCheckMessage)}`);
};

export const printSecurityCheck = (
  rootDir: string,
  suspiciousFilesResults: SuspiciousFileResult[],
  config: RepomixConfigMerged,
) => {
  if (!config.security.enableSecurityCheck) {
    return;
  }

  logger.log(pc.white('🔎 Security Check:'));
  logger.log(pc.dim('──────────────────'));

  if (suspiciousFilesResults.length === 0) {
    logger.log(`${pc.green('✔')} ${pc.white('No suspicious files detected.')}`);
  } else {
    logger.log(pc.yellow(`${suspiciousFilesResults.length} suspicious file(s) detected and excluded from the output:`));
    suspiciousFilesResults.forEach((suspiciousFilesResult, index) => {
      const relativeFilePath = path.relative(rootDir, suspiciousFilesResult.filePath);
      logger.log(`${pc.white(`${index + 1}.`)} ${pc.white(relativeFilePath)}`);
      logger.log(pc.dim(`   - ${suspiciousFilesResult.messages.join('\n   - ')}`));
    });
    logger.log(pc.yellow('\nThese files have been excluded from the output for security reasons.'));
    logger.log(pc.yellow('Please review these files for potential sensitive information.'));
  }
};

export const printTopFiles = (
  fileCharCounts: Record<string, number>,
  fileTokenCounts: Record<string, number>,
  topFilesLength: number,
) => {
  const topFilesLengthStrLen = topFilesLength.toString().length;
  logger.log(pc.white(`📈 Top ${topFilesLength} Files by Character Count and Token Count:`));
  logger.log(pc.dim(`─────────────────────────────────────────────────${'─'.repeat(topFilesLengthStrLen)}`));

  const topFiles = Object.entries(fileCharCounts)
    .sort((a, b) => b[1] - a[1])
    .slice(0, topFilesLength);

  topFiles.forEach(([filePath, charCount], index) => {
    const tokenCount = fileTokenCounts[filePath];
    const indexString = `${index + 1}.`.padEnd(3, ' ');
    logger.log(
      `${pc.white(`${indexString}`)} ${pc.white(filePath)} ${pc.dim(`(${charCount.toLocaleString()} chars, ${tokenCount.toLocaleString()} tokens)`)}`,
    );
  });
};

export const printCompletion = () => {
  logger.log(pc.green('🎉 All Done!'));
  logger.log(pc.white('Your repository has been successfully packed.'));

  logger.log('');
  logger.log(`💡 Repomix is now available in your browser! Try it at ${pc.underline('https://repomix.com')}`);
};
</file>

<file path="src/cli/cliSpinner.ts">
import cliSpinners from 'cli-spinners';
import logUpdate from 'log-update';
import pc from 'picocolors';
import type { CliOptions } from './types.js';

export class Spinner {
  private spinner = cliSpinners.dots;
  private message: string;
  private currentFrame = 0;
  private interval: ReturnType<typeof setInterval> | null = null;
  private readonly isQuiet: boolean;

  constructor(message: string, cliOptions: CliOptions) {
    this.message = message;
    // If the user has specified the verbose flag, don't show the spinner
    this.isQuiet = cliOptions.quiet || cliOptions.verbose || false;
  }

  start(): void {
    if (this.isQuiet) {
      return;
    }

    const frames = this.spinner.frames;
    const framesLength = frames.length;
    this.interval = setInterval(() => {
      this.currentFrame++;
      const frame = frames[this.currentFrame % framesLength];
      logUpdate(`${pc.cyan(frame)} ${this.message}`);
    }, this.spinner.interval);
  }

  update(message: string): void {
    if (this.isQuiet) {
      return;
    }

    this.message = message;
  }

  stop(finalMessage: string): void {
    if (this.isQuiet) {
      return;
    }

    if (this.interval) {
      clearInterval(this.interval);
      this.interval = null;
    }
    logUpdate(finalMessage);
    logUpdate.done();
  }

  succeed(message: string): void {
    if (this.isQuiet) {
      return;
    }

    this.stop(`${pc.green('✔')} ${message}`);
  }

  fail(message: string): void {
    if (this.isQuiet) {
      return;
    }

    this.stop(`${pc.red('✖')} ${message}`);
  }
}
</file>

<file path="src/config/configLoad.ts">
import * as fs from 'node:fs/promises';
import path from 'node:path';
import JSON5 from 'json5';
import pc from 'picocolors';
import { RepomixError, rethrowValidationErrorIfZodError } from '../shared/errorHandle.js';
import { logger } from '../shared/logger.js';
import {
  type RepomixConfigCli,
  type RepomixConfigFile,
  type RepomixConfigMerged,
  defaultConfig,
  defaultFilePathMap,
  repomixConfigFileSchema,
  repomixConfigMergedSchema,
} from './configSchema.js';
import { getGlobalDirectory } from './globalDirectory.js';

const defaultConfigPath = 'repomix.config.json';

const getGlobalConfigPath = () => {
  return path.join(getGlobalDirectory(), 'repomix.config.json');
};

export const loadFileConfig = async (rootDir: string, argConfigPath: string | null): Promise<RepomixConfigFile> => {
  let useDefaultConfig = false;
  let configPath = argConfigPath;
  if (!configPath) {
    useDefaultConfig = true;
    configPath = defaultConfigPath;
  }

  const fullPath = path.resolve(rootDir, configPath);

  logger.trace('Loading local config from:', fullPath);

  // Check local file existence
  const isLocalFileExists = await fs
    .stat(fullPath)
    .then((stats) => stats.isFile())
    .catch(() => false);

  if (isLocalFileExists) {
    return await loadAndValidateConfig(fullPath);
  }

  if (useDefaultConfig) {
    // Try to load global config
    const globalConfigPath = getGlobalConfigPath();
    logger.trace('Loading global config from:', globalConfigPath);

    const isGlobalFileExists = await fs
      .stat(globalConfigPath)
      .then((stats) => stats.isFile())
      .catch(() => false);

    if (isGlobalFileExists) {
      return await loadAndValidateConfig(globalConfigPath);
    }

    logger.log(
      pc.dim(
        `No custom config found at ${configPath} or global config at ${globalConfigPath}.\nYou can add a config file for additional settings. Please check https://github.com/yamadashy/repomix for more information.`,
      ),
    );
    return {};
  }
  throw new RepomixError(`Config file not found at ${configPath}`);
};

const loadAndValidateConfig = async (filePath: string): Promise<RepomixConfigFile> => {
  try {
    const fileContent = await fs.readFile(filePath, 'utf-8');
    const config = JSON5.parse(fileContent);
    return repomixConfigFileSchema.parse(config);
  } catch (error) {
    rethrowValidationErrorIfZodError(error, 'Invalid config schema');
    if (error instanceof SyntaxError) {
      throw new RepomixError(`Invalid JSON5 in config file ${filePath}: ${error.message}`);
    }
    if (error instanceof Error) {
      throw new RepomixError(`Error loading config from ${filePath}: ${error.message}`);
    }
    throw new RepomixError(`Error loading config from ${filePath}`);
  }
};

export const mergeConfigs = (
  cwd: string,
  fileConfig: RepomixConfigFile,
  cliConfig: RepomixConfigCli,
): RepomixConfigMerged => {
  logger.trace('Default config:', defaultConfig);

  const baseConfig = defaultConfig;

  // If the output file path is not provided in the config file or CLI, use the default file path for the style
  if (cliConfig.output?.filePath == null && fileConfig.output?.filePath == null) {
    const style = cliConfig.output?.style || fileConfig.output?.style || baseConfig.output.style;
    baseConfig.output.filePath = defaultFilePathMap[style];

    logger.trace('Default output file path is set to:', baseConfig.output.filePath);
  }

  const mergedConfig = {
    cwd,
    output: {
      ...baseConfig.output,
      ...fileConfig.output,
      ...cliConfig.output,
    },
    include: [...(baseConfig.include || []), ...(fileConfig.include || []), ...(cliConfig.include || [])],
    ignore: {
      ...baseConfig.ignore,
      ...fileConfig.ignore,
      ...cliConfig.ignore,
      customPatterns: [
        ...(baseConfig.ignore.customPatterns || []),
        ...(fileConfig.ignore?.customPatterns || []),
        ...(cliConfig.ignore?.customPatterns || []),
      ],
    },
    security: {
      ...baseConfig.security,
      ...fileConfig.security,
      ...cliConfig.security,
    },
  };

  try {
    return repomixConfigMergedSchema.parse(mergedConfig);
  } catch (error) {
    rethrowValidationErrorIfZodError(error, 'Invalid merged config');
    throw error;
  }
};
</file>

<file path="src/config/globalDirectory.ts">
import os from 'node:os';
import path from 'node:path';

export const getGlobalDirectory = () => {
  if (process.platform === 'win32') {
    const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
    return path.join(localAppData, 'Repomix');
  }

  if (process.env.XDG_CONFIG_HOME) {
    return path.join(process.env.XDG_CONFIG_HOME, 'repomix');
  }

  return path.join(os.homedir(), '.config', 'repomix');
};
</file>

<file path="src/core/file/workers/fileCollectWorker.ts">
import * as fs from 'node:fs/promises';
import path from 'node:path';
import iconv from 'iconv-lite';
import { isBinary } from 'istextorbinary';
import jschardet from 'jschardet';
import pc from 'picocolors';
import { logger } from '../../../shared/logger.js';

// Maximum file size to process (50MB)
// This prevents out-of-memory errors when processing very large files
export const MAX_FILE_SIZE = 50 * 1024 * 1024;

export interface FileCollectTask {
  filePath: string;
  rootDir: string;
}

export default async ({ filePath, rootDir }: FileCollectTask) => {
  const fullPath = path.resolve(rootDir, filePath);
  const content = await readRawFile(fullPath);

  if (content) {
    return {
      path: filePath,
      content,
    };
  }

  return null;
};

const readRawFile = async (filePath: string): Promise<string | null> => {
  try {
    const stats = await fs.stat(filePath);

    if (stats.size > MAX_FILE_SIZE) {
      const sizeMB = (stats.size / 1024 / 1024).toFixed(1);
      logger.log('');
      logger.log('⚠️ Large File Warning:');
      logger.log('──────────────────────');
      logger.log(`File exceeds size limit: ${sizeMB}MB > ${MAX_FILE_SIZE / 1024 / 1024}MB (${filePath})`);
      logger.log(pc.dim('Add this file to .repomixignore if you want to exclude it permanently'));
      logger.log('');
      return null;
    }

    if (isBinary(filePath)) {
      logger.debug(`Skipping binary file: ${filePath}`);
      return null;
    }

    logger.trace(`Reading file: ${filePath}`);

    const buffer = await fs.readFile(filePath);

    if (isBinary(null, buffer)) {
      logger.debug(`Skipping binary file (content check): ${filePath}`);
      return null;
    }

    const encoding = jschardet.detect(buffer).encoding || 'utf-8';
    const content = iconv.decode(buffer, encoding);

    return content;
  } catch (error) {
    logger.warn(`Failed to read file: ${filePath}`, error);
    return null;
  }
};
</file>

<file path="src/core/file/workers/fileProcessWorker.ts">
import type { RepomixConfigMerged } from '../../../config/configSchema.js';
import { logger } from '../../../shared/logger.js';
import { parseFile } from '../../treeSitter/parseFile.js';
import { getFileManipulator } from '../fileManipulate.js';
import type { ProcessedFile, RawFile } from '../fileTypes.js';

export interface FileProcessTask {
  rawFile: RawFile;
  config: RepomixConfigMerged;
}

export default async ({ rawFile, config }: FileProcessTask): Promise<ProcessedFile> => {
  const processedContent = await processContent(rawFile, config);
  return {
    path: rawFile.path,
    content: processedContent,
  };
};

export const processContent = async (rawFile: RawFile, config: RepomixConfigMerged) => {
  const processStartAt = process.hrtime.bigint();
  let processedContent = rawFile.content;
  const manipulator = getFileManipulator(rawFile.path);

  logger.trace(`Processing file: ${rawFile.path}`);

  if (manipulator && config.output.removeComments) {
    processedContent = manipulator.removeComments(processedContent);
  }

  if (config.output.removeEmptyLines && manipulator) {
    processedContent = manipulator.removeEmptyLines(processedContent);
  }

  processedContent = processedContent.trim();

  if (config.output.compress) {
    try {
      const parsedContent = await parseFile(processedContent, rawFile.path, config);
      if (parsedContent === undefined) {
        logger.trace(`Failed to parse ${rawFile.path} in compressed mode. Using original content.`);
      }
      processedContent = parsedContent ?? processedContent;
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error(`Error parsing ${rawFile.path} in compressed mode: ${message}`);
      //re-throw error
      throw error;
    }
  } else if (config.output.showLineNumbers) {
    const lines = processedContent.split('\n');
    const padding = lines.length.toString().length;
    const numberedLines = lines.map((line, i) => `${(i + 1).toString().padStart(padding)}: ${line}`);
    processedContent = numberedLines.join('\n');
  }

  const processEndAt = process.hrtime.bigint();
  logger.trace(`Processed file: ${rawFile.path}. Took: ${(Number(processEndAt - processStartAt) / 1e6).toFixed(2)}ms`);

  return processedContent;
};
</file>

<file path="src/core/file/fileCollect.ts">
import pc from 'picocolors';
import { logger } from '../../shared/logger.js';
import { initPiscina } from '../../shared/processConcurrency.js';
import type { RepomixProgressCallback } from '../../shared/types.js';
import type { RawFile } from './fileTypes.js';
import type { FileCollectTask } from './workers/fileCollectWorker.js';

const initTaskRunner = (numOfTasks: number) => {
  const pool = initPiscina(numOfTasks, new URL('./workers/fileCollectWorker.js', import.meta.url).href);
  return (task: FileCollectTask) => pool.run(task);
};

export const collectFiles = async (
  filePaths: string[],
  rootDir: string,
  progressCallback: RepomixProgressCallback = () => {},
  deps = {
    initTaskRunner,
  },
): Promise<RawFile[]> => {
  const runTask = deps.initTaskRunner(filePaths.length);
  const tasks = filePaths.map(
    (filePath) =>
      ({
        filePath,
        rootDir,
      }) satisfies FileCollectTask,
  );

  try {
    const startTime = process.hrtime.bigint();
    logger.trace(`Starting file collection for ${filePaths.length} files using worker pool`);

    let completedTasks = 0;
    const totalTasks = tasks.length;

    const results = await Promise.all(
      tasks.map((task) =>
        runTask(task).then((result) => {
          completedTasks++;
          progressCallback(`Collect file... (${completedTasks}/${totalTasks}) ${pc.dim(task.filePath)}`);
          logger.trace(`Collect files... (${completedTasks}/${totalTasks}) ${task.filePath}`);
          return result;
        }),
      ),
    );

    const endTime = process.hrtime.bigint();
    const duration = Number(endTime - startTime) / 1e6;
    logger.trace(`File collection completed in ${duration.toFixed(2)}ms`);

    return results.filter((file): file is RawFile => file !== null);
  } catch (error) {
    logger.error('Error during file collection:', error);
    throw error;
  }
};
</file>

<file path="src/core/file/fileProcess.ts">
import pc from 'picocolors';
import type { RepomixConfigMerged } from '../../config/configSchema.js';
import { logger } from '../../shared/logger.js';
import { initPiscina } from '../../shared/processConcurrency.js';
import type { RepomixProgressCallback } from '../../shared/types.js';
import { type FileManipulator, getFileManipulator } from './fileManipulate.js';
import type { ProcessedFile, RawFile } from './fileTypes.js';
import type { FileProcessTask } from './workers/fileProcessWorker.js';

type GetFileManipulator = (filePath: string) => FileManipulator | null;

const initTaskRunner = (numOfTasks: number) => {
  const pool = initPiscina(numOfTasks, new URL('./workers/fileProcessWorker.js', import.meta.url).href);
  return (task: FileProcessTask) => pool.run(task);
};

export const processFiles = async (
  rawFiles: RawFile[],
  config: RepomixConfigMerged,
  progressCallback: RepomixProgressCallback,
  deps: {
    initTaskRunner: typeof initTaskRunner;
    getFileManipulator: GetFileManipulator;
  } = {
    initTaskRunner,
    getFileManipulator,
  },
): Promise<ProcessedFile[]> => {
  const runTask = deps.initTaskRunner(rawFiles.length);
  const tasks = rawFiles.map(
    (rawFile, index) =>
      ({
        rawFile,
        config,
      }) satisfies FileProcessTask,
  );

  try {
    const startTime = process.hrtime.bigint();
    logger.trace(`Starting file processing for ${rawFiles.length} files using worker pool`);

    let completedTasks = 0;
    const totalTasks = tasks.length;

    const results = await Promise.all(
      tasks.map((task) =>
        runTask(task).then((result) => {
          completedTasks++;
          progressCallback(`Processing file... (${completedTasks}/${totalTasks}) ${pc.dim(task.rawFile.path)}`);
          logger.trace(`Processing file... (${completedTasks}/${totalTasks}) ${task.rawFile.path}`);
          return result;
        }),
      ),
    );

    const endTime = process.hrtime.bigint();
    const duration = Number(endTime - startTime) / 1e6;
    logger.trace(`File processing completed in ${duration.toFixed(2)}ms`);

    return results;
  } catch (error) {
    logger.error('Error during file processing:', error);
    throw error;
  }
};
</file>

<file path="src/core/file/fileTreeGenerate.ts">
import nodepath from 'node:path';

interface TreeNode {
  name: string;
  children: TreeNode[];
  isDirectory: boolean;
}

const createTreeNode = (name: string, isDirectory: boolean): TreeNode => ({ name, children: [], isDirectory });

export const generateFileTree = (files: string[], emptyDirPaths: string[] = []): TreeNode => {
  const root: TreeNode = createTreeNode('root', true);

  for (const file of files) {
    addPathToTree(root, file, false);
  }

  // Add empty directories
  for (const dir of emptyDirPaths) {
    addPathToTree(root, dir, true);
  }

  return root;
};

const addPathToTree = (root: TreeNode, path: string, isDirectory: boolean): void => {
  const parts = path.split(nodepath.sep);
  let currentNode = root;

  for (let i = 0; i < parts.length; i++) {
    const part = parts[i];
    const isLastPart = i === parts.length - 1;
    let child = currentNode.children.find((c) => c.name === part);

    if (!child) {
      child = createTreeNode(part, !isLastPart || isDirectory);
      currentNode.children.push(child);
    }

    currentNode = child;
  }
};

const sortTreeNodes = (node: TreeNode) => {
  node.children.sort((a, b) => {
    if (a.isDirectory === b.isDirectory) {
      return a.name.localeCompare(b.name);
    }
    return a.isDirectory ? -1 : 1;
  });

  for (const child of node.children) {
    sortTreeNodes(child);
  }
};

export const treeToString = (node: TreeNode, prefix = ''): string => {
  sortTreeNodes(node);
  let result = '';

  for (const child of node.children) {
    result += `${prefix}${child.name}${child.isDirectory ? '/' : ''}\n`;
    if (child.isDirectory) {
      result += treeToString(child, `${prefix}  `);
    }
  }

  return result;
};

export const generateTreeString = (files: string[], emptyDirPaths: string[] = []): string => {
  const tree = generateFileTree(files, emptyDirPaths);
  return treeToString(tree).trim();
};
</file>

<file path="src/core/file/fileTypes.ts">
export interface RawFile {
  path: string;
  content: string;
}

export interface ProcessedFile {
  path: string;
  content: string;
}
</file>

<file path="src/core/file/packageJsonParse.ts">
import * as fs from 'node:fs/promises';
import path from 'node:path';
import * as url from 'node:url';
import { logger } from '../../shared/logger.js';

export const getVersion = async (): Promise<string> => {
  try {
    const packageJson = await parsePackageJson();

    if (!packageJson.version) {
      logger.warn('No version found in package.json');
      return 'unknown';
    }

    return packageJson.version;
  } catch (error) {
    logger.error('Error reading package.json:', error);
    return 'unknown';
  }
};

const parsePackageJson = async (): Promise<{
  name: string;
  version: string;
}> => {
  const dirName = url.fileURLToPath(new URL('.', import.meta.url));
  const packageJsonPath = path.join(dirName, '..', '..', '..', 'package.json');
  const packageJsonFile = await fs.readFile(packageJsonPath, 'utf-8');
  const packageJson = JSON.parse(packageJsonFile);
  return packageJson;
};
</file>

<file path="src/core/file/permissionCheck.ts">
import { constants } from 'node:fs';
import * as fs from 'node:fs/promises';
import { platform } from 'node:os';
import { logger } from '../../shared/logger.js';

export interface PermissionCheckResult {
  hasAllPermission: boolean;
  error?: Error;
  details?: {
    read?: boolean;
    write?: boolean;
    execute?: boolean;
  };
}

export class PermissionError extends Error {
  constructor(
    message: string,
    public readonly path: string,
    public readonly code?: string,
  ) {
    super(message);
    this.name = 'PermissionError';
  }
}

export const checkDirectoryPermissions = async (dirPath: string): Promise<PermissionCheckResult> => {
  try {
    // First try to read directory contents
    await fs.readdir(dirPath);

    // Check specific permissions
    const details = {
      read: false,
      write: false,
      execute: false,
    };

    try {
      await fs.access(dirPath, constants.R_OK);
      details.read = true;
    } catch {}

    try {
      await fs.access(dirPath, constants.W_OK);
      details.write = true;
    } catch {}

    try {
      await fs.access(dirPath, constants.X_OK);
      details.execute = true;
    } catch {}

    const hasAllPermissions = details.read && details.write && details.execute;

    if (!hasAllPermissions) {
      return {
        hasAllPermission: false,
        details,
      };
    }

    return {
      hasAllPermission: true,
      details,
    };
  } catch (error) {
    if (error instanceof Error && 'code' in error) {
      switch (error.code) {
        case 'EPERM':
        case 'EACCES':
        case 'EISDIR':
          return {
            hasAllPermission: false,
            error: new PermissionError(getMacOSPermissionMessage(dirPath, error.code), dirPath, error.code),
          };
        default:
          logger.debug('Directory permission check error:', error);
          return {
            hasAllPermission: false,
            error: error as Error,
          };
      }
    }
    return {
      hasAllPermission: false,
      error: error instanceof Error ? error : new Error(String(error)),
    };
  }
};

const getMacOSPermissionMessage = (dirPath: string, errorCode?: string): string => {
  if (platform() === 'darwin') {
    return `Permission denied: Cannot access '${dirPath}', error code: ${errorCode}.

This error often occurs when macOS security restrictions prevent access to the directory.
To fix this:

1. Open System Settings
2. Navigate to Privacy & Security > Files and Folders
3. Find your terminal app (Terminal.app, iTerm2, VS Code, etc.)
4. Grant necessary folder access permissions

If your terminal app is not listed:
- Try running repomix command again
- When prompted by macOS, click "Allow"
- Restart your terminal app if needed
`;
  }

  return `Permission denied: Cannot access '${dirPath}'`;
};
</file>

<file path="src/core/metrics/workers/fileMetricsWorker.ts">
import type { TiktokenEncoding } from 'tiktoken';
import { logger } from '../../../shared/logger.js';
import type { ProcessedFile } from '../../file/fileTypes.js';
import { TokenCounter } from '../../tokenCount/tokenCount.js';
import type { FileMetrics } from './types.js';

export interface FileMetricsTask {
  file: ProcessedFile;
  index: number;
  totalFiles: number;
  encoding: TiktokenEncoding;
}

// Worker-level singleton for TokenCounter
let tokenCounter: TokenCounter | null = null;

const getTokenCounter = (encoding: TiktokenEncoding): TokenCounter => {
  if (!tokenCounter) {
    tokenCounter = new TokenCounter(encoding);
  }
  return tokenCounter;
};

export default async ({ file, encoding }: FileMetricsTask): Promise<FileMetrics> => {
  const processStartAt = process.hrtime.bigint();
  const metrics = await calculateIndividualFileMetrics(file, encoding);
  const processEndAt = process.hrtime.bigint();
  logger.trace(
    `Calculated metrics for ${file.path}. Took: ${(Number(processEndAt - processStartAt) / 1e6).toFixed(2)}ms`,
  );

  return metrics;
};

export const calculateIndividualFileMetrics = async (
  file: ProcessedFile,
  encoding: TiktokenEncoding,
): Promise<FileMetrics> => {
  const charCount = file.content.length;
  const tokenCounter = getTokenCounter(encoding);
  const tokenCount = tokenCounter.countTokens(file.content, file.path);

  return { path: file.path, charCount, tokenCount };
};

// Cleanup when worker is terminated
process.on('exit', () => {
  if (tokenCounter) {
    tokenCounter.free();
    tokenCounter = null;
  }
});
</file>

<file path="src/core/metrics/workers/outputMetricsWorker.ts">
import type { TiktokenEncoding } from 'tiktoken';
import { logger } from '../../../shared/logger.js';
import { TokenCounter } from '../../tokenCount/tokenCount.js';

export interface OutputMetricsTask {
  content: string;
  encoding: TiktokenEncoding;
  path?: string;
}

// Worker-level singleton for TokenCounter
let tokenCounter: TokenCounter | null = null;

const getTokenCounter = (encoding: TiktokenEncoding): TokenCounter => {
  if (!tokenCounter) {
    tokenCounter = new TokenCounter(encoding);
  }
  return tokenCounter;
};

export default async ({ content, encoding, path }: OutputMetricsTask): Promise<number> => {
  const processStartAt = process.hrtime.bigint();
  const counter = getTokenCounter(encoding);
  const tokenCount = counter.countTokens(content, path);

  const processEndAt = process.hrtime.bigint();
  logger.trace(
    `Counted output tokens. Count: ${tokenCount}. Took: ${(Number(processEndAt - processStartAt) / 1e6).toFixed(2)}ms`,
  );

  return tokenCount;
};

// Cleanup when worker is terminated
process.on('exit', () => {
  if (tokenCounter) {
    tokenCounter.free();
    tokenCounter = null;
  }
});
</file>

<file path="src/core/metrics/workers/types.ts">
export interface FileMetrics {
  path: string;
  charCount: number;
  tokenCount: number;
}
</file>

<file path="src/core/metrics/calculateAllFileMetrics.ts">
import pc from 'picocolors';
import type { TiktokenEncoding } from 'tiktoken';
import { logger } from '../../shared/logger.js';
import { initPiscina } from '../../shared/processConcurrency.js';
import type { RepomixProgressCallback } from '../../shared/types.js';
import type { ProcessedFile } from '../file/fileTypes.js';
import type { FileMetricsTask } from './workers/fileMetricsWorker.js';
import type { FileMetrics } from './workers/types.js';

const initTaskRunner = (numOfTasks: number) => {
  const pool = initPiscina(numOfTasks, new URL('./workers/fileMetricsWorker.js', import.meta.url).href);
  return (task: FileMetricsTask) => pool.run(task);
};

export const calculateAllFileMetrics = async (
  processedFiles: ProcessedFile[],
  tokenCounterEncoding: TiktokenEncoding,
  progressCallback: RepomixProgressCallback,
  deps = {
    initTaskRunner,
  },
): Promise<FileMetrics[]> => {
  const runTask = deps.initTaskRunner(processedFiles.length);
  const tasks = processedFiles.map(
    (file, index) =>
      ({
        file,
        index,
        totalFiles: processedFiles.length,
        encoding: tokenCounterEncoding,
      }) satisfies FileMetricsTask,
  );

  try {
    const startTime = process.hrtime.bigint();
    logger.trace(`Starting metrics calculation for ${processedFiles.length} files using worker pool`);

    let completedTasks = 0;
    const results = await Promise.all(
      tasks.map((task) =>
        runTask(task).then((result) => {
          completedTasks++;
          progressCallback(`Calculating metrics... (${completedTasks}/${task.totalFiles}) ${pc.dim(task.file.path)}`);
          logger.trace(`Calculating metrics... (${completedTasks}/${task.totalFiles}) ${task.file.path}`);
          return result;
        }),
      ),
    );

    const endTime = process.hrtime.bigint();
    const duration = Number(endTime - startTime) / 1e6;
    logger.trace(`Metrics calculation completed in ${duration.toFixed(2)}ms`);

    return results;
  } catch (error) {
    logger.error('Error during metrics calculation:', error);
    throw error;
  }
};
</file>

<file path="src/core/metrics/calculateMetrics.ts">
import type { RepomixConfigMerged } from '../../config/configSchema.js';
import type { RepomixProgressCallback } from '../../shared/types.js';
import type { ProcessedFile } from '../file/fileTypes.js';
import { calculateAllFileMetrics } from './calculateAllFileMetrics.js';
import { calculateOutputMetrics } from './calculateOutputMetrics.js';

export interface CalculateMetricsResult {
  totalFiles: number;
  totalCharacters: number;
  totalTokens: number;
  fileCharCounts: Record<string, number>;
  fileTokenCounts: Record<string, number>;
}

export const calculateMetrics = async (
  processedFiles: ProcessedFile[],
  output: string,
  progressCallback: RepomixProgressCallback,
  config: RepomixConfigMerged,
  deps = {
    calculateAllFileMetrics,
    calculateOutputMetrics,
  },
): Promise<CalculateMetricsResult> => {
  progressCallback('Calculating metrics...');

  const [fileMetrics, totalTokens] = await Promise.all([
    deps.calculateAllFileMetrics(processedFiles, config.tokenCount.encoding, progressCallback),
    deps.calculateOutputMetrics(output, config.tokenCount.encoding, config.output.filePath),
  ]);

  const totalFiles = processedFiles.length;
  const totalCharacters = output.length;

  const fileCharCounts: Record<string, number> = {};
  const fileTokenCounts: Record<string, number> = {};
  for (const file of fileMetrics) {
    fileCharCounts[file.path] = file.charCount;
    fileTokenCounts[file.path] = file.tokenCount;
  }

  return {
    totalFiles,
    totalCharacters,
    totalTokens,
    fileCharCounts,
    fileTokenCounts,
  };
};
</file>

<file path="src/core/metrics/calculateOutputMetrics.ts">
import type { TiktokenEncoding } from 'tiktoken';
import { logger } from '../../shared/logger.js';
import { initPiscina } from '../../shared/processConcurrency.js';
import type { OutputMetricsTask } from './workers/outputMetricsWorker.js';

const CHUNK_SIZE = 1000;
const MIN_CONTENT_LENGTH_FOR_PARALLEL = 1_000_000; // 1000KB

const initTaskRunner = (numOfTasks: number) => {
  const pool = initPiscina(numOfTasks, new URL('./workers/outputMetricsWorker.js', import.meta.url).href);
  return (task: OutputMetricsTask) => pool.run(task);
};

export const calculateOutputMetrics = async (
  content: string,
  encoding: TiktokenEncoding,
  path?: string,
  deps = {
    initTaskRunner,
  },
): Promise<number> => {
  const shouldRunInParallel = content.length > MIN_CONTENT_LENGTH_FOR_PARALLEL;
  const numOfTasks = shouldRunInParallel ? CHUNK_SIZE : 1;
  const runTask = deps.initTaskRunner(numOfTasks);

  try {
    logger.trace(`Starting output token count for ${path || 'output'}`);
    const startTime = process.hrtime.bigint();

    let result: number;

    if (shouldRunInParallel) {
      // Split content into chunks for parallel processing
      const chunkSize = Math.ceil(content.length / CHUNK_SIZE);
      const chunks: string[] = [];

      for (let i = 0; i < content.length; i += chunkSize) {
        chunks.push(content.slice(i, i + chunkSize));
      }

      // Process chunks in parallel
      const chunkResults = await Promise.all(
        chunks.map((chunk, index) =>
          runTask({
            content: chunk,
            encoding,
            path: path ? `${path}-chunk-${index}` : undefined,
          }),
        ),
      );

      // Sum up the results
      result = chunkResults.reduce((sum, count) => sum + count, 0);
    } else {
      // Process small content directly
      result = await runTask({ content, encoding, path });
    }

    const endTime = process.hrtime.bigint();
    const duration = Number(endTime - startTime) / 1e6;
    logger.trace(`Output token count completed in ${duration.toFixed(2)}ms`);

    return result;
  } catch (error) {
    logger.error('Error during token count:', error);
    throw error;
  }
};
</file>

<file path="src/core/output/outputStyles/markdownStyle.ts">
import Handlebars from 'handlebars';

export const getMarkdownTemplate = () => {
  return /* md */ `
{{{generationHeader}}}

{{#if fileSummaryEnabled}}
# File Summary

## Purpose
{{{summaryPurpose}}}

## File Format
{{{summaryFileFormat}}}
4. Multiple file entries, each consisting of:
  a. A header with the file path (## File: path/to/file)
  b. The full contents of the file in a code block

## Usage Guidelines
{{{summaryUsageGuidelines}}}

## Notes
{{{summaryNotes}}}

## Additional Info
{{#if headerText}}
### User Provided Header
{{{headerText}}}
{{/if}}

{{/if}}
{{#if directoryStructureEnabled}}
# Directory Structure
\`\`\`
{{{treeString}}}
\`\`\`

{{/if}}
# Files

{{#each processedFiles}}
## File: {{{this.path}}}
{{{../markdownCodeBlockDelimiter}}}{{{getFileExtension this.path}}}
{{{this.content}}}
{{{../markdownCodeBlockDelimiter}}}

{{/each}}

{{#if instruction}}
# Instruction
{{{instruction}}}
{{/if}}
`;
};

Handlebars.registerHelper('getFileExtension', (filePath) => {
  const extension = filePath.split('.').pop()?.toLowerCase();
  switch (extension) {
    case 'js':
    case 'jsx':
      return 'javascript';
    case 'ts':
    case 'tsx':
      return 'typescript';
    case 'vue':
      return 'vue';
    case 'py':
      return 'python';
    case 'rb':
      return 'ruby';
    case 'java':
      return 'java';
    case 'c':
    case 'cpp':
      return 'cpp';
    case 'cs':
      return 'csharp';
    case 'go':
      return 'go';
    case 'rs':
      return 'rust';
    case 'php':
      return 'php';
    case 'swift':
      return 'swift';
    case 'kt':
      return 'kotlin';
    case 'scala':
      return 'scala';
    case 'html':
      return 'html';
    case 'css':
      return 'css';
    case 'scss':
    case 'sass':
      return 'scss';
    case 'json':
      return 'json';
    case 'json5':
      return 'json5';
    case 'xml':
      return 'xml';
    case 'yaml':
    case 'yml':
      return 'yaml';
    case 'md':
      return 'markdown';
    case 'sh':
    case 'bash':
      return 'bash';
    case 'sql':
      return 'sql';
    case 'dockerfile':
      return 'dockerfile';
    case 'dart':
      return 'dart';
    case 'fs':
    case 'fsx':
      return 'fsharp';
    case 'r':
      return 'r';
    case 'pl':
    case 'pm':
      return 'perl';
    case 'lua':
      return 'lua';
    case 'groovy':
      return 'groovy';
    case 'hs':
      return 'haskell';
    case 'ex':
    case 'exs':
      return 'elixir';
    case 'erl':
      return 'erlang';
    case 'clj':
    case 'cljs':
      return 'clojure';
    case 'ps1':
      return 'powershell';
    case 'vb':
      return 'vb';
    case 'coffee':
      return 'coffeescript';
    case 'tf':
    case 'tfvars':
      return 'hcl';
    case 'proto':
      return 'protobuf';
    case 'pug':
      return 'pug';
    case 'graphql':
    case 'gql':
      return 'graphql';
    case 'toml':
      return 'toml';
    default:
      return '';
  }
});
</file>

<file path="src/core/output/outputStyles/plainStyle.ts">
const PLAIN_SEPARATOR = '='.repeat(16);
const PLAIN_LONG_SEPARATOR = '='.repeat(64);

export const getPlainTemplate = () => {
  return `
{{{generationHeader}}}

{{#if fileSummaryEnabled}}
${PLAIN_LONG_SEPARATOR}
File Summary
${PLAIN_LONG_SEPARATOR}

Purpose:
--------
{{{summaryPurpose}}}

File Format:
------------
{{{summaryFileFormat}}}
4. Multiple file entries, each consisting of:
  a. A separator line (================)
  b. The file path (File: path/to/file)
  c. Another separator line
  d. The full contents of the file
  e. A blank line

Usage Guidelines:
-----------------
{{{summaryUsageGuidelines}}}

Notes:
------
{{{summaryNotes}}}

Additional Info:
----------------
{{#if headerText}}
User Provided Header:
-----------------------
{{{headerText}}}
{{/if}}

{{/if}}
{{#if directoryStructureEnabled}}
${PLAIN_LONG_SEPARATOR}
Directory Structure
${PLAIN_LONG_SEPARATOR}
{{{treeString}}}

{{/if}}
${PLAIN_LONG_SEPARATOR}
Files
${PLAIN_LONG_SEPARATOR}

{{#each processedFiles}}
${PLAIN_SEPARATOR}
File: {{{this.path}}}
${PLAIN_SEPARATOR}
{{{this.content}}}

{{/each}}

{{#if instruction}}
${PLAIN_LONG_SEPARATOR}
Instruction
${PLAIN_LONG_SEPARATOR}
{{{instruction}}}
{{/if}}

${PLAIN_LONG_SEPARATOR}
End of Codebase
${PLAIN_LONG_SEPARATOR}
`;
};
</file>

<file path="src/core/output/outputStyles/xmlStyle.ts">
export const getXmlTemplate = () => {
  return /* xml */ `
{{{generationHeader}}}

{{#if fileSummaryEnabled}}
<file_summary>
This section contains a summary of this file.

<purpose>
{{{summaryPurpose}}}
</purpose>

<file_format>
{{{summaryFileFormat}}}
4. Repository files, each consisting of:
  - File path as an attribute
  - Full contents of the file
</file_format>

<usage_guidelines>
{{{summaryUsageGuidelines}}}
</usage_guidelines>

<notes>
{{{summaryNotes}}}
</notes>

<additional_info>
{{#if headerText}}
<user_provided_header>
{{{headerText}}}
</user_provided_header>
{{/if}}

</additional_info>

</file_summary>

{{/if}}
{{#if directoryStructureEnabled}}
<directory_structure>
{{{treeString}}}
</directory_structure>

{{/if}}
<files>
This section contains the contents of the repository's files.

{{#each processedFiles}}
<file path="{{{this.path}}}">
{{{this.content}}}
</file>

{{/each}}
</files>

{{#if instruction}}
<instruction>
{{{instruction}}}
</instruction>
{{/if}}
`;
};
</file>

<file path="src/core/output/outputGeneratorTypes.ts">
import type { RepomixConfigMerged } from '../../config/configSchema.js';
import type { ProcessedFile } from '../file/fileTypes.js';

export interface OutputGeneratorContext {
  generationDate: string;
  treeString: string;
  processedFiles: ProcessedFile[];
  config: RepomixConfigMerged;
  instruction: string;
}

export interface RenderContext {
  readonly generationHeader: string;
  readonly summaryPurpose: string;
  readonly summaryFileFormat: string;
  readonly summaryUsageGuidelines: string;
  readonly summaryNotes: string;
  readonly headerText: string | undefined;
  readonly instruction: string;
  readonly treeString: string;
  readonly processedFiles: ReadonlyArray<ProcessedFile>;
  readonly fileSummaryEnabled: boolean;
  readonly directoryStructureEnabled: boolean;
  readonly escapeFileContent: boolean;
  readonly markdownCodeBlockDelimiter: string;
}
</file>

<file path="src/core/packager/copyToClipboardIfEnabled.ts">
import clipboard from 'clipboardy';
import type { RepomixConfigMerged } from '../../config/configSchema.js';
import { logger } from '../../shared/logger.js';
import type { RepomixProgressCallback } from '../../shared/types.js';

// Additionally copy to clipboard if flag is raised
export const copyToClipboardIfEnabled = async (
  output: string,
  progressCallback: RepomixProgressCallback,
  config: RepomixConfigMerged,
): Promise<undefined> => {
  if (config.output.copyToClipboard) {
    progressCallback('Copying to clipboard...');
    logger.trace('Copying output to clipboard');
    await clipboard.write(output);
  }
};
</file>

<file path="src/core/security/filterOutUntrustedFiles.ts">
import type { RawFile } from '../file/fileTypes.js';
import type { SuspiciousFileResult } from './securityCheck.js';

export const filterOutUntrustedFiles = (
  rawFiles: RawFile[],
  suspiciousFilesResults: SuspiciousFileResult[],
): RawFile[] =>
  rawFiles.filter((rawFile) => !suspiciousFilesResults.some((result) => result.filePath === rawFile.path));
</file>

<file path="src/core/security/securityCheck.ts">
import pc from 'picocolors';
import { logger } from '../../shared/logger.js';
import { initPiscina } from '../../shared/processConcurrency.js';
import type { RepomixProgressCallback } from '../../shared/types.js';
import type { RawFile } from '../file/fileTypes.js';
import type { SecurityCheckTask } from './workers/securityCheckWorker.js';

export interface SuspiciousFileResult {
  filePath: string;
  messages: string[];
}

const initTaskRunner = (numOfTasks: number) => {
  const pool = initPiscina(numOfTasks, new URL('./workers/securityCheckWorker.js', import.meta.url).href);
  return (task: SecurityCheckTask) => pool.run(task);
};

export const runSecurityCheck = async (
  rawFiles: RawFile[],
  progressCallback: RepomixProgressCallback = () => {},
  deps = {
    initTaskRunner,
  },
): Promise<SuspiciousFileResult[]> => {
  const runTask = deps.initTaskRunner(rawFiles.length);
  const tasks = rawFiles.map(
    (file) =>
      ({
        filePath: file.path,
        content: file.content,
      }) satisfies SecurityCheckTask,
  );

  try {
    logger.trace(`Starting security check for ${tasks.length} files`);
    const startTime = process.hrtime.bigint();

    let completedTasks = 0;
    const totalTasks = tasks.length;

    const results = await Promise.all(
      tasks.map((task) =>
        runTask(task).then((result) => {
          completedTasks++;
          progressCallback(`Running security check... (${completedTasks}/${totalTasks}) ${pc.dim(task.filePath)}`);
          logger.trace(`Running security check... (${completedTasks}/${totalTasks}) ${task.filePath}`);
          return result;
        }),
      ),
    );

    const endTime = process.hrtime.bigint();
    const duration = Number(endTime - startTime) / 1e6;
    logger.trace(`Security check completed in ${duration.toFixed(2)}ms`);

    return results.filter((result): result is SuspiciousFileResult => result !== null);
  } catch (error) {
    logger.error('Error during security check:', error);
    throw error;
  }
};
</file>

<file path="src/core/security/validateFileSafety.ts">
import type { RepomixConfigMerged } from '../../config/configSchema.js';
import { logger } from '../../shared/logger.js';
import type { RepomixProgressCallback } from '../../shared/types.js';
import type { RawFile } from '../file/fileTypes.js';
import { filterOutUntrustedFiles } from './filterOutUntrustedFiles.js';
import { type SuspiciousFileResult, runSecurityCheck } from './securityCheck.js';

// marks which files are suspicious and which are safe
export const validateFileSafety = async (
  rawFiles: RawFile[],
  progressCallback: RepomixProgressCallback,
  config: RepomixConfigMerged,
  deps = {
    runSecurityCheck,
    filterOutUntrustedFiles,
  },
) => {
  let suspiciousFilesResults: SuspiciousFileResult[] = [];

  if (config.security.enableSecurityCheck) {
    progressCallback('Running security check...');
    suspiciousFilesResults = await deps.runSecurityCheck(rawFiles, progressCallback);
  }

  const safeRawFiles = deps.filterOutUntrustedFiles(rawFiles, suspiciousFilesResults);
  const safeFilePaths = safeRawFiles.map((file) => file.path);
  logger.trace('Safe files count:', safeRawFiles.length);

  return {
    safeRawFiles,
    safeFilePaths,
    suspiciousFilesResults,
  };
};
</file>

<file path="src/core/tokenCount/tokenCount.ts">
import { type Tiktoken, type TiktokenEncoding, get_encoding } from 'tiktoken';
import { logger } from '../../shared/logger.js';

export class TokenCounter {
  private encoding: Tiktoken;

  constructor(encodingName: TiktokenEncoding) {
    // Setup encoding with the specified model
    this.encoding = get_encoding(encodingName);
  }

  public countTokens(content: string, filePath?: string): number {
    try {
      return this.encoding.encode(content).length;
    } catch (error) {
      let message = '';
      if (error instanceof Error) {
        message = error.message;
      } else {
        message = String(error);
      }

      if (filePath) {
        logger.warn(`Failed to count tokens. path: ${filePath}, error: ${message}`);
      } else {
        logger.warn(`Failed to count tokens. error: ${message}`);
      }

      return 0;
    }
  }

  public free(): void {
    this.encoding.free();
  }
}
</file>

<file path="src/core/treeSitter/parseStrategies/DefaultParseStrategy.ts">
import type { SyntaxNode } from 'web-tree-sitter';
import type { ParseContext, ParseStrategy } from './ParseStrategy.js';

export class DefaultParseStrategy implements ParseStrategy {
  parseCapture(
    capture: { node: SyntaxNode; name: string },
    lines: string[],
    processedChunks: Set<string>,
    context: ParseContext,
  ): string | null {
    const { node, name } = capture;
    const startRow = node.startPosition.row;
    const endRow = node.endPosition.row;

    if (!lines[startRow]) {
      return null;
    }

    const isNameCapture = name.includes('name');
    const isCommentCapture = name.includes('comment');
    const isImportCapture = name.includes('import') || name.includes('require');
    const shouldSelect = isNameCapture || isCommentCapture || isImportCapture;

    if (!shouldSelect) {
      return null;
    }

    const selectedLines = lines.slice(startRow, endRow + 1);
    if (selectedLines.length < 1) {
      return null;
    }

    const chunk = selectedLines.join('\n');
    const normalizedChunk = chunk.trim();

    if (processedChunks.has(normalizedChunk)) {
      return null;
    }

    processedChunks.add(normalizedChunk);
    return chunk;
  }
}
</file>

<file path="src/core/treeSitter/parseStrategies/GoParseStrategy.ts">
import type { SyntaxNode } from 'web-tree-sitter';
import type { ParseContext, ParseStrategy } from './ParseStrategy.js';

enum CaptureType {
  Comment = 'comment',
  Type = 'definition.type',
  Interface = 'definition.interface',
  Struct = 'definition.struct',
  Package = 'definition.package',
  Import = 'definition.import',
  Function = 'definition.function',
  Method = 'definition.method',
  Module = 'definition.module',
  Variable = 'definition.variable',
  Constant = 'definition.constant',
}

type ParseResult = {
  content: string | null;
  processedSignatures?: Set<string>;
};

export class GoParseStrategy implements ParseStrategy {
  parseCapture(
    capture: { node: SyntaxNode; name: string },
    lines: string[],
    processedChunks: Set<string>,
    context: ParseContext,
  ): string | null {
    const { node, name } = capture;
    const startRow = node.startPosition.row;
    const endRow = node.endPosition.row;

    if (!lines[startRow]) {
      return null;
    }

    const captureTypes = this.getCaptureType(name);

    // Comments
    if (captureTypes.has(CaptureType.Comment)) {
      return this.parseBlockDeclaration(lines, startRow, endRow, processedChunks).content;
    }

    // Package declarations
    if (captureTypes.has(CaptureType.Package) || captureTypes.has(CaptureType.Module)) {
      return this.parseSimpleDeclaration(lines, startRow, processedChunks).content;
    }

    // Import declarations
    if (captureTypes.has(CaptureType.Import)) {
      return lines[startRow].includes('(')
        ? this.parseBlockDeclaration(lines, startRow, endRow, processedChunks).content
        : this.parseSimpleDeclaration(lines, startRow, processedChunks).content;
    }

    // Variable declarations
    if (captureTypes.has(CaptureType.Variable)) {
      return this.parseBlockDeclaration(lines, startRow, endRow, processedChunks).content;
    }

    // Constant declarations
    if (captureTypes.has(CaptureType.Constant)) {
      return this.parseBlockDeclaration(lines, startRow, endRow, processedChunks).content;
    }

    // Type definitions
    if (
      captureTypes.has(CaptureType.Type) ||
      captureTypes.has(CaptureType.Interface) ||
      captureTypes.has(CaptureType.Struct)
    ) {
      return this.parseTypeDefinition(lines, startRow, endRow, processedChunks).content;
    }

    // Function declarations
    if (captureTypes.has(CaptureType.Function)) {
      return this.parseFunctionOrMethod(lines, startRow, endRow, processedChunks, false).content;
    }

    // Method declarations
    if (captureTypes.has(CaptureType.Method)) {
      return this.parseFunctionOrMethod(lines, startRow, endRow, processedChunks, true).content;
    }

    return null;
  }

  private getCaptureType(name: string): Set<CaptureType> {
    const types = new Set<CaptureType>();
    for (const type of Object.values(CaptureType)) {
      if (name.includes(type)) {
        types.add(type);
      }
    }
    return types;
  }

  private getFunctionName(lines: string[], startRow: number): string | null {
    const line = lines[startRow];
    // "func funcName(" pattern detection
    const match = line.match(/func\s+([A-Za-z0-9_]+)\s*\(/);
    if (match?.[1]) {
      return match[1];
    }
    return null;
  }

  // Helper to get method name including receiver type
  private getMethodWithReceiver(lines: string[], startRow: number): string | null {
    const line = lines[startRow];
    // "func (r ReceiverType) methodName(" pattern detection
    const match = line.match(/func\s+\(([^)]+)\)\s+([A-Za-z0-9_]+)\s*\(/);
    if (match?.[2]) {
      return match[2];
    }
    return null;
  }

  private findClosingToken(
    lines: string[],
    startRow: number,
    endRow: number,
    openToken: string,
    closeToken: string,
  ): number {
    for (let i = startRow; i <= endRow; i++) {
      if (lines[i].includes(closeToken)) {
        return i;
      }
    }
    return startRow;
  }

  private parseSimpleDeclaration(lines: string[], startRow: number, processedChunks: Set<string>): ParseResult {
    const declaration = lines[startRow].trim();
    if (processedChunks.has(declaration)) {
      return { content: null };
    }
    processedChunks.add(declaration);
    return { content: declaration };
  }

  private parseBlockDeclaration(
    lines: string[],
    startRow: number,
    endRow: number,
    processedChunks: Set<string>,
  ): ParseResult {
    const blockEndRow = lines[startRow].includes('(')
      ? this.findClosingToken(lines, startRow, endRow, '(', ')')
      : endRow;

    const declaration = lines.slice(startRow, blockEndRow + 1).join('\n');
    if (processedChunks.has(declaration)) {
      return { content: null };
    }
    processedChunks.add(declaration);
    return { content: declaration };
  }

  private parseFunctionOrMethod(
    lines: string[],
    startRow: number,
    endRow: number,
    processedChunks: Set<string>,
    isMethod: boolean,
  ): ParseResult {
    const nameKey = isMethod ? 'method' : 'func';
    const getName = isMethod ? this.getMethodWithReceiver : this.getFunctionName;
    const name = getName.call(this, lines, startRow);

    if (name && processedChunks.has(`${nameKey}:${name}`)) {
      return { content: null };
    }

    const signatureEndRow = this.findClosingToken(lines, startRow, endRow, '{', '{');
    const signature = lines
      .slice(startRow, signatureEndRow + 1)
      .join('\n')
      .trim();
    const cleanSignature = signature.split('{')[0].trim();

    if (processedChunks.has(cleanSignature)) {
      return { content: null };
    }

    processedChunks.add(cleanSignature);
    if (name) {
      processedChunks.add(`${nameKey}:${name}`);
    }
    return { content: cleanSignature };
  }

  private parseTypeDefinition(
    lines: string[],
    startRow: number,
    endRow: number,
    processedChunks: Set<string>,
  ): ParseResult {
    const signatureEndRow = lines[startRow].includes('{')
      ? this.findClosingToken(lines, startRow, endRow, '{', '}')
      : endRow;

    const definition = lines.slice(startRow, signatureEndRow + 1).join('\n');
    if (processedChunks.has(definition)) {
      return { content: null };
    }
    processedChunks.add(definition);
    return { content: definition };
  }
}
</file>

<file path="src/core/treeSitter/parseStrategies/PythonParseStrategy.ts">
import type { SyntaxNode } from 'web-tree-sitter';
import type { ParseContext, ParseStrategy } from './ParseStrategy.js';

enum CaptureType {
  Comment = 'comment',
  Class = 'definition.class',
  Function = 'definition.function',
  Docstring = 'docstring',
  TypeAlias = 'definition.type_alias',
}

type ParseResult = {
  content: string | null;
  processedSignatures?: Set<string>;
};

export class PythonParseStrategy implements ParseStrategy {
  parseCapture(
    capture: { node: SyntaxNode; name: string },
    lines: string[],
    processedChunks: Set<string>,
    context: ParseContext,
  ): string | null {
    const { node, name } = capture;
    const startRow = node.startPosition.row;
    const endRow = node.endPosition.row;

    if (!lines[startRow]) {
      return null;
    }

    const captureTypes = this.getCaptureType(name);

    // Class definition
    if (captureTypes.has(CaptureType.Class)) {
      return this.parseClassDefinition(lines, startRow, processedChunks).content;
    }

    // Function definition
    if (captureTypes.has(CaptureType.Function)) {
      return this.parseFunctionDefinition(lines, startRow, processedChunks).content;
    }

    // Docstring
    if (captureTypes.has(CaptureType.Docstring)) {
      return this.parseDocstringOrComment(lines, startRow, endRow, processedChunks).content;
    }

    // Comment
    if (captureTypes.has(CaptureType.Comment)) {
      return this.parseDocstringOrComment(lines, startRow, endRow, processedChunks).content;
    }

    // Type alias
    if (captureTypes.has(CaptureType.TypeAlias)) {
      return this.parseTypeAlias(lines, startRow, processedChunks).content;
    }

    return null;
  }

  private getCaptureType(name: string): Set<CaptureType> {
    const types = new Set<CaptureType>();
    for (const type of Object.values(CaptureType)) {
      if (name.includes(type)) {
        types.add(type);
      }
    }
    return types;
  }

  private getDecorators(lines: string[], startRow: number): string[] {
    const decorators: string[] = [];
    let currentRow = startRow - 1;

    while (currentRow >= 0) {
      const line = lines[currentRow].trim();
      if (line.startsWith('@')) {
        decorators.unshift(line); // Add to beginning to maintain order
      } else {
        break;
      }
      currentRow--;
    }

    return decorators;
  }

  private getClassInheritance(lines: string[], startRow: number): string | null {
    const line = lines[startRow];
    const match = line.match(/class\s+\w+\s*\((.*?)\):/);
    return match ? line.replace(/:\s*$/, '') : line.replace(/:\s*$/, '');
  }

  private getFunctionSignature(lines: string[], startRow: number): string | null {
    const line = lines[startRow];
    const match = line.match(/def\s+(\w+)\s*\((.*?)\)(\s*->\s*[^:]+)?:/);
    if (!match) return null;
    return line.replace(/:\s*$/, '');
  }

  private parseClassDefinition(lines: string[], startRow: number, processedChunks: Set<string>): ParseResult {
    const decorators = this.getDecorators(lines, startRow);
    const classDefinition = this.getClassInheritance(lines, startRow);
    const fullDefinition = [...decorators, classDefinition].join('\n');

    if (processedChunks.has(fullDefinition)) {
      return { content: null };
    }

    processedChunks.add(fullDefinition);
    return { content: fullDefinition };
  }

  private parseFunctionDefinition(lines: string[], startRow: number, processedChunks: Set<string>): ParseResult {
    const decorators = this.getDecorators(lines, startRow);
    const signature = this.getFunctionSignature(lines, startRow);

    if (!signature) {
      return { content: null };
    }

    const fullDefinition = [...decorators, signature].join('\n');
    if (processedChunks.has(fullDefinition)) {
      return { content: null };
    }

    processedChunks.add(fullDefinition);
    return { content: fullDefinition };
  }

  private parseDocstringOrComment(
    lines: string[],
    startRow: number,
    endRow: number,
    processedChunks: Set<string>,
  ): ParseResult {
    const content = lines.slice(startRow, endRow + 1).join('\n');

    if (processedChunks.has(content)) {
      return { content: null };
    }

    processedChunks.add(content);
    return { content };
  }

  private parseTypeAlias(lines: string[], startRow: number, processedChunks: Set<string>): ParseResult {
    const typeAlias = lines[startRow].trim();

    if (processedChunks.has(typeAlias)) {
      return { content: null };
    }

    processedChunks.add(typeAlias);
    return { content: typeAlias };
  }
}
</file>

<file path="src/core/treeSitter/parseStrategies/TypeScriptParseStrategy.ts">
import type { SyntaxNode } from 'web-tree-sitter';
import type { ParseContext, ParseStrategy } from './ParseStrategy.js';

enum CaptureType {
  Comment = 'comment',
  Interface = 'definition.interface',
  Type = 'definition.type',
  Enum = 'definition.enum',
  Class = 'definition.class',
  Import = 'definition.import',
  Function = 'definition.function',
  Method = 'definition.method',
  Property = 'definition.property',
}

type ParseResult = {
  content: string | null;
  processedSignatures?: Set<string>;
};

export class TypeScriptParseStrategy implements ParseStrategy {
  private static readonly FUNCTION_NAME_PATTERN = /(?:export\s+)?(?:const|let|var)\s+([a-zA-Z0-9_$]+)\s*=/;

  parseCapture(
    capture: { node: SyntaxNode; name: string },
    lines: string[],
    processedChunks: Set<string>,
    context: ParseContext,
  ): string | null {
    const { node, name } = capture;
    const startRow = node.startPosition.row;
    const endRow = node.endPosition.row;

    if (!lines[startRow]) {
      return null;
    }

    const captureTypes = this.getCaptureType(name);

    // Function capture
    if (captureTypes.has(CaptureType.Function) || captureTypes.has(CaptureType.Method)) {
      return this.parseFunctionDefinition(lines, startRow, endRow, processedChunks).content;
    }

    // Class capture
    if (captureTypes.has(CaptureType.Class)) {
      return this.parseClassDefinition(lines, startRow, endRow, processedChunks).content;
    }

    // Type definition or import capture
    if (
      captureTypes.has(CaptureType.Interface) ||
      captureTypes.has(CaptureType.Type) ||
      captureTypes.has(CaptureType.Enum) ||
      captureTypes.has(CaptureType.Import)
    ) {
      return this.parseTypeOrImport(lines, startRow, endRow, processedChunks).content;
    }

    // Comment capture
    if (captureTypes.has(CaptureType.Comment)) {
      return lines
        .slice(startRow, endRow + 1)
        .join('\n')
        .trim();
    }

    return null;
  }

  private getFunctionName(lines: string[], startRow: number): string | null {
    const line = lines[startRow];
    const match = line.match(TypeScriptParseStrategy.FUNCTION_NAME_PATTERN);
    return match?.[1] ?? null;
  }

  private getCaptureType(name: string): Set<CaptureType> {
    const types = new Set<CaptureType>();
    for (const type of Object.values(CaptureType)) {
      if (name.includes(type)) {
        types.add(type);
      }
    }
    return types;
  }

  private parseFunctionDefinition(
    lines: string[],
    startRow: number,
    endRow: number,
    processedChunks: Set<string>,
  ): ParseResult {
    const functionName = this.getFunctionName(lines, startRow);
    if (functionName && processedChunks.has(`func:${functionName}`)) {
      return { content: null };
    }

    const signatureEndRow = this.findSignatureEnd(lines, startRow, endRow);
    const selectedLines = lines.slice(startRow, signatureEndRow + 1);
    const cleanedSignature = this.cleanFunctionSignature(selectedLines);

    if (processedChunks.has(cleanedSignature)) {
      return { content: null };
    }

    processedChunks.add(cleanedSignature);
    if (functionName) {
      processedChunks.add(`func:${functionName}`);
    }

    return { content: cleanedSignature };
  }

  private findSignatureEnd(lines: string[], startRow: number, endRow: number): number {
    for (let i = startRow; i <= endRow; i++) {
      const line = lines[i].trim();
      if (line.includes(')') && (line.endsWith('{') || line.endsWith('=>') || line.endsWith(';'))) {
        return i;
      }
    }
    return startRow;
  }

  private cleanFunctionSignature(lines: string[]): string {
    const result = [...lines];
    const lastLineIndex = result.length - 1;
    const lastLine = result[lastLineIndex];

    if (lastLine) {
      if (lastLine.includes('{')) {
        result[lastLineIndex] = lastLine.substring(0, lastLine.indexOf('{')).trim();
      } else if (lastLine.includes('=>')) {
        result[lastLineIndex] = lastLine.substring(0, lastLine.indexOf('=>')).trim();
      }
    }

    return result.join('\n').trim();
  }

  private parseClassDefinition(
    lines: string[],
    startRow: number,
    endRow: number,
    processedChunks: Set<string>,
  ): ParseResult {
    const selectedLines = [lines[startRow]];

    if (startRow + 1 <= endRow) {
      const nextLine = lines[startRow + 1].trim();
      if (nextLine.includes('extends') || nextLine.includes('implements')) {
        selectedLines.push(nextLine);
      }
    }

    const cleanedLines = selectedLines.map((line) => line.replace(/\{.*$/, '').trim());
    const definition = cleanedLines.join('\n').trim();

    if (processedChunks.has(definition)) {
      return { content: null };
    }

    processedChunks.add(definition);
    return { content: definition };
  }

  private parseTypeOrImport(
    lines: string[],
    startRow: number,
    endRow: number,
    processedChunks: Set<string>,
  ): ParseResult {
    const selectedLines = lines.slice(startRow, endRow + 1);
    const definition = selectedLines.join('\n').trim();

    if (processedChunks.has(definition)) {
      return { content: null };
    }

    processedChunks.add(definition);
    return { content: definition };
  }
}
</file>

<file path="src/core/treeSitter/queries/queryC.ts">
export const queryC = `
(comment) @comment

(struct_specifier name: (type_identifier) @name.definition.class body:(_)) @definition.class

(declaration type: (union_specifier name: (type_identifier) @name.definition.class)) @definition.class

(function_declarator declarator: (identifier) @name.definition.function) @definition.function

(type_definition declarator: (type_identifier) @name.definition.type) @definition.type

(enum_specifier name: (type_identifier) @name.definition.type) @definition.type
`;
</file>

<file path="src/core/treeSitter/queries/queryCpp.ts">
export const queryCpp = `
(comment) @comment

(struct_specifier name: (type_identifier) @name.definition.class body:(_)) @definition.class

(declaration type: (union_specifier name: (type_identifier) @name.definition.class)) @definition.class

(function_declarator declarator: (identifier) @name.definition.function) @definition.function

(function_declarator declarator: (field_identifier) @name.definition.function) @definition.function

(function_declarator declarator: (qualified_identifier scope: (namespace_identifier) @scope name: (identifier) @name.definition.method)) @definition.method

(type_definition declarator: (type_identifier) @name.definition.type) @definition.type

(enum_specifier name: (type_identifier) @name.definition.type) @definition.type

(class_specifier name: (type_identifier) @name.definition.class) @definition.class
`;
</file>

<file path="src/core/treeSitter/queries/queryCSharp.ts">
export const queryCSharp = `
(comment) @comment

(class_declaration
 name: (identifier) @name.definition.class
 ) @definition.class

(class_declaration
   bases: (base_list (_) @name.reference.class)
 ) @reference.class

(interface_declaration
 name: (identifier) @name.definition.interface
 ) @definition.interface

(interface_declaration
 bases: (base_list (_) @name.reference.interface)
 ) @reference.interface

(method_declaration
 name: (identifier) @name.definition.method
 ) @definition.method

(object_creation_expression
 type: (identifier) @name.reference.class
 ) @reference.class

(type_parameter_constraints_clause
 target: (identifier) @name.reference.class
 ) @reference.class

(type_constraint
 type: (identifier) @name.reference.class
 ) @reference.class

(variable_declaration
 type: (identifier) @name.reference.class
 ) @reference.class

(invocation_expression
 function:
  (member_access_expression
    name: (identifier) @name.reference.send
 )
) @reference.send

(namespace_declaration
 name: (identifier) @name.definition.module
) @definition.module
`;
</file>

<file path="src/core/treeSitter/queries/queryJavascript.ts">
export const queryJavascript = `
(comment) @comment

(
  (comment)* @doc
  .
  (method_definition
    name: (property_identifier) @name.definition.method) @definition.method
  (#not-eq? @name.definition.method "constructor")
  (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
  (#select-adjacent! @doc @definition.method)
)

(
  (comment)* @doc
  .
  [
    (class
      name: (_) @name.definition.class)
    (class_declaration
      name: (_) @name.definition.class)
  ] @definition.class
  (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
  (#select-adjacent! @doc @definition.class)
)

(
  (comment)* @doc
  .
  [
    (function_declaration
      name: (identifier) @name.definition.function)
    (generator_function
      name: (identifier) @name.definition.function)
    (generator_function_declaration
      name: (identifier) @name.definition.function)
  ] @definition.function
  (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
  (#select-adjacent! @doc @definition.function)
)

(
  (comment)* @doc
  .
  (lexical_declaration
    (variable_declarator
      name: (identifier) @name.definition.function
      value: [(arrow_function) (function_declaration)]) @definition.function)
  (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
  (#select-adjacent! @doc @definition.function)
)

(
  (comment)* @doc
  .
  (variable_declaration
    (variable_declarator
      name: (identifier) @name.definition.function
      value: [(arrow_function) (function_declaration)]) @definition.function)
  (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
  (#select-adjacent! @doc @definition.function)
)

(assignment_expression
  left: [
    (identifier) @name.definition.function
    (member_expression
      property: (property_identifier) @name.definition.function)
  ]
  right: [(arrow_function) (function_declaration)]
) @definition.function

(pair
  key: (property_identifier) @name.definition.function
  value: [(arrow_function) (function_declaration)]) @definition.function

(
  (call_expression
    function: (identifier) @name.reference.call) @reference.call
  (#not-match? @name.reference.call "^(require)$")
)

(call_expression
  function: (member_expression
    property: (property_identifier) @name.reference.call)
  arguments: (_) @reference.call)

(new_expression
  constructor: (_) @name.reference.class) @reference.class
`;
</file>

<file path="src/core/treeSitter/queries/queryPhp.ts">
export const queryPhp = `
; For repomix
(comment) @comment
(namespace_use_clause) @definition.import
(enum_declaration name: (name) @name) @definition.enum

; tree-sitter-php
(namespace_definition
  name: (namespace_name) @name) @definition.module

(interface_declaration
  name: (name) @name) @definition.interface

(trait_declaration
  name: (name) @name) @definition.interface

(class_declaration
  name: (name) @name) @definition.class

(class_interface_clause [(name) (qualified_name)] @name) @reference.implementation

(property_declaration
  (property_element (variable_name (name) @name))) @definition.field

(function_definition
  name: (name) @name) @definition.function

(method_declaration
  name: (name) @name) @definition.function

(object_creation_expression
  [
    (qualified_name (name) @name)
    (variable_name (name) @name)
  ]) @reference.class

(function_call_expression
  function: [
    (qualified_name (name) @name)
    (variable_name (name)) @name
  ]) @reference.call

(scoped_call_expression
  name: (name) @name) @reference.call

(member_call_expression
  name: (name) @name) @reference.call
`;
</file>

<file path="src/core/treeSitter/queries/querySwift.ts">
export const querySwift = `
(comment) @comment

(class_declaration
  name: (type_identifier) @name) @definition.class

(protocol_declaration
  name: (type_identifier) @name) @definition.interface

(class_declaration
    (class_body
        [
            (function_declaration
                name: (simple_identifier) @name
            )
            (subscript_declaration
                (parameter (simple_identifier) @name)
            )
            (init_declaration "init" @name)
            (deinit_declaration "deinit" @name)
        ]
    )
) @definition.method

(class_declaration
    (class_body
        [
            (property_declaration
                (pattern (simple_identifier) @name)
            )
        ]
    )
) @definition.property

(property_declaration
    (pattern (simple_identifier) @name)
) @definition.property

(function_declaration
    name: (simple_identifier) @name) @definition.function
`;
</file>

<file path="src/core/treeSitter/queries/queryTypescript.ts">
export const queryTypescript = `
(import_statement
  (import_clause (identifier) @name.reference.module)) @definition.import

(import_statement
  (import_clause 
    (named_imports
      (import_specifier 
        name: (identifier) @name.reference.module))) @definition.import)

(comment) @comment

(function_signature
  name: (identifier) @name.definition.function) @definition.function

(method_signature
  name: (property_identifier) @name.definition.method) @definition.method

(abstract_method_signature
  name: (property_identifier) @name.definition.method) @definition.method

(abstract_class_declaration
  name: (type_identifier) @name.definition.class) @definition.class

(module
  name: (identifier) @name.definition.module) @definition.module

(interface_declaration
  name: (type_identifier) @name.definition.interface) @definition.interface

(type_annotation
  (type_identifier) @name.reference.type) @reference.type

(new_expression
  constructor: (identifier) @name.reference.class) @reference.class

(function_declaration
  name: (identifier) @name.definition.function) @definition.function

(method_definition
  name: (property_identifier) @name.definition.method) @definition.method

(class_declaration
  name: (type_identifier) @name.definition.class) @definition.class

(interface_declaration
  name: (type_identifier) @name.definition.class) @definition.class

(type_alias_declaration
  name: (type_identifier) @name.definition.type) @definition.type

(enum_declaration
  name: (identifier) @name.definition.enum) @definition.enum

(lexical_declaration
    (variable_declarator
      name: (identifier) @name.definition.function
      value: (arrow_function)
    )
  ) @definition.function

(variable_declaration
    (variable_declarator
      name: (identifier) @name.definition.function
      value: (arrow_function)
    )
) @definition.function

(assignment_expression
    left: [(identifier) @name.definition.function]
    right: (arrow_function)
) @definition.function
`;
</file>

<file path="src/core/treeSitter/queries/README.md">
# Credits
Repomix uses modified versions of tree-sitter queries from Aider and Cline:
* [https://github.com/Aider-AI/aider](https://github.com/Aider-AI/aider) — licensed under the Apache License 2.0.
* [https://github.com/cline/cline](https://github.com/cline/cline) — licensed under the Apache License 2.0.

Aider uses modified versions of the tags.scm files from these open source 
tree-sitter language implementations:

* [https://github.com/tree-sitter/tree-sitter-c](https://github.com/tree-sitter/tree-sitter-c) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-c-sharp](https://github.com/tree-sitter/tree-sitter-c-sharp) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-cpp](https://github.com/tree-sitter/tree-sitter-cpp) — licensed under the MIT License.
* [https://github.com/Wilfred/tree-sitter-elisp](https://github.com/Wilfred/tree-sitter-elisp) — licensed under the MIT License.
* [https://github.com/elixir-lang/tree-sitter-elixir](https://github.com/elixir-lang/tree-sitter-elixir) — licensed under the Apache License, Version 2.0.
* [https://github.com/elm-tooling/tree-sitter-elm](https://github.com/elm-tooling/tree-sitter-elm) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-go](https://github.com/tree-sitter/tree-sitter-go) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-java](https://github.com/tree-sitter/tree-sitter-java) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-javascript](https://github.com/tree-sitter/tree-sitter-javascript) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-ocaml](https://github.com/tree-sitter/tree-sitter-ocaml) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-php](https://github.com/tree-sitter/tree-sitter-php) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-python](https://github.com/tree-sitter/tree-sitter-python) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-ql](https://github.com/tree-sitter/tree-sitter-ql) — licensed under the MIT License.
* [https://github.com/r-lib/tree-sitter-r](https://github.com/r-lib/tree-sitter-r) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-ruby](https://github.com/tree-sitter/tree-sitter-ruby) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-rust](https://github.com/tree-sitter/tree-sitter-rust) — licensed under the MIT License.
* [https://github.com/tree-sitter/tree-sitter-typescript](https://github.com/tree-sitter/tree-sitter-typescript) — licensed under the MIT License.
</file>

<file path="src/core/treeSitter/languageParser.ts">
import * as path from 'node:path';
import Parser from 'web-tree-sitter';

import { RepomixError } from '../../shared/errorHandle.js';
import { ext2Lang } from './ext2Lang.js';
import { type SupportedLang, lang2Query } from './lang2Query.js';
import { loadLanguage } from './loadLanguage.js';
import { type ParseStrategy, createParseStrategy } from './parseStrategies/ParseStrategy.js';

interface LanguageResources {
  parser: Parser;
  query: Parser.Query;
  strategy: ParseStrategy;
}

export class LanguageParser {
  private loadedResources: Map<SupportedLang, LanguageResources> = new Map();
  private initialized = false;

  private getFileExtension(filePath: string): string {
    return path.extname(filePath).toLowerCase().slice(1);
  }

  private async prepareLang(name: SupportedLang): Promise<LanguageResources> {
    try {
      const lang = await loadLanguage(name);
      const parser = new Parser();
      parser.setLanguage(lang);
      const query = lang.query(lang2Query[name]);
      const strategy = createParseStrategy(name);

      const resources: LanguageResources = {
        parser,
        query,
        strategy,
      };

      this.loadedResources.set(name, resources);
      return resources;
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      throw new RepomixError(`Failed to prepare language ${name}: ${message}`);
    }
  }

  private async getResources(name: SupportedLang): Promise<LanguageResources> {
    if (!this.initialized) {
      throw new RepomixError('LanguageParser is not initialized. Call init() first.');
    }

    const resources = this.loadedResources.get(name);
    if (!resources) {
      return this.prepareLang(name);
    }
    return resources;
  }

  public async getParserForLang(name: SupportedLang): Promise<Parser> {
    const resources = await this.getResources(name);
    return resources.parser;
  }

  public async getQueryForLang(name: SupportedLang): Promise<Parser.Query> {
    const resources = await this.getResources(name);
    return resources.query;
  }

  public async getStrategyForLang(name: SupportedLang): Promise<ParseStrategy> {
    const resources = await this.getResources(name);
    return resources.strategy;
  }

  public guessTheLang(filePath: string): SupportedLang | undefined {
    const ext = this.getFileExtension(filePath);
    if (!Object.keys(ext2Lang).includes(ext)) {
      return undefined;
    }
    return ext2Lang[ext as keyof typeof ext2Lang] as SupportedLang;
  }

  public async init(): Promise<void> {
    if (this.initialized) {
      return;
    }

    try {
      await Parser.init();
      this.initialized = true;
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      throw new RepomixError(`Failed to initialize parser: ${message}`);
    }
  }

  public async dispose(): Promise<void> {
    for (const resources of this.loadedResources.values()) {
      resources.parser.delete();
    }
    this.loadedResources.clear();
    this.initialized = false;
  }
}
</file>

<file path="src/core/treeSitter/loadLanguage.ts">
import fs from 'node:fs/promises';
import { createRequire } from 'node:module';
import path from 'node:path';
import Parser from 'web-tree-sitter';

const require = createRequire(import.meta.url);

export async function loadLanguage(langName: string): Promise<Parser.Language> {
  if (!langName) {
    throw new Error('Invalid language name');
  }

  try {
    const wasmPath = await getWasmPath(langName);
    return await Parser.Language.load(wasmPath);
  } catch (error: unknown) {
    const message = error instanceof Error ? error.message : String(error);
    throw new Error(`Failed to load language ${langName}: ${message}`);
  }
}

async function getWasmPath(langName: string): Promise<string> {
  const wasmPath = require.resolve(`tree-sitter-wasms/out/tree-sitter-${langName}.wasm`);
  try {
    await fs.access(wasmPath);
    return wasmPath;
  } catch {
    throw new Error(`WASM file not found for language ${langName}: ${wasmPath}`);
  }
}
</file>

<file path="src/core/treeSitter/parseFile.ts">
import type { QueryCapture } from 'web-tree-sitter';
import type { RepomixConfigMerged } from '../../config/configSchema.js';
import { logger } from '../../shared/logger.js';
import type { SupportedLang } from './lang2Query.js';
import { LanguageParser } from './languageParser.js';
import { type ParseContext, createParseStrategy } from './parseStrategies/ParseStrategy.js';

interface CapturedChunk {
  content: string;
  startRow: number;
  endRow: number;
}

let languageParserSingleton: LanguageParser | null = null;

export const CHUNK_SEPARATOR = '⋮----';

// TODO: Do something with config: RepomixConfigMerged, it is not used (yet)
export const parseFile = async (fileContent: string, filePath: string, config: RepomixConfigMerged) => {
  const languageParser = await getLanguageParserSingleton();

  // Split the file content into individual lines
  const lines = fileContent.split('\n');
  if (lines.length < 1) {
    return '';
  }

  const lang: SupportedLang | undefined = languageParser.guessTheLang(filePath);
  if (lang === undefined) {
    // Language not supported
    return undefined;
  }

  const query = await languageParser.getQueryForLang(lang);
  const parser = await languageParser.getParserForLang(lang);
  const processedChunks = new Set<string>();
  const capturedChunks: CapturedChunk[] = [];

  try {
    // Parse the file content into an Abstract Syntax Tree (AST)
    const tree = parser.parse(fileContent);

    // Get the appropriate parse strategy for the language
    const parseStrategy = createParseStrategy(lang);

    // Create parse context
    const context: ParseContext = {
      fileContent,
      lines,
      tree,
      query,
      config,
    };

    // Apply the query to the AST and get the captures
    const captures = query.captures(tree.rootNode);

    // Sort captures by their start position
    captures.sort((a, b) => a.node.startPosition.row - b.node.startPosition.row);

    for (const capture of captures) {
      const capturedChunkContent = parseStrategy.parseCapture(capture, lines, processedChunks, context);
      if (capturedChunkContent !== null) {
        capturedChunks.push({
          content: capturedChunkContent.trim(),
          startRow: capture.node.startPosition.row,
          endRow: capture.node.endPosition.row,
        });
      }
    }
  } catch (error: unknown) {
    logger.log(`Error parsing file: ${error}\n`);
  }

  const filteredChunks = filterDuplicatedChunks(capturedChunks);
  const mergedChunks = mergeAdjacentChunks(filteredChunks);

  return mergedChunks
    .map((chunk) => chunk.content)
    .join(`\n${CHUNK_SEPARATOR}\n`)
    .trim();
};

const getLanguageParserSingleton = async () => {
  if (!languageParserSingleton) {
    languageParserSingleton = new LanguageParser();
    await languageParserSingleton.init();
  }
  return languageParserSingleton;
};

const filterDuplicatedChunks = (chunks: CapturedChunk[]): CapturedChunk[] => {
  // Group chunks by their start row
  const chunksByStartRow = new Map<number, CapturedChunk[]>();

  for (const chunk of chunks) {
    const startRow = chunk.startRow;
    if (!chunksByStartRow.has(startRow)) {
      chunksByStartRow.set(startRow, []);
    }
    chunksByStartRow.get(startRow)?.push(chunk);
  }

  // For each start row, keep the chunk with the most content
  const filteredChunks: CapturedChunk[] = [];
  for (const [_, rowChunks] of chunksByStartRow) {
    rowChunks.sort((a, b) => b.content.length - a.content.length);
    filteredChunks.push(rowChunks[0]);
  }

  // Sort filtered chunks by start row
  return filteredChunks.sort((a, b) => a.startRow - b.startRow);
};

const mergeAdjacentChunks = (chunks: CapturedChunk[]): CapturedChunk[] => {
  if (chunks.length <= 1) {
    return chunks;
  }

  const merged: CapturedChunk[] = [chunks[0]];

  for (let i = 1; i < chunks.length; i++) {
    const current = chunks[i];
    const previous = merged[merged.length - 1];

    // Merge the current chunk with the previous one
    if (previous.endRow + 1 === current.startRow) {
      previous.content += `\n${current.content}`;
      previous.endRow = current.endRow;
    } else {
      merged.push(current);
    }
  }

  return merged;
};
</file>

<file path="src/shared/constants.ts">
export const REPOMIX_GITHUB_URL = 'https://github.com/yamadashy/repomix';
export const REPOMIX_ISSUES_URL = `${REPOMIX_GITHUB_URL}/issues`;
export const REPOMIX_DISCORD_URL = 'https://discord.gg/wNYzTwZFku';
</file>

<file path="src/shared/errorHandle.ts">
import { z } from 'zod';
import { REPOMIX_DISCORD_URL, REPOMIX_ISSUES_URL } from './constants.js';
import { logger, repomixLogLevels } from './logger.js';

export class RepomixError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'RepomixError';
  }
}

export class RepomixConfigValidationError extends RepomixError {
  constructor(message: string) {
    super(message);
    this.name = 'RepomixConfigValidationError';
  }
}

export const handleError = (error: unknown): void => {
  logger.log('');

  if (error instanceof RepomixError) {
    logger.error(`✖ ${error.message}`);
    // If expected error, show stack trace for debugging
    logger.debug('Stack trace:', error.stack);
  } else if (error instanceof Error) {
    logger.error(`✖ Unexpected error: ${error.message}`);
    // If unexpected error, show stack trace by default
    logger.note('Stack trace:', error.stack);

    if (logger.getLogLevel() < repomixLogLevels.DEBUG) {
      logger.log('');
      logger.note('For detailed debug information, use the --verbose flag');
    }
  } else {
    // Unknown errors
    logger.error('✖ An unknown error occurred');

    if (logger.getLogLevel() < repomixLogLevels.DEBUG) {
      logger.note('For detailed debug information, use the --verbose flag');
    }
  }

  // Community support information
  logger.log('');
  logger.info('Need help?');
  logger.info(`• File an issue on GitHub: ${REPOMIX_ISSUES_URL}`);
  logger.info(`• Join our Discord community: ${REPOMIX_DISCORD_URL}`);
};

export const rethrowValidationErrorIfZodError = (error: unknown, message: string): void => {
  if (error instanceof z.ZodError) {
    const zodErrorText = error.errors.map((err) => `[${err.path.join('.')}] ${err.message}`).join('\n  ');
    throw new RepomixConfigValidationError(
      `${message}\n\n  ${zodErrorText}\n\n  Please check the config file and try again.`,
    );
  }
};
</file>

<file path="src/shared/logger.ts">
import util from 'node:util';
import pc from 'picocolors';

export const repomixLogLevels = {
  SILENT: -1, // No output
  ERROR: 0, // error
  WARN: 1, // warn
  INFO: 2, // success, info, log, note
  DEBUG: 3, // debug, trace
} as const;

export type RepomixLogLevel = (typeof repomixLogLevels)[keyof typeof repomixLogLevels];

class RepomixLogger {
  private level: RepomixLogLevel = repomixLogLevels.INFO;

  constructor() {
    this.init();
  }

  init() {
    this.setLogLevel(repomixLogLevels.INFO);
  }

  setLogLevel(level: RepomixLogLevel) {
    this.level = level;
  }

  getLogLevel(): RepomixLogLevel {
    return this.level;
  }

  error(...args: unknown[]) {
    if (this.level >= repomixLogLevels.ERROR) {
      console.error(pc.red(this.formatArgs(args)));
    }
  }

  warn(...args: unknown[]) {
    if (this.level >= repomixLogLevels.WARN) {
      console.log(pc.yellow(this.formatArgs(args)));
    }
  }

  success(...args: unknown[]) {
    if (this.level >= repomixLogLevels.INFO) {
      console.log(pc.green(this.formatArgs(args)));
    }
  }

  info(...args: unknown[]) {
    if (this.level >= repomixLogLevels.INFO) {
      console.log(pc.cyan(this.formatArgs(args)));
    }
  }

  log(...args: unknown[]) {
    if (this.level >= repomixLogLevels.INFO) {
      console.log(this.formatArgs(args));
    }
  }

  note(...args: unknown[]) {
    if (this.level >= repomixLogLevels.INFO) {
      console.log(pc.dim(this.formatArgs(args)));
    }
  }

  debug(...args: unknown[]) {
    if (this.level >= repomixLogLevels.DEBUG) {
      console.log(pc.blue(this.formatArgs(args)));
    }
  }

  trace(...args: unknown[]) {
    if (this.level >= repomixLogLevels.DEBUG) {
      console.log(pc.gray(this.formatArgs(args)));
    }
  }

  private formatArgs(args: unknown[]): string {
    return args
      .map((arg) => (typeof arg === 'object' ? util.inspect(arg, { depth: null, colors: true }) : arg))
      .join(' ');
  }
}

export const logger = new RepomixLogger();

export const setLogLevel = (level: RepomixLogLevel) => {
  logger.setLogLevel(level);
};
</file>

<file path="src/shared/processConcurrency.ts">
import os from 'node:os';
import { Piscina } from 'piscina';
import { logger } from './logger.js';

export const getProcessConcurrency = (): number => {
  return typeof os.availableParallelism === 'function' ? os.availableParallelism() : os.cpus().length;
};

export const getWorkerThreadCount = (numOfTasks: number): { minThreads: number; maxThreads: number } => {
  const processConcurrency = getProcessConcurrency();

  const minThreads = 1;

  // Limit max threads based on number of tasks
  const maxThreads = Math.max(minThreads, Math.min(processConcurrency, Math.ceil(numOfTasks / 100)));

  return {
    minThreads,
    maxThreads,
  };
};

export const initPiscina = (numOfTasks: number, workerPath: string): Piscina => {
  const { minThreads, maxThreads } = getWorkerThreadCount(numOfTasks);

  logger.trace(
    `Initializing worker pool with min=${minThreads}, max=${maxThreads} threads. Worker path: ${workerPath}`,
  );

  return new Piscina({
    filename: workerPath,
    minThreads,
    maxThreads,
    idleTimeout: 5000,
  });
};
</file>

<file path="src/shared/types.ts">
export type RepomixProgressCallback = (message: string) => void;
</file>

<file path="src/index.ts">
// ---------------------------------------------------------------------------------------------------------------------
// Core
// ---------------------------------------------------------------------------------------------------------------------
export { pack } from './core/packager.js';

// ---------------------------------------------------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------------------------------------------------
export type { RepomixConfigFile as RepomixConfig } from './config/configSchema.js';

// ---------------------------------------------------------------------------------------------------------------------
// Shard
// ---------------------------------------------------------------------------------------------------------------------
export { setLogLevel } from './shared/logger.js';

// ---------------------------------------------------------------------------------------------------------------------
// CLI
// ---------------------------------------------------------------------------------------------------------------------
export { run as cli } from './cli/cliRun.js';
export type { CliOptions } from './cli/types.js';

// Run CLI Repomix
export { runCli } from './cli/cliRun.js';

// Init action
export { runInitAction } from './cli/actions/initAction.js';

// Default action
export { runDefaultAction } from './cli/actions/defaultAction.js';

// Remote action
export { runRemoteAction } from './cli/actions/remoteAction.js';
export { isValidRemoteValue } from './cli/actions/remoteAction.js';
</file>

<file path="tests/cli/actions/initAction.test.ts">
import * as fs from 'node:fs/promises';
import path from 'node:path';
import * as prompts from '@clack/prompts';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { createConfigFile, createIgnoreFile } from '../../../src/cli/actions/initAction.js';
import { getGlobalDirectory } from '../../../src/config/globalDirectory.js';

vi.mock('node:fs/promises');
vi.mock('@clack/prompts');
vi.mock('../../../src/shared/folderUtils');
vi.mock('../../../src/config/globalDirectory.js');

describe('initAction', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  afterEach(() => {
    vi.resetAllMocks();
  });

  describe('createConfigFile', () => {
    it('should create a new local config file when one does not exist', async () => {
      vi.mocked(fs.access).mockRejectedValue(new Error('File does not exist'));
      vi.mocked(prompts.group).mockResolvedValue({
        outputFilePath: 'custom-output.txt',
        outputStyle: 'xml',
      });
      vi.mocked(prompts.confirm).mockResolvedValue(true);

      await createConfigFile('/test/dir', false);

      const configPath = path.resolve('/test/dir/repomix.config.json');

      console.log('configPath', configPath);

      expect(fs.writeFile).toHaveBeenCalledWith(configPath, expect.stringContaining('"filePath": "custom-output.txt"'));
      expect(fs.writeFile).toHaveBeenCalledWith(configPath, expect.stringContaining('"style": "xml"'));
    });

    it('should create a new global config file when one does not exist', async () => {
      vi.mocked(fs.access).mockRejectedValue(new Error('File does not exist'));
      vi.mocked(prompts.group).mockResolvedValue({
        outputFilePath: 'global-output.txt',
        outputStyle: 'plain',
      });
      vi.mocked(prompts.confirm).mockResolvedValue(true);
      vi.mocked(getGlobalDirectory).mockImplementation(() => '/global/repomix');

      await createConfigFile('/test/dir', true);

      const configPath = path.resolve('/global/repomix/repomix.config.json');

      expect(fs.mkdir).toHaveBeenCalledWith(path.dirname(configPath), { recursive: true });
      expect(fs.writeFile).toHaveBeenCalledWith(configPath, expect.stringContaining('"filePath": "global-output.txt"'));
      expect(fs.writeFile).toHaveBeenCalledWith(configPath, expect.stringContaining('"style": "plain"'));
    });

    it('should prompt to overwrite when config file already exists', async () => {
      vi.mocked(fs.access).mockResolvedValue(undefined);
      vi.mocked(prompts.confirm).mockResolvedValue(true);
      vi.mocked(prompts.group).mockResolvedValue({
        outputFilePath: 'new-output.txt',
        outputStyle: 'xml',
      });

      await createConfigFile('/test/dir', false);

      expect(prompts.confirm).toHaveBeenCalled();
      expect(fs.writeFile).toHaveBeenCalled();
    });

    it('should not overwrite when user chooses not to', async () => {
      vi.mocked(fs.access).mockResolvedValue(undefined);
      vi.mocked(prompts.confirm).mockResolvedValue(false);

      await createConfigFile('/test/dir', false);

      expect(prompts.confirm).toHaveBeenCalled();
      expect(fs.writeFile).not.toHaveBeenCalled();
    });

    it('should handle user cancellation', async () => {
      vi.mocked(fs.access).mockRejectedValue(new Error('File does not exist'));
      vi.mocked(prompts.group).mockImplementation(() => {
        throw new Error('User cancelled');
      });

      await createConfigFile('/test/dir', false);

      expect(fs.writeFile).not.toHaveBeenCalled();
    });
  });

  describe('createIgnoreFile', () => {
    it('should not create a new .repomixignore file when global flag is set', async () => {
      const result = await createIgnoreFile('/test/dir', true);

      expect(result).toBe(false);
      expect(fs.writeFile).not.toHaveBeenCalled();
    });

    it('should create a new .repomixignore file when one does not exist', async () => {
      vi.mocked(fs.access).mockRejectedValue(new Error('File does not exist'));
      vi.mocked(prompts.confirm).mockResolvedValue(true);

      await createIgnoreFile('/test/dir', false);

      const ignorePath = path.resolve('/test/dir/.repomixignore');

      expect(fs.writeFile).toHaveBeenCalledWith(
        ignorePath,
        expect.stringContaining('# Add patterns to ignore here, one per line'),
      );
    });

    it('should prompt to overwrite when .repomixignore file already exists', async () => {
      vi.mocked(fs.access).mockResolvedValue(undefined);
      vi.mocked(prompts.confirm)
        .mockResolvedValueOnce(true) // First call for creating the file
        .mockResolvedValueOnce(true); // Second call for overwriting

      await createIgnoreFile('/test/dir', false);

      expect(prompts.confirm).toHaveBeenCalledTimes(2);
      expect(fs.writeFile).toHaveBeenCalled();
    });

    it('should not overwrite when user chooses not to', async () => {
      vi.mocked(fs.access).mockResolvedValue(undefined);
      vi.mocked(prompts.confirm)
        .mockResolvedValueOnce(true) // First call for creating the file
        .mockResolvedValueOnce(false); // Second call for overwriting

      await createIgnoreFile('/test/dir', false);

      expect(prompts.confirm).toHaveBeenCalledTimes(2);
      expect(fs.writeFile).not.toHaveBeenCalled();
    });

    it('should return false when user chooses not to create .repomixignore', async () => {
      vi.mocked(prompts.confirm).mockResolvedValue(false);

      const result = await createIgnoreFile('/test/dir', false);

      expect(result).toBe(false);
      expect(fs.writeFile).not.toHaveBeenCalled();
    });

    it('should handle user cancellation', async () => {
      vi.mocked(prompts.confirm).mockResolvedValue(false);

      await createIgnoreFile('/test/dir', false);

      expect(fs.writeFile).not.toHaveBeenCalled();
    });
  });
});
</file>

<file path="tests/cli/actions/migrationAction.test.ts">
import * as fs from 'node:fs/promises';
import path from 'node:path';
import * as prompts from '@clack/prompts';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { runMigrationAction } from '../../../src/cli/actions/migrationAction.js';
import { logger } from '../../../src/shared/logger.js';

vi.mock('node:fs/promises');
vi.mock('@clack/prompts');
vi.mock('../../../src/shared/logger');

describe('migrationAction', () => {
  const mockRootDir = '/test/dir';
  const oldConfigPath = path.join(mockRootDir, 'repopack.config.json');
  const newConfigPath = path.join(mockRootDir, 'repomix.config.json');
  const oldIgnorePath = path.join(mockRootDir, '.repopackignore');
  const newIgnorePath = path.join(mockRootDir, '.repomixignore');
  const oldInstructionPath = path.join(mockRootDir, 'repopack-instruction.md');
  const newInstructionPath = path.join(mockRootDir, 'repomix-instruction.md');
  const gitignorePath = path.join(mockRootDir, '.gitignore');

  const mockOutputPaths = {
    oldTxt: path.join(mockRootDir, 'repopack-output.txt'),
    newTxt: path.join(mockRootDir, 'repomix-output.txt'),
    oldXml: path.join(mockRootDir, 'repopack-output.xml'),
    newXml: path.join(mockRootDir, 'repomix-output.xml'),
    oldMd: path.join(mockRootDir, 'repopack-output.md'),
    newMd: path.join(mockRootDir, 'repomix-output.md'),
  };

  beforeEach(() => {
    vi.resetAllMocks();
  });

  afterEach(() => {
    vi.resetAllMocks();
  });

  test('should migrate all files when they exist', async () => {
    // Mock file existence checks
    vi.mocked(fs.access).mockImplementation(async (path) => {
      if (
        path === oldConfigPath ||
        path === oldIgnorePath ||
        path === oldInstructionPath ||
        path === mockOutputPaths.oldTxt ||
        path === mockOutputPaths.oldXml
      ) {
        return Promise.resolve();
      }
      return Promise.reject(new Error('File not found'));
    });

    // Mock file content
    const mockConfigContent = JSON.stringify({
      output: {
        filePath: 'repopack-output.txt',
        instructionFilePath: 'repopack-instruction.md',
      },
    });
    const mockIgnoreContent = 'repopack-output.txt\n*.log';
    const mockInstructionContent = '# Repopack Instructions';
    const mockOutputContent = 'Repopack output content';

    vi.mocked(fs.readFile).mockImplementation(async (path) => {
      if (path === oldConfigPath) return mockConfigContent;
      if (path === oldIgnorePath) return mockIgnoreContent;
      if (path === oldInstructionPath) return mockInstructionContent;
      if (path === mockOutputPaths.oldTxt || path === mockOutputPaths.oldXml) {
        return mockOutputContent;
      }
      return '';
    });

    // Mock user confirmation
    vi.mocked(prompts.confirm).mockResolvedValue(true);

    // Run migration
    const result = await runMigrationAction(mockRootDir);

    // Verify results
    expect(result.configMigrated).toBe(true);
    expect(result.ignoreMigrated).toBe(true);
    expect(result.instructionMigrated).toBe(true);
    expect(result.outputFilesMigrated).toContain(mockOutputPaths.newTxt);
    expect(result.outputFilesMigrated).toContain(mockOutputPaths.newXml);
    expect(result.error).toBeUndefined();

    // Verify file operations for config
    expect(fs.writeFile).toHaveBeenCalledWith(
      newConfigPath,
      JSON.stringify(
        {
          output: {
            filePath: 'repomix-output.txt',
            instructionFilePath: 'repomix-instruction.md',
          },
        },
        null,
        2,
      ),
      'utf8',
    );

    // Verify other file operations
    expect(fs.writeFile).toHaveBeenCalledWith(newIgnorePath, 'repomix-output.txt\n*.log', 'utf8');
    expect(fs.writeFile).toHaveBeenCalledWith(newInstructionPath, '# Repomix Instructions', 'utf8');

    // Verify old files were removed
    expect(fs.unlink).toHaveBeenCalledWith(oldConfigPath);
    expect(fs.unlink).toHaveBeenCalledWith(oldIgnorePath);
    expect(fs.unlink).toHaveBeenCalledWith(oldInstructionPath);
    expect(fs.unlink).toHaveBeenCalledWith(mockOutputPaths.oldTxt);
    expect(fs.unlink).toHaveBeenCalledWith(mockOutputPaths.oldXml);
  });

  test('should update gitignore content when it exists and contains repopack references', async () => {
    // Mock file existence only for gitignore and oldConfig
    vi.mocked(fs.access).mockImplementation(async (path) => {
      if (path === gitignorePath || path === oldConfigPath) {
        return Promise.resolve();
      }
      return Promise.reject(new Error('File not found'));
    });

    // Mock file content only for gitignore
    const mockGitignoreContent = 'node_modules/\nrepopack-output.txt';
    vi.mocked(fs.readFile).mockImplementation(async (path) => {
      if (path === gitignorePath) return mockGitignoreContent;
      if (path === oldConfigPath) return '{}';
      return '';
    });

    // Mock user confirmation
    vi.mocked(prompts.confirm).mockResolvedValue(true);

    // Run migration
    await runMigrationAction(mockRootDir);

    // Verify gitignore was updated
    expect(fs.writeFile).toHaveBeenCalledWith(gitignorePath, 'node_modules/\nrepomix-output.txt', 'utf8');
    expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Updated repopack references in'));
  });

  test('should handle non-updated files correctly', async () => {
    // Mock file existence only for gitignore and oldConfig
    vi.mocked(fs.access).mockImplementation(async (path) => {
      if (path === gitignorePath || path === oldConfigPath) {
        return Promise.resolve();
      }
      return Promise.reject(new Error('File not found'));
    });

    // Mock file content with no repopack references
    vi.mocked(fs.readFile).mockImplementation(async (path) => {
      if (path === gitignorePath) return 'node_modules/\n*.log';
      if (path === oldConfigPath) return '{}';
      return '';
    });

    // Mock user confirmation
    vi.mocked(prompts.confirm).mockResolvedValue(true);

    // Run migration
    await runMigrationAction(mockRootDir);

    // Verify no gitignore update was performed
    expect(fs.writeFile).not.toHaveBeenCalledWith(gitignorePath, expect.any(String), expect.any(String));
    // Verify debug message was logged
    expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('No changes needed in'));
  });

  test('should skip migration when no old files exist', async () => {
    // Mock all files not existing
    vi.mocked(fs.access).mockRejectedValue(new Error('File not found'));

    // Run migration
    const result = await runMigrationAction(mockRootDir);

    // Verify no migration occurred
    expect(result.configMigrated).toBe(false);
    expect(result.ignoreMigrated).toBe(false);
    expect(result.instructionMigrated).toBe(false);
    expect(result.outputFilesMigrated).toHaveLength(0);
    expect(prompts.confirm).not.toHaveBeenCalled();
    expect(logger.debug).toHaveBeenCalledWith('No Repopack files found to migrate.');
  });

  test('should skip files when they already exist and user declines overwrite', async () => {
    // Mock old and new files existing
    vi.mocked(fs.access).mockResolvedValue(undefined);

    // Mock user confirming migration but declining overwrites
    vi.mocked(prompts.confirm)
      .mockResolvedValueOnce(true) // Migration confirmation
      .mockResolvedValue(false); // All overwrite confirmations

    // Run migration
    const result = await runMigrationAction(mockRootDir);

    // Verify nothing was migrated
    expect(result.configMigrated).toBe(false);
    expect(result.ignoreMigrated).toBe(false);
    expect(result.instructionMigrated).toBe(false);
    expect(result.outputFilesMigrated).toHaveLength(0);
    expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('Skipping migration'));
  });
});
</file>

<file path="tests/cli/actions/versionAction.test.ts">
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { runVersionAction } from '../../../src/cli/actions/versionAction.js';
import * as packageJsonParser from '../../../src/core/file/packageJsonParse.js';
import { logger } from '../../../src/shared/logger.js';

vi.mock('../../../src/core/file/packageJsonParse');

describe('versionAction', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  afterEach(() => {
    vi.resetAllMocks();
  });

  it('should print the correct version', async () => {
    vi.mocked(packageJsonParser.getVersion).mockResolvedValue('1.2.3');

    const loggerSpy = vi.spyOn(logger, 'log').mockImplementation(vi.fn());
    await runVersionAction();

    expect(packageJsonParser.getVersion).toHaveBeenCalled();
    expect(loggerSpy).toHaveBeenCalledWith('1.2.3');
  });
});
</file>

<file path="tests/cli/cliPrint.test.ts">
import path from 'node:path';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { printCompletion, printSecurityCheck, printSummary, printTopFiles } from '../../src/cli/cliPrint.js';
import type { SuspiciousFileResult } from '../../src/core/security/securityCheck.js';
import { logger } from '../../src/shared/logger.js';
import { createMockConfig } from '../testing/testUtils.js';

vi.mock('../../src/shared/logger');
vi.mock('picocolors', () => ({
  default: {
    white: (str: string) => `WHITE:${str}`,
    dim: (str: string) => `DIM:${str}`,
    green: (str: string) => `GREEN:${str}`,
    yellow: (str: string) => `YELLOW:${str}`,
    red: (str: string) => `RED:${str}`,
    cyan: (str: string) => `CYAN:${str}`,
    underline: (str: string) => `UNDERLINE:${str}`,
  },
}));

describe('cliPrint', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  describe('printSummary', () => {
    test('should print summary with suspicious files and security check enabled', () => {
      const config = createMockConfig({
        security: { enableSecurityCheck: true },
      });
      const suspiciousFiles: SuspiciousFileResult[] = [
        { filePath: 'suspicious.txt', messages: ['Contains sensitive data'] },
      ];

      printSummary(10, 1000, 200, 'output.txt', suspiciousFiles, config);

      expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('1 suspicious file(s) detected and excluded'));
    });

    test('should print summary with security check disabled', () => {
      const config = createMockConfig({
        security: { enableSecurityCheck: false },
      });

      printSummary(10, 1000, 200, 'output.txt', [], config);

      expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Security check disabled'));
    });
  });

  describe('printSecurityCheck', () => {
    test('should skip printing when security check is disabled', () => {
      const config = createMockConfig({
        security: { enableSecurityCheck: false },
      });

      printSecurityCheck('/root', [], config);
      expect(logger.log).not.toHaveBeenCalled();
    });

    test('should print message when no suspicious files found', () => {
      const config = createMockConfig({
        security: { enableSecurityCheck: true },
      });

      printSecurityCheck('/root', [], config);

      expect(logger.log).toHaveBeenCalledWith('WHITE:🔎 Security Check:');
      expect(logger.log).toHaveBeenCalledWith('DIM:──────────────────');
      expect(logger.log).toHaveBeenCalledWith('GREEN:✔ WHITE:No suspicious files detected.');
    });

    test('should print details of suspicious files when found', () => {
      const config = createMockConfig({
        security: { enableSecurityCheck: true },
      });
      const configRelativePath = path.join('config', 'secrets.txt');
      const suspiciousFiles: SuspiciousFileResult[] = [
        {
          filePath: path.join('/root', configRelativePath),
          messages: ['Contains API key', 'Contains password'],
        },
      ];

      printSecurityCheck('/root', suspiciousFiles, config);

      expect(logger.log).toHaveBeenCalledWith('YELLOW:1 suspicious file(s) detected and excluded from the output:');
      expect(logger.log).toHaveBeenCalledWith(`WHITE:1. WHITE:${configRelativePath}`);
      expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Contains API key'));
      expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Contains password'));
      expect(logger.log).toHaveBeenCalledWith(
        expect.stringContaining('Please review these files for potential sensitive information.'),
      );
    });
  });

  describe('printTopFiles', () => {
    test('should print top files sorted by character count', () => {
      const fileCharCounts = {
        'src/index.ts': 1000,
        'src/utils.ts': 500,
        'README.md': 2000,
      };
      const fileTokenCounts = {
        'src/index.ts': 200,
        'src/utils.ts': 100,
        'README.md': 400,
      };

      printTopFiles(fileCharCounts, fileTokenCounts, 2);

      expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Top 2 Files'));
      expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('README.md'));
      expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('src/index.ts'));
      expect(logger.log).not.toHaveBeenCalledWith(expect.stringContaining('src/utils.ts'));
    });

    test('should handle empty file list', () => {
      printTopFiles({}, {}, 5);

      expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Top 5 Files'));
    });
  });

  describe('printCompletion', () => {
    test('should print completion message', () => {
      printCompletion();

      expect(logger.log).toHaveBeenCalledWith('GREEN:🎉 All Done!');
      expect(logger.log).toHaveBeenCalledWith('WHITE:Your repository has been successfully packed.');
    });
  });
});
</file>

<file path="tests/config/configLoad.test.ts">
import type { Stats } from 'node:fs';
import * as fs from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import pc from 'picocolors';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { loadFileConfig, mergeConfigs } from '../../src/config/configLoad.js';
import type { RepomixConfigCli, RepomixConfigFile } from '../../src/config/configSchema.js';
import { getGlobalDirectory } from '../../src/config/globalDirectory.js';
import { RepomixConfigValidationError } from '../../src/shared/errorHandle.js';
import { logger } from '../../src/shared/logger.js';

vi.mock('node:fs/promises');
vi.mock('../../src/shared/logger', () => ({
  logger: {
    trace: vi.fn(),
    note: vi.fn(),
    log: vi.fn(),
  },
}));
vi.mock('../../src/config/globalDirectory', () => ({
  getGlobalDirectory: vi.fn(),
}));

describe('configLoad', () => {
  beforeEach(() => {
    vi.resetAllMocks();
    process.env = {};
  });

  describe('loadFileConfig', () => {
    test('should load and parse a valid local config file', async () => {
      const mockConfig = {
        output: { filePath: 'test-output.txt' },
        ignore: { useDefaultPatterns: true },
      };
      vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig));
      vi.mocked(fs.stat).mockResolvedValue({ isFile: () => true } as Stats);

      const result = await loadFileConfig(process.cwd(), 'test-config.json');
      expect(result).toEqual(mockConfig);
    });

    test('should throw RepomixConfigValidationError for invalid config', async () => {
      const invalidConfig = {
        output: { filePath: 123, style: 'invalid' }, // Invalid filePath type and invalid style
        ignore: { useDefaultPatterns: 'not a boolean' }, // Invalid type
      };
      vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(invalidConfig));
      vi.mocked(fs.stat).mockResolvedValue({ isFile: () => true } as Stats);

      await expect(loadFileConfig(process.cwd(), 'test-config.json')).rejects.toThrow(RepomixConfigValidationError);
    });

    test('should load global config when local config is not found', async () => {
      const mockGlobalConfig = {
        output: { filePath: 'global-output.txt' },
        ignore: { useDefaultPatterns: false },
      };
      vi.mocked(getGlobalDirectory).mockReturnValue('/global/repomix');
      vi.mocked(fs.stat)
        .mockRejectedValueOnce(new Error('File not found')) // Local config
        .mockResolvedValueOnce({ isFile: () => true } as Stats); // Global config
      vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockGlobalConfig));

      const result = await loadFileConfig(process.cwd(), null);
      expect(result).toEqual(mockGlobalConfig);
      expect(fs.readFile).toHaveBeenCalledWith(path.join('/global/repomix', 'repomix.config.json'), 'utf-8');
    });

    test('should return an empty object if no config file is found', async () => {
      const loggerSpy = vi.spyOn(logger, 'log').mockImplementation(vi.fn());
      vi.mocked(getGlobalDirectory).mockReturnValue('/global/repomix');
      vi.mocked(fs.stat).mockRejectedValue(new Error('File not found'));

      const result = await loadFileConfig(process.cwd(), null);
      expect(result).toEqual({});

      expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('No custom config found'));
    });

    test('should throw an error for invalid JSON', async () => {
      vi.mocked(fs.readFile).mockResolvedValue('invalid json');
      vi.mocked(fs.stat).mockResolvedValue({ isFile: () => true } as Stats);

      await expect(loadFileConfig(process.cwd(), 'test-config.json')).rejects.toThrow('Invalid JSON');
    });

    test('should parse config file with comments', async () => {
      const configWithComments = `{
        // Output configuration
        "output": {
          "filePath": "test-output.txt"
        },
        /* Ignore configuration */
        "ignore": {
          "useGitignore": true // Use .gitignore file
        }
      }`;

      vi.mocked(fs.readFile).mockResolvedValue(configWithComments);
      vi.mocked(fs.stat).mockResolvedValue({ isFile: () => true } as Stats);

      const result = await loadFileConfig(process.cwd(), 'test-config.json');
      expect(result).toEqual({
        output: { filePath: 'test-output.txt' },
        ignore: { useGitignore: true },
      });
    });

    test('should parse config file with JSON5 features', async () => {
      const configWithJSON5Features = `{
        // Output configuration
        output: {
          filePath: 'test-output.txt',
          style: 'plain',
        },
        /* Ignore configuration */
        ignore: {
          useGitignore: true, // Use .gitignore file
          customPatterns: [
            '*.log',
            '*.tmp',
            '*.temp', // Trailing comma
          ],
        },
      }`;

      vi.mocked(fs.readFile).mockResolvedValue(configWithJSON5Features);
      vi.mocked(fs.stat).mockResolvedValue({ isFile: () => true } as Stats);

      const result = await loadFileConfig(process.cwd(), 'test-config.json');
      expect(result).toEqual({
        output: { filePath: 'test-output.txt', style: 'plain' },
        ignore: {
          useGitignore: true,
          customPatterns: ['*.log', '*.tmp', '*.temp'],
        },
      });
    });
  });

  describe('mergeConfigs', () => {
    test('should correctly merge configs', () => {
      const fileConfig: RepomixConfigFile = {
        output: { filePath: 'file-output.txt' },
        ignore: { useDefaultPatterns: true, customPatterns: ['file-ignore'] },
      };
      const cliConfig: RepomixConfigCli = {
        output: { filePath: 'cli-output.txt' },
        ignore: { customPatterns: ['cli-ignore'] },
      };

      const result = mergeConfigs(process.cwd(), fileConfig, cliConfig);

      expect(result.output.filePath).toBe('cli-output.txt');
      expect(result.ignore.useDefaultPatterns).toBe(true);
      expect(result.ignore.customPatterns).toContain('file-ignore');
      expect(result.ignore.customPatterns).toContain('cli-ignore');
    });

    test('should throw RepomixConfigValidationError for invalid merged config', () => {
      const fileConfig: RepomixConfigFile = {
        output: { filePath: 'file-output.txt', style: 'plain' },
      };
      const cliConfig: RepomixConfigCli = {
        // @ts-ignore
        output: { style: 'invalid' }, // Invalid style
      };

      expect(() => mergeConfigs(process.cwd(), fileConfig, cliConfig)).toThrow(RepomixConfigValidationError);
    });
  });
});
</file>

<file path="tests/config/globalDirectory.test.ts">
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { getGlobalDirectory } from '../../src/config/globalDirectory.js';

vi.mock('node:os');

describe('getGlobalDirectory', () => {
  const originalPlatform = process.platform;
  const originalEnv = process.env;

  beforeEach(() => {
    vi.resetAllMocks();
    process.env = { ...originalEnv };
  });

  afterEach(() => {
    Object.defineProperty(process, 'platform', { value: originalPlatform });
    process.env = originalEnv;
  });

  describe('Windows platform', () => {
    test('should use LOCALAPPDATA when available', () => {
      Object.defineProperty(process, 'platform', { value: 'win32' });
      process.env.LOCALAPPDATA = 'C:\\Users\\Test\\AppData\\Local';

      const result = getGlobalDirectory();
      expect(result).toBe(path.join('C:\\Users\\Test\\AppData\\Local', 'Repomix'));
    });

    test('should fall back to homedir when LOCALAPPDATA is not available', () => {
      Object.defineProperty(process, 'platform', { value: 'win32' });
      process.env.LOCALAPPDATA = undefined;
      vi.mocked(os.homedir).mockReturnValue('C:\\Users\\Test');

      const result = getGlobalDirectory();
      expect(result).toBe(path.join('C:\\Users\\Test', 'AppData', 'Local', 'Repomix'));
    });
  });

  describe('Unix platforms', () => {
    test('should use XDG_CONFIG_HOME when available', () => {
      Object.defineProperty(process, 'platform', { value: 'linux' });
      process.env.XDG_CONFIG_HOME = '/custom/config';

      const result = getGlobalDirectory();
      expect(result).toBe(path.join('/custom/config', 'repomix'));
    });

    test('should fall back to ~/.config on Linux', () => {
      Object.defineProperty(process, 'platform', { value: 'linux' });
      process.env.XDG_CONFIG_HOME = undefined;
      vi.mocked(os.homedir).mockReturnValue('/home/test');

      const result = getGlobalDirectory();
      expect(result).toBe(path.join('/home/test', '.config', 'repomix'));
    });

    test('should fall back to ~/.config on macOS', () => {
      Object.defineProperty(process, 'platform', { value: 'darwin' });
      process.env.XDG_CONFIG_HOME = undefined;
      vi.mocked(os.homedir).mockReturnValue('/Users/test');

      const result = getGlobalDirectory();
      expect(result).toBe(path.join('/Users/test', '.config', 'repomix'));
    });
  });

  describe('Edge cases', () => {
    test('should handle empty homedir', () => {
      Object.defineProperty(process, 'platform', { value: 'linux' });
      process.env.XDG_CONFIG_HOME = undefined;
      vi.mocked(os.homedir).mockReturnValue('');

      const result = getGlobalDirectory();
      expect(result).toBe(path.join('', '.config', 'repomix'));
    });

    test('should handle unusual XDG_CONFIG_HOME paths', () => {
      Object.defineProperty(process, 'platform', { value: 'linux' });
      process.env.XDG_CONFIG_HOME = '////multiple///slashes///';

      const result = getGlobalDirectory();
      expect(result).toBe(path.join('////multiple///slashes///', 'repomix'));
    });
  });
});
</file>

<file path="tests/core/file/fileCollect.test.ts">
import type { Stats } from 'node:fs';
import * as fs from 'node:fs/promises';
import path from 'node:path';
import iconv from 'iconv-lite';
import { isBinary } from 'istextorbinary';
import jschardet from 'jschardet';
import pc from 'picocolors';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { collectFiles } from '../../../src/core/file/fileCollect.js';
import type { FileCollectTask } from '../../../src/core/file/workers/fileCollectWorker.js';
import { MAX_FILE_SIZE } from '../../../src/core/file/workers/fileCollectWorker.js';
import fileCollectWorker from '../../../src/core/file/workers/fileCollectWorker.js';
import { logger } from '../../../src/shared/logger.js';

vi.mock('node:fs/promises');
vi.mock('istextorbinary');
vi.mock('jschardet');
vi.mock('iconv-lite');
vi.mock('../../../src/shared/logger');

const mockInitTaskRunner = () => {
  return async (task: FileCollectTask) => {
    return await fileCollectWorker(task);
  };
};

describe('fileCollect', () => {
  beforeEach(() => {
    vi.resetAllMocks();

    // Setup basic file size mock to fix stat
    vi.mocked(fs.stat).mockResolvedValue({
      size: 1024,
      isFile: () => true,
    } as Stats);
  });

  afterEach(() => {
    vi.resetAllMocks();
  });

  it('should collect non-binary files', async () => {
    const mockFilePaths = ['file1.txt', 'file2.txt'];
    const mockRootDir = '/root';

    vi.mocked(isBinary).mockReturnValue(false);
    vi.mocked(fs.readFile).mockResolvedValue(Buffer.from('file content'));
    vi.mocked(jschardet.detect).mockReturnValue({ encoding: 'utf-8', confidence: 0.99 });
    vi.mocked(iconv.decode).mockReturnValue('decoded content');

    const result = await collectFiles(mockFilePaths, mockRootDir, () => {}, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(result).toEqual([
      { path: 'file1.txt', content: 'decoded content' },
      { path: 'file2.txt', content: 'decoded content' },
    ]);
  });

  it('should skip binary files', async () => {
    const mockFilePaths = ['binary.bin', 'text.txt'];
    const mockRootDir = '/root';

    vi.mocked(isBinary)
      .mockReturnValueOnce(true) // for binary.bin
      .mockReturnValueOnce(false); // for text.txt
    vi.mocked(fs.readFile).mockResolvedValue(Buffer.from('file content'));
    vi.mocked(jschardet.detect).mockReturnValue({ encoding: 'utf-8', confidence: 0.99 });
    vi.mocked(iconv.decode).mockReturnValue('decoded content');

    const result = await collectFiles(mockFilePaths, mockRootDir, () => {}, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(result).toEqual([{ path: 'text.txt', content: 'decoded content' }]);
    expect(logger.debug).toHaveBeenCalledWith(`Skipping binary file: ${path.resolve('/root/binary.bin')}`);
  });

  it('should skip large files', async () => {
    const mockFilePaths = ['large.txt', 'normal.txt'];
    const mockRootDir = '/root';
    const largePath = path.resolve('/root/large.txt');

    vi.mocked(fs.stat)
      .mockResolvedValueOnce({
        // for large.txt
        size: MAX_FILE_SIZE + 1024, // Slightly over limit
        isFile: () => true,
      } as Stats)
      .mockResolvedValueOnce({
        // for normal.txt
        size: 1024,
        isFile: () => true,
      } as Stats);
    vi.mocked(isBinary).mockReturnValue(false);
    vi.mocked(fs.readFile).mockResolvedValue(Buffer.from('file content'));
    vi.mocked(jschardet.detect).mockReturnValue({ encoding: 'utf-8', confidence: 0.99 });
    vi.mocked(iconv.decode).mockReturnValue('decoded content');

    const result = await collectFiles(mockFilePaths, mockRootDir, () => {}, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(result).toEqual([{ path: 'normal.txt', content: 'decoded content' }]);
    expect(logger.log).toHaveBeenCalledWith('⚠️ Large File Warning:');
    expect(logger.log).toHaveBeenCalledWith('──────────────────────');
    expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('File exceeds size limit:'));
    expect(logger.log).toHaveBeenCalledWith(expect.stringContaining(largePath));
    expect(logger.log).toHaveBeenCalledWith(
      pc.dim('Add this file to .repomixignore if you want to exclude it permanently'),
    );

    // Verify fs.readFile is not called for the large file
    expect(fs.readFile).not.toHaveBeenCalledWith(largePath);
    expect(fs.readFile).toHaveBeenCalledTimes(1);
  });

  it('should handle file read errors', async () => {
    const mockFilePaths = ['error.txt'];
    const mockRootDir = '/root';

    vi.mocked(isBinary).mockReturnValue(false);
    vi.mocked(fs.readFile).mockRejectedValue(new Error('Read error'));

    const result = await collectFiles(mockFilePaths, mockRootDir, () => {}, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(result).toEqual([]);
    expect(logger.warn).toHaveBeenCalledWith(
      `Failed to read file: ${path.resolve('/root/error.txt')}`,
      expect.any(Error),
    );
  });
});
</file>

<file path="tests/core/file/filePathSort.test.ts">
import path from 'node:path';
import { describe, expect, test } from 'vitest';
import { sortPaths } from '../../../src/core/file/filePathSort.js';

describe('filePathSort', () => {
  const sep = path.sep;

  test('should sort directories before files', () => {
    const input = ['file.txt', `dir${sep}`, 'another_file.js', `another_dir${sep}`];
    const expected = [`another_dir${sep}`, `dir${sep}`, 'another_file.js', 'file.txt'];
    expect(sortPaths(input)).toEqual(expected);
  });

  test('should sort subdirectories correctly', () => {
    const input = [`dir${sep}subdir${sep}file.txt`, `dir${sep}file.js`, `dir${sep}subdir${sep}`, 'file.txt'];
    const expected = [`dir${sep}subdir${sep}`, `dir${sep}subdir${sep}file.txt`, `dir${sep}file.js`, 'file.txt'];
    expect(sortPaths(input)).toEqual(expected);
  });

  test('should sort files alphabetically within the same directory', () => {
    const input = [`dir${sep}c.txt`, `dir${sep}a.txt`, `dir${sep}b.txt`];
    const expected = [`dir${sep}a.txt`, `dir${sep}b.txt`, `dir${sep}c.txt`];
    expect(sortPaths(input)).toEqual(expected);
  });

  test('should handle empty input', () => {
    expect(sortPaths([])).toEqual([]);
  });

  test('should handle complex directory structure', () => {
    const input = [
      `src${sep}utils${sep}file3.ts`,
      `src${sep}index.ts`,
      `tests${sep}utils${sep}a.ts`,
      `src${sep}utils${sep}b.ts`,
      'package.json',
      'README.md',
      `src${sep}components${sep}Component.tsx`,
    ];
    const expected = [
      `src${sep}components${sep}Component.tsx`,
      `src${sep}utils${sep}b.ts`,
      `src${sep}utils${sep}file3.ts`,
      `src${sep}index.ts`,
      `tests${sep}utils${sep}a.ts`,
      'package.json',
      'README.md',
    ];
    expect(sortPaths(input)).toEqual(expected);
  });

  test('should handle paths with multiple separators', () => {
    const input = [`a${sep}b${sep}c`, `a${sep}b`, `a${sep}b${sep}`];
    const expected = [`a${sep}b`, `a${sep}b${sep}`, `a${sep}b${sep}c`];
    expect(sortPaths(input)).toEqual(expected);
  });

  test('should be case-insensitive', () => {
    const input = [`B${sep}`, `a${sep}`, 'C', 'd'];
    const expected = [`a${sep}`, `B${sep}`, 'C', 'd'];
    expect(sortPaths(input)).toEqual(expected);
  });
});
</file>

<file path="tests/core/file/fileProcess.test.ts">
import { describe, expect, it, vi } from 'vitest';
import type { FileManipulator } from '../../../src/core/file/fileManipulate.js';
import { processFiles } from '../../../src/core/file/fileProcess.js';
import type { RawFile } from '../../../src/core/file/fileTypes.js';
import { type FileProcessTask, processContent } from '../../../src/core/file/workers/fileProcessWorker.js';
import fileProcessWorker from '../../../src/core/file/workers/fileProcessWorker.js';
import { createMockConfig } from '../../testing/testUtils.js';

const createMockFileManipulator = (): FileManipulator => ({
  removeComments: (content: string) => content.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, ''),
  removeEmptyLines: (content: string) => content.replace(/^\s*[\r\n]/gm, ''),
});

const mockGetFileManipulator = (filePath: string): FileManipulator | null => {
  if (filePath.endsWith('.js')) {
    return createMockFileManipulator();
  }
  return null;
};

const mockInitTaskRunner = (numOfTasks: number) => {
  return async (task: FileProcessTask) => {
    return await fileProcessWorker(task);
  };
};

describe('fileProcess', () => {
  describe('processFiles', () => {
    it('should process multiple files', async () => {
      const mockRawFiles: RawFile[] = [
        { path: 'file1.js', content: '// comment\nconst a = 1;' },
        { path: 'file2.js', content: '/* comment */\nconst b = 2;' },
      ];
      const config = createMockConfig({
        output: {
          removeComments: true,
          removeEmptyLines: true,
        },
      });

      const result = await processFiles(mockRawFiles, config, () => {}, {
        initTaskRunner: mockInitTaskRunner,
        getFileManipulator: mockGetFileManipulator,
      });

      expect(result).toEqual([
        { path: 'file1.js', content: 'const a = 1;' },
        { path: 'file2.js', content: 'const b = 2;' },
      ]);
    });
  });

  describe('processContent', () => {
    it('should remove comments and empty lines when configured', async () => {
      const content = '// comment\nconst a = 1;\n\n/* multi-line\ncomment */\nconst b = 2;';
      const filePath = 'test.js';
      const config = createMockConfig({
        output: {
          removeComments: true,
          removeEmptyLines: true,
        },
      });

      const result = await processContent({ path: filePath, content }, config);

      expect(result).toBe('const a = 1;\nconst b = 2;');
    });

    it('should not remove comments or empty lines when not configured', async () => {
      const content = '// comment\nconst a = 1;\n\n/* multi-line\ncomment */\nconst b = 2;';
      const filePath = 'test.js';
      const config = createMockConfig({
        output: {
          removeComments: false,
          removeEmptyLines: false,
        },
      });

      const result = await processContent({ path: filePath, content }, config);

      expect(result).toBe(content.trim());
    });

    it('should handle files without a manipulator', async () => {
      const content = 'Some content';
      const filePath = 'unknown.ext';
      const config = createMockConfig({
        output: {
          removeComments: true,
          removeEmptyLines: true,
        },
      });

      const result = await processContent({ path: filePath, content }, config);

      expect(result).toBe(content);
    });

    it('should add line numbers when showLineNumbers is true', async () => {
      const content = 'Line 1\nLine 2\nLine 3';
      const filePath = 'test.txt';
      const config = createMockConfig({
        output: {
          showLineNumbers: true,
          removeComments: false,
          removeEmptyLines: false,
        },
      });

      const result = await processContent({ path: filePath, content }, config);

      expect(result).toBe('1: Line 1\n2: Line 2\n3: Line 3');
    });

    it('should not add line numbers when showLineNumbers is false', async () => {
      const content = 'Line 1\nLine 2\nLine 3';
      const filePath = 'test.txt';
      const config = createMockConfig({
        output: {
          showLineNumbers: false,
          removeComments: false,
          removeEmptyLines: false,
        },
      });

      const result = await processContent({ path: filePath, content }, config);

      expect(result).toBe('Line 1\nLine 2\nLine 3');
    });

    it('should handle empty content when showLineNumbers is true', async () => {
      const content = '';
      const filePath = 'empty.txt';
      const config = createMockConfig({
        output: {
          showLineNumbers: true,
          removeComments: false,
          removeEmptyLines: false,
        },
      });

      const result = await processContent({ path: filePath, content }, config);

      expect(result).toBe('1: ');
    });

    it('should pad line numbers correctly for files with many lines', async () => {
      const content = Array(100).fill('Line').join('\n');
      const filePath = 'long.txt';
      const config = createMockConfig({
        output: {
          showLineNumbers: true,
          removeComments: false,
          removeEmptyLines: false,
        },
      });

      const result = await processContent({ path: filePath, content }, config);

      const lines = result.split('\n');
      expect(lines[0]).toBe('  1: Line');
      expect(lines[9]).toBe(' 10: Line');
      expect(lines[99]).toBe('100: Line');
    });
  });
});
</file>

<file path="tests/core/file/packageJsonParse.test.ts">
import * as fs from 'node:fs/promises';
import path from 'node:path';
import * as url from 'node:url';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { getVersion } from '../../../src/core/file/packageJsonParse.js';
import { logger } from '../../../src/shared/logger.js';

vi.mock('fs/promises');
vi.mock('url');

describe('packageJsonParse', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  test('getVersion should return correct version from package.json', async () => {
    const mockPackageJson = {
      name: 'repomix',
      version: '1.2.3',
    };

    vi.mocked(url.fileURLToPath).mockReturnValue('/mock/path/to/src/core/file');
    vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockPackageJson));

    const version = await getVersion();

    expect(version).toBe('1.2.3');
    expect(url.fileURLToPath).toHaveBeenCalledWith(expect.any(URL));
    expect(fs.readFile).toHaveBeenCalledWith(
      path.join('/mock/path/to/src/core/file', '..', '..', '..', 'package.json'),
      'utf-8',
    );
  });

  test('getVersion should handle missing version in package.json', async () => {
    const mockPackageJson = {
      name: 'repomix',
    };

    const loggerSpy = vi.spyOn(logger, 'warn').mockImplementation(vi.fn());

    vi.mocked(url.fileURLToPath).mockReturnValue('/mock/path/to/src/core/file2');
    vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockPackageJson));

    const version = await getVersion();

    expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('No version found in package.json'));

    expect(version).toBe('unknown');
  });
});
</file>

<file path="tests/core/file/permissionCheck.test.ts">
import { constants } from 'node:fs';
import * as fs from 'node:fs/promises';
import { platform } from 'node:os';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { PermissionError, checkDirectoryPermissions } from '../../../src/core/file/permissionCheck.js';

vi.mock('node:fs/promises');
vi.mock('node:os');

describe('permissionCheck', () => {
  const testDirPath = '/test/directory';

  beforeEach(() => {
    vi.resetAllMocks();
    vi.mocked(platform).mockReturnValue('linux');
  });

  describe('successful cases', () => {
    test('should return success when all permissions are available', async () => {
      // Mock successful readdir
      vi.mocked(fs.readdir).mockResolvedValue([]);

      // Mock successful access checks
      vi.mocked(fs.access).mockResolvedValue(undefined);

      const result = await checkDirectoryPermissions(testDirPath);

      expect(result).toEqual({
        hasAllPermission: true,
        details: {
          read: true,
          write: true,
          execute: true,
        },
      });

      // Verify all permission checks were called
      expect(fs.access).toHaveBeenCalledWith(testDirPath, constants.R_OK);
      expect(fs.access).toHaveBeenCalledWith(testDirPath, constants.W_OK);
      expect(fs.access).toHaveBeenCalledWith(testDirPath, constants.X_OK);
    });

    test('should pass with only required permissions', async () => {
      // Mock successful readdir
      vi.mocked(fs.readdir).mockResolvedValue([]);

      // Mock mixed permission check results
      vi.mocked(fs.access).mockImplementation(async (path, mode) => {
        if (mode === constants.R_OK || mode === constants.X_OK) {
          return Promise.resolve(undefined);
        }
        return Promise.reject(new Error('Permission denied'));
      });

      const result = await checkDirectoryPermissions(testDirPath);

      expect(result).toEqual({
        hasAllPermission: false,
        details: {
          read: true,
          write: false,
          execute: true,
        },
      });
    });
  });

  describe('error cases', () => {
    test('should handle EPERM error', async () => {
      const error = new Error('Permission denied');
      (error as NodeJS.ErrnoException).code = 'EPERM';
      vi.mocked(fs.readdir).mockRejectedValue(error);

      const result = await checkDirectoryPermissions(testDirPath);

      expect(result).toEqual({
        hasAllPermission: false,
        error: expect.any(PermissionError),
      });
      expect(result.error).toBeInstanceOf(PermissionError);
      expect(result.error?.message).toContain('Permission denied');
    });

    test('should handle EACCES error', async () => {
      const error = new Error('Access denied');
      (error as NodeJS.ErrnoException).code = 'EACCES';
      vi.mocked(fs.readdir).mockRejectedValue(error);

      const result = await checkDirectoryPermissions(testDirPath);

      expect(result).toEqual({
        hasAllPermission: false,
        error: expect.any(PermissionError),
      });
      expect(result.error).toBeInstanceOf(PermissionError);
      expect(result.error?.message).toContain('Permission denied');
    });

    test('should handle EISDIR error', async () => {
      const error = new Error('Is a directory');
      (error as NodeJS.ErrnoException).code = 'EISDIR';
      vi.mocked(fs.readdir).mockRejectedValue(error);

      const result = await checkDirectoryPermissions(testDirPath);

      expect(result).toEqual({
        hasAllPermission: false,
        error: expect.any(PermissionError),
      });
    });

    test('should handle non-Error objects', async () => {
      vi.mocked(fs.readdir).mockRejectedValue('String error');

      const result = await checkDirectoryPermissions(testDirPath);

      expect(result).toEqual({
        hasAllPermission: false,
        error: new Error('String error'),
      });
    });
  });

  describe('platform specific behavior', () => {
    test('should return macOS specific error message', async () => {
      // Mock platform as macOS
      vi.mocked(platform).mockReturnValue('darwin');

      const error = new Error('Permission denied');
      (error as NodeJS.ErrnoException).code = 'EACCES';
      vi.mocked(fs.readdir).mockRejectedValue(error);

      const result = await checkDirectoryPermissions(testDirPath);

      expect(result.error).toBeInstanceOf(PermissionError);
      expect(result.error?.message).toContain('macOS security restrictions');
      expect(result.error?.message).toContain('System Settings');
      expect(result.error?.message).toContain('Privacy & Security');
    });

    test('should return standard error message for non-macOS platforms', async () => {
      // Mock platform as Windows
      vi.mocked(platform).mockReturnValue('win32');

      const error = new Error('Permission denied');
      (error as NodeJS.ErrnoException).code = 'EACCES';
      vi.mocked(fs.readdir).mockRejectedValue(error);

      const result = await checkDirectoryPermissions(testDirPath);

      expect(result.error).toBeInstanceOf(PermissionError);
      expect(result.error?.message).toBe(`Permission denied: Cannot access '${testDirPath}'`);
      expect(result.error?.message).not.toContain('macOS security restrictions');
    });
  });

  describe('PermissionError class', () => {
    test('should create PermissionError with correct properties', () => {
      const message = 'Test error message';
      const path = '/test/path';
      const code = 'EACCES';

      const error = new PermissionError(message, path, code);

      expect(error).toBeInstanceOf(Error);
      expect(error.name).toBe('PermissionError');
      expect(error.message).toBe(message);
      expect(error.path).toBe(path);
      expect(error.code).toBe(code);
    });

    test('should create PermissionError without code', () => {
      const message = 'Test error message';
      const path = '/test/path';

      const error = new PermissionError(message, path);

      expect(error).toBeInstanceOf(Error);
      expect(error.name).toBe('PermissionError');
      expect(error.message).toBe(message);
      expect(error.path).toBe(path);
      expect(error.code).toBeUndefined();
    });
  });

  describe('edge cases', () => {
    test('should handle undefined error code', async () => {
      const error = new Error('Permission denied');
      vi.mocked(fs.readdir).mockRejectedValue(error);

      const result = await checkDirectoryPermissions(testDirPath);

      expect(result).toEqual({
        hasAllPermission: false,
        error: error,
      });
    });

    test('should handle partial permission checks failing', async () => {
      // Mock successful readdir
      vi.mocked(fs.readdir).mockResolvedValue([]);

      // Mock access to fail for write permission only
      vi.mocked(fs.access).mockImplementation(async (path, mode) => {
        if (mode === constants.W_OK) {
          throw new Error('Write permission denied');
        }
        return Promise.resolve(undefined);
      });

      const result = await checkDirectoryPermissions(testDirPath);

      expect(result).toEqual({
        hasAllPermission: false,
        details: {
          read: true,
          write: false,
          execute: true,
        },
      });
    });

    test('should handle all permission checks failing', async () => {
      // Mock successful readdir
      vi.mocked(fs.readdir).mockResolvedValue([]);

      // Mock all access checks to fail
      vi.mocked(fs.access).mockRejectedValue(new Error('Permission denied'));

      const result = await checkDirectoryPermissions(testDirPath);

      expect(result).toEqual({
        hasAllPermission: false,
        details: {
          read: false,
          write: false,
          execute: false,
        },
      });
    });
  });
});
</file>

<file path="tests/core/metrics/calculateAllFileMetrics.test.ts">
import { describe, expect, it, vi } from 'vitest';
import type { ProcessedFile } from '../../../src/core/file/fileTypes.js';
import { calculateAllFileMetrics } from '../../../src/core/metrics/calculateAllFileMetrics.js';
import type { FileMetricsTask } from '../../../src/core/metrics/workers/fileMetricsWorker.js';
import fileMetricsWorker from '../../../src/core/metrics/workers/fileMetricsWorker.js';
import type { RepomixProgressCallback } from '../../../src/shared/types.js';

vi.mock('../../shared/processConcurrency', () => ({
  getProcessConcurrency: () => 1,
}));

const mockInitTaskRunner = (numOfTasks: number) => {
  return async (task: FileMetricsTask) => {
    return await fileMetricsWorker(task);
  };
};

describe('calculateAllFileMetrics', () => {
  it('should calculate metrics for all files', async () => {
    const processedFiles: ProcessedFile[] = [
      { path: 'file1.txt', content: 'a'.repeat(100) },
      { path: 'file2.txt', content: 'b'.repeat(200) },
    ];
    const progressCallback: RepomixProgressCallback = vi.fn();

    const result = await calculateAllFileMetrics(processedFiles, 'o200k_base', progressCallback, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(result).toEqual([
      { path: 'file1.txt', charCount: 100, tokenCount: 13 },
      { path: 'file2.txt', charCount: 200, tokenCount: 50 },
    ]);
  });
});
</file>

<file path="tests/core/metrics/calculateMetrics.test.ts">
import { type Mock, describe, expect, it, vi } from 'vitest';
import type { ProcessedFile } from '../../../src/core/file/fileTypes.js';
import { calculateAllFileMetrics } from '../../../src/core/metrics/calculateAllFileMetrics.js';
import { calculateMetrics } from '../../../src/core/metrics/calculateMetrics.js';
import { TokenCounter } from '../../../src/core/tokenCount/tokenCount.js';
import type { RepomixProgressCallback } from '../../../src/shared/types.js';
import { createMockConfig } from '../../testing/testUtils.js';

vi.mock('../../../src/core/tokenCount/tokenCount.js');
vi.mock('../../../src/core/metrics/aggregateMetrics.js');
vi.mock('../../../src/core/metrics/calculateAllFileMetrics.js');

describe('calculateMetrics', () => {
  it('should calculate metrics and return the result', async () => {
    const processedFiles: ProcessedFile[] = [
      { path: 'file1.txt', content: 'a'.repeat(100) },
      { path: 'file2.txt', content: 'b'.repeat(200) },
    ];
    const output = 'a'.repeat(300);
    const progressCallback: RepomixProgressCallback = vi.fn();

    const mockTokenCounter = {
      countTokens: vi.fn(),
      free: vi.fn(),
    };
    (TokenCounter as unknown as Mock).mockImplementation(() => mockTokenCounter);

    const fileMetrics = [
      { path: 'file1.txt', charCount: 100, tokenCount: 10 },
      { path: 'file2.txt', charCount: 200, tokenCount: 20 },
    ];
    (calculateAllFileMetrics as unknown as Mock).mockResolvedValue(fileMetrics);

    const aggregatedResult = {
      totalFiles: 2,
      totalCharacters: 300,
      totalTokens: 30,
      fileCharCounts: {
        'file1.txt': 100,
        'file2.txt': 200,
      },
      fileTokenCounts: {
        'file1.txt': 10,
        'file2.txt': 20,
      },
    };

    const config = createMockConfig();

    const result = await calculateMetrics(processedFiles, output, progressCallback, config, {
      calculateAllFileMetrics,
      calculateOutputMetrics: () => Promise.resolve(30),
    });

    expect(progressCallback).toHaveBeenCalledWith('Calculating metrics...');
    expect(calculateAllFileMetrics).toHaveBeenCalledWith(processedFiles, 'o200k_base', progressCallback);
    expect(result).toEqual(aggregatedResult);
  });
});
</file>

<file path="tests/core/metrics/calculateOutputMetrics.test.ts">
import { describe, expect, it, vi } from 'vitest';
import { calculateOutputMetrics } from '../../../src/core/metrics/calculateOutputMetrics.js';
import type { OutputMetricsTask } from '../../../src/core/metrics/workers/outputMetricsWorker.js';
import outputMetricsWorker from '../../../src/core/metrics/workers/outputMetricsWorker.js';
import { logger } from '../../../src/shared/logger.js';

vi.mock('../../../src/shared/logger');

const mockInitTaskRunner = () => {
  return async (task: OutputMetricsTask) => {
    return await outputMetricsWorker(task);
  };
};

describe('calculateOutputMetrics', () => {
  it('should calculate metrics for output content', async () => {
    const content = 'test content';
    const encoding = 'o200k_base';
    const path = 'test.txt';

    const result = await calculateOutputMetrics(content, encoding, path, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(result).toBe(2); // 'test content' should be counted as 2 tokens
  });

  it('should work without a specified path', async () => {
    const content = 'test content';
    const encoding = 'o200k_base';

    const result = await calculateOutputMetrics(content, encoding, undefined, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(result).toBe(2);
  });

  it('should handle errors from worker', async () => {
    const content = 'test content';
    const encoding = 'o200k_base';
    const mockError = new Error('Worker error');

    const mockErrorTaskRunner = () => {
      return async () => {
        throw mockError;
      };
    };

    await expect(
      calculateOutputMetrics(content, encoding, undefined, {
        initTaskRunner: mockErrorTaskRunner,
      }),
    ).rejects.toThrow('Worker error');

    expect(logger.error).toHaveBeenCalledWith('Error during token count:', mockError);
  });

  it('should handle empty content', async () => {
    const content = '';
    const encoding = 'o200k_base';

    const result = await calculateOutputMetrics(content, encoding, undefined, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(result).toBe(0);
  });

  it('should work with longer complex content', async () => {
    const content = 'This is a longer test content with multiple sentences. It should work correctly.';
    const encoding = 'o200k_base';

    const result = await calculateOutputMetrics(content, encoding, undefined, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(result).toBeGreaterThan(0);
    expect(typeof result).toBe('number');
  });
});
</file>

<file path="tests/core/output/outputStyles/markdownStyle.test.ts">
import Handlebars from 'handlebars';
import { describe, expect, test } from 'vitest';
import { getMarkdownTemplate } from '../../../../src/core/output/outputStyles/markdownStyle.js';

describe('markdownStyle', () => {
  describe('getMarkdownTemplate', () => {
    test('should return valid markdown template', () => {
      const template = getMarkdownTemplate();
      expect(template).toContain('# File Summary');
      expect(template).toContain('# Directory Structure');
      expect(template).toContain('# Files');
      expect(template).toContain('{{#if instruction}}');
      expect(template).toContain('# Instruction');
    });

    test('should correctly render template with basic data', () => {
      const template = getMarkdownTemplate();
      const compiledTemplate = Handlebars.compile(template);
      const data = {
        generationHeader: 'Generated Test Header',
        summaryPurpose: 'Test Purpose',
        summaryFileFormat: 'Test Format',
        summaryUsageGuidelines: 'Test Guidelines',
        summaryNotes: 'Test Notes',
        treeString: 'src/\n  index.ts',
        processedFiles: [
          {
            path: 'src/index.ts',
            content: 'console.log("Hello");',
          },
        ],
        fileSummaryEnabled: true,
        directoryStructureEnabled: true,
      };

      const result = compiledTemplate(data);

      expect(result).toContain('Generated Test Header');
      expect(result).toContain('Test Purpose');
      expect(result).toContain('Test Format');
      expect(result).toContain('Test Guidelines');
      expect(result).toContain('Test Notes');
      expect(result).toContain('src/\n  index.ts');
      expect(result).toContain('## File: src/index.ts');
      expect(result).toContain('console.log("Hello");');
    });

    test('should render optional header text when provided', () => {
      const template = getMarkdownTemplate();
      const compiledTemplate = Handlebars.compile(template);
      const data = {
        headerText: 'Custom Header Text',
        processedFiles: [],
        fileSummaryEnabled: true,
        directoryStructureEnabled: true,
      };

      const result = compiledTemplate(data);

      expect(result).toContain('### User Provided Header');
      expect(result).toContain('Custom Header Text');
    });

    test('should not render header section when headerText is not provided', () => {
      const template = getMarkdownTemplate();
      const compiledTemplate = Handlebars.compile(template);
      const data = {
        processedFiles: [],
        fileSummaryEnabled: true,
        directoryStructureEnabled: true,
      };

      const result = compiledTemplate(data);

      expect(result).not.toContain('### User Provided Header');
    });

    test('should render instruction section when provided', () => {
      const template = getMarkdownTemplate();
      const compiledTemplate = Handlebars.compile(template);
      const data = {
        instruction: 'Custom Instruction Text',
        processedFiles: [],
        fileSummaryEnabled: true,
        directoryStructureEnabled: true,
      };

      const result = compiledTemplate(data);

      expect(result).toContain('# Instruction');
      expect(result).toContain('Custom Instruction Text');
    });
  });

  describe('getFileExtension helper', () => {
    // Helper to get extension mapping result
    const getExtension = (filePath: string): string => {
      const helper = Handlebars.helpers.getFileExtension as Handlebars.HelperDelegate;
      return helper(filePath) as string;
    };

    // JavaScript variants
    test('should handle JavaScript related extensions', () => {
      expect(getExtension('file.js')).toBe('javascript');
      expect(getExtension('file.jsx')).toBe('javascript');
      expect(getExtension('file.ts')).toBe('typescript');
      expect(getExtension('file.tsx')).toBe('typescript');
    });

    // Web technologies
    test('should handle web technology extensions', () => {
      expect(getExtension('file.html')).toBe('html');
      expect(getExtension('file.css')).toBe('css');
      expect(getExtension('file.scss')).toBe('scss');
      expect(getExtension('file.sass')).toBe('scss');
      expect(getExtension('file.vue')).toBe('vue');
    });

    // Backend languages
    test('should handle backend language extensions', () => {
      expect(getExtension('file.py')).toBe('python');
      expect(getExtension('file.rb')).toBe('ruby');
      expect(getExtension('file.php')).toBe('php');
      expect(getExtension('file.java')).toBe('java');
      expect(getExtension('file.go')).toBe('go');
    });

    // System programming languages
    test('should handle system programming language extensions', () => {
      expect(getExtension('file.c')).toBe('cpp');
      expect(getExtension('file.cpp')).toBe('cpp');
      expect(getExtension('file.rs')).toBe('rust');
      expect(getExtension('file.swift')).toBe('swift');
      expect(getExtension('file.kt')).toBe('kotlin');
    });

    // Configuration and data format files
    test('should handle configuration and data format extensions', () => {
      expect(getExtension('file.json')).toBe('json');
      expect(getExtension('file.json5')).toBe('json5');
      expect(getExtension('file.xml')).toBe('xml');
      expect(getExtension('file.yaml')).toBe('yaml');
      expect(getExtension('file.yml')).toBe('yaml');
      expect(getExtension('file.toml')).toBe('toml');
    });

    // Shell and scripting
    test('should handle shell and scripting extensions', () => {
      expect(getExtension('file.sh')).toBe('bash');
      expect(getExtension('file.bash')).toBe('bash');
      expect(getExtension('file.ps1')).toBe('powershell');
    });

    // Database and query languages
    test('should handle database related extensions', () => {
      expect(getExtension('file.sql')).toBe('sql');
      expect(getExtension('file.graphql')).toBe('graphql');
      expect(getExtension('file.gql')).toBe('graphql');
    });

    // Functional programming languages
    test('should handle functional programming language extensions', () => {
      expect(getExtension('file.fs')).toBe('fsharp');
      expect(getExtension('file.fsx')).toBe('fsharp');
      expect(getExtension('file.hs')).toBe('haskell');
      expect(getExtension('file.clj')).toBe('clojure');
      expect(getExtension('file.cljs')).toBe('clojure');
    });

    // Other languages and tools
    test('should handle other programming language extensions', () => {
      expect(getExtension('file.scala')).toBe('scala');
      expect(getExtension('file.dart')).toBe('dart');
      expect(getExtension('file.ex')).toBe('elixir');
      expect(getExtension('file.exs')).toBe('elixir');
      expect(getExtension('file.erl')).toBe('erlang');
      expect(getExtension('file.coffee')).toBe('coffeescript');
    });

    // Infrastructure and templating
    test('should handle infrastructure and templating extensions', () => {
      expect(getExtension('file.tf')).toBe('hcl');
      expect(getExtension('file.tfvars')).toBe('hcl');
      expect(getExtension('file.dockerfile')).toBe('dockerfile');
      expect(getExtension('file.pug')).toBe('pug');
      expect(getExtension('file.proto')).toBe('protobuf');
    });

    // Miscellaneous
    test('should handle miscellaneous file extensions', () => {
      expect(getExtension('file.md')).toBe('markdown');
      expect(getExtension('file.r')).toBe('r');
      expect(getExtension('file.pl')).toBe('perl');
      expect(getExtension('file.pm')).toBe('perl');
      expect(getExtension('file.lua')).toBe('lua');
      expect(getExtension('file.groovy')).toBe('groovy');
      expect(getExtension('file.vb')).toBe('vb');
    });

    // Edge cases
    test('should handle edge cases', () => {
      expect(getExtension('file')).toBe(''); // No extension
      expect(getExtension('.gitignore')).toBe(''); // Dotfile
      expect(getExtension('file.unknown')).toBe(''); // Unknown extension
      expect(getExtension('path/to/file.js')).toBe('javascript'); // Path with directory
    });
  });
});
</file>

<file path="tests/core/output/outputStyles/plainStyle.test.ts">
import process from 'node:process';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { generateOutput } from '../../../../src/core/output/outputGenerate.js';
import { createMockConfig } from '../../../testing/testUtils.js';

vi.mock('fs/promises');

describe('plainStyle', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  test('generateOutput for plain should include user-provided header text', async () => {
    const mockConfig = createMockConfig({
      output: {
        filePath: 'output.txt',
        style: 'plain',
        headerText: 'Custom header text',
        topFilesLength: 2,
        showLineNumbers: false,
        removeComments: false,
        removeEmptyLines: false,
      },
    });

    const output = await generateOutput([process.cwd()], mockConfig, [], []);

    expect(output).toContain('File Summary');
    expect(output).toContain('Directory Structure');
    expect(output).toContain('Custom header text');
    expect(output).toContain('Files');
  });
});
</file>

<file path="tests/core/output/outputStyles/xmlStyle.test.ts">
import process from 'node:process';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { generateOutput } from '../../../../src/core/output/outputGenerate.js';
import { createMockConfig } from '../../../testing/testUtils.js';

vi.mock('fs/promises');

describe('xmlStyle', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  test('generateOutput for xml should include user-provided header text', async () => {
    const mockConfig = createMockConfig({
      output: {
        filePath: 'output.txt',
        style: 'xml',
        headerText: 'Custom header text',
        topFilesLength: 2,
        showLineNumbers: false,
        removeComments: false,
        removeEmptyLines: false,
      },
    });

    const output = await generateOutput([process.cwd()], mockConfig, [], []);

    expect(output).toContain('file_summary');
    expect(output).toContain('directory_structure');
    expect(output).toContain('Custom header text');
    expect(output).toContain('files');
  });
});
</file>

<file path="tests/core/output/outputStyleDecorate.test.ts">
import { describe, expect, it } from 'vitest';
import {
  analyzeContent,
  generateHeader,
  generateSummaryNotes,
  generateSummaryPurpose,
  generateSummaryUsageGuidelines,
} from '../../../src/core/output/outputStyleDecorate.js';
import { createMockConfig } from '../../testing/testUtils.js';

describe('analyzeContent', () => {
  it('should detect entire codebase when using default settings', () => {
    const config = createMockConfig();
    const result = analyzeContent(config);
    expect(result.selection.isEntireCodebase).toBe(true);
  });

  it('should detect subset when using include patterns', () => {
    const config = createMockConfig({
      include: ['src/**/*.ts'],
    });
    const result = analyzeContent(config);
    expect(result.selection.isEntireCodebase).toBe(false);
    expect(result.selection.include).toBe(true);
  });

  it('should detect processing states', () => {
    const config = createMockConfig({
      output: {
        removeComments: true,
        removeEmptyLines: true,
      },
    });
    const result = analyzeContent(config);
    expect(result.processing.commentsRemoved).toBe(true);
    expect(result.processing.emptyLinesRemoved).toBe(true);
  });
});

describe('generateHeader', () => {
  const mockDate = '2025-01-29T11:23:01.763Z';

  it('should generate header for entire codebase', () => {
    const config = createMockConfig();
    const header = generateHeader(config, mockDate);
    expect(header).toContain('entire codebase');
    expect(header).not.toContain('subset');
  });

  it('should generate header for subset with processing', () => {
    const config = createMockConfig({
      include: ['src/**/*.ts'],
      output: {
        removeComments: true,
      },
    });
    const header = generateHeader(config, mockDate);
    expect(header).toContain('subset of the codebase');
    expect(header).toContain('comments have been removed');
  });

  it('should include security check disabled warning', () => {
    const config = createMockConfig({
      security: {
        enableSecurityCheck: false,
      },
    });
    const header = generateHeader(config, mockDate);
    expect(header).toContain('security check has been disabled');
  });

  it('should include multiple processing states', () => {
    const config = createMockConfig({
      output: {
        removeComments: true,
        removeEmptyLines: true,
        showLineNumbers: true,
      },
    });
    const header = generateHeader(config, mockDate);
    expect(header).toContain('comments have been removed');
    expect(header).toContain('empty lines have been removed');
    expect(header).toContain('line numbers have been added');
  });
});

describe('generateSummaryPurpose', () => {
  it('should generate consistent purpose text', () => {
    const purpose = generateSummaryPurpose();
    expect(purpose).toContain('packed representation');
    expect(purpose).toContain('AI systems');
    expect(purpose).toContain('code review');
  });
});

describe('generateSummaryUsageGuidelines', () => {
  it('should include header text note when headerText is provided', () => {
    const config = createMockConfig({
      output: {
        headerText: 'Custom header',
      },
    });
    const guidelines = generateSummaryUsageGuidelines(config, '');
    expect(guidelines).toContain('Repository Description');
  });

  it('should include instruction note when instruction is provided', () => {
    const config = createMockConfig();
    const guidelines = generateSummaryUsageGuidelines(config, 'Custom instruction');
    expect(guidelines).toContain('Repository Instruction');
  });
});

describe('generateSummaryNotes', () => {
  it('should include selection information', () => {
    const config = createMockConfig({
      include: ['src/**/*.ts'],
      ignore: {
        customPatterns: ['*.test.ts'],
      },
    });
    const notes = generateSummaryNotes(config);
    expect(notes).toContain('Only files matching these patterns are included: src/**/*.ts');
    expect(notes).toContain('Files matching these patterns are excluded: *.test.ts');
  });

  it('should include processing notes', () => {
    const config = createMockConfig({
      output: {
        removeComments: true,
        showLineNumbers: true,
        style: 'xml',
        parsableStyle: true,
      },
      security: {
        enableSecurityCheck: false,
      },
    });
    const notes = generateSummaryNotes(config);
    expect(notes).toContain('Code comments have been removed');
    expect(notes).toContain('Line numbers have been added');
    expect(notes).toContain('Content has been formatted for parsing in xml style');
    expect(notes).toContain('Security check has been disabled');
  });

  it('should handle case with minimal processing', () => {
    const config = createMockConfig();
    const notes = generateSummaryNotes(config);
    expect(notes).toContain('Files matching patterns in .gitignore are excluded');
    expect(notes).toContain('Files matching default ignore patterns are excluded');
    expect(notes).not.toContain('Code comments have been removed');
  });
});
</file>

<file path="tests/core/packager/copyToClipboardIfEnabled.test.ts">
import clipboard from 'clipboardy';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { copyToClipboardIfEnabled } from '../../../src/core/packager/copyToClipboardIfEnabled.js';
import type { RepomixProgressCallback } from '../../../src/shared/types.js';

vi.mock('clipboardy');
vi.mock('../../shared/logger');

describe('copyToClipboardIfEnabled', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  it('should copy output to clipboard if flag enabled in config', async () => {
    const output = 'test output';
    const config: RepomixConfigMerged = {
      output: { copyToClipboard: true },
    } as RepomixConfigMerged;
    const progressCallback: RepomixProgressCallback = vi.fn();

    await copyToClipboardIfEnabled(output, progressCallback, config);

    expect(progressCallback).toHaveBeenCalledWith('Copying to clipboard...');
    expect(clipboard.write).toHaveBeenCalledWith(output);
  });

  it('should not copy output to clipboard if flag disabled in config', async () => {
    const output = 'test output';
    const config: RepomixConfigMerged = {
      output: { copyToClipboard: false },
    } as RepomixConfigMerged;
    const progressCallback: RepomixProgressCallback = vi.fn();

    await copyToClipboardIfEnabled(output, progressCallback, config);

    expect(progressCallback).not.toHaveBeenCalled();
    expect(clipboard.write).not.toHaveBeenCalled();
  });
});
</file>

<file path="tests/core/packager/writeOutputToDisk.test.ts">
import fs from 'node:fs/promises';
import path from 'node:path';
import { describe, expect, it, vi } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { writeOutputToDisk } from '../../../src/core/packager/writeOutputToDisk.js';

vi.mock('node:fs/promises');
vi.mock('../../shared/logger');

describe('writeOutputToDisk', () => {
  it('should write output to the specified file path', async () => {
    const output = 'test output';
    const config: RepomixConfigMerged = {
      cwd: '/test/directory',
      output: { filePath: 'output.txt' },
    } as RepomixConfigMerged;

    const outputPath = path.resolve(config.cwd, config.output.filePath);

    await writeOutputToDisk(output, config);

    expect(fs.writeFile).toHaveBeenCalledWith(outputPath, output);
  });
});
</file>

<file path="tests/core/security/workers/securityCheckWorker.test.ts">
import type { SecretLintCoreConfig } from '@secretlint/types';
import { describe, expect, test } from 'vitest';
import { createSecretLintConfig, runSecretLint } from '../../../../src/core/security/workers/securityCheckWorker.js';

describe('securityCheck', () => {
  const config: SecretLintCoreConfig = createSecretLintConfig();

  test('should detect sensitive information', async () => {
    // Sensitive content with secrets from https://secretlint.github.io/
    // secretlint-disable
    const sensitiveContent = `
# Secretlint Demo

URL: https://user:pass@example.com

GitHub Token: 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

SendGrid: "SG.APhb3zgjtx3hajdas1TjBB.H7Sgbba3afgKSDyB442aDK0kpGO3SD332313-L5528Kewhere"

AWS_SECRET_ACCESS_KEY = wJalrXUtnFEMI/K7MDENG/bPxRfiCYSECRETSKEY

Slack:
xoxa-23984754863-2348975623103
xoxb-23984754863-2348975623103
xoxo-23984754863-2348975623103

Private Key:

-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCYdGaf5uYMsilGHfnx/zxXtihdGFr3hCWwebHGhgEAVn0xlsTd
1QwoKi+rpI1O6hzyVOuoQtboODsONGRlHbNl6yJ936Yhmr8PiNwpA5qIxZAdmFv2
tqEllWr0dGPPm3B/2NbjuMpSiJNAcBQa46X++doG5yNMY8NCgTsjBZIBKwIDAQAB
AoGAN+Pkg5aIm/rsurHeoeMqYhV7srVtE/S0RIA4tkkGMPOELhvRzGmAbXEZzNkk
nNujBQww4JywYK3MqKZ4b8F1tMG3infs1w8V7INAYY/c8HzfrT3f+MVxijoKV2Fl
JlUXCclztoZhxAxhCR+WC1Upe1wIrWNwad+JA0Vws/mwrEECQQDxiT/Q0lK+gYaa
+riFeZmOaqwhlFlYNSK2hCnLz0vbnvnZE5ITQoV+yiy2+BhpMktNFsYNCfb0pdKN
D87x+jr7AkEAoZWITvqErh1RbMCXd26QXZEfZyrvVZMpYf8BmWFaBXIbrVGme0/Q
d7amI6B8Vrowyt+qgcUk7rYYaA39jYB7kQJAdaX2sY5gw25v1Dlfe5Q5WYdYBJsv
0alAGUrS2PVF69nJtRS1SDBUuedcVFsP+N2IlCoNmfhKk+vZXOBgWrkZ1QJAGJlE
FAntUvhhofW72VG6ppPmPPV7VALARQvmOWxpoPSbJAqPFqyy5tamejv/UdCshuX/
9huGINUV6BlhJT6PEQJAF/aqQTwZqJdwwJqYEQArSmyOW7UDAlQMmKMofjBbeBvd
H4PSJT5bvaEhxRj7QCwonoX4ZpV0beTnzloS55Z65g==
-----END RSA PRIVATE KEY-----
    `;
    // secretlint-enable

    const secretLintResult = await runSecretLint('test.md', sensitiveContent, config);
    expect(secretLintResult).not.toBeNull();
  });

  test('should not detect sensitive information in normal content', async () => {
    const normalContent = `
# Normal Content

This is a regular markdown file with no sensitive information.

Here's some code:

\`\`\`javascript
function greet(name) {
  console.log(\`Hello, \${name}!\`);
}
\`\`\`

And here's a list:

1. Item 1
2. Item 2
3. Item 3

That's all!
    `;

    const secretLintResult = await runSecretLint('normal.md', normalContent, config);
    expect(secretLintResult).toBeNull();
  });
});
</file>

<file path="tests/core/security/filterOutUntrustedFiles.test.ts">
import { describe, expect, it } from 'vitest';
import type { RawFile } from '../../../src/core/file/fileTypes.js';
import { filterOutUntrustedFiles } from '../../../src/core/security/filterOutUntrustedFiles.js';
import type { SuspiciousFileResult } from '../../../src/core/security/securityCheck.js';

describe('filterOutUntrustedFiles', () => {
  it('should filter out untrusted files', () => {
    const rawFiles: RawFile[] = [
      { path: 'file1.txt', content: 'content 1' },
      { path: 'file2.txt', content: 'content 2' },
      { path: 'file3.txt', content: 'content 3' },
    ];
    const suspiciousFilesResults: SuspiciousFileResult[] = [
      { filePath: 'file2.txt', messages: ['something suspicious.'] },
    ];
    const expectedGoodFiles = [rawFiles[0], rawFiles[2]];

    const result = filterOutUntrustedFiles(rawFiles, suspiciousFilesResults);

    expect(result).toEqual(expectedGoodFiles);
  });

  it('should return all files if no suspicious files', () => {
    const rawFiles: RawFile[] = [
      { path: 'file1.txt', content: 'content 1' },
      { path: 'file2.txt', content: 'content 2' },
      { path: 'file3.txt', content: 'content 3' },
    ];
    const suspiciousFilesResults: SuspiciousFileResult[] = [];

    const result = filterOutUntrustedFiles(rawFiles, suspiciousFilesResults);

    expect(result).toEqual(rawFiles);
  });
});
</file>

<file path="tests/core/security/securityCheck.test.ts">
// src/core/security/securityCheck.test.ts

import pc from 'picocolors';
import { describe, expect, it, vi } from 'vitest';
import type { RawFile } from '../../../src/core/file/fileTypes.js';
import { runSecurityCheck } from '../../../src/core/security/securityCheck.js';
import type { SecurityCheckTask } from '../../../src/core/security/workers/securityCheckWorker.js';
import securityCheckWorker from '../../../src/core/security/workers/securityCheckWorker.js';
import { logger } from '../../../src/shared/logger.js';

vi.mock('../../../src/shared/logger');

const mockFiles: RawFile[] = [
  {
    path: 'test1.js',
    // secretlint-disable
    content: 'URL: https://user:pass@example.com', // Clear security issue
    // secretlint-enable
  },
  {
    path: 'test2.js',
    content: 'console.log("Hello World");', // No secrets
  },
];

const mockInitTaskRunner = () => {
  return async (task: SecurityCheckTask) => {
    return await securityCheckWorker(task);
  };
};

describe('runSecurityCheck', () => {
  it('should identify files with security issues', async () => {
    const result = await runSecurityCheck(mockFiles, () => {}, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(result).toHaveLength(1);
    expect(result[0].filePath).toBe('test1.js');
    expect(result[0].messages).toHaveLength(1);
  });

  it('should call progress callback with correct messages', async () => {
    const progressCallback = vi.fn();

    await runSecurityCheck(mockFiles, progressCallback, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(progressCallback).toHaveBeenCalledWith(
      expect.stringContaining(`Running security check... (1/2) ${pc.dim('test1.js')}`),
    );
    expect(progressCallback).toHaveBeenCalledWith(
      expect.stringContaining(`Running security check... (2/2) ${pc.dim('test2.js')}`),
    );
  });

  it('should handle worker errors gracefully', async () => {
    const mockError = new Error('Worker error');
    const mockErrorTaskRunner = () => {
      return async () => {
        throw mockError;
      };
    };

    await expect(
      runSecurityCheck(mockFiles, () => {}, {
        initTaskRunner: mockErrorTaskRunner,
      }),
    ).rejects.toThrow('Worker error');

    expect(logger.error).toHaveBeenCalledWith('Error during security check:', mockError);
  });

  it('should handle empty file list', async () => {
    const result = await runSecurityCheck([], () => {}, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(result).toEqual([]);
  });

  it('should log performance metrics in trace mode', async () => {
    await runSecurityCheck(mockFiles, () => {}, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(logger.trace).toHaveBeenCalledWith(expect.stringContaining('Starting security check for'));
    expect(logger.trace).toHaveBeenCalledWith(expect.stringContaining('Security check completed in'));
  });

  it('should process files in parallel', async () => {
    const startTime = Date.now();

    await runSecurityCheck(mockFiles, () => {}, {
      initTaskRunner: mockInitTaskRunner,
    });

    const endTime = Date.now();
    const duration = endTime - startTime;

    // Parallel processing should be faster than sequential
    expect(duration).toBeLessThan(1000); // Adjust threshold as needed
  });

  it('should not modify original files', async () => {
    const originalFiles = JSON.parse(JSON.stringify(mockFiles));

    await runSecurityCheck(mockFiles, () => {}, {
      initTaskRunner: mockInitTaskRunner,
    });

    expect(mockFiles).toEqual(originalFiles);
  });
});
</file>

<file path="tests/core/security/validateFileSafety.test.ts">
import { describe, expect, it, vi } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import type { RawFile } from '../../../src/core/file/fileTypes.js';
import type { SuspiciousFileResult } from '../../../src/core/security/securityCheck.js';
import { validateFileSafety } from '../../../src/core/security/validateFileSafety.js';
import type { RepomixProgressCallback } from '../../../src/shared/types.js';

describe('validateFileSafety', () => {
  it('should validate file safety and return safe files and paths', async () => {
    const rawFiles: RawFile[] = [
      { path: 'file1.txt', content: 'content1' },
      { path: 'file2.txt', content: 'content2' },
      { path: 'file3.txt', content: 'content3' },
    ];
    const safeRawFiles = [rawFiles[0], rawFiles[1]];
    const config: RepomixConfigMerged = {
      security: { enableSecurityCheck: true },
    } as RepomixConfigMerged;
    const progressCallback: RepomixProgressCallback = vi.fn();
    const suspiciousFilesResults: SuspiciousFileResult[] = [
      { filePath: 'file2.txt', messages: ['something suspicious.'] },
    ];
    const deps = {
      runSecurityCheck: vi.fn().mockResolvedValue(suspiciousFilesResults),
      filterOutUntrustedFiles: vi.fn().mockReturnValue(safeRawFiles),
    };

    const result = await validateFileSafety(rawFiles, progressCallback, config, deps);

    expect(deps.runSecurityCheck).toHaveBeenCalledWith(rawFiles, progressCallback);
    expect(deps.filterOutUntrustedFiles).toHaveBeenCalledWith(rawFiles, suspiciousFilesResults);
    expect(result).toEqual({
      safeRawFiles,
      safeFilePaths: ['file1.txt', 'file2.txt'],
      suspiciousFilesResults,
    });
  });
});
</file>

<file path="tests/core/tokenCount/tokenCount.test.ts">
import { type Tiktoken, get_encoding } from 'tiktoken';
import { type Mock, afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { TokenCounter } from '../../../src/core/tokenCount/tokenCount.js';
import { logger } from '../../../src/shared/logger.js';

vi.mock('tiktoken', () => ({
  get_encoding: vi.fn(),
}));

vi.mock('../../../src/shared/logger');

describe('TokenCounter', () => {
  let tokenCounter: TokenCounter;
  let mockEncoder: {
    encode: Mock;
    free: Mock;
  };

  beforeEach(() => {
    // Initialize mock encoder
    mockEncoder = {
      encode: vi.fn(),
      free: vi.fn(),
    };

    // Setup mock encoder behavior
    vi.mocked(get_encoding).mockReturnValue(mockEncoder as unknown as Tiktoken);

    // Create new TokenCounter instance
    tokenCounter = new TokenCounter('o200k_base');
  });

  afterEach(() => {
    tokenCounter.free();
    vi.resetAllMocks();
  });

  test('should initialize with o200k_base encoding', () => {
    expect(get_encoding).toHaveBeenCalledWith('o200k_base');
  });

  test('should correctly count tokens for simple text', () => {
    const text = 'Hello, world!';
    const mockTokens = [123, 456, 789]; // Example token IDs
    mockEncoder.encode.mockReturnValue(mockTokens);

    const count = tokenCounter.countTokens(text);

    expect(count).toBe(3); // Length of mockTokens
    expect(mockEncoder.encode).toHaveBeenCalledWith(text);
  });

  test('should handle empty string', () => {
    mockEncoder.encode.mockReturnValue([]);

    const count = tokenCounter.countTokens('');

    expect(count).toBe(0);
    expect(mockEncoder.encode).toHaveBeenCalledWith('');
  });

  test('should handle multi-line text', () => {
    const text = 'Line 1\nLine 2\nLine 3';
    const mockTokens = [1, 2, 3, 4, 5, 6];
    mockEncoder.encode.mockReturnValue(mockTokens);

    const count = tokenCounter.countTokens(text);

    expect(count).toBe(6);
    expect(mockEncoder.encode).toHaveBeenCalledWith(text);
  });

  test('should handle special characters', () => {
    const text = '!@#$%^&*()_+';
    const mockTokens = [1, 2, 3];
    mockEncoder.encode.mockReturnValue(mockTokens);

    const count = tokenCounter.countTokens(text);

    expect(count).toBe(3);
    expect(mockEncoder.encode).toHaveBeenCalledWith(text);
  });

  test('should handle unicode characters', () => {
    const text = '你好，世界！🌍';
    const mockTokens = [1, 2, 3, 4];
    mockEncoder.encode.mockReturnValue(mockTokens);

    const count = tokenCounter.countTokens(text);

    expect(count).toBe(4);
    expect(mockEncoder.encode).toHaveBeenCalledWith(text);
  });

  test('should handle code snippets', () => {
    const text = `
      function hello() {
        console.log("Hello, world!");
      }
    `;
    const mockTokens = Array(10).fill(1); // 10 tokens
    mockEncoder.encode.mockReturnValue(mockTokens);

    const count = tokenCounter.countTokens(text);

    expect(count).toBe(10);
    expect(mockEncoder.encode).toHaveBeenCalledWith(text);
  });

  test('should handle markdown text', () => {
    const text = `
      # Heading
      ## Subheading
      * List item 1
      * List item 2
      
      **Bold text** and _italic text_
    `;
    const mockTokens = Array(15).fill(1); // 15 tokens
    mockEncoder.encode.mockReturnValue(mockTokens);

    const count = tokenCounter.countTokens(text);

    expect(count).toBe(15);
    expect(mockEncoder.encode).toHaveBeenCalledWith(text);
  });

  test('should handle very long text', () => {
    const text = 'a'.repeat(10000);
    const mockTokens = Array(100).fill(1); // 100 tokens
    mockEncoder.encode.mockReturnValue(mockTokens);

    const count = tokenCounter.countTokens(text);

    expect(count).toBe(100);
    expect(mockEncoder.encode).toHaveBeenCalledWith(text);
  });

  test('should properly handle encoding errors without file path', () => {
    const error = new Error('Encoding error');
    mockEncoder.encode.mockImplementation(() => {
      throw error;
    });

    const count = tokenCounter.countTokens('test content');

    expect(count).toBe(0);
    expect(logger.warn).toHaveBeenCalledWith('Failed to count tokens. error: Encoding error');
  });

  test('should properly handle encoding errors with file path', () => {
    const error = new Error('Encoding error');
    mockEncoder.encode.mockImplementation(() => {
      throw error;
    });

    const count = tokenCounter.countTokens('test content', 'test.txt');

    expect(count).toBe(0);
    expect(logger.warn).toHaveBeenCalledWith('Failed to count tokens. path: test.txt, error: Encoding error');
  });

  test('should free encoder resources on cleanup', () => {
    tokenCounter.free();
    expect(mockEncoder.free).toHaveBeenCalled();
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.comments.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile comment support', () => {
  // Test for JavaScript/TypeScript comments
  test('should parse all types of JavaScript/TypeScript comments', async () => {
    const fileContent = [
      '// Single line comment',
      '/* Multi-line comment',
      '   spanning multiple lines */',
      'function func1() {}',
      '',
      '/**',
      ' * JSDoc documentation',
      ' * @param {string} name - The name parameter',
      ' * @returns {void}',
      ' */',
      'function func2(name) {}',
      '',
      '/** Single line JSDoc */',
      'const x = 1;',
    ].join('\n');

    const filePath = 'test.ts';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'Single line comment',
      'Multi-line comment',
      'JSDoc documentation',
      '@param {string} name - The name parameter',
      '@returns {void}',
      'Single line JSDoc',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  // Test for Python comments
  test('should parse all types of Python comments', async () => {
    const fileContent = [
      '# Single line comment',
      "'''",
      'Multi-line docstring',
      'with multiple lines',
      "'''",
      'def func1(): pass',
      '',
      '"""',
      'Double quote docstring',
      'with indentation',
      '    indented line',
      '"""',
      'def func2(): pass',
    ].join('\n');

    const filePath = 'test.py';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = ['Single line comment', 'Multi-line docstring', 'Double quote docstring', 'indented line'];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  // Test for Java comments
  test('should parse all types of Java comments', async () => {
    const fileContent = [
      '// Single line comment',
      '/* Multi-line comment',
      '   spanning multiple lines */',
      'public class Test {',
      '    /**',
      '     * JavaDoc for class field',
      '     */',
      '    private int field;',
      '',
      '    /**',
      '     * JavaDoc for method',
      '     * @param name The name parameter',
      '     * @return A greeting message',
      '     * @throws Exception If something goes wrong',
      '     */',
      '    public String greet(String name) {',
      '        return "Hello";',
      '    }',
      '}',
    ].join('\n');

    const filePath = 'test.java';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'Single line comment',
      'Multi-line comment',
      'JavaDoc for class field',
      'JavaDoc for method',
      '@param name The name parameter',
      '@return A greeting message',
      '@throws Exception If something goes wrong',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  // Test for C# comments
  test('should parse all types of C# comments', async () => {
    const fileContent = [
      '// Single line comment',
      '/* Multi-line comment',
      '   spanning multiple lines */',
      '',
      '/// <summary>',
      '/// XML documentation for class',
      '/// </summary>',
      'public class Test {',
      '    /// <summary>',
      '    /// Property documentation',
      '    /// </summary>',
      '    /// <value>The value description</value>',
      '    public int Property { get; set; }',
      '}',
    ].join('\n');

    const filePath = 'test.cs';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'Single line comment',
      'Multi-line comment',
      'XML documentation for class',
      'Property documentation',
      '<value>The value description</value>',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  // Test for Rust comments
  test('should parse all types of Rust comments', async () => {
    const fileContent = [
      '// Single line comment',
      '/* Multi-line comment',
      '   spanning multiple lines */',
      '',
      '//! Module documentation',
      '',
      '/// Documentation for struct',
      'struct Test {',
      '    /// Field documentation',
      '    field: i32',
      '}',
    ].join('\n');

    const filePath = 'test.rs';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'Single line comment',
      'Multi-line comment',
      'Module documentation',
      'Documentation for struct',
      'Field documentation',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  // Test for Ruby comments
  test('should parse all types of Ruby comments', async () => {
    const fileContent = [
      '# Single line comment',
      'def func1',
      'end',
      '',
      '# Documentation comment',
      '# @param name [String] The name',
      '# @return [void]',
      'def func2(name)',
      'end',
      '',
      '=begin',
      'Multi-line comment block',
      'using =begin/=end syntax',
      '=end',
      'def func3',
      'end',
    ].join('\n');

    const filePath = 'test.rb';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'Single line comment',
      'Documentation comment',
      '@param name [String] The name',
      '@return [void]',
      'Multi-line comment block',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.cpp.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for C/C++', () => {
  // Test for C++
  test('should parse C++ correctly', async () => {
    const fileContent = `
      // Main entry point
      /* The main function that outputs
       * a greeting to the world
       */
      int main() {
        std::cout << "Hello, world!"; return 0;
      }
    `;
    const filePath = 'dummy.cpp';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      '// Main entry point',
      '/* The main function that outputs',
      '* a greeting to the world',
      '*/',
      'int main() {',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should parse C++ header correctly', async () => {
    const fileContent = `
      // Header file main function
      /* This header declares the main function
       * for the program entry point
       */
      int main() { std::cout << "Hello, world!"; return 0; }
    `;
    const filePath = 'dummy.hpp';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = ['main', 'Header file main function', 'This header declares the main function'];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  // Test for C
  test('should parse C correctly', async () => {
    const fileContent = `
      /* The main function
       * Prints a greeting to stdout
       */
      // Entry point of the program
      int main() { printf("Hello, world!"); return 0; }
    `;
    const filePath = 'dummy.c';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = ['main', 'The main function', 'Prints a greeting to stdout', 'Entry point of the program'];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should parse C header correctly', async () => {
    const fileContent = `
      /* Header file for main function
       * Declares the program entry point
       */
      // Main function prototype
      int main() { printf("Hello, world!"); return 0; }
    `;
    const filePath = 'dummy.h';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'main',
      'Header file for main function',
      'Declares the program entry point',
      'Main function prototype',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.csharp.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for C#', () => {
  test('should parse C# correctly', async () => {
    const fileContent = `
      // Program class containing the entry point
      /// <summary>
      /// The main program class
      /// </summary>
      class Program {
        // The main entry point
        /// <summary>
        /// Writes a greeting to the console
        /// </summary>
        static void Main() {
          Console.WriteLine("Hello, world!");
        }
      }
    `;
    const filePath = 'dummy.cs';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      '// Program class containing the entry point',
      '/// <summary>',
      'class Program {',
      '// The main program class',
      '// The main entry point',
      '/// Writes a greeting to the console',
      'static void Main() {',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.go.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for Go', () => {
  test('should parse Go correctly', async () => {
    const fileContent = `
      // Package main is the entry point
      package main

      import (
        "fmt"
        "os"
      )

      // User represents a person
      type User struct {
        Name string
        Age  int
      }

      // Greeter is something that can greet
      type Greeter interface {
        Greet() string
      }

      // Constants
      const (
        MaxUsers = 100
        Version  = "1.0.0"
      )

      // Variables
      var (
        debugMode = false
        logLevel  = "info"
      )

      // SayHello prints a greeting message
      func SayHello(name string) {
        fmt.Printf("Hello, %s!\\n", name)
      }

      // Greet implements the Greeter interface
      func (u User) Greet() string {
        return fmt.Sprintf("Hello, I'm %s!", u.Name)
      }

      // Main entry point
      func main() {
        user := User{Name: "John", Age: 30}
        fmt.Println(user.Greet())
        SayHello(os.Args[1])
      }
    `;
    const filePath = 'sample.go';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      // Package declaration
      'package main',
      'Package main is the entry point',

      // Imports
      'import (',
      '"fmt"',
      '"os"',

      // Struct definition
      'type User struct',
      'User represents a person',
      'Name string',
      'Age  int',

      // Interface definition
      'type Greeter interface',
      'Greeter is something that can greet',
      'Greet() string',

      // Constants
      'const (',
      'MaxUsers = 100',
      'Version  = "1.0.0"',

      // Variables
      'var (',
      'debugMode = false',
      'logLevel  = "info"',

      // Functions
      'func SayHello(name string)',
      'SayHello prints a greeting message',

      // Methods
      'func (u User) Greet() string',
      'Greet implements the Greeter interface',

      // Main function
      'func main()',
      'Main entry point',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should handle single imports', async () => {
    const fileContent = `
      package main

      import "fmt"

      func main() {
        fmt.Println("Hello, world!")
      }
    `;
    const filePath = 'simple.go';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = ['import "fmt"', 'func main()'];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should handle different comment styles', async () => {
    const fileContent = `
      // This is a line comment
      package main

      /* This is a block comment
         spanning multiple lines */
      func main() {
        // Inside function comment
        fmt.Println("Hello")
      }
    `;
    const filePath = 'comments.go';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'This is a line comment',
      'This is a block comment\n         spanning multiple lines',
      'Inside function comment',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should handle type aliases', async () => {
    const fileContent = `
      package main

      // UserID is a type alias for integer
      type UserID int

      // Result is a type alias for map
      type Result map[string]interface{}
    `;
    const filePath = 'types.go';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'type UserID int',
      'UserID is a type alias for integer',
      'type Result map[string]interface{}',
      'Result is a type alias for map',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.java.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for Java', () => {
  test('should parse Java correctly', async () => {
    const fileContent = `
      /**
       * A simple Hello World class
       */
      public class HelloWorld {
        /**
         * Main entry point
         * @param args command line arguments
         */
        public static void main(String[] args) {
          System.out.println("Hello, world!");
        }
      }
    `;
    const filePath = 'dummy.java';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      '/**',
      '* A simple Hello World class',
      '*/',
      'public class HelloWorld {',
      '* Main entry point',
      '* @param args command line arguments',
      'public static void main(String[] args) {',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.javascript.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { CHUNK_SEPARATOR, parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for JavaScript', () => {
  test('should filter captures with same start row', async () => {
    const fileContent = `
      /**
       * Greeting function
       * @param name The name to greet
       */
      function sayHello(name) {  // inline comment
        console.log("Hello, " + name);
      }

      // next function
      function sayGoodbye(name) {
        console.log("Goodbye, " + name);
      }
    `;
    const filePath = 'dummy.js';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');
    // Check content
    expect(result).toContain('/**\n       * Greeting function\n       * @param name The name to greet\n       */');
    expect(result).toContain('// inline comment');
    expect(result).not.toBeUndefined();
    if (result) {
      // Check separator
      const parts = result.split(`${CHUNK_SEPARATOR}\n`);
      expect(parts.length).toBeGreaterThan(1);
      for (const part of parts) {
        expect(part.trim()).not.toBe('');
      }
    }
  });

  test('should parse JSX correctly', async () => {
    const fileContent = `
      // React component function
      /**
       * A hello world component
       * @param {string} name - The name to display
       */
      function sayHello(name) { console.log("Hello, " + name); }
    `;
    const filePath = 'dummy.jsx';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      '// React component function',
      '* A hello world component',
      '* @param {string} name - The name to display',
      'function sayHello(name) { console.log("Hello, " + name); }',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.php.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for PHP', () => {
  test('should parse PHP correctly', async () => {
    const fileContent = `
<?php

namespace App;

use App\Greeter;

// Define the greeting function
function greet($name) {
    // Print the personalized greeting message
    echo "Hello, " . $name . "!";
}

// Execute the greeting function
greet("John");

interface Greeter {
  public function greet($name);
}

final class GreeterImpl implements Greeter {
  public function greet($name) {
    echo "Hello, " . $name . "!";
  }
}

trait GreeterTrait {
  public function greet($name) {
    echo "Hello, " . $name . "!";
  }
}

/**
 * Greeting function
 * @param name The name to greet
 * @return string The greeting message
 */
enum GreeterEnum: string {
  case GREET = "Hello, %s!";
}

?>
`;
    const filePath = 'dummy.php';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');
    const expectContents = [
      'namespace App;',
      'use AppGreeter;',
      'function greet($name) {',
      '// Define the greeting function',
      '// Print the personalized greeting message',
      '// Execute the greeting function',
      'interface Greeter {',
      'public function greet($name) {',
      'final class GreeterImpl implements Greeter {',
      'public function greet($name) {',
      'trait GreeterTrait {',
      'public function greet($name) {',
      '/**',
      '* Greeting function',
      '* @param name The name to greet',
      '* @return string The greeting message',
      '*/',
      'enum GreeterEnum: string {',
    ];
    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.python.test.ts">
import { beforeEach, describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for Python', () => {
  const defaultConfig = {} as RepomixConfigMerged;

  test('should parse basic Python correctly', async () => {
    const fileContent = `
      # Python greeting function
      '''
      A simple greeting function that prints a hello message
      Args:
          name: The name to greet
      '''
      def greet(name): print(f"Hello, {name}")
    `;
    const filePath = 'dummy.py';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'greet',
      'Python greeting function',
      'A simple greeting function',
      'Args:',
      'name: The name to greet',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should parse class with decorators and inheritance', async () => {
    const fileContent = `
      @dataclass
      @register
      class UserModel(BaseModel):
          name: str
          age: int
    `;

    const result = await parseFile(fileContent, 'example.py', defaultConfig);

    const expectContents = ['@dataclass', '@register', 'class UserModel(BaseModel)'];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should parse function with decorators and type annotations', async () => {
    const fileContent = `
      @route("/users")
      @authenticate
      def get_users(page: int = 1, limit: int = 10) -> List[User]:
          return users[page:limit]
    `;

    const result = await parseFile(fileContent, 'example.py', defaultConfig);

    const expectContents = [
      '@route("/users")',
      '@authenticate',
      'def get_users(page: int = 1, limit: int = 10) -> List[User]',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should parse docstring', async () => {
    const fileContent = `
      """
      This is a docstring
      with multiple lines
      """
    `;

    const result = await parseFile(fileContent, 'example.py', defaultConfig);

    const expectContents = ['This is a docstring', 'with multiple lines'];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should parse comments', async () => {
    const fileContent = '# This is a single line comment';

    const result = await parseFile(fileContent, 'example.py', defaultConfig);

    const expectContents = ['# This is a single line comment'];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should parse type aliases', async () => {
    const fileContent = `
      UserId = int
      Result = Union[Success, Error]
    `;

    const result = await parseFile(fileContent, 'example.py', defaultConfig);

    const expectContents = ['UserId = int', 'Result = Union[Success, Error]'];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.rust.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for Rust', () => {
  test('should parse Rust correctly', async () => {
    const fileContent = `
      // Module declaration
      mod greetings {
          // Trait definition
          pub trait Greeter {
              /// Says hello to someone
              fn greet(&self, name: &str) -> String;
          }

          // Struct definition
          #[derive(Debug)]
          pub struct SimpleGreeter {
              prefix: String
          }

          // Implementation block
          impl Greeter for SimpleGreeter {
              fn greet(&self, name: &str) -> String {
                  format!("{}, {}!", self.prefix, name)
              }
          }

          // Enum definition
          #[derive(Debug)]
          pub enum Language {
              English,
              Japanese,
              Spanish
          }

          // Main function
          fn main() {
              let greeter = SimpleGreeter {
                  prefix: String::from("Hello")
              };
              println!("{}", greeter.greet("World"));
          }
      }
    `;
    const filePath = 'dummy.rs';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'mod greetings {',
      'pub trait Greeter {',
      '// Says hello to someone',
      'fn greet(&self, name: &str) -> String {',
      'pub struct SimpleGreeter {',
      'impl Greeter for SimpleGreeter {',
      'pub enum Language {',
      'fn main() {',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { CHUNK_SEPARATOR, parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile', () => {
  // Test for merging adjacent chunks
  test('should merge adjacent chunks', async () => {
    const fileContent = `
      /**
       * First function
       */
      function first() {
        console.log('first');
      }

      /**
       * Second function, right after first
       */
      function second() {
        console.log('second');
      }

      // Some space

      /**
       * Third function
       */
      function third() {
        console.log('third');
      }
    `;
    const filePath = 'dummy.js';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    expect(result).not.toBeUndefined();

    if (result) {
      const chunks = result.split(`\n${CHUNK_SEPARATOR}\n`);
      expect(chunks.length).toBe(4);

      expect(chunks[0]).toContain('* First function');
      expect(chunks[0]).toContain('function first() {');
      expect(chunks[1]).toContain('* Second function');
      expect(chunks[1]).toContain('function second() {');
      expect(chunks[2]).toContain('// Some space');
      expect(chunks[3]).toContain('* Third function');
      expect(chunks[3]).toContain('function third() {');
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.typescript.test.ts">
import { beforeEach, describe, expect, test, vi } from 'vitest';
import type { Language, Point, Query, SyntaxNode, Tree, TreeCursor } from 'web-tree-sitter';
import type { Edit, Range } from 'web-tree-sitter';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';
import { TypeScriptParseStrategy } from '../../../src/core/treeSitter/parseStrategies/TypeScriptParseStrategy.js';

interface MockContext {
  fileContent: string;
  lines: string[];
  tree: Tree;
  query: Query;
  config: RepomixConfigMerged;
}

describe('TypeScript File Parsing', () => {
  describe('parseFile for TypeScript', () => {
    test('should parse TypeScript correctly', async () => {
      const fileContent = `
        // TypeScript function
        /**
         * Says hello to the given name
         * @param name The name to greet
         */
        function sayHello(name) { console.log("Hello, " + name); }
      `;
      const filePath = 'dummy.ts';
      const config = {};
      const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
      expect(typeof result).toBe('string');

      const expectContents = [
        'sayHello',
        'TypeScript function',
        'Says hello to the given name',
        '@param name The name to greet',
      ];

      for (const expectContent of expectContents) {
        expect(result).toContain(expectContent);
      }
    });

    test('should parse TypeScript arrow functions correctly', async () => {
      const fileContent = `
          // Arrow function for addition
          /** Adds two numbers together
           * @param x First number
           * @param y Second number
           */
          const add = (x: number, y: number): number => {
              return x + y;
          };
          // Function type declaration
          let multiply: (a: number, b:number) => number;
          multiply = (a, b) => {
              return a*b;
          }
      `;
      const filePath = 'dummy.ts';
      const config = { output: { compress: true } };
      const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);

      expect(typeof result).toBe('string');

      const expectContents = [
        'add',
        'multiply',
        'Arrow function for addition',
        'Adds two numbers together',
        '@param x First number',
        '@param y Second number',
        'Function type declaration',
      ];

      for (const expectContent of expectContents) {
        expect(result).toContain(expectContent);
      }
    });

    test('should parse TSX correctly', async () => {
      const fileContent = `
        // Greeting component
        /**
         * A simple greeting component
         * @param name Person to greet
         */
        function greet(name: string){ console.log("Hello, " + name); }
      `;
      const filePath = 'dummy.tsx';
      const config = {};
      const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
      expect(typeof result).toBe('string');

      const expectContents = [
        'greet',
        'Greeting component',
        'A simple greeting component',
        '@param name Person to greet',
      ];

      for (const expectContent of expectContents) {
        expect(result).toContain(expectContent);
      }
    });
  });

  describe('TypeScript Parse Strategy', () => {
    let strategy: TypeScriptParseStrategy;
    let processedChunks: Set<string>;
    let mockContext: MockContext;

    // Helper function to create a mock capture object
    const createMockCapture = (name: string, startRow: number, endRow: number): { node: SyntaxNode; name: string } => {
      return {
        node: {
          startPosition: { row: startRow } as Point,
          endPosition: { row: endRow } as Point,
        } as SyntaxNode,
        name,
      };
    };

    beforeEach(() => {
      strategy = new TypeScriptParseStrategy();
      processedChunks = new Set<string>();

      const mockCursor: TreeCursor = {
        nodeType: '',
        nodeTypeId: 0,
        nodeStateId: 0,
        nodeText: '',
        nodeIsNamed: false,
        nodeIsMissing: false,
        startPosition: { row: 0, column: 0 } as Point,
        endPosition: { row: 0, column: 0 } as Point,
        startIndex: 0,
        endIndex: 0,
        nodeId: 0,
        currentNode: {} as SyntaxNode,
        currentDepth: 0,
        currentDescendantIndex: 0,
        currentFieldId: 0,
        currentFieldName: '',
        gotoFirstChild(): boolean {
          return false;
        },
        gotoLastChild(): boolean {
          return false;
        },
        gotoNextSibling(): boolean {
          return false;
        },
        gotoPreviousSibling(): boolean {
          return false;
        },
        gotoParent(): boolean {
          return false;
        },
        gotoFirstChildForIndex(index: number): boolean {
          return false;
        },
        gotoFirstChildForPosition(goalPosition: Point): boolean {
          return false;
        },
        gotoDescendant(goalDescendantIndex: number): boolean {
          return false;
        },
        reset(): void {},
        resetTo(cursor: TreeCursor): void {},
        delete(): void {},
      };

      const mockTree: Tree = {
        rootNode: {} as SyntaxNode,
        rootNodeWithOffset(offsetBytes: number, offsetExtent: Point): SyntaxNode {
          return {} as SyntaxNode;
        },
        getChangedRanges(other: Tree): Range[] {
          return [];
        },
        getIncludedRanges(): Range[] {
          return [];
        },
        copy(): Tree {
          return this;
        },
        delete() {},
        edit(delta: Edit) {},
        getLanguage(): Language {
          return {} as Language;
        },
        walk(): TreeCursor {
          return mockCursor;
        },
      };

      mockContext = {
        fileContent: '',
        lines: [],
        tree: mockTree,
        query: {} as Query,
        config: {} as RepomixConfigMerged,
      };
    });

    describe('Type declarations', () => {
      test('should correctly parse class declarations with extends', () => {
        const lines = ['class ChildClass extends ParentClass {', '  constructor() { super(); }', '}'];
        const capture = createMockCapture('definition.class', 0, 2);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('class ChildClass extends ParentClass');
      });

      test('should correctly parse class declarations with implements', () => {
        const lines = [
          'class MyService implements IService {',
          '  getData(): Promise<Data> { return Promise.resolve({}); }',
          '}',
        ];
        const capture = createMockCapture('definition.class', 0, 2);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('class MyService implements IService');
      });

      test('should correctly parse interface declarations', () => {
        const lines = [
          'interface IRepository<T> {',
          '  findById(id: string): Promise<T>;',
          '  save(entity: T): Promise<void>;',
          '}',
        ];
        const capture = createMockCapture('definition.interface', 0, 3);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe(
          [
            'interface IRepository<T> {',
            '  findById(id: string): Promise<T>;',
            '  save(entity: T): Promise<void>;',
            '}',
          ].join('\n'),
        );
      });

      test('should correctly parse type aliases', () => {
        const lines = ['type RequestHandler = (req: Request, res: Response) => Promise<void>;'];
        const capture = createMockCapture('definition.type', 0, 0);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('type RequestHandler = (req: Request, res: Response) => Promise<void>;');
      });

      test('should correctly parse enum declarations', () => {
        const lines = [
          'enum Direction {',
          '  Up = "UP",',
          '  Down = "DOWN",',
          '  Left = "LEFT",',
          '  Right = "RIGHT"',
          '}',
        ];
        const capture = createMockCapture('definition.enum', 0, 5);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe(
          ['enum Direction {', '  Up = "UP",', '  Down = "DOWN",', '  Left = "LEFT",', '  Right = "RIGHT"', '}'].join(
            '\n',
          ),
        );
      });
    });

    describe('Import declarations', () => {
      test('should correctly parse named imports', () => {
        const lines = ['import { useState, useEffect } from "react";'];
        const capture = createMockCapture('definition.import', 0, 0);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('import { useState, useEffect } from "react";');
      });

      test('should correctly parse default imports', () => {
        const lines = ['import React from "react";'];
        const capture = createMockCapture('definition.import', 0, 0);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('import React from "react";');
      });
    });

    describe('Property declarations', () => {
      test('should correctly parse class properties', () => {
        const lines = ['class Example {', '  private readonly name: string;', '  public age: number = 0;', '}'];
        const capture = createMockCapture('definition.property', 1, 1);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBeNull();
      });

      test('should correctly parse interface properties', () => {
        const lines = ['interface Config {', '  readonly apiKey: string;', '  timeout?: number;', '}'];
        const capture = createMockCapture('definition.property', 1, 1);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBeNull();
      });
    });

    describe('Duplicate detection for type definitions', () => {
      test('should detect duplicate class definitions', () => {
        const lines = ['class Example {', '  constructor() {}', '}'];
        const capture1 = createMockCapture('definition.class', 0, 2);
        const capture2 = createMockCapture('definition.class', 0, 2);

        const result1 = strategy.parseCapture(capture1, lines, processedChunks, mockContext);
        expect(result1).toBe('class Example');

        const result2 = strategy.parseCapture(capture2, lines, processedChunks, mockContext);
        expect(result2).toBeNull();
      });
    });

    describe('Function declarations', () => {
      test('should correctly parse standard function declarations', () => {
        const lines = [
          'function myFunction(param1: string, param2: number): void {',
          '  console.log(param1, param2);',
          '}',
        ];
        const capture = createMockCapture('name.definition.function', 0, 2);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('function myFunction(param1: string, param2: number): void');
        expect(processedChunks.size).toBe(1);
      });

      test('should correctly parse exported function declarations', () => {
        const lines = [
          'export function exportedFunction(param: string): string {',
          '  return param.toUpperCase();',
          '}',
        ];
        const capture = createMockCapture('name.definition.function', 0, 2);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('export function exportedFunction(param: string): string');
        expect(processedChunks.size).toBe(1);
      });
    });

    describe('Arrow functions', () => {
      test('should correctly parse arrow function expressions', () => {
        const lines = ['const arrowFunc = (a: number, b: number): number => {', '  return a + b;', '};'];
        const capture = createMockCapture('name.definition.function', 0, 2);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('const arrowFunc = (a: number, b: number): number =>');
        expect(processedChunks.has('func:arrowFunc')).toBe(true);
      });

      test('should correctly parse exported arrow function expressions', () => {
        const lines = ['export const exportedArrow = (text: string): string => {', '  return text.trim();', '};'];
        const capture = createMockCapture('name.definition.function', 0, 2);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('export const exportedArrow = (text: string): string =>');
        expect(processedChunks.has('func:exportedArrow')).toBe(true);
      });

      test('should correctly parse one-line arrow functions', () => {
        const lines = ['const shortArrow = (x: number): number => x * 2;'];
        const capture = createMockCapture('name.definition.function', 0, 0);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('const shortArrow = (x: number): number');
        expect(processedChunks.has('func:shortArrow')).toBe(true);
      });

      test('should correctly parse async arrow functions', () => {
        const lines = [
          'const asyncArrow = async (url: string): Promise<Response> => {',
          '  return await fetch(url);',
          '};',
        ];
        const capture = createMockCapture('name.definition.function', 0, 2);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('const asyncArrow = async (url: string): Promise<Response> =>');
        expect(processedChunks.has('func:asyncArrow')).toBe(true);
      });
    });

    describe('Method declarations', () => {
      test('should correctly parse class method declarations', () => {
        const lines = ['class MyClass {', '  myMethod(param: string): void {', '    console.log(param);', '  }', '}'];
        const capture = createMockCapture('name.definition.method', 1, 3);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('myMethod(param: string): void');
      });

      test('should correctly parse interface method signatures', () => {
        const lines = ['interface MyInterface {', '  methodSignature(param: number): boolean;', '}'];
        const capture = createMockCapture('name.definition.method', 1, 1);

        const result = strategy.parseCapture(capture, lines, processedChunks, mockContext);

        expect(result).toBe('methodSignature(param: number): boolean;');
      });
    });

    describe('Function name extraction', () => {
      test('should extract function name from standard declaration', () => {
        const lines = ['const myFunc = (a: number) => a * 2;'];

        // Using private method directly through type casting to test name extraction
        const functionName = (
          strategy as unknown as { getFunctionName(lines: string[], index: number): string }
        ).getFunctionName(lines, 0);

        expect(functionName).toBe('myFunc');
      });

      test('should extract function name from exported declaration', () => {
        const lines = ['export const exportedFunc = (a: number) => a * 2;'];

        const functionName = (
          strategy as unknown as { getFunctionName(lines: string[], index: number): string }
        ).getFunctionName(lines, 0);

        expect(functionName).toBe('exportedFunc');
      });

      test('should handle variable declarations without function assignment', () => {
        const lines = ['const justVariable = 42;'];

        const functionName = (
          strategy as unknown as { getFunctionName(lines: string[], index: number): string }
        ).getFunctionName(lines, 0);

        expect(functionName).toBe('justVariable');
      });
    });
  });
});
</file>

<file path="tests/shared/logger.test.ts">
import pc from 'picocolors';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { logger, repomixLogLevels } from '../../src/shared/logger.js';

vi.mock('picocolors', () => ({
  default: {
    red: vi.fn((str) => `RED:${str}`),
    yellow: vi.fn((str) => `YELLOW:${str}`),
    green: vi.fn((str) => `GREEN:${str}`),
    cyan: vi.fn((str) => `CYAN:${str}`),
    dim: vi.fn((str) => `DIM:${str}`),
    blue: vi.fn((str) => `BLUE:${str}`),
    gray: vi.fn((str) => `GRAY:${str}`),
  },
}));

describe('logger', () => {
  beforeEach(() => {
    vi.spyOn(console, 'error').mockImplementation(vi.fn());
    vi.spyOn(console, 'log').mockImplementation(vi.fn());
    logger.init();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('log levels', () => {
    it('should not log anything in SILENT mode', () => {
      logger.setLogLevel(repomixLogLevels.SILENT);

      logger.error('Error message');
      logger.warn('Warning message');
      logger.success('Success message');
      logger.info('Info message');
      logger.note('Note message');
      logger.debug('Debug message');
      logger.trace('Trace message');
      logger.log('Log message');

      expect(console.error).not.toHaveBeenCalled();
      expect(console.log).not.toHaveBeenCalled();
    });

    it('should only log errors in ERROR mode', () => {
      logger.setLogLevel(repomixLogLevels.ERROR);

      logger.error('Error message');
      logger.warn('Warning message');

      expect(console.error).toHaveBeenCalledWith('RED:Error message');
      expect(console.log).not.toHaveBeenCalled();
    });
  });

  it('should log error messages', () => {
    logger.error('Error message');
    expect(console.error).toHaveBeenCalledWith('RED:Error message');
  });

  it('should log warning messages', () => {
    logger.warn('Warning message');
    expect(console.log).toHaveBeenCalledWith('YELLOW:Warning message');
  });

  it('should log success messages', () => {
    logger.success('Success message');
    expect(console.log).toHaveBeenCalledWith('GREEN:Success message');
  });

  it('should log info messages', () => {
    logger.info('Info message');
    expect(console.log).toHaveBeenCalledWith('CYAN:Info message');
  });

  it('should log log messages', () => {
    logger.log('Note message');
    expect(console.log).toHaveBeenCalledWith('Note message');
  });

  it('should not log debug messages when verbose is false', () => {
    logger.debug('Debug message');
    expect(console.log).not.toHaveBeenCalled();
  });

  it('should log debug messages when verbose is true', () => {
    logger.setLogLevel(repomixLogLevels.DEBUG);
    logger.debug('Debug message');
    expect(console.log).toHaveBeenCalledWith('BLUE:Debug message');
  });

  it('should not log trace messages when verbose is false', () => {
    logger.trace('Trace message');
    expect(console.log).not.toHaveBeenCalled();
  });

  it('should log trace messages when verbose is true', () => {
    logger.setLogLevel(repomixLogLevels.DEBUG);
    logger.trace('Trace message');
    expect(console.log).toHaveBeenCalledWith(pc.gray('Trace message'));
  });

  it('should format object arguments correctly', () => {
    const obj = { key: 'value' };
    logger.info('Object:', obj);
    expect(console.log).toHaveBeenCalledWith(expect.stringContaining('CYAN:Object: '));
  });

  it('should handle multiple arguments', () => {
    logger.info('Multiple', 'arguments', 123);
    expect(console.log).toHaveBeenCalledWith('CYAN:Multiple arguments 123');
  });
});
</file>

<file path="tests/shared/processConcurrency.test.ts">
import os from 'node:os';
import { Piscina } from 'piscina';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getProcessConcurrency, getWorkerThreadCount, initPiscina } from '../../src/shared/processConcurrency.js';

vi.mock('node:os');
vi.mock('piscina');

describe('processConcurrency', () => {
  describe('getProcessConcurrency', () => {
    it('should use os.availableParallelism when available', () => {
      const mockAvailableParallelism = vi.fn().mockReturnValue(4);
      vi.mocked(os).availableParallelism = mockAvailableParallelism;

      const result = getProcessConcurrency();

      expect(result).toBe(4);
      expect(mockAvailableParallelism).toHaveBeenCalled();
    });
  });

  describe('getWorkerThreadCount', () => {
    beforeEach(() => {
      vi.mocked(os).availableParallelism = vi.fn().mockReturnValue(8);
    });

    it('should return minimum 1 thread', () => {
      const { minThreads, maxThreads } = getWorkerThreadCount(1);

      expect(minThreads).toBe(1);
      expect(maxThreads).toBe(1);
    });

    it('should limit max threads based on number of tasks', () => {
      const { minThreads, maxThreads } = getWorkerThreadCount(1000);

      expect(minThreads).toBe(1);
      expect(maxThreads).toBe(8); // Limited by CPU count
    });

    it('should scale max threads based on task count', () => {
      const { maxThreads: maxThreads1 } = getWorkerThreadCount(200);
      const { maxThreads: maxThreads2 } = getWorkerThreadCount(400);

      expect(maxThreads2).toBeGreaterThan(maxThreads1);
    });

    it('should handle large numbers of tasks', () => {
      const { minThreads, maxThreads } = getWorkerThreadCount(10000);

      expect(minThreads).toBe(1);
      expect(maxThreads).toBe(8); // Limited by CPU count
    });

    it('should handle zero tasks', () => {
      const { minThreads, maxThreads } = getWorkerThreadCount(0);

      expect(minThreads).toBe(1);
      expect(maxThreads).toBe(1);
    });
  });

  describe('initPiscina', () => {
    beforeEach(() => {
      vi.mocked(os).availableParallelism = vi.fn().mockReturnValue(4);
      vi.mocked(Piscina).mockImplementation(() => ({}) as unknown as Piscina);
    });

    it('should initialize Piscina with correct configuration', () => {
      const workerPath = '/path/to/worker.js';
      const piscina = initPiscina(500, workerPath);

      expect(Piscina).toHaveBeenCalledWith({
        filename: workerPath,
        minThreads: 1,
        maxThreads: 4,
        idleTimeout: 5000,
      });
      expect(piscina).toBeDefined();
    });
  });
});
</file>

<file path="website/client/.vitepress/theme/component.d.ts">
declare module '*.vue' {
  import type { DefineComponent } from 'vue';
  // biome-ignore lint lint/suspicious/noExplicitAny: Vue component
  const component: DefineComponent<{}, {}, any>;
  export default component;
}
</file>

<file path="website/client/.vitepress/theme/custom.css">
:root {
  --vp-c-brand-1: #f97316;
  --vp-c-brand-2: #ea580c;
  --vp-c-brand-3: #c2410c;
  --vp-c-brand-soft: rgba(249, 115, 22, 0.1);

  --vp-c-danger: #f44336;

  --vp-home-hero-name-color: transparent;
  --vp-home-hero-name-background: linear-gradient(120deg, #f97316 30%, #ffb25c);

  --vp-home-hero-image-background-image: linear-gradient(-45deg, rgba(249, 115, 22, 0.3) 30%, rgba(249, 115, 22, 0.1));
  --vp-home-hero-image-filter: blur(96px);
}

.hero-description__accent {
  color: var(--vp-c-brand-1);
}
.cli-section {
  padding-top: 64px;
  margin-bottom: 32px;
  max-width: 800px;
  margin-left: auto;
  margin-right: auto;
}

.cli-section h2 {
  font-size: 2rem;
  font-weight: 600;
  margin-bottom: 1.5rem;
  background: linear-gradient(120deg, #f97316 30%, #ffb25c);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
</file>

<file path="website/client/.vitepress/theme/index.ts">
import type { Theme } from 'vitepress';
import DefaultTheme from 'vitepress/theme';
import { h } from 'vue';
import Home from '../../components/Home.vue';
import './custom.css';

export default {
  extends: DefaultTheme,
  Layout: () => {
    return h(DefaultTheme.Layout, null, {
      'home-hero-after': () => h(Home),
    });
  },
} satisfies Theme;
</file>

<file path="website/client/.vitepress/theme/style.css">
/**
 * Customize default theme styling by overriding CSS variables:
 * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
 */

/**
 * Colors
 *
 * Each colors have exact same color scale system with 3 levels of solid
 * colors with different brightness, and 1 soft color.
 *
 * - `XXX-1`: The most solid color used mainly for colored text. It must
 *   satisfy the contrast ratio against when used on top of `XXX-soft`.
 *
 * - `XXX-2`: The color used mainly for hover state of the button.
 *
 * - `XXX-3`: The color for solid background, such as bg color of the button.
 *   It must satisfy the contrast ratio with pure white (#ffffff) text on
 *   top of it.
 *
 * - `XXX-soft`: The color used for subtle background such as custom container
 *   or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
 *   on top of it.
 *
 *   The soft color must be semi transparent alpha channel. This is crucial
 *   because it allows adding multiple "soft" colors on top of each other
 *   to create a accent, such as when having inline code block inside
 *   custom containers.
 *
 * - `default`: The color used purely for subtle indication without any
 *   special meanings attached to it such as bg color for menu hover state.
 *
 * - `brand`: Used for primary brand colors, such as link text, button with
 *   brand theme, etc.
 *
 * - `tip`: Used to indicate useful information. The default theme uses the
 *   brand color for this by default.
 *
 * - `warning`: Used to indicate warning to the users. Used in custom
 *   container, badges, etc.
 *
 * - `danger`: Used to show error, or dangerous message to the users. Used
 *   in custom container, badges, etc.
 * -------------------------------------------------------------------------- */

:root {
  --vp-c-default-1: var(--vp-c-gray-1);
  --vp-c-default-2: var(--vp-c-gray-2);
  --vp-c-default-3: var(--vp-c-gray-3);
  --vp-c-default-soft: var(--vp-c-gray-soft);

  --vp-c-brand-1: var(--vp-c-indigo-1);
  --vp-c-brand-2: var(--vp-c-indigo-2);
  --vp-c-brand-3: var(--vp-c-indigo-3);
  --vp-c-brand-soft: var(--vp-c-indigo-soft);

  --vp-c-tip-1: var(--vp-c-brand-1);
  --vp-c-tip-2: var(--vp-c-brand-2);
  --vp-c-tip-3: var(--vp-c-brand-3);
  --vp-c-tip-soft: var(--vp-c-brand-soft);

  --vp-c-warning-1: var(--vp-c-yellow-1);
  --vp-c-warning-2: var(--vp-c-yellow-2);
  --vp-c-warning-3: var(--vp-c-yellow-3);
  --vp-c-warning-soft: var(--vp-c-yellow-soft);

  --vp-c-danger-1: var(--vp-c-red-1);
  --vp-c-danger-2: var(--vp-c-red-2);
  --vp-c-danger-3: var(--vp-c-red-3);
  --vp-c-danger-soft: var(--vp-c-red-soft);
}

/**
 * Component: Button
 * -------------------------------------------------------------------------- */

:root {
  --vp-button-brand-border: transparent;
  --vp-button-brand-text: var(--vp-c-white);
  --vp-button-brand-bg: var(--vp-c-brand-3);
  --vp-button-brand-hover-border: transparent;
  --vp-button-brand-hover-text: var(--vp-c-white);
  --vp-button-brand-hover-bg: var(--vp-c-brand-2);
  --vp-button-brand-active-border: transparent;
  --vp-button-brand-active-text: var(--vp-c-white);
  --vp-button-brand-active-bg: var(--vp-c-brand-1);
}

/**
 * Component: Home
 * -------------------------------------------------------------------------- */

:root {
  --vp-home-hero-name-color: transparent;
  --vp-home-hero-name-background: -webkit-linear-gradient(120deg, #bd34fe 30%, #41d1ff);

  --vp-home-hero-image-background-image: linear-gradient(-45deg, #bd34fe 50%, #47caff 50%);
  --vp-home-hero-image-filter: blur(44px);
}

@media (min-width: 640px) {
  :root {
    --vp-home-hero-image-filter: blur(56px);
  }
}

@media (min-width: 960px) {
  :root {
    --vp-home-hero-image-filter: blur(68px);
  }
}

/**
 * Component: Custom Block
 * -------------------------------------------------------------------------- */

:root {
  --vp-custom-block-tip-border: transparent;
  --vp-custom-block-tip-text: var(--vp-c-text-1);
  --vp-custom-block-tip-bg: var(--vp-c-brand-soft);
  --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
}

/**
 * Component: Algolia
 * -------------------------------------------------------------------------- */

.DocSearch {
  --docsearch-primary-color: var(--vp-c-brand-1) !important;
}
</file>

<file path="website/client/components/api/client.ts">
export interface PackOptions {
  removeComments: boolean;
  removeEmptyLines: boolean;
  showLineNumbers: boolean;
  fileSummary?: boolean;
  directoryStructure?: boolean;
  includePatterns?: string;
  ignorePatterns?: string;
  outputParsable?: boolean;
}

export interface PackRequest {
  url: string;
  format: 'xml' | 'markdown' | 'plain';
  options: PackOptions;
  signal?: AbortSignal;
  file?: File;
}

export interface PackResult {
  content: string;
  format: string;
  metadata: {
    repository: string;
    timestamp: string;
    summary: {
      totalFiles: number;
      totalCharacters: number;
      totalTokens: number;
    };
    topFiles: {
      path: string;
      charCount: number;
    }[];
  };
}

export interface ErrorResponse {
  error: string;
}

export class ApiError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'ApiError';
  }
}

const API_BASE_URL = import.meta.env.PROD ? 'https://api.repomix.com' : 'http://localhost:8080';

export async function packRepository(request: PackRequest): Promise<PackResult> {
  const formData = new FormData();

  if (request.file) {
    formData.append('file', request.file);
  } else {
    formData.append('url', request.url);
  }
  formData.append('format', request.format);
  formData.append('options', JSON.stringify(request.options));

  const response = await fetch(`${API_BASE_URL}/api/pack`, {
    method: 'POST',
    body: formData,
    signal: request.signal,
  });

  const data = await response.json();

  if (!response.ok) {
    throw new ApiError((data as ErrorResponse).error);
  }

  return data as PackResult;
}
</file>

<file path="website/client/components/Home/Hero.vue">
<script setup>
import { useData } from 'vitepress';

const { site } = useData();
</script>

<template>
  <section class="hero">
    <h1 class="hero-title">
      {{ site.title }}
    </h1>
    <p class="hero-description">
      Pack your codebase into <span class="hero-description__accent">AI-friendly</span> formats
    </p>
  </section>
</template>

<style scoped>
.hero {
  text-align: center;
  padding: 20px 20px;
  max-width: 960px;
  margin: 0 auto;
}

.hero-title {
  font-size: 64px;
  font-weight: 700;
  line-height: 1.2;
  margin-bottom: 24px;
  background: -webkit-linear-gradient(0deg, #f97316 30%, #ffb25c);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.hero-description {
  font-size: 32px;
  font-weight: 600;
  line-height: 1.4;
  margin-bottom: 16px;
  color: var(--vp-c-text-1);
}

.hero-description__accent {
  color: var(--vp-c-brand-1);
}

.hero-tagline {
  font-size: 18px;
  color: var(--vp-c-text-2);
  margin: 0;
}

@media (max-width: 768px) {
  .hero-title {
    font-size: 48px;
  }

  .hero-description {
    font-size: 24px;
  }

  .hero-tagline {
    font-size: 16px;
  }
}
</style>
</file>

<file path="website/client/components/Home/PackButton.vue">
<template>
  <button
    class="pack-button"
    :disabled="!isValid || loading"
    aria-label="Pack repository"
    type="submit"
  >
    {{ loading ? 'Processing...' : 'Pack' }}
    <svg v-if="!loading"
         class="pack-button-icon"
         width="20"
         height="20"
         viewBox="96.259 93.171 300 300"
    >
      <g transform="matrix(1.160932, 0, 0, 1.160932, 97.635941, 94.725143)">
        <path
          fill="currentColor"
          d="M 128.03 -1.486 L 21.879 65.349 L 21.848 190.25 L 127.979 256.927 L 234.2 190.27 L 234.197 65.463 L 128.03 -1.486 Z M 208.832 70.323 L 127.984 121.129 L 47.173 70.323 L 128.144 19.57 L 208.832 70.323 Z M 39.669 86.367 L 119.188 136.415 L 119.255 230.529 L 39.637 180.386 L 39.669 86.367 Z M 136.896 230.506 L 136.887 136.575 L 216.469 86.192 L 216.417 180.46 L 136.896 230.506 Z M 136.622 230.849"
        />
      </g>
    </svg>
  </button>
</template>

<script setup>
defineProps({
  loading: Boolean,
  isValid: Boolean,
});
</script>

<style scoped>
.pack-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 24px;
  height: 50px;
  width: 100%;
  font-size: 16px;
  font-weight: 500;
  background: var(--vp-c-brand-1);
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.2s ease;

}

.pack-button:hover:not(:disabled) {
  background: var(--vp-c-brand-2);
}

.pack-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.pack-button-icon {
  font-size: 20px;
  line-height: 1;
}

@media (max-width: 768px) {
  .pack-button {
    width: 100%;
  }
}
</style>
</file>

<file path="website/client/components/Home/TryItPackOptions.vue">
<script setup lang="ts">
import { HelpCircle } from 'lucide-vue-next';
import { AnalyticsAction } from '../utils/analytics';
import { handleOptionChange } from '../utils/requestHandlers';

const props = defineProps<{
  format: 'xml' | 'markdown' | 'plain';
  includePatterns: string;
  ignorePatterns: string;
  fileSummary: boolean;
  directoryStructure: boolean;
  removeComments: boolean;
  removeEmptyLines: boolean;
  showLineNumbers: boolean;
  outputParsable: boolean;
}>();

const emit = defineEmits<{
  'update:format': [value: 'xml' | 'markdown' | 'plain'];
  'update:includePatterns': [value: string];
  'update:ignorePatterns': [value: string];
  'update:fileSummary': [value: boolean];
  'update:directoryStructure': [value: boolean];
  'update:removeComments': [value: boolean];
  'update:removeEmptyLines': [value: boolean];
  'update:showLineNumbers': [value: boolean];
  'update:outputParsable': [value: boolean];
}>();

function handleFormatChange(newFormat: 'xml' | 'markdown' | 'plain') {
  emit('update:format', newFormat);
  handleOptionChange(newFormat, AnalyticsAction.FORMAT_CHANGE);
}

function handleIncludePatternsUpdate(patterns: string) {
  emit('update:includePatterns', patterns);
  handleOptionChange(patterns, AnalyticsAction.UPDATE_INCLUDE_PATTERNS);
}

function handleIgnorePatternsUpdate(patterns: string) {
  emit('update:ignorePatterns', patterns);
  handleOptionChange(patterns, AnalyticsAction.UPDATE_IGNORE_PATTERNS);
}

function handleFileSummaryToggle(enabled: boolean) {
  emit('update:fileSummary', enabled);
  handleOptionChange(enabled, AnalyticsAction.TOGGLE_FILE_SUMMARY);
}

function handleDirectoryStructureToggle(enabled: boolean) {
  emit('update:directoryStructure', enabled);
  handleOptionChange(enabled, AnalyticsAction.TOGGLE_DIRECTORY_STRUCTURE);
}

function handleRemoveCommentsToggle(enabled: boolean) {
  emit('update:removeComments', enabled);
  handleOptionChange(enabled, AnalyticsAction.TOGGLE_REMOVE_COMMENTS);
}

function handleRemoveEmptyLinesToggle(enabled: boolean) {
  emit('update:removeEmptyLines', enabled);
  handleOptionChange(enabled, AnalyticsAction.TOGGLE_REMOVE_EMPTY_LINES);
}

function handleShowLineNumbersToggle(enabled: boolean) {
  emit('update:showLineNumbers', enabled);
  handleOptionChange(enabled, AnalyticsAction.TOGGLE_LINE_NUMBERS);
}

function handleOutputParsableToggle(enabled: boolean) {
  emit('update:outputParsable', enabled);
  handleOptionChange(enabled, AnalyticsAction.TOGGLE_OUTPUT_PARSABLE);
}
</script>

<template>
  <div class="options-container">
    <div class="left-column">
      <div class="option-section">
        <p class="option-label">Output Format</p>
        <div class="format-buttons">
          <button
            class="format-button"
            :class="{ active: format === 'xml' }"
            @click="handleFormatChange('xml')"
            type="button"
          >
            XML
          </button>
          <button
            class="format-button"
            :class="{ active: format === 'markdown' }"
            @click="handleFormatChange('markdown')"
            type="button"
          >
            Markdown
          </button>
          <button
            class="format-button"
            :class="{ active: format === 'plain' }"
            @click="handleFormatChange('plain')"
            type="button"
          >
            Plain
          </button>
        </div>
      </div>

      <div class="option-section">
        <p class="option-label">Include Patterns (using <a href="https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax" target="_blank" rel="noopener noreferrer">glob patterns</a>)</p>
        <input
          :value="includePatterns"
          @input="event => handleIncludePatternsUpdate((event.target as HTMLInputElement).value)"
          type="text"
          class="repository-input"
          placeholder="Comma-separated patterns to include. e.g., src/**/*.ts"
          aria-label="Include patterns"
        />
      </div>

      <div class="option-section">
        <p class="option-label">Ignore Patterns</p>
        <input
          :value="ignorePatterns"
          @input="event => handleIgnorePatternsUpdate((event.target as HTMLInputElement).value)"
          type="text"
          class="repository-input"
          placeholder="Comma-separated patterns to ignore. e.g., **/*.test.ts,README.md"
          aria-label="Ignore patterns"
        />
      </div>
    </div>

    <div class="right-column">
      <div class="option-section">
        <p class="option-label">Output Options</p>
        <div class="checkbox-group">
          <label class="checkbox-label">
            <input
              :checked="fileSummary"
              @change="event => handleFileSummaryToggle((event.target as HTMLInputElement).checked)"
              type="checkbox"
              class="checkbox-input"
            />
            <span>Include File Summary</span>
          </label>
          <label class="checkbox-label">
            <input
              :checked="directoryStructure"
              @change="event => handleDirectoryStructureToggle((event.target as HTMLInputElement).checked)"
              type="checkbox"
              class="checkbox-input"
            />
            <span>Include Directory Structure</span>
          </label>
          <label class="checkbox-label">
            <input
              :checked="removeComments"
              @change="event => handleRemoveCommentsToggle((event.target as HTMLInputElement).checked)"
              type="checkbox"
              class="checkbox-input"
            />
            <span>Remove Comments</span>
          </label>
          <label class="checkbox-label">
            <input
              :checked="removeEmptyLines"
              @change="event => handleRemoveEmptyLinesToggle((event.target as HTMLInputElement).checked)"
              type="checkbox"
              class="checkbox-input"
            />
            <span>Remove Empty Lines</span>
          </label>
          <label class="checkbox-label">
            <input
              :checked="showLineNumbers"
              @change="event => handleShowLineNumbersToggle((event.target as HTMLInputElement).checked)"
              type="checkbox"
              class="checkbox-input"
            />
            <span>Show Line Numbers</span>
          </label>
          <label class="checkbox-label">
            <input
              :checked="outputParsable"
              @change="event => handleOutputParsableToggle((event.target as HTMLInputElement).checked)"
              type="checkbox"
              class="checkbox-input"
            />
            <div class="parsable-option">
              <span>Output Parsable Format</span>
              <div class="tooltip-container">
                <HelpCircle
                  :size="16"
                  class="help-icon"
                  aria-label="More information about parsable format"
                />
                <div class="tooltip-content">
                  Whether to escape the output based on the chosen style schema. Note that this can increase token count.
                  <div class="tooltip-arrow"></div>
                </div>
              </div>
            </div>
          </label>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.options-container {
  display: grid;
  grid-template-columns: 60% 40%;
  gap: 24px;
  margin-bottom: 24px;
}

.left-column,
.right-column {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.option-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.option-section input {
  padding: 8px 12px;
  font-size: 16px;
  border: 1px solid var(--vp-c-border);
  border-radius: 8px;
  background: var(--vp-c-bg);
  color: var(--vp-c-text-1);
  transition: border-color 0.2s;
}

.option-label {
  font-size: 14px;
  font-weight: 500;
  margin: 0;
  color: var(--vp-c-text-2);
}

.option-label a {
  color: var(--vp-c-brand-1);
}

.option-label a:hover {
  text-decoration: underline;
}

.format-buttons {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.format-button {
  padding: 8px 16px;
  font-size: 14px;
  border: 1px solid var(--vp-c-border);
  border-radius: 6px;
  background: var(--vp-c-bg);
  color: var(--vp-c-text-1);
  cursor: pointer;
  transition: all 0.2s ease;
}

.format-button:hover {
  border-color: var(--vp-c-brand-1);
}

.format-button.active {
  background: var(--vp-c-brand-1);
  border-color: var(--vp-c-brand-1);
  color: white;
}

.checkbox-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 14px;
  color: var(--vp-c-text-1);
}

.checkbox-input {
  width: 16px;
  height: 16px;
  accent-color: var(--vp-c-brand-1);
}

.parsable-option {
  display: flex;
  align-items: center;
  gap: 4px;
}

.tooltip-container {
  position: relative;
  display: inline-block;
}

.help-icon {
  color: #666;
  cursor: help;
  transition: color 0.2s;
}

.help-icon:hover {
  color: #333;
}

.tooltip-content {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 8px;
  padding: 8px 12px;
  background: #333;
  color: white;
  font-size: 0.875rem;
  width: 250px;
  border-radius: 4px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s, visibility 0.2s;
  z-index: 10;
  text-align: left;
}

.tooltip-container:hover .tooltip-content {
  opacity: 1;
  visibility: visible;
}

.tooltip-arrow {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border-width: 8px;
  border-style: solid;
  border-color: #333 transparent transparent transparent;
}

@media (max-width: 640px) {
  .options-container {
    grid-template-columns: 1fr;
    gap: 24px;
  }

  .left-column,
  .right-column {
    gap: 24px;
  }
}
</style>
</file>

<file path="website/client/components/Home/TryItResult.vue">
<script setup lang="ts">
import type { PackResult } from '../api/client';
import TryItResultContent from './TryItResultContent.vue';
import TryItResultErrorContent from './TryItResultErrorContent.vue';

defineProps<{
  result: PackResult | null;
  loading: boolean;
  error: string | null;
  repositoryUrl?: string;
}>();
</script>

<template>
  <div class="result-viewer">
    <div v-if="loading" class="loading">
      <div class="spinner"></div>
      <p>Processing repository...</p>
    </div>

    <TryItResultErrorContent
      v-else-if="error"
      :message="error"
      :repository-url="repositoryUrl"
    />

    <TryItResultContent
      v-else-if="result"
      :result="result"
    />
  </div>
</template>

<style scoped>
.result-viewer {
  margin-top: 24px;
  border: 1px solid var(--vp-c-border);
  border-radius: 8px;
  overflow: hidden;
}

.loading {
  padding: 48px;
  text-align: center;
}

.spinner {
  width: 40px;
  height: 40px;
  margin: 0 auto 16px;
  border: 3px solid var(--vp-c-brand-1);
  border-radius: 50%;
  border-top-color: transparent;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}
</style>
</file>

<file path="website/client/components/Home/TryItResultErrorContent.vue">
<script setup lang="ts">
import { AlertTriangle, Copy } from 'lucide-vue-next';
import { computed, ref } from 'vue';

const props = defineProps<{
  message: string;
  repositoryUrl?: string;
}>();

const copied = ref(false);
const commandWithRepo = computed(() => {
  const baseCommand = 'npx repomix --remote';
  return props.repositoryUrl ? `${baseCommand} ${props.repositoryUrl}` : `${baseCommand} <repository-url>`;
});

const copyCommand = async (event: Event) => {
  event.preventDefault();
  event.stopPropagation();
  await navigator.clipboard.writeText(commandWithRepo.value);
  copied.value = true;
  setTimeout(() => {
    copied.value = false;
  }, 2000);
};
</script>

<template>
  <div class="error">
    <div class="error-content">
      <AlertTriangle :size="32" class="error-icon" />
      <p class="error-message">{{ message }}</p>
      <div class="suggestion">
        <p>Try using the command line tool instead:</p>
        <div class="command-block">
          <code>{{ commandWithRepo }}</code>
          <button class="copy-button" @click="copyCommand" :class="{ copied }">
            <Copy :size="14" />
            {{ copied ? 'Copied!' : 'Copy' }}
          </button>
        </div>
        <p class="guide-link">
          See <a href="#power-user-guide">Power User Guide</a> for more details.
        </p>
      </div>
    </div>
  </div>
</template>

<style scoped>
.error {
  padding: 32px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.error-content {
  max-width: 700px;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.error-icon {
  color: var(--vp-c-danger-1);
  margin-bottom: 16px;
}

.error-message {
  color: var(--vp-c-danger-1);
  font-size: 1.1em;
  margin: 0 0 24px;
}

.suggestion {
  background: var(--vp-c-bg-soft);
  padding: 16px;
  border-radius: 8px;
  border: 1px solid var(--vp-c-border);
  width: 100%;
}

.suggestion p {
  margin: 0 0 12px;
  color: var(--vp-c-text-2);
}

.command-block {
  background: var(--vp-c-bg-alt);
  border: 1px solid var(--vp-c-border);
  border-radius: 6px;
  padding: 12px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  font-family: var(--vp-font-family-mono);
}

code {
  color: var(--vp-c-text-1);
}

.copy-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border: 1px solid var(--vp-c-border);
  border-radius: 4px;
  background: var(--vp-c-bg-soft);
  color: var(--vp-c-text-2);
  font-size: 12px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.copy-button:hover {
  border-color: var(--vp-c-brand-1);
  color: var(--vp-c-brand-1);
}

.copy-button.copied {
  background: var(--vp-c-brand-1);
  color: white;
  border-color: var(--vp-c-brand-1);
}

.guide-link a {
  color: var(--vp-c-brand-1);
  text-decoration: none;
  font-weight: 500;
}

.guide-link a:hover {
  text-decoration: underline;
}
</style>
</file>

<file path="website/client/components/Home/TryItUrlInput.vue">
<script setup lang="ts">
import { AlertTriangle } from 'lucide-vue-next';
import { computed } from 'vue';
import { isValidRemoteValue } from '../utils/validation';
import PackButton from './PackButton.vue';

const props = defineProps<{
  url: string;
  loading: boolean;
  showButton?: boolean;
}>();

const emit = defineEmits<{
  'update:url': [value: string];
  submit: [];
  keydown: [event: KeyboardEvent];
}>();

const isValidUrl = computed(() => {
  if (!props.url) return false;
  return isValidRemoteValue(props.url.trim());
});

function handleUrlInput(event: Event) {
  const input = event.target as HTMLInputElement;
  emit('update:url', input.value);
}

function handleKeydown(event: KeyboardEvent) {
  emit('keydown', event);
}
</script>

<template>
  <div class="input-group">
    <div class="url-input-container">
      <input
        :value="url"
        @input="handleUrlInput"
        @keydown="handleKeydown"
        type="text"
        placeholder="GitHub repository URL or user/repo (e.g., yamadashy/repomix)"
        class="repository-input"
        :class="{ 'invalid': url && !isValidUrl }"
        aria-label="GitHub repository URL"
      />
    </div>

    <div v-if="url && !isValidUrl" class="url-warning">
      <AlertTriangle class="warning-icon" size="16" />
      <span>Please enter a valid GitHub repository URL (e.g., yamadashy/repomix)</span>
    </div>
    <div v-if="showButton" class="pack-button-container">
      <PackButton :isValid="isValidUrl" :loading="loading"/>
    </div>
  </div>
</template>

<style scoped>
.input-group {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.url-input-container {
  flex: 1;
  position: relative;
  height: 100%;
}

.repository-input {
  width: 100%;
  height: 50px;
  padding: 12px 16px;
  font-size: 16px;
  border: 1px solid var(--vp-c-border);
  border-radius: 8px;
  background: var(--vp-c-bg);
  color: var(--vp-c-text-1);
  transition: border-color 0.2s;
}

.repository-input:focus {
  outline: none;
  border-color: var(--vp-c-brand-1);
}

.repository-input.invalid {
  border-color: var(--vp-c-danger-1);
}

.url-warning {
  margin-top: 8px;
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--vp-c-warning-1);
  font-size: 14px;
}

.warning-icon {
  flex-shrink: 0;
  color: var(--vp-c-warning-1);
}

.pack-button-container {
  margin-top: 16px;
  display: flex;
  justify-content: center;
  width: 100%;
}
</style>
</file>

<file path="website/client/components/utils/analytics.ts">
// Analytics event categories
export const AnalyticsCategory = {
  REPOSITORY: 'repository',
  FORMAT: 'format',
  OPTIONS: 'options',
  OUTPUT: 'output',
} as const;

// Analytics event actions
export const AnalyticsAction = {
  // Repository events
  PACK_START: 'pack_start',
  PACK_SUCCESS: 'pack_success',
  PACK_SUCCESS_FILES: 'pack_success_files',
  PACK_SUCCESS_CHARS: 'pack_success_chars',
  PACK_ERROR: 'pack_error',

  // Format events
  FORMAT_CHANGE: 'format_change',

  // Options events
  TOGGLE_REMOVE_COMMENTS: 'toggle_remove_comments',
  TOGGLE_REMOVE_EMPTY_LINES: 'toggle_remove_empty_lines',
  TOGGLE_LINE_NUMBERS: 'toggle_line_numbers',
  TOGGLE_FILE_SUMMARY: 'toggle_file_summary',
  TOGGLE_DIRECTORY_STRUCTURE: 'toggle_directory_structure',
  TOGGLE_OUTPUT_PARSABLE: 'toggle_output_parsable', // Added this action
  UPDATE_INCLUDE_PATTERNS: 'update_include_patterns',
  UPDATE_IGNORE_PATTERNS: 'update_ignore_patterns',

  // Output events
  COPY_OUTPUT: 'copy_output',
  DOWNLOAD_OUTPUT: 'download_output',
} as const;

export type AnalyticsCategoryType = (typeof AnalyticsCategory)[keyof typeof AnalyticsCategory];
export type AnalyticsActionType = (typeof AnalyticsAction)[keyof typeof AnalyticsAction];

// Google Analytics event tracking interface
interface GAEventParams {
  category: AnalyticsCategoryType;
  action: AnalyticsActionType;
  label?: string;
  value?: number;
}

// Track an event using gtag
export function trackEvent({ category, action, label, value }: GAEventParams): void {
  if (typeof window === 'undefined' || !window.gtag) {
    return;
  }

  window.gtag('event', action, {
    event_category: category,
    event_label: label,
    value: value,
  });
}

// Analytics utility functions for specific events
export const analyticsUtils = {
  // Repository events
  trackPackStart(repoUrl: string): void {
    trackEvent({
      category: AnalyticsCategory.REPOSITORY,
      action: AnalyticsAction.PACK_START,
      label: repoUrl,
    });
  },

  trackPackSuccess(repoUrl: string, totalFiles: number, totalChars: number): void {
    trackEvent({
      category: AnalyticsCategory.REPOSITORY,
      action: AnalyticsAction.PACK_SUCCESS_FILES,
      label: `${repoUrl}_files`,
      value: totalFiles,
    });
    trackEvent({
      category: AnalyticsCategory.REPOSITORY,
      action: AnalyticsAction.PACK_SUCCESS_CHARS,
      label: `${repoUrl}_chars`,
      value: totalChars,
    });
  },

  trackPackError(repoUrl: string, error: string): void {
    trackEvent({
      category: AnalyticsCategory.REPOSITORY,
      action: AnalyticsAction.PACK_ERROR,
      label: `${repoUrl} - ${error}`,
    });
  },

  // Options events
  trackOptionToggle(action: AnalyticsActionType, enabled: boolean): void {
    trackEvent({
      category: AnalyticsCategory.OPTIONS,
      action: action,
      label: enabled ? 'enabled' : 'disabled',
    });
  },

  // Output events
  trackCopyOutput(format: string): void {
    trackEvent({
      category: AnalyticsCategory.OUTPUT,
      action: AnalyticsAction.COPY_OUTPUT,
      label: format,
    });
  },

  trackDownloadOutput(format: string): void {
    trackEvent({
      category: AnalyticsCategory.OUTPUT,
      action: AnalyticsAction.DOWNLOAD_OUTPUT,
      label: format,
    });
  },
};

// Type definitions for window.gtag
declare global {
  interface Window {
    gtag: (
      command: 'event',
      action: string,
      params: {
        event_category: string;
        event_label?: string;
        value?: number;
      },
    ) => void;
  }
}
</file>

<file path="website/client/components/utils/requestHandlers.ts">
import type { PackOptions, PackRequest, PackResult } from '../api/client';
import { packRepository } from '../api/client';
import { type AnalyticsActionType, analyticsUtils } from './analytics';

interface RequestHandlerOptions {
  onSuccess?: (result: PackResult) => void;
  onError?: (error: string) => void;
  signal?: AbortSignal;
  file?: File;
}

/**
 * Handle repository packing request
 */
export async function handlePackRequest(
  url: string,
  format: 'xml' | 'markdown' | 'plain',
  options: PackOptions,
  handlerOptions: RequestHandlerOptions = {},
): Promise<void> {
  const { onSuccess, onError, signal, file } = handlerOptions;
  const processedUrl = url.trim();

  // Track pack start
  analyticsUtils.trackPackStart(processedUrl);

  try {
    const request: PackRequest = {
      url: processedUrl,
      format,
      options,
      signal,
      file,
    };

    const response = await packRepository(request);

    // Track successful pack
    if (response.metadata.summary) {
      analyticsUtils.trackPackSuccess(
        processedUrl,
        response.metadata.summary.totalFiles,
        response.metadata.summary.totalCharacters,
      );
    }

    onSuccess?.(response);
  } catch (err) {
    const errorMessage = err instanceof Error ? err.message : 'An unexpected error occurred';

    if (errorMessage === 'AbortError') {
      onError?.('Request was cancelled');
      return;
    }

    analyticsUtils.trackPackError(processedUrl, errorMessage);
    console.error('Error processing repository:', err);
    onError?.(errorMessage);
  }
}

/**
 * Handle form input changes with analytics tracking
 */
export function handleOptionChange(value: boolean | string, analyticsAction: AnalyticsActionType): void {
  if (typeof value === 'boolean') {
    analyticsUtils.trackOptionToggle(analyticsAction, value);
  } else {
    analyticsUtils.trackOptionToggle(analyticsAction, Boolean(value));
  }
}
</file>

<file path="website/client/components/utils/resultViewer.ts">
import type { Ace } from 'ace-builds';
import type { PackResult } from '../api/client';
import { analyticsUtils } from './analytics';

/**
 * Format timestamp to locale string
 */
export function formatTimestamp(timestamp: string): string {
  return new Date(timestamp).toLocaleString();
}

/**
 * Handle clipboard copy with analytics tracking
 */
export async function copyToClipboard(content: string, format: string): Promise<boolean> {
  try {
    await navigator.clipboard.writeText(content);
    analyticsUtils.trackCopyOutput(format);
    return true;
  } catch (err) {
    console.error('Failed to copy:', err);
    return false;
  }
}

/**
 * Convert repository name to format suitable for filename
 */
function formatRepositoryName(repository: string): string {
  // Extract owner and repo from GitHub URL format or use as is
  const match = repository.match(/(?:https:\/\/github\.com\/)?([^/]+)\/([^/]+)(?:\.git)?$/);
  if (match) {
    const [, owner, repo] = match;
    return `${owner}-${repo}`;
  }
  // For non-GitHub repositories or local files, clean up the name
  return repository.replace(/[\/\\]/g, '-').replace(/\.git$/, '');
}

/**
 * Handle file download with analytics tracking
 */
export function downloadResult(content: string, format: string, result: PackResult): void {
  const blob = new Blob([content], { type: 'text/plain' });
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement('a');
  const extension = format === 'markdown' ? 'md' : format === 'xml' ? 'xml' : 'txt';

  const repoName = formatRepositoryName(result.metadata.repository);
  a.href = url;
  a.download = `repomix-output-${repoName}.${extension}`;
  document.body.appendChild(a);
  a.click();

  analyticsUtils.trackDownloadOutput(format);
  window.URL.revokeObjectURL(url);
  document.body.removeChild(a);
}

/**
 * Get Ace editor options
 */
export function getEditorOptions(): Partial<Ace.EditorOptions> {
  return {
    readOnly: true,
    wrap: false,
    showPrintMargin: false,
    fontSize: '13px',
    useWorker: false,
    highlightActiveLine: false,
  };
}
</file>

<file path="website/client/components/utils/validation.ts">
/**
 * Validates a GitHub repository URL or shorthand format
 * TODO: Share this validation logic with repomix core (src/cli/actions/remoteAction.ts)
 */
export function isValidRemoteValue(remoteValue: string): boolean {
  // Check the short form of the GitHub URL. e.g. yamadashy/repomix
  const namePattern = '[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?';
  const shortFormRegex = new RegExp(`^${namePattern}/${namePattern}$`);
  if (shortFormRegex.test(remoteValue)) {
    return true;
  }

  // Check the direct form of the GitHub URL. e.g.  https://github.com/yamadashy/repomix or https://gist.github.com/yamadashy/1234567890abcdef
  try {
    new URL(remoteValue);
    return true;
  } catch (error) {
    return false;
  }
}
</file>

<file path="website/client/components/Home.vue">
<script setup>
import Hero from './Home/Hero.vue';
import TryIt from './Home/TryIt.vue';
</script>

<template>
  <div class="home">
    <Hero />
    <TryIt />
  </div>
</template>

<style scoped>
.home {
  padding-bottom: 60px;
}
</style>
</file>

<file path="website/client/src/de/guide/development/index.md">
# Zu Repomix beitragen

## Schnellstart

```bash
git clone https://github.com/yamadashy/repomix.git
cd repomix
npm install
```

## Entwicklungsbefehle

```bash
# CLI ausführen
npm run repomix

# Tests ausführen
npm run test
npm run test-coverage

# Code linting
npm run lint
```

## Code-Stil

- [Biome](https://biomejs.dev/) für Linting und Formatierung verwenden
- Dependency Injection für Testbarkeit
- Dateien unter 250 Zeilen halten
- Tests für neue Funktionen hinzufügen

## Pull-Request-Richtlinien

1. Alle Tests ausführen
2. Linting-Prüfungen bestehen
3. Dokumentation aktualisieren
4. Bestehenden Code-Stil befolgen

## Hilfe benötigt?

- [Issue erstellen](https://github.com/yamadashy/repomix/issues)
- [Discord beitreten](https://discord.gg/wNYzTwZFku)
</file>

<file path="website/client/src/de/guide/development/setup.md">
# Entwicklungsumgebung einrichten

## Voraussetzungen

- Node.js ≥ 18.0.0
- Git
- npm
- pnpm (empfohlen)

## Lokale Entwicklung

### Repository klonen

```bash
git clone https://github.com/yamadashy/repomix.git
cd repomix
```

### Abhängigkeiten installieren

Mit pnpm (empfohlen):
```bash
pnpm install
```

Mit npm:
```bash
npm install
```

### Entwicklungsserver starten

```bash
# CLI ausführen
pnpm run repomix

# Entwicklungsserver für die Website starten
pnpm run website:dev
```

## Docker-Entwicklung

```bash
# Image erstellen
docker build -t repomix .

# Container ausführen
docker run -v ./:/app -it --rm repomix
```

## Projektstruktur

```text
.
├── src/                # Quellcode
│   ├── cli/           # CLI-Implementierung
│   ├── config/        # Konfigurationshandling
│   ├── core/          # Kernfunktionalität
│   └── shared/        # Gemeinsame Hilfsprogramme
├── tests/             # Testdateien
├── website/           # Dokumentationswebsite
└── package.json       # Projektabhängigkeiten
```

## Tests

```bash
# Alle Tests ausführen
pnpm run test

# Tests mit Abdeckungsbericht
pnpm run test:coverage

# Bestimmte Tests ausführen
pnpm run test -- tests/cli
```

## Code-Qualität

```bash
# Linting ausführen
pnpm run lint-biome
pnpm run lint-ts
pnpm run lint-secretlint

# Linting mit automatischer Korrektur
pnpm run lint:fix

# Typenprüfung
pnpm run typecheck
```

## Dokumentation

Die Dokumentation befindet sich im Verzeichnis `website/`. Um die Dokumentationswebsite lokal zu entwickeln:

```bash
# Entwicklungsserver starten
pnpm run website:dev

# Produktions-Build erstellen
pnpm run website:build
```

## Release-Prozess

1. Version aktualisieren
```bash
pnpm version patch  # oder minor/major
```

2. Tests und Build ausführen
```bash
pnpm run test:coverage
pnpm run build
```

3. Veröffentlichen
```bash
pnpm publish
```

## Fehlerbehebung

### Häufige Probleme

1. **Node.js Version**
   - Stellen Sie sicher, dass Sie Node.js ≥ 18.0.0 verwenden
   - Überprüfen Sie mit `node --version`

2. **Abhängigkeitsprobleme**
   - Löschen Sie `node_modules` und den Lock-File
   - Führen Sie `pnpm install` erneut aus

3. **Build-Fehler**
   - Führen Sie `pnpm run clean` aus
   - Bauen Sie das Projekt neu mit `pnpm run build`

### Support

Bei Problemen:
- Öffnen Sie ein [GitHub Issue](https://github.com/yamadashy/repomix/issues)
- Treten Sie unserem [Discord-Server](https://discord.gg/wNYzTwZFku) bei
</file>

<file path="website/client/src/de/guide/tips/best-practices.md">
# Best Practices für KI-unterstützte Entwicklung: Aus meiner Erfahrung

Obwohl ich noch kein großes Projekt mit KI erfolgreich abgeschlossen habe, möchte ich meine bisherigen Erfahrungen in der Entwicklung mit KI teilen.

## Grundlegender Entwicklungsansatz

Bei der Arbeit mit KI kann der Versuch, alle Funktionen auf einmal zu implementieren, zu unerwarteten Problemen und Projektstillstand führen. Deshalb ist es effektiver, mit der Kernfunktionalität zu beginnen und jede Funktion einzeln aufzubauen, wobei eine solide Implementierung sichergestellt wird, bevor man weitermacht.

### Die Kraft des bestehenden Codes

Dieser Ansatz ist effektiv, weil die Implementierung der Kernfunktionalität es Ihnen ermöglicht, Ihr ideales Design und Ihren Codierungsstil durch tatsächlichen Code zu materialisieren. Der effektivste Weg, Ihre Projektvision zu kommunizieren, ist durch Code, der Ihre Standards und Präferenzen widerspiegelt.

Indem Sie mit Kernfunktionen beginnen und sicherstellen, dass jede Komponente richtig funktioniert, bevor Sie weitergehen, behält das gesamte Projekt seine Konsistenz, was es der KI erleichtert, angemesseneren Code zu generieren.

## Der modulare 

Ansatz
Code in kleinere Module aufzuteilen ist entscheidend. Nach meiner Erfahrung macht es die Begrenzung von Dateien auf etwa 250 Codezeilen einfacher, der KI klare Anweisungen zu geben und den Versuch-und-Irrtum-Prozess effizienter zu gestalten. Während die Token-Anzahl ein genaueres Maß wäre, ist die Zeilenanzahl für menschliche Entwickler praktischer, daher verwenden wir diese als Richtlinie.

Diese Modularisierung beschränkt sich nicht nur auf die Trennung von Frontend, Backend und Datenbankkomponenten - es geht darum, die Funktionalität auf einer viel feineren Ebene aufzuteilen. Zum Beispiel könnten Sie innerhalb einer einzelnen Funktion die Validierung, Fehlerbehandlung und andere spezifische Funktionalitäten in separate Module aufteilen.

## Qualitätssicherung durch Tests

Ich halte Tests für entscheidend in der KI-unterstützten Entwicklung. Tests dienen nicht nur als Qualitätssicherungsmaßnahmen, sondern auch als Dokumentation, die die Codeabsichten klar demonstriert. Wenn Sie die KI bitten, neue Funktionen zu implementieren, fungiert der bestehende Testcode effektiv als Spezifikationsdokument.

Tests sind auch ein ausgezeichnetes Werkzeug zur Validierung der Korrektheit von KI-generiertem Code. Wenn Sie beispielsweise die KI neue Funktionalität für ein Modul implementieren lassen, ermöglicht das vorherige Schreiben von Testfällen eine objektive Bewertung, ob der generierte Code wie erwartet funktioniert.

## Balance zwischen Planung und Implementierung

Bevor Sie umfangreiche Funktionen implementieren, empfehle ich, zunächst den Plan mit der KI zu besprechen. Die Organisation von Anforderungen und die Berücksichtigung der Architektur führen zu einer reibungsloseren Implementierung. Eine gute Praxis ist es, zuerst die Anforderungen zusammenzustellen und dann zu einer separaten Chat-Sitzung für die Implementierungsarbeit überzugehen.

## Fazit

Durch die Befolgung dieser Praktiken können Sie die Stärken der KI nutzen und gleichzeitig eine konsistente, hochwertige Codebasis aufbauen. Selbst wenn Ihr Projekt wächst, bleibt jede Komponente gut definiert und handhabbar.
</file>

<file path="website/client/src/de/guide/comment-removal.md">
# Kommentarentfernung

Repomix kann beim Generieren der Ausgabedatei automatisch Kommentare aus Ihrer Codebasis entfernen. Dies kann helfen, Störungen zu reduzieren und sich auf den eigentlichen Code zu konzentrieren.

## Verwendung

Um die Kommentarentfernung zu aktivieren, setzen Sie die Option `removeComments` in Ihrer `repomix.config.json` auf `true`:

```json
{
  "output": {
    "removeComments": true
  }
}
```

## Unterstützte Sprachen

Repomix unterstützt die Kommentarentfernung für eine Vielzahl von Programmiersprachen, einschließlich:

- JavaScript/TypeScript (`//`, `/* */`)
- Python (`#`, `"""`, `'''`)
- Java (`//`, `/* */`)
- C/C++ (`//`, `/* */`)
- HTML (`<!-- -->`)
- CSS (`/* */`)
- Und viele mehr...

## Beispiel

Gegeben sei der folgende JavaScript-Code:

```javascript
// Dies ist ein einzeiliger Kommentar
function test() {
  /* Dies ist ein
     mehrzeiliger Kommentar */
  return true;
}
```

Mit aktivierter Kommentarentfernung wird die Ausgabe wie folgt aussehen:

```javascript
function test() {
  return true;
}
```

## Hinweise

- Die Kommentarentfernung wird vor anderen Verarbeitungsschritten durchgeführt, wie z.B. der Zeilennummerierung.
- Einige Kommentare, wie JSDoc-Kommentare, können je nach Sprache und Kontext erhalten bleiben.
</file>

<file path="website/client/src/de/guide/custom-instructions.md">
# Benutzerdefinierte Anweisungen

Repomix ermöglicht es Ihnen, benutzerdefinierte Anweisungen bereitzustellen, die in die Ausgabedatei aufgenommen werden. Dies kann nützlich sein, um Kontext oder spezifische Richtlinien für KI-Systeme hinzuzufügen, die das Repository verarbeiten.

## Verwendung

Um eine benutzerdefinierte Anweisung einzufügen, erstellen Sie eine Markdown-Datei (z.B. `repomix-instruction.md`) im Stammverzeichnis Ihres Repositories. Geben Sie dann den Pfad zu dieser Datei in Ihrer `repomix.config.json` an:

```json
{
  "output": {
    "instructionFilePath": "repomix-instruction.md"
  }
}
```

Der Inhalt dieser Datei wird in der Ausgabe unter dem Abschnitt "Instruction" aufgenommen.

## Beispiel

```markdown
# Repository-Anweisungen

Dieses Repository enthält den Quellcode für das Repomix-Tool. Bitte beachten Sie diese Richtlinien bei der Analyse des Codes:

1. Konzentrieren Sie sich auf die Kernfunktionalität im Verzeichnis `src/core`.
2. Achten Sie besonders auf die Sicherheitsprüfungen in `src/core/security`.
3. Ignorieren Sie alle Dateien im Verzeichnis `tests`.
```

Dies führt zu folgendem Abschnitt in der Ausgabe:

```xml
<instruction>
# Repository-Anweisungen

Dieses Repository enthält den Quellcode für das Repomix-Tool. Bitte beachten Sie diese Richtlinien bei der Analyse des Codes:

1. Konzentrieren Sie sich auf die Kernfunktionalität im Verzeichnis `src/core`.
2. Achten Sie besonders auf die Sicherheitsprüfungen in `src/core/security`.
3. Ignorieren Sie alle Dateien im Verzeichnis `tests`.
</instruction>
```
</file>

<file path="website/client/src/de/guide/installation.md">
# Installation

## Verwendung von npx (Keine Installation erforderlich)

```bash
npx repomix
```

## Globale Installation

### npm
```bash
npm install -g repomix
```

### Yarn
```bash
yarn global add repomix
```

### Homebrew (macOS/Linux)
```bash
brew install repomix
```

## Docker Installation

Docker-Image herunterladen und ausführen:

```bash
# Aktuelles Verzeichnis
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix

# Bestimmtes Verzeichnis
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory

# Remote-Repository
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote yamadashy/repomix
```

## VSCode-Erweiterung

Führen Sie Repomix direkt in VSCode mit der von der Community gepflegten [Repomix Runner](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner) Erweiterung aus.

Funktionen:
- Packen Sie jeden Ordner mit wenigen Klicks
- Wählen Sie zwischen Datei- oder Inhaltsmodus zum Kopieren
- Automatische Bereinigung von Ausgabedateien
- Funktioniert mit repomix.config.json

Installieren Sie sie vom [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner).

## Systemanforderungen

- Node.js: ≥ 18.0.0
- Git: Erforderlich für die Verarbeitung von Remote-Repositories

## Überprüfung

Überprüfen Sie nach der Installation, ob Repomix funktioniert:

```bash
repomix --version
repomix --help
```
</file>

<file path="website/client/src/de/guide/prompt-examples.md">
# Prompt-Beispiele

## Code-Review

### Architektur-Review
```
Analysieren Sie die Architektur dieser Codebasis:
1. Bewerten Sie die Gesamtstruktur und Muster
2. Identifizieren Sie potenzielle Architekturprobleme
3. Schlagen Sie Verbesserungen für die Skalierbarkeit vor
4. Notieren Sie Bereiche, die Best Practices folgen

Konzentrieren Sie sich auf Wartbarkeit und Modularität.
```

### Sicherheits-Review
```
Führen Sie eine Sicherheitsüberprüfung dieser Codebasis durch:
1. Identifizieren Sie potenzielle Sicherheitslücken
2. Prüfen Sie auf häufige Sicherheits-Anti-Patterns
3. Überprüfen Sie die Fehlerbehandlung und Eingabevalidierung
4. Bewerten Sie die Abhängigkeitssicherheit

Geben Sie konkrete Beispiele und Behebungsschritte an.
```

### Performance-Review
```
Überprüfen Sie die Codebasis auf Leistung:
1. Identifizieren Sie Leistungsengpässe
2. Überprüfen Sie die Ressourcennutzung
3. Überprüfen Sie die algorithmische Effizienz
4. Bewerten Sie Caching-Strategien

Fügen Sie spezifische Optimierungsempfehlungen hinzu.
```

## Dokumentationsgenerierung

### API-Dokumentation
```
Generieren Sie eine umfassende API-Dokumentation:
1. Listen und beschreiben Sie alle öffentlichen Endpunkte
2. Dokumentieren Sie Anfrage-/Antwortformate
3. Fügen Sie Verwendungsbeispiele hinzu
4. Notieren Sie eventuelle Einschränkungen
```

### Entwicklerhandbuch
```
Erstellen Sie ein Entwicklerhandbuch, das Folgendes umfasst:
1. Einrichtungsanweisungen
2. Projektstrukturübersicht
3. Entwicklungsworkflow
4. Testansatz
5. Häufige Fehlerbehebungsschritte
```

### Architektur-Dokumentation
```
Dokumentieren Sie die Systemarchitektur:
1. Überblick auf hoher Ebene
2. Komponenteninteraktionen
3. Datenflussdiagramme
4. Designentscheidungen und Begründung
5. Systembeschränkungen und -grenzen
```

## Analyse und Verbesserung

### Abhängigkeitsanalyse
```
Analysieren Sie die Projektabhängigkeiten:
1. Identifizieren Sie veraltete Pakete
2. Prüfen Sie auf Sicherheitslücken
3. Schlagen Sie alternative Pakete vor
4. Überprüfen Sie Abhängigkeitsnutzungsmuster

Fügen Sie spezifische Upgrade-Empfehlungen hinzu.
```

### Testabdeckung
```
Überprüfen Sie die Testabdeckung:
1. Identifizieren Sie ungetestete Komponenten
2. Schlagen Sie zusätzliche Testfälle vor
3. Überprüfen Sie die Testqualität
4. Empfehlen Sie Teststrategien
```

### Codequalität
```
Bewerten Sie die Codequalität und schlagen Sie Verbesserungen vor:
1. Überprüfen Sie Namenskonventionen
2. Prüfen Sie die Codeorganisation
3. Bewerten Sie die Fehlerbehandlung
4. Überprüfen Sie Kommentierungspraktiken

Geben Sie konkrete Beispiele für gute und problematische Muster an.
```

## Tipps für bessere Ergebnisse

1. **Seien Sie spezifisch**: Formulieren Sie klare Ziele und Bewertungskriterien
2. **Setzen Sie Kontext**: Geben Sie Ihre Rolle und das benötigte Expertiseniveau an
3. **Antwortformat**: Definieren Sie, wie die Antwort strukturiert sein soll
4. **Priorisieren**: Geben Sie an, welche Aspekte am wichtigsten sind

## Modellspezifische Hinweise

### Claude
- Verwenden Sie das XML-Ausgabeformat
- Platzieren Sie wichtige Anweisungen am Ende
- Spezifizieren Sie die Antwortstruktur

### ChatGPT
- Verwenden Sie das Markdown-Format
- Teilen Sie große Codebasen in Abschnitte auf
- Verwenden Sie System-Rollen-Prompts

### Gemini
- Funktioniert mit allen Formaten
- Konzentrieren Sie sich pro Anfrage auf bestimmte Bereiche
- Verwenden Sie schrittweise Analyse
</file>

<file path="website/client/src/de/guide/remote-repository-processing.md">
# Remote-Repository-Verarbeitung

## Grundlegende Verwendung

Öffentliche Repositories verarbeiten:
```bash
# Mit vollständiger URL
repomix --remote https://github.com/user/repo

# Mit GitHub-Kurzform
repomix --remote user/repo
```

## Branch- und Commit-Auswahl

```bash
# Bestimmter Branch
repomix --remote user/repo --remote-branch main

# Tag
repomix --remote user/repo --remote-branch v1.0.0

# Commit-Hash
repomix --remote user/repo --remote-branch 935b695
```

## Voraussetzungen

- Git muss installiert sein
- Internetverbindung
- Lesezugriff auf das Repository

## Ausgabekontrolle

```bash
# Benutzerdefinierter Ausgabeort
repomix --remote user/repo -o custom-output.xml

# Mit XML-Format
repomix --remote user/repo --style xml

# Kommentare entfernen
repomix --remote user/repo --remove-comments
```

## Docker-Verwendung

```bash
# Verarbeitung und Ausgabe in das aktuelle Verzeichnis
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo

# Ausgabe in bestimmtes Verzeichnis
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo
```

## Häufige Probleme

### Zugriffsprobleme
- Stellen Sie sicher, dass das Repository öffentlich ist
- Überprüfen Sie die Git-Installation
- Überprüfen Sie die Internetverbindung

### Große Repositories
- Verwenden Sie `--include`, um bestimmte Pfade auszuwählen
- Aktivieren Sie `--remove-comments`
- Verarbeiten Sie Branches separat
</file>

<file path="website/client/src/de/guide/security.md">
# Sicherheit

## Sicherheitsprüfungsfunktion

Repomix verwendet [Secretlint](https://github.com/secretlint/secretlint) zur Erkennung sensibler Informationen in Ihren Dateien:
- API-Schlüssel
- Zugangstoken
- Anmeldedaten
- Private Schlüssel
- Umgebungsvariablen

## Konfiguration

Sicherheitsprüfungen sind standardmäßig aktiviert.

Deaktivierung über CLI:
```bash
repomix --no-security-check
```

Oder in `repomix.config.json`:
```json
{
  "security": {
    "enableSecurityCheck": false
  }
}
```

## Sicherheitsmaßnahmen

1. **Ausschluss von Binärdateien**: Binärdateien werden nicht in die Ausgabe aufgenommen
2. **Git-bewusst**: Berücksichtigt `.gitignore`-Muster
3. **Automatische Erkennung**: Sucht nach häufigen Sicherheitsproblemen:
    - AWS-Anmeldedaten
    - Datenbankverbindungszeichenfolgen
    - Authentifizierungstoken
    - Private Schlüssel

## Wenn die Sicherheitsprüfung Probleme findet

Beispielausgabe:
```bash
🔍 Sicherheitsprüfung:
──────────────────
2 verdächtige Datei(en) erkannt und ausgeschlossen:
1. config/credentials.json
   - AWS-Zugriffsschlüssel gefunden
2. .env.local
   - Datenbank-Passwort gefunden
```

## Best Practices

1. Überprüfen Sie die Ausgabe immer vor dem Teilen
2. Verwenden Sie `.repomixignore` für sensible Pfade
3. Lassen Sie Sicherheitsprüfungen aktiviert
4. Entfernen Sie sensible Dateien aus dem Repository

## Melden von Sicherheitsproblemen

Haben Sie eine Sicherheitslücke gefunden? Bitte:
1. Öffnen Sie kein öffentliches Issue
2. E-Mail: koukun0120@gmail.com
3. Oder nutzen Sie [GitHub Security Advisories](https://github.com/yamadashy/repomix/security/advisories/new)
</file>

<file path="website/client/src/de/index.md">
---
layout: home
title: Repomix
titleTemplate: Packen Sie Ihren Codebase in KI-freundliche Formate
aside: false
editLink: false

features:
  - icon: 🤖
    title: KI-Optimiert
    details: Formatiert Ihren Codebase so, dass er für KI leicht zu verstehen und zu verarbeiten ist.

  - icon: ⚙️
    title: Git-Bewusst
    details: Berücksichtigt automatisch Ihre .gitignore-Dateien.

  - icon: 🛡️
    title: Sicherheitsorientiert
    details: Integriert Secretlint für robuste Sicherheitsprüfungen zur Erkennung und Verhinderung der Aufnahme sensibler Informationen.

  - icon: 📊
    title: Token-Zählung
    details: Bietet Token-Zählungen für jede Datei und das gesamte Repository, nützlich für LLM-Kontextgrenzen.

---

<div class="cli-section">

## Schnellstart

Sobald Sie mit Repomix eine gepackte Datei (`repomix-output.txt`) erstellt haben, können Sie diese mit einer Aufforderung wie dieser an einen KI-Assistenten senden:

```
Diese Datei enthält alle Dateien im Repository in einer Datei zusammengefasst.
Ich möchte den Code refaktorieren, bitte überprüfen Sie ihn zuerst.
```

Die KI wird Ihren gesamten Codebase analysieren und umfassende Einblicke geben:

![Repomix File Usage 1](/images/docs/repomix-file-usage-1.png)

Bei der Diskussion spezifischer Änderungen kann die KI bei der Code-Generierung helfen. Mit Funktionen wie Claudes Artifacts können Sie sogar mehrere voneinander abhängige Dateien erhalten:

![Repomix File Usage 2](/images/docs/repomix-file-usage-2.png)

Viel Spaß beim Programmieren! 🚀

## Leitfaden für fortgeschrittene Benutzer

Für fortgeschrittene Benutzer, die mehr Kontrolle benötigen, bietet Repomix umfangreiche Anpassungsmöglichkeiten über seine CLI-Schnittstelle.

### Schnellstart

Sie können Repomix sofort in Ihrem Projektverzeichnis ohne Installation ausprobieren:

```bash
npx repomix
```

Oder installieren Sie es global für wiederholte Verwendung:

```bash
# Installation mit npm
npm install -g repomix

# Alternativ mit yarn
yarn global add repomix

# Alternativ mit Homebrew (macOS/Linux)
brew install repomix

# Dann in einem beliebigen Projektverzeichnis ausführen
repomix
```

Das war's! Repomix generiert eine `repomix-output.txt` Datei in Ihrem aktuellen Verzeichnis, die Ihr gesamtes Repository in einem KI-freundlichen Format enthält.

### Verwendung

Um Ihr gesamtes Repository zu packen:

```bash
repomix
```

Um ein bestimmtes Verzeichnis zu packen:

```bash
repomix path/to/directory
```

Um bestimmte Dateien oder Verzeichnisse mit [Glob-Mustern](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax) zu packen:

```bash
repomix --include "src/**/*.ts,**/*.md"
```

Um bestimmte Dateien oder Verzeichnisse auszuschließen:

```bash
repomix --ignore "**/*.log,tmp/"
```

Um ein Remote-Repository zu packen:
```bash
# Kurzform verwenden
npx repomix --remote yamadashy/repomix

# Vollständige URL verwenden (unterstützt Branches und spezifische Pfade)
npx repomix --remote https://github.com/yamadashy/repomix
npx repomix --remote https://github.com/yamadashy/repomix/tree/main

# Commit-URL verwenden
npx repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1
```

Um eine neue Konfigurationsdatei (`repomix.config.json`) zu initialisieren:

```bash
repomix --init
```

Sobald Sie die gepackte Datei erstellt haben, können Sie sie mit generativen KI-Tools wie Claude, ChatGPT und Gemini verwenden.

#### Docker-Verwendung

Sie können Repomix auch mit Docker ausführen 🐳  
Dies ist nützlich, wenn Sie Repomix in einer isolierten Umgebung ausführen oder Container bevorzugen.

Grundlegende Verwendung (aktuelles Verzeichnis):

```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix
```

Um ein bestimmtes Verzeichnis zu packen:
```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory
```

Ein Remote-Repository verarbeiten und in ein `output`-Verzeichnis ausgeben:

```bash
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote https://github.com/yamadashy/repomix
```

### Ausgabeformate

Wählen Sie Ihr bevorzugtes Ausgabeformat:

```bash
# XML-Format (Standard)
repomix --style xml

# Markdown-Format
repomix --style markdown

# Klartext-Format
repomix --style plain
```

### Anpassung

Erstellen Sie eine `repomix.config.json` für dauerhafte Einstellungen:

```json
{
  "output": {
    "style": "markdown",
    "filePath": "custom-output.md",
    "removeComments": true,
    "showLineNumbers": true,
    "topFilesLength": 10
  },
  "ignore": {
    "customPatterns": ["*.test.ts", "docs/**"]
  }
}
```

### Weitere Beispiele
::: tip
💡 Besuchen Sie unser [GitHub-Repository](https://github.com/yamadashy/repomix) für vollständige Dokumentation und weitere Beispiele!
:::

</div>
</file>

<file path="website/client/src/en/guide/development/index.md">
# Contributing to Repomix

## Quick Start

```bash
git clone https://github.com/yamadashy/repomix.git
cd repomix
npm install
```

## Development Commands

```bash
# Run CLI
npm run repomix

# Run tests
npm run test
npm run test-coverage

# Lint code
npm run lint
```

## Code Style

- Use [Biome](https://biomejs.dev/) for linting and formatting
- Dependency injection for testability
- Keep files under 250 lines
- Add tests for new features

## Pull Request Guidelines

1. Run all tests
2. Pass linting checks
3. Update documentation
4. Follow existing code style

## Need Help?

- [Open an issue](https://github.com/yamadashy/repomix/issues)
- [Join Discord](https://discord.gg/wNYzTwZFku)
</file>

<file path="website/client/src/en/guide/development/setup.md">
# Development Setup

## Prerequisites

- Node.js ≥ 18.0.0
- Git
- npm

## Local Development

```bash
# Clone repository
git clone https://github.com/yamadashy/repomix.git
cd repomix

# Install dependencies
npm install

# Run CLI
npm run repomix
```

## Docker Development

```bash
# Build image
docker build -t repomix .

# Run container
docker run -v ./:/app -it --rm repomix
```

## Project Structure

```
src/
├── cli/          # CLI implementation
├── config/       # Configuration handling
├── core/         # Core functionality
└── shared/       # Shared utilities
```

## Testing

```bash
# Run tests
npm run test

# Test coverage
npm run test-coverage

# Linting
npm run lint-biome
npm run lint-ts
npm run lint-secretlint
```

## Release Process

1. Update version
```bash
npm version patch  # or minor/major
```

2. Run tests and build
```bash
npm run test-coverage
npm run build
```

3. Publish
```bash
npm publish
```
</file>

<file path="website/client/src/en/guide/tips/best-practices.md">
# AI-Assisted Development Best Practices: From My Experience

While I haven't successfully completed a large-scale project using AI yet, I'd like to share what I've learned so far from my experience working with AI in development.

## Basic Development Approach

When working with AI, attempting to implement all features at once can lead to unexpected issues and project stagnation. That's why it's more effective to start with core functionality and build each feature one at a time, ensuring solid implementation before moving forward.

### The Power of Existing Code

This approach is effective because implementing core functionality allows you to materialize your ideal design and coding style through actual code. The most effective way to communicate your project vision is through code that reflects your standards and preferences.

By starting with core features and ensuring each component works properly before moving on, the entire project maintains consistency, making it easier for AI to generate more appropriate code.

## The Modular Approach

Breaking code into smaller modules is crucial. In my experience, keeping files around 250 lines of code makes it easier to give clear instructions to AI and makes the trial-and-error process more efficient. While token count would be a more accurate metric, line count is more practical for human developers to work with, so we use that as a guideline.

This modularization isn't just about separating frontend, backend, and database components - it's about breaking down functionality at a much finer level. For example, within a single feature, you might separate validation, error handling, and other specific functionalities into distinct modules. Of course, high-level separation is also important, and implementing this modular approach gradually helps maintain clear instructions and enables AI to generate more appropriate code. This approach is effective not just for AI but for human developers as well.

## Ensuring Quality Through Testing

I consider testing to be crucial in AI-assisted development. Tests serve not only as quality assurance measures but also as documentation that clearly demonstrates code intentions. When asking AI to implement new features, existing test code effectively acts as a specification document.

Tests are also an excellent tool for validating the correctness of AI-generated code. For instance, when having AI implement new functionality for a module, writing test cases beforehand allows you to objectively evaluate whether the generated code behaves as expected. This aligns well with Test-Driven Development (TDD) principles and is particularly effective when collaborating with AI.

## Balancing Planning and Implementation

Before implementing large-scale features, I recommend first discussing the plan with AI. Organizing requirements and considering architecture leads to smoother implementation. A good practice is to compile requirements first, then move to a separate chat session for implementation work.

It's essential to have human review of AI output and make adjustments as needed. While the quality of AI-generated code is generally moderate, it still accelerates development compared to writing everything from scratch.

## Conclusion

By following these practices, you can leverage AI's strengths while building a consistent, high-quality codebase. Even as your project grows in size, each component remains well-defined and manageable.
</file>

<file path="website/client/src/en/guide/comment-removal.md">
# Comment Removal

Repomix can automatically remove comments from your codebase when generating the output file. This can help reduce noise and focus on the actual code.

## Usage

To enable comment removal, set the `removeComments` option to `true` in your `repomix.config.json`:

```json
{
  "output": {
    "removeComments": true
  }
}
```

## Supported Languages

Repomix supports comment removal for a wide range of programming languages, including:

- JavaScript/TypeScript (`//`, `/* */`)
- Python (`#`, `"""`, `'''`)
- Java (`//`, `/* */`)
- C/C++ (`//`, `/* */`)
- HTML (`<!-- -->`)
- CSS (`/* */`)
- And many more...

## Example

Given the following JavaScript code:

```javascript
// This is a single-line comment
function test() {
  /* This is a
     multi-line comment */
  return true;
}
```

With comment removal enabled, the output will be:

```javascript
function test() {
  return true;
}
```

## Notes

- Comment removal is performed before other processing steps, such as line number addition.
- Some comments, such as JSDoc comments, may be preserved depending on the language and context.
</file>

<file path="website/client/src/en/guide/custom-instructions.md">
# Custom Instructions

Repomix allows you to provide custom instructions that will be included in the output file. This can be useful for adding context or specific guidelines for AI systems processing the repository.

## Usage

To include a custom instruction, create a markdown file (e.g., `repomix-instruction.md`) in the root of your repository. Then, specify the path to this file in your `repomix.config.json`:

```json
{
  "output": {
    "instructionFilePath": "repomix-instruction.md"
  }
}
```

The content of this file will be included in the output under the "Instruction" section.

## Example

```markdown
# Repository Instructions

This repository contains the source code for the Repomix tool. Please follow these guidelines when analyzing the code:

1. Focus on the core functionality in the `src/core` directory.
2. Pay special attention to the security checks in `src/core/security`.
3. Ignore any files in the `tests` directory.
```

This will result in the following section in the output:

```xml
<instruction>
# Repository Instructions

This repository contains the source code for the Repomix tool. Please follow these guidelines when analyzing the code:

1. Focus on the core functionality in the `src/core` directory.
2. Pay special attention to the security checks in `src/core/security`.
3. Ignore any files in the `tests` directory.
</instruction>
```
</file>

<file path="website/client/src/en/guide/installation.md">
# Installation

## Using npx (No Installation Required)

```bash
npx repomix
```

## Global Installation

### npm
```bash
npm install -g repomix
```

### Yarn
```bash
yarn global add repomix
```

### Homebrew (macOS/Linux)
```bash
brew install repomix
```

## Docker Installation

Pull and run the Docker image:

```bash
# Current directory
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix

# Specific directory
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory

# Remote repository
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote yamadashy/repomix
```

## VSCode Extension

Run Repomix directly in VSCode with the community-maintained [Repomix Runner](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner) extension.

Features:
- Pack any folder with just a few clicks
- Choose between file or content mode for copying
- Automatic cleanup of output files
- Works with repomix.config.json

Install it from the [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner).

## System Requirements

- Node.js: ≥ 18.0.0
- Git: Required for remote repository processing

## Verification

After installation, verify that Repomix is working:

```bash
repomix --version
repomix --help
```
</file>

<file path="website/client/src/en/guide/prompt-examples.md">
# Prompt Examples

## Code Review

### Architecture Review
```
Analyze this codebase's architecture:
1. Evaluate the overall structure and patterns
2. Identify potential architectural issues
3. Suggest improvements for scalability
4. Note areas that follow best practices

Focus on maintainability and modularity.
```

### Security Review
```
Perform a security review of this codebase:
1. Identify potential security vulnerabilities
2. Check for common security anti-patterns
3. Review error handling and input validation
4. Assess dependency security

Provide specific examples and remediation steps.
```

### Performance Review
```
Review the codebase for performance:
1. Identify performance bottlenecks
2. Check resource utilization
3. Review algorithmic efficiency
4. Assess caching strategies

Include specific optimization recommendations.
```

## Documentation Generation

### API Documentation
```
Generate comprehensive API documentation:
1. List and describe all public endpoints
2. Document request/response formats
3. Include usage examples
4. Note any limitations or constraints
```

### Developer Guide
```
Create a developer guide covering:
1. Setup instructions
2. Project structure overview
3. Development workflow
4. Testing approach
5. Common troubleshooting steps
```

### Architecture Documentation
```
Document the system architecture:
1. High-level overview
2. Component interactions
3. Data flow diagrams
4. Design decisions and rationale
5. System constraints and limitations
```

## Analysis and Improvement

### Dependency Analysis
```
Analyze the project dependencies:
1. Identify outdated packages
2. Check for security vulnerabilities
3. Suggest alternative packages
4. Review dependency usage patterns

Include specific upgrade recommendations.
```

### Test Coverage
```
Review the test coverage:
1. Identify untested components
2. Suggest additional test cases
3. Review test quality
4. Recommend testing strategies
```

### Code Quality
```
Assess code quality and suggest improvements:
1. Review naming conventions
2. Check code organization
3. Evaluate error handling
4. Review commenting practices

Provide specific examples of good and problematic patterns.
```

## Tips for Better Results

1. **Be Specific**: Include clear objectives and evaluation criteria
2. **Set Context**: Specify your role and expertise level needed
3. **Request Format**: Define how you want the response structured
4. **Prioritize**: Indicate which aspects are most important

## Model-Specific Notes

### Claude
- Use XML output format
- Place important instructions at the end
- Specify response structure

### ChatGPT
- Use Markdown format
- Break large codebases into sections
- Include system role prompts

### Gemini
- Works with all formats
- Focus on specific areas per request
- Use step-by-step analysis
</file>

<file path="website/client/src/en/guide/remote-repository-processing.md">
# Remote Repository Processing

## Basic Usage

Process public repositories:
```bash
# Using full URL
repomix --remote https://github.com/user/repo

# Using GitHub shorthand
repomix --remote user/repo
```

## Branch and Commit Selection

```bash
# Specific branch
repomix --remote user/repo --remote-branch main

# Tag
repomix --remote user/repo --remote-branch v1.0.0

# Commit hash
repomix --remote user/repo --remote-branch 935b695
```

## Requirements

- Git must be installed
- Internet connection
- Read access to repository

## Output Control

```bash
# Custom output location
repomix --remote user/repo -o custom-output.xml

# With XML format
repomix --remote user/repo --style xml

# Remove comments
repomix --remote user/repo --remove-comments
```

## Docker Usage

```bash
# Process and output to current directory
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo

# Output to specific directory
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo
```

## Common Issues

### Access Issues
- Ensure repository is public
- Check Git installation
- Verify internet connection

### Large Repositories
- Use `--include` to select specific paths
- Enable `--remove-comments`
- Process branches separately
</file>

<file path="website/client/src/en/guide/security.md">
# Security

## Security Check Feature

Repomix uses [Secretlint](https://github.com/secretlint/secretlint) to detect sensitive information in your files:
- API keys
- Access tokens
- Credentials
- Private keys
- Environment variables

## Configuration

Security checks are enabled by default.

Disable via CLI:
```bash
repomix --no-security-check
```

Or in `repomix.config.json`:
```json
{
  "security": {
    "enableSecurityCheck": false
  }
}
```

## Security Measures

1. **Binary File Exclusion**: Binary files are not included in output
2. **Git-Aware**: Respects `.gitignore` patterns
3. **Automated Detection**: Scans for common security issues:
    - AWS credentials
    - Database connection strings
    - Authentication tokens
    - Private keys

## When Security Check Finds Issues

Example output:
```bash
🔍 Security Check:
──────────────────
2 suspicious file(s) detected and excluded:
1. config/credentials.json
   - Found AWS access key
2. .env.local
   - Found database password
```

## Best Practices

1. Always review output before sharing
2. Use `.repomixignore` for sensitive paths
3. Keep security checks enabled
4. Remove sensitive files from repository

## Reporting Security Issues

Found a security vulnerability? Please:
1. Do not open a public issue
2. Email: koukun0120@gmail.com
3. Or use [GitHub Security Advisories](https://github.com/yamadashy/repomix/security/advisories/new)
</file>

<file path="website/client/src/en/index.md">
---
layout: home
title: Repomix
titleTemplate: Pack your codebase into AI-friendly formats
aside: false
editLink: false

features:
  - icon: 🤖
    title: AI-Optimized
    details: Formats your codebase in a way that's easy for AI to understand and process.

  - icon: ⚙️
    title: Git-Aware
    details: Automatically respects your .gitignore files.

  - icon: 🛡️
    title: Security-Focused
    details: Incorporates Secretlint for robust security checks to detect and prevent inclusion of sensitive information.

  - icon: 📊
    title: Token Counting
    details: Provides token counts for each file and the entire repository, useful for LLM context limits.

---

<div class="cli-section">


## Quick Start

Once you've generated a packed file (`repomix-output.txt`) using Repomix, you can send it to an AI assistant with a prompt like:

```
This file contains all the files in the repository combined into one.
I want to refactor the code, so please review it first.
```

The AI will analyze your entire codebase and provide comprehensive insights:

![Repomix File Usage 1](/images/docs/repomix-file-usage-1.png)

When discussing specific changes, the AI can help generate code. With features like Claude's Artifacts, you can even receive multiple interdependent files:

![Repomix File Usage 2](/images/docs/repomix-file-usage-2.png)

Happy coding! 🚀



## Power User Guide

For advanced users who need more control, Repomix offers extensive customization options through its CLI interface.

### Quick Start

You can try Repomix instantly in your project directory without installation:

```bash
npx repomix
```

Or install globally for repeated use:

```bash
# Install using npm
npm install -g repomix

# Alternatively using yarn
yarn global add repomix

# Alternatively using Homebrew (macOS/Linux)
brew install repomix

# Then run in any project directory
repomix
```

That's it! Repomix will generate a `repomix-output.txt` file in your current directory, containing your entire repository in an AI-friendly format.



### Usage

To pack your entire repository:

```bash
repomix
```

To pack a specific directory:

```bash
repomix path/to/directory
```

To pack specific files or directories using [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax):

```bash
repomix --include "src/**/*.ts,**/*.md"
```

To exclude specific files or directories:

```bash
repomix --ignore "**/*.log,tmp/"
```

To pack a remote repository:
```bash
# Using shorthand format
npx repomix --remote yamadashy/repomix

# Using full URL (supports branches and specific paths)
npx repomix --remote https://github.com/yamadashy/repomix
npx repomix --remote https://github.com/yamadashy/repomix/tree/main

# Using commit's URL
npx repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1
```

To initialize a new configuration file (`repomix.config.json`):

```bash
repomix --init
```

Once you have generated the packed file, you can use it with Generative AI tools like Claude, ChatGPT, and Gemini.

#### Docker Usage

You can also run Repomix using Docker 🐳  
This is useful if you want to run Repomix in an isolated environment or prefer using containers.

Basic usage (current directory):

```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix
```

To pack a specific directory:
```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory
```

Process a remote repository and output to a `output` directory:

```bash
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote https://github.com/yamadashy/repomix
```

### Output Formats

Choose your preferred output format:

```bash
# XML format (default)
repomix --style xml

# Markdown format
repomix --style markdown

# Plain text format
repomix --style plain
```

### Customization

Create a `repomix.config.json` for persistent settings:

```json
{
  "output": {
    "style": "markdown",
    "filePath": "custom-output.md",
    "removeComments": true,
    "showLineNumbers": true,
    "topFilesLength": 10
  },
  "ignore": {
    "customPatterns": ["*.test.ts", "docs/**"]
  }
}
```

### More Examples
::: tip
💡 Check out our [GitHub repository](https://github.com/yamadashy/repomix) for complete documentation and more examples!
:::

</div>
</file>

<file path="website/client/src/es/guide/development/index.md">
# Contribuir a Repomix

## Inicio rápido

```bash
git clone https://github.com/yamadashy/repomix.git
cd repomix
npm install
```

## Comandos de desarrollo

```bash
# Ejecutar CLI
npm run repomix

# Ejecutar pruebas
npm run test
npm run test-coverage

# Linting de código
npm run lint
```

## Estilo de código

- Usa [Biome](https://biomejs.dev/) para linting y formateo
- Inyección de dependencias para la testabilidad
- Mantén los archivos por debajo de las 250 líneas
- Agrega pruebas para las nuevas funciones

## Pautas para Pull Requests

1. Ejecuta todas las pruebas
2. Pasa las comprobaciones de linting
3. Actualiza la documentación
4. Sigue el estilo de código existente

## ¿Necesitas ayuda?

- [Abre un issue](https://github.com/yamadashy/repomix/issues)
- [Únete a Discord](https://discord.gg/wNYzTwZFku)
</file>

<file path="website/client/src/es/guide/development/setup.md">
# Configuración del entorno de desarrollo

## Requisitos previos

- Node.js ≥ 18.0.0
- Git
- npm

## Desarrollo local

```bash
# Clonar el repositorio
git clone https://github.com/yamadashy/repomix.git
cd repomix

# Instalar dependencias
npm install

# Ejecutar CLI
npm run repomix
```

## Desarrollo con Docker

```bash
# Construir la imagen
docker build -t repomix .

# Ejecutar el contenedor
docker run -v ./:/app -it --rm repomix
```

## Estructura del proyecto

```
src/
├── cli/          # Implementación de la CLI
├── config/       # Manejo de la configuración
├── core/         # Funcionalidad principal
└── shared/       # Utilidades compartidas
```

## Pruebas

```bash
# Ejecutar pruebas
npm run test

# Cobertura de pruebas
npm run test-coverage

# Linting
npm run lint-biome
npm run lint-ts
npm run lint-secretlint
```

## Proceso de lanzamiento

1. Actualizar la versión
```bash
npm version patch  # o minor/major
```

2. Ejecutar pruebas y construir
```bash
npm run test-coverage
npm run build
```

3. Publicar
```bash
npm publish
</file>

<file path="website/client/src/es/guide/tips/best-practices.md">
# Mejores prácticas para el desarrollo asistido por IA: Desde mi experiencia

Aunque todavía no he completado con éxito un proyecto a gran escala utilizando IA, me gustaría compartir lo que he aprendido hasta ahora de mi experiencia trabajando con IA en el desarrollo.

## Enfoque de desarrollo básico

Cuando se trabaja con IA, intentar implementar todas las funciones a la vez puede llevar a problemas inesperados y al estancamiento del proyecto. Por eso es más efectivo comenzar con la funcionalidad principal y construir cada función una por una, asegurando una implementación sólida antes de seguir adelante.

### El poder del código existente

Este enfoque es efectivo porque implementar la funcionalidad principal te permite materializar tu diseño ideal y estilo de codificación a través de código real. La forma más efectiva de comunicar la visión de tu proyecto es a través de código que refleje tus estándares y preferencias.

Al comenzar con las funciones principales y asegurar que cada componente funcione correctamente antes de continuar, todo el proyecto mantiene la consistencia, lo que facilita que la IA genere código más apropiado.

## El enfoque modular

Dividir el código en módulos más pequeños es crucial. En mi experiencia, mantener los archivos alrededor de 250 líneas de código facilita dar instrucciones claras a la IA y hace que el proceso de prueba y error sea más eficiente. Si bien el recuento de tokens sería una métrica más precisa, el recuento de líneas es más práctico para los desarrolladores humanos, por lo que lo usamos como una guía.

Esta modularización no se trata solo de separar los componentes de frontend, backend y base de datos, sino de desglosar la funcionalidad a un nivel mucho más fino. Por ejemplo, dentro de una sola función, podrías separar la validación, el manejo de errores y otras funcionalidades específicas en módulos distintos. Por supuesto, la separación de alto nivel también es importante, e implementar este enfoque modular gradualmente ayuda a mantener instrucciones claras y permite que la IA genere código más apropiado. Este enfoque es efectivo no solo para la IA sino también para los desarrolladores humanos.

## Asegurar la calidad a través de las pruebas

Considero que las pruebas son cruciales en el desarrollo asistido por IA. Las pruebas no solo sirven como medidas de garantía de calidad, sino también como documentación que demuestra claramente las intenciones del código. Al pedirle a la IA que implemente nuevas funciones, el código de prueba existente actúa efectivamente como un documento de especificación.

Las pruebas también son una excelente herramienta para validar la corrección del código generado por IA. Por ejemplo, al hacer que la IA implemente una nueva funcionalidad para un módulo, escribir casos de prueba de antemano te permite evaluar objetivamente si el código generado se comporta como se espera. Esto se alinea bien con los principios de desarrollo basado en pruebas (TDD) y es particularmente efectivo cuando se colabora con la IA.

## Equilibrar la planificación y la implementación

Antes de implementar funciones a gran escala, recomiendo discutir primero el plan con la IA. Organizar los requisitos y considerar la arquitectura conduce a una implementación más fluida. Una buena práctica es compilar primero los requisitos y luego pasar a una sesión de chat separada para el trabajo de implementación.

Es esencial que un humano revise el resultado de la IA y haga los ajustes necesarios. Si bien la calidad del código generado por IA es generalmente moderada, aún acelera el desarrollo en comparación con escribir todo desde cero.

## Conclusión

Siguiendo estas prácticas, puedes aprovechar las fortalezas de la IA mientras construyes una base de código consistente y de alta calidad. Incluso a medida que tu proyecto crece en tamaño, cada componente permanece bien definido y manejable.
</file>

<file path="website/client/src/es/guide/comment-removal.md">
# Eliminación de comentarios

Repomix puede eliminar automáticamente los comentarios de tu código al generar el archivo de salida. Esto puede ayudar a reducir el ruido y centrarse en el código real.

## Uso

Para habilitar la eliminación de comentarios, establece la opción `removeComments` a `true` en tu archivo `repomix.config.json`:

```json
{
  "output": {
    "removeComments": true
  }
}
```

## Lenguajes soportados

Repomix soporta la eliminación de comentarios para una amplia gama de lenguajes de programación, incluyendo:

- JavaScript/TypeScript (`//`, `/* */`)
- Python (`#`, `"""`, `'''`)
- Java (`//`, `/* */`)
- C/C++ (`//`, `/* */`)
- HTML (`<!-- -->`)
- CSS (`/* */`)
- Y muchos más...

## Ejemplo

Dado el siguiente código JavaScript:

```javascript
// Este es un comentario de una sola línea
function test() {
  /* Este es un
     comentario multilínea */
  return true;
}
```

Con la eliminación de comentarios habilitada, la salida será:

```javascript
function test() {
  return true;
}
```

## Notas

- La eliminación de comentarios se realiza antes que otros pasos de procesamiento, como la adición de números de línea.
- Algunos comentarios, como los comentarios JSDoc, pueden conservarse dependiendo del lenguaje y el contexto.
</file>

<file path="website/client/src/es/guide/custom-instructions.md">
# Instrucciones Personalizadas

Repomix te permite proporcionar instrucciones personalizadas que se incluirán en el archivo de salida. Esto puede ser útil para agregar contexto o pautas específicas para los sistemas de IA que procesan el repositorio.

## Uso

Para incluir una instrucción personalizada, crea un archivo markdown (por ejemplo, `repomix-instruction.md`) en la raíz de tu repositorio. Luego, especifica la ruta a este archivo en tu `repomix.config.json`:

```json
{
  "output": {
    "instructionFilePath": "repomix-instruction.md"
  }
}
```

El contenido de este archivo se incluirá en la salida bajo la sección "Instruction".

## Ejemplo

```markdown
# Instrucciones del Repositorio

Este repositorio contiene el código fuente de la herramienta Repomix. Por favor, sigue estas pautas al analizar el código:

1. Concéntrate en la funcionalidad principal en el directorio `src/core`.
2. Presta especial atención a las verificaciones de seguridad en `src/core/security`.
3. Ignora cualquier archivo en el directorio `tests`.
```

Esto resultará en la siguiente sección en la salida:

```xml
<instruction>
# Instrucciones del Repositorio

Este repositorio contiene el código fuente de la herramienta Repomix. Por favor, sigue estas pautas al analizar el código:

1. Concéntrate en la funcionalidad principal en el directorio `src/core`.
2. Presta especial atención a las verificaciones de seguridad en `src/core/security`.
3. Ignora cualquier archivo en el directorio `tests`.
</instruction>
</file>

<file path="website/client/src/es/guide/installation.md">
# Instalación

## Usando npx (no requiere instalación)

```bash
npx repomix
```

## Instalación global

### npm
```bash
npm install -g repomix
```

### Yarn
```bash
yarn global add repomix
```

### Homebrew (macOS/Linux)
```bash
brew install repomix
```

## Instalación con Docker

Extrae y ejecuta la imagen de Docker:

```bash
# Directorio actual
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix

# Directorio específico
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix ruta/al/directorio

# Repositorio remoto
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote yamadashy/repomix
```

## Extensión de VSCode

Ejecuta Repomix directamente en VSCode con la extensión [Repomix Runner](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner) mantenida por la comunidad.

Características:
- Empaqueta cualquier carpeta con unos pocos clics
- Elige entre modo archivo o contenido para copiar
- Limpieza automática de archivos de salida
- Compatible con repomix.config.json

Instálala desde el [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner).

## Requisitos del sistema

- Node.js: ≥ 18.0.0
- Git: Requerido para el procesamiento de repositorios remotos

## Verificación

Después de la instalación, verifica que Repomix esté funcionando:

```bash
repomix --version
repomix --help
</file>

<file path="website/client/src/es/guide/prompt-examples.md">
# Ejemplos de prompts

## Revisión de código

### Revisión de arquitectura
```
Analiza la arquitectura de esta base de código:
1. Evalúa la estructura general y los patrones
2. Identifica posibles problemas de arquitectura
3. Sugiere mejoras para la escalabilidad
4. Señala las áreas que siguen las mejores prácticas

Concéntrate en la mantenibilidad y la modularidad.
```

### Revisión de seguridad
```
Realiza una revisión de seguridad de esta base de código:
1. Identifica posibles vulnerabilidades de seguridad
2. Busca antipatrones de seguridad comunes
3. Revisa el manejo de errores y la validación de entradas
4. Evalúa la seguridad de las dependencias

Proporciona ejemplos específicos y pasos de remediación.
```

### Revisión de rendimiento
```
Revisa el rendimiento de la base de código:
1. Identifica cuellos de botella de rendimiento
2. Comprueba la utilización de recursos
3. Revisa la eficiencia algorítmica
4. Evalúa las estrategias de almacenamiento en caché

Incluye recomendaciones de optimización específicas.
```

## Generación de documentación

### Documentación de API
```
Genera documentación de API completa:
1. Enumera y describe todos los endpoints públicos
2. Documenta los formatos de solicitud/respuesta
3. Incluye ejemplos de uso
4. Señala cualquier limitación o restricción
```

### Guía para desarrolladores
```
Crea una guía para desarrolladores que cubra:
1. Instrucciones de configuración
2. Descripción general de la estructura del proyecto
3. Flujo de trabajo de desarrollo
4. Enfoque de pruebas
5. Pasos comunes para la solución de problemas
```

### Documentación de arquitectura
```
Documenta la arquitectura del sistema:
1. Descripción general de alto nivel
2. Interacciones entre componentes
3. Diagramas de flujo de datos
4. Decisiones de diseño y justificación
5. Restricciones y limitaciones del sistema
```

## Análisis y mejora

### Análisis de dependencias
```
Analiza las dependencias del proyecto:
1. Identifica paquetes obsoletos
2. Busca vulnerabilidades de seguridad
3. Sugiere paquetes alternativos
4. Revisa los patrones de uso de dependencias

Incluye recomendaciones de actualización específicas.
```

### Cobertura de pruebas
```
Revisa la cobertura de pruebas:
1. Identifica componentes no probados
2. Sugiere casos de prueba adicionales
3. Revisa la calidad de las pruebas
4. Recomienda estrategias de prueba
```

### Calidad del código
```
Evalúa la calidad del código y sugiere mejoras:
1. Revisa las convenciones de nomenclatura
2. Comprueba la organización del código
3. Evalúa el manejo de errores
4. Revisa las prácticas de comentarios

Proporciona ejemplos específicos de patrones buenos y problemáticos.
```

## Consejos para obtener mejores resultados

1. **Sé específico**: Incluye objetivos claros y criterios de evaluación
2. **Establece el contexto**: Especifica tu rol y el nivel de experiencia necesario
3. **Solicita un formato**: Define cómo quieres que se estructure la respuesta
4. **Prioriza**: Indica qué aspectos son más importantes

## Notas específicas del modelo

### Claude
- Usa el formato de salida XML
- Coloca las instrucciones importantes al final
- Especifica la estructura de la respuesta

### ChatGPT
- Usa el formato Markdown
- Divide las bases de código grandes en secciones
- Incluye prompts de rol del sistema

### Gemini
- Funciona con todos los formatos
- Concéntrate en áreas específicas por solicitud
- Usa un análisis paso a paso
</file>

<file path="website/client/src/es/guide/remote-repository-processing.md">
# Procesamiento de repositorios remotos

## Uso básico

Procesar repositorios públicos:
```bash
# Usando URL completo
repomix --remote https://github.com/usuario/repositorio

# Usando la abreviatura de GitHub
repomix --remote usuario/repositorio
```

## Selección de rama y commit

```bash
# Rama específica
repomix --remote usuario/repositorio --remote-branch main

# Etiqueta
repomix --remote usuario/repositorio --remote-branch v1.0.0

# Hash de commit
repomix --remote usuario/repositorio --remote-branch 935b695
```

## Requisitos

- Git debe estar instalado
- Conexión a Internet
- Acceso de lectura al repositorio

## Control de salida

```bash
# Ubicación de salida personalizada
repomix --remote usuario/repositorio -o salida-personalizada.xml

# Con formato XML
repomix --remote usuario/repositorio --style xml

# Eliminar comentarios
repomix --remote usuario/repositorio --remove-comments
```

## Uso de Docker

```bash
# Procesar y generar la salida en el directorio actual
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote usuario/repositorio

# Generar la salida en un directorio específico
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote usuario/repositorio
```

## Problemas comunes

### Problemas de acceso
- Asegúrate de que el repositorio sea público
- Comprueba la instalación de Git
- Verifica la conexión a Internet

### Repositorios grandes
- Usa `--include` para seleccionar rutas específicas
- Habilita `--remove-comments`
- Procesa las ramas por separado
</file>

<file path="website/client/src/es/guide/security.md">
# Seguridad

## Función de verificación de seguridad

Repomix utiliza [Secretlint](https://github.com/secretlint/secretlint) para detectar información sensible en tus archivos:
- Claves de API
- Tokens de acceso
- Credenciales
- Claves privadas
- Variables de entorno

## Configuración

Las verificaciones de seguridad están habilitadas de forma predeterminada.

Deshabilitar a través de CLI:
```bash
repomix --no-security-check
```

O en `repomix.config.json`:
```json
{
  "security": {
    "enableSecurityCheck": false
  }
}
```

## Medidas de seguridad

1. **Exclusión de archivos binarios**: Los archivos binarios no se incluyen en la salida
2. **Compatible con Git**: Respeta los patrones de `.gitignore`
3. **Detección automatizada**: Busca problemas de seguridad comunes:
    - Credenciales de AWS
    - Cadenas de conexión de bases de datos
    - Tokens de autenticación
    - Claves privadas

## Cuando la verificación de seguridad encuentra problemas

Ejemplo de salida:
```bash
🔍 Verificación de seguridad:
──────────────────
2 archivo(s) sospechoso(s) detectado(s) y excluido(s):
1. config/credentials.json
   - Se encontró la clave de acceso de AWS
2. .env.local
   - Se encontró la contraseña de la base de datos
```

## Mejores prácticas

1. Siempre revisa la salida antes de compartirla
2. Usa `.repomixignore` para rutas sensibles
3. Mantén las verificaciones de seguridad habilitadas
4. Elimina los archivos sensibles del repositorio

## Reportar problemas de seguridad

¿Encontraste una vulnerabilidad de seguridad? Por favor:
1. No abras un issue público
2. Envía un correo electrónico a: koukun0120@gmail.com
3. O usa [GitHub Security Advisories](https://github.com/yamadashy/repomix/security/advisories/new)
</file>

<file path="website/client/src/es/index.md">
---
layout: home
title: Repomix
titleTemplate: Empaqueta tu código en formatos amigables para la IA
aside: false
editLink: false

features:
  - icon: 🤖
    title: Optimizado para IA
    details: Formatea tu código de una manera que sea fácil de entender y procesar para la IA.

  - icon: ⚙️
    title: Compatible con Git
    details: Respeta automáticamente tus archivos .gitignore.

  - icon: 🛡️
    title: Enfocado en la seguridad
    details: Incorpora Secretlint para realizar robustas comprobaciones de seguridad que detectan y previenen la inclusión de información sensible.

  - icon: 📊
    title: Conteo de tokens
    details: Proporciona recuentos de tokens para cada archivo y para todo el repositorio, útil para los límites de contexto de los LLM.

---

<div class="cli-section">

## Inicio rápido

Una vez que hayas generado un archivo empaquetado (`repomix-output.txt`) usando Repomix, puedes enviarlo a un asistente de IA con un prompt como:

```
Este archivo contiene todos los archivos del repositorio combinados en uno.
Quiero refactorizar el código, así que por favor revísalo primero.
```

La IA analizará todo tu código y proporcionará información completa:

![Repomix File Usage 1](/images/docs/repomix-file-usage-1.png)

Al discutir cambios específicos, la IA puede ayudar a generar código. Con funciones como los Artefactos de Claude, incluso puedes recibir múltiples archivos interdependientes:

![Repomix File Usage 2](/images/docs/repomix-file-usage-2.png)

¡Feliz programación! 🚀



## Guía para usuarios avanzados

Para los usuarios avanzados que necesitan más control, Repomix ofrece amplias opciones de personalización a través de su interfaz de línea de comandos.

### Inicio rápido

Puedes probar Repomix instantáneamente en el directorio de tu proyecto sin necesidad de instalación:

```bash
npx repomix
```

O instalarlo globalmente para uso repetido:

```bash
# Instalar usando npm
npm install -g repomix

# Alternativamente usando yarn
yarn global add repomix

# Alternativamente usando Homebrew (macOS/Linux)
brew install repomix

# Luego ejecutar en cualquier directorio de proyecto
repomix
```

¡Eso es todo! Repomix generará un archivo `repomix-output.txt` en tu directorio actual, que contendrá todo tu repositorio en un formato amigable para la IA.



### Uso

Para empaquetar todo tu repositorio:

```bash
repomix
```

Para empaquetar un directorio específico:

```bash
repomix ruta/al/directorio
```

Para empaquetar archivos o directorios específicos usando [patrones glob](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax):

```bash
repomix --include "src/**/*.ts,**/*.md"
```

Para excluir archivos o directorios específicos:

```bash
repomix --ignore "**/*.log,tmp/"
```

Para empaquetar un repositorio remoto:
```bash
# Usando formato abreviado
npx repomix --remote yamadashy/repomix

# Usando URL completa (soporta ramas y rutas específicas)
npx repomix --remote https://github.com/yamadashy/repomix
npx repomix --remote https://github.com/yamadashy/repomix/tree/main

# Usando URL de confirmación
npx repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1
```

Para inicializar un nuevo archivo de configuración (`repomix.config.json`):

```bash
repomix --init
```

Una vez que hayas generado el archivo empaquetado, puedes usarlo con herramientas de IA generativa como Claude, ChatGPT y Gemini.

#### Uso de Docker

También puedes ejecutar Repomix usando Docker 🐳  
Esto es útil si deseas ejecutar Repomix en un entorno aislado o prefieres usar contenedores.

Uso básico (directorio actual):

```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix
```

Para empaquetar un directorio específico:
```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix ruta/al/directorio
```

Procesar un repositorio remoto y generar la salida en un directorio `output`:

```bash
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote https://github.com/yamadashy/repomix
```

### Formatos de salida

Elige tu formato de salida preferido:

```bash
# Formato XML (predeterminado)
repomix --style xml

# Formato Markdown
repomix --style markdown

# Formato de texto plano
repomix --style plain
```

### Personalización

Crea un archivo `repomix.config.json` para configuraciones persistentes:

```json
{
  "output": {
    "style": "markdown",
    "filePath": "custom-output.md",
    "removeComments": true,
    "showLineNumbers": true,
    "topFilesLength": 10
  },
  "ignore": {
    "customPatterns": ["*.test.ts", "docs/**"]
  }
}
```

### Más ejemplos
::: tip
💡 ¡Consulta nuestro [repositorio de GitHub](https://github.com/yamadashy/repomix) para obtener la documentación completa y más ejemplos!
:::

</div>
</file>

<file path="website/client/src/ja/guide/development/index.md">
# Repomixへの貢献

## クイックスタート

```bash
git clone https://github.com/yamadashy/repomix.git
cd repomix
npm install
```

## 開発コマンド

```bash
# CLIを実行
npm run repomix

# テストを実行
npm run test
npm run test-coverage

# コードのリント
npm run lint
```

## コーディングスタイル

- [Biome](https://biomejs.dev/)を使用してリントとフォーマットを行う
- テスト可能性のために依存性注入を使用
- ファイルは250行以下に保つ
- 新機能には必ずテストを追加

## プルリクエストのガイドライン

1. 全てのテストを実行
2. リントチェックをパス
3. ドキュメントを更新
4. 既存のコードスタイルに従う

## サポートが必要な場合

- [イシューを作成](https://github.com/yamadashy/repomix/issues)
- [Discordに参加](https://discord.gg/wNYzTwZFku)
</file>

<file path="website/client/src/ja/guide/development/setup.md">
# 開発環境のセットアップ

## 前提条件

- Node.js ≥ 18.0.0
- Git
- npm

## ローカル開発

```bash
# リポジトリのクローン
git clone https://github.com/yamadashy/repomix.git
cd repomix

# 依存関係のインストール
npm install

# CLIの実行
npm run repomix
```

## Docker開発

```bash
# イメージのビルド
docker build -t repomix .

# コンテナの実行
docker run -v ./:/app -it --rm repomix
```

## プロジェクト構造

```
src/
├── cli/          # CLI実装
├── config/       # 設定の処理
├── core/         # コア機能
└── shared/       # 共有ユーティリティ
```

## テスト

```bash
# テストの実行
npm run test

# テストカバレッジ
npm run test-coverage

# リント
npm run lint-biome
npm run lint-ts
npm run lint-secretlint
```

## リリースプロセス

1. バージョンの更新
```bash
npm version patch  # または minor/major
```

2. テストとビルドの実行
```bash
npm run test-coverage
npm run build
```

3. 公開
```bash
npm publish
```
</file>

<file path="website/client/src/ja/guide/tips/best-practices.md">
# AI支援開発のベストプラクティス：私の経験から

私はまだAIを使用した大規模な開発を経験したわけではありませんが、これまでの経験から学んだことを共有したいと思います。

## 開発の基本アプローチ

AIと共に開発を進める際、一度にすべての機能を実装しようとすると、予期せぬ問題が発生し、プロジェクト全体が行き詰まってしまうことがあります。そのため、コア機能から始めて一つ一つの機能を確実に作り上げていく方法が効果的です。

### 既存コードの重要性

この方法が効果的な理由は、コア機能の実装を通じて、あなたの理想とする設計やコーディングスタイルをコードとして具体化できる点にあります。
プロジェクトのビジョンを最も効果的に伝えるのは、あなたの基準や好みを反映したコードそのものなのです。

コア機能から始めつつ、一つ一つの機能を確実に動く状態にしてから次の機能に進むことで、プロジェクト全体が一貫性を持ち、AIがより適切なコードを生成しやすくなります。

## モジュール分割のアプローチ

また、コードは小さなモジュールに分割することが重要です。私の経験では、行数を250行程度に抑えることで結果的にAIに明確な指示を与えやすくなり、試行錯誤のプロセスも効率的になります。実際にはトークン数のほうが指標として正しいのですが、人間に判断しやすいのは行数なので、ここでは行数としています。

これはフロントエンド、バックエンド、データベースなどの大きな単位の話ではなく、もっと小さな単位、例えば一つの機能の中でもバリデーションやエラーハンドリングなど、個別の機能をモジュールとして分割することが重要です。

もちろん大きな単位で分けるのも重要で、段階的にモジュールを分割しておくことで、指示も明確になり、AIがより適切なコードを生成しやすくなります。AIに限らず人間にも効果的なアプローチですね。


## テストを通じた品質の確保
私はテストも重要だと考えています。
テストは単なる品質保証の手段としてだけでなく、コードの意図を明確に示すドキュメントとしても機能します。AIに新しい機能の実装を依頼する際、既存のテストコードは実装すべき機能の仕様書として働きます。

また、AIが生成したコードの正しさを判断する際、テストは非常に効果的なツールとなります。例えば、あるモジュールの新しい機能をAIに実装してもらう場合、テストケースを先に書いておくことで、生成されたコードが期待通りの動作をするかを客観的に評価できます。これはテスト駆動開発（TDD）の考え方とも親和性が高く、AIとの協業において特に有効です。


## 計画と実装のバランス

大規模な機能を実装する前には、まずAIと計画について話し合うことをお勧めします。要件を整理し、アーキテクチャを検討することで、後の実装がスムーズになります。要件をまずまとめて、別のチャットに移動して実装を進めるのも良いでしょう。
また、AIの出力は必ず人間がレビューし、必要に応じて調整を加えることが重要です。AIの出力の品質は一般的に中程度ですが、それでも一からコードを書くよりも開発速度は向上します。



## まとめ

この方法を実践することで、AIの強みを活かしながら、一貫性のある高品質なコードベースを構築できます。プロジェクトの規模が大きくなっても、各部分が明確に定義され、管理しやすい状態を維持できるでしょう。
</file>

<file path="website/client/src/ja/guide/comment-removal.md">
# ソースコード中のコメント削除

Repomixは、パッケージング時に、ソースコード中のコメントを自動的に削除する機能を提供します。これにより、出力ファイルからノイズを減らし、実際のコードに焦点を当てることができます。

## 使用方法

コメントの削除を有効にするには、`repomix.config.json`で`removeComments`オプションを`true`に設定します。

```json
{
  "output": {
    "removeComments": true
  }
}
```

## サポートされている言語

Repomixは以下を含む多くのプログラミング言語のコメント削除をサポートしています。

- JavaScript/TypeScript (`//`, `/* */`)
- Python (`#`, `"""`, `'''`)
- Java (`//`, `/* */`)
- C/C++ (`//`, `/* */`)
- HTML (`<!-- -->`)
- CSS (`/* */`)
- その他多数の言語

## 使用例と出力例

以下のようなJavaScriptコードがある場合

```javascript
// これは単一行コメントです
function test() {
  /* これは
     複数行コメントです */
  return true;
}
```

コメント削除を有効にすると、出力は以下のようになります。

```javascript
function test() {
  return true;
}
```

## 制限事項

- コメントの削除は、行番号の追加など、他の処理よりも先に行われます。
- JSDocコメントなど、一部のコメントは削除されない場合があります。
</file>

<file path="website/client/src/ja/guide/custom-instructions.md">
# カスタム指示

Repomixでは、出力ファイルに含めるカスタム指示（独自の指示）を提供することができます。これは、AIシステムにプロジェクトの特定のコンテキストや要件を理解させるのに役立ちます。

## 使用方法

カスタム指示を含めるには、リポジトリのルートにマークダウンファイル（例：`repomix-instruction.md`）を作成し、`repomix.config.json`でそのパスを指定します。

```json
{
  "output": {
    "instructionFilePath": "repomix-instruction.md"
  }
}
```

このファイルの内容は出力の「Instruction」セクションに含まれます。

## 使用例

```markdown
# コーディングガイドライン

- Airbnb JavaScript スタイルガイドに従う
- 適切な場合は、ファイルを小さな単位に分割する
- 自明でないロジックにはコメントを追加。すべてのテキストは英語で記述
- すべての新機能には対応するユニットテストを作成する

# 生成内容について

- 特に指定がない限り、すべての内容を省略せずに含める
- 大規模なコードベースを処理しながら、出力の品質を維持する
```

これは出力ファイルで以下のように表示されます。

```xml
<instruction>
# コーディングガイドライン

- Airbnb JavaScript スタイルガイドに従う
- 適切な場合は、ファイルを小さな単位に分割する
- 自明でないロジックにはコメントを追加。すべてのテキストは英語で記述
- すべての新機能には対応するユニットテストを作成する

# 生成内容について

- 特に指定がない限り、すべての内容を省略せずに含める
- 大規模なコードベースを処理しながら、出力の品質を維持する
</instruction>
```

## 注意点

- カスタム指示は出力ファイルの`Instruction`セクションに含まれます。
- カスタム指示は適切な長さに保つことを推奨します。過度に長い指示は逆効果になる場合があります。
- プロジェクト固有のガイドラインや要件に焦点を当てることで、AIがプロジェクトのコンテキストを理解しやすくなり、より効果的な結果が得られます。
</file>

<file path="website/client/src/ja/guide/installation.md">
# インストール

## npx を使用する方法 (インストール不要)

```bash
npx repomix
```

## グローバルインストール

### npm
```bash
npm install -g repomix
```

### Yarn
```bash
yarn global add repomix
```

### Homebrew（macOS/Linux）
```bash
brew install repomix
```

## Dockerを使用する方法

以下のコマンドで Docker イメージをプルして実行できます。

```bash
# カレントディレクトリを処理
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix

# 特定のディレクトリを処理
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory

# リモートリポジトリを処理
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote yamadashy/repomix
```

## VSCode 拡張機能

VSCodeでRepomixを直接実行できるコミュニティメンテナンスの[Repomix Runner](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner)拡張機能があります。

機能:
- クリック数回でフォルダをパック
- ファイルまたはコンテンツモードでのコピーが可能
- 出力ファイルの自動クリーンアップ
- repomix.config.jsonと連携

[VSCode マーケットプレイス](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner)からインストールできます。

## システム要件

- Node.js: 18.0.0 以上
- Git: リモートリポジトリを処理する場合はインストールしてください

## インストールの確認

インストール後、以下のコマンドで Repomix が正常に動作することを確認できます。

```bash
repomix --version
repomix --help
```
</file>

<file path="website/client/src/ja/guide/prompt-examples.md">
# プロンプト例

## コードレビュー

### アーキテクチャレビュー
```
このコードベースのアーキテクチャを分析してください。
1. 全体的な構造とパターンを評価
2. アーキテクチャ上の潜在的な問題点を特定
3. スケーラビリティ向上のための改善案を提案
4. ベストプラクティスに従っている部分を指摘

保守性とモジュール性に特に注目してください。
```

### セキュリティレビュー
```
このコードベースのセキュリティレビューを実施してください。
1. 潜在的なセキュリティ脆弱性の特定
2. 一般的なセキュリティアンチパターンのチェック
3. エラー処理と入力バリデーションの確認
4. 依存関係のセキュリティ評価

具体的な例と対策手順を含めてください。
```

### パフォーマンスレビュー
```
パフォーマンスの観点からコードベースをレビューしてください。
1. パフォーマンスのボトルネックを特定
2. リソース使用状況の確認
3. アルゴリズムの効率性の評価
4. キャッシュ戦略の評価

具体的な最適化の推奨事項を含めてください。
```

## ドキュメント生成

### API ドキュメント
```
包括的なAPIドキュメントを生成してください。
1. すべてのパブリックエンドポイントをリストアップし説明
2. リクエスト/レスポンスのフォーマットを文書化
3. 使用例を含める
4. 制限事項や制約を記載
```

### 開発者ガイド
```
以下の内容を含む開発者ガイドを作成してください。
1. セットアップ手順
2. プロジェクト構造の概要
3. 開発ワークフロー
4. テストアプローチ
5. 一般的なトラブルシューティング手順
```

### アーキテクチャドキュメント
```
システムアーキテクチャを文書化してください。
1. 高レベルの概要
2. コンポーネント間の相互作用
3. データフロー図
4. 設計上の決定と根拠
5. システムの制約と制限事項
```

## 分析と改善

### 依存関係の分析
```
プロジェクトの依存関係を分析してください。
1. 古くなったパッケージの特定
2. セキュリティ脆弱性のチェック
3. 代替パッケージの提案
4. 依存関係の使用パターンの確認

具体的なアップグレード推奨事項を含めてください。
```

### テストカバレッジ
```
テストカバレッジを確認してください。
1. テストされていないコンポーネントの特定
2. 追加のテストケースの提案
3. テストの品質の確認
4. テスト戦略の推奨

具体的な改善案を提供してください。
```

### コード品質
```
コード品質を評価し、改善点を提案してください。
1. 命名規則の確認
2. コード構成の確認
3. エラー処理の評価
4. コメントの実践の確認

良いパターンと問題のあるパターンの具体例を提供してください。
```

## より良い結果を得るためのヒント

1. **具体的に**: 明確な目標と評価基準を含める
2. **コンテキストを設定**: あなたの役割と必要な専門知識レベルを指定
3. **レスポンス形式**: 希望する回答の構造を指定
4. **優先順位**: どの側面が最も重要かを示す

## モデル別の注意点

### Claude
- XML出力形式を使用
- 重要な指示を最後に配置
- レスポンス構造を指定

### ChatGPT
- Markdown形式を使用
- 大規模なコードベースはセクションに分割
- システムロールプロンプトを含める

### Gemini
- すべての形式で動作
- 特定の領域に焦点を絞る
- ステップバイステップの分析を使用
</file>

<file path="website/client/src/ja/guide/remote-repository-processing.md">
# リモートリポジトリの処理

## 基本的な使用方法

パブリックリポジトリを処理
```bash
# 完全なURLを使用
repomix --remote https://github.com/user/repo

# GitHubのショートハンド形式を使用
repomix --remote user/repo
```

## ブランチとコミットの選択

```bash
# 特定のブランチ
repomix --remote user/repo --remote-branch main

# タグ
repomix --remote user/repo --remote-branch v1.0.0

# コミットハッシュ
repomix --remote user/repo --remote-branch 935b695
```

## 必要条件

- Gitがインストールされていること
- インターネット接続があること
- リポジトリへの読み取りアクセス権があること

## 出力の制御

```bash
# 出力先のカスタマイズ
repomix --remote user/repo -o custom-output.xml

# XML形式で出力
repomix --remote user/repo --style xml

# コメントを削除
repomix --remote user/repo --remove-comments
```

## Docker使用時

```bash
# カレントディレクトリに出力
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo

# 特定のディレクトリに出力
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo
```

## 一般的な問題

### アクセスの問題
- リポジトリがパブリックであることを確認
- Gitのインストールを確認
- インターネット接続を確認

### 大規模リポジトリの処理
- `--include`で特定のパスを選択
- `--remove-comments`を有効化
- ブランチごとに個別に処理
</file>

<file path="website/client/src/ja/guide/security.md">
# セキュリティ

## セキュリティチェック機能

Repomixは[Secretlint](https://github.com/secretlint/secretlint)を使用して、ファイル内の機密情報を検出します：
- APIキー
- アクセストークン
- 認証情報
- 秘密鍵
- 環境変数

## 設定

セキュリティチェックはデフォルトで有効になっています。

CLIで無効化する場合
```bash
repomix --no-security-check
```

または`repomix.config.json`で
```json
{
  "security": {
    "enableSecurityCheck": false
  }
}
```

## セキュリティ対策

1. **バイナリファイルの除外**: バイナリファイルは出力に含まれません
2. **Git対応**: `.gitignore`パターンを尊重します
3. **自動検出**: 以下を含む一般的なセキュリティ問題を検出
    - AWSの認証情報
    - データベース接続文字列
    - 認証トークン
    - 秘密鍵

## セキュリティチェックで問題が見つかった場合

出力例
```bash
🔍 Security Check:
──────────────────
2 suspicious file(s) detected and excluded:
1. config/credentials.json
   - Found AWS access key
2. .env.local
   - Found database password
```

## ベストプラクティス

1. 共有する前に必ず出力を確認
2. `.repomixignore`を使用して機密性のあるパスを除外
3. セキュリティチェックを有効に保つ
4. 機密ファイルをリポジトリから削除

## セキュリティ問題の報告

セキュリティ脆弱性を発見した場合は
1. パブリックなイシューは作成しないでください
2. メール: koukun0120@gmail.com
3. または[GitHub Security Advisories](https://github.com/yamadashy/repomix/security/advisories/new)を使用
</file>

<file path="website/client/src/ja/index.md">
---
layout: home
title: Repomix
titleTemplate: コードベースをAIフレンドリーな形式にパッケージング
aside: false
editLink: false

features:
  - icon: 🤖
    title: AI最適化
    details: コードベースをAIが理解・処理しやすい形式にフォーマット

  - icon: ⚙️
    title: Git対応
    details: .gitignoreファイルを自動的に認識し、適切なファイル除外

  - icon: 🛡️
    title: セキュリティ重視
    details: Secretlintを組み込み、機密情報の検出と除外

  - icon: 📊
    title: トークンカウント
    details: ファイルごとおよびコードベース全体のトークン数を計測し、LLMのコンテキスト制限に対応

---

<div class="cli-section">

## クイックスタート

Repomixを使用すると、コードベース全体を1ファイル（`repomix-output.txt`）にできます。

そのまま Claude に次のようなプロンプトと一緒に送ると、

```
このファイルはコードベース内のファイルを1つにまとめたものです。
コードのリファクタリングを行いたいので、まずはコードレビューをお願いします。
```

全体の内容を理解した上で、リファクタリングなどを進めることができます。

![Repomixの使用例1](/images/docs/repomix-file-usage-1.png)

具体的な内容を提案すると、それに従って良い感じのコードを生成してくれます。Claude だと Artifacts 機能で複数のファイルが出力できるため、依存関係にある複数のコードも一緒に生成できます。

![Repomixの使用例2](/images/docs/repomix-file-usage-2.png)

良いコーディング体験を！🚀

## より凝った使い方をしたい方へ

Repomix はCLIでも使えます。

ウェブ上では簡単な設定しかできませんが、より詳細な設定で実行したい場合はCLIを使うことをお勧めします。

### クイックスタート

任意のディレクトリで以下のコマンドを実行すると、 `repomix-output.txt` が生成され、それ以降の使い方は同様です。

```bash
npx repomix
```

または、グローバルにインストールして繰り返し使用することもできます。

```bash
# npmを使用してインストール
npm install -g repomix

# または、yarnを使用
yarn global add repomix

# または、Homebrewを使用（macOS/Linux）
brew install repomix

# その後、任意のプロジェクトディレクトリで実行
repomix
```


### CLIの使用方法

カレントディレクトリ全体をまとめる。

```bash
repomix
```

特定のディレクトリをまとめる。

```bash
repomix path/to/directory
```

[glob パターン](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax)を使用して特定のファイルやディレクトリを指定。

```bash
repomix --include "src/**/*.ts,**/*.md"
```

特定のファイルやディレクトリを除外。

```bash
repomix --ignore "**/*.log,tmp/"
```

リモートリポジトリをまとめる。

```bash
# ショートハンド形式を使用
npx repomix --remote yamadashy/repomix

# 完全なURL（ブランチや特定のパスをサポート）
npx repomix --remote https://github.com/yamadashy/repomix
npx repomix --remote https://github.com/yamadashy/repomix/tree/main

# コミットのURLを使用
npx repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1
```

設定ファイル（`repomix.config.json`）の初期化

```bash
repomix --init
```

生成されたファイルは、Claude、ChatGPT、Geminiなどの生成AIツールで使用できます。

### Docker使用方法 🐳

Dockerを使用してRepomixを実行することも可能で、分離された環境でRepomixを実行したい場合に便利です。

基本的な使用方法

```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix
```

特定のディレクトリをまとめる
```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory
```

リモートリポジトリを処理し、`output`ディレクトリに出力

```bash
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote https://github.com/yamadashy/repomix
```

### 出力フォーマット

出力フォーマットは3種類あり、`xml`, `markdown`, `plain` を選択できます。
LLMによっては得意・不得意があるので、適切なフォーマットを選択してください。

```bash
# プレーンテキストフォーマット（デフォルト）
repomix --style plain

# XMLフォーマット
repomix --style xml

# Markdownフォーマット
repomix --style markdown
```

### カスタマイズ

フォルダごとの永続的な設定のために`repomix --init`で`repomix.config.json`を作成できます。

```json
{
  "output": {
    "style": "markdown",
    "filePath": "custom-output.md",
    "removeComments": true,
    "showLineNumbers": true,
    "topFilesLength": 10
  },
  "ignore": {
    "customPatterns": ["*.test.ts", "docs/**"]
  }
}
```

### その他の例
::: tip
💡 詳細なドキュメントと更なる例については、[GitHubリポジトリ](https://github.com/yamadashy/repomix)をご覧ください！
:::

</div>
</file>

<file path="website/client/src/ko/guide/development/index.md">
# Repomix에 기여하기

## 빠른 시작

```bash
git clone https://github.com/yamadashy/repomix.git
cd repomix
npm install
```

## 개발 명령어

```bash
# CLI 실행
npm run repomix

# 테스트 실행
npm run test
npm run test-coverage

# 코드 린트
npm run lint
```

## 코드 스타일

- 린트 및 포맷팅에 [Biome](https://biomejs.dev/)을 사용합니다.
- 테스트 용이성을 위해 의존성 주입을 사용합니다.
- 파일 길이를 250줄 미만으로 유지합니다.
- 새로운 기능에 대한 테스트를 추가합니다.

## Pull Request 가이드라인

1. 모든 테스트를 실행합니다.
2. 린트 검사를 통과합니다.
3. 문서를 업데이트합니다.
4. 기존 코드 스타일을 따릅니다.

## 도움이 필요하신가요?

- [이슈 열기](https://github.com/yamadashy/repomix/issues)
- [Discord 참여](https://discord.gg/wNYzTwZFku)
</file>

<file path="website/client/src/ko/guide/development/setup.md">
# 개발 환경 설정

## 필수 구성 요소

- Node.js ≥ 18.0.0
- Git
- npm

## 로컬 개발

```bash
# 저장소 복제
git clone https://github.com/yamadashy/repomix.git
cd repomix

# 의존성 설치
npm install

# CLI 실행
npm run repomix
```

## Docker 개발

```bash
# 이미지 빌드
docker build -t repomix .

# 컨테이너 실행
docker run -v ./:/app -it --rm repomix
```

## 프로젝트 구조

```
src/
├── cli/          # CLI 구현
├── config/       # 구성 처리
├── core/         # 핵심 기능
└── shared/       # 공유 유틸리티
```

## 테스트

```bash
# 테스트 실행
npm run test

# 테스트 커버리지
npm run test-coverage

# 린트
npm run lint-biome
npm run lint-ts
npm run lint-secretlint
```

## 릴리스 프로세스

1. 버전 업데이트
```bash
npm version patch  # 또는 minor/major
```

2. 테스트 및 빌드 실행
```bash
npm run test-coverage
npm run build
```

3. 게시
```bash
npm publish
</file>

<file path="website/client/src/ko/guide/tips/best-practices.md">
# AI 지원 개발 모범 사례: 나의 경험으로부터

저는 아직 AI를 사용하여 대규모 프로젝트를 성공적으로 완료하지는 못했지만, 지금까지 개발 과정에서 AI와 함께 작업하면서 배운 점을 공유하고자 합니다.

## 기본 개발 접근 방식

AI와 함께 작업할 때, 모든 기능을 한 번에 구현하려고 하면 예상치 못한 문제와 프로젝트 정체로 이어질 수 있습니다. 그렇기 때문에 핵심 기능부터 시작하여 각 기능을 하나씩 구축하고, 다음 단계로 넘어가기 전에 견고한 구현을 보장하는 것이 더 효과적입니다.

### 기존 코드의 힘

이 접근 방식이 효과적인 이유는 핵심 기능을 구현하면 실제 코드를 통해 이상적인 설계와 코딩 스타일을 구체화할 수 있기 때문입니다. 프로젝트 비전을 전달하는 가장 효과적인 방법은 표준과 선호도를 반영하는 코드를 작성하는 것입니다.

핵심 기능부터 시작하여 각 구성 요소가 제대로 작동하는지 확인한 후 다음 단계로 진행하면 전체 프로젝트의 일관성이 유지되므로 AI가 더 적절한 코드를 생성하기 쉬워집니다.

## 모듈식 접근 방식

코드를 더 작은 모듈로 나누는 것이 중요합니다. 제 경험상, 파일을 약 250줄 정도로 유지하면 AI에게 명확한 지침을 제공하기 쉽고 시행착오 프로세스가 더 효율적입니다. 토큰 수가 더 정확한 지표가 될 수 있지만, 줄 수는 개발자가 작업하기에 더 실용적이므로 이를 지침으로 사용합니다.

이러한 모듈화는 단순히 프런트엔드, 백엔드, 데이터베이스 구성 요소를 분리하는 것뿐만 아니라 훨씬 더 세분화된 수준에서 기능을 분리하는 것입니다. 예를 들어, 단일 기능 내에서 유효성 검사, 오류 처리 및 기타 특정 기능을 별개의 모듈로 분리할 수 있습니다. 물론, 높은 수준의 분리도 중요하며, 이러한 모듈식 접근 방식을 점진적으로 구현하면 명확한 지침을 유지하고 AI가 더 적절한 코드를 생성할 수 있습니다. 이 접근 방식은 AI뿐만 아니라 사람 개발자에게도 효과적입니다.

## 테스트를 통한 품질 보장

저는 AI 지원 개발에서 테스트가 매우 중요하다고 생각합니다. 테스트는 품질 보장 수단일 뿐만 아니라 코드 의도를 명확하게 보여주는 문서 역할도 합니다. AI에게 새로운 기능 구현을 요청할 때, 기존 테스트 코드는 사실상 사양 문서 역할을 합니다.

테스트는 또한 AI가 생성한 코드의 정확성을 검증하는 훌륭한 도구입니다. 예를 들어, AI에게 모듈에 대한 새로운 기능 구현을 요청할 때, 테스트 케이스를 미리 작성하면 생성된 코드가 예상대로 작동하는지 객관적으로 평가할 수 있습니다. 이는 테스트 주도 개발(TDD) 원칙과 잘 부합하며 AI와 협업할 때 특히 효과적입니다.

## 계획과 구현의 균형

대규모 기능을 구현하기 전에 먼저 AI와 계획에 대해 논의하는 것이 좋습니다. 요구 사항을 정리하고 아키텍처를 고려하면 구현이 더 원활해집니다. 좋은 방법은 먼저 요구 사항을 정리한 다음 별도의 대화 세션으로 이동하여 구현 작업을 수행하는 것입니다.

AI의 출력을 사람이 검토하고 필요에 따라 조정하는 것이 중요합니다. AI가 생성한 코드의 품질은 일반적으로 보통 수준이지만, 모든 것을 처음부터 작성하는 것보다 개발 속도를 높여줍니다.

## 결론

이러한 관행을 따르면 AI의 강점을 활용하면서 일관성 있고 고품질의 코드베이스를 구축할 수 있습니다. 프로젝트 규모가 커지더라도 각 구성 요소는 잘 정의되고 관리하기 쉬운 상태로 유지됩니다.
</file>

<file path="website/client/src/ko/guide/comment-removal.md">
# 주석 제거

Repomix는 출력 파일을 생성할 때 코드베이스에서 주석을 자동으로 제거할 수 있습니다. 이를 통해 노이즈를 줄이고 실제 코드에 집중할 수 있습니다.

## 사용법

주석 제거를 활성화하려면 `repomix.config.json`에서 `removeComments` 옵션을 `true`로 설정합니다.

```json
{
  "output": {
    "removeComments": true
  }
}
```

## 지원되는 언어

Repomix는 다음을 포함한 광범위한 프로그래밍 언어에 대한 주석 제거를 지원합니다.

- JavaScript/TypeScript (`//`, `/* */`)
- Python (`#`, `"""`, `'''`)
- Java (`//`, `/* */`)
- C/C++ (`//`, `/* */`)
- HTML (`<!-- -->`)
- CSS (`/* */`)
- 그리고 더 많은 언어들...

## 예시

다음 JavaScript 코드가 주어졌을 때:

```javascript
// 이것은 한 줄 주석입니다
function test() {
  /* 이것은
     여러 줄 주석입니다 */
  return true;
}
```

주석 제거를 활성화하면 출력은 다음과 같습니다.

```javascript
function test() {
  return true;
}
```

## 참고

- 주석 제거는 행 번호 추가와 같은 다른 처리 단계 전에 수행됩니다.
- JSDoc 주석과 같은 일부 주석은 언어 및 컨텍스트에 따라 보존될 수 있습니다.
</file>

<file path="website/client/src/ko/guide/custom-instructions.md">
# 사용자 정의 지시사항

Repomix는 출력 파일에 포함될 사용자 정의 지시사항을 제공할 수 있게 해줍니다. 이는 AI 시스템이 프로젝트의 특정 맥락이나 요구 사항을 이해하는 데 도움이 됩니다.

## 사용법

사용자 정의 지시사항을 포함하려면 저장소의 루트에 마크다운 파일(예: `repomix-instruction.md`)을 생성하고, `repomix.config.json`에서 해당 파일의 경로를 지정하세요:

```json
{
  "output": {
    "instructionFilePath": "repomix-instruction.md"
  }
}
```

이 파일의 내용은 출력의 "Instruction" 섹션에 포함됩니다.

## 사용 예시

```markdown
# 코딩 지침

- Airbnb JavaScript 스타일 가이드를 따르세요.
- 필요한 경우 파일을 더 작은 단위로 분할하세요.
- 불분명한 로직에는 주석을 추가하세요. 모든 텍스트는 영어로 작성하세요.
- 모든 새로운 기능에 대해 해당 단위 테스트를 작성하세요.

## 생성된 콘텐츠에 대하여

- 특별한 지정이 없는 한, 모든 콘텐츠를 생략 없이 포함하세요.
- 대규모 코드베이스를 처리할 때도 출력 품질을 유지하세요.
```

이는 출력 파일에서 다음과 같이 표시됩니다:

```xml
<instruction>
# 코딩 가이드라인

- Airbnb JavaScript 스타일 가이드를 따릅니다
- 필요한 경우 파일을 작은 단위로 분할합니다
- 명확하지 않은 로직에는 주석을 추가합니다. 모든 텍스트는 영어로 작성합니다
- 모든 새로운 기능에는 해당하는 단위 테스트를 작성합니다

## 생성 내용에 대해

- 특별한 지정이 없는 한, 모든 내용은 축약하지 않고 포함합니다
- 대규모 코드베이스를 처리하면서도 출력의 품질을 유지합니다
</instruction>
```

## 모범 사례

1. **간결하고 명확하게**: 지시사항은 간결하되, 필요한 세부 사항은 모두 포함해야 합니다.
2. **구체적인 예시 제공**: 적절한 경우 코드 예시를 사용하여 설명을 보완합니다.
3. **우선순위 설정**: 가장 중요한 지시사항을 문서 앞부분에 배치하여 강조합니다.
4. **맥락 포함**: 프로젝트의 배경과 중요한 고려사항을 제공하여 AI가 작업을 더 잘 이해하도록 돕습니다.
5. **구조화된 콘텐츠**: 제목과 목록을 사용하여 지시사항을 체계적으로 구성하고 가독성을 높입니다.

## 주의 사항

- 지시사항에 민감한 정보를 포함하지 마세요
- 프로젝트의 변화를 반영하여 정기적으로 지시사항을 업데이트하세요
- 지시사항이 프로젝트의 다른 문서와 일관성을 유지하도록 하세요
- 명확한 계층 구조를 사용하여 콘텐츠를 구성하세요
</file>

<file path="website/client/src/ko/guide/installation.md">
# 설치

## npx 사용 (설치 불필요)

```bash
npx repomix
```

## 전역 설치

### npm
```bash
npm install -g repomix
```

### Yarn
```bash
yarn global add repomix
```

### Homebrew (macOS/Linux)
```bash
brew install repomix
```

## Docker 설치

Docker를 사용하면 환경 설정 문제를 피할 수 있어 가장 편리한 방법 중 하나입니다. 아래와 같이 실행하세요:

```bash
# 현재 디렉토리 처리
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix

# 특정 디렉토리 처리
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory

# 원격 저장소 처리
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote yamadashy/repomix
```

## VSCode 확장 프로그램

커뮤니티에서 관리하는 [Repomix Runner](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner) 확장 프로그램을 통해 VSCode에서 직접 Repomix를 실행할 수 있습니다.

기능:
- 몇 번의 클릭으로 폴더 패키징
- 파일 또는 콘텐츠 모드로 복사
- 출력 파일 자동 정리
- repomix.config.json 지원

[VSCode 마켓플레이스](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner)에서 설치하세요.

## 시스템 요구 사항

- Node.js: 18.0.0 이상
- Git: 원격 저장소 처리 시 필요

## 설치 확인

설치가 완료된 후, 다음 명령어로 Repomix가 정상적으로 작동하는지 확인하세요:

```bash
repomix --version
repomix --help
```
</file>

<file path="website/client/src/ko/guide/prompt-examples.md">
# 프롬프트 예시

## 코드 리뷰

### 아키텍처 리뷰
```
이 코드베이스의 아키텍처를 분석하세요:
1. 전체 구조와 패턴을 평가
2. 잠재적인 아키텍처 문제 식별
3. 확장성을 위한 개선 사항 제안
4. 모범 사례를 따르는 영역 기록

유지보수성과 모듈성에 중점을 두세요.
```

### 보안 리뷰
```
이 코드베이스의 보안 검토를 수행하세요:
1. 잠재적인 보안 취약점 식별
2. 일반적인 보안 안티패턴 확인
3. 오류 처리 및 입력 유효성 검사 검토
4. 의존성 보안 평가

구체적인 예시와 해결 단계를 제공하세요.
```

### 성능 리뷰
```
코드베이스의 성능을 검토하세요:
1. 성능 병목 현상 식별
2. 리소스 사용량 확인
3. 알고리즘 효율성 검토
4. 캐싱 전략 평가

구체적인 최적화 권장 사항을 포함하세요.
```

## 문서 생성

### API 문서
```
포괄적인 API 문서를 생성하세요:
1. 모든 공개 엔드포인트 나열 및 설명
2. 요청/응답 형식 문서화
3. 사용 예시 포함
4. 제한 사항 기록
```

### 개발자 가이드
```
다음 내용을 포함하는 개발자 가이드를 작성하세요:
1. 설정 지침
2. 프로젝트 구조 개요
3. 개발 워크플로우
4. 테스트 접근 방식
5. 일반적인 문제 해결 단계
```

### 아키텍처 문서
```
시스템 아키텍처를 문서화하세요:
1. 상위 수준 개요
2. 컴포넌트 상호 작용
3. 데이터 흐름 다이어그램
4. 설계 결정 및 근거
5. 시스템 제약 사항 및 한계
```

## 분석 및 개선

### 의존성 분석
```
프로젝트 의존성을 분석하세요:
1. 오래된 패키지 식별
2. 보안 취약점 확인
3. 대체 패키지 제안
4. 의존성 사용 패턴 검토

구체적인 업그레이드 권장 사항을 포함하세요.
```

### 테스트 커버리지
```
테스트 커버리지를 검토하세요:
1. 테스트되지 않은 컴포넌트 식별
2. 추가 테스트 케이스 제안
3. 테스트 품질 검토
4. 테스트 전략 권장
```

### 코드 품질
```
코드 품질을 평가하고 개선 사항을 제안하세요:
1. 명명 규칙 검토
2. 코드 구성 확인
3. 오류 처리 평가
4. 주석 작성 관행 검토

좋은 패턴과 문제가 있는 패턴의 구체적인 예시를 제공하세요.
```

## 더 나은 결과를 위한 팁

1. **구체적으로 작성**: 명확한 목표와 평가 기준을 포함하세요
2. **컨텍스트 설정**: 귀하의 역할과 필요한 전문성 수준을 지정하세요
3. **응답 형식**: 원하는 응답 구조를 정의하세요
4. **우선순위 지정**: 가장 중요한 측면을 표시하세요

## 모델별 참고 사항

### Claude
- XML 출력 형식 사용
- 중요한 지시사항을 마지막에 배치
- 응답 구조 지정

### ChatGPT
- 마크다운 형식 사용
- 큰 코드베이스를 섹션으로 분할
- 시스템 역할 프롬프트 사용

### Gemini
- 모든 형식과 호환
- 요청당 특정 영역에 집중
- 단계별 분석 사용
</file>

<file path="website/client/src/ko/guide/remote-repository-processing.md">
# 원격 저장소 처리

## 기본 사용법

공개 저장소 처리:
```bash
# 전체 URL 사용
repomix --remote https://github.com/user/repo

# GitHub 단축형 사용
repomix --remote user/repo
```

## 브랜치 및 커밋 선택

```bash
# 특정 브랜치
repomix --remote user/repo --remote-branch main

# 태그
repomix --remote user/repo --remote-branch v1.0.0

# 커밋 해시
repomix --remote user/repo --remote-branch 935b695
```

## 요구 사항

- Git이 설치되어 있어야 함
- 인터넷 연결
- 저장소에 대한 읽기 권한

## 출력 제어

```bash
# 사용자 지정 출력 위치
repomix --remote user/repo -o custom-output.xml

# XML 형식 사용
repomix --remote user/repo --style xml

# 주석 제거
repomix --remote user/repo --remove-comments
```

## Docker 사용

```bash
# 현재 디렉토리에서 처리 및 출력
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo

# 특정 디렉토리에 출력
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo
```

## 일반적인 문제

### 접근 문제
- 저장소가 공개되어 있는지 확인
- Git 설치 확인
- 인터넷 연결 확인

### 대용량 저장소
- `--include`를 사용하여 특정 경로 선택
- `--remove-comments` 활성화
- 브랜치별로 개별 처리
</file>

<file path="website/client/src/ko/guide/security.md">
# 보안

## 보안 검사 기능

Repomix는 [Secretlint](https://github.com/secretlint/secretlint)를 사용하여 파일 내의 민감한 정보를 감지합니다:
- API 키
- 액세스 토큰
- 인증 정보
- 개인 키
- 환경 변수

## 설정

보안 검사는 기본적으로 활성화되어 있습니다.

명령행에서 비활성화:
```bash
repomix --no-security-check
```

또는 `repomix.config.json`에서 설정:
```json
{
  "security": {
    "enableSecurityCheck": false
  }
}
```

## 보안 조치

1. **바이너리 파일 제외**: 출력에 바이너리 파일이 포함되지 않음
2. **Git 인식**: `.gitignore` 패턴을 준수
3. **자동 감지**: 다음과 같은 일반적인 보안 문제를 스캔:
    - AWS 자격 증명
    - 데이터베이스 연결 문자열
    - 인증 토큰
    - 개인 키

## 보안 검사에서 문제가 발견된 경우

출력 예시:
```bash
🔍 Security Check:
──────────────────
2 suspicious file(s) detected and excluded:
1. config/credentials.json
   - Found AWS access key
2. .env.local
   - Found database password
```

## 모범 사례

1. 공유하기 전에 반드시 출력 내용 검토
2. `.repomixignore`를 사용하여 민감한 경로 제외
3. 보안 검사 기능 활성화 유지
4. 저장소에서 민감한 파일 제거

## 보안 문제 보고

보안 취약점을 발견하셨다면:
1. 공개 이슈를 생성하지 마세요
2. 이메일: koukun0120@gmail.com
3. 또는 [GitHub 보안 권고](https://github.com/yamadashy/repomix/security/advisories/new) 사용
</file>

<file path="website/client/src/ko/index.md">
---
layout: home
title: Repomix
titleTemplate: 코드베이스를 AI 친화적인 형식으로 패키징
aside: false
editLink: false

features:
  - icon: 🤖
    title: AI 최적화
    details: 코드베이스를 AI가 이해하고 처리하기 쉬운 형식으로 변환합니다.

  - icon: ⚙️
    title: Git 인식
    details: .gitignore 파일을 자동으로 인식하고 처리합니다.

  - icon: 🛡️
    title: 보안 중심
    details: Secretlint를 통합하여 민감한 정보를 감지하고 보호합니다.

  - icon: 📊
    title: 토큰 카운팅
    details: LLM 컨텍스트 제한을 위한 파일별 및 전체 토큰 수를 제공합니다.

---

<div class="cli-section">

## 빠른 시작

Repomix를 사용하여 패키지 파일(`repomix-output.txt`)을 생성한 후, 다음과 같은 프롬프트와 함께 AI 어시스턴트에게 전송할 수 있습니다:

```
이 파일은 저장소의 모든 파일을 하나로 통합한 것입니다.
코드를 리팩터링하고 싶으니 먼저 검토해 주세요.
```

AI는 전체 코드베이스를 분석하고 포괄적인 인사이트를 제공할 것입니다:

![Repomix 사용 예시 1](/images/docs/repomix-file-usage-1.png)

구체적인 변경 사항을 논의할 때는 AI가 코드 생성을 도와줍니다. Claude의 Artifacts와 같은 기능을 사용하면 상호 의존적인 여러 파일도 한 번에 받을 수 있습니다:

![Repomix 사용 예시 2](/images/docs/repomix-file-usage-2.png)

즐거운 코딩 되세요! 🚀



## 파워 유저 가이드

고급 사용자를 위해 Repomix는 CLI 인터페이스를 통해 다양한 사용자 정의 옵션을 제공합니다.

### 빠른 시작

프로젝트 디렉토리에서 설치 없이 바로 Repomix를 시작할 수 있습니다:

```bash
npx repomix
```

또는 반복 사용을 위해 전역 설치:

```bash
# npm으로 설치
npm install -g repomix

# 또는 yarn으로 설치
yarn global add repomix

# 또는 Homebrew로 설치 (macOS/Linux)
brew install repomix

# 그런 다음 아무 프로젝트 디렉토리에서 실행
repomix
```

이게 전부입니다! Repomix가 현재 디렉토리에 `repomix-output.txt` 파일을 생성하며, 이 파일에는 AI 친화적인 형식으로 정리된 전체 코드베이스가 포함됩니다.



### 사용법

전체 저장소를 패키징:

```bash
repomix
```

특정 디렉토리를 패키징:

```bash
repomix path/to/directory
```

[glob 패턴](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax)을 사용하여 특정 파일이나 디렉토리를 지정:

```bash
repomix --include "src/**/*.ts,**/*.md"
```

특정 파일이나 디렉토리 제외:

```bash
repomix --ignore "**/*.log,tmp/"
```

원격 저장소 처리:
```bash
# 단축형 사용
npx repomix --remote yamadashy/repomix

# 전체 URL 사용 (브랜치 및 특정 경로 지원)
npx repomix --remote https://github.com/yamadashy/repomix
npx repomix --remote https://github.com/yamadashy/repomix/tree/main

# 커밋 URL 사용
npx repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1
```

새 설정 파일(`repomix.config.json`) 초기화:

```bash
repomix --init
```

생성된 파일은 Claude, ChatGPT, Gemini와 같은 생성형 AI 도구와 함께 사용할 수 있습니다.

#### Docker 사용법

Docker를 사용하여 Repomix를 실행할 수도 있습니다 🐳  
격리된 환경에서 Repomix를 실행하거나 컨테이너를 선호하는 경우에 유용합니다.

기본 사용법(현재 디렉토리):

```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix
```

특정 디렉토리를 처리:
```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory
```

원격 저장소를 처리하고 `output` 디렉토리에 출력:

```bash
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote https://github.com/yamadashy/repomix
```

### 출력 형식

선호하는 출력 형식을 선택하세요:

```bash
# XML 형식(기본값)
repomix --style xml

# Markdown 형식
repomix --style markdown

# 일반 텍스트 형식
repomix --style plain
```

### 사용자 정의

`repomix.config.json`을 생성하여 지속적인 설정을 관리할 수 있습니다:

```json
{
  "output": {
    "style": "markdown",
    "filePath": "custom-output.md",
    "removeComments": true,
    "showLineNumbers": true,
    "topFilesLength": 10
  },
  "ignore": {
    "customPatterns": ["*.test.ts", "docs/**"]
  }
}
```

### 더 많은 예제
::: tip
💡 전체 문서와 더 많은 예제는 [GitHub 저장소](https://github.com/yamadashy/repomix)를 참조하세요!
:::

</div>
</file>

<file path="website/client/src/pt-br/guide/development/index.md">
# Contribuindo para o Repomix

## Início Rápido

```bash
git clone https://github.com/yamadashy/repomix.git
cd repomix
npm install
```

## Comandos de Desenvolvimento

```bash
# Executar CLI
npm run repomix

# Executar testes
npm run test
npm run test-coverage

# Lintar código
npm run lint
```

## Estilo de Código

- Use [Biome](https://biomejs.dev/) para lintar e formatar
- Injeção de dependência para testabilidade
- Mantenha os arquivos com menos de 250 linhas
- Adicione testes para novos recursos

## Diretrizes para Pull Requests

1. Execute todos os testes
2. Passe nas verificações de lint
3. Atualize a documentação
4. Siga o estilo de código existente

## Precisa de Ajuda?

- [Abra uma issue](https://github.com/yamadashy/repomix/issues)
- [Junte-se ao Discord](https://discord.gg/wNYzTwZFku)
</file>

<file path="website/client/src/pt-br/guide/development/setup.md">
# Configuração de Desenvolvimento

## Pré-requisitos

- Node.js ≥ 18.0.0
- Git
- npm

## Desenvolvimento Local

```bash
# Clonar repositório
git clone https://github.com/yamadashy/repomix.git
cd repomix

# Instalar dependências
npm install

# Executar CLI
npm run repomix
```

## Desenvolvimento com Docker

```bash
# Construir imagem
docker build -t repomix .

# Executar container
docker run -v ./:/app -it --rm repomix
```

## Estrutura do Projeto

```
src/
├── cli/          # Implementação da CLI
├── config/       # Manipulação de configuração
├── core/         # Funcionalidade principal
└── shared/       # Utilitários compartilhados
```

## Testando

```bash
# Executar testes
npm run test

# Cobertura de teste
npm run test-coverage

# Linting
npm run lint-biome
npm run lint-ts
npm run lint-secretlint
```

## Processo de Release

1. Atualizar versão
```bash
npm version patch  # ou minor/major
```

2. Executar testes e construir
```bash
npm run test-coverage
npm run build
```

3. Publicar
```bash
npm publish
</file>

<file path="website/client/src/pt-br/guide/tips/best-practices.md">
# Melhores Práticas para Desenvolvimento com IA: Da Minha Experiência

Embora eu ainda não tenha concluído um grande projeto com IA, gostaria de compartilhar minhas experiências até agora no desenvolvimento com IA.

## Abordagem Básica de Desenvolvimento

Ao trabalhar com IA, tentar implementar todas as funcionalidades de uma vez pode levar a problemas inesperados e estagnação do projeto. Portanto, é mais eficaz começar com a funcionalidade principal e construir cada recurso individualmente, garantindo uma implementação sólida antes de prosseguir.

### O Poder do Código Existente

Essa abordagem é eficaz porque a implementação da funcionalidade principal permite materializar seu design ideal e estilo de codificação através de código real. A maneira mais eficaz de comunicar sua visão do projeto é através de código que reflita seus padrões e preferências.

Começando com funcionalidades principais e garantindo que cada componente funcione corretamente antes de avançar, o projeto inteiro mantém sua consistência, tornando mais fácil para a IA gerar código mais apropriado.

## Abordagem Modular

Dividir o código em módulos menores é crucial. Com base em minha experiência, limitar os arquivos a cerca de 250 linhas de código torna mais fácil dar instruções claras à IA e tornar o processo de tentativa e erro mais eficiente. Embora a contagem de tokens seria uma medida mais precisa, a contagem de linhas é mais prática para desenvolvedores humanos, então usamos isso como diretriz.

Essa modularização não se limita apenas à separação de componentes frontend, backend e banco de dados - trata-se de dividir a funcionalidade em um nível muito mais fino. Por exemplo, dentro de uma única função, você pode separar a validação, tratamento de erros e outras funcionalidades específicas em módulos separados.

## Garantia de Qualidade através de Testes

Considero os testes cruciais no desenvolvimento assistido por IA. Os testes servem não apenas como medidas de garantia de qualidade, mas também como documentação que demonstra claramente as intenções do código. Quando você pede à IA para implementar novos recursos, o código de teste existente serve efetivamente como um documento de especificação.

Os testes também são uma excelente ferramenta para validar a correção do código gerado pela IA. Por exemplo, se você fizer a IA implementar nova funcionalidade para um módulo, escrever casos de teste antecipadamente permite uma avaliação objetiva se o código gerado funciona conforme esperado.

## Equilíbrio entre Planejamento e Implementação

Antes de implementar recursos extensos, recomendo discutir primeiro o plano com a IA. Organizar os requisitos e considerar a arquitetura leva a uma implementação mais suave. Uma boa prática é primeiro compilar os requisitos e depois mudar para uma sessão de chat separada para o trabalho de implementação.

## Conclusão

Seguindo essas práticas, você pode aproveitar os pontos fortes da IA enquanto mantém uma base de código consistente e de alta qualidade. Mesmo à medida que seu projeto cresce, cada componente permanece bem definido e gerenciável.
</file>

<file path="website/client/src/pt-br/guide/comment-removal.md">
# Remoção de Comentários

O Repomix pode remover automaticamente os comentários do seu código ao gerar o arquivo de saída. Isso pode ajudar a reduzir o ruído e focar no código real.

## Uso

Para habilitar a remoção de comentários, defina a opção `removeComments` como `true` no seu `repomix.config.json`:

```json
{
  "output": {
    "removeComments": true
  }
}
```

## Linguagens Suportadas

O Repomix suporta a remoção de comentários para uma ampla gama de linguagens de programação, incluindo:

- JavaScript/TypeScript (`//`, `/* */`)
- Python (`#`, `"""`, `'''`)
- Java (`//`, `/* */`)
- C/C++ (`//`, `/* */`)
- HTML (`<!-- -->`)
- CSS (`/* */`)
- E muitas outras...

## Exemplo

Dado o seguinte código JavaScript:

```javascript
// Este é um comentário de linha única
function test() {
  /* Este é um
     comentário de várias linhas */
  return true;
}
```

Com a remoção de comentários habilitada, a saída será:

```javascript
function test() {
  return true;
}
```

## Notas

- A remoção de comentários é realizada antes de outras etapas de processamento, como a adição de números de linha.
- Alguns comentários, como os comentários JSDoc, podem ser preservados dependendo da linguagem e do contexto.
</file>

<file path="website/client/src/pt-br/guide/custom-instructions.md">
# Instruções Personalizadas

O Repomix permite que você forneça instruções personalizadas que serão incluídas no arquivo de saída. Isso pode ser útil para adicionar contexto ou diretrizes específicas para sistemas de IA que processam o repositório.

## Uso

Para incluir uma instrução personalizada, crie um arquivo markdown (por exemplo, `repomix-instruction.md`) na raiz do seu repositório. Em seguida, especifique o caminho para este arquivo no seu `repomix.config.json`:

```json
{
  "output": {
    "instructionFilePath": "repomix-instruction.md"
  }
}
```

O conteúdo deste arquivo será incluído na saída sob a seção "Instruction".

## Exemplo

```markdown
# Instruções do Repositório

Este repositório contém o código-fonte da ferramenta Repomix. Por favor, siga estas diretrizes ao analisar o código:

1. Concentre-se na funcionalidade principal no diretório `src/core`.
2. Preste atenção especial às verificações de segurança em `src/core/security`.
3. Ignore quaisquer arquivos no diretório `tests`.
```

Isso resultará na seguinte seção na saída:

```xml
<instruction>
# Instruções do Repositório

Este repositório contém o código-fonte da ferramenta Repomix. Por favor, siga estas diretrizes ao analisar o código:

1. Concentre-se na funcionalidade principal no diretório `src/core`.
2. Preste atenção especial às verificações de segurança em `src/core/security`.
3. Ignore quaisquer arquivos no diretório `tests`.
</instruction>
</file>

<file path="website/client/src/pt-br/guide/installation.md">
# Instalação

## Usando npx (Nenhuma Instalação Necessária)

```bash
npx repomix
```

## Instalação Global

### npm
```bash
npm install -g repomix
```

### Yarn
```bash
yarn global add repomix
```

### Homebrew (macOS/Linux)
```bash
brew install repomix
```

## Instalação via Docker

Baixe e execute a imagem Docker:

```bash
# Diretório atual
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix

# Diretório específico
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory

# Repositório remoto
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote yamadashy/repomix
```

## Extensão VSCode

Execute o Repomix diretamente no VSCode com a extensão [Repomix Runner](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner) mantida pela comunidade.

Recursos:
- Empacote qualquer pasta com apenas alguns cliques
- Escolha entre modo arquivo ou conteúdo para copiar
- Limpeza automática de arquivos de saída
- Funciona com repomix.config.json

Instale através do [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner).

## Requisitos de Sistema

- Node.js: ≥ 18.0.0
- Git: Necessário para processamento de repositório remoto

## Verificação

Após a instalação, verifique se o Repomix está funcionando:

```bash
repomix --version
repomix --help
```
</file>

<file path="website/client/src/pt-br/guide/prompt-examples.md">
# Exemplos de Prompts

## Revisão de Código

### Revisão de Arquitetura
```
Analise a arquitetura desta base de código:
1. Avalie a estrutura geral e os padrões
2. Identifique possíveis problemas de arquitetura
3. Sugira melhorias para escalabilidade
4. Observe áreas que seguem as melhores práticas

Concentre-se na manutenibilidade e modularidade.
```

### Revisão de Segurança
```
Realize uma revisão de segurança desta base de código:
1. Identifique possíveis vulnerabilidades de segurança
2. Verifique se há antipadrões de segurança comuns
3. Revise o tratamento de erros e a validação de entrada
4. Avalie a segurança das dependências

Forneça exemplos específicos e etapas de correção.
```

### Revisão de Desempenho
```
Revise a base de código para desempenho:
1. Identifique gargalos de desempenho
2. Verifique a utilização de recursos
3. Revise a eficiência algorítmica
4. Avalie as estratégias de cache

Inclua recomendações específicas de otimização.
```

## Geração de Documentação

### Documentação de API
```
Gere documentação abrangente da API:
1. Liste e descreva todos os endpoints públicos
2. Documente os formatos de solicitação/resposta
3. Inclua exemplos de uso
4. Observe quaisquer limitações ou restrições
```

### Guia do Desenvolvedor
```
Crie um guia do desenvolvedor cobrindo:
1. Instruções de configuração
2. Visão geral da estrutura do projeto
3. Fluxo de trabalho de desenvolvimento
4. Abordagem de teste
5. Etapas comuns de solução de problemas
```

### Documentação de Arquitetura
```
Documente a arquitetura do sistema:
1. Visão geral de alto nível
2. Interações de componentes
3. Diagramas de fluxo de dados
4. Decisões de design e justificativa
5. Restrições e limitações do sistema
```

## Análise e Melhoria

### Análise de Dependências
```
Analise as dependências do projeto:
1. Identifique pacotes desatualizados
2. Verifique se há vulnerabilidades de segurança
3. Sugira pacotes alternativos
4. Revise os padrões de uso de dependências

Inclua recomendações específicas de atualização.
```

### Cobertura de Testes
```
Revise a cobertura de testes:
1. Identifique componentes não testados
2. Sugira casos de teste adicionais
3. Revise a qualidade do teste
4. Recomende estratégias de teste
```

### Qualidade do Código
```
Avalie a qualidade do código e sugira melhorias:
1. Revise as convenções de nomenclatura
2. Verifique a organização do código
3. Avalie o tratamento de erros
4. Revise as práticas de comentários

Forneça exemplos específicos de padrões bons e problemáticos.
```

## Dicas para Obter Melhores Resultados

1. **Seja Específico**: Inclua objetivos claros e critérios de avaliação
2. **Defina o Contexto**: Especifique sua função e o nível de especialização necessário
3. **Formato da Solicitação**: Defina como você deseja que a resposta seja estruturada
4. **Priorize**: Indique quais aspectos são mais importantes

## Notas Específicas do Modelo

### Claude
- Use o formato de saída XML
- Coloque instruções importantes no final
- Especifique a estrutura da resposta

### ChatGPT
- Use o formato Markdown
- Divida grandes bases de código em seções
- Inclua prompts de função do sistema

### Gemini
- Funciona com todos os formatos
- Concentre-se em áreas específicas por solicitação
- Use análise passo a passo
</file>

<file path="website/client/src/pt-br/guide/remote-repository-processing.md">
# Processamento de Repositório Remoto

## Uso Básico

Processar repositórios públicos:
```bash
# Usando URL completo
repomix --remote https://github.com/user/repo

# Usando atalho do GitHub
repomix --remote user/repo
```

## Seleção de Branch e Commit

```bash
# Branch específico
repomix --remote user/repo --remote-branch main

# Tag
repomix --remote user/repo --remote-branch v1.0.0

# Hash do commit
repomix --remote user/repo --remote-branch 935b695
```

## Requisitos

- Git deve estar instalado
- Conexão com a internet
- Acesso de leitura ao repositório

## Controle de Saída

```bash
# Local de saída personalizado
repomix --remote user/repo -o custom-output.xml

# Com formato XML
repomix --remote user/repo --style xml

# Remover comentários
repomix --remote user/repo --remove-comments
```

## Uso com Docker

```bash
# Processar e enviar para o diretório atual
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo

# Enviar para um diretório específico
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo
```

## Problemas Comuns

### Problemas de Acesso
- Certifique-se de que o repositório é público
- Verifique a instalação do Git
- Verifique a conexão com a internet

### Repositórios Grandes
- Use `--include` para selecionar caminhos específicos
- Habilite `--remove-comments`
- Processe branches separadamente
</file>

<file path="website/client/src/pt-br/guide/security.md">
# Segurança

## Recurso de Verificação de Segurança

O Repomix usa o [Secretlint](https://github.com/secretlint/secretlint) para detectar informações confidenciais em seus arquivos:
- Chaves de API
- Tokens de acesso
- Credenciais
- Chaves privadas
- Variáveis de ambiente

## Configuração

As verificações de segurança são habilitadas por padrão.

Desativar via CLI:
```bash
repomix --no-security-check
```

Ou em `repomix.config.json`:
```json
{
  "security": {
    "enableSecurityCheck": false
  }
}
```

## Medidas de Segurança

1. **Exclusão de Arquivos Binários**: Arquivos binários não são incluídos na saída
2. **Compatível com Git**: Respeita os padrões do `.gitignore`
3. **Detecção Automatizada**: Verifica problemas de segurança comuns:
    - Credenciais da AWS
    - Strings de conexão de banco de dados
    - Tokens de autenticação
    - Chaves privadas

## Quando a Verificação de Segurança Encontra Problemas

Exemplo de saída:
```bash
🔍 Verificação de Segurança:
──────────────────
2 arquivo(s) suspeito(s) detectados e excluídos:
1. config/credentials.json
   - Chave de acesso da AWS encontrada
2. .env.local
   - Senha do banco de dados encontrada
```

## Melhores Práticas

1. Sempre revise a saída antes de compartilhar
2. Use `.repomixignore` para caminhos confidenciais
3. Mantenha as verificações de segurança habilitadas
4. Remova arquivos confidenciais do repositório

## Reportando Problemas de Segurança

Encontrou uma vulnerabilidade de segurança? Por favor:
1. Não abra uma issue pública
2. Envie um e-mail para: koukun0120@gmail.com
3. Ou use [Avisos de Segurança do GitHub](https://github.com/yamadashy/repomix/security/advisories/new)
</file>

<file path="website/client/src/pt-br/index.md">
---
layout: home
title: Repomix
titleTemplate: Compacte seu código-fonte em formatos amigáveis para IA
aside: false
editLink: false

features:
  - icon: 🤖
    title: Otimizado para IA
    details: Formata seu código-fonte de uma maneira fácil para a IA entender e processar.

  - icon: ⚙️
    title: Consciente do Git
    details: Respeita automaticamente seus arquivos .gitignore.

  - icon: 🛡️
    title: Focado na Segurança
    details: Incorpora o Secretlint para verificações de segurança robustas para detectar e prevenir a inclusão de informações confidenciais.

  - icon: 📊
    title: Contagem de Tokens
    details: Fornece contagens de tokens para cada arquivo e para todo o repositório, útil para limites de contexto de LLM.

---

<div class="cli-section">

## Início Rápido

Depois de gerar um arquivo compactado (`repomix-output.txt`) usando o Repomix, você pode enviá-lo para um assistente de IA com um prompt como:

```
Este arquivo contém todos os arquivos do repositório combinados em um.
Eu quero refatorar o código, então, por favor, revise-o primeiro.
```

A IA analisará todo o seu código-fonte e fornecerá insights abrangentes:

![Repomix File Usage 1](/images/docs/repomix-file-usage-1.png)

Ao discutir mudanças específicas, a IA pode ajudar a gerar código. Com recursos como o Artifacts do Claude, você pode até receber vários arquivos interdependentes:

![Repomix File Usage 2](/images/docs/repomix-file-usage-2.png)

Feliz codificação! 🚀

## Guia do Usuário Avançado

Para usuários avançados que precisam de mais controle, o Repomix oferece extensas opções de personalização através de sua interface CLI.

### Início Rápido

Você pode experimentar o Repomix instantaneamente no diretório do seu projeto sem instalação:

```bash
npx repomix
```

Ou instale globalmente para uso repetido:

```bash
# Instalar usando npm
npm install -g repomix

# Alternativamente usando yarn
yarn global add repomix

# Alternativamente usando Homebrew (macOS/Linux)
brew install repomix

# Então execute em qualquer diretório de projeto
repomix
```

É isso! O Repomix irá gerar um arquivo `repomix-output.txt` no seu diretório atual, contendo todo o seu repositório em um formato amigável para IA.

### Uso

Para compactar todo o seu repositório:

```bash
repomix
```

Para compactar um diretório específico:

```bash
repomix path/to/directory
```

Para compactar arquivos ou diretórios específicos usando [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax):

```bash
repomix --include "src/**/*.ts,**/*.md"
```

Para excluir arquivos ou diretórios específicos:

```bash
repomix --ignore "**/*.log,tmp/"
```

Para compactar um repositório remoto:
```bash
# Usando formato abreviado
npx repomix --remote yamadashy/repomix

# Usando URL completa (suporta branches e caminhos específicos)
npx repomix --remote https://github.com/yamadashy/repomix
npx repomix --remote https://github.com/yamadashy/repomix/tree/main

# Usando URL do commit
npx repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1
```

Para inicializar um novo arquivo de configuração (`repomix.config.json`):

```bash
repomix --init
```

Depois de gerar o arquivo compactado, você pode usá-lo com ferramentas de IA Generativa como Claude, ChatGPT e Gemini.

#### Uso do Docker

Você também pode executar o Repomix usando o Docker 🐳
Isso é útil se você quiser executar o Repomix em um ambiente isolado ou preferir usar contêineres.

Uso básico (diretório atual):

```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix
```

Para compactar um diretório específico:
```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory
```

Processar um repositório remoto e enviar para um diretório `output`:

```bash
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote https://github.com/yamadashy/repomix
```

### Formatos de Saída

Escolha seu formato de saída preferido:

```bash
# Formato XML (padrão)
repomix --style xml

# Formato Markdown
repomix --style markdown

# Formato de texto simples
repomix --style plain
```

### Customização

Crie um `repomix.config.json` para configurações persistentes:

```json
{
  "output": {
    "style": "markdown",
    "filePath": "custom-output.md",
    "removeComments": true,
    "showLineNumbers": true,
    "topFilesLength": 10
  },
  "ignore": {
    "customPatterns": ["*.test.ts", "docs/**"]
  }
}
```

### Mais Exemplos
::: tip
💡 Confira nosso [repositório GitHub](https://github.com/yamadashy/repomix) para documentação completa e mais exemplos!
:::

</div>
</file>

<file path="website/client/src/public/images/repomix-logo.svg">
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="96.259 93.171 300 300" xmlns:bx="https://boxy-svg.com">
  <g id="group-1" transform="matrix(1.160932, 0, 0, 1.160932, 97.635941, 94.725143)" style="">
    <path style="fill-rule: nonzero; fill-opacity: 1; stroke-width: 2; fill: rgb(234, 127, 58);" d="M 128.03 -1.486 L 21.879 65.349 L 21.848 190.25 L 127.979 256.927 L 234.2 190.27 L 234.197 65.463 L 128.03 -1.486 Z M 208.832 70.323 L 127.984 121.129 L 47.173 70.323 L 128.144 19.57 L 208.832 70.323 Z M 39.669 86.367 L 119.188 136.415 L 119.255 230.529 L 39.637 180.386 L 39.669 86.367 Z M 136.896 230.506 L 136.887 136.575 L 216.469 86.192 L 216.417 180.46 L 136.896 230.506 Z M 136.622 230.849"/>
  </g>
</svg>
</file>

<file path="website/client/src/zh-cn/guide/development/index.md">
# 参与 Repomix 开发

## 快速开始

```bash
git clone https://github.com/yamadashy/repomix.git
cd repomix
npm install
```

## 开发命令

```bash
# 运行 CLI
npm run repomix

# 运行测试
npm run test
npm run test-coverage

# 代码检查
npm run lint
```

## 代码风格

- 使用 [Biome](https://biomejs.dev/) 进行代码检查和格式化
- 使用依赖注入以提高可测试性
- 保持文件不超过 250 行
- 为新功能添加测试用例

## Pull Request 提交指南

1. 运行所有测试
2. 通过代码检查
3. 更新文档
4. 遵循现有代码风格

## 需要帮助？

- [提交 Issue](https://github.com/yamadashy/repomix/issues)
- [加入 Discord](https://discord.gg/wNYzTwZFku)
</file>

<file path="website/client/src/zh-cn/guide/development/setup.md">
# 开发环境搭建

## 前提条件

- Node.js ≥ 18.0.0
- Git
- npm
- pnpm（推荐）

## 本地开发

### 克隆仓库

```bash
git clone https://github.com/yamadashy/repomix.git
cd repomix
```

### 安装依赖

使用 pnpm（推荐）：
```bash
pnpm install
```

使用 npm：
```bash
npm install
```

### 启动开发服务器

```bash
# 运行 CLI
pnpm run repomix

# 启动文档网站开发服务器
pnpm run website:dev
```

## Docker 开发环境

```bash
# 构建镜像
docker build -t repomix .

# 运行容器
docker run -v ./:/app -it --rm repomix
```

## 项目结构

```text
.
├── src/                # 源代码
│   ├── cli/           # CLI 实现
│   ├── config/        # 配置处理
│   ├── core/          # 核心功能
│   └── shared/        # 共享工具
├── tests/             # 测试文件
├── website/           # 文档网站
└── package.json       # 项目依赖
```

## 测试

```bash
# 运行所有测试
pnpm run test

# 生成测试覆盖率报告
pnpm run test:coverage

# 运行特定测试
pnpm run test -- tests/cli
```

## 代码质量

```bash
# 运行代码检查
pnpm run lint

# 自动修复代码问题
pnpm run lint:fix

# 类型检查
pnpm run typecheck
```

## 文档

文档位于 `website/` 目录。要在本地开发文档网站：

```bash
# 启动开发服务器
pnpm run website:dev

# 构建生产版本
pnpm run website:build
```

## 故障排除

### 常见问题

1. **Node.js 版本**
   - 确保使用 Node.js ≥ 18.0.0
   - 使用 `node --version` 检查版本

2. **依赖问题**
   - 删除 `node_modules` 和锁定文件
   - 重新运行 `pnpm install`

3. **构建错误**
   - 运行 `pnpm run clean`
   - 重新构建项目 `pnpm run build`

### 支持

如果遇到问题：
- 创建 [GitHub Issue](https://github.com/yamadashy/repomix/issues)
- 加入我们的 [Discord 服务器](https://discord.gg/wNYzTwZFku)
</file>

<file path="website/client/src/zh-cn/guide/tips/best-practices.md">
# AI 辅助开发最佳实践：从实践经验谈起

虽然我还没有完成一个大型的 AI 辅助开发项目，但我想分享一下到目前为止从与 AI 合作开发中学到的经验。

## 基本开发方法

在与 AI 合作时，试图一次性实现所有功能可能会导致意外问题和项目停滞。因此，从核心功能开始，一步一步稳扎稳打地构建每个功能是更有效的方法。

### 现有代码的重要性

这种方法之所以有效，是因为通过核心功能的实现，你可以将你理想中的设计和编码风格具体化为实际代码。向 AI 传达项目愿景的最有效方式就是通过反映你的标准和偏好的代码本身。

从核心功能开始，确保每个功能在进入下一个功能之前都能正常工作，这样整个项目就能保持一致性，使 AI 更容易生成更合适的代码。

## 模块化方法

将代码分解成更小的模块至关重要。根据经验，将文件限制在 250 行左右的代码使得向 AI 提供清晰的指示更容易，并使试错过程更有效。虽然令牌计数会是更准确的指标，但对人类开发者来说，行数更容易判断，所以我们使用行数作为参考。

这不仅仅是关于前端、后端和数据库等大单元的分离，而是关于更精细层面的功能划分。例如，在一个功能内部，也要将验证、错误处理等具体功能分离成独立模块。

当然，大单元的分离也很重要，逐步实施模块化方法不仅让指令更清晰，也让 AI 能生成更合适的代码。这种方法不仅对 AI，对人类开发者来说也是有效的。

## 通过测试确保质量

我认为测试在 AI 辅助开发中尤为重要。测试不仅作为质量保证手段，还作为清晰展示代码意图的文档。当要求 AI 实现新功能时，现有的测试代码有效地充当了规范文档。

测试也是验证 AI 生成代码正确性的绝佳工具。例如，当让 AI 为某个模块实现新功能时，预先编写测试用例可以客观评估生成的代码是否符合预期。这与测试驱动开发（TDD）的理念高度契合，在与 AI 协作时特别有效。

## 规划与实现的平衡

在实现大规模功能之前，建议先与 AI 讨论计划。整理需求并考虑架构可以使后续实现更顺畅。先整理需求，然后在新的对话中进行实现是个好方法。

此外，AI 的输出必须经过人工审查，并在必要时进行调整。虽然 AI 输出的质量通常处于中等水平，但与从头开始编写代码相比，仍然可以提高开发速度。

## 结语

通过实践这些方法，你可以充分发挥 AI 的优势，同时构建一个连贯的、高质量的代码库。即使项目规模增长，每个部分都能保持清晰定义和易于管理的状态。
</file>

<file path="website/client/src/zh-cn/guide/comment-removal.md">
# 注释移除

Repomix 可以在生成输出文件时自动移除代码中的注释。这有助于减少干扰，让代码更加简洁。

## 使用方法

要启用注释移除，在 `repomix.config.json` 中将 `removeComments` 选项设置为 `true`：

```json
{
  "output": {
    "removeComments": true
  }
}
```

## 支持的语言

Repomix 支持移除多种编程语言的注释，包括：

- JavaScript/TypeScript (`//`, `/* */`)
- Python (`#`, `"""`, `'''`)
- Java (`//`, `/* */`)
- C/C++ (`//`, `/* */`)
- HTML (`<!-- -->`)
- CSS (`/* */`)
- 以及更多语言...

## 示例

以下是 JavaScript 代码示例：

```javascript
// 这是单行注释
function test() {
  /* 这是
     多行注释 */
  return true;
}
```

启用注释移除后，输出将变为：

```javascript
function test() {
  return true;
}
```

## 注意事项

- 注释移除在其他处理步骤（如行号添加）之前执行
- 某些注释，例如 JSDoc 注释，可能会根据语言和上下文保留
- 如果你需要保留某些重要注释，请考虑使用其他方式记录这些信息，例如使用自定义指令

## 建议用法

1. **选择性使用**：
   - 对于需要向 AI 展示实现细节的代码，保留注释
   - 对于主要关注代码结构的分析，移除注释

2. **配合其他功能**：
   - 与 `--remove-empty-lines` 选项组合使用，获得更简洁的输出
   - 使用自定义指令提供额外的上下文信息

3. **性能考虑**：
   - 移除注释可以减少输出文件大小
   - 对于大型代码库特别有用
</file>

<file path="website/client/src/zh-cn/guide/custom-instructions.md">
# 自定义指令

Repomix 允许你提供自定义指令，这些指令将被包含在输出文件中。这对于为处理代码库的 AI 系统提供上下文或特定指导非常有用。

## 使用方法

要包含自定义指令，请在仓库根目录创建一个 markdown 文件（例如 `repomix-instruction.md`）。然后，在 `repomix.config.json` 中指定该文件的路径：

```json
{
  "output": {
    "instructionFilePath": "repomix-instruction.md"
  }
}
```

该文件的内容将在输出中的"Instruction"部分中显示。

## 示例

```markdown
# 仓库指令

这个仓库包含了 Repomix 工具的源代码。在分析代码时请遵循以下指导原则：

1. 重点关注 `src/core` 目录中的核心功能
2. 特别注意 `src/core/security` 中的安全检查
3. 忽略 `tests` 目录中的文件

## 代码规范
- 遵循 TypeScript 最佳实践
- 确保所有公共 API 都有适当的文档
- 使用依赖注入模式以便于测试

## 安全考虑
- 确保所有用户输入都经过适当验证
- 避免在日志中记录敏感信息
- 使用安全的依赖版本
```

这将在输出中生成以下部分：

```xml
<instruction>
# 仓库指令

这个仓库包含了 Repomix 工具的源代码。在分析代码时请遵循以下指导原则：

1. 重点关注 `src/core` 目录中的核心功能
2. 特别注意 `src/core/security` 中的安全检查
3. 忽略 `tests` 目录中的文件

## 代码规范
- 遵循 TypeScript 最佳实践
- 确保所有公共 API 都有适当的文档
- 使用依赖注入模式以便于测试

## 安全考虑
- 确保所有用户输入都经过适当验证
- 避免在日志中记录敏感信息
- 使用安全的依赖版本
</instruction>
```

## 最佳实践

1. **保持简洁明确**：指令应该简短但详细
2. **提供具体示例**：在适当的情况下添加代码示例
3. **设置优先级**：将最重要的指令放在前面
4. **包含上下文**：提供项目背景和重要考虑因素
5. **结构化内容**：使用标题和列表使指令易于阅读

## 注意事项

- 避免在指令中包含敏感信息
- 定期更新指令以反映项目的变化
- 确保指令与项目的其他文档保持一致
- 使用清晰的层次结构组织内容
</file>

<file path="website/client/src/zh-cn/guide/installation.md">
# 安装

## 使用 npx（无需安装）

```bash
npx repomix
```

## 全局安装

### npm 安装
```bash
npm install -g repomix
```

### Yarn 安装
```bash
yarn global add repomix
```

### Homebrew 安装（macOS/Linux）
```bash
brew install repomix
```

## Docker 安装

使用 Docker 是最便捷的方式之一，可以避免环境配置问题。以下是具体步骤：

```bash
# 处理当前目录
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix

# 处理指定目录
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory

# 处理远程仓库
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote yamadashy/repomix
```

## VSCode 扩展

通过社区维护的 [Repomix Runner](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner) 扩展，您可以直接在 VSCode 中运行 Repomix。

功能：
- 只需点击几下即可打包任何文件夹
- 可选择文件或内容模式进行复制
- 自动清理输出文件
- 支持 repomix.config.json

从 [VSCode 应用商店](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner)安装。

## 系统要求

- Node.js: ≥ 18.0.0
- Git: 处理远程仓库时需要

## 验证安装

安装完成后，可以通过以下命令验证 Repomix 是否正常工作：

```bash
repomix --version
repomix --help
```
</file>

<file path="website/client/src/zh-cn/guide/prompt-examples.md">
# 提示示例

## 代码评审

### 架构评审
```
分析此代码库的架构：
1. 评估整体结构和模式
2. 识别潜在的架构问题
3. 提出改进可扩展性的建议
4. 标注遵循最佳实践的部分

重点关注可维护性和模块化。
```

### 安全性评审
```
对此代码库进行安全性评审：
1. 识别潜在的安全漏洞
2. 检查常见的安全反模式
3. 评审错误处理和输入验证
4. 评估依赖项的安全性

请提供具体示例和修复步骤。
```

### 性能评审
```
从性能角度评审代码库：
1. 识别性能瓶颈
2. 检查资源使用情况
3. 评审算法效率
4. 评估缓存策略

包含具体的优化建议。
```

## 文档生成

### API 文档
```
生成全面的 API 文档：
1. 列出并描述所有公共端点
2. 记录请求/响应格式
3. 包含使用示例
4. 说明限制和约束
```

### 开发者指南
```
创建包含以下内容的开发者指南：
1. 环境搭建说明
2. 项目结构概览
3. 开发工作流程
4. 测试方法
5. 常见问题排查步骤
```

### 架构文档
```
记录系统架构：
1. 高层概述
2. 组件交互
3. 数据流程图
4. 设计决策及理由
5. 系统限制和约束
```

## 分析与改进

### 依赖项分析
```
分析项目依赖：
1. 识别过时的包
2. 检查安全漏洞
3. 建议替代包
4. 评审依赖使用模式

包含具体的升级建议。
```

### 测试覆盖率
```
评审测试覆盖率：
1. 识别未测试的组件
2. 建议额外的测试用例
3. 评审测试质量
4. 推荐测试策略
```

### 代码质量
```
评估代码质量并提出改进建议：
1. 评审命名规范
2. 检查代码组织
3. 评估错误处理
4. 评审注释实践

提供具体的良好和问题模式示例。
```

## 获得更好结果的技巧

1. **明确具体**：包含清晰的目标和评估标准
2. **设置上下文**：说明你的角色和所需的专业水平
3. **请求格式**：定义期望的响应结构
4. **设置优先级**：指出哪些方面最重要

## 模型特定说明

### Claude
- 使用 XML 输出格式
- 将重要指令放在最后
- 指定响应结构

### ChatGPT
- 使用 Markdown 格式
- 将大型代码库分成小节
- 包含系统角色提示

### Gemini
- 适用于所有格式
- 每次请求专注于特定领域
- 使用逐步分析
</file>

<file path="website/client/src/zh-cn/guide/remote-repository-processing.md">
# 远程仓库处理

## 基本用法

处理公共仓库：
```bash
# 使用完整 URL
repomix --remote https://github.com/user/repo

# 使用 GitHub 简写
repomix --remote user/repo
```

## 分支和提交选择

```bash
# 指定分支
repomix --remote user/repo --remote-branch main

# 指定标签
repomix --remote user/repo --remote-branch v1.0.0

# 指定提交哈希
repomix --remote user/repo --remote-branch 935b695
```

## 系统要求

- 必须安装 Git
- 需要网络连接
- 需要仓库的读取权限

## 输出控制

```bash
# 自定义输出位置
repomix --remote user/repo -o custom-output.xml

# 使用 XML 格式
repomix --remote user/repo --style xml

# 移除注释
repomix --remote user/repo --remove-comments
```

## Docker 使用方法

```bash
# 在当前目录处理并输出
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo

# 输出到指定目录
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo
```

## 常见问题

### 访问问题
- 确保仓库是公开的
- 检查 Git 是否已安装
- 验证网络连接

### 大型仓库处理
- 使用 `--include` 选择特定路径
- 启用 `--remove-comments`
- 分开处理不同分支
</file>

<file path="website/client/src/zh-cn/guide/security.md">
# 安全性

## 安全检查功能

Repomix 使用 [Secretlint](https://github.com/secretlint/secretlint) 检测文件中的敏感信息：
- API 密钥
- 访问令牌
- 认证凭证
- 私钥
- 环境变量

## 配置

安全检查默认启用。

通过命令行禁用：
```bash
repomix --no-security-check
```

或在 `repomix.config.json` 中配置：
```json
{
  "security": {
    "enableSecurityCheck": false
  }
}
```

## 安全措施

1. **二进制文件排除**：输出中不包含二进制文件
2. **Git 感知**：遵循 `.gitignore` 模式
3. **自动检测**：扫描常见安全问题：
    - AWS 凭证
    - 数据库连接字符串
    - 认证令牌
    - 私钥

## 安全检查发现问题时

输出示例：
```bash
🔍 Security Check:
──────────────────
2 suspicious file(s) detected and excluded:
1. config/credentials.json
   - Found AWS access key
2. .env.local
   - Found database password
```

## 最佳实践

1. 分享前务必检查输出内容
2. 使用 `.repomixignore` 排除敏感路径
3. 保持安全检查功能启用
4. 从仓库中移除敏感文件

## 报告安全问题

如果发现安全漏洞，请：
1. 不要创建公开的 Issue
2. 发送邮件至：koukun0120@gmail.com
3. 或使用 [GitHub 安全公告](https://github.com/yamadashy/repomix/security/advisories/new)
</file>

<file path="website/client/src/zh-cn/index.md">
---
layout: home
title: Repomix
titleTemplate: 将代码库打包成AI友好的格式
aside: false
editLink: false

features:
  - icon: 🤖
    title: AI 优化
    details: 以 AI 易于理解和处理的方式格式化代码库。

  - icon: ⚙️
    title: Git 感知
    details: 自动识别并尊重您的 .gitignore 文件。

  - icon: 🛡️
    title: 注重安全
    details: 集成 Secretlint 进行强大的安全检查，检测并防止敏感信息的泄露。

  - icon: 📊
    title: 令牌计数
    details: 提供每个文件和整个代码库的令牌计数，便于控制 LLM 上下文限制。

---

<div class="cli-section">

## 快速开始

使用 Repomix 生成打包文件（`repomix-output.txt`）后，您可以将其发送给 AI 助手，并附上这样的提示：

```
此文件包含了仓库中所有文件的合并内容。
我想重构代码，请先帮我审查一下。
```

AI 将分析您的整个代码库并提供全面的见解：

![Repomix 使用示例1](/images/docs/repomix-file-usage-1.png)

在讨论具体修改时，AI 可以帮助生成代码。通过像 Claude 的 Artifacts 这样的功能，您甚至可以一次性接收多个相互依赖的文件：

![Repomix 使用示例2](/images/docs/repomix-file-usage-2.png)

祝您编码愉快！🚀



## 进阶使用指南

对于需要更多控制的高级用户，Repomix 通过其 CLI 界面提供了广泛的自定义选项。

### 快速上手

您可以在项目目录中无需安装即可立即尝试 Repomix：

```bash
npx repomix
```

或者全局安装以便重复使用：

```bash
# 使用 npm 安装
npm install -g repomix

# 或使用 yarn 安装
yarn global add repomix

# 或使用 Homebrew 安装（macOS/Linux）
brew install repomix

# 然后在任意项目目录中运行
repomix
```

就是这么简单！Repomix 将在您的当前目录中生成一个 `repomix-output.txt` 文件，其中包含了以 AI 友好格式整理的整个代码库。



### 基本用法

打包整个代码库：

```bash
repomix
```

打包特定目录：

```bash
repomix path/to/directory
```

使用 [glob 模式](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax)打包特定文件：

```bash
repomix --include "src/**/*.ts,**/*.md"
```

排除特定文件：

```bash
repomix --ignore "**/*.log,tmp/"
```

处理远程仓库：
```bash
# 使用简写格式
npx repomix --remote yamadashy/repomix

# 使用完整 URL（支持分支和特定路径）
npx repomix --remote https://github.com/yamadashy/repomix
npx repomix --remote https://github.com/yamadashy/repomix/tree/main

# 使用提交 URL
npx repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1
```

初始化配置文件（`repomix.config.json`）：

```bash
repomix --init
```

生成打包文件后，您可以将其用于 Claude、ChatGPT、Gemini 等生成式 AI 工具。

#### Docker 使用方法

您也可以使用 Docker 运行 Repomix 🐳  
如果您想在隔离环境中运行 Repomix 或更偏好使用容器，这是一个很好的选择。

基本用法（当前目录）：

```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix
```

打包特定目录：
```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory
```

处理远程仓库并输出到 `output` 目录：

```bash
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote https://github.com/yamadashy/repomix
```

### 输出格式

选择您偏好的输出格式：

```bash
# XML 格式（默认）
repomix --style xml

# Markdown 格式
repomix --style markdown

# 纯文本格式
repomix --style plain
```

### 自定义设置

创建 `repomix.config.json` 进行持久化设置：

```json
{
  "output": {
    "style": "markdown",
    "filePath": "custom-output.md",
    "removeComments": true,
    "showLineNumbers": true,
    "topFilesLength": 10
  },
  "ignore": {
    "customPatterns": ["*.test.ts", "docs/**"]
  }
}
```

### 更多示例
::: tip
💡 查看我们的 [GitHub 仓库](https://github.com/yamadashy/repomix)获取完整文档和更多示例！
:::

</div>
</file>

<file path="website/client/.gitignore">
.vitepress/cache
.vitepress/dist
node_modules
stats.html
</file>

<file path="website/client/.tool-versions">
nodejs 23.6.0
</file>

<file path="website/client/Dockerfile">
FROM node:23-alpine

RUN apk add --no-cache git

WORKDIR /app

COPY package*.json ./

RUN npm i

COPY . .

EXPOSE 5173

CMD ["npm", "run", "docs:dev"]
</file>

<file path="website/client/tsconfig.json">
{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "lib": ["ESNext", "DOM"],
    "skipLibCheck": true,
    "noEmit": true,
    "baseUrl": ".",
    "types": ["vite/client", "vitepress/client"]
  },
  "include": [".vitepress/**/*.ts", ".vitepress/**/*.d.ts", ".vitepress/**/*.tsx", ".vitepress/**/*.vue"],
  "exclude": ["node_modules", "dist"]
}
</file>

<file path="website/client/tsconfig.node.json">
{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true
  },
  "include": [".vitepress/config.ts", ".vitepress/config/**/*", ".vitepress/theme/**/*"]
}
</file>

<file path="website/server/src/utils/cache.ts">
import pako from 'pako';
import type { PackOptions } from '../types.js';

interface CacheEntry<T> {
  value: Uint8Array; // Compressed data
  timestamp: number;
}

export class RequestCache<T> {
  private cache: Map<string, CacheEntry<T>> = new Map();
  private readonly ttl: number;

  constructor(ttlInSeconds = 60) {
    this.ttl = ttlInSeconds * 1000;

    // Set up periodic cache cleanup
    setInterval(() => this.cleanup(), ttlInSeconds * 1000);
  }

  get(key: string): T | undefined {
    const entry = this.cache.get(key);
    if (!entry) {
      return undefined;
    }

    const now = Date.now();
    if (now - entry.timestamp > this.ttl) {
      this.cache.delete(key);
      return undefined;
    }

    try {
      // Decompress and return the data
      const decompressedData = pako.inflate(entry.value, { to: 'string' });
      return JSON.parse(decompressedData);
    } catch (error) {
      console.error('Error decompressing cache entry:', error);
      this.cache.delete(key);
      return undefined;
    }
  }

  set(key: string, value: T): void {
    try {
      // Convert data to JSON string and compress
      const jsonString = JSON.stringify(value);
      const compressedData = pako.deflate(jsonString);

      this.cache.set(key, {
        value: compressedData,
        timestamp: Date.now(),
      });
    } catch (error) {
      console.error('Error compressing cache entry:', error);
    }
  }

  // Remove expired entries from cache
  cleanup(): void {
    const now = Date.now();
    for (const [key, entry] of this.cache.entries()) {
      if (now - entry.timestamp > this.ttl) {
        this.cache.delete(key);
      }
    }
  }
}

// Cache key generation utility
export function generateCacheKey(
  identifier: string,
  format: string,
  options: PackOptions,
  type: 'url' | 'file',
): string {
  return JSON.stringify({
    identifier,
    format,
    options,
    type,
  });
}
</file>

<file path="website/server/src/utils/errorHandler.ts">
import type { ContentfulStatusCode } from 'hono/utils/http-status';

export class AppError extends Error {
  constructor(
    message: string,
    public readonly statusCode: ContentfulStatusCode = 500,
  ) {
    super(message);
    this.name = 'AppError';
  }
}

export function handlePackError(error: unknown): AppError {
  if (error instanceof AppError) {
    return error;
  }

  if (error instanceof Error) {
    const errorMessage = error.message || '';

    if (errorMessage.includes('Repository not found')) {
      return new AppError('Repository not found', 404);
    }

    if (errorMessage.includes('Failed to clone repository')) {
      return new AppError('Failed to clone repository', 422);
    }

    if (errorMessage.includes('circular') || errorMessage.includes('Converting circular structure to JSON')) {
      return new AppError(
        'Failed to process repository: circular dependency detected in the repository structure',
        422,
      );
    }

    return new AppError(process.env.NODE_ENV === 'production' ? 'An unexpected error occurred' : error.message);
  }

  return new AppError('An unexpected error occurred');
}

export function safeJSONStringify(obj: unknown): string {
  const cache = new Set();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (cache.has(value)) {
        return '[Circular Reference]';
      }
      cache.add(value);
    }
    return value;
  });
}
</file>

<file path="website/server/src/utils/fileUtils.ts">
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import AdmZip from 'adm-zip';
import { FILE_SIZE_LIMITS, formatFileSize } from '../constants.js';
import { AppError } from './errorHandler.js';

export async function extractZip(file: File, destPath: string): Promise<void> {
  try {
    // Validate file size before processing
    if (file.size > FILE_SIZE_LIMITS.MAX_ZIP_SIZE) {
      throw new AppError(`File size exceeds maximum limit of ${formatFileSize(FILE_SIZE_LIMITS.MAX_ZIP_SIZE)}`);
    }

    const arrayBuffer = await file.arrayBuffer();
    const buffer = Buffer.from(arrayBuffer);
    const zip = new AdmZip(buffer);

    // Get zip entries for validation
    const entries = zip.getEntries();

    // Validate number of files
    if (entries.length > FILE_SIZE_LIMITS.MAX_FILES) {
      throw new AppError(
        `ZIP contains too many files (${entries.length}). Maximum allowed: ${FILE_SIZE_LIMITS.MAX_FILES}`,
      );
    }

    // Validate total uncompressed size
    const totalUncompressedSize = entries.reduce((sum, entry) => sum + entry.header.size, 0);
    if (totalUncompressedSize > FILE_SIZE_LIMITS.MAX_UNCOMPRESSED_SIZE) {
      throw new AppError(
        `Uncompressed size exceeds maximum limit of ${formatFileSize(FILE_SIZE_LIMITS.MAX_UNCOMPRESSED_SIZE)}`,
      );
    }

    // Check for unsafe paths (directory traversal prevention)
    for (const entry of entries) {
      const entryPath = path.join(destPath, entry.entryName);
      if (!entryPath.startsWith(destPath)) {
        throw new AppError('ZIP contains unsafe file paths');
      }
    }

    await fs.mkdir(destPath, { recursive: true });
    zip.extractAllTo(destPath, true);
  } catch (error) {
    if (error instanceof AppError) {
      throw error;
    }
    throw new AppError(`Failed to extract ZIP file: ${error instanceof Error ? error.message : 'Unknown error'}`);
  }
}

export const createTempDirectory = async (): Promise<string> => {
  try {
    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'repomix-'));
    return tempDir;
  } catch (error) {
    throw new AppError(`Failed to create temporary directory: ${(error as Error).message}`);
  }
};

export const cleanupTempDirectory = async (directory: string): Promise<void> => {
  try {
    if (!directory.includes('repomix-')) {
      throw new AppError('Invalid temporary directory path');
    }
    await fs.rm(directory, { recursive: true, force: true });
  } catch (error) {
    if (error instanceof AppError) {
      throw error;
    }
    console.error(`Failed to cleanup temporary directory: ${directory}`, error);
  }
};

export const copyOutputToCurrentDirectory = async (
  sourceDir: string,
  targetDir: string,
  outputFileName: string,
): Promise<void> => {
  // Sanitize file paths
  const sanitizedFileName = path.basename(outputFileName);
  const sourcePath = path.join(sourceDir, sanitizedFileName);
  const targetPath = path.join(targetDir, sanitizedFileName);

  try {
    // Verify source exists
    await fs.access(sourcePath);

    // Ensure target directory exists
    await fs.mkdir(targetDir, { recursive: true });

    await fs.copyFile(sourcePath, targetPath);
  } catch (error) {
    throw new AppError(
      `Failed to copy output file: ${(error as Error).message}. Source: ${sourcePath}, Target: ${targetPath}`,
    );
  }
};
</file>

<file path="website/server/src/utils/logger.ts">
import { LoggingWinston } from '@google-cloud/logging-winston';
import type { Context, Next } from 'hono';
import winston from 'winston';
import { getClientIP } from './network.js';
import { calculateLatency, formatLatencyForDisplay } from './time.js';

// Augment Hono's context type
declare module 'hono' {
  interface ContextVariableMap {
    requestId: string;
  }
}

// Configure transports based on environment
function createLogger() {
  const transports: winston.transport[] = [
    new winston.transports.Console({
      format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
    }),
  ];

  // Add Cloud Logging transport only in production
  if (process.env.NODE_ENV === 'production') {
    const loggingWinston = new LoggingWinston();
    transports.push(loggingWinston);
  }

  return winston.createLogger({
    level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
    transports,
  });
}

// Create the logger instance
const logger = createLogger();

// Generate unique request identifier
function generateRequestId(): string {
  return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}

// Error response interface with request tracking
interface ErrorResponse {
  error: string;
  requestId: string;
  timestamp: string;
}

// Generate standardized error response
export function createErrorResponse(message: string, requestId: string): ErrorResponse {
  return {
    error: message,
    requestId,
    timestamp: new Date().toISOString(),
  };
}

// Main logging middleware for Hono
export function cloudLogger() {
  return async function loggerMiddleware(c: Context, next: Next) {
    const requestId = generateRequestId();
    const startTime = Date.now();

    // Add request ID to context for tracking
    c.set('requestId', requestId);

    // Collect basic request information
    const method = c.req.method;
    const url = new URL(c.req.url);
    const userAgent = c.req.header('user-agent');
    const referer = c.req.header('referer');
    const remoteIp = getClientIP(c);

    // Log request start
    logger.info({
      message: `${method} ${url.pathname} started`,
      requestId,
      httpRequest: {
        requestMethod: method,
        requestUrl: url.toString(),
        userAgent,
        referer,
        remoteIp,
      },
    });

    try {
      // Process the request
      await next();

      // Collect response information
      const status = c.res.status;
      const latency = calculateLatency(startTime);
      const contentLength = Number.parseInt(c.res.headers.get('content-length') || '0', 10);

      // Log successful response
      logger.info({
        message: `${method} ${url.pathname} completed in ${formatLatencyForDisplay(startTime)}`,
        requestId,
        httpRequest: {
          requestMethod: method,
          requestUrl: url.toString(),
          status,
          latency, // Now uses the correct format { seconds: number, nanos: number }
          responseSize: contentLength,
          userAgent,
          referer,
          remoteIp,
        },
      });
    } catch (error) {
      // Log error information
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      logger.error({
        message: `${method} ${url.pathname} failed: ${errorMessage} (${formatLatencyForDisplay(startTime)})`,
        requestId,
        error: {
          message: errorMessage,
          stack: error instanceof Error ? error.stack : undefined,
        },
        httpRequest: {
          requestMethod: method,
          requestUrl: url.toString(),
          status: 500,
          latency: calculateLatency(startTime), // Now uses the correct format
          userAgent,
          referer,
          remoteIp,
        },
      });
      throw error;
    }
  };
}

// Utility logging functions
export function logDebug(message: string, context?: Record<string, unknown>): void {
  logger.debug({
    message,
    ...context,
  });
}

export function logInfo(message: string, context?: Record<string, unknown>): void {
  logger.info({
    message,
    ...context,
  });
}

export function logWarning(message: string, context?: Record<string, unknown>): void {
  logger.warn({
    message,
    ...context,
  });
}

export function logError(message: string, error?: Error, context?: Record<string, unknown>): void {
  logger.error({
    message,
    error: error
      ? {
          message: error.message,
          stack: error.stack,
        }
      : undefined,
    ...context,
  });
}
</file>

<file path="website/server/src/utils/network.ts">
import type { Context } from 'hono';

/**
 * Get client IP address
 * Handles various headers in Cloud Run environment
 */
export function getClientIP(c: Context): string {
  return (
    c.req.header('x-forwarded-for')?.split(',')[0] ||
    c.req.header('x-real-ip') ||
    c.req.header('cf-connecting-ip') ||
    '0.0.0.0'
  );
}
</file>

<file path="website/server/src/utils/processConcurrency.ts">
import os from 'node:os';

export const getProcessConcurrency = () => {
  const cpuCount = typeof os.availableParallelism === 'function' ? os.availableParallelism() : os.cpus().length;

  // Use all available CPUs except one
  return Math.max(1, cpuCount - 1);
};
</file>

<file path="website/server/src/utils/rateLimit.ts">
interface RateLimitEntry {
  count: number;
  resetTime: number;
}

export class RateLimiter {
  private limits: Map<string, RateLimitEntry> = new Map();
  private readonly windowMs: number;
  private readonly maxRequests: number;

  constructor(windowMs = 60000, maxRequests = 10) {
    this.windowMs = windowMs;
    this.maxRequests = maxRequests;
  }

  isAllowed(identifier: string): boolean {
    const now = Date.now();
    const entry = this.limits.get(identifier);

    if (!entry || now > entry.resetTime) {
      this.limits.set(identifier, {
        count: 1,
        resetTime: now + this.windowMs,
      });
      return true;
    }

    if (entry.count >= this.maxRequests) {
      return false;
    }

    entry.count += 1;
    return true;
  }

  getRemainingTime(identifier: string): number {
    const entry = this.limits.get(identifier);
    if (!entry) return 0;
    return Math.max(0, entry.resetTime - Date.now());
  }
}
</file>

<file path="website/server/src/utils/sharedInstance.ts">
import type { PackResult } from '../types.js';
import { RequestCache } from './cache.js';
import { RateLimiter } from './rateLimit.js';

// Create shared instances
export const cache = new RequestCache<PackResult>(180); // 3 minutes cache
export const rateLimiter = new RateLimiter(60_000, 3); // 3 requests per minute
</file>

<file path="website/server/src/utils/time.ts">
/**
 * Convert milliseconds to a Duration string that Cloud Logging expects
 * Format: "1.500s" -> { seconds: 1, nanos: 500000000 }
 */
export function formatDuration(durationMs: number): {
  seconds: number;
  nanos: number;
} {
  const seconds = Math.floor(durationMs / 1000);
  const nanos = (durationMs % 1000) * 1_000_000; // Convert remaining ms to nanoseconds
  return { seconds, nanos };
}

/**
 * Calculate latency between start time and now
 */
export function calculateLatency(startTime: number): {
  seconds: number;
  nanos: number;
} {
  const latencyMs = Date.now() - startTime;
  return formatDuration(latencyMs);
}

/**
 * Format latency for display purposes (not for Cloud Logging)
 */
export function formatLatencyForDisplay(startTime: number): string {
  const latencyMs = Date.now() - startTime;
  return `${(latencyMs / 1000).toFixed(3)}s`;
}
</file>

<file path="website/server/src/utils/validation.ts">
import { z } from 'zod';
import { AppError } from './errorHandler.js';

export function validateRequest<T>(schema: z.ZodSchema<T>, data: unknown): T {
  try {
    return schema.parse(data);
  } catch (error) {
    if (error instanceof z.ZodError) {
      const messages = error.errors.map((err) => `${err.path.join('.')}: ${err.message}`).join(', ');
      throw new AppError(`Invalid request: ${messages}`, 400);
    }
    throw error;
  }
}

export function sanitizePattern(patterns: string | undefined): string {
  if (!patterns) return '';

  return (
    patterns
      .split(',')
      .map((p) => p.trim())
      .filter(Boolean)
      // Additional security checks
      .filter((p) => {
        // Prevent absolute paths
        if (p.startsWith('/')) return false;
        // Prevent directory traversal
        if (p.includes('../') || p.includes('..\\')) return false;
        // Prevent environment variable expansion
        if (p.includes('$')) return false;
        // Prevent command execution
        if (p.includes('`') || p.includes('$(')) return false;
        return true;
      })
      .join(',')
  );
}
</file>

<file path="website/server/src/constants.ts">
export const FILE_SIZE_LIMITS = {
  MAX_REQUEST_SIZE: 10 * 1024 * 1024, // 10MB
  MAX_ZIP_SIZE: 10 * 1024 * 1024, // 10MB
  MAX_UNCOMPRESSED_SIZE: 50 * 1024 * 1024, // 50MB
  MAX_FILES: 1000, // Maximum number of files in zip
} as const;

// Helper function to format size for error messages
export const formatFileSize = (bytes: number): string => {
  return `${bytes / 1024 / 1024}MB`;
};
</file>

<file path="website/server/src/index.ts">
import { serve } from '@hono/node-server';
import { Hono } from 'hono';
import { bodyLimit } from 'hono/body-limit';
import { compress } from 'hono/compress';
import { cors } from 'hono/cors';
import { timeout } from 'hono/timeout';
import { FILE_SIZE_LIMITS } from './constants.js';
import { processZipFile } from './processZipFile.js';
import { processRemoteRepo } from './remoteRepo.js';
import type { PackResult } from './types.js';
import { handlePackError } from './utils/errorHandler.js';
import { cloudLogger, createErrorResponse, logError, logInfo } from './utils/logger.js';
import { getProcessConcurrency } from './utils/processConcurrency.js';
import { calculateLatency, formatLatencyForDisplay } from './utils/time.js';

// Log server metrics on startup
logInfo('Server starting', {
  metrics: {
    processConcurrency: getProcessConcurrency(),
  },
});

const app = new Hono();

// Setup custom logger
app.use('*', cloudLogger());

// Configure CORS
app.use(
  '/*',
  cors({
    origin: ['http://localhost:5173', 'https://repomix.com', 'https://api.repomix.com'],
    allowMethods: ['GET', 'POST', 'OPTIONS'],
    allowHeaders: ['Content-Type'],
    maxAge: 86400,
    credentials: true,
  }),
);

// Enable compression
app.use(compress());

// Set timeout for API routes
app.use('/api', timeout(30000));

// Health check endpoint
app.get('/health', (c) => c.text('OK'));

// Main packing endpoint
app.post(
  '/api/pack',
  bodyLimit({
    maxSize: FILE_SIZE_LIMITS.MAX_REQUEST_SIZE,
    onError: (c) => {
      const requestId = c.get('requestId');
      const response = createErrorResponse('File size too large', requestId);
      return c.json(response, 413);
    },
  }),
  async (c) => {
    try {
      const formData = await c.req.formData();
      const requestId = c.get('requestId');

      // Get form data
      const format = formData.get('format') as 'xml' | 'markdown' | 'plain';
      const options = JSON.parse(formData.get('options') as string);
      const file = formData.get('file') as File | null;
      const url = formData.get('url') as string | null;

      // Validate input
      if (!file && !url) {
        return c.json(createErrorResponse('Either repository URL or file is required', requestId), 400);
      }

      if (!['xml', 'markdown', 'plain'].includes(format)) {
        return c.json(createErrorResponse('Invalid format specified', requestId), 400);
      }

      // Get client IP
      const clientIp =
        c.req.header('x-forwarded-for')?.split(',')[0] ||
        c.req.header('x-real-ip') ||
        c.req.header('cf-connecting-ip') ||
        '0.0.0.0';

      const startTime = Date.now();

      // Process file or repository
      let result: PackResult;
      if (file) {
        result = await processZipFile(file, format, options, clientIp);
      } else {
        if (!url) {
          return c.json(createErrorResponse('Repository URL is required', requestId), 400);
        }
        result = await processRemoteRepo(url, format, options, clientIp);
      }

      // Log operation result
      logInfo('Pack operation completed', {
        requestId,
        format,
        repository: result.metadata.repository,
        duration: formatLatencyForDisplay(startTime),
        inputType: file ? 'file' : url ? 'url' : 'unknown',
        metrics: {
          totalFiles: result.metadata.summary?.totalFiles,
          totalCharacters: result.metadata.summary?.totalCharacters,
          totalTokens: result.metadata.summary?.totalTokens,
        },
      });

      return c.json(result);
    } catch (error) {
      // Handle errors
      logError('Pack operation failed', error instanceof Error ? error : new Error('Unknown error'), {
        requestId: c.get('requestId'),
      });

      const appError = handlePackError(error);
      return c.json(createErrorResponse(appError.message, c.get('requestId')), appError.statusCode);
    }
  },
);

// Start server
const port = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 3000;
logInfo(`Server starting on port ${port}`);

serve({
  fetch: app.fetch,
  port,
});

// Export app for testing
export default app;
</file>

<file path="website/server/src/processZipFile.ts">
import { randomUUID } from 'node:crypto';
import fs from 'node:fs/promises';
import path from 'node:path';
import AdmZip from 'adm-zip';
import { type CliOptions, runDefaultAction, setLogLevel } from 'repomix';
import { packRequestSchema } from './schemas/request.js';
import type { PackOptions, PackResult } from './types.js';
import { generateCacheKey } from './utils/cache.js';
import { AppError } from './utils/errorHandler.js';
import { cleanupTempDirectory, copyOutputToCurrentDirectory, createTempDirectory } from './utils/fileUtils.js';
import { cache, rateLimiter } from './utils/sharedInstance.js';
import { sanitizePattern, validateRequest } from './utils/validation.js';

// Enhanced ZIP extraction limits
const ZIP_SECURITY_LIMITS = {
  MAX_FILES: 10000, // Maximum number of files in the archive
  MAX_UNCOMPRESSED_SIZE: 100_000_000, // Maximum total uncompressed size (100MB)
  MAX_COMPRESSION_RATIO: 100, // Maximum compression ratio to prevent ZIP bombs
  MAX_PATH_LENGTH: 200, // Maximum file path length
  MAX_NESTING_LEVEL: 50, // Maximum directory nesting level
};

/**
 * Process an uploaded ZIP file
 */
export async function processZipFile(
  file: File,
  format: string,
  options: PackOptions,
  clientIp: string,
): Promise<PackResult> {
  // Validate the request (excluding URL validation)
  const validatedData = validateRequest(packRequestSchema, {
    file,
    format,
    options,
  });

  // Rate limit check
  if (!rateLimiter.isAllowed(clientIp)) {
    const remainingTime = Math.ceil(rateLimiter.getRemainingTime(clientIp) / 1000);
    throw new AppError(`Rate limit exceeded. Please try again in ${remainingTime} seconds.`, 429);
  }

  if (!file) {
    throw new AppError('File is required for file processing', 400);
  }

  const cacheKey = generateCacheKey(
    `${file.name}-${file.size}-${file.lastModified}`,
    validatedData.format,
    validatedData.options,
    'file',
  );

  // Check if the result is already cached
  const cachedResult = cache.get(cacheKey);
  if (cachedResult) {
    return cachedResult;
  }

  // Sanitize patterns
  const sanitizedIncludePatterns = sanitizePattern(validatedData.options.includePatterns);
  const sanitizedIgnorePatterns = sanitizePattern(validatedData.options.ignorePatterns);

  const outputFilePath = `repomix-output-${randomUUID()}.txt`;

  // Create CLI options
  const cliOptions = {
    output: outputFilePath,
    style: validatedData.format,
    parsableStyle: validatedData.options.outputParsable,
    removeComments: validatedData.options.removeComments,
    removeEmptyLines: validatedData.options.removeEmptyLines,
    outputShowLineNumbers: validatedData.options.showLineNumbers,
    fileSummary: validatedData.options.fileSummary,
    directoryStructure: validatedData.options.directoryStructure,
    securityCheck: false,
    topFilesLen: 10,
    include: sanitizedIncludePatterns,
    ignore: sanitizedIgnorePatterns,
    quiet: true, // Enable quiet mode to suppress output
  } as CliOptions;

  setLogLevel(-1);

  const tempDirPath = await createTempDirectory();

  try {
    // Extract the ZIP file to the temporary directory with enhanced security checks
    await extractZipWithSecurity(file, tempDirPath);

    // Execute default action on the extracted directory
    const result = await runDefaultAction([tempDirPath], tempDirPath, cliOptions);
    await copyOutputToCurrentDirectory(tempDirPath, process.cwd(), result.config.output.filePath);
    const { packResult } = result;

    // Read the generated file
    const content = await fs.readFile(outputFilePath, 'utf-8');

    // Create pack result
    const packResultData: PackResult = {
      content,
      format,
      metadata: {
        repository: file.name,
        timestamp: new Date().toISOString(),
        summary: {
          totalFiles: packResult.totalFiles,
          totalCharacters: packResult.totalCharacters,
          totalTokens: packResult.totalTokens,
        },
        topFiles: Object.entries(packResult.fileCharCounts)
          .map(([path, charCount]) => ({
            path,
            charCount,
            tokenCount: packResult.fileTokenCounts[path] || 0,
          }))
          .sort((a, b) => b.charCount - a.charCount)
          .slice(0, cliOptions.topFilesLen),
      },
    };

    // Save the result to cache
    cache.set(cacheKey, packResultData);

    return packResultData;
  } catch (error) {
    console.error('Error processing uploaded file:', error);
    if (error instanceof AppError) {
      throw error;
    }
    throw new AppError(`File processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 500);
  } finally {
    cleanupTempDirectory(tempDirPath);
    // Clean up the output file
    try {
      await fs.unlink(outputFilePath);
    } catch (err) {
      // Ignore file deletion errors
      console.warn('Failed to cleanup output file:', err);
    }
  }
}

/**
 * Enhanced ZIP extraction with security checks
 */
async function extractZipWithSecurity(file: File, destPath: string): Promise<void> {
  try {
    const arrayBuffer = await file.arrayBuffer();
    const buffer = Buffer.from(arrayBuffer);
    const zip = new AdmZip(buffer);

    // Get all entries for validation
    const entries = zip.getEntries();

    // 1. Check number of files
    if (entries.length > ZIP_SECURITY_LIMITS.MAX_FILES) {
      throw new AppError(
        `ZIP contains too many files (${entries.length}). Maximum allowed: ${ZIP_SECURITY_LIMITS.MAX_FILES}`,
        413,
      );
    }

    // 2. Calculate total uncompressed size
    const totalUncompressedSize = entries.reduce((sum, entry) => sum + entry.header.size, 0);

    if (totalUncompressedSize > ZIP_SECURITY_LIMITS.MAX_UNCOMPRESSED_SIZE) {
      throw new AppError(
        `Uncompressed size (${(totalUncompressedSize / 1_000_000).toFixed(2)}MB) exceeds maximum limit of ${
          ZIP_SECURITY_LIMITS.MAX_UNCOMPRESSED_SIZE / 1_000_000
        }MB`,
        413,
      );
    }

    // 3. Check compression ratio (ZIP bomb detection)
    if (file.size > 0) {
      const compressionRatio = totalUncompressedSize / file.size;
      if (compressionRatio > ZIP_SECURITY_LIMITS.MAX_COMPRESSION_RATIO) {
        throw new AppError(
          `Suspicious compression ratio (${compressionRatio.toFixed(2)}:1). Maximum allowed: ${ZIP_SECURITY_LIMITS.MAX_COMPRESSION_RATIO}:1`,
          400,
        );
      }
    }

    // 4. Validate all entries for path traversal, file extensions, and nesting level
    const processedPaths = new Set<string>();

    for (const entry of entries) {
      // Skip directories
      if (entry.isDirectory) continue;

      const entryPath = entry.entryName;

      // 4.1 Check for unsafe paths (directory traversal prevention)
      const normalizedPath = path.normalize(path.join(destPath, entryPath));
      if (!normalizedPath.startsWith(destPath)) {
        throw new AppError(
          `Security violation: Potential directory traversal attack detected in path: ${entryPath}`,
          400,
        );
      }

      // 4.2 Check path length
      if (entryPath.length > ZIP_SECURITY_LIMITS.MAX_PATH_LENGTH) {
        throw new AppError(
          `File path exceeds maximum length: ${entryPath.length} > ${ZIP_SECURITY_LIMITS.MAX_PATH_LENGTH}`,
          400,
        );
      }

      // 4.3 Check nesting level
      const nestingLevel = entryPath.split('/').length - 1;
      if (nestingLevel > ZIP_SECURITY_LIMITS.MAX_NESTING_LEVEL) {
        throw new AppError(
          `Directory nesting level exceeds maximum: ${nestingLevel} > ${ZIP_SECURITY_LIMITS.MAX_NESTING_LEVEL}`,
          400,
        );
      }

      // 4.4 Check for duplicate paths (could indicate ZipSlip vulnerability attempts)
      if (processedPaths.has(normalizedPath)) {
        throw new AppError(`Duplicate file path detected: ${entryPath}. This could indicate a malicious archive.`, 400);
      }
      processedPaths.add(normalizedPath);
    }

    // If all checks pass, extract the ZIP
    await fs.mkdir(destPath, { recursive: true });
    zip.extractAllTo(destPath, true);
  } catch (error) {
    if (error instanceof AppError) {
      throw error;
    }
    throw new AppError(`Failed to extract ZIP file: ${error instanceof Error ? error.message : 'Unknown error'}`);
  }
}
</file>

<file path="website/server/src/remoteRepo.ts">
import { randomUUID } from 'node:crypto';
import fs from 'node:fs/promises';
import { type CliOptions, runCli } from 'repomix';
import { packRequestSchema } from './schemas/request.js';
import type { PackOptions, PackResult } from './types.js';
import { generateCacheKey } from './utils/cache.js';
import { AppError } from './utils/errorHandler.js';
import { cache, rateLimiter } from './utils/sharedInstance.js';
import { sanitizePattern, validateRequest } from './utils/validation.js';

export async function processRemoteRepo(
  repoUrl: string,
  format: string,
  options: PackOptions,
  clientIp: string,
): Promise<PackResult> {
  // Validate the request
  const validatedData = validateRequest(packRequestSchema, {
    url: repoUrl,
    format,
    options,
  });

  // Rate limit check
  if (!rateLimiter.isAllowed(clientIp)) {
    const remainingTime = Math.ceil(rateLimiter.getRemainingTime(clientIp) / 1000);
    throw new AppError(`Rate limit exceeded. Please try again in ${remainingTime} seconds.`, 429);
  }

  if (!validatedData.url) {
    throw new AppError('Repository URL is required for remote processing', 400);
  }

  // Generate cache key
  const cacheKey = generateCacheKey(validatedData.url, validatedData.format, validatedData.options, 'url');

  // Check if the result is already cached
  const cachedResult = cache.get(cacheKey);
  if (cachedResult) {
    return cachedResult;
  }

  // Sanitize ignore patterns
  const sanitizedIncludePatterns = sanitizePattern(validatedData.options.includePatterns);
  const sanitizedIgnorePatterns = sanitizePattern(validatedData.options.ignorePatterns);

  const outputFilePath = `repomix-output-${randomUUID()}.txt`;

  // Create CLI options with correct mapping
  const cliOptions = {
    remote: repoUrl,
    output: outputFilePath,
    style: validatedData.format,
    parsableStyle: validatedData.options.outputParsable,
    removeComments: validatedData.options.removeComments,
    removeEmptyLines: validatedData.options.removeEmptyLines,
    outputShowLineNumbers: validatedData.options.showLineNumbers,
    fileSummary: validatedData.options.fileSummary,
    directoryStructure: validatedData.options.directoryStructure,
    securityCheck: false,
    topFilesLen: 10,
    include: sanitizedIncludePatterns,
    ignore: sanitizedIgnorePatterns,
    quiet: true, // Enable quiet mode to suppress output
  } as CliOptions;

  try {
    // Execute remote action
    const result = await runCli(['.'], process.cwd(), cliOptions);
    if (!result) {
      throw new AppError('Remote action failed to return a result', 500);
    }
    const { packResult } = result;

    // Read the generated file
    const content = await fs.readFile(outputFilePath, 'utf-8');

    // Create pack result
    const packResultData: PackResult = {
      content,
      format,
      metadata: {
        repository: repoUrl,
        timestamp: new Date().toISOString(),
        summary: {
          totalFiles: packResult.totalFiles,
          totalCharacters: packResult.totalCharacters,
          totalTokens: packResult.totalTokens,
        },
        topFiles: Object.entries(packResult.fileCharCounts)
          .map(([path, charCount]) => ({
            path,
            charCount,
            tokenCount: packResult.fileTokenCounts[path] || 0,
          }))
          .sort((a, b) => b.charCount - a.charCount)
          .slice(0, cliOptions.topFilesLen),
      },
    };

    // Save the result to cache
    cache.set(cacheKey, packResultData);

    return packResultData;
  } catch (error) {
    console.error('Error in remote action:', error);
    if (error instanceof Error) {
      throw new AppError(`Remote action failed: ${error.message}`, 500);
    }
    throw new AppError('Remote action failed with unknown error', 500);
  } finally {
    // Clean up the output file
    try {
      await fs.unlink(outputFilePath);
    } catch (err) {
      // Ignore file deletion errors
      console.warn('Failed to cleanup output file:', err);
    }
  }
}
</file>

<file path="website/server/src/types.ts">
export interface PackOptions {
  removeComments?: boolean;
  removeEmptyLines?: boolean;
  showLineNumbers?: boolean;
  fileSummary?: boolean;
  directoryStructure?: boolean;
  includePatterns?: string;
  ignorePatterns?: string;
  outputParsable?: boolean;
}

interface TopFile {
  path: string;
  charCount: number;
  tokenCount: number;
}

interface PackSummary {
  totalFiles: number;
  totalCharacters: number;
  totalTokens: number;
}

export interface PackResult {
  content: string;
  format: string;
  metadata: {
    repository: string;
    timestamp: string;
    summary?: PackSummary;
    topFiles?: TopFile[];
  };
}

export interface ErrorResponse {
  error: string;
}
</file>

<file path="website/server/.dockerignore">
# Dependencies
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Build outputs
dist
build
*.tsbuildinfo

# Development files
.git
.gitignore
.env
.env.*
.npmrc
.yarnrc
.editorconfig
README.md
CHANGELOG.md
LICENSE
*.md

# IDE files
.vscode
.idea
*.swp
*.swo
.DS_Store
Thumbs.db

# Test files
__tests__
test
tests
coverage
.nyc_output

# Docker files
Dockerfile
.dockerignore

# Temporary files
tmp
temp
*.tmp
*.temp

# Log files
logs
*.log

# Cache directories
.npm
.eslintcache

# Cloud platform specific
.gcloudignore
cloudbuild.yaml
cloudbuild.yml

# TypeScript source maps
*.map

# Misc
.git
.github
.circleci
.husky
</file>

<file path="website/server/.gitignore">
dist
node_modules
</file>

<file path="website/server/cloudbuild.yaml">
steps:
  # Build the container image
  - name: 'gcr.io/cloud-builders/docker'
    id: 'build'
    args:
      - 'build'
      - '-t'
      - '$_REGION-docker.pkg.dev/$PROJECT_ID/repomix/server:$BUILD_ID'
      - '--cache-from'
      - '$_REGION-docker.pkg.dev/$PROJECT_ID/repomix/server:latest'
      - '--build-arg'
      - 'NODE_ENV=production'
      - '.'

  # Push the container image
  - name: 'gcr.io/cloud-builders/docker'
    id: 'push'
    args:
      - 'push'
      - '$_REGION-docker.pkg.dev/$PROJECT_ID/repomix/server:$BUILD_ID'

  # Deploy to Cloud Run
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    id: 'deploy'
    entrypoint: 'gcloud'
    args:
      - 'run'
      - 'deploy'
      - '$_SERVICE_NAME'
      - '--image'
      - '$_REGION-docker.pkg.dev/$PROJECT_ID/repomix/server:$BUILD_ID'
      - '--region'
      - '$_REGION'
      - '--platform'
      - 'managed'
      - '--port'
      - '8080'
      - '--memory'
      - '2048Mi'
      - '--cpu'
      - '2'
      - '--min-instances'
      - '0'
      - '--max-instances'
      - '10'
      - '--timeout'
      - '35s'
      - '--ingress'
      - 'all'
      - '--allow-unauthenticated'
      - '--set-env-vars'
      - 'NODE_ENV=production'

  # Tag the image as latest
  - name: 'gcr.io/cloud-builders/docker'
    id: 'tag-latest'
    args:
      - 'tag'
      - '$_REGION-docker.pkg.dev/$PROJECT_ID/repomix/server:$BUILD_ID'
      - '$_REGION-docker.pkg.dev/$PROJECT_ID/repomix/server:latest'

  # Push the latest tag
  - name: 'gcr.io/cloud-builders/docker'
    id: 'push-latest'
    args:
      - 'push'
      - '$_REGION-docker.pkg.dev/$PROJECT_ID/repomix/server:latest'

substitutions:
  _REGION: asia-northeast1
  _SERVICE_NAME: repomix-server

options:
  logging: CLOUD_LOGGING_ONLY
  dynamic_substitutions: true

images:
  - '$_REGION-docker.pkg.dev/$PROJECT_ID/repomix/server:$BUILD_ID'
  - '$_REGION-docker.pkg.dev/$PROJECT_ID/repomix/server:latest'
</file>

<file path="website/server/Dockerfile">
# ==============================================================================
# Base image
# ==============================================================================
FROM node:23-alpine AS builder

# Install git and other dependencies
RUN apk add --no-cache \
    git \
    ca-certificates

WORKDIR /app
COPY package*.json ./

# Install dependencies
RUN npm i

# Copy source code
COPY . .

# Build TypeScript
RUN npm run build

# ==============================================================================
# Production image
# ==============================================================================
FROM node:23-alpine

# Install git and other dependencies
RUN apk add --no-cache \
    git \
    ca-certificates \
    curl

WORKDIR /app

# Copy only necessary files
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules

# Set environment variables
ENV NODE_ENV=production \
    PORT=8080

# Expose port
EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# Start the server
CMD ["npm", "start"]
</file>

<file path="website/server/package.json">
{
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "PORT=8080 tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "clean": "rimraf dist",
    "cloud-deploy": "gcloud builds submit --config=cloudbuild.yaml ."
  },
  "dependencies": {
    "@google-cloud/logging-winston": "^6.0.0",
    "@hono/node-server": "^1.13.8",
    "adm-zip": "^0.5.16",
    "hono": "^4.6.20",
    "pako": "^2.1.0",
    "repomix": "^0.2.29",
    "winston": "^3.17.0",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@types/adm-zip": "^0.5.7",
    "@types/node": "^22.13.0",
    "@types/pako": "^2.0.3",
    "rimraf": "^6.0.1",
    "tsx": "^4.19.2",
    "typescript": "^5.7.3"
  }
}
</file>

<file path="website/server/tsconfig.json">
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"]
}
</file>

<file path="website/compose.yml">
# Run website in development mode
# $ docker compose -f website/compose.yml up --build

services:
  client:
    build:
      context: ./client
      dockerfile: Dockerfile
    ports:
      - "5173:5173"
    volumes:
      - ./client:/app
    environment:
      - NODE_ENV=development
    # override default command
    command: sh -c "npm i && npm run docs:dev -- --port 5173 --host"

  server:
    build:
      context: ./server
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    volumes:
      - ./server:/app
    environment:
      - NODE_ENV=development
      - PORT=8080
    # override default command
    command: sh -c "npm i && npm run dev"
    develop:
      watch:
        - action: sync+restart
          path: ./server
          target: /app
</file>

<file path="website/README.md">
# Repomix Website

This directory contains the source code for the Repomix website, built with [VitePress](https://vitepress.dev/) and [Vue.js](https://vuejs.org/)

## Prerequisites

- Docker must be installed on your system

## Development

To start the development server:

```bash
# Start the website development server
npm run website

# Access the website at http://localhost:5173/
```

## Documentation

When updating documentation, you only need to update the English version (`client/src/en/`).
The maintainers will handle translations to other languages.

## Building for Production

To build the website for production:

```bash
npm run website:build
```

The built files will be available in the `client/dist` directory.
</file>

<file path=".clinerules">
# Important
- Follow custom AI instructions written in `.github/copilot-instructions.md`.
</file>

<file path=".codecov.yml">
coverage:
  status:
    patch:
      default:
        target: 80%
        informational: true
    project:
      default:
        target: 75%
        threshold: 1%
</file>

<file path=".dockerignore">
node_modules
npm-debug.log
.git
.gitignore
.env
.env.*
*.md
.vscode
coverage
.nyc_output
dist
build
</file>

<file path=".editorconfig">
root = true

[*.*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
max_line_length = null

[*.md]
trim_trailing_whitespace = false
</file>

<file path=".gitignore">
# Dependency directories
node_modules/

# Build output
lib/

# Logs
*.log

# OS generated files
.DS_Store

# Editor directories and files
.vscode/
.idea/

# Test coverage
coverage/

# Temporary files
*.tmp
*.temp

# Repomix output
repomix-output.txt
repomix-output.xml
repomix-output.md

# ESLint cache
.eslintcache

# yarn
.yarn/

# biome
.biome/

# aider
.aider*
</file>

<file path=".node-version">
23.6.0
</file>

<file path=".npmignore">
# Source files
src/

# Test files
tests/
coverage/

# Configuration files
tsconfig.json
tsconfig.build.json
.eslintrc.js
eslint.config.mjs
prettier.config.mjs
vite.config.ts
biome.json

# Git files
.gitignore
.git

# CI files
.github/

# yarn files
.yarn

# ESLint files
.eslintcache

# Config files
.editorconfig
.node-version
.tool-versions
repomix.config.js

# Editor files
.vscode/
.idea/
.memo/

# Logs
*.log

# Repomix output
repomix-output.txt

# Development scripts
scripts/

# Documentation files (except README and LICENSE)
docs/
CONTRIBUTING.md
CHANGELOG.md

# Temporary files
*.tmp
*.temp

# OS generated files
.DS_Store
Thumbs.db

# biome
.biome/

# Website
website/

# Devcontainer
.devcontainer/
</file>

<file path=".repomixignore">
node_modules
.yarn
.eslinttcache
tests/integration-tests/fixtures
</file>

<file path=".secretlintrc.json">
{
  "rules": [
    {
      "id": "@secretlint/secretlint-rule-preset-recommend"
    }
  ]
}
</file>

<file path=".tool-versions">
nodejs 23.6.0
</file>

<file path="biome.json">
{
  "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
  "files": {
    "include": [
      "./bin/**",
      "./src/**",
      "./tests/**",
      "./website/**",
      "./.devcontainer/**",
      "./.github/**",
      "package.json",
      "biome.json",
      ".secretlintrc.json",
      "tsconfig.json",
      "tsconfig.build.json",
      "vite.config.ts",
      "repomix.config.json"
    ],
    "ignore": [
      "website/client/.vitepress/.temp",
      "website/client/.vitepress/dist",
      "website/client/.vitepress/cache",
      "website/server/dist"
    ]
  },
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "formatter": {
    "enabled": true,
    "formatWithErrors": false,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 120
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single",
      "trailingCommas": "all",
      "semicolons": "always"
    }
  },
  "json": {
    "parser": {
      "allowComments": true,
      "allowTrailingCommas": true
    },
    "formatter": {
      "enabled": false
    }
  }
}
</file>

<file path="CLAUDE.md">
# Repomix Development Guide

## Build & Development Commands
- `npm run build` - Build the project
- `npm run lint` - Run all linters (Biome, TypeScript, secretlint)
- `npm run lint-biome` - Run Biome linter with auto-fix
- `npm run lint-ts` - Run TypeScript type checking
- `npm test` - Run all tests
- `npm test -- /path/to/file.test.ts` - Run a specific test file
- `npm test -- -t "test name"` - Run tests matching description
- `npm run test-coverage` - Run tests with coverage report
- `npm run repomix` - Build and run the CLI tool

## Code Style Guidelines
- **Formatting**: 2 spaces indentation, 120 char line width, single quotes, trailing commas
- **Imports**: Use `node:` prefix for built-ins, `.js` extension for relative imports, `import type` for types
- **TypeScript**: Strict type checking, explicit return types, prefer interfaces for object types
- **Error Handling**: Custom errors extend `RepomixError`, descriptive messages, proper try/catch
- **Dependencies**: Inject dependencies through deps object parameter for testability
- **File Structure**: Keep files under 250 lines, organize by feature, use workers for concurrent operations
- **Testing**: Use Vitest, mock dependencies, descriptive test names, arrange/act/assert pattern

## Naming Conventions
- PascalCase: Classes, interfaces, types
- camelCase: Variables, functions, methods, file names
- Test files: `[filename].test.ts`

## Git Workflow
- Write detailed commit messages focusing on the "why" rather than the "what"
- Run `npm run lint && npm test` before committing changes
</file>

<file path="CODE_OF_CONDUCT.md">
# Repomix Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we, as contributors and maintainers, pledge to make participation in our project and community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contribute to creating a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as physical or electronic addresses, without explicit permission
* Other conduct that could reasonably be considered inappropriate in a professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that do not align with this Code of Conduct or to ban temporarily or permanently any contributor for behaviors that they deem inappropriate, threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [koukun0120@gmail.com](mailto:koukun0120@gmail.com). All complaints will be reviewed and investigated, resulting in a response deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality concerning the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html).

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this Code of Conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq).
</file>

<file path="CONTRIBUTING.md">
# Contribution Guide

Thanks for your interest in **Repomix**! 🚀 We'd love your help to make it even better. Here's how you can get involved:


- **Create an Issue**: Spot a bug? Have an idea for a new feature? Let us know by creating an issue.
- **Submit a Pull Request**: Found something to fix or improve? Jump in and submit a PR!
- **Spread the Word**: Share your experience with Repomix on social media, blogs, or with your tech community.
- **Use Repomix**: The best feedback comes from real-world usage, so feel free to integrate Repomix into your own projects!

## Maintainers

Repomix is maintained by Yamadashy ([@yamadashy](https://github.com/yamadashy)). While all contributions are welcome, please understand that not every suggestion may be accepted if they don't align with the project's goals or coding standards.

---

## Pull Requests

Before submitting a Pull Request, please ensure:

1. Your code passes all tests: Run `npm run test`
2. Your code adheres to our linting standards: Run `npm run lint`
3. You have updated relevant documentation (especially README.md) if you've added or changed functionality.

## Local Development

To set up Repomix for local development:

```bash
git clone https://github.com/yamadashy/repomix.git
cd repomix
npm install
```

To run Repomix locally:

```bash
npm run repomix
```

### Docker Usage
You can also run Repomix using Docker. Here's how:

First, build the Docker image:
```bash
docker build -t repomix .
```

Then, run the Docker container:
```bash
docker run -v ./:/app -it --rm repomix
```

### Coding Style

We use [Biome](https://biomejs.dev/) for linting and formatting. Please make sure your code follows the style guide by running:

```bash
npm run lint
```

### Testing

We use [Vitest](https://vitest.dev/) for testing. To run the tests:

```bash
npm run test
```

For test coverage:

```bash
npm run test-coverage
```

### Documentation

When adding new features or making changes, please update the relevant documentation in the README.md file.

### Website Development

The Repomix website is built with [VitePress](https://vitepress.dev/). To run the website locally:

```bash
# Prerequisites: Docker must be installed on your system

# Start the website development server
npm run website

# Access the website at http://localhost:5173/
```

The website source code is located in the `website` directory. The main components are:

- `website/client`: Frontend code (Vue.js components, styles, etc.)
- `website/server`: Backend API server

When updating documentation, you only need to update the English version (`website/client/src/en/`).
The maintainers will handle translations to other languages.

## Releasing

New versions are managed by the maintainer. If you think a release is needed, open an issue to discuss it

Thank you for contributing to Repomix!
</file>

<file path="Dockerfile">
FROM node:22-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
    git \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

RUN mkdir /repomix
WORKDIR /repomix

# Install dependencies and build repomix, then link the package to the global scope
# To reduce the size of the layer, npm ci and npm link are executed in the same RUN command
COPY . .
RUN npm ci \
    && npm run build \
    && npm link \
    && npm ci --omit=dev \
    && npm cache clean --force

WORKDIR /app

# Check the operation of repomix
RUN repomix --version
RUN repomix --help

ENTRYPOINT ["repomix"]
</file>

<file path="LICENSE">
Copyright 2024 Kazuki Yamada

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</file>

<file path="repomix-instruction.md">
# Repomix Project Structure and Overview

This document provides a structural overview of the Repomix project, designed to aid AI code assistants (like Copilot) in understanding the codebase.

Please refer to `README.md` for a complete and up-to-date project overview, and `CONTRIBUTING.md` for implementation guidelines and contribution procedures.

## Project Overview

Repomix is a tool that packs the contents of a software repository into a single file, making it easier for AI systems to analyze and process the codebase. It supports various output formats (plain text, XML, Markdown), ignores files based on configurable patterns, and performs security checks to exclude potentially sensitive information.

## Directory Structure

The project is organized into the following directories:

```
repomix/
├── src/ # Main source code
│   ├── cli/ # Command-line interface logic (argument parsing, command handling, output)
│   ├── config/ # Configuration loading, schema, and defaults
│   ├── core/ # Core logic of Repomix
│   │   ├── file/ # File handling (reading, processing, searching, tree structure generation, git commands)
│   │   ├── metrics/ # Calculating code metrics (character count, token count)
│   │   ├── output/ # Output generation (different styles, headers, etc.)
│   │   ├── packager/ # Orchestrates file collection, processing, output, and clipboard operations.
│   │   ├── security/ # Security checks to exclude sensitive files
│   │   ├── tokenCount/ # Token counting using Tiktoken
│   │   └── tree-sitter/ # Code parsing using Tree-sitter and language-specific queries
│   └── shared/ # Shared utilities and types (error handling, logging, helper functions)
├── tests/ # Unit and integration tests (organized mirroring src/)
│   ├── cli/
│   ├── config/
│   ├── core/
│   ├── integration-tests/
│   ├── shared/
│   └── testing/
└── website/ # Documentation website (VitePress)
    ├── client/      # Client-side code (Vue.js components, styles, configuration)
    │   ├── .vitepress/  # VitePress configuration and theme
    │   │   ├── config/  # Site configuration files (navigation, sidebar, etc.)
    │   │   └── theme/   # Custom theme and styles
    │   ├── components/ # Vue.js components for the website
    │   └── src/        # Markdown files for the documentation in various languages (en, ja, etc.)
    └── server/      # Server-side API (for remote repository processing)
        └── src/       # Server source code (API endpoints, request handling)
```

----------------------------------------------------------------

# Coding Guidelines
- Follow the Airbnb JavaScript Style Guide.
- Split files into smaller, focused units when appropriate:
  - Aim to keep code files under 250 lines. If a file exceeds 250 lines, split it into multiple files based on functionality.
- Add comments to clarify non-obvious logic. **Ensure all comments are written in English.**
- Provide corresponding unit tests for all new features.
- After implementation, verify changes by running:
  ```bash
  npm run lint  # Ensure code style compliance
  npm run test  # Verify all tests pass
  ```

## Dependencies and Testing
- Inject dependencies through a deps object parameter for testability
- Example:
  ```typescript
  export const functionName = async (
    param1: Type1,
    param2: Type2,
    deps = {
      defaultFunction1,
      defaultFunction2,
    }
  ) => {
    // Use deps.defaultFunction1() instead of direct call
  };
  ```
- Mock dependencies by passing test doubles through deps object
- Use vi.mock() only when dependency injection is not feasible

## Generate Comprehensive Output
- Include all content without abbreviation, unless specified otherwise
- Optimize for handling large codebases while maintaining output quality

----------------------------------------------------------------

# GitHub Release Note Guidelines
When writing release notes, please follow these guidelines:

- When referencing issues or PRs, use the gh command to verify the content:
  ```bash
  gh issue view <issue-number>  # For checking issue content
  gh pr view <pr-number>        # For checking PR content
  ```
  This helps ensure accuracy in release note descriptions.

Here are some examples of release notes that follow the guidelines:

v0.2.25
````md
This release brings significant improvements to output formatting and introduces flexible remote repository handling capabilities along with enhanced logging features.

# Improvements ⚡

## Remote Repository Enhancement (#335)
- Added branch/tag parsing directly from repository URLs:
```bash
repomix --remote https://github.com/yamadashy/repomix/tree/0.1.x
```
Functions identically to:
```bash
repomix --remote https://github.com/yamadashy/repomix --remote-branch 0.1.x
```

Special thanks to @huy-trn for implementing this user-friendly feature!

## Enhanced Output Formatting (#328, #329, #330)
- Added "End of Codebase" marker for better clarity in output
- Improved output header accuracy:
  - Better representation of codebase scope
  - Clear indication when using `--include` or `--ignore` options

Special thanks to @gitkenan for adding the "End of Codebase" marker and reporting the header issue!

## Path Pattern Support (#337)
- Added support for special characters in paths:
  - Handles parentheses in include patterns (e.g., `src/(categories)/**/*`)
  - Improved escaping for `[]` and `{}`
  - Essential for Next.js route groups and similar frameworks

Thank you @matheuscoelhomalta for improving path pattern support!

# How to Update

```bash
npm update -g repomix
```

---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.
````

v0.2.24
````md
This release significantly enhances configuration flexibility with comprehensive CLI flag support and expands default ignore patterns for better project scaffolding. 

# What's New 🚀

## CLI Flags Revolution (#324)
- New command-line configuration now available.

```
- `--no-gitignore`: Disable .gitignore file usage
- `--no-default-patterns`: Disable default patterns
- `--header-text <text>`: Custom text to include in the file header
- `--instruction-file-path <path>`: Path to a file containing detailed custom instructions
- `--include-empty-directories`: Include empty directories in the output
```

Special recognition to @massdo for driving ecosystem growth.

# Improvements ⚡

## Enhanced Ignore Patterns (#318, #322)
- Expanded default ignores for Rust projects:
  - `target/`, `Cargo.lock`, build artifacts
  - PHP, Ruby, Go, Elixir, Haskell: package manager lock files

To @boralg for helping curate Rust-specific patterns!

# How to Update
```bash
npm update -g repomix
```

---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.
````

v0.2.23
````md
This release adds significant performance improvements for large repositories, making Repomix faster and more efficient when needed.

# Improvements ⚡

## Parallel Processing Enhancement (#309)
- Implemented worker threads using [Piscina](https://github.com/piscinajs/piscina) for parallel processing

### Benchmark Results
- `yamadashy.repomix`: No significant change
  - Before: 868.73 millis
  - After: 671.26 millis
- `facebook/react`: 29x faster
  - Before: 123.31 secs
  - After: 4.19 secs
- `vercel/next.js`: 58x faster
  - Before: 17.85 mins
  - After: 17.27 secs

Note: While Repomix is not primarily designed for processing large repositories, and speed is not a primary goal, faster processing can provide a better user experience when working with larger codebases.

# How to Update

```bash
npm update -g repomix
```


---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.
````

v0.2.22
````md
This release introduces significant improvements to large file handling and expands the Repomix ecosystem with new tools and community channels.

# Improvements ⚡ 

## Improved Large File Handling (#302)

- Added a file size limit check (50MB) to prevent memory issues
- Graceful error handling for large files with clear user guidance:

Special thanks to @slavashvets for their continued contributions!

# Ecosystem Growth 🤝 

## New VS Code Extension (#300)
A community-created VS Code extension "Repomix Runner" is now available:
- Run Repomix directly from VS Code
- Extension by @massdo: [View on VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner)

Thank you @massdo for bringing Repomix to VS Code and expanding our tooling ecosystem!

## Official Social Media
- Launched official Repomix X (Twitter) account: [@repomix_ai](https://x.com/repomix_ai)
  - Follow for updates, tips, and community highlights

# How to Update

```bash
npm update -g repomix
```

---

Join our growing community on [Discord](https://discord.gg/BF8GxZHE2C) and follow us on [X](https://x.com/repomix_ai) for updates!
````

v0.2.21
````md
This release introduces significant improvements to output formatting and documentation, featuring a new parsable style option for enhanced XML handling.

# What's New 🚀 

## Enhanced Output Style Control (#287)
- Added new `parsableStyle` option for better output handling:
  - Ensures output strictly follows the specification of the chosen format
  - Provides properly escaped XML output with fast-xml-parser
  - Dynamically adjusts markdown code block delimiters to avoid content conflicts
- Available via CLI flag `--parsable-style` or in configuration file

Special thanks to @atollk for their first contribution!

# Documentation 📚

## README Enhancements (#296)
- Updated Homebrew installation documentation to include Linux support

Special thanks to @chenrui333 for their continued contributions!

## Website Multi-Language Support (#293)
- Enhanced multi-language support in [repomix.com](https://repomix.com)

# How to Update

To update to the latest version, run:
```bash
npm update -g repomix
```


---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.
</file>

<file path="SECURITY.md">
# Security Policy

## Reporting a Vulnerability

To securely report a vulnerability, please [open an advisory on GitHub](https://github.com/yamadashy/repomix/security/advisories/new) or report it by sending an email to `koukun0120@gmail.com`.
</file>

<file path="tsconfig.build.json">
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "rootDir": "./src"
  },
  "include": ["./src/**/*"]
}
</file>

<file path="tsconfig.json">
{
  "compileOnSave": false,
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "es2016",
    "outDir": "./lib",
    "rootDir": ".",
    "strict": true,
    "esModuleInterop": true,
    "noImplicitAny": true,
    "skipLibCheck": true,
    "lib": ["es2022"],
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "types": ["node", "picocolors"]
  },
  "include": ["src/**/*", "tests/**/*"],
  "exclude": ["tests/integration-tests/fixtures"]
}
</file>

<file path="typos.toml">
[files]
extend-exclude = [
    "website/client/src/**/*.md"
]
</file>

<file path="vitest.config.ts">
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    include: ['tests/**/*.test.ts'],
    coverage: {
      include: ['src/**/*'],
      exclude: ['src/index.ts'],
      reporter: ['text', 'json', 'html'],
    },
    watch: false,
  },
});
</file>

<file path=".github/CODEOWNERS">
# Default owner for everything in the repo
* @yamadashy
</file>

<file path="src/cli/actions/initAction.ts">
import fs from 'node:fs/promises';
import path from 'node:path';
import * as prompts from '@clack/prompts';
import pc from 'picocolors';
import {
  type RepomixConfigFile,
  type RepomixOutputStyle,
  defaultConfig,
  defaultFilePathMap,
} from '../../config/configSchema.js';
import { getGlobalDirectory } from '../../config/globalDirectory.js';
import { logger } from '../../shared/logger.js';

const onCancelOperation = () => {
  prompts.cancel('Initialization cancelled.');
  process.exit(0);
};

export const runInitAction = async (rootDir: string, isGlobal: boolean): Promise<void> => {
  prompts.intro(pc.bold(`Welcome to Repomix ${isGlobal ? 'Global ' : ''}Configuration!`));

  try {
    // Step 1: Ask if user wants to create a config file
    const isCreatedConfig = await createConfigFile(rootDir, isGlobal);

    // Step 2: Ask if user wants to create a .repomixignore file
    const isCreatedIgnoreFile = await createIgnoreFile(rootDir, isGlobal);

    if (!isCreatedConfig && !isCreatedIgnoreFile) {
      prompts.outro(
        pc.yellow('No files were created. You can run this command again when you need to create configuration files.'),
      );
    } else {
      prompts.outro(pc.green('Initialization complete! You can now use Repomix with your specified settings.'));
    }
  } catch (error) {
    logger.error('An error occurred during initialization:', error);
  }
};

export const createConfigFile = async (rootDir: string, isGlobal: boolean): Promise<boolean> => {
  const configPath = path.resolve(isGlobal ? getGlobalDirectory() : rootDir, 'repomix.config.json');

  const isCreateConfig = await prompts.confirm({
    message: `Do you want to create a ${isGlobal ? 'global ' : ''}${pc.green('repomix.config.json')} file?`,
  });
  if (!isCreateConfig) {
    prompts.log.info(`Skipping ${pc.green('repomix.config.json')} file creation.`);
    return false;
  }
  if (prompts.isCancel(isCreateConfig)) {
    onCancelOperation();
    return false;
  }

  let isConfigFileExists = false;
  try {
    await fs.access(configPath);
    isConfigFileExists = true;
  } catch {
    // File doesn't exist, so we can proceed
  }

  if (isConfigFileExists) {
    const isOverwrite = await prompts.confirm({
      message: `A ${isGlobal ? 'global ' : ''}${pc.green('repomix.config.json')} file already exists. Do you want to overwrite it?`,
    });
    if (!isOverwrite) {
      prompts.log.info(`Skipping ${pc.green('repomix.config.json')} file creation.`);
      return false;
    }
    if (prompts.isCancel(isOverwrite)) {
      onCancelOperation();
      return false;
    }
  }

  const options = await prompts.group(
    {
      outputStyle: () => {
        return prompts.select({
          message: 'Output style:',
          options: [
            { value: 'xml', label: 'XML', hint: 'Structured XML format' },
            { value: 'markdown', label: 'Markdown', hint: 'Markdown format' },
            { value: 'plain', label: 'Plain', hint: 'Simple text format' },
          ],
          initialValue: defaultConfig.output.style,
        });
      },
      outputFilePath: ({ results }) => {
        const defaultFilePath = defaultFilePathMap[results.outputStyle as RepomixOutputStyle];
        return prompts.text({
          message: 'Output file path:',
          initialValue: defaultFilePath,
          validate: (value) => (value.length === 0 ? 'Output file path is required' : undefined),
        });
      },
    },
    {
      onCancel: onCancelOperation,
    },
  );

  const config: RepomixConfigFile = {
    ...defaultConfig,
    output: {
      ...defaultConfig.output,
      filePath: options.outputFilePath as string,
      style: options.outputStyle as RepomixOutputStyle,
    },
  };

  await fs.mkdir(path.dirname(configPath), { recursive: true });
  await fs.writeFile(configPath, JSON.stringify(config, null, 2));

  const relativeConfigPath = path.relative(rootDir, configPath);

  prompts.log.success(
    pc.green(`${isGlobal ? 'Global config' : 'Config'} file created!\n`) + pc.dim(`Path: ${relativeConfigPath}`),
  );

  return true;
};

export const createIgnoreFile = async (rootDir: string, isGlobal: boolean): Promise<boolean> => {
  if (isGlobal) {
    prompts.log.info(`Skipping ${pc.green('.repomixignore')} file creation for global configuration.`);
    return false;
  }

  const ignorePath = path.resolve(rootDir, '.repomixignore');
  const createIgnore = await prompts.confirm({
    message: `Do you want to create a ${pc.green('.repomixignore')} file?`,
  });
  if (!createIgnore) {
    prompts.log.info(`Skipping ${pc.green('.repomixignore')} file creation.`);
    return false;
  }
  if (prompts.isCancel(createIgnore)) {
    onCancelOperation();
    return false;
  }

  let isIgnoreFileExists = false;
  try {
    await fs.access(ignorePath);
    isIgnoreFileExists = true;
  } catch {
    // File doesn't exist, so we can proceed
  }

  if (isIgnoreFileExists) {
    const overwrite = await prompts.confirm({
      message: `A ${pc.green('.repomixignore')} file already exists. Do you want to overwrite it?`,
    });

    if (!overwrite) {
      prompts.log.info(`${pc.green('.repomixignore')} file creation skipped. Existing file will not be modified.`);
      return false;
    }
  }

  const defaultIgnoreContent = `# Add patterns to ignore here, one per line
# Example:
# *.log
# tmp/
`;

  await fs.writeFile(ignorePath, defaultIgnoreContent);
  prompts.log.success(
    pc.green('Created .repomixignore file!\n') + pc.dim(`Path: ${path.relative(rootDir, ignorePath)}`),
  );

  return true;
};
</file>

<file path="src/core/file/filePathSort.ts">
import path from 'node:path';

// Sort paths for general use (not affected by git change count)
export const sortPaths = (filePaths: string[]): string[] => {
  return filePaths.sort((a, b) => {
    const partsA = a.split(path.sep);
    const partsB = b.split(path.sep);

    for (let i = 0; i < Math.min(partsA.length, partsB.length); i++) {
      if (partsA[i] !== partsB[i]) {
        const isLastA = i === partsA.length - 1;
        const isLastB = i === partsB.length - 1;

        if (!isLastA && isLastB) return -1; // Directory
        if (isLastA && !isLastB) return 1; // File

        return partsA[i].localeCompare(partsB[i]); // Alphabetical order
      }
    }

    // Sort by path length when all parts are equal
    return partsA.length - partsB.length;
  });
};
</file>

<file path="src/core/file/gitCommand.ts">
import { execFile } from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';
import { promisify } from 'node:util';
import { RepomixError } from '../../shared/errorHandle.js';
import { logger } from '../../shared/logger.js';

const execFileAsync = promisify(execFile);

export const getFileChangeCount = async (
  directory: string,
  maxCommits = 100,
  deps = {
    execFileAsync,
  },
): Promise<Record<string, number>> => {
  try {
    const result = await deps.execFileAsync('git', [
      '-C',
      directory,
      'log',
      '--pretty=format:',
      '--name-only',
      '-n',
      maxCommits.toString(),
    ]);

    const fileChangeCounts: Record<string, number> = {};
    const lines = result.stdout.split('\n').filter(Boolean);

    for (const line of lines) {
      fileChangeCounts[line] = (fileChangeCounts[line] || 0) + 1;
    }

    return fileChangeCounts;
  } catch (error) {
    logger.trace('Failed to get file change counts:', (error as Error).message);
    return {};
  }
};

export const isGitInstalled = async (
  deps = {
    execFileAsync,
  },
) => {
  try {
    const result = await deps.execFileAsync('git', ['--version']);
    return !result.stderr;
  } catch (error) {
    logger.trace('Git is not installed:', (error as Error).message);
    return false;
  }
};

export const execGitShallowClone = async (
  url: string,
  directory: string,
  remoteBranch?: string,
  deps = {
    execFileAsync,
  },
) => {
  // Check if the URL is valid
  try {
    new URL(url);
  } catch (error) {
    throw new RepomixError(`Invalid repository URL. Please provide a valid URL. url: ${url}`);
  }

  if (remoteBranch) {
    await deps.execFileAsync('git', ['-C', directory, 'init']);
    await deps.execFileAsync('git', ['-C', directory, 'remote', 'add', 'origin', url]);
    try {
      await deps.execFileAsync('git', ['-C', directory, 'fetch', '--depth', '1', 'origin', remoteBranch]);
      await deps.execFileAsync('git', ['-C', directory, 'checkout', 'FETCH_HEAD']);
    } catch (err: unknown) {
      // git fetch --depth 1 origin <short SHA> always throws "couldn't find remote ref" error
      const isRefNotfoundError =
        err instanceof Error && err.message.includes(`couldn't find remote ref ${remoteBranch}`);

      if (!isRefNotfoundError) {
        // Rethrow error as nothing else we can do
        throw err;
      }

      // Short SHA detection - matches a hexadecimal string of 4 to 39 characters
      // If the string matches this regex, it MIGHT be a short SHA
      // If the string doesn't match, it is DEFINITELY NOT a short SHA
      const isNotShortSHA = !remoteBranch.match(/^[0-9a-f]{4,39}$/i);

      if (isNotShortSHA) {
        // Rethrow error as nothing else we can do
        throw err;
      }

      // Maybe the error is due to a short SHA, let's try again
      // Can't use --depth 1 here as we need to fetch the specific commit
      await deps.execFileAsync('git', ['-C', directory, 'fetch', 'origin']);
      await deps.execFileAsync('git', ['-C', directory, 'checkout', remoteBranch]);
    }
  } else {
    await deps.execFileAsync('git', ['clone', '--depth', '1', url, directory]);
  }

  // Clean up .git directory
  await fs.rm(path.join(directory, '.git'), { recursive: true, force: true });
};
</file>

<file path="src/core/output/outputGenerate.ts">
import fs from 'node:fs/promises';
import path from 'node:path';
import { XMLBuilder } from 'fast-xml-parser';
import Handlebars from 'handlebars';
import type { RepomixConfigMerged } from '../../config/configSchema.js';
import { RepomixError } from '../../shared/errorHandle.js';
import { type FileSearchResult, searchFiles } from '../file/fileSearch.js';
import { generateTreeString } from '../file/fileTreeGenerate.js';
import type { ProcessedFile } from '../file/fileTypes.js';
import type { OutputGeneratorContext, RenderContext } from './outputGeneratorTypes.js';
import { sortOutputFiles } from './outputSort.js';
import {
  generateHeader,
  generateSummaryFileFormat,
  generateSummaryNotes,
  generateSummaryPurpose,
  generateSummaryUsageGuidelines,
} from './outputStyleDecorate.js';
import { getMarkdownTemplate } from './outputStyles/markdownStyle.js';
import { getPlainTemplate } from './outputStyles/plainStyle.js';
import { getXmlTemplate } from './outputStyles/xmlStyle.js';

const calculateMarkdownDelimiter = (files: ReadonlyArray<ProcessedFile>): string => {
  const maxBackticks = files
    .flatMap((file) => file.content.match(/`+/g) ?? [])
    .reduce((max, match) => Math.max(max, match.length), 0);
  return '`'.repeat(Math.max(3, maxBackticks + 1));
};

const createRenderContext = (outputGeneratorContext: OutputGeneratorContext): RenderContext => {
  return {
    generationHeader: generateHeader(outputGeneratorContext.config, outputGeneratorContext.generationDate), // configを追加
    summaryPurpose: generateSummaryPurpose(),
    summaryFileFormat: generateSummaryFileFormat(),
    summaryUsageGuidelines: generateSummaryUsageGuidelines(
      outputGeneratorContext.config,
      outputGeneratorContext.instruction,
    ),
    summaryNotes: generateSummaryNotes(outputGeneratorContext.config),
    headerText: outputGeneratorContext.config.output.headerText,
    instruction: outputGeneratorContext.instruction,
    treeString: outputGeneratorContext.treeString,
    processedFiles: outputGeneratorContext.processedFiles,
    fileSummaryEnabled: outputGeneratorContext.config.output.fileSummary,
    directoryStructureEnabled: outputGeneratorContext.config.output.directoryStructure,
    escapeFileContent: outputGeneratorContext.config.output.parsableStyle,
    markdownCodeBlockDelimiter: calculateMarkdownDelimiter(outputGeneratorContext.processedFiles),
  };
};

const generateParsableXmlOutput = async (renderContext: RenderContext): Promise<string> => {
  const xmlBuilder = new XMLBuilder({ ignoreAttributes: false });
  const xmlDocument = {
    repomix: {
      '#text': renderContext.generationHeader,
      file_summary: renderContext.fileSummaryEnabled
        ? {
            '#text': 'This section contains a summary of this file.',
            purpose: renderContext.summaryPurpose,
            file_format: `${renderContext.summaryFileFormat}
4. Repository files, each consisting of:
  - File path as an attribute
  - Full contents of the file`,
            usage_guidelines: renderContext.summaryUsageGuidelines,
            notes: renderContext.summaryNotes,
            additional_info: {
              user_provided_header: renderContext.headerText,
            },
          }
        : undefined,
      directory_structure: renderContext.directoryStructureEnabled ? renderContext.treeString : undefined,
      files: {
        '#text': "This section contains the contents of the repository's files.",
        file: renderContext.processedFiles.map((file) => ({
          '#text': file.content,
          '@_path': file.path,
        })),
      },
      instruction: renderContext.instruction ? renderContext.instruction : undefined,
    },
  };
  try {
    return xmlBuilder.build(xmlDocument);
  } catch (error) {
    throw new RepomixError(
      `Failed to generate XML output: ${error instanceof Error ? error.message : 'Unknown error'}`,
    );
  }
};

const generateHandlebarOutput = async (config: RepomixConfigMerged, renderContext: RenderContext): Promise<string> => {
  let template: string;
  switch (config.output.style) {
    case 'xml':
      template = getXmlTemplate();
      break;
    case 'markdown':
      template = getMarkdownTemplate();
      break;
    case 'plain':
      template = getPlainTemplate();
      break;
    default:
      throw new RepomixError(`Unknown output style: ${config.output.style}`);
  }

  try {
    const compiledTemplate = Handlebars.compile(template);
    return `${compiledTemplate(renderContext).trim()}\n`;
  } catch (error) {
    throw new RepomixError(`Failed to compile template: ${error instanceof Error ? error.message : 'Unknown error'}`);
  }
};

export const generateOutput = async (
  rootDirs: string[],
  config: RepomixConfigMerged,
  processedFiles: ProcessedFile[],
  allFilePaths: string[],
  deps = {
    buildOutputGeneratorContext,
    generateHandlebarOutput,
    generateParsableXmlOutput,
    sortOutputFiles,
  },
): Promise<string> => {
  // Sort processed files by git change count if enabled
  const sortedProcessedFiles = await deps.sortOutputFiles(processedFiles, config);

  const outputGeneratorContext = await deps.buildOutputGeneratorContext(
    rootDirs,
    config,
    allFilePaths,
    sortedProcessedFiles,
  );
  const renderContext = createRenderContext(outputGeneratorContext);

  if (!config.output.parsableStyle) return deps.generateHandlebarOutput(config, renderContext);
  switch (config.output.style) {
    case 'xml':
      return deps.generateParsableXmlOutput(renderContext);
    case 'markdown':
      return deps.generateHandlebarOutput(config, renderContext);
    default:
      return deps.generateHandlebarOutput(config, renderContext);
  }
};

export const buildOutputGeneratorContext = async (
  rootDirs: string[],
  config: RepomixConfigMerged,
  allFilePaths: string[],
  processedFiles: ProcessedFile[],
): Promise<OutputGeneratorContext> => {
  let repositoryInstruction = '';

  if (config.output.instructionFilePath) {
    const instructionPath = path.resolve(config.cwd, config.output.instructionFilePath);
    try {
      repositoryInstruction = await fs.readFile(instructionPath, 'utf-8');
    } catch {
      throw new RepomixError(`Instruction file not found at ${instructionPath}`);
    }
  }

  let emptyDirPaths: string[] = [];
  if (config.output.includeEmptyDirectories) {
    try {
      emptyDirPaths = (await Promise.all(rootDirs.map((rootDir) => searchFiles(rootDir, config)))).reduce(
        (acc: FileSearchResult, curr: FileSearchResult) =>
          ({
            filePaths: [...acc.filePaths, ...curr.filePaths],
            emptyDirPaths: [...acc.emptyDirPaths, ...curr.emptyDirPaths],
          }) as FileSearchResult,
        { filePaths: [], emptyDirPaths: [] },
      ).emptyDirPaths;
    } catch (error) {
      if (error instanceof Error) {
        throw new RepomixError(`Failed to search for empty directories: ${error.message}`);
      }
    }
  }

  return {
    generationDate: new Date().toISOString(),
    treeString: generateTreeString(allFilePaths, emptyDirPaths),
    processedFiles,
    config,
    instruction: repositoryInstruction,
  };
};
</file>

<file path="src/core/output/outputSort.ts">
import fs from 'node:fs/promises';
import path from 'node:path';
import type { RepomixConfigMerged } from '../../config/configSchema.js';
import { logger } from '../../shared/logger.js';
import type { ProcessedFile } from '../file/fileTypes.js';
import { getFileChangeCount, isGitInstalled } from '../file/gitCommand.js';

// Sort files by git change count for output
export const sortOutputFiles = async (
  files: ProcessedFile[],
  config: RepomixConfigMerged,
  deps = {
    getFileChangeCount,
    isGitInstalled,
  },
): Promise<ProcessedFile[]> => {
  // If git sort is not enabled, return original order
  if (!config.output.git?.sortByChanges) {
    logger.trace('Git sort is not enabled');
    return files;
  }

  // Check if Git is installed
  const gitInstalled = await deps.isGitInstalled();
  if (!gitInstalled) {
    logger.trace('Git is not installed');
    return files;
  }

  // If `.git` directory is not found, return original order
  const gitFolderPath = path.resolve(config.cwd, '.git');
  try {
    await fs.access(gitFolderPath);
  } catch {
    logger.trace('Git folder not found');
    return files;
  }

  try {
    // Get file change counts
    const fileChangeCounts = await deps.getFileChangeCount(config.cwd, config.output.git?.sortByChangesMaxCommits);

    const sortedFileChangeCounts = Object.entries(fileChangeCounts).sort((a, b) => b[1] - a[1]);
    logger.trace('Git File change counts max commits:', config.output.git?.sortByChangesMaxCommits);
    logger.trace('Git File change counts:', sortedFileChangeCounts);

    // Sort files by change count (files with more changes go to the bottom)
    return [...files].sort((a, b) => {
      const countA = fileChangeCounts[a.path] || 0;
      const countB = fileChangeCounts[b.path] || 0;
      return countA - countB;
    });
  } catch (error) {
    // If git command fails, return original order
    return files;
  }
};
</file>

<file path="src/core/output/outputStyleDecorate.ts">
import type { RepomixConfigMerged } from '../../config/configSchema.js';

interface ContentInfo {
  selection: {
    isEntireCodebase: boolean;
    include?: boolean;
    ignore?: boolean;
    gitignore?: boolean;
    defaultIgnore?: boolean;
  };
  processing: {
    commentsRemoved: boolean;
    emptyLinesRemoved: boolean;
    securityCheckEnabled: boolean;
    showLineNumbers: boolean;
    parsableStyle: boolean;
    compressed: boolean;
  };
  sorting: {
    gitChanges: boolean;
  };
}

export const analyzeContent = (config: RepomixConfigMerged): ContentInfo => {
  return {
    selection: {
      isEntireCodebase: !config.include.length && !config.ignore.customPatterns.length,
      include: config.include.length > 0,
      ignore: config.ignore.customPatterns.length > 0,
      gitignore: config.ignore.useGitignore,
      defaultIgnore: config.ignore.useDefaultPatterns,
    },
    processing: {
      commentsRemoved: config.output.removeComments,
      emptyLinesRemoved: config.output.removeEmptyLines,
      securityCheckEnabled: config.security.enableSecurityCheck,
      showLineNumbers: config.output.showLineNumbers,
      parsableStyle: config.output.parsableStyle,
      compressed: config.output.compress,
    },
    sorting: {
      gitChanges: config.output.git?.sortByChanges ?? false,
    },
  };
};

export const generateHeader = (config: RepomixConfigMerged, generationDate: string): string => {
  const info = analyzeContent(config);

  // Generate selection description
  let description: string;
  if (info.selection.isEntireCodebase) {
    description = 'This file is a merged representation of the entire codebase';
  } else {
    const parts = [];
    if (info.selection.include) {
      parts.push('specifically included files');
    }
    if (info.selection.ignore) {
      parts.push('files not matching ignore patterns');
    }
    description = `This file is a merged representation of a subset of the codebase, containing ${parts.join(' and ')}`;
  }

  // Add processing information
  const processingNotes = [];
  if (info.processing.commentsRemoved) {
    processingNotes.push('comments have been removed');
  }
  if (info.processing.emptyLinesRemoved) {
    processingNotes.push('empty lines have been removed');
  }
  if (info.processing.showLineNumbers) {
    processingNotes.push('line numbers have been added');
  }
  if (info.processing.parsableStyle) {
    processingNotes.push(`content has been formatted for parsing in ${config.output.style} style`);
  }
  if (info.processing.compressed) {
    processingNotes.push('content has been compressed (code blocks are separated by ⋮---- delimiter)');
  }
  if (!info.processing.securityCheckEnabled) {
    processingNotes.push('security check has been disabled');
  }

  const processingInfo =
    processingNotes.length > 0 ? `The content has been processed where ${processingNotes.join(', ')}.` : '';

  return `${description}, combined into a single document by Repomix.\n${processingInfo}`.trim();
};

export const generateSummaryPurpose = (): string => {
  return `
This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.
`.trim();
};

export const generateSummaryFileFormat = (): string => {
  return `
The content is organized as follows:
1. This summary section
2. Repository information
3. Directory structure
`.trim();
};

export const generateSummaryUsageGuidelines = (config: RepomixConfigMerged, repositoryInstruction: string): string => {
  return `
- This file should be treated as read-only. Any changes should be made to the
  original repository files, not this packed version.
- When processing this file, use the file path to distinguish
  between different files in the repository.
- Be aware that this file may contain sensitive information. Handle it with
  the same level of security as you would the original repository.
${config.output.headerText ? '- Pay special attention to the Repository Description. These contain important context and guidelines specific to this project.' : ''}
${repositoryInstruction ? '- Pay special attention to the Repository Instruction. These contain important context and guidelines specific to this project.' : ''}
`.trim();
};

export const generateSummaryNotes = (config: RepomixConfigMerged): string => {
  const info = analyzeContent(config);
  const notes = [
    "- Some files may have been excluded based on .gitignore rules and Repomix's configuration",
    '- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files',
  ];

  // File selection notes
  if (info.selection.include) {
    notes.push(`- Only files matching these patterns are included: ${config.include.join(', ')}`);
  }
  if (info.selection.ignore) {
    notes.push(`- Files matching these patterns are excluded: ${config.ignore.customPatterns.join(', ')}`);
  }
  if (info.selection.gitignore) {
    notes.push('- Files matching patterns in .gitignore are excluded');
  }
  if (info.selection.defaultIgnore) {
    notes.push('- Files matching default ignore patterns are excluded');
  }

  // Processing notes
  if (info.processing.commentsRemoved) {
    notes.push('- Code comments have been removed from supported file types');
  }
  if (info.processing.emptyLinesRemoved) {
    notes.push('- Empty lines have been removed from all files');
  }
  if (info.processing.showLineNumbers) {
    notes.push('- Line numbers have been added to the beginning of each line');
  }
  if (info.processing.parsableStyle) {
    notes.push(`- Content has been formatted for parsing in ${config.output.style} style`);
  }
  if (info.processing.compressed) {
    notes.push('- Content has been compressed - code blocks are separated by ⋮---- delimiter');
  }
  if (!info.processing.securityCheckEnabled) {
    notes.push('- Security check has been disabled - content may contain sensitive information');
  }

  // Sorting notes
  if (info.sorting.gitChanges) {
    notes.push('- Files are sorted by Git change count (files with more changes are at the bottom)');
  }

  return notes.join('\n');
};
</file>

<file path="src/core/packager/writeOutputToDisk.ts">
import fs from 'node:fs/promises';
import path from 'node:path';
import type { RepomixConfigMerged } from '../../config/configSchema.js';
import { logger } from '../../shared/logger.js';

// Write output to file. path is relative to the cwd
export const writeOutputToDisk = async (output: string, config: RepomixConfigMerged): Promise<undefined> => {
  const outputPath = path.resolve(config.cwd, config.output.filePath);
  logger.trace(`Writing output to: ${outputPath}`);

  // Create output directory if it doesn't exist
  await fs.mkdir(path.dirname(outputPath), { recursive: true });

  await fs.writeFile(outputPath, output);
};
</file>

<file path="src/core/security/workers/securityCheckWorker.ts">
import { lintSource } from '@secretlint/core';
import { creator } from '@secretlint/secretlint-rule-preset-recommend';
import type { SecretLintCoreConfig } from '@secretlint/types';
import { logger } from '../../../shared/logger.js';

export interface SecurityCheckTask {
  filePath: string;
  content: string;
}

export interface SuspiciousFileResult {
  filePath: string;
  messages: string[];
}

export default async ({ filePath, content }: SecurityCheckTask) => {
  const config = createSecretLintConfig();

  try {
    const processStartAt = process.hrtime.bigint();
    const secretLintResult = await runSecretLint(filePath, content, config);
    const processEndAt = process.hrtime.bigint();

    logger.trace(
      `Checked security on ${filePath}. Took: ${(Number(processEndAt - processStartAt) / 1e6).toFixed(2)}ms`,
    );

    return secretLintResult;
  } catch (error) {
    logger.error(`Error checking security on ${filePath}:`, error);
    throw error;
  }
};

export const runSecretLint = async (
  filePath: string,
  content: string,
  config: SecretLintCoreConfig,
): Promise<SuspiciousFileResult | null> => {
  const result = await lintSource({
    source: {
      filePath: filePath,
      content: content,
      ext: filePath.split('.').pop() || '',
      contentType: 'text',
    },
    options: {
      config: config,
    },
  });

  if (result.messages.length > 0) {
    logger.trace(`Found ${result.messages.length} issues in ${filePath}`);
    logger.trace(result.messages.map((message) => `  - ${message.message}`).join('\n'));

    return {
      filePath,
      messages: result.messages.map((message) => message.message),
    };
  }

  return null;
};

export const createSecretLintConfig = (): SecretLintCoreConfig => ({
  rules: [
    {
      id: '@secretlint/secretlint-rule-preset-recommend',
      rule: creator,
    },
  ],
});
</file>

<file path="src/core/treeSitter/parseStrategies/CssParseStrategy.ts">
import type { SyntaxNode } from 'web-tree-sitter';
import type { ParseContext, ParseStrategy } from './ParseStrategy.js';

export class CssParseStrategy implements ParseStrategy {
  parseCapture(
    capture: { node: SyntaxNode; name: string },
    lines: string[],
    processedChunks: Set<string>,
    context: ParseContext,
  ): string | null {
    const { node, name } = capture;
    const startRow = node.startPosition.row;
    const endRow = node.endPosition.row;

    if (!lines[startRow]) {
      return null;
    }

    // Process CSS-specific capture names
    const isCommentCapture = name.includes('comment');
    const isSelectorCapture = name.includes('selector') || name.includes('definition.selector');
    const isAtRuleCapture = name.includes('at_rule') || name.includes('definition.at_rule');

    const shouldSelect = isCommentCapture || isSelectorCapture || isAtRuleCapture;

    if (!shouldSelect) {
      return null;
    }

    // Extract all lines for comments, only the first line for others
    let selectedLines: string[];
    if (isCommentCapture) {
      selectedLines = lines.slice(startRow, endRow + 1);
    } else {
      // For selectors and at-rules, extract only the first line
      selectedLines = [lines[startRow]];
    }

    if (selectedLines.length < 1) {
      return null;
    }

    const chunk = selectedLines.join('\n');
    const normalizedChunk = chunk.trim();

    if (processedChunks.has(normalizedChunk)) {
      return null;
    }

    processedChunks.add(normalizedChunk);
    return chunk;
  }
}
</file>

<file path="src/core/treeSitter/parseStrategies/VueParseStrategy.ts">
import type { SyntaxNode } from 'web-tree-sitter';
import type { ParseContext, ParseStrategy } from './ParseStrategy.js';

export class VueParseStrategy implements ParseStrategy {
  parseCapture(
    capture: { node: SyntaxNode; name: string },
    lines: string[],
    processedChunks: Set<string>,
    context: ParseContext,
  ): string | null {
    const { node, name } = capture;
    const startRow = node.startPosition.row;
    const endRow = node.endPosition.row;

    if (!lines[startRow]) {
      return null;
    }

    // Extract the content based on the capture type
    const selectedLines = lines.slice(startRow, endRow + 1);
    if (selectedLines.length < 1) {
      return null;
    }

    const chunk = selectedLines.join('\n');
    const normalizedChunk = chunk.trim();

    // Create a unique ID for this chunk
    const chunkId = `${name}:${startRow}`;
    if (processedChunks.has(chunkId)) {
      return null;
    }

    processedChunks.add(chunkId);
    return chunk;
  }
}
</file>

<file path="src/core/treeSitter/queries/queryCss.ts">
export const queryCss = `
(comment) @comment

(rule_set
  (selectors) @name.definition.selector
) @definition.selector

(at_rule) @definition.at_rule
`;
</file>

<file path="src/core/treeSitter/queries/queryGo.ts">
export const queryGo = `
; For repomix
(comment) @comment
(package_clause) @definition.package
(import_declaration) @definition.import
(import_spec) @definition.import
(var_declaration) @definition.variable
(const_declaration) @definition.constant

; tree-sitter-go
(
  (comment)* @doc
  .
  (function_declaration
    name: (identifier) @name) @definition.function
  (#strip! @doc "^//\\\\s*")
  (#set-adjacent! @doc @definition.function)
)

(
  (comment)* @doc
  .
  (method_declaration
    name: (field_identifier) @name) @definition.method
  (#strip! @doc "^//\\\\s*")
  (#set-adjacent! @doc @definition.method)
)

(call_expression
  function: [
    (identifier) @name
    (parenthesized_expression (identifier) @name)
    (selector_expression field: (field_identifier) @name)
    (parenthesized_expression (selector_expression field: (field_identifier) @name))
  ]) @reference.call

(type_spec
  name: (type_identifier) @name) @definition.type

(type_identifier) @name @reference.type

(package_clause "package" (package_identifier) @name)

(type_declaration (type_spec name: (type_identifier) @name type: (interface_type)))

(type_declaration (type_spec name: (type_identifier) @name type: (struct_type)))

; Import statements
(import_declaration
  (import_spec_list
    (import_spec
      path: (interpreted_string_literal) @name.reference.module))) @definition.import

(import_declaration
  (import_spec
    path: (interpreted_string_literal) @name.reference.module)) @definition.import

(package_clause
  (package_identifier) @name.reference.module) @definition.package

(var_declaration (var_spec name: (identifier) @name))

(const_declaration (const_spec name: (identifier) @name))
`;
</file>

<file path="src/core/treeSitter/queries/queryJava.ts">
export const queryJava = `
(line_comment) @comment
(block_comment) @comment

(import_declaration
  .
  (identifier) @name.reference.module) @definition.import

(package_declaration
  .
  (identifier) @name.reference.module) @definition.import

(class_declaration
  name: (identifier) @name.definition.class) @definition.class

(method_declaration
  name: (identifier) @name.definition.method) @definition.method

(method_invocation
  name: (identifier) @name.reference.call
  arguments: (argument_list) @reference.call)

(interface_declaration
  name: (identifier) @name.definition.interface) @definition.interface

(type_list
  (type_identifier) @name.reference.implementation) @reference.implementation

(object_creation_expression
  type: (type_identifier) @name.reference.class) @reference.class

(superclass (type_identifier) @name.reference.class) @reference.class
`;
</file>

<file path="src/core/treeSitter/queries/queryPython.ts">
export const queryPython = `
(comment) @comment

(expression_statement
  (string) @comment) @docstring

; Import statements
(import_statement
  name: (dotted_name) @name.reference.module) @definition.import

(import_from_statement
  module_name: (dotted_name) @name.reference.module) @definition.import

(import_from_statement
  name: (dotted_name) @name.reference.module) @definition.import

(class_definition
  name: (identifier) @name.definition.class) @definition.class

(function_definition
  name: (identifier) @name.definition.function) @definition.function

(call
  function: [
      (identifier) @name.reference.call
      (attribute
        attribute: (identifier) @name.reference.call)
  ]) @reference.call

(assignment
  left: (identifier) @name.definition.type_alias) @definition.type_alias
`;
</file>

<file path="src/core/treeSitter/queries/queryRuby.ts">
export const queryRuby = `
(comment) @comment

; Import statements
(call
  (identifier) @name.reference.module) @definition.import

; Method definitions

(
  (comment)* @doc
  .
  [
    (method
      name: (_) @name.definition.method) @definition.method
    (singleton_method
      name: (_) @name.definition.method) @definition.method
  ]
  (#strip! @doc "^#\\s*")
  (#select-adjacent! @doc @definition.method)
)

(alias
  name: (_) @name.definition.method) @definition.method

(setter
  (identifier) @ignore)

; Class definitions

(
  (comment)* @doc
  .
  [
    (class
      name: [
        (constant) @name.definition.class
        (scope_resolution
          name: (_) @name.definition.class)
      ]) @definition.class
    (singleton_class
      value: [
        (constant) @name.definition.class
        (scope_resolution
          name: (_) @name.definition.class)
      ]) @definition.class
  ]
  (#strip! @doc "^#\\s*")
  (#select-adjacent! @doc @definition.class)
)

; Module definitions

(
  (module
    name: [
      (constant) @name.definition.module
      (scope_resolution
        name: (_) @name.definition.module)
    ]) @definition.module
)

; Calls

(call method: (identifier) @name.reference.call) @reference.call

(
  [(identifier) (constant)] @name.reference.call @reference.call
  (#is-not? local)
  (#not-match? @name.reference.call "^(lambda|load|require|require_relative|__FILE__|__LINE__)$")
)
`;
</file>

<file path="src/core/treeSitter/queries/queryRust.ts">
export const queryRust = `
(line_comment) @comment
(block_comment) @comment

; Import statements
(use_declaration
  (scoped_identifier) @name.reference.module) @definition.import

(use_declaration
  (identifier) @name.reference.module) @definition.import

(extern_crate_declaration
  (identifier) @name.reference.module) @definition.import

; ADT definitions

(struct_item
    name: (type_identifier) @name.definition.class) @definition.class

(enum_item
    name: (type_identifier) @name.definition.class) @definition.class

(union_item
    name: (type_identifier) @name.definition.class) @definition.class

; type aliases

(type_item
    name: (type_identifier) @name.definition.class) @definition.class

; method definitions

(declaration_list
    (function_item
        name: (identifier) @name.definition.method)) @definition.method

; function definitions

(function_item
    name: (identifier) @name.definition.function) @definition.function

; trait definitions
(trait_item
    name: (type_identifier) @name.definition.interface) @definition.interface

; module definitions
(mod_item
    name: (identifier) @name.definition.module) @definition.module

; macro definitions

(macro_definition
    name: (identifier) @name.definition.macro) @definition.macro

; references

(call_expression
    function: (identifier) @name.reference.call) @reference.call

(call_expression
    function: (field_expression
        field: (field_identifier) @name.reference.call)) @reference.call

(macro_invocation
    macro: (identifier) @name.reference.call) @reference.call

; implementations

(impl_item
    trait: (type_identifier) @name.reference.implementation) @reference.implementation

(impl_item
    type: (type_identifier) @name.reference.implementation
    !trait) @reference.implementation
`;
</file>

<file path="src/core/treeSitter/queries/querySolidity.ts">
export const querySolidity = `
;; Comments
(comment) @comment

;; Contract declarations
(contract_declaration
  name: (identifier) @name.definition.class) @definition.class

;; Interface declarations
(interface_declaration
  name: (identifier) @name.definition.interface) @definition.interface

;; Function declarations
(function_definition
  name: (identifier) @name.definition.function) @definition.function

;; Import statements
(import_directive) @definition.import

; Event definitions
(event_definition
  name: (identifier) @name.definition.event) @definition.event

; Modifier definitions
(modifier_definition
  name: (identifier) @name.definition.modifier) @definition.modifier
`;
</file>

<file path="src/core/treeSitter/queries/queryVue.ts">
export const queryVue = `
(comment) @comment

(template_element) @template

(script_element) @script

(style_element) @style

(interpolation) @interpolation
`;
</file>

<file path="src/core/packager.ts">
import type { RepomixConfigMerged } from '../config/configSchema.js';
import type { RepomixProgressCallback } from '../shared/types.js';
import { collectFiles } from './file/fileCollect.js';
import { sortPaths } from './file/filePathSort.js';
import { processFiles } from './file/fileProcess.js';
import { FileSearchResult, searchFiles } from './file/fileSearch.js';
import type { RawFile } from './file/fileTypes.js';
import { calculateMetrics } from './metrics/calculateMetrics.js';
import { generateOutput } from './output/outputGenerate.js';
import { copyToClipboardIfEnabled } from './packager/copyToClipboardIfEnabled.js';
import { writeOutputToDisk } from './packager/writeOutputToDisk.js';
import type { SuspiciousFileResult } from './security/securityCheck.js';
import { validateFileSafety } from './security/validateFileSafety.js';

export interface PackResult {
  totalFiles: number;
  totalCharacters: number;
  totalTokens: number;
  fileCharCounts: Record<string, number>;
  fileTokenCounts: Record<string, number>;
  suspiciousFilesResults: SuspiciousFileResult[];
}

export const pack = async (
  rootDirs: string[],
  config: RepomixConfigMerged,
  progressCallback: RepomixProgressCallback = () => {},
  deps = {
    searchFiles,
    collectFiles,
    processFiles,
    generateOutput,
    validateFileSafety,
    writeOutputToDisk,
    copyToClipboardIfEnabled,
    calculateMetrics,
    sortPaths,
  },
): Promise<PackResult> => {
  progressCallback('Searching for files...');
  const filePathsByDir = await Promise.all(
    rootDirs.map(async (rootDir) => ({
      rootDir,
      filePaths: (await deps.searchFiles(rootDir, config)).filePaths,
    })),
  );

  // Sort file paths
  progressCallback('Sorting files...');
  const allFilePaths = filePathsByDir.flatMap(({ filePaths }) => filePaths);
  const sortedFilePaths = await deps.sortPaths(allFilePaths);

  // Regroup sorted file paths by rootDir
  const sortedFilePathsByDir = rootDirs.map((rootDir) => ({
    rootDir,
    filePaths: sortedFilePaths.filter((filePath) =>
      filePathsByDir.find((item) => item.rootDir === rootDir)?.filePaths.includes(filePath),
    ),
  }));

  progressCallback('Collecting files...');
  const rawFiles = (
    await Promise.all(
      sortedFilePathsByDir.map(({ rootDir, filePaths }) => deps.collectFiles(filePaths, rootDir, progressCallback)),
    )
  ).reduce((acc: RawFile[], curr: RawFile[]) => acc.concat(...curr), []);

  const { safeFilePaths, safeRawFiles, suspiciousFilesResults } = await deps.validateFileSafety(
    rawFiles,
    progressCallback,
    config,
  );
  // Process files (remove comments, etc.)
  progressCallback('Processing files...');
  const processedFiles = await deps.processFiles(safeRawFiles, config, progressCallback);

  progressCallback('Generating output...');
  const output = await deps.generateOutput(rootDirs, config, processedFiles, safeFilePaths);

  progressCallback('Writing output file...');
  await deps.writeOutputToDisk(output, config);

  await deps.copyToClipboardIfEnabled(output, progressCallback, config);

  const metrics = await deps.calculateMetrics(processedFiles, output, progressCallback, config);

  return {
    ...metrics,
    suspiciousFilesResults,
  };
};
</file>

<file path="src/mcp/tools/fileSystemReadDirectoryTool.ts">
import fs from 'node:fs/promises';
import path from 'node:path';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { logger } from '../../shared/logger.js';

/**
 * Register file system directory listing tool
 */
export const registerFileSystemReadDirectoryTool = (mcpServer: McpServer) => {
  mcpServer.tool(
    'file_system_read_directory',
    'List contents of a directory using an absolute path.',
    {
      path: z.string().describe('Absolute path to the directory to list'),
    },
    async ({ path: directoryPath }): Promise<CallToolResult> => {
      try {
        logger.trace(`Listing directory at absolute path: ${directoryPath}`);

        // Ensure path is absolute
        if (!path.isAbsolute(directoryPath)) {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: `Error: Path must be absolute. Received: ${directoryPath}`,
              },
            ],
          };
        }

        // Check if directory exists
        try {
          const stats = await fs.stat(directoryPath);
          if (!stats.isDirectory()) {
            return {
              isError: true,
              content: [
                {
                  type: 'text',
                  text: `Error: The specified path is not a directory: ${directoryPath}. Use file_system_read_file for files.`,
                },
              ],
            };
          }
        } catch {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: `Error: Directory not found at path: ${directoryPath}`,
              },
            ],
          };
        }

        // Read directory contents
        const entries = await fs.readdir(directoryPath, { withFileTypes: true });
        const formatted = entries
          .map((entry) => `${entry.isDirectory() ? '[DIR]' : '[FILE]'} ${entry.name}`)
          .join('\n');

        return {
          content: [
            {
              type: 'text',
              text: `Contents of ${directoryPath}:`,
            },
            {
              type: 'text',
              text: formatted || '(empty directory)',
            },
          ],
        };
      } catch (error) {
        logger.error(`Error in file_system_read_directory tool: ${error}`);
        return {
          isError: true,
          content: [
            {
              type: 'text',
              text: `Error listing directory: ${error instanceof Error ? error.message : String(error)}`,
            },
          ],
        };
      }
    },
  );
};
</file>

<file path="src/mcp/tools/fileSystemReadFileTool.ts">
import fs from 'node:fs/promises';
import path from 'node:path';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import type { SuspiciousFileResult } from '../../core/security/securityCheck.js';
import { createSecretLintConfig, runSecretLint } from '../../core/security/workers/securityCheckWorker.js';
import { logger } from '../../shared/logger.js';

/**
 * Register file system read file tool with security checks
 */
export const registerFileSystemReadFileTool = (mcpServer: McpServer) => {
  mcpServer.tool(
    'file_system_read_file',
    'Read a file using an absolute path with security validation.',
    {
      path: z.string().describe('Absolute path to the file to read'),
    },
    async ({ path: filePath }): Promise<CallToolResult> => {
      try {
        logger.trace(`Reading file at absolute path: ${filePath}`);

        // Ensure path is absolute
        if (!path.isAbsolute(filePath)) {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: `Error: Path must be absolute. Received: ${filePath}`,
              },
            ],
          };
        }

        // Check if file exists
        try {
          await fs.access(filePath);
        } catch {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: `Error: File not found at path: ${filePath}`,
              },
            ],
          };
        }

        // Check if it's a directory
        const stats = await fs.stat(filePath);
        if (stats.isDirectory()) {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: `Error: The specified path is a directory, not a file: ${filePath}. Use file_system_read_directory for directories.`,
              },
            ],
          };
        }

        // Read file content
        const fileContent = await fs.readFile(filePath, 'utf8');

        // Perform security check using the existing worker
        const config = createSecretLintConfig();
        const securityCheckResult = await runSecretLint(filePath, fileContent, config);

        // If security check found issues, block the file
        if (securityCheckResult !== null) {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: `Error: Security check failed. The file at ${filePath} may contain sensitive information.`,
              },
            ],
          };
        }

        return {
          content: [
            {
              type: 'text',
              text: `Content of ${filePath}:`,
            },
            {
              type: 'text',
              text: fileContent,
            },
          ],
        };
      } catch (error) {
        logger.error(`Error in file_system_read_file tool: ${error}`);
        return {
          isError: true,
          content: [
            {
              type: 'text',
              text: `Error reading file: ${error instanceof Error ? error.message : String(error)}`,
            },
          ],
        };
      }
    },
  );
};
</file>

<file path="tests/cli/actions/mcpAction.test.ts">
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { runMcpAction } from '../../../src/cli/actions/mcpAction.js';
import { runMcpServer } from '../../../src/mcp/mcpServer.js';
import { logger } from '../../../src/shared/logger.js';

vi.mock('../../../src/mcp/mcpServer');
vi.mock('../../../src/shared/logger');

describe('mcpAction', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  test('should start MCP server and log trace message', async () => {
    const mockRunMcpServer = vi.mocked(runMcpServer);
    mockRunMcpServer.mockResolvedValue();

    await runMcpAction();

    expect(logger.trace).toHaveBeenCalledWith('Starting Repomix MCP server...');
    expect(mockRunMcpServer).toHaveBeenCalled();
  });

  test('should handle MCP server startup error', async () => {
    const mockRunMcpServer = vi.mocked(runMcpServer);
    mockRunMcpServer.mockRejectedValue(new Error('Server startup failed'));

    await expect(runMcpAction()).rejects.toThrow('Server startup failed');

    expect(logger.trace).toHaveBeenCalledWith('Starting Repomix MCP server...');
    expect(mockRunMcpServer).toHaveBeenCalled();
  });
});
</file>

<file path="tests/cli/cliRun.test.ts">
import { beforeEach, describe, expect, test, vi } from 'vitest';
import * as defaultAction from '../../src/cli/actions/defaultAction.js';
import * as initAction from '../../src/cli/actions/initAction.js';
import * as remoteAction from '../../src/cli/actions/remoteAction.js';
import * as versionAction from '../../src/cli/actions/versionAction.js';
import { run, runCli } from '../../src/cli/cliRun.js';
import type { CliOptions } from '../../src/cli/types.js';
import type { RepomixConfigMerged } from '../../src/config/configSchema.js';
import type { PackResult } from '../../src/core/packager.js';
import { type RepomixLogLevel, logger, repomixLogLevels } from '../../src/shared/logger.js';

let logLevel: RepomixLogLevel;

vi.mock('../../src/shared/logger', () => ({
  repomixLogLevels: {
    SILENT: -1,
    ERROR: 0,
    WARN: 1,
    INFO: 2,
    DEBUG: 3,
  },
  logger: {
    log: vi.fn(),
    trace: vi.fn(),
    debug: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
    success: vi.fn(),
    note: vi.fn(),
    setLogLevel: vi.fn((level: RepomixLogLevel) => {
      logLevel = level;
    }),
    getLogLevel: vi.fn(() => logLevel),
  },
}));

vi.mock('commander', () => ({
  program: {
    description: vi.fn().mockReturnThis(),
    arguments: vi.fn().mockReturnThis(),
    option: vi.fn().mockReturnThis(),
    action: vi.fn().mockReturnThis(),
    parseAsync: vi.fn().mockResolvedValue(undefined),
  },
}));

vi.mock('../../src/cli/actions/defaultAction');
vi.mock('../../src/cli/actions/initAction');
vi.mock('../../src/cli/actions/remoteAction');
vi.mock('../../src/cli/actions/versionAction');

describe('cliRun', () => {
  beforeEach(() => {
    vi.resetAllMocks();

    vi.mocked(defaultAction.runDefaultAction).mockResolvedValue({
      config: {
        cwd: process.cwd(),
        output: {
          filePath: 'repomix-output.txt',
          style: 'plain',
          parsableStyle: false,
          fileSummary: true,
          directoryStructure: true,
          topFilesLength: 5,
          showLineNumbers: false,
          removeComments: false,
          removeEmptyLines: false,
          compress: false,
          copyToClipboard: false,
          git: {
            sortByChanges: true,
            sortByChangesMaxCommits: 100,
          },
        },
        include: [],
        ignore: {
          useGitignore: true,
          useDefaultPatterns: true,
          customPatterns: [],
        },
        security: {
          enableSecurityCheck: true,
        },
        tokenCount: {
          encoding: 'o200k_base',
        },
      } satisfies RepomixConfigMerged,
      packResult: {
        totalFiles: 0,
        totalCharacters: 0,
        totalTokens: 0,
        fileCharCounts: {},
        fileTokenCounts: {},
        suspiciousFilesResults: [],
      } satisfies PackResult,
    });
    vi.mocked(initAction.runInitAction).mockResolvedValue();
    vi.mocked(remoteAction.runRemoteAction).mockResolvedValue({
      config: {
        cwd: process.cwd(),
        output: {
          filePath: 'repomix-output.txt',
          style: 'plain',
          parsableStyle: false,
          fileSummary: true,
          directoryStructure: true,
          topFilesLength: 5,
          showLineNumbers: false,
          removeComments: false,
          removeEmptyLines: false,
          compress: false,
          copyToClipboard: false,
          git: {
            sortByChanges: true,
            sortByChangesMaxCommits: 100,
          },
        },
        include: [],
        ignore: {
          useGitignore: true,
          useDefaultPatterns: true,
          customPatterns: [],
        },
        security: {
          enableSecurityCheck: true,
        },
        tokenCount: {
          encoding: 'o200k_base',
        },
      } satisfies RepomixConfigMerged,
      packResult: {
        totalFiles: 0,
        totalCharacters: 0,
        totalTokens: 0,
        fileCharCounts: {},
        fileTokenCounts: {},
        suspiciousFilesResults: [],
      } satisfies PackResult,
    });
    vi.mocked(versionAction.runVersionAction).mockResolvedValue();
  });

  test('should run without arguments', async () => {
    await expect(run()).resolves.not.toThrow();
  });

  describe('executeAction', () => {
    test('should execute default action when no special options provided', async () => {
      await runCli(['.'], process.cwd(), {});

      expect(defaultAction.runDefaultAction).toHaveBeenCalledWith(['.'], process.cwd(), expect.any(Object));
    });

    test('should enable verbose logging when verbose option is true', async () => {
      await runCli(['.'], process.cwd(), { verbose: true });

      expect(logger.setLogLevel).toHaveBeenCalledWith(repomixLogLevels.DEBUG);
    });

    test('should execute version action when version option is true', async () => {
      await runCli(['.'], process.cwd(), { version: true });

      expect(versionAction.runVersionAction).toHaveBeenCalled();
      expect(defaultAction.runDefaultAction).not.toHaveBeenCalled();
    });

    test('should execute init action when init option is true', async () => {
      await runCli(['.'], process.cwd(), { init: true });

      expect(initAction.runInitAction).toHaveBeenCalledWith(process.cwd(), false);
      expect(defaultAction.runDefaultAction).not.toHaveBeenCalled();
    });

    test('should execute remote action when remote option is provided', async () => {
      await runCli(['.'], process.cwd(), {
        remote: 'yamadashy/repomix',
      });

      expect(remoteAction.runRemoteAction).toHaveBeenCalledWith('yamadashy/repomix', expect.any(Object));
      expect(defaultAction.runDefaultAction).not.toHaveBeenCalled();
    });
  });

  describe('parsable style flag', () => {
    test('should disable parsable style by default', async () => {
      await runCli(['.'], process.cwd(), {});

      expect(defaultAction.runDefaultAction).toHaveBeenCalledWith(
        ['.'],
        process.cwd(),
        expect.not.objectContaining({
          parsableStyle: false,
        }),
      );
    });

    test('should handle --parsable-style flag', async () => {
      await runCli(['.'], process.cwd(), { parsableStyle: true });

      expect(defaultAction.runDefaultAction).toHaveBeenCalledWith(
        ['.'],
        process.cwd(),
        expect.objectContaining({
          parsableStyle: true,
        }),
      );
    });
  });

  describe('security check flag', () => {
    test('should enable security check by default', async () => {
      await runCli(['.'], process.cwd(), {});

      expect(defaultAction.runDefaultAction).toHaveBeenCalledWith(
        ['.'],
        process.cwd(),
        expect.not.objectContaining({
          securityCheck: false,
        }),
      );
    });

    test('should handle --no-security-check flag', async () => {
      await runCli(['.'], process.cwd(), { securityCheck: false });

      expect(defaultAction.runDefaultAction).toHaveBeenCalledWith(
        ['.'],
        process.cwd(),
        expect.objectContaining({
          securityCheck: false,
        }),
      );
    });

    test('should handle explicit --security-check flag', async () => {
      await runCli(['.'], process.cwd(), { securityCheck: true });

      expect(defaultAction.runDefaultAction).toHaveBeenCalledWith(
        ['.'],
        process.cwd(),
        expect.objectContaining({
          securityCheck: true,
        }),
      );
    });

    test('should handle explicit --no-gitignore flag', async () => {
      await runCli(['.'], process.cwd(), { gitignore: false });

      expect(defaultAction.runDefaultAction).toHaveBeenCalledWith(
        ['.'],
        process.cwd(),
        expect.objectContaining({
          gitignore: false,
        }),
      );
    });

    test('should handle explicit --no-default-patterns flag', async () => {
      await runCli(['.'], process.cwd(), { defaultPatterns: false });

      expect(defaultAction.runDefaultAction).toHaveBeenCalledWith(
        ['.'],
        process.cwd(),
        expect.objectContaining({
          defaultPatterns: false,
        }),
      );
    });

    test('should handle explicit --header-text flag', async () => {
      await runCli(['.'], process.cwd(), {
        headerText: 'I am a good header text',
      });

      expect(defaultAction.runDefaultAction).toHaveBeenCalledWith(
        ['.'],
        process.cwd(),
        expect.objectContaining({
          headerText: 'I am a good header text',
        }),
      );
    });

    test('should handle --instruction-file-path flag', async () => {
      await runCli(['.'], process.cwd(), {
        instructionFilePath: 'path/to/instruction.txt',
      });

      expect(defaultAction.runDefaultAction).toHaveBeenCalledWith(
        ['.'],
        process.cwd(),
        expect.objectContaining({
          instructionFilePath: 'path/to/instruction.txt',
        }),
      );
    });

    test('should handle --include-empty-directories flag', async () => {
      await runCli(['.'], process.cwd(), {
        includeEmptyDirectories: true,
      });

      expect(defaultAction.runDefaultAction).toHaveBeenCalledWith(
        ['.'],
        process.cwd(),
        expect.objectContaining({
          includeEmptyDirectories: true,
        }),
      );
    });
  });

  describe('quiet mode', () => {
    test('should set log level to SILENT when quiet option is true', async () => {
      const options: CliOptions = {
        quiet: true,
      };

      await runCli(['.'], process.cwd(), options);

      expect(logger.getLogLevel()).toBe(repomixLogLevels.SILENT);
    });

    test('should set log level to DEBUG when verbose option is true', async () => {
      const options: CliOptions = {
        verbose: true,
      };

      await runCli(['.'], process.cwd(), options);

      expect(logger.getLogLevel()).toBe(repomixLogLevels.DEBUG);
    });

    test('should set log level to INFO by default', async () => {
      const options: CliOptions = {};

      await runCli(['.'], process.cwd(), options);

      expect(logger.getLogLevel()).toBe(repomixLogLevels.INFO);
    });
  });
});
</file>

<file path="tests/config/configSchema.test.ts">
import { describe, expect, it } from 'vitest';
import { z } from 'zod';
import {
  repomixConfigBaseSchema,
  repomixConfigCliSchema,
  repomixConfigDefaultSchema,
  repomixConfigFileSchema,
  repomixConfigMergedSchema,
  repomixOutputStyleSchema,
} from '../../src/config/configSchema.js';

describe('configSchema', () => {
  describe('repomixOutputStyleSchema', () => {
    it('should accept valid output styles', () => {
      expect(repomixOutputStyleSchema.parse('plain')).toBe('plain');
      expect(repomixOutputStyleSchema.parse('xml')).toBe('xml');
    });

    it('should reject invalid output styles', () => {
      expect(() => repomixOutputStyleSchema.parse('invalid')).toThrow(z.ZodError);
    });
  });

  describe('repomixConfigBaseSchema', () => {
    it('should accept valid base config', () => {
      const validConfig = {
        output: {
          filePath: 'output.txt',
          style: 'plain',
          removeComments: true,
        },
        include: ['**/*.js'],
        ignore: {
          useGitignore: true,
          customPatterns: ['node_modules'],
        },
        security: {
          enableSecurityCheck: true,
        },
      };
      expect(repomixConfigBaseSchema.parse(validConfig)).toEqual(validConfig);
    });

    it('should accept empty object', () => {
      expect(repomixConfigBaseSchema.parse({})).toEqual({});
    });

    it('should reject invalid types', () => {
      const invalidConfig = {
        output: {
          filePath: 123, // Should be string
          style: 'invalid', // Should be 'plain' or 'xml'
        },
        include: 'not-an-array', // Should be an array
      };
      expect(() => repomixConfigBaseSchema.parse(invalidConfig)).toThrow(z.ZodError);
    });
  });

  describe('repomixConfigDefaultSchema', () => {
    it('should accept valid default config', () => {
      const validConfig = {
        output: {
          filePath: 'output.txt',
          style: 'plain',
          parsableStyle: false,
          fileSummary: true,
          directoryStructure: true,
          removeComments: false,
          removeEmptyLines: false,
          compress: false,
          topFilesLength: 5,
          showLineNumbers: false,
          copyToClipboard: true,
          git: {
            sortByChanges: true,
            sortByChangesMaxCommits: 100,
          },
        },
        include: [],
        ignore: {
          customPatterns: [],
          useGitignore: true,
          useDefaultPatterns: true,
        },
        security: {
          enableSecurityCheck: true,
        },
        tokenCount: {
          encoding: 'o200k_base',
        },
      };
      expect(repomixConfigDefaultSchema.parse(validConfig)).toEqual(validConfig);
    });

    it('should reject incomplete config', () => {
      const validConfig = {};
      expect(() => repomixConfigDefaultSchema.parse(validConfig)).not.toThrow();
    });
  });

  describe('repomixConfigFileSchema', () => {
    it('should accept valid file config', () => {
      const validConfig = {
        output: {
          filePath: 'custom-output.txt',
          style: 'xml',
        },
        ignore: {
          customPatterns: ['*.log'],
        },
      };
      expect(repomixConfigFileSchema.parse(validConfig)).toEqual(validConfig);
    });

    it('should accept partial config', () => {
      const partialConfig = {
        output: {
          filePath: 'partial-output.txt',
        },
      };
      expect(repomixConfigFileSchema.parse(partialConfig)).toEqual(partialConfig);
    });
  });

  describe('repomixConfigCliSchema', () => {
    it('should accept valid CLI config', () => {
      const validConfig = {
        output: {
          filePath: 'cli-output.txt',
          showLineNumbers: true,
        },
        include: ['src/**/*.ts'],
      };
      expect(repomixConfigCliSchema.parse(validConfig)).toEqual(validConfig);
    });

    it('should reject invalid CLI options', () => {
      const invalidConfig = {
        output: {
          filePath: 123, // Should be string
        },
      };
      expect(() => repomixConfigCliSchema.parse(invalidConfig)).toThrow(z.ZodError);
    });
  });

  describe('repomixConfigMergedSchema', () => {
    it('should accept valid merged config', () => {
      const validConfig = {
        cwd: '/path/to/project',
        output: {
          filePath: 'merged-output.txt',
          style: 'plain',
          parsableStyle: false,
          fileSummary: true,
          directoryStructure: true,
          removeComments: true,
          removeEmptyLines: false,
          compress: false,
          topFilesLength: 10,
          showLineNumbers: true,
          copyToClipboard: false,
          git: {
            sortByChanges: true,
            sortByChangesMaxCommits: 100,
          },
        },
        include: ['**/*.js', '**/*.ts'],
        ignore: {
          useGitignore: true,
          useDefaultPatterns: true,
          customPatterns: ['*.log'],
        },
        security: {
          enableSecurityCheck: true,
        },
        tokenCount: {
          encoding: 'o200k_base',
        },
      };
      expect(repomixConfigMergedSchema.parse(validConfig)).toEqual(validConfig);
    });

    it('should reject merged config missing required fields', () => {
      const invalidConfig = {
        output: {
          filePath: 'output.txt',
          // Missing required fields
        },
      };
      expect(() => repomixConfigMergedSchema.parse(invalidConfig)).toThrow(z.ZodError);
    });

    it('should reject merged config with invalid types', () => {
      const invalidConfig = {
        cwd: '/path/to/project',
        output: {
          filePath: 'output.txt',
          style: 'plain',
          removeComments: 'not-a-boolean', // Should be boolean
          removeEmptyLines: false,
          compress: false,
          topFilesLength: '5', // Should be number
          showLineNumbers: false,
        },
        include: ['**/*.js'],
        ignore: {
          useGitignore: true,
          useDefaultPatterns: true,
        },
        security: {
          enableSecurityCheck: true,
        },
      };
      expect(() => repomixConfigMergedSchema.parse(invalidConfig)).toThrow(z.ZodError);
    });
  });
});
</file>

<file path="tests/core/file/fileManipulate.test.ts">
import { describe, expect, test } from 'vitest';
import { getFileManipulator } from '../../../src/core/file/fileManipulate.js';

describe('fileManipulate', () => {
  const testCases = [
    {
      name: 'C comment removal',
      ext: '.c',
      input: `
        // Single line comment
        int main() {
          /* Multi-line
             comment */
          return 0;
        }
      `,
      expected: `

        int main() {


          return 0;
        }
`,
    },
    {
      name: 'C++ header file comment removal',
      ext: '.h',
      input: `
        // Single line comment
        #ifndef MY_HEADER_H
        #define MY_HEADER_H
        /* Multi-line
           comment block */
        class MyClass {
          // Method comment
          void method();
          /**
           * Documentation comment
           */
          int value;
        };
        #endif // MY_HEADER_H
      `,
      expected: `

        #ifndef MY_HEADER_H
        #define MY_HEADER_H


        class MyClass {

          void method();



          int value;
        };
        #endif
`,
    },
    {
      name: 'C++ source file comment removal',
      ext: '.cc',
      input: `
        // Single line comment
        #include "myheader.h"
        /* Multi-line
           comment block */
        void MyClass::method() {
          // Implementation comment
          /* Another
             multi-line comment */
          int x = 0; // Inline comment
        }
      `,
      expected: `

        #include "myheader.h"


        void MyClass::method() {



          int x = 0;
        }
`,
    },
    {
      name: 'C++ .cpp file comment removal',
      ext: '.cpp',
      input: `
        // Single line comment
        #include <iostream>
        /* Multi-line
           comment block */
        int main() {
          // Implementation comment
          std::cout << "Hello, world!" << std::endl; // Inline comment
          /* Another
             multi-line comment */
          return 0;
        }
      `,
      expected: `

        #include <iostream>


        int main() {

          std::cout << "Hello, world!" << std::endl;


          return 0;
        }
`,
    },
    {
      name: 'C++ .hpp file comment removal',
      ext: '.hpp',
      input: `
        // Single line comment
        #pragma once
        /* Multi-line
           comment block */
        namespace test {
          // Class comment
          template <typename T>
          class Test {
            /**
             * Documentation comment
             */
            public:
              T value;
          };
        } // namespace test
      `,
      expected: `

        #pragma once


        namespace test {

          template <typename T>
          class Test {



            public:
              T value;
          };
        }
`,
    },
    {
      name: 'C# comment removal',
      ext: '.cs',
      input: `
        // Single line comment
        public class Test {
          /* Multi-line
             comment */
          public void Method() {}
        }
      `,
      expected: `

        public class Test {


          public void Method() {}
        }
`,
    },
    {
      name: 'CSS comment removal',
      ext: '.css',
      input: `
        /* Comment */
        body {
          color: red; /* Inline comment */
        }
      `,
      expected: `

        body {
          color: red;
        }
`,
    },
    {
      name: 'HTML comment removal',
      ext: '.html',
      input: '<div><!-- Comment -->Content</div>',
      expected: '<div>Content</div>',
    },
    {
      name: 'Java comment removal',
      ext: '.java',
      input: `
        // Single line comment
        public class Test {
          /* Multi-line
             comment */
          public void method() {}
        }
      `,
      expected: `

        public class Test {


          public void method() {}
        }
`,
    },
    {
      name: 'JavaScript comment removal',
      ext: '.js',
      input: `
        // Single line comment
        function test() {
          /* Multi-line
             comment */
          return true;
        }
      `,
      expected: `

        function test() {


          return true;
        }
`,
    },
    {
      name: 'Less comment removal',
      ext: '.less',
      input: `
        // Single line comment
        @variable: #888;
        /* Multi-line
           comment */
        body { color: @variable; }
      `,
      expected: `

        @variable: #888;


        body { color: @variable; }
`,
    },
    {
      name: 'PHP comment removal',
      ext: '.php',
      input: `
        <?php
        // Single line comment
        # Another single line comment
        function test() {
          /* Multi-line
             comment */
          return true;
        }
        ?>
      `,
      expected: `
        <?php


        function test() {


          return true;
        }
        ?>
`,
    },
    {
      name: 'Python comment, docstring removal',
      ext: '.py',
      input: `
        # Single line comment
        def test():
          '''
          docstring
          '''
          return True
        """
        Another docstring
        """
      `,
      expected: `

        def test():

          return True

`,
    },
    {
      name: 'Python docstring removal mixing string declaration',
      ext: '.py',
      input: `
        var = """
        string variable
        """
        """
        docstring
        """
      `,
      expected: `
        var = """
        string variable
        """

`,
    },
    {
      name: 'Python comment f-string is not removed',
      ext: '.py',
      input: `
        # Single line comment
        def test():
          f'f-string'
          f"""
          f-string
          """
          return True
      `,
      expected: `

        def test():
          f'f-string'
          f"""
          f-string
          """
          return True
`,
    },
    {
      name: 'Python comment multi-line string literal is not removed',
      ext: '.py',
      input: `
        def test():
          hoge = """
          multi-line
          string
          """
          return True
      `,
      expected: `
        def test():
          hoge = """
          multi-line
          string
          """
          return True
`,
    },
    {
      name: 'Python nested quotes',
      ext: '.py',
      input: `
        """
        '''
        docstring
        '''
        """
      `,
      expected: `

`,
    },
    {
      name: 'Python nested triple quotes with different types',
      ext: '.py',
      input: `
      def func():
        """
        Outer docstring
        '''
        Inner single quotes
        '''
        Still in outer docstring
        """
        return True
    `,
      expected: `
      def func():

        return True
`,
    },
    {
      name: 'Python inline comments',
      ext: '.py',
      input: `
      x = 5  # This is an inline comment
      y = 10  # Another inline comment
      z = x + y
    `,
      expected: `
      x = 5
      y = 10
      z = x + y
`,
    },
    {
      name: 'Python multi-line statement with string',
      ext: '.py',
      input: `
      long_string = "This is a long string that spans " \\
                    "multiple lines in the code, " \\
                    "but is actually a single string"
      # Comment after multi-line statement
    `,
      expected: `
      long_string = "This is a long string that spans " \\
                    "multiple lines in the code, " \\
                    "but is actually a single string"

`,
    },
    {
      name: 'Python docstring with triple quotes inside string literals',
      ext: '.py',
      input: `
      def func():
        """This is a docstring"""
        x = "This is not a docstring: '''"
        y = '"""This is also not a docstring: """'
        return x + y
    `,
      expected: `
      def func():

        x = "This is not a docstring: '''"
        y = '"""This is also not a docstring: """'
        return x + y
`,
    },
    {
      name: 'Python mixed comments and docstrings',
      ext: '.py',
      input: `
      # This is a comment
      def func():
        '''
        This is a docstring
        '''
        x = 5  # Inline comment
        """
        This is another docstring
        """
        # Another comment
        return x
    `,
      expected: `

      def func():

        x = 5


        return x
`,
    },
    {
      name: 'Python f-strings with triple quotes',
      ext: '.py',
      input: `
      x = 10
      y = 20
      f"""
      This f-string contains a calculation: {x + y}
      """
      # Comment after f-string
    `,
      expected: `
      x = 10
      y = 20
      f"""
      This f-string contains a calculation: {x + y}
      """

`,
    },
    {
      name: 'Python escaped hash in string',
      ext: '.py',
      input: `
      text = "This string contains an \# escaped hash"
      # This is a real comment
    `,
      expected: `
      text = "This string contains an \# escaped hash"

`,
    },
    {
      name: 'Python nested function with docstrings',
      ext: '.py',
      input: `
      def outer():
        """Outer docstring"""
        def inner():
          """Inner docstring"""
          pass
        return inner
    `,
      expected: `
      def outer():

        def inner():

          pass
        return inner
`,
    },
    {
      name: 'Python comment-like content in string',
      ext: '.py',
      input: `
      x = "This is not a # comment"
      y = 'Neither is this # comment'
      z = """
      This is not a # comment
      Neither is this # comment
      """
    `,
      expected: `
      x = "This is not a # comment"
      y = 'Neither is this # comment'
      z = """
      This is not a # comment
      Neither is this # comment
      """
`,
    },
    {
      name: 'Python docstring with backslashes',
      ext: '.py',
      input: `
      def func():
        """
        This docstring has \\ backslashes
        It shouldn't \\""" confuse the parser
        """
        return True
    `,
      expected: `
      def func():

        return True
`,
    },
    {
      name: 'Python mixed single and double quotes',
      ext: '.py',
      input: `
      x = '\"\"\""'  # This is not a docstring start
      y = "'''"  # Neither is this
      """But this is a docstring"""
    `,
      expected: `
      x = '\"\"\""'
      y = "'''"

`,
    },
    {
      name: 'Ruby comment removal',
      ext: '.rb',
      input: `
        # Single line comment
        def test
          =begin
          Multi-line comment
          =end
          true
        end
      `,
      expected: `

        def test



          true
        end
`,
    },
    {
      name: 'Sass comment removal',
      ext: '.sass',
      input: `
        // Single line comment
        $variable: #888
        /* Multi-line
           comment */
        body
          color: $variable
      `,
      expected: `

        $variable: #888


        body
          color: $variable
`,
    },
    {
      name: 'SCSS comment removal',
      ext: '.scss',
      input: `
        // Single line comment
        $variable: #888;
        /* Multi-line
           comment */
        body { color: $variable; }
      `,
      expected: `

        $variable: #888;


        body { color: $variable; }
`,
    },
    {
      name: 'SQL comment removal',
      ext: '.sql',
      input: `
        -- Single line comment
        SELECT * FROM table WHERE id = 1;
      `,
      expected: `

        SELECT * FROM table WHERE id = 1;
`,
    },
    {
      name: 'Swift comment removal',
      ext: '.swift',
      input: `
        // Single line comment
        func test() {
          /* Multi-line
             comment */
          return true
        }
      `,
      expected: `

        func test() {


          return true
        }
`,
    },
    {
      name: 'TypeScript comment removal',
      ext: '.ts',
      input: `
        // Single line comment
        function test(): boolean {
          /* Multi-line
             comment */
          return true;
        }
      `,
      expected: `

        function test(): boolean {


          return true;
        }
`,
    },
    {
      name: 'XML comment removal',
      ext: '.xml',
      input: '<root><!-- Comment --><element>Content</element></root>',
      expected: '<root><element>Content</element></root>',
    },
    {
      name: 'Dart comment removal',
      ext: '.dart',
      input: `
        // Single line comment
        void main() {
          /* Multi-line
             comment */
          print('Hello');
        }
      `,
      expected: `

        void main() {


          print('Hello');
        }
`,
    },
    {
      name: 'Go comment removal',
      ext: '.go',
      input: `
        // Single line comment
        func main() {
          /* Multi-line
             comment */
          fmt.Println("Hello")
        }
      `,
      expected: `

        func main() {


          fmt.Println("Hello")
        }
`,
    },
    {
      name: 'Kotlin comment removal',
      ext: '.kt',
      input: `
        // Single line comment
        fun main() {
          /* Multi-line
             comment */
          println("Hello")
        }
      `,
      expected: `

        fun main() {


          println("Hello")
        }
`,
    },
    {
      name: 'Rust comment removal',
      ext: '.rs',
      input: `
        // Single line comment
        fn main() {
          /* Multi-line
             comment */
          println!("Hello");
        }
      `,
      expected: `

        fn main() {


          println!("Hello");
        }
`,
    },
    {
      name: 'Shell script comment removal',
      ext: '.sh',
      input: `
        # Single line comment
        echo "Hello"
      `,
      expected: `

        echo "Hello"
`,
    },
    {
      name: 'YAML comment removal',
      ext: '.yml',
      input: `
        key: value  # Comment
        another_key: another_value
      `,
      expected: `
        key: value
        another_key: another_value
`,
    },
    {
      name: 'Vue file comment removal',
      ext: '.vue',
      input: `
        <template>
          <!-- HTML comment -->
          <div>{{ message }}</div>
        </template>
        <script>
        // JavaScript comment
        export default {
          data() {
            return {
              message: 'Hello'
            }
          }
        }
        </script>
        <style>
        /* CSS comment */
        .test { color: red; }
        </style>
      `,
      expected: `
        <template>

          <div>{{ message }}</div>
        </template>
        <script>

        export default {
          data() {
            return {
              message: 'Hello'
            }
          }
        }
        </script>
        <style>

        .test { color: red; }
        </style>
`,
    },
    {
      name: 'Svelte file comment removal',
      ext: '.svelte',
      input: `
        <!-- HTML comment -->
        <div>{message}</div>
        <script>
        // JavaScript comment
        let message = 'Hello';
        </script>
        <style>
        /* CSS comment */
        div { color: red; }
        </style>
      `,
      expected: `

        <div>{message}</div>
        <script>

        let message = 'Hello';
        </script>
        <style>

        div { color: red; }
        </style>
`,
    },
    {
      name: 'C++ triple slash comment removal (.cpp)',
      ext: '.cpp',
      input: `
        /// Triple slash documentation comment
        #include <iostream>
        // Single line comment
        int main() {
          std::cout << "Hello, world!" << std::endl; /// Inline triple slash comment
          return 0; // Normal comment
        }
      `,
      expected: `

        #include <iostream>

        int main() {
          std::cout << "Hello, world!" << std::endl;
          return 0;
        }
`,
    },
    {
      name: 'C++ triple slash comment removal (.hpp)',
      ext: '.hpp',
      input: `
        /// Class documentation with triple slash
        class Test {
          public:
            /// Method documentation
            void method();
            int value; /// Variable documentation
        };
      `,
      expected: `

        class Test {
          public:

            void method();
            int value;
        };
`,
    },
    {
      name: 'C++ triple slash comment removal',
      ext: '.cpp',
      input: `
        /// This is a triple slash comment.\nint foo = 1; /// Another triple slash comment.\n// Regular single line comment\n/* Multi-line\n   comment */\nint bar = 2; /// Comment with trailing spaces  \n`,
      expected: `

        int foo = 1;\n\n\n\nint bar = 2;\n`,
    },
  ];

  for (const { name, ext, input, expected } of testCases) {
    test(name, () => {
      const manipulator = getFileManipulator(`test${ext}`);
      expect(manipulator?.removeComments(input)).toBe(expected);
    });
  }

  test('Unsupported file type', () => {
    const manipulator = getFileManipulator('test.unsupported');
    expect(manipulator).toBeNull();
  });
});
</file>

<file path="tests/core/file/gitCommand.test.ts">
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { execGitShallowClone, getFileChangeCount, isGitInstalled } from '../../../src/core/file/gitCommand.js';
import { logger } from '../../../src/shared/logger.js';

vi.mock('../../../src/shared/logger');

describe('gitCommand', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  describe('getFileChangeCount', () => {
    test('should count file changes correctly', async () => {
      const mockOutput = `
file1.ts
file2.ts
file1.ts
file3.ts
file2.ts
`.trim();
      const mockFileExecAsync = vi.fn().mockResolvedValue({ stdout: mockOutput });

      const result = await getFileChangeCount('/test/dir', 5, { execFileAsync: mockFileExecAsync });

      expect(result).toEqual({
        'file1.ts': 2,
        'file2.ts': 2,
        'file3.ts': 1,
      });
      expect(mockFileExecAsync).toHaveBeenCalledWith('git', [
        '-C',
        '/test/dir',
        'log',
        '--pretty=format:',
        '--name-only',
        '-n',
        '5',
      ]);
    });

    test('should return empty object when git command fails', async () => {
      const mockFileExecAsync = vi.fn().mockRejectedValue(new Error('git command failed'));

      const result = await getFileChangeCount('/test/dir', 5, { execFileAsync: mockFileExecAsync });

      expect(result).toEqual({});
      expect(logger.trace).toHaveBeenCalledWith('Failed to get file change counts:', 'git command failed');
    });
  });

  describe('isGitInstalled', () => {
    test('should return true when git is installed', async () => {
      const mockFileExecAsync = vi.fn().mockResolvedValue({ stdout: 'git version 2.34.1', stderr: '' });

      const result = await isGitInstalled({ execFileAsync: mockFileExecAsync });

      expect(result).toBe(true);
      expect(mockFileExecAsync).toHaveBeenCalledWith('git', ['--version']);
    });

    test('should return false and log error when git command fails', async () => {
      const mockFileExecAsync = vi.fn().mockRejectedValue(new Error('Command not found: git'));

      const result = await isGitInstalled({ execFileAsync: mockFileExecAsync });

      expect(result).toBe(false);
      expect(mockFileExecAsync).toHaveBeenCalledWith('git', ['--version']);
      expect(logger.trace).toHaveBeenCalledWith('Git is not installed:', 'Command not found: git');
    });

    test('should return false and log error with custom error message', async () => {
      const customError = new Error('Custom git error message');
      const mockFileExecAsync = vi.fn().mockRejectedValue(customError);

      const result = await isGitInstalled({ execFileAsync: mockFileExecAsync });

      expect(result).toBe(false);
      expect(mockFileExecAsync).toHaveBeenCalledWith('git', ['--version']);
      expect(logger.trace).toHaveBeenCalledWith('Git is not installed:', 'Custom git error message');
    });

    test('should return false when git command fails with empty error message', async () => {
      const customError = new Error('');
      const mockFileExecAsync = vi.fn().mockRejectedValue(customError);

      const result = await isGitInstalled({ execFileAsync: mockFileExecAsync });

      expect(result).toBe(false);
      expect(mockFileExecAsync).toHaveBeenCalledWith('git', ['--version']);
      expect(logger.trace).toHaveBeenCalledWith('Git is not installed:', '');
    });

    test('should return false when git command returns stderr', async () => {
      const mockFileExecAsync = vi.fn().mockResolvedValue({ stdout: '', stderr: 'git: command not found' });

      const result = await isGitInstalled({ execFileAsync: mockFileExecAsync });

      expect(result).toBe(false);
      expect(mockFileExecAsync).toHaveBeenCalledWith('git', ['--version']);
    });
  });

  describe('execGitShallowClone', () => {
    test('should execute without branch option if not specified by user', async () => {
      const mockFileExecAsync = vi.fn().mockResolvedValue({ stdout: '', stderr: '' });
      const url = 'https://github.com/user/repo.git';
      const directory = '/tmp/repo';
      const remoteBranch = undefined;

      await execGitShallowClone(url, directory, remoteBranch, { execFileAsync: mockFileExecAsync });

      expect(mockFileExecAsync).toHaveBeenCalledWith('git', ['clone', '--depth', '1', url, directory]);
    });

    test('should throw error when git clone fails', async () => {
      const mockFileExecAsync = vi.fn().mockRejectedValue(new Error('Authentication failed'));
      const url = 'https://github.com/user/repo.git';
      const directory = '/tmp/repo';
      const remoteBranch = undefined;

      await expect(
        execGitShallowClone(url, directory, remoteBranch, { execFileAsync: mockFileExecAsync }),
      ).rejects.toThrow('Authentication failed');

      expect(mockFileExecAsync).toHaveBeenCalledWith('git', ['clone', '--depth', '1', url, directory]);
    });

    test('should execute commands correctly when branch is specified', async () => {
      const mockFileExecAsync = vi.fn().mockResolvedValue({ stdout: '', stderr: '' });

      const url = 'https://github.com/user/repo.git';
      const directory = '/tmp/repo';
      const remoteBranch = 'main';

      await execGitShallowClone(url, directory, remoteBranch, { execFileAsync: mockFileExecAsync });

      expect(mockFileExecAsync).toHaveBeenCalledTimes(4);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(1, 'git', ['-C', directory, 'init']);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(2, 'git', ['-C', directory, 'remote', 'add', 'origin', url]);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(3, 'git', [
        '-C',
        directory,
        'fetch',
        '--depth',
        '1',
        'origin',
        remoteBranch,
      ]);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(4, 'git', ['-C', directory, 'checkout', 'FETCH_HEAD']);
    });

    test('should throw error when git fetch fails', async () => {
      const mockFileExecAsync = vi
        .fn()
        .mockResolvedValueOnce('Success on first call')
        .mockResolvedValueOnce('Success on second call')
        .mockRejectedValueOnce(new Error('Authentication failed'));

      const url = 'https://github.com/user/repo.git';
      const directory = '/tmp/repo';
      const remoteBranch = 'b188a6cb39b512a9c6da7235b880af42c78ccd0d';

      await expect(
        execGitShallowClone(url, directory, remoteBranch, { execFileAsync: mockFileExecAsync }),
      ).rejects.toThrow('Authentication failed');
      expect(mockFileExecAsync).toHaveBeenCalledTimes(3);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(1, 'git', ['-C', directory, 'init']);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(2, 'git', ['-C', directory, 'remote', 'add', 'origin', url]);
      expect(mockFileExecAsync).toHaveBeenLastCalledWith('git', [
        '-C',
        directory,
        'fetch',
        '--depth',
        '1',
        'origin',
        remoteBranch,
      ]);
    });

    test('should handle short SHA correctly', async () => {
      const url = 'https://github.com/user/repo.git';
      const directory = '/tmp/repo';
      const shortSha = 'ce9b621';
      const mockFileExecAsync = vi
        .fn()
        .mockResolvedValueOnce('Success on first call')
        .mockResolvedValueOnce('Success on second call')
        .mockRejectedValueOnce(
          new Error(
            `Command failed: git fetch --depth 1 origin ${shortSha}\nfatal: couldn't find remote ref ${shortSha}`,
          ),
        );

      await execGitShallowClone(url, directory, shortSha, { execFileAsync: mockFileExecAsync });

      expect(mockFileExecAsync).toHaveBeenCalledTimes(5);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(1, 'git', ['-C', directory, 'init']);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(2, 'git', ['-C', directory, 'remote', 'add', 'origin', url]);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(3, 'git', [
        '-C',
        directory,
        'fetch',
        '--depth',
        '1',
        'origin',
        shortSha,
      ]);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(4, 'git', ['-C', directory, 'fetch', 'origin']);
      expect(mockFileExecAsync).toHaveBeenLastCalledWith('git', ['-C', directory, 'checkout', shortSha]);
    });

    test("should throw error when remote ref is not found, and it's not due to short SHA", async () => {
      const url = 'https://github.com/user/repo.git';
      const directory = '/tmp/repo';
      const remoteBranch = 'b188a6cb39b512a9c6da7235b880af42c78ccd0d';
      const errMessage = `Command failed: git fetch --depth 1 origin ${remoteBranch}\nfatal: couldn't find remote ref ${remoteBranch}`;

      const mockFileExecAsync = vi
        .fn()
        .mockResolvedValueOnce('Success on first call')
        .mockResolvedValueOnce('Success on second call')
        .mockRejectedValueOnce(new Error(errMessage));

      await expect(
        execGitShallowClone(url, directory, remoteBranch, { execFileAsync: mockFileExecAsync }),
      ).rejects.toThrow(errMessage);
      expect(mockFileExecAsync).toHaveBeenCalledTimes(3);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(1, 'git', ['-C', directory, 'init']);
      expect(mockFileExecAsync).toHaveBeenNthCalledWith(2, 'git', ['-C', directory, 'remote', 'add', 'origin', url]);
      expect(mockFileExecAsync).toHaveBeenLastCalledWith('git', [
        '-C',
        directory,
        'fetch',
        '--depth',
        '1',
        'origin',
        remoteBranch,
      ]);
    });
  });
});
</file>

<file path="tests/core/output/outputGenerate.test.ts">
import process from 'node:process';
import { XMLParser } from 'fast-xml-parser';
import { describe, expect, test, vi } from 'vitest';
import type { ProcessedFile } from '../../../src/core/file/fileTypes.js';
import { generateOutput } from '../../../src/core/output/outputGenerate.js';
import { createMockConfig } from '../../testing/testUtils.js';

describe('outputGenerate', () => {
  const mockDeps = {
    buildOutputGeneratorContext: vi.fn(),
    generateHandlebarOutput: vi.fn(),
    generateParsableXmlOutput: vi.fn(),
    sortOutputFiles: vi.fn(),
  };
  test('generateOutput should use sortOutputFiles before generating content', async () => {
    const mockConfig = createMockConfig({
      output: {
        filePath: 'output.txt',
        style: 'plain',
        git: { sortByChanges: true },
      },
    });
    const mockProcessedFiles: ProcessedFile[] = [
      { path: 'file1.txt', content: 'content1' },
      { path: 'file2.txt', content: 'content2' },
    ];
    const sortedFiles = [
      { path: 'file2.txt', content: 'content2' },
      { path: 'file1.txt', content: 'content1' },
    ];

    mockDeps.sortOutputFiles.mockResolvedValue(sortedFiles);
    mockDeps.buildOutputGeneratorContext.mockResolvedValue({
      processedFiles: sortedFiles,
      config: mockConfig,
      treeString: '',
      generationDate: new Date().toISOString(),
      instruction: '',
    });
    mockDeps.generateHandlebarOutput.mockResolvedValue('mock output');

    const output = await generateOutput([process.cwd()], mockConfig, mockProcessedFiles, [], mockDeps);

    expect(mockDeps.sortOutputFiles).toHaveBeenCalledWith(mockProcessedFiles, mockConfig);
    expect(mockDeps.buildOutputGeneratorContext).toHaveBeenCalledWith([process.cwd()], mockConfig, [], sortedFiles);
    expect(output).toBe('mock output');
  });

  test('generateOutput should write correct content to file (plain style)', async () => {
    const mockConfig = createMockConfig({
      output: {
        filePath: 'output.txt',
        style: 'plain',
        topFilesLength: 2,
        showLineNumbers: false,
        removeComments: false,
        removeEmptyLines: false,
      },
    });
    const mockProcessedFiles: ProcessedFile[] = [
      { path: 'file1.txt', content: 'content1' },
      { path: 'dir/file2.txt', content: 'content2' },
    ];

    const output = await generateOutput([process.cwd()], mockConfig, mockProcessedFiles, []);

    expect(output).toContain('File Summary');
    expect(output).toContain('File: file1.txt');
    expect(output).toContain('content1');
    expect(output).toContain('File: dir/file2.txt');
    expect(output).toContain('content2');
  });

  test('generateOutput should write correct content to file (parsable xml style)', async () => {
    const mockConfig = createMockConfig({
      output: {
        filePath: 'output.txt',
        style: 'xml',
        parsableStyle: true,
        topFilesLength: 2,
        showLineNumbers: false,
        removeComments: false,
        removeEmptyLines: false,
      },
    });
    const mockProcessedFiles: ProcessedFile[] = [
      { path: 'file1.txt', content: '<div>foo</div>' },
      { path: 'dir/file2.txt', content: 'if (a && b)' },
    ];

    const output = await generateOutput([process.cwd()], mockConfig, mockProcessedFiles, []);

    const parser = new XMLParser({ ignoreAttributes: false });
    const parsedOutput = parser.parse(output);

    expect(parsedOutput.repomix.file_summary).not.toBeUndefined();
    expect(parsedOutput.repomix.files.file).toEqual([
      {
        '#text': mockProcessedFiles[0].content,
        '@_path': mockProcessedFiles[0].path,
      },
      {
        '#text': mockProcessedFiles[1].content,
        '@_path': mockProcessedFiles[1].path,
      },
    ]);
  });

  test('generateOutput should write correct content to file (parsable markdown style)', async () => {
    const mockConfig = createMockConfig({
      output: {
        filePath: 'output.txt',
        style: 'markdown',
        parsableStyle: true,
        topFilesLength: 2,
        showLineNumbers: false,
        removeComments: false,
        removeEmptyLines: false,
      },
    });
    const mockProcessedFiles: ProcessedFile[] = [
      { path: 'file1.txt', content: 'content1' },
      { path: 'dir/file2.txt', content: '```\ncontent2\n```' },
    ];

    const output = await generateOutput([process.cwd()], mockConfig, mockProcessedFiles, []);

    expect(output).toContain('# File Summary');
    expect(output).toContain('## File: file1.txt');
    expect(output).toContain('````\ncontent1\n````');
    expect(output).toContain('## File: dir/file2.txt');
    expect(output).toContain('````\n```\ncontent2\n```\n````');
  });
});
</file>

<file path="tests/core/output/outputSort.test.ts">
import path from 'node:path';
import { describe, expect, test, vi } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import type { ProcessedFile } from '../../../src/core/file/fileTypes.js';
import { sortOutputFiles } from '../../../src/core/output/outputSort.js';

vi.mock('node:fs/promises');

describe('outputSort', () => {
  const sep = path.sep;

  describe('sort by git changes', () => {
    const mockConfig = {
      output: {
        git: {
          sortByChanges: true,
          sortByChangesMaxCommits: 150,
        },
      },
      cwd: '/test',
    } as unknown as RepomixConfigMerged;

    test('should sort files by git change count', async () => {
      const input: ProcessedFile[] = [
        { path: `src${sep}utils${sep}file1.ts`, content: 'content1' },
        { path: `src${sep}utils${sep}file2.ts`, content: 'content2' },
        { path: `src${sep}utils${sep}file3.ts`, content: 'content3' },
      ];

      const mockGetFileChangeCount = vi.fn().mockResolvedValue({
        [`src${sep}utils${sep}file1.ts`]: 5,
        [`src${sep}utils${sep}file2.ts`]: 10,
        [`src${sep}utils${sep}file3.ts`]: 2,
      });

      const mockIsGitInstalled = vi.fn().mockResolvedValue(true);

      const expected = [
        { path: `src${sep}utils${sep}file3.ts`, content: 'content3' }, // 2 changes
        { path: `src${sep}utils${sep}file1.ts`, content: 'content1' }, // 5 changes
        { path: `src${sep}utils${sep}file2.ts`, content: 'content2' }, // 10 changes
      ];

      expect(
        await sortOutputFiles(input, mockConfig, {
          getFileChangeCount: mockGetFileChangeCount,
          isGitInstalled: mockIsGitInstalled,
        }),
      ).toEqual(expected);

      expect(mockGetFileChangeCount).toHaveBeenCalledWith('/test', 150);
      expect(mockIsGitInstalled).toHaveBeenCalled();
    });

    test('should return original order when git is not installed', async () => {
      const input: ProcessedFile[] = [
        { path: `src${sep}utils${sep}file1.ts`, content: 'content1' },
        { path: `src${sep}utils${sep}file2.ts`, content: 'content2' },
      ];

      const mockGetFileChangeCount = vi.fn();
      const mockIsGitInstalled = vi.fn().mockResolvedValue(false);

      const result = await sortOutputFiles(input, mockConfig, {
        getFileChangeCount: mockGetFileChangeCount,
        isGitInstalled: mockIsGitInstalled,
      });

      expect(result).toEqual(input);
      expect(mockGetFileChangeCount).not.toHaveBeenCalled();
    });

    test('should return original order when git command fails', async () => {
      const input: ProcessedFile[] = [
        { path: `src${sep}utils${sep}file1.ts`, content: 'content1' },
        { path: `src${sep}utils${sep}file2.ts`, content: 'content2' },
      ];

      const mockGetFileChangeCount = vi.fn().mockRejectedValue(new Error('git command failed'));
      const mockIsGitInstalled = vi.fn().mockResolvedValue(true);

      const result = await sortOutputFiles(input, mockConfig, {
        getFileChangeCount: mockGetFileChangeCount,
        isGitInstalled: mockIsGitInstalled,
      });

      expect(result).toEqual(input);
    });

    test('should return original order when git sort is disabled', async () => {
      const input: ProcessedFile[] = [
        { path: `src${sep}utils${sep}file1.ts`, content: 'content1' },
        { path: `src${sep}utils${sep}file2.ts`, content: 'content2' },
      ];

      const config = {
        output: {
          git: {
            sortByChanges: false,
          },
        },
        cwd: '/test',
      } as unknown as RepomixConfigMerged;

      const mockGetFileChangeCount = vi.fn();
      const mockIsGitInstalled = vi.fn();

      const result = await sortOutputFiles(input, config, {
        getFileChangeCount: mockGetFileChangeCount,
        isGitInstalled: mockIsGitInstalled,
      });

      expect(result).toEqual(input);
      expect(mockGetFileChangeCount).not.toHaveBeenCalled();
      expect(mockIsGitInstalled).not.toHaveBeenCalled();
    });
  });
});
</file>

<file path="tests/core/treeSitter/LanguageParser.test.ts">
import { beforeAll, describe, expect, it } from 'vitest';
import { LanguageParser } from '../../../src/core/treeSitter/languageParser.js';

describe('LanguageParser', () => {
  let parser: LanguageParser;

  beforeAll(() => {
    parser = new LanguageParser();
  });

  describe('guessTheLang', () => {
    it('should return the correct language based on file extension', () => {
      const testCases = [
        { filePath: 'file.js', expected: 'javascript' },
        { filePath: 'file.ts', expected: 'typescript' },
        { filePath: 'file.sol', expected: 'solidity' },
        { filePath: 'Contract.sol', expected: 'solidity' },
        { filePath: 'path/to/MyContract.sol', expected: 'solidity' },
      ];

      for (const { filePath, expected } of testCases) {
        const lang = parser.guessTheLang(filePath);
        expect(lang).toBe(expected);
      }
    });

    it('should return undefined for unsupported extensions', () => {
      const filePath = 'file.txt';
      const lang = parser.guessTheLang(filePath);

      expect(lang).toBeUndefined();
    });
  });
});
</file>

<file path="tests/core/treeSitter/loadLanguage.test.ts">
import fs from 'node:fs/promises';
import { describe, expect, it, vi } from 'vitest';
import Parser from 'web-tree-sitter';
import { loadLanguage } from '../../../src/core/treeSitter/loadLanguage.js';

vi.mock('node:fs/promises');
vi.mock('web-tree-sitter', () => ({
  default: {
    Language: {
      load: vi.fn(),
    },
  },
}));
vi.mock('node:module', () => ({
  createRequire: () => ({
    resolve: (path: string) => `/mock/path/${path}`,
  }),
}));

describe('loadLanguage', () => {
  it('should throw error for empty language name', async () => {
    await expect(loadLanguage('')).rejects.toThrow('Invalid language name');
  });

  it('should load language successfully', async () => {
    const mockAccess = vi.mocked(fs.access);
    mockAccess.mockResolvedValue(undefined);

    const mockLoadLanguage = vi.fn().mockResolvedValue({ success: true });
    Parser.Language.load = mockLoadLanguage;

    await loadLanguage('javascript');

    expect(mockAccess).toHaveBeenCalledWith('/mock/path/tree-sitter-wasms/out/tree-sitter-javascript.wasm');
    expect(mockLoadLanguage).toHaveBeenCalledWith('/mock/path/tree-sitter-wasms/out/tree-sitter-javascript.wasm');
  });

  it('should throw error when WASM file is not found', async () => {
    const mockAccess = vi.mocked(fs.access);
    mockAccess.mockRejectedValue(new Error('File not found'));

    await expect(loadLanguage('javascript')).rejects.toThrow(
      'WASM file not found for language javascript: /mock/path/tree-sitter-wasms/out/tree-sitter-javascript.wasm',
    );
  });

  it('should handle language load error', async () => {
    const mockAccess = vi.mocked(fs.access);
    mockAccess.mockResolvedValue(undefined);

    const mockLoadLanguage = vi.fn().mockRejectedValue(new Error('Load failed'));
    Parser.Language.load = mockLoadLanguage;

    await expect(loadLanguage('javascript')).rejects.toThrow('Failed to load language javascript: Load failed');
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.c.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for C', () => {
  test('should parse C correctly', async () => {
    const fileContent = `
      /**
       * A simple C program demonstrating various language features
       */
      #include <stdio.h>
      #include <stdlib.h>

      /* Define a constant */
      #define MAX_SIZE 100

      /**
       * Structure representing a person
       */
      struct Person {
        char* name;
        int age;
      };

      /**
       * Union for different number types
       */
      union Number {
        int i;
        float f;
        double d;
      };

      /**
       * Enum for status codes
       */
      enum Status {
        SUCCESS = 0,
        ERROR = 1,
        PENDING = 2
      };

      /* Type definition for a pointer to function returning int */
      typedef int (*IntFunctionPtr)(int, int);

      /**
       * Add two integers
       * @param a First integer
       * @param b Second integer
       * @return Sum of a and b
       */
      int add(int a, int b) {
        // Add two numbers
        return a + b;
      }

      /**
       * Main function
       * @return Exit status
       */
      int main(int argc, char** argv) {
        // Create a person
        struct Person person;
        person.name = "John";
        person.age = 30;

        // Print info
        printf("Name: %s, Age: %d\\n", person.name, person.age);

        // Use union
        union Number num;
        num.i = 42;
        printf("Integer: %d\\n", num.i);

        // Function pointer
        IntFunctionPtr operation = add;
        printf("Result: %d\\n", operation(5, 3));

        return SUCCESS;
      }
    `;
    const filePath = 'sample.c';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      // Comments
      'A simple C program demonstrating various language features',
      'Define a constant',

      // Struct
      'struct Person',
      'Structure representing a person',

      // Union
      'Union for different number types',

      // Enum
      'enum Status',
      'Enum for status codes',

      // Type definition comment
      'Type definition for a pointer to function returning int',

      // Functions
      'int add(int a, int b)',
      'Add two integers',
      '@param a First integer',
      '@param b Second integer',
      '@return Sum of a and b',

      // Main function
      'int main(int argc, char** argv)',
      'Main function',
      '@return Exit status',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should handle function declarations and definitions', async () => {
    const fileContent = `
      /* Function declaration */
      void print_message(const char* message);

      /* Function definition */
      int calculate_sum(int values[], int count) {
        int sum = 0;
        for (int i = 0; i < count; i++) {
          sum += values[i];
        }
        return sum;
      }

      /* Function with no params */
      void init(void) {
        // Initialize something
      }
    `;
    const filePath = 'functions.c';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'Function declaration',
      'Function definition',
      'int calculate_sum(int values[], int count)',
      'Function with no params',
      'void init(void)',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should handle complex type definitions', async () => {
    const fileContent = `
      /* Simple typedef */
      typedef unsigned long size_t;

      /* Struct typedef */
      typedef struct {
        double x;
        double y;
      } Point;

      /* Enum typedef */
      typedef enum {
        RED,
        GREEN,
        BLUE
      } Color;

      /* Function pointer typedef */
      typedef void (*Callback)(void* data);

      /* Array typedef */
      typedef char String[256];
    `;
    const filePath = 'types.c';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'Simple typedef',
      'Struct typedef',
      'Enum typedef',
      'Function pointer typedef',
      'Array typedef',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.ruby.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for Ruby', () => {
  test('should parse Ruby correctly', async () => {
    const fileContent = `
      # User module for handling user-related functionality
      module User
        # Constants for user status
        ACTIVE = 1
        INACTIVE = 0

        # Person class represents a human user
        class Person
          attr_accessor :name, :age

          # Initialize a new person
          # @param name [String] the person's name
          # @param age [Integer] the person's age
          def initialize(name, age)
            @name = name
            @age = age
          end

          # Return a greeting
          # @return [String] a personalized greeting
          def greet
            "Hello, I'm #{@name} and I'm #{@age} years old!"
          end

          # A class method to create a Person
          def self.create(name, age)
            new(name, age)
          end
        end

        # Create a module method
        def self.find_by_name(name)
          # Implementation
        end
      end

      # Include external libraries
      require 'json'
      require_relative './helpers'

      # Create a user and greet
      person = User::Person.new("John", 30)
      puts person.greet
    `;
    const filePath = 'sample.rb';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      // Module comment
      'User module for handling user-related functionality',

      // Module definition
      'module User',

      // Constants
      'ACTIVE = 1',
      'INACTIVE = 0',

      // Class comment
      'Person class represents a human user',

      // Class definition
      'class Person',
      'attr_accessor :name, :age',

      // Method comments
      'Initialize a new person',
      "@param name [String] the person's name",
      "@param age [Integer] the person's age",

      // Method definitions
      'def initialize(name, age)',
      'def greet',
      'def self.create(name, age)',

      // Module method
      'def self.find_by_name(name)',

      // Require statements
      "require 'json'",
      "require_relative './helpers'",
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should handle single class with inheritance', async () => {
    const fileContent = `
      class Admin < User
        # Admin has higher privileges
        def admin?
          true
        end
      end
    `;
    const filePath = 'admin.rb';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = ['class Admin < User', 'Admin has higher privileges', 'def admin?'];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should handle different comment styles', async () => {
    const fileContent = `
      # This is a regular comment
      class Example
        # This is a documentation comment
        # with multiple lines
        def method_with_comment
          # In-method comment
          puts "Hello"
        end

        =begin
        This is a multi-line comment block
        that can span multiple lines
        =end
        def another_method
          puts "World"
        end
      end
    `;
    const filePath = 'comments.rb';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'This is a regular comment',
      '# This is a documentation comment',
      '# with multiple lines',
      'In-method comment',
      'This is a multi-line comment block',
      'that can span multiple lines',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should handle singleton methods and module mixins', async () => {
    const fileContent = `
      module Loggable
        def log(message)
          puts "[LOG] #{message}"
        end
      end

      class Service
        include Loggable
        extend Comparable

        # Singleton method
        def self.fetch_data
          # Implementation
        end

        # Instance method
        def process
          log("Processing...")
        end
      end

      # Monkey patching an existing class
      class String
        def palindrome?
          self == self.reverse
        end
      end
    `;
    const filePath = 'service.rb';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'module Loggable',
      'def log(message)',
      'class Service',
      'Singleton method',
      'def self.fetch_data',
      'Instance method',
      'def process',
      'class String',
      'def palindrome?',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.solidity.test.ts">
import { beforeAll, describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { LanguageParser } from '../../../src/core/treeSitter/languageParser.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('Solidity File Parsing', () => {
  let parser: LanguageParser;
  const defaultConfig: RepomixConfigMerged = {
    cwd: process.cwd(),
    output: {
      filePath: 'repomix-output.xml',
      style: 'xml',
      parsableStyle: false,
      fileSummary: true,
      directoryStructure: true,
      removeComments: false,
      removeEmptyLines: false,
      compress: false,
      topFilesLength: 5,
      showLineNumbers: false,
      copyToClipboard: false,
      git: {
        sortByChanges: true,
        sortByChangesMaxCommits: 100,
      },
    },
    include: [],
    ignore: {
      useGitignore: true,
      useDefaultPatterns: true,
      customPatterns: [],
    },
    security: {
      enableSecurityCheck: true,
    },
    tokenCount: {
      encoding: 'o200k_base',
    },
  };

  beforeAll(async () => {
    parser = new LanguageParser();
    await parser.init();
  });

  test('should parse Solidity contract definitions correctly', async () => {
    const content = `
contract BaseContract {
    uint256 internal value;
}

interface IMyInterface {
    function getValue() external view returns (uint256);
}

abstract contract AbstractContract {
    function abstractFunction() virtual external;
}

contract MyContract is BaseContract, IMyInterface {
    function getValue() external view override returns (uint256) {
        return value;
    }
}`;

    const config = {
      ...defaultConfig,
      output: {
        ...defaultConfig.output,
        removeComments: true,
      },
    };

    const result = await parseFile(content, 'test.sol', config);

    // 各種コントラクト定義が保持されていることを確認
    expect(result).toContain('contract BaseContract {');
    expect(result).toContain('interface IMyInterface {');
    expect(result).toContain('abstract contract AbstractContract {');
    expect(result).toContain('contract MyContract is BaseContract, IMyInterface {');

    // 関数定義が保持されていることを確認
    expect(result).toContain('function getValue() external view returns (uint256)');
    expect(result).toContain('function abstractFunction() virtual external');
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.swift.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for Swift', () => {
  test('should parse Swift correctly', async () => {
    const fileContent = `
      // Swift sample demonstrating various language features
      import Foundation

      /// Protocol defining a shape
      protocol Shape {
        /// Calculate the area of the shape
        func area() -> Double

        /// The name of the shape
        var name: String { get }
      }

      /// A class representing a circle
      class Circle: Shape {
        /// The radius of the circle
        let radius: Double

        /// The name of the shape
        var name: String {
          return "Circle"
        }

        /// Initialize with a radius
        init(radius: Double) {
          self.radius = radius
        }

        /// Calculate the area using πr²
        func area() -> Double {
          return Double.pi * radius * radius
        }

        /// Deinitializer
        deinit {
          print("Circle deinitialized")
        }
      }

      /// A class representing a rectangle
      class Rectangle: Shape {
        /// The width of the rectangle
        let width: Double

        /// The height of the rectangle
        let height: Double

        /// The name of the shape
        var name: String {
          return "Rectangle"
        }

        /// Initialize with width and height
        init(width: Double, height: Double) {
          self.width = width
          self.height = height
        }

        /// Calculate the area using width × height
        func area() -> Double {
          return width * height
        }

        /// Access by index, where 0 is width and 1 is height
        subscript(index: Int) -> Double {
          get {
            switch index {
            case 0: return width
            case 1: return height
            default: return 0
            }
          }
        }
      }

      /// Calculate the total area of shapes
      func calculateTotalArea(shapes: [Shape]) -> Double {
        return shapes.reduce(0) { $0 + $1.area() }
      }

      // Create shapes and calculate their areas
      let circle = Circle(radius: 5)
      let rectangle = Rectangle(width: 10, height: 20)
      let shapes: [Shape] = [circle, rectangle]
      let totalArea = calculateTotalArea(shapes: shapes)
      print("Total area: \\(totalArea)")
    `;
    const filePath = 'sample.swift';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      // Protocol
      'Protocol defining a shape',
      'protocol Shape',
      '/// Calculate the area of the shape',
      '/// The name of the shape',

      // Classes
      'A class representing a circle',
      'class Circle: Shape',
      'A class representing a rectangle',
      'class Rectangle: Shape',

      // Properties
      'The radius of the circle',
      'let radius: Double',
      'The width of the rectangle',
      'let width: Double',
      'The height of the rectangle',
      'let height: Double',

      // Methods
      'Calculate the area using πr²',
      'Calculate the area using width × height',

      // Initializers
      'Initialize with a radius',
      'init(radius: Double)',
      'Initialize with width and height',
      'init(width: Double, height: Double)',

      // Deinitializer
      'Deinitializer',
      'deinit',

      // Subscript
      'Access by index, where 0 is width and 1 is height',
      'subscript(index: Int) -> Double',

      // Global function
      'Calculate the total area of shapes',
      'func calculateTotalArea(shapes: [Shape]) -> Double',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should handle extensions and computed properties', async () => {
    const fileContent = `
      // Define a basic struct
      struct Point {
        var x: Double
        var y: Double
      }

      // Extensions
      extension Point {
        // Computed property
        var magnitude: Double {
          return sqrt(x*x + y*y)
        }

        // Method
        func distance(to point: Point) -> Double {
          let dx = x - point.x
          let dy = y - point.y
          return sqrt(dx*dx + dy*dy)
        }

        // Static method
        static func zero() -> Point {
          return Point(x: 0, y: 0)
        }
      }
    `;
    const filePath = 'extensions.swift';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'struct Point',
      'var x: Double',
      'var y: Double',
      // 注：extension Pointが含まれないようなので削除
      'Computed property',
      'var magnitude: Double',
      'Method',
      'func distance(to point: Point) -> Double',
      'Static method',
      'static func zero() -> Point',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should handle enums and generic types', async () => {
    const fileContent = `
      /// Represents a result with either a success value or an error
      enum Result<Success, Failure> where Failure: Error {
        /// The success case with associated value
        case success(Success)

        /// The failure case with associated error
        case failure(Failure)

        /// Returns the success value or throws the error
        func get() throws -> Success {
          switch self {
          case .success(let value):
            return value
          case .failure(let error):
            throw error
          }
        }
      }

      /// Types of HTTP methods
      enum HTTPMethod: String {
        case get = "GET"
        case post = "POST"
        case put = "PUT"
        case delete = "DELETE"
      }

      /// Generic stack implementation
      struct Stack<Element> {
        private var elements: [Element] = []

        /// Adds an element to the top of the stack
        mutating func push(_ element: Element) {
          elements.append(element)
        }

        /// Removes and returns the top element
        mutating func pop() -> Element? {
          return elements.popLast()
        }
      }
    `;
    const filePath = 'generics.swift';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'Represents a result with either a success value or an error',
      'enum Result<Success, Failure> where Failure: Error',
      'The success case with associated value',
      'The failure case with associated error',
      'Returns the success value or throws the error',
      'func get() throws -> Success',
      'Types of HTTP methods',
      'enum HTTPMethod: String',
      'Generic stack implementation',
      'struct Stack<Element>',
      'Adds an element to the top of the stack',
      'mutating func push(_ element: Element)',
      'Removes and returns the top element',
      'mutating func pop() -> Element?',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.vue.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { CHUNK_SEPARATOR, parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for Vue', () => {
  test('should parse Vue files correctly', async () => {
    const fileContent = `
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>{{ greeting }}</p>
  </div>
</template>

<script>
// Hello component
/**
 * Vue component that displays a greeting message
 */
export default {
  name: 'HelloWorld',
  props: {
    msg: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      greeting: 'Welcome to Vue!'
    };
  },
  methods: {
    /**
     * Updates the greeting message
     * @param newGreeting The new greeting message
     */
    updateGreeting(newGreeting) {
      this.greeting = newGreeting;
    }
  }
};
</script>

<style scoped>
.hello {
  margin: 20px;
  padding: 20px;
  border: 1px solid #ccc;
}
</style>
    `;
    const filePath = 'HelloWorld.vue';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      '<template>',
      '<script>',
      '<style scoped>',
      '// Hello component',
      '* Vue component that displays a greeting message',
      'updateGreeting',
      '* Updates the greeting message',
      '@param newGreeting The new greeting message',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should parse Vue files with TypeScript correctly', async () => {
    const fileContent = `
<template>
  <div class="counter">
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script lang="ts">
// Counter component
import { defineComponent } from 'vue';

/**
 * A simple counter component
 */
export default defineComponent({
  name: 'Counter',
  data() {
    return {
      count: 0
    };
  },
  methods: {
    /**
     * Increments the counter
     */
    increment(): void {
      this.count++;
    }
  }
});
</script>
    `;
    const filePath = 'Counter.vue';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      '<template>',
      '<script lang="ts">',
      '// Counter component',
      '* A simple counter component',
      'increment',
      '* Increments the counter',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should parse Vue files with composition API correctly', async () => {
    const fileContent = `
<template>
  <div class="composition">
    <p>Message: {{ message }}</p>
    <button @click="updateMessage">Update</button>
  </div>
</template>

<script setup>
// Composition API example
import { ref } from 'vue';

/**
 * Message state
 */
const message = ref('Hello from Composition API');

/**
 * Updates the message
 */
function updateMessage() {
  message.value = 'Updated message!';
}
</script>
    `;
    const filePath = 'Composition.vue';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      '<template>',
      '<script setup>',
      '// Composition API example',
      '* Message state',
      'updateMessage',
      '* Updates the message',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });
});
</file>

<file path="tests/core/packager.test.ts">
import path from 'node:path';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { pack } from '../../src/core/packager.js';
import { TokenCounter } from '../../src/core/tokenCount/tokenCount.js';
import { createMockConfig } from '../testing/testUtils.js';

vi.mock('node:fs/promises');
vi.mock('fs/promises');
vi.mock('../../src/core/tokenCount/tokenCount');
vi.mock('clipboardy', () => ({
  default: {
    write: vi.fn(),
  },
}));

describe('packager', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  test('pack should orchestrate packing files and generating output', async () => {
    const file2Path = path.join('dir1', 'file2.txt');
    const mockRawFiles = [
      { path: 'file1.txt', content: 'raw content 1' },
      { path: file2Path, content: 'raw content 2' },
    ];
    const mockSafeRawFiles = [
      { path: 'file1.txt', content: 'safed content 1' },
      { path: file2Path, content: 'safed content 2' },
    ];
    const mockProcessedFiles = [
      { path: 'file1.txt', content: 'processed content 1' },
      { path: file2Path, content: 'processed content 2' },
    ];
    const mockOutput = 'mock output';
    const mockFilePaths = ['file1.txt', file2Path];

    const mockDeps = {
      searchFiles: vi.fn().mockResolvedValue({
        filePaths: mockFilePaths,
        emptyDirPaths: [],
      }),
      sortPaths: vi.fn().mockImplementation((paths) => Promise.resolve(paths)),
      collectFiles: vi.fn().mockResolvedValue(mockRawFiles),
      processFiles: vi.fn().mockReturnValue(mockProcessedFiles),
      validateFileSafety: vi.fn().mockResolvedValue({
        safeFilePaths: mockFilePaths,
        safeRawFiles: mockSafeRawFiles,
        suspiciousFileResults: [],
      }),
      generateOutput: vi.fn().mockResolvedValue(mockOutput),
      writeOutputToDisk: vi.fn().mockResolvedValue(undefined),
      copyToClipboardIfEnabled: vi.fn().mockResolvedValue(undefined),
      calculateMetrics: vi.fn().mockResolvedValue({
        totalFiles: 2,
        totalCharacters: 11,
        totalTokens: 10,
        fileCharCounts: {
          'file1.txt': 19,
          [file2Path]: 19,
        },
        fileTokenCounts: {
          'file1.txt': 10,
          [file2Path]: 10,
        },
      }),
    };

    vi.mocked(TokenCounter.prototype.countTokens).mockReturnValue(10);

    const mockConfig = createMockConfig();
    const progressCallback = vi.fn();
    const result = await pack(['root'], mockConfig, progressCallback, mockDeps);

    expect(mockDeps.searchFiles).toHaveBeenCalledWith('root', mockConfig);
    expect(mockDeps.collectFiles).toHaveBeenCalledWith(mockFilePaths, 'root', progressCallback);
    expect(mockDeps.validateFileSafety).toHaveBeenCalled();
    expect(mockDeps.processFiles).toHaveBeenCalled();
    expect(mockDeps.writeOutputToDisk).toHaveBeenCalled();
    expect(mockDeps.generateOutput).toHaveBeenCalled();
    expect(mockDeps.calculateMetrics).toHaveBeenCalled();

    expect(mockDeps.validateFileSafety).toHaveBeenCalledWith(mockRawFiles, progressCallback, mockConfig);
    expect(mockDeps.processFiles).toHaveBeenCalledWith(mockSafeRawFiles, mockConfig, progressCallback);
    expect(mockDeps.generateOutput).toHaveBeenCalledWith(['root'], mockConfig, mockProcessedFiles, mockFilePaths);
    expect(mockDeps.writeOutputToDisk).toHaveBeenCalledWith(mockOutput, mockConfig);
    expect(mockDeps.copyToClipboardIfEnabled).toHaveBeenCalledWith(mockOutput, progressCallback, mockConfig);
    expect(mockDeps.calculateMetrics).toHaveBeenCalledWith(
      mockProcessedFiles,
      mockOutput,
      progressCallback,
      mockConfig,
    );

    // Check the result of pack function
    expect(result.totalFiles).toBe(2);
    expect(result.totalCharacters).toBe(11);
    expect(result.totalTokens).toBe(10);
    expect(result.fileCharCounts).toEqual({
      'file1.txt': 19,
      [file2Path]: 19,
    });
    expect(result.fileTokenCounts).toEqual({
      'file1.txt': 10,
      [file2Path]: 10,
    });
  });
});
</file>

<file path="tests/integration-tests/packager.test.ts">
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import process from 'node:process';
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import { loadFileConfig, mergeConfigs } from '../../src/config/configLoad.js';
import type { RepomixConfigFile, RepomixConfigMerged, RepomixOutputStyle } from '../../src/config/configSchema.js';
import { collectFiles } from '../../src/core/file/fileCollect.js';
import { searchFiles } from '../../src/core/file/fileSearch.js';
import type { ProcessedFile } from '../../src/core/file/fileTypes.js';
import type { FileCollectTask } from '../../src/core/file/workers/fileCollectWorker.js';
import fileCollectWorker from '../../src/core/file/workers/fileCollectWorker.js';
import fileProcessWorker from '../../src/core/file/workers/fileProcessWorker.js';
import { generateOutput } from '../../src/core/output/outputGenerate.js';
import { pack } from '../../src/core/packager.js';
import { copyToClipboardIfEnabled } from '../../src/core/packager/copyToClipboardIfEnabled.js';
import { writeOutputToDisk } from '../../src/core/packager/writeOutputToDisk.js';
import { filterOutUntrustedFiles } from '../../src/core/security/filterOutUntrustedFiles.js';
import { validateFileSafety } from '../../src/core/security/validateFileSafety.js';
import { isWindows } from '../testing/testUtils.js';

const fixturesDir = path.join(__dirname, 'fixtures', 'packager');
const inputsDir = path.join(fixturesDir, 'inputs');
const outputsDir = path.join(fixturesDir, 'outputs');

const mockCollectFileInitTaskRunner = () => {
  return async (task: FileCollectTask) => {
    return await fileCollectWorker(task);
  };
};

describe.runIf(!isWindows)('packager integration', () => {
  const testCases = [
    {
      desc: 'simple plain style',
      input: 'simple-project',
      output: 'simple-project-output.txt',
      config: {},
    },
    {
      desc: 'simple xml style',
      input: 'simple-project',
      output: 'simple-project-output.xml',
      config: {
        output: { style: 'xml', filePath: 'simple-project-output.xml' },
      },
    },
  ];

  let tempDir: string;

  beforeEach(async () => {
    // Create a temporary directory for each test
    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'repomix-test-'));
  });

  afterEach(async () => {
    // Clean up the temporary directory after each test
    await fs.rm(tempDir, { recursive: true, force: true });
  });

  for (const { desc, input, output, config } of testCases) {
    test(`should correctly pack ${desc}`, async () => {
      const inputDir = path.join(inputsDir, input);
      const expectedOutputPath = path.join(outputsDir, output);
      const actualOutputPath = path.join(tempDir, output);

      const fileConfig: RepomixConfigFile = await loadFileConfig(inputDir, null);
      const mergedConfig: RepomixConfigMerged = mergeConfigs(process.cwd(), fileConfig, {
        output: {
          filePath: actualOutputPath,
          style: (config.output?.style || 'plain') as RepomixOutputStyle,
          git: { sortByChanges: false },
        },
      });

      // Run the pack function
      await pack([inputDir], mergedConfig, () => {}, {
        searchFiles,
        sortPaths: (filePaths) => filePaths,
        collectFiles: (filePaths, rootDir, progressCallback) => {
          return collectFiles(filePaths, rootDir, progressCallback, {
            initTaskRunner: mockCollectFileInitTaskRunner,
          });
        },
        processFiles: async (rawFiles, config, progressCallback) => {
          const processedFiles: ProcessedFile[] = [];
          for (const rawFile of rawFiles) {
            processedFiles.push(await fileProcessWorker({ rawFile, config }));
          }
          return processedFiles;
        },
        generateOutput,
        validateFileSafety: (rawFiles, progressCallback, config) => {
          return validateFileSafety(rawFiles, progressCallback, config, {
            runSecurityCheck: async () => [],
            filterOutUntrustedFiles,
          });
        },
        writeOutputToDisk,
        copyToClipboardIfEnabled,
        calculateMetrics: async (processedFiles, output, progressCallback, config) => {
          return {
            totalFiles: processedFiles.length,
            totalCharacters: processedFiles.reduce((acc, file) => acc + file.content.length, 0),
            totalTokens: processedFiles.reduce((acc, file) => acc + file.content.split(/\s+/).length, 0),
            fileCharCounts: processedFiles.reduce(
              (acc, file) => {
                acc[file.path] = file.content.length;
                return acc;
              },
              {} as Record<string, number>,
            ),
            fileTokenCounts: processedFiles.reduce(
              (acc, file) => {
                acc[file.path] = file.content.split(/\s+/).length;
                return acc;
              },
              {} as Record<string, number>,
            ),
          };
        },
      });

      // Read the actual and expected outputs
      const actualOutput = await fs.readFile(actualOutputPath, 'utf-8');
      const expectedOutput = await fs.readFile(expectedOutputPath, 'utf-8');

      // Compare the outputs
      expect(actualOutput).toBe(expectedOutput);

      // Optionally, update the expected output if explicitly requested
      if (process.env.UPDATE_EXPECTED_OUTPUT) {
        await fs.writeFile(expectedOutputPath, actualOutput);
        console.log(`Updated expected output for ${desc}`);
      }
    });
  }
});
</file>

<file path="tests/mcp/tools/fileSystemReadDirectoryTool.test.ts">
import { promises as fs } from 'node:fs';
import type { Dirent, Stats } from 'node:fs';
import path from 'node:path';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { registerFileSystemReadDirectoryTool } from '../../../src/mcp/tools/fileSystemReadDirectoryTool.js';
import { logger } from '../../../src/shared/logger.js';

vi.mock('node:fs');
vi.mock('node:path');
vi.mock('../../../src/shared/logger.js');

describe('FileSystemReadDirectoryTool', () => {
  const mockServer = {
    tool: vi.fn().mockReturnThis(),
  } as unknown as McpServer;

  let toolHandler: (args: { path: string }) => Promise<CallToolResult>;

  beforeEach(() => {
    vi.resetAllMocks();
    registerFileSystemReadDirectoryTool(mockServer);
    toolHandler = (mockServer.tool as ReturnType<typeof vi.fn>).mock.calls[0][3];

    // デフォルトのpath.isAbsoluteの動作をモック
    vi.mocked(path.isAbsolute).mockImplementation((p: string) => p.startsWith('/'));
  });

  test('should register tool with correct parameters', () => {
    expect(mockServer.tool).toHaveBeenCalledWith(
      'file_system_read_directory',
      'List contents of a directory using an absolute path.',
      expect.any(Object),
      expect.any(Function),
    );
  });

  test('should handle relative path error', async () => {
    const testPath = 'relative/path';
    vi.mocked(path.isAbsolute).mockReturnValue(false);

    const result = await toolHandler({ path: testPath });

    expect(result).toEqual({
      isError: true,
      content: [
        {
          type: 'text',
          text: `Error: Path must be absolute. Received: ${testPath}`,
        },
      ],
    });
  });

  test('should handle non-existent directory', async () => {
    const testPath = '/non/existent/dir';
    vi.mocked(path.isAbsolute).mockReturnValue(true);
    vi.mocked(fs.access).mockRejectedValue(new Error('ENOENT'));

    const result = await toolHandler({ path: testPath });

    expect(result).toEqual({
      isError: true,
      content: [
        {
          type: 'text',
          text: `Error: Directory not found at path: ${testPath}`,
        },
      ],
    });
  });
});
</file>

<file path="tests/mcp/tools/fileSystemReadFileTool.test.ts">
import { promises as fs } from 'node:fs';
import type { Stats } from 'node:fs';
import path from 'node:path';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { runSecretLint } from '../../../src/core/security/workers/securityCheckWorker.js';
import { registerFileSystemReadFileTool } from '../../../src/mcp/tools/fileSystemReadFileTool.js';
import { logger } from '../../../src/shared/logger.js';

vi.mock('node:fs');
vi.mock('node:path');
vi.mock('../../../src/shared/logger.js');
vi.mock('../../../src/core/security/workers/securityCheckWorker.js');

describe('FileSystemReadFileTool', () => {
  const mockServer = {
    tool: vi.fn().mockReturnThis(),
  } as unknown as McpServer;

  let toolHandler: (args: { path: string }) => Promise<CallToolResult>;

  beforeEach(() => {
    vi.resetAllMocks();
    registerFileSystemReadFileTool(mockServer);
    toolHandler = (mockServer.tool as ReturnType<typeof vi.fn>).mock.calls[0][3];

    // デフォルトのpath.isAbsoluteの動作をモック
    vi.mocked(path.isAbsolute).mockImplementation((p: string) => p.startsWith('/'));
  });

  test('should register tool with correct parameters', () => {
    expect(mockServer.tool).toHaveBeenCalledWith(
      'file_system_read_file',
      'Read a file using an absolute path with security validation.',
      expect.any(Object),
      expect.any(Function),
    );
  });

  test('should handle relative path error', async () => {
    const testPath = 'relative/path.txt';
    vi.mocked(path.isAbsolute).mockReturnValue(false);

    const result = await toolHandler({ path: testPath });

    expect(result).toEqual({
      isError: true,
      content: [
        {
          type: 'text',
          text: `Error: Path must be absolute. Received: ${testPath}`,
        },
      ],
    });
  });

  test('should handle non-existent file', async () => {
    const testPath = '/non/existent/file.txt';
    vi.mocked(path.isAbsolute).mockReturnValue(true);
    vi.mocked(fs.access).mockRejectedValue(new Error('ENOENT'));

    const result = await toolHandler({ path: testPath });

    expect(result).toEqual({
      isError: true,
      content: [
        {
          type: 'text',
          text: `Error: File not found at path: ${testPath}`,
        },
      ],
    });
  });
});
</file>

<file path="tests/mcp/tools/packCodebaseTool.test.ts">
import path from 'node:path';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { runCli } from '../../../src/cli/cliRun.js';
import { createToolWorkspace, formatToolError, formatToolResponse } from '../../../src/mcp/tools/mcpToolRuntime.js';
import { registerPackCodebaseTool } from '../../../src/mcp/tools/packCodebaseTool.js';

vi.mock('node:path');
vi.mock('../../../src/cli/cliRun.js');
vi.mock('../../../src/mcp/tools/mcpToolRuntime.js');

describe('PackCodebaseTool', () => {
  const mockServer = {
    tool: vi.fn().mockReturnThis(),
  } as unknown as McpServer;

  let toolHandler: (args: {
    directory: string;
    compress?: boolean;
    includePatterns?: string;
    ignorePatterns?: string;
    topFilesLength?: number;
  }) => Promise<CallToolResult>;

  const defaultPackResult = {
    totalFiles: 10,
    totalCharacters: 1000,
    totalTokens: 500,
    fileCharCounts: { 'test.js': 100 },
    fileTokenCounts: { 'test.js': 50 },
    suspiciousFilesResults: [],
  };

  beforeEach(() => {
    vi.resetAllMocks();
    registerPackCodebaseTool(mockServer);
    toolHandler = (mockServer.tool as ReturnType<typeof vi.fn>).mock.calls[0][3];

    // デフォルトのパスの動作をモック
    vi.mocked(path.join).mockImplementation((...args) => args.join('/'));

    // mcpToolRuntimeのデフォルトの動作をモック
    vi.mocked(createToolWorkspace).mockResolvedValue('/temp/dir');
    vi.mocked(formatToolResponse).mockReturnValue({
      content: [{ type: 'text', text: 'Success response' }],
    });
    vi.mocked(formatToolError).mockReturnValue({
      isError: true,
      content: [{ type: 'text', text: 'Error response' }],
    });

    // runCliのデフォルト動作
    vi.mocked(runCli).mockImplementation(async (directories, cwd, opts = {}) => ({
      packResult: defaultPackResult,
      config: {
        output: {
          filePath: opts.output ?? '/temp/dir/repomix-output.xml',
          style: opts.style ?? 'xml',
          parsableStyle: false,
          fileSummary: true,
          directoryStructure: true,
          removeComments: false,
          removeEmptyLines: false,
          compress: opts.compress ?? true,
          topFilesLength: opts.topFilesLen ?? 5,
          showLineNumbers: false,
          copyToClipboard: false,
          git: {
            sortByChanges: true,
            sortByChangesMaxCommits: 100,
          },
          includeEmptyDirectories: false,
        },
        cwd,
        include: Array.isArray(opts.include) ? opts.include : opts.include ? [opts.include] : [],
        ignore: {
          useGitignore: true,
          useDefaultPatterns: true,
          customPatterns: opts.ignore ? [opts.ignore] : [],
        },
        security: {
          enableSecurityCheck: opts.securityCheck ?? true,
        },
        tokenCount: {
          encoding: 'o200k_base' as const,
        },
      },
    }));
  });

  test('should register tool with correct parameters', () => {
    expect(mockServer.tool).toHaveBeenCalledWith(
      'pack_codebase',
      'Package local code directory into a consolidated file for AI analysis',
      expect.any(Object),
      expect.any(Function),
    );
  });

  test('should handle custom options', async () => {
    const testDir = '/test/project';
    const options = {
      directory: testDir,
      compress: false,
      includePatterns: '**/*.js',
      ignorePatterns: 'test/**',
      topFilesLength: 10,
    };

    await toolHandler(options);

    expect(runCli).toHaveBeenCalledWith(
      ['.'],
      testDir,
      expect.objectContaining({
        compress: false,
        include: '**/*.js',
        ignore: 'test/**',
        topFilesLen: 10,
      }),
    );
  });

  test('should handle CLI execution failure', async () => {
    const testDir = '/test/project';
    vi.mocked(runCli).mockResolvedValue(undefined);

    const result = await toolHandler({ directory: testDir });

    expect(result).toEqual({
      isError: true,
      content: [
        {
          type: 'text',
          text: JSON.stringify(
            {
              success: false,
              error: 'Failed to return a result',
            },
            null,
            2,
          ),
        },
      ],
    });
  });

  test('should handle general error', async () => {
    const testDir = '/test/project';
    const error = new Error('Pack failed');
    vi.mocked(runCli).mockRejectedValue(error);

    const result = await toolHandler({ directory: testDir });

    expect(formatToolError).toHaveBeenCalledWith(error);
    expect(result).toEqual({
      isError: true,
      content: [{ type: 'text', text: 'Error response' }],
    });
  });

  test('should handle workspace creation error', async () => {
    const testDir = '/test/project';
    const error = new Error('Workspace creation failed');
    vi.mocked(createToolWorkspace).mockRejectedValue(error);

    const result = await toolHandler({ directory: testDir });

    expect(formatToolError).toHaveBeenCalledWith(error);
    expect(result).toEqual({
      isError: true,
      content: [{ type: 'text', text: 'Error response' }],
    });
  });
});
</file>

<file path="tests/mcp/mcpServer.test.ts">
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { getVersion } from '../../src/core/file/packageJsonParse.js';
import { createMcpServer, runMcpServer } from '../../src/mcp/mcpServer.js';
import { logger } from '../../src/shared/logger.js';

// Mock dependencies
vi.mock('@modelcontextprotocol/sdk/server/mcp.js');
vi.mock('@modelcontextprotocol/sdk/server/stdio.js');
vi.mock('../../src/core/file/packageJsonParse.js');
vi.mock('../../src/shared/logger.js');

describe('MCP Server', () => {
  let mockExit: ReturnType<typeof vi.fn<(code?: number) => never>>;
  const mockVersion = '1.0.0';

  const mockLogger = {
    trace: vi.fn(),
    error: vi.fn(),
  };

  beforeEach(async () => {
    vi.resetAllMocks();
    mockExit = vi.fn();
    vi.mocked(getVersion).mockResolvedValue(mockVersion);

    // ロガーのモックを設定
    vi.mocked(logger.trace).mockImplementation(mockLogger.trace);
    vi.mocked(logger.error).mockImplementation(mockLogger.error);
  });

  describe('createMcpServer', () => {
    test('should create server with correct configuration', async () => {
      const server = await createMcpServer();

      expect(McpServer).toHaveBeenCalledWith({
        name: 'repomix-mcp-server',
        version: mockVersion,
      });
      expect(server).toBeDefined();
    });
  });

  describe('runMcpServer', () => {
    test('should connect server with stdio transport', async () => {
      await expect(runMcpServer({ processExit: mockExit })).resolves.toBeUndefined();

      expect(StdioServerTransport).toHaveBeenCalled();
      expect(vi.mocked(McpServer).mock.results[0].value.connect).toHaveBeenCalledWith(
        vi.mocked(StdioServerTransport).mock.results[0].value,
      );
      expect(logger.trace).toHaveBeenCalledWith('Repomix MCP Server running on stdio');
    });

    test('should handle connection error', async () => {
      const error = new Error('Connection failed');
      const mockMcpServer = {
        tool: vi.fn().mockReturnThis(),
        prompt: vi.fn().mockReturnThis(),
        connect: vi.fn().mockRejectedValue(error),
        close: vi.fn().mockResolvedValue(undefined),
        ...createMockServerProps(),
      } as unknown as McpServer;

      vi.mocked(McpServer).mockImplementation(() => mockMcpServer);
      try {
        await runMcpServer({ processExit: mockExit });
      } catch (e) {
        expect(e).toEqual(error);
      }

      expect(logger.error).toHaveBeenCalledWith('Failed to start MCP server:', error);
      expect(mockExit).toHaveBeenCalledWith(1);
    });

    test('should handle SIGINT signal', async () => {
      const mockServer = {
        tool: vi.fn().mockReturnThis(),
        prompt: vi.fn().mockReturnThis(),
        connect: vi.fn().mockResolvedValue(undefined),
        close: vi.fn().mockResolvedValue(undefined),
        ...createMockServerProps(),
      } as unknown as McpServer;

      vi.mocked(McpServer).mockImplementation(() => mockServer);
      await expect(runMcpServer({ processExit: mockExit })).resolves.toBeUndefined();
      mockLogger.trace.mockClear();
      process.emit('SIGINT');

      // 非同期処理の完了を待つ
      await new Promise((resolve) => setTimeout(resolve, 0));

      expect(mockServer.close).toHaveBeenCalled();
      expect(logger.trace).toHaveBeenCalledWith('Repomix MCP Server shutdown complete');
      expect(mockExit).toHaveBeenCalledWith(0);
    });

    test('should handle SIGTERM signal', async () => {
      const mockServer = {
        tool: vi.fn().mockReturnThis(),
        prompt: vi.fn().mockReturnThis(),
        connect: vi.fn().mockResolvedValue(undefined),
        close: vi.fn().mockResolvedValue(undefined),
        ...createMockServerProps(),
      } as unknown as McpServer;

      vi.mocked(McpServer).mockImplementation(() => mockServer);
      await expect(runMcpServer({ processExit: mockExit })).resolves.toBeUndefined();
      mockLogger.trace.mockClear();
      process.emit('SIGTERM');

      // 非同期処理の完了を待つ
      await new Promise((resolve) => setTimeout(resolve, 0));

      expect(mockServer.close).toHaveBeenCalled();
      expect(logger.trace).toHaveBeenCalledWith('Repomix MCP Server shutdown complete');
      expect(mockExit).toHaveBeenCalledWith(0);
    });

    test('should handle shutdown error', async () => {
      const error = new Error('Shutdown failed');
      const mockServer = {
        tool: vi.fn().mockReturnThis(),
        prompt: vi.fn().mockReturnThis(),
        connect: vi.fn().mockResolvedValue(undefined),
        close: vi.fn().mockRejectedValue(error),
        ...createMockServerProps(),
      } as unknown as McpServer;

      vi.mocked(McpServer).mockImplementation(() => mockServer);
      await expect(runMcpServer({ processExit: mockExit })).resolves.toBeUndefined();
      mockLogger.error.mockClear();
      process.emit('SIGINT');

      // 非同期処理の完了を待つ
      await new Promise((resolve) => setTimeout(resolve, 0));

      expect(logger.error).toHaveBeenCalledWith('Error during MCP server shutdown:', error);
      expect(mockExit).toHaveBeenCalledWith(1);
    });
  });
});

function createMockServerProps() {
  return {
    server: {},
    _registeredResources: new Map(),
    _registeredResourceTemplates: new Map(),
    _registeredTools: new Map(),
    _registeredPrompts: new Map(),
    _registeredAgents: new Map(),
    _registeredInstructions: new Map(),
    _registeredRoutes: new Map(),
    _registeredSessions: new Map(),
    _registeredSockets: new Map(),
    _registeredStreams: new Map(),
    _registeredWebSockets: new Map(),
    _registeredWorkers: new Map(),
    _toolHandlersInitialized: false,
    _completionHandlerInitialized: false,
    setToolRequestHandlers: vi.fn(),
    setCompletionRequestHandler: vi.fn(),
    _messageHandler: vi.fn(),
    _transport: {},
    _connected: false,
    _completionHandler: vi.fn(),
    _toolHandlers: new Map(),
    _validateHandlersInitialized: vi.fn(),
    handlePromptCompletion: vi.fn(),
    handleResourceCompletion: vi.fn(),
    _resourceHandlersInitialized: false,
    setResourceRequestHandlers: vi.fn(),
    _resourceHandlers: new Map(),
    _validateSession: vi.fn(),
    _validateResource: vi.fn(),
    _resourceTemplateHandlers: new Map(),
  };
}
</file>

<file path="tests/testing/testUtils.ts">
import os from 'node:os';
import process from 'node:process';
import { type RepomixConfigMerged, defaultConfig } from '../../src/config/configSchema.js';

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends (infer U)[]
    ? DeepPartial<U>[]
    : T[P] extends readonly (infer U)[]
      ? readonly DeepPartial<U>[]
      : T[P] extends object
        ? DeepPartial<T[P]>
        : T[P];
};

export const createMockConfig = (config: DeepPartial<RepomixConfigMerged> = {}): RepomixConfigMerged => {
  return {
    cwd: process.cwd(),
    output: {
      ...defaultConfig.output,
      ...config.output,
      git: {
        ...defaultConfig.output.git,
        ...config.output?.git,
      },
    },
    ignore: {
      ...defaultConfig.ignore,
      ...config.ignore,
      customPatterns: [...(defaultConfig.ignore.customPatterns || []), ...(config.ignore?.customPatterns || [])],
    },
    include: [...(defaultConfig.include || []), ...(config.include || [])],
    security: {
      ...defaultConfig.security,
      ...config.security,
    },
    tokenCount: {
      ...defaultConfig.tokenCount,
      ...config.tokenCount,
    },
  };
};

export const isWindows = os.platform() === 'win32';
export const isMac = os.platform() === 'darwin';
export const isLinux = os.platform() === 'linux';
</file>

<file path="website/client/.vitepress/config/configDe.ts">
import { type DefaultTheme, defineConfig } from 'vitepress';

export const configDe = defineConfig({
  lang: 'de',
  description: 'Paketieren Sie Ihre Codebasis in KI-freundliche Formate',
  themeConfig: {
    nav: [
      { text: 'Anleitung', link: '/de/guide/' },
      { text: 'Discord beitreten', link: 'https://discord.gg/wNYzTwZFku' },
    ],
    sidebar: {
      '/de/guide/': [
        {
          text: 'Anleitung',
          items: [
            { text: 'Erste Schritte', link: '/de/guide/' },
            { text: 'Installation', link: '/de/guide/installation' },
            { text: 'Grundlegende Verwendung', link: '/de/guide/usage' },
            { text: 'Prompt-Beispiele', link: '/de/guide/prompt-examples' },
            { text: 'Ausgabeformate', link: '/de/guide/output' },
            { text: 'Kommandozeilenoptionen', link: '/de/guide/command-line-options' },
            { text: 'Remote-Repository-Verarbeitung', link: '/de/guide/remote-repository-processing' },
            { text: 'Konfiguration', link: '/de/guide/configuration' },
            { text: 'Benutzerdefinierte Anweisungen', link: '/de/guide/custom-instructions' },
            { text: 'Kommentare entfernen', link: '/de/guide/comment-removal' },
            { text: 'Code-Komprimierung', link: '/de/guide/code-compress' },
            { text: 'Sicherheit', link: '/de/guide/security' },
            { text: 'MCP-Server', link: '/de/guide/mcp-server' },
            {
              text: 'Tipps & Tricks',
              items: [{ text: 'Best Practices', link: '/de/guide/tips/best-practices' }],
            },
            {
              text: 'Entwicklung',
              items: [
                { text: 'Mitwirken', link: '/de/guide/development/' },
                { text: 'Einrichtung', link: '/de/guide/development/setup' },
              ],
            },
          ],
        },
      ],
    },
  },
});

export const configDeSearch: DefaultTheme.LocalSearchOptions['locales'] = {
  de: {
    translations: {
      button: {
        buttonText: 'Suchen',
        buttonAriaLabel: 'Suchen',
      },
      modal: {
        noResultsText: 'Keine Ergebnisse gefunden',
        resetButtonTitle: 'Suche zurücksetzen',
        backButtonTitle: 'Zurück',
        displayDetails: 'Details anzeigen',
        footer: {
          selectText: 'Auswählen',
          navigateText: 'Navigieren',
          closeText: 'Schließen',
        },
      },
    },
  },
};
</file>

<file path="website/client/.vitepress/config/configEnUs.ts">
import { defineConfig } from 'vitepress';

export const configEnUs = defineConfig({
  lang: 'en-US',
  description: 'Pack your codebase into AI-friendly formats',
  themeConfig: {
    nav: [
      // guide
      { text: 'Guide', link: '/guide/' },
      { text: 'Join Discord', link: 'https://discord.gg/wNYzTwZFku' },
    ],
    sidebar: {
      '/guide/': [
        {
          text: 'Guide',
          items: [
            { text: 'Getting Started', link: '/guide/' },
            { text: 'Installation', link: '/guide/installation' },
            { text: 'Basic Usage', link: '/guide/usage' },
            { text: 'Prompt Examples', link: '/guide/prompt-examples' },
            { text: 'Output Formats', link: '/guide/output' },
            { text: 'Command Line Options', link: '/guide/command-line-options' },
            { text: 'Remote Repository Processing', link: '/guide/remote-repository-processing' },
            { text: 'Configuration', link: '/guide/configuration' },
            { text: 'Custom Instructions', link: '/guide/custom-instructions' },
            { text: 'Comment Removal', link: '/guide/comment-removal' },
            { text: 'Code Compression', link: '/guide/code-compress' },
            { text: 'Security', link: '/guide/security' },
            { text: 'MCP Server', link: '/guide/mcp-server' },
            {
              text: 'Tips & Tricks',
              items: [{ text: 'Best Practices', link: '/guide/tips/best-practices' }],
            },
            {
              text: 'Development',
              items: [
                { text: 'Contributing', link: '/guide/development/' },
                { text: 'Setup', link: '/guide/development/setup' },
              ],
            },
          ],
        },
      ],
    },
  },
});
</file>

<file path="website/client/.vitepress/config/configEs.ts">
import { type DefaultTheme, defineConfig } from 'vitepress';

export const configEs = defineConfig({
  lang: 'es',
  description: 'Empaqueta tu código en formatos amigables para IA',
  themeConfig: {
    nav: [
      { text: 'Guía', link: '/es/guide/' },
      { text: 'Unirse a Discord', link: 'https://discord.gg/wNYzTwZFku' },
    ],
    sidebar: {
      '/es/guide/': [
        {
          text: 'Guía',
          items: [
            { text: 'Comenzar', link: '/es/guide/' },
            { text: 'Instalación', link: '/es/guide/installation' },
            { text: 'Uso Básico', link: '/es/guide/usage' },
            { text: 'Ejemplos de Prompts', link: '/es/guide/prompt-examples' },
            { text: 'Formatos de Salida', link: '/es/guide/output' },
            { text: 'Opciones de Línea de Comandos', link: '/es/guide/command-line-options' },
            { text: 'Procesamiento de Repositorios Remotos', link: '/es/guide/remote-repository-processing' },
            { text: 'Configuración', link: '/es/guide/configuration' },
            { text: 'Instrucciones Personalizadas', link: '/es/guide/custom-instructions' },
            { text: 'Eliminación de Comentarios', link: '/es/guide/comment-removal' },
            { text: 'Compresión de Código', link: '/es/guide/code-compress' },
            { text: 'Seguridad', link: '/es/guide/security' },
            { text: 'Servidor MCP', link: '/es/guide/mcp-server' },
            {
              text: 'Consejos y Trucos',
              items: [{ text: 'Mejores Prácticas', link: '/es/guide/tips/best-practices' }],
            },
            {
              text: 'Desarrollo',
              items: [
                { text: 'Contribuir', link: '/es/guide/development/' },
                { text: 'Configuración', link: '/es/guide/development/setup' },
              ],
            },
          ],
        },
      ],
    },
  },
});

export const configEsSearch: DefaultTheme.LocalSearchOptions['locales'] = {
  es: {
    translations: {
      button: {
        buttonText: 'Buscar',
        buttonAriaLabel: 'Buscar',
      },
      modal: {
        noResultsText: 'No se encontraron resultados',
        resetButtonTitle: 'Reiniciar búsqueda',
        backButtonTitle: 'Atrás',
        displayDetails: 'Mostrar detalles',
        footer: {
          selectText: 'Seleccionar',
          navigateText: 'Navegar',
          closeText: 'Cerrar',
        },
      },
    },
  },
};
</file>

<file path="website/client/.vitepress/config/configFr.ts">
import { type DefaultTheme, defineConfig } from 'vitepress';

export const configFr = defineConfig({
  lang: 'fr-FR',
  description: "Empaquetez votre code dans des formats adaptés à l'IA",
  themeConfig: {
    nav: [
      { text: 'Guide', link: '/fr/guide/' },
      { text: 'Rejoindre Discord', link: 'https://discord.gg/wNYzTwZFku' },
    ],
    sidebar: {
      '/fr/guide/': [
        {
          text: 'Guide',
          items: [
            { text: 'Pour commencer', link: '/fr/guide/' },
            { text: 'Installation', link: '/fr/guide/installation' },
            { text: 'Utilisation de base', link: '/fr/guide/usage' },
            { text: 'Exemples de prompts', link: '/fr/guide/prompt-examples' },
            { text: 'Formats de sortie', link: '/fr/guide/output' },
            { text: 'Options de ligne de commande', link: '/fr/guide/command-line-options' },
            { text: 'Traitement des dépôts distants', link: '/fr/guide/remote-repository-processing' },
            { text: 'Configuration', link: '/fr/guide/configuration' },
            { text: 'Instructions personnalisées', link: '/fr/guide/custom-instructions' },
            { text: 'Suppression des commentaires', link: '/fr/guide/comment-removal' },
            { text: 'Compression de code', link: '/fr/guide/code-compress' },
            { text: 'Sécurité', link: '/fr/guide/security' },
            { text: 'Serveur MCP', link: '/fr/guide/mcp-server' },
            {
              text: 'Astuces et conseils',
              items: [{ text: 'Meilleures pratiques', link: '/fr/guide/tips/best-practices' }],
            },
            {
              text: 'Développement',
              items: [
                { text: 'Contribuer', link: '/fr/guide/development/' },
                { text: 'Configuration', link: '/fr/guide/development/setup' },
              ],
            },
          ],
        },
      ],
    },
  },
});

export const configFrSearch: DefaultTheme.LocalSearchOptions['locales'] = {
  fr: {
    translations: {
      button: {
        buttonText: 'Rechercher',
        buttonAriaLabel: 'Rechercher',
      },
      modal: {
        noResultsText: 'Aucun résultat trouvé',
        resetButtonTitle: 'Réinitialiser la recherche',
        backButtonTitle: 'Retour',
        displayDetails: 'Afficher les détails',
        footer: {
          selectText: 'Sélectionner',
          navigateText: 'Naviguer',
          closeText: 'Fermer',
        },
      },
    },
  },
};
</file>

<file path="website/client/.vitepress/config/configJa.ts">
import { type DefaultTheme, defineConfig } from 'vitepress';

export const configJa = defineConfig({
  lang: 'ja',
  description: 'コードベースをAIフレンドリーな形式にパッケージング',
  themeConfig: {
    nav: [
      // guide
      { text: '使い方', link: '/ja/guide/' },
      { text: 'Discordに参加', link: 'https://discord.gg/wNYzTwZFku' },
    ],
    sidebar: {
      '/ja/guide/': [
        {
          text: '使い方',
          items: [
            { text: 'はじめに', link: '/ja/guide/' },
            { text: 'インストール', link: '/ja/guide/installation' },
            { text: '基本的な使い方', link: '/ja/guide/usage' },
            { text: 'プロンプト例', link: '/ja/guide/prompt-examples' },
            { text: '出力フォーマット', link: '/ja/guide/output' },
            { text: 'コマンドラインオプション', link: '/ja/guide/command-line-options' },
            { text: 'リモートリポジトリの処理', link: '/ja/guide/remote-repository-processing' },
            { text: '設定', link: '/ja/guide/configuration' },
            { text: 'カスタム指示', link: '/ja/guide/custom-instructions' },
            { text: 'コメントの削除', link: '/ja/guide/comment-removal' },
            { text: 'コード圧縮', link: '/ja/guide/code-compress' },
            { text: 'セキュリティ', link: '/ja/guide/security' },
            { text: 'MCPサーバー', link: '/ja/guide/mcp-server' },
            {
              text: 'ヒント＆テクニック',
              items: [{ text: 'ベストプラクティス', link: '/ja/guide/tips/best-practices' }],
            },
            {
              text: '開発',
              items: [
                { text: '開発への貢献', link: '/ja/guide/development/' },
                { text: '環境構築', link: '/ja/guide/development/setup' },
              ],
            },
          ],
        },
      ],
    },
  },
});

export const configJaSearch: DefaultTheme.LocalSearchOptions['locales'] = {
  ja: {
    translations: {
      button: {
        buttonText: '検索',
        buttonAriaLabel: '検索',
      },
      modal: {
        noResultsText: '検索結果がありません',
        resetButtonTitle: '検索をリセット',
        backButtonTitle: '前に戻る',
        displayDetails: '詳細を表示',
        footer: {
          selectText: '選択',
          navigateText: '移動',
          closeText: '閉じる',
        },
      },
    },
  },
};
</file>

<file path="website/client/.vitepress/config/configKo.ts">
import { type DefaultTheme, defineConfig } from 'vitepress';

export const configKo = defineConfig({
  lang: 'ko',
  description: '코드베이스를 AI 친화적인 형식으로 패키징',
  themeConfig: {
    nav: [
      { text: '가이드', link: '/ko/guide/' },
      { text: 'Discord 참여', link: 'https://discord.gg/wNYzTwZFku' },
    ],
    sidebar: {
      '/ko/guide/': [
        {
          text: '가이드',
          items: [
            { text: '시작하기', link: '/ko/guide/' },
            { text: '설치', link: '/ko/guide/installation' },
            { text: '기본 사용법', link: '/ko/guide/usage' },
            { text: '프롬프트 예제', link: '/ko/guide/prompt-examples' },
            { text: '출력 형식', link: '/ko/guide/output' },
            { text: '명령줄 옵션', link: '/ko/guide/command-line-options' },
            { text: '원격 저장소 처리', link: '/ko/guide/remote-repository-processing' },
            { text: '설정', link: '/ko/guide/configuration' },
            { text: '사용자 정의 지침', link: '/ko/guide/custom-instructions' },
            { text: '주석 제거', link: '/ko/guide/comment-removal' },
            { text: '코드 압축', link: '/ko/guide/code-compress' },
            { text: '보안', link: '/ko/guide/security' },
            { text: 'MCP 서버', link: '/ko/guide/mcp-server' },
            {
              text: '팁과 요령',
              items: [{ text: '모범 사례', link: '/ko/guide/tips/best-practices' }],
            },
            {
              text: '개발',
              items: [
                { text: '기여하기', link: '/ko/guide/development/' },
                { text: '환경 설정', link: '/ko/guide/development/setup' },
              ],
            },
          ],
        },
      ],
    },
  },
});

export const configKoSearch: DefaultTheme.LocalSearchOptions['locales'] = {
  ko: {
    translations: {
      button: {
        buttonText: '검색',
        buttonAriaLabel: '검색',
      },
      modal: {
        noResultsText: '검색 결과가 없습니다',
        resetButtonTitle: '검색 초기화',
        backButtonTitle: '뒤로',
        displayDetails: '상세 정보 표시',
        footer: {
          selectText: '선택',
          navigateText: '이동',
          closeText: '닫기',
        },
      },
    },
  },
};
</file>

<file path="website/client/.vitepress/config/configPtBr.ts">
import { type DefaultTheme, defineConfig } from 'vitepress';

export const configPtBr = defineConfig({
  lang: 'pt-BR',
  description: 'Empacote sua base de código em formatos amigáveis para IA',
  themeConfig: {
    nav: [
      { text: 'Guia', link: '/pt-br/guide/' },
      { text: 'Entrar no Discord', link: 'https://discord.gg/wNYzTwZFku' },
    ],
    sidebar: {
      '/pt-br/guide/': [
        {
          text: 'Guia',
          items: [
            { text: 'Começando', link: '/pt-br/guide/' },
            { text: 'Instalação', link: '/pt-br/guide/installation' },
            { text: 'Uso Básico', link: '/pt-br/guide/usage' },
            { text: 'Exemplos de Prompt', link: '/pt-br/guide/prompt-examples' },
            { text: 'Formatos de Saída', link: '/pt-br/guide/output' },
            { text: 'Opções de Linha de Comando', link: '/pt-br/guide/command-line-options' },
            { text: 'Processamento de Repositório Remoto', link: '/pt-br/guide/remote-repository-processing' },
            { text: 'Configuração', link: '/pt-br/guide/configuration' },
            { text: 'Instruções Personalizadas', link: '/pt-br/guide/custom-instructions' },
            { text: 'Remoção de Comentários', link: '/pt-br/guide/comment-removal' },
            { text: 'Compressão de Código', link: '/pt-br/guide/code-compress' },
            { text: 'Segurança', link: '/pt-br/guide/security' },
            { text: 'Servidor MCP', link: '/pt-br/guide/mcp-server' },
            {
              text: 'Dicas e Truques',
              items: [{ text: 'Melhores Práticas', link: '/pt-br/guide/tips/best-practices' }],
            },
            {
              text: 'Desenvolvimento',
              items: [
                { text: 'Contribuindo', link: '/pt-br/guide/development/' },
                { text: 'Configuração', link: '/pt-br/guide/development/setup' },
              ],
            },
          ],
        },
      ],
    },
  },
});

export const configPtBrSearch: DefaultTheme.LocalSearchOptions['locales'] = {
  'pt-BR': {
    translations: {
      button: {
        buttonText: 'Pesquisar',
        buttonAriaLabel: 'Pesquisar',
      },
      modal: {
        noResultsText: 'Nenhum resultado encontrado',
        resetButtonTitle: 'Limpar pesquisa',
        backButtonTitle: 'Voltar',
        displayDetails: 'Mostrar detalhes',
        footer: {
          selectText: 'Selecionar',
          navigateText: 'Navegar',
          closeText: 'Fechar',
        },
      },
    },
  },
};
</file>

<file path="website/client/.vitepress/config/configShard.ts">
import { visualizer } from 'rollup-plugin-visualizer';
import { type ManifestOptions, VitePWA } from 'vite-plugin-pwa';
import { defineConfig } from 'vitepress';
import { configDeSearch } from './configDe';
import { configEsSearch } from './configEs';
import { configJaSearch } from './configJa';
import { configKoSearch } from './configKo';
import { configPtBrSearch } from './configPtBr';
import { configZhCnSearch } from './configZhCn';

const googleAnalyticsTag = 'G-7PTT4PLC69';

// PWA Manifest Configuration
const manifest: Partial<ManifestOptions> = {
  name: 'Repomix',
  short_name: 'Repomix',
  description: 'Pack your codebase into AI-friendly formats',
  theme_color: '#f97316',
  background_color: '#ffffff',
  display: 'standalone',
  icons: [
    {
      src: '/images/pwa/repomix-192x192.png',
      sizes: '192x192',
      type: 'image/png',
    },
    {
      src: '/images/pwa/repomix-512x512.png',
      sizes: '512x512',
      type: 'image/png',
    },
    {
      src: '/images/pwa/repomix-512x512.png',
      sizes: '512x512',
      type: 'image/png',
      purpose: 'any maskable',
    },
  ],
};

export const configShard = defineConfig({
  title: 'Repomix',

  srcDir: 'src',

  rewrites: {
    // rewrite to `en` locale
    'en/:rest*': ':rest*',
  },

  lastUpdated: true,
  cleanUrls: true,
  metaChunk: true,

  sitemap: {
    hostname: 'https://repomix.com/',
  },

  // Shared configuration
  themeConfig: {
    logo: { src: '/images/repomix-logo.svg', width: 24, height: 24 },
    search: {
      provider: 'local',
      options: {
        locales: {
          ...configDeSearch,
          ...configEsSearch,
          ...configJaSearch,
          ...configKoSearch,
          ...configPtBrSearch,
          ...configZhCnSearch,
        },
      },
    },
    socialLinks: [
      { icon: 'x', link: 'https://x.com/repomix_ai' },
      { icon: 'discord', link: 'https://discord.gg/wNYzTwZFku' },
      { icon: 'github', link: 'https://github.com/yamadashy/repomix' },
    ],
    footer: {
      message: 'Released under the MIT License.',
      copyright: 'Copyright © 2024 Kazuki Yamada',
    },
    // Language selection
    langMenuLabel: 'Languages',
  },

  head: [
    // Favicon
    ['link', { rel: 'icon', href: '/images/repomix-logo.svg' }],

    // OGP
    ['meta', { property: 'og:type', content: 'website' }],
    ['meta', { property: 'og:title', content: 'Repomix' }],
    ['meta', { property: 'og:site_name', content: 'Repomix' }],
    ['meta', { property: 'og:image', content: 'https://repomix.com/images/og-image-large.png' }],
    ['meta', { property: 'og:url', content: 'https://repomix.com' }],
    ['meta', { property: 'og:description', content: 'Pack your codebase into AI-friendly formats' }],
    ['meta', { name: 'twitter:card', content: 'summary_large_image' }],
    ['meta', { property: 'twitter:domain', content: 'https://repomix.com' }],
    ['meta', { property: 'twitter:url', content: 'https://repomix.com' }],
    ['meta', { name: 'twitter:title', content: 'Repomix' }],
    ['meta', { name: 'twitter:description', content: 'Pack your codebase into AI-friendly formats' }],
    ['meta', { name: 'twitter:image', content: 'https://repomix.com/images/og-image-large.png' }],
    ['meta', { name: 'thumbnail', content: 'https://repomix.com/images/og-image-large.png' }],

    // PWA
    ['meta', { name: 'theme-color', content: '#f97316' }],
    ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
    ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }],
    ['meta', { name: 'apple-mobile-web-app-title', content: 'Repomix' }],
    ['link', { rel: 'apple-touch-icon', href: '/images/pwa/repomix-192x192.png' }],
    ['link', { rel: 'mask-icon', href: '/images/repomix-logo.svg', color: '#f97316' }],

    // Google Analytics
    [
      'script',
      {
        async: true,
        src: `https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsTag}`,
      },
    ],
    [
      'script',
      {},
      `window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
      gtag('config', '${googleAnalyticsTag}');`,
    ],
  ],

  vite: {
    build: {
      rollupOptions: {
        plugins: [visualizer()],
      },
    },
    plugins: [
      VitePWA({
        registerType: 'autoUpdate',
        manifest,
        workbox: {
          globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2}'],
          runtimeCaching: [
            {
              urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
              handler: 'CacheFirst',
              options: {
                cacheName: 'google-fonts-cache',
                expiration: {
                  maxEntries: 10,
                  maxAgeSeconds: 60 * 60 * 24 * 7, // 7 days
                },
                cacheableResponse: {
                  statuses: [0, 200],
                },
              },
            },
            {
              urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
              handler: 'CacheFirst',
              options: {
                cacheName: 'gstatic-fonts-cache',
                expiration: {
                  maxEntries: 10,
                  maxAgeSeconds: 60 * 60 * 24 * 7, // 7 days
                },
                cacheableResponse: {
                  statuses: [0, 200],
                },
              },
            },
          ],
        },
      }),
    ],
  },
});
</file>

<file path="website/client/.vitepress/config/configZhCn.ts">
import { type DefaultTheme, defineConfig } from 'vitepress';

export const configZhCn = defineConfig({
  lang: 'zh-CN',
  description: '将代码库打包成AI友好的格式',
  themeConfig: {
    nav: [
      { text: '指南', link: '/zh-cn/guide/' },
      { text: '加入 Discord', link: 'https://discord.gg/wNYzTwZFku' },
    ],
    sidebar: {
      '/zh-cn/guide/': [
        {
          text: '指南',
          items: [
            { text: '开始使用', link: '/zh-cn/guide/' },
            { text: '安装', link: '/zh-cn/guide/installation' },
            { text: '基本用法', link: '/zh-cn/guide/usage' },
            { text: '提示示例', link: '/zh-cn/guide/prompt-examples' },
            { text: '输出格式', link: '/zh-cn/guide/output' },
            { text: '命令行选项', link: '/zh-cn/guide/command-line-options' },
            { text: '远程仓库处理', link: '/zh-cn/guide/remote-repository-processing' },
            { text: '配置', link: '/zh-cn/guide/configuration' },
            { text: '自定义指令', link: '/zh-cn/guide/custom-instructions' },
            { text: '注释移除', link: '/zh-cn/guide/comment-removal' },
            { text: '代码压缩', link: '/zh-cn/guide/code-compress' },
            { text: '安全性', link: '/zh-cn/guide/security' },
            { text: 'MCP 服务器', link: '/zh-cn/guide/mcp-server' },
            {
              text: '技巧与窍门',
              items: [{ text: '最佳实践', link: '/zh-cn/guide/tips/best-practices' }],
            },
            {
              text: '开发',
              items: [
                { text: '贡献指南', link: '/zh-cn/guide/development/' },
                { text: '环境配置', link: '/zh-cn/guide/development/setup' },
              ],
            },
          ],
        },
      ],
    },
  },
});

export const configZhCnSearch: DefaultTheme.LocalSearchOptions['locales'] = {
  'zh-CN': {
    translations: {
      button: {
        buttonText: '搜索',
        buttonAriaLabel: '搜索',
      },
      modal: {
        noResultsText: '未找到相关结果',
        resetButtonTitle: '清除搜索',
        backButtonTitle: '返回',
        displayDetails: '显示详情',
        footer: {
          selectText: '选择',
          navigateText: '切换',
          closeText: '关闭',
        },
      },
    },
  },
};
</file>

<file path="website/client/.vitepress/config.ts">
import { defineConfig } from 'vitepress';
import { configDe } from './config/configDe';
import { configEnUs } from './config/configEnUs';
import { configEs } from './config/configEs';
import { configFr } from './config/configFr';
import { configJa } from './config/configJa';
import { configKo } from './config/configKo';
import { configPtBr } from './config/configPtBr';
import { configShard } from './config/configShard';
import { configZhCn } from './config/configZhCn';

export default defineConfig({
  ...configShard,
  locales: {
    root: { label: 'English', ...configEnUs },
    'zh-cn': { label: '简体中文', ...configZhCn },
    ja: { label: '日本語', ...configJa },
    es: { label: 'Español', ...configEs },
    'pt-br': { label: 'Português', ...configPtBr },
    ko: { label: '한국어', ...configKo },
    de: { label: 'Deutsch', ...configDe },
    fr: { label: 'Français', ...configFr },
  },
});
</file>

<file path="website/client/components/Home/TryItFileUpload.vue">
<script setup lang="ts">
import { AlertTriangle, FolderArchive } from 'lucide-vue-next';
import { ref } from 'vue';
import PackButton from './PackButton.vue';

const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB

const props = defineProps<{
  loading: boolean;
  showButton?: boolean;
}>();

const emit = defineEmits<{
  upload: [file: File];
}>();

const fileInput = ref<HTMLInputElement | null>(null);
const dragActive = ref(false);
const selectedFile = ref<File | null>(null);
const errorMessage = ref<string | null>(null);

function validateFile(file: File): boolean {
  errorMessage.value = null;

  if (file.type !== 'application/zip' && !file.name.endsWith('.zip')) {
    errorMessage.value = 'Please upload a ZIP file';
    return false;
  }

  if (file.size > MAX_FILE_SIZE) {
    const sizeMB = (file.size / (1024 * 1024)).toFixed(1);
    errorMessage.value = `File size (${sizeMB}MB) exceeds the 10MB limit`;
    return false;
  }

  return true;
}

function handleFileSelect(files: FileList | null) {
  if (!files || files.length === 0) return;

  const file = files[0];
  if (validateFile(file)) {
    selectedFile.value = file;
    emit('upload', file);
  } else {
    selectedFile.value = null;
  }
}

function triggerFileInput() {
  fileInput.value?.click();
}
</script>

<template>
  <div class="upload-wrapper">
    <div class="upload-container"
         :class="{ 'drag-active': dragActive, 'has-error': errorMessage }"
         @dragover.prevent="dragActive = true"
         @dragleave="dragActive = false"
         @drop.prevent="handleFileSelect($event.dataTransfer?.files || null)"
         @click="triggerFileInput"
    >
      <input
        ref="fileInput"
        type="file"
        accept=".zip"
        class="hidden-input"
        @change="(e) => handleFileSelect((e.target as HTMLInputElement).files)"
      />
      <div class="upload-content">
        <div class="upload-icon">
          <AlertTriangle v-if="errorMessage" class="icon-error" size="20" />
          <FolderArchive v-else class="icon-folder" size="20" />
        </div>
        <div class="upload-text">
          <p v-if="errorMessage" class="error-message">
            {{ errorMessage }}
          </p>
          <p v-else-if="selectedFile" class="selected-file">
            Selected: {{ selectedFile.name }}
            <button class="clear-button" @click.stop="selectedFile = null">×</button>
          </p>
          <template v-else>
            <p>Drop your ZIP file here or click to browse (max 10MB)</p>
          </template>
        </div>
      </div>
    </div>
  </div>
  <div v-if="showButton" class="pack-button-container">
    <PackButton
      :loading="loading"
      :isValid="!!selectedFile"
    />
  </div>
</template>

<style scoped>
.upload-wrapper {
  width: 100%;
}

.upload-container {
  border: 2px dashed var(--vp-c-border);
  border-radius: 8px;
  padding: 0 16px;
  cursor: pointer;
  transition: all 0.2s ease;
  height: 50px;
  display: flex;
  align-items: center;
  background: var(--vp-c-bg);
  user-select: none;
}

.upload-container:hover {
  border-color: var(--vp-c-brand-1);
  background-color: var(--vp-c-bg-soft);
}

.drag-active {
  border-color: var(--vp-c-brand-1);
  background-color: var(--vp-c-bg-soft);
}

.has-error {
  border-color: var(--vp-c-danger-1);
}

.hidden-input {
  display: none;
}

.upload-content {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 12px;
  width: 100%;
  pointer-events: none; /* Allow clicks to pass through to container */
}

.upload-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.icon-folder {
  color: var(--vp-c-text-1);
}

.icon-error {
  color: var(--vp-c-danger-1);
}

.upload-text {
  flex: 1;
  font-size: 14px;
}

.upload-text p {
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.error-message {
  color: var(--vp-c-danger-1);
}

.selected-file {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}

.clear-button {
  background: none;
  border: none;
  color: var(--vp-c-text-2);
  cursor: pointer;
  font-size: 1.2em;
  padding: 0 4px;
  line-height: 1;
  flex-shrink: 0;
  pointer-events: auto; /* Re-enable pointer events for button */
}

.clear-button:hover {
  color: var(--vp-c-text-1);
}

.pack-button-container {
  width: 100%;
  display: flex;
  justify-content: center;
  margin-top: 16px;
}

@media (max-width: 640px) {
  .upload-text p {
    font-size: 13px;
  }
}
</style>
</file>

<file path="website/client/components/Home/TryItResultContent.vue">
<script setup lang="ts">
import ace, { type Ace } from 'ace-builds';
import themeTomorrowUrl from 'ace-builds/src-noconflict/theme-tomorrow?url';
import themeTomorrowNightUrl from 'ace-builds/src-noconflict/theme-tomorrow_night?url';
import { BarChart2, Copy, Download, GitFork, HeartHandshake, PackageSearch } from 'lucide-vue-next';
import { useData } from 'vitepress';
import { computed, ref, watch } from 'vue';
import { VAceEditor } from 'vue3-ace-editor';
import type { PackResult } from '../api/client';
import { copyToClipboard, downloadResult, formatTimestamp, getEditorOptions } from '../utils/resultViewer';

ace.config.setModuleUrl('ace/theme/tomorrow', themeTomorrowUrl);
ace.config.setModuleUrl('ace/theme/tomorrow_night', themeTomorrowNightUrl);

const lightTheme = 'tomorrow';
const darkTheme = 'tomorrow_night';

const props = defineProps<{
  result: PackResult;
}>();

const copied = ref(false);
const { isDark } = useData();
const editorInstance = ref<Ace.Editor | null>(null);

const editorOptions = computed(() => ({
  ...getEditorOptions(),
  theme: isDark.value ? `ace/theme/${darkTheme}` : `ace/theme/${lightTheme}`,
}));

watch(isDark, (newIsDark) => {
  if (editorInstance.value) {
    editorInstance.value.setTheme(newIsDark ? `ace/theme/${darkTheme}` : `ace/theme/${lightTheme}`);
  }
});

const formattedTimestamp = computed(() => {
  return formatTimestamp(props.result.metadata.timestamp);
});

const handleCopy = async (event: Event) => {
  event.preventDefault();
  event.stopPropagation();

  const success = await copyToClipboard(props.result.content, props.result.format);
  if (success) {
    copied.value = true;
    setTimeout(() => {
      copied.value = false;
    }, 2000);
  }
};

const handleDownload = (event: Event) => {
  event.preventDefault();
  event.stopPropagation();
  downloadResult(props.result.content, props.result.format, props.result);
};

const handleEditorMount = (editor: Ace.Editor) => {
  editorInstance.value = editor;
};
</script>

<template>
  <div class="content-wrapper">
    <div class="metadata-panel">
      <div class="metadata-section">
        <h3><GitFork :size="16" class="section-icon" /> Repository Info</h3>
        <dl>
          <dt>Repository</dt>
          <dd>{{ result.metadata.repository }}</dd>
          <dt>Generated At</dt>
          <dd>{{ formattedTimestamp }}</dd>
          <dt>Format</dt>
          <dd>{{ result.format }}</dd>
        </dl>
      </div>

      <div class="metadata-section">
        <h3><PackageSearch :size="16" class="section-icon" /> Pack Summary</h3>
        <dl v-if="result.metadata.summary">
          <dt>Total Files</dt>
          <dd>{{ result.metadata.summary.totalFiles.toLocaleString() }} <span class="unit">files</span></dd>
          <dt>Total Size</dt>
          <dd>{{ result.metadata.summary.totalCharacters.toLocaleString() }} <span class="unit">chars</span></dd>
          <dt>Total Tokens</dt>
          <dd>{{ result.metadata.summary.totalTokens.toLocaleString() }} <span class="unit">tokens</span></dd>
        </dl>
      </div>

      <div class="metadata-section" v-if="result.metadata.topFiles">
        <h3><BarChart2 :size="16" class="section-icon" /> Top {{ result.metadata.topFiles.length }} Files</h3>
        <ol class="top-files-list">
          <li v-for="file in result.metadata.topFiles" :key="file.path">
            <div class="file-path">{{ file.path }}</div>
            <div class="file-stats">
              {{ file.charCount.toLocaleString() }} <span class="unit">chars</span> <span class="separator-unit">|</span> {{ file.tokenCount.toLocaleString() }} <span class="unit">tokens</span> <span class="separator-unit">|</span> {{ ((file.tokenCount / result.metadata.summary.totalTokens) * 100).toFixed(1) }}<span class="unit">%</span>
            </div>
          </li>
        </ol>
      </div>
    </div>

    <div class="output-panel">
      <div class="output-actions">
        <button
          class="action-button"
          @click="handleCopy"
          :class="{ copied }"
        >
          <Copy :size="16" />
          {{ copied ? 'Copied!' : 'Copy' }}
        </button>
        <button
          class="action-button"
          @click="handleDownload"
        >
          <Download :size="16" />
          Download
        </button>
      </div>
      <div class="editor-container">
        <VAceEditor
          v-model:value="result.content"
          :lang="'text'"
          :style="{ height: '100%', width: '100%' }"
          :options="editorOptions"
          @mount="handleEditorMount"
        />
      </div>
    </div>
    <div class="support-notice">
      <div class="support-message">
        <a href="https://github.com/sponsors/yamadashy" target="_blank" rel="noopener noreferrer" class="support-link">
          <HeartHandshake :size="14" class="support-icon" />
          This tool is independently developed. Your support helps maintain and improve it. Thank you!
        </a>
      </div>
    </div>
  </div>
</template>

<style scoped>
.content-wrapper {
  display: grid;
  grid-template-columns: 300px 1fr;
  grid-template-rows: 500px auto;
}

.metadata-panel {
  padding: 16px;
  border-right: 1px solid var(--vp-c-border);
  background: var(--vp-c-bg-soft);
  overflow-y: auto;
}

.metadata-section {
  margin-bottom: 24px;
}

.metadata-section:last-child {
  margin-bottom: 0;
}

.metadata-section h3 {
  font-size: 14px;
  font-weight: 600;
  margin: 0 0 12px;
  color: var(--vp-c-text-1);
  display: flex;
  align-items: center;
  gap: 6px;
}

.section-icon {
  color: var(--vp-c-text-2);
}

dl {
  margin: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 8px;
  font-size: 13px;
}

dt {
  color: var(--vp-c-text-2);
  font-weight: 500;
}

dd {
  margin: 0;
  color: var(--vp-c-text-1);
  text-transform: lowercase;
}

.unit {
  color: var(--vp-c-text-2);
  margin-left: 0.3em;
}

.separator-unit {
  color: var(--vp-c-text-3);
  margin: 0 0.5em;
}

.top-files-list {
  margin: 0;
  padding: 0 0 0 0;
  font-size: 13px;
}

.top-files-list li {
  margin-bottom: 8px;
  border-left: 2px solid var(--vp-c-divider);
  padding-left: 8px;
}

.file-path {
  color: var(--vp-c-text-1);
  margin-bottom: 2px;
  word-break: break-all;
}

.file-stats {
  font-size: 12px;
  color: var(--vp-c-text-1);
  display: flex;
  align-items: center;
}

.output-panel {
  display: flex;
  flex-direction: column;
  height: 100%;
  max-height: 500px;
  background: var(--vp-c-bg);
  overflow: hidden;
}

.output-actions {
  display: flex;
  gap: 8px;
  padding: 12px;
  background: var(--vp-c-bg);
  border-bottom: 1px solid var(--vp-c-border);
  flex-shrink: 0;
}

.action-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--vp-c-border);
  border-radius: 6px;
  background: var(--vp-c-bg-soft);
  color: var(--vp-c-text-1);
  cursor: pointer;
  transition: all 0.2s ease;
}

.action-button:hover {
  border-color: var(--vp-c-brand-1);
}

.action-button.copied {
  background: var(--vp-c-brand-1);
  color: white;
  border-color: var(--vp-c-brand-1);
}

.editor-container {
  height: 100%;
  width: 100%;
  font-family: var(--vp-font-family-mono);
}

.support-notice {
  grid-column: 1 / -1;
  padding: 8px;
  background: var(--vp-c-bg-soft);
  border-top: 1px solid var(--vp-c-border);
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  min-height: 45px;
}

.support-message {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--vp-c-text-3);
  font-size: 12px;
  width: 100%;
}

.support-icon {
  flex-shrink: 0;
  transition: color 0.3s ease;
}

.support-link {
  text-decoration: none;
  font-weight: normal;
  transition: color 0.3s ease;
  display: flex;
  align-items: center;
  gap: 6px;
}

.support-link:hover {
  color: var(--vp-c-brand-1);
}

.support-link:hover .support-icon {
  color: var(--vp-c-brand-1);
}

@media (max-width: 768px) {
  .content-wrapper {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(500px, auto) auto;
    height: auto;
  }

  .metadata-panel {
    border-right: none;
    border-bottom: 1px solid var(--vp-c-border);
  }

  .output-panel {
    height: 500px;
  }

  .support-notice {
    padding: 16px;
  }

  .support-message {
    max-width: 100%;
  }
}
</style>
</file>

<file path="website/client/src/de/guide/code-compress.md">
# Code-Komprimierung

Die Code-Komprimierung ist eine leistungsstarke Funktion, die wichtige Code-Strukturen intelligent extrahiert und dabei Implementierungsdetails entfernt. Dies ist besonders nützlich, um die Token-Anzahl zu reduzieren und gleichzeitig wichtige strukturelle Informationen über Ihre Codebasis beizubehalten.

> [!NOTE]
> Dies ist eine experimentelle Funktion, die wir basierend auf Benutzerfeedback und praktischer Nutzung aktiv verbessern werden.

## Grundlegende Verwendung

Aktivieren Sie die Code-Komprimierung mit der Option `--compress`:

```bash
repomix --compress
```

Sie können sie auch mit Remote-Repositories verwenden:

```bash
repomix --remote user/repo --compress
```

## Funktionsweise

Der Komprimierungsalgorithmus verarbeitet Code mithilfe von Tree-Sitter-Parsing, um wesentliche strukturelle Elemente zu extrahieren und zu bewahren, während Implementierungsdetails entfernt werden.

Die Komprimierung bewahrt:
- Funktions- und Methodensignaturen
- Schnittstellen- und Typdefinitionen
- Klassenstrukturen und Eigenschaften
- Wichtige strukturelle Elemente

Während sie entfernt:
- Funktions- und Methodenimplementierungen
- Details zu Schleifen- und Bedingungslogik
- Interne Variablendeklarationen
- Implementierungsspezifischen Code

### Beispiel

Ursprünglicher TypeScript-Code:

```typescript
import { ShoppingItem } from './shopping-item';

/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
  let total = 0;
  for (const item of items) {
    total += item.price * item.quantity;
  }
  return total;
}

// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

Nach der Komprimierung:

```typescript
import { ShoppingItem } from './shopping-item';
⋮----
/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
⋮----
// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

## Konfiguration

Sie können die Komprimierung in Ihrer Konfigurationsdatei aktivieren:

```json
{
  "output": {
    "compress": true
  }
}
```

## Anwendungsfälle

Die Code-Komprimierung ist besonders nützlich wenn:
- Code-Struktur und Architektur analysiert werden
- Token-Anzahl für LLM-Verarbeitung reduziert werden soll
- Hochrangige Dokumentation erstellt wird
- Code-Muster und Signaturen verstanden werden sollen
- API- und Schnittstellendesigns geteilt werden

## Verwandte Optionen

Sie können die Komprimierung mit anderen Optionen kombinieren:
- `--remove-comments`: Code-Kommentare entfernen
- `--remove-empty-lines`: Leere Zeilen entfernen
- `--output-show-line-numbers`: Zeilennummern zur Ausgabe hinzufügen
</file>

<file path="website/client/src/de/guide/index.md">
# Erste Schritte mit Repomix

Repomix ist ein Tool, das Ihr gesamtes Repository in eine einzige, KI-freundliche Datei verpackt. Es wurde entwickelt, um Ihren Codebase an große Sprachmodelle (LLMs) wie ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok und mehr zu übergeben.

## Schnellstart

Führen Sie diesen Befehl in Ihrem Projektverzeichnis aus:

```bash
npx repomix
```

Das war's! Sie finden eine `repomix-output.txt` Datei, die Ihr gesamtes Repository in einem KI-freundlichen Format enthält.

Sie können diese Datei dann mit einem Prompt wie diesem an einen KI-Assistenten senden:

```
Diese Datei enthält alle Dateien im Repository in einer Datei zusammengefasst.
Ich möchte den Code refaktorieren, bitte überprüfen Sie ihn zuerst.
```

Die KI wird Ihren gesamten Codebase analysieren und umfassende Einblicke geben:

![Repomix File Usage 1](/images/docs/repomix-file-usage-1.png)

Bei der Diskussion spezifischer Änderungen kann die KI bei der Code-Generierung helfen. Mit Funktionen wie Claudes Artifacts können Sie sogar mehrere voneinander abhängige Dateien erhalten:

![Repomix File Usage 2](/images/docs/repomix-file-usage-2.png)

Viel Spaß beim Programmieren! 🚀

## Kernfunktionen

- **KI-optimierte Ausgabe**: Formatiert Ihren Codebase für einfache KI-Verarbeitung
- **Token-Zählung**: Verfolgt Token-Nutzung für LLM-Kontextgrenzen
- **Git-bewusst**: Berücksichtigt Ihre `.gitignore`-Dateien und `.git/info/exclude`-Dateien
- **Sicherheitsorientiert**: Erkennt sensible Informationen
- **Mehrere Ausgabeformate**: Wählen Sie zwischen Klartext, XML oder Markdown

## Was kommt als Nächstes?

- [Installationsanleitung](installation.md): Verschiedene Möglichkeiten, Repomix zu installieren
- [Verwendungsleitfaden](usage.md): Lernen Sie grundlegende und erweiterte Funktionen kennen
- [Konfiguration](configuration.md): Passen Sie Repomix an Ihre Bedürfnisse an
- [Sicherheitsfunktionen](security.md): Erfahren Sie mehr über Sicherheitsprüfungen

## Community

Treten Sie unserer [Discord-Community](https://discord.gg/wNYzTwZFku) bei für:
- Hilfe mit Repomix
- Teilen Sie Ihre Erfahrungen
- Vorschlagen neuer Funktionen
- Verbindung mit anderen Benutzern

## Unterstützung

Haben Sie einen Fehler gefunden oder brauchen Sie Hilfe?
- [Öffnen Sie ein Issue auf GitHub](https://github.com/yamadashy/repomix/issues)
- Treten Sie unserem Discord-Server bei
- Lesen Sie die [Dokumentation](https://repomix.com)
</file>

<file path="website/client/src/de/guide/output.md">
# Ausgabeformate

Repomix unterstützt drei Ausgabeformate:
- XML (Standard)
- Markdown
- Klartext

## XML-Format

```bash
repomix --style xml
```

Das XML-Format ist für die KI-Verarbeitung optimiert:

::: tip Warum XML?
XML-Tags helfen KI-Modellen wie Claude, Inhalte genauer zu analysieren. Die [Claude-Dokumentation](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags) empfiehlt die Verwendung von XML-Tags für strukturierte Prompts.
:::

```xml
Diese Datei ist eine zusammengeführte Darstellung der gesamten Codebasis...

<file_summary>
(Metadaten und KI-Anweisungen)
</file_summary>

<directory_structure>
src/
  index.ts
  utils/
    helper.ts
</directory_structure>

<files>
<file path="src/index.ts">
// Dateiinhalt hier
</file>
</files>

## Markdown-Format

```bash
repomix --style markdown
```

Markdown bietet lesbare Formatierung:

```markdown
Diese Datei ist eine zusammengeführte Darstellung der gesamten Codebasis...

# Dateizusammenfassung
(Metadaten und KI-Anweisungen)

# Verzeichnisstruktur
```text
src/
  index.ts
  utils/
    helper.ts
```

# Dateien

## Datei: src/index.ts
```typescript
// Dateiinhalt hier
``` 

## Klartext-Format

```bash
repomix --style plain
```

Ausgabestruktur:
```text
Diese Datei ist eine zusammengeführte Darstellung der gesamten Codebasis...

================
Dateizusammenfassung
================
(Metadaten und KI-Anweisungen)

================
Verzeichnisstruktur
================
src/
  index.ts
  utils/
    helper.ts

================
Dateien
================

================
Datei: src/index.ts
================
// Dateiinhalt hier
```
</file>

<file path="website/client/src/de/guide/usage.md">
# Grundlegende Verwendung

## Schnellstart

Packen Sie Ihr gesamtes Repository:
```bash
repomix
```

## Häufige Anwendungsfälle

### Bestimmte Verzeichnisse packen
```bash
repomix path/to/directory
```

### Bestimmte Dateien einschließen
Verwenden Sie [Glob-Muster](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax):
```bash
repomix --include "src/**/*.ts,**/*.md"
```

### Dateien ausschließen
```bash
repomix --ignore "**/*.log,tmp/"
```

### Remote-Repositories
```bash
# Mit GitHub-URL
repomix --remote https://github.com/user/repo

# Mit Kurzform
repomix --remote user/repo

# Bestimmter Branch/Tag/Commit
repomix --remote user/repo --remote-branch main
repomix --remote user/repo --remote-branch 935b695
```

## Ausgabeformate

### XML (Standard)
```bash
repomix --style xml
```

### Markdown
```bash
repomix --style markdown
```

### Klartext
```bash
repomix --style plain
```

## Zusätzliche Optionen

### Kommentare entfernen
```bash
repomix --remove-comments
```

### Zeilennummern anzeigen
```bash
repomix --output-show-line-numbers
```

### In die Zwischenablage kopieren
```bash
repomix --copy
```

### Sicherheitsprüfung deaktivieren
```bash
repomix --no-security-check
```

## Konfiguration

Konfigurationsdatei initialisieren:
```bash
repomix --init
```

Siehe [Konfigurationsleitfaden](/de/guide/configuration) für detaillierte Optionen.
</file>

<file path="website/client/src/en/guide/code-compress.md">
# Code Compression

Code compression is a powerful feature that intelligently extracts essential code structures while removing implementation details. This is particularly useful for reducing token count while maintaining important structural information about your codebase.

> [!NOTE]  
> This is an experimental feature that we'll be actively improving based on user feedback and real-world usage

## Basic Usage

Enable code compression using the `--compress` flag:

```bash
repomix --compress
```

You can also use it with remote repositories:

```bash
repomix --remote user/repo --compress
```

## How It Works

The compression algorithm processes code using tree-sitter parsing to extract and preserve essential structural elements while removing implementation details.

The compression preserves:
- Function and method signatures
- Interface and type definitions
- Class structures and properties
- Important structural elements

While removing:
- Function and method implementations
- Loop and conditional logic details
- Internal variable declarations
- Implementation-specific code

### Example

Original TypeScript code:

```typescript
import { ShoppingItem } from './shopping-item';

/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
  let total = 0;
  for (const item of items) {
    total += item.price * item.quantity;
  }
  return total;
}

// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

After compression:

```typescript
import { ShoppingItem } from './shopping-item';
⋮----
/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
⋮----
// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

## Configuration

You can enable compression in your configuration file:

```json
{
  "output": {
    "compress": true
  }
}
```

## Use Cases

Code compression is particularly useful when:
- Analyzing code structure and architecture
- Reducing token count for LLM processing
- Creating high-level documentation
- Understanding code patterns and signatures
- Sharing API and interface designs

## Related Options

You can combine compression with other options:
- `--remove-comments`: Remove code comments
- `--remove-empty-lines`: Remove empty lines
- `--output-show-line-numbers`: Add line numbers to output
</file>

<file path="website/client/src/en/guide/index.md">
# Getting Started with Repomix

Repomix is a tool that packs your entire repository into a single, AI-friendly file. It's designed to help you feed your codebase to Large Language Models (LLMs) like ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok, and more.

## Quick Start

Run this command in your project directory:

```bash
npx repomix
```

That's it! You'll find a `repomix-output.txt` file containing your entire repository in an AI-friendly format.

You can then send this file to an AI assistant with a prompt like:

```
This file contains all the files in the repository combined into one.
I want to refactor the code, so please review it first.
```

The AI will analyze your entire codebase and provide comprehensive insights:

![Repomix File Usage 1](/images/docs/repomix-file-usage-1.png)

When discussing specific changes, the AI can help generate code. With features like Claude's Artifacts, you can even receive multiple interdependent files:

![Repomix File Usage 2](/images/docs/repomix-file-usage-2.png)

Happy coding! 🚀

## Core Features

- **AI-Optimized Output**: Formats your codebase for easy AI processing
- **Token Counting**: Tracks token usage for LLM context limits
- **Git-Aware**: Respects your `.gitignore` and `.git/info/exclude` files
- **Security-Focused**: Detects sensitive information
- **Multiple Output Formats**: Choose between plain text, XML, or Markdown

## What's Next?

- [Installation Guide](installation.md): Different ways to install Repomix
- [Usage Guide](usage.md): Learn about basic and advanced features
- [Configuration](configuration.md): Customize Repomix for your needs
- [Security Features](security.md): Learn about security checks

## Community

Join our [Discord community](https://discord.gg/wNYzTwZFku) for:
- Getting help with Repomix
- Sharing your experiences
- Suggesting new features
- Connecting with other users

## Support

Found a bug or need help?
- [Open an issue on GitHub](https://github.com/yamadashy/repomix/issues)
- Join our Discord server
- Check the [documentation](https://repomix.com)
</file>

<file path="website/client/src/en/guide/output.md">
# Output Formats

Repomix supports three output formats:
- XML (default)
- Markdown
- Plain Text 

## XML Format

```bash
repomix --style xml
```

XML format is optimized for AI processing:

```xml
This file is a merged representation of the entire codebase...

<file_summary>
(Metadata and AI instructions)
</file_summary>

<directory_structure>
src/
  index.ts
  utils/
    helper.ts
</directory_structure>

<files>
<file path="src/index.ts">
// File contents here
</file>
</files>
```

::: tip Why XML?
XML tags help AI models like Claude parse content more accurately. [Claude Documentation](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags) recommends using XML tags for structured prompts.
:::

## Markdown Format

```bash
repomix --style markdown
```

Markdown provides readable formatting:

```markdown
This file is a merged representation of the entire codebase...

# File Summary
(Metadata and AI instructions)

# Directory Structure
```
src/
index.ts
utils/
helper.ts
```

# Files

## File: src/index.ts
```typescript
// File contents here
```
```

## Usage with AI Models

Each format works well with AI models, but consider:
- Use XML for Claude (best parsing accuracy)
- Use Markdown for general readability
- Use Plain Text for simplicity and universal compatibility

## Customization

Set default format in `repomix.config.json`:
```json
{
  "output": {
    "style": "xml",
    "filePath": "output.xml"
  }
}
```

## Plain Text Format

```bash
repomix --style plain
```

Output structure:
```text
This file is a merged representation of the entire codebase...

================
File Summary
================
(Metadata and AI instructions)

================
Directory Structure
================
src/
  index.ts
  utils/
    helper.ts

================
Files
================

================
File: src/index.ts
================
// File contents here
```
</file>

<file path="website/client/src/es/guide/code-compress.md">
# Compresión de Código

La compresión de código es una función poderosa que extrae de manera inteligente las estructuras esenciales del código mientras elimina los detalles de implementación. Esto es particularmente útil para reducir el conteo de tokens mientras se mantiene la información estructural importante de tu base de código.

> [!NOTE]
> Esta es una función experimental que mejoraremos activamente según los comentarios de los usuarios y el uso en el mundo real.

## Uso Básico

Habilita la compresión de código usando la bandera `--compress`:

```bash
repomix --compress
```

También puedes usarlo con repositorios remotos:

```bash
repomix --remote user/repo --compress
```

## Cómo Funciona

El algoritmo de compresión procesa el código utilizando el análisis de Tree-sitter para extraer y preservar elementos estructurales esenciales mientras elimina los detalles de implementación.

La compresión preserva:
- Firmas de funciones y métodos
- Definiciones de interfaces y tipos
- Estructuras de clases y propiedades
- Elementos estructurales importantes

Mientras elimina:
- Implementaciones de funciones y métodos
- Detalles de lógica de bucles y condicionales
- Declaraciones de variables internas
- Código específico de implementación

### Ejemplo

Código TypeScript original:

```typescript
import { ShoppingItem } from './shopping-item';

/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
  let total = 0;
  for (const item of items) {
    total += item.price * item.quantity;
  }
  return total;
}

// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

Después de la compresión:

```typescript
import { ShoppingItem } from './shopping-item';
⋮----
/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
⋮----
// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

## Configuración

Puedes habilitar la compresión en tu archivo de configuración:

```json
{
  "output": {
    "compress": true
  }
}
```

## Casos de Uso

La compresión de código es particularmente útil cuando:
- Analizas la estructura y arquitectura del código
- Reduces el conteo de tokens para procesamiento con LLM
- Creas documentación de alto nivel
- Comprendes patrones y firmas de código
- Compartes diseños de API e interfaces

## Opciones Relacionadas

Puedes combinar la compresión con otras opciones:
- `--remove-comments`: Eliminar comentarios del código
- `--remove-empty-lines`: Eliminar líneas vacías
- `--output-show-line-numbers`: Agregar números de línea a la salida
</file>

<file path="website/client/src/es/guide/index.md">
# Primeros pasos con Repomix

Repomix es una herramienta que empaqueta todo tu repositorio en un solo archivo amigable para la IA. Está diseñado para ayudarte a alimentar tu código a modelos de lenguaje grandes (LLMs) como ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok y más.

## Inicio rápido

Ejecuta este comando en el directorio de tu proyecto:

```bash
npx repomix
```

¡Eso es todo! Encontrarás un archivo `repomix-output.txt` que contiene todo tu repositorio en un formato amigable para la IA.

Luego puedes enviar este archivo a un asistente de IA con un prompt como:

```
Este archivo contiene todos los archivos del repositorio combinados en uno.
Quiero refactorizar el código, así que por favor revísalo primero.
```

La IA analizará todo tu código y proporcionará información completa:

![Repomix File Usage 1](/images/docs/repomix-file-usage-1.png)

Al discutir cambios específicos, la IA puede ayudar a generar código. Con funciones como los Artefactos de Claude, incluso puedes recibir múltiples archivos interdependientes:

![Repomix File Usage 2](/images/docs/repomix-file-usage-2.png)

¡Feliz programación! 🚀

## Características principales

- **Salida optimizada para IA**: Formatea tu código para un fácil procesamiento por parte de la IA
- **Conteo de tokens**: Realiza un seguimiento del uso de tokens para los límites de contexto de los LLM
- **Compatible con Git**: Respeta tus archivos `.gitignore` y `.git/info/exclude`
- **Enfocado en la seguridad**: Detecta información sensible
- **Múltiples formatos de salida**: Elige entre texto plano, XML o Markdown

## ¿Qué sigue?

- [Guía de instalación](installation.md): Diferentes formas de instalar Repomix
- [Guía de uso](usage.md): Aprende sobre las funciones básicas y avanzadas
- [Configuración](configuration.md): Personaliza Repomix para tus necesidades
- [Funciones de seguridad](security.md): Aprende sobre las comprobaciones de seguridad

## Comunidad

Únete a nuestra [comunidad de Discord](https://discord.gg/wNYzTwZFku) para:
- Obtener ayuda con Repomix
- Compartir tus experiencias
- Sugerir nuevas funciones
- Conectarte con otros usuarios

## Soporte

¿Encontraste un error o necesitas ayuda?
- [Abre un issue en GitHub](https://github.com/yamadashy/repomix/issues)
- Únete a nuestro servidor de Discord
- Consulta la [documentación](https://repomix.com)
</file>

<file path="website/client/src/es/guide/output.md">
# Formatos de salida

Repomix admite tres formatos de salida:
- XML (predeterminado)
- Markdown
- Texto sin formato

## Formato XML

```bash
repomix --style xml
```

El formato XML está optimizado para el procesamiento de IA:

```xml
Este archivo es una representación fusionada de toda la base de código...

<file_summary>
(Metadatos e instrucciones de IA)
</file_summary>

<directory_structure>
src/
  index.ts
  utils/
    helper.ts
</directory_structure>

<files>
<file path="src/index.ts">
// Contenido del archivo aquí
</file>
</files>
```

::: tip ¿Por qué XML?
Las etiquetas XML ayudan a los modelos de IA como Claude a analizar el contenido con mayor precisión. La [documentación de Claude](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags) recomienda usar etiquetas XML para prompts estructurados.
:::

## Formato Markdown

```bash
repomix --style markdown
```

Markdown proporciona un formato legible:

```markdown
Este archivo es una representación fusionada de toda la base de código...

# Resumen de archivos
(Metadatos e instrucciones de IA)

# Estructura de directorios
```
src/
index.ts
utils/
helper.ts
```

# Archivos

## Archivo: src/index.ts
```typescript
// Contenido del archivo aquí
```
```

## Uso con modelos de IA

Cada formato funciona bien con modelos de IA, pero considera:
- Usar XML para Claude (mejor precisión de análisis)
- Usar Markdown para legibilidad general
- Usar texto sin formato para simplicidad y compatibilidad universal

## Personalización

Establece el formato predeterminado en `repomix.config.json`:
```json
{
  "output": {
    "style": "xml",
    "filePath": "output.xml"
  }
}

## Formato de texto sin formato

```bash
repomix --style plain
```

Estructura de salida:
```text
Este archivo es una representación fusionada de toda la base de código...

================
Resumen de archivos
================
(Metadatos e instrucciones de IA)

================
Estructura de directorios
================
src/
  index.ts
  utils/
    helper.ts

================
Archivos
================

================
Archivo: src/index.ts
================
// Contenido del archivo aquí
```
</file>

<file path="website/client/src/es/guide/usage.md">
# Uso básico

## Inicio rápido

Empaqueta todo tu repositorio:
```bash
repomix
```

## Casos de uso comunes

### Empaquetar directorios específicos
```bash
repomix ruta/al/directorio
```

### Incluir archivos específicos
Usa [patrones glob](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax):
```bash
repomix --include "src/**/*.ts,**/*.md"
```

### Excluir archivos
```bash
repomix --ignore "**/*.log,tmp/"
```

### Repositorios remotos
```bash
# Usando la URL de GitHub
repomix --remote https://github.com/usuario/repositorio

# Usando la abreviatura
repomix --remote usuario/repositorio

# Rama/etiqueta/commit específico
repomix --remote usuario/repositorio --remote-branch main
repomix --remote usuario/repositorio --remote-branch 935b695
```

## Formatos de salida

### XML (predeterminado)
```bash
repomix --style xml
```

### Markdown
```bash
repomix --style markdown
```

### Texto sin formato
```bash
repomix --style plain
```

## Opciones adicionales

### Eliminar comentarios
```bash
repomix --remove-comments
```

### Mostrar números de línea
```bash
repomix --output-show-line-numbers
```

### Copiar al portapapeles
```bash
repomix --copy
```

### Deshabilitar la verificación de seguridad
```bash
repomix --no-security-check
```

## Configuración

Inicializar el archivo de configuración:
```bash
repomix --init
```

Consulta la [Guía de configuración](/guide/configuration) para obtener opciones detalladas.
</file>

<file path="website/client/src/fr/guide/development/index.md">
# Contribuer à Repomix

## Démarrage rapide

```bash
git clone https://github.com/yamadashy/repomix.git
cd repomix
npm install
```

## Commandes de développement

```bash
# Exécuter le CLI
npm run repomix
# Exécuter les tests
npm run test
npm run test-coverage
# Linter le code
npm run lint
```

## Style de code

- Utiliser [Biome](https://biomejs.dev/) pour le linting et le formatage
- Injection de dépendances pour la testabilité
- Maintenir les fichiers en dessous de 250 lignes
- Ajouter des tests pour les nouvelles fonctionnalités

## Directives pour les Pull Requests

1. Exécuter tous les tests
2. Passer les vérifications de linting
3. Mettre à jour la documentation
4. Suivre le style de code existant

## Besoin d'aide?

- [Ouvrir un ticket](https://github.com/yamadashy/repomix/issues)
- [Rejoindre Discord](https://discord.gg/wNYzTwZFku)
</file>

<file path="website/client/src/fr/guide/development/setup.md">
# Configuration de l'environnement de développement

## Prérequis

- Node.js ≥ 18.0.0
- Git
- npm

## Développement local

```bash
# Cloner le dépôt
git clone https://github.com/yamadashy/repomix.git
cd repomix
# Installer les dépendances
npm install
# Exécuter le CLI
npm run repomix
```

## Développement avec Docker

```bash
# Construire l'image
docker build -t repomix .
# Exécuter le conteneur
docker run -v ./:/app -it --rm repomix
```

## Structure du projet

```
src/
├── cli/          # Implémentation du CLI
├── config/       # Gestion de la configuration
├── core/         # Fonctionnalités principales
└── shared/       # Utilitaires partagés
```

## Tests

```bash
# Exécuter les tests
npm run test
# Couverture des tests
npm run test-coverage
# Linting
npm run lint-biome
npm run lint-ts
npm run lint-secretlint
```

## Processus de publication

1. Mettre à jour la version
```bash
npm version patch  # ou minor/major
```

2. Exécuter les tests et construire
```bash
npm run test-coverage
npm run build
```

3. Publier
```bash
npm publish
```
</file>

<file path="website/client/src/fr/guide/tips/best-practices.md">
# Meilleures pratiques de développement assisté par IA : De mon expérience

Bien que je n'aie pas encore réussi à mener à bien un projet à grande échelle en utilisant l'IA, je souhaite partager ce que j'ai appris jusqu'à présent de mon expérience de travail avec l'IA dans le développement.

## Approche de développement de base

Lorsque l'on travaille avec l'IA, tenter d'implémenter toutes les fonctionnalités en une fois peut conduire à des problèmes inattendus et à une stagnation du projet. C'est pourquoi il est plus efficace de commencer par les fonctionnalités principales et de construire chaque fonctionnalité une par une, en s'assurant d'une implémentation solide avant d'avancer.

### La puissance du code existant

Cette approche est efficace car l'implémentation des fonctionnalités principales vous permet de matérialiser votre design idéal et votre style de codage à travers du code réel. La manière la plus efficace de communiquer votre vision du projet est à travers du code qui reflète vos standards et préférences.

En commençant par les fonctionnalités principales et en s'assurant que chaque composant fonctionne correctement avant de passer à la suite, l'ensemble du projet maintient sa cohérence, rendant plus facile pour l'IA de générer du code plus approprié.

## L'approche modulaire

La décomposition du code en modules plus petits est cruciale. D'après mon expérience, maintenir les fichiers autour de 250 lignes de code rend plus facile de donner des instructions claires à l'IA et rend le processus d'essai-erreur plus efficace. Bien que le nombre de tokens serait une métrique plus précise, le nombre de lignes est plus pratique pour les développeurs humains, nous utilisons donc cela comme ligne directrice.

Cette modularisation ne consiste pas seulement à séparer les composants frontend, backend et base de données - il s'agit de décomposer les fonctionnalités à un niveau beaucoup plus fin. Par exemple, au sein d'une même fonctionnalité, vous pourriez séparer la validation, la gestion des erreurs et d'autres fonctionnalités spécifiques en modules distincts. Bien sûr, la séparation de haut niveau est également importante, et implémenter cette approche modulaire progressivement aide à maintenir des instructions claires et permet à l'IA de générer du code plus approprié. Cette approche est efficace non seulement pour l'IA mais aussi pour les développeurs humains.

## Assurer la qualité par les tests

Je considère que les tests sont cruciaux dans le développement assisté par IA. Les tests servent non seulement de mesures d'assurance qualité mais aussi de documentation qui démontre clairement les intentions du code. Lorsque vous demandez à l'IA d'implémenter de nouvelles fonctionnalités, le code de test existant agit efficacement comme un document de spécification.

Les tests sont également un excellent outil pour valider l'exactitude du code généré par l'IA. Par exemple, lorsque vous faites implémenter une nouvelle fonctionnalité pour un module par l'IA, écrire des cas de test au préalable vous permet d'évaluer objectivement si le code généré se comporte comme prévu. Cela s'aligne bien avec les principes du Développement Piloté par les Tests (TDD) et est particulièrement efficace lors de la collaboration avec l'IA.

## Équilibrer planification et implémentation

Avant d'implémenter des fonctionnalités à grande échelle, je recommande de d'abord discuter du plan avec l'IA. Organiser les exigences et réfléchir à l'architecture conduit à une implémentation plus fluide. Une bonne pratique consiste à compiler d'abord les exigences, puis passer à une session de chat séparée pour le travail d'implémentation.

Il est essentiel que les humains examinent la sortie de l'IA et fassent des ajustements si nécessaire. Bien que la qualité du code généré par l'IA soit généralement modérée, cela accélère tout de même le développement par rapport à l'écriture de tout à partir de zéro.

## Conclusion

En suivant ces pratiques, vous pouvez exploiter les points forts de l'IA tout en construisant une base de code cohérente et de haute qualité. Même lorsque votre projet grandit en taille, chaque composant reste bien défini et gérable.
</file>

<file path="website/client/src/fr/guide/code-compress.md">
# Compression de code

La compression de code est une fonctionnalité puissante qui extrait intelligemment les structures de code essentielles tout en supprimant les détails d'implémentation. C'est particulièrement utile pour réduire le nombre de tokens tout en conservant les informations structurelles importantes de votre base de code.

> [!NOTE]  
> Il s'agit d'une fonctionnalité expérimentale que nous améliorerons activement en fonction des retours utilisateurs et de l'usage réel

## Utilisation de base

Activez la compression de code en utilisant l'option `--compress`:

```bash
repomix --compress
```

Vous pouvez également l'utiliser avec des dépôts distants:

```bash
repomix --remote user/repo --compress
```

## Fonctionnement

L'algorithme de compression traite le code en utilisant l'analyse tree-sitter pour extraire et préserver les éléments structurels essentiels tout en supprimant les détails d'implémentation.

La compression préserve:
- Les signatures de fonctions et de méthodes
- Les définitions d'interfaces et de types
- Les structures de classes et leurs propriétés
- Les éléments structurels importants

Tout en supprimant:
- Les implémentations de fonctions et de méthodes
- Les détails de logique des boucles et conditions
- Les déclarations de variables internes
- Le code spécifique à l'implémentation

### Exemple

Code TypeScript original:

```typescript
import { ShoppingItem } from './shopping-item';
/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
  let total = 0;
  for (const item of items) {
    total += item.price * item.quantity;
  }
  return total;
}
// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

Après compression:

```typescript
import { ShoppingItem } from './shopping-item';
⋮----
/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
⋮----
// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

## Configuration

Vous pouvez activer la compression dans votre fichier de configuration:

```json
{
  "output": {
    "compress": true
  }
}
```

## Cas d'utilisation

La compression de code est particulièrement utile pour:
- Analyser la structure et l'architecture du code
- Réduire le nombre de tokens pour le traitement par les LLM
- Créer une documentation de haut niveau
- Comprendre les motifs de code et les signatures
- Partager les conceptions d'API et d'interfaces

## Options associées

Vous pouvez combiner la compression avec d'autres options:
- `--remove-comments`: Supprimer les commentaires du code
- `--remove-empty-lines`: Supprimer les lignes vides
- `--output-show-line-numbers`: Ajouter les numéros de ligne à la sortie
</file>

<file path="website/client/src/fr/guide/command-line-options.md">
# Options de ligne de commande

## Options de base
- `-v, --version`: Afficher la version de l'outil

## Options de sortie
- `-o, --output <fichier>`: Nom du fichier de sortie (par défaut: `repomix-output.txt`)
- `--style <type>`: Style de sortie (`plain`, `xml`, `markdown`) (par défaut: `plain`)
- `--parsable-style`: Activer une sortie analysable basée sur le schéma du style choisi (par défaut: `false`)
- `--compress`: Effectuer une extraction intelligente du code, en se concentrant sur les signatures essentielles de fonctions et de classes tout en supprimant les détails d'implémentation. Pour plus de détails et d'exemples, voir [Guide de compression de code](code-compress).
- `--output-show-line-numbers`: Ajouter les numéros de ligne (par défaut: `false`)
- `--copy`: Copier dans le presse-papiers (par défaut: `false`)
- `--no-file-summary`: Désactiver le résumé des fichiers (par défaut: `true`)
- `--no-directory-structure`: Désactiver la structure des répertoires (par défaut: `true`)
- `--remove-comments`: Supprimer les commentaires (par défaut: `false`)
- `--remove-empty-lines`: Supprimer les lignes vides (par défaut: `false`)
- `--header-text <texte>`: Texte personnalisé à inclure dans l'en-tête du fichier
- `--instruction-file-path <chemin>`: Chemin vers un fichier contenant des instructions personnalisées détaillées
- `--include-empty-directories`: Inclure les répertoires vides dans la sortie (par défaut: `false`)

## Options de filtrage
- `--include <motifs>`: Motifs d'inclusion (séparés par des virgules)
- `-i, --ignore <motifs>`: Motifs d'exclusion (séparés par des virgules)
- `--no-gitignore`: Désactiver l'utilisation du fichier .gitignore
- `--no-default-patterns`: Désactiver les motifs par défaut

## Options de dépôt distant
- `--remote <url>`: Traiter un dépôt distant
- `--remote-branch <n>`: Spécifier le nom de la branche distante, le tag ou le hash de commit (par défaut: branche par défaut du dépôt)

## Options de configuration
- `-c, --config <chemin>`: Chemin du fichier de configuration personnalisé
- `--init`: Créer un fichier de configuration
- `--global`: Utiliser la configuration globale

## Options de sécurité
- `--no-security-check`: Désactiver la vérification de sécurité (par défaut: `true`)

## Options de comptage de tokens
- `--token-count-encoding <encodage>`: Spécifier l'encodage du comptage de tokens (par exemple, `o200k_base`, `cl100k_base`) (par défaut: `o200k_base`)

## Autres options
- `--top-files-len <nombre>`: Nombre de fichiers principaux à afficher (par défaut: `5`)
- `--verbose`: Activer la journalisation détaillée
- `--quiet`: Désactiver toutes les sorties vers stdout

## Exemples

```bash
# Utilisation de base
repomix
# Sortie personnalisée
repomix -o output.xml --style xml
# Sortie personnalisée avec compression
repomix --compress
# Traiter des fichiers spécifiques
repomix --include "src/**/*.ts" --ignore "**/*.test.ts"
# Dépôt distant avec branche
repomix --remote https://github.com/user/repo/tree/main
# Dépôt distant avec commit
repomix --remote https://github.com/user/repo/commit/836abcd7335137228ad77feb28655d85712680f1
# Dépôt distant avec format abrégé
repomix --remote user/repo
```
</file>

<file path="website/client/src/fr/guide/comment-removal.md">
# Suppression des commentaires

Repomix peut automatiquement supprimer les commentaires de votre base de code lors de la génération du fichier de sortie. Cela peut aider à réduire le bruit et à se concentrer sur le code réel.

## Utilisation

Pour activer la suppression des commentaires, définissez l'option `removeComments` sur `true` dans votre `repomix.config.json`:

```json
{
  "output": {
    "removeComments": true
  }
}
```

## Langages pris en charge

Repomix prend en charge la suppression des commentaires pour une large gamme de langages de programmation, notamment:

- JavaScript/TypeScript (`//`, `/* */`)
- Python (`#`, `"""`, `'''`)
- Java (`//`, `/* */`)
- C/C++ (`//`, `/* */`)
- HTML (`<!-- -->`)
- CSS (`/* */`)
- Et bien d'autres...

## Exemple

Considérons le code JavaScript suivant:

```javascript
// Ceci est un commentaire sur une ligne
function test() {
  /* Ceci est un
     commentaire sur
     plusieurs lignes */
  return true;
}
```

Avec la suppression des commentaires activée, la sortie sera:

```javascript
function test() {
  return true;
}
```

## Remarques

- La suppression des commentaires est effectuée avant les autres étapes de traitement, comme l'ajout de numéros de ligne.
- Certains commentaires, comme les commentaires JSDoc, peuvent être préservés selon le langage et le contexte.
</file>

<file path="website/client/src/fr/guide/configuration.md">
# Configuration

## Démarrage rapide

Créer un fichier de configuration:
```bash
repomix --init
```

## Fichier de configuration

`repomix.config.json`:
```json
{
  "output": {
    "filePath": "repomix-output.xml",
    "style": "xml",
    "parsableStyle": true,
    "compress": false,
    "headerText": "Texte d'en-tête personnalisé",
    "instructionFilePath": "repomix-instruction.md",
    "fileSummary": true,
    "directoryStructure": true,
    "removeComments": false,
    "removeEmptyLines": false,
    "topFilesLength": 5,
    "showLineNumbers": false,
    "copyToClipboard": false,
    "includeEmptyDirectories": false,
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  },
  "include": ["**/*"],
  "ignore": {
    "useGitignore": true,
    "useDefaultPatterns": true,
    "customPatterns": ["tmp/", "*.log"]
  },
  "security": {
    "enableSecurityCheck": true
  }
}
```

## Configuration globale

Créer une configuration globale:
```bash
repomix --init --global
```

Emplacement:
- Windows: `%LOCALAPPDATA%\Repomix\repomix.config.json`
- macOS/Linux: `~/.config/repomix/repomix.config.json`

## Motifs d'exclusion

Priorité:
1. Options CLI (`--ignore`)
2. `.repomixignore`
3. `.gitignore` et `.git/info/exclude`
4. Motifs par défaut

Exemple de `.repomixignore`:
```text
# Répertoires de cache
.cache/
tmp/
# Sorties de build
dist/
build/
# Logs
*.log
```

## Motifs d'exclusion par défaut

Motifs communs inclus par défaut:
```text
node_modules/**
.git/**
coverage/**
dist/**
```

Liste complète: [defaultIgnore.ts](https://github.com/yamadashy/repomix/blob/main/src/config/defaultIgnore.ts)

## Exemples

### Compression de code

Lorsque `output.compress` est défini sur `true`, Repomix extraira intelligemment les structures de code essentielles tout en supprimant les détails d'implémentation. Cela aide à réduire le nombre de tokens tout en conservant les informations structurelles importantes.

Pour plus de détails et d'exemples, consultez le [Guide de compression de code](code-compress).

### Intégration Git

La configuration `output.git` vous permet de contrôler comment les fichiers sont triés en fonction de l'historique Git:
- `sortByChanges`: Lorsque défini sur `true`, les fichiers sont triés par nombre de changements Git (commits qui ont modifié le fichier). Les fichiers avec plus de changements apparaissent en bas de la sortie. Cela peut aider à prioriser les fichiers plus activement développés. Par défaut: `true`
- `sortByChangesMaxCommits`: Le nombre maximum de commits à analyser lors du comptage des modifications de fichiers. Par défaut: `100`

Exemple de configuration:
```json
{
  "output": {
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  }
}
```

### Suppression des commentaires

Lorsque `output.removeComments` est défini sur `true`, Repomix supprimera les commentaires des types de fichiers pris en charge pour réduire la taille de sortie et se concentrer sur le contenu essentiel du code.

Pour les langages pris en charge et des exemples détaillés, consultez le [Guide de suppression des commentaires](comment-removal).
</file>

<file path="website/client/src/fr/guide/custom-instructions.md">
# Instructions personnalisées

Repomix vous permet de fournir des instructions personnalisées qui seront incluses dans le fichier de sortie. Cela peut être utile pour ajouter du contexte ou des directives spécifiques pour les systèmes d'IA traitant le dépôt.

## Utilisation

Pour inclure une instruction personnalisée, créez un fichier markdown (par exemple, `repomix-instruction.md`) à la racine de votre dépôt. Ensuite, spécifiez le chemin vers ce fichier dans votre `repomix.config.json`:

```json
{
  "output": {
    "instructionFilePath": "repomix-instruction.md"
  }
}
```

Le contenu de ce fichier sera inclus dans la sortie sous la section "Instruction".

## Exemple

```markdown
# Instructions du dépôt
Ce dépôt contient le code source de l'outil Repomix. Veuillez suivre ces directives lors de l'analyse du code:
1. Concentrez-vous sur les fonctionnalités principales dans le répertoire `src/core`.
2. Portez une attention particulière aux vérifications de sécurité dans `src/core/security`.
3. Ignorez tous les fichiers dans le répertoire `tests`.
```

Cela donnera la section suivante dans la sortie:

```xml
<instruction>
# Instructions du dépôt
Ce dépôt contient le code source de l'outil Repomix. Veuillez suivre ces directives lors de l'analyse du code:
1. Concentrez-vous sur les fonctionnalités principales dans le répertoire `src/core`.
2. Portez une attention particulière aux vérifications de sécurité dans `src/core/security`.
3. Ignorez tous les fichiers dans le répertoire `tests`.
</instruction>
```
</file>

<file path="website/client/src/fr/guide/index.md">
# Commencer avec Repomix

Repomix est un outil qui regroupe l'ensemble de votre dépôt de code en un seul fichier adapté à l'IA. Il est conçu pour vous aider à fournir votre base de code aux Grands Modèles de Langage (LLMs) comme ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok, et plus encore.

## Démarrage rapide

Exécutez cette commande dans le répertoire de votre projet:

```bash
npx repomix
```

C'est tout! Vous trouverez un fichier `repomix-output.txt` contenant l'intégralité de votre dépôt dans un format adapté à l'IA.

Vous pouvez ensuite envoyer ce fichier à un assistant IA avec une instruction comme:

```
Ce fichier contient tous les fichiers du dépôt combinés en un seul.
Je souhaite refactoriser le code, veuillez donc d'abord l'examiner.
```

L'IA analysera votre base de code complète et fournira des informations détaillées:

![Utilisation du fichier Repomix 1](/images/docs/repomix-file-usage-1.png)

Lors de la discussion de modifications spécifiques, l'IA peut vous aider à générer du code. Avec des fonctionnalités comme les Artefacts de Claude, vous pouvez même recevoir plusieurs fichiers interdépendants:

![Utilisation du fichier Repomix 2](/images/docs/repomix-file-usage-2.png)

Bon codage! 🚀

## Fonctionnalités principales

- **Sortie optimisée pour l'IA**: Formate votre base de code pour un traitement facile par l'IA
- **Comptage de tokens**: Suit l'utilisation des tokens pour les limites de contexte des LLM
- **Compatible avec Git**: Respecte vos fichiers `.gitignore` et `.git/info/exclude`
- **Axé sur la sécurité**: Détecte les informations sensibles
- **Plusieurs formats de sortie**: Choisissez entre texte brut, XML ou Markdown

## Prochaines étapes

- [Guide d'installation](installation.md): Différentes façons d'installer Repomix
- [Guide d'utilisation](usage.md): Découvrez les fonctionnalités de base et avancées
- [Configuration](configuration.md): Personnalisez Repomix selon vos besoins
- [Fonctionnalités de sécurité](security.md): Découvrez les vérifications de sécurité

## Communauté

Rejoignez notre [communauté Discord](https://discord.gg/wNYzTwZFku) pour:
- Obtenir de l'aide avec Repomix
- Partager vos expériences
- Suggérer de nouvelles fonctionnalités
- Vous connecter avec d'autres utilisateurs

## Support

Vous avez trouvé un bug ou besoin d'aide?
- [Ouvrez un ticket sur GitHub](https://github.com/yamadashy/repomix/issues)
- Rejoignez notre serveur Discord
- Consultez la [documentation](https://repomix.com)
</file>

<file path="website/client/src/fr/guide/installation.md">
# Installation

## Utilisation avec npx (Sans installation requise)

```bash
npx repomix
```

## Installation globale

### npm

```bash
npm install -g repomix
```

### Yarn

```bash
yarn global add repomix
```

### Homebrew (macOS/Linux)

```bash
brew install repomix
```

## Installation avec Docker

Téléchargez et exécutez l'image Docker:

```bash
# Répertoire courant
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix
# Répertoire spécifique
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory
# Dépôt distant
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote yamadashy/repomix
```

## Extension VSCode

Exécutez Repomix directement dans VSCode avec l'extension communautaire [Repomix Runner](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner).

Fonctionnalités:
- Empaquetez n'importe quel dossier en quelques clics
- Choisissez entre le mode fichier ou contenu pour la copie
- Nettoyage automatique des fichiers de sortie
- Compatible avec repomix.config.json

Installez-la depuis le [Marketplace VSCode](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner).

## Configuration requise

- Node.js: ≥ 18.0.0
- Git: Requis pour le traitement des dépôts distants

## Vérification

Après l'installation, vérifiez que Repomix fonctionne:

```bash
repomix --version
repomix --help
```
</file>

<file path="website/client/src/fr/guide/mcp-server.md">
# Serveur MCP

Repomix prend en charge le [Model Context Protocol (MCP)](https://modelcontextprotocol.io), permettant aux assistants IA d'interagir directement avec votre base de code. Lorsqu'il est exécuté en tant que serveur MCP, Repomix fournit des outils permettant aux assistants IA de packager des dépôts locaux ou distants pour analyse sans nécessiter de préparation manuelle des fichiers.

> [!NOTE]  
> Il s'agit d'une fonctionnalité expérimentale que nous améliorerons activement en fonction des retours utilisateurs et de l'usage réel

## Exécuter Repomix comme serveur MCP

Pour exécuter Repomix en tant que serveur MCP, utilisez l'option `--mcp`:
```bash
repomix --mcp
```

Cela démarre Repomix en mode serveur MCP, le rendant disponible pour les assistants IA qui prennent en charge le Model Context Protocol.

## Outils MCP disponibles

En mode serveur MCP, Repomix fournit les outils suivants:

### pack_codebase

Cet outil package un répertoire de code local dans un fichier consolidé pour l'analyse par IA.

**Paramètres:**
- `directory`: (Requis) Chemin absolu vers le répertoire à packager
- `compress`: (Optionnel, par défaut: true) Effectuer ou non l'extraction intelligente de code pour réduire le nombre de tokens
- `includePatterns`: (Optionnel) Liste de motifs d'inclusion séparés par des virgules
- `ignorePatterns`: (Optionnel) Liste de motifs d'exclusion séparés par des virgules

**Exemple:**
```json
{
  "directory": "/path/to/your/project",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### pack_remote_repository

Cet outil récupère, clone et package un dépôt GitHub dans un fichier consolidé pour l'analyse par IA.

**Paramètres:**
- `remote`: (Requis) URL du dépôt GitHub ou format utilisateur/dépôt (par exemple, yamadashy/repomix)
- `compress`: (Optionnel, par défaut: true) Effectuer ou non l'extraction intelligente de code pour réduire le nombre de tokens
- `includePatterns`: (Optionnel) Liste de motifs d'inclusion séparés par des virgules
- `ignorePatterns`: (Optionnel) Liste de motifs d'exclusion séparés par des virgules

**Exemple:**
```json
{
  "remote": "yamadashy/repomix",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### file_system_read_file et file_system_read_directory

Le serveur MCP de Repomix fournit deux outils système de fichiers qui permettent aux assistants IA d'interagir en toute sécurité avec le système de fichiers local:

1. `file_system_read_file`
   - Lit le contenu des fichiers en utilisant des chemins absolus
   - Implémente la validation de sécurité avec [Secretlint](https://github.com/secretlint/secretlint)
   - Empêche l'accès aux fichiers contenant des informations sensibles
   - Renvoie du contenu formaté avec des messages d'erreur clairs pour les chemins invalides ou les problèmes de sécurité

2. `file_system_read_directory`
   - Liste le contenu des répertoires en utilisant des chemins absolus
   - Affiche les fichiers et répertoires avec des indicateurs clairs (`[FILE]` ou `[DIR]`)
   - Fournit une traversée sécurisée des répertoires avec une gestion appropriée des erreurs
   - Valide les chemins et s'assure qu'ils sont absolus

Les deux outils intègrent des mesures de sécurité robustes:
- Validation des chemins absolus pour prévenir les attaques par traversée de répertoire
- Vérifications des permissions pour assurer des droits d'accès appropriés
- Intégration avec Secretlint pour la détection d'informations sensibles
- Messages d'erreur clairs pour un meilleur débogage et une meilleure sensibilisation à la sécurité

**Exemple:**
```typescript
// Lecture d'un fichier
const fileContent = await tools.file_system_read_file({
  path: '/absolute/path/to/file.txt'
});
// Liste du contenu d'un répertoire
const dirContent = await tools.file_system_read_directory({
  path: '/absolute/path/to/directory'
});
```

Ces outils sont particulièrement utiles lorsque les assistants IA doivent:
- Analyser des fichiers spécifiques dans la base de code
- Naviguer dans les structures de répertoires
- Vérifier l'existence et l'accessibilité des fichiers
- Assurer des opérations sécurisées sur le système de fichiers

## Configuration des serveurs MCP

Pour utiliser Repomix comme serveur MCP avec des assistants IA comme Claude, vous devez configurer les paramètres MCP:

### Pour Cline (extension VS Code)

Modifiez le fichier `cline_mcp_settings.json`:
```json
{
  "mcpServers": {
    "repomix": {
      "command": "npx",
      "args": [
        "-y",
        "repomix",
        "--mcp"
      ]
    }
  }
}
```

### Pour Claude Desktop

Modifiez le fichier `claude_desktop_config.json` avec une configuration similaire à celle de Cline.

## Avantages de l'utilisation de Repomix comme serveur MCP

L'utilisation de Repomix comme serveur MCP offre plusieurs avantages:

1. **Intégration directe**: Les assistants IA peuvent analyser directement votre base de code sans préparation manuelle des fichiers.
2. **Flux de travail efficace**: Simplifie le processus d'analyse du code en éliminant le besoin de générer et de télécharger manuellement des fichiers.
3. **Sortie cohérente**: Garantit que l'assistant IA reçoit la base de code dans un format cohérent et optimisé.
4. **Fonctionnalités avancées**: Exploite toutes les fonctionnalités de Repomix comme la compression de code, le comptage de tokens et les vérifications de sécurité.

Une fois configuré, votre assistant IA peut utiliser directement les capacités de Repomix pour analyser les bases de code, rendant les flux de travail d'analyse de code plus efficaces.
</file>

<file path="website/client/src/fr/guide/output.md">
# Formats de sortie

Repomix prend en charge trois formats de sortie:
- XML (par défaut)
- Markdown
- Texte brut

## Format XML

```bash
repomix --style xml
```

Le format XML est optimisé pour le traitement par l'IA:

```xml
Ce fichier est une représentation fusionnée de toute la base de code...
<file_summary>
(Métadonnées et instructions pour l'IA)
</file_summary>
<directory_structure>
src/
  index.ts
  utils/
    helper.ts
</directory_structure>
<files>
<file path="src/index.ts">
// Contenu du fichier ici
</file>
</files>
```

::: tip Pourquoi XML?
Les balises XML aident les modèles d'IA comme Claude à analyser le contenu plus précisément. La [Documentation de Claude](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags) recommande d'utiliser des balises XML pour les prompts structurés.
:::

## Format Markdown

```bash
repomix --style markdown
```

Le Markdown offre un formatage lisible:

```markdown
Ce fichier est une représentation fusionnée de toute la base de code...
# Résumé du fichier
(Métadonnées et instructions pour l'IA)
# Structure des répertoires
```
src/
index.ts
utils/
helper.ts
```
# Fichiers
## Fichier: src/index.ts
```typescript
// Contenu du fichier ici
```
```

## Utilisation avec les modèles d'IA

Chaque format fonctionne bien avec les modèles d'IA, mais considérez:
- Utilisez XML pour Claude (meilleure précision d'analyse)
- Utilisez Markdown pour une meilleure lisibilité générale
- Utilisez le texte brut pour la simplicité et une compatibilité universelle

## Personnalisation

Définissez le format par défaut dans `repomix.config.json`:

```json
{
  "output": {
    "style": "xml",
    "filePath": "output.xml"
  }
}
```

## Format texte brut

```bash
repomix --style plain
```

Structure de sortie:

```text
Ce fichier est une représentation fusionnée de toute la base de code...
================
Résumé du fichier
================
(Métadonnées et instructions pour l'IA)
================
Structure des répertoires
================
src/
  index.ts
  utils/
    helper.ts
================
Fichiers
================
================
Fichier: src/index.ts
================
// Contenu du fichier ici
```
</file>

<file path="website/client/src/fr/guide/prompt-examples.md">
# Exemples de prompts

## Revue de code

### Revue d'architecture
```
Analysez l'architecture de cette base de code:
1. Évaluez la structure globale et les motifs de conception
2. Identifiez les problèmes architecturaux potentiels
3. Suggérez des améliorations pour la scalabilité
4. Notez les zones qui suivent les meilleures pratiques
Concentrez-vous sur la maintenabilité et la modularité.
```

### Revue de sécurité
```
Effectuez une revue de sécurité de cette base de code:
1. Identifiez les vulnérabilités potentielles
2. Vérifiez les anti-patterns courants de sécurité
3. Examinez la gestion des erreurs et la validation des entrées
4. Évaluez la sécurité des dépendances
Fournissez des exemples spécifiques et des étapes de correction.
```

### Revue de performance
```
Examinez la base de code pour les performances:
1. Identifiez les goulots d'étranglement
2. Vérifiez l'utilisation des ressources
3. Examinez l'efficacité algorithmique
4. Évaluez les stratégies de mise en cache
Incluez des recommandations d'optimisation spécifiques.
```

## Génération de documentation

### Documentation d'API
```
Générez une documentation complète de l'API:
1. Listez et décrivez tous les points d'accès publics
2. Documentez les formats de requête/réponse
3. Incluez des exemples d'utilisation
4. Notez les limitations ou contraintes
```

### Guide du développeur
```
Créez un guide du développeur couvrant:
1. Instructions de configuration
2. Aperçu de la structure du projet
3. Flux de travail de développement
4. Approche de test
5. Étapes courantes de dépannage
```

### Documentation d'architecture
```
Documentez l'architecture du système:
1. Vue d'ensemble de haut niveau
2. Interactions entre composants
3. Diagrammes de flux de données
4. Décisions de conception et justification
5. Contraintes et limitations du système
```

## Analyse et amélioration

### Analyse des dépendances
```
Analysez les dépendances du projet:
1. Identifiez les packages obsolètes
2. Vérifiez les vulnérabilités de sécurité
3. Suggérez des packages alternatifs
4. Examinez les modèles d'utilisation des dépendances
Incluez des recommandations spécifiques de mise à niveau.
```

### Couverture de tests
```
Examinez la couverture des tests:
1. Identifiez les composants non testés
2. Suggérez des cas de test supplémentaires
3. Examinez la qualité des tests
4. Recommandez des stratégies de test
```

### Qualité du code
```
Évaluez la qualité du code et suggérez des améliorations:
1. Examinez les conventions de nommage
2. Vérifiez l'organisation du code
3. Évaluez la gestion des erreurs
4. Examinez les pratiques de commentaires
Fournissez des exemples spécifiques de modèles bons et problématiques.
```

## Conseils pour de meilleurs résultats

1. **Soyez spécifique**: Incluez des objectifs clairs et des critères d'évaluation
2. **Établissez le contexte**: Spécifiez votre rôle et le niveau d'expertise requis
3. **Format de demande**: Définissez comment vous souhaitez structurer la réponse
4. **Priorisez**: Indiquez quels aspects sont les plus importants

## Notes spécifiques aux modèles

### Claude
- Utilisez le format de sortie XML
- Placez les instructions importantes à la fin
- Spécifiez la structure de réponse

### ChatGPT
- Utilisez le format Markdown
- Divisez les grandes bases de code en sections
- Incluez des prompts de rôle système

### Gemini
- Fonctionne avec tous les formats
- Concentrez-vous sur des domaines spécifiques par demande
- Utilisez une analyse étape par étape
</file>

<file path="website/client/src/fr/guide/remote-repository-processing.md">
# Traitement des dépôts distants

## Utilisation de base

Traiter des dépôts publics:
```bash
# En utilisant l'URL complète
repomix --remote https://github.com/user/repo
# En utilisant le format abrégé GitHub
repomix --remote user/repo
```

## Sélection de branche et de commit

```bash
# Branche spécifique
repomix --remote user/repo --remote-branch main
# Tag
repomix --remote user/repo --remote-branch v1.0.0
# Hash de commit
repomix --remote user/repo --remote-branch 935b695
```

## Prérequis

- Git doit être installé
- Connexion Internet
- Accès en lecture au dépôt

## Contrôle de la sortie

```bash
# Emplacement de sortie personnalisé
repomix --remote user/repo -o custom-output.xml
# Avec format XML
repomix --remote user/repo --style xml
# Supprimer les commentaires
repomix --remote user/repo --remove-comments
```

## Utilisation avec Docker

```bash
# Traiter et sortir dans le répertoire courant
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo
# Sortie vers un répertoire spécifique
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix \
  --remote user/repo
```

## Problèmes courants

### Problèmes d'accès
- Assurez-vous que le dépôt est public
- Vérifiez l'installation de Git
- Vérifiez la connexion Internet

### Dépôts volumineux
- Utilisez `--include` pour sélectionner des chemins spécifiques
- Activez `--remove-comments`
- Traitez les branches séparément
</file>

<file path="website/client/src/fr/guide/security.md">
# Sécurité

## Fonctionnalité de vérification de sécurité

Repomix utilise [Secretlint](https://github.com/secretlint/secretlint) pour détecter les informations sensibles dans vos fichiers:
- Clés d'API
- Jetons d'accès
- Identifiants
- Clés privées
- Variables d'environnement

## Configuration

Les vérifications de sécurité sont activées par défaut.

Désactivation via CLI:
```bash
repomix --no-security-check
```

Ou dans `repomix.config.json`:
```json
{
  "security": {
    "enableSecurityCheck": false
  }
}
```

## Mesures de sécurité

1. **Exclusion des fichiers binaires**: Les fichiers binaires ne sont pas inclus dans la sortie
2. **Compatible avec Git**: Respecte les motifs `.gitignore`
3. **Détection automatisée**: Analyse les problèmes de sécurité courants:
    - Identifiants AWS
    - Chaînes de connexion aux bases de données
    - Jetons d'authentification
    - Clés privées

## Lorsque la vérification de sécurité trouve des problèmes

Exemple de sortie:
```bash
🔍 Vérification de sécurité:
────────────────────────────
2 fichier(s) suspect(s) détecté(s) et exclu(s):
1. config/credentials.json
   - Clé d'accès AWS trouvée
2. .env.local
   - Mot de passe de base de données trouvé
```

## Meilleures pratiques

1. Toujours examiner la sortie avant de la partager
2. Utiliser `.repomixignore` pour les chemins sensibles
3. Garder les vérifications de sécurité activées
4. Supprimer les fichiers sensibles du dépôt

## Signalement des problèmes de sécurité

Vous avez trouvé une vulnérabilité de sécurité? Veuillez:
1. Ne pas ouvrir un ticket public
2. Envoyer un email à: koukun0120@gmail.com
3. Ou utiliser les [Avis de sécurité GitHub](https://github.com/yamadashy/repomix/security/advisories/new)
</file>

<file path="website/client/src/fr/index.md">
---
layout: home
title: Repomix
titleTemplate: Empaquetez votre code dans des formats adaptés à l'IA
aside: false
editLink: false

features:
  - icon: 🤖
    title: Optimisé pour l'IA
    details: Formate votre base de code d'une manière facilement compréhensible et traitable par l'IA.
  - icon: ⚙️
    title: Compatible avec Git
    details: Respecte automatiquement vos fichiers .gitignore.
  - icon: 🛡️
    title: Axé sur la sécurité
    details: Intègre Secretlint pour des vérifications de sécurité robustes afin de détecter et prévenir l'inclusion d'informations sensibles.
  - icon: 📊
    title: Comptage de tokens
    details: Fournit le nombre de tokens pour chaque fichier et l'ensemble du dépôt, utile pour les limites de contexte des LLM.

---

<div class="cli-section">

## Démarrage rapide

Une fois que vous avez généré un fichier compressé (`repomix-output.txt`) avec Repomix, vous pouvez l'envoyer à un assistant IA avec une instruction comme :

```
Ce fichier contient tous les fichiers du dépôt combinés en un seul.
Je souhaite refactoriser le code, veuillez donc d'abord l'examiner.
```

L'IA analysera votre base de code complète et fournira des informations détaillées :

![Utilisation de Repomix 1](/images/docs/repomix-file-usage-1.png)

Lors de la discussion de modifications spécifiques, l'IA peut vous aider à générer du code. Avec des fonctionnalités comme les Artefacts de Claude, vous pouvez même recevoir plusieurs fichiers interdépendants :

![Utilisation de Repomix 2](/images/docs/repomix-file-usage-2.png)

Bon codage ! 🚀

## Guide de l'utilisateur avancé

Pour les utilisateurs avancés qui ont besoin de plus de contrôle, Repomix offre de nombreuses options de personnalisation via son interface en ligne de commande.

### Démarrage rapide

Vous pouvez essayer Repomix instantanément dans votre répertoire de projet sans installation :

```bash
npx repomix
```

Ou l'installer globalement pour une utilisation répétée :

```bash
# Installation avec npm
npm install -g repomix

# Alternativement avec yarn
yarn global add repomix

# Alternativement avec Homebrew (macOS/Linux)
brew install repomix

# Puis exécuter dans n'importe quel répertoire de projet
repomix
```

C'est tout ! Repomix générera un fichier `repomix-output.txt` dans votre répertoire actuel, contenant l'intégralité de votre dépôt dans un format adapté à l'IA.

### Utilisation

Pour empaqueter tout votre dépôt :

```bash
repomix
```

Pour empaqueter un répertoire spécifique :

```bash
repomix path/to/directory
```

Pour empaqueter des fichiers ou répertoires spécifiques en utilisant des [motifs glob](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax) :

```bash
repomix --include "src/**/*.ts,**/*.md"
```

Pour exclure des fichiers ou répertoires spécifiques :

```bash
repomix --ignore "**/*.log,tmp/"
```

Pour empaqueter un dépôt distant :
```bash
# Utilisation du format abrégé
npx repomix --remote yamadashy/repomix

# Utilisation de l'URL complète (prend en charge les branches et les chemins spécifiques)
npx repomix --remote https://github.com/yamadashy/repomix
npx repomix --remote https://github.com/yamadashy/repomix/tree/main

# Utilisation de l'URL d'un commit
npx repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1
```

Pour initialiser un nouveau fichier de configuration (`repomix.config.json`) :

```bash
repomix --init
```

Une fois que vous avez généré le fichier compressé, vous pouvez l'utiliser avec des outils d'IA générative comme Claude, ChatGPT et Gemini.

#### Utilisation avec Docker

Vous pouvez également exécuter Repomix avec Docker 🐳  
C'est utile si vous souhaitez exécuter Repomix dans un environnement isolé ou préférez utiliser des conteneurs.

Utilisation de base (répertoire courant) :

```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix
```

Pour empaqueter un répertoire spécifique :
```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory
```

Traiter un dépôt distant et sortir vers un répertoire `output` :

```bash
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote https://github.com/yamadashy/repomix
```

### Formats de sortie

Choisissez votre format de sortie préféré :

```bash
# Format XML (par défaut)
repomix --style xml

# Format Markdown
repomix --style markdown

# Format texte brut
repomix --style plain
```

### Personnalisation

Créez un `repomix.config.json` pour des paramètres persistants :

```json
{
  "output": {
    "style": "markdown",
    "filePath": "custom-output.md",
    "removeComments": true,
    "showLineNumbers": true,
    "topFilesLength": 10
  },
  "ignore": {
    "customPatterns": ["*.test.ts", "docs/**"]
  }
}
```

### Plus d'exemples
::: tip
💡 Consultez notre [dépôt GitHub](https://github.com/yamadashy/repomix) pour une documentation complète et plus d'exemples !
:::

</div>
</file>

<file path="website/client/src/ja/guide/code-compress.md">
# コード圧縮
コード圧縮は、実装の詳細を省きながら、コードの本質的な構造を抽出する強力な機能です。トークン数を削減しながらコードベースの重要な構造情報を維持したい場合に特に有用です。

> [!NOTE]
> これは実験的な機能であり、ユーザーのフィードバックや実際の使用状況に基づいて積極的に改善を行っています。

## 基本的な使い方

`--compress`フラグを使用してコード圧縮を有効にします：

```bash
repomix --compress
```

リモートリポジトリでも使用できます：

```bash
repomix --remote user/repo --compress
```

## 仕組み

圧縮アルゴリズムは、tree-sitterパーシングを使用してコードを処理し、本質的な構造要素を抽出・保持しながら、実装の詳細を除外します。

圧縮で保持される要素：
- 関数やメソッドのシグネチャ
- インターフェースと型定義
- クラス構造とプロパティ
- 重要な構造的要素

以下の要素は除外されます：
- 関数やメソッドの実装内容
- ループや条件分岐のロジック詳細
- 内部変数の宣言
- 実装固有のコード

### 例

元のTypeScriptコード：

```typescript
import { ShoppingItem } from './shopping-item';

/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
  let total = 0;
  for (const item of items) {
    total += item.price * item.quantity;
  }
  return total;
}

// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

圧縮後：

```typescript
import { ShoppingItem } from './shopping-item';
⋮----
/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
⋮----
// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

## 設定

設定ファイルで圧縮を有効にすることもできます：

```json
{
  "output": {
    "compress": true
  }
}
```

## ユースケース

コード圧縮は以下のような場合に特に有用です：
- コードの構造やアーキテクチャの分析
- LLM処理のためのトークン数削減
- 高レベルなドキュメントの作成
- コードパターンやシグネチャの理解
- APIやインターフェース設計の共有

## 関連オプション

圧縮は以下のオプションと組み合わせることができます：
- `--remove-comments`: コードコメントを削除
- `--remove-empty-lines`: 空行を削除
- `--output-show-line-numbers`: 出力に行番号を追加
</file>

<file path="website/client/src/ja/guide/index.md">
# Repomixとは

Repomixは、リポジトリ全体をAIフレンドリーな単一ファイルにパッケージングするツールです。ChatGPT、DeepSeek、Perplexity、Gemini、Gemma、Llama、Grokなどの大規模言語モデル（LLM）にコードベースを提供するために設計されています。

## クイックスタート

プロジェクトディレクトリで以下のコマンドを実行するだけです。

```bash
npx repomix
```

これだけで、`repomix-output.txt`ファイルにAIが理解しやすい形式でリポジトリ全体がまとめられます。

このファイルを以下のようなプロンプトとともにAIアシスタントに送信できます。

```
このファイルはリポジトリ内のすべてのファイルを1つにまとめたものです。
コードのリファクタリングを行いたいので、まずはコードレビューをお願いします。
```

すると、AIはコードベース全体を分析し、包括的な洞察を提供してくれます。

![Repomixの使用例1](/images/docs/repomix-file-usage-1.png)

具体的な変更点を議論する際には、AIはコードの生成をサポートしてくれます。例えば、Claudeのアーティファクト機能などを使用すると、相互に依存する複数のファイルを一度に生成することも可能です。

![Repomixの使用例2](/images/docs/repomix-file-usage-2.png)

良いコーディング体験を！🚀

## 主な機能

- **AI最適化**: コードベースをAIが理解しやすい形式にフォーマット化
- **トークンカウント**: LLMのコンテキスト制限に対応するためのトークン数を計測
- **Git対応**: `.gitignore`および`.git/info/exclude`ファイルを自動的に認識してパッケージング対象から除外
- **セキュリティ重視**: [Secretlint](https://github.com/secretlint/secretlint)を使用した機密情報の検出と保護
- **複数の出力形式**: プレーンテキスト、XML、Markdownの出力形式を選択可能

## 次のステップ

- [インストールガイド](installation.md): Repomixをインストールするにはこちら
- [使用方法](usage.md): 基本的な使い方から高度な使い方まで
- [設定](configuration.md): Repomixをカスタマイズするにはこちら
- [セキュリティ機能](security.md): セキュリティチェックの詳細はこちら

## コミュニティ

[Discordコミュニティ](https://discord.gg/wNYzTwZFku)に参加して、Repomixの使い方について質問したり、経験やノウハウを共有したり、新機能を提案したり、他のユーザーと交流しましょう。

## サポート

バグを見つけた場合や支援が必要な場合は、[GitHubでイシューを作成](https://github.com/yamadashy/repomix/issues)するか、Discordサーバーに参加してください。
</file>

<file path="website/client/src/ja/guide/output.md">
# 出力フォーマット

Repomixは3つの出力フォーマットをサポートしています。
- XML（デフォルト）
- Markdown
- プレーンテキスト

## XMLフォーマット

```bash
repomix --style xml
```

XMLフォーマットはAI処理に最適化されています。

```xml
このファイルは、コードベース全体を1つのドキュメントにまとめた表現です...

<file_summary>
（メタデータとAI向けの使用説明）
</file_summary>

<directory_structure>
src/
  index.ts
  utils/
    helper.ts
</directory_structure>

<files>
<file path="src/index.js">
// ファイルの内容がここに表示されます
</file>
</files>

<instruction>
（output.instructionFilePathで指定されたカスタム指示）
</instruction>
```

::: tip なぜXML？
XMLタグはClaudeなどのAIモデルがコンテンツをより正確に解析するのに役立ちます。[Claude公式ドキュメント](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags)では、構造化されたプロンプトにXMLタグを使用することを推奨しています。
:::

## Markdownフォーマット

```bash
repomix --style markdown
```

Markdownは読みやすいフォーマットを提供します。

```markdown
このファイルは、コードベース全体を1つのドキュメントにまとめた表現です...

# ファイルサマリー
（メタデータとAI向けの使用説明）

# ディレクトリ構造
```
src/
index.ts
utils/
helper.ts
```

# ファイル

## File: src/index.ts
```typescript
// ファイルの内容がここに表示されます
```
```

## AIモデルとの使用

各フォーマットはAIモデルで問題なく動作しますが、以下の点を考慮してください。
- XMLはClaude用に最適化（最も正確な解析）
- Markdownは一般的な読みやすさを重視
- プレーンテキストはシンプルさと互換性を重視

## カスタマイズ

`repomix.config.json`でデフォルトのフォーマットを設定
```json
{
  "output": {
    "style": "xml",
    "filePath": "output.xml"
  }
}
```

## プレーンテキストフォーマット

```bash
repomix --style plain
```

出力の構造
```text
このファイルは、コードベース全体を1つのドキュメントにまとめた表現です...

================
ファイルサマリー
================
（メタデータとAI向けの使用説明）

================
ディレクトリ構造
================
src/
  index.ts
  utils/
    helper.ts

================
ファイル
================

================
File: src/index.js
================
// ファイルの内容がここに表示されます

================
File: src/utils.js
================
// ファイルの内容がここに表示されます
```
</file>

<file path="website/client/src/ja/guide/usage.md">
# 基本的な使い方

## クイックスタート

リポジトリ全体をパッケージ化：
```bash
repomix
```

## 一般的な使用例

### 特定のディレクトリをパッケージ化
```bash
repomix path/to/directory
```

### 特定のファイルを含める
[glob パターン](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax)を使用：
```bash
repomix --include "src/**/*.ts,**/*.md"
```

### ファイルを除外
```bash
repomix --ignore "**/*.log,tmp/"
```

### リモートリポジトリ
```bash
# GitHub URLを使用
repomix --remote https://github.com/user/repo

# ショートハンドを使用
repomix --remote user/repo

# 特定のブランチ/タグ/コミット
repomix --remote user/repo --remote-branch main
repomix --remote user/repo --remote-branch 935b695
```

### コード圧縮
```bash
repomix --compress

# リモートリポジトリでも使用可能：
repomix --remote user/repo --compress
```

## 出力形式

### XML（デフォルト）
```bash
repomix --style xml
```

### Markdown
```bash
repomix --style markdown
```

### プレーンテキスト
```bash
repomix --style plain
```

## その他のオプション

### コメントを削除
```bash
repomix --remove-comments
```

### 行番号を表示
```bash
repomix --output-show-line-numbers
```

### クリップボードにコピー
```bash
repomix --copy
```

### セキュリティチェックを無効化
```bash
repomix --no-security-check
```

## 設定

設定ファイルを初期化：
```bash
repomix --init
```

詳細なオプションについては[設定ガイド](/ja/guide/configuration)を参照してください。
</file>

<file path="website/client/src/ko/guide/code-compress.md">
# 코드 압축

코드 압축은 구현 세부 사항을 제거하면서 필수적인 코드 구조를 지능적으로 추출하는 강력한 기능입니다. 이는 코드베이스의 중요한 구조적 정보를 유지하면서 토큰 수를 줄일 때 특히 유용합니다.

> [!NOTE]
> 이것은 실험적인 기능으로, 사용자 피드백과 실제 사용 사례를 바탕으로 지속적으로 개선될 예정입니다.

## 기본 사용법

`--compress` 플래그를 사용하여 코드 압축을 활성화합니다:

```bash
repomix --compress
```

원격 저장소에서도 사용할 수 있습니다:

```bash
repomix --remote user/repo --compress
```

## 작동 방식

압축 알고리즘은 Tree-sitter 파싱을 사용하여 코드를 처리하고, 구현 세부 사항을 제거하면서 필수적인 구조적 요소를 추출하고 보존합니다.

압축 시 유지되는 요소:
- 함수와 메서드 시그니처
- 인터페이스와 타입 정의
- 클래스 구조와 속성
- 중요한 구조적 요소

제거되는 요소:
- 함수와 메서드 구현
- 반복문과 조건문 로직 세부 사항
- 내부 변수 선언
- 구현 관련 코드

### 예시

원본 TypeScript 코드:

```typescript
import { ShoppingItem } from './shopping-item';

/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
  let total = 0;
  for (const item of items) {
    total += item.price * item.quantity;
  }
  return total;
}

// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

압축 후:

```typescript
import { ShoppingItem } from './shopping-item';
⋮----
/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
⋮----
// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

## 설정

설정 파일에서 압축을 활성화할 수 있습니다:

```json
{
  "output": {
    "compress": true
  }
}
```

## 사용 사례

코드 압축은 다음과 같은 경우에 특히 유용합니다:
- 코드 구조와 아키텍처 분석
- LLM 처리를 위한 토큰 수 감소
- 고수준 문서 작성
- 코드 패턴과 시그니처 이해
- API와 인터페이스 설계 공유

## 관련 옵션

압축은 다음 옵션들과 함께 사용할 수 있습니다:
- `--remove-comments`: 코드 주석 제거
- `--remove-empty-lines`: 빈 줄 제거
- `--output-show-line-numbers`: 출력에 줄 번호 추가
</file>

<file path="website/client/src/ko/guide/index.md">
# Repomix 시작하기

Repomix는 전체 저장소를 AI 친화적인 단일 파일로 패키징하는 도구입니다. ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok 등의 대규모 언어 모델(LLM)에 코드베이스를 제공하는 데 도움이 되도록 설계되었습니다.

## 빠른 시작

프로젝트 디렉토리에서 다음 명령을 실행하세요.

```bash
npx repomix
```

이게 전부입니다! AI 친화적인 형식으로 전체 저장소를 포함하는 `repomix-output.txt` 파일이 생성됩니다.

그런 다음 이 파일을 다음과 같은 프롬프트와 함께 AI 어시스턴트에 보낼 수 있습니다.

```
이 파일에는 저장소의 모든 파일이 하나로 결합되어 있습니다.
코드를 리팩터링하고 싶으니 먼저 검토해 주세요.
```

AI는 전체 코드베이스를 분석하고 포괄적인 인사이트를 제공합니다.

![Repomix 파일 사용 예시 1](/images/docs/repomix-file-usage-1.png)

특정 변경 사항을 논의할 때 AI가 코드 생성을 도울 수 있습니다. Claude의 Artifacts와 같은 기능을 사용하면 상호 의존적인 여러 파일을 받을 수도 있습니다.

![Repomix 파일 사용 예시 2](/images/docs/repomix-file-usage-2.png)

즐거운 코딩 되세요! 🚀

## 핵심 기능

- **AI 최적화 출력**: AI 처리에 용이한 형식으로 코드베이스를 구성합니다.
- **토큰 계산**: LLM 컨텍스트 제한을 위한 토큰 사용량을 추적합니다.
- **Git 인식**: `.gitignore` 파일과 `.git/info/exclude` 파일을 존중합니다.
- **보안 중심**: 민감한 정보를 탐지합니다.
- **다양한 출력 형식**: 일반 텍스트, XML, Markdown 중에서 선택할 수 있습니다.

## 다음 단계

- [설치 가이드](installation.md): Repomix를 설치하는 다양한 방법
- [사용 가이드](usage.md): 기본 및 고급 기능에 대해 알아보기
- [구성](configuration.md): 필요에 맞게 Repomix를 사용자 정의하기
- [보안 기능](security.md): 보안 검사에 대해 알아보기

## 커뮤니티

[Discord 커뮤니티](https://discord.gg/wNYzTwZFku)에 참여하세요:
- Repomix에 대한 도움 받기
- 경험 공유
- 새로운 기능 제안
- 다른 사용자와 소통

## 지원

버그를 발견했거나 도움이 필요하신가요?
- [GitHub에 이슈 열기](https://github.com/yamadashy/repomix/issues)
- Discord 서버에 참여하기
- [문서 확인하기](https://repomix.com)
</file>

<file path="website/client/src/ko/guide/output.md">
# 출력 형식

Repomix는 세 가지 출력 형식을 지원합니다:
- XML (기본값)
- Markdown
- 일반 텍스트

## XML 형식

```bash
repomix --style xml
```

XML 형식은 AI 처리에 최적화되어 있습니다:

```xml
이 파일은 전체 코드베이스를 하나의 문서로 통합한 것입니다...

<file_summary>
(메타데이터 및 AI 지시사항)
</file_summary>

<directory_structure>
src/
  index.ts
  utils/
    helper.ts
</directory_structure>

<files>
<file path="src/index.ts">
// 파일 내용
</file>
</files>
```

::: tip XML을 사용하는 이유
XML 태그는 Claude와 같은 AI 모델이 내용을 더 정확하게 파싱하는 데 도움이 됩니다. [Claude 공식 문서](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags)에서는 구조화된 프롬프트에 XML 태그 사용을 권장하고 있습니다.
:::

## Markdown 형식

```bash
repomix --style markdown
```

Markdown은 읽기 쉬운 형식을 제공합니다:

```markdown
이 파일은 전체 코드베이스를 하나의 문서로 통합한 것입니다...

# 파일 요약
(메타데이터 및 AI 지시사항)

# 디렉토리 구조
```
src/
index.ts
utils/
helper.ts
```

# 파일

## File: src/index.ts
```typescript
// 파일 내용
```
```

## AI 모델과의 사용

각 형식은 AI 모델에서 잘 작동하지만, 다음 사항을 고려하세요:
- Claude에는 XML 사용 (가장 정확한 파싱)
- 일반적인 가독성을 위해서는 Markdown
- 단순성과 호환성을 위해서는 일반 텍스트

## 사용자 정의

`repomix.config.json`에서 기본 형식 설정:
```json
{
  "output": {
    "style": "xml",
    "filePath": "output.xml"
  }
}
```

## 일반 텍스트 형식

```bash
repomix --style plain
```

출력 구조:
```text
이 파일은 전체 코드베이스를 하나의 문서로 통합한 것입니다...

================
파일 요약
================
(메타데이터 및 AI 지시사항)

================
디렉토리 구조
================
src/
  index.ts
  utils/
    helper.ts

================
파일
================

================
File: src/index.ts
================
// 파일 내용
```
</file>

<file path="website/client/src/ko/guide/usage.md">
# 기본 사용법

## 빠른 시작

저장소 전체를 패키징:
```bash
repomix
```

## 일반적인 사용 사례

### 특정 디렉토리 패키징
```bash
repomix path/to/directory
```

### 특정 파일 포함
[glob 패턴](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax) 사용:
```bash
repomix --include "src/**/*.ts,**/*.md"
```

### 파일 제외
```bash
repomix --ignore "**/*.log,tmp/"
```

### 원격 저장소 처리
```bash
# GitHub URL 사용
repomix --remote https://github.com/user/repo

# 단축형 사용
repomix --remote user/repo

# 특정 브랜치/태그/커밋
repomix --remote user/repo --remote-branch main
repomix --remote user/repo --remote-branch 935b695
```

## 출력 형식

### XML (기본값)
```bash
repomix --style xml
```

### Markdown
```bash
repomix --style markdown
```

### 일반 텍스트
```bash
repomix --style plain
```

## 추가 옵션

### 주석 제거
```bash
repomix --remove-comments
```

### 행 번호 표시
```bash
repomix --output-show-line-numbers
```

### 클립보드에 복사
```bash
repomix --copy
```

### 보안 검사 비활성화
```bash
repomix --no-security-check
```

## 설정

설정 파일 초기화:
```bash
repomix --init
```

더 자세한 설정 옵션은 [설정 가이드](/ko/guide/configuration)를 참조하세요.
</file>

<file path="website/client/src/pt-br/guide/code-compress.md">
# Compressão de Código

A compressão de código é um recurso poderoso que extrai estruturas essenciais do código de forma inteligente enquanto remove detalhes de implementação. Isso é particularmente útil para reduzir a contagem de tokens enquanto mantém informações estruturais importantes sobre sua base de código.

> [!NOTE]
> Este é um recurso experimental que estaremos melhorando ativamente com base no feedback dos usuários e no uso no mundo real.

## Uso Básico

Ative a compressão de código usando a flag `--compress`:

```bash
repomix --compress
```

Você também pode usá-la com repositórios remotos:

```bash
repomix --remote user/repo --compress
```

## Como Funciona

O algoritmo de compressão processa o código usando análise Tree-sitter para extrair e preservar elementos estruturais essenciais enquanto remove detalhes de implementação.

A compressão preserva:
- Assinaturas de funções e métodos
- Definições de interfaces e tipos
- Estruturas de classes e propriedades
- Elementos estruturais importantes

Enquanto remove:
- Implementações de funções e métodos
- Detalhes de lógica de loops e condicionais
- Declarações de variáveis internas
- Código específico de implementação

### Exemplo

Código TypeScript original:

```typescript
import { ShoppingItem } from './shopping-item';

/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
  let total = 0;
  for (const item of items) {
    total += item.price * item.quantity;
  }
  return total;
}

// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

Após a compressão:

```typescript
import { ShoppingItem } from './shopping-item';
⋮----
/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
⋮----
// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

## Configuração

Você pode ativar a compressão em seu arquivo de configuração:

```json
{
  "output": {
    "compress": true
  }
}
```

## Casos de Uso

A compressão de código é particularmente útil quando:
- Analisando estrutura e arquitetura do código
- Reduzindo contagem de tokens para processamento LLM
- Criando documentação de alto nível
- Compreendendo padrões e assinaturas de código
- Compartilhando designs de API e interface

## Opções Relacionadas

Você pode combinar a compressão com outras opções:
- `--remove-comments`: Remover comentários do código
- `--remove-empty-lines`: Remover linhas vazias
- `--output-show-line-numbers`: Adicionar números de linha à saída
</file>

<file path="website/client/src/pt-br/guide/index.md">
# Introdução ao Repomix

O Repomix é uma ferramenta que compacta todo o seu repositório em um único arquivo amigável para IA. Ele foi projetado para ajudá-lo a alimentar seu código-fonte para Modelos de Linguagem Grandes (LLMs) como ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok e mais.

## Início Rápido

Execute este comando no diretório do seu projeto:

```bash
npx repomix
```

É isso! Você encontrará um arquivo `repomix-output.txt` contendo todo o seu repositório em um formato amigável para IA.

Você pode então enviar este arquivo para um assistente de IA com um prompt como:

```
Este arquivo contém todos os arquivos do repositório combinados em um só.
Eu quero refatorar o código, então, por favor, revise-o primeiro.
```

A IA analisará todo o seu código-fonte e fornecerá insights abrangentes:

![Uso do Arquivo Repomix 1](/images/docs/repomix-file-usage-1.png)

Ao discutir mudanças específicas, a IA pode ajudar a gerar código. Com recursos como o Artifacts do Claude, você pode até receber vários arquivos interdependentes:

![Uso do Arquivo Repomix 2](/images/docs/repomix-file-usage-2.png)

Feliz codificação! 🚀

## Principais Recursos

- **Saída Otimizada para IA**: Formata seu código-fonte para fácil processamento por IA
- **Contagem de Tokens**: Rastreia o uso de tokens para limites de contexto de LLM
- **Consciente do Git**: Respeita seus arquivos `.gitignore` e `.git/info/exclude`
- **Focado em Segurança**: Detecta informações sensíveis
- **Múltiplos Formatos de Saída**: Escolha entre texto simples, XML ou Markdown

## O que vem a seguir?

- [Guia de Instalação](installation.md): Diferentes maneiras de instalar o Repomix
- [Guia de Uso](usage.md): Aprenda sobre recursos básicos e avançados
- [Configuração](configuration.md): Personalize o Repomix para suas necessidades
- [Recursos de Segurança](security.md): Aprenda sobre verificações de segurança

## Comunidade

Junte-se à nossa [comunidade Discord](https://discord.gg/wNYzTwZFku) para:
- Obter ajuda com o Repomix
- Compartilhar suas experiências
- Sugerir novos recursos
- Conectar-se com outros usuários

## Suporte

Encontrou um bug ou precisa de ajuda?
- [Abra um problema no GitHub](https://github.com/yamadashy/repomix/issues)
- Junte-se ao nosso servidor Discord
- Verifique a [documentação](https://repomix.com)
</file>

<file path="website/client/src/pt-br/guide/output.md">
# Formatos de Saída

O Repomix suporta três formatos de saída:
- XML (padrão)
- Markdown
- Texto simples

## Formato XML

```bash
repomix --style xml
```

O formato XML é otimizado para processamento por IA:

::: tip Por que XML?
As tags XML ajudam modelos de IA como o Claude a analisar o conteúdo com mais precisão. A [documentação do Claude](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags) recomenda o uso de tags XML para prompts estruturados.
:::

```xml
Este arquivo é uma representação consolidada de toda a base de código...

<file_summary>
(Metadados e instruções para IA)
</file_summary>

<directory_structure>
src/
  index.ts
  utils/
    helper.ts
</directory_structure>

<files>
<file path="src/index.ts">
// Conteúdo do arquivo aqui
</file>
</files>
```

## Formato Markdown

```bash
repomix --style markdown
```

O Markdown oferece formatação legível:

```markdown
Este arquivo é uma representação consolidada de toda a base de código...

# Resumo do Arquivo
(Metadados e instruções para IA)

# Estrutura de Diretórios
```text
src/
  index.ts
  utils/
    helper.ts
```

# Arquivos

## Arquivo: src/index.ts
```typescript
// Conteúdo do arquivo aqui
```
```

## Uso com Modelos de IA

Cada formato funciona bem com modelos de IA, mas considere:
- Use XML para Claude (melhor precisão de análise)
- Use Markdown para leitura geral
- Use Texto Simples para simplicidade e compatibilidade universal

## Customização

Defina o formato padrão no `repomix.config.json`:
```json
{
  "output": {
    "style": "xml",
    "filePath": "output.xml"
  }
}
```

## Formato de Texto Simples

```bash
repomix --style plain
```

Estrutura de saída:
```text
Este arquivo é uma representação consolidada de toda a base de código...

================
Resumo do Arquivo
================
(Metadados e instruções para IA)

================
Estrutura de Diretórios
================
src/
  index.ts
  utils/
    helper.ts

================
Arquivos
================

================
Arquivo: src/index.ts
================
// Conteúdo do arquivo aqui
```
</file>

<file path="website/client/src/pt-br/guide/usage.md">
# Uso Básico

## Início Rápido

Compacte todo o seu repositório:
```bash
repomix
```

## Casos de Uso Comuns

### Compactar Diretórios Específicos
```bash
repomix path/to/directory
```

### Incluir Arquivos Específicos
Use [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax):
```bash
repomix --include "src/**/*.ts,**/*.md"
```

### Excluir Arquivos
```bash
repomix --ignore "**/*.log,tmp/"
```

### Repositórios Remotos
```bash
# Usando URL do GitHub
repomix --remote https://github.com/user/repo

# Usando abreviação
repomix --remote user/repo

# Branch/tag/commit específico
repomix --remote user/repo --remote-branch main
repomix --remote user/repo --remote-branch 935b695
```

## Formatos de Saída

### XML (Padrão)
```bash
repomix --style xml
```

### Markdown
```bash
repomix --style markdown
```

### Texto Simples
```bash
repomix --style plain
```

## Opções Adicionais

### Remover Comentários
```bash
repomix --remove-comments
```

### Mostrar Números de Linha
```bash
repomix --output-show-line-numbers
```

### Copiar para a Área de Transferência
```bash
repomix --copy
```

### Desativar Verificação de Segurança
```bash
repomix --no-security-check
```

## Configuração

Inicializar arquivo de configuração:
```bash
repomix --init
```

Veja o [Guia de Configuração](/pt-br/guide/configuration) para opções detalhadas.
</file>

<file path="website/client/src/zh-cn/guide/code-compress.md">
# 代码压缩
代码压缩是一个强大的功能，它能够在移除实现细节的同时智能提取关键代码结构。在需要减少令牌数量的同时保持代码库的重要结构信息时，这个功能特别有用。

> [!NOTE]
> 这是一个实验性功能，我们将根据用户反馈和实际使用情况积极改进。

## 基本用法

使用 `--compress` 标志启用代码压缩：

```bash
repomix --compress
```

也可以在远程仓库中使用：

```bash
repomix --remote user/repo --compress
```

## 工作原理

压缩算法使用 Tree-sitter 解析处理代码，提取并保留基本结构元素，同时移除实现细节。

压缩会保留：
- 函数和方法签名
- 接口和类型定义
- 类结构和属性
- 重要的结构元素

同时会移除：
- 函数和方法实现
- 循环和条件逻辑细节
- 内部变量声明
- 具体实现代码

### 示例

原始 TypeScript 代码：

```typescript
import { ShoppingItem } from './shopping-item';

/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
  let total = 0;
  for (const item of items) {
    total += item.price * item.quantity;
  }
  return total;
}

// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

压缩后：

```typescript
import { ShoppingItem } from './shopping-item';
⋮----
/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
⋮----
// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

## 配置

你可以在配置文件中启用压缩：

```json
{
  "output": {
    "compress": true
  }
}
```

## 使用场景

代码压缩在以下情况特别有用：
- 分析代码结构和架构
- 减少用于 LLM 处理的令牌数量
- 创建高层次文档
- 理解代码模式和签名
- 共享 API 和接口设计

## 相关选项

你可以将压缩与其他选项结合使用：
- `--remove-comments`: 移除代码注释
- `--remove-empty-lines`: 移除空行
- `--output-show-line-numbers`: 在输出中添加行号
</file>

<file path="website/client/src/zh-cn/guide/index.md">
# Repomix 入门指南

Repomix 是一个将代码库打包成单个 AI 友好文件的工具。它专为帮助你将代码提供给大型语言模型（如 ChatGPT、DeepSeek、Perplexity、Gemini、Gemma、Llama、Grok 等）而设计。

## 快速开始

在你的项目目录中运行以下命令：

```bash
npx repomix
```

就这么简单！你会在当前目录中找到一个 `repomix-output.txt` 文件，其中包含了以 AI 友好格式整理的整个代码库。

然后，你可以将此文件发送给 AI 助手，并附上类似这样的提示：

```
这个文件包含了仓库中所有文件的合并内容。
我想重构代码，请先帮我审查一下。
```

AI 将分析你的整个代码库并提供全面的见解：

![Repomix 使用示例1](/images/docs/repomix-file-usage-1.png)

在讨论具体修改时，AI 可以帮助生成代码。通过像 Claude 的 Artifacts 这样的功能，你甚至可以一次性接收多个相互依赖的文件：

![Repomix 使用示例2](/images/docs/repomix-file-usage-2.png)

祝你编码愉快！🚀

## 核心功能

- **AI 优化**：以 AI 易于理解的格式整理代码库
- **令牌计数**：为 LLM 上下文限制提供令牌使用统计
- **Git 感知**：自动识别并遵循 `.gitignore` 和 `.git/info/exclude` 文件
- **注重安全**：使用 Secretlint 进行敏感信息检测
- **多种输出格式**：可选纯文本、XML 或 Markdown 格式

## 下一步

- [安装指南](installation.md)：了解安装 Repomix 的不同方式
- [使用指南](usage.md)：学习基本和高级功能
- [配置](configuration.md)：根据需求自定义 Repomix
- [安全功能](security.md)：了解安全检查详情

## 社区

加入我们的 [Discord 社区](https://discord.gg/wNYzTwZFku)：
- 获取 Repomix 使用帮助
- 分享你的使用经验
- 提出新功能建议
- 与其他用户交流

## 支持

发现问题或需要帮助？
- [在 GitHub 上提交问题](https://github.com/yamadashy/repomix/issues)
- 加入 Discord 服务器
- 查看[文档](https://repomix.com)
</file>

<file path="website/client/src/zh-cn/guide/output.md">
# 输出格式

Repomix 支持三种输出格式：
- XML（默认）
- Markdown
- 纯文本

## XML 格式

```bash
repomix --style xml
```

XML 格式针对 AI 处理进行了优化：

```xml
本文件是整个代码库的合并表示形式...

<file_summary>
（元数据和 AI 指令）
</file_summary>

<directory_structure>
src/
  index.ts
  utils/
    helper.ts
</directory_structure>

<files>
<file path="src/index.ts">
// 文件内容
</file>
</files>
```

::: tip 为什么选择 XML？
XML 标签有助于像 Claude 这样的 AI 模型更准确地解析内容。[Claude 官方文档](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags)推荐使用 XML 标签来构建结构化提示。
:::

## Markdown 格式

```bash
repomix --style markdown
```

Markdown 提供了易读的格式：

```markdown
本文件是整个代码库的合并表示形式...

# 文件概要
（元数据和 AI 指令）

# 目录结构
```
src/
index.ts
utils/
helper.ts
```

# 文件

## File: src/index.ts
```typescript
// 文件内容
```
```

## 在 AI 模型中的使用

每种格式都能在 AI 模型中正常工作，但需要考虑以下几点：
- 对 Claude 使用 XML（解析最准确）
- 对一般可读性使用 Markdown
- 对简单性和通用兼容性使用纯文本

## 自定义设置

在 `repomix.config.json` 中设置默认格式：
```json
{
  "output": {
    "style": "xml",
    "filePath": "output.xml"
  }
}
```

## 纯文本格式

```bash
repomix --style plain
```

输出结构：
```text
本文件是整个代码库的合并表示形式...

================
文件概要
================
（元数据和 AI 指令）

================
目录结构
================
src/
  index.ts
  utils/
    helper.ts

================
文件
================

================
File: src/index.ts
================
// 文件内容
```
</file>

<file path="website/client/src/zh-cn/guide/usage.md">
# 基本用法

## 快速开始

打包整个仓库：
```bash
repomix
```

## 常见使用场景

### 打包指定目录
```bash
repomix path/to/directory
```

### 包含特定文件
使用 [glob 模式](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax)：
```bash
repomix --include "src/**/*.ts,**/*.md"
```

### 排除文件
```bash
repomix --ignore "**/*.log,tmp/"
```

### 处理远程仓库
```bash
# 使用 GitHub URL
repomix --remote https://github.com/user/repo

# 使用简写形式
repomix --remote user/repo

# 指定分支/标签/提交
repomix --remote user/repo --remote-branch main
repomix --remote user/repo --remote-branch 935b695
```

## 输出格式

### XML（默认）
```bash
repomix --style xml
```

### Markdown
```bash
repomix --style markdown
```

### 纯文本
```bash
repomix --style plain
```

## 其他选项

### 移除注释
```bash
repomix --remove-comments
```

### 显示行号
```bash
repomix --output-show-line-numbers
```

### 复制到剪贴板
```bash
repomix --copy
```

### 禁用安全检查
```bash
repomix --no-security-check
```

## 配置

初始化配置文件：
```bash
repomix --init
```

更多详细配置选项请参阅[配置指南](/zh-cn/guide/configuration)。
</file>

<file path="website/client/package.json">
{
  "private": true,
  "type": "module",
  "scripts": {
    "docs:dev": "vitepress dev",
    "docs:build": "vitepress build",
    "docs:preview": "vitepress preview"
  },
  "dependencies": {
    "jszip": "^3.10.1",
    "lucide-vue-next": "^0.474.0",
    "vite-plugin-pwa": "^0.21.1",
    "vue3-ace-editor": "^2.2.4"
  },
  "devDependencies": {
    "@types/gtag.js": "^0.0.20",
    "rollup-plugin-visualizer": "^5.14.0",
    "vitepress": "^1.6.3"
  }
}
</file>

<file path="website/server/src/schemas/request.ts">
import { isValidRemoteValue } from 'repomix';
import { z } from 'zod';

// Regular expression to validate ignore patterns
// Allowed characters: alphanumeric, *, ?, /, -, _, ., !, (, ), space, comma
const ignorePatternRegex = /^[a-zA-Z0-9*?\/\-_.,!()\s]*$/;

export const packOptionsSchema = z
  .object({
    removeComments: z.boolean().optional(),
    removeEmptyLines: z.boolean().optional(),
    showLineNumbers: z.boolean().optional(),
    fileSummary: z.boolean().optional(),
    directoryStructure: z.boolean().optional(),
    includePatterns: z
      .string()
      .max(1000, 'Include patterns too long')
      .optional()
      .transform((val) => val?.trim()),
    ignorePatterns: z
      .string()
      .regex(ignorePatternRegex, 'Invalid characters in ignore patterns')
      .max(1000, 'Ignore patterns too long')
      .optional()
      .transform((val) => val?.trim()),
    outputParsable: z.boolean().optional(),
  })
  .strict();

const isValidZipFile = (file: File) => {
  return file.type === 'application/zip' || file.name.endsWith('.zip');
};

const fileSchema = z
  .custom<File>()
  .refine((file) => file instanceof File, {
    message: 'Invalid file format',
  })
  .refine((file) => isValidZipFile(file), {
    message: 'Only ZIP files are allowed',
  })
  .refine((file) => file.size <= 10 * 1024 * 1024, {
    // 10MB limit
    message: 'File size must be less than 10MB',
  });

export const packRequestSchema = z
  .object({
    url: z
      .string()
      .min(1, 'Repository URL is required')
      .max(200, 'Repository URL is too long')
      .transform((val) => val.trim())
      .refine((val) => isValidRemoteValue(val), { message: 'Invalid repository URL' })
      .optional(),
    file: fileSchema.optional(),
    format: z.enum(['xml', 'markdown', 'plain']),
    options: packOptionsSchema,
  })
  .strict()
  .refine((data) => data.url || data.file, {
    message: 'Either URL or file must be provided',
  })
  .refine((data) => !(data.url && data.file), {
    message: 'Cannot provide both URL and file',
  });

export type PackRequest = z.infer<typeof packRequestSchema>;
</file>

<file path=".cursorrules">
# Important
- Follow custom AI instructions written in `.github/copilot-instructions.md`.
</file>

<file path="repomix.config.json">
{
  "output": {
    "filePath": "repomix-output.xml",
    "style": "xml",
    "compress": false,
    "headerText": "This repository contains the source code for the Repomix tool.\nRepomix is designed to pack repository contents into a single file,\nmaking it easier for AI systems to analyze and process the codebase.\n\nKey Features:\n- Configurable ignore patterns\n- Custom header text support\n- Efficient file processing and packing\n\nPlease refer to the README.md file for more detailed information on usage and configuration.\n",
    "instructionFilePath": "repomix-instruction.md",
    "fileSummary": true,
    "directoryStructure": true,
    "removeComments": false,
    "removeEmptyLines": false,
    "topFilesLength": 5,
    "showLineNumbers": false,
    "includeEmptyDirectories": true,
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  },
  "include": [],
  "ignore": {
    "useGitignore": true,
    "useDefaultPatterns": true,
    // ignore is specified in .repomixignore
    "customPatterns": []
  },
  "security": {
    "enableSecurityCheck": true
  },
  "tokenCount": {
    "encoding": "o200k_base"
  }
}
</file>

<file path=".github/copilot-instructions.md">
# Repomix Project Structure and Overview

This document provides a structural overview of the Repomix project, designed to aid AI code assistants (like Copilot) in understanding the codebase.

Please refer to `README.md` for a complete and up-to-date project overview, and `CONTRIBUTING.md` for implementation guidelines and contribution procedures.

## Project Overview

Repomix is a tool that packs the contents of a software repository into a single file, making it easier for AI systems to analyze and process the codebase. It supports various output formats (XML, Markdown, or plain text), ignores files based on configurable patterns, and performs security checks to exclude potentially sensitive information.

## Directory Structure

The project is organized into the following directories:

```
repomix/
├── src/ # Main source code
│   ├── cli/ # Command-line interface logic (argument parsing, command handling, output)
│   ├── config/ # Configuration loading, schema, and defaults
│   ├── core/ # Core logic of Repomix
│   │   ├── file/ # File handling (reading, processing, searching, tree structure generation, git commands)
│   │   ├── metrics/ # Calculating code metrics (character count, token count)
│   │   ├── output/ # Output generation (different styles, headers, etc.)
│   │   ├── packager/ # Orchestrates file collection, processing, output, and clipboard operations.
│   │   ├── security/ # Security checks to exclude sensitive files
│   │   ├── mcp/ # MCP server integration (packaging codebases for AI analysis)
│   │   ├── tokenCount/ # Token counting using Tiktoken
│   │   └── treeSitter/ # Code parsing using Tree-sitter and language-specific queries
│   └── shared/ # Shared utilities and types (error handling, logging, helper functions)
├── tests/ # Unit and integration tests (organized mirroring src/)
│   ├── cli/
│   ├── config/
│   ├── core/
│   ├── integration-tests/
│   ├── shared/
│   └── testing/
└── website/ # Documentation website (VitePress)
    ├── client/      # Client-side code (Vue.js components, styles, configuration)
    │   ├── .vitepress/  # VitePress configuration and theme
    │   │   ├── config/  # Site configuration files (navigation, sidebar, etc.)
    │   │   └── theme/   # Custom theme and styles
    │   ├── components/ # Vue.js components for the website
    │   └── src/        # Markdown files for the documentation in various languages (en, ja, etc.)
    └── server/      # Server-side API (for remote repository processing)
        └── src/       # Server source code (API endpoints, request handling)
```

----------------------------------------------------------------

# Coding Guidelines
- Follow the Airbnb JavaScript Style Guide.
- Split files into smaller, focused units when appropriate:
  - Aim to keep code files under 250 lines. If a file exceeds 250 lines, split it into multiple files based on functionality.
- Add comments to clarify non-obvious logic. **Ensure all comments are written in English.**
- Provide corresponding unit tests for all new features.
- After implementation, verify changes by running:
  ```bash
  npm run lint  # Ensure code style compliance
  npm run test  # Verify all tests pass
  ```

## Dependencies and Testing
- Inject dependencies through a deps object parameter for testability
- Example:
  ```typescript
  export const functionName = async (
    param1: Type1,
    param2: Type2,
    deps = {
      defaultFunction1,
      defaultFunction2,
    }
  ) => {
    // Use deps.defaultFunction1() instead of direct call
  };
  ```
- Mock dependencies by passing test doubles through deps object
- Use vi.mock() only when dependency injection is not feasible

## Generate Comprehensive Output
- Include all content without abbreviation, unless specified otherwise
- Optimize for handling large codebases while maintaining output quality

----------------------------------------------------------------

# GitHub Release Note Guidelines
When writing release notes, please follow these guidelines:

- When referencing issues or PRs, use the gh command to verify the content:
  ```bash
  gh issue view <issue-number>  # For checking issue content
  gh pr view <pr-number>        # For checking PR content
  ```
  This helps ensure accuracy in release note descriptions.

Here are some examples of release notes that follow the guidelines:

v0.2.25
````md
This release brings significant improvements to output formatting and introduces flexible remote repository handling capabilities along with enhanced logging features.

# Improvements ⚡

## Remote Repository Enhancement (#335)
- Added branch/tag parsing directly from repository URLs:
```bash
repomix --remote https://github.com/yamadashy/repomix/tree/0.1.x
```
Functions identically to:
```bash
repomix --remote https://github.com/yamadashy/repomix --remote-branch 0.1.x
```

Special thanks to @huy-trn for implementing this user-friendly feature!

## Enhanced Output Formatting (#328, #329, #330)
- Added "End of Codebase" marker for better clarity in output
- Improved output header accuracy:
  - Better representation of codebase scope
  - Clear indication when using `--include` or `--ignore` options

Special thanks to @gitkenan for adding the "End of Codebase" marker and reporting the header issue!

## Path Pattern Support (#337)
- Added support for special characters in paths:
  - Handles parentheses in include patterns (e.g., `src/(categories)/**/*`)
  - Improved escaping for `[]` and `{}`
  - Essential for Next.js route groups and similar frameworks

Thank you @matheuscoelhomalta for improving path pattern support!

# How to Update

```bash
npm update -g repomix
```

---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.
````

v0.2.24
````md
This release significantly enhances configuration flexibility with comprehensive CLI flag support and expands default ignore patterns for better project scaffolding. 

# What's New 🚀

## CLI Flags Revolution (#324)
- New command-line configuration now available.

```
- `--no-gitignore`: Disable .gitignore file usage
- `--no-default-patterns`: Disable default patterns
- `--header-text <text>`: Custom text to include in the file header
- `--instruction-file-path <path>`: Path to a file containing detailed custom instructions
- `--include-empty-directories`: Include empty directories in the output
```

Special recognition to @massdo for driving ecosystem growth.

# Improvements ⚡

## Enhanced Ignore Patterns (#318, #322)
- Expanded default ignores for Rust projects:
  - `target/`, `Cargo.lock`, build artifacts
  - PHP, Ruby, Go, Elixir, Haskell: package manager lock files

To @boralg for helping curate Rust-specific patterns!

# How to Update
```bash
npm update -g repomix
```

---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.
````

v0.2.23
````md
This release adds significant performance improvements for large repositories, making Repomix faster and more efficient when needed.

# Improvements ⚡

## Parallel Processing Enhancement (#309)
- Implemented worker threads using [Piscina](https://github.com/piscinajs/piscina) for parallel processing

### Benchmark Results
- `yamadashy.repomix`: No significant change
  - Before: 868.73 millis
  - After: 671.26 millis
- `facebook/react`: 29x faster
  - Before: 123.31 secs
  - After: 4.19 secs
- `vercel/next.js`: 58x faster
  - Before: 17.85 mins
  - After: 17.27 secs

Note: While Repomix is not primarily designed for processing large repositories, and speed is not a primary goal, faster processing can provide a better user experience when working with larger codebases.

# How to Update

```bash
npm update -g repomix
```


---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.
````

v0.2.22
````md
This release introduces significant improvements to large file handling and expands the Repomix ecosystem with new tools and community channels.

# Improvements ⚡ 

## Improved Large File Handling (#302)

- Added a file size limit check (50MB) to prevent memory issues
- Graceful error handling for large files with clear user guidance:

Special thanks to @slavashvets for their continued contributions!

# Ecosystem Growth 🤝 

## New VS Code Extension (#300)
A community-created VS Code extension "Repomix Runner" is now available:
- Run Repomix directly from VS Code
- Extension by @massdo: [View on VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner)

Thank you @massdo for bringing Repomix to VS Code and expanding our tooling ecosystem!

## Official Social Media
- Launched official Repomix X (Twitter) account: [@repomix_ai](https://x.com/repomix_ai)
  - Follow for updates, tips, and community highlights

# How to Update

```bash
npm update -g repomix
```

---

Join our growing community on [Discord](https://discord.gg/BF8GxZHE2C) and follow us on [X](https://x.com/repomix_ai) for updates!
````

v0.2.21
````md
This release introduces significant improvements to output formatting and documentation, featuring a new parsable style option for enhanced XML handling.

# What's New 🚀 

## Enhanced Output Style Control (#287)
- Added new `parsableStyle` option for better output handling:
  - Ensures output strictly follows the specification of the chosen format
  - Provides properly escaped XML output with fast-xml-parser
  - Dynamically adjusts markdown code block delimiters to avoid content conflicts
- Available via CLI flag `--parsable-style` or in configuration file

Special thanks to @atollk for their first contribution!

# Documentation 📚

## README Enhancements (#296)
- Updated Homebrew installation documentation to include Linux support

Special thanks to @chenrui333 for their continued contributions!

## Website Multi-Language Support (#293)
- Enhanced multi-language support in [repomix.com](https://repomix.com)

# How to Update

To update to the latest version, run:
```bash
npm update -g repomix
```


---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.
</file>

<file path="src/cli/actions/mcpAction.ts">
import { runMcpServer } from '../../mcp/mcpServer.js';
import { logger } from '../../shared/logger.js';

export const runMcpAction = async (): Promise<void> => {
  logger.trace('Starting Repomix MCP server...');
  await runMcpServer();
};
</file>

<file path="src/cli/actions/remoteAction.ts">
import * as fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import GitUrlParse, { type GitUrl } from 'git-url-parse';
import pc from 'picocolors';
import { execGitShallowClone, isGitInstalled } from '../../core/file/gitCommand.js';
import { RepomixError } from '../../shared/errorHandle.js';
import { logger } from '../../shared/logger.js';
import { Spinner } from '../cliSpinner.js';
import type { CliOptions } from '../types.js';
import { type DefaultActionRunnerResult, runDefaultAction } from './defaultAction.js';
interface IGitUrl extends GitUrl {
  commit: string | undefined;
}
export const runRemoteAction = async (
  repoUrl: string,
  cliOptions: CliOptions,
  deps = {
    isGitInstalled,
    execGitShallowClone,
    runDefaultAction,
  },
): Promise<DefaultActionRunnerResult> => {
  if (!(await deps.isGitInstalled())) {
    throw new RepomixError('Git is not installed or not in the system PATH.');
  }

  const parsedFields = parseRemoteValue(repoUrl);
  const spinner = new Spinner('Cloning repository...', cliOptions);
  const tempDirPath = await createTempDirectory();
  let result: DefaultActionRunnerResult;

  try {
    spinner.start();

    // Clone the repository
    await cloneRepository(parsedFields.repoUrl, tempDirPath, cliOptions.remoteBranch || parsedFields.remoteBranch, {
      execGitShallowClone: deps.execGitShallowClone,
    });

    spinner.succeed('Repository cloned successfully!');
    logger.log('');

    // Run the default action on the cloned repository
    result = await deps.runDefaultAction([tempDirPath], tempDirPath, cliOptions);
    await copyOutputToCurrentDirectory(tempDirPath, process.cwd(), result.config.output.filePath);
  } catch (error) {
    spinner.fail('Error during repository cloning. cleanup...');
    throw error;
  } finally {
    // Cleanup the temporary directory
    await cleanupTempDirectory(tempDirPath);
  }

  return result;
};

// Check the short form of the GitHub URL. e.g. yamadashy/repomix
const VALID_NAME_PATTERN = '[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?';
const validShorthandRegex = new RegExp(`^${VALID_NAME_PATTERN}/${VALID_NAME_PATTERN}$`);
export const isValidShorthand = (remoteValue: string): boolean => {
  return validShorthandRegex.test(remoteValue);
};

export const parseRemoteValue = (remoteValue: string): { repoUrl: string; remoteBranch: string | undefined } => {
  if (isValidShorthand(remoteValue)) {
    logger.trace(`Formatting GitHub shorthand: ${remoteValue}`);
    return {
      repoUrl: `https://github.com/${remoteValue}.git`,
      remoteBranch: undefined,
    };
  }

  try {
    const parsedFields = GitUrlParse(remoteValue) as IGitUrl;

    // This will make parsedFields.toString() automatically append '.git' to the returned url
    parsedFields.git_suffix = true;

    const ownerSlashRepo =
      parsedFields.full_name.split('/').length > 1 ? parsedFields.full_name.split('/').slice(-2).join('/') : '';

    if (ownerSlashRepo !== '' && !isValidShorthand(ownerSlashRepo)) {
      throw new RepomixError('Invalid owner/repo in repo URL');
    }

    const repoUrl = parsedFields.toString(parsedFields.protocol);

    if (parsedFields.ref) {
      return {
        repoUrl: repoUrl,
        remoteBranch: parsedFields.filepath ? `${parsedFields.ref}/${parsedFields.filepath}` : parsedFields.ref,
      };
    }

    if (parsedFields.commit) {
      return {
        repoUrl: repoUrl,
        remoteBranch: parsedFields.commit,
      };
    }

    return {
      repoUrl: repoUrl,
      remoteBranch: undefined,
    };
  } catch (error) {
    throw new RepomixError('Invalid remote repository URL or repository shorthand (owner/repo)');
  }
};

export const isValidRemoteValue = (remoteValue: string): boolean => {
  try {
    parseRemoteValue(remoteValue);
    return true;
  } catch (error) {
    return false;
  }
};

export const createTempDirectory = async (): Promise<string> => {
  const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'repomix-'));
  logger.trace(`Created temporary directory. (path: ${pc.dim(tempDir)})`);
  return tempDir;
};

export const cloneRepository = async (
  url: string,
  directory: string,
  remoteBranch?: string,
  deps = {
    execGitShallowClone,
  },
): Promise<void> => {
  logger.log(`Clone repository: ${url} to temporary directory. ${pc.dim(`path: ${directory}`)}`);
  logger.log('');

  try {
    await deps.execGitShallowClone(url, directory, remoteBranch);
  } catch (error) {
    throw new RepomixError(`Failed to clone repository: ${(error as Error).message}`);
  }
};

export const cleanupTempDirectory = async (directory: string): Promise<void> => {
  logger.trace(`Cleaning up temporary directory: ${directory}`);
  await fs.rm(directory, { recursive: true, force: true });
};

export const copyOutputToCurrentDirectory = async (
  sourceDir: string,
  targetDir: string,
  outputFileName: string,
): Promise<void> => {
  const sourcePath = path.resolve(sourceDir, outputFileName);
  const targetPath = path.resolve(targetDir, outputFileName);

  try {
    logger.trace(`Copying output file from: ${sourcePath} to: ${targetPath}`);

    // Create target directory if it doesn't exist
    await fs.mkdir(path.dirname(targetPath), { recursive: true });

    await fs.copyFile(sourcePath, targetPath);
  } catch (error) {
    throw new RepomixError(`Failed to copy output file: ${(error as Error).message}`);
  }
};
</file>

<file path="src/config/configSchema.ts">
import type { TiktokenEncoding } from 'tiktoken';
import { z } from 'zod';

// Output style enum
export const repomixOutputStyleSchema = z.enum(['xml', 'markdown', 'plain']);
export type RepomixOutputStyle = z.infer<typeof repomixOutputStyleSchema>;

// Default values map
export const defaultFilePathMap: Record<RepomixOutputStyle, string> = {
  xml: 'repomix-output.xml',
  markdown: 'repomix-output.md',
  plain: 'repomix-output.txt',
} as const;

// Base config schema
export const repomixConfigBaseSchema = z.object({
  output: z
    .object({
      filePath: z.string().optional(),
      style: repomixOutputStyleSchema.optional(),
      parsableStyle: z.boolean().optional(),
      headerText: z.string().optional(),
      instructionFilePath: z.string().optional(),
      fileSummary: z.boolean().optional(),
      directoryStructure: z.boolean().optional(),
      removeComments: z.boolean().optional(),
      removeEmptyLines: z.boolean().optional(),
      compress: z.boolean().optional(),
      topFilesLength: z.number().optional(),
      showLineNumbers: z.boolean().optional(),
      copyToClipboard: z.boolean().optional(),
      includeEmptyDirectories: z.boolean().optional(),
      git: z
        .object({
          sortByChanges: z.boolean().optional(),
          sortByChangesMaxCommits: z.number().optional(),
        })
        .optional(),
    })
    .optional(),
  include: z.array(z.string()).optional(),
  ignore: z
    .object({
      useGitignore: z.boolean().optional(),
      useDefaultPatterns: z.boolean().optional(),
      customPatterns: z.array(z.string()).optional(),
    })
    .optional(),
  security: z
    .object({
      enableSecurityCheck: z.boolean().optional(),
    })
    .optional(),
  tokenCount: z
    .object({
      encoding: z.string().optional(),
    })
    .optional(),
});

// Default config schema with default values
export const repomixConfigDefaultSchema = z.object({
  output: z
    .object({
      filePath: z.string().default(defaultFilePathMap.xml),
      style: repomixOutputStyleSchema.default('xml'),
      parsableStyle: z.boolean().default(false),
      headerText: z.string().optional(),
      instructionFilePath: z.string().optional(),
      fileSummary: z.boolean().default(true),
      directoryStructure: z.boolean().default(true),
      removeComments: z.boolean().default(false),
      removeEmptyLines: z.boolean().default(false),
      compress: z.boolean().default(false),
      topFilesLength: z.number().int().min(0).default(5),
      showLineNumbers: z.boolean().default(false),
      copyToClipboard: z.boolean().default(false),
      includeEmptyDirectories: z.boolean().optional(),
      git: z
        .object({
          sortByChanges: z.boolean().default(true),
          sortByChangesMaxCommits: z.number().int().min(1).default(100),
        })
        .default({}),
    })
    .default({}),
  include: z.array(z.string()).default([]),
  ignore: z
    .object({
      useGitignore: z.boolean().default(true),
      useDefaultPatterns: z.boolean().default(true),
      customPatterns: z.array(z.string()).default([]),
    })
    .default({}),
  security: z
    .object({
      enableSecurityCheck: z.boolean().default(true),
    })
    .default({}),
  tokenCount: z
    .object({
      encoding: z
        .string()
        .default('o200k_base')
        .transform((val) => val as TiktokenEncoding),
    })
    .default({}),
});

export const repomixConfigFileSchema = repomixConfigBaseSchema;

export const repomixConfigCliSchema = repomixConfigBaseSchema;

export const repomixConfigMergedSchema = repomixConfigDefaultSchema
  .and(repomixConfigFileSchema)
  .and(repomixConfigCliSchema)
  .and(
    z.object({
      cwd: z.string(),
    }),
  );

export type RepomixConfigDefault = z.infer<typeof repomixConfigDefaultSchema>;
export type RepomixConfigFile = z.infer<typeof repomixConfigFileSchema>;
export type RepomixConfigCli = z.infer<typeof repomixConfigCliSchema>;
export type RepomixConfigMerged = z.infer<typeof repomixConfigMergedSchema>;

export const defaultConfig = repomixConfigDefaultSchema.parse({});
</file>

<file path="src/config/defaultIgnore.ts">
export const defaultIgnoreList = [
  // Version control
  '.git/**',
  '.hg/**',
  '.hgignore',
  '.svn/**',

  // Dependency directories
  '**/node_modules/**',
  '**/bower_components/**',
  '**/jspm_packages/**',
  'vendor/**',
  '**/.bundle/**',
  '**/.gradle/**',
  'target/**',

  // Logs
  'logs/**',
  '**/*.log',
  '**/npm-debug.log*',
  '**/yarn-debug.log*',
  '**/yarn-error.log*',

  // Runtime data
  'pids/**',
  '*.pid',
  '*.seed',
  '*.pid.lock',

  // Directory for instrumented libs generated by jscoverage/JSCover
  'lib-cov/**',

  // Coverage directory used by tools like istanbul
  'coverage/**',

  // nyc test coverage
  '.nyc_output/**',

  // Grunt intermediate storage
  '.grunt/**',

  // node-waf configuration
  '.lock-wscript',

  // Compiled binary addons
  'build/Release/**',

  // TypeScript v1 declaration files
  'typings/**',

  // Optional npm cache directory
  '**/.npm/**',

  // Cache directories
  '.eslintcache',
  '.rollup.cache/**',
  '.webpack.cache/**',
  '.parcel-cache/**',
  '.sass-cache/**',
  '*.cache',

  // Optional REPL history
  '.node_repl_history',

  // Output of 'npm pack'
  '*.tgz',

  // Yarn files
  '**/.yarn/**',

  // Yarn Integrity file
  '**/.yarn-integrity',

  // dotenv environment variables file
  '.env',

  // next.js build output
  '.next/**',

  // nuxt.js build output
  '.nuxt/**',

  // vuepress build output
  '.vuepress/dist/**',

  // Serverless directories
  '.serverless/**',

  // FuseBox cache
  '.fusebox/**',

  // DynamoDB Local files
  '.dynamodb/**',

  // TypeScript output
  'dist/**',

  // OS generated files
  '**/.DS_Store',
  '**/Thumbs.db',

  // Editor directories and files
  '.idea/**',
  '.vscode/**',
  '**/*.swp',
  '**/*.swo',
  '**/*.swn',
  '**/*.bak',

  // Build outputs
  'build/**',
  'out/**',

  // Temporary files
  'tmp/**',
  'temp/**',

  // repomix output
  '**/repomix-output.*',
  '**/repopack-output.*', // Legacy

  // Essential Node.js-related entries
  '**/package-lock.json',
  '**/yarn-error.log',
  '**/yarn.lock',
  '**/pnpm-lock.yaml',
  '**/bun.lockb',
  '**/bun.lock',

  // Essential Python-related entries
  '**/__pycache__/**',
  '**/*.py[cod]',
  '**/venv/**',
  '**/.venv/**',
  '**/.pytest_cache/**',
  '**/.mypy_cache/**',
  '**/.ipynb_checkpoints/**',
  '**/Pipfile.lock',
  '**/poetry.lock',
  '**/uv.lock',

  // Essential Rust-related entries
  '**/Cargo.lock',
  '**/Cargo.toml.orig',
  '**/target/**',
  '**/*.rs.bk',

  // Essential PHP-related entries
  '**/composer.lock',

  // Essential Ruby-related entries
  '**/Gemfile.lock',

  // Essential Go-related entries
  '**/go.sum',

  // Essential Elixir-related entries
  '**/mix.lock',

  // Essential Haskell-related entries
  '**/stack.yaml.lock',
  '**/cabal.project.freeze',
];
</file>

<file path="src/core/treeSitter/parseStrategies/ParseStrategy.ts">
import type { Query, SyntaxNode, Tree } from 'web-tree-sitter';
import type { RepomixConfigMerged } from '../../../config/configSchema.js';
import type { SupportedLang } from '../lang2Query.js';
import { CssParseStrategy } from './CssParseStrategy.js';
import { DefaultParseStrategy } from './DefaultParseStrategy.js';
import { GoParseStrategy } from './GoParseStrategy.js';
import { PythonParseStrategy } from './PythonParseStrategy.js';
import { TypeScriptParseStrategy } from './TypeScriptParseStrategy.js';
import { VueParseStrategy } from './VueParseStrategy.js';

export interface ParseContext {
  fileContent: string;
  lines: string[];
  tree: Tree;
  query: Query;
  config: RepomixConfigMerged;
}

export interface ParseStrategy {
  parseCapture(
    capture: { node: SyntaxNode; name: string },
    lines: string[],
    processedChunks: Set<string>,
    context: ParseContext,
  ): string | null;
}

export function createParseStrategy(lang: SupportedLang): ParseStrategy {
  switch (lang) {
    case 'typescript':
      return new TypeScriptParseStrategy();
    case 'python':
      return new PythonParseStrategy();
    case 'go':
      return new GoParseStrategy();
    case 'css':
      return new CssParseStrategy();
    case 'vue':
      return new VueParseStrategy();
    default:
      return new DefaultParseStrategy();
  }
}
</file>

<file path="src/mcp/tools/packCodebaseTool.ts">
import path from 'node:path';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { runCli } from '../../cli/cliRun.js';
import type { CliOptions } from '../../cli/types.js';
import { createToolWorkspace, formatToolError, formatToolResponse } from './mcpToolRuntime.js';

export const registerPackCodebaseTool = (mcpServer: McpServer) => {
  mcpServer.tool(
    'pack_codebase',
    'Package local code directory into a consolidated file for AI analysis',
    {
      directory: z.string().describe('Directory to pack (Absolute path)'),
      compress: z
        .boolean()
        .default(true)
        .describe(
          'Utilize Tree-sitter to intelligently extract essential code signatures and structure while removing implementation details, significantly reducing token usage (default: true)',
        ),
      includePatterns: z
        .string()
        .optional()
        .describe(
          'Specify which files to include using fast-glob compatible patterns (e.g., "**/*.js,src/**"). Only files matching these patterns will be processed. It is recommended to pack only necessary files.',
        ),
      ignorePatterns: z
        .string()
        .optional()
        .describe(
          'Specify additional files to exclude using fast-glob compatible patterns (e.g., "test/**,*.spec.js"). These patterns complement .gitignore and default ignores. It is recommended to pack only necessary files.',
        ),
      topFilesLength: z
        .number()
        .optional()
        .default(10)
        .describe('Number of top files to display in the metrics (default: 10)'),
    },
    async ({ directory, compress, includePatterns, ignorePatterns, topFilesLength }): Promise<CallToolResult> => {
      let tempDir = '';

      try {
        tempDir = await createToolWorkspace();
        const outputFilePath = path.join(tempDir, 'repomix-output.xml');

        const cliOptions = {
          compress,
          include: includePatterns,
          ignore: ignorePatterns,
          output: outputFilePath,
          style: 'xml',
          securityCheck: true,
          topFilesLen: topFilesLength,
          quiet: true,
        } as CliOptions;

        const result = await runCli(['.'], directory, cliOptions);
        if (!result) {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: JSON.stringify(
                  {
                    success: false,
                    error: 'Failed to return a result',
                  },
                  null,
                  2,
                ),
              },
            ],
          };
        }

        // Extract metrics information from the pack result
        const { packResult } = result;

        return formatToolResponse({ directory }, packResult, outputFilePath, topFilesLength);
      } catch (error) {
        return formatToolError(error);
      }
    },
  );
};
</file>

<file path="src/mcp/tools/packRemoteRepositoryTool.ts">
import path from 'node:path';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { runCli } from '../../cli/cliRun.js';
import type { CliOptions } from '../../cli/types.js';
import { createToolWorkspace, formatToolError, formatToolResponse } from './mcpToolRuntime.js';

export const registerPackRemoteRepositoryTool = (mcpServer: McpServer) => {
  mcpServer.tool(
    'pack_remote_repository',
    'Fetch, clone and package a GitHub repository into a consolidated file for AI analysis',
    {
      remote: z.string().describe('GitHub repository URL or user/repo (e.g., yamadashy/repomix)'),
      compress: z
        .boolean()
        .default(true)
        .describe(
          'Utilize Tree-sitter to intelligently extract essential code signatures and structure while removing implementation details, significantly reducing token usage (default: true)',
        ),
      includePatterns: z
        .string()
        .optional()
        .describe(
          'Specify which files to include using fast-glob compatible patterns (e.g., "**/*.js,src/**"). Only files matching these patterns will be processed. It is recommended to pack only necessary files.',
        ),
      ignorePatterns: z
        .string()
        .optional()
        .describe(
          'Specify additional files to exclude using fast-glob compatible patterns (e.g., "test/**,*.spec.js"). These patterns complement .gitignore and default ignores. It is recommended to pack only necessary files.',
        ),
      topFilesLength: z
        .number()
        .optional()
        .default(10)
        .describe('Number of top files to display in the metrics (default: 10)'),
    },
    async ({ remote, compress, includePatterns, ignorePatterns, topFilesLength }): Promise<CallToolResult> => {
      let tempDir = '';

      try {
        tempDir = await createToolWorkspace();
        const outputFilePath = path.join(tempDir, 'repomix-output.xml');

        const cliOptions = {
          remote,
          compress,
          include: includePatterns,
          ignore: ignorePatterns,
          output: outputFilePath,
          style: 'xml',
          securityCheck: true,
          topFilesLen: topFilesLength,
          quiet: true,
        } as CliOptions;

        const result = await runCli(['.'], process.cwd(), cliOptions);
        if (!result) {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: 'Failed to return a result',
              },
            ],
          };
        }

        // Extract metrics information from the pack result
        const { packResult } = result;

        return formatToolResponse({ repository: remote }, packResult, outputFilePath, topFilesLength);
      } catch (error) {
        return formatToolError(error);
      }
    },
  );
};
</file>

<file path="tests/cli/actions/defaultAction.test.ts">
import process from 'node:process';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { runDefaultAction } from '../../../src/cli/actions/defaultAction.js';
import type { CliOptions } from '../../../src/cli/types.js';
import * as configLoader from '../../../src/config/configLoad.js';
import * as packageJsonParser from '../../../src/core/file/packageJsonParse.js';
import * as packager from '../../../src/core/packager.js';

vi.mock('../../../src/core/packager');
vi.mock('../../../src/config/configLoad');
vi.mock('../../../src/core/file/packageJsonParse');
vi.mock('../../../src/shared/logger');

describe('defaultAction', () => {
  beforeEach(() => {
    vi.resetAllMocks();
    vi.mocked(packageJsonParser.getVersion).mockResolvedValue('1.0.0');
    vi.mocked(configLoader.loadFileConfig).mockResolvedValue({});
    vi.mocked(configLoader.mergeConfigs).mockReturnValue({
      cwd: process.cwd(),
      output: {
        filePath: 'output.txt',
        style: 'plain',
        parsableStyle: false,
        fileSummary: true,
        directoryStructure: true,
        topFilesLength: 5,
        showLineNumbers: false,
        removeComments: false,
        removeEmptyLines: false,
        compress: false,
        copyToClipboard: false,
        git: {
          sortByChanges: true,
          sortByChangesMaxCommits: 100,
        },
      },
      ignore: {
        useGitignore: true,
        useDefaultPatterns: true,
        customPatterns: [],
      },
      include: [],
      security: {
        enableSecurityCheck: true,
      },
      tokenCount: {
        encoding: 'o200k_base',
      },
    });
    vi.mocked(packager.pack).mockResolvedValue({
      totalFiles: 10,
      totalCharacters: 1000,
      totalTokens: 200,
      fileCharCounts: {},
      fileTokenCounts: {},
      suspiciousFilesResults: [],
    });
  });

  afterEach(() => {
    vi.resetAllMocks();
  });

  it('should run the default command successfully', async () => {
    const options: CliOptions = {
      output: 'custom-output.txt',
      verbose: true,
    };

    await runDefaultAction(['.'], process.cwd(), options);

    expect(configLoader.loadFileConfig).toHaveBeenCalled();
    expect(configLoader.mergeConfigs).toHaveBeenCalled();
    expect(packager.pack).toHaveBeenCalled();
  });

  it('should handle custom include patterns', async () => {
    const options: CliOptions = {
      include: '*.js,*.ts',
    };

    await runDefaultAction(['.'], process.cwd(), options);

    expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
      process.cwd(),
      expect.anything(),
      expect.objectContaining({
        include: ['*.js', '*.ts'],
      }),
    );
  });

  it('should handle custom ignore patterns', async () => {
    const options: CliOptions = {
      ignore: 'node_modules,*.log',
    };

    await runDefaultAction(['.'], process.cwd(), options);

    expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
      process.cwd(),
      expect.anything(),
      expect.objectContaining({
        ignore: {
          customPatterns: ['node_modules', '*.log'],
        },
      }),
    );
  });

  it('should handle custom output style', async () => {
    const options: CliOptions = {
      style: 'xml',
    };

    await runDefaultAction(['.'], process.cwd(), options);

    expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
      process.cwd(),
      expect.anything(),
      expect.objectContaining({
        output: expect.objectContaining({
          style: 'xml',
        }),
      }),
    );
  });

  it('should handle errors gracefully', async () => {
    vi.mocked(packager.pack).mockRejectedValue(new Error('Test error'));

    const options: CliOptions = {};

    await expect(runDefaultAction(['.'], process.cwd(), options)).rejects.toThrow('Test error');
  });

  describe('parsableStyle flag', () => {
    it('should handle --parsable-style flag', async () => {
      const options: CliOptions = {
        parsableStyle: true,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          output: {
            parsableStyle: true,
          },
        }),
      );
    });

    it('should handle explicit --no-parsable-style flag', async () => {
      const options: CliOptions = {
        parsableStyle: false,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          output: {
            parsableStyle: false,
          },
        }),
      );
    });
  });

  describe('security check flag', () => {
    it('should handle --no-security-check flag', async () => {
      const options: CliOptions = {
        securityCheck: false,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          security: {
            enableSecurityCheck: false,
          },
        }),
      );
    });

    it('should handle explicit --security-check flag', async () => {
      const options: CliOptions = {
        securityCheck: true,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({}),
      );
    });
  });

  describe('gitignore flag', () => {
    it('should handle explicit --no-gitignore flag', async () => {
      const options: CliOptions = {
        gitignore: false,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          ignore: {
            useGitignore: false,
          },
        }),
      );
    });

    it('should handle explicit --no-gitignore flag', async () => {
      const options: CliOptions = {
        gitignore: false,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({}),
      );
    });
  });

  describe('defaultPatterns flag', () => {
    it('should handle explicit --no-default-patterns flag', async () => {
      const options: CliOptions = {
        defaultPatterns: false,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          ignore: {
            useDefaultPatterns: false,
          },
        }),
      );
    });

    it('should handle explicit --no-default-patterns flag', async () => {
      const options: CliOptions = {
        defaultPatterns: false,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({}),
      );
    });
  });

  describe('fileSummary flag', () => {
    it('should handle --no-file-summary flag', async () => {
      const options: CliOptions = {
        fileSummary: false,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          output: {
            fileSummary: false,
          },
        }),
      );
    });

    it('should handle explicit --file-summary flag', async () => {
      const options: CliOptions = {
        fileSummary: true,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({}),
      );
    });
  });

  describe('directoryStructure flag', () => {
    it('should handle --no-directory-structure flag', async () => {
      const options: CliOptions = {
        directoryStructure: false,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          output: {
            directoryStructure: false,
          },
        }),
      );
    });

    it('should handle explicit --directory-structure flag', async () => {
      const options: CliOptions = {
        directoryStructure: true,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({}),
      );
    });
  });

  describe('removeComments flag', () => {
    it('should handle --remove-comments flag', async () => {
      const options: CliOptions = {
        removeComments: true,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          output: {
            removeComments: true,
          },
        }),
      );
    });

    it('should handle explicit --no-remove-comments flag', async () => {
      const options: CliOptions = {
        removeComments: false,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          output: {
            removeComments: false,
          },
        }),
      );
    });
  });

  describe('removeEmptyLines flag', () => {
    it('should handle --remove-empty-lines flag', async () => {
      const options: CliOptions = {
        removeEmptyLines: true,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          output: {
            removeEmptyLines: true,
          },
        }),
      );
    });

    it('should handle explicit --no-remove-empty-lines flag', async () => {
      const options: CliOptions = {
        removeEmptyLines: false,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          output: {
            removeEmptyLines: false,
          },
        }),
      );
    });
  });

  describe('headerText flag', () => {
    it('should handle --header-text flag', async () => {
      const options: CliOptions = {
        headerText: 'Another header text',
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          output: {
            headerText: 'Another header text',
          },
        }),
      );
    });
  });

  describe('instructionFilePath flag', () => {
    it('should handle --instruction-file-path flag', async () => {
      const options: CliOptions = {
        instructionFilePath: 'path/to/instruction.txt',
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          output: {
            instructionFilePath: 'path/to/instruction.txt',
          },
        }),
      );
    });
  });

  describe('includeEmptyDirectories flag', () => {
    it('should handle --include-empty-directories flag', async () => {
      const options: CliOptions = {
        includeEmptyDirectories: true,
      };

      await runDefaultAction(['.'], process.cwd(), options);

      expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
        process.cwd(),
        expect.anything(),
        expect.objectContaining({
          output: {
            includeEmptyDirectories: true,
          },
        }),
      );
    });
  });
});
</file>

<file path="tests/cli/actions/remoteAction.test.ts">
import * as fs from 'node:fs/promises';
import path from 'node:path';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import type { DefaultActionRunnerResult } from '../../../src/cli/actions/defaultAction.js';
import {
  copyOutputToCurrentDirectory,
  isValidRemoteValue,
  parseRemoteValue,
  runRemoteAction,
} from '../../../src/cli/actions/remoteAction.js';
import { createMockConfig } from '../../testing/testUtils.js';

vi.mock('node:fs/promises', async (importOriginal) => {
  const actual = await importOriginal<typeof import('node:fs/promises')>();
  return {
    ...actual,
    copyFile: vi.fn(),
    mkdir: vi.fn(),
  };
});
vi.mock('../../../src/shared/logger');

describe('remoteAction functions', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  describe('runRemoteAction', () => {
    test('should clone the repository', async () => {
      vi.mocked(fs.copyFile).mockResolvedValue(undefined);
      await runRemoteAction(
        'yamadashy/repomix',
        {},
        {
          isGitInstalled: async () => Promise.resolve(true),
          execGitShallowClone: async (url: string, directory: string) => {
            await fs.writeFile(path.join(directory, 'README.md'), 'Hello, world!');
          },
          runDefaultAction: async () => {
            return {
              packResult: {
                totalFiles: 1,
                totalCharacters: 1,
                totalTokens: 1,
                fileCharCounts: {},
                fileTokenCounts: {},
                suspiciousFilesResults: [],
              },
              config: createMockConfig(),
            } satisfies DefaultActionRunnerResult;
          },
        },
      );
    });
  });

  describe('parseRemoteValue', () => {
    test('should convert GitHub shorthand to full URL', () => {
      expect(parseRemoteValue('user/repo')).toEqual({
        repoUrl: 'https://github.com/user/repo.git',
        remoteBranch: undefined,
      });
      expect(parseRemoteValue('user-name/repo-name')).toEqual({
        repoUrl: 'https://github.com/user-name/repo-name.git',
        remoteBranch: undefined,
      });
      expect(parseRemoteValue('user_name/repo_name')).toEqual({
        repoUrl: 'https://github.com/user_name/repo_name.git',
        remoteBranch: undefined,
      });
      expect(parseRemoteValue('a.b/a-b_c')).toEqual({
        repoUrl: 'https://github.com/a.b/a-b_c.git',
        remoteBranch: undefined,
      });
    });

    test('should handle HTTPS URLs', () => {
      expect(parseRemoteValue('https://github.com/user/repo')).toEqual({
        repoUrl: 'https://github.com/user/repo.git',
        remoteBranch: undefined,
      });
      expect(parseRemoteValue('https://github.com/user/repo.git')).toEqual({
        repoUrl: 'https://github.com/user/repo.git',
        remoteBranch: undefined,
      });
    });

    test('should not modify SSH URLs', () => {
      const sshUrl = 'git@github.com:user/repo.git';
      const parsed = parseRemoteValue(sshUrl);
      expect(parsed).toEqual({
        repoUrl: sshUrl,
        remoteBranch: undefined,
      });
    });

    test('should get correct branch name from url', () => {
      expect(parseRemoteValue('https://github.com/username/repo/tree/branchname')).toEqual({
        repoUrl: 'https://github.com/username/repo.git',
        remoteBranch: 'branchname',
      });
      expect(parseRemoteValue('https://some.gitlab.domain/some/path/username/repo/-/tree/branchname')).toEqual({
        repoUrl: 'https://some.gitlab.domain/some/path/username/repo.git',
        remoteBranch: 'branchname',
      });
      expect(
        parseRemoteValue('https://some.gitlab.domain/some/path/username/repo/-/tree/branchname/withslash'),
      ).toEqual({
        repoUrl: 'https://some.gitlab.domain/some/path/username/repo.git',
        remoteBranch: 'branchname/withslash',
      });
    });

    test('should get correct commit hash from url', () => {
      expect(
        parseRemoteValue(
          'https://some.gitlab.domain/some/path/username/repo/commit/c482755296cce46e58f87d50f25f545c5d15be6f',
        ),
      ).toEqual({
        repoUrl: 'https://some.gitlab.domain/some/path/username/repo.git',
        remoteBranch: 'c482755296cce46e58f87d50f25f545c5d15be6f',
      });
    });
    test('should throw when the URL is invalid or harmful', () => {
      expect(() => parseRemoteValue('some random string')).toThrowError();
    });
  });

  describe('copyOutputToCurrentDirectory', () => {
    test('should copy output file', async () => {
      const sourceDir = '/source/dir';
      const targetDir = '/target/dir';
      const fileName = 'output.txt';

      vi.mocked(fs.copyFile).mockResolvedValue();

      await copyOutputToCurrentDirectory(sourceDir, targetDir, fileName);

      expect(fs.copyFile).toHaveBeenCalledWith(path.resolve(sourceDir, fileName), path.resolve(targetDir, fileName));
    });

    test('should throw error when copy fails', async () => {
      const sourceDir = '/source/dir';
      const targetDir = '/target/dir';
      const fileName = 'output.txt';

      vi.mocked(fs.copyFile).mockRejectedValue(new Error('Permission denied'));

      await expect(copyOutputToCurrentDirectory(sourceDir, targetDir, fileName)).rejects.toThrow(
        'Failed to copy output file',
      );
    });
  });

  describe('isValidRemoteValue', () => {
    describe('GitHub shorthand format (user/repo)', () => {
      test('should accept valid repository names', () => {
        // Test cases for valid repository names with various allowed characters
        const validUrls = [
          'user/repo',
          'user123/repo-name',
          'org-name/repo_name',
          'user.name/repo.test',
          'user_name/repo_test',
          'a/b', // Minimum length case
          'user-name123/repo-test123.sub_123', // Complex case
        ];

        for (const url of validUrls) {
          expect(isValidRemoteValue(url), `URL should be valid: ${url}`).toBe(true);
        }
      });

      test('should reject invalid repository names', () => {
        // Test cases for invalid patterns and disallowed characters
        const invalidUrls = [
          '', // Empty string
          'user', // Missing slash
          '/repo', // Missing username
          'user/', // Missing repository name
          '-user/repo', // Starts with hyphen
          'user/-repo', // Repository starts with hyphen
          'user./repo', // Username ends with dot
          'user/repo.', // Repository ends with dot
          'user/repo#branch', // Contains invalid character
          'user/repo/extra', // Extra path segment
          'us!er/repo', // Contains invalid character
          'user/re*po', // Contains invalid character
          'user//repo', // Double slash
          '.user/repo', // Starts with dot
          'user/.repo', // Repository starts with dot
        ];

        for (const url of invalidUrls) {
          expect(isValidRemoteValue(url), `URL should be invalid: ${url}`).toBe(false);
        }
      });
    });

    describe('Full URL format', () => {
      test('should accept valid URLs', () => {
        // Test cases for standard URL formats
        const validUrls = [
          'https://example.com',
          'http://localhost',
          'https://github.com/user/repo',
          'https://gitlab.com/user/repo',
          'https://domain.com/path/to/something',
        ];

        for (const url of validUrls) {
          expect(isValidRemoteValue(url), `URL should be valid: ${url}`).toBe(true);
        }
      });

      test('should reject invalid URLs', () => {
        // Test cases for malformed URLs
        const invalidUrls = ['not-a-url', 'http://', 'https://', '://no-protocol.com', 'http://[invalid]'];

        for (const url of invalidUrls) {
          expect(isValidRemoteValue(url), `URL should be invalid: ${url}`).toBe(false);
        }
      });
    });
  });
});
</file>

<file path="tests/core/treeSitter/parseFile.css.test.ts">
import { describe, expect, test } from 'vitest';
import type { RepomixConfigMerged } from '../../../src/config/configSchema.js';
import { parseFile } from '../../../src/core/treeSitter/parseFile.js';

describe('parseFile for CSS', () => {
  test('should parse CSS correctly', async () => {
    const fileContent = `
      /* Main styles for the application */
      body {
        font-family: 'Arial', sans-serif;
        line-height: 1.6;
        color: #333;
        background-color: #f4f4f4;
        margin: 0;
        padding: 0;
      }

      /* Header styles */
      .header {
        background-color: #35424a;
        color: #ffffff;
        padding: 20px;
        border-bottom: #e8491d 3px solid;
      }

      /* Navigation styles */
      .nav {
        display: flex;
        justify-content: space-between;
        align-items: center;
      }

      @media (max-width: 768px) {
        .nav {
          flex-direction: column;
        }
        
        .container {
          width: 95%;
        }
      }
    `;
    const filePath = 'style.css';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      // Comments (all lines should be extracted)
      'Main styles for the application',
      'Header styles',
      'Navigation styles',

      // Selectors (only the first line should be extracted)
      'body {',
      '.header {',
      '.nav {',

      // at-rules are not extracted with the current query, so removed from expectations
      // '@media (max-width: 768px) {',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }

    // Properties should not be extracted
    const unexpectedContents = [
      'font-family:',
      'line-height:',
      'color:',
      'background-color:',
      'margin:',
      'padding:',
      'display:',
      'justify-content:',
      'align-items:',
      'flex-direction:',
      'width:',
    ];

    for (const unexpectedContent of unexpectedContents) {
      expect(result).not.toContain(unexpectedContent);
    }
  });

  test('should handle different comment styles', async () => {
    const fileContent = `
      /* Single line comment */
      body { color: black; }

      /* Multi-line comment
         spanning multiple lines */
      .container { width: 100%; }
    `;
    const filePath = 'comments.css';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'Single line comment',
      'Multi-line comment\n         spanning multiple lines',
      'body {',
      '.container {',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }
  });

  test('should handle various at-rules', async () => {
    const fileContent = `
      @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
      
      @charset "UTF-8";
      
      @keyframes fadeIn {
        from { opacity: 0; }
        to { opacity: 1; }
      }
      
      @media screen and (min-width: 768px) {
        body { font-size: 16px; }
      }
    `;
    const filePath = 'at-rules.css';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    // Output the result for debugging
    console.log('At-Rules Parse Result:', result);

    // Skip testing at-rules as they are not extracted in the current implementation
    // Enable this test when at-rule extraction is implemented in the future

    // Inner rules should not be extracted
    const unexpectedContents = ['from { opacity: 0; }', 'to { opacity: 1; }'];

    for (const unexpectedContent of unexpectedContents) {
      expect(result).not.toContain(unexpectedContent);
    }
  });

  test('should handle complex selectors', async () => {
    const fileContent = `
      /* Complex selectors */
      body.dark-theme .container > div:first-child {
        color: white;
        background-color: #333;
      }
      
      .sidebar ul li a:hover,
      .sidebar ul li a:focus {
        text-decoration: underline;
        color: blue;
      }
      
      #main-content h1 + p::first-line {
        font-weight: bold;
        font-size: 1.2em;
      }
    `;
    const filePath = 'complex-selectors.css';
    const config = {};
    const result = await parseFile(fileContent, filePath, config as RepomixConfigMerged);
    expect(typeof result).toBe('string');

    const expectContents = [
      'Complex selectors',
      'body.dark-theme .container > div:first-child {',
      '.sidebar ul li a:hover,',
      '#main-content h1 + p::first-line {',
    ];

    for (const expectContent of expectContents) {
      expect(result).toContain(expectContent);
    }

    // Properties should not be extracted
    const unexpectedContents = [
      'color: white;',
      'background-color: #333;',
      'text-decoration: underline;',
      'color: blue;',
      'font-weight: bold;',
      'font-size: 1.2em;',
    ];

    for (const unexpectedContent of unexpectedContents) {
      expect(result).not.toContain(unexpectedContent);
    }
  });
});
</file>

<file path="website/client/src/de/guide/command-line-options.md">
# Kommandozeilenoptionen

## Grundlegende Optionen
- `-v, --version`: Zeigt die Version an

## Ausgabeoptionen
- `-o, --output <file>`: Ausgabedateiname (Standard: `repomix-output.txt`)
- `--style <type>`: Ausgabeformat (`plain`, `xml`, `markdown`) (Standard: `plain`)
- `--parsable-style`: Aktiviert parsbare Ausgabe basierend auf dem gewählten Formatschema (Standard: `false`)
- `--compress`: Führt eine intelligente Code-Extraktion durch, die sich auf Funktions- und Klassensignaturen konzentriert und Implementierungsdetails entfernt. Weitere Details und Beispiele finden Sie im [Code-Komprimierungsleitfaden](code-compress)
- `--output-show-line-numbers`: Fügt Zeilennummern hinzu (Standard: `false`)
- `--copy`: In die Zwischenablage kopieren (Standard: `false`)
- `--no-file-summary`: Deaktiviert die Dateizusammenfassung (Standard: `true`)
- `--no-directory-structure`: Deaktiviert die Verzeichnisstruktur (Standard: `true`)
- `--remove-comments`: Entfernt Kommentare (Standard: `false`)
- `--remove-empty-lines`: Entfernt leere Zeilen (Standard: `false`)
- `--header-text <text>`: Benutzerdefinierter Text für den Dateikopf
- `--instruction-file-path <path>`: Pfad zu einer Datei mit detaillierten benutzerdefinierten Anweisungen
- `--include-empty-directories`: Leere Verzeichnisse in die Ausgabe einbeziehen (Standard: `false`)

## Filteroptionen
- `--include <patterns>`: Einzuschließende Muster (durch Komma getrennt)
- `-i, --ignore <patterns>`: Zu ignorierende Muster (durch Komma getrennt)
- `--no-gitignore`: Deaktiviert die Verwendung der .gitignore-Datei
- `--no-default-patterns`: Deaktiviert Standardmuster

## Remote-Repository-Optionen
- `--remote <url>`: Remote-Repository verarbeiten
- `--remote-branch <name>`: Remote-Branch-Name, Tag oder Commit-Hash angeben (Standard ist der Standard-Branch des Repositories)

## Konfigurationsoptionen
- `-c, --config <path>`: Pfad zur benutzerdefinierten Konfigurationsdatei
- `--init`: Konfigurationsdatei erstellen
- `--global`: Globale Konfiguration verwenden

## Sicherheitsoptionen
- `--no-security-check`: Deaktiviert die Sicherheitsprüfung (Standard: `true`)

## Token-Zähloptionen
- `--token-count-encoding <encoding>`: Token-Zählkodierung festlegen (z.B. `o200k_base`, `cl100k_base`) (Standard: `o200k_base`)

## Weitere Optionen
- `--top-files-len <number>`: Anzahl der anzuzeigenden Top-Dateien (Standard: `5`)
- `--verbose`: Ausführliche Protokollierung aktivieren
- `--quiet`: Deaktiviert alle Ausgaben an stdout

## Beispiele

```bash
# Grundlegende Verwendung
repomix

# Benutzerdefinierte Ausgabe
repomix -o output.xml --style xml

# Benutzerdefinierte Ausgabe mit Komprimierung
repomix --compress

# Bestimmte Dateien verarbeiten
repomix --include "src/**/*.ts" --ignore "**/*.test.ts"

# Remote-Repository mit Branch
repomix --remote https://github.com/user/repo/tree/main

# Remote-Repository mit Commit
repomix --remote https://github.com/user/repo/commit/836abcd7335137228ad77feb28655d85712680f1

# Remote-Repository mit Kurzform
repomix --remote user/repo
```
</file>

<file path="website/client/src/de/guide/configuration.md">
# Konfiguration

## Schnellstart

Konfigurationsdatei erstellen:
```bash
repomix --init
```

## Konfigurationsdatei

`repomix.config.json`:
```json
{
  "output": {
    "filePath": "repomix-output.xml",
    "style": "xml",
    "parsableStyle": true,
    "compress": false,
    "headerText": "Benutzerdefinierter Kopftext",
    "instructionFilePath": "repomix-instruction.md",
    "fileSummary": true,
    "directoryStructure": true,
    "removeComments": false,
    "removeEmptyLines": false,
    "topFilesLength": 5,
    "showLineNumbers": false,
    "copyToClipboard": false,
    "includeEmptyDirectories": false,
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  },
  "include": ["**/*"],
  "ignore": {
    "useGitignore": true,
    "useDefaultPatterns": true,
    "customPatterns": ["tmp/", "*.log"]
  },
  "security": {
    "enableSecurityCheck": true
  }
}
```

## Globale Konfiguration

Globale Konfiguration erstellen:
```bash
repomix --init --global
```

Speicherort:
- Windows: `%LOCALAPPDATA%\Repomix\repomix.config.json`
- macOS/Linux: `~/.config/repomix/repomix.config.json`

## Ignore-Muster

Prioritätsreihenfolge:
1. CLI-Optionen (`--ignore`)
2. `.repomixignore`
3. `.gitignore` und `.git/info/exclude`
4. Standard-Muster

Beispiel für `.repomixignore`:
```text
# Cache-Verzeichnisse
.cache/
tmp/

# Build-Ausgaben
dist/
build/

# Logs
*.log
```

## Standard-Ignore-Muster

Standardmäßig enthaltene häufige Muster:
```text
node_modules/**
.git/**
coverage/**
dist/**
```

Vollständige Liste: [defaultIgnore.ts](https://github.com/yamadashy/repomix/blob/main/src/config/defaultIgnore.ts)

## Beispiele

### Code-Komprimierung

Wenn `output.compress` auf `true` gesetzt ist, extrahiert Repomix wesentliche Code-Strukturen und entfernt dabei Implementierungsdetails. Dies reduziert die Token-Anzahl und behält gleichzeitig wichtige strukturelle Informationen bei.

Weitere Details und Beispiele finden Sie im [Code-Komprimierungs-Leitfaden](code-compress).

### Git-Integration

Die `output.git`-Konfiguration ermöglicht es Ihnen, die Sortierung von Dateien basierend auf der Git-Historie zu steuern:

- `sortByChanges`: Wenn auf `true` gesetzt, werden Dateien nach der Anzahl der Git-Änderungen (Commits, die die Datei geändert haben) sortiert. Dateien mit mehr Änderungen erscheinen am Ende der Ausgabe. Dies hilft dabei, aktiver entwickelte Dateien zu priorisieren. Standard: `true`
- `sortByChangesMaxCommits`: Die maximale Anzahl von Commits, die bei der Zählung der Dateiänderungen analysiert werden. Standard: `100`

Beispielkonfiguration:
```json
{
  "output": {
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  }
}
```

### Kommentarentfernung

Wenn `output.removeComments` auf `true` gesetzt ist, werden Kommentare aus unterstützten Dateitypen entfernt, um die Ausgabegröße zu reduzieren und sich auf den wesentlichen Code-Inhalt zu konzentrieren.

Unterstützte Sprachen und detaillierte Beispiele finden Sie im [Kommentarentfernungs-Leitfaden](comment-removal).
</file>

<file path="website/client/src/en/guide/command-line-options.md">
# Command Line Options

## Basic Options
- `-v, --version`: Show tool version

## Output Options
- `-o, --output <file>`: Output file name (default: `repomix-output.txt`)
- `--style <type>`: Output style (`plain`, `xml`, `markdown`) (default: `plain`)
- `--parsable-style`: Enable parsable output based on the chosen style schema (default: `false`)
- `--compress`: Perform intelligent code extraction, focusing on essential function and class signatures while removing implementation details. For more details and examples, see [Code Compression Guide](code-compress).
- `--output-show-line-numbers`: Add line numbers (default: `false`)
- `--copy`: Copy to clipboard (default: `false`)
- `--no-file-summary`: Disable file summary (default: `true`)
- `--no-directory-structure`: Disable directory structure (default: `true`)
- `--remove-comments`: Remove comments (default: `false`)
- `--remove-empty-lines`: Remove empty lines (default: `false`)
- `--header-text <text>`: Custom text to include in the file header
- `--instruction-file-path <path>`: Path to a file containing detailed custom instructions
- `--include-empty-directories`: Include empty directories in the output (default: `false`)

## Filter Options
- `--include <patterns>`: Include patterns (comma-separated)
- `-i, --ignore <patterns>`: Ignore patterns (comma-separated)
- `--no-gitignore`: Disable .gitignore file usage
- `--no-default-patterns`: Disable default patterns

## Remote Repository Options
- `--remote <url>`: Process remote repository
- `--remote-branch <name>`: Specify the remote branch name, tag, or commit hash (defaults to repository default branch)

## Configuration Options
- `-c, --config <path>`: Custom config file path
- `--init`: Create config file
- `--global`: Use global config

## Security Options
- `--no-security-check`: Disable security check (default: `true`)

## Token Count Options
- `--token-count-encoding <encoding>`: Specify token count encoding (e.g., `o200k_base`, `cl100k_base`) (default: `o200k_base`)

## Other Options
- `--top-files-len <number>`: Number of top files to show (default: `5`)
- `--verbose`: Enable verbose logging
- `--quiet`: Disable all output to stdout

## Examples

```bash
# Basic usage
repomix

# Custom output
repomix -o output.xml --style xml

# Custom output with compression
repomix --compress

# Process specific files
repomix --include "src/**/*.ts" --ignore "**/*.test.ts"

# Remote repository with branch
repomix --remote https://github.com/user/repo/tree/main

# Remote repository with commit
repomix --remote https://github.com/user/repo/commit/836abcd7335137228ad77feb28655d85712680f1

# Remote repository with shorthand
repomix --remote user/repo
```
</file>

<file path="website/client/src/en/guide/configuration.md">
# Configuration

## Quick Start

Create configuration file:
```bash
repomix --init
```

## Configuration File

`repomix.config.json`:
```json
{
  "output": {
    "filePath": "repomix-output.xml",
    "style": "xml",
    "parsableStyle": true,
    "compress": false,
    "headerText": "Custom header text",
    "instructionFilePath": "repomix-instruction.md",
    "fileSummary": true,
    "directoryStructure": true,
    "removeComments": false,
    "removeEmptyLines": false,
    "topFilesLength": 5,
    "showLineNumbers": false,
    "copyToClipboard": false,
    "includeEmptyDirectories": false,
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  },
  "include": ["**/*"],
  "ignore": {
    "useGitignore": true,
    "useDefaultPatterns": true,
    "customPatterns": ["tmp/", "*.log"]
  },
  "security": {
    "enableSecurityCheck": true
  }
}
```

## Global Configuration

Create global configuration:
```bash
repomix --init --global
```

Location:
- Windows: `%LOCALAPPDATA%\Repomix\repomix.config.json`
- macOS/Linux: `~/.config/repomix/repomix.config.json`

## Ignore Patterns

Priority:
1. CLI options (`--ignore`)
2. `.repomixignore`
3. `.gitignore` and `.git/info/exclude`
4. Default patterns

`.repomixignore` example:
```text
# Cache directories
.cache/
tmp/

# Build outputs
dist/
build/

# Logs
*.log
```

## Default Ignore Patterns

Common patterns included by default:
```text
node_modules/**
.git/**
coverage/**
dist/**
```

Full list: [defaultIgnore.ts](https://github.com/yamadashy/repomix/blob/main/src/config/defaultIgnore.ts)

## Examples

### Code Compression

When `output.compress` is set to `true`, Repomix will intelligently extract essential code structures while removing implementation details. This helps reduce token count while maintaining important structural information.

For more details and examples, see [Code Compression Guide](code-compress).

### Git Integration

The `output.git` configuration allows you to control how files are sorted based on Git history:

- `sortByChanges`: When set to `true`, files are sorted by the number of Git changes (commits that modified the file). Files with more changes appear at the bottom of the output. This can help prioritize more actively developed files. Default: `true`
- `sortByChangesMaxCommits`: The maximum number of commits to analyze when counting file changes. Default: `100`

Example configuration:
```json
{
  "output": {
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  }
}
```

### Comment Removal

When `output.removeComments` is set to `true`, Repomix will remove comments from supported file types to reduce the output size and focus on essential code content.

For supported languages and detailed examples, see [Comment Removal Guide](comment-removal).
</file>

<file path="website/client/src/en/guide/usage.md">
# Basic Usage

## Quick Start

Pack your entire repository:
```bash
repomix
```

## Common Use Cases

### Pack Specific Directories
```bash
repomix path/to/directory
```

### Include Specific Files
Use [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax):
```bash
repomix --include "src/**/*.ts,**/*.md"
```

### Exclude Files
```bash
repomix --ignore "**/*.log,tmp/"
```

### Remote Repositories
```bash
# Using GitHub URL
repomix --remote https://github.com/user/repo

# Using shorthand
repomix --remote user/repo

# Specific branch/tag/commit
repomix --remote user/repo --remote-branch main
repomix --remote user/repo --remote-branch 935b695
```


### Code Compression

```bash
repomix --compress

# You can also use it with remote repositories:
repomix --remote yamadashy/repomix --compress
```

## Output Formats

### XML (Default)
```bash
repomix --style xml
```

### Markdown
```bash
repomix --style markdown
```

### Plain Text
```bash
repomix --style plain
```

## Additional Options

### Remove Comments
```bash
repomix --remove-comments
```

### Show Line Numbers
```bash
repomix --output-show-line-numbers
```

### Copy to Clipboard
```bash
repomix --copy
```

### Disable Security Check
```bash
repomix --no-security-check
```

## Configuration

Initialize configuration file:
```bash
repomix --init
```

See [Configuration Guide](/guide/configuration) for detailed options.
</file>

<file path="website/client/src/es/guide/command-line-options.md">
# Opciones de Línea de Comandos

## Opciones Básicas
- `-v, --version`: Muestra la versión

## Opciones de Salida
- `-o, --output <file>`: Nombre del archivo de salida (predeterminado: `repomix-output.txt`)
- `--style <type>`: Estilo de salida (`plain`, `xml`, `markdown`) (predeterminado: `plain`)
- `--parsable-style`: Habilita la salida analizable basada en el esquema del estilo elegido (predeterminado: `false`)
- `--compress`: Realiza una extracción inteligente de código, centrándose en las firmas de funciones y clases mientras elimina los detalles de implementación. Para más detalles y ejemplos, consulte la [Guía de Compresión de Código](code-compress)
- `--output-show-line-numbers`: Agrega números de línea (predeterminado: `false`)
- `--copy`: Copiar al portapapeles (predeterminado: `false`)
- `--no-file-summary`: Deshabilita el resumen de archivos (predeterminado: `true`)
- `--no-directory-structure`: Deshabilita la estructura de directorios (predeterminado: `true`)
- `--remove-comments`: Elimina comentarios (predeterminado: `false`)
- `--remove-empty-lines`: Elimina líneas vacías (predeterminado: `false`)
- `--header-text <text>`: Texto personalizado para incluir en el encabezado del archivo
- `--instruction-file-path <path>`: Ruta al archivo con instrucciones personalizadas detalladas
- `--include-empty-directories`: Incluye directorios vacíos en la salida (predeterminado: `false`)

## Opciones de Filtrado
- `--include <patterns>`: Patrones a incluir (separados por comas)
- `-i, --ignore <patterns>`: Patrones a ignorar (separados por comas)
- `--no-gitignore`: Deshabilita el uso del archivo .gitignore
- `--no-default-patterns`: Deshabilita los patrones predeterminados

## Opciones de Repositorio Remoto
- `--remote <url>`: Procesa repositorio remoto
- `--remote-branch <name>`: Especifica el nombre de la rama remota, etiqueta o hash de commit (por defecto es la rama principal del repositorio)

## Opciones de Configuración
- `-c, --config <path>`: Ruta del archivo de configuración personalizado
- `--init`: Crea archivo de configuración
- `--global`: Usa configuración global

## Opciones de Seguridad
- `--no-security-check`: Deshabilita la verificación de seguridad (predeterminado: `true`)

## Opciones de Conteo de Tokens
- `--token-count-encoding <encoding>`: Especifica la codificación para el conteo de tokens (ej. `o200k_base`, `cl100k_base`) (predeterminado: `o200k_base`)

## Otras Opciones
- `--top-files-len <number>`: Número de archivos principales a mostrar (predeterminado: `5`)
- `--verbose`: Habilita el registro detallado
- `--quiet`: Deshabilita toda la salida a stdout

## Ejemplos

```bash
# Uso básico
repomix

# Salida personalizada
repomix -o output.xml --style xml

# Salida personalizada con compresión
repomix --compress

# Procesar archivos específicos
repomix --include "src/**/*.ts" --ignore "**/*.test.ts"

# Repositorio remoto con rama
repomix --remote https://github.com/user/repo/tree/main

# Repositorio remoto con commit
repomix --remote https://github.com/user/repo/commit/836abcd7335137228ad77feb28655d85712680f1

# Repositorio remoto con formato abreviado
repomix --remote user/repo
```
</file>

<file path="website/client/src/es/guide/configuration.md">
# Configuración

## Inicio Rápido

Crear archivo de configuración:
```bash
repomix --init
```

## Archivo de Configuración

`repomix.config.json`:
```json
{
  "output": {
    "filePath": "repomix-output.xml",
    "style": "xml",
    "parsableStyle": true,
    "compress": false,
    "headerText": "Texto de encabezado personalizado",
    "instructionFilePath": "repomix-instruction.md",
    "fileSummary": true,
    "directoryStructure": true,
    "removeComments": false,
    "removeEmptyLines": false,
    "topFilesLength": 5,
    "showLineNumbers": false,
    "copyToClipboard": false,
    "includeEmptyDirectories": false,
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  },
  "include": ["**/*"],
  "ignore": {
    "useGitignore": true,
    "useDefaultPatterns": true,
    "customPatterns": ["tmp/", "*.log"]
  },
  "security": {
    "enableSecurityCheck": true
  }
}
```

## Configuración Global

Crear configuración global:
```bash
repomix --init --global
```

Ubicación:
- Windows: `%LOCALAPPDATA%\Repomix\repomix.config.json`
- macOS/Linux: `~/.config/repomix/repomix.config.json`

## Patrones de Ignorar

Prioridad:
1. Opciones CLI (`--ignore`)
2. `.repomixignore`
3. `.gitignore` y `.git/info/exclude`
4. Patrones predeterminados

Ejemplo de `.repomixignore`:
```text
# Directorios de caché
.cache/
tmp/

# Salidas de construcción
dist/
build/

# Registros
*.log
```

## Patrones de Ignorar Predeterminados

Patrones comunes incluidos por defecto:
```text
node_modules/**
.git/**
coverage/**
dist/**
```

Lista completa: [defaultIgnore.ts](https://github.com/yamadashy/repomix/blob/main/src/config/defaultIgnore.ts)

## Ejemplos

### Compresión de Código

Cuando `output.compress` está configurado como `true`, Repomix extraerá las estructuras esenciales del código mientras elimina los detalles de implementación. Esto reduce el conteo de tokens mientras mantiene información estructural importante.

Para más detalles y ejemplos, consulte la [Guía de Compresión de Código](code-compress).

### Integración con Git

La configuración `output.git` le permite controlar cómo se ordenan los archivos según el historial de Git:

- `sortByChanges`: Cuando está configurado como `true`, los archivos se ordenan por el número de cambios en Git (commits que modificaron el archivo). Los archivos con más cambios aparecen al final de la salida. Esto ayuda a priorizar los archivos más activamente desarrollados. Por defecto: `true`
- `sortByChangesMaxCommits`: El número máximo de commits a analizar al contar los cambios de archivos. Por defecto: `100`

Ejemplo de configuración:
```json
{
  "output": {
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  }
}
```

### Eliminación de Comentarios

Cuando `output.removeComments` está configurado como `true`, se eliminan los comentarios de los tipos de archivo soportados para reducir el tamaño de la salida y enfocarse en el contenido esencial del código.

Para ver los lenguajes soportados y ejemplos detallados, consulte la [Guía de Eliminación de Comentarios](comment-removal).
</file>

<file path="website/client/src/fr/guide/usage.md">
# Utilisation de base

## Démarrage rapide

Empaquetez tout votre dépôt:

```bash
repomix
```

## Cas d'utilisation courants

### Empaqueter des répertoires spécifiques

```bash
repomix path/to/directory
```

### Inclure des fichiers spécifiques

Utilisez des [motifs glob](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax):

```bash
repomix --include "src/**/*.ts,**/*.md"
```

### Exclure des fichiers

```bash
repomix --ignore "**/*.log,tmp/"
```

### Dépôts distants

```bash
# En utilisant l'URL GitHub
repomix --remote https://github.com/user/repo
# En utilisant le format abrégé
repomix --remote user/repo
# Branche/tag/commit spécifique
repomix --remote user/repo --remote-branch main
repomix --remote user/repo --remote-branch 935b695
```

### Compression de code

```bash
repomix --compress
# Vous pouvez également l'utiliser avec des dépôts distants:
repomix --remote yamadashy/repomix --compress
```

## Formats de sortie

### XML (Par défaut)

```bash
repomix --style xml
```

### Markdown

```bash
repomix --style markdown
```

### Texte brut

```bash
repomix --style plain
```

## Options supplémentaires

### Supprimer les commentaires

```bash
repomix --remove-comments
```

### Afficher les numéros de ligne

```bash
repomix --output-show-line-numbers
```

### Copier dans le presse-papiers

```bash
repomix --copy
```

### Désactiver la vérification de sécurité

```bash
repomix --no-security-check
```

## Configuration

Initialiser le fichier de configuration:

```bash
repomix --init
```

Consultez le [Guide de configuration](/fr/guide/configuration) pour les options détaillées.
</file>

<file path="website/client/src/ja/guide/command-line-options.md">
# コマンドラインオプション

## 基本オプション
- `-v, --version`: バージョンを表示

## 出力オプション
- `-o, --output <file>`: 出力ファイル名（デフォルト: `repomix-output.txt`）
- `--style <type>`: 出力形式（`plain`、`xml`、`markdown`）（デフォルト: `plain`）
- `--parsable-style`: 選択した形式のスキーマに基づいて解析可能な出力を有効化（デフォルト: `false`）
- `--compress`: 関数やクラスのシグネチャなどの重要な構造を保持しながら、実装の詳細を削除するインテリジェントなコード抽出を実行します。詳細と例については、[コード圧縮ガイド](code-compress)を参照してください。
- `--output-show-line-numbers`: 行番号を追加（デフォルト: `false`）
- `--copy`: クリップボードにコピー（デフォルト: `false`）
- `--no-file-summary`: ファイルサマリーを無効化（デフォルト: `true`）
- `--no-directory-structure`: ディレクトリ構造を無効化（デフォルト: `true`）
- `--remove-comments`: コメントを削除（デフォルト: `false`）
- `--remove-empty-lines`: 空行を削除（デフォルト: `false`）
- `--header-text <text>`: ファイルヘッダーに含めるカスタムテキスト
- `--instruction-file-path <path>`: 詳細なカスタム指示を含むファイルのパス
- `--include-empty-directories`: 空のディレクトリを出力に含める（デフォルト: `false`）

## フィルターオプション
- `--include <patterns>`: 含めるパターン（カンマ区切り）
- `-i, --ignore <patterns>`: 除外パターン（カンマ区切り）
- `--no-gitignore`: .gitignoreファイルの使用を無効化
- `--no-default-patterns`: デフォルトパターンを無効化

## リモートリポジトリオプション
- `--remote <url>`: リモートリポジトリを処理
- `--remote-branch <name>`: リモートブランチ名、タグ、またはコミットハッシュを指定（デフォルトはリポジトリのデフォルトブランチ）

## 設定オプション
- `-c, --config <path>`: カスタム設定ファイルのパス
- `--init`: 設定ファイルを作成
- `--global`: グローバル設定を使用

## セキュリティオプション
- `--no-security-check`: セキュリティチェックを無効化（デフォルト: `true`）

## トークンカウントオプション
- `--token-count-encoding <encoding>`: トークンカウントのエンコーディングを指定（例: `o200k_base`、`cl100k_base`）（デフォルト: `o200k_base`）

## その他のオプション
- `--top-files-len <number>`: 表示するトップファイルの数（デフォルト: `5`）
- `--verbose`: 詳細なログを有効化
- `--quiet`: 標準出力へのすべての出力を無効化

## 使用例

```bash
# 基本的な使用方法
repomix

# カスタム出力
repomix -o output.xml --style xml

# 圧縮を使用したカスタム出力
repomix --compress

# 特定のファイルを処理
repomix --include "src/**/*.ts" --ignore "**/*.test.ts"

# ブランチを指定したリモートリポジトリ
repomix --remote https://github.com/user/repo/tree/main

# コミットを指定したリモートリポジトリ
repomix --remote https://github.com/user/repo/commit/836abcd7335137228ad77feb28655d85712680f1

# ショートハンドを使用したリモートリポジトリ
repomix --remote user/repo
```
</file>

<file path="website/client/src/ja/guide/configuration.md">
# 設定

## クイックスタート

設定ファイルを作成：
```bash
repomix --init
```

## 設定ファイル

`repomix.config.json`:
```json
{
  "output": {
    "filePath": "repomix-output.xml",
    "style": "xml",
    "parsableStyle": true,
    "compress": false,
    "headerText": "カスタムヘッダーテキスト",
    "instructionFilePath": "repomix-instruction.md",
    "fileSummary": true,
    "directoryStructure": true,
    "removeComments": false,
    "removeEmptyLines": false,
    "topFilesLength": 5,
    "showLineNumbers": false,
    "copyToClipboard": false,
    "includeEmptyDirectories": false,
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  },
  "include": ["**/*"],
  "ignore": {
    "useGitignore": true,
    "useDefaultPatterns": true,
    "customPatterns": ["tmp/", "*.log"]
  },
  "security": {
    "enableSecurityCheck": true
  }
}
```

## グローバル設定

グローバル設定を作成：
```bash
repomix --init --global
```

設定ファイルの場所：
- Windows: `%LOCALAPPDATA%\Repomix\repomix.config.json`
- macOS/Linux: `~/.config/repomix/repomix.config.json`

## 除外パターン

優先順位：
1. CLIオプション（`--ignore`）
2. `.repomixignore`
3. `.gitignore` および `.git/info/exclude`
4. デフォルトパターン

`.repomixignore` の例：
```text
# キャッシュディレクトリ
.cache/
tmp/

# ビルド出力
dist/
build/

# ログ
*.log
```

## デフォルトの除外パターン

デフォルトで含まれる一般的なパターン：
```text
node_modules/**
.git/**
coverage/**
dist/**
```

全リスト: [defaultIgnore.ts](https://github.com/yamadashy/repomix/blob/main/src/config/defaultIgnore.ts)

## 設定例

### コード圧縮

`output.compress`を`true`に設定すると、Repomixは実装の詳細を削除しながら、本質的なコード構造を抽出します。これにより、トークン数を削減しながら重要な構造情報を維持できます。

詳細と例については[コード圧縮ガイド](code-compress)をご覧ください。

### Git統合

`output.git`設定では、Gitの履歴に基づいてファイルをソートする方法を制御できます：

- `sortByChanges`: `true`に設定すると、ファイルはGitの変更回数（そのファイルを変更したコミット数）でソートされます。より多くの変更があるファイルが出力の下部に表示されます。これは、より活発に開発されているファイルを優先するのに役立ちます。デフォルト: `true`
- `sortByChangesMaxCommits`: ファイルの変更回数を数える際に分析する最大コミット数。デフォルト: `100`

設定例：
```json
{
  "output": {
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  }
}
```

### コメントの削除

`output.removeComments`を`true`に設定すると、サポートされているファイルタイプからコメントが削除され、出力サイズを削減し、本質的なコード内容に焦点を当てることができます。

サポートされている言語と詳細な例については[コメント削除ガイド](comment-removal)をご覧ください。
</file>

<file path="website/client/src/ko/guide/command-line-options.md">
# 명령줄 옵션

## 기본 옵션
- `-v, --version`: 버전 표시

## 출력 옵션
- `-o, --output <file>`: 출력 파일 이름 (기본값: `repomix-output.txt`)
- `--style <type>`: 출력 스타일 (`plain`, `xml`, `markdown`) (기본값: `plain`)
- `--parsable-style`: 선택한 스타일 스키마에 기반한 파싱 가능한 출력 활성화 (기본값: `false`)
- `--compress`: 함수와 클래스 시그니처에 중점을 두고 구현 세부 사항을 제거하는 지능형 코드 추출을 수행합니다. 자세한 내용과 예제는 [코드 압축 가이드](code-compress)를 참조하세요.
- `--output-show-line-numbers`: 줄 번호 추가 (기본값: `false`)
- `--copy`: 클립보드에 복사 (기본값: `false`)
- `--no-file-summary`: 파일 요약 비활성화 (기본값: `true`)
- `--no-directory-structure`: 디렉토리 구조 비활성화 (기본값: `true`)
- `--remove-comments`: 주석 제거 (기본값: `false`)
- `--remove-empty-lines`: 빈 줄 제거 (기본값: `false`)
- `--header-text <text>`: 파일 헤더에 포함할 사용자 정의 텍스트
- `--instruction-file-path <path>`: 상세한 사용자 정의 지침이 포함된 파일 경로
- `--include-empty-directories`: 출력에 빈 디렉토리 포함 (기본값: `false`)

## 필터 옵션
- `--include <patterns>`: 포함할 패턴 (쉼표로 구분)
- `-i, --ignore <patterns>`: 무시할 패턴 (쉼표로 구분)
- `--no-gitignore`: .gitignore 파일 사용 비활성화
- `--no-default-patterns`: 기본 패턴 비활성화

## 원격 저장소 옵션
- `--remote <url>`: 원격 저장소 처리
- `--remote-branch <name>`: 원격 브랜치 이름, 태그 또는 커밋 해시 지정 (기본값은 저장소의 기본 브랜치)

## 설정 옵션
- `-c, --config <path>`: 사용자 정의 설정 파일 경로
- `--init`: 설정 파일 생성
- `--global`: 전역 설정 사용

## 보안 옵션
- `--no-security-check`: 보안 검사 비활성화 (기본값: `true`)

## 토큰 카운트 옵션
- `--token-count-encoding <encoding>`: 토큰 카운트 인코딩 지정 (예: `o200k_base`, `cl100k_base`) (기본값: `o200k_base`)

## 기타 옵션
- `--top-files-len <number>`: 표시할 상위 파일 수 (기본값: `5`)
- `--verbose`: 상세 로깅 활성화
- `--quiet`: 표준 출력에 대한 모든 출력 비활성화

## 예제

```bash
# 기본 사용법
repomix

# 사용자 정의 출력
repomix -o output.xml --style xml

# 압축을 사용한 사용자 정의 출력
repomix --compress

# 특정 파일 처리
repomix --include "src/**/*.ts" --ignore "**/*.test.ts"

# 브랜치를 지정한 원격 저장소
repomix --remote https://github.com/user/repo/tree/main

# 커밋을 지정한 원격 저장소
repomix --remote https://github.com/user/repo/commit/836abcd7335137228ad77feb28655d85712680f1

# 단축형을 사용한 원격 저장소
repomix --remote user/repo
```
</file>

<file path="website/client/src/ko/guide/configuration.md">
# 설정

## 빠른 시작

설정 파일 생성:
```bash
repomix --init
```

## 설정 파일

`repomix.config.json`:
```json
{
  "output": {
    "filePath": "repomix-output.xml",
    "style": "xml",
    "parsableStyle": true,
    "compress": false,
    "headerText": "사용자 정의 헤더 텍스트",
    "instructionFilePath": "repomix-instruction.md",
    "fileSummary": true,
    "directoryStructure": true,
    "removeComments": false,
    "removeEmptyLines": false,
    "topFilesLength": 5,
    "showLineNumbers": false,
    "copyToClipboard": false,
    "includeEmptyDirectories": false,
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  },
  "include": ["**/*"],
  "ignore": {
    "useGitignore": true,
    "useDefaultPatterns": true,
    "customPatterns": ["tmp/", "*.log"]
  },
  "security": {
    "enableSecurityCheck": true
  }
}
```

## 전역 설정

전역 설정 생성:
```bash
repomix --init --global
```

위치:
- Windows: `%LOCALAPPDATA%\Repomix\repomix.config.json`
- macOS/Linux: `~/.config/repomix/repomix.config.json`

## 무시 패턴

우선순위:
1. CLI 옵션 (`--ignore`)
2. `.repomixignore`
3. `.gitignore` 및 `.git/info/exclude`
4. 기본 패턴

`.repomixignore` 예시:
```text
# 캐시 디렉토리
.cache/
tmp/

# 빌드 출력
dist/
build/

# 로그
*.log
```

## 기본 무시 패턴

기본적으로 포함되는 일반적인 패턴:
```text
node_modules/**
.git/**
coverage/**
dist/**
```

전체 목록: [defaultIgnore.ts](https://github.com/yamadashy/repomix/blob/main/src/config/defaultIgnore.ts)

## 예제

### 코드 압축

`output.compress`를 `true`로 설정하면, Repomix는 구현 세부 사항을 제거하면서 필수적인 코드 구조를 추출합니다. 이를 통해 중요한 구조적 정보를 유지하면서 토큰 수를 줄일 수 있습니다.

자세한 내용과 예제는 [코드 압축 가이드](code-compress)를 참조하세요.

### Git 통합

`output.git` 설정을 통해 Git 히스토리를 기반으로 파일을 정렬하는 방법을 제어할 수 있습니다:

- `sortByChanges`: `true`로 설정하면, 파일은 Git 변경 횟수(해당 파일을 수정한 커밋 수)에 따라 정렬됩니다. 더 많은 변경이 있는 파일이 출력의 하단에 표시됩니다. 이는 더 활발하게 개발되는 파일을 우선순위화하는 데 도움이 됩니다. 기본값: `true`
- `sortByChangesMaxCommits`: 파일 변경 횟수를 계산할 때 분석할 최대 커밋 수입니다. 기본값: `100`

설정 예시:
```json
{
  "output": {
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  }
}
```

### 주석 제거

`output.removeComments`를 `true`로 설정하면, 지원되는 파일 유형에서 주석이 제거되어 출력 크기를 줄이고 핵심 코드 내용에 집중할 수 있습니다.

지원되는 언어와 자세한 예제는 [주석 제거 가이드](comment-removal)를 참조하세요.
</file>

<file path="website/client/src/pt-br/guide/command-line-options.md">
# Opções de Linha de Comando

## Opções Básicas
- `-v, --version`: Mostra a versão

## Opções de Saída
- `-o, --output <file>`: Nome do arquivo de saída (padrão: `repomix-output.txt`)
- `--style <type>`: Estilo de saída (`plain`, `xml`, `markdown`) (padrão: `plain`)
- `--parsable-style`: Habilita saída analisável baseada no esquema do estilo escolhido (padrão: `false`)
- `--compress`: Realiza extração inteligente de código, focando nas assinaturas de funções e classes enquanto remove detalhes de implementação. Para mais detalhes e exemplos, consulte o [Guia de Compressão de Código](code-compress)
- `--output-show-line-numbers`: Adiciona números de linha (padrão: `false`)
- `--copy`: Copia para a área de transferência (padrão: `false`)
- `--no-file-summary`: Desabilita o resumo de arquivos (padrão: `true`)
- `--no-directory-structure`: Desabilita a estrutura de diretórios (padrão: `true`)
- `--remove-comments`: Remove comentários (padrão: `false`)
- `--remove-empty-lines`: Remove linhas vazias (padrão: `false`)
- `--header-text <text>`: Texto personalizado para incluir no cabeçalho do arquivo
- `--instruction-file-path <path>`: Caminho para um arquivo contendo instruções personalizadas detalhadas
- `--include-empty-directories`: Inclui diretórios vazios na saída (padrão: `false`)

## Opções de Filtro
- `--include <patterns>`: Padrões para incluir (separados por vírgula)
- `-i, --ignore <patterns>`: Padrões para ignorar (separados por vírgula)
- `--no-gitignore`: Desabilita o uso do arquivo .gitignore
- `--no-default-patterns`: Desabilita padrões padrão

## Opções de Repositório Remoto
- `--remote <url>`: Processa repositório remoto
- `--remote-branch <name>`: Especifica o nome do branch remoto, tag ou hash do commit (padrão é o branch padrão do repositório)

## Opções de Configuração
- `-c, --config <path>`: Caminho do arquivo de configuração personalizado
- `--init`: Cria arquivo de configuração
- `--global`: Usa configuração global

## Opções de Segurança
- `--no-security-check`: Desabilita verificação de segurança (padrão: `true`)

## Opções de Contagem de Tokens
- `--token-count-encoding <encoding>`: Especifica a codificação para contagem de tokens (ex: `o200k_base`, `cl100k_base`) (padrão: `o200k_base`)

## Outras Opções
- `--top-files-len <number>`: Número de arquivos principais para mostrar (padrão: `5`)
- `--verbose`: Habilita log detalhado
- `--quiet`: Desabilita toda saída para stdout

## Exemplos

```bash
# Uso básico
repomix

# Saída personalizada
repomix -o output.xml --style xml

# Saída personalizada com compressão
repomix --compress

# Processar arquivos específicos
repomix --include "src/**/*.ts" --ignore "**/*.test.ts"

# Repositório remoto com branch
repomix --remote https://github.com/user/repo/tree/main

# Repositório remoto com commit
repomix --remote https://github.com/user/repo/commit/836abcd7335137228ad77feb28655d85712680f1

# Repositório remoto com formato curto
repomix --remote user/repo
```
</file>

<file path="website/client/src/pt-br/guide/configuration.md">
# Configuração

## Início Rápido

Criar arquivo de configuração:
```bash
repomix --init
```

## Arquivo de Configuração

`repomix.config.json`:
```json
{
  "output": {
    "filePath": "repomix-output.xml",
    "style": "xml",
    "parsableStyle": true,
    "compress": false,
    "headerText": "Texto personalizado do cabeçalho",
    "instructionFilePath": "repomix-instruction.md",
    "fileSummary": true,
    "directoryStructure": true,
    "removeComments": false,
    "removeEmptyLines": false,
    "topFilesLength": 5,
    "showLineNumbers": false,
    "copyToClipboard": false,
    "includeEmptyDirectories": false,
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  },
  "include": ["**/*"],
  "ignore": {
    "useGitignore": true,
    "useDefaultPatterns": true,
    "customPatterns": ["tmp/", "*.log"]
  },
  "security": {
    "enableSecurityCheck": true
  }
}
```

## Configuração Global

Criar configuração global:
```bash
repomix --init --global
```

Localização:
- Windows: `%LOCALAPPDATA%\Repomix\repomix.config.json`
- macOS/Linux: `~/.config/repomix/repomix.config.json`

## Padrões de Ignorar

Prioridade:
1. Opções CLI (`--ignore`)
2. `.repomixignore`
3. `.gitignore` e `.git/info/exclude`
4. Padrões padrão

Exemplo de `.repomixignore`:
```text
# Diretórios de cache
.cache/
tmp/

# Saídas de build
dist/
build/

# Logs
*.log
```

## Padrões de Ignorar Padrão

Padrões comuns incluídos por padrão:
```text
node_modules/**
.git/**
coverage/**
dist/**
```

Lista completa: [defaultIgnore.ts](https://github.com/yamadashy/repomix/blob/main/src/config/defaultIgnore.ts)

## Exemplos

### Compressão de Código

Quando `output.compress` está configurado como `true`, o Repomix extrairá as estruturas essenciais do código enquanto remove os detalhes de implementação. Isso reduz a contagem de tokens enquanto mantém informações estruturais importantes.

Para mais detalhes e exemplos, consulte o [Guia de Compressão de Código](code-compress).

### Integração com Git

A configuração `output.git` permite controlar como os arquivos são ordenados com base no histórico do Git:

- `sortByChanges`: Quando configurado como `true`, os arquivos são ordenados pelo número de alterações no Git (commits que modificaram o arquivo). Arquivos com mais alterações aparecem no final da saída. Isso ajuda a priorizar arquivos mais ativamente desenvolvidos. Padrão: `true`
- `sortByChangesMaxCommits`: O número máximo de commits a analisar ao contar as alterações de arquivos. Padrão: `100`

Exemplo de configuração:
```json
{
  "output": {
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  }
}
```

### Remoção de Comentários

Quando `output.removeComments` está configurado como `true`, os comentários são removidos dos tipos de arquivo suportados para reduzir o tamanho da saída e focar no conteúdo essencial do código.

Para linguagens suportadas e exemplos detalhados, consulte o [Guia de Remoção de Comentários](comment-removal).
</file>

<file path="website/client/src/zh-cn/guide/command-line-options.md">
# 命令行选项

## 基本选项
- `-v, --version`: 显示版本

## 输出选项
- `-o, --output <file>`: 输出文件名（默认：`repomix-output.txt`）
- `--style <type>`: 输出样式（`plain`、`xml`、`markdown`）（默认：`plain`）
- `--parsable-style`: 启用基于所选样式模式的可解析输出（默认：`false`）
- `--compress`: 执行智能代码提取，专注于函数和类的签名，同时删除实现细节。有关详细信息和示例，请参阅[代码压缩指南](code-compress)。
- `--output-show-line-numbers`: 添加行号（默认：`false`）
- `--copy`: 复制到剪贴板（默认：`false`）
- `--no-file-summary`: 禁用文件摘要（默认：`true`）
- `--no-directory-structure`: 禁用目录结构（默认：`true`）
- `--remove-comments`: 移除注释（默认：`false`）
- `--remove-empty-lines`: 移除空行（默认：`false`）
- `--header-text <text>`: 文件头部包含的自定义文本
- `--instruction-file-path <path>`: 包含详细自定义指令的文件路径
- `--include-empty-directories`: 在输出中包含空目录（默认：`false`）

## 过滤选项
- `--include <patterns>`: 包含模式（逗号分隔）
- `-i, --ignore <patterns>`: 忽略模式（逗号分隔）
- `--no-gitignore`: 禁用 .gitignore 文件
- `--no-default-patterns`: 禁用默认模式

## 远程仓库选项
- `--remote <url>`: 处理远程仓库
- `--remote-branch <name>`: 指定远程分支名称、标签或提交哈希（默认为仓库的默认分支）

## 配置选项
- `-c, --config <path>`: 自定义配置文件路径
- `--init`: 创建配置文件
- `--global`: 使用全局配置

## 安全选项
- `--no-security-check`: 禁用安全检查（默认：`true`）

## 令牌计数选项
- `--token-count-encoding <encoding>`: 指定令牌计数编码（如 `o200k_base`、`cl100k_base`）（默认：`o200k_base`）

## 其他选项
- `--top-files-len <number>`: 显示的顶部文件数量（默认：`5`）
- `--verbose`: 启用详细日志
- `--quiet`: 禁止所有标准输出

## 示例

```bash
# 基本用法
repomix

# 自定义输出
repomix -o output.xml --style xml

# 使用压缩的自定义输出
repomix --compress

# 处理特定文件
repomix --include "src/**/*.ts" --ignore "**/*.test.ts"

# 带分支的远程仓库
repomix --remote https://github.com/user/repo/tree/main

# 带提交的远程仓库
repomix --remote https://github.com/user/repo/commit/836abcd7335137228ad77feb28655d85712680f1

# 使用简写的远程仓库
repomix --remote user/repo
```
</file>

<file path="website/client/src/zh-cn/guide/configuration.md">
# 配置

## 快速开始

创建配置文件：
```bash
repomix --init
```

## 配置文件

`repomix.config.json`：
```json
{
  "output": {
    "filePath": "repomix-output.xml",
    "style": "xml",
    "parsableStyle": true,
    "compress": false,
    "headerText": "自定义头部文本",
    "instructionFilePath": "repomix-instruction.md",
    "fileSummary": true,
    "directoryStructure": true,
    "removeComments": false,
    "removeEmptyLines": false,
    "topFilesLength": 5,
    "showLineNumbers": false,
    "copyToClipboard": false,
    "includeEmptyDirectories": false,
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  },
  "include": ["**/*"],
  "ignore": {
    "useGitignore": true,
    "useDefaultPatterns": true,
    "customPatterns": ["tmp/", "*.log"]
  },
  "security": {
    "enableSecurityCheck": true
  }
}
```

## 全局配置

创建全局配置：
```bash
repomix --init --global
```

位置：
- Windows: `%LOCALAPPDATA%\Repomix\repomix.config.json`
- macOS/Linux: `~/.config/repomix/repomix.config.json`

## 忽略模式

优先级：
1. CLI 选项 (`--ignore`)
2. `.repomixignore`
3. `.gitignore` 和 `.git/info/exclude`
4. 默认模式

`.repomixignore` 示例：
```text
# 缓存目录
.cache/
tmp/

# 构建输出
dist/
build/

# 日志
*.log
```

## 默认忽略模式

默认包含的常见模式：
```text
node_modules/**
.git/**
coverage/**
dist/**
```

完整列表：[defaultIgnore.ts](https://github.com/yamadashy/repomix/blob/main/src/config/defaultIgnore.ts)

## 示例

### 代码压缩

当 `output.compress` 设置为 `true` 时，Repomix 将提取基本代码结构，同时移除实现细节。这可以在保持重要的结构信息的同时减少令牌数量。

更多详细信息和示例，请参阅[代码压缩指南](code-compress)。

### Git 集成

`output.git` 配置允许您控制如何基于 Git 历史记录对文件进行排序：

- `sortByChanges`：当设置为 `true` 时，文件将按 Git 更改次数（修改该文件的提交数）进行排序。更改次数较多的文件将出现在输出的底部。这有助于优先处理更活跃开发的文件。默认值：`true`
- `sortByChangesMaxCommits`：计算文件更改次数时要分析的最大提交数。默认值：`100`

配置示例：
```json
{
  "output": {
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  }
}
```

### 注释移除

当 `output.removeComments` 设置为 `true` 时，将从支持的文件类型中移除注释，以减少输出大小并专注于核心代码内容。

有关支持的语言和详细示例，请参阅[注释移除指南](comment-removal)。
</file>

<file path="src/cli/actions/defaultAction.ts">
import path from 'node:path';
import { loadFileConfig, mergeConfigs } from '../../config/configLoad.js';
import {
  type RepomixConfigCli,
  type RepomixConfigFile,
  type RepomixConfigMerged,
  type RepomixOutputStyle,
  repomixConfigCliSchema,
} from '../../config/configSchema.js';
import { type PackResult, pack } from '../../core/packager.js';
import { rethrowValidationErrorIfZodError } from '../../shared/errorHandle.js';
import { logger } from '../../shared/logger.js';
import { printCompletion, printSecurityCheck, printSummary, printTopFiles } from '../cliPrint.js';
import { Spinner } from '../cliSpinner.js';
import type { CliOptions } from '../types.js';
import { runMigrationAction } from './migrationAction.js';

export interface DefaultActionRunnerResult {
  packResult: PackResult;
  config: RepomixConfigMerged;
}

export const runDefaultAction = async (
  directories: string[],
  cwd: string,
  cliOptions: CliOptions,
): Promise<DefaultActionRunnerResult> => {
  logger.trace('Loaded CLI options:', cliOptions);

  // Run migration before loading config
  await runMigrationAction(cwd);

  // Load the config file
  const fileConfig: RepomixConfigFile = await loadFileConfig(cwd, cliOptions.config ?? null);
  logger.trace('Loaded file config:', fileConfig);

  // Parse the CLI options into a config
  const cliConfig: RepomixConfigCli = buildCliConfig(cliOptions);
  logger.trace('CLI config:', cliConfig);

  // Merge default, file, and CLI configs
  const config: RepomixConfigMerged = mergeConfigs(cwd, fileConfig, cliConfig);

  logger.trace('Merged config:', config);

  const targetPaths = directories.map((directory) => path.resolve(cwd, directory));

  const spinner = new Spinner('Packing files...', cliOptions);
  spinner.start();

  let packResult: PackResult;

  try {
    packResult = await pack(targetPaths, config, (message) => {
      spinner.update(message);
    });
  } catch (error) {
    spinner.fail('Error during packing');
    throw error;
  }

  spinner.succeed('Packing completed successfully!');
  logger.log('');

  if (config.output.topFilesLength > 0) {
    printTopFiles(packResult.fileCharCounts, packResult.fileTokenCounts, config.output.topFilesLength);
    logger.log('');
  }

  printSecurityCheck(cwd, packResult.suspiciousFilesResults, config);
  logger.log('');

  printSummary(
    packResult.totalFiles,
    packResult.totalCharacters,
    packResult.totalTokens,
    config.output.filePath,
    packResult.suspiciousFilesResults,
    config,
  );
  logger.log('');

  printCompletion();

  return {
    packResult,
    config,
  };
};

/**
 * Builds CLI configuration from command-line options.
 *
 * Note: Due to Commander.js behavior with --no-* flags:
 * - When --no-* flags are used (e.g., --no-file-summary), the options explicitly become false
 * - When no flag is specified, Commander defaults to true (e.g., options.fileSummary === true)
 * - For --no-* flags, we only apply the setting when it's explicitly false to respect config file values
 * - This allows the config file to maintain control unless explicitly overridden by CLI
 */
const buildCliConfig = (options: CliOptions): RepomixConfigCli => {
  const cliConfig: RepomixConfigCli = {};

  if (options.output) {
    cliConfig.output = { filePath: options.output };
  }
  if (options.include) {
    cliConfig.include = options.include.split(',');
  }
  if (options.ignore) {
    cliConfig.ignore = { customPatterns: options.ignore.split(',') };
  }
  // Only apply gitignore setting if explicitly set to false
  if (options.gitignore === false) {
    cliConfig.ignore = { ...cliConfig.ignore, useGitignore: options.gitignore };
  }
  // Only apply defaultPatterns setting if explicitly set to false
  if (options.defaultPatterns === false) {
    cliConfig.ignore = {
      ...cliConfig.ignore,
      useDefaultPatterns: options.defaultPatterns,
    };
  }
  if (options.topFilesLen !== undefined) {
    cliConfig.output = {
      ...cliConfig.output,
      topFilesLength: options.topFilesLen,
    };
  }
  if (options.outputShowLineNumbers !== undefined) {
    cliConfig.output = {
      ...cliConfig.output,
      showLineNumbers: options.outputShowLineNumbers,
    };
  }
  if (options.copy) {
    cliConfig.output = { ...cliConfig.output, copyToClipboard: options.copy };
  }
  if (options.style) {
    cliConfig.output = {
      ...cliConfig.output,
      style: options.style.toLowerCase() as RepomixOutputStyle,
    };
  }
  if (options.parsableStyle !== undefined) {
    cliConfig.output = {
      ...cliConfig.output,
      parsableStyle: options.parsableStyle,
    };
  }
  // Only apply securityCheck setting if explicitly set to false
  if (options.securityCheck === false) {
    cliConfig.security = { enableSecurityCheck: options.securityCheck };
  }
  // Only apply fileSummary setting if explicitly set to false
  if (options.fileSummary === false) {
    cliConfig.output = {
      ...cliConfig.output,
      fileSummary: false,
    };
  }
  // Only apply directoryStructure setting if explicitly set to false
  if (options.directoryStructure === false) {
    cliConfig.output = {
      ...cliConfig.output,
      directoryStructure: false,
    };
  }
  if (options.removeComments !== undefined) {
    cliConfig.output = {
      ...cliConfig.output,
      removeComments: options.removeComments,
    };
  }
  if (options.removeEmptyLines !== undefined) {
    cliConfig.output = {
      ...cliConfig.output,
      removeEmptyLines: options.removeEmptyLines,
    };
  }
  if (options.headerText !== undefined) {
    cliConfig.output = { ...cliConfig.output, headerText: options.headerText };
  }

  if (options.compress !== undefined) {
    cliConfig.output = { ...cliConfig.output, compress: options.compress };
  }

  if (options.tokenCountEncoding) {
    cliConfig.tokenCount = { encoding: options.tokenCountEncoding };
  }
  if (options.instructionFilePath) {
    cliConfig.output = {
      ...cliConfig.output,
      instructionFilePath: options.instructionFilePath,
    };
  }
  if (options.includeEmptyDirectories) {
    cliConfig.output = {
      ...cliConfig.output,
      includeEmptyDirectories: options.includeEmptyDirectories,
    };
  }

  // Only apply gitSortByChanges setting if explicitly set to false
  if (options.gitSortByChanges === false) {
    cliConfig.output = {
      ...cliConfig.output,
      git: {
        ...cliConfig.output?.git,
        sortByChanges: false,
      },
    };
  }

  try {
    return repomixConfigCliSchema.parse(cliConfig);
  } catch (error) {
    rethrowValidationErrorIfZodError(error, 'Invalid cli arguments');
    throw error;
  }
};
</file>

<file path="src/cli/types.ts">
import type { OptionValues } from 'commander';
import type { RepomixOutputStyle } from '../config/configSchema.js';

export interface CliOptions extends OptionValues {
  // Basic Options
  version?: boolean;

  // Output Options
  output?: string;
  style?: RepomixOutputStyle;
  parsableStyle?: boolean;
  compress?: boolean;
  outputShowLineNumbers?: boolean;
  copy?: boolean;
  fileSummary?: boolean;
  directoryStructure?: boolean;
  removeComments?: boolean;
  removeEmptyLines?: boolean;
  headerText?: string;
  instructionFilePath?: string;
  includeEmptyDirectories?: boolean;
  gitSortByChanges?: boolean;

  // Filter Options
  include?: string;
  ignore?: string;
  gitignore?: boolean;
  defaultPatterns?: boolean;

  // Remote Repository Options
  remote?: string;
  remoteBranch?: string;

  // Configuration Options
  config?: string;
  init?: boolean;
  global?: boolean;

  // Security Options
  securityCheck?: boolean;

  // Token Count Options
  tokenCountEncoding?: string;

  // MCP
  mcp?: boolean;

  // Other Options
  topFilesLen?: number;
  verbose?: boolean;
  quiet?: boolean;
}
</file>

<file path="src/core/file/fileSearch.ts">
import fs from 'node:fs/promises';
import path from 'node:path';
import { globby } from 'globby';
import { minimatch } from 'minimatch';
import type { RepomixConfigMerged } from '../../config/configSchema.js';
import { defaultIgnoreList } from '../../config/defaultIgnore.js';
import { RepomixError } from '../../shared/errorHandle.js';
import { logger } from '../../shared/logger.js';
import { sortPaths } from './filePathSort.js';
import { PermissionError, checkDirectoryPermissions } from './permissionCheck.js';

export interface FileSearchResult {
  filePaths: string[];
  emptyDirPaths: string[];
}

const findEmptyDirectories = async (
  rootDir: string,
  directories: string[],
  ignorePatterns: string[],
): Promise<string[]> => {
  const emptyDirs: string[] = [];

  for (const dir of directories) {
    const fullPath = path.join(rootDir, dir);
    try {
      const entries = await fs.readdir(fullPath);
      const hasVisibleContents = entries.some((entry) => !entry.startsWith('.'));

      if (!hasVisibleContents) {
        // This checks if the directory itself matches any ignore patterns
        const shouldIgnore = ignorePatterns.some((pattern) => minimatch(dir, pattern) || minimatch(`${dir}/`, pattern));

        if (!shouldIgnore) {
          emptyDirs.push(dir);
        }
      }
    } catch (error) {
      logger.debug(`Error checking directory ${dir}:`, error);
    }
  }

  return emptyDirs;
};

// Check if a path is a git worktree reference file
const isGitWorktreeRef = async (gitPath: string): Promise<boolean> => {
  try {
    const stats = await fs.stat(gitPath);
    if (!stats.isFile()) {
      return false;
    }

    const content = await fs.readFile(gitPath, 'utf8');
    return content.startsWith('gitdir:');
  } catch {
    return false;
  }
};

/**
 * Escapes special characters in glob patterns to handle paths with parentheses.
 * Example: "src/(categories)" -> "src/\\(categories\\)"
 */
export const escapeGlobPattern = (pattern: string): string => {
  // First escape backslashes
  const escapedBackslashes = pattern.replace(/\\/g, '\\\\');
  // Then escape special characters
  return escapedBackslashes.replace(/[()[\]{}]/g, '\\$&');
};

// Get all file paths considering the config
export const searchFiles = async (rootDir: string, config: RepomixConfigMerged): Promise<FileSearchResult> => {
  // First check directory permissions
  const permissionCheck = await checkDirectoryPermissions(rootDir);

  if (permissionCheck.details?.read !== true) {
    if (permissionCheck.error instanceof PermissionError) {
      throw permissionCheck.error;
    }
    throw new RepomixError(
      `Target directory is not readable or does not exist. Please check folder access permissions for your terminal app.\npath: ${rootDir}`,
    );
  }

  const includePatterns =
    config.include.length > 0 ? config.include.map((pattern) => escapeGlobPattern(pattern)) : ['**/*'];

  try {
    const [ignorePatterns, ignoreFilePatterns] = await Promise.all([
      getIgnorePatterns(rootDir, config),
      getIgnoreFilePatterns(config),
    ]);

    logger.trace('Include patterns:', includePatterns);
    logger.trace('Ignore patterns:', ignorePatterns);
    logger.trace('Ignore file patterns:', ignoreFilePatterns);

    // Check if .git is a worktree reference
    const gitPath = path.join(rootDir, '.git');
    const isWorktree = await isGitWorktreeRef(gitPath);

    // Modify ignore patterns for git worktree
    const adjustedIgnorePatterns = [...ignorePatterns];
    if (isWorktree) {
      // Remove '.git/**' pattern and add '.git' to ignore the reference file
      const gitIndex = adjustedIgnorePatterns.indexOf('.git/**');
      if (gitIndex !== -1) {
        adjustedIgnorePatterns.splice(gitIndex, 1);
        adjustedIgnorePatterns.push('.git');
      }
    }

    const filePaths = await globby(includePatterns, {
      cwd: rootDir,
      ignore: [...adjustedIgnorePatterns],
      ignoreFiles: [...ignoreFilePatterns],
      onlyFiles: true,
      absolute: false,
      dot: true,
      followSymbolicLinks: false,
    }).catch((error) => {
      // Handle EPERM errors specifically
      if (error.code === 'EPERM' || error.code === 'EACCES') {
        throw new PermissionError(
          `Permission denied while scanning directory. Please check folder access permissions for your terminal app. path: ${rootDir}`,
          rootDir,
        );
      }
      throw error;
    });

    let emptyDirPaths: string[] = [];
    if (config.output.includeEmptyDirectories) {
      const directories = await globby(includePatterns, {
        cwd: rootDir,
        ignore: [...adjustedIgnorePatterns],
        ignoreFiles: [...ignoreFilePatterns],
        onlyDirectories: true,
        absolute: false,
        dot: true,
        followSymbolicLinks: false,
      });

      emptyDirPaths = await findEmptyDirectories(rootDir, directories, adjustedIgnorePatterns);
    }

    logger.trace(`Filtered ${filePaths.length} files`);

    return {
      filePaths: sortPaths(filePaths),
      emptyDirPaths: sortPaths(emptyDirPaths),
    };
  } catch (error: unknown) {
    // Re-throw PermissionError as is
    if (error instanceof PermissionError) {
      throw error;
    }

    if (error instanceof Error) {
      logger.error('Error filtering files:', error.message);
      throw new Error(`Failed to filter files in directory ${rootDir}. Reason: ${error.message}`);
    }

    logger.error('An unexpected error occurred:', error);
    throw new Error('An unexpected error occurred while filtering files.');
  }
};

export const parseIgnoreContent = (content: string): string[] => {
  if (!content) return [];

  return content.split('\n').reduce<string[]>((acc, line) => {
    const trimmedLine = line.trim();
    if (trimmedLine && !trimmedLine.startsWith('#')) {
      acc.push(trimmedLine);
    }
    return acc;
  }, []);
};

export const getIgnoreFilePatterns = async (config: RepomixConfigMerged): Promise<string[]> => {
  const ignoreFilePatterns: string[] = [];

  if (config.ignore.useGitignore) {
    ignoreFilePatterns.push('**/.gitignore');
  }

  ignoreFilePatterns.push('**/.repomixignore');

  return ignoreFilePatterns;
};

export const getIgnorePatterns = async (rootDir: string, config: RepomixConfigMerged): Promise<string[]> => {
  const ignorePatterns = new Set<string>();

  // Add default ignore patterns
  if (config.ignore.useDefaultPatterns) {
    logger.trace('Adding default ignore patterns');
    for (const pattern of defaultIgnoreList) {
      ignorePatterns.add(pattern);
    }
  }

  // Add repomix output file
  if (config.output.filePath) {
    const absoluteOutputPath = path.resolve(config.cwd, config.output.filePath);
    const relativeToTargetPath = path.relative(rootDir, absoluteOutputPath);

    logger.trace('Adding output file to ignore patterns:', relativeToTargetPath);

    ignorePatterns.add(relativeToTargetPath);
  }

  // Add custom ignore patterns
  if (config.ignore.customPatterns) {
    logger.trace('Adding custom ignore patterns:', config.ignore.customPatterns);
    for (const pattern of config.ignore.customPatterns) {
      ignorePatterns.add(pattern);
    }
  }

  // Add patterns from .git/info/exclude if useGitignore is enabled
  if (config.ignore.useGitignore) {
    const excludeFilePath = path.join(rootDir, '.git', 'info', 'exclude');

    try {
      const excludeFileContent = await fs.readFile(excludeFilePath, 'utf8');
      const excludePatterns = parseIgnoreContent(excludeFileContent);

      for (const pattern of excludePatterns) {
        ignorePatterns.add(pattern);
      }
    } catch (error) {
      // File might not exist or might not be accessible, which is fine
      logger.trace('Could not read .git/info/exclude file:', error instanceof Error ? error.message : String(error));
    }
  }

  return Array.from(ignorePatterns);
};
</file>

<file path="src/core/treeSitter/lang2Query.ts">
import { queryC } from './queries/queryC.js';
import { queryCSharp } from './queries/queryCSharp.js';
import { queryCpp } from './queries/queryCpp.js';
import { queryCss } from './queries/queryCss.js';
import { queryGo } from './queries/queryGo.js';
import { queryJava } from './queries/queryJava.js';
import { queryJavascript } from './queries/queryJavascript.js';
import { queryPhp } from './queries/queryPhp.js';
import { queryPython } from './queries/queryPython.js';
import { queryRuby } from './queries/queryRuby.js';
import { queryRust } from './queries/queryRust.js';
import { querySolidity } from './queries/querySolidity.js';
import { querySwift } from './queries/querySwift.js';
import { queryTypescript } from './queries/queryTypescript.js';
import { queryVue } from './queries/queryVue.js';

export const lang2Query = {
  javascript: queryJavascript,
  typescript: queryTypescript,
  c: queryC,
  cpp: queryCpp,
  python: queryPython,
  rust: queryRust,
  go: queryGo,
  c_sharp: queryCSharp,
  ruby: queryRuby,
  java: queryJava,
  php: queryPhp,
  swift: querySwift,
  solidity: querySolidity,
  css: queryCss,
  vue: queryVue,
};

export type SupportedLang = keyof typeof lang2Query;
</file>

<file path="src/mcp/prompts/packRemoteRepositoryPrompts.ts">
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

/**
 * Register Repomix-related prompts to the MCP server
 */
export const registerPackRemoteRepositoryPrompt = (mcpServer: McpServer) => {
  // Pack Remote Repository Prompt
  mcpServer.prompt(
    'pack_remote_repository',
    'Pack a remote GitHub repository for analysis',
    {
      repository: z.string().describe('GitHub repository URL or owner/repo format (e.g., "yamadashy/repomix")'),
      includePatterns: z
        .string()
        .optional()
        .describe(
          'Comma-separated list of glob patterns to include (e.g., "src/**,lib/**"). It is recommended to pack only necessary files.',
        ),
      ignorePatterns: z
        .string()
        .optional()
        .describe(
          'Comma-separated list of glob patterns to ignore (e.g., "**/*.test.js,**/*.spec.js"). It is recommended to pack only necessary files.',
        ),
    },
    async ({ repository, includePatterns, ignorePatterns }) => {
      // Convert compress string to boolean
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Please analyze the GitHub repository at ${repository}.

First, use the pack_remote_repository tool with these parameters:
- repository: "${repository}"
${includePatterns ? `- includePatterns: "${includePatterns}"` : ''}
${ignorePatterns ? `- ignorePatterns: "${ignorePatterns}"` : ''}

Once you have the packed repository:
1. Read the code using the outputId from the tool response
2. Give me a high-level overview of this project
3. Explain its architecture and main components
4. Identify the key technologies and dependencies used
5. Highlight any interesting patterns or design decisions

Please be thorough in your analysis.`,
            },
          },
        ],
      };
    },
  );
};
</file>

<file path="src/mcp/tools/readRepomixOutputTool.ts">
import fs from 'node:fs/promises';
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { logger } from '../../shared/logger.js';
import { getOutputFilePath } from './mcpToolRuntime.js';

/**
 * Register the tool to read Repomix output files
 */
export const registerReadRepomixOutputTool = (mcpServer: McpServer) => {
  mcpServer.tool(
    'read_repomix_output',
    'Read the contents of a Repomix output file in environments where direct file access is not possible. This tool is specifically intended for cases where the client cannot access the file system directly, such as in web-based environments or sandboxed applications. For systems with direct file access, use standard file operations instead.',
    {
      outputId: z.string().describe('ID of the Repomix output file to read'),
    },
    async ({ outputId }): Promise<CallToolResult> => {
      try {
        logger.trace(`Reading Repomix output with ID: ${outputId}`);

        // Get the file path from the registry
        const filePath = getOutputFilePath(outputId);
        if (!filePath) {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: `Error: Output file with ID ${outputId} not found. The output file may have been deleted or the ID is invalid.`,
              },
            ],
          };
        }

        // Check if the file exists
        try {
          await fs.access(filePath);
        } catch (error) {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: `Error: Output file does not exist at path: ${filePath}. The temporary file may have been cleaned up.`,
              },
            ],
          };
        }

        // Read the file content
        const content = await fs.readFile(filePath, 'utf8');

        return {
          content: [
            {
              type: 'resource',
              resource: {
                text: content,
                uri: `file://${filePath}`,
                mimeType: 'application/xml',
              },
            },
          ],
        };
      } catch (error) {
        logger.error(`Error reading Repomix output: ${error}`);
        return {
          isError: true,
          content: [
            {
              type: 'text',
              text: `Error reading Repomix output: ${error instanceof Error ? error.message : String(error)}`,
            },
          ],
        };
      }
    },
  );
};
</file>

<file path="tests/core/file/fileSearch.test.ts">
import type { Stats } from 'node:fs';
import * as fs from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import { globby } from 'globby';
import { minimatch } from 'minimatch';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import {
  escapeGlobPattern,
  getIgnoreFilePatterns,
  getIgnorePatterns,
  parseIgnoreContent,
  searchFiles,
} from '../../../src/core/file/fileSearch.js';
import { createMockConfig, isWindows } from '../../testing/testUtils.js';

vi.mock('fs/promises');
vi.mock('globby');

describe('fileSearch', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

  describe('getIgnoreFilePaths', () => {
    test('should return correct paths when .gitignore and .repomixignore exist', async () => {
      vi.mocked(fs.access).mockResolvedValue(undefined);
      const mockConfig = createMockConfig({
        ignore: {
          useGitignore: true,
          useDefaultPatterns: true,
          customPatterns: [],
        },
      });
      const filePatterns = await getIgnoreFilePatterns(mockConfig);
      expect(filePatterns).toEqual(['**/.gitignore', '**/.repomixignore']);
    });

    test('should not include .gitignore when useGitignore is false', async () => {
      vi.mocked(fs.access).mockResolvedValue(undefined);
      const mockConfig = createMockConfig({
        ignore: {
          useGitignore: false,
          useDefaultPatterns: true,
          customPatterns: [],
        },
      });
      const filePatterns = await getIgnoreFilePatterns(mockConfig);
      expect(filePatterns).toEqual(['**/.repomixignore']);
    });

    test('should handle empty directories when enabled', async () => {
      const mockConfig = createMockConfig({
        output: {
          includeEmptyDirectories: true,
        },
      });

      const mockFilePaths = ['src/file1.js', 'src/file2.js'];
      const mockEmptyDirs = ['src/empty', 'empty-root'];

      vi.mocked(globby).mockImplementation(async (_, options) => {
        if (options?.onlyDirectories) {
          return mockEmptyDirs;
        }
        return mockFilePaths;
      });

      vi.mocked(fs.readdir).mockResolvedValue([]);

      const result = await searchFiles('/mock/root', mockConfig);

      expect(result.filePaths).toEqual(mockFilePaths);
      expect(result.emptyDirPaths.sort()).toEqual(mockEmptyDirs.sort());
    });

    test('should not collect empty directories when disabled', async () => {
      const mockConfig = createMockConfig({
        output: {
          includeEmptyDirectories: false,
        },
      });

      const mockFilePaths = ['src/file1.js', 'src/file2.js'];

      vi.mocked(globby).mockImplementation(async (_, options) => {
        if (options?.onlyDirectories) {
          throw new Error('Should not search for directories when disabled');
        }
        return mockFilePaths;
      });

      const result = await searchFiles('/mock/root', mockConfig);

      expect(result.filePaths).toEqual(mockFilePaths);
      expect(result.emptyDirPaths).toEqual([]);
      expect(globby).toHaveBeenCalledTimes(1);
    });
  });

  describe('getIgnorePatterns', () => {
    test('should return default patterns when useDefaultPatterns is true', async () => {
      const mockConfig = createMockConfig({
        ignore: {
          useGitignore: true,
          useDefaultPatterns: true,
          customPatterns: [],
        },
      });

      const patterns = await getIgnorePatterns(process.cwd(), mockConfig);

      expect(patterns.length).toBeGreaterThan(0);
      expect(patterns).toContain('**/node_modules/**');
    });

    test('should include custom patterns', async () => {
      const mockConfig = createMockConfig({
        ignore: {
          useGitignore: true,
          useDefaultPatterns: false,
          customPatterns: ['*.custom', 'temp/'],
        },
      });

      const patterns = await getIgnorePatterns(process.cwd(), mockConfig);

      expect(patterns).toEqual(['repomix-output.xml', '*.custom', 'temp/']);
    });

    test('should combine default and custom patterns', async () => {
      const mockConfig = createMockConfig({
        ignore: {
          useGitignore: true,
          useDefaultPatterns: true,
          customPatterns: ['*.custom', 'temp/'],
        },
      });

      const patterns = await getIgnorePatterns(process.cwd(), mockConfig);

      expect(patterns).toContain('**/node_modules/**');
      expect(patterns).toContain('*.custom');
      expect(patterns).toContain('temp/');
    });

    test('should include patterns from .git/info/exclude when useGitignore is true', async () => {
      const mockConfig = createMockConfig({
        ignore: {
          useGitignore: true,
          useDefaultPatterns: false,
          customPatterns: [],
        },
      });

      const mockExcludeContent = `
# Test exclude file
*.ignored
temp-files/
`;

      vi.mocked(fs.readFile).mockImplementation(async (filePath) => {
        // Use path.join to create platform-specific path for testing
        const excludePath = path.join('.git', 'info', 'exclude');
        if (filePath.toString().endsWith(excludePath)) {
          return mockExcludeContent;
        }
        return '';
      });

      const patterns = await getIgnorePatterns('/mock/root', mockConfig);

      // Only test for the exclude file patterns
      expect(patterns).toContain('*.ignored');
      expect(patterns).toContain('temp-files/');
    });
  });

  describe('parseIgnoreContent', () => {
    test('should correctly parse ignore content', () => {
      const content = `
# Comment
node_modules
*.log

.DS_Store
      `;

      const patterns = parseIgnoreContent(content);

      expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']);
    });

    test('should handle mixed line endings', () => {
      const content = 'node_modules\n*.log\r\n.DS_Store\r';

      const patterns = parseIgnoreContent(content);

      expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']);
    });
  });

  describe('filterFiles', () => {
    beforeEach(() => {
      vi.resetAllMocks();
    });

    test('should call globby with correct parameters', async () => {
      const mockConfig = createMockConfig({
        include: ['**/*.js'],
        ignore: {
          useGitignore: true,
          useDefaultPatterns: false,
          customPatterns: ['*.custom'],
        },
      });

      vi.mocked(globby).mockResolvedValue(['file1.js', 'file2.js']);
      vi.mocked(fs.access).mockResolvedValue(undefined);

      await searchFiles('/mock/root', mockConfig);

      expect(globby).toHaveBeenCalledWith(
        ['**/*.js'],
        expect.objectContaining({
          cwd: '/mock/root',
          ignore: expect.arrayContaining(['*.custom']),
          ignoreFiles: expect.arrayContaining(['**/.gitignore', '**/.repomixignore']),
          onlyFiles: true,
          absolute: false,
          dot: true,
          followSymbolicLinks: false,
        }),
      );
    });

    test.runIf(!isWindows)('Honor .gitignore files in subdirectories', async () => {
      const mockConfig = createMockConfig({
        include: ['**/*.js'],
        ignore: {
          useGitignore: true,
          useDefaultPatterns: false,
          customPatterns: [],
        },
      });

      const mockFileStructure = [
        'root/file1.js',
        'root/subdir/file2.js',
        'root/subdir/ignored.js',
        'root/another/file3.js',
      ];

      const mockGitignoreContent = {
        '/mock/root/.gitignore': '*.log',
        '/mock/root/subdir/.gitignore': 'ignored.js',
      };

      vi.mocked(globby).mockImplementation(async () => {
        // Simulate filtering files based on .gitignore
        return mockFileStructure.filter((file) => {
          const relativePath = file.replace('root/', '');
          const dir = path.dirname(relativePath);
          const gitignorePath = path.join('/mock/root', dir, '.gitignore');
          const gitignoreContent = mockGitignoreContent[gitignorePath as keyof typeof mockGitignoreContent];
          if (gitignoreContent && minimatch(path.basename(file), gitignoreContent)) {
            return false;
          }
          return true;
        });
      });

      vi.mocked(fs.readFile).mockImplementation(async (filePath) => {
        return mockGitignoreContent[filePath as keyof typeof mockGitignoreContent] || '';
      });

      const result = await searchFiles('/mock/root', mockConfig);
      expect(result.filePaths).toEqual(['root/another/file3.js', 'root/subdir/file2.js', 'root/file1.js']);
      expect(result.filePaths).not.toContain('root/subdir/ignored.js');
      expect(result.emptyDirPaths).toEqual([]);
    });

    test('should not apply .gitignore when useGitignore is false', async () => {
      const mockConfig = createMockConfig({
        include: ['**/*.js'],
        ignore: {
          useGitignore: false,
          useDefaultPatterns: false,
          customPatterns: [],
        },
      });

      const mockFileStructure = [
        'root/file1.js',
        'root/another/file3.js',
        'root/subdir/file2.js',
        'root/subdir/ignored.js',
      ];

      vi.mocked(globby).mockResolvedValue(mockFileStructure);

      const result = await searchFiles('/mock/root', mockConfig);

      expect(result.filePaths).toEqual(mockFileStructure);
      expect(result.filePaths).toContain('root/subdir/ignored.js');
      expect(result.emptyDirPaths).toEqual([]);
    });

    test('should handle git worktree correctly', async () => {
      // Mock .git file content for worktree
      const gitWorktreeContent = 'gitdir: /path/to/main/repo/.git/worktrees/feature-branch';

      // Mock fs.stat and fs.readFile for .git file
      vi.mocked(fs.stat).mockResolvedValue({
        isFile: () => true,
      } as Stats);
      vi.mocked(fs.readFile).mockResolvedValue(gitWorktreeContent);

      // Mock globby to return some test files
      vi.mocked(globby).mockResolvedValue(['file1.js', 'file2.js']);

      const mockConfig = createMockConfig({
        ignore: {
          useGitignore: true,
          useDefaultPatterns: true,
          customPatterns: [],
        },
      });

      const result = await searchFiles('/test/dir', mockConfig);

      // Check that globby was called with correct ignore patterns
      const globbyCall = vi.mocked(globby).mock.calls[0];
      const ignorePatterns = globbyCall[1]?.ignore as string[];

      // Verify .git file (not directory) is in ignore patterns
      expect(ignorePatterns).toContain('.git');
      // Verify .git/** is not in ignore patterns
      expect(ignorePatterns).not.toContain('.git/**');

      // Verify the files were returned correctly
      expect(result.filePaths).toEqual(['file1.js', 'file2.js']);
    });

    test('should handle regular git repository correctly', async () => {
      // Mock .git as a directory
      vi.mocked(fs.stat).mockResolvedValue({
        isFile: () => false,
      } as Stats);

      // Mock globby to return some test files
      vi.mocked(globby).mockResolvedValue(['file1.js', 'file2.js']);

      const mockConfig = createMockConfig({
        ignore: {
          useGitignore: true,
          useDefaultPatterns: true,
          customPatterns: [],
        },
      });

      const result = await searchFiles('/test/dir', mockConfig);

      // Check that globby was called with correct ignore patterns
      const globbyCall = vi.mocked(globby).mock.calls[0];
      const ignorePatterns = globbyCall[1]?.ignore as string[];

      // Verify .git/** is in ignore patterns for regular git repos
      expect(ignorePatterns).toContain('.git/**');
      // Verify just .git is not in ignore patterns
      expect(ignorePatterns).not.toContain('.git');

      // Verify the files were returned correctly
      expect(result.filePaths).toEqual(['file1.js', 'file2.js']);
    });
  });

  describe('escapeGlobPattern', () => {
    test('should escape parentheses in pattern', () => {
      const pattern = 'src/(categories)/**/*.ts';
      expect(escapeGlobPattern(pattern)).toBe('src/\\(categories\\)/**/*.ts');
    });

    test('should escape multiple types of brackets', () => {
      const pattern = 'src/(auth)/[id]/{slug}/**/*.ts';
      expect(escapeGlobPattern(pattern)).toBe('src/\\(auth\\)/\\[id\\]/\\{slug\\}/**/*.ts');
    });

    test('should handle nested brackets', () => {
      const pattern = 'src/(auth)/([id])/**/*.ts';
      expect(escapeGlobPattern(pattern)).toBe('src/\\(auth\\)/\\(\\[id\\]\\)/**/*.ts');
    });

    test('should handle empty string', () => {
      expect(escapeGlobPattern('')).toBe('');
    });

    test('should not modify patterns without special characters', () => {
      const pattern = 'src/components/**/*.ts';
      expect(escapeGlobPattern(pattern)).toBe(pattern);
    });

    test('should handle multiple occurrences of the same bracket type', () => {
      const pattern = 'src/(auth)/(settings)/**/*.ts';
      expect(escapeGlobPattern(pattern)).toBe('src/\\(auth\\)/\\(settings\\)/**/*.ts');
    });
  });

  test('should escape backslashes in pattern', () => {
    const pattern = 'src\\temp\\(categories)';
    expect(escapeGlobPattern(pattern)).toBe('src\\\\temp\\\\\\(categories\\)');
  });

  test('should handle patterns with already escaped special characters', () => {
    const pattern = 'src\\\\(categories)';
    expect(escapeGlobPattern(pattern)).toBe('src\\\\\\\\\\(categories\\)');
  });

  test('should handle patterns with mixed backslashes and special characters', () => {
    const pattern = 'src\\temp\\[id]\\{slug}';
    expect(escapeGlobPattern(pattern)).toBe('src\\\\temp\\\\\\[id\\]\\\\\\{slug\\}');
  });
});
</file>

<file path="website/client/components/Home/TryIt.vue">
<template>
  <div class="container">
    <form class="try-it-container" @submit.prevent="handleSubmit">
      <div class="input-row">
        <div class="tab-container">
          <button
            type="button"
            :class="{ active: mode === 'url' }"
            @click="setMode('url')"
          >
            <Link2 size="20" class="icon" />
          </button>
          <button
            type="button"
            :class="{ active: mode === 'folder' }"
            @click="setMode('folder')"
          >
            <FolderOpen size="20" class="icon" />
          </button>
          <button
            type="button"
            :class="{ active: mode === 'file' }"
            @click="setMode('file')"
          >
            <FolderArchive size="20" class="icon" />
          </button>
        </div>

        <div class="input-field">
          <TryItFileUpload
            v-if="mode === 'file'"
            @upload="handleFileUpload"
            :loading="loading"
            :show-button="false"
          />
          <TryItFolderUpload
            v-else-if="mode === 'folder'"
            @upload="handleFileUpload"
            :loading="loading"
            :show-button="false"
          />
          <TryItUrlInput
            v-else
            v-model:url="url"
            :loading="loading"
            @keydown="handleKeydown"
            :show-button="false"
          />
        </div>

        <div class="pack-button-wrapper">
          <PackButton
            :loading="loading"
            :isValid="isSubmitValid"
          />
        </div>
      </div>

      <TryItPackOptions
        v-model:format="format"
        v-model:include-patterns="includePatterns"
        v-model:ignore-patterns="ignorePatterns"
        v-model:file-summary="fileSummary"
        v-model:directory-structure="directoryStructure"
        v-model:remove-comments="removeComments"
        v-model:remove-empty-lines="removeEmptyLines"
        v-model:show-line-numbers="showLineNumbers"
        v-model:output-parsable="outputParsable"
      />

      <div v-if="hasExecuted">
        <TryItResult
          :result="result"
          :loading="loading"
          :error="error"
          :repository-url="inputRepositoryUrl"
        />
      </div>
    </form>
  </div>
</template>

<script setup lang="ts">
import { FolderArchive, FolderOpen, Link2 } from 'lucide-vue-next';
import { computed, ref } from 'vue';
import type { PackResult } from '../api/client';
import { handlePackRequest } from '../utils/requestHandlers';
import { isValidRemoteValue } from '../utils/validation';
import PackButton from './PackButton.vue';
import TryItFileUpload from './TryItFileUpload.vue';
import TryItFolderUpload from './TryItFolderUpload.vue';
import TryItPackOptions from './TryItPackOptions.vue';
import TryItResult from './TryItResult.vue';
import TryItUrlInput from './TryItUrlInput.vue';

// Form input states
const url = ref('');
const format = ref<'xml' | 'markdown' | 'plain'>('xml');
const removeComments = ref(false);
const removeEmptyLines = ref(false);
const showLineNumbers = ref(false);
const fileSummary = ref(true);
const directoryStructure = ref(true);
const includePatterns = ref('');
const ignorePatterns = ref('');
const outputParsable = ref(false);
const inputRepositoryUrl = ref('');

// Processing states
const loading = ref(false);
const error = ref<string | null>(null);
const result = ref<PackResult | null>(null);
const hasExecuted = ref(false);
const mode = ref<'url' | 'file' | 'folder'>('url');
const uploadedFile = ref<File | null>(null);

// Compute if the current mode's input is valid for submission
const isSubmitValid = computed(() => {
  switch (mode.value) {
    case 'url':
      return !!url.value && isValidRemoteValue(url.value.trim());
    case 'file':
    case 'folder':
      return !!uploadedFile.value;
    default:
      return false;
  }
});

// Explicitly set the mode and handle related state changes
function setMode(newMode: 'url' | 'file' | 'folder') {
  mode.value = newMode;
}

const TIMEOUT_MS = 30_000;
let requestController: AbortController | null = null;

async function handleSubmit() {
  // Check if current mode has valid input
  if (!isSubmitValid.value) return;

  // Cancel any pending request
  if (requestController) {
    requestController.abort();
  }
  requestController = new AbortController();

  loading.value = true;
  error.value = null;
  result.value = null;
  hasExecuted.value = true;
  inputRepositoryUrl.value = url.value;

  const timeoutId = setTimeout(() => {
    if (requestController) {
      requestController.abort('Request timed out');
      throw new Error('Request timed out');
    }
  }, TIMEOUT_MS);

  await handlePackRequest(
    mode.value === 'url' ? url.value : '',
    format.value,
    {
      removeComments: removeComments.value,
      removeEmptyLines: removeEmptyLines.value,
      showLineNumbers: showLineNumbers.value,
      fileSummary: fileSummary.value,
      directoryStructure: directoryStructure.value,
      includePatterns: includePatterns.value ? includePatterns.value.trim() : undefined,
      ignorePatterns: ignorePatterns.value ? ignorePatterns.value.trim() : undefined,
      outputParsable: outputParsable.value,
    },
    {
      onSuccess: (response) => {
        result.value = response;
      },
      onError: (errorMessage) => {
        error.value = errorMessage;
      },
      signal: requestController.signal,
      file: mode.value === 'file' || mode.value === 'folder' ? uploadedFile.value || undefined : undefined,
    },
  );

  clearTimeout(timeoutId);

  loading.value = false;
  requestController = null;
}

function handleKeydown(event: KeyboardEvent) {
  if (event.key === 'Enter' && mode.value === 'url' && isSubmitValid.value && !loading.value) {
    handleSubmit();
  }
}

function handleFileUpload(file: File) {
  uploadedFile.value = file;
}
</script>

<style scoped>
.container {
  padding: 0 20px;
  margin: 0 auto;
  max-width: 960px;
}

.try-it-container {
  background: var(--vp-c-bg-soft);
  border: 1px solid var(--vp-c-border);
  border-radius: 12px;
  padding: 24px;
}

.input-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 12px;
  margin-bottom: 24px;
  align-items: start;
}

.tab-container {
  display: flex;
  flex-direction: row;
  width: 240px;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--vp-c-border);
}

.tab-container button {
  flex: 1;
  height: 48px;
  padding: 0 16px;
  background: var(--vp-c-bg);
  cursor: pointer;
  font-size: 16px;
  white-space: nowrap;
  transition: all 0.2s;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.tab-container button:not(:first-child)::before {
  content: '';
  position: absolute;
  left: 0;
  top: 25%;
  height: 50%;
  width: 1px;
  background-color: var(--vp-c-border);
}

.tab-container button:first-child {
  border-radius: 8px 0 0 8px;
}

.tab-container button:last-child {
  border-radius: 0 8px 8px 0;
}

.tab-container button.active {
  background: var(--vp-c-brand-1);
  color: white;
}

.tab-container button.active::before {
  display: none;
}

.tab-container button.active + button::before {
  display: none;
}

.tab-container button .icon {
  color: var(--vp-c-text-1);
}

.tab-container button.active .icon {
  color: white;
}

.input-field {
  align-self: start;
  min-width: 0;
  flex: 1;
  overflow: hidden;
}

.pack-button-wrapper {
  display: flex;
  align-items: stretch;
  align-self: start;
  flex-shrink: 0;
}

/* Responsive adjustments */
@media (max-width: 768px) {
  .input-row {
    grid-template-columns: 1fr;
    gap: 12px;
  }

  .tab-container {
    width: 100%;
  }

  .pack-button-wrapper {
    width: 100%;
  }
}
</style>
</file>

<file path="src/cli/cliRun.ts">
import process from 'node:process';
import { Option, program } from 'commander';
import pc from 'picocolors';
import { getVersion } from '../core/file/packageJsonParse.js';
import { handleError } from '../shared/errorHandle.js';
import { logger, repomixLogLevels } from '../shared/logger.js';
import { runDefaultAction } from './actions/defaultAction.js';
import { runInitAction } from './actions/initAction.js';
import { runMcpAction } from './actions/mcpAction.js';
import { runRemoteAction } from './actions/remoteAction.js';
import { runVersionAction } from './actions/versionAction.js';
import type { CliOptions } from './types.js';

export const run = async () => {
  try {
    program
      .description('Repomix - Pack your repository into a single AI-friendly file')
      .argument('[directories...]', 'list of directories to process', ['.'])
      // Basic Options
      .option('-v, --version', 'show version information')
      // Output Options
      .option('-o, --output <file>', 'specify the output file name')
      .option('--style <type>', 'specify the output style (xml, markdown, plain)')
      .option('--parsable-style', 'by escaping and formatting, ensure the output is parsable as a document of its type')
      .option('--compress', 'perform code compression to reduce token count')
      .option('--output-show-line-numbers', 'add line numbers to each line in the output')
      .option('--copy', 'copy generated output to system clipboard')
      .option('--no-file-summary', 'disable file summary section output')
      .option('--no-directory-structure', 'disable directory structure section output')
      .option('--remove-comments', 'remove comments')
      .option('--remove-empty-lines', 'remove empty lines')
      .option('--header-text <text>', 'specify the header text')
      .option('--instruction-file-path <path>', 'path to a file containing detailed custom instructions')
      .option('--include-empty-directories', 'include empty directories in the output')
      .option('--no-git-sort-by-changes', 'disable sorting files by git change count')
      // Filter Options
      .option('--include <patterns>', 'list of include patterns (comma-separated)')
      .option('-i, --ignore <patterns>', 'additional ignore patterns (comma-separated)')
      .option('--no-gitignore', 'disable .gitignore file usage')
      .option('--no-default-patterns', 'disable default patterns')
      // Remote Repository Options
      .option('--remote <url>', 'process a remote Git repository')
      .option(
        '--remote-branch <name>',
        'specify the remote branch name, tag, or commit hash (defaults to repository default branch)',
      )
      // Configuration Options
      .option('-c, --config <path>', 'path to a custom config file')
      .option('--init', 'initialize a new repomix.config.json file')
      .option('--global', 'use global configuration (only applicable with --init)')
      // Security Options
      .option('--no-security-check', 'disable security check')
      // Token Count Options
      .option('--token-count-encoding <encoding>', 'specify token count encoding (e.g., o200k_base, cl100k_base)')
      // MCP
      .option('--mcp', 'run as a MCP server')
      // Other Options
      .option('--top-files-len <number>', 'specify the number of top files to display', Number.parseInt)
      .addOption(new Option('--verbose', 'enable verbose logging for detailed output').conflicts('quiet'))
      .addOption(new Option('--quiet', 'disable all output to stdout').conflicts('verbose'))
      .action(commanderActionEndpoint);

    await program.parseAsync(process.argv);
  } catch (error) {
    handleError(error);
  }
};

const commanderActionEndpoint = async (directories: string[], options: CliOptions = {}) => {
  await runCli(directories, process.cwd(), options);
};

export const runCli = async (directories: string[], cwd: string, options: CliOptions) => {
  // Set log level based on verbose and quiet flags
  if (options.quiet) {
    logger.setLogLevel(repomixLogLevels.SILENT);
  } else if (options.verbose) {
    logger.setLogLevel(repomixLogLevels.DEBUG);
  } else {
    logger.setLogLevel(repomixLogLevels.INFO);
  }

  logger.trace('directories:', directories);
  logger.trace('cwd:', cwd);
  logger.trace('options:', options);

  if (options.mcp) {
    return await runMcpAction();
  }

  if (options.version) {
    await runVersionAction();
    return;
  }

  const version = await getVersion();
  logger.log(pc.dim(`\n📦 Repomix v${version}\n`));

  if (options.init) {
    await runInitAction(cwd, options.global || false);
    return;
  }

  if (options.remote) {
    return await runRemoteAction(options.remote, options);
  }

  return await runDefaultAction(directories, cwd, options);
};
</file>

<file path="src/core/file/fileManipulate.ts">
import path from 'node:path';
import strip from 'strip-comments';

export interface FileManipulator {
  removeComments(content: string): string;
  removeEmptyLines(content: string): string;
}

const rtrimLines = (content: string): string =>
  content
    .split('\n')
    .map((line) => line.trimEnd())
    .join('\n');

class BaseManipulator implements FileManipulator {
  removeComments(content: string): string {
    return content;
  }

  removeEmptyLines(content: string): string {
    return content
      .split('\n')
      .filter((line) => line.trim() !== '')
      .join('\n');
  }
}

class StripCommentsManipulator extends BaseManipulator {
  private language: string;

  constructor(language: string) {
    super();
    this.language = language;
  }

  removeComments(content: string): string {
    const result = strip(content, {
      language: this.language,
      preserveNewlines: true,
    });
    return rtrimLines(result);
  }
}

class CppManipulator extends BaseManipulator {
  removeComments(content: string): string {
    let result = strip(content, {
      language: 'c',
      preserveNewlines: true,
    });

    result = result
      .split('\n')
      .map((line) => {
        const tripleSlashIndex = line.indexOf('///');
        if (tripleSlashIndex !== -1) {
          return line.substring(0, tripleSlashIndex).trimEnd();
        }
        return line;
      })
      .join('\n');

    return rtrimLines(result);
  }
}

class PythonManipulator extends BaseManipulator {
  removeDocStrings(content: string): string {
    if (!content) return '';
    const lines = content.split('\n');

    let result = '';

    let buffer = '';
    let quoteType: '' | "'" | '"' = '';
    let tripleQuotes = 0;

    const doubleQuoteRegex = /^\s*(?<!\\)(?:""")\s*(?:\n)?[\s\S]*?(?<!("""))(?<!\\)(?:""")/gm;
    const singleQuoteRegex = /^\s*(?<!\\)(?:''')\s*(?:\n)?[\s\S]*?(?<!('''))(?<!\\)(?:''')/gm;

    const sz = lines.length;
    for (let i = 0; i < sz; i++) {
      const line = lines[i] + (i !== sz - 1 ? '\n' : '');
      buffer += line;
      if (quoteType === '') {
        const indexSingle = line.search(/(?<![\"])(?<!\\)'''(?![\"])/g);
        const indexDouble = line.search(/(?<![\'])(?<!\\)"""(?![\'])/g);
        if (indexSingle !== -1 && (indexDouble === -1 || indexSingle < indexDouble)) {
          quoteType = "'";
        } else if (indexDouble !== -1 && (indexSingle === -1 || indexDouble < indexSingle)) {
          quoteType = '"';
        }
      }
      if (quoteType === "'") {
        tripleQuotes += (line.match(/(?<![\"])(?<!\\)'''(?!["])/g) || []).length;
      }
      if (quoteType === '"') {
        tripleQuotes += (line.match(/(?<![\'])(?<!\\)"""(?![\'])/g) || []).length;
      }

      if (tripleQuotes % 2 === 0) {
        const docstringRegex = quoteType === '"' ? doubleQuoteRegex : singleQuoteRegex;
        buffer = buffer.replace(docstringRegex, '');
        result += buffer;
        buffer = '';
        tripleQuotes = 0;
        quoteType = '';
      }
    }

    result += buffer;
    return result;
  }

  removeHashComments(content: string): string {
    const searchInPairs = (pairs: [number, number][], hashIndex: number): boolean => {
      return pairs.some(([start, end]) => hashIndex > start && hashIndex < end);
    };

    let result = '';
    const pairs: [number, number][] = [];
    let prevQuote = 0;
    while (prevQuote < content.length) {
      const openingQuote = content.slice(prevQuote + 1).search(/(?<!\\)(?:"|'|'''|""")/g) + prevQuote + 1;
      if (openingQuote === prevQuote) break;

      let closingQuote = -1;
      if (content.startsWith('"""', openingQuote) || content.startsWith("'''", openingQuote)) {
        const quoteType = content.slice(openingQuote, openingQuote + 3);
        closingQuote = content.indexOf(quoteType, openingQuote + 3);
      } else {
        const quoteType = content[openingQuote];
        closingQuote = content.indexOf(quoteType, openingQuote + 1);
      }

      if (closingQuote === -1) break;
      pairs.push([openingQuote, closingQuote]);
      prevQuote = closingQuote;
    }
    let prevHash = 0;
    while (prevHash < content.length) {
      const hashIndex = content.slice(prevHash).search(/(?<!\\)#/g) + prevHash;
      if (hashIndex === prevHash - 1) {
        result += content.slice(prevHash);
        break;
      }

      const isInsideString = searchInPairs(pairs, hashIndex);
      const nextNewLine = content.indexOf('\n', hashIndex);

      if (!isInsideString) {
        if (nextNewLine === -1) {
          result += content.slice(prevHash);
          break;
        }
        result += `${content.slice(prevHash, hashIndex)}\n`;
      } else {
        if (nextNewLine === -1) {
          result += content.slice(prevHash);
          break;
        }
        result += `${content.slice(prevHash, nextNewLine)}\n`;
      }

      prevHash = nextNewLine + 1;
    }
    return result;
  }

  removeComments(content: string): string {
    let result = this.removeDocStrings(content);
    result = this.removeHashComments(result);
    return rtrimLines(result);
  }
}

class CompositeManipulator extends BaseManipulator {
  private manipulators: FileManipulator[];

  constructor(...manipulators: FileManipulator[]) {
    super();
    this.manipulators = manipulators;
  }

  removeComments(content: string): string {
    return this.manipulators.reduce((acc, manipulator) => manipulator.removeComments(acc), content);
  }
}

const manipulators: Record<string, FileManipulator> = {
  '.c': new StripCommentsManipulator('c'),
  '.h': new StripCommentsManipulator('c'),
  '.hpp': new CppManipulator(),
  '.cpp': new CppManipulator(),
  '.cc': new CppManipulator(),
  '.cxx': new CppManipulator(),
  '.cs': new StripCommentsManipulator('csharp'),
  '.css': new StripCommentsManipulator('css'),
  '.dart': new StripCommentsManipulator('c'),
  '.go': new StripCommentsManipulator('c'),
  '.html': new StripCommentsManipulator('html'),
  '.java': new StripCommentsManipulator('java'),
  '.js': new StripCommentsManipulator('javascript'),
  '.jsx': new StripCommentsManipulator('javascript'),
  '.kt': new StripCommentsManipulator('c'),
  '.less': new StripCommentsManipulator('less'),
  '.php': new StripCommentsManipulator('php'),
  '.rb': new StripCommentsManipulator('ruby'),
  '.rs': new StripCommentsManipulator('c'),
  '.sass': new StripCommentsManipulator('sass'),
  '.scss': new StripCommentsManipulator('sass'),
  '.sh': new StripCommentsManipulator('perl'),
  '.sol': new StripCommentsManipulator('c'),
  '.sql': new StripCommentsManipulator('sql'),
  '.swift': new StripCommentsManipulator('swift'),
  '.ts': new StripCommentsManipulator('javascript'),
  '.tsx': new StripCommentsManipulator('javascript'),
  '.xml': new StripCommentsManipulator('xml'),
  '.yaml': new StripCommentsManipulator('perl'),
  '.yml': new StripCommentsManipulator('perl'),

  '.py': new PythonManipulator(),

  '.vue': new CompositeManipulator(
    new StripCommentsManipulator('html'),
    new StripCommentsManipulator('css'),
    new StripCommentsManipulator('javascript'),
  ),
  '.svelte': new CompositeManipulator(
    new StripCommentsManipulator('html'),
    new StripCommentsManipulator('css'),
    new StripCommentsManipulator('javascript'),
  ),
};

export const getFileManipulator = (filePath: string): FileManipulator | null => {
  const ext = path.extname(filePath);
  return manipulators[ext] || null;
};
</file>

<file path="src/core/treeSitter/ext2Lang.ts">
/**
 * @see https://unpkg.com/browse/tree-sitter-wasms@latest/out/
 */
export const ext2Lang = {
  vue: 'vue',
  cjs: 'javascript',
  mjs: 'javascript',
  mjsx: 'javascript',
  js: 'javascript',
  jsx: 'javascript',
  ctx: 'typescript',
  mts: 'typescript',
  mtsx: 'typescript',
  ts: 'typescript',
  tsx: 'typescript',
  h: 'c',
  c: 'c',
  hpp: 'cpp',
  cpp: 'cpp',
  py: 'python',
  rs: 'rust',
  java: 'java',
  go: 'go',
  cs: 'c_sharp',
  rb: 'ruby',
  php: 'php',
  swift: 'swift',
  css: 'css',
  sol: 'solidity',
};
</file>

<file path="src/mcp/tools/mcpToolRuntime.ts">
import crypto from 'node:crypto';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { logger } from '../../shared/logger.js';

// Map to store generated output files
const outputFileRegistry = new Map<string, string>();

// Register an output file
export const registerOutputFile = (id: string, filePath: string): void => {
  outputFileRegistry.set(id, filePath);
};

// Get file path from output ID
export const getOutputFilePath = (id: string): string | undefined => {
  return outputFileRegistry.get(id);
};

export interface McpToolMetrics {
  totalFiles: number;
  totalCharacters: number;
  totalTokens: number;
  fileCharCounts: Record<string, number>;
  fileTokenCounts: Record<string, number>;
}

export interface McpToolContext {
  directory?: string;
  repository?: string;
}

/**
 * Creates a temporary directory for MCP tool operations
 */
export const createToolWorkspace = async (): Promise<string> => {
  try {
    const tmpBaseDir = path.join(os.tmpdir(), 'repomix', 'mcp-outputs');
    await fs.mkdir(tmpBaseDir, { recursive: true });
    const tempDir = await fs.mkdtemp(`${tmpBaseDir}/`);
    return tempDir;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    throw new Error(`Failed to create temporary directory: ${message}`);
  }
};

/**
 * Generate a unique output ID
 */
export const generateOutputId = (): string => {
  return crypto.randomBytes(8).toString('hex');
};

/**
 * Creates a result object with metrics information for MCP tools
 */
export const formatToolResponse = (
  context: McpToolContext,
  metrics: McpToolMetrics,
  outputFilePath: string,
  topFilesLen = 5,
): CallToolResult => {
  // Generate output ID and register the file
  const outputId = generateOutputId();
  registerOutputFile(outputId, outputFilePath);

  // Get top files by character count
  const topFiles = Object.entries(metrics.fileCharCounts)
    .map(([filePath, charCount]) => ({
      path: filePath,
      charCount,
      tokenCount: metrics.fileTokenCounts[filePath] || 0,
    }))
    .sort((a, b) => b.charCount - a.charCount)
    .slice(0, topFilesLen);

  // Create JSON string with all the metrics information
  const jsonResult = JSON.stringify(
    {
      ...(context.directory ? { directory: context.directory } : {}),
      ...(context.repository ? { repository: context.repository } : {}),
      outputFilePath,
      outputId,
      metrics: {
        totalFiles: metrics.totalFiles,
        totalCharacters: metrics.totalCharacters,
        totalTokens: metrics.totalTokens,
        topFiles,
      },
    },
    null,
    2,
  );

  return {
    content: [
      {
        type: 'text',
        text: '🎉 Successfully packed codebase!\nPlease review the metrics below and consider adjusting compress/includePatterns/ignorePatterns if the token count is too high and you need to reduce it before reading the file content.',
      },
      {
        type: 'text',
        text: jsonResult,
      },
      {
        type: 'text',
        text: `For environments with direct file system access, you can read the file directly using path: ${outputFilePath}`,
      },
      {
        type: 'text',
        text: `For environments without direct file access (e.g., web browsers or sandboxed apps), use the \`read_repomix_output\` tool with this outputId: ${outputId} to access the packed codebase contents.`,
      },
    ],
  };
};

/**
 * Creates an error result for MCP tools
 */
export const formatToolError = (error: unknown): CallToolResult => {
  logger.error(`Error in MCP tool: ${error instanceof Error ? error.message : String(error)}`);

  return {
    isError: true,
    content: [
      {
        type: 'text',
        text: JSON.stringify(
          {
            success: false,
            error: error instanceof Error ? error.message : String(error),
          },
          null,
          2,
        ),
      },
    ],
  };
};
</file>

<file path="src/mcp/mcpServer.ts">
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { getVersion } from '../core/file/packageJsonParse.js';
import { logger } from '../shared/logger.js';
import { registerPackRemoteRepositoryPrompt } from './prompts/packRemoteRepositoryPrompts.js';
import { registerFileSystemReadDirectoryTool } from './tools/fileSystemReadDirectoryTool.js';
import { registerFileSystemReadFileTool } from './tools/fileSystemReadFileTool.js';
import { registerPackCodebaseTool } from './tools/packCodebaseTool.js';
import { registerPackRemoteRepositoryTool } from './tools/packRemoteRepositoryTool.js';
import { registerReadRepomixOutputTool } from './tools/readRepomixOutputTool.js';

export const createMcpServer = async () => {
  const mcpServer = new McpServer({
    name: 'repomix-mcp-server',
    version: await getVersion(),
  });

  // Register the prompts
  registerPackRemoteRepositoryPrompt(mcpServer);

  // Register the tools
  registerPackCodebaseTool(mcpServer);
  registerPackRemoteRepositoryTool(mcpServer);
  registerReadRepomixOutputTool(mcpServer);
  registerFileSystemReadFileTool(mcpServer);
  registerFileSystemReadDirectoryTool(mcpServer);

  return mcpServer;
};

type Dependencies = {
  processExit?: (code?: number) => never;
};

const defaultDependencies: Dependencies = {
  processExit: process.exit,
};

export const runMcpServer = async (deps: Dependencies = defaultDependencies) => {
  const server = await createMcpServer();
  const transport = new StdioServerTransport();
  const processExit = deps.processExit ?? process.exit;

  const handleExit = async () => {
    try {
      await server.close();
      logger.trace('Repomix MCP Server shutdown complete');
      processExit(0);
    } catch (error) {
      logger.error('Error during MCP server shutdown:', error);
      processExit(1);
    }
  };

  process.on('SIGINT', handleExit);
  process.on('SIGTERM', handleExit);

  try {
    await server.connect(transport);
    logger.trace('Repomix MCP Server running on stdio');
  } catch (error) {
    logger.error('Failed to start MCP server:', error);
    processExit(1);
  }
};
</file>

<file path="website/client/src/de/guide/mcp-server.md">
# MCP-Server

Repomix unterstützt das [Model Context Protocol (MCP)](https://modelcontextprotocol.io), das es KI-Assistenten ermöglicht, direkt mit Ihrer Codebasis zu interagieren. Wenn Repomix als MCP-Server ausgeführt wird, stellt es Tools bereit, die es KI-Assistenten ermöglichen, lokale oder entfernte Repositories ohne manuelle Dateivorbereitung für die Analyse zu verpacken.

> [!NOTE]  
> Dies ist eine experimentelle Funktion, die wir basierend auf Benutzerfeedback und praktischer Nutzung aktiv verbessern werden

## Repomix als MCP-Server ausführen

Um Repomix als MCP-Server auszuführen, verwenden Sie die `--mcp`-Flag:

```bash
repomix --mcp
```

Dadurch wird Repomix im MCP-Server-Modus gestartet und steht KI-Assistenten zur Verfügung, die das Model Context Protocol unterstützen.

## Verfügbare MCP-Tools

Wenn Repomix als MCP-Server ausgeführt wird, stellt es die folgenden Tools bereit:

### pack_codebase

Dieses Tool verpackt ein lokales Code-Verzeichnis in eine konsolidierte Datei für die KI-Analyse.

**Parameter:**
- `directory`: (Erforderlich) Absoluter Pfad zum zu verpackenden Verzeichnis
- `compress`: (Optional, Standard: true) Ob eine intelligente Code-Extraktion durchgeführt werden soll, um die Token-Anzahl zu reduzieren
- `includePatterns`: (Optional) Kommagetrennte Liste von Einschlussmuster
- `ignorePatterns`: (Optional) Kommagetrennte Liste von Ausschlussmuster

**Beispiel:**
```json
{
  "directory": "/path/to/your/project",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### pack_remote_repository

Dieses Tool holt, klont und verpackt ein GitHub-Repository in eine konsolidierte Datei für die KI-Analyse.

**Parameter:**
- `remote`: (Erforderlich) GitHub-Repository-URL oder user/repo-Format (z.B. yamadashy/repomix)
- `compress`: (Optional, Standard: true) Ob eine intelligente Code-Extraktion durchgeführt werden soll, um die Token-Anzahl zu reduzieren
- `includePatterns`: (Optional) Kommagetrennte Liste von Einschlussmuster
- `ignorePatterns`: (Optional) Kommagetrennte Liste von Ausschlussmuster

**Beispiel:**
```json
{
  "remote": "yamadashy/repomix",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### file_system_read_file und file_system_read_directory

Der Repomix MCP-Server bietet zwei Dateisystemwerkzeuge, die es KI-Assistenten ermöglichen, sicher mit dem lokalen Dateisystem zu interagieren:

1. `file_system_read_file`
   - Liest Dateiinhalte unter Verwendung absoluter Pfade
   - Implementiert Sicherheitsvalidierung mit [Secretlint](https://github.com/secretlint/secretlint)
   - Verhindert den Zugriff auf Dateien mit sensiblen Informationen
   - Liefert klare Fehlermeldungen für ungültige Pfade und Sicherheitsprobleme

2. `file_system_read_directory`
   - Listet Verzeichnisinhalte unter Verwendung absoluter Pfade
   - Zeigt Dateien und Verzeichnisse mit klaren Indikatoren (`[FILE]` oder `[DIR]`)
   - Bietet sichere Verzeichnisnavigation mit angemessener Fehlerbehandlung
   - Validiert Pfade und stellt sicher, dass sie absolut sind

Beide Werkzeuge beinhalten robuste Sicherheitsmaßnahmen:
- Validierung absoluter Pfade zur Verhinderung von Directory Traversal-Angriffen
- Berechtigungsprüfungen zur Gewährleistung angemessener Zugriffsrechte
- Integration mit Secretlint zur Erkennung sensibler Informationen
- Klare Fehlermeldungen für Debugging und Sicherheitsbewusstsein

**Beispiel:**
```typescript
// Datei lesen
const fileContent = await tools.file_system_read_file({
  path: '/absolute/path/to/file.txt'
});

// Verzeichnisinhalt auflisten
const dirContent = await tools.file_system_read_directory({
  path: '/absolute/path/to/directory'
});
```

Diese Werkzeuge sind besonders nützlich, wenn KI-Assistenten:
- Bestimmte Dateien im Codebase analysieren müssen
- Verzeichnisstrukturen navigieren müssen
- Existenz und Zugänglichkeit von Dateien überprüfen müssen
- Sichere Dateisystemoperationen gewährleisten müssen

## MCP-Server konfigurieren

Um Repomix als MCP-Server mit KI-Assistenten wie Claude zu verwenden, müssen Sie die MCP-Einstellungen konfigurieren:

### Für Cline (VS Code-Erweiterung)

Bearbeiten Sie die `cline_mcp_settings.json`-Datei:

```json
{
  "mcpServers": {
    "repomix": {
      "command": "npx",
      "args": [
        "-y",
        "repomix",
        "--mcp"
      ]
    }
  }
}
```

### Für Claude Desktop

Bearbeiten Sie die `claude_desktop_config.json`-Datei mit einer ähnlichen Konfiguration wie bei Cline.

## Vorteile der Verwendung von Repomix als MCP-Server

Die Verwendung von Repomix als MCP-Server bietet mehrere Vorteile:

1. **Direkte Integration**: KI-Assistenten können Ihre Codebasis ohne manuelle Dateivorbereitung direkt analysieren.
2. **Effizienter Workflow**: Optimiert den Prozess der Codeanalyse, indem die Notwendigkeit entfällt, Dateien manuell zu generieren und hochzuladen.
3. **Konsistente Ausgabe**: Stellt sicher, dass der KI-Assistent die Codebasis in einem konsistenten, optimierten Format erhält.
4. **Erweiterte Funktionen**: Nutzt alle Funktionen von Repomix wie Code-Komprimierung, Token-Zählung und Sicherheitsprüfungen.

Nach der Konfiguration kann Ihr KI-Assistent die Funktionen von Repomix direkt nutzen, um Codebasen zu analysieren, was Codeanalyse-Workflows effizienter macht.
</file>

<file path="llms-install.md">
# Repomix MCP Server Installation Guide

This guide is specifically designed for AI agents like Cline to install and configure the Repomix MCP server for use with LLM applications like Claude Desktop, Cursor, Roo Code, and Cline.

## Overview

Repomix MCP server is a powerful tool that packages local or remote codebases into AI-friendly formats. It allows AI assistants to analyze code efficiently without manual file preparation, optimizing token usage and providing consistent output.

## Prerequisites

Before installation, you need:

1. Node.js 18.0.0 or higher
2. npm (Node Package Manager)

## Installation and Configuration

### Configure MCP Settings

Add the Repomix MCP server configuration to your MCP settings file based on your LLM client:

#### Configuration File Locations

- Cline (VS Code Extension): `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
- Roo Code (VS Code Extension): `~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json`
- Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Cursor: `[project root]/.cursor/mcp.json`

Add this configuration to your chosen client's settings file:

```json
{
  "mcpServers": {
    "repomix": {
      "command": "npx",
      "args": [
        "-y",
        "repomix",
        "--mcp"
      ],
      "disabled": false,
      "autoApprove": []
    }
  }
}
```

This configuration uses `npx` to run Repomix directly without requiring a global installation.

## Available MCP Tools

Once configured, you'll have access to these Repomix tools:

### 1. pack_codebase

This tool packages a local code directory into a consolidated file for AI analysis.

**Parameters:**
- `directory`: (Required) Absolute path to the directory to pack
- `compress`: (Optional, default: true) Whether to perform intelligent code extraction to reduce token count
- `includePatterns`: (Optional) Comma-separated list of include patterns
- `ignorePatterns`: (Optional) Comma-separated list of ignore patterns

**Example:**
```json
{
  "directory": "/path/to/your/project",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### 2. pack_remote_repository

This tool fetches, clones, and packages a GitHub repository into a consolidated file for AI analysis.

**Parameters:**
- `remote`: (Required) GitHub repository URL or user/repo format (e.g., yamadashy/repomix)
- `compress`: (Optional, default: true) Whether to perform intelligent code extraction to reduce token count
- `includePatterns`: (Optional) Comma-separated list of include patterns
- `ignorePatterns`: (Optional) Comma-separated list of ignore patterns

**Example:**
```json
{
  "remote": "yamadashy/repomix",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### 3. read_repomix_output

This tool reads the contents of a Repomix output file in environments where direct file access is not possible.

**Parameters:**
- `outputId`: (Required) ID of the Repomix output file to read

**Features:**
- Specifically designed for web-based environments or sandboxed applications
- Retrieves the content of previously generated outputs using their ID
- Provides secure access to packed codebase without requiring file system access

**Example:**
```json
{
  "outputId": "8f7d3b1e2a9c6054"
}
```

### 4. file_system_read_file

This tool reads a file using an absolute path with security validation.

**Parameters:**
- `path`: (Required) Absolute path to the file to read

**Security features:**
- Implements security validation using [Secretlint](https://github.com/secretlint/secretlint)
- Prevents access to files containing sensitive information
- Validates absolute paths to prevent directory traversal attacks

**Example:**
```json
{
  "path": "/absolute/path/to/file.txt"
}
```

### 5. file_system_read_directory

This tool lists contents of a directory using an absolute path.

**Parameters:**
- `path`: (Required) Absolute path to the directory to list

**Features:**
- Shows files and directories with clear indicators (`[FILE]` or `[DIR]`)
- Provides safe directory traversal with proper error handling
- Validates paths and ensures they are absolute

**Example:**
```json
{
  "path": "/absolute/path/to/directory"
}
```

## Verify Installation

To verify the installation is working:

1. Restart your LLM application (Cline, Claude Desktop, etc.)
2. Test the connection by running a simple command like:
   ```
   Please package the local directory /path/to/project for AI analysis using Repomix.
   ```
   or
   ```
   Please fetch and package the GitHub repository yamadashy/repomix for AI analysis.
   ```

## Usage Examples

Here are some examples of how to use Repomix MCP server with AI assistants:

### Local Codebase Analysis

```
Can you analyze the code in my project at /path/to/project? Please use Repomix to package it first.
```

### Remote Repository Analysis

```
I'd like you to review the code in the GitHub repository username/repo. Please use Repomix to package it first.
```

### Specific File Types Analysis

```
Please package my project at /path/to/project, but only include TypeScript files and markdown documentation.
```

## Troubleshooting

### Common Issues and Solutions

1. **MCP server connection issues**
   - Verify the syntax in your MCP settings file is correct
   - Ensure you have an active internet connection (needed for npx to fetch the package)
   - Check if any other MCP servers are causing conflicts

2. **Packaging failures**
   - Verify the specified directory or repository exists
   - Check if you have sufficient disk space
   - For remote repositories, ensure you have internet connectivity
   - Try with simpler parameters first, then add complexity

3. **JSON parsing errors in configuration**
   - Make sure your MCP settings file is properly formatted
   - Verify all paths use forward slashes, even on Windows
   - Check for any missing commas or brackets in the configuration

## Additional Information

For more detailed information, visit the [Repomix official documentation](https://repomix.com). You can also join the [Discord community](https://discord.gg/wNYzTwZFku) for support and questions.
</file>

<file path="website/client/src/en/guide/mcp-server.md">
# MCP Server

Repomix supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io), allowing AI assistants to directly interact with your codebase. When run as an MCP server, Repomix provides tools that enable AI assistants to package local or remote repositories for analysis without requiring manual file preparation.

> [!NOTE]  
> This is an experimental feature that we'll be actively improving based on user feedback and real-world usage

## Running Repomix as an MCP Server

To run Repomix as an MCP server, use the `--mcp` flag:

```bash
repomix --mcp
```

This starts Repomix in MCP server mode, making it available for AI assistants that support the Model Context Protocol.

## Available MCP Tools

When running as an MCP server, Repomix provides the following tools:

### pack_codebase

This tool packages a local code directory into a consolidated file for AI analysis.

**Parameters:**
- `directory`: (Required) Absolute path to the directory to pack
- `compress`: (Optional, default: true) Whether to perform intelligent code extraction to reduce token count
- `includePatterns`: (Optional) Comma-separated list of include patterns
- `ignorePatterns`: (Optional) Comma-separated list of ignore patterns

**Example:**
```json
{
  "directory": "/path/to/your/project",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### pack_remote_repository

This tool fetches, clones, and packages a GitHub repository into a consolidated file for AI analysis.

**Parameters:**
- `remote`: (Required) GitHub repository URL or user/repo format (e.g., yamadashy/repomix)
- `compress`: (Optional, default: true) Whether to perform intelligent code extraction to reduce token count
- `includePatterns`: (Optional) Comma-separated list of include patterns
- `ignorePatterns`: (Optional) Comma-separated list of ignore patterns

**Example:**
```json
{
  "remote": "yamadashy/repomix",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### read_repomix_output

This tool reads the contents of a Repomix output file in environments where direct file access is not possible.

**Parameters:**
- `outputId`: (Required) ID of the Repomix output file to read

**Features:**
- Specifically designed for web-based environments or sandboxed applications
- Retrieves the content of previously generated outputs using their ID
- Provides secure access to packed codebase without requiring file system access

**Example:**
```json
{
  "outputId": "8f7d3b1e2a9c6054"
}
```

### file_system_read_file and file_system_read_directory

Repomix's MCP server provides two file system tools that allow AI assistants to safely interact with the local file system:

1. `file_system_read_file`
   - Reads file contents using absolute paths
   - Implements security validation using [Secretlint](https://github.com/secretlint/secretlint)
   - Prevents access to files containing sensitive information
   - Returns formatted content with clear error messages for invalid paths or security issues

2. `file_system_read_directory`
   - Lists directory contents using absolute paths
   - Shows both files and directories with clear indicators (`[FILE]` or `[DIR]`)
   - Provides safe directory traversal with proper error handling
   - Validates paths and ensures they are absolute

Both tools incorporate robust security measures:
- Absolute path validation to prevent directory traversal attacks
- Permission checks to ensure proper access rights
- Integration with Secretlint for sensitive information detection
- Clear error messaging for better debugging and security awareness

**Example:**
```typescript
// Reading a file
const fileContent = await tools.file_system_read_file({
  path: '/absolute/path/to/file.txt'
});

// Listing directory contents
const dirContent = await tools.file_system_read_directory({
  path: '/absolute/path/to/directory'
});
```

These tools are particularly useful when AI assistants need to:
- Analyze specific files in the codebase
- Navigate directory structures
- Verify file existence and accessibility
- Ensure secure file system operations

## Configuring MCP Servers

To use Repomix as an MCP server with AI assistants like Claude, you need to configure the MCP settings:

### For Cline (VS Code extension)

Edit the `cline_mcp_settings.json` file:

```json
{
  "mcpServers": {
    "repomix": {
      "command": "npx",
      "args": [
        "-y",
        "repomix",
        "--mcp"
      ]
    }
  }
}
```

### For Claude Desktop

Edit the `claude_desktop_config.json` file with similar configuration to Cline's config.

## Benefits of Using Repomix as an MCP Server

Using Repomix as an MCP server offers several advantages:

1. **Direct Integration**: AI assistants can directly analyze your codebase without manual file preparation.
2. **Efficient Workflow**: Streamlines the process of code analysis by eliminating the need to manually generate and upload files.
3. **Consistent Output**: Ensures that the AI assistant receives the codebase in a consistent, optimized format.
4. **Advanced Features**: Leverages all of Repomix's features like code compression, token counting, and security checks.

Once configured, your AI assistant can directly use Repomix's capabilities to analyze codebases, making code analysis workflows more efficient.
</file>

<file path="website/client/src/es/guide/mcp-server.md">
# Servidor MCP

Repomix es compatible con el [Model Context Protocol (MCP)](https://modelcontextprotocol.io), lo que permite a los asistentes de IA interactuar directamente con tu código. Cuando se ejecuta como servidor MCP, Repomix proporciona herramientas que permiten a los asistentes de IA empaquetar repositorios locales o remotos para su análisis sin necesidad de preparación manual de archivos.

> [!NOTE]  
> Esta es una función experimental que mejoraremos activamente según los comentarios de los usuarios y el uso en el mundo real

## Ejecutar Repomix como servidor MCP

Para ejecutar Repomix como servidor MCP, utiliza la opción `--mcp`:

```bash
repomix --mcp
```

Esto inicia Repomix en modo servidor MCP, haciéndolo disponible para asistentes de IA que soporten el Model Context Protocol.

## Herramientas MCP disponibles

Cuando se ejecuta como servidor MCP, Repomix proporciona las siguientes herramientas:

### pack_codebase

Esta herramienta empaqueta un directorio de código local en un archivo consolidado para análisis de IA.

**Parámetros:**
- `directory`: (Requerido) Ruta absoluta al directorio a empaquetar
- `compress`: (Opcional, predeterminado: true) Si se debe realizar extracción inteligente de código para reducir el recuento de tokens
- `includePatterns`: (Opcional) Lista separada por comas de patrones de inclusión
- `ignorePatterns`: (Opcional) Lista separada por comas de patrones de exclusión

**Ejemplo:**
```json
{
  "directory": "/path/to/your/project",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### pack_remote_repository

Esta herramienta obtiene, clona y empaqueta un repositorio de GitHub en un archivo consolidado para análisis de IA.

**Parámetros:**
- `remote`: (Requerido) URL del repositorio de GitHub o formato usuario/repo (ej. yamadashy/repomix)
- `compress`: (Opcional, predeterminado: true) Si se debe realizar extracción inteligente de código para reducir el recuento de tokens
- `includePatterns`: (Opcional) Lista separada por comas de patrones de inclusión
- `ignorePatterns`: (Opcional) Lista separada por comas de patrones de exclusión

**Ejemplo:**
```json
{
  "remote": "yamadashy/repomix",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### read_repomix_output

Esta herramienta lee el contenido de un archivo de salida de Repomix en entornos donde no es posible el acceso directo a archivos.

**Parámetros:**
- `outputId`: (Requerido) ID del archivo de salida de Repomix para leer

**Características:**
- Diseñado específicamente para entornos basados en web o aplicaciones en sandbox
- Recupera el contenido de salidas generadas previamente usando su ID
- Proporciona acceso seguro al código empaquetado sin requerir acceso al sistema de archivos

**Ejemplo:**
```json
{
  "outputId": "8f7d3b1e2a9c6054"
}
```

### file_system_read_file y file_system_read_directory

El servidor MCP de Repomix proporciona dos herramientas de sistema de archivos que permiten a los asistentes de IA interactuar de manera segura con el sistema de archivos local:

1. `file_system_read_file`
   - Lee contenido de archivos usando rutas absolutas
   - Implementa validación de seguridad usando [Secretlint](https://github.com/secretlint/secretlint)
   - Previene el acceso a archivos que contienen información sensible
   - Devuelve mensajes de error claros para rutas inválidas y problemas de seguridad

2. `file_system_read_directory`
   - Lista contenidos de directorios usando rutas absolutas
   - Muestra archivos y directorios con indicadores claros (`[FILE]` o `[DIR]`)
   - Proporciona navegación segura de directorios con manejo apropiado de errores
   - Valida rutas y asegura que sean absolutas

Ambas herramientas incorporan medidas de seguridad robustas:
- Validación de rutas absolutas para prevenir ataques de traversal de directorios
- Verificaciones de permisos para asegurar derechos de acceso apropiados
- Integración con Secretlint para detección de información sensible
- Mensajes de error claros para depuración y conciencia de seguridad

**Ejemplo:**
```typescript
// Leer un archivo
const fileContent = await tools.file_system_read_file({
  path: '/absolute/path/to/file.txt'
});

// Listar contenidos de directorio
const dirContent = await tools.file_system_read_directory({
  path: '/absolute/path/to/directory'
});
```

Estas herramientas son particularmente útiles cuando los asistentes de IA necesitan:
- Analizar archivos específicos en el código base
- Navegar estructuras de directorios
- Verificar existencia y accesibilidad de archivos
- Asegurar operaciones seguras del sistema de archivos

## Configuración de servidores MCP

Para usar Repomix como servidor MCP con asistentes de IA como Claude, necesitas configurar los ajustes de MCP:

### Para Cline (extensión de VS Code)

Edita el archivo `cline_mcp_settings.json`:

```json
{
  "mcpServers": {
    "repomix": {
      "command": "npx",
      "args": [
        "-y",
        "repomix",
        "--mcp"
      ]
    }
  }
}
```

### Para Claude Desktop

Edita el archivo `claude_desktop_config.json` con una configuración similar a la de Cline.

## Beneficios de usar Repomix como servidor MCP

Usar Repomix como servidor MCP ofrece varias ventajas:

1. **Integración directa**: Los asistentes de IA pueden analizar tu código directamente sin preparación manual de archivos.
2. **Flujo de trabajo eficiente**: Optimiza el proceso de análisis de código eliminando la necesidad de generar y cargar archivos manualmente.
3. **Salida consistente**: Asegura que el asistente de IA reciba el código en un formato consistente y optimizado.
4. **Funciones avanzadas**: Aprovecha todas las características de Repomix como compresión de código, conteo de tokens y verificaciones de seguridad.

Una vez configurado, tu asistente de IA puede usar directamente las capacidades de Repomix para analizar bases de código, haciendo que los flujos de trabajo de análisis de código sean más eficientes.
</file>

<file path="website/client/src/ja/guide/mcp-server.md">
# MCPサーバー

Repomixは[Model Context Protocol (MCP)](https://modelcontextprotocol.io)をサポートしており、AIアシスタントがコードベースと直接対話できるようになります。MCPサーバーとして実行すると、Repomixはローカルまたはリモートリポジトリを手動でファイル準備することなく、AI分析用にパッケージ化するツールを提供します。

> [!NOTE]  
> これは実験的な機能であり、ユーザーのフィードバックと実際の使用状況に基づいて積極的に改善を進めていきます

## RepomixをMCPサーバーとして実行する

RepomixをMCPサーバーとして実行するには、`--mcp`フラグを使用します：

```bash
repomix --mcp
```

これによりRepomixがMCPサーバーモードで起動し、Model Context ProtocolをサポートするAIアシスタントから利用できるようになります。

## 利用可能なMCPツール

MCPサーバーとして実行すると、Repomixは以下のツールを提供します：

### pack_codebase

このツールはローカルのコードディレクトリをAI分析用に単一ファイルにパッケージ化します。

**パラメータ：**
- `directory`: (必須) パッケージ化するディレクトリの絶対パス
- `compress`: (オプション、デフォルト: true) トークン数を削減するためのインテリジェントなコード抽出を実行するかどうか
- `includePatterns`: (オプション) カンマ区切りの包含パターンリスト
- `ignorePatterns`: (オプション) カンマ区切りの除外パターンリスト

**例：**
```json
{
  "directory": "/path/to/your/project",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### pack_remote_repository

このツールはGitHubリポジトリを取得、クローン、パッケージ化してAI分析用の単一ファイルを作成します。

**パラメータ：**
- `remote`: (必須) GitHubリポジトリURLまたはuser/repo形式（例：yamadashy/repomix）
- `compress`: (オプション、デフォルト: true) トークン数を削減するためのインテリジェントなコード抽出を実行するかどうか
- `includePatterns`: (オプション) カンマ区切りの包含パターンリスト
- `ignorePatterns`: (オプション) カンマ区切りの除外パターンリスト

**例：**
```json
{
  "remote": "yamadashy/repomix",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### read_repomix_output

このツールは直接ファイルアクセスできない環境でRepomix出力ファイルの内容を読み込みます。

**パラメータ：**
- `outputId`: (必須) 読み込むRepomix出力ファイルのID

**機能：**
- ウェブベース環境やサンドボックスアプリケーション向けに特別に設計
- IDを使用して以前に生成された出力の内容を取得
- ファイルシステムアクセスを必要とせずにパッケージ化されたコードベースへの安全なアクセスを提供

**例：**
```json
{
  "outputId": "8f7d3b1e2a9c6054"
}
```

### file_system_read_file と file_system_read_directory

RepomixのMCPサーバーは、AIアシスタントがローカルファイルシステムと安全にやり取りするための2つのファイルシステムツールを提供しています：

1. `file_system_read_file`
   - 絶対パスを使用してファイルの内容を読み取り
   - [Secretlint](https://github.com/secretlint/secretlint)を使用したセキュリティ検証を実装
   - 機密情報を含むファイルへのアクセスを防止
   - 無効なパスやセキュリティの問題に対する明確なエラーメッセージを返す

2. `file_system_read_directory`
   - 絶対パスを使用してディレクトリの内容を一覧表示
   - ファイルとディレクトリを明確な指標（`[FILE]`または`[DIR]`）で表示
   - 適切なエラー処理による安全なディレクトリ走査を提供
   - パスの検証と絶対パスの確認を実施

両ツールは堅牢なセキュリティ対策を組み込んでいます：
- ディレクトリトラバーサル攻撃を防ぐための絶対パス検証
- 適切なアクセス権を確保するための権限チェック
- 機密情報検出のためのSecretlintとの統合
- デバッグとセキュリティ認識のための明確なエラーメッセージ

**例：**
```typescript
// ファイルの読み取り
const fileContent = await tools.file_system_read_file({
  path: '/absolute/path/to/file.txt'
});

// ディレクトリの内容一覧
const dirContent = await tools.file_system_read_directory({
  path: '/absolute/path/to/directory'
});
```

これらのツールは、AIアシスタントが以下のような操作を必要とする場合に特に有用です：
- コードベース内の特定のファイルを分析
- ディレクトリ構造をナビゲート
- ファイルの存在とアクセス可能性を確認
- 安全なファイルシステム操作を確保

## MCPサーバーの設定

RepomixをMCPサーバーとしてClaudeなどのAIアシスタントで使用するには、MCP設定を構成する必要があります：

### Cline（VS Code拡張機能）の場合

`cline_mcp_settings.json`ファイルを編集します：

```json
{
  "mcpServers": {
    "repomix": {
      "command": "npx",
      "args": [
        "-y",
        "repomix",
        "--mcp"
      ]
    }
  }
}
```

### Claude Desktopの場合

`claude_desktop_config.json`ファイルをClineの設定と同様に編集します。

## RepomixをMCPサーバーとして使用する利点

RepomixをMCPサーバーとして使用すると、いくつかの利点があります：

1. **直接統合**: AIアシスタントが手動でファイルを準備することなく、コードベースを直接分析できます。
2. **効率的なワークフロー**: ファイルを手動で生成してアップロードする必要がなくなり、コード分析のプロセスが効率化されます。
3. **一貫した出力**: AIアシスタントが一貫性のある最適化された形式でコードベースを受け取ることができます。
4. **高度な機能**: コード圧縮、トークンカウント、セキュリティチェックなど、Repomixのすべての機能を活用できます。

設定が完了すると、AIアシスタントはRepomixの機能を直接使用してコードベースを分析できるようになり、コード分析ワークフローがより効率的になります。
</file>

<file path="website/client/src/ko/guide/mcp-server.md">
# MCP 서버

Repomix는 [Model Context Protocol (MCP)](https://modelcontextprotocol.io)를 지원하며, AI 어시스턴트가 코드베이스와 직접 상호작용할 수 있게 해줍니다. MCP 서버로 실행하면 Repomix는 AI 어시스턴트가 수동 파일 준비 없이 로컬 또는 원격 저장소를 분석용으로 패키징할 수 있는 도구를 제공합니다.

> [!NOTE]  
> 이것은 실험적인 기능으로, 사용자 피드백과 실제 사용 사례를 바탕으로 지속적으로 개선해 나갈 예정입니다

## Repomix를 MCP 서버로 실행하기

Repomix를 MCP 서버로 실행하려면 `--mcp` 플래그를 사용하세요:

```bash
repomix --mcp
```

이렇게 하면 Repomix가 MCP 서버 모드로 시작되어 Model Context Protocol을 지원하는 AI 어시스턴트에서 사용할 수 있게 됩니다.

## 사용 가능한 MCP 도구

MCP 서버로 실행할 때 Repomix는 다음 도구를 제공합니다:

### pack_codebase

이 도구는 로컬 코드 디렉토리를 AI 분석용 통합 파일로 패키징합니다.

**매개변수:**
- `directory`: (필수) 패키징할 디렉토리의 절대 경로
- `compress`: (선택, 기본값: true) 토큰 수를 줄이기 위해 지능형 코드 추출을 수행할지 여부
- `includePatterns`: (선택) 쉼표로 구분된 포함 패턴 목록
- `ignorePatterns`: (선택) 쉼표로 구분된 제외 패턴 목록

**예시:**
```json
{
  "directory": "/path/to/your/project",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### pack_remote_repository

이 도구는 GitHub 저장소를 가져와 클론하고 AI 분석용 통합 파일로 패키징합니다.

**매개변수:**
- `remote`: (필수) GitHub 저장소 URL 또는 사용자/저장소 형식(예: yamadashy/repomix)
- `compress`: (선택, 기본값: true) 토큰 수를 줄이기 위해 지능형 코드 추출을 수행할지 여부
- `includePatterns`: (선택) 쉼표로 구분된 포함 패턴 목록
- `ignorePatterns`: (선택) 쉼표로 구분된 제외 패턴 목록

**예시:**
```json
{
  "remote": "yamadashy/repomix",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### read_repomix_output

이 도구는 직접 파일 접근이 불가능한 환경에서 Repomix 출력 파일의 내용을 읽습니다.

**매개변수:**
- `outputId`: (필수) 읽을 Repomix 출력 파일의 ID

**기능:**
- 웹 기반 환경이나 샌드박스 애플리케이션을 위해 특별히 설계됨
- ID를 사용하여 이전에 생성된 출력의 내용을 검색
- 파일 시스템 접근 없이 패키징된 코드베이스에 안전하게 접근 제공

**예시:**
```json
{
  "outputId": "8f7d3b1e2a9c6054"
}
```

### file_system_read_file 및 file_system_read_directory

Repomix의 MCP 서버는 AI 어시스턴트가 로컬 파일 시스템과 안전하게 상호 작용할 수 있는 두 가지 파일 시스템 도구를 제공합니다:

1. `file_system_read_file`
   - 절대 경로를 사용하여 파일 내용 읽기
   - [Secretlint](https://github.com/secretlint/secretlint)를 사용한 보안 검증 구현
   - 민감한 정보가 포함된 파일에 대한 접근 방지
   - 잘못된 경로나 보안 문제에 대한 명확한 오류 메시지 반환

2. `file_system_read_directory`
   - 절대 경로를 사용하여 디렉토리 내용 나열
   - 파일과 디렉토리를 명확한 지표(`[FILE]` 또는 `[DIR]`)로 표시
   - 안전한 디렉토리 탐색과 적절한 오류 처리 제공
   - 경로 검증 및 절대 경로 확인

두 도구 모두 강력한 보안 조치를 포함하고 있습니다:
- 디렉토리 순회 공격을 방지하기 위한 절대 경로 검증
- 적절한 접근 권한을 보장하기 위한 권한 검사
- 민감한 정보 감지를 위한 Secretlint 통합
- 디버깅과 보안 인식을 위한 명확한 오류 메시지

**예시:**
```typescript
// 파일 읽기
const fileContent = await tools.file_system_read_file({
  path: '/absolute/path/to/file.txt'
});

// 디렉토리 내용 나열
const dirContent = await tools.file_system_read_directory({
  path: '/absolute/path/to/directory'
});
```

이러한 도구는 AI 어시스턴트가 다음과 같은 작업을 수행해야 할 때 특히 유용합니다:
- 코드베이스의 특정 파일 분석
- 디렉토리 구조 탐색
- 파일 존재 여부 및 접근 가능성 확인
- 안전한 파일 시스템 작업 보장

## MCP 서버 구성하기

Claude와 같은 AI 어시스턴트와 함께 Repomix를 MCP 서버로 사용하려면 MCP 설정을 구성해야 합니다:

### Cline(VS Code 확장)의 경우

`cline_mcp_settings.json` 파일을 편집하세요:

```json
{
  "mcpServers": {
    "repomix": {
      "command": "npx",
      "args": [
        "-y",
        "repomix",
        "--mcp"
      ]
    }
  }
}
```

### Claude Desktop의 경우

Cline의 구성과 유사하게 `claude_desktop_config.json` 파일을 편집하세요.

## Repomix를 MCP 서버로 사용하는 이점

Repomix를 MCP 서버로 사용하면 여러 이점이 있습니다:

1. **직접 통합**: AI 어시스턴트가 수동 파일 준비 없이 코드베이스를 직접 분석할 수 있습니다.
2. **효율적인 워크플로우**: 파일을 수동으로 생성하고 업로드할 필요가 없어 코드 분석 프로세스가 간소화됩니다.
3. **일관된 출력**: AI 어시스턴트가 일관되고 최적화된 형식으로 코드베이스를 받을 수 있습니다.
4. **고급 기능**: 코드 압축, 토큰 카운팅, 보안 검사와 같은 Repomix의 모든 기능을 활용할 수 있습니다.

구성이 완료되면 AI 어시스턴트가 Repomix의 기능을 직접 사용하여 코드베이스를 분석할 수 있어 코드 분석 워크플로우가 더 효율적이 됩니다.
</file>

<file path="website/client/src/pt-br/guide/mcp-server.md">
# Servidor MCP

O Repomix suporta o [Model Context Protocol (MCP)](https://modelcontextprotocol.io), permitindo que assistentes de IA interajam diretamente com sua base de código. Quando executado como um servidor MCP, o Repomix fornece ferramentas que permitem aos assistentes de IA empacotar repositórios locais ou remotos para análise sem necessidade de preparação manual de arquivos.

> [!NOTE]  
> Este é um recurso experimental que estaremos melhorando ativamente com base no feedback dos usuários e no uso no mundo real

## Executando o Repomix como um Servidor MCP

Para executar o Repomix como um servidor MCP, use a flag `--mcp`:

```bash
repomix --mcp
```

Isso inicia o Repomix no modo servidor MCP, tornando-o disponível para assistentes de IA que suportam o Model Context Protocol.

## Ferramentas MCP Disponíveis

Quando executado como um servidor MCP, o Repomix fornece as seguintes ferramentas:

### pack_codebase

Esta ferramenta empacota um diretório de código local em um arquivo consolidado para análise de IA.

**Parâmetros:**
- `directory`: (Obrigatório) Caminho absoluto para o diretório a ser empacotado
- `compress`: (Opcional, padrão: true) Se deve realizar extração inteligente de código para reduzir a contagem de tokens
- `includePatterns`: (Opcional) Lista separada por vírgulas de padrões de inclusão
- `ignorePatterns`: (Opcional) Lista separada por vírgulas de padrões de exclusão

**Exemplo:**
```json
{
  "directory": "/path/to/your/project",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### pack_remote_repository

Esta ferramenta busca, clona e empacota um repositório GitHub em um arquivo consolidado para análise de IA.

**Parâmetros:**
- `remote`: (Obrigatório) URL do repositório GitHub ou formato usuário/repo (ex: yamadashy/repomix)
- `compress`: (Opcional, padrão: true) Se deve realizar extração inteligente de código para reduzir a contagem de tokens
- `includePatterns`: (Opcional) Lista separada por vírgulas de padrões de inclusão
- `ignorePatterns`: (Opcional) Lista separada por vírgulas de padrões de exclusão

**Exemplo:**
```json
{
  "remote": "yamadashy/repomix",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### read_repomix_output

Esta ferramenta lê o conteúdo de um arquivo de saída do Repomix em ambientes onde o acesso direto a arquivos não é possível.

**Parâmetros:**
- `outputId`: (Obrigatório) ID do arquivo de saída do Repomix a ser lido

**Funcionalidades:**
- Projetado especificamente para ambientes baseados na web ou aplicações em sandbox
- Recupera o conteúdo de saídas geradas anteriormente usando seu ID
- Fornece acesso seguro à base de código empacotada sem requerer acesso ao sistema de arquivos

**Exemplo:**
```json
{
  "outputId": "8f7d3b1e2a9c6054"
}
```

### file_system_read_file e file_system_read_directory

O servidor MCP do Repomix fornece duas ferramentas de sistema de arquivos que permitem que os assistentes de IA interajam com segurança com o sistema de arquivos local:

1. `file_system_read_file`
   - Lê conteúdo de arquivos usando caminhos absolutos
   - Implementa validação de segurança usando [Secretlint](https://github.com/secretlint/secretlint)
   - Previne acesso a arquivos contendo informações sensíveis
   - Retorna mensagens de erro claras para caminhos inválidos e problemas de segurança

2. `file_system_read_directory`
   - Lista conteúdos de diretórios usando caminhos absolutos
   - Mostra arquivos e diretórios com indicadores claros (`[FILE]` ou `[DIR]`)
   - Fornece navegação segura de diretórios com tratamento apropriado de erros
   - Valida caminhos e garante que sejam absolutos

Ambas as ferramentas incorporam medidas de segurança robustas:
- Validação de caminhos absolutos para prevenir ataques de travessia de diretórios
- Verificações de permissões para garantir direitos de acesso apropriados
- Integração com Secretlint para detecção de informações sensíveis
- Mensagens de erro claras para depuração e consciência de segurança

**Exemplo:**
```typescript
// Ler um arquivo
const fileContent = await tools.file_system_read_file({
  path: '/absolute/path/to/file.txt'
});

// Listar conteúdo do diretório
const dirContent = await tools.file_system_read_directory({
  path: '/absolute/path/to/directory'
});
```

Essas ferramentas são particularmente úteis quando os assistentes de IA precisam:
- Analisar arquivos específicos no código-base
- Navegar estruturas de diretórios
- Verificar existência e acessibilidade de arquivos
- Garantir operações seguras do sistema de arquivos

## Configurando Servidores MCP

Para usar o Repomix como um servidor MCP com assistentes de IA como o Claude, você precisa configurar as definições do MCP:

### Para o Cline (extensão do VS Code)

Edite o arquivo `cline_mcp_settings.json`:

```json
{
  "mcpServers": {
    "repomix": {
      "command": "npx",
      "args": [
        "-y",
        "repomix",
        "--mcp"
      ]
    }
  }
}
```

### Para o Claude Desktop

Edite o arquivo `claude_desktop_config.json` com uma configuração similar à do Cline.

## Benefícios de Usar o Repomix como um Servidor MCP

Usar o Repomix como um servidor MCP oferece várias vantagens:

1. **Integração Direta**: Assistentes de IA podem analisar sua base de código diretamente sem preparação manual de arquivos.
2. **Fluxo de Trabalho Eficiente**: Otimiza o processo de análise de código eliminando a necessidade de gerar e carregar arquivos manualmente.
3. **Saída Consistente**: Garante que o assistente de IA receba a base de código em um formato consistente e otimizado.
4. **Recursos Avançados**: Aproveita todos os recursos do Repomix como compressão de código, contagem de tokens e verificações de segurança.

Uma vez configurado, seu assistente de IA pode usar diretamente as capacidades do Repomix para analisar bases de código, tornando os fluxos de trabalho de análise de código mais eficientes.
</file>

<file path="website/client/src/zh-cn/guide/mcp-server.md">
# MCP服务器

Repomix 支持 [Model Context Protocol (MCP)](https://modelcontextprotocol.io)，允许 AI 助手直接与您的代码库交互。当作为 MCP 服务器运行时，Repomix 提供了工具，使 AI 助手能够在无需手动准备文件的情况下打包本地或远程仓库进行分析。

> [!NOTE]  
> 这是一个实验性功能，我们将根据用户反馈和实际使用情况积极改进

## 将 Repomix 作为 MCP 服务器运行

要将 Repomix 作为 MCP 服务器运行，请使用 `--mcp` 标志：

```bash
repomix --mcp
```

这会以 MCP 服务器模式启动 Repomix，使其可供支持 Model Context Protocol 的 AI 助手使用。

## 可用的 MCP 工具

当作为 MCP 服务器运行时，Repomix 提供以下工具：

### pack_codebase

此工具将本地代码目录打包成一个用于 AI 分析的整合文件。

**参数：**
- `directory`：（必需）要打包的目录的绝对路径
- `compress`：（可选，默认值：true）是否执行智能代码提取以减少令牌计数
- `includePatterns`：（可选）以逗号分隔的包含模式列表
- `ignorePatterns`：（可选）以逗号分隔的忽略模式列表

**示例：**
```json
{
  "directory": "/path/to/your/project",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### pack_remote_repository

此工具获取、克隆并将 GitHub 仓库打包成一个用于 AI 分析的整合文件。

**参数：**
- `remote`：（必需）GitHub 仓库 URL 或用户/仓库格式（例如，yamadashy/repomix）
- `compress`：（可选，默认值：true）是否执行智能代码提取以减少令牌计数
- `includePatterns`：（可选）以逗号分隔的包含模式列表
- `ignorePatterns`：（可选）以逗号分隔的忽略模式列表

**示例：**
```json
{
  "remote": "yamadashy/repomix",
  "compress": true,
  "includePatterns": "src/**/*.ts,**/*.md",
  "ignorePatterns": "**/*.log,tmp/"
}
```

### read_repomix_output

此工具在无法直接访问文件的环境中读取Repomix输出文件的内容。

**参数：**
- `outputId`：（必需）要读取的Repomix输出文件的ID

**功能：**
- 专为基于Web的环境或沙箱应用程序设计
- 使用其ID检索先前生成的输出内容
- 无需文件系统访问权限即可安全访问打包的代码库

**示例：**
```json
{
  "outputId": "8f7d3b1e2a9c6054"
}
```

### file_system_read_file 和 file_system_read_directory

Repomix的MCP服务器提供了两个文件系统工具，允许AI助手安全地与本地文件系统交互：

1. `file_system_read_file`
   - 使用绝对路径读取文件内容
   - 使用[Secretlint](https://github.com/secretlint/secretlint)实现安全验证
   - 防止访问包含敏感信息的文件
   - 对无效路径和安全问题返回清晰的错误消息

2. `file_system_read_directory`
   - 使用绝对路径列出目录内容
   - 使用清晰的指示符（`[FILE]`或`[DIR]`）显示文件和目录
   - 提供安全的目录遍历和适当的错误处理
   - 验证路径并确保使用绝对路径

这两个工具都包含了强大的安全措施：
- 绝对路径验证以防止目录遍历攻击
- 权限检查以确保适当的访问权限
- 与Secretlint集成以检测敏感信息
- 清晰的错误消息以便于调试和安全意识

**示例：**
```typescript
// 读取文件
const fileContent = await tools.file_system_read_file({
  path: '/absolute/path/to/file.txt'
});

// 列出目录内容
const dirContent = await tools.file_system_read_directory({
  path: '/absolute/path/to/directory'
});
```

这些工具在AI助手需要执行以下操作时特别有用：
- 分析代码库中的特定文件
- 导航目录结构
- 验证文件存在性和可访问性
- 确保安全的文件系统操作

## 配置 MCP 服务器

要将 Repomix 作为 MCP 服务器与 Claude 等 AI 助手一起使用，您需要配置 MCP 设置：

### 对于 Cline（VS Code 扩展）

编辑 `cline_mcp_settings.json` 文件：

```json
{
  "mcpServers": {
    "repomix": {
      "command": "npx",
      "args": [
        "-y",
        "repomix",
        "--mcp"
      ]
    }
  }
}
```

### 对于 Claude Desktop

使用与 Cline 类似的配置编辑 `claude_desktop_config.json` 文件。

## 将 Repomix 作为 MCP 服务器使用的好处

将 Repomix 作为 MCP 服务器使用提供了几个优势：

1. **直接集成**：AI 助手可以直接分析您的代码库，无需手动文件准备。
2. **高效工作流**：通过消除手动生成和上传文件的需求，简化了代码分析过程。
3. **一致输出**：确保 AI 助手以一致、优化的格式接收代码库。
4. **高级功能**：利用 Repomix 的所有功能，如代码压缩、令牌计数和安全检查。

配置完成后，您的 AI 助手可以直接使用 Repomix 的功能来分析代码库，使代码分析工作流更加高效。
</file>

<file path="website/client/components/Home/TryItFolderUpload.vue">
<script setup lang="ts">
import JSZip from 'jszip';
import { AlertTriangle, FolderOpen } from 'lucide-vue-next';
import { ref } from 'vue';
import PackButton from './PackButton.vue';

const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB

const props = defineProps<{
  loading: boolean;
  showButton?: boolean;
}>();

const emit = defineEmits<{
  upload: [file: File];
}>();

const fileInput = ref<HTMLInputElement | null>(null);
const dragActive = ref(false);
const selectedFolder = ref<string | null>(null);
const errorMessage = ref<string | null>(null);

// Common validation logic
const validateFolder = (files: File[]): boolean => {
  errorMessage.value = null;

  if (files.length === 0) {
    errorMessage.value = 'The folder is empty.';
    return false;
  }

  const totalSize = files.reduce((sum, file) => sum + file.size, 0);
  if (totalSize > MAX_FILE_SIZE) {
    const sizeMB = (totalSize / (1024 * 1024)).toFixed(1);
    errorMessage.value = `File size (${sizeMB}MB) exceeds the 10MB limit`;
    return false;
  }

  return true;
};

// Create ZIP file
const createZipFile = async (files: File[], folderName: string): Promise<File> => {
  const zip = new JSZip();

  for (const file of files) {
    const path = file.webkitRelativePath || file.name;
    zip.file(path, file);
  }

  const zipBlob = await zip.generateAsync({ type: 'blob' });
  return new File([zipBlob], `${folderName}.zip`);
};

// Common folder processing logic
const processFolder = async (files: File[], folderName: string): Promise<void> => {
  if (!validateFolder(files)) {
    selectedFolder.value = null;
    return;
  }

  const zipFile = await createZipFile(files, folderName);
  selectedFolder.value = folderName;
  emit('upload', zipFile);
};

// File selection handler
const handleFileSelect = async (files: FileList | null): Promise<void> => {
  if (!files || files.length === 0) return;

  const fileArray = Array.from(files);
  const folderName = files[0].webkitRelativePath.split('/')[0];

  await processFolder(fileArray, folderName);
};

// Folder drop handler
const handleFolderDrop = async (event: DragEvent): Promise<void> => {
  event.preventDefault();
  dragActive.value = false;
  errorMessage.value = null;

  if (!event.dataTransfer?.items?.length) return;

  // Check directory reading capability
  if (!('webkitGetAsEntry' in DataTransferItem.prototype)) {
    errorMessage.value = "Your browser doesn't support folder drop. Please use the browse button instead.";
    return;
  }

  const entry = event.dataTransfer.items[0].webkitGetAsEntry();
  if (!entry?.isDirectory) {
    errorMessage.value = 'Please drop a folder, not a file.';
    return;
  }

  const folderName = entry.name;

  try {
    const files = await collectFilesFromEntry(entry);
    await processFolder(files, folderName);
  } catch (error) {
    console.error('Error processing dropped folder:', error);
    errorMessage.value = 'Failed to process the folder. Please try again or use the browse button.';
  }
};

const isFileEntry = (entry: FileSystemEntry): entry is FileSystemFileEntry => {
  return entry.isFile;
};
const isDirectoryEntry = (entry: FileSystemEntry): entry is FileSystemDirectoryEntry => {
  return entry.isDirectory;
};

// Recursively collect files from entry
const collectFilesFromEntry = async (entry: FileSystemEntry, path = ''): Promise<File[]> => {
  if (isFileEntry(entry)) {
    return new Promise((resolve, reject) => {
      entry.file((file: File) => {
        // Create custom file with path information
        const customFile = new File([file], file.name, {
          type: file.type,
          lastModified: file.lastModified,
        });

        // Add relative path information
        Object.defineProperty(customFile, 'webkitRelativePath', {
          value: path ? `${path}/${file.name}` : file.name,
        });

        resolve([customFile]);
      }, reject);
    });
  }

  if (isDirectoryEntry(entry) && entry.createReader) {
    return new Promise((resolve, reject) => {
      const dirReader = entry.createReader();
      const allFiles: File[] = [];

      // Function to read entries in directory
      function readEntries() {
        dirReader.readEntries(async (entries: FileSystemEntry[]) => {
          if (entries.length === 0) {
            resolve(allFiles);
          } else {
            try {
              // Process each entry
              for (const childEntry of entries) {
                const newPath = path ? `${path}/${childEntry.name}` : childEntry.name;
                const files = await collectFilesFromEntry(childEntry, newPath);
                allFiles.push(...files);
              }
              // Continue reading (some browsers return entries in batches)
              readEntries();
            } catch (error) {
              reject(error);
            }
          }
        }, reject);
      }

      readEntries();
    });
  }

  return []; // If neither file nor directory
};

const triggerFileInput = () => {
  fileInput.value?.click();
};
</script>

<template>
  <div class="upload-wrapper">
    <div class="upload-container"
         :class="{ 'drag-active': dragActive, 'has-error': errorMessage }"
         @dragover.prevent="dragActive = true"
         @dragleave="dragActive = false"
         @drop.prevent="handleFolderDrop($event)"
         @click="triggerFileInput"
    >
      <input
        ref="fileInput"
        type="file"
        directory
        webkitdirectory
        mozdirectory
        class="hidden-input"
        @change="(e) => handleFileSelect((e.target as HTMLInputElement).files)"
      />
      <div class="upload-content">
        <div class="upload-icon">
          <AlertTriangle v-if="errorMessage" class="icon-error" size="20" />
          <FolderOpen v-else class="icon-folder" size="20" />
        </div>
        <div class="upload-text">
          <p v-if="errorMessage" class="error-message">
            {{ errorMessage }}
          </p>
          <p v-else-if="selectedFolder" class="selected-file">
            Selected: {{ selectedFolder }}
            <button class="clear-button" @click.stop="selectedFolder = null">×</button>
          </p>
          <template v-else>
            <p>Drop your folder here or click to browse (max 10MB)</p>
          </template>
        </div>
      </div>
    </div>
  </div>
  <div v-if="showButton" class="pack-button-container">
    <PackButton
      :loading="loading"
      :isValid="!!selectedFolder"
    />
  </div>
</template>

<style scoped>
.upload-wrapper {
  width: 100%;
  min-width: 0;
}

.upload-container {
  border: 2px dashed var(--vp-c-border);
  border-radius: 8px;
  padding: 0 16px;
  cursor: pointer;
  transition: all 0.2s ease;
  height: 50px;
  display: flex;
  align-items: center;
  background: var(--vp-c-bg);
  user-select: none;
  width: 100%;
  box-sizing: border-box;
}

.upload-container:hover {
  border-color: var(--vp-c-brand-1);
  background-color: var(--vp-c-bg-soft);
}

.drag-active {
  border-color: var(--vp-c-brand-1);
  background-color: var(--vp-c-bg-soft);
}

.has-error {
  border-color: var(--vp-c-danger-1);
}

.hidden-input {
  display: none;
}

.upload-content {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 12px;
  width: 100%;
  pointer-events: none;
  overflow: hidden;
}

.upload-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.icon-folder {
  color: var(--vp-c-text-1);
}

.icon-error {
  color: var(--vp-c-danger-1);
}

.upload-text {
  flex: 1;
  font-size: 14px;
  min-width: 0;
  overflow: hidden;
}

.upload-text p {
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  width: 100%;
}

.error-message {
  color: var(--vp-c-danger-1);
}

.selected-file {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  overflow: hidden;
}

.clear-button {
  background: none;
  border: none;
  color: var(--vp-c-text-2);
  cursor: pointer;
  font-size: 1.2em;
  padding: 0 4px;
  line-height: 1;
  flex-shrink: 0;
  pointer-events: auto; /* Re-enable pointer events for button */
}

.clear-button:hover {
  color: var(--vp-c-text-1);
}

.pack-button-container {
  width: 100%;
  display: flex;
  justify-content: center;
  margin-top: 16px;
}

@media (max-width: 640px) {
  .upload-text p {
    font-size: 13px;
  }
}
</style>
</file>

<file path="package.json">
{
  "name": "repomix",
  "version": "0.3.0",
  "description": "A tool to pack repository contents to single file for AI consumption",
  "main": "./lib/index.js",
  "types": "./lib/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./lib/index.d.ts",
        "default": "./lib/index.js"
      },
      "require": {
        "types": "./lib/index.d.ts",
        "default": "./lib/index.js"
      },
      "default": "./lib/index.js"
    }
  },
  "bin": "./bin/repomix.cjs",
  "scripts": {
    "clean": "rimraf lib",
    "build": "npm run clean && tsc -p tsconfig.build.json --sourceMap --declaration",
    "lint": "npm run lint-biome && npm run lint-ts && npm run lint-secretlint",
    "lint-biome": "biome check --write",
    "lint-ts": "tsc --noEmit",
    "lint-secretlint": "secretlint \"**/*\" --secretlintignore .gitignore",
    "test": "vitest",
    "test-coverage": "vitest run --coverage",
    "repomix": "npm run build && node --trace-warnings bin/repomix.cjs",
    "repomix-src": "npm run repomix -- --include 'src,tests'",
    "repomix-website": "npm run repomix -- --include 'website'",
    "website": "docker compose -f website/compose.yml up --build",
    "npm-publish": "npm run lint && npm run test-coverage && npm run build && npm publish",
    "npm-release-patch": "npm version patch && npm run npm-publish",
    "npm-release-minor": "npm version minor && npm run npm-publish",
    "npm-release-prerelease": "npm version prerelease && npm run npm-publish"
  },
  "keywords": [
    "repository",
    "generative-ai",
    "ai",
    "llm",
    "source-code",
    "code-analysis",
    "codebase-packer",
    "development-tool",
    "ai-assistant",
    "code-review"
  ],
  "repository": {
    "type": "git",
    "url": "git://github.com/yamadashy/repomix.git"
  },
  "bugs": {
    "url": "https://github.com/yamadashy/repomix/issues"
  },
  "author": "Kazuki Yamada <koukun0120@gmail.com>",
  "homepage": "https://github.com/yamadashy/repomix",
  "license": "MIT",
  "publishConfig": {
    "access": "public"
  },
  "type": "module",
  "dependencies": {
    "@clack/prompts": "^0.10.0",
    "@modelcontextprotocol/sdk": "^1.6.1",
    "@secretlint/core": "^9.2.0",
    "@secretlint/secretlint-rule-preset-recommend": "^9.2.0",
    "cli-spinners": "^2.9.2",
    "clipboardy": "^4.0.0",
    "commander": "^13.1.0",
    "fast-xml-parser": "^5.0.8",
    "git-url-parse": "^16.0.1",
    "globby": "^14.1.0",
    "handlebars": "^4.7.8",
    "iconv-lite": "^0.6.3",
    "istextorbinary": "^9.5.0",
    "jschardet": "^3.1.4",
    "json5": "^2.2.3",
    "log-update": "^6.1.0",
    "minimatch": "^10.0.1",
    "picocolors": "^1.1.1",
    "piscina": "^4.8.0",
    "strip-comments": "^2.0.1",
    "strip-json-comments": "^5.0.1",
    "tiktoken": "^1.0.20",
    "tree-sitter-wasms": "^0.1.12",
    "web-tree-sitter": "^0.24.7",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@biomejs/biome": "^1.9.4",
    "@types/git-url-parse": "^9.0.3",
    "@types/node": "^22.13.10",
    "@types/strip-comments": "^2.0.4",
    "@vitest/coverage-v8": "^3.0.8",
    "rimraf": "^6.0.1",
    "secretlint": "^9.2.0",
    "typescript": "^5.8.2",
    "vitest": "^3.0.8"
  },
  "engines": {
    "node": ">=18.0.0",
    "yarn": ">=1.22.22"
  }
}
</file>

<file path="README.md">
<div align="center">
  <a href="https://repomix.com">
    <img src="website/client/src/public/images/repomix-title.png" alt="Repomix" width="500" height="auto" />
  </a>
  <p align="center">
    <b>Pack your codebase into AI-friendly formats</b>
  </p>
</div>

<hr />

<p align="center">
  <a href="https://repomix.com"><b>Use Repomix online! 👉 repomix.com</b></a><br>
</p>

<p align="center">
  Need discussion? Join us on <a href="https://discord.gg/wNYzTwZFku">Discord</a>!<br>
  <i>Share your experience and tips</i><br>
  <i>Stay updated on new features</i><br>
  <i>Get help with configuration and usage</i><br>
</p>

<hr />

[![Actions Status](https://github.com/yamadashy/repomix/actions/workflows/ci.yml/badge.svg)](https://github.com/yamadashy/repomix/actions?query=workflow%3A"ci")
[![npm](https://img.shields.io/npm/v/repomix.svg?maxAge=1000)](https://www.npmjs.com/package/repomix)
[![npm](https://img.shields.io/npm/d18m/repomix)](https://www.npmjs.com/package/repomix)
[![npm](https://img.shields.io/npm/l/repomix.svg?maxAge=1000)](https://github.com/yamadashy/repomix/blob/main/LICENSE)
[![node](https://img.shields.io/node/v/repomix.svg?maxAge=1000)](https://www.npmjs.com/package/repomix)
[![codecov](https://codecov.io/github/yamadashy/repomix/graph/badge.svg)](https://codecov.io/github/yamadashy/repomix)

📦 Repomix is a powerful tool that packs your entire repository into a single, AI-friendly file.  
It is perfect for when you need to feed your codebase to Large Language Models (LLMs) or other AI tools like Claude,
ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok, and more.

## 🎉 New: Repomix Website & Discord Community!

- Try Repomix in your browser at [repomix.com](https://repomix.com/)
- Join our [Discord Server](https://discord.gg/wNYzTwZFku) for support and discussion

**We look forward to seeing you there!**

## 🌟 Features

- **AI-Optimized**: Formats your codebase in a way that's easy for AI to understand and process.
- **Token Counting**: Provides token counts for each file and the entire repository, useful for LLM context limits.
- **Simple to Use**: You need just one command to pack your entire repository.
- **Customizable**: Easily configure what to include or exclude.
- **Git-Aware**: Automatically respects your `.gitignore` files and `.git/info/exclude`.
- **Security-Focused**: Incorporates [Secretlint](https://github.com/secretlint/secretlint) for robust security checks to detect and prevent inclusion of sensitive information.
- **Code Compression**: The `--compress` option uses [Tree-sitter](https://github.com/tree-sitter/tree-sitter) to extract key code elements, reducing token count while preserving structure.

## 🚀 Quick Start

### Using the CLI Tool `>_`

You can try Repomix instantly in your project directory without installation:

```bash
npx repomix
```

Or install globally for repeated use:

```bash
# Install using npm
npm install -g repomix

# Alternatively using yarn
yarn global add repomix

# Alternatively using Homebrew (macOS/Linux)
brew install repomix

# Then run in any project directory
repomix
```

That's it! Repomix will generate a `repomix-output.xml` file in your current directory, containing your entire
repository in an AI-friendly format.

You can then send this file to an AI assistant with a prompt like:

```
This file contains all the files in the repository combined into one.
I want to refactor the code, so please review it first.
```

![Repomix File Usage 1](website/client/src/public/images/docs/repomix-file-usage-1.png)

When you propose specific changes, the AI might be able to generate code accordingly. With features like Claude's
Artifacts, you could potentially output multiple files, allowing for the generation of multiple interdependent pieces of
code.

![Repomix File Usage 2](website/client/src/public/images/docs/repomix-file-usage-2.png)

Happy coding! 🚀

### Using The Website 🌐

Want to try it quickly? Visit the official website at [repomix.com](https://repomix.com). Simply enter your repository
name, fill in any optional details, and click the **Pack** button to see your generated output.

#### Available Options

The website offers several convenient features:

- Customizable output format (XML, Markdown, or Plain Text)
- Instant token count estimation
- Much more!

### Using The VSCode Extension ⚡️

A community-maintained VSCode extension lets you run Repomix right inside your editor with just a few clicks. Run it on any folder, manage outputs seamlessly, and control everything through VSCode's intuitive interface. 

Want your output as a file or just the content? Need automatic cleanup? This extension has you covered. Plus, it works smoothly with your existing repomix.config.json.

Try it now on the [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner)!
Source code is available on [GitHub](https://github.com/massdo/repomix-runner).

### Alternative Tools 🛠️

If you're using Python, you might want to check out `Gitingest`, which is better suited for Python ecosystem and data
science workflows:
https://github.com/cyclotruc/gitingest

## 📊 Usage

To pack your entire repository:

```bash
repomix
```

To pack a specific directory:

```bash
repomix path/to/directory
```

To pack specific files or directories
using [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax):

```bash
repomix --include "src/**/*.ts,**/*.md"
```

To exclude specific files or directories:

```bash
repomix --ignore "**/*.log,tmp/"
```

To pack a remote repository:

```bash
repomix --remote https://github.com/yamadashy/repomix

# You can also use GitHub shorthand:
repomix --remote yamadashy/repomix

# You can specify the branch name, tag, or commit hash:
repomix --remote https://github.com/yamadashy/repomix --remote-branch main

# Or use a specific commit hash:
repomix --remote https://github.com/yamadashy/repomix --remote-branch 935b695

# Another convenient way is specifying the branch's URL
repomix --remote https://github.com/yamadashy/repomix/tree/main

# Commit's URL is also supported
repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1

```

To compress the output:

```bash
repomix --compress

# You can also use it with remote repositories:
repomix --remote yamadashy/repomix --compress
```

To initialize a new configuration file (`repomix.config.json`):

```bash
repomix --init
```

Once you have generated the packed file, you can use it with Generative AI tools like ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok, and more.

### Docker Usage 🐳

You can also run Repomix using Docker.  
This is useful if you want to run Repomix in an isolated environment or prefer using containers.

Basic usage (current directory):

```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix
```

To pack a specific directory:

```bash
docker run -v .:/app -it --rm ghcr.io/yamadashy/repomix path/to/directory
```

Process a remote repository and output to a `output` directory:

```bash
docker run -v ./output:/app -it --rm ghcr.io/yamadashy/repomix --remote https://github.com/yamadashy/repomix
```

### Prompt Examples

Once you have generated the packed file with Repomix, you can use it with AI tools like ChatGPT, DeepSeek, Perplexity, Gemini, Gemma, Llama, Grok, and more.
Here are some example prompts to get you started:

#### Code Review and Refactoring

For a comprehensive code review and refactoring suggestions:

```
This file contains my entire codebase. Please review the overall structure and suggest any improvements or refactoring opportunities, focusing on maintainability and scalability.
```

#### Documentation Generation

To generate project documentation:

```
Based on the codebase in this file, please generate a detailed README.md that includes an overview of the project, its main features, setup instructions, and usage examples.
```

#### Test Case Generation

For generating test cases:

```
Analyze the code in this file and suggest a comprehensive set of unit tests for the main functions and classes. Include edge cases and potential error scenarios.
```

#### Code Quality Assessment

Evaluate code quality and adherence to best practices:

```
Review the codebase for adherence to coding best practices and industry standards. Identify areas where the code could be improved in terms of readability, maintainability, and efficiency. Suggest specific changes to align the code with best practices.
```

#### Library Overview

Get a high-level understanding of the library

```
This file contains the entire codebase of library. Please provide a comprehensive overview of the library, including its main purpose, key features, and overall architecture.
```

Feel free to modify these prompts based on your specific needs and the capabilities of the AI tool you're using.

### Community Discussion

Check out our [community discussion](https://github.com/yamadashy/repomix/discussions/154) where users share:

- Which AI tools they're using with Repomix
- Effective prompts they've discovered
- How Repomix has helped them
- Tips and tricks for getting the most out of AI code analysis

Feel free to join the discussion and share your own experiences! Your insights could help others make better use of
Repomix.

### Output File Format

Repomix generates a single file with clear separators between different parts of your codebase.  
To enhance AI comprehension, the output file begins with an AI-oriented explanation, making it easier for AI models to
understand the context and structure of the packed repository.

#### XML Format (default)

The XML format structures the content in a hierarchical manner:

```xml
This file is a merged representation of the entire codebase, combining all repository files into a single document.

<file_summary>
  (Metadata and usage AI instructions)
</file_summary>

<directory_structure>
src/
cli/
cliOutput.ts
index.ts

(...remaining directories)
</directory_structure>

<files>
<file path="src/index.js">
  // File contents here
</file>

(...remaining files)
</files>

<instruction>
(Custom instructions from `output.instructionFilePath`)
</instruction>
```

For those interested in the potential of XML tags in AI contexts:  
https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-tags

> When your prompts involve multiple components like context, instructions, and examples, XML tags can be a
> game-changer. They help Claude parse your prompts more accurately, leading to higher-quality outputs.

This means that the XML output from Repomix is not just a different format, but potentially a more effective way to feed
your codebase into AI systems for analysis, code review, or other tasks.

#### Markdown Format

To generate output in Markdown format, use the `--style markdown` option:

```bash
repomix --style markdown
```

The Markdown format structures the content in a hierarchical manner:

````markdown
This file is a merged representation of the entire codebase, combining all repository files into a single document.

# File Summary

(Metadata and usage AI instructions)

# Repository Structure

```
src/
  cli/
    cliOutput.ts
    index.ts
```

(...remaining directories)

# Repository Files

## File: src/index.js

```
// File contents here
```

(...remaining files)

# Instruction

(Custom instructions from `output.instructionFilePath`)
````

This format provides a clean, readable structure that is both human-friendly and easily parseable by AI systems.

#### Plain Text Format

To generate output in plain text format, use the `--style plain` option:

```bash
repomix --style plain
```

```text
This file is a merged representation of the entire codebase, combining all repository files into a single document.

================================================================
File Summary
================================================================
(Metadata and usage AI instructions)

================================================================
Directory Structure
================================================================
src/
  cli/
    cliOutput.ts
    index.ts
  config/
    configLoader.ts

(...remaining directories)

================================================================
Files
================================================================

================
File: src/index.js
================
// File contents here

================
File: src/utils.js
================
// File contents here

(...remaining files)

================================================================
Instruction
================================================================
(Custom instructions from `output.instructionFilePath`)
```

### Command Line Options

#### Basic Options
- `-v, --version`: Show tool version

#### Output Options
- `-o, --output <file>`: Specify the output file name
- `--style <style>`: Specify the output style (`xml`, `markdown`, `plain`)
- `--parsable-style`: Enable parsable output based on the chosen style schema. Note that this can increase token count.
- `--compress`: Perform intelligent code extraction, focusing on essential function and class signatures to reduce token count
- `--output-show-line-numbers`: Show line numbers in the output
- `--copy`: Additionally copy generated output to system clipboard
- `--no-file-summary`: Disable file summary section output
- `--no-directory-structure`: Disable directory structure section output
- `--remove-comments`: Remove comments from supported file types
- `--remove-empty-lines`: Remove empty lines from the output
- `--header-text <text>`: Custom text to include in the file header
- `--instruction-file-path <path>`: Path to a file containing detailed custom instructions
- `--include-empty-directories`: Include empty directories in the output
- `--no-git-sort-by-changes`: Disable sorting files by git change count (enabled by default)

#### Filter Options
- `--include <patterns>`: List of include patterns (comma-separated)
- `-i, --ignore <patterns>`: Additional ignore patterns (comma-separated)
- `--no-gitignore`: Disable .gitignore file usage
- `--no-default-patterns`: Disable default patterns

#### Remote Repository Options
- `--remote <url>`: Process a remote Git repository
- `--remote-branch <name>`: Specify the remote branch name, tag, or commit hash (defaults to repository default branch)

#### Configuration Options
- `-c, --config <path>`: Path to a custom config file
- `--init`: Create config file
- `--global`: Use global config

#### Security Options
- `--no-security-check`: Disable security check

#### Token Count Options
- `--token-count-encoding <encoding>`: Specify token count encoding (e.g., `o200k_base`, `cl100k_base`)

#### MCP
- `--mcp`: Run as a [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server

#### Other Options
- `--top-files-len <number>`: Number of top files to display in the summary
- `--verbose`: Enable verbose logging
- `--quiet`: Disable all output to stdout

Examples:

```bash
repomix -o custom-output.txt
repomix -i "*.log,tmp" -v
repomix -c ./custom-config.json
repomix --style xml
repomix --remote https://github.com/user/repo
npx repomix src
```

### Updating Repomix

To update a globally installed Repomix:

```bash
# Using npm
npm update -g repomix

# Using yarn
yarn global upgrade repomix
```

Using `npx repomix` is generally more convenient as it always uses the latest version.

### Remote Repository Processing

Repomix supports processing remote Git repositories without the need for manual cloning. This feature allows you to
quickly analyze any public Git repository with a single command.

To process a remote repository, use the `--remote` option followed by the repository URL:

```bash
repomix --remote https://github.com/yamadashy/repomix
```

You can also use GitHub's shorthand format:

```bash
repomix --remote yamadashy/repomix
```

You can specify the branch name, tag, or commit hash:

```bash
# Using --remote-branch option
repomix --remote https://github.com/yamadashy/repomix --remote-branch main

# Using branch's URL
repomix --remote https://github.com/yamadashy/repomix/tree/main
```

Or use a specific commit hash:

```bash
# Using --remote-branch option
repomix --remote https://github.com/yamadashy/repomix --remote-branch 935b695

# Using commit's URL
repomix --remote https://github.com/yamadashy/repomix/commit/836abcd7335137228ad77feb28655d85712680f1
```

### Code Compression

The `--compress` option utilizes [Tree-sitter](https://github.com/tree-sitter/tree-sitter) to perform intelligent code extraction, focusing on essential function and class signatures while removing implementation details. This can help reduce token count while retaining important structural information.

```bash
repomix --compress
```

For example, this code:

```typescript
import { ShoppingItem } from './shopping-item';

/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
  let total = 0;
  for (const item of items) {
    total += item.price * item.quantity;
  }
  return total;
}

// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

Will be compressed to:

```typescript
import { ShoppingItem } from './shopping-item';
⋮----
/**
 * Calculate the total price of shopping items
 */
const calculateTotal = (
  items: ShoppingItem[]
) => {
⋮----
// Shopping item interface
interface Item {
  name: string;
  price: number;
  quantity: number;
}
```

> [!NOTE]
> This is an experimental feature that we'll be actively improving based on user feedback and real-world usage

### MCP Integration

Repomix supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io), allowing AI assistants to directly interact with your codebase. When run as an MCP server, Repomix provides tools that enable AI assistants to package local or remote repositories for analysis without requiring manual file preparation.

```bash
repomix --mcp
```

#### Available MCP Tools

When running as an MCP server, Repomix provides the following tools:

1. **pack_codebase**: Package a local code directory into a consolidated file for AI analysis
  - Parameters:
    - `directory`: Absolute path to the directory to pack
    - `compress`: (Optional, default: true) Whether to perform intelligent code extraction
    - `includePatterns`: (Optional) Comma-separated list of include patterns
    - `ignorePatterns`: (Optional) Comma-separated list of ignore patterns

2. **pack_remote_repository**: Fetch, clone and package a GitHub repository
  - Parameters:
    - `remote`: GitHub repository URL or user/repo format (e.g., yamadashy/repomix)
    - `compress`: (Optional, default: true) Whether to perform intelligent code extraction
    - `includePatterns`: (Optional) Comma-separated list of include patterns
    - `ignorePatterns`: (Optional) Comma-separated list of ignore patterns

3. **read_repomix_output**: Read the contents of a Repomix output file in environments where direct file access is not possible
  - Parameters:
    - `outputId`: ID of the Repomix output file to read
  - Features:
    - Specifically designed for web-based environments or sandboxed applications
    - Retrieves the content of previously generated outputs using their ID
    - Provides secure access to packed codebase without requiring file system access

4. **file_system_read_file**: Read a file using an absolute path with security validation
  - Parameters:
    - `path`: Absolute path to the file to read
  - Security features:
    - Implements security validation using [Secretlint](https://github.com/secretlint/secretlint)
    - Prevents access to files containing sensitive information
    - Validates absolute paths to prevent directory traversal attacks

5. **file_system_read_directory**: List contents of a directory using an absolute path
  - Parameters:
    - `path`: Absolute path to the directory to list
  - Features:
    - Shows files and directories with clear indicators (`[FILE]` or `[DIR]`)
    - Provides safe directory traversal with proper error handling
    - Validates paths and ensures they are absolute

#### Configuring MCP Servers

To use Repomix as an MCP server with AI assistants like Claude, you need to configure the MCP settings:

**For Cline (VS Code extension):**

Edit the `cline_mcp_settings.json` file:
```json
{
  "mcpServers": {
    "repomix": {
      "command": "npx",
      "args": [
        "-y",
        "repomix",
        "--mcp"
      ]
    }
  }
}
```

**For Claude Desktop:**

Edit the `claude_desktop_config.json` file with similar configuration to Cline's config.

Once configured, your AI assistant can directly use Repomix's capabilities to analyze codebases without manual file preparation, making code analysis workflows more efficient.

## ⚙️ Configuration

Create a `repomix.config.json` file in your project root for custom configurations.

```bash
repomix --init
```

Here's an explanation of the configuration options:

| Option                           | Description                                                                                                                  | Default                |
|----------------------------------|------------------------------------------------------------------------------------------------------------------------------|------------------------|
| `output.filePath`                | The name of the output file                                                                                                  | `"repomix-output.xml"` |
| `output.style`                   | The style of the output (`xml`, `markdown`, `plain`)                                                                         | `"xml"`                |
| `output.parsableStyle`           | Whether to escape the output based on the chosen style schema. Note that this can increase token count.                      | `false`                |
| `output.compress`                | Whether to perform intelligent code extraction to reduce token count                                                         | `false`                |
| `output.headerText`              | Custom text to include in the file header                                                                                    | `null`                 |
| `output.instructionFilePath`     | Path to a file containing detailed custom instructions                                                                       | `null`                 |
| `output.fileSummary`             | Whether to include a summary section at the beginning of the output                                                          | `true`                 |
| `output.directoryStructure`      | Whether to include the directory structure in the output                                                                     | `true`                 |
| `output.removeComments`          | Whether to remove comments from supported file types                                                                         | `false`                |
| `output.removeEmptyLines`        | Whether to remove empty lines from the output                                                                                | `false`                |
| `output.showLineNumbers`         | Whether to add line numbers to each line in the output                                                                       | `false`                |
| `output.copyToClipboard`         | Whether to copy the output to system clipboard in addition to saving the file                                                | `false`                |
| `output.topFilesLength`          | Number of top files to display in the summary. If set to 0, no summary will be displayed                                     | `5`                    |
| `output.includeEmptyDirectories` | Whether to include empty directories in the repository structure                                                             | `false`                |
| `output.git.sortByChanges`       | Whether to sort files by git change count (files with more changes appear at the bottom)                                     | `true`                 |
| `output.git.sortByChangesMaxCommits` | Maximum number of commits to analyze for git changes                                                                     | `100`                  |
| `include`                        | Patterns of files to include (using [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax))  | `[]`                   |
| `ignore.useGitignore`            | Whether to use patterns from the project's `.gitignore` file                                                                 | `true`                 |
| `ignore.useDefaultPatterns`      | Whether to use default ignore patterns                                                                                       | `true`                 |
| `ignore.customPatterns`          | Additional patterns to ignore (using [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax)) | `[]`                   |
| `security.enableSecurityCheck`   | Whether to perform security checks on files                                                                                  | `true`                 |
| `tokenCount.encoding`            | Token count encoding for AI model context limits (e.g., `o200k_base`, `cl100k_base`)                                         | `"o200k_base"`         |

The configuration file supports [JSON5](https://json5.org/) syntax, which allows:
- Comments (both single-line and multi-line)
- Trailing commas in objects and arrays
- Unquoted property names
- More relaxed string syntax

Example configuration:

```json5
{
  "output": {
    "filePath": "repomix-output.xml",
    "style": "xml",
    "parsableStyle": true,
    "compress": false,
    "headerText": "Custom header information for the packed file.",
    "fileSummary": true,
    "directoryStructure": true,
    "removeComments": false,
    "removeEmptyLines": false,
    "showLineNumbers": false,
    "copyToClipboard": true,
    "topFilesLength": 5,
    "includeEmptyDirectories": false,
    "git": {
      "sortByChanges": true,
      "sortByChangesMaxCommits": 100
    }
  },
  "include": [
    "**/*"
  ],
  "ignore": {
    "useGitignore": true,
    "useDefaultPatterns": true,
    // Patterns can also be specified in .repomixignore
    "customPatterns": [
      "additional-folder",
      "**/*.log"
    ],
  },
  "security": {
    "enableSecurityCheck": true
  },
  "tokenCount": {
    "encoding": "o200k_base"
  },
}
```

### Global Configuration

To create a global configuration file:

```bash
repomix --init --global
```

The global configuration file will be created in:

- Windows: `%LOCALAPPDATA%\Repomix\repomix.config.json`
- macOS/Linux: `$XDG_CONFIG_HOME/repomix/repomix.config.json` or `~/.config/repomix/repomix.config.json`

Note: Local configuration (if present) takes precedence over global configuration.

### Include and Ignore

#### Include Patterns

Repomix now supports specifying files to include
using [glob patterns](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax). This allows for more
flexible and powerful file selection:

- Use `**/*.js` to include all JavaScript files in any directory
- Use `src/**/*` to include all files within the `src` directory and its subdirectories
- Combine multiple patterns like `["src/**/*.js", "**/*.md"]` to include JavaScript files in `src` and all Markdown
  files

#### Ignore Patterns

Repomix offers multiple methods to set ignore patterns for excluding specific files or directories during the packing
process:

- **.gitignore**: By default, patterns listed in your project's `.gitignore` files and `.git/info/exclude` are used. This behavior can be controlled with the `ignore.useGitignore` setting or the `--no-gitignore` cli option.
- **Default patterns**: Repomix includes a default list of commonly excluded files and directories (e.g., node_modules,
  .git, binary files). This feature can be controlled with the `ignore.useDefaultPatterns` setting or the `--no-default-patterns` cli option. Please
  see [defaultIgnore.ts](src/config/defaultIgnore.ts) for more details.
- **.repomixignore**: You can create a `.repomixignore` file in your project root to define Repomix-specific ignore
  patterns. This file follows the same format as `.gitignore`.
- **Custom patterns**: Additional ignore patterns can be specified using the `ignore.customPatterns` option in the
  configuration file. You can overwrite this setting with the `-i, --ignore` command line option.

Priority Order (from highest to lowest):

1. Custom patterns `ignore.customPatterns`
2. `.repomixignore`
3. `.gitignore` and `.git/info/exclude` (if `ignore.useGitignore` is true and `--no-gitignore` is not used)
4. Default patterns (if `ignore.useDefaultPatterns` is true and `--no-default-patterns` is not used)

This approach allows for flexible file exclusion configuration based on your project's needs. It helps optimize the size
of the generated pack file by ensuring the exclusion of security-sensitive files and large binary files, while
preventing the leakage of confidential information.

Note: Binary files are not included in the packed output by default, but their paths are listed in the "Repository
Structure" section of the output file. This provides a complete overview of the repository structure while keeping the
packed file efficient and text-based.

### Custom Instruction

The `output.instructionFilePath` option allows you to specify a separate file containing detailed instructions or
context about your project. This allows AI systems to understand the specific context and requirements of your project,
potentially leading to more relevant and tailored analysis or suggestions.

Here's an example of how you might use this feature:

1. Create a file named `repomix-instruction.md` in your project root:

```markdown
# Coding Guidelines

- Follow the Airbnb JavaScript Style Guide
- Suggest splitting files into smaller, focused units when appropriate
- Add comments for non-obvious logic. Keep all text in English
- All new features should have corresponding unit tests

# Generate Comprehensive Output

- Include all content without abbreviation, unless specified otherwise
- Optimize for handling large codebases while maintaining output quality
```

2. In your `repomix.config.json`, add the `instructionFilePath` option:

```json5
{
  "output": {
    "instructionFilePath": "repomix-instruction.md",
    // other options...
  }
}
```

When Repomix generates the output, it will include the contents of `repomix-instruction.md` in a dedicated section.

Note: The instruction content is appended at the end of the output file. This placement can be particularly effective
for AI systems. For those interested in understanding why this might be beneficial, Anthropic provides some insights in
their documentation:  
https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/long-context-tips

> Put long-form data at the top: Place your long documents and inputs (~20K+ tokens) near the top of your prompt, above
> your query, instructions, and examples. This can significantly improve Claude's performance across all models.
> Queries at the end can improve response quality by up to 30% in tests, especially with complex, multi-document inputs.

### Comment Removal

When `output.removeComments` is set to `true`, Repomix will attempt to remove comments from supported file types. This
feature can help reduce the size of the output file and focus on the essential code content.

Supported languages include:  
HTML, CSS, JavaScript, TypeScript, Vue, Svelte, Python, PHP, Ruby, C, C#, Java, Go, Rust, Swift, Kotlin, Dart, Shell,
and YAML.

Note: The comment removal process is conservative to avoid accidentally removing code. In complex cases, some comments
might be retained.

## 🔍 Security Check

Repomix includes a security check feature that uses [Secretlint](https://github.com/secretlint/secretlint) to detect
potentially sensitive information in your files. This feature helps you identify possible security risks before sharing
your packed repository.

The security check results will be displayed in the CLI output after the packing process is complete. If any suspicious
files are detected, you'll see a list of these files along with a warning message.

Example output:

```
🔍 Security Check:
──────────────────
2 suspicious file(s) detected:
1. src/utils/test.txt
2. tests/utils/secretLintUtils.test.ts

Please review these files for potentially sensitive information.
```

By default, Repomix's security check feature is enabled. You can disable it by setting `security.enableSecurityCheck` to
`false` in your configuration file:

```json
{
  "security": {
    "enableSecurityCheck": false
  }
}
```

Or using the `--no-security-check` command line option:

```bash
repomix --no-security-check
```

> [!NOTE]
> Disabling security checks may expose sensitive information. Use this option with caution and only when necessary, such
> as when working with test files or documentation that contains example credentials.




## 🤝 Contribution

We welcome contributions from the community! To get started, please refer to our [Contributing Guide](CONTRIBUTING.md).

### Contributors

<a href="https://github.com/yamadashy/repomix/graphs/contributors">
  <img alt="contributors" src="https://contrib.rocks/image?repo=yamadashy/repomix"/>
</a>



## 🔒 Privacy Policy

### Repomix CLI Tool
- **Data Collection**: The Repomix CLI tool does **not** collect, transmit, or store any user data, telemetry, or repository information.
- **Network Usage**: Repomix CLI operates fully offline after installation. The only cases where an internet connection is needed are:
  - Installation via npm/yarn.
  - Using the `--remote` flag to process remote repositories.
  - Checking for updates (manually triggered).
- **Security Considerations**: Since all processing is local, Repomix CLI is safe to use with private and internal repositories.

### Repomix Website ([repomix.com](https://repomix.com/))
- **Data Collection**: The Repomix website uses **Google Analytics** to collect usage data, such as page views and user interactions. This helps us understand how the website is used and improve the user experience.

### Liability Disclaimer
Repomix (both the CLI tool and the website) is provided "as is" without any warranties or guarantees.  
We do not take responsibility for how the generated output is used, including but not limited to its accuracy, legality, or any potential consequences arising from its use.


## 📜 License

This project is licensed under the [MIT License](LICENSE).

<p align="center">
  &nbsp;&nbsp;&nbsp;
  <a href="#-repomix" target="_blank">
    Back To Top
  </a>

</p>
</file>

</files>

<instruction>
# Repomix Project Structure and Overview

This document provides a structural overview of the Repomix project, designed to aid AI code assistants (like Copilot) in understanding the codebase.

Please refer to `README.md` for a complete and up-to-date project overview, and `CONTRIBUTING.md` for implementation guidelines and contribution procedures.

## Project Overview

Repomix is a tool that packs the contents of a software repository into a single file, making it easier for AI systems to analyze and process the codebase. It supports various output formats (plain text, XML, Markdown), ignores files based on configurable patterns, and performs security checks to exclude potentially sensitive information.

## Directory Structure

The project is organized into the following directories:

```
repomix/
├── src/ # Main source code
│   ├── cli/ # Command-line interface logic (argument parsing, command handling, output)
│   ├── config/ # Configuration loading, schema, and defaults
│   ├── core/ # Core logic of Repomix
│   │   ├── file/ # File handling (reading, processing, searching, tree structure generation, git commands)
│   │   ├── metrics/ # Calculating code metrics (character count, token count)
│   │   ├── output/ # Output generation (different styles, headers, etc.)
│   │   ├── packager/ # Orchestrates file collection, processing, output, and clipboard operations.
│   │   ├── security/ # Security checks to exclude sensitive files
│   │   ├── tokenCount/ # Token counting using Tiktoken
│   │   └── tree-sitter/ # Code parsing using Tree-sitter and language-specific queries
│   └── shared/ # Shared utilities and types (error handling, logging, helper functions)
├── tests/ # Unit and integration tests (organized mirroring src/)
│   ├── cli/
│   ├── config/
│   ├── core/
│   ├── integration-tests/
│   ├── shared/
│   └── testing/
└── website/ # Documentation website (VitePress)
    ├── client/      # Client-side code (Vue.js components, styles, configuration)
    │   ├── .vitepress/  # VitePress configuration and theme
    │   │   ├── config/  # Site configuration files (navigation, sidebar, etc.)
    │   │   └── theme/   # Custom theme and styles
    │   ├── components/ # Vue.js components for the website
    │   └── src/        # Markdown files for the documentation in various languages (en, ja, etc.)
    └── server/      # Server-side API (for remote repository processing)
        └── src/       # Server source code (API endpoints, request handling)
```

----------------------------------------------------------------

# Coding Guidelines
- Follow the Airbnb JavaScript Style Guide.
- Split files into smaller, focused units when appropriate:
  - Aim to keep code files under 250 lines. If a file exceeds 250 lines, split it into multiple files based on functionality.
- Add comments to clarify non-obvious logic. **Ensure all comments are written in English.**
- Provide corresponding unit tests for all new features.
- After implementation, verify changes by running:
  ```bash
  npm run lint  # Ensure code style compliance
  npm run test  # Verify all tests pass
  ```

## Dependencies and Testing
- Inject dependencies through a deps object parameter for testability
- Example:
  ```typescript
  export const functionName = async (
    param1: Type1,
    param2: Type2,
    deps = {
      defaultFunction1,
      defaultFunction2,
    }
  ) => {
    // Use deps.defaultFunction1() instead of direct call
  };
  ```
- Mock dependencies by passing test doubles through deps object
- Use vi.mock() only when dependency injection is not feasible

## Generate Comprehensive Output
- Include all content without abbreviation, unless specified otherwise
- Optimize for handling large codebases while maintaining output quality

----------------------------------------------------------------

# GitHub Release Note Guidelines
When writing release notes, please follow these guidelines:

- When referencing issues or PRs, use the gh command to verify the content:
  ```bash
  gh issue view <issue-number>  # For checking issue content
  gh pr view <pr-number>        # For checking PR content
  ```
  This helps ensure accuracy in release note descriptions.

Here are some examples of release notes that follow the guidelines:

v0.2.25
````md
This release brings significant improvements to output formatting and introduces flexible remote repository handling capabilities along with enhanced logging features.

# Improvements ⚡

## Remote Repository Enhancement (#335)
- Added branch/tag parsing directly from repository URLs:
```bash
repomix --remote https://github.com/yamadashy/repomix/tree/0.1.x
```
Functions identically to:
```bash
repomix --remote https://github.com/yamadashy/repomix --remote-branch 0.1.x
```

Special thanks to @huy-trn for implementing this user-friendly feature!

## Enhanced Output Formatting (#328, #329, #330)
- Added "End of Codebase" marker for better clarity in output
- Improved output header accuracy:
  - Better representation of codebase scope
  - Clear indication when using `--include` or `--ignore` options

Special thanks to @gitkenan for adding the "End of Codebase" marker and reporting the header issue!

## Path Pattern Support (#337)
- Added support for special characters in paths:
  - Handles parentheses in include patterns (e.g., `src/(categories)/**/*`)
  - Improved escaping for `[]` and `{}`
  - Essential for Next.js route groups and similar frameworks

Thank you @matheuscoelhomalta for improving path pattern support!

# How to Update

```bash
npm update -g repomix
```

---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.
````

v0.2.24
````md
This release significantly enhances configuration flexibility with comprehensive CLI flag support and expands default ignore patterns for better project scaffolding. 

# What's New 🚀

## CLI Flags Revolution (#324)
- New command-line configuration now available.

```
- `--no-gitignore`: Disable .gitignore file usage
- `--no-default-patterns`: Disable default patterns
- `--header-text <text>`: Custom text to include in the file header
- `--instruction-file-path <path>`: Path to a file containing detailed custom instructions
- `--include-empty-directories`: Include empty directories in the output
```

Special recognition to @massdo for driving ecosystem growth.

# Improvements ⚡

## Enhanced Ignore Patterns (#318, #322)
- Expanded default ignores for Rust projects:
  - `target/`, `Cargo.lock`, build artifacts
  - PHP, Ruby, Go, Elixir, Haskell: package manager lock files

To @boralg for helping curate Rust-specific patterns!

# How to Update
```bash
npm update -g repomix
```

---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.
````

v0.2.23
````md
This release adds significant performance improvements for large repositories, making Repomix faster and more efficient when needed.

# Improvements ⚡

## Parallel Processing Enhancement (#309)
- Implemented worker threads using [Piscina](https://github.com/piscinajs/piscina) for parallel processing

### Benchmark Results
- `yamadashy.repomix`: No significant change
  - Before: 868.73 millis
  - After: 671.26 millis
- `facebook/react`: 29x faster
  - Before: 123.31 secs
  - After: 4.19 secs
- `vercel/next.js`: 58x faster
  - Before: 17.85 mins
  - After: 17.27 secs

Note: While Repomix is not primarily designed for processing large repositories, and speed is not a primary goal, faster processing can provide a better user experience when working with larger codebases.

# How to Update

```bash
npm update -g repomix
```


---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.
````

v0.2.22
````md
This release introduces significant improvements to large file handling and expands the Repomix ecosystem with new tools and community channels.

# Improvements ⚡ 

## Improved Large File Handling (#302)

- Added a file size limit check (50MB) to prevent memory issues
- Graceful error handling for large files with clear user guidance:

Special thanks to @slavashvets for their continued contributions!

# Ecosystem Growth 🤝 

## New VS Code Extension (#300)
A community-created VS Code extension "Repomix Runner" is now available:
- Run Repomix directly from VS Code
- Extension by @massdo: [View on VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=DorianMassoulier.repomix-runner)

Thank you @massdo for bringing Repomix to VS Code and expanding our tooling ecosystem!

## Official Social Media
- Launched official Repomix X (Twitter) account: [@repomix_ai](https://x.com/repomix_ai)
  - Follow for updates, tips, and community highlights

# How to Update

```bash
npm update -g repomix
```

---

Join our growing community on [Discord](https://discord.gg/BF8GxZHE2C) and follow us on [X](https://x.com/repomix_ai) for updates!
````

v0.2.21
````md
This release introduces significant improvements to output formatting and documentation, featuring a new parsable style option for enhanced XML handling.

# What's New 🚀 

## Enhanced Output Style Control (#287)
- Added new `parsableStyle` option for better output handling:
  - Ensures output strictly follows the specification of the chosen format
  - Provides properly escaped XML output with fast-xml-parser
  - Dynamically adjusts markdown code block delimiters to avoid content conflicts
- Available via CLI flag `--parsable-style` or in configuration file

Special thanks to @atollk for their first contribution!

# Documentation 📚

## README Enhancements (#296)
- Updated Homebrew installation documentation to include Linux support

Special thanks to @chenrui333 for their continued contributions!

## Website Multi-Language Support (#293)
- Enhanced multi-language support in [repomix.com](https://repomix.com)

# How to Update

To update to the latest version, run:
```bash
npm update -g repomix
```


---

As always, if you encounter any issues or have suggestions, please let us know through our GitHub issues or join our [Discord community](https://discord.gg/wNYzTwZFku) for support.

</instruction>
