Skip to content

Bundler

codebrief.tools.bundler

Bundle Generator for CodeBrief.

This module provides functions to create comprehensive context bundles by orchestrating calls to multiple CodeBrief tools. It aggregates outputs from tree generation, Git context, dependency listing, and file flattening into a single, well-structured Markdown document suitable for LLM consumption.

Core functionalities: - Orchestrate multiple tool outputs into a single bundle - Support configurable inclusion/exclusion of different context sections - Handle multiple flatten specifications for different parts of the project - Generate structured Markdown with clear sectioning and navigation - Graceful error handling when individual tools encounter issues - Support for both file output and console display

Functions

generate_tree_content(project_root, config_global_excludes)

Generate directory tree content for the bundle.

Parameters:

NameTypeDescriptionDefault
project_rootPath

The root directory of the project

required
config_global_excludesList[str]

Global exclude patterns from config

required

Returns:

TypeDescription
str

Formatted tree content as string

Source code in src/codebrief/tools/bundler.py
def generate_tree_content(
    project_root: Path,
    config_global_excludes: List[str],
) -> str:
    """
    Generate directory tree content for the bundle.

    Args:
        project_root: The root directory of the project
        config_global_excludes: Global exclude patterns from config

    Returns:
        Formatted tree content as string
    """
    try:
        # Use the updated generate_and_output_tree function that returns a string
        tree_result = tree_generator.generate_and_output_tree(
            root_dir=project_root,
            output_file_path=None,  # This will return the tree string
            ignore_list=config_global_excludes,
        )

        return (
            f"# Directory Tree\n\n{tree_result}"
            if tree_result
            else "# Directory Tree\n\nError generating directory tree.\n"
        )

    except Exception as e:
        return f"# Directory Tree\n\nError generating directory tree: {e}\n"

generate_git_content(project_root, log_count=5, full_diff=False, diff_options=None)

Generate Git context content for the bundle.

Parameters:

NameTypeDescriptionDefault
project_rootPath

The root directory of the project

required
log_countint

Number of recent commits to include

5
full_diffbool

Whether to include full diff output

False
diff_optionsOptional[str]

Optional diff options string

None

Returns:

TypeDescription
str

Formatted Git context as string

Source code in src/codebrief/tools/bundler.py
def generate_git_content(
    project_root: Path,
    log_count: int = 5,
    full_diff: bool = False,
    diff_options: Optional[str] = None,
) -> str:
    """
    Generate Git context content for the bundle.

    Args:
        project_root: The root directory of the project
        log_count: Number of recent commits to include
        full_diff: Whether to include full diff output
        diff_options: Optional diff options string

    Returns:
        Formatted Git context as string
    """
    try:
        return git_provider.get_git_context(
            project_root=project_root,
            diff_options=diff_options,
            log_count=log_count,
            full_diff=full_diff,
        )
    except Exception as e:
        return f"# Git Context\n\nError generating Git context: {e}\n"

generate_deps_content(project_root)

Generate dependency list content for the bundle.

Parameters:

NameTypeDescriptionDefault
project_rootPath

The root directory of the project

required

Returns:

TypeDescription
str

Formatted dependency content as string

Source code in src/codebrief/tools/bundler.py
def generate_deps_content(project_root: Path) -> str:
    """
    Generate dependency list content for the bundle.

    Args:
        project_root: The root directory of the project

    Returns:
        Formatted dependency content as string
    """
    try:
        # Use the updated list_dependencies function that returns a string
        deps_result = dependency_lister.list_dependencies(
            project_path=project_root,
            output_file=None,  # This will return the markdown string
        )

        return (
            deps_result
            if deps_result
            else "# Project Dependencies\n\nNo dependencies found.\n"
        )

    except Exception as e:
        return f"# Project Dependencies\n\nError generating dependency list: {e}\n"

generate_flatten_content(project_root, flatten_path, config_global_excludes)

Generate flattened file content for a specific path.

Parameters:

NameTypeDescriptionDefault
project_rootPath

The root directory of the project

required
flatten_pathPath

The specific path to flatten

required
config_global_excludesList[str]

Global exclude patterns from config

required

Returns:

TypeDescription
str

Formatted flattened content as string

Source code in src/codebrief/tools/bundler.py
def generate_flatten_content(
    project_root: Path,
    flatten_path: Path,
    config_global_excludes: List[str],
) -> str:
    """
    Generate flattened file content for a specific path.

    Args:
        project_root: The root directory of the project
        flatten_path: The specific path to flatten
        config_global_excludes: Global exclude patterns from config

    Returns:
        Formatted flattened content as string
    """
    try:
        # Use the updated flatten_code_logic function that returns a string
        flatten_result = flattener.flatten_code_logic(
            root_dir=flatten_path,
            output_file_path=None,  # This will return the content string
            include_patterns=None,  # Use defaults
            exclude_patterns=None,  # Use defaults + config
            config_global_excludes=config_global_excludes,
        )

        if not flatten_result or not flatten_result.strip():
            return f"# Files: {flatten_path.relative_to(project_root) if flatten_path != project_root else 'Project Root'}\n\nNo files found to flatten in this path.\n"

        # Add a header for this flatten section
        relative_path = (
            flatten_path.relative_to(project_root)
            if flatten_path != project_root
            else "Project Root"
        )
        header = f"# Files: {relative_path}\n\n"
        return header + flatten_result.strip()

    except Exception as e:
        relative_path = (
            flatten_path.relative_to(project_root)
            if flatten_path != project_root
            else "Project Root"
        )
        return f"# Files: {relative_path}\n\nError flattening files: {e}\n"

create_bundle(project_root, output_file_path=None, include_tree=True, include_git=True, include_deps=True, flatten_paths=None, git_log_count=5, git_full_diff=False, git_diff_options=None)

Create a comprehensive context bundle by aggregating multiple tool outputs.

This function orchestrates calls to various CodeBrief tools and combines their outputs into a single, well-structured Markdown document. The bundle includes a table of contents and clear sectioning for easy navigation.

Parameters:

NameTypeDescriptionDefault
project_rootPath

The root directory of the project to bundle

required
output_file_pathOptional[Path]

Optional path to save the bundle. If None, returns string

None
include_treebool

Whether to include directory tree (default: True)

True
include_gitbool

Whether to include Git context (default: True)

True
include_depsbool

Whether to include dependency information (default: True)

True
flatten_pathsOptional[List[Path]]

Optional list of paths to flatten and include

None
git_log_countint

Number of recent commits to include in Git context

5
git_full_diffbool

Whether to include full diff in Git context

False
git_diff_optionsOptional[str]

Optional Git diff options string

None

Returns:

TypeDescription
Optional[str]

Bundle content as string if no output file specified, None otherwise.

Raises:

TypeDescription
Exit

If project_root is invalid or critical errors occur

Source code in src/codebrief/tools/bundler.py
def create_bundle(
    project_root: Path,
    output_file_path: Optional[Path] = None,
    include_tree: bool = True,
    include_git: bool = True,
    include_deps: bool = True,
    flatten_paths: Optional[List[Path]] = None,
    git_log_count: int = 5,
    git_full_diff: bool = False,
    git_diff_options: Optional[str] = None,
) -> Optional[str]:
    """
    Create a comprehensive context bundle by aggregating multiple tool outputs.

    This function orchestrates calls to various CodeBrief tools and combines
    their outputs into a single, well-structured Markdown document. The bundle
    includes a table of contents and clear sectioning for easy navigation.

    Args:
        project_root: The root directory of the project to bundle
        output_file_path: Optional path to save the bundle. If None, returns string
        include_tree: Whether to include directory tree (default: True)
        include_git: Whether to include Git context (default: True)
        include_deps: Whether to include dependency information (default: True)
        flatten_paths: Optional list of paths to flatten and include
        git_log_count: Number of recent commits to include in Git context
        git_full_diff: Whether to include full diff in Git context
        git_diff_options: Optional Git diff options string

    Returns:
        Bundle content as string if no output file specified, None otherwise.

    Raises:
        typer.Exit: If project_root is invalid or critical errors occur
    """
    if not project_root.exists():
        console.print(
            f"[bold red]Error: Project path '{project_root}' does not exist.[/bold red]"
        )
        raise typer.Exit(code=1)

    if not project_root.is_dir():
        console.print(
            f"[bold red]Error: Project path '{project_root}' is not a directory.[/bold red]"
        )
        raise typer.Exit(code=1)

    # Load configuration
    config = config_manager.load_config(project_root)
    config_global_excludes = config.get("global_exclude_patterns", [])
    if not isinstance(config_global_excludes, list):
        warnings.warn(
            "Config Warning: 'global_exclude_patterns' should be a list. Using empty list.",
            UserWarning,
            stacklevel=2,
        )
        config_global_excludes = []

    if output_file_path:
        console.print(
            f"[dim]Generating context bundle for '{project_root.resolve()}'...[/dim]"
        )

    # Start building the bundle
    bundle_sections = []

    # Header and project info
    project_name = project_root.name if project_root.name else "Unknown Project"
    bundle_sections.append(f"# CodeBrief Bundle: {project_name}")
    bundle_sections.append("")
    bundle_sections.append(f"**Project Root:** `{project_root.resolve()}`")
    bundle_sections.append("")

    # Table of Contents
    toc_items = []
    if include_tree:
        toc_items.append("- [Directory Tree](#directory-tree)")
    if include_git:
        toc_items.append("- [Git Context](#git-context)")
    if include_deps:
        toc_items.append("- [Project Dependencies](#project-dependencies)")
    if flatten_paths:
        for flatten_path in flatten_paths:
            relative_path = (
                flatten_path.relative_to(project_root)
                if flatten_path != project_root
                else "project-root"
            )
            relative_path_str = str(relative_path)
            toc_items.append(
                f"- [Files: {relative_path}](#files-{relative_path_str.replace('/', '-').replace('_', '-').lower()})"
            )

    if toc_items:
        bundle_sections.append("## Table of Contents")
        bundle_sections.append("")
        bundle_sections.extend(toc_items)
        bundle_sections.append("")
        bundle_sections.append("---")
        bundle_sections.append("")

    # Generate and add each section
    section_count = 0

    # 1. Directory Tree
    if include_tree:
        if output_file_path:
            console.print("[dim]  - Generating directory tree...[/dim]")

        tree_content = generate_tree_content(project_root, config_global_excludes)

        bundle_sections.append("## Directory Tree")
        bundle_sections.append("")
        bundle_sections.append("```")
        bundle_sections.append(tree_content)
        bundle_sections.append("```")
        bundle_sections.append("")
        section_count += 1

    # 2. Git Context
    if include_git:
        if output_file_path:
            console.print("[dim]  - Generating Git context...[/dim]")

        git_content = generate_git_content(
            project_root,
            log_count=git_log_count,
            full_diff=git_full_diff,
            diff_options=git_diff_options,
        )

        # Remove the main header from git content since we'll add our own
        git_lines = git_content.split("\n")
        if git_lines and git_lines[0].startswith("# Git Context"):
            git_lines = git_lines[1:]  # Remove the first line
            if git_lines and git_lines[0] == "":
                git_lines = git_lines[1:]  # Remove empty line after header

        bundle_sections.append("## Git Context")
        bundle_sections.append("")
        bundle_sections.extend(git_lines)
        bundle_sections.append("")
        section_count += 1

    # 3. Dependencies
    if include_deps:
        if output_file_path:
            console.print("[dim]  - Generating dependency information...[/dim]")

        deps_content = generate_deps_content(project_root)

        # Remove the main header from deps content since we'll add our own
        deps_lines = deps_content.split("\n")
        if deps_lines and deps_lines[0].startswith("# Project Dependencies"):
            deps_lines = deps_lines[1:]  # Remove the first line
            if deps_lines and deps_lines[0] == "":
                deps_lines = deps_lines[1:]  # Remove empty line after header

        bundle_sections.append("## Project Dependencies")
        bundle_sections.append("")
        bundle_sections.extend(deps_lines)
        bundle_sections.append("")
        section_count += 1

    # 4. Flattened Files
    if flatten_paths:
        for flatten_path in flatten_paths:
            if not flatten_path.exists():
                console.print(
                    f"[yellow]Warning: Flatten path '{flatten_path}' does not exist. Skipping.[/yellow]"
                )
                continue

            if output_file_path:
                relative_path = (
                    flatten_path.relative_to(project_root)
                    if flatten_path != project_root
                    else "project root"
                )
                console.print(
                    f"[dim]  - Flattening files in '{relative_path}'...[/dim]"
                )

            flatten_content = generate_flatten_content(
                project_root, flatten_path, config_global_excludes
            )

            # Remove the main header from flatten content since we'll add our own
            flatten_lines = flatten_content.split("\n")
            if flatten_lines and flatten_lines[0].startswith("# Files:"):
                section_title = flatten_lines[0][2:]  # Remove "# " prefix
                flatten_lines = flatten_lines[1:]  # Remove the first line
                if flatten_lines and flatten_lines[0] == "":
                    flatten_lines = flatten_lines[1:]  # Remove empty line after header
            else:
                relative_path = (
                    flatten_path.relative_to(project_root)
                    if flatten_path != project_root
                    else "Project Root"
                )
                section_title = f"Files: {relative_path}"

            bundle_sections.append(f"## {section_title}")
            bundle_sections.append("")
            bundle_sections.extend(flatten_lines)
            bundle_sections.append("")
            section_count += 1

    # Footer
    bundle_sections.append("---")
    bundle_sections.append("")
    bundle_sections.append(
        f"*Bundle generated by CodeBrief - {section_count} sections included*"
    )

    # Combine all sections
    bundle_content = "\n".join(bundle_sections)

    # Output the bundle
    if output_file_path:
        try:
            output_file_path.parent.mkdir(parents=True, exist_ok=True)
            with output_file_path.open("w", encoding="utf-8") as f:
                f.write(bundle_content)
            console.print(
                f"[green]Successfully created context bundle: '{output_file_path.resolve()}'[/green]"
            )
            console.print(f"[dim]Bundle contains {section_count} sections[/dim]")
        except OSError as e:
            console.print(
                f"[bold red]Error writing bundle to '{output_file_path}': {e}[/bold red]"
            )
            raise typer.Exit(code=1) from e
        return None
    else:
        # Return the bundle content for the main command to handle
        return bundle_content

Modules

Overview

The bundler module provides comprehensive context aggregation functionality, combining multiple codebrief tools into structured, well-organized bundles. It orchestrates the execution of tree generation, Git context extraction, dependency analysis, and file flattening to create comprehensive project context documents.

Key Features

  • Multi-Tool Integration: Combines tree, git-info, deps, and flatten tools
  • Flexible Configuration: Selective inclusion/exclusion of context sections
  • Structured Output: Well-organized Markdown with table of contents
  • Path Flexibility: Support for multiple flatten paths and custom configurations
  • Error Resilience: Graceful handling of tool failures and missing components

Functions

create_bundle

Create a comprehensive context bundle combining multiple tools.

Parameters: - project_root (Path): Root directory of the project - output_file_path (Optional[Path]): Output file path (None for stdout) - exclude_tree (bool, optional): Skip directory tree section (default: False) - exclude_git (bool, optional): Skip Git context section (default: False) - exclude_deps (bool, optional): Skip dependencies section (default: False) - exclude_files (bool, optional): Skip flattened files section (default: False) - flatten_paths (Optional[List[Path]]): Specific paths to flatten (default: [project_root]) - git_log_count (int, optional): Number of Git commits to include (default: 10) - git_full_diff (bool, optional): Include full Git diff (default: False) - git_diff_options (Optional[str]): Custom Git diff options (default: None)

Returns: - None (outputs to file or stdout)

Raises: - FileNotFoundError: If project_root doesn't exist - PermissionError: If output file cannot be written - Various exceptions from underlying tools (handled gracefully)

Helper Functions

generate_tree_content

Generate directory tree content for the bundle.

Parameters: - project_root (Path): Root directory - config_global_excludes (List[str]): Global exclude patterns

Returns: - str: Formatted tree content

generate_git_content

Generate Git context content for the bundle.

Parameters: - project_root (Path): Root directory - log_count (int): Number of commits - full_diff (bool): Include full diff - diff_options (Optional[str]): Custom diff options

Returns: - str: Formatted Git content

generate_deps_content

Generate dependencies content for the bundle.

Parameters: - project_root (Path): Root directory

Returns: - str: Formatted dependencies content

generate_flatten_content

Generate flattened files content for the bundle.

Parameters: - project_root (Path): Root directory - flatten_paths (List[Path]): Paths to flatten - config_global_excludes (List[str]): Global exclude patterns

Returns: - str: Formatted flattened content

Usage Examples

Basic Usage

from pathlib import Path
from codebrief.tools.bundler import create_bundle

# Create complete bundle
create_bundle(
    project_root=Path("."),
    output_file_path=Path("project-bundle.md")
)

Advanced Configuration

# Create selective bundle for code review
create_bundle(
    project_root=Path("."),
    output_file_path=Path("review-bundle.md"),
    exclude_deps=True,
    flatten_paths=[Path("src"), Path("tests")],
    git_log_count=5,
    git_full_diff=True
)

CLI Integration

# This is how the CLI command uses the function
from codebrief.tools.bundler import create_bundle

def bundle_command(
    root_dir: Path,
    output: Optional[Path] = None,
    exclude_tree: bool = False,
    exclude_git: bool = False,
    exclude_deps: bool = False,
    exclude_files: bool = False,
    flatten: Optional[List[Path]] = None,
    git_log_count: int = 10,
    git_full_diff: bool = False,
    git_diff_options: Optional[str] = None,
):
    create_bundle(
        project_root=root_dir,
        output_file_path=output,
        exclude_tree=exclude_tree,
        exclude_git=exclude_git,
        exclude_deps=exclude_deps,
        exclude_files=exclude_files,
        flatten_paths=flatten or [root_dir],
        git_log_count=git_log_count,
        git_full_diff=git_full_diff,
        git_diff_options=git_diff_options,
    )

Output Structure

The bundler creates well-organized Markdown documents:

# codebrief Bundle

## Table of Contents
- [Directory Tree](#directory-tree)
- [Git Context](#git-context)
- [Dependencies](#dependencies)
- [Files: src/codebrief/tools](#files-srccodebrieftools)

## Directory Tree
πŸ“ my-project/
β”œβ”€β”€ πŸ“„ README.md
β”œβ”€β”€ πŸ“ src/
β”‚   └── πŸ“ codebrief/
└── πŸ“ tests/

## Git Context
# Git Context

## Repository Information
- **Current Branch:** main
- **Repository Status:** Clean working directory

## Recent Commits (Last 10)
[Git commit history...]

## Dependencies
# Dependencies

## Python Dependencies (pyproject.toml)
- typer: ^0.9.0
- rich: ^13.0.0

## Files: src/codebrief/tools
# --- File: src/codebrief/tools/bundler.py ---
[File contents...]

# --- File: src/codebrief/tools/git_provider.py ---
[File contents...]

Bundle Sections

Directory Tree Section

  • Uses the tree_generator module
  • Respects .llmignore and configuration patterns
  • Provides visual project structure overview

Git Context Section

  • Uses the git_provider module
  • Includes branch info, status, and commit history
  • Optional full diff and custom diff options

Dependencies Section

  • Uses the dependency_lister module
  • Analyzes Python and Node.js dependencies
  • Supports multiple dependency file formats

Files Section

  • Uses the flattener module
  • Aggregates file contents from specified paths
  • Intelligent file filtering and binary file handling

Error Handling

The bundler handles various error scenarios:

Missing Tools

# If a tool fails, the section is skipped with a note
create_bundle(Path("."))
# Output includes: "## Git Context\n*Git context unavailable*"

Invalid Paths

# Graceful handling of non-existent flatten paths
create_bundle(
    project_root=Path("."),
    flatten_paths=[Path("nonexistent")]
)
# Skips invalid paths, continues with valid ones

Permission Issues

# Clear error messages for file access issues
create_bundle(
    project_root=Path("."),
    output_file_path=Path("/etc/bundle.md")
)
# Raises PermissionError with helpful message

Configuration Integration

Works seamlessly with codebrief's configuration system:

[tool.codebrief]
default_output_filename_bundle = "project-bundle.md"
global_exclude_patterns = ["*.pyc", "__pycache__/", ".venv/"]

Performance Considerations

  • Parallel Processing: Tools run independently where possible
  • Memory Efficient: Streams output to files for large projects
  • Configurable Scope: Selective inclusion reduces processing time
  • Caching: Reuses configuration and ignore patterns across tools

Testing

The module includes comprehensive test coverage:

  • 7 test cases covering core functionality
  • Helper function testing for individual components
  • Integration testing with real project structures
  • Error scenario testing for robustness
  • Configuration integration testing

Dependencies

  • pathlib: For path handling
  • typing: For type annotations
  • io.StringIO: For output capture and manipulation
  • codebrief.tools.tree_generator: Directory tree generation
  • codebrief.tools.git_provider: Git context extraction
  • codebrief.tools.dependency_lister: Dependency analysis
  • codebrief.tools.flattener: File content aggregation
  • codebrief.utils.config_manager: Configuration management

Best Practices

Bundle Composition

# For code review
create_bundle(
    exclude_deps=True,
    flatten_paths=[Path("src"), Path("tests")],
    git_log_count=5
)

# For documentation
create_bundle(
    exclude_git=True,
    flatten_paths=[Path("docs"), Path("README.md")]
)

# For debugging
create_bundle(
    git_full_diff=True,
    flatten_paths=[Path("src")]
)

Performance Optimization

# Large projects - selective flattening
create_bundle(
    flatten_paths=[Path("src/core")],  # Specific paths only
    exclude_deps=True,  # Skip if not needed
    git_log_count=3     # Limit Git history
)