@cristiancorreau/forge 2.9.13 → 2.10.1
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 +41 -0
- package/README.md +20 -7
- package/assets/adapters/claude-code/commands/session-close.md +5 -106
- package/assets/adapters/claude-code/commands/session-start.md +5 -56
- package/assets/adapters/codex/HOOKS.md +55 -0
- package/assets/adapters/codex/commands/session-close.md +5 -45
- package/assets/adapters/codex/commands/session-start.md +6 -41
- package/assets/adapters/opencode/HOOKS.md +31 -3
- package/assets/adapters/opencode/commands/session-close.md +5 -106
- package/assets/adapters/opencode/commands/session-start.md +6 -57
- package/assets/core/hooks/hooks-registry.yaml +18 -31
- package/assets/core/hooks/pre-edit-check.js +108 -0
- package/assets/core/schemas/project.schema.json +6 -1
- package/assets/core/skills/README.md +9 -0
- package/assets/core/skills/session-close/SKILL.md +137 -0
- package/assets/core/skills/session-start/SKILL.md +86 -0
- package/assets/manifest.json +2 -2
- package/dist/cli.js +12 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +11 -0
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +28 -5
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +128 -8
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/scaffold.d.ts.map +1 -1
- package/dist/commands/scaffold.js +107 -4
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/session.d.ts +3 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +78 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +27 -1
- package/dist/commands/validate.js.map +1 -1
- package/dist/lib/catalog.d.ts.map +1 -1
- package/dist/lib/catalog.js +2 -0
- package/dist/lib/catalog.js.map +1 -1
- package/dist/lib/generators/claude-code.d.ts.map +1 -1
- package/dist/lib/generators/claude-code.js +14 -2
- package/dist/lib/generators/claude-code.js.map +1 -1
- package/dist/lib/generators/codex.d.ts +1 -0
- package/dist/lib/generators/codex.d.ts.map +1 -1
- package/dist/lib/generators/codex.js +13 -3
- package/dist/lib/generators/codex.js.map +1 -1
- package/dist/lib/generators/kiro.d.ts +2 -0
- package/dist/lib/generators/kiro.d.ts.map +1 -1
- package/dist/lib/generators/kiro.js +32 -0
- package/dist/lib/generators/kiro.js.map +1 -1
- package/dist/lib/generators/opencode.d.ts +9 -0
- package/dist/lib/generators/opencode.d.ts.map +1 -1
- package/dist/lib/generators/opencode.js +64 -1
- package/dist/lib/generators/opencode.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -1,62 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: session-start
|
|
3
|
-
description: Inicializa una sesión de trabajo
|
|
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
|
|
6
|
+
Inicializa la sesión de trabajo siguiendo el skill central `core/skills/session-start/SKILL.md`.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
8
|
+
- hook: pre-edit-check.js
|
|
7
9
|
event: PreToolUse
|
|
8
10
|
matcher: "Edit|Write"
|
|
9
|
-
description:
|
|
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.
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"type": ["array", "null"],
|
|
116
116
|
"items": {
|
|
117
117
|
"type": "string",
|
|
118
|
-
"enum": ["vitest", "jest", "pytest", "rspec", "phpunit", "playwright", "cypress"]
|
|
118
|
+
"enum": ["vitest", "jest", "node-test", "pytest", "rspec", "phpunit", "playwright", "cypress"]
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
}
|
|
@@ -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
|
+
```
|
package/assets/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Agentic development framework for Claude Code",
|
|
3
|
+
"version": "2.10.1",
|
|
4
|
+
"description": "Agentic development framework for Claude Code, OpenCode, Codex and Kiro",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": "https://github.com/cristiancorreau/forge",
|
|
7
7
|
"layers": {
|
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 (
|
|
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;
|
|
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,
|
|
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"}
|
package/dist/commands/audit.js
CHANGED
|
@@ -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');
|