@cristiancorreau/forge 2.9.5 → 2.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/assets/adapters/claude-code/commands/new-feature.md +10 -5
  2. package/assets/adapters/claude-code/commands/plan.md +61 -36
  3. package/assets/adapters/claude-code/commands/session-start.md +1 -1
  4. package/assets/adapters/claude-code/commands/ship.md +6 -4
  5. package/assets/adapters/claude-code/commands/work.md +8 -6
  6. package/assets/core/skills/README.md +2 -2
  7. package/assets/core/skills/aitmpl-search/SKILL.md +7 -19
  8. package/assets/core/skills/local2prod/SKILL.md +1 -1
  9. package/assets/core/skills/new-feature/SKILL.md +1 -1
  10. package/assets/core/skills/phase-kickoff/SKILL.md +2 -0
  11. package/assets/core/skills/spec/SKILL.md +2 -0
  12. package/assets/core/skills/wiki-ingest/SKILL.md +7 -7
  13. package/assets/core/skills/wiki-lint/SKILL.md +4 -4
  14. package/assets/core/skills/wiki-query/SKILL.md +3 -3
  15. package/dist/commands/doctor.d.ts.map +1 -1
  16. package/dist/commands/doctor.js +2 -1
  17. package/dist/commands/doctor.js.map +1 -1
  18. package/dist/commands/init.js +1 -1
  19. package/dist/lib/paths.d.ts +1 -2
  20. package/dist/lib/paths.d.ts.map +1 -1
  21. package/dist/lib/paths.js +12 -16
  22. package/dist/lib/paths.js.map +1 -1
  23. package/dist/version.d.ts +1 -1
  24. package/dist/version.js +1 -1
  25. package/package.json +2 -2
  26. package/assets/adapters/claude-code/generate-claude-md.py +0 -304
  27. package/assets/adapters/codex/generate-codex-config.py +0 -269
  28. package/assets/adapters/kiro/generate-steering.py +0 -367
  29. package/assets/adapters/opencode/generate-agents-md.py +0 -262
  30. package/assets/core/hooks/pre-bash-check.py +0 -202
  31. package/assets/core/hooks/pre-edit-check.py +0 -317
  32. package/assets/forge.py +0 -1265
  33. package/assets/requirements.txt +0 -2
  34. package/assets/scripts/aitmpl-search.py +0 -808
  35. package/assets/scripts/forge-add-opportunities.py +0 -92
  36. package/assets/scripts/forge-audit.py +0 -1061
  37. package/assets/scripts/forge-generate-all.py +0 -283
  38. package/assets/scripts/forge-init.py +0 -900
  39. package/assets/scripts/forge-migrate-project-yaml.py +0 -397
  40. package/assets/scripts/forge-scaffold-profile.py +0 -181
  41. package/assets/scripts/forge-teardown.py +0 -193
  42. package/assets/scripts/forge-validate-project-yaml.py +0 -457
  43. package/assets/scripts/forge-wizard.py +0 -1003
  44. package/assets/scripts/setup-codex.sh +0 -229
  45. package/assets/scripts/team-install.sh +0 -147
  46. package/assets/scripts/token-stats.py +0 -201
  47. package/dist/lib/python.d.ts +0 -4
  48. package/dist/lib/python.d.ts.map +0 -1
  49. package/dist/lib/python.js +0 -46
  50. package/dist/lib/python.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cristiancorreau/forge",
3
- "version": "2.9.5",
3
+ "version": "2.9.7",
4
4
  "description": "Agentic development framework — multi-runtime support for Claude Code, OpenCode, Codex and Kiro",
5
5
  "author": "Cristian Correa <cristian@socialweb.cl>",
6
6
  "license": "Apache-2.0",
@@ -28,7 +28,7 @@
28
28
  "build:all": "npm run build:assets && npm run build",
29
29
  "dev": "tsc --watch",
30
30
  "prepublishOnly": "npm run build:all",
31
- "test": "node --test test/commands.test.mjs"
31
+ "test": "node --test test/commands.test.mjs test/assets.test.mjs"
32
32
  },
33
33
  "files": [
34
34
  "dist",
@@ -1,304 +0,0 @@
1
- #!/usr/bin/env python3
2
- # Copyright 2026 Cristian Correa — Apache License 2.0
3
- # https://github.com/cristiancorreau/forge
4
- """
5
- Genera CLAUDE.md para un proyecto usando forge.
6
-
7
- Usage:
8
- python3 .agentic/adapters/claude-code/generate-claude-md.py
9
-
10
- Lee project.yaml en la raíz y genera CLAUDE.md adaptado al stack del proyecto.
11
- Si ya existe un CLAUDE.md, muestra el diff antes de sobreescribir.
12
-
13
- Requiere: pyyaml
14
- """
15
- import os
16
- import sys
17
- from pathlib import Path
18
-
19
- FORCE = "--force" in sys.argv
20
-
21
- try:
22
- import yaml
23
- except ImportError:
24
- print("ERROR: pyyaml requerido. pip install pyyaml", file=sys.stderr)
25
- sys.exit(1)
26
-
27
-
28
- def find_project_root() -> Path:
29
- here = Path.cwd()
30
- for p in [here] + list(here.parents):
31
- if (p / "project.yaml").exists():
32
- return p
33
- raise FileNotFoundError("No se encontró project.yaml")
34
-
35
-
36
- def _render_phases(config: dict) -> str:
37
- sprint = config.get("sprint", {})
38
- current = sprint.get("current", 1)
39
- phases = sprint.get("phases", [])
40
- if not phases:
41
- return (
42
- f"- **Sprint actual:** Sprint {current}\n"
43
- "- **Completadas:** —\n"
44
- "- **En curso:** —\n"
45
- "- **Pendientes:** —"
46
- )
47
- lines = [f"- **Sprint actual:** Sprint {current}"]
48
- for phase in phases:
49
- pid = phase.get("id", "?")
50
- name = phase.get("name", "")
51
- specs = phase.get("specs", [])
52
- status = phase.get("status", "pendiente")
53
- spec_list = ", ".join(specs) if specs else "—"
54
- lines.append(f"- **Fase {pid} — {name}** ({status}): {spec_list}")
55
- return "\n".join(lines)
56
-
57
-
58
- _AGENT_TRIGGER = {
59
- "orchestrator": ("tareas multi-agente, análisis de >3 archivos, descomposición de features completas", None),
60
- "backend-engineer": ("endpoints, middleware, validaciones, lógica de negocio", "api"),
61
- "api-engineer": ("endpoints REST, middleware, validaciones, migraciones de BD", "api"),
62
- "frontend-engineer": ("componentes UI, páginas, estilos, integración con API", "frontend"),
63
- "admin-engineer": ("UI de gestión interna, dashboards de admin", "admin"),
64
- "mobile-engineer": ("pantallas móviles, navegación, stores de estado", "mobile"),
65
- "fullstack-engineer": ("features full-stack end-to-end que abarcan backend y frontend", None),
66
- "test-engineer": ("tests unitarios, integración, E2E — nunca código de producción", "tests"),
67
- "docs-writer": ("specs, ADRs, READMEs, documentación — nunca código de producción", "specs"),
68
- "compliance-reviewer": ("revisión de PRs con PII, consentimientos, logs de auditoría", None),
69
- "security-auditor": ("auditoría de vulnerabilidades, revisión de dependencias, pentest", None),
70
- "migration-specialist": ("migraciones de versión de framework (ej: L6→L13)", "migrations"),
71
- "wp-engineer": ("temas WordPress, FSE, Gutenberg, child themes", "frontend"),
72
- "divi-engineer": ("layouts Divi 5, módulos custom, Divi Builder", "frontend"),
73
- "elementor-engineer": ("templates Elementor Pro, widgets custom", "frontend"),
74
- "scanner-engineer": ("scraping, crawling, extracción estructurada de datos", "scanner"),
75
- }
76
-
77
-
78
- def _build_agent_scope_table(agents_cfg: dict, paths: dict) -> str:
79
- active = agents_cfg.get("active", [])
80
- compliance = agents_cfg.get("compliance", [])
81
- agent_paths = {**(paths or {})}
82
- rows = []
83
- for agent in active + compliance:
84
- trigger, path_key = _AGENT_TRIGGER.get(agent, ("implementación", None))
85
- scope = agent_paths.get(path_key) if path_key else None
86
- scope_str = f"`{scope}`" if scope else "`/`"
87
- rows.append(f"| `{agent}` | {scope_str} | {trigger} |")
88
- if not rows:
89
- return ""
90
- header = (
91
- "## Agentes y su scope\n\n"
92
- "| Agente | Scope | Cuándo usarlo |\n"
93
- "|--------|-------|---------------|\n"
94
- )
95
- return header + "\n".join(rows) + "\n\n> Invocar el agente del scope correcto, no el orchestrator, para tareas acotadas.\n\n"
96
-
97
-
98
- def generate_claude_md(config: dict) -> str:
99
- proj = config.get("project", {})
100
- stack = config.get("stack", {})
101
- team = config.get("team", {})
102
- compliance_cfg = config.get("compliance", {})
103
- agents_cfg = config.get("agents", {})
104
- paths = config.get("paths", {})
105
-
106
- name = proj.get("name", "Mi Proyecto")
107
- description = proj.get("description", "")
108
- language = proj.get("language", "typescript")
109
- backend = stack.get("backend") or "N/A"
110
- frontend = stack.get("frontend") or "N/A"
111
- database = stack.get("database") or "N/A"
112
- frameworks = compliance_cfg.get("frameworks", [])
113
- specs_path = paths.get("specs", "docs/specs")
114
- progress_path = paths.get("progress", "docs/progress.html")
115
- agent_scope_section = _build_agent_scope_table(agents_cfg, paths)
116
-
117
- # Agentes activos
118
- active = agents_cfg.get("active", [])
119
-
120
- # Comandos según lenguaje
121
- if language == "typescript":
122
- dev_cmd = "pnpm dev"
123
- test_cmd = "pnpm test"
124
- lint_cmd = "pnpm lint"
125
- build_cmd = "pnpm build"
126
- elif language == "python":
127
- dev_cmd = "python manage.py runserver # o uvicorn main:app"
128
- test_cmd = "pytest"
129
- lint_cmd = "ruff check ."
130
- build_cmd = "# no aplica"
131
- elif language == "ruby":
132
- dev_cmd = "rails server"
133
- test_cmd = "bundle exec rspec"
134
- lint_cmd = "rubocop"
135
- build_cmd = "# no aplica"
136
- elif language == "go":
137
- dev_cmd = "go run ./cmd/..."
138
- test_cmd = "go test ./..."
139
- lint_cmd = "golangci-lint run"
140
- build_cmd = "go build ./..."
141
- elif language == "php":
142
- dev_cmd = "php artisan serve # o php -S localhost:8000 -t public"
143
- test_cmd = "vendor/bin/phpunit # o ./vendor/bin/pest"
144
- lint_cmd = "vendor/bin/phpstan analyse"
145
- build_cmd = "composer install --no-dev --optimize-autoloader"
146
- elif language == "mixed":
147
- dev_cmd = "# ver documentación del proyecto (stack mixto)"
148
- test_cmd = "# ver documentación del proyecto (stack mixto)"
149
- lint_cmd = "# ver documentación del proyecto (stack mixto)"
150
- build_cmd = "# ver documentación del proyecto (stack mixto)"
151
- else:
152
- dev_cmd = "# ver documentación del proyecto"
153
- test_cmd = "# ver documentación del proyecto"
154
- lint_cmd = "# ver documentación del proyecto"
155
- build_cmd = "# ver documentación del proyecto"
156
-
157
- compliance_section = ""
158
- if frameworks:
159
- compliance_section = f"""
160
- ## Compliance activo
161
-
162
- Este proyecto opera bajo los siguientes marcos regulatorios:
163
- {chr(10).join(f'- **{f.upper()}**' for f in frameworks)}
164
-
165
- Reglas no-negociables:
166
- - Sin datos personales en logs de stdout
167
- - Consentimiento explícito antes de cualquier tracker no esencial
168
- - Logs de auditoría append-only (sin UPDATE/DELETE)
169
- - Derechos del titular implementados (acceso, rectificación, supresión, oposición, portabilidad)
170
- """
171
-
172
- return f"""# CLAUDE.md — {name}
173
-
174
- > Generado por forge. Actualizar project.yaml para cambiar la configuración.
175
- > Si actualizás el código de manera que invalide algo aquí, actualizá este archivo en el mismo PR.
176
-
177
- ## Misión del proyecto
178
-
179
- {description}
180
-
181
- ## Stack
182
-
183
- - **Lenguaje**: {language}
184
- - **Backend**: {backend}
185
- - **Frontend**: {frontend}
186
- - **Base de datos**: {database}
187
- - **Testing**: {", ".join(stack.get("testing", []))}
188
-
189
- {agent_scope_section}## Estructura
190
-
191
- ```
192
- {proj.get("slug", "proyecto")}/
193
- ├── CLAUDE.md ← Estás acá
194
- ├── AGENTS.md ← Convenciones del agent team
195
- ├── project.yaml ← Config de forge (fuente de verdad)
196
- ├── {specs_path}/ ← Specs de features (requeridas antes de implementar)
197
- └── {progress_path} ← Dashboard de progreso del proyecto
198
- ```
199
-
200
- ## Cómo trabajar (SDD)
201
-
202
- Cuando recibás una tarea:
203
-
204
- 1. **Identificá la spec.** Si no está en `{specs_path}/`, pará y pedí que se cree.
205
- 2. **Leé la spec correspondiente.**
206
- 3. **Leé el `CLAUDE.md` del módulo** que vas a modificar (si existe).
207
- 4. **Proponé opciones** para decisiones arquitectónicas no cubiertas por la spec.
208
- 5. **Esperá aprobación** antes de generar código.
209
- 6. **Tests** junto con la implementación, no al final.
210
- 7. **Al terminar**, actualizá la spec con decisiones tomadas durante la implementación.
211
- 8. **Antes de cerrar**: `{test_cmd}`, `{lint_cmd}`.
212
- {compliance_section}
213
- ## Phases activas y estado
214
-
215
- {_render_phases(config)}
216
-
217
- > Actualizar `project.yaml` (sprint.phases) para reflejar el estado real.
218
-
219
- ## Comandos frecuentes
220
-
221
- ```bash
222
- {dev_cmd} # Desarrollo
223
- {test_cmd} # Tests
224
- {lint_cmd} # Lint
225
- {build_cmd} # Build
226
- ```
227
-
228
- ## Qué NO hacer
229
-
230
- - No implementar sin spec en `{specs_path}/`
231
- - No hardcodear tokens, passwords o secrets
232
- - No usar `any` en TypeScript sin comentario que explique por qué
233
- - No commits con `console.log` o `print` de depuración
234
- - No hacer force push a main/master
235
- """
236
-
237
-
238
- def _generate_architecture_rules(root: Path, forge: Path, config: dict):
239
- """Crea .claude/architecture.rules desde el template si no existe. Nunca sobreescribe."""
240
- dst = root / ".claude" / "architecture.rules"
241
- if dst.exists():
242
- return
243
-
244
- # Buscar el template en forge
245
- tpl_path = forge / "core" / "templates" / "claude-md" / "architecture.rules"
246
- if not tpl_path.exists():
247
- return
248
-
249
- project_name = config.get("project", {}).get("name", "Mi Proyecto")
250
- content = tpl_path.read_text(encoding="utf-8")
251
- content = content.replace("<NOMBRE_PROYECTO>", project_name)
252
-
253
- dst.parent.mkdir(parents=True, exist_ok=True)
254
- dst.write_text(content, encoding="utf-8")
255
- print(f"[OK] .claude/architecture.rules — creado desde template")
256
-
257
-
258
- def _find_forge_dir(root: Path) -> Path:
259
- """Localiza el directorio de forge relativo a este script."""
260
- # Este script está en forge/adapters/claude-code/generate-claude-md.py
261
- script_dir = Path(__file__).parent
262
- # forge_dir = forge/
263
- candidate = script_dir.parent.parent
264
- if (candidate / "core").exists():
265
- return candidate
266
- # Fallback: buscar .agentic o forge en la raíz del proyecto
267
- for name in (".agentic", "forge"):
268
- c = root / name
269
- if (c / "core").exists():
270
- return c
271
- return candidate
272
-
273
-
274
- def main():
275
- try:
276
- root = find_project_root()
277
- except FileNotFoundError as e:
278
- print(f"ERROR: {e}", file=sys.stderr)
279
- sys.exit(1)
280
-
281
- with open(root / "project.yaml") as f:
282
- config = yaml.safe_load(f)
283
-
284
- content = generate_claude_md(config)
285
- output_path = root / "CLAUDE.md"
286
-
287
- if output_path.exists() and not FORCE:
288
- print(f"CLAUDE.md ya existe en {root}. Sobreescribir? [s/N] ", end="")
289
- resp = input().strip().lower()
290
- if resp not in ("s", "si", "sí", "y", "yes"):
291
- print("Cancelado.")
292
- sys.exit(0)
293
-
294
- with open(output_path, "w") as f:
295
- f.write(content)
296
-
297
- print(f"CLAUDE.md generado en {output_path}")
298
-
299
- forge = _find_forge_dir(root)
300
- _generate_architecture_rules(root, forge, config)
301
-
302
-
303
- if __name__ == "__main__":
304
- main()
@@ -1,269 +0,0 @@
1
- #!/usr/bin/env python3
2
- # Copyright 2026 Cristian Correa — Apache License 2.0
3
- # https://github.com/cristiancorreau/forge
4
- """
5
- generate-codex-config.py — Genera AGENTS.md para Codex CLI (OpenAI).
6
-
7
- Usage:
8
- python3 .agentic/adapters/codex/generate-codex-config.py
9
-
10
- Lee project.yaml en la raíz y genera AGENTS.md enriquecido para Codex CLI.
11
-
12
- Diferencias respecto al adapter OpenCode:
13
- - Incluye sección "Workflow SDD" con pasos explícitos (Codex opera autónomamente)
14
- - Incluye "Security rules" y "Autonomy limits" inline (crítico para ejecución sin supervisión)
15
- - Header identifica el archivo como generado para Codex CLI
16
-
17
- Codex CLI lee AGENTS.md desde la raíz del repositorio como contexto del agente.
18
- Referencia: https://github.com/openai/codex
19
-
20
- Requiere: pyyaml
21
- """
22
- from __future__ import annotations
23
-
24
- import sys
25
- from pathlib import Path
26
-
27
- try:
28
- import yaml
29
- except ImportError:
30
- print("ERROR: pyyaml requerido. pip install pyyaml", file=sys.stderr)
31
- sys.exit(1)
32
-
33
-
34
- def find_project_root() -> Path:
35
- here = Path.cwd()
36
- for p in [here] + list(here.parents):
37
- if (p / "project.yaml").exists():
38
- return p
39
- raise FileNotFoundError("No se encontró project.yaml")
40
-
41
-
42
- def find_forge_dir() -> Path:
43
- root = find_project_root()
44
- for candidate in [root / ".agentic", root / "forge", Path(__file__).parent.parent.parent]:
45
- if (candidate / "core").exists():
46
- return candidate
47
- raise FileNotFoundError("No se encontró el directorio forge con core/")
48
-
49
-
50
- def read_agent_description(forge: Path, name: str, profiles: list[str]) -> str:
51
- """Lee el frontmatter description del agente desde forge (profiles > core)."""
52
- for profile in profiles:
53
- p = forge / "profiles" / profile / "agents" / f"{name}.md"
54
- if p.exists():
55
- content = p.read_text()
56
- for line in content.splitlines():
57
- if line.startswith("description:"):
58
- return line.split(":", 1)[1].strip().strip('"')
59
- p = forge / "core" / "agents" / f"{name}.md"
60
- if p.exists():
61
- content = p.read_text()
62
- for line in content.splitlines():
63
- if line.startswith("description:"):
64
- return line.split(":", 1)[1].strip().strip('"')
65
- return "Agente de implementación"
66
-
67
-
68
- def generate_agents_md(config: dict, forge: Path) -> str:
69
- """
70
- Genera AGENTS.md enriquecido para Codex CLI.
71
-
72
- Codex CLI opera autónomamente en terminal: las reglas de seguridad y los
73
- límites de autonomía deben estar inline en AGENTS.md, no en un archivo
74
- separado, para que el agente las reciba como contexto en cada sesión.
75
- """
76
- proj = config.get("project", {})
77
- agents_cfg = config.get("agents", {})
78
- compliance_cfg = config.get("compliance", {})
79
- stack = config.get("stack", {})
80
- paths = config.get("paths", {})
81
-
82
- name = proj.get("name", "Mi Proyecto")
83
- description = proj.get("description", "")
84
- language = proj.get("language", "typescript")
85
- active = agents_cfg.get("active", [])
86
- compliance = agents_cfg.get("compliance", [])
87
- specialized = agents_cfg.get("specialized", [])
88
- profiles = agents_cfg.get("profiles", [])
89
- frameworks = compliance_cfg.get("frameworks", [])
90
- specs_path = paths.get("specs", "docs/specs")
91
-
92
- if frameworks and "compliance-reviewer" not in active + compliance:
93
- compliance = list(set(compliance + ["compliance-reviewer"]))
94
-
95
- lines = [
96
- f"# AGENTS.md — {name}",
97
- "",
98
- f"> Generado por forge (adapter Codex CLI).",
99
- f"> Fuente de verdad: `project.yaml`. Re-ejecutar `generate-codex-config.py` al cambiar agentes.",
100
- "",
101
- ]
102
-
103
- if description:
104
- lines += [description, ""]
105
-
106
- lines += [
107
- "## Stack",
108
- "",
109
- f"- **Lenguaje:** {language}",
110
- f"- **Backend:** {stack.get('backend') or 'N/A'}",
111
- f"- **Frontend:** {stack.get('frontend') or 'N/A'}",
112
- f"- **Base de datos:** {stack.get('database') or 'N/A'}",
113
- f"- **Testing:** {', '.join(stack.get('testing', []))}",
114
- "",
115
- "## Workflow: Spec-Driven Development",
116
- "",
117
- "Antes de implementar cualquier feature:",
118
- "",
119
- f"1. Verificar que existe una spec en `{specs_path}/`.",
120
- "2. Si no existe, crearla y esperar aprobación antes de codificar.",
121
- "3. Implementar con tests junto al código, no al final.",
122
- "4. Actualizar la spec con decisiones tomadas durante la implementación.",
123
- "",
124
- "## Reglas de seguridad (no negociables)",
125
- "",
126
- "- Sin hardcodear tokens, passwords ni secrets — usar variables de entorno.",
127
- "- Parámetros preparados en todas las queries SQL — nunca concatenar input del usuario.",
128
- "- PII nunca en logs de stdout.",
129
- "- Verificar autenticación Y autorización en cada endpoint de API.",
130
- "- Sin force push a main/master.",
131
- "",
132
- "## Límites de autonomía",
133
- "",
134
- "- Modificar solo archivos dentro del scope declarado del agente.",
135
- "- Preguntar antes de eliminar archivos o ejecutar comandos destructivos.",
136
- "- Nunca hacer commit directo a main/master.",
137
- "- Correr los tests antes de marcar una tarea como completa.",
138
- "- Si una tarea requiere acceso a credenciales no disponibles, detenerse y reportar.",
139
- "",
140
- "## Roster de agentes",
141
- "",
142
- ]
143
-
144
- if active:
145
- lines += ["### Activos", ""]
146
- for agent in active:
147
- desc = read_agent_description(forge, agent, profiles)
148
- lines.append(f"#### `{agent}`")
149
- lines.append(desc)
150
- lines.append("")
151
-
152
- if compliance:
153
- lines += ["### Compliance y revisión", ""]
154
- for agent in compliance:
155
- desc = read_agent_description(forge, agent, profiles)
156
- lines.append(f"#### `{agent}`")
157
- lines.append(desc)
158
- lines.append("")
159
-
160
- if specialized:
161
- lines += ["### Especializados del proyecto", ""]
162
- for agent in specialized:
163
- desc = read_agent_description(forge, agent, profiles)
164
- lines.append(f"#### `{agent}`")
165
- lines.append(desc)
166
- lines.append("")
167
-
168
- if frameworks:
169
- lines += [
170
- "## Compliance activo",
171
- "",
172
- f"Marcos regulatorios: {', '.join(f.upper() for f in frameworks)}",
173
- "",
174
- "Incluir `compliance-reviewer` en toda tarea que toque:",
175
- "- Datos de usuarios o consentimientos",
176
- "- Logs de auditoría",
177
- "- Endpoints de derechos del titular (DSAR)",
178
- "",
179
- ]
180
-
181
- lines += [
182
- "## Forge v2 Commands",
183
- "",
184
- "Codex CLI no soporta slash commands. Usa las plantillas de prompt de",
185
- "`adapters/codex/commands/` copiando su contenido directamente en tu sesión.",
186
- "",
187
- "| Comando | Archivo | Cuándo usarlo |",
188
- "|---------|---------|---------------|",
189
- "| plan | `adapters/codex/commands/plan.md` | Antes de implementar — verifica o crea la spec |",
190
- "| work | `adapters/codex/commands/work.md` | Implementar una feature con spec APPROVED |",
191
- "| review | `adapters/codex/commands/review.md` | Revisar código o cambios antes del merge |",
192
- "| ship | `adapters/codex/commands/ship.md` | Verificar que el proyecto está listo para deploy |",
193
- "| session-start | `adapters/codex/commands/session-start.md` | Al iniciar cada sesión de trabajo |",
194
- "| session-close | `adapters/codex/commands/session-close.md` | Al cerrar cada sesión de trabajo |",
195
- "",
196
- "### Flujo SDD recomendado",
197
- "",
198
- "```",
199
- "1. session-start → verificar ambiente y branch",
200
- "2. plan → spec antes que código (DRAFT → APPROVED)",
201
- "3. work → implementar con tests (spec APPROVED → IMPLEMENTED)",
202
- "4. review → checklist de seguridad y calidad",
203
- "5. ship → verificar build, tests y variables antes del deploy",
204
- "6. session-close → registrar progreso y limpiar",
205
- "```",
206
- "",
207
- "## Reglas de producción (pre-bash-check)",
208
- "",
209
- "Los siguientes comandos están bloqueados en contexto de producción.",
210
- "Si necesitás ejecutarlos, hacelo manualmente en la terminal con plena consciencia:",
211
- "",
212
- "- `--force-reset` — puede destruir datos de base de datos",
213
- "- `prisma migrate reset` — borra y re-crea la base de datos",
214
- "- `DROP TABLE` / `DROP DATABASE` — destruye tablas o bases de datos",
215
- "- `TRUNCATE` — elimina todos los registros de una tabla",
216
- "- `DELETE FROM <tabla>;` — sin cláusula WHERE, elimina todo",
217
- "- `dropdb` — elimina una base de datos completa",
218
- "- `rm -rf /` — elimina recursivamente desde la raíz",
219
- "- `git push --force` sin `--with-lease` — sobreescribe historial remoto",
220
- "",
221
- "Contexto de producción = la URL de producción o el project_id aparecen",
222
- "en el comando, o hay variables de entorno `PROD_*` / `PRODUCTION_*` activas.",
223
- "",
224
- "Lección del 2026-04-28: --force-reset borró 225 usuarios y 35 formularios",
225
- "en producción. Estas reglas existen por eso.",
226
- "",
227
- "## Branch guard (pre-edit-check)",
228
- "",
229
- "No editar archivos de código fuente directamente en `main` o `master`.",
230
- "",
231
- "Archivos de código = `.py`, `.ts`, `.js`, `.tsx`, `.jsx`, `.php`, `.rb`,",
232
- "`.go`, `.rs`, `.java`, `.cs`, `.cpp`, `.c`, `.sh`",
233
- "",
234
- "Excenciones permitidas en rama protegida:",
235
- "- Archivos de documentación (`docs/`, `.md`)",
236
- "- Archivos de configuración en raíz (`.yaml`, `.yml`, `.json`)",
237
- "- `CLAUDE.md`, `AGENTS.md`, `README.md`, `CHANGELOG.md`",
238
- "",
239
- "Si estás en `main` o `master`, crea una feature branch primero:",
240
- "```bash",
241
- "git checkout -b feature/<tema>-$(date +%Y-%m-%d)",
242
- "```",
243
- "",
244
- ]
245
-
246
- return "\n".join(lines)
247
-
248
-
249
- def main():
250
- try:
251
- root = find_project_root()
252
- forge = find_forge_dir()
253
- except FileNotFoundError as e:
254
- print(f"ERROR: {e}", file=sys.stderr)
255
- sys.exit(1)
256
-
257
- with open(root / "project.yaml") as f:
258
- config = yaml.safe_load(f)
259
-
260
- content = generate_agents_md(config, forge)
261
- output_path = root / "AGENTS.md"
262
- with open(output_path, "w") as f:
263
- f.write(content)
264
- print(f" [OK] AGENTS.md generado en {output_path}")
265
- print(" (adapter Codex: incluye SDD workflow + security rules + autonomy limits)")
266
-
267
-
268
- if __name__ == "__main__":
269
- main()