@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.
- package/README.md +36 -21
- package/package.json +7 -2
- package/skills/forge/SKILL.md +56 -122
- package/skills/forge/command/forge.md +59 -14
- package/skills/forge/reference/adr.md +242 -0
- package/skills/forge/reference/anti-corruption-layer.md +340 -0
- package/skills/forge/reference/api-design.md +7 -0
- package/skills/forge/reference/api-versioning.md +354 -0
- package/skills/forge/reference/architectural-depth-checklist.md +311 -0
- package/skills/forge/reference/architecture-template.md +41 -0
- package/skills/forge/reference/assay.md +6 -0
- package/skills/forge/reference/bounded-contexts.md +311 -0
- package/skills/forge/reference/chain.md +6 -0
- package/skills/forge/reference/cohesion-checklist.md +256 -0
- package/skills/forge/reference/cqrs.md +286 -0
- package/skills/forge/reference/data-patterns.md +6 -0
- package/skills/forge/reference/di-strategies.md +6 -0
- package/skills/forge/reference/errors.md +5 -0
- package/skills/forge/reference/events.md +8 -0
- package/skills/forge/reference/evolutionary-architecture.md +300 -0
- package/skills/forge/reference/forge.md +7 -0
- package/skills/forge/reference/hooks.md +6 -0
- package/skills/forge/reference/idempotency.md +283 -0
- package/skills/forge/reference/inscribe.md +5 -0
- package/skills/forge/reference/inspect.md +6 -0
- package/skills/forge/reference/modular-monolith.md +252 -0
- package/skills/forge/reference/observability.md +5 -0
- package/skills/forge/reference/quench.md +5 -0
- package/skills/forge/reference/relocate.md +6 -0
- package/skills/forge/reference/sagas.md +359 -0
- package/skills/forge/reference/security-patterns.md +6 -0
- package/skills/forge/reference/smelt.md +6 -0
- package/skills/forge/reference/temper.md +6 -0
- package/skills/forge/reference/testing-patterns.md +6 -0
- package/skills/forge/reference/transactional-outbox.md +311 -0
- package/skills/forge/scripts/architecture.mjs +10 -5
- package/skills/forge/scripts/assay.mjs +2 -2
- package/skills/forge/scripts/chain.mjs +31 -5
- package/skills/forge/scripts/context.mjs +24 -4
- package/skills/forge/scripts/detect.mjs +39 -30
- package/skills/forge/scripts/forge-boot.mjs +108 -0
- package/skills/forge/scripts/forge-config.mjs +182 -3
- package/skills/forge/scripts/forge-state.mjs +1 -1
- package/skills/forge/scripts/forgeSentinel-lib.mjs +86 -0
- package/skills/forge/scripts/forgeSentinel.mjs +184 -0
- package/skills/forge/scripts/forgeSmith-admin.mjs +104 -0
- package/skills/forge/scripts/forgeSmith.mjs +164 -0
- package/skills/forge/scripts/graph.mjs +65 -9
- package/skills/forge/scripts/hook.mjs +2 -2
- package/skills/forge/scripts/inspect.mjs +56 -48
- package/skills/forge/scripts/parse-imports.mjs +0 -2
- package/skills/forge/scripts/pin.mjs +10 -3
- package/skills/forge/scripts/posttool.mjs +2 -2
- package/skills/forge/scripts/recommendation-engine.mjs +125 -0
- package/skills/forge/scripts/rollback.mjs +5 -3
- package/skills/forge/templates/agents/SKILL.md.template +283 -0
- package/skills/forge/templates/agents/agents/hooks.json +18 -0
- package/skills/forge/templates/agents/claude/CLAUDE.md +17 -16
- package/skills/forge/templates/agents/claude/settings.local.json +18 -0
- package/skills/forge/templates/agents/codex/hooks.json +18 -0
- package/skills/forge/templates/agents/cursor/.cursorrules +22 -5
- package/skills/forge/templates/agents/cursor/hooks.json +11 -0
- package/skills/forge/templates/agents/gemini/SKILL.md +13 -0
- package/skills/forge/templates/feature/acl-gateway.ts.md +52 -0
- package/skills/forge/templates/feature/acl-repository.ts.md +36 -0
- package/skills/forge/templates/feature/acl-translator.ts.md +24 -0
- package/skills/forge/templates/feature/cqrs-query.ts.md +40 -0
- package/skills/forge/templates/feature/outbox-repository.ts.md +36 -0
- package/skills/forge/templates/feature/saga-orchestrator.ts.md +72 -0
- package/skills/forge/templates/platform/outbox-relayer.ts.md +80 -0
- package/skills/forge/tests/core.test.mjs +284 -0
- package/src/agents.mjs +35 -2
- package/src/cli.js +112 -39
- 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
|
-
|
|
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-
|
|
85
|
-
{ name: "forge-
|
|
86
|
-
{ name: "forge-
|
|
87
|
-
{ name: "forge-
|
|
88
|
-
{ name: "forge-
|
|
89
|
-
{ name: "forge-
|
|
90
|
-
{ name: "forge-
|
|
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: "Assay — ensayo arquitectónico multi-persona", flags: "--persona=<id> | --json | --save | history" },
|
|
93
|
+
{ name: "forge-graph", desc: "Graph — construir el grafo arquitectónico del proyecto", flags: "--json" },
|
|
94
|
+
{ name: "forge-armorer", desc: "Armorer — reporte de ownership, huérfanos y duplicados", flags: "" },
|
|
95
|
+
{ name: "forge-bootstrap", desc: "Bootstrap — inicializar platform, shared e infra layers", flags: "" },
|
|
96
|
+
{ name: "forge-relocate", desc: "Relocate — migrar feature legacy a la nueva estructura", flags: "" },
|
|
97
|
+
{ name: "forge-reforge", desc: "Reforge — refactorizar la arquitectura de un feature", flags: "--cycles" },
|
|
98
|
+
{ name: "forge-quench", desc: "Quench — verificar 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
|
|
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
|
-
]
|
|
107
|
-
|
|
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
|
|
141
|
-
const
|
|
142
|
-
|
|
161
|
+
async function installAgentTemplates(agentDir, agentName) {
|
|
162
|
+
const config = AGENT_CONFIGS[agentName];
|
|
163
|
+
if (!config) return;
|
|
143
164
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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(
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
199
|
+
async function installCursor() {
|
|
200
|
+
await installAgentTemplates(join(process.cwd(), ".cursor"), "cursor");
|
|
155
201
|
}
|
|
156
202
|
|
|
157
203
|
async function installClaude() {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const dest = join(claudeDir, "CLAUDE.md");
|
|
204
|
+
await installAgentTemplates(join(process.cwd(), ".claude"), "claude");
|
|
205
|
+
}
|
|
161
206
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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 --
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
372
|
-
|
|
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
|
|
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();
|