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
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* files.js — Utilidades de sistema de archivos para Guild v1
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { mkdirSync, copyFileSync, existsSync, readdirSync, readFileSync } from 'fs';
|
|
6
|
+
import { join, dirname, resolve } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const TEMPLATES_DIR = join(__dirname, '..', 'templates');
|
|
11
|
+
const AGENTS_DIR = join('.claude', 'agents');
|
|
12
|
+
const SKILLS_DIR = join('.claude', 'skills');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Lista los nombres de los 9 agentes v1.
|
|
16
|
+
*/
|
|
17
|
+
export function getAgentNames() {
|
|
18
|
+
return [
|
|
19
|
+
'advisor',
|
|
20
|
+
'product-owner',
|
|
21
|
+
'tech-lead',
|
|
22
|
+
'developer',
|
|
23
|
+
'code-reviewer',
|
|
24
|
+
'qa',
|
|
25
|
+
'bugfix',
|
|
26
|
+
'db-migration',
|
|
27
|
+
'platform-expert',
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Copia los templates de agentes y skills al proyecto del usuario.
|
|
33
|
+
*/
|
|
34
|
+
export async function copyTemplates() {
|
|
35
|
+
mkdirSync(AGENTS_DIR, { recursive: true });
|
|
36
|
+
mkdirSync(SKILLS_DIR, { recursive: true });
|
|
37
|
+
|
|
38
|
+
// Copy flat agent .md files
|
|
39
|
+
for (const name of getAgentNames()) {
|
|
40
|
+
const src = join(TEMPLATES_DIR, 'agents', `${name}.md`);
|
|
41
|
+
const dest = join(AGENTS_DIR, `${name}.md`);
|
|
42
|
+
if (existsSync(src)) {
|
|
43
|
+
copyFileSync(src, dest);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Copy skill directories with SKILL.md
|
|
48
|
+
const skillsTemplate = join(TEMPLATES_DIR, 'skills');
|
|
49
|
+
if (existsSync(skillsTemplate)) {
|
|
50
|
+
const skills = readdirSync(skillsTemplate, { withFileTypes: true })
|
|
51
|
+
.filter(d => d.isDirectory())
|
|
52
|
+
.map(d => d.name);
|
|
53
|
+
|
|
54
|
+
for (const skill of skills) {
|
|
55
|
+
const skillDir = join(SKILLS_DIR, skill);
|
|
56
|
+
mkdirSync(skillDir, { recursive: true });
|
|
57
|
+
|
|
58
|
+
const src = join(skillsTemplate, skill, 'SKILL.md');
|
|
59
|
+
const dest = join(skillDir, 'SKILL.md');
|
|
60
|
+
if (existsSync(src)) {
|
|
61
|
+
copyFileSync(src, dest);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Lee el contenido de PROJECT.md si existe.
|
|
69
|
+
*/
|
|
70
|
+
export function readProjectMd() {
|
|
71
|
+
const path = 'PROJECT.md';
|
|
72
|
+
if (!existsSync(path)) return null;
|
|
73
|
+
return readFileSync(path, 'utf8');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Lee el contenido de SESSION.md si existe.
|
|
78
|
+
*/
|
|
79
|
+
export function readSessionMd() {
|
|
80
|
+
const path = 'SESSION.md';
|
|
81
|
+
if (!existsSync(path)) return null;
|
|
82
|
+
return readFileSync(path, 'utf8');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resuelve la raiz del proyecto Guild caminando hacia arriba desde startDir.
|
|
87
|
+
* Busca .claude/ o PROJECT.md como marcadores de un proyecto Guild.
|
|
88
|
+
* Retorna la ruta absoluta del proyecto o null si no se encuentra.
|
|
89
|
+
*/
|
|
90
|
+
export function resolveProjectRoot(startDir = process.cwd()) {
|
|
91
|
+
let dir = resolve(startDir);
|
|
92
|
+
while (true) {
|
|
93
|
+
if (existsSync(join(dir, '.claude')) || existsSync(join(dir, 'PROJECT.md'))) {
|
|
94
|
+
return dir;
|
|
95
|
+
}
|
|
96
|
+
const parent = dirname(dir);
|
|
97
|
+
if (parent === dir) {
|
|
98
|
+
// Reached filesystem root without finding a project
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
dir = parent;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* generators.js — Genera los archivos de estado del proyecto (v1)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { writeFileSync } from 'fs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Genera PROJECT.md con los datos del onboarding.
|
|
9
|
+
* V1: solo metadata cruda — CLAUDE.md tiene el contexto enriquecido.
|
|
10
|
+
*/
|
|
11
|
+
export async function generateProjectMd(data) {
|
|
12
|
+
const date = new Date().toISOString().split('T')[0];
|
|
13
|
+
|
|
14
|
+
let content = `# PROJECT.md
|
|
15
|
+
> Generado por Guild v1 el ${date}
|
|
16
|
+
|
|
17
|
+
## Proyecto
|
|
18
|
+
- **Nombre:** ${data.name}
|
|
19
|
+
- **Tipo:** ${data.type}
|
|
20
|
+
- **Stack:** ${data.stack}
|
|
21
|
+
- **Codigo existente:** ${data.hasExistingCode ? 'Si' : 'No'}
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
if (data.github?.repoUrl) {
|
|
25
|
+
content += `
|
|
26
|
+
## GitHub
|
|
27
|
+
- **Repositorio:** ${data.github.repoUrl}
|
|
28
|
+
`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
writeFileSync('PROJECT.md', content, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Genera CLAUDE.md — documento central con placeholders para guild-specialize.
|
|
36
|
+
*/
|
|
37
|
+
export async function generateClaudeMd(data) {
|
|
38
|
+
const content = `# ${data.name}
|
|
39
|
+
|
|
40
|
+
## Framework
|
|
41
|
+
Este proyecto usa Guild. Leer SESSION.md al inicio de cada sesion.
|
|
42
|
+
|
|
43
|
+
## Stack
|
|
44
|
+
${data.stack}
|
|
45
|
+
|
|
46
|
+
## Estructura del proyecto
|
|
47
|
+
[PENDIENTE: guild-specialize]
|
|
48
|
+
|
|
49
|
+
## Convenciones de codigo
|
|
50
|
+
[PENDIENTE: guild-specialize]
|
|
51
|
+
|
|
52
|
+
## Patrones de arquitectura
|
|
53
|
+
[PENDIENTE: guild-specialize]
|
|
54
|
+
|
|
55
|
+
## Variables de entorno
|
|
56
|
+
[PENDIENTE: guild-specialize]
|
|
57
|
+
|
|
58
|
+
## Reglas globales
|
|
59
|
+
- No implementar sin plan aprobado
|
|
60
|
+
- Actualizar SESSION.md al cerrar cada sesion
|
|
61
|
+
- ESModules en todo el codigo
|
|
62
|
+
- path.join() siempre para construir paths
|
|
63
|
+
|
|
64
|
+
## Subagent rules
|
|
65
|
+
- Guild agent roles (advisor, developer, tech-lead, etc.) are NOT Claude Code subagent_types
|
|
66
|
+
- Always use \`subagent_type: "general-purpose"\` when spawning agents via Task tool
|
|
67
|
+
- CLAUDE.md and SESSION.md changes must be committed separately from feature code
|
|
68
|
+
- No \`git stash\` in automated pipelines — use \`wip:\` commits instead
|
|
69
|
+
- Parallel agents must use git worktrees for isolation
|
|
70
|
+
|
|
71
|
+
## Skills disponibles
|
|
72
|
+
- /guild-specialize — enriquecer CLAUDE.md explorando el proyecto real
|
|
73
|
+
- /build-feature — pipeline completo de desarrollo
|
|
74
|
+
- /new-feature — crear branch y scaffold para feature
|
|
75
|
+
- /council — debatir decisiones con multiples agentes
|
|
76
|
+
- /review — code review sobre el diff actual
|
|
77
|
+
- /qa-cycle — ciclo QA + bugfix
|
|
78
|
+
- /status — ver estado del proyecto
|
|
79
|
+
- /dev-flow — ver fase actual del pipeline
|
|
80
|
+
- /session-start — cargar contexto y retomar trabajo
|
|
81
|
+
- /session-end — guardar estado en SESSION.md
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
writeFileSync('CLAUDE.md', content, 'utf8');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Genera SESSION.md inicial.
|
|
89
|
+
*/
|
|
90
|
+
export async function generateSessionMd() {
|
|
91
|
+
const date = new Date().toISOString().split('T')[0];
|
|
92
|
+
|
|
93
|
+
const content = `# SESSION.md
|
|
94
|
+
|
|
95
|
+
## Sesion activa
|
|
96
|
+
- **Fecha:** ${date}
|
|
97
|
+
- **Tarea en curso:** —
|
|
98
|
+
- **Agente activo:** —
|
|
99
|
+
- **Estado:** Proyecto recien inicializado con Guild v1
|
|
100
|
+
|
|
101
|
+
## Contexto relevante
|
|
102
|
+
- Onboarding completado. Ver PROJECT.md para datos del proyecto.
|
|
103
|
+
- CLAUDE.md tiene placeholders — ejecutar /guild-specialize para enriquecer.
|
|
104
|
+
|
|
105
|
+
## Proximos pasos
|
|
106
|
+
1. Abrir Claude Code y ejecutar /guild-specialize
|
|
107
|
+
2. Definir la primera feature con /build-feature
|
|
108
|
+
`;
|
|
109
|
+
|
|
110
|
+
writeFileSync('SESSION.md', content, 'utf8');
|
|
111
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* github.js — Integración con GitHub CLI (gh)
|
|
3
|
+
*
|
|
4
|
+
* Requiere que el usuario tenga gh instalado y autenticado.
|
|
5
|
+
* Todas las operaciones son no-bloqueantes — si gh no está disponible,
|
|
6
|
+
* Guild funciona normalmente sin integración GitHub.
|
|
7
|
+
*
|
|
8
|
+
* Uses execFileSync with array-based arguments to prevent shell injection
|
|
9
|
+
* through user-controlled strings (issue titles, bodies, labels).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execFileSync } from 'node:child_process';
|
|
13
|
+
|
|
14
|
+
const LABELS = [
|
|
15
|
+
{ name: 'backlog', color: '8E8E8E', description: 'Tarea documentada, pendiente de iniciar' },
|
|
16
|
+
{ name: 'in-progress', color: '0075CA', description: 'En implementación' },
|
|
17
|
+
{ name: 'in-review', color: 'E4A800', description: 'En validación QA' },
|
|
18
|
+
{ name: 'done', color: '2EA44F', description: 'Completada y mergeada' },
|
|
19
|
+
{ name: 'bug', color: 'D73A4A', description: 'Bug reportado por QA' },
|
|
20
|
+
{ name: 'blocked', color: 'E99695', description: 'Bloqueada por dependencia' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Verifica si gh CLI está instalado y autenticado.
|
|
25
|
+
*/
|
|
26
|
+
export function isGhAvailable() {
|
|
27
|
+
try {
|
|
28
|
+
execFileSync('gh', ['auth', 'status'], { stdio: 'ignore' });
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Configura los labels de estado en el repositorio GitHub.
|
|
37
|
+
*/
|
|
38
|
+
export async function setupGithubLabels(repoUrl) {
|
|
39
|
+
if (!isGhAvailable()) {
|
|
40
|
+
console.warn('gh CLI no disponible — saltando configuración de labels.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const repo = extractRepoFromUrl(repoUrl);
|
|
45
|
+
if (!repo) return;
|
|
46
|
+
|
|
47
|
+
for (const label of LABELS) {
|
|
48
|
+
try {
|
|
49
|
+
execFileSync('gh', [
|
|
50
|
+
'label', 'create', label.name,
|
|
51
|
+
'--color', label.color,
|
|
52
|
+
'--description', label.description,
|
|
53
|
+
'--repo', repo,
|
|
54
|
+
'--force',
|
|
55
|
+
], { stdio: 'ignore' });
|
|
56
|
+
} catch {
|
|
57
|
+
// Label ya existe o error no crítico
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Asigna un issue a @me y cambia su label de estado.
|
|
64
|
+
*/
|
|
65
|
+
export function assignIssue(issueNumber, fromLabel, toLabel) {
|
|
66
|
+
if (!isGhAvailable()) return;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
execFileSync('gh', [
|
|
70
|
+
'issue', 'assign', String(issueNumber), '--assignee', '@me',
|
|
71
|
+
], { stdio: 'ignore' });
|
|
72
|
+
execFileSync('gh', [
|
|
73
|
+
'issue', 'edit', String(issueNumber),
|
|
74
|
+
'--add-label', toLabel,
|
|
75
|
+
'--remove-label', fromLabel,
|
|
76
|
+
], { stdio: 'ignore' });
|
|
77
|
+
} catch {
|
|
78
|
+
// Non-critical
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Agrega un comentario a un issue.
|
|
84
|
+
*/
|
|
85
|
+
export function commentIssue(issueNumber, body) {
|
|
86
|
+
if (!isGhAvailable()) return;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
execFileSync('gh', [
|
|
90
|
+
'issue', 'comment', String(issueNumber), '--body', body,
|
|
91
|
+
], { stdio: 'ignore' });
|
|
92
|
+
} catch {
|
|
93
|
+
// Non-critical
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Cierra un issue con un comentario.
|
|
99
|
+
*/
|
|
100
|
+
export function closeIssue(issueNumber, comment) {
|
|
101
|
+
if (!isGhAvailable()) return;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
execFileSync('gh', [
|
|
105
|
+
'issue', 'close', String(issueNumber), '--comment', comment,
|
|
106
|
+
], { stdio: 'ignore' });
|
|
107
|
+
} catch {
|
|
108
|
+
// Non-critical
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Crea un issue de bug referenciando una tarea padre.
|
|
114
|
+
*/
|
|
115
|
+
export function createBugIssue(title, body, parentIssueNumber) {
|
|
116
|
+
if (!isGhAvailable()) return null;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const result = execFileSync('gh', [
|
|
120
|
+
'issue', 'create',
|
|
121
|
+
'--title', title,
|
|
122
|
+
'--body', body,
|
|
123
|
+
'--label', 'bug',
|
|
124
|
+
], { encoding: 'utf8' });
|
|
125
|
+
const issueUrl = result.trim();
|
|
126
|
+
const issueNumber = issueUrl.split('/').pop();
|
|
127
|
+
|
|
128
|
+
if (parentIssueNumber) {
|
|
129
|
+
commentIssue(parentIssueNumber, `Bug encontrado: ${issueUrl}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { number: issueNumber, url: issueUrl };
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Extrae "owner/repo" de una URL de GitHub.
|
|
140
|
+
*/
|
|
141
|
+
function extractRepoFromUrl(url) {
|
|
142
|
+
const match = url.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
143
|
+
return match ? match[1] : null;
|
|
144
|
+
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// Guild Agents - coming soon
|