@itz4blitz/agentful 1.2.0 → 1.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.
Files changed (59) hide show
  1. package/README.md +28 -1
  2. package/bin/cli.js +11 -1055
  3. package/bin/hooks/block-file-creation.js +271 -0
  4. package/bin/hooks/product-spec-watcher.js +151 -0
  5. package/lib/index.js +0 -11
  6. package/lib/init.js +2 -21
  7. package/lib/parallel-execution.js +235 -0
  8. package/lib/presets.js +26 -4
  9. package/package.json +4 -7
  10. package/template/.claude/agents/architect.md +2 -2
  11. package/template/.claude/agents/backend.md +17 -30
  12. package/template/.claude/agents/frontend.md +17 -39
  13. package/template/.claude/agents/orchestrator.md +63 -4
  14. package/template/.claude/agents/product-analyzer.md +1 -1
  15. package/template/.claude/agents/tester.md +16 -29
  16. package/template/.claude/commands/agentful-generate.md +221 -14
  17. package/template/.claude/commands/agentful-init.md +621 -0
  18. package/template/.claude/commands/agentful-product.md +1 -1
  19. package/template/.claude/commands/agentful-start.md +99 -1
  20. package/template/.claude/product/EXAMPLES.md +2 -2
  21. package/template/.claude/product/index.md +1 -1
  22. package/template/.claude/settings.json +22 -0
  23. package/template/.claude/skills/research/SKILL.md +432 -0
  24. package/template/CLAUDE.md +5 -6
  25. package/template/bin/hooks/architect-drift-detector.js +242 -0
  26. package/template/bin/hooks/product-spec-watcher.js +151 -0
  27. package/version.json +1 -1
  28. package/bin/hooks/post-agent.js +0 -101
  29. package/bin/hooks/post-feature.js +0 -227
  30. package/bin/hooks/pre-agent.js +0 -118
  31. package/bin/hooks/pre-feature.js +0 -138
  32. package/lib/VALIDATION_README.md +0 -455
  33. package/lib/ci/claude-action-integration.js +0 -641
  34. package/lib/ci/index.js +0 -10
  35. package/lib/core/analyzer.js +0 -497
  36. package/lib/core/cli.js +0 -141
  37. package/lib/core/detectors/conventions.js +0 -342
  38. package/lib/core/detectors/framework.js +0 -276
  39. package/lib/core/detectors/index.js +0 -15
  40. package/lib/core/detectors/language.js +0 -199
  41. package/lib/core/detectors/patterns.js +0 -356
  42. package/lib/core/generator.js +0 -626
  43. package/lib/core/index.js +0 -9
  44. package/lib/core/output-parser.js +0 -458
  45. package/lib/core/storage.js +0 -515
  46. package/lib/core/templates.js +0 -556
  47. package/lib/pipeline/cli.js +0 -423
  48. package/lib/pipeline/engine.js +0 -928
  49. package/lib/pipeline/executor.js +0 -440
  50. package/lib/pipeline/index.js +0 -33
  51. package/lib/pipeline/integrations.js +0 -559
  52. package/lib/pipeline/schemas.js +0 -288
  53. package/lib/remote/client.js +0 -361
  54. package/lib/server/auth.js +0 -270
  55. package/lib/server/client-example.js +0 -190
  56. package/lib/server/executor.js +0 -477
  57. package/lib/server/index.js +0 -494
  58. package/lib/update-helpers.js +0 -505
  59. package/lib/validation.js +0 -460
@@ -1,626 +0,0 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
- import { createRequire } from 'module';
5
- import Handlebars from 'handlebars';
6
- import { TemplateManager } from './templates.js';
7
- import { StorageManager } from './storage.js';
8
-
9
- const require = createRequire(import.meta.url);
10
-
11
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
-
13
- /**
14
- * Agent Generator
15
- *
16
- * Converts architecture analysis into specialized agents using templates.
17
- * Supports Handlebars templating with custom helpers for code injection.
18
- *
19
- * @example
20
- * const generator = new AgentGenerator('/path/to/project');
21
- * await generator.initialize();
22
- * const agents = await generator.generateAgents();
23
- */
24
- export class AgentGenerator {
25
- constructor(projectPath, options = {}) {
26
- this.projectPath = projectPath;
27
- this.options = {
28
- templateDir: options.templateDir || path.join(__dirname, '../../templates'),
29
- agentsDir: options.agentsDir || '.agentful/agents',
30
- preserveCustom: options.preserveCustom !== false,
31
- validateOutput: options.validateOutput !== false,
32
- ...options
33
- };
34
-
35
- this.templateManager = null;
36
- this.storageManager = null;
37
- this.architecture = null;
38
- this.handlebars = Handlebars.create();
39
-
40
- this._registerHelpers();
41
- }
42
-
43
- /**
44
- * Initialize generator
45
- */
46
- async initialize() {
47
- // Initialize template manager
48
- this.templateManager = new TemplateManager(this.projectPath, this.options.templateDir);
49
- await this.templateManager.initialize();
50
-
51
- // Initialize storage manager
52
- this.storageManager = new StorageManager(this.projectPath, {
53
- agentsDir: this.options.agentsDir,
54
- preserveCustom: this.options.preserveCustom
55
- });
56
- await this.storageManager.initialize();
57
-
58
- // Load architecture analysis
59
- this.architecture = await this._loadArchitecture();
60
-
61
- if (!this.architecture) {
62
- throw new Error('Architecture analysis not found. Run /agentful-analyze first.');
63
- }
64
- }
65
-
66
- /**
67
- * Generate agents from architecture analysis
68
- *
69
- * @returns {Promise<{agents: Array, duration: number, generated: number}>}
70
- */
71
- async generateAgents() {
72
- const startTime = Date.now();
73
- const agents = [];
74
-
75
- // Determine which agents to generate based on architecture
76
- const agentSpecs = this._determineAgentsToGenerate();
77
-
78
- // Generate all agents in parallel
79
- const agentPromises = agentSpecs.map(async (spec) => {
80
- try {
81
- return await this._generateAgent(spec);
82
- } catch (error) {
83
- console.error(`Failed to generate ${spec.name} agent:`, error.message);
84
- return null;
85
- }
86
- });
87
-
88
- const results = await Promise.all(agentPromises);
89
- agents.push(...results.filter(agent => agent !== null));
90
-
91
- // Validate generated agents
92
- if (this.options.validateOutput) {
93
- await this._validateAgents(agents);
94
- }
95
-
96
- // Save agents to storage
97
- await this.storageManager.saveAgents(agents);
98
-
99
- const duration = Date.now() - startTime;
100
-
101
- return {
102
- agents,
103
- generated: agents.length,
104
- duration,
105
- performance: duration < 5000 ? 'PASS' : 'SLOW'
106
- };
107
- }
108
-
109
- /**
110
- * Generate a single agent
111
- *
112
- * @param {Object} spec - Agent specification
113
- * @returns {Promise<Object>} Generated agent
114
- * @private
115
- */
116
- async _generateAgent(spec) {
117
- // Load appropriate template
118
- const template = await this.templateManager.loadTemplate(spec.template);
119
-
120
- // Prepare context for template compilation
121
- const context = await this._buildContext(spec);
122
-
123
- // Compile template
124
- const content = this._compileTemplate(template, context);
125
-
126
- // Extract code examples from codebase
127
- const examples = await this._extractCodeExamples(spec);
128
-
129
- // Inject examples into content
130
- const enhancedContent = this._injectExamples(content, examples);
131
-
132
- // Create agent metadata
133
- const metadata = {
134
- name: spec.name,
135
- version: '1.0.0',
136
- template: spec.template,
137
- generated: new Date().toISOString(),
138
- customized: false,
139
- checksum: this._calculateChecksum(enhancedContent),
140
- architecture: {
141
- framework: spec.framework,
142
- language: spec.language,
143
- patterns: spec.patterns || []
144
- }
145
- };
146
-
147
- return {
148
- metadata,
149
- content: enhancedContent,
150
- configuration: spec.configuration || {}
151
- };
152
- }
153
-
154
- /**
155
- * Determine which agents to generate based on architecture
156
- *
157
- * @returns {Array} Agent specifications
158
- * @private
159
- */
160
- _determineAgentsToGenerate() {
161
- const specs = [];
162
- const { techStack, patterns, projectType } = this.architecture;
163
-
164
- // Always generate core agents
165
- specs.push(
166
- { name: 'orchestrator', template: 'base/orchestrator' },
167
- { name: 'architect', template: 'base/architect' },
168
- { name: 'reviewer', template: 'base/reviewer' },
169
- { name: 'fixer', template: 'base/fixer' },
170
- { name: 'tester', template: 'base/tester' }
171
- );
172
-
173
- // Generate backend agent if server-side code detected
174
- if (techStack.backend || patterns.includes('backend')) {
175
- specs.push({
176
- name: 'backend',
177
- template: this._selectBackendTemplate(),
178
- framework: techStack.backend?.framework,
179
- language: techStack.backend?.language || techStack.language,
180
- patterns: patterns.filter(p => p.includes('backend') || p.includes('api'))
181
- });
182
- }
183
-
184
- // Generate frontend agent if client-side code detected
185
- if (techStack.frontend || patterns.includes('frontend')) {
186
- specs.push({
187
- name: 'frontend',
188
- template: this._selectFrontendTemplate(),
189
- framework: techStack.frontend?.framework,
190
- language: techStack.frontend?.language || techStack.language,
191
- patterns: patterns.filter(p => p.includes('frontend') || p.includes('ui'))
192
- });
193
- }
194
-
195
- // Generate database agent if database detected
196
- if (techStack.database) {
197
- specs.push({
198
- name: 'database',
199
- template: this._selectDatabaseTemplate(),
200
- database: techStack.database
201
- });
202
- }
203
-
204
- // Generate framework-specific agents
205
- if (techStack.framework) {
206
- const frameworkAgent = this._createFrameworkAgent(techStack.framework);
207
- if (frameworkAgent) {
208
- specs.push(frameworkAgent);
209
- }
210
- }
211
-
212
- return specs;
213
- }
214
-
215
- /**
216
- * Select backend template based on framework
217
- *
218
- * @returns {string} Template path
219
- * @private
220
- */
221
- _selectBackendTemplate() {
222
- const framework = this.architecture.techStack.backend?.framework;
223
-
224
- const frameworkTemplates = {
225
- 'express': 'frameworks/express-backend',
226
- 'fastify': 'frameworks/fastify-backend',
227
- 'nestjs': 'frameworks/nestjs-backend',
228
- 'koa': 'frameworks/koa-backend',
229
- 'django': 'frameworks/django-backend',
230
- 'flask': 'frameworks/flask-backend',
231
- 'spring': 'frameworks/spring-backend'
232
- };
233
-
234
- return frameworkTemplates[framework] || 'base/backend';
235
- }
236
-
237
- /**
238
- * Select frontend template based on framework
239
- *
240
- * @returns {string} Template path
241
- * @private
242
- */
243
- _selectFrontendTemplate() {
244
- const framework = this.architecture.techStack.frontend?.framework;
245
-
246
- const frameworkTemplates = {
247
- 'react': 'frameworks/react-frontend',
248
- 'vue': 'frameworks/vue-frontend',
249
- 'angular': 'frameworks/angular-frontend',
250
- 'svelte': 'frameworks/svelte-frontend',
251
- 'nextjs': 'frameworks/nextjs-frontend',
252
- 'nuxt': 'frameworks/nuxt-frontend'
253
- };
254
-
255
- return frameworkTemplates[framework] || 'base/frontend';
256
- }
257
-
258
- /**
259
- * Select database template based on database type
260
- *
261
- * @returns {string} Template path
262
- * @private
263
- */
264
- _selectDatabaseTemplate() {
265
- const dbType = this.architecture.techStack.database?.type;
266
-
267
- const dbTemplates = {
268
- 'postgresql': 'patterns/postgresql',
269
- 'mysql': 'patterns/mysql',
270
- 'mongodb': 'patterns/mongodb',
271
- 'sqlite': 'patterns/sqlite',
272
- 'redis': 'patterns/redis'
273
- };
274
-
275
- return dbTemplates[dbType] || 'patterns/database';
276
- }
277
-
278
- /**
279
- * Create framework-specific agent
280
- *
281
- * @param {string} framework - Framework name
282
- * @returns {Object|null} Agent spec or null
283
- * @private
284
- */
285
- _createFrameworkAgent(framework) {
286
- const frameworkAgents = {
287
- 'nextjs': {
288
- name: 'nextjs-specialist',
289
- template: 'frameworks/nextjs',
290
- framework: 'nextjs'
291
- },
292
- 'nestjs': {
293
- name: 'nestjs-specialist',
294
- template: 'frameworks/nestjs',
295
- framework: 'nestjs'
296
- }
297
- };
298
-
299
- return frameworkAgents[framework] || null;
300
- }
301
-
302
- /**
303
- * Build context for template compilation
304
- *
305
- * @param {Object} spec - Agent specification
306
- * @returns {Promise<Object>} Template context
307
- * @private
308
- */
309
- async _buildContext(spec) {
310
- const context = {
311
- projectName: this.architecture.projectName || path.basename(this.projectPath),
312
- language: spec.language || this.architecture.techStack.language,
313
- framework: spec.framework,
314
- projectType: this.architecture.projectType,
315
- techStack: this.architecture.techStack,
316
- patterns: spec.patterns || [],
317
- conventions: this.architecture.conventions || {},
318
- timestamp: new Date().toISOString()
319
- };
320
-
321
- // Add framework-specific context
322
- if (spec.framework) {
323
- context.frameworkConfig = await this._extractFrameworkConfig(spec.framework);
324
- }
325
-
326
- // Add database context
327
- if (spec.database) {
328
- context.database = spec.database;
329
- context.orm = this.architecture.techStack.orm;
330
- }
331
-
332
- return context;
333
- }
334
-
335
- /**
336
- * Compile template with Handlebars
337
- *
338
- * @param {string} template - Template content
339
- * @param {Object} context - Template context
340
- * @returns {string} Compiled content
341
- * @private
342
- */
343
- _compileTemplate(template, context) {
344
- try {
345
- const compiled = this.handlebars.compile(template);
346
- return compiled(context);
347
- } catch (error) {
348
- throw new Error(`Template compilation failed: ${error.message}`);
349
- }
350
- }
351
-
352
- /**
353
- * Extract code examples from analyzed codebase
354
- *
355
- * @param {Object} spec - Agent specification
356
- * @returns {Promise<Object>} Code examples
357
- * @private
358
- */
359
- async _extractCodeExamples(spec) {
360
- const examples = {
361
- patterns: [],
362
- conventions: [],
363
- bestPractices: []
364
- };
365
-
366
- // Extract patterns from architecture analysis
367
- if (this.architecture.codeExamples) {
368
- const relevantExamples = this.architecture.codeExamples.filter(ex => {
369
- return ex.category === spec.name || ex.tags?.includes(spec.name);
370
- });
371
-
372
- examples.patterns = relevantExamples.slice(0, 3); // Limit to 3 examples
373
- }
374
-
375
- // Extract conventions
376
- if (this.architecture.conventions) {
377
- const conventions = this.architecture.conventions[spec.name];
378
- if (conventions) {
379
- examples.conventions = Array.isArray(conventions)
380
- ? conventions
381
- : [conventions];
382
- }
383
- }
384
-
385
- return examples;
386
- }
387
-
388
- /**
389
- * Inject code examples into agent content
390
- *
391
- * @param {string} content - Agent content
392
- * @param {Object} examples - Code examples
393
- * @returns {string} Enhanced content
394
- * @private
395
- */
396
- _injectExamples(content, examples) {
397
- let enhanced = content;
398
-
399
- // Add examples section if we have examples
400
- if (examples.patterns.length > 0) {
401
- const examplesSection = this._buildExamplesSection(examples);
402
-
403
- // Inject before the last section (usually "Rules" or similar)
404
- const lastHeadingIndex = enhanced.lastIndexOf('\n## ');
405
- if (lastHeadingIndex !== -1) {
406
- enhanced =
407
- enhanced.slice(0, lastHeadingIndex) +
408
- '\n\n' + examplesSection +
409
- enhanced.slice(lastHeadingIndex);
410
- } else {
411
- enhanced += '\n\n' + examplesSection;
412
- }
413
- }
414
-
415
- return enhanced;
416
- }
417
-
418
- /**
419
- * Build examples section
420
- *
421
- * @param {Object} examples - Code examples
422
- * @returns {string} Examples markdown
423
- * @private
424
- */
425
- _buildExamplesSection(examples) {
426
- const sections = [];
427
-
428
- sections.push('## Code Examples from This Project\n');
429
- sections.push('These examples show how THIS specific project implements patterns:\n');
430
-
431
- for (const example of examples.patterns) {
432
- sections.push(`### ${example.title || 'Example'}\n`);
433
- sections.push(`**File**: \`${example.file}\`\n`);
434
-
435
- if (example.description) {
436
- sections.push(`${example.description}\n`);
437
- }
438
-
439
- sections.push('```' + (example.language || '') + '\n');
440
- sections.push(example.code + '\n');
441
- sections.push('```\n');
442
- }
443
-
444
- if (examples.conventions.length > 0) {
445
- sections.push('### Project Conventions\n');
446
- for (const convention of examples.conventions) {
447
- sections.push(`- ${convention}\n`);
448
- }
449
- }
450
-
451
- return sections.join('\n');
452
- }
453
-
454
- /**
455
- * Validate generated agents
456
- *
457
- * @param {Array} agents - Generated agents
458
- * @returns {Promise<void>}
459
- * @private
460
- */
461
- async _validateAgents(agents) {
462
- const errors = [];
463
-
464
- for (const agent of agents) {
465
- // Validate frontmatter
466
- const frontmatterMatch = agent.content.match(/^---\n([\s\S]*?)\n---/);
467
- if (!frontmatterMatch) {
468
- errors.push(`${agent.metadata.name}: Missing frontmatter`);
469
- continue;
470
- }
471
-
472
- // Validate required frontmatter fields
473
- const frontmatter = frontmatterMatch[1];
474
- const requiredFields = ['name', 'description', 'model', 'tools'];
475
-
476
- for (const field of requiredFields) {
477
- if (!frontmatter.includes(`${field}:`)) {
478
- errors.push(`${agent.metadata.name}: Missing required field '${field}' in frontmatter`);
479
- }
480
- }
481
-
482
- // Validate structure (should have at least one heading)
483
- if (!agent.content.includes('# ')) {
484
- errors.push(`${agent.metadata.name}: No main heading found`);
485
- }
486
-
487
- // Validate no broken references
488
- const references = agent.content.match(/@\w+/g) || [];
489
- for (const ref of references) {
490
- const agentName = ref.slice(1);
491
- // Check if referenced agent exists in generated set or is a known agent
492
- const exists = agents.some(a => a.metadata.name === agentName) ||
493
- ['orchestrator', 'architect', 'backend', 'frontend', 'reviewer', 'fixer', 'tester'].includes(agentName);
494
-
495
- if (!exists) {
496
- console.warn(`${agent.metadata.name}: Reference to unknown agent: ${ref}`);
497
- }
498
- }
499
- }
500
-
501
- if (errors.length > 0) {
502
- throw new Error(`Agent validation failed:\n${errors.join('\n')}`);
503
- }
504
- }
505
-
506
- /**
507
- * Load architecture analysis
508
- *
509
- * @returns {Promise<Object|null>} Architecture data
510
- * @private
511
- */
512
- async _loadArchitecture() {
513
- const architecturePath = path.join(this.projectPath, '.agentful', 'architecture.json');
514
-
515
- try {
516
- const content = await fs.readFile(architecturePath, 'utf-8');
517
- return JSON.parse(content);
518
- } catch (error) {
519
- if (error.code === 'ENOENT') {
520
- return null;
521
- }
522
- throw error;
523
- }
524
- }
525
-
526
- /**
527
- * Extract framework configuration
528
- *
529
- * @param {string} framework - Framework name
530
- * @returns {Promise<Object>} Framework config
531
- * @private
532
- */
533
- async _extractFrameworkConfig(framework) {
534
- const config = {};
535
-
536
- // Try to load framework config files
537
- const configFiles = {
538
- 'nextjs': 'next.config.js',
539
- 'nestjs': 'nest-cli.json',
540
- 'vue': 'vue.config.js',
541
- 'react': 'react-app-env.d.ts'
542
- };
543
-
544
- const configFile = configFiles[framework];
545
- if (configFile) {
546
- const configPath = path.join(this.projectPath, configFile);
547
- try {
548
- await fs.access(configPath);
549
- config.configFile = configFile;
550
- config.hasConfig = true;
551
- } catch {
552
- config.hasConfig = false;
553
- }
554
- }
555
-
556
- return config;
557
- }
558
-
559
- /**
560
- * Calculate checksum for content
561
- *
562
- * @param {string} content - Content to hash
563
- * @returns {string} Checksum
564
- * @private
565
- */
566
- _calculateChecksum(content) {
567
- const crypto = require('crypto');
568
- return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
569
- }
570
-
571
- /**
572
- * Register Handlebars helpers
573
- *
574
- * @private
575
- */
576
- _registerHelpers() {
577
- // Helper: Capitalize first letter
578
- this.handlebars.registerHelper('capitalize', (str) => {
579
- return str ? str.charAt(0).toUpperCase() + str.slice(1) : '';
580
- });
581
-
582
- // Helper: Uppercase
583
- this.handlebars.registerHelper('uppercase', (str) => {
584
- return str ? str.toUpperCase() : '';
585
- });
586
-
587
- // Helper: Code block
588
- this.handlebars.registerHelper('codeBlock', (code, language) => {
589
- return `\`\`\`${language || ''}\n${code}\n\`\`\``;
590
- });
591
-
592
- // Helper: List items
593
- this.handlebars.registerHelper('list', (items) => {
594
- if (!Array.isArray(items)) return '';
595
- return items.map(item => `- ${item}`).join('\n');
596
- });
597
-
598
- // Helper: Join array
599
- this.handlebars.registerHelper('join', (arr, separator) => {
600
- if (!Array.isArray(arr)) return '';
601
- return arr.join(separator || ', ');
602
- });
603
-
604
- // Helper: Conditional equality
605
- this.handlebars.registerHelper('eq', (a, b) => {
606
- return a === b;
607
- });
608
-
609
- // Helper: Conditional includes
610
- this.handlebars.registerHelper('includes', (arr, item) => {
611
- return Array.isArray(arr) && arr.includes(item);
612
- });
613
-
614
- // Helper: Format date
615
- this.handlebars.registerHelper('formatDate', (date) => {
616
- return new Date(date).toISOString().split('T')[0];
617
- });
618
-
619
- // Helper: JSON stringify
620
- this.handlebars.registerHelper('json', (obj) => {
621
- return JSON.stringify(obj, null, 2);
622
- });
623
- }
624
- }
625
-
626
- export default AgentGenerator;
package/lib/core/index.js DELETED
@@ -1,9 +0,0 @@
1
- /**
2
- * agentful Core Module
3
- *
4
- * Agent generation system with template management and storage.
5
- */
6
-
7
- export { AgentGenerator } from './generator.js';
8
- export { TemplateManager } from './templates.js';
9
- export { StorageManager } from './storage.js';