@ronaldjdevfs/forge 1.2.0 → 1.3.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.
Files changed (74) hide show
  1. package/README.md +36 -21
  2. package/package.json +7 -2
  3. package/skills/forge/SKILL.md +56 -122
  4. package/skills/forge/command/forge.md +59 -14
  5. package/skills/forge/reference/adr.md +242 -0
  6. package/skills/forge/reference/anti-corruption-layer.md +340 -0
  7. package/skills/forge/reference/api-design.md +7 -0
  8. package/skills/forge/reference/api-versioning.md +354 -0
  9. package/skills/forge/reference/architectural-depth-checklist.md +311 -0
  10. package/skills/forge/reference/architecture-template.md +41 -0
  11. package/skills/forge/reference/assay.md +6 -0
  12. package/skills/forge/reference/bounded-contexts.md +311 -0
  13. package/skills/forge/reference/chain.md +6 -0
  14. package/skills/forge/reference/cohesion-checklist.md +256 -0
  15. package/skills/forge/reference/cqrs.md +286 -0
  16. package/skills/forge/reference/data-patterns.md +6 -0
  17. package/skills/forge/reference/di-strategies.md +6 -0
  18. package/skills/forge/reference/errors.md +5 -0
  19. package/skills/forge/reference/events.md +8 -0
  20. package/skills/forge/reference/evolutionary-architecture.md +300 -0
  21. package/skills/forge/reference/forge.md +7 -0
  22. package/skills/forge/reference/hooks.md +6 -0
  23. package/skills/forge/reference/idempotency.md +283 -0
  24. package/skills/forge/reference/inscribe.md +5 -0
  25. package/skills/forge/reference/inspect.md +6 -0
  26. package/skills/forge/reference/modular-monolith.md +252 -0
  27. package/skills/forge/reference/observability.md +5 -0
  28. package/skills/forge/reference/quench.md +5 -0
  29. package/skills/forge/reference/relocate.md +6 -0
  30. package/skills/forge/reference/sagas.md +359 -0
  31. package/skills/forge/reference/security-patterns.md +6 -0
  32. package/skills/forge/reference/smelt.md +6 -0
  33. package/skills/forge/reference/temper.md +6 -0
  34. package/skills/forge/reference/testing-patterns.md +6 -0
  35. package/skills/forge/reference/transactional-outbox.md +311 -0
  36. package/skills/forge/scripts/architecture.mjs +10 -5
  37. package/skills/forge/scripts/assay.mjs +2 -2
  38. package/skills/forge/scripts/chain.mjs +31 -5
  39. package/skills/forge/scripts/context.mjs +24 -4
  40. package/skills/forge/scripts/detect.mjs +39 -30
  41. package/skills/forge/scripts/forge-boot.mjs +108 -0
  42. package/skills/forge/scripts/forge-config.mjs +182 -3
  43. package/skills/forge/scripts/forge-state.mjs +1 -1
  44. package/skills/forge/scripts/forgeSentinel-lib.mjs +86 -0
  45. package/skills/forge/scripts/forgeSentinel.mjs +184 -0
  46. package/skills/forge/scripts/forgeSmith-admin.mjs +104 -0
  47. package/skills/forge/scripts/forgeSmith.mjs +164 -0
  48. package/skills/forge/scripts/graph.mjs +65 -9
  49. package/skills/forge/scripts/hook.mjs +2 -2
  50. package/skills/forge/scripts/inspect.mjs +56 -48
  51. package/skills/forge/scripts/parse-imports.mjs +0 -2
  52. package/skills/forge/scripts/pin.mjs +10 -3
  53. package/skills/forge/scripts/posttool.mjs +2 -2
  54. package/skills/forge/scripts/recommendation-engine.mjs +125 -0
  55. package/skills/forge/scripts/rollback.mjs +5 -3
  56. package/skills/forge/templates/agents/SKILL.md.template +283 -0
  57. package/skills/forge/templates/agents/agents/hooks.json +18 -0
  58. package/skills/forge/templates/agents/claude/CLAUDE.md +17 -16
  59. package/skills/forge/templates/agents/claude/settings.local.json +18 -0
  60. package/skills/forge/templates/agents/codex/hooks.json +18 -0
  61. package/skills/forge/templates/agents/cursor/.cursorrules +22 -5
  62. package/skills/forge/templates/agents/cursor/hooks.json +11 -0
  63. package/skills/forge/templates/agents/gemini/SKILL.md +13 -0
  64. package/skills/forge/templates/feature/acl-gateway.ts.md +52 -0
  65. package/skills/forge/templates/feature/acl-repository.ts.md +36 -0
  66. package/skills/forge/templates/feature/acl-translator.ts.md +24 -0
  67. package/skills/forge/templates/feature/cqrs-query.ts.md +40 -0
  68. package/skills/forge/templates/feature/outbox-repository.ts.md +36 -0
  69. package/skills/forge/templates/feature/saga-orchestrator.ts.md +72 -0
  70. package/skills/forge/templates/platform/outbox-relayer.ts.md +80 -0
  71. package/skills/forge/tests/core.test.mjs +284 -0
  72. package/src/agents.mjs +35 -2
  73. package/src/cli.js +112 -39
  74. package/src/wizard.mjs +142 -90
package/src/agents.mjs CHANGED
@@ -35,6 +35,34 @@ function detect(cwd) {
35
35
  detected: existsSync(join(cwd, ".opencode")),
36
36
  });
37
37
 
38
+ agents.push({
39
+ id: "cursor-project",
40
+ label: "Cursor",
41
+ scope: "Proyecto",
42
+ detected: existsSync(join(cwd, ".cursor")) || existsSync(join(cwd, ".cursorrules")),
43
+ });
44
+
45
+ agents.push({
46
+ id: "codex-project",
47
+ label: "Codex CLI",
48
+ scope: "Proyecto",
49
+ detected: existsSync(join(cwd, ".codex")),
50
+ });
51
+
52
+ agents.push({
53
+ id: "gemini-project",
54
+ label: "Gemini Code Assist",
55
+ scope: "Proyecto",
56
+ detected: existsSync(join(cwd, ".gemini")),
57
+ });
58
+
59
+ agents.push({
60
+ id: "agents-project",
61
+ label: "Agentes Genéricos",
62
+ scope: "Proyecto",
63
+ detected: existsSync(join(cwd, ".agents")),
64
+ });
65
+
38
66
  agents.push({
39
67
  id: "copilot-project",
40
68
  label: "GitHub Copilot",
@@ -43,7 +71,7 @@ function detect(cwd) {
43
71
  });
44
72
 
45
73
  agents.push({
46
- id: "codex",
74
+ id: "codex-global",
47
75
  label: "Codex CLI",
48
76
  scope: "Global",
49
77
  detected: existsSync(join(HOME, ".codex")),
@@ -52,4 +80,9 @@ function detect(cwd) {
52
80
  return agents;
53
81
  }
54
82
 
55
- export { detect as detectForWizard };
83
+ function detectAll(cwd) {
84
+ const all = detect(cwd);
85
+ return all.filter(a => a.detected).map(a => a.id);
86
+ }
87
+
88
+ export { detect as detectForWizard, detectAll };
package/src/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { copyFileSync, mkdirSync, existsSync, writeFileSync, readFileSync, cpSync } from "fs";
3
+ import { copyFileSync, mkdirSync, existsSync, writeFileSync, readFileSync, cpSync, readdirSync } from "fs";
4
4
  import { join, dirname, relative } from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { execSync } from "child_process";
@@ -11,6 +11,14 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
11
11
  const SKILL_SRC = join(__dirname, "..", "skills", "forge");
12
12
  const AGENTS_TEMPLATES = join(SKILL_SRC, "templates", "agents");
13
13
 
14
+ const AGENT_CONFIGS = {
15
+ claude: { dir: ".claude", template: "claude", skillPath: ".claude/skills/forge", need: ["CLAUDE.md", "settings.local.json"], postInstall: "forgeSentinel" },
16
+ cursor: { dir: ".cursor", template: "cursor", skillPath: ".cursor/skills/forge", need: [".cursorrules", "hooks.json"], postInstall: "forgeSmith" },
17
+ codex: { dir: ".agents", template: "codex", skillPath: ".agents/skills/forge", need: ["hooks.json"], postInstall: "forgeSentinel" },
18
+ gemini: { dir: ".gemini", template: "gemini", skillPath: ".gemini/skills/forge", need: ["SKILL.md"], postInstall: null },
19
+ agents: { dir: ".agents", template: "agents", skillPath: ".agents/skills/forge", need: ["hooks.json"], postInstall: "forgeSentinel" },
20
+ };
21
+
14
22
  function getTargetDir(global) {
15
23
  if (global) {
16
24
  const home = process.env.HOME || process.env.USERPROFILE;
@@ -78,16 +86,20 @@ function ensureDependencies(configDir) {
78
86
  }
79
87
 
80
88
  const COMMANDS = [
81
- { name: "forge-forge", desc: "Forge — inicializar proyecto arquitectónicamente" },
82
- { name: "forge-cast", desc: "Cast — crear un nuevo feature hexagonal desde cero" },
83
- { name: "forge-inspect", desc: "Inspect — inspeccionar la conformidad arquitectónica" },
84
- { name: "forge-relocate", desc: "Relocatemigrar feature legacy a la nueva estructura" },
85
- { name: "forge-reforge", desc: "Reforgerefactorizar la arquitectura de un feature" },
86
- { name: "forge-quench", desc: "Quenchverificar reglas arquitectónicas del proyecto" },
87
- { name: "forge-temper", desc: "Temperendurecer la arquitectura (DI, seguridad)" },
88
- { name: "forge-chain", desc: "Chainanalizar cadena de dependencias entre features" },
89
- { name: "forge-inscribe", desc: "Inscribegenerar y mantener ARCHITECTURE.md" },
90
- { name: "forge-smelt", desc: "Smeltextraer código reutilizable a shared/" },
89
+ { name: "forge-forge", desc: "Forge — inicializar proyecto arquitectónicamente", flags: "" },
90
+ { name: "forge-cast", desc: "Cast — crear un nuevo feature hexagonal desde cero", flags: "" },
91
+ { name: "forge-inspect", desc: "Inspect — inspeccionar la conformidad arquitectónica", flags: "--json | --diff | --full | --summary | --severity=<nivel> | --force" },
92
+ { name: "forge-assay", desc: "Assayensayo arquitectónico multi-persona", flags: "--persona=<id> | --json | --save | history" },
93
+ { name: "forge-graph", desc: "Graphconstruir el grafo arquitectónico del proyecto", flags: "--json" },
94
+ { name: "forge-armorer", desc: "Armorerreporte de ownership, huérfanos y duplicados", flags: "" },
95
+ { name: "forge-bootstrap", desc: "Bootstrapinicializar platform, shared e infra layers", flags: "" },
96
+ { name: "forge-relocate", desc: "Relocatemigrar feature legacy a la nueva estructura", flags: "" },
97
+ { name: "forge-reforge", desc: "Reforgerefactorizar la arquitectura de un feature", flags: "--cycles" },
98
+ { name: "forge-quench", desc: "Quenchverificar reglas arquitectónicas del proyecto", flags: "--fix | --show-ignores | --severity=<nivel> | --json" },
99
+ { name: "forge-temper", desc: "Temper — endurecer la arquitectura (DI, seguridad)", flags: "" },
100
+ { name: "forge-chain", desc: "Chain — analizar cadena de dependencias entre features", flags: "--json" },
101
+ { name: "forge-inscribe", desc: "Inscribe — generar y mantener ARCHITECTURE.md", flags: "--output=<path>" },
102
+ { name: "forge-smelt", desc: "Smelt — extraer código reutilizable a shared/", flags: "" },
91
103
  ];
92
104
 
93
105
  function generateCommands(configDir) {
@@ -95,7 +107,8 @@ function generateCommands(configDir) {
95
107
  mkdirSync(cmdsDir, { recursive: true });
96
108
  for (const cmd of COMMANDS) {
97
109
  const sub = cmd.name.replace("forge-", "");
98
- const content = [
110
+ const hasFlags = cmd.flags && cmd.flags.length > 0;
111
+ const lines = [
99
112
  "---",
100
113
  `description: ${cmd.desc}`,
101
114
  "agent: build",
@@ -103,8 +116,16 @@ function generateCommands(configDir) {
103
116
  "",
104
117
  `Ejecuta el subcomando ${sub} de Forge con los argumentos: $ARGUMENTS`,
105
118
  "",
106
- ].join("\n");
107
- writeFileSync(join(cmdsDir, `${cmd.name}.md`), content);
119
+ ];
120
+ if (hasFlags) {
121
+ lines.push(
122
+ `Flags disponibles: ${cmd.flags}`,
123
+ "",
124
+ `Si $ARGUMENTS está vacío, pregunta al usuario qué flags quiere usar con la tool question (checkboxes múltiples). Si no selecciona ninguna, ejecuta sin flags.`,
125
+ "",
126
+ );
127
+ }
128
+ writeFileSync(join(cmdsDir, `${cmd.name}.md`), lines.join("\n"));
108
129
  }
109
130
  }
110
131
 
@@ -137,40 +158,70 @@ async function installOpenCode(isGlobal = false) {
137
158
  log.success("OpenCode configurado correctamente");
138
159
  }
139
160
 
140
- async function installCursor() {
141
- const src = join(AGENTS_TEMPLATES, "cursor", ".cursorrules");
142
- const dest = join(process.cwd(), ".cursorrules");
161
+ async function installAgentTemplates(agentDir, agentName) {
162
+ const config = AGENT_CONFIGS[agentName];
163
+ if (!config) return;
143
164
 
144
- if (!existsSync(src)) {
145
- log.error("Template .cursorrules no encontrado");
146
- process.exit(1);
165
+ const templateDir = join(AGENTS_TEMPLATES, config.template);
166
+ if (!existsSync(templateDir)) {
167
+ log.error(`Template para ${agentName} no encontrado en ${templateDir}`);
168
+ return;
147
169
  }
148
170
 
149
171
  const s = spinner();
150
- s.start("Creando .cursorrules");
151
- copyFileSync(src, dest);
152
- s.stop(".cursorrules creado");
172
+ s.start(`Copiando templates para ${agentName} en ${agentDir}/`);
173
+
174
+ mkdirSync(agentDir, { recursive: true });
175
+
176
+ // Copy skill into <agentDir>/skills/forge/
177
+ const skillDest = join(agentDir, "skills", "forge");
178
+ cpSync(SKILL_SRC, skillDest, { recursive: true, force: true });
179
+
180
+ // Copy template files (hooks, CLAUDE.md, .cursorrules, etc.)
181
+ for (const entry of readdirSync(templateDir)) {
182
+ const srcFile = join(templateDir, entry);
183
+ const destFile = join(agentDir, entry);
184
+ cpSync(srcFile, destFile, { recursive: true, force: true });
185
+ }
186
+
187
+ // Render SKILL.md with agent-specific path
188
+ const templatePath = join(AGENTS_TEMPLATES, "SKILL.md.template");
189
+ if (existsSync(templatePath)) {
190
+ const template = readFileSync(templatePath, "utf-8");
191
+ const rendered = template.replace(/\{\{AGENT_PATH\}\}/g, config.skillPath);
192
+ writeFileSync(join(skillDest, "SKILL.md"), rendered, "utf-8");
193
+ }
194
+
195
+ s.stop(`${agentName} configurado en ${agentDir}/`);
196
+ log.success(`${agentName} listo (skill + ${config.postInstall || "templates"})`);
197
+ }
153
198
 
154
- log.success("Cursor configurado correctamente");
199
+ async function installCursor() {
200
+ await installAgentTemplates(join(process.cwd(), ".cursor"), "cursor");
155
201
  }
156
202
 
157
203
  async function installClaude() {
158
- const claudeDir = join(process.cwd(), ".claude");
159
- const src = join(AGENTS_TEMPLATES, "claude", "CLAUDE.md");
160
- const dest = join(claudeDir, "CLAUDE.md");
204
+ await installAgentTemplates(join(process.cwd(), ".claude"), "claude");
205
+ }
161
206
 
162
- if (!existsSync(src)) {
163
- log.error("Template CLAUDE.md no encontrado");
164
- process.exit(1);
207
+ async function installCodex() {
208
+ // Codex hooks point to .agents/skills/forge/ via git root
209
+ await installAgentTemplates(join(process.cwd(), ".agents"), "codex");
210
+ // Also copy hooks.json into .codex/ so Codex CLI picks it up
211
+ const codexDir = join(process.cwd(), ".codex");
212
+ mkdirSync(codexDir, { recursive: true });
213
+ const hooksSrc = join(AGENTS_TEMPLATES, "codex", "hooks.json");
214
+ if (existsSync(hooksSrc)) {
215
+ cpSync(hooksSrc, join(codexDir, "hooks.json"), { force: true });
165
216
  }
217
+ }
166
218
 
167
- const s = spinner();
168
- s.start("Creando .claude/CLAUDE.md");
169
- mkdirSync(claudeDir, { recursive: true });
170
- copyFileSync(src, dest);
171
- s.stop(".claude/CLAUDE.md creado");
219
+ async function installGemini() {
220
+ await installAgentTemplates(join(process.cwd(), ".gemini"), "gemini");
221
+ }
172
222
 
173
- log.success("Claude Code configurado correctamente");
223
+ async function installAgentsGeneric() {
224
+ await installAgentTemplates(join(process.cwd(), ".agents"), "agents");
174
225
  }
175
226
 
176
227
  async function installAgents(selected, isGlobal = false) {
@@ -185,6 +236,15 @@ async function installAgents(selected, isGlobal = false) {
185
236
  case "claude":
186
237
  await installClaude();
187
238
  break;
239
+ case "codex":
240
+ await installCodex();
241
+ break;
242
+ case "gemini":
243
+ await installGemini();
244
+ break;
245
+ case "agents":
246
+ await installAgentsGeneric();
247
+ break;
188
248
  }
189
249
  }
190
250
  }
@@ -200,10 +260,19 @@ USO
200
260
  forge install --opencode Instalar solo para OpenCode
201
261
  forge install --cursor Instalar solo para Cursor
202
262
  forge install --claude Instalar solo para Claude Code
203
- forge install --all Instalar para todos los agentes
263
+ forge install --codex Instalar solo para Codex CLI
264
+ forge install --gemini Instalar solo para Gemini Code Assist
265
+ forge install --all Instalar para todos los agentes detectados
204
266
  forge install --global Instalar OpenCode globalmente (~/.config/opencode/)
205
267
  forge install --help Mostrar esta ayuda
206
268
 
269
+ AGENTES SOPORTADOS
270
+ OpenCode, Claude Code, Cursor, Codex CLI, Gemini Code Assist, Agentes Genéricos
271
+
272
+ HOOKS
273
+ forgeSentinel (PostToolUse) — guardia arquitectónico post-escritura
274
+ forgeSmith (preToolUse) — guardia arquitectónico pre-escritura (Cursor)
275
+
207
276
  OPCIONES
208
277
  -g, --global Instalar OpenCode en ~/.config/opencode/skills/forge/
209
278
  -h, --help Mostrar esta ayuda
@@ -223,6 +292,8 @@ async function main() {
223
292
  const hasOpenCode = args.includes("--opencode");
224
293
  const hasCursor = args.includes("--cursor");
225
294
  const hasClaude = args.includes("--claude");
295
+ const hasCodex = args.includes("--codex");
296
+ const hasGemini = args.includes("--gemini");
226
297
  const hasAll = args.includes("--all");
227
298
 
228
299
  const subcommandIndex = args.indexOf("skills");
@@ -230,12 +301,14 @@ async function main() {
230
301
 
231
302
  if (command === "install") {
232
303
  if (hasAll) {
233
- await installAgents(["opencode", "cursor", "claude"], isGlobal);
234
- } else if (hasOpenCode || hasCursor || hasClaude) {
304
+ await installAgents(["opencode", "cursor", "claude", "codex", "gemini", "agents"], isGlobal);
305
+ } else if (hasOpenCode || hasCursor || hasClaude || hasCodex || hasGemini) {
235
306
  const selected = [];
236
307
  if (hasOpenCode) selected.push("opencode");
237
308
  if (hasCursor) selected.push("cursor");
238
309
  if (hasClaude) selected.push("claude");
310
+ if (hasCodex) selected.push("codex");
311
+ if (hasGemini) selected.push("gemini");
239
312
  await installAgents(selected, isGlobal);
240
313
  } else {
241
314
  await runWizard();
package/src/wizard.mjs CHANGED
@@ -93,6 +93,56 @@ function copyAgentTemplate(name, dest) {
93
93
  }
94
94
  }
95
95
 
96
+ const AGENT_SKILL_PATHS = {
97
+ claude: ".claude/skills/forge",
98
+ cursor: ".cursor/skills/forge",
99
+ codex: ".agents/skills/forge",
100
+ gemini: ".gemini/skills/forge",
101
+ agents: ".agents/skills/forge",
102
+ };
103
+
104
+ function installAgentTemplates(agentDir, agentName) {
105
+ const templateDir = join(AGENTS_TEMPLATES, agentName);
106
+ if (!existsSync(templateDir)) {
107
+ return { ok: false, error: `Template ${agentName} no encontrado` };
108
+ }
109
+ mkdirSync(agentDir, { recursive: true });
110
+ // Copy skill into <agentDir>/skills/forge/
111
+ const skillDest = join(agentDir, "skills", "forge");
112
+ cpSync(SKILL_SRC, skillDest, { recursive: true, force: true });
113
+ // Copy template files (hooks, CLAUDE.md, .cursorrules, etc.)
114
+ copyAgentTemplate(agentName, agentDir);
115
+
116
+ // Render SKILL.md with agent-specific path
117
+ const templatePath = join(AGENTS_TEMPLATES, "SKILL.md.template");
118
+ if (existsSync(templatePath)) {
119
+ const template = readFileSync(templatePath, "utf-8");
120
+ const skillPath = AGENT_SKILL_PATHS[agentName] || agentName;
121
+ const rendered = template.replace(/\{\{AGENT_PATH\}\}/g, skillPath);
122
+ writeFileSync(join(skillDest, "SKILL.md"), rendered, "utf-8");
123
+ }
124
+
125
+ // Codex special case: also copy hooks.json to .codex/
126
+ if (agentName === "codex") {
127
+ const codexDir = join(process.cwd(), ".codex");
128
+ mkdirSync(codexDir, { recursive: true });
129
+ const hooksSrc = join(templateDir, "hooks.json");
130
+ if (existsSync(hooksSrc)) {
131
+ cpSync(hooksSrc, join(codexDir, "hooks.json"), { force: true });
132
+ }
133
+ }
134
+
135
+ return { ok: true, dest: agentDir };
136
+ }
137
+
138
+ const HOOK_LABELS = {
139
+ claude: "forgeSentinel (PostToolUse)",
140
+ cursor: "forgeSmith (preToolUse)",
141
+ codex: "forgeSentinel (PostToolUse)",
142
+ gemini: "—",
143
+ agents: "forgeSentinel (PostToolUse)",
144
+ };
145
+
96
146
  // --- Welcome ---
97
147
 
98
148
  async function welcomePhase() {
@@ -235,6 +285,10 @@ async function summaryPhase(agentSelection) {
235
285
  console.log(` ${pc.bold("Componentes")}`);
236
286
  console.log();
237
287
 
288
+ const hasHooks = agentSelection.selectedIds.some(id =>
289
+ ["claude-project", "claude-global", "cursor-project", "codex-project", "codex-global", "agents-project"].includes(id)
290
+ );
291
+
238
292
  const components = [
239
293
  "Forge Skill",
240
294
  "Reglas",
@@ -242,7 +296,8 @@ async function summaryPhase(agentSelection) {
242
296
  "Comandos",
243
297
  "Plantillas",
244
298
  "Agentes",
245
- ];
299
+ hasHooks ? "forgeSentinel (PostToolUse) / forgeSmith (preToolUse)" : null,
300
+ ].filter(Boolean);
246
301
  for (const comp of components) {
247
302
  console.log(` ${pc.green("\u2714")} ${comp}`);
248
303
  }
@@ -275,102 +330,97 @@ async function summaryPhase(agentSelection) {
275
330
 
276
331
  // --- Installation ---
277
332
 
333
+ const AGENT_MAP = {
334
+ "claude-global": { name: "claude", dir: () => join(HOME, ".claude"), verify: "CLAUDE.md" },
335
+ "claude-project": { name: "claude", dir: () => join(process.cwd(), ".claude"), verify: "CLAUDE.md" },
336
+ "cursor-project": { name: "cursor", dir: () => join(process.cwd(), ".cursor"), verify: ".cursorrules" },
337
+ "codex-project": { name: "codex", dir: () => join(process.cwd(), ".agents"), verify: "hooks.json" },
338
+ "codex-global": { name: "codex", dir: () => join(HOME, ".codex"), verify: "hooks.json" },
339
+ "gemini-project": { name: "gemini", dir: () => join(process.cwd(), ".gemini"), verify: "SKILL.md" },
340
+ "agents-project": { name: "agents", dir: () => join(process.cwd(), ".agents"), verify: "hooks.json" },
341
+ "opencode-global": null,
342
+ "opencode-project": null,
343
+ "copilot-project": null,
344
+ };
345
+
278
346
  function buildAgentSteps(agent, cwd) {
279
347
  const label = `${agent.label} (${agent.scope})`;
280
348
  const s = (title, fn) => ({ title: `${pc.dim("\u2502")} ${title}`, task: fn });
281
349
 
282
- switch (agent.id) {
283
- case "claude-global":
284
- case "claude-project": {
285
- const dest = agent.id === "claude-global" ? join(HOME, ".claude") : join(cwd, ".claude");
286
- const steps = [];
287
- steps.push(s("Copiando Forge en .claude/", async () => {
288
- mkdirSync(dest, { recursive: true });
289
- copyAgentTemplate("claude", dest);
290
- cpSync(SKILL_SRC, join(dest, "forge"), { recursive: true, force: true });
291
- return "Forge instalado en .claude/";
292
- }));
293
- steps.push(s("Verificando instalaci\u00f3n", async () => {
294
- const ok = existsSync(join(dest, "CLAUDE.md"));
295
- return ok ? "\u2714 Instalaci\u00f3n verificada" : "ERROR: CLAUDE.md no encontrado";
296
- }));
297
- return { label, dest, steps };
298
- }
299
-
300
- case "opencode-global": {
301
- const dest = join(HOME, ".config", "opencode", "skills", "forge");
302
- const configDir = join(HOME, ".config", "opencode");
303
- const steps = [];
304
- steps.push(s("Copiando Skill en opencode/", async () => {
305
- mkdirSync(dest, { recursive: true });
306
- cpSync(SKILL_SRC, dest, { recursive: true, force: true });
307
- return "Skill copiada";
308
- }));
309
- steps.push(s("Registrando comandos + dependencias", async () => {
310
- generateCommands(configDir);
311
- ensureDependencies(configDir);
312
- return "Comandos y dependencias listos";
313
- }));
314
- steps.push(s("Verificando instalaci\u00f3n", async () => {
315
- const ok = existsSync(join(dest, "scripts", "context.mjs"));
316
- return ok ? "\u2714 Instalaci\u00f3n verificada" : "ERROR: Skill no encontrada";
317
- }));
318
- return { label, dest, steps };
319
- }
350
+ // Handle special cases (opencode, copilot) separately
351
+ if (agent.id === "opencode-global") {
352
+ const dest = join(HOME, ".config", "opencode", "skills", "forge");
353
+ const configDir = join(HOME, ".config", "opencode");
354
+ const steps = [];
355
+ steps.push(s("Copiando Skill en opencode/", async () => {
356
+ mkdirSync(dest, { recursive: true });
357
+ cpSync(SKILL_SRC, dest, { recursive: true, force: true });
358
+ return "Skill copiada";
359
+ }));
360
+ steps.push(s("Registrando comandos + dependencias", async () => {
361
+ generateCommands(configDir);
362
+ ensureDependencies(configDir);
363
+ return "Comandos y dependencias listos";
364
+ }));
365
+ steps.push(s("Verificando instalaci\u00f3n", async () => {
366
+ const ok = existsSync(join(dest, "scripts", "context.mjs"));
367
+ return ok ? "\u2714 Instalaci\u00f3n verificada" : "ERROR: Skill no encontrada";
368
+ }));
369
+ return { label, dest, steps };
370
+ }
320
371
 
321
- case "opencode-project": {
322
- const dest = join(cwd, ".opencode", "skills", "forge");
323
- const configDir = join(cwd, ".opencode");
324
- const steps = [];
325
- steps.push(s("Copiando Skill en .opencode/", async () => {
326
- mkdirSync(dest, { recursive: true });
327
- cpSync(SKILL_SRC, dest, { recursive: true, force: true });
328
- return "Skill copiada";
329
- }));
330
- steps.push(s("Registrando comandos + dependencias", async () => {
331
- generateCommands(configDir);
332
- ensureDependencies(configDir);
333
- return "Comandos y dependencias listos";
334
- }));
335
- steps.push(s("Verificando instalaci\u00f3n", async () => {
336
- const ok = existsSync(join(dest, "scripts", "context.mjs"));
337
- return ok ? "\u2714 Instalaci\u00f3n verificada" : "ERROR: Skill no encontrada";
338
- }));
339
- return { label, dest, steps };
340
- }
372
+ if (agent.id === "opencode-project") {
373
+ const dest = join(cwd, ".opencode", "skills", "forge");
374
+ const configDir = join(cwd, ".opencode");
375
+ const steps = [];
376
+ steps.push(s("Copiando Skill en .opencode/", async () => {
377
+ mkdirSync(dest, { recursive: true });
378
+ cpSync(SKILL_SRC, dest, { recursive: true, force: true });
379
+ return "Skill copiada";
380
+ }));
381
+ steps.push(s("Registrando comandos + dependencias", async () => {
382
+ generateCommands(configDir);
383
+ ensureDependencies(configDir);
384
+ return "Comandos y dependencias listos";
385
+ }));
386
+ steps.push(s("Verificando instalaci\u00f3n", async () => {
387
+ const ok = existsSync(join(dest, "scripts", "context.mjs"));
388
+ return ok ? "\u2714 Instalaci\u00f3n verificada" : "ERROR: Skill no encontrada";
389
+ }));
390
+ return { label, dest, steps };
391
+ }
341
392
 
342
- case "copilot-project": {
343
- const ghDir = join(cwd, ".github");
344
- const dest = join(ghDir, "copilot-instructions.md");
345
- const steps = [];
346
- steps.push(s("Instalando reglas en .github/", async () => {
347
- mkdirSync(ghDir, { recursive: true });
348
- const src = join(AGENTS_TEMPLATES, "cursor", ".cursorrules");
349
- if (existsSync(src)) copyFileSync(src, dest);
350
- const ok = existsSync(dest);
351
- return ok ? "Reglas instaladas" : "ERROR";
352
- }));
353
- return { label, dest, steps };
354
- }
393
+ if (agent.id === "copilot-project") {
394
+ const ghDir = join(cwd, ".github");
395
+ const dest = join(ghDir, "copilot-instructions.md");
396
+ const steps = [];
397
+ steps.push(s("Instalando reglas en .github/", async () => {
398
+ mkdirSync(ghDir, { recursive: true });
399
+ const src = join(AGENTS_TEMPLATES, "cursor", ".cursorrules");
400
+ if (existsSync(src)) copyFileSync(src, dest);
401
+ const ok = existsSync(dest);
402
+ return ok ? "Reglas instaladas" : "ERROR";
403
+ }));
404
+ return { label, dest, steps };
405
+ }
355
406
 
356
- case "codex": {
357
- const dest = join(HOME, ".codex");
358
- const steps = [];
359
- steps.push(s("Instalando Forge en .codex/", async () => {
360
- mkdirSync(dest, { recursive: true });
361
- copyAgentTemplate("cursor", dest);
362
- cpSync(SKILL_SRC, join(dest, "forge"), { recursive: true, force: true });
363
- return "Forge instalado";
364
- }));
365
- steps.push(s("Verificando instalaci\u00f3n", async () => {
366
- return existsSync(dest) ? "\u2714 Instalaci\u00f3n verificada" : "ERROR";
367
- }));
368
- return { label, dest, steps };
369
- }
407
+ // Generic agent types via AGENT_MAP
408
+ const map = AGENT_MAP[agent.id];
409
+ if (!map) return null;
370
410
 
371
- default:
372
- return null;
373
- }
411
+ const dest = map.dir();
412
+ const agentName = map.name;
413
+ const steps = [];
414
+ steps.push(s(`Instalando Forge + hooks en ${dest}/`, async () => {
415
+ const r = installAgentTemplates(dest, agentName);
416
+ return r.ok ? `Forge + ${HOOK_LABELS[agentName] || "templates"} instalados` : `ERROR: ${r.error}`;
417
+ }));
418
+ steps.push(s("Verificando instalaci\u00f3n", async () => {
419
+ const ok = existsSync(join(dest, "skills", "forge", "scripts", "context.mjs")) &&
420
+ existsSync(join(dest, map.verify));
421
+ return ok ? "\u2714 Instalaci\u00f3n verificada" : "ERROR: archivos no encontrados";
422
+ }));
423
+ return { label, dest, steps };
374
424
  }
375
425
 
376
426
  async function installPhase(result, cwd) {
@@ -465,7 +515,9 @@ export async function runWizard() {
465
515
  console.log();
466
516
  console.log(` \u2022 Reinicia tu agente si est\u00e1 abierto.`);
467
517
  console.log(` \u2022 Ejecut\u00e1 el comando "forge" dentro del agente.`);
468
- console.log(` \u2022 Consult\u00e1 la documentaci\u00f3n para comenzar.`);
518
+ console.log(` \u2022 forgeSentinel se activa autom\u00e1ticamente tras cada escritura.`);
519
+ console.log(` \u2022 forgeSmith (Cursor) previene escrituras con violaciones CRITICAL.`);
520
+ console.log(` \u2022 Gesti\u00f3n de hooks: node .<agent>/skills/forge/scripts/forgeSmith-admin.mjs`);
469
521
  console.log();
470
522
  console.log(" " + SEP);
471
523
  console.log();