@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
@@ -1,397 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- forge-migrate-project-yaml.py — Migra un project.yaml de v1 a v2 de Forge.
4
-
5
- Usage:
6
- python3 .agentic/scripts/forge-migrate-project-yaml.py
7
- python3 .agentic/scripts/forge-migrate-project-yaml.py --dry-run
8
- python3 .agentic/scripts/forge-migrate-project-yaml.py --backup
9
-
10
- Detecta si el project.yaml es v1 (sin secciones 'deploy' estructurado, 'rules', 'mcp', 'github')
11
- y agrega las secciones nuevas preservando todos los valores existentes.
12
-
13
- Exit codes:
14
- 0 — migración exitosa (o ya era v2)
15
- 1 — error
16
- """
17
-
18
- import sys
19
- import os
20
- import re
21
- import shutil
22
- import argparse
23
- from pathlib import Path
24
- from typing import Any, Optional, Tuple
25
-
26
-
27
- # ---------------------------------------------------------------------------
28
- # Búsqueda de project.yaml
29
- # ---------------------------------------------------------------------------
30
-
31
- def find_project_yaml(start: Path) -> Optional[Path]:
32
- current = start.resolve()
33
- while True:
34
- candidate = current / "project.yaml"
35
- if candidate.exists():
36
- return candidate
37
- parent = current.parent
38
- if parent == current:
39
- return None
40
- current = parent
41
-
42
-
43
- # ---------------------------------------------------------------------------
44
- # Carga YAML
45
- # ---------------------------------------------------------------------------
46
-
47
- def load_yaml_raw(path: Path) -> Tuple[dict, str]:
48
- """
49
- Carga el YAML y retorna (data, raw_text).
50
- Requiere pyyaml.
51
- """
52
- try:
53
- import yaml
54
- except ImportError:
55
- print("ERROR: pyyaml no está instalado. Instalarlo con: pip install pyyaml")
56
- sys.exit(1)
57
-
58
- with open(path, "r", encoding="utf-8") as f:
59
- raw = f.read()
60
-
61
- data = yaml.safe_load(raw)
62
- return (data if data is not None else {}), raw
63
-
64
-
65
- # ---------------------------------------------------------------------------
66
- # Detección de versión
67
- # ---------------------------------------------------------------------------
68
-
69
- def detect_version(data: dict) -> str:
70
- """
71
- Detecta si el project.yaml es v1 o v2.
72
- Criterio: si tiene 'rules' O 'mcp' O 'github' O 'project.mode' → es v2.
73
- """
74
- if "rules" in data or "mcp" in data or "github" in data:
75
- return "2"
76
- project = data.get("project", {}) or {}
77
- if "mode" in project:
78
- return "2"
79
- return "1"
80
-
81
-
82
- # ---------------------------------------------------------------------------
83
- # Generador del bloque v2 en YAML comentado
84
- # ---------------------------------------------------------------------------
85
-
86
- V2_ADDITIONS = """\
87
- # ---------------------------------------------------------------------------
88
- # schema_version: "2"
89
- # Las siguientes secciones fueron agregadas por forge-migrate-project-yaml.py
90
- # ---------------------------------------------------------------------------
91
-
92
- # nuevo en v2: modo operativo del proyecto
93
- # Si ya existe project.mode, puedes ignorar este comentario.
94
-
95
- agents:
96
- # nuevo en v2: mapeo rol → modelo específico de Claude
97
- by_role:
98
- orchestrator: null # ej: claude-opus-4-7
99
- senior-backend: null # ej: claude-sonnet-4-6
100
-
101
- deploy:
102
- # nuevo en v2
103
- provider: null # vercel | railway | fly | aws | github-actions | custom | null
104
- project_id: null # ID del proyecto en la plataforma (ej: prj_xxx en Vercel)
105
- production_url: null # https://mi-proyecto.vercel.app
106
- smoke_tests: [] # Tests de humo post-deploy
107
- # Ejemplo:
108
- # smoke_tests:
109
- # - url: /api/health
110
- # expect_status: 200
111
- # expect_json:
112
- # status: ok
113
- # - url: https://mi-proyecto.vercel.app
114
- # expect_status: 200
115
-
116
- mcp:
117
- # nuevo en v2: servidores MCP del proyecto
118
- servers: []
119
- # Ejemplo:
120
- # servers:
121
- # - name: supabase
122
- # auto_approve:
123
- # - list_tables
124
- # - execute_sql
125
-
126
- github:
127
- # nuevo en v2: integración con GitHub Projects
128
- project:
129
- number: null # Número del GitHub Project
130
- owner: null # usuario u organización
131
- repo: null # nombre del repositorio
132
- status_field_id: null # ID del campo Status
133
- status_in_progress: null # ej: "In Progress"
134
- status_done: null # ej: "Done"
135
-
136
- rules:
137
- # nuevo en v2: guardrails del proyecto
138
- forbidden_in_production:
139
- - "console.log" # eliminar logs de debug en producción
140
- - "TODO:" # no dejar TODOs sin resolver
141
- - "FIXME:"
142
- required_review_before_ship: false
143
- require_spec_before_implementation: false
144
- conventional_commits: true
145
- forbidden_patterns: [] # regex evaluadas por el hook pre-edit-check
146
- # Ejemplo:
147
- # forbidden_patterns:
148
- # - "process\\.env\\.[A-Z_]+\\s*=\\s*['\\\"][^'\\\"]+['\\\"]" # hardcoded env values
149
-
150
- scripts:
151
- # nuevo en v2: comando ejecutado por post-turn-check.sh
152
- check: null # ej: "pnpm typecheck && pnpm lint"
153
- """
154
-
155
-
156
- def build_v2_yaml(data: dict, raw: str) -> str:
157
- """
158
- Construye el YAML v2 completo.
159
- Estrategia: agrega un encabezado de schema_version y las secciones nuevas al final,
160
- preservando el contenido original.
161
-
162
- Para las secciones que ya existen en el original (como 'deploy' con solo 'provider' y 'branch'),
163
- se hace un merge inteligente para no duplicar.
164
- """
165
- try:
166
- import yaml
167
- except ImportError:
168
- print("ERROR: pyyaml no está instalado.")
169
- sys.exit(1)
170
-
171
- # Secciones que ya existen en el YAML original
172
- existing_sections = set(data.keys())
173
-
174
- lines_to_add = []
175
-
176
- # Encabezado
177
- lines_to_add.append("# ---------------------------------------------------------------------------")
178
- lines_to_add.append('# schema_version: "2"')
179
- lines_to_add.append("# Las siguientes secciones fueron agregadas por forge-migrate-project-yaml.py")
180
- lines_to_add.append("# ---------------------------------------------------------------------------")
181
- lines_to_add.append("")
182
-
183
- # project.mode — agregar si no existe
184
- project = data.get("project", {}) or {}
185
- needs_project_mode = "mode" not in project
186
-
187
- # agents.by_role — agregar si agents existe pero no tiene by_role
188
- agents = data.get("agents", {}) or {}
189
- needs_agents_by_role = "agents" in existing_sections and "by_role" not in agents
190
-
191
- # Secciones completamente nuevas
192
- needs_deploy_new_fields = True # siempre agregar campos nuevos de deploy
193
- needs_mcp = "mcp" not in existing_sections
194
- needs_github = "github" not in existing_sections
195
- needs_rules = "rules" not in existing_sections
196
- needs_scripts = "scripts" not in existing_sections
197
-
198
- # --- project.mode patch ---
199
- if needs_project_mode:
200
- lines_to_add.append("# ACCIÓN REQUERIDA: Agrega 'mode' a la sección project arriba.")
201
- lines_to_add.append("# Valores: startup | standard | enterprise")
202
- lines_to_add.append("# Ejemplo:")
203
- lines_to_add.append("# project:")
204
- lines_to_add.append('# mode: "standard"')
205
- lines_to_add.append("")
206
-
207
- # --- agents.by_role ---
208
- if needs_agents_by_role:
209
- lines_to_add.append("# nuevo en v2 — agregar bajo la sección agents existente:")
210
- lines_to_add.append("# agents:")
211
- lines_to_add.append("# by_role:")
212
- lines_to_add.append("# orchestrator: claude-opus-4-7")
213
- lines_to_add.append("# senior-backend: claude-sonnet-4-6")
214
- lines_to_add.append("")
215
-
216
- # --- deploy (campos nuevos de v2) ---
217
- deploy = data.get("deploy", {}) or {}
218
- existing_deploy_keys = set(deploy.keys()) if isinstance(deploy, dict) else set()
219
- deploy_new_fields = []
220
-
221
- if "project_id" not in existing_deploy_keys:
222
- deploy_new_fields.append(" project_id: null # ID del proyecto en la plataforma (ej: prj_xxx en Vercel)")
223
- if "production_url" not in existing_deploy_keys:
224
- deploy_new_fields.append(" production_url: null # https://mi-proyecto.vercel.app")
225
- if "smoke_tests" not in existing_deploy_keys:
226
- deploy_new_fields.append(" smoke_tests: [] # Tests de humo post-deploy")
227
- deploy_new_fields.append(" # Ejemplo de smoke test:")
228
- deploy_new_fields.append(" # smoke_tests:")
229
- deploy_new_fields.append(" # - url: /api/health")
230
- deploy_new_fields.append(" # expect_status: 200")
231
- deploy_new_fields.append(" # expect_json:")
232
- deploy_new_fields.append(" # status: ok")
233
-
234
- if deploy_new_fields:
235
- if "deploy" in existing_sections:
236
- lines_to_add.append("# nuevo en v2 — campos adicionales para la sección deploy existente:")
237
- lines_to_add.append("# (agregar manualmente bajo la sección deploy arriba)")
238
- for line in deploy_new_fields:
239
- lines_to_add.append("# " + line.lstrip())
240
- else:
241
- lines_to_add.append("")
242
- lines_to_add.append("deploy: # nuevo en v2")
243
- if "provider" not in existing_deploy_keys:
244
- lines_to_add.append(" provider: null # vercel | railway | fly | aws | github-actions | custom | null")
245
- lines_to_add.extend(deploy_new_fields)
246
- lines_to_add.append("")
247
-
248
- # --- mcp ---
249
- if needs_mcp:
250
- lines_to_add.append("mcp: # nuevo en v2")
251
- lines_to_add.append(" servers: []")
252
- lines_to_add.append(" # Ejemplo:")
253
- lines_to_add.append(" # servers:")
254
- lines_to_add.append(" # - name: supabase")
255
- lines_to_add.append(" # auto_approve:")
256
- lines_to_add.append(" # - list_tables")
257
- lines_to_add.append(" # - execute_sql")
258
- lines_to_add.append("")
259
-
260
- # --- github ---
261
- if needs_github:
262
- lines_to_add.append("github: # nuevo en v2")
263
- lines_to_add.append(" project:")
264
- lines_to_add.append(" number: null # Número del GitHub Project")
265
- lines_to_add.append(" owner: null # usuario u organización")
266
- lines_to_add.append(" repo: null # nombre del repositorio")
267
- lines_to_add.append(" status_field_id: null # ID del campo Status")
268
- lines_to_add.append(' status_in_progress: null # ej: "In Progress"')
269
- lines_to_add.append(' status_done: null # ej: "Done"')
270
- lines_to_add.append("")
271
-
272
- # --- rules ---
273
- if needs_rules:
274
- lines_to_add.append("rules: # nuevo en v2")
275
- lines_to_add.append(" forbidden_in_production:")
276
- lines_to_add.append(' - "console.log"')
277
- lines_to_add.append(' - "TODO:"')
278
- lines_to_add.append(' - "FIXME:"')
279
- lines_to_add.append(" required_review_before_ship: false")
280
- lines_to_add.append(" require_spec_before_implementation: false")
281
- lines_to_add.append(" conventional_commits: true")
282
- lines_to_add.append(" forbidden_patterns: []")
283
- lines_to_add.append("")
284
-
285
- # --- scripts ---
286
- if needs_scripts:
287
- lines_to_add.append("scripts: # nuevo en v2")
288
- lines_to_add.append(' check: null # ej: "pnpm typecheck && pnpm lint"')
289
- lines_to_add.append("")
290
-
291
- if not lines_to_add or all(l == "" for l in lines_to_add):
292
- return raw # nada que agregar
293
-
294
- # Construir resultado final
295
- additions = "\n".join(lines_to_add)
296
- result = raw.rstrip("\n") + "\n\n" + additions
297
- return result
298
-
299
-
300
- # ---------------------------------------------------------------------------
301
- # Diff simple
302
- # ---------------------------------------------------------------------------
303
-
304
- def show_diff(original: str, new: str):
305
- """Muestra un diff básico entre el contenido original y el nuevo."""
306
- orig_lines = original.splitlines(keepends=True)
307
- new_lines = new.splitlines(keepends=True)
308
-
309
- import difflib
310
- diff = difflib.unified_diff(
311
- orig_lines, new_lines,
312
- fromfile="project.yaml (v1)",
313
- tofile="project.yaml (v2)",
314
- lineterm=""
315
- )
316
- diff_text = "".join(diff)
317
- if diff_text:
318
- print(diff_text)
319
- else:
320
- print("(sin cambios)")
321
-
322
-
323
- # ---------------------------------------------------------------------------
324
- # Main
325
- # ---------------------------------------------------------------------------
326
-
327
- def main():
328
- parser = argparse.ArgumentParser(
329
- description="Migra project.yaml de v1 a v2 de Forge"
330
- )
331
- parser.add_argument(
332
- "--dry-run",
333
- action="store_true",
334
- help="Muestra el diff sin escribir cambios"
335
- )
336
- parser.add_argument(
337
- "--backup",
338
- action="store_true",
339
- help="Crea project.yaml.bak antes de modificar"
340
- )
341
- args = parser.parse_args()
342
-
343
- # Buscar project.yaml
344
- project_yaml_path = find_project_yaml(Path.cwd())
345
- if project_yaml_path is None:
346
- print("ERROR: No se encontró project.yaml en el directorio actual ni superiores")
347
- sys.exit(1)
348
-
349
- print(f"Leyendo: {project_yaml_path}")
350
-
351
- # Cargar
352
- data, raw = load_yaml_raw(project_yaml_path)
353
-
354
- # Detectar versión
355
- version = detect_version(data)
356
- if version == "2":
357
- print("El project.yaml ya está en v2 (contiene secciones 'rules', 'mcp', 'github' o 'project.mode').")
358
- print("No se requiere migración.")
359
- sys.exit(0)
360
-
361
- print(f"Versión detectada: v{version} → migrando a v2...")
362
- print()
363
-
364
- # Construir nuevo contenido
365
- new_content = build_v2_yaml(data, raw)
366
-
367
- if args.dry_run:
368
- print("--- DRY RUN: mostrando diff (no se escribirán cambios) ---")
369
- print()
370
- show_diff(raw, new_content)
371
- sys.exit(0)
372
-
373
- # Backup
374
- if args.backup:
375
- backup_path = project_yaml_path.with_suffix(".yaml.bak")
376
- shutil.copy2(project_yaml_path, backup_path)
377
- print(f"Backup creado: {backup_path}")
378
-
379
- # Escribir
380
- with open(project_yaml_path, "w", encoding="utf-8") as f:
381
- f.write(new_content)
382
-
383
- print(f"Migración completada: {project_yaml_path}")
384
- print()
385
- print("Próximos pasos:")
386
- print(" 1. Revisa el archivo y completa los campos null con valores reales")
387
- print(" 2. Agrega project.mode (startup | standard | enterprise) si no está")
388
- print(" 3. Valida con: python3 .agentic/scripts/forge-validate-project-yaml.py")
389
- print()
390
-
391
- # Mostrar diff informativo
392
- print("--- Cambios aplicados ---")
393
- show_diff(raw, new_content)
394
-
395
-
396
- if __name__ == "__main__":
397
- main()
@@ -1,181 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- forge-scaffold-profile.py — Crea un profile Tier 2 para un stack no cubierto por forge.
4
-
5
- Uso:
6
- python3 .agentic/scripts/forge-scaffold-profile.py --name django --engineer api-engineer
7
- python3 .agentic/scripts/forge-scaffold-profile.py \\
8
- --name django --engineer api-engineer \\
9
- --description "Backend Django con DRF" \\
10
- --stack-details "Django 4.2 + PostgreSQL + Django REST Framework"
11
- """
12
- from __future__ import annotations
13
-
14
- import argparse
15
- import sys
16
- from pathlib import Path
17
- from typing import Optional
18
-
19
-
20
- def _find_forge_dir() -> Path:
21
- here = Path(__file__).resolve().parent
22
- for candidate in [here.parent, here.parent.parent]:
23
- if (candidate / "core").exists() and (candidate / "profiles").exists():
24
- return candidate
25
- for p in [Path.cwd()] + list(Path.cwd().parents):
26
- for sub in [p / ".agentic", p / "forge"]:
27
- if (sub / "core").exists() and (sub / "profiles").exists():
28
- return sub
29
- raise FileNotFoundError(
30
- "No se encontró el directorio forge con core/ y profiles/. "
31
- "Ejecutar desde el repositorio de forge o desde un proyecto que lo tenga instalado."
32
- )
33
-
34
-
35
- def _agent_md(
36
- name: str,
37
- engineer: str,
38
- description: str,
39
- stack_details: str,
40
- ) -> str:
41
- slug_title = name.replace("-", " ").title()
42
- eng_title = engineer.replace("-", " ").title()
43
-
44
- desc_line = (
45
- description
46
- if description
47
- else f"Implementa el backend del proyecto usando {slug_title}. NO trabaja fuera del directorio definido en project.yaml."
48
- )
49
-
50
- stack_block = (
51
- stack_details
52
- if stack_details
53
- else f"- **Framework:** {slug_title}\n- **Lenguaje:** (especificar)\n- **ORM/DB:** (especificar)\n- **Tests:** (especificar)"
54
- )
55
-
56
- return f"""\
57
- ---
58
- name: {engineer}
59
- description: {desc_line}
60
- model: sonnet
61
- tools: Read, Grep, Glob, Bash, Edit, Write
62
- tier: 2
63
- profile: {name}
64
- ---
65
-
66
- # {eng_title} — {slug_title}
67
-
68
- Implementás el backend del proyecto con {slug_title}. Tu scope es el directorio
69
- definido en el `CLAUDE.md` del proyecto. Leé ese archivo antes de empezar.
70
-
71
- ## Stack
72
-
73
- {stack_block}
74
-
75
- ## Tu trabajo
76
-
77
- - Implementar endpoints, modelos y migraciones según las specs en `docs/specs/`.
78
- - Escribir tests unitarios y de integración para toda la lógica nueva.
79
- - Correr el linter, typecheck y tests antes de reportar al orchestrator.
80
- - Proponer un plan antes de codificar cuando la tarea afecte >3 archivos.
81
-
82
- ## Reglas
83
-
84
- - **Logs de auditoría son append-only.** NUNCA `UPDATE` ni `DELETE` sobre tablas de eventos.
85
- - **PII nunca en logs.** Solo IDs o indicadores no reversibles.
86
- - **Parámetros preparados siempre:** nunca interpolar input del usuario en SQL.
87
- - **Auth + authz en cada endpoint:** verificar sesión Y permisos por recurso.
88
- - **Migraciones reversibles:** toda migración tiene su operación inversa documentada.
89
- - Sin spec en `docs/specs/` → no empieces. Pedí que se cree primero.
90
-
91
- ## Workflow
92
-
93
- 1. Leer el `CLAUDE.md` del proyecto y la spec de la feature activa.
94
- 2. Revisar el data model si la tarea toca schema.
95
- 3. Si la tarea toca compliance, informar al compliance-reviewer antes de implementar.
96
- 4. Implementar con tests (TDD para lógica core, integración para endpoints).
97
- 5. Correr tests + linter + typecheck.
98
- 6. Reportar al orchestrator: qué se hizo, qué archivos se tocaron, qué falta.
99
-
100
- ## No hagas
101
-
102
- - No salgas del directorio de API/backend del proyecto.
103
- - No implementes lógica de UI ni de frontend.
104
- - No modifiques specs ni documentación de arquitectura sin aprobación.
105
- - No mergees ni crees PRs directamente.
106
- - No uses queries SQL raw con interpolación de strings.
107
- """
108
-
109
-
110
- def main() -> None:
111
- parser = argparse.ArgumentParser(
112
- description="Crea un profile Tier 2 para un stack no cubierto por forge."
113
- )
114
- parser.add_argument("--name", required=True, metavar="SLUG", help="Nombre del profile (ej: django)")
115
- parser.add_argument("--engineer", required=True, metavar="AGENT", help="Nombre del agente (ej: api-engineer)")
116
- parser.add_argument("--description", default="", metavar="DESC", help="Descripción breve del agente")
117
- parser.add_argument("--stack-details", default="", metavar="DETAILS", help="Detalles del stack (tecnologías, versiones)")
118
-
119
- args = parser.parse_args()
120
-
121
- name: str = args.name.strip().lower()
122
- engineer: str = args.engineer.strip().lower()
123
-
124
- if not name or not engineer:
125
- print("Error: --name y --engineer son obligatorios y no pueden estar vacíos.", file=sys.stderr)
126
- sys.exit(1)
127
-
128
- try:
129
- forge = _find_forge_dir()
130
- except FileNotFoundError as e:
131
- print(f"Error: {e}", file=sys.stderr)
132
- sys.exit(1)
133
-
134
- profile_dir = forge / "profiles" / name / "agents"
135
- agent_file = profile_dir / f"{engineer}.md"
136
-
137
- if agent_file.exists():
138
- print(f"Error: {agent_file} ya existe. Editar manualmente si querés actualizarlo.", file=sys.stderr)
139
- sys.exit(1)
140
-
141
- profile_dir.mkdir(parents=True, exist_ok=True)
142
-
143
- content = _agent_md(
144
- name=name,
145
- engineer=engineer,
146
- description=args.description.strip(),
147
- stack_details=args.stack_details.strip(),
148
- )
149
-
150
- with open(agent_file, "w") as f:
151
- f.write(content)
152
-
153
- print(f"Profile creado: {agent_file.relative_to(forge.parent) if forge.parent != forge else agent_file}")
154
- print()
155
- print("Próximos pasos:")
156
- print()
157
- print(f" 1. Revisar y completar el agente:")
158
- print(f" {agent_file}")
159
- print()
160
- print(f" 2. Documentar el profile en docs/agent-standard.md:")
161
- print(f" Agregar una fila en la tabla Tier 2:")
162
- print(f" | `{name}` | `{engineer}` |")
163
- print()
164
- print(f" 3. Activar el profile en project.yaml del proyecto:")
165
- print(f" agents:")
166
- print(f" profiles:")
167
- print(f" - {name}")
168
- print()
169
- print(f" 4. Instalar el agente:")
170
- print(f" python3 .agentic/scripts/forge-init.py --tool claude-code")
171
- print()
172
- print(f" 5. Agregar tests en tests/test_profiles.py para el nuevo profile.")
173
- print()
174
- # forge-init.py gestiona profiles dinámicamente desde el filesystem, no tiene un
175
- # registro estático de nombres — no es necesario editar su código para que funcione.
176
- print("Nota: forge-init.py detecta profiles automáticamente desde forge/profiles/.")
177
- print("No es necesario modificar ese script.")
178
-
179
-
180
- if __name__ == "__main__":
181
- main()