Generate LLM-friendly markdown from your project files.
project-context
is a Python tool that generates LLM-friendly markdown documentation of your entire project structure and contents. It creates a single markdown file containing both a visual tree representation of your project and the actual content of your source files, making it easy to share your codebase context with AI assistants.
- Intelligent file filtering: Automatically respects
.gitignore
files and Git tracking status - Flexible inclusion/exclusion: Use regex patterns to precisely control which files are included or excluded
- Customizable output: Support for custom Jinja2 templates to format the output
- Smart content selection: Automatically includes the most common file type in your project for markdown output
- Pre-commit integration: Can automatically generate context files on every commit
Let's say you have a basic Python package with this structure:
hello-world-pkg/
├── .gitignore
├── README.md
├── pyproject.toml
└── src/
└── hello_world/
├── __init__.py
└── main.py
Where src/hello_world/main.py
contains:
def main():
print("Hello, World!")
if __name__ == "__main__":
main()
Running project-context
from the project root would generate the following output to stdout:
# hello-world-pkg
## Project Structure
hello-world-pkg/
├── .gitignore
├── README.md
├── pyproject.toml
└── src/
└── hello_world/
├── __init__.py
└── main.py
## Project Contents
### src/hello_world/main.py
```
def main():
print("Hello, World!")
if __name__ == "__main__":
main()
```
This single markdown file now contains your entire project context in a format that's perfect for pasting into ChatGPT, Claude, Gemini, or any other LLM when you need help with your code!
uv tool install project-context
Evoke using uvx project-context
pip install project-context
Evoke using project-context
By default, all files tracked by Git are included in the directory tree.
Generate context for the current directory and write it to stdout:
project-context
Save output to a file:
project-context -o CONTEXT.md
By default, all files that are tracked by Git are included in the directory tree in the Project structure
section, and only the most common file type is included as part of the Project contents
section. In the above example, the only file included was src/hello_world/main.py
since .py
is the most common file type in this project (__init__.py
was excluded since it was empty).
Flag | Description | Example |
---|---|---|
--root, -r |
Root directory to use. Defaults to the current working directory. | -r my-repo |
--exclude, -e |
Regex patterns to exclude paths from the tree | -e 'test.*' |
--include, -i |
Only include paths matching these regex patterns | -i '.*\.py$' -i '.*\.y[a]?ml$ |
--always-include, -a |
Always include these paths regardless of exclusion rules | -a 'README.*' |
--contents, -c |
Regex patterns to include paths matching these patterns in contents section | -c '.*\.py$' -c '^README\.*' |
--output, -o |
Output file path (prints to stdout if not specified) | -o CONTEXT.md |
--template, -t |
Path to custom Jinja2 template file | -t my_template.md.j2 |
Important: Be careful to escape your regex args properly. Notice the use of single quotes around regex patterns to avoid issues due to shell expansion.
All regex flags can be specified multiple times to include/exclude multiple patterns, and combined as needed.
For any file in the root directory, the inclusion/exclusion rules are applied in the following order:
- IF the path matches any pattern in always-include, THEN it is included in the tree
- ELSE IF the path matches any pattern in exclude, THEN it is excluded from the tree
- ELSE IF there are include patterns THEN
- IF the path matches any pattern in include, THEN it is included in the tree
- ELSE it is excluded from the tree
- ELSE IF the path is a file tracked by Git, THEN it is included in the tree
- ELSE IF the path is a file that is ignored using a
.gitignore
, THEN it is excluded from the tree - ELSE IF the path is a dot-file (ie its name starts with a
.
), THEN it is excluded from the tree - ELSE the path is included in the tree
- IF the path is included in the tree, THEN
- IF there are contents patterns, THEN
- IF the path matches any patterns in contents, THEN its contents are included in the contents section
- ELSE the path's contents are excluded from the contents section
- ELSE IF the path suffix is the most common file type in the project, THEN its contents are included in the contents section
- ELSE the path's contents are excluded from the contents section
- IF there are contents patterns, THEN
- ELSE the path's contents are excluded from the contents section
Write the context of a subdirectory to a custom dot-file:
project-context -r src/my_project -o src/my_project/.context.md
Include Python files, YAML files, and markdown files in the content section:
project-context -c '.*\.(py|md|yaml)$'
Exclude multiple patterns from the project context:
project-context -e '^\..*' -e '.*\.yaml$'
Exclude all YAML files, except for your .pre-commit-config.yaml
:
project-context -e '.*\.yaml$' -a '\.pre-commit-config\.yaml'
Only include typescript files and README files:
project-context -i '.*\.ts$' -a 'README.*'
Generate the output using a custom template and write to file:
project-context -t custom_template.md.j2 -o CONTEXT.md
For automated context generation on each commit, add this to your .pre-commit-config.yaml
:
repos:
- repo: https://github.com/jeffmm/project-context
rev: main # or specific version tag
hooks:
- id: project-context
name: Generate LLM context from project contents
files: '' # change as needed if you only want to update when specific files change
args: ['-o', 'CONTEXT.md'] # default args, update as needed
Important:
Consider adding CONTEXT.md
to your .gitignore
file if you don't want to track the generated context file in your repository, since it effectively duplicates your project contents.
The pre-commit hook will automatically regenerate the context file whenever you make a commit, ensuring your project context is always up-to-date for sharing with LLMs.
The default output template is:
# {{ root }}
## Project Structure
{{ tree }}
## Project Contents
{{ contents }}
For a well-documented project, the default can work well enough on its own, but you can further customize the output format by providing your own Jinja2 template file. This can be useful for providing additional context or constraints that might improve the quality of AI responses.
project-context
defines three variables for rendering the output: root
, tree
, and contents
.
For example, using a template like this would add a header to the context with some project-specific details that can help steer the LLM to produce output that's more aligned to your project's needs:
{# custom_template.md.j2 #}
# {{ root }}
## Project Description
### High-level Overview:
Here is a brief description of the project and its purpose...
### Project Roadmap
Here is the planned roadmap for the project...
### Developer Guidelines
Here are some guidelines and constraints on how the project should be maintained...
## Project Structure
{{ tree }}
## Project Contents
{{ contents }}
Then you can generate the context using your custom template like this:
project-context -t custom_template.md.j2 -o CONTEXT.md