@itz4blitz/agentful 0.2.1 → 0.3.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/agents/orchestrator.md +121 -610
- package/.claude/commands/agentful-generate.md +206 -0
- package/.claude/skills/conversation/SKILL.md +152 -975
- package/bin/cli.js +108 -583
- package/bin/hooks/health-check.sh +16 -16
- package/lib/index.js +6 -36
- package/lib/init.js +162 -0
- package/package.json +1 -2
- package/version.json +1 -1
- package/.claude/commands/agentful-agents.md +0 -668
- package/.claude/commands/agentful-skills.md +0 -635
- package/.claude/product/CHANGES.md +0 -276
- package/lib/agent-generator.js +0 -778
- package/lib/domain-detector.js +0 -468
- package/lib/domain-structure-generator.js +0 -770
- package/lib/project-analyzer.js +0 -701
- package/lib/tech-stack-detector.js +0 -1091
- package/lib/template-engine.js +0 -240
- package/templates/agents/domain-agent.template.md +0 -208
- package/templates/agents/tech-agent.template.md +0 -124
package/lib/agent-generator.js
DELETED
|
@@ -1,778 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smart Agent Generation System
|
|
3
|
-
*
|
|
4
|
-
* Analyzes codebase and generates contextually-aware agents
|
|
5
|
-
* that understand the project's tech stack, patterns, and conventions.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import fs from 'fs/promises';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { fileURLToPath } from 'url';
|
|
11
|
-
import TemplateEngine from './template-engine.js';
|
|
12
|
-
|
|
13
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
-
const __dirname = path.dirname(__filename);
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Default core agent types registry
|
|
18
|
-
* Users can extend this by passing custom agent types to the constructor
|
|
19
|
-
*/
|
|
20
|
-
const DEFAULT_CORE_AGENT_TYPES = ['backend', 'frontend', 'tester', 'reviewer', 'fixer'];
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Default agent patterns configuration
|
|
24
|
-
* Defines how each agent type should extract patterns from the codebase
|
|
25
|
-
*/
|
|
26
|
-
const DEFAULT_AGENT_PATTERNS = {
|
|
27
|
-
backend: {
|
|
28
|
-
directories: ['src/repositories', 'src/services', 'src/controllers', 'src/routes', 'api'],
|
|
29
|
-
keywords: ['repository', 'service', 'controller', 'route', 'handler'],
|
|
30
|
-
},
|
|
31
|
-
frontend: {
|
|
32
|
-
directories: ['src/components', 'src/pages', 'src/app', 'components', 'pages'],
|
|
33
|
-
keywords: ['component', 'hook', 'page', 'view'],
|
|
34
|
-
},
|
|
35
|
-
tester: {
|
|
36
|
-
directories: ['tests', 'test', '__tests__', '__tests__', 'spec'],
|
|
37
|
-
keywords: ['describe', 'test', 'it', 'expect', 'mock'],
|
|
38
|
-
},
|
|
39
|
-
reviewer: {
|
|
40
|
-
directories: ['src'],
|
|
41
|
-
keywords: ['export', 'function', 'class', 'interface'],
|
|
42
|
-
},
|
|
43
|
-
fixer: {
|
|
44
|
-
directories: ['src'],
|
|
45
|
-
keywords: ['error', 'bug', 'fix', 'throw'],
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
class AgentGenerator {
|
|
50
|
-
/**
|
|
51
|
-
* @param {string} projectPath - Path to the project
|
|
52
|
-
* @param {object} analysis - Project analysis results
|
|
53
|
-
* @param {object} options - Configuration options
|
|
54
|
-
* @param {string[]} options.customAgentTypes - Additional core agent types to generate
|
|
55
|
-
* @param {object} options.customAgentPatterns - Pattern configurations for custom agent types
|
|
56
|
-
*/
|
|
57
|
-
constructor(projectPath, analysis, options = {}) {
|
|
58
|
-
this.projectPath = projectPath;
|
|
59
|
-
this.analysis = analysis;
|
|
60
|
-
this.templatesDir = path.join(__dirname, '../templates/agents');
|
|
61
|
-
this.agentsDir = path.join(projectPath, '.claude/agents/auto-generated');
|
|
62
|
-
|
|
63
|
-
// Extensible agent type registry
|
|
64
|
-
this.coreAgentTypes = [...DEFAULT_CORE_AGENT_TYPES];
|
|
65
|
-
this.agentPatterns = { ...DEFAULT_AGENT_PATTERNS };
|
|
66
|
-
|
|
67
|
-
// Apply custom agent types if provided
|
|
68
|
-
if (options.customAgentTypes && Array.isArray(options.customAgentTypes)) {
|
|
69
|
-
this.coreAgentTypes.push(...options.customAgentTypes);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Apply custom agent patterns if provided
|
|
73
|
-
if (options.customAgentPatterns && typeof options.customAgentPatterns === 'object') {
|
|
74
|
-
Object.assign(this.agentPatterns, options.customAgentPatterns);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Register a custom agent type at runtime
|
|
80
|
-
* @param {string} type - Agent type name
|
|
81
|
-
* @param {object} pattern - Pattern configuration for the agent type
|
|
82
|
-
* @param {string[]} pattern.directories - Directories to scan for this agent type
|
|
83
|
-
* @param {string[]} pattern.keywords - Keywords to look for in code
|
|
84
|
-
*/
|
|
85
|
-
registerAgentType(type, pattern) {
|
|
86
|
-
if (!this.coreAgentTypes.includes(type)) {
|
|
87
|
-
this.coreAgentTypes.push(type);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (pattern) {
|
|
91
|
-
this.agentPatterns[type] = pattern;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Get all registered core agent types
|
|
97
|
-
* @returns {string[]} List of core agent types
|
|
98
|
-
*/
|
|
99
|
-
getCoreAgentTypes() {
|
|
100
|
-
return [...this.coreAgentTypes];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Get pattern configuration for a specific agent type
|
|
105
|
-
* @param {string} type - Agent type name
|
|
106
|
-
* @returns {object|null} Pattern configuration or null if not found
|
|
107
|
-
*/
|
|
108
|
-
getAgentPattern(type) {
|
|
109
|
-
return this.agentPatterns[type] || null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Load custom agent types from a configuration file
|
|
114
|
-
* @param {string} configPath - Path to the configuration file (JSON)
|
|
115
|
-
* @returns {Promise<void>}
|
|
116
|
-
*/
|
|
117
|
-
async loadCustomAgentTypesFromConfig(configPath) {
|
|
118
|
-
try {
|
|
119
|
-
const content = await fs.readFile(configPath, 'utf-8');
|
|
120
|
-
const config = JSON.parse(content);
|
|
121
|
-
|
|
122
|
-
if (config.customAgentTypes && Array.isArray(config.customAgentTypes)) {
|
|
123
|
-
for (const agentConfig of config.customAgentTypes) {
|
|
124
|
-
if (agentConfig.type && agentConfig.pattern) {
|
|
125
|
-
this.registerAgentType(agentConfig.type, agentConfig.pattern);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
console.log(`✅ Loaded custom agent types from ${configPath}`);
|
|
131
|
-
} catch (error) {
|
|
132
|
-
console.warn(`⚠️ Could not load custom agent types from ${configPath}: ${error.message}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Main entry point - generates all agents based on analysis
|
|
138
|
-
*/
|
|
139
|
-
async generateAgents() {
|
|
140
|
-
console.log('🤖 Generating agents...');
|
|
141
|
-
|
|
142
|
-
// Ensure agents directory exists
|
|
143
|
-
await fs.mkdir(this.agentsDir, { recursive: true });
|
|
144
|
-
|
|
145
|
-
// Generate core agents (always)
|
|
146
|
-
const coreAgents = await this.generateCoreAgents();
|
|
147
|
-
|
|
148
|
-
// Generate domain agents (conditional based on detected domains)
|
|
149
|
-
const domainAgents = await this.generateDomainAgents();
|
|
150
|
-
|
|
151
|
-
// Generate tech-specific agents (conditional based on tech stack)
|
|
152
|
-
const techAgents = await this.generateTechAgents();
|
|
153
|
-
|
|
154
|
-
// Update architecture.json with generated agent info
|
|
155
|
-
await this.updateArchitectureConfig({
|
|
156
|
-
core: coreAgents,
|
|
157
|
-
domains: domainAgents,
|
|
158
|
-
tech: techAgents,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
console.log(`✅ Generated ${coreAgents.length + domainAgents.length + techAgents.length} agents`);
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
core: coreAgents,
|
|
165
|
-
domains: domainAgents,
|
|
166
|
-
tech: techAgents,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Generate core agents (always needed)
|
|
172
|
-
*/
|
|
173
|
-
async generateCoreAgents() {
|
|
174
|
-
const agents = [];
|
|
175
|
-
|
|
176
|
-
for (const type of this.coreAgentTypes) {
|
|
177
|
-
const agentPath = path.join(this.agentsDir, `${type}.md`);
|
|
178
|
-
const template = await this.loadTemplate(`${type}-agent.template.md`);
|
|
179
|
-
|
|
180
|
-
if (!template) {
|
|
181
|
-
console.warn(`⚠️ No template found for ${type}, skipping`);
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Extract patterns from actual code
|
|
186
|
-
const patterns = await this.extractPatterns(type);
|
|
187
|
-
|
|
188
|
-
// Interpolate template with project-specific data
|
|
189
|
-
const content = TemplateEngine.render(template, {
|
|
190
|
-
language: this.analysis.primaryLanguage || 'javascript',
|
|
191
|
-
framework: this.analysis.primaryFramework || 'custom',
|
|
192
|
-
patterns: patterns.code,
|
|
193
|
-
conventions: patterns.conventions,
|
|
194
|
-
samples: patterns.samples,
|
|
195
|
-
generated_at: new Date().toISOString(),
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
await fs.writeFile(agentPath, content);
|
|
199
|
-
agents.push({ type, path: agentPath });
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return agents;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Generate domain-specific agents (auth, billing, etc.)
|
|
207
|
-
*/
|
|
208
|
-
async generateDomainAgents() {
|
|
209
|
-
const domains = this.analysis.domains || [];
|
|
210
|
-
const agents = [];
|
|
211
|
-
|
|
212
|
-
for (const domain of domains) {
|
|
213
|
-
const agentPath = path.join(this.agentsDir, `${domain.name}-agent.md`);
|
|
214
|
-
|
|
215
|
-
// Extract domain-specific code samples
|
|
216
|
-
const samples = await this.extractDomainSamples(domain);
|
|
217
|
-
|
|
218
|
-
// Generate domain context
|
|
219
|
-
const domainContext = {
|
|
220
|
-
domain: domain.name,
|
|
221
|
-
features: domain.features || [],
|
|
222
|
-
language: this.analysis.primaryLanguage || 'javascript',
|
|
223
|
-
framework: this.analysis.primaryFramework || 'custom',
|
|
224
|
-
confidence: domain.confidence || 0.5,
|
|
225
|
-
codeSamples: samples.code,
|
|
226
|
-
patterns: samples.patterns,
|
|
227
|
-
endpoints: samples.endpoints,
|
|
228
|
-
models: samples.models,
|
|
229
|
-
generated_at: new Date().toISOString(),
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const template = await this.loadTemplate('domain-agent.template.md');
|
|
233
|
-
const content = TemplateEngine.render(template, domainContext);
|
|
234
|
-
|
|
235
|
-
await fs.writeFile(agentPath, content);
|
|
236
|
-
agents.push({ type: domain.name, path: agentPath });
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return agents;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Generate tech-specific agents (Next.js, Prisma, etc.)
|
|
244
|
-
*/
|
|
245
|
-
async generateTechAgents() {
|
|
246
|
-
const techStack = this.analysis.techStack || {};
|
|
247
|
-
const agents = [];
|
|
248
|
-
|
|
249
|
-
// Framework-specific agents
|
|
250
|
-
if (techStack.framework) {
|
|
251
|
-
const framework = techStack.framework.toLowerCase();
|
|
252
|
-
if (['nextjs', 'nuxt', 'remix'].includes(framework)) {
|
|
253
|
-
const agent = await this.generateFrameworkAgent(framework);
|
|
254
|
-
if (agent) agents.push(agent);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// ORM-specific agents
|
|
259
|
-
if (techStack.orm) {
|
|
260
|
-
const orm = techStack.orm.toLowerCase();
|
|
261
|
-
if (['prisma', 'drizzle', 'typeorm', 'mongoose'].includes(orm)) {
|
|
262
|
-
const agent = await this.generateORMAgent(orm);
|
|
263
|
-
if (agent) agents.push(agent);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Database-specific agents
|
|
268
|
-
if (techStack.database) {
|
|
269
|
-
const db = techStack.database.toLowerCase();
|
|
270
|
-
if (['postgresql', 'mongodb', 'mysql', 'sqlite'].includes(db)) {
|
|
271
|
-
const agent = await this.generateDatabaseAgent(db);
|
|
272
|
-
if (agent) agents.push(agent);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return agents;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Generate framework-specific agent
|
|
281
|
-
*/
|
|
282
|
-
async generateFrameworkAgent(framework) {
|
|
283
|
-
const agentPath = path.join(this.agentsDir, `${framework}-agent.md`);
|
|
284
|
-
const template = await this.loadTemplate('tech-agent.template.md');
|
|
285
|
-
|
|
286
|
-
if (!template) return null;
|
|
287
|
-
|
|
288
|
-
const samples = await this.extractFrameworkSamples(framework);
|
|
289
|
-
|
|
290
|
-
const content = TemplateEngine.render(template, {
|
|
291
|
-
tech: framework,
|
|
292
|
-
techType: 'framework',
|
|
293
|
-
language: this.analysis.primaryLanguage || 'javascript',
|
|
294
|
-
framework: framework,
|
|
295
|
-
patterns: samples.patterns,
|
|
296
|
-
conventions: samples.conventions,
|
|
297
|
-
samples: samples.code,
|
|
298
|
-
generated_at: new Date().toISOString(),
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
await fs.writeFile(agentPath, content);
|
|
302
|
-
return { type: framework, path: agentPath };
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Generate ORM-specific agent
|
|
307
|
-
*/
|
|
308
|
-
async generateORMAgent(orm) {
|
|
309
|
-
const agentPath = path.join(this.agentsDir, `${orm}-agent.md`);
|
|
310
|
-
const template = await this.loadTemplate('tech-agent.template.md');
|
|
311
|
-
|
|
312
|
-
if (!template) return null;
|
|
313
|
-
|
|
314
|
-
const samples = await this.extractORMSamples(orm);
|
|
315
|
-
|
|
316
|
-
const content = TemplateEngine.render(template, {
|
|
317
|
-
tech: orm,
|
|
318
|
-
techType: 'orm',
|
|
319
|
-
language: this.analysis.primaryLanguage || 'javascript',
|
|
320
|
-
framework: this.analysis.primaryFramework || 'custom',
|
|
321
|
-
patterns: samples.patterns,
|
|
322
|
-
conventions: samples.conventions,
|
|
323
|
-
samples: samples.code,
|
|
324
|
-
generated_at: new Date().toISOString(),
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
await fs.writeFile(agentPath, content);
|
|
328
|
-
return { type: orm, path: agentPath };
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Generate database-specific agent
|
|
333
|
-
*/
|
|
334
|
-
async generateDatabaseAgent(database) {
|
|
335
|
-
const agentPath = path.join(this.agentsDir, `${database}-agent.md`);
|
|
336
|
-
const template = await this.loadTemplate('tech-agent.template.md');
|
|
337
|
-
|
|
338
|
-
if (!template) return null;
|
|
339
|
-
|
|
340
|
-
const samples = await this.extractDatabaseSamples(database);
|
|
341
|
-
|
|
342
|
-
const content = TemplateEngine.render(template, {
|
|
343
|
-
tech: database,
|
|
344
|
-
techType: 'database',
|
|
345
|
-
language: this.analysis.primaryLanguage || 'javascript',
|
|
346
|
-
framework: this.analysis.primaryFramework || 'custom',
|
|
347
|
-
patterns: samples.patterns,
|
|
348
|
-
conventions: samples.conventions,
|
|
349
|
-
samples: samples.code,
|
|
350
|
-
generated_at: new Date().toISOString(),
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
await fs.writeFile(agentPath, content);
|
|
354
|
-
return { type: database, path: agentPath };
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Extract code patterns for a specific agent type
|
|
359
|
-
*/
|
|
360
|
-
async extractPatterns(agentType) {
|
|
361
|
-
const patterns = {
|
|
362
|
-
code: [],
|
|
363
|
-
conventions: [],
|
|
364
|
-
samples: [],
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
// Get pattern configuration from registry
|
|
368
|
-
const config = this.agentPatterns[agentType];
|
|
369
|
-
if (!config) {
|
|
370
|
-
console.warn(`⚠️ No pattern configuration found for agent type: ${agentType}`);
|
|
371
|
-
return patterns;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Scan directories for patterns
|
|
375
|
-
for (const dir of config.directories) {
|
|
376
|
-
const dirPath = path.join(this.projectPath, dir);
|
|
377
|
-
try {
|
|
378
|
-
const files = await this.scanDirectory(dirPath, 10); // Sample up to 10 files
|
|
379
|
-
|
|
380
|
-
for (const file of files) {
|
|
381
|
-
const content = await fs.readFile(file, 'utf-8');
|
|
382
|
-
const relativePath = path.relative(this.projectPath, file);
|
|
383
|
-
|
|
384
|
-
// Extract code samples
|
|
385
|
-
if (content.length > 0 && content.length < 2000) {
|
|
386
|
-
patterns.samples.push({
|
|
387
|
-
path: relativePath,
|
|
388
|
-
content: content,
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Identify patterns
|
|
393
|
-
for (const keyword of config.keywords) {
|
|
394
|
-
if (content.toLowerCase().includes(keyword)) {
|
|
395
|
-
patterns.code.push({
|
|
396
|
-
keyword,
|
|
397
|
-
context: this.extractContext(content, keyword),
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
} catch (error) {
|
|
403
|
-
// Directory doesn't exist, skip
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Detect naming conventions
|
|
408
|
-
patterns.conventions = await this.detectConventions(agentType);
|
|
409
|
-
|
|
410
|
-
return patterns;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Extract domain-specific code samples
|
|
415
|
-
*/
|
|
416
|
-
async extractDomainSamples(domain) {
|
|
417
|
-
const samples = {
|
|
418
|
-
code: [],
|
|
419
|
-
patterns: [],
|
|
420
|
-
endpoints: [],
|
|
421
|
-
models: [],
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
// Find domain-specific files
|
|
425
|
-
const domainPatterns = {
|
|
426
|
-
'auth-agent': ['auth', 'user', 'login', 'register', 'session', 'token'],
|
|
427
|
-
'billing-agent': ['billing', 'payment', 'subscription', 'invoice', 'stripe'],
|
|
428
|
-
'content-agent': ['content', 'post', 'article', 'blog', 'page'],
|
|
429
|
-
'notification-agent': ['notification', 'email', 'sms', 'push'],
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
const keywords = domainPatterns[domain.name] || [domain.name];
|
|
433
|
-
|
|
434
|
-
// Scan for domain files
|
|
435
|
-
const files = await this.findFilesByKeywords(keywords, 5);
|
|
436
|
-
|
|
437
|
-
for (const file of files) {
|
|
438
|
-
const content = await fs.readFile(file, 'utf-8');
|
|
439
|
-
const relativePath = path.relative(this.projectPath, file);
|
|
440
|
-
|
|
441
|
-
samples.code.push({
|
|
442
|
-
path: relativePath,
|
|
443
|
-
content: content.substring(0, 1500), // Limit size
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
// Extract API endpoints
|
|
447
|
-
if (content.includes('router.') || content.includes('app.') || content.includes('@Get')) {
|
|
448
|
-
samples.endpoints.push(...this.extractEndpoints(content, relativePath));
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Extract data models
|
|
452
|
-
if (content.includes('model') || content.includes('schema') || content.includes('interface')) {
|
|
453
|
-
samples.models.push(...this.extractModels(content, relativePath));
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return samples;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Extract framework-specific samples
|
|
462
|
-
*/
|
|
463
|
-
async extractFrameworkSamples(framework) {
|
|
464
|
-
const samples = {
|
|
465
|
-
code: [],
|
|
466
|
-
patterns: [],
|
|
467
|
-
conventions: [],
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
const frameworkPatterns = {
|
|
471
|
-
nextjs: ['app/', 'pages/', 'middleware.ts', 'next.config'],
|
|
472
|
-
nuxt: ['pages/', 'components/', 'nuxt.config'],
|
|
473
|
-
remix: ['routes/', 'app/routes/', 'loader', 'action'],
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
const patterns = frameworkPatterns[framework] || [];
|
|
477
|
-
|
|
478
|
-
for (const pattern of patterns) {
|
|
479
|
-
const files = await this.findFilesByPattern(pattern, 3);
|
|
480
|
-
|
|
481
|
-
for (const file of files) {
|
|
482
|
-
const content = await fs.readFile(file, 'utf-8');
|
|
483
|
-
const relativePath = path.relative(this.projectPath, file);
|
|
484
|
-
|
|
485
|
-
samples.code.push({
|
|
486
|
-
path: relativePath,
|
|
487
|
-
content: content.substring(0, 1000),
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return samples;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Extract ORM-specific samples
|
|
497
|
-
*/
|
|
498
|
-
async extractORMSamples(orm) {
|
|
499
|
-
const samples = {
|
|
500
|
-
code: [],
|
|
501
|
-
patterns: [],
|
|
502
|
-
conventions: [],
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
const ormFiles = {
|
|
506
|
-
prisma: ['schema.prisma', 'client.ts'],
|
|
507
|
-
drizzle: ['schema.ts', 'db.ts'],
|
|
508
|
-
typeorm: ['entity.ts', 'repository.ts'],
|
|
509
|
-
mongoose: ['model.ts', 'schema.ts'],
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
const files = ormFiles[orm] || [];
|
|
513
|
-
|
|
514
|
-
for (const file of files) {
|
|
515
|
-
const foundFiles = await this.findFilesByPattern(file, 2);
|
|
516
|
-
|
|
517
|
-
for (const foundFile of foundFiles) {
|
|
518
|
-
const content = await fs.readFile(foundFile, 'utf-8');
|
|
519
|
-
const relativePath = path.relative(this.projectPath, foundFile);
|
|
520
|
-
|
|
521
|
-
samples.code.push({
|
|
522
|
-
path: relativePath,
|
|
523
|
-
content: content.substring(0, 1000),
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
return samples;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* Extract database-specific samples
|
|
533
|
-
*/
|
|
534
|
-
async extractDatabaseSamples(database) {
|
|
535
|
-
const samples = {
|
|
536
|
-
code: [],
|
|
537
|
-
patterns: [],
|
|
538
|
-
conventions: [],
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
// Look for migration files, SQL files, etc.
|
|
542
|
-
const patterns = ['migrations/', '*.sql', 'schema.sql', 'seeds/'];
|
|
543
|
-
|
|
544
|
-
for (const pattern of patterns) {
|
|
545
|
-
const files = await this.findFilesByPattern(pattern, 3);
|
|
546
|
-
|
|
547
|
-
for (const file of files) {
|
|
548
|
-
const content = await fs.readFile(file, 'utf-8');
|
|
549
|
-
const relativePath = path.relative(this.projectPath, file);
|
|
550
|
-
|
|
551
|
-
samples.code.push({
|
|
552
|
-
path: relativePath,
|
|
553
|
-
content: content.substring(0, 1000),
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
return samples;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Scan directory for files
|
|
563
|
-
*/
|
|
564
|
-
async scanDirectory(dirPath, maxFiles = 10) {
|
|
565
|
-
const files = [];
|
|
566
|
-
|
|
567
|
-
try {
|
|
568
|
-
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
569
|
-
|
|
570
|
-
for (const entry of entries) {
|
|
571
|
-
if (files.length >= maxFiles) break;
|
|
572
|
-
|
|
573
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
574
|
-
|
|
575
|
-
if (entry.isDirectory()) {
|
|
576
|
-
const subFiles = await this.scanDirectory(fullPath, maxFiles - files.length);
|
|
577
|
-
files.push(...subFiles);
|
|
578
|
-
} else if (entry.isFile() && this.isSourceFile(entry.name)) {
|
|
579
|
-
files.push(fullPath);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
} catch (error) {
|
|
583
|
-
// Directory doesn't exist or can't be read
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
return files;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Find files by keywords in name
|
|
591
|
-
*/
|
|
592
|
-
async findFilesByKeywords(keywords, maxFiles = 5) {
|
|
593
|
-
const allFiles = [];
|
|
594
|
-
|
|
595
|
-
for (const keyword of keywords) {
|
|
596
|
-
const files = await this.findFilesByPattern(keyword, maxFiles);
|
|
597
|
-
allFiles.push(...files);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
return allFiles.slice(0, maxFiles);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
/**
|
|
604
|
-
* Find files by pattern
|
|
605
|
-
*/
|
|
606
|
-
async findFilesByPattern(pattern, maxFiles = 5) {
|
|
607
|
-
const files = [];
|
|
608
|
-
|
|
609
|
-
const scanDir = async (dirPath) => {
|
|
610
|
-
try {
|
|
611
|
-
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
612
|
-
|
|
613
|
-
for (const entry of entries) {
|
|
614
|
-
if (files.length >= maxFiles) return;
|
|
615
|
-
|
|
616
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
617
|
-
|
|
618
|
-
if (entry.isDirectory()) {
|
|
619
|
-
// Skip node_modules and similar
|
|
620
|
-
if (!['node_modules', '.git', 'dist', 'build'].includes(entry.name)) {
|
|
621
|
-
await scanDir(fullPath);
|
|
622
|
-
}
|
|
623
|
-
} else if (entry.isFile()) {
|
|
624
|
-
if (entry.name.toLowerCase().includes(pattern.toLowerCase()) ||
|
|
625
|
-
fullPath.toLowerCase().includes(pattern.toLowerCase())) {
|
|
626
|
-
files.push(fullPath);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
} catch (error) {
|
|
631
|
-
// Can't read directory
|
|
632
|
-
}
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
await scanDir(this.projectPath);
|
|
636
|
-
return files;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
/**
|
|
640
|
-
* Check if file is a source file
|
|
641
|
-
*/
|
|
642
|
-
isSourceFile(filename) {
|
|
643
|
-
const extensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.go', '.rs'];
|
|
644
|
-
return extensions.some(ext => filename.endsWith(ext));
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
/**
|
|
648
|
-
* Extract context around a keyword
|
|
649
|
-
*/
|
|
650
|
-
extractContext(content, keyword) {
|
|
651
|
-
const lines = content.split('\n');
|
|
652
|
-
const context = [];
|
|
653
|
-
|
|
654
|
-
for (let i = 0; i < lines.length; i++) {
|
|
655
|
-
if (lines[i].toLowerCase().includes(keyword.toLowerCase())) {
|
|
656
|
-
const start = Math.max(0, i - 2);
|
|
657
|
-
const end = Math.min(lines.length, i + 3);
|
|
658
|
-
context.push(lines.slice(start, end).join('\n'));
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
return context.slice(0, 3); // Max 3 contexts
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
/**
|
|
666
|
-
* Detect naming conventions
|
|
667
|
-
*/
|
|
668
|
-
async detectConventions(agentType) {
|
|
669
|
-
const conventions = [];
|
|
670
|
-
|
|
671
|
-
// Sample some files to detect conventions
|
|
672
|
-
const files = await this.findFilesByPattern('.ts', 5);
|
|
673
|
-
|
|
674
|
-
for (const file of files) {
|
|
675
|
-
const content = await fs.readFile(file, 'utf-8');
|
|
676
|
-
|
|
677
|
-
// Detect import style
|
|
678
|
-
if (content.includes('@/')) {
|
|
679
|
-
conventions.push('Uses @ alias for imports');
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// Detect naming patterns
|
|
683
|
-
if (content.match(/class \w+/)) {
|
|
684
|
-
conventions.push('Uses class-based components');
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
if (content.match(/export (const|function) \w+/)) {
|
|
688
|
-
conventions.push('Uses functional exports');
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
return [...new Set(conventions)]; // Deduplicate
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* Extract API endpoints from code
|
|
697
|
-
*/
|
|
698
|
-
extractEndpoints(content, filePath) {
|
|
699
|
-
const endpoints = [];
|
|
700
|
-
const lines = content.split('\n');
|
|
701
|
-
|
|
702
|
-
for (const line of lines) {
|
|
703
|
-
if (line.includes('router.') || line.includes('app.')) {
|
|
704
|
-
endpoints.push({
|
|
705
|
-
file: filePath,
|
|
706
|
-
code: line.trim(),
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
return endpoints.slice(0, 5);
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
* Extract data models from code
|
|
716
|
-
*/
|
|
717
|
-
extractModels(content, filePath) {
|
|
718
|
-
const models = [];
|
|
719
|
-
const lines = content.split('\n');
|
|
720
|
-
|
|
721
|
-
for (const line of lines) {
|
|
722
|
-
if (line.includes('model ') || line.includes('schema ') || line.includes('interface ')) {
|
|
723
|
-
models.push({
|
|
724
|
-
file: filePath,
|
|
725
|
-
code: line.trim(),
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
return models.slice(0, 5);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* Load template file
|
|
735
|
-
*/
|
|
736
|
-
async loadTemplate(templateName) {
|
|
737
|
-
const templatePath = path.join(this.templatesDir, templateName);
|
|
738
|
-
|
|
739
|
-
try {
|
|
740
|
-
return await fs.readFile(templatePath, 'utf-8');
|
|
741
|
-
} catch (error) {
|
|
742
|
-
console.warn(`Template not found: ${templateName}`);
|
|
743
|
-
return null;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* Update architecture.json with agent info
|
|
749
|
-
*/
|
|
750
|
-
async updateArchitectureConfig(agents) {
|
|
751
|
-
const configPath = path.join(this.projectPath, '.agentful/architecture.json');
|
|
752
|
-
|
|
753
|
-
let config = {};
|
|
754
|
-
|
|
755
|
-
try {
|
|
756
|
-
const content = await fs.readFile(configPath, 'utf-8');
|
|
757
|
-
config = JSON.parse(content);
|
|
758
|
-
} catch (error) {
|
|
759
|
-
// Config doesn't exist yet
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
config.agents = {
|
|
763
|
-
generated: {
|
|
764
|
-
core: agents.core.map(a => a.type),
|
|
765
|
-
domains: agents.domains.map(a => a.type),
|
|
766
|
-
tech: agents.tech.map(a => a.type),
|
|
767
|
-
},
|
|
768
|
-
generatedAt: new Date().toISOString(),
|
|
769
|
-
};
|
|
770
|
-
|
|
771
|
-
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
export default AgentGenerator;
|
|
776
|
-
|
|
777
|
-
// Export default configurations for use by consumers
|
|
778
|
-
export { DEFAULT_CORE_AGENT_TYPES, DEFAULT_AGENT_PATTERNS };
|