@cristiancorreau/forge 2.1.0

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 (153) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/LICENSE +191 -0
  3. package/README.md +156 -0
  4. package/assets/adapters/claude-code/commands/deploy-check.md +12 -0
  5. package/assets/adapters/claude-code/commands/new-feature.md +11 -0
  6. package/assets/adapters/claude-code/commands/plan.md +116 -0
  7. package/assets/adapters/claude-code/commands/review.md +219 -0
  8. package/assets/adapters/claude-code/commands/session-close.md +109 -0
  9. package/assets/adapters/claude-code/commands/session-start.md +59 -0
  10. package/assets/adapters/claude-code/commands/ship.md +133 -0
  11. package/assets/adapters/claude-code/commands/wiki-ingest.md +7 -0
  12. package/assets/adapters/claude-code/commands/wiki-lint.md +5 -0
  13. package/assets/adapters/claude-code/commands/wiki-query.md +7 -0
  14. package/assets/adapters/claude-code/commands/work.md +101 -0
  15. package/assets/adapters/claude-code/generate-claude-md.py +304 -0
  16. package/assets/adapters/codex/commands/plan.md +63 -0
  17. package/assets/adapters/codex/commands/review.md +53 -0
  18. package/assets/adapters/codex/commands/session-close.md +53 -0
  19. package/assets/adapters/codex/commands/session-start.md +49 -0
  20. package/assets/adapters/codex/commands/ship.md +53 -0
  21. package/assets/adapters/codex/commands/work.md +53 -0
  22. package/assets/adapters/codex/generate-codex-config.py +269 -0
  23. package/assets/adapters/codex/hooks/codex.yaml.tpl +43 -0
  24. package/assets/adapters/codex/hooks/forge-codex-finish.sh +158 -0
  25. package/assets/adapters/codex/hooks/forge-codex-start.sh +186 -0
  26. package/assets/adapters/kiro/generate-steering.py +367 -0
  27. package/assets/adapters/opencode/HOOKS.md +123 -0
  28. package/assets/adapters/opencode/commands/plan.md +119 -0
  29. package/assets/adapters/opencode/commands/review.md +164 -0
  30. package/assets/adapters/opencode/commands/session-close.md +111 -0
  31. package/assets/adapters/opencode/commands/session-start.md +62 -0
  32. package/assets/adapters/opencode/commands/ship.md +135 -0
  33. package/assets/adapters/opencode/commands/work.md +82 -0
  34. package/assets/adapters/opencode/generate-agents-md.py +262 -0
  35. package/assets/core/agents/backend-engineer.md +61 -0
  36. package/assets/core/agents/compliance-reviewer.md +83 -0
  37. package/assets/core/agents/docs-writer.md +77 -0
  38. package/assets/core/agents/frontend-engineer.md +70 -0
  39. package/assets/core/agents/orchestrator.md +104 -0
  40. package/assets/core/agents/security-auditor.md +54 -0
  41. package/assets/core/agents/test-engineer.md +57 -0
  42. package/assets/core/hooks/hooks-registry.yaml +48 -0
  43. package/assets/core/hooks/post-turn-check.sh +139 -0
  44. package/assets/core/hooks/pre-bash-check.py +202 -0
  45. package/assets/core/hooks/pre-edit-check.py +317 -0
  46. package/assets/core/hooks/session-start.sh +184 -0
  47. package/assets/core/schemas/project.schema.json +503 -0
  48. package/assets/core/skills/README.md +88 -0
  49. package/assets/core/skills/aitmpl-search/SKILL.md +74 -0
  50. package/assets/core/skills/browser-test/SKILL.md +177 -0
  51. package/assets/core/skills/db-migrate/SKILL.md +163 -0
  52. package/assets/core/skills/local2prod/SKILL.md +147 -0
  53. package/assets/core/skills/new-feature/SKILL.md +155 -0
  54. package/assets/core/skills/obsidian-sync/SKILL.md +152 -0
  55. package/assets/core/skills/phase-kickoff/SKILL.md +69 -0
  56. package/assets/core/skills/security-audit/SKILL.md +125 -0
  57. package/assets/core/skills/spec/SKILL.md +72 -0
  58. package/assets/core/skills/wiki-ingest/SKILL.md +183 -0
  59. package/assets/core/skills/wiki-lint/SKILL.md +109 -0
  60. package/assets/core/skills/wiki-query/SKILL.md +100 -0
  61. package/assets/core/templates/claude-md/architecture.rules +20 -0
  62. package/assets/core/templates/claude-md/global.md +30 -0
  63. package/assets/core/templates/claude-md/project.md +36 -0
  64. package/assets/core/templates/daily-note.md +38 -0
  65. package/assets/core/templates/spec-template.md +43 -0
  66. package/assets/core/workflows/sdd.md +69 -0
  67. package/assets/core/workflows/sprint.md +59 -0
  68. package/assets/forge.py +1265 -0
  69. package/assets/hooks/pre-commit +43 -0
  70. package/assets/manifest.json +274 -0
  71. package/assets/profiles/astro/README.md +24 -0
  72. package/assets/profiles/astro/agents/frontend-engineer.md +74 -0
  73. package/assets/profiles/django/agents/api-engineer.md +83 -0
  74. package/assets/profiles/expo/README.md +24 -0
  75. package/assets/profiles/expo/agents/mobile-engineer.md +69 -0
  76. package/assets/profiles/express/agents/api-engineer.md +60 -0
  77. package/assets/profiles/fastapi/README.md +32 -0
  78. package/assets/profiles/fastapi/agents/api-engineer.md +87 -0
  79. package/assets/profiles/go-gin/agents/api-engineer.md +98 -0
  80. package/assets/profiles/hono-drizzle/README.md +31 -0
  81. package/assets/profiles/hono-drizzle/agents/api-engineer.md +82 -0
  82. package/assets/profiles/laravel/README.md +32 -0
  83. package/assets/profiles/laravel/agents/api-engineer.md +114 -0
  84. package/assets/profiles/laravel/agents/fullstack-engineer.md +67 -0
  85. package/assets/profiles/laravel/agents/migration-specialist.md +420 -0
  86. package/assets/profiles/nestjs/agents/api-engineer.md +79 -0
  87. package/assets/profiles/nextjs-admin/README.md +32 -0
  88. package/assets/profiles/nextjs-admin/agents/admin-engineer.md +78 -0
  89. package/assets/profiles/playwright-crawler/agents/scanner-engineer.md +51 -0
  90. package/assets/profiles/rails/agents/fullstack-engineer.md +61 -0
  91. package/assets/profiles/sveltekit/agents/frontend-engineer.md +96 -0
  92. package/assets/profiles/vuenuxt/agents/frontend-engineer.md +82 -0
  93. package/assets/profiles/wordpress/README.md +30 -0
  94. package/assets/profiles/wordpress/agents/divi-engineer.md +273 -0
  95. package/assets/profiles/wordpress/agents/elementor-engineer.md +310 -0
  96. package/assets/profiles/wordpress/agents/wp-engineer.md +216 -0
  97. package/assets/requirements.txt +2 -0
  98. package/assets/scripts/aitmpl-search.py +808 -0
  99. package/assets/scripts/forge-add-opportunities.py +92 -0
  100. package/assets/scripts/forge-audit.py +1061 -0
  101. package/assets/scripts/forge-generate-all.py +283 -0
  102. package/assets/scripts/forge-init.py +900 -0
  103. package/assets/scripts/forge-migrate-project-yaml.py +397 -0
  104. package/assets/scripts/forge-scaffold-profile.py +181 -0
  105. package/assets/scripts/forge-teardown.py +193 -0
  106. package/assets/scripts/forge-validate-project-yaml.py +457 -0
  107. package/assets/scripts/forge-wizard.py +1003 -0
  108. package/assets/scripts/setup-codex.sh +229 -0
  109. package/assets/scripts/team-install.sh +147 -0
  110. package/assets/scripts/token-stats.py +201 -0
  111. package/assets/templates/modes/enterprise.yaml.tpl +114 -0
  112. package/assets/templates/modes/multi-runtime.yaml.tpl +89 -0
  113. package/assets/templates/modes/new-stack.yaml.tpl +101 -0
  114. package/assets/templates/modes/startup.yaml.tpl +74 -0
  115. package/assets/templates/project.yaml.tpl +185 -0
  116. package/assets/templates/wiki/concepts/_template.md +22 -0
  117. package/assets/templates/wiki/entities/_template.md +19 -0
  118. package/assets/templates/wiki/index.md +32 -0
  119. package/assets/templates/wiki/log.md +6 -0
  120. package/assets/templates/wiki/sources/_template.md +25 -0
  121. package/dist/cli.d.ts +3 -0
  122. package/dist/cli.d.ts.map +1 -0
  123. package/dist/cli.js +64 -0
  124. package/dist/cli.js.map +1 -0
  125. package/dist/commands/audit.d.ts +2 -0
  126. package/dist/commands/audit.d.ts.map +1 -0
  127. package/dist/commands/audit.js +21 -0
  128. package/dist/commands/audit.js.map +1 -0
  129. package/dist/commands/doctor.d.ts +2 -0
  130. package/dist/commands/doctor.d.ts.map +1 -0
  131. package/dist/commands/doctor.js +58 -0
  132. package/dist/commands/doctor.js.map +1 -0
  133. package/dist/commands/generate.d.ts +2 -0
  134. package/dist/commands/generate.d.ts.map +1 -0
  135. package/dist/commands/generate.js +27 -0
  136. package/dist/commands/generate.js.map +1 -0
  137. package/dist/commands/init.d.ts +2 -0
  138. package/dist/commands/init.d.ts.map +1 -0
  139. package/dist/commands/init.js +22 -0
  140. package/dist/commands/init.js.map +1 -0
  141. package/dist/commands/validate.d.ts +2 -0
  142. package/dist/commands/validate.d.ts.map +1 -0
  143. package/dist/commands/validate.js +20 -0
  144. package/dist/commands/validate.js.map +1 -0
  145. package/dist/lib/paths.d.ts +10 -0
  146. package/dist/lib/paths.d.ts.map +1 -0
  147. package/dist/lib/paths.js +49 -0
  148. package/dist/lib/paths.js.map +1 -0
  149. package/dist/lib/python.d.ts +4 -0
  150. package/dist/lib/python.d.ts.map +1 -0
  151. package/dist/lib/python.js +46 -0
  152. package/dist/lib/python.js.map +1 -0
  153. package/package.json +46 -0
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env bash
2
+ # Forge v2 — Codex onStart hook: forge-codex-start.sh
3
+ #
4
+ # Se ejecuta al inicio de cada sesión de Codex CLI.
5
+ # Equivalente al hook session-start.sh de Claude Code (UserPromptSubmit).
6
+ #
7
+ # DIFERENCIAS vs Claude Code:
8
+ # - Claude Code: session-start.sh corre en UserPromptSubmit (automático en cada sesión).
9
+ # - Codex CLI: este script corre en onStart (automático también, pero el timing
10
+ # exacto depende de la versión de Codex).
11
+ # - Claude Code: puede bloquear (exit 2) para detener la sesión.
12
+ # - Codex CLI: onStart no puede bloquear la sesión — solo puede imprimir advertencias.
13
+ # Exit 2 aquí no impide que Codex continúe; es informacional.
14
+ #
15
+ # LIMITACIONES de Codex vs Claude Code:
16
+ # - No hay PreToolUse: no se puede interceptar comandos individuales antes de ejecutarlos.
17
+ # - No hay PostToolUse: no se puede inspeccionar el resultado de cada tool call.
18
+ # - Las reglas de seguridad (pre-bash-check, branch guard) son instruccionales en
19
+ # AGENTS.md, no mecánicas. El agente debe respetar las reglas voluntariamente.
20
+ #
21
+ # Instalación: este script debe copiarse o enlazarse en .codex/forge-codex-start.sh
22
+ # El setup-codex.sh lo hace automáticamente.
23
+
24
+ set -uo pipefail
25
+
26
+ CHECKS_PASSED=0
27
+ CHECKS_TOTAL=0
28
+ OUTPUT=""
29
+
30
+ check() {
31
+ local name="$1"
32
+ local result="$2" # "ok" | "warn: <msg>" | "error: <msg>"
33
+ CHECKS_TOTAL=$((CHECKS_TOTAL + 1))
34
+ if [[ "$result" == "ok" ]]; then
35
+ CHECKS_PASSED=$((CHECKS_PASSED + 1))
36
+ elif [[ "$result" == error:* ]]; then
37
+ local msg="${result#error: }"
38
+ OUTPUT+=" error: ${msg}\n"
39
+ else
40
+ local msg="${result#warn: }"
41
+ OUTPUT+=" warn: ${msg}\n"
42
+ fi
43
+ }
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Check 1 — Herramientas básicas disponibles
47
+ # ---------------------------------------------------------------------------
48
+ if ! command -v git &>/dev/null; then
49
+ check "git" "error: git no está instalado o no está en PATH"
50
+ else
51
+ check "git" "ok"
52
+ fi
53
+
54
+ if ! command -v python3 &>/dev/null; then
55
+ check "python3" "error: python3 no está instalado o no está en PATH"
56
+ else
57
+ check "python3" "ok"
58
+ fi
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # Check 2 — Branch actual no es main/master
62
+ # ---------------------------------------------------------------------------
63
+ CURRENT_BRANCH="$(git branch --show-current 2>/dev/null || true)"
64
+ if [[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "master" ]]; then
65
+ check "branch" "warn: branch '${CURRENT_BRANCH}' — crea una feature branch antes de implementar: git checkout -b feature/<tema>-$(date +%Y-%m-%d)"
66
+ else
67
+ check "branch" "ok"
68
+ fi
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # Check 3 — Cambios sin commitear
72
+ # ---------------------------------------------------------------------------
73
+ GIT_STATUS="$(git status --short 2>/dev/null || true)"
74
+ if [[ -n "$GIT_STATUS" ]]; then
75
+ check "uncommitted" "warn: cambios sin commitear en el worktree"
76
+ else
77
+ check "uncommitted" "ok"
78
+ fi
79
+
80
+ # ---------------------------------------------------------------------------
81
+ # Check 4 — project.yaml existe
82
+ # ---------------------------------------------------------------------------
83
+ PROJECT_YAML=""
84
+ SEARCH_PATH="$(pwd)"
85
+ for _i in 1 2 3 4 5 6; do
86
+ if [[ -f "${SEARCH_PATH}/project.yaml" ]]; then
87
+ PROJECT_YAML="${SEARCH_PATH}/project.yaml"
88
+ break
89
+ fi
90
+ PARENT="$(dirname "$SEARCH_PATH")"
91
+ [[ "$PARENT" == "$SEARCH_PATH" ]] && break
92
+ SEARCH_PATH="$PARENT"
93
+ done
94
+
95
+ if [[ -z "$PROJECT_YAML" ]]; then
96
+ check "project.yaml" "warn: project.yaml no encontrado — ejecutar forge-wizard.py o scripts/setup-codex.sh"
97
+ else
98
+ check "project.yaml" "ok"
99
+
100
+ # Check 5 — project.yaml tiene campos requeridos
101
+ YAML_VALID="$(python3 - "$PROJECT_YAML" <<'PYEOF'
102
+ import sys, yaml
103
+ path = sys.argv[1]
104
+ try:
105
+ with open(path) as f:
106
+ data = yaml.safe_load(f)
107
+ if not isinstance(data, dict):
108
+ print("invalid")
109
+ sys.exit(0)
110
+ missing = []
111
+ project = data.get("project", {})
112
+ if not project.get("name"):
113
+ missing.append("project.name")
114
+ if not project.get("mode"):
115
+ missing.append("project.mode")
116
+ if missing:
117
+ print("missing:" + ",".join(missing))
118
+ else:
119
+ print("ok")
120
+ except Exception as e:
121
+ print(f"error:{e}")
122
+ PYEOF
123
+ )"
124
+ if [[ "$YAML_VALID" == "ok" ]]; then
125
+ check "project.yaml.fields" "ok"
126
+ elif [[ "$YAML_VALID" == missing:* ]]; then
127
+ MISSING_FIELDS="${YAML_VALID#missing:}"
128
+ check "project.yaml.fields" "warn: project.yaml faltan campos: ${MISSING_FIELDS}"
129
+ else
130
+ check "project.yaml.fields" "warn: project.yaml no se pudo parsear — verificar sintaxis"
131
+ fi
132
+
133
+ # Check 6 — Variables de entorno de producción activas
134
+ HAS_DEPLOY="$(python3 - "$PROJECT_YAML" <<'PYEOF'
135
+ import sys, yaml
136
+ path = sys.argv[1]
137
+ try:
138
+ with open(path) as f:
139
+ data = yaml.safe_load(f)
140
+ if isinstance(data, dict) and data.get("deploy"):
141
+ print("yes")
142
+ else:
143
+ print("no")
144
+ except Exception:
145
+ print("no")
146
+ PYEOF
147
+ )"
148
+ if [[ "$HAS_DEPLOY" == "yes" ]]; then
149
+ PROD_VARS=""
150
+ while IFS='=' read -r key _value; do
151
+ if [[ "$key" =~ ^(PROD_|PRODUCTION_|PROD$|PRODUCTION$) ]]; then
152
+ PROD_VARS+="${key} "
153
+ fi
154
+ done < <(env)
155
+ if [[ -n "$PROD_VARS" ]]; then
156
+ check "prod-env" "warn: variables de producción activas en sesión: ${PROD_VARS// /, } — verificar que es intencional"
157
+ else
158
+ check "prod-env" "ok"
159
+ fi
160
+ else
161
+ check "prod-env" "ok"
162
+ fi
163
+ fi
164
+
165
+ # ---------------------------------------------------------------------------
166
+ # Salida
167
+ # ---------------------------------------------------------------------------
168
+ WARNINGS=$((CHECKS_TOTAL - CHECKS_PASSED))
169
+
170
+ if [[ $WARNINGS -eq 0 ]]; then
171
+ # Silencioso — todo OK
172
+ exit 0
173
+ fi
174
+
175
+ # Construir etiquetas para el resumen
176
+ LABELS=""
177
+ [[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "master" ]] && LABELS+="[branch ${CURRENT_BRANCH}] "
178
+ [[ -n "$GIT_STATUS" ]] && LABELS+="[cambios sin commitear] "
179
+ [[ -z "$PROJECT_YAML" ]] && LABELS+="[sin project.yaml] "
180
+
181
+ printf "forge-codex session: %d advertencia(s) — %s\n" "$WARNINGS" "${LABELS%% }"
182
+ printf "%b" "$OUTPUT"
183
+
184
+ # Nota: en Codex CLI, onStart no puede bloquear la sesión.
185
+ # Salimos con 0 siempre — las advertencias son informacionales.
186
+ exit 0
@@ -0,0 +1,367 @@
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()
@@ -0,0 +1,123 @@
1
+ # Forge Hooks — Adaptación para OpenCode
2
+
3
+ OpenCode no tiene un sistema de hooks equivalente al PreToolUse/Stop de Claude Code. Este documento describe cómo adaptar cada comportamiento de hook de Forge para OpenCode.
4
+
5
+ ---
6
+
7
+ ## 1. Equivalencia de hooks
8
+
9
+ | Hook Forge (Claude Code) | Equivalente en OpenCode | Mecanismo |
10
+ |--------------------------|------------------------|-----------|
11
+ | `PreToolUse:Edit` — branch guard (bloquear edición en main) | **Ninguno nativo** | Instrucción en AGENTS.md |
12
+ | `PreToolUse:Bash` — detección de console.log/print de debug | **Ninguno nativo** | Instrucción en AGENTS.md |
13
+ | `PreToolUse:Bash` — bloqueo de comandos destructivos en producción | **Ninguno nativo** | Instrucción en AGENTS.md |
14
+ | `Stop` — resumen de sesión y persistencia de memoria | **Ninguno nativo** | Flujo `/session-close` |
15
+ | `pre-commit` git hook — inyección de token stats en progress.html | **Compatible** | El hook git funciona igual en OpenCode |
16
+
17
+ **Conclusión:** OpenCode no tiene PreToolUse ni Stop hooks. Todos los guardrails deben embeberse como instrucciones en AGENTS.md.
18
+
19
+ ---
20
+
21
+ ## 2. Guardrails que deben embeberse en AGENTS.md
22
+
23
+ ### Branch guard (equivale a PreToolUse:Edit)
24
+
25
+ ```markdown
26
+ ## Guardrail: Branch guard
27
+
28
+ NUNCA editar código cuando la rama actual sea `main`, `master` o `develop`.
29
+
30
+ Antes de cualquier edición de archivo:
31
+ 1. Verificar la rama actual con `git branch --show-current`
32
+ 2. Si la rama es `main`, `master` o `develop`: detener y pedir al usuario que cree una rama de feature antes de continuar.
33
+
34
+ Excepción: archivos de documentación (*.md) pueden editarse en main si el usuario lo indica explícitamente.
35
+ ```
36
+
37
+ ### Debug detection (equivale a PreToolUse:Bash con grep de debug patterns)
38
+
39
+ ```markdown
40
+ ## Guardrail: Detección de debug
41
+
42
+ Antes de hacer commit de cualquier archivo, verificar que no contenga:
43
+ - `console.log(` en JavaScript/TypeScript (excepción: archivos de logger o utilidades de logging)
44
+ - `print(` en Python que no sea logging de producción
45
+ - `debugger;` en JavaScript/TypeScript
46
+ - `binding.pry` en Ruby
47
+ - `dd(` o `dump(` en PHP
48
+
49
+ Si se detectan estos patrones en código que va a commitearse: reportar la línea exacta y pedir confirmación antes de continuar.
50
+ ```
51
+
52
+ ### Producción safety (equivale a PreToolUse:Bash con comandos destructivos)
53
+
54
+ ```markdown
55
+ ## Guardrail: Producción safety
56
+
57
+ Nunca ejecutar estos comandos sin confirmación explícita del usuario:
58
+ - `DROP TABLE`, `DROP DATABASE`, `TRUNCATE` en cualquier base de datos de producción
59
+ - `rm -rf` en directorios que no sean temporales o de build
60
+ - `git push --force` a main/master (usar `--force-with-lease` en feature branches si es estrictamente necesario)
61
+ - Deploy directo a producción sin haber ejecutado `/review` primero
62
+
63
+ Para cualquier comando potencialmente destructivo: describir el efecto exacto y pedir confirmación explícita antes de ejecutar.
64
+ ```
65
+
66
+ ### SQL injection prevention
67
+
68
+ ```markdown
69
+ ## Guardrail: SQL injection
70
+
71
+ Nunca concatenar input del usuario directamente en strings SQL.
72
+ Siempre usar parámetros preparados o el ORM del proyecto.
73
+
74
+ Ejemplo prohibido: `f"SELECT * FROM users WHERE email = '{email}'"`
75
+ Ejemplo correcto: `cursor.execute("SELECT * FROM users WHERE email = %s", (email,))`
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 3. Configuración de proyecto OpenCode
81
+
82
+ OpenCode utiliza un archivo `.opencode/config.json` para configuración a nivel de proyecto. Ejemplo recomendado para proyectos Forge:
83
+
84
+ ```json
85
+ {
86
+ "model": "claude-sonnet-4-5",
87
+ "context": {
88
+ "files": [
89
+ "AGENTS.md",
90
+ "project.yaml"
91
+ ]
92
+ }
93
+ }
94
+ ```
95
+
96
+ **Notas:**
97
+ - `context.files`: archivos que OpenCode incluye automáticamente como contexto en cada sesión. Incluir siempre `AGENTS.md` y `project.yaml`.
98
+ - `model`: el modelo a usar. Recomendado `claude-sonnet-4-5` o superior para proyectos con `project.mode: standard` o `enterprise`.
99
+
100
+ ---
101
+
102
+ ## 4. Flujo recomendado sin hooks
103
+
104
+ Dado que OpenCode no tiene hooks automáticos, el cumplimiento de los guardrails depende de:
105
+
106
+ 1. **AGENTS.md bien escrito**: incluir las secciones de guardrails del punto 2 en el AGENTS.md generado por `generate-agents-md.py`.
107
+ 2. **Disciplina de sesión**: usar `/session-start` al comenzar y `/session-close` al terminar para mantener el estado documentado.
108
+ 3. **Pre-commit git hook**: el hook de git en `hooks/pre-commit` funciona igual en OpenCode — instalarlo en el proyecto cliente con `git config core.hooksPath .githooks`.
109
+
110
+ ---
111
+
112
+ ## 5. Referencia cruzada con comandos Forge v2
113
+
114
+ Los comandos SDD disponibles en OpenCode (en `.opencode/commands/`) son:
115
+
116
+ | Comando | Descripción |
117
+ |---------|-------------|
118
+ | `/session-start` | Inicializa sesión, detecta branch y estado del repo |
119
+ | `/plan` | Crea o revisa specs en docs/specs/ |
120
+ | `/work` | Implementa una spec en modo serial |
121
+ | `/review` | Revisión de código con veredicto vinculante |
122
+ | `/ship` | Pipeline de deploy con polling y smoke tests |
123
+ | `/session-close` | Cierra sesión con commit, daily note y PR |