@coder-dao/contextforge
Context Forge
Context Forge (contextforge) turns a code repository into one AI-ready Markdown file named repository-context.md.
It is built for quickly giving an AI assistant the shape and source content of a repository without copying files one by one. The generated Markdown starts with a repository structure tree, then lists each included file as a ## File: path/to/file section with fenced code blocks.
Highlights
- Generates
repository-context.mdby default. - Uses
gpt-tokenizerwith theo200k_basetokenizer for token counts. - Keeps the config simple: include files, exclude files, exclude directories, exclude extensions, and directory-structure behavior.
- Lets excluded files still appear in the repository tree while omitting their content to save AI context tokens.
- Lets
includeFilesoverride content exclusions for specific files or subdirectories. - Removes empty lines by default to reduce output size.
Development setup
pnpm install
pnpm build
pnpm link --global
contextforge
During development, you can also run the CLI directly without linking:
pnpm dev
CLI usage
Create a repository context file in the current directory:
contextforge
Create a context file for another directory:
contextforge ./apps/web
Use a custom config path:
contextforge --config ./contextforge.config.json
Override the output path for one run:
contextforge --output ./ai/repository-context.md
Suppress the success summary:
contextforge --quiet
Initialize config
Create a default contextforge.config.json:
contextforge init
Overwrite an existing config:
contextforge init --force
CLI output
A successful run prints a readable summary like this:
Context Forge
Forged repository context successfully.
──────────────────────────────────────
Total Files: 128
Total Tokens: 1,35,342
Total Characters: 1,71,600
File Extensions: .json (8), .md (5), .ts (64), .tsx (51)
Output Markdown: /path/to/repository-context.md
Time Taken: 1.24s
Generated Markdown format
The output is intentionally close to Repomix's Markdown structure: summary, repository structure, then file sections.
# Repository Structure
```text
.
├── README.md
├── package.json
└── src/
└── index.ts
```
# Repository Files
## File: README.md
```markdown
# Example
```
## File: src/index.ts
```typescript
export function hello() {
return "world";
}
```
Default config
contextforge init creates this default shape:
{
"output": "repository-context.md",
"removeEmptyLines": true,
"includeFiles": [".env.example", "**/.env.example"],
"excludeFiles": [".DS_Store", "**/.DS_Store"],
"excludeDirectories": ["node_modules", ".git", ".next", "dist", "coverage"],
"excludeFileExtensions": [".png", ".jpg", ".pdf", ".zip", ".woff2"],
"directoryStructure": {
"includeIgnored": true,
"excludePatterns": ["node_modules/**", ".git/**", "dist/**"]
}
}
The real generated config includes the full default exclude lists.
Config options
| Option | Type | Default | What it does |
|---|---|---|---|
output |
string |
repository-context.md |
Path for the generated Markdown file. Relative paths are resolved from the target repository directory. |
removeEmptyLines |
boolean |
true |
Removes blank lines from included file contents before writing Markdown. This reduces token usage while preserving non-empty source lines. |
includeFiles |
string[] |
[".env.example", "**/.env.example"] |
Glob patterns that force file contents to be included. These patterns override excludeFiles, excludeDirectories, and excludeFileExtensions. |
excludeFiles |
string[] |
Common env, lock, log, OS, and generated file patterns | File glob patterns whose contents should not be included. Files may still appear in the repository tree when directoryStructure.includeIgnored is true. |
excludeDirectories |
string[] |
Common dependency, build, cache, coverage, and metadata folders | Directory names or paths whose file contents should not be included. A value like tests matches any folder named tests; a value like drizzle/meta matches that path and everything below it. |
excludeFileExtensions |
string[] |
Common image, archive, audio, video, font, and PDF extensions | File extensions whose contents should not be included. These files can still appear in the repository tree. |
directoryStructure.includeIgnored |
boolean |
true |
When true, files excluded from content can still be listed in the repository tree. This is useful for test files, generated files, or binary assets where paths are useful but contents are too noisy. |
directoryStructure.excludePatterns |
string[] |
Noisy folders like node_modules/**, .git/**, dist/** |
Hard skips for the repository tree traversal. Use this for directories you do not want scanned or shown at all. |
Glob behavior
- Patterns use forward slashes, even on Windows.
- Dotfiles are supported.
- File patterns without slashes, like
*.log, match by basename. - Directory patterns without slashes, like
tests, match any path segment namedtests. - Inclusion wins over content exclusion.
includeFilesis the override mechanism. directoryStructure.excludePatternsis a hard skip and is meant for very large or noisy paths.
Example: keep tests in the tree, omit test contents
Use this when you want the AI to know that tests exist, but you do not want to spend tokens on test content.
{
"output": "repository-context.md",
"removeEmptyLines": true,
"includeFiles": [".env.example", "**/.env.example"],
"excludeFiles": [
"**/*.test.ts",
"**/*.test.tsx",
"**/*.spec.ts",
"**/*.spec.tsx"
],
"excludeDirectories": ["node_modules", ".git", "dist", "coverage"],
"excludeFileExtensions": [
".png",
".jpg",
".jpeg",
".gif",
".webp",
".pdf",
".zip"
],
"directoryStructure": {
"includeIgnored": true,
"excludePatterns": ["node_modules/**", ".git/**", "dist/**", "coverage/**"]
}
}
With this config, src/button.test.ts appears under # Repository Structure, but it does not get a ## File: src/button.test.ts content section.
Example: exclude a whole directory, but include one file inside it
This is useful when a directory is mostly generated or noisy, but one file is important.
{
"excludeDirectories": ["legacy"],
"includeFiles": ["legacy/README.md", "legacy/src/public-api.ts"],
"directoryStructure": {
"includeIgnored": true,
"excludePatterns": ["node_modules/**", ".git/**", "dist/**"]
}
}
legacy/README.md and legacy/src/public-api.ts are included with content because includeFiles overrides excludeDirectories.
Example: include .env.example, but never include real env files
The default config already does this:
{
"includeFiles": [".env.example", "**/.env.example"],
"excludeFiles": [".env", ".env.*", "**/.env", "**/.env.*"]
}
.env.example is included, but .env, .env.local, and nested env files are omitted from content.
Example: hide noisy folders from both the tree and file contents
Add noisy paths to directoryStructure.excludePatterns when you do not even want them scanned or listed.
{
"excludeDirectories": [
"node_modules",
".git",
"dist",
"coverage",
"storybook-static"
],
"directoryStructure": {
"includeIgnored": true,
"excludePatterns": [
"node_modules/**",
".git/**",
"dist/**",
"coverage/**",
"storybook-static/**"
]
}
}
Programmatic usage
import { generateContext } from "contextforge";
const result = await generateContext({
targetDirectory: process.cwd(),
});
console.log(result.stats.totalTokens);
console.log(result.outputPath);
Notes on binary files
Binary-looking files are never written as content blocks. If they are not hidden by directoryStructure.excludePatterns, they can still appear in the repository tree so the AI can understand that assets exist without consuming binary data.
Troubleshooting
contextforge command not found after linking
Run the build first, then link globally:
pnpm build
pnpm link --global
A file is excluded even though I need it
Add it to includeFiles:
{
"includeFiles": ["path/to/important-file.ts"]
}
A folder is not visible in the tree
Check directoryStructure.excludePatterns. Those patterns are hard skips.
The output includes too many files
Add specific patterns to excludeFiles, excludeDirectories, or excludeFileExtensions. For very large folders, also add the path to directoryStructure.excludePatterns.