@wipal/agent-team 1.0.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/.claude/rules/common/general-rules.md +141 -0
- package/.claude/rules/lessons/lessons.md +91 -0
- package/.claude/rules/role-rules/dev-fe-rules.md +146 -0
- package/.claude/rules/role-rules/sa-rules.md +226 -0
- package/.claude/skills/SKILL-INDEX.md +299 -0
- package/.claude/skills/community/security-validator/SKILL.md +392 -0
- package/.claude/skills/core/agent-creation/SKILL.md +338 -0
- package/.claude/skills/core/code-review/SKILL.md +154 -0
- package/.claude/skills/core/git-automation/SKILL.md +93 -0
- package/.claude/skills/core/retrospect-work/SKILL.md +172 -0
- package/.claude/skills/domain/architecture/adr-writing/SKILL.md +254 -0
- package/.claude/skills/domain/architecture/adr-writing/references/adr-best-practices.md +257 -0
- package/.claude/skills/domain/architecture/adr-writing/references/adr-examples.md +246 -0
- package/.claude/skills/domain/architecture/adr-writing/references/adr-template.md +160 -0
- package/.claude/skills/domain/architecture/architecture-patterns/SKILL.md +316 -0
- package/.claude/skills/domain/architecture/architecture-patterns/references/event-driven.md +393 -0
- package/.claude/skills/domain/architecture/architecture-patterns/references/microservices.md +315 -0
- package/.claude/skills/domain/architecture/architecture-patterns/references/monolith.md +321 -0
- package/.claude/skills/domain/architecture/architecture-patterns/references/serverless.md +457 -0
- package/.claude/skills/domain/architecture/performance-engineering/SKILL.md +227 -0
- package/.claude/skills/domain/architecture/performance-engineering/references/benchmarking.md +336 -0
- package/.claude/skills/domain/architecture/performance-engineering/references/caching-strategies.md +284 -0
- package/.claude/skills/domain/architecture/performance-engineering/references/optimization.md +298 -0
- package/.claude/skills/domain/architecture/security-architecture/SKILL.md +206 -0
- package/.claude/skills/domain/architecture/security-architecture/references/auth-patterns.md +209 -0
- package/.claude/skills/domain/architecture/security-architecture/references/compliance.md +246 -0
- package/.claude/skills/domain/architecture/security-architecture/references/threat-modeling.md +219 -0
- package/.claude/skills/domain/architecture/system-design/SKILL.md +227 -0
- package/.claude/skills/domain/architecture/system-design/references/distributed-systems.md +231 -0
- package/.claude/skills/domain/architecture/system-design/references/resilience.md +344 -0
- package/.claude/skills/domain/architecture/system-design/references/scalability.md +303 -0
- package/.claude/skills/domain/architecture/tech-selection/SKILL.md +192 -0
- package/.claude/skills/domain/architecture/tech-selection/references/build-vs-buy.md +258 -0
- package/.claude/skills/domain/architecture/tech-selection/references/evaluation-framework.md +203 -0
- package/.claude/skills/domain/architecture/tech-selection/references/tech-radar.md +257 -0
- package/.claude/skills/domain/backend/api-design/SKILL.md +121 -0
- package/.claude/skills/domain/backend/database-design/SKILL.md +156 -0
- package/.claude/skills/domain/backend/performance-be/SKILL.md +210 -0
- package/.claude/skills/domain/backend/security/SKILL.md +138 -0
- package/.claude/skills/domain/backend/testing-be/SKILL.md +203 -0
- package/.claude/skills/domain/devops/ci-cd/SKILL.md +188 -0
- package/.claude/skills/domain/devops/containerization/SKILL.md +177 -0
- package/.claude/skills/domain/devops/deployment/SKILL.md +198 -0
- package/.claude/skills/domain/devops/infrastructure-as-code/SKILL.md +178 -0
- package/.claude/skills/domain/devops/monitoring/SKILL.md +163 -0
- package/.claude/skills/domain/frontend/accessibility/SKILL.md +179 -0
- package/.claude/skills/domain/frontend/frontend-design/SKILL.md +138 -0
- package/.claude/skills/domain/frontend/performance-fe/SKILL.md +195 -0
- package/.claude/skills/domain/frontend/state-management/SKILL.md +190 -0
- package/.claude/skills/domain/frontend/testing-fe/SKILL.md +193 -0
- package/.claude/skills/domain/product/requirements-gathering/SKILL.md +136 -0
- package/.claude/skills/domain/product/roadmap-planning/SKILL.md +169 -0
- package/.claude/skills/domain/product/sprint-planning/SKILL.md +151 -0
- package/.claude/skills/domain/product/stakeholder-communication/SKILL.md +162 -0
- package/.claude/skills/domain/product/user-stories/SKILL.md +141 -0
- package/.claude/skills/domain/quality/bug-reporting/SKILL.md +150 -0
- package/.claude/skills/domain/quality/regression-testing/SKILL.md +178 -0
- package/.claude/skills/domain/quality/test-automation/SKILL.md +185 -0
- package/.claude/skills/domain/quality/test-planning/SKILL.md +177 -0
- package/.claude/skills/leadership/code-review-advanced/SKILL.md +167 -0
- package/.claude/skills/leadership/mentoring/SKILL.md +151 -0
- package/.claude/skills/leadership/technical-debt/SKILL.md +166 -0
- package/.claude/skills/leadership/technical-decision/SKILL.md +160 -0
- package/.claude/skills/security-reports/.gitkeep +0 -0
- package/.claude/skills/skills-registry.yaml +441 -0
- package/README.md +232 -0
- package/bin/agent-team.js +107 -0
- package/package.json +51 -0
- package/src/commands/add.js +227 -0
- package/src/commands/init.js +136 -0
- package/src/commands/list.js +66 -0
- package/src/commands/remove.js +71 -0
- package/src/commands/switch.js +53 -0
- package/src/index.js +11 -0
- package/src/interactive/prompts.js +153 -0
- package/src/server/api/agents.js +150 -0
- package/src/server/api/roles.js +97 -0
- package/src/server/api/skills.js +79 -0
- package/src/server/index.js +78 -0
- package/src/ui/agents.html +174 -0
- package/src/ui/css/styles.css +470 -0
- package/src/ui/index.html +107 -0
- package/src/ui/roles.html +371 -0
- package/src/ui/skills.html +332 -0
- package/src/utils/file-utils.js +193 -0
- package/src/utils/skill-resolver.js +594 -0
- package/src/utils/skill-scanner.js +154 -0
- package/templates/CLAUDE.md.tmpl +42 -0
- package/templates/knowledge.md.tmpl +31 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Skills - Agent Team Dashboard</title>
|
|
7
|
+
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
|
8
|
+
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
|
9
|
+
<link rel="stylesheet" href="/ui/css/styles.css">
|
|
10
|
+
<style>
|
|
11
|
+
.skill-card {
|
|
12
|
+
background: var(--bg-card);
|
|
13
|
+
border: 1px solid var(--border);
|
|
14
|
+
border-radius: 0.5rem;
|
|
15
|
+
padding: 1rem;
|
|
16
|
+
transition: all 0.2s;
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
}
|
|
19
|
+
.skill-card:hover {
|
|
20
|
+
border-color: var(--primary);
|
|
21
|
+
box-shadow: var(--shadow);
|
|
22
|
+
}
|
|
23
|
+
.skill-name {
|
|
24
|
+
font-size: 1rem;
|
|
25
|
+
font-weight: 600;
|
|
26
|
+
margin-bottom: 0.25rem;
|
|
27
|
+
}
|
|
28
|
+
.skill-category {
|
|
29
|
+
display: inline-block;
|
|
30
|
+
background: var(--primary);
|
|
31
|
+
color: white;
|
|
32
|
+
padding: 0.125rem 0.5rem;
|
|
33
|
+
border-radius: 1rem;
|
|
34
|
+
font-size: 0.65rem;
|
|
35
|
+
text-transform: uppercase;
|
|
36
|
+
margin-bottom: 0.25rem;
|
|
37
|
+
}
|
|
38
|
+
.skill-description {
|
|
39
|
+
color: var(--text-muted);
|
|
40
|
+
font-size: 0.8rem;
|
|
41
|
+
line-height: 1.4;
|
|
42
|
+
display: -webkit-box;
|
|
43
|
+
-webkit-line-clamp: 2;
|
|
44
|
+
-webkit-box-orient: vertical;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
}
|
|
47
|
+
.skills-grid {
|
|
48
|
+
display: grid;
|
|
49
|
+
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
50
|
+
gap: 0.75rem;
|
|
51
|
+
}
|
|
52
|
+
.filter-bar {
|
|
53
|
+
display: flex;
|
|
54
|
+
gap: 1rem;
|
|
55
|
+
margin-bottom: 1rem;
|
|
56
|
+
align-items: center;
|
|
57
|
+
}
|
|
58
|
+
.filter-bar .input {
|
|
59
|
+
max-width: 250px;
|
|
60
|
+
}
|
|
61
|
+
.view-toggle {
|
|
62
|
+
display: flex;
|
|
63
|
+
gap: 0.5rem;
|
|
64
|
+
}
|
|
65
|
+
.view-btn {
|
|
66
|
+
padding: 0.5rem 1rem;
|
|
67
|
+
border: 1px solid var(--border);
|
|
68
|
+
border-radius: 0.375rem;
|
|
69
|
+
background: var(--bg-card);
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
font-size: 0.875rem;
|
|
72
|
+
transition: all 0.2s;
|
|
73
|
+
}
|
|
74
|
+
.view-btn:hover {
|
|
75
|
+
border-color: var(--primary);
|
|
76
|
+
}
|
|
77
|
+
.view-btn.active {
|
|
78
|
+
background: var(--primary);
|
|
79
|
+
color: white;
|
|
80
|
+
border-color: var(--primary);
|
|
81
|
+
}
|
|
82
|
+
.empty-state {
|
|
83
|
+
text-align: center;
|
|
84
|
+
padding: 3rem;
|
|
85
|
+
color: var(--text-muted);
|
|
86
|
+
}
|
|
87
|
+
.empty-state h3 {
|
|
88
|
+
margin-bottom: 0.5rem;
|
|
89
|
+
}
|
|
90
|
+
.skill-detail {
|
|
91
|
+
background: var(--bg-card);
|
|
92
|
+
border: 1px solid var(--border);
|
|
93
|
+
border-radius: 0.5rem;
|
|
94
|
+
padding: 1.5rem;
|
|
95
|
+
}
|
|
96
|
+
.skill-detail pre {
|
|
97
|
+
background: var(--bg);
|
|
98
|
+
padding: 1rem;
|
|
99
|
+
border-radius: 0.375rem;
|
|
100
|
+
overflow-x: auto;
|
|
101
|
+
font-size: 0.8rem;
|
|
102
|
+
white-space: pre-wrap;
|
|
103
|
+
}
|
|
104
|
+
.skill-content {
|
|
105
|
+
margin-top: 1rem;
|
|
106
|
+
line-height: 1.6;
|
|
107
|
+
}
|
|
108
|
+
.skill-content h1, .skill-content h2, .skill-content h3 {
|
|
109
|
+
margin-top: 1rem;
|
|
110
|
+
margin-bottom: 0.5rem;
|
|
111
|
+
}
|
|
112
|
+
.back-btn {
|
|
113
|
+
display: inline-flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
gap: 0.5rem;
|
|
116
|
+
color: var(--text-muted);
|
|
117
|
+
text-decoration: none;
|
|
118
|
+
margin-bottom: 1rem;
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
}
|
|
121
|
+
.back-btn:hover {
|
|
122
|
+
color: var(--primary);
|
|
123
|
+
}
|
|
124
|
+
.used-by {
|
|
125
|
+
background: var(--bg);
|
|
126
|
+
padding: 0.5rem 0.75rem;
|
|
127
|
+
border-radius: 0.375rem;
|
|
128
|
+
font-size: 0.75rem;
|
|
129
|
+
margin-top: 0.5rem;
|
|
130
|
+
}
|
|
131
|
+
.used-by strong {
|
|
132
|
+
color: var(--text);
|
|
133
|
+
}
|
|
134
|
+
</style>
|
|
135
|
+
</head>
|
|
136
|
+
<body>
|
|
137
|
+
<div class="container" x-data="skillsApp()">
|
|
138
|
+
<!-- Header -->
|
|
139
|
+
<header class="header">
|
|
140
|
+
<h1>Skills</h1>
|
|
141
|
+
<p class="subtitle">Skills being used in your project</p>
|
|
142
|
+
</header>
|
|
143
|
+
|
|
144
|
+
<!-- Navigation -->
|
|
145
|
+
<nav class="nav">
|
|
146
|
+
<a href="/ui/" class="nav-link">Dashboard</a>
|
|
147
|
+
<a href="/ui/agents.html" class="nav-link">Agents</a>
|
|
148
|
+
<a href="/ui/skills.html" class="nav-link active">Skills</a>
|
|
149
|
+
<a href="/ui/roles.html" class="nav-link">Roles</a>
|
|
150
|
+
</nav>
|
|
151
|
+
|
|
152
|
+
<!-- Skill List View -->
|
|
153
|
+
<div x-show="!selectedSkill">
|
|
154
|
+
<!-- Filter Bar -->
|
|
155
|
+
<div class="filter-bar">
|
|
156
|
+
<input type="text" class="input" placeholder="Search..."
|
|
157
|
+
x-model="searchQuery" @input="filterSkills()">
|
|
158
|
+
|
|
159
|
+
<div class="view-toggle">
|
|
160
|
+
<button class="view-btn" :class="{ active: viewMode === 'used' }"
|
|
161
|
+
@click="viewMode = 'used'; filterSkills()">
|
|
162
|
+
In Use (<span x-text="usedSkills.length"></span>)
|
|
163
|
+
</button>
|
|
164
|
+
<button class="view-btn" :class="{ active: viewMode === 'all' }"
|
|
165
|
+
@click="viewMode = 'all'; filterSkills()">
|
|
166
|
+
All Available (<span x-text="allSkills.length"></span>)
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<!-- Skills Grid -->
|
|
172
|
+
<section class="section">
|
|
173
|
+
<!-- Used Skills View -->
|
|
174
|
+
<div x-show="viewMode === 'used'">
|
|
175
|
+
<div x-show="usedSkills.length > 0" class="skills-grid">
|
|
176
|
+
<template x-for="skill in filteredSkills" :key="skill.name">
|
|
177
|
+
<div class="skill-card" @click="selectSkill(skill)">
|
|
178
|
+
<span class="skill-category" x-text="skill.category || 'general'"></span>
|
|
179
|
+
<div class="skill-name" x-text="skill.name"></div>
|
|
180
|
+
<div class="skill-description" x-text="skill.description || 'No description'"></div>
|
|
181
|
+
<div class="used-by" x-show="skill.usedBy?.length">
|
|
182
|
+
<strong>Used by:</strong> <span x-text="skill.usedBy.join(', ')"></span>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</template>
|
|
186
|
+
</div>
|
|
187
|
+
<div x-show="usedSkills.length === 0" class="empty-state">
|
|
188
|
+
<h3>No skills in use yet</h3>
|
|
189
|
+
<p>Add an agent to see their skills here</p>
|
|
190
|
+
<a href="/ui/agents.html" class="btn btn-primary" style="margin-top: 1rem;">
|
|
191
|
+
Add Agent
|
|
192
|
+
</a>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<!-- All Skills View -->
|
|
197
|
+
<div x-show="viewMode === 'all'" class="skills-grid">
|
|
198
|
+
<template x-for="skill in filteredSkills" :key="skill.name">
|
|
199
|
+
<div class="skill-card" @click="selectSkill(skill)">
|
|
200
|
+
<span class="skill-category" x-text="skill.category || 'general'"></span>
|
|
201
|
+
<div class="skill-name" x-text="skill.name"></div>
|
|
202
|
+
<div class="skill-description" x-text="skill.description || 'No description'"></div>
|
|
203
|
+
</div>
|
|
204
|
+
</template>
|
|
205
|
+
</div>
|
|
206
|
+
</section>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<!-- Skill Detail View -->
|
|
210
|
+
<div x-show="selectedSkill" x-cloak>
|
|
211
|
+
<div class="back-btn" @click="selectedSkill = null">
|
|
212
|
+
← Back to skills
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<div class="skill-detail">
|
|
216
|
+
<span class="skill-category" x-text="selectedSkill?.category || 'general'"></span>
|
|
217
|
+
<h2 x-text="selectedSkill?.name" style="margin-top: 0.5rem;"></h2>
|
|
218
|
+
<p class="skill-description" x-text="selectedSkill?.description"></p>
|
|
219
|
+
|
|
220
|
+
<div class="used-by" x-show="selectedSkill?.usedBy?.length" style="margin-top: 1rem;">
|
|
221
|
+
<strong>Used by agents:</strong> <span x-text="selectedSkill?.usedBy?.join(', ')"></span>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div class="skill-content" id="skill-content">
|
|
225
|
+
<div class="loading">Loading...</div>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<script>
|
|
232
|
+
function skillsApp() {
|
|
233
|
+
return {
|
|
234
|
+
allSkills: [],
|
|
235
|
+
usedSkills: [],
|
|
236
|
+
filteredSkills: [],
|
|
237
|
+
viewMode: 'used',
|
|
238
|
+
searchQuery: '',
|
|
239
|
+
selectedSkill: null,
|
|
240
|
+
|
|
241
|
+
async init() {
|
|
242
|
+
await Promise.all([
|
|
243
|
+
this.loadAllSkills(),
|
|
244
|
+
this.loadUsedSkills()
|
|
245
|
+
]);
|
|
246
|
+
this.filterSkills();
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
async loadAllSkills() {
|
|
250
|
+
try {
|
|
251
|
+
const response = await fetch('/api/skills');
|
|
252
|
+
this.allSkills = await response.json();
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error('Failed to load skills:', error);
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
async loadUsedSkills() {
|
|
259
|
+
try {
|
|
260
|
+
// Get agents first
|
|
261
|
+
const agentsRes = await fetch('/api/agents');
|
|
262
|
+
const agentsData = await agentsRes.json();
|
|
263
|
+
|
|
264
|
+
if (!agentsData.agents || agentsData.agents.length === 0) {
|
|
265
|
+
this.usedSkills = [];
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Collect all skills used by agents
|
|
270
|
+
const skillUsage = {};
|
|
271
|
+
|
|
272
|
+
for (const agent of agentsData.agents) {
|
|
273
|
+
const agentDir = agent.path || `.claude/agents/${agent.name}`;
|
|
274
|
+
const skillsDir = `${agentDir}/skills`;
|
|
275
|
+
|
|
276
|
+
// Get skills from agent's variants.json or scan directory
|
|
277
|
+
if (agent.skills) {
|
|
278
|
+
for (const skillName of agent.skills) {
|
|
279
|
+
if (!skillUsage[skillName]) {
|
|
280
|
+
skillUsage[skillName] = [];
|
|
281
|
+
}
|
|
282
|
+
skillUsage[skillName].push(agent.name);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Get skill details for used skills
|
|
288
|
+
const usedSkillNames = Object.keys(skillUsage);
|
|
289
|
+
this.usedSkills = this.allSkills
|
|
290
|
+
.filter(s => usedSkillNames.includes(s.name))
|
|
291
|
+
.map(s => ({
|
|
292
|
+
...s,
|
|
293
|
+
usedBy: skillUsage[s.name] || []
|
|
294
|
+
}));
|
|
295
|
+
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.error('Failed to load used skills:', error);
|
|
298
|
+
this.usedSkills = [];
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
filterSkills() {
|
|
303
|
+
const source = this.viewMode === 'used' ? this.usedSkills : this.allSkills;
|
|
304
|
+
|
|
305
|
+
if (!this.searchQuery) {
|
|
306
|
+
this.filteredSkills = source;
|
|
307
|
+
} else {
|
|
308
|
+
const query = this.searchQuery.toLowerCase();
|
|
309
|
+
this.filteredSkills = source.filter(skill =>
|
|
310
|
+
skill.name.toLowerCase().includes(query) ||
|
|
311
|
+
(skill.description || '').toLowerCase().includes(query)
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
async selectSkill(skill) {
|
|
317
|
+
this.selectedSkill = skill;
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const response = await fetch(`/api/skills/${skill.name}/content`);
|
|
321
|
+
const content = await response.text();
|
|
322
|
+
document.getElementById('skill-content').innerHTML = content;
|
|
323
|
+
} catch (error) {
|
|
324
|
+
document.getElementById('skill-content').innerHTML =
|
|
325
|
+
'<p style="color: var(--text-muted);">Failed to load skill content</p>';
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
</script>
|
|
331
|
+
</body>
|
|
332
|
+
</html>
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File utilities for agent-team CLI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the package root directory
|
|
15
|
+
*/
|
|
16
|
+
export function getPackageRoot() {
|
|
17
|
+
return PACKAGE_ROOT;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the current working directory (project root)
|
|
22
|
+
*/
|
|
23
|
+
export function getProjectRoot() {
|
|
24
|
+
return process.cwd();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get .claude directory path in project
|
|
29
|
+
*/
|
|
30
|
+
export function getClaudeDir(projectRoot = getProjectRoot()) {
|
|
31
|
+
return path.join(projectRoot, '.claude');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get agents directory path
|
|
36
|
+
*/
|
|
37
|
+
export function getAgentsDir(projectRoot = getProjectRoot()) {
|
|
38
|
+
return path.join(getClaudeDir(projectRoot), 'agents');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get specific agent directory path
|
|
43
|
+
*/
|
|
44
|
+
export function getAgentDir(agentName, projectRoot = getProjectRoot()) {
|
|
45
|
+
return path.join(getAgentsDir(projectRoot), agentName);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if .claude directory exists
|
|
50
|
+
*/
|
|
51
|
+
export async function isInitialized(projectRoot = getProjectRoot()) {
|
|
52
|
+
return fs.exists(getClaudeDir(projectRoot));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if agent exists
|
|
57
|
+
*/
|
|
58
|
+
export async function agentExists(agentName, projectRoot = getProjectRoot()) {
|
|
59
|
+
return fs.exists(getAgentDir(agentName, projectRoot));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all agents in project
|
|
64
|
+
*/
|
|
65
|
+
export async function getAgents(projectRoot = getProjectRoot()) {
|
|
66
|
+
const agentsDir = getAgentsDir(projectRoot);
|
|
67
|
+
|
|
68
|
+
if (!(await fs.exists(agentsDir))) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
73
|
+
const agents = [];
|
|
74
|
+
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
if (entry.isDirectory()) {
|
|
77
|
+
const agentDir = path.join(agentsDir, entry.name);
|
|
78
|
+
const variantsPath = path.join(agentDir, 'variants.json');
|
|
79
|
+
const skillsDir = path.join(agentDir, 'skills');
|
|
80
|
+
|
|
81
|
+
let variants = null;
|
|
82
|
+
if (await fs.exists(variantsPath)) {
|
|
83
|
+
try {
|
|
84
|
+
variants = await fs.readJson(variantsPath);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Ignore parse errors
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Get skills list from agent's skills directory
|
|
91
|
+
let skills = [];
|
|
92
|
+
if (await fs.exists(skillsDir)) {
|
|
93
|
+
try {
|
|
94
|
+
const skillEntries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
95
|
+
skills = skillEntries
|
|
96
|
+
.filter(e => e.isDirectory())
|
|
97
|
+
.map(e => e.name);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
// Ignore errors
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
agents.push({
|
|
104
|
+
name: entry.name,
|
|
105
|
+
path: agentDir,
|
|
106
|
+
variants,
|
|
107
|
+
skills
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return agents;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Copy skills from package to agent folder
|
|
117
|
+
*/
|
|
118
|
+
export async function copySkills(skillNames, targetDir, sourceDir = null) {
|
|
119
|
+
const skillsSourceDir = sourceDir || path.join(PACKAGE_ROOT, '.claude', 'skills');
|
|
120
|
+
const targetSkillsDir = path.join(targetDir, 'skills');
|
|
121
|
+
|
|
122
|
+
await fs.ensureDir(targetSkillsDir);
|
|
123
|
+
|
|
124
|
+
const copiedSkills = [];
|
|
125
|
+
|
|
126
|
+
for (const skillName of skillNames) {
|
|
127
|
+
// Find skill in various locations
|
|
128
|
+
const possiblePaths = [
|
|
129
|
+
// Core skills
|
|
130
|
+
path.join(skillsSourceDir, 'core', skillName),
|
|
131
|
+
// Domain skills - frontend
|
|
132
|
+
path.join(skillsSourceDir, 'domain', 'frontend', skillName),
|
|
133
|
+
// Domain skills - backend
|
|
134
|
+
path.join(skillsSourceDir, 'domain', 'backend', skillName),
|
|
135
|
+
// Domain skills - architecture
|
|
136
|
+
path.join(skillsSourceDir, 'domain', 'architecture', skillName),
|
|
137
|
+
// Domain skills - devops
|
|
138
|
+
path.join(skillsSourceDir, 'domain', 'devops', skillName),
|
|
139
|
+
// Domain skills - product
|
|
140
|
+
path.join(skillsSourceDir, 'domain', 'product', skillName),
|
|
141
|
+
// Domain skills - quality
|
|
142
|
+
path.join(skillsSourceDir, 'domain', 'quality', skillName),
|
|
143
|
+
// Leadership skills
|
|
144
|
+
path.join(skillsSourceDir, 'leadership', skillName),
|
|
145
|
+
// Community skills
|
|
146
|
+
path.join(skillsSourceDir, 'community', skillName),
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
for (const skillPath of possiblePaths) {
|
|
150
|
+
if (await fs.exists(skillPath)) {
|
|
151
|
+
const targetPath = path.join(targetSkillsDir, skillName);
|
|
152
|
+
await fs.copy(skillPath, targetPath);
|
|
153
|
+
copiedSkills.push(skillName);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return copiedSkills;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Copy rules from package to project
|
|
164
|
+
*/
|
|
165
|
+
export async function copyRules(projectRoot = getProjectRoot()) {
|
|
166
|
+
const rulesSourceDir = path.join(PACKAGE_ROOT, '.claude', 'rules');
|
|
167
|
+
const targetRulesDir = path.join(getClaudeDir(projectRoot), 'rules');
|
|
168
|
+
|
|
169
|
+
await fs.ensureDir(targetRulesDir);
|
|
170
|
+
await fs.copy(rulesSourceDir, targetRulesDir, { overwrite: false });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Read template file
|
|
175
|
+
*/
|
|
176
|
+
export async function readTemplate(templateName) {
|
|
177
|
+
const templatePath = path.join(PACKAGE_ROOT, 'templates', templateName);
|
|
178
|
+
return fs.readFile(templatePath, 'utf-8');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Process template with variables
|
|
183
|
+
*/
|
|
184
|
+
export function processTemplate(template, variables) {
|
|
185
|
+
let result = template;
|
|
186
|
+
|
|
187
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
188
|
+
const regex = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g');
|
|
189
|
+
result = result.replace(regex, value);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return result;
|
|
193
|
+
}
|