@cristiancorreau/forge 2.9.13 → 2.10.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 (56) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +8 -3
  3. package/assets/adapters/claude-code/commands/session-close.md +5 -106
  4. package/assets/adapters/claude-code/commands/session-start.md +5 -56
  5. package/assets/adapters/codex/HOOKS.md +55 -0
  6. package/assets/adapters/codex/commands/session-close.md +5 -45
  7. package/assets/adapters/codex/commands/session-start.md +6 -41
  8. package/assets/adapters/opencode/HOOKS.md +31 -3
  9. package/assets/adapters/opencode/commands/session-close.md +5 -106
  10. package/assets/adapters/opencode/commands/session-start.md +6 -57
  11. package/assets/core/hooks/hooks-registry.yaml +18 -31
  12. package/assets/core/hooks/pre-edit-check.js +108 -0
  13. package/assets/core/schemas/project.schema.json +5 -0
  14. package/assets/core/skills/README.md +9 -0
  15. package/assets/core/skills/session-close/SKILL.md +137 -0
  16. package/assets/core/skills/session-start/SKILL.md +86 -0
  17. package/assets/manifest.json +1 -1
  18. package/dist/cli.js +12 -1
  19. package/dist/cli.js.map +1 -1
  20. package/dist/commands/audit.d.ts.map +1 -1
  21. package/dist/commands/audit.js +11 -0
  22. package/dist/commands/audit.js.map +1 -1
  23. package/dist/commands/generate.d.ts.map +1 -1
  24. package/dist/commands/generate.js +28 -5
  25. package/dist/commands/generate.js.map +1 -1
  26. package/dist/commands/init.d.ts.map +1 -1
  27. package/dist/commands/init.js +40 -4
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/scaffold.d.ts.map +1 -1
  30. package/dist/commands/scaffold.js +107 -4
  31. package/dist/commands/scaffold.js.map +1 -1
  32. package/dist/commands/session.d.ts +3 -0
  33. package/dist/commands/session.d.ts.map +1 -0
  34. package/dist/commands/session.js +78 -0
  35. package/dist/commands/session.js.map +1 -0
  36. package/dist/commands/validate.d.ts.map +1 -1
  37. package/dist/commands/validate.js +27 -1
  38. package/dist/commands/validate.js.map +1 -1
  39. package/dist/lib/catalog.d.ts.map +1 -1
  40. package/dist/lib/catalog.js +2 -0
  41. package/dist/lib/catalog.js.map +1 -1
  42. package/dist/lib/generators/codex.d.ts +1 -0
  43. package/dist/lib/generators/codex.d.ts.map +1 -1
  44. package/dist/lib/generators/codex.js +13 -3
  45. package/dist/lib/generators/codex.js.map +1 -1
  46. package/dist/lib/generators/kiro.d.ts +2 -0
  47. package/dist/lib/generators/kiro.d.ts.map +1 -1
  48. package/dist/lib/generators/kiro.js +32 -0
  49. package/dist/lib/generators/kiro.js.map +1 -1
  50. package/dist/lib/generators/opencode.d.ts +9 -0
  51. package/dist/lib/generators/opencode.d.ts.map +1 -1
  52. package/dist/lib/generators/opencode.js +64 -1
  53. package/dist/lib/generators/opencode.js.map +1 -1
  54. package/dist/version.d.ts +1 -1
  55. package/dist/version.js +1 -1
  56. package/package.json +2 -2
@@ -1,62 +1,11 @@
1
1
  ---
2
2
  name: session-start
3
- description: Inicializa una sesión de trabajo detectando el estado del repo y enrutando según el escenario.
3
+ description: Inicializa una sesión de trabajo siguiendo el skill central de forge.
4
4
  ---
5
5
 
6
- Inicializa la sesión de trabajo: detecta el estado del repo, identifica el escenario y actúa según corresponda.
6
+ Inicializa la sesión de trabajo siguiendo el skill central `core/skills/session-start/SKILL.md`.
7
7
 
8
- ## Paso 1 Leer estado del repo
9
-
10
- Ejecutar los siguientes comandos y guardar sus resultados:
11
-
12
- - `git branch --show-current` → rama actual
13
- - `git status --short` → cambios sin commitear
14
- - `git log --oneline -5` → commits recientes
15
- - `gh pr list --author @me --state open --json number,title,headRefName 2>/dev/null` → PRs abiertos (saltar si gh no está disponible)
16
- - `git branch --sort=-committerdate --format='%(refname:short)' | grep -v 'HEAD' | head -8` → ramas recientes
17
-
18
- ## Paso 2 — Leer configuración del proyecto
19
-
20
- - Si existe `project.yaml` en el directorio actual, leerlo para obtener: `project.mode`, `project.name`, `stack.*`, `agents.active`
21
- - Si existe `AGENTS.md`, leerlo para obtener contexto del proyecto
22
- - Si ninguno existe, continuar con defaults: mode=startup
23
-
24
- ## Paso 3 — Evaluar escenario y actuar
25
-
26
- ### Escenario A — Ya en una rama de feature (no es main/master/develop)
27
-
28
- - Mostrar los últimos 5 commits de esta rama para contexto
29
- - Mostrar archivos con cambios sin commitear si los hay
30
- - Reportar: "Continuando sesión en [branch]. Contexto: [mensaje del último commit]."
31
- - Preguntar: "¿Qué trabajamos hoy?"
32
-
33
- ### Escenario B — En main/master con PRs abiertos o ramas recientes de feature
34
-
35
- - Listar los PRs abiertos del Paso 1 como menú numerado
36
- - Listar las ramas recientes que no sean main/master/develop del Paso 1
37
- - Preguntar: "¿Continuás uno de estos o arrancamos algo nuevo?"
38
- - Si el usuario elige continuar uno existente: hacer checkout de esa rama
39
- - Si el usuario quiere algo nuevo: pasar al flujo del Escenario C
40
-
41
- ### Escenario C — En main/master sin trabajo previo identificado
42
-
43
- - Esperar el primer mensaje del usuario describiendo qué quiere trabajar
44
- - Antes de cualquier edición de código, proponer un nombre de rama siguiendo la convención:
45
- - `feature/<tema-corto>-$(date +%Y-%m-%d)` para features
46
- - `fix/<tema-corto>-$(date +%Y-%m-%d)` para correcciones
47
- - `chore/<tema-corto>-$(date +%Y-%m-%d)` para tareas técnicas
48
- - `docs/<tema-corto>-$(date +%Y-%m-%d)` para documentación
49
- - Crear la rama: `git checkout -b <nombre-propuesto>`
50
- - Confirmar: "Branch creada: [nombre]. Listo para trabajar."
51
-
52
- ## Paso 4 — Recordatorio de reglas de sesión
53
-
54
- Una vez determinado el escenario, enunciar estas reglas una sola vez:
55
-
56
- "Reglas de sesión: (1) No editar código en main. (2) Conventional Commits. (3) Spec antes de implementar si el proyecto es standard/enterprise. (4) Implementar en serie — OpenCode no soporta subagentes paralelos. (5) Cerrar con /session-close."
57
-
58
- ## Comportamiento adaptativo
59
-
60
- - Si `gh` no está disponible: omitir los pasos que lo requieren y agregar nota "gh no disponible — revisar PRs en GitHub.com manualmente"
61
- - Si `project.yaml` no existe: continuar con defaults, no interrumpir el flujo
62
- - Si la rama actual no sigue la convención de nombres: mencionarlo pero no bloquear
8
+ Ejecuta sus 4 pasos en orden: (1) leer estado del repo, (2) leer `project.yaml`
9
+ (en OpenCode, leer también `AGENTS.md` para contexto), (3) evaluar el escenario
10
+ A/B/C y enrutar, (4) enunciar las reglas de sesión una sola vez. Recordá la regla
11
+ extra de OpenCode: implementar en serie — OpenCode no soporta subagentes paralelos.
@@ -1,48 +1,35 @@
1
1
  # Forge v2 — Hooks registry
2
2
  # Define qué hooks se instalan según mode y stack del proyecto.
3
+ # Solo se referencian hooks JavaScript puro (.js) o shell scripts (.sh):
4
+ # sin dependencias de Python. Cada entrada debe tener un archivo real en core/hooks/.
3
5
 
4
6
  universal:
5
7
  # Se instalan en TODOS los proyectos
6
- - hook: pre-edit-check.py
8
+ - hook: pre-edit-check.js
7
9
  event: PreToolUse
8
10
  matcher: "Edit|Write"
9
- description: "Branch guard, debug detection, secret detection"
11
+ description: >-
12
+ Branch guard, debug detection, secret detection y spec gate (issue #28):
13
+ al editar código en una rama feature exige una spec APPROVED en
14
+ docs/specs/. Backward-compatible: advierte por defecto y solo bloquea con
15
+ mode=enterprise + rules.require_spec_before_implementation. Ver
16
+ docs/spec-gate-flow.md.
10
17
  - hook: post-turn-check.sh
11
18
  event: Stop
12
19
  description: "Typecheck/lint sobre archivos modificados"
20
+ - hook: session-start.sh
21
+ event: SessionStart
22
+ description: "Carga project.yaml + estado al iniciar la sesión"
13
23
 
14
24
  standard:
15
25
  # mode: standard + enterprise
16
- - hook: pre-bash-check.py
26
+ - hook: pre-bash-check.js
17
27
  event: PreToolUse
18
28
  matcher: "Bash"
19
29
  description: "Bloquea comandos destructivos en producción"
20
30
 
21
- enterprise:
22
- # mode: enterprise solamente (además de standard)
23
- - hook: audit-log-append.py
24
- event: PostToolUse
25
- description: "Audit log inmutable para SOC2/compliance"
26
-
27
- stack:
28
- # Por stack (además de los universales)
29
- supabase:
30
- - hook: check-destructive-sql.py
31
- event: PreToolUse
32
- matcher: "Bash"
33
- description: "Detecta SQL destructivo contra Supabase"
34
- prisma:
35
- - hook: prisma-safety.py
36
- event: PreToolUse
37
- matcher: "Bash"
38
- description: "Bloquea migrate reset en producción"
39
- nextjs-admin:
40
- - hook: prisma-safety.py
41
- event: PreToolUse
42
- matcher: "Bash"
43
- description: "Bloquea prisma migrate reset/fresh en producción (aplica si el profile usa Prisma como ORM)"
44
- laravel:
45
- - hook: composer-check.py
46
- event: PreToolUse
47
- matcher: "Bash"
48
- description: "Verifica que composer install/update no instale paquetes de dev en producción; advierte sobre artisan migrate:fresh/reset"
31
+ # NOTA (issue #23): los hooks de enterprise y por stack (audit-log-append,
32
+ # check-destructive-sql, prisma-safety, composer-check) aún no están
33
+ # implementados como .js/.sh, por lo que se omiten del registry hasta que
34
+ # exista una spec SDD aprobada y su implementación en core/hooks/.
35
+ # No agregar referencias a hooks .py: la CLI corre 100% en Node.js.
@@ -6,11 +6,57 @@
6
6
  'use strict';
7
7
 
8
8
  const { execSync } = require('child_process');
9
+ const fs = require('fs');
9
10
  const path = require('path');
10
11
 
11
12
  const DEBUG = !['', '0', 'false', 'False'].includes(process.env.DEBUG || '');
12
13
  const dbg = msg => DEBUG && process.stdout.write(`[forge-hook-debug] ${msg}\n`);
13
14
 
15
+ // ---------------------------------------------------------------------------
16
+ // project.yaml — minimal loader (no YAML dependency, mirrors pre-bash-check.js)
17
+ // Only needs `mode` and `rules.require_spec_before_implementation`, both of
18
+ // which are flat or one-level-nested scalars.
19
+ // ---------------------------------------------------------------------------
20
+ function findRepoRoot(start) {
21
+ let dir = start;
22
+ for (let i = 0; i < 8; i++) {
23
+ if (fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, 'project.yaml'))) {
24
+ return dir;
25
+ }
26
+ const parent = path.dirname(dir);
27
+ if (parent === dir) break;
28
+ dir = parent;
29
+ }
30
+ return start;
31
+ }
32
+
33
+ function loadProjectYaml() {
34
+ try {
35
+ let dir = process.cwd();
36
+ for (let i = 0; i < 8; i++) {
37
+ const candidate = path.join(dir, 'project.yaml');
38
+ if (fs.existsSync(candidate)) {
39
+ return { text: fs.readFileSync(candidate, 'utf8'), root: dir };
40
+ }
41
+ const parent = path.dirname(dir);
42
+ if (parent === dir) break;
43
+ dir = parent;
44
+ }
45
+ } catch (e) { dbg(`project.yaml load error: ${e}`); }
46
+ return { text: '', root: findRepoRoot(process.cwd()) };
47
+ }
48
+
49
+ // Match either a top-level `mode: enterprise` or a nested `project.mode`.
50
+ function isEnterpriseMode(text) {
51
+ return /^\s*mode\s*:\s*["']?enterprise["']?/m.test(text || '');
52
+ }
53
+
54
+ // The hard gate is opt-in: only escalates to a block when the project
55
+ // explicitly enables `rules.require_spec_before_implementation`.
56
+ function specGateRequired(text) {
57
+ return /require_spec_before_implementation\s*:\s*(true|yes|on)\b/i.test(text || '');
58
+ }
59
+
14
60
  // ---------------------------------------------------------------------------
15
61
  // File classification
16
62
  // ---------------------------------------------------------------------------
@@ -100,6 +146,49 @@ function detectSecrets(content) {
100
146
  return found;
101
147
  }
102
148
 
149
+ // ---------------------------------------------------------------------------
150
+ // Spec gate (issue #28) — require an APPROVED spec in docs/specs/ before code
151
+ // edits on a feature branch. BACKWARD-COMPATIBLE: warns by default, escalates
152
+ // to a hard block ONLY when mode=enterprise AND require_spec_before_implementation.
153
+ // ---------------------------------------------------------------------------
154
+ const PROTECTED_BRANCHES = new Set(['main', 'master', 'develop']);
155
+
156
+ // A spec file counts as APPROVED when a status/estado header line names APPROVED
157
+ // on its own (not the template's "DRAFT | REVIEW | APPROVED | IMPLEMENTED" menu).
158
+ function specIsApproved(content) {
159
+ const lines = content.split('\n');
160
+ for (const line of lines) {
161
+ if (!/^\s*>?\s*(estado|status)\s*:/i.test(line)) continue;
162
+ const value = line.replace(/^\s*>?\s*(estado|status)\s*:/i, '').trim();
163
+ if (/\bAPPROVED\b/i.test(value) && !value.includes('|')) return true;
164
+ }
165
+ return false;
166
+ }
167
+
168
+ // Returns true when docs/specs/ holds at least one APPROVED spec.
169
+ function hasApprovedSpec(repoRoot) {
170
+ const specsDir = path.join(repoRoot, 'docs', 'specs');
171
+ let entries;
172
+ try { entries = fs.readdirSync(specsDir); } catch { return false; }
173
+ for (const name of entries) {
174
+ if (!name.endsWith('.md')) continue;
175
+ if (name === '_template.md' || name === 'README.md') continue;
176
+ try {
177
+ if (specIsApproved(fs.readFileSync(path.join(specsDir, name), 'utf8'))) return true;
178
+ } catch { /* ignore unreadable spec */ }
179
+ }
180
+ return false;
181
+ }
182
+
183
+ const SPEC_GATE_MSG =
184
+ `forge: no se encontró una spec APPROVED en docs/specs/.\n\n` +
185
+ ` El flujo spec-first (SDD) exige una spec aprobada antes de editar código.\n\n` +
186
+ ` Para crearla:\n` +
187
+ ` 1. cp docs/specs/_template.md docs/specs/<id>-<slug>.md\n` +
188
+ ` 2. completá Contexto, Decisión y Criterios de aceptación\n` +
189
+ ` 3. pasá el encabezado a "Estado: APPROVED" tras la revisión\n\n` +
190
+ ` Detalle del gate: docs/spec-gate-flow.md\n`;
191
+
103
192
  // ---------------------------------------------------------------------------
104
193
  // Main
105
194
  // ---------------------------------------------------------------------------
@@ -136,6 +225,25 @@ process.stdin.on('end', () => {
136
225
  );
137
226
  process.exit(2);
138
227
  }
228
+
229
+ // 1b. Spec gate — require an APPROVED spec on feature branches.
230
+ if (!PROTECTED_BRANCHES.has(branch) && branch) {
231
+ const { text, root } = loadProjectYaml();
232
+ if (!hasApprovedSpec(root)) {
233
+ const block = isEnterpriseMode(text) && specGateRequired(text);
234
+ dbg(`spec gate: no APPROVED spec — ${block ? 'BLOCK (enterprise)' : 'WARN'}`);
235
+ if (block) {
236
+ process.stdout.write(SPEC_GATE_MSG);
237
+ process.exit(2);
238
+ }
239
+ warnings.push(
240
+ `Spec gate: no hay una spec APPROVED en docs/specs/.\n` +
241
+ ` El flujo spec-first lo recomienda antes de editar código.\n` +
242
+ ` (Solo advertencia; se vuelve bloqueante con mode=enterprise +\n` +
243
+ ` rules.require_spec_before_implementation. Ver docs/spec-gate-flow.md)`
244
+ );
245
+ }
246
+ }
139
247
  }
140
248
 
141
249
  // 2. Debug and secret detection on new content
@@ -160,6 +160,11 @@
160
160
  "type": ["array", "null"],
161
161
  "items": { "type": "string" }
162
162
  },
163
+ "specialized": {
164
+ "description": "Agentes Tier 3 (de dominio) propios del proyecto, que viven en .claude/agents/ y no provienen de forge",
165
+ "type": ["array", "null"],
166
+ "items": { "type": "string" }
167
+ },
163
168
  "profiles": {
164
169
  "description": "Profiles stack-específicos (Tier 2) que reemplazan agentes genéricos",
165
170
  "type": ["array", "null"],
@@ -3,6 +3,8 @@
3
3
  ## Mapa de skills y dependencias
4
4
 
5
5
  ```
6
+ session-start → (sesión de trabajo) → session-close
7
+
6
8
  new-feature ──────────────────────────────────────────────────┐
7
9
  │ │
8
10
  ├── spec (Fase 1 — verificar/crear spec) │
@@ -26,6 +28,8 @@ wiki-lint → health check + auto-reparación
26
28
 
27
29
  | Skill | Categoría | Requiere herramienta externa | Invocado por |
28
30
  |-------|-----------|------------------------------|--------------|
31
+ | `session-start` | Sesión | No (`gh` opcional) | Usuario directo |
32
+ | `session-close` | Sesión | No (`gh` opcional) | Usuario directo |
29
33
  | `spec` | Core | No | new-feature, orchestrator |
30
34
  | `phase-kickoff` | Core | No | orchestrator, new-feature |
31
35
  | `security-audit` | Universal | No | new-feature, security-auditor |
@@ -40,6 +44,11 @@ wiki-lint → health check + auto-reparación
40
44
 
41
45
  ## Categorías
42
46
 
47
+ ### Sesión — bookend del flujo de trabajo
48
+ Generan los slash commands `/session-start` y `/session-close`. Abren y cierran el ciclo SDD:
49
+ - **`session-start`**: detecta el estado del repo y enruta según el escenario (rama activa, main con PRs, main limpio)
50
+ - **`session-close`**: pipeline de 8 pasos (commit → changeset → GitHub Projects → daily note → RELEASE-NOTES → cierre → sync → PR)
51
+
43
52
  ### Core — siempre presentes
44
53
  - **`spec`**: redactar specs siguiendo la plantilla obligatoria
45
54
  - **`phase-kickoff`**: protocolo para iniciar un sprint o fase nueva
@@ -0,0 +1,137 @@
1
+ # Skill: session-close
2
+
3
+ Cierra la sesión de trabajo con un pipeline de 8 pasos: commit, changeset,
4
+ GitHub Projects, daily note, release notes, commit de cierre, sync y PR. Es el
5
+ último paso del flujo de trabajo SDD.
6
+
7
+ Triggers: /session-close, "cerrar sesión", "terminar sesión", "cerrar el día"
8
+
9
+ ---
10
+
11
+ ## Cuándo usar este skill
12
+
13
+ - Al terminar de trabajar en una rama de feature
14
+ - Para dejar la rama lista para PR (commit, daily note, sync con main)
15
+ - Para registrar progreso y cerrar issues trabajados en la sesión
16
+
17
+ No usar para iniciar la sesión — para eso está `session-start`.
18
+
19
+ ---
20
+
21
+ ## Paso 1 — Verificar estado
22
+
23
+ Ejecutar `git status --short` y `git diff --stat HEAD 2>/dev/null`. Reportar qué hay pendiente.
24
+
25
+ ## Paso 2 — Commitear cambios pendientes
26
+
27
+ Si hay cambios sin commitear:
28
+
29
+ - Preguntar al usuario: "¿Qué describe este commit? (usa Conventional Commits: feat/fix/chore/docs/refactor/test)"
30
+ - Commitear con: `git add -A && git commit -m "<type>(<scope>): <descripción>"`
31
+ - Incluir siempre en el cuerpo del commit el `Co-Authored-By` del runtime activo
32
+
33
+ Si no hay nada que commitear: indicar "Nada pendiente de commitear." y continuar.
34
+
35
+ ## Paso 3 — Changeset (condicional)
36
+
37
+ Si el commit del Paso 2 fue de tipo `feat:` o `fix:` Y `package.json` contiene `"@changesets/cli"`:
38
+
39
+ - Ejecutar `npx changeset` para generar el changeset
40
+
41
+ En cualquier otro caso, omitir este paso sin mencionarlo.
42
+
43
+ ## Paso 4 — GitHub Projects (condicional)
44
+
45
+ Leer `project.yaml`. Si tiene una sección `github.project` con `number`, `owner` y `repo`:
46
+
47
+ - Preguntar: "¿Qué issues trabajaste en esta sesión? (números separados por coma, o Enter para saltar)"
48
+ - Si el usuario provee números: ejecutar `gh issue close <N> --comment "Completado en esta sesión"` para cada uno
49
+ - Mover los issues a Done en el proyecto si es posible con gh CLI
50
+
51
+ Si `project.yaml` no tiene sección `github.project`: indicar "GitHub Projects no configurado en project.yaml." y continuar.
52
+
53
+ ## Paso 5 — Daily note
54
+
55
+ Crear el directorio `docs/daily-notes/` si no existe.
56
+
57
+ Determinar:
58
+ - `FECHA`: resultado de `date +%Y-%m-%d`
59
+ - `TEMA`: derivado del nombre de la rama actual, eliminando el prefijo de tipo y el sufijo de fecha (ej: `feature/billing-webpay-2026-05-16` → `billing-webpay`)
60
+
61
+ Crear el archivo `docs/daily-notes/FECHA-TEMA.md` con este contenido:
62
+
63
+ ```
64
+ # Session FECHA — TEMA
65
+
66
+ ## Completado
67
+ [listar qué se implementó o cambió en esta sesión]
68
+
69
+ ## Archivos modificados
70
+ [output de: git diff --name-only HEAD~1..HEAD 2>/dev/null || git diff --name-only HEAD 2>/dev/null]
71
+
72
+ ## Commits
73
+ [output de: git log --oneline HEAD~3..HEAD]
74
+
75
+ ## Decisiones tomadas
76
+ [preguntar al usuario: "¿Alguna decisión de diseño o arquitectura que vale registrar?"]
77
+
78
+ ## Blockers para próxima sesión
79
+ [preguntar al usuario: "¿Quedó algo bloqueado o incompleto?"]
80
+ ```
81
+
82
+ Completar las secciones "Archivos modificados" y "Commits" con los outputs reales. Completar "Completado", "Decisiones tomadas" y "Blockers" con las respuestas del usuario.
83
+
84
+ ## Paso 6 — RELEASE-NOTES.md
85
+
86
+ Agregar al final de `RELEASE-NOTES.md` (crear si no existe):
87
+
88
+ ```
89
+ ## FECHA — TEMA
90
+ [resumen en una línea de qué cambió]
91
+ ```
92
+
93
+ Usar el resumen de la daily note para redactar la línea.
94
+
95
+ ## Paso 7 — Commit de cierre
96
+
97
+ Commitear los archivos generados:
98
+
99
+ ```
100
+ git add docs/daily-notes/ RELEASE-NOTES.md && git commit -m "docs(progress): session close FECHA — TEMA"
101
+ ```
102
+
103
+ ## Paso 8 — Sync y PR
104
+
105
+ Ejecutar:
106
+
107
+ ```
108
+ git fetch origin main
109
+ git log HEAD..origin/main --oneline
110
+ ```
111
+
112
+ - Si main tiene commits nuevos: ejecutar `git rebase origin/main` y luego `git push --force-with-lease origin <rama-actual>`
113
+ - Si main no tiene cambios nuevos: ejecutar `git push -u origin <rama-actual>`
114
+
115
+ Luego crear el PR:
116
+
117
+ ```
118
+ gh pr create --title "<resumen de cambios>" --body "<resumen de sesión tomado de la daily note>"
119
+ ```
120
+
121
+ Si `gh` no está disponible: hacer solo el push y recordar al usuario "Crea el PR manualmente en GitHub."
122
+
123
+ ## Al finalizar el pipeline
124
+
125
+ Confirmar con: "Sesión cerrada. PR creado: [URL]" o, si gh no estuvo disponible: "Sesión cerrada. Push hecho a [branch] — crea el PR en GitHub."
126
+
127
+ ---
128
+
129
+ ## Relación con otros skills
130
+
131
+ `session-close` cierra el ciclo que abre `session-start`. El Paso 8 (sync + PR)
132
+ es equivalente al deploy de `local2prod` cuando el proyecto publica desde main;
133
+ si ya usaste `local2prod` en la sesión, podés saltear el push y solo abrir el PR.
134
+
135
+ ```
136
+ session-start → new-feature → session-close (commit → … → sync → PR)
137
+ ```
@@ -0,0 +1,86 @@
1
+ # Skill: session-start
2
+
3
+ Inicializa una sesión de trabajo: detecta el estado del repo, identifica el
4
+ escenario y enruta según corresponda. Es el primer paso del flujo de trabajo
5
+ SDD, antes de cualquier edición de código.
6
+
7
+ Triggers: /session-start, "iniciar sesión", "arrancar sesión", "empezar a trabajar"
8
+
9
+ ---
10
+
11
+ ## Cuándo usar este skill
12
+
13
+ - Al abrir el editor/agente y comenzar a trabajar en el repo
14
+ - Para retomar una rama de feature en progreso
15
+ - Para decidir si continuar un PR abierto o arrancar algo nuevo
16
+
17
+ No usar para cerrar la sesión — para eso está `session-close`.
18
+
19
+ ---
20
+
21
+ ## Paso 1 — Leer estado del repo
22
+
23
+ Ejecutar los siguientes comandos y guardar sus resultados:
24
+
25
+ - `git branch --show-current` → rama actual
26
+ - `git status --short` → cambios sin commitear
27
+ - `git log --oneline -5` → commits recientes
28
+ - `gh pr list --author @me --state open --json number,title,headRefName 2>/dev/null` → PRs abiertos (saltar si gh no está disponible)
29
+ - `git branch --sort=-committerdate --format='%(refname:short)' | grep -v 'HEAD' | head -8` → ramas recientes
30
+
31
+ ## Paso 2 — Leer configuración del proyecto
32
+
33
+ - Si existe `project.yaml` en el directorio actual, leerlo para obtener: `project.mode`, `project.name`, `stack.*`, `agents.active`
34
+ - Si existe `wiki/index.md`, leerlo para obtener contexto del proyecto
35
+ - Si ninguno existe, continuar con defaults: mode=startup, sin checks de compliance
36
+
37
+ ## Paso 3 — Evaluar escenario y actuar
38
+
39
+ ### Escenario A — Ya en una rama de feature (no es main/master/develop)
40
+
41
+ - Mostrar los últimos 5 commits de esta rama para contexto
42
+ - Mostrar archivos con cambios sin commitear si los hay
43
+ - Reportar: "Continuando sesión en [branch]. Contexto: [mensaje del último commit]."
44
+ - Preguntar: "¿Qué trabajamos hoy?"
45
+
46
+ ### Escenario B — En main/master con PRs abiertos o ramas recientes de feature
47
+
48
+ - Listar los PRs abiertos del Paso 1 como menú numerado
49
+ - Listar las ramas recientes que no sean main/master/develop del Paso 1
50
+ - Preguntar: "¿Continuás uno de estos o arrancamos algo nuevo?"
51
+ - Si el usuario elige continuar uno existente: hacer checkout de esa rama
52
+ - Si el usuario quiere algo nuevo: pasar al flujo del Escenario C
53
+
54
+ ### Escenario C — En main/master sin trabajo previo identificado
55
+
56
+ - Esperar el primer mensaje del usuario describiendo qué quiere trabajar
57
+ - Antes de cualquier edición de código, proponer un nombre de rama siguiendo la convención:
58
+ - `feature/<tema-corto>-$(date +%Y-%m-%d)` para features
59
+ - `fix/<tema-corto>-$(date +%Y-%m-%d)` para correcciones
60
+ - `chore/<tema-corto>-$(date +%Y-%m-%d)` para tareas técnicas
61
+ - `docs/<tema-corto>-$(date +%Y-%m-%d)` para documentación
62
+ - Crear la rama: `git checkout -b <nombre-propuesto>`
63
+ - Confirmar: "Branch creada: [nombre]. Listo para trabajar."
64
+
65
+ ## Paso 4 — Recordatorio de reglas de sesión
66
+
67
+ Una vez determinado el escenario, enunciar estas reglas una sola vez:
68
+
69
+ "Reglas de sesión: (1) No editar código en main. (2) Conventional Commits. (3) Spec antes de implementar si el proyecto es standard/enterprise. (4) Cerrar con /session-close."
70
+
71
+ ## Comportamiento adaptativo
72
+
73
+ - Si `gh` no está disponible: omitir los pasos que lo requieren y agregar nota "gh no disponible — revisar PRs en GitHub.com manualmente"
74
+ - Si `project.yaml` no existe: continuar con defaults, no interrumpir el flujo
75
+ - Si la rama actual no sigue la convención de nombres: mencionarlo pero no bloquear
76
+
77
+ ---
78
+
79
+ ## Relación con otros skills
80
+
81
+ `session-start` abre el ciclo de trabajo; `session-close` lo cierra. Entre ambos,
82
+ `new-feature` orquesta la implementación (spec → seguridad → migración → deploy).
83
+
84
+ ```
85
+ session-start → new-feature (spec, security-audit, db-migrate, …) → session-close
86
+ ```
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge",
3
- "version": "2.0.1",
3
+ "version": "2.10.0",
4
4
  "description": "Agentic development framework for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "repository": "https://github.com/cristiancorreau/forge",
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import { skills } from './commands/skills.js';
10
10
  import { aitmplSearch } from './commands/aitmpl-search.js';
11
11
  import { scaffold } from './commands/scaffold.js';
12
12
  import { teardown } from './commands/teardown.js';
13
+ import { sessionStart, sessionClose } from './commands/session.js';
13
14
  import { VERSION } from './version.js';
14
15
  const HELP = `forge v${VERSION} — Agentic development framework
15
16
 
@@ -19,7 +20,7 @@ Setup
19
20
  init Initialize forge in a project (wizard + post-install dashboard)
20
21
  generate Generate runtime config files from project.yaml
21
22
  migrate Migrate project.yaml from the v1 schema to v2 (--dry-run, --backup)
22
- scaffold Scaffold a new Tier 2 profile (profiles/<stack>/agents/<engineer>.md)
23
+ scaffold Scaffold a new agent: Tier 2 profile, or Tier 3 domain agent (--tier 3)
23
24
  teardown Cleanly uninstall forge from a project (manifest-driven)
24
25
 
25
26
  Inspect
@@ -29,6 +30,10 @@ Inspect
29
30
  skills List available forge skills grouped by category
30
31
  aitmpl-search Search the curated offline catalog (frameworks, MCP servers, profiles)
31
32
 
33
+ Workflow
34
+ session-start Open a work session (prints the /session-start skill steps)
35
+ session-close Close a work session (prints the /session-close skill steps)
36
+
32
37
  Knowledge
33
38
  wiki Manage the project knowledge base (status | ingest | query | lint)
34
39
 
@@ -81,6 +86,12 @@ switch (cmd) {
81
86
  case 'teardown':
82
87
  exitCode = await teardown(rest);
83
88
  break;
89
+ case 'session-start':
90
+ exitCode = await sessionStart(rest);
91
+ break;
92
+ case 'session-close':
93
+ exitCode = await sessionClose(rest);
94
+ break;
84
95
  case '-v':
85
96
  case '--version':
86
97
  console.log(VERSION);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,IAAI,GAAG,UAAU,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiC7B,CAAC;AAEF,MAAM,CAAC,EAAE,AAAD,EAAG,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;AAExC,IAAI,QAAQ,GAAG,CAAC,CAAC;AAEjB,QAAQ,GAAG,EAAE,CAAC;IACZ,KAAK,MAAM;QACT,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM;IACR,KAAK,OAAO;QACV,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM;IACR,KAAK,UAAU;QACb,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM;IACR,KAAK,UAAU;QACb,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM;IACR,KAAK,QAAQ;QACX,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM;IACR,KAAK,SAAS;QACZ,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM;IACR,KAAK,MAAM;QACT,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,MAAM;IACR,KAAK,QAAQ;QACX,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM;IACR,KAAK,eAAe;QAClB,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM;IACR,KAAK,UAAU;QACb,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM;IACR,KAAK,UAAU;QACb,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM;IACR,KAAK,IAAI,CAAC;IACV,KAAK,WAAW;QACd,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,MAAM;IACR,KAAK,SAAS,CAAC;IACf,KAAK,IAAI,CAAC;IACV,KAAK,QAAQ;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,MAAM;IACR;QACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,iCAAiC,CAAC,CAAC;QACxE,QAAQ,GAAG,CAAC,CAAC;AACjB,CAAC;AAED,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"AA0NA,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA4J3D"}
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"AA0NA,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuK3D"}
@@ -263,6 +263,17 @@ export async function audit(args) {
263
263
  issues.push(...agentIssues);
264
264
  }
265
265
  }
266
+ // Tier 3: cada agente declarado en agents.specialized debe tener su archivo.
267
+ const specialized = config?.agents?.specialized ?? [];
268
+ for (const name of specialized) {
269
+ const file = join(agentsDir, `${name}.md`);
270
+ if (existsSync(file)) {
271
+ issues.push({ level: 'ok', check: 'specialized', message: `Tier 3 '${name}' declarado y presente en .claude/agents/` });
272
+ }
273
+ else {
274
+ issues.push({ level: 'error', check: 'specialized', message: `Tier 3 '${name}' en agents.specialized pero falta .claude/agents/${name}.md` });
275
+ }
276
+ }
266
277
  }
267
278
  // Check hooks
268
279
  const hooksDir = join(root, '.claude', 'hooks');