[
  {
    "path": ".assets/flow_diagram.md",
    "content": "---\nconfig:\n  flowchart:\n    nodeSpacing: 15\n    rankSpacing: 50\n    curve: monotoneX\n  layout: fixed\n---\nflowchart LR\n subgraph S1[\"1. Input Source\"]\n        A[(\"📂 Codebase & Git\")]\n  end\n subgraph S2[\"2. Code2Prompt Core\"]\n    direction LR\n        B{\"🔍 Filtrage & Config\"}\n        C[\"🧠 Smart Processing\n(Parse CSV, Notebooks, JSONL)\"]\n        D[\"🎨 Templating Layer\n(Handlebars + Token Count)\"]\n  end\n subgraph S3[\"3. Delivery Interfaces\"]\n    direction TB\n        E[\"💻 CLI / TUI\"]\n        F[\"🐍 Python SDK\"]\n        G[\"🔌 MCP Server\"]\n  end\n    A --> B\n    B --> C\n    C --> D\n    D --> E & F & G\n    E --> H(\"🤖 LLM / AI Model\")\n    F --> H\n    G --> H\n    H -. 📝 Generate &amp; <br> Integrate Code .-> A\n\n     A:::input\n     B:::core\n     C:::core\n     D:::core\n     E:::delivery\n     F:::delivery\n     G:::delivery\n     H:::ai\n    classDef input fill:#e1f5fe,stroke:#01579b,stroke-width:2px\n    classDef core fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px\n    classDef delivery fill:#fff3e0,stroke:#ef6c00,stroke-width:2px\n    classDef ai fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px\n    classDef loop fill:#ffffff,stroke:#333,stroke-width:1px,stroke-dasharray: 5 5"
  },
  {
    "path": ".c2pconfig",
    "content": "default_output = \"clipboard\"\ninclude_patterns = [\"*.rs\"]\nexclude_patterns = [\"**/test*\"]\nline_numbers = false\nabsolute_path = true\n\n[user_variables]\nproject = \"code2prompt\"\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#package-ecosystem-\n\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\" # .github/workflows/*.yml\n    target-branch: \"main\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"cargo\" # Cargo.lock\n    target-branch: \"main\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"cargo\" # Cargo.lock\n    target-branch: \"main\"\n    directory: \"/crates/code2prompt-core\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"pip\" # pyproject.toml\n    target-branch: \"main\"\n    directory: \"/crates/code2prompt-python\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"uv\" # requirements.lock\n    target-branch: \"main\"\n    directory: \"/crates/code2prompt-python\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"npm\" # package.json and yarn.lock\n    target-branch: \"main\"\n    directory: \"/website\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "# Run tests for code2prompt\nname: Code2prompt Continuous Integration\n\non:\n  push:\n    branches: [ \"main\" ]\n  pull_request:\n    branches: [ \"main\" ]\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Run tests\n      run: cargo test --verbose\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "# Build and publish release on tags push\n\nname: Code2prompt Release\n\non:\n  push:\n    tags:\n      - 'v[0-9]*.[0-9]*.[0-9]*'\n\njobs:\n  build:\n    strategy:\n      matrix:\n        include:\n          - os: ubuntu-latest\n            target: x86_64-unknown-linux-gnu\n          - os: macos-latest\n            target: x86_64-apple-darwin\n          - os: macos-latest\n            target: aarch64-apple-darwin\n          - os: windows-latest\n            target: x86_64-pc-windows-msvc\n    runs-on: ${{ matrix.os }}\n    outputs:\n      asset-path: ${{ steps.set_asset.outputs.asset-path }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n\n      - name: Set up Rust toolchain\n        uses: actions-rs/toolchain@v1\n        with:\n          toolchain: stable\n          target: ${{ matrix.target }}\n          override: true\n\n      - name: Cache Rust dependencies\n        uses: actions/cache@v5\n        with:\n          path: |\n            ~/.cargo/bin/\n            ~/.cargo/registry/index/\n            ~/.cargo/registry/cache/\n            ~/.cargo/git/db/\n            target/\n          key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}\n          restore-keys: |\n            ${{ runner.os }}-${{ matrix.target }}-cargo-\n\n      - name: Install extra dependencies on Ubuntu\n        if: runner.os == 'Linux'\n        run: |\n          if [ \"${{ matrix.target }}\" = \"aarch64-unknown-linux-gnu\" ]; then\n            sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu\n          fi\n\n      - name: Cache LLVM on Windows\n        if: runner.os == 'Windows'\n        id: cache-llvm\n        uses: actions/cache@v5\n        with:\n          path: C:\\Program Files\\LLVM\n          key: windows-llvm-latest\n          \n      - name: Install LLVM on Windows\n        if: runner.os == 'Windows' && steps.cache-llvm.outputs.cache-hit != 'true'\n        run: |\n          choco install llvm\n\n      - name: Build\n        run: cargo build --release --target ${{ matrix.target }}\n\n      # Packaging for Windows (PowerShell)\n      - name: Package Binary (Windows)\n        if: runner.os == 'Windows'\n        id: package_windows\n        shell: pwsh\n        run: |\n          $BIN_DIR = \"target/${{ matrix.target }}/release\"\n          $BIN_NAME = \"code2prompt\"\n          New-Item -ItemType Directory -Force -Path release | Out-Null\n          Copy-Item \"$BIN_DIR\\$BIN_NAME.exe\" \"release/${BIN_NAME}-${{ matrix.target }}.exe\"\n          # Enregistrer le chemin de l'artefact dans un fichier\n          Set-Content -Path asset_windows.txt -Value \"release/${BIN_NAME}-${{ matrix.target }}.exe\"\n\n      # Packaging for Linux/macOS (bash)\n      - name: Package Binary (Unix)\n        if: runner.os != 'Windows'\n        id: package_unix\n        shell: bash\n        run: |\n          BIN_DIR=target/${{ matrix.target }}/release\n          BIN_NAME=code2prompt\n          mkdir -p release\n          cp \"$BIN_DIR/$BIN_NAME\" \"release/${BIN_NAME}-${{ matrix.target }}\"\n          echo \"release/${BIN_NAME}-${{ matrix.target }}\" > asset_unix.txt\n\n      # Get Artifact's path according to OS and defines it as output\n      - name: Set asset output\n        id: set_asset\n        shell: bash\n        run: |\n          if [ -f asset_windows.txt ]; then\n            ASSET_PATH=$(cat asset_windows.txt)\n          else\n            ASSET_PATH=$(cat asset_unix.txt)\n          fi\n          echo \"Asset path: $ASSET_PATH\"\n          echo \"::set-output name=asset-path::$ASSET_PATH\"\n\n      - name: Upload Artifact\n        uses: actions/upload-artifact@v6\n        with:\n          name: asset-${{ matrix.target }}\n          path: ${{ steps.set_asset.outputs.asset-path }}\n\n  release:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Download artifacts\n        uses: actions/download-artifact@v7\n        with:\n          path: artifacts\n\n      - name: Create GitHub Release and upload assets\n        if: startsWith(github.ref, 'refs/tags/')\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: ${{ github.ref }}\n          name: Release ${{ github.ref }}\n          body: \"Automatically generated release for ${{ github.ref }}\"\n          files: |\n            artifacts/**\n        env:\n          GITHUB_TOKEN: ${{ secrets.C2P_RELEASE_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/website.yml",
    "content": "name: Code2prompt Website\n\non:\n  # Trigger the workflow every time you push to the `main` branch\n  # Using a different branch name? Replace `main` with your branch’s name\n  push:\n    branches: [main]\n  # Allows you to run this workflow manually from the Actions tab on GitHub.\n  workflow_dispatch:\n\n# Allow this job to clone the repo and create a page deployment\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout your repository using git\n        uses: actions/checkout@v6\n      - name: Install, build, and upload your site\n        uses: withastro/action@v5\n        with:\n          path: website # The root location of your Astro project inside the repository. (optional)\n          # node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 20. (optional)\n          package-manager: pnpm # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional)\n\n  deploy:\n    needs: build\n    runs-on: ubuntu-latest\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\ndebug/\ntarget/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\n# Cargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n# MSVC Windows builds of rustc generate these, which store debugging information\n*.pdb\n\n### Linux ###\n*~\n\n# temporary files which can be created if a process still has a handle open of a deleted file\n.fuse_hidden*\n\n# KDE directory preferences\n.directory\n\n# Linux trash folder which might appear on any partition or disk\n.Trash-*\n\n# .nfs files are created when an open file is removed but is still being accessed\n.nfs*\n\n### macOS ###\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n### macOS Patch ###\n# iCloud generated files\n*.icloud\n\n### Windows ###\n# Windows thumbnail cache files\nThumbs.db\nThumbs.db:encryptable\nehthumbs.db\nehthumbs_vista.db\n\n# Dump file\n*.stackdump\n\n# Folder config file\n[Dd]esktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# UV\n#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#uv.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control\n.pdm.toml\n.pdm-python\n.pdm-build/\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n\n# PyPI configuration file\n.pypirc\n\n.claude\nCLAUDE\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nresolver = \"2\"\nmembers = [\n    \"crates/code2prompt-core\",\n    \"crates/code2prompt\",\n    \"crates/code2prompt-python\",\n]\ndefault-members = [\"crates/code2prompt-core\", \"crates/code2prompt\"]\n\n[profile.release]\nlto = \"thin\"\npanic = 'abort'\ncodegen-units = 1\n\n[workspace.dependencies]\nanyhow = \"1.0.98\"\nansi_term = \"0.12.1\"\narboard = { version = \"3.6.0\" }\nbracoxide = \"0.1.8\"\ncolored = \"3.0.0\"\ncsv = \"1.4.0\"\nchrono = { version = \"0.4\", features = [\"serde\"] }\nchardetng = { version = \"0.1.17\" }\nclap = { version = \"4.5\", features = [\"derive\"] }\ncontent_inspector = \"0.2.4\"\ncrossterm = \"0.29.0\"\ndirs = \"6.0.0\"\nderive_builder = { version = \"0.20.2\" }\nenv_logger = { version = \"0.11.3\" }\nencoding_rs = { version = \"0.8.35\" }\nindicatif = \"0.18.0\"\ninquire = \"0.9.1\"\nlog = \"0.4\"\nlscolors = { version = \"0.21.0\", features = [\"ansi_term\"] }\nignore = \"0.4.25\"\ngit2 = { version = \"0.20.2\", default-features = false, features = [\n    \"https\",\n    \"vendored-libgit2\",\n    \"vendored-openssl\",\n] }\nglobset = \"0.4.15\"\nhandlebars = \"6.4.0\"\nonce_cell = \"1.19.0\"\npyo3 = { version = \"0.27\", features = [\"extension-module\", \"abi3-py312\"] }\nratatui = \"0.29.0\"\nregex = \"1.10.3\"\nrayon = \"1.11.0\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0.148\"\ntermtree = \"0.5\"\ntiktoken-rs = \"0.9.1\"\nterminal_size = \"0.4.3\"\ntokio = { version = \"1.49.0\", features = [\"full\"] }\ntoml = \"0.9.10\"\ntui-tree-widget = \"0.23.0\"\ntui-textarea = \"0.7\"\nunicode-width = \"0.2.0\"\nwalkdir = \"2.4.0\"\nwinapi = { version = \"0.3.9\", features = [\"errhandlingapi\"] }\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 Mufeed VH\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <a href=\"https://code2prompt.dev\">\n    <img align=\"center\" width=\"550px\" src=\"https://github.com/mufeedvh/code2prompt/blob/main/.assets/logo_dark_v0.0.2.svg?raw=true\" alt=\"Code2prompt\"/>\n  </a>\n  <br>\n  <h3>Convert your codebase into a single LLM prompt.</h3>\n</div>\n\n<p align=\"center\">\n  <a href=\"https://code2prompt.dev\"><b>Website</b></a> •\n  <a href=\"https://code2prompt.dev/docs/welcome/\"><b>Documentation</b></a> •\n  <a href=\"https://discord.com/invite/ZZyBbsHTwH\"><b>Discord</b></a>\n</p>\n\n<div align=\"center\">\n\n[![License](https://img.shields.io/github/license/mufeedvh/code2prompt.svg?style=flat-square)](https://github.com/mufeedvh/code2prompt/blob/master/LICENSE)\n[![Crates.io](https://img.shields.io/crates/v/code2prompt.svg?style=flat-square)](https://crates.io/crates/code2prompt)\n[![PyPI](https://img.shields.io/pypi/v/code2prompt-rs?style=flat-square&logo=pypi&logoColor=white)](https://pypi.org/project/code2prompt-rs/)\n[![CI](https://github.com/mufeedvh/code2prompt/actions/workflows/ci.yml/badge.svg?style=flat-square)](https://github.com/mufeedvh/code2prompt/actions)\n[![Discord](https://img.shields.io/discord/1342336677905039451?style=flat-square&logo=discord&logoColor=white)](https://discord.com/invite/ZZyBbsHTwH)\n[![Docs.rs](https://docs.rs/code2prompt-core/badge.svg?style=flat-square)](https://docs.rs/code2prompt-core)\n[![Crates.io Downloads](https://img.shields.io/crates/d/code2prompt.svg?style=flat-square)](https://crates.io/crates/code2prompt)\n[![GitHub Stars](https://img.shields.io/github/stars/mufeedvh/code2prompt?style=social)](https://github.com/mufeedvh/code2prompt)\n\n</div>\n\n---\n\n<h1 align=\"center\">\n  <a href=\"https://code2prompt.dev\"><img src=\"https://github.com/mufeedvh/code2prompt/blob/main/.assets/demo.gif?raw=true\" alt=\"code2prompt demo\"></a>\n</h1>\n\n![Flow Diagram](https://github.com/mufeedvh/code2prompt/blob/main/.assets/flow_diagram.png?raw=true)\n\n**Code2Prompt** is a powerful context engineering tool designed to ingest codebases and format them for Large Language Models. Whether you are manually copying context for ChatGPT, building AI agents via Python, or running a MCP server, Code2Prompt streamlines the context preparation process.\n\n## ⚡ Quick Install\n\n### Cargo\n\n```bash\ncargo install code2prompt \n```\n\nTo enable optional Wayland support (e.g., for clipboard integration on Wayland-based systems), use the `wayland` feature flag:\n\n```bash\ncargo install --features wayland code2prompt\n```\n\n### Homebrew\n\n```bash\nbrew install code2prompt\n```\n\n### SDK with pip 🐍\n\n```bash\npip install code2prompt-rs\n```\n\n## 🚀 Quick Start\n\nOnce installed, generating a prompt from your codebase is as simple as pointing the tool to your directory.\n\n**Basic Usage**: Generate a prompt from the current directory and copy it to the clipboard.\n\n```sh\ncode2prompt .\n```\n\n**Save to file**:\n\n```sh\ncode2prompt path/to/project --output-file prompt.txt\n```\n\n## 🌐 Ecosystem\n\nCode2Prompt is more than just a CLI tool. It is a complete ecosystem for codebase context.\n\n| 🧱 Core Library <br><img src=\"https://img.shields.io/badge/Rust-FF6700?style=for-the-badge&logo=rust&logoColor=white\" alt=\"Rust Core Badge\"/>| 💻 CLI Tool <br><img src=\"https://img.shields.io/badge/Terminal-2C3E50?style=for-the-badge&logo=gnu-bash&logoColor=white\" alt=\"CLI Badge\"/> | 🐍 Python SDK <br><img src=\"https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white\" alt=\"Python SDK Badge\"/> | 🤖 MCP Server <img src=\"https://img.shields.io/badge/Agentic%20Flow-7E57C2?style=for-the-badge&logo=server&logoColor=white\" alt=\"MCP Server Badge\"/> |\n| :---: | :---: | :---: | :---: |\n| The internal, high-speed library responsible for secure file traversal, respecting `.gitignore` rules, and structuring Git metadata. | Designed for humans, featuring both a minimal CLI and an interactive TUI. Generate formatted prompts, track token usage, and outputs the result to your clipboard or stdout. | Provides fast Python bindings to the Rust Core. Ideal for AI Agents, automation scripts, or deep integration into RAG pipelines. Available on PyPI. | Run Code2Prompt as a local service, enabling agentic applications to read your local codebase efficiently without bloating your context window. |\n\n## 📚 Documentation\n\nCheck our online [documentation](https://code2prompt.dev/docs/welcome/) for detailed instructions\n\n## ✨ Features\n\nCode2Prompt transforms your entire codebase into a well-structured prompt for large language models. Key features include:\n\n- **Terminal User Interface (TUI)**: Interactive terminal interface for configuring and generating prompts\n- **Smart Filtering**: Include/exclude files using glob patterns and respect `.gitignore` rules\n- **Flexible Templating**: Customize prompts with Handlebars templates for different use cases\n- **Automatic Code Processing**: Convert codebases of any size into readable, formatted prompts\n- **Token Tracking**: Track token usage to stay within LLM context limits\n- **Smart File Reading**: Simplify reading various file formats for LLMs (CSV, Notebooks, JSONL, etc.)\n- **Git Integration**: Include diffs, logs, and branch comparisons in your prompts\n- **Blazing Fast**: Built in Rust for high performance and low resource usage\n\nStop manually copying files and formatting code for LLMs. Code2Prompt handles the tedious work so you can focus on getting insights and solutions from AI models.\n\n## Alternative Installation\n\nRefer to the [documentation](https://code2prompt.dev/docs/how_to/install/) for detailed installation instructions.\n\n### Binary releases\n\nDownload the latest binary for your OS from [Releases](https://github.com/mufeedvh/code2prompt/releases).\n\n### Source build\n\nRequires:\n\n- [Git](https://git-scm.org/downloads), [Rust](https://rust-lang.org/tools/install) and `Cargo`.\n\n```sh\ngit clone https://github.com/mufeedvh/code2prompt.git\ncd code2prompt/\ncargo install --path crates/code2prompt\n```\n\n## ⭐ Star Gazing\n\n[![Star History Chart](https://api.star-history.com/svg?repos=mufeedvh/code2prompt&type=Date)](https://star-history.com/#mufeedvh/code2prompt&Date)\n\n## 📜 License\n\nLicensed under the MIT License, see <a href=\"https://github.com/mufeedvh/code2prompt/blob/master/LICENSE\">LICENSE</a> for more information.\n\n## Liked the project?\n\nIf you liked the project and found it useful, please give it a :star: !\n\n## 👥 Contribution\n\nWays to contribute:\n\n- Suggest a feature\n- Report a bug\n- Fix something and open a pull request\n- Help me document the code\n- Spread the word\n"
  },
  {
    "path": "README_ES.md",
    "content": "# code2prompt\n\n[![crates.io](https://img.shields.io/crates/v/code2prompt.svg)](https://crates.io/crates/code2prompt)\n[![LICENSE](https://img.shields.io/github/license/mufeedvh/code2prompt.svg#cache1)](https://github.com/mufeedvh/code2prompt/blob/master/LICENSE)\n\n<h1 align=\"center\">\n  <a href=\"https://github.com/mufeedvh/code2prompt\"><img src=\".assets/code2prompt-screenshot.png\" alt=\"code2prompt\"></a>\n</h1>\n\n`code2prompt` es una herramienta de línea de comandos (CLI) que convierte tu base de código en un único prompt para LLM, incluyendo un árbol de archivos fuente, plantillas de prompts y conteo de tokens.\n\n## Tabla de Contenidos\n\n- [Características](#features)\n- [Instalación](#installation)\n- [Uso](#usage)\n- [Plantillas](#templates)\n- [Variables Definidas por el Usuario](#user-defined-variables)\n- [Tokenizadores](#tokenizers)\n- [Contribución](#contribution)\n- [Licencia](#license)\n- [Apoya al Autor](#support-the-author)\n\n## Características\n\nPuedes ejecutar esta herramienta en un directorio completo, y generará un prompt bien formateado en Markdown que detalla la estructura del árbol de archivos fuente y todo el código. Luego puedes cargar este documento en modelos como GPT o Claude con ventanas de contexto amplias y pedirles que:\n\n- Generen prompts para LLM rápidamente a partir de bases de código de cualquier tamaño.\n- Personalicen la generación de prompts usando plantillas de Handlebars (ver la [plantilla predeterminada](src/default_template.hbs))\n- Respete los archivos `.gitignore`.\n- Filtren y excluyan archivos utilizando patrones glob.\n- Muestren el conteo de tokens del prompt generado (Ver [Tokenizadores](#tokenizers) para más detalles).\n- Incluyan opcionalmente salidas de `git diff` (archivos en estado staged) en el prompt generado.\n- Copien automáticamente el prompt generado al portapapeles.\n- Guarden el prompt generado en un archivo de salida.\n- Excluyan archivos y carpetas por nombre o ruta.\n- Añadan números de línea a los bloques de código fuente.\n\nPuedes personalizar las plantillas de prompts para lograr cualquier caso de uso deseado. Básicamente, recorre una base de código y crea un prompt con todos los archivos fuente combinados. En resumen, automatiza la tarea de copiar y formatear múltiples archivos fuente en un único prompt y te informa cuántos tokens consume.\n\n## Instalación\n\n### Lanzamiento de binarios\nDescarga el binario más reciente para tu sistema operativo desde [Releases](https://github.com/mufeedvh/code2prompt/releases).\n\n### Construcción desde código fuente\nRequisitos:\n\n- [Git](https://git-scm.org/downloads), [Rust](https://rust-lang.org/tools/install) y Cargo.\n\n```sh\ngit clone https://github.com/mufeedvh/code2prompt.git\ncd code2prompt/\ncargo build --release\n```\n\n## cargo\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\nPara versiones no publicadas:\n\n```sh\ncargo install --git https://github.com/mufeedvh/code2prompt\n```\n\n### AUR\n`code2prompt` está disponible en [`AUR`](https://aur.archlinux.org/packages?O=0&K=code2prompt).  Instálalo usando cualquier gestor AUR.\n\n```sh\nparu/yay -S code2prompt\n```\n\n### Nix\nSi utilizas Nix, puedes instalarlo con `nix-env` o `profile`:\n\n```sh\n# Sin flakes:\nnix-env -iA nixpkgs.code2prompt\n# Con flakes:\nnix profile install nixpkgs#code2prompt\n```\n\n## Uso\n\nGenera un prompt desde un directorio de código:\n\n```sh\ncode2prompt path/to/codebase\n```\n\nUsa un archivo de plantilla Handlebars personalizado:\n\n```sh\ncode2prompt path/to/codebase -t path/to/template.hbs\n```\n\nFiltrar archivos usando patrones glob:\n\n```sh\ncode2prompt path/to/codebase --include=\"*.rs,*.toml\"\n```\n\nExcluir archivos usando patrones glob:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.txt,*.md\"\n```\n\nExcluir archivos/carpetas del árbol de origen basándose en patrones de exclusión:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.npy,*.wav\" --exclude-from-tree\n```\n\nMostrar el conteo de tokens del prompt generado:\n\n```sh\ncode2prompt path/to/codebase --tokens\n```\n\nEspecificar un tokenizador para el conteo de tokens:\n\n```sh\ncode2prompt path/to/codebase --tokens --encoding=p50k\n```\n\nTokenizadores soportados: `cl100k`, `p50k`, `p50k_edit`, `r50k_bas`.\n> [!NOTE]  \n> Ver [Tokenizadores](#tokenizers) para más detalles.\n\nGuardar el prompt generado en un archivo de salida:\n\n```sh\ncode2prompt path/to/codebase --output=output.txt\n```\n\nImprimir salida como JSON:\n\n```sh\ncode2prompt path/to/codebase --json\n```\n\nLa salida JSON tendrá la siguiente estructura:\n\n```json\n{\n  \"prompt\": \"<Generated Prompt>\", \n  \"directory_name\": \"codebase\",\n  \"token_count\": 1234,\n  \"model_info\": \"Modelos de ChatGPT, text-embedding-ada-002\",\n  \"files\": []\n}\n```\n\nGenerar un mensaje de commit de Git (para archivos en estado staged):\n\n```sh\ncode2prompt path/to/codebase --diff -t templates/write-git-commit.hbs\n```\n\nGenerar una Pull Request comparando ramas (para archivos en estado staged):\n\n```sh\ncode2prompt path/to/codebase --git-diff-branch 'main, development' --git-log-branch 'main, development' -t templates/write-github-pull-request.hbs\n```\n\nAñadir números de línea a los bloques de código fuente:\n\n```sh\ncode2prompt path/to/codebase --line-number\n```\n\nDesactivar el envoltorio de código dentro de bloques de código markdown:\n\n```sh\ncode2prompt path/to/codebase --no-codeblock\n```\n\n- Reescribir el código a otro idioma.\n- Encontrar errores/vulnerabilidades de seguridad.\n- Documentar el código.\n- Implementar nuevas características.\n\n> Inicialmente escribí esto para uso personal para utilizar la ventana de contexto de 200K de Claude 3.0 y ha resultado ser bastante útil, ¡así que decidí hacerlo de código abierto!\n\n## Plantillas\n\n`code2prompt` viene con un conjunto de plantillas integradas para casos de uso comunes. Puedes encontrarlas en el directorio [`templates`](templates).\n\n### [`document-the-code.hbs`](templates/document-the-code.hbs)\n\nUsa esta plantilla para generar prompts para documentar el código. Añadirá comentarios de documentación a todas las funciones, métodos, clases y módulos públicos en la base de código.\n\n### [`find-security-vulnerabilities.hbs`](templates/find-security-vulnerabilities.hbs)\n\nUsa esta plantilla para generar prompts para encontrar posibles vulnerabilidades de seguridad en la base de código. Buscará problemas de seguridad comunes y proporcionará recomendaciones sobre cómo solucionarlos o mitigarlos.\n\n### [`clean-up-code.hbs`](templates/clean-up-code.hbs)\n\nUsa esta plantilla para generar prompts para limpiar y mejorar la calidad del código. Buscará oportunidades para mejorar la legibilidad, adherencia a las mejores prácticas, eficiencia, manejo de errores, y más.\n\n### [`fix-bugs.hbs`](templates/fix-bugs.hbs)\n\nUsa esta plantilla para generar prompts para corregir errores en la base de código. Ayudará a diagnosticar problemas, proporcionar sugerencias de corrección y actualizar el código con las correcciones propuestas.\n\n### [`write-github-pull-request.hbs`](templates/write-github-pull-request.hbs)\n\nUsa esta plantilla para crear una descripción de Pull Request de GitHub en markdown comparando el git diff y el git log de dos ramas.\n\n### [`write-github-readme.hbs`](templates/write-github-readme.hbs)\n\nUsa esta plantilla para generar un archivo README de alta calidad para el proyecto, adecuado para alojar en GitHub. Analizará la base de código para entender su propósito y funcionalidad, y generará el contenido del README en formato Markdown.\n\n### [`write-git-commit.hbs`](templates/write-git-commit.hbs)\n\nUsa esta plantilla para generar commits de git a partir de los archivos en estado staged en tu directorio git. Analizará la base de código para entender su propósito y funcionalidad, y generará el contenido del mensaje de commit de git en formato Markdown.\n\n### [`improve-performance.hbs`](templates/improve-performance.hbs)\n\nUsa esta plantilla para generar prompts para mejorar el rendimiento de la base de código. Buscará oportunidades de optimización, proporcionará sugerencias específicas y actualizará el código con los cambios.\n\nPuedes usar estas plantillas pasando el flag `-t` seguido de la ruta al archivo de plantilla. Por ejemplo:\n\n```sh\ncode2prompt path/to/codebase -t templates/document-the-code.hbs\n```\n\n## Variables Definidas por el Usuario\n\n`code2prompt` soporta el uso de variables definidas por el usuario en las plantillas de Handlebars. Cualquier variable en la plantilla que no sea parte del contexto predeterminado (`absolute_code_path`, `source_tree`, `files`) será tratada como una variable definida por el usuario.\n\nDurante la generación del prompt, `code2prompt` solicitará al usuario que ingrese valores para estas variables definidas por el usuario. Esto permite una mayor personalización de los prompts generados basados en la entrada del usuario.\n\nPor ejemplo, si tu plantilla incluye `{{challenge_name}}` y `{{challenge_description}}`, se te pedirá que ingreses valores para estas variables al ejecutar `code2prompt`.\n\nEsta característica permite crear plantillas reutilizables que pueden adaptarse a diferentes escenarios basados en la información proporcionada por el usuario.\n\n## Tokenizadores\n\nLa tokenización se implementa usando [`tiktoken-rs`](https://github.com/zurawiki/tiktoken-rs). `tiktoken` soporta estas codificaciones utilizadas por los modelos de OpenAI:\n\n| Nombre de codificación  | Modelos de OpenAI                                                          |\n| ----------------------- | ------------------------------------------------------------------------- |\n| `cl100k_base`           | Modelos de ChatGPT, `text-embedding-ada-002`                              |\n| `p50k_base`             | Modelos de código, `text-davinci-002`, `text-davinci-003`                 |\n| `p50k_edit`             | Usar para modelos de edición como `text-davinci-edit-001`, `code-davinci-edit-001` |\n| `r50k_base` (o `gpt2`)  | Modelos GPT-3 como `davinci`                                              |\n| `o200k_base`            | Modelos GPT-4o                                                            |\n\nPara más contexto sobre los diferentes tokenizadores, ver el [OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/66b988407d8d13cad5060a881dc8c892141f2d5c/examples/How_to_count_tokens_with_tiktoken.ipynb)\n\n## ¿Cómo es útil?\n\n`code2prompt` facilita la generación de prompts para LLMs desde tu base de código. Recorre el directorio, construye una estructura de árbol y recopila información sobre cada archivo. Puedes personalizar la generación de prompts usando plantillas de Handlebars. El prompt generado se copia automáticamente en tu portapapeles y también se puede guardar en un archivo de salida. `code2prompt` ayuda a agilizar el proceso de creación de prompts para análisis de código, generación y otras tareas.\n\n## Contribución\n\nFormas de contribuir:\n\n- Sugerir una característica\n- Reportar un error  \n- Arreglar algo y abrir un pull request\n- Ayudarme a documentar el código\n- Difundir la palabra\n\n## Licencia\n\nLicenciado bajo la Licencia MIT, ver <a href=\"https://github.com/mufeedvh/code2prompt/blob/master/LICENSE\">LICENSE</a> para más información.\n\n## ¿Te gustó el proyecto?\n\nSi te gustó el proyecto y lo encontraste útil, por favor dale una :star: y considera apoyar a los autores!\n"
  },
  {
    "path": "crates/code2prompt/Cargo.toml",
    "content": "[package]\nname = \"code2prompt\"\nversion = \"4.2.0\"\nedition = \"2024\"\ndescription = \"Command-line interface for code2prompt\"\nlicense = \"MIT\"\nrepository = \"https://github.com/mufeedvh/code2prompt\"\nreadme = \"../../README.md\"\n\n[features]\nwayland = [\"arboard/wayland-data-control\"]\n\n[dependencies]\ncode2prompt_core = { path = \"../code2prompt-core\", version = \"4.2.0\" }\nclap = { workspace = true }\nenv_logger = { workspace = true }\narboard = { workspace = true }\nanyhow = { workspace = true }\ncolored = { workspace = true }\nindicatif = { workspace = true }\nlog = { workspace = true }\nserde = { workspace = true }\nserde_json = { workspace = true }\ntoml = { workspace = true }\ninquire = { workspace = true }\nterminal_size = { workspace = true }\nlscolors = { workspace = true }\nansi_term = { workspace = true }\nratatui = { workspace = true }\ncrossterm = { workspace = true }\ntokio = { workspace = true }\ntui-tree-widget = { workspace = true }\ntui-textarea = { workspace = true }\nwalkdir = { workspace = true }\nunicode-width = { workspace = true }\nbracoxide = { workspace = true }\ngit2 = { workspace = true }\nchrono = { workspace = true }\ndirs = { workspace = true }\nregex = { workspace = true }\nhandlebars = { workspace = true }\nignore = { workspace = true }\ntiktoken-rs = { workspace = true }\n\n[target.'cfg(windows)'.dependencies]\nwinapi = { workspace = true }\n\n[[bin]]\nname = \"code2prompt\"\npath = \"src/main.rs\"\n\n[dev-dependencies]\ntempfile = \"3.24\"\nassert_cmd = \"2.1.1\"\npredicates = \"3.1\"\nenv_logger = \"0.11.3\"\nrstest = \"0.26\"\n"
  },
  {
    "path": "crates/code2prompt/src/args.rs",
    "content": "//! Command-line argument parsing and validation.\n//!\n//! This module defines the CLI structure using clap for parsing command-line arguments\n//! and options for the code2prompt tool. It supports both TUI and CLI modes with\n//! comprehensive configuration options for file selection, output formatting,\n//! tokenization, and git integration.\nuse anyhow::{Result, anyhow};\nuse clap::{Parser, builder::ValueParser};\nuse code2prompt_core::{\n    sort::FileSortMethod, template::OutputFormat, tokenizer::TokenFormat, tokenizer::TokenizerType,\n};\nuse serde::de::DeserializeOwned;\nuse std::path::PathBuf;\n\n// ~~~ CLI Arguments ~~~\n#[derive(Parser, Debug)]\n#[clap(\n    name = env!(\"CARGO_PKG_NAME\"),\n    version = env!(\"CARGO_PKG_VERSION\"),\n    author = env!(\"CARGO_PKG_AUTHORS\")\n)]\n#[command(arg_required_else_help = true)]\npub struct Cli {\n    /// Path to the codebase directory\n    #[arg(value_name = \"PATH_TO_ANALYZE\", default_value = \".\")]\n    pub path: PathBuf,\n\n    /// Optional output file (use \"-\" for stdout)\n    #[arg(short = 'O', long = \"output-file\", value_name = \"FILE\")]\n    pub output_file: Option<String>,\n\n    /// Launch the Terminal User Interface\n    #[clap(long)]\n    pub tui: bool,\n\n    /// Patterns to include\n    #[clap(short = 'i', long = \"include\")]\n    pub include: Vec<String>,\n\n    /// Patterns to exclude\n    #[clap(short = 'e', long = \"exclude\")]\n    pub exclude: Vec<String>,\n\n    /// Output format\n    #[clap(\n        short = 'F',\n        long = \"output-format\",\n        value_name = \"markdown, json, xml\",\n        value_parser = ValueParser::new(parse_serde::<OutputFormat>)\n    )]\n    pub output_format: Option<OutputFormat>,\n\n    /// Optional Path to a custom Handlebars template\n    #[clap(short, long, value_name = \"TEMPLATE\")]\n    pub template: Option<PathBuf>,\n\n    /// List the full directory tree\n    #[clap(long)]\n    pub full_directory_tree: bool,\n\n    /// Token encoding to use for token count\n    #[clap(\n        long,\n        value_name = \"cl100k, p50k, p50k_edit, r50k\",\n        value_parser = ValueParser::new(parse_serde::<TokenizerType>),\n    )]\n    pub encoding: Option<TokenizerType>,\n\n    /// Display the token count of the generated prompt. Accepts a format: \"raw\" (machine parsable) or \"format\" (human readable)\n    #[clap(\n        long,\n        value_name = \"raw,format\",\n        value_parser = ValueParser::new(parse_serde::<TokenFormat>),\n    )]\n    pub token_format: Option<TokenFormat>,\n\n    /// Include git diff\n    #[clap(short, long)]\n    pub diff: bool,\n\n    /// Generate git diff between two branches\n    #[clap(long, value_name = \"BRANCHES\", num_args = 2, value_delimiter = ',')]\n    pub git_diff_branch: Option<Vec<String>>,\n\n    /// Retrieve git log between two branches\n    #[clap(long, value_name = \"BRANCHES\", num_args = 2, value_delimiter = ',')]\n    pub git_log_branch: Option<Vec<String>>,\n\n    /// Add line numbers to the source code\n    #[clap(short, long)]\n    pub line_numbers: bool,\n\n    /// If true, paths in the output will be absolute instead of relative.\n    #[clap(long)]\n    pub absolute_paths: bool,\n\n    /// Follow symlinks\n    #[clap(short = 'L', long)]\n    pub follow_symlinks: bool,\n\n    /// Include hidden directories and files\n    #[clap(long)]\n    pub hidden: bool,\n\n    /// Disable wrapping code inside markdown code blocks\n    #[clap(long)]\n    pub no_codeblock: bool,\n\n    /// Copy output to clipboard\n    #[clap(short = 'c', long)]\n    pub clipboard: bool,\n\n    /// Optional Disable copying to clipboard (deprecated, use default behavior)\n    #[clap(long, hide = true)]\n    pub no_clipboard: bool,\n\n    /// Skip .gitignore rules\n    #[clap(long)]\n    pub no_ignore: bool,\n\n    /// Sort order for files\n    #[clap(\n        long,\n        value_name = \"name_asc, name_desc, date_asc, date_desc\",\n        value_parser = ValueParser::new(parse_serde::<FileSortMethod>),\n    )]\n    pub sort: Option<FileSortMethod>,\n\n    /// Suppress progress and success messages\n    #[clap(short = 'q', long)]\n    pub quiet: bool,\n\n    /// Display a visual token map of files (similar to disk usage tools)\n    #[clap(long)]\n    pub token_map: bool,\n\n    /// Maximum number of lines to display in token map (default: terminal height - 10)\n    #[clap(long, value_name = \"NUMBER\")]\n    pub token_map_lines: Option<usize>,\n\n    /// Minimum percentage of tokens to display in token map (default: 0.1%)\n    #[clap(long, value_name = \"PERCENT\")]\n    pub token_map_min_percent: Option<f64>,\n\n    /// Start with all files deselected\n    #[clap(long)]\n    pub deselected: bool,\n\n    #[arg(long, hide = true)]\n    pub clipboard_daemon: bool,\n}\n\n/// Helper function to parse serde deserializable enum from string inputs.\nfn parse_serde<T: DeserializeOwned>(s: &str) -> Result<T> {\n    serde_json::from_value(serde_json::Value::String(s.to_string()))\n        .map_err(|e| anyhow!(\"Failed to parse value: {}\", e))\n}\n"
  },
  {
    "path": "crates/code2prompt/src/clipboard.rs",
    "content": "use anyhow::{Context, Result};\n\n#[cfg(not(target_os = \"linux\"))]\n/// Copies the provided text to the system clipboard.\n///\n/// This is a simple, one-shot copy operation suitable for non-Linux platforms\n/// or scenarios where maintaining the clipboard content is not required.\n///\n/// # Arguments\n///\n/// * `text` - The text content to be copied.\n///\n/// # Returns\n///\n/// * `Result<()>` - Returns Ok on success, or an error if the clipboard could not be accessed.\npub fn copy_text_to_clipboard(text: &str) -> Result<()> {\n    use arboard::Clipboard;\n    match Clipboard::new() {\n        Ok(mut clipboard) => {\n            clipboard\n                .set_text(text.to_string())\n                .context(\"Failed to copy to clipboard\")?;\n            Ok(())\n        }\n        Err(e) => Err(anyhow::anyhow!(\"Failed to initialize clipboard: {}\", e)),\n    }\n}\n\n#[cfg(target_os = \"linux\")]\n/// Entry point for the clipboard daemon process on Linux.\n///\n/// This function reads clipboard content from its standard input, sets it as the system clipboard,\n/// and then waits to serve clipboard requests. This ensures that the clipboard content remains available\n/// even after the main application exits. The daemon will exit automatically once the clipboard is overwritten.\n///\n/// # Returns\n///\n/// * `Result<()>` - Returns Ok on success or an error if clipboard operations fail.\npub fn serve_clipboard_daemon() -> Result<()> {\n    use arboard::{Clipboard, LinuxClipboardKind, SetExtLinux};\n    use std::io::Read;\n\n    // Read content from stdin\n    let mut content_from_stdin = String::new();\n    std::io::stdin()\n        .read_to_string(&mut content_from_stdin)\n        .context(\"Failed to read from stdin\")?;\n\n    // Initialize the clipboard\n    let mut clipboard = Clipboard::new().context(\"Failed to initialize clipboard\")?;\n\n    // Explicitly set the clipboard selection to Clipboard (not Primary)\n    clipboard\n        .set()\n        .clipboard(LinuxClipboardKind::Clipboard)\n        .wait()\n        .text(content_from_stdin)\n        .context(\"Failed to set clipboard content\")?;\n    Ok(())\n}\n\n#[cfg(target_os = \"linux\")]\n/// Spawns a daemon process to maintain clipboard content on Linux.\n///\n/// On Linux (Wayland/X11), the clipboard content is owned by the process that defined it.\n/// If the main application exits, the clipboard would be cleared.\n/// To avoid this, this function spawns a new process that will run in the background\n/// (daemon) and maintain the clipboard content until it is overwritten by a new copy.\n///\n/// # Arguments\n///\n/// * `text` - The text to be served by the daemon process.\n///\n/// # Returns\n///\n/// * `Result<()>` - Returns Ok if the daemon process was spawned and the content was sent successfully,\n///   or an error if the process could not be launched or written to.\npub fn spawn_clipboard_daemon(content: &str) -> Result<()> {\n    use std::process::{Command, Stdio};\n    use log::info;\n\n    // ~~~ Setting up the command to run the daemon ~~~\n    let current_exe: std::path::PathBuf =\n        std::env::current_exe().context(\"Failed to get current executable path\")?;\n    let mut args: Vec<String> = std::env::args().collect();\n    args.push(\"--clipboard-daemon\".to_string());\n\n    // ~~~ Spawn the clipboard daemon process ~~~\n    let mut child = Command::new(current_exe)\n        .args(&args[1..])\n        .stdin(Stdio::piped())\n        .stdout(Stdio::null())\n        .stderr(Stdio::null())\n        .spawn()\n        .context(\"Failed to launch clipboard daemon process\")?;\n\n    // ~~~ Write the content to the daemon's standard input ~~~\n    use std::io::Write;\n    let mut stdin = child\n        .stdin\n        .take()\n        .context(\"Failed to acquire stdin pipe for clipboard daemon process\")?;\n    stdin\n        .write_all(content.as_bytes())\n        .context(\"Failed to write content to clipboard daemon process\")?;\n    info!(\"Clipboard daemon launched successfully\");\n\n    Ok(())\n}\n\n/// Copy text to clipboard\npub fn copy_to_clipboard(text: &str) -> Result<()> {\n    #[cfg(target_os = \"linux\")]\n    {\n        spawn_clipboard_daemon(text)\n    }\n    #[cfg(not(target_os = \"linux\"))]\n    {\n        copy_text_to_clipboard(text)\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/config.rs",
    "content": "//! Configuration parsing and session creation utilities.\n//!\n//! This module handles the conversion of command-line arguments into\n//! Code2PromptSession instances, consolidating all configuration parsing\n//! logic in one place for better maintainability and separation of concerns.\n\nuse anyhow::{Context, Result};\nuse code2prompt_core::{\n    configuration::Code2PromptConfig,\n    session::Code2PromptSession,\n    sort::FileSortMethod,\n    template::{OutputFormat, extract_undefined_variables},\n    tokenizer::TokenizerType,\n};\nuse inquire::Text;\nuse log::error;\nuse std::path::PathBuf;\n\nuse crate::{args::Cli, config_loader::ConfigSource};\n\n/// Unified session builder that merges configuration layering in one place\n/// - base: Some(&ConfigSource) to use loaded config as defaults; None to use CLI defaults\n/// - args: CLI arguments\n/// - tui_mode: whether running in TUI mode (enables token map by default)\npub fn build_session(\n    base: Option<&ConfigSource>,\n    args: &Cli,\n    tui_mode: bool,\n) -> Result<Code2PromptSession> {\n    let mut configuration = Code2PromptConfig::builder();\n\n    let cfg = base.map(|b| &b.config);\n\n    // Path: config path takes precedence if provided, otherwise CLI path\n    if let Some(c) = cfg {\n        if let Some(path) = &c.path {\n            configuration.path(PathBuf::from(path));\n        } else {\n            configuration.path(args.path.clone());\n        }\n    } else {\n        configuration.path(args.path.clone());\n    }\n\n    // Include/Exclude patterns:\n    // If CLI provides any patterns, they override config patterns completely (to avoid conflicts)\n    let use_cli_patterns = !args.include.is_empty() || !args.exclude.is_empty();\n    let (include_patterns, exclude_patterns) = if use_cli_patterns {\n        (\n            expand_comma_separated_patterns(&args.include),\n            expand_comma_separated_patterns(&args.exclude),\n        )\n    } else if let Some(c) = cfg {\n        (c.include_patterns.clone(), c.exclude_patterns.clone())\n    } else {\n        (\n            expand_comma_separated_patterns(&args.include),\n            expand_comma_separated_patterns(&args.exclude),\n        )\n    };\n    configuration\n        .include_patterns(include_patterns)\n        .exclude_patterns(exclude_patterns);\n\n    // Display options: CLI overrides config (logical-or semantics for booleans)\n    let cfg_line_numbers = cfg.map(|c| c.line_numbers).unwrap_or(false);\n    let cfg_absolute = cfg.map(|c| c.absolute_path).unwrap_or(false);\n    let cfg_full_tree = cfg.map(|c| c.full_directory_tree).unwrap_or(false);\n    configuration\n        .line_numbers(args.line_numbers || cfg_line_numbers)\n        .absolute_path(args.absolute_paths || cfg_absolute)\n        .full_directory_tree(args.full_directory_tree || cfg_full_tree);\n\n    // Output format: CLI overrides config\n    let output_format = if let Some(output_format_str) = args.output_format {\n        output_format_str\n    } else if let Some(c) = cfg {\n        c.output_format.unwrap_or(OutputFormat::Markdown)\n    } else {\n        OutputFormat::Markdown\n    };\n    configuration.output_format(output_format);\n\n    // Sort method: CLI overrides config\n    let sort_method = if let Some(sort_str) = args.sort {\n        sort_str\n    } else if let Some(c) = cfg {\n        c.sort_method.unwrap_or(FileSortMethod::NameAsc)\n    } else {\n        FileSortMethod::NameAsc\n    };\n    configuration.sort_method(sort_method);\n\n    // Tokenizer settings: CLI overrides config\n    let tokenizer_type = if let Some(encoding) = args.encoding {\n        encoding\n    } else if let Some(c) = cfg {\n        c.encoding.unwrap_or(TokenizerType::Cl100kBase)\n    } else {\n        TokenizerType::Cl100kBase\n    };\n\n    // Token format: CLI overrides config\n    let token_format = if let Some(format) = args.token_format {\n        format\n    } else if let Some(c) = cfg {\n        c.token_format\n            .unwrap_or(code2prompt_core::tokenizer::TokenFormat::Format)\n    } else {\n        code2prompt_core::tokenizer::TokenFormat::Format\n    };\n\n    configuration\n        .encoding(tokenizer_type)\n        .token_format(token_format);\n\n    // Template: CLI overrides config\n    let (template_str, template_name) = if args.template.is_some() {\n        parse_template(&args.template).map_err(|e| {\n            error!(\"Failed to parse template: {}\", e);\n            e\n        })?\n    } else if let Some(c) = cfg {\n        (\n            c.template_str.clone().unwrap_or_default(),\n            c.template_name\n                .clone()\n                .unwrap_or_else(|| \"default\".to_string()),\n        )\n    } else {\n        (\"\".to_string(), \"default\".to_string())\n    };\n\n    configuration\n        .template_str(template_str)\n        .template_name(template_name);\n\n    // Git options: CLI overrides config\n    let diff_branches = parse_branch_argument(&args.git_diff_branch).or_else(|| {\n        cfg.and_then(|c| {\n            c.diff_branches.as_ref().and_then(|branches| {\n                if branches.len() == 2 {\n                    Some((branches[0].clone(), branches[1].clone()))\n                } else {\n                    None\n                }\n            })\n        })\n    });\n\n    let log_branches = parse_branch_argument(&args.git_log_branch).or_else(|| {\n        cfg.and_then(|c| {\n            c.log_branches.as_ref().and_then(|branches| {\n                if branches.len() == 2 {\n                    Some((branches[0].clone(), branches[1].clone()))\n                } else {\n                    None\n                }\n            })\n        })\n    });\n\n    let cfg_diff_enabled = cfg.map(|c| c.diff_enabled).unwrap_or(false);\n    let cfg_token_map_enabled = cfg.map(|c| c.token_map_enabled).unwrap_or(false);\n    let cfg_deselected = cfg.map(|c| c.deselected).unwrap_or(false);\n\n    configuration\n        .diff_enabled(args.diff || cfg_diff_enabled)\n        .diff_branches(diff_branches)\n        .log_branches(log_branches)\n        .no_ignore(args.no_ignore)\n        .hidden(args.hidden)\n        .no_codeblock(args.no_codeblock)\n        .follow_symlinks(args.follow_symlinks)\n        .token_map_enabled(args.token_map || cfg_token_map_enabled || tui_mode)\n        .deselected(args.deselected || cfg_deselected);\n\n    // User variables from config (if available)\n    if let Some(c) = cfg {\n        configuration.user_variables(c.user_variables.clone());\n    }\n\n    let session = Code2PromptSession::new(configuration.build()?);\n    Ok(session)\n}\n\n/// Parses the branch argument from command line options.\n///\n/// Takes an optional vector of strings and converts it to a tuple of two branch names\n/// if exactly two branches are provided.\n///\n/// # Arguments\n///\n/// * `branch_arg` - An optional vector containing branch names\n///\n/// # Returns\n///\n/// * `Option<(String, String)>` - A tuple of (from_branch, to_branch) if two branches were provided, None otherwise\npub fn parse_branch_argument(branch_arg: &Option<Vec<String>>) -> Option<(String, String)> {\n    match branch_arg {\n        Some(branches) if branches.len() == 2 => Some((branches[0].clone(), branches[1].clone())),\n        _ => None,\n    }\n}\n\n/// Loads a template from a file path or returns default values.\n///\n/// # Arguments\n///\n/// * `template_arg` - An optional path to a template file\n///\n/// # Returns\n///\n/// * `Result<(String, String)>` - A tuple containing (template_content, template_name)\n///   where template_name is \"custom\" for user-provided templates or \"default\" otherwise\npub fn parse_template(template_arg: &Option<PathBuf>) -> Result<(String, String)> {\n    match template_arg {\n        Some(path) => {\n            let template_str =\n                std::fs::read_to_string(path).context(\"Failed to load custom template file\")?;\n            Ok((template_str, \"custom\".to_string()))\n        }\n        None => Ok((\"\".to_string(), \"default\".to_string())),\n    }\n}\n\n/// Handles user-defined variables in the template and adds them to the session.\n///\n/// This function extracts undefined variables from the template and prompts\n/// the user to provide values for them through interactive input.\n///\n/// # Arguments\n///\n/// * `session` - The Code2PromptSession to modify\n/// * `template_content` - The template content string to analyze\n///\n/// # Returns\n///\n/// * `Result<()>` - An empty result indicating success or an error\npub fn handle_undefined_variables(\n    session: &mut Code2PromptSession,\n    template_content: &str,\n) -> Result<()> {\n    let undefined_variables = extract_undefined_variables(template_content);\n\n    for var in undefined_variables.iter() {\n        // Check if variable is already defined in user_variables\n        if !session.config.user_variables.contains_key(var) {\n            let prompt = format!(\"Enter value for '{}': \", var);\n            let answer = Text::new(&prompt)\n                .with_help_message(\"Fill user defined variable in template\")\n                .prompt()\n                .unwrap_or_default();\n            session.config.user_variables.insert(var.clone(), answer);\n        }\n    }\n\n    Ok(())\n}\n\n/// Expands comma-separated patterns while preserving brace expansion patterns\n///\n/// This function handles the expansion of comma-separated include/exclude patterns\n/// while being careful not to split patterns that contain brace expansion syntax.\n///\n/// # Arguments\n///\n/// * `patterns` - A vector of pattern strings that may contain comma-separated values\n///\n/// # Returns\n///\n/// * `Vec<String>` - A vector of individual patterns\nfn expand_comma_separated_patterns(patterns: &[String]) -> Vec<String> {\n    let mut expanded = Vec::new();\n\n    for pattern in patterns {\n        // If the pattern contains braces, don't split on commas (preserve brace expansion)\n        if pattern.contains('{') && pattern.contains('}') {\n            expanded.push(pattern.clone());\n        } else {\n            // Split on commas for regular patterns\n            for part in pattern.split(',') {\n                let trimmed = part.trim();\n                if !trimmed.is_empty() {\n                    expanded.push(trimmed.to_string());\n                }\n            }\n        }\n    }\n\n    expanded\n}\n"
  },
  {
    "path": "crates/code2prompt/src/config_loader.rs",
    "content": "//! Configuration file loading and management.\n//!\n//! This module handles loading TOML configuration files from multiple locations\n//! with proper priority handling and informational messages.\n\nuse anyhow::{Context, Result};\nuse code2prompt_core::configuration::{OutputDestination, TomlConfig};\nuse colored::*;\nuse log::{debug, info};\nuse std::path::Path;\n\n/// Configuration source information\n#[derive(Debug, Clone)]\npub struct ConfigSource {\n    pub config: TomlConfig,\n}\n\n/// Load configuration with proper priority handling\npub fn load_config(quiet: bool) -> Result<ConfigSource> {\n    // Check for local config first (.c2pconfig in current directory)\n    let local_config_path = std::env::current_dir()?.join(\".c2pconfig\");\n    if local_config_path.exists() {\n        match load_config_from_file(&local_config_path) {\n            Ok(config) => {\n                if !quiet {\n                    eprintln!(\n                        \"{}{}{} Using config from: {}\",\n                        \"[\".bold().white(),\n                        \"i\".bold().blue(),\n                        \"]\".bold().white(),\n                        local_config_path.display()\n                    );\n                }\n                info!(\"Loaded local config from: {}\", local_config_path.display());\n                return Ok(ConfigSource { config });\n            }\n            Err(e) => {\n                debug!(\"Failed to load local config: {}\", e);\n            }\n        }\n    }\n\n    // Check for global config (~/.config/code2prompt/.c2pconfig)\n    if let Some(config_dir) = dirs::config_dir() {\n        let global_config_path = config_dir.join(\"code2prompt\").join(\".c2pconfig\");\n        if global_config_path.exists() {\n            match load_config_from_file(&global_config_path) {\n                Ok(config) => {\n                    if !quiet {\n                        eprintln!(\n                            \"{}{}{} Using config from: {}\",\n                            \"[\".bold().white(),\n                            \"i\".bold().blue(),\n                            \"]\".bold().white(),\n                            global_config_path.display()\n                        );\n                    }\n                    info!(\n                        \"Loaded global config from: {}\",\n                        global_config_path.display()\n                    );\n                    return Ok(ConfigSource { config });\n                }\n                Err(e) => {\n                    debug!(\"Failed to load global config: {}\", e);\n                }\n            }\n        }\n    }\n\n    // Use default configuration\n    if !quiet {\n        eprintln!(\n            \"{}{}{} Using default configuration\",\n            \"[\".bold().white(),\n            \"i\".bold().blue(),\n            \"]\".bold().white(),\n        );\n    }\n    info!(\"Using default configuration\");\n\n    Ok(ConfigSource {\n        config: TomlConfig::default(),\n    })\n}\n\n/// Load TOML configuration from a file\nfn load_config_from_file(path: &Path) -> Result<TomlConfig> {\n    let content = std::fs::read_to_string(path)\n        .with_context(|| format!(\"Failed to read config file: {}\", path.display()))?;\n\n    TomlConfig::from_toml_str(&content)\n        .with_context(|| format!(\"Failed to parse TOML config file: {}\", path.display()))\n}\n\n/// Get the default output destination from config\npub fn get_default_output_destination(config_source: &ConfigSource) -> OutputDestination {\n    config_source.config.default_output.clone()\n}\n"
  },
  {
    "path": "crates/code2prompt/src/main.rs",
    "content": "//! code2prompt is a command-line tool to generate an LLM prompt from a codebase directory.\n//!\n//! Authors: Olivier D'Ancona (@ODAncona), Mufeed VH (@mufeedvh)\nmod args;\nmod clipboard;\nmod config;\nmod config_loader;\nmod model;\nmod token_map;\nmod tui;\nmod utils;\nmod view;\nmod widgets;\n\nuse crate::utils::format_number;\nuse anyhow::{Context, Result};\nuse args::Cli;\nuse clap::Parser;\nuse code2prompt_core::template::write_to_file;\nuse colored::*;\nuse indicatif::{ProgressBar, ProgressStyle};\nuse log::{debug, error, info};\nuse std::io::Write;\nuse tui::run_tui;\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    env_logger::init();\n    info! {\"Args: {:?}\", std::env::args().collect::<Vec<_>>()};\n\n    let args: Cli = Cli::parse();\n\n    // ~~~ Clipboard Daemon ~~~\n    #[cfg(target_os = \"linux\")]\n    {\n        use clipboard::serve_clipboard_daemon;\n        if args.clipboard_daemon {\n            info! {\"Serving clipboard daemon...\"};\n            serve_clipboard_daemon()?;\n            info! {\"Shutting down gracefully...\"};\n            return Ok(());\n        }\n    }\n\n    // ~~~ TUI or CLI Mode ~~~\n    if args.tui {\n        // ~~~ Build Session for TUI ~~~\n        let session = config::build_session(None, &args, args.tui).unwrap_or_else(|e| {\n            error!(\"Failed to create session: {}\", e);\n            std::process::exit(1);\n        });\n        run_tui(session).await\n    } else {\n        run_cli_mode_with_args(args).await\n    }\n}\n\n/// Run the CLI mode with parsed arguments\nasync fn run_cli_mode_with_args(args: Cli) -> Result<()> {\n    use code2prompt_core::configuration::OutputDestination;\n    use config_loader::{get_default_output_destination, load_config};\n\n    let quiet_mode = args.quiet;\n\n    // ~~~ Load Configuration ~~~\n    let config_source = load_config(quiet_mode)?; // load config files first (local > global), then apply CLI args on top\n\n    // ~~~ Build Session with config + CLI args ~~~\n    let mut session = config::build_session(Some(&config_source), &args, false)?;\n\n    // ~~~ Determine Output Behavior ~~~\n    let default_output = get_default_output_destination(&config_source);\n\n    // Determine final output destinations (Solution B: Unix-style behavior)\n    let output_to_clipboard = if args.clipboard {\n        // Explicit clipboard flag - ONLY clipboard, no stdout\n        true\n    } else if args.output_file.is_some() {\n        // Output file specified, don't use clipboard unless explicitly requested\n        false\n    } else {\n        // Use config default\n        matches!(default_output, OutputDestination::Clipboard)\n    };\n\n    let output_to_stdout = if args.clipboard {\n        false\n    } else if let Some(ref output_file) = args.output_file {\n        output_file == \"-\"\n    } else {\n        match default_output {\n            OutputDestination::Stdout => true,\n            OutputDestination::Clipboard => false,\n            OutputDestination::File => false,\n        }\n    };\n\n    // ~~~ Create Session ~~~\n    let spinner = if !quiet_mode {\n        Some(setup_spinner(\"Traversing directory and building tree...\"))\n    } else {\n        None\n    };\n\n    // ~~~ Gather Repository Data ~~~\n    session.load_codebase().map_err(|e| {\n        if let Some(s) = spinner.as_ref() {\n            s.finish_with_message(\"Failed!\".red().to_string())\n        }\n        error!(\"Failed to build directory tree: \\n{}\", e);\n        anyhow::anyhow!(\"Failed to build directory tree: {}\", e)\n    })?;\n    if let Some(s) = spinner.as_ref() {\n        s.set_message(\"Proceeding…\")\n    }\n\n    // ~~~ Git Related ~~~\n    // Git Diff\n    if session.config.diff_enabled {\n        if let Some(s) = spinner.as_ref() {\n            s.set_message(\"Generating git diff...\")\n        }\n        session.load_git_diff().unwrap_or_else(|e| {\n            if let Some(s) = spinner.as_ref() {\n                s.finish_with_message(\"Failed!\".red().to_string())\n            }\n            error!(\"Failed to generate git diff: {}\", e);\n            std::process::exit(1);\n        });\n    }\n\n    // Load Git diff between branches if provided\n    if session.config.diff_branches.is_some() {\n        if let Some(s) = spinner.as_ref() {\n            s.set_message(\"Generating git diff between two branches...\")\n        }\n        session\n            .load_git_diff_between_branches()\n            .unwrap_or_else(|e| {\n                if let Some(s) = spinner.as_ref() {\n                    s.finish_with_message(\"Failed!\".red().to_string())\n                }\n                error!(\"Failed to generate git diff: {}\", e);\n                std::process::exit(1);\n            });\n    }\n\n    // Load Git log between branches if provided\n    if session.config.log_branches.is_some() {\n        if let Some(ref s) = spinner {\n            s.set_message(\"Generating git log between two branches...\");\n        }\n        session.load_git_log_between_branches().unwrap_or_else(|e| {\n            if let Some(ref s) = spinner {\n                s.finish_with_message(\"Failed!\".red().to_string());\n            }\n            error!(\"Failed to generate git log: {}\", e);\n            std::process::exit(1);\n        });\n    }\n\n    // ~~~ Template ~~~\n\n    // Handle undefined variables (modifies session.config.user_variables)\n    let template_str_clone = session.config.template_str.clone();\n    config::handle_undefined_variables(&mut session, &template_str_clone)?;\n\n    // Data - now build after handling undefined variables\n    let data = session.build_template_data();\n    debug!(\n        \"Template Context: absolute_code_path={}, files_count={}, has_user_vars={}\",\n        data.absolute_code_path,\n        data.files.map(|f| f.len()).unwrap_or(0),\n        !session.config.user_variables.is_empty()\n    );\n\n    // Render\n    let rendered = session.render_prompt(&data).unwrap_or_else(|e| {\n        error!(\"Failed to render prompt: {}\", e);\n        std::process::exit(1);\n    });\n\n    if let Some(ref s) = spinner {\n        s.finish_with_message(\"Codebase Traversal Done!\".green().to_string());\n    }\n\n    // ~~~ Token Count ~~~\n    let token_count = rendered.token_count;\n    let formatted_token_count = format_number(token_count, &session.config.token_format);\n    let model_info = rendered.model_info;\n\n    if !quiet_mode {\n        eprintln!(\n            \"{}{}{} Token count: {}, Model info: {}\",\n            \"[\".bold().white(),\n            \"i\".bold().blue(),\n            \"]\".bold().white(),\n            formatted_token_count,\n            model_info\n        );\n    }\n\n    // ~~~ Token Map Display ~~~\n    if args.token_map {\n        use crate::token_map::{display_token_map, generate_token_map_with_limit};\n\n        if let Some(files) = session.data.files.as_ref() {\n            // Calculate total tokens from individual file counts\n            let total_from_files: usize = files.iter().map(|f| f.token_count).sum();\n\n            // Get max lines from command line or calculate from terminal height\n            let max_lines = args.token_map_lines.unwrap_or_else(|| {\n                terminal_size::terminal_size()\n                    .map(|(_, terminal_size::Height(h))| {\n                        let height = h as usize;\n                        // Ensure minimum of 10 lines, subtract 10 for other output\n                        if height > 20 { height - 10 } else { 10 }\n                    })\n                    .unwrap_or(20) // Default to 20 lines if terminal size detection fails\n            });\n\n            // Use the sum of individual file tokens for the map with line limit\n            let entries = generate_token_map_with_limit(\n                files,\n                total_from_files,\n                Some(max_lines),\n                args.token_map_min_percent,\n            );\n            display_token_map(&entries, total_from_files);\n        }\n    }\n\n    // ~~~ Output to Stdout ~~~\n    if output_to_stdout {\n        print!(\"{}\", &rendered.prompt);\n        std::io::stdout()\n            .flush()\n            .context(\"Failed to flush stdout\")?;\n    }\n\n    // ~~~ Copy to Clipboard ~~~\n    if output_to_clipboard {\n        use crate::clipboard::copy_to_clipboard;\n        match copy_to_clipboard(&rendered.prompt) {\n            Ok(_) => {\n                if !quiet_mode {\n                    eprintln!(\n                        \"{}{}{} {}\",\n                        \"[\".bold().white(),\n                        \"✓\".bold().green(),\n                        \"]\".bold().white(),\n                        \"Copied to clipboard successfully.\".green()\n                    );\n                }\n            }\n            Err(e) => {\n                if !quiet_mode {\n                    eprintln!(\n                        \"{}{}{} {}\",\n                        \"[\".bold().white(),\n                        \"!\".bold().red(),\n                        \"]\".bold().white(),\n                        format!(\"Failed to copy to clipboard: {}\", e).red()\n                    );\n                }\n            }\n        }\n    }\n\n    // ~~~ Output File ~~~\n    if let Some(ref output_file) = args.output_file\n        && output_file != \"-\"\n    {\n        output_prompt(\n            Some(std::path::Path::new(output_file)),\n            &rendered.prompt,\n            quiet_mode,\n        )?;\n    }\n\n    Ok(())\n}\n\n/// Sets up a progress spinner with a given message\n///\n/// # Arguments\n///\n/// * `message` - A message to display with the spinner\n///\n/// # Returns\n///\n/// * `ProgressBar` - The configured progress spinner\nfn setup_spinner(message: &str) -> ProgressBar {\n    let spinner = ProgressBar::new_spinner();\n    spinner.enable_steady_tick(std::time::Duration::from_millis(220));\n    let done_symbol = format!(\n        \"{}{}{}\",\n        \"[\".bold().white(),\n        \"✓\".bold().green(),\n        \"]\".bold().white()\n    );\n    spinner.set_style(\n        ProgressStyle::default_spinner()\n            .tick_strings(&[\n                \"▹▹▹▹▹\",\n                \"▸▹▹▹▹\",\n                \"▹▸▹▹▹\",\n                \"▹▹▸▹▹\",\n                \"▹▹▹▸▹\",\n                \"▹▹▹▹▸\",\n                &done_symbol,\n            ])\n            .template(\"{spinner:.blue} {msg}\")\n            .unwrap(),\n    );\n    spinner.set_message(message.to_string());\n    spinner\n}\n\n// ~~~ Output to file or stdout ~~~\nfn output_prompt(\n    effective_output: Option<&std::path::Path>,\n    rendered: &str,\n    quiet: bool,\n) -> Result<()> {\n    let output_path = match effective_output {\n        Some(path) => path,\n        None => return Ok(()), // nothing to do\n    };\n\n    let path_str = output_path.to_string_lossy();\n    if path_str == \"-\" {\n        // stdout\n        print!(\"{}\", rendered);\n        std::io::stdout()\n            .flush()\n            .context(\"Failed to flush stdout\")?;\n    } else {\n        // file\n        write_to_file(&path_str, rendered)\n            .context(format!(\"Failed to write to file: {}\", path_str))?;\n\n        if !quiet {\n            eprintln!(\n                \"{}{}{} {}\",\n                \"[\".bold().white(),\n                \"✓\".bold().green(),\n                \"]\".bold().white(),\n                format!(\"Prompt written to file: {}\", path_str).green()\n            );\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/code2prompt/src/model/commands.rs",
    "content": "//! Command system for handling side effects in the Model-View-Update architecture.\n//!\n//! This module implements the Cmd pattern from Elm/Redux, allowing the Model::update()\n//! function to remain pure while still triggering side effects like async operations,\n//! file I/O, and clipboard operations.\n\nuse std::collections::HashMap;\n\n/// Commands represent side effects that should be executed after model updates.\n/// This allows Model::update() to remain pure while still triggering necessary\n/// side effects like async operations, file I/O, etc.\n#[derive(Debug, Clone)]\npub enum Cmd {\n    /// No command - pure state update only\n    None,\n\n    /// Run analysis in background\n    RunAnalysis {\n        template_content: String,\n        user_variables: HashMap<String, String>,\n    },\n\n    /// Copy text to clipboard\n    CopyToClipboard(String),\n\n    /// Save text to file\n    SaveToFile { filename: String, content: String },\n\n    /// Save template to custom directory\n    SaveTemplate { filename: String, content: String },\n\n    /// Refresh file tree from session\n    RefreshFileTree,\n}\n"
  },
  {
    "path": "crates/code2prompt/src/model/mod.rs",
    "content": "//! Data structures and application state management for the TUI.\n//!\n//! This module contains the core data structures that represent the application state,\n//! including the main Model struct, tab definitions, message types for event handling,\n//! and all state management submodules. It serves as the central state container\n//! for the terminal user interface.\n\npub mod commands;\npub mod prompt_output;\npub mod settings;\npub mod statistics;\npub mod template;\n\npub use commands::*;\npub use prompt_output::*;\npub use settings::*;\npub use statistics::*;\npub use template::*;\n\nuse crate::utils::directory_contains_selected_files;\nuse code2prompt_core::session::Code2PromptSession;\n\n/// The five main tabs of the TUI\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum Tab {\n    FileTree,\n    Settings,\n    Statistics,\n    Template,\n    PromptOutput,\n}\n\n/// Input mode for the FileTree tab\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum FileTreeInputMode {\n    Normal,\n    Search,\n}\n\n/// Hierarchical file node for TUI display with proper parent-child relationships\n#[derive(Debug, Clone)]\npub struct DisplayFileNode {\n    pub path: std::path::PathBuf,\n    pub name: String,\n    pub is_directory: bool,\n    pub is_expanded: bool,\n    pub level: usize,\n    pub children_loaded: bool,\n    pub children: Vec<DisplayFileNode>,\n}\n\nimpl DisplayFileNode {\n    pub fn new(path: std::path::PathBuf, level: usize) -> Self {\n        let name = path\n            .file_name()\n            .map(|n| n.to_string_lossy().to_string())\n            .unwrap_or_else(|| path.to_string_lossy().to_string());\n\n        let is_directory = path.is_dir();\n\n        Self {\n            path,\n            name,\n            is_directory,\n            is_expanded: false,\n            level,\n            children_loaded: false,\n            children: Vec::new(),\n        }\n    }\n\n    /// Find a node by path in the tree (recursive)\n    pub fn find_node_mut(&mut self, target_path: &std::path::Path) -> Option<&mut DisplayFileNode> {\n        if self.path == target_path {\n            return Some(self);\n        }\n\n        for child in &mut self.children {\n            if let Some(found) = child.find_node_mut(target_path) {\n                return Some(found);\n            }\n        }\n\n        None\n    }\n\n    /// Load children for this directory node\n    pub fn load_children(\n        &mut self,\n        session: &mut code2prompt_core::session::Code2PromptSession,\n    ) -> Result<(), Box<dyn std::error::Error>> {\n        if !self.is_directory || self.children_loaded {\n            return Ok(());\n        }\n\n        self.children.clear();\n\n        // Use ignore crate to respect gitignore\n        use ignore::WalkBuilder;\n        let walker = WalkBuilder::new(&self.path).max_depth(Some(1)).build();\n\n        for entry in walker {\n            let entry = entry?;\n            let path = entry.path();\n\n            if path == self.path {\n                continue; // Skip self\n            }\n\n            let mut child = DisplayFileNode::new(path.to_path_buf(), self.level + 1);\n\n            // Auto-expand if contains selected files\n            if child.is_directory && directory_contains_selected_files(&child.path, session) {\n                child.is_expanded = true;\n            }\n\n            self.children.push(child);\n        }\n\n        // Sort children: directories first, then alphabetically\n        self.children\n            .sort_by(|a, b| match (a.is_directory, b.is_directory) {\n                (true, false) => std::cmp::Ordering::Less,\n                (false, true) => std::cmp::Ordering::Greater,\n                _ => a.name.cmp(&b.name),\n            });\n\n        self.children_loaded = true;\n        Ok(())\n    }\n}\n\n/// Messages for updating the model\n#[derive(Debug, Clone)]\npub enum Message {\n    SwitchTab(Tab),\n    Quit,\n\n    UpdateSearchQuery(String),\n    ToggleFileSelection(usize),\n    ExpandDirectory(usize),\n    CollapseDirectory(usize),\n    MoveTreeCursor(i32),\n    RefreshFileTree,\n\n    EnterSearchMode,\n    ExitSearchMode,\n\n    MoveSettingsCursor(i32),\n    ToggleSetting(usize),\n    CycleSetting(usize),\n\n    RunAnalysis,\n    AnalysisComplete(AnalysisResults),\n    AnalysisError(String),\n\n    CopyToClipboard,\n    SaveToFile(String),\n    ScrollOutput(i16),\n\n    CycleStatisticsView(i8),\n    ScrollStatistics(i16),\n\n    SaveTemplate(String),\n    ReloadTemplate,\n    LoadTemplate,\n    RefreshTemplates,\n\n    SetTemplateFocus(TemplateFocus, FocusMode),\n    SetTemplateFocusMode(FocusMode),\n    TemplateEditorInput(ratatui::crossterm::event::KeyEvent),\n    TemplatePickerMove(i32),\n\n    VariableStartEditing(String),\n    VariableInputChar(char),\n    VariableInputBackspace,\n    VariableInputEnter,\n    VariableInputCancel,\n    VariableNavigateUp,\n    VariableNavigateDown,\n}\n\n/// Represents the overall state of the TUI application.\n#[derive(Debug, Clone)]\npub struct Model {\n    pub session: Code2PromptSession,\n    pub current_tab: Tab,\n    pub should_quit: bool,\n    pub file_tree_input_mode: FileTreeInputMode,\n    pub file_tree_nodes: Vec<DisplayFileNode>,\n    pub search_query: String,\n    pub tree_cursor: usize,\n    pub file_tree_scroll: u16,\n    pub settings: SettingsState,\n    pub statistics: StatisticsState,\n    pub template: TemplateState,\n    pub prompt_output: PromptOutputState,\n    pub status_message: String,\n}\n\nimpl Default for Model {\n    fn default() -> Self {\n        let config = code2prompt_core::configuration::Code2PromptConfig::default();\n        let session = Code2PromptSession::new(config);\n\n        Model {\n            session,\n            current_tab: Tab::FileTree,\n            should_quit: false,\n            file_tree_input_mode: FileTreeInputMode::Normal,\n            file_tree_nodes: Vec::new(),\n            search_query: String::new(),\n            tree_cursor: 0,\n            file_tree_scroll: 0,\n            settings: SettingsState::default(),\n            statistics: StatisticsState::default(),\n            template: TemplateState::default(),\n            prompt_output: PromptOutputState::default(),\n            status_message: String::new(),\n        }\n    }\n}\n\nimpl Model {\n    pub fn new(session: Code2PromptSession) -> Self {\n        Model {\n            session,\n            current_tab: Tab::FileTree,\n            should_quit: false,\n            file_tree_input_mode: FileTreeInputMode::Normal,\n            file_tree_nodes: Vec::new(),\n            search_query: String::new(),\n            tree_cursor: 0,\n            file_tree_scroll: 0,\n            settings: SettingsState::default(),\n            statistics: StatisticsState::default(),\n            template: TemplateState::default(),\n            prompt_output: PromptOutputState::default(),\n            status_message: String::new(),\n        }\n    }\n\n    /// Get grouped settings for display\n    pub fn get_settings_groups(&self) -> Vec<SettingsGroup> {\n        crate::view::format_settings_groups(&self.session)\n    }\n\n    pub fn update(&self, message: Message) -> (Self, Cmd) {\n        let mut new_model = self.clone();\n\n        match message {\n            Message::Quit => {\n                new_model.should_quit = true;\n                new_model.status_message = \"Goodbye!\".to_string();\n                (new_model, Cmd::None)\n            }\n\n            Message::SwitchTab(tab) => {\n                new_model.current_tab = tab;\n                new_model.status_message = format!(\"Switched to {:?} tab\", tab);\n                (new_model, Cmd::None)\n            }\n\n            Message::RefreshFileTree => {\n                new_model.status_message = \"Refreshing file tree...\".to_string();\n                (new_model, Cmd::RefreshFileTree)\n            }\n\n            Message::UpdateSearchQuery(query) => {\n                new_model.search_query = query;\n                new_model.tree_cursor = 0; // Reset cursor when search changes\n                new_model.file_tree_scroll = 0; // Reset scroll when search changes\n                (new_model, Cmd::None)\n            }\n\n            Message::EnterSearchMode => {\n                new_model.file_tree_input_mode = FileTreeInputMode::Search;\n                new_model.status_message = \"Search mode - Type to search, Esc to exit\".to_string();\n                (new_model, Cmd::None)\n            }\n\n            Message::ExitSearchMode => {\n                new_model.file_tree_input_mode = FileTreeInputMode::Normal;\n                new_model.status_message = \"Exited search mode\".to_string();\n                (new_model, Cmd::None)\n            }\n\n            Message::MoveTreeCursor(delta) => {\n                let visible_nodes = crate::utils::get_visible_nodes(\n                    &new_model.file_tree_nodes,\n                    &new_model.search_query,\n                    &mut new_model.session,\n                );\n                let visible_count = visible_nodes.len();\n\n                if visible_count > 0 {\n                    let new_cursor = if delta > 0 {\n                        (new_model.tree_cursor + delta as usize).min(visible_count - 1)\n                    } else {\n                        new_model.tree_cursor.saturating_sub((-delta) as usize)\n                    };\n                    new_model.tree_cursor = new_cursor;\n                }\n                (new_model, Cmd::None)\n            }\n\n            Message::MoveSettingsCursor(delta) => {\n                let settings_count = new_model\n                    .settings\n                    .get_settings_items(&new_model.session)\n                    .len();\n                if settings_count > 0 {\n                    let new_cursor = if delta > 0 {\n                        (new_model.settings.settings_cursor + delta as usize)\n                            .min(settings_count - 1)\n                    } else {\n                        new_model\n                            .settings\n                            .settings_cursor\n                            .saturating_sub((-delta) as usize)\n                    };\n                    new_model.settings.settings_cursor = new_cursor;\n                }\n                (new_model, Cmd::None)\n            }\n\n            Message::ToggleFileSelection(index) => {\n                let visible_nodes = crate::utils::get_visible_nodes(\n                    &new_model.file_tree_nodes,\n                    &new_model.search_query,\n                    &mut new_model.session,\n                );\n\n                if let Some(display_node) = visible_nodes.get(index) {\n                    let node_path = display_node.node.path.clone();\n                    let name = display_node.node.name.clone();\n                    let is_directory = display_node.node.is_directory;\n                    let current = display_node.is_selected;\n\n                    // Convert to relative path for session\n                    let relative_path =\n                        if let Ok(rel) = node_path.strip_prefix(&new_model.session.config.path) {\n                            rel.to_path_buf()\n                        } else {\n                            node_path.clone()\n                        };\n\n                    // Update session selection state (single source of truth)\n                    new_model.session.toggle_file_selection(relative_path);\n\n                    let action = if current { \"Deselected\" } else { \"Selected\" };\n                    let extra = if is_directory { \" (and contents)\" } else { \"\" };\n                    new_model.status_message = format!(\"{} {}{}\", action, name, extra);\n                }\n                (new_model, Cmd::None)\n            }\n\n            Message::ExpandDirectory(index) => {\n                let visible_nodes = crate::utils::get_visible_nodes(\n                    &new_model.file_tree_nodes,\n                    &new_model.search_query,\n                    &mut new_model.session,\n                );\n\n                if let Some(display_node) = visible_nodes.get(index)\n                    && display_node.node.is_directory\n                {\n                    let node_path = display_node.node.path.clone();\n                    let name = display_node.node.name.clone();\n\n                    // Ensure the path exists in the tree first\n                    if let Err(e) = crate::utils::ensure_path_exists_in_tree(\n                        &mut new_model.file_tree_nodes,\n                        &node_path,\n                        &mut new_model.session,\n                    ) {\n                        new_model.status_message =\n                            format!(\"Failed to ensure path exists for {}: {}\", name, e);\n                        return (new_model, Cmd::None);\n                    }\n\n                    // Find and expand the node in the tree\n                    let mut found = false;\n                    for root_node in &mut new_model.file_tree_nodes {\n                        if let Some(node) = root_node.find_node_mut(&node_path) {\n                            if !node.is_expanded {\n                                node.is_expanded = true;\n                                // Load children if not already loaded\n                                if !node.children_loaded\n                                    && let Err(e) = node.load_children(&mut new_model.session)\n                                {\n                                    new_model.status_message =\n                                        format!(\"Failed to load children for {}: {}\", name, e);\n                                    return (new_model, Cmd::None);\n                                }\n                                new_model.status_message = format!(\"Expanded {}\", name);\n                            } else {\n                                new_model.status_message = format!(\"{} is already expanded\", name);\n                            }\n                            found = true;\n                            break;\n                        }\n                    }\n\n                    if !found {\n                        new_model.status_message = format!(\"Could not find directory {}\", name);\n                    }\n                }\n                (new_model, Cmd::None)\n            }\n\n            Message::CollapseDirectory(index) => {\n                let visible_nodes = crate::utils::get_visible_nodes(\n                    &new_model.file_tree_nodes,\n                    &new_model.search_query,\n                    &mut new_model.session,\n                );\n\n                if let Some(display_node) = visible_nodes.get(index)\n                    && display_node.node.is_directory\n                {\n                    let node_path = display_node.node.path.clone();\n                    let name = display_node.node.name.clone();\n\n                    // Find and collapse the node in the tree\n                    let mut found = false;\n                    for root_node in &mut new_model.file_tree_nodes {\n                        if let Some(node) = root_node.find_node_mut(&node_path)\n                            && node.is_expanded\n                        {\n                            node.is_expanded = false;\n                            new_model.status_message = format!(\"Collapsed {}\", name);\n                            found = true;\n                            break;\n                        }\n                    }\n\n                    if !found {\n                        new_model.status_message = format!(\"Could not find directory {}\", name);\n                    }\n                }\n                (new_model, Cmd::None)\n            }\n\n            Message::ToggleSetting(index) => {\n                let items = new_model.settings.get_settings_items(&new_model.session);\n                if let Some(item) = items.get(index) {\n                    let setting_name = new_model.settings.update_setting_by_key(\n                        &mut new_model.session,\n                        item.key,\n                        SettingAction::Toggle,\n                    );\n                    new_model.status_message = format!(\"Toggled {}\", setting_name);\n                } else {\n                    new_model.status_message = format!(\"Invalid setting index: {}\", index);\n                }\n                (new_model, Cmd::None)\n            }\n\n            Message::CycleSetting(index) => {\n                let items = new_model.settings.get_settings_items(&new_model.session);\n                if let Some(item) = items.get(index) {\n                    let setting_name = new_model.settings.update_setting_by_key(\n                        &mut new_model.session,\n                        item.key,\n                        SettingAction::Cycle,\n                    );\n                    new_model.status_message = format!(\"Cycled {}\", setting_name);\n                } else {\n                    new_model.status_message = format!(\"Invalid setting index: {}\", index);\n                }\n                (new_model, Cmd::None)\n            }\n\n            Message::RunAnalysis => {\n                if !new_model.prompt_output.analysis_in_progress {\n                    new_model.prompt_output.analysis_in_progress = true;\n                    new_model.prompt_output.analysis_error = None;\n                    new_model.status_message = \"Running analysis...\".to_string();\n                    new_model.current_tab = Tab::PromptOutput; // Switch to output tab\n\n                    let cmd = Cmd::RunAnalysis {\n                        template_content: new_model.template.get_template_content().to_string(),\n                        user_variables: new_model.template.variables.user_variables.clone(),\n                    };\n                    (new_model, cmd)\n                } else {\n                    new_model.status_message = \"Analysis already in progress...\".to_string();\n                    (new_model, Cmd::None)\n                }\n            }\n\n            Message::AnalysisComplete(results) => {\n                new_model.prompt_output.analysis_in_progress = false;\n                new_model.prompt_output.generated_prompt = Some(results.generated_prompt);\n                new_model.prompt_output.token_count = results.token_count;\n                new_model.prompt_output.file_count = results.file_count;\n                // Reset output scroll so the new content starts at the top.\n                new_model.prompt_output.output_scroll = 0;\n                new_model.statistics.token_map_entries = results.token_map_entries;\n                let tokens = results.token_count.unwrap_or(0);\n                new_model.status_message = format!(\n                    \"Analysis complete! {} tokens, {} files\",\n                    tokens, results.file_count\n                );\n                (new_model, Cmd::None)\n            }\n\n            Message::AnalysisError(error) => {\n                new_model.prompt_output.analysis_in_progress = false;\n                new_model.prompt_output.analysis_error = Some(error.clone());\n                new_model.status_message = format!(\"Analysis failed: {}\", error);\n                (new_model, Cmd::None)\n            }\n\n            Message::CopyToClipboard => {\n                if let Some(prompt) = &new_model.prompt_output.generated_prompt {\n                    let cmd = Cmd::CopyToClipboard(prompt.clone());\n                    (new_model, cmd)\n                } else {\n                    new_model.status_message = \"No prompt to copy\".to_string();\n                    (new_model, Cmd::None)\n                }\n            }\n\n            Message::SaveToFile(filename) => {\n                if let Some(prompt) = &new_model.prompt_output.generated_prompt {\n                    let cmd = Cmd::SaveToFile {\n                        filename,\n                        content: prompt.clone(),\n                    };\n                    (new_model, cmd)\n                } else {\n                    new_model.status_message = \"No prompt to save\".to_string();\n                    (new_model, Cmd::None)\n                }\n            }\n\n            Message::ScrollOutput(delta) => {\n                // Apply delta only; widgets will clamp based on actual viewport.\n                let new_scroll = if delta < 0 {\n                    new_model\n                        .prompt_output\n                        .output_scroll\n                        .saturating_sub((-delta) as u16)\n                } else {\n                    new_model\n                        .prompt_output\n                        .output_scroll\n                        .saturating_add(delta as u16)\n                };\n                new_model.prompt_output.output_scroll = new_scroll;\n                (new_model, Cmd::None)\n            }\n\n            Message::CycleStatisticsView(direction) => {\n                new_model.statistics.view = if direction > 0 {\n                    new_model.statistics.view.next()\n                } else {\n                    new_model.statistics.view.prev()\n                };\n                new_model.statistics.scroll = 0;\n                new_model.status_message =\n                    format!(\"Switched to {} view\", new_model.statistics.view.as_str());\n                (new_model, Cmd::None)\n            }\n\n            Message::ScrollStatistics(delta) => {\n                let new_scroll = if delta < 0 {\n                    new_model.statistics.scroll.saturating_sub((-delta) as u16)\n                } else {\n                    new_model.statistics.scroll.saturating_add(delta as u16)\n                };\n                new_model.statistics.scroll = new_scroll;\n                (new_model, Cmd::None)\n            }\n\n            Message::SaveTemplate(filename) => {\n                let content = new_model.template.get_template_content().to_string();\n                let cmd = Cmd::SaveTemplate {\n                    filename: filename.clone(),\n                    content,\n                };\n                new_model.status_message = \"Saving template...\".to_string();\n                (new_model, cmd)\n            }\n\n            Message::ReloadTemplate => {\n                new_model.template.editor = crate::model::template::EditorState::default();\n                new_model.template.sync_variables_with_template();\n                new_model.status_message = \"Reloaded template\".to_string();\n                (new_model, Cmd::None)\n            }\n\n            Message::LoadTemplate => {\n                let result = new_model.template.load_selected_template();\n                match result {\n                    Ok(template_name) => {\n                        new_model.template.sync_variables_with_template();\n                        new_model.status_message = format!(\"Loaded template: {}\", template_name);\n                    }\n                    Err(e) => {\n                        new_model.status_message = format!(\"Failed to load template: {}\", e);\n                    }\n                }\n                (new_model, Cmd::None)\n            }\n\n            Message::RefreshTemplates => {\n                new_model.template.picker.refresh();\n                new_model.status_message = \"Templates refreshed\".to_string();\n                (new_model, Cmd::None)\n            }\n\n            Message::SetTemplateFocus(focus, mode) => {\n                new_model.template.set_focus(focus);\n                new_model.template.set_focus_mode(mode);\n                if mode == crate::model::template::FocusMode::EditingVariable {\n                    new_model\n                        .template\n                        .variables\n                        .move_to_first_missing_variable();\n                }\n                new_model.status_message = format!(\"Template focus: {:?} ({:?})\", focus, mode);\n                (new_model, Cmd::None)\n            }\n\n            Message::SetTemplateFocusMode(mode) => {\n                new_model.template.set_focus_mode(mode);\n                new_model.status_message = format!(\"Template mode: {:?}\", mode);\n                (new_model, Cmd::None)\n            }\n\n            Message::TemplateEditorInput(key) => {\n                new_model.template.editor.editor.input(key);\n                new_model.template.editor.sync_content_from_textarea();\n                new_model.template.editor.validate_template();\n                new_model.template.sync_variables_with_template();\n                (new_model, Cmd::None)\n            }\n\n            Message::TemplatePickerMove(delta) => {\n                if delta > 0 {\n                    new_model.template.picker.move_cursor_down();\n                } else {\n                    new_model.template.picker.move_cursor_up();\n                }\n                (new_model, Cmd::None)\n            }\n\n            Message::VariableStartEditing(var_name) => {\n                new_model.template.variables.editing_variable = Some(var_name.clone());\n                new_model.template.variables.show_variable_input = true;\n                new_model.template.variables.variable_input_content.clear();\n                new_model.status_message = format!(\"Editing variable: {}\", var_name);\n                (new_model, Cmd::None)\n            }\n\n            Message::VariableInputChar(c) => {\n                new_model.template.variables.add_char_to_input(c);\n                (new_model, Cmd::None)\n            }\n\n            Message::VariableInputBackspace => {\n                new_model.template.variables.remove_char_from_input();\n                (new_model, Cmd::None)\n            }\n\n            Message::VariableInputEnter => {\n                if let Some((var_name, value)) = new_model.template.variables.finish_editing() {\n                    new_model.status_message = format!(\"Set {} = {}\", var_name, value);\n                    new_model.template.sync_variables_with_template();\n                }\n                (new_model, Cmd::None)\n            }\n\n            Message::VariableInputCancel => {\n                new_model.template.variables.cancel_editing();\n                new_model.status_message = \"Cancelled variable editing\".to_string();\n                (new_model, Cmd::None)\n            }\n\n            Message::VariableNavigateUp => {\n                if new_model.template.variables.cursor > 0 {\n                    new_model.template.variables.cursor -= 1;\n                }\n                (new_model, Cmd::None)\n            }\n\n            Message::VariableNavigateDown => {\n                let variables = new_model.template.get_organized_variables();\n                if new_model.template.variables.cursor < variables.len().saturating_sub(1) {\n                    new_model.template.variables.cursor += 1;\n                }\n                (new_model, Cmd::None)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/model/prompt_output.rs",
    "content": "//! Prompt output state management for the TUI application.\n//!\n//! This module contains the prompt output state and related functionality\n//! for managing generated prompts and analysis results in the TUI.\n\n/// Prompt output state containing all prompt output related data\n#[derive(Debug, Default, Clone)]\npub struct PromptOutputState {\n    pub generated_prompt: Option<String>,\n    pub token_count: Option<usize>,\n    pub file_count: usize,\n    pub analysis_in_progress: bool,\n    pub analysis_error: Option<String>,\n    pub output_scroll: u16,\n}\n\n/// Results from code2prompt analysis\n#[derive(Debug, Clone)]\npub struct AnalysisResults {\n    pub file_count: usize,\n    pub token_count: Option<usize>,\n    pub generated_prompt: String,\n    pub token_map_entries: Vec<crate::token_map::TokenMapEntry>,\n}\n"
  },
  {
    "path": "crates/code2prompt/src/model/settings.rs",
    "content": "//! Settings state management for the TUI application.\n//!\n//! This module contains the settings state, settings groups, and related\n//! functionality for managing configuration options in the TUI.\n\nuse code2prompt_core::session::Code2PromptSession;\nuse code2prompt_core::template::OutputFormat;\nuse code2prompt_core::tokenizer::TokenFormat;\n\n/// Settings state containing cursor position and related data\n#[derive(Default, Debug, Clone)]\npub struct SettingsState {\n    pub settings_cursor: usize,\n}\n\n/// Settings group for organizing settings\n#[derive(Debug, Clone)]\npub struct SettingsGroup {\n    pub name: String,\n    pub items: Vec<SettingsItem>,\n}\n\n/// Settings item for display and interaction\n#[derive(Debug, Clone)]\npub struct SettingsItem {\n    pub key: SettingKey,\n    pub name: String,\n    pub description: String,\n    pub setting_type: SettingType,\n}\n\n#[derive(Debug, Clone)]\npub enum SettingType {\n    Boolean(bool),\n    Choice {\n        options: Vec<String>,\n        selected: usize,\n    },\n}\n\n#[derive(Debug, Clone)]\npub enum SettingAction {\n    Toggle,\n    Cycle,\n}\n\n/// Unique identifier for each setting\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum SettingKey {\n    LineNumbers,\n    AbsolutePaths,\n    NoCodeblock,\n    OutputFormat,\n    TokenFormat,\n    FullDirectoryTree,\n    SortMethod,\n    TokenizerType,\n    GitDiff,\n    FollowSymlinks,\n    HiddenFiles,\n    NoIgnore,\n    Deselected,\n}\n\nimpl SettingsState {\n    /// Get flattened list of settings for display (uses format_settings_groups)\n    pub fn get_settings_items(&self, session: &Code2PromptSession) -> Vec<SettingsItem> {\n        crate::view::format_settings_groups(session)\n            .into_iter()\n            .flat_map(|group| group.items)\n            .collect()\n    }\n\n    /// Update setting based on SettingKey and action\n    pub fn update_setting_by_key(\n        &self,\n        session: &mut Code2PromptSession,\n        key: SettingKey,\n        action: SettingAction,\n    ) -> &'static str {\n        match (key, action) {\n            (SettingKey::LineNumbers, SettingAction::Toggle | SettingAction::Cycle) => {\n                session.config.line_numbers = !session.config.line_numbers;\n                \"Line Numbers\"\n            }\n            (SettingKey::AbsolutePaths, SettingAction::Toggle | SettingAction::Cycle) => {\n                session.config.absolute_path = !session.config.absolute_path;\n                \"Absolute Paths\"\n            }\n            (SettingKey::NoCodeblock, SettingAction::Toggle | SettingAction::Cycle) => {\n                session.config.no_codeblock = !session.config.no_codeblock;\n                \"No Codeblock\"\n            }\n            (SettingKey::OutputFormat, SettingAction::Cycle) => {\n                session.config.output_format = match session.config.output_format {\n                    OutputFormat::Markdown => OutputFormat::Json,\n                    OutputFormat::Json => OutputFormat::Xml,\n                    OutputFormat::Xml => OutputFormat::Markdown,\n                };\n                \"Output Format\"\n            }\n            (SettingKey::TokenFormat, SettingAction::Cycle) => {\n                session.config.token_format = match session.config.token_format {\n                    TokenFormat::Raw => TokenFormat::Format,\n                    TokenFormat::Format => TokenFormat::Raw,\n                };\n                \"Token Format\"\n            }\n            (SettingKey::FullDirectoryTree, SettingAction::Toggle | SettingAction::Cycle) => {\n                session.config.full_directory_tree = !session.config.full_directory_tree;\n                \"Full Directory Tree\"\n            }\n            (SettingKey::SortMethod, SettingAction::Cycle) => {\n                session.config.sort_method = Some(match session.config.sort_method {\n                    Some(code2prompt_core::sort::FileSortMethod::NameAsc) => {\n                        code2prompt_core::sort::FileSortMethod::NameDesc\n                    }\n                    Some(code2prompt_core::sort::FileSortMethod::NameDesc) => {\n                        code2prompt_core::sort::FileSortMethod::DateAsc\n                    }\n                    Some(code2prompt_core::sort::FileSortMethod::DateAsc) => {\n                        code2prompt_core::sort::FileSortMethod::DateDesc\n                    }\n                    Some(code2prompt_core::sort::FileSortMethod::DateDesc) | None => {\n                        code2prompt_core::sort::FileSortMethod::NameAsc\n                    }\n                });\n                \"Sort Method\"\n            }\n            (SettingKey::TokenizerType, SettingAction::Cycle) => {\n                session.config.encoding = match session.config.encoding {\n                    code2prompt_core::tokenizer::TokenizerType::Cl100kBase => {\n                        code2prompt_core::tokenizer::TokenizerType::O200kBase\n                    }\n                    code2prompt_core::tokenizer::TokenizerType::O200kBase => {\n                        code2prompt_core::tokenizer::TokenizerType::P50kBase\n                    }\n                    code2prompt_core::tokenizer::TokenizerType::P50kBase => {\n                        code2prompt_core::tokenizer::TokenizerType::P50kEdit\n                    }\n                    code2prompt_core::tokenizer::TokenizerType::P50kEdit => {\n                        code2prompt_core::tokenizer::TokenizerType::R50kBase\n                    }\n                    code2prompt_core::tokenizer::TokenizerType::R50kBase => {\n                        code2prompt_core::tokenizer::TokenizerType::Cl100kBase\n                    }\n                };\n                \"Tokenizer Type\"\n            }\n            (SettingKey::GitDiff, SettingAction::Toggle | SettingAction::Cycle) => {\n                session.config.diff_enabled = !session.config.diff_enabled;\n                \"Git Diff\"\n            }\n            (SettingKey::FollowSymlinks, SettingAction::Toggle | SettingAction::Cycle) => {\n                session.config.follow_symlinks = !session.config.follow_symlinks;\n                \"Follow Symlinks\"\n            }\n            (SettingKey::HiddenFiles, SettingAction::Toggle | SettingAction::Cycle) => {\n                session.config.hidden = !session.config.hidden;\n                \"Hidden Files\"\n            }\n            (SettingKey::NoIgnore, SettingAction::Toggle | SettingAction::Cycle) => {\n                session.config.no_ignore = !session.config.no_ignore;\n                \"No Ignore\"\n            }\n            (SettingKey::Deselected, SettingAction::Toggle | SettingAction::Cycle) => {\n                session.set_deselected(!session.config.deselected);\n                \"Deselected by Default\"\n            }\n            _ => \"Unknown Setting\",\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/model/statistics/mod.rs",
    "content": "//! Statistics state management for the TUI application.\n//!\n//! This module contains the statistics state and related functionality,\n//! including different statistics views and their management.\n\npub mod types;\n\nuse crate::model::DisplayFileNode;\nuse crate::utils::format_number;\npub use types::*;\n\n/// Statistics state containing all statistics-related data\n#[derive(Debug, Clone)]\npub struct StatisticsState {\n    pub view: StatisticsView,\n    pub scroll: u16,\n    pub token_map_entries: Vec<crate::token_map::TokenMapEntry>,\n}\n\nimpl Default for StatisticsState {\n    fn default() -> Self {\n        StatisticsState {\n            view: StatisticsView::Overview,\n            scroll: 0,\n            token_map_entries: Vec::new(),\n        }\n    }\n}\n\nimpl StatisticsState {\n    /// Count selected files using session-based approach\n    pub fn count_selected_files(\n        session: &mut code2prompt_core::session::Code2PromptSession,\n    ) -> usize {\n        session.get_selected_files().unwrap_or_default().len()\n    }\n\n    /// Count total files in the tree nodes\n    pub fn count_total_files(nodes: &[DisplayFileNode]) -> usize {\n        fn rec(n: &DisplayFileNode) -> usize {\n            if !n.is_directory {\n                1\n            } else {\n                n.children.iter().map(rec).sum()\n            }\n        }\n        nodes.iter().map(rec).sum()\n    }\n\n    /// Format number according to token format setting (moved from widget)\n    pub fn format_number(\n        num: usize,\n        token_format: &code2prompt_core::tokenizer::TokenFormat,\n    ) -> String {\n        format_number(num, token_format)\n    }\n\n    /// Aggregate tokens by file extension (moved from widget - business logic belongs in Model)\n    pub fn aggregate_by_extension(&self) -> Vec<(String, usize, usize)> {\n        let mut extension_stats: std::collections::HashMap<String, (usize, usize)> =\n            std::collections::HashMap::new();\n\n        for entry in &self.token_map_entries {\n            if !entry.metadata.is_dir {\n                let extension = entry\n                    .name\n                    .split('.')\n                    .next_back()\n                    .map(|ext| format!(\".{}\", ext))\n                    .unwrap_or_else(|| \"(no extension)\".to_string());\n\n                let (tokens, count) = extension_stats.entry(extension).or_insert((0, 0));\n                *tokens += entry.tokens;\n                *count += 1;\n            }\n        }\n\n        // Convert to sorted vec (by tokens desc)\n        let mut ext_vec: Vec<(String, usize, usize)> = extension_stats\n            .into_iter()\n            .map(|(ext, (tokens, count))| (ext, tokens, count))\n            .collect();\n        ext_vec.sort_by(|a, b| b.1.cmp(&a.1));\n        ext_vec\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/model/statistics/types.rs",
    "content": "//! Statistics view types and enums.\n//!\n//! This module contains the StatisticsView enum and related types\n//! for managing different statistics views in the TUI.\n\n/// Different views available in the Statistics tab\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum StatisticsView {\n    Overview,   // General statistics and summary\n    TokenMap,   // Token distribution by directory/file\n    Extensions, // Token distribution by file extension\n}\n\nimpl StatisticsView {\n    pub fn next(&self) -> Self {\n        match self {\n            StatisticsView::Overview => StatisticsView::TokenMap,\n            StatisticsView::TokenMap => StatisticsView::Extensions,\n            StatisticsView::Extensions => StatisticsView::Overview,\n        }\n    }\n\n    pub fn prev(&self) -> Self {\n        match self {\n            StatisticsView::Overview => StatisticsView::Extensions,\n            StatisticsView::TokenMap => StatisticsView::Overview,\n            StatisticsView::Extensions => StatisticsView::TokenMap,\n        }\n    }\n\n    pub fn as_str(&self) -> &'static str {\n        match self {\n            StatisticsView::Overview => \"Overview\",\n            StatisticsView::TokenMap => \"Token Map\",\n            StatisticsView::Extensions => \"Extensions\",\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/model/template/editor.rs",
    "content": "//! Template editor state management.\n//!\n//! This module contains the state and logic for the template editor component,\n//! including TextArea management, validation, and content synchronization.\n\nuse regex::Regex;\nuse std::collections::HashSet;\nuse tui_textarea::TextArea;\n\n/// State for the template editor component\n#[derive(Debug)]\npub struct EditorState {\n    pub content: String,\n    pub editor: TextArea<'static>,\n    pub current_template_name: String,\n    pub is_valid: bool,\n    pub validation_message: String,\n    pub template_variables: Vec<String>, // Variables found in template\n}\n\nimpl Clone for EditorState {\n    fn clone(&self) -> Self {\n        let mut new_editor = TextArea::from(self.editor.lines().iter().map(|s| s.as_str()));\n        new_editor.move_cursor(tui_textarea::CursorMove::Jump(\n            self.editor.cursor().0.try_into().unwrap_or(0),\n            self.editor.cursor().1.try_into().unwrap_or(0),\n        ));\n\n        Self {\n            content: self.content.clone(),\n            editor: new_editor,\n            current_template_name: self.current_template_name.clone(),\n            is_valid: self.is_valid,\n            validation_message: self.validation_message.clone(),\n            template_variables: self.template_variables.clone(),\n        }\n    }\n}\n\nimpl Default for EditorState {\n    fn default() -> Self {\n        // Load default markdown template from API\n        let content = if let Some(builtin_template) =\n            code2prompt_core::builtin_templates::BuiltinTemplates::get_template(\"default-markdown\")\n        {\n            builtin_template.content\n        } else {\n            \"# {{project_name}}\\n\\n{{#if files}}\\n{{#each files}}\\n## {{path}}\\n\\n```{{extension}}\\n{{content}}\\n```\\n\\n{{/each}}\\n{{/if}}\"\n        };\n\n        let editor = TextArea::from(content.lines());\n\n        let mut state = Self {\n            content: content.to_string(),\n            editor,\n            current_template_name: \"Default (Markdown)\".to_string(),\n            is_valid: true,\n            validation_message: String::new(),\n            template_variables: Vec::new(),\n        };\n\n        state.analyze_template_variables();\n        state\n    }\n}\n\nimpl EditorState {\n    /// Update content from TextArea and re-analyze variables\n    pub fn sync_content_from_textarea(&mut self) {\n        self.content = self.editor.lines().join(\"\\n\");\n        self.analyze_template_variables();\n    }\n\n    /// Parse template content to extract all {{variable}} references\n    pub fn analyze_template_variables(&mut self) {\n        let re = Regex::new(r\"\\{\\{\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\}\\}\").unwrap();\n        let mut found_vars = HashSet::new();\n\n        for cap in re.captures_iter(&self.content) {\n            if let Some(var_name) = cap.get(1) {\n                found_vars.insert(var_name.as_str().to_string());\n            }\n        }\n\n        self.template_variables = found_vars.into_iter().collect();\n        self.template_variables.sort();\n    }\n\n    /// Get all variables found in the template\n    pub fn get_template_variables(&self) -> &[String] {\n        &self.template_variables\n    }\n\n    /// Validate template syntax with enhanced Handlebars checking\n    pub fn validate_template(&mut self) {\n        // First check for balanced braces\n        let open_count = self.content.matches(\"{{\").count();\n        let close_count = self.content.matches(\"}}\").count();\n\n        if open_count != close_count {\n            self.is_valid = false;\n            self.validation_message = format!(\n                \"Unbalanced braces: {} opening, {} closing\",\n                open_count, close_count\n            );\n            return;\n        }\n\n        // Try to compile the template with Handlebars\n        match self.compile_template() {\n            Ok(_) => {\n                self.is_valid = true;\n                self.validation_message = String::new();\n            }\n            Err(e) => {\n                self.is_valid = false;\n                self.validation_message = format!(\"Template syntax error: {}\", e);\n            }\n        }\n    }\n\n    /// Attempt to compile the template to check for syntax errors\n    fn compile_template(&self) -> Result<(), String> {\n        let mut handlebars = handlebars::Handlebars::new();\n\n        // Set strict mode to catch undefined variables\n        handlebars.set_strict_mode(false); // Allow undefined variables for now\n\n        match handlebars.register_template_string(\"test\", &self.content) {\n            Ok(_) => Ok(()),\n            Err(e) => Err(format!(\"{}\", e)),\n        }\n    }\n\n    /// Get current template content\n    pub fn get_content(&self) -> &str {\n        &self.content\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/model/template/mod.rs",
    "content": "//! Template state management module.\n//!\n//! This module coordinates the three template sub-components:\n//! - Editor: Template content editing and validation\n//! - Variable: Variable management and validation\n//! - Picker: Template selection and loading\n\npub mod editor;\npub mod picker;\npub mod variable;\n\npub use editor::EditorState;\npub use picker::{ActiveList, PickerState};\npub use variable::{VariableCategory, VariableInfo, VariableState};\n\n/// Which component is currently focused\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum TemplateFocus {\n    Editor,\n    Variables,\n    Picker,\n}\n\n/// Focus mode determines interaction behavior\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum FocusMode {\n    Normal,          // Can switch between panels with e/v/p\n    EditingTemplate, // Locked to editor, ESC to exit\n    EditingVariable, // Locked to variables, ESC to exit\n}\n\n/// Coordinated template state containing all sub-components\n#[derive(Debug, Clone)]\npub struct TemplateState {\n    pub editor: EditorState,\n    pub variables: VariableState,\n    pub picker: PickerState,\n    pub focus: TemplateFocus,\n    pub focus_mode: FocusMode,\n    pub status_message: String,\n}\n\nimpl Default for TemplateState {\n    fn default() -> Self {\n        let mut state = Self {\n            editor: EditorState::default(),\n            variables: VariableState::default(),\n            picker: PickerState::default(),\n            focus: TemplateFocus::Editor,\n            focus_mode: FocusMode::Normal,\n            status_message: String::new(),\n        };\n\n        // Initialize variable state with template variables\n        state.sync_variables_with_template();\n        state\n    }\n}\n\nimpl TemplateState {\n    /// Create template state from model (for TUI integration)\n    pub fn from_model(model: &crate::model::Model) -> Self {\n        // Create a new state based on the model's template state\n        model.template.clone()\n    }\n\n    /// Synchronize variables with current template content\n    pub fn sync_variables_with_template(&mut self) {\n        let template_vars = self.editor.get_template_variables();\n        self.variables.update_missing_variables(template_vars);\n    }\n\n    /// Set focus to a specific component\n    pub fn set_focus(&mut self, focus: TemplateFocus) {\n        self.focus = focus;\n    }\n\n    /// Get current focus\n    pub fn get_focus(&self) -> TemplateFocus {\n        self.focus\n    }\n\n    /// Set focus mode\n    pub fn set_focus_mode(&mut self, mode: FocusMode) {\n        self.focus_mode = mode;\n    }\n\n    /// Get current focus mode\n    pub fn get_focus_mode(&self) -> FocusMode {\n        self.focus_mode\n    }\n\n    /// Check if currently in an editing mode\n    pub fn is_in_editing_mode(&self) -> bool {\n        matches!(\n            self.focus_mode,\n            FocusMode::EditingTemplate | FocusMode::EditingVariable\n        )\n    }\n\n    /// Get organized variables for display\n    pub fn get_organized_variables(&self) -> Vec<VariableInfo> {\n        self.variables\n            .get_organized_variables(self.editor.get_template_variables())\n    }\n\n    /// Get current template content for analysis\n    pub fn get_template_content(&self) -> &str {\n        self.editor.get_content()\n    }\n\n    /// Get status message\n    pub fn get_status(&self) -> &str {\n        &self.status_message\n    }\n\n    /// Load the currently selected template from the picker\n    pub fn load_selected_template(&mut self) -> Result<String, String> {\n        let selected_template = self.get_selected_template()?;\n\n        // Load template content based on type\n        let (content, template_name) = if selected_template\n            .path\n            .to_string_lossy()\n            .starts_with(\"builtin://\")\n        {\n            // Load built-in template from embedded resources\n            let path_str = selected_template.path.to_string_lossy();\n            let template_key = path_str.strip_prefix(\"builtin://\").unwrap_or(\"\");\n\n            if let Some(builtin_template) =\n                code2prompt_core::builtin_templates::BuiltinTemplates::get_template(template_key)\n            {\n                (\n                    builtin_template.content.to_string(),\n                    builtin_template.name.to_string(),\n                )\n            } else {\n                return Err(format!(\"Built-in template '{}' not found\", template_key));\n            }\n        } else {\n            // Load template from file\n            let content = std::fs::read_to_string(&selected_template.path)\n                .map_err(|e| format!(\"Failed to read template file: {}\", e))?;\n            (content, selected_template.name.clone())\n        };\n\n        // Update editor with new content\n        self.editor.content = content.clone();\n        self.editor.current_template_name = template_name.clone();\n\n        // Create new TextArea with the content\n        self.editor.editor = tui_textarea::TextArea::from(content.lines());\n\n        // Sync and validate\n        self.editor.sync_content_from_textarea();\n        self.editor.validate_template();\n\n        Ok(template_name)\n    }\n\n    /// Get the currently selected template from the picker\n    fn get_selected_template(&self) -> Result<&picker::TemplateFile, String> {\n        match self.picker.active_list {\n            ActiveList::Default => self\n                .picker\n                .default_templates\n                .get(self.picker.default_cursor)\n                .ok_or_else(|| \"No default template selected\".to_string()),\n            ActiveList::Custom => self\n                .picker\n                .custom_templates\n                .get(self.picker.custom_cursor)\n                .ok_or_else(|| \"No custom template selected\".to_string()),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/model/template/picker.rs",
    "content": "//! Template picker state management.\n//!\n//! This module contains the state and logic for the template picker component,\n//! including loading templates from default and custom directories.\n\nuse std::path::PathBuf;\n\n/// Represents a template file\n#[derive(Debug, Clone)]\npub struct TemplateFile {\n    pub name: String,\n    pub path: PathBuf,\n}\n\n/// Which list is currently active in the picker\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum ActiveList {\n    Default,\n    Custom,\n}\n\n/// State for the template picker component\n#[derive(Debug, Clone)]\npub struct PickerState {\n    pub default_templates: Vec<TemplateFile>,\n    pub custom_templates: Vec<TemplateFile>,\n    pub active_list: ActiveList,\n    pub default_cursor: usize,\n    pub custom_cursor: usize,\n}\n\nimpl Default for PickerState {\n    fn default() -> Self {\n        let mut state = Self {\n            default_templates: Vec::new(),\n            custom_templates: Vec::new(),\n            active_list: ActiveList::Default,\n            default_cursor: 0,\n            custom_cursor: 0,\n        };\n\n        state.load_all_templates();\n        state\n    }\n}\n\nimpl PickerState {\n    /// Load all templates from default and custom directories\n    pub fn load_all_templates(&mut self) {\n        self.load_default_templates();\n        self.load_custom_templates();\n    }\n\n    /// Load built-in default templates\n    fn load_default_templates(&mut self) {\n        self.default_templates.clear();\n\n        // Load all built-in templates from the core\n        let builtin_templates = code2prompt_core::builtin_templates::BuiltinTemplates::get_all();\n\n        // Sort templates by name for consistent ordering\n        let mut template_entries: Vec<_> = builtin_templates.iter().collect();\n        template_entries.sort_by(|a, b| a.1.name.cmp(b.1.name));\n\n        for (key, template) in template_entries {\n            self.default_templates.push(TemplateFile {\n                name: template.name.to_string(),\n                path: PathBuf::from(format!(\"builtin://{}\", key)),\n            });\n        }\n    }\n\n    /// Load custom templates from user directory\n    fn load_custom_templates(&mut self) {\n        self.custom_templates.clear();\n\n        // Load templates from custom directory using utility function\n        if let Ok(all_templates) = crate::utils::load_all_templates() {\n            for (name, path) in all_templates {\n                // All templates from load_all_templates are custom\n                self.custom_templates.push(TemplateFile {\n                    name,\n                    path: PathBuf::from(path),\n                });\n            }\n        }\n    }\n\n    /// Move cursor up in unified list\n    pub fn move_cursor_up(&mut self) {\n        let total_items = self.get_total_selectable_items();\n        if total_items == 0 {\n            return;\n        }\n\n        let current_global = self.get_global_template_index();\n        let new_global = if current_global == 0 {\n            total_items - 1 // Wrap to bottom\n        } else {\n            current_global - 1\n        };\n\n        self.set_cursor_from_global_position(new_global);\n    }\n\n    /// Move cursor down in unified list\n    pub fn move_cursor_down(&mut self) {\n        let total_items = self.get_total_selectable_items();\n        if total_items == 0 {\n            return;\n        }\n\n        let current_global = self.get_global_template_index();\n        let new_global = (current_global + 1) % total_items;\n\n        self.set_cursor_from_global_position(new_global);\n    }\n\n    /// Refresh templates by reloading from directories\n    pub fn refresh(&mut self) {\n        self.load_all_templates();\n\n        // Reset cursors if they're out of bounds\n        if self.default_cursor >= self.default_templates.len() {\n            self.default_cursor = self.default_templates.len().saturating_sub(1);\n        }\n        if self.custom_cursor >= self.custom_templates.len() {\n            self.custom_cursor = self.custom_templates.len().saturating_sub(1);\n        }\n    }\n\n    /// Get global cursor position for unified list display (for rendering)\n    pub fn get_global_cursor_position(&self) -> usize {\n        let mut position = 0;\n\n        // Count default templates section\n        if !self.default_templates.is_empty() {\n            position += 1; // Section header\n            if self.active_list == ActiveList::Default {\n                position += self.default_cursor;\n                return position;\n            }\n            position += self.default_templates.len();\n        }\n\n        // Count custom templates section\n        if !self.custom_templates.is_empty() {\n            if !self.default_templates.is_empty() {\n                position += 1; // Separator\n            }\n            position += 1; // Section header\n            if self.active_list == ActiveList::Custom {\n                position += self.custom_cursor;\n                return position;\n            }\n        }\n\n        position\n    }\n\n    /// Get global template index (for navigation logic)\n    fn get_global_template_index(&self) -> usize {\n        match self.active_list {\n            ActiveList::Default => self.default_cursor,\n            ActiveList::Custom => self.default_templates.len() + self.custom_cursor,\n        }\n    }\n\n    /// Get total number of selectable items (templates only, not headers)\n    fn get_total_selectable_items(&self) -> usize {\n        self.default_templates.len() + self.custom_templates.len()\n    }\n\n    /// Set cursor position from global position in unified list\n    fn set_cursor_from_global_position(&mut self, global_pos: usize) {\n        let mut template_index = 0;\n\n        // Check if position is in default templates\n        if global_pos < self.default_templates.len() {\n            self.active_list = ActiveList::Default;\n            self.default_cursor = global_pos;\n            return;\n        }\n        template_index += self.default_templates.len();\n\n        // Check if position is in custom templates\n        if global_pos < template_index + self.custom_templates.len() {\n            self.active_list = ActiveList::Custom;\n            self.custom_cursor = global_pos - template_index;\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/model/template/variable.rs",
    "content": "//! Template variable state management.\n//!\n//! This module contains the state and logic for managing template variables,\n//! including system variables, user-defined variables, and missing variables.\n\nuse std::collections::HashMap;\n\n/// Variable categories for display and management\n#[derive(Debug, Clone, PartialEq)]\npub enum VariableCategory {\n    System,  // From build_template_data\n    User,    // User-defined\n    Missing, // In template but not defined\n}\n\n/// Information about a template variable\n#[derive(Debug, Clone)]\npub struct VariableInfo {\n    pub name: String,\n    pub value: Option<String>,\n    pub category: VariableCategory,\n    pub description: Option<String>,\n}\n\n/// State for the template variable component\n#[derive(Debug, Clone)]\npub struct VariableState {\n    pub system_variables: HashMap<String, String>, // System variables with descriptions\n    pub user_variables: HashMap<String, String>,   // User-defined variables\n    pub missing_variables: Vec<String>,            // Variables in template but not defined\n    pub cursor: usize,                             // Current cursor position in variable list\n    pub editing_variable: Option<String>,          // Currently editing variable name\n    pub variable_input_content: String,            // Content being typed for variable\n    pub show_variable_input: bool,                 // Show variable input dialog\n}\n\nimpl Default for VariableState {\n    fn default() -> Self {\n        Self {\n            system_variables: Self::get_default_system_variables(),\n            user_variables: HashMap::new(),\n            missing_variables: Vec::new(),\n            cursor: 0,\n            editing_variable: None,\n            variable_input_content: String::new(),\n            show_variable_input: false,\n        }\n    }\n}\n\nimpl VariableState {\n    /// Get default system variables that are available from build_template_data\n    fn get_default_system_variables() -> HashMap<String, String> {\n        let mut vars = HashMap::new();\n\n        // Main template variables from build_template_data()\n        vars.insert(\n            \"absolute_code_path\".to_string(),\n            \"Path to the codebase directory\".to_string(),\n        );\n        vars.insert(\n            \"source_tree\".to_string(),\n            \"Directory tree structure\".to_string(),\n        );\n        vars.insert(\n            \"files\".to_string(),\n            \"Array of file objects with content\".to_string(),\n        );\n        vars.insert(\n            \"git_diff\".to_string(),\n            \"Git diff output (if enabled)\".to_string(),\n        );\n        vars.insert(\n            \"git_diff_branch\".to_string(),\n            \"Git diff between branches\".to_string(),\n        );\n        vars.insert(\n            \"git_log_branch\".to_string(),\n            \"Git log between branches\".to_string(),\n        );\n\n        // File object properties (used within {{#each files}} loops)\n        vars.insert(\n            \"path\".to_string(),\n            \"File path (available in {{#each files}} context)\".to_string(),\n        );\n        vars.insert(\n            \"code\".to_string(),\n            \"File content (available in {{#each files}} context)\".to_string(),\n        );\n        vars.insert(\n            \"extension\".to_string(),\n            \"File extension (available in {{#each files}} context)\".to_string(),\n        );\n        vars.insert(\n            \"token_count\".to_string(),\n            \"Token count for file (available in {{#each files}} context)\".to_string(),\n        );\n        vars.insert(\n            \"metadata\".to_string(),\n            \"File metadata (available in {{#each files}} context)\".to_string(),\n        );\n        vars.insert(\n            \"mod_time\".to_string(),\n            \"File modification time (available in {{#each files}} context)\".to_string(),\n        );\n\n        vars\n    }\n\n    /// Update missing variables based on template variables\n    pub fn update_missing_variables(&mut self, template_variables: &[String]) {\n        self.missing_variables.clear();\n\n        for var in template_variables {\n            if !self.system_variables.contains_key(var) && !self.user_variables.contains_key(var) {\n                self.missing_variables.push(var.clone());\n            }\n        }\n\n        self.missing_variables.sort();\n    }\n\n    /// Get all variables organized by category for display\n    pub fn get_organized_variables(&self, template_variables: &[String]) -> Vec<VariableInfo> {\n        let mut variables = Vec::new();\n\n        // System variables (only those used in template)\n        for var in template_variables {\n            if let Some(desc) = self.system_variables.get(var) {\n                variables.push(VariableInfo {\n                    name: var.clone(),\n                    value: Some(\"(system)\".to_string()),\n                    category: VariableCategory::System,\n                    description: Some(desc.clone()),\n                });\n            }\n        }\n\n        // User variables (only those used in template)\n        for var in template_variables {\n            if let Some(value) = self.user_variables.get(var) {\n                variables.push(VariableInfo {\n                    name: var.clone(),\n                    value: Some(value.clone()),\n                    category: VariableCategory::User,\n                    description: None,\n                });\n            }\n        }\n\n        // Missing variables\n        for var in &self.missing_variables {\n            variables.push(VariableInfo {\n                name: var.clone(),\n                value: None,\n                category: VariableCategory::Missing,\n                description: Some(\"⚠️ Not defined\".to_string()),\n            });\n        }\n\n        variables\n    }\n\n    /// Set a user variable\n    pub fn set_user_variable(&mut self, key: String, value: String) {\n        self.user_variables.insert(key, value);\n    }\n\n    /// Check if there are missing variables\n    pub fn has_missing_variables(&self) -> bool {\n        !self.missing_variables.is_empty()\n    }\n\n    /// Cancel variable editing\n    pub fn cancel_editing(&mut self) {\n        self.editing_variable = None;\n        self.variable_input_content.clear();\n        self.show_variable_input = false;\n    }\n\n    /// Finish editing variable and save\n    pub fn finish_editing(&mut self) -> Option<(String, String)> {\n        if let Some(var_name) = self.editing_variable.take() {\n            let value = self.variable_input_content.clone();\n            self.set_user_variable(var_name.clone(), value.clone());\n            self.variable_input_content.clear();\n            self.show_variable_input = false;\n            Some((var_name, value))\n        } else {\n            None\n        }\n    }\n\n    /// Add character to variable input\n    pub fn add_char_to_input(&mut self, c: char) {\n        self.variable_input_content.push(c);\n    }\n\n    /// Remove character from variable input\n    pub fn remove_char_from_input(&mut self) {\n        self.variable_input_content.pop();\n    }\n\n    /// Get current variable input content\n    pub fn get_input_content(&self) -> &str {\n        &self.variable_input_content\n    }\n\n    /// Check if currently editing a variable\n    pub fn is_editing(&self) -> bool {\n        self.show_variable_input\n    }\n\n    /// Get currently editing variable name\n    pub fn get_editing_variable(&self) -> Option<&String> {\n        self.editing_variable.as_ref()\n    }\n\n    /// Move cursor to first missing/user-defined variable\n    pub fn move_to_first_missing_variable(&mut self) {\n        // This will be called when entering variable editing mode\n        // For now, just reset cursor to 0, but we could enhance this\n        // to find the first missing variable in the organized list\n        self.cursor = 0;\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/token_map.rs",
    "content": "//! Token map visualization and analysis.\n//!\n//! This module provides functionality for generating and displaying visual token maps\n//! that show how tokens are distributed across files in a codebase. It creates\n//! hierarchical tree structures with visual bars and colors, similar to disk usage\n//! analyzers but for token consumption.\nuse code2prompt_core::path::FileEntry;\nuse lscolors::{Indicator, LsColors};\nuse serde::Deserialize;\nuse std::cmp::Ordering;\nuse std::collections::{BTreeMap, BinaryHeap, HashMap};\nuse std::path::Path;\nuse unicode_width::UnicodeWidthStr;\n\n/// Color information for TUI rendering\n#[derive(Debug, Clone)]\npub enum TuiColor {\n    White,\n    Gray,\n    Red,\n    Green,\n    Blue,\n    Yellow,\n    Cyan,\n    Magenta,\n    LightRed,\n    LightGreen,\n    LightBlue,\n    LightYellow,\n    LightCyan,\n    LightMagenta,\n}\n\n/// Formatted line for TUI token map display with separate components\n#[derive(Debug, Clone)]\npub struct TuiTokenMapLine {\n    pub tokens_part: String,\n    pub prefix_part: String,\n    pub name_part: String,\n    pub name_color: TuiColor,\n    pub bar_part: String,\n    pub percentage_part: String,\n}\n\n#[derive(Debug, Clone, Copy, Deserialize)]\npub struct EntryMetadata {\n    pub is_dir: bool,\n}\n\n#[derive(Debug, Clone)]\nstruct TreeNode {\n    tokens: usize,\n    children: BTreeMap<String, TreeNode>,\n    path: String,\n    metadata: Option<EntryMetadata>,\n}\n\nimpl TreeNode {\n    fn with_path(path: String) -> Self {\n        TreeNode {\n            tokens: 0,\n            children: BTreeMap::new(),\n            path,\n            metadata: None,\n        }\n    }\n}\n\n// For priority queue ordering\n#[derive(Debug, Clone, Eq, PartialEq)]\nstruct NodePriority {\n    tokens: usize,\n    path: String,\n    depth: usize,\n}\n\nimpl Ord for NodePriority {\n    fn cmp(&self, other: &Self) -> Ordering {\n        // Order by tokens (descending), then by depth (ascending), then by path\n        self.tokens\n            .cmp(&other.tokens)\n            .then_with(|| other.depth.cmp(&self.depth))\n            .then_with(|| self.path.cmp(&other.path))\n    }\n}\n\nimpl PartialOrd for NodePriority {\n    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\n/// Generate a hierarchical token map with optional display limits.\n///\n/// Creates a tree structure showing token distribution across files and directories,\n/// with optional limits on the number of entries and minimum percentage thresholds\n/// for inclusion in the output.\n///\n/// # Arguments\n///\n/// * `files` - Array of file metadata from the code2prompt session\n/// * `total_tokens` - Total token count for percentage calculations\n/// * `max_lines` - Maximum number of entries to return (None for unlimited)\n/// * `min_percent` - Minimum percentage threshold for inclusion (None for no limit)\n///\n/// # Returns\n///\n/// * `Vec<TokenMapEntry>` - Hierarchical list of token map entries ready for display\npub fn generate_token_map_with_limit(\n    files: &[FileEntry],\n    total_tokens: usize,\n    max_lines: Option<usize>,\n    min_percent: Option<f64>,\n) -> Vec<TokenMapEntry> {\n    let max_lines = max_lines.unwrap_or(20);\n    let min_percent = min_percent.unwrap_or(0.1);\n\n    let mut root = TreeNode::with_path(String::new());\n    root.tokens = total_tokens;\n\n    // Insert all files into the tree\n    for file in files {\n        let path_str = &file.path;\n        let tokens = file.token_count;\n        let metadata = EntryMetadata {\n            is_dir: file.metadata.is_dir,\n        };\n\n        let path = Path::new(path_str);\n\n        // Skip the root component if it exists\n        let components: Vec<_> = path\n            .components()\n            .filter_map(|c| c.as_os_str().to_str())\n            .collect();\n\n        insert_path(&mut root, &components, tokens, String::new(), metadata);\n    }\n\n    // Use priority queue to select most significant entries\n    let allowed_nodes = select_nodes_to_display(&root, total_tokens, max_lines, min_percent);\n\n    // Convert tree to sorted entries for display\n    let mut entries = Vec::new();\n    rebuild_filtered_tree(\n        &root,\n        String::new(),\n        &allowed_nodes,\n        &mut entries,\n        0,\n        total_tokens,\n        true,\n    );\n\n    // Add summary for hidden files if needed\n    let displayed_tokens: usize = entries\n        .iter()\n        .map(|e| {\n            if !e.metadata.is_dir {\n                e.tokens\n            } else {\n                // For directories, only count their direct file children to avoid double counting\n                0\n            }\n        })\n        .sum();\n\n    let hidden_tokens = calculate_file_tokens(&root) - displayed_tokens;\n    if hidden_tokens > 0 {\n        entries.push(TokenMapEntry {\n            path: \"(other files)\".to_string(),\n            name: \"(other files)\".to_string(),\n            tokens: hidden_tokens,\n            percentage: (hidden_tokens as f64 / total_tokens as f64) * 100.0,\n            depth: 0,\n            is_last: true,\n            metadata: EntryMetadata { is_dir: false },\n        });\n    }\n\n    entries\n}\n\nfn calculate_file_tokens(node: &TreeNode) -> usize {\n    if node.metadata.is_some_and(|m| !m.is_dir) {\n        node.tokens\n    } else {\n        node.children.values().map(calculate_file_tokens).sum()\n    }\n}\n\nfn insert_path(\n    node: &mut TreeNode,\n    components: &[&str],\n    tokens: usize,\n    parent_path: String,\n    file_metadata: EntryMetadata,\n) {\n    if components.is_empty() {\n        return;\n    }\n\n    if components.len() == 1 {\n        // This is a file\n        let file_name = components[0].to_string();\n        let file_path = if parent_path.is_empty() {\n            file_name.clone()\n        } else {\n            format!(\"{}/{}\", parent_path, file_name)\n        };\n        let child = node\n            .children\n            .entry(file_name)\n            .or_insert_with(|| TreeNode::with_path(file_path));\n        child.tokens = tokens;\n        child.metadata = Some(file_metadata);\n    } else {\n        // This is a directory\n        let dir_name = components[0].to_string();\n        let dir_path = if parent_path.is_empty() {\n            dir_name.clone()\n        } else {\n            format!(\"{}/{}\", parent_path, dir_name)\n        };\n        let child = node\n            .children\n            .entry(dir_name)\n            .or_insert_with(|| TreeNode::with_path(dir_path.clone()));\n        child.tokens += tokens;\n        child.metadata = Some(EntryMetadata { is_dir: true });\n        insert_path(child, &components[1..], tokens, dir_path, file_metadata);\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct TokenMapEntry {\n    pub path: String,\n    pub name: String,\n    pub tokens: usize,\n    pub percentage: f64,\n    pub depth: usize,\n    pub is_last: bool,\n    pub metadata: EntryMetadata,\n}\n\n/// Select nodes to display using priority queue\nfn select_nodes_to_display(\n    root: &TreeNode,\n    total_tokens: usize,\n    max_lines: usize,\n    min_percent: f64,\n) -> HashMap<String, usize> {\n    let mut heap = BinaryHeap::new();\n    let mut allowed_nodes = HashMap::new();\n    let min_tokens = (total_tokens as f64 * min_percent / 100.0) as usize;\n\n    // Start with root children\n    for child in root.children.values() {\n        if child.tokens >= min_tokens {\n            heap.push(NodePriority {\n                tokens: child.tokens,\n                path: child.path.clone(),\n                depth: 0,\n            });\n        }\n    }\n\n    // Process nodes by priority\n    while allowed_nodes.len() < max_lines.saturating_sub(1) && !heap.is_empty() {\n        if let Some(node_priority) = heap.pop() {\n            allowed_nodes.insert(node_priority.path.clone(), node_priority.depth);\n\n            // Find the node in the tree and add its children\n            if let Some(node) = find_node_by_path(root, &node_priority.path) {\n                for child in node.children.values() {\n                    if child.tokens >= min_tokens && !allowed_nodes.contains_key(&child.path) {\n                        heap.push(NodePriority {\n                            tokens: child.tokens,\n                            path: child.path.clone(),\n                            depth: node_priority.depth + 1,\n                        });\n                    }\n                }\n            }\n        }\n    }\n\n    allowed_nodes\n}\n\n/// Find a node by its path\nfn find_node_by_path<'a>(root: &'a TreeNode, path: &str) -> Option<&'a TreeNode> {\n    if path.is_empty() {\n        return Some(root);\n    }\n\n    let components: Vec<&str> = path.split('/').collect();\n    let mut current = root;\n\n    for component in components {\n        match current.children.get(component) {\n            Some(child) => current = child,\n            None => return None,\n        }\n    }\n\n    Some(current)\n}\n\n/// Rebuild tree with only allowed nodes\nfn rebuild_filtered_tree(\n    node: &TreeNode,\n    path: String,\n    allowed_nodes: &HashMap<String, usize>,\n    entries: &mut Vec<TokenMapEntry>,\n    depth: usize,\n    total_tokens: usize,\n    is_last: bool,\n) {\n    // Check if this node should be included\n    if !path.is_empty() && allowed_nodes.contains_key(&path) {\n        let percentage = (node.tokens as f64 / total_tokens as f64) * 100.0;\n        let name = path.split('/').next_back().unwrap_or(&path).to_string();\n\n        let metadata = node.metadata.unwrap_or(EntryMetadata { is_dir: true });\n\n        entries.push(TokenMapEntry {\n            path: path.clone(),\n            name,\n            tokens: node.tokens,\n            percentage,\n            depth,\n            is_last,\n            metadata,\n        });\n    }\n\n    // Process children that are in allowed_nodes\n    let mut filtered_children: Vec<_> = node\n        .children\n        .iter()\n        .filter(|(_, child)| allowed_nodes.contains_key(&child.path))\n        .collect();\n\n    // Sort by tokens descending\n    filtered_children.sort_by(|a, b| b.1.tokens.cmp(&a.1.tokens));\n\n    let child_count = filtered_children.len();\n    for (i, (name, child)) in filtered_children.into_iter().enumerate() {\n        let child_path = if path.is_empty() {\n            name.clone()\n        } else {\n            format!(\"{}/{}\", path, name)\n        };\n\n        let is_last_child = i == child_count - 1;\n        rebuild_filtered_tree(\n            child,\n            child_path,\n            allowed_nodes,\n            entries,\n            depth + 1,\n            total_tokens,\n            is_last_child,\n        );\n    }\n}\n\nfn should_enable_colors() -> bool {\n    // Check NO_COLOR environment variable (https://no-color.org/)\n    if std::env::var_os(\"NO_COLOR\").is_some() {\n        return false;\n    }\n\n    // Check if we're in a terminal\n    if terminal_size::terminal_size().is_none() {\n        return false;\n    }\n\n    // On Windows, enable ANSI support\n    #[cfg(windows)]\n    {\n        use log::error;\n        match ansi_term::enable_ansi_support() {\n            Ok(_) => true,\n            Err(_) => {\n                error!(\"This version of Windows does not support ANSI colors\");\n                false\n            }\n        }\n    }\n\n    #[cfg(not(windows))]\n    {\n        true\n    }\n}\n\n/// Display a visual token map with colors and hierarchical tree structure.\n///\n/// Renders the token map entries as a formatted tree with visual progress bars,\n/// colors based on file types, and proper Unicode tree drawing characters.\n/// Automatically adapts to terminal width and applies appropriate colors.\n///\n/// # Arguments\n///\n/// * `entries` - The token map entries to display\n/// * `total_tokens` - Total token count for percentage calculations\npub fn display_token_map(entries: &[TokenMapEntry], total_tokens: usize) {\n    if entries.is_empty() {\n        return;\n    }\n\n    // Initialize LsColors from environment\n    let ls_colors = LsColors::from_env().unwrap_or_default();\n    let colors_enabled = should_enable_colors();\n\n    // Terminal width detection\n    let terminal_width = terminal_size::terminal_size()\n        .map(|(terminal_size::Width(w), _)| w as usize)\n        .unwrap_or(80);\n\n    // Calculate max token width for alignment\n    let max_token_width = entries\n        .iter()\n        .map(|e| format_tokens(e.tokens).len())\n        .max()\n        .unwrap_or(3)\n        .max(format_tokens(total_tokens).len())\n        .max(4);\n\n    // Calculate max name length including tree prefix\n    let max_name_length = entries\n        .iter()\n        .map(|e| {\n            let prefix_width = if e.depth == 0 { 3 } else { (e.depth * 2) + 3 };\n            prefix_width + UnicodeWidthStr::width(e.name.as_str())\n        })\n        .max()\n        .unwrap_or(20)\n        .min(terminal_width / 2);\n\n    // Calculate bar width\n    let bar_width = terminal_width\n        .saturating_sub(max_token_width + 3 + max_name_length + 2 + 2 + 5)\n        .max(20);\n\n    // Initialize parent bars array\n    let mut parent_bars: Vec<String> = vec![String::new(); 10];\n    parent_bars[0] = \"█\".repeat(bar_width);\n\n    for (i, entry) in entries.iter().enumerate() {\n        // Build tree prefix using shared logic\n        let prefix = build_tree_prefix(entry, entries, i);\n\n        // Format tokens\n        let tokens_str = format_tokens(entry.tokens);\n\n        // Generate hierarchical bar\n        let parent_bar = if entry.depth > 0 {\n            &parent_bars[entry.depth - 1]\n        } else {\n            &parent_bars[0]\n        };\n\n        let bar = generate_hierarchical_bar(bar_width, parent_bar, entry.percentage, entry.depth);\n\n        // Update parent bars\n        if entry.depth < parent_bars.len() {\n            parent_bars[entry.depth] = bar.clone();\n        }\n\n        // Format percentage\n        let percentage_str = format!(\"{:>4.0}%\", entry.percentage);\n\n        // Calculate padding for name\n        let prefix_display_width = prefix.chars().count();\n        let name_padding = max_name_length\n            .saturating_sub(prefix_display_width + UnicodeWidthStr::width(entry.name.as_str()));\n\n        // Create name with padding FIRST\n        let name_with_padding = format!(\"{}{}\", entry.name, \" \".repeat(name_padding));\n\n        // THEN apply colors to the name+padding combination\n        let colored_name_with_padding = if colors_enabled && entry.name != \"(other files)\" {\n            // Use our cached metadata to choose the coloring strategy\n            let ansi_style = if entry.metadata.is_dir {\n                // For directories, we know the type. No need to hit the filesystem.\n                ls_colors\n                    .style_for_indicator(Indicator::Directory)\n                    .map(|s| s.to_ansi_term_style())\n                    .unwrap_or_default()\n            } else {\n                // For files, rely on extension-based styling (no filesystem stat).\n                ls_colors\n                    .style_for_path(std::path::Path::new(&entry.path))\n                    .map(lscolors::Style::to_ansi_term_style)\n                    .unwrap_or_default()\n            };\n\n            // Apply style to name WITH padding\n            format!(\"{}\", ansi_style.paint(name_with_padding))\n        } else {\n            name_with_padding\n        };\n\n        eprintln!(\n            \"{:>width$}   {}{} │{}│ {}\",\n            tokens_str,\n            prefix,\n            colored_name_with_padding,\n            bar,\n            percentage_str,\n            width = max_token_width\n        );\n    }\n}\n\n/// Build tree prefix for an entry (shared logic for CLI and TUI)\nfn build_tree_prefix(entry: &TokenMapEntry, entries: &[TokenMapEntry], index: usize) -> String {\n    let mut prefix = String::new();\n\n    // Add vertical lines for parent levels\n    for d in 0..entry.depth {\n        if d < entry.depth - 1 {\n            // Check if we need a vertical line at this depth\n            let needs_line = entries\n                .iter()\n                .skip(index + 1)\n                .take_while(|entry| entry.depth > d)\n                .any(|entry| entry.depth == d + 1);\n            if needs_line {\n                prefix.push_str(\"│ \");\n            } else {\n                prefix.push_str(\"  \");\n            }\n        } else if entry.is_last {\n            prefix.push_str(\"└─\");\n        } else {\n            prefix.push_str(\"├─\");\n        }\n    }\n\n    // Special handling for root\n    if entry.depth == 0 && index == 0 && entry.name != \"(other files)\" {\n        prefix = \"┌─\".to_string();\n    }\n\n    // Check if has children\n    let has_children = entries\n        .get(index + 1)\n        .map(|next| next.depth > entry.depth)\n        .unwrap_or(false);\n\n    // Add the connecting character\n    if entry.depth > 0 || entry.name == \"(other files)\" {\n        if has_children {\n            prefix.push('┬');\n        } else {\n            prefix.push('─');\n        }\n    } else if index == 0 {\n        prefix.push('┴');\n    }\n\n    prefix.push(' ');\n    prefix\n}\n\n/// Determine TUI color for an entry based on file type and extension\nfn determine_tui_color(entry: &TokenMapEntry) -> TuiColor {\n    if entry.metadata.is_dir {\n        TuiColor::Cyan\n    } else {\n        match entry.name.split('.').next_back().unwrap_or(\"\") {\n            // Systems / compiled langs\n            \"rs\" => TuiColor::Yellow,\n            \"c\" | \"h\" | \"cpp\" | \"cxx\" | \"hpp\" => TuiColor::Blue,\n            \"go\" => TuiColor::LightBlue,\n            \"java\" | \"kt\" | \"kts\" => TuiColor::Red,\n            \"swift\" => TuiColor::LightRed,\n            \"zig\" => TuiColor::LightYellow,\n\n            // Web\n            \"js\" | \"mjs\" | \"cjs\" => TuiColor::LightGreen,\n            \"ts\" | \"tsx\" | \"jsx\" => TuiColor::LightCyan,\n            \"html\" | \"htm\" => TuiColor::Magenta,\n            \"css\" | \"scss\" | \"less\" => TuiColor::LightMagenta,\n\n            // Scripting / automation\n            \"py\" => TuiColor::LightYellow,\n            \"sh\" | \"bash\" | \"zsh\" => TuiColor::Gray,\n            \"rb\" => TuiColor::LightRed,\n            \"pl\" => TuiColor::LightCyan,\n            \"php\" => TuiColor::LightMagenta,\n            \"lua\" => TuiColor::LightBlue,\n\n            // Data / config / markup\n            \"json\" | \"toml\" | \"yaml\" | \"yml\" => TuiColor::Magenta,\n            \"xml\" => TuiColor::LightGreen,\n            \"csv\" => TuiColor::Green,\n            \"ini\" => TuiColor::Gray,\n\n            // Docs\n            \"md\" | \"txt\" | \"rst\" | \"adoc\" => TuiColor::Green,\n            \"pdf\" => TuiColor::Red,\n\n            // Default\n            _ => TuiColor::White,\n        }\n    }\n}\n\n/// Format token map entries for TUI display with adaptive layout.\n///\n/// Creates formatted lines with tree structure and color information suitable\n/// for rendering in a TUI interface using ratatui. This function uses the same\n/// adaptive layout logic as the CLI version but returns structured data components\n/// instead of printing directly.\n///\n/// # Arguments\n///\n/// * `entries` - The token map entries to format\n/// * `total_tokens` - Total token count for percentage calculations\n/// * `terminal_width` - Width of the terminal/TUI area for adaptive layout\n///\n/// # Returns\n///\n/// * `Vec<TuiTokenMapLine>` - Formatted lines ready for TUI rendering\npub fn format_token_map_for_tui(\n    entries: &[TokenMapEntry],\n    total_tokens: usize,\n    terminal_width: usize,\n) -> Vec<TuiTokenMapLine> {\n    if entries.is_empty() {\n        return Vec::new();\n    }\n\n    // Use the same adaptive layout logic as CLI\n    let terminal_width = terminal_width.max(80); // Minimum width\n\n    // Calculate max token width for alignment (same as CLI)\n    let max_token_width = entries\n        .iter()\n        .map(|e| format_tokens(e.tokens).len())\n        .max()\n        .unwrap_or(3)\n        .max(format_tokens(total_tokens).len())\n        .max(4);\n\n    // Calculate max name length including tree prefix (same as CLI)\n    let max_name_length = entries\n        .iter()\n        .map(|e| {\n            let prefix_width = if e.depth == 0 { 3 } else { (e.depth * 2) + 3 };\n            prefix_width + UnicodeWidthStr::width(e.name.as_str())\n        })\n        .max()\n        .unwrap_or(20)\n        .min(terminal_width / 2);\n\n    // Calculate bar width (adjusted for TUI to prevent overflow)\n    // TUI needs a bit more space than CLI to prevent the percentage column from overflowing\n    let bar_width = terminal_width\n        .saturating_sub(max_token_width + 3 + max_name_length + 2 + 2 + 7) // +2 more chars for TUI\n        .max(15); // Minimum bar width reduced slightly for TUI\n\n    // Initialize parent bars array (same as CLI)\n    let mut parent_bars: Vec<String> = vec![String::new(); 10];\n    parent_bars[0] = \"█\".repeat(bar_width);\n\n    let mut lines = Vec::new();\n\n    for (i, entry) in entries.iter().enumerate() {\n        // Build tree prefix using shared logic\n        let prefix = build_tree_prefix(entry, entries, i);\n\n        // Format tokens\n        let tokens_str = format_tokens(entry.tokens);\n\n        // Generate hierarchical bar (same as CLI)\n        let parent_bar = if entry.depth > 0 {\n            &parent_bars[entry.depth - 1]\n        } else {\n            &parent_bars[0]\n        };\n\n        let bar = generate_hierarchical_bar(bar_width, parent_bar, entry.percentage, entry.depth);\n\n        // Update parent bars (same as CLI)\n        if entry.depth < parent_bars.len() {\n            parent_bars[entry.depth] = bar.clone();\n        }\n\n        // Format percentage\n        let percentage_str = format!(\"{:>4.0}%\", entry.percentage);\n\n        // Calculate padding for name (same as CLI)\n        let prefix_display_width = prefix.chars().count();\n        let name_padding = max_name_length\n            .saturating_sub(prefix_display_width + UnicodeWidthStr::width(entry.name.as_str()));\n\n        // Create name with padding\n        let name_with_padding = format!(\"{}{}\", entry.name, \" \".repeat(name_padding));\n\n        // Determine color based on entry type and extension\n        let name_color = determine_tui_color(entry);\n\n        // Create structured components for TUI rendering\n        lines.push(TuiTokenMapLine {\n            tokens_part: format!(\"{:>width$}\", tokens_str, width = max_token_width),\n            prefix_part: prefix,\n            name_part: name_with_padding,\n            name_color,\n            bar_part: format!(\"│{}│\", bar),\n            percentage_part: percentage_str,\n        });\n    }\n\n    lines\n}\n\n// Format token counts with K/M suffixes (dust-style)\nfn format_tokens(tokens: usize) -> String {\n    if tokens >= 1_000_000 {\n        let millions = (tokens + 500_000) / 1_000_000;\n        format!(\"{}M\", millions)\n    } else if tokens >= 1_000 {\n        let thousands = (tokens + 500) / 1_000;\n        format!(\"{}K\", thousands)\n    } else {\n        format!(\"{}\", tokens)\n    }\n}\n\n// Generate bar with dust-style depth shading\nfn generate_hierarchical_bar(\n    bar_width: usize,\n    parent_bar: &str,\n    percentage: f64,\n    depth: usize,\n) -> String {\n    // Calculate how many characters should be filled for this entry\n    let filled_chars = ((percentage / 100.0) * bar_width as f64).round() as usize;\n    let mut result = String::new();\n\n    // Depth determines which shade to use for parent's solid blocks\n    let shade_char = match depth.max(1) {\n        1 => ' ', // Level 1: parent blocks become spaces\n        2 => '░', // Level 2: light shade\n        3 => '▒', // Level 3: medium shade\n        _ => '▓', // Level 4+: dark shade\n    };\n\n    // Process each character position\n    let parent_chars: Vec<char> = parent_bar.chars().collect();\n    for i in 0..bar_width {\n        if i < filled_chars {\n            // This is our filled portion - always solid\n            result.push('█');\n        } else if i < parent_chars.len() {\n            // This is parent's portion\n            let parent_char = parent_chars[i];\n            if parent_char == '█' {\n                // Replace parent's solid blocks with our shade\n                result.push(shade_char);\n            } else {\n                // Keep parent's existing shading\n                result.push(parent_char);\n            }\n        } else {\n            // Beyond parent's bar - empty\n            result.push(' ');\n        }\n    }\n\n    result\n}\n"
  },
  {
    "path": "crates/code2prompt/src/tui.rs",
    "content": "//! Terminal User Interface implementation.\n//!\n//! This module implements the complete TUI for code2prompt using ratatui and crossterm.\n//! It provides a tabbed interface with file selection, settings configuration,\n//! statistics viewing, and prompt output. The interface supports keyboard navigation,\n//! file tree browsing, real-time analysis, and clipboard integration.\n\nuse anyhow::Result;\nuse code2prompt_core::session::Code2PromptSession;\nuse crossterm::{\n    execute,\n    terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},\n};\nuse ratatui::{\n    crossterm::event::{KeyCode, KeyEvent, KeyModifiers},\n    prelude::*,\n    widgets::*,\n};\nuse std::io::{Stdout, stdout};\nuse tokio::sync::mpsc;\n\nuse crate::clipboard::copy_to_clipboard;\nuse crate::model::{\n    AnalysisResults, Cmd, FileTreeInputMode, Message, Model, StatisticsView, Tab, TemplateState,\n    template::{FocusMode, TemplateFocus, VariableCategory},\n};\nuse crate::token_map::generate_token_map_with_limit;\nuse crate::utils::{save_template_to_custom_dir, save_to_file};\nuse crate::widgets::{\n    FileSelectionWidget, OutputWidget, SettingsWidget, StatisticsByExtensionWidget,\n    StatisticsOverviewWidget, StatisticsTokenMapWidget, TemplateWidget,\n};\n\nuse crate::utils::build_file_tree_from_session;\n\npub struct TuiApp {\n    model: Model,\n    terminal: Terminal<CrosstermBackend<Stdout>>,\n    message_tx: mpsc::UnboundedSender<Message>,\n    message_rx: mpsc::UnboundedReceiver<Message>,\n}\n\nimpl TuiApp {\n    /// Create a new TUI application.\n    ///\n    /// Initializes the terminal and sets up the application state from the provided session.\n    /// The initial file tree is requested via a `RefreshFileTree` message in `run()`.\n    ///\n    /// Returns an error if the terminal cannot be initialized.\n    pub fn new(session: Code2PromptSession) -> Result<Self> {\n        let terminal = init_terminal()?;\n        let (message_tx, message_rx) = mpsc::unbounded_channel();\n        let model = Model::new(session);\n\n        Ok(Self {\n            model,\n            terminal,\n            message_tx,\n            message_rx,\n        })\n    }\n\n    // ~~~ Optimized Main Loop ~~~\n    pub async fn run(&mut self) -> Result<()> {\n        // Initialize file tree\n        self.handle_message(Message::RefreshFileTree)?;\n\n        loop {\n            // Process all available events with coalescing\n            let mut messages = Vec::new();\n\n            // Drain all available keyboard events\n            while crossterm::event::poll(std::time::Duration::from_millis(0))? {\n                if let crossterm::event::Event::Key(key) = crossterm::event::read()?\n                    && key.kind == crossterm::event::KeyEventKind::Press\n                {\n                    // Convert to ratatui KeyEvent\n                    let ratatui_key = self.convert_crossterm_key(key);\n\n                    // Handle the key event\n                    if let Some(message) = self.handle_key_event(ratatui_key) {\n                        if let Some(last_message) = messages.last_mut()\n                            && self.try_coalesce_messages(last_message, &message)\n                        {\n                            continue; // Message was coalesced\n                        }\n                        messages.push(message);\n                    }\n                }\n            }\n\n            // Handle all messages\n            for message in messages {\n                self.handle_message(message)?;\n            }\n\n            // Handle internal messages (non-blocking)\n            while let Ok(message) = self.message_rx.try_recv() {\n                self.handle_message(message)?;\n            }\n\n            // Render the UI\n            let model = self.model.clone();\n            self.terminal.draw(|frame| {\n                TuiApp::render_with_model(&model, frame);\n            })?;\n\n            if self.model.should_quit {\n                break;\n            }\n\n            // Small sleep to prevent busy waiting\n            tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;\n        }\n\n        Ok(())\n    }\n\n    /// Render the TUI using the provided model and frame.\n    ///\n    /// This function handles the layout and rendering of all components based on the current state.\n    /// It divides the terminal into sections for the tab bar, content area, and status bar,\n    /// and renders the appropriate widgets for the active tab.\n    ///\n    /// # Arguments\n    ///\n    /// * `model` - The current application state model\n    /// * `frame` - The frame to render the UI components onto\n    ///\n    fn render_with_model(model: &Model, frame: &mut Frame) {\n        let area = frame.area();\n\n        // ~~~ Main layout ~~~\n        let main_layout = Layout::default()\n            .direction(Direction::Vertical)\n            .constraints([\n                Constraint::Length(3), // Tab bar\n                Constraint::Min(0),    // Content\n                Constraint::Length(3), // Status bar\n            ])\n            .split(area);\n\n        // Tab bar\n        Self::render_tab_bar_static(model, frame, main_layout[0]);\n\n        // Current tab content\n        match model.current_tab {\n            Tab::FileTree => {\n                let widget = FileSelectionWidget::new(model);\n                let mut state = ();\n                frame.render_stateful_widget(widget, main_layout[1], &mut state);\n            }\n            Tab::Settings => {\n                let widget = SettingsWidget::new(model);\n                let mut state = ();\n                frame.render_stateful_widget(widget, main_layout[1], &mut state);\n            }\n            Tab::Statistics => match model.statistics.view {\n                StatisticsView::Overview => {\n                    let widget = StatisticsOverviewWidget::new(model);\n                    frame.render_widget(widget, main_layout[1]);\n                }\n                StatisticsView::TokenMap => {\n                    let widget = StatisticsTokenMapWidget::new(model);\n                    let mut state = ();\n                    frame.render_stateful_widget(widget, main_layout[1], &mut state);\n                }\n                StatisticsView::Extensions => {\n                    let widget = StatisticsByExtensionWidget::new(model);\n                    let mut state = ();\n                    frame.render_stateful_widget(widget, main_layout[1], &mut state);\n                }\n            },\n            Tab::Template => {\n                let widget = TemplateWidget::new(model);\n                let mut state = TemplateState::from_model(model);\n                frame.render_stateful_widget(widget, main_layout[1], &mut state);\n            }\n            Tab::PromptOutput => {\n                let widget = OutputWidget::new(model);\n                let mut state = ();\n                frame.render_stateful_widget(widget, main_layout[1], &mut state);\n            }\n        }\n\n        // Status bar\n        Self::render_status_bar_static(model, frame, main_layout[2]);\n    }\n\n    /// Handle a key event and return an optional message.\n    ///\n    /// This function processes keyboard input, prioritizing search mode\n    /// when active. It handles global shortcuts for tab switching and quitting,\n    /// as well as delegating tab-specific key events to the appropriate handlers.\n    /// # Arguments\n    ///\n    /// * `key` - The key event to handle.\n    ///\n    /// # Returns\n    ///\n    /// * `Option<Message>` - An optional message to be processed by the main loop.\n    ///   \n    fn handle_key_event(&self, key: KeyEvent) -> Option<Message> {\n        // Check if we're in search mode first - this takes priority over global shortcuts\n        if self.model.file_tree_input_mode == FileTreeInputMode::Search\n            && self.model.current_tab == Tab::FileTree\n        {\n            return self.handle_file_tree_keys(key);\n        }\n\n        // Check if we're in template editing mode - ESC should exit editing mode, not quit app\n        if self.model.current_tab == Tab::Template && self.model.template.is_in_editing_mode() {\n            if key.code == KeyCode::Esc {\n                return Some(Message::SetTemplateFocusMode(FocusMode::Normal));\n            }\n            // In editing modes, delegate to template handler\n            return self.handle_template_keys(key);\n        }\n\n        // Global shortcuts (only when not in search mode or template editing mode)\n        match key.code {\n            KeyCode::Char('q') if key.modifiers.contains(KeyModifiers::CONTROL) => {\n                return Some(Message::Quit);\n            }\n            KeyCode::Esc => return Some(Message::Quit),\n            KeyCode::Char('1') => return Some(Message::SwitchTab(Tab::FileTree)),\n            KeyCode::Char('2') => return Some(Message::SwitchTab(Tab::Settings)),\n            KeyCode::Char('3') => return Some(Message::SwitchTab(Tab::Statistics)),\n            KeyCode::Char('4') => return Some(Message::SwitchTab(Tab::Template)),\n            KeyCode::Char('5') => return Some(Message::SwitchTab(Tab::PromptOutput)),\n            KeyCode::Tab if !key.modifiers.contains(KeyModifiers::SHIFT) => {\n                // Cycle through tabs: Selection -> Settings -> Statistics -> Template -> Output -> Selection\n                let next_tab = match self.model.current_tab {\n                    Tab::FileTree => Tab::Settings,\n                    Tab::Settings => Tab::Statistics,\n                    Tab::Statistics => Tab::Template,\n                    Tab::Template => Tab::PromptOutput,\n                    Tab::PromptOutput => Tab::FileTree,\n                };\n                return Some(Message::SwitchTab(next_tab));\n            }\n            KeyCode::BackTab | KeyCode::Tab if key.modifiers.contains(KeyModifiers::SHIFT) => {\n                // Cycle through tabs in reverse: Selection <- Settings <- Statistics <- Template <- Output <- Selection\n                let prev_tab = match self.model.current_tab {\n                    Tab::FileTree => Tab::PromptOutput,\n                    Tab::Settings => Tab::FileTree,\n                    Tab::Statistics => Tab::Settings,\n                    Tab::Template => Tab::Statistics,\n                    Tab::PromptOutput => Tab::Template,\n                };\n                return Some(Message::SwitchTab(prev_tab));\n            }\n            _ => {}\n        }\n\n        // Tab-specific shortcuts\n        match self.model.current_tab {\n            Tab::FileTree => self.handle_file_tree_keys(key),\n            Tab::Settings => self.handle_settings_keys(key),\n            Tab::Statistics => self.handle_statistics_keys(key),\n            Tab::Template => self.handle_template_keys(key),\n            Tab::PromptOutput => self.handle_prompt_output_keys(key),\n        }\n    }\n\n    fn handle_file_tree_keys(&self, key: KeyEvent) -> Option<Message> {\n        // Pure logic in TUI - no direct widget calls (Elm/Redux pattern)\n        if self.model.file_tree_input_mode == FileTreeInputMode::Search {\n            match key.code {\n                KeyCode::Esc => Some(Message::ExitSearchMode),\n                KeyCode::Enter => {\n                    // Apply search and exit search mode\n                    Some(Message::ExitSearchMode)\n                }\n                KeyCode::Backspace => {\n                    let mut query = self.model.search_query.clone();\n                    query.pop();\n                    Some(Message::UpdateSearchQuery(query))\n                }\n                KeyCode::Char(c) => {\n                    let mut query = self.model.search_query.clone();\n                    query.push(c);\n                    Some(Message::UpdateSearchQuery(query))\n                }\n                _ => None,\n            }\n        } else {\n            // Normal navigation mode\n            match key.code {\n                KeyCode::Up => Some(Message::MoveTreeCursor(-1)),\n                KeyCode::Down => Some(Message::MoveTreeCursor(1)),\n                KeyCode::PageUp => Some(Message::MoveTreeCursor(-10)),\n                KeyCode::PageDown => Some(Message::MoveTreeCursor(10)),\n                KeyCode::Home => Some(Message::MoveTreeCursor(-9999)),\n                KeyCode::End => Some(Message::MoveTreeCursor(9999)),\n                KeyCode::Char(' ') => Some(Message::ToggleFileSelection(self.model.tree_cursor)),\n                KeyCode::Enter => Some(Message::RunAnalysis),\n                KeyCode::Right => Some(Message::ExpandDirectory(self.model.tree_cursor)),\n                KeyCode::Left => Some(Message::CollapseDirectory(self.model.tree_cursor)),\n                KeyCode::Char('/') => Some(Message::EnterSearchMode),\n                KeyCode::Char('s') | KeyCode::Char('S') => Some(Message::EnterSearchMode),\n                KeyCode::Char('r') | KeyCode::Char('R') => Some(Message::RefreshFileTree),\n                _ => None,\n            }\n        }\n    }\n\n    fn handle_settings_keys(&self, key: KeyEvent) -> Option<Message> {\n        match key.code {\n            KeyCode::Up => Some(Message::MoveSettingsCursor(-1)),\n            KeyCode::Down => Some(Message::MoveSettingsCursor(1)),\n            KeyCode::Char(' ') => Some(Message::ToggleSetting(self.model.settings.settings_cursor)),\n            KeyCode::Left | KeyCode::Right => {\n                Some(Message::CycleSetting(self.model.settings.settings_cursor))\n            }\n            KeyCode::Enter => Some(Message::RunAnalysis),\n            _ => None,\n        }\n    }\n\n    fn handle_statistics_keys(&self, key: KeyEvent) -> Option<Message> {\n        match key.code {\n            KeyCode::Enter => Some(Message::RunAnalysis),\n            KeyCode::Left => Some(Message::CycleStatisticsView(-1)), // Previous view\n            KeyCode::Right => Some(Message::CycleStatisticsView(1)), // Next view\n            KeyCode::Up => Some(Message::ScrollStatistics(-1)),\n            KeyCode::Down => Some(Message::ScrollStatistics(1)),\n            KeyCode::PageUp => Some(Message::ScrollStatistics(-5)),\n            KeyCode::PageDown => Some(Message::ScrollStatistics(5)),\n            KeyCode::Home => Some(Message::ScrollStatistics(-9999)),\n            KeyCode::End => Some(Message::ScrollStatistics(9999)),\n            _ => None,\n        }\n    }\n\n    fn handle_template_keys(&self, key: KeyEvent) -> Option<Message> {\n        let is_in_editing_mode = self.model.template.is_in_editing_mode();\n        let current_focus = self.model.template.get_focus();\n\n        // Handle ESC key to exit editing modes\n        if key.code == KeyCode::Esc && is_in_editing_mode {\n            return Some(Message::SetTemplateFocusMode(FocusMode::Normal));\n        }\n\n        if is_in_editing_mode {\n            match current_focus {\n                TemplateFocus::Editor => {\n                    return Some(Message::TemplateEditorInput(key));\n                }\n                TemplateFocus::Variables => {\n                    if self.model.template.variables.is_editing() {\n                        // Currently editing a variable value\n                        match key.code {\n                            KeyCode::Char(c) => return Some(Message::VariableInputChar(c)),\n                            KeyCode::Backspace => return Some(Message::VariableInputBackspace),\n                            KeyCode::Enter => return Some(Message::VariableInputEnter),\n                            KeyCode::Esc => return Some(Message::VariableInputCancel),\n                            _ => return None,\n                        }\n                    } else {\n                        // Navigating variables list\n                        match key.code {\n                            KeyCode::Up => return Some(Message::VariableNavigateUp),\n                            KeyCode::Down => return Some(Message::VariableNavigateDown),\n                            KeyCode::Enter | KeyCode::Char(' ') => {\n                                // Start editing the current variable\n                                let variables = self.model.template.get_organized_variables();\n                                if let Some(var) =\n                                    variables.get(self.model.template.variables.cursor)\n                                    && var.category == VariableCategory::Missing\n                                {\n                                    return Some(Message::VariableStartEditing(var.name.clone()));\n                                }\n                                return None;\n                            }\n                            _ => return None,\n                        }\n                    }\n                }\n                _ => {}\n            }\n        }\n\n        // Normal mode: Handle global shortcuts and focus switching\n        match key.code {\n            KeyCode::Char('e') | KeyCode::Char('E') => {\n                return Some(Message::SetTemplateFocus(\n                    TemplateFocus::Editor,\n                    FocusMode::EditingTemplate,\n                ));\n            }\n            KeyCode::Char('v') | KeyCode::Char('V') => {\n                return Some(Message::SetTemplateFocus(\n                    TemplateFocus::Variables,\n                    FocusMode::EditingVariable,\n                ));\n            }\n            KeyCode::Char('p') | KeyCode::Char('P') => {\n                return Some(Message::SetTemplateFocus(\n                    TemplateFocus::Picker,\n                    FocusMode::Normal,\n                ));\n            }\n            KeyCode::Char('s') | KeyCode::Char('S') => {\n                // Save template with timestamp\n                let timestamp = chrono::Utc::now().format(\"%Y%m%d_%H%M%S\");\n                let filename = format!(\"custom_template_{}\", timestamp);\n                return Some(Message::SaveTemplate(filename));\n            }\n            KeyCode::Char('r') | KeyCode::Char('R') => {\n                // Reload default template\n                return Some(Message::ReloadTemplate);\n            }\n            KeyCode::Enter => {\n                // Run analysis\n                return Some(Message::RunAnalysis);\n            }\n            _ => {}\n        }\n\n        // Handle input for focused component in normal mode\n        if current_focus == TemplateFocus::Picker {\n            match key.code {\n                KeyCode::Up => return Some(Message::TemplatePickerMove(-1)),\n                KeyCode::Down => return Some(Message::TemplatePickerMove(1)),\n                KeyCode::Enter | KeyCode::Char('l') | KeyCode::Char('L') | KeyCode::Char(' ') => {\n                    return Some(Message::LoadTemplate);\n                }\n                KeyCode::Char('r') | KeyCode::Char('R') => {\n                    return Some(Message::RefreshTemplates);\n                }\n                _ => {}\n            }\n        }\n\n        None\n    }\n\n    fn handle_prompt_output_keys(&self, key: KeyEvent) -> Option<Message> {\n        match key.code {\n            KeyCode::Up => Some(Message::ScrollOutput(-1)),\n            KeyCode::Down => Some(Message::ScrollOutput(1)),\n            KeyCode::PageUp => Some(Message::ScrollOutput(-10)),\n            KeyCode::PageDown => Some(Message::ScrollOutput(10)),\n            KeyCode::Home => Some(Message::ScrollOutput(-9999)),\n            KeyCode::End => Some(Message::ScrollOutput(9999)),\n            KeyCode::Char('c') | KeyCode::Char('C') => Some(Message::CopyToClipboard),\n            KeyCode::Char('s') | KeyCode::Char('S') => {\n                let timestamp = chrono::Utc::now().format(\"%Y%m%d_%H%M%S\");\n                let filename = format!(\"prompt_{}.md\", timestamp);\n                Some(Message::SaveToFile(filename))\n            }\n            KeyCode::Enter => Some(Message::RunAnalysis),\n            _ => None,\n        }\n    }\n\n    /// Handle a message using the Elm/Redux pattern.\n    /// This uses the pure Model::update() function and executes any side effects.\n    fn handle_message(&mut self, message: Message) -> Result<()> {\n        let (new_model, cmd) = self.model.update(message);\n        self.model = new_model;\n\n        // Execute any side effects\n        self.execute_cmd(cmd)?;\n\n        Ok(())\n    }\n\n    /// Execute a command (side effect) from the Model::update() function.\n    /// This is where all the impure operations happen.\n    fn execute_cmd(&mut self, cmd: Cmd) -> Result<()> {\n        match cmd {\n            Cmd::None => {\n                // No side effect\n            }\n\n            Cmd::RefreshFileTree => {\n                // Always use session-based tree building for proper pattern initialization\n                match build_file_tree_from_session(&mut self.model.session) {\n                    Ok(tree) => {\n                        self.model.file_tree_nodes = tree;\n                        self.model.status_message =\n                            \"File tree loaded with patterns applied and files auto-expanded\"\n                                .to_string();\n                    }\n                    Err(e) => {\n                        self.model.status_message = format!(\"Error loading files: {}\", e);\n                    }\n                }\n            }\n\n            Cmd::RunAnalysis {\n                template_content,\n                user_variables,\n            } => {\n                // Use the current session state (with all user selections)\n                let mut session = self.model.session.clone();\n                let tx = self.message_tx.clone();\n\n                tokio::spawn(async move {\n                    // Set custom template content\n                    session.config.template_str = template_content;\n                    session.config.template_name = \"Custom Template\".to_string();\n\n                    // Transfer user variables from TUI to session config\n                    session.config.user_variables = user_variables;\n\n                    match session.generate_prompt() {\n                        Ok(rendered) => {\n                            // Convert to AnalysisResults format expected by TUI\n                            let token_map_entries = if rendered.token_count > 0 {\n                                if let Some(files) = session.data.files.as_ref() {\n                                    generate_token_map_with_limit(\n                                        files,\n                                        rendered.token_count,\n                                        Some(50),\n                                        Some(0.5),\n                                    )\n                                } else {\n                                    Vec::new()\n                                }\n                            } else {\n                                Vec::new()\n                            };\n\n                            let result = AnalysisResults {\n                                file_count: rendered.files.len(),\n                                token_count: Some(rendered.token_count),\n                                generated_prompt: rendered.prompt,\n                                token_map_entries,\n                            };\n                            let _ = tx.send(Message::AnalysisComplete(result));\n                        }\n                        Err(e) => {\n                            let _ = tx.send(Message::AnalysisError(e.to_string()));\n                        }\n                    }\n                });\n            }\n\n            Cmd::CopyToClipboard(content) => match copy_to_clipboard(&content) {\n                Ok(_) => {\n                    self.model.status_message = \"Copied to clipboard!\".to_string();\n                }\n                Err(e) => {\n                    self.model.status_message = format!(\"Copy failed: {}\", e);\n                }\n            },\n\n            Cmd::SaveToFile { filename, content } => {\n                match save_to_file(std::path::Path::new(&filename), &content) {\n                    Ok(_) => {\n                        self.model.status_message = format!(\"Saved to {}\", filename);\n                    }\n                    Err(e) => {\n                        self.model.status_message = format!(\"Save failed: {}\", e);\n                    }\n                }\n            }\n\n            Cmd::SaveTemplate { filename, content } => {\n                match save_template_to_custom_dir(std::path::Path::new(&filename), &content) {\n                    Ok(_) => {\n                        self.model.status_message = format!(\"Template saved as {}\", filename);\n                        // Refresh templates to show the new one\n                        self.model.template.picker.refresh();\n                    }\n                    Err(e) => {\n                        self.model.status_message = format!(\"Template save failed: {}\", e);\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    fn render_tab_bar_static(model: &Model, frame: &mut Frame, area: Rect) {\n        let tabs = vec![\n            \"1. Selection\",\n            \"2. Settings\",\n            \"3. Statistics\",\n            \"4. Template\",\n            \"5. Output\",\n        ];\n        let selected = match model.current_tab {\n            Tab::FileTree => 0,\n            Tab::Settings => 1,\n            Tab::Statistics => 2,\n            Tab::Template => 3,\n            Tab::PromptOutput => 4,\n        };\n\n        let tabs_widget = Tabs::new(tabs)\n            .block(\n                Block::default()\n                    .borders(Borders::ALL)\n                    .title(\"Code2Prompt TUI\"),\n            )\n            .select(selected)\n            .style(Style::default().fg(Color::White))\n            .highlight_style(\n                Style::default()\n                    .fg(Color::Yellow)\n                    .add_modifier(Modifier::BOLD),\n            );\n\n        frame.render_widget(tabs_widget, area);\n    }\n\n    fn render_status_bar_static(model: &Model, frame: &mut Frame, area: Rect) {\n        let status_text = if !model.status_message.is_empty() {\n            model.status_message.clone()\n        } else {\n            \"Tab/Shift+Tab: Switch tabs | 1/2/3/4: Direct tab | Enter: Run Analysis | Esc/Ctrl+Q: Quit\".to_string()\n        };\n\n        let status_widget = Paragraph::new(status_text)\n            .block(Block::default().borders(Borders::ALL))\n            .style(Style::default().fg(Color::Cyan));\n        frame.render_widget(status_widget, area);\n    }\n\n    /// Convert crossterm KeyEvent to ratatui KeyEvent\n    fn convert_crossterm_key(&self, key: crossterm::event::KeyEvent) -> KeyEvent {\n        use ratatui::crossterm::event::{KeyCode, KeyEventKind, KeyEventState, KeyModifiers};\n\n        KeyEvent {\n            code: match key.code {\n                crossterm::event::KeyCode::Backspace => KeyCode::Backspace,\n                crossterm::event::KeyCode::Enter => KeyCode::Enter,\n                crossterm::event::KeyCode::Left => KeyCode::Left,\n                crossterm::event::KeyCode::Right => KeyCode::Right,\n                crossterm::event::KeyCode::Up => KeyCode::Up,\n                crossterm::event::KeyCode::Down => KeyCode::Down,\n                crossterm::event::KeyCode::Home => KeyCode::Home,\n                crossterm::event::KeyCode::End => KeyCode::End,\n                crossterm::event::KeyCode::PageUp => KeyCode::PageUp,\n                crossterm::event::KeyCode::PageDown => KeyCode::PageDown,\n                crossterm::event::KeyCode::Tab => KeyCode::Tab,\n                crossterm::event::KeyCode::BackTab => KeyCode::BackTab,\n                crossterm::event::KeyCode::Delete => KeyCode::Delete,\n                crossterm::event::KeyCode::Insert => KeyCode::Insert,\n                crossterm::event::KeyCode::F(n) => KeyCode::F(n),\n                crossterm::event::KeyCode::Char(c) => KeyCode::Char(c),\n                crossterm::event::KeyCode::Null => KeyCode::Null,\n                crossterm::event::KeyCode::Esc => KeyCode::Esc,\n                _ => KeyCode::Null, // Simplified for other key codes\n            },\n            modifiers: KeyModifiers::from_bits_truncate(key.modifiers.bits()),\n            kind: match key.kind {\n                crossterm::event::KeyEventKind::Press => KeyEventKind::Press,\n                crossterm::event::KeyEventKind::Repeat => KeyEventKind::Repeat,\n                crossterm::event::KeyEventKind::Release => KeyEventKind::Release,\n            },\n            state: KeyEventState::from_bits_truncate(key.state.bits()),\n        }\n    }\n\n    /// Try to coalesce two messages if they are similar (e.g., scroll events)\n    fn try_coalesce_messages(&self, last_message: &mut Message, new_message: &Message) -> bool {\n        match (last_message, new_message) {\n            (Message::MoveTreeCursor(delta1), Message::MoveTreeCursor(delta2)) => {\n                *delta1 += delta2;\n                true\n            }\n            (Message::MoveSettingsCursor(delta1), Message::MoveSettingsCursor(delta2)) => {\n                *delta1 += delta2;\n                true\n            }\n            (Message::ScrollStatistics(delta1), Message::ScrollStatistics(delta2)) => {\n                *delta1 += delta2;\n                true\n            }\n            (Message::ScrollOutput(delta1), Message::ScrollOutput(delta2)) => {\n                *delta1 += delta2;\n                true\n            }\n            (Message::TemplatePickerMove(delta1), Message::TemplatePickerMove(delta2)) => {\n                *delta1 += delta2;\n                true\n            }\n            _ => false, // Cannot coalesce these messages\n        }\n    }\n}\n\n/// Run the Terminal User Interface.\n///\n/// This is the main entry point for the TUI mode. It parses command-line arguments,\n/// initializes the TUI application, and runs the main event loop until the user exits.\n///\n/// # Returns\n///\n/// * `Result<()>` - Ok on successful exit, Err if initialization or runtime errors occur\n///\n/// # Errors\n///\n/// Returns an error if the TUI cannot be initialized or if runtime errors occur during execution.\npub async fn run_tui(session: Code2PromptSession) -> Result<()> {\n    let mut app = TuiApp::new(session)?;\n\n    let result = app.run().await;\n\n    // Clean up terminal\n    restore_terminal()?;\n\n    result\n}\n\nfn init_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {\n    enable_raw_mode()?;\n    let mut stdout = stdout();\n    execute!(stdout, EnterAlternateScreen)?;\n    let backend = CrosstermBackend::new(stdout);\n    Terminal::new(backend).map_err(Into::into)\n}\n\nfn restore_terminal() -> Result<()> {\n    disable_raw_mode()?;\n    execute!(stdout(), LeaveAlternateScreen)?;\n    Ok(())\n}\n"
  },
  {
    "path": "crates/code2prompt/src/utils.rs",
    "content": "//! Utility functions for the TUI application.\n//!\n//! This module contains helper functions for building file trees,\n//! managing file operations, and other utility functions used throughout the TUI.\n\nuse crate::model::DisplayFileNode;\nuse anyhow::Result;\nuse code2prompt_core::session::Code2PromptSession;\nuse regex::Regex;\nuse std::path::Path;\n\n/// Build hierarchical file tree from session using traverse_directory with SelectionEngine\npub fn build_file_tree_from_session(\n    session: &mut Code2PromptSession,\n) -> Result<Vec<DisplayFileNode>> {\n    let mut root_nodes = Vec::new();\n\n    // Build root level nodes using ignore crate to respect gitignore\n    use ignore::WalkBuilder;\n    let walker = WalkBuilder::new(&session.config.path)\n        .max_depth(Some(1))\n        .git_ignore(!session.config.no_ignore) // Respect the no_ignore flag\n        .hidden(!session.config.hidden) // Also respect the hidden flag for consistency\n        .build();\n\n    for entry in walker {\n        let entry = entry?;\n        let path = entry.path();\n\n        if path == session.config.path {\n            continue; // Skip root directory itself\n        }\n\n        let mut node = DisplayFileNode::new(path.to_path_buf(), 0);\n\n        // Auto-expand recursively if directory contains selected files\n        if node.is_directory {\n            auto_expand_recursively(&mut node, session);\n        }\n\n        root_nodes.push(node);\n    }\n\n    // Sort root nodes: directories first, then alphabetically\n    root_nodes.sort_by(|a, b| match (a.is_directory, b.is_directory) {\n        (true, false) => std::cmp::Ordering::Less,\n        (false, true) => std::cmp::Ordering::Greater,\n        _ => a.name.cmp(&b.name),\n    });\n\n    Ok(root_nodes)\n}\n\n/// Recursively auto-expand directories that contain selected files\nfn auto_expand_recursively(node: &mut DisplayFileNode, session: &mut Code2PromptSession) {\n    if !node.is_directory {\n        return;\n    }\n\n    if directory_contains_selected_files(&node.path, session) {\n        node.is_expanded = true;\n        // Load children\n        if let Err(e) = node.load_children(session) {\n            eprintln!(\"Warning: Failed to load children for {}: {}\", node.name, e);\n            return;\n        }\n\n        // Recursively auto-expand children\n        for child in &mut node.children {\n            if child.is_directory {\n                auto_expand_recursively(child, session);\n            }\n        }\n    }\n}\n\n/// Check if a directory contains any selected files (helper function)\npub(crate) fn directory_contains_selected_files(\n    dir_path: &Path,\n    session: &mut Code2PromptSession,\n) -> bool {\n    if let Ok(entries) = std::fs::read_dir(dir_path) {\n        for entry in entries.flatten() {\n            let path = entry.path();\n            let relative_path = if let Ok(rel) = path.strip_prefix(&session.config.path) {\n                rel\n            } else {\n                continue;\n            };\n\n            if session.is_file_selected(relative_path) {\n                return true;\n            }\n\n            // Recursively check subdirectories\n            if path.is_dir() && directory_contains_selected_files(&path, session) {\n                return true;\n            }\n        }\n    }\n    false\n}\n\n/// Get visible nodes for display (flattened tree with search filtering)\npub fn get_visible_nodes(\n    nodes: &[DisplayFileNode],\n    search_query: &str,\n    session: &mut Code2PromptSession,\n) -> Vec<DisplayNodeWithSelection> {\n    let mut visible = Vec::new();\n    let search_active = !search_query.is_empty();\n    let matcher = build_query_matcher(search_query);\n    collect_visible_nodes_recursive(nodes, &matcher, session, &mut visible, search_active);\n    visible\n}\n\n/// Simple matcher that supports case-insensitive substring and '*'/'?' wildcards.\nenum QueryMatcher {\n    Substr(String),\n    Regex(Regex),\n}\n\nfn build_query_matcher(raw: &str) -> QueryMatcher {\n    // Trim incidental whitespace for more predictable matches.\n    let raw = raw.trim();\n    let has_wildcards = raw.contains('*') || raw.contains('?');\n    if has_wildcards {\n        // Escape regex meta, then re-introduce wildcards\n        let mut pat = regex::escape(raw);\n        pat = pat.replace(r\"\\*\", \".*\").replace(r\"\\?\", \".\");\n        let anchored = format!(\"(?i)^{}$\", pat); // (?i) = case-insensitive\n        QueryMatcher::Regex(Regex::new(&anchored).unwrap_or_else(|_| Regex::new(\".*\").unwrap()))\n    } else {\n        QueryMatcher::Substr(raw.to_lowercase())\n    }\n}\n\nfn matches(m: &QueryMatcher, text: &str) -> bool {\n    match m {\n        QueryMatcher::Substr(needle) => text.to_lowercase().contains(needle),\n        QueryMatcher::Regex(re) => re.is_match(text),\n    }\n}\n\n/// Node with selection state for display\n#[derive(Debug, Clone)]\npub struct DisplayNodeWithSelection {\n    pub node: DisplayFileNode,\n    pub is_selected: bool,\n}\n\n/// Recursively collect visible nodes\nfn collect_visible_nodes_recursive(\n    nodes: &[DisplayFileNode],\n    matcher: &QueryMatcher,\n    session: &mut Code2PromptSession,\n    visible: &mut Vec<DisplayNodeWithSelection>,\n    search_active: bool,\n) {\n    for node in nodes {\n        // Case-insensitive match on name or full path (with optional wildcards)\n        let matches_current = if matches!(matcher, QueryMatcher::Substr(s) if s.is_empty()) {\n            true\n        } else {\n            matches(matcher, &node.name) || matches(matcher, &node.path.to_string_lossy())\n        };\n\n        if search_active {\n            // In search mode, traverse into directories regardless of expansion\n            let mut child_results: Vec<DisplayNodeWithSelection> = Vec::new();\n            if node.is_directory {\n                let children = get_children_for_search(node, session);\n                collect_visible_nodes_recursive(\n                    &children,\n                    matcher,\n                    session,\n                    &mut child_results,\n                    true,\n                );\n            }\n\n            let include_self = matches_current || !child_results.is_empty();\n\n            if include_self {\n                let relative_path = if let Ok(rel) = node.path.strip_prefix(&session.config.path) {\n                    rel\n                } else {\n                    &node.path\n                };\n                let is_selected = session.is_file_selected(relative_path);\n\n                // Show directories as expanded in search results for better context\n                let mut node_clone = node.clone();\n                if node_clone.is_directory {\n                    node_clone.is_expanded = true;\n                }\n\n                visible.push(DisplayNodeWithSelection {\n                    node: node_clone,\n                    is_selected,\n                });\n\n                visible.extend(child_results);\n            }\n        } else {\n            // Normal mode: only include node if it matches (empty query matches all)\n            if matches_current {\n                let relative_path = if let Ok(rel) = node.path.strip_prefix(&session.config.path) {\n                    rel\n                } else {\n                    &node.path\n                };\n                let is_selected = session.is_file_selected(relative_path);\n\n                visible.push(DisplayNodeWithSelection {\n                    node: node.clone(),\n                    is_selected,\n                });\n\n                // Only descend if the directory is expanded\n                if node.is_directory && node.is_expanded {\n                    collect_visible_nodes_recursive(\n                        &node.children,\n                        matcher,\n                        session,\n                        visible,\n                        false,\n                    );\n                }\n            }\n        }\n    }\n}\n\n/// Save content to a file\npub fn save_to_file(path: &Path, content: &str) -> Result<()> {\n    std::fs::write(path, content)?;\n    Ok(())\n}\n\n/// Format a number with thousand separators according to TokenFormat\n///\n/// - TokenFormat::Raw: returns the number as-is (e.g., \"1234567\")\n/// - TokenFormat::Format: adds separators every 3 digits (e.g., \"1,234,567\")\n///\n/// # Arguments\n/// * `num` - The number to format\n/// * `format` - The token format setting\n///\n/// # Returns\n/// Formatted string representation of the number\npub fn format_number(num: usize, format: &code2prompt_core::tokenizer::TokenFormat) -> String {\n    use code2prompt_core::tokenizer::TokenFormat;\n\n    match format {\n        TokenFormat::Raw => num.to_string(),\n        TokenFormat::Format => {\n            let s = num.to_string();\n            let chars: Vec<char> = s.chars().collect();\n            let mut result = String::new();\n\n            for (i, c) in chars.iter().enumerate() {\n                if i > 0 && (chars.len() - i).is_multiple_of(3) {\n                    result.push(',');\n                }\n                result.push(*c);\n            }\n            result\n        }\n    }\n}\n\n/// Load children for search mode without mutating the original tree\nfn get_children_for_search(\n    node: &DisplayFileNode,\n    session: &mut Code2PromptSession,\n) -> Vec<DisplayFileNode> {\n    if !node.is_directory {\n        return Vec::new();\n    }\n\n    if node.children_loaded {\n        return node.children.clone();\n    }\n\n    // Load children on the fly without mutating the original tree\n    let mut children: Vec<DisplayFileNode> = Vec::new();\n\n    // Use ignore crate to respect gitignore\n    use ignore::WalkBuilder;\n    let walker = WalkBuilder::new(&node.path)\n        .max_depth(Some(1))\n        .git_ignore(!session.config.no_ignore) // Respect the no_ignore flag\n        .hidden(!session.config.hidden) // Also respect the hidden flag for consistency\n        .build();\n\n    for entry in walker.flatten() {\n        let path = entry.path();\n        if path == node.path {\n            continue;\n        }\n\n        let mut child = DisplayFileNode::new(path.to_path_buf(), node.level + 1);\n\n        // Auto-expand if contains selected files\n        if child.is_directory && directory_contains_selected_files(&child.path, session) {\n            child.is_expanded = true;\n        }\n\n        children.push(child);\n    }\n\n    // Sort children: directories first, then alphabetically\n    children.sort_by(|a, b| match (a.is_directory, b.is_directory) {\n        (true, false) => std::cmp::Ordering::Less,\n        (false, true) => std::cmp::Ordering::Greater,\n        _ => a.name.cmp(&b.name),\n    });\n\n    children\n}\n\n/// Save template to custom directory\npub fn save_template_to_custom_dir(filename: &Path, content: &str) -> Result<()> {\n    let templates_dir = if let Some(cfg) = dirs::config_dir() {\n        cfg.join(\"code2prompt\").join(\"templates\")\n    } else {\n        // Fallback to current directory if config_dir not available\n        std::env::current_dir()?.join(\"templates\")\n    };\n\n    std::fs::create_dir_all(&templates_dir)?;\n    let full_path = templates_dir.join(filename);\n    std::fs::write(full_path, content)?;\n    Ok(())\n}\n\n/// Find custom templates and return (display_name, absolute_path).\npub fn load_all_templates() -> Result<Vec<(String, String)>> {\n    let mut out = Vec::new();\n\n    // Candidate roots\n    let mut roots = Vec::new();\n    roots.push(std::env::current_dir()?.join(\"templates\"));\n    if let Some(cfg) = dirs::config_dir() {\n        roots.push(cfg.join(\"code2prompt\").join(\"templates\"));\n    }\n\n    // Accept common template extensions\n    let is_template = |p: &Path| {\n        matches!(\n            p.extension().and_then(|e| e.to_str()),\n            Some(\"hbs\") | Some(\"handlebars\") | Some(\"md\") | Some(\"tmpl\")\n        )\n    };\n\n    for root in roots {\n        if !root.exists() {\n            continue;\n        }\n        for entry in walkdir::WalkDir::new(&root).min_depth(1).max_depth(2) {\n            let entry = entry?;\n            let p = entry.path();\n            if p.is_file() && is_template(p) {\n                let name = p\n                    .file_stem()\n                    .and_then(|s| s.to_str())\n                    .unwrap_or(\"template\")\n                    .to_string();\n                out.push((\n                    name,\n                    p.canonicalize()\n                        .unwrap_or_else(|_| p.to_path_buf())\n                        .to_string_lossy()\n                        .into(),\n                ));\n            }\n        }\n    }\n\n    // De-duplicate (same path could appear twice)\n    // Let the compiler infer tuple types for the sort closure.\n    out.sort_by(|a: &(String, String), b: &(String, String)| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));\n    out.dedup_by(|a, b| a.1 == b.1);\n\n    Ok(out)\n}\n\n/// Ensure a path exists in the file tree by creating missing intermediate nodes\npub fn ensure_path_exists_in_tree(\n    root_nodes: &mut Vec<DisplayFileNode>,\n    target_path: &Path,\n    session: &mut Code2PromptSession,\n) -> Result<()> {\n    let root_path = &session.config.path;\n\n    // Get relative path components\n    let relative_path = if let Ok(rel) = target_path.strip_prefix(root_path) {\n        rel\n    } else {\n        return Ok(()); // Path is not under root, nothing to do\n    };\n\n    let components: Vec<_> = relative_path.components().collect();\n    if components.is_empty() {\n        return Ok(());\n    }\n\n    // Build path incrementally\n    let mut current_path = root_path.to_path_buf();\n    let mut current_nodes = root_nodes;\n\n    for (level, component) in components.into_iter().enumerate() {\n        current_path.push(component);\n\n        // Find or create node at this level\n        let node_name = component.as_os_str().to_string_lossy().to_string();\n\n        // Look for existing node\n        let existing_index = current_nodes.iter().position(|n| n.name == node_name);\n\n        if let Some(index) = existing_index {\n            // Node exists, ensure it's loaded if it's a directory\n            let node = &mut current_nodes[index];\n            if node.is_directory && !node.children_loaded {\n                let _ = node.load_children(session);\n            }\n            current_nodes = &mut current_nodes[index].children;\n        } else {\n            // Node doesn't exist, create it\n            let mut new_node = DisplayFileNode::new(current_path.clone(), level);\n\n            if new_node.is_directory {\n                let _ = new_node.load_children(session);\n            }\n\n            current_nodes.push(new_node);\n\n            // Sort to maintain order\n            current_nodes.sort_by(|a, b| match (a.is_directory, b.is_directory) {\n                (true, false) => std::cmp::Ordering::Less,\n                (false, true) => std::cmp::Ordering::Greater,\n                _ => a.name.cmp(&b.name),\n            });\n\n            // Find the newly inserted node\n            let new_index = current_nodes\n                .iter()\n                .position(|n| n.name == node_name)\n                .unwrap();\n            current_nodes = &mut current_nodes[new_index].children;\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/code2prompt/src/view/formatters.rs",
    "content": "//! Formatting functions for display purposes.\n//!\n//! This module contains pure functions that format data for display in the TUI.\n//! These functions were previously scattered in Model and widgets.\n\nuse code2prompt_core::sort::FileSortMethod;\nuse code2prompt_core::template::OutputFormat;\nuse code2prompt_core::tokenizer::TokenFormat;\nuse code2prompt_core::{session::Code2PromptSession, tokenizer::TokenizerType};\n\nuse crate::model::{SettingKey, SettingType, SettingsGroup, SettingsItem};\n\n/// Format settings groups for display\npub fn format_settings_groups(session: &Code2PromptSession) -> Vec<SettingsGroup> {\n    vec![\n        SettingsGroup {\n            name: \"Output Format\".to_string(),\n            items: vec![\n                SettingsItem {\n                    key: SettingKey::LineNumbers,\n                    name: \"Line Numbers\".to_string(),\n                    description: \"Show line numbers in output\".to_string(),\n                    setting_type: SettingType::Boolean(session.config.line_numbers),\n                },\n                SettingsItem {\n                    key: SettingKey::AbsolutePaths,\n                    name: \"Absolute Paths\".to_string(),\n                    description: \"Use absolute instead of relative paths\".to_string(),\n                    setting_type: SettingType::Boolean(session.config.absolute_path),\n                },\n                SettingsItem {\n                    key: SettingKey::NoCodeblock,\n                    name: \"No Codeblock\".to_string(),\n                    description: \"Don't wrap code in markdown blocks\".to_string(),\n                    setting_type: SettingType::Boolean(session.config.no_codeblock),\n                },\n                SettingsItem {\n                    key: SettingKey::OutputFormat,\n                    name: \"Output Format\".to_string(),\n                    description: \"Format for generated output\".to_string(),\n                    setting_type: SettingType::Choice {\n                        options: vec![\n                            \"Markdown\".to_string(),\n                            \"JSON\".to_string(),\n                            \"XML\".to_string(),\n                        ],\n                        selected: match session.config.output_format {\n                            OutputFormat::Markdown => 0,\n                            OutputFormat::Json => 1,\n                            OutputFormat::Xml => 2,\n                        },\n                    },\n                },\n                SettingsItem {\n                    key: SettingKey::TokenFormat,\n                    name: \"Token Format\".to_string(),\n                    description: \"How to display token counts\".to_string(),\n                    setting_type: SettingType::Choice {\n                        options: vec![\n                            TokenFormat::Raw.to_string(),\n                            TokenFormat::Format.to_string(),\n                        ],\n                        selected: match session.config.token_format {\n                            TokenFormat::Raw => 0,\n                            TokenFormat::Format => 1,\n                        },\n                    },\n                },\n                SettingsItem {\n                    key: SettingKey::FullDirectoryTree,\n                    name: \"Full Directory Tree\".to_string(),\n                    description: \"Show complete directory structure\".to_string(),\n                    setting_type: SettingType::Boolean(session.config.full_directory_tree),\n                },\n            ],\n        },\n        SettingsGroup {\n            name: \"Sorting & Organization\".to_string(),\n            items: vec![SettingsItem {\n                key: SettingKey::SortMethod,\n                name: \"Sort Method\".to_string(),\n                description: \"How to sort files in output\".to_string(),\n                setting_type: SettingType::Choice {\n                    options: vec![\n                        FileSortMethod::NameAsc.to_string(),\n                        FileSortMethod::NameDesc.to_string(),\n                        FileSortMethod::DateAsc.to_string(),\n                        FileSortMethod::DateDesc.to_string(),\n                    ],\n                    selected: match session.config.sort_method {\n                        Some(FileSortMethod::NameAsc) => 0,\n                        Some(FileSortMethod::NameDesc) => 1,\n                        Some(FileSortMethod::DateAsc) => 2,\n                        Some(FileSortMethod::DateDesc) => 3,\n                        None => 0,\n                    },\n                },\n            }],\n        },\n        SettingsGroup {\n            name: \"Tokenizer & Encoding\".to_string(),\n            items: vec![SettingsItem {\n                key: SettingKey::TokenizerType,\n                name: \"Tokenizer Type\".to_string(),\n                description: \"Encoding method for token counting\".to_string(),\n                setting_type: SettingType::Choice {\n                    options: vec![\n                        TokenizerType::Cl100kBase.to_string(),\n                        TokenizerType::O200kBase.to_string(),\n                        TokenizerType::P50kBase.to_string(),\n                        TokenizerType::P50kEdit.to_string(),\n                        TokenizerType::R50kBase.to_string(),\n                    ],\n                    selected: match session.config.encoding {\n                        TokenizerType::Cl100kBase => 0,\n                        TokenizerType::O200kBase => 1,\n                        TokenizerType::P50kBase => 2,\n                        TokenizerType::P50kEdit => 3,\n                        TokenizerType::R50kBase => 4,\n                    },\n                },\n            }],\n        },\n        SettingsGroup {\n            name: \"Git Integration\".to_string(),\n            items: vec![SettingsItem {\n                key: SettingKey::GitDiff,\n                name: \"Git Diff\".to_string(),\n                description: \"Include git diff in output\".to_string(),\n                setting_type: SettingType::Boolean(session.config.diff_enabled),\n            }],\n        },\n        SettingsGroup {\n            name: \"File Selection\".to_string(),\n            items: vec![\n                SettingsItem {\n                    key: SettingKey::FollowSymlinks,\n                    name: \"Follow Symlinks\".to_string(),\n                    description: \"Follow symbolic links\".to_string(),\n                    setting_type: SettingType::Boolean(session.config.follow_symlinks),\n                },\n                SettingsItem {\n                    key: SettingKey::HiddenFiles,\n                    name: \"Hidden Files\".to_string(),\n                    description: \"Include hidden files and directories\".to_string(),\n                    setting_type: SettingType::Boolean(session.config.hidden),\n                },\n                SettingsItem {\n                    key: SettingKey::NoIgnore,\n                    name: \"No Ignore\".to_string(),\n                    description: \"Ignore .gitignore rules\".to_string(),\n                    setting_type: SettingType::Boolean(session.config.no_ignore),\n                },\n                SettingsItem {\n                    key: SettingKey::Deselected,\n                    name: \"Deselected by Default\".to_string(),\n                    description: \"Start with all files deselected\".to_string(),\n                    setting_type: SettingType::Boolean(session.config.deselected),\n                },\n            ],\n        },\n    ]\n}\n"
  },
  {
    "path": "crates/code2prompt/src/view/mod.rs",
    "content": "//! View layer for the TUI application.\n//!\n//! This module contains all the formatting and display logic that was previously\n//! mixed into the Model and widgets. It provides pure functions that take data\n//! and return formatted strings or display structures.\n\npub mod formatters;\n\npub use formatters::*;\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/file_selection.rs",
    "content": "//! File selection widget for directory tree navigation and file selection.\n\nuse crate::model::Model;\nuse ratatui::{\n    prelude::*,\n    widgets::{Block, Borders, List, ListItem, Paragraph},\n};\n\n/// State for the file selection widget - no longer needed, read directly from Model\npub type FileSelectionState = ();\n\n/// Widget for file selection with directory tree, search, and filter patterns\npub struct FileSelectionWidget<'a> {\n    pub model: &'a Model,\n}\n\nimpl<'a> FileSelectionWidget<'a> {\n    pub fn new(model: &'a Model) -> Self {\n        Self { model }\n    }\n}\n\nimpl<'a> StatefulWidget for FileSelectionWidget<'a> {\n    type State = FileSelectionState;\n\n    fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {\n        let layout = Layout::default()\n            .direction(Direction::Vertical)\n            .constraints([\n                Constraint::Min(0),    // File tree\n                Constraint::Length(3), // Search bar\n                Constraint::Length(3), // Pattern info\n                Constraint::Length(3), // Instructions\n            ])\n            .split(area);\n\n        // File tree with scroll support - use new session-based approach\n        let mut session_clone = self.model.session.clone();\n        let visible_nodes = crate::utils::get_visible_nodes(\n            &self.model.file_tree_nodes,\n            &self.model.search_query,\n            &mut session_clone,\n        );\n        let total_nodes = visible_nodes.len();\n\n        // Calculate viewport dimensions\n        let tree_area = layout[0];\n        let content_height = tree_area.height.saturating_sub(2).max(1) as usize; // Account for borders, keep >= 1\n\n        // Derive a local, clamped scroll that keeps the cursor visible\n        let cursor = self.model.tree_cursor.min(total_nodes.saturating_sub(1));\n        let mut scroll_start = self.model.file_tree_scroll as usize;\n        if cursor < scroll_start {\n            scroll_start = cursor;\n        } else if cursor >= scroll_start.saturating_add(content_height) {\n            scroll_start = cursor.saturating_add(1).saturating_sub(content_height);\n        }\n        let max_scroll = total_nodes.saturating_sub(content_height);\n        scroll_start = scroll_start.min(max_scroll);\n\n        let scroll_end = (scroll_start + content_height).min(total_nodes);\n\n        // Create items only for visible viewport\n        let items: Vec<ListItem> = visible_nodes\n            .iter()\n            .enumerate()\n            .skip(scroll_start)\n            .take(content_height)\n            .map(|(i, display_node)| {\n                let node = &display_node.node;\n                let is_selected = display_node.is_selected;\n\n                let indent = \"  \".repeat(node.level);\n                let icon = if node.is_directory {\n                    if node.is_expanded { \"📂\" } else { \"📁\" }\n                } else {\n                    \"📄\"\n                };\n                let checkbox = if is_selected { \"☑\" } else { \"☐\" };\n\n                let content = format!(\"{}{} {} {}\", indent, icon, checkbox, node.name);\n                let mut style = Style::default();\n\n                // Adjust cursor position for viewport\n                if i == cursor {\n                    style = style.bg(Color::Blue).fg(Color::White);\n                }\n\n                if is_selected {\n                    style = style.fg(Color::Green);\n                }\n\n                ListItem::new(content).style(style)\n            })\n            .collect();\n\n        // Create title with scroll indicator\n        let scroll_indicator = if total_nodes > content_height {\n            let current_start = scroll_start + 1;\n            let current_end = scroll_end;\n            format!(\n                \"Files ({}) | Showing {}-{} of {}\",\n                total_nodes, current_start, current_end, total_nodes\n            )\n        } else {\n            format!(\"Files ({})\", total_nodes)\n        };\n\n        let tree_widget = List::new(items)\n            .block(\n                Block::default()\n                    .borders(Borders::ALL)\n                    .title(scroll_indicator),\n            )\n            .highlight_style(Style::default().bg(Color::Blue).fg(Color::White));\n\n        Widget::render(tree_widget, layout[0], buf);\n\n        // Search bar - read directly from Model\n        let title_spans = vec![\n            Span::styled(\n                \"s\",\n                Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),\n            ),\n            Span::styled(\"earch\", Style::default().fg(Color::White)),\n            Span::styled(\" (text or * ? wildcards)\", Style::default().fg(Color::Gray)),\n        ];\n\n        let search_widget = Paragraph::new(self.model.search_query.as_str())\n            .block(\n                Block::default()\n                    .borders(Borders::ALL)\n                    .title(Line::from(title_spans)),\n            )\n            .style(\n                Style::default().fg(if self.model.search_query.contains('*') {\n                    Color::Yellow\n                } else {\n                    Color::Green\n                }),\n            );\n        Widget::render(search_widget, layout[1], buf);\n\n        // Pattern info\n        let include_text = if self.model.session.config.include_patterns.is_empty() {\n            \"All files\".to_string()\n        } else {\n            format!(\n                \"Include: {}\",\n                self.model.session.config.include_patterns.join(\", \")\n            )\n        };\n        let exclude_text = if self.model.session.config.exclude_patterns.is_empty() {\n            \"\".to_string()\n        } else {\n            format!(\n                \" | Exclude: {}\",\n                self.model.session.config.exclude_patterns.join(\", \")\n            )\n        };\n        let pattern_info = format!(\"{}{}\", include_text, exclude_text);\n\n        let pattern_widget = Paragraph::new(pattern_info)\n            .block(\n                Block::default()\n                    .borders(Borders::ALL)\n                    .title(\"Filter Patterns\"),\n            )\n            .style(Style::default().fg(Color::Cyan));\n        Widget::render(pattern_widget, layout[2], buf);\n\n        // Instructions\n        let instructions = Paragraph::new(\n            \"Enter: Run Analysis | ↑↓: Navigate | Space: Select/Deselect | ←→: Expand/Collapse | PgUp/PgDn: Scroll | S: Search Mode | Esc: Exit\"\n        )\n        .block(Block::default().borders(Borders::ALL).title(\"Controls\"))\n        .style(Style::default().fg(Color::Gray));\n        Widget::render(instructions, layout[3], buf);\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/mod.rs",
    "content": "//! Widget components for the TUI interface.\n//!\n//! This module contains all the widget implementations using Ratatui's native widget system.\n//! Each widget is responsible for rendering a specific part of the UI and managing its own state.\n\npub mod file_selection;\npub mod output;\npub mod settings;\npub mod statistics_by_extension;\npub mod statistics_overview;\npub mod statistics_token_map;\npub mod template;\n\npub use file_selection::FileSelectionWidget;\npub use output::OutputWidget;\npub use settings::SettingsWidget;\npub use statistics_by_extension::StatisticsByExtensionWidget;\npub use statistics_overview::StatisticsOverviewWidget;\npub use statistics_token_map::StatisticsTokenMapWidget;\npub use template::TemplateWidget;\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/output.rs",
    "content": "//! Output widget for displaying generated prompt with scrolling capability.\n\nuse crate::model::Model;\nuse ratatui::{\n    prelude::*,\n    widgets::{Block, Borders, Paragraph, Wrap},\n};\n\n/// State for the output widget - no longer needed, read directly from Model\npub type OutputState = ();\n\n/// Widget for output display with scrolling\npub struct OutputWidget<'a> {\n    pub model: &'a Model,\n}\n\nimpl<'a> OutputWidget<'a> {\n    pub fn new(model: &'a Model) -> Self {\n        Self { model }\n    }\n}\n\nimpl<'a> StatefulWidget for OutputWidget<'a> {\n    type State = OutputState;\n\n    fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {\n        let layout = Layout::default()\n            .direction(Direction::Vertical)\n            .constraints([\n                Constraint::Length(3), // Info bar\n                Constraint::Min(0),    // Prompt content\n                Constraint::Length(3), // Controls\n            ])\n            .split(area);\n\n        // Simplified status bar - focus only on prompt availability\n        let info_text = if self.model.prompt_output.analysis_in_progress {\n            \"Generating prompt...\".to_string()\n        } else if let Some(error) = &self.model.prompt_output.analysis_error {\n            format!(\"Generation failed: {}\", error)\n        } else if self.model.prompt_output.generated_prompt.is_some() {\n            \"✓ Prompt ready! Copy (C) or Save (S)\".to_string()\n        } else {\n            \"Press Enter to generate prompt from selected files\".to_string()\n        };\n\n        let info_widget = Paragraph::new(info_text)\n            .block(\n                Block::default()\n                    .borders(Borders::ALL)\n                    .title(\"Generated Prompt\"),\n            )\n            .style(if self.model.prompt_output.analysis_error.is_some() {\n                Style::default().fg(Color::Red)\n            } else if self.model.prompt_output.analysis_in_progress {\n                Style::default().fg(Color::Yellow)\n            } else {\n                Style::default().fg(Color::Green)\n            });\n        Widget::render(info_widget, layout[0], buf);\n\n        // Prompt content\n        let content = if self.model.prompt_output.analysis_in_progress {\n            \"Generating prompt...\".to_string()\n        } else if let Some(prompt) = &self.model.prompt_output.generated_prompt {\n            prompt.clone()\n        } else {\n            \"Press <Enter> to run analysis and generate prompt.\\n\\nSelected files will be processed according to your settings.\".to_string()\n        };\n\n        // Compute viewport-aware scroll\n        let content_height = layout[1].height.saturating_sub(2).max(1) as usize; // borders\n        let (display_scroll, scroll_info) =\n            if let Some(prompt) = &self.model.prompt_output.generated_prompt {\n                let total_lines = prompt.lines().count();\n                let max_scroll = total_lines.saturating_sub(content_height);\n                let ds = self\n                    .model\n                    .prompt_output\n                    .output_scroll\n                    .min(max_scroll as u16);\n                let current_line = ds as usize + 1;\n                (\n                    ds,\n                    format!(\"Generated Prompt (Line {}/{})\", current_line, total_lines),\n                )\n            } else {\n                (\n                    self.model.prompt_output.output_scroll,\n                    \"Generated Prompt\".to_string(),\n                )\n            };\n\n        let prompt_widget = Paragraph::new(content)\n            .block(Block::default().borders(Borders::ALL).title(scroll_info))\n            .wrap(Wrap { trim: false })\n            .scroll((display_scroll, 0));\n        Widget::render(prompt_widget, layout[1], buf);\n\n        // Controls\n        let controls_text = if self.model.prompt_output.generated_prompt.is_some() {\n            \"↑↓/PgUp/PgDn: Scroll | C: Copy | S: Save | Enter: Re-run\"\n        } else {\n            \"Enter: Run Analysis\"\n        };\n\n        let controls_widget = Paragraph::new(controls_text)\n            .block(Block::default().borders(Borders::ALL).title(\"Controls\"))\n            .style(Style::default().fg(Color::Gray));\n        Widget::render(controls_widget, layout[2], buf);\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/settings.rs",
    "content": "//! Settings widget for configuration management.\n\nuse crate::model::Model;\nuse ratatui::{\n    prelude::*,\n    widgets::{Block, Borders, List, ListItem, Paragraph},\n};\n\n/// State for the settings widget - no longer needed, read directly from Model\npub type SettingsState = ();\n\n/// Widget for settings configuration\npub struct SettingsWidget<'a> {\n    pub model: &'a Model,\n}\n\nimpl<'a> SettingsWidget<'a> {\n    pub fn new(model: &'a Model) -> Self {\n        Self { model }\n    }\n}\n\nimpl<'a> StatefulWidget for SettingsWidget<'a> {\n    type State = SettingsState;\n\n    fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {\n        let settings_groups = self.model.get_settings_groups();\n\n        let layout = Layout::default()\n            .direction(Direction::Vertical)\n            .constraints([\n                Constraint::Min(0),    // Settings list\n                Constraint::Length(3), // Instructions\n            ])\n            .split(area);\n\n        // Build grouped settings display\n        let mut items: Vec<ListItem> = Vec::new();\n        let mut item_index = 0;\n\n        for group in &settings_groups {\n            // Group header\n            items.push(\n                ListItem::new(format!(\"── {} ──\", group.name)).style(\n                    Style::default()\n                        .fg(Color::Yellow)\n                        .add_modifier(Modifier::BOLD),\n                ),\n            );\n\n            // Group items\n            for item in &group.items {\n                let value_display = match &item.setting_type {\n                    crate::model::SettingType::Boolean(val) => {\n                        if *val {\n                            \"[●] ON\".to_string()\n                        } else {\n                            \"[○] OFF\".to_string()\n                        }\n                    }\n                    crate::model::SettingType::Choice { options, selected } => {\n                        let current = options.get(*selected).cloned().unwrap_or_default();\n                        let total = options.len();\n                        format!(\"[▼ {} ({}/{})]\", current, selected + 1, total)\n                    }\n                };\n\n                // Better aligned layout: Name (20 chars) | Value (15 chars) | Description\n                let content = format!(\n                    \"  {:<20} {:<15} {}\",\n                    item.name, value_display, item.description\n                );\n                let mut style = Style::default();\n\n                // Read cursor directly from Model\n                if item_index == self.model.settings.settings_cursor {\n                    style = style\n                        .bg(Color::Blue)\n                        .fg(Color::White)\n                        .add_modifier(Modifier::BOLD);\n                }\n\n                // Color based on setting type\n                match &item.setting_type {\n                    crate::model::SettingType::Boolean(true) => {\n                        style = style.fg(Color::Green);\n                    }\n                    crate::model::SettingType::Boolean(false) => {\n                        style = style.fg(Color::Red);\n                    }\n                    crate::model::SettingType::Choice { .. } => {\n                        style = style.fg(Color::Cyan);\n                    }\n                }\n\n                items.push(ListItem::new(content).style(style));\n                item_index += 1;\n            }\n\n            // Add spacing between groups\n            items.push(ListItem::new(\"\"));\n        }\n\n        let settings_widget = List::new(items)\n            .block(Block::default().borders(Borders::ALL).title(\"Settings\"))\n            .highlight_style(Style::default().bg(Color::Blue).fg(Color::White));\n\n        Widget::render(settings_widget, layout[0], buf);\n\n        // Instructions\n        let instructions = Paragraph::new(\n            \"Enter: Run Analysis | ↑↓: Navigate | Space: Toggle | ←→: Cycle Options\",\n        )\n        .block(Block::default().borders(Borders::ALL).title(\"Controls\"))\n        .style(Style::default().fg(Color::Gray));\n        Widget::render(instructions, layout[1], buf);\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/statistics_by_extension.rs",
    "content": "//! Statistics by extension widget for displaying extension-based histogram.\n\nuse crate::model::{Model, StatisticsState};\nuse ratatui::{\n    prelude::*,\n    widgets::{Block, Borders, List, ListItem, Paragraph, Wrap},\n};\n\n/// State for the extension statistics widget - eliminated redundant state\npub type ExtensionState = ();\n\n/// Widget for extension-based statistics display\npub struct StatisticsByExtensionWidget<'a> {\n    pub model: &'a Model,\n}\n\nimpl<'a> StatisticsByExtensionWidget<'a> {\n    pub fn new(model: &'a Model) -> Self {\n        Self { model }\n    }\n}\n\nimpl<'a> StatefulWidget for StatisticsByExtensionWidget<'a> {\n    type State = ExtensionState;\n\n    fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {\n        let layout = Layout::default()\n            .direction(Direction::Vertical)\n            .constraints([\n                Constraint::Min(0),    // Extension statistics content\n                Constraint::Length(3), // Instructions\n            ])\n            .split(area);\n\n        let title = \"📁 By Extension\";\n\n        if self.model.statistics.token_map_entries.is_empty() {\n            let placeholder_text = if self.model.prompt_output.generated_prompt.is_some() {\n                \"\\nNo token map data available.\\n\\nPress Enter to re-run analysis.\"\n            } else {\n                \"\\nRun analysis first to see token breakdown by file extension.\\n\\nPress Enter to run analysis.\"\n            };\n\n            let placeholder_widget = Paragraph::new(placeholder_text)\n                .block(Block::default().borders(Borders::ALL).title(title))\n                .wrap(Wrap { trim: true })\n                .style(Style::default().fg(Color::Gray))\n                .alignment(Alignment::Center);\n\n            Widget::render(placeholder_widget, layout[0], buf);\n\n            // Instructions\n            let instructions =\n                Paragraph::new(\"Enter: Run Analysis | ←→: Switch View | Tab/Shift+Tab: Switch Tab\")\n                    .block(Block::default().borders(Borders::ALL).title(\"Controls\"))\n                    .style(Style::default().fg(Color::Gray));\n            Widget::render(instructions, layout[1], buf);\n            return;\n        }\n\n        // Use business logic from Model - pure Elm/Redux pattern\n        let ext_vec = self.model.statistics.aggregate_by_extension();\n        let total_tokens = self.model.prompt_output.token_count.unwrap_or(0);\n\n        // Calculate viewport for scrolling - read directly from Model\n        let content_height = layout[0].height.saturating_sub(2).max(1) as usize;\n        let total = ext_vec.len();\n        let max_scroll = total.saturating_sub(content_height);\n        let scroll_start = (self.model.statistics.scroll as usize).min(max_scroll);\n        let scroll_end = (scroll_start + content_height).min(total);\n\n        // Calculate dynamic column widths based on available space and content\n        let available_width = layout[0].width.saturating_sub(4) as usize; // Account for borders and padding\n\n        // Calculate maximum widths needed for each column\n        let max_ext_width = ext_vec\n            .iter()\n            .map(|(ext, _, _)| ext.len())\n            .max()\n            .unwrap_or(12)\n            .max(12); // Minimum 12 chars for \"Extension\"\n\n        let max_tokens_width = ext_vec\n            .iter()\n            .map(|(_, tokens, _)| {\n                StatisticsState::format_number(*tokens, &self.model.session.config.token_format)\n                    .len()\n            })\n            .max()\n            .unwrap_or(6)\n            .max(6); // Minimum 6 chars for tokens\n\n        let max_count_width = ext_vec\n            .iter()\n            .map(|(_, _, count)| count.to_string().len())\n            .max()\n            .unwrap_or(3)\n            .max(3); // Minimum 3 chars for count\n\n        // Fixed widths for percentage and separators\n        let percentage_width = 7; // \"(100.0%)\"\n        let separators_width = 8; // \" │ │ \" + \" | \" + \" files\"\n\n        // Calculate remaining space for the progress bar\n        let fixed_content_width = max_ext_width\n            + max_tokens_width\n            + percentage_width\n            + max_count_width\n            + separators_width\n            + 5; // +5 for \"files\"\n        let bar_width = if available_width > fixed_content_width {\n            (available_width - fixed_content_width).clamp(10, 40) // Between 10 and 40 chars\n        } else {\n            15 // Fallback minimum bar width\n        };\n\n        // Create list items with dynamic formatting\n        let items: Vec<ListItem> = ext_vec\n            .iter()\n            .skip(scroll_start)\n            .take(content_height)\n            .map(|(extension, tokens, count)| {\n                let percentage = if total_tokens > 0 {\n                    (*tokens as f64 / total_tokens as f64) * 100.0\n                } else {\n                    0.0\n                };\n\n                // Create visual bar with calculated width\n                let filled_chars = ((percentage / 100.0) * bar_width as f64) as usize;\n                let bar = format!(\n                    \"{}{}\",\n                    \"█\".repeat(filled_chars),\n                    \"░\".repeat(bar_width.saturating_sub(filled_chars))\n                );\n\n                // Choose color based on extension\n                let color = match extension.as_str() {\n                    \".rs\" => Color::LightRed,\n                    \".md\" | \".txt\" | \".rst\" => Color::Green,\n                    \".toml\" | \".json\" | \".yaml\" | \".yml\" => Color::Magenta,\n                    \".js\" | \".ts\" | \".jsx\" | \".tsx\" => Color::Cyan,\n                    \".py\" => Color::LightYellow,\n                    \".go\" => Color::LightBlue,\n                    \".java\" | \".kt\" => Color::Red,\n                    \".cpp\" | \".c\" | \".h\" => Color::Blue,\n                    _ => Color::White,\n                };\n\n                // Format with dynamic column widths\n                let formatted_tokens = StatisticsState::format_number(\n                    *tokens,\n                    &self.model.session.config.token_format,\n                );\n                let content = format!(\n                    \"{:<width_ext$} │{}│ {:>width_tokens$} ({:>4.1}%) | {:>width_count$} files\",\n                    extension,\n                    bar,\n                    formatted_tokens,\n                    percentage,\n                    count,\n                    width_ext = max_ext_width,\n                    width_tokens = max_tokens_width,\n                    width_count = max_count_width\n                );\n\n                ListItem::new(content).style(Style::default().fg(color))\n            })\n            .collect();\n\n        // Create title with scroll indicator\n        let scroll_title = if ext_vec.len() > content_height {\n            format!(\n                \"{} | Showing {}-{} of {}\",\n                title,\n                scroll_start + 1,\n                scroll_end,\n                ext_vec.len()\n            )\n        } else {\n            title.to_string()\n        };\n\n        // Add header row for better column alignment\n        let header = format!(\n            \"{:<width_ext$} │{:^width_bar$}│ {:>width_tokens$} {:>7} | {:>width_count$} Files\",\n            \"Extension\",\n            \"Usage\",\n            \"Tokens\",\n            \"Percent\",\n            \"Count\",\n            width_ext = max_ext_width,\n            width_bar = bar_width,\n            width_tokens = max_tokens_width,\n            width_count = max_count_width\n        );\n\n        let mut all_items = vec![\n            ListItem::new(header).style(\n                Style::default()\n                    .fg(Color::Yellow)\n                    .add_modifier(Modifier::BOLD),\n            ),\n            ListItem::new(\"─\".repeat(available_width.min(120)))\n                .style(Style::default().fg(Color::DarkGray)),\n        ];\n        all_items.extend(items);\n\n        let extensions_widget = List::new(all_items)\n            .block(Block::default().borders(Borders::ALL).title(scroll_title))\n            .style(Style::default().fg(Color::White));\n\n        Widget::render(extensions_widget, layout[0], buf);\n\n        // Instructions\n        let instructions = Paragraph::new(\"Enter: Run Analysis | ←→: Switch View | ↑↓/PgUp/PgDn: Scroll | Tab/Shift+Tab: Switch Tab\")\n            .block(Block::default().borders(Borders::ALL).title(\"Controls\"))\n            .style(Style::default().fg(Color::Gray));\n        Widget::render(instructions, layout[1], buf);\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/statistics_overview.rs",
    "content": "//! Statistics overview widget for displaying analysis summary.\nuse crate::model::{Model, StatisticsState};\nuse ratatui::{\n    prelude::*,\n    widgets::{Block, Borders, List, ListItem, Paragraph, Wrap},\n};\n\n/// Widget for statistics overview (stateless)\npub struct StatisticsOverviewWidget<'a> {\n    pub model: &'a Model,\n}\n\nimpl<'a> StatisticsOverviewWidget<'a> {\n    pub fn new(model: &'a Model) -> Self {\n        Self { model }\n    }\n}\n\nimpl<'a> Widget for StatisticsOverviewWidget<'a> {\n    fn render(self, area: Rect, buf: &mut Buffer) {\n        let layout = Layout::default()\n            .direction(Direction::Vertical)\n            .constraints([\n                Constraint::Min(0),    // Statistics content\n                Constraint::Length(3), // Instructions\n            ])\n            .split(area);\n\n        // Check if analysis has been run\n        if self.model.prompt_output.generated_prompt.is_none()\n            && !self.model.prompt_output.analysis_in_progress\n        {\n            // Show placeholder when no analysis has been run\n            let placeholder_text =\n                \"\\nNo analysis data available yet.\\n\\nPress Enter to run analysis.\";\n\n            let placeholder_widget = Paragraph::new(placeholder_text)\n                .block(Block::default().borders(Borders::ALL).title(\"📊 Overview\"))\n                .wrap(Wrap { trim: true })\n                .style(Style::default().fg(Color::Gray))\n                .alignment(Alignment::Center);\n\n            Widget::render(placeholder_widget, layout[0], buf);\n\n            // Instructions for when no analysis is available\n            let instructions = Paragraph::new(\"Enter: Go to Selection | Tab/Shift+Tab: Switch Tab\")\n                .block(Block::default().borders(Borders::ALL).title(\"Controls\"))\n                .style(Style::default().fg(Color::Gray));\n            Widget::render(instructions, layout[1], buf);\n            return;\n        }\n\n        let mut stats_items: Vec<ListItem> = Vec::new();\n\n        // Analysis Status (most important first)\n        let (status_text, status_color) = if self.model.prompt_output.analysis_in_progress {\n            (\"Generating prompt...\".to_string(), Color::Yellow)\n        } else if self.model.prompt_output.analysis_error.is_some() {\n            (\"Analysis failed\".to_string(), Color::Red)\n        } else if self.model.prompt_output.generated_prompt.is_some() {\n            (\"Analysis complete\".to_string(), Color::Green)\n        } else {\n            (\"Ready to analyze\".to_string(), Color::Gray)\n        };\n\n        stats_items.push(\n            ListItem::new(format!(\"Status: {}\", status_text)).style(\n                Style::default()\n                    .fg(status_color)\n                    .add_modifier(Modifier::BOLD),\n            ),\n        );\n\n        if let Some(error) = &self.model.prompt_output.analysis_error {\n            stats_items.push(\n                ListItem::new(format!(\"  Error: {}\", error)).style(Style::default().fg(Color::Red)),\n            );\n        }\n        stats_items.push(ListItem::new(\"\"));\n\n        // File Summary\n        stats_items.push(\n            ListItem::new(\"📁 File Summary\").style(\n                Style::default()\n                    .fg(Color::Cyan)\n                    .add_modifier(Modifier::BOLD),\n            ),\n        );\n\n        let mut session_clone = self.model.session.clone();\n        let selected_count = StatisticsState::count_selected_files(&mut session_clone);\n        let eligible_count = StatisticsState::count_total_files(&self.model.file_tree_nodes);\n        let total_files = self.model.prompt_output.file_count;\n        stats_items.push(ListItem::new(format!(\n            \"  • Selected (current): {} files\",\n            selected_count\n        )));\n        stats_items.push(ListItem::new(format!(\n            \"  • Eligible (current filters): {} files\",\n            eligible_count\n        )));\n        stats_items.push(ListItem::new(format!(\n            \"  • Included (last run): {} files\",\n            total_files\n        )));\n\n        if selected_count > 0 && eligible_count > 0 {\n            let percentage = (selected_count as f64 / eligible_count as f64 * 100.0) as usize;\n            stats_items.push(ListItem::new(format!(\n                \"  • Selection Rate (current): {}%\",\n                percentage\n            )));\n        }\n        stats_items.push(ListItem::new(\"\"));\n\n        // Token Summary\n        stats_items.push(\n            ListItem::new(\"🎯 Token Summary\").style(\n                Style::default()\n                    .fg(Color::Magenta)\n                    .add_modifier(Modifier::BOLD),\n            ),\n        );\n\n        if let Some(token_count) = self.model.prompt_output.token_count {\n            stats_items.push(ListItem::new(format!(\n                \"  • Total Tokens: {}\",\n                StatisticsState::format_number(\n                    token_count,\n                    &self.model.session.config.token_format\n                )\n            )));\n            if selected_count > 0 {\n                let avg_tokens = token_count / selected_count;\n                stats_items.push(ListItem::new(format!(\n                    \"  • Avg per File: {}\",\n                    StatisticsState::format_number(\n                        avg_tokens,\n                        &self.model.session.config.token_format\n                    )\n                )));\n            }\n        } else {\n            stats_items.push(ListItem::new(\"  • Total Tokens: Not calculated\"));\n        }\n        stats_items.push(ListItem::new(\"\"));\n\n        // Configuration Summary\n        stats_items.push(\n            ListItem::new(\"⚙️  Configuration\").style(\n                Style::default()\n                    .fg(Color::Yellow)\n                    .add_modifier(Modifier::BOLD),\n            ),\n        );\n\n        let output_format = match self.model.session.config.output_format {\n            code2prompt_core::template::OutputFormat::Markdown => \"Markdown\",\n            code2prompt_core::template::OutputFormat::Json => \"JSON\",\n            code2prompt_core::template::OutputFormat::Xml => \"XML\",\n        };\n        stats_items.push(ListItem::new(format!(\"  • Output: {}\", output_format)));\n        stats_items.push(ListItem::new(format!(\n            \"  • Line Numbers: {}\",\n            if self.model.session.config.line_numbers {\n                \"On\"\n            } else {\n                \"Off\"\n            }\n        )));\n        stats_items.push(ListItem::new(format!(\n            \"  • Git Diff: {}\",\n            if self.model.session.config.diff_enabled {\n                \"On\"\n            } else {\n                \"Off\"\n            }\n        )));\n\n        let pattern_summary = format!(\n            \"  • Patterns: {} include, {} exclude\",\n            self.model.session.config.include_patterns.len(),\n            self.model.session.config.exclude_patterns.len()\n        );\n        stats_items.push(ListItem::new(pattern_summary));\n\n        let stats_widget = List::new(stats_items)\n            .block(Block::default().borders(Borders::ALL).title(\"📊 Overview\"))\n            .style(Style::default().fg(Color::White));\n\n        Widget::render(stats_widget, layout[0], buf);\n\n        // Instructions\n        let instructions =\n            Paragraph::new(\"Enter: Run Analysis | ←→: Switch View | Tab/Shift+Tab: Switch Tab\")\n                .block(Block::default().borders(Borders::ALL).title(\"Controls\"))\n                .style(Style::default().fg(Color::Gray));\n        Widget::render(instructions, layout[1], buf);\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/statistics_token_map.rs",
    "content": "//! Statistics token map widget for displaying token distribution.\n\nuse crate::model::Model;\nuse crate::token_map::{TuiColor, format_token_map_for_tui};\nuse ratatui::{\n    prelude::*,\n    widgets::{Block, Borders, List, ListItem, Paragraph, Wrap},\n};\n\n/// State for the token map widget - no longer needed, read directly from Model\npub type TokenMapState = ();\n\n/// Widget for token map display\npub struct StatisticsTokenMapWidget<'a> {\n    pub model: &'a Model,\n}\n\nimpl<'a> StatisticsTokenMapWidget<'a> {\n    pub fn new(model: &'a Model) -> Self {\n        Self { model }\n    }\n}\n\nimpl<'a> StatefulWidget for StatisticsTokenMapWidget<'a> {\n    type State = TokenMapState;\n\n    fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {\n        let layout = Layout::default()\n            .direction(Direction::Vertical)\n            .constraints([\n                Constraint::Min(0),    // Token map content\n                Constraint::Length(3), // Instructions\n            ])\n            .split(area);\n\n        let title = \"🗂️  Token Map\";\n\n        if self.model.statistics.token_map_entries.is_empty() {\n            let placeholder_text = if self.model.prompt_output.generated_prompt.is_some() {\n                \"\\nNo token map data available.\\n\\nPress Enter to re-run analysis.\"\n            } else {\n                \"\\nRun analysis first to see token distribution.\\n\\nPress Enter to run analysis.\"\n            };\n\n            let placeholder_widget = Paragraph::new(placeholder_text)\n                .block(Block::default().borders(Borders::ALL).title(title))\n                .wrap(Wrap { trim: true })\n                .style(Style::default().fg(Color::Gray))\n                .alignment(Alignment::Center);\n\n            Widget::render(placeholder_widget, layout[0], buf);\n\n            // Instructions\n            let instructions =\n                Paragraph::new(\"Enter: Run Analysis | ←→: Switch View | Tab/Shift+Tab: Switch Tab\")\n                    .block(Block::default().borders(Borders::ALL).title(\"Controls\"))\n                    .style(Style::default().fg(Color::Gray));\n            Widget::render(instructions, layout[1], buf);\n            return;\n        }\n\n        // Use the shared token map formatting logic from token_map.rs with adaptive layout\n        let total_tokens = self.model.prompt_output.token_count.unwrap_or(0);\n        let terminal_width = area.width as usize;\n        let formatted_lines = format_token_map_for_tui(\n            &self.model.statistics.token_map_entries,\n            total_tokens,\n            terminal_width,\n        );\n\n        // Calculate viewport for scrolling - read directly from Model\n        let content_height = layout[0].height.saturating_sub(2).max(1) as usize; // Account for borders\n        let total = formatted_lines.len();\n        let max_scroll = total.saturating_sub(content_height);\n        let scroll_start = (self.model.statistics.scroll as usize).min(max_scroll);\n        let scroll_end = (scroll_start + content_height).min(formatted_lines.len());\n\n        // Convert formatted lines to ListItems with proper column layout and filename coloring\n        let items: Vec<ListItem> = formatted_lines\n            .iter()\n            .skip(scroll_start)\n            .take(content_height)\n            .map(|line| {\n                // Convert TuiColor to ratatui Color for filename only\n                let name_color = match line.name_color {\n                    TuiColor::White => Color::White,\n                    TuiColor::Gray => Color::Gray,\n                    TuiColor::Red => Color::Red,\n                    TuiColor::Green => Color::Green,\n                    TuiColor::Blue => Color::Blue,\n                    TuiColor::Yellow => Color::Yellow,\n                    TuiColor::Cyan => Color::Cyan,\n                    TuiColor::Magenta => Color::Magenta,\n                    TuiColor::LightRed => Color::LightRed,\n                    TuiColor::LightGreen => Color::LightGreen,\n                    TuiColor::LightBlue => Color::LightBlue,\n                    TuiColor::LightYellow => Color::LightYellow,\n                    TuiColor::LightCyan => Color::LightCyan,\n                    TuiColor::LightMagenta => Color::LightMagenta,\n                };\n\n                // Create spans with proper coloring - only filename gets color, rest is white\n                let spans = vec![\n                    Span::styled(&line.tokens_part, Style::default().fg(Color::White)),\n                    Span::styled(\"   \", Style::default().fg(Color::White)), // spacing\n                    Span::styled(&line.prefix_part, Style::default().fg(Color::White)),\n                    Span::styled(&line.name_part, Style::default().fg(name_color)), // Only filename colored\n                    Span::styled(\" \", Style::default().fg(Color::White)),           // spacing\n                    Span::styled(&line.bar_part, Style::default().fg(Color::White)),\n                    Span::styled(\" \", Style::default().fg(Color::White)), // spacing\n                    Span::styled(&line.percentage_part, Style::default().fg(Color::White)),\n                ];\n\n                ListItem::new(Line::from(spans))\n            })\n            .collect();\n\n        // Create title with scroll indicator\n        let scroll_title = if formatted_lines.len() > content_height {\n            format!(\n                \"{} | Showing {}-{} of {}\",\n                title,\n                scroll_start + 1,\n                scroll_end,\n                formatted_lines.len()\n            )\n        } else {\n            title.to_string()\n        };\n\n        let token_map_widget =\n            List::new(items).block(Block::default().borders(Borders::ALL).title(scroll_title));\n\n        Widget::render(token_map_widget, layout[0], buf);\n\n        // Instructions\n        let instructions = Paragraph::new(\"Enter: Run Analysis | ←→: Switch View | ↑↓/PgUp/PgDn: Scroll | Tab/Shift+Tab: Switch Tab\")\n            .block(Block::default().borders(Borders::ALL).title(\"Controls\"))\n            .style(Style::default().fg(Color::Gray));\n        Widget::render(instructions, layout[1], buf);\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/template/editor.rs",
    "content": "//! Template Editor sub-widget.\n//!\n//! This widget provides an editable text area for template content with validation.\n\nuse crate::model::template::EditorState;\nuse ratatui::{\n    prelude::*,\n    widgets::{Block, Borders},\n};\n\n/// Template Editor sub-widget\npub struct TemplateEditorWidget;\n\nimpl TemplateEditorWidget {\n    pub fn new() -> Self {\n        Self\n    }\n\n    /// Render the template editor\n    pub fn render(\n        &self,\n        area: Rect,\n        buf: &mut Buffer,\n        state: &mut EditorState,\n        is_focused: bool,\n        has_missing_vars: bool,\n    ) {\n        // Determine border style based on validation and focus\n        let border_style = if is_focused {\n            Style::default().fg(Color::Yellow) // Focused\n        } else {\n            Style::default().fg(Color::Rgb(139, 69, 19)) // Brown for normal\n        };\n\n        // Create title with validation status\n        let title_spans = if !state.is_valid {\n            vec![\n                Span::styled(\"Template \", Style::default().fg(Color::White)),\n                Span::styled(\n                    \"e\",\n                    Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),\n                ),\n                Span::styled(\"ditor \", Style::default().fg(Color::White)),\n                Span::styled(\n                    format!(\"(SYNTAX ERROR: {})\", state.validation_message),\n                    Style::default().fg(Color::Red),\n                ),\n            ]\n        } else if has_missing_vars {\n            vec![\n                Span::styled(\"Template \", Style::default().fg(Color::White)),\n                Span::styled(\n                    \"e\",\n                    Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),\n                ),\n                Span::styled(\"ditor \", Style::default().fg(Color::White)),\n                Span::styled(\" (MISSING VARIABLES)\", Style::default().fg(Color::Red)),\n            ]\n        } else {\n            vec![\n                Span::styled(\"Template \", Style::default().fg(Color::White)),\n                Span::styled(\n                    \"e\",\n                    Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),\n                ),\n                Span::styled(\"ditor \", Style::default().fg(Color::White)),\n                Span::styled(\" (VALID)\", Style::default().fg(Color::Green)),\n            ]\n        };\n\n        // Configure TextArea\n        let mut textarea = state.editor.clone();\n        textarea.set_block(\n            Block::default()\n                .borders(Borders::ALL)\n                .title(Line::from(title_spans))\n                .border_style(border_style),\n        );\n\n        // Set cursor and text styles based on focus and validation\n        if is_focused {\n            textarea.set_cursor_line_style(Style::default().add_modifier(Modifier::UNDERLINED));\n            textarea.set_cursor_style(Style::default().fg(Color::Yellow));\n        }\n\n        // Set text color - always use brown highlight for invalid, white for valid\n        if !state.is_valid || has_missing_vars {\n            textarea.set_style(Style::default().fg(Color::Rgb(139, 69, 19))); // Brown highlight\n        } else {\n            textarea.set_style(Style::default().fg(Color::White));\n        }\n\n        // Render the TextArea\n        Widget::render(&textarea, area, buf);\n    }\n}\n\nimpl Default for TemplateEditorWidget {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/template/mod.rs",
    "content": "//! Template widget module.\n//!\n//! This module coordinates the three template sub-widgets:\n//! - Editor: Template content editing and validation\n//! - Variable: Variable management and validation  \n//! - Picker: Template selection and loading\n\npub mod editor;\npub mod picker;\npub mod variable;\n\npub use editor::TemplateEditorWidget;\npub use picker::TemplatePickerWidget;\npub use variable::TemplateVariableWidget;\n\nuse crate::model::Model;\nuse crate::model::template::{TemplateFocus, TemplateState};\nuse ratatui::{\n    prelude::*,\n    widgets::{Block, Borders, Paragraph},\n};\n\n/// Main Template widget that coordinates the 3 sub-widgets\npub struct TemplateWidget {\n    editor: TemplateEditorWidget,\n    variables: TemplateVariableWidget,\n    picker: TemplatePickerWidget,\n}\n\nimpl TemplateWidget {\n    pub fn new(_model: &Model) -> Self {\n        Self {\n            editor: TemplateEditorWidget::new(),\n            variables: TemplateVariableWidget::new(),\n            picker: TemplatePickerWidget::new(),\n        }\n    }\n\n    /// Render the template widget with 3 columns\n    pub fn render(&self, area: Rect, buf: &mut Buffer, state: &mut TemplateState) {\n        // Main layout - content and footer\n        let chunks = Layout::default()\n            .direction(Direction::Vertical)\n            .constraints([\n                Constraint::Min(0),    // Content (3 columns)\n                Constraint::Length(3), // Footer\n            ])\n            .split(area);\n\n        // 3-column layout for content\n        self.render_content(chunks[0], buf, state);\n\n        // Footer\n        self.render_footer(chunks[1], buf, state);\n    }\n\n    /// Render the 3-column content area\n    fn render_content(&self, area: Rect, buf: &mut Buffer, state: &mut TemplateState) {\n        // Flexible 3-column layout\n        let min_width = 30;\n        let available_width = area.width.saturating_sub(6); // Account for borders\n\n        let constraints = if available_width >= min_width * 3 {\n            // Full 3-column layout\n            vec![\n                Constraint::Percentage(40), // Editor\n                Constraint::Percentage(35), // Variables\n                Constraint::Percentage(25), // Picker\n            ]\n        } else if available_width >= min_width * 2 {\n            // 2-column layout, hide picker or make it smaller\n            vec![\n                Constraint::Percentage(60), // Editor\n                Constraint::Percentage(40), // Variables\n                Constraint::Length(0),      // Picker hidden\n            ]\n        } else {\n            // Single column, show only focused column\n            match state.get_focus() {\n                TemplateFocus::Editor => vec![\n                    Constraint::Percentage(100),\n                    Constraint::Length(0),\n                    Constraint::Length(0),\n                ],\n                TemplateFocus::Variables => vec![\n                    Constraint::Length(0),\n                    Constraint::Percentage(100),\n                    Constraint::Length(0),\n                ],\n                TemplateFocus::Picker => vec![\n                    Constraint::Length(0),\n                    Constraint::Length(0),\n                    Constraint::Percentage(100),\n                ],\n            }\n        };\n\n        let columns = Layout::default()\n            .direction(Direction::Horizontal)\n            .constraints(constraints)\n            .split(area);\n\n        // Render each column if it has space\n        if columns[0].width > 0 {\n            let is_editor_focused = state.get_focus() == TemplateFocus::Editor;\n            let is_editing_template =\n                state.get_focus_mode() == crate::model::template::FocusMode::EditingTemplate;\n            let has_missing_vars = state.variables.has_missing_variables();\n            self.editor.render(\n                columns[0],\n                buf,\n                &mut state.editor,\n                is_editor_focused || is_editing_template,\n                has_missing_vars,\n            );\n        }\n\n        if columns[1].width > 0 {\n            let variables = state.get_organized_variables();\n            let is_variables_focused = state.get_focus() == TemplateFocus::Variables;\n            let is_editing_variable =\n                state.get_focus_mode() == crate::model::template::FocusMode::EditingVariable;\n            self.variables.render(\n                columns[1],\n                buf,\n                &state.variables,\n                &variables,\n                is_variables_focused || is_editing_variable,\n            );\n        }\n\n        if columns[2].width > 0 {\n            self.picker.render(\n                columns[2],\n                buf,\n                &state.picker,\n                state.get_focus() == TemplateFocus::Picker,\n            );\n        }\n    }\n\n    /// Render the footer with controls and status\n    fn render_footer(&self, area: Rect, buf: &mut Buffer, state: &TemplateState) {\n        let footer_content = if !state.get_status().is_empty() {\n            // Simple text for status messages\n            vec![Span::styled(\n                state.get_status(),\n                Style::default().fg(Color::Gray),\n            )]\n        } else {\n            // Show different controls based on focus mode\n            match state.get_focus_mode() {\n                crate::model::template::FocusMode::Normal => {\n                    // Normal mode: can switch focus with colored letters\n                    let mut spans = vec![\n                        Span::styled(\n                            \"Enter: Run Analysis | Focus: \",\n                            Style::default().fg(Color::Gray),\n                        ),\n                        Span::styled(\n                            \"e\",\n                            Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),\n                        ),\n                        Span::styled(\"(dit) \", Style::default().fg(Color::Gray)),\n                        Span::styled(\n                            \"v\",\n                            Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),\n                        ),\n                        Span::styled(\"(ariables) \", Style::default().fg(Color::Gray)),\n                        Span::styled(\n                            \"p\",\n                            Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),\n                        ),\n                        Span::styled(\"(icker) | \", Style::default().fg(Color::Gray)),\n                        Span::styled(\n                            \"s\",\n                            Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),\n                        ),\n                        Span::styled(\"(ave Template) \", Style::default().fg(Color::Gray)),\n                    ];\n\n                    let specific_controls = match state.get_focus() {\n                        TemplateFocus::Editor => \"\",\n                        TemplateFocus::Variables => \"\",\n                        TemplateFocus::Picker => {\n                            TemplatePickerWidget::get_help_text(true, state.picker.active_list)\n                        }\n                    };\n\n                    spans.push(Span::styled(\n                        specific_controls,\n                        Style::default().fg(Color::Gray),\n                    ));\n                    spans\n                }\n                crate::model::template::FocusMode::EditingTemplate => {\n                    vec![Span::styled(\n                        \"EDIT MODE: Type to edit template | ESC: Exit edit mode\",\n                        Style::default().fg(Color::Gray),\n                    )]\n                }\n                crate::model::template::FocusMode::EditingVariable => {\n                    let text = if state.variables.is_editing() {\n                        \"VARIABLE INPUT: Type value | Enter: Save | ESC: Cancel\"\n                    } else {\n                        \"VARIABLE MODE: ↑↓: Navigate | Space: Edit variable | Tab: Next | ESC: Exit\"\n                    };\n                    vec![Span::styled(text, Style::default().fg(Color::Gray))]\n                }\n            }\n        };\n\n        let footer = Paragraph::new(Line::from(footer_content))\n            .block(Block::default().borders(Borders::ALL).title(\"Controls\"));\n        footer.render(area, buf);\n    }\n}\n\nimpl StatefulWidget for TemplateWidget {\n    type State = TemplateState;\n\n    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {\n        TemplateWidget::render(&self, area, buf, state);\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/template/picker.rs",
    "content": "//! Template Picker sub-widget.\n//!\n//! This widget provides template selection with separate default and custom lists.\n\nuse crate::model::template::{ActiveList, PickerState};\nuse ratatui::{\n    prelude::*,\n    widgets::{Block, Borders, List, ListItem},\n};\n\n/// Template Picker sub-widget\npub struct TemplatePickerWidget;\n\nimpl TemplatePickerWidget {\n    pub fn new() -> Self {\n        Self\n    }\n\n    /// Render the template picker as a single unified list with groups\n    pub fn render(&self, area: Rect, buf: &mut Buffer, state: &PickerState, is_focused: bool) {\n        let border_style = if is_focused {\n            Style::default().fg(Color::Yellow)\n        } else {\n            Style::default().fg(Color::Gray)\n        };\n\n        // Create unified list with section headers\n        let mut items = Vec::new();\n        let mut item_index = 0;\n        let global_cursor = state.get_global_cursor_position();\n\n        // Default Templates Section\n        if !state.default_templates.is_empty() {\n            // Section header\n            items.push(ListItem::new(Line::from(vec![\n                Span::styled(\"📄 \", Style::default().fg(Color::White)),\n                Span::styled(\n                    \"Default Templates\",\n                    Style::default()\n                        .fg(Color::Cyan)\n                        .add_modifier(Modifier::BOLD),\n                ),\n            ])));\n            item_index += 1;\n\n            // Default template items\n            for template in state.default_templates.iter() {\n                let is_selected = global_cursor == item_index;\n                let style = if is_selected && is_focused {\n                    Style::default()\n                        .fg(Color::Yellow)\n                        .add_modifier(Modifier::BOLD)\n                } else if is_selected {\n                    Style::default()\n                        .fg(Color::Cyan)\n                        .add_modifier(Modifier::BOLD)\n                } else {\n                    Style::default().fg(Color::White)\n                };\n\n                let prefix = if is_selected { \"► \" } else { \"  \" };\n                items.push(ListItem::new(format!(\"{}📄 {}\", prefix, template.name)).style(style));\n                item_index += 1;\n            }\n        }\n\n        // Custom Templates Section\n        if !state.custom_templates.is_empty() {\n            // Add separator if we have default templates\n            if !state.default_templates.is_empty() {\n                items.push(ListItem::new(\"\"));\n                item_index += 1;\n            }\n\n            // Section header\n            items.push(ListItem::new(Line::from(vec![\n                Span::styled(\"📝 \", Style::default().fg(Color::White)),\n                Span::styled(\n                    \"Custom Templates\",\n                    Style::default()\n                        .fg(Color::Green)\n                        .add_modifier(Modifier::BOLD),\n                ),\n            ])));\n            item_index += 1;\n\n            // Custom template items\n            for template in state.custom_templates.iter() {\n                let is_selected = global_cursor == item_index;\n                let style = if is_selected && is_focused {\n                    Style::default()\n                        .fg(Color::Yellow)\n                        .add_modifier(Modifier::BOLD)\n                } else if is_selected {\n                    Style::default()\n                        .fg(Color::Green)\n                        .add_modifier(Modifier::BOLD)\n                } else {\n                    Style::default().fg(Color::White)\n                };\n\n                let prefix = if is_selected { \"► \" } else { \"  \" };\n                items.push(ListItem::new(format!(\"{}📝 {}\", prefix, template.name)).style(style));\n                item_index += 1;\n            }\n        }\n\n        // Create title with focus indicators\n        let title_spans = vec![\n            Span::styled(\"Template \", Style::default().fg(Color::White)),\n            Span::styled(\n                \"p\",\n                Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),\n            ),\n            Span::styled(\"icker\", Style::default().fg(Color::White)),\n        ];\n\n        let list = List::new(items).block(\n            Block::default()\n                .borders(Borders::ALL)\n                .title(Line::from(title_spans))\n                .border_style(border_style),\n        );\n\n        Widget::render(list, area, buf);\n    }\n\n    /// Get help text for the picker\n    pub fn get_help_text(is_focused: bool, _active_list: ActiveList) -> &'static str {\n        if is_focused {\n            \"↑↓: Navigate | l/Space: Load | r: Refresh\"\n        } else {\n            \"Press 'p' to focus picker\"\n        }\n    }\n}\n\nimpl Default for TemplatePickerWidget {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/template/variable.rs",
    "content": "//! Template Variable sub-widget.\n//!\n//! This widget provides a 2-column display for template variables with direct editing.\n\nuse crate::model::template::{VariableCategory, VariableInfo, VariableState};\nuse ratatui::{\n    prelude::*,\n    widgets::{Block, Borders, Clear, Paragraph},\n};\n\n/// Template Variable sub-widget\npub struct TemplateVariableWidget;\n\nimpl TemplateVariableWidget {\n    pub fn new() -> Self {\n        Self\n    }\n\n    /// Render the variable widget\n    pub fn render(\n        &self,\n        area: Rect,\n        buf: &mut Buffer,\n        state: &VariableState,\n        variables: &[VariableInfo],\n        is_focused: bool,\n    ) {\n        let border_style = if is_focused {\n            Style::default().fg(Color::Yellow)\n        } else {\n            Style::default().fg(Color::Gray)\n        };\n\n        // Create table-like display with 2 columns\n        let mut lines = Vec::new();\n\n        // Header\n        lines.push(Line::from(vec![\n            Span::styled(\n                \"Name\",\n                Style::default()\n                    .fg(Color::White)\n                    .add_modifier(Modifier::BOLD),\n            ),\n            Span::raw(\"                \"), // Spacing\n            Span::styled(\n                \"Description/Value\",\n                Style::default()\n                    .fg(Color::White)\n                    .add_modifier(Modifier::BOLD),\n            ),\n        ]));\n\n        lines.push(Line::from(vec![Span::raw(\n            \"────────────────────────────────────────────────────────────────────────────────\",\n        )]));\n\n        // Variable rows\n        for (i, var_info) in variables.iter().enumerate() {\n            let is_selected = i == state.cursor && is_focused;\n\n            let name_style = if is_selected {\n                Style::default()\n                    .fg(Color::Yellow)\n                    .add_modifier(Modifier::BOLD)\n            } else {\n                match var_info.category {\n                    VariableCategory::System => Style::default().fg(Color::Green),\n                    VariableCategory::User => Style::default().fg(Color::Cyan),\n                    VariableCategory::Missing => Style::default().fg(Color::Red),\n                }\n            };\n\n            let value_style = if is_selected {\n                Style::default()\n                    .fg(Color::Yellow)\n                    .add_modifier(Modifier::BOLD)\n            } else {\n                Style::default().fg(Color::White)\n            };\n\n            let prefix = match var_info.category {\n                VariableCategory::System => \"🔧 \",\n                VariableCategory::User => \"👤 \",\n                VariableCategory::Missing => \"❌ \",\n            };\n\n            let name_part = format!(\"{}{{{{{}}}}}\", prefix, var_info.name);\n            let name_padded = format!(\"{:<24}\", name_part);\n\n            let value_part = match var_info.category {\n                VariableCategory::System => var_info\n                    .description\n                    .as_ref()\n                    .unwrap_or(&\"System variable\".to_string())\n                    .clone(),\n                VariableCategory::User => var_info\n                    .value\n                    .as_ref()\n                    .unwrap_or(&\"(empty)\".to_string())\n                    .clone(),\n                VariableCategory::Missing => \"⚠️ Not defined\".to_string(), // NO \"Press Enter to set\"\n            };\n\n            let line = if is_selected {\n                // Highlight entire row for selected item\n                Line::from(vec![Span::styled(\n                    format!(\"► {}{}\", name_padded, value_part),\n                    name_style,\n                )])\n            } else {\n                Line::from(vec![\n                    Span::styled(format!(\"  {}\", name_padded), name_style),\n                    Span::styled(value_part, value_style),\n                ])\n            };\n\n            lines.push(line);\n        }\n\n        let title_spans = vec![\n            Span::styled(\n                \"v\",\n                Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),\n            ),\n            Span::styled(\"ariables\", Style::default().fg(Color::White)),\n        ];\n\n        let paragraph = Paragraph::new(lines)\n            .block(\n                Block::default()\n                    .borders(Borders::ALL)\n                    .title(Line::from(title_spans))\n                    .border_style(border_style),\n            )\n            .wrap(ratatui::widgets::Wrap { trim: false });\n\n        Widget::render(paragraph, area, buf);\n\n        // Render variable input popup if active\n        if state.is_editing() {\n            self.render_variable_input(area, buf, state);\n        }\n    }\n\n    /// Render variable input popup\n    fn render_variable_input(&self, area: Rect, buf: &mut Buffer, state: &VariableState) {\n        let popup_area = Self::centered_rect(60, 20, area);\n        Clear.render(popup_area, buf);\n\n        let var_name = state\n            .get_editing_variable()\n            .map(|s| s.as_str())\n            .unwrap_or(\"Unknown\");\n        let title = format!(\"Set Variable: {}\", var_name);\n\n        let paragraph = Paragraph::new(state.get_input_content()).block(\n            Block::default()\n                .borders(Borders::ALL)\n                .title(title)\n                .border_style(Style::default().fg(Color::Yellow)),\n        );\n\n        Widget::render(paragraph, popup_area, buf);\n    }\n\n    /// Create centered rectangle for popup\n    fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {\n        let popup_layout = Layout::default()\n            .direction(Direction::Vertical)\n            .constraints([\n                Constraint::Percentage((100 - percent_y) / 2),\n                Constraint::Percentage(percent_y),\n                Constraint::Percentage((100 - percent_y) / 2),\n            ])\n            .split(r);\n\n        Layout::default()\n            .direction(Direction::Horizontal)\n            .constraints([\n                Constraint::Percentage((100 - percent_x) / 2),\n                Constraint::Percentage(percent_x),\n                Constraint::Percentage((100 - percent_x) / 2),\n            ])\n            .split(popup_layout[1])[1]\n    }\n}\n\nimpl Default for TemplateVariableWidget {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt/tests/common/fixtures.rs",
    "content": "//! rstest fixtures for code2prompt integration tests\n\nuse super::test_env::*;\nuse colored::*;\nuse log::info;\nuse rstest::*;\nuse std::fs;\n\n/// Fixture for basic test environment with standard file hierarchy\n#[fixture]\npub fn basic_test_env() -> BasicTestEnv {\n    let env = BasicTestEnv::new();\n    create_standard_hierarchy(env.dir.path());\n    env\n}\n\n/// Fixture for git test environment with gitignore setup\n#[fixture]\npub fn git_test_env() -> GitTestEnv {\n    let env = GitTestEnv::new();\n    create_git_hierarchy(env.dir.path());\n    env\n}\n\n/// Fixture for stdout test environment with simple files\n#[fixture]\npub fn stdout_test_env() -> StdoutTestEnv {\n    let env = StdoutTestEnv::new();\n    create_simple_test_files(env.dir.path());\n    env\n}\n\n/// Fixture for template test environment with code structure\n#[fixture]\npub fn template_test_env() -> TemplateTestEnv {\n    let env = TemplateTestEnv::new();\n    create_test_codebase(env.dir.path());\n    env\n}\n\n/// Create standard test hierarchy (lowercase/uppercase directories with various files)\npub fn create_standard_hierarchy(base_path: &std::path::Path) {\n    let lowercase_dir = base_path.join(\"lowercase\");\n    let uppercase_dir = base_path.join(\"uppercase\");\n\n    fs::create_dir_all(&lowercase_dir).unwrap();\n    fs::create_dir_all(&uppercase_dir).unwrap();\n\n    let files = vec![\n        (\"lowercase/foo.py\", \"content foo.py\"),\n        (\"lowercase/bar.py\", \"content bar.py\"),\n        (\"lowercase/baz.py\", \"content baz.py\"),\n        (\"lowercase/qux.txt\", \"content qux.txt\"),\n        (\"lowercase/corge.txt\", \"content corge.txt\"),\n        (\"lowercase/grault.txt\", \"content grault.txt\"),\n        (\"uppercase/FOO.py\", \"CONTENT FOO.PY\"),\n        (\"uppercase/BAR.py\", \"CONTENT BAR.PY\"),\n        (\"uppercase/BAZ.py\", \"CONTENT BAZ.PY\"),\n        (\"uppercase/QUX.txt\", \"CONTENT QUX.TXT\"),\n        (\"uppercase/CORGE.txt\", \"CONTENT CORGE.TXT\"),\n        (\"uppercase/GRAULT.txt\", \"CONTENT GRAULT.TXT\"),\n    ];\n\n    for (file_path, content) in files {\n        create_temp_file(base_path, file_path, content);\n    }\n\n    info!(\n        \"{}{}{} {}\",\n        \"[\".bold().white(),\n        \"✓\".bold().green(),\n        \"]\".bold().white(),\n        \"Standard test hierarchy created\".green()\n    );\n}\n\n/// Create git test hierarchy with gitignore\npub fn create_git_hierarchy(base_path: &std::path::Path) {\n    let test_dir = base_path.join(\"test_dir\");\n    fs::create_dir_all(&test_dir).unwrap();\n\n    let files = vec![\n        (\"test_dir/included.txt\", \"Included file\"),\n        (\"test_dir/ignored.txt\", \"Ignored file\"),\n    ];\n\n    for (file_path, content) in files {\n        create_temp_file(base_path, file_path, content);\n    }\n\n    // Create a .gitignore file\n    let gitignore_path = base_path.join(\".gitignore\");\n    let mut gitignore_file =\n        std::fs::File::create(&gitignore_path).expect(\"Failed to create .gitignore file\");\n    use std::io::Write;\n    writeln!(gitignore_file, \"test_dir/ignored.txt\").expect(\"Failed to write to .gitignore file\");\n\n    info!(\n        \"{}{}{} {}\",\n        \"[\".bold().white(),\n        \"✓\".bold().green(),\n        \"]\".bold().white(),\n        \"Git test hierarchy created\".green()\n    );\n}\n\n/// Create simple test files for stdout tests\npub fn create_simple_test_files(base_path: &std::path::Path) {\n    let files = vec![\n        (\"test.py\", \"print('Hello, World!')\"),\n        (\"README.md\", \"# Test Project\\nThis is a test.\"),\n        (\"config.json\", r#\"{\"name\": \"test\", \"version\": \"1.0.0\"}\"#),\n    ];\n\n    for (file_path, content) in files {\n        create_temp_file(base_path, file_path, content);\n    }\n\n    info!(\n        \"{}{}{} {}\",\n        \"[\".bold().white(),\n        \"✓\".bold().green(),\n        \"]\".bold().white(),\n        \"Simple test files created\".green()\n    );\n}\n\n/// Create test codebase for template tests\npub fn create_test_codebase(base_path: &std::path::Path) {\n    let files = vec![\n        (\n            \"src/main.rs\",\n            \"fn main() {\\n    println!(\\\"Hello, world!\\\");\\n}\",\n        ),\n        (\n            \"src/lib.rs\",\n            \"pub fn add(a: i32, b: i32) -> i32 {\\n    a + b\\n}\",\n        ),\n        (\n            \"tests/test.rs\",\n            \"#[test]\\nfn test_add() {\\n    assert_eq!(3, add(1, 2));\\n}\",\n        ),\n    ];\n\n    for (file_path, content) in files {\n        create_temp_file(base_path, file_path, content);\n    }\n\n    info!(\n        \"{}{}{} {}\",\n        \"[\".bold().white(),\n        \"✓\".bold().green(),\n        \"]\".bold().white(),\n        \"Test codebase created\".green()\n    );\n}\n"
  },
  {
    "path": "crates/code2prompt/tests/common/mod.rs",
    "content": "//! Common test utilities and fixtures for code2prompt integration tests\n//!\n//! This module provides reusable fixtures and utilities to reduce code duplication\n//! across integration tests using rstest.\n\npub mod fixtures;\npub mod test_env;\n\npub use test_env::*;\n\nuse std::sync::Once;\n\nstatic INIT: Once = Once::new();\n\n/// Initialize logger for tests (called once)\npub fn init_logger() {\n    INIT.call_once(|| {\n        env_logger::builder()\n            .is_test(true)\n            .filter_level(log::LevelFilter::Debug)\n            .try_init()\n            .expect(\"Failed to initialize logger\");\n    });\n}\n"
  },
  {
    "path": "crates/code2prompt/tests/common/test_env.rs",
    "content": "//! Test environment types and utilities\n\n#![allow(dead_code)]\n\nuse assert_cmd::Command;\nuse std::fs::{self, File};\nuse std::io::Write;\nuse std::path::Path;\nuse tempfile::TempDir;\n\n/// Basic test environment with temporary directory and output file\npub struct BasicTestEnv {\n    pub dir: TempDir,\n    output_file: String,\n}\n\nimpl BasicTestEnv {\n    pub fn new() -> Self {\n        super::init_logger();\n        let dir = tempfile::tempdir().unwrap();\n        let output_file = dir.path().join(\"output.txt\").to_str().unwrap().to_string();\n        BasicTestEnv { dir, output_file }\n    }\n\n    pub fn command(&self) -> Command {\n        let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n        cmd.arg(self.dir.path().to_str().unwrap())\n            .arg(\"--output-file\")\n            .arg(&self.output_file)\n            .arg(\"--no-clipboard\");\n        cmd\n    }\n\n    pub fn read_output(&self) -> String {\n        let file_path = self.dir.path().join(\"output.txt\");\n        std::fs::read_to_string(&file_path)\n            .unwrap_or_else(|_| panic!(\"Failed to read output file: {:?}\", file_path))\n    }\n}\n\n/// Git-enabled test environment\npub struct GitTestEnv {\n    pub dir: TempDir,\n    output_file: String,\n}\n\nimpl GitTestEnv {\n    pub fn new() -> Self {\n        super::init_logger();\n        let dir = tempfile::tempdir().unwrap();\n        let _repo = git2::Repository::init(dir.path()).expect(\"Failed to initialize repository\");\n        let output_file = dir.path().join(\"output.txt\").to_str().unwrap().to_string();\n        GitTestEnv { dir, output_file }\n    }\n\n    pub fn command(&self) -> Command {\n        let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n        cmd.arg(self.dir.path().to_str().unwrap())\n            .arg(\"--output-file\")\n            .arg(&self.output_file)\n            .arg(\"--no-clipboard\");\n        cmd\n    }\n\n    pub fn read_output(&self) -> String {\n        let file_path = self.dir.path().join(\"output.txt\");\n        std::fs::read_to_string(&file_path)\n            .unwrap_or_else(|_| panic!(\"Failed to read output file: {:?}\", file_path))\n    }\n}\n\n/// Simple test environment for stdout tests\npub struct StdoutTestEnv {\n    pub dir: TempDir,\n}\n\nimpl StdoutTestEnv {\n    pub fn new() -> Self {\n        super::init_logger();\n        let dir = tempfile::tempdir().unwrap();\n        StdoutTestEnv { dir }\n    }\n\n    pub fn path(&self) -> &str {\n        self.dir.path().to_str().unwrap()\n    }\n}\n\n/// Template test environment\npub struct TemplateTestEnv {\n    pub dir: TempDir,\n    output_file: std::path::PathBuf,\n}\n\nimpl TemplateTestEnv {\n    pub fn new() -> Self {\n        super::init_logger();\n        let dir = tempfile::tempdir().unwrap();\n        let output_file = dir.path().join(\"output.txt\");\n        TemplateTestEnv { dir, output_file }\n    }\n\n    pub fn command(&self) -> Command {\n        let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n        cmd.arg(self.dir.path().to_str().unwrap())\n            .arg(\"--output-file\")\n            .arg(self.output_file.to_str().unwrap())\n            .arg(\"--no-clipboard\");\n        cmd\n    }\n\n    pub fn read_output(&self) -> String {\n        std::fs::read_to_string(&self.output_file)\n            .unwrap_or_else(|_| panic!(\"Failed to read output file: {:?}\", self.output_file))\n    }\n\n    pub fn output_file_exists(&self) -> bool {\n        self.output_file.exists()\n    }\n}\n\n/// Utility functions\npub fn create_temp_file(dir: &Path, name: &str, content: &str) -> std::path::PathBuf {\n    let file_path = dir.join(name);\n    let parent_dir = file_path.parent().unwrap();\n    fs::create_dir_all(parent_dir)\n        .unwrap_or_else(|_| panic!(\"Failed to create directory: {:?}\", parent_dir));\n    let mut file = File::create(&file_path)\n        .unwrap_or_else(|_| panic!(\"Failed to create temp file: {:?}\", file_path));\n    writeln!(file, \"{}\", content)\n        .unwrap_or_else(|_| panic!(\"Failed to write to temp file: {:?}\", file_path));\n    file_path\n}\n"
  },
  {
    "path": "crates/code2prompt/tests/config_test.rs",
    "content": "//! Tests for TOML configuration functionality\n//!\n//! This module tests the TOML configuration loading, parsing, and integration\n//! with the new Unix-style behavior.\n\nmod common;\n\nuse code2prompt_core::sort::FileSortMethod;\nuse code2prompt_core::template::OutputFormat;\nuse common::*;\nuse predicates::prelude::*;\nuse predicates::str::contains;\nuse std::fs;\nuse tempfile::TempDir;\n\n/// Test TOML configuration parsing\n#[test]\nfn test_toml_config_parsing() {\n    let toml_content = r#\"\ndefault_output = \"clipboard\"\npath = \"./src\"\ninclude_patterns = [\"*.rs\", \"*.toml\"]\nexclude_patterns = [\"target\", \"node_modules\"]\nline_numbers = true\nabsolute_path = false\nfull_directory_tree = false\noutput_format = \"markdown\"\nsort_method = \"name_asc\"\nencoding = \"cl100k\"\ntoken_format = \"format\"\ndiff_enabled = true\ndiff_branches = [\"main\", \"feature-x\"]\nlog_branches = [\"v1.0.0\", \"v1.1.0\"]\ntemplate_name = \"default\"\ntemplate_str = \"\"\ntoken_map_enabled = true\n\n[user_variables]\nproject = \"code2prompt\"\nauthor = \"ODAncona\"\n\"#;\n\n    use code2prompt_core::configuration::TomlConfig;\n    let config = TomlConfig::from_toml_str(toml_content).expect(\"Should parse TOML config\");\n\n    assert_eq!(\n        config.default_output,\n        code2prompt_core::configuration::OutputDestination::Clipboard\n    );\n    assert_eq!(config.path, Some(\"./src\".to_string()));\n    assert_eq!(config.include_patterns, vec![\"*.rs\", \"*.toml\"]);\n    assert_eq!(config.exclude_patterns, vec![\"target\", \"node_modules\"]);\n    assert!(config.line_numbers);\n    assert!(!config.absolute_path);\n    assert!(!config.full_directory_tree);\n    assert_eq!(config.output_format, Some(OutputFormat::Markdown));\n    assert_eq!(config.sort_method, Some(FileSortMethod::NameAsc));\n    assert_eq!(\n        config.encoding,\n        Some(code2prompt_core::tokenizer::TokenizerType::Cl100kBase)\n    );\n    assert_eq!(\n        config.token_format,\n        Some(code2prompt_core::tokenizer::TokenFormat::Format)\n    );\n    assert!(config.diff_enabled);\n    assert_eq!(\n        config.diff_branches,\n        Some(vec![\"main\".to_string(), \"feature-x\".to_string()])\n    );\n    assert_eq!(\n        config.log_branches,\n        Some(vec![\"v1.0.0\".to_string(), \"v1.1.0\".to_string()])\n    );\n    assert_eq!(config.template_name, Some(\"default\".to_string()));\n    assert!(config.token_map_enabled);\n    assert_eq!(\n        config.user_variables.get(\"project\"),\n        Some(&\"code2prompt\".to_string())\n    );\n    assert_eq!(\n        config.user_variables.get(\"author\"),\n        Some(&\"ODAncona\".to_string())\n    );\n}\n\n/// Test TOML config export functionality\n#[test]\nfn test_toml_config_export() {\n    use code2prompt_core::configuration::{Code2PromptConfig, export_config_to_toml};\n\n    let config = Code2PromptConfig::builder()\n        .path(\"./test\")\n        .include_patterns(vec![\"*.rs\".to_string()])\n        .exclude_patterns(vec![\"target\".to_string()])\n        .line_numbers(true)\n        .build()\n        .unwrap();\n\n    let toml_str = export_config_to_toml(&config).expect(\"Should export to TOML\");\n\n    // Verify the exported TOML contains expected values\n    assert!(toml_str.contains(\"default_output = \\\"stdout\\\"\"));\n    assert!(toml_str.contains(\"path = \\\"./test\\\"\"));\n    assert!(toml_str.contains(\"include_patterns = [\\\"*.rs\\\"]\"));\n    assert!(toml_str.contains(\"exclude_patterns = [\\\"target\\\"]\"));\n    assert!(toml_str.contains(\"line_numbers = true\"));\n}\n\n/// Test local config file loading\n#[test]\nfn test_local_config_file_loading() {\n    let temp_dir = TempDir::new().expect(\"Should create temp dir\");\n    let config_path = temp_dir.path().join(\".c2pconfig\");\n\n    let toml_content = r#\"\ndefault_output = \"stdout\"\ninclude_patterns = [\"*.rs\"]\nline_numbers = true\n\"#;\n\n    fs::write(&config_path, toml_content).expect(\"Should write config file\");\n\n    // Change to the temp directory\n    let original_dir = std::env::current_dir().expect(\"Should get current dir\");\n    std::env::set_current_dir(temp_dir.path()).expect(\"Should change dir\");\n\n    // Test that the config is loaded (we can't easily test the actual loading here\n    // without more complex setup, but we can test the file exists)\n    assert!(config_path.exists());\n\n    // Restore original directory\n    std::env::set_current_dir(original_dir).expect(\"Should restore dir\");\n}\n\n/// Test new Unix-style default behavior (stdout)\n#[test]\nfn test_unix_style_default_stdout() {\n    let temp_dir = TempDir::new().expect(\"Should create temp dir\");\n\n    // Create a test.py file with expected content\n    fs::write(temp_dir.path().join(\"test.py\"), \"print('Hello, World!')\")\n        .expect(\"Should write test file\");\n\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    let temp_path = temp_dir.path().to_path_buf();\n    cmd.arg(&temp_path)\n        .assert()\n        .success()\n        .stdout(contains(\"test.py\"))\n        .stdout(contains(\"print('Hello, World!')\"));\n\n    // Keep temp_dir alive until the end\n    drop(temp_dir);\n}\n\n/// Test new clipboard flag\n#[test]\nfn test_clipboard_flag() {\n    let test_env = StdoutTestEnv::new();\n\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(test_env.path())\n        .arg(\"-c\") // New clipboard flag\n        .assert()\n        .success()\n        // Should not output to stdout when using clipboard\n        .stdout(contains(\"test.py\").not());\n}\n\n/// Test that CLI args override config files\n#[test]\nfn test_cli_args_override_config() {\n    let temp_dir = TempDir::new().expect(\"Should create temp dir\");\n    let config_path = temp_dir.path().join(\".c2pconfig\");\n\n    // Create a config that would normally exclude .py files\n    let toml_content = r#\"\ndefault_output = \"clipboard\"\nexclude_patterns = [\"*.py\"]\n\"#;\n\n    fs::write(&config_path, toml_content).expect(\"Should write config file\");\n    fs::write(temp_dir.path().join(\"test.py\"), \"print('Hello')\").expect(\"Should write test file\");\n\n    // CLI args should override config - include .py files despite config\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.current_dir(temp_dir.path())\n        .arg(\".\")\n        .arg(\"-i\")\n        .arg(\"*.py\") // CLI override\n        .arg(\"-O\")\n        .arg(\"-\") // Force output to stdout to see the result\n        .assert()\n        .success()\n        .stdout(contains(\"test.py\"))\n        .stdout(contains(\"print('Hello')\"));\n}\n\n/// Test configuration info messages\n#[test]\nfn test_config_info_messages() {\n    let temp_dir = TempDir::new().expect(\"Should create temp dir\");\n    let config_path = temp_dir.path().join(\".c2pconfig\");\n\n    let toml_content = r#\"\ndefault_output = \"stdout\"\n\"#;\n\n    fs::write(&config_path, toml_content).expect(\"Should write config file\");\n    fs::write(temp_dir.path().join(\"test.txt\"), \"content\").expect(\"Should write test file\");\n\n    // Run with the temp directory as argument and set current directory for the command\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.current_dir(temp_dir.path())\n        .arg(\".\")\n        .assert()\n        .success()\n        .stderr(contains(\"[i] Using config from:\"));\n}\n\n/// Test default configuration message\n#[test]\nfn test_default_config_message() {\n    let temp_dir = TempDir::new().expect(\"Should create temp dir\");\n    fs::write(temp_dir.path().join(\"test.txt\"), \"content\").expect(\"Should write test file\");\n\n    // Run with the temp directory as argument and set current directory for the command\n    // No config file exists, so it should use default configuration\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.current_dir(temp_dir.path())\n        .arg(\".\")\n        .assert()\n        .success()\n        .stderr(contains(\"[i] Using default configuration\"));\n}\n\n/// Test CLI args message - now CLI args are applied on top of config\n#[test]\nfn test_cli_args_message() {\n    let test_env = StdoutTestEnv::new();\n\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(test_env.path())\n        .arg(\"-i\")\n        .arg(\"*.py\")\n        .assert()\n        .success()\n        .stderr(contains(\"[i] Using default configuration\")); // Now always loads config first\n}\n"
  },
  {
    "path": "crates/code2prompt/tests/git_integration_test.rs",
    "content": "//! Git integration tests for code2prompt\n//!\n//! This module tests git-related functionality including gitignore handling\n//! and git repository integration using rstest fixtures.\n\nmod common;\n\nuse common::fixtures::*;\nuse common::*;\nuse log::debug;\nuse predicates::prelude::*;\nuse predicates::str::contains;\nuse rstest::*;\n\n/// Test gitignore functionality - files should be ignored by default\n#[rstest]\nfn test_gitignore(git_test_env: GitTestEnv) {\n    let mut cmd = git_test_env.command();\n    cmd.assert().success();\n\n    let output = git_test_env.read_output();\n    debug!(\"Test gitignore output:\\n{}\", output);\n\n    // Should include files not in gitignore\n    assert!(contains(\"included.txt\").eval(&output));\n    assert!(contains(\"Included file\").eval(&output));\n\n    // Should exclude files in gitignore\n    assert!(contains(\"ignored.txt\").not().eval(&output));\n    assert!(contains(\"Ignored file\").not().eval(&output));\n}\n\n/// Test --no-ignore flag - should include gitignored files\n#[rstest]\nfn test_gitignore_no_ignore(git_test_env: GitTestEnv) {\n    let mut cmd = git_test_env.command();\n    cmd.arg(\"--no-ignore\").assert().success();\n\n    let output = git_test_env.read_output();\n    debug!(\"Test --no-ignore flag output:\\n{}\", output);\n\n    // Should include all files when ignoring gitignore\n    assert!(contains(\"included.txt\").eval(&output));\n    assert!(contains(\"Included file\").eval(&output));\n    assert!(contains(\"ignored.txt\").eval(&output));\n    assert!(contains(\"Ignored file\").eval(&output));\n}\n\n/// Test that git repository is properly initialized in fixture\n#[rstest]\nfn test_git_repo_initialization(git_test_env: GitTestEnv) {\n    // Verify that the git repository exists\n    let git_dir = git_test_env.dir.path().join(\".git\");\n    assert!(git_dir.exists(), \"Git repository should be initialized\");\n    assert!(git_dir.is_dir(), \"Git directory should be a directory\");\n}\n\n/// Test gitignore with different patterns\n#[rstest]\n#[case(\"*.log\", \"test.log\", \"Log file content\")]\n#[case(\"build/\", \"build/output.txt\", \"Build output\")]\n#[case(\"*.tmp\", \"temp.tmp\", \"Temporary content\")]\nfn test_gitignore_patterns(\n    #[case] pattern: &str,\n    #[case] file_path: &str,\n    #[case] file_content: &str,\n) {\n    let env = GitTestEnv::new();\n\n    // Create the test file\n    create_temp_file(env.dir.path(), file_path, file_content);\n\n    // Create gitignore with the pattern\n    let gitignore_path = env.dir.path().join(\".gitignore\");\n    std::fs::write(&gitignore_path, pattern).expect(\"Failed to write gitignore\");\n\n    let mut cmd = env.command();\n    cmd.assert().success();\n\n    let output = env.read_output();\n    debug!(\"Test gitignore pattern '{}' output:\\n{}\", pattern, output);\n\n    // File should be ignored\n    assert!(\n        contains(file_content).not().eval(&output),\n        \"File with pattern '{}' should be ignored\",\n        pattern\n    );\n\n    // Test with --no-ignore\n    let mut cmd_no_ignore = env.command();\n    cmd_no_ignore.arg(\"--no-ignore\").assert().success();\n\n    let output_no_ignore = env.read_output();\n    assert!(\n        contains(file_content).eval(&output_no_ignore),\n        \"File with pattern '{}' should be included with --no-ignore\",\n        pattern\n    );\n}\n"
  },
  {
    "path": "crates/code2prompt/tests/integration_test.rs",
    "content": "//! Integration tests for code2prompt file filtering functionality\n//!\n//! This module tests the include/exclude patterns, file filtering,\n//! and directory tree generation features using rstest fixtures.\n\nmod common;\n\nuse common::fixtures::*;\nuse common::*;\nuse log::debug;\nuse predicates::prelude::*;\nuse predicates::str::contains;\nuse rstest::*;\n\n/// Test file filtering with various include/exclude patterns\n#[rstest]\nfn test_file_filtering(\n    basic_test_env: BasicTestEnv,\n    #[values(\n        (\"include_extensions\", vec![\"--include=*.py\"], vec![\"foo.py\", \"content foo.py\", \"FOO.py\", \"CONTENT FOO.PY\"], vec![\"content qux.txt\"]),\n        (\"exclude_extensions\", vec![\"--exclude=*.txt\"], vec![\"foo.py\", \"content foo.py\", \"FOO.py\", \"CONTENT FOO.PY\"], vec![\"lowercase/qux.txt\", \"content qux.txt\"]),\n        (\"include_files\", vec![\"--include=**/foo.py,**/bar.py\"], vec![\"foo.py\", \"content foo.py\", \"bar.py\", \"content bar.py\"], vec![\"lowercase/baz.py\", \"content baz.py\"]),\n        (\"include_folders\", vec![\"--include=**/lowercase/**\"], vec![\"foo.py\", \"content foo.py\", \"baz.py\", \"content baz.py\"], vec![\"uppercase/FOO\"]),\n        (\"exclude_files\", vec![\"--exclude=**/foo.py,**/bar.py\"], vec![\"baz.py\", \"content baz.py\"], vec![\"lowercase/foo.py\", \"content foo.py\", \"lowercase/bar.py\", \"content bar.py\"]),\n        (\"exclude_folders\", vec![\"--exclude=**/uppercase/**\"], vec![\"foo.py\", \"content foo.py\", \"baz.py\", \"content baz.py\"], vec![\"CONTENT FOO.py\"])\n    )]\n    test_case: (&str, Vec<&str>, Vec<&str>, Vec<&str>),\n) {\n    let (name, args, should_include, should_exclude) = test_case;\n\n    let mut cmd = basic_test_env.command();\n    for arg in args {\n        cmd.arg(arg);\n    }\n    cmd.assert().success();\n\n    let output = basic_test_env.read_output();\n    debug!(\"Test {} output:\\n{}\", name, output);\n\n    // Check that expected content is included\n    for expected in should_include {\n        assert!(\n            contains(expected).eval(&output),\n            \"Test {}: Expected '{}' to be included in output\",\n            name,\n            expected\n        );\n    }\n\n    // Check that expected content is excluded\n    for expected in should_exclude {\n        assert!(\n            contains(expected).not().eval(&output),\n            \"Test {}: Expected '{}' to be excluded from output\",\n            name,\n            expected\n        );\n    }\n}\n\n/// Test include/exclude combination with exclude priority\n#[rstest]\nfn test_include_exclude_with_exclude_priority(basic_test_env: BasicTestEnv) {\n    let mut cmd = basic_test_env.command();\n    cmd.arg(\"--include=*.py,**/lowercase/**\")\n        .arg(\"--exclude=**/foo.py,**/uppercase/**\")\n        .assert()\n        .success();\n\n    let output = basic_test_env.read_output();\n    debug!(\"Test include and exclude combinations output:\\n{}\", output);\n\n    // Should include\n    assert!(contains(\"lowercase/baz.py\").eval(&output));\n    assert!(contains(\"content baz.py\").eval(&output));\n\n    // Should exclude (exclude takes priority)\n    assert!(contains(\"lowercase/foo.py\").not().eval(&output));\n    assert!(contains(\"content foo.py\").not().eval(&output));\n    assert!(contains(\"uppercase/FOO.py\").not().eval(&output));\n    assert!(contains(\"CONTENT FOO.PY\").not().eval(&output));\n}\n\n/// Test with no filters (should include everything)\n#[rstest]\nfn test_no_filters(basic_test_env: BasicTestEnv) {\n    let mut cmd = basic_test_env.command();\n    cmd.assert().success();\n\n    let output = basic_test_env.read_output();\n    debug!(\"Test no filters output:\\n{}\", output);\n\n    // Should include all files\n    let expected_files = vec![\n        \"foo.py\",\n        \"content foo.py\",\n        \"baz.py\",\n        \"content baz.py\",\n        \"FOO.py\",\n        \"CONTENT FOO.PY\",\n        \"BAZ.py\",\n        \"CONTENT BAZ.PY\",\n    ];\n\n    for expected in expected_files {\n        assert!(\n            contains(expected).eval(&output),\n            \"Expected '{}' to be included when no filters are applied\",\n            expected\n        );\n    }\n}\n\n/// Test full directory tree generation\n#[rstest]\nfn test_full_directory_tree(basic_test_env: BasicTestEnv) {\n    let mut cmd = basic_test_env.command();\n    cmd.arg(\"--full-directory-tree\")\n        .arg(\"--exclude\")\n        .arg(\"**/uppercase/**\")\n        .assert()\n        .success();\n\n    let output = basic_test_env.read_output();\n    debug!(\"Test full directory tree output:\\n{}\", output);\n\n    // Should show directory structure\n    assert!(contains(\"├── lowercase\").eval(&output));\n    assert!(contains(\"└── uppercase\").eval(&output));\n\n    // Should show files in tree format\n    assert!(contains(\"├── foo.py\").eval(&output));\n    assert!(contains(\"├── bar.py\").eval(&output));\n    assert!(contains(\"├── baz.py\").eval(&output));\n\n    // Should show excluded directory structure but not content\n    assert!(contains(\"├── FOO.py\").eval(&output));\n    assert!(contains(\"├── BAR.py\").eval(&output));\n    assert!(contains(\"├── BAZ.py\").eval(&output));\n    assert!(!contains(\"CONTENT BAR.PY\").eval(&output));\n}\n\n/// Test brace expansion patterns\n#[rstest]\nfn test_brace_expansion(basic_test_env: BasicTestEnv) {\n    let mut cmd = basic_test_env.command();\n    cmd.arg(\"--include\")\n        .arg(\"lowercase/{foo.py,bar.py,baz.py}\")\n        .arg(\"--exclude\")\n        .arg(\"lowercase/{qux.txt,corge.txt,grault.txt}\")\n        .assert()\n        .success();\n\n    let output = basic_test_env.read_output();\n    debug!(\"Test brace expansion output:\\n{}\", output);\n\n    // Should include specified Python files\n    assert!(contains(\"foo.py\").eval(&output));\n    assert!(contains(\"content foo.py\").eval(&output));\n    assert!(contains(\"bar.py\").eval(&output));\n    assert!(contains(\"content bar.py\").eval(&output));\n    assert!(contains(\"baz.py\").eval(&output));\n    assert!(contains(\"content baz.py\").eval(&output));\n\n    // Should exclude specified text files\n    assert!(contains(\"qux.txt\").not().eval(&output));\n    assert!(contains(\"corge.txt\").not().eval(&output));\n    assert!(contains(\"grault.txt\").not().eval(&output));\n}\n\n/// Test command creation helper\n#[rstest]\nfn test_command_helper(basic_test_env: BasicTestEnv) {\n    // Test that our fixture creates working commands\n    let mut cmd = basic_test_env.command();\n    cmd.assert().success();\n\n    // Verify output file was created and is readable\n    let output = basic_test_env.read_output();\n    assert!(!output.is_empty(), \"Output should not be empty\");\n}\n"
  },
  {
    "path": "crates/code2prompt/tests/std_output_test.rs",
    "content": "//! Standard output tests for code2prompt\n//!\n//! This module tests stdout functionality, output redirection,\n//! and various output modes using rstest fixtures.\n\nmod common;\n\nuse common::fixtures::*;\nuse common::*;\nuse log::debug;\nuse predicates::prelude::*;\nuse predicates::str::contains;\nuse rstest::*;\n\n/// ~~~ Default Output Behavior ~~~\n#[rstest]\nfn test_output_default(stdout_test_env: StdoutTestEnv) {\n    // Default behavior: output to stdout with status messages in stderr\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(stdout_test_env.path())\n        .assert()\n        .success()\n        // Content should be in stdout\n        .stdout(contains(\"test.py\"))\n        .stdout(contains(\"print('Hello, World!')\"))\n        // Status messages should be in stderr\n        .stderr(contains(\"Token count:\"))\n        // Status messages should NOT be in stdout\n        .stdout(contains(\"Token count:\").not());\n\n    debug!(\"✓ Default stdout output test passed\");\n}\n\n/// ~~~ Stdout Configurations ~~~\n#[rstest]\n#[case(\"explicit_dash\", vec![\"-O\", \"-\", \"--no-clipboard\"], vec![\"test.py\", \"print('Hello, World!')\", \"README.md\", \"# Test Project\"], vec![\"✓\",\"▹▹▹▹▸ Done!\",\"Token count:\",\"Copied to clipboard successfully\"], true)]\n#[case(\"long_form\", vec![\"--output-file\", \"-\", \"--no-clipboard\"], vec![\"test.py\", \"print('Hello, World!')\", \"README.md\", \"# Test Project\"], vec![\"✓\",\"▹▹▹▹▸ Done!\",\"Token count:\",\"Copied to clipboard successfully\"], true)]\n#[case(\"quiet_mode\", vec![\"--quiet\", \"-O\", \"-\", \"--no-clipboard\"], vec![\"test.py\", \"print('Hello, World!')\"], vec![\"✓\",\"▹▹▹▹▸ Done!\",\"Token count:\",\"Copied to clipboard successfully\"], true)]\nfn test_stdout_configurations(\n    stdout_test_env: StdoutTestEnv,\n    #[case] test_name: &str,\n    #[case] args: Vec<&str>,\n    #[case] should_contain: Vec<&str>,\n    #[case] should_not_contain: Vec<&str>,\n    #[case] should_succeed: bool,\n) {\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(stdout_test_env.path());\n\n    for arg in args {\n        cmd.arg(arg);\n    }\n\n    let assertion = cmd.assert();\n\n    if should_succeed {\n        let assertion = assertion.success();\n\n        // Check content that should be present\n        let mut assertion = assertion;\n        for content in should_contain {\n            assertion = assertion.stdout(contains(content));\n        }\n\n        // Check content that should not be present\n        for content in should_not_contain {\n            assertion = assertion.stdout(contains(content).not());\n        }\n\n        debug!(\"✓ {} test passed\", test_name);\n    } else {\n        assertion.failure();\n        debug!(\"✓ {} test passed (correctly failed)\", test_name);\n    }\n}\n\n/// ~~~ File Output Configurations ~~~\n#[rstest]\n#[case(\"file_output\", vec![\"--output-file\", \"output.txt\", \"--no-clipboard\"], vec![\"test.py\", \"print('Hello, World!')\", \"README.md\"], vec![], true)]\n#[case(\"file_output_quiet\", vec![\"--output-file\", \"output.txt\", \"--quiet\", \"--no-clipboard\"], vec![\"test.py\", \"print('Hello, World!')\"], vec![\"✓\"], true)]\n#[case(\"file_output_json\", vec![\"--output-file\", \"output.txt\", \"--output-format\", \"json\", \"--no-clipboard\"], vec![\"{\", \"\\\"files\\\"\", \"test.py\"], vec![], true)]\n#[case(\"file_output_xml\", vec![\"--output-file\", \"output.txt\", \"--output-format\", \"xml\", \"--no-clipboard\"], vec![\"<directory>\", \"</file>\", \"test.py\"], vec![], true)]\n#[case(\"file_output_markdown\", vec![\"--output-file\", \"output.txt\", \"--output-format\", \"markdown\", \"--no-clipboard\"], vec![\"Source Tree:\", \"```\", \"test.py\"], vec![], true)]\nfn test_file_output_configurations(\n    stdout_test_env: StdoutTestEnv,\n    #[case] test_name: &str,\n    #[case] args: Vec<&str>,\n    #[case] should_contain: Vec<&str>,\n    #[case] should_not_contain: Vec<&str>,\n    #[case] should_succeed: bool,\n) {\n    let output_file = stdout_test_env.dir.path().join(\"output.txt\");\n\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(stdout_test_env.path());\n\n    // Replace \"output.txt\" in args with the actual path\n    for arg in args {\n        if arg == \"output.txt\" {\n            cmd.arg(output_file.to_str().unwrap());\n        } else {\n            cmd.arg(arg);\n        }\n    }\n\n    let assertion = cmd.assert();\n\n    if should_succeed {\n        assertion.success();\n\n        // Read the output file and check its contents\n        let file_content =\n            std::fs::read_to_string(&output_file).expect(\"Should be able to read output file\");\n\n        // Check content that should be present\n        for content in should_contain {\n            assert!(\n                file_content.contains(content),\n                \"Test {}: Expected '{}' in file output\",\n                test_name,\n                content\n            );\n        }\n\n        // Check content that should not be present\n        for content in should_not_contain {\n            assert!(\n                !file_content.contains(content),\n                \"Test {}: Expected '{}' NOT to be in file output\",\n                test_name,\n                content\n            );\n        }\n\n        debug!(\"✓ {} test passed\", test_name);\n    } else {\n        assertion.failure();\n        debug!(\"✓ {} test passed (correctly failed)\", test_name);\n    }\n}\n\n/// Test conflicting output options (should fail)\n#[rstest]\nfn test_conflicting_output_options_should_fail(stdout_test_env: StdoutTestEnv) {\n    // Test: Using both default stdout and explicit -O - should fail\n    // This is a logical conflict - you can't output to stdout in two different ways\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(stdout_test_env.path())\n        .arg(\"-\")\n        .arg(\"-O\")\n        .arg(\"-\")\n        .arg(\"--no-clipboard\")\n        .assert()\n        .failure();\n\n    debug!(\"✓ Conflicting output options test passed (correctly failed)\");\n}\n\n// Using both output file and stdout should fail\n#[rstest]\nfn test_output_file_vs_stdout_conflict(stdout_test_env: StdoutTestEnv) {\n    let output_file = stdout_test_env.dir.path().join(\"output.txt\");\n\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(stdout_test_env.path())\n        .arg(\"--output-file\")\n        .arg(output_file.to_str().unwrap())\n        .arg(\"-O\")\n        .arg(\"-\")\n        .arg(\"--no-clipboard\")\n        .assert()\n        .failure()\n        .stderr(\n            contains(\"cannot be used multiple times\")\n                .or(contains(\"conflict\"))\n                .or(contains(\"mutually exclusive\")),\n        );\n\n    debug!(\"✓ Output file vs stdout conflict test passed (correctly failed)\");\n}\n\n/// Test stdout with different output formats\n#[rstest]\n#[case(\"json\", \"{\", \"\\\"files\\\"\")]\n#[case(\"xml\", \"<\", \">\")]\n#[case(\"markdown\", \"Source Tree:\", \"```\")]\nfn test_stdout_with_different_formats(\n    stdout_test_env: StdoutTestEnv,\n    #[case] format: &str,\n    #[case] expected_start: &str,\n    #[case] expected_content: &str,\n) {\n    // Test: Stdout should work with different output formats\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(stdout_test_env.path())\n        .arg(\"--output-format\")\n        .arg(format)\n        .arg(\"-O\")\n        .arg(\"-\")\n        .arg(\"--no-clipboard\")\n        .assert()\n        .success()\n        .stdout(contains(expected_start))\n        .stdout(contains(expected_content))\n        .stdout(contains(\"test.py\"));\n\n    debug!(\"✓ Stdout with {} format test passed\", format);\n}\n\n/// Test stderr messages in normal mode (should show status messages)\n#[rstest]\nfn test_stderr_messages_normal_mode(stdout_test_env: StdoutTestEnv) {\n    let output_file = stdout_test_env.dir.path().join(\"output.txt\");\n\n    // Test with file output in normal mode - should show success message in stderr\n    // Note: In test environment, auto-quiet is enabled, so Token count might not appear\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(stdout_test_env.path())\n        .arg(\"--output-file\")\n        .arg(output_file.to_str().unwrap())\n        .arg(\"--no-clipboard\")\n        .assert()\n        .success()\n        .stderr(contains(\"Prompt written to file:\"));\n\n    debug!(\"✓ Normal mode stderr messages test passed\");\n}\n\n/// Test stderr messages in quiet mode\n#[rstest]\nfn test_stderr_messages_quiet_mode(stdout_test_env: StdoutTestEnv) {\n    let output_file = stdout_test_env.dir.path().join(\"output.txt\");\n\n    // Test with file output in quiet mode - should still show file write confirmation\n    // but suppress other messages\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(stdout_test_env.path())\n        .arg(\"--output-file\")\n        .arg(output_file.to_str().unwrap())\n        .arg(\"--quiet\")\n        .arg(\"--no-clipboard\")\n        .assert()\n        .success()\n        .stderr(contains(\"Done!\").not());\n    // Note: Even in quiet mode, file write confirmation might still appear\n    // This is expected behavior for important operations\n\n    debug!(\"✓ Quiet mode stderr messages test passed\");\n}\n\n/// Test stderr messages with clipboard operations\n#[rstest]\nfn test_stderr_messages_with_clipboard(stdout_test_env: StdoutTestEnv) {\n    // Test without --no-clipboard flag - should attempt clipboard operation\n    // Note: In test environment (non-terminal), auto-quiet is enabled\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(stdout_test_env.path()).assert().success();\n    // In test environment, clipboard operations might be silent due to auto-quiet\n    // This is expected behavior\n\n    debug!(\"✓ Clipboard stderr messages test passed\");\n}\n\n/// Test stderr behavior with different output formats\n#[rstest]\n#[case(\"json\")]\n#[case(\"xml\")]\n#[case(\"markdown\")]\nfn test_stderr_with_output_formats(stdout_test_env: StdoutTestEnv, #[case] format: &str) {\n    let output_file = stdout_test_env.dir.path().join(\"output.txt\");\n\n    // Test that stderr messages appear regardless of output format\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(stdout_test_env.path())\n        .arg(\"--output-file\")\n        .arg(output_file.to_str().unwrap())\n        .arg(\"--output-format\")\n        .arg(format)\n        .arg(\"--no-clipboard\")\n        .assert()\n        .success()\n        .stderr(contains(\"Prompt written to file:\"));\n\n    debug!(\"✓ Stderr with {} format test passed\", format);\n}\n\n/// Test that stdout and stderr are properly separated\n#[rstest]\nfn test_stdout_stderr_separation(stdout_test_env: StdoutTestEnv) {\n    // Test that when outputting to stdout, status messages go to stderr, not stdout\n    let mut cmd = assert_cmd::cargo::cargo_bin_cmd!(\"code2prompt\");\n    cmd.arg(stdout_test_env.path())\n        .arg(\"-O\")\n        .arg(\"-\")\n        .arg(\"--no-clipboard\")\n        .assert()\n        .success()\n        // Content should be in stdout\n        .stdout(contains(\"test.py\"))\n        .stdout(contains(\"print('Hello, World!')\"))\n        // Status messages should NOT be in stdout (they go to stderr in non-quiet mode)\n        .stdout(contains(\"Token count:\").not())\n        .stdout(contains(\"✓\").not());\n\n    debug!(\"✓ Stdout/stderr separation test passed\");\n}\n\n/// Test that fixture creates proper test environment\n#[rstest]\nfn test_stdout_fixture_setup(stdout_test_env: StdoutTestEnv) {\n    // Verify that the fixture created the expected files\n    let test_files = vec![\"test.py\", \"README.md\", \"config.json\"];\n\n    for file in test_files {\n        let file_path = stdout_test_env.dir.path().join(file);\n        assert!(file_path.exists(), \"Test file {} should exist\", file);\n    }\n\n    debug!(\"✓ Stdout fixture setup test passed\");\n}\n"
  },
  {
    "path": "crates/code2prompt/tests/template_integration_test.rs",
    "content": "//! Template integration tests for code2prompt\n//!\n//! This module tests template functionality, output formats,\n//! and template rendering using rstest fixtures.\n\nmod common;\n\nuse common::fixtures::*;\nuse common::*;\nuse log::debug;\nuse predicates::prelude::*;\nuse predicates::str::{contains, ends_with, starts_with};\nuse rstest::*;\n\n/// Test different output format templates\n#[rstest]\n#[case(\"markdown\", vec![\"Source Tree:\", \"```rs\", \"fn main()\", \"Hello, world!\"])]\n#[case(\"xml\", vec![\"<directory>\", \"</file>\", \".rs\\\"\", \"fn main()\", \"Hello, world!\"])]\nfn test_output_format_templates(\n    template_test_env: TemplateTestEnv,\n    #[case] format: &str,\n    #[case] expected_content: Vec<&str>,\n) {\n    let mut cmd = template_test_env.command();\n    cmd.arg(format!(\"--output-format={}\", format))\n        .assert()\n        .success();\n\n    let output = template_test_env.read_output();\n    debug!(\"{} template output:\\n{}\", format, output);\n\n    // Check format-specific content\n    for expected in expected_content {\n        assert!(\n            contains(expected).eval(&output),\n            \"Expected '{}' in {} format output\",\n            expected,\n            format\n        );\n    }\n}\n\n/// Test JSON output format (special case with structured output)\n#[rstest]\nfn test_json_output_format(template_test_env: TemplateTestEnv) {\n    let mut cmd = template_test_env.command();\n    cmd.arg(\"--output-format=json\").assert().success();\n\n    let output = template_test_env.read_output();\n    debug!(\"JSON output format:\\n{}\", output);\n\n    // JSON output should be structured\n    assert!(starts_with(\"{\").eval(&output));\n    assert!(contains(\"\\\"directory_name\\\":\").eval(&output));\n    assert!(contains(\"\\\"prompt\\\": \\\"<directory>\").eval(&output));\n    assert!(ends_with(\"}\").eval(&output));\n}\n\n/// Test that template fixture creates proper codebase structure\n#[rstest]\nfn test_template_fixture_setup(template_test_env: TemplateTestEnv) {\n    // Verify that the fixture created the expected code structure\n    let expected_files = vec![\n        (\"src/main.rs\", \"fn main()\"),\n        (\"src/lib.rs\", \"pub fn add\"),\n        (\"tests/test.rs\", \"#[test]\"),\n    ];\n\n    for (file_path, expected_content) in expected_files {\n        let file_path = template_test_env.dir.path().join(file_path);\n        assert!(\n            file_path.exists(),\n            \"Test file {} should exist\",\n            file_path.display()\n        );\n\n        let content =\n            std::fs::read_to_string(&file_path).expect(\"Should be able to read test file\");\n        assert!(\n            content.contains(expected_content),\n            \"File {} should contain '{}'\",\n            file_path.display(),\n            expected_content\n        );\n    }\n\n    debug!(\"✓ Template fixture setup test passed\");\n}\n\n/// Test basic template rendering with default format\n#[rstest]\nfn test_basic_template_rendering(template_test_env: TemplateTestEnv) {\n    let mut cmd = template_test_env.command();\n    cmd.assert().success();\n\n    let output = template_test_env.read_output();\n    debug!(\"Basic template rendering output:\\n{}\", output);\n\n    // Should contain code from all test files\n    assert!(contains(\"fn main()\").eval(&output));\n    assert!(contains(\"Hello, world!\").eval(&output));\n    assert!(contains(\"pub fn add\").eval(&output));\n    assert!(contains(\"#[test]\").eval(&output));\n    assert!(contains(\"assert_eq!\").eval(&output));\n}\n\n/// Test template with different file extensions\n#[rstest]\nfn test_template_with_file_extensions(template_test_env: TemplateTestEnv) {\n    let mut cmd = template_test_env.command();\n    cmd.assert().success();\n\n    let output = template_test_env.read_output();\n    debug!(\"Template with file extensions output:\\n{}\", output);\n\n    // Should properly identify and format Rust files\n    assert!(contains(\"src/main.rs\").eval(&output));\n    assert!(contains(\"src/lib.rs\").eval(&output));\n    assert!(contains(\"tests/test.rs\").eval(&output));\n}\n\n/// Test template output contains proper structure\n#[rstest]\nfn test_template_output_structure(template_test_env: TemplateTestEnv) {\n    let mut cmd = template_test_env.command();\n    cmd.assert().success();\n\n    let output = template_test_env.read_output();\n    debug!(\"Template output structure:\\n{}\", output);\n\n    // Should contain directory structure information\n    assert!(contains(\"src\").eval(&output));\n    assert!(contains(\"tests\").eval(&output));\n\n    // Should contain file content\n    assert!(!output.trim().is_empty(), \"Output should not be empty\");\n\n    // Should be properly formatted (not just raw concatenation)\n    let line_count = output.lines().count();\n    assert!(\n        line_count > 10,\n        \"Output should have substantial content with multiple lines\"\n    );\n}\n\n/// Test template with include/exclude filters\n#[rstest]\n#[case(\"--include=*.rs\", vec![\"src/main.rs\", \"src/lib.rs\", \"tests/test.rs\"])]\n#[case(\"--exclude=**/test.rs\", vec![\"src/main.rs\", \"src/lib.rs\"])]\n#[case(\"--include=src/**\", vec![\"src/main.rs\", \"src/lib.rs\"])]\nfn test_template_with_filters(\n    template_test_env: TemplateTestEnv,\n    #[case] filter_arg: &str,\n    #[case] expected_files: Vec<&str>,\n) {\n    let mut cmd = template_test_env.command();\n    cmd.arg(filter_arg).assert().success();\n\n    let output = template_test_env.read_output();\n    debug!(\"Template with filter '{}' output:\\n{}\", filter_arg, output);\n\n    // Should contain expected files\n    for expected_file in expected_files {\n        assert!(\n            contains(expected_file).eval(&output),\n            \"Expected file '{}' with filter '{}'\",\n            expected_file,\n            filter_arg\n        );\n    }\n}\n\n/// Test template command creation\n#[rstest]\nfn test_template_command_creation(template_test_env: TemplateTestEnv) {\n    // Test that our fixture creates working commands\n    let mut cmd = template_test_env.command();\n    cmd.assert().success();\n\n    // Verify output file was created and is readable\n    let output = template_test_env.read_output();\n    assert!(!output.is_empty(), \"Template output should not be empty\");\n\n    // Verify the output file exists\n    assert!(\n        template_test_env.output_file_exists(),\n        \"Output file should exist after command execution\"\n    );\n}\n"
  },
  {
    "path": "crates/code2prompt-core/Cargo.toml",
    "content": "[package]\nname = \"code2prompt_core\"\nversion = \"4.2.0\"\nauthors = [\n    \"Mufeed VH <mufeed@lyminal.space>\",\n    \"Olivier D'Ancona <olivier_dancona@hotmail.com>\",\n]\ndescription = \"A command-line (CLI) tool to generate an LLM prompt from codebases of any size, fast.\"\nkeywords = [\"code\", \"ingestion\", \"prompt\", \"llm\", \"agent\"]\ncategories = [\"command-line-utilities\", \"development-tools\"]\nhomepage = \"https://code2prompt.dev\"\ndocumentation = \"https://code2prompt.dev/docs/welcome\"\nrepository = \"https://github.com/mufeedvh/code2prompt\"\nlicense = \"MIT\"\nexclude = [\".github/*\", \".assets/*\"]\nedition = \"2024\"\nreadme = \"../../README.md\"\n\n\n[features]\ndefault = []\n\n[dependencies]\nanyhow = { workspace = true }\nbracoxide = { workspace = true }\ncolored = { workspace = true }\ncontent_inspector = { workspace = true }\ncsv = { workspace = true }\nderive_builder = { workspace = true }\nencoding_rs = { workspace = true }\nignore = { workspace = true }\nindicatif = { workspace = true }\ngit2 = { workspace = true }\nglobset = { workspace = true }\nhandlebars = { workspace = true }\nlog = { workspace = true }\nonce_cell = { workspace = true }\nregex = { workspace = true }\nserde = { workspace = true }\nserde_json = { workspace = true }\ntermtree = { workspace = true }\ntiktoken-rs = { workspace = true }\ntoml = { workspace = true }\nrayon = { workspace = true }\nchardetng = { workspace = true }\n\n[lib]\nname = \"code2prompt_core\"\npath = \"src/lib.rs\"\ncrate-type = [\"rlib\"]\n\n[package.metadata.deb]\nsection = \"utility\"\nassets = [[\"target/release/code2prompt_core\", \"/usr/bin/\", \"755\"]]\n\n[dev-dependencies]\ntempfile = \"3.24\"\nassert_cmd = \"2.1.1\"\npredicates = \"3.1\"\nenv_logger = \"0.11.3\"\nrstest = \"0.26.1\"\n"
  },
  {
    "path": "crates/code2prompt-core/src/builtin_templates.rs",
    "content": "//! Built-in templates embedded as static resources.\n//!\n//! This module provides access to all built-in templates that are embedded\n//! directly into the binary, making them available even when the crate is\n//! installed from crates.io without access to the source file structure.\n\nuse std::{collections::HashMap, sync::OnceLock};\n\n/// Information about a built-in template\n#[derive(Debug, Clone, Copy)]\npub struct BuiltinTemplate {\n    pub name: &'static str,\n    pub content: &'static str,\n    pub description: &'static str,\n}\n\n/// All built-in templates embedded as static strings\npub struct BuiltinTemplates;\n\nstatic TEMPLATES: OnceLock<HashMap<&'static str, BuiltinTemplate>> = OnceLock::new();\n\nimpl BuiltinTemplates {\n    /// Get all available built-in templates\n    pub fn get_all() -> &'static HashMap<&'static str, BuiltinTemplate> {\n        TEMPLATES.get_or_init(|| {\n            HashMap::from([\n                (\n                    \"default-markdown\",\n                    BuiltinTemplate {\n                        name: \"Default (Markdown)\",\n                        content: include_str!(\"default_template_md.hbs\"),\n                        description: \"Default markdown template for code analysis\",\n                    },\n                ),\n                (\n                    \"default-xml\",\n                    BuiltinTemplate {\n                        name: \"Default (XML)\",\n                        content: include_str!(\"default_template_xml.hbs\"),\n                        description: \"Default XML template for code analysis\",\n                    },\n                ),\n                (\n                    \"binary-exploitation-ctf-solver\",\n                    BuiltinTemplate {\n                        name: \"Binary Exploitation CTF Solver\",\n                        content: include_str!(\"../templates/binary-exploitation-ctf-solver.hbs\"),\n                        description: \"Template for solving binary exploitation CTF challenges\",\n                    },\n                ),\n                (\n                    \"clean-up-code\",\n                    BuiltinTemplate {\n                        name: \"Clean Up Code\",\n                        content: include_str!(\"../templates/clean-up-code.hbs\"),\n                        description: \"Template for code cleanup and refactoring\",\n                    },\n                ),\n                (\n                    \"cryptography-ctf-solver\",\n                    BuiltinTemplate {\n                        name: \"Cryptography CTF Solver\",\n                        content: include_str!(\"../templates/cryptography-ctf-solver.hbs\"),\n                        description: \"Template for solving cryptography CTF challenges\",\n                    },\n                ),\n                (\n                    \"document-the-code\",\n                    BuiltinTemplate {\n                        name: \"Document the Code\",\n                        content: include_str!(\"../templates/document-the-code.hbs\"),\n                        description: \"Template for generating code documentation\",\n                    },\n                ),\n                (\n                    \"find-security-vulnerabilities\",\n                    BuiltinTemplate {\n                        name: \"Find Security Vulnerabilities\",\n                        content: include_str!(\"../templates/find-security-vulnerabilities.hbs\"),\n                        description: \"Template for security vulnerability analysis\",\n                    },\n                ),\n                (\n                    \"fix-bugs\",\n                    BuiltinTemplate {\n                        name: \"Fix Bugs\",\n                        content: include_str!(\"../templates/fix-bugs.hbs\"),\n                        description: \"Template for bug fixing and debugging\",\n                    },\n                ),\n                (\n                    \"improve-performance\",\n                    BuiltinTemplate {\n                        name: \"Improve Performance\",\n                        content: include_str!(\"../templates/improve-performance.hbs\"),\n                        description: \"Template for performance optimization\",\n                    },\n                ),\n                (\n                    \"refactor\",\n                    BuiltinTemplate {\n                        name: \"Refactor\",\n                        content: include_str!(\"../templates/refactor.hbs\"),\n                        description: \"Template for code refactoring\",\n                    },\n                ),\n                (\n                    \"reverse-engineering-ctf-solver\",\n                    BuiltinTemplate {\n                        name: \"Reverse Engineering CTF Solver\",\n                        content: include_str!(\"../templates/reverse-engineering-ctf-solver.hbs\"),\n                        description: \"Template for solving reverse engineering CTF challenges\",\n                    },\n                ),\n                (\n                    \"web-ctf-solver\",\n                    BuiltinTemplate {\n                        name: \"Web CTF Solver\",\n                        content: include_str!(\"../templates/web-ctf-solver.hbs\"),\n                        description: \"Template for solving web CTF challenges\",\n                    },\n                ),\n                (\n                    \"write-git-commit\",\n                    BuiltinTemplate {\n                        name: \"Write Git Commit\",\n                        content: include_str!(\"../templates/write-git-commit.hbs\"),\n                        description: \"Template for generating git commit messages\",\n                    },\n                ),\n                (\n                    \"write-github-pull-request\",\n                    BuiltinTemplate {\n                        name: \"Write GitHub Pull Request\",\n                        content: include_str!(\"../templates/write-github-pull-request.hbs\"),\n                        description: \"Template for generating GitHub pull request descriptions\",\n                    },\n                ),\n                (\n                    \"write-github-readme\",\n                    BuiltinTemplate {\n                        name: \"Write GitHub README\",\n                        content: include_str!(\"../templates/write-github-readme.hbs\"),\n                        description: \"Template for generating GitHub README files\",\n                    },\n                ),\n            ])\n        })\n    }\n\n    /// Get a specific template by its key\n    pub fn get_template(key: &str) -> Option<BuiltinTemplate> {\n        Self::get_all().get(key).cloned()\n    }\n\n    /// Get all template keys\n    pub fn get_template_keys() -> Vec<&'static str> {\n        Self::get_all().keys().copied().collect()\n    }\n\n    /// Check if a template exists\n    pub fn has_template(key: &str) -> bool {\n        Self::get_all().contains_key(key)\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/configuration.rs",
    "content": "//! This module defines the `Code2PromptConfig` struct and its Builder for configuring the behavior\n//! of code2prompt in a stateless manner. It includes all parameters needed for file traversal,\n//! code filtering, token counting, and more.\n\nuse crate::template::OutputFormat;\nuse crate::tokenizer::TokenizerType;\nuse crate::{sort::FileSortMethod, tokenizer::TokenFormat};\nuse derive_builder::Builder;\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::path::PathBuf;\n\n/// A stateless configuration object describing all the preferences and filters\n/// applied when generating a code prompt. It does not store any mutable data,\n/// so it can be cloned freely or shared across multiple sessions.\n#[derive(Debug, Clone, Default, Builder)]\n#[builder(setter(into), default)]\npub struct Code2PromptConfig {\n    /// Path to the root directory of the codebase.\n    pub path: PathBuf,\n\n    /// List of glob-like patterns to include.\n    pub include_patterns: Vec<String>,\n\n    /// List of glob-like patterns to exclude.\n    pub exclude_patterns: Vec<String>,\n\n    /// If true, code lines will be numbered in the output.\n    pub line_numbers: bool,\n\n    /// If true, paths in the output will be absolute instead of relative.\n    pub absolute_path: bool,\n\n    /// If true, code2prompt will generate a full directory tree, ignoring include/exclude rules.\n    pub full_directory_tree: bool,\n\n    /// If true, code blocks will not be wrapped in Markdown fences (```).\n    pub no_codeblock: bool,\n\n    /// If true, symbolic links will be followed during traversal.\n    pub follow_symlinks: bool,\n\n    /// If true, hidden files and directories will be included.\n    pub hidden: bool,\n\n    /// If true, .gitignore rules will be ignored.\n    pub no_ignore: bool,\n\n    /// Defines the sorting method for files.\n    pub sort_method: Option<FileSortMethod>,\n\n    /// Determines the output format of the final prompt.\n    pub output_format: OutputFormat,\n\n    /// An optional custom Handlebars template string.\n    pub custom_template: Option<String>,\n\n    /// The tokenizer encoding to use for counting tokens.\n    pub encoding: TokenizerType,\n\n    /// The counting format to use for token counting.\n    pub token_format: TokenFormat,\n\n    /// If true, the git diff between HEAD and index will be included.\n    pub diff_enabled: bool,\n\n    /// If set, contains two branch names for which code2prompt will generate a git diff.\n    pub diff_branches: Option<(String, String)>,\n\n    /// If set, contains two branch names for which code2prompt will retrieve the git log.\n    pub log_branches: Option<(String, String)>,\n\n    /// The name of the template used.\n    pub template_name: String,\n\n    /// The template string itself.\n    pub template_str: String,\n\n    /// Extra template data\n    pub user_variables: HashMap<String, String>,\n\n    /// If true, detailed token map breakdown will be displayed in output.\n    ///\n    /// Note: Token counting always happens internally for performance optimization\n    /// (parallelized during file I/O). This flag only controls whether the breakdown\n    /// is shown to users in the final output.\n    pub token_map_enabled: bool,\n\n    /// If true, starts with all files deselected.\n    pub deselected: bool,\n}\n\nimpl Code2PromptConfig {\n    pub fn builder() -> Code2PromptConfigBuilder {\n        Code2PromptConfigBuilder::default()\n    }\n}\n\n/// Output destination for code2prompt\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]\n#[serde(rename_all = \"lowercase\")]\npub enum OutputDestination {\n    #[default]\n    Stdout,\n    Clipboard,\n    File,\n}\n\n/// TOML configuration structure that can be serialized/deserialized\n#[derive(Debug, Clone, Serialize, Deserialize, Default)]\n#[serde(default)]\npub struct TomlConfig {\n    /// Default output behavior: \"stdout\", \"clipboard\", or \"file\"\n    pub default_output: OutputDestination,\n\n    /// Path to the codebase directory\n    pub path: Option<String>,\n\n    /// Patterns to include\n    pub include_patterns: Vec<String>,\n\n    /// Patterns to exclude\n    pub exclude_patterns: Vec<String>,\n\n    /// Display options\n    pub line_numbers: bool,\n    pub absolute_path: bool,\n    pub full_directory_tree: bool,\n\n    /// Output format\n    pub output_format: Option<OutputFormat>,\n\n    /// Sort method\n    pub sort_method: Option<FileSortMethod>,\n\n    /// Tokenizer settings\n    pub encoding: Option<TokenizerType>,\n    pub token_format: Option<TokenFormat>,\n\n    /// Git settings\n    pub diff_enabled: bool,\n    pub diff_branches: Option<Vec<String>>,\n    pub log_branches: Option<Vec<String>>,\n\n    /// Template settings\n    pub template_name: Option<String>,\n    pub template_str: Option<String>,\n\n    /// User variables\n    pub user_variables: HashMap<String, String>,\n\n    /// Token map\n    pub token_map_enabled: bool,\n\n    /// Initial selection state\n    pub deselected: bool,\n}\n\nimpl TomlConfig {\n    /// Load TOML configuration from a string\n    pub fn from_toml_str(content: &str) -> Result<Self, toml::de::Error> {\n        toml::from_str(content)\n    }\n\n    /// Convert TOML configuration to string\n    pub fn to_string(&self) -> Result<String, toml::ser::Error> {\n        toml::to_string_pretty(self)\n    }\n\n    /// Convert TomlConfig to Code2PromptConfig\n    pub fn to_code2prompt_config(&self) -> Code2PromptConfig {\n        let mut builder = Code2PromptConfig::builder();\n\n        if let Some(path) = &self.path {\n            builder.path(PathBuf::from(path));\n        }\n\n        builder\n            .include_patterns(self.include_patterns.clone())\n            .exclude_patterns(self.exclude_patterns.clone())\n            .line_numbers(self.line_numbers)\n            .absolute_path(self.absolute_path)\n            .full_directory_tree(self.full_directory_tree);\n\n        builder.output_format(self.output_format.unwrap_or_default());\n\n        builder.sort_method(self.sort_method);\n\n        builder.encoding(self.encoding.unwrap_or_default());\n\n        builder.token_format(self.token_format.unwrap_or_default());\n\n        builder.diff_enabled(self.diff_enabled);\n\n        if let Some(diff_branches) = &self.diff_branches\n            && diff_branches.len() == 2\n        {\n            builder.diff_branches(Some((diff_branches[0].clone(), diff_branches[1].clone())));\n        }\n\n        if let Some(log_branches) = &self.log_branches\n            && log_branches.len() == 2\n        {\n            builder.log_branches(Some((log_branches[0].clone(), log_branches[1].clone())));\n        }\n\n        if let Some(template_name) = &self.template_name {\n            builder.template_name(template_name.clone());\n        }\n\n        if let Some(template_str) = &self.template_str {\n            builder.template_str(template_str.clone());\n        }\n\n        builder\n            .user_variables(self.user_variables.clone())\n            .token_map_enabled(self.token_map_enabled)\n            .deselected(self.deselected);\n\n        builder.build().unwrap_or_default()\n    }\n}\n\n/// Export a Code2PromptConfig to TOML format\npub fn export_config_to_toml(config: &Code2PromptConfig) -> Result<String, toml::ser::Error> {\n    let toml_config = TomlConfig {\n        default_output: OutputDestination::Stdout, // Default for new behavior\n        path: Some(config.path.to_string_lossy().to_string()),\n        include_patterns: config.include_patterns.clone(),\n        exclude_patterns: config.exclude_patterns.clone(),\n        line_numbers: config.line_numbers,\n        absolute_path: config.absolute_path,\n        full_directory_tree: config.full_directory_tree,\n        output_format: Some(config.output_format),\n        sort_method: config.sort_method,\n        encoding: Some(config.encoding),\n        token_format: Some(config.token_format),\n        diff_enabled: config.diff_enabled,\n        diff_branches: config\n            .diff_branches\n            .as_ref()\n            .map(|(a, b)| vec![a.clone(), b.clone()]),\n        log_branches: config\n            .log_branches\n            .as_ref()\n            .map(|(a, b)| vec![a.clone(), b.clone()]),\n        template_name: if config.template_name.is_empty() {\n            None\n        } else {\n            Some(config.template_name.clone())\n        },\n        template_str: if config.template_str.is_empty() {\n            None\n        } else {\n            Some(config.template_str.clone())\n        },\n        user_variables: config.user_variables.clone(),\n        token_map_enabled: config.token_map_enabled,\n        deselected: config.deselected,\n    };\n\n    toml_config.to_string()\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/default_template_md.hbs",
    "content": "Project Path: {{ absolute_code_path }}\n\nSource Tree:\n\n```txt\n{{ source_tree }}\n```\n\n{{#each files}}\n{{#if code}}\n`{{path}}`:\n\n{{code}}\n\n{{/if}}\n{{/each}}\n\n{{#if git_diff}}\nGit Diff:\n{{ git_diff }}\n{{/if}}"
  },
  {
    "path": "crates/code2prompt-core/src/default_template_xml.hbs",
    "content": "<directory>{{absolute_code_path}}</directory>\n\n<source-tree>\n  {{source_tree}}\n</source-tree>\n\n<files>\n  {{#each files}}\n    {{#if code}}\n      <file path=\"{{path}}\">\n        {{code}}\n      </file>\n    {{/if}}\n  {{/each}}\n</files>\n\n{{#if git_diff}}\n  <git-diff>\n    {{git_diff}}\n  </git-diff>\n{{/if}}"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/csv.rs",
    "content": "//! CSV file processor with schema extraction.\n//!\n//! This processor uses the `csv` crate to robustly parse CSV files and extract:\n//! - Column headers\n//! - One sample data row\n//!\n//! This provides sufficient context for LLMs to understand the data structure\n//! without wasting tokens on thousands of rows.\n\nuse super::{DefaultTextProcessor, FileProcessor};\nuse anyhow::{Context, Result};\nuse std::path::Path;\n\n/// CSV processor that extracts headers and one sample row.\n///\n/// Uses streaming to avoid loading large files into memory.\n/// Falls back to raw text if parsing fails.\npub struct CsvProcessor;\n\nimpl CsvProcessor {\n    /// Internal processing with specific delimiter.\n    ///\n    /// # Arguments\n    ///\n    /// * `content` - Raw CSV bytes\n    /// * `delimiter` - Field delimiter (b',' for CSV, b'\\t' for TSV)\n    /// * `path` - File path for error messages\n    pub(crate) fn process_with_delimiter(\n        &self,\n        content: &[u8],\n        delimiter: u8,\n        _path: &Path,\n    ) -> Result<String> {\n        let mut reader = csv::ReaderBuilder::new()\n            .delimiter(delimiter)\n            .flexible(true) // Allow variable number of fields\n            .from_reader(content);\n\n        // Extract headers\n        let headers = reader\n            .headers()\n            .context(\"Failed to read CSV headers\")?\n            .iter()\n            .map(|s| s.to_string())\n            .collect::<Vec<_>>();\n\n        if headers.is_empty() {\n            anyhow::bail!(\"CSV file has no headers\");\n        }\n\n        // Read first data row\n        let mut records = reader.records();\n        let first_row = records\n            .next()\n            .transpose()\n            .context(\"Failed to read first data row\")?;\n\n        let mut output = String::new();\n        output.push_str(\"CSV Schema (1 sample row):\\n\");\n        output.push_str(&format!(\"Headers: {}\\n\", headers.join(\", \")));\n\n        if let Some(row) = first_row {\n            let values: Vec<String> = row.iter().map(|field| format!(\"\\\"{}\\\"\", field)).collect();\n            output.push_str(&format!(\"Sample: {}\\n\", values.join(\", \")));\n\n            // Count remaining rows for truncation message\n            let remaining_rows = records.count();\n            if remaining_rows > 0 {\n                output.push_str(&format!(\"... [{} more rows omitted]\\n\", remaining_rows));\n            }\n        } else {\n            output.push_str(\"(No data rows found)\\n\");\n        }\n\n        Ok(output)\n    }\n}\n\nimpl FileProcessor for CsvProcessor {\n    fn process(&self, content: &[u8], path: &Path) -> Result<String> {\n        match self.process_with_delimiter(content, b',', path) {\n            Ok(result) => Ok(result),\n            Err(e) => {\n                log::warn!(\n                    \"CSV parsing failed for {:?}: {}. Using raw text fallback.\",\n                    path,\n                    e\n                );\n                // Fallback to raw text\n                let fallback = DefaultTextProcessor;\n                fallback.process(content, path)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/default.rs",
    "content": "//! Default text processor for standard file types.\n//!\n//! This processor handles all file types that don't require special processing.\n//! It converts raw bytes to UTF-8 strings using lossy conversion to handle\n//! invalid UTF-8 sequences gracefully.\n\nuse super::FileProcessor;\nuse anyhow::Result;\nuse chardetng::EncodingDetector;\nuse std::path::Path;\n\n/// Default processor that converts bytes to UTF-8 string.\n///\n/// This processor uses the `chardetng` crate to detect the encoding of the input bytes\n/// and converts them to a UTF-8 string. If the encoding cannot be determined, it\n/// defaults to UTF-8. Invalid sequences are replaced with the Unicode replacement character.\npub struct DefaultTextProcessor;\n\nimpl FileProcessor for DefaultTextProcessor {\n    fn process(&self, content: &[u8], _path: &Path) -> Result<String> {\n        let mut detector = EncodingDetector::new();\n        detector.feed(content, true);\n\n        // Guess the encoding; if none is found, default to UTF-8\n        let encoding = detector.guess(None, true);\n\n        let (cow, _encoding_used, _had_errors) = encoding.decode(content);\n\n        match cow {\n            std::borrow::Cow::Owned(s) => Ok(s),\n            std::borrow::Cow::Borrowed(s) => Ok(s.to_string()),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/ipynb.rs",
    "content": "//! Jupyter Notebook (.ipynb) file processor.\n//!\n//! This processor parses Jupyter notebook JSON and extracts:\n//! - Total number of cells and their types\n//! - Code cells only (ignoring markdown and raw cells)\n//! - First 2-3 code cells as samples\n//!\n//! This provides LLMs with notebook structure context without overwhelming them with all cells.\n\nuse super::{DefaultTextProcessor, FileProcessor};\nuse anyhow::{Context, Result};\nuse serde_json::Value;\nuse std::path::Path;\n\n/// Jupyter Notebook processor that extracts code cells and metadata.\npub struct JupyterNotebookProcessor;\n\nimpl FileProcessor for JupyterNotebookProcessor {\n    fn process(&self, content: &[u8], _path: &Path) -> Result<String> {\n        // Parse notebook JSON\n        let notebook: Value =\n            serde_json::from_slice(content).context(\"Failed to parse .ipynb file as JSON\")?;\n\n        // Extract cells array\n        let cells = notebook\n            .get(\"cells\")\n            .and_then(|v| v.as_array())\n            .context(\"Notebook has no 'cells' array\")?;\n\n        // Count cell types\n        let mut code_cells = Vec::new();\n        let mut markdown_count = 0;\n        let mut raw_count = 0;\n\n        for cell in cells {\n            let cell_type = cell\n                .get(\"cell_type\")\n                .and_then(|v| v.as_str())\n                .unwrap_or(\"unknown\");\n\n            match cell_type {\n                \"code\" => code_cells.push(cell),\n                \"markdown\" => markdown_count += 1,\n                \"raw\" => raw_count += 1,\n                _ => {}\n            }\n        }\n\n        let total_cells = cells.len();\n\n        // Format output\n        let mut output = String::new();\n        output.push_str(\"Jupyter Notebook Summary:\\n\");\n        output.push_str(&format!(\n            \"Total cells: {} ({} code, {} markdown, {} raw)\\n\\n\",\n            total_cells,\n            code_cells.len(),\n            markdown_count,\n            raw_count\n        ));\n\n        if code_cells.is_empty() {\n            output.push_str(\"(No code cells found)\\n\");\n            return Ok(output);\n        }\n\n        // Show first 2-3 code cells\n        let max_cells_to_show = 3.min(code_cells.len());\n\n        for (idx, cell) in code_cells.iter().take(max_cells_to_show).enumerate() {\n            output.push_str(&format!(\"Code Cell #{}:\\n\", idx + 1));\n\n            // Extract source code\n            if let Some(source) = cell.get(\"source\") {\n                let code = match source {\n                    Value::String(s) => s.clone(),\n                    Value::Array(arr) => {\n                        // Join array of strings\n                        arr.iter()\n                            .filter_map(|v| v.as_str())\n                            .collect::<Vec<_>>()\n                            .join(\"\")\n                    }\n                    _ => String::from(\"(Unable to extract source)\"),\n                };\n\n                output.push_str(\"```python\\n\");\n                output.push_str(&code);\n                if !code.ends_with('\\n') {\n                    output.push('\\n');\n                }\n                output.push_str(\"```\\n\\n\");\n            }\n        }\n\n        if code_cells.len() > max_cells_to_show {\n            output.push_str(&format!(\n                \"... [{} more code cells omitted]\\n\",\n                code_cells.len() - max_cells_to_show\n            ));\n        }\n\n        Ok(output)\n    }\n}\n\nimpl JupyterNotebookProcessor {\n    /// Process with fallback to raw text on error.\n    pub fn process_with_fallback(&self, content: &[u8], path: &Path) -> Result<String> {\n        match self.process(content, path) {\n            Ok(result) => Ok(result),\n            Err(e) => {\n                log::warn!(\n                    \"Jupyter notebook parsing failed for {:?}: {}. Using raw text fallback.\",\n                    path,\n                    e\n                );\n                let fallback = DefaultTextProcessor;\n                fallback.process(content, path)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/jsonl.rs",
    "content": "//! JSON Lines (JSONL) file processor with schema extraction.\n//!\n//! This processor parses JSONL/NDJSON files and extracts:\n//! - Field names from the first JSON object\n//! - One sample JSON object\n//!\n//! This provides sufficient context for LLMs without including thousands of lines.\n\nuse super::{DefaultTextProcessor, FileProcessor};\nuse anyhow::{Context, Result};\nuse serde_json::Value;\nuse std::path::Path;\n\n/// JSONL processor that extracts schema and one sample line.\npub struct JsonLinesProcessor;\n\nimpl FileProcessor for JsonLinesProcessor {\n    fn process(&self, content: &[u8], _path: &Path) -> Result<String> {\n        let text = String::from_utf8_lossy(content);\n        let mut lines = text.lines();\n\n        // Get first line\n        let first_line = match lines.next() {\n            Some(line) if !line.trim().is_empty() => line,\n            _ => {\n                anyhow::bail!(\"JSONL file is empty or has no valid lines\");\n            }\n        };\n\n        // Parse first line as JSON\n        let json_obj: Value = serde_json::from_str(first_line)\n            .with_context(|| format!(\"Failed to parse first line as JSON: {}\", first_line))?;\n\n        // Extract field names\n        let fields = if let Value::Object(map) = &json_obj {\n            map.keys().cloned().collect::<Vec<_>>()\n        } else {\n            anyhow::bail!(\"First line is not a JSON object\");\n        };\n\n        if fields.is_empty() {\n            anyhow::bail!(\"JSON object has no fields\");\n        }\n\n        // Count remaining lines\n        let remaining_lines = lines.filter(|line| !line.trim().is_empty()).count();\n\n        // Format output\n        let mut output = String::new();\n        output.push_str(\"JSONL Schema (1 sample line):\\n\");\n        output.push_str(&format!(\"Fields: {}\\n\", fields.join(\", \")));\n        output.push_str(&format!(\"Sample: {}\\n\", first_line));\n\n        if remaining_lines > 0 {\n            output.push_str(&format!(\"... [{} more lines omitted]\\n\", remaining_lines));\n        }\n\n        Ok(output)\n    }\n}\n\nimpl JsonLinesProcessor {\n    /// Process with fallback to raw text on error.\n    pub fn process_with_fallback(&self, content: &[u8], path: &Path) -> Result<String> {\n        match self.process(content, path) {\n            Ok(result) => Ok(result),\n            Err(e) => {\n                log::warn!(\n                    \"JSONL parsing failed for {:?}: {}. Using raw text fallback.\",\n                    path,\n                    e\n                );\n                let fallback = DefaultTextProcessor;\n                fallback.process(content, path)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/mod.rs",
    "content": "//! File processor module for handling different file types intelligently.\n//!\n//! This module provides a strategy pattern for processing file contents based on their extension\n//! in order to optimize for LLM token usage. The main idea is to extract the schema rather than\n//! raw data where applicable. (e.g., schema + sample for CSV, code cells for Jupyter notebooks).\n\nuse anyhow::Result;\nuse std::path::Path;\n\nmod csv;\nmod default;\nmod ipynb;\nmod jsonl;\nmod tsv;\n\npub use csv::CsvProcessor;\npub use default::DefaultTextProcessor;\npub use ipynb::JupyterNotebookProcessor;\npub use jsonl::JsonLinesProcessor;\npub use tsv::TsvProcessor;\n\n/// Trait for processing file contents into LLM-optimized string representations.\n///\n/// Each processor takes raw bytes and produces a formatted string suitable for\n/// inclusion in an LLM prompt. Processors may extract schemas, truncate content,\n/// or apply other transformations to reduce token usage while preserving semantic value.\npub trait FileProcessor: Send + Sync {\n    /// Process file content and return a formatted string.\n    ///\n    /// # Arguments\n    ///\n    /// * `content` - Raw file bytes\n    /// * `path` - File path for context and error messages\n    ///\n    /// # Returns\n    ///\n    /// * `Result<String>` - Processed content or error\n    fn process(&self, content: &[u8], path: &Path) -> Result<String>;\n}\n\n/// Factory function to get the appropriate processor for a file extension.\n///\n/// # Arguments\n///\n/// * `extension` - File extension (without dot)\n///\n/// # Returns\n///\n/// * `Box<dyn FileProcessor>` - Processor instance for the given extension\n///\n/// # Examples\n///\n/// ```ignore\n/// let processor = get_processor_for_extension(\"csv\");\n/// let result = processor.process(&bytes, path)?;\n/// ```\npub fn get_processor_for_extension(extension: &str) -> Box<dyn FileProcessor> {\n    match extension.to_lowercase().as_str() {\n        \"csv\" => Box::new(CsvProcessor),\n        \"tsv\" => Box::new(TsvProcessor),\n        \"jsonl\" | \"ndjson\" => Box::new(JsonLinesProcessor),\n        \"ipynb\" => Box::new(JupyterNotebookProcessor),\n        // Future processors can be added here:\n        // \"parquet\" => Box::new(ParquetProcessor),\n        // \"xml\" => Box::new(XmlProcessor),\n        _ => Box::new(DefaultTextProcessor),\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/tsv.rs",
    "content": "//! TSV (Tab-Separated Values) file processor.\n//!\n//! This processor is a thin wrapper around the CSV processor with tab delimiter.\n//! It extracts headers and one sample row from TSV files.\n\nuse super::{CsvProcessor, FileProcessor};\nuse anyhow::Result;\nuse std::path::Path;\n\n/// TSV processor that reuses CSV logic with tab delimiter.\npub struct TsvProcessor;\n\nimpl FileProcessor for TsvProcessor {\n    fn process(&self, content: &[u8], path: &Path) -> Result<String> {\n        let csv_processor = CsvProcessor;\n        match csv_processor.process_with_delimiter(content, b'\\t', path) {\n            Ok(mut result) => {\n                // Replace \"CSV\" with \"TSV\" in the output\n                result = result.replace(\"CSV Schema\", \"TSV Schema\");\n                Ok(result)\n            }\n            Err(e) => {\n                log::warn!(\n                    \"TSV parsing failed for {:?}: {}. Using raw text fallback.\",\n                    path,\n                    e\n                );\n                // Fallback to raw text\n                let fallback = super::DefaultTextProcessor;\n                fallback.process(content, path)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/filter.rs",
    "content": "//! This module contains pure filtering logic for files based on glob patterns.\n//!\n//! This module provides reusable, stateless functions for pattern matching and file filtering.\n\nuse bracoxide::explode;\nuse colored::*;\nuse globset::{Glob, GlobSet, GlobSetBuilder};\nuse log::{debug, warn};\nuse std::path::Path;\n\n/// FilterEngine encapsulates pattern-based file filtering logic.\n/// This handles the base patterns (A, B in the A,A',B,B' system).\n#[derive(Debug, Clone)]\npub struct FilterEngine {\n    include_globset: GlobSet,\n    exclude_globset: GlobSet,\n}\n\nimpl FilterEngine {\n    /// Create a new FilterEngine with the given patterns\n    pub fn new(include_patterns: &[String], exclude_patterns: &[String]) -> Self {\n        Self {\n            include_globset: build_globset(include_patterns),\n            exclude_globset: build_globset(exclude_patterns),\n        }\n    }\n\n    /// Check if a file matches the base patterns (A, B logic)\n    pub fn matches_patterns(&self, path: &Path) -> bool {\n        should_include_file(path, &self.include_globset, &self.exclude_globset)\n    }\n\n    /// Get access to the include globset (for advanced usage)\n    pub fn include_globset(&self) -> &GlobSet {\n        &self.include_globset\n    }\n\n    /// Get access to the exclude globset (for advanced usage)\n    pub fn exclude_globset(&self) -> &GlobSet {\n        &self.exclude_globset\n    }\n\n    /// Check if there are any include patterns\n    pub fn has_include_patterns(&self) -> bool {\n        !self.include_globset.is_empty()\n    }\n\n    /// Check if a file is excluded by exclude patterns\n    pub fn is_excluded(&self, path: &Path) -> bool {\n        self.exclude_globset.is_match(path)\n    }\n}\n\n/// Constructs a `GlobSet` from a list of glob patterns.\n///\n/// This function takes a slice of `String` patterns, attempts to convert each\n/// pattern into a `Glob`, and adds it to a `GlobSetBuilder`. If any pattern is\n/// invalid, it is ignored. The function then builds and returns a `GlobSet`.\n///\n/// # Arguments\n///\n/// * `patterns` - A slice of `String` containing glob patterns.\n///\n/// # Returns\n///\n/// * A `globset::GlobSet` containing all valid glob patterns from the input.\npub fn build_globset(patterns: &[String]) -> GlobSet {\n    let mut builder = GlobSetBuilder::new();\n\n    let mut expanded_patterns = Vec::new();\n    for pattern in patterns {\n        if pattern.contains('{') {\n            match explode(pattern) {\n                Ok(exp) => expanded_patterns.extend(exp),\n                Err(e) => warn!(\"⚠️ Invalid brace pattern '{}': {:?}\", pattern, e),\n            }\n        } else {\n            expanded_patterns.push(pattern.clone());\n        }\n    }\n\n    for pattern in expanded_patterns {\n        // If the pattern does not contain a '/' or the platform's separator, prepend \"**/\"\n        let normalized_pattern = if pattern.contains('/') {\n            pattern.trim_start_matches(\"./\").to_string()\n        } else {\n            format!(\"**/{}\", pattern.trim_start_matches(\"./\"))\n        };\n\n        match Glob::new(&normalized_pattern) {\n            Ok(glob) => {\n                builder.add(glob);\n                debug!(\"✅ Glob pattern added: '{}'\", normalized_pattern);\n            }\n            Err(_) => {\n                warn!(\"⚠️ Invalid pattern: '{}'\", normalized_pattern);\n            }\n        }\n    }\n\n    match builder.build() {\n        Ok(set) => set,\n        Err(e) => {\n            warn!(\"❌ Failed to build GlobSet: {e}\");\n            GlobSetBuilder::new()\n                .build()\n                .expect(\"empty GlobSet never fails\")\n        }\n    }\n}\n\n/// Determines whether a file should be included based on the provided glob patterns.\n///\n/// Note: The `path` argument must be a relative path (i.e. relative to the base directory)\n/// for the patterns to match as expected. Absolute paths will not yield correct matching.\n///\n/// # Arguments\n///\n/// * `path` - A relative path to the file that will be checked against the patterns.\n/// * `include_globset` - A GlobSet specifying which files to include.\n///   If empty, all files are considered included unless excluded.\n/// * `exclude_globset` - A GlobSet specifying which files to exclude.\n///\n/// # Returns\n///\n/// * `bool` - Returns `true` if the file should be included; otherwise, returns `false`.\n///\n/// # Behavior\n///\n/// When both include and exclude patterns match, exclude patterns take precedence.\npub fn should_include_file(\n    path: &Path,\n    include_globset: &GlobSet,\n    exclude_globset: &GlobSet,\n) -> bool {\n    // ~~~ Matching ~~~\n    let included = include_globset.is_match(path);\n    let excluded = exclude_globset.is_match(path);\n\n    // ~~~ Decision ~~~\n    let result = match (included, excluded) {\n        (true, true) => false,  // If both match, exclude takes precedence\n        (true, false) => true,  // If only included, include it\n        (false, true) => false, // If only excluded, exclude it\n        (false, false) => include_globset.is_empty(), // If no include patterns, include everything\n    };\n\n    debug!(\n        \"Result: {}, {}: {}, {}: {}, Path: {:?}\",\n        result,\n        \"included\".bold().green(),\n        included,\n        \"excluded\".bold().red(),\n        excluded,\n        path.display()\n    );\n    result\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/git.rs",
    "content": "//! This module handles git operations.\n\nuse anyhow::{Context, Result};\nuse git2::{DiffOptions, Repository};\nuse log::info;\nuse std::path::Path;\n\n/// Generates a git diff for the repository at the provided path.\n///\n/// This function compares the repository's HEAD tree with the index to produce a diff of staged changes.\n/// It also checks for unstaged changes (differences between the index and the working directory) and,\n/// if found, appends a notification to the output.\n///\n/// If there are no staged changes, the function returns a message in the format:\n/// `\"no diff between HEAD and index\"`.\n///\n/// # Arguments\n///\n/// * `repo_path` - A reference to the path of the git repository.\n///\n/// # Returns\n///\n/// * `Result<String>` - On success, returns either the diff (with an appended note if unstaged changes exist)\n///   or a message indicating that there is no diff between the compared git objects.\n///   In case of error, returns an appropriate error.\npub fn get_git_diff(repo_path: &Path) -> Result<String> {\n    info!(\"Opening repository at path: {:?}\", repo_path);\n    let repo = Repository::open(repo_path).context(\"Failed to open repository\")?;\n\n    let head = repo.head().context(\"Failed to get repository head\")?;\n    let head_tree = head.peel_to_tree().context(\"Failed to peel to tree\")?;\n\n    // Generate diff for staged changes (HEAD vs. index)\n    let staged_diff = repo\n        .diff_tree_to_index(\n            Some(&head_tree),\n            None,\n            Some(DiffOptions::new().ignore_whitespace(true)),\n        )\n        .context(\"Failed to generate diff for staged changes\")?;\n\n    let mut staged_diff_text = Vec::new();\n    staged_diff\n        .print(git2::DiffFormat::Patch, |_delta, _hunk, line| {\n            staged_diff_text.extend_from_slice(line.content());\n            true\n        })\n        .context(\"Failed to print staged diff\")?;\n\n    let staged_diff_output = String::from_utf8_lossy(&staged_diff_text).into_owned();\n\n    // If there is no staged diff, return a message indicating so.\n    if staged_diff_output.trim().is_empty() {\n        return Ok(\"no diff between HEAD and index\".to_string());\n    }\n\n    // Generate diff for unstaged changes (index vs. working directory)\n    let unstaged_diff = repo\n        .diff_index_to_workdir(None, Some(DiffOptions::new().ignore_whitespace(true)))\n        .context(\"Failed to generate diff for unstaged changes\")?;\n\n    let mut unstaged_diff_text = Vec::new();\n    unstaged_diff\n        .print(git2::DiffFormat::Patch, |_delta, _hunk, line| {\n            unstaged_diff_text.extend_from_slice(line.content());\n            true\n        })\n        .context(\"Failed to print unstaged diff\")?;\n\n    let unstaged_diff_output = String::from_utf8_lossy(&unstaged_diff_text).into_owned();\n\n    let mut output = staged_diff_output;\n    if !unstaged_diff_output.trim().is_empty() {\n        output.push_str(\"\\nNote: Some changes are not staged.\");\n    }\n\n    info!(\"Generated git diff successfully\");\n    Ok(output)\n}\n\n/// Generates a git diff between two branches for the repository at the provided path\n///\n/// # Arguments\n///\n/// * `repo_path` - A reference to the path of the git repository\n/// * `branch1` - The name of the first branch\n/// * `branch2` - The name of the second branch\n///\n/// # Returns\n///\n/// * `Result<String, git2::Error>` - The generated git diff as a string or an error\npub fn get_git_diff_between_branches(\n    repo_path: &Path,\n    branch1: &str,\n    branch2: &str,\n) -> Result<String> {\n    info!(\"Opening repository at path: {:?}\", repo_path);\n    let repo = Repository::open(repo_path).context(\"Failed to open repository\")?;\n\n    for branch in [branch1, branch2].iter() {\n        if !branch_exists(&repo, branch) {\n            return Err(anyhow::anyhow!(\"Branch {} doesn't exist!\", branch));\n        }\n    }\n\n    let branch1_commit = repo.revparse_single(branch1)?.peel_to_commit()?;\n    let branch2_commit = repo.revparse_single(branch2)?.peel_to_commit()?;\n\n    let branch1_tree = branch1_commit.tree()?;\n    let branch2_tree = branch2_commit.tree()?;\n\n    let diff = repo\n        .diff_tree_to_tree(\n            Some(&branch1_tree),\n            Some(&branch2_tree),\n            Some(DiffOptions::new().ignore_whitespace(true)),\n        )\n        .context(\"Failed to generate diff between branches\")?;\n\n    let mut diff_text = Vec::new();\n    diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {\n        diff_text.extend_from_slice(line.content());\n        true\n    })\n    .context(\"Failed to print diff\")?;\n\n    info!(\"Generated git diff between branches successfully\");\n    Ok(String::from_utf8_lossy(&diff_text).into_owned())\n}\n\n/// Retrieves the git log between two branches for the repository at the provided path\n///\n/// # Arguments\n///\n/// * `repo_path` - A reference to the path of the git repository\n/// * `branch1` - The name of the first branch (e.g., \"master\")\n/// * `branch2` - The name of the second branch (e.g., \"migrate-manifest-v3\")\n///\n/// # Returns\n///\n/// * `Result<String, git2::Error>` - The git log as a string or an error\npub fn get_git_log(repo_path: &Path, branch1: &str, branch2: &str) -> Result<String> {\n    info!(\"Opening repository at path: {:?}\", repo_path);\n    let repo = Repository::open(repo_path).context(\"Failed to open repository\")?;\n\n    for branch in [branch1, branch2].iter() {\n        if !branch_exists(&repo, branch) {\n            return Err(anyhow::anyhow!(\"Branch {} doesn't exist!\", branch));\n        }\n    }\n\n    let branch1_commit = repo.revparse_single(branch1)?.peel_to_commit()?;\n    let branch2_commit = repo.revparse_single(branch2)?.peel_to_commit()?;\n\n    let mut revwalk = repo.revwalk().context(\"Failed to create revwalk\")?;\n    revwalk\n        .push(branch2_commit.id())\n        .context(\"Failed to push branch2 commit to revwalk\")?;\n    revwalk\n        .hide(branch1_commit.id())\n        .context(\"Failed to hide branch1 commit from revwalk\")?;\n    revwalk.set_sorting(git2::Sort::REVERSE)?;\n\n    let mut log_text = String::new();\n    for oid in revwalk {\n        let oid = oid.context(\"Failed to get OID from revwalk\")?;\n        let commit = repo.find_commit(oid).context(\"Failed to find commit\")?;\n        log_text.push_str(&format!(\n            \"{} - {}\\n\",\n            &commit.id().to_string()[..7],\n            commit.summary().unwrap_or(\"No commit message\")\n        ));\n    }\n\n    info!(\"Retrieved git log successfully\");\n    Ok(log_text)\n}\n\n/// Checks if a git reference exists in the given repository\n///\n/// This function can validate any git reference including:\n/// - Local and remote branch names\n/// - Commit hashes (full or abbreviated)\n/// - Tags\n/// - Any reference that git rev-parse can resolve\n///\n/// # Arguments\n///\n/// * `repo` - A reference to the `Repository` where the reference should be checked\n/// * `branch_name` - A string slice that holds the name of the reference to check\n///\n/// # Returns\n///\n/// * `bool` - `true` if the reference exists, `false` otherwise\nfn branch_exists(repo: &Repository, branch_name: &str) -> bool {\n    repo.revparse_single(branch_name).is_ok()\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/lib.rs",
    "content": "//! Core library for code2prompt.\npub mod builtin_templates;\npub mod configuration;\npub mod file_processor;\npub mod filter;\npub mod git;\npub mod path;\npub mod selection;\npub mod session;\npub mod sort;\npub mod template;\npub mod tokenizer;\npub mod util;\n"
  },
  {
    "path": "crates/code2prompt-core/src/path.rs",
    "content": "//! This module contains the functions for traversing the directory and processing the files.\nuse crate::configuration::Code2PromptConfig;\nuse crate::file_processor;\nuse crate::filter::{build_globset, should_include_file};\nuse crate::sort::{FileSortMethod, sort_files, sort_tree};\nuse crate::tokenizer::count_tokens;\nuse crate::util::strip_utf8_bom;\nuse anyhow::Result;\nuse content_inspector::{ContentType, inspect};\nuse ignore::WalkBuilder;\nuse log::debug;\nuse rayon::prelude::*;\nuse serde::{Deserialize, Serialize};\nuse std::fs;\nuse std::io::Read;\nuse std::path::{Path, PathBuf};\nuse termtree::Tree;\n\n#[derive(Debug, Clone, Copy, Serialize, Deserialize)]\npub struct EntryMetadata {\n    pub is_dir: bool,\n    pub is_symlink: bool,\n}\n\nimpl From<&std::fs::Metadata> for EntryMetadata {\n    fn from(meta: &std::fs::Metadata) -> Self {\n        Self {\n            is_dir: meta.is_dir(),\n            is_symlink: meta.is_symlink(),\n        }\n    }\n}\n\n/// Represents a file entry with all its metadata and content\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct FileEntry {\n    pub path: String,\n    pub extension: String,\n    pub code: String,\n    pub token_count: usize,\n    pub metadata: EntryMetadata,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub mod_time: Option<u64>,\n}\n\n/// Represents a file that needs to be processed\n#[derive(Debug, Clone)]\nstruct FileToProcess {\n    /// Absolute path to the file\n    absolute_path: PathBuf,\n    /// Relative path from the root\n    relative_path: PathBuf,\n    /// File metadata\n    metadata: std::fs::Metadata,\n}\n\n/// Traverses the directory and returns the string representation of the tree and the vector of file entries.\n///\n/// This function uses the provided configuration to determine which files to include, how to format them,\n/// and how to structure the directory tree.\n///\n/// # Arguments\n///\n/// * `config` - Configuration object containing path, include/exclude patterns, and other settings\n/// * `selection_engine` - Optional SelectionEngine for advanced file selection with user actions\n///\n/// # Returns\n///\n/// * `Result<(String, Vec<FileEntry>)>` - A tuple containing the string representation of the directory\n///   tree and a vector of file entries\npub fn traverse_directory(\n    config: &Code2PromptConfig,\n    selection_engine: Option<&mut crate::selection::SelectionEngine>,\n) -> Result<(String, Vec<FileEntry>)> {\n    // Phase 1: Discovery - Build tree and collect files to process\n    let (tree, files_to_process) = discover_files(config, selection_engine)?;\n\n    // Phase 2: Processing - Process files in parallel\n    let mut files = process_files_parallel(files_to_process, config)?;\n\n    // Phase 3: Assembly - Sort and return results\n    assemble_results(tree, &mut files, config)\n}\n\n/// Phase 1: Discovery - Walk directories, build tree, and collect files that need processing\n///\n/// This phase is sequential because:\n/// - Directory walking is already optimized\n/// - Tree building needs sequential structure\n/// - Selection engine has caching that would need synchronization\nfn discover_files(\n    config: &Code2PromptConfig,\n    mut selection_engine: Option<&mut crate::selection::SelectionEngine>,\n) -> Result<(Tree<String>, Vec<FileToProcess>)> {\n    let canonical_root_path = config.path.canonicalize()?;\n    let parent_directory = display_name(&canonical_root_path);\n\n    let include_globset = build_globset(&config.include_patterns);\n    let exclude_globset = build_globset(&config.exclude_patterns);\n\n    // Build the Walker\n    let walker = WalkBuilder::new(&canonical_root_path)\n        .hidden(!config.hidden)\n        .git_ignore(!config.no_ignore)\n        .follow_links(config.follow_symlinks)\n        .build()\n        .filter_map(|entry| entry.ok());\n\n    // Build the Tree\n    let mut tree = Tree::new(parent_directory.to_owned());\n    let mut files_to_process = Vec::new();\n\n    for entry in walker {\n        let path = entry.path();\n        if let Ok(relative_path) = path.strip_prefix(&canonical_root_path) {\n            // Use SelectionEngine if available, otherwise fall back to pattern matching\n            let entry_match = if let Some(engine) = selection_engine.as_mut() {\n                engine.is_selected(relative_path)\n            } else {\n                should_include_file(relative_path, &include_globset, &exclude_globset)\n            };\n\n            // Directory Tree\n            let include_in_tree = config.full_directory_tree || entry_match;\n\n            if include_in_tree {\n                let mut current_tree = &mut tree;\n                for component in relative_path.components() {\n                    let component_str = component.as_os_str().to_string_lossy().to_string();\n                    current_tree = if let Some(pos) = current_tree\n                        .leaves\n                        .iter_mut()\n                        .position(|child| child.root == component_str)\n                    {\n                        &mut current_tree.leaves[pos]\n                    } else {\n                        let new_tree = Tree::new(component_str.clone());\n                        current_tree.leaves.push(new_tree);\n                        current_tree.leaves.last_mut().unwrap()\n                    };\n                }\n            }\n\n            // Collect files for processing\n            if path.is_file()\n                && entry_match\n                && let Ok(metadata) = entry.metadata()\n            {\n                files_to_process.push(FileToProcess {\n                    absolute_path: path.to_path_buf(),\n                    relative_path: relative_path.to_path_buf(),\n                    metadata,\n                });\n            }\n        }\n    }\n\n    Ok((tree, files_to_process))\n}\n\n/// Phase 2: Processing - Process files in parallel using rayon\n///\n/// This phase processes files in parallel:\n/// - Read file contents (I/O bound)\n/// - Process file content (CPU/I/O bound)\n/// - Tokenize if enabled (CPU bound)\n/// - Build FileEntry structures\nfn process_files_parallel(\n    files_to_process: Vec<FileToProcess>,\n    config: &Code2PromptConfig,\n) -> Result<Vec<FileEntry>> {\n    // Process files in parallel with rayon\n    let files: Vec<Option<FileEntry>> = files_to_process\n        .par_iter()\n        .map(|file_info| process_single_file(file_info, config))\n        .collect();\n\n    // Filter out None values (files that failed to process or were empty)\n    Ok(files.into_iter().flatten().collect())\n}\n\n/// Read file with single-pass binary detection\n///\n/// Reads file incrementally: first 8KB for binary detection, then remainder if text.\nfn read_file_with_binary_check(path: &Path, file_size: u64) -> std::io::Result<Option<Vec<u8>>> {\n    const SAMPLE_SIZE: usize = 8192;\n\n    let mut file = fs::File::open(path)?;\n    let mut buffer = Vec::with_capacity(file_size.min(1024 * 1024 * 10) as usize); // Cap at 10MB initial allocation\n\n    // Read first chunk for binary detection\n    let bytes_to_read = SAMPLE_SIZE.min(file_size as usize);\n    let mut sample_buffer = vec![0u8; bytes_to_read];\n    file.read_exact(&mut sample_buffer)?;\n\n    // Check if binary\n    if inspect(&sample_buffer) == ContentType::BINARY {\n        return Ok(None); // Return None for binary files\n    }\n\n    // It's text! Add sample to buffer and read the rest\n    buffer.extend_from_slice(&sample_buffer);\n\n    // Read remaining bytes if file is larger than sample\n    if file_size > SAMPLE_SIZE as u64 {\n        file.read_to_end(&mut buffer)?;\n    }\n\n    Ok(Some(buffer))\n}\n\n/// Process a single file and return its FileEntry representation\nfn process_single_file(file_info: &FileToProcess, config: &Code2PromptConfig) -> Option<FileEntry> {\n    let path = &file_info.absolute_path;\n    let relative_path = &file_info.relative_path;\n    let metadata = &file_info.metadata;\n\n    let code_bytes = match read_file_with_binary_check(path, metadata.len()) {\n        Ok(Some(bytes)) => bytes,\n        Ok(None) => {\n            debug!(\"Skipped binary file: {}\", path.display());\n            return None;\n        }\n        Err(e) => {\n            debug!(\"Failed to read file {}: {}\", path.display(), e);\n            return None;\n        }\n    };\n\n    let clean_bytes = strip_utf8_bom(&code_bytes);\n\n    // Get appropriate processor for file extension\n    let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or(\"\");\n    let processor = file_processor::get_processor_for_extension(extension);\n\n    // Process file content\n    let code = match processor.process(clean_bytes, path) {\n        Ok(processed) => processed,\n        Err(e) => {\n            log::warn!(\n                \"File processing failed for {}: {}. Using raw text fallback.\",\n                path.display(),\n                e\n            );\n            String::from_utf8_lossy(clean_bytes).into_owned()\n        }\n    };\n\n    // Wrap code block\n    let code_block = wrap_code_block(&code, extension, config.line_numbers, config.no_codeblock);\n\n    // Filter empty or invalid files\n    if code.trim().is_empty() || code.contains(char::REPLACEMENT_CHARACTER) {\n        debug!(\"Excluded file (empty or invalid UTF-8): {}\", path.display());\n        return None;\n    }\n\n    // Build filepath\n    let file_path = if config.absolute_path {\n        path.to_string_lossy().to_string()\n    } else {\n        relative_path.to_string_lossy().to_string()\n    };\n\n    // Always calculate token count in parallel (amortized by I/O wait time)\n    // This enables zero-overhead token counting regardless of display preferences\n    let token_count = count_tokens(&code, &config.encoding);\n\n    // Get modification time if date sorting is requested\n    let mod_time = if let Some(method) = config.sort_method {\n        if method == FileSortMethod::DateAsc || method == FileSortMethod::DateDesc {\n            metadata\n                .modified()\n                .ok()\n                .and_then(|mtime| mtime.duration_since(std::time::SystemTime::UNIX_EPOCH).ok())\n                .map(|d| d.as_secs())\n        } else {\n            None\n        }\n    } else {\n        None\n    };\n\n    debug!(target: \"included_files\", \"Included file: {}\", file_path);\n\n    Some(FileEntry {\n        path: file_path,\n        extension: extension.to_string(),\n        code: code_block,\n        token_count,\n        metadata: EntryMetadata::from(metadata),\n        mod_time,\n    })\n}\n\n/// Phase 3: Assembly - Sort results and return\nfn assemble_results(\n    mut tree: Tree<String>,\n    files: &mut [FileEntry],\n    config: &Code2PromptConfig,\n) -> Result<(String, Vec<FileEntry>)> {\n    // Sort tree and files\n    sort_tree(&mut tree, config.sort_method);\n    sort_files(files, config.sort_method);\n\n    Ok((tree.to_string(), files.to_owned()))\n}\n\n/// Returns the file name or the string representation of the path.\n///\n/// # Arguments\n///\n/// * `p` - The path to label.\n///\n/// # Returns\n///\n/// * `String` - The file name or string representation of the path.\npub fn display_name<P: AsRef<Path>>(p: P) -> String {\n    let path = p.as_ref();\n    // File name if available\n    if let Some(name) = path.file_name() {\n        return name.to_string_lossy().into_owned();\n    }\n    // Current directory name\n    if let Ok(cwd) = std::env::current_dir()\n        && let Some(name) = cwd.file_name()\n    {\n        return name.to_string_lossy().into_owned();\n    }\n    // Fallback\n    \".\".to_string()\n}\n\n/// Wraps the code block with a delimiter and adds line numbers if required.\n///\n/// # Arguments\n///\n/// * `code` - The code block to wrap.\n/// * `extension` - The file extension of the code block.\n/// * `line_numbers` - Whether to add line numbers to the code.\n/// * `no_codeblock` - Whether to not wrap the code block with a delimiter.\n///\n/// # Returns\n///\n/// * `String` - The wrapped code block.\npub fn wrap_code_block(\n    code: &str,\n    extension: &str,\n    line_numbers: bool,\n    no_codeblock: bool,\n) -> String {\n    let delimiter = \"`\".repeat(3);\n    let mut code_with_line_numbers = String::new();\n\n    if line_numbers {\n        for (line_number, line) in code.lines().enumerate() {\n            code_with_line_numbers.push_str(&format!(\"{:4} | {}\\n\", line_number + 1, line));\n        }\n    } else {\n        code_with_line_numbers = code.to_string();\n    }\n\n    if no_codeblock {\n        code_with_line_numbers\n    } else {\n        format!(\n            \"{}{}\\n{}\\n{}\",\n            delimiter, extension, code_with_line_numbers, delimiter\n        )\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/selection.rs",
    "content": "//! This module contains the SelectionEngine that handles user file selection with precedence rules.\n//!\n//! The SelectionEngine implements the A,A',B,B' system where:\n//! - A, B: Base patterns (handled by FilterEngine)\n//! - A', B': User actions with precedence rules (specific > generic, recent > old)\n\nuse crate::filter::FilterEngine;\nuse std::collections::HashMap;\nuse std::path::{Path, PathBuf};\nuse std::time::SystemTime;\n\n/// Represents a user action on a file or directory\n#[derive(Debug, Clone)]\npub struct SelectionAction {\n    pub path: PathBuf,\n    pub action: ActionType,\n    pub timestamp: SystemTime,\n    pub specificity: u32, // Higher = more specific (more path components)\n}\n\n/// Type of selection action\n#[derive(Debug, Clone, PartialEq)]\npub enum ActionType {\n    Include,\n    Exclude,\n}\n\n/// SelectionEngine handles both pattern-based filtering and user actions\n/// with clear precedence rules: specific > generic, recent > old\n#[derive(Clone)]\npub struct SelectionEngine {\n    /// Base pattern filtering (A, B in A,A',B,B' system)\n    filter_engine: FilterEngine,\n\n    /// User actions (A', B' in A,A',B,B' system)\n    user_actions: Vec<SelectionAction>,\n\n    /// Cache for performance\n    cache: HashMap<PathBuf, bool>,\n\n    /// Default behavior when no patterns or user actions match\n    deselected_by_default: bool,\n}\n\nimpl SelectionEngine {\n    /// Create a new SelectionEngine with base patterns\n    pub fn new(\n        include_patterns: Vec<String>,\n        exclude_patterns: Vec<String>,\n        deselected_by_default: bool,\n    ) -> Self {\n        Self {\n            filter_engine: FilterEngine::new(&include_patterns, &exclude_patterns),\n            user_actions: Vec::new(),\n            cache: HashMap::new(),\n            deselected_by_default,\n        }\n    }\n\n    /// The core decision method: determines if a file should be selected\n    /// Uses precedence rules: specific > generic, recent > old\n    pub fn is_selected(&mut self, path: &Path) -> bool {\n        // Check cache first for performance\n        if let Some(&cached) = self.cache.get(path) {\n            return cached;\n        }\n\n        let result = self.compute_selection(path);\n        self.cache.insert(path.to_path_buf(), result);\n        result\n    }\n\n    /// Compute selection without caching\n    fn compute_selection(&self, path: &Path) -> bool {\n        // Rule 1: Find the most specific and recent user action\n        if let Some(action) = self.find_applicable_user_action(path) {\n            return action.action == ActionType::Include;\n        }\n\n        // Rule 2: Fall back to existing FilterEngine logic (A, B)\n        if self.filter_engine.has_include_patterns() {\n            // If there are include patterns, use them\n            self.filter_engine.matches_patterns(path)\n        } else {\n            // No include patterns: default behavior depends on deselected_by_default\n            if self.deselected_by_default {\n                false\n            } else {\n                !self.filter_engine.is_excluded(path)\n            }\n        }\n    }\n\n    /// Find the most applicable user action using precedence rules\n    fn find_applicable_user_action(&self, path: &Path) -> Option<&SelectionAction> {\n        let applicable_actions: Vec<&SelectionAction> = self\n            .user_actions\n            .iter()\n            .filter(|action| self.action_applies_to_path(action, path))\n            .collect();\n\n        if applicable_actions.is_empty() {\n            return None;\n        }\n\n        // Apply precedence rules: specific > generic, recent > old\n        applicable_actions.into_iter().max_by(|a, b| {\n            // First compare specificity (higher is better)\n            match a.specificity.cmp(&b.specificity) {\n                std::cmp::Ordering::Equal => {\n                    // If same specificity, compare timestamp (more recent is better)\n                    a.timestamp.cmp(&b.timestamp)\n                }\n                other => other,\n            }\n        })\n    }\n\n    /// Check if a user action applies to a given path\n    fn action_applies_to_path(&self, action: &SelectionAction, path: &Path) -> bool {\n        // Exact match\n        if action.path == path {\n            return true;\n        }\n\n        // Directory action applies to all children\n        if path.starts_with(&action.path) {\n            return true;\n        }\n\n        false\n    }\n\n    /// Calculate specificity score for a path (more components = more specific)\n    fn calculate_specificity(&self, path: &Path) -> u32 {\n        path.components().count() as u32\n    }\n\n    /// User interaction: include a file or directory\n    pub fn include_file(&mut self, path: PathBuf) {\n        self.add_user_action(path, ActionType::Include);\n    }\n\n    /// User interaction: exclude a file or directory\n    pub fn exclude_file(&mut self, path: PathBuf) {\n        self.add_user_action(path, ActionType::Exclude);\n    }\n\n    /// User interaction: toggle selection state\n    pub fn toggle_file(&mut self, path: PathBuf) {\n        let current_state = self.is_selected(&path);\n        let new_action = if current_state {\n            ActionType::Exclude\n        } else {\n            ActionType::Include\n        };\n        self.add_user_action(path, new_action);\n    }\n\n    /// Add a user action with timestamp and specificity\n    fn add_user_action(&mut self, path: PathBuf, action: ActionType) {\n        let specificity = self.calculate_specificity(&path);\n        let user_action = SelectionAction {\n            path,\n            action,\n            timestamp: SystemTime::now(),\n            specificity,\n        };\n\n        self.user_actions.push(user_action);\n        self.cache.clear(); // Invalidate cache when actions change\n    }\n\n    /// Get all currently selected files by scanning the filesystem\n    pub fn get_selected_files(&mut self, root_path: &Path) -> Result<Vec<PathBuf>, std::io::Error> {\n        // If we have user actions, return files based on those actions\n        if !self.user_actions.is_empty() {\n            let mut selected = Vec::new();\n\n            // Clone the actions to avoid borrow checker issues\n            let actions = self.user_actions.clone();\n\n            // Collect files from user actions that are includes\n            for action in &actions {\n                if action.action == ActionType::Include {\n                    // Check if this action is still the winning action for this path\n                    if self.is_selected(&action.path) {\n                        selected.push(action.path.clone());\n                    }\n                }\n            }\n\n            // Remove duplicates and sort\n            selected.sort();\n            selected.dedup();\n            return Ok(selected);\n        }\n\n        // Otherwise, scan filesystem for pattern matches\n        let mut selected = Vec::new();\n        self.collect_selected_files_recursive(root_path, root_path, &mut selected)?;\n        Ok(selected)\n    }\n\n    /// Recursively collect selected files\n    fn collect_selected_files_recursive(\n        &mut self,\n        root_path: &Path,\n        current_dir: &Path,\n        selected: &mut Vec<PathBuf>,\n    ) -> Result<(), std::io::Error> {\n        for entry in std::fs::read_dir(current_dir)? {\n            let entry = entry?;\n            let path = entry.path();\n\n            // Convert to relative path for selection checking\n            let relative_path = if let Ok(rel) = path.strip_prefix(root_path) {\n                rel\n            } else {\n                continue;\n            };\n\n            if self.is_selected(relative_path) {\n                if path.is_file() {\n                    selected.push(relative_path.to_path_buf());\n                } else if path.is_dir() {\n                    // Recursively check subdirectories\n                    self.collect_selected_files_recursive(root_path, &path, selected)?;\n                }\n            }\n        }\n        Ok(())\n    }\n\n    /// Clear all user actions (reset to pattern-only behavior)\n    pub fn clear_user_actions(&mut self) {\n        self.user_actions.clear();\n        self.cache.clear();\n    }\n\n    /// Get the number of user actions\n    pub fn user_action_count(&self) -> usize {\n        self.user_actions.len()\n    }\n\n    /// Check if there are any user actions\n    pub fn has_user_actions(&self) -> bool {\n        !self.user_actions.is_empty()\n    }\n\n    /// Get access to the underlying filter engine\n    pub fn filter_engine(&self) -> &FilterEngine {\n        &self.filter_engine\n    }\n\n    /// Set whether the engine should default to deselected\n    pub fn set_deselected_by_default(&mut self, value: bool) {\n        self.deselected_by_default = value;\n        self.cache.clear();\n    }\n}\n\nimpl std::fmt::Debug for SelectionEngine {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"SelectionEngine\")\n            .field(\"filter_engine\", &self.filter_engine)\n            .field(\"user_actions\", &self.user_actions)\n            .field(\"cache_size\", &self.cache.len())\n            .field(\"deselected_by_default\", &self.deselected_by_default)\n            .finish()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_specificity_calculation() {\n        let engine = SelectionEngine::new(vec![], vec![], false);\n\n        assert_eq!(engine.calculate_specificity(Path::new(\"file.rs\")), 1);\n        assert_eq!(engine.calculate_specificity(Path::new(\"src/main.rs\")), 2);\n        assert_eq!(\n            engine.calculate_specificity(Path::new(\"src/utils/helper.rs\")),\n            3\n        );\n    }\n\n    #[test]\n    fn test_precedence_rules() {\n        let mut engine = SelectionEngine::new(vec![], vec![], false);\n\n        // Add less specific action first\n        engine.exclude_file(PathBuf::from(\"src\"));\n\n        // Add more specific action later\n        engine.include_file(PathBuf::from(\"src/main.rs\"));\n\n        // More specific should win\n        assert!(!engine.is_selected(Path::new(\"src/lib.rs\"))); // Excluded by src/\n        assert!(engine.is_selected(Path::new(\"src/main.rs\"))); // Included specifically\n    }\n\n    #[test]\n    fn test_recent_wins_over_old() {\n        let mut engine = SelectionEngine::new(vec![], vec![], false);\n\n        // First action\n        engine.exclude_file(PathBuf::from(\"main.rs\"));\n        assert!(!engine.is_selected(Path::new(\"main.rs\")));\n\n        // More recent action with same specificity\n        engine.include_file(PathBuf::from(\"main.rs\"));\n        assert!(engine.is_selected(Path::new(\"main.rs\")));\n    }\n\n    #[test]\n    fn test_deselected_by_default() {\n        let mut engine = SelectionEngine::new(vec![], vec![], true);\n\n        // By default everything is deselected\n        assert!(!engine.is_selected(Path::new(\"main.rs\")));\n        assert!(!engine.is_selected(Path::new(\"src/lib.rs\")));\n\n        // User action should still work\n        engine.include_file(PathBuf::from(\"main.rs\"));\n        assert!(engine.is_selected(Path::new(\"main.rs\")));\n        assert!(!engine.is_selected(Path::new(\"src/lib.rs\")));\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/session.rs",
    "content": "//! This module defines a Code2promptSession struct that provide a stateful interface to code2prompt-core.\n//! It allows you to load codebase data, Git info, and render prompts using a template.\n\nuse anyhow::{Context, Result};\nuse serde::Serialize;\nuse std::collections::HashMap;\nuse std::path::PathBuf;\n\nuse crate::configuration::Code2PromptConfig;\nuse crate::git::{get_git_diff, get_git_diff_between_branches, get_git_log};\nuse crate::path::{FileEntry, display_name, traverse_directory, wrap_code_block};\nuse crate::selection::SelectionEngine;\nuse crate::template::{OutputFormat, handlebars_setup, render_template};\nuse crate::tokenizer::{TokenizerType, count_tokens};\n\n/// Represents a live session that holds stateful data about the user's codebase,\n/// including which files have been added or removed, or other data that evolves over time.\n#[derive(Debug, Clone)]\npub struct Code2PromptSession {\n    pub config: Code2PromptConfig,\n    pub selection_engine: SelectionEngine,\n    pub data: SessionData,\n}\n\n/// Represents the collected data about the code (tree + files) and optional Git info.\n/// The session loads these pieces separately, so you can manage them step by step.\n#[derive(Debug, Default, Clone)]\npub struct SessionData {\n    pub absolute_code_path: Option<String>,\n    pub source_tree: Option<String>,\n    pub files: Option<Vec<FileEntry>>,\n    pub stats: Option<serde_json::Value>,\n    pub git_diff: Option<String>,\n    pub git_diff_branch: Option<String>,\n    pub git_log_branch: Option<String>,\n}\n\n/// Zero-copy template context for rendering\n/// Uses references to avoid deep copying of heavy data\n#[derive(Serialize)]\npub struct TemplateContext<'a> {\n    pub absolute_code_path: &'a str,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub source_tree: &'a Option<String>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub files: Option<&'a [FileEntry]>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub git_diff: &'a Option<String>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub git_diff_branch: &'a Option<String>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub git_log_branch: &'a Option<String>,\n\n    #[serde(flatten)]\n    pub user_variables: &'a HashMap<String, String>,\n}\n\n/// Encapsulates the final rendered prompt and some metadata\n#[derive(Debug)]\npub struct RenderedPrompt {\n    pub prompt: String,\n    pub directory_name: String,\n    pub token_count: usize,\n    pub model_info: &'static str,\n    pub files: Vec<String>,\n}\n\nimpl Code2PromptSession {\n    /// Creates a new session with SelectionEngine for pattern-based and user-driven file selection\n    pub fn new(config: Code2PromptConfig) -> Self {\n        let selection_engine = SelectionEngine::new(\n            config.include_patterns.clone(),\n            config.exclude_patterns.clone(),\n            config.deselected,\n        );\n\n        Self {\n            selection_engine,\n            config,\n            data: SessionData::default(),\n        }\n    }\n\n    /// Add pattern and recreate SelectionEngine\n    pub fn add_include_pattern(&mut self, pattern: String) -> &mut Self {\n        self.config.include_patterns.push(pattern);\n        // Recreate SelectionEngine with new patterns\n        self.selection_engine = SelectionEngine::new(\n            self.config.include_patterns.clone(),\n            self.config.exclude_patterns.clone(),\n            self.config.deselected,\n        );\n        self\n    }\n\n    pub fn add_exclude_pattern(&mut self, pattern: String) -> &mut Self {\n        self.config.exclude_patterns.push(pattern);\n        // Recreate SelectionEngine with new patterns\n        self.selection_engine = SelectionEngine::new(\n            self.config.include_patterns.clone(),\n            self.config.exclude_patterns.clone(),\n            self.config.deselected,\n        );\n        self\n    }\n\n    /// User interaction: include a file (delegates to SelectionEngine)\n    pub fn select_file(&mut self, path: PathBuf) -> &mut Self {\n        let relative_path = if path.is_absolute() {\n            path.strip_prefix(&self.config.path)\n                .unwrap_or(&path)\n                .to_path_buf()\n        } else {\n            path\n        };\n\n        self.selection_engine.include_file(relative_path);\n        self\n    }\n\n    /// User interaction: exclude a file (delegates to SelectionEngine)\n    pub fn deselect_file(&mut self, path: PathBuf) -> &mut Self {\n        let relative_path = if path.is_absolute() {\n            path.strip_prefix(&self.config.path)\n                .unwrap_or(&path)\n                .to_path_buf()\n        } else {\n            path\n        };\n\n        self.selection_engine.exclude_file(relative_path);\n        self\n    }\n\n    /// User interaction: toggle file selection (delegates to SelectionEngine)\n    pub fn toggle_file_selection(&mut self, path: PathBuf) -> &mut Self {\n        let relative_path = if path.is_absolute() {\n            path.strip_prefix(&self.config.path)\n                .unwrap_or(&path)\n                .to_path_buf()\n        } else {\n            path\n        };\n\n        self.selection_engine.toggle_file(relative_path);\n        self\n    }\n\n    /// Check if a file is selected (delegates to SelectionEngine)\n    pub fn is_file_selected(&mut self, path: &std::path::Path) -> bool {\n        let relative_path = if path.is_absolute() {\n            path.strip_prefix(&self.config.path).unwrap_or(path)\n        } else {\n            path\n        };\n\n        self.selection_engine.is_selected(relative_path)\n    }\n\n    /// Get all currently selected files (delegates to SelectionEngine)\n    pub fn get_selected_files(&mut self) -> Result<Vec<PathBuf>> {\n        Ok(self\n            .selection_engine\n            .get_selected_files(&self.config.path)?)\n    }\n\n    /// Clear all user actions (reset to pattern-only behavior)\n    pub fn clear_user_actions(&mut self) -> &mut Self {\n        self.selection_engine.clear_user_actions();\n        self\n    }\n\n    /// Check if there are any user actions beyond base patterns\n    pub fn has_user_actions(&self) -> bool {\n        self.selection_engine.has_user_actions()\n    }\n\n    /// Set deselected by default and update selection engine\n    pub fn set_deselected(&mut self, value: bool) -> &mut Self {\n        self.config.deselected = value;\n        self.selection_engine.set_deselected_by_default(value);\n        self\n    }\n\n    /// Loads the codebase data (source tree and file list) into the session.\n    pub fn load_codebase(&mut self) -> Result<()> {\n        let (tree, files) = traverse_directory(&self.config, Some(&mut self.selection_engine))\n            .with_context(|| \"Failed to traverse directory\")?;\n\n        // Store absolute_code_path as Single Source of Truth\n        self.data.absolute_code_path = Some(display_name(&self.config.path));\n        self.data.source_tree = Some(tree);\n        self.data.files = Some(files);\n\n        Ok(())\n    }\n\n    /// Loads the Git diff into the session data.\n    pub fn load_git_diff(&mut self) -> Result<()> {\n        let diff = get_git_diff(&self.config.path)?;\n        self.data.git_diff = Some(diff);\n        Ok(())\n    }\n\n    /// Loads the Git diff between two branches into the session data.\n    pub fn load_git_diff_between_branches(&mut self) -> Result<()> {\n        if let Some((b1, b2)) = &self.config.diff_branches {\n            let diff = get_git_diff_between_branches(&self.config.path, b1, b2)?;\n            self.data.git_diff_branch = Some(diff);\n        }\n        Ok(())\n    }\n\n    /// Loads the Git log between two branches into the session data.\n    pub fn load_git_log_between_branches(&mut self) -> Result<()> {\n        if let Some((b1, b2)) = &self.config.log_branches {\n            let log_output = get_git_log(&self.config.path, b1, b2)?;\n            self.data.git_log_branch = Some(log_output);\n        }\n        Ok(())\n    }\n\n    /// Constructs a zero-copy template context for rendering.\n    pub fn build_template_data(&self) -> TemplateContext<'_> {\n        TemplateContext {\n            absolute_code_path: self.data.absolute_code_path.as_deref().unwrap_or(\"unknown\"),\n            source_tree: &self.data.source_tree,\n            files: self.data.files.as_deref(),\n            git_diff: &self.data.git_diff,\n            git_diff_branch: &self.data.git_diff_branch,\n            git_log_branch: &self.data.git_log_branch,\n            user_variables: &self.config.user_variables,\n        }\n    }\n\n    /// Renders the final prompt given a template context. Returns both\n    /// the rendered prompt and the token count information.\n    pub fn render_prompt(&self, template_context: &TemplateContext) -> Result<RenderedPrompt> {\n        // ~~~ Template selection ~~~\n        let mut template_str = self.config.template_str.clone();\n        let mut template_name = self.config.template_name.clone();\n        if self.config.template_str.is_empty() {\n            template_str = match self.config.output_format {\n                OutputFormat::Markdown => include_str!(\"./default_template_md.hbs\").to_string(),\n                OutputFormat::Xml | OutputFormat::Json => {\n                    include_str!(\"./default_template_xml.hbs\").to_string()\n                }\n            };\n            template_name = match self.config.output_format {\n                OutputFormat::Markdown => \"markdown\".to_string(),\n                OutputFormat::Xml | OutputFormat::Json => \"xml\".to_string(),\n            };\n        }\n\n        // ~~~ Rendering ~~~\n        let handlebars = handlebars_setup(&template_str, &template_name)?;\n        let template_content = render_template(&handlebars, &template_name, template_context)?;\n\n        // ~~~ Informations ~~~\n        let tokenizer_type: TokenizerType = self.config.encoding;\n        // Always use the cached calculation: Σ(FileTokens) + TemplateOverhead\n        // This avoids re-tokenizing the entire rendered output (sequential bottleneck)\n        let token_count = self.calculate_token_count_from_cache(&tokenizer_type);\n\n        let model_info = tokenizer_type.description();\n        let directory_name = template_context.absolute_code_path.to_string();\n        let files: Vec<String> = self\n            .data\n            .files\n            .as_ref()\n            .map(|files| files.iter().map(|file| file.path.clone()).collect())\n            .unwrap_or_default();\n\n        // ~~~ Final output format ~~~\n        let final_output = match self.config.output_format {\n            OutputFormat::Json => {\n                let json_data = serde_json::json!({\n                    \"prompt\": template_content,\n                    \"directory_name\": directory_name.clone(),\n                    \"token_count\": token_count,\n                    \"model_info\": model_info,\n                    \"files\": files.clone(),\n                });\n                serde_json::to_string_pretty(&json_data)?\n            }\n            _ => template_content,\n        };\n\n        Ok(RenderedPrompt {\n            prompt: final_output,\n            directory_name,\n            token_count,\n            model_info,\n            files,\n        })\n    }\n\n    /// Calculate exact token count using cached per-file token counts + skeleton rendering\n    ///\n    /// This method provides precise token counting by:\n    /// 1. Summing the cached per-file token counts (from actual content tokenized in parallel)\n    /// 2. Rendering a \"skeleton\" template with empty file contents to get structural tokens\n    /// 3. Adding them together for an exact count\n    ///\n    /// This approach avoids re-tokenizing the entire rendered output (sequential bottleneck).\n    ///\n    /// # Arguments\n    ///\n    /// * `tokenizer_type` - The tokenizer to use for tokenization\n    ///\n    /// # Returns\n    ///\n    /// * `usize` - The exact total token count\n    fn calculate_token_count_from_cache(&self, tokenizer_type: &TokenizerType) -> usize {\n        // Sum up cached per-file token counts (tokens from actual file content)\n        let files_token_count: usize = self\n            .data\n            .files\n            .as_ref()\n            .map(|files| files.iter().map(|file| file.token_count).sum())\n            .unwrap_or(0);\n\n        // Calculate exact structural/template overhead using skeleton rendering\n        let structural_tokens = self.calculate_structural_tokens(tokenizer_type);\n\n        files_token_count + structural_tokens\n    }\n\n    /// Calculate structural tokens by rendering a skeleton template\n    ///\n    /// Creates FileEntry \"skeletons\" with empty code blocks but same structure,\n    /// renders the template, and counts tokens. This gives us the exact token count\n    /// for everything except the actual file content (tree, headers, wrappers, git info).\n    ///\n    /// # Arguments\n    ///\n    /// * `tokenizer_type` - The tokenizer to use for counting\n    ///\n    /// # Returns\n    ///\n    /// * `usize` - The number of structural tokens\n    fn calculate_structural_tokens(&self, tokenizer_type: &TokenizerType) -> usize {\n        // Create skeleton file entries (empty code, but same structure/metadata)\n        let skeleton_files: Option<Vec<FileEntry>> = self.data.files.as_ref().map(|files| {\n            files\n                .iter()\n                .map(|file| {\n                    // Create empty code block with same wrapping structure\n                    let empty_code_block = wrap_code_block(\n                        \"\",\n                        &file.extension,\n                        self.config.line_numbers,\n                        self.config.no_codeblock,\n                    );\n\n                    FileEntry {\n                        path: file.path.clone(),\n                        extension: file.extension.clone(),\n                        code: empty_code_block,\n                        token_count: 0, // Not used in skeleton\n                        metadata: file.metadata,\n                        mod_time: file.mod_time,\n                    }\n                })\n                .collect()\n        });\n\n        // Build skeleton template context (same structure, but with empty file contents)\n        let skeleton_context = TemplateContext {\n            absolute_code_path: self.data.absolute_code_path.as_deref().unwrap_or(\"unknown\"),\n            source_tree: &self.data.source_tree,\n            files: skeleton_files.as_deref(),\n            git_diff: &self.data.git_diff,\n            git_diff_branch: &self.data.git_diff_branch,\n            git_log_branch: &self.data.git_log_branch,\n            user_variables: &self.config.user_variables,\n        };\n\n        // Render skeleton template\n        let template_str = if self.config.template_str.is_empty() {\n            match self.config.output_format {\n                OutputFormat::Markdown => include_str!(\"./default_template_md.hbs\").to_string(),\n                OutputFormat::Xml | OutputFormat::Json => {\n                    include_str!(\"./default_template_xml.hbs\").to_string()\n                }\n            }\n        } else {\n            self.config.template_str.clone()\n        };\n\n        let template_name = if self.config.template_name.is_empty() {\n            match self.config.output_format {\n                OutputFormat::Markdown => \"markdown\".to_string(),\n                OutputFormat::Xml | OutputFormat::Json => \"xml\".to_string(),\n            }\n        } else {\n            self.config.template_name.clone()\n        };\n\n        // Render and count tokens\n        match handlebars_setup(&template_str, &template_name) {\n            Ok(handlebars) => {\n                match render_template(&handlebars, &template_name, &skeleton_context) {\n                    Ok(skeleton_rendered) => count_tokens(&skeleton_rendered, tokenizer_type),\n                    Err(_) => {\n                        // Fallback to simple estimation if rendering fails\n                        self.fallback_structural_estimate(tokenizer_type)\n                    }\n                }\n            }\n            Err(_) => {\n                // Fallback to simple estimation if handlebars setup fails\n                self.fallback_structural_estimate(tokenizer_type)\n            }\n        }\n    }\n\n    /// Fallback estimation when skeleton rendering fails\n    ///\n    /// Uses a simple heuristic based on tree/git sizes as a safety net.\n    ///\n    /// # Arguments\n    ///\n    /// * `tokenizer_type` - The tokenizer to use\n    ///\n    /// # Returns\n    ///\n    /// * `usize` - Estimated structural tokens\n    fn fallback_structural_estimate(&self, tokenizer_type: &TokenizerType) -> usize {\n        let mut total_chars = 0;\n\n        if let Some(tree) = &self.data.source_tree {\n            total_chars += tree.len();\n        }\n        if let Some(diff) = &self.data.git_diff {\n            total_chars += diff.len();\n        }\n        if let Some(diff_branch) = &self.data.git_diff_branch {\n            total_chars += diff_branch.len();\n        }\n        if let Some(log_branch) = &self.data.git_log_branch {\n            total_chars += log_branch.len();\n        }\n\n        // Simple approximation: ~4 chars per token + buffer for headers\n        let estimated = (total_chars / 4) + 100;\n\n        // For better accuracy on smaller sizes, actually tokenize\n        if total_chars < 10000 {\n            let combined = format!(\n                \"{}{}{}{}\",\n                self.data.source_tree.as_deref().unwrap_or(\"\"),\n                self.data.git_diff.as_deref().unwrap_or(\"\"),\n                self.data.git_diff_branch.as_deref().unwrap_or(\"\"),\n                self.data.git_log_branch.as_deref().unwrap_or(\"\")\n            );\n            count_tokens(&combined, tokenizer_type)\n        } else {\n            estimated\n        }\n    }\n\n    pub fn generate_prompt(&mut self) -> Result<RenderedPrompt> {\n        self.load_codebase()?;\n\n        // ~~~~ Load Git info ~~~\n        if self.config.diff_enabled {\n            match self.load_git_diff() {\n                Ok(_) => {}\n                Err(e) => log::warn!(\"Git diff could not be loaded: {}\", e),\n            }\n        }\n\n        // ~~~ Load Git info between branches ~~~\n        if self.config.diff_branches.is_some() {\n            match self.load_git_diff_between_branches() {\n                Ok(_) => {}\n                Err(e) => log::warn!(\"Git branch diff could not be loaded: {}\", e),\n            }\n        }\n\n        // ~~~ Load Git log between branches ~~~\n        if self.config.log_branches.is_some() {\n            match self.load_git_log_between_branches() {\n                Ok(_) => {}\n                Err(e) => log::warn!(\"Git branch log could not be loaded: {}\", e),\n            }\n        }\n        let template_data = self.build_template_data();\n        let rendered = self.render_prompt(&template_data)?;\n        Ok(rendered)\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/sort.rs",
    "content": "//! This module provides sorting methods for files and directory trees.\n\nuse crate::path::FileEntry;\nuse serde::{self, Deserialize, Serialize};\nuse std::fmt;\nuse termtree::Tree;\n\n// Define the available sort methods.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]\n#[serde(rename_all = \"snake_case\")]\npub enum FileSortMethod {\n    /// Sort files by name (A → Z)\n    NameAsc,\n    /// Sort files by name (Z → A)\n    NameDesc,\n    /// Sort files by modification date (oldest first)\n    DateAsc,\n    /// Sort files by modification date (newest first)\n    DateDesc,\n}\n\nimpl fmt::Display for FileSortMethod {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            FileSortMethod::NameAsc => write!(f, \"Name (A → Z)\"),\n            FileSortMethod::NameDesc => write!(f, \"Name (Z → A)\"),\n            FileSortMethod::DateAsc => write!(f, \"Date (Old → New)\"),\n            FileSortMethod::DateDesc => write!(f, \"Date (New → Old)\"),\n        }\n    }\n}\n\n/// Sorts the provided `files` in place using the specified `sort_method`.\n///\n/// If `sort_method` is `None`, no sorting will be performed.\n///\n/// # Arguments\n///\n/// * `files` - A mutable slice of FileEntry representing files.\n/// * `sort_method` - An optional `FileSortMethod` indicating how to sort the files.\npub fn sort_files(files: &mut [FileEntry], sort_method: Option<FileSortMethod>) {\n    if let Some(method) = sort_method {\n        match method {\n            FileSortMethod::NameAsc => {\n                files.sort_by(|a, b| a.path.cmp(&b.path));\n            }\n            FileSortMethod::NameDesc => {\n                files.sort_by(|a, b| b.path.cmp(&a.path));\n            }\n            FileSortMethod::DateAsc => {\n                files.sort_by_key(|f| f.mod_time.unwrap_or(0));\n            }\n            FileSortMethod::DateDesc => {\n                files.sort_by_key(|f| std::cmp::Reverse(f.mod_time.unwrap_or(0)));\n            }\n        }\n    }\n}\n\n/// Recursively sorts a directory tree (represented by `termtree::Tree<D>`) in place using the specified\n/// `FileSortMethod`. For directory nodes, since modification time is typically unavailable, this function\n/// falls back to sorting by name. In effect, DateAsc is treated as NameAsc and DateDesc as NameDesc for directories.\n///\n/// If `sort_method` is `None`, no sorting is performed.\n///\n/// # Arguments\n///\n/// * `tree` - A mutable reference to the directory tree.\n/// * `sort_method` - An optional `FileSortMethod` that determines the sorting order.\npub fn sort_tree<D: Ord + std::fmt::Display>(\n    tree: &mut Tree<D>,\n    sort_method: Option<FileSortMethod>,\n) {\n    if let Some(method) = sort_method {\n        // For directories we only have the name (the root), so date-based sorts fall back to name sorting.\n        let ascending = match method {\n            FileSortMethod::NameAsc | FileSortMethod::DateAsc => true,\n            FileSortMethod::NameDesc | FileSortMethod::DateDesc => false,\n        };\n        sort_tree_impl(tree, ascending);\n    }\n}\n\n/// Internal helper: recursively sorts the leaves of a directory tree in the specified order.\nfn sort_tree_impl<D: Ord + std::fmt::Display>(tree: &mut Tree<D>, ascending: bool) {\n    tree.leaves.sort_by(|a, b| {\n        if ascending {\n            a.root.cmp(&b.root)\n        } else {\n            b.root.cmp(&a.root)\n        }\n    });\n    for leaf in &mut tree.leaves {\n        sort_tree_impl(leaf, ascending);\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/template.rs",
    "content": "//! This module contains the functions to set up the Handlebars template engine and render the template with the provided data.\n//! It also includes functions for handling user-defined variables, copying the rendered output to the clipboard, and writing it to a file.\nuse anyhow::{Result, anyhow};\nuse handlebars::{Handlebars, no_escape};\nuse regex::Regex;\nuse serde::{Deserialize, Serialize};\nuse std::io::Write;\n\n/// Set up the Handlebars template engine with a template string and a template name.\n///\n/// # Arguments\n///\n/// * `template_str` - The Handlebars template string.\n/// * `template_name` - The name of the template.\n///\n/// # Returns\n///\n/// * `Result<Handlebars<'static>>` - The configured Handlebars instance.\npub fn handlebars_setup(template_str: &str, template_name: &str) -> Result<Handlebars<'static>> {\n    let mut handlebars = Handlebars::new();\n    handlebars.register_escape_fn(no_escape);\n\n    handlebars\n        .register_template_string(template_name, template_str)\n        .map_err(|e| anyhow!(\"Failed to register template: {}\", e))?;\n\n    Ok(handlebars)\n}\n\n/// Extracts the undefined variables from the template string.\n///\n/// # Arguments\n///\n/// * `template` - The Handlebars template string.\n///\n/// # Returns\n///\n/// * `Vec<String>` - A vector of undefined variable names.\npub fn extract_undefined_variables(template: &str) -> Vec<String> {\n    let registered_identifiers = [\n        \"absolute_code_path\",\n        \"source_tree\",\n        \"files\",\n        \"path\",\n        \"code\",\n        \"git_diff\",\n        \"git_diff_branch\",\n        \"git_log_branch\"\n    ];\n    let re = Regex::new(r\"\\{\\{\\s*(?P<var>[a-zA-Z_][a-zA-Z_0-9]*)\\s*\\}\\}\").unwrap();\n    re.captures_iter(template)\n        .map(|cap| cap[\"var\"].to_string())\n        .filter(|var| !registered_identifiers.contains(&var.as_str()))\n        .collect()\n}\n\n/// Renders the template with the provided data.\n///\n/// # Arguments\n///\n/// * `handlebars` - The configured Handlebars instance.\n/// * `template_name` - The name of the template.\n/// * `data` - Any serializable data object.\n///\n/// # Returns\n///\n/// * `Result<String>` - The rendered template as a string.\npub fn render_template<T: Serialize>(\n    handlebars: &Handlebars,\n    template_name: &str,\n    data: &T,\n) -> Result<String> {\n    let rendered = handlebars\n        .render(template_name, data)\n        .map_err(|e| anyhow!(\"Failed to render template: {}\", e))?;\n    Ok(rendered.trim().to_string())\n}\n\n/// Writes the rendered template to a specified output file\n///\n/// # Arguments\n///\n/// * `output_path` - The path to the output file.\n/// * `rendered` - The rendered template string.\n///\n/// # Returns\n///\n/// * `Result<()>` - An empty result indicating success or an error.\npub fn write_to_file(output_path: &str, rendered: &str) -> Result<()> {\n    let file = std::fs::File::create(output_path)?;\n    let mut writer = std::io::BufWriter::new(file);\n    write!(writer, \"{}\", rendered)?;\n    Ok(())\n}\n\n/// Enum to represent the output format.\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum OutputFormat {\n    #[default]\n    Markdown,\n    Json,\n    Xml,\n}\n\nimpl std::fmt::Display for OutputFormat {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            OutputFormat::Markdown => write!(f, \"markdown\"),\n            OutputFormat::Json => write!(f, \"json\"),\n            OutputFormat::Xml => write!(f, \"xml\"),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/tokenizer.rs",
    "content": "//! This module encapsulates the logic for counting the tokens in the rendered text.\nuse log::debug;\nuse serde::{Deserialize, Serialize};\nuse std::fmt;\nuse std::sync::OnceLock;\nuse tiktoken_rs::{CoreBPE, cl100k_base, o200k_base, p50k_base, p50k_edit, r50k_base};\n\n#[derive(Default, Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum TokenFormat {\n    #[default]\n    Raw,\n    Format,\n}\n\nimpl fmt::Display for TokenFormat {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            TokenFormat::Raw => write!(f, \"Raw\"),\n            TokenFormat::Format => write!(f, \"Formatted\"),\n        }\n    }\n}\n\n/// Tokenizer types supported by tiktoken.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]\npub enum TokenizerType {\n    #[serde(alias = \"o200k\")]\n    O200kBase,\n    #[default]\n    #[serde(alias = \"cl100k\")]\n    Cl100kBase,\n    #[serde(alias = \"p50k\")]\n    P50kBase,\n    #[serde(alias = \"p50k_edit\")]\n    P50kEdit,\n    #[serde(alias = \"r50k\")]\n    R50kBase,\n}\n\nimpl fmt::Display for TokenizerType {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            TokenizerType::O200kBase => write!(f, \"o200k (GPT-4o)\"),\n            TokenizerType::Cl100kBase => write!(f, \"cl100k (ChatGPT)\"),\n            TokenizerType::P50kBase => write!(f, \"p50k (Code models)\"),\n            TokenizerType::P50kEdit => write!(f, \"p50k_edit (Edit models)\"),\n            TokenizerType::R50kBase => write!(f, \"r50k (GPT-3)\"),\n        }\n    }\n}\n\n/// Returns a description of the tokenizer type.\nimpl TokenizerType {\n    pub fn description(&self) -> &'static str {\n        match self {\n            TokenizerType::O200kBase => \"OpenAI models, ChatGPT-4o\",\n            TokenizerType::Cl100kBase => \"ChatGPT models, text-embedding-ada-002\",\n            TokenizerType::P50kBase => \"Code models, text-davinci-002, text-davinci-003\",\n            TokenizerType::P50kEdit => {\n                \"Edit models like text-davinci-edit-001, code-davinci-edit-001\"\n            }\n            TokenizerType::R50kBase => \"GPT-3 models like davinci\",\n        }\n    }\n}\n\n// Cache tokenizers to avoid expensive re-initialization\nstatic O200K_BASE: OnceLock<CoreBPE> = OnceLock::new();\nstatic CL100K_BASE: OnceLock<CoreBPE> = OnceLock::new();\nstatic P50K_BASE: OnceLock<CoreBPE> = OnceLock::new();\nstatic P50K_EDIT: OnceLock<CoreBPE> = OnceLock::new();\nstatic R50K_BASE: OnceLock<CoreBPE> = OnceLock::new();\n\n/// Counts the tokens in the provided text using the specified tokenizer type.\n///\n/// # Arguments\n///\n/// * `rendered` - The text to count tokens in\n/// * `tokenizer_type` - The tokenizer encoding to use\n///\n/// # Returns\n///\n/// * `usize` - The number of tokens in the text\npub fn count_tokens(rendered: &str, tokenizer_type: &TokenizerType) -> usize {\n    use std::time::Instant;\n    let start = Instant::now();\n\n    let bpe = match tokenizer_type {\n        TokenizerType::O200kBase => O200K_BASE.get_or_init(|| o200k_base().unwrap()),\n        TokenizerType::Cl100kBase => CL100K_BASE.get_or_init(|| cl100k_base().unwrap()),\n        TokenizerType::P50kBase => P50K_BASE.get_or_init(|| p50k_base().unwrap()),\n        TokenizerType::P50kEdit => P50K_EDIT.get_or_init(|| p50k_edit().unwrap()),\n        TokenizerType::R50kBase => R50K_BASE.get_or_init(|| r50k_base().unwrap()),\n    };\n\n    let token_count = bpe.encode_with_special_tokens(rendered).len();\n\n    if std::env::var(\"DEBUG_TOKENIZER\").is_ok() {\n        debug!(\n            \"Tokenized {} chars in {:?}\",\n            rendered.len(),\n            start.elapsed()\n        );\n    }\n\n    token_count\n}\n"
  },
  {
    "path": "crates/code2prompt-core/src/util.rs",
    "content": "//! This module contains util functions\n\n/// Removes a UTF‑8 Byte Order Mark (BOM) from the beginning of a byte slice if present.\n///\n/// The UTF‑8 BOM is the byte sequence `[0xEF, 0xBB, 0xBF]`. This function checks whether\n/// the provided slice starts with these bytes and, if so, returns a subslice without them.\n/// Otherwise, it returns the original slice.\npub fn strip_utf8_bom(data: &[u8]) -> &[u8] {\n    const BOM: &[u8] = &[0xEF, 0xBB, 0xBF];\n    if data.starts_with(BOM) {\n        &data[BOM.len()..]\n    } else {\n        data\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/templates/binary-exploitation-ctf-solver.hbs",
    "content": "Challenge Name: {{challenge_name}}\nCategory: Binary Exploitation\n\nDescription: {{challenge_description}}\n\nProvided Files:\n{{#each files}}\n{{#if code}} \n`{{path}}`:\n{{code}}\n\n{{/if}}\n{{/each}}\n\nTo solve this binary exploitation challenge:\n\n1. Examine the provided source code (if any):\n- Identify vulnerabilities (buffer overflow, use-after-free, integer issues, etc.)\n- Understand intended behavior and user input \n- Note compiled binary type (ELF 32/64-bit, Windows PE, etc.)\n\n2. Perform static analysis on the binary:\n- Enumerate input vectors (local files, network port, stdin, etc.) \n- Reverse engineer relevant code paths\n- Locate vulnerable functions (unsafe C functions, syscalls, etc.)\n- Check for stack canaries, NX, PIE, ASLR, RELRO \n\n3. Proceed to dynamic analysis:\n- Attach debugger and send input\n- Determine segfault type (IP overwrite, invalid read/write, etc.)\n- Inspect registers, stack, heap contents\n- Dump process memory \n- Set breakpoints and watchpoints as needed\n\n4. Develop your exploit strategy:\n- Goal (EIP control, arbitrary read/write, information leak, etc.) \n- Payload (spawning a shell, leaking a flag, ret2libc, ROP, etc.)\n- Method to reach vulnerable code\n- Bypassing any exploit mitigations\n\n5. Construct your exploit payload:\n- Determine bad characters and encoding \n- Find ROP gadgets, function addresses, etc. as needed\n- Use pwntools, Ropper, one_gadget, etc. \n- Build payload in debugger, then script it\n\n6. If remote, ensure your exploit is stable and reliable:\n- Adapt to remote environment \n- Handle network quirks, latency\n- Encode payload for transmission\n\n7. Launch the exploit, catch the shell or leaked flag.\n\nInclude your process, not just the final payload. Stay within scope (no attacking unintended targets)."
  },
  {
    "path": "crates/code2prompt-core/templates/clean-up-code.hbs",
    "content": "Project Path: {{ absolute_code_path }}\n\nI'd like your help cleaning up and improving the code quality in this project. Please review all the code files carefully:\n\nSource Tree:\n```\n{{ source_tree }} \n```\n\n{{#each files}}\n{{#if code}}\n`{{path}}`:\n\n{{code}}\n\n{{/if}} \n{{/each}}\n\nWhen reviewing the code, look for opportunities to improve:\n- Readability and clarity \n- Adherence to language idioms and best practices\n- Modularity and code organization \n- Efficiency and performance (within reason)\n- Consistency in style and conventions\n- Error handling and reliability\n- Simplicity (remove unused code, simplify complex logic)\n- Naming of variables, functions, classes, etc.\n- Formatting and whitespace\n- Comments and documentation \n\nMake sure your changes don't alter existing behavior (except perhaps for improved error handling). Try to infer the original intent as much as possible, and refactor towards that intent.\n\nFor each change you make, include a brief code comment explaining your rationale, something like:\n\n// Refactored to improve readability and efficiency. \n// Combined error handling logic into a reusable function.\n\nBe thoughtful and judicious with your changes. I trust your programming expertise! Let me know if any part of the original code is unclear."
  },
  {
    "path": "crates/code2prompt-core/templates/cryptography-ctf-solver.hbs",
    "content": "Challenge Name: {{challenge_name}}\nCategory: Cryptography\n\nDescription: {{challenge_description}}\n\nProvided Files:\n{{#each files}}\n{{#if code}}\n`{{path}}`:\n{{code}}\n\n{{/if}}\n{{/each}}\n\nI need your help to solve this cryptography challenge. Here are some steps to follow:\n\n1. Identify the type of encryption or encoding used based on the challenge description and any provided files. Common types include:\n- Classical ciphers (Caesar, Vigenère, substitution, etc.) \n- Modern symmetric ciphers (AES, DES, etc.)\n- Asymmetric cryptography (RSA, ECC, etc.)\n- Hashes and password cracking\n- Encoding schemes (Base64, hex, etc.)\n\n2. If there are any encrypted messages or ciphertexts, paste them here. Also include any keys, IVs, or other relevant parameters.\n\n3. Analyze the encryption for weaknesses. Look for:\n- Weak keys or poor randomness\n- Use of insecure modes like ECB\n- Oracles that leak information \n- Flaws in custom encryption schemes\n- Reused one-time pads or nonces\n- Hash length extension attacks\n\n4. Attempt to decrypt the message:\n- Brute-force attack if key space is small\n- Frequency analysis and cribs for classical ciphers\n- Exploit mathematical weaknesses of RSA\n- Crack hashes with wordlists/rules/masks\n- Abuse padding oracle vulnerabilities\n\n5. If you successfully decrypt, the flag format is usually `flag{...}`. Submit that to the scoring system.\n\nLet me know if you need any other information to solve the challenge! Cryptography can be tricky."
  },
  {
    "path": "crates/code2prompt-core/templates/document-the-code.hbs",
    "content": "Project Path: {{ absolute_code_path }}\n\nSource Tree: \n```\n{{ source_tree }}\n```\n\n{{#each files}}\n{{#if code}}\n`{{path}}`:\n\n{{code}}  \n\n{{/if}}\n{{/each}}\n\nI'd like you to add documentation comments to all public functions, methods, classes and modules in this codebase.\n\nFor each one, the comment should include:\n1. A brief description of what it does\n2. Explanations of all parameters including types/constraints \n3. Description of the return value (if applicable)\n4. Any notable error or edge cases handled\n5. Links to any related code entities\n\nTry to keep comments concise but informative. Use the function/parameter names as clues to infer their purpose. Analyze the implementation carefully to determine behavior.\n\nComments should use the idiomatic style for the language, e.g. /// for Rust, \"\"\" for Python, /** */ for TypeScript, etc. Place them directly above the function/class/module definition.\n\nLet me know if you have any questions! And be sure to review your work for accuracy before submitting."
  },
  {
    "path": "crates/code2prompt-core/templates/find-security-vulnerabilities.hbs",
    "content": "Project Path: {{ absolute_code_path }}\n\nI want you to carefully review the code in this project and identify any potential security vulnerabilities or weaknesses. Take your time, think step-by-step, and consider all the code paths and interactions between different parts of the codebase.\n\nSource Tree:\n```\n{{ source_tree }}\n```\n\n{{#each files}}\n{{#if code}} \n`{{path}}`:\n\n{{code}}\n\n{{/if}}\n{{/each}}\n\nWhen analyzing the code, look for common security issues like:\n- Input validation vulnerabilities \n- Weak authentication or authorization\n- Insecure handling of sensitive data\n- Injection flaws (SQL injection, XXE, command injection, etc)\n- Cross-site scripting (XSS)\n- Insecure configuration settings\n- Outdated or vulnerable dependencies\n- Privilege escalation\n- Unrestricted resource consumption (via DoS, etc)\n- Insecure cryptography (like weak keys, etc)\n- Unrestricted file uploads\n- Insecure deserialization\n- Insecure randomness\n- Insecure logging and monitoring\n- Deserialization attacks (like Pickle, etc)\n- Business logic vulnerabilities (example scenario: user can withdraw 3 times in a row but the code allows for 4)\n\nFor each vulnerability you find, provide:\n1. The file path and line number(s)\n2. A description of the issue and why it's a vulnerability\n3. The potential impact if the vulnerability was exploited\n4. The code snippets responsible for the vulnerability, from source to sink and which user input or value is passed\n5. Exploit PoC (Proof of Concept)\n6. Recommendations on how to fix or mitigate the vulnerability\n\nAfter you have finished analyzing the codebase, provide a Markdown table with the following headers: Vulnerability Name, Vulnerability Description, File Path, CVSS Vector, Confidence Score, Exploitation Steps.\n\nBe as thorough and detailed as possible in your analysis. The security of this codebase is critical.\n"
  },
  {
    "path": "crates/code2prompt-core/templates/fix-bugs.hbs",
    "content": "Project Path: {{ absolute_code_path }}\n\nI need your help tracking down and fixing some bugs that have been reported in this codebase. Here are the files involved:\n\nSource Tree:\n```\n{{ source_tree }}\n```\n\n{{#each files}} \n{{#if code}}\n`{{path}}`: \n\n{{code}}\n\n{{/if}}\n{{/each}}\n\nI suspect the bugs are related to:\n- Incorrect handling of edge cases \n- Off-by-one errors in loops or array indexing\n- Unexpected data types\n- Uncaught exceptions\n- Concurrency issues\n- Improper configuration settings\n\nTo diagnose:\n1. Review the code carefully and systematically \n2. Trace the relevant code paths \n3. Consider boundary conditions and potential error states\n4. Look for antipatterns that tend to cause bugs\n5. Run the code mentally with example inputs \n6. Think about interactions between components\n\nWhen you find potential bugs, for each one provide:\n1. File path and line number(s)\n2. Description of the issue and why it's a bug\n3. Example input that would trigger the bug \n4. Suggestions for how to fix it\n\nAfter analysis, please update the code with your proposed fixes. Try to match the existing code style. Add regression tests if possible to prevent the bugs from recurring.\n\nI appreciate your diligence and attention to detail! Let me know if you need any clarification on the intended behavior of the code."
  },
  {
    "path": "crates/code2prompt-core/templates/improve-performance.hbs",
    "content": "Project Path: {{ absolute_code_path }}\n\nI'd like your help improving the performance of this codebase. It works correctly, but we need it to be faster and more efficient. Analyze the code thoroughly with this goal in mind:\n\nSource Tree:\n```\n{{ source_tree }}\n``` \n\n{{#each files}}\n{{#if code}}\n`{{path}}`:\n\n{{code}}\n\n{{/if}}\n{{/each}}\n\nWhen looking for optimization opportunities, consider:\n- Algorithm complexity and big O analysis \n- Expensive operations like disk/network I/O\n- Unnecessary iterations or computations\n- Repeated calculations of the same value \n- Inefficient data structures or data types\n- Opportunities to cache or memoize results\n- Parallelization with threads/async \n- More efficient built-in functions or libraries\n- Query or code paths that can be short-circuited\n- Reducing memory allocations and copying\n- Compiler or interpreter optimizations to leverage\n\nFor each potential improvement, provide:\n1. File path and line number(s) \n2. Description of the issue/inefficiency\n3. Estimated impact on performance \n4. Specific suggestions for optimization\n\nThen update the code with your changes. Be sure to maintain readability and organization. Minor optimizations that significantly reduce clarity are not worth it.\n\nAdd benchmarks if possible to quantify the performance improvements. Document any new usage constraints (e.g. increased memory requirements).\n\nTry to prioritize the changes that will have the largest impact on typical usage scenarios based on your understanding of the codebase. Let me know if you have any questions!"
  },
  {
    "path": "crates/code2prompt-core/templates/refactor.hbs",
    "content": "Project Path: {{ absolute_code_path }}\n\nI need your help refactoring this codebase to improve its design, maintainability, and performance. Here are the files involved:\n\nSource Tree:\n```\n{{ source_tree }}\n```\n\n{{#each files}} \n{{#if code}}\n`{{path}}`: \n\n{{code}}\n\n{{/if}}\n{{/each}}\n\nSome areas to focus on during the refactoring:\n\n- Adherence to SOLID principles (Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, Dependency Inversion)\n- Separation of concerns\n- Reducing duplication (DRY - Don't Repeat Yourself)\n- Improving naming and code readability\n- Enhancing modularity and reusability\n- Optimizing performance\n- Removing dead or redundant code\n- Updating to modern language features or idioms where appropriate\n- Ensuring consistent code style and formatting\n\nTo refactor effectively:\n\n- Understand the current design and architecture\n- Identify pain points, code smells, and areas for improvement\n- Break down the refactoring into manageable steps\n- Ensure the existing tests pass after each refactoring step\n- Look for opportunities to extract reusable functions, classes or modules\n- Consider performance implications of design changes\n- Keep the code readable and maintainable\n- Preserve the original functionality and API contracts\n\nFor each major refactoring you propose, please provide:\n\n- File path(s) and line number(s)\n- Description of the current code and why it needs refactoring\n- Explanation of your proposed changes and their benefits\n- Updated code snippets with your refactoring applied\n\nAfter refactoring, please share the updated codebase. Update any relevant documentation to reflect the changes. Add new unit tests for the refactored code if applicable.\n"
  },
  {
    "path": "crates/code2prompt-core/templates/reverse-engineering-ctf-solver.hbs",
    "content": "Challenge Name: {{challenge_name}}\nCategory: Reverse Engineering \n\nDescription: {{challenge_description}}\n\nProvided Files:\n{{#each files}} \n{{#if code}}\n`{{path}}`:\n{{code}}\n\n{{/if}}\n{{/each}}\n\nHere's a plan to tackle this reverse engineering challenge:\n\n1. Identify the target file type(s):\n- Compiled binary (ELF, PE, Mach-O)  \n- Bytecode (Java, .NET, Python, etc.)\n- Obfuscated script (JavaScript, Lua, etc.)\n- Document (maldoc, PDF) with macros \n\n2. Set up your analysis environment:\n- Disassembler/decompiler (Ghidra, IDA Pro, radare2)\n- Debugger (gdb, x64dbg, WinDbg)\n- VM or container to isolate malware\n- Automated unpacking/deobfuscation tools\n\n3. Perform static analysis:\n- Scan strings for clues, crypto/encoding, flag format\n- Examine imported functions for interesting behavior\n- Decompile and review logic, control flow \n- Locate comparison with user input or flag\n\n4. Proceed to dynamic analysis if needed:\n- Run binary in debugger \n- Set breakpoints on key functions\n- Inspect variables, memory, and registers\n- Modify execution flow or patch binary\n\n5. Identify and bypass anti-reversing:\n- Packed or obfuscated code\n- Anti-debug checks (IsDebuggerPresent, timing, etc.) \n- Junk code, opaque predicates\n- Virtualization/emulation\n\n6. Solve any necessary steps:\n- Satisfy input checks (password, serial, etc.)\n- Defuse anti-tampering protections\n- Decrypt embedded resources\n- Forge crypto/hash to match expected value\n\n7. Locate the flag in memory, output, or decrypted resource.\n\nProvide the key reversing insights you discover. Focus on reaching the minimum goal, not full understanding."
  },
  {
    "path": "crates/code2prompt-core/templates/web-ctf-solver.hbs",
    "content": "Challenge Name: {{challenge_name}}  \nCategory: Web Exploitation\n\nDescription: {{challenge_description}}\n\nTarget URL: {{target_url}}\n\nProvided Files:\n{{#each files}}\n{{#if code}}\n`{{path}}`:\n{{code}}\n\n{{/if}} \n{{/each}}\n\nTo solve this web exploitation challenge, follow these steps:\n\n1. Explore the target web app in a browser. Note down:\n- Visible URLs and endpoints \n- Login/auth mechanisms\n- Key functionality and pages\n- User-supplied input fields \n- Technologies used (framework, frontend, backend, DB)\n\n2. View page source and inspect HTTP traffic. Look for:\n- HTML comments with clues/TODOs\n- JavaScript source code \n- API endpoints and request/response formats\n- Cookies, auth tokens, headers\n\n3. Test for common web vulns:\n- SQL injection in search/login/URLs \n- Cross-site scripting (XSS) in input fields\n- Server-side template injection \n- Command injection \n- Directory traversal\n- Insecure file uploads\n- Broken access control for admin/hidden pages\n\n4. If you find an exploitable vuln, craft a malicious payload:\n- SQL injection to bypass login, dump DB, UNION query\n- XSS to steal admin cookies/creds or call APIs \n- Template injection to leak source or run OS commands\n- Directory traversal to view sensitive files\n\n5. The flag is often in an admin page, DBdump, or source code file. Access it via the vulnerability.\n\nProvide the vulnerable URL and your exploit payload. Stay within scope and rules - no scanning/attacking other targets."
  },
  {
    "path": "crates/code2prompt-core/templates/write-git-commit.hbs",
    "content": "Project Path: {{ absolute_code_path }}\n\nI'd like you to generate a high-quality git commit message for the provided `git diff`. Analyze the diff to understand the purpose and functionality.\n\nSource Tree:\n```\n{{ source_tree }}\n```\n\n{{#if git_diff}}\nDiff:\n```\n{{git_diff}}\n```\n{{/if}}\n\nThe git commit should adhere to these points:\n\n1. Concise Subject: Short and informative subject line, less than 50 characters.\n2. Descriptive Body: Optional summary of 1 to 2 sentences, wrapping at 72 characters.\n3. Use Imperative Mood: For example, \"Fix bug\" instead of \"Fixed bug\" or \"Fixes bug.\"\n4. Capitalize the Subject: First letter of the subject should be capitalized.\n5. No Period at the End: Subject line does not end with a period.\n7. Separate Subject From Body With a Blank Line: If using a body, leave one blank line after the subject.\n\nWrite the content in Markdown format. Use your analysis of the diff to generate a short, naccurate and helpful commit message.\n\nFeel free to infer reasonable details if needed, but try to stick to what can be determined from the diff itself. Let me know if you have any other questions as you're writing!\n"
  },
  {
    "path": "crates/code2prompt-core/templates/write-github-pull-request.hbs",
    "content": "Project Path: {{ absolute_code_path }}\n\nI want you to generate a high-quality well-crafted Github pull request description for this project.\nI will provide you with the source tree, git diff, git log, and pull request template.\n\nSource Tree:\n```\n{{ source_tree }}\n```\n\n{{#if git_diff_branch}}\nGit diff:\n```\n{{git_diff_branch}}\n```\n{{/if}}\n\n\n{{#if git_log_branch}}\nGit log:\n```\n{{git_log_branch}}\n```\n{{/if}}\n\n\nThe Pull Request description should include the following template and adhere best practice:\n```\nTitle: provide with concise and informative title.\n\n# What is this?\n- Explain the motivation why this is needed and the expected outcome of implementing this.\n- Write it in a humanized manner.\n\n# Changes\n- Provide list of key changes with good structure.\n- Mention the class name, function name, and file name.\n- Explain the code changes.\nFor example:\n# Changes\n## Added Features:\n    1. **New Functions in `file_name.`**:\n        - `function_name.`: code description.\n## Code Changes:\n    1. **In `file_name.`**:\n## Documentation Updates:\n    1. **In `file_name.`**:\n\n# Demo\n- N/A\n\n# Context\n- N/A\n```\n\nPlease, analyze the git diff and git log to understand the changes. Do not output git log and git diff to the content.\nUse your analysis of the code to generate accurate and helpful content, but also explain things clearly for users who may not be familiar with the implementation details.\nWrite the content in Markdown format and follow the provided pull request template.\n"
  },
  {
    "path": "crates/code2prompt-core/templates/write-github-readme.hbs",
    "content": "Project Path: {{ absolute_code_path }}\n\nI'd like you to generate a high-quality README file for this project, suitable for hosting on GitHub. Analyze the codebase to understand the purpose, functionality, and structure of the project. \n\nSource Tree:\n```\n{{ source_tree }}\n```\n\n{{#each files}}\n{{#if code}}\n`{{path}}`:\n\n{{code}}\n\n{{/if}}\n{{/each}}\n\nThe README should include the following sections:\n\n1. Project Title\n2. Brief description (1-2 sentences)\n3. Features\n4. Installation instructions\n5. Usage examples\n6. Configuration options (if applicable) \n7. Contribution guidelines\n8. Testing instructions\n9. License\n10. Acknowledgements/Credits\n\nWrite the content in Markdown format. Use your analysis of the code to generate accurate and helpful content, but also explain things clearly for users who may not be familiar with the implementation details.\n\nFeel free to infer reasonable details if needed, but try to stick to what can be determined from the codebase itself. Let me know if you have any other questions as you're writing!"
  },
  {
    "path": "crates/code2prompt-core/tests/binary_detection_test.rs",
    "content": "//! Tests for binary file detection using content_inspector\n\nuse code2prompt_core::configuration::Code2PromptConfig;\nuse code2prompt_core::path::traverse_directory;\nuse std::fs;\nuse tempfile::TempDir;\n\n/// Helper to create a test directory with mixed binary and text files\nfn create_test_directory_with_binary() -> TempDir {\n    let temp_dir = TempDir::new().unwrap();\n    let base_path = temp_dir.path();\n\n    // Create text files\n    fs::write(base_path.join(\"text.txt\"), \"This is a text file\").unwrap();\n    fs::write(\n        base_path.join(\"code.rs\"),\n        \"fn main() { println!(\\\"Hello\\\"); }\",\n    )\n    .unwrap();\n    fs::write(base_path.join(\"data.json\"), r#\"{\"key\": \"value\"}\"#).unwrap();\n\n    // Create text file with non-UTF8 encoding (GB2312)\n    let mut gb2312_data = b\"GB2312 test: \".to_vec();\n    // Append \"你好\" encoded in GB2312\n    // '你' is 0xC4 0xE3\n    // '好' is 0xBA 0xC3\n    gb2312_data.extend_from_slice(&[0xC4, 0xE3, 0xBA, 0xC3]);\n    fs::write(base_path.join(\"chinese_gb2312.txt\"), gb2312_data).unwrap();\n\n    // Create binary files (simulated)\n    // PNG header signature\n    let mut png_data = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];\n    // Append some zeros and random high bytes to ensure it hits the binary heuristic\n    png_data.extend_from_slice(&[0x00, 0x00, 0x00, 0xFF, 0xFE]);\n    fs::write(base_path.join(\"image.png\"), png_data).unwrap();\n\n    // Random binary data\n    let binary_data: Vec<u8> = (0..100).map(|i| (i * 7) as u8).collect();\n    fs::write(base_path.join(\"binary.bin\"), binary_data).unwrap();\n\n    // JPEG header\n    let mut jpeg_data = vec![0xFF, 0xD8, 0xFF, 0xE0];\n    jpeg_data.extend_from_slice(&[0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00]);\n    fs::write(base_path.join(\"photo.jpg\"), jpeg_data).unwrap();\n\n    // Compiled object file simulation (ELF header with more data)\n    let mut elf_data = vec![0x7F, b'E', b'L', b'F']; // ELF magic\n    // Add more binary data to make it clearly binary\n    elf_data.extend_from_slice(&[0x02, 0x01, 0x01, 0x00]); // 64-bit, little endian, etc\n    elf_data.extend((0..50).map(|i| (i * 13) as u8)); // More binary content\n    fs::write(base_path.join(\"compiled.o\"), elf_data).unwrap();\n\n    temp_dir\n}\n\n#[test]\nfn test_binary_files_are_skipped() {\n    let temp_dir = create_test_directory_with_binary();\n    let config = Code2PromptConfig::builder()\n        .path(temp_dir.path().to_path_buf())\n        .build()\n        .unwrap();\n\n    let (_, files) = traverse_directory(&config, None).unwrap();\n\n    // Should only include text files, not binary files\n    let file_paths: Vec<String> = files.iter().map(|f| f.path.clone()).collect();\n\n    // Text files should be included\n    assert!(file_paths.iter().any(|p| p.contains(\"text.txt\")));\n    assert!(file_paths.iter().any(|p| p.contains(\"code.rs\")));\n    assert!(file_paths.iter().any(|p| p.contains(\"data.json\")));\n    assert!(file_paths.iter().any(|p| p.contains(\"chinese_gb2312.txt\")));\n\n    // Binary files should be excluded\n    assert!(!file_paths.iter().any(|p| p.contains(\"image.png\")));\n    assert!(!file_paths.iter().any(|p| p.contains(\"binary.bin\")));\n    assert!(!file_paths.iter().any(|p| p.contains(\"photo.jpg\")));\n    assert!(!file_paths.iter().any(|p| p.contains(\"compiled.o\")));\n\n    // Should have exactly 3 text files\n    assert_eq!(files.len(), 4);\n}\n\n#[test]\nfn test_empty_file_handling() {\n    let temp_dir = TempDir::new().unwrap();\n    let base_path = temp_dir.path();\n\n    // Create an empty file\n    fs::write(base_path.join(\"empty.txt\"), \"\").unwrap();\n\n    let config = Code2PromptConfig::builder()\n        .path(base_path.to_path_buf())\n        .build()\n        .unwrap();\n\n    let (_, files) = traverse_directory(&config, None).unwrap();\n\n    // Empty files should be excluded (existing behavior)\n    assert_eq!(files.len(), 0);\n}\n\n#[test]\nfn test_small_binary_file() {\n    let temp_dir = TempDir::new().unwrap();\n    let base_path = temp_dir.path();\n\n    // Create a very small binary file (less than 8KB)\n    let small_binary: Vec<u8> = vec![0x00, 0xFF, 0x00, 0xFF, 0xFE, 0xED];\n    fs::write(base_path.join(\"small.bin\"), small_binary).unwrap();\n\n    let config = Code2PromptConfig::builder()\n        .path(base_path.to_path_buf())\n        .build()\n        .unwrap();\n\n    let (_, files) = traverse_directory(&config, None).unwrap();\n\n    // Small binary file should still be detected and excluded\n    assert_eq!(files.len(), 0);\n}\n\n#[test]\nfn test_text_file_with_unicode() {\n    let temp_dir = TempDir::new().unwrap();\n    let base_path = temp_dir.path();\n\n    // Create text file with Unicode characters\n    fs::write(\n        base_path.join(\"unicode.txt\"),\n        \"Hello 世界 🌍 Здравствуй мир\",\n    )\n    .unwrap();\n\n    let config = Code2PromptConfig::builder()\n        .path(base_path.to_path_buf())\n        .build()\n        .unwrap();\n\n    let (_, files) = traverse_directory(&config, None).unwrap();\n\n    // Unicode text should be detected as text and included\n    assert_eq!(files.len(), 1);\n    let file_path = &files[0].path;\n    assert!(file_path.contains(\"unicode.txt\"));\n}\n\n#[test]\nfn test_mixed_directory_structure() {\n    let temp_dir = TempDir::new().unwrap();\n    let base_path = temp_dir.path();\n\n    // Create nested structure with mixed files\n    fs::create_dir(base_path.join(\"src\")).unwrap();\n    fs::create_dir(base_path.join(\"assets\")).unwrap();\n\n    // Text files in src/\n    fs::write(base_path.join(\"src/main.rs\"), \"fn main() {}\").unwrap();\n    fs::write(base_path.join(\"src/lib.rs\"), \"pub mod test {}\").unwrap();\n\n    // Binary files in assets/\n    let png_data = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];\n    fs::write(base_path.join(\"assets/logo.png\"), png_data).unwrap();\n\n    let config = Code2PromptConfig::builder()\n        .path(base_path.to_path_buf())\n        .build()\n        .unwrap();\n\n    let (_, files) = traverse_directory(&config, None).unwrap();\n\n    // Should only have 2 text files from src/\n    assert_eq!(files.len(), 2);\n\n    let file_paths: Vec<String> = files.iter().map(|f| f.path.clone()).collect();\n\n    assert!(file_paths.iter().any(|p| p.contains(\"main.rs\")));\n    assert!(file_paths.iter().any(|p| p.contains(\"lib.rs\")));\n    assert!(!file_paths.iter().any(|p| p.contains(\"logo.png\")));\n}\n\n#[test]\nfn test_large_text_file() {\n    let temp_dir = TempDir::new().unwrap();\n    let base_path = temp_dir.path();\n\n    // Create a large text file (> 8KB) to test that full file is read after sample\n    let large_text = \"Lorem ipsum dolor sit amet. \".repeat(1000); // ~28KB\n    fs::write(base_path.join(\"large.txt\"), &large_text).unwrap();\n\n    let config = Code2PromptConfig::builder()\n        .path(base_path.to_path_buf())\n        .build()\n        .unwrap();\n\n    let (_, files) = traverse_directory(&config, None).unwrap();\n\n    // Large text file should be detected and included\n    assert_eq!(files.len(), 1);\n\n    // Verify the entire content was read (not just the sample)\n    let code = &files[0].code;\n    assert!(code.contains(&large_text));\n}\n\n#[test]\nfn test_pdf_detection() {\n    let temp_dir = TempDir::new().unwrap();\n    let base_path = temp_dir.path();\n\n    // PDF file header\n    let pdf_header = b\"%PDF-1.4\\n\";\n    fs::write(base_path.join(\"document.pdf\"), pdf_header).unwrap();\n\n    let config = Code2PromptConfig::builder()\n        .path(base_path.to_path_buf())\n        .build()\n        .unwrap();\n\n    let (_, files) = traverse_directory(&config, None).unwrap();\n\n    // PDF should be detected as binary and excluded\n    assert_eq!(files.len(), 0);\n}\n\n#[test]\nfn test_various_text_formats() {\n    let temp_dir = TempDir::new().unwrap();\n    let base_path = temp_dir.path();\n\n    // Various text file formats\n    fs::write(base_path.join(\"config.yaml\"), \"key: value\\n\").unwrap();\n    fs::write(base_path.join(\"data.xml\"), \"<root><item/></root>\").unwrap();\n    fs::write(base_path.join(\"script.sh\"), \"#!/bin/bash\\necho 'test'\").unwrap();\n    fs::write(base_path.join(\"style.css\"), \"body { margin: 0; }\").unwrap();\n    fs::write(base_path.join(\"page.html\"), \"<html><body></body></html>\").unwrap();\n\n    let config = Code2PromptConfig::builder()\n        .path(base_path.to_path_buf())\n        .build()\n        .unwrap();\n\n    let (_, files) = traverse_directory(&config, None).unwrap();\n\n    // All text formats should be included\n    assert_eq!(files.len(), 5);\n}\n"
  },
  {
    "path": "crates/code2prompt-core/tests/file_processor_test.rs",
    "content": "//! Tests for file processor module\n//!\n//! This file contains all tests for the file processor implementations,\n//! organized by processor type.\n\nuse code2prompt_core::file_processor::*;\nuse std::path::PathBuf;\n\n// ============================================================================\n// CSV Processor Tests\n// ============================================================================\n\nmod csv_tests {\n    use super::*;\n\n    #[test]\n    fn test_csv_with_headers_and_data() {\n        let processor = CsvProcessor;\n        let content = b\"name,age,city\\nAlice,30,NYC\\nBob,25,LA\\nCharlie,35,SF\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.csv\"))\n            .unwrap();\n\n        assert!(result.contains(\"Headers: name, age, city\"));\n        assert!(result.contains(\"Sample: \\\"Alice\\\", \\\"30\\\", \\\"NYC\\\"\"));\n        assert!(result.contains(\"[2 more rows omitted]\"));\n    }\n\n    #[test]\n    fn test_csv_with_quoted_fields() {\n        let processor = CsvProcessor;\n        let content =\n            b\"name,description\\n\\\"John Doe\\\",\\\"Software Engineer, Senior\\\"\\n\\\"Jane\\\",\\\"Manager\\\"\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.csv\"))\n            .unwrap();\n\n        assert!(result.contains(\"Headers: name, description\"));\n        assert!(result.contains(\"Sample: \\\"John Doe\\\", \\\"Software Engineer, Senior\\\"\"));\n    }\n\n    #[test]\n    fn test_csv_empty() {\n        let processor = CsvProcessor;\n        let content = b\"name,age\\n\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.csv\"))\n            .unwrap();\n\n        assert!(result.contains(\"Headers: name, age\"));\n        assert!(result.contains(\"(No data rows found)\"));\n    }\n\n    #[test]\n    fn test_csv_malformed_fallback() {\n        let processor = CsvProcessor;\n        let content = b\"not a valid csv file\\nwith random\\ncontent\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.csv\"))\n            .unwrap();\n\n        // Should fallback to raw text\n        assert!(result.contains(\"not a valid csv file\"));\n    }\n}\n\n// ============================================================================\n// TSV Processor Tests\n// ============================================================================\n\nmod tsv_tests {\n    use super::*;\n\n    #[test]\n    fn test_tsv_with_headers_and_data() {\n        let processor = TsvProcessor;\n        let content = b\"name\\tage\\tcity\\nAlice\\t30\\tNYC\\nBob\\t25\\tLA\\nCharlie\\t35\\tSF\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.tsv\"))\n            .unwrap();\n\n        assert!(result.contains(\"TSV Schema\"));\n        assert!(result.contains(\"Headers: name, age, city\"));\n        assert!(result.contains(\"Sample: \\\"Alice\\\", \\\"30\\\", \\\"NYC\\\"\"));\n        assert!(result.contains(\"[2 more rows omitted]\"));\n    }\n\n    #[test]\n    fn test_tsv_with_spaces() {\n        let processor = TsvProcessor;\n        let content = b\"name\\tdescription\\nJohn Doe\\tSoftware Engineer\\nJane\\tManager\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.tsv\"))\n            .unwrap();\n\n        assert!(result.contains(\"TSV Schema\"));\n        assert!(result.contains(\"Headers: name, description\"));\n        assert!(result.contains(\"Sample: \\\"John Doe\\\", \\\"Software Engineer\\\"\"));\n    }\n\n    #[test]\n    fn test_tsv_empty() {\n        let processor = TsvProcessor;\n        let content = b\"name\\tage\\n\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.tsv\"))\n            .unwrap();\n\n        assert!(result.contains(\"Headers: name, age\"));\n        assert!(result.contains(\"(No data rows found)\"));\n    }\n}\n\n// ============================================================================\n// JSONL Processor Tests\n// ============================================================================\n\nmod jsonl_tests {\n    use super::*;\n\n    #[test]\n    fn test_jsonl_with_multiple_lines() {\n        let processor = JsonLinesProcessor;\n        let content = b\"{\\\"id\\\":1,\\\"name\\\":\\\"Alice\\\",\\\"age\\\":30}\\n{\\\"id\\\":2,\\\"name\\\":\\\"Bob\\\",\\\"age\\\":25}\\n{\\\"id\\\":3,\\\"name\\\":\\\"Charlie\\\",\\\"age\\\":35}\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.jsonl\"))\n            .unwrap();\n\n        assert!(result.contains(\"JSONL Schema\"));\n        assert!(\n            result.contains(\"Fields: id, name, age\")\n                || result.contains(\"Fields: name, id, age\")\n                || result.contains(\"Fields: age, id, name\")\n        );\n        assert!(result.contains(\"Sample: {\\\"id\\\":1,\\\"name\\\":\\\"Alice\\\",\\\"age\\\":30}\"));\n        assert!(result.contains(\"[2 more lines omitted]\"));\n    }\n\n    #[test]\n    fn test_jsonl_single_line() {\n        let processor = JsonLinesProcessor;\n        let content = b\"{\\\"user\\\":\\\"john\\\",\\\"action\\\":\\\"login\\\"}\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.jsonl\"))\n            .unwrap();\n\n        assert!(result.contains(\"JSONL Schema\"));\n        assert!(result.contains(\"user\") && result.contains(\"action\"));\n        assert!(result.contains(\"Sample: {\\\"user\\\":\\\"john\\\",\\\"action\\\":\\\"login\\\"}\"));\n        assert!(!result.contains(\"more lines omitted\"));\n    }\n\n    #[test]\n    fn test_jsonl_with_nested_objects() {\n        let processor = JsonLinesProcessor;\n        let content = b\"{\\\"id\\\":1,\\\"user\\\":{\\\"name\\\":\\\"Alice\\\",\\\"email\\\":\\\"alice@example.com\\\"}}\\n{\\\"id\\\":2,\\\"user\\\":{\\\"name\\\":\\\"Bob\\\",\\\"email\\\":\\\"bob@example.com\\\"}}\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.jsonl\"))\n            .unwrap();\n\n        assert!(result.contains(\"JSONL Schema\"));\n        assert!(result.contains(\"id\") && result.contains(\"user\"));\n    }\n\n    #[test]\n    fn test_jsonl_empty_file() {\n        let processor = JsonLinesProcessor;\n        let content = b\"\";\n        let result = processor.process(content, &PathBuf::from(\"test.jsonl\"));\n\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_jsonl_invalid_json() {\n        let processor = JsonLinesProcessor;\n        let content = b\"not a valid json\\nanother line\";\n        let result = processor.process(content, &PathBuf::from(\"test.jsonl\"));\n\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_jsonl_with_fallback() {\n        let processor = JsonLinesProcessor;\n        let content = b\"invalid json content\";\n        let result = processor\n            .process_with_fallback(content, &PathBuf::from(\"test.jsonl\"))\n            .unwrap();\n\n        // Should fallback to raw text\n        assert!(result.contains(\"invalid json content\"));\n    }\n}\n\n// ============================================================================\n// Jupyter Notebook Processor Tests\n// ============================================================================\n\nmod ipynb_tests {\n    use super::*;\n\n    #[test]\n    fn test_ipynb_with_code_cells() {\n        let processor = JupyterNotebookProcessor;\n        let content = r##\"{\n            \"cells\": [\n                {\n                    \"cell_type\": \"code\",\n                    \"source\": [\"import pandas as pd\\n\", \"df = pd.read_csv(\\\"data.csv\\\")\"]\n                },\n                {\n                    \"cell_type\": \"markdown\",\n                    \"source\": [\"# This is a title\"]\n                },\n                {\n                    \"cell_type\": \"code\",\n                    \"source\": \"df.head()\"\n                }\n            ]\n        }\"##;\n\n        let result = processor\n            .process(content.as_bytes(), &PathBuf::from(\"test.ipynb\"))\n            .unwrap();\n\n        assert!(result.contains(\"Jupyter Notebook Summary\"));\n        assert!(result.contains(\"Total cells: 3 (2 code, 1 markdown, 0 raw)\"));\n        assert!(result.contains(\"Code Cell #1:\"));\n        assert!(result.contains(\"import pandas as pd\"));\n        assert!(result.contains(\"Code Cell #2:\"));\n        assert!(result.contains(\"df.head()\"));\n    }\n\n    #[test]\n    fn test_ipynb_with_many_code_cells() {\n        let processor = JupyterNotebookProcessor;\n        let content = r#\"{\n            \"cells\": [\n                {\"cell_type\": \"code\", \"source\": \"cell1\"},\n                {\"cell_type\": \"code\", \"source\": \"cell2\"},\n                {\"cell_type\": \"code\", \"source\": \"cell3\"},\n                {\"cell_type\": \"code\", \"source\": \"cell4\"},\n                {\"cell_type\": \"code\", \"source\": \"cell5\"}\n            ]\n        }\"#;\n\n        let result = processor\n            .process(content.as_bytes(), &PathBuf::from(\"test.ipynb\"))\n            .unwrap();\n\n        assert!(result.contains(\"Total cells: 5 (5 code, 0 markdown, 0 raw)\"));\n        assert!(result.contains(\"Code Cell #1:\"));\n        assert!(result.contains(\"Code Cell #2:\"));\n        assert!(result.contains(\"Code Cell #3:\"));\n        assert!(result.contains(\"[2 more code cells omitted]\"));\n        assert!(!result.contains(\"Code Cell #4:\"));\n    }\n\n    #[test]\n    fn test_ipynb_no_code_cells() {\n        let processor = JupyterNotebookProcessor;\n        let content = r##\"{\n            \"cells\": [\n                {\"cell_type\": \"markdown\", \"source\": \"# Title\"},\n                {\"cell_type\": \"markdown\", \"source\": \"Some text\"}\n            ]\n        }\"##;\n\n        let result = processor\n            .process(content.as_bytes(), &PathBuf::from(\"test.ipynb\"))\n            .unwrap();\n\n        assert!(result.contains(\"Total cells: 2 (0 code, 2 markdown, 0 raw)\"));\n        assert!(result.contains(\"(No code cells found)\"));\n    }\n\n    #[test]\n    fn test_ipynb_invalid_json() {\n        let processor = JupyterNotebookProcessor;\n        let content = b\"not a valid json\";\n\n        let result = processor.process(content, &PathBuf::from(\"test.ipynb\"));\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_ipynb_with_fallback() {\n        let processor = JupyterNotebookProcessor;\n        let content = b\"invalid notebook content\";\n\n        let result = processor\n            .process_with_fallback(content, &PathBuf::from(\"test.ipynb\"))\n            .unwrap();\n\n        // Should fallback to raw text\n        assert!(result.contains(\"invalid notebook content\"));\n    }\n}\n\n// ============================================================================\n// Default Text Processor Tests\n// ============================================================================\n\nmod default_tests {\n    use super::*;\n\n    #[test]\n    fn test_valid_utf8() {\n        let processor = DefaultTextProcessor;\n        let content = b\"Hello, world!\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.txt\"))\n            .unwrap();\n        assert_eq!(result, \"Hello, world!\");\n    }\n\n    #[test]\n    fn test_invalid_utf8() {\n        let processor = DefaultTextProcessor;\n        let content = b\"Hello\\xFF\\xFEworld\";\n        let result = processor\n            .process(content, &PathBuf::from(\"test.txt\"))\n            .unwrap();\n        assert!(result.contains(\"Hello\"));\n        assert!(result.contains(\"world\"));\n    }\n\n    #[test]\n    fn test_gb2312_encoding_detection() {\n        let processor = DefaultTextProcessor;\n\n        // 1. Create the byte sequence for \"GB2312 test: \"\n        let mut content = b\"GB2312 test: \".to_vec();\n\n        // 2. Append \"你好\" encoded in GB2312 MANY TIMES.\n        // Heuristic detectors need enough data (usually 100+ bytes) to be accurate.\n        // '你' = 0xC4 0xE3\n        // '好' = 0xBA 0xC3\n        let chinese_word = [0xC4, 0xE3, 0xBA, 0xC3];\n\n        // Repeat it 25 times to ensure the detector picks it up\n        for _ in 0..25 {\n            content.extend_from_slice(&chinese_word);\n        }\n\n        // 3. Process\n        let result = processor\n            .process(&content, &PathBuf::from(\"chinese.txt\"))\n            .unwrap();\n\n        // 4. Assert that it was decoded back to UTF-8 correctly\n        // If the processor works, it should turn those hex bytes back into \"你好\"\n        assert!(result.contains(\"你好\"));\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/tests/filter_test.rs",
    "content": "/// This file tests the filter logic\n/// Code2prompt uses the file globbing and globpattern to match files\nuse code2prompt_core::filter::{build_globset, should_include_file};\nuse rstest::*;\nuse std::path::Path;\nuse tempfile::{TempDir, tempdir};\n\n// ~~~ Fixtures ~~~\n#[fixture]\nfn test_dir() -> TempDir {\n    let dir = tempdir().expect(\"Failed to create temp dir\");\n    let lowercase_dir = dir.path().join(\"lowercase\");\n    let uppercase_dir = dir.path().join(\"uppercase\");\n    let secret_dir = dir.path().join(\".secret\");\n    std::fs::create_dir_all(&lowercase_dir).expect(\"Failed to create lowercase directory\");\n    std::fs::create_dir_all(&uppercase_dir).expect(\"Failed to create uppercase directory\");\n    std::fs::create_dir_all(&secret_dir).expect(\"Failed to create secret directory\");\n\n    let files = vec![\n        (\"lowercase/foo.py\", \"content foo.py\"),\n        (\"lowercase/bar.py\", \"content bar.py\"),\n        (\"lowercase/baz.py\", \"content baz.py\"),\n        (\"lowercase/qux.txt\", \"content qux.txt\"),\n        (\"lowercase/corge.txt\", \"content corge.txt\"),\n        (\"lowercase/grault.txt\", \"content grault.txt\"),\n        (\"uppercase/FOO.py\", \"CONTENT FOO.PY\"),\n        (\"uppercase/BAR.py\", \"CONTENT BAR.PY\"),\n        (\"uppercase/BAZ.py\", \"CONTENT BAZ.PY\"),\n        (\"uppercase/QUX.txt\", \"CONTENT QUX.TXT\"),\n        (\"uppercase/CORGE.txt\", \"CONTENT CORGE.TXT\"),\n        (\"uppercase/GRAULT.txt\", \"CONTENT GRAULT.TXT\"),\n        (\".secret/secret.txt\", \"SECRET\"),\n    ];\n\n    for (file_path, content) in files {\n        let path = dir.path().join(file_path);\n        std::fs::create_dir_all(path.parent().unwrap()).unwrap();\n        std::fs::write(path, content).unwrap();\n    }\n    dir\n}\n\nfn base_path(test_dir: &TempDir) -> &Path {\n    test_dir.path()\n}\n\n// ~~~ Filter Tests ~~~\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    // Helper\n    fn test_files_inclusion(\n        base_path: &Path,\n        include_patterns: &[String],\n        exclude_patterns: &[String],\n        expected_included: &[&str],\n        expected_excluded: &[&str],\n    ) {\n        let include_globset = build_globset(include_patterns);\n        let exclude_globset = build_globset(exclude_patterns);\n\n        for file in expected_included {\n            let path = base_path.join(file);\n            let relative_path = path.strip_prefix(base_path).unwrap();\n            assert!(\n                should_include_file(relative_path, &include_globset, &exclude_globset),\n                \"File {} should be included\",\n                file\n            );\n        }\n\n        for file in expected_excluded {\n            let path = base_path.join(file);\n            let relative_path = path.strip_prefix(base_path).unwrap();\n            assert!(\n                !should_include_file(relative_path, &include_globset, &exclude_globset),\n                \"File {} should be excluded\",\n                file\n            );\n        }\n    }\n\n    // ~~~ No Pattern ~~~\n    #[rstest]\n    fn test_no_include_no_exclude_path() {\n        let path = Path::new(\"src/main.rs\");\n        let include_patterns = build_globset(&[]);\n        let exclude_patterns = build_globset(&[]);\n        assert!(should_include_file(\n            path,\n            &include_patterns,\n            &exclude_patterns\n        ));\n    }\n\n    #[rstest]\n    fn test_no_include_no_exclude_empty(test_dir: TempDir) {\n        let base_path = base_path(&test_dir);\n        let include_patterns = vec![];\n        let exclude_patterns = vec![];\n\n        let expected_included = &[\n            \"lowercase/foo.py\",\n            \"lowercase/bar.py\",\n            \"lowercase/baz.py\",\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            &[],\n        );\n    }\n\n    // ~~~ Exclusion Only ~~~\n    #[rstest]\n    fn test_no_include_exclude_path() {\n        let path = Path::new(\"src/main.rs\");\n        let include_patterns = build_globset(&[]);\n        let exclude_patterns = build_globset(&[\"*.rs\".to_string()]);\n        assert!(!should_include_file(\n            path,\n            &include_patterns,\n            &exclude_patterns\n        ));\n    }\n\n    #[rstest]\n    fn test_no_include_exclude_by_filename(test_dir: TempDir) {\n        let base_path = base_path(&test_dir);\n        let include_patterns = vec![];\n        let exclude_patterns = vec![\"default_template.hbs\".to_string()];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            &[],\n            &[\"src/default_template.hbs\"],\n        );\n    }\n\n    #[rstest]\n    fn test_no_include_exclude_path_patterns(test_dir: TempDir) {\n        let base_path = base_path(&test_dir);\n        let include_patterns = vec![];\n        let exclude_patterns = vec![\"lowercase/{*.txt,*.py}\".to_string()];\n\n        let expected_included = &[\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n        ];\n\n        let expected_excluded = &[\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n            \"lowercase/foo.py\",\n            \"lowercase/bar.py\",\n            \"lowercase/baz.py\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    #[rstest]\n    fn test_no_include_exclude_folders(test_dir: TempDir) {\n        let base_path = test_dir.path();\n\n        let include_patterns: Vec<String> = vec![]; // include everything by default\n        let exclude_patterns = vec![\"**/lowercase/**\".to_string()];\n\n        let expected_included = &[\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n        ];\n\n        let expected_excluded = &[\n            \"lowercase/foo.py\",\n            \"lowercase/bar.py\",\n            \"lowercase/baz.py\",\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    #[rstest]\n    fn test_no_include_exclude_files(test_dir: TempDir) {\n        let base_path = test_dir.path();\n\n        let include_patterns: Vec<String> = vec![]; // include everything by default\n        let exclude_patterns = vec![\"**/foo.py\".to_string(), \"**/bar.py\".to_string()];\n\n        let expected_included = &[\n            \"lowercase/baz.py\",\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n        ];\n\n        let expected_excluded = &[\"lowercase/foo.py\", \"lowercase/bar.py\"];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    #[rstest]\n    fn test_no_include_exclude_patterns(test_dir: TempDir) {\n        let base_path = test_dir.path();\n\n        let include_patterns: Vec<String> = vec![]; // include everything by default\n        let exclude_patterns = vec![\"*.txt\".to_string()];\n\n        let expected_included = &[\n            \"lowercase/foo.py\",\n            \"lowercase/bar.py\",\n            \"lowercase/baz.py\",\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n        ];\n\n        let expected_excluded = &[\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    // ~~~ Inclusion Only ~~~\n    #[rstest]\n    fn test_include_no_exclude_patterns(test_dir: TempDir) {\n        let base_path = base_path(&test_dir);\n        let include_patterns = vec![\"*.py\".to_string()];\n        let exclude_patterns = vec![];\n\n        let expected_included = &[\n            \"lowercase/foo.py\",\n            \"lowercase/bar.py\",\n            \"lowercase/baz.py\",\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n        ];\n\n        let expected_excluded = &[\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    #[rstest]\n    fn test_include_no_exclude_files(test_dir: TempDir) {\n        let base_path = test_dir.path();\n\n        let include_patterns = vec![\"**/foo.py\".to_string(), \"**/bar.py\".to_string()];\n        let exclude_patterns = vec![];\n\n        let expected_included = &[\"lowercase/foo.py\", \"lowercase/bar.py\"];\n\n        let expected_excluded = &[\n            \"lowercase/baz.py\",\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    #[rstest]\n    fn test_include_no_exclude_folders(test_dir: TempDir) {\n        let base_path = test_dir.path();\n\n        let include_patterns = vec![\"**/lowercase/**\".to_string()];\n        let exclude_patterns = vec![];\n\n        let expected_included = &[\n            \"lowercase/foo.py\",\n            \"lowercase/bar.py\",\n            \"lowercase/baz.py\",\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n        ];\n\n        let expected_excluded = &[\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    #[rstest]\n    fn test_include_no_exclude_by_path_pattern(test_dir: TempDir) {\n        let base_path = test_dir.path();\n\n        let include_patterns = vec![\"lowercase/{*.txt,*.py}\".to_string()];\n        let exclude_patterns = vec![];\n\n        let expected_included = &[\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n            \"lowercase/foo.py\",\n            \"lowercase/bar.py\",\n            \"lowercase/baz.py\",\n        ];\n\n        let expected_excluded = &[\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n            \".secret/secret.txt\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    #[rstest]\n    fn test_include_no_exclude_by_filename(test_dir: TempDir) {\n        let base_path = test_dir.path();\n\n        let include_patterns = vec![\"default_template.hbs\".to_string()];\n        let exclude_patterns = vec![];\n\n        let expected_included = &[\"src/default_template.hbs\"];\n\n        let expected_excluded = &[\"src/filter.rs\", \"src/git.rs\", \"src/lib.rs\", \"src/token.rs\"];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    // ~~~ Inclusion & Exclusion ~~~\n    #[rstest]\n    fn test_include_exclude_conflict_file(test_dir: TempDir) {\n        let base_path = base_path(&test_dir);\n        let include_patterns = vec![\"**/foo.py\".to_string()];\n        let exclude_patterns = vec![\"**/foo.py\".to_string()];\n\n        // Tous les fichiers devraient être exclus (conflit, exclude l'emporte)\n        let expected_excluded = &[\n            \"lowercase/foo.py\",\n            \"lowercase/bar.py\",\n            \"lowercase/baz.py\",\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            &[],\n            expected_excluded,\n        );\n    }\n\n    #[rstest]\n    fn test_include_exclude_exclude_takes_precedence(test_dir: TempDir) {\n        let base_path = test_dir.path();\n\n        let include_patterns = vec![\"**/*.py\".to_string()];\n        let exclude_patterns = vec![\"**/uppercase/*\".to_string()];\n\n        let expected_included = &[\"lowercase/foo.py\", \"lowercase/bar.py\", \"lowercase/baz.py\"];\n\n        let expected_excluded = &[\n            \"uppercase/FOO.py\",   // excluded explicitly\n            \"lowercase/qux.txt\",  // doesn’t match include\n            \"uppercase/QUX.txt\",  // excluded explicitly\n            \".secret/secret.txt\", // doesn’t match include\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    #[rstest]\n    fn test_include_exclude_conflict_folder(test_dir: TempDir) {\n        let base_path = test_dir.path();\n\n        let include_patterns = vec![\"**/lowercase/**\".to_string()];\n        let exclude_patterns = vec![\"**/lowercase/**\".to_string()];\n\n        let expected_included: &[&str] = &[]; // nothing should be included\n\n        let expected_excluded = &[\n            \"lowercase/foo.py\",\n            \"lowercase/bar.py\",\n            \"lowercase/qux.txt\",\n            \"lowercase/baz.py\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    #[rstest]\n    fn test_include_exclude_conflict_extension(test_dir: TempDir) {\n        let base_path = test_dir.path();\n\n        let include_patterns = vec![\"*.py\".to_string()];\n        let exclude_patterns = vec![\"*.py\".to_string()];\n\n        let expected_included: &[&str] = &[]; // nothing included\n\n        let expected_excluded = &[\n            \"lowercase/foo.py\",\n            \"lowercase/bar.py\",\n            \"lowercase/baz.py\",\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n\n    // ~~~ Brace expansion ~~~\n    #[rstest]\n    fn test_brace_expansion_first_item(test_dir: TempDir) {\n        let base_path: &Path = base_path(&test_dir);\n        let include_patterns = vec![\"lowercase/{foo.py,bar.py,baz.py}\".to_string()];\n        let exclude_patterns = vec![\"lowercase/{qux.py,corge.py,grault.py}\".to_string()];\n\n        let expected_included = &[\"foo.py\", \"bar.py\", \"baz.py\"]\n            .iter()\n            .map(|f| format!(\"lowercase/{}\", f))\n            .collect::<Vec<_>>();\n\n        let expected_excluded = &[\"qux.txt\", \"corge.txt\", \"grault.txt\"]\n            .iter()\n            .map(|f| format!(\"lowercase/{}\", f))\n            .collect::<Vec<_>>();\n\n        // Conversion pour utiliser avec test_files_inclusion\n        let expected_included: Vec<&str> = expected_included.iter().map(|s| s.as_str()).collect();\n        let expected_excluded: Vec<&str> = expected_excluded.iter().map(|s| s.as_str()).collect();\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            &expected_included,\n            &expected_excluded,\n        );\n    }\n\n    #[rstest]\n    fn test_brace_expansion_multiple_patterns(test_dir: TempDir) {\n        let base_path: &Path = base_path(&test_dir);\n\n        let include_patterns = vec![\n            \"lowercase/{foo,bar,baz}.py\".to_string(),\n            \"uppercase/{FOO,BAR,BAZ}.py\".to_string(),\n        ];\n        let exclude_patterns = vec![];\n\n        // Explicitly list what should be included\n        let expected_included = &[\n            \"lowercase/foo.py\",\n            \"lowercase/bar.py\",\n            \"lowercase/baz.py\",\n            \"uppercase/FOO.py\",\n            \"uppercase/BAR.py\",\n            \"uppercase/BAZ.py\",\n        ];\n\n        // Explicitly list what should be excluded\n        let expected_excluded = &[\n            \"lowercase/qux.txt\",\n            \"lowercase/corge.txt\",\n            \"lowercase/grault.txt\",\n            \"uppercase/QUX.txt\",\n            \"uppercase/CORGE.txt\",\n            \"uppercase/GRAULT.txt\",\n            \".secret/secret.txt\",\n        ];\n\n        test_files_inclusion(\n            base_path,\n            &include_patterns,\n            &exclude_patterns,\n            expected_included,\n            expected_excluded,\n        );\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/tests/git_test.rs",
    "content": "use code2prompt_core::git::{get_git_diff, get_git_diff_between_branches, get_git_log};\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use git2::{Repository, RepositoryInitOptions, Signature};\n    use std::fs;\n    use tempfile::TempDir;\n\n    #[test]\n    fn test_get_git_diff() {\n        // Create a temporary directory\n        let temp_dir = TempDir::new().expect(\"Failed to create temp dir\");\n        let repo_path = temp_dir.path();\n\n        // Initialize a new Git repository\n        let repo = Repository::init(repo_path).expect(\"Failed to initialize repository\");\n\n        // Create a new file in the repository\n        let file_path = repo_path.join(\"test_file.txt\");\n        fs::write(&file_path, \"Initial content\").expect(\"Failed to write to test file\");\n\n        // Stage and commit the new file\n        let mut index = repo.index().expect(\"Failed to get repository index\");\n        index\n            .add_path(file_path.strip_prefix(repo_path).unwrap())\n            .expect(\"Failed to add file to index\");\n        index.write().expect(\"Failed to write index\");\n\n        let tree_id = index.write_tree().expect(\"Failed to write tree\");\n        let tree = repo.find_tree(tree_id).expect(\"Failed to find tree\");\n        let signature =\n            Signature::now(\"Test\", \"test@example.com\").expect(\"Failed to create signature\");\n\n        repo.commit(\n            Some(\"HEAD\"),\n            &signature,\n            &signature,\n            \"Initial commit\",\n            &tree,\n            &[],\n        )\n        .expect(\"Failed to commit\");\n\n        // Modify the file\n        fs::write(&file_path, \"Modified content\").expect(\"Failed to modify test file\");\n\n        // Add the modified file to the index again\n        let mut index = repo.index().expect(\"Failed to get repository index\");\n        index\n            .add_path(file_path.strip_prefix(repo_path).unwrap())\n            .expect(\"Failed to add file to index\");\n        index.write().expect(\"Failed to write index\");\n\n        // Get the git diff using the function from the module\n        let diff = get_git_diff(repo_path).expect(\"Failed to get git diff\");\n\n        // Print the diff for debugging\n        println!(\"Generated diff:\\n{}\", diff);\n\n        // Assert that the diff contains the expected content\n        assert!(diff.contains(\"Modified content\"));\n    }\n\n    #[test]\n    fn test_get_git_diff_between_branches() {\n        // Create a temporary directory\n        let temp_dir = TempDir::new().expect(\"Failed to create temp dir\");\n        let repo_path = temp_dir.path();\n\n        // Initialize a new Git repository\n        let mut binding = RepositoryInitOptions::new();\n        let init_options = binding.initial_head(\"master\");\n        let repo = Repository::init_opts(repo_path, init_options)\n            .expect(\"Failed to initialize repository\");\n\n        // Create a new file in the repository\n        let file_path = repo_path.join(\"test_file.txt\");\n        fs::write(&file_path, \"Initial content\").expect(\"Failed to write to test file\");\n\n        // Stage and commit the new file\n        let mut index = repo.index().expect(\"Failed to get repository index\");\n        index\n            .add_path(file_path.strip_prefix(repo_path).unwrap())\n            .expect(\"Failed to add file to index\");\n        index.write().expect(\"Failed to write index\");\n\n        let tree_id = index.write_tree().expect(\"Failed to write tree\");\n        let tree = repo.find_tree(tree_id).expect(\"Failed to find tree\");\n        let signature =\n            Signature::now(\"Test\", \"test@example.com\").expect(\"Failed to create signature\");\n\n        let master_commit = repo\n            .commit(\n                Some(\"HEAD\"),\n                &signature,\n                &signature,\n                \"Initial commit in master branch\",\n                &tree,\n                &[],\n            )\n            .expect(\"Failed to commit\");\n\n        // Create a new branch and make a commit on the master branch\n        repo.branch(\n            \"development\",\n            &repo\n                .find_commit(master_commit)\n                .expect(\"Failed to find commit\"),\n            false,\n        )\n        .expect(\"Failed to create new branch\");\n\n        // Modify the file in the new branch\n        repo.set_head(\"refs/heads/development\")\n            .expect(\"Failed to set HEAD\");\n        repo.checkout_head(None).expect(\"Failed to checkout HEAD\");\n        fs::write(&file_path, \"Content in new branch\")\n            .expect(\"Failed to modify test file in new branch\");\n\n        let mut index = repo.index().expect(\"Failed to get repository index\");\n        index\n            .add_path(file_path.strip_prefix(repo_path).unwrap())\n            .expect(\"Failed to add file to index\");\n        index.write().expect(\"Failed to write index\");\n\n        let tree_id = index.write_tree().expect(\"Failed to write tree\");\n        let tree = repo.find_tree(tree_id).expect(\"Failed to find tree\");\n\n        repo.commit(\n            Some(\"HEAD\"),\n            &signature,\n            &signature,\n            \"New commit in branch development\",\n            &tree,\n            &[&repo\n                .find_commit(master_commit)\n                .expect(\"Failed to find commit\")],\n        )\n        .expect(\"Failed to commit in new branch\");\n\n        // Get the git diff between branches\n        let diff = get_git_diff_between_branches(repo_path, \"master\", \"development\")\n            .expect(\"Failed to get git diff between branches\");\n\n        // Print the diff for debugging\n        println!(\"Generated diff between branches:\\n{}\", diff);\n\n        // Assert that the diff contains the expected content\n        assert!(diff.contains(\"Initial content\"));\n        assert!(diff.contains(\"Content in new branch\"));\n    }\n\n    #[test]\n    fn test_get_git_log() {\n        // Create a temporary directory\n        let temp_dir = TempDir::new().expect(\"Failed to create temp dir\");\n        let repo_path = temp_dir.path();\n\n        // Initialize a new Git repository\n        let mut binding = RepositoryInitOptions::new();\n        let init_options = binding.initial_head(\"master\");\n        let repo = Repository::init_opts(repo_path, init_options)\n            .expect(\"Failed to initialize repository\");\n\n        // Create a new file in the repository\n        let file_path = repo_path.join(\"test_file.txt\");\n        fs::write(&file_path, \"Initial content\").expect(\"Failed to write to test file\");\n\n        // Stage and commit the new file\n        let mut index = repo.index().expect(\"Failed to get repository index\");\n        index\n            .add_path(file_path.strip_prefix(repo_path).unwrap())\n            .expect(\"Failed to add file to index\");\n        index.write().expect(\"Failed to write index\");\n\n        let tree_id = index.write_tree().expect(\"Failed to write tree\");\n        let tree = repo.find_tree(tree_id).expect(\"Failed to find tree\");\n        let signature =\n            Signature::now(\"Test\", \"test@example.com\").expect(\"Failed to create signature\");\n\n        let master_commit = repo\n            .commit(\n                Some(\"HEAD\"),\n                &signature,\n                &signature,\n                \"Initial commit in branch master\",\n                &tree,\n                &[],\n            )\n            .expect(\"Failed to commit\");\n\n        // Create a new branch and make a commit on the master branch\n        repo.branch(\n            \"development\",\n            &repo\n                .find_commit(master_commit)\n                .expect(\"Failed to find commit\"),\n            false,\n        )\n        .expect(\"Failed to create new branch\");\n\n        // Modify the file in the new branch\n        repo.set_head(\"refs/heads/development\")\n            .expect(\"Failed to set HEAD\");\n        repo.checkout_head(None).expect(\"Failed to checkout HEAD\");\n        fs::write(&file_path, \"Content in development\")\n            .expect(\"Failed to modify test file in new branch\");\n\n        let mut index = repo.index().expect(\"Failed to get repository index\");\n        index\n            .add_path(file_path.strip_prefix(repo_path).unwrap())\n            .expect(\"Failed to add file to index\");\n        index.write().expect(\"Failed to write index\");\n\n        let tree_id = index.write_tree().expect(\"Failed to write tree\");\n        let tree = repo.find_tree(tree_id).expect(\"Failed to find tree\");\n\n        repo.commit(\n            Some(\"HEAD\"),\n            &signature,\n            &signature,\n            \"First commit in development\",\n            &tree,\n            &[&repo\n                .find_commit(master_commit)\n                .expect(\"Failed to find commit\")],\n        )\n        .expect(\"Failed to commit in new branch\");\n\n        // Make a second commit in the development branch\n        fs::write(&file_path, \"Second content in development\")\n            .expect(\"Failed to modify test file in new branch\");\n\n        let mut index = repo.index().expect(\"Failed to get repository index\");\n        index\n            .add_path(file_path.strip_prefix(repo_path).unwrap())\n            .expect(\"Failed to add file to index\");\n        index.write().expect(\"Failed to write index\");\n\n        let tree_id = index.write_tree().expect(\"Failed to write tree\");\n        let tree = repo.find_tree(tree_id).expect(\"Failed to find tree\");\n\n        repo.commit(\n            Some(\"HEAD\"),\n            &signature,\n            &signature,\n            \"Second commit in development\",\n            &tree,\n            &[&repo\n                .find_commit(repo.head().unwrap().target().unwrap())\n                .expect(\"Failed to find commit\")],\n        )\n        .expect(\"Failed to commit second change in new branch\");\n\n        // Get the git log between branches\n        let log = get_git_log(repo_path, \"master\", \"development\")\n            .expect(\"Failed to get git log between branches\");\n\n        // Print the log for debugging\n        println!(\"Generated git log:\\n{}\", log);\n\n        // Assert that the log contains the expected content\n        assert!(log.contains(\"First commit in development\"));\n        assert!(log.contains(\"Second commit in development\"));\n    }\n\n    #[test]\n    fn test_git_diff_with_commit_hashes_and_tags() {\n        // Create a temporary directory\n        let temp_dir = TempDir::new().expect(\"Failed to create temp dir\");\n        let repo_path = temp_dir.path();\n\n        // Initialize a new Git repository\n        let mut binding = RepositoryInitOptions::new();\n        let init_options = binding.initial_head(\"master\");\n        let repo = Repository::init_opts(repo_path, init_options)\n            .expect(\"Failed to initialize repository\");\n\n        // Create a new file in the repository\n        let file_path = repo_path.join(\"test_file.txt\");\n        fs::write(&file_path, \"Initial content\").expect(\"Failed to write to test file\");\n\n        // Stage and commit the new file\n        let mut index = repo.index().expect(\"Failed to get repository index\");\n        index\n            .add_path(file_path.strip_prefix(repo_path).unwrap())\n            .expect(\"Failed to add file to index\");\n        index.write().expect(\"Failed to write index\");\n\n        let tree_id = index.write_tree().expect(\"Failed to write tree\");\n        let tree = repo.find_tree(tree_id).expect(\"Failed to find tree\");\n        let signature =\n            Signature::now(\"Test\", \"test@example.com\").expect(\"Failed to create signature\");\n\n        let first_commit_id = repo\n            .commit(\n                Some(\"HEAD\"),\n                &signature,\n                &signature,\n                \"First commit\",\n                &tree,\n                &[],\n            )\n            .expect(\"Failed to commit\");\n\n        // Create a tag for the first commit\n        let first_commit = repo\n            .find_commit(first_commit_id)\n            .expect(\"Failed to find first commit\");\n        repo.tag(\n            \"v1.0.0\",\n            first_commit.as_object(),\n            &signature,\n            \"Version 1.0.0\",\n            false,\n        )\n        .expect(\"Failed to create tag\");\n\n        // Make a second commit\n        fs::write(&file_path, \"Modified content\").expect(\"Failed to modify test file\");\n        let mut index = repo.index().expect(\"Failed to get repository index\");\n        index\n            .add_path(file_path.strip_prefix(repo_path).unwrap())\n            .expect(\"Failed to add file to index\");\n        index.write().expect(\"Failed to write index\");\n\n        let tree_id = index.write_tree().expect(\"Failed to write tree\");\n        let tree = repo.find_tree(tree_id).expect(\"Failed to find tree\");\n\n        let second_commit_id = repo\n            .commit(\n                Some(\"HEAD\"),\n                &signature,\n                &signature,\n                \"Second commit\",\n                &tree,\n                &[&first_commit],\n            )\n            .expect(\"Failed to commit second change\");\n\n        // Test 1: Diff between commit hashes (full hash)\n        let first_commit_hash = first_commit_id.to_string();\n        let second_commit_hash = second_commit_id.to_string();\n\n        let diff_full_hash =\n            get_git_diff_between_branches(repo_path, &first_commit_hash, &second_commit_hash)\n                .expect(\"Failed to get git diff between full commit hashes\");\n\n        assert!(diff_full_hash.contains(\"Initial content\"));\n        assert!(diff_full_hash.contains(\"Modified content\"));\n\n        // Test 2: Diff between abbreviated commit hashes\n        let first_commit_short = &first_commit_hash[..7];\n        let second_commit_short = &second_commit_hash[..7];\n\n        let diff_short_hash =\n            get_git_diff_between_branches(repo_path, first_commit_short, second_commit_short)\n                .expect(\"Failed to get git diff between abbreviated commit hashes\");\n\n        assert!(diff_short_hash.contains(\"Initial content\"));\n        assert!(diff_short_hash.contains(\"Modified content\"));\n\n        // Test 3: Diff between tag and commit hash\n        let diff_tag_to_hash =\n            get_git_diff_between_branches(repo_path, \"v1.0.0\", &second_commit_hash)\n                .expect(\"Failed to get git diff between tag and commit hash\");\n\n        assert!(diff_tag_to_hash.contains(\"Initial content\"));\n        assert!(diff_tag_to_hash.contains(\"Modified content\"));\n\n        // Test 4: Diff between tag and HEAD\n        let diff_tag_to_head = get_git_diff_between_branches(repo_path, \"v1.0.0\", \"HEAD\")\n            .expect(\"Failed to get git diff between tag and HEAD\");\n\n        assert!(diff_tag_to_head.contains(\"Initial content\"));\n        assert!(diff_tag_to_head.contains(\"Modified content\"));\n\n        // Test 5: Error case - invalid reference should still fail\n        let result = get_git_diff_between_branches(repo_path, \"nonexistent_reference\", \"HEAD\");\n\n        assert!(result.is_err());\n        assert!(result\n            .unwrap_err()\n            .to_string()\n            .contains(\"Branch nonexistent_reference doesn't exist!\"));\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/tests/path_test.rs",
    "content": "//! # Path Module Tests\n//!\n//! Tests for path traversal, directory structure handling, and file processing.\n//! Uses rstest for parameterized testing and fixtures for test environment setup.\n\nuse code2prompt_core::{\n    configuration::Code2PromptConfig,\n    path::{EntryMetadata, FileEntry, traverse_directory},\n};\nuse git2::Repository;\nuse rstest::*;\nuse std::{\n    fs::{self},\n    path::Path,\n};\nuse tempfile::{TempDir, tempdir};\n\n// ~~~ Fixtures ~~~\n\n/// Creates a temporary directory with a git repository and test files\n#[fixture]\nfn git_repo_with_files() -> TempDir {\n    let dir = tempdir().expect(\"Failed to create temp dir\");\n    let _repo = Repository::init(dir.path()).expect(\"Failed to init git repo\");\n\n    // Create test files, including one in target/\n    let files = vec![\n        (\"src/main.rs\", \"// Main file\"),\n        (\"target/debug/app\", \"// Binary in target/\"),\n        (\".gitignore\", \"target/\\n*.log\"),\n        (\"README.md\", \"# Project Code2prompt\"),\n    ];\n\n    for (path, content) in files {\n        let full_path = dir.path().join(path);\n        if let Some(parent) = full_path.parent() {\n            fs::create_dir_all(parent).expect(\"Failed to create dir\");\n        }\n        fs::write(full_path, content).expect(\"Failed to write file\");\n    }\n    dir\n}\n\n/// Creates a simple directory structure without git\n#[fixture]\nfn simple_dir_structure() -> TempDir {\n    let dir = tempdir().expect(\"Failed to create temp dir\");\n\n    let files = vec![\n        (\"file1.txt\", \"Content 1\"),\n        (\"subdir/file2.txt\", \"Content 2\"),\n        (\"subdir/nested/file3.txt\", \"Content 3\"),\n    ];\n\n    for (path, content) in files {\n        let full_path = dir.path().join(path);\n        if let Some(parent) = full_path.parent() {\n            fs::create_dir_all(parent).expect(\"Failed to create dir\");\n        }\n        fs::write(full_path, content).expect(\"Failed to write file\");\n    }\n\n    dir\n}\n\n/// Helper to create a basic config for testing\nfn base_config(path: &Path) -> Code2PromptConfig {\n    Code2PromptConfig::builder()\n        .path(path.to_path_buf())\n        .build()\n        .expect(\"Failed to build config\")\n}\n\n// ~~~ Test Helpers ~~~\n\n/// Checks if a file exists in the output\nfn file_exists(files: &[FileEntry], path: &str) -> bool {\n    files.iter().any(|file| file.path.contains(path))\n}\n\n/// Gets metadata for a specific file\nfn get_metadata(files: &[FileEntry], path: &str) -> Option<EntryMetadata> {\n    files\n        .iter()\n        .find(|file| file.path.contains(path))\n        .map(|file| file.metadata)\n}\n\n// ~~~ Tests ~~~\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    // ~~~ Basic Traversal Tests ~~~\n\n    #[rstest]\n    fn test_basic_traversal(simple_dir_structure: TempDir) {\n        let config = base_config(simple_dir_structure.path());\n        let (tree_str, files) = traverse_directory(&config, None).unwrap();\n\n        // Check tree contains all files\n        assert!(tree_str.contains(\"file1.txt\"));\n        assert!(tree_str.contains(\"subdir\"));\n        assert!(tree_str.contains(\"file2.txt\"));\n\n        // Check files are processed\n        assert_eq!(files.len(), 3);\n        assert!(file_exists(&files, \"file1.txt\"));\n        assert!(file_exists(&files, \"file2.txt\"));\n        assert!(file_exists(&files, \"file3.txt\"));\n    }\n\n    // ~~~ Git Ignore Tests ~~~\n\n    #[rstest]\n    fn test_respects_gitignore(git_repo_with_files: TempDir) {\n        let config = Code2PromptConfig::builder()\n            .path(git_repo_with_files.path().to_path_buf())\n            .no_ignore(false) // Respect .gitignore\n            .build()\n            .unwrap();\n\n        let (_, files) = traverse_directory(&config, None).unwrap();\n\n        // Verify target/ files are excluded\n        assert!(!file_exists(&files, \"target/debug/app\"));\n\n        // Verify non-ignored files are included\n        assert!(file_exists(&files, \"src/main.rs\"));\n        assert!(file_exists(&files, \"README.md\"));\n    }\n\n    #[rstest]\n    fn test_ignores_gitignore_when_disabled(git_repo_with_files: TempDir) {\n        let config = Code2PromptConfig::builder()\n            .path(git_repo_with_files.path().to_path_buf())\n            .no_ignore(true)\n            .build()\n            .unwrap();\n\n        let (_, files) = traverse_directory(&config, None).unwrap();\n\n        assert!(file_exists(&files, \"src/main.rs\"));\n        assert!(file_exists(&files, \"README.md\"));\n        assert!(file_exists(&files, \"target/debug/app\"));\n    }\n\n    // ~~~ Hidden Files Tests ~~~\n    #[rstest]\n    fn test_excludes_hidden_files_by_default(simple_dir_structure: TempDir) {\n        // Add a hidden file\n        fs::write(simple_dir_structure.path().join(\".hidden\"), \"secret\").unwrap();\n\n        let config = base_config(simple_dir_structure.path());\n        let (tree_str, files) = traverse_directory(&config, None).unwrap();\n\n        // Hidden file should not appear\n        assert!(!tree_str.contains(\".hidden\"));\n        assert!(!file_exists(&files, \".hidden\"));\n    }\n\n    #[rstest]\n    fn test_includes_hidden_files_when_enabled(simple_dir_structure: TempDir) {\n        // Add a hidden file\n        fs::write(simple_dir_structure.path().join(\".hidden\"), \"secret\").unwrap();\n\n        let config = Code2PromptConfig::builder()\n            .path(simple_dir_structure.path().to_path_buf())\n            .hidden(true)\n            .build()\n            .unwrap();\n\n        let (tree_str, files) = traverse_directory(&config, None).unwrap();\n\n        // Hidden file should appear\n        assert!(tree_str.contains(\".hidden\"));\n        assert!(file_exists(&files, \".hidden\"));\n    }\n\n    // ~~~ File Content Tests ~~~\n    #[rstest]\n    fn test_file_content_processing(simple_dir_structure: TempDir) {\n        let config = Code2PromptConfig::builder()\n            .path(simple_dir_structure.path().to_path_buf())\n            .line_numbers(true)\n            .build()\n            .unwrap();\n\n        let (_, files) = traverse_directory(&config, None).unwrap();\n\n        // Find file1.txt and check its content\n        if let Some(file) = files.iter().find(|f| f.path.contains(\"file1.txt\")) {\n            let code = &file.code;\n            assert!(code.contains(\"Content 1\"));\n            assert!(code.contains(\"1 |\")); // Line numbers should be present\n        } else {\n            panic!(\"file1.txt not found in output\");\n        }\n    }\n\n    // ~~~ Metadata Tests ~~~\n\n    #[rstest]\n    fn test_file_metadata(simple_dir_structure: TempDir) {\n        let config = base_config(simple_dir_structure.path());\n        let (_, files) = traverse_directory(&config, None).unwrap();\n\n        // Check metadata for file1.txt\n        if let Some(metadata) = get_metadata(&files, \"file1.txt\") {\n            assert!(!metadata.is_dir);\n            assert!(!metadata.is_symlink);\n        } else {\n            panic!(\"Metadata not found for file1.txt\");\n        }\n    }\n\n    // ~~~ Absolute vs Relative Path Tests ~~~\n\n    #[rstest]\n    fn test_relative_paths_by_default(simple_dir_structure: TempDir) {\n        let config = base_config(simple_dir_structure.path());\n        let (_, files) = traverse_directory(&config, None).unwrap();\n\n        // Paths should be relative by default\n        assert!(files.iter().all(|file| !file.path.starts_with('/')));\n    }\n\n    #[rstest]\n    fn test_absolute_paths_when_enabled(simple_dir_structure: TempDir) {\n        let config = Code2PromptConfig::builder()\n            .path(simple_dir_structure.path().to_path_buf())\n            .absolute_path(true)\n            .build()\n            .unwrap();\n\n        let (_, files) = traverse_directory(&config, None).unwrap();\n\n        // Paths should be absolute when enabled\n        let abs_path = simple_dir_structure.path().canonicalize().unwrap();\n        assert!(\n            files\n                .iter()\n                .all(|file| file.path.starts_with(abs_path.to_str().unwrap()))\n        );\n    }\n\n    // ~~~ Symlink Tests ~~~\n\n    // #[rstest]\n    // #[cfg(unix)] // Only run on Unix\n    // fn test_symlink_following_disabled_by_default(simple_dir_structure: TempDir) {\n    //     // Create a symlink to file1.txt\n    //     let link_path = simple_dir_structure.path().join(\"link_to_file\");\n    //     std::os::unix::fs::symlink(simple_dir_structure.path().join(\"file1.txt\"), &link_path)\n    //         .unwrap();\n\n    //     // Traverse with follow_symlinks=false (default)\n    //     let config = base_config(simple_dir_structure.path());\n    //     let (tree_str, files) = traverse_directory(&config, None).unwrap();\n\n    //     // 1. Symlink should appear in the tree (it's a directory entry)\n    //     assert!(tree_str.contains(\"link_to_file\"));\n\n    //     // 2. But its *content* (file1.txt's content) should NOT appear in `files`\n    //     //    because we didn't follow the symlink.\n    //     assert!(!file_exists(&files, \"link_to_file\"));\n\n    //     // 3. file1.txt should still exist independently\n    //     assert!(file_exists(&files, \"file1.txt\"));\n    // }\n\n    #[rstest]\n    fn test_symlink_following_when_enabled(simple_dir_structure: TempDir) {\n        let link_path = simple_dir_structure.path().join(\"link_to_file\");\n        #[cfg(unix)]\n        {\n            std::os::unix::fs::symlink(simple_dir_structure.path().join(\"file1.txt\"), &link_path)\n                .unwrap();\n        }\n\n        let config = Code2PromptConfig::builder()\n            .path(simple_dir_structure.path().to_path_buf())\n            .follow_symlinks(true)\n            .build()\n            .unwrap();\n\n        let (tree_str, _) = traverse_directory(&config, None).unwrap();\n\n        // Symlink should be followed when enabled\n        #[cfg(unix)]\n        assert!(tree_str.contains(\"link_to_file\"));\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/tests/session_integration_test.rs",
    "content": "//! Integration tests for the session with simplified file selection\n\nuse code2prompt_core::configuration::Code2PromptConfig;\nuse code2prompt_core::session::Code2PromptSession;\nuse std::fs;\nuse tempfile::TempDir;\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    fn create_test_project() -> TempDir {\n        let temp_dir = TempDir::new().unwrap();\n        let base_path = temp_dir.path();\n\n        // Create test directory structure\n        fs::create_dir_all(base_path.join(\"src\")).unwrap();\n        fs::create_dir_all(base_path.join(\"tests\")).unwrap();\n\n        // Create test files\n        fs::write(base_path.join(\"src/main.rs\"), \"fn main() {}\").unwrap();\n        fs::write(base_path.join(\"src/lib.rs\"), \"pub mod utils;\").unwrap();\n        fs::write(base_path.join(\"src/utils.rs\"), \"pub fn helper() {}\").unwrap();\n        fs::write(base_path.join(\"tests/test_main.rs\"), \"#[test] fn test() {}\").unwrap();\n        fs::write(base_path.join(\"README.md\"), \"# Test Project\").unwrap();\n\n        temp_dir\n    }\n\n    #[test]\n    fn test_session_select_deselect_file() {\n        let temp_dir = create_test_project();\n        let config = Code2PromptConfig::builder()\n            .path(temp_dir.path().to_path_buf())\n            .exclude_patterns(vec![\"*\".to_string()]) // Exclude everything initially\n            .build()\n            .unwrap();\n\n        let mut session = Code2PromptSession::new(config);\n        let main_rs_relative = std::path::PathBuf::from(\"src/main.rs\");\n\n        // Initially, no files should be selected (excluded by pattern)\n        assert!(!session.is_file_selected(&main_rs_relative));\n        assert!(session.get_selected_files().unwrap().is_empty());\n\n        // Select the file using relative path (user action overrides pattern)\n        session.select_file(main_rs_relative.clone());\n        assert!(session.is_file_selected(&main_rs_relative));\n        assert_eq!(session.get_selected_files().unwrap().len(), 1);\n\n        // Deselect the file\n        session.deselect_file(main_rs_relative.clone());\n        assert!(!session.is_file_selected(&main_rs_relative));\n        assert!(session.get_selected_files().unwrap().is_empty());\n    }\n\n    #[test]\n    fn test_session_multiple_files() {\n        let temp_dir = create_test_project();\n        let config = Code2PromptConfig::builder()\n            .path(temp_dir.path().to_path_buf())\n            .build()\n            .unwrap();\n\n        let mut session = Code2PromptSession::new(config);\n        let main_rs_relative = std::path::PathBuf::from(\"src/main.rs\");\n        let utils_rs_relative = std::path::PathBuf::from(\"src/utils.rs\");\n        let readme_relative = std::path::PathBuf::from(\"README.md\");\n\n        // Select multiple files using relative paths\n        session.select_file(main_rs_relative.clone());\n        session.select_file(utils_rs_relative.clone());\n        session.select_file(readme_relative.clone());\n\n        assert!(session.is_file_selected(&main_rs_relative));\n        assert!(session.is_file_selected(&utils_rs_relative));\n        assert!(session.is_file_selected(&readme_relative));\n        assert_eq!(session.get_selected_files().unwrap().len(), 3);\n\n        // Deselect one file\n        session.deselect_file(utils_rs_relative.clone());\n        assert!(session.is_file_selected(&main_rs_relative));\n        assert!(!session.is_file_selected(&utils_rs_relative));\n        assert!(session.is_file_selected(&readme_relative));\n        assert_eq!(session.get_selected_files().unwrap().len(), 2);\n    }\n\n    #[test]\n    fn test_session_multiple_file_selection() {\n        let temp_dir = create_test_project();\n        let config = Code2PromptConfig::builder()\n            .path(temp_dir.path().to_path_buf())\n            .build()\n            .unwrap();\n\n        let mut session = Code2PromptSession::new(config);\n        let main_rs_relative = std::path::PathBuf::from(\"src/main.rs\");\n        let utils_rs_relative = std::path::PathBuf::from(\"src/utils.rs\");\n\n        // Select multiple files individually using relative paths\n        session.select_file(main_rs_relative.clone());\n        session.select_file(utils_rs_relative.clone());\n\n        assert!(session.is_file_selected(&main_rs_relative));\n        assert!(session.is_file_selected(&utils_rs_relative));\n        assert_eq!(session.get_selected_files().unwrap().len(), 2);\n    }\n\n    #[test]\n    fn test_session_clear_user_actions() {\n        let temp_dir = create_test_project();\n        let config = Code2PromptConfig::builder()\n            .path(temp_dir.path().to_path_buf())\n            .exclude_patterns(vec![\"*\".to_string()]) // Exclude everything initially\n            .build()\n            .unwrap();\n\n        let mut session = Code2PromptSession::new(config);\n        let main_rs_relative = std::path::PathBuf::from(\"src/main.rs\");\n        let utils_rs_relative = std::path::PathBuf::from(\"src/utils.rs\");\n\n        // Select some files using relative paths (user actions override exclude patterns)\n        session.select_file(main_rs_relative.clone());\n        session.select_file(utils_rs_relative.clone());\n        assert_eq!(session.get_selected_files().unwrap().len(), 2);\n\n        // Clear all user actions (reset to pattern-only behavior)\n        session.clear_user_actions();\n        // After clearing user actions, files should be excluded by the exclude pattern\n        assert!(session.get_selected_files().unwrap().is_empty());\n    }\n\n    #[test]\n    fn test_session_add_patterns() {\n        let temp_dir = create_test_project();\n        let config = Code2PromptConfig::builder()\n            .path(temp_dir.path().to_path_buf())\n            .build()\n            .unwrap();\n\n        let mut session = Code2PromptSession::new(config);\n\n        // Initially no patterns\n        assert!(session.config.include_patterns.is_empty());\n        assert!(session.config.exclude_patterns.is_empty());\n\n        // Add patterns\n        session.add_include_pattern(\"*.rs\".to_string());\n        session.add_exclude_pattern(\"**/test*\".to_string());\n\n        assert_eq!(session.config.include_patterns.len(), 1);\n        assert_eq!(session.config.exclude_patterns.len(), 1);\n        assert_eq!(session.config.include_patterns[0], \"*.rs\");\n        assert_eq!(session.config.exclude_patterns[0], \"**/test*\");\n    }\n\n    #[test]\n    fn test_session_relative_path_handling() {\n        let temp_dir = create_test_project();\n        let config = Code2PromptConfig::builder()\n            .path(temp_dir.path().to_path_buf())\n            .build()\n            .unwrap();\n\n        let mut session = Code2PromptSession::new(config);\n        let main_rs_absolute = temp_dir.path().join(\"src/main.rs\");\n        let main_rs_relative = std::path::PathBuf::from(\"src/main.rs\");\n\n        // Select using absolute path\n        session.select_file(main_rs_absolute.clone());\n\n        // Should be found using both absolute and relative paths\n        assert!(session.is_file_selected(&main_rs_absolute));\n        assert!(session.is_file_selected(&main_rs_relative));\n\n        // The stored path should be relative\n        let selected_files = session.get_selected_files().unwrap();\n        assert_eq!(selected_files.len(), 1);\n        assert_eq!(selected_files[0], main_rs_relative);\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/tests/sort_test.rs",
    "content": "use code2prompt_core::path::{EntryMetadata, FileEntry};\nuse code2prompt_core::sort::{FileSortMethod, sort_files, sort_tree};\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use termtree::Tree;\n\n    #[test]\n    fn test_sort_files_name_asc() {\n        // Create a vector of FileEntry objects\n        let mut files = vec![\n            FileEntry {\n                path: \"zeta.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(100),\n            },\n            FileEntry {\n                path: \"alpha.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(200),\n            },\n            FileEntry {\n                path: \"beta.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(150),\n            },\n        ];\n\n        // Sort by file name in ascending order (A → Z)\n        sort_files(&mut files, Some(FileSortMethod::NameAsc));\n\n        // Expected order is: \"alpha.txt\", \"beta.txt\", \"zeta.txt\"\n        let expected = vec![\"alpha.txt\", \"beta.txt\", \"zeta.txt\"];\n        let result: Vec<String> = files.iter().map(|f| f.path.clone()).collect();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn test_sort_files_name_desc() {\n        // Create a vector of FileEntry objects\n        let mut files = vec![\n            FileEntry {\n                path: \"alpha.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(100),\n            },\n            FileEntry {\n                path: \"zeta.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(200),\n            },\n            FileEntry {\n                path: \"beta.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(150),\n            },\n        ];\n\n        // Sort by file name in descending order (Z → A)\n        sort_files(&mut files, Some(FileSortMethod::NameDesc));\n\n        // Expected order is: \"zeta.txt\", \"beta.txt\", \"alpha.txt\"\n        let expected = vec![\"zeta.txt\", \"beta.txt\", \"alpha.txt\"];\n        let result: Vec<String> = files.iter().map(|f| f.path.clone()).collect();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn test_sort_files_date_asc() {\n        // Create a vector of FileEntry objects\n        let mut files = vec![\n            FileEntry {\n                path: \"file1.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(300),\n            },\n            FileEntry {\n                path: \"file2.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(100),\n            },\n            FileEntry {\n                path: \"file3.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(200),\n            },\n        ];\n\n        // Sort by modification time in ascending order (oldest first)\n        sort_files(&mut files, Some(FileSortMethod::DateAsc));\n\n        // Expected order is: \"file2.txt\" (100), \"file3.txt\" (200), \"file1.txt\" (300)\n        let expected = vec![\"file2.txt\", \"file3.txt\", \"file1.txt\"];\n        let result: Vec<String> = files.iter().map(|f| f.path.clone()).collect();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn test_sort_files_date_desc() {\n        // Create a vector of FileEntry objects\n        let mut files = vec![\n            FileEntry {\n                path: \"file1.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(300),\n            },\n            FileEntry {\n                path: \"file2.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(100),\n            },\n            FileEntry {\n                path: \"file3.txt\".to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some(200),\n            },\n        ];\n\n        // Sort by modification time in descending order (newest first)\n        sort_files(&mut files, Some(FileSortMethod::DateDesc));\n\n        // Expected order is: \"file1.txt\" (300), \"file3.txt\" (200), \"file2.txt\" (100)\n        let expected = vec![\"file1.txt\", \"file3.txt\", \"file2.txt\"];\n        let result: Vec<String> = files.iter().map(|f| f.path.clone()).collect();\n        assert_eq!(result, expected);\n    }\n\n    #[test]\n    fn test_sort_files_none() {\n        // When sort method is None, the original order should be preserved.\n        let original_paths = vec![\"zeta.txt\", \"alpha.txt\", \"beta.txt\"];\n        let mut files: Vec<FileEntry> = original_paths\n            .iter()\n            .enumerate()\n            .map(|(i, path)| FileEntry {\n                path: path.to_string(),\n                extension: \"txt\".to_string(),\n                code: String::new(),\n                token_count: 0,\n                metadata: EntryMetadata {\n                    is_dir: false,\n                    is_symlink: false,\n                },\n                mod_time: Some((i as u64 + 1) * 100),\n            })\n            .collect();\n\n        // Sorting with None should leave the order unchanged.\n        sort_files(&mut files, None);\n        let result: Vec<String> = files.iter().map(|f| f.path.clone()).collect();\n        assert_eq!(result, original_paths);\n    }\n\n    #[test]\n    fn test_sort_tree_name_asc() {\n        // Build a simple tree with unsorted leaf nodes.\n        let mut tree = Tree::new(\"root\".to_string());\n        tree.leaves.push(Tree::new(\"zeta\".to_string()));\n        tree.leaves.push(Tree::new(\"alpha\".to_string()));\n        tree.leaves.push(Tree::new(\"beta\".to_string()));\n\n        // Sort the tree using NameAsc.\n        sort_tree(&mut tree, Some(FileSortMethod::NameAsc));\n\n        // Extract the sorted names.\n        let sorted: Vec<String> = tree.leaves.iter().map(|node| node.root.clone()).collect();\n        let expected = vec![\"alpha\".to_string(), \"beta\".to_string(), \"zeta\".to_string()];\n        assert_eq!(sorted, expected);\n    }\n\n    #[test]\n    fn test_sort_tree_name_desc() {\n        let mut tree = Tree::new(\"root\".to_string());\n        tree.leaves.push(Tree::new(\"alpha\".to_string()));\n        tree.leaves.push(Tree::new(\"zeta\".to_string()));\n        tree.leaves.push(Tree::new(\"beta\".to_string()));\n\n        // Sort the tree using NameDesc.\n        sort_tree(&mut tree, Some(FileSortMethod::NameDesc));\n\n        let sorted: Vec<String> = tree.leaves.iter().map(|node| node.root.clone()).collect();\n        let expected = vec![\"zeta\".to_string(), \"beta\".to_string(), \"alpha\".to_string()];\n        assert_eq!(sorted, expected);\n    }\n\n    #[test]\n    fn test_sort_tree_date_asc_falls_back_to_name() {\n        // For directory trees, date-based sorting should fall back to name-based sorting.\n        let mut tree = Tree::new(\"root\".to_string());\n        tree.leaves.push(Tree::new(\"delta\".to_string()));\n        tree.leaves.push(Tree::new(\"charlie\".to_string()));\n        tree.leaves.push(Tree::new(\"bravo\".to_string()));\n\n        sort_tree(&mut tree, Some(FileSortMethod::DateAsc));\n\n        let sorted: Vec<String> = tree.leaves.iter().map(|node| node.root.clone()).collect();\n        let expected = vec![\n            \"bravo\".to_string(),\n            \"charlie\".to_string(),\n            \"delta\".to_string(),\n        ];\n        assert_eq!(sorted, expected);\n    }\n\n    #[test]\n    fn test_sort_tree_none() {\n        // If sort_method is None, the tree should remain in its original order.\n        let mut tree = Tree::new(\"root\".to_string());\n        tree.leaves.push(Tree::new(\"zeta\".to_string()));\n        tree.leaves.push(Tree::new(\"alpha\".to_string()));\n        tree.leaves.push(Tree::new(\"beta\".to_string()));\n\n        let original: Vec<String> = tree.leaves.iter().map(|node| node.root.clone()).collect();\n        sort_tree(&mut tree, None);\n        let after: Vec<String> = tree.leaves.iter().map(|node| node.root.clone()).collect();\n\n        assert_eq!(original, after);\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/tests/template_test.rs",
    "content": "use code2prompt_core::template::{extract_undefined_variables, handlebars_setup, render_template};\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use serde_json::json;\n\n    #[test]\n    fn test_handlebars_setup() {\n        let template_str = \"Hello, {{name}}!\";\n        let template_name = \"test_template\";\n\n        // Call the handlebars_setup function\n        let handlebars =\n            handlebars_setup(template_str, template_name).expect(\"Failed to set up Handlebars\");\n\n        // Prepare the data\n        let data = json!({\n            \"name\": \"Bernard\"\n        });\n\n        // Render the template\n        let rendered = render_template(&handlebars, \"test_template\", &data);\n\n        // Assert the result\n        match rendered {\n            Ok(output) => assert_eq!(output, \"Hello, Bernard!\"),\n            Err(e) => panic!(\"Template rendering failed: {}\", e),\n        }\n    }\n\n    #[test]\n    fn test_extract_undefined_variables() {\n        let template_str = \"{{name}} is learning {{language}} and {{framework}}!\";\n        let variables = extract_undefined_variables(template_str);\n        assert_eq!(variables, vec![\"name\", \"language\", \"framework\"]);\n    }\n\n    #[test]\n    fn test_render_template() {\n        let template_str = \"{{greeting}}, {{name}}!\";\n        let template_name = \"test_template\";\n        let handlebars = handlebars_setup(template_str, template_name).unwrap();\n        let data = json!({ \"greeting\": \"Hello\", \"name\": \"Bernard\" });\n        let rendered = render_template(&handlebars, template_name, &data);\n\n        match rendered {\n            Ok(output) => assert_eq!(output, \"Hello, Bernard!\"),\n            Err(e) => panic!(\"Template rendering failed: {}\", e),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-core/tests/util_test.rs",
    "content": "use code2prompt_core::util::strip_utf8_bom;\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_strip_utf8_bom_when_present() {\n        let input = b\"\\xEF\\xBB\\xBFHello, world!\";\n        let expected = b\"Hello, world!\";\n        let output = strip_utf8_bom(input);\n        assert_eq!(\n            output, expected,\n            \"BOM should be stripped from the beginning of the input.\"\n        );\n    }\n\n    #[test]\n    fn test_strip_utf8_bom_when_not_present() {\n        let input = b\"Hello, world!\";\n        let output = strip_utf8_bom(input);\n        assert_eq!(\n            output, input,\n            \"Input without a BOM should remain unchanged.\"\n        );\n    }\n\n    #[test]\n    fn test_strip_utf8_bom_empty_input() {\n        let input = b\"\";\n        let output = strip_utf8_bom(input);\n        assert_eq!(\n            output, input,\n            \"An empty input should return an empty output.\"\n        );\n    }\n\n    #[test]\n    fn test_strip_utf8_bom_only_bom() {\n        let input = b\"\\xEF\\xBB\\xBF\";\n        let expected = b\"\";\n        let output = strip_utf8_bom(input);\n        assert_eq!(\n            output, expected,\n            \"Input that is only a BOM should return an empty slice.\"\n        );\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-python/.python-version",
    "content": "3.13.1\n"
  },
  {
    "path": "crates/code2prompt-python/Cargo.toml",
    "content": "[package]\nname = \"code2prompt-python\"\nversion = \"3.2.0\"\nedition = \"2024\"\n\n[lib]\nname = \"code2prompt_rs\"\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nserde_json = { workspace = true }\ncode2prompt_core = { path = \"../code2prompt-core\" }\npyo3 = { workspace = true }\n"
  },
  {
    "path": "crates/code2prompt-python/pyproject.toml",
    "content": "[project]\nname = \"code2prompt_rs\"\nversion = \"3.2.1\"\ndescription = \"Python bindings for code2prompt\"\nauthors = [\n    { name = \"Olivier D'Ancona\", email = \"olivier_dancona@hotmail.com\" },\n    { name = \"Mufeed VH\", email = \"contact@mufeedvh.com\" },\n]\ndependencies = [\"pip>=25.0.1\", \"patchelf>=0.17.2.1\"]\nrequires-python = \">= 3.11\"\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3 :: Only\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Rust\",\n    \"Topic :: Software Development :: Libraries :: Python Modules\",\n]\n\n[build-system]\nrequires = [\"maturin>=1.0,<2.0\"]\nbuild-backend = \"maturin\"\n\n[tool.maturin]\nbindings = \"pyo3\"\nmodule-name = \"code2prompt_rs\"\nmanifest-path = \"Cargo.toml\"\npython-source = \"python-sdk\"\nfeatures = [\"pyo3/extension-module\"]\n\n[tool.rye]\nmanaged = true\ndev-dependencies = [\"maturin>=1.8.2\", \"pytest>=8.3.5\"]\n\n[tool.rye.scripts]\nbuild = \"maturin develop\"\n\n[tool.hatch.metadata]\nallow-direct-references = true\n\n[tool.hatch.build.targets.wheel]\npackages = [\"src/python_sdk\"]\n\n[project.urls]\nHomepage = \"https://code2prompt.dev\"\nDocumentation = \"https://code2prompt.dev/docs/welcome\"\nRepository = \"https://github.com/mufeedvh/code2prompt\"\n"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/.gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# UV\n#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#uv.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control\n.pdm.toml\n.pdm-python\n.pdm-build/\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n\n# PyPI configuration file\n.pypirc"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/README.md",
    "content": "# code2prompt Python SDK\n\nPython bindings for [code2prompt](https://github.com/mufeedvh/code2prompt) - A tool to generate LLM prompts from codebases.\n\n## Installation\n\n### Local Development Installation\n\n1. Clone the repository:\n\n```bash\ngit clone https://github.com/mufeedvh/code2prompt.git\ncd code2prompt\n```\n\n2. Install development dependencies:\n\n```bash\npython3 -m venv .venv\nsource .venv/bin/activate\npip install maturin pytest\n```\n\n3. Build and install the package locally:\n\n```bash\ncd code2prompt/ # root repo directory\nmaturin develop -r\n```\n\n### Running Examples\n\nTry out the example script:\n\n```bash\npython examples/basic_usage.py\n```\n\n## Usage\n\n```python\nfrom code2prompt import CodePrompt\n\n# Create a new CodePrompt instance\nprompt = CodePrompt(\n    path=\"./my_project\",\n    include_patterns=[\"*.py\", \"*.rs\"],  # Optional: Only include Python and Rust files\n    exclude_patterns=[\"**/tests/*\"],     # Optional: Exclude test files\n    line_numbers=True,                   # Optional: Add line numbers to code\n)\n\n# Generate a prompt\nresult = prompt.generate(\n    template=None,  # Optional: Custom Handlebars template\n    encoding=\"cl100k\"  # Optional: Token encoding (for token counting)\n)\n\n# Access the generated prompt and metadata\nprint(f\"Generated prompt: {result['prompt']}\")\nprint(f\"Token count: {result['token_count']}\")\nprint(f\"Model info: {result['model_info']}\")\n\n# Git operations\ngit_diff = prompt.get_git_diff()\nbranch_diff = prompt.get_git_diff_between_branches(\"main\", \"feature\")\ngit_log = prompt.get_git_log(\"main\", \"feature\")\n```\n\n## API Reference\n\n### `CodePrompt`\n\nMain class for generating prompts from code.\n\n#### Constructor\n\n```python\nCodePrompt(\n    path: str,\n    include_patterns: List[str] = [],\n    exclude_patterns: List[str] = [],\n    include_priority: bool = False,\n    line_numbers: bool = False,\n    relative_paths: bool = False,\n    exclude_from_tree: bool = False,\n    no_codeblock: bool = False,\n    follow_symlinks: bool = False\n)\n```\n\n- `path`: Path to the codebase directory\n- `include_patterns`: List of glob patterns for files to include\n- `exclude_patterns`: List of glob patterns for files to exclude\n- `include_priority`: Give priority to include patterns in case of conflicts\n- `line_numbers`: Add line numbers to code blocks\n- `relative_paths`: Use relative paths instead of absolute\n- `exclude_from_tree`: Exclude files from source tree based on patterns\n- `no_codeblock`: Don't wrap code in markdown code blocks\n- `follow_symlinks`: Follow symbolic links when traversing directories\n\n#### Methods\n\n##### `generate(template: Optional[str] = None, encoding: Optional[str] = None) -> Dict`\n\nGenerate a prompt from the codebase.\n\n- `template`: Optional custom Handlebars template\n- `encoding`: Optional token encoding (cl100k, p50k, p50k_edit, r50k, gpt2)\n\nReturns a dictionary containing:\n\n- `prompt`: The generated prompt\n- `directory`: The processed directory path\n- `token_count`: Number of tokens (if encoding was specified)\n- `model_info`: Information about the model (if encoding was specified)\n\n##### `get_git_diff() -> str`\n\nGet git diff for the repository.\n\n##### `get_git_diff_between_branches(branch1: str, branch2: str) -> str`\n\nGet git diff between two branches.\n\n##### `get_git_log(branch1: str, branch2: str) -> str`\n\nGet git log between two branches.\n\n## License\n\nMIT License - see LICENSE file for details.\n"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/__init__.py",
    "content": "\"\"\"\ncode2prompt is a Python library for generating LLM prompts from codebases.\n\nIt provides a simple interface to the Rust-based code2prompt library, allowing you to:\n- Generate prompts from code directories\n- Filter files using glob patterns\n- Get git diffs and logs\n- Count tokens for different models\n\"\"\"\n\n# Import the Python wrapper class from the renamed file\nfrom .code2prompt_rs import Code2Prompt\n\n__all__ = ['Code2Prompt']"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/code2prompt_rs/__init__.py",
    "content": "\"\"\"\ncode2prompt is a Python library for generating LLM prompts from codebases.\n\nIt provides a simple interface to the Rust-based code2prompt library, allowing you to:\n- Generate prompts from code directories\n- Filter files using glob patterns\n- Get git diffs and logs\n- Count tokens for different models\n\"\"\"\n\n# Import the Python wrapper class from the renamed file\nfrom .code2prompt import Code2Prompt\n\n__all__ = ['Code2Prompt']"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/code2prompt_rs/code2prompt.py",
    "content": "# Import the Rust module\nfrom . import code2prompt_rs as rust_sdk\nfrom pathlib import Path\n\nclass RenderedPrompt:\n    def __init__(self, prompt, token_count, directory, model_info):\n        self.prompt = prompt\n        self.token_count = token_count\n        self.directory = directory\n        self.model_info = model_info\n\nclass Code2Prompt:\n    def __init__(self, path, include_patterns=None, exclude_patterns=None, \n                 include_priority=False, line_numbers=False, absolute_paths=False,\n                 full_directory_tree=False, code_blocks=True, follow_symlinks=False, include_hidden=False):\n        \"\"\"\n        Initialize a Code2Prompt configuration for generating prompts from code.\n        \n        Args:\n            path: Path to the code directory\n            include_patterns: List of glob patterns for files to include\n            exclude_patterns: List of glob patterns for files to exclude\n            include_priority: Whether to prioritize include patterns over exclude\n            line_numbers: Whether to include line numbers in the output\n            absolute_paths: Whether to use absolute paths in the output\n            full_directory_tree: Whether to include the full directory tree\n            code_blocks: Whether to wrap code in markdown code blocks\n            follow_symlinks: Whether to follow symlinks\n            include_hidden: Whether to include hidden files (default is False)\n        \"\"\"\n        # Stocker la configuration\n        self.path = Path(path)\n        self.include_patterns = include_patterns or []\n        self.exclude_patterns = exclude_patterns or []\n        self.include_priority = include_priority\n        self.line_numbers = line_numbers\n        self.absolute_paths = absolute_paths\n        self.full_directory_tree = full_directory_tree\n        self.code_blocks = code_blocks\n        self.follow_symlinks = follow_symlinks\n        self.include_hidden = include_hidden\n        \n        # Initializer une session uniquement quand nécessaire\n        self._session = None\n\n    def session(self) -> rust_sdk.PyCode2PromptSession:\n        \"\"\"\n        Create a PyCode2PromptSession with the current configuration.\n        \"\"\"\n        # Créer la session Rust avec la configuration actuelle\n        session = rust_sdk.PyCode2PromptSession(str(self.path))\n        \n        # Appliquer toutes les configurations\n        if self.include_patterns:\n            session = session.include(self.include_patterns)\n        if self.exclude_patterns:\n            session = session.exclude(self.exclude_patterns)\n            \n        session = session.include_priority(self.include_priority)\n        session = session.with_line_numbers(self.line_numbers)\n        session = session.with_absolute_paths(self.absolute_paths)\n        session = session.with_full_directory_tree(self.full_directory_tree)\n        session = session.with_code_blocks(self.code_blocks)\n        session = session.follow_symlinks(self.follow_symlinks)\n        session = session.include_hidden(self.include_hidden)\n        \n        return session\n    \n    def generate(self, template=None, encoding=None) -> RenderedPrompt:\n        \"\"\"\n        Generate a prompt from the code.\n        \n        Args:\n            template: Optional template string to use\n            encoding: Token encoding to use (e.g., 'cl100k', 'gpt2')\n        \n        Returns:\n            String containing the generated prompt\n        \"\"\"\n        # Apply optional configurations\n        session = self._session or self.session()\n        \n        if encoding:\n            session = session.with_token_encoding(encoding)\n        \n        if template:\n            session = session.with_template(template)\n            \n        # Generate the prompt\n        result = session.generate()\n        \n        # Get token count\n        try:\n            token_count = session.token_count()\n        except Exception:\n            token_count = 0\n            \n        # Return a dictionary with results\n        return RenderedPrompt(\n            prompt=result,\n            token_count=token_count,\n            directory=self.path,\n            model_info=session.info()\n        )\n    \n    def token_count(self, encoding=None):\n        \"\"\"Get token count for the prompt with specified encoding.\"\"\"\n        session = self._session or self.session()\n        if encoding:\n            session = session.with_token_encoding(encoding)\n        return session.token_count()\n    \n    def info(self):\n        \"\"\"Get information about the current session.\"\"\"\n        session = self._session or self.session()\n        return session.info()"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/examples/basic_usage.py",
    "content": "\"\"\"Example usage of the code2prompt Python SDK.\"\"\"\n\nfrom code2prompt_rs import Code2Prompt\n\ndef main():\n    # Create a Code2Prompt instance for the current directory\n    prompt = Code2Prompt(\n        path=\".\",\n        include_patterns=[\"*.py\", \"*.rs\"],  # Only include Python and Rust files\n        exclude_patterns=[\"**/tests/*\"],     # Exclude test files\n        line_numbers=True                    # Add line numbers to code\n    )\n\n    # Generate a prompt with token counting\n    result = prompt.generate(encoding=\"cl100k\")\n    \n    # Print the results\n    print(f\"Generated prompt for directory: {result['directory']}\")\n    print(f\"Token count: {result['token_count']}\")\n    print(f\"Model info: {result['model_info']}\")\n    \n    # Print the first 1000 characters of the prompt, or less if shorter\n    print(\"\\nPrompt preview:\")\n    prompt_text = result['prompt']\n    if prompt_text:\n        preview_length = min(1000, len(prompt_text))\n        print(f\"{prompt_text[:preview_length]}...\")\n    else:\n        print(\"No prompt generated\")\n\n    # Git operations example\n    print(\"\\nGit operations:\")\n    \n    try:\n        # Get current changes\n        diff = prompt.get_git_diff()\n        print(\"\\nCurrent git diff:\")\n        print(diff[:200] + \"...\" if diff else \"No changes\")\n        \n        # Get diff between branches\n        branch_diff = prompt.get_git_diff_between_branches(\"main\", \"develop\")\n        print(\"\\nDiff between main and develop:\")\n        print(branch_diff[:200] + \"...\" if branch_diff else \"No differences\")\n        \n        # Get git log\n        git_log = prompt.get_git_log(\"main\", \"develop\")\n        print(\"\\nGit log between main and develop:\")\n        print(git_log[:200] + \"...\" if git_log else \"No log entries\")\n        \n    except Exception as e:\n        print(f\"Git operations failed: {e}\")\n\nif __name__ == \"__main__\":\n    main()"
  },
  {
    "path": "crates/code2prompt-python/src/lib.rs",
    "content": "mod python;\n"
  },
  {
    "path": "crates/code2prompt-python/src/python.rs",
    "content": "use pyo3::prelude::*;\nuse std::collections::HashMap;\nuse std::path::PathBuf;\n\nuse code2prompt_core::configuration::Code2PromptConfigBuilder;\nuse code2prompt_core::session::Code2PromptSession;\nuse code2prompt_core::sort::FileSortMethod;\nuse code2prompt_core::template::OutputFormat;\nuse code2prompt_core::tokenizer::{TokenFormat, TokenizerType};\n\n#[pyclass]\n#[derive(Clone)]\nstruct PyCode2PromptSession {\n    inner: Code2PromptSession,\n}\n\n#[pymethods]\nimpl PyCode2PromptSession {\n    #[new]\n    fn new(path: &str) -> PyResult<Self> {\n        let config = Code2PromptConfigBuilder::default()\n            .path(PathBuf::from(path))\n            .build()\n            .map_err(|e| {\n                PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(\n                    \"Failed to create config: {}\",\n                    e\n                ))\n            })?;\n\n        Ok(Self {\n            inner: Code2PromptSession::new(config),\n        })\n    }\n\n    // Configure methods that modify the config\n    fn include(&mut self, patterns: Vec<String>) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        config.include_patterns = patterns;\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn exclude(&mut self, patterns: Vec<String>) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        config.exclude_patterns = patterns;\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn with_line_numbers(&mut self, value: bool) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        config.line_numbers = value;\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn with_absolute_paths(&mut self, value: bool) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        config.absolute_path = value;\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn with_full_directory_tree(&mut self, value: bool) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        config.full_directory_tree = value;\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn with_code_blocks(&mut self, value: bool) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        config.no_codeblock = !value; // Invert because API is different\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn follow_symlinks(&mut self, value: bool) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        config.follow_symlinks = value;\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn include_hidden(&mut self, value: bool) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        config.hidden = value;\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn no_ignore(&mut self, value: bool) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        config.no_ignore = value;\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn sort_by(&mut self, method: &str) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        match method.to_lowercase().as_str() {\n            \"name\" | \"name_asc\" => config.sort_method = Some(FileSortMethod::NameAsc),\n            \"name_desc\" => config.sort_method = Some(FileSortMethod::NameDesc),\n            \"date\" | \"date_asc\" => config.sort_method = Some(FileSortMethod::DateAsc),\n            \"date_desc\" => config.sort_method = Some(FileSortMethod::DateDesc),\n            _ => {\n                return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(\n                    \"Invalid sort method: {}. Valid values: name_asc, name_desc, date_asc, date_desc\",\n                    method\n                )));\n            }\n        }\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn output_format(&mut self, format: &str) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        match format.to_lowercase().as_str() {\n            \"markdown\" => config.output_format = OutputFormat::Markdown,\n            // Assuming from the error that there's a Plain variant - please replace if needed\n            \"xml\" | \"text\" => config.output_format = OutputFormat::Xml,\n            \"json\" => config.output_format = OutputFormat::Json,\n            _ => {\n                return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(\n                    \"Invalid output format: {}\",\n                    format\n                )));\n            }\n        }\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn with_token_encoding(&mut self, encoding: &str) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        match encoding.to_lowercase().as_str() {\n            \"cl100k\" => config.encoding = TokenizerType::Cl100kBase,\n            \"o200k\" => config.encoding = TokenizerType::O200kBase,\n            \"p50k\" => config.encoding = TokenizerType::P50kBase,\n            \"p50k_edit\" => config.encoding = TokenizerType::P50kEdit,\n            \"r50k\" => config.encoding = TokenizerType::R50kBase,\n            _ => {\n                return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(\n                    \"Invalid token encoding: {}\",\n                    encoding\n                )));\n            }\n        }\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn with_token_format(&mut self, format: &str) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        match format.to_lowercase().as_str() {\n            \"raw\" => config.token_format = TokenFormat::Raw,\n            \"format\" => config.token_format = TokenFormat::Format,\n            _ => {\n                return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(\n                    \"Invalid token format: {}. Use 'raw' or 'format'.\",\n                    format\n                )));\n            }\n        }\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    #[pyo3(signature = (template, name=None))]\n    fn with_template(&mut self, template: String, name: Option<String>) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        config.template_str = template;\n        if let Some(name_val) = name {\n            config.template_name = name_val;\n        } else {\n            config.template_name = \"custom\".to_string();\n        }\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    #[pyo3(signature = (key, value))]\n    fn with_variable(&mut self, key: String, value: String) -> PyResult<Py<Self>> {\n        let mut config = self.inner.config.clone();\n        config.user_variables.insert(key, value);\n        self.inner = Code2PromptSession::new(config);\n\n        Python::attach(|py| {\n            Ok(Py::new(\n                py,\n                Self {\n                    inner: self.inner.clone(),\n                },\n            )?)\n        })\n    }\n\n    fn generate(&mut self) -> PyResult<String> {\n        match self.inner.generate_prompt() {\n            Ok(rendered) => Ok(rendered.prompt),\n            Err(e) => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(\n                \"Failed to generate prompt: {}\",\n                e\n            ))),\n        }\n    }\n\n    fn info(&self) -> PyResult<HashMap<String, String>> {\n        // Since there's no direct info() method, we'll create a simple info map\n        let mut info = HashMap::new();\n        info.insert(\n            \"path\".to_string(),\n            self.inner.config.path.to_string_lossy().to_string(),\n        );\n        info.insert(\n            \"include_patterns\".to_string(),\n            format!(\"{:?}\", self.inner.config.include_patterns),\n        );\n        info.insert(\n            \"exclude_patterns\".to_string(),\n            format!(\"{:?}\", self.inner.config.exclude_patterns),\n        );\n\n        Ok(info)\n    }\n\n    fn token_count(&self) -> PyResult<usize> {\n        // Generate the prompt and count tokens\n        match self.inner.clone().generate_prompt() {\n            Ok(rendered) => Ok(rendered.token_count),\n            Err(e) => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(\n                \"Failed to count tokens: {}\",\n                e\n            ))),\n        }\n    }\n}\n\n// Module definition - Updated PyO3 syntax\n#[pymodule(name = \"code2prompt_rs\")]\nfn code2prompt_rs(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {\n    m.add_class::<PyCode2PromptSession>()?;\n    Ok(())\n}\n"
  },
  {
    "path": "crates/code2prompt-python/src/python.rs.bak",
    "content": "use pyo3::prelude::*;\nuse pyo3::types::PyDict;\nuse std::path::PathBuf;\n\nuse code2prompt_core::{\n    git::{get_git_diff, get_git_diff_between_branches, get_git_log},\n    path::traverse_directory,\n    template::{handlebars_setup, render_template},\n    tokenizer::{count_tokens, TokenizerType},\n};\n\n/// Python module for code2prompt\n#[pymodule(name = \"code2prompt_rs\")]\nfn code2prompt_rs(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {\n    m.add_class::<Code2Prompt>()?;\n    Ok(())\n}\n\n/// Main class for generating prompts from code\n#[pyclass]\nstruct Code2Prompt {\n    path: PathBuf,\n    include_patterns: Vec<String>,\n    exclude_patterns: Vec<String>,\n    include_priority: bool,\n    line_numbers: bool,\n    relative_paths: bool,\n    exclude_from_tree: bool,\n    no_codeblock: bool,\n    follow_symlinks: bool,\n    hidden: bool,\n    no_ignore: bool,\n}\n\n#[pymethods]\nimpl Code2Prompt {\n    /// Create a new Code2Prompt instance\n    ///\n    /// Args:\n    ///     path (str): Path to the codebase directory\n    ///     include_patterns (List[str], optional): Patterns to include. Defaults to [].\n    ///     exclude_patterns (List[str], optional): Patterns to exclude. Defaults to [].\n    ///     include_priority (bool, optional): Give priority to include patterns. Defaults to False.\n    ///     line_numbers (bool, optional): Add line numbers to code. Defaults to False.\n    ///     relative_paths (bool, optional): Use relative paths. Defaults to False.\n    ///     exclude_from_tree (bool, optional): Exclude files from tree based on patterns. Defaults to False.\n    ///     no_codeblock (bool, optional): Don't wrap code in markdown blocks. Defaults to False.\n    ///     follow_symlinks (bool, optional): Follow symbolic links. Defaults to False.\n    ///     hidden (bool, optional): Include hidden directories and files. Defaults to False.\n    ///     no_ignore (bool, optional): Skip .gitignore rules. Defaults to False.\n    #[new]\n    #[pyo3(signature = (\n        path,\n        include_patterns = vec![],\n        exclude_patterns = vec![],\n        include_priority = false,\n        line_numbers = false,\n        relative_paths = false,\n        exclude_from_tree = false,\n        no_codeblock = false,\n        follow_symlinks = false,\n        hidden = false,\n        no_ignore = false,\n    ))]\n    fn new(\n        path: String,\n        include_patterns: Vec<String>,\n        exclude_patterns: Vec<String>,\n        include_priority: bool,\n        line_numbers: bool,\n        relative_paths: bool,\n        exclude_from_tree: bool,\n        no_codeblock: bool,\n        follow_symlinks: bool,\n        hidden: bool,\n        no_ignore: bool,\n    ) -> Self {\n        Self {\n            path: PathBuf::from(path),\n            include_patterns,\n            exclude_patterns,\n            include_priority,\n            line_numbers,\n            relative_paths,\n            exclude_from_tree,\n            no_codeblock,\n            follow_symlinks,\n            hidden,\n            no_ignore,\n        }\n    }\n\n    /// Generate a prompt from the codebase\n    ///\n    /// Args:\n    ///     template (str, optional): Custom Handlebars template. Defaults to None.\n    ///     encoding (str, optional): Token encoding to use. Defaults to \"cl100k\".\n    ///\n    /// Returns:\n    ///     dict: Dictionary containing the rendered prompt and metadata\n    #[pyo3(signature = (template=None, encoding=None))]\n    fn generate(&self, template: Option<String>, encoding: Option<String>) -> PyResult<PyObject> {\n        Python::with_gil(|py| {\n            // Traverse directory\n            let (tree, files) = traverse_directory(\n                &self.path,\n                &self.include_patterns,\n                &self.exclude_patterns,\n                self.include_priority,\n                self.line_numbers,\n                self.relative_paths,\n                self.exclude_from_tree,\n                self.no_codeblock,\n                self.follow_symlinks,\n                self.hidden,\n                self.no_ignore,\n                None,\n            )\n            .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;\n\n            // Setup template\n            let template_content = template\n                .unwrap_or_else(|| include_str!(\"../../default_template_md.hbs\").to_string());\n            let handlebars = handlebars_setup(&template_content, \"template\")\n                .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;\n\n            // Prepare data\n            let data = serde_json::json!({\n                \"absolute_code_path\": self.path.display().to_string(),\n                \"source_tree\": tree,\n                \"files\": files,\n            });\n\n            // Render template\n            let rendered = render_template(&handlebars, \"template\", &data)\n                .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;\n\n            // Select tokenizer type\n            let tokenizer_type = encoding\n                .as_deref()\n                .unwrap_or(\"cl100k\")\n                .parse::<TokenizerType>()\n                .unwrap_or(TokenizerType::Cl100kBase); // Fallback to `cl100k`\n\n            let model_info = tokenizer_type.description();\n\n            // Count tokens\n            let token_count = count_tokens(&rendered, &tokenizer_type);\n\n            // Create return dictionary\n            let result = PyDict::new(py);\n            result.set_item(\"prompt\", rendered)?;\n            result.set_item(\"directory\", self.path.display().to_string())?;\n            result.set_item(\"token_count\", token_count)?;\n            result.set_item(\"model_info\", model_info)?;\n\n            Ok(result.into())\n        })\n    }\n\n    /// Get git diff for the repository\n    ///\n    /// Returns:\n    ///     str: Git diff output\n    fn get_git_diff(&self) -> PyResult<String> {\n        get_git_diff(&self.path)\n            .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))\n    }\n\n    /// Get git diff between two branches\n    ///\n    /// Args:\n    ///     branch1 (str): First branch name\n    ///     branch2 (str): Second branch name\n    ///\n    /// Returns:\n    ///     str: Git diff output\n    fn get_git_diff_between_branches(&self, branch1: &str, branch2: &str) -> PyResult<String> {\n        get_git_diff_between_branches(&self.path, branch1, branch2)\n            .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))\n    }\n\n    /// Get git log between two branches\n    ///\n    /// Args:\n    ///     branch1 (str): First branch name\n    ///     branch2 (str): Second branch name\n    ///\n    /// Returns:\n    ///     str: Git log output\n    fn get_git_log(&self, branch1: &str, branch2: &str) -> PyResult<String> {\n        get_git_log(&self.path, branch1, branch2)\n            .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))\n    }\n}\n"
  },
  {
    "path": "crates/code2prompt-python/tests/__init__.py",
    "content": ""
  },
  {
    "path": "crates/code2prompt-python/tests/conftest.py",
    "content": "\"\"\"Pytest fixtures for code2prompt tests.\"\"\"\nimport os\nimport pytest\nimport tempfile\nimport shutil\nfrom pathlib import Path\n\n@pytest.fixture(scope=\"module\")\ndef test_hierarchy():\n    \"\"\"Create a test hierarchy of files and directories.\"\"\"\n    # Create a temporary directory\n    temp_dir = tempfile.mkdtemp()\n    \n    try:\n        # Create directories\n        lowercase_dir = Path(temp_dir) / \"lowercase\"\n        uppercase_dir = Path(temp_dir) / \"uppercase\"\n        secret_dir = Path(temp_dir) / \".secret\"\n        \n        for dir_path in [lowercase_dir, uppercase_dir, secret_dir]:\n            dir_path.mkdir(parents=True, exist_ok=True)\n        \n        # Create files\n        files = [\n            (\"lowercase/foo.py\", \"def foo():\\n    return 'foo'\\n\"),\n            (\"lowercase/bar.py\", \"def bar():\\n    return 'bar'\\n\"),\n            (\"lowercase/baz.py\", \"def baz():\\n    return 'baz'\\n\"),\n            (\"lowercase/qux.txt\", \"content qux.txt\"),\n            (\"lowercase/corge.txt\", \"content corge.txt\"),\n            (\"lowercase/grault.txt\", \"content grault.txt\"),\n            (\"uppercase/FOO.py\", \"def FOO():\\n    return 'FOO'\\n\"),\n            (\"uppercase/BAR.py\", \"def BAR():\\n    return 'BAR'\\n\"),\n            (\"uppercase/BAZ.py\", \"def BAZ():\\n    return 'BAZ'\\n\"),\n            (\"uppercase/QUX.txt\", \"CONTENT QUX.TXT\"),\n            (\"uppercase/CORGE.txt\", \"CONTENT CORGE.TXT\"),\n            (\"uppercase/GRAULT.txt\", \"CONTENT GRAULT.TXT\"),\n            (\".secret/secret.txt\", \"SECRET\"),\n        ]\n        \n        for file_path, content in files:\n            full_path = Path(temp_dir) / file_path\n            full_path.write_text(content)\n            \n        # Create a gitignore file\n        gitignore_path = Path(temp_dir) / \".gitignore\"\n        gitignore_path.write_text(\"*.txt\\n\")\n            \n        # Return the path\n        yield temp_dir\n    finally:\n        # Clean up\n        shutil.rmtree(temp_dir)\n\n@pytest.fixture\ndef test_dir(test_hierarchy):\n    \"\"\"Return the path to the test hierarchy.\"\"\"\n    return test_hierarchy"
  },
  {
    "path": "crates/code2prompt-python/tests/test_config.py",
    "content": "\"\"\"Tests for Code2Prompt configuration.\"\"\"\nimport pytest\nfrom pathlib import Path\nfrom code2prompt_rs import Code2Prompt\n\ndef test_basic_initialization(test_dir):\n    \"\"\"Test that Code2Prompt can be initialized with minimal settings.\"\"\"\n    prompt = Code2Prompt(path=test_dir)\n    assert prompt is not None\n    assert str(prompt.path) == test_dir\n    assert prompt.include_patterns == []\n    assert prompt.exclude_patterns == []\n\ndef test_initialization_with_options(test_dir):\n    \"\"\"Test initialization with various options.\"\"\"\n    prompt = Code2Prompt(\n        path=test_dir,\n        include_patterns=[\"*.py\"],\n        exclude_patterns=[\"**/uppercase/*\"],\n        include_priority=True,\n        line_numbers=True,\n        absolute_paths=True,\n        full_directory_tree=True,\n        code_blocks=False,\n        follow_symlinks=True\n    )\n    \n    assert prompt.include_patterns == [\"*.py\"]\n    assert prompt.exclude_patterns == [\"**/uppercase/*\"]\n    assert prompt.include_priority is True\n    assert prompt.line_numbers is True\n    assert prompt.absolute_paths is True\n    assert prompt.full_directory_tree is True\n    assert prompt.code_blocks is False\n    assert prompt.follow_symlinks is True\n\ndef test_session_creation(test_dir):\n    \"\"\"Test that a session can be created.\"\"\"\n    prompt = Code2Prompt(path=test_dir)\n    session = prompt.session()\n    assert session is not None\n    \n    # Verify that the session contains expected info\n    info = session.info()\n    assert \"path\" in info\n    assert Path(info[\"path\"]) == Path(test_dir)\n\ndef test_configuration_chain(test_dir):\n    \"\"\"Test using session for complex configuration.\"\"\"\n    prompt = Code2Prompt(path=test_dir)\n    session = prompt.session()\n    \n    # Apply multiple configurations (using the original session would\n    # involve setting up method calls to return 'self')\n    session = session.include([\"*.py\"])\n    session = session.exclude([\"**/uppercase/*\"])\n    session = session.with_line_numbers(True)\n    \n    # Verify configuration was applied\n    info = session.info()\n    assert info[\"include_patterns\"] != \"[]\""
  },
  {
    "path": "crates/code2prompt-python/tests/test_generation.py",
    "content": "\"\"\"Tests for prompt generation.\"\"\"\nimport pytest\nfrom code2prompt_rs import Code2Prompt\n\ndef test_generate_basic(test_dir):\n    \"\"\"Test basic prompt generation.\"\"\"\n    prompt = Code2Prompt(path=test_dir)\n    result = prompt.generate()\n    \n    # Basic checks\n    assert result.prompt is not None\n    assert isinstance(result.prompt, str)\n    assert result.token_count >= 0\n    assert str(result.directory) == test_dir\n\ndef test_generate_with_include_patterns(test_dir):\n    \"\"\"Test generation with include patterns.\"\"\"\n    prompt = Code2Prompt(\n        path=test_dir,\n        include_patterns=[\"*.py\"]\n    )\n    result = prompt.generate()\n    \n    # Check that Python files are included\n    assert \"foo.py\" in result.prompt\n    assert \"bar.py\" in result.prompt\n    \n    # Check that text files are excluded\n    assert \"qux.txt\" not in result.prompt\n    assert \"corge.txt\" not in result.prompt\n\ndef test_generate_with_exclude_patterns(test_dir):\n    \"\"\"Test generation with exclude patterns.\"\"\"\n    prompt = Code2Prompt(\n        path=test_dir,\n        exclude_patterns=[\"**/uppercase/*\"]\n    )\n    result = prompt.generate()\n    \n    # Check that uppercase directory files are excluded\n    assert \"FOO.py\" not in result.prompt\n    assert \"BAR.py\" not in result.prompt\n    \n    # Check that lowercase directory files are included\n    assert \"foo.py\" in result.prompt or \"lowercase/foo.py\" in result.prompt\n\ndef test_generate_with_line_numbers(test_dir):\n    \"\"\"Test generation with line numbers.\"\"\"\n    prompt = Code2Prompt(\n        path=test_dir,\n        include_patterns=[\"lowercase/foo.py\"],\n        line_numbers=True\n    )\n    result = prompt.generate()\n    \n    # Check for line numbers in output (either format 1: or 1.|)\n    assert \"1:\" in result.prompt or \"1 |\" in result.prompt\n\ndef test_generate_with_relative_and_absolute_paths(test_dir):\n    \"\"\"Test generation with absolute paths.\"\"\"\n    prompt_absolute = Code2Prompt(\n        path=test_dir,\n        include_patterns=[\"lowercase/foo.py\"],\n        absolute_paths=True\n    )\n    result = prompt_absolute.generate()\n    \n    # Should include absolute path format\n    assert test_dir in result.prompt\n    \n    # Should include absolute path\n    assert \"lowercase/foo.py\" in result.prompt\n\n    prompt_relative = Code2Prompt(\n        path=test_dir,\n        include_patterns=[\"lowercase/foo.py\"],\n        absolute_paths=False\n    )\n    result = prompt_relative.generate()\n    \n    # Should not include absolute path format\n    assert test_dir not in result.prompt\n    \n    # Should include absolute path\n    assert \"lowercase/foo.py\" in result.prompt\n\ndef test_generate_with_custom_template(test_dir):\n    \"\"\"Test generation with custom template.\"\"\"\n    template = \"\"\"# Code Overview\n    {% for file in files %}\n    ## {{ file.path }}\n    ```{{ file.language }}\n    {{ file.content }}\" \\\n    \"{% endfor %}\"\"\"\n\n    prompt = Code2Prompt(\n        path=test_dir,\n        include_patterns=[\"lowercase/foo.py\"]\n    )\n    result = prompt.generate(template=template)\n\n    # Check that custom template was used\n    assert \"# Code Overview\" in result.prompt\n    assert \"## \" in result.prompt\n\n\ndef test_token_count(test_dir):\n    \"\"\"Test token counting.\"\"\"\n    prompt = Code2Prompt(path=test_dir)\n\n    # Get token count directly\n    token_count = prompt.token_count(encoding=\"cl100k\")\n    assert isinstance(token_count, int)\n    assert token_count > 0\n\n    # Compare with generated result\n    result = prompt.generate(encoding=\"cl100k\")\n    assert result.token_count == token_count\n\ndef test_multiple_encoding_options(test_dir):\n    \"\"\"Test with different encoding options.\"\"\"\n    prompt = Code2Prompt(\n    path=test_dir,\n    include_patterns=[\"lowercase/foo.py\"]\n    )\n\n    # Try different encodings\n    encodings = [\"cl100k\", \"gpt2\", \"p50k_base\"]\n    token_counts = {}\n\n    for encoding in encodings:\n        try:\n            count = prompt.token_count(encoding=encoding)\n            token_counts[encoding] = count\n        except Exception as e:\n            # Some encodings might not be available, that's OK\n            print(f\"Encoding {encoding} failed: {e}\")\n\n    # At least one encoding should work\n    assert len(token_counts) > 0\n\n    # Different encodings might give different counts\n    # (but for very small files they might be the same)\n    if len(token_counts) > 1:\n        unique_counts = set(token_counts.values())\n        print(f\"Token counts: {token_counts}\")"
  },
  {
    "path": "crates/code2prompt-python/tests/test_special_feature.py",
    "content": "## test_special_features.py - Tests pour fonctionnalités spéciales\n\n\"\"\"Tests for special features of Code2Prompt.\"\"\"\nimport pytest\nimport os\nfrom pathlib import Path\nfrom code2prompt_rs import Code2Prompt\n\ndef test_hidden_files(test_dir):\n    \"\"\"Test handling of hidden files.\"\"\"\n    # First, with hidden files excluded (default)\n    prompt = Code2Prompt(path=test_dir)\n    result = prompt.generate()\n    \n    # The .secret directory should be excluded\n    assert \"secret.txt\" not in result.prompt\n    \n    # Now, include hidden files\n    prompt = Code2Prompt(\n        path=test_dir,\n        include_hidden=True\n    )\n    result = prompt.generate()\n    \n    # Should include .secret directory now\n    assert \"secret.txt\" in result.prompt or \".secret/secret.txt\" in result.prompt\n\ndef test_directory_tree(test_dir):\n    \"\"\"Test full directory tree generation.\"\"\"\n    prompt = Code2Prompt(\n        path=test_dir,\n        full_directory_tree=True\n    )\n    result = prompt.generate()\n    \n    # Should include directory structure\n    assert \"lowercase\" in result.prompt\n    assert \"uppercase\" in result.prompt\n\ndef test_no_code_blocks(test_dir):\n    \"\"\"Test generation without code blocks.\"\"\"\n    # With code blocks (default)\n    prompt = Code2Prompt(\n        path=test_dir,\n        include_patterns=[\"lowercase/foo.py\"]\n    )\n    with_blocks = prompt.generate()\n    \n    # Without code blocks\n    prompt = Code2Prompt(\n        path=test_dir,\n        include_patterns=[\"lowercase/foo.py\"],\n        code_blocks=False\n    )\n    without_blocks = prompt.generate()\n    \n    # Code blocks typically include ```python or ```py\n    assert \"```py\" in with_blocks.prompt\n    assert \"```py\" not in without_blocks.prompt\n\ndef test_sort_files(test_dir):\n    \"\"\"Test different sorting methods if available.\"\"\"\n    # This test depends on if sort_by is exposed in your API\n    try:\n        # Default should be name ascending\n        prompt = Code2Prompt(path=test_dir)\n        session = prompt.session()\n        \n        # Try to sort by name_desc if method exists\n        if hasattr(session, \"sort_by\"):\n            session = session.sort_by(\"name_desc\")\n            result = session.generate()\n            # Hard to verify sort in output, but should not error\n            assert result is not None\n    except AttributeError:\n        # If sort_by isn't implemented, just pass the test\n        pass"
  },
  {
    "path": "llms-install.md",
    "content": "# Code2Prompt MCP Server Installation Guide\n\nThis 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.\n\n## Overview of code2prompt-mcp\n\nAn MCP server that generates contextual prompts from codebases, making it easier for AI assistants to understand and work with your code repositories.\n\ncode2prompt-mcp leverages the high-performance [code2prompt-rs](https://github.com/yourusername/code2prompt-rs) Rust library to analyze codebases and produce structured summaries. It helps bridge the gap between your code and language models by extracting relevant context in a format that's optimized for AI consumption.\n\n## Prerequisites\n\nBefore installation, you need:\n\n1. Install rye for dependency management. `curl -sSf https://rye.astral.sh/get | bash` on linux or macOS. Make sure to select to add rye to your PATH when prompted.\n\n\n## Installation and Configuration\n\nClone the repository and install dependencies:\n\n```bash\ngit clone https://github.com/odancona/code2prompt-mcp.git\ncd code2prompt-mcp\n```\n\nInstall all the required dependencies specified in the `pyproject.toml` file in the `.venv` directory with :\n\n```bash\nrye build\n```\n\nThis will create a virtual environment and install all necessary packages.\n\nThen, configure the MCP server configuration file. To run the environnment, you have several options. The first one would be to activate the virtual environment and run the server:\n\n```bash\ncd <installation_directory>\nsource .venv/bin/activate\npython code2prompt_mcp.main\n```\n\nAlternatively, you can run the server directly using rye:\n\n```bash\nrye run python code2prompt_mcp.main\n```\n\nIt's important to run this command in the cloned directory to use `pyproject.toml` and the virtual environment created by rye.\n\nIf you want to be able to run the MCP server from anywhere, you can create a configuration file for your LLM application. Here's an example configuration:\n\n```json\n{\n  \"mcpServers\": {\n    \"code2prompt\": {\n      \"command\": \"bash\",\n      \"args\": [\n        \"-c\",\n        \"cd /path/to/code2prompt-mcp && rye run python /path/to/code2prompt-mcp/src/code2prompt_mcp/main.py\"\n      ],\n      \"env\": {}\n    }\n  }\n}\n```\n\n## Verify Installation\n\nTo verify the installation is working:\n\n1. Restart your LLM application (Cline, Claude Desktop, etc.)\n2. Test the connection by running a simple command like:\n   ```\n   Please get context from /path/to/project for AI analysis using Code2Prompt.\n   ```\n\n\n## Usage Examples\n\nHere are some examples of how to use Code2Prompt MCP server with AI assistants:\n\n### Local Codebase Analysis\n\n```\nCan you analyze the code in my project at /path/to/project? Please use Code2prompt MCP to get the context.\n```\n\n\n### Specific File Types Analysis\n\n```\nPlease get all python files and remove markdown files and the folder tests, use Code2prompt MCP for context.\n```\n"
  },
  {
    "path": "website/.gitignore",
    "content": "# build output\ndist/\n# generated types\n.astro/\n\n# dependencies\nnode_modules/\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n\n# environment variables\n.env\n.env.production\n\n# macOS-specific files\n.DS_Store\n\n.yarn\n\n.yarnrc.yml"
  },
  {
    "path": "website/.vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\"astro-build.astro-vscode\"],\n  \"unwantedRecommendations\": []\n}\n"
  },
  {
    "path": "website/.vscode/launch.json",
    "content": "{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"command\": \"./node_modules/.bin/astro dev\",\n      \"name\": \"Development server\",\n      \"request\": \"launch\",\n      \"type\": \"node-terminal\"\n    }\n  ]\n}\n"
  },
  {
    "path": "website/README.md",
    "content": "# Starlight Starter Kit: Basics\n\n[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build)\n\n```\nyarn create astro@latest -- --template starlight\n```\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/starlight/tree/main/examples/basics)\n[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/starlight/tree/main/examples/basics)\n[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/withastro/starlight&create_from_path=examples/basics)\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fwithastro%2Fstarlight%2Ftree%2Fmain%2Fexamples%2Fbasics&project-name=my-starlight-docs&repository-name=my-starlight-docs)\n\n> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!\n\n## 🚀 Project Structure\n\nInside of your Astro + Starlight project, you'll see the following folders and files:\n\n```\n.\n├── public/\n├── src/\n│   ├── assets/\n│   ├── content/\n│   │   ├── docs/\n│   └── content.config.ts\n├── astro.config.mjs\n├── package.json\n└── tsconfig.json\n```\n\nStarlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name.\n\nImages can be added to `src/assets/` and embedded in Markdown with a relative link.\n\nStatic assets, like favicons, can be placed in the `public/` directory.\n\n## 🧞 Commands\n\nAll commands are run from the root of the project, from a terminal:\n\n| Command                   | Action                                           |\n| :------------------------ | :----------------------------------------------- |\n| `yarn install`             | Installs dependencies                            |\n| `yarn dev`             | Starts local dev server at `localhost:4321`      |\n| `yarn build`           | Build your production site to `./dist/`          |\n| `yarn preview`         | Preview your build locally, before deploying     |\n| `yarn astro ...`       | Run CLI commands like `astro add`, `astro check` |\n| `yarn astro -- --help` | Get help using the Astro CLI                     |\n\n## 👀 Want to learn more?\n\nCheck out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat).\n"
  },
  {
    "path": "website/astro.config.mjs",
    "content": "// @ts-check\nimport { defineConfig } from \"astro/config\";\nimport starlight from \"@astrojs/starlight\";\nimport react from \"@astrojs/react\";\nimport remarkMath from \"remark-math\";\nimport rehypeMathjax from \"rehype-mathjax\";\nimport mdx from \"@astrojs/mdx\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport sitemap from \"@astrojs/sitemap\";\nimport starlightBlog from \"starlight-blog\";\n\nimport { passthroughImageService } from \"astro/config\";\n\n// https://astro.build/config\nexport default defineConfig({\n  site: \"https://code2prompt.dev\",\n  redirects: {\n    \"/fr\": \"/\",\n    \"/de\": \"/\",\n    \"/es\": \"/\",\n    \"/zh\": \"/\",\n    \"/ja\": \"/\",\n    \"/ru\": \"/\",\n  },\n  integrations: [\n    starlight({\n      title: \"Code2prompt\",\n      logo: {\n        light: \"./src/assets/logo_dark_v0.0.1.svg\",\n        dark: \"./src/assets/logo_light_v0.0.1.svg\",\n      },\n      defaultLocale: \"root\",\n      locales: {\n        // English docs in `src/content/docs/`\n        root: {\n          label: \"English\",\n          lang: \"en\",\n        },\n        // French docs in `src/content/docs/fr/docs/`\n        fr: {\n          label: \"Français\",\n          lang: \"fr\",\n        },\n        // German docs in `src/content/docs/de/docs/`\n        de: {\n          label: \"Deutsch\",\n          lang: \"de\",\n        },\n        // Spanish docs in `src/content/docs/es/docs/`\n        es: {\n          label: \"Español\",\n          lang: \"es\",\n        },\n        // Chinese docs in `src/content/docs/zh/docs/`\n        zh: {\n          label: \"中文\",\n          lang: \"zh\",\n        },\n        // Japanese docs in `src/content/docs/ja/docs/`\n        ja: {\n          label: \"日本語\",\n          lang: \"ja\",\n        },\n        // Russian docs in `src/content/docs/ru/docs/`\n        ru: {\n          label: \"Русский\",\n          lang: \"ru\",\n        },\n      },\n      social: [\n        {\n          icon: \"discord\",\n          label: \"Discord\",\n          href: \"https://discord.gg/ZZyBbsHTwH\",\n        },\n        {\n          icon: \"github\",\n          label: \"GitHub\",\n          href: \"https://github.com/mufeedvh/code2prompt\",\n        },\n      ],\n      sidebar: [\n        {\n          label: \"Documentation 🚀 \",\n          items: [\n            {\n              label: \"Tutorials\",\n              items: [\n                {\n                  label: \"Getting Started\",\n                  link: \"docs/tutorials/getting_started\",\n                },\n                {\n                  label: \"Learn Templating\",\n                  link: \"docs/tutorials/learn_templates\",\n                },\n                {\n                  label: \"Learn Filtering\",\n                  link: \"docs/tutorials/learn_filters\",\n                },\n                { \n                  label: \"Learn Configuration\", \n                  link: \"docs/tutorials/configuration\"\n                },\n              ],\n            },\n            {\n              label: \"Explanations\",\n              items: [\n                {\n                  label: \"What are Glob Patterns?\",\n                  link: \"docs/explanations/glob_patterns\",\n                },\n                {\n                  label: \"How the Glob Pattern Filter Works\",\n                  link: \"docs/explanations/glob_pattern_filter\",\n                },\n                {\n                  label: \"Understanding Tokenizers\",\n                  link: \"docs/explanations/tokenizers\",\n                },\n              ],\n            },\n            {\n              label: \"How-To Guides\",\n              items: [\n                { label: \"Install Code2Prompt\", link: \"docs/how_to/install\" },\n                { label: \"Filter Files\", link: \"docs/how_to/filter_files\" },\n              ],\n            },\n          ],\n        },\n        { label: \"Welcome 👋\", link: \"docs/welcome\" },\n        {\n          label: \"Vision 🔮\",\n          link: \"docs/vision\",\n        },\n      ],\n      plugins: [\n        starlightBlog({\n          authors: {\n            ODAncona: {\n              name: \"Olivier D'Ancona\",\n              title: \"Data Scientist\",\n              picture: \"assets/images/odancona.png\",\n              url: \"https://www.linkedin.com/in/odancona/\",\n            },\n          },\n        }),\n      ],\n    }),\n    react(),\n    mdx(),\n    sitemap(),\n  ],\n\n  markdown: {\n    remarkPlugins: [remarkMath],\n    rehypePlugins: [rehypeMathjax],\n  },\n\n  vite: {\n    plugins: [tailwindcss()],\n  },\n  image: {\n    service: passthroughImageService(),\n  },\n});\n"
  },
  {
    "path": "website/package.json",
    "content": "{\n  \"name\": \"code2prompt-website\",\n  \"type\": \"module\",\n  \"version\": \"0.1.0\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"dev\": \"astro dev\",\n    \"start\": \"astro dev\",\n    \"build\": \"astro build\",\n    \"preview\": \"astro preview\",\n    \"astro\": \"astro\"\n  },\n  \"dependencies\": {\n    \"@astrojs/markdoc\": \"0.15.10\",\n    \"@astrojs/mdx\": \"4.3.13\",\n    \"@astrojs/partytown\": \"^2.1.4\",\n    \"@astrojs/prism\": \"3.3.0\",\n    \"@astrojs/react\": \"4.4.2\",\n    \"@astrojs/sitemap\": \"3.6.0\",\n    \"@astrojs/starlight\": \"^0.37.1\",\n    \"@astrojs/upgrade\": \"^0.6.2\",\n    \"@tailwindcss/vite\": \"^4.1.18\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"astro\": \"5.16.5\",\n    \"marked\": \"^17.0.1\",\n    \"prismjs\": \"^1.30.0\",\n    \"rehype-mathjax\": \"^7.1.0\",\n    \"remark-math\": \"^6.0.0\",\n    \"sharp\": \"^0.34.5\",\n    \"starlight-blog\": \"^0.25.2\",\n    \"tailwindcss\": \"^4.1.18\"\n  },\n  \"devDependencies\": {\n    \"dlx\": \"^0.2.1\",\n    \"prettier-plugin-astro\": \"^0.14.1\",\n    \"react\": \"^19.2.3\",\n    \"react-dom\": \"^19.2.3\"\n  },\n  \"packageManager\": \"pnpm@10.18.0\"\n}\n"
  },
  {
    "path": "website/pnpm-workspace.yaml",
    "content": "onlyBuiltDependencies:\n  - esbuild\n  - sharp\n"
  },
  {
    "path": "website/public/CNAME",
    "content": "code2prompt.dev"
  },
  {
    "path": "website/public/assets/css/marquee.css",
    "content": ".scroller__inner {\n  padding-block: 1rem;\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1rem;\n}\n\n.scroller[data-animated=\"true\"] {\n  overflow: hidden;\n  -webkit-mask: linear-gradient(\n    90deg,\n    transparent,\n    white 20%,\n    white 80%,\n    transparent\n  );\n  mask: linear-gradient(90deg, transparent, white 20%, white 80%, transparent);\n}\n\n.scroller[data-animated=\"true\"] .scroller__inner {\n  width: max-content;\n  flex-wrap: nowrap;\n  animation: scroll var(--_animation-duration, 40s)\n    var(--_animation-direction, forwards) linear infinite;\n}\n\n.scroller[data-direction=\"right\"] {\n  --_animation-direction: reverse;\n}\n\n.scroller[data-direction=\"left\"] {\n  --_animation-direction: forwards;\n}\n\n.scroller[data-speed=\"medium\"] {\n  --_animation-duration: 20s;\n}\n\n.scroller[data-speed=\"fast\"] {\n  --_animation-duration: 10s;\n}\n\n.scroller[data-speed=\"slow\"] {\n  --_animation-duration: 60s;\n}\n\n@keyframes scroll {\n  to {\n    transform: translate(calc(-50% - 0.5rem));\n  }\n}\n\n/* general styles */\n\n:root {\n  --clr-neutral-100: hsl(0, 0%, 100%);\n  --clr-primary-100: hsl(205, 15%, 58%);\n  --clr-primary-400: hsl(215, 25%, 27%);\n  --clr-primary-800: hsl(217, 33%, 17%);\n  --clr-primary-900: hsl(218, 33%, 9%);\n}\n\nhtml {\n  color-scheme: dark;\n}\n\nbody {\n  display: grid;\n  min-block-size: 100vh;\n  place-content: center;\n  font-family: system-ui;\n  font-size: 1.125rem;\n  background-color: var(--clr-primary-800);\n}\n\n.tag-list {\n  margin: 0;\n  padding-inline: 0;\n  list-style: none;\n  color: white;\n}\n\n.tag-list li {\n  padding: 1rem;\n  background: var(--clr-primary-400);\n  border-radius: 0.5rem;\n  box-shadow: 0 0.5rem 1rem -0.25rem var(--clr-primary-900);\n}\n\n/* for testing purposed to ensure the animation lined up correctly */\n.test {\n  background: red !important;\n}\n\n.custom-flex-col {\n  display: flex;\n  flex-direction: column;\n}\n\n.img-box {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n\n.custom-flex-col > * {\n  margin: 0;\n}\n\n.content-evenly {\n  justify-content: space-evenly;\n}\n\n.m0 {\n  margin: 0 !important;\n}\n\n.ml0 {\n  margin-left: 0 !important;\n}\n.mr0 {\n  margin-right: 0 !important;\n}\n\n.pl0 {\n  padding-left: 0 !important;\n}\n\n.pr0 {\n  padding-right: 0 !important;\n}\n\n.cust-h {\n  height: 240px;\n}\n\n.img-scroll {\n  max-height: 90px;\n  max-width: 150px;\n}\n"
  },
  {
    "path": "website/public/assets/js/main.js",
    "content": "function copyToClipboard() {\r\n  const codeBlock = document.getElementById(\"code-block\").innerText;\r\n  navigator.clipboard.writeText(codeBlock);\r\n}\r\n\r\n(function ($) {\r\n  // Scrolly.\r\n  $(\".scrolly\").scrolly();\r\n\r\n  const scrollers = document.querySelectorAll(\".scroller\");\r\n\r\n  // If a user hasn't opted in for recuded motion, then we add the animation\r\n  if (!window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\r\n    addAnimation();\r\n  }\r\n\r\n  function addAnimation() {\r\n    scrollers.forEach((scroller) => {\r\n      // add data-animated=\"true\" to every `.scroller` on the page\r\n      scroller.setAttribute(\"data-animated\", true);\r\n\r\n      // Make an array from the elements within `.scroller-inner`\r\n      const scrollerInner = scroller.querySelector(\".scroller__inner\");\r\n      const scrollerContent = Array.from(scrollerInner.children);\r\n\r\n      // For each item in the array, clone it\r\n      // add aria-hidden to it\r\n      // add it into the `.scroller-inner`\r\n      scrollerContent.forEach((item) => {\r\n        const duplicatedItem = item.cloneNode(true);\r\n        duplicatedItem.setAttribute(\"aria-hidden\", true);\r\n        scrollerInner.appendChild(duplicatedItem);\r\n      });\r\n    });\r\n  }\r\n})(jQuery);\r\n"
  },
  {
    "path": "website/public/prism-theme.css",
    "content": "/**\n * atom-dark theme for `prism.js`\n * Based on Atom's `atom-dark` theme: https://github.com/atom/atom-dark-syntax\n * @author Joe Gibson (@gibsjose)\n */\n\ncode[class*=\"language-\"],\npre[class*=\"language-\"] {\n  color: #c5c8c6;\n  text-shadow: 0 1px rgba(0, 0, 0, 0.3);\n  font-family: Inconsolata, Monaco, Consolas, \"Courier New\", Courier, monospace;\n  direction: ltr;\n  text-align: left;\n  white-space: pre;\n  word-spacing: normal;\n  word-break: normal;\n  line-height: 1.5;\n\n  -moz-tab-size: 4;\n  -o-tab-size: 4;\n  tab-size: 4;\n\n  -webkit-hyphens: none;\n  -moz-hyphens: none;\n  -ms-hyphens: none;\n  hyphens: none;\n}\n\n/* Code blocks */\npre[class*=\"language-\"] {\n  padding: 1em;\n  margin: 0.5em 0;\n  overflow: auto;\n  border-radius: 0.3em;\n}\n\n:not(pre) > code[class*=\"language-\"],\npre[class*=\"language-\"] {\n  background: #1d1f21;\n}\n\n/* Inline code */\n:not(pre) > code[class*=\"language-\"] {\n  padding: 0.1em;\n  border-radius: 0.3em;\n}\n\n.token.comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n  color: #7c7c7c;\n}\n\n.token.punctuation {\n  color: #c5c8c6;\n}\n\n.namespace {\n  opacity: 0.7;\n}\n\n.token.property,\n.token.keyword,\n.token.tag {\n  color: #96cbfe;\n}\n\n.token.class-name {\n  color: #ffffb6;\n  text-decoration: underline;\n}\n\n.token.boolean,\n.token.constant {\n  color: #99cc99;\n}\n\n.token.symbol,\n.token.deleted {\n  color: #f92672;\n}\n\n.token.number {\n  color: #ff73fd;\n}\n\n.token.selector,\n.token.attr-name,\n.token.string,\n.token.char,\n.token.builtin,\n.token.inserted {\n  color: #a8ff60;\n}\n\n.token.variable {\n  color: #c6c5fe;\n}\n\n.token.operator {\n  color: #ededed;\n}\n\n.token.entity {\n  color: #ffffb6;\n  cursor: help;\n}\n\n.token.url {\n  color: #96cbfe;\n}\n\n.language-css .token.string,\n.style .token.string {\n  color: #87c38a;\n}\n\n.token.atrule,\n.token.attr-value {\n  color: #f9ee98;\n}\n\n.token.function {\n  color: #dad085;\n}\n\n.token.regex {\n  color: #e9c062;\n}\n\n.token.important {\n  color: #fd971f;\n}\n\n.token.important,\n.token.bold {\n  font-weight: bold;\n}\n\n.token.italic {\n  font-style: italic;\n}\n"
  },
  {
    "path": "website/src/assets/examples/history_notes/history_notes/history/medieval.txt",
    "content": "Key events: Fall of Rome, Viking raids, Magna Carta\nImportant dates: 476 AD, 793 AD, 1215 AD\n"
  },
  {
    "path": "website/src/assets/examples/history_notes/history_notes/history/renaissance.txt",
    "content": "Key figures: Leonardo da Vinci, Michelangelo, Copernicus\nCultural shifts: Humanism, art revival, printing press\n"
  },
  {
    "path": "website/src/assets/examples/history_notes/history_notes/history/ww2.txt",
    "content": "Key events: D-Day, Hiroshima, End of War\nLeaders: Churchill, Roosevelt, Hitler, Stalin\n"
  },
  {
    "path": "website/src/assets/examples/history_notes/history_notes/meta/my_revision_goals.txt",
    "content": "Focus: Memorize key events, understand causes & consequences\nDeadline: 2 weeks\n"
  },
  {
    "path": "website/src/assets/examples/history_notes/prompt.md",
    "content": "Project Path: history_notes\n\nSource Tree:\n\n```txt\nhistory_notes\n├── history\n│   ├── medieval.txt\n│   ├── renaissance.txt\n│   └── ww2.txt\n└── meta\n    └── my_revision_goals.txt\n```\n\n`history_notes/history/medieval.txt`:\n\n```txt\nKey events: Fall of Rome, Viking raids, Magna Carta\nImportant dates: 476 AD, 793 AD, 1215 AD\n```\n\n`history_notes/history/renaissance.txt`:\n\n```txt\nKey figures: Leonardo da Vinci, Michelangelo, Copernicus\nCultural shifts: Humanism, art revival, printing press\n```\n\n`history_notes/history/ww2.txt`:\n\n```txt\nKey events: D-Day, Hiroshima, End of War\nLeaders: Churchill, Roosevelt, Hitler, Stalin\n```\n\n`history_notes/meta/my_revision_goals.txt`:\n\n```txt\nFocus: Memorize key events, understand causes & consequences\nDeadline: 2 weeks\n```\n"
  },
  {
    "path": "website/src/assets/examples/history_notes/question.txt",
    "content": "Goal: Create interactive flashcards for my upcoming history exam\n\nFormat: Generate question-answer pairs in markdown format:\n- Each flashcard as a separate section\n- Questions that test key facts, dates, and connections\n- Concise but comprehensive answers\n- Organized by historical period\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/my_recipes/pantry/my_ingredients.txt",
    "content": "Available: pasta, tomato sauce, cheese\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/my_recipes/recipes/pasta.txt",
    "content": "Ingredients: pasta, tomato sauce, garlic, basil\nInstructions: Boil pasta, heat sauce, mix, serve.\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/my_recipes/recipes/pizza.txt",
    "content": "Ingredients: flour, tomato sauce, cheese, oregano\nInstructions: Make dough, add toppings, bake.\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/my_recipes/recipes/salad.txt",
    "content": "Ingredients: lettuce, tomato, cucumber, olive oil\nInstructions: Chop ingredients, mix, drizzle oil.\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/my_recipes/recipes/soup.txt",
    "content": "Ingredients: carrots, potatoes, chicken broth, onions\nInstructions: Boil broth, add vegetables, simmer.\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/prompt.md",
    "content": "Project Path: my_recipes\n\nSource Tree:\n\n```txt\nmy_recipes\n├── pantry\n│   └── my_ingredients.txt\n└── recipes\n    ├── pasta.txt\n    ├── pizza.txt\n    ├── salad.txt\n    └── soup.txt\n```\n\n`my_recipes/pantry/my_ingredients.txt`:\n\n```txt\nAvailable: pasta, tomato sauce, cheese\n```\n\n`my_recipes/recipes/pasta.txt`:\n\n```txt\nIngredients: pasta, tomato sauce, garlic, basil\nInstructions: Boil pasta, heat sauce, mix, serve.\n```\n\n`my_recipes/recipes/pizza.txt`:\n\n```txt\nIngredients: flour, tomato sauce, cheese, oregano\nInstructions: Make dough, add toppings, bake.\n```\n\n`my_recipes/recipes/salad.txt`:\n\n```txt\nIngredients: lettuce, tomato, cucumber, olive oil\nInstructions: Chop ingredients, mix, drizzle oil.\n```\n\n`my_recipes/recipes/soup.txt`:\n\n```txt\nIngredients: carrots, potatoes, chicken broth, onions\nInstructions: Boil broth, add vegetables, simmer.\n```\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/question.txt",
    "content": "Goal: Suggest a recipe I can cook tonight based on my available ingredients\n\nFormat: Provide a personalized recommendation with:\n- Recipe name and brief description\n- Ingredient checklist (what I have vs. what I need)\n- Step-by-step cooking instructions\n- Estimated prep and cooking time\n"
  },
  {
    "path": "website/src/assets/examples/node_app/node_app/README.md",
    "content": "Simple Node.js app to process JSON data.\nFeature idea: Add a filter function to sort users by age.\n"
  },
  {
    "path": "website/src/assets/examples/node_app/node_app/data/sample.json",
    "content": "{\n    \"users\": [\n        {\n            \"name\": \"Alice\",\n            \"age\": 25\n        },\n        {\n            \"name\": \"Bob\",\n            \"age\": 30\n        }\n    ]\n}"
  },
  {
    "path": "website/src/assets/examples/node_app/node_app/src/index.js",
    "content": "import express from \"express\";\n\nconst app = express();\nconst PORT = process.env.PORT || 3000;\n"
  },
  {
    "path": "website/src/assets/examples/node_app/node_app/src/utils.js",
    "content": "function processData() {\n  console.log(\"Processing data...\");\n}\n\nmodule.exports = { processData };\n"
  },
  {
    "path": "website/src/assets/examples/node_app/prompt.md",
    "content": "Project Path: node_app\n\nSource Tree:\n\n```txt\nnode_app\n├── README.md\n├── data\n│   └── sample.json\n└── src\n    ├── index.js\n    └── utils.js\n```\n\n`node_app/README.md`:\n\n```md\nSimple Node.js app to process JSON data.\nFeature idea: Add a filter function to sort users by age.\n```\n\n`node_app/data/sample.json`:\n\n```json\n{\n    \"users\": [\n        {\n            \"name\": \"Alice\",\n            \"age\": 25\n        },\n        {\n            \"name\": \"Bob\",\n            \"age\": 30\n        }\n    ]\n}\n```\n\n`node_app/src/index.js`:\n\n```js\nconst { processData } = require(\"./utils\");\n\nconsole.log(\"App started\");\nprocessData();\n```\n\n`node_app/src/utils.js`:\n\n```js\nfunction processData() {\n  console.log(\"Processing data...\");\n}\n\nmodule.exports = { processData };\n```\n"
  },
  {
    "path": "website/src/assets/examples/node_app/question.txt",
    "content": "Goal: Add a user filtering feature to sort users by age in ascending order\n\nFormat: Provide the complete implementation with:\n- Updated utils.js with the new filterUsersByAge function\n- Modified index.js to demonstrate the filtering\n- Clear code comments explaining the logic\n"
  },
  {
    "path": "website/src/components/Footer.astro",
    "content": "<footer id=\"footer\">\n  <p>&copy; Olivier D'Ancona & Mufeed VH</p>\n</footer>\n"
  },
  {
    "path": "website/src/components/Header.astro",
    "content": "---\nimport { Image } from \"astro:assets\";\nimport Code2promptLogo from \"/src/assets/logo_light_v0.0.1.svg\";\n---\n\n<section\n  id=\"header\"\n  class=\"w-full bg-gradient-to-b from-gray-900 to-blue-900 text-white py-16 min-h-[100vh] relative\"\n>\n  <div class=\"flex flex-col items-center justify-center p-8\">\n    <a href=\"/docs/welcome\">\n      <Image\n        src={Code2promptLogo}\n        alt=\"Code2Prompt Logo\"\n        class=\"h-[180px] mx-auto\"\n      />\n    </a>\n\n    <h1 class=\"text-4xl font-bold mt-6 mb-2\">Code2Prompt</h1>\n\n    <p class=\"text-xl text-center mt-2 mb-8 max-w-2xl\">\n      Transform Your Code into AI-Optimized Prompts in Seconds\n    </p>\n\n    <div class=\"flex gap-4 flex-wrap\">\n      <a href=\"/docs/welcome\">\n        <button\n          class=\"bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg hover:shadow-xl transition duration-200\"\n        >\n          Documentation\n        </button>\n      </a>\n      <a href=\"/docs/how_to/install\">\n        <button\n          class=\"bg-gray-700 hover:bg-gray-800 text-white font-bold py-3 px-6 rounded-lg shadow-lg hover:shadow-xl transition duration-200\"\n        >\n          Installation\n        </button>\n      </a>\n    </div>\n  </div>\n\n  <!-- Scroll Indicator Arrow -->\n  <div\n    id=\"scroll-indicator\"\n    class=\"absolute bottom-8 opacity-70 hover:opacity-100 transition-all duration-300 cursor-pointer\"\n    style=\"left: 50%; transform: translateX(-50%);\"\n  >\n    <svg\n      width=\"32\"\n      height=\"32\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      class=\"text-white drop-shadow-lg block\"\n    >\n      <path\n        d=\"M7 10L12 15L17 10\"\n        stroke=\"currentColor\"\n        stroke-width=\"2.5\"\n        stroke-linecap=\"round\"\n        stroke-linejoin=\"round\"></path>\n    </svg>\n  </div>\n</section>\n\n<script>\n  // Handle scroll indicator visibility\n  const scrollIndicator = document.getElementById(\"scroll-indicator\");\n\n  function handleScroll() {\n    if (scrollIndicator) {\n      if (window.scrollY > 50) {\n        // Hide arrow when scrolled down\n        scrollIndicator.style.opacity = \"0\";\n        scrollIndicator.style.transform = \"translateX(-50%) translateY(20px)\";\n        scrollIndicator.style.pointerEvents = \"none\";\n      } else {\n        // Show arrow when at top\n        scrollIndicator.style.opacity = \"0.7\";\n        scrollIndicator.style.transform = \"translateX(-50%) translateY(0)\";\n        scrollIndicator.style.pointerEvents = \"auto\";\n        scrollIndicator.style.display = \"block\";\n      }\n    }\n  }\n\n  // Add scroll event listener\n  window.addEventListener(\"scroll\", handleScroll);\n\n  // Handle click on scroll indicator\n  if (scrollIndicator) {\n    scrollIndicator.addEventListener(\"click\", () => {\n      window.scrollTo({\n        top: window.innerHeight,\n        behavior: \"smooth\",\n      });\n    });\n  }\n</script>\n\n<style>\n  @keyframes bounce {\n    0%,\n    20%,\n    53%,\n    80%,\n    100% {\n      transform: translateX(-50%) translateY(0);\n    }\n    40%,\n    43% {\n      transform: translateX(-50%) translateY(-10px);\n    }\n    70% {\n      transform: translateX(-50%) translateY(-5px);\n    }\n    90% {\n      transform: translateX(-50%) translateY(-2px);\n    }\n  }\n\n  #scroll-indicator {\n    animation: bounce 2s infinite;\n  }\n</style>\n"
  },
  {
    "path": "website/src/components/Section0.astro",
    "content": "---\nimport { Prism } from \"@astrojs/prism\";\nimport { FileTree } from '@astrojs/starlight/components';\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n// Configuration for examples dir\nconst EXAMPLES_DIR = \"src/assets/examples\";\nconst exampleDirs = fs\n  .readdirSync(EXAMPLES_DIR)\n  .filter((dir) => fs.statSync(path.join(EXAMPLES_DIR, dir)).isDirectory());\n\n// Function to extract directory structure from prompt.md\nfunction extractDirectoryTree(promptContent: string) {\n  const treeMatch = promptContent.match(/```txt\\n([\\s\\S]*?)```/);\n  return treeMatch ? treeMatch[1].trim() : \"\";\n}\n\n// Function to parse prompt.md to get example data\nfunction parsePromptMd(promptPath: string) {\n  try {\n    const promptContent = fs.readFileSync(promptPath, \"utf-8\");\n    const directoryTree = extractDirectoryTree(promptContent);\n\n    // Extract question if it exists in a separate file\n    let question = \"\";\n    const questionPath = path.dirname(promptPath) + \"/question.txt\";\n    if (fs.existsSync(questionPath)) {\n      question = fs.readFileSync(questionPath, \"utf-8\").trim();\n    } else {\n      // Default to a generated question\n      question = `Add me a this cool feature`;\n    }\n\n    return {\n      promptContent,\n      directoryTree,\n      question,\n    };\n  } catch (error) {\n    console.error(`Error parsing prompt.md: ${error}`);\n    return null;\n  }\n}\n\n// Map directory names to categories\nconst categoryMap: Record<string, string> = {\n  \"node_app\": \"Codebase 👨‍💻\",\n  \"history_notes\": \"Personal Notes 📖\",\n  \"my_recipes\": \"Recipes Database 🧑‍🍳\"\n};\n\n// Load data for all examples and categorize them\nconst examples = exampleDirs\n  .map((dir) => {\n    const promptPath = path.join(EXAMPLES_DIR, dir, \"prompt.md\");\n    if (fs.existsSync(promptPath)) {\n      const data = parsePromptMd(promptPath);\n      if (data) {\n        return {\n          name: dir,\n          category: categoryMap[dir] || \"Other\",\n          directoryTree: data.directoryTree,\n          prompt: data.question,\n          promptContent: data.promptContent.trim()\n        };\n      }\n    }\n    return null;\n  })\n  .filter((example): example is NonNullable<typeof example> => example !== null);\n\n// Default to first example\nconst { defaultTab = 2 } = Astro.props;\n---\n\n<script>\n  // Tabs functionality\n  const tabButtons = document.querySelectorAll('[role=\"tab\"]');\n  const tabPanes = document.querySelectorAll('.tab-pane');\n  \n  function matchColumnHeights() {\n    // Wait for content to render\n    setTimeout(() => {\n      const activePane = document.querySelector('.tab-pane:not(.hidden)');\n      if (activePane) {\n        const leftColumn = activePane.querySelector('.left-column') as HTMLElement;\n        const rightColumn = activePane.querySelector('.right-column') as HTMLElement;\n        const contextContent = activePane.querySelector('.context-content') as HTMLElement;\n        \n        if (leftColumn && rightColumn && contextContent) {\n          // Reset heights\n          rightColumn.style.height = 'auto';\n          contextContent.style.height = 'auto';\n          \n          // Get left column height\n          const leftHeight = leftColumn.offsetHeight;\n          \n          // Set right column to match left column height\n          rightColumn.style.height = leftHeight + 'px';\n          \n          // Calculate available space for context content\n          const rightColumnPadding = 48; // 6 * 8px (p-6)\n          const contextHeader = rightColumn.querySelector('.context-header') as HTMLElement;\n          const headerHeight = contextHeader ? contextHeader.offsetHeight : 0;\n          const availableHeight = leftHeight - rightColumnPadding - headerHeight - 12; // 12px for margin\n          \n          contextContent.style.height = availableHeight + 'px';\n        }\n      }\n    }, 100);\n  }\n  \n  tabButtons.forEach(button => {\n    button.addEventListener('click', () => {\n      // Hide all tab content\n      tabPanes.forEach(pane => {\n        pane.classList.add('hidden');\n        pane.classList.remove('block');\n      });\n      \n      // Deactivate all tabs\n      tabButtons.forEach(tab => {\n        tab.classList.remove('border-blue-600', 'text-blue-600');\n        tab.classList.add('border-transparent', 'text-gray-500');\n        tab.setAttribute('aria-selected', 'false');\n      });\n      \n      // Activate the clicked tab\n      button.classList.add('border-blue-600', 'text-blue-600');\n      button.classList.remove('border-transparent', 'text-gray-500');\n      button.setAttribute('aria-selected', 'true');\n      \n      // Show the corresponding content\n      const index = button.getAttribute('data-index');\n      const targetContent = document.getElementById(`tab-content-${index}`);\n      if (targetContent) {\n        targetContent.classList.add('block');\n        targetContent.classList.remove('hidden');\n        matchColumnHeights();\n      }\n    });\n  });\n  \n  // Match heights on initial load\n  document.addEventListener('DOMContentLoaded', matchColumnHeights);\n  window.addEventListener('resize', matchColumnHeights);\n</script>\n\n<div class=\"w-full bg-gray-100\">\n  <section id=\"what\" class=\"bg-gray-100 py-12 max-w-[90vw] mx-auto\">\n    <div class=\"mx-auto px-4\">\n      <!-- Section Header -->\n      <div class=\"text-center mb-10\">\n        <h2 class=\"text-3xl font-bold text-gray-800\">What is Code2Prompt ?</h2>\n        <p class=\"mb-2 m-4 text-gray-800\">\n          Code2Prompt is a context engineering tool that ingests your codebase, turning your repository into structured, AI-ready prompts.\n        </p>\n\n        <p class=\"text-lg text-gray-600 mt-2\">Transform any repository into meaningful context following the Goal + Format + Context framework... Check it out !</p>\n      </div>\n      \n      <!-- Tab Navigation -->\n      <div class=\"mb-6\">\n        <div class=\"border-b border-gray-200 bg-white rounded-t-lg shadow-sm\">\n          <ul class=\"flex flex-wrap -mb-px text-sm font-medium text-center\" id=\"exampleTabs\" role=\"tablist\">\n            {examples.map((example, index) => (\n              <li class=\"mr-2\" role=\"presentation\">\n                <button \n                  class={`inline-block p-4 rounded-t-lg border-b-2 ${index === defaultTab ? 'border-blue-600 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}`}\n                  id={`tab-${index}`}\n                  data-tabs-target={`#tab-content-${index}`}\n                  type=\"button\"\n                  role=\"tab\"\n                  aria-controls={`tab-content-${index}`}\n                  aria-selected={index === defaultTab}\n                  data-index={index}\n                >\n                  {example.category}\n                </button>\n              </li>\n            ))}\n          </ul>\n        </div>\n      </div>\n\n      <div class=\"tab-content\">\n        {examples.map((example, index) => (\n          <div \n            class={`tab-pane ${index === defaultTab ? 'block' : 'hidden'}`} \n            id={`tab-content-${index}`}\n            role=\"tabpanel\"\n            aria-labelledby={`tab-${index}`}\n          >\n            <div class=\"flex flex-col md:flex-row md:gap-6 md:items-start\">\n              <!-- Left Column: Directory Tree + Goal + Format -->\n              <div class=\"w-full md:w-1/3 mb-6 md:mb-0 p-6 bg-white rounded-md shadow-md left-column\">\n                <header class=\"mb-4 border-b pb-2\">\n                  <h2 class=\"text-xl font-bold text-gray-800\">{example.category}</h2>\n                </header>\n                <div class=\"text-gray-800 p-2\">\n                  <pre class=\"text-sm font-mono whitespace-pre bg-gray-50 p-4 rounded-md border border-gray-200\">{example.directoryTree}</pre>\n                </div>\n                \n                <!-- Command Section -->\n                <div class=\"mt-6 pt-4 border-t border-gray-200\">\n                  <h3 class=\"text-lg font-semibold text-gray-800 mb-2\">Command</h3>\n                  <div class=\"bg-gray-900 p-3 rounded-md text-gray-200 font-mono text-sm overflow-x-auto\">\n                    $ code2prompt {example.name}\n                  </div>\n                </div>\n\n                <!-- Goal Section -->\n                <div class=\"mt-6 pt-4 border-t border-gray-200\">\n                  <header class=\"mb-3\">\n                    <h3 class=\"text-lg font-bold text-blue-600\">Goal</h3>\n                    <p class=\"text-xs text-gray-600\">What you want to achieve</p>\n                  </header>\n                  <div class=\"bg-blue-50 border-l-4 border-blue-500 p-3 rounded-md\">\n                    <div class=\"text-gray-800 text-sm\">\n                      {example.prompt.split('\\n\\nFormat:')[0].replace('Goal: ', '')}\n                    </div>\n                  </div>\n                </div>\n\n                <!-- Format Section -->\n                <div class=\"mt-4\">\n                  <header class=\"mb-3\">\n                    <h3 class=\"text-lg font-bold text-green-600\">Format</h3>\n                    <p class=\"text-xs text-gray-600\">How you want the output structured</p>\n                  </header>\n                  <div class=\"bg-green-50 border-l-4 border-green-500 p-3 rounded-md\">\n                    <div class=\"text-gray-800 text-sm whitespace-pre-line\">\n                      {example.prompt.split('Format: ')[1] || 'Standard format'}\n                    </div>\n                  </div>\n                </div>\n              </div>\n\n              <!-- Right Column: Context Only -->\n              <div class=\"w-full md:w-2/3 relative p-6 bg-white rounded-md shadow-md right-column\">\n                <!-- Context Section -->\n                <div>\n                  <header class=\"mb-3 context-header\">\n                    <h3 class=\"text-xl font-bold text-purple-600\">Context</h3>\n                    <p class=\"text-sm text-gray-600\">Relevant information provided by Code2Prompt</p>\n                  </header>\n                  <div class=\"prose max-w-none context-content\">\n                    <Prism\n                      code={example.promptContent}\n                      lang=\"markdown\"\n                      class=\"overflow-auto w-full prism-small-font h-full\"\n                    />\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        ))}\n      </div>\n    </div>\n  </section>\n</div>\n"
  },
  {
    "path": "website/src/components/Section1.astro",
    "content": "---\nimport { Prism } from \"@astrojs/prism\";\n---\n\n<div class=\"w-full bg-gray-100\">\n  <section id=\"one\" class=\"bg-gray-100 py-12 max-w-[90vw] mx-auto\">\n    <div class=\"container mx-auto\">\n      <div class=\"flex flex-col md:flex-row md:gap-6\">\n        <!-- Left column (1/3) -->\n        <div class=\"w-full md:w-1/3 mb-6 md:mb-0 p-4 m-4\">\n          <header class=\"mb-4 text-gray-800\">\n            <h2 class=\"text-2xl font-semibold\">Why Code2prompt ?</h2>\n          </header>\n\n          <p class=\"mb-2 m-4 text-gray-800\">\n            Code2Prompt introduces a new development workflow, enabling AI and\n            human agents to interact with code efficiently.\n          </p>\n\n          <p class=\"mb-2 m-4 text-gray-800\">\n            Code2Prompt leverages glob patterns to include or exclude only the\n            relevant files.\n          </p>\n\n          <p class=\"mb-2 m-4 text-gray-800\">\n            This allows you to query LLMs without extra noise, thus reducing\n            hallucination and increasing performance.\n          </p>\n        </div>\n\n        <!-- Right column (2/3) with background image -->\n        <div class=\"w-full md:w-2/3 relative m-2 p-2\">\n          <!-- Code block container -->\n          <div\n            class=\"bg-gray-900 bg-opacity-80 rounded-md relative flex items-center justify-center h-full\"\n          >\n            <!-- Copy button -->\n            <button\n              class=\"absolute top-2 right-2 bg-gray-700 hover:bg-gray-600 text-white text-sm px-2 py-1 rounded-md flex items-center z-10\"\n              onclick=\"copyCode(this)\"\n            >\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                class=\"h-4 w-4 mr-1\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n                stroke=\"currentColor\"\n              >\n                <path\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                  stroke-width=\"2\"\n                  d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\"\n                ></path>\n              </svg>\n              Copy\n            </button>\n\n            <!-- Code block with appropriate word wrapping -->\n            <Prism\n              code={`code2prompt . --include \"*.js,*.html\" --exclude \"node_modules/\"`}\n              lang=\"js\"\n              class=\"overflow-x-auto w-auto ml-10 mr-10\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <!-- Marquee sections - keeping the custom classes -->\n    <div class=\"scroller\" data-speed=\"slow\">\n      <ul class=\"tag-list scroller__inner\">\n        <li>Code it</li>\n        <li>Parse it</li>\n        <li>Extract it</li>\n        <li>Format it</li>\n        <li>Analyze it</li>\n        <li>Optimize it</li>\n        <li>Rewrite it</li>\n        <li>Summarize it</li>\n        <li>Filter it</li>\n        <li>Search it</li>\n        <li>Sort it</li>\n        <li>Query it</li>\n        <li>Compare it</li>\n        <li>Deploy it</li>\n        <li>Debug it</li>\n        <li>Refactor it</li>\n        <li>Automate it</li>\n        <li>Run it</li>\n        <li>Monitor it</li>\n        <li>Test it</li>\n        <li>Track it</li>\n        <li>Patch it</li>\n        <li>Secure it</li>\n        <li>Train it</li>\n        <li>Validate it</li>\n        <li>Package it</li>\n        <li>Upgrade it</li>\n        <li>Integrate it</li>\n        <li>Unlock it</li>\n      </ul>\n    </div>\n    <div class=\"scroller\" data-direction=\"right\" data-speed=\"fast\">\n      <div class=\"scroller__inner\">\n        <img class=\"img-scroll\" src=\"/assets/images/Ollama.svg\" alt=\"LLaMA\" />\n        <img class=\"img-scroll\" src=\"/assets/images/Groq.svg\" alt=\"Grok\" />\n        <img class=\"img-scroll\" src=\"/assets/images/Qwen.svg\" alt=\"Qwen\" />\n        <img\n          class=\"img-scroll\"\n          src=\"/assets/images/Mistral.svg\"\n          alt=\"Mistral\"\n        />\n        <img\n          class=\"img-scroll\"\n          src=\"/assets/images/Deepseek.svg\"\n          alt=\"DeepSeek\"\n        />\n        <img class=\"img-scroll\" src=\"/assets/images/Gemini.svg\" alt=\"Gemini\" />\n        <img class=\"img-scroll\" src=\"/assets/images/OpenAI.svg\" alt=\"GPT\" />\n        <img class=\"img-scroll\" src=\"/assets/images/Claude.svg\" alt=\"Claude\" />\n      </div>\n    </div>\n  </section>\n</div>\n"
  },
  {
    "path": "website/src/components/Section2.astro",
    "content": "---\nimport { Image } from \"astro:assets\";\nimport SDK from \"/src/assets/SDK.svg\";\nimport CLI from \"/src/assets/CLI.svg\";\nimport MCP from \"/src/assets/MCP.svg\";\n---\n\n<section id=\"two\" class=\"main style2 py-16 bg-gray-50 dark:bg-gray-900\">\n  <div class=\"container mx-auto px-4\">\n    <header class=\"major text-center mb-12\">\n      <h2 class=\"text-3xl md:text-4xl font-bold text-blue-100 dark:text-white\">\n        How to use Code2Prompt ?\n      </h2>\n    </header>\n    <p\n      class=\"text-center text-lg mx-auto mb-12 max-w-3xl text-gray-600 dark:text-gray-300\"\n    >\n      Code2Prompt comes with different flavors depending on your needs.\n    </p>\n    <div class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mt-8\">\n      <!-- CLI Card -->\n      <a\n        href=\"https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt\"\n      >\n        <div\n          class=\"bg-white dark:bg-gray-800 rounded-lg shadow-lg hover:shadow-xl transition-all duration-300 overflow-hidden border border-gray-200 dark:border-gray-700 h-full\"\n        >\n          <!-- Improved icon container with better contrast -->\n          <div\n            class=\"p-8 flex justify-center items-center bg-white dark:bg-gray-700\"\n          >\n            <Image\n              class=\"w-2/3 max-h-36 filter dark:invert\"\n              src={CLI}\n              alt=\"CLI\"\n            />\n          </div>\n          <div class=\"p-6 border-t border-gray-200 dark:border-gray-700\">\n            <h3 class=\"text-xl font-bold mb-3 text-blue-100 dark:text-white\">\n              CLI\n            </h3>\n            <p class=\"text-gray-600 dark:text-gray-300\">\n              For Humans. Generate structured prompts from the terminal at light\n              speed ⚡\n            </p>\n          </div>\n        </div>\n      </a>\n\n      <!-- SDK Card -->\n      <a href=\"https://pypi.org/project/code2prompt-rs/\">\n        <div\n          class=\"bg-white dark:bg-gray-800 rounded-lg shadow-lg hover:shadow-xl transition-all duration-300 overflow-hidden border border-gray-200 dark:border-gray-700 h-full\"\n        >\n          <!-- Improved icon container with better contrast -->\n          <div\n            class=\"p-8 flex justify-center items-center bg-white dark:bg-gray-700\"\n          >\n            <Image\n              class=\"w-2/3 max-h-36 filter dark:invert\"\n              src={SDK}\n              alt=\"SDK\"\n            />\n          </div>\n          <div class=\"p-6 border-t border-gray-200 dark:border-gray-700\">\n            <h3 class=\"text-xl font-bold mb-3 text-blue-100 dark:text-white\">\n              Python SDK\n            </h3>\n            <p class=\"text-gray-600 dark:text-gray-300\">\n              For programs. Integrate Code2Prompt into Python applications for\n              advanced automation 🖥️\n            </p>\n          </div>\n        </div>\n      </a>\n\n      <!-- MCP Card -->\n      <a href=\"https://github.com/ODAncona/code2prompt-mcp\">\n        <div\n          class=\"bg-white dark:bg-gray-800 rounded-lg shadow-lg hover:shadow-xl transition-all duration-300 overflow-hidden border border-gray-200 dark:border-gray-700 h-full\"\n        >\n          <!-- Improved icon container with better contrast -->\n          <div\n            class=\"p-8 flex justify-center items-center bg-white dark:bg-gray-700\"\n          >\n            <Image\n              class=\"w-2/3 max-h-36 filter dark:invert\"\n              src={MCP}\n              alt=\"MCP\"\n            />\n          </div>\n          <div class=\"p-6 border-t border-gray-200 dark:border-gray-700\">\n            <h3 class=\"text-xl font-bold mb-3 text-blue-100 dark:text-white\">\n              MCP\n            </h3>\n            <p class=\"text-gray-600 dark:text-gray-300\">\n              For Agents. Model Context Protocol to dope agents context 🤖\n            </p>\n          </div>\n        </div>\n      </a>\n    </div>\n  </div>\n</section>\n"
  },
  {
    "path": "website/src/components/Section3.astro",
    "content": "<section id=\"three\" class=\"bg-white py-24 sm:py-32\">\n  <div class=\"mx-auto max-w-7xl px-6 lg:px-4\">\n    <div class=\"mx-auto max-w-2xl lg:text-center\">\n      <h2 class=\"text-base font-semibold text-indigo-600\">\n        Built for creators\n      </h2>\n      <p\n        class=\"mt-2 text-4xl font-semibold tracking-tight text-pretty text-gray-900 sm:text-5xl lg:text-balance\"\n      >\n        Discover the new features\n      </p>\n      <p class=\"mt-6 text-lg text-gray-600\">\n        Code2Prompt transforms your codebase into structured prompts for AI\n        models, making it easier to get accurate, context-aware responses.\n      </p>\n    </div>\n    <div class=\"mx-auto mt-16 max-w-2xl sm:mt-20 lg:mt-24 lg:max-w-4xl\">\n      <dl\n        class=\"grid max-w-xl grid-cols-1 gap-x-8 gap-y-10 lg:max-w-none lg:grid-cols-2 lg:gap-y-16\"\n      >\n        <div class=\"relative pl-16\">\n          <dt class=\"text-base font-semibold text-gray-900\">\n            <div\n              class=\"absolute top-0 left-0 flex size-10 items-center justify-center rounded-lg bg-indigo-600\"\n            >\n              <svg\n                class=\"size-6\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n                stroke-width=\"1.5\"\n                stroke=\"currentColor\"\n                aria-hidden=\"true\"\n              >\n                <path\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                  d=\"M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z\"\n                ></path>\n              </svg>\n            </div>\n            High-Performance\n          </dt>\n          <dd class=\"mt-2 text-base text-gray-600\">\n            Written in Rust for speed and efficiency, handling large codebases\n            with minimal resource usage.\n          </dd>\n        </div>\n        <div class=\"relative pl-16\">\n          <dt class=\"text-base font-semibold text-gray-900\">\n            <div\n              class=\"absolute top-0 left-0 flex size-10 items-center justify-center rounded-lg bg-indigo-600\"\n            >\n              <svg\n                class=\"size-6\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n                stroke-width=\"1.5\"\n                stroke=\"currentColor\"\n                aria-hidden=\"true\"\n              >\n                <path\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                  d=\"M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z\"\n                ></path>\n              </svg>\n            </div>\n            Handlebars-Powered\n          </dt>\n          <dd class=\"mt-2 text-base text-gray-600\">\n            Customizable prompt generation using Handlebars templates, giving\n            you full control over output format.\n          </dd>\n        </div>\n        <div class=\"relative pl-16\">\n          <dt class=\"text-base font-semibold text-gray-900\">\n            <div\n              class=\"absolute top-0 left-0 flex size-10 items-center justify-center rounded-lg bg-indigo-600\"\n            >\n              <svg\n                class=\"size-6\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n                stroke-width=\"1.5\"\n                stroke=\"currentColor\"\n                aria-hidden=\"true\"\n              >\n                <path\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                  d=\"M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z\"\n                ></path>\n              </svg>\n            </div>\n            Smart Filtering\n          </dt>\n          <dd class=\"mt-2 text-base text-gray-600\">\n            Supports include/exclude patterns with glob matching and smart\n            context for precise code selection (soon).\n          </dd>\n        </div>\n        <div class=\"relative pl-16\">\n          <dt class=\"text-base font-semibold text-gray-900\">\n            <div\n              class=\"absolute top-0 left-0 flex size-10 items-center justify-center rounded-lg bg-indigo-600\"\n            >\n              <svg\n                class=\"size-6\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n                stroke-width=\"1.5\"\n                stroke=\"currentColor\"\n                aria-hidden=\"true\"\n              >\n                <path\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                  d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5\"\n                ></path>\n              </svg>\n            </div>\n            Multi-Format Support\n          </dt>\n          <dd class=\"mt-2 text-base text-gray-600\">\n            Exports structured prompts in JSON, Markdown, or XML with different\n            formatting options to suit your workflow.\n          </dd>\n        </div>\n        <div class=\"relative pl-16\">\n          <dt class=\"text-base font-semibold text-gray-900\">\n            <div\n              class=\"absolute top-0 left-0 flex size-10 items-center justify-center rounded-lg bg-indigo-600\"\n            >\n              <svg\n                class=\"size-6\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n                stroke-width=\"1.5\"\n                stroke=\"currentColor\"\n                aria-hidden=\"true\"\n              >\n                <path\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                  d=\"M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75\"\n                ></path>\n              </svg>\n            </div>\n            Git Integration\n          </dt>\n          <dd class=\"mt-2 text-base text-gray-600\">\n            Includes Git diff and log extraction for better context, making it\n            easier to understand code changes over time.\n          </dd>\n        </div>\n        <div class=\"relative pl-16\">\n          <dt class=\"text-base font-semibold text-gray-900\">\n            <div\n              class=\"absolute top-0 left-0 flex size-10 items-center justify-center rounded-lg bg-indigo-600\"\n            >\n              <svg\n                class=\"size-6\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n                stroke-width=\"1.5\"\n                stroke=\"currentColor\"\n                aria-hidden=\"true\"\n              >\n                <path\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                  d=\"M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z\"\n                ></path>\n              </svg>\n            </div>\n            Open-Source & Community-Driven\n          </dt>\n          <dd class=\"mt-2 text-base text-gray-600\">\n            Built with developer collaboration under the MIT license,\n            encouraging contributions and extensions.\n          </dd>\n        </div>\n      </dl>\n    </div>\n  </div>\n</section>\n"
  },
  {
    "path": "website/src/components/Section4.astro",
    "content": "<section id=\"four\" class=\"py-12 text-center\">\n  <div class=\"container mx-auto px-4\">\n    <h2 class=\"text-3xl font-bold mb-4\">Join the Community</h2>\n    <p class=\"mb-8 max-w-2xl mx-auto\">\n      Code2Prompt is built by and for developers. Contribute, suggest features,\n      and help shape the future of AI-driven code analysis.\n    </p>\n    <div class=\"flex flex-col sm:flex-row justify-center gap-4\">\n      <a\n        href=\"https://github.com/mufeedvh/code2prompt\"\n        class=\"bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-md\"\n        >Contribute on GitHub</a\n      >\n      <a\n        href=\"https://discord.gg/ZZyBbsHTwH\"\n        class=\"bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-6 rounded-md\"\n        >Join our Discord</a\n      >\n    </div>\n  </div>\n</section>\n"
  },
  {
    "path": "website/src/content/docs/blog/2025.04.11_why_I_wrote_code2prompt.mdx",
    "content": "---\ntitle: \"Why I Developed Code2Prompt\"\ndate: 2025-04-11\nlastUpdated: 2025-04-11\ntags:\n  - open-source\n  - code2prompt\n  - AI\n  - Agent\nexcerpt: \"The story behind code2prompt: my Open-Source quest to tackle context challenges in LLM workflows\"\nauthors:\n  - ODAncona\ncover:\n  alt: \"An illustration of code2prompt streamlining code context for AI agents.\"\n  image: \"/src/assets/logo_dark_v0.0.2.svg\"\nfeatured: false\ndraft: false\n---\n\n## Introduction\n\nI've always been fascinated by how Large Language Models (LLMs) transform coding workflows—generating tests, docstrings, or even shipping entire features in minutes. But as I pushed these models further, a few critical pain points kept surfacing:\n\n| Planning Difficulties | High Token Costs | Hallucinations |\n| --------------------- | ---------------- | -------------- |\n| 🧠 ➡️ 🤯              | 🔥 ➡️ 💸         | 💬 ➡️ 🌀       |\n\nThat's why I started contributing to `code2prompt`, a Rust-based tool to help feed just the proper context into LLMs.\n\nIn this post, I'll share my journey and explain why I'm convinced that `code2prompt` is relevant today and integrates so well and why it's become my go-to solution for better, faster AI coding workflows.\n\n## My First Steps with LLMs 👣\n\nI started experimenting with LLMs on `OpenAI Playground` with `text-davinci-003` when it gained traction in November 2023. Language models enabled a new revolution. It felt like having a brilliant new assistant who would crank out unit tests and docstrings almost on command. I enjoyed pushing the models to their limits—testing everything from small talk and ethical dilemmas to jailbreaks and complex coding tasks. However, as I took on more extensive projects, I quickly realized that the models had glaring limitations. At first, I could only fit a few hundred lines of code into the context window, and even then, the models often struggled to understand the code's purpose or structure. That's why I quickly noticed that the importance of context was paramount. The more concise my instructions were and the better the context, the better the results.\n\n![OpenAI Playground](/assets/blog/post1/playground.png)\n\n## Model Evolution 🏗️\n\nThe models could produce impressive results but often struggled with larger codebases or complex tasks. I found myself spending more time crafting prompts than actually coding. At the same time, the models kept improving with the release of new versions. They increased reasoning abilities and context size, offering new perspectives and possibilities. I could fit almost two thousand lines of code into the context window then, and the results improved. I could write entire features in a matter of a few iterations, and I was amazed by how quickly I could get results. I was convinced that LLMs were the future of coding, and I wanted to be part of that revolution. I firmly believe that AI won't replace us yet. But will assist us in the form of assistants where humans are the experts still in control.\n\n## My First Projects with LLMs🚀\n\nI started to write a `ROS` pathfinding module for a robotic competition, generate features for a clean architecture `Flutter` cross-platform app, and made a small web app to keep track of my expenses in `Next.js`. The fact that I built this small app in one evening, in a framework I'd never touched before, was a game-changer moment for me; LLMs weren't just tools but multipliers. I developed `bboxconverter', a package to convert bounding boxes, and the list goes on. LLMs can help you learn new technologies and frameworks quickly; that's awesome.\n\n## A New Paradigm: Software 3.0 💡\n\nI dove deeper into LLMs and started to build agents and scaffold around them. I reproduced the famous paper [RestGPT](https://restgpt.github.io/). The idea is excellent: give LLMs the ability to call some REST API with an OpenAPI specification, such as `Spotify` or `TMDB.` These capabilities introduce a new software programming paradigm, which I like to call **Software 3.0**.\n\n| Software 1.0 | Software 2.0 | Software 3.0 |\n| ------------ | ------------ | ------------ |\n| Rules-based  | Data-driven  | Agentic      |\n\nThe same idea propelled the [MCP](https://modelcontextprotocol.io/introduction) protocol, which allows LLMs to call tools and resources directly in a seamless way because, by design, the tool needs a description to be called by the LLM in the opposite of REST Apis that doesn't necessarily require OpenAPI specification.\n\n## The Limitations of LLMs 🧩\n\n### Hallucinations 🌀\n\nWhile reproducing the famous paper `RESTGPT,` I noticed some serious limitations of LLMs. The paper's authors encountered the same issues I had: LLMs were **hallucinating**. They generate code that is not implemented, inventing arguments and simply following the instructions to the letter without leveraging common sense. E.g., in the original RestGPT codebase, the authors asked in [the caller prompt](https://github.com/Yifan-Song793/RestGPT/blob/main/model/caller.py).\n\n> \"to not get clever and make up steps that don't exist in the plan.\"\n\nI found this statement funny and very interesting because it was the first time I encountered someone instructing LLMs not to hallucinate.\n\n### Limited Context-Size 📏\n\nAnother limitation was the context size; LLMs perform well in finding the needle in the haystack but struggle to make sense of it. When you give too much context to the language models, they tend to get lost in the details and lose sight of the big picture, which is annoying and requires constant steering. The way I like to think about it is in a similar way as the [curse of dimensionality](https://towardsdatascience.com/curse-of-dimensionality-a-curse-to-machine-learning-c122ee33bfeb/). Replace the word \"dimension\" or \"feature\" by \"context\", and you get the idea.\n\n![Curse of Dimensionality](/assets/blog/post1/curse_of_dimensionality.png)\n\nThe more context you give to the LLM, the more difficult it is to find the correct answer. I came up with a nice sentence to summarize this idea:\n\n> Provide as little context as possible but as much as necessary\n\nThis is heavily inspired by the famous [quote of Alain Berset](https://www.lematin.ch/story/alain-berset-la-formule-qui-defie-le-temps-166189802108), a Swiss politician 🇨🇭 who said during the COVID-19 lockdown:\n\n> \"Nous souhaitons agir aussi vite que possible, mais aussi lentement que nécessaire\"\n\nThis represents the idea of compromise and applies to the context size of LLMs!\n\n## Searching for a Better Way: code2prompt 🔨\n\nTherefore, I needed a way to load, filter, and organize my code context quickly by provisioning the least amount possible of context with the best quality possible. I tried manually copying files or snippets into prompts, but that became unwieldy and error-prone. I knew automating the tedious process of forging the context to ask better prompts would be helpful. Then, one day, I typed \"code2prompt\" into Google, hoping to find a tool that piped my code directly into prompts.\n\nLo and behold, I discovered a **Rust-based project** by [Mufeed](https://www.reddit.com/r/rust/comments/1bghroh/i_made_code2prompt_a_cli_tool_to_convert_your/) named _code2prompt_, sporting about 200 stars on GitHub. It was still basic at the time: a simple CLI tool with basic limited filter capacity and templates. I saw enormous potential and jumped in straight to contribute, implementing glob pattern matching, among other features, and soon became the main contributor.\n\n## Vision & Integrations 🔮\n\nToday, there are several ways to provide context to LLMs. Generating from the larger context, using Retrieval-Augmented Generation (RAG), [compressing the code](https://www.all-hands.dev/blog/openhands-context-condensensation-for-more-efficient-ai-agents), or even using a combination of these methods. Context forging is a hot topic that will evolve rapidly in the coming months. However, my approach is **KISS**: Keep It Simple, Stupid. The best way to provide context to LLMs is to use the simplest and most efficient way possible. You forge precisely the context you need; it's deterministic, contrary to RAG.\n\nThat's why I decided to push `code2prompt` further as a simple tool that can be used in any workflow. I wanted to make it easy to use, easy to integrate, and easy to extend. That's why I added new ways to interact with the tool.\n\n- **Core**: The core of `code2prompt` is a Rust library that provides the basic functionality to forge context from your codebase. It includes a simple API to load, filter, and organize your code context.\n- **CLI:** The command line interface is the simplest way to use `code2prompt`. You can forge context from your codebase and pipe it directly into your prompts.\n- **Python API:** The Python API is a simple wrapper around the CLI that allows you to use `code2prompt` in your Python scripts and agents. You can forge context from your codebase and pipe it directly into your prompts.\n- **MCP**: The `code2prompt` MCP server allows LLMs to use `code2prompt` as a tool, thus making themselves capable of forging the context.\n\nThe vision is described further in the [vision page](/docs/vision) in the doc.\n\n## Integration with agents 👤\n\nI believe that future agents will need to have a way to ingest context, and `code2prompt` is the simple and efficient way to do it for textual repositories like codebase, documentation, or notes. A propical place to use `code2prompt` would be in a codebase with meaningful naming conventions. For example, in clean architecture, there is a clear separation of concerns and layers. The relevant context usually resides in different files and folders but share the same name. This is a perfect use case for `code2prompt`, where you can use the glob pattern to grab the relevant files.\n\n**Glob Pattern-first:** Precisely select or exclude files with minimal fuss.\n\nFurthermore, the core library is designed as a stateful context manager, allowing you to add or remove files as your conversation with the LLM evolves. This is particularly useful when providing context for a specific task or goal. You can easily add or remove files from the context without re-running the process.\n\n**Stateful Context:** Add or remove files as your conversation with the LLM evolves.\n\nThose capabilities make `code2prompt` a perfect fit for agent-based workflows. The MCP server allows seamless integration with popular AI agent frameworks like [Aider](https://github.com/paul-gauthier/aider), [Goose](https://block.github.io/goose/), or [Cline](https://github.com/jhillyerd/cline). Let them handle complex goals while `code2prompt` delivers the perfect code context.\n\n## Why Code2prompt Matters ✊\n\nAs LLMs evolve and context windows expand, it might seem like purely brute-forcing entire repositories into prompts is enough. However, **token costs** and **prompt coherence** remain significant roadblocks for small companies and developers. Focusing on just the code that matters, `code2prompt` keeps your LLM usage efficient, cost-effective, and less prone to hallucination.\n\n**In short:**\n\n- **Reduce hallucinations** by providing the right amount of context\n- **Reduce token-usage** costs by manually curating the proper context needed\n- **Improve LLM performance** by giving the right amount of context\n- Integrates the agentic stack as a context feeder for text repositories\n\n## You can join It's Open Source! 🌐\n\nEvery new contributor is welcome! Come aboard if you're interested in Rust, forging innovative AI tools, or simply want a better workflow for your code-based prompts.\n\nThanks for reading, and I hope my story inspired you to check out code2prompt. It's been an incredible journey, and it's just getting started!\n\n**Olivier D'Ancona**\n"
  },
  {
    "path": "website/src/content/docs/de/blog/2025.04.11_why_I_wrote_code2prompt.mdx",
    "content": "---\ntitle: \"Warum ich Code2Prompt entwickelt habe\"\ndate: 2025-04-11\nlastUpdated: 2025-04-11\ntags:\n  - open-source\n  - code2prompt\n  - KI\n  - Agent\nexcerpt: \"Die Geschichte hinter code2prompt: meine Open-Source-Suche nach Lösungen für Kontext-Herausforderungen in LLM-Workflows\"\nauthors:\n  - ODAncona\ncover:\n  alt: \"Eine Illustration von code2prompt, das den Code-Kontext für KI-Agenten optimiert.\"\n  image: \"/src/assets/logo_dark_v0.0.2.svg\"\nfeatured: false\ndraft: false\n---\n\n## Einführung\n\nIch bin seit jeher fasziniert davon, wie große Sprachmodelle (LLMs) die Codierungs-Workflows verändern - sei es durch die Generierung von Tests, Docstrings oder sogar ganzen Features in Minuten. Aber als ich diese Modelle weiterentwickelte, traten einige kritische Schmerzpunkte auf:\n\n| Planungsprobleme | Hohe Token-Kosten | Halluzinationen |\n| ---------------- | ----------------- | --------------- |\n| 🧠 ➡️ 🤯         | 🔥 ➡️ 💸          | 💬 ➡️ 🌀        |\n\nDeshalb begann ich, mich mit `code2prompt` zu beschäftigen, einem Rust-basierten Tool, das dabei hilft, den richtigen Kontext für LLMs bereitzustellen.\n\nIn diesem Beitrag teile ich meine Reise und erkläre, warum ich davon überzeugt bin, dass `code2prompt` heute relevant ist und sich so gut integrieren lässt, und warum es zu meiner bevorzugten Lösung für bessere, schnellere KI-Codierungs-Workflows geworden ist.\n\n## Meine ersten Schritte mit LLMs 👣\n\nIch begann im November 2023 mit Experimenten mit LLMs auf `OpenAI Playground` mit `text-davinci-003`. Die Sprachmodelle ermöglichten eine neue Revolution. Es fühlte sich an, als hätte ich einen brillanten neuen Assistenten, der auf Kommando Unit-Tests und Docstrings erstellen konnte. Ich genoss es, die Modelle an ihre Grenzen zu bringen - von Small Talk und ethischen Dilemmata bis hin zu Jailbreaks und komplexen Codierungsaufgaben. Als ich jedoch an umfangreicheren Projekten arbeitete, erkannte ich schnell, dass die Modelle offensichtliche Einschränkungen aufwiesen. Zunächst konnte ich nur wenige hundert Zeilen Code in das Kontextfenster einfügen, und selbst dann hatten die Modelle oft Schwierigkeiten, den Zweck oder die Struktur des Codes zu verstehen. Deshalb erkannte ich schnell, dass der Kontext von größter Bedeutung war. Je präziser meine Anweisungen waren und je besser der Kontext, desto besser die Ergebnisse.\n\n![OpenAI Playground](/assets/blog/post1/playground.png)\n\n## Modell-Evolution 🏗️\n\nDie Modelle konnten beeindruckende Ergebnisse liefern, aber oft hatten sie Schwierigkeiten mit größeren Codebasen oder komplexen Aufgaben. Ich fand mich immer wieder dabei, mehr Zeit für die Erstellung von Prompts aufzuwenden als tatsächlich zu codieren. Gleichzeitig verbesserten sich die Modelle mit der Veröffentlichung neuer Versionen. Sie erhöhten ihre Denkfähigkeiten und Kontextgröße, boten neue Perspektiven und Möglichkeiten. Ich konnte fast zweitausend Zeilen Code in das Kontextfenster einfügen, und die Ergebnisse verbesserten sich. Ich konnte ganze Features in wenigen Iterationen schreiben, und ich war beeindruckt davon, wie schnell ich Ergebnisse erzielen konnte. Ich war überzeugt, dass LLMs die Zukunft des Codierens waren, und ich wollte Teil dieser Revolution sein. Ich bin fest davon überzeugt, dass KI uns nicht ersetzen wird, sondern uns als Assistenten unterstützen wird, während Menschen immer noch die Experten sind.\n\n## Meine ersten Projekte mit LLMs 🚀\n\nIch begann, ein `ROS`-Pathfinding-Modul für einen Roboter-Wettbewerb zu schreiben, generierte Features für eine saubere Architektur-`Flutter`-Cross-Plattform-App und entwickelte eine kleine Web-App, um meine Ausgaben in `Next.js` zu verfolgen. Die Tatsache, dass ich diese kleine App an einem Abend in einem Framework, das ich noch nie zuvor verwendet hatte, erstellte, war ein gamechanger für mich; LLMs waren nicht nur Werkzeuge, sondern Multiplikatoren. Ich entwickelte `bboxconverter`, ein Paket zum Konvertieren von Bounding-Boxes, und vieles mehr. LLMs können Ihnen helfen, neue Technologien und Frameworks schnell zu erlernen; das ist großartig.\n\n## Ein neues Paradigma: Software 3.0 💡\n\nIch tauchte tiefer in LLMs ein und begann, Agenten und Scaffoldings darum herum zu bauen. Ich reproduzierte das berühmte Paper [RestGPT](https://restgpt.github.io/). Die Idee ist großartig: Geben Sie LLMs die Möglichkeit, einige REST-APIs mit einer OpenAPI-Spezifikation aufzurufen, wie z.B. `Spotify` oder `TMDB`. Diese Fähigkeiten führen ein neues Software-Programmierparadigma ein, das ich **Software 3.0** nenne.\n\n| Software 1.0 | Software 2.0   | Software 3.0 |\n| ------------ | -------------- | ------------ |\n| Regelbasiert | Datengesteuert | Agentisch    |\n\nDie gleiche Idee trieb das [MCP](https://modelcontextprotocol.io/introduction)-Protokoll voran, das es LLMs ermöglicht, Tools und Ressourcen direkt auf eine nahtlose Weise aufzurufen, da das Tool per Design eine Beschreibung benötigt, um vom LLM aufgerufen zu werden, im Gegensatz zu REST-APIs, die nicht unbedingt eine OpenAPI-Spezifikation erfordern.\n\n## Die Einschränkungen von LLMs 🧩\n\n### Halluzinationen 🌀\n\nWährend ich das berühmte Paper `RESTGPT` reproduzierte, bemerkte ich einige schwerwiegende Einschränkungen von LLMs. Die Autoren des Papiers begegneten den gleichen Problemen wie ich: LLMs **halluzinierten**. Sie generierten Code, der nicht implementiert war, erfanden Argumente und folgten einfach den Anweisungen buchstäblich, ohne gesunden Menschenverstand zu verwenden. Zum Beispiel fragten die Autoren in [dem Caller-Prompt](https://github.com/Yifan-Song793/RestGPT/blob/main/model/caller.py).\n\n> \"Nicht clever werden und Schritte erfinden, die nicht im Plan existieren.\"\n\nIch fand diese Aussage lustig und sehr interessant, weil es das erste Mal war, dass ich jemanden sah, der LLMs anwies, nicht zu halluzinieren.\n\n### Begrenzte Kontextgröße 📏\n\nEine weitere Einschränkung war die Kontextgröße; LLMs funktionieren gut beim Finden der Nadel im Heuhaufen, aber haben Schwierigkeiten, einen Sinn daraus zu machen. Wenn Sie den Sprachmodellen zu viel Kontext geben, tendieren sie dazu, sich in den Details zu verlieren und die Übersicht zu verlieren, was ärgerlich ist und ständige Steuerung erfordert. Die Art und Weise, wie ich darüber nachdenke, ist ähnlich wie bei [dem Fluch der Dimensionalität](https://towardsdatascience.com/curse-of-dimensionality-a-curse-to-machine-learning-c122ee33bfeb/). Ersetzen Sie das Wort \"Dimension\" oder \"Feature\" durch \"Kontext\", und Sie erhalten die Idee.\n\n![Fluch der Dimensionalität](/assets/blog/post1/curse_of_dimensionality.png)\n\nJe mehr Kontext Sie dem LLM geben, desto schwieriger ist es, die richtige Antwort zu finden. Ich kam auf einen schönen Satz, um diese Idee zusammenzufassen:\n\n> Stellen Sie so wenig Kontext wie möglich, aber so viel wie nötig bereit.\n\nDies ist stark inspiriert von dem berühmten [Zitat von Alain Berset](https://www.lematin.ch/story/alain-berset-la-formule-qui-defie-le-temps-166189802108), einem Schweizer Politiker 🇨🇭, der während des COVID-19-Lockdowns sagte:\n\n> \"Wir möchten so schnell wie möglich handeln, aber auch so langsam wie nötig.\"\n\nDies repräsentiert die Idee des Kompromisses und gilt für die Kontextgröße von LLMs!\n\n## Die Suche nach einem besseren Weg: code2prompt 🔨\n\nDeshalb benötigte ich eine Möglichkeit, meinen Code-Kontext schnell zu laden, zu filtern und zu organisieren, indem ich die kleinstmögliche Menge an Kontext mit der besten Qualität bereitstellte. Ich versuchte, Dateien oder Code-Snippets manuell in Prompts zu kopieren, aber das wurde unhandlich und fehleranfällig. Ich wusste, dass die Automatisierung des mühsamen Prozesses der Kontextgestaltung, um bessere Prompts zu stellen, hilfreich sein würde. Dann gab ich eines Tages \"code2prompt\" in Google ein, in der Hoffnung, ein Tool zu finden, das meinen Code direkt in Prompts einspeist.\n\nUnd tatsächlich entdeckte ich ein **Rust-basiertes Projekt** von [Mufeed](https://www.reddit.com/r/rust/comments/1bghroh/i_made_code2prompt_a_cli_tool_to_convert_your/) namens _code2prompt_, das etwa 200 Sterne auf GitHub hatte. Es war damals noch einfach: ein einfaches CLI-Tool mit grundlegender Filterkapazität und Vorlagen. Ich sah enormes Potenzial und sprang direkt ein, um beizutragen, implementierte unter anderem die glob-Musterübereinstimmung und wurde bald zum Hauptmitarbeiter.\n\n## Vision & Integrationen 🔮\n\nHeute gibt es mehrere Möglichkeiten, Kontext für LLMs bereitzustellen. Generierung aus dem größeren Kontext, Verwendung von Retrieval-Augmented Generation (RAG), [Komprimierung des Codes](https://www.all-hands.dev/blog/openhands-context-condensensation-for-more-efficient-ai-agents) oder sogar Verwendung einer Kombination dieser Methoden. Kontextgestaltung ist ein heißes Thema, das sich in den kommenden Monaten schnell entwickeln wird. Mein Ansatz ist jedoch **KISS**: Keep It Simple, Stupid. Die beste Möglichkeit, Kontext für LLMs bereitzustellen, besteht darin, die einfachste und effizienteste Methode zu verwenden. Sie gestalten genau den Kontext, den Sie benötigen; es ist deterministisch, im Gegensatz zu RAG.\n\nDeshalb beschloss ich, `code2prompt` weiterzuentwickeln, als ein einfaches Tool, das in jedem Workflow verwendet werden kann. Ich wollte es einfach zu bedienen, einfach zu integrieren und einfach zu erweitern machen. Deshalb fügte ich neue Möglichkeiten hinzu, mit dem Tool zu interagieren.\n\n- **Core**: Der Core von `code2prompt` ist eine Rust-Bibliothek, die die grundlegende Funktionalität bietet, um Kontext aus Ihrem Code-Bestand zu gestalten. Sie enthält eine einfache API, um Ihren Code-Kontext zu laden, zu filtern und zu organisieren.\n- **CLI:** Die Kommandozeilen-Schnittstelle ist die einfachste Möglichkeit, `code2prompt` zu verwenden. Sie können Kontext aus Ihrem Code-Bestand gestalten und direkt in Ihre Prompts einspeisen.\n- **Python-API:** Die Python-API ist eine einfache Wrapper-Funktion um die CLI, die es Ihnen ermöglicht, `code2prompt` in Ihren Python-Skripten und Agenten zu verwenden. Sie können Kontext aus Ihrem Code-Bestand gestalten und direkt in Ihre Prompts einspeisen.\n- **MCP**: Der `code2prompt`-MCP-Server ermöglicht es LLMs, `code2prompt` als Tool zu verwenden, und macht sie dadurch in der Lage, Kontext zu gestalten.\n\nDie Vision wird auf der [Vision-Seite](/docs/vision) im Detail beschrieben.\n\n## Integration mit Agenten 👤\n\nIch bin davon überzeugt, dass zukünftige Agenten eine Möglichkeit benötigen, Kontext zu verdauen, und `code2prompt` ist die einfache und effiziente Möglichkeit, dies für Text-Repositorys wie Code-Bestand, Dokumentation oder Notizen zu tun. Ein typischer Ort, an dem `code2prompt` verwendet werden kann, ist in einem Code-Bestand mit sinnvollen Benennungskonventionen. Zum Beispiel gibt es in einer sauberen Architektur eine klare Trennung von Belangen und Schichten. Der relevante Kontext befindet sich normalerweise in verschiedenen Dateien und Ordnern, teilt aber denselben Namen. Dies ist ein perfektes Anwendungsbeispiel für `code2prompt`, bei dem Sie die glob-Musterübereinstimmung verwenden können, um die relevanten Dateien zu erfassen.\n\n**Glob-Muster zuerst:** Wählen Sie Dateien präzise aus oder schließen Sie sie aus mit minimalem Aufwand.\n\nDarüber hinaus ist die Core-Bibliothek als zustandsbehafteter Kontext-Manager konzipiert, der es Ihnen ermöglicht, Dateien hinzuzufügen oder zu entfernen, während Ihre Unterhaltung mit dem LLM fortschreitet. Dies ist besonders nützlich, wenn Sie Kontext für eine bestimmte Aufgabe oder ein bestimmtes Ziel bereitstellen. Sie können Dateien leicht hinzufügen oder entfernen, ohne den Prozess neu zu starten.\n\n**Zustandsbehafteter Kontext:** Fügen Sie Dateien hinzu oder entfernen Sie sie, während Ihre Unterhaltung mit dem LLM fortschreitet.\n\nDiese Fähigkeiten machen `code2prompt` zu einem perfekten Fit für agentenbasierte Workflows. Der MCP-Server ermöglicht eine nahtlose Integration mit beliebten KI-Agenten-Frameworks wie [Aider](https://github.com/paul-gauthier/aider), [Goose](https://block.github.io/goose/) oder [Cline](https://github.com/jhillyerd/cline). Lassen Sie sie komplexe Ziele bearbeiten, während `code2prompt` den perfekten Code-Kontext liefert.\n\n## Warum Code2prompt wichtig ist ✊\n\nWenn LLMs sich weiterentwickeln und Kontextfenster expandieren, könnte es scheinen, als ob das bloße Brute-Forcen ganzer Repositorys in Prompts ausreicht. Allerdings bleiben **Token-Kosten** und **Prompt-Kohärenz** erhebliche Hindernisse für kleine Unternehmen und Entwickler. Indem Sie sich auf den relevanten Code konzentrieren, hält `code2prompt` Ihre LLM-Nutzung effizient, kosteneffektiv und weniger anfällig für Halluzinationen.\n\n**In Kürze:**\n\n- **Reduzieren Sie Halluzinationen**, indem Sie den richtigen Kontext bereitstellen\n- **Reduzieren Sie Token-Verbrauchs**-Kosten, indem Sie den richtigen Kontext manuell kuratieren\n- **Verbessern Sie die LLM-Leistung**, indem Sie den richtigen Kontext bereitstellen\n- Integriert die agentische Stack als Kontext-Feeder für Text-Repositorys\n\n## Sie können sich anschließen! 🌐\n\nJeder neue Mitwirkende ist willkommen! Kommen Sie an Bord, wenn Sie an Rust, der Gestaltung innovativer KI-Tools oder einfach nur an einem besseren Workflow für Ihre Code-basierten Prompts interessiert sind.\n\nVielen Dank für das Lesen, und ich hoffe, meine Geschichte hat Sie inspiriert, code2prompt zu überprüfen. Es war eine unglaubliche Reise, und sie fängt gerade erst an!\n\n**Olivier D'Ancona**\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/explanations/glob_pattern_filter.mdx",
    "content": "---\ntitle: Wie der Glob-Musterfilter funktioniert\ndescription: Wie Code2Prompt entscheidet, welche Dateien mit Include- (-i) und Exclude- (-e) Glob-Mustern beibehalten oder verworfen werden.\n---\n\nCode2Prompt verwendet Glob-Muster, um Dateien und Verzeichnisse einzuschließen oder auszuschließen, ähnlich wie Tools wie tree oder grep. Es ermöglicht Ihnen, zwei unabhängige _Listen_ von Glob-Mustern zu übergeben:\n\n- **Include-Liste** (`--include` oder `-i`) - \"Diese Muster erlauben Dateien\"\n- **Exclude-Liste** (`--exclude` oder `-e`) - \"Diese Muster verbieten Dateien\"\n\nCode2prompt muss für jede Datei im Projekt entscheiden, ob sie beibehalten oder verworfen wird. Diese Seite erklärt die Regeln und die dahinter stehenden Design-Entscheidungen.\n\n---\n\n## 1. Mengen und Symbole\n\nWährend der Erklärung verwenden wir die übliche Mengennotation\n\n| Symbol                            | Bedeutung                                                        |\n| --------------------------------- | ---------------------------------------------------------------- |\n| $A$                               | Menge der Dateien, die **mindestens ein** Include-Muster treffen |\n| $B$                               | Menge der Dateien, die **mindestens ein** Exclude-Muster treffen |\n| $\\Omega$                          | der gesamte Projektbaum (das _Universum_)                        |\n| $C = A \\cap B$                    | Dateien, die beide Listen treffen (die _Überschneidung_)         |\n| $D = \\Omega \\setminus (A \\cup B)$ | Dateien, die keine Liste treffen                                 |\n\n---\n\n## 2. Vier Situationen\n\n### Übersicht der vier Situationen\n\n| Include-Liste | Exclude-Liste | Behaltene Dateien |\n| ------------- | ------------- | ----------------- |\n| A = ∅         | B = ∅         | Ω                 |\n| A = ∅         | B ≠ ∅         | ¬B                |\n| A ≠ ∅         | B = ∅         | A                 |\n| A ≠ ∅         | B ≠ ∅         | A \\ B             |\n\n1. **Keine Include-Liste, keine Exclude-Liste**\n\n   Wenn keine Muster angegeben sind, werden alle Dateien beibehalten (`Ω`).\n\n2. **Nur Exclude-Liste**\n\n   In diesem Fall fungiert Code2Prompt als Blacklist und entfernt Dateien, die den ausgeschlossenen Mustern entsprechen (` Ω \\ B = ¬B`).\n\n3. **Nur Include-Liste**\n\n   Wenn nur eine Include-Liste angegeben ist, fungiert Code2Prompt als Whitelist und behält nur Dateien, die den eingeschlossenen Mustern entsprechen (`A`).\n\n4. **Include- _und_ Exclude-Listen**\n\n   Wenn beide Listen angegeben sind, behält Code2Prompt Dateien, die den Include-Mustern entsprechen, entfernt aber diejenigen, die den Exclude-Mustern entsprechen (`A \\ B`).\n\n---\n\n## 3. Mehr über die Überschneidung\n\nMit beiden Listen vorhanden (`A ≠ ∅`, `B ≠ ∅`) haben Sie vier logische Möglichkeiten\nfür die Überschneidung `C` und den Rest `D`.\n\n| `C` gewünscht? | `D` gewünscht? | Vernünftig?                                                               |\n| -------------- | -------------- | ------------------------------------------------------------------------- |\n| Nein           | Nein           | Standardverhalten (`A \\ B`)                                               |\n| Ja             | Nein           | Gleiches Verhalten wie Fall 3 (`A`)                                       |\n| Nein           | Ja             | überraschend (\"verwerfe was ich angefordert habe `C`, behalte was nicht\") |\n| Ja             | Ja             | Gleiches Verhalten wie Fall 1 (`Ω`)                                       |\n\nAus diesem Grund wurde die Option `--include-priority` entfernt. Denn es wäre das gleiche Ergebnis, als hätten Sie nur eine Include-Liste (Fall 3).\n\n## 4. Schnelle Referenztabelle\n\n| Behalten möchten…                              | Verwenden            |\n| ---------------------------------------------- | -------------------- |\n| alles                                          | kein `-i`, kein `-e` |\n| alles _außer_ bestimmten Mustern               | nur `-e`             |\n| _nur_ was den Mustern entspricht               | nur `-i`             |\n| was `-i` entspricht, minus was `-e` entspricht | `-i` **und** `-e`    |\n\n---\n\nDieses Design hält das mentale Modell einfach:\n\n- Die Include-Liste ist eine Whitelist, sobald sie existiert.\n- Die Exclude-Liste ist eine darüber gelegte Blacklist.\n- Die Überschneidung wird standardmäßig verworfen\n"
  },
  {
    "path": "website/src/content/docs/de/docs/explanations/glob_patterns.md",
    "content": "---\ntitle: Glob-Muster\ndescription: Eine Einführung in Glob-Muster, die Platzhalterzeichen verwenden, um Dateinamen und -pfade abzugleichen.\n---\n\nGlob-Muster sind eine einfache, aber leistungsstarke Möglichkeit, Dateinamen und -pfade mithilfe von Platzhalterzeichen abzugleichen. Sie werden häufig in Kommandozeilen-Interfaces und Programmiersprachen verwendet, um Mengen von Dateinamen oder Verzeichnissen anzugeben. Hier ist eine Aufschlüsselung der am häufigsten verwendeten Glob-Muster:\n\n## Grundlegende Platzhalter\n\n- `*`: Passt auf eine beliebige Anzahl von Zeichen, einschließlich null Zeichen.\n  - Beispiel: `*.txt` passt auf alle Dateien, die mit `.txt` enden.\n\n- `?`: Passt auf genau ein Zeichen.\n  - Beispiel: `file?.txt` passt auf `file1.txt`, `fileA.txt`, aber nicht auf `file10.txt`.\n\n- `[]`: Passt auf jedes der eingeschlossenen Zeichen.\n  - Beispiel: `file[1-3].txt` passt auf `file1.txt`, `file2.txt`, `file3.txt`.\n\n- `[!]` oder `[^]`: Passt auf jedes Zeichen, das nicht eingeschlossen ist.\n  - Beispiel: `file[!1-3].txt` passt auf `file4.txt`, `fileA.txt`, aber nicht auf `file1.txt`.\n\n## Erweiterte Muster\n\n- `**`: Passt auf eine beliebige Anzahl von Verzeichnissen und Unterverzeichnissen rekursiv.\n  - Beispiel: `**/*.txt` passt auf alle `.txt`-Dateien im aktuellen Verzeichnis und in allen Unterverzeichnissen.\n\n- `{}`: Passt auf jedes der durch Kommas getrennten Muster, die eingeschlossen sind.\n  - Beispiel: `file{1,2,3}.txt` passt auf `file1.txt`, `file2.txt`, `file3.txt`.\n\n## Beispiele\n\n1. **Alle Textdateien in einem Verzeichnis abgleichen:**\n\n   ```sh\n   *.txt\n   ```\n\n2. **Alle Dateien mit einer einzelnen Ziffer vor der Erweiterung abgleichen:**\n\n   ```sh\n   file?.txt\n   ```\n\n3. **Dateien mit den Erweiterungen `.jpg` oder `.png` abgleichen:**\n\n   ```sh\n   *.{jpg,png}\n   ```\n\n4. **Alle `.txt`-Dateien in einem beliebigen Unterverzeichnis abgleichen:**\n\n   ```sh\n   **/*.txt\n   ```\n\n5. **Dateien, die mit `a` oder `b` beginnen und mit `.txt` enden, abgleichen:**\n\n   ```sh\n   {a,b}*.txt\n   ```\n\n## Anwendungsfälle\n\n- **Kommandozeilen-Tools:** Glob-Muster werden umfassend in Kommandozeilen-Tools wie `ls`, `cp`, `mv` und `rm` verwendet, um mehrere Dateien oder Verzeichnisse anzugeben.\n- **Programmiersprachen:** Sprachen wie Python, JavaScript und Ruby unterstützen Glob-Muster für die Dateimatching über Bibliotheken wie `glob` in Python.\n- **Build-Systeme:** Tools wie Makefile verwenden Glob-Muster, um Quelldateien und Abhängigkeiten anzugeben.\n\n## Schlussfolgerung\n\nGlob-Muster bieten eine flexible und intuitive Möglichkeit, Dateinamen und -pfade abzugleichen, was sie für Skripting, Automatisierung und Dateiverwaltungsaufgaben unverzichtbar macht. Das Verständnis und die Nutzung dieser Muster können Ihre Produktivität und Effizienz bei der Handhabung von Dateien und Verzeichnissen erheblich steigern.\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/explanations/tokenizers.md",
    "content": "---\ntitle: Tokenisierung in Code2Prompt\ndescription: Erfahren Sie mehr über Tokenisierung und wie Code2Prompt Text für LLMs verarbeitet.\n---\n\nBei der Arbeit mit Sprachmodellen muss Text in ein Format umgewandelt werden, das das Modell verstehen kann – **Tokens**, die Sequenzen von Zahlen sind. Diese Transformation wird von einem **Tokenizer** durchgeführt.\n\n---\n\n## Was ist ein Tokenizer?\n\nEin Tokenizer konvertiert rohen Text in Tokens, die die Bausteine für die Verarbeitung von Eingaben durch Sprachmodelle sind. Diese Tokens können je nach Design des Tokenizers Wörter, Subwörter oder sogar einzelne Zeichen darstellen.\n\nFür `code2prompt` verwenden wir den **tiktoken**-Tokenizer. Er ist effizient, robust und für OpenAI-Modelle optimiert.\nSie können seine Funktionalität im offiziellen Repository erkunden\n\n👉 [tiktoken GitHub Repository](https://github.com/openai/tiktoken)\n\nWenn Sie mehr über Tokenizer im Allgemeinen erfahren möchten, lesen Sie den\n\n👉 [Mistral Tokenization Guide](https://docs.mistral.ai/guides/tokenization/).\n\n## Implementierung in `code2prompt`\n\nDie Tokenisierung wird mit [`tiktoken-rs`](https://github.com/zurawiki/tiktoken-rs) implementiert. `tiktoken` unterstützt diese Kodierungen, die von OpenAI-Modellen verwendet werden:\n\n| CLI-Argument | Kodierungsname           | OpenAI-Modelle                                                           |\n| ---- | ----------------------- | ------------------------------------------------------------------------- |\n| `cl100k` | `cl100k_base`           | ChatGPT-Modelle, `text-embedding-ada-002`                                  |\n| `p50k` | `p50k_base`             | Code-Modelle, `text-davinci-002`, `text-davinci-003`                       |\n| `p50k_edit` | `p50k_edit`             | Für Edit-Modelle wie `text-davinci-edit-001`, `code-davinci-edit-001` |\n| `r50k` | `r50k_base` (oder `gpt2`) | GPT-3-Modelle wie `davinci`                                               |\n| `gpt2` | `o200k_base`            | GPT-4o-Modelle                                                             |\n\nFür mehr Kontext zu den verschiedenen Tokenizern siehe das [OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/66b988407d8d13cad5060a881dc8c892141f2d5c/examples/How_to_count_tokens_with_tiktoken.ipynb)\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/how_to/filter_files.md",
    "content": "---\ntitle: Filtern von Dateien in Code2Prompt\ndescription: Eine Schritt-für-Schritt-Anleitung zum Einschließen oder Ausschließen von Dateien mithilfe verschiedener Filtermethoden.\n---\n\n\n## Verwendung\n\nGenerieren Sie einen Prompt aus einem Codebasis-Verzeichnis:\n\n```sh\ncode2prompt path/to/codebase\n```\n\nVerwenden Sie eine benutzerdefinierte Handlebars-Vorlagendatei:\n\n```sh\ncode2prompt path/to/codebase -t path/to/template.hbs\n```\n\nFiltern Sie Dateien mithilfe von Glob-Mustern:\n\n```sh\ncode2prompt path/to/codebase --include=\"*.rs,*.toml\"\n```\n\nSchließen Sie Dateien mithilfe von Glob-Mustern aus:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.txt,*.md\"\n```\n\nSchließen Sie Dateien/Ordner aus dem Quellbaum basierend auf Ausschlussmustern aus:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.npy,*.wav\" --exclude-from-tree\n```\n\nZeigen Sie die Tokenanzahl des generierten Prompts an:\n\n```sh\ncode2prompt path/to/codebase --tokens\n```\n\nGeben Sie einen Tokenizer für die Tokenanzahl an:\n\n```sh\ncode2prompt path/to/codebase --tokens --encoding=p50k\n```\n\nUnterstützte Tokenizer: `cl100k`, `p50k`, `p50k_edit`, `r50k_bas`.\n> [!HINWEIS]  \n> Siehe [Tokenizer](#tokenizers) für weitere Details.\n\nSpeichern Sie den generierten Prompt in einer Ausgabedatei:\n\n```sh\ncode2prompt path/to/codebase --output=output.txt\n```\n\nDrucken Sie die Ausgabe als JSON:\n\n```sh\ncode2prompt path/to/codebase --json\n```\n\nDie JSON-Ausgabe hat die folgende Struktur:\n\n```json\n{\n  \"prompt\": \"<Generierter Prompt>\", \n  \"directory_name\": \"codebase\",\n  \"token_count\": 1234,\n  \"model_info\": \"ChatGPT-Modelle, text-embedding-ada-002\",\n  \"files\": []\n}\n```\n\nGenerieren Sie eine Git-Commit-Nachricht (für bereitgestellte Dateien):\n\n```sh\ncode2prompt path/to/codebase --diff -t templates/write-git-commit.hbs\n```\n\nGenerieren Sie eine Pull-Anfrage mit Branch-Vergleich (für bereitgestellte Dateien):\n\n```sh\ncode2prompt path/to/codebase --git-diff-branch 'main, development' --git-log-branch 'main, development' -t templates/write-github-pull-request.hbs\n```\n\nFügen Sie Zeilennummern zu Quellcodeblöcken hinzu:\n\n```sh\ncode2prompt path/to/codebase --line-number\n```\n\nDeaktivieren Sie das Umbrechen von Code innerhalb von Markdown-Codeblöcken:\n\n```sh\ncode2prompt path/to/codebase --no-codeblock\n```\n\n- Übersetzen Sie den Code in eine andere Sprache.\n- Suchen Sie nach Fehlern/Sicherheitslücken.\n- Dokumentieren Sie den Code.\n- Implementieren Sie neue Funktionen.\n\n> Ich habe dies ursprünglich für den persönlichen Gebrauch geschrieben, um das 200K-Kontextfenster von Claude 3.0 zu nutzen, und es hat sich als ziemlich nützlich erwiesen, daher habe ich mich entschieden, es Open-Source zu machen!\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/how_to/install.mdx",
    "content": "---\ntitle: Installation von Code2Prompt\ndescription: Eine umfassende Installationsanleitung für Code2Prompt auf verschiedenen Betriebssystemen.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Steps } from \"@astrojs/starlight/components\";\n\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\n\n<Card title=\"Übersicht über die Anleitung\">\n  Willkommen bei der Installationsanleitung für `Code2Prompt`. Dieses Dokument\n  bietet Schritt-für-Schritt-Anleitungen für die Installation auf verschiedenen\n  Plattformen, einschließlich Windows, macOS und Linux.\n</Card>\n\n**TL;DR**\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n## Voraussetzung\n\nStellen Sie sicher, dass [Rust](https://www.rust-lang.org/tools/install) und Cargo auf Ihrem System installiert sind.\n\n```sh\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\nDies ist die offizielle Methode, um die neueste stabile Version von Rust und Cargo zu installieren.\nStellen Sie sicher, dass Sie Ihre `PATH`-Variable nach der Installation von Rust aktualisieren. Starten Sie Ihr Terminal neu oder führen Sie die vom Installer vorgeschlagenen Anweisungen aus.\n\n```sh\nsource $HOME/.cargo/env\n```\n\nSie können überprüfen, ob alles korrekt installiert ist, indem Sie Folgendes ausführen:\n\n```sh\ncargo --version\ngit --version\n```\n\n## Command Line Interface (CLI) 👨‍💻\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n#### 🧪 Installieren Sie die neueste (unveröffentlichte) Version von GitHub\n\nWenn Sie die neuesten Funktionen oder Fixes vor ihrer Veröffentlichung auf crates.io möchten:\n\n```sh\ncargo install --git https://github.com/mufeedvh/code2prompt\n```\n\n### Quellcode-Build\n\nIdeal für Entwickler, die aus dem Quellcode bauen oder zum Projekt beitragen möchten.\n\n<Steps>\n\n1.  🛠️ Voraussetzungen installieren :\n\n    - [Rust](https://www.rust-lang.org/tools/install) und Cargo\n    - [Git](https://git-scm.com/downloads)\n\n2.  📥 Repository klonen :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt\n    ```\n\n3.  📦 Binary installieren :\n\n    Um aus dem Quellcode zu bauen und zu installieren:\n\n    ```sh\n    cargo install --path crates/code2prompt\n    ```\n\n    Um die Binary ohne Installation zu bauen:\n\n    ```sh\n    cargo build --release\n    ```\n\n    Die Binary ist im `target/release`-Verzeichnis verfügbar.\n\n4.  🚀 Ausführen :\n\n    ```sh\n    code2prompt --help\n    ```\n\n</Steps>\n\n### Binary-Releases\n\nAm besten für Benutzer, die die neueste Version ohne Quellcode-Build verwenden möchten.\n\nLaden Sie die neueste Binary für Ihr Betriebssystem von [Releases](https://github.com/mufeedvh/code2prompt/releases) herunter.\n\n⚠️ Binary-Releases können hinter der neuesten GitHub-Version zurückliegen. Für die neuesten Funktionen sollten Sie den Quellcode-Build in Betracht ziehen.\n\n### AUR\n\nSpeziell für Arch Linux-Benutzer ist `code2prompt` im AUR verfügbar.\n\n`code2prompt` ist im [`AUR`](https://aur.archlinux.org/packages?O=0&K=code2prompt) verfügbar. Installieren Sie es mit einem AUR-Helfer.\n\n```sh\nparu/yay -S code2prompt\n```\n\n### Nix\n\nWenn Sie Nix verwenden, können Sie es mit nix-env oder nix profile installieren.\n\n```sh\n# ohne Flakes:\nnix-env -iA nixpkgs.code2prompt\n# mit Flakes:\nnix profile install nixpkgs#code2prompt\n```\n\n## Software Development Kit (SDK) 🐍\n\n### Pypi\n\nSie können die Python-Bindings von Pypi herunterladen\n\n```sh\npip install code2prompt_rs\n```\n\n### Quellcode-Build\n\n<Steps>\n\n1.  🛠️ Voraussetzungen installieren :\n\n    - [Rust](https://www.rust-lang.org/tools/install) und Cargo\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 Repository klonen :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt/crates/code2prompt-python\n    ```\n\n3.  📦 Abhängigkeiten installieren :\n\n    Der `rye`-Befehl erstellt eine virtuelle Umgebung und installiert alle Abhängigkeiten.\n\n    ```sh\n    rye sync\n    ```\n\n4.  ⚙️ Paket bauen :\n\n    Sie werden das Paket in der virtuellen Umgebung im `.venv`-Verzeichnis entwickeln.\n\n    ```sh\n    rye run maturin develop -r\n    ```\n\n</Steps>\n\n## Model Context Protocol (MCP) 🤖\n\n### Automatische Installation\n\nDer `code2prompt`-MCP-Server wird bald in MCP-Registries verfügbar sein.\n\n### Manuelle Installation\n\nDer `code2prompt`-MCP-Server ist noch ein Prototyp und wird bald in das Haupt-Repository integriert.\n\nUm den MCP-Server lokal auszuführen und mit `Cline`, `Goose` oder `Aider` zu verwenden:\n\n<Steps>\n\n1.  🛠️ Voraussetzungen installieren :\n\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 Repository klonen :\n\n    ```sh\n    git clone https://github.com/odancona/code2prompt-mcp.git\n    cd code2prompt-mcp\n    ```\n\n3.  📦 Abhängigkeiten installieren :\n\n    Der `rye`-Befehl erstellt eine virtuelle Umgebung und installiert alle Abhängigkeiten im `.venv`-Verzeichnis.\n\n    ```sh\n    rye sync\n    ```\n\n4.  🚀 Server ausführen :\n\n    Der MCP-Server ist jetzt installiert. Sie können ihn mit:\n\n    ```sh\n    . .venv/bin/activate\n    python -m src/code2prompt_mcp/main.py\n    ```\n\n5.  🔌 Mit Agenten integrieren :\n\n            Zum Beispiel können Sie ihn mit `Cline` mit einer ähnlichen Konfiguration integrieren:\n\n            ```json\n            {\n              \"mcpServers\": {\n                \"code2prompt\": {\n                  \"command\": \"bash\",\n                  \"args\": [\n                    \"-c\",\n                    \"cd /home/olivier/projet/code2prompt-mcp && rye run python /home/olivier/projet/code2prompt-mcp/src/code2prompt_mcp/main.py\"\n                  ],\n                  \"env\": {}\n                }\n              }\n            }\n            ```\n\n</Steps>\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/how_to/ssh.md",
    "content": "---\ntitle: Verwenden Sie Code2prompt CLI mit SSH\ndescription: Eine Anleitung zur Verwendung von Code2Prompt CLI mit SSH für die Remote-Codebasis-Analyse.\n---\n\n## Warum funktioniert es nicht?\n\nWenn Sie versuchen, die `code2prompt`-CLI auf einem Remote-Server über SSH auszuführen, kann der Befehl den Clipboard nicht finden. Dies liegt daran, dass die `code2prompt`-CLI den Clipboard verwendet, um die generierte Eingabeaufforderung zu kopieren, und SSH-Sitzungen normalerweise keinen Zugriff auf den lokalen Clipboard haben.\n\n## Lösung\n\nUm die `code2prompt`-CLI mit SSH zu verwenden, können Sie die Ausgabe in eine Datei umleiten, anstatt sie in den Clipboard zu kopieren. Auf diese Weise können Sie immer noch die Eingabeaufforderung generieren und für die spätere Verwendung speichern.\n\nVerwenden Sie die Option `--output-file`, um die Ausgabedatei anzugeben, in der die generierte Eingabeaufforderung gespeichert wird. Zum Beispiel:\n\n```sh\nssh user@remote-server \"code2prompt path/to/codebase -O output.txt\"\n```\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/references/command_line_options.md",
    "content": "---\ntitle: Code2Prompt-Befehlszeilenoptionen\ndescription: Ein Referenzhandbuch für alle verfügbaren CLI-Optionen in Code2Prompt.\n---\n\n# Befehlszeilenoptionen\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/references/default_template.md",
    "content": "---\ntitle: Standardvorlage für Code2Prompt\ndescription: Erfahren Sie mehr über die Standardvorlagestruktur, die in Code2Prompt verwendet wird.\n---\n\n# Standardvorlage\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/tutorials/getting_started.mdx",
    "content": "---\ntitle: Erste Schritte mit Code2Prompt\ndescription: Ein umfassendes Tutorial, das die Kernfunktionalität von Code2Prompt und seine Verwendung in CLI-, SDK- und MCP-Integrationen vorstellt.\n---\n\nimport { Aside } from \"@astrojs/starlight/components\";\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\n\n<Card title=\"Tutorial-Übersicht\">\n  Willkommen bei Code2Prompt! Dieses Tutorial bietet eine umfassende Einführung\n  in die Verwendung von Code2Prompt zur Generierung von KI-bereiten Prompts aus\n  Ihren Codebasen. Wir werden seine Kernfunktionalität erkunden und seine\n  Verwendung in verschiedenen Integrationsmethoden demonstrieren: Command Line\n  Interface (CLI), Software Development Kit (SDK) und Model Context Protocol\n  (MCP).\n</Card>\n\n## Was ist Code2Prompt?\n\nCode2Prompt ist ein vielseitiges Tool, das entwickelt wurde, um die Lücke zwischen Ihrer Codebasis und großen Sprachmodellen (LLMs) zu schließen. Es extrahiert intelligent relevante Code-Snippets, wendet leistungsstarke Filterung an und formatiert die Informationen in strukturierte Prompts, die für die LLM-Verarbeitung optimiert sind. Dies vereinfacht Aufgaben wie Code-Dokumentation, Fehlererkennung, Refaktorisierung und mehr.\n\nCode2Prompt bietet verschiedene Integrationspunkte:\n\n<Tabs>\n  <TabItem label=\"Core\" icon=\"seti:rust\">\n    Eine Core-Rust-Bibliothek, die die Grundlage für Code-Ingestion und\n    Prompt-Bearbeitung bietet.\n  </TabItem>\n  <TabItem label=\"CLI\" icon=\"seti:powershell\">\n    Eine benutzerfreundliche Kommandozeilen-Schnittstelle für schnelle\n    Prompt-Generierung. Ideal für interaktive Verwendung und einmalige Aufgaben.\n  </TabItem>\n  <TabItem label=\"SDK\" icon=\"seti:python\">\n    Ein leistungsstarkes Software Development Kit (SDK) für nahtlose Integration\n    in Ihre Python-Projekte. Perfekt für die Automatisierung von\n    Prompt-Generierung innerhalb größerer Workflows.\n  </TabItem>\n  <TabItem label=\"MCP\" icon=\"seti:db\">\n    Ein Model Context Protocol (MCP)-Server für die erweiterte Integration mit\n    LLM-Agenten. Ermöglicht anspruchsvolle, Echtzeit-Interaktionen mit Ihrer\n    Codebasis.\n  </TabItem>\n</Tabs>\n\n## 📥 Installation\n\nFür detaillierte Installationsanweisungen für alle Methoden (CLI, SDK, MCP) lesen Sie bitte die umfassende [Installationsanleitung](/../docs/how_to/install).\n\n## 🏁 Generierung von Prompts: Ein CLI-Beispiel\n\nBeginnen wir mit einem einfachen Beispiel mit der CLI. Erstellen Sie ein Beispielprojekt:\n\n```bash\nmkdir -p my_project/{src,tests}\ntouch my_project/src/main.rs my_project/tests/test_1.rs\necho 'fn main() { println!(\"Hello, world!\"); }' > my_project/src/main.rs\n```\n\nGenerieren Sie nun einen Prompt:\n\n```bash\ncode2prompt my_project\n```\n\nDies kopiert einen Prompt in Ihre Zwischenablage. Sie können dies anpassen:\n\n- **Filterung:** `code2prompt my_project --include=\"*.rs\" --exclude=\"tests/*\"` (enthält nur `.rs`-Dateien, schließt `tests`-Verzeichnis aus)\n- **Ausgabedatei:** `code2prompt my_project --output-file=my_prompt.txt`\n- **JSON-Ausgabe:** `code2prompt my_project -O json` (strukturierte JSON-Ausgabe)\n- **Benutzerdefinierte Vorlagen:** `code2prompt my_project -t my_template.hbs` (benötigt die Erstellung von `my_template.hbs`)\n\nSiehe die Tutorials [Lernen Sie Kontextfilterung](/../docs/tutorials/learn_filters) und [Lernen Sie Handlebar-Vorlagen](/../docs/tutorials/learn_templates), um mehr über die erweiterten Verwendungen zu erfahren.\n\n## 🐍 SDK-Integration (Python)\n\nFür die programmgesteuerte Kontrolle verwenden Sie das Python-SDK:\n\n```python\nfrom code2prompt_rs import Code2Prompt\n\nconfig = {\n    \"path\": \"my_project\",\n    \"include_patterns\": [\"*.rs\"],\n    \"exclude_patterns\": [\"tests/*\"],\n}\n\nc2p = Code2Prompt(**config)\nprompt = c2p.generate_prompt()\nprint(prompt)\n```\n\nDies erfordert die Installation des SDK (`pip install code2prompt_rs`). Lesen Sie die SDK-Dokumentation für weitere Details.\n\n## 🤖 MCP-Server-Integration (Erweitert)\n\nFür die erweiterte Integration mit LLM-Agenten führen Sie den `code2prompt`-MCP-Server aus (siehe Installationsanleitung für Details). Dies ermöglicht Agenten, den Code-Kontext dynamisch anzufordern. Dies ist eine erweiterte Funktion, und weitere Dokumentationen sind auf der Projekt-Website verfügbar.\n\n<Card title=\"Nächste Schritte\">\n  Erkunden Sie die erweiterten Tutorials und Dokumentationen, um die Fähigkeiten\n  von Code2Prompt zu beherrschen und es in Ihre Workflows zu integrieren.\n</Card>\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/tutorials/learn_filters.mdx",
    "content": "---\ntitle: Lernen Sie Kontextfilterung mit Code2Prompt\ndescription: Lernen Sie, wie Sie Dateien in Ihren LLM-Eingaben mit leistungsstarken Filteroptionen ausschließen oder einschließen können.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"Tutorial-Überblick\">\n  Dieses Tutorial zeigt, wie Sie das **Glob-Muster-Tool** in der\n  `code2prompt`-CLI verwenden, um Dateien basierend auf Einschluss- und\n  Ausschlussmustern zu filtern und zu verwalten.\n</Card>\n\nGlob-Muster funktionieren ähnlich wie Tools wie `tree` oder `grep` und bieten leistungsstarke Filterfunktionen. Weitere Informationen finden Sie in der [detaillierten Erklärung](/docs/explanations/glob_patterns).\n\n---\n\n## Voraussetzungen\n\nStellen Sie sicher, dass Sie `code2prompt` installiert haben. Wenn Sie es noch nicht installiert haben, lesen Sie die [Installationsanleitung](/docs/how_to/install).\n\n---\n\n## Verständnis von Einschluss- und Ausschlussmustern\n\nGlob-Muster ermöglichen es Ihnen, Regeln für die Filterung von Dateien und Verzeichnissen anzugeben.\n\n- **Einschlussmuster** (`--include`): Geben Sie Dateien und Verzeichnisse an, die Sie einschließen möchten.\n- **Ausschlussmuster** (`--exclude`): Geben Sie Dateien und Verzeichnisse an, die Sie ausschließen möchten.\n- **Priorität** (`--include-priority`): Löst Konflikte zwischen Einschluss- und Ausschlussmustern.\n\n---\n\n## Einrichtung der Umgebung\n\nUm mit Glob-Mustern zu üben, erstellen wir eine Beispieldatenstruktur mit einigen Dateien.\n\n### Bash-Script zum Erstellen der Teststruktur\n\nFühren Sie dieses Skript aus, um eine temporäre Verzeichnisstruktur zu erstellen:\n\n```bash\n#!/bin/bash\n\n# Erstelle Basisverzeichnis\nmkdir -p test_dir/{lowercase,uppercase,.secret}\n\n# Erstelle Dateien in der Struktur\necho \"content foo.py\" > \"test_dir/lowercase/foo.py\"\necho \"content bar.py\" > \"test_dir/lowercase/bar.py\"\necho \"content baz.py\" > \"test_dir/lowercase/baz.py\"\necho \"content qux.txt\" > \"test_dir/lowercase/qux.txt\"\necho \"content corge.txt\" > \"test_dir/lowercase/corge.txt\"\necho \"content grault.txt\" > \"test_dir/lowercase/grault.txt\"\n\necho \"CONTENT FOO.py\" > \"test_dir/uppercase/FOO.PY\"\necho \"CONTENT BAR.py\" > \"test_dir/uppercase/BAR.PY\"\necho \"CONTENT BAZ.py\" > \"test_dir/uppercase/BAZ.PY\"\necho \"CONTENT QUX.txt\" > \"test_dir/uppercase/QUX.TXT\"\necho \"CONTENT CORGE.txt\" > \"test_dir/uppercase/CORGE.TXT\"\necho \"CONTENT GRAULT.txt\" > \"test_dir/uppercase/GRAULT.TXT\"\n\necho \"top secret\" > \"test_dir/.secret/secret.txt\"\n```\n\nUm die Struktur später zu bereinigen, führen Sie aus:\n\n```bash\nrm -rf test_dir\n```\n\nDies erstellt die folgende Verzeichnisstruktur:\n\nimport { FileTree } from \"@astrojs/starlight/components\";\n\n<FileTree>\n- test_dir \n  - lowercase \n    - foo.py \n    - bar.py \n    - baz.py \n    - qux.txt \n    - corge.txt \n    - grault.txt\n  - uppercase \n    - FOO.py \n    - BAR.py \n    - BAZ.py \n    - QUX.txt \n    - CORGE.txt \n    - GRAULT.txt \n  - .secret \n    - secret.txt\n</FileTree>\n\n---\n\n## Beispiele: Filtern von Dateien mit Einschluss- und Ausschlussmustern\n\n### Fall 1: Kein Einschluss, kein Ausschluss\n\nBefehl:\n\n```bash\ncode2prompt test_dir\n```\n\n#### Ergebnis\n\nAlle Dateien sind eingeschlossen:\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n- `.secret/secret.txt`\n\n---\n\n### Fall 2: Ausschluss bestimmter Dateitypen\n\n`.txt`-Dateien ausschließen:\n\n```bash\ncode2prompt test_dir --exclude=\"*.txt\"\n```\n\n#### Ergebnis\n\nAusschlossen:\n\n- Alle `.txt`-Dateien\n\nEingeschlossen:\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n\n---\n\n### Fall 3: Einschluss bestimmter Dateitypen\n\nNur Python-Dateien einschließen:\n\n```bash\ncode2prompt test_dir --include=\"*.py\"\n```\n\n#### Ergebnis\n\nEingeschlossen:\n\n- Alle `.py`-Dateien\n\nAusschlossen:\n\n- `.secret/secret.txt`\n\n---\n\n### Fall 4: Einschluss und Ausschluss mit Priorität\n\n`.py`-Dateien einschließen, aber Dateien im `uppercase`-Verzeichnis ausschließen:\n\n```bash\ncode2prompt test_dir --include=\"*.py\" --exclude=\"**/uppercase/*\" --include-priority=true\n```\n\n#### Ergebnis\n\nEingeschlossen:\n\n- Alle `lowercase/1`-Dateien mit `.py`-Erweiterung\n\nAusschlossen:\n\n- Alle `uppercase`-Dateien\n- `.secret/secret.txt`\n\n---\n\n## Zusammenfassung\n\nDas Glob-Muster-Tool in `code2prompt` ermöglicht es Ihnen, Dateien und Verzeichnisse effektiv zu filtern, indem Sie:\n\n- `--include` verwenden, um Dateien anzugeben, die eingeschlossen werden sollen\n- `--exclude` verwenden, um Dateien anzugeben, die ausgeschlossen werden sollen\n- `--include-priority` verwenden, um Konflikte zwischen Mustern zu lösen\n\nUm zu üben, erstellen Sie die Beispieldatenstruktur, probieren Sie die Befehle aus und sehen Sie, wie das Tool Dateien dynamisch filtert.\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/tutorials/learn_templates.mdx",
    "content": "---\ntitle: Lernen Sie Handlebar-Vorlagen mit Code2Prompt kennen\ndescription: Verstehen Sie, wie Sie benutzerdefinierte Handlebars-Vorlagen für die Prompt-Generierung verwenden und erstellen.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"Tutorial-Überblick\">\n  Dieses Tutorial zeigt, wie Sie benutzerdefinierte Handlebars-Vorlagen für die\n  Prompt-Generierung im `code2prompt`-CLI verwenden und erstellen.\n</Card>\n\n---\n\n## Voraussetzungen\n\nStellen Sie sicher, dass Sie `code2prompt` installiert haben. Wenn Sie es noch nicht installiert haben, lesen Sie die [Installationsanleitung](/docs/how_to/install).\n\n---\n\n## Was sind Handlebars-Vorlagen?\n\n[Handlebars](https://handlebarsjs.com/) ist eine beliebte Template-Engine, die es ermöglicht, dynamische Vorlagen mit Platzhaltern zu erstellen.\nIn `code2prompt` werden Handlebars-Vorlagen verwendet, um die generierten Prompts basierend auf der Codebasis-Struktur und benutzerdefinierten Variablen zu formatieren.\n\n## Wie werden Handlebars-Vorlagen verwendet?\n\nSie können diese Vorlagen verwenden, indem Sie die `-t`- oder `--template`-Flagge gefolgt vom Pfad zur Vorlagendatei übergeben. Zum Beispiel:\n\n```sh\ncode2prompt path/to/codebase -t templates/document-the-code.hbs\n```\n\n## Vorlage-Syntax\n\nHandlebars-Vorlagen verwenden eine einfache Syntax für Platzhalter und Ausdrücke. Sie platzieren Variablen in doppelten geschweiften Klammern `{{variable_name}}`, um sie in den generierten Prompt aufzunehmen.\n`Code2prompt` bietet eine Reihe von Standardvariablen, die Sie in Ihren Vorlagen verwenden können:\n\n- `absolute_code_path`: Der absolute Pfad zur Codebasis.\n- `source_tree`: Der Quellbaum der Codebasis, der alle Dateien und Verzeichnisse enthält.\n- `files`: Eine Liste von Dateien in der Codebasis, einschließlich ihrer Pfade und Inhalte.\n- `git_diff`: Der Git-Diff der Codebasis, wenn zutreffend.\n- `code`: Der Codeinhalt der aktuell verarbeiteten Datei.\n- `path`: Der Pfad der aktuell verarbeiteten Datei.\n\nSie können auch Handlebars-Helfer verwenden, um bedingte Logik, Schleifen und andere Operationen innerhalb Ihrer Vorlagen auszuführen. Zum Beispiel:\n\n```handlebars\n{{#if files}}\n  {{#each files}}\n    Datei:\n    {{this.path}}\n    Inhalt:\n    {{this.content}}\n  {{/each}}\n{{else}}\n  Keine Dateien gefunden.\n{{/if}}\n```\n\n---\n\n## Vorhandene Vorlagen\n\n`code2prompt` wird mit einer Reihe von integrierten Vorlagen für gängige Anwendungsfälle geliefert. Sie finden sie im [`templates`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates)-Verzeichnis.\n\n### [`document-the-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/document-the-code.hbs)\n\nVerwenden Sie diese Vorlage, um Prompts für die Dokumentation des Codes zu generieren. Sie fügt Dokumentationskommentare zu allen öffentlichen Funktionen, Methoden, Klassen und Modulen in der Codebasis hinzu.\n\n### [`find-security-vulnerabilities.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/find-security-vulnerabilities.hbs)\n\nVerwenden Sie diese Vorlage, um Prompts für die Suche nach potenziellen Sicherheitslücken in der Codebasis zu generieren. Sie sucht nach gängigen Sicherheitsproblemen und bietet Empfehlungen, wie diese behoben oder gemildert werden können.\n\n### [`clean-up-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/clean-up-code.hbs)\n\nVerwenden Sie diese Vorlage, um Prompts für die Bereinigung und Verbesserung der Codequalität zu generieren. Sie sucht nach Möglichkeiten, die Lesbarkeit, Einhaltung von Best Practices, Effizienz, Fehlerbehandlung und mehr zu verbessern.\n\n### [`fix-bugs.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/fix-bugs.hbs)\n\nVerwenden Sie diese Vorlage, um Prompts für die Behebung von Fehlern in der Codebasis zu generieren. Sie hilft bei der Diagnose von Problemen, bietet Vorschläge für die Behebung und aktualisiert den Code mit den vorgeschlagenen Änderungen.\n\n### [`write-github-pull-request.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-pull-request.hbs)\n\nVerwenden Sie diese Vorlage, um eine GitHub-Pull-Request-Beschreibung in Markdown zu erstellen, indem Sie den Git-Diff und den Git-Log von zwei Branches vergleichen.\n\n### [`write-github-readme.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-readme.hbs)\n\nVerwenden Sie diese Vorlage, um eine hochwertige README-Datei für das Projekt zu erstellen, die für die Hosting auf GitHub geeignet ist. Sie analysiert die Codebasis, um ihren Zweck und ihre Funktionalität zu verstehen, und generiert den README-Inhalt im Markdown-Format.\n\n### [`write-git-commit.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-git-commit.hbs)\n\nVerwenden Sie diese Vorlage, um Git-Commits aus den gestagten Dateien in Ihrem Git-Verzeichnis zu generieren. Sie analysiert die Codebasis, um ihren Zweck und ihre Funktionalität zu verstehen, und generiert den Git-Commit-Nachrichtentext im Markdown-Format.\n\n### [`improve-performance.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/improve-performance.hbs)\n\nVerwenden Sie diese Vorlage, um Prompts für die Verbesserung der Leistung der Codebasis zu generieren. Sie sucht nach Optimierungsmöglichkeiten, bietet spezifische Vorschläge und aktualisiert den Code mit den Änderungen.\n\n## Benutzerdefinierte Variablen\n\n`code2prompt` unterstützt die Verwendung von benutzerdefinierten Variablen in den Handlebars-Vorlagen. Alle Variablen in der Vorlage, die nicht Teil des Standardkontexts (`absolute_code_path`, `source_tree`, `files`) sind, werden als benutzerdefinierte Variablen behandelt.\n\nWährend der Prompt-Generierung fordert `code2prompt` den Benutzer auf, Werte für diese benutzerdefinierten Variablen einzugeben. Dies ermöglicht eine weitere Anpassung der generierten Prompts basierend auf der Benutzereingabe.\n\nZum Beispiel, wenn Ihre Vorlage `{{challenge_name}}` und `{{challenge_description}}` enthält, werden Sie aufgefordert, Werte für diese Variablen einzugeben, wenn Sie `code2prompt` ausführen.\n\nDiese Funktion ermöglicht die Erstellung von wiederverwendbaren Vorlagen, die an verschiedene Szenarien basierend auf der Benutzereingabe angepasst werden können.\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/vision.mdx",
    "content": "---\ntitle: Die Vision von Code2Prompt\ndescription: Entdecken Sie die Vision hinter Code2Prompt und wie es die Interaktion von LLM mit Code verbessert.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Aside } from \"@astrojs/starlight/components\";\n\n<Card title=\"Zweck 🎯\">\n  `code2prompt` wurde entwickelt, um Entwicklern und KI-Agenten eine effektivere\n  Interaktion mit Codebasen zu ermöglichen.\n</Card>\n\n## Das Problem 🚩\n\nGroße Sprachmodelle (LLMs) haben die Art und Weise, wie wir mit Code interagieren, revolutioniert. Sie stehen jedoch immer noch vor erheblichen Herausforderungen bei der Codegenerierung:\n\n- **Planung und Argumentation**: LLMs fehlt die Fähigkeit, zu planen und zu argumentieren, was für Aufgaben wie Codegenerierung, Refaktorisierung und Debugging entscheidend ist. Sie haben oft Schwierigkeiten, das große Ganze zu überblicken und sind kurzsichtig.\n- **Kontextgröße**: LLMs haben ein begrenztes Kontextfenster, das ihre Fähigkeit, große Codebasen zu analysieren und zu verstehen, einschränkt.\n- **Halluzination**: LLMs können Code generieren, der korrekt erscheint, aber tatsächlich falsch oder unsinnig ist. Dieses Phänomen, bekannt als Halluzination, tritt auf, wenn das Modell nicht genügend Kontext oder Verständnis der Codebasis hat.\n\nHier kommt `code2prompt` ins Spiel.\n\n## Die Lösung ✅\n\nWir glauben, dass Planung und Argumentation durch menschliche oder KI-Agenten mit Gerüsttechniken erreicht werden können. Diese Agenten müssen einen **hochwertigen Kontext** der Codebasis sammeln, der für die jeweilige Aufgabe gefiltert, strukturiert und formatiert ist.\n\nDie Faustregel lautet:\n\n<Aside type=\"tip\">\n  > So wenig Kontext wie möglich, aber so viel wie nötig bereitstellen\n</Aside>\n\nDies ist praktisch schwierig zu erreichen, insbesondere bei großen Codebasen. `code2prompt` ist jedoch ein einfaches Tool, das Entwicklern und KI-Agenten hilft, Codebasen effektiver zu verdauen.\n\nEs automatisiert den Prozess des Durchquerens einer Codebasis, Filterns von Dateien und Formatierens in strukturierte Prompts, die LLMs verstehen können. Dadurch hilft es, die Herausforderungen der Planung, Argumentation und Halluzination zu mildern.\n\nSie können verstehen, wie `code2prompt` diese Herausforderungen in folgendem Abschnitt angeht.\n\n## Architektur ⛩️\n\n<img\n  src=\"/assets/images/architecture.svg\"\n  alt=\"Architektur von code2prompt\"\n  style=\"width: 75%;\"\n/>\n\n`code2prompt` ist modular konzipiert, um eine einfache Integration in verschiedene Workflows zu ermöglichen. Es kann als Core-Bibliothek, Kommandozeilen-Interface (CLI), Software-Entwicklungskit (SDK) oder sogar als Model Context Protocol (MCP)-Server verwendet werden.\n\n### Core\n\n`code2prompt` ist ein Code-Ingestion-Tool, das den Prozess der Erstellung von LLM-Prompts für Codeanalyse, -generierung und andere Aufgaben optimiert. Es funktioniert, indem es Verzeichnisse durchquert, eine Baumstruktur aufbaut und Informationen über jede Datei sammelt. Die Core-Bibliothek kann leicht in andere Anwendungen integriert werden.\n\n### CLI\n\nDas Kommandozeilen-Interface (CLI) von `code2prompt` wurde für Menschen entwickelt, um Prompts direkt aus Ihrer Codebasis zu generieren. Der generierte Prompt wird automatisch in die Zwischenablage kopiert und kann auch in eine Ausgabedatei gespeichert werden. Darüber hinaus können Sie die Prompt-Generierung mithilfe von Handlebars-Vorlagen anpassen. Schauen Sie sich die bereitgestellten Prompts in der Dokumentation an!\n\n### SDK\n\nDas Software-Entwicklungskit (SDK) von `code2prompt` bietet eine Python-Bindung für die Core-Bibliothek. Dies ist perfekt für KI-Agenten oder Automatisierungsskripte, die nahtlos mit Codebasen interagieren möchten. Das SDK ist auf Pypi gehostet und kann über pip installiert werden.\n\n### MCP\n\n`code2prompt` ist auch als Model Context Protocol (MCP)-Server verfügbar, der es ermöglicht, ihn als lokalen Dienst auszuführen. Dies ermöglicht LLMs auf Steroiden, indem es ihnen ein Tool bereitstellt, um automatisch einen gut strukturierten Kontext Ihrer Codebasis zu sammeln.\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n"
  },
  {
    "path": "website/src/content/docs/de/docs/welcome.mdx",
    "content": "---\ntitle: Code2Prompt-Dokumentation\ndescription: Offizielle Code2prompt-Dokumentation\ntemplate: splash\nhero:\n  tagline: Verwandeln Sie Ihren Code in Sekunden in KI-optimierte Prompts\n  image:\n    file: ../../../../assets/logo_dark_v0.0.1.svg\n  actions:\n    - text: Loslegen 🚀\n      link: /docs/tutorials/getting_started\n    - text: Installation 📥\n      link: /docs/how_to/install\n---\n\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\nimport { LinkCard } from \"@astrojs/starlight/components\";\n\n## Schnellstart\n\n<LinkCard title=\"Loslegen 🚀\" href=\"/docs/tutorials/getting_started\" />\n<LinkCard title=\"Installation 📥\" href=\"/docs/how_to/install\" />\n<LinkCard title=\"Filtern lernen 🔍\" href=\"/docs/tutorials/learn_filters\" />\n<LinkCard title=\"Vorlagen lernen 📝\" href=\"/docs/tutorials/learn_templates\" />\n<LinkCard title=\"Vision 🔮\" href=\"/docs/vision\" />\n\n`code2prompt` ist ein leistungsstarkes Code-Ingestion-Tool, das entwickelt wurde, um Prompts für Code-Analyse, -Generierung und andere Aufgaben zu erstellen. Es funktioniert, indem es Verzeichnisse durchläuft, eine Baumstruktur aufbaut und Informationen über jede Datei sammelt.\n\nEs vereinfacht den Prozess der Kombination und Formatierung von Code, wodurch es leicht wird, Code mit LLMs zu analysieren, zu dokumentieren oder zu refactoren.\n\nSie können `code2prompt` auf folgende Weise verwenden:\n\n<CardGrid>\n  <Card title=\"Core\" icon=\"seti:rust\">\n    Core-Bibliothek blitzschnell für Code-Ingestion\n  </Card>\n  <Card title=\"CLI\" icon=\"seti:powershell\">\n    Kommandozeilen-Interface speziell für Menschen entwickelt\n  </Card>\n  <Card title=\"SDK\" icon=\"seti:python\">\n    Software Development Kit für KI-Agenten und Automatisierungsskripte\n  </Card>\n  <Card title=\"MCP\" icon=\"seti:folder\">\n    Model Context Protocol-Server für LLMs auf Steroiden\n  </Card>\n</CardGrid>\n---\n\n## Hauptfunktionen\n\n- **LLM-Prompts generieren**: Schnell vollständige Codebasen in strukturierte LLM-Prompts umwandeln.\n- **Glob-Muster-Filterung**: Bestimmte Dateien und Verzeichnisse mithilfe von Glob-Mustern ein- oder ausschließen.\n- **Benutzerdefinierte Vorlagen**: Prompt-Generierung mit Handlebars-Vorlagen anpassen.\n- **Token-Zählung**: Token-Verwendung analysieren und für LLMs mit variierenden Kontextfenstern optimieren.\n- **Git-Integration**: Git-Diffs und Commit-Nachrichten in Prompts für Code-Reviews einschließen.\n- **Respektiert `.gitignore`**: Dateien, die in `.gitignore` aufgeführt sind, automatisch ignorieren, um die Prompt-Generierung zu optimieren.\n\n---\n\n## Warum `code2prompt`?\n\n1. **Zeit sparen**:\n\n   - Automatisiert den Prozess der Durchquerung einer Codebasis und der Formatierung von Dateien für LLMs.\n   - Vermeidet wiederholtes Kopieren und Einfügen von Code.\n\n2. **Produktivität steigern**:\n\n   - Bietet ein strukturiertes und konsistentes Format für Code-Analyse.\n   - Hilft dabei, Bugs zu identifizieren, Code zu refactoren und Dokumentation schneller zu schreiben.\n\n3. **Große Codebasen verarbeiten**:\n\n   - Entwickelt, um nahtlos mit großen Codebasen zu arbeiten, unter Berücksichtigung der Kontextlimits von LLMs.\n\n4. **Benutzerdefinierte Workflows**:\n   - Flexible Optionen für die Filterung von Dateien, die Verwendung von Vorlagen und die Generierung gezielter Prompts.\n\n---\n\n## Beispielanwendungsfälle\n\n- **Code-Dokumentation**:\n  Automatisch Dokumentation für öffentliche Funktionen, Methoden und Klassen generieren.\n\n- **Bug-Erkennung**:\n  Potenzielle Bugs und Schwachstellen durch Analyse der Codebasis mit LLMs finden.\n\n- **Refactoring**:\n  Code vereinfachen und optimieren, indem Prompts für Code-Qualitätsverbesserungen generiert werden.\n\n- **Lernen und Erkunden**:\n  Neue Codebasen verstehen, indem Zusammenfassungen und detaillierte Aufschlüsselungen generiert werden.\n\n- **Git-Commit- und PR-Beschreibungen**:\n  Sinnvolle Commit-Nachrichten und Pull-Request-Beschreibungen aus Git-Diffs generieren.\n\n```\n\n> Diese Seite wurde für Ihre Bequemlichkeit automatisch übersetzt. Bitte greifen Sie für den Originalinhalt auf die englische Version zurück.\n```\n"
  },
  {
    "path": "website/src/content/docs/docs/explanations/glob_pattern_filter.mdx",
    "content": "---\ntitle: How the Glob Pattern Filter Works\ndescription: How Code2Prompt decides which files to keep or discard using include (-i) and exclude (-e) globs.\n---\n\nCode2Prompt uses glob patterns to include or exclude files and directories, working similarly to tools like tree or grep. It lets you pass two independent _lists_ of glob patterns:\n\n- **include list** (`--include` or `-i`) - “these patterns allow files”\n- **exclude list** (`--exclude` or `-e`) - “these patterns disallow files”\n\nCode2prompt must decide, for every file in the project, whether it is kept or discarded. This page explains the rules, and the design choices behind them.\n\n---\n\n## 1. Sets and Symbols\n\nThroughout the explanation we use the usual set notation\n\n| Symbol                            | Meaning                                                  |\n| --------------------------------- | -------------------------------------------------------- |\n| $A$                               | set of files that match **at least one** include pattern |\n| $B$                               | set of files that match **at least one** exclude pattern |\n| $\\Omega$                          | the whole project tree (the _universe_)                  |\n| $C = A \\cap B$                    | files that match both lists (the _overlap_)              |\n| $D = \\Omega \\setminus (A \\cup B)$ | files that match neither list                            |\n\n---\n\n## 2. Four Situations\n\n### Overview of the four situations\n\n| Include list | Exclude list | Files kept |\n| ------------ | ------------ | ---------- |\n| A = ∅        | B = ∅        | Ω          |\n| A = ∅        | B ≠ ∅        | ¬B         |\n| A ≠ ∅        | B = ∅        | A          |\n| A ≠ ∅        | B ≠ ∅        | A \\ B      |\n\n1. **No include list, no exclude list**\n\n   If no patterns are specified, all files are kept (`Ω`).\n\n2. **Exclude list only**\n\n   In this case, Code2Prompt acts as a blacklist, removing files that match the excluded patterns (` Ω \\ B = ¬B`).\n\n3. **Include list only**\n\n   If only an include list is specified, Code2Prompt acts as a whitelist, keeping only files that match the included patterns (`A`).\n\n4. **Include _and_ exclude lists**\n\n   If both lists are specified, Code2Prompt keeps files that match the include patterns, but removes those that match the exclude patterns (`A \\ B`).\n\n---\n\n## 3. More on the overlap\n\nWith both lists present (`A ≠ ∅`, `B ≠ ∅`) you have four logical possibilities\nfor the overlap `C` and the rest `D`.\n\n| Want `C`? | Want `D`? | Reasonable?                                                     |\n| --------- | --------- | --------------------------------------------------------------- |\n| No        | No        | Default behaviour (`A \\ B`)                                     |\n| Yes       | No        | Same behavior as case 3 (`A`)                                   |\n| No        | Yes       | surprising (“discard what I asked for `C`, keep what I didn't”) |\n| Yes       | Yes       | Same behavior as case 1 (`Ω`)                                   |\n\nThis is for this reason that the `--include-priority` option was removed. Because, it would be the same result as if you had only an include list (case 3).\n\n## 4. Quick reference table\n\n| Want to keep…                              | Use               |\n| ------------------------------------------ | ----------------- |\n| everything                                 | no `-i`, no `-e`  |\n| everything _except_ some patterns          | `-e` only         |\n| _only_ what matches the patterns           | `-i` only         |\n| what matches `-i`, minus what matches `-e` | `-i` **and** `-e` |\n\n---\n\nThis design keeps the mental model simple:\n\n- The include list is a whitelist as soon as it exists.\n- The exclude list is a blacklist layered on top.\n- The overlap is discarded by default\n"
  },
  {
    "path": "website/src/content/docs/docs/explanations/glob_patterns.md",
    "content": "---\ntitle: Understanding Glob Patterns\ndescription: A detailed explanation of glob patterns and how they are used in Code2Prompt.\n---\n\nGlob patterns are a simple yet powerful way to match file names and paths using wildcard characters. They are commonly used in command-line interfaces and programming languages to specify sets of filenames or directories. Here's a breakdown of the most commonly used glob patterns:\n\n## Basic Wildcards\n\n- `*`: Matches any number of characters, including zero characters.\n  - Example: `*.txt` matches all files ending with `.txt`.\n\n- `?`: Matches exactly one character.\n  - Example: `file?.txt` matches `file1.txt`, `fileA.txt`, but not `file10.txt`.\n\n- `[]`: Matches any one of the enclosed characters.\n  - Example: `file[1-3].txt` matches `file1.txt`, `file2.txt`, `file3.txt`.\n\n- `[!]` or `[^]`: Matches any character not enclosed.\n  - Example: `file[!1-3].txt` matches `file4.txt`, `fileA.txt`, but not `file1.txt`.\n\n## Advanced Patterns\n\n- `**`: Matches any number of directories and subdirectories recursively.\n  - Example: `**/*.txt` matches all `.txt` files in the current directory and all subdirectories.\n\n- `{}`: Matches any of the comma-separated patterns enclosed.\n  - Example: `file{1,2,3}.txt` matches `file1.txt`, `file2.txt`, `file3.txt`.\n\n## Examples\n\n1. **Matching all text files in a directory:**\n\n   ```sh\n   *.txt\n   ```\n\n2. **Matching all files with a single digit before the extension:**\n\n   ```sh\n   file?.txt\n   ```\n\n3. **Matching files with extensions `.jpg` or `.png`:**\n\n   ```sh\n   *.{jpg,png}\n   ```\n\n4. **Matching all `.txt` files in any subdirectory:**\n\n   ```sh\n   **/*.txt\n   ```\n\n5. **Matching files that start with `a` or `b` and end with `.txt`:**\n\n   ```sh\n   {a,b}*.txt\n   ```\n\n## Use Cases\n\n- **Command-Line Tools:** Glob patterns are extensively used in command-line tools like `ls`, `cp`, `mv`, and `rm` to specify multiple files or directories.\n- **Programming Languages:** Languages like Python, JavaScript, and Ruby support glob patterns for file matching through libraries like `glob` in Python.\n- **Build Systems:** Tools like Makefile use glob patterns to specify source files and dependencies.\n\n## Conclusion\n\nGlob patterns provide a flexible and intuitive way to match filenames and paths, making them invaluable for scripting, automation, and file management tasks. Understanding and utilizing these patterns can significantly enhance your productivity and efficiency in handling files and directories.\n"
  },
  {
    "path": "website/src/content/docs/docs/explanations/tokenizers.md",
    "content": "---\ntitle: Tokenization in Code2Prompt\ndescription: Learn about tokenization and how Code2Prompt processes text for LLMs.\n---\n\nWhen working with language models, text needs to be transformed into a format that the model can understand—**tokens**, which are sequences of numbers. This transformation is handled by a **tokenizer**.\n\n---\n\n## What is a Tokenizer?\n\nA tokenizer converts raw text into tokens, which are the building blocks for how language models process input. These tokens can represent words, subwords, or even individual characters, depending on the tokenizer's design.\n\nFor `code2prompt`, we use the **tiktoken** tokenizer. It’s efficient, robust, and optimized for OpenAI models.\nYou can explore its functionality in the official repository\n\n👉 [tiktoken GitHub Repository](https://github.com/openai/tiktoken)\n\nIf you want to learn more about tokenizer in general, check out the\n\n👉 [Mistral Tokenization Guide](https://docs.mistral.ai/guides/tokenization/).\n\n## Implementation in `code2prompt`\n\nTokenization is implemented using [`tiktoken-rs`](https://github.com/zurawiki/tiktoken-rs). `tiktoken` supports these encodings used by OpenAI models:\n\n| CLI Argument | Encoding name           | OpenAI models                                                             |\n|----| ----------------------- | ------------------------------------------------------------------------- |\n|`cl100k`| `cl100k_base`           | ChatGPT models, `text-embedding-ada-002`                                  |\n|`p50k`| `p50k_base`             | Code models, `text-davinci-002`, `text-davinci-003`                       |\n|`p50k_edit`| `p50k_edit`             | Use for edit models like `text-davinci-edit-001`, `code-davinci-edit-001` |\n|`r50k`| `r50k_base` (or `gpt2`) | GPT-3 models like `davinci`                                               |\n|`gpt2`| `o200k_base`            | GPT-4o models                                                             |\n\nFor more context on the different tokenizers, see the [OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/66b988407d8d13cad5060a881dc8c892141f2d5c/examples/How_to_count_tokens_with_tiktoken.ipynb)\n"
  },
  {
    "path": "website/src/content/docs/docs/how_to/filter_files.md",
    "content": "---\ntitle: Filtering Files in Code2Prompt\ndescription: A step-by-step guide to including or excluding files using different filtering methods.\n---\n\n\n## Usage\n\nGenerate a prompt from a codebase directory:\n\n```sh\ncode2prompt path/to/codebase\n```\n\nUse a custom Handlebars template file:\n\n```sh\ncode2prompt path/to/codebase -t path/to/template.hbs\n```\n\nFilter files using glob patterns:\n\n```sh\ncode2prompt path/to/codebase --include=\"*.rs,*.toml\"\n```\n\nExclude files using glob patterns:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.txt,*.md\"\n```\n\nExclude files/folders from the source tree based on exclude patterns:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.npy,*.wav\" --exclude-from-tree\n```\n\nDisplay the token count of the generated prompt:\n\n```sh\ncode2prompt path/to/codebase --tokens\n```\n\nSpecify a tokenizer for token count:\n\n```sh\ncode2prompt path/to/codebase --tokens --encoding=p50k\n```\n\nSupported tokenizers: `cl100k`, `p50k`, `p50k_edit`, `r50k_bas`.\n> [!NOTE]  \n> See [Tokenizers](#tokenizers) for more details.\n\nSave the generated prompt to an output file:\n\n```sh\ncode2prompt path/to/codebase --output=output.txt\n```\n\nPrint output as JSON:\n\n```sh\ncode2prompt path/to/codebase --json\n```\n\nThe JSON output will have the following structure:\n\n```json\n{\n  \"prompt\": \"<Generated Prompt>\", \n  \"directory_name\": \"codebase\",\n  \"token_count\": 1234,\n  \"model_info\": \"ChatGPT models, text-embedding-ada-002\",\n  \"files\": []\n}\n```\n\nGenerate a Git commit message (for staged files):\n\n```sh\ncode2prompt path/to/codebase --diff -t templates/write-git-commit.hbs\n```\n\nGenerate a Pull Request with branch comparing (for staged files):\n\n```sh\ncode2prompt path/to/codebase --git-diff-branch 'main, development' --git-log-branch 'main, development' -t templates/write-github-pull-request.hbs\n```\n\nAdd line numbers to source code blocks:\n\n```sh\ncode2prompt path/to/codebase --line-number\n```\n\nDisable wrapping code inside markdown code blocks:\n\n```sh\ncode2prompt path/to/codebase --no-codeblock\n```\n\n- Rewrite the code to another language.\n- Find bugs/security vulnerabilities.\n- Document the code.\n- Implement new features.\n\n> I initially wrote this for personal use to utilize Claude 3.0's 200K context window and it has proven to be pretty useful so I decided to open-source it!\n"
  },
  {
    "path": "website/src/content/docs/docs/how_to/install.mdx",
    "content": "---\ntitle: Installing Code2Prompt\ndescription: A complete installation guide for Code2Prompt on different operating systems.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Steps } from \"@astrojs/starlight/components\";\n\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\n\n<Card title=\"Guide Overview\">\n  Welcome to the `Code2Prompt` installation guide. This document provides\n  step-by-step instructions for installing it on various platforms, including\n  Windows, macOS, and Linux.\n</Card>\n\n**TL;DR**\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n## Prerequisite\n\nMake sure [Rust](https://www.rust-lang.org/tools/install) and cargo are installed on your system.\n\n```sh\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\nThis is the official way to install the latest stable version of Rust and Cargo.\nMake sure to refresh your `PATH` variable after installing Rust. Restart your terminal or run the instructions proposed by the installer.\n\n```sh\nsource $HOME/.cargo/env\n```\n\nYou can check that everything is installed correctly by running:\n\n```sh\ncargo --version\ngit --version\n```\n\n## Command Line Interface (CLI) 👨‍💻\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n#### 🧪 Install the latest (unpublished) version from GitHub\n\nIf you want the latest features or fixes before they're released on crates.io:\n\n```sh\ncargo install --git https://github.com/mufeedvh/code2prompt\n```\n\n### Source build\n\nIdeal for developers that want to build from source or contribute to the project.\n\n<Steps>\n\n1.  🛠️ Install Prerequisites :\n\n    - [Rust](https://www.rust-lang.org/tools/install) and Cargo\n    - [Git](https://git-scm.com/downloads)\n\n2.  📥 Clone the repository :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt\n    ```\n\n3.  📦 Install the binary :\n\n    To build and install from source:\n\n    ```sh\n    cargo install --path crates/code2prompt\n    ```\n\n    To build the binary without installing it:\n\n    ```sh\n    cargo build --release\n    ```\n\n    The binary will be available in the `target/release` directory.\n\n4.  🚀 Run it :\n\n    ```sh\n    code2prompt --help\n    ```\n\n</Steps>\n\n### Binary releases\n\nBest for users that want to use the latest version without building from source.\n\nDownload the latest binary for your OS from [Releases](https://github.com/mufeedvh/code2prompt/releases).\n\n⚠️ Binary releases may lag behind the latest GitHub version. For cutting-edge features, consider building from source.\n\n### AUR\n\nSpecifically for Arch Linux users, `code2prompt` is available in the AUR.\n\n`code2prompt` is available in the [`AUR`](https://aur.archlinux.org/packages?O=0&K=code2prompt). Install it via any AUR helpers.\n\n```sh\nparu/yay -S code2prompt\n```\n\n### Nix\n\nIf you're using Nix, you can install it using either nix-env or nix profile.\n\n```sh\n# without flakes:\nnix-env -iA nixpkgs.code2prompt\n# with flakes:\nnix profile install nixpkgs#code2prompt\n```\n\n## Software Development Kit (SDK) 🐍\n\n### Pypi\n\nYou can download the python bindings from Pypi\n\n```sh\npip install code2prompt_rs\n```\n\n### Source build\n\n<Steps>\n\n1.  🛠️ Install Prerequisites :\n\n    - [Rust](https://www.rust-lang.org/tools/install) and Cargo\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 Clone the repository :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt/crates/code2prompt-python\n    ```\n\n3.  📦 Install the dependencies :\n\n    The `rye` command will create a virtual environment and install all the dependencies.\n\n    ```sh\n    rye sync\n    ```\n\n4.  ⚙️ Build the package :\n\n    You will develop the package in the virtual environment located in `.venv` folder at the root of the project.\n\n    ```sh\n    rye run maturin develop -r\n    ```\n\n</Steps>\n\n## Model Context Protocol (MCP) 🤖\n\n### Automated installation\n\nThe `code2prompt` MCP server will soon be available in MCP registries.\n\n### Manual installation\n\nThe `code2prompt` MCP server is still a prototype and will be integrated to the main repository soon.\n\nTo run the MCP server, locally to use it with `Cline`, `Goose` or `Aider`:\n\n<Steps>\n\n1.  🛠️ Install Prerequisites :\n\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 Clone the repository :\n\n    ```sh\n    git clone https://github.com/odancona/code2prompt-mcp.git\n    cd code2prompt-mcp\n    ```\n\n3.  📦 Install the dependencies :\n\n    The `rye` command will create a virtual environment and install all the dependencies in the `.venv` folder.\n\n    ```sh\n    rye sync\n    ```\n\n4.  🚀 Run the server :\n\n    The MCP server is now installed. You can run it using:\n\n    ```sh\n    . .venv/bin/activate\n    python -m src/code2prompt_mcp/main.py\n    ```\n\n5.  🔌 Integrate with Agents :\n\n            For instance, you can integrate it with `Cline`, using a similar configuration:\n\n            ```json\n            {\n              \"mcpServers\": {\n                \"code2prompt\": {\n                  \"command\": \"bash\",\n                  \"args\": [\n                    \"-c\",\n                    \"cd /home/olivier/projet/code2prompt-mcp && rye run python /home/olivier/projet/code2prompt-mcp/src/code2prompt_mcp/main.py\"\n                  ],\n                  \"env\": {}\n                }\n              }\n            }\n            ```\n\n</Steps>\n"
  },
  {
    "path": "website/src/content/docs/docs/how_to/ssh.md",
    "content": "---\ntitle: Use Code2prompt CLI with SSH\ndescription: A guide to using Code2Prompt CLI with SSH for remote codebase analysis.\n---\n\n## Why it doesn't work?\n\nWhen you try to run the `code2prompt` CLI on a remote server via SSH, the command is unable to find the clipboard. This is because the `code2prompt` CLI uses the clipboard to copy the generated prompt, and SSH sessions typically do not have access to the local clipboard.\n\n## Solution\n\nTo use the `code2prompt` CLI with SSH, you can redirect the output to a file instead of copying it to the clipboard. This way, you can still generate the prompt and save it for later use.\n\nUse the `--output-file` option to specify the output file where the generated prompt will be saved. For example:\n\n```sh\nssh user@remote-server \"code2prompt path/to/codebase -O output.txt\"\n```\n"
  },
  {
    "path": "website/src/content/docs/docs/references/command_line_options.md",
    "content": "---\ntitle: Code2Prompt Command-Line Options\ndescription: A reference guide for all available CLI options in Code2Prompt.\n---\n\n# Command-Line Options\n"
  },
  {
    "path": "website/src/content/docs/docs/references/default_template.md",
    "content": "---\ntitle: Default Template for Code2Prompt\ndescription: Learn about the default template structure used in Code2Prompt.\n---\n\n# Default Template\n"
  },
  {
    "path": "website/src/content/docs/docs/tutorials/configuration.mdx",
    "content": "---\ntitle: Configuring Code2Prompt 📖\ndescription: Learn how to use .c2pconfig to automate your prompt generation workflow and ensure team consistency.\n---\n\nimport { Card, Steps, Aside, Tabs, TabItem } from \"@astrojs/starlight/components\";\n\n<Card title=\"Tutorial Overview\">\n  Manually typing long exclude patterns or specific tokenizer settings every time can be tedious. Therefore this tutorial shows you **how to use a `.c2pconfig` configuration file** to \"set and forget\" your project settings.\n</Card>\n\n---\n\n## Prerequisites\n\nEnsure you have `code2prompt` installed. If you haven't installed it yet, refer to the [Installation Guide](/docs/how_to/install). Familiarity with [TOML syntax](https://toml.io/en/) is helpful but not required.\n\n---\n\n## What is .c2pconfig?\n\nThe `.c2pconfig` file is a configuration file written in **TOML** format. When you run `code2prompt`, it automatically searches for this file in your current working directory. \n\nIt allows you to define:\n* **Filtering Rules:** Persistent include/exclude patterns.\n* **Output Formats:** Default to JSON, Markdown, or XML.\n* **Template Context:** Pre-define variables for your Handlebars templates.\n\n---\n\n## Quick Start\n\nCreate a file named `.c2pconfig` at the root of your project to define your base behavior.\n\n```toml\n# .c2pconfig example\ndefault_output = \"stdout\" # Options: stdout, clipboard, file\ninclude_patterns = [\"src/**/*.rs\", \"Cargo.toml\"]\nexclude_patterns = [\"**/target/**\", \"tests/fixtures/**\"]\nline_numbers = true\noutput_format = \"markdown\"\n\n[user_variables]\nproject_name = \"MyAwesomeProject\"\nauthor = \"Developer\"\n\n```\n\n---\n\n## Configuration Reference\n\nThe following table describes the keys available in the configuration file.\n\n| Key | Type | Description |\n| --- | --- | --- |\n| `path` | String | Default path to codebase (usually `.`). |\n| `include_patterns` | Array | Glob patterns of files to include. |\n| `exclude_patterns` | Array | Glob patterns of files to exclude. |\n| `line_numbers` | Boolean | If `true`, adds line numbers to code blocks. |\n| `absolute_path` | Boolean | Use absolute paths instead of relative paths. |\n| `full_directory_tree` | Boolean | Generate the full tree even for excluded files. |\n| `output_format` | String | `markdown`, `json`, or `xml`. |\n| `sort_method` | String | `name_asc`, `name_desc`, `date_asc`, `date_desc`. |\n| `encoding` | String | Tokenizer: `cl100k`, `p50k`, `o200k`. |\n| `diff_enabled` | Boolean | Include git diff (HEAD vs Index). |\n| `token_map_enabled` | Boolean | Display a hierarchical token usage map. |\n\n---\n\n## Implementation Guide\n\nFollow these steps to integrate a configuration file into your workflow.\n\n<Steps>\n\n1. **Initialize your Configuration**\n        Navigate to your project root and create the config file:\n        ```bash\n        touch .c2pconfig\n\n        ```\n\n2. **Define your Source of Truth**\n        Exclude heavy directories like `node_modules` or build artifacts to keep the LLM context clean.\n        ```toml\n        exclude_patterns = [\n        \"**/node_modules/**\",\n        \"package-lock.json\",\n        \"dist/**\"\n        ]\n\n        ```\n\n3. **Set your Model Encoding**\n        Match the tokenizer to your target LLM. Use `o200k` for GPT-4o, or `cl100k` for Claude and GPT-4.\n        ```toml\n        encoding = \"o200k\"\n\n        ```\n\n4. **Inject Custom Context**\n        Use the `[user_variables]` section to pass data into your [Handlebars templates](/docs/learn/templates).\n        ```toml\n        [user_variables]\n        project_goal = \"Refactor the authentication module for better security.\"\n\n        ```\n\n5. **Run with Zero Arguments**\n        Simply run the tool. It will now respect all your predefined rules without extra CLI flags.\n        ```bash\n        code2prompt .\n\n        ```\n\n</Steps>\n\n---\n\n## Understanding Precedence\n\nIt is important to understand how `code2prompt` decides which settings to use when multiple sources conflict.\n\n<Aside type=\"tip\" title=\"Priority Order\">\n**CLI Arguments > Configuration File > Default Settings**\n\nArguments passed directly via the CLI will always override values defined in `.c2pconfig`. This allows you to maintain a \"base\" config while remaining flexible for one-off commands.\n\n</Aside>\n\n### Advanced Filtering Logic\n\nThe engine uses a tiered selection system:\n\n* **Static (A/B):** Defined in your `.c2pconfig` (Include/Exclude).\n* **Dynamic (A'/B'):** If you use **Interactive Mode**, your manual toggle selections override the static patterns for that specific session.\n\n---\n\n## Example: The \"Review-Ready\" Config\n\nUse this setup if your primary goal is generating prompts for code reviews.\n\n```toml\ndefault_output = \"clipboard\"\nline_numbers = true\ntoken_map_enabled = true\n\nexclude_patterns = [\n    \"tests/**\",\n    \"**/migrations/**\",\n    \"*.md\"\n]\n\n[user_variables]\nreview_focus = \"Check for DRY principle violations and complexity.\"\n\n```\n\n---\n\n## Next Steps\n\n<Card title=\"Level Up your Workflow\">\n\n* **Master Templates:** Explore [Custom Templates](/docs/tutorials/learn_templates) to see how to use `user_variables` effectively.\n* **Refine Filtering:** Check the [Filtering Guide](/docs/tutorials/learn_filters) for advanced glob pattern syntax.\n</Card>"
  },
  {
    "path": "website/src/content/docs/docs/tutorials/getting_started.mdx",
    "content": "---\ntitle: Getting Started with Code2Prompt\ndescription: A comprehensive tutorial introducing Code2Prompt's core functionality and its use across CLI, SDK, and MCP integrations.\n---\n\nimport { Aside } from \"@astrojs/starlight/components\";\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\n\n<Card title=\"Tutorial Overview\">\n  Welcome to Code2Prompt! This tutorial provides a comprehensive introduction to\n  using Code2Prompt to generate AI-ready prompts from your codebases. We'll\n  explore its core functionality and demonstrate its usage across different\n  integration methods: Command Line Interface (CLI), Software Development Kit\n  (SDK), and Model Context Protocol (MCP).\n</Card>\n\n## What is Code2Prompt?\n\nCode2Prompt is a versatile tool designed to bridge the gap between your codebase and Large Language Models (LLMs). It intelligently extracts relevant code snippets, applies powerful filtering, and formats the information into structured prompts optimized for LLM consumption. This simplifies tasks like code documentation, bug detection, refactoring, and more.\n\nCode2Prompt offers different integration points:\n\n<Tabs>\n  <TabItem label=\"Core\" icon=\"seti:rust\">\n    A core rust library that provides the foundation for code ingestion and\n    prompt\n  </TabItem>\n  <TabItem label=\"CLI\" icon=\"seti:powershell\">\n    A user-friendly command-line interface for quick prompt generation. Ideal\n    for interactive use and one-off tasks.\n  </TabItem>\n  <TabItem label=\"SDK\" icon=\"seti:python\">\n    A powerful Software Development Kit (SDK) for seamless integration into your\n    Python projects. Perfect for automating prompt generation within larger\n    workflows.\n  </TabItem>\n  <TabItem label=\"MCP\" icon=\"seti:db\">\n    A Model Context Protocol (MCP) server for advanced integration with LLM\n    agents. Enables sophisticated, real-time interactions with your codebase.\n  </TabItem>\n</Tabs>\n\n## 📥 Installation\n\nFor detailed installation instructions for all methods (CLI, SDK, MCP), please refer to the comprehensive [Installation Guide](/docs/how_to/install).\n\n## 🏁 Generating Prompts: A CLI Example\n\nLet's start with a simple example using the CLI. Create a sample project:\n\n```bash\nmkdir -p my_project/{src,tests}\ntouch my_project/src/main.rs my_project/tests/test_1.rs\necho 'fn main() { println!(\"Hello, world!\"); }' > my_project/src/main.rs\n```\n\nNow, generate a prompt:\n\n```bash\ncode2prompt my_project\n```\n\nThis copies a prompt to your clipboard. You can customize this:\n\n- **Filtering:** `code2prompt my_project --include=\"*.rs\" --exclude=\"tests/*\"` (includes only `.rs` files, excludes `tests` directory)\n- **Output File:** `code2prompt my_project --output-file=my_prompt.txt`\n- **JSON Output:** `code2prompt my_project -O json` (structured JSON output)\n- **Custom Templates:** `code2prompt my_project -t my_template.hbs` (requires creating `my_template.hbs`)\n\nSee the [Learn Context Filtering](/docs/tutorials/learn_filters) and [Learn Handlebar Templates](/docs/tutorials/learn_templates) tutorials to learn more advanced usages.\n\n## 🐍 SDK Integration (Python)\n\nFor programmatic control, use the Python SDK:\n\n```python\nfrom code2prompt_rs import Code2Prompt\n\nconfig = {\n    \"path\": \"my_project\",\n    \"include_patterns\": [\"*.rs\"],\n    \"exclude_patterns\": [\"tests/*\"],\n}\n\nc2p = Code2Prompt(**config)\nprompt = c2p.generate_prompt()\nprint(prompt)\n```\n\nThis requires installing the SDK (`pip install code2prompt_rs`). Refer to the SDK documentation for more details.\n\n## 🤖 MCP Server Integration (Advanced)\n\nFor advanced integration with LLM agents, run the `code2prompt` MCP server (see the installation guide for details). This allows agents to request code context dynamically. This is an advanced feature, and further documentation is available on the project's website.\n\n<Card title=\"Next Steps\">\n  Explore the advanced tutorials and documentation to master Code2Prompt's\n  capabilities and integrate it into your workflows.\n</Card>\n"
  },
  {
    "path": "website/src/content/docs/docs/tutorials/learn_filters.mdx",
    "content": "---\ntitle: Learn Context Filtering with Code2Prompt\ndescription: Learn how to exclude or include files in your LLM prompts using powerful filtering options.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"Tutorial Overview\">\n  This tutorial demonstrates how to use the **glob pattern tool** in\n  `code2prompt` CLI to filter and manage files based on include and exclude\n  patterns.\n</Card>\n\nGlob patterns work similarly to tools like `tree` or `grep`,\nproviding powerful filtering capabilities. Check out the [detailed\nexplanation](/docs/explanations/glob_patterns) for more information.\n\n---\n\n## Prerequisites\n\nEnsure you have `code2prompt` installed. If you haven't installed it yet, refer to the [Installation Guide](/docs/how_to/install).\n\n---\n\n## Understanding Include and Exclude Patterns\n\nGlob patterns allow you to specify rules for filtering files and directories.\n\n- **Include Patterns** (`--include`): Specify files and directories you want to include.\n- **Exclude Patterns** (`--exclude`): Specify files and directories you want to exclude.\n- **Priority** (`--include-priority`): Resolves conflicts between include and exclude patterns.\n\n---\n\n## Setting Up the Environment\n\nTo practice with glob patterns, let's create a sample folder structure with some files.\n\n### Generate Test Structure\n\nRun this script to set up a temporary directory structure\n\n```bash\n#!/bin/bash\n\n# Create base directory\nmkdir -p test_dir/{lowercase,uppercase,.secret}\n\n# Create files in the structure\necho \"content foo.py\" > \"test_dir/lowercase/foo.py\"\necho \"content bar.py\" > \"test_dir/lowercase/bar.py\"\necho \"content baz.py\" > \"test_dir/lowercase/baz.py\"\necho \"content qux.txt\" > \"test_dir/lowercase/qux.txt\"\necho \"content corge.txt\" > \"test_dir/lowercase/corge.txt\"\necho \"content grault.txt\" > \"test_dir/lowercase/grault.txt\"\n\necho \"CONTENT FOO.py\" > \"test_dir/uppercase/FOO.PY\"\necho \"CONTENT BAR.py\" > \"test_dir/uppercase/BAR.PY\"\necho \"CONTENT BAZ.py\" > \"test_dir/uppercase/BAZ.PY\"\necho \"CONTENT QUX.txt\" > \"test_dir/uppercase/QUX.TXT\"\necho \"CONTENT CORGE.txt\" > \"test_dir/uppercase/CORGE.TXT\"\necho \"CONTENT GRAULT.txt\" > \"test_dir/uppercase/GRAULT.TXT\"\n\necho \"top secret\" > \"test_dir/.secret/secret.txt\"\n```\n\nTo clean up the structure later, run:\n\n```bash\nrm -rf test_dir\n```\n\nIt will create the following directory structure:\n\nimport { FileTree } from \"@astrojs/starlight/components\";\n\n\n<FileTree>\n- test_dir \n  - lowercase \n    - foo.py \n    - bar.py \n    - baz.py \n    - qux.txt \n    - corge.txt \n    - grault.txt\n  - uppercase \n    - FOO.py \n    - BAR.py \n    - BAZ.py \n    - QUX.txt \n    - CORGE.txt \n    - GRAULT.txt \n  - .secret \n    - secret.txt\n</FileTree>\n\n---\n\n## General Usage of `code2prompt`\n\nBy default, `code2prompt` includes all files in the specified directory respecting the `.gitignore`.\n\n\n\n## Filtering Files with Include and Exclude Patterns\n\nThe `-e` for `--exclude` and `-i` for `--include` options allow you to filter files dynamically based on glob patterns.\n\n### Case 1: Default Behavior (No Filters)\n\nCommand:\n\n```bash\ncode2prompt test_dir\n```\n\n#### Result\n\nAll files are included:\n\n- `lowercase/foo.py`\n- `lowercase/bar.py` ...\n- `uppercase/FOO.py` ...\n- `.secret/secret.txt`\n\n---\n\n### Case 2: Exclude Specific File Types\n\nExclude `.txt` or `.md` files:\n\n```bash\ncode2prompt test_dir --exclude=\"*.txt,*.md\"\n```\n\n#### Result\n\nExcluded:\n\n- All `.txt` or `.md` files\n\nIncluded:\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n\n---\n\n### Case 3: Include Specific File Types\n\nInclude only Python files:\n\n```bash\ncode2prompt test_dir --include=\"*.py\"\n```\n\n#### Result\n\nIncluded:\n\n- All `.py` files\n\nExcluded:\n\n- `.secret/secret.txt`\n\n---\n\n### Case 4: Include and Exclude with Priority\n\nInclude `.py` files but exclude files in the `uppercase` folder:\n\n```bash\ncode2prompt test_dir --include=\"*.py\" --exclude=\"**/uppercase/*\" --include-priority=true\n```\n\n#### Result\n\nIncluded:\n\n- All `lowercase/1` files having `.py` extension\n\nExcluded:\n\n- All `uppercase` files\n- `.secret/secret.txt`\n\n---\n\n### Case 5: Exclude Specific Directory\n\nExclude the `uppercase` directory:\n\n```bash\ncode2prompt test_dir --exclude \"**/uppercase*\"\n```\n\nor with short syntax:\n\n```bash\ncode2prompt test_dir -e \"uppercase*\"\n```\n\n#### Result\n\nIncluded:\n\n- All files in `lowercase` and `.secret`\n\nExcluded:\n\n- All files in `uppercase`\n\n## Summary\n\nThe glob pattern tool in `code2prompt` allows you to filter files and directories effectively using:\n\n- `--include` for specifying files to include\n- `--exclude` for files to exclude\n- `--include-priority` for resolving conflicts between patterns\n\nTo practice, set up the sample directory, try out the commands, and see how the tool filters files dynamically.\n"
  },
  {
    "path": "website/src/content/docs/docs/tutorials/learn_templates.mdx",
    "content": "---\ntitle: Learn Handlebar Templates with Code2Prompt\ndescription: Understand how to use and create custom Handlebars templates for prompt generation.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"Tutorial Overview\">\n  This tutorial demonstrates how to use and create custom Handlebars templates\n  for prompt generation in `code2prompt` CLI.\n</Card>\n\n---\n\n## Prerequisites\n\nEnsure you have `code2prompt` installed. If you haven't installed it yet, refer to the [Installation Guide](/docs/how_to/install).\n\n---\n\n## What are Handlebars Templates ?\n\n[Handlebars](https://handlebarsjs.com/) is a popular templating engine that allows you to create dynamic templates using placeholders.\nIn `code2prompt`, Handlebars templates are used to format the generated prompts based on the codebase structure and user-defined variables.\n\n## How to use Handlebars Templates ?\n\nYou can use these templates by passing the `-t` or `--template` flag followed by the path to the template file. For example:\n\n```sh\ncode2prompt path/to/codebase -t templates/document-the-code.hbs\n```\n\n## Template Syntax\n\nHandlebars templates use a simple syntax for placeholders and expressions. You will place variables in double curly braces `{{variable_name}}` to include them in the generated prompt.\n`Code2prompt` provides a set of default variables that you can use in your templates:\n\n- `absolute_code_path`: The absolute path to the codebase.\n- `source_tree`: The source tree of the codebase, which includes all files and directories.\n- `files`: A list of files in the codebase, including their paths and contents.\n- `git_diff`: The git diff of the codebase, if applicable.\n- `code`: The code content of the file being processed.\n- `path`: The path of the file being processed.\n\nYou can also use Handlebars helpers to perform conditional logic, loops, and other operations within your templates. For example:\n\n```handlebars\n{{#if files}}\n  {{#each files}}\n    File:\n    {{this.path}}\n    Content:\n    {{this.content}}\n  {{/each}}\n{{else}}\n  No files found.\n{{/if}}\n```\n\n---\n\n## Existing Templates\n\n`code2prompt` comes with a set of built-in templates for common use cases. You can find them in the [`templates`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates) directory.\n\n### [`document-the-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/document-the-code.hbs)\n\nUse this template to generate prompts for documenting the code. It will add documentation comments to all public functions, methods, classes and modules in the codebase.\n\n### [`find-security-vulnerabilities.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/find-security-vulnerabilities.hbs)\n\nUse this template to generate prompts for finding potential security vulnerabilities in the codebase. It will look for common security issues and provide recommendations on how to fix or mitigate them.\n\n### [`clean-up-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/clean-up-code.hbs)\n\nUse this template to generate prompts for cleaning up and improving the code quality. It will look for opportunities to improve readability, adherence to best practices, efficiency, error handling, and more.\n\n### [`fix-bugs.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/fix-bugs.hbs)\n\nUse this template to generate prompts for fixing bugs in the codebase. It will help diagnose issues, provide fix suggestions, and update the code with proposed fixes.\n\n### [`write-github-pull-request.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-pull-request.hbs)\n\nUse this template to create GitHub pull request description in markdown by comparing the git diff and git log of two branches.\n\n### [`write-github-readme.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-readme.hbs)\n\nUse this template to generate a high-quality README file for the project, suitable for hosting on GitHub. It will analyze the codebase to understand its purpose and functionality, and generate the README content in Markdown format.\n\n### [`write-git-commit.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-git-commit.hbs)\n\nUse this template to generate git commits from the staged files in your git directory. It will analyze the codebase to understand its purpose and functionality, and generate the git commit message content in Markdown format.\n\n### [`improve-performance.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/improve-performance.hbs)\n\nUse this template to generate prompts for improving the performance of the codebase. It will look for optimization opportunities, provide specific suggestions, and update the code with the changes.\n\n## User Defined Variables\n\n`code2prompt` supports the use of user defined variables in the Handlebars templates. Any variables in the template that are not part of the default context (`absolute_code_path`, `source_tree`, `files`) will be treated as user defined variables.\n\nDuring prompt generation, `code2prompt` will prompt the user to enter values for these user defined variables. This allows for further customization of the generated prompts based on user input.\n\nFor example, if your template includes `{{challenge_name}}` and `{{challenge_description}}`, you will be prompted to enter values for these variables when running `code2prompt`.\n\nThis feature enables creating reusable templates that can be adapted to different scenarios based on user provided information.\n"
  },
  {
    "path": "website/src/content/docs/docs/vision.mdx",
    "content": "---\ntitle: Code2Prompt's Vision\ndescription: Discover the vision behind Code2Prompt and how it enhances LLM interactions with code.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Aside } from \"@astrojs/starlight/components\";\n\n<Card title=\"Purpose 🎯\">\n  `code2prompt` was created to help developers and AI agents interact with\n  codebases more effectively.\n</Card>\n\n## The Problem 🚩\n\nLarge Language Models (LLMs) have revolutionized the way we interact with code. However, they still face significant challenges with code generation:\n\n- **Planning and Reasoning**: LLMs lacks the ability to plan and reason, which is crucial for tasks like code generation, refactoring, and debugging. They often struggle to get the big picture and are short sighted.\n- **Context size**: LLMs have a limited context window, which restricts their ability to analyze and understand large codebases.\n- **Hallucination**: LLMs can generate code that appears correct but is actually incorrect or nonsensical. This phenomenon, known as hallucination, occurs when the model lacks sufficient context or understanding of the codebase.\n\nThis is where `code2prompt` comes in.\n\n## The Solution ✅\n\nWe believe that planning and reasoning can be achieved by human or AI agents with scaffolding techniques. These agents needs to gather a **high quality context** of the codebase that is filtered, structured, and formatted for the task at hand.\n\nThe thumb rule would be:\n\n<Aside type=\"tip\">\n  > provide as little context as possible, but as much as necessary\n</Aside>\n\nThis is practically difficult to achieve, especially for large codebases. However, `code2prompt` is a simple tool that can help developers and AI agents ingest codebase more effectively.\n\nIt automates the process of traversing a codebase, filtering files, and formatting them into structured prompts that LLMs can understand. By doing so, it helps to mitigate the challenges of planning, reasoning, and hallucination.\n\nYou can understand how `code2prompt` is designed to tackles these challenges in the following section.\n\n## Architecture ⛩️\n\n<img\n  src=\"/assets/images/architecture.svg\"\n  alt=\"Architecture of code2prompt\"\n  style=\"width: 75%;\"\n/>\n\n`code2prompt` is designed in a modular way, allowing for easy integration into various workflows. It can be used as a core library, a command line interface (CLI), a software development kit (SDK), or even as a Model Context Protocol (MCP) server.\n\n### Core\n\n`code2prompt` is a code ingestion tool that streamline the process of creating LLM prompts for code analysis, generation, and other tasks. It works by traversing directories, building a tree structure, and gathering informations about each file. The core library can easily be integrated into other applications.\n\n### CLI\n\n`code2prompt` command line interface (CLI) was designed for humans to generate prompts directly from your codebase. The generated prompt is automatically copied to your clipboard and can also be saved to an output file. Furthermore, you can customize the prompt generation using Handlebars templates. Check out the provided prompts in the doc !\n\n### SDK\n\n`code2prompt` software development kit (SDK) offers python binding to the core library. This is perfect for AI agents or automation scripts that want to interact with codebase seamlessly. The SDK is hosted on Pypi and can be installed via pip.\n\n### MCP\n\n`code2prompt` is also available as a Model Context Protocol (MCP) server, which allows you to run it as a local service. This enables LLMs on steroids by providing them a tool to automatically gather a well-structured context of your codebase.\n"
  },
  {
    "path": "website/src/content/docs/docs/welcome.mdx",
    "content": "---\ntitle: Code2Prompt Documentation\ndescription: Official Code2prompt Documentation\ntemplate: splash\nhero:\n  tagline: Transform Your Code into AI-Optimized Prompts in Seconds\n  image:\n    file: ../../../assets/logo_dark_v0.0.1.svg\n  actions:\n    - text: Get Started 🚀\n      link: /docs/tutorials/getting_started\n    - text: Installation 📥\n      link: /docs/how_to/install\n---\n\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\nimport { LinkCard } from \"@astrojs/starlight/components\";\n\n## Quick Start\n\n<LinkCard title=\"Getting Started 🚀\" href=\"/docs/tutorials/getting_started\" />\n<LinkCard title=\"Installation 📥\" href=\"/docs/how_to/install\" />\n<LinkCard title=\"Learn Filtering 🔍\" href=\"/docs/tutorials/learn_filters\" />\n<LinkCard title=\"Learn Templating 📝\" href=\"/docs/tutorials/learn_templates\" />\n<LinkCard title=\"Vision 🔮\" href=\"/docs/vision\" />\n\n`code2prompt` is a powerful code ingestion tool designed to generate prompts for code analysis, generation, and other tasks. It works by traversing directories, building a tree structure, and gathering informations about each file.\n\nIt simplifies the process of combining and formatting code, making it easy to analyze, document, or refactor code using LLMs\n\nYou can use `code2prompt` the following ways:\n\n<CardGrid>\n  <Card title=\"Core\" icon=\"seti:rust\">\n    Core library blazingly fast for code ingestion\n  </Card>\n  <Card title=\"CLI\" icon=\"seti:powershell\">\n    Command Line Interface specially designed for humans\n  </Card>\n  <Card title=\"SDK\" icon=\"seti:python\">\n    Software Development Kit for AI agents and automation scripts\n  </Card>\n  <Card title=\"MCP\" icon=\"seti:folder\">\n    Model Context Protocol server for LLMs on steroids\n  </Card>\n</CardGrid>\n---\n\n## Key Features\n\n- **Generate LLM Prompts**: Quickly convert entire codebases into structured LLM prompts.\n- **Glob Pattern Filtering**: Include or exclude specific files and directories using glob patterns.\n- **Customizable Templates**: Tailor prompt generation with Handlebars templates.\n- **Token Counting**: Analyze token usage and optimize for LLMs with varying context windows.\n- **Git Integration**: Include Git diffs and commit messages in prompts for code reviews.\n- **Respects `.gitignore`**: Automatically ignores files listed in `.gitignore` to streamline prompt generation.\n\n---\n\n## Why `code2prompt`?\n\n1. **Save Time**:\n\n   - Automates the process of traversing a codebase and formatting files for LLMs.\n   - Avoids repetitive copy-pasting of code.\n\n2. **Improve Productivity**:\n\n   - Provides a structured and consistent format for code analysis.\n   - Helps identify bugs, refactor code, and write documentation faster.\n\n3. **Handle Large Codebases**:\n\n   - Designed to work seamlessly with large codebases, respecting context limits of LLMs.\n\n4. **Customizable Workflows**:\n   - Flexible options for filtering files, using templates, and generating targeted prompts.\n\n---\n\n## Example Use Cases\n\n- **Code Documentation**:\n  Automatically generate documentation for public functions, methods, and classes.\n\n- **Bug Detection**:\n  Find potential bugs and vulnerabilities by analyzing your codebase with LLMs.\n\n- **Refactoring**:\n  Simplify and optimize code by generating prompts for code quality improvements.\n\n- **Learning and Exploration**:\n  Understand new codebases by generating summaries and detailed breakdowns.\n\n- **Git Commit and PR Descriptions**:\n  Generate meaningful commit messages and pull request descriptions from Git diffs.\n"
  },
  {
    "path": "website/src/content/docs/es/blog/2025.04.11_why_I_wrote_code2prompt.mdx",
    "content": "---\ntitle: \"Por qué desarrollé Code2Prompt\"\ndate: 2025-04-11\nlastUpdated: 2025-04-11\ntags:\n  - open-source\n  - code2prompt\n  - AI\n  - Agent\nexcerpt: \"La historia detrás de code2prompt: mi búsqueda de código abierto para abordar los desafíos de contexto en los flujos de trabajo de LLM\"\nauthors:\n  - ODAncona\ncover:\n  alt: \"Una ilustración de code2prompt que agiliza el contexto del código para agentes de inteligencia artificial.\"\n  image: \"/src/assets/logo_dark_v0.0.2.svg\"\nfeatured: false\ndraft: false\n---\n\n## Introducción\n\nSiempre me ha fascinado cómo los modelos de lenguaje grandes (LLM) transforman los flujos de trabajo de codificación, generando pruebas, docstrings o incluso enviando características completas en minutos. Pero a medida que empujaba a estos modelos más lejos, surgían algunos puntos críticos:\n\n| Dificultades de planificación | Altos costos de tokens | Alucinaciones |\n| ----------------------------- | ---------------------- | ------------- |\n| 🧠 ➡️ 🤯                      | 🔥 ➡️ 💸               | 💬 ➡️ 🌀      |\n\nEs por eso que comencé a contribuir a `code2prompt`, una herramienta basada en Rust para ayudar a proporcionar el contexto adecuado a los LLM.\n\nEn este post, compartiré mi viaje y explicaré por qué estoy convencido de que `code2prompt` es relevante hoy en día y se integra tan bien, y por qué se ha convertido en mi solución para flujos de trabajo de codificación con inteligencia artificial más rápidos y mejores.\n\n## Mis primeros pasos con LLM 👣\n\nComencé a experimentar con LLM en `OpenAI Playground` con `text-davinci-003` cuando ganó popularidad en noviembre de 2023. Los modelos de lenguaje permitieron una nueva revolución. Se sintió como tener un asistente brillante que generaba pruebas unitarias y docstrings casi a pedido. Disfruté empujando a los modelos a sus límites, probando todo, desde charlas pequeñas y dilemas éticos hasta jailbreaks y tareas de codificación complejas. Sin embargo, a medida que asumí proyectos más extensos, rápidamente me di cuenta de que los modelos tenían limitaciones evidentes. Al principio, solo podía ajustar unas pocas cientos de líneas de código en la ventana de contexto, y ni siquiera entonces, los modelos a menudo luchaban por comprender el propósito o la estructura del código. Es por eso que rápidamente noté que la importancia del contexto era fundamental. Cuanto más concisas eran mis instrucciones y mejor era el contexto, mejores eran los resultados.\n\n![OpenAI Playground](/assets/blog/post1/playground.png)\n\n## Evolución del modelo 🏗️\n\nLos modelos podían producir resultados impresionantes, pero a menudo luchaban con bases de código más grandes o tareas complejas. Me encontré pasando más tiempo elaborando indicaciones que codificando. Al mismo tiempo, los modelos seguían mejorando con el lanzamiento de nuevas versiones. Aumentaron las habilidades de razonamiento y el tamaño del contexto, ofreciendo nuevas perspectivas y posibilidades. Pude ajustar casi dos mil líneas de código en la ventana de contexto, y los resultados mejoraron. Pude escribir características completas en cuestión de unas pocas iteraciones, y me asombré de lo rápido que podía obtener resultados. Estaba convencido de que los LLM eran el futuro de la codificación, y quería ser parte de esa revolución. Creo firmemente que la inteligencia artificial no nos reemplazará todavía. Pero nos asistirá en forma de asistentes donde los humanos siguen siendo los expertos en control.\n\n## Mis primeros proyectos con LLM 🚀\n\nComencé a escribir un módulo de búsqueda de rutas `ROS` para una competencia robótica, generar características para una aplicación multiplataforma `Flutter` de arquitectura limpia, y creé una pequeña aplicación web para rastrear mis gastos en `Next.js`. El hecho de que construyera esta pequeña aplicación en una noche, en un marco que nunca había tocado antes, fue un momento que cambió el juego para mí; los LLM no eran solo herramientas, sino multiplicadores. Desarrollé `bboxconverter`, un paquete para convertir cajas de límites, y la lista sigue. Los LLM pueden ayudarlo a aprender nuevas tecnologías y marcos rápidamente; eso es genial.\n\n## Un nuevo paradigma: Software 3.0 💡\n\nMe sumergí más en los LLM y comencé a construir agentes y andamiaje alrededor de ellos. Reproduje el famoso artículo [RestGPT](https://restgpt.github.io/). La idea es excelente: dar a los LLM la capacidad de llamar a algunas API REST con una especificación OpenAPI, como `Spotify` o `TMDB`. Estas capacidades introducen un nuevo paradigma de programación de software, que me gusta llamar **Software 3.0**.\n\n| Software 1.0     | Software 2.0        | Software 3.0 |\n| ---------------- | ------------------- | ------------ |\n| Basado en reglas | Impulsado por datos | Agente       |\n\nLa misma idea impulsó el protocolo [MCP](https://modelcontextprotocol.io/introduction), que permite a los LLM llamar a herramientas y recursos directamente de manera fluida porque, por diseño, la herramienta necesita una descripción para ser llamada por el LLM en el opuesto de las API REST que no requieren necesariamente una especificación OpenAPI.\n\n## Las limitaciones de los LLM 🧩\n\n### Alucinaciones 🌀\n\nMientras reproducía el famoso artículo `RESTGPT`, noté algunas limitaciones graves de los LLM. Los autores del artículo encontraron los mismos problemas que yo: los LLM estaban **alucinando**. Generan código que no se implementa, inventando argumentos y simplemente siguiendo las instrucciones al pie de la letra sin aprovechar el sentido común. Por ejemplo, en el código base original de RestGPT, los autores preguntaron en [la indicación del llamador](https://github.com/Yifan-Song793/RestGPT/blob/main/model/caller.py).\n\n> \"para no ser astuto y hacer pasos que no existen en el plan\".\n\nEncontré esta afirmación divertida y muy interesante porque fue la primera vez que encontré a alguien instruyendo a los LLM para que no alucinaran.\n\n### Tamaño de contexto limitado 📏\n\nOtra limitación fue el tamaño del contexto; los LLM se desempeñan bien para encontrar la aguja en el pajar, pero luchan por entenderlo. Cuando le das demasiado contexto a los modelos de lenguaje, tienden a perderse en los detalles y perder de vista la imagen general, lo cual es molesto y requiere una dirección constante. La forma en que me gusta pensar al respecto es de manera similar a [la maldición de la dimensionalidad](https://towardsdatascience.com/curse-of-dimensionality-a-curse-to-machine-learning-c122ee33bfeb/). Reemplaza la palabra \"dimensión\" o \"característica\" por \"contexto\", y obtienes la idea.\n\n![Maldición de la dimensionalidad](/assets/blog/post1/curse_of_dimensionality.png)\n\nCuanto más contexto le das al LLM, más difícil es encontrar la respuesta correcta. Surgí con una frase agradable para resumir esta idea:\n\n> Proporcionar la menor cantidad de contexto posible pero la necesaria\n\nEsto está fuertemente inspirado en la famosa [cita de Alain Berset](https://www.lematin.ch/story/alain-berset-la-formule-qui-defie-le-temps-166189802108), un político suizo 🇨🇭 que dijo durante el bloqueo de COVID-19:\n\n> \"Queremos actuar lo más rápido posible, pero también lo más lentamente necesario\".\n\nEsto representa la idea de compromiso y se aplica al tamaño del contexto de los LLM.\n\n## Buscando una mejor manera: code2prompt 🔨\n\nPor lo tanto, necesitaba una forma de cargar, filtrar y organizar mi contexto de código rápidamente proporcionando la menor cantidad posible de contexto con la mejor calidad posible. Intenté copiar manualmente archivos o fragmentos en indicaciones, pero eso se volvió engorroso y propenso a errores. Sabía que automatizar el proceso tedioso de forjar el contexto para hacer mejores indicaciones sería útil. Luego, un día, escribí \"code2prompt\" en Google, esperando encontrar una herramienta que canalizara mi código directamente en indicaciones.\n\nY he aquí, descubrí un proyecto **basado en Rust** de [Mufeed](https://www.reddit.com/r/rust/comments/1bghroh/i_made_code2prompt_a_cli_tool_to_convert_your/) llamado _code2prompt_, con alrededor de 200 estrellas en GitHub. Todavía era básico en ese momento: una herramienta CLI simple con capacidad de filtro limitada y plantillas. Vi un enorme potencial y me uní directamente para contribuir, implementando la coincidencia de patrones glob, entre otras características, y pronto me convertí en el principal contribuyente.\n\n## Visión e integraciones 🔮\n\nHoy en día, hay varias formas de proporcionar contexto a los LLM. Generar a partir del contexto más grande, utilizando la generación aumentada de recuperación (RAG), [comprimiendo el código](https://www.all-hands.dev/blog/openhands-context-condensensation-for-more-efficient-ai-agents), o incluso utilizando una combinación de estos métodos. La creación de contexto es un tema candente que evolucionará rápidamente en los próximos meses. Sin embargo, mi enfoque es **KISS**: Manténlo simple, estúpido. La mejor forma de proporcionar contexto a los LLM es utilizar la forma más simple y eficiente posible. Forjas precisamente el contexto que necesitas; es determinista, a diferencia de RAG.\n\nEs por eso que decidí impulsar `code2prompt` más lejos como una herramienta simple que se puede utilizar en cualquier flujo de trabajo. Quería hacerlo fácil de usar, fácil de integrar y fácil de extender. Es por eso que agregué nuevas formas de interactuar con la herramienta.\n\n- **Núcleo**: El núcleo de `code2prompt` es una biblioteca de Rust que proporciona la funcionalidad básica para forjar contexto a partir de tu base de código. Incluye una API simple para cargar, filtrar y organizar tu contexto de código.\n- **CLI:** La interfaz de línea de comandos es la forma más simple de usar `code2prompt`. Puedes forjar contexto a partir de tu base de código y canalizarlo directamente en tus indicaciones.\n- **API de Python:** La API de Python es un envoltorio simple alrededor de la CLI que te permite usar `code2prompt` en tus scripts y agentes de Python. Puedes forjar contexto a partir de tu base de código y canalizarlo directamente en tus indicaciones.\n- **MCP**: El servidor MCP de `code2prompt` permite a los LLM usar `code2prompt` como una herramienta, lo que les permite ser capaces de forjar el contexto.\n\nLa visión se describe más a fondo en la [página de visión](/docs/vision) en el documento.\n\n## Integración con agentes 👤\n\nCreo que los agentes futuros necesitarán tener una forma de ingerir contexto, y `code2prompt` es la forma simple y eficiente de hacerlo para repositorios textuales como bases de código, documentación o notas. Un lugar propicio para usar `code2prompt` sería en una base de código con convenciones de nombres significativas. Por ejemplo, en la arquitectura limpia, hay una clara separación de preocupaciones y capas. El contexto relevante suele residir en diferentes archivos y carpetas pero comparte el mismo nombre. Este es un caso de uso perfecto para `code2prompt`, donde puedes usar el patrón glob para agarrar los archivos relevantes.\n\n**Basado en patrones glob:** Selecciona o excluye archivos con minimal molestia.\n\nAdemás, la biblioteca central está diseñada como un administrador de contexto estatal, lo que te permite agregar o eliminar archivos a medida que evoluciona tu conversación con el LLM. Esto es particularmente útil cuando proporcionas contexto para una tarea o objetivo específico. Puedes agregar o eliminar archivos del contexto sin volver a ejecutar el proceso.\n\n**Contexto estatal:** Agrega o elimina archivos a medida que evoluciona tu conversación con el LLM.\n\nEstas capacidades hacen que `code2prompt` sea un ajuste perfecto para flujos de trabajo basados en agentes. El servidor MCP permite una integración perfecta con marcos de agentes de inteligencia artificial populares como [Aider](https://github.com/paul-gauthier/aider), [Goose](https://block.github.io/goose/), o [Cline](https://github.com/jhillyerd/cline). Dejan que manejen objetivos complejos mientras `code2prompt` entrega el contexto de código perfecto.\n\n## Por qué Code2prompt importa ✊\n\nA medida que los LLM evolucionan y las ventanas de contexto se expanden, puede parecer que simplemente forzar a los repositorios enteros en indicaciones es suficiente. Sin embargo, **los costos de tokens** y la **coherencia de las indicaciones** siguen siendo importantes obstáculos para las pequeñas empresas y los desarrolladores. Centrándose solo en el código que importa, `code2prompt` mantiene tu uso de LLM eficiente, rentable y menos propenso a la alucinación.\n\n**En resumen:**\n\n- **Reduce las alucinaciones** proporcionando la cantidad adecuada de contexto\n- **Reduce los costos de tokens** mediante la curación manual del contexto adecuado necesario\n- **Mejora el rendimiento de LLM** proporcionando la cantidad adecuada de contexto\n- Integra la pila agéntica como un alimentador de contexto para repositorios textuales\n\n## Puedes unirte ¡Es de código abierto! 🌐\n\n¡Todos los nuevos contribuyentes son bienvenidos! ¡Ven a bordo si estás interesado en Rust, forjando herramientas innovadoras de inteligencia artificial o simplemente quieres un mejor flujo de trabajo para tus indicaciones basadas en código!\n\nGracias por leer, y espero que mi historia te haya inspirado a revisar code2prompt. Ha sido un viaje increíble, y apenas está empezando.\n\n**Olivier D'Ancona**\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/explanations/glob_pattern_filter.mdx",
    "content": "---\ntitle: Cómo funciona el filtro de patrones Glob\ndescription: Cómo Code2Prompt decide qué archivos conservar o descartar usando globs de inclusión (-i) y exclusión (-e).\n---\n\nCode2Prompt utiliza patrones glob para incluir o excluir archivos y directorios, funcionando de manera similar a herramientas como tree o grep. Te permite pasar dos _listas_ independientes de patrones glob:\n\n- **lista de inclusión** (`--include` o `-i`) - \"estos patrones permiten archivos\"\n- **lista de exclusión** (`--exclude` o `-e`) - \"estos patrones prohíben archivos\"\n\nCode2prompt debe decidir, para cada archivo en el proyecto, si se conserva o se descarta. Esta página explica las reglas y las decisiones de diseño que las respaldan.\n\n---\n\n## 1. Conjuntos y Símbolos\n\nA lo largo de la explicación usamos la notación de conjuntos habitual\n\n| Símbolo                           | Significado                                                                |\n| --------------------------------- | -------------------------------------------------------------------------- |\n| $A$                               | conjunto de archivos que coinciden con **al menos un** patrón de inclusión |\n| $B$                               | conjunto de archivos que coinciden con **al menos un** patrón de exclusión |\n| $\\Omega$                          | todo el árbol del proyecto (el _universo_)                                 |\n| $C = A \\cap B$                    | archivos que coinciden con ambas listas (la _superposición_)               |\n| $D = \\Omega \\setminus (A \\cup B)$ | archivos que no coinciden con ninguna lista                                |\n\n---\n\n## 2. Cuatro Situaciones\n\n### Resumen de las cuatro situaciones\n\n| Lista de inclusión | Lista de exclusión | Archivos conservados |\n| ------------------ | ------------------ | -------------------- |\n| A = ∅              | B = ∅              | Ω                    |\n| A = ∅              | B ≠ ∅              | ¬B                   |\n| A ≠ ∅              | B = ∅              | A                    |\n| A ≠ ∅              | B ≠ ∅              | A \\ B                |\n\n1. **Sin lista de inclusión, sin lista de exclusión**\n\n   Si no se especifican patrones, se conservan todos los archivos (`Ω`).\n\n2. **Solo lista de exclusión**\n\n   En este caso, Code2Prompt actúa como una lista negra, eliminando archivos que coinciden con los patrones excluidos (` Ω \\ B = ¬B`).\n\n3. **Solo lista de inclusión**\n\n   Si solo se especifica una lista de inclusión, Code2Prompt actúa como una lista blanca, conservando solo archivos que coinciden con los patrones incluidos (`A`).\n\n4. **Listas de inclusión _y_ exclusión**\n\n   Si se especifican ambas listas, Code2Prompt conserva archivos que coinciden con los patrones de inclusión, pero elimina aquellos que coinciden con los patrones de exclusión (`A \\ B`).\n\n---\n\n## 3. Más sobre la superposición\n\nCon ambas listas presentes (`A ≠ ∅`, `B ≠ ∅`) tienes cuatro posibilidades lógicas\npara la superposición `C` y el resto `D`.\n\n| ¿Quieres `C`? | ¿Quieres `D`? | ¿Razonable?                                                     |\n| ------------- | ------------- | --------------------------------------------------------------- |\n| No            | No            | Comportamiento predeterminado (`A \\ B`)                         |\n| Sí            | No            | Mismo comportamiento que el caso 3 (`A`)                        |\n| No            | Sí            | sorprendente (\"descartar lo que pedí `C`, conservar lo que no\") |\n| Sí            | Sí            | Mismo comportamiento que el caso 1 (`Ω`)                        |\n\nPor esta razón se eliminó la opción `--include-priority`. Porque sería el mismo resultado que si solo tuvieras una lista de inclusión (caso 3).\n\n## 4. Tabla de referencia rápida\n\n| Quieres conservar…                                       | Usa                |\n| -------------------------------------------------------- | ------------------ |\n| todo                                                     | sin `-i`, sin `-e` |\n| todo _excepto_ algunos patrones                          | solo `-e`          |\n| _solo_ lo que coincide con los patrones                  | solo `-i`          |\n| lo que coincide con `-i`, menos lo que coincide con `-e` | `-i` **y** `-e`    |\n\n---\n\nEste diseño mantiene el modelo mental simple:\n\n- La lista de inclusión es una lista blanca tan pronto como existe.\n- La lista de exclusión es una lista negra superpuesta encima.\n- La superposición se descarta por defecto\n"
  },
  {
    "path": "website/src/content/docs/es/docs/explanations/glob_patterns.md",
    "content": "---\ntitle: Entendiendo patrones Glob\ndescription: Una explicación detallada de los patrones Glob y cómo se utilizan en Code2Prompt.\n---\n\nLos patrones Glob son una forma sencilla pero poderosa de coincidir nombres de archivos y rutas utilizando caracteres comodín. Se utilizan comúnmente en interfaces de línea de comandos y lenguajes de programación para especificar conjuntos de nombres de archivos o directorios. A continuación, se presenta un desglose de los patrones Glob más comúnmente utilizados:\n\n## Comodines básicos\n\n- `*`: Coincide con cualquier número de caracteres, incluidos cero caracteres.\n  - Ejemplo: `*.txt` coincide con todos los archivos que terminan con `.txt`.\n\n- `?`: Coincide exactamente con un carácter.\n  - Ejemplo: `file?.txt` coincide con `file1.txt`, `fileA.txt`, pero no con `file10.txt`.\n\n- `[]`: Coincide con cualquiera de los caracteres incluidos.\n  - Ejemplo: `file[1-3].txt` coincide con `file1.txt`, `file2.txt`, `file3.txt`.\n\n- `[!]` o `[^]`: Coincide con cualquier carácter no incluido.\n  - Ejemplo: `file[!1-3].txt` coincide con `file4.txt`, `fileA.txt`, pero no con `file1.txt`.\n\n## Patrones avanzados\n\n- `**`: Coincide con cualquier número de directorios y subdirectorios de forma recursiva.\n  - Ejemplo: `**/*.txt` coincide con todos los archivos `.txt` en el directorio actual y todos los subdirectorios.\n\n- `{}`: Coincide con cualquiera de los patrones separados por comas incluidos.\n  - Ejemplo: `file{1,2,3}.txt` coincide con `file1.txt`, `file2.txt`, `file3.txt`.\n\n## Ejemplos\n\n1. **Coincidir con todos los archivos de texto en un directorio:**\n\n   ```sh\n   *.txt\n   ```\n\n2. **Coincidir con todos los archivos con un solo dígito antes de la extensión:**\n\n   ```sh\n   file?.txt\n   ```\n\n3. **Coincidir con archivos con extensiones `.jpg` o `.png`:**\n\n   ```sh\n   *.{jpg,png}\n   ```\n\n4. **Coincidir con todos los archivos `.txt` en cualquier subdirectorio:**\n\n   ```sh\n   **/*.txt\n   ```\n\n5. **Coincidir con archivos que comienzan con `a` o `b` y terminan con `.txt`:**\n\n   ```sh\n   {a,b}*.txt\n   ```\n\n## Casos de uso\n\n- **Herramientas de línea de comandos:** Los patrones Glob se utilizan ampliamente en herramientas de línea de comandos como `ls`, `cp`, `mv` y `rm` para especificar varios archivos o directorios.\n- **Lenguajes de programación:** Lenguajes como Python, JavaScript y Ruby admiten patrones Glob para la coincidencia de archivos a través de bibliotecas como `glob` en Python.\n- **Sistemas de compilación:** Herramientas como Makefile utilizan patrones Glob para especificar archivos fuente y dependencias.\n\n## Conclusión\n\nLos patrones Glob proporcionan una forma flexible e intuitiva de coincidir nombres de archivos y rutas, lo que los hace invaluables para tareas de scripting, automatización y administración de archivos. Comprender y utilizar estos patrones puede mejorar significativamente su productividad y eficiencia en el manejo de archivos y directorios.\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/explanations/tokenizers.md",
    "content": "---\ntitle: Tokenización en Code2Prompt\ndescription: Aprende sobre la tokenización y cómo Code2Prompt procesa texto para LLMs.\n---\n\nCuando se trabaja con modelos de lenguaje, el texto debe transformarse en un formato que el modelo pueda entender: **tokens**, que son secuencias de números. Esta transformación se realiza mediante un **tokenizador**.\n\n---\n\n## ¿Qué es un Tokenizador?\n\nUn tokenizador convierte texto sin procesar en tokens, que son los bloques de construcción para cómo los modelos de lenguaje procesan la entrada. Estos tokens pueden representar palabras, subpalabras o incluso caracteres individuales, dependiendo del diseño del tokenizador.\n\nPara `code2prompt`, utilizamos el tokenizador **tiktoken**. Es eficiente, robusto y optimizado para modelos de OpenAI.\nPuedes explorar su funcionalidad en el repositorio oficial\n\n👉 [Repositorio de GitHub de tiktoken](https://github.com/openai/tiktoken)\n\nSi deseas aprender más sobre tokenizadores en general, consulta\n\n👉 [Guía de Tokenización de Mistral](https://docs.mistral.ai/guides/tokenization/).\n\n## Implementación en `code2prompt`\n\nLa tokenización se implementa utilizando [`tiktoken-rs`](https://github.com/zurawiki/tiktoken-rs). `tiktoken` admite estos codificaciones utilizadas por los modelos de OpenAI:\n\n| Argumento de CLI | Nombre de codificación  | Modelos de OpenAI                                                         |\n|----|-----------------------| ------------------------------------------------------------------------- |\n|`cl100k`| `cl100k_base`           | Modelos de ChatGPT, `text-embedding-ada-002`                              |\n|`p50k`| `p50k_base`             | Modelos de código, `text-davinci-002`, `text-davinci-003`                 |\n|`p50k_edit`| `p50k_edit`             | Utilizar para modelos de edición como `text-davinci-edit-001`, `code-davinci-edit-001` |\n|`r50k`| `r50k_base` (o `gpt2`) | Modelos de GPT-3 como `davinci`                                            |\n|`gpt2`| `o200k_base`            | Modelos de GPT-4o                                                         |\n\nPara obtener más contexto sobre los diferentes tokenizadores, consulta [OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/66b988407d8d13cad5060a881dc8c892141f2d5c/examples/How_to_count_tokens_with_tiktoken.ipynb)\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/how_to/filter_files.md",
    "content": "---\ntitle: Filtrado de Archivos en Code2Prompt\ndescription: Una guía paso a paso para incluir o excluir archivos utilizando diferentes métodos de filtrado.\n---\n\n\n## Uso\n\nGenerar un prompt desde un directorio de base de código:\n\n```sh\ncode2prompt path/to/codebase\n```\n\nUtilizar un archivo de plantilla Handlebars personalizado:\n\n```sh\ncode2prompt path/to/codebase -t path/to/template.hbs\n```\n\nFiltrar archivos utilizando patrones glob:\n\n```sh\ncode2prompt path/to/codebase --include=\"*.rs,*.toml\"\n```\n\nExcluir archivos utilizando patrones glob:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.txt,*.md\"\n```\n\nExcluir archivos/carpeta del árbol de origen según patrones de exclusión:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.npy,*.wav\" --exclude-from-tree\n```\n\nMostrar el recuento de tokens del prompt generado:\n\n```sh\ncode2prompt path/to/codebase --tokens\n```\n\nEspecificar un tokenizador para el recuento de tokens:\n\n```sh\ncode2prompt path/to/codebase --tokens --encoding=p50k\n```\n\nTokenizadores compatibles: `cl100k`, `p50k`, `p50k_edit`, `r50k_bas`.\n> [!NOTE]  \n> Consulte [Tokenizadores](#tokenizadores) para obtener más detalles.\n\nGuardar el prompt generado en un archivo de salida:\n\n```sh\ncode2prompt path/to/codebase --output=output.txt\n```\n\nImprimir la salida como JSON:\n\n```sh\ncode2prompt path/to/codebase --json\n```\n\nLa salida JSON tendrá la siguiente estructura:\n\n```json\n{\n  \"prompt\": \"<Prompt generado>\", \n  \"directory_name\": \"codebase\",\n  \"token_count\": 1234,\n  \"model_info\": \"Modelos ChatGPT, text-embedding-ada-002\",\n  \"files\": []\n}\n```\n\nGenerar un mensaje de commit de Git (para archivos en staging):\n\n```sh\ncode2prompt path/to/codebase --diff -t templates/write-git-commit.hbs\n```\n\nGenerar una solicitud de Pull Request con comparación de ramas (para archivos en staging):\n\n```sh\ncode2prompt path/to/codebase --git-diff-branch 'main, development' --git-log-branch 'main, development' -t templates/write-github-pull-request.hbs\n```\n\nAgregar números de línea a bloques de código fuente:\n\n```sh\ncode2prompt path/to/codebase --line-number\n```\n\nDeshabilitar el ajuste de código dentro de bloques de código markdown:\n\n```sh\ncode2prompt path/to/codebase --no-codeblock\n```\n\n- Reescribir el código en otro lenguaje.\n- Encontrar errores/vulnerabilidades de seguridad.\n- Documentar el código.\n- Implementar nuevas características.\n\n> Inicialmente escribí esto para uso personal para aprovechar la ventana de contexto de 200K de Claude 3.0 y resultó ser bastante útil, así que decidí open-sourcearlo.\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/how_to/install.mdx",
    "content": "---\ntitle: Instalación de Code2Prompt\ndescription: Una guía de instalación completa para Code2Prompt en diferentes sistemas operativos.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Steps } from \"@astrojs/starlight/components\";\n\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\n\n<Card title=\"Resumen de la Guía\">\n  Bienvenido a la guía de instalación de `Code2Prompt`. Este documento\n  proporciona instrucciones paso a paso para instalarlo en varias plataformas,\n  incluyendo Windows, macOS y Linux.\n</Card>\n\n**TL;DR**\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n## Requisitos previos\n\nAsegúrese de que [Rust](https://www.rust-lang.org/tools/install) y cargo estén instalados en su sistema.\n\n```sh\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\nEsta es la forma oficial de instalar la última versión estable de Rust y Cargo.\nAsegúrese de refrescar su variable `PATH` después de instalar Rust. Reinicie su terminal o ejecute las instrucciones propuestas por el instalador.\n\n```sh\nsource $HOME/.cargo/env\n```\n\nPuede verificar que todo esté instalado correctamente ejecutando:\n\n```sh\ncargo --version\ngit --version\n```\n\n## Interfaz de Línea de Comando (CLI) 👨‍💻\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n#### 🧪 Instalar la última versión (no publicada) desde GitHub\n\nSi desea las últimas características o correcciones antes de que se publiquen en crates.io:\n\n```sh\ncargo install --git https://github.com/mufeedvh/code2prompt\n```\n\n### Compilación desde código fuente\n\nIdeal para desarrolladores que desean compilar desde código fuente o contribuir al proyecto.\n\n<Steps>\n\n1.  🛠️ Instalar requisitos previos :\n\n    - [Rust](https://www.rust-lang.org/tools/install) y Cargo\n    - [Git](https://git-scm.com/downloads)\n\n2.  📥 Clonar el repositorio :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt\n    ```\n\n3.  📦 Instalar el binario :\n\n    Para compilar e instalar desde código fuente:\n\n    ```sh\n    cargo install --path crates/code2prompt\n    ```\n\n    Para compilar el binario sin instalarlo:\n\n    ```sh\n    cargo build --release\n    ```\n\n    El binario estará disponible en el directorio `target/release`.\n\n4.  🚀 Ejecutarlo :\n\n    ```sh\n    code2prompt --help\n    ```\n\n</Steps>\n\n### Lanzamientos binarios\n\nMejor para usuarios que desean utilizar la última versión sin compilar desde código fuente.\n\nDescargue el último binario para su sistema operativo desde [Lanzamientos](https://github.com/mufeedvh/code2prompt/releases).\n\n⚠️ Los lanzamientos binarios pueden retrasarse respecto a la última versión de GitHub. Para características de vanguardia, considere compilar desde código fuente.\n\n### AUR\n\nEspecíficamente para usuarios de Arch Linux, `code2prompt` está disponible en AUR.\n\n`code2prompt` está disponible en [`AUR`](https://aur.archlinux.org/packages?O=0&K=code2prompt). Instálelo mediante cualquier ayudante de AUR.\n\n```sh\nparu/yay -S code2prompt\n```\n\n### Nix\n\nSi está utilizando Nix, puede instalarlo utilizando nix-env o nix profile.\n\n```sh\n# sin flakes:\nnix-env -iA nixpkgs.code2prompt\n# con flakes:\nnix profile install nixpkgs#code2prompt\n```\n\n## Kit de Desarrollo de Software (SDK) 🐍\n\n### Pypi\n\nPuede descargar los enlaces de Python desde Pypi\n\n```sh\npip install code2prompt_rs\n```\n\n### Compilación desde código fuente\n\n<Steps>\n\n1.  🛠️ Instalar requisitos previos :\n\n    - [Rust](https://www.rust-lang.org/tools/install) y Cargo\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 Clonar el repositorio :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt/crates/code2prompt-python\n    ```\n\n3.  📦 Instalar dependencias :\n\n    El comando `rye` creará un entorno virtual e instalará todas las dependencias.\n\n    ```sh\n    rye sync\n    ```\n\n4.  ⚙️ Compilar el paquete :\n\n    Desarrollará el paquete en el entorno virtual ubicado en la carpeta `.venv` en la raíz del proyecto.\n\n    ```sh\n    rye run maturin develop -r\n    ```\n\n</Steps>\n\n## Protocolo de Contexto de Modelo (MCP) 🤖\n\n### Instalación automatizada\n\nEl servidor MCP de `code2prompt` estará disponible pronto en registros de MCP.\n\n### Instalación manual\n\nEl servidor MCP de `code2prompt` sigue siendo un prototipo y se integrará al repositorio principal pronto.\n\nPara ejecutar el servidor MCP, localmente para usarlo con `Cline`, `Goose` o `Aider`:\n\n<Steps>\n\n1.  🛠️ Instalar requisitos previos :\n\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 Clonar el repositorio :\n\n    ```sh\n    git clone https://github.com/odancona/code2prompt-mcp.git\n    cd code2prompt-mcp\n    ```\n\n3.  📦 Instalar dependencias :\n\n    El comando `rye` creará un entorno virtual e instalará todas las dependencias en la carpeta `.venv`.\n\n    ```sh\n    rye sync\n    ```\n\n4.  🚀 Ejecutar el servidor :\n\n    El servidor MCP ahora está instalado. Puede ejecutarlo utilizando:\n\n    ```sh\n    . .venv/bin/activate\n    python -m src/code2prompt_mcp/main.py\n    ```\n\n5.  🔌 Integrar con Agentes :\n\n            Por ejemplo, puede integrarlo con `Cline`, utilizando una configuración similar:\n\n            ```json\n            {\n              \"mcpServers\": {\n                \"code2prompt\": {\n                  \"command\": \"bash\",\n                  \"args\": [\n                    \"-c\",\n                    \"cd /home/olivier/projet/code2prompt-mcp && rye run python /home/olivier/projet/code2prompt-mcp/src/code2prompt_mcp/main.py\"\n                  ],\n                  \"env\": {}\n                }\n              }\n            }\n            ```\n\n</Steps>\n\nNote that I did not change any of the code blocks, commands or variable names, I only translated the content. Also, I did not modify the frontmatter and metadata at the top of the file.\n\nPlease let me know if you need any further assistance.\n\nAlso, I adjusted the relative paths by adding one \"../\" level when they start with \"../\" but there were none in this text.\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/how_to/ssh.md",
    "content": "---\ntitle: Uso de Code2prompt CLI con SSH\ndescription: Una guía para usar Code2Prompt CLI con SSH para análisis remoto de base de código.\n---\n\n## ¿Por qué no funciona?\n\nCuando intentas ejecutar el CLI de `code2prompt` en un servidor remoto a través de SSH, el comando no puede encontrar el portapapeles. Esto se debe a que el CLI de `code2prompt` utiliza el portapapeles para copiar el mensaje generado, y las sesiones de SSH normalmente no tienen acceso al portapapeles local.\n\n## Solución\n\nPara usar el CLI de `code2prompt` con SSH, puedes redirigir la salida a un archivo en lugar de copiarla al portapapeles. De esta manera, aún puedes generar el mensaje y guardarlo para su uso posterior.\n\nUtiliza la opción `--output-file` para especificar el archivo de salida donde se guardará el mensaje generado. Por ejemplo:\n\n```sh\nssh user@remote-server \"code2prompt path/to/codebase -O output.txt\"\n```\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/references/command_line_options.md",
    "content": "---\ntitle: Opciones de línea de comandos de Code2Prompt\ndescription: Una guía de referencia para todas las opciones de CLI disponibles en Code2Prompt.\n---\n\n# Opciones de línea de comandos\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/references/default_template.md",
    "content": "---\ntitle: Plantilla Predeterminada para Code2Prompt\ndescription: Obtenga información sobre la estructura de la plantilla predeterminada utilizada en Code2Prompt.\n---\n\n# Plantilla Predeterminada\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/tutorials/getting_started.mdx",
    "content": "---\ntitle: Getting Started with Code2Prompt\ndescription: A comprehensive tutorial introducing Code2Prompt's core functionality and its use across CLI, SDK, and MCP integrations.\n---\n\nimport { Aside } from \"@astrojs/starlight/components\";\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\n\n<Card title=\"Descripción del Tutorial\">\n  Bienvenido a Code2Prompt. Este tutorial proporciona una introducción integral\n  al uso de Code2Prompt para generar indicaciones listas para IA a partir de sus\n  bases de código. Exploraremos su funcionalidad central y demostraremos su uso\n  en diferentes métodos de integración: Interfaz de Línea de Comando (CLI), Kit\n  de Desarrollo de Software (SDK) y Protocolo de Contexto de Modelo (MCP).\n</Card>\n\n## ¿Qué es Code2Prompt?\n\nCode2Prompt es una herramienta versátil diseñada para cerrar la brecha entre su base de código y Modelos de Lenguaje grandes (LLM). Extrae inteligentemente fragmentos de código relevantes, aplica un filtrado potente y formatea la información en indicaciones estructuradas optimizadas para el consumo de LLM. Esto simplifica tareas como documentación de código, detección de errores, refactoring y más.\n\nCode2Prompt ofrece diferentes puntos de integración:\n\n<Tabs>\n  <TabItem label=\"Núcleo\" icon=\"seti:rust\">\n    Una biblioteca de núcleo de Rust que proporciona la base para la ingestión\n    de código y las indicaciones\n  </TabItem>\n  <TabItem label=\"CLI\" icon=\"seti:powershell\">\n    Una interfaz de línea de comandos fácil de usar para la generación rápida de\n    indicaciones. Ideal para uso interactivo y tareas puntuales.\n  </TabItem>\n  <TabItem label=\"SDK\" icon=\"seti:python\">\n    Un kit de desarrollo de software (SDK) potente para una integración perfecta\n    en sus proyectos de Python. Perfecto para automatizar la generación de\n    indicaciones dentro de flujos de trabajo más grandes.\n  </TabItem>\n  <TabItem label=\"MCP\" icon=\"seti:db\">\n    Un servidor de Protocolo de Contexto de Modelo (MCP) para integración\n    avanzada con agentes de LLM. Permite interacciones sofisticadas y en tiempo\n    real con su base de código.\n  </TabItem>\n</Tabs>\n\n## 📥 Instalación\n\nPara obtener instrucciones de instalación detalladas para todos los métodos (CLI, SDK, MCP), consulte la [Guía de Instalación](/../docs/how_to/install) completa.\n\n## 🏁 Generación de Indicaciones: Un ejemplo de CLI\n\nComencemos con un ejemplo sencillo utilizando la CLI. Cree un proyecto de muestra:\n\n```bash\nmkdir -p my_project/{src,tests}\ntouch my_project/src/main.rs my_project/tests/test_1.rs\necho 'fn main() { println!(\"Hello, world!\"); }' > my_project/src/main.rs\n```\n\nAhora, genere una indicación:\n\n```bash\ncode2prompt my_project\n```\n\nEsto copia una indicación en su portapapeles. Puede personalizar esto:\n\n- **Filtrado:** `code2prompt my_project --include=\"*.rs\" --exclude=\"tests/*\"` (incluye solo archivos `.rs`, excluye el directorio `tests`)\n- **Archivo de Salida:** `code2prompt my_project --output-file=my_prompt.txt`\n- **Salida JSON:** `code2prompt my_project -O json` (salida JSON estructurada)\n- **Plantillas personalizadas:** `code2prompt my_project -t my_template.hbs` (requiere crear `my_template.hbs`)\n\nConsulte los tutoriales [Aprender filtrado de contexto ](/../docs/tutorials/learn_filters) y [Aprender plantillas de Handlebar ](/../docs/tutorials/learn_templates) para obtener más información sobre usos avanzados.\n\n## 🐍 Integración con SDK (Python)\n\nPara obtener control programático, utilice el SDK de Python:\n\n```python\nfrom code2prompt_rs import Code2Prompt\n\nconfig = {\n    \"path\": \"my_project\",\n    \"include_patterns\": [\"*.rs\"],\n    \"exclude_patterns\": [\"tests/*\"],\n}\n\nc2p = Code2Prompt(**config)\nprompt = c2p.generate_prompt()\nprint(prompt)\n```\n\nEsto requiere instalar el SDK (`pip install code2prompt_rs`). Consulte la documentación del SDK para obtener más detalles.\n\n## 🤖 Integración con el servidor MCP (Avanzado)\n\nPara una integración avanzada con agentes de LLM, ejecute el servidor MCP de `code2prompt` (consulte la guía de instalación para obtener detalles). Esto permite a los agentes solicitar contexto de código dinámicamente. Esta es una característica avanzada y se proporciona más documentación en el sitio web del proyecto.\n\n<Card title=\"Próximos Pasos\">\n  Explore los tutoriales y la documentación avanzados para dominar las\n  capacidades de Code2Prompt e integrarlo en sus flujos de trabajo.\n</Card>\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/tutorials/learn_filters.mdx",
    "content": "---\ntitle: Aprender filtrado de contexto con Code2Prompt\ndescription: Aprende a excluir o incluir archivos en tus indicaciones LLM utilizando opciones de filtrado potentes.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"Resumen del tutorial\">\n  Este tutorial demuestra cómo utilizar la herramienta de patrones glob en\n  `code2prompt` CLI para filtrar y gestionar archivos según patrones de\n  inclusión y exclusión.\n</Card>\n\nLos patrones glob funcionan de manera similar a herramientas como `tree` o `grep`,\nproporcionando capacidades de filtrado potentes. Consulta la [explicación detallada](/docs/explanations/glob_patterns) para obtener más información.\n\n---\n\n## Requisitos previos\n\nAsegúrate de tener `code2prompt` instalado. Si aún no lo has instalado, consulta la [Guía de instalación](/docs/how_to/install).\n\n---\n\n## Entendiendo patrones de inclusión y exclusión\n\nLos patrones glob te permiten especificar reglas para filtrar archivos y directorios.\n\n- **Patrones de inclusión** (`--include`): Especifica los archivos y directorios que deseas incluir.\n- **Patrones de exclusión** (`--exclude`): Especifica los archivos y directorios que deseas excluir.\n- **Prioridad** (`--include-priority`): Resuelve conflictos entre patrones de inclusión y exclusión.\n\n---\n\n## Configuración del entorno\n\nPara practicar con patrones glob, creemos una estructura de carpetas de muestra con algunos archivos.\n\n### Script Bash para generar la estructura de prueba\n\nEjecuta este script para configurar una estructura de directorio temporal:\n\n```bash\n#!/bin/bash\n\n# Crea el directorio base\nmkdir -p test_dir/{lowercase,uppercase,.secret}\n\n# Crea archivos en la estructura\necho \"content foo.py\" > \"test_dir/lowercase/foo.py\"\necho \"content bar.py\" > \"test_dir/lowercase/bar.py\"\necho \"content baz.py\" > \"test_dir/lowercase/baz.py\"\necho \"content qux.txt\" > \"test_dir/lowercase/qux.txt\"\necho \"content corge.txt\" > \"test_dir/lowercase/corge.txt\"\necho \"content grault.txt\" > \"test_dir/lowercase/grault.txt\"\n\necho \"CONTENT FOO.py\" > \"test_dir/uppercase/FOO.PY\"\necho \"CONTENT BAR.py\" > \"test_dir/uppercase/BAR.PY\"\necho \"CONTENT BAZ.py\" > \"test_dir/uppercase/BAZ.PY\"\necho \"CONTENT QUX.txt\" > \"test_dir/uppercase/QUX.TXT\"\necho \"CONTENT CORGE.txt\" > \"test_dir/uppercase/CORGE.TXT\"\necho \"CONTENT GRAULT.txt\" > \"test_dir/uppercase/GRAULT.TXT\"\n\necho \"top secret\" > \"test_dir/.secret/secret.txt\"\n```\n\nPara limpiar la estructura más tarde, ejecuta:\n\n```bash\nrm -rf test_dir\n```\n\nCreará la siguiente estructura de directorio:\n\nimport { FileTree } from \"@astrojs/starlight/components\";\n\n<FileTree>\n- test_dir \n  - lowercase \n    - foo.py \n    - bar.py \n    - baz.py \n    - qux.txt \n    - corge.txt \n    - grault.txt\n  - uppercase \n    - FOO.py \n    - BAR.py \n    - BAZ.py \n    - QUX.txt \n    - CORGE.txt \n    - GRAULT.txt \n  - .secret \n    - secret.txt\n</FileTree>\n\n---\n\n## Ejemplos: filtrar archivos con patrones de inclusión y exclusión\n\n### Caso 1: Sin inclusión, sin exclusión\n\nComando:\n\n```bash\ncode2prompt test_dir\n```\n\n#### Resultado\n\nSe incluyen todos los archivos:\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n- `.secret/secret.txt`\n\n---\n\n### Caso 2: Excluir tipos de archivo específicos\n\nExcluir archivos `.txt`:\n\n```bash\ncode2prompt test_dir --exclude=\"*.txt\"\n```\n\n#### Resultado\n\nExcluidos:\n\n- Todos los archivos `.txt`\n\nIncluidos:\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n\n---\n\n### Caso 3: Incluir tipos de archivo específicos\n\nIncluir solo archivos Python:\n\n```bash\ncode2prompt test_dir --include=\"*.py\"\n```\n\n#### Resultado\n\nIncluidos:\n\n- Todos los archivos `.py`\n\nExcluidos:\n\n- `.secret/secret.txt`\n\n---\n\n### Caso 4: Incluir y excluir con prioridad\n\nIncluir archivos `.py` pero excluir archivos en la carpeta `uppercase`:\n\n```bash\ncode2prompt test_dir --include=\"*.py\" --exclude=\"**/uppercase/*\" --include-priority=true\n```\n\n#### Resultado\n\nIncluidos:\n\n- Todos los archivos `lowercase/1` con extensión `.py`\n\nExcluidos:\n\n- Todos los archivos `uppercase`\n- `.secret/secret.txt`\n\n---\n\n## Resumen\n\nLa herramienta de patrones glob en `code2prompt` te permite filtrar archivos y directorios de manera efectiva utilizando:\n\n- `--include` para especificar archivos a incluir\n- `--exclude` para archivos a excluir\n- `--include-priority` para resolver conflictos entre patrones\n\nPara practicar, configura el directorio de muestra, prueba los comandos y observa cómo la herramienta filtra archivos dinámicamente.\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/tutorials/learn_templates.mdx",
    "content": "---\ntitle: Aprenda plantillas de Handlebar con Code2Prompt\ndescription: Entienda cómo usar y crear plantillas personalizadas de Handlebars para la generación de prompts.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"Descripción general del tutorial\">\n  Este tutorial demuestra cómo usar y crear plantillas personalizadas de\n  Handlebars para la generación de prompts en el CLI de `code2prompt`.\n</Card>\n\n---\n\n## Requisitos previos\n\nAsegúrese de tener instalado `code2prompt`. Si aún no lo ha instalado, consulte la [Guía de instalación](/docs/how_to/install).\n\n---\n\n## ¿Qué son las plantillas de Handlebars?\n\n[Handlebars](https://handlebarsjs.com/) es un motor de plantillas popular que le permite crear plantillas dinámicas utilizando marcadores de posición.\nEn `code2prompt`, las plantillas de Handlebars se utilizan para dar formato a los prompts generados según la estructura del código base y las variables definidas por el usuario.\n\n## Cómo usar plantillas de Handlebars\n\nPuede utilizar estas plantillas pasando la bandera `-t` o `--template` seguida de la ruta al archivo de plantilla. Por ejemplo:\n\n```sh\ncode2prompt path/to/codebase -t templates/document-the-code.hbs\n```\n\n## Sintaxis de plantilla\n\nLas plantillas de Handlebars utilizan una sintaxis simple para marcadores de posición y expresiones. Colocará variables entre llaves dobles `{{nombre_de_variable}}` para incluirlas en el prompt generado.\n`Code2prompt` proporciona un conjunto de variables predeterminadas que puede utilizar en sus plantillas:\n\n- `absolute_code_path`: La ruta absoluta al código base.\n- `source_tree`: El árbol de origen del código base, que incluye todos los archivos y directorios.\n- `files`: Una lista de archivos en el código base, incluidas sus rutas y contenidos.\n- `git_diff`: El diff de git del código base, si corresponde.\n- `code`: El contenido del código del archivo que se está procesando.\n- `path`: La ruta del archivo que se está procesando.\n\nTambién puede utilizar ayudantes de Handlebars para realizar lógica condicional, bucles y otras operaciones dentro de sus plantillas. Por ejemplo:\n\n```handlebars\n{{#if files}}\n  {{#each files}}\n    Archivo:\n    {{this.path}}\n    Contenido:\n    {{this.content}}\n  {{/each}}\n{{else}}\n  No se encontraron archivos.\n{{/if}}\n```\n\n---\n\n## Plantillas existentes\n\n`code2prompt` viene con un conjunto de plantillas integradas para casos de uso comunes. Puede encontrarlas en el directorio [`templates`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates).\n\n### [`document-the-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/document-the-code.hbs)\n\nUtilice esta plantilla para generar prompts para documentar el código. Agregará comentarios de documentación a todas las funciones públicas, métodos, clases y módulos en el código base.\n\n### [`find-security-vulnerabilities.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/find-security-vulnerabilities.hbs)\n\nUtilice esta plantilla para generar prompts para encontrar vulnerabilidades de seguridad potenciales en el código base. Buscará problemas de seguridad comunes y proporcionará recomendaciones sobre cómo solucionarlos o mitigarlos.\n\n### [`clean-up-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/clean-up-code.hbs)\n\nUtilice esta plantilla para generar prompts para limpiar y mejorar la calidad del código. Buscará oportunidades para mejorar la legibilidad, la adherencia a las mejores prácticas, la eficiencia, el manejo de errores y más.\n\n### [`fix-bugs.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/fix-bugs.hbs)\n\nUtilice esta plantilla para generar prompts para solucionar errores en el código base. Ayudará a diagnosticar problemas, proporcionará sugerencias de solución y actualizará el código con las correcciones propuestas.\n\n### [`write-github-pull-request.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-pull-request.hbs)\n\nUtilice esta plantilla para crear una descripción de solicitud de extracción de GitHub en markdown comparando el diff de git y el registro de git de dos ramas.\n\n### [`write-github-readme.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-readme.hbs)\n\nUtilice esta plantilla para generar un archivo README de alta calidad para el proyecto, adecuado para alojar en GitHub. Analizará el código base para comprender su propósito y funcionalidad, y generará el contenido del README en formato Markdown.\n\n### [`write-git-commit.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-git-commit.hbs)\n\nUtilice esta plantilla para generar confirmaciones de git a partir de los archivos preparados en su directorio de git. Analizará el código base para comprender su propósito y funcionalidad, y generará el contenido del mensaje de confirmación de git en formato Markdown.\n\n### [`improve-performance.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/improve-performance.hbs)\n\nUtilice esta plantilla para generar prompts para mejorar el rendimiento del código base. Buscará oportunidades de optimización, proporcionará sugerencias específicas y actualizará el código con los cambios.\n\n## Variables definidas por el usuario\n\n`code2prompt` admite el uso de variables definidas por el usuario en las plantillas de Handlebars. Cualquier variable en la plantilla que no sea parte del contexto predeterminado (`absolute_code_path`, `source_tree`, `files`) se tratará como una variable definida por el usuario.\n\nDurante la generación de prompts, `code2prompt` solicitará al usuario que ingrese valores para estas variables definidas por el usuario. Esto permite una mayor personalización de los prompts generados según la entrada del usuario.\n\nPor ejemplo, si su plantilla incluye `{{challenge_name}}` y `{{challenge_description}}`, se le pedirá que ingrese valores para estas variables al ejecutar `code2prompt`.\n\nEsta función permite crear plantillas reutilizables que se pueden adaptar a diferentes escenarios según la información proporcionada por el usuario.\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/vision.mdx",
    "content": "---\ntitle: La visión de Code2Prompt\ndescription: Descubre la visión detrás de Code2Prompt y cómo mejora las interacciones de LLM con el código.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Aside } from \"@astrojs/starlight/components\";\n\n<Card title=\"Propósito 🎯\">\n  `code2prompt` fue creado para ayudar a los desarrolladores y agentes de\n  inteligencia artificial a interactuar con bases de código de manera más\n  efectiva.\n</Card>\n\n## El problema 🚩\n\nLos modelos de lenguaje grandes (LLM) han revolucionado la forma en que interactuamos con el código. Sin embargo, todavía enfrentan desafíos significativos con la generación de código:\n\n- **Planificación y razonamiento**: Los LLM carecen de la capacidad de planificar y razonar, lo cual es crucial para tareas como la generación de código, la refactorización y la depuración. A menudo luchan por obtener una visión general y son cortoplacistas.\n- **Tamaño del contexto**: Los LLM tienen una ventana de contexto limitada, lo que restringe su capacidad para analizar y comprender bases de código grandes.\n- **Alucinación**: Los LLM pueden generar código que parece correcto pero que en realidad es incorrecto o absurdo. Este fenómeno, conocido como alucinación, ocurre cuando el modelo carece de contexto suficiente o comprensión de la base de código.\n\nAquí es donde entra en juego `code2prompt`.\n\n## La solución ✅\n\nCreemos que la planificación y el razonamiento pueden lograrse mediante técnicas de andamiaje con agentes humanos o de inteligencia artificial. Estos agentes necesitan recopilar un **contexto de alta calidad** de la base de código que esté filtrado, estructurado y formateado para la tarea en cuestión.\n\nLa regla general sería:\n\n<Aside type=\"tip\">\n  > proporcionar la menor cantidad de contexto posible, pero la necesaria\n</Aside>\n\nEsto es prácticamente difícil de lograr, especialmente para bases de código grandes. Sin embargo, `code2prompt` es una herramienta simple que puede ayudar a los desarrolladores y agentes de inteligencia artificial a ingerir la base de código de manera más efectiva.\n\nAutomatiza el proceso de recorrer una base de código, filtrar archivos y formatearlos en indicaciones estructuradas que los LLM pueden comprender. Al hacerlo, ayuda a mitigar los desafíos de planificación, razonamiento y alucinación.\n\nPuede entender cómo `code2prompt` está diseñado para abordar estos desafíos en la siguiente sección.\n\n## Arquitectura ⛩️\n\n<img\n  src=\"/assets/images/architecture.svg\"\n  alt=\"Arquitectura de code2prompt\"\n  style=\"width: 75%;\"\n/>\n\n`code2prompt` está diseñado de manera modular, lo que permite una fácil integración en varios flujos de trabajo. Puede utilizarse como una biblioteca central, una interfaz de línea de comandos (CLI), un kit de desarrollo de software (SDK) o incluso como un servidor de protocolo de contexto de modelo (MCP).\n\n### Central\n\n`code2prompt` es una herramienta de ingesta de código que agiliza el proceso de crear indicaciones de LLM para análisis de código, generación y otras tareas. Funciona recorriendo directorios, construyendo una estructura de árbol y recopilando información sobre cada archivo. La biblioteca central se puede integrar fácilmente en otras aplicaciones.\n\n### CLI\n\nLa interfaz de línea de comandos (CLI) de `code2prompt` fue diseñada para que los humanos generen indicaciones directamente desde su base de código. La indicación generada se copia automáticamente al portapapeles y también se puede guardar en un archivo de salida. Además, puede personalizar la generación de indicaciones utilizando plantillas de Handlebars. ¡Eche un vistazo a las indicaciones proporcionadas en la documentación!\n\n### SDK\n\nEl kit de desarrollo de software (SDK) de `code2prompt` ofrece una vinculación de Python a la biblioteca central. Esto es perfecto para agentes de inteligencia artificial o scripts de automatización que desean interactuar con la base de código sin problemas. El SDK se hospeda en Pypi y se puede instalar mediante pip.\n\n### MCP\n\n`code2prompt` también está disponible como un servidor de protocolo de contexto de modelo (MCP), lo que permite ejecutarlo como un servicio local. Esto permite a los LLM en esteroides proporcionarles una herramienta para recopilar automáticamente un contexto bien estructurado de su base de código.\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/es/docs/welcome.mdx",
    "content": "---\ntitle: Documentación de Code2Prompt\ndescription: Documentación oficial de Code2prompt\ntemplate: splash\nhero:\n  tagline: Transforma tu código en indicaciones optimizadas para IA en segundos\n  image:\n    file: ../../../../assets/logo_dark_v0.0.1.svg\n  actions:\n    - text: Empezar 🚀\n      link: /docs/tutorials/getting_started\n    - text: Instalación 📥\n      link: /docs/how_to/install\n---\n\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\nimport { LinkCard } from \"@astrojs/starlight/components\";\n\n## Inicio rápido\n\n<LinkCard title=\"Empezar 🚀\" href=\"/docs/tutorials/getting_started\" />\n<LinkCard title=\"Instalación 📥\" href=\"/docs/how_to/install\" />\n<LinkCard title=\"Aprender filtrado 🔍\" href=\"/docs/tutorials/learn_filters\" />\n<LinkCard\n  title=\"Aprender plantillas 📝\"\n  href=\"/docs/tutorials/learn_templates\"\n/>\n<LinkCard title=\"Visión 🔮\" href=\"/docs/vision\" />\n\n`code2prompt` es una poderosa herramienta de ingesta de código diseñada para generar indicaciones para análisis de código, generación y otras tareas. Funciona recorriendo directorios, construyendo una estructura de árbol y recopilando información sobre cada archivo.\n\nSimplifica el proceso de combinar y formatear código, lo que facilita el análisis, la documentación o la refactorización de código utilizando LLMs.\n\nPuedes utilizar `code2prompt` de las siguientes maneras:\n\n<CardGrid>\n  <Card title=\"Núcleo\" icon=\"seti:rust\">\n    Biblioteca central extremadamente rápida para ingesta de código\n  </Card>\n  <Card title=\"CLI\" icon=\"seti:powershell\">\n    Interfaz de línea de comandos especialmente diseñada para humanos\n  </Card>\n  <Card title=\"SDK\" icon=\"seti:python\">\n    Kit de desarrollo de software para agentes de IA y scripts de automatización\n  </Card>\n  <Card title=\"MCP\" icon=\"seti:folder\">\n    Servidor de protocolo de contexto de modelo para LLMs mejorados\n  </Card>\n</CardGrid>\n\n## Características clave\n\n- **Generar indicaciones LLM**: Convierte rápidamente bases de código enteras en indicaciones estructuradas para LLM.\n- **Filtrado de patrones Glob**: Incluye o excluye archivos y directorios específicos utilizando patrones Glob.\n- **Plantillas personalizables**: Adapta la generación de indicaciones con plantillas Handlebars.\n- **Conteo de tokens**: Analiza el uso de tokens y optimiza para LLMs con ventanas de contexto variables.\n- **Integración con Git**: Incluye diferencias de Git y mensajes de confirmación en las indicaciones para revisiones de código.\n- **Respeta `.gitignore`**: Ignora automáticamente archivos listados en `.gitignore` para agilizar la generación de indicaciones.\n\n## ¿Por qué `code2prompt`?\n\n1. **Ahorra tiempo**:\n\n   - Automatiza el proceso de recorrer una base de código y formatear archivos para LLMs.\n   - Evita copiar y pegar repetidamente código.\n\n2. **Mejora la productividad**:\n\n   - Proporciona un formato estructurado y consistente para el análisis de código.\n   - Ayuda a identificar errores, refactorizar código y escribir documentación más rápido.\n\n3. **Maneja bases de código grandes**:\n\n   - Diseñado para funcionar sin problemas con bases de código grandes, respetando los límites de contexto de LLMs.\n\n4. **Flujos de trabajo personalizables**:\n   - Opciones flexibles para filtrar archivos, utilizar plantillas y generar indicaciones específicas.\n\n## Casos de uso ejemplo\n\n- **Documentación de código**:\n  Genera automáticamente documentación para funciones públicas, métodos y clases.\n\n- **Detección de errores**:\n  Encuentra posibles errores y vulnerabilidades analizando tu base de código con LLMs.\n\n- **Refactorización**:\n  Simplifica y optimiza código generando indicaciones para mejoras en la calidad del código.\n\n- **Aprendizaje y exploración**:\n  Entiende nuevas bases de código generando resúmenes y descomposiciones detalladas.\n\n- **Descripciones de confirmaciones de Git y PR**:\n  Genera mensajes de confirmación significativos y descripciones de solicitudes de extracción a partir de diferencias de Git.\n\n> Esta página ha sido traducida automáticamente para su conveniencia. Consulte la versión en inglés para ver el contenido original.\n"
  },
  {
    "path": "website/src/content/docs/fr/blog/2025.04.11_why_I_wrote_code2prompt.mdx",
    "content": "---\ntitle: \"Pourquoi j'ai développé Code2Prompt\"\ndate: 2025-04-11\nlastUpdated: 2025-04-11\ntags:\n  - open-source\n  - code2prompt\n  - IA\n  - Agent\nexcerpt: \"L'histoire derrière code2prompt : ma quête Open-Source pour relever les défis de contexte dans les flux de travail LLM\"\nauthors:\n  - ODAncona\ncover:\n  alt: \"Une illustration de code2prompt simplifiant le contexte de code pour les agents IA.\"\n  image: \"/src/assets/logo_dark_v0.0.2.svg\"\nfeatured: false\ndraft: false\n---\n\n## Introduction\n\nJe suis toujours fasciné par la façon dont les modèles de langage à grande échelle (LLM) transforment les flux de travail de codage - générant des tests, des docstrings ou même des fonctionnalités entières en quelques minutes. Mais à mesure que je poussais ces modèles plus loin, quelques points de douleur critiques continuaient à émerger :\n\n| Difficultés de planification | Coûts de jetons élevés | Hallucinations |\n| ---------------------------- | ---------------------- | -------------- |\n| 🧠 ➡️ 🤯                     | 🔥 ➡️ 💸               | 💬 ➡️ 🌀       |\n\nC'est pourquoi j'ai commencé à contribuer à `code2prompt`, un outil basé sur Rust pour aider à fournir juste le contexte approprié aux LLM.\n\nDans cet article, je partagerai mon parcours et expliquerai pourquoi je suis convaincu que `code2prompt` est pertinent aujourd'hui et s'intègre si bien, et pourquoi il est devenu ma solution incontournable pour des flux de travail de codage IA meilleurs et plus rapides.\n\n## Mes premiers pas avec les LLM 👣\n\nJ'ai commencé à expérimenter avec les LLM sur `OpenAI Playground` avec `text-davinci-003` lorsqu'il a gagné en popularité en novembre 2023. Les modèles de langage ont permis une nouvelle révolution. Cela ressemblait à avoir un assistant brillant qui pouvait produire des tests unitaires et des docstrings presque sur commande. J'ai apprécié pousser les modèles à leurs limites - testant tout, des conversations informelles et des dilemmes éthiques aux jailbreaks et aux tâches de codage complexes. Cependant, à mesure que j'ai abordé des projets plus importants, j'ai rapidement réalisé que les modèles avaient des limitations criantes. Au début, je ne pouvais adapter que quelques centaines de lignes de code dans la fenêtre de contexte, et même alors, les modèles avaient souvent du mal à comprendre le but ou la structure du code. C'est pourquoi j'ai rapidement remarqué que l'importance du contexte était primordiale. Plus mes instructions étaient concises et meilleur était le contexte, meilleurs étaient les résultats.\n\n![OpenAI Playground](/assets/blog/post1/playground.png)\n\n## Évolution des modèles 🏗️\n\nLes modèles pouvaient produire des résultats impressionnants mais avaient souvent du mal avec des bases de code plus importantes ou des tâches complexes. Je me suis retrouvé à passer plus de temps à élaborer des invites qu'à coder réellement. Dans le même temps, les modèles continuaient à s'améliorer avec la sortie de nouvelles versions. Ils ont augmenté leurs capacités de raisonnement et la taille du contexte, offrant de nouvelles perspectives et possibilités. Je pouvais adapter presque deux mille lignes de code dans la fenêtre de contexte, et les résultats se sont améliorés. Je pouvais écrire des fonctionnalités entières en quelques itérations, et j'ai été impressionné par la rapidité avec laquelle je pouvais obtenir des résultats. J'étais convaincu que les LLM étaient l'avenir du codage, et je voulais en faire partie. Je crois fermement que l'IA ne nous remplacera pas encore. Mais nous assistera sous la forme d'assistants où les humains sont les experts encore en contrôle.\n\n## Mes premiers projets avec les LLM 🚀\n\nJ'ai commencé à écrire un module de recherche de chemin `ROS` pour un concours de robotique, à générer des fonctionnalités pour une application `Flutter` multiplateforme d'architecture propre, et j'ai créé une petite application Web pour suivre mes dépenses en `Next.js`. Le fait que j'ai construit cette petite application en une soirée, dans un framework que je n'avais jamais utilisé auparavant, a été un moment décisif pour moi ; les LLM n'étaient pas seulement des outils mais des multiplicateurs. J'ai développé `bboxconverter`, un package pour convertir des boîtes de bounding, et la liste continue. Les LLM peuvent vous aider à apprendre de nouvelles technologies et frameworks rapidement ; c'est incroyable.\n\n## Un nouveau paradigme : Software 3.0 💡\n\nJe me suis approfondi dans les LLM et j'ai commencé à construire des agents et des squelettes autour d'eux. J'ai reproduit le célèbre article [RestGPT](https://restgpt.github.io/). L'idée est excellente : donner aux LLM la capacité d'appeler certaines API REST avec une spécification OpenAPI, telles que `Spotify` ou `TMDB`. Ces capacités introduisent un nouveau paradigme de programmation logiciel, que j'aime appeler **Software 3.0**.\n\n| Software 1.0        | Software 2.0           | Software 3.0 |\n| ------------------- | ---------------------- | ------------ |\n| Basé sur des règles | Piloté par les données | Agence       |\n\nLa même idée a propulsé le protocole [MCP](https://modelcontextprotocol.io/introduction), qui permet aux LLM d'appeler des outils et des ressources directement de manière transparente, car par conception, l'outil a besoin d'une description pour être appelé par le LLM, contrairement aux API REST qui ne nécessitent pas nécessairement de spécification OpenAPI.\n\n## Les limitations des LLM 🧩\n\n### Hallucinations 🌀\n\nLors de la reproduction du célèbre article `RESTGPT`, j'ai remarqué certaines limitations graves des LLM. Les auteurs de l'article ont rencontré les mêmes problèmes que moi : les LLM **hallucinaient**. Ils génèrent du code qui n'est pas implémenté, inventant des arguments et suivant simplement les instructions à la lettre sans utiliser le bon sens. Par exemple, dans le code source original de RestGPT, les auteurs ont demandé dans [l'invite de l'appelant](https://github.com/Yifan-Song793/RestGPT/blob/main/model/caller.py).\n\n> \"de ne pas être malin et d'inventer des étapes qui n'existent pas dans le plan.\"\n\nJ'ai trouvé cette déclaration amusante et très intéressante parce que c'était la première fois que je rencontrais quelqu'un qui instruisait les LLM à ne pas halluciner.\n\n### Taille de contexte limitée 📏\n\nUne autre limitation était la taille du contexte ; les LLM performent bien pour trouver l'aiguille dans la botte de foin mais ont du mal à en comprendre le sens. Lorsque vous donnez trop de contexte aux modèles de langage, ils ont tendance à se perdre dans les détails et à perdre de vue l'ensemble, ce qui est ennuyeux et nécessite une direction constante. La façon dont j'aime y penser est similaire à [la malédiction de la dimensionnalité](https://towardsdatascience.com/curse-of-dimensionality-a-curse-to-machine-learning-c122ee33bfeb/). Remplacez le mot \"dimension\" ou \"fonctionnalité\" par \"contexte\", et vous obtenez l'idée.\n\n![Malédiction de la dimensionnalité](/assets/blog/post1/curse_of_dimensionality.png)\n\nPlus vous donnez de contexte au LLM, plus il est difficile de trouver la bonne réponse. J'ai créé une phrase agréable pour résumer cette idée :\n\n> Fournissez aussi peu de contexte que possible mais autant que nécessaire\n\nCeci est fortement inspiré par la célèbre [citation d'Alain Berset](https://www.lematin.ch/story/alain-berset-la-formule-qui-defie-le-temps-166189802108), un politicien suisse 🇨🇭 qui a déclaré pendant le confinement COVID-19 :\n\n> \"Nous souhaitons agir aussi vite que possible, mais aussi lentement que nécessaire\"\n\nCela représente l'idée de compromis et s'applique à la taille du contexte des LLM !\n\n## Recherche d'une meilleure façon : code2prompt 🔨\n\nPar conséquent, j'avais besoin d'un moyen de charger, de filtrer et d'organiser rapidement mon contexte de code en fournissant la moindre quantité possible de contexte avec la meilleure qualité possible. J'ai essayé de copier manuellement des fichiers ou des extraits dans des invites, mais cela est devenu encombrant et sujet aux erreurs. Je savais que l'automatisation du processus fastidieux de forgeage du contexte pour poser de meilleures questions serait utile. Ensuite, un jour, j'ai tapé \"code2prompt\" sur Google, espérant trouver un outil qui acheminait mon code directement dans des invites.\n\nEt voici ! J'ai découvert un projet **basé sur Rust** de [Mufeed](https://www.reddit.com/r/rust/comments/1bghroh/i_made_code2prompt_a_cli_tool_to_convert_your/) nommé _code2prompt_, qui comptait environ 200 étoiles sur GitHub. C'était encore basique à l'époque : un simple outil CLI avec une capacité de filtration limitée et des modèles. J'ai vu un énorme potentiel et j'ai sauté directement pour contribuer, en mettant en œuvre la correspondance de modèles globaux, entre autres fonctionnalités, et je suis rapidement devenu le principal contributeur.\n\n## Vision & Intégrations 🔮\n\nAujourd'hui, il existe plusieurs façons de fournir du contexte aux LLM. Générer à partir du contexte plus large, utiliser la génération augmentée de récupération (RAG), [compresser le code](https://www.all-hands.dev/blog/openhands-context-condensensation-for-more-efficient-ai-agents), ou même utiliser une combinaison de ces méthodes. Le forgeage de contexte est un sujet brûlant qui évoluera rapidement dans les prochains mois. Cependant, mon approche est **KISS** : Keep It Simple, Stupid. La meilleure façon de fournir du contexte aux LLM est d'utiliser la façon la plus simple et la plus efficace possible. Vous forgez précisément le contexte dont vous avez besoin ; c'est déterministe, contrairement à RAG.\n\nC'est pourquoi j'ai décidé de pousser `code2prompt` plus loin en tant qu'outil simple pouvant être utilisé dans n'importe quel flux de travail. Je voulais le rendre facile à utiliser, facile à intégrer et facile à étendre. C'est pourquoi j'ai ajouté de nouvelles façons d'interagir avec l'outil.\n\n- **Core** : Le cœur de `code2prompt` est une bibliothèque Rust qui fournit la fonctionnalité de base pour forger le contexte à partir de votre base de code. Il comprend une API simple pour charger, filtrer et organiser votre contexte de code.\n- **CLI** : L'interface de ligne de commande est la façon la plus simple d'utiliser `code2prompt`. Vous pouvez forger le contexte à partir de votre base de code et le canaliser directement dans vos invites.\n- **API Python** : L'API Python est un simple wrapper autour de CLI qui vous permet d'utiliser `code2prompt` dans vos scripts et agents Python. Vous pouvez forger le contexte à partir de votre base de code et le canaliser directement dans vos invites.\n- **MCP** : Le serveur MCP `code2prompt` permet aux LLM d'utiliser `code2prompt` en tant qu'outil, les rendant ainsi capables de forger le contexte.\n\nLa vision est décrite plus en détail dans la [page de vision](/docs/vision) de la documentation.\n\n## Intégration avec les agents 👤\n\nJe crois que les futurs agents auront besoin d'un moyen d'ingérer du contexte, et `code2prompt` est la façon simple et efficace de le faire pour les référentiels textuels comme la base de code, la documentation ou les notes. Un endroit typique pour utiliser `code2prompt` serait dans une base de code avec des conventions de dénomination significatives. Par exemple, dans l'architecture propre, il existe une séparation claire des préoccupations et des couches. Le contexte pertinent réside généralement dans différents fichiers et dossiers mais partage le même nom. C'est un cas d'utilisation parfait pour `code2prompt`, où vous pouvez utiliser le modèle global pour saisir les fichiers pertinents.\n\n**Basé sur le modèle global** : Sélectionnez ou excluez précisément les fichiers avec un minimum de tracas.\n\nEn outre, la bibliothèque principale est conçue en tant que gestionnaire de contexte étatique, vous permettant d'ajouter ou de supprimer des fichiers à mesure que votre conversation avec le LLM évolue. Ceci est particulièrement utile pour fournir du contexte pour une tâche ou un objectif spécifique. Vous pouvez facilement ajouter ou supprimer des fichiers du contexte sans relancer le processus.\n\n**Contexte étatique** : Ajoutez ou supprimez des fichiers à mesure que votre conversation avec le LLM évolue.\n\nCes capacités font de `code2prompt` un choix parfait pour les flux de travail basés sur des agents. Le serveur MCP permet une intégration transparente avec des frameworks d'agents IA populaires tels que [Aider](https://github.com/paul-gauthier/aider), [Goose](https://block.github.io/goose/), ou [Cline](https://github.com/jhillyerd/cline). Laissez-les gérer des objectifs complexes pendant que `code2prompt` fournit le contexte de code parfait.\n\n## Pourquoi Code2prompt compte ✊\n\nÀ mesure que les LLM évoluent et que les fenêtres de contexte s'étendent, il peut sembler que simplement forcer des référentiels entiers dans des invites suffit. Cependant, les **coûts de jetons** et la **cohérence des invites** restent des obstacles importants pour les petites entreprises et les développeurs. En se concentrant sur le code qui compte, `code2prompt` maintient votre utilisation de LLM efficace, rentable et moins encline à l'hallucination.\n\n**En bref :**\n\n- **Réduire les hallucinations** en fournissant la bonne quantité de contexte\n- **Réduire les coûts de jetons** en curant manuellement le contexte approprié nécessaire\n- **Améliorer les performances de LLM** en donnant la bonne quantité de contexte\n- Intègre la pile agence en tant que fournisseur de contexte pour les référentiels textuels\n\n## Vous pouvez rejoindre ! C'est Open Source ! 🌐\n\nTout nouveau contributeur est le bienvenu ! Venez à bord si vous êtes intéressé par Rust, la création d'outils IA innovants, ou si vous voulez simplement un meilleur flux de travail pour vos invites basées sur le code.\n\nMerci de lire, et j'espère que mon histoire vous a inspiré à découvrir code2prompt. C'est un incroyable voyage, et cela ne fait que commencer !\n\n**Olivier D'Ancona**\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/explanations/glob_pattern_filter.mdx",
    "content": "---\ntitle: Comment fonctionne le filtre de modèle Glob\ndescription: Comment Code2Prompt décide quelles fichiers garder ou écarter en utilisant les globs d'inclusion (-i) et d'exclusion (-e).\n---\n\nCode2Prompt utilise des modèles glob pour inclure ou exclure des fichiers et répertoires, fonctionnant de manière similaire à des outils comme tree ou grep. Il vous permet de passer deux _listes_ indépendantes de modèles glob :\n\n- **liste d'inclusion** (`--include` ou `-i`) - \"ces modèles autorisent les fichiers\"\n- **liste d'exclusion** (`--exclude` ou `-e`) - \"ces modèles interdisent les fichiers\"\n\nCode2prompt doit décider, pour chaque fichier du projet, s'il est conservé ou écarté. Cette page explique les règles et les choix de conception qui les sous-tendent.\n\n---\n\n## 1. Ensembles et Symboles\n\nTout au long de l'explication, nous utilisons la notation d'ensemble habituelle\n\n| Symbole                           | Signification                                                                |\n| --------------------------------- | ---------------------------------------------------------------------------- |\n| $A$                               | ensemble des fichiers qui correspondent à **au moins un** modèle d'inclusion |\n| $B$                               | ensemble des fichiers qui correspondent à **au moins un** modèle d'exclusion |\n| $\\Omega$                          | l'arbre de projet entier (l'_univers_)                                       |\n| $C = A \\cap B$                    | fichiers qui correspondent aux deux listes (le _chevauchement_)              |\n| $D = \\Omega \\setminus (A \\cup B)$ | fichiers qui ne correspondent à aucune liste                                 |\n\n---\n\n## 2. Quatre Situations\n\n### Aperçu des quatre situations\n\n| Liste d'inclusion | Liste d'exclusion | Fichiers conservés |\n| ----------------- | ----------------- | ------------------ |\n| A = ∅             | B = ∅             | Ω                  |\n| A = ∅             | B ≠ ∅             | ¬B                 |\n| A ≠ ∅             | B = ∅             | A                  |\n| A ≠ ∅             | B ≠ ∅             | A \\ B              |\n\n1. **Pas de liste d'inclusion, pas de liste d'exclusion**\n\n   Si aucun modèle n'est spécifié, tous les fichiers sont conservés (`Ω`).\n\n2. **Liste d'exclusion seulement**\n\n   Dans ce cas, Code2Prompt agit comme une liste noire, supprimant les fichiers qui correspondent aux modèles exclus (` Ω \\ B = ¬B`).\n\n3. **Liste d'inclusion seulement**\n\n   Si seule une liste d'inclusion est spécifiée, Code2Prompt agit comme une liste blanche, ne conservant que les fichiers qui correspondent aux modèles inclus (`A`).\n\n4. **Listes d'inclusion _et_ d'exclusion**\n\n   Si les deux listes sont spécifiées, Code2Prompt conserve les fichiers qui correspondent aux modèles d'inclusion, mais supprime ceux qui correspondent aux modèles d'exclusion (`A \\ B`).\n\n---\n\n## 3. Plus sur le chevauchement\n\nAvec les deux listes présentes (`A ≠ ∅`, `B ≠ ∅`), vous avez quatre possibilités logiques\npour le chevauchement `C` et le reste `D`.\n\n| Vouloir `C` ? | Vouloir `D` ? | Raisonnable ?                                                             |\n| ------------- | ------------- | ------------------------------------------------------------------------- |\n| Non           | Non           | Comportement par défaut (`A \\ B`)                                         |\n| Oui           | Non           | Même comportement que le cas 3 (`A`)                                      |\n| Non           | Oui           | surprenant (\"écarter ce que j'ai demandé `C`, garder ce que je n'ai pas\") |\n| Oui           | Oui           | Même comportement que le cas 1 (`Ω`)                                      |\n\nC'est pour cette raison que l'option `--include-priority` a été supprimée. Parce que ce serait le même résultat que si vous n'aviez qu'une liste d'inclusion (cas 3).\n\n## 4. Table de référence rapide\n\n| Vous voulez garder…                                      | Utilisez                 |\n| -------------------------------------------------------- | ------------------------ |\n| tout                                                     | pas de `-i`, pas de `-e` |\n| tout _sauf_ certains modèles                             | `-e` seulement           |\n| _seulement_ ce qui correspond aux modèles                | `-i` seulement           |\n| ce qui correspond à `-i`, moins ce qui correspond à `-e` | `-i` **et** `-e`         |\n\n---\n\nCette conception maintient le modèle mental simple :\n\n- La liste d'inclusion est une liste blanche dès qu'elle existe.\n- La liste d'exclusion est une liste noire superposée par-dessus.\n- Le chevauchement est écarté par défaut\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/explanations/glob_patterns.md",
    "content": "---\ntitle: Comprendre les modèles Glob\ndescription: Une explication détaillée des modèles Glob et de leur utilisation dans Code2Prompt.\n---\n\nLes modèles Glob sont un moyen simple mais puissant de faire correspondre les noms de fichiers et les chemins d'accès à l'aide de caractères génériques. Ils sont couramment utilisés dans les interfaces de ligne de commande et les langages de programmation pour spécifier des ensembles de noms de fichiers ou de répertoires. Voici une analyse des modèles Glob les plus couramment utilisés :\n\n## Générateurs de base\n\n- `*` : Correspond à tout nombre de caractères, y compris zéro caractère.\n  - Exemple : `*.txt` correspond à tous les fichiers se terminant par `.txt`.\n\n- `?` : Correspond exactement à un caractère.\n  - Exemple : `file?.txt` correspond à `file1.txt`, `fileA.txt`, mais pas à `file10.txt`.\n\n- `[]` : Correspond à l'un des caractères enfermés.\n  - Exemple : `file[1-3].txt` correspond à `file1.txt`, `file2.txt`, `file3.txt`.\n\n- `[!]` ou `[^]` : Correspond à tout caractère non enfermé.\n  - Exemple : `file[!1-3].txt` correspond à `file4.txt`, `fileA.txt`, mais pas à `file1.txt`.\n\n## Modèles avancés\n\n- `**` : Correspond à tout nombre de répertoires et sous-répertoires de manière récursive.\n  - Exemple : `**/*.txt` correspond à tous les fichiers `.txt` dans le répertoire actuel et tous les sous-répertoires.\n\n- `{}` : Correspond à l'un des modèles séparés par des virgules enfermés.\n  - Exemple : `file{1,2,3}.txt` correspond à `file1.txt`, `file2.txt`, `file3.txt`.\n\n## Exemples\n\n1. **Faire correspondre tous les fichiers texte dans un répertoire :**\n\n   ```sh\n   *.txt\n   ```\n\n2. **Faire correspondre tous les fichiers avec un seul chiffre avant l'extension :**\n\n   ```sh\n   file?.txt\n   ```\n\n3. **Faire correspondre les fichiers avec les extensions `.jpg` ou `.png` :**\n\n   ```sh\n   *.{jpg,png}\n   ```\n\n4. **Faire correspondre tous les fichiers `.txt` dans n'importe quel sous-répertoire :**\n\n   ```sh\n   **/*.txt\n   ```\n\n5. **Faire correspondre les fichiers qui commencent par `a` ou `b` et se terminent par `.txt` :**\n\n   ```sh\n   {a,b}*.txt\n   ```\n\n## Cas d'utilisation\n\n- **Outils de ligne de commande :** Les modèles Glob sont largement utilisés dans les outils de ligne de commande tels que `ls`, `cp`, `mv` et `rm` pour spécifier plusieurs fichiers ou répertoires.\n- **Langages de programmation :** Les langages tels que Python, JavaScript et Ruby prennent en charge les modèles Glob pour la correspondance de fichiers via des bibliothèques telles que `glob` en Python.\n- **Systèmes de build :** Des outils tels que Makefile utilisent des modèles Glob pour spécifier les fichiers source et les dépendances.\n\n## Conclusion\n\nLes modèles Glob fournissent un moyen flexible et intuitif de faire correspondre les noms de fichiers et les chemins d'accès, les rendant indispensables pour les tâches de script, d'automatisation et de gestion de fichiers. Comprendre et utiliser ces modèles peut considérablement améliorer votre productivité et votre efficacité dans la gestion des fichiers et des répertoires.\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/explanations/tokenizers.md",
    "content": "---\ntitle: Tokenisation dans Code2Prompt\ndescription: Découvrez la tokenisation et comment Code2Prompt traite le texte pour les LLMs.\n---\n\nLorsque l'on travaille avec des modèles de langage, le texte doit être transformé en un format que le modèle peut comprendre — **tokens**, qui sont des séquences de nombres. Cette transformation est gérée par un **tokeniseur**.\n\n---\n\n## Qu'est-ce qu'un Tokeniseur ?\n\nUn tokeniseur convertit le texte brut en tokens, qui sont les blocs de construction pour la façon dont les modèles de langage traitent l'entrée. Ces tokens peuvent représenter des mots, des sous-mots ou même des caractères individuels, selon la conception du tokeniseur.\n\nPour `code2prompt`, nous utilisons le tokeniseur **tiktoken**. Il est efficace, robuste et optimisé pour les modèles OpenAI.\nVous pouvez explorer sa fonctionnalité dans le référentiel officiel\n\n👉 [Référentiel GitHub de tiktoken](https://github.com/openai/tiktoken)\n\nSi vous souhaitez en savoir plus sur les tokeniseurs en général, consultez le\n\n👉 [Guide de tokenisation Mistral](https://docs.mistral.ai/guides/tokenization/).\n\n## Implémentation dans `code2prompt`\n\nLa tokenisation est implémentée à l'aide de [`tiktoken-rs`](https://github.com/zurawiki/tiktoken-rs). `tiktoken` prend en charge ces encodages utilisés par les modèles OpenAI :\n\n| Argument CLI | Nom de l'encodage       | Modèles OpenAI                                                           |\n| ---- | ----------------------- | ----------------------------------------------------------------------- |\n| `cl100k` | `cl100k_base`           | Modèles ChatGPT, `text-embedding-ada-002`                                |\n| `p50k` | `p50k_base`             | Modèles de code, `text-davinci-002`, `text-davinci-003`                   |\n| `p50k_edit` | `p50k_edit`             | Utiliser pour les modèles d'édition comme `text-davinci-edit-001`, `code-davinci-edit-001` |\n| `r50k` | `r50k_base` (ou `gpt2`) | Modèles GPT-3 comme `davinci`                                             |\n| `gpt2` | `o200k_base`            | Modèles GPT-4o                                                           |\n\nPour plus de contexte sur les différents tokeniseurs, consultez le [OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/66b988407d8d13cad5060a881dc8c892141f2d5c/examples/How_to_count_tokens_with_tiktoken.ipynb)\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/how_to/filter_files.md",
    "content": "---\ntitle: Filtrage de fichiers dans Code2Prompt\ndescription: Un guide étape par étape pour inclure ou exclure des fichiers à l'aide de différentes méthodes de filtrage.\n---\n\n\n## Utilisation\n\nGénérez une invite à partir d'un répertoire de base de code :\n\n```sh\ncode2prompt path/to/codebase\n```\n\nUtilisez un fichier de modèle Handlebars personnalisé :\n\n```sh\ncode2prompt path/to/codebase -t path/to/template.hbs\n```\n\nFiltrez les fichiers à l'aide de modèles glob :\n\n```sh\ncode2prompt path/to/codebase --include=\"*.rs,*.toml\"\n```\n\nExcluez les fichiers à l'aide de modèles glob :\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.txt,*.md\"\n```\n\nExcluez les fichiers/dossiers de l'arborescence source en fonction des modèles d'exclusion :\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.npy,*.wav\" --exclude-from-tree\n```\n\nAffichez le nombre de jetons de l'invite générée :\n\n```sh\ncode2prompt path/to/codebase --tokens\n```\n\nSpécifiez un tokenizeur pour le décompte des jetons :\n\n```sh\ncode2prompt path/to/codebase --tokens --encoding=p50k\n```\n\nTokenizeurs pris en charge : `cl100k`, `p50k`, `p50k_edit`, `r50k_bas`.\n> [!NOTE]  \n> Voir [Tokenizeurs](#tokenizers) pour plus de détails.\n\nEnregistrez l'invite générée dans un fichier de sortie :\n\n```sh\ncode2prompt path/to/codebase --output=output.txt\n```\n\nImprimez la sortie au format JSON :\n\n```sh\ncode2prompt path/to/codebase --json\n```\n\nLa sortie JSON aura la structure suivante :\n\n```json\n{\n  \"prompt\": \"<Invite générée>\", \n  \"directory_name\": \"codebase\",\n  \"token_count\": 1234,\n  \"model_info\": \"Modèles ChatGPT, text-embedding-ada-002\",\n  \"files\": []\n}\n```\n\nGénérez un message de commit Git (pour les fichiers en scène) :\n\n```sh\ncode2prompt path/to/codebase --diff -t templates/write-git-commit.hbs\n```\n\nGénérez une demande de tirage avec comparaison de branche (pour les fichiers en scène) :\n\n```sh\ncode2prompt path/to/codebase --git-diff-branch 'main, development' --git-log-branch 'main, development' -t templates/write-github-pull-request.hbs\n```\n\nAjoutez des numéros de ligne aux blocs de code source :\n\n```sh\ncode2prompt path/to/codebase --line-number\n```\n\nDésactivez l'emballage de code à l'intérieur des blocs de code markdown :\n\n```sh\ncode2prompt path/to/codebase --no-codeblock\n```\n\n- Réécrivez le code dans un autre langage.\n- Recherchez des bogues/vulnérabilités de sécurité.\n- Documentez le code.\n- Implémentez de nouvelles fonctionnalités.\n\n> J'ai initialement écrit cela pour une utilisation personnelle afin de profiter de la fenêtre de contexte de 200K de Claude 3.0 et cela s'est avéré assez utile, alors j'ai décidé de le rendre open-source !\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/how_to/install.mdx",
    "content": "---\ntitle: Installation de Code2Prompt\ndescription: Guide d'installation complet pour Code2Prompt sur différents systèmes d'exploitation.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Steps } from \"@astrojs/starlight/components\";\n\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\n\n<Card title=\"Présentation du guide\">\n  Bienvenue dans le guide d'installation de `Code2Prompt`. Ce document fournit\n  des instructions étape par étape pour l'installer sur différentes plateformes,\n  notamment Windows, macOS et Linux.\n</Card>\n\n**TL;DR**\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n## Prérequis\n\nAssurez-vous que [Rust](https://www.rust-lang.org/tools/install) et cargo sont installés sur votre système.\n\n```sh\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\nC'est la façon officielle d'installer la dernière version stable de Rust et Cargo. Assurez-vous de rafraîchir votre variable `PATH` après avoir installé Rust. Redémarrez votre terminal ou exécutez les instructions proposées par l'installateur.\n\n```sh\nsource $HOME/.cargo/env\n```\n\nVous pouvez vérifier que tout est installé correctement en exécutant :\n\n```sh\ncargo --version\ngit --version\n```\n\n## Interface de ligne de commande (CLI) 👨‍💻\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n#### 🧪 Installer la dernière version (non publiée) depuis GitHub\n\nSi vous voulez les dernières fonctionnalités ou corrections avant leur publication sur crates.io :\n\n```sh\ncargo install --git https://github.com/mufeedvh/code2prompt\n```\n\n### Construction à partir du code source\n\nIdéal pour les développeurs qui veulent construire à partir du code source ou contribuer au projet.\n\n<Steps>\n\n1.  🛠️ Installer les prérequis :\n\n    - [Rust](https://www.rust-lang.org/tools/install) et Cargo\n    - [Git](https://git-scm.com/downloads)\n\n2.  📥 Cloner le référentiel :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt\n    ```\n\n3.  📦 Installer l'exécutable :\n\n    Pour construire et installer à partir du code source :\n\n    ```sh\n    cargo install --path crates/code2prompt\n    ```\n\n    Pour construire l'exécutable sans l'installer :\n\n    ```sh\n    cargo build --release\n    ```\n\n    L'exécutable sera disponible dans le répertoire `target/release`.\n\n4.  🚀 L'exécuter :\n\n    ```sh\n    code2prompt --help\n    ```\n\n</Steps>\n\n### Publications binaires\n\nLe meilleur choix pour les utilisateurs qui veulent utiliser la dernière version sans construire à partir du code source.\n\nTéléchargez la dernière version binaire pour votre système d'exploitation à partir de [Releases](https://github.com/mufeedvh/code2prompt/releases).\n\n⚠️ Les publications binaires peuvent être à la traîne par rapport à la dernière version GitHub. Pour des fonctionnalités de pointe, envisagez de construire à partir du code source.\n\n### AUR\n\nSpécifiquement pour les utilisateurs d'Arch Linux, `code2prompt` est disponible dans l'AUR.\n\n`code2prompt` est disponible dans l'[AUR](https://aur.archlinux.org/packages?O=0&K=code2prompt). Installez-le via n'importe quel assistant AUR.\n\n```sh\nparu/yay -S code2prompt\n```\n\n### Nix\n\nSi vous utilisez Nix, vous pouvez l'installer en utilisant soit nix-env, soit nix profile.\n\n```sh\n# sans flakes :\nnix-env -iA nixpkgs.code2prompt\n# avec flakes :\nnix profile install nixpkgs#code2prompt\n```\n\n## Kit de développement logiciel (SDK) 🐍\n\n### Pypi\n\nVous pouvez télécharger les liaisons Python à partir de Pypi\n\n```sh\npip install code2prompt_rs\n```\n\n### Construction à partir du code source\n\n<Steps>\n\n1.  🛠️ Installer les prérequis :\n\n    - [Rust](https://www.rust-lang.org/tools/install) et Cargo\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 Cloner le référentiel :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt/crates/code2prompt-python\n    ```\n\n3.  📦 Installer les dépendances :\n\n    La commande `rye` créera un environnement virtuel et installera toutes les dépendances.\n\n    ```sh\n    rye sync\n    ```\n\n4.  ⚙️ Construire le paquet :\n\n    Vous développerez le paquet dans l'environnement virtuel situé dans le dossier `.venv` à la racine du projet.\n\n    ```sh\n    rye run maturin develop -r\n    ```\n\n</Steps>\n\n## Protocole de contexte de modèle (MCP) 🤖\n\n### Installation automatisée\n\nLe serveur MCP `code2prompt` sera bientôt disponible dans les registres MCP.\n\n### Installation manuelle\n\nLe serveur MCP `code2prompt` est encore un prototype et sera intégré au référentiel principal bientôt.\n\nPour exécuter le serveur MCP, localement pour l'utiliser avec `Cline`, `Goose` ou `Aider` :\n\n<Steps>\n\n1.  🛠️ Installer les prérequis :\n\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 Cloner le référentiel :\n\n    ```sh\n    git clone https://github.com/odancona/code2prompt-mcp.git\n    cd code2prompt-mcp\n    ```\n\n3.  📦 Installer les dépendances :\n\n    La commande `rye` créera un environnement virtuel et installera toutes les dépendances dans le dossier `.venv`.\n\n    ```sh\n    rye sync\n    ```\n\n4.  🚀 Exécuter le serveur :\n\n    Le serveur MCP est maintenant installé. Vous pouvez l'exécuter en utilisant :\n\n    ```sh\n    . .venv/bin/activate\n    python -m src/code2prompt_mcp/main.py\n    ```\n\n5.  🔌 Intégrer avec les agents :\n\n            Par exemple, vous pouvez l'intégrer avec `Cline`, en utilisant une configuration similaire :\n\n            ```json\n            {\n              \"mcpServers\": {\n                \"code2prompt\": {\n                  \"command\": \"bash\",\n                  \"args\": [\n                    \"-c\",\n                    \"cd /home/olivier/projet/code2prompt-mcp && rye run python /home/olivier/projet/code2prompt-mcp/src/code2prompt_mcp/main.py\"\n                  ],\n                  \"env\": {}\n                }\n              }\n            }\n            ```\n\n</Steps>\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/how_to/ssh.md",
    "content": "---\ntitle: Utiliser Code2prompt CLI avec SSH\ndescription: Un guide pour utiliser Code2Prompt CLI avec SSH pour l'analyse à distance d'une base de code.\n---\n\n## Pourquoi ça ne fonctionne pas ?\n\nLorsque vous essayez d'exécuter la CLI `code2prompt` sur un serveur distant via SSH, la commande est incapable de trouver le presse-papiers. En effet, la CLI `code2prompt` utilise le presse-papiers pour copier l'invite générée, et les sessions SSH n'ont généralement pas accès au presse-papiers local.\n\n## Solution\n\nPour utiliser la CLI `code2prompt` avec SSH, vous pouvez rediriger la sortie vers un fichier au lieu de la copier dans le presse-papiers. De cette façon, vous pouvez toujours générer l'invite et la sauvegarder pour une utilisation ultérieure.\n\nUtilisez l'option `--output-file` pour spécifier le fichier de sortie où l'invite générée sera enregistrée. Par exemple :\n\n```sh\nssh user@remote-server \"code2prompt path/to/codebase -O output.txt\"\n```\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/references/command_line_options.md",
    "content": "---\ntitle: Options de ligne de commande Code2Prompt\ndescription: Guide de référence pour toutes les options CLI disponibles dans Code2Prompt.\n---\n\n# Options de ligne de commande\n\nPlease let me know if you want me to translate anything else.\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/references/default_template.md",
    "content": "---\ntitle: Modèle par défaut pour Code2Prompt\ndescription: Découvrez la structure du modèle par défaut utilisé dans Code2Prompt.\n---\n\n# Modèle par défaut\n\n(Note: I translated the content as per your request, and as there was no actual content to translate apart from the title, description and heading, I left it as is. If there's more content to translate, please provide it.)\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/tutorials/getting_started.mdx",
    "content": "---\ntitle: Getting Started with Code2Prompt\ndescription: A comprehensive tutorial introducing Code2Prompt's core functionality and its use across CLI, SDK, and MCP integrations.\n---\n\nimport { Aside } from \"@astrojs/starlight/components\";\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\n\n<Card title=\"Présentation du Tutoriel\">\n  Bienvenue dans Code2Prompt ! Ce tutoriel fournit une introduction complète à\n  l'utilisation de Code2Prompt pour générer des invites prêtes à être utilisées\n  par l'IA à partir de vos bases de code. Nous explorerons sa fonctionnalité\n  principale et démontrerons son utilisation à travers différentes méthodes\n  d'intégration : Interface de Ligne de Commande (CLI), Kit de Développement de\n  Logiciels (SDK) et Protocole de Contexte de Modèle (MCP).\n</Card>\n\n## Qu'est-ce que Code2Prompt ?\n\nCode2Prompt est un outil polyvalent conçu pour combler le fossé entre votre base de code et les Modèles de Langage à Grande Échelle (LLM). Il extrait intelligemment des extraits de code pertinents, applique un filtrage puissant et formate les informations en invites structurées optimisées pour la consommation LLM. Cela simplifie des tâches telles que la documentation de code, la détection de bogues, la refactorisation, etc.\n\nCode2Prompt offre différents points d'intégration :\n\n<Tabs>\n  <TabItem label=\"Core\" icon=\"seti:rust\">\n    Une bibliothèque Rust de base qui fournit les fondations pour l'ingestion de\n    code et la génération d'invites.\n  </TabItem>\n  <TabItem label=\"CLI\" icon=\"seti:powershell\">\n    Une interface de ligne de commande conviviale pour une génération rapide\n    d'invites. Idéale pour une utilisation interactive et des tâches\n    ponctuelles.\n  </TabItem>\n  <TabItem label=\"SDK\" icon=\"seti:python\">\n    Un Kit de Développement de Logiciels (SDK) puissant pour une intégration\n    transparente dans vos projets Python. Parfait pour automatiser la génération\n    d'invites dans des workflows plus larges.\n  </TabItem>\n  <TabItem label=\"MCP\" icon=\"seti:db\">\n    Un serveur de Protocole de Contexte de Modèle (MCP) pour une intégration\n    avancée avec les agents LLM. Permet des interactions sophistiquées en temps\n    réel avec votre base de code.\n  </TabItem>\n</Tabs>\n\n## 📥 Installation\n\nPour des instructions d'installation détaillées pour toutes les méthodes (CLI, SDK, MCP), veuillez vous référer au [Guide d'Installation](/../../docs/how_to/install).\n\n## 🏁 Génération d'Invites : Un Exemple de CLI\n\nCommençons par un exemple simple en utilisant la CLI. Créez un projet échantillon :\n\n```bash\nmkdir -p my_project/{src,tests}\ntouch my_project/src/main.rs my_project/tests/test_1.rs\necho 'fn main() { println!(\"Hello, world!\"); }' > my_project/src/main.rs\n```\n\nMaintenant, générez une invite :\n\n```bash\ncode2prompt my_project\n```\n\nCela copie une invite dans votre presse-papiers. Vous pouvez personnaliser cela :\n\n- **Filtrage :** `code2prompt my_project --include=\"*.rs\" --exclude=\"tests/*\"` (inclut uniquement les fichiers `.rs`, exclut le répertoire `tests`)\n- **Fichier de sortie :** `code2prompt my_project --output-file=my_prompt.txt`\n- **Sortie JSON :** `code2prompt my_project -O json` (sortie JSON structurée)\n- **Modèles personnalisés :** `code2prompt my_project -t my_template.hbs` (nécessite la création de `my_template.hbs`)\n\nVoir les tutoriels [Apprendre le filtrage de contexte](/../../docs/tutorials/learn_filters) et [Apprendre les modèles Handlebar](/../../docs/tutorials/learn_templates) pour en savoir plus sur les utilisations avancées.\n\n## 🐍 Intégration SDK (Python)\n\nPour un contrôle programmatique, utilisez le SDK Python :\n\n```python\nfrom code2prompt_rs import Code2Prompt\n\nconfig = {\n    \"path\": \"my_project\",\n    \"include_patterns\": [\"*.rs\"],\n    \"exclude_patterns\": [\"tests/*\"],\n}\n\nc2p = Code2Prompt(**config)\nprompt = c2p.generate_prompt()\nprint(prompt)\n```\n\nCela nécessite l'installation du SDK (`pip install code2prompt_rs`). Référez-vous à la documentation du SDK pour plus de détails.\n\n## 🤖 Intégration du Serveur MCP (Avancé)\n\nPour une intégration avancée avec les agents LLM, exécutez le serveur MCP `code2prompt` (voir le guide d'installation pour les détails). Cela permet aux agents de demander du contexte de code de manière dynamique. Il s'agit d'une fonctionnalité avancée, et une documentation supplémentaire est disponible sur le site Web du projet.\n\n<Card title=\"Étapes suivantes\">\n  Explorez les tutoriels avancés et la documentation pour maîtriser les\n  capacités de Code2Prompt et l'intégrer dans vos workflows.\n</Card>\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/tutorials/learn_filters.mdx",
    "content": "---\ntitle: Apprendre le filtrage de contexte avec Code2Prompt\ndescription: Découvrez comment exclure ou inclure des fichiers dans vos invites LLM à l'aide d'options de filtrage puissantes.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"Présentation du tutoriel\">\n  Ce tutoriel démontre comment utiliser l'outil de modèle de glob dans\n  l'interface de ligne de commande `code2prompt` pour filtrer et gérer des\n  fichiers en fonction de modèles d'inclusion et d'exclusion.\n</Card>\n\nLes modèles de glob fonctionnent de manière similaire à des outils comme `tree` ou `grep`, offrant des capacités de filtrage puissantes. Consultez l'[explication détaillée](/docs/explanations/glob_patterns) pour plus d'informations.\n\n---\n\n## Prérequis\n\nAssurez-vous d'avoir installé `code2prompt`. Si vous ne l'avez pas encore installé, reportez-vous au [Guide d'installation](/docs/how_to/install).\n\n---\n\n## Comprendre les modèles d'inclusion et d'exclusion\n\nLes modèles de glob vous permettent de spécifier des règles pour filtrer des fichiers et des répertoires.\n\n- **Modèles d'inclusion** (`--include`) : Spécifiez les fichiers et répertoires que vous souhaitez inclure.\n- **Modèles d'exclusion** (`--exclude`) : Spécifiez les fichiers et répertoires que vous souhaitez exclure.\n- **Priorité** (`--include-priority`) : Résout les conflits entre les modèles d'inclusion et d'exclusion.\n\n---\n\n## Configuration de l'environnement\n\nPour pratiquer avec les modèles de glob, créons une structure de dossier échantillon avec quelques fichiers.\n\n### Script Bash pour générer la structure de test\n\nExécutez ce script pour configurer une structure de répertoire temporaire :\n\n```bash\n#!/bin/bash\n\n# Créer le répertoire de base\nmkdir -p test_dir/{lowercase,uppercase,.secret}\n\n# Créer des fichiers dans la structure\necho \"content foo.py\" > \"test_dir/lowercase/foo.py\"\necho \"content bar.py\" > \"test_dir/lowercase/bar.py\"\necho \"content baz.py\" > \"test_dir/lowercase/baz.py\"\necho \"content qux.txt\" > \"test_dir/lowercase/qux.txt\"\necho \"content corge.txt\" > \"test_dir/lowercase/corge.txt\"\necho \"content grault.txt\" > \"test_dir/lowercase/grault.txt\"\n\necho \"CONTENT FOO.py\" > \"test_dir/uppercase/FOO.PY\"\necho \"CONTENT BAR.py\" > \"test_dir/uppercase/BAR.PY\"\necho \"CONTENT BAZ.py\" > \"test_dir/uppercase/BAZ.PY\"\necho \"CONTENT QUX.txt\" > \"test_dir/uppercase/QUX.TXT\"\necho \"CONTENT CORGE.txt\" > \"test_dir/uppercase/CORGE.TXT\"\necho \"CONTENT GRAULT.txt\" > \"test_dir/uppercase/GRAULT.TXT\"\n\necho \"top secret\" > \"test_dir/.secret/secret.txt\"\n```\n\nPour nettoyer la structure plus tard, exécutez :\n\n```bash\nrm -rf test_dir\n```\n\nCela créera la structure de répertoire suivante :\n\nimport { FileTree } from \"@astrojs/starlight/components\";\n\n<FileTree>\n- test_dir \n  - lowercase \n    - foo.py \n    - bar.py \n    - baz.py \n    - qux.txt \n    - corge.txt \n    - grault.txt\n  - uppercase \n    - FOO.py \n    - BAR.py \n    - BAZ.py \n    - QUX.txt \n    - CORGE.txt \n    - GRAULT.txt \n  - .secret \n    - secret.txt\n</FileTree>\n\n---\n\n## Exemples : Filtrage de fichiers avec des modèles d'inclusion et d'exclusion\n\n### Cas 1 : Aucun modèle d'inclusion, aucun modèle d'exclusion\n\nCommande :\n\n```bash\ncode2prompt test_dir\n```\n\n#### Résultat\n\nTous les fichiers sont inclus :\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n- `.secret/secret.txt`\n\n---\n\n### Cas 2 : Exclure des types de fichiers spécifiques\n\nExclure les fichiers `.txt` :\n\n```bash\ncode2prompt test_dir --exclude=\"*.txt\"\n```\n\n#### Résultat\n\nExclus :\n\n- Tous les fichiers `.txt`\n\nInclus :\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n\n---\n\n### Cas 3 : Inclure des types de fichiers spécifiques\n\nInclure uniquement les fichiers Python :\n\n```bash\ncode2prompt test_dir --include=\"*.py\"\n```\n\n#### Résultat\n\nInclus :\n\n- Tous les fichiers `.py`\n\nExclus :\n\n- `.secret/secret.txt`\n\n---\n\n### Cas 4 : Inclure et exclure avec priorité\n\nInclure les fichiers `.py` mais exclure les fichiers dans le dossier `uppercase` :\n\n```bash\ncode2prompt test_dir --include=\"*.py\" --exclude=\"**/uppercase/*\" --include-priority=true\n```\n\n#### Résultat\n\nInclus :\n\n- Tous les fichiers `lowercase/1` ayant l'extension `.py`\n\nExclus :\n\n- Tous les fichiers `uppercase`\n- `.secret/secret.txt`\n\n---\n\n## Résumé\n\nL'outil de modèle de glob dans `code2prompt` vous permet de filtrer efficacement des fichiers et des répertoires à l'aide de :\n\n- `--include` pour spécifier les fichiers à inclure\n- `--exclude` pour les fichiers à exclure\n- `--include-priority` pour résoudre les conflits entre les modèles\n\nPour pratiquer, configurez le répertoire échantillon, essayez les commandes et voyez comment l'outil filtre les fichiers de manière dynamique.\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/tutorials/learn_templates.mdx",
    "content": "---\ntitle: Apprendre les modèles Handlebar avec Code2Prompt\ndescription: Comprendre comment utiliser et créer des modèles Handlebars personnalisés pour la génération de invites.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"Vue d'ensemble du tutoriel\">\n  Ce tutoriel démontre comment utiliser et créer des modèles Handlebars\n  personnalisés pour la génération d'invites dans l'outil de ligne de commande\n  `code2prompt`.\n</Card>\n\n---\n\n## Prérequis\n\nAssurez-vous d'avoir `code2prompt` installé. Si vous ne l'avez pas encore installé, reportez-vous au [Guide d'installation](/docs/how_to/install).\n\n---\n\n## Qu'est-ce que les modèles Handlebars ?\n\n[Handlebars](https://handlebarsjs.com/) est un moteur de templating populaire qui permet de créer des modèles dynamiques à l'aide de placeholders.\nDans `code2prompt`, les modèles Handlebars sont utilisés pour formater les invites générées en fonction de la structure du codebase et des variables définies par l'utilisateur.\n\n## Comment utiliser les modèles Handlebars ?\n\nVous pouvez utiliser ces modèles en passant le drapeau `-t` ou `--template` suivi du chemin vers le fichier de modèle. Par exemple :\n\n```sh\ncode2prompt path/to/codebase -t templates/document-the-code.hbs\n```\n\n## Syntaxe des modèles\n\nLes modèles Handlebars utilisent une syntaxe simple pour les placeholders et les expressions. Vous placerez les variables entre des doubles accolades `{{variable_name}}` pour les inclure dans l'invite générée.\n`Code2prompt` fournit un ensemble de variables par défaut que vous pouvez utiliser dans vos modèles :\n\n- `absolute_code_path` : Le chemin absolu vers le codebase.\n- `source_tree` : L'arbre de source du codebase, qui comprend tous les fichiers et répertoires.\n- `files` : Une liste de fichiers dans le codebase, y compris leurs chemins et contenus.\n- `git_diff` : Le diff Git du codebase, si applicable.\n- `code` : Le contenu du code du fichier en cours de traitement.\n- `path` : Le chemin du fichier en cours de traitement.\n\nVous pouvez également utiliser des helpers Handlebars pour effectuer des logiques conditionnelles, des boucles et d'autres opérations dans vos modèles. Par exemple :\n\n```handlebars\n{{#if files}}\n  {{#each files}}\n    Fichier :\n    {{this.path}}\n    Contenu :\n    {{this.content}}\n  {{/each}}\n{{else}}\n  Aucun fichier trouvé.\n{{/if}}\n```\n\n---\n\n## Modèles existants\n\n`code2prompt` est livré avec un ensemble de modèles intégrés pour les cas d'utilisation courants. Vous pouvez les trouver dans le répertoire [`templates`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates).\n\n### [`document-the-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/document-the-code.hbs)\n\nUtilisez ce modèle pour générer des invites pour documenter le code. Il ajoutera des commentaires de documentation à toutes les fonctions publiques, méthodes, classes et modules du codebase.\n\n### [`find-security-vulnerabilities.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/find-security-vulnerabilities.hbs)\n\nUtilisez ce modèle pour générer des invites pour trouver des vulnérabilités de sécurité potentielles dans le codebase. Il recherchera des problèmes de sécurité courants et fournira des recommandations sur la façon de les corriger ou de les atténuer.\n\n### [`clean-up-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/clean-up-code.hbs)\n\nUtilisez ce modèle pour générer des invites pour nettoyer et améliorer la qualité du code. Il recherchera des opportunités pour améliorer la lisibilité, la conformité aux meilleures pratiques, l'efficacité, la gestion des erreurs, etc.\n\n### [`fix-bugs.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/fix-bugs.hbs)\n\nUtilisez ce modèle pour générer des invites pour corriger les bogues dans le codebase. Il aidera à diagnostiquer les problèmes, à fournir des suggestions de correction et à mettre à jour le code avec les corrections proposées.\n\n### [`write-github-pull-request.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-pull-request.hbs)\n\nUtilisez ce modèle pour créer une description de pull request GitHub en markdown en comparant le diff Git et le log Git de deux branches.\n\n### [`write-github-readme.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-readme.hbs)\n\nUtilisez ce modèle pour générer un fichier README de haute qualité pour le projet, adapté à l'hébergement sur GitHub. Il analysera le codebase pour comprendre son objectif et sa fonctionnalité, et générera le contenu du README en format Markdown.\n\n### [`write-git-commit.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-git-commit.hbs)\n\nUtilisez ce modèle pour générer des commits Git à partir des fichiers en scène dans votre répertoire Git. Il analysera le codebase pour comprendre son objectif et sa fonctionnalité, et générera le contenu du message de commit en format Markdown.\n\n### [`improve-performance.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/improve-performance.hbs)\n\nUtilisez ce modèle pour générer des invites pour améliorer les performances du codebase. Il recherchera des opportunités d'optimisation, fournira des suggestions spécifiques et mettra à jour le code avec les modifications.\n\n## Variables définies par l'utilisateur\n\n`code2prompt` prend en charge l'utilisation de variables définies par l'utilisateur dans les modèles Handlebars. Toutes les variables du modèle qui ne font pas partie du contexte par défaut (`absolute_code_path`, `source_tree`, `files`) seront traitées comme des variables définies par l'utilisateur.\n\nLors de la génération d'invites, `code2prompt` invitera l'utilisateur à saisir des valeurs pour ces variables définies par l'utilisateur. Cela permet une personnalisation supplémentaire des invites générées en fonction des entrées utilisateur.\n\nPar exemple, si votre modèle inclut `{{challenge_name}}` et `{{challenge_description}}`, vous serez invité à saisir des valeurs pour ces variables lors de l'exécution de `code2prompt`.\n\nCette fonctionnalité permet de créer des modèles réutilisables qui peuvent être adaptés à différents scénarios en fonction des informations fournies par l'utilisateur.\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/vision.mdx",
    "content": "---\ntitle: La vision de Code2Prompt\ndescription: Découvrez la vision derrière Code2Prompt et comment il améliore les interactions LLM avec le code.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Aside } from \"@astrojs/starlight/components\";\n\n<Card title=\"Objectif 🎯\">\n  `code2prompt` a été créé pour aider les développeurs et les agents IA à\n  interagir avec les bases de code de manière plus efficace.\n</Card>\n\n## Le problème 🚩\n\nLes modèles de langage à grande échelle (LLM) ont révolutionné la façon dont nous interagissons avec le code. Cependant, ils font encore face à des défis importants en matière de génération de code :\n\n- **Planification et raisonnement** : Les LLM manquent de capacité de planification et de raisonnement, qui est cruciale pour des tâches telles que la génération de code, la refactorisation et le débogage. Ils ont souvent du mal à avoir une vue d'ensemble et sont shortsightés.\n- **Taille du contexte** : Les LLM ont une fenêtre de contexte limitée, ce qui restreint leur capacité à analyser et à comprendre de grandes bases de code.\n- **Hallucination** : Les LLM peuvent générer du code qui semble correct mais est en réalité incorrect ou absurde. Ce phénomène, appelé hallucination, se produit lorsque le modèle manque de contexte suffisant ou de compréhension de la base de code.\n\nC'est là que `code2prompt` intervient.\n\n## La solution ✅\n\nNous pensons que la planification et le raisonnement peuvent être atteints par des humains ou des agents IA avec des techniques de scaffolding. Ces agents doivent collecter un **contexte de haute qualité** de la base de code qui est filtré, structuré et formaté pour la tâche à accomplir.\n\nLa règle générale serait :\n\n<Aside type=\"tip\">\n  > fournir le moins de contexte possible, mais autant que nécessaire\n</Aside>\n\nCeci est pratiquement difficile à atteindre, surtout pour de grandes bases de code. Cependant, `code2prompt` est un outil simple qui peut aider les développeurs et les agents IA à ingérer la base de code de manière plus efficace.\n\nIl automatise le processus de parcours d'une base de code, de filtrage de fichiers et de formatage en invites structurées que les LLM peuvent comprendre. Ce faisant, il aide à atténuer les défis de planification, de raisonnement et d'hallucination.\n\nVous pouvez comprendre comment `code2prompt` est conçu pour relever ces défis dans la section suivante.\n\n## Architecture ⛩️\n\n<img\n  src=\"/assets/images/architecture.svg\"\n  alt=\"Architecture de code2prompt\"\n  style=\"width: 75%;\"\n/>\n\n`code2prompt` est conçu de manière modulaire, permettant une intégration facile dans divers flux de travail. Il peut être utilisé comme une bibliothèque principale, une interface de ligne de commande (CLI), un kit de développement logiciel (SDK) ou même comme un serveur de protocole de contexte de modèle (MCP).\n\n### Principal\n\n`code2prompt` est un outil d'ingestion de code qui simplifie le processus de création d'invites LLM pour l'analyse de code, la génération et d'autres tâches. Il fonctionne en parcourant les répertoires, en construisant une structure arborescente et en collectant des informations sur chaque fichier. La bibliothèque principale peut être facilement intégrée dans d'autres applications.\n\n### CLI\n\nL'interface de ligne de commande (CLI) de `code2prompt` a été conçue pour que les humains génèrent des invites directement à partir de votre base de code. L'invite générée est automatiquement copiée dans votre presse-papiers et peut également être enregistrée dans un fichier de sortie. De plus, vous pouvez personnaliser la génération d'invites à l'aide de modèles Handlebars. Consultez les invites fournies dans la documentation !\n\n### SDK\n\nLe kit de développement logiciel (SDK) de `code2prompt` offre une liaison Python à la bibliothèque principale. Ceci est parfait pour les agents IA ou les scripts d'automatisation qui souhaitent interagir avec la base de code de manière transparente. Le SDK est hébergé sur Pypi et peut être installé via pip.\n\n### MCP\n\n`code2prompt` est également disponible en tant que serveur de protocole de contexte de modèle (MCP), ce qui vous permet de l'exécuter en tant que service local. Cela permet aux LLM de fournir un outil pour collecter automatiquement un contexte bien structuré de votre base de code.\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/fr/docs/welcome.mdx",
    "content": "---\ntitle: Documentation Code2Prompt\ndescription: Documentation officielle de Code2prompt\ntemplate: splash\nhero:\n  tagline: Transformez votre code en invites optimisées pour l'IA en secondes\n  image:\n    file: ../../../../assets/logo_dark_v0.0.1.svg\n  actions:\n    - text: Commencer 🚀\n      link: ../../docs/tutorials/getting_started\n    - text: Installation 📥\n      link: ../../docs/how_to/install\n---\n\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\nimport { LinkCard } from \"@astrojs/starlight/components\";\n\n## Démarrage rapide\n\n<LinkCard title=\"Commencer 🚀\" href=\"../../docs/tutorials/getting_started\" />\n<LinkCard title=\"Installation 📥\" href=\"../../docs/how_to/install\" />\n<LinkCard\n  title=\"Apprendre le filtrage 🔍\"\n  href=\"../../docs/tutorials/learn_filters\"\n/>\n<LinkCard\n  title=\"Apprendre la mise en page 📝\"\n  href=\"../../docs/tutorials/learn_templates\"\n/>\n<LinkCard title=\"Vision 🔮\" href=\"../../docs/vision\" />\n\n`code2prompt` est un outil puissant d'ingestion de code conçu pour générer des invites pour l'analyse de code, la génération et d'autres tâches. Il fonctionne en parcourant les répertoires, en construisant une structure d'arbre et en collectant des informations sur chaque fichier.\n\nIl simplifie le processus de combinaison et de formatage du code, facilitant l'analyse, la documentation ou la refactorisation du code à l'aide de LLMs.\n\nVous pouvez utiliser `code2prompt` de les manières suivantes :\n\n<CardGrid>\n  <Card title=\"Coeur\" icon=\"seti:rust\">\n    Bibliothèque centrale extrêmement rapide pour l'ingestion de code\n  </Card>\n  <Card title=\"CLI\" icon=\"seti:powershell\">\n    Interface de ligne de commande spécialement conçue pour les humains\n  </Card>\n  <Card title=\"SDK\" icon=\"seti:python\">\n    Kit de développement logiciel pour les agents d'IA et les scripts\n    d'automatisation\n  </Card>\n  <Card title=\"MCP\" icon=\"seti:folder\">\n    Serveur de protocole de contexte de modèle pour LLMs sur steroids\n  </Card>\n</CardGrid>\n\n## Fonctionnalités clés\n\n- **Générer des invites LLM** : Convertissez rapidement des bases de code entières en invites structurées LLM.\n- **Filtrage par modèle Glob** : Incluez ou excluez des fichiers et des répertoires spécifiques à l'aide de modèles Glob.\n- **Modèles personnalisables** : Adaptez la génération d'invites avec des modèles Handlebars.\n- **Comptage des jetons** : Analysez l'utilisation des jetons et optimisez pour les LLMs avec des fenêtres de contexte variables.\n- **Intégration Git** : Incluez les différences Git et les messages de commit dans les invites pour les revues de code.\n- **Respecte `.gitignore`** : Ignore automatiquement les fichiers répertoriés dans `.gitignore` pour rationaliser la génération d'invites.\n\n## Pourquoi `code2prompt` ?\n\n1. **Gagner du temps** :\n\n   - Automatise le processus de parcours d'une base de code et de formatage des fichiers pour les LLMs.\n   - Évite la copie et le collage répétitifs de code.\n\n2. **Améliorer la productivité** :\n\n   - Fournit un format structuré et cohérent pour l'analyse de code.\n   - Aide à identifier les bogues, à refactoriser le code et à écrire la documentation plus rapidement.\n\n3. **Gérer de grandes bases de code** :\n\n   - Conçu pour fonctionner de manière transparente avec de grandes bases de code, en respectant les limites de contexte des LLMs.\n\n4. **Workflows personnalisables** :\n   - Options flexibles pour filtrer les fichiers, utiliser des modèles et générer des invites ciblées.\n\n## Exemples de cas d'utilisation\n\n- **Documentation de code** :\n  Générez automatiquement de la documentation pour les fonctions publiques, les méthodes et les classes.\n\n- **Détection de bogues** :\n  Recherchez les bogues et les vulnérabilités potentiels en analysant votre base de code avec les LLMs.\n\n- **Refactorisation** :\n  Simplifiez et optimisez le code en générant des invites pour améliorer la qualité du code.\n\n- **Apprentissage et exploration** :\n  Comprenez de nouvelles bases de code en générant des résumés et des ventilations détaillées.\n\n- **Descriptions de commit Git et de PR** :\n  Générez des messages de commit significatifs et des descriptions de demandes de tirage à partir des différences Git.\n\n> Cette page a été traduite automatiquement pour votre commodité. Veuillez vous référer à la version anglaise pour le contenu original.\n"
  },
  {
    "path": "website/src/content/docs/ja/blog/2025.04.11_why_I_wrote_code2prompt.mdx",
    "content": "---\ntitle: \"Code2Prompt を開発した理由\"\ndate: 2025-04-11\nlastUpdated: 2025-04-11\ntags:\n  - open-source\n  - code2prompt\n  - AI\n  - Agent\nexcerpt: \"code2prompt の裏側: LLM ワークフローのコンテキスト課題に取り組むためのオープンソースの探求\"\nauthors:\n  - ODAncona\ncover:\n  alt: \"code2prompt が AI エージェントのためのコードコンテキストを合理化するイラスト\"\n  image: \"/src/assets/logo_dark_v0.0.2.svg\"\nfeatured: false\ndraft: false\n---\n\n## はじめに\n\n私は、Large Language Models (LLMs) がコーディングワークフローをどのように変革するか、テストやドキュメント文字列の生成、さらには数分で機能を出荷することに興味を持っています。しかし、これらのモデルをさらに押し進めるにつれて、いくつかの重要な課題が表面化しました。\n\n| 計画の困難さ | 高いトークンコスト | 幻覚     |\n| ------------ | ------------------ | -------- |\n| 🧠 ➡️ 🤯     | 🔥 ➡️ 💸           | 💬 ➡️ 🌀 |\n\nそこで、私は `code2prompt` に貢献し始めました。これは、Rust ベースのツールで、LLM に適切なコンテキストを供給するのに役立ちます。\n\nこの投稿では、私の旅を共有し、なぜ `code2prompt` が今日の関連性があり、統合が簡単で、私の頼りになるソリューションになったのかを説明します。\n\n## LLM との最初のステップ 👣\n\n私は 2023 年 11 月に `OpenAI Playground` で `text-davinci-003` を使って LLM を実験し始めました。言語モデルは新しい革命をもたらしました。優れた新しいアシスタントが、ほぼコマンドに従って単体テストやドキュメント文字列を生成するように感じました。私はモデルを限界まで押し上げ、小さな会話から倫理的なジレンマ、脱獄、そして複雑なコーディングタスクまで、すべてをテストしました。しかし、より大規模なプロジェクトに取り組むにつれて、モデルには明らかな限界があることにすぐに気付きました。最初は、コンテキストウィンドウに数百行のコードしか収められず、モデルはコードの目的や構造を理解するのに苦労することがよくありました。そのため、コンテキストの重要性が極めて高いことにすぐに気付きました。より簡潔な命令とより良いコンテキストが、結果をより良くするのです。\n\n![OpenAI Playground](/assets/blog/post1/playground.png)\n\n## モデルの進化 🏗️\n\nモデルは印象的な結果を生み出しましたが、より大きなコードベースや複雑なタスクでは苦労することがよくありました。私は、プロンプトを作成することに多くの時間を費やすよりも、実際にコーディングすることに多くの時間を費やしていることに気付きました。同時に、モデルは新しいバージョンのリリースとともに改善を続け、推論能力とコンテキストサイズが向上し、新しい視点や可能性が広がりました。その後、コンテキストウィンドウにほぼ 2000 行のコードを収めることができ、結果が向上しました。数回の反復で機能全体を記述することができ、結果が得られる速度に驚かされました。私は、LLM がコーディングの未来であると確信し、その革命の一部になりたいと考えました。\n\n## LLM による最初のプロジェクト 🚀\n\n私は、ロボット競技用の `ROS` パスファインディングモジュールを作成し、クリーンアーキテクチャの `Flutter` クロスプラットフォームアプリの機能を生成し、`Next.js` で小さなウェブアプリを作成して経費を管理しました。私は、見慣れないフレームワークでこの小さなアプリを 1 日で構築できたことが、大きな転換点となりました。LLM は単なるツールではなく、乗数であることを実感しました。私は、`bboxconverter` というバウンディングボックスを変換するパッケージを開発し、他にも多くのプロジェクトを行いました。LLM は、新しいテクノロジーやフレームワークを迅速に学ぶのに役立ちます。\n\n## 新しいパラダイム: Software 3.0 💡\n\n私は、LLM をさらに深く掘り下げ、エージェントや足場を構築し始めました。私は、[RestGPT](https://restgpt.github.io/) という有名な論文を再現しました。アイデアは素晴らしいものでした。LLM に OpenAPI 仕様のある REST API を呼び出す能力を与えることです。Spotify や TMDB のような。これらの機能は、**Software 3.0** と呼ぶ新しいソフトウェアプログラミングパラダイムを導入します。\n\n| Software 1.0 | Software 2.0 | Software 3.0   |\n| ------------ | ------------ | -------------- |\n| ルールベース | データ駆動型 | エージェント型 |\n\n同じアイデアが [MCP](https://modelcontextprotocol.io/introduction) プロトコルを推進しました。このプロトコルにより、LLM はツールやリソースを直接呼び出すことができます。\n\n## LLM の限界 🧩\n\n### 幻覚 🌀\n\n私は、RestGPT の有名な論文を再現しながら、LLM の深刻な限界に気付きました。論文の著者も私と同じ問題に遭遇しました。LLM は **幻覚** を起こしていました。実装されていないコードを生成し、引数をでっち上げ、単に命令に従うだけで、常識を働かせませんでした。\n\n### コンテキストサイズの制限 📏\n\nもう 1 つの限界はコンテキストサイズでした。LLM は、針を見つけることは得意ですが、意味を理解するのは苦手です。言語モデルにコンテキストを多く与えすぎると、詳細に迷い込み、全体像を見失いがちになります。\n\n![Curse of Dimensionality](/assets/blog/post1/curse_of_dimensionality.png)\n\nコンテキストを増やすと、LLM が正しい答えを見つけるのが難しくなります。私たちは、コンテキストサイズに関して妥協点を見つける必要があります。\n\n## より良い方法の探求: code2prompt 🔨\n\nそこで、私はコードコンテキストを迅速にロード、フィルタリング、整理する方法を必要としました。ファイルを手動でコピーしたり、スニペットをプロンプトに貼り付けたりしていましたが、これは煩雑でエラーが発生しやすい作業でした。そこで、私はコンテキストを自動化するツールを探し始めました。そして、ある日、Google で \"code2prompt\" を検索したところ、Mufeed による **Rust ベースのプロジェクト** を見つけました。このプロジェクトは約 200 個のスターを獲得していました。当時は基本的な CLI ツールでしたが、フィルター機能やテンプレートは限られていました。私は大きな可能性を感じ、すぐにコントリビューターになりました。\n\n## ビジョンと統合 🔮\n\n今日、LLM にコンテキストを提供する方法はたくさんあります。より大きなコンテキストから生成したり、Retrieval-Augmented Generation (RAG) を使用したり、コードを圧縮したり、これらの手法の組み合わせを使用したりできます。コンテキストの構築はホットトピックであり、今後数ヶ月で急速に進化するでしょう。しかし、私のアプローチは **KISS** です。Keep It Simple, Stupid です。LLM にコンテキストを提供する最も簡単で効率的な方法は、必要に応じてコンテキストを正確に構築することです。これは RAG とは異なり、決定論的です。\n\n## エージェントとの統合 👤\n\n私は、将来のエージェントはコンテキストを摂取する方法を必要とし、 `code2prompt` はそれを行う簡単で効率的な方法であると信じています。コードベースやドキュメント、メモなどのテキストリポジトリに最適です。 `code2prompt` を使用するのに最適な場所は、意味のある名前の規約があるコードベースです。たとえば、クリーンアーキテクチャでは、関心の分離とレイヤーが明確に分かれています。関連するコンテキストは通常、異なるファイルやフォルダーにありますが、同じ名前を共有します。\n\n**Glob パターン優先:** ファイルを選択または除外するのが簡単です。\n\nさらに、コアライブラリはステートフルコンテキストマネージャーとして設計されており、会話が進むにつれてファイルを追加または削除できます。これは、特定のタスクや目標のためのコンテキストを提供する場合に特に便利です。\n\n**ステートフルコンテキスト:** 会話が進むにつれてファイルを追加または削除できます。\n\nこれらの機能により、 `code2prompt` はエージェントベースのワークフローの最適なツールになります。MCP サーバーを使用すると、Aider や Goose などの人気のある AI エージェントフレームワークとシームレスに統合できます。\n\n## Code2prompt が重要な理由 ✊\n\nLLM が進化し、コンテキストウィンドウが拡大するにつれて、リポジトリ全体をプロンプトに強制的に押し込むだけで十分であるように思えるかもしれません。しかし、**トークンコスト** と **プロンプトの一貫性** は依然として、小規模な企業や開発者にとって大きな障害です。重要なコードに焦点を当てることで、 `code2prompt` は LLM の使用を効率的でコスト効果が高く、幻覚を起こしにくくします。\n\n**要するに:**\n\n- **幻覚を減らす** ことで、適切な量のコンテキストを提供します。\n- **トークン使用コストを減らす** ことで、適切なコンテキストを手動でキュレートします。\n- **LLM のパフォーマンスを向上させる** ことで、適切な量のコンテキストを提供します。\n- テキストリポジトリ用のコンテキストフィーダーとして、エージェントスタックと統合します。\n\n## オープンソースです! 🌐\n\n新しいコントリビューターは大歓迎です! Rust や革新的な AI ツールの構築に興味がある場合、またはコードベースのプロンプト用のより優れたワークフローを探している場合は、ぜひ参加してください。\n\nこのブログ投稿を最後まで読んでいただき、私のストーリーが code2prompt をチェックするきっかけになれば幸いです。\n\n**Olivier D'Ancona**\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/explanations/glob_pattern_filter.mdx",
    "content": "---\ntitle: Globパターンフィルターの動作原理\ndescription: Code2Promptがインクルード（-i）とエクスクルード（-e）のglobを使用して、どのファイルを保持または破棄するかを決定する方法。\n---\n\nCode2Prompt は glob パターンを使用してファイルとディレクトリを含めたり除外したりし、tree や grep などのツールと同様に動作します。2 つの独立した*リスト*の glob パターンを渡すことができます：\n\n- **インクルードリスト** (`--include` または `-i`) - \"これらのパターンはファイルを許可する\"\n- **エクスクルードリスト** (`--exclude` または `-e`) - \"これらのパターンはファイルを禁止する\"\n\nCode2prompt は、プロジェクト内のすべてのファイルについて、それを保持するか破棄するかを決定する必要があります。このページでは、ルールとその背後にある設計選択について説明します。\n\n---\n\n## 1. 集合と記号\n\n説明全体を通して、通常の集合記法を使用します\n\n| 記号                              | 意味                                                                |\n| --------------------------------- | ------------------------------------------------------------------- |\n| $A$                               | **少なくとも 1 つの**インクルードパターンに一致するファイルの集合   |\n| $B$                               | **少なくとも 1 つの**エクスクルードパターンに一致するファイルの集合 |\n| $\\Omega$                          | プロジェクトツリー全体（_宇宙_）                                    |\n| $C = A \\cap B$                    | 両方のリストに一致するファイル（_重複_）                            |\n| $D = \\Omega \\setminus (A \\cup B)$ | どちらのリストにも一致しないファイル                                |\n\n---\n\n## 2. 4 つの状況\n\n### 4 つの状況の概要\n\n| インクルードリスト | エクスクルードリスト | 保持されるファイル |\n| ------------------ | -------------------- | ------------------ |\n| A = ∅              | B = ∅                | Ω                  |\n| A = ∅              | B ≠ ∅                | ¬B                 |\n| A ≠ ∅              | B = ∅                | A                  |\n| A ≠ ∅              | B ≠ ∅                | A \\ B              |\n\n1. **インクルードリストなし、エクスクルードリストなし**\n\n   パターンが指定されていない場合、すべてのファイルが保持されます（`Ω`）。\n\n2. **エクスクルードリストのみ**\n\n   この場合、Code2Prompt はブラックリストとして機能し、除外されたパターンに一致するファイルを削除します（` Ω \\ B = ¬B`）。\n\n3. **インクルードリストのみ**\n\n   インクルードリストのみが指定されている場合、Code2Prompt はホワイトリストとして機能し、含まれるパターンに一致するファイルのみを保持します（`A`）。\n\n4. **インクルード*および*エクスクルードリスト**\n\n   両方のリストが指定されている場合、Code2Prompt はインクルードパターンに一致するファイルを保持しますが、エクスクルードパターンに一致するものは削除します（`A \\ B`）。\n\n---\n\n## 3. 重複についてさらに詳しく\n\n両方のリストが存在する場合（`A ≠ ∅`、`B ≠ ∅`）、重複`C`と残り`D`について\n4 つの論理的可能性があります。\n\n| `C`が欲しい？ | `D`が欲しい？ | 合理的？                                                    |\n| ------------- | ------------- | ----------------------------------------------------------- |\n| いいえ        | いいえ        | デフォルトの動作（`A \\ B`）                                 |\n| はい          | いいえ        | ケース 3 と同じ動作（`A`）                                  |\n| いいえ        | はい          | 驚き（\"要求した`C`を破棄し、要求しなかったものを保持する\"） |\n| はい          | はい          | ケース 1 と同じ動作（`Ω`）                                  |\n\nこのため、`--include-priority`オプションが削除されました。なぜなら、インクルードリストのみを持つ場合（ケース 3）と同じ結果になるからです。\n\n## 4. クイックリファレンステーブル\n\n| 保持したいもの…                                | 使用する           |\n| ---------------------------------------------- | ------------------ |\n| すべて                                         | `-i`なし、`-e`なし |\n| いくつかのパターン*以外の*すべて               | `-e`のみ           |\n| パターンに一致するもの*のみ*                   | `-i`のみ           |\n| `-i`に一致するもの、マイナス`-e`に一致するもの | `-i`**および**`-e` |\n\n---\n\nこの設計はメンタルモデルをシンプルに保ちます：\n\n- インクルードリストは存在するとすぐにホワイトリストになります。\n- エクスクルードリストはその上に重ねられたブラックリストです。\n- 重複はデフォルトで破棄されます\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/explanations/glob_patterns.md",
    "content": "---\ntitle: グロブパターンの理解\ndescription: Code2Promptにおけるグロブパターンとその使用方法についての詳細な説明\n---\n\nグロブパターンは、ワイルドカード文字を使用してファイル名とパスを一致させるシンプルで強力な方法です。これらは、コマンドラインインターフェイスやプログラミング言語で、ファイル名やディレクトリのセットを指定するために一般的に使用されます。以下は、最も一般的に使用されるグロブパターンの内訳です。\n\n## 基本的なワイルドカード\n\n- `*`: 任意の数の文字（0文字を含む）に一致します。\n  - 例: `*.txt`は、`.txt`で終わるすべてのファイルに一致します。\n\n- `?`: ちょうど1つの文字に一致します。\n  - 例: `file?.txt`は、`file1.txt`や`fileA.txt`には一致しますが、`file10.txt`には一致しません。\n\n- `[]`: 括弧内に含まれる任意の1つの文字に一致します。\n  - 例: `file[1-3].txt`は、`file1.txt`、`file2.txt`、`file3.txt`に一致します。\n\n- `[!]`または`[^]`: 括弧内に含まれない任意の文字に一致します。\n  - 例: `file[!1-3].txt`は、`file4.txt`や`fileA.txt`には一致しますが、`file1.txt`には一致しません。\n\n## 高度なパターン\n\n- `**`: 任意の数のディレクトリとサブディレクトリに再帰的に一致します。\n  - 例: `**/*.txt`は、現在のディレクトリとすべてのサブディレクトリ内の`.txt`ファイルをすべて一致させます。\n\n- `{}`: カンマで区切られたパターンのいずれかに一致します。\n  - 例: `file{1,2,3}.txt`は、`file1.txt`、`file2.txt`、`file3.txt`に一致します。\n\n## 例\n\n1. **ディレクトリ内のすべてのテキストファイルを一致させる:**\n\n   ```sh\n   *.txt\n   ```\n\n2. **拡張子前に1桁の数字が付くファイルをすべて一致させる:**\n\n   ```sh\n   file?.txt\n   ```\n\n3. **`.jpg`または`.png`の拡張子を持つファイルを一致させる:**\n\n   ```sh\n   *.{jpg,png}\n   ```\n\n4. **サブディレクトリ内のすべての`.txt`ファイルを一致させる:**\n\n   ```sh\n   **/*.txt\n   ```\n\n5. **`a`または`b`で始まり、`.txt`で終わるファイルを一致させる:**\n\n   ```sh\n   {a,b}*.txt\n   ```\n\n## 使用例\n\n- **コマンドラインツール:** グロブパターンは、`ls`、`cp`、`mv`、`rm`などのコマンドラインツールで、複数のファイルまたはディレクトリを指定するために広く使用されています。\n- **プログラミング言語:** Python、JavaScript、Rubyなどの言語は、Pythonの`glob`ライブラリなど、グロブパターンをファイルの一致に使用するためのライブラリをサポートしています。\n- **ビルドシステム:** Makefileなどのツールは、ソースファイルと依存関係を指定するためにグロブパターンを使用しています。\n\n## 結論\n\nグロブパターンは、ファイル名とパスを一致させるための柔軟で直感的な方法を提供し、スクリプト作成、自動化、ファイル管理タスクに不可欠なものとなっています。これらのパターンを理解して活用することで、ファイルとディレクトリの処理における生産性と効率を大幅に向上させることができます。\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/explanations/tokenizers.md",
    "content": "---\ntitle: Code2Promptにおけるトークン化\ndescription: Code2PromptがLLM用にテキストを処理する方法について、トークン化の概要を学びます。\n---\n\n言語モデルを扱う場合、テキストをモデルが理解できる形式—**トークン**（数列）に変換する必要があります。この変換は、**トークナイザー**によって処理されます。\n\n---\n\n## トークナイザーとは？\n\nトークナイザーは、生のテキストをトークンに変換します。これは、言語モデルが入力を処理するための基本的な構成要素です。これらのトークンは、トークナイザーの設計に応じて、単語、サブワード、または個々の文字を表すことができます。\n\n`code2prompt`では、**tiktoken**トークナイザーを使用します。これは、効率的で堅牢であり、OpenAIモデルに最適化されています。\nその機能は、公式リポジトリで確認できます。\n\n👉 [tiktoken GitHub リポジトリ](https://github.com/openai/tiktoken)\n\nトークナイザー全般について詳しく知りたい場合は、以下を参照してください。\n\n👉 [Mistral トークン化ガイド](https://docs.mistral.ai/guides/tokenization/).\n\n## `code2prompt`での実装\n\nトークン化は、[`tiktoken-rs`](https://github.com/zurawiki/tiktoken-rs)を使用して実装されます。`tiktoken`は、OpenAIモデルで使用される以下のエンコーディングをサポートしています。\n\n| CLI引数 | エンコーディング名 | OpenAIモデル |\n| --- | --- | --- |\n| `cl100k` | `cl100k_base` | ChatGPTモデル、`text-embedding-ada-002` |\n| `p50k` | `p50k_base` | コードモデル、`text-davinci-002`、`text-davinci-003` |\n| `p50k_edit` | `p50k_edit` | `text-davinci-edit-001`、`code-davinci-edit-001`などの編集モデル |\n| `r50k` | `r50k_base`（または`gpt2`） | `davinci`などのGPT-3モデル |\n| `gpt2` | `o200k_base` | GPT-4oモデル |\n\nトークナイザーの詳細については、[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/66b988407d8d13cad5060a881dc8c892141f2d5c/examples/How_to_count_tokens_with_tiktoken.ipynb)を参照してください。\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/how_to/filter_files.md",
    "content": "---\ntitle: Code2Promptでのファイルフィルタリング\ndescription: 異なるフィルタリング方法を使用してファイルをインクルードまたはエクスクルードするステップバイステップガイド。\n---\n\n\n## 使用方法\n\nコードベースディレクトリからプロンプトを生成する:\n\n```sh\ncode2prompt path/to/codebase\n```\n\nカスタムHandlebarsテンプレートファイルを使用する:\n\n```sh\ncode2prompt path/to/codebase -t path/to/template.hbs\n```\n\nグロブパターンを使用してファイルをフィルタリングする:\n\n```sh\ncode2prompt path/to/codebase --include=\"*.rs,*.toml\"\n```\n\nグロブパターンを使用してファイルを除外する:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.txt,*.md\"\n```\n\nソースツリーから除外パターンに基づいてファイル/フォルダを除外する:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.npy,*.wav\" --exclude-from-tree\n```\n\n生成されたプロンプトのトークン数を表示する:\n\n```sh\ncode2prompt path/to/codebase --tokens\n```\n\nトークン数にトークナイザを指定する:\n\n```sh\ncode2prompt path/to/codebase --tokens --encoding=p50k\n```\n\nサポートされているトークナイザ: `cl100k`, `p50k`, `p50k_edit`, `r50k_bas`.\n> [!注意]  \n> 詳細は[トークナイザ](#tokenizers)を参照してください。\n\n生成されたプロンプトを出力ファイルに保存する:\n\n```sh\ncode2prompt path/to/codebase --output=output.txt\n```\n\n出力をJSONとして印刷する:\n\n```sh\ncode2prompt path/to/codebase --json\n```\n\nJSON出力の構造は以下の通りである:\n\n```json\n{\n  \"prompt\": \"<生成されたプロンプト>\", \n  \"directory_name\": \"codebase\",\n  \"token_count\": 1234,\n  \"model_info\": \"ChatGPTモデル、text-embedding-ada-002\",\n  \"files\": []\n}\n```\n\nGitコミットメッセージ（ステージングされたファイルに対して）を生成する:\n\n```sh\ncode2prompt path/to/codebase --diff -t templates/write-git-commit.hbs\n```\n\nPull Requestをブランチ比較（ステージングされたファイルに対して）で生成する:\n\n```sh\ncode2prompt path/to/codebase --git-diff-branch 'main, development' --git-log-branch 'main, development' -t templates/write-github-pull-request.hbs\n```\n\nソースコードブロックに行番号を追加する:\n\n```sh\ncode2prompt path/to/codebase --line-number\n```\n\nMarkdownコードブロック内のコードのラッピングを無効にする:\n\n```sh\ncode2prompt path/to/codebase --no-codeblock\n```\n\n- コードを別の言語に書き直す。\n- バグ/セキュリティ脆弱性を発見する。\n- コードを文書化する。\n- 新しい機能を実装する。\n\n> 最初にこれは、Claude 3.0の200Kコンテキストウィンドウを利用するために個人使用で書いたものであり、かなり役に立ったのでオープンソース化することにした！\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/how_to/install.mdx",
    "content": "---\ntitle: Code2Prompt のインストール\ndescription: Code2Prompt をさまざまなオペレーティングシステムにインストールするための完全なガイドです。\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Steps } from \"@astrojs/starlight/components\";\n\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\n\n<Card title=\"ガイドの概要\">\n  Code2Prompt\n  のインストールガイドへようこそ。このドキュメントでは、Windows、macOS、Linuxを含む様々なプラットフォームへのCode2Promptのインストール手順をステップごとに説明します。\n</Card>\n\n**TL;DR**\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n## 前提条件\n\nシステムに[Rust](https://www.rust-lang.org/tools/install)とcargoがインストールされていることを確認してください。\n\n```sh\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\nこれは、RustとCargoの最新安定版をインストールする公式の方法です。Rustをインストールした後、`PATH`変数を更新してください。ターミナルを再起動するか、インストーラーが提案するコマンドを実行します。\n\n```sh\nsource $HOME/.cargo/env\n```\n\nすべてが正しくインストールされていることを確認するには、次のコマンドを実行します。\n\n```sh\ncargo --version\ngit --version\n```\n\n## コマンドラインインターフェイス（CLI）👨‍💻\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n#### 🧪 GitHubから最新の（未公開の）バージョンをインストールする\n\ncrates.ioで公開される前に最新の機能や修正を使用したい場合:\n\n```sh\ncargo install --git https://github.com/mufeedvh/code2prompt\n```\n\n### ソースビルド\n\n開発者がソースからビルドしたり、プロジェクトに貢献したい場合に最適です。\n\n<Steps>\n\n1.  🛠️ 前提条件をインストールする :\n\n    - [Rust](https://www.rust-lang.org/tools/install)とCargo\n    - [Git](https://git-scm.com/downloads)\n\n2.  📥 リポジトリをクローンする :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt\n    ```\n\n3.  📦 バイナリをインストールする :\n\n    ソースからビルドしてインストールするには:\n\n    ```sh\n    cargo install --path crates/code2prompt\n    ```\n\n    バイナリをインストールせずにビルドするには:\n\n    ```sh\n    cargo build --release\n    ```\n\n    バイナリは`target/release`ディレクトリで利用できます。\n\n4.  🚀 実行する :\n\n    ```sh\n    code2prompt --help\n    ```\n\n</Steps>\n\n### バイナリリリース\n\nソースからビルドせずに最新バージョンを使用したいユーザーに最適です。\n\n[リリース](https://github.com/mufeedvh/code2prompt/releases)からお使いのOSの最新バイナリをダウンロードしてください。\n\n⚠️ バイナリリリースは、最新のGitHubバージョンよりも遅れる場合があります。最先端の機能を使用するには、ソースからビルドすることを検討してください。\n\n### AUR\n\n特にArch Linuxユーザー向けに、`code2prompt`はAURで利用可能です。\n\n`code2prompt`は[`AUR`](https://aur.archlinux.org/packages?O=0&K=code2prompt)で利用できます。AURヘルパーを使用してインストールしてください。\n\n```sh\nparu/yay -S code2prompt\n```\n\n### Nix\n\nNixを使用している場合、nix-envまたはnixプロファイルのいずれかを使用してインストールできます。\n\n```sh\n# without flakes:\nnix-env -iA nixpkgs.code2prompt\n# with flakes:\nnix profile install nixpkgs#code2prompt\n```\n\n## ソフトウェア開発キット（SDK）🐍\n\n### Pypi\n\nPypiからPythonバインディングをダウンロードできます\n\n```sh\npip install code2prompt_rs\n```\n\n### ソースビルド\n\n<Steps>\n\n1.  🛠️ 前提条件をインストールする :\n\n    - [Rust](https://www.rust-lang.org/tools/install)とCargo\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 リポジトリをクローンする :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt/crates/code2prompt-python\n    ```\n\n3.  📦 依存関係をインストールする :\n\n    `rye`コマンドは、仮想環境を作成し、すべての依存関係をインストールします。\n\n    ```sh\n    rye sync\n    ```\n\n4.  ⚙️ パッケージをビルドする :\n\n    プロジェクトのルートにある`.venv`フォルダ内の仮想環境でパッケージを開発します。\n\n    ```sh\n    rye run maturin develop -r\n    ```\n\n</Steps>\n\n## Model Context Protocol（MCP）🤖\n\n### 自動インストール\n\n`code2prompt`MCPサーバーは、近日中にMCPレジストリで利用できるようになります。\n\n### 手動インストール\n\n`code2prompt`MCPサーバーはまだプロトタイプであり、近日中にメインのリポジトリに統合されます。\n\n`Cline`,`Goose`または`Aider`で使用するために、ローカルでMCPサーバーを実行するには:\n\n<Steps>\n\n1.  🛠️ 前提条件をインストールする :\n\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 リポジトリをクローンする :\n\n    ```sh\n    git clone https://github.com/odancona/code2prompt-mcp.git\n    cd code2prompt-mcp\n    ```\n\n3.  📦 依存関係をインストールする :\n\n    `rye`コマンドは、仮想環境を作成し、`.venv`フォルダ内のすべての依存関係をインストールします。\n\n    ```sh\n    rye sync\n    ```\n\n4.  🚀 サーバーを実行する :\n\n    MCPサーバーはインストールされました。次のコマンドを使用して実行できます:\n\n    ```sh\n    . .venv/bin/activate\n    python -m src/code2prompt_mcp/main.py\n    ```\n\n5.  🔌 エージェントとの統合 :\n\n            例えば、次のような構成を使用して、`Cline`と統合できます:\n\n            ```json\n            {\n              \"mcpServers\": {\n                \"code2prompt\": {\n                  \"command\": \"bash\",\n                  \"args\": [\n                    \"-c\",\n                    \"cd /home/olivier/projet/code2prompt-mcp && rye run python /home/olivier/projet/code2prompt-mcp/src/code2prompt_mcp/main.py\"\n                  ],\n                  \"env\": {}\n                }\n              }\n            }\n            ```\n\n</Steps>\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/how_to/ssh.md",
    "content": "---\ntitle: Code2prompt CLIをSSHで使用する\ndescription: SSHを使用したCode2Prompt CLIによるリモートコードベース分析のガイド\n---\n\n## なぜ動作しないのか？\n\nSSH経由でリモートサーバー上で`code2prompt` CLIを実行しようとすると、コマンドはクリップボードを見つけることができません。これは、`code2prompt` CLIが生成されたプロンプトをコピーするためにクリップボードを使用するためであり、SSHセッションは通常、ローカルクリップボードにアクセスできないためです。\n\n## 解決策\n\n`code2prompt` CLIをSSHで使用するには、クリップボードにコピーする代わりに、出力先をファイルにリダイレクトすることができます。これにより、プロンプトを生成し、後で使用するために保存することができます。\n\n`--output-file`オプションを使用して、生成されたプロンプトが保存される出力ファイルを指定します。例えば:\n\n```sh\nssh user@remote-server \"code2prompt path/to/codebase -O output.txt\"\n```\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/references/command_line_options.md",
    "content": "---\ntitle: Code2Prompt コマンドラインオプション\ndescription: Code2Prompt で利用可能なすべての CLI オプションのリファレンスガイドです。\n---\n\n# コマンドラインオプション\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/references/default_template.md",
    "content": "---\ntitle: Code2Prompt のデフォルト テンプレート\ndescription: Code2Prompt で使用されるデフォルトのテンプレート構造について学びます。\n---\n\n# デフォルト テンプレート\n\n        ./\n        ├── ja/\n        │   ├── ブログ/\n        │   └── ドキュメント/\n        ├── fr/\n        │   ├── blog/\n        │   └── docs/\n        ├── de/\n        │   ├── blog/\n        │   └── docs/\n        ├── es/\n        │   ├── blog/\n        │   └── docs/\n\n becomes\n\n        ./\n        ├── ja/\n        │   ├── ブログ/\n        │   └── ドキュメント/\n        ├── fr/\n        │   ├── ../../blog/\n        │   └── ../../docs/\n        ├── de/\n        │   ├── ../../blog/\n        │   └── ../../docs/\n        ├── es/\n        │   ├── ../../blog/\n        │   └── ../../docs/\n\nso the code blocks ,commands and variable names remain the same\nthus\n\nHere is the complete response\n\n---\n\ntitle: Code2Prompt のデフォルト テンプレート\ndescription: Code2Prompt で使用されるデフォルトのテンプレート構造について学びます\n---\n\n# デフォルト テンプレート\n\n        ./\n        ├── ja/\n        │   ├── ブログ/\n        │   └── ドキュメント/\n        ├── fr/\n        │   ├── ../../blog/\n        │   └── ../../docs/\n        ├── de/\n        │   ├── ../../blog/\n        │   └── ../../docs/\n        ├── es/\n        │   ├── ../../blog/\n        │   └── ../../docs/\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/tutorials/getting_started.mdx",
    "content": "---\ntitle: Code2Prompt の使い方を始める\ndescription: Code2Prompt のコア機能と、CLI、SDK、MCP 統合におけるその使用法を紹介する総合的なチュートリアル。\n---\n\nimport { Aside } from \"@astrojs/starlight/components\";\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\n\n<Card title=\"チュートリアル概要\">\n  Code2Promptへようこそ！\n  このチュートリアルでは、Code2Promptを使用してコードベースからAI対応のプロンプトを生成する方法を総合的に紹介します。コア機能を探求し、コマンドラインインターフェイス（CLI）、ソフトウェア開発キット（SDK）、モデルコンテキストプロトコル（MCP）などの異なる統合方法での使用法を示します。\n</Card>\n\n## Code2Promptとは？\n\nCode2Promptは、コードベースと大規模言語モデル（LLM）間のギャップを埋めるように設計された汎用ツールです。関連するコードスニペットをインテリジェントに抽出し、強力なフィルタリングを適用し、LLM消費用に最適化された構造化されたプロンプトにフォーマットします。これにより、コードドキュメント、バグ検出、リファクタリングなどのタスクが簡素化されます。\n\nCode2Promptは異なる統合ポイントを提供します:\n\n<Tabs>\n  <TabItem label=\"コア\" icon=\"seti:rust\">\n    コード摂取とプロンプトの基礎を提供するコアRustライブラリです。\n  </TabItem>\n  <TabItem label=\"CLI\" icon=\"seti:powershell\">\n    クイックプロンプト生成のためのユーザーフレンドリーなコマンドラインインターフェイス。インタラクティブな使用とワンオフタスクに最適です。\n  </TabItem>\n  <TabItem label=\"SDK\" icon=\"seti:python\">\n    Pythonプロジェクトへのシームレスな統合のための強力なソフトウェア開発キット（SDK）。より大きなワークフロー内でのプロンプト生成の自動化に最適です。\n  </TabItem>\n  <TabItem label=\"MCP\" icon=\"seti:db\">\n    LLMエージェントとの高度な統合のためのモデルコンテキストプロトコル（MCP）サーバー。コードベースとの高度なリアルタイムインタラクションを可能にします。\n  </TabItem>\n</Tabs>\n\n## 📥 インストール\n\nすべての方法（CLI、SDK、MCP）の詳細なインストール手順については、総合的な[インストールガイド](/docs/how_to/install)を参照してください。\n\n## 🏁 プロンプトの生成：CLIの例\n\nCLIを使用した簡単な例から始めましょう。サンプルプロジェクトを作成します。\n\n```bash\nmkdir -p my_project/{src,tests}\ntouch my_project/src/main.rs my_project/tests/test_1.rs\necho 'fn main() { println!(\"Hello, world!\"); }' > my_project/src/main.rs\n```\n\nここで、プロンプトを生成します。\n\n```bash\ncode2prompt my_project\n```\n\nこれにより、プロンプトがクリップボードにコピーされます。これをカスタマイズできます。\n\n- **フィルタリング:** `code2prompt my_project --include=\"*.rs\" --exclude=\"tests/*\"`（.rsファイルのみを含め、testsディレクトリを除外）\n- **出力ファイル:** `code2prompt my_project --output-file=my_prompt.txt`\n- **JSON出力:** `code2prompt my_project -O json`（構造化されたJSON出力）\n- **カスタムテンプレート:** `code2prompt my_project -t my_template.hbs`（my_template.hbsを作成する必要があります）\n\n詳細な使用法については、[コンテキストフィルタリングの学習](/docs/tutorials/learn_filters)と[ハンドルバーテンプレートの学習](/docs/tutorials/learn_templates)のチュートリアルを参照してください。\n\n## 🐍 SDK統合（Python）\n\nプログラム制御を使用するには、Python SDKを使用します。\n\n```python\nfrom code2prompt_rs import Code2Prompt\n\nconfig = {\n    \"path\": \"my_project\",\n    \"include_patterns\": [\"*.rs\"],\n    \"exclude_patterns\": [\"tests/*\"],\n}\n\nc2p = Code2Prompt(**config)\nprompt = c2p.generate_prompt()\nprint(prompt)\n```\n\nこれには、SDK（`pip install code2prompt_rs`）のインストールが必要です。詳細については、SDKのドキュメントを参照してください。\n\n## 🤖 MCPサーバー統合（高度な）\n\nLLMエージェントとの高度な統合については、`code2prompt` MCPサーバーを実行します（詳細はインストールガイドを参照）。これにより、エージェントがコードコンテキストを動的に要求できます。これは高度な機能であり、プロジェクトのWebサイトでさらにドキュメントが提供されています。\n\n<Card title=\"次のステップ\">\n  Code2Promptの機能をマスターし、ワークフローに統合するには、高度なチュートリアルとドキュメントを参照してください。\n</Card>\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/tutorials/learn_filters.mdx",
    "content": "---\ntitle: Learn Context Filtering with Code2Prompt\ndescription: Learn how to exclude or include files in your LLM prompts using powerful filtering options.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"チュートリアルの概要\">\n  このチュートリアルでは、`code2prompt` CLIの**globパターン\n  ツール**を使用して、ファイルやディレクトリをフィルタリングし、管理する方法を説明します。\n</Card>\n\nGlobパターンは、`tree`や`grep`などのツールと同様に、強力なフィルタリング機能を提供します。詳細な説明は、[こちら](/docs/explanations/glob_patterns)をご覧ください。\n\n---\n\n## 前提条件\n\n`code2prompt`がインストールされていることを確認してください。まだインストールしていない場合は、[インストールガイド](/docs/how_to/install)を参照してください。\n\n---\n\n## インクルードおよびエクスクルードパターンの理解\n\nGlobパターンは、ファイルやディレクトリのフィルタリングルールを指定できます。\n\n- **インクルードパターン** (`--include`): 含めたいファイルやディレクトリを指定します。\n- **エクスクルードパターン** (`--exclude`): 除外したいファイルやディレクトリを指定します。\n- **優先度** (`--include-priority`): インクルードパターンとエクスクルードパターンの競合を解決します。\n\n---\n\n## 環境の設定\n\nGlobパターンを使って練習するために、サンプルフォルダー構造とファイルを作成しましょう。\n\n### テスト構造を生成するBashスクリプト\n\n以下のスクリプトを実行して、一時的なディレクトリ構造を設定します。\n\n```bash\n#!/bin/bash\n\n# ベースディレクトリを作成\nmkdir -p test_dir/{lowercase,uppercase,.secret}\n\n# 構造内のファイルを作成\necho \"content foo.py\" > \"test_dir/lowercase/foo.py\"\necho \"content bar.py\" > \"test_dir/lowercase/bar.py\"\necho \"content baz.py\" > \"test_dir/lowercase/baz.py\"\necho \"content qux.txt\" > \"test_dir/lowercase/qux.txt\"\necho \"content corge.txt\" > \"test_dir/lowercase/corge.txt\"\necho \"content grault.txt\" > \"test_dir/lowercase/grault.txt\"\n\necho \"CONTENT FOO.py\" > \"test_dir/uppercase/FOO.PY\"\necho \"CONTENT BAR.py\" > \"test_dir/uppercase/BAR.PY\"\necho \"CONTENT BAZ.py\" > \"test_dir/uppercase/BAZ.PY\"\necho \"CONTENT QUX.txt\" > \"test_dir/uppercase/QUX.TXT\"\necho \"CONTENT CORGE.txt\" > \"test_dir/uppercase/CORGE.TXT\"\necho \"CONTENT GRAULT.txt\" > \"test_dir/uppercase/GRAULT.TXT\"\n\necho \"top secret\" > \"test_dir/.secret/secret.txt\"\n```\n\n後で構造をクリーンアップするには、次のコマンドを実行します。\n\n```bash\nrm -rf test_dir\n```\n\n以下のディレクトリ構造が作成されます。\n\nimport { FileTree } from \"@astrojs/starlight/components\";\n\n<FileTree>\n  - test_dir - lowercase - foo.py - bar.py - baz.py - qux.txt - corge.txt -\n  grault.txt - uppercase - FOO.PY - BAR.PY - BAZ.PY - QUX.txt - CORGE.txt -\n  GRAULT.txt - .secret - secret.txt\n</FileTree>\n\n---\n\n## 例: インクルードおよびエクスクルードパターンを使用したファイルのフィルタリング\n\n### ケース1: インクルードなし、エクスクルードなし\n\nコマンド:\n\n```bash\ncode2prompt test_dir\n```\n\n#### 結果\n\nすべてのファイルが含まれます:\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n- `.secret/secret.txt`\n\n---\n\n### ケース2: 特定のファイルタイプをエクスクルードする\n\n`.txt`ファイルをエクスクルードします:\n\n```bash\ncode2prompt test_dir --exclude=\"*.txt\"\n```\n\n#### 結果\n\nエクスクルード:\n\n- すべての`.txt`ファイル\n\nインクルード:\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n\n---\n\n### ケース3: 特定のファイルタイプをインクルードする\n\nPythonファイルのみを含めます:\n\n```bash\ncode2prompt test_dir --include=\"*.py\"\n```\n\n#### 結果\n\nインクルード:\n\n- すべての`.py`ファイル\n\nエクスクルード:\n\n- `.secret/secret.txt`\n\n---\n\n### ケース4: インクルードとエクスクルードを優先度で制御する\n\n`.py`ファイルを含めますが、`uppercase`フォルダ内のファイルをエクスクルードします:\n\n```bash\ncode2prompt test_dir --include=\"*.py\" --exclude=\"**/uppercase/*\" --include-priority=true\n```\n\n#### 結果\n\nインクルード:\n\n- `lowercase`内のすべての`.py`拡張子のファイル\n\nエクスクルード:\n\n- すべての`uppercase`ファイル\n- `.secret/secret.txt`\n\n---\n\n## まとめ\n\n`code2prompt`のglobパターン ツールを使用すると、以下のようにファイルやディレクトリを効果的にフィルタリングできます。\n\n- `--include`でインクルードするファイルを指定\n- `--exclude`でエクスクルードするファイルを指定\n- `--include-priority`でパターン間の競合を解決\n\n練習として、サンプルディレクトリを設定し、コマンドを実行して、ツールがファイルを動的にフィルタリングする様子を確認してください。\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/tutorials/learn_templates.mdx",
    "content": "---\ntitle: Code2PromptでHandlebarテンプレートを学ぶ\ndescription: Code2PromptのためのカスタムHandlebarsテンプレートの作成と使用方法を理解する。\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"チュートリアルの概要\">\n  このチュートリアルでは、Code2Prompt\n  CLIのためのカスタムHandlebarsテンプレートの作成と使用方法を説明します。\n</Card>\n\n---\n\n## 前提条件\n\n`code2prompt`がインストールされていることを確認してください。まだインストールしていない場合は、[インストールガイド](/docs/how_to/install)を参照してください。\n\n---\n\n## Handlebarsテンプレートとは\n\n[Handlebars](https://handlebarsjs.com/)は、プレースホルダを使用した動的テンプレートを作成できる人気のテンプレートエンジンです。\n`code2prompt`では、Handlebarsテンプレートは、コードベースの構造とユーザー定義の変数に基づいて生成されたプロンプトをフォーマットするために使用されます。\n\n## Handlebarsテンプレートの使用方法\n\nテンプレートファイルへのパスを`-t`または`--template`フラグで指定することで、これらのテンプレートを使用できます。例：\n\n```sh\ncode2prompt path/to/codebase -t templates/document-the-code.hbs\n```\n\n## テンプレート構文\n\nHandlebarsテンプレートは、プレースホルダと式のためのシンプルな構文を使用します。変数は、生成されたプロンプトに含めるために、`{{variable_name}}`の二重中括弧内に配置します。\n`code2prompt`は、テンプレートで使用できるデフォルトの変数のセットを提供します。\n\n- `absolute_code_path`: コードベースへの絶対パス。\n- `source_tree`: コードベースのソースツリーで、すべてのファイルとディレクトリが含まれます。\n- `files`: コードベース内のファイルのリストで、パスと内容が含まれます。\n- `git_diff`: コードベースのgit diff。適用可能な場合。\n- `code`: 処理中のファイルのコード内容。\n- `path`: 処理中のファイルのパス。\n\nHandlebarsヘルパーを使用して、テンプレート内で条件付きロジック、ループ、その他の操作を実行することもできます。例：\n\n```handlebars\n{{#if files}}\n  {{#each files}}\n    ファイル:\n    {{this.path}}\n    内容:\n    {{this.content}}\n  {{/each}}\n{{else}}\n  ファイルが見つかりません。\n{{/if}}\n```\n\n---\n\n## 既存のテンプレート\n\n`code2prompt`には、一般的なユースケースのための組み込みテンプレートのセットが付属しています。これらは[`templates`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates)ディレクトリで見つけることができます。\n\n### [`document-the-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/document-the-code.hbs)\n\nこのテンプレートを使用して、コードのドキュメント化のためのプロンプトを生成します。コードベース内のすべてのパブリック関数、メソッド、クラス、およびモジュールにドキュメントコメントを追加します。\n\n### [`find-security-vulnerabilities.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/find-security-vulnerabilities.hbs)\n\nこのテンプレートを使用して、コードベース内の潜在的なセキュリティ脆弱性を見つけるためのプロンプトを生成します。一般的なセキュリティ問題を探し、それらを修正または軽減する方法に関する推奨事項を提供します。\n\n### [`clean-up-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/clean-up-code.hbs)\n\nこのテンプレートを使用して、コードの品質を向上および改善するためのプロンプトを生成します。読みやすさ、ベストプラクティスの遵守、効率性、エラー処理などの改善機会を探します。\n\n### [`fix-bugs.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/fix-bugs.hbs)\n\nこのテンプレートを使用して、コードベース内のバグを修正するためのプロンプトを生成します。問題の診断、修正の提案、およびコードの更新を行います。\n\n### [`write-github-pull-request.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-pull-request.hbs)\n\nこのテンプレートを使用して、2つのブランチのgit diffとgitログを比較して、GitHubプルリクエストの説明をマークダウンで作成します。\n\n### [`write-github-readme.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-readme.hbs)\n\nこのテンプレートを使用して、プロジェクト用の高品質のREADMEファイルを生成します。コードベースを分析してその目的と機能を理解し、マークダウン形式でREADMEコンテンツを生成します。\n\n### [`write-git-commit.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-git-commit.hbs)\n\nこのテンプレートを使用して、gitディレクトリ内のステージングされたファイルからgitコミットを生成します。コードベースを分析してその目的と機能を理解し、マークダウン形式でgitコミットメッセージコンテンツを生成します。\n\n### [`improve-performance.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/improve-performance.hbs)\n\nこのテンプレートを使用して、コードベースのパフォーマンスを向上させるためのプロンプトを生成します。最適化の機会を探し、特定の提案を提供し、コードを更新します。\n\n## ユーザー定義変数\n\n`code2prompt`は、Handlebarsテンプレートでのユーザー定義変数の使用をサポートしています。テンプレート内のデフォルトコンテキスト（`absolute_code_path`、`source_tree`、`files`）に含まれない変数は、ユーザー定義変数として扱われます。\n\nプロンプト生成中、`code2prompt`はユーザー定義変数の値を入力するようユーザーに求めます。これにより、ユーザーの入力に基づいて生成されたプロンプトをさらにカスタマイズできます。\n\nたとえば、テンプレートに`{{challenge_name}}`と`{{challenge_description}}`が含まれている場合、`code2prompt`を実行すると、これらの変数の値を入力するよう求められます。\n\nこの機能により、ユーザーが提供した情報に基づいてさまざまなシナリオに適応できる、再利用可能なテンプレートを作成できます。\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/vision.mdx",
    "content": "---\ntitle: Code2Promptのビジョン\ndescription: Code2Promptの背後にあるビジョンと、コードとのLLMインタラクションをどのように強化するかをご覧ください。\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Aside } from \"@astrojs/starlight/components\";\n\n<Card title=\"目的 🎯\">\n  `code2prompt`は、開発者とAIエージェントがコードベースと効果的に相互作用できるように支援するために作成されました。\n</Card>\n\n## 問題点 🚩\n\n大規模言語モデル（LLM）は、コードとのインタラクション方法に革命をもたらしました。しかし、コード生成に関しては、まだ大きな課題に直面しています。\n\n- **計画と推論**: LLMには計画と推論能力が欠けており、コード生成、リファクタリング、デバッグなどのタスクに不可欠です。彼らは全体像を把握するのに苦労することが多く、近視眼的です。\n- **コンテキストサイズ**: LLMには限られたコンテキストウィンドウがあり、大きなコードベースを分析して理解する能力を制限します。\n- **幻覚**: LLMは、正しいように見えるが、実際には正しくない、または意味をなさないコードを生成することがあります。この現象は、モデルがコードベースのコンテキストまたは理解を十分に持っていない場合に発生する幻覚と呼ばれます。\n\nこの問題を解決するために、`code2prompt`があります。\n\n## 解決策 ✅\n\n私たちは、計画と推論は、足場技術を使用することで、人間またはAIエージェントによって達成できると信じています。これらのエージェントは、タスクに適したフィルター処理された構造化されたフォーマットされた**高品質のコンテキスト**を収集する必要があります。\n\n経験則は次のとおりです。\n\n<Aside type=\"tip\">\n  > 可能な限り最小限のコンテキストを提供しますが、必要なだけ提供します。\n</Aside>\n\nこれは、特に大きなコードベースの場合、実際に困難です。しかし、`code2prompt`は、開発者とAIエージェントがコードベースをより効果的に吸収できるように支援するシンプルなツールです。\n\nこれにより、コードベースのトラバース、ファイルのフィルタリング、LLMが理解できる構造化されたプロンプトへのフォーマットを自動化します。これにより、計画、推論、幻覚の課題を軽減できます。\n\n次のセクションでは、`code2prompt`がこれらの課題にどのように取り組むように設計されているかを理解できます。\n\n## アーキテクチャ ⛩️\n\n<img\n  src=\"/assets/images/architecture.svg\"\n  alt=\"code2promptのアーキテクチャ\"\n  style=\"width: 75%;\"\n/>\n\n`code2prompt`はモジュール式に設計されており、さまざまなワークフローへの簡単な統合を可能にします。コアライブラリ、コマンドラインインターフェイス（CLI）、ソフトウェア開発キット（SDK）、またはモデルコンテキストプロトコル（MCP）サーバーとして使用できます。\n\n### コア\n\n`code2prompt`は、コード分析、生成、その他のタスクのためのLLMプロンプトを作成するプロセスを合理化するコード吸収ツールです。ディレクトリをトラバースし、ツリー構造を構築し、各ファイルに関する情報を収集することで動作します。コアライブラリは、他のアプリケーションに簡単に統合できます。\n\n### CLI\n\n`code2prompt`コマンドラインインターフェイス（CLI）は、コードベースからプロンプトを直接生成するために人間が使用できるように設計されています。生成されたプロンプトは自動的にクリップボードにコピーされ、出力ファイルに保存することもできます。さらに、Handlebarsテンプレートを使用してプロンプト生成をカスタマイズできます。ドキュメントに提供されているプロンプトを確認してください。\n\n### SDK\n\n`code2prompt`ソフトウェア開発キット（SDK）は、コアライブラリへのPythonバインディングを提供します。これは、コードベースとシームレスに相互作用したいAIエージェントまたは自動化スクリプトに最適です。SDKはPypiにホストされており、pip経由でインストールできます。\n\n### MCP\n\n`code2prompt`は、モデルコンテキストプロトコル（MCP）サーバーとしても利用でき、ローカルサービスとして実行できます。これにより、LLMにコードベースの構造化されたコンテキストを自動的に収集するツールを提供することで、LLMを強化できます。\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ja/docs/welcome.mdx",
    "content": "---\ntitle: Code2Prompt Documentation\ndescription: 公式Code2promptドキュメント\ntemplate: splash\nhero:\n  tagline: コードをAI最適化されたプロンプトに数秒で変換する\n  image:\n    file: ../../../../assets/logo_dark_v0.0.1.svg\n  actions:\n    - text: 始める 🚀\n      link: /docs/tutorials/getting_started\n    - text: インストール 📥\n      link: /docs/how_to/install\n---\n\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\nimport { LinkCard } from \"@astrojs/starlight/components\";\n\n## クイックスタート\n\n<LinkCard title=\"始める 🚀\" href=\"../../docs/tutorials/getting_started\" />\n<LinkCard title=\"インストール 📥\" href=\"../../docs/how_to/install\" />\n<LinkCard\n  title=\"フィルタリングについて学ぶ 🔍\"\n  href=\"../../docs/tutorials/learn_filters\"\n/>\n<LinkCard\n  title=\"テンプレートについて学ぶ 📝\"\n  href=\"../../docs/tutorials/learn_templates\"\n/>\n<LinkCard title=\"ビジョン 🔮\" href=\"../../docs/vision\" />\n\n`code2prompt`は、コード分析、生成、その他のタスクのためのプロンプトを生成するように設計された強力なコードインジェストツールです。ディレクトリをトラバースし、ツリー構造を構築し、各ファイルに関する情報を収集することで動作します。\n\nLLMを使用したコードの分析、ドキュメント化、リファクタリングを容易にするために、コードの結合とフォーマットを簡素化します。\n\n以下の方法で`code2prompt`を使用できます:\n\n<CardGrid>\n  <Card title=\"コア\" icon=\"seti:rust\">\n    コードインジェストのためのコアライブラリ\n  </Card>\n  <Card title=\"CLI\" icon=\"seti:powershell\">\n    人間向けに特別に設計されたコマンドラインインターフェース\n  </Card>\n  <Card title=\"SDK\" icon=\"seti:python\">\n    AIエージェントと自動化スクリプトのためのソフトウェア開発キット\n  </Card>\n  <Card title=\"MCP\" icon=\"seti:folder\">\n    強力なLLMのためのモデルコンテキストプロトコルサーバー\n  </Card>\n</CardGrid>\n\n## 主な機能\n\n- **LLMプロンプトの生成**: 構造化されたLLMプロンプトにコードベース全体を迅速に変換します。\n- **グロブパターンによるフィルタリング**: グロブパターンを使用して特定のファイルやディレクトリを含めたり除外したりします。\n- **カスタマイズ可能なテンプレート**: Handlebarsテンプレートを使用してプロンプト生成を調整します。\n- **トークンカウント**: トークン使用量を分析し、コンテキストウィンドウが異なるLLMを最適化します。\n- **Git統合**: コードレビューのためにGitの差分とコミットメッセージをプロンプトに含めます。\n- **.gitignoreを尊重**: `.gitignore`にリストされたファイルを自動的に無視してプロンプト生成を合理化します。\n\n## なぜ`code2prompt`?\n\n1. **時間を節約**:\n\n   - コードベースをトラバースし、LLM用のファイルをフォーマットするプロセスを自動化します。\n   - コードのコピーと貼り付けを繰り返す必要がなくなります。\n\n2. **生産性を向上**:\n\n   - コード分析のための構造化された一貫したフォーマットを提供します。\n   - バグの特定、コードのリファクタリング、ドキュメントの作成を迅速に行うことができます。\n\n3. **大規模なコードベースの処理**:\n\n   - 大規模なコードベースでシームレスに動作するように設計されており、LLMのコンテキスト制限を尊重します。\n\n4. **カスタマイズ可能なワークフロー**:\n\n   - ファイルのフィルタリング、テンプレートの使用、ターゲットプロンプトの生成のための柔軟なオプション。\n\n## 使用例\n\n- **コードドキュメント**:\n  公開関数、メソッド、クラスのドキュメントを自動生成します。\n\n- **バグ検出**:\n  コードベースをLLMで分析して潜在的なバグや脆弱性を検出します。\n\n- **リファクタリング**:\n  コード品質の向上のためのプロンプトを生成してコードを簡素化および最適化します。\n\n- **学習と探索**:\n  サマリや詳細な内訳を生成して新しいコードベースを理解します。\n\n- **GitコミットとPRの説明**:\n  Gitの差分から意味のあるコミットメッセージとプルリクエストの説明を生成します。\n\n> このページは便宜上、自動的に翻訳されています。元のコンテンツについては英語版を参照してください。\n"
  },
  {
    "path": "website/src/content/docs/ru/blog/2025.04.11_why_I_wrote_code2prompt.mdx",
    "content": "---\ntitle: \"Почему я разработал Code2Prompt\"\ndate: 2025-04-11\nlastUpdated: 2025-04-11\ntags:\n  - open-source\n  - code2prompt\n  - AI\n  - Agent\nexcerpt: \"История создания code2prompt: мой путь к открытому исходному коду для решения проблем контекста в рабочих процессах LLM\"\nauthors:\n  - ODAncona\ncover:\n  alt: \"Иллюстрация code2prompt, оптимизирующая контекст кода для агентов ИИ.\"\n  image: \"/src/assets/logo_dark_v0.0.2.svg\"\nfeatured: false\ndraft: false\n---\n\n## Введение\n\nМеня всегда увлекало, как большие языковые модели (LLM) преобразуют рабочие процессы кодирования — генерируют тесты, комментарии или даже реализуют целые функции за считанные минуты. Но когда я глубже погрузился в эти модели, выявились несколько критических проблем:\n\n| Трудности планирования | Высокие затраты на токены | Галлюцинации |\n| ---------------------- | ------------------------- | ------------ |\n| 🧠 ➡️ 🤯               | 🔥 ➡️ 💸                  | 💬 ➡️ 🌀     |\n\nИменно поэтому я начал работать над `code2prompt`, инструментом на основе Rust, который помогает подавать в LLM именно тот контекст, который необходим.\n\nВ этом посте я поделюсь своей историей и объясню, почему я убежден, что `code2prompt` актуален сегодня и прекрасно интегрируется, и почему он стал моим основным решением для более быстрых и эффективных рабочих процессов кодирования с ИИ.\n\n## Первые шаги с LLM 👣\n\nЯ начал экспериментировать с LLM в `OpenAI Playground` с `text-davinci-003`, когда он приобрел популярность в ноябре 2023 года. Языковые модели открыли новую революцию. Это было похоже на наличие блестящего нового помощника, который мог генерировать单元-тесты и комментарии почти по команде. Я наслаждался тестированием моделей на их пределах — проверкой всего, от небольших разговоров и этических дилемм до jailbreaks и сложных задач кодирования. Однако, когда я взялся за более крупные проекты, я быстро осознал, что модели имеют явные ограничения. Сначала я мог поместить в окно контекста только несколько сотен строк кода, и даже тогда модели часто с трудом понимали назначение или структуру кода. Именно поэтому я быстро осознал, что важность контекста имеет первостепенное значение. Чем более краткими были мои инструкции и лучше контекст, тем лучше результаты.\n\n![OpenAI Playground](/assets/blog/post1/playground.png)\n\n## Эволюция моделей 🏗️\n\nМодели могли производить впечатляющие результаты, но часто испытывали трудности с более крупными кодовыми базами или сложными задачами. Я обнаружил, что трачу больше времени на создание подсказок, чем на фактическое кодирование. В то же время модели продолжали улучшаться с выпуском новых версий. Они увеличивали способности рассуждения и размер контекста, предлагая новые перспективы и возможности. Я мог поместить в окно контекста почти две тысячи строк кода, и результаты улучшились. Я мог написать целые функции за несколько итераций, и меня поразило, как быстро я мог получить результаты. Я был убежден, что LLM — это будущее кодирования, и я хотел быть частью этой революции. Я твердо верю, что ИИ не заменит нас пока. Но будет помогать нам в качестве помощников, где люди по-прежнему являются экспертами и контролируют ситуацию.\n\n## Мои первые проекты с LLM 🚀\n\nЯ начал писать модуль поиска пути `ROS` для роботизированного соревнования, генерировать функции для кроссплатформенного приложения `Flutter` с чистой архитектурой и создал небольшое веб-приложение для отслеживания расходов в `Next.js`. Тот факт, что я создал это небольшое приложение за один вечер, в фреймворке, с которым я никогда не работал раньше, был поворотным моментом для меня; LLM были не просто инструментами, но и умножителями. Я разработал `bboxconverter`, пакет для преобразования ограничивающих рамок, и список продолжается. LLM могут помочь вам быстро изучить новые технологии и фреймворки; это потрясающе.\n\n## Новый парадигма: Software 3.0 💡\n\nЯ глубже погрузился в LLM и начал создавать агентов и скелеты вокруг них. Я повторил известную статью [RestGPT](https://restgpt.github.io/). Идея отличная: дать LLM возможность вызывать некоторые REST API с помощью спецификации OpenAPI, такие как `Spotify` или `TMDB`. Эти возможности вводят новый парадигму программирования программного обеспечения, который я называю **Software 3.0**.\n\n| Software 1.0     | Software 2.0     | Software 3.0 |\n| ---------------- | ---------------- | ------------ |\n| На основе правил | На основе данных | Агентный     |\n\nТа же идея привела к протоколу [MCP](https://modelcontextprotocol.io/introduction), который позволяет LLM вызывать инструменты и ресурсы напрямую бесшовным образом, потому что по дизайну инструмент нуждается в описании, чтобы быть вызванным LLM, в отличие от REST API, который не обязательно требует спецификации OpenAPI.\n\n## Ограничения LLM 🧩\n\n### Галлюцинации 🌀\n\nПри повторении известной статьи `RESTGPT` я заметил некоторые серьезные ограничения LLM. Авторы статьи столкнулись с теми же проблемами, что и я: LLM **галлюцинировали**. Они генерируют код, который не реализован, изобретая аргументы и просто следуя инструкциям буквально без использования здравого смысла. Например, в исходном коде RestGPT авторы спросили в [подсказке вызывающего](https://github.com/Yifan-Song793/RestGPT/blob/main/model/caller.py).\n\n> \"не быть слишком умным и не придумывать шаги, которых нет в плане.\"\n\nЯ нашел это заявление забавным и очень интересным, потому что это был первый раз, когда я встретил кого-то, кто инструктирует LLM не галлюцинировать.\n\n### Ограниченный размер контекста 📏\n\nДругим ограничением был размер контекста; LLM хорошо справляются с поиском иголки в стоге сена, но с трудом понимают его. Когда вы даете слишком много контекста языковым моделям, они склонны теряться в деталях и теряют из виду общую картину, что раздражает и требует постоянного управления. Способ, которым я люблю думать об этом, похож на [проклятие размерности](https://towardsdatascience.com/curse-of-dimensionality-a-curse-to-machine-learning-c122ee33bfeb/). Замените слово \"размерность\" или \"функция\" на \"контекст\", и вы получите идею.\n\n![Проклятие размерности](/assets/blog/post1/curse_of_dimensionality.png)\n\nЧем больше контекста вы даете LLM, тем труднее найти правильный ответ. Я придумал nice предложение, чтобы суммировать эту идею:\n\n> Предоставьте как можно меньше контекста, но как можно больше, чем необходимо\n\nЭто heavily вдохновлено известным [цитатой Alain Berset](https://www.lematin.ch/story/alain-berset-la-formule-qui-defie-le-temps-166189802108), швейцарского политика 🇨🇭, который сказал во время карантина COVID-19:\n\n> \"Мы хотим действовать как можно быстрее, но и как можно медленнее, когда это необходимо\"\n\nЭто представляет идею компромисса и применяется к размеру контекста LLM!\n\n## Поиск лучшего способа: code2prompt 🔨\n\nСледовательно, мне нужен был способ быстро загружать, фильтровать и организовывать контекст моего кода, предоставляя как можно меньше контекста с лучшим качеством. Я попробовал вручную копировать файлы или фрагменты в подсказки, но это стало неудобным и подверженным ошибкам. Я знал, что автоматизация tedious процесса создания контекста для запросов лучших подсказок будет полезна. Затем, однажды, я ввел \"code2prompt\" в Google, надеясь найти инструмент, который напрямую подключает мой код к подсказкам.\n\nИ, voilà, я обнаружил проект на основе Rust [Mufeed](https://www.reddit.com/r/rust/comments/1bghroh/i_made_code2prompt_a_cli_tool_to_convert_your/) под названием _code2prompt_, который имеет около 200 звезд на GitHub. Это было еще просто на тот момент: простой инструмент CLI с базовой ограниченной емкостью фильтрации и шаблонами. Я увидел огромный потенциал и сразу же присоединился к нему, реализовав совпадение шаблонов glob, среди других функций, и вскоре стал основным участником.\n\n## Видение и интеграции 🔮\n\nСегодня существует несколько способов предоставить контекст LLM. Генерация из более крупного контекста, использование генерации на основе извлечения (RAG), [сжатие кода](https://www.all-hands.dev/blog/openhands-context-condensensation-for-more-efficient-ai-agents), или даже использование комбинации этих методов. Создание контекста — это горячая тема, которая будет быстро развиваться в ближайшие месяцы. Однако, мой подход — **KISS**: Keep It Simple, Stupid. Лучший способ предоставить контекст LLM — использовать самый простой и эффективный способ. Вы создаете именно тот контекст, который вам нужен; это детерминировано, в отличие от RAG.\n\nИменно поэтому я решил продвинуть `code2prompt` дальше как простой инструмент, который можно использовать в любом рабочем процессе. Я хотел сделать его легким в использовании, легким в интеграции и легким в расширении. Именно поэтому я добавил новые способы взаимодействия с инструментом.\n\n- **Core**: Ядро `code2prompt` — это библиотека Rust, которая предоставляет базовую функциональность для создания контекста из вашей кодовой базы. Она включает в себя простой API для загрузки, фильтрации и организации контекста вашего кода.\n- **CLI:** Интерфейс командной строки — это самый простой способ использования `code2prompt`. Вы можете создать контекст из вашей кодовой базы и напрямую подключить его к вашим подсказкам.\n- **Python API:** Python API — это простой wrapper вокруг CLI, который позволяет вам использовать `code2prompt` в ваших Python-скриптах и агентах. Вы можете создать контекст из вашей кодовой базы и напрямую подключить его к вашим подсказкам.\n- **MCP**: Сервер `code2prompt` MCP позволяет LLM использовать `code2prompt` как инструмент, тем самым делая их способными создавать контекст.\n\nВидение описано дальше на [странице видения](/docs/vision) в документации.\n\n## Интеграция с агентами 👤\n\nЯ считаю, что будущие агенты будут нуждаться в способе потреблять контекст, и `code2prompt` — это простой и эффективный способ сделать это для текстовых репозиториев, таких как кодовая база, документация или заметки. Типичное место для использования `code2prompt` будет в кодовой базе с осмысленными соглашениями об именовании. Например, в чистой архитектуре есть четкое разделение проблем и слоев. Соответствующий контекст обычно resides в разных файлах и папках, но имеет одно и то же имя. Это идеальный случай использования `code2prompt`, где вы можете использовать шаблон glob для захвата соответствующих файлов.\n\n**На основе шаблонов glob:** Точно выберите или исключите файлы с минимальными усилиями.\n\nКроме того, основная библиотека разработана как менеджер контекста с состоянием, позволяя вам добавлять или удалять файлы по мере развития вашего разговора с LLM. Это особенно полезно при предоставлении контекста для конкретной задачи или цели. Вы можете легко добавлять или удалять файлы из контекста без повторного запуска процесса.\n\n**Состояние контекста:** Добавляйте или удаляйте файлы по мере развития вашего разговора с LLM.\n\nЭти возможности делают `code2prompt` идеальным выбором для рабочих процессов на основе агентов. Сервер MCP позволяет бесшовную интеграцию с популярными фреймворками ИИ-агентов, такими как [Aider](https://github.com/paul-gauthier/aider), [Goose](https://block.github.io/goose/), или [Cline](https://github.com/jhillyerd/cline). Пусть они обрабатывают сложные цели, а `code2prompt` доставляет идеальный контекст кода.\n\n## Почему Code2prompt имеет значение ✊\n\nПо мере того, как LLM развиваются и окна контекста расширяются, может показаться, что просто форсирование всех репозиториев в подсказки достаточно. Однако **стоимость токенов** и **согласованность подсказок** остаются значительными препятствиями для небольших компаний и разработчиков. Сосредоточившись на коде, который имеет значение, `code2prompt` делает ваше использование LLM эффективным, экономичным и менее подверженным галлюцинациям.\n\n**Вкратце:**\n\n- **Уменьшите галлюцинации**, предоставляя правильное количество контекста\n- **Уменьшите стоимость токенов**, тщательно создавая необходимый контекст\n- **Улучшите производительность LLM**, предоставляя правильное количество контекста\n- Интегрируется со стеком агентов как поставщик контекста для текстовых репозиториев\n\n## Вы можете присоединиться! 🌐\n\nКаждый новый участник приветствуется! Присоединяйтесь, если вы заинтересованы в Rust, создании инновационных инструментов ИИ или просто хотите лучший рабочий процесс для ваших кодовых подсказок.\n\nСпасибо за чтение, и я надеюсь, что моя история вдохновила вас проверить code2prompt. Это было невероятное путешествие, и оно только начинается!\n\n**Olivier D'Ancona**\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/explanations/glob_pattern_filter.mdx",
    "content": "---\ntitle: Как работает фильтр по паттернам Glob\ndescription: Как Code2Prompt решает, какие файлы сохранить или отбросить, используя glob включений (-i) и исключений (-e).\n---\n\nCode2Prompt использует паттерны glob для включения или исключения файлов и директорий, работая аналогично инструментам типа tree или grep. Он позволяет передавать два независимых _списка_ glob паттернов:\n\n- **список включений** (`--include` или `-i`) - \"эти паттерны разрешают файлы\"\n- **список исключений** (`--exclude` или `-e`) - \"эти паттерны запрещают файлы\"\n\nCode2prompt должен решить для каждого файла в проекте, сохранить его или отбросить. Эта страница объясняет правила и решения дизайна, стоящие за ними.\n\n---\n\n## 1. Множества и Символы\n\nНа протяжении всего объяснения мы используем обычную нотацию множеств\n\n| Символ                            | Значение                                                                       |\n| --------------------------------- | ------------------------------------------------------------------------------ |\n| $A$                               | множество файлов, которые соответствуют **хотя бы одному** паттерну включения  |\n| $B$                               | множество файлов, которые соответствуют **хотя бы одному** паттерну исключения |\n| $\\Omega$                          | всё дерево проекта (_вселенная_)                                               |\n| $C = A \\cap B$                    | файлы, которые соответствуют обоим спискам (_пересечение_)                     |\n| $D = \\Omega \\setminus (A \\cup B)$ | файлы, которые не соответствуют ни одному списку                               |\n\n---\n\n## 2. Четыре Ситуации\n\n### Обзор четырех ситуаций\n\n| Список включений | Список исключений | Сохранённые файлы |\n| ---------------- | ----------------- | ----------------- |\n| A = ∅            | B = ∅             | Ω                 |\n| A = ∅            | B ≠ ∅             | ¬B                |\n| A ≠ ∅            | B = ∅             | A                 |\n| A ≠ ∅            | B ≠ ∅             | A \\ B             |\n\n1. **Нет списка включений, нет списка исключений**\n\n   Если паттерны не указаны, сохраняются все файлы (`Ω`).\n\n2. **Только список исключений**\n\n   В этом случае Code2Prompt действует как чёрный список, удаляя файлы, которые соответствуют исключённым паттернам (` Ω \\ B = ¬B`).\n\n3. **Только список включений**\n\n   Если указан только список включений, Code2Prompt действует как белый список, сохраняя только файлы, которые соответствуют включённым паттернам (`A`).\n\n4. **Списки включений _и_ исключений**\n\n   Если указаны оба списка, Code2Prompt сохраняет файлы, которые соответствуют паттернам включения, но удаляет те, которые соответствуют паттернам исключения (`A \\ B`).\n\n---\n\n## 3. Подробнее о пересечении\n\nПри наличии обоих списков (`A ≠ ∅`, `B ≠ ∅`) у вас есть четыре логические возможности\nдля пересечения `C` и остатка `D`.\n\n| Нужно `C`? | Нужно `D`? | Разумно?                                                                |\n| ---------- | ---------- | ----------------------------------------------------------------------- |\n| Нет        | Нет        | Поведение по умолчанию (`A \\ B`)                                        |\n| Да         | Нет        | То же поведение, что и случай 3 (`A`)                                   |\n| Нет        | Да         | удивительно (\"отбросить то, что я запросил `C`, сохранить то, что нет\") |\n| Да         | Да         | То же поведение, что и случай 1 (`Ω`)                                   |\n\nПо этой причине была удалена опция `--include-priority`. Потому что это был бы тот же результат, как если бы у вас был только список включений (случай 3).\n\n## 4. Таблица быстрого справочника\n\n| Хотите сохранить…                                    | Используйте        |\n| ---------------------------------------------------- | ------------------ |\n| всё                                                  | нет `-i`, нет `-e` |\n| всё _кроме_ некоторых паттернов                      | только `-e`        |\n| _только_ то, что соответствует паттернам             | только `-i`        |\n| что соответствует `-i`, минус что соответствует `-e` | `-i` **и** `-e`    |\n\n---\n\nЭтот дизайн сохраняет ментальную модель простой:\n\n- Список включений является белым списком, как только он существует.\n- Список исключений является чёрным списком, наложенным сверху.\n- Пересечение отбрасывается по умолчанию\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/explanations/glob_patterns.md",
    "content": "---\ntitle: Понимание шаблонов Glob\ndescription: Подробное объяснение шаблонов glob и их использования в Code2Prompt.\n---\n\nШаблоны glob - это простой, но мощный способ сопоставления имен файлов и путей с использованием символов-заменителей. Они обычно используются в интерфейсах командной строки и языках программирования для указания наборов имен файлов или директорий. Вот разбор наиболее часто используемых шаблонов glob:\n\n## Базовые шаблоны-заменители\n\n- `*`: Сопоставляется с любым количеством символов, включая нулевое количество символов.\n  - Пример: `*.txt` сопоставляется со всеми файлами, оканчивающимися на `.txt`.\n\n- `?`: Сопоставляется ровно с одним символом.\n  - Пример: `file?.txt` сопоставляется с `file1.txt`, `fileA.txt`, но не с `file10.txt`.\n\n- `[]`: Сопоставляется с любым из заключенных внутри скобок символов.\n  - Пример: `file[1-3].txt` сопоставляется с `file1.txt`, `file2.txt`, `file3.txt`.\n\n- `[!]` или `[^]`: Сопоставляется с любым символом, не заключенным внутри скобок.\n  - Пример: `file[!1-3].txt` сопоставляется с `file4.txt`, `fileA.txt`, но не с `file1.txt`.\n\n## Расширенные шаблоны\n\n- `**`: Сопоставляется с любым количеством директорий и поддиректорий рекурсивно.\n  - Пример: `**/*.txt` сопоставляется со всеми файлами `.txt` в текущей директории и всех поддиректориях.\n\n- `{}`: Сопоставляется с любым из шаблонов, перечисленных через запятую внутри скобок.\n  - Пример: `file{1,2,3}.txt` сопоставляется с `file1.txt`, `file2.txt`, `file3.txt`.\n\n## Примеры\n\n1. **Сопоставление всех текстовых файлов в директории:**\n\n   ```sh\n   *.txt\n   ```\n\n2. **Сопоставление всех файлов с одним цифровым символом перед расширением:**\n\n   ```sh\n   file?.txt\n   ```\n\n3. **Сопоставление файлов с расширениями `.jpg` или `.png`:**\n\n   ```sh\n   *.{jpg,png}\n   ```\n\n4. **Сопоставление всех файлов `.txt` в любой поддиректории:**\n\n   ```sh\n   **/*.txt\n   ```\n\n5. **Сопоставление файлов, начинающихся с `a` или `b` и оканчивающихся на `.txt`:**\n\n   ```sh\n   {a,b}*.txt\n   ```\n\n## Варианты использования\n\n- **Инструменты командной строки:** Шаблоны glob широко используются в инструментах командной строки, таких как `ls`, `cp`, `mv` и `rm`, для указания нескольких файлов или директорий.\n- **Языки программирования:** Языки, такие как Python, JavaScript и Ruby, поддерживают шаблоны glob для сопоставления файлов через библиотеки, такие как `glob` в Python.\n- **Системы сборки:** Инструменты, такие как Makefile, используют шаблоны glob для указания исходных файлов и зависимостей.\n\n## Заключение\n\nШаблоны glob обеспечивают гибкий и интуитивный способ сопоставления имен файлов и путей, что делает их незаменимыми для задач сценариев, автоматизации и управления файлами. Понимание и использование этих шаблонов может существенно повысить вашу производительность и эффективность при работе с файлами и директориями.\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/explanations/tokenizers.md",
    "content": "---\ntitle: Токенизация в Code2Prompt\ndescription: Узнайте о токенизации и том, как Code2Prompt обрабатывает текст для больших языковых моделей.\n---\n\nПри работе с языковыми моделями текст необходимо преобразовать в формат, который модель может понять — **токены**, являющиеся последовательностями чисел. Это преобразование выполняется **токенизатором**.\n\n---\n\n## Что такое токенизатор?\n\nТокенизатор преобразует сырой текст в токены, которые являются строительными блоками для обработки входных данных языковыми моделями. Эти токены могут представлять слова, под слова или даже отдельные символы, в зависимости от конструкции токенизатора.\n\nДля `code2prompt` мы используем **tiktoken** токенизатор. Он эффективен, надежен и оптимизирован для моделей OpenAI.\nВы можете изучить его функциональность в официальном репозитории\n\n👉 [репозиторий tiktoken на GitHub](https://github.com/openai/tiktoken)\n\nЕсли вы хотите узнать больше о токенизаторе в целом, ознакомьтесь с\n\n👉 [Руководством по токенизации Mistral](https://docs.mistral.ai/guides/tokenization/).\n\n## Реализация в `code2prompt`\n\nТокенизация реализована с помощью [`tiktoken-rs`](https://github.com/zurawiki/tiktoken-rs). `tiktoken` поддерживает следующие кодировки, используемые моделями OpenAI:\n\n| Аргумент CLI | Имя кодировки           | Модели OpenAI                                                             |\n|----| ----------------------- | ------------------------------------------------------------------------- |\n|`cl100k`| `cl100k_base`           | Модели ChatGPT, `text-embedding-ada-002`                                  |\n|`p50k`| `p50k_base`             | Модели кода, `text-davinci-002`, `text-davinci-003`                       |\n|`p50k_edit`| `p50k_edit`             | Используется для моделей редактирования, таких как `text-davinci-edit-001`, `code-davinci-edit-001` |\n|`r50k`| `r50k_base` (или `gpt2`) | Модели GPT-3, такие как `davinci`                                               |\n|`gpt2`| `o200k_base`            | Модели GPT-4o                                                             |\n\nДля более глубокого понимания различных токенизаторов см. [OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/66b988407d8d13cad5060a881dc8c892141f2d5c/examples/How_to_count_tokens_with_tiktoken.ipynb)\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/how_to/filter_files.md",
    "content": "---\ntitle: Фильтрация файлов в Code2Prompt\ndescription: Пошаговое руководство по включению или исключению файлов с помощью различных методов фильтрации.\n---\n\n\n## Использование\n\nСгенерировать запрос из директории codebase:\n\n```sh\ncode2prompt path/to/codebase\n```\n\nИспользовать пользовательский файл шаблона Handlebars:\n\n```sh\ncode2prompt path/to/codebase -t path/to/template.hbs\n```\n\nФильтровать файлы с помощью шаблонов glob:\n\n```sh\ncode2prompt path/to/codebase --include=\"*.rs,*.toml\"\n```\n\nИсключить файлы с помощью шаблонов glob:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.txt,*.md\"\n```\n\nИсключить файлы/папки из дерева исходных файлов на основе шаблонов исключения:\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.npy,*.wav\" --exclude-from-tree\n```\n\nОтобразить количество токенов сгенерированного запроса:\n\n```sh\ncode2prompt path/to/codebase --tokens\n```\n\nУказать токенизатор для подсчета токенов:\n\n```sh\ncode2prompt path/to/codebase --tokens --encoding=p50k\n```\n\nПоддерживаемые токенизаторы: `cl100k`, `p50k`, `p50k_edit`, `r50k_bas`.\n> [!ПРИМЕЧАНИЕ]  \n> См. [Токенизаторы](#tokenizers) для более подробной информации.\n\nСохранить сгенерированный запрос в выходной файл:\n\n```sh\ncode2prompt path/to/codebase --output=output.txt\n```\n\nВывести результат в формате JSON:\n\n```sh\ncode2prompt path/to/codebase --json\n```\n\nВыходные данные в формате JSON будут иметь следующую структуру:\n\n```json\n{\n  \"prompt\": \"<Сгенерированный запрос>\", \n  \"directory_name\": \"codebase\",\n  \"token_count\": 1234,\n  \"model_info\": \"Модели ChatGPT, text-embedding-ada-002\",\n  \"files\": []\n}\n```\n\nСгенерировать сообщение коммита Git (для staged файлов):\n\n```sh\ncode2prompt path/to/codebase --diff -t templates/write-git-commit.hbs\n```\n\nСгенерировать запрос на Pull Request с сравнением веток (для staged файлов):\n\n```sh\ncode2prompt path/to/codebase --git-diff-branch 'main, development' --git-log-branch 'main, development' -t templates/write-github-pull-request.hbs\n```\n\nДобавить номера строк к блокам исходного кода:\n\n```sh\ncode2prompt path/to/codebase --line-number\n```\n\nОтключить оборачивание кода внутри блоков markdown:\n\n```sh\ncode2prompt path/to/codebase --no-codeblock\n```\n\n- Переписать код на другой язык.\n- Найти ошибки/уязвимости безопасности.\n- Документировать код.\n- Реализовать новые функции.\n\n> Изначально я написал это для личного использования, чтобы использовать окно контекста Claude 3.0 размером 200K, и оно оказалось довольно полезным, поэтому я решил сделать его открытым!\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/how_to/install.mdx",
    "content": "---\ntitle: Установка Code2Prompt\ndescription: Полное руководство по установке Code2Prompt на разных операционных системах.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Steps } from \"@astrojs/starlight/components\";\n\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\n\n<Card title=\"Обзор руководства\">\n  Добро пожаловать в руководство по установке `Code2Prompt`. Этот документ\n  содержит пошаговые инструкции по установке на различных платформах, включая\n  Windows, macOS и Linux.\n</Card>\n\n**Краткий обзор**\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n## Предварительные требования\n\nУбедитесь, что [Rust](https://www.rust-lang.org/tools/install) и cargo установлены на вашей системе.\n\n```sh\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\nЭто официальный способ установки последней стабильной версии Rust и Cargo. Обязательно обновите переменную `PATH` после установки Rust. Перезапустите терминал или выполните предложенные установщиком инструкции.\n\n```sh\nsource $HOME/.cargo/env\n```\n\nВы можете проверить, что всё установлено правильно, выполнив:\n\n```sh\ncargo --version\ngit --version\n```\n\n## Интерфейс командной строки (CLI) 👨‍💻\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n#### 🧪 Установка последней (неопубликованной) версии с GitHub\n\nЕсли вы хотите получить последние функции или исправления до их выпуска на crates.io:\n\n```sh\ncargo install --git https://github.com/mufeedvh/code2prompt\n```\n\n### Сборка из исходного кода\n\nИдеально для разработчиков, которые хотят собрать из исходного кода или внести свой вклад в проект.\n\n<Steps>\n\n1.  🛠️ Установка предварительных требований :\n\n    - [Rust](https://www.rust-lang.org/tools/install) и Cargo\n    - [Git](https://git-scm.com/downloads)\n\n2.  📥 Клонирование репозитория :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt\n    ```\n\n3.  📦 Установка бинарного файла :\n\n    Чтобы собрать и установить из исходного кода:\n\n    ```sh\n    cargo install --path crates/code2prompt\n    ```\n\n    Чтобы собрать бинарный файл без установки:\n\n    ```sh\n    cargo build --release\n    ```\n\n    Бинарный файл будет доступен в каталоге `target/release`.\n\n4.  🚀 Запуск :\n\n    ```sh\n    code2prompt --help\n    ```\n\n</Steps>\n\n### Бинарные релизы\n\nЛучше всего для пользователей, которые хотят использовать последнюю версию без сборки из исходного кода.\n\nЗагрузите последний бинарный файл для вашей ОС из [Релизов](https://github.com/mufeedvh/code2prompt/releases).\n\n⚠️ Бинарные релизы могут отставать от последней версии на GitHub. Для получения новейших функций рассмотрите сборку из исходного кода.\n\n### AUR\n\nСпециально для пользователей Arch Linux, `code2prompt` доступен в AUR.\n\n`code2prompt` доступен в [`AUR`](https://aur.archlinux.org/packages?O=0&K=code2prompt). Установите его с помощью любого AUR-клиента.\n\n```sh\nparu/yay -S code2prompt\n```\n\n### Nix\n\nЕсли вы используете Nix, вы можете установить его с помощью nix-env или nix profile.\n\n```sh\n# без flakes:\nnix-env -iA nixpkgs.code2prompt\n# с flakes:\nnix profile install nixpkgs#code2prompt\n```\n\n## Программный 개발 Kit (SDK) 🐍\n\n### Pypi\n\nВы можете загрузить привязки Python из Pypi\n\n```sh\npip install code2prompt_rs\n```\n\n### Сборка из исходного кода\n\n<Steps>\n\n1.  🛠️ Установка предварительных требований :\n\n    - [Rust](https://www.rust-lang.org/tools/install) и Cargo\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 Клонирование репозитория :\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt/crates/code2prompt-python\n    ```\n\n3.  📦 Установка зависимостей :\n\n    Команда `rye` создаст виртуальную среду и установит все зависимости.\n\n    ```sh\n    rye sync\n    ```\n\n4.  ⚙️ Сборка пакета :\n\n    Вы будете разрабатывать пакет в виртуальной среде, расположенной в папке `.venv` в корне проекта.\n\n    ```sh\n    rye run maturin develop -r\n    ```\n\n</Steps>\n\n## Протокол контекста модели (MCP) 🤖\n\n### Автоматическая установка\n\nСервер `code2prompt` MCP скоро будет доступен в реестрах MCP.\n\n### Ручная установка\n\nСервер `code2prompt` MCP всё ещё является прототипом и будет интегрирован в основной репозиторий вскоре.\n\nЧтобы запустить сервер MCP локально для использования с `Cline`, `Goose` или `Aider`:\n\n<Steps>\n\n1.  🛠️ Установка предварительных требований :\n\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 Клонирование репозитория :\n\n    ```sh\n    git clone https://github.com/odancona/code2prompt-mcp.git\n    cd code2prompt-mcp\n    ```\n\n3.  📦 Установка зависимостей :\n\n    Команда `rye` создаст виртуальную среду и установит все зависимости в папке `.venv`.\n\n    ```sh\n    rye sync\n    ```\n\n4.  🚀 Запуск сервера :\n\n    Сервер MCP теперь установлен. Вы можете запустить его с помощью:\n\n    ```sh\n    . .venv/bin/activate\n    python -m src/code2prompt_mcp/main.py\n    ```\n\n5.  🔌 Интеграция с агентами :\n\n            Например, вы можете интегрировать его с `Cline`, используя аналогичную конфигурацию:\n\n            ```json\n            {\n              \"mcpServers\": {\n                \"code2prompt\": {\n                  \"command\": \"bash\",\n                  \"args\": [\n                    \"-c\",\n                    \"cd /home/olivier/projet/code2prompt-mcp && rye run python /home/olivier/projet/code2prompt-mcp/src/code2prompt_mcp/main.py\"\n                  ],\n                  \"env\": {}\n                }\n              }\n            }\n            ```\n\n</Steps>\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/how_to/ssh.md",
    "content": "---\ntitle: Использование Code2prompt CLI с SSH\ndescription: Руководство по использованию Code2Prompt CLI с SSH для удаленного анализа базы кода.\n---\n\n## Почему это не работает?\n\nКогда вы пытаетесь запустить `code2prompt` CLI на удаленном сервере через SSH, команда не может найти буфер обмена. Это связано с тем, что `code2prompt` CLI использует буфер обмена для копирования сгенерированного запроса, а сеансы SSH обычно не имеют доступа к локальному буферу обмена.\n\n## Решение\n\nЧтобы использовать `code2prompt` CLI с SSH, вы можете перенаправить вывод в файл вместо копирования в буфер обмена. Таким образом, вы можете по-прежнему генерировать запрос и сохранять его для последующего использования.\n\nИспользуйте опцию `--output-file`, чтобы указать файл вывода, где будет сохранен сгенерированный запрос. Например:\n\n```sh\nssh user@remote-server \"code2prompt path/to/codebase -O output.txt\"\n```\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/references/command_line_options.md",
    "content": "---\ntitle: Параметры командной строки Code2Prompt\ndescription: Справочное руководство по всем доступным параметрам CLI в Code2Prompt.\n---\n\n# Параметры командной строки\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/references/default_template.md",
    "content": "---\ntitle: Шаблон по умолчанию для Code2Prompt\ndescription: Узнайте о структуре шаблона по умолчанию, используемого в Code2Prompt.\n---\n\n# Шаблон по умолчанию\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/tutorials/getting_started.mdx",
    "content": "---\ntitle: Getting Started with Code2Prompt\ndescription: A comprehensive tutorial introducing Code2Prompt's core functionality and its use across CLI, SDK, and MCP integrations.\n---\n\nimport { Aside } from \"@astrojs/starlight/components\";\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\n\n<Card title=\"Обзор учебника\">\n  Добро пожаловать в Code2Prompt! Этот учебник предоставляет всестороннее\n  введение в использование Code2Prompt для генерации готовых к использованию ИИ\n  подсказок из вашего кода. Мы рассмотрим его основные функции и\n  продемонстрируем его использование в различных методах интеграции: интерфейс\n  командной строки (CLI), набор средств разработки (SDK) и протокол контекста\n  модели (MCP).\n</Card>\n\n## Что такое Code2Prompt?\n\nCode2Prompt - это универсальный инструмент, предназначенный для устранения разрыва между вашим кодом и большими языковыми моделями (LLM).\nОн интеллектуально извлекает соответствующие фрагменты кода, применяет мощную фильтрацию и форматирует информацию в структурированные подсказки, оптимизированные для потребления LLM.\nЭто упрощает задачи, такие как документирование кода, обнаружение ошибок, рефакторинг и многое другое.\n\nCode2Prompt предлагает различные точки интеграции:\n\n<Tabs>\n  <TabItem label=\"Core\" icon=\"seti:rust\">\n    Основная библиотека Rust, обеспечивающая основу для потребления кода и\n    подсказок.\n  </TabItem>\n  <TabItem label=\"CLI\" icon=\"seti:powershell\">\n    Дружественный интерфейс командной строки для быстрой генерации подсказок.\n    Идеален для интерактивного использования и одноразовых задач.\n  </TabItem>\n  <TabItem label=\"SDK\" icon=\"seti:python\">\n    Мощный набор средств разработки (SDK) для бесшовной интеграции в ваши\n    проекты на Python. Идеален для автоматизации генерации подсказок в рамках\n    более крупных рабочих процессов.\n  </TabItem>\n  <TabItem label=\"MCP\" icon=\"seti:db\">\n    Сервер протокола контекста модели (MCP) для расширенной интеграции с\n    агентами LLM. Позволяет осуществлять сложные,实时 взаимодействия с вашим\n    кодом.\n  </TabItem>\n</Tabs>\n\n## 📥 Установка\n\nДля получения подробных инструкций по установке всех методов (CLI, SDK, MCP) обратитесь к всестороннему [Руководству по установке](/docs/how_to/install).\n\n## 🏁 Генерация подсказок: пример CLI\n\nДавайте начнем с простого примера использования CLI.\nСоздайте образец проекта:\n\n```bash\nmkdir -p my_project/{src,tests}\ntouch my_project/src/main.rs my_project/tests/test_1.rs\necho 'fn main() { println!(\"Hello, world!\"); }' > my_project/src/main.rs\n```\n\nТеперь сгенерируйте подсказку:\n\n```bash\ncode2prompt my_project\n```\n\nЭто копирует подсказку в ваш буфер обмена.\nВы можете настроить это:\n\n- **Фильтрация:** `code2prompt my_project --include=\"*.rs\" --exclude=\"tests/*\"` (включает только файлы `.rs`, исключает каталог `tests`)\n- **Файл вывода:** `code2prompt my_project --output-file=my_prompt.txt`\n- **JSON-вывод:** `code2prompt my_project -O json` (структурированный JSON-вывод)\n- **Пользовательские шаблоны:** `code2prompt my_project -t my_template.hbs` (требуется создание `my_template.hbs`)\n\nСм. учебники [Learn Context Filtering](/docs/tutorials/learn_filters) и [Learn Handlebar Templates](/docs/tutorials/learn_templates), чтобы узнать о более продвинутом использовании.\n\n## 🐍 Интеграция SDK (Python)\n\nДля программного управления используйте Python SDK:\n\n```python\nfrom code2prompt_rs import Code2Prompt\n\nconfig = {\n    \"path\": \"my_project\",\n    \"include_patterns\": [\"*.rs\"],\n    \"exclude_patterns\": [\"tests/*\"],\n}\n\nc2p = Code2Prompt(**config)\nprompt = c2p.generate_prompt()\nprint(prompt)\n```\n\nЭто требует установки SDK (`pip install code2prompt_rs`).\nОбратитесь к документации SDK для более подробной информации.\n\n## 🤖 Интеграция с сервером MCP (расширенная)\n\nДля расширенной интеграции с агентами LLM запустите сервер `code2prompt` MCP (см. руководство по установке для подробностей).\nЭто позволяет агентам запрашивать контекст кода динамически.\nЭто расширенная функция, и дополнительная документация доступна на сайте проекта.\n\n<Card title=\"Следующие шаги\">\n  Изучите расширенные учебники и документацию, чтобы освоить возможности\n  Code2Prompt и интегрировать его в ваши рабочие процессы.\n</Card>\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/tutorials/learn_filters.mdx",
    "content": "---\ntitle: Learn Context Filtering with Code2Prompt\ndescription: Learn how to exclude or include files in your LLM prompts using powerful filtering options.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"Tutorial Overview\">\n  В этом руководстве демонстрируется, как использовать инструмент **glob\n  pattern** в интерфейсе командной строки `code2prompt`, чтобы фильтровать и\n  управлять файлами на основе шаблонов включения и исключения.\n</Card>\n\nШаблоны glob работают аналогично инструментам, таким как `tree` или `grep`, обеспечивая мощные возможности фильтрации. Ознакомьтесь с [подробным объяснением](/docs/explanations/glob_patterns) для получения дополнительной информации.\n\n---\n\n## Требования\n\nУбедитесь, что у вас установлен `code2prompt`. Если вы еще не установили его, обратитесь к [Руководству по установке](/docs/how_to/install).\n\n---\n\n## Понимание шаблонов включения и исключения\n\nШаблоны glob позволяют указать правила для фильтрации файлов и директорий.\n\n- **Шаблоны включения** (`--include`): Укажите файлы и директории, которые вы хотите включить.\n- **Шаблоны исключения** (`--exclude`): Укажите файлы и директории, которые вы хотите исключить.\n- **Приоритет** (`--include-priority`): Разрешает конфликты между шаблонами включения и исключения.\n\n---\n\n## Настройка окружения\n\nЧтобы практиковаться с шаблонами glob, давайте создадим тестовую структуру папок с некоторыми файлами.\n\n### Сценарий Bash для создания тестовой структуры\n\nЗапустите этот сценарий, чтобы создать временную структуру директорий:\n\n```bash\n#!/bin/bash\n\n# Create base directory\nmkdir -p test_dir/{lowercase,uppercase,.secret}\n\n# Create files in the structure\necho \"content foo.py\" > \"test_dir/lowercase/foo.py\"\necho \"content bar.py\" > \"test_dir/lowercase/bar.py\"\necho \"content baz.py\" > \"test_dir/lowercase/baz.py\"\necho \"content qux.txt\" > \"test_dir/lowercase/qux.txt\"\necho \"content corge.txt\" > \"test_dir/lowercase/corge.txt\"\necho \"content grault.txt\" > \"test_dir/lowercase/grault.txt\"\n\necho \"CONTENT FOO.py\" > \"test_dir/uppercase/FOO.PY\"\necho \"CONTENT BAR.py\" > \"test_dir/uppercase/BAR.PY\"\necho \"CONTENT BAZ.py\" > \"test_dir/uppercase/BAZ.PY\"\necho \"CONTENT QUX.txt\" > \"test_dir/uppercase/QUX.TXT\"\necho \"CONTENT CORGE.txt\" > \"test_dir/uppercase/CORGE.TXT\"\necho \"CONTENT GRAULT.txt\" > \"test_dir/uppercase/GRAULT.TXT\"\n\necho \"top secret\" > \"test_dir/.secret/secret.txt\"\n```\n\nЧтобы очистить структуру позже, запустите:\n\n```bash\nrm -rf test_dir\n```\n\nОн создаст следующую структуру директорий:\n\nimport { FileTree } from \"@astrojs/starlight/components\";\n\n<FileTree>\n  - test_dir - lowercase - foo.py - bar.py - baz.py - qux.txt - corge.txt -\n  grault.txt - uppercase - FOO.PY - BAR.PY - BAZ.PY - QUX.txt - CORGE.txt -\n  GRAULT.txt - .secret - secret.txt\n</FileTree>\n\n---\n\n## Примеры: Фильтрация файлов с помощью шаблонов включения и исключения\n\n### Случай 1: Нет включения, нет исключения\n\nКоманда:\n\n```bash\ncode2prompt test_dir\n```\n\n#### Результат\n\nВсе файлы включены:\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n- `.secret/secret.txt`\n\n---\n\n### Случай 2: Исключение определенных типов файлов\n\nИсключить `.txt` файлы:\n\n```bash\ncode2prompt test_dir --exclude=\"*.txt\"\n```\n\n#### Результат\n\nИсключены:\n\n- Все `.txt` файлы\n\nВключены:\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n\n---\n\n### Случай 3: Включение определенных типов файлов\n\nВключить только Python файлы:\n\n```bash\ncode2prompt test_dir --include=\"*.py\"\n```\n\n#### Результат\n\nВключены:\n\n- Все `.py` файлы\n\nИсключены:\n\n- `.secret/secret.txt`\n\n---\n\n### Случай 4: Включение и исключение с приоритетом\n\nВключить `.py` файлы, но исключить файлы в папке `uppercase`:\n\n```bash\ncode2prompt test_dir --include=\"*.py\" --exclude=\"**/uppercase/*\" --include-priority=true\n```\n\n#### Результат\n\nВключены:\n\n- Все `lowercase/1` файлы с расширением `.py`\n\nИсключены:\n\n- Все `uppercase` файлы\n- `.secret/secret.txt`\n\n---\n\n## Резюме\n\nИнструмент glob pattern в `code2prompt` позволяет эффективно фильтровать файлы и директории с помощью:\n\n- `--include` для указания файлов для включения\n- `--exclude` для указания файлов для исключения\n- `--include-priority` для разрешения конфликтов между шаблонами\n\nЧтобы практиковаться, настройте тестовую директорию, попробуйте команды и посмотрите, как инструмент динамически фильтрует файлы.\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/tutorials/learn_templates.mdx",
    "content": "---\ntitle: Изучите шаблоны Handlebar с Code2Prompt\ndescription: Поймите, как использовать и создавать пользовательские шаблоны Handlebars для генерации подсказок.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"Обзор урока\">\n  Этот урок демонстрирует, как использовать и создавать пользовательские шаблоны\n  Handlebars для генерации подсказок в CLI `code2prompt`.\n</Card>\n\n---\n\n## Предварительные требования\n\nУбедитесь, что у вас установлен `code2prompt`. Если вы еще не установили его, обратитесь к [Руководству по установке](/docs/how_to/install).\n\n---\n\n## Что такое шаблоны Handlebars?\n\n[Handlebars](https://handlebarsjs.com/) — это популярный механизм шаблонов, который позволяет создавать динамические шаблоны с помощью заполнителей.\nВ `code2prompt` шаблоны Handlebars используются для форматирования сгенерированных подсказок на основе структуры codebase и переменных, определенных пользователем.\n\n## Как использовать шаблоны Handlebars?\n\nВы можете использовать эти шаблоны, передав флаг `-t` или `--template`, за которым следует путь к файлу шаблона. Например:\n\n```sh\ncode2prompt path/to/codebase -t templates/document-the-code.hbs\n```\n\n## Синтаксис шаблонов\n\nШаблоны Handlebars используют простой синтаксис для заполнителей и выражений. Вы будете помещать переменные в двойные фигурные скобки `{{variable_name}}`, чтобы включить их в сгенерированную подсказку.\n`Code2prompt` предоставляет набор переменных по умолчанию, которые вы можете использовать в своих шаблонах:\n\n- `absolute_code_path`: Абсолютный путь к codebase.\n- `source_tree`: Дерево исходного кода codebase, которое включает все файлы и директории.\n- `files`: Список файлов в codebase, включая их пути и содержимое.\n- `git_diff`: Разница git codebase, если применимо.\n- `code`: Содержимое кода файла, который обрабатывается.\n- `path`: Путь файла, который обрабатывается.\n\nВы также можете использовать помощники Handlebars для выполнения условной логики, циклов и других операций в ваших шаблонах. Например:\n\n```handlebars\n{{#if files}}\n  {{#each files}}\n    Файл:\n    {{this.path}}\n    Содержимое:\n    {{this.content}}\n  {{/each}}\n{{else}}\n  Файлы не найдены.\n{{/if}}\n```\n\n---\n\n## Существующие шаблоны\n\n`code2prompt` поставляется с набором встроенных шаблонов для общих случаев использования. Вы можете найти их в директории [`templates`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates).\n\n### [`document-the-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/document-the-code.hbs)\n\nИспользуйте этот шаблон для генерации подсказок для документирования кода. Он добавит комментарии документации ко всем публичным функциям, методам, классам и модулям в codebase.\n\n### [`find-security-vulnerabilities.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/find-security-vulnerabilities.hbs)\n\nИспользуйте этот шаблон для генерации подсказок для поиска потенциальных уязвимостей безопасности в codebase. Он будет искать общие проблемы безопасности и предоставлять рекомендации по их устранению или смягчению.\n\n### [`clean-up-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/clean-up-code.hbs)\n\nИспользуйте этот шаблон для генерации подсказок для очистки и улучшения качества кода. Он будет искать возможности для улучшения читаемости, соблюдения лучших практик, эффективности, обработки ошибок и многого другого.\n\n### [`fix-bugs.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/fix-bugs.hbs)\n\nИспользуйте этот шаблон для генерации подсказок для исправления ошибок в codebase. Он поможет диагностировать проблемы, предоставить предложения по исправлению и обновить код с предложенными исправлениями.\n\n### [`write-github-pull-request.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-pull-request.hbs)\n\nИспользуйте этот шаблон для создания описания pull request GitHub в формате markdown, сравнивая разницу git и журнал git двух веток.\n\n### [`write-github-readme.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-readme.hbs)\n\nИспользуйте этот шаблон для генерации высококачественного файла README для проекта, подходящего для размещения на GitHub. Он проанализирует codebase, чтобы понять его цель и функциональность, и сгенерирует содержимое README в формате Markdown.\n\n### [`write-git-commit.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-git-commit.hbs)\n\nИспользуйте этот шаблон для генерации коммитов git из staged файлов в директории git. Он проанализирует codebase, чтобы понять его цель и функциональность, и сгенерирует содержимое сообщения коммита git в формате Markdown.\n\n### [`improve-performance.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/improve-performance.hbs)\n\nИспользуйте этот шаблон для генерации подсказок для улучшения производительности codebase. Он будет искать возможности для оптимизации, предоставлять конкретные предложения и обновлять код с изменениями.\n\n## Переменные, определенные пользователем\n\n`code2prompt` поддерживает использование переменных, определенных пользователем, в шаблонах Handlebars. Любые переменные в шаблоне, которые не являются частью контекста по умолчанию (`absolute_code_path`, `source_tree`, `files`), будут рассматриваться как переменные, определенные пользователем.\n\nВо время генерации подсказок `code2prompt` предложит пользователю ввести значения для этих переменных, определенных пользователем. Это позволяет для дальнейшей настройки сгенерированных подсказок на основе пользовательского ввода.\n\nНапример, если ваш шаблон включает `{{challenge_name}}` и `{{challenge_description}}`, вам будет предложено ввести значения для этих переменных при запуске `code2prompt`.\n\nЭта функция позволяет создавать многоразовые шаблоны, которые могут быть адаптированы к разным сценариям на основе информации, предоставленной пользователем.\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/vision.mdx",
    "content": "---\ntitle: Видение Code2Prompt\ndescription: Узнайте о видении Code2Prompt и том, как оно улучшает взаимодействие LLM с кодом.\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Aside } from \"@astrojs/starlight/components\";\n\n<Card title=\"Цель 🎯\">\n  `code2prompt` был создан для помощи разработчикам и агентам ИИ более\n  эффективно взаимодействовать с кодовыми базами.\n</Card>\n\n## Проблема 🚩\n\nБольшие языковые модели (LLM) революционизировали способ взаимодействия с кодом. Однако они все еще сталкиваются с существенными проблемами при генерации кода:\n\n- **Планирование и рассуждение**: LLM не хватает способности планировать и рассуждать, что крайне важно для задач, таких как генерация кода, рефакторинг и отладка. Они часто испытывают трудности с пониманием общей картины и имеют ограниченный взгляд.\n- **Размер контекста**: LLM имеют ограниченное окно контекста, что ограничивает их способность анализировать и понимать большие кодовые базы.\n- **Галлюцинация**: LLM могут генерировать код, который выглядит правильным, но на самом деле является неверным или бессмысленным. Это явление, известное как галлюцинация, возникает, когда модель не имеет достаточного контекста или понимания кодовой базы.\n\nИменно здесь вступает в действие `code2prompt`.\n\n## Решение ✅\n\nМы считаем, что планирование и рассуждение могут быть достигнуты человеком или агентами ИИ с помощью методов структурирования. Этим агентам необходимо собрать **высококачественный контекст** кодовой базы, который отфильтрован, структурирован и отформатирован для конкретной задачи.\n\nОсновное правило будет таким:\n\n<Aside type=\"tip\">\n  > предоставляйте как можно меньше контекста, но как можно больше, чтобы это\n  было необходимо\n</Aside>\n\nНа практике это сложно достичь, особенно для больших кодовых баз. Однако `code2prompt` — это простой инструмент, который может помочь разработчикам и агентам ИИ более эффективно усваивать кодовую базу.\n\nОн автоматизирует процесс обхода кодовой базы, фильтрации файлов и форматирования их в структурированные подсказки, которые могут понять LLM. Таким образом, он помогает смягчить проблемы планирования, рассуждения и галлюцинации.\n\nВы можете понять, как `code2prompt` предназначен для решения этих проблем, в следующем разделе.\n\n## Архитектура ⛩️\n\n<img\n  src=\"/assets/images/architecture.svg\"\n  alt=\"Архитектура code2prompt\"\n  style=\"width: 75%;\"\n/>\n\n`code2prompt` разработан по модульному принципу, что позволяет легко интегрировать его в различные рабочие процессы. Его можно использовать в качестве основной библиотеки, интерфейса командной строки (CLI), набора средств разработки (SDK) или даже в качестве сервера протокола контекста модели (MCP).\n\n### Основная часть\n\n`code2prompt` — это инструмент усвоения кода, который упрощает процесс создания подсказок LLM для анализа кода, генерации и других задач. Он работает, обходя директории, создавая древовидную структуру и собирая информацию о каждом файле. Основная библиотека может быть легко интегрирована в другие приложения.\n\n### CLI\n\nИнтерфейс командной строки `code2prompt` (CLI) был разработан для людей, чтобы генерировать подсказки непосредственно из вашей кодовой базы. Сгенерированная подсказка автоматически копируется в буфер обмена и может быть сохранена в выходной файл. Кроме того, вы можете настроить генерацию подсказок с помощью шаблонов Handlebars. Ознакомьтесь с предоставленными подсказками в документации!\n\n### SDK\n\nНабор средств разработки `code2prompt` (SDK) предлагает привязку Python к основной библиотеке. Это идеально подходит для агентов ИИ или скриптов автоматизации, которые хотят взаимодействовать с кодовой базой беспрепятственно. SDK размещен на Pypi и может быть установлен с помощью pip.\n\n### MCP\n\n`code2prompt` также доступен в качестве сервера протокола контекста модели (MCP), что позволяет запускать его в качестве локальной службы. Это позволяет LLM на стероидах, предоставляя им инструмент для автоматического сбора хорошо структурированного контекста вашей кодовой базы.\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/ru/docs/welcome.mdx",
    "content": "---\ntitle: Документация Code2Prompt\ndescription: Официальная документация Code2prompt\ntemplate: splash\nhero:\n  tagline: Преобразуйте свой код в оптимизированные для ИИ подсказки за секунды\n  image:\n    file: ../../../../assets/logo_dark_v0.0.1.svg\n  actions:\n    - text: Начать работу 🚀\n      link: /docs/tutorials/getting_started\n    - text: Установка 📥\n      link: /docs/how_to/install\n---\n\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\nimport { LinkCard } from \"@astrojs/starlight/components\";\n\n## Быстрый старт\n\n<LinkCard title=\"Начало работы 🚀\" href=\"/docs/tutorials/getting_started\" />\n<LinkCard title=\"Установка 📥\" href=\"/docs/how_to/install\" />\n<LinkCard\n  title=\"Узнайте о фильтрации 🔍\"\n  href=\"/docs/tutorials/learn_filters\"\n/>\n<LinkCard\n  title=\"Узнайте о шаблонизации 📝\"\n  href=\"/docs/tutorials/learn_templates\"\n/>\n<LinkCard title=\"Видение 🔮\" href=\"/docs/vision\" />\n\n`code2prompt` - это мощный инструмент для анализа и обработки кода, предназначенный для генерации подсказок для анализа, генерации и других задач. Он работает путем обхода директорий, построения древовидной структуры и сбора информации о каждом файле.\n\nОн упрощает процесс объединения и форматирования кода, делая его легко анализируемым, документируемым или рефакторируемым с помощью LLM.\n\nВы можете использовать `code2prompt` следующими способами:\n\n<CardGrid>\n  <Card title=\"Ядро\" icon=\"seti:rust\">\n    Ядро библиотеки для быстрого анализа кода\n  </Card>\n  <Card title=\"CLI\" icon=\"seti:powershell\">\n    Интерфейс командной строки, специально разработанный для людей\n  </Card>\n  <Card title=\"SDK\" icon=\"seti:python\">\n    Программный инструментарий для агентов ИИ и скриптов автоматизации\n  </Card>\n  <Card title=\"MCP\" icon=\"seti:folder\">\n    Сервер протокола контекста модели для LLM на стероидах\n  </Card>\n</CardGrid>\n---\n\n## Ключевые особенности\n\n- **Генерация подсказок LLM**: Быстро преобразуйте целые базы кода в структурированные подсказки LLM.\n- **Фильтрация по шаблону Glob**: Включайте или исключайте определенные файлы и директории с помощью шаблонов glob.\n- **Настройка шаблонов**: Адаптируйте генерацию подсказок с помощью шаблонов Handlebars.\n- **Подсчет токенов**: Анализируйте использование токенов и оптимизируйте для LLM с разными окнами контекста.\n- **Интеграция с Git**: Включайте разницы Git и сообщения о коммитах в подсказки для обзора кода.\n- **Уважение к `.gitignore`**: Автоматически игнорирует файлы, перечисленные в `.gitignore`, чтобы упростить генерацию подсказок.\n\n---\n\n## Почему `code2prompt`?\n\n1. **Экономьте время**:\n\n   - Автоматизирует процесс обхода базы кода и форматирования файлов для LLM.\n   - Избегает повторяющегося копирования и вставки кода.\n\n2. **Повышайте производительность**:\n\n   - Предоставляет структурированный и последовательный формат для анализа кода.\n   - Помогает выявлять ошибки, рефакторировать код и писать документацию быстрее.\n\n3. **Работа с большими базами кода**:\n\n   - Разработан для работы с большими базами кода, уважая ограничения контекста LLM.\n\n4. **Настройка рабочих процессов**:\n   - Гибкие возможности для фильтрации файлов, использования шаблонов и генерации целевых подсказок.\n\n---\n\n## Примеры использования\n\n- **Документация кода**:\n  Автоматически генерируйте документацию для публичных функций, методов и классов.\n\n- **Обнаружение ошибок**:\n  Найдите потенциальные ошибки и уязвимости, анализируя вашу базу кода с помощью LLM.\n\n- **Рефакторинг**:\n  Упростите и оптимизируйте код, генерируя подсказки для улучшения качества кода.\n\n- **Обучение и исследование**:\n  Поймите новые базы кода, генерируя сводки и подробные разборы.\n\n- **Описания коммитов и PR**:\n  Генерируйте осмысленные сообщения о коммитах и описания pull-запросов из разниц Git.\n\n> Эта страница была автоматически переведена для вашего удобства. Обратитесь к английской версии для получения оригинального содержания.\n"
  },
  {
    "path": "website/src/content/docs/zh/blog/2025.04.11_why_I_wrote_code2prompt.mdx",
    "content": "---\ntitle: \"为什么我开发了Code2Prompt\"\ndate: 2025-04-11\nlastUpdated: 2025-04-11\ntags:\n  - 开源\n  - code2prompt\n  - AI\n  - 智能代理\nexcerpt: \"code2prompt背后的故事：我为了解决LLM工作流中的上下文挑战而进行的开源探索\"\nauthors:\n  - ODAncona\ncover:\n  alt: \"code2prompt简化AI智能体代码上下文的示意图\"\n  image: \"/src/assets/logo_dark_v0.0.2.svg\"\nfeatured: false\ndraft: false\n---\n\n## 介绍\n\n我一直对大型语言模型（LLMs）如何改变编码工作流程感到着迷——它们可以生成测试、文档字符串，甚至在几分钟内完成整个功能的编写。但当我进一步推动这些模型时，几个关键的痛点不断出现：\n\n| 规划困难 | 高Token成本 | 幻觉     |\n| -------- | ----------- | -------- |\n| 🧠 ➡️ 🤯 | 🔥 ➡️ 💸    | 💬 ➡️ 🌀 |\n\n这就是为什么我开始为`code2prompt`做出贡献，这是一个基于Rust的工具，旨在为LLMs提供恰到好处的上下文。\n\n在本文中，我将分享我的经验，并解释为什么我相信`code2prompt`在今天是相关的，并且可以很好地集成，为什么它成为了我更好、更快地进行AI编码工作流程的首选解决方案。\n\n## 我与LLMs的初步接触 👣\n\n我于2023年11月开始在`OpenAI Playground`上使用`text-davinci-003`尝试LLMs。语言模型开启了一场新的革命。感觉就像拥有一个出色的新助手，可以几乎按照命令生成单元测试和文档字符串。我喜欢将模型推向极限——测试从闲聊和伦理困境到越狱和复杂编码任务的一切。然而，当我承担更大的项目时，我很快意识到模型有明显的局限性。起初，我只能将几百行代码放入上下文窗口，即使如此，模型也经常难以理解代码的目的或结构。这就是为什么我迅速意识到上下文的重要性至关重要。我的指令越简洁，上下文越好，结果就越好。\n\n![OpenAI Playground](/assets/blog/post1/playground.png)\n\n## 模型演进 🏗️\n\n模型可以产生令人印象深刻的结果，但往往难以处理较大的代码库或复杂的任务。我发现自己花费更多时间编写提示词，而不是实际编码。同时，模型通过发布新版本不断改进。它们增加了推理能力和上下文大小，提供了新的视角和可能性。我可以将近2000行代码放入上下文窗口，然后结果就改进了。我可以在几个迭代中编写整个功能，并且我对快速获得结果的速度感到惊讶。我相信LLMs是编码的未来，并且我想成为这场革命的一部分。我坚信，AI不会取代我们，但会以人类仍然是专家和控制者的助手形式辅助我们。\n\n## 我与LLMs的第一个项目 🚀\n\n我开始编写一个`ROS`路径查找模块，用于机器人竞赛，为一个干净架构的`Flutter`跨平台应用程序生成功能，并制作了一个小型的`Next.js`网页应用程序来跟踪我的费用。我在一个晚上使用一个我从未接触过的框架构建了这个小型应用程序，这是一个改变游戏规则的时刻；LLMs不仅仅是工具，而是倍增器。我开发了`bboxconverter`，一个用于转换边界框的包，还有很多其他项目。LLMs可以帮助您快速学习新技术和框架；这很棒。\n\n## 一个新的范式：软件3.0 💡\n\n我深入研究了LLMs，并开始围绕它们构建智能体和脚手架。我重现了著名的论文[RestGPT](https://restgpt.github.io/)。这个想法非常棒：给LLMs调用某些REST API的能力，使用OpenAPI规范，如`Spotify`或`TMDB`。这些功能引入了一种新的软件编程范式，我称之为**软件3.0**。\n\n| 软件1.0  | 软件2.0  | 软件3.0  |\n| -------- | -------- | -------- |\n| 基于规则 | 数据驱动 | 智能代理 |\n\n同样的想法推动了[MCP](https://modelcontextprotocol.io/introduction)协议的发展，该协议允许LLMs直接调用工具和资源，而无需设计工具描述即可被LLM调用，不像REST Apis那样不一定需要OpenAPI规范。\n\n## LLMs的局限性 🧩\n\n### 幻觉 🌀\n\n在重现著名的论文`RESTGPT`时，我注意到了LLMs的一些严重局限性。论文作者遇到了与我相同的问题：LLMs正在**幻觉**。它们生成未实现的代码，发明参数，简单地逐字遵循指令，而不利用常识。例如，在原始RestGPT代码库中，作者在[调用者提示](https://github.com/Yifan-Song793/RestGPT/blob/main/model/caller.py)中要求。\n\n> “不要耍聪明，不要编造计划中不存在的步骤。”\n\n我觉得这句话很有趣，也非常有趣，因为这是我第一次遇到有人指示LLMs不要幻觉。\n\n### 上下文大小受限 📏\n\n另一个限制是上下文大小；LLMs在寻找关键信息时表现良好，但难以理解它。当你给语言模型太多上下文时，它们往往会陷入细节，失去对全局的把握，这很令人沮丧，需要不断调整。我认为这与[维数诅咒](https://towardsdatascience.com/curse-of-dimensionality-a-curse-to-machine-learning-c122ee33bfeb)类似。把“维数”或“特征”替换为“上下文”，你就明白了。\n\n![Curse of Dimensionality](/assets/blog/post1/curse_of_dimensionality.png)\n\n你给LLM的上下文越多，就越难找到正确答案。我总结了这个想法的妙语：\n\n> 提供尽可能少但必要的上下文\n\n这高度受到瑞士政治家[Alain Berset](https://www.lematin.ch/story/alain-berset-la-formule-qui-defie-le-temps-166189802108)的名言启发，他在COVID-19封锁期间说过：\n\n> “我们希望尽快行动，但也需要时放慢速度”\n\n这代表了折衷的想法，也适用于LLMs的上下文大小！\n\n## 寻找更好的方法：code2prompt 🔨\n\n因此，我需要一种方法来快速加载、过滤和组织代码上下文，通过提供尽可能少但高质量的上下文。我尝试手动复制文件或代码片段到提示中，但这变得笨拙且容易出错。我知道自动化构造上下文以提出更好提示的繁琐过程会有帮助。然后，有一天，我在谷歌上输入了“code2prompt”，希望能找到一个可以直接将代码输送到提示的工具。\n\n果然，我发现了一个由[Mufeed](https://www.reddit.com/r/rust/comments/1bghroh/i_made_code2prompt_a_cli_tool_to_convert_your/)创建的**基于Rust的项目**，名为*code2prompt*，在GitHub上拥有大约200个星标。当时，它仍然很基础：一个简单的CLI工具，具有基本的过滤能力和模板。我看到了巨大的潜力，于是直接跳进去贡献，实现了glob模式匹配等功能，并很快成为主要贡献者。\n\n## 愿景与集成 🔮\n\n如今，有几种方法可以为LLMs提供上下文。从更大的上下文中生成，使用检索增强生成（RAG），[压缩代码](https://www.all-hands.dev/blog/openhands-context-condensensation-for-more-efficient-ai-agents)，甚至使用这些方法的组合。上下文构造是一个热门话题，在未来几个月内将迅速发展。然而，我的方法是**KISS**：保持简单，笨蛋。向LLMs提供上下文的最简单有效的方法是使用最简单的方法。你精确构造所需的上下文；它是确定性的，这与RAG相反。\n\n这就是为什么我决定将`code2prompt`作为一个简单的工具进一步推动，可以在任何工作流中使用。我希望它易于使用、集成和扩展。这就是为什么我添加了与工具交互的新方法。\n\n- **核心**: `code2prompt`的核心是一个Rust库，提供从代码库中构造上下文的基本功能。它包括一个简单的API来加载、过滤和组织代码上下文。\n- **CLI**: 命令行界面是使用`code2prompt`的最简单方式。你可以构造代码库的上下文，并直接将其输送到提示中。\n- **Python API**: Python API是围绕CLI的简单包装器，允许你在Python脚本和智能体中使用`code2prompt`。你可以构造代码库的上下文，并直接将其输送到提示中。\n- **MCP**: `code2prompt` MCP服务器允许LLMs使用`code2prompt`作为工具，从而使自己能够构造上下文。\n\n更多信息请参见文档中的[愿景页面](/docs/vision)。\n\n## 与智能体集成 👤\n\n我相信未来的智能体需要一种方法来摄取上下文，而`code2prompt`是一种简单有效的方法，适用于基于文本的存储库，如代码库、文档或笔记。一个典型的地方是在具有有意义的命名约定的代码库中使用`code2prompt`。例如，在干净的架构中，关注点和层之间有清晰的分离。相关的上下文通常驻留在不同的文件和文件夹中，但共享相同的名称。这是`code2prompt`的完美用例，您可以使用glob模式来获取相关文件。\n\n**基于Glob模式**：以最小的麻烦精确选择或排除文件。\n\n此外，核心库被设计为有状态的上下文管理器，允许您在与LLM的对话过程中添加或删除文件。当为特定任务或目标提供上下文时，这特别有用。您可以轻松地添加或删除文件，而无需重新运行进程。\n\n**有状态上下文**：在与LLM的对话过程中添加或删除文件。\n\n这些功能使`code2prompt`成为基于智能体的工作流的理想选择。MCP服务器允许与流行的AI智能体框架（如[Aider](https://github.com/paul-gauthier/aider）、[Goose](https://block.github.io/goose/)或[Cline](https://github.com/jhillyerd/cline)）无缝集成。让它们处理复杂目标的同时，`code2prompt`提供完美的代码上下文。\n\n## 为什么Code2prompt很重要 ✊\n\n随着LLMs的发展和上下文窗口的扩大，简单地将整个存储库强制输入提示可能看起来足够了。然而，**Token成本**和**提示连贯性**仍然是小公司和开发者的重大障碍。专注于最重要的代码，`code2prompt`使您的LLM使用效率高、成本效益高，并且不容易产生幻觉。\n\n**简而言之：**\n\n- **减少幻觉**：通过提供适量的上下文\n- **降低Token使用**成本：通过手动管理所需的适当上下文\n- **提高LLM性能**：通过提供适量的上下文\n- 将智能体堆栈集成作为文本存储库的上下文提供者\n\n## 加入开源！ 🌐\n\n欢迎每一位新贡献者！如果您对Rust、构造创新AI工具感兴趣，或者只是想要一个更好的基于代码提示的工作流，请加入我们。\n\n感谢阅读，我希望我的故事能激励您尝试code2prompt。这是一段令人难以置信的旅程，才刚刚开始！\n\n**Olivier D'Ancona**\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/explanations/glob_pattern_filter.mdx",
    "content": "---\ntitle: Glob模式过滤器的工作原理\ndescription: Code2Prompt如何使用包含(-i)和排除(-e)glob来决定保留或丢弃哪些文件。\n---\n\nCode2Prompt 使用 glob 模式来包含或排除文件和目录，工作方式类似于 tree 或 grep 等工具。它允许您传递两个独立的 glob 模式*列表*：\n\n- **包含列表** (`--include` 或 `-i`) - \"这些模式允许文件\"\n- **排除列表** (`--exclude` 或 `-e`) - \"这些模式禁止文件\"\n\nCode2prompt 必须为项目中的每个文件决定是保留还是丢弃。本页面解释了规则以及背后的设计选择。\n\n---\n\n## 1. 集合和符号\n\n在整个解释过程中，我们使用通常的集合符号\n\n| 符号                              | 含义                               |\n| --------------------------------- | ---------------------------------- |\n| $A$                               | 匹配**至少一个**包含模式的文件集合 |\n| $B$                               | 匹配**至少一个**排除模式的文件集合 |\n| $\\Omega$                          | 整个项目树（_全集_）               |\n| $C = A \\cap B$                    | 匹配两个列表的文件（_重叠_）       |\n| $D = \\Omega \\setminus (A \\cup B)$ | 不匹配任何列表的文件               |\n\n---\n\n## 2. 四种情况\n\n### 四种情况概览\n\n| 包含列表 | 排除列表 | 保留的文件 |\n| -------- | -------- | ---------- |\n| A = ∅    | B = ∅    | Ω          |\n| A = ∅    | B ≠ ∅    | ¬B         |\n| A ≠ ∅    | B = ∅    | A          |\n| A ≠ ∅    | B ≠ ∅    | A \\ B      |\n\n1. **没有包含列表，没有排除列表**\n\n   如果没有指定模式，则保留所有文件 (`Ω`)。\n\n2. **仅排除列表**\n\n   在这种情况下，Code2Prompt 充当黑名单，删除匹配排除模式的文件 (` Ω \\ B = ¬B`)。\n\n3. **仅包含列表**\n\n   如果仅指定包含列表，Code2Prompt 充当白名单，仅保留匹配包含模式的文件 (`A`)。\n\n4. **包含*和*排除列表**\n\n   如果同时指定了两个列表，Code2Prompt 保留匹配包含模式的文件，但删除匹配排除模式的文件 (`A \\ B`)。\n\n---\n\n## 3. 关于重叠的更多信息\n\n当两个列表都存在时 (`A ≠ ∅`, `B ≠ ∅`)，对于重叠 `C` 和其余部分 `D`，\n您有四种逻辑可能性。\n\n| 需要 `C`？ | 需要 `D`？ | 合理吗？                                         |\n| ---------- | ---------- | ------------------------------------------------ |\n| 否         | 否         | 默认行为 (`A \\ B`)                               |\n| 是         | 否         | 与情况 3 相同的行为 (`A`)                        |\n| 否         | 是         | 令人惊讶（\"丢弃我请求的 `C`，保留我没有请求的\"） |\n| 是         | 是         | 与情况 1 相同的行为 (`Ω`)                        |\n\n正是由于这个原因，`--include-priority` 选项被删除了。因为这将与只有包含列表（情况 3）的结果相同。\n\n## 4. 快速参考表\n\n| 想要保留…                              | 使用             |\n| -------------------------------------- | ---------------- |\n| 一切                                   | 无 `-i`，无 `-e` |\n| 除某些模式*之外*的一切                 | 仅 `-e`          |\n| *仅*匹配模式的内容                     | 仅 `-i`          |\n| 匹配 `-i` 的内容，减去匹配 `-e` 的内容 | `-i` **和** `-e` |\n\n---\n\n这种设计保持了心理模型的简单性：\n\n- 包含列表一旦存在就是白名单。\n- 排除列表是叠加在上面的黑名单。\n- 重叠部分默认被丢弃\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/explanations/glob_patterns.md",
    "content": "---\ntitle: Understanding Glob Patterns\ndescription: A detailed explanation of glob patterns and how they are used in Code2Prompt.\n---\n\nGlob 模式是一种简单而强大的方法，用于使用通配符匹配文件名和路径。它们在命令行界面和编程语言中被广泛使用，以指定文件名或目录的集合。以下是一些最常用的 Glob 模式的详细介绍：\n\n## 基本通配符\n\n- `*`：匹配任意数量的字符，包括零个字符。\n  - 示例：`*.txt` 匹配所有以 `.txt` 结尾的文件。\n\n- `?`：匹配恰好一个字符。\n  - 示例：`file?.txt` 匹配 `file1.txt`、`fileA.txt`，但不匹配 `file10.txt`。\n\n- `[]`：匹配任意一个括弧内的字符。\n  - 示例：`file[1-3].txt` 匹配 `file1.txt`、`file2.txt`、`file3.txt`。\n\n- `[!]` 或 `[^]`：匹配任意一个不在括弧内的字符。\n  - 示例：`file[!1-3].txt` 匹配 `file4.txt`、`fileA.txt`，但不匹配 `file1.txt`。\n\n## 高级模式\n\n- `**`：递归匹配任意数量的目录和子目录。\n  - 示例：`**/*.txt` 匹配当前目录和所有子目录中的所有 `.txt` 文件。\n\n- `{}`：匹配任意一个用逗号分隔的模式。\n  - 示例：`file{1,2,3}.txt` 匹配 `file1.txt`、`file2.txt`、`file3.txt`。\n\n## 示例\n\n1. **匹配目录中的所有文本文件：**\n\n   ```sh\n   *.txt\n   ```\n\n2. **匹配扩展名前面有一个数字的所有文件：**\n\n   ```sh\n   file?.txt\n   ```\n\n3. **匹配扩展名为 `.jpg` 或 `.png` 的文件：**\n\n   ```sh\n   *.{jpg,png}\n   ```\n\n4. **匹配任何子目录中的所有 `.txt` 文件：**\n\n   ```sh\n   **/*.txt\n   ```\n\n5. **匹配以 `a` 或 `b` 开头并以 `.txt` 结尾的文件：**\n\n   ```sh\n   {a,b}*.txt\n   ```\n\n## 用例\n\n- **命令行工具：** Glob 模式在 `ls`、`cp`、`mv` 和 `rm` 等命令行工具中被广泛使用，以指定多个文件或目录。\n- **编程语言：** Python、JavaScript 和 Ruby 等语言通过 Python 中的 `glob` 库等支持文件匹配的 Glob 模式。\n- **构建系统：** Makefile 等工具使用 Glob 模式指定源文件和依赖项。\n\n## 结论\n\nGlob 模式提供了一种灵活直观的方法来匹配文件名和路径，使其对于脚本编写、自动化和文件管理任务来说非常宝贵。理解和利用这些模式可以显著提高您处理文件和目录的效率和生产力。\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/explanations/tokenizers.md",
    "content": "---\ntitle: Code2Prompt 中的分词\ndescription: 了解分词以及 Code2Prompt 如何为大型语言模型处理文本。\n---\n\n在处理语言模型时，文本需要转换为模型可以理解的格式——**tokens**，即数字序列。这种转换由 **tokenizer** 处理。\n\n---\n\n## 什么是 Tokenizer？\n\nTokenizer 将原始文本转换为 tokens，这些是语言模型处理输入的基本单位。这些 tokens 可以根据 tokenizer 的设计表示单词、子单词甚至单个字符。\n\n对于 `code2prompt`，我们使用 **tiktoken** tokenizer。它高效、稳健，并针对 OpenAI 模型进行了优化。\n您可以在官方仓库中探索其功能\n\n👉 [tiktoken GitHub 仓库](https://github.com/openai/tiktoken)\n\n如果您想了解更多关于 tokenizer 的信息，请查看\n\n👉 [Mistral 分词指南](https://docs.mistral.ai/guides/tokenization/).\n\n## 在 `code2prompt` 中的实现\n\n分词使用 [`tiktoken-rs`](https://github.com/zurawiki/tiktoken-rs) 实现。`tiktoken` 支持 OpenAI 模型使用的以下编码：\n\n| 命令行参数 | 编码名称           | OpenAI 模型                                                             |\n| ---- | ----------------------- | ------------------------------------------------------------------------- |\n|`cl100k`| `cl100k_base`           | ChatGPT 模型，`text-embedding-ada-002`                                  |\n|`p50k`| `p50k_base`             | 代码模型，`text-davinci-002`，`text-davinci-003`                       |\n|`p50k_edit`| `p50k_edit`             | 用于编辑模型，如 `text-davinci-edit-001`，`code-davinci-edit-001` |\n|`r50k`| `r50k_base`（或 `gpt2`） | GPT-3 模型，如 `davinci`                                               |\n|`gpt2`| `o200k_base`            | GPT-4o 模型                                                             |\n\n有关不同 tokenizer 的更多上下文，请参阅 [OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/66b988407d8d13cad5060a881dc8c892141f2d5c/examples/How_to_count_tokens_with_tiktoken.ipynb)\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/how_to/filter_files.md",
    "content": "---\ntitle: 在 Code2Prompt 中筛选文件\ndescription: 使用不同筛选方法包含或排除文件的逐步指南。\n---\n\n\n## 用法\n\n从代码库目录生成提示：\n\n```sh\ncode2prompt path/to/codebase\n```\n\n使用自定义 Handlebars 模板文件：\n\n```sh\ncode2prompt path/to/codebase -t path/to/template.hbs\n```\n\n使用 glob 模式筛选文件：\n\n```sh\ncode2prompt path/to/codebase --include=\"*.rs,*.toml\"\n```\n\n使用 glob 模式排除文件：\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.txt,*.md\"\n```\n\n根据排除模式从源树中排除文件/文件夹：\n\n```sh\ncode2prompt path/to/codebase --exclude=\"*.npy,*.wav\" --exclude-from-tree\n```\n\n显示生成的提示的 token 数量：\n\n```sh\ncode2prompt path/to/codebase --tokens\n```\n\n指定 token 计数器的 tokenizer：\n\n```sh\ncode2prompt path/to/codebase --tokens --encoding=p50k\n```\n\n支持的 tokenizer：`cl100k`、`p50k`、`p50k_edit`、`r50k_bas`。\n> [!NOTE]  \n> 详见 [Tokenizers](#tokenizers)。\n\n将生成的提示保存到输出文件：\n\n```sh\ncode2prompt path/to/codebase --output=output.txt\n```\n\n以 JSON 格式打印输出：\n\n```sh\ncode2prompt path/to/codebase --json\n```\n\nJSON 输出结构如下：\n\n```json\n{\n  \"prompt\": \"<Generated Prompt>\", \n  \"directory_name\": \"codebase\",\n  \"token_count\": 1234,\n  \"model_info\": \"ChatGPT models, text-embedding-ada-002\",\n  \"files\": []\n}\n```\n\n生成 Git 提交消息（针对暂存文件）：\n\n```sh\ncode2prompt path/to/codebase --diff -t templates/write-git-commit.hbs\n```\n\n生成拉取请求与分支比较（针对暂存文件）：\n\n```sh\ncode2prompt path/to/codebase --git-diff-branch 'main, development' --git-log-branch 'main, development' -t templates/write-github-pull-request.hbs\n```\n\n在源代码块中添加行号：\n\n```sh\ncode2prompt path/to/codebase --line-number\n```\n\n禁用在 Markdown 代码块中换行代码：\n\n```sh\ncode2prompt path/to/codebase --no-codeblock\n```\n\n- 将代码重写为另一种语言。\n- 查找错误/安全漏洞。\n- 记录代码。\n- 实现新功能。\n\n> 我最初编写此工具用于个人使用，以便利用 Claude 3.0 的 200K 上下文窗口，事实证明它非常有用，因此我决定将其开源！\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/how_to/install.mdx",
    "content": "---\ntitle: 安装 Code2Prompt\ndescription: 不同操作系统上安装 Code2Prompt 的完整指南。\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Steps } from \"@astrojs/starlight/components\";\n\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\n\n<Card title=\"指南概述\">\n  欢迎来到 `Code2Prompt` 安装指南。本文档提供了在各种平台（包括 Windows、macOS\n  和 Linux）上安装 Code2Prompt 的逐步说明。\n</Card>\n\n**TL;DR**\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n## 前置条件\n\n确保您的系统上已安装 [Rust](https://www.rust-lang.org/tools/install) 和 cargo。\n\n```sh\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n```\n\n这是安装最新稳定版 Rust 和 Cargo 的官方方法。安装 Rust 后，请确保刷新您的 `PATH` 变量。重启您的终端或运行安装程序建议的命令。\n\n```sh\nsource $HOME/.cargo/env\n```\n\n您可以通过运行以下命令检查所有内容是否正确安装：\n\n```sh\ncargo --version\ngit --version\n```\n\n## 命令行界面（CLI） 👨‍💻\n\n```bash\n# Cargo\n$ cargo install code2prompt\n\n# Homebrew\n$ brew install code2prompt\n```\n\n#### 🧪 从 GitHub 安装最新（未发布）版本\n\n如果您想要在 crates.io 发布之前使用最新功能或修复：\n\n```sh\ncargo install --git https://github.com/mufeedvh/code2prompt\n```\n\n### 源代码构建\n\n适用于想要从源代码构建或为项目做出贡献的开发人员。\n\n<Steps>\n\n1.  🛠️ 安装前置条件：\n\n    - [Rust](https://www.rust-lang.org/tools/install) 和 Cargo\n    - [Git](https://git-scm.com/downloads)\n\n2.  📥 克隆仓库：\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt\n    ```\n\n3.  📦 安装二进制文件：\n\n    从源代码构建和安装：\n\n    ```sh\n    cargo install --path crates/code2prompt\n    ```\n\n    在不安装的情况下构建二进制文件：\n\n    ```sh\n    cargo build --release\n    ```\n\n    二进制文件将在 `target/release` 目录中可用。\n\n4.  🚀 运行它：\n\n    ```sh\n    code2prompt --help\n    ```\n\n</Steps>\n\n### 二进制发布\n\n最适合想要使用最新版本而无需从源代码构建的用户。\n\n从 [Releases](https://github.com/mufeedvh/code2prompt/releases) 下载您操作系统的最新二进制文件。\n\n⚠️ 二进制发布可能会落后于最新的 GitHub 版本。若要使用前沿功能，请考虑从源代码构建。\n\n### AUR\n\n专门为 Arch Linux 用户，`code2prompt` 可在 AUR 上使用。\n\n`code2prompt` 可在 [`AUR`](https://aur.archlinux.org/packages?O=0&K=code2prompt) 上使用。通过任何 AUR 助手安装它。\n\n```sh\nparu/yay -S code2prompt\n```\n\n### Nix\n\n如果您正在使用 Nix，可以使用 nix-env 或 nix profile 安装。\n\n```sh\n# without flakes:\nnix-env -iA nixpkgs.code2prompt\n# with flakes:\nnix profile install nixpkgs#code2prompt\n```\n\n## 软件开发工具包（SDK） 🐍\n\n### Pypi\n\n您可以从 Pypi 下载 Python 绑定。\n\n```sh\npip install code2prompt_rs\n```\n\n### 源代码构建\n\n<Steps>\n\n1.  🛠️ 安装前置条件：\n\n    - [Rust](https://www.rust-lang.org/tools/install) 和 Cargo\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 克隆仓库：\n\n    ```sh\n    git clone https://github.com/mufeedvh/code2prompt.git\n    cd code2prompt/crates/code2prompt-python\n    ```\n\n3.  📦 安装依赖项：\n\n    `rye` 命令将创建虚拟环境并安装所有依赖项。\n\n    ```sh\n    rye sync\n    ```\n\n4.  ⚙️ 构建包：\n\n    您将在项目根目录的 `.venv` 文件夹中位于虚拟环境中开发包。\n\n    ```sh\n    rye run maturin develop -r\n    ```\n\n</Steps>\n\n## 模型上下文协议（MCP） 🤖\n\n### 自动安装\n\n`code2prompt` MCP 服务器将很快在 MCP 注册表中可用。\n\n### 手动安装\n\n`code2prompt` MCP 服务器仍处于原型阶段，很快将集成到主仓库中。\n\n在本地运行 MCP 服务器，以便与 `Cline`、`Goose` 或 `Aider` 一起使用：\n\n<Steps>\n\n1.  🛠️ 安装前置条件：\n\n    - [Git](https://git-scm.com/downloads)\n    - [Rye](https://rye.astral.sh/)\n\n2.  📥 克隆仓库：\n\n    ```sh\n    git clone https://github.com/odancona/code2prompt-mcp.git\n    cd code2prompt-mcp\n    ```\n\n3.  📦 安装依赖项：\n\n    `rye` 命令将创建虚拟环境并在 `.venv` 文件夹中安装所有依赖项。\n\n    ```sh\n    rye sync\n    ```\n\n4.  🚀 运行服务器：\n\n    MCP 服务器现已安装。您可以使用以下命令运行它：\n\n    ```sh\n    . .venv/bin/activate\n    python -m src/code2prompt_mcp/main.py\n    ```\n\n5.  🔌 与代理集成：\n\n            例如，您可以使用类似的配置将其与 `Cline` 集成：\n\n            ```json\n            {\n              \"mcpServers\": {\n                \"code2prompt\": {\n                  \"command\": \"bash\",\n                  \"args\": [\n                    \"-c\",\n                    \"cd /home/olivier/projet/code2prompt-mcp && rye run python /home/olivier/projet/code2prompt-mcp/src/code2prompt_mcp/main.py\"\n                  ],\n                  \"env\": {}\n                }\n              }\n            }\n            ```\n\n</Steps>\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/how_to/ssh.md",
    "content": "---\ntitle: 在 SSH 环境下使用 Code2prompt CLI\ndescription: 使用 Code2Prompt CLI 与 SSH 进行远程代码库分析的指南。\n---\n\n## 为什么无法工作？\n\n当您尝试通过 SSH 在远程服务器上运行 `code2prompt` CLI 时，命令无法找到剪贴板。这是因为 `code2prompt` CLI 使用剪贴板复制生成的提示，而 SSH 会话通常无法访问本地剪贴板。\n\n## 解决方案\n\n要在 SSH 环境下使用 `code2prompt` CLI，您可以将输出重定向到文件，而不是复制到剪贴板。这样，您仍然可以生成提示并将其保存以备后用。\n\n使用 `--output-file` 选项指定输出文件，其中将保存生成的提示。例如：\n\n```sh\nssh user@remote-server \"code2prompt path/to/codebase -O output.txt\"\n```\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/references/command_line_options.md",
    "content": "---\ntitle: Code2Prompt 命令行选项\ndescription: Code2Prompt 所有可用 CLI 选项的参考指南。\n---\n\n# 命令行选项\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/references/default_template.md",
    "content": "---\ntitle: Code2Prompt 默认模板\ndescription: 了解 Code2Prompt 中使用的默认模板结构。\n---\n\n# 默认模板\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/tutorials/getting_started.mdx",
    "content": "---\ntitle: Code2Prompt入门指南\ndescription: 本教程全面介绍了Code2Prompt的核心功能及其在CLI、SDK和MCP集成中的应用。\n---\n\nimport { Aside } from \"@astrojs/starlight/components\";\nimport { Tabs, TabItem } from \"@astrojs/starlight/components\";\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\n\n<Card title=\"教程概述\">\n  欢迎使用Code2Prompt！本教程将为您全面介绍如何使用Code2Prompt从代码库中生成适用于AI的提示。我们将探索其核心功能，并演示其在不同集成方式下的使用：命令行界面（CLI）、软件开发工具包（SDK）和模型上下文协议（MCP）。\n</Card>\n\n## 什么是Code2Prompt？\n\nCode2Prompt是一款多功能工具，旨在弥合代码库与大型语言模型（LLM）之间的差距。它智能地提取相关代码片段，应用强大的过滤功能，并将信息格式化为适用于LLM的 structured 提示。这简化了代码文档、错误检测、重构等任务。\n\nCode2Prompt提供不同的集成点：\n\n<Tabs>\n  <TabItem label=\"核心\" icon=\"seti:rust\">\n    一个核心的Rust库，为代码读取和提示生成提供基础。\n  </TabItem>\n  <TabItem label=\"CLI\" icon=\"seti:powershell\">\n    一个用户友好的命令行界面，用于快速生成提示。适用于交互式使用和一次性任务。\n  </TabItem>\n  <TabItem label=\"SDK\" icon=\"seti:python\">\n    一个功能强大的软件开发工具包（SDK），可与Python项目无缝集成。适用于在更大的工作流中自动生成提示。\n  </TabItem>\n  <TabItem label=\"MCP\" icon=\"seti:db\">\n    一个模型上下文协议（MCP）服务器，用于与LLM代理进行高级集成。使代码库能够进行复杂、实时的交互。\n  </TabItem>\n</Tabs>\n\n## 📥 安装\n\n有关所有方法（CLI、SDK、MCP）的详细安装说明，请参阅综合[安装指南](/docs/how_to/install)。\n\n## 🏁 生成提示：CLI示例\n\n让我们从使用CLI的一个简单示例开始。创建一个示例项目：\n\n```bash\nmkdir -p my_project/{src,tests}\ntouch my_project/src/main.rs my_project/tests/test_1.rs\necho 'fn main() { println!(\"Hello, world!\"); }' > my_project/src/main.rs\n```\n\n现在，生成一个提示：\n\n```bash\ncode2prompt my_project\n```\n\n这会将提示复制到您的剪贴板。您可以自定义此操作：\n\n- **过滤：** `code2prompt my_project --include=\"*.rs\" --exclude=\"tests/*\"`（仅包含 `.rs` 文件，排除 `tests` 目录）\n- **输出文件：** `code2prompt my_project --output-file=my_prompt.txt`\n- **JSON输出：** `code2prompt my_project -O json`（结构化JSON输出）\n- **自定义模板：** `code2prompt my_project -t my_template.hbs`（需要创建 `my_template.hbs`）\n\n请参阅[了解上下文过滤](/docs/tutorials/learn_filters)和[了解Handlebar模板](/docs/tutorials/learn_templates)教程，以了解更多高级用法。\n\n## 🐍 SDK集成（Python）\n\n对于程序化控制，请使用Python SDK：\n\n```python\nfrom code2prompt_rs import Code2Prompt\n\nconfig = {\n    \"path\": \"my_project\",\n    \"include_patterns\": [\"*.rs\"],\n    \"exclude_patterns\": [\"tests/*\"],\n}\n\nc2p = Code2Prompt(**config)\nprompt = c2p.generate_prompt()\nprint(prompt)\n```\n\n这需要安装SDK（`pip install code2prompt_rs`）。有关更多详细信息，请参阅SDK文档。\n\n## 🤖 MCP服务器集成（高级）\n\n对于与LLM代理的高级集成，请运行`code2prompt`MCP服务器（有关详细信息，请参阅安装指南）。这允许代理动态请求代码上下文。这是一个高级功能，项目的网站上有进一步的文档。\n\n<Card title=\"下一步\">\n  探索高级教程和文档，以掌握Code2Prompt的功能，并将其集成到您的工作流中。\n</Card>\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/tutorials/learn_filters.mdx",
    "content": "---\ntitle: 使用 Code2Prompt 学习上下文过滤\ndescription: 学习如何使用强大的过滤选项在 LLM 提示中排除或包含文件。\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"教程概述\">\n  本教程演示如何使用 `code2prompt` CLI 中的 **glob\n  模式工具**，根据包含和排除模式过滤和管理文件。\n</Card>\n\nGlob 模式的工作方式类似于 `tree` 或 `grep` 等工具，提供强大的过滤功能。有关更多信息，请查看[详细说明](/docs/explanations/glob_patterns)。\n\n---\n\n## 前提条件\n\n确保您已安装 `code2prompt`。如果尚未安装，请参考[安装指南](/docs/how_to/install)。\n\n---\n\n## 了解包含和排除模式\n\nGlob 模式允许您指定过滤文件和目录的规则。\n\n- **包含模式** (`--include`)：指定要包含的文件和目录。\n- **排除模式** (`--exclude`)：指定要排除的文件和目录。\n- **优先级** (`--include-priority`)：解决包含和排除模式之间的冲突。\n\n---\n\n## 设置环境\n\n为了使用 glob 模式进行实践，我们来创建一个包含一些文件的示例文件夹结构。\n\n### 生成测试结构的 Bash 脚本\n\n运行此脚本以设置临时目录结构：\n\n```bash\n#!/bin/bash\n\n# 创建基础目录\nmkdir -p test_dir/{lowercase,uppercase,.secret}\n\n# 在结构中创建文件\necho \"content foo.py\" > \"test_dir/lowercase/foo.py\"\necho \"content bar.py\" > \"test_dir/lowercase/bar.py\"\necho \"content baz.py\" > \"test_dir/lowercase/baz.py\"\necho \"content qux.txt\" > \"test_dir/lowercase/qux.txt\"\necho \"content corge.txt\" > \"test_dir/lowercase/corge.txt\"\necho \"content grault.txt\" > \"test_dir/lowercase/grault.txt\"\n\necho \"CONTENT FOO.py\" > \"test_dir/uppercase/FOO.PY\"\necho \"CONTENT BAR.py\" > \"test_dir/uppercase/BAR.PY\"\necho \"CONTENT BAZ.py\" > \"test_dir/uppercase/BAZ.PY\"\necho \"CONTENT QUX.txt\" > \"test_dir/uppercase/QUX.TXT\"\necho \"CONTENT CORGE.txt\" > \"test_dir/uppercase/CORGE.TXT\"\necho \"CONTENT GRAULT.txt\" > \"test_dir/uppercase/GRAULT.TXT\"\n\necho \"top secret\" > \"test_dir/.secret/secret.txt\"\n```\n\n要清理结构，请运行：\n\n```bash\nrm -rf test_dir\n```\n\n它将创建以下目录结构：\n\nimport { FileTree } from \"@astrojs/starlight/components\";\n\n<FileTree>\n  - test_dir - lowercase - foo.py - bar.py - baz.py - qux.txt - corge.txt -\n  grault.txt - uppercase - FOO.PY - BAR.PY - BAZ.PY - QUX.txt - CORGE.txt -\n  GRAULT.txt - .secret - secret.txt\n</FileTree>\n\n---\n\n## 示例：使用包含和排除模式过滤文件\n\n### 案例 1：无包含，无排除\n\n命令：\n\n```bash\ncode2prompt test_dir\n```\n\n#### 结果\n\n所有文件都被包含：\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n- `.secret/secret.txt`\n\n---\n\n### 案例 2：排除特定文件类型\n\n排除 `.txt` 文件：\n\n```bash\ncode2prompt test_dir --exclude=\"*.txt\"\n```\n\n#### 结果\n\n已排除：\n\n- 所有 `.txt` 文件\n\n已包含：\n\n- `lowercase/foo.py`\n- `lowercase/bar.py`\n- `uppercase/FOO.py`\n\n---\n\n### 案例 3：包含特定文件类型\n\n仅包含 Python 文件：\n\n```bash\ncode2prompt test_dir --include=\"*.py\"\n```\n\n#### 结果\n\n已包含：\n\n- 所有 `.py` 文件\n\n已排除：\n\n- `.secret/secret.txt`\n\n---\n\n### 案例 4：包含和排除具有优先级\n\n包含 `.py` 文件，但排除 `uppercase` 文件夹中的文件：\n\n```bash\ncode2prompt test_dir --include=\"*.py\" --exclude=\"**/uppercase/*\" --include-priority=true\n```\n\n#### 结果\n\n已包含：\n\n- 所有 `lowercase/1` 文件，具有 `.py` 扩展名\n\n已排除：\n\n- 所有 `uppercase` 文件\n- `.secret/secret.txt`\n\n---\n\n## 总结\n\n`code2prompt` 中的 glob 模式工具允许您使用以下方法有效地过滤文件和目录：\n\n- `--include` 指定要包含的文件\n- `--exclude` 指定要排除的文件\n- `--include-priority` 解决模式之间的冲突\n\n要练习，请设置示例目录，尝试运行命令，并查看工具如何动态过滤文件。\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/tutorials/learn_templates.mdx",
    "content": "---\ntitle: 使用 Code2Prompt 学习 Handlebar 模板\ndescription: 了解如何使用和创建自定义 Handlebars 模板进行提示生成。\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\n\n<Card title=\"教程概述\">\n  本教程演示如何使用和创建自定义 Handlebars 模板，在 `code2prompt` CLI\n  中进行提示生成。\n</Card>\n\n---\n\n## 先决条件\n\n确保您已安装 `code2prompt`。如果您尚未安装，请参考 [安装指南](/docs/how_to/install)。\n\n---\n\n## 什么是 Handlebars 模板？\n\n[Handlebars](https://handlebarsjs.com/) 是一个流行的模板引擎，允许您使用占位符创建动态模板。在 `code2prompt` 中，Handlebars 模板用于根据代码库结构和用户定义的变量格式化生成的提示。\n\n## 如何使用 Handlebars 模板？\n\n您可以通过传递 `-t` 或 `--template` 标志，后面跟着模板文件的路径来使用这些模板。例如：\n\n```sh\ncode2prompt path/to/codebase -t templates/document-the-code.hbs\n```\n\n## 模板语法\n\nHandlebars 模板使用简单的语法表示占位符和表达式。您将变量放在双花括号 `{{variable_name}}` 中，以将其包含在生成的提示中。 `code2prompt` 提供了一些默认变量，您可以在模板中使用：\n\n- `absolute_code_path`：代码库的绝对路径。\n- `source_tree`：代码库的源树，包括所有文件和目录。\n- `files`：代码库中的文件列表，包括其路径和内容。\n- `git_diff`：代码库的 git diff（如果适用）。\n- `code`：正在处理的文件的内容。\n- `path`：正在处理的文件的路径。\n\n您还可以使用 Handlebars 助手在模板中执行条件逻辑、循环和其他操作。例如：\n\n```handlebars\n{{#if files}}\n  {{#each files}}\n    文件：\n    {{this.path}}\n    内容：\n    {{this.content}}\n  {{/each}}\n{{else}}\n  未找到文件。\n{{/if}}\n```\n\n---\n\n## 现有模板\n\n`code2prompt` 带有一些内置模板，用于常见用例。您可以在 [`templates`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates) 目录中找到它们。\n\n### [`document-the-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/document-the-code.hbs)\n\n使用此模板生成文档代码的提示。它将在代码库中的所有公共函数、方法、类和模块中添加文档注释。\n\n### [`find-security-vulnerabilities.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/find-security-vulnerabilities.hbs)\n\n使用此模板生成查找代码库中潜在安全漏洞的提示。它将查找常见的安全问题，并提供有关如何修复或缓解它们的建议。\n\n### [`clean-up-code.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/clean-up-code.hbs)\n\n使用此模板生成清理和提高代码质量的提示。它将查找改进可读性、遵守最佳实践、效率、错误处理等机会。\n\n### [`fix-bugs.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/fix-bugs.hbs)\n\n使用此模板生成修复代码库中错误的提示。它将帮助诊断问题、提供修复建议，并使用建议的修复更新代码。\n\n### [`write-github-pull-request.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-pull-request.hbs)\n\n使用此模板通过比较两个分支的 git diff 和 git log，创建 GitHub 拉取请求描述，格式为 Markdown。\n\n### [`write-github-readme.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-github-readme.hbs)\n\n使用此模板为项目生成高质量的 README 文件，适合在 GitHub 上托管。它将分析代码库以了解其目的和功能，并以 Markdown 格式生成 README 内容。\n\n### [`write-git-commit.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/write-git-commit.hbs)\n\n使用此模板从 git 目录中的暂存文件生成 git 提交。它将分析代码库以了解其目的和功能，并以 Markdown 格式生成 git 提交消息内容。\n\n### [`improve-performance.hbs`](https://github.com/mufeedvh/code2prompt/tree/main/crates/code2prompt-core/templates/improve-performance.hbs)\n\n使用此模板生成改进代码库性能的提示。它将查找优化机会、提供具体建议，并使用更改更新代码。\n\n## 用户定义变量\n\n`code2prompt` 支持在 Handlebars 模板中使用用户定义的变量。模板中的任何不属于默认上下文（`absolute_code_path`、`source_tree`、`files`）的变量都将被视为用户定义的变量。\n\n在生成提示期间，`code2prompt` 将提示用户输入这些用户定义的变量的值。这允许根据用户输入进一步自定义生成的提示。\n\n例如，如果您的模板包含 `{{challenge_name}}` 和 `{{challenge_description}}`，则在运行 `code2prompt` 时将被提示输入这些变量的值。\n\n此功能使得创建可重用的模板成为可能，这些模板可以根据用户提供的信息适应不同的场景。\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/vision.mdx",
    "content": "---\ntitle: Code2Prompt 的愿景\ndescription: 了解 Code2Prompt 背后的愿景，以及它如何增强 LLM 与代码的交互。\n---\n\nimport { Card } from \"@astrojs/starlight/components\";\nimport { Aside } from \"@astrojs/starlight/components\";\n\n<Card title=\"目的 🎯\">\n  `code2prompt` 的诞生是为了帮助开发者和 AI 代理更有效地与代码库交互。\n</Card>\n\n## 问题 🚩\n\n大型语言模型（LLMs）已经革新了我们与代码交互的方式。然而，它们在代码生成方面仍然面临着重大挑战：\n\n- **规划和推理**：LLMs 缺乏规划和推理能力，这对于代码生成、重构和调试等任务至关重要。它们往往难以把握全局，目光短浅。\n- **上下文大小**：LLMs 的上下文窗口有限，这限制了它们分析和理解大型代码库的能力。\n- **幻觉**：LLMs 可以生成看似正确但实际上错误或无意义的代码。这种现象被称为幻觉，发生于模型缺乏足够的上下文或对代码库的理解时。\n\n这就是 `code2prompt` 的用武之地。\n\n## 解决方案 ✅\n\n我们相信，通过脚手架技术，规划和推理可以由人类或 AI 代理实现。这些代理需要收集 **高质量的上下文**，即针对特定任务过滤、结构化和格式化后的代码库。\n\n经验法则是：\n\n<Aside type=\"tip\">> 提供尽可能少的上下文，但又足够必要</Aside>\n\n这在实践中很难实现，尤其是对于大型代码库。然而，`code2prompt` 是一个简单的工具，可以帮助开发者和 AI 代理更有效地消化代码库。\n\n它自动遍历代码库，过滤文件，并将它们格式化为 LLMs 可以理解的结构化提示。这样，它有助于减轻规划、推理和幻觉的挑战。\n\n您可以在以下部分了解 `code2prompt` 如何设计以应对这些挑战。\n\n## 架构 ⛩️\n\n<img\n  src=\"/assets/images/architecture.svg\"\n  alt=\"code2prompt 架构\"\n  style=\"width: 75%;\"\n/>\n\n`code2prompt` 以模块化方式设计，便于与各种工作流集成。它可以用作核心库、命令行接口（CLI）、软件开发工具包（SDK）或模型上下文协议（MCP）服务器。\n\n### 核心\n\n`code2prompt` 是一个代码消化工具，简化了为代码分析、生成和其他任务创建 LLM 提示的过程。它通过遍历目录、构建树结构和收集每个文件的信息来工作。核心库可以轻松集成到其他应用程序中。\n\n### CLI\n\n`code2prompt` 命令行接口（CLI）旨在让人类直接从代码库生成提示。生成的提示会自动复制到剪贴板，也可以保存到输出文件。此外，您可以使用 Handlebars 模板自定义提示生成。查看文档中提供的提示！\n\n### SDK\n\n`code2prompt` 软件开发工具包（SDK）为核心库提供了 Python 绑定。这对于希望与代码库无缝交互的 AI 代理或自动化脚本来说是完美的。SDK 托管在 Pypi 上，可以通过 pip 安装。\n\n### MCP\n\n`code2prompt` 也可用作模型上下文协议（MCP）服务器，允许您将其作为本地服务运行。这通过为 LLMs 提供一个工具，使其能够自动收集代码库的良好结构化的上下文，从而增强了 LLMs 的能力。\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content/docs/zh/docs/welcome.mdx",
    "content": "---\ntitle: Code2Prompt 文件\ndescription: Code2prompt 官方文档\ntemplate: splash\nhero:\n  tagline: 几秒钟内将代码转化为人工智能优化的提示信息\n  image:\n    file: ../../../../assets/logo_dark_v0.0.1.svg\n  actions:\n    - text: 开始 🚀\n      link: /zh/docs/tutorials/getting_started\n    - text: 安装 📥\n      link: /zh/docs/how_to/install\n---\n\nimport { Card, CardGrid } from \"@astrojs/starlight/components\";\nimport { LinkCard } from \"@astrojs/starlight/components\";\n\n## Quick Start\n\n<LinkCard\n  title=\"Getting Started 🚀\"\n  href=\"/zh/docs/tutorials/getting_started\"\n/>\n<LinkCard title=\"Installation 📥\" href=\"/zh/docs/how_to/install\" />\n<LinkCard title=\"Learn Filtering 🔍\" href=\"/zh/docs/tutorials/learn_filters\" />\n<LinkCard\n  title=\"Learn Templating 📝\"\n  href=\"/zh/docs/tutorials/learn_templates\"\n/>\n<LinkCard title=\"Vision 🔮\" href=\"/zh/docs/vision\" />\n\n`code2prompt` 是一款强大的代码摄入工具，旨在为代码分析、生成和其他任务生成提示。它通过遍历目录、构建树结构并收集每个文件的信息来工作。\n\n它简化了组合和格式化代码的过程，使得使用大型语言模型（LLM）轻松分析和文档代码或重构代码变得容易。\n\n您可以使用 `code2prompt` 以下列方式：\n\n<CardGrid>\n  <Card title=\"Core\" icon=\"seti:rust\">\n    核心库，极速代码摄入\n  </Card>\n  <Card title=\"CLI\" icon=\"seti:powershell\">\n    专门为人类设计的命令行界面\n  </Card>\n  <Card title=\"SDK\" icon=\"seti:python\">\n    面向 AI 代理和自动化脚本的软件开发工具包\n  </Card>\n  <Card title=\"MCP\" icon=\"seti:folder\">\n    面向强化 LLM 的模型上下文协议服务器\n  </Card>\n</CardGrid>\n\n##\n\n- **生成 LLM 提示**: 快速将整个代码库转换为结构化的 LLM 提示。\n- **Glob 模式过滤**: 使用 glob 模式包含或排除特定文件和目录。\n- **可定制的模板**: 使用 Handlebars 模板定制提示生成。\n- **Token 计数**: 分析 token 使用情况，并针对不同上下文窗口的 LLM 进行优化。\n- **Git 集成**: 在提示中包含 Git diff 和提交消息，以进行代码审查。\n- **尊重 `.gitignore`**: 自动忽略 `.gitignore` 中列出的文件，以简化提示生成。\n\n## 为什么选择 `code2prompt`？\n\n1. **节省时间**:\n\n   - 自动遍历代码库并为 LLM 格式化文件。\n   - 避免重复复制粘贴代码。\n\n2. **提高生产力**:\n\n   - 为代码分析提供结构化和一致的格式。\n   - 帮助更快地识别错误、重构代码和编写文档。\n\n3. **处理大型代码库**:\n\n   - 设计用于与大型代码库无缝协作，尊重 LLM 的上下文限制。\n\n4. **可定制的流程**:\n   - 灵活的选项用于过滤文件、使用模板和生成有针对性的提示。\n\n## 示例用例\n\n- **代码文档**:\n  自动为公共函数、方法和类生成文档。\n\n- **错误检测**:\n  通过使用 LLM 分析代码库来查找潜在的错误和漏洞。\n\n- **重构**:\n  通过生成代码质量改进提示来简化和优化代码。\n\n- **学习和探索**:\n  通过生成摘要和详细分析来理解新的代码库。\n\n- **Git 提交和 PR 描述**:\n  从 Git diff 中生成有意义的提交消息和拉取请求描述。\n\n> 为了您的方便，本页面已自动翻译。请参考英文版本获取原始内容。\n"
  },
  {
    "path": "website/src/content.config.ts",
    "content": "import { defineCollection } from \"astro:content\";\nimport { docsLoader } from \"@astrojs/starlight/loaders\";\nimport { docsSchema } from \"@astrojs/starlight/schema\";\nimport { blogSchema } from \"starlight-blog/schema\";\n\nexport const collections = {\n  docs: defineCollection({\n    loader: docsLoader(),\n    schema: docsSchema({\n      extend: (context) => blogSchema(context),\n    }),\n  }),\n};\n"
  },
  {
    "path": "website/src/layouts/BaseLayout.astro",
    "content": "---\nimport \"/src/styles/global.css\";\nconst title = \"Code2Prompt\";\nconst description =\n  \"Transform your codebase into AI-optimized prompts effortlessly.\";\n---\n\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>{title}</title>\n    <meta name=\"description\" content={description} />\n    <meta property=\"og:image\" content=\"/favicon.svg\" />\n    <link rel=\"icon\" href=\"/favicon.svg\" />\n    <link rel=\"sitemap\" href=\"/sitemap-index.xml\" />\n\n    <link rel=\"stylesheet\" href=\"/assets/css/marquee.css\" />\n    <link rel=\"stylesheet\" href=\"/prism-theme.css\" />\n\n    <script src=\"/assets/js/jquery.min.js\" defer></script>\n    <script src=\"/assets/js/jquery.scrolly.min.js\" defer></script>\n    <script src=\"/assets/js/main.js\" defer></script>\n  </head>\n\n  <body>\n    <slot />\n  </body>\n</html>\n"
  },
  {
    "path": "website/src/layouts/BlogPostLayout.astro",
    "content": "---\nimport BaseLayout from \"./BaseLayout.astro\";\nconst { frontmatter } = Astro.props;\n---\n\n<BaseLayout>\n  <div class=\"py-12 max-w-4xl mx-auto px-4\">\n    <div class=\"mb-8\">\n      <a\n        href=\"/blog\"\n        class=\"text-blue-600 dark:text-blue-400 hover:underline mb-4 inline-block\"\n      >\n        ← Back to all posts\n      </a>\n      <h1\n        class=\"text-3xl md:text-4xl font-bold text-gray-800 dark:text-white mt-4 mb-2\"\n      >\n        {frontmatter.title}\n      </h1>\n\n      {\n        frontmatter.date && (\n          <p class=\"text-gray-500 dark:text-gray-400 mb-4\">\n            {new Date(frontmatter.date).toLocaleDateString(\"en-US\", {\n              year: \"numeric\",\n              month: \"long\",\n              day: \"numeric\",\n            })}\n          </p>\n        )\n      }\n\n      {\n        frontmatter.author && (\n          <p class=\"text-gray-600 dark:text-gray-300 mb-4\">\n            By {frontmatter.author}\n          </p>\n        )\n      }\n\n      {\n        frontmatter.description && (\n          <p class=\"text-xl text-gray-600 dark:text-gray-300 mb-6 font-medium\">\n            {frontmatter.description}\n          </p>\n        )\n      }\n    </div>\n\n    <div class=\"prose prose-lg max-w-none dark:prose-invert\">\n      <slot />\n    </div>\n  </div>\n</BaseLayout>\n"
  },
  {
    "path": "website/src/pages/index.astro",
    "content": "---\nimport BaseLayout from \"../layouts/BaseLayout.astro\";\nimport Header from \"../components/Header.astro\";\nimport SectionZero from \"../components/Section0.astro\";\nimport SectionOne from \"../components/Section1.astro\";\nimport SectionTwo from \"../components/Section2.astro\";\nimport SectionThree from \"../components/Section3.astro\";\nimport SectionFour from \"../components/Section4.astro\";\nimport Footer from \"../components/Footer.astro\";\n---\n\n<BaseLayout>\n  <Header />\n  <SectionZero />\n  <SectionTwo />\n  <SectionOne />\n  <SectionThree />\n  <SectionFour />\n  <Footer />\n</BaseLayout>\n"
  },
  {
    "path": "website/src/pages/robots.txt.ts",
    "content": "import type { APIRoute } from \"astro\";\n\nconst getRobotsTxt = (sitemapURL: URL) => `\nUser-agent: *\nAllow: /\n\nSitemap: ${sitemapURL.href}\n`;\n\nexport const GET: APIRoute = ({ site }) => {\n  const sitemapURL = new URL(\"sitemap-index.xml\", site);\n  return new Response(getRobotsTxt(sitemapURL));\n};\n"
  },
  {
    "path": "website/src/styles/global.css",
    "content": "@import \"tailwindcss\";\n\n/* Basic typography improvements */\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  @apply font-bold tracking-tight;\n}\n\nh1 {\n  @apply text-4xl md:text-5xl mb-6;\n}\n\nh2 {\n  @apply text-3xl md:text-4xl mb-4;\n}\n\np {\n  @apply leading-relaxed;\n}\n"
  },
  {
    "path": "website/tsconfig.json",
    "content": "{\n  \"extends\": \"astro/tsconfigs/strict\",\n  \"include\": [\n    \".astro/types.d.ts\",\n    \"**/*\"\n  ],\n  \"exclude\": [\n    \"dist\"\n  ],\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"react\"\n  }\n}"
  }
]