ContextForge MCP Server
README.md
# ContextForge
> Pipeline determinista que convierte cualquier repo en un índice consultable por agentes IA. **94.4 % menos tokens por sesión, 17.9× compresión, ÷28× costo SDD con prompt caching** — sin LLM en el pipeline, sin lock-in de agente.
```
214 600 tokens (todo el repo) → 11 988 tokens (lo que importa a la tarea)
```
[]() []() [](https://www.npmjs.com/package/@anai-raia-alex/contextforge-cli) [](https://github.com/alejandro-cedeno-10/contextforge-cli/pkgs/container/contextforge-mcp)
---
## El problema que resuelve
Cada vez que un agente IA (Claude Code, Cursor, OpenCode) abría el repo tenía que:
1. **Leer TODO** para entender qué existe
2. **Recordar correr `pnpm forge context` manualmente** antes de cada tarea
3. **Adivinar qué archivos tocar** sin contexto del grafo
4. **No había gate pre-commit** — specs y guardrails se saltaban fácil
**Ahora todo eso es automático** vía MCP + hooks.
---
## Cómo encaja cada pieza
```
Dev / Agente (Claude Code · Cursor · OpenCode) escribe tarea
│
▼
[Hook UserPromptSubmit] ← automático, sin hacer nada
→ MCP: select_agent_context(task)
→ Agente recibe: skills relevantes + domains tocados
│
▼
Agente llama forge_context(task) ← MCP tool, sin pnpm, sin disco
→ ~20 archivos rankeados por PageRank + BFS
→ sabe exactamente qué leer, no el repo entero
│
▼
Agente llama forge_spec("mi-feature") ← MCP tool
→ escribe openspec/changes/mi-feature/
├── proposal.md ← motivación y alcance
├── design.md ← decisiones técnicas
├── tasks.md ← lista de tareas
├── specs/ ← Requirements + Scenarios (Given/When/Then)
├── graph.subset.json ← subgrafo FROZEN de los archivos relevantes
├── context.md ← mapa de lectura para el agente
└── agent-manifest.json ← skills filtradas SOLO para este change
│
▼
Agente/Dev llena proposal.md + specs/*.md
openspec validate mi-feature ← verifica formato Requirement + Scenario
│
▼
Agente llama forge_implement("mi-feature") ← MCP tool
→ .contextforge/implement-plan.json
allowedFiles: [solo los del context-pack]
maxLocDelta: estimado del pack
│
▼
Agente implementa código
│
▼
Agente intenta git commit
│
▼
[Hook PreToolUse] ← automático, bloquea si hay violaciones
→ node .claude/hooks/forge-pre-commit.mjs
→ forge implement --check valida git diff vs guardrails
→ archivo fuera de allowedFiles → exit 2 → COMMIT BLOQUEADO
→ Agente recibe lista de violaciones y corrige antes de reintentar
│
▼
Commit pasa → merge
│
▼
Agente corre openspec archive mi-feature
│
▼
[Hook PostToolUse(openspec)] ← automático, en background
→ forge_rebuild_graph async
→ grafo fresco para la próxima tarea
```
---
## ContextForge no reemplaza OpenSpec — lo alimenta
**OpenSpec** es el framework de specs. **Claude Code / Cursor / OpenCode** son los agentes IA que lo usan. ContextForge le da a los agentes el contexto correcto para que OpenSpec reciba datos reales del código, no inventados.
| OpenSpec hace | ContextForge aporta |
| ------------------------------------------- | ---------------------------------------------------------- |
| Valida formato `Requirement:` + `Scenario:` | El contexto correcto para escribir la spec |
| Archiva y mueve specs a `openspec/specs/` | El subgrafo frozen que dice qué archivos aplican |
| `openspec validate` chequea Given/When/Then | `forge_check` chequea que el código respete los guardrails |
| Historial de specs por feature | `forge_status` muestra qué changes tienen subgrafo activo |
---
## Antes vs ahora
**Sin hooks ni MCP** — el dev recordaba (o no) cada paso:
```bash
pnpm forge scan
pnpm forge graph
pnpm forge context "mi tarea"
pnpm forge spec mi-feature
# llenar docs...
pnpm forge implement mi-feature
# codear...
pnpm forge implement --check # se olvidaba frecuentemente
git commit
openspec archive mi-feature
pnpm forge scan && pnpm forge graph # se olvidaba siempre
```
**Ahora** — los hooks y MCP tools hacen el trabajo:
```bash
# Agente recibe contexto automático al escribir el prompt (hook)
# Llama forge_context → forge_spec → forge_implement via MCP (sin pnpm)
# Dev/agente llena las specs → openspec validate confirma formato
# Agente codea
git commit # → hook bloquea si hay violaciones, las explica
openspec archive mi-feature # → hook reconstruye grafo en background
```
`pnpm forge` manual solo para bootstrap (`forge init`) o forzar reconstrucción (`forge graph --force`).
---
## Instalación
**npm — público, sin token:**
```bash
# CLI (uso diario)
pnpm add -g @anai-raia-alex/contextforge-cli # global
# o
pnpm add -D @anai-raia-alex/contextforge-cli # por proyecto
# MCP server (para agentes vía Node)
pnpm add -D @anai-raia-alex/contextforge-mcp
# OpenSpec CLI (recomendado, activa el modo handoff)
npm i -g @fission-ai/openspec
```
**Docker — sin instalar Node, multi-arch (amd64+arm64):**
```bash
docker pull ghcr.io/alejandro-cedeno-10/contextforge-mcp:latest
```
**Desde fuente (desarrollo):**
```bash
git clone https://github.com/alejandro-cedeno-10/contextforge-cli.git
cd contextforge-cli
pnpm install && pnpm build
```
---
## Quick start (5 comandos para empezar)
```bash
# 1. Inicializar (corre 'openspec init' si está en PATH; instala instrucciones del agente)
pnpm forge init
# 2. Indexar el repo (incremental; cache por hash)
pnpm forge scan
pnpm forge graph
# 3. Por cada tarea
pnpm forge context "fix race en tokenLedger writer"
# 4. SDD opcional (con OpenSpec)
pnpm forge spec mi-feature-id # spec-input + handoff/fallback
openspec validate mi-feature-id
# 5. Implementar con guardrails
pnpm forge implement mi-feature-id
# (trabajas con el agente)
pnpm forge implement --check # gate pre-commit
```
---
## Las tres capas
### Capa 1 — Grafos para ahorrar tokens
En lugar de "leer todo el repo", **rankeamos los archivos por relevancia** a tu tarea con el grafo de dependencias real del código + Personalized PageRank.
**Pipeline:**
```bash
pnpm forge scan # Indexa con BLAKE3 (incremental)
pnpm forge graph # Grafo file + symbol con tree-sitter
pnpm forge context "<tarea>" # PageRank + BFS + budget
```
**Algoritmo:**
```
1. resolve_seeds(task) → archivos semilla por keywords
2. personalized_pagerank() → ranking de relevancia (alpha=0.85, 50 iter)
3. bfs_expand(depth=2) → captura deps directas + transitivas
4. score_nodes() → pagerank × (1/bfs_dist) × edge_multiplier
5. greedy_pack(budget=12000) → llena hasta 12k tokens (full → excerpt → summary)
```
**Edge multipliers:** `tests=1.2 · defines=1.0 · calls=1.0 · imports=0.8 · references=0.6`
**Ahorro medido en este repo:**
| Sin ContextForge | Con context-pack |
| ----------------------------------- | ------------------------- |
| 214 600 tokens | 11 988 tokens |
| ~$0.64 / sesión (Claude Sonnet 4.6) | ~$0.036 / sesión |
| 128 archivos enviados | 50 archivos seleccionados |
**Compresión: 17.9× · Ahorro: 94.4 %**
### Capa 2 — SDD apoyando a OpenSpec (no compitiendo con él)
> v0.3.0+ — `forge spec` ya **no genera el spec final**. Genera la **entrada** (`spec-input.json` + `spec-prompt.md`) para que OpenSpec o un agente IA lo escriban con todo el contexto correcto.
**Tres roles separados, cada uno hace lo suyo:**
```
┌─────────────────────┐ ┌─────────────────────┐ ┌────────────────┐
│ ContextForge │ │ OpenSpec │ │ Agente IA │
├─────────────────────┤ ├─────────────────────┤ ├────────────────┤
ROL │ Selección de │ │ Estructura + │ │ Redacción │
│ contexto │ │ validación │ │ │
├─────────────────────┤ ├─────────────────────┤ ├────────────────┤
TÉC. │ PageRank + BFS + │ │ Schemas RFC 2119 │ │ LLM │
│ budget tokens │ │ Given/When/Then │ │ │
├─────────────────────┤ ├─────────────────────┤ ├────────────────┤
INPUT │ scan + graph + │ │ spec-input.json │ │ spec-prompt.md│
│ task │ │ │ │ + context-pack│
├─────────────────────┤ ├─────────────────────┤ ├────────────────┤
OUTPUT │ context-pack + │ │ proposal/design/ │ │ .md llenos │
│ spec-input + prompt│ │ tasks/spec.md │ │ + commits │
└─────────────────────┘ └─────────────────────┘ └────────────────┘
```
**Pipeline SDD completo:**
```bash
pnpm forge context "<tarea>" # context-pack + agent-manifest
pnpm forge spec mi-feature-id # spec-input + handoff/fallback
openspec list # ver changes activos
openspec validate mi-feature-id # validación oficial
pnpm forge implement mi-feature-id # plan con guardrails
# (trabajas con el agente)
pnpm forge implement --check # gate pre-commit
openspec archive mi-feature-id -y # al mergear: mueve a specs/
```
**Modo handoff vs modo fallback:**
| Modo | Cuándo | Qué pasa |
| ------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Handoff** (default si OpenSpec CLI está) | `openspec --version` resuelve | `forge spec` ejecuta `openspec new change` + emite `.contextforge/spec-prompt.md` con instrucciones canónicas + contexto del grafo. El dev pega ese prompt en su agente |
| **Fallback** (sin OpenSpec CLI o `--no-openspec`) | CLI ausente, CI sin instalación | ContextForge emite el scaffold con **formato moderno** (`### Requirement:` + `#### Scenario:`). Cuando se instale el CLI después, `openspec validate` pasa sin retoque |
Mismo contrato (`spec-input.json`), dos formas de consumirlo. Portable Mac/Linux/Windows.
**Forma de cada Requirement (RFC 2119 + Given/When/Then):**
```markdown
### Requirement: System produces fix-token-race within budget
The system MUST implement the change `fix-token-race` while respecting
`guardrails.allowedFiles` and not exceeding `guardrails.maxLocDelta`.
#### Scenario: change is implemented within scope
- **Given** a valid `.contextforge/context-pack.json` for the task
- **When** the developer runs `pnpm forge implement fix-token-race`
- **Then** `pnpm forge implement --check` exits with code 0
```
**Guardrails de implementación (derivados del grafo):**
`forge implement` produce `implement-plan.json` cuyos campos vienen del context-pack:
- `allowedFiles[]` ← exactamente los archivos del pack
- `forbiddenPaths[]` — nunca tocar
- `maxLocDelta` — derivado del tamaño del pack
- `maxFilesChanged` — `allowedFiles.length + 2`
`forge implement --check` valida tu `git diff` antes del commit. Si te saliste del scope, te lo dice exactamente y el commit no pasa.
### Capa 3 — Solo cargar las skills/rules que aplican a la tarea
`forge context` ya emite **5 artefactos sin paso extra**:
```
.contextforge/context-pack.json ← archivos relevantes
.contextforge/token-ledger.json ← métricas
.contextforge/agent-manifest.json ← NEUTRAL (validado por schema)
.claude/agent-manifest.md ← renderer Claude Code
.cursor/rules/contextforge-active.mdc ← renderer Cursor (Auto-Attached)
.contextforge/manifests/opencode-readme.md ← renderer OpenCode
```
**Reglas de matching (priorizadas):**
1. `alwaysApply: true` → siempre incluida.
2. frontmatter `domains: [..]` ∩ dominios tocados → match domain.
3. nombre `contextforge-domain-<slug>` del dominio tocado → match explicit (legacy `ctx-<slug>` aún soportado).
4. nada matchea → skipped (con razón).
Frontmatter mal formado nunca crashea: cae en `skipped` con `frontmatter parse error`.
**Activación por agente (sin pasos manuales después de setup):**
| Agente | Cómo se carga | Setup |
| --------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| **Claude Code** | Hook `UserPromptSubmit` regenera el manifest en cada prompt | Pegar snippet en `.claude/settings.json` (ver `docs/integrations/claude-code-hook.md`) |
| **OpenCode** | El agente llama tool MCP `select_agent_context({task})` al inicio. Live, sin disco | `opencode.json` ya configurado |
| **Cursor** | Rule Auto-Attached con `globs:` por dominio. Se activa al abrir un archivo del dominio | Sin setup; `forge context` regenera la rule |
---
## El grafo en detalle
Esta sección explica **qué genera `forge graph`**, **cómo lo genera**, **cómo lo visualiza**, y **cómo el agente lo consume**. Si vienes de [Understand-Anything](https://github.com/openSVM/understand-anything), vas a reconocer la idea: convertir un repo en un mapa navegable. La diferencia es que aquí el pipeline es **100 % determinista** (sin LLM en el loop, sin coste por correrlo), `cacheable por archivo`, y los outputs son JSON portables que cualquier agente puede consumir sin la app.
### Pipeline de generación (qué pasa cuando corres `forge graph`)
```
┌──────────────┐ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ scan.json │ → │ per-file │ → │ graph passes│ → │ graph.json │
│ (BLAKE3) │ │ cache hit? │ │ (4 etapas) │ │ + cache.json│
└──────────────┘ └─────────────┘ └──────────────┘ └──────────────┘
│ │ │
│ │ ├─ Pass 0: file nodes
│ │ ├─ Pass 1: parse code/test (paralelo, n=cpus)
│ │ ├─ Pass 2: defines + imports + tests
│ │ ├─ Pass 3: extends / implements
│ │ ├─ Pass 3.5 (--with-calls): calls
│ │ └─ Pass 4: folder + contains
│ │
│ └─ Si `file.hash` coincide con cache, reusa el fragment parseado
│ y solo recomputa las aristas cross-file (imports/extends/calls).
│
└─ Hash global del scan: si no cambió, `forge graph` ni siquiera arranca (skip).
```
**Output canónico** — `.contextforge/graph.json` cumple `docs/schemas/graph.schema.json`:
```json
{
"schemaVersion": "0.2.0",
"project": { "name": "contextforge-cli", "root": "." },
"generatedAt": "2026-05-08T...Z",
"scanRef": { "path": ".contextforge/scan.json", "scanHash": "..." },
"parser": { "engine": "heuristic" },
"stats": {
"nodesByType": { "file": 287, "folder": 117, "symbol": 3104 },
"edgesByType": { "imports": 71, "defines": 2256, "contains": 376 }
},
"nodes": [
/* ordenados por id, JSON byte-estable */
],
"edges": [
/* ordenadas por (from,to,type) */
]
}
```
### Tipos de nodo y arista que produce
| Nodo | Cuándo aparece |
| --------- | -------------------------------------------------------------------------------- |
| `file` | Uno por cada archivo del scan (`code`/`test`/`doc`/`config`/`asset`) |
| `symbol` | Por cada función/clase/interface/type/var/enum exportada o interna |
| `folder` | Sintético, derivado de los paths (anidación completa) |
| `package` | Por cada **import externo único** (`react`, `node:path`, `@anthropic-ai/sdk`, …) |
| Arista | De qué | Cuándo se emite |
| ------------ | ----------------------------------- | ---------------------------------------------------------------------- |
| `defines` | `file → symbol` | Siempre que el archivo contiene un símbolo |
| `imports` | `file → file` o `file → package` | Relativos · alias workspace (pnpm) · alias `tsconfig.paths` · externos |
| `tests` | `file:test → file:impl` | Convención `*.test.ts` ↔ `*.ts` |
| `extends` | `symbol → symbol` | Clase/interface extiende un símbolo del mismo file o de un import |
| `implements` | `symbol → symbol` | `class A implements B` resuelto |
| `contains` | `folder → folder` y `folder → file` | Estructura de carpetas |
| `calls` | `file → symbol` | **Solo con `--with-calls`** (heurística regex, ruido aceptado) |
| `references` | `file → symbol` | **Solo con `--with-refs`** (PascalCase, fuera de imports/defines) |
**Lenguajes parseables** hoy (heurística regex, no tree-sitter WASM): TypeScript, TSX, JavaScript, JSX, Python, Go, Rust, Java. El resto se incluye como `file` pero sin símbolos.
### Incrementalidad y reproducibilidad
```bash
pnpm forge graph # 1ª vez en este repo: 112 archivos parseados
# editas un archivo
pnpm forge scan # actualiza hashes
pnpm forge graph # → "cache: 111 reutilizados, 1 reparseados"
pnpm forge graph --force # ignora ambos caches; reconstruye todo
pnpm forge graph --with-calls # añade aristas calls (heurística, opt-in)
pnpm forge graph --with-refs # añade aristas references PascalCase (opt-in)
# resolución de imports
# 1. workspace pnpm (packages/<name>)
# 2. tsconfig.paths (compilerOptions.paths con baseUrl)
# 3. relativos (./foo, ../bar)
# 4. externos → nodo type=package (react, node:fs, @scope/lib)
# export a otros formatos
pnpm forge graph --export=dot > graph.dot
pnpm forge graph --export=graphml > graph.graphml
# Render con Graphviz: dot -Tsvg graph.dot -o graph.svg
# O abrir graph.graphml directamente en Gephi.
# enriquecimiento opcional con LLM (Anthropic)
ANTHROPIC_API_KEY=sk-... pnpm forge graph --enrich
# Agrega summary / tags / complexity a los símbolos exportados.
# El default sigue siendo determinista; --enrich es la única excepción opt-in.
```
- **Cache global por hash del scan**: si `scan.json` no cambió, `forge graph` no arranca.
- **Cache por archivo** (`.contextforge/graph.cache.json`): solo se reparsean los archivos cuyo hash BLAKE3 cambió.
- **Output byte-idéntico** entre dos corridas (excluyendo `generatedAt`): los nodos y aristas se ordenan al final por `id` / `(from,to,type)`, así que `git diff` del JSON es revisable y el prompt cache de Claude no se invalida innecesariamente.
### Visualización: `forge viz`
```bash
pnpm forge graph
pnpm forge viz # genera .contextforge/graph.html
# abre el archivo en cualquier navegador, no necesita servidor ni Node
```
El HTML viewer es **standalone** (un solo archivo, Cytoscape vía CDN) y trae:
```
┌──────────────────────────────────────────────────────────────────────┐
│ ContextForge [Grafo] [Dominios] 287 nodos · 2703 edges · 50 pack│
├──────────────┬───────────────────────────────────────────────────────┤
│ Estadísticas │ │
│ 287 archivos │ ●─────● │
│ 3104 símbolos│ ╱ ╲ ● ← archivo en pack │
│ 2703 edges │ ●─────●───● ◇ ← símbolo exportado │
│ 50 en pack │ ╲ ╱ ◇ ← dashed = interno │
│ │ ●─● ▢ ← carpeta │
│ Tour ◀ ▶ ✕ │ │
│ 3 / 50 │ │
│ │ │
│ Leyenda nodos │ ┌──────────────┐ │
│ ● Código 128 │ │ src/ │ ← agrupación por dominio │
│ ● Test 24 │ │ ┌────┐ ┌──┐ │ (toggle "Agrupar") │
│ ● Doc 17 │ │ │a.ts│ │b │ │ │
│ ▢ Carpeta 117│ │ └────┘ └──┘ │ │
│ ◇ Interno 218│ └──────────────┘ │
│ ● En pack 50 │ │
│ │ │
│ Leyenda edges │ │
│ ─ imports │ │
│ ─ defines │ │
│ ─ tests │ │
│ ─ extends │ │
│ ─ contains │ │
│ │ │
│ Filtros │ │
│ [Solo pack] [+Sím] [+Carpetas] [Agrupar] │
│ [⊕ Centrar] [⟳ Layout] │
│ │ │
│ 🔍 Buscar... │ │
│ │ │
│ Nodo selec. │ │
│ src/main.ts │ │
│ [code][ts] │ │
│ Define 4 sím │ │
│ → importa: 3 │ │
│ ← usado por:5│ │
└───────────────┴────────────────────────────────────────────────────────┘
```
**Capacidades del viewer:**
- **Dos vistas**: grafo file/symbol/folder + vista de **dominios** (agregación por carpeta top-level, edges = imports cruzados entre dominios).
- **Tour por context-pack**: `▶` recorre uno por uno los archivos seleccionados por PageRank para tu tarea, centrándose y mostrando vecinos.
- **Filtros incrementales**: solo pack (default), añadir símbolos, añadir carpetas, agrupar por dominio.
- **Estilos diferenciados** para `exported:false` (símbolos internos en dashed) y nodos `folder`.
- **Búsqueda** por nombre o ruta con resaltado.
- **Panel de nodo**: kind, lang, contadores de edges entrantes/salientes, lista navegable de vecinos.
### Cómo lo usa el agente
El agente **nunca** lee el repo a ciegas. Lee artefactos derivados, en este orden:
```
1. .contextforge/agent-context.md ← qué hay disponible y en qué orden leerlo
2. .contextforge/context-pack.json ← solo los archivos relevantes a la tarea
3. .contextforge/agent-manifest.json ← qué skills/rules aplican (con razón)
4. .contextforge/graph.json ← solo si necesita ver vecinos / cross-deps
```
**El grafo no se inyecta entero al prompt**. El agente lo consulta puntualmente:
| Pregunta del agente | Cómo responde el grafo |
| ------------------------------------ | ---------------------------------------------------------------- |
| "¿Qué archivos tocan a `Foo`?" | Edges `defines`/`imports` entrantes del símbolo |
| "¿Quién testea esto?" | Edges `tests` entrantes del file |
| "¿De qué hereda `Bar`?" | Edge `extends` saliente del símbolo |
| "¿En qué carpeta vive este archivo?" | Edge `contains` entrante del file |
| "Dame el subgrafo de mi tarea" | Lo entrega `forge context` ya filtrado (PageRank + BFS + budget) |
| "Que nada se salga del scope" | `implement-plan.json::allowedFiles[]` derivado del pack |
**Vía MCP** — los agentes que hablen MCP pueden llamar tools sin tocar disco:
```jsonc
{ "tool": "forge_neighbors", "arguments": { "path": "src/foo.ts" } }
// → { incoming: [...], outgoing: [...], symbols: [...] }
{ "tool": "forge_context", "arguments": { "task": "fix race in writer" } }
// → { files: [...], packTokens: 11988, tokenLedger: {...} }
```
### Vs. Understand-Anything (qué tomamos, qué no)
| Aspecto | Understand-Anything | ContextForge |
| ------------------------- | ---------------------------------- | ------------------------------------------------ |
| Pipeline | Tree-sitter + LLM agents (híbrido) | Regex/tree-sitter, **0 LLM** en el pipeline |
| Lenguajes soportados | 10+ con WASM | 8 con heurística (TS/JS/Py/Go/Rust/Java/TSX/JSX) |
| Schema de nodo | 21 tipos (con dominio + knowledge) | 4 tipos canónicos (file/symbol/folder/package) |
| Schema de arista | 35 tipos | 8 tipos (los que un agente usa de verdad) |
| Enriquecimiento (summary) | LLM agents enriquecen cada nodo | Determinista: contadores derivados del grafo |
| Coste de regenerar | API key + tiempo + ruido | CPU-only · cache por archivo · idempotente |
| Visualización | Dashboard rico, tours guiados | HTML standalone con tour + dominios + filtros |
| Persistencia | knowledge-graph.json + embeddings | graph.json + graph.cache.json (BLAKE3) |
| Roadmap LLM enrich | Es la base | Opt-in futuro (`--enrich`), nunca el default |
**La filosofía**: el grafo de ContextForge **no intenta entender el código**, intenta darte un índice rápido y barato. La inteligencia semántica vive en el agente, no en el pipeline. Por eso es reproducible y por eso el output sirve de input al prompt cache de Claude (descuento del 90 % en iteraciones 2+ del SDD).
---
## Cómo el grafo le da superpoderes a OpenSpec
> El bloque clave de v0.3.0+. Sin grafo, OpenSpec hace su trabajo bien pero el agente trabaja a ciegas. Con grafo, el spec se vuelve trazable, validable y barato.
**1. El `spec-input.json` deriva del grafo, no del repo.**
| Campo | Lo aporta el grafo |
| ----------------- | ---------------------------------------------------------------------- |
| `affectedFiles` | El context-pack ya filtró por PageRank → spec apunta a archivos reales |
| `crossDomainDeps` | El grafo de imports → design.md menciona dependencias correctas |
| `domain` | Inferencia por mayoría de paths del pack |
| `evidence` | Referencias trazables a los artefactos JSON validados |
| `purpose` | Inferencia determinista del nombre del archivo |
**2. El agente recibe el spec-prompt, NO el repo.**
`forge spec` genera `.contextforge/spec-prompt.md` con 4 secciones:
```
1. Contexto del repo (de ContextForge)
- Tarea, dominio inferido, archivos afectados, cross-domain deps, evidencia
2. Instrucciones canónicas de OpenSpec
- Lo que devuelve `openspec instructions proposal --change <id> --json`
3. Restricciones para tu salida
- Token budget, allowed files, formato moderno
4. Output esperado
- Editar openspec/changes/<id>/{proposal,design,tasks,specs}.md
```
El agente no abre el repo. Solo lee este prompt + el context-pack si necesita más detalle.
**3. El multiplicador escondido — prompt caching.**
Claude descuenta **90 % del precio** de tokens cacheados. ContextForge mete un boost porque su output **es determinista**:
- Mismo `forge context` → mismo `context-pack.json` byte-a-byte.
- Mismo `forge spec` → mismo `spec-prompt.md` byte-a-byte.
Entre las **iteraciones 2, 3, N** del SDD, el agente cachea todo el prompt. Solo paga full por el delta.
**4. Ahorro doble medido.**
```
Por iteración SDD:
Sin ContextForge 217 600 tokens $0.65
Con ContextForge 18 988 tokens $0.057 (-91%)
3 iteraciones de un feature normal:
Sin caching 3 × $0.65 = $1.96
Con caching activo $0.07 (÷28×)
```
**5. Trazabilidad auditable.**
Cada path en `design.md` apunta a un archivo del `context-pack.json`. Cada commit puede verificarse con `forge implement --check` contra `allowedFiles[]` derivado del pack.
**6. Subgrafo congelado dentro del change (v0.3.7+).**
`forge spec <id>` ahora también escribe `openspec/changes/<id>/graph.subset.json` — un subgrafo limitado al context-pack del change + 1 hop por aristas. Eso significa:
- Las skills/prompts del agente que leen `openspec/changes/<id>/` tienen el grafo del momento exacto en que se autorizó el spec, sin necesidad de reabrir `.contextforge/graph.json` (que pudo haber mutado).
- `design.md` incluye una sección "Context graph (subset)" con stats y la referencia al adjunto.
- Vía MCP: `forge_change_subgraph({ change_id: "mi-feature" })` lo devuelve directo, ideal para que un agente lo cargue al inicio de una sesión de implementación.
```
openspec/changes/mi-feature/
├── proposal.md
├── design.md ← incluye tabla con stats del subset
├── tasks.md
├── specs/<domain>/spec.md
├── graph.subset.json ← subgrafo self-contained · validado por JSON Schema
└── graph.subset.html ← viewer interactivo standalone (Cytoscape, sin servidor)
```
Trazabilidad real: el subgrafo es `byte-stable` (orden determinista), validado por JSON Schema (`docs/schemas/graph-subset.schema.json`), y queda commiteado con el resto del change. Si seis meses después el grafo global cambió, puedes comparar este subset con el subgrafo equivalente de hoy y ver el drift.
**Tabla — qué aporta cada herramienta:**
| Aporte | ContextForge | OpenSpec | Agente IA |
| --------------------------------------------------------- | :----------: | :------: | :-------: |
| Ranking de archivos por relevancia | ✓ | | |
| Grafo de dependencias | ✓ | | |
| Token budget + selección por presupuesto | ✓ | | |
| Trazabilidad (cada archivo viene del pack) | ✓ | | |
| Schemas estables (Requirement+Scenario) | | ✓ | |
| Validación estructural (`openspec validate`) | | ✓ | |
| Templates canónicos por artefacto | | ✓ | |
| Instalación de instrucciones por agente (`openspec init`) | | ✓ | |
| Redacción de prosa | | | ✓ |
| Decisiones de diseño contextual | | | ✓ |
| Determinismo + cacheabilidad | ✓ | ✓ | |
---
## Documentación generada para agentes (`forge init`)
`forge init` genera `.contextforge/agent-context.md` — un archivo derivado que cualquier agente puede leer al iniciar sesión y entender:
- Qué artefactos hay disponibles y en qué orden leerlos.
- Cómo consumirlos (no leer el repo a ciegas).
- La receta SDD completa (ContextForge ⊕ OpenSpec).
- Política para agentes (no specs a mano, no bullets, gate pre-commit).
- Tabla de comandos clave por intención.
Adicionalmente, `forge init` detecta si `openspec` CLI está en PATH y, si el directorio `openspec/` no existe, ejecuta:
```bash
openspec init . --tools=claude,cursor,opencode --force
```
Eso instala las **instrucciones canónicas de OpenSpec** para los 3 agentes (las que aparecen en `.claude/`, `.cursor/rules/`, `opencode.json`). Idempotente: si el directorio `openspec/` ya existe, salta.
---
## Comandos completos
| Comando | Qué hace | LLM |
| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | :----: |
| `forge init` | `.contextforge/` + `agent-context.md` + `openspec init` (si está) | No |
| `forge scan` | Indexa con BLAKE3 (incremental) | No |
| `forge graph [--force] [--with-calls] [--with-refs] [--enrich] [--export=<dot\|graphml>]` | Grafo file/symbol/folder/package con cache por archivo (BLAKE3) | No (¹) |
| `forge context "<tarea>" [--no-manifest] [--force]` | PageRank + BFS + budget · auto-emite manifest | No |
| `forge spec <id> [--no-openspec]` | spec-input.json + handoff/fallback OpenSpec | No |
| `forge implement <id>` | Plan con guardrails derivados del pack | No |
| `forge implement --check` | Valida diff vs guardrails | No |
| `forge skills [--force]` | Auto-genera skills por dominio (`contextforge-domain-<slug>.md`) | No |
| `forge manifest [--agents=...] [--force]` | Re-genera manifest sin re-rankear | No |
| `forge sync [--since X] [--rebuild]` | Reporta delta desde un ref de git | No |
| `forge impact` | Health check de artefactos + cobertura | No |
| `forge viz` | Visualización HTML interactiva del grafo | No |
| `forge docs [--force]` | Scaffold Diátaxis (tutorials/how-to/reference/explanation/adr/architecture) | No |
**Política**: ningún comando llama a un LLM por defecto. Todo es derivable, reproducible, auditable.
(¹) `forge graph --enrich` es la **única excepción opt-in** — agrega `summary`/`tags`/`complexity` a símbolos exportados vía Anthropic API. Requiere `ANTHROPIC_API_KEY`. Sin la flag, el pipeline sigue 100 % determinista.
---
## Servidor MCP
`packages/mcp` expone **10 tools** consumibles por cualquier cliente MCP (Claude Code, OpenCode, etc.).
| Tool | Propósito |
| ----------------------- | ------------------------------------------------------------------------------------ |
| `forge_status` | Estado de los artefactos (frescura, conteos, savings) |
| `forge_domain_map` | Mapa de dominios + dependencias cruzadas |
| `forge_neighbors` | Vecinos directos de un archivo en el grafo |
| `forge_context` | Selección PageRank para una tarea (con o sin contenido) |
| `forge_check` | Valida git diff contra guardrails del implement-plan |
| `forge_change_subgraph` | Subgrafo congelado de un OpenSpec change (`openspec/changes/<id>/graph.subset.json`) |
| `forge_change_context` | Mapa de lectura del change (`openspec/changes/<id>/context.md`) |
| `forge_archive_change` | **Cierra un change**: `openspec archive` + rebuild graph padre + refresh subgrafos |
| `select_agent_context` | **Runtime**: computa agent-manifest en memoria para una tarea (cache mtime) |
| `get_agent_manifest` | **Offline**: lee `.contextforge/agent-manifest.json` precomputado |
**Wiring (Docker, recomendado):**
```jsonc
{
"mcpServers": {
"contextforge": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-v",
"${PWD}:/project",
"-e",
"PROJECT_ROOT=/project",
"ghcr.io/alejandro-cedeno-10/contextforge-mcp:latest"
]
}
}
}
```
**Wiring (npm):**
```jsonc
{
"mcpServers": {
"contextforge": {
"command": "node",
"args": ["./node_modules/@anai-raia-alex/contextforge-mcp/dist/index.js"],
"env": { "PROJECT_ROOT": "." }
}
}
}
```
---
## Artefactos generados
Todos los JSON validados con JSON Schema 2020-12 antes de escribirse.
```
.contextforge/
scan.json # archivos con hashes BLAKE3
graph.json # 368 nodos, 267 edges
context-pack.json # archivos seleccionados dentro del presupuesto
token-ledger.json # métricas auditables del ahorro
agent-manifest.json # skills/rules relevantes a la tarea (neutral)
spec-input.json # entrada determinista para OpenSpec / agente
spec-prompt.md # prompt copy-paste (modo handoff)
implement-plan.json # guardrails: allowedFiles, maxLocDelta, etc.
agent-context.md # documentación del repo para agentes IA
graph.html # visualización interactiva (Cytoscape.js)
manifests/opencode-readme.md # instrucciones MCP para OpenCode
.claude/agent-manifest.md # manifest renderer Claude Code
.cursor/rules/contextforge-active.mdc # rule auto-attached por dominios tocados
openspec/changes/<id>/
proposal.md # intent, scope, why, alternatives
design.md # decisiones técnicas con archivos del grafo
tasks.md # T1, T1.1, T2... checklist
specs/<dominio>/spec.md # ## ADDED Requirements + ### Requirement: + #### Scenario:
```
### Schemas
| Artefacto | Schema |
| --------------------- | ----------------------------------------- |
| `scan.json` | `docs/schemas/scan.schema.json` |
| `graph.json` | `docs/schemas/graph.schema.json` |
| `context-pack.json` | `docs/schemas/context-pack.schema.json` |
| `implement-plan.json` | `docs/schemas/implement-plan.schema.json` |
| `token-ledger.json` | `docs/schemas/token-ledger.schema.json` |
| `agent-manifest.json` | `docs/schemas/agent-manifest.schema.json` |
| `spec-input.json` | `docs/schemas/spec-input.schema.json` |
---
## Métricas reales (este repo, hoy)
| Métrica | Valor |
| ------------------------ | ---------------------------------------------------- |
| Tests | **270 / 270** verde (26 archivos) |
| Coverage | ≥ 80 % global · módulos `manifest/` y `spec/` ≥ 95 % |
| Token savings | **94.4 %** vs baseline |
| Compresión | **17.9×** por sesión · **÷28×** SDD con caching |
| Archivos del repo | 128 |
| Archivos en context-pack | 50 |
| Tokens del context-pack | 11 988 |
| Tokens baseline | 214 606 |
| Grafo | 368 nodos, 267 edges |
| Comandos `forge` | 13 |
| Tools MCP | 7 |
---
## Distribución
| Canal | Identificador |
| ---------------------------------- | ------------------------------------------------------------------------------ |
| **npmjs.com** (público, sin token) | `@anai-raia-alex/contextforge-{core,cli,mcp}` |
| **GitHub Container Registry** | `ghcr.io/alejandro-cedeno-10/contextforge-mcp:latest` (multi-arch amd64+arm64) |
| **Repo público** | https://github.com/alejandro-cedeno-10/contextforge-cli |
---
## Integración con agentes — matriz
| Agente | Configuración | Selección por tarea | Estado |
| ----------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ---------- |
| **Claude Code** | `.claude/skills/` (3 task-oriented + N auto-generadas) · hook `UserPromptSubmit` | Hook inyecta manifest live + JSON precomputado | Listo |
| **Cursor** | `.cursor/rules/contextforge.mdc` (alwaysApply) + `contextforge-active.mdc` (Auto-Attached) | Rule por glob regenerada por `forge context` (Cursor no tiene hooks reactivos) | Listo |
| **OpenCode** | `opencode.json` con MCP server registrado | Tool MCP `select_agent_context({task})` live | Listo |
| **Codex / otros** | Leen `.contextforge/*.json` directamente | Lectura directa de `agent-manifest.json` | Compatible |
Los `.contextforge/*.json` son **portables**. Cualquier agente futuro los puede consumir sin modificación.
---
## Principios
- **Determinismo primero**: ningún comando llama a un LLM. Reproducible, auditable, cacheable.
- **OpenSpec por defecto**: `forge spec` delega a OpenSpec CLI cuando está, fallback determinista cuando no.
- **Cache por hash**: si `scan.json` no cambió, `forge graph` salta el rebuild.
- **Presupuesto explícito**: cada context-pack tiene `maxInputTokens` trazable en `token-ledger.json`.
- **JSON validado**: ningún artefacto se escribe sin pasar JSON Schema.
- **Portable**: cualquier agente puede consumir `.contextforge/*.json` sin modificación.
- **Sin lock-in**: si mañana sale otra herramienta SDD, lee los mismos JSON.
---
## Para la exposición — los 5 puntos memorables
1. **94 % menos tokens por sesión** — verificado con `token-ledger.json`, no estimado.
2. **÷28× costo SDD con prompt caching** — porque el output es determinista byte-a-byte.
3. **No competimos con OpenSpec, lo apoyamos**. Tres roles separados (CF / OpenSpec / agente). Si OpenSpec evoluciona, no nos pisamos.
4. **El agente solo carga lo de tu tarea actual** — context-pack + manifest. Las 100 skills viejas no entran al contexto.
5. **Sin lock-in**. Los `.contextforge/*.json` son neutrales. Si mañana sale otra herramienta, los puede consumir igual.
---
## Estructura del monorepo
```
packages/
core/ → scanner, parser tree-sitter, graph builder, selector PageRank,
packer, schema validator, skill builder, agent-manifest, spec-input,
spec promptRenderer
cli/ → comandos forge (init, scan, graph, context, spec, implement, manifest,
skills, sync, impact, viz, docs)
mcp/ → servidor MCP con 7 tools
.claude/skills/ → 3 skills task-oriented (contextforge-*) +
N auto-generadas (contextforge-domain-*) +
agent-manifest.md (per-task)
.cursor/rules/ → contextforge.mdc (alwaysApply) +
contextforge-active.mdc (Auto-Attached, regenerada por tarea)
opencode.json → configuración MCP para OpenCode
docs/integrations/ → guías de wiring por agente
docs/explanation/ → contextforge-and-openspec.md (lectura clave)
```
---
## Tests
```bash
pnpm test # todos los tests
pnpm test:coverage # con cobertura
pnpm typecheck # tsc -b --force (project references)
```
Suite actual: **270/270 tests pasando** (26 archivos), coverage global ≥ 80 % · módulos `manifest/` y `spec/` ≥ 95 %.
---
## Documentación
| Documento | Descripción |
| ----------------------------------------------- | --------------------------------------------------------------------------------------- |
| `docs/explanation/contextforge-and-openspec.md` | **Lectura clave**: 3 roles (CF / OpenSpec / agente), ahorro medido, prompt caching ÷28× |
| `docs/how-to/use-contextforge.md` | Guía paso a paso: instalación, pipeline, integración por agente |
| `docs/integrations/claude-code-hook.md` | Snippet copy-paste para activar manifest en runtime en Claude Code |
| `docs/integrations/cursor-rules.md` | Los 3 modos de rules en Cursor + estrategia recomendada |
| `docs/integrations/opencode-mcp.md` | Ejemplo de `select_agent_context` desde OpenCode |
| `docs/token-savings-architecture.md` | Análisis de ahorro de tokens por capas con tabla de costos |
| `docs/EXAMPLES/end-to-end-flow.md` | Walkthrough completo del pipeline con outputs reales |
| `docs/IMPLEMENTATION_TASKS.md` | Backlog de sprints con criterios verificables |
| `docs/CHANGELOG-schemas.md` | Historial de cambios de schemas |
| `openspec/changes/forge-spec-as-prepare-step/` | OpenSpec change del re-arquitectura v0.3.0 |
| `openspec/changes/agent-manifest/` | OpenSpec change del manifest por tarea |
| `openspec/changes/auto-domain-skills/` | OpenSpec change de las skills auto-generadas |
| `CONTEXTFORGE_SOURCE_OF_TRUTH.md` | Decisiones de diseño, arquitectura, roadmap |
---
## Licencia
MIT
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues