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.
- package/bin/guild.js +49 -6
- package/package.json +2 -3
- package/src/commands/__tests__/doctor.test.js +85 -0
- package/src/commands/__tests__/list.test.js +82 -0
- package/src/commands/__tests__/new-agent.test.js +40 -0
- package/src/commands/__tests__/status.test.js +35 -0
- package/src/commands/doctor.js +99 -0
- package/src/commands/init.js +5 -6
- package/src/commands/list.js +82 -0
- package/src/commands/new-agent.js +5 -10
- package/src/commands/status.js +1 -2
- package/src/templates/agents/advisor.md +2 -0
- package/src/templates/agents/bugfix.md +2 -0
- package/src/templates/agents/code-reviewer.md +2 -0
- package/src/templates/agents/db-migration.md +2 -0
- package/src/templates/agents/developer.md +2 -0
- package/src/templates/agents/platform-expert.md +89 -0
- package/src/templates/agents/product-owner.md +2 -0
- package/src/templates/agents/qa.md +2 -0
- package/src/templates/agents/tech-lead.md +2 -0
- package/src/templates/skills/build-feature/SKILL.md +114 -14
- package/src/templates/skills/council/SKILL.md +33 -1
- package/src/templates/skills/dev-flow/SKILL.md +15 -1
- package/src/templates/skills/guild-specialize/SKILL.md +44 -1
- package/src/templates/skills/new-feature/SKILL.md +29 -2
- package/src/templates/skills/qa-cycle/SKILL.md +40 -11
- package/src/templates/skills/review/SKILL.md +31 -3
- package/src/templates/skills/session-end/SKILL.md +31 -1
- package/src/templates/skills/session-start/SKILL.md +33 -3
- package/src/templates/skills/status/SKILL.md +20 -1
- package/src/utils/files.js +23 -2
- package/src/utils/generators.js +7 -0
- 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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
|
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": "
|
|
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
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -28,7 +28,7 @@ export async function runInit() {
|
|
|
28
28
|
|
|
29
29
|
if (p.isCancel(overwrite) || !overwrite) {
|
|
30
30
|
p.cancel('Cancelado.');
|
|
31
|
-
|
|
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.');
|
|
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.');
|
|
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.');
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.');
|
|
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
|
-
|
|
91
|
-
process.exit(1);
|
|
86
|
+
throw error;
|
|
92
87
|
}
|
|
93
88
|
}
|
|
94
89
|
|
package/src/commands/status.js
CHANGED
|
@@ -9,8 +9,7 @@ import { join } from 'path';
|
|
|
9
9
|
|
|
10
10
|
export async function runStatus() {
|
|
11
11
|
if (!existsSync('PROJECT.md')) {
|
|
12
|
-
|
|
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');
|