@cristiancorreau/forge 2.9.6 → 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.
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +2 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/lib/paths.d.ts +1 -2
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/paths.js +12 -16
- package/dist/lib/paths.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/assets/adapters/claude-code/generate-claude-md.py +0 -304
- package/assets/adapters/codex/generate-codex-config.py +0 -269
- package/assets/adapters/kiro/generate-steering.py +0 -367
- package/assets/adapters/opencode/generate-agents-md.py +0 -262
- package/assets/core/hooks/pre-bash-check.py +0 -202
- package/assets/core/hooks/pre-edit-check.py +0 -317
- package/assets/forge.py +0 -1265
- package/assets/requirements.txt +0 -2
- package/assets/scripts/aitmpl-search.py +0 -808
- package/assets/scripts/forge-add-opportunities.py +0 -92
- package/assets/scripts/forge-audit.py +0 -1061
- package/assets/scripts/forge-generate-all.py +0 -283
- package/assets/scripts/forge-init.py +0 -900
- package/assets/scripts/forge-migrate-project-yaml.py +0 -397
- package/assets/scripts/forge-scaffold-profile.py +0 -181
- package/assets/scripts/forge-teardown.py +0 -193
- package/assets/scripts/forge-validate-project-yaml.py +0 -457
- package/assets/scripts/forge-wizard.py +0 -1003
- package/assets/scripts/setup-codex.sh +0 -229
- package/assets/scripts/team-install.sh +0 -147
- package/assets/scripts/token-stats.py +0 -201
- package/dist/lib/python.d.ts +0 -4
- package/dist/lib/python.d.ts.map +0 -1
- package/dist/lib/python.js +0 -46
- package/dist/lib/python.js.map +0 -1
|
@@ -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()
|
|
@@ -1,367 +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-steering.py — Genera archivos .kiro/steering/ para Kiro IDE.
|
|
6
|
-
|
|
7
|
-
Usage:
|
|
8
|
-
python3 .agentic/adapters/kiro/generate-steering.py
|
|
9
|
-
|
|
10
|
-
Lee project.yaml en la raíz y genera los steering files de Kiro:
|
|
11
|
-
.kiro/steering/product.md ← descripción del producto y stack
|
|
12
|
-
.kiro/steering/structure.md ← estructura del proyecto, workflow SDD y flujo de sesión Forge v2
|
|
13
|
-
.kiro/steering/agents.md ← roster de agentes y sus responsabilidades
|
|
14
|
-
.kiro/steering/commands.md ← 6 comandos Forge para contexto del agente Kiro
|
|
15
|
-
.kiro/steering/compliance.md ← reglas de compliance (si hay frameworks activos)
|
|
16
|
-
.kiro/hooks/pre-edit-branch-guard.json ← hook que bloquea ediciones directas en main/master
|
|
17
|
-
|
|
18
|
-
Kiro usa estos archivos como contexto persistente en todas las conversaciones.
|
|
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 write_file(path: Path, content: str, force: bool = False) -> str:
|
|
51
|
-
if path.exists() and not force:
|
|
52
|
-
return "KEEP"
|
|
53
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
-
path.write_text(content)
|
|
55
|
-
return "OK"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def generate_product_md(config: dict) -> str:
|
|
59
|
-
proj = config.get("project", {})
|
|
60
|
-
stack = config.get("stack", {})
|
|
61
|
-
team = config.get("team", {})
|
|
62
|
-
|
|
63
|
-
return f"""# {proj.get('name', 'Mi Proyecto')}
|
|
64
|
-
|
|
65
|
-
{proj.get('description', '')}
|
|
66
|
-
|
|
67
|
-
## Stack
|
|
68
|
-
|
|
69
|
-
- **Lenguaje:** {proj.get('language', 'typescript')}
|
|
70
|
-
- **Backend:** {stack.get('backend') or 'N/A'}
|
|
71
|
-
- **Frontend:** {stack.get('frontend') or 'N/A'}
|
|
72
|
-
- **Base de datos:** {stack.get('database') or 'N/A'}
|
|
73
|
-
- **Cache:** {stack.get('cache') or 'N/A'}
|
|
74
|
-
- **Testing:** {', '.join(stack.get('testing', []))}
|
|
75
|
-
|
|
76
|
-
## Estado del proyecto
|
|
77
|
-
|
|
78
|
-
- **Status:** {proj.get('status', 'active')}
|
|
79
|
-
- **Equipo:** {team.get('name', 'Equipo Principal')}
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def generate_structure_md(config: dict) -> str:
|
|
84
|
-
proj = config.get("project", {})
|
|
85
|
-
paths = config.get("paths", {})
|
|
86
|
-
specs_path = paths.get("specs", "docs/specs")
|
|
87
|
-
language = proj.get("language", "typescript")
|
|
88
|
-
|
|
89
|
-
cmd_map = {
|
|
90
|
-
"typescript": ("pnpm dev", "pnpm test", "pnpm lint"),
|
|
91
|
-
"python": ("uvicorn main:app --reload # o python manage.py runserver", "pytest", "ruff check ."),
|
|
92
|
-
"ruby": ("rails server", "bundle exec rspec", "rubocop"),
|
|
93
|
-
"go": ("go run ./cmd/...", "go test ./...", "golangci-lint run"),
|
|
94
|
-
"php": ("php artisan serve", "vendor/bin/phpunit", "vendor/bin/phpstan analyse"),
|
|
95
|
-
}
|
|
96
|
-
dev_cmd, test_cmd, lint_cmd = cmd_map.get(language, ("# ver docs del proyecto",) * 3)
|
|
97
|
-
|
|
98
|
-
return f"""# Estructura del proyecto
|
|
99
|
-
|
|
100
|
-
## Workflow: Spec-Driven Development (SDD)
|
|
101
|
-
|
|
102
|
-
Antes de implementar cualquier feature:
|
|
103
|
-
|
|
104
|
-
1. Verificar que existe una spec en `{specs_path}/`.
|
|
105
|
-
2. Si no existe, crear la spec y obtener aprobación antes de codificar.
|
|
106
|
-
3. Implementar con tests junto al código, no al final.
|
|
107
|
-
4. Actualizar la spec con decisiones tomadas durante la implementación.
|
|
108
|
-
|
|
109
|
-
## Flujo de sesión — Forge v2
|
|
110
|
-
|
|
111
|
-
Cada sesión de trabajo sigue este orden:
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
session-start → Leer AGENTS.md + specs relevantes. Confirmar rama activa.
|
|
115
|
-
plan → Descomponer la tarea. Identificar spec. Presentar opciones si hay decisiones abiertas.
|
|
116
|
-
work → Implementar con tests. Un agente por scope. Actualizar spec si hay desvíos.
|
|
117
|
-
review → Lint + tests verdes. Compliance review si toca PII o auth.
|
|
118
|
-
ship → PR abierto, CI verde. No mergear sin revisión si >5 archivos del mismo módulo.
|
|
119
|
-
session-close → Reportar cambios, decisiones tomadas y próximos pasos.
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## Comandos frecuentes
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
{dev_cmd} # Desarrollo
|
|
126
|
-
{test_cmd} # Tests
|
|
127
|
-
{lint_cmd} # Lint
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Reglas no-negociables
|
|
131
|
-
|
|
132
|
-
- Sin hardcodear tokens, passwords ni secrets.
|
|
133
|
-
- Parámetros preparados en todas las queries SQL — nunca concatenar input del usuario.
|
|
134
|
-
- PII nunca en logs de stdout.
|
|
135
|
-
- Auth + authz verificados en cada endpoint.
|
|
136
|
-
- Sin force push a main/master.
|
|
137
|
-
"""
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def generate_agents_md(config: dict) -> str:
|
|
141
|
-
agents_cfg = config.get("agents", {})
|
|
142
|
-
compliance_cfg = config.get("compliance", {})
|
|
143
|
-
|
|
144
|
-
active = agents_cfg.get("active", [])
|
|
145
|
-
compliance = agents_cfg.get("compliance", [])
|
|
146
|
-
specialized = agents_cfg.get("specialized", [])
|
|
147
|
-
frameworks = compliance_cfg.get("frameworks", [])
|
|
148
|
-
|
|
149
|
-
if frameworks and "compliance-reviewer" not in active + compliance:
|
|
150
|
-
compliance = list(set(compliance + ["compliance-reviewer"]))
|
|
151
|
-
|
|
152
|
-
role_descriptions = {
|
|
153
|
-
"orchestrator": "Coordina al team. Descompone tareas, delega y sintetiza. No implementa código directamente.",
|
|
154
|
-
"backend-engineer": "Backend — API, base de datos, lógica de negocio.",
|
|
155
|
-
"frontend-engineer": "Frontend — UI, componentes, integración con API.",
|
|
156
|
-
"api-engineer": "API — framework HTTP + ORM + lógica de negocio.",
|
|
157
|
-
"admin-engineer": "Admin dashboard — UI de gestión interna.",
|
|
158
|
-
"mobile-engineer": "Apps móviles.",
|
|
159
|
-
"fullstack-engineer": "Features full-stack (backend + frontend).",
|
|
160
|
-
"test-engineer": "Testing — unitario, integración, E2E.",
|
|
161
|
-
"docs-writer": "Documentación — specs, ADRs, READMEs.",
|
|
162
|
-
"compliance-reviewer": "Revisa cambios contra marcos regulatorios activos. Tiene poder de veto.",
|
|
163
|
-
"security-auditor": "Auditoría de seguridad — auth, authz, OWASP Top 10.",
|
|
164
|
-
"scanner-engineer": "Scanner de cookies y trackers.",
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
lines = [
|
|
168
|
-
"# Roster de agentes",
|
|
169
|
-
"",
|
|
170
|
-
"## Reglas operativas",
|
|
171
|
-
"",
|
|
172
|
-
"- El orchestrator coordina; los demás agentes no se comunican directamente entre sí.",
|
|
173
|
-
"- Cada agente respeta su scope — no sale del directorio que le corresponde.",
|
|
174
|
-
"- Specs en `docs/specs/` antes de implementar. Sin spec, sin código.",
|
|
175
|
-
"",
|
|
176
|
-
]
|
|
177
|
-
|
|
178
|
-
if active:
|
|
179
|
-
lines += ["## Agentes activos", ""]
|
|
180
|
-
for name in active:
|
|
181
|
-
desc = role_descriptions.get(name, "Agente de implementación")
|
|
182
|
-
lines.append(f"- **`{name}`** — {desc}")
|
|
183
|
-
lines.append("")
|
|
184
|
-
|
|
185
|
-
if compliance:
|
|
186
|
-
lines += ["## Agentes de revisión", ""]
|
|
187
|
-
for name in compliance:
|
|
188
|
-
desc = role_descriptions.get(name, "Agente revisor")
|
|
189
|
-
lines.append(f"- **`{name}`** — {desc}")
|
|
190
|
-
lines.append("")
|
|
191
|
-
|
|
192
|
-
if specialized:
|
|
193
|
-
lines += ["## Agentes especializados del proyecto", ""]
|
|
194
|
-
for name in specialized:
|
|
195
|
-
desc = role_descriptions.get(name, "Agente especializado del proyecto")
|
|
196
|
-
lines.append(f"- **`{name}`** — {desc}")
|
|
197
|
-
lines.append("")
|
|
198
|
-
|
|
199
|
-
return "\n".join(lines)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def generate_commands_md(config: dict) -> str:
|
|
203
|
-
paths = config.get("paths", {})
|
|
204
|
-
specs_path = paths.get("specs", "docs/specs")
|
|
205
|
-
|
|
206
|
-
return f"""# Forge commands — referencia para el agente
|
|
207
|
-
|
|
208
|
-
Este proyecto usa Forge (framework de agentic development). Los siguientes comandos
|
|
209
|
-
son los flujos de trabajo centrales que el agente debe seguir cuando el usuario los invoca.
|
|
210
|
-
|
|
211
|
-
## new-feature
|
|
212
|
-
|
|
213
|
-
Inicia la implementación de una nueva feature siguiendo SDD.
|
|
214
|
-
|
|
215
|
-
1. Si no existe spec para la feature en `{specs_path}/`, crearla primero.
|
|
216
|
-
2. Leer la spec antes de proponer cualquier implementación.
|
|
217
|
-
3. Proponer el plan y esperar aprobación explícita.
|
|
218
|
-
4. Implementar con tests junto a la implementación, no al final.
|
|
219
|
-
5. Al terminar, actualizar la spec con las decisiones tomadas.
|
|
220
|
-
|
|
221
|
-
## review
|
|
222
|
-
|
|
223
|
-
Revisa el estado actual de la rama activa.
|
|
224
|
-
|
|
225
|
-
1. Listar archivos modificados (`git diff --name-only main`).
|
|
226
|
-
2. Ejecutar tests y lint. Reportar resultados.
|
|
227
|
-
3. Verificar que los cambios corresponden a una spec aprobada.
|
|
228
|
-
4. Identificar items pendientes antes de poder hacer merge.
|
|
229
|
-
|
|
230
|
-
## deploy-check
|
|
231
|
-
|
|
232
|
-
Verifica que el deploy de producción está sano después de un merge.
|
|
233
|
-
|
|
234
|
-
1. Confirmar que el build de CI pasó.
|
|
235
|
-
2. Revisar runtime logs (errores 5xx, excepciones no manejadas).
|
|
236
|
-
3. Smoke test de los endpoints principales.
|
|
237
|
-
4. Reportar estado: OK | DEGRADED | DOWN.
|
|
238
|
-
|
|
239
|
-
## wiki-ingest
|
|
240
|
-
|
|
241
|
-
Indexa documentación externa para que el agente pueda consultarla.
|
|
242
|
-
|
|
243
|
-
1. Recibir URL o path a la documentación.
|
|
244
|
-
2. Parsear y fragmentar el contenido.
|
|
245
|
-
3. Almacenar en el directorio de wiki del proyecto.
|
|
246
|
-
4. Confirmar qué secciones quedaron disponibles.
|
|
247
|
-
|
|
248
|
-
## wiki-query
|
|
249
|
-
|
|
250
|
-
Consulta la wiki indexada para responder preguntas técnicas.
|
|
251
|
-
|
|
252
|
-
1. Buscar en los fragmentos indexados con la consulta del usuario.
|
|
253
|
-
2. Sintetizar la respuesta citando las fuentes.
|
|
254
|
-
3. Si no hay resultado en la wiki, indicarlo explícitamente — no inventar.
|
|
255
|
-
|
|
256
|
-
## wiki-lint
|
|
257
|
-
|
|
258
|
-
Verifica la calidad y consistencia de la wiki indexada.
|
|
259
|
-
|
|
260
|
-
1. Detectar fragmentos desactualizados (referencias a versiones antiguas).
|
|
261
|
-
2. Identificar contradicciones entre documentos.
|
|
262
|
-
3. Reportar items que requieren revisión manual.
|
|
263
|
-
"""
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def generate_branch_guard_hook() -> str:
|
|
267
|
-
"""Genera el hook JSON de Kiro que advierte al editar en main/master."""
|
|
268
|
-
import json
|
|
269
|
-
hook = {
|
|
270
|
-
"name": "branch-guard",
|
|
271
|
-
"description": "Warns when editing files directly on main or master branch.",
|
|
272
|
-
"event": "onBeforeEdit",
|
|
273
|
-
"condition": {
|
|
274
|
-
"type": "gitBranch",
|
|
275
|
-
"branches": ["main", "master"]
|
|
276
|
-
},
|
|
277
|
-
"action": {
|
|
278
|
-
"type": "agentPrompt",
|
|
279
|
-
"prompt": (
|
|
280
|
-
"STOP: You are about to edit files on the '{{branch}}' branch. "
|
|
281
|
-
"Direct commits to main/master are not allowed in this project. "
|
|
282
|
-
"Create a feature branch first: `git checkout -b feat/<name>`. "
|
|
283
|
-
"Do not proceed with the edit until the user confirms or switches branch."
|
|
284
|
-
)
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return json.dumps(hook, indent=2, ensure_ascii=False)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
def generate_compliance_md(config: dict) -> str | None:
|
|
291
|
-
compliance_cfg = config.get("compliance", {})
|
|
292
|
-
frameworks = compliance_cfg.get("frameworks", [])
|
|
293
|
-
if not frameworks:
|
|
294
|
-
return None
|
|
295
|
-
|
|
296
|
-
return f"""# Compliance activo
|
|
297
|
-
|
|
298
|
-
Este proyecto opera bajo los siguientes marcos regulatorios:
|
|
299
|
-
{chr(10).join(f'- **{f.upper()}**' for f in frameworks)}
|
|
300
|
-
|
|
301
|
-
## Reglas no-negociables
|
|
302
|
-
|
|
303
|
-
- Sin datos personales en logs de stdout.
|
|
304
|
-
- Consentimiento explícito antes de cualquier tracker no esencial.
|
|
305
|
-
- Logs de auditoría append-only (sin UPDATE/DELETE sobre tablas de eventos).
|
|
306
|
-
- Derechos del titular implementados: acceso, rectificación, supresión, oposición, portabilidad.
|
|
307
|
-
- TLS 1.2+ para toda comunicación externa.
|
|
308
|
-
|
|
309
|
-
## Cuándo involucrar al compliance-reviewer
|
|
310
|
-
|
|
311
|
-
- Cualquier cambio que toque datos de usuarios o consentimientos.
|
|
312
|
-
- Nuevos endpoints que reciban PII.
|
|
313
|
-
- Cambios en la lógica de logs de auditoría.
|
|
314
|
-
- Implementación de derechos del titular (DSAR).
|
|
315
|
-
|
|
316
|
-
> Nota: el agente compliance-reviewer opera como primer filtro técnico.
|
|
317
|
-
> No sustituye revisión legal profesional para proyectos con obligaciones regulatorias reales.
|
|
318
|
-
"""
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
def main():
|
|
322
|
-
force = "--force" in sys.argv
|
|
323
|
-
|
|
324
|
-
try:
|
|
325
|
-
root = find_project_root()
|
|
326
|
-
forge = find_forge_dir()
|
|
327
|
-
except FileNotFoundError as e:
|
|
328
|
-
print(f"ERROR: {e}", file=sys.stderr)
|
|
329
|
-
sys.exit(1)
|
|
330
|
-
|
|
331
|
-
with open(root / "project.yaml") as f:
|
|
332
|
-
config = yaml.safe_load(f)
|
|
333
|
-
|
|
334
|
-
steering_dir = root / ".kiro" / "steering"
|
|
335
|
-
steering_dir.mkdir(parents=True, exist_ok=True)
|
|
336
|
-
|
|
337
|
-
files = {
|
|
338
|
-
"product.md": generate_product_md(config),
|
|
339
|
-
"structure.md": generate_structure_md(config),
|
|
340
|
-
"agents.md": generate_agents_md(config),
|
|
341
|
-
"commands.md": generate_commands_md(config),
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
compliance_content = generate_compliance_md(config)
|
|
345
|
-
if compliance_content:
|
|
346
|
-
files["compliance.md"] = compliance_content
|
|
347
|
-
|
|
348
|
-
for filename, content in files.items():
|
|
349
|
-
path = steering_dir / filename
|
|
350
|
-
status = write_file(path, content, force=force)
|
|
351
|
-
icon = "[OK] " if status == "OK" else "[KEEP]"
|
|
352
|
-
print(f" {icon} .kiro/steering/{filename}" + (" — ya existe (--force para sobreescribir)" if status == "KEEP" else ""))
|
|
353
|
-
|
|
354
|
-
# Hooks
|
|
355
|
-
hooks_dir = root / ".kiro" / "hooks"
|
|
356
|
-
hook_path = hooks_dir / "pre-edit-branch-guard.json"
|
|
357
|
-
hook_status = write_file(hook_path, generate_branch_guard_hook(), force=force)
|
|
358
|
-
hook_icon = "[OK] " if hook_status == "OK" else "[KEEP]"
|
|
359
|
-
print(f" {hook_icon} .kiro/hooks/pre-edit-branch-guard.json" + (" — ya existe (--force para sobreescribir)" if hook_status == "KEEP" else ""))
|
|
360
|
-
|
|
361
|
-
print(f"\nKiro steering files en {steering_dir.relative_to(root)}/")
|
|
362
|
-
if not force:
|
|
363
|
-
print(" Usar --force para sobreescribir archivos existentes.")
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if __name__ == "__main__":
|
|
367
|
-
main()
|