@ronaldjdevfs/forge 1.1.0 → 1.3.0-beta
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 +38 -23
- package/package.json +11 -3
- package/skills/forge/SKILL.md +11 -6
- 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/pin.mjs +10 -3
- package/skills/forge/scripts/posttool.mjs +17 -211
- 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 +99 -0
- 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 +97 -0
- package/skills/forge/templates/agents/cursor/hooks.json +11 -0
- package/skills/forge/templates/agents/gemini/SKILL.md +13 -0
- package/skills/forge/tests/core.test.mjs +4 -4
- package/src/agents.mjs +88 -0
- package/src/cli.js +187 -54
- package/src/wizard.mjs +526 -0
- package/logo.png +0 -0
package/src/cli.js
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { copyFileSync, mkdirSync, existsSync,
|
|
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";
|
|
7
|
+
import { spinner, log } from "@clack/prompts";
|
|
8
|
+
import { runWizard } from "./wizard.mjs";
|
|
7
9
|
|
|
8
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
+
const SKILL_SRC = join(__dirname, "..", "skills", "forge");
|
|
12
|
+
const AGENTS_TEMPLATES = join(SKILL_SRC, "templates", "agents");
|
|
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
|
+
};
|
|
11
21
|
|
|
12
22
|
function getTargetDir(global) {
|
|
13
23
|
if (global) {
|
|
@@ -30,16 +40,7 @@ function getConfigDir(global) {
|
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
function copyRecursive(src, dest) {
|
|
33
|
-
|
|
34
|
-
for (const entry of readdirSync(src)) {
|
|
35
|
-
const srcPath = join(src, entry);
|
|
36
|
-
const destPath = join(dest, entry);
|
|
37
|
-
if (statSync(srcPath).isDirectory()) {
|
|
38
|
-
copyRecursive(srcPath, destPath);
|
|
39
|
-
} else {
|
|
40
|
-
copyFileSync(srcPath, destPath);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
+
cpSync(src, dest, { recursive: true, force: true });
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
function detectPM(configDir) {
|
|
@@ -62,7 +63,6 @@ function ensureDependencies(configDir) {
|
|
|
62
63
|
if (!pkg.dependencies["@opencode-ai/plugin"]) {
|
|
63
64
|
pkg.dependencies["@opencode-ai/plugin"] = "1.17.9";
|
|
64
65
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
65
|
-
console.log(` ✓ Actualizado ${relative(process.cwd(), pkgPath)}`);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const pm = detectPM(configDir);
|
|
@@ -74,30 +74,28 @@ function ensureDependencies(configDir) {
|
|
|
74
74
|
|
|
75
75
|
try {
|
|
76
76
|
runPM(pm);
|
|
77
|
-
console.log(` ✓ Dependencias instaladas (${pm})`);
|
|
78
77
|
} catch {
|
|
79
78
|
for (const fallback of order[pm]) {
|
|
80
79
|
try {
|
|
81
80
|
runPM(fallback);
|
|
82
|
-
console.log(` ✓ Dependencias instaladas (${fallback})`);
|
|
83
81
|
return;
|
|
84
|
-
} catch {}
|
|
82
|
+
} catch { }
|
|
85
83
|
}
|
|
86
|
-
|
|
84
|
+
log.warn("No se pudieron instalar dependencias. Ejecutá 'pnpm install' | 'npm install' manualmente en " + configDir);
|
|
87
85
|
}
|
|
88
86
|
}
|
|
89
87
|
|
|
90
88
|
const COMMANDS = [
|
|
91
|
-
{ name: "forge-forge",
|
|
92
|
-
{ name: "forge-cast",
|
|
89
|
+
{ name: "forge-forge", desc: "Forge — inicializar proyecto arquitectónicamente" },
|
|
90
|
+
{ name: "forge-cast", desc: "Cast — crear un nuevo feature hexagonal desde cero" },
|
|
93
91
|
{ name: "forge-inspect", desc: "Inspect — inspeccionar la conformidad arquitectónica" },
|
|
94
|
-
{ name: "forge-relocate",desc: "Relocate — migrar feature legacy a la nueva estructura" },
|
|
92
|
+
{ name: "forge-relocate", desc: "Relocate — migrar feature legacy a la nueva estructura" },
|
|
95
93
|
{ name: "forge-reforge", desc: "Reforge — refactorizar la arquitectura de un feature" },
|
|
96
|
-
{ name: "forge-quench",
|
|
97
|
-
{ name: "forge-temper",
|
|
98
|
-
{ name: "forge-chain",
|
|
99
|
-
{ name: "forge-inscribe",desc: "Inscribe — generar y mantener ARCHITECTURE.md" },
|
|
100
|
-
{ name: "forge-smelt",
|
|
94
|
+
{ name: "forge-quench", desc: "Quench — verificar reglas arquitectónicas del proyecto" },
|
|
95
|
+
{ name: "forge-temper", desc: "Temper — endurecer la arquitectura (DI, seguridad)" },
|
|
96
|
+
{ name: "forge-chain", desc: "Chain — analizar cadena de dependencias entre features" },
|
|
97
|
+
{ name: "forge-inscribe", desc: "Inscribe — generar y mantener ARCHITECTURE.md" },
|
|
98
|
+
{ name: "forge-smelt", desc: "Smelt — extraer código reutilizable a shared/" },
|
|
101
99
|
];
|
|
102
100
|
|
|
103
101
|
function generateCommands(configDir) {
|
|
@@ -116,46 +114,159 @@ function generateCommands(configDir) {
|
|
|
116
114
|
].join("\n");
|
|
117
115
|
writeFileSync(join(cmdsDir, `${cmd.name}.md`), content);
|
|
118
116
|
}
|
|
119
|
-
console.log(` ✓ Comandos /forge-* generados en .opencode/commands/`);
|
|
120
117
|
}
|
|
121
118
|
|
|
122
|
-
|
|
123
|
-
console.log(`
|
|
124
|
-
Forge — Architecture OS
|
|
125
|
-
|
|
126
|
-
USO
|
|
127
|
-
forge install Instalar skill en el proyecto actual
|
|
128
|
-
forge install --global Instalar globalmente (~/.config/opencode/)
|
|
129
|
-
forge skills install (alias del comando install)
|
|
119
|
+
// --- Direct installers ---
|
|
130
120
|
|
|
131
|
-
|
|
132
|
-
-g, --global Instalar en ~/.config/opencode/skills/forge/
|
|
133
|
-
-h, --help Mostrar esta ayuda
|
|
134
|
-
`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function install(global) {
|
|
121
|
+
async function installOpenCode(isGlobal = false) {
|
|
138
122
|
if (!existsSync(SKILL_SRC)) {
|
|
139
|
-
|
|
123
|
+
log.error("No se encuentra skills/forge/ en el paquete. ¿Corrupto?");
|
|
140
124
|
process.exit(1);
|
|
141
125
|
}
|
|
142
126
|
|
|
143
|
-
const target = getTargetDir(
|
|
144
|
-
const configDir = getConfigDir(
|
|
127
|
+
const target = getTargetDir(isGlobal);
|
|
128
|
+
const configDir = getConfigDir(isGlobal);
|
|
129
|
+
const rel = relative(process.cwd(), target);
|
|
145
130
|
|
|
146
|
-
|
|
147
|
-
console.log(` ${"=".repeat(40)}`);
|
|
148
|
-
console.log(` Destino: ${target}\n`);
|
|
131
|
+
const s = spinner();
|
|
149
132
|
|
|
133
|
+
s.start("Copiando skill a " + rel);
|
|
150
134
|
copyRecursive(SKILL_SRC, target);
|
|
151
|
-
|
|
135
|
+
s.stop("Skill copiada a " + rel);
|
|
136
|
+
|
|
137
|
+
s.start("Generando comandos /forge-*");
|
|
152
138
|
generateCommands(configDir);
|
|
139
|
+
s.stop("Comandos generados en " + relative(process.cwd(), join(configDir, "commands")));
|
|
140
|
+
|
|
141
|
+
s.start("Instalando dependencias");
|
|
153
142
|
ensureDependencies(configDir);
|
|
154
|
-
|
|
155
|
-
|
|
143
|
+
s.stop("Dependencias instaladas");
|
|
144
|
+
|
|
145
|
+
log.success("OpenCode configurado correctamente");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function installAgentTemplates(agentDir, agentName) {
|
|
149
|
+
const config = AGENT_CONFIGS[agentName];
|
|
150
|
+
if (!config) return;
|
|
151
|
+
|
|
152
|
+
const templateDir = join(AGENTS_TEMPLATES, config.template);
|
|
153
|
+
if (!existsSync(templateDir)) {
|
|
154
|
+
log.error(`Template para ${agentName} no encontrado en ${templateDir}`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const s = spinner();
|
|
159
|
+
s.start(`Copiando templates para ${agentName} en ${agentDir}/`);
|
|
160
|
+
|
|
161
|
+
mkdirSync(agentDir, { recursive: true });
|
|
162
|
+
|
|
163
|
+
// Copy skill into <agentDir>/skills/forge/
|
|
164
|
+
const skillDest = join(agentDir, "skills", "forge");
|
|
165
|
+
cpSync(SKILL_SRC, skillDest, { recursive: true, force: true });
|
|
166
|
+
|
|
167
|
+
// Copy template files (hooks, CLAUDE.md, .cursorrules, etc.)
|
|
168
|
+
for (const entry of readdirSync(templateDir)) {
|
|
169
|
+
const srcFile = join(templateDir, entry);
|
|
170
|
+
const destFile = join(agentDir, entry);
|
|
171
|
+
cpSync(srcFile, destFile, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Render SKILL.md with agent-specific path
|
|
175
|
+
const templatePath = join(AGENTS_TEMPLATES, "SKILL.md.template");
|
|
176
|
+
if (existsSync(templatePath)) {
|
|
177
|
+
const template = readFileSync(templatePath, "utf-8");
|
|
178
|
+
const rendered = template.replace(/\{\{AGENT_PATH\}\}/g, config.skillPath);
|
|
179
|
+
writeFileSync(join(skillDest, "SKILL.md"), rendered, "utf-8");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
s.stop(`${agentName} configurado en ${agentDir}/`);
|
|
183
|
+
log.success(`${agentName} listo (skill + ${config.postInstall || "templates"})`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function installCursor() {
|
|
187
|
+
await installAgentTemplates(join(process.cwd(), ".cursor"), "cursor");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function installClaude() {
|
|
191
|
+
await installAgentTemplates(join(process.cwd(), ".claude"), "claude");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function installCodex() {
|
|
195
|
+
// Codex hooks point to .agents/skills/forge/ via git root
|
|
196
|
+
await installAgentTemplates(join(process.cwd(), ".agents"), "codex");
|
|
197
|
+
// Also copy hooks.json into .codex/ so Codex CLI picks it up
|
|
198
|
+
const codexDir = join(process.cwd(), ".codex");
|
|
199
|
+
mkdirSync(codexDir, { recursive: true });
|
|
200
|
+
const hooksSrc = join(AGENTS_TEMPLATES, "codex", "hooks.json");
|
|
201
|
+
if (existsSync(hooksSrc)) {
|
|
202
|
+
cpSync(hooksSrc, join(codexDir, "hooks.json"), { force: true });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function installGemini() {
|
|
207
|
+
await installAgentTemplates(join(process.cwd(), ".gemini"), "gemini");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function installAgentsGeneric() {
|
|
211
|
+
await installAgentTemplates(join(process.cwd(), ".agents"), "agents");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function installAgents(selected, isGlobal = false) {
|
|
215
|
+
for (const agent of selected) {
|
|
216
|
+
switch (agent) {
|
|
217
|
+
case "opencode":
|
|
218
|
+
await installOpenCode(isGlobal);
|
|
219
|
+
break;
|
|
220
|
+
case "cursor":
|
|
221
|
+
await installCursor();
|
|
222
|
+
break;
|
|
223
|
+
case "claude":
|
|
224
|
+
await installClaude();
|
|
225
|
+
break;
|
|
226
|
+
case "codex":
|
|
227
|
+
await installCodex();
|
|
228
|
+
break;
|
|
229
|
+
case "gemini":
|
|
230
|
+
await installGemini();
|
|
231
|
+
break;
|
|
232
|
+
case "agents":
|
|
233
|
+
await installAgentsGeneric();
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// --- CLI ---
|
|
240
|
+
|
|
241
|
+
function printHelp() {
|
|
242
|
+
console.log(`
|
|
243
|
+
⚔️ Forge — Architecture OS
|
|
244
|
+
|
|
245
|
+
USO
|
|
246
|
+
forge install Mostrar wizard interactivo de instalación
|
|
247
|
+
forge install --opencode Instalar solo para OpenCode
|
|
248
|
+
forge install --cursor Instalar solo para Cursor
|
|
249
|
+
forge install --claude Instalar solo para Claude Code
|
|
250
|
+
forge install --codex Instalar solo para Codex CLI
|
|
251
|
+
forge install --gemini Instalar solo para Gemini Code Assist
|
|
252
|
+
forge install --all Instalar para todos los agentes detectados
|
|
253
|
+
forge install --global Instalar OpenCode globalmente (~/.config/opencode/)
|
|
254
|
+
forge install --help Mostrar esta ayuda
|
|
255
|
+
|
|
256
|
+
AGENTES SOPORTADOS
|
|
257
|
+
OpenCode, Claude Code, Cursor, Codex CLI, Gemini Code Assist, Agentes Genéricos
|
|
258
|
+
|
|
259
|
+
HOOKS
|
|
260
|
+
forgeSentinel (PostToolUse) — guardia arquitectónico post-escritura
|
|
261
|
+
forgeSmith (preToolUse) — guardia arquitectónico pre-escritura (Cursor)
|
|
262
|
+
|
|
263
|
+
OPCIONES
|
|
264
|
+
-g, --global Instalar OpenCode en ~/.config/opencode/skills/forge/
|
|
265
|
+
-h, --help Mostrar esta ayuda
|
|
266
|
+
`);
|
|
156
267
|
}
|
|
157
268
|
|
|
158
|
-
function main() {
|
|
269
|
+
async function main() {
|
|
159
270
|
const args = process.argv.slice(2);
|
|
160
271
|
const isHelp = args.includes("-h") || args.includes("--help");
|
|
161
272
|
const isGlobal = args.includes("-g") || args.includes("--global");
|
|
@@ -165,15 +276,37 @@ function main() {
|
|
|
165
276
|
return;
|
|
166
277
|
}
|
|
167
278
|
|
|
279
|
+
const hasOpenCode = args.includes("--opencode");
|
|
280
|
+
const hasCursor = args.includes("--cursor");
|
|
281
|
+
const hasClaude = args.includes("--claude");
|
|
282
|
+
const hasCodex = args.includes("--codex");
|
|
283
|
+
const hasGemini = args.includes("--gemini");
|
|
284
|
+
const hasAll = args.includes("--all");
|
|
285
|
+
|
|
168
286
|
const subcommandIndex = args.indexOf("skills");
|
|
169
287
|
const command = subcommandIndex !== -1 ? args[subcommandIndex + 1] : args[0];
|
|
170
288
|
|
|
171
289
|
if (command === "install") {
|
|
172
|
-
|
|
290
|
+
if (hasAll) {
|
|
291
|
+
await installAgents(["opencode", "cursor", "claude", "codex", "gemini", "agents"], isGlobal);
|
|
292
|
+
} else if (hasOpenCode || hasCursor || hasClaude || hasCodex || hasGemini) {
|
|
293
|
+
const selected = [];
|
|
294
|
+
if (hasOpenCode) selected.push("opencode");
|
|
295
|
+
if (hasCursor) selected.push("cursor");
|
|
296
|
+
if (hasClaude) selected.push("claude");
|
|
297
|
+
if (hasCodex) selected.push("codex");
|
|
298
|
+
if (hasGemini) selected.push("gemini");
|
|
299
|
+
await installAgents(selected, isGlobal);
|
|
300
|
+
} else {
|
|
301
|
+
await runWizard();
|
|
302
|
+
}
|
|
173
303
|
} else {
|
|
174
304
|
printHelp();
|
|
175
305
|
process.exit(1);
|
|
176
306
|
}
|
|
177
307
|
}
|
|
178
308
|
|
|
179
|
-
main()
|
|
309
|
+
main().catch((err) => {
|
|
310
|
+
console.error("[ERROR]", err.message);
|
|
311
|
+
process.exit(1);
|
|
312
|
+
});
|