@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,288 +0,0 @@
1
- /**
2
- * JSON Schemas for Pipeline Definitions
3
- *
4
- * Provides validation schemas for pipeline YAML files
5
- */
6
-
7
- export const pipelineSchema = {
8
- type: 'object',
9
- required: ['name', 'jobs'],
10
- properties: {
11
- name: {
12
- type: 'string',
13
- description: 'Pipeline name',
14
- pattern: '^[a-z0-9-]+$'
15
- },
16
- version: {
17
- type: 'string',
18
- description: 'Pipeline version',
19
- pattern: '^\\d+\\.\\d+(\\.\\d+)?$',
20
- default: '1.0'
21
- },
22
- description: {
23
- type: 'string',
24
- description: 'Pipeline description'
25
- },
26
- triggers: {
27
- type: 'array',
28
- description: 'Pipeline triggers',
29
- items: {
30
- $ref: '#/definitions/trigger'
31
- }
32
- },
33
- env: {
34
- type: 'object',
35
- description: 'Environment variables',
36
- additionalProperties: { type: 'string' }
37
- },
38
- jobs: {
39
- type: 'array',
40
- description: 'Pipeline jobs',
41
- minItems: 1,
42
- items: {
43
- $ref: '#/definitions/job'
44
- }
45
- },
46
- concurrency: {
47
- type: 'object',
48
- description: 'Concurrency settings',
49
- properties: {
50
- maxConcurrentJobs: {
51
- type: 'integer',
52
- minimum: 1,
53
- maximum: 10,
54
- default: 3
55
- },
56
- cancelInProgress: {
57
- type: 'boolean',
58
- default: false
59
- }
60
- }
61
- },
62
- timeout: {
63
- type: 'integer',
64
- description: 'Default job timeout in milliseconds',
65
- minimum: 1000,
66
- default: 1800000 // 30 minutes
67
- }
68
- },
69
- definitions: {
70
- trigger: {
71
- type: 'object',
72
- required: ['type'],
73
- properties: {
74
- type: {
75
- type: 'string',
76
- enum: ['push', 'pull_request', 'schedule', 'manual', 'webhook']
77
- },
78
- branches: {
79
- type: 'array',
80
- items: { type: 'string' }
81
- },
82
- cron: {
83
- type: 'string',
84
- description: 'Cron expression for scheduled triggers'
85
- },
86
- webhook: {
87
- type: 'object',
88
- properties: {
89
- secret: { type: 'string' },
90
- filters: { type: 'object' }
91
- }
92
- }
93
- }
94
- },
95
- job: {
96
- type: 'object',
97
- required: ['id', 'agent'],
98
- properties: {
99
- id: {
100
- type: 'string',
101
- description: 'Unique job identifier',
102
- pattern: '^[a-z0-9-]+$'
103
- },
104
- name: {
105
- type: 'string',
106
- description: 'Human-readable job name'
107
- },
108
- agent: {
109
- type: 'string',
110
- description: 'Agent to execute (name or path)'
111
- },
112
- task: {
113
- type: 'string',
114
- description: 'Task description for the agent'
115
- },
116
- prompt: {
117
- type: 'string',
118
- description: 'Additional prompt instructions'
119
- },
120
- dependsOn: {
121
- oneOf: [
122
- { type: 'string' },
123
- { type: 'array', items: { type: 'string' } }
124
- ],
125
- description: 'Job dependencies'
126
- },
127
- when: {
128
- type: 'string',
129
- description: 'Conditional execution expression'
130
- },
131
- inputs: {
132
- type: 'object',
133
- description: 'Job-specific inputs',
134
- additionalProperties: true
135
- },
136
- timeout: {
137
- type: 'integer',
138
- description: 'Job timeout in milliseconds',
139
- minimum: 1000
140
- },
141
- retry: {
142
- $ref: '#/definitions/retry'
143
- },
144
- continueOnError: {
145
- type: 'boolean',
146
- description: 'Continue pipeline even if this job fails',
147
- default: false
148
- },
149
- execution: {
150
- type: 'object',
151
- description: 'Execution configuration',
152
- properties: {
153
- method: {
154
- type: 'string',
155
- enum: ['subprocess', 'api'],
156
- default: 'subprocess'
157
- },
158
- isolation: {
159
- type: 'string',
160
- enum: ['shared', 'isolated'],
161
- default: 'shared'
162
- }
163
- }
164
- },
165
- runsOn: {
166
- type: 'string',
167
- description: 'Runner environment (for CI/CD integrations)',
168
- default: 'ubuntu-latest'
169
- },
170
- stage: {
171
- type: 'string',
172
- description: 'Pipeline stage (for GitLab CI)',
173
- default: 'test'
174
- },
175
- setup: {
176
- type: 'array',
177
- description: 'Setup steps before job execution',
178
- items: {
179
- type: 'object',
180
- required: ['command'],
181
- properties: {
182
- name: { type: 'string' },
183
- command: { type: 'string' }
184
- }
185
- }
186
- }
187
- }
188
- },
189
- retry: {
190
- type: 'object',
191
- description: 'Retry policy',
192
- properties: {
193
- maxAttempts: {
194
- type: 'integer',
195
- minimum: 0,
196
- maximum: 5,
197
- default: 0
198
- },
199
- backoff: {
200
- type: 'string',
201
- enum: ['exponential', 'linear', 'fixed'],
202
- default: 'exponential'
203
- },
204
- delayMs: {
205
- type: 'integer',
206
- minimum: 0,
207
- default: 2000
208
- }
209
- }
210
- }
211
- }
212
- };
213
-
214
- /**
215
- * Validate pipeline definition
216
- *
217
- * @param {Object} pipeline - Pipeline definition
218
- * @returns {Object} Validation result { valid: boolean, errors: array }
219
- */
220
- export function validatePipeline(pipeline) {
221
- // Basic validation
222
- const errors = [];
223
-
224
- if (!pipeline || typeof pipeline !== 'object') {
225
- errors.push('Pipeline must be a valid object');
226
- return { valid: false, errors };
227
- }
228
-
229
- if (!pipeline.name) {
230
- errors.push('Pipeline must have a name');
231
- }
232
-
233
- if (!pipeline.jobs || !Array.isArray(pipeline.jobs) || pipeline.jobs.length === 0) {
234
- errors.push('Pipeline must have at least one job');
235
- }
236
-
237
- // Collect all job IDs first
238
- const jobIds = new Set();
239
- for (const job of pipeline.jobs || []) {
240
- if (job.id) {
241
- if (jobIds.has(job.id)) {
242
- errors.push(`Duplicate job id: ${job.id}`);
243
- } else {
244
- jobIds.add(job.id);
245
- }
246
- }
247
- }
248
-
249
- // Validate jobs
250
- for (const job of pipeline.jobs || []) {
251
- // Check if job is a valid object
252
- if (!job || typeof job !== 'object') {
253
- errors.push('Each job must be a valid object');
254
- continue;
255
- }
256
-
257
- if (!job.id) {
258
- errors.push('Each job must have an id');
259
- }
260
-
261
- if (!job.agent) {
262
- errors.push(`Job ${job.id} must specify an agent`);
263
- }
264
-
265
- // Validate dependencies
266
- if ('dependsOn' in job) {
267
- const deps = Array.isArray(job.dependsOn) ? job.dependsOn : [job.dependsOn];
268
- for (const depId of deps) {
269
- // Check for empty string or invalid values
270
- if (typeof depId !== 'string' || depId === '') {
271
- errors.push(`Job ${job.id} depends on unknown job: ${depId}`);
272
- } else if (!jobIds.has(depId)) {
273
- errors.push(`Job ${job.id} depends on unknown job: ${depId}`);
274
- }
275
- }
276
- }
277
- }
278
-
279
- return {
280
- valid: errors.length === 0,
281
- errors
282
- };
283
- }
284
-
285
- export default {
286
- pipelineSchema,
287
- validatePipeline
288
- };
@@ -1,361 +0,0 @@
1
- /**
2
- * Agentful Remote Client
3
- *
4
- * HTTP client for triggering agent execution on remote agentful servers.
5
- * Supports Tailscale, HMAC, and SSH tunnel authentication modes.
6
- *
7
- * @module remote/client
8
- */
9
-
10
- import crypto from 'crypto';
11
- import fs from 'fs';
12
- import path from 'path';
13
- import os from 'os';
14
-
15
- /**
16
- * Remote configuration storage
17
- */
18
- const CONFIG_DIR = path.join(os.homedir(), '.agentful');
19
- const CONFIG_FILE = path.join(CONFIG_DIR, 'remotes.json');
20
-
21
- /**
22
- * Ensure config directory exists
23
- */
24
- function ensureConfigDir() {
25
- if (!fs.existsSync(CONFIG_DIR)) {
26
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
27
- }
28
- }
29
-
30
- /**
31
- * Load remote configurations
32
- * @returns {Object} Remote configurations
33
- */
34
- export function loadRemotes() {
35
- ensureConfigDir();
36
-
37
- if (!fs.existsSync(CONFIG_FILE)) {
38
- return {};
39
- }
40
-
41
- try {
42
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
43
- } catch (error) {
44
- console.error('Failed to load remotes.json:', error.message);
45
- return {};
46
- }
47
- }
48
-
49
- /**
50
- * Save remote configurations
51
- * @param {Object} remotes - Remote configurations
52
- */
53
- export function saveRemotes(remotes) {
54
- ensureConfigDir();
55
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(remotes, null, 2), 'utf-8');
56
- }
57
-
58
- /**
59
- * Add a remote configuration
60
- * @param {string} name - Remote name
61
- * @param {string} url - Server URL
62
- * @param {Object} options - Remote options
63
- * @param {string} [options.auth='tailscale'] - Auth mode
64
- * @param {string} [options.secret] - HMAC secret
65
- */
66
- export function addRemote(name, url, options = {}) {
67
- const { auth = 'tailscale', secret } = options;
68
-
69
- // Validate auth mode
70
- if (auth === 'hmac' && !secret) {
71
- throw new Error('HMAC auth requires --secret');
72
- }
73
-
74
- // Parse and validate URL
75
- let parsedUrl;
76
- try {
77
- parsedUrl = new URL(url);
78
- } catch (error) {
79
- throw new Error(`Invalid URL: ${url}`);
80
- }
81
-
82
- const remotes = loadRemotes();
83
-
84
- remotes[name] = {
85
- url: parsedUrl.toString(),
86
- auth,
87
- secret: secret || null,
88
- added: new Date().toISOString(),
89
- };
90
-
91
- saveRemotes(remotes);
92
- }
93
-
94
- /**
95
- * Remove a remote configuration
96
- * @param {string} name - Remote name
97
- */
98
- export function removeRemote(name) {
99
- const remotes = loadRemotes();
100
-
101
- if (!remotes[name]) {
102
- throw new Error(`Remote not found: ${name}`);
103
- }
104
-
105
- delete remotes[name];
106
- saveRemotes(remotes);
107
- }
108
-
109
- /**
110
- * Get a remote configuration
111
- * @param {string} name - Remote name
112
- * @returns {Object} Remote configuration
113
- */
114
- export function getRemote(name) {
115
- const remotes = loadRemotes();
116
-
117
- if (!remotes[name]) {
118
- throw new Error(`Remote not found: ${name}. Run: agentful remote add ${name} <url>`);
119
- }
120
-
121
- return remotes[name];
122
- }
123
-
124
- /**
125
- * List all remote configurations
126
- * @returns {Object} Remote configurations
127
- */
128
- export function listRemotes() {
129
- return loadRemotes();
130
- }
131
-
132
- /**
133
- * Generate HMAC signature for request
134
- * @param {string} body - Request body
135
- * @param {string} secret - HMAC secret
136
- * @returns {Object} Headers with signature
137
- */
138
- function generateHMACHeaders(body, secret) {
139
- const timestamp = Date.now().toString();
140
- const signature = crypto
141
- .createHmac('sha256', secret)
142
- .update(timestamp + body)
143
- .digest('hex');
144
-
145
- return {
146
- 'X-Agentful-Signature': signature,
147
- 'X-Agentful-Timestamp': timestamp,
148
- };
149
- }
150
-
151
- /**
152
- * Execute an agent on a remote server
153
- * @param {string} remoteName - Remote name
154
- * @param {string} agent - Agent name
155
- * @param {string} task - Task description
156
- * @param {Object} options - Execution options
157
- * @param {number} [options.timeout] - Execution timeout in ms
158
- * @param {Object} [options.env] - Environment variables
159
- * @returns {Promise<Object>} Execution result
160
- */
161
- export async function executeRemoteAgent(remoteName, agent, task, options = {}) {
162
- const remote = getRemote(remoteName);
163
- const { timeout, env } = options;
164
-
165
- // Build request body
166
- const body = JSON.stringify({
167
- agent,
168
- task,
169
- timeout,
170
- env,
171
- });
172
-
173
- // Build headers
174
- const headers = {
175
- 'Content-Type': 'application/json',
176
- };
177
-
178
- // Add HMAC signature if needed
179
- if (remote.auth === 'hmac') {
180
- Object.assign(headers, generateHMACHeaders(body, remote.secret));
181
- }
182
-
183
- // Make request
184
- const response = await fetch(`${remote.url}/trigger`, {
185
- method: 'POST',
186
- headers,
187
- body,
188
- });
189
-
190
- if (!response.ok) {
191
- const error = await response.json().catch(() => ({ message: response.statusText }));
192
- throw new Error(`Request failed (${response.status}): ${error.message || error.error}`);
193
- }
194
-
195
- return await response.json();
196
- }
197
-
198
- /**
199
- * Get execution status from remote server
200
- * @param {string} remoteName - Remote name
201
- * @param {string} executionId - Execution ID
202
- * @returns {Promise<Object>} Execution status
203
- */
204
- export async function getRemoteExecutionStatus(remoteName, executionId) {
205
- const remote = getRemote(remoteName);
206
-
207
- // Build headers
208
- const headers = {};
209
-
210
- // Add HMAC signature if needed
211
- if (remote.auth === 'hmac') {
212
- Object.assign(headers, generateHMACHeaders('', remote.secret));
213
- }
214
-
215
- // Make request
216
- const response = await fetch(`${remote.url}/status/${executionId}`, {
217
- method: 'GET',
218
- headers,
219
- });
220
-
221
- if (!response.ok) {
222
- const error = await response.json().catch(() => ({ message: response.statusText }));
223
- throw new Error(`Request failed (${response.status}): ${error.message || error.error}`);
224
- }
225
-
226
- return await response.json();
227
- }
228
-
229
- /**
230
- * List executions on remote server
231
- * @param {string} remoteName - Remote name
232
- * @param {Object} filters - Filter options
233
- * @param {string} [filters.agent] - Filter by agent name
234
- * @param {string} [filters.state] - Filter by state
235
- * @param {number} [filters.limit] - Maximum number of results
236
- * @returns {Promise<Object>} Executions list
237
- */
238
- export async function listRemoteExecutions(remoteName, filters = {}) {
239
- const remote = getRemote(remoteName);
240
-
241
- // Build query string
242
- const params = new URLSearchParams();
243
- if (filters.agent) params.append('agent', filters.agent);
244
- if (filters.state) params.append('state', filters.state);
245
- if (filters.limit) params.append('limit', filters.limit.toString());
246
-
247
- // Build headers
248
- const headers = {};
249
-
250
- // Add HMAC signature if needed
251
- if (remote.auth === 'hmac') {
252
- Object.assign(headers, generateHMACHeaders('', remote.secret));
253
- }
254
-
255
- // Make request
256
- const url = `${remote.url}/executions${params.toString() ? '?' + params.toString() : ''}`;
257
- const response = await fetch(url, {
258
- method: 'GET',
259
- headers,
260
- });
261
-
262
- if (!response.ok) {
263
- const error = await response.json().catch(() => ({ message: response.statusText }));
264
- throw new Error(`Request failed (${response.status}): ${error.message || error.error}`);
265
- }
266
-
267
- return await response.json();
268
- }
269
-
270
- /**
271
- * List available agents on remote server
272
- * @param {string} remoteName - Remote name
273
- * @returns {Promise<Object>} Agents list
274
- */
275
- export async function listRemoteAgents(remoteName) {
276
- const remote = getRemote(remoteName);
277
-
278
- // Build headers
279
- const headers = {};
280
-
281
- // Add HMAC signature if needed
282
- if (remote.auth === 'hmac') {
283
- Object.assign(headers, generateHMACHeaders('', remote.secret));
284
- }
285
-
286
- // Make request
287
- const response = await fetch(`${remote.url}/agents`, {
288
- method: 'GET',
289
- headers,
290
- });
291
-
292
- if (!response.ok) {
293
- const error = await response.json().catch(() => ({ message: response.statusText }));
294
- throw new Error(`Request failed (${response.status}): ${error.message || error.error}`);
295
- }
296
-
297
- return await response.json();
298
- }
299
-
300
- /**
301
- * Check remote server health
302
- * @param {string} remoteName - Remote name
303
- * @returns {Promise<Object>} Health status
304
- */
305
- export async function checkRemoteHealth(remoteName) {
306
- const remote = getRemote(remoteName);
307
-
308
- // No auth required for /health endpoint
309
- const response = await fetch(`${remote.url}/health`, {
310
- method: 'GET',
311
- });
312
-
313
- if (!response.ok) {
314
- const error = await response.json().catch(() => ({ message: response.statusText }));
315
- throw new Error(`Health check failed (${response.status}): ${error.message || error.error}`);
316
- }
317
-
318
- return await response.json();
319
- }
320
-
321
- /**
322
- * Poll execution until completion
323
- * @param {string} remoteName - Remote name
324
- * @param {string} executionId - Execution ID
325
- * @param {Object} options - Polling options
326
- * @param {number} [options.interval=5000] - Polling interval in ms
327
- * @param {Function} [options.onUpdate] - Callback for status updates
328
- * @returns {Promise<Object>} Final execution status
329
- */
330
- export async function pollExecution(remoteName, executionId, options = {}) {
331
- const { interval = 5000, onUpdate } = options;
332
-
333
- while (true) {
334
- const status = await getRemoteExecutionStatus(remoteName, executionId);
335
-
336
- if (onUpdate) {
337
- onUpdate(status);
338
- }
339
-
340
- // Check if completed
341
- if (status.state === 'completed' || status.state === 'failed') {
342
- return status;
343
- }
344
-
345
- // Wait before next poll
346
- await new Promise((resolve) => setTimeout(resolve, interval));
347
- }
348
- }
349
-
350
- export default {
351
- addRemote,
352
- removeRemote,
353
- getRemote,
354
- listRemotes,
355
- executeRemoteAgent,
356
- getRemoteExecutionStatus,
357
- listRemoteExecutions,
358
- listRemoteAgents,
359
- checkRemoteHealth,
360
- pollExecution,
361
- };