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.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/bin/guild.js +95 -0
- package/package.json +58 -7
- 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 +140 -0
- package/src/commands/list.js +82 -0
- package/src/commands/new-agent.js +92 -0
- package/src/commands/status.js +57 -0
- package/src/templates/agents/advisor.md +47 -0
- package/src/templates/agents/bugfix.md +50 -0
- package/src/templates/agents/code-reviewer.md +52 -0
- package/src/templates/agents/db-migration.md +50 -0
- package/src/templates/agents/developer.md +50 -0
- package/src/templates/agents/platform-expert.md +89 -0
- package/src/templates/agents/product-owner.md +51 -0
- package/src/templates/agents/qa.md +50 -0
- package/src/templates/agents/tech-lead.md +50 -0
- package/src/templates/skills/build-feature/SKILL.md +200 -0
- package/src/templates/skills/council/SKILL.md +145 -0
- package/src/templates/skills/dev-flow/SKILL.md +69 -0
- package/src/templates/skills/guild-specialize/SKILL.md +156 -0
- package/src/templates/skills/new-feature/SKILL.md +86 -0
- package/src/templates/skills/qa-cycle/SKILL.md +73 -0
- package/src/templates/skills/review/SKILL.md +66 -0
- package/src/templates/skills/session-end/SKILL.md +84 -0
- package/src/templates/skills/session-start/SKILL.md +83 -0
- package/src/templates/skills/status/SKILL.md +81 -0
- package/src/utils/files.js +103 -0
- package/src/utils/generators.js +111 -0
- package/src/utils/github.js +144 -0
- 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
|
+
[](https://www.npmjs.com/package/guild-agents)
|
|
4
|
+
[](https://github.com/guild-agents/guild/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](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
|
|
4
|
-
"description": "A multi-agent framework for Claude Code",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
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-
|
|
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
|
+
}
|