guild-agents 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/bin/guild.js +49 -6
  2. package/package.json +2 -3
  3. package/src/commands/__tests__/doctor.test.js +85 -0
  4. package/src/commands/__tests__/list.test.js +82 -0
  5. package/src/commands/__tests__/new-agent.test.js +40 -0
  6. package/src/commands/__tests__/status.test.js +35 -0
  7. package/src/commands/doctor.js +99 -0
  8. package/src/commands/init.js +5 -6
  9. package/src/commands/list.js +82 -0
  10. package/src/commands/new-agent.js +5 -10
  11. package/src/commands/status.js +1 -2
  12. package/src/templates/agents/advisor.md +2 -0
  13. package/src/templates/agents/bugfix.md +2 -0
  14. package/src/templates/agents/code-reviewer.md +2 -0
  15. package/src/templates/agents/db-migration.md +2 -0
  16. package/src/templates/agents/developer.md +2 -0
  17. package/src/templates/agents/platform-expert.md +89 -0
  18. package/src/templates/agents/product-owner.md +2 -0
  19. package/src/templates/agents/qa.md +2 -0
  20. package/src/templates/agents/tech-lead.md +2 -0
  21. package/src/templates/skills/build-feature/SKILL.md +114 -14
  22. package/src/templates/skills/council/SKILL.md +33 -1
  23. package/src/templates/skills/dev-flow/SKILL.md +15 -1
  24. package/src/templates/skills/guild-specialize/SKILL.md +44 -1
  25. package/src/templates/skills/new-feature/SKILL.md +29 -2
  26. package/src/templates/skills/qa-cycle/SKILL.md +40 -11
  27. package/src/templates/skills/review/SKILL.md +31 -3
  28. package/src/templates/skills/session-end/SKILL.md +31 -1
  29. package/src/templates/skills/session-start/SKILL.md +33 -3
  30. package/src/templates/skills/status/SKILL.md +20 -1
  31. package/src/utils/files.js +23 -2
  32. package/src/utils/generators.js +7 -0
  33. package/src/utils/github.js +32 -14
package/bin/guild.js CHANGED
@@ -26,8 +26,13 @@ program
26
26
  .command('init')
27
27
  .description('Inicializar Guild v1 en el proyecto actual')
28
28
  .action(async () => {
29
- const { runInit } = await import('../src/commands/init.js');
30
- await runInit();
29
+ try {
30
+ const { runInit } = await import('../src/commands/init.js');
31
+ await runInit();
32
+ } catch (err) {
33
+ console.error(err.message);
34
+ process.exit(1);
35
+ }
31
36
  });
32
37
 
33
38
  // guild new-agent
@@ -36,8 +41,13 @@ program
36
41
  .description('Crear un nuevo agente')
37
42
  .argument('<name>', 'Nombre del agente (lowercase, guiones)')
38
43
  .action(async (name) => {
39
- const { runNewAgent } = await import('../src/commands/new-agent.js');
40
- await runNewAgent(name);
44
+ try {
45
+ const { runNewAgent } = await import('../src/commands/new-agent.js');
46
+ await runNewAgent(name);
47
+ } catch (err) {
48
+ console.error(err.message);
49
+ process.exit(1);
50
+ }
41
51
  });
42
52
 
43
53
  // guild status
@@ -45,8 +55,41 @@ program
45
55
  .command('status')
46
56
  .description('Ver estado del proyecto Guild')
47
57
  .action(async () => {
48
- const { runStatus } = await import('../src/commands/status.js');
49
- await runStatus();
58
+ try {
59
+ const { runStatus } = await import('../src/commands/status.js');
60
+ await runStatus();
61
+ } catch (err) {
62
+ console.error(err.message);
63
+ process.exit(1);
64
+ }
65
+ });
66
+
67
+ // guild doctor
68
+ program
69
+ .command('doctor')
70
+ .description('Verify Guild setup and report issues')
71
+ .action(async () => {
72
+ try {
73
+ const { runDoctor } = await import('../src/commands/doctor.js');
74
+ await runDoctor();
75
+ } catch (err) {
76
+ console.error(err.message);
77
+ process.exit(1);
78
+ }
79
+ });
80
+
81
+ // guild list
82
+ program
83
+ .command('list')
84
+ .description('List installed agents and skills')
85
+ .action(async () => {
86
+ try {
87
+ const { runList } = await import('../src/commands/list.js');
88
+ await runList();
89
+ } catch (err) {
90
+ console.error(err.message);
91
+ process.exit(1);
92
+ }
50
93
  });
51
94
 
52
95
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guild-agents",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "A multi-agent framework for Claude Code — specialized AI teams for every project",
5
5
  "type": "module",
6
6
  "files": [
@@ -11,7 +11,7 @@
11
11
  "!src/utils/__tests__/"
12
12
  ],
13
13
  "bin": {
14
- "guild": "./bin/guild.js"
14
+ "guild": "bin/guild.js"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "vitest run",
@@ -51,7 +51,6 @@
51
51
  "@clack/prompts": "^0.9.0",
52
52
  "chalk": "^5.3.0",
53
53
  "commander": "^12.0.0",
54
- "fs-extra": "^11.2.0",
55
54
  "picocolors": "^1.0.0"
56
55
  },
57
56
  "engines": {
@@ -0,0 +1,85 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdirSync, rmSync, mkdtempSync, writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+
6
+ describe('runDoctor', () => {
7
+ let tempDir;
8
+ let originalCwd;
9
+
10
+ beforeEach(() => {
11
+ tempDir = mkdtempSync(join(tmpdir(), 'guild-doctor-'));
12
+ originalCwd = process.cwd();
13
+ vi.resetModules();
14
+ });
15
+
16
+ afterEach(() => {
17
+ process.chdir(originalCwd);
18
+ rmSync(tempDir, { recursive: true, force: true });
19
+ });
20
+
21
+ it('should pass all checks for a healthy project', async () => {
22
+ // Setup a complete Guild project
23
+ mkdirSync(join(tempDir, '.claude', 'agents'), { recursive: true });
24
+ mkdirSync(join(tempDir, '.claude', 'skills', 'build-feature'), { recursive: true });
25
+ writeFileSync(join(tempDir, '.claude', 'agents', 'advisor.md'), '---\nname: advisor\n---');
26
+ writeFileSync(join(tempDir, '.claude', 'skills', 'build-feature', 'SKILL.md'), '---\nname: build-feature\n---');
27
+ writeFileSync(join(tempDir, 'CLAUDE.md'), '# CLAUDE');
28
+ writeFileSync(join(tempDir, 'PROJECT.md'), '# PROJECT');
29
+ writeFileSync(join(tempDir, 'SESSION.md'), '# SESSION');
30
+
31
+ process.chdir(tempDir);
32
+ const { runDoctor } = await import('../doctor.js');
33
+ // Should not throw for healthy project
34
+ await expect(runDoctor()).resolves.toBeUndefined();
35
+ });
36
+
37
+ it('should throw when .claude directory is missing', async () => {
38
+ writeFileSync(join(tempDir, 'CLAUDE.md'), '# CLAUDE');
39
+ writeFileSync(join(tempDir, 'PROJECT.md'), '# PROJECT');
40
+ writeFileSync(join(tempDir, 'SESSION.md'), '# SESSION');
41
+
42
+ process.chdir(tempDir);
43
+ const { runDoctor } = await import('../doctor.js');
44
+ await expect(runDoctor()).rejects.toThrow('Guild setup has issues');
45
+ });
46
+
47
+ it('should throw when agents directory is empty', async () => {
48
+ mkdirSync(join(tempDir, '.claude', 'agents'), { recursive: true });
49
+ mkdirSync(join(tempDir, '.claude', 'skills', 'test-skill'), { recursive: true });
50
+ writeFileSync(join(tempDir, '.claude', 'skills', 'test-skill', 'SKILL.md'), '---\nname: test\n---');
51
+ writeFileSync(join(tempDir, 'CLAUDE.md'), '# CLAUDE');
52
+ writeFileSync(join(tempDir, 'PROJECT.md'), '# PROJECT');
53
+ writeFileSync(join(tempDir, 'SESSION.md'), '# SESSION');
54
+
55
+ process.chdir(tempDir);
56
+ const { runDoctor } = await import('../doctor.js');
57
+ await expect(runDoctor()).rejects.toThrow('Guild setup has issues');
58
+ });
59
+
60
+ it('should throw when CLAUDE.md is missing', async () => {
61
+ mkdirSync(join(tempDir, '.claude', 'agents'), { recursive: true });
62
+ mkdirSync(join(tempDir, '.claude', 'skills', 'test-skill'), { recursive: true });
63
+ writeFileSync(join(tempDir, '.claude', 'agents', 'advisor.md'), '---\nname: advisor\n---');
64
+ writeFileSync(join(tempDir, '.claude', 'skills', 'test-skill', 'SKILL.md'), '---\nname: test\n---');
65
+ writeFileSync(join(tempDir, 'PROJECT.md'), '# PROJECT');
66
+ writeFileSync(join(tempDir, 'SESSION.md'), '# SESSION');
67
+
68
+ process.chdir(tempDir);
69
+ const { runDoctor } = await import('../doctor.js');
70
+ await expect(runDoctor()).rejects.toThrow('Guild setup has issues');
71
+ });
72
+
73
+ it('should throw when PROJECT.md is missing', async () => {
74
+ mkdirSync(join(tempDir, '.claude', 'agents'), { recursive: true });
75
+ mkdirSync(join(tempDir, '.claude', 'skills', 'test-skill'), { recursive: true });
76
+ writeFileSync(join(tempDir, '.claude', 'agents', 'advisor.md'), '---\nname: advisor\n---');
77
+ writeFileSync(join(tempDir, '.claude', 'skills', 'test-skill', 'SKILL.md'), '---\nname: test\n---');
78
+ writeFileSync(join(tempDir, 'CLAUDE.md'), '# CLAUDE');
79
+ writeFileSync(join(tempDir, 'SESSION.md'), '# SESSION');
80
+
81
+ process.chdir(tempDir);
82
+ const { runDoctor } = await import('../doctor.js');
83
+ await expect(runDoctor()).rejects.toThrow('Guild setup has issues');
84
+ });
85
+ });
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdirSync, rmSync, mkdtempSync, writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+
6
+ describe('runList', () => {
7
+ let tempDir;
8
+ let originalCwd;
9
+
10
+ beforeEach(() => {
11
+ tempDir = mkdtempSync(join(tmpdir(), 'guild-list-'));
12
+ originalCwd = process.cwd();
13
+ vi.resetModules();
14
+ });
15
+
16
+ afterEach(() => {
17
+ process.chdir(originalCwd);
18
+ rmSync(tempDir, { recursive: true, force: true });
19
+ });
20
+
21
+ it('should list agents and skills with descriptions', async () => {
22
+ mkdirSync(join(tempDir, '.claude', 'agents'), { recursive: true });
23
+ mkdirSync(join(tempDir, '.claude', 'skills', 'build-feature'), { recursive: true });
24
+ writeFileSync(
25
+ join(tempDir, '.claude', 'agents', 'advisor.md'),
26
+ '---\nname: advisor\ndescription: "Strategic advisor"\n---\n# Advisor'
27
+ );
28
+ writeFileSync(
29
+ join(tempDir, '.claude', 'skills', 'build-feature', 'SKILL.md'),
30
+ '---\nname: build-feature\ndescription: "Full pipeline"\n---\n# Build Feature'
31
+ );
32
+
33
+ process.chdir(tempDir);
34
+ const { runList } = await import('../list.js');
35
+ await expect(runList()).resolves.toBeUndefined();
36
+ });
37
+
38
+ it('should handle missing agents directory', async () => {
39
+ mkdirSync(join(tempDir, '.claude', 'skills', 'test'), { recursive: true });
40
+ writeFileSync(
41
+ join(tempDir, '.claude', 'skills', 'test', 'SKILL.md'),
42
+ '---\nname: test\n---'
43
+ );
44
+
45
+ process.chdir(tempDir);
46
+ const { runList } = await import('../list.js');
47
+ await expect(runList()).resolves.toBeUndefined();
48
+ });
49
+
50
+ it('should handle missing skills directory', async () => {
51
+ mkdirSync(join(tempDir, '.claude', 'agents'), { recursive: true });
52
+ writeFileSync(
53
+ join(tempDir, '.claude', 'agents', 'advisor.md'),
54
+ '---\nname: advisor\n---'
55
+ );
56
+
57
+ process.chdir(tempDir);
58
+ const { runList } = await import('../list.js');
59
+ await expect(runList()).resolves.toBeUndefined();
60
+ });
61
+
62
+ it('should handle empty directories', async () => {
63
+ mkdirSync(join(tempDir, '.claude', 'agents'), { recursive: true });
64
+ mkdirSync(join(tempDir, '.claude', 'skills'), { recursive: true });
65
+
66
+ process.chdir(tempDir);
67
+ const { runList } = await import('../list.js');
68
+ await expect(runList()).resolves.toBeUndefined();
69
+ });
70
+
71
+ it('should handle agents without frontmatter', async () => {
72
+ mkdirSync(join(tempDir, '.claude', 'agents'), { recursive: true });
73
+ writeFileSync(
74
+ join(tempDir, '.claude', 'agents', 'custom.md'),
75
+ '# Custom Agent\nNo frontmatter here'
76
+ );
77
+
78
+ process.chdir(tempDir);
79
+ const { runList } = await import('../list.js');
80
+ await expect(runList()).resolves.toBeUndefined();
81
+ });
82
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdirSync, rmSync, mkdtempSync, writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+
6
+ describe('runNewAgent', () => {
7
+ let tempDir;
8
+ let originalCwd;
9
+
10
+ beforeEach(() => {
11
+ tempDir = mkdtempSync(join(tmpdir(), 'guild-test-'));
12
+ originalCwd = process.cwd();
13
+ vi.resetModules();
14
+ });
15
+
16
+ afterEach(() => {
17
+ process.chdir(originalCwd);
18
+ rmSync(tempDir, { recursive: true, force: true });
19
+ });
20
+
21
+ it('should throw with invalid agent name', async () => {
22
+ process.chdir(tempDir);
23
+ const { runNewAgent } = await import('../new-agent.js');
24
+ await expect(runNewAgent('Invalid Name!')).rejects.toThrow('Nombre invalido');
25
+ });
26
+
27
+ it('should throw when .claude/agents/ does not exist', async () => {
28
+ process.chdir(tempDir);
29
+ const { runNewAgent } = await import('../new-agent.js');
30
+ await expect(runNewAgent('valid-name')).rejects.toThrow('Guild no esta instalado');
31
+ });
32
+
33
+ it('should throw when agent already exists', async () => {
34
+ process.chdir(tempDir);
35
+ mkdirSync(join(tempDir, '.claude', 'agents'), { recursive: true });
36
+ writeFileSync(join(tempDir, '.claude', 'agents', 'existing.md'), '# existing');
37
+ const { runNewAgent } = await import('../new-agent.js');
38
+ await expect(runNewAgent('existing')).rejects.toThrow('ya existe');
39
+ });
40
+ });
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mkdirSync, writeFileSync, rmSync, mkdtempSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+
6
+ describe('runStatus', () => {
7
+ let tempDir;
8
+ let originalCwd;
9
+
10
+ beforeEach(() => {
11
+ tempDir = mkdtempSync(join(tmpdir(), 'guild-test-'));
12
+ originalCwd = process.cwd();
13
+ vi.resetModules();
14
+ });
15
+
16
+ afterEach(() => {
17
+ process.chdir(originalCwd);
18
+ rmSync(tempDir, { recursive: true, force: true });
19
+ });
20
+
21
+ it('should throw when PROJECT.md does not exist', async () => {
22
+ process.chdir(tempDir);
23
+ const { runStatus } = await import('../status.js');
24
+ await expect(runStatus()).rejects.toThrow('Guild no esta instalado');
25
+ });
26
+
27
+ it('should not throw when PROJECT.md exists', async () => {
28
+ process.chdir(tempDir);
29
+ writeFileSync(join(tempDir, 'PROJECT.md'), '**Nombre:** TestProject\n**Stack:** Node.js');
30
+ mkdirSync(join(tempDir, '.claude', 'agents'), { recursive: true });
31
+ mkdirSync(join(tempDir, '.claude', 'skills'), { recursive: true });
32
+ const { runStatus } = await import('../status.js');
33
+ await expect(runStatus()).resolves.not.toThrow();
34
+ });
35
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * doctor.js — Validates Guild setup and reports actionable fixes
3
+ */
4
+
5
+ import * as p from '@clack/prompts';
6
+ import chalk from 'chalk';
7
+ import { existsSync, readdirSync } from 'fs';
8
+ import { join } from 'path';
9
+
10
+ export async function runDoctor() {
11
+ p.intro(chalk.bold.cyan('Guild — Doctor'));
12
+
13
+ const checks = [];
14
+ let healthy = true;
15
+
16
+ // Check .claude/ directory
17
+ if (existsSync('.claude')) {
18
+ checks.push({ name: '.claude/ directory', pass: true });
19
+ } else {
20
+ checks.push({ name: '.claude/ directory', pass: false, fix: 'Run: guild init' });
21
+ healthy = false;
22
+ }
23
+
24
+ // Check agents
25
+ const agentsDir = join('.claude', 'agents');
26
+ if (existsSync(agentsDir)) {
27
+ const agents = readdirSync(agentsDir).filter(f => f.endsWith('.md'));
28
+ if (agents.length > 0) {
29
+ checks.push({ name: `Agents (${agents.length} found)`, pass: true });
30
+ } else {
31
+ checks.push({ name: 'Agents', pass: false, fix: 'Run: guild init (no agent .md files found in .claude/agents/)' });
32
+ healthy = false;
33
+ }
34
+ } else {
35
+ checks.push({ name: 'Agents directory', pass: false, fix: 'Run: guild init (missing .claude/agents/)' });
36
+ healthy = false;
37
+ }
38
+
39
+ // Check skills
40
+ const skillsDir = join('.claude', 'skills');
41
+ if (existsSync(skillsDir)) {
42
+ const skills = readdirSync(skillsDir, { withFileTypes: true })
43
+ .filter(d => d.isDirectory())
44
+ .filter(d => existsSync(join(skillsDir, d.name, 'SKILL.md')));
45
+ if (skills.length > 0) {
46
+ checks.push({ name: `Skills (${skills.length} found)`, pass: true });
47
+ } else {
48
+ checks.push({ name: 'Skills', pass: false, fix: 'Run: guild init (no SKILL.md files found in .claude/skills/)' });
49
+ healthy = false;
50
+ }
51
+ } else {
52
+ checks.push({ name: 'Skills directory', pass: false, fix: 'Run: guild init (missing .claude/skills/)' });
53
+ healthy = false;
54
+ }
55
+
56
+ // Check CLAUDE.md
57
+ if (existsSync('CLAUDE.md')) {
58
+ checks.push({ name: 'CLAUDE.md', pass: true });
59
+ } else {
60
+ checks.push({ name: 'CLAUDE.md', pass: false, fix: 'Run: guild init (creates CLAUDE.md with project instructions)' });
61
+ healthy = false;
62
+ }
63
+
64
+ // Check PROJECT.md
65
+ if (existsSync('PROJECT.md')) {
66
+ checks.push({ name: 'PROJECT.md', pass: true });
67
+ } else {
68
+ checks.push({ name: 'PROJECT.md', pass: false, fix: 'Run: guild init (creates PROJECT.md with project identity)' });
69
+ healthy = false;
70
+ }
71
+
72
+ // Check SESSION.md
73
+ if (existsSync('SESSION.md')) {
74
+ checks.push({ name: 'SESSION.md', pass: true });
75
+ } else {
76
+ checks.push({ name: 'SESSION.md', pass: false, fix: 'Run: guild init (creates SESSION.md for session tracking)' });
77
+ healthy = false;
78
+ }
79
+
80
+ // Display results
81
+ for (const check of checks) {
82
+ if (check.pass) {
83
+ p.log.success(`${chalk.green('✓')} ${check.name}`);
84
+ } else {
85
+ p.log.error(`${chalk.red('✗')} ${check.name}`);
86
+ p.log.info(chalk.gray(` Fix: ${check.fix}`));
87
+ }
88
+ }
89
+
90
+ const passed = checks.filter(c => c.pass).length;
91
+ const total = checks.length;
92
+
93
+ if (healthy) {
94
+ p.outro(chalk.green(`All checks passed (${passed}/${total})`));
95
+ } else {
96
+ p.outro(chalk.red(`${total - passed} issue(s) found (${passed}/${total} passed)`));
97
+ throw new Error('Guild setup has issues. Run the suggested fixes above.');
98
+ }
99
+ }
@@ -28,7 +28,7 @@ export async function runInit() {
28
28
 
29
29
  if (p.isCancel(overwrite) || !overwrite) {
30
30
  p.cancel('Cancelado.');
31
- process.exit(0);
31
+ return;
32
32
  }
33
33
  }
34
34
 
@@ -40,7 +40,7 @@ export async function runInit() {
40
40
  if (!val) return 'El nombre es requerido';
41
41
  },
42
42
  });
43
- if (p.isCancel(name)) { p.cancel('Cancelado.'); process.exit(0); }
43
+ if (p.isCancel(name)) { p.cancel('Cancelado.'); return; }
44
44
 
45
45
  // ─── Tipo ───────────────────────────────────────────────────────────────────
46
46
  const type = await p.select({
@@ -53,7 +53,7 @@ export async function runInit() {
53
53
  { value: 'fullstack', label: 'Otro / Fullstack' },
54
54
  ],
55
55
  });
56
- if (p.isCancel(type)) { p.cancel('Cancelado.'); process.exit(0); }
56
+ if (p.isCancel(type)) { p.cancel('Cancelado.'); return; }
57
57
 
58
58
  // ─── Stack ──────────────────────────────────────────────────────────────────
59
59
  const stack = await p.text({
@@ -63,7 +63,7 @@ export async function runInit() {
63
63
  if (!val) return 'El stack es requerido';
64
64
  },
65
65
  });
66
- if (p.isCancel(stack)) { p.cancel('Cancelado.'); process.exit(0); }
66
+ if (p.isCancel(stack)) { p.cancel('Cancelado.'); return; }
67
67
 
68
68
  // ─── GitHub ─────────────────────────────────────────────────────────────────
69
69
  let github = null;
@@ -118,8 +118,7 @@ export async function runInit() {
118
118
  spinner.stop('Estructura creada.');
119
119
  } catch (error) {
120
120
  spinner.stop('Error durante la inicializacion.');
121
- p.log.error(error.message);
122
- process.exit(1);
121
+ throw error;
123
122
  }
124
123
 
125
124
  // ─── Resumen ────────────────────────────────────────────────────────────────
@@ -0,0 +1,82 @@
1
+ /**
2
+ * list.js — Lists installed agents and skills with descriptions
3
+ */
4
+
5
+ import * as p from '@clack/prompts';
6
+ import chalk from 'chalk';
7
+ import { existsSync, readdirSync, readFileSync } from 'fs';
8
+ import { join } from 'path';
9
+
10
+ /**
11
+ * Parses YAML frontmatter from a markdown file content.
12
+ * Returns an object with key-value pairs from the frontmatter.
13
+ */
14
+ function parseFrontmatter(content) {
15
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
16
+ if (!match) return {};
17
+ const fm = {};
18
+ for (const line of match[1].split('\n')) {
19
+ const colonIndex = line.indexOf(':');
20
+ if (colonIndex === -1) continue;
21
+ const key = line.slice(0, colonIndex).trim();
22
+ const value = line.slice(colonIndex + 1).trim().replace(/^["']|["']$/g, '');
23
+ if (key && value) fm[key] = value;
24
+ }
25
+ return fm;
26
+ }
27
+
28
+ export async function runList() {
29
+ p.intro(chalk.bold.cyan('Guild — Agents & Skills'));
30
+
31
+ // List agents
32
+ const agentsDir = join('.claude', 'agents');
33
+ p.log.step('Agents');
34
+
35
+ if (existsSync(agentsDir)) {
36
+ const agentFiles = readdirSync(agentsDir).filter(f => f.endsWith('.md')).sort();
37
+ if (agentFiles.length > 0) {
38
+ for (const file of agentFiles) {
39
+ const content = readFileSync(join(agentsDir, file), 'utf8');
40
+ const fm = parseFrontmatter(content);
41
+ const name = fm.name || file.replace('.md', '');
42
+ const desc = fm.description || chalk.gray('(no description)');
43
+ p.log.info(` ${chalk.bold(name)} — ${desc}`);
44
+ }
45
+ } else {
46
+ p.log.info(chalk.gray(' No agents found'));
47
+ }
48
+ } else {
49
+ p.log.info(chalk.gray(' No agents directory (.claude/agents/)'));
50
+ }
51
+
52
+ // List skills
53
+ const skillsDir = join('.claude', 'skills');
54
+ p.log.step('Skills');
55
+
56
+ if (existsSync(skillsDir)) {
57
+ const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
58
+ .filter(d => d.isDirectory())
59
+ .map(d => d.name)
60
+ .sort();
61
+ if (skillDirs.length > 0) {
62
+ for (const dir of skillDirs) {
63
+ const skillFile = join(skillsDir, dir, 'SKILL.md');
64
+ if (existsSync(skillFile)) {
65
+ const content = readFileSync(skillFile, 'utf8');
66
+ const fm = parseFrontmatter(content);
67
+ const name = fm.name || dir;
68
+ const desc = fm.description || chalk.gray('(no description)');
69
+ p.log.info(` ${chalk.bold(name)} — ${desc}`);
70
+ } else {
71
+ p.log.info(` ${chalk.bold(dir)} — ${chalk.gray('(missing SKILL.md)')}`);
72
+ }
73
+ }
74
+ } else {
75
+ p.log.info(chalk.gray(' No skills found'));
76
+ }
77
+ } else {
78
+ p.log.info(chalk.gray(' No skills directory (.claude/skills/)'));
79
+ }
80
+
81
+ p.outro('');
82
+ }
@@ -21,22 +21,18 @@ export async function runNewAgent(agentName) {
21
21
 
22
22
  // Validar nombre
23
23
  if (!isValidAgentName(agentName)) {
24
- p.log.error(`Nombre invalido: "${agentName}". Solo lowercase, numeros y guiones.`);
25
- p.log.info('Ejemplo: guild new-agent security-auditor');
26
- process.exit(1);
24
+ throw new Error(`Nombre invalido: "${agentName}". Solo lowercase, numeros y guiones. Ejemplo: guild new-agent security-auditor`);
27
25
  }
28
26
 
29
27
  // Verificar Guild instalado
30
28
  if (!existsSync(AGENTS_DIR)) {
31
- p.log.error('Guild no esta instalado. Ejecuta: guild init');
32
- process.exit(1);
29
+ throw new Error('Guild no esta instalado. Ejecuta: guild init');
33
30
  }
34
31
 
35
32
  // Verificar que el agente NO existe
36
33
  const agentPath = join(AGENTS_DIR, `${agentName}.md`);
37
34
  if (existsSync(agentPath)) {
38
- p.log.error(`El agente "${agentName}" ya existe.`);
39
- process.exit(1);
35
+ throw new Error(`El agente "${agentName}" ya existe.`);
40
36
  }
41
37
 
42
38
  // Pedir descripcion
@@ -45,7 +41,7 @@ export async function runNewAgent(agentName) {
45
41
  placeholder: 'ej: Evalua oportunidades de trading basado en analisis tecnico',
46
42
  validate: (val) => !val ? 'La descripcion es requerida' : undefined,
47
43
  });
48
- if (p.isCancel(description)) { p.cancel('Cancelado.'); process.exit(0); }
44
+ if (p.isCancel(description)) { p.cancel('Cancelado.'); return; }
49
45
 
50
46
  // Crear agente
51
47
  const spinner = p.spinner();
@@ -87,8 +83,7 @@ Eres ${agentName} de [PROYECTO].
87
83
  p.outro(chalk.bold.cyan(`Agente ${agentName} listo.`));
88
84
  } catch (error) {
89
85
  spinner.stop('Error al crear agente.');
90
- p.log.error(error.message);
91
- process.exit(1);
86
+ throw error;
92
87
  }
93
88
  }
94
89
 
@@ -9,8 +9,7 @@ import { join } from 'path';
9
9
 
10
10
  export async function runStatus() {
11
11
  if (!existsSync('PROJECT.md')) {
12
- p.log.error('Guild no esta instalado. Ejecuta: guild init');
13
- process.exit(1);
12
+ throw new Error('Guild no esta instalado. Ejecuta: guild init');
14
13
  }
15
14
 
16
15
  const projectMd = readFileSync('PROJECT.md', 'utf8');
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: advisor
3
3
  description: "Evalua ideas y da direccion estrategica antes de comprometer trabajo"
4
+ tools: Read, Glob, Grep
5
+ permissionMode: plan
4
6
  ---
5
7
 
6
8
  # Advisor
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: bugfix
3
3
  description: "Diagnostico y resolucion de bugs"
4
+ tools: Read, Write, Edit, Bash, Glob, Grep
5
+ permissionMode: bypassPermissions
4
6
  ---
5
7
 
6
8
  # Bugfix
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: code-reviewer
3
3
  description: "Revisa calidad, patterns, deuda tecnica"
4
+ tools: Read, Glob, Grep
5
+ permissionMode: plan
4
6
  ---
5
7
 
6
8
  # Code Reviewer
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: db-migration
3
3
  description: "Cambios de schema, migraciones seguras"
4
+ tools: Read, Write, Edit, Bash, Glob, Grep
5
+ permissionMode: bypassPermissions
4
6
  ---
5
7
 
6
8
  # DB Migration
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: developer
3
3
  description: "Implementa features siguiendo las convenciones del proyecto"
4
+ tools: Read, Write, Edit, Bash, Glob, Grep
5
+ permissionMode: bypassPermissions
4
6
  ---
5
7
 
6
8
  # Developer