@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.
Files changed (89) hide show
  1. package/.claude/rules/common/general-rules.md +141 -0
  2. package/.claude/rules/lessons/lessons.md +91 -0
  3. package/.claude/rules/role-rules/dev-fe-rules.md +146 -0
  4. package/.claude/rules/role-rules/sa-rules.md +226 -0
  5. package/.claude/skills/SKILL-INDEX.md +299 -0
  6. package/.claude/skills/community/security-validator/SKILL.md +392 -0
  7. package/.claude/skills/core/agent-creation/SKILL.md +338 -0
  8. package/.claude/skills/core/code-review/SKILL.md +154 -0
  9. package/.claude/skills/core/git-automation/SKILL.md +93 -0
  10. package/.claude/skills/core/retrospect-work/SKILL.md +172 -0
  11. package/.claude/skills/domain/architecture/adr-writing/SKILL.md +254 -0
  12. package/.claude/skills/domain/architecture/adr-writing/references/adr-best-practices.md +257 -0
  13. package/.claude/skills/domain/architecture/adr-writing/references/adr-examples.md +246 -0
  14. package/.claude/skills/domain/architecture/adr-writing/references/adr-template.md +160 -0
  15. package/.claude/skills/domain/architecture/architecture-patterns/SKILL.md +316 -0
  16. package/.claude/skills/domain/architecture/architecture-patterns/references/event-driven.md +393 -0
  17. package/.claude/skills/domain/architecture/architecture-patterns/references/microservices.md +315 -0
  18. package/.claude/skills/domain/architecture/architecture-patterns/references/monolith.md +321 -0
  19. package/.claude/skills/domain/architecture/architecture-patterns/references/serverless.md +457 -0
  20. package/.claude/skills/domain/architecture/performance-engineering/SKILL.md +227 -0
  21. package/.claude/skills/domain/architecture/performance-engineering/references/benchmarking.md +336 -0
  22. package/.claude/skills/domain/architecture/performance-engineering/references/caching-strategies.md +284 -0
  23. package/.claude/skills/domain/architecture/performance-engineering/references/optimization.md +298 -0
  24. package/.claude/skills/domain/architecture/security-architecture/SKILL.md +206 -0
  25. package/.claude/skills/domain/architecture/security-architecture/references/auth-patterns.md +209 -0
  26. package/.claude/skills/domain/architecture/security-architecture/references/compliance.md +246 -0
  27. package/.claude/skills/domain/architecture/security-architecture/references/threat-modeling.md +219 -0
  28. package/.claude/skills/domain/architecture/system-design/SKILL.md +227 -0
  29. package/.claude/skills/domain/architecture/system-design/references/distributed-systems.md +231 -0
  30. package/.claude/skills/domain/architecture/system-design/references/resilience.md +344 -0
  31. package/.claude/skills/domain/architecture/system-design/references/scalability.md +303 -0
  32. package/.claude/skills/domain/architecture/tech-selection/SKILL.md +192 -0
  33. package/.claude/skills/domain/architecture/tech-selection/references/build-vs-buy.md +258 -0
  34. package/.claude/skills/domain/architecture/tech-selection/references/evaluation-framework.md +203 -0
  35. package/.claude/skills/domain/architecture/tech-selection/references/tech-radar.md +257 -0
  36. package/.claude/skills/domain/backend/api-design/SKILL.md +121 -0
  37. package/.claude/skills/domain/backend/database-design/SKILL.md +156 -0
  38. package/.claude/skills/domain/backend/performance-be/SKILL.md +210 -0
  39. package/.claude/skills/domain/backend/security/SKILL.md +138 -0
  40. package/.claude/skills/domain/backend/testing-be/SKILL.md +203 -0
  41. package/.claude/skills/domain/devops/ci-cd/SKILL.md +188 -0
  42. package/.claude/skills/domain/devops/containerization/SKILL.md +177 -0
  43. package/.claude/skills/domain/devops/deployment/SKILL.md +198 -0
  44. package/.claude/skills/domain/devops/infrastructure-as-code/SKILL.md +178 -0
  45. package/.claude/skills/domain/devops/monitoring/SKILL.md +163 -0
  46. package/.claude/skills/domain/frontend/accessibility/SKILL.md +179 -0
  47. package/.claude/skills/domain/frontend/frontend-design/SKILL.md +138 -0
  48. package/.claude/skills/domain/frontend/performance-fe/SKILL.md +195 -0
  49. package/.claude/skills/domain/frontend/state-management/SKILL.md +190 -0
  50. package/.claude/skills/domain/frontend/testing-fe/SKILL.md +193 -0
  51. package/.claude/skills/domain/product/requirements-gathering/SKILL.md +136 -0
  52. package/.claude/skills/domain/product/roadmap-planning/SKILL.md +169 -0
  53. package/.claude/skills/domain/product/sprint-planning/SKILL.md +151 -0
  54. package/.claude/skills/domain/product/stakeholder-communication/SKILL.md +162 -0
  55. package/.claude/skills/domain/product/user-stories/SKILL.md +141 -0
  56. package/.claude/skills/domain/quality/bug-reporting/SKILL.md +150 -0
  57. package/.claude/skills/domain/quality/regression-testing/SKILL.md +178 -0
  58. package/.claude/skills/domain/quality/test-automation/SKILL.md +185 -0
  59. package/.claude/skills/domain/quality/test-planning/SKILL.md +177 -0
  60. package/.claude/skills/leadership/code-review-advanced/SKILL.md +167 -0
  61. package/.claude/skills/leadership/mentoring/SKILL.md +151 -0
  62. package/.claude/skills/leadership/technical-debt/SKILL.md +166 -0
  63. package/.claude/skills/leadership/technical-decision/SKILL.md +160 -0
  64. package/.claude/skills/security-reports/.gitkeep +0 -0
  65. package/.claude/skills/skills-registry.yaml +441 -0
  66. package/README.md +232 -0
  67. package/bin/agent-team.js +107 -0
  68. package/package.json +51 -0
  69. package/src/commands/add.js +227 -0
  70. package/src/commands/init.js +136 -0
  71. package/src/commands/list.js +66 -0
  72. package/src/commands/remove.js +71 -0
  73. package/src/commands/switch.js +53 -0
  74. package/src/index.js +11 -0
  75. package/src/interactive/prompts.js +153 -0
  76. package/src/server/api/agents.js +150 -0
  77. package/src/server/api/roles.js +97 -0
  78. package/src/server/api/skills.js +79 -0
  79. package/src/server/index.js +78 -0
  80. package/src/ui/agents.html +174 -0
  81. package/src/ui/css/styles.css +470 -0
  82. package/src/ui/index.html +107 -0
  83. package/src/ui/roles.html +371 -0
  84. package/src/ui/skills.html +332 -0
  85. package/src/utils/file-utils.js +193 -0
  86. package/src/utils/skill-resolver.js +594 -0
  87. package/src/utils/skill-scanner.js +154 -0
  88. package/templates/CLAUDE.md.tmpl +42 -0
  89. 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
+ }