@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.
Files changed (43) hide show
  1. package/assets/core/hooks/pre-bash-check.js +144 -0
  2. package/assets/core/hooks/pre-edit-check.js +166 -0
  3. package/dist/cli.js +1 -1
  4. package/dist/commands/audit.d.ts.map +1 -1
  5. package/dist/commands/audit.js +154 -9
  6. package/dist/commands/audit.js.map +1 -1
  7. package/dist/commands/doctor.d.ts.map +1 -1
  8. package/dist/commands/doctor.js +39 -28
  9. package/dist/commands/doctor.js.map +1 -1
  10. package/dist/commands/generate.d.ts.map +1 -1
  11. package/dist/commands/generate.js +114 -5
  12. package/dist/commands/generate.js.map +1 -1
  13. package/dist/commands/init.d.ts.map +1 -1
  14. package/dist/commands/init.js +283 -9
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/validate.d.ts.map +1 -1
  17. package/dist/commands/validate.js +92 -6
  18. package/dist/commands/validate.js.map +1 -1
  19. package/dist/lib/generators/claude-code.d.ts +3 -0
  20. package/dist/lib/generators/claude-code.d.ts.map +1 -0
  21. package/dist/lib/generators/claude-code.js +140 -0
  22. package/dist/lib/generators/claude-code.js.map +1 -0
  23. package/dist/lib/generators/codex.d.ts +3 -0
  24. package/dist/lib/generators/codex.d.ts.map +1 -0
  25. package/dist/lib/generators/codex.js +69 -0
  26. package/dist/lib/generators/codex.js.map +1 -0
  27. package/dist/lib/generators/kiro.d.ts +7 -0
  28. package/dist/lib/generators/kiro.d.ts.map +1 -0
  29. package/dist/lib/generators/kiro.js +134 -0
  30. package/dist/lib/generators/kiro.js.map +1 -0
  31. package/dist/lib/generators/opencode.d.ts +3 -0
  32. package/dist/lib/generators/opencode.d.ts.map +1 -0
  33. package/dist/lib/generators/opencode.js +96 -0
  34. package/dist/lib/generators/opencode.js.map +1 -0
  35. package/dist/lib/wizard.d.ts +17 -0
  36. package/dist/lib/wizard.d.ts.map +1 -0
  37. package/dist/lib/wizard.js +162 -0
  38. package/dist/lib/wizard.js.map +1 -0
  39. package/dist/lib/yaml.d.ts +96 -0
  40. package/dist/lib/yaml.d.ts.map +1 -0
  41. package/dist/lib/yaml.js +26 -0
  42. package/dist/lib/yaml.js.map +1 -0
  43. 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.1.0';
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":"AAcA,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAO3D"}
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"}
@@ -1,21 +1,166 @@
1
- import { resolveScript } from '../lib/paths.js';
2
- import { runPython } from '../lib/python.js';
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. Detects missing agents,
6
- outdated profiles, incomplete hooks, and available opportunities.
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 Output results as JSON (suitable for CI)
10
- --only <id,...> Audit only specific agents or profiles
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 script = resolveScript('forge-audit.py');
19
- return runPython(script, args);
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;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,IAAI,GAAG;;;;;;;;;CASZ,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAc;IACxC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,MAAM,GAAG,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC/C,OAAO,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AACjC,CAAC"}
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAElE,MAAM,IAAI,GAAG;;;;;;;;CAQZ,CAAC;AAQF,MAAM,oBAAoB,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAC/E,MAAM,iBAAiB,GAAG,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;AAEvD,SAAS,gBAAgB,CAAC,OAAe;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACrD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,SAAiB,EAAE,SAAiB;IACtD,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,wCAAwC,EAAE,CAAC,CAAC;IACnG,CAAC;IAED,MAAM,WAAW,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAE9C,KAAK,MAAM,KAAK,IAAI,oBAAoB,EAAE,CAAC;QACzC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,yBAAyB,KAAK,EAAE,EAAE,CAAC,CAAC;QAC9F,CAAC;IACH,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;QACxC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,qBAAqB,OAAO,EAAE,EAAE,CAAC,CAAC;QAC5F,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAc;IACxC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEzC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAiB,EAAE,CAAC;IAEhC,wBAAwB;IACxB,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,mDAAmD,EAAE,CAAC,CAAC;IACvH,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,CAAC;YACH,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;YACnC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;YACjC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC,CAAC;YAC7F,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBACnB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC,CAAC;YAC7F,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBAClB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;YAC5F,CAAC;QACH,CAAC;QAAC,OAAO,CAAU,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,qBAAqB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrI,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IACvD,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IAEhD,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,kCAAkC,EAAE,CAAC,CAAC;QAE5F,kBAAkB;QAClB,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAClF,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,6CAA6C,EAAE,CAAC,CAAC;QAC7G,CAAC;QAED,eAAe;QACf,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;QAClD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,iDAAiD,EAAE,CAAC,CAAC;QAC9G,CAAC;aAAM,CAAC;YACN,MAAM,UAAU,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YACzE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC5B,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,8CAA8C,EAAE,CAAC,CAAC;YAC3G,CAAC;iBAAM,CAAC;gBACN,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;oBACnC,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;oBACzF,MAAM,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;gBAC9B,CAAC;YACH,CAAC;QACH,CAAC;QAED,cAAc;QACd,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gDAAgD,EAAE,CAAC,CAAC;QAC5G,CAAC;aAAM,CAAC;YACN,MAAM,SAAS,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,SAAS,CAAC,MAAM,uBAAuB,EAAE,CAAC,CAAC;QACpG,CAAC;QAED,sBAAsB;QACtB,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC;YACvD,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC,CAAC;QAC1F,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,6CAA6C,EAAE,CAAC,CAAC;QACjH,CAAC;IACH,CAAC;IAED,IAAI,WAAW,IAAI,CAAC,YAAY,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,sCAAsC,EAAE,CAAC,CAAC;IAClG,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;IACrF,CAAC;IAED,IAAI,CAAC,YAAY,IAAI,CAAC,WAAW,IAAI,CAAC,OAAO,EAAE,CAAC;QAC9C,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,oDAAoD,EAAE,CAAC,CAAC;IAClH,CAAC;IAED,UAAU;IACV,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;IAC9D,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAC/D,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;IAEvD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;YACzB,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE;YACjC,MAAM;SACP,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACf,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,KAAK,GAA2B,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;YACpF,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,SAAS,QAAQ,eAAe,MAAM,UAAU,CAAC,CAAC;QAChF,IAAI,MAAM,KAAK,CAAC,IAAI,QAAQ,KAAK,CAAC;YAAE,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IACjG,CAAC;IAED,OAAO,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAGA,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAwD7D"}
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"}
@@ -1,35 +1,35 @@
1
- import { findPython, hasPyyaml } from '../lib/python.js';
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
- // Python
7
- const python = findPython();
8
- if (python) {
9
- console.log(` ✓ Python: ${python}`);
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('Python 3.9+ not found');
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 { existsSync } = await import('fs');
41
- const { join } = await import('path');
42
- const projectYaml = join(process.cwd(), 'project.yaml');
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: not found (run forge init)');
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('Some checks failed. Fix the issues above and run forge doctor again.');
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,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,KAAe;IAC1C,IAAI,EAAE,GAAG,IAAI,CAAC;IAEd,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAElD,SAAS;IACT,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,eAAe,MAAM,EAAE,CAAC,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QACzC,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,EAAE,GAAG,KAAK,CAAC;IACb,CAAC;IAED,SAAS;IACT,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,OAAO,MAAM,wBAAwB,CAAC,CAAC;YACnD,EAAE,GAAG,KAAK,CAAC;QACb,CAAC;IACH,CAAC;IAED,aAAa;IACb,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,gBAAgB,EAAE,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QACpB,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAC;QACtC,EAAE,GAAG,KAAK,CAAC;IACb,CAAC;IAED,eAAe;IACf,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC;IACxD,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACzC,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,IAAI,EAAE,EAAE,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACpC,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,sEAAsE,CAAC,CAAC;IACtF,CAAC;IAED,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC"}
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":"AAoBA,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAO9D"}
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"}