@cristiancorreau/forge 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/core/hooks/pre-bash-check.js +144 -0
- package/assets/core/hooks/pre-edit-check.js +166 -0
- package/dist/cli.js +1 -1
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +154 -9
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +39 -28
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +114 -5
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +283 -9
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +92 -6
- package/dist/commands/validate.js.map +1 -1
- package/dist/lib/generators/claude-code.d.ts +3 -0
- package/dist/lib/generators/claude-code.d.ts.map +1 -0
- package/dist/lib/generators/claude-code.js +140 -0
- package/dist/lib/generators/claude-code.js.map +1 -0
- package/dist/lib/generators/codex.d.ts +3 -0
- package/dist/lib/generators/codex.d.ts.map +1 -0
- package/dist/lib/generators/codex.js +69 -0
- package/dist/lib/generators/codex.js.map +1 -0
- package/dist/lib/generators/kiro.d.ts +7 -0
- package/dist/lib/generators/kiro.d.ts.map +1 -0
- package/dist/lib/generators/kiro.js +134 -0
- package/dist/lib/generators/kiro.js.map +1 -0
- package/dist/lib/generators/opencode.d.ts +3 -0
- package/dist/lib/generators/opencode.d.ts.map +1 -0
- package/dist/lib/generators/opencode.js +96 -0
- package/dist/lib/generators/opencode.js.map +1 -0
- package/dist/lib/wizard.d.ts +17 -0
- package/dist/lib/wizard.d.ts.map +1 -0
- package/dist/lib/wizard.js +162 -0
- package/dist/lib/wizard.js.map +1 -0
- package/dist/lib/yaml.d.ts +96 -0
- package/dist/lib/yaml.d.ts.map +1 -0
- package/dist/lib/yaml.js +26 -0
- package/dist/lib/yaml.js.map +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* forge — PreToolUse hook: pre-bash-check.js
|
|
4
|
+
* Blocks destructive commands in production context. Zero Python dependency.
|
|
5
|
+
*
|
|
6
|
+
* Context: on 2026-04-28, --force-reset was accidentally executed against
|
|
7
|
+
* production DB, deleting 225 users and 35 forms. This hook prevents recurrence.
|
|
8
|
+
*/
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const DEBUG = !['', '0', 'false', 'False'].includes(process.env.DEBUG || '');
|
|
15
|
+
const dbg = msg => DEBUG && process.stdout.write(`[forge-hook-debug] ${msg}\n`);
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Load project.yaml (minimal YAML parser for flat key: value)
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
function loadProjectYaml() {
|
|
21
|
+
try {
|
|
22
|
+
let dir = process.cwd();
|
|
23
|
+
for (let i = 0; i < 6; i++) {
|
|
24
|
+
const candidate = path.join(dir, 'project.yaml');
|
|
25
|
+
if (fs.existsSync(candidate)) {
|
|
26
|
+
const text = fs.readFileSync(candidate, 'utf8');
|
|
27
|
+
return parseYamlMinimal(text);
|
|
28
|
+
}
|
|
29
|
+
const parent = path.dirname(dir);
|
|
30
|
+
if (parent === dir) break;
|
|
31
|
+
dir = parent;
|
|
32
|
+
}
|
|
33
|
+
} catch (e) { dbg(`project.yaml load error: ${e}`); }
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseYamlMinimal(text) {
|
|
38
|
+
// Minimal parser: handles nested sections and key: value pairs
|
|
39
|
+
const result = {};
|
|
40
|
+
let currentSection = null;
|
|
41
|
+
let currentSubSection = null;
|
|
42
|
+
for (const rawLine of text.split('\n')) {
|
|
43
|
+
const line = rawLine.trimEnd();
|
|
44
|
+
if (!line || line.trim().startsWith('#')) continue;
|
|
45
|
+
const indent = line.length - line.trimStart().length;
|
|
46
|
+
const trimmed = line.trimStart();
|
|
47
|
+
const colonIdx = trimmed.indexOf(':');
|
|
48
|
+
if (colonIdx === -1) continue;
|
|
49
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
50
|
+
const val = trimmed.slice(colonIdx + 1).trim();
|
|
51
|
+
if (indent === 0) { currentSection = key; currentSubSection = null; if (val) result[key] = val; else result[key] = {}; }
|
|
52
|
+
else if (indent === 2 && currentSection) { currentSubSection = key; if (!result[currentSection] || typeof result[currentSection] !== 'object') result[currentSection] = {}; if (val) result[currentSection][key] = val; else result[currentSection][key] = {}; }
|
|
53
|
+
else if (indent === 4 && currentSection && currentSubSection) { if (!result[currentSection][currentSubSection] || typeof result[currentSection][currentSubSection] !== 'object') result[currentSection][currentSubSection] = {}; if (val) result[currentSection][currentSubSection][key] = val; }
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Dangerous patterns
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
const DANGEROUS = [
|
|
62
|
+
[/--force-reset/, '--force-reset'],
|
|
63
|
+
[/prisma\s+migrate\s+reset/i, 'prisma migrate reset'],
|
|
64
|
+
[/DROP\s+TABLE/i, 'DROP TABLE'],
|
|
65
|
+
[/TRUNCATE\s+/i, 'TRUNCATE'],
|
|
66
|
+
[/DELETE\s+FROM\s+\w+\s*;/i, 'DELETE FROM sin WHERE'],
|
|
67
|
+
[/DROP\s+DATABASE/i, 'DROP DATABASE'],
|
|
68
|
+
[/dropdb\s+/, 'dropdb'],
|
|
69
|
+
[/rm\s+-rf\s+\//, 'rm -rf /'],
|
|
70
|
+
[/git\s+push\s+--force(?!\s+--with-lease)/, 'git push --force sin --with-lease'],
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
function matchDangerous(command) {
|
|
74
|
+
for (const [re, label] of DANGEROUS) {
|
|
75
|
+
if (re.test(command)) return label;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function matchForbidden(command, project) {
|
|
81
|
+
try {
|
|
82
|
+
const forbidden = (project.rules || {}).forbidden_in_production;
|
|
83
|
+
if (!Array.isArray(forbidden)) return null;
|
|
84
|
+
for (const pattern of forbidden) {
|
|
85
|
+
if (new RegExp(pattern).test(command)) return pattern;
|
|
86
|
+
}
|
|
87
|
+
} catch (e) { dbg(`forbidden_in_production error: ${e}`); }
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isProductionContext(command, project) {
|
|
92
|
+
const deploy = project.deploy || {};
|
|
93
|
+
const prodUrl = deploy.production_url || '';
|
|
94
|
+
const projectId = deploy.project_id || '';
|
|
95
|
+
if (prodUrl && command.includes(prodUrl)) { dbg(`prod context: URL match`); return true; }
|
|
96
|
+
if (projectId && command.includes(projectId)) { dbg(`prod context: project_id match`); return true; }
|
|
97
|
+
for (const key of Object.keys(process.env)) {
|
|
98
|
+
if (/^(PROD_|PRODUCTION_|PROD$|PRODUCTION$)/i.test(key) && process.env[key]) { dbg(`prod context: env ${key}`); return true; }
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Main
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
let raw = '';
|
|
107
|
+
process.stdin.setEncoding('utf8');
|
|
108
|
+
process.stdin.on('data', chunk => { raw += chunk; });
|
|
109
|
+
process.stdin.on('end', () => {
|
|
110
|
+
if (!raw.trim()) { dbg('empty stdin'); process.exit(0); }
|
|
111
|
+
let data;
|
|
112
|
+
try { data = JSON.parse(raw); } catch (e) { dbg(`parse error: ${e}`); process.exit(0); }
|
|
113
|
+
|
|
114
|
+
const toolName = data.tool_name || '';
|
|
115
|
+
if (toolName !== 'Bash') process.exit(0);
|
|
116
|
+
|
|
117
|
+
const command = (data.tool_input || {}).command || '';
|
|
118
|
+
if (!command) process.exit(0);
|
|
119
|
+
dbg(`command: ${command.slice(0, 200)}`);
|
|
120
|
+
|
|
121
|
+
const project = loadProjectYaml();
|
|
122
|
+
const label = matchDangerous(command) || matchForbidden(command, project);
|
|
123
|
+
if (!label) process.exit(0);
|
|
124
|
+
|
|
125
|
+
const snippet = command.slice(0, 120) + (command.length > 120 ? '...' : '');
|
|
126
|
+
const inProd = isProductionContext(command, project);
|
|
127
|
+
|
|
128
|
+
if (inProd) {
|
|
129
|
+
process.stdout.write(
|
|
130
|
+
`forge: BLOQUEADO — comando destructivo detectado en contexto de producción.\n\n` +
|
|
131
|
+
` Comando: ${snippet}\n Patrón: ${label}\n\n` +
|
|
132
|
+
` Ejecutá esto MANUALMENTE con plena consciencia de que afecta producción.\n\n` +
|
|
133
|
+
` Lección del 2026-04-28: --force-reset borró 225 usuarios en producción.\n`
|
|
134
|
+
);
|
|
135
|
+
process.exit(2);
|
|
136
|
+
} else {
|
|
137
|
+
process.stdout.write(
|
|
138
|
+
`forge: ADVERTENCIA — comando potencialmente destructivo.\n\n` +
|
|
139
|
+
` Comando: ${snippet}\n Patrón: ${label}\n\n` +
|
|
140
|
+
` Si apunta a producción, cancelá y ejecutá manualmente.\n`
|
|
141
|
+
);
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* forge — PreToolUse hook: pre-edit-check.js
|
|
4
|
+
* Branch guard, debug detection, hardcoded secret detection. Zero Python dependency.
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const DEBUG = !['', '0', 'false', 'False'].includes(process.env.DEBUG || '');
|
|
12
|
+
const dbg = msg => DEBUG && process.stdout.write(`[forge-hook-debug] ${msg}\n`);
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// File classification
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const CODE_EXTS = new Set(['.py','.ts','.js','.tsx','.jsx','.php','.rb','.go','.rs','.java','.cs','.cpp','.c','.sh']);
|
|
18
|
+
const NON_CODE_EXTS = new Set(['.md','.yaml','.yml','.json','.toml','.txt','.lock']);
|
|
19
|
+
const ROOT_PROTECTED = new Set(['README.md','CLAUDE.md','CHANGELOG.md','AGENTS.md']);
|
|
20
|
+
const PROTECTED_DIRS = ['docs/', '.claude/'];
|
|
21
|
+
|
|
22
|
+
function isCodeFile(filePath) {
|
|
23
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
24
|
+
if (CODE_EXTS.has(ext)) return true;
|
|
25
|
+
if (NON_CODE_EXTS.has(ext)) return false;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isExemptFromBranchGuard(filePath) {
|
|
30
|
+
const norm = filePath.replace(/\\/g, '/');
|
|
31
|
+
for (const d of PROTECTED_DIRS) {
|
|
32
|
+
if (norm.startsWith(d) || norm.includes(`/${d.replace(/\/$/, '')}`)) return true;
|
|
33
|
+
}
|
|
34
|
+
const base = path.basename(norm);
|
|
35
|
+
if (ROOT_PROTECTED.has(base)) return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Branch guard
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
function getCurrentBranch() {
|
|
43
|
+
try {
|
|
44
|
+
return execSync('git branch --show-current', { encoding: 'utf8', timeout: 3000 }).trim();
|
|
45
|
+
} catch { return ''; }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Debug detection patterns per language
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
const DEBUG_PATTERNS = [
|
|
52
|
+
{ re: /\bconsole\.(log|debug|warn|error|trace)\s*\(/, lang: 'TypeScript/JavaScript' },
|
|
53
|
+
{ re: /\bprint\s*\(/, lang: 'Python' },
|
|
54
|
+
{ re: /\bdd\s*\(/, lang: 'PHP (dd)' },
|
|
55
|
+
{ re: /\bvar_dump\s*\(/, lang: 'PHP (var_dump)' },
|
|
56
|
+
{ re: /\bdump\s*\(/, lang: 'PHP (dump)' },
|
|
57
|
+
{ re: /\bdebugger\b/, lang: 'JavaScript debugger' },
|
|
58
|
+
{ re: /\bbinding\.pry\b/, lang: 'Ruby (binding.pry)' },
|
|
59
|
+
{ re: /\bbyebug\b/, lang: 'Ruby (byebug)' },
|
|
60
|
+
{ re: /\bp\s+\w+/, lang: 'Ruby (p)' },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
function detectDebugStatements(content) {
|
|
64
|
+
const found = [];
|
|
65
|
+
const lines = content.split('\n');
|
|
66
|
+
lines.forEach((line, idx) => {
|
|
67
|
+
for (const { re, lang } of DEBUG_PATTERNS) {
|
|
68
|
+
if (re.test(line)) {
|
|
69
|
+
found.push({ line: idx + 1, snippet: line.trim().slice(0, 80), lang });
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return found;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Secret detection
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
const SECRET_PATTERNS = [
|
|
81
|
+
{ re: /(?:password|passwd|pwd)\s*=\s*['"][^'"]{4,}['"]/i, label: 'password hardcodeado' },
|
|
82
|
+
{ re: /(?:api_?key|apikey)\s*=\s*['"][^'"]{8,}['"]/i, label: 'API key hardcodeada' },
|
|
83
|
+
{ re: /(?:secret|token)\s*=\s*['"][^'"]{8,}['"]/i, label: 'secret/token hardcodeado' },
|
|
84
|
+
{ re: /sk-[a-zA-Z0-9]{20,}/, label: 'OpenAI API key' },
|
|
85
|
+
{ re: /ghp_[a-zA-Z0-9]{30,}/, label: 'GitHub Personal Access Token' },
|
|
86
|
+
{ re: /eyJ[a-zA-Z0-9_-]{20,}\.[a-zA-Z0-9_-]{20,}/, label: 'JWT token' },
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
function detectSecrets(content) {
|
|
90
|
+
const found = [];
|
|
91
|
+
const lines = content.split('\n');
|
|
92
|
+
lines.forEach((line, idx) => {
|
|
93
|
+
for (const { re, label } of SECRET_PATTERNS) {
|
|
94
|
+
if (re.test(line)) {
|
|
95
|
+
found.push({ line: idx + 1, label, snippet: line.trim().slice(0, 60) + '...' });
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return found;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Main
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
let raw = '';
|
|
107
|
+
process.stdin.setEncoding('utf8');
|
|
108
|
+
process.stdin.on('data', chunk => { raw += chunk; });
|
|
109
|
+
process.stdin.on('end', () => {
|
|
110
|
+
if (!raw.trim()) { process.exit(0); }
|
|
111
|
+
let data;
|
|
112
|
+
try { data = JSON.parse(raw); } catch { process.exit(0); }
|
|
113
|
+
|
|
114
|
+
const toolName = data.tool_name || '';
|
|
115
|
+
if (!['Write', 'Edit', 'MultiEdit'].includes(toolName)) process.exit(0);
|
|
116
|
+
|
|
117
|
+
const toolInput = data.tool_input || {};
|
|
118
|
+
const filePath = toolInput.file_path || toolInput.path || '';
|
|
119
|
+
if (!filePath) process.exit(0);
|
|
120
|
+
|
|
121
|
+
dbg(`file: ${filePath}`);
|
|
122
|
+
|
|
123
|
+
const warnings = [];
|
|
124
|
+
|
|
125
|
+
// 1. Branch guard
|
|
126
|
+
if (!isExemptFromBranchGuard(filePath) && isCodeFile(filePath)) {
|
|
127
|
+
const branch = getCurrentBranch();
|
|
128
|
+
dbg(`branch: ${branch}`);
|
|
129
|
+
if (branch === 'main' || branch === 'master') {
|
|
130
|
+
process.stdout.write(
|
|
131
|
+
`forge: BLOQUEADO — editando código directamente en ${branch}.\n\n` +
|
|
132
|
+
` Archivo: ${filePath}\n\n` +
|
|
133
|
+
` Creá una rama antes de editar código:\n` +
|
|
134
|
+
` git checkout -b feat/descripcion\n\n` +
|
|
135
|
+
` Ramas de documentación (.md, .yaml, .json) están permitidas en ${branch}.\n`
|
|
136
|
+
);
|
|
137
|
+
process.exit(2);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. Debug and secret detection on new content
|
|
142
|
+
const newContent = toolInput.new_string || toolInput.content || '';
|
|
143
|
+
if (newContent && isCodeFile(filePath)) {
|
|
144
|
+
const debugHits = detectDebugStatements(newContent);
|
|
145
|
+
const secretHits = detectSecrets(newContent);
|
|
146
|
+
|
|
147
|
+
if (debugHits.length > 0) {
|
|
148
|
+
const items = debugHits.map(h => ` línea ${h.line}: ${h.snippet} (${h.lang})`).join('\n');
|
|
149
|
+
warnings.push(`Debug statements detectados:\n${items}`);
|
|
150
|
+
}
|
|
151
|
+
if (secretHits.length > 0) {
|
|
152
|
+
const items = secretHits.map(h => ` línea ${h.line}: ${h.label} — ${h.snippet}`).join('\n');
|
|
153
|
+
warnings.push(`Posibles secrets hardcodeados:\n${items}\n Usar variables de entorno en su lugar.`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (warnings.length > 0) {
|
|
158
|
+
process.stdout.write(
|
|
159
|
+
`forge: ADVERTENCIA — revisá antes de continuar:\n\n` +
|
|
160
|
+
warnings.map(w => ` • ${w}`).join('\n\n') + '\n'
|
|
161
|
+
);
|
|
162
|
+
// Don't block — just warn
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
process.exit(0);
|
|
166
|
+
});
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { audit } from './commands/audit.js';
|
|
|
4
4
|
import { generate } from './commands/generate.js';
|
|
5
5
|
import { validate } from './commands/validate.js';
|
|
6
6
|
import { doctor } from './commands/doctor.js';
|
|
7
|
-
const VERSION = '2.
|
|
7
|
+
const VERSION = '2.3.0';
|
|
8
8
|
const HELP = `forge v${VERSION} — Agentic development framework
|
|
9
9
|
|
|
10
10
|
Usage: forge <command> [options]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"AAgEA,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuH3D"}
|
package/dist/commands/audit.js
CHANGED
|
@@ -1,21 +1,166 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { findProjectYaml, loadProjectYaml } from '../lib/yaml.js';
|
|
3
4
|
const HELP = `Usage: forge audit [options]
|
|
4
5
|
|
|
5
|
-
Audit a project against the forge standard.
|
|
6
|
-
|
|
6
|
+
Audit a project against the forge standard. Checks installed agents,
|
|
7
|
+
hooks, runtime config, and project.yaml completeness.
|
|
7
8
|
|
|
8
9
|
Options:
|
|
9
|
-
--json
|
|
10
|
-
--
|
|
11
|
-
-h, --help Show this help
|
|
10
|
+
--json Output results as JSON
|
|
11
|
+
-h, --help Show this help
|
|
12
12
|
`;
|
|
13
|
+
const REQUIRED_FRONTMATTER = ['name', 'description', 'model', 'tools', 'tier'];
|
|
14
|
+
const REQUIRED_SECTIONS = ['## Reglas', '## No hagas'];
|
|
15
|
+
function parseFrontmatter(content) {
|
|
16
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
17
|
+
if (!match)
|
|
18
|
+
return {};
|
|
19
|
+
const result = {};
|
|
20
|
+
for (const line of match[1].split('\n')) {
|
|
21
|
+
const [key, ...rest] = line.split(':');
|
|
22
|
+
if (key && rest.length > 0)
|
|
23
|
+
result[key.trim()] = rest.join(':').trim();
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
function auditAgent(agentPath, agentName) {
|
|
28
|
+
const issues = [];
|
|
29
|
+
let content;
|
|
30
|
+
try {
|
|
31
|
+
content = readFileSync(agentPath, 'utf-8');
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [{ level: 'error', check: agentName, message: 'No se puede leer el archivo del agente' }];
|
|
35
|
+
}
|
|
36
|
+
const frontmatter = parseFrontmatter(content);
|
|
37
|
+
for (const field of REQUIRED_FRONTMATTER) {
|
|
38
|
+
if (!frontmatter[field]) {
|
|
39
|
+
issues.push({ level: 'warn', check: agentName, message: `Frontmatter faltante: ${field}` });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const section of REQUIRED_SECTIONS) {
|
|
43
|
+
if (!content.includes(section)) {
|
|
44
|
+
issues.push({ level: 'warn', check: agentName, message: `Sección faltante: ${section}` });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (issues.length === 0) {
|
|
48
|
+
issues.push({ level: 'ok', check: agentName, message: 'Agente válido' });
|
|
49
|
+
}
|
|
50
|
+
return issues;
|
|
51
|
+
}
|
|
13
52
|
export async function audit(args) {
|
|
14
53
|
if (args.includes('-h') || args.includes('--help')) {
|
|
15
54
|
process.stdout.write(HELP);
|
|
16
55
|
return 0;
|
|
17
56
|
}
|
|
18
|
-
const
|
|
19
|
-
|
|
57
|
+
const jsonMode = args.includes('--json');
|
|
58
|
+
const root = process.cwd();
|
|
59
|
+
const issues = [];
|
|
60
|
+
// 1. Check project.yaml
|
|
61
|
+
const yamlPath = findProjectYaml(root);
|
|
62
|
+
if (!yamlPath) {
|
|
63
|
+
issues.push({ level: 'error', check: 'project.yaml', message: 'No se encontró project.yaml — ejecutar forge init' });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
issues.push({ level: 'ok', check: 'project.yaml', message: `Encontrado: ${yamlPath}` });
|
|
67
|
+
}
|
|
68
|
+
let config = null;
|
|
69
|
+
if (yamlPath) {
|
|
70
|
+
try {
|
|
71
|
+
config = loadProjectYaml(yamlPath);
|
|
72
|
+
const mode = config.project.mode;
|
|
73
|
+
if (!mode) {
|
|
74
|
+
issues.push({ level: 'warn', check: 'project.yaml', message: 'project.mode no definido' });
|
|
75
|
+
}
|
|
76
|
+
if (!config.deploy) {
|
|
77
|
+
issues.push({ level: 'info', check: 'project.yaml', message: "Sección 'deploy' ausente" });
|
|
78
|
+
}
|
|
79
|
+
if (!config.rules) {
|
|
80
|
+
issues.push({ level: 'info', check: 'project.yaml', message: "Sección 'rules' ausente" });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
issues.push({ level: 'error', check: 'project.yaml', message: `Error al parsear: ${e instanceof Error ? e.message : String(e)}` });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// 2. Check runtime config
|
|
88
|
+
const hasClaudeDir = existsSync(join(root, '.claude'));
|
|
89
|
+
const hasAgentsMd = existsSync(join(root, 'AGENTS.md'));
|
|
90
|
+
const hasKiro = existsSync(join(root, '.kiro'));
|
|
91
|
+
if (hasClaudeDir) {
|
|
92
|
+
issues.push({ level: 'ok', check: 'runtime', message: 'Claude Code detectado (.claude/)' });
|
|
93
|
+
// Check CLAUDE.md
|
|
94
|
+
if (existsSync(join(root, 'CLAUDE.md'))) {
|
|
95
|
+
issues.push({ level: 'ok', check: 'CLAUDE.md', message: 'CLAUDE.md presente' });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
issues.push({ level: 'warn', check: 'CLAUDE.md', message: 'CLAUDE.md ausente — ejecutar forge generate' });
|
|
99
|
+
}
|
|
100
|
+
// Check agents
|
|
101
|
+
const agentsDir = join(root, '.claude', 'agents');
|
|
102
|
+
if (!existsSync(agentsDir)) {
|
|
103
|
+
issues.push({ level: 'warn', check: 'agents', message: '.claude/agents/ no existe — ejecutar forge init' });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const agentFiles = readdirSync(agentsDir).filter(f => f.endsWith('.md'));
|
|
107
|
+
if (agentFiles.length === 0) {
|
|
108
|
+
issues.push({ level: 'warn', check: 'agents', message: 'No hay agentes instalados en .claude/agents/' });
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
for (const agentFile of agentFiles) {
|
|
112
|
+
const agentIssues = auditAgent(join(agentsDir, agentFile), agentFile.replace('.md', ''));
|
|
113
|
+
issues.push(...agentIssues);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Check hooks
|
|
118
|
+
const hooksDir = join(root, '.claude', 'hooks');
|
|
119
|
+
if (!existsSync(hooksDir)) {
|
|
120
|
+
issues.push({ level: 'info', check: 'hooks', message: '.claude/hooks/ no existe — ejecutar forge init' });
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const hookFiles = readdirSync(hooksDir);
|
|
124
|
+
issues.push({ level: 'ok', check: 'hooks', message: `${hookFiles.length} hook(s) instalado(s)` });
|
|
125
|
+
}
|
|
126
|
+
// Check settings.json
|
|
127
|
+
if (existsSync(join(root, '.claude', 'settings.json'))) {
|
|
128
|
+
issues.push({ level: 'ok', check: 'settings.json', message: 'settings.json presente' });
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
issues.push({ level: 'info', check: 'settings.json', message: 'settings.json ausente — ejecutar forge init' });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (hasAgentsMd && !hasClaudeDir) {
|
|
135
|
+
issues.push({ level: 'ok', check: 'runtime', message: 'OpenCode/Codex detectado (AGENTS.md)' });
|
|
136
|
+
}
|
|
137
|
+
if (hasKiro) {
|
|
138
|
+
issues.push({ level: 'ok', check: 'runtime', message: 'Kiro detectado (.kiro/)' });
|
|
139
|
+
}
|
|
140
|
+
if (!hasClaudeDir && !hasAgentsMd && !hasKiro) {
|
|
141
|
+
issues.push({ level: 'warn', check: 'runtime', message: 'No se detectó ningún runtime — ejecutar forge init' });
|
|
142
|
+
}
|
|
143
|
+
// Summary
|
|
144
|
+
const errors = issues.filter(i => i.level === 'error').length;
|
|
145
|
+
const warnings = issues.filter(i => i.level === 'warn').length;
|
|
146
|
+
const ok = issues.filter(i => i.level === 'ok').length;
|
|
147
|
+
if (jsonMode) {
|
|
148
|
+
console.log(JSON.stringify({
|
|
149
|
+
summary: { errors, warnings, ok },
|
|
150
|
+
issues,
|
|
151
|
+
}, null, 2));
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
console.log('forge audit\n');
|
|
155
|
+
for (const issue of issues) {
|
|
156
|
+
const icons = { ok: '✓', warn: '!', error: '✗', info: 'i' };
|
|
157
|
+
const icon = icons[issue.level] ?? '·';
|
|
158
|
+
console.log(` [${icon}] ${issue.check.padEnd(20)} ${issue.message}`);
|
|
159
|
+
}
|
|
160
|
+
console.log(`\n Resumen: ${ok} OK · ${warnings} warnings · ${errors} errores`);
|
|
161
|
+
if (errors === 0 && warnings === 0)
|
|
162
|
+
console.log(' El proyecto cumple con el estándar forge.');
|
|
163
|
+
}
|
|
164
|
+
return errors > 0 ? 1 : 0;
|
|
20
165
|
}
|
|
21
166
|
//# sourceMappingURL=audit.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAKA,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA8D7D"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
2
3
|
import { resolveForgeRoot } from '../lib/paths.js';
|
|
4
|
+
import { findProjectYaml } from '../lib/yaml.js';
|
|
3
5
|
export async function doctor(_args) {
|
|
4
6
|
let ok = true;
|
|
5
7
|
console.log('forge doctor — environment check\n');
|
|
6
|
-
//
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Node.js version
|
|
9
|
+
const nodeVersion = process.versions.node;
|
|
10
|
+
const [major] = nodeVersion.split('.').map(Number);
|
|
11
|
+
if (major >= 18) {
|
|
12
|
+
console.log(` ✓ Node.js: ${nodeVersion}`);
|
|
10
13
|
}
|
|
11
14
|
else {
|
|
12
|
-
console.log(
|
|
13
|
-
console.log(' macOS: brew install python3');
|
|
14
|
-
console.log(' Ubuntu: sudo apt install python3');
|
|
15
|
-
console.log(' Win: https://python.org/downloads');
|
|
15
|
+
console.log(` ✗ Node.js ${nodeVersion} — se requiere >= 18`);
|
|
16
16
|
ok = false;
|
|
17
17
|
}
|
|
18
|
-
// pyyaml
|
|
19
|
-
if (python) {
|
|
20
|
-
if (hasPyyaml(python)) {
|
|
21
|
-
console.log(' ✓ pyyaml: installed');
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
console.log(' ✗ pyyaml not found');
|
|
25
|
-
console.log(` ${python} -m pip install pyyaml`);
|
|
26
|
-
ok = false;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
18
|
// forge root
|
|
30
19
|
try {
|
|
31
20
|
const root = resolveForgeRoot();
|
|
32
21
|
console.log(` ✓ forge root: ${root}`);
|
|
22
|
+
// Core assets
|
|
23
|
+
const coreOk = existsSync(join(root, 'core', 'agents'))
|
|
24
|
+
&& existsSync(join(root, 'core', 'schemas'))
|
|
25
|
+
&& existsSync(join(root, 'scripts'));
|
|
26
|
+
if (coreOk) {
|
|
27
|
+
console.log(' ✓ forge assets: completos (core/, scripts/, profiles/, adapters/)');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.log(' ✗ forge assets: incompletos — reinstalar con npx @cristiancorreau/forge');
|
|
31
|
+
ok = false;
|
|
32
|
+
}
|
|
33
33
|
}
|
|
34
34
|
catch (e) {
|
|
35
35
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -37,21 +37,32 @@ export async function doctor(_args) {
|
|
|
37
37
|
ok = false;
|
|
38
38
|
}
|
|
39
39
|
// project.yaml
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (existsSync(projectYaml)) {
|
|
44
|
-
console.log(' ✓ project.yaml: found');
|
|
40
|
+
const projectYaml = findProjectYaml(process.cwd());
|
|
41
|
+
if (projectYaml) {
|
|
42
|
+
console.log(` ✓ project.yaml: ${projectYaml}`);
|
|
45
43
|
}
|
|
46
44
|
else {
|
|
47
|
-
console.log(' ~ project.yaml:
|
|
45
|
+
console.log(' ~ project.yaml: no encontrado (ejecutar forge init)');
|
|
48
46
|
}
|
|
47
|
+
// .claude / AGENTS.md / .kiro
|
|
48
|
+
const cwd = process.cwd();
|
|
49
|
+
const hasClaude = existsSync(join(cwd, '.claude'));
|
|
50
|
+
const hasAgents = existsSync(join(cwd, 'AGENTS.md'));
|
|
51
|
+
const hasKiro = existsSync(join(cwd, '.kiro'));
|
|
52
|
+
if (hasClaude)
|
|
53
|
+
console.log(' ✓ runtime: Claude Code (.claude/)');
|
|
54
|
+
if (hasAgents && !hasClaude)
|
|
55
|
+
console.log(' ✓ runtime: OpenCode / Codex (AGENTS.md)');
|
|
56
|
+
if (hasKiro)
|
|
57
|
+
console.log(' ✓ runtime: Kiro (.kiro/)');
|
|
58
|
+
if (!hasClaude && !hasAgents && !hasKiro)
|
|
59
|
+
console.log(' ~ runtime: no detectado (ejecutar forge init)');
|
|
49
60
|
console.log('');
|
|
50
61
|
if (ok) {
|
|
51
|
-
console.log('All checks passed.');
|
|
62
|
+
console.log('All checks passed. No se requiere Python — forge es 100% Node.js.');
|
|
52
63
|
}
|
|
53
64
|
else {
|
|
54
|
-
console.log('
|
|
65
|
+
console.log('Algunos checks fallaron. Ver detalles arriba.');
|
|
55
66
|
}
|
|
56
67
|
return ok ? 0 : 1;
|
|
57
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAmDA,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA6F9D"}
|