CLAUDE.md Forge
Developer Reference

Everything you need to understand, self-host, or extend CLAUDE.md Forge — from the stack detection algorithm to the API contract.

Overview

CLAUDE.md Forge is a Next.js 16 utility that turns any GitHub repository URL — or a plain-text stack description — into a complete Claude Code configuration ZIP in under 30 seconds.

The output contains four files your project drops directly into its root directory. Claude Code automatically reads CLAUDE.md at the start of every session, giving the AI persistent knowledge of your project's conventions, principles, and skills.

CLAUDE.md

AI context file — project rules, principles, and selected skills

skills/*.md

6–8 individual skill files auto-selected for your stack

hooks/pre-commit.sh

Bash hook for linting and type-checking before every commit

SETUP_GUIDE.md

Step-by-step instructions for placing files in your project

Quick Start

Clone, configure, and run locally in four commands:

git clone https://github.com/spooky-may/project-forge.git cd project-forge npm install npm run dev # → http://localhost:3000

Before running, create .env.local with your API keys. See Environment Variables.

Environment Variables

OPENROUTER_API_KEYstringrequired

OpenRouter API key used for CLAUDE.md generation. Obtain from openrouter.ai/keys. All generation runs through the Claude model.

GITHUB_TOKENstring

GitHub Personal Access Token. Without it, the GitHub API is limited to 60 unauthenticated requests per hour per IP. With a token, the limit increases to 5,000 requests per hour. Only public_repo scope is needed — read-only access.

# .env.local OPENROUTER_API_KEY=sk-or-v1-... GITHUB_TOKEN=ghp_...

Forge Pipeline

Every forge request to POST /api/forge runs through a synchronous five-step pipeline:

01
Rate limit check

In-memory map keyed by IP. 5 forges per IP per day, resetting at midnight UTC. Returns 429 if exceeded.

02
Context extraction

URL mode: packRepo() fetches manifest files from GitHub. Describe mode: uses the raw text input as context.

03
Stack detection

detectStack() runs keyword matching over the context string and returns a string[] of detected tags (e.g. ["nextjs","typescript","tailwind"]).

04
Skill selection

selectSkills() scores all 59 skills against the detected tags. Top 6–8 by score are selected. At least one universal skill is always included.

05
AI generation + ZIP

callClaude() sends the context and selected skill names to the model. The response becomes CLAUDE.md. buildZip() bundles everything with fflate.

GitHub URL Scanning

packRepo() in lib/github.ts extracts context from a GitHub repository without cloning it. It makes three types of requests:

  1. GET /repos/{owner}/{repo} — gets the default branch name
  2. GET /repos/{owner}/{repo}/git/trees/{branch}?recursive=1 — gets the full file tree
  3. GET raw.githubusercontent.com/... — parallel-fetches manifest file content (no auth needed)

Files matched as manifests (case-insensitive):

package.json tsconfig.json next.config.* vite.config.* tailwind.config.* prisma/schema.prisma requirements.txt pyproject.toml go.mod Cargo.toml .eslintrc* README.md

Content is concatenated as // path\ncontent\n\n and truncated to 32,000 characters before being passed to stack detection and AI generation.

Stack Detection

detectStack() in lib/detectStack.ts scans the packed context string for keywords and returns a string[] of technology tags.

next / nextjs
→ nextjs
react
→ react
typescript / tsconfig
→ typescript
tailwind
→ tailwind
prisma
→ database
fastapi / django / flask
→ python, backend
graphql
→ graphql
docker / dockerfile
→ docker
playwright
→ playwright
vitest / jest
→ testing
go.mod
→ golang
cargo.toml / rustlang
→ rust

The describe mode input is scanned with the same function — users can write natural-language descriptions like "Next.js app with Prisma and TypeScript" and detection works correctly.

Skill Scoring Algorithm

selectSkills() in lib/selectSkills.ts scores each of the 59 skills against the detected tag array using this formula:

score = (skill.tags ∩ detectedTags).length + 0.3 if skill.tags includes 'universal'

Skills are then sorted by score descending. The top 6–8 are returned, with at minimum one universal skill always included (even if its score is 0.3 and others score higher).

Skills with score === 0 that are not tagged universal are excluded entirely. This keeps the output focused — a Python project won't get React-specific skills.

CLAUDE.md

The generated CLAUDE.md follows this structure:

# Project: [repo name] ## Core Principles [Karpathy-inspired engineering philosophy] ## Stack [Detected technologies] ## Selected Skills [Inline snippet content from each selected skill] --- Generated by CLAUDE.md Forge — claudemdforge.site

Claude Code reads CLAUDE.md at the start of every session. The file should be committed to your repository root — it's part of your project's source truth, not a generated artifact to be regenerated each time.

You can edit CLAUDE.md freely after generating it. Common additions include project-specific conventions, team preferences, and task context.

Skill Files

Each selected skill is saved as a standalone markdown file in skills/:

skills/ ├── clean-code-principles.md ├── typescript-strict-mode.md ├── nextjs-app-router.md └── [3–5 more skills...]

Skill files are referenced from CLAUDE.md with @skills/filename.md. Claude Code resolves these relative imports automatically, keeping the main context file compact while making each skill individually editable.

File names are derived from skill titles using kebab-case slug conversion.

Pre-commit Hook

The generated hooks/pre-commit.sh is a standard Bash hook that runs on every commit:

#!/bin/bash set -e # TypeScript check npx tsc --noEmit # Lint npx eslint . --ext .ts,.tsx --max-warnings 0 # Tests (if present) [ -f "package.json" ] && grep -q '"test"' package.json && npm test -- --passWithNoTests echo "✓ Pre-commit checks passed"

To install: cp hooks/pre-commit.sh .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit. The SETUP_GUIDE.md includes these exact steps.

SETUP_GUIDE.md

The SETUP_GUIDE.md is generated alongside the other files and contains step-by-step instructions for the developer:

  1. Unzip claude-forge-output.zip in your project root
  2. Commit CLAUDE.md and the skills/ directory
  3. Install the pre-commit hook
  4. Open Claude Code — it reads CLAUDE.md automatically
  5. Edit CLAUDE.md to add your project-specific conventions

POST /api/forge

The single API endpoint that drives the Forge button.

Request

POST /api/forge Content-Type: application/json { "mode": "url" | "describe", "input": string }
mode"url" | "describe"required

url — input is a GitHub repository URL. Forge fetches and packs manifest files.
describe — input is a plain-text stack description. Used directly as context.

inputstringrequired

For url mode: a GitHub URL in the form https://github.com/owner/repo.
For describe mode: a natural-language description of your project stack.

Response (200)

{ "claudeMd": string, // generated CLAUDE.md content "skills": Skill[], // 6–8 selected skill objects "tags": string[], // detected stack tags "zipBase64": string // base64-encoded ZIP file }

Error responses

400Missing or invalid fields (mode or input)
429Rate limit exceeded — 5 forges per IP per day
500Upstream error (GitHub API, OpenRouter, or internal)

Rate Limits

The Forge API is rate-limited at the edge using an in-memory Map keyed by IP address.

5 / day
Requests per IP
Midnight UTC
Reset cadence
Timestamp comparison
Reset mechanism
In-memory Map
Storage

The in-memory store resets on server restart (e.g. every Vercel deployment). For stricter production rate-limiting, replace lib/rateLimit.ts with a Redis- or KV-backed implementation.

Self-Hosting Guide

CLAUDE.md Forge can be deployed to any Node.js hosting environment:

# Vercel (recommended) npm i -g vercel vercel --prod # Or: Node.js server npm run build npm start # listens on PORT env var, default 3000

Set the following environment variables in your hosting dashboard:

OPENROUTER_API_KEY=sk-or-v1-... GITHUB_TOKEN=ghp_... # optional

No database is required. The rate limiter uses an in-memory Map that resets on process restart.

Model Configuration

The model is configured in lib/claude.ts. To swap models, change the model field in the OpenRouter request body:

// lib/claude.ts body: JSON.stringify({ model: 'mistralai/ministral-3b-2512', // change this messages: [...], max_tokens: 2000, })

Any OpenRouter-supported model can be used. Faster / cheaper alternatives:

mistralai/ministral-3b-2512Default — fast, high quality, cheap
mistralai/mistral-small-3.1Lightweight, very low cost
google/gemini-flash-1.5Fast, good instruction following
anthropic/claude-3-5-haikuBest instruction following, higher cost