npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@coder-dao/contextforge

v1.0.3

Published

Forge a repository into a single AI-ready Markdown context file.

Readme

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.md by default.
  • Uses gpt-tokenizer with the o200k_base tokenizer 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 includeFiles override 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 named tests.
  • Inclusion wins over content exclusion. includeFiles is the override mechanism.
  • directoryStructure.excludePatterns is 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.