@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,515 +0,0 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import crypto from 'crypto';
4
- import { atomicWrite } from '../atomic.js';
5
-
6
- /**
7
- * Storage Manager
8
- *
9
- * Handles persistence of generated and custom agents with:
10
- * - Separation of generated vs custom agents
11
- * - Version control integration
12
- * - Metadata tracking
13
- * - Checksum validation
14
- * - User modification preservation
15
- */
16
- export class StorageManager {
17
- constructor(projectPath, options = {}) {
18
- this.projectPath = projectPath;
19
- this.options = {
20
- agentsDir: options.agentsDir || '.agentful/agents',
21
- preserveCustom: options.preserveCustom !== false,
22
- gitEnabled: options.gitEnabled !== false,
23
- ...options
24
- };
25
-
26
- this.agentsPath = path.join(projectPath, this.options.agentsDir);
27
- this.generatedPath = path.join(this.agentsPath, 'generated');
28
- this.customPath = path.join(this.agentsPath, 'custom');
29
- this.metadataPath = path.join(projectPath, '.agentful', 'metadata.json');
30
-
31
- this.metadata = null;
32
- }
33
-
34
- /**
35
- * Initialize storage system
36
- */
37
- async initialize() {
38
- await this._createDirectoryStructure();
39
- await this._loadMetadata();
40
- await this._updateGitignore();
41
- }
42
-
43
- /**
44
- * Save generated agents
45
- *
46
- * @param {Array} agents - Generated agents
47
- * @param {Object} options - Save options
48
- * @returns {Promise<void>}
49
- */
50
- async saveAgents(agents, options = {}) {
51
- // Preserve custom agents before clearing
52
- await this._preserveCustomAgents();
53
-
54
- // Clear generated directory
55
- await this._clearGeneratedDirectory();
56
-
57
- // Save each agent
58
- const savedAgents = [];
59
- for (const agent of agents) {
60
- const saved = await this._saveAgent(agent);
61
- savedAgents.push(saved);
62
- }
63
-
64
- // Update metadata
65
- await this._updateMetadata({
66
- lastGeneration: new Date().toISOString(),
67
- generatedAgents: savedAgents.map(a => a.name),
68
- customAgents: await this._listCustomAgents()
69
- });
70
-
71
- return savedAgents;
72
- }
73
-
74
- /**
75
- * Load agent by name
76
- *
77
- * @param {string} name - Agent name
78
- * @returns {Promise<Object|null>} Agent or null if not found
79
- */
80
- async loadAgent(name) {
81
- // Check custom first (takes precedence)
82
- const customAgent = await this._loadAgentFromPath(
83
- path.join(this.customPath, `${name}.md`)
84
- );
85
- if (customAgent) {
86
- return { ...customAgent, customized: true };
87
- }
88
-
89
- // Check generated
90
- const generatedAgent = await this._loadAgentFromPath(
91
- path.join(this.generatedPath, `${name}.md`)
92
- );
93
- if (generatedAgent) {
94
- return { ...generatedAgent, customized: false };
95
- }
96
-
97
- return null;
98
- }
99
-
100
- /**
101
- * List all agents (custom and generated)
102
- *
103
- * @returns {Promise<Array>} Agent list
104
- */
105
- async listAgents() {
106
- const agents = [];
107
-
108
- // List custom agents
109
- const customAgents = await this._listAgentsInDirectory(this.customPath, true);
110
- agents.push(...customAgents);
111
-
112
- // List generated agents (exclude if custom version exists)
113
- const generatedAgents = await this._listAgentsInDirectory(this.generatedPath, false);
114
- const customNames = new Set(customAgents.map(a => a.name));
115
-
116
- for (const agent of generatedAgents) {
117
- if (!customNames.has(agent.name)) {
118
- agents.push(agent);
119
- }
120
- }
121
-
122
- return agents;
123
- }
124
-
125
- /**
126
- * Check if agent has been customized
127
- *
128
- * @param {string} name - Agent name
129
- * @returns {Promise<boolean>} True if customized
130
- */
131
- async isCustomized(name) {
132
- const customPath = path.join(this.customPath, `${name}.md`);
133
-
134
- try {
135
- await fs.access(customPath);
136
- return true;
137
- } catch {
138
- return false;
139
- }
140
- }
141
-
142
- /**
143
- * Move agent to custom (mark as user-modified)
144
- *
145
- * @param {string} name - Agent name
146
- * @returns {Promise<void>}
147
- */
148
- async markAsCustom(name) {
149
- const generatedFile = path.join(this.generatedPath, `${name}.md`);
150
- const customFile = path.join(this.customPath, `${name}.md`);
151
-
152
- // Copy generated to custom
153
- try {
154
- const content = await fs.readFile(generatedFile, 'utf-8');
155
- await fs.writeFile(customFile, content, 'utf-8');
156
-
157
- // Update metadata
158
- await this._updateMetadata({
159
- customAgents: await this._listCustomAgents()
160
- });
161
- } catch (error) {
162
- throw new Error(`Failed to mark ${name} as custom: ${error.message}`);
163
- }
164
- }
165
-
166
- /**
167
- * Get agent statistics
168
- *
169
- * @returns {Promise<Object>} Statistics
170
- */
171
- async getStatistics() {
172
- const customAgents = await this._listCustomAgents();
173
- const generatedAgents = await this._listGeneratedAgents();
174
-
175
- return {
176
- total: customAgents.length + generatedAgents.length,
177
- custom: customAgents.length,
178
- generated: generatedAgents.length,
179
- customNames: customAgents,
180
- generatedNames: generatedAgents
181
- };
182
- }
183
-
184
- /**
185
- * Save individual agent
186
- *
187
- * @param {Object} agent - Agent data
188
- * @returns {Promise<Object>} Saved agent metadata
189
- * @private
190
- */
191
- async _saveAgent(agent) {
192
- const agentFile = path.join(this.generatedPath, `${agent.metadata.name}.md`);
193
- const metadataFile = path.join(this.generatedPath, `${agent.metadata.name}.json`);
194
-
195
- // Ensure directory exists
196
- await fs.mkdir(this.generatedPath, { recursive: true });
197
-
198
- // Write agent content
199
- await atomicWrite(agentFile, agent.content);
200
-
201
- // Write metadata
202
- const metadata = {
203
- ...agent.metadata,
204
- savedAt: new Date().toISOString(),
205
- checksum: this._calculateChecksum(agent.content)
206
- };
207
-
208
- await atomicWrite(metadataFile, JSON.stringify(metadata, null, 2));
209
-
210
- return metadata;
211
- }
212
-
213
- /**
214
- * Load agent from path
215
- *
216
- * @param {string} agentPath - Agent file path
217
- * @returns {Promise<Object|null>} Agent or null
218
- * @private
219
- */
220
- async _loadAgentFromPath(agentPath) {
221
- try {
222
- const content = await fs.readFile(agentPath, 'utf-8');
223
- const metadataPath = agentPath.replace('.md', '.json');
224
-
225
- let metadata = {};
226
- try {
227
- const metadataContent = await fs.readFile(metadataPath, 'utf-8');
228
- metadata = JSON.parse(metadataContent);
229
- } catch {
230
- // Metadata file doesn't exist, extract from frontmatter
231
- metadata = this._extractMetadataFromContent(content);
232
- }
233
-
234
- return {
235
- name: metadata.name || path.basename(agentPath, '.md'),
236
- content,
237
- metadata,
238
- path: agentPath
239
- };
240
- } catch (error) {
241
- if (error.code === 'ENOENT') {
242
- return null;
243
- }
244
- throw error;
245
- }
246
- }
247
-
248
- /**
249
- * List agents in directory
250
- *
251
- * @param {string} directory - Directory path
252
- * @param {boolean} customized - Whether agents are customized
253
- * @returns {Promise<Array>} Agent list
254
- * @private
255
- */
256
- async _listAgentsInDirectory(directory, customized) {
257
- const agents = [];
258
-
259
- try {
260
- const files = await fs.readdir(directory);
261
-
262
- for (const file of files) {
263
- if (file.endsWith('.md')) {
264
- const name = path.basename(file, '.md');
265
- const filePath = path.join(directory, file);
266
- const metadataPath = path.join(directory, `${name}.json`);
267
-
268
- let metadata = {};
269
- try {
270
- const content = await fs.readFile(metadataPath, 'utf-8');
271
- metadata = JSON.parse(content);
272
- } catch {
273
- // Metadata file doesn't exist
274
- }
275
-
276
- const stats = await fs.stat(filePath);
277
-
278
- agents.push({
279
- name,
280
- customized,
281
- path: filePath,
282
- lastModified: stats.mtime,
283
- ...metadata
284
- });
285
- }
286
- }
287
- } catch (error) {
288
- if (error.code !== 'ENOENT') {
289
- throw error;
290
- }
291
- // Directory doesn't exist, return empty array
292
- }
293
-
294
- return agents;
295
- }
296
-
297
- /**
298
- * Preserve custom agents before regeneration
299
- *
300
- * @private
301
- */
302
- async _preserveCustomAgents() {
303
- if (!this.options.preserveCustom) {
304
- return;
305
- }
306
-
307
- // Check if any generated agents have been modified
308
- try {
309
- const generatedAgents = await this._listAgentsInDirectory(this.generatedPath, false);
310
-
311
- for (const agent of generatedAgents) {
312
- const content = await fs.readFile(agent.path, 'utf-8');
313
- const currentChecksum = this._calculateChecksum(content);
314
-
315
- // Check if checksum differs from metadata (agent was modified)
316
- if (agent.checksum && currentChecksum !== agent.checksum) {
317
- // Move to custom directory
318
- const customFile = path.join(this.customPath, path.basename(agent.path));
319
- await fs.copyFile(agent.path, customFile);
320
-
321
- console.log(`Preserved modified agent: ${agent.name}`);
322
- }
323
- }
324
- } catch (error) {
325
- // Directory doesn't exist or other error, skip preservation
326
- }
327
- }
328
-
329
- /**
330
- * Clear generated directory
331
- *
332
- * @private
333
- */
334
- async _clearGeneratedDirectory() {
335
- try {
336
- const files = await fs.readdir(this.generatedPath);
337
-
338
- for (const file of files) {
339
- await fs.unlink(path.join(this.generatedPath, file));
340
- }
341
- } catch (error) {
342
- if (error.code !== 'ENOENT') {
343
- throw error;
344
- }
345
- // Directory doesn't exist, create it
346
- await fs.mkdir(this.generatedPath, { recursive: true });
347
- }
348
- }
349
-
350
- /**
351
- * List custom agent names
352
- *
353
- * @returns {Promise<Array>} Custom agent names
354
- * @private
355
- */
356
- async _listCustomAgents() {
357
- try {
358
- const files = await fs.readdir(this.customPath);
359
- return files.filter(f => f.endsWith('.md')).map(f => path.basename(f, '.md'));
360
- } catch {
361
- return [];
362
- }
363
- }
364
-
365
- /**
366
- * List generated agent names
367
- *
368
- * @returns {Promise<Array>} Generated agent names
369
- * @private
370
- */
371
- async _listGeneratedAgents() {
372
- try {
373
- const files = await fs.readdir(this.generatedPath);
374
- return files.filter(f => f.endsWith('.md')).map(f => path.basename(f, '.md'));
375
- } catch {
376
- return [];
377
- }
378
- }
379
-
380
- /**
381
- * Create directory structure
382
- *
383
- * @private
384
- */
385
- async _createDirectoryStructure() {
386
- const directories = [
387
- this.agentsPath,
388
- this.generatedPath,
389
- this.customPath,
390
- path.dirname(this.metadataPath)
391
- ];
392
-
393
- for (const dir of directories) {
394
- await fs.mkdir(dir, { recursive: true });
395
- }
396
- }
397
-
398
- /**
399
- * Load metadata
400
- *
401
- * @private
402
- */
403
- async _loadMetadata() {
404
- try {
405
- const content = await fs.readFile(this.metadataPath, 'utf-8');
406
- this.metadata = JSON.parse(content);
407
- } catch (error) {
408
- if (error.code === 'ENOENT') {
409
- // Initialize with defaults
410
- this.metadata = {
411
- version: '1.0.0',
412
- created: new Date().toISOString(),
413
- lastGeneration: null,
414
- generatedAgents: [],
415
- customAgents: []
416
- };
417
- } else {
418
- throw error;
419
- }
420
- }
421
- }
422
-
423
- /**
424
- * Update metadata
425
- *
426
- * @param {Object} updates - Metadata updates
427
- * @private
428
- */
429
- async _updateMetadata(updates) {
430
- this.metadata = {
431
- ...this.metadata,
432
- ...updates,
433
- lastUpdated: new Date().toISOString()
434
- };
435
-
436
- await atomicWrite(this.metadataPath, JSON.stringify(this.metadata, null, 2));
437
- }
438
-
439
- /**
440
- * Update .gitignore
441
- *
442
- * @private
443
- */
444
- async _updateGitignore() {
445
- if (!this.options.gitEnabled) {
446
- return;
447
- }
448
-
449
- const gitignorePath = path.join(this.projectPath, '.gitignore');
450
- const ignorePatterns = [
451
- '',
452
- '# agentful generated agents',
453
- '.agentful/agents/generated/',
454
- '.agentful/metadata.json'
455
- ];
456
-
457
- try {
458
- let content = '';
459
- try {
460
- content = await fs.readFile(gitignorePath, 'utf-8');
461
- } catch {
462
- // .gitignore doesn't exist
463
- }
464
-
465
- // Only add if not already present
466
- if (!content.includes('# agentful generated agents')) {
467
- content += '\n' + ignorePatterns.join('\n') + '\n';
468
- await fs.writeFile(gitignorePath, content, 'utf-8');
469
- }
470
- } catch (error) {
471
- // Ignore errors (non-git project, permissions, etc.)
472
- }
473
- }
474
-
475
- /**
476
- * Calculate checksum for content
477
- *
478
- * @param {string} content - Content to hash
479
- * @returns {string} Checksum
480
- * @private
481
- */
482
- _calculateChecksum(content) {
483
- return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
484
- }
485
-
486
- /**
487
- * Extract metadata from agent frontmatter
488
- *
489
- * @param {string} content - Agent content
490
- * @returns {Object} Metadata
491
- * @private
492
- */
493
- _extractMetadataFromContent(content) {
494
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
495
- if (!frontmatterMatch) {
496
- return {};
497
- }
498
-
499
- const frontmatter = frontmatterMatch[1];
500
- const metadata = {};
501
- const lines = frontmatter.split('\n');
502
-
503
- for (const line of lines) {
504
- const match = line.match(/^(\w+):\s*(.+)$/);
505
- if (match) {
506
- const [, key, value] = match;
507
- metadata[key] = value.trim();
508
- }
509
- }
510
-
511
- return metadata;
512
- }
513
- }
514
-
515
- export default StorageManager;