Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 49 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

[![Tests](https://github.com/wr1/cfold/actions/workflows/python-app.yml/badge.svg)](https://github.com/wr1/cfold/actions/workflows/python-app.yml)

`cfold` is a command-line tool that helps you prepare codebases for interaction with Large Language Models (LLMs). It can "fold" a directory of code into a single text file and "unfold" a modified version back into a directory structure.
`cfold` is a command-line tool that helps you prepare codebases for interaction with Large Language Models (LLMs). It can "fold" a directory or specific files into a single text file and "unfold" a modified version back into a directory structure. All file paths are handled relative to the current working directory (CWD).

## Installation

```bash
pip install cfold
pip install .
```

Or locally with Poetry:
Or install locally with Poetry:

```bash
poetry install
Expand All @@ -21,25 +21,63 @@ python -m pip install .

### Folding a codebase

Fold specific files or the current directory into a single text file:

```bash
cfold fold <directory> -o <output_file>
cfold fold [files...] -o <output_file> [--prompt <prompt_file>]
```

- `<directory>`: The directory to fold (defaults to current directory).
- `[files...]`: Specific files to fold (optional; if omitted, folds the entire current directory).
- `-o <output_file>`: Output file (default: `codefold.txt`).
- Supports `.foldignore` for exclusions.
- `--prompt <prompt_file>`: Optional file to append as a prompt in the output.
- Supports `.foldignore` for excluding files when folding a directory.

Example:

```bash
cfold fold src/main.py docs/index.md -o folded.txt --prompt prompt.txt
```

### Unfolding a codebase

Unfold a modified fold file back into a directory structure:

```bash
cfold unfold <fold_file> [-i <original_dir>] [-o <output_dir>]
```

- `<fold_file>`: The modified fold file to unfold.
- `-i <original_dir>`: Original directory to merge with (optional).
- `-o <output_dir>`: Output directory (default: CWD).

Example:

```bash
cfold unfold folded.txt -o output_dir
```

### Initializing a project template

Create a template file with LLM instructions for project setup:

```bash
cfold unfold <fold_file> -d <output_directory>
cfold init [<output_file>] [--custom <instruction>]
```

- `<fold_file>`: The modified fold file.
- `-d <output_directory>`: Output directory (default: CWD).
- `<output_file>`: Output file (default: `start.txt`).
- `--custom <instruction>`: Custom instruction for the LLM.

Example:

```bash
cfold init start.txt --custom "Build a Python CLI tool."
```

## Fold File Format

- Starts with LLM instructions.
- Files are in `# --- File: <path> ---` sections.
- Modify with full content, delete with `# DELETE`, add with new sections.
- Files are in `# --- File: <path> ---` sections with paths relative to CWD.
- Modify files by providing full content.
- Delete files with `# DELETE`.
- Add new files with new `# --- File: <path> ---` sections.
- Markdown files have `MD:` prefix per line (stripped on unfold).
5 changes: 3 additions & 2 deletions cfold/cfold.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def fold(files=None, output="codefold.txt", prompt_file=None):
files = [
os.path.abspath(f)
for f in files
if os.path.isfile(f) and should_include_file(f)
if os.path.isfile(f) # and should_include_file(f)
]

if not files:
Expand Down Expand Up @@ -63,7 +63,8 @@ def unfold(fold_file, original_dir=None, output_dir=None):
output_dir = os.path.abspath(output_dir or cwd)

with open(fold_file, "r", encoding="utf-8") as infile:
content = infile.read()
# hack to deal with grok sometimes not rendering as code block
content = infile.read().replace("CFOLD: ", "").replace("CFOLD:", "")
sections = re.split(r"(# --- File: .+? ---)\n", content)[1:]
if len(sections) % 2 != 0:
print("Warning: Malformed fold file - odd number of sections")
Expand Down
6 changes: 4 additions & 2 deletions cfold/resources/instructions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# - Always preserve '# --- File: path ---' format for file sections.
# - Supports .foldignore file with gitignore-style patterns to exclude files during folding (directory mode).
# - Paths are relative to the current working directory (CWD) by default.
# - Can you preped 'CFOLD: ' to every line in the output? So for markdown a line starts 'CFOLD: MD: '
# Refactoring Guidelines:
# - Prefer organizing code into subdirectories based on functionality (e.g., 'src/utils/', 'src/core/', 'src/cli/').
# - Split functionality into small, focused files (e.g., one function or class per file where practical, typically 50-200 lines).
Expand All @@ -23,5 +24,6 @@
# - '# --- File: src/core/main.py ---' with reduced content.
# - '# --- File: src/utils/helper.py ---' with extracted functions.
# - Aim for modularity: Each file should have a single responsibility, making it easier to modify or replace individually.
# - Reply using a single txt file for implementing changes to multiple files using 'cfold unfold <singlefile.txt>'.
#- Assume that when a test is commented out, the test didn’t pass; do not just reenable the test, but attempt to fix it.
# - Reply using a single plain text code block for implementing changes to multiple files using 'cfold unfold <singlefile.txt>'.
# - No need to include LLM instructions in the output.
# Please return the cfold formatted output directly in the chat without opening Grok Studio or the canvas panel.
12 changes: 11 additions & 1 deletion cfold/utils/foldignore.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import fnmatch
import os


def load_foldignore(directory):
"""Load and parse .foldignore file if it exists."""
print(f"Loading .foldignore from {directory}")
ignore_file = Path(directory) / ".foldignore"
ignore_patterns = []
if ignore_file.exists():
Expand All @@ -14,10 +16,18 @@ def load_foldignore(directory):
ignore_patterns.append(line)
return ignore_patterns


def should_include_file(filepath, ignore_patterns=None, root_dir=None):
"""Check if a file should be included based on extension, exclusion rules, and .foldignore patterns."""
INCLUDED_EXTENSIONS = {".py", ".toml", ".md", ".yml", ".yaml"}
EXCLUDED_DIRS = {".pytest_cache", "__pycache__", "build", "dist", ".egg-info", "venv"}
EXCLUDED_DIRS = {
".pytest_cache",
"__pycache__",
"build",
"dist",
".egg-info",
"venv",
}
EXCLUDED_FILES = {".pyc"}

path = Path(filepath)
Expand Down
28 changes: 20 additions & 8 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
# API Reference

This page documents the Python API for the `cfold` module. All file paths are relative to the current working directory (CWD).

## `cfold.fold(files=None, output="codefold.txt", prompt_file=None)`

- `files`: Files or directory to fold (default: CWD).
- `output`: Output file (default: `codefold.txt`).
- `prompt_file`: Optional prompt file.
Fold specific files or the current directory into a single text file.

- `files`: List of file paths to fold (default: `None`, folds the current directory).
- `output`: Output file path (default: `"codefold.txt"`).
- `prompt_file`: Path to an optional prompt file to append (default: `None`).

When `files` is `None`, the function folds all valid files in the current directory, respecting `.foldignore` patterns.

## `cfold.unfold(fold_file, original_dir=None, output_dir=None)`

- `fold_file`: The fold file to unfold.
- `original_dir`: Original directory to merge (optional).
- `output_dir`: Output directory (default: CWD).
Unfold a modified fold file into a directory structure.

- `fold_file`: Path to the fold file to unfold.
- `original_dir`: Path to the original directory to merge with (default: `None`).
- `output_dir`: Output directory path (default: CWD).

Supports full content rewrites, deletions (`# DELETE`), and new file creation.

## `cfold.init(output="start.txt", custom_instruction="")`

- `output`: Output file (default: `start.txt`).
- `custom_instruction`: Custom project purpose.
Initialize a project template with LLM instructions.

- `output`: Output file path (default: `"start.txt"`).
- `custom_instruction`: Custom instruction for the LLM (default: `""`).
36 changes: 31 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
# cfold Documentation

`cfold` is a command-line tool for folding Python codebases into a single text file and unfolding modified versions back into a directory, ideal for LLM interaction.
`cfold` is a command-line tool for folding Python codebases into a single text file and unfolding modified versions back into a directory, ideal for LLM interaction. All file paths are handled relative to the current working directory (CWD).

## Features

- **Fold**: Combine code into a `.txt` file.
- **Unfold**: Apply changes back to a directory.
- **Init**: Generate a project template.
- Supports Poetry, GitHub CI, and MkDocs.
- **Fold**: Combine specific files or the current directory into a `.txt` file, with optional prompt inclusion.
- **Unfold**: Apply changes (modifications, deletions, or new files) back to a directory.
- **Init**: Generate a project template with custom instructions.
- Supports Poetry, GitHub CI, MkDocs, and `.foldignore` for file exclusions.

## Quick Start

Install:

```bash
pip install cfold
```

Fold a directory:

```bash
cfold fold -o folded.txt
```

Unfold changes:

```bash
cfold unfold folded.txt -o output_dir
```

Initialize a project:

```bash
cfold init start.txt --custom "Build a Python CLI tool."
```
88 changes: 81 additions & 7 deletions docs/usage.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,113 @@
# Usage

This guide explains how to use the `cfold` command-line tool to fold and unfold Python codebases for LLM interaction. All file paths are relative to the current working directory (CWD).

## Installation

Install `cfold` using pip:

```bash
pip install cfold
```

Or with Poetry:
If you encounter pip errors (e.g., `html5lib` issues), reinstall pip:

```bash
python -m ensurepip --upgrade
python -m pip install --force-reinstall pip
pip install cfold
```

Or install locally with Poetry:

```bash
poetry install
python -m pip install .
```

## Commands

### `cfold init`

Initialize a project template with LLM instructions:

```bash
cfold init [<output_file>] [--custom <instruction>]
```

- `<output_file>`: Output file (default: `start.txt`).
- `--custom <instruction>`: Custom instruction for the LLM (e.g., project purpose).

Example:

```bash
cfold init start.txt --custom "Build a tool."
cfold init start.txt --custom "Build a tool for code folding."
```

### `cfold fold`

Fold specific files or the current directory into a single text file:

```bash
cfold fold -o folded.txt
cfold fold [files...] [--output <output_file>] [--prompt <prompt_file>]
```

Options:

```bash
usage: cfold fold [-h] [--output OUTPUT] [--prompt PROMPT] [files ...]

positional arguments:
files Files to fold (optional; if omitted, folds the current directory)

options:
-h, --help show this help message and exit
--output OUTPUT, -o OUTPUT
Output file (e.g., folded.txt; default: codefold.txt)
--prompt PROMPT, -p PROMPT
Optional file containing a prompt to append to the output
```

Example:

```bash
cfold fold src/main.py -o folded.txt --prompt prompt.txt
```

### `cfold unfold`

Unfold a modified fold file into a directory structure:

```bash
cfold unfold <fold_file> [--original-dir <original_dir>] [--output-dir <output_dir>]
```

Options:

```bash
usage: cfold unfold [-h] [--original-dir ORIGINAL_DIR] [--output-dir OUTPUT_DIR] foldfile

positional arguments:
foldfile File to unfold (e.g., folded.txt)

options:
-h, --help show this help message and exit
--original-dir ORIGINAL_DIR, -i ORIGINAL_DIR
Original project directory to merge with
--output-dir OUTPUT_DIR, -o OUTPUT_DIR
Output directory (defaults to current directory)
```

Example:

```bash
cfold unfold folded.txt -o output_dir
cfold unfold folded.txt -i original_project -o output_dir
```

## Refactoring

- Modify with full content.
- Delete with `# DELETE`.
- Add new files with `# --- File: path ---`.
- **Modify**: Provide the full content under `# --- File: <path> ---`.
- **Delete**: Use `# DELETE` under the file's header.
- **Add**: Create a new `# --- File: <path> ---` section with full content.
- **Move/Rename**: Delete the old file with `# DELETE` and add a new file section with the updated path and content.
- Paths are relative to the CWD.
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tool.poetry]
authors = ["Your Name <your.email@example.com>"]
authors = ["wr1 <8971152+wr1@users.noreply.github.com>"]
description = "A command line tool to fold and unfold Python projects for LLM interaction"
license = "MIT"
name = "cfold"
Expand All @@ -8,12 +8,12 @@ readme = "README.md"
version = "0.1.0"

[tool.poetry.dependencies]
python = "^3.11"
pytest-cov = ">5.0.0"
python = "^3.11"

[tool.poetry.group.dev.dependencies]
pytest = "^7.1.3"
flake8 = "^6.0.0"
pytest = "^7.1.3"

[tool.poetry.scripts]
cfold = "cfold.cfold:main"
Expand Down