@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,458 +0,0 @@
1
- /**
2
- * Output Parser - Structured data extraction from agent responses
3
- *
4
- * Parses raw agent output to extract:
5
- * - JSON blocks (structured data)
6
- * - Decision points (requiring user input)
7
- * - Progress markers (completion tracking)
8
- * - Questions (blocking queries)
9
- * - Plain text (fallback)
10
- *
11
- * @module lib/core/output-parser
12
- */
13
-
14
- /**
15
- * Main output parser class
16
- */
17
- export class OutputParser {
18
- /**
19
- * Parse agent output and return structured result
20
- *
21
- * @param {string} rawOutput - Raw agent response text
22
- * @returns {ParsedOutput} Parsed output with type and extracted data
23
- */
24
- parse(rawOutput) {
25
- if (!rawOutput || typeof rawOutput !== 'string') {
26
- return {
27
- type: 'text',
28
- data: null,
29
- raw: rawOutput || ''
30
- };
31
- }
32
-
33
- // Try parsing in order of specificity
34
- const jsonResult = this.extractJSON(rawOutput);
35
- if (jsonResult.found) {
36
- return {
37
- type: 'structured',
38
- data: jsonResult.data,
39
- raw: rawOutput,
40
- jsonBlocks: jsonResult.all
41
- };
42
- }
43
-
44
- const decisions = this.extractDecisions(rawOutput);
45
- if (decisions.length > 0) {
46
- return {
47
- type: 'decisions',
48
- decisions,
49
- raw: rawOutput
50
- };
51
- }
52
-
53
- const progress = this.extractProgress(rawOutput);
54
- if (progress) {
55
- return {
56
- type: 'progress',
57
- progress,
58
- raw: rawOutput
59
- };
60
- }
61
-
62
- const questions = this.extractQuestions(rawOutput);
63
- if (questions.length > 0) {
64
- return {
65
- type: 'question',
66
- questions,
67
- raw: rawOutput
68
- };
69
- }
70
-
71
- return {
72
- type: 'text',
73
- data: rawOutput.trim(),
74
- raw: rawOutput
75
- };
76
- }
77
-
78
- /**
79
- * Extract JSON from markdown code blocks or inline JSON
80
- *
81
- * @param {string} text - Text containing potential JSON
82
- * @returns {Object} { found: boolean, data: object|null, all: array }
83
- */
84
- extractJSON(text) {
85
- const result = {
86
- found: false,
87
- data: null,
88
- all: []
89
- };
90
-
91
- // Pattern 1: Markdown JSON code blocks
92
- const codeBlockPattern = /```(?:json)?\s*\n([\s\S]*?)\n```/g;
93
- let match;
94
-
95
- while ((match = codeBlockPattern.exec(text)) !== null) {
96
- try {
97
- const parsed = JSON.parse(match[1].trim());
98
- result.all.push(parsed);
99
- if (!result.found) {
100
- result.data = parsed;
101
- result.found = true;
102
- }
103
- } catch (e) {
104
- // Not valid JSON, skip
105
- }
106
- }
107
-
108
- // Pattern 2: Inline JSON objects (if no code blocks found)
109
- if (!result.found) {
110
- const inlinePattern = /(\{[\s\S]*?\})/g;
111
-
112
- while ((match = inlinePattern.exec(text)) !== null) {
113
- try {
114
- const parsed = JSON.parse(match[1]);
115
- // Verify it's a real object (not just {})
116
- if (Object.keys(parsed).length > 0) {
117
- result.all.push(parsed);
118
- if (!result.found) {
119
- result.data = parsed;
120
- result.found = true;
121
- }
122
- }
123
- } catch (e) {
124
- // Not valid JSON, skip
125
- }
126
- }
127
- }
128
-
129
- return result;
130
- }
131
-
132
- /**
133
- * Extract decision points from agent output
134
- *
135
- * Format:
136
- * DECISION: Question text?
137
- * OPTIONS:
138
- * - Option A (description)
139
- * - Option B (description)
140
- *
141
- * @param {string} text - Text containing decisions
142
- * @returns {Array<Decision>} Array of decision objects
143
- */
144
- extractDecisions(text) {
145
- const decisions = [];
146
-
147
- // Pattern: DECISION: ... OPTIONS: ...
148
- const decisionPattern = /DECISION:\s*([^\n]+)\s*(?:OPTIONS?:)?\s*((?:[-*]\s*[^\n]+\n?)*)/gi;
149
- let match;
150
-
151
- while ((match = decisionPattern.exec(text)) !== null) {
152
- const question = match[1].trim();
153
- const optionsText = match[2].trim();
154
-
155
- // Parse options
156
- const options = [];
157
- const optionPattern = /[-*]\s*([^\n(]+)(?:\(([^)]+)\))?/g;
158
- let optionMatch;
159
-
160
- while ((optionMatch = optionPattern.exec(optionsText)) !== null) {
161
- options.push({
162
- value: optionMatch[1].trim(),
163
- description: optionMatch[2]?.trim() || null
164
- });
165
- }
166
-
167
- decisions.push({
168
- question,
169
- options: options.length > 0 ? options : null,
170
- context: this._extractContext(text, match.index)
171
- });
172
- }
173
-
174
- // Alternative pattern: Simple "DECIDE: " marker
175
- const simplePattern = /DECIDE:\s*([^\n]+)/gi;
176
-
177
- while ((match = simplePattern.exec(text)) !== null) {
178
- const question = match[1].trim();
179
-
180
- // Don't duplicate if already found
181
- if (!decisions.some(d => d.question === question)) {
182
- decisions.push({
183
- question,
184
- options: null,
185
- context: this._extractContext(text, match.index)
186
- });
187
- }
188
- }
189
-
190
- return decisions;
191
- }
192
-
193
- /**
194
- * Extract progress indicators from text
195
- *
196
- * Patterns:
197
- * - [PROGRESS: 50%]
198
- * - Progress: 7/10 tests passing
199
- * - 3 of 5 features complete
200
- *
201
- * @param {string} text - Text containing progress markers
202
- * @returns {Progress|null} Progress object or null
203
- */
204
- extractProgress(text) {
205
- // Pattern 1: [PROGRESS: 50%]
206
- const bracketPattern = /\[PROGRESS:\s*(\d+)%\]/i;
207
- let match = bracketPattern.exec(text);
208
-
209
- if (match) {
210
- return {
211
- percentage: parseInt(match[1], 10),
212
- format: 'percentage',
213
- raw: match[0]
214
- };
215
- }
216
-
217
- // Pattern 2: Progress: 7/10 tests passing
218
- const ratioPattern = /Progress:\s*(\d+)\s*\/\s*(\d+)\s*([^\n]*)/i;
219
- match = ratioPattern.exec(text);
220
-
221
- if (match) {
222
- const current = parseInt(match[1], 10);
223
- const total = parseInt(match[2], 10);
224
- return {
225
- current,
226
- total,
227
- percentage: total > 0 ? Math.round((current / total) * 100) : 0,
228
- format: 'ratio',
229
- description: match[3].trim() || null,
230
- raw: match[0]
231
- };
232
- }
233
-
234
- // Pattern 3: 3 of 5 features complete
235
- const ofPattern = /(\d+)\s+of\s+(\d+)\s+([^\n]*?)\s+(?:complete|done|finished|passing)/i;
236
- match = ofPattern.exec(text);
237
-
238
- if (match) {
239
- const current = parseInt(match[1], 10);
240
- const total = parseInt(match[2], 10);
241
- return {
242
- current,
243
- total,
244
- percentage: total > 0 ? Math.round((current / total) * 100) : 0,
245
- format: 'of',
246
- description: match[3].trim() || null,
247
- raw: match[0]
248
- };
249
- }
250
-
251
- // Pattern 4: Simple percentage
252
- const percentPattern = /(\d+)%\s+(?:complete|done|finished)/i;
253
- match = percentPattern.exec(text);
254
-
255
- if (match) {
256
- return {
257
- percentage: parseInt(match[1], 10),
258
- format: 'percentage',
259
- raw: match[0]
260
- };
261
- }
262
-
263
- return null;
264
- }
265
-
266
- /**
267
- * Extract questions requiring user input
268
- *
269
- * Patterns:
270
- * - Lines ending with "?"
271
- * - "QUESTION: " prefix
272
- * - "Need input: " prefix
273
- *
274
- * @param {string} text - Text containing questions
275
- * @returns {Array<Question>} Array of question objects
276
- */
277
- extractQuestions(text) {
278
- const questions = [];
279
- const lines = text.split('\n');
280
-
281
- for (let i = 0; i < lines.length; i++) {
282
- const line = lines[i].trim();
283
-
284
- // Skip empty lines and code blocks
285
- if (!line || line.startsWith('```')) continue;
286
-
287
- // Pattern 1: Explicit QUESTION: marker
288
- let match = /^QUESTION:\s*(.+)$/i.exec(line);
289
- if (match) {
290
- questions.push({
291
- question: match[1].trim(),
292
- type: 'explicit',
293
- line: i + 1,
294
- context: this._getLineContext(lines, i)
295
- });
296
- continue;
297
- }
298
-
299
- // Pattern 2: "Need input:" prefix
300
- match = /^(?:Need input|Needs? input|Input needed):\s*(.+)$/i.exec(line);
301
- if (match) {
302
- questions.push({
303
- question: match[1].trim(),
304
- type: 'input_request',
305
- line: i + 1,
306
- context: this._getLineContext(lines, i)
307
- });
308
- continue;
309
- }
310
-
311
- // Pattern 3: Questions ending with "?"
312
- // Only consider substantial questions (>10 chars, starts with question word or context)
313
- if (line.endsWith('?') && line.length > 10) {
314
- const questionWords = /^(what|where|when|why|how|which|who|should|can|could|would|will|is|are|do|does)/i;
315
- if (questionWords.test(line)) {
316
- questions.push({
317
- question: line,
318
- type: 'inferred',
319
- line: i + 1,
320
- context: this._getLineContext(lines, i)
321
- });
322
- }
323
- }
324
- }
325
-
326
- return questions;
327
- }
328
-
329
- /**
330
- * Extract surrounding context for a match
331
- *
332
- * @private
333
- * @param {string} text - Full text
334
- * @param {number} index - Match index
335
- * @param {number} radius - Characters before/after to include
336
- * @returns {string} Context snippet
337
- */
338
- _extractContext(text, index, radius = 100) {
339
- const start = Math.max(0, index - radius);
340
- const end = Math.min(text.length, index + radius);
341
-
342
- let context = text.substring(start, end).trim();
343
-
344
- // Add ellipsis if truncated
345
- if (start > 0) context = '...' + context;
346
- if (end < text.length) context = context + '...';
347
-
348
- return context;
349
- }
350
-
351
- /**
352
- * Get context lines around a specific line
353
- *
354
- * @private
355
- * @param {Array<string>} lines - All lines
356
- * @param {number} lineIndex - Target line index
357
- * @param {number} radius - Lines before/after to include
358
- * @returns {Array<string>} Context lines
359
- */
360
- _getLineContext(lines, lineIndex, radius = 2) {
361
- const start = Math.max(0, lineIndex - radius);
362
- const end = Math.min(lines.length, lineIndex + radius + 1);
363
- return lines.slice(start, end);
364
- }
365
- }
366
-
367
- /**
368
- * Convenience function for parsing agent output
369
- *
370
- * @param {string} rawOutput - Raw agent response
371
- * @returns {ParsedOutput} Parsed output
372
- */
373
- export function parseAgentOutput(rawOutput) {
374
- const parser = new OutputParser();
375
- return parser.parse(rawOutput);
376
- }
377
-
378
- /**
379
- * Extract only JSON from agent output
380
- *
381
- * @param {string} text - Text containing JSON
382
- * @returns {Object} { found: boolean, data: object|null, all: array }
383
- */
384
- export function extractJSON(text) {
385
- const parser = new OutputParser();
386
- return parser.extractJSON(text);
387
- }
388
-
389
- /**
390
- * Extract only decisions from agent output
391
- *
392
- * @param {string} text - Text containing decisions
393
- * @returns {Array<Decision>} Array of decisions
394
- */
395
- export function extractDecisions(text) {
396
- const parser = new OutputParser();
397
- return parser.extractDecisions(text);
398
- }
399
-
400
- /**
401
- * Extract only progress from agent output
402
- *
403
- * @param {string} text - Text containing progress markers
404
- * @returns {Progress|null} Progress object or null
405
- */
406
- export function extractProgress(text) {
407
- const parser = new OutputParser();
408
- return parser.extractProgress(text);
409
- }
410
-
411
- /**
412
- * Extract only questions from agent output
413
- *
414
- * @param {string} text - Text containing questions
415
- * @returns {Array<Question>} Array of questions
416
- */
417
- export function extractQuestions(text) {
418
- const parser = new OutputParser();
419
- return parser.extractQuestions(text);
420
- }
421
-
422
- /**
423
- * @typedef {Object} ParsedOutput
424
- * @property {'structured'|'decisions'|'progress'|'question'|'text'} type - Output type
425
- * @property {*} data - Extracted data (type-specific)
426
- * @property {string} raw - Original raw output
427
- */
428
-
429
- /**
430
- * @typedef {Object} Decision
431
- * @property {string} question - Decision question
432
- * @property {Array<Option>|null} options - Available options (if provided)
433
- * @property {string} context - Surrounding text context
434
- */
435
-
436
- /**
437
- * @typedef {Object} Option
438
- * @property {string} value - Option value
439
- * @property {string|null} description - Option description
440
- */
441
-
442
- /**
443
- * @typedef {Object} Progress
444
- * @property {number} percentage - Progress as percentage (0-100)
445
- * @property {'percentage'|'ratio'|'of'} format - Format type
446
- * @property {number} [current] - Current count (for ratio/of formats)
447
- * @property {number} [total] - Total count (for ratio/of formats)
448
- * @property {string|null} [description] - Progress description
449
- * @property {string} raw - Raw progress marker
450
- */
451
-
452
- /**
453
- * @typedef {Object} Question
454
- * @property {string} question - Question text
455
- * @property {'explicit'|'input_request'|'inferred'} type - Question detection type
456
- * @property {number} line - Line number in output
457
- * @property {Array<string>} context - Surrounding lines for context
458
- */