guild-agents 0.0.1 → 0.2.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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/bin/guild.js +95 -0
  4. package/package.json +58 -7
  5. package/src/commands/__tests__/doctor.test.js +85 -0
  6. package/src/commands/__tests__/list.test.js +82 -0
  7. package/src/commands/__tests__/new-agent.test.js +40 -0
  8. package/src/commands/__tests__/status.test.js +35 -0
  9. package/src/commands/doctor.js +99 -0
  10. package/src/commands/init.js +140 -0
  11. package/src/commands/list.js +82 -0
  12. package/src/commands/new-agent.js +92 -0
  13. package/src/commands/status.js +57 -0
  14. package/src/templates/agents/advisor.md +47 -0
  15. package/src/templates/agents/bugfix.md +50 -0
  16. package/src/templates/agents/code-reviewer.md +52 -0
  17. package/src/templates/agents/db-migration.md +50 -0
  18. package/src/templates/agents/developer.md +50 -0
  19. package/src/templates/agents/platform-expert.md +89 -0
  20. package/src/templates/agents/product-owner.md +51 -0
  21. package/src/templates/agents/qa.md +50 -0
  22. package/src/templates/agents/tech-lead.md +50 -0
  23. package/src/templates/skills/build-feature/SKILL.md +200 -0
  24. package/src/templates/skills/council/SKILL.md +145 -0
  25. package/src/templates/skills/dev-flow/SKILL.md +69 -0
  26. package/src/templates/skills/guild-specialize/SKILL.md +156 -0
  27. package/src/templates/skills/new-feature/SKILL.md +86 -0
  28. package/src/templates/skills/qa-cycle/SKILL.md +73 -0
  29. package/src/templates/skills/review/SKILL.md +66 -0
  30. package/src/templates/skills/session-end/SKILL.md +84 -0
  31. package/src/templates/skills/session-start/SKILL.md +83 -0
  32. package/src/templates/skills/status/SKILL.md +81 -0
  33. package/src/utils/files.js +103 -0
  34. package/src/utils/generators.js +111 -0
  35. package/src/utils/github.js +144 -0
  36. package/index.js +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 guild-agents
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # Guild
2
+
3
+ [![npm version](https://img.shields.io/npm/v/guild-agents)](https://www.npmjs.com/package/guild-agents)
4
+ [![CI](https://github.com/guild-agents/guild/actions/workflows/ci.yml/badge.svg)](https://github.com/guild-agents/guild/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+ [![Node.js >= 18](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
7
+
8
+ A multi-agent framework for Claude Code.
9
+
10
+ Sets up 8 specialized agents and 10 skill-based workflows as `.claude/` files in any project.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install -g guild-agents
16
+ ```
17
+
18
+ Or run directly without installing:
19
+
20
+ ```bash
21
+ npx guild-agents init
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```bash
27
+ npx guild-agents init
28
+ ```
29
+
30
+ Interactive onboarding asks for project name, type, stack, and repo details, then generates the full agent and skill structure.
31
+
32
+ Open Claude Code in your project and run:
33
+
34
+ ```
35
+ /guild-specialize
36
+ ```
37
+
38
+ This explores your actual codebase and enriches CLAUDE.md with real conventions, patterns, and stack details.
39
+
40
+ Then start building:
41
+
42
+ ```
43
+ /build-feature Add user authentication with JWT
44
+ ```
45
+
46
+ This runs the full pipeline: evaluation, spec, implementation, review, and QA.
47
+
48
+ `guild init` generates: CLAUDE.md, PROJECT.md, SESSION.md, `.claude/agents/` (8 agents), `.claude/skills/` (10 skills).
49
+
50
+ ## How It Works
51
+
52
+ **Agents** are the WHO. Each agent is a flat `.md` file in `.claude/agents/` that defines identity, responsibilities, and process. Skills invoke agents via the Task tool when their expertise is needed.
53
+
54
+ **Skills** are the HOW. Each skill is a workflow defined in `.claude/skills/*/SKILL.md` and invoked as a slash command. Skills orchestrate one or more agents through a structured process.
55
+
56
+ **State** is maintained across sessions through three files:
57
+ - `CLAUDE.md` — central enriched context (stack, conventions, rules)
58
+ - `PROJECT.md` — project metadata (name, type, architecture)
59
+ - `SESSION.md` — session continuity (current task, progress, next steps)
60
+
61
+ After init, agents are generic. Running `/guild-specialize` reads the real codebase and tailors each agent to the project's specific stack and patterns.
62
+
63
+ ## Agents
64
+
65
+ | Agent | Role |
66
+ |---|---|
67
+ | advisor | Evaluates ideas and provides strategic direction |
68
+ | product-owner | Turns approved ideas into concrete tasks |
69
+ | tech-lead | Defines technical approach and architecture |
70
+ | developer | Implements features following project conventions |
71
+ | code-reviewer | Reviews quality, patterns, and technical debt |
72
+ | qa | Testing, edge cases, regression validation |
73
+ | bugfix | Bug diagnosis and resolution |
74
+ | db-migration | Schema changes and safe migrations |
75
+
76
+ ## Skills
77
+
78
+ | Skill | Description |
79
+ |---|---|
80
+ | `/guild-specialize` | Explores codebase, enriches CLAUDE.md with real stack and conventions |
81
+ | `/build-feature` | Full pipeline: evaluation, spec, implementation, review, QA |
82
+ | `/new-feature` | Creates branch and scaffold for a new feature |
83
+ | `/council` | Convenes multiple agents to debate a decision |
84
+ | `/qa-cycle` | QA and bugfix loop until clean |
85
+ | `/review` | Code review on the current diff |
86
+ | `/dev-flow` | Shows current pipeline phase and next step |
87
+ | `/status` | Project and session state overview |
88
+ | `/session-start` | Loads context and resumes work |
89
+ | `/session-end` | Saves state to SESSION.md |
90
+
91
+ ## CLI Commands
92
+
93
+ ```bash
94
+ guild init # Interactive project onboarding
95
+ guild new-agent <name> # Create a custom agent
96
+ guild status # Show project status
97
+ ```
98
+
99
+ ## Generated Structure
100
+
101
+ Running `guild init` creates the following in your project root:
102
+
103
+ ```
104
+ CLAUDE.md
105
+ PROJECT.md
106
+ SESSION.md
107
+ .claude/
108
+ agents/
109
+ advisor.md
110
+ product-owner.md
111
+ tech-lead.md
112
+ developer.md
113
+ code-reviewer.md
114
+ qa.md
115
+ bugfix.md
116
+ db-migration.md
117
+ skills/
118
+ guild-specialize/SKILL.md
119
+ build-feature/SKILL.md
120
+ new-feature/SKILL.md
121
+ council/SKILL.md
122
+ qa-cycle/SKILL.md
123
+ review/SKILL.md
124
+ dev-flow/SKILL.md
125
+ status/SKILL.md
126
+ session-start/SKILL.md
127
+ session-end/SKILL.md
128
+ ```
129
+
130
+ All files are markdown, tracked by git, and work fully offline.
131
+
132
+ ## Requirements
133
+
134
+ - Node.js >= 18
135
+ - Claude Code
136
+ - `gh` CLI (optional, for GitHub integration)
137
+
138
+ ## Contributing
139
+
140
+ Two types of contributions:
141
+
142
+ - **Agent and skill templates** (`src/templates/`) — improve agent definitions or skill workflows.
143
+ - **CLI code** (`src/`, `bin/`) — bug fixes, new commands, onboarding improvements.
144
+
145
+ See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for details.
146
+
147
+ ## License
148
+
149
+ MIT — see [LICENSE](LICENSE).
package/bin/guild.js ADDED
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Guild v1 — CLI entry point
5
+ * Usage:
6
+ * guild init — onboarding interactivo v1
7
+ * guild new-agent — crear un nuevo agente
8
+ * guild status — ver estado del proyecto
9
+ */
10
+
11
+ import { program } from 'commander';
12
+ import { readFileSync } from 'fs';
13
+ import { fileURLToPath } from 'url';
14
+ import { dirname, join } from 'path';
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
18
+
19
+ program
20
+ .name('guild')
21
+ .description('Multi-agent framework for Claude Code')
22
+ .version(pkg.version);
23
+
24
+ // guild init
25
+ program
26
+ .command('init')
27
+ .description('Inicializar Guild v1 en el proyecto actual')
28
+ .action(async () => {
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
+ }
36
+ });
37
+
38
+ // guild new-agent
39
+ program
40
+ .command('new-agent')
41
+ .description('Crear un nuevo agente')
42
+ .argument('<name>', 'Nombre del agente (lowercase, guiones)')
43
+ .action(async (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
+ }
51
+ });
52
+
53
+ // guild status
54
+ program
55
+ .command('status')
56
+ .description('Ver estado del proyecto Guild')
57
+ .action(async () => {
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
+ }
93
+ });
94
+
95
+ program.parse();
package/package.json CHANGED
@@ -1,13 +1,64 @@
1
1
  {
2
2
  "name": "guild-agents",
3
- "version": "0.0.1",
4
- "description": "A multi-agent framework for Claude Code",
5
- "main": "index.js",
6
- "keywords": ["claude", "claude-code", "agents", "ai", "workflow"],
7
- "author": "tu-nombre",
3
+ "version": "0.2.0",
4
+ "description": "A multi-agent framework for Claude Code — specialized AI teams for every project",
5
+ "type": "module",
6
+ "files": [
7
+ "bin/",
8
+ "src/commands/",
9
+ "src/templates/",
10
+ "src/utils/",
11
+ "!src/utils/__tests__/"
12
+ ],
13
+ "bin": {
14
+ "guild": "bin/guild.js"
15
+ },
16
+ "scripts": {
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "test:coverage": "vitest run --coverage",
20
+ "lint": "eslint src/",
21
+ "dev": "node bin/guild.js",
22
+ "prepublishOnly": "npm test && npm run lint"
23
+ },
24
+ "keywords": [
25
+ "claude",
26
+ "claude-code",
27
+ "agents",
28
+ "ai",
29
+ "workflow",
30
+ "multi-agent",
31
+ "developer-tools",
32
+ "cli",
33
+ "framework",
34
+ "anthropic",
35
+ "ai-agents",
36
+ "automation",
37
+ "code-review",
38
+ "nodejs"
39
+ ],
40
+ "author": "",
8
41
  "license": "MIT",
9
42
  "repository": {
10
43
  "type": "git",
11
- "url": "https://github.com/guild-ai/guild"
44
+ "url": "git+https://github.com/guild-agents/guild.git"
45
+ },
46
+ "homepage": "https://github.com/guild-agents/guild",
47
+ "bugs": {
48
+ "url": "https://github.com/guild-agents/guild/issues"
49
+ },
50
+ "dependencies": {
51
+ "@clack/prompts": "^0.9.0",
52
+ "chalk": "^5.3.0",
53
+ "commander": "^12.0.0",
54
+ "picocolors": "^1.0.0"
55
+ },
56
+ "engines": {
57
+ "node": ">=18.0.0"
58
+ },
59
+ "devDependencies": {
60
+ "@eslint/js": "^10.0.1",
61
+ "eslint": "^10.0.1",
62
+ "vitest": "^4.0.18"
12
63
  }
13
- }
64
+ }
@@ -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
+ }