@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.
- package/CHANGELOG.md +228 -0
- package/LICENSE +191 -0
- package/README.md +156 -0
- package/assets/adapters/claude-code/commands/deploy-check.md +12 -0
- package/assets/adapters/claude-code/commands/new-feature.md +11 -0
- package/assets/adapters/claude-code/commands/plan.md +116 -0
- package/assets/adapters/claude-code/commands/review.md +219 -0
- package/assets/adapters/claude-code/commands/session-close.md +109 -0
- package/assets/adapters/claude-code/commands/session-start.md +59 -0
- package/assets/adapters/claude-code/commands/ship.md +133 -0
- package/assets/adapters/claude-code/commands/wiki-ingest.md +7 -0
- package/assets/adapters/claude-code/commands/wiki-lint.md +5 -0
- package/assets/adapters/claude-code/commands/wiki-query.md +7 -0
- package/assets/adapters/claude-code/commands/work.md +101 -0
- package/assets/adapters/claude-code/generate-claude-md.py +304 -0
- package/assets/adapters/codex/commands/plan.md +63 -0
- package/assets/adapters/codex/commands/review.md +53 -0
- package/assets/adapters/codex/commands/session-close.md +53 -0
- package/assets/adapters/codex/commands/session-start.md +49 -0
- package/assets/adapters/codex/commands/ship.md +53 -0
- package/assets/adapters/codex/commands/work.md +53 -0
- package/assets/adapters/codex/generate-codex-config.py +269 -0
- package/assets/adapters/codex/hooks/codex.yaml.tpl +43 -0
- package/assets/adapters/codex/hooks/forge-codex-finish.sh +158 -0
- package/assets/adapters/codex/hooks/forge-codex-start.sh +186 -0
- package/assets/adapters/kiro/generate-steering.py +367 -0
- package/assets/adapters/opencode/HOOKS.md +123 -0
- package/assets/adapters/opencode/commands/plan.md +119 -0
- package/assets/adapters/opencode/commands/review.md +164 -0
- package/assets/adapters/opencode/commands/session-close.md +111 -0
- package/assets/adapters/opencode/commands/session-start.md +62 -0
- package/assets/adapters/opencode/commands/ship.md +135 -0
- package/assets/adapters/opencode/commands/work.md +82 -0
- package/assets/adapters/opencode/generate-agents-md.py +262 -0
- package/assets/core/agents/backend-engineer.md +61 -0
- package/assets/core/agents/compliance-reviewer.md +83 -0
- package/assets/core/agents/docs-writer.md +77 -0
- package/assets/core/agents/frontend-engineer.md +70 -0
- package/assets/core/agents/orchestrator.md +104 -0
- package/assets/core/agents/security-auditor.md +54 -0
- package/assets/core/agents/test-engineer.md +57 -0
- package/assets/core/hooks/hooks-registry.yaml +48 -0
- package/assets/core/hooks/post-turn-check.sh +139 -0
- package/assets/core/hooks/pre-bash-check.py +202 -0
- package/assets/core/hooks/pre-edit-check.py +317 -0
- package/assets/core/hooks/session-start.sh +184 -0
- package/assets/core/schemas/project.schema.json +503 -0
- package/assets/core/skills/README.md +88 -0
- package/assets/core/skills/aitmpl-search/SKILL.md +74 -0
- package/assets/core/skills/browser-test/SKILL.md +177 -0
- package/assets/core/skills/db-migrate/SKILL.md +163 -0
- package/assets/core/skills/local2prod/SKILL.md +147 -0
- package/assets/core/skills/new-feature/SKILL.md +155 -0
- package/assets/core/skills/obsidian-sync/SKILL.md +152 -0
- package/assets/core/skills/phase-kickoff/SKILL.md +69 -0
- package/assets/core/skills/security-audit/SKILL.md +125 -0
- package/assets/core/skills/spec/SKILL.md +72 -0
- package/assets/core/skills/wiki-ingest/SKILL.md +183 -0
- package/assets/core/skills/wiki-lint/SKILL.md +109 -0
- package/assets/core/skills/wiki-query/SKILL.md +100 -0
- package/assets/core/templates/claude-md/architecture.rules +20 -0
- package/assets/core/templates/claude-md/global.md +30 -0
- package/assets/core/templates/claude-md/project.md +36 -0
- package/assets/core/templates/daily-note.md +38 -0
- package/assets/core/templates/spec-template.md +43 -0
- package/assets/core/workflows/sdd.md +69 -0
- package/assets/core/workflows/sprint.md +59 -0
- package/assets/forge.py +1265 -0
- package/assets/hooks/pre-commit +43 -0
- package/assets/manifest.json +274 -0
- package/assets/profiles/astro/README.md +24 -0
- package/assets/profiles/astro/agents/frontend-engineer.md +74 -0
- package/assets/profiles/django/agents/api-engineer.md +83 -0
- package/assets/profiles/expo/README.md +24 -0
- package/assets/profiles/expo/agents/mobile-engineer.md +69 -0
- package/assets/profiles/express/agents/api-engineer.md +60 -0
- package/assets/profiles/fastapi/README.md +32 -0
- package/assets/profiles/fastapi/agents/api-engineer.md +87 -0
- package/assets/profiles/go-gin/agents/api-engineer.md +98 -0
- package/assets/profiles/hono-drizzle/README.md +31 -0
- package/assets/profiles/hono-drizzle/agents/api-engineer.md +82 -0
- package/assets/profiles/laravel/README.md +32 -0
- package/assets/profiles/laravel/agents/api-engineer.md +114 -0
- package/assets/profiles/laravel/agents/fullstack-engineer.md +67 -0
- package/assets/profiles/laravel/agents/migration-specialist.md +420 -0
- package/assets/profiles/nestjs/agents/api-engineer.md +79 -0
- package/assets/profiles/nextjs-admin/README.md +32 -0
- package/assets/profiles/nextjs-admin/agents/admin-engineer.md +78 -0
- package/assets/profiles/playwright-crawler/agents/scanner-engineer.md +51 -0
- package/assets/profiles/rails/agents/fullstack-engineer.md +61 -0
- package/assets/profiles/sveltekit/agents/frontend-engineer.md +96 -0
- package/assets/profiles/vuenuxt/agents/frontend-engineer.md +82 -0
- package/assets/profiles/wordpress/README.md +30 -0
- package/assets/profiles/wordpress/agents/divi-engineer.md +273 -0
- package/assets/profiles/wordpress/agents/elementor-engineer.md +310 -0
- package/assets/profiles/wordpress/agents/wp-engineer.md +216 -0
- package/assets/requirements.txt +2 -0
- package/assets/scripts/aitmpl-search.py +808 -0
- package/assets/scripts/forge-add-opportunities.py +92 -0
- package/assets/scripts/forge-audit.py +1061 -0
- package/assets/scripts/forge-generate-all.py +283 -0
- package/assets/scripts/forge-init.py +900 -0
- package/assets/scripts/forge-migrate-project-yaml.py +397 -0
- package/assets/scripts/forge-scaffold-profile.py +181 -0
- package/assets/scripts/forge-teardown.py +193 -0
- package/assets/scripts/forge-validate-project-yaml.py +457 -0
- package/assets/scripts/forge-wizard.py +1003 -0
- package/assets/scripts/setup-codex.sh +229 -0
- package/assets/scripts/team-install.sh +147 -0
- package/assets/scripts/token-stats.py +201 -0
- package/assets/templates/modes/enterprise.yaml.tpl +114 -0
- package/assets/templates/modes/multi-runtime.yaml.tpl +89 -0
- package/assets/templates/modes/new-stack.yaml.tpl +101 -0
- package/assets/templates/modes/startup.yaml.tpl +74 -0
- package/assets/templates/project.yaml.tpl +185 -0
- package/assets/templates/wiki/concepts/_template.md +22 -0
- package/assets/templates/wiki/entities/_template.md +19 -0
- package/assets/templates/wiki/index.md +32 -0
- package/assets/templates/wiki/log.md +6 -0
- package/assets/templates/wiki/sources/_template.md +25 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +64 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +21 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +58 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +27 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +22 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/validate.d.ts +2 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +20 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/lib/paths.d.ts +10 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +49 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/python.d.ts +4 -0
- package/dist/lib/python.d.ts.map +1 -0
- package/dist/lib/python.js +46 -0
- package/dist/lib/python.js.map +1 -0
- 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 |
|