@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,440 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import fs from 'fs/promises';
3
- import path from 'path';
4
- import { EventEmitter } from 'events';
5
-
6
- /**
7
- * Agent Execution Runtime
8
- *
9
- * Handles the actual execution of AI agents:
10
- * - Agent invocation via subprocess or API
11
- * - Context passing between jobs
12
- * - Streaming logs and progress
13
- * - Cancellation and cleanup
14
- * - Result collection
15
- */
16
-
17
- /**
18
- * Agent Executor
19
- *
20
- * Executes agents and manages their lifecycle
21
- */
22
- export class AgentExecutor extends EventEmitter {
23
- constructor(options = {}) {
24
- super();
25
-
26
- this.options = {
27
- agentsDir: options.agentsDir || '.claude/agents',
28
- tempDir: options.tempDir || '.agentful/pipelines/temp',
29
- claudeCommand: options.claudeCommand || 'claude',
30
- streamLogs: options.streamLogs !== false,
31
- ...options
32
- };
33
-
34
- this.activeExecutions = new Map(); // executionId -> ExecutionContext
35
- }
36
-
37
- /**
38
- * Execute an agent
39
- *
40
- * @param {Object} jobDef - Job definition from pipeline
41
- * @param {Object} context - Execution context (inputs, variables)
42
- * @param {Object} options - Execution options
43
- * @returns {Promise<Object>} Execution result with output
44
- */
45
- async execute(jobDef, context, options = {}) {
46
- const executionId = this._generateExecutionId(jobDef.id);
47
-
48
- // Build execution context
49
- const execContext = {
50
- executionId,
51
- jobId: jobDef.id,
52
- agent: jobDef.agent,
53
- startTime: Date.now(),
54
- cancelled: false,
55
- process: null,
56
- output: '',
57
- error: '',
58
- exitCode: null
59
- };
60
-
61
- this.activeExecutions.set(executionId, execContext);
62
-
63
- try {
64
- // Prepare agent invocation
65
- const agentFile = await this._resolveAgentFile(jobDef.agent);
66
- const prompt = await this._buildAgentPrompt(jobDef, context);
67
-
68
- // Determine execution method
69
- const method = jobDef.execution?.method || 'subprocess';
70
-
71
- let result;
72
- if (method === 'subprocess') {
73
- result = await this._executeViaSubprocess(execContext, agentFile, prompt, context, options);
74
- } else if (method === 'api') {
75
- result = await this._executeViaAPI(execContext, jobDef, prompt, context, options);
76
- } else {
77
- throw new Error(`Unknown execution method: ${method}`);
78
- }
79
-
80
- return result;
81
-
82
- } finally {
83
- this.activeExecutions.delete(executionId);
84
- }
85
- }
86
-
87
- /**
88
- * Cancel an active execution
89
- *
90
- * @param {string} executionId - Execution ID to cancel
91
- * @returns {Promise<boolean>} True if cancelled, false if not found
92
- */
93
- async cancel(executionId) {
94
- const execContext = this.activeExecutions.get(executionId);
95
- if (!execContext) return false;
96
-
97
- execContext.cancelled = true;
98
-
99
- if (execContext.process) {
100
- // Kill the subprocess
101
- execContext.process.kill('SIGTERM');
102
-
103
- // Force kill after timeout
104
- setTimeout(() => {
105
- if (execContext.process && !execContext.process.killed) {
106
- execContext.process.kill('SIGKILL');
107
- }
108
- }, 5000);
109
- }
110
-
111
- return true;
112
- }
113
-
114
- /**
115
- * Get execution status
116
- *
117
- * @param {string} executionId - Execution ID
118
- * @returns {Object|null} Execution context or null if not found
119
- */
120
- getExecutionStatus(executionId) {
121
- return this.activeExecutions.get(executionId) || null;
122
- }
123
-
124
- /**
125
- * Internal: Execute via subprocess
126
- *
127
- * @private
128
- */
129
- async _executeViaSubprocess(execContext, agentFile, prompt, context, options) {
130
- // Write context to temp file
131
- const contextFile = await this._writeContextFile(execContext.executionId, context);
132
- const outputFile = path.join(this.options.tempDir, `${execContext.executionId}-output.json`);
133
-
134
- // Prepare agent prompt with context reference
135
- const fullPrompt = this._injectContextReference(prompt, contextFile, outputFile);
136
-
137
- // Read agent definition file
138
- const agentContent = await fs.readFile(agentFile, 'utf-8');
139
-
140
- // Combine agent definition with prompt
141
- const combinedPrompt = `${agentContent}\n\n---\n\n${fullPrompt}`;
142
-
143
- return new Promise((resolve, reject) => {
144
- // Spawn Claude Code with -p flag
145
- const args = ['-p', combinedPrompt];
146
-
147
- if (options.timeout) {
148
- args.push('--timeout', options.timeout.toString());
149
- }
150
-
151
- const proc = spawn(this.options.claudeCommand, args, {
152
- cwd: process.cwd(),
153
- env: { ...process.env }
154
- });
155
-
156
- execContext.process = proc;
157
-
158
- // Setup timeout
159
- let timeoutHandle;
160
- if (options.timeout) {
161
- timeoutHandle = setTimeout(() => {
162
- if (!execContext.cancelled) {
163
- proc.kill('SIGTERM');
164
- reject(new Error(`Agent execution timed out after ${options.timeout}ms`));
165
- }
166
- }, options.timeout);
167
- }
168
-
169
- // Capture stdout
170
- proc.stdout.on('data', (data) => {
171
- const text = data.toString();
172
- execContext.output += text;
173
-
174
- if (this.options.streamLogs && options.onLog) {
175
- options.onLog(text);
176
- }
177
-
178
- // Try to extract progress updates
179
- this._extractProgress(text, options.onProgress);
180
- });
181
-
182
- // Capture stderr
183
- proc.stderr.on('data', (data) => {
184
- const text = data.toString();
185
- execContext.error += text;
186
-
187
- if (this.options.streamLogs && options.onLog) {
188
- options.onLog(`[ERROR] ${text}`);
189
- }
190
- });
191
-
192
- // Handle process exit
193
- proc.on('close', async (code) => {
194
- if (timeoutHandle) clearTimeout(timeoutHandle);
195
-
196
- execContext.exitCode = code;
197
- execContext.process = null;
198
-
199
- try {
200
- // Read output file if exists
201
- let output = null;
202
- try {
203
- const outputContent = await fs.readFile(outputFile, 'utf-8');
204
- output = JSON.parse(outputContent);
205
- } catch (error) {
206
- // No output file or invalid JSON - use stdout
207
- output = { stdout: execContext.output };
208
- }
209
-
210
- // Cleanup temp files
211
- await this._cleanupTempFiles(execContext.executionId);
212
-
213
- if (execContext.cancelled) {
214
- reject(new Error('Execution was cancelled'));
215
- } else if (code === 0) {
216
- resolve({
217
- success: true,
218
- output,
219
- duration: Date.now() - execContext.startTime
220
- });
221
- } else {
222
- reject(new Error(`Agent failed with exit code ${code}: ${execContext.error}`));
223
- }
224
- } catch (error) {
225
- reject(error);
226
- }
227
- });
228
-
229
- proc.on('error', (error) => {
230
- if (timeoutHandle) clearTimeout(timeoutHandle);
231
- reject(new Error(`Failed to spawn agent: ${error.message}`));
232
- });
233
- });
234
- }
235
-
236
- /**
237
- * Internal: Execute via API
238
- *
239
- * @private
240
- */
241
- async _executeViaAPI(execContext, jobDef, prompt, context, options) {
242
- // This would integrate with Claude API directly
243
- // For now, throw error indicating this needs to be implemented
244
-
245
- throw new Error('API execution not yet implemented. Use subprocess method.');
246
-
247
- // Future implementation would look like:
248
- // const response = await fetch('https://api.anthropic.com/v1/messages', {
249
- // method: 'POST',
250
- // headers: { ... },
251
- // body: JSON.stringify({ ... })
252
- // });
253
- }
254
-
255
- /**
256
- * Internal: Resolve agent file path
257
- *
258
- * @private
259
- */
260
- async _resolveAgentFile(agentName) {
261
- // Try with .md extension
262
- let agentFile = path.join(this.options.agentsDir, `${agentName}.md`);
263
-
264
- try {
265
- await fs.access(agentFile);
266
- return agentFile;
267
- } catch (error) {
268
- // Try without extension
269
- agentFile = path.join(this.options.agentsDir, agentName);
270
-
271
- try {
272
- await fs.access(agentFile);
273
- return agentFile;
274
- } catch (error) {
275
- throw new Error(`Agent not found: ${agentName}`);
276
- }
277
- }
278
- }
279
-
280
- /**
281
- * Internal: Build agent prompt from job definition
282
- *
283
- * @private
284
- */
285
- async _buildAgentPrompt(jobDef, context) {
286
- let prompt = jobDef.prompt || '';
287
-
288
- // Replace variables in prompt
289
- prompt = this._interpolateVariables(prompt, context);
290
-
291
- // Add task description
292
- if (jobDef.task) {
293
- prompt = `${jobDef.task}\n\n${prompt}`;
294
- }
295
-
296
- return prompt;
297
- }
298
-
299
- /**
300
- * Internal: Interpolate variables in text
301
- *
302
- * @private
303
- */
304
- _interpolateVariables(text, context) {
305
- return text.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (match, path) => {
306
- const value = this._getNestedValue(context, path);
307
- return value !== undefined ? value : match;
308
- });
309
- }
310
-
311
- /**
312
- * Internal: Get nested value from object
313
- *
314
- * @private
315
- */
316
- _getNestedValue(obj, path) {
317
- return path.split('.').reduce((current, prop) => {
318
- return current && current[prop] !== undefined ? current[prop] : undefined;
319
- }, obj);
320
- }
321
-
322
- /**
323
- * Internal: Inject context reference into prompt
324
- *
325
- * @private
326
- */
327
- _injectContextReference(prompt, contextFile, outputFile) {
328
- return `
329
- # Pipeline Job Context
330
-
331
- Input context is available at: ${contextFile}
332
- Write your output to: ${outputFile}
333
-
334
- Output format (JSON):
335
- \`\`\`json
336
- {
337
- "success": true,
338
- "data": { ... },
339
- "message": "Job completed successfully"
340
- }
341
- \`\`\`
342
-
343
- ---
344
-
345
- # Task
346
-
347
- ${prompt}
348
- `;
349
- }
350
-
351
- /**
352
- * Internal: Write context to temp file
353
- *
354
- * @private
355
- */
356
- async _writeContextFile(executionId, context) {
357
- const contextFile = path.join(this.options.tempDir, `${executionId}-context.json`);
358
- await fs.mkdir(this.options.tempDir, { recursive: true });
359
- await fs.writeFile(contextFile, JSON.stringify(context, null, 2));
360
- return contextFile;
361
- }
362
-
363
- /**
364
- * Internal: Write prompt to temp file
365
- *
366
- * @private
367
- */
368
- async _writePromptFile(executionId, prompt) {
369
- const promptFile = path.join(this.options.tempDir, `${executionId}-prompt.txt`);
370
- await fs.mkdir(this.options.tempDir, { recursive: true });
371
- await fs.writeFile(promptFile, prompt);
372
- return promptFile;
373
- }
374
-
375
-
376
- /**
377
- * Internal: Cleanup temp files
378
- *
379
- * @private
380
- */
381
- async _cleanupTempFiles(executionId) {
382
- const files = [
383
- path.join(this.options.tempDir, `${executionId}-context.json`),
384
- path.join(this.options.tempDir, `${executionId}-output.json`),
385
- path.join(this.options.tempDir, `${executionId}-prompt.txt`)
386
- ];
387
-
388
- for (const file of files) {
389
- try {
390
- await fs.unlink(file);
391
- } catch (error) {
392
- // Ignore errors - file might not exist
393
- }
394
- }
395
- }
396
-
397
- /**
398
- * Internal: Extract progress from output
399
- *
400
- * @private
401
- */
402
- _extractProgress(text, onProgress) {
403
- if (!onProgress) return;
404
-
405
- // Look for progress markers: [PROGRESS: 45%] or similar
406
- const progressMatch = text.match(/\[PROGRESS:\s*(\d+)%\]/i);
407
- if (progressMatch) {
408
- const progress = parseInt(progressMatch[1], 10);
409
- onProgress(progress);
410
- return;
411
- }
412
-
413
- // Look for task completion markers
414
- if (text.includes('Task completed') || text.includes('✓')) {
415
- onProgress(100);
416
- }
417
- }
418
-
419
- /**
420
- * Internal: Generate execution ID
421
- *
422
- * @private
423
- */
424
- _generateExecutionId(jobId) {
425
- const timestamp = Date.now();
426
- const random = Math.random().toString(36).substring(2, 9);
427
- return `${jobId}-${timestamp}-${random}`;
428
- }
429
- }
430
-
431
- /**
432
- * Agent Executor Factory
433
- *
434
- * Creates agent executor with custom configuration
435
- */
436
- export function createAgentExecutor(options = {}) {
437
- return new AgentExecutor(options);
438
- }
439
-
440
- export default AgentExecutor;
@@ -1,33 +0,0 @@
1
- /**
2
- * Pipeline Orchestration System
3
- *
4
- * Entry point for agentful pipeline orchestration
5
- */
6
-
7
- export { PipelineEngine, JobStatus, PipelineStatus } from './engine.js';
8
- export { AgentExecutor, createAgentExecutor } from './executor.js';
9
- export {
10
- GitHubActionsAdapter,
11
- GitLabCIAdapter,
12
- JenkinsAdapter,
13
- WebhookHandler
14
- } from './integrations.js';
15
- export { pipelineSchema, validatePipeline } from './schemas.js';
16
-
17
- // Re-export for convenience
18
- export * from './engine.js';
19
- export * from './executor.js';
20
- export * from './integrations.js';
21
- export * from './schemas.js';
22
-
23
- export default {
24
- PipelineEngine,
25
- AgentExecutor,
26
- createAgentExecutor,
27
- GitHubActionsAdapter,
28
- GitLabCIAdapter,
29
- JenkinsAdapter,
30
- WebhookHandler,
31
- pipelineSchema,
32
- validatePipeline
33
- };