@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,494 +0,0 @@
1
- /**
2
- * Agentful Remote Execution Server
3
- *
4
- * Secure HTTP server for remote agent execution with multiple authentication modes.
5
- *
6
- * @module server
7
- */
8
-
9
- import http from 'http';
10
- import https from 'https';
11
- import { readFileSync } from 'fs';
12
- import { createAuthMiddleware, captureRawBody } from './auth.js';
13
- import {
14
- executeAgent,
15
- getExecutionStatus,
16
- listExecutions,
17
- startPeriodicCleanup,
18
- } from './executor.js';
19
- import { listAvailableAgents } from '../ci/claude-action-integration.js';
20
-
21
- /**
22
- * Rate limiting configuration
23
- */
24
- const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute
25
- const RATE_LIMIT_MAX_REQUESTS = 60; // 60 requests per minute per IP
26
-
27
- /**
28
- * In-memory rate limit store
29
- */
30
- const rateLimitStore = new Map();
31
-
32
- /**
33
- * Clean up old rate limit entries periodically (every 2 minutes)
34
- */
35
- setInterval(() => {
36
- const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS;
37
- for (const [ip, data] of rateLimitStore.entries()) {
38
- if (data.windowStart < cutoff) {
39
- rateLimitStore.delete(ip);
40
- }
41
- }
42
- }, 2 * 60 * 1000);
43
-
44
- /**
45
- * Rate limiting middleware
46
- * @param {Object} req - Request object
47
- * @param {Object} res - Response object
48
- * @returns {boolean} True if request is allowed
49
- */
50
- function checkRateLimit(req, res) {
51
- const clientIP = req.socket.remoteAddress;
52
- const now = Date.now();
53
-
54
- let rateLimitData = rateLimitStore.get(clientIP);
55
-
56
- // Initialize or reset window
57
- if (!rateLimitData || now - rateLimitData.windowStart > RATE_LIMIT_WINDOW_MS) {
58
- rateLimitData = {
59
- windowStart: now,
60
- requestCount: 0,
61
- };
62
- rateLimitStore.set(clientIP, rateLimitData);
63
- }
64
-
65
- // Increment request count
66
- rateLimitData.requestCount++;
67
-
68
- // Check if limit exceeded
69
- if (rateLimitData.requestCount > RATE_LIMIT_MAX_REQUESTS) {
70
- res.writeHead(429, {
71
- 'Content-Type': 'application/json',
72
- 'Retry-After': Math.ceil(RATE_LIMIT_WINDOW_MS / 1000),
73
- });
74
- res.end(
75
- JSON.stringify({
76
- error: 'Too Many Requests',
77
- message: `Rate limit exceeded. Maximum ${RATE_LIMIT_MAX_REQUESTS} requests per minute.`,
78
- retryAfter: Math.ceil(RATE_LIMIT_WINDOW_MS / 1000),
79
- })
80
- );
81
- return false;
82
- }
83
-
84
- return true;
85
- }
86
-
87
- /**
88
- * Parse JSON body manually (to support raw body capture)
89
- * @param {string} rawBody - Raw request body
90
- * @returns {Object} Parsed JSON
91
- */
92
- function parseJSON(rawBody) {
93
- try {
94
- return JSON.parse(rawBody);
95
- } catch (error) {
96
- throw new Error('Invalid JSON body');
97
- }
98
- }
99
-
100
- /**
101
- * Simple request router
102
- * @param {string} method - HTTP method
103
- * @param {string} path - Request path
104
- * @param {Function} handler - Route handler
105
- * @returns {Object} Route definition
106
- */
107
- function route(method, path, handler) {
108
- return { method, path, handler };
109
- }
110
-
111
- /**
112
- * Match route against request
113
- * @param {Object} route - Route definition
114
- * @param {string} method - Request method
115
- * @param {string} path - Request path
116
- * @returns {Object|null} Match result with params or null
117
- */
118
- function matchRoute(route, method, path) {
119
- if (route.method !== method) {
120
- return null;
121
- }
122
-
123
- // Simple path matching with :param support
124
- const routeParts = route.path.split('/');
125
- const pathParts = path.split('/');
126
-
127
- if (routeParts.length !== pathParts.length) {
128
- return null;
129
- }
130
-
131
- const params = {};
132
-
133
- for (let i = 0; i < routeParts.length; i++) {
134
- if (routeParts[i].startsWith(':')) {
135
- const paramName = routeParts[i].substring(1);
136
- params[paramName] = pathParts[i];
137
- } else if (routeParts[i] !== pathParts[i]) {
138
- return null;
139
- }
140
- }
141
-
142
- return { params };
143
- }
144
-
145
- /**
146
- * Create agentful server
147
- * @param {Object} config - Server configuration
148
- * @param {string} [config.auth='tailscale'] - Authentication mode
149
- * @param {number} [config.port=3000] - Server port
150
- * @param {string} [config.secret] - HMAC secret (required for hmac mode)
151
- * @param {boolean} [config.https=false] - Enable HTTPS
152
- * @param {string} [config.cert] - SSL certificate path (for HTTPS)
153
- * @param {string} [config.key] - SSL key path (for HTTPS)
154
- * @param {string} [config.projectRoot] - Project root directory
155
- * @param {string} [config.corsOrigin] - CORS allowed origin (default: same-origin only)
156
- * @returns {Object} Server instance
157
- */
158
- export function createServer(config = {}) {
159
- const {
160
- auth = 'tailscale',
161
- port = 3000,
162
- secret,
163
- https: enableHttps = false,
164
- cert,
165
- key,
166
- projectRoot = process.cwd(),
167
- corsOrigin = null,
168
- } = config;
169
-
170
- // Validate configuration
171
- if (auth === 'hmac' && !secret) {
172
- throw new Error('HMAC mode requires --secret');
173
- }
174
-
175
- if (enableHttps && (!cert || !key)) {
176
- throw new Error('HTTPS mode requires --cert and --key');
177
- }
178
-
179
- // Create authentication middleware
180
- const authMiddleware = createAuthMiddleware(auth, { secret });
181
-
182
- // Define routes
183
- const routes = [
184
- // Health check (no auth required)
185
- route('GET', '/health', async (req, res) => {
186
- res.writeHead(200, { 'Content-Type': 'application/json' });
187
- res.end(
188
- JSON.stringify({
189
- status: 'healthy',
190
- uptime: process.uptime(),
191
- mode: auth,
192
- timestamp: Date.now(),
193
- })
194
- );
195
- }),
196
-
197
- // List available agents
198
- route('GET', '/agents', async (req, res, params) => {
199
- try {
200
- const agents = await listAvailableAgents(projectRoot);
201
-
202
- res.writeHead(200, { 'Content-Type': 'application/json' });
203
- res.end(
204
- JSON.stringify({
205
- agents,
206
- count: agents.length,
207
- })
208
- );
209
- } catch (error) {
210
- res.writeHead(500, { 'Content-Type': 'application/json' });
211
- res.end(
212
- JSON.stringify({
213
- error: 'Failed to list agents',
214
- message: error.message,
215
- })
216
- );
217
- }
218
- }),
219
-
220
- // Trigger agent execution
221
- route('POST', '/trigger', async (req, res, params) => {
222
- try {
223
- const body = parseJSON(req.rawBody || '');
224
-
225
- // Validate request
226
- if (!body.agent) {
227
- res.writeHead(400, { 'Content-Type': 'application/json' });
228
- return res.end(
229
- JSON.stringify({
230
- error: 'Validation failed',
231
- message: 'Missing required field: agent',
232
- })
233
- );
234
- }
235
-
236
- if (!body.task) {
237
- res.writeHead(400, { 'Content-Type': 'application/json' });
238
- return res.end(
239
- JSON.stringify({
240
- error: 'Validation failed',
241
- message: 'Missing required field: task',
242
- })
243
- );
244
- }
245
-
246
- // Log agent execution request
247
- console.log(`[${new Date().toISOString()}] Executing agent: ${body.agent}`);
248
- console.log(`[${new Date().toISOString()}] Task: ${body.task}`);
249
-
250
- // Start agent execution in background (async mode)
251
- const executionTimeout = body.timeout || 10 * 60 * 1000; // Default 10 min for execution
252
-
253
- const result = await executeAgent(body.agent, body.task, {
254
- projectRoot,
255
- timeout: executionTimeout,
256
- env: body.env,
257
- async: true, // Return immediately with executionId
258
- });
259
-
260
- console.log(`[${new Date().toISOString()}] Execution started: ${result.executionId}`);
261
-
262
- res.writeHead(202, { 'Content-Type': 'application/json' });
263
- res.end(
264
- JSON.stringify({
265
- executionId: result.executionId,
266
- message: 'Agent execution started',
267
- statusUrl: `/status/${result.executionId}`,
268
- })
269
- );
270
- } catch (error) {
271
- res.writeHead(400, { 'Content-Type': 'application/json' });
272
- res.end(
273
- JSON.stringify({
274
- error: 'Bad request',
275
- message: error.message,
276
- })
277
- );
278
- }
279
- }),
280
-
281
- // Get execution status
282
- route('GET', '/status/:executionId', async (req, res, params) => {
283
- const execution = getExecutionStatus(params.executionId);
284
-
285
- if (!execution) {
286
- res.writeHead(404, { 'Content-Type': 'application/json' });
287
- return res.end(
288
- JSON.stringify({
289
- error: 'Not found',
290
- message: 'Execution not found',
291
- })
292
- );
293
- }
294
-
295
- res.writeHead(200, { 'Content-Type': 'application/json' });
296
- res.end(JSON.stringify(execution));
297
- }),
298
-
299
- // List executions
300
- route('GET', '/executions', async (req, res, params) => {
301
- const url = new URL(req.url, `http://${req.headers.host}`);
302
- const filters = {
303
- agent: url.searchParams.get('agent'),
304
- state: url.searchParams.get('state'),
305
- limit: parseInt(url.searchParams.get('limit') || '100', 10),
306
- };
307
-
308
- const executions = listExecutions(filters);
309
-
310
- res.writeHead(200, { 'Content-Type': 'application/json' });
311
- res.end(
312
- JSON.stringify({
313
- executions,
314
- count: executions.length,
315
- })
316
- );
317
- }),
318
- ];
319
-
320
- // Request handler
321
- const requestHandler = (req, res) => {
322
- const startTime = Date.now();
323
- const clientIP = req.socket.remoteAddress;
324
-
325
- // Log incoming request
326
- const timestamp = new Date().toISOString();
327
- console.log(`[${timestamp}] ${req.method} ${req.url} from ${clientIP}`);
328
-
329
- // Add CORS headers (restricted by default)
330
- if (corsOrigin) {
331
- res.setHeader('Access-Control-Allow-Origin', corsOrigin);
332
- } else {
333
- // Same-origin only - no CORS header
334
- // Browsers will enforce same-origin policy
335
- }
336
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
337
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Agentful-Signature, X-Agentful-Timestamp');
338
-
339
- // Handle preflight
340
- if (req.method === 'OPTIONS') {
341
- res.writeHead(204);
342
- const duration = Date.now() - startTime;
343
- console.log(`[${new Date().toISOString()}] Response sent: 204 (${duration}ms)`);
344
- return res.end();
345
- }
346
-
347
- // Apply rate limiting
348
- if (!checkRateLimit(req, res)) {
349
- const duration = Date.now() - startTime;
350
- console.log(`[${new Date().toISOString()}] Response sent: 429 Rate Limited (${duration}ms)`);
351
- return; // Rate limit exceeded, response already sent
352
- }
353
-
354
- // Intercept res.end to log responses
355
- const originalEnd = res.end;
356
- res.end = function(...args) {
357
- const duration = Date.now() - startTime;
358
- const statusCode = res.statusCode;
359
- console.log(`[${new Date().toISOString()}] Response sent: ${statusCode} (${duration}ms)`);
360
- originalEnd.apply(res, args);
361
- };
362
-
363
- // Capture raw body (needed for HMAC verification)
364
- captureRawBody(req, res, () => {
365
- // Apply authentication (except for /health)
366
- if (req.url !== '/health') {
367
- authMiddleware(req, res, () => {
368
- handleRequest(req, res);
369
- });
370
- } else {
371
- handleRequest(req, res);
372
- }
373
- });
374
- };
375
-
376
- // Route request to handler
377
- function handleRequest(req, res) {
378
- const url = new URL(req.url, `http://${req.headers.host}`);
379
- const path = url.pathname;
380
-
381
- // Find matching route
382
- for (const routeDef of routes) {
383
- const match = matchRoute(routeDef, req.method, path);
384
- if (match) {
385
- return routeDef.handler(req, res, match.params);
386
- }
387
- }
388
-
389
- // No route matched - 404
390
- res.writeHead(404, { 'Content-Type': 'application/json' });
391
- res.end(
392
- JSON.stringify({
393
- error: 'Not found',
394
- message: `Route not found: ${req.method} ${path}`,
395
- })
396
- );
397
- }
398
-
399
- // Create HTTP or HTTPS server
400
- let server;
401
- if (enableHttps) {
402
- const credentials = {
403
- cert: readFileSync(cert, 'utf-8'),
404
- key: readFileSync(key, 'utf-8'),
405
- };
406
- server = https.createServer(credentials, requestHandler);
407
- } else {
408
- server = http.createServer(requestHandler);
409
- }
410
-
411
- // Always bind to all interfaces (0.0.0.0)
412
- // Security is enforced through authentication middleware, not binding address
413
- const host = '0.0.0.0';
414
-
415
- return {
416
- start: () => {
417
- return new Promise((resolve, reject) => {
418
- server.listen(port, host, (error) => {
419
- if (error) {
420
- return reject(error);
421
- }
422
-
423
- const protocol = enableHttps ? 'https' : 'http';
424
- console.log(`Agentful server listening on ${protocol}://${host}:${port}`);
425
- console.log(`Authentication mode: ${auth}`);
426
-
427
- if (auth === 'none') {
428
- console.log('Warning: No authentication enabled - use SSH tunnel for secure remote access');
429
- }
430
-
431
- // Start periodic cleanup
432
- startPeriodicCleanup();
433
-
434
- resolve();
435
- });
436
- });
437
- },
438
-
439
- stop: () => {
440
- return new Promise((resolve) => {
441
- server.close(() => {
442
- console.log('Server stopped');
443
- resolve();
444
- });
445
- });
446
- },
447
-
448
- server,
449
- };
450
- }
451
-
452
- /**
453
- * Start server from CLI arguments
454
- * @param {Object} args - Parsed CLI arguments
455
- */
456
- export async function startServerFromCLI(args) {
457
- const config = {
458
- auth: args.auth || 'tailscale',
459
- port: args.port || 3000,
460
- secret: args.secret,
461
- https: args.https || false,
462
- cert: args.cert,
463
- key: args.key,
464
- projectRoot: args.projectRoot || process.cwd(),
465
- corsOrigin: args.corsOrigin || null,
466
- };
467
-
468
- const server = createServer(config);
469
-
470
- // Handle graceful shutdown
471
- process.on('SIGTERM', async () => {
472
- console.log('Received SIGTERM, shutting down gracefully...');
473
- await server.stop();
474
- process.exit(0);
475
- });
476
-
477
- process.on('SIGINT', async () => {
478
- console.log('Received SIGINT, shutting down gracefully...');
479
- await server.stop();
480
- process.exit(0);
481
- });
482
-
483
- try {
484
- await server.start();
485
- } catch (error) {
486
- console.error('Failed to start server:', error.message);
487
- process.exit(1);
488
- }
489
- }
490
-
491
- export default {
492
- createServer,
493
- startServerFromCLI,
494
- };