@itz4blitz/agentful 0.3.0 → 0.5.1

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 (94) hide show
  1. package/README.md +139 -10
  2. package/bin/cli.js +1032 -48
  3. package/bin/hooks/README.md +338 -82
  4. package/bin/hooks/analyze-trigger.js +69 -0
  5. package/bin/hooks/block-random-docs.js +77 -0
  6. package/bin/hooks/health-check.js +153 -0
  7. package/bin/hooks/post-agent.js +101 -0
  8. package/bin/hooks/post-feature.js +227 -0
  9. package/bin/hooks/pre-agent.js +118 -0
  10. package/bin/hooks/pre-feature.js +138 -0
  11. package/lib/VALIDATION_README.md +455 -0
  12. package/lib/atomic.js +350 -0
  13. package/lib/ci/claude-action-integration.js +641 -0
  14. package/lib/ci/index.js +10 -0
  15. package/lib/core/CLAUDE_EXECUTOR.md +371 -0
  16. package/lib/core/README.md +321 -0
  17. package/lib/core/analyzer.js +497 -0
  18. package/lib/core/claude-executor.example.js +210 -0
  19. package/lib/core/claude-executor.js +1046 -0
  20. package/lib/core/cli.js +141 -0
  21. package/lib/core/detectors/conventions.js +342 -0
  22. package/lib/core/detectors/framework.js +276 -0
  23. package/lib/core/detectors/index.js +15 -0
  24. package/lib/core/detectors/language.js +199 -0
  25. package/lib/core/detectors/patterns.js +356 -0
  26. package/lib/core/generator.js +626 -0
  27. package/lib/core/index.js +9 -0
  28. package/lib/core/output-parser.example.js +250 -0
  29. package/lib/core/output-parser.js +458 -0
  30. package/lib/core/storage.js +515 -0
  31. package/lib/core/templates.js +556 -0
  32. package/lib/index.js +32 -0
  33. package/lib/init.js +497 -25
  34. package/lib/pipeline/cli.js +423 -0
  35. package/lib/pipeline/engine.js +928 -0
  36. package/lib/pipeline/executor.js +440 -0
  37. package/lib/pipeline/index.js +33 -0
  38. package/lib/pipeline/integrations.js +559 -0
  39. package/lib/pipeline/schemas.js +288 -0
  40. package/lib/presets.js +207 -0
  41. package/lib/remote/client.js +361 -0
  42. package/lib/server/auth.js +286 -0
  43. package/lib/server/client-example.js +190 -0
  44. package/lib/server/executor.js +426 -0
  45. package/lib/server/index.js +469 -0
  46. package/lib/update-helpers.js +505 -0
  47. package/lib/validation.js +460 -0
  48. package/package.json +19 -2
  49. package/template/.claude/agents/architect.md +260 -0
  50. package/template/.claude/agents/backend.md +203 -0
  51. package/template/.claude/agents/fixer.md +244 -0
  52. package/template/.claude/agents/frontend.md +232 -0
  53. package/template/.claude/agents/orchestrator.md +528 -0
  54. package/template/.claude/agents/product-analyzer.md +1130 -0
  55. package/template/.claude/agents/reviewer.md +229 -0
  56. package/template/.claude/agents/tester.md +242 -0
  57. package/{.claude → template/.claude}/commands/agentful-analyze.md +151 -43
  58. package/template/.claude/commands/agentful-decide.md +470 -0
  59. package/{.claude → template/.claude}/commands/agentful-product.md +92 -8
  60. package/template/.claude/commands/agentful-start.md +432 -0
  61. package/{.claude → template/.claude}/commands/agentful-status.md +88 -3
  62. package/template/.claude/commands/agentful-update.md +402 -0
  63. package/template/.claude/commands/agentful-validate.md +369 -0
  64. package/{.claude → template/.claude}/commands/agentful.md +111 -195
  65. package/template/.claude/product/EXAMPLES.md +167 -0
  66. package/{.claude → template/.claude}/settings.json +9 -13
  67. package/{.claude → template/.claude}/skills/conversation/SKILL.md +13 -7
  68. package/template/.claude/skills/deployment/SKILL.md +116 -0
  69. package/template/.claude/skills/product-planning/SKILL.md +463 -0
  70. package/{.claude → template/.claude}/skills/product-tracking/SKILL.md +10 -21
  71. package/template/.claude/skills/testing/SKILL.md +228 -0
  72. package/template/.claude/skills/validation/SKILL.md +650 -0
  73. package/template/CLAUDE.md +84 -16
  74. package/template/bin/hooks/block-random-docs.js +121 -0
  75. package/version.json +1 -1
  76. package/.claude/agents/architect.md +0 -524
  77. package/.claude/agents/backend.md +0 -315
  78. package/.claude/agents/fixer.md +0 -263
  79. package/.claude/agents/frontend.md +0 -274
  80. package/.claude/agents/orchestrator.md +0 -283
  81. package/.claude/agents/product-analyzer.md +0 -799
  82. package/.claude/agents/reviewer.md +0 -332
  83. package/.claude/agents/tester.md +0 -410
  84. package/.claude/commands/agentful-decide.md +0 -214
  85. package/.claude/commands/agentful-start.md +0 -182
  86. package/.claude/commands/agentful-validate.md +0 -127
  87. package/.claude/product/EXAMPLES.md +0 -610
  88. package/.claude/product/README.md +0 -344
  89. package/.claude/skills/validation/SKILL.md +0 -271
  90. package/bin/hooks/analyze-trigger.sh +0 -57
  91. package/bin/hooks/health-check.sh +0 -36
  92. package/template/PRODUCT.md +0 -584
  93. /package/{.claude → template/.claude}/commands/agentful-generate.md +0 -0
  94. /package/{.claude → template/.claude}/product/index.md +0 -0
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Output Parser Usage Examples
3
+ *
4
+ * Demonstrates how to use the OutputParser to extract structured data
5
+ * from agent responses.
6
+ */
7
+
8
+ import {
9
+ OutputParser,
10
+ parseAgentOutput,
11
+ extractJSON,
12
+ extractDecisions,
13
+ extractProgress,
14
+ extractQuestions
15
+ } from './output-parser.js';
16
+
17
+ // Example 1: Parsing structured JSON output
18
+ const jsonOutput = `
19
+ I've analyzed the codebase and found the following:
20
+
21
+ \`\`\`json
22
+ {
23
+ "files": 42,
24
+ "lines": 3500,
25
+ "coverage": 85.2,
26
+ "issues": [
27
+ {"type": "warning", "file": "index.js", "line": 10}
28
+ ]
29
+ }
30
+ \`\`\`
31
+
32
+ This shows good code quality overall.
33
+ `;
34
+
35
+ const result1 = parseAgentOutput(jsonOutput);
36
+ console.log('Type:', result1.type); // 'structured'
37
+ console.log('Data:', result1.data); // { files: 42, lines: 3500, ... }
38
+
39
+ // Example 2: Extracting decisions
40
+ const decisionOutput = `
41
+ I need your input on the authentication approach:
42
+
43
+ DECISION: Should we use JWT or sessions for authentication?
44
+ OPTIONS:
45
+ - JWT (stateless, scalable, better for microservices)
46
+ - Sessions (simpler, better for monoliths, easier to revoke)
47
+
48
+ This decision affects the architecture significantly.
49
+ `;
50
+
51
+ const result2 = parseAgentOutput(decisionOutput);
52
+ console.log('Type:', result2.type); // 'decisions'
53
+ console.log('Decisions:', result2.decisions);
54
+ // [{
55
+ // question: "Should we use JWT or sessions for authentication?",
56
+ // options: [
57
+ // { value: "JWT", description: "stateless, scalable, better for microservices" },
58
+ // { value: "Sessions", description: "simpler, better for monoliths, easier to revoke" }
59
+ // ],
60
+ // context: "..."
61
+ // }]
62
+
63
+ // Example 3: Tracking progress
64
+ const progressOutput = `
65
+ Running validation checks...
66
+
67
+ [PROGRESS: 75%]
68
+
69
+ Progress: 6/8 tests passing
70
+
71
+ Still working on the integration tests.
72
+ `;
73
+
74
+ const result3 = parseAgentOutput(progressOutput);
75
+ console.log('Type:', result3.type); // 'progress'
76
+ console.log('Progress:', result3.progress);
77
+ // {
78
+ // percentage: 75,
79
+ // format: 'percentage',
80
+ // raw: '[PROGRESS: 75%]'
81
+ // }
82
+
83
+ // Example 4: Detecting questions
84
+ const questionOutput = `
85
+ I'm implementing the user service.
86
+
87
+ QUESTION: What should be the password minimum length?
88
+
89
+ Should we enforce special characters in passwords?
90
+
91
+ I'll continue with the default settings for now.
92
+ `;
93
+
94
+ const result4 = parseAgentOutput(questionOutput);
95
+ console.log('Type:', result4.type); // 'question'
96
+ console.log('Questions:', result4.questions);
97
+ // [
98
+ // {
99
+ // question: "What should be the password minimum length?",
100
+ // type: "explicit",
101
+ // line: 3,
102
+ // context: [...]
103
+ // },
104
+ // {
105
+ // question: "Should we enforce special characters in passwords?",
106
+ // type: "inferred",
107
+ // line: 5,
108
+ // context: [...]
109
+ // }
110
+ // ]
111
+
112
+ // Example 5: Plain text fallback
113
+ const plainOutput = `
114
+ I've completed the implementation. All tests are passing.
115
+ `;
116
+
117
+ const result5 = parseAgentOutput(plainOutput);
118
+ console.log('Type:', result5.type); // 'text'
119
+ console.log('Data:', result5.data); // "I've completed the implementation..."
120
+
121
+ // Example 6: Using specific extraction functions
122
+ const mixedOutput = `
123
+ Here's the analysis:
124
+
125
+ \`\`\`json
126
+ {"status": "complete", "score": 95}
127
+ \`\`\`
128
+
129
+ DECISION: Deploy to production now?
130
+ OPTIONS:
131
+ - Yes (code is ready)
132
+ - No (wait for QA)
133
+
134
+ Progress: 4/5 features complete
135
+
136
+ What's the deployment schedule?
137
+ `;
138
+
139
+ // Extract only JSON
140
+ const jsonOnly = extractJSON(mixedOutput);
141
+ console.log('Found JSON:', jsonOnly.found); // true
142
+ console.log('Data:', jsonOnly.data); // { status: "complete", score: 95 }
143
+
144
+ // Extract only decisions
145
+ const decisionsOnly = extractDecisions(mixedOutput);
146
+ console.log('Decisions:', decisionsOnly.length); // 1
147
+
148
+ // Extract only progress
149
+ const progressOnly = extractProgress(mixedOutput);
150
+ console.log('Progress:', progressOnly.percentage); // 80
151
+
152
+ // Extract only questions
153
+ const questionsOnly = extractQuestions(mixedOutput);
154
+ console.log('Questions:', questionsOnly.length); // 1
155
+
156
+ // Example 7: Using the parser class directly
157
+ const parser = new OutputParser();
158
+
159
+ const agentResponse = `
160
+ Implementation complete. Here's the summary:
161
+
162
+ \`\`\`json
163
+ {
164
+ "endpoint": "/api/users",
165
+ "method": "POST",
166
+ "validation": "zod",
167
+ "authentication": "JWT"
168
+ }
169
+ \`\`\`
170
+ `;
171
+
172
+ const parsed = parser.parse(agentResponse);
173
+ if (parsed.type === 'structured') {
174
+ console.log('Extracted structured data:', parsed.data);
175
+ console.log('Endpoint:', parsed.data.endpoint); // "/api/users"
176
+ console.log('Method:', parsed.data.method); // "POST"
177
+ }
178
+
179
+ // Example 8: Multiple JSON blocks
180
+ const multiJsonOutput = `
181
+ \`\`\`json
182
+ {"config": "development"}
183
+ \`\`\`
184
+
185
+ And here's the production config:
186
+
187
+ \`\`\`json
188
+ {"config": "production", "debug": false}
189
+ \`\`\`
190
+ `;
191
+
192
+ const multiResult = extractJSON(multiJsonOutput);
193
+ console.log('All JSON blocks:', multiResult.all);
194
+ // [
195
+ // { config: "development" },
196
+ // { config: "production", debug: false }
197
+ // ]
198
+ console.log('First block:', multiResult.data); // { config: "development" }
199
+
200
+ // Example 9: Integration with agent orchestrator
201
+ async function handleAgentResponse(agentOutput) {
202
+ const parsed = parseAgentOutput(agentOutput);
203
+
204
+ switch (parsed.type) {
205
+ case 'structured':
206
+ // Store structured data in state
207
+ await saveToState(parsed.data);
208
+ break;
209
+
210
+ case 'decisions':
211
+ // Add to decisions.json for user resolution
212
+ await addPendingDecisions(parsed.decisions);
213
+ break;
214
+
215
+ case 'progress':
216
+ // Update completion tracking
217
+ await updateProgress(parsed.progress.percentage);
218
+ break;
219
+
220
+ case 'question':
221
+ // Prompt user for input
222
+ await promptUser(parsed.questions);
223
+ break;
224
+
225
+ case 'text':
226
+ // Log informational output
227
+ console.log('Agent:', parsed.data);
228
+ break;
229
+ }
230
+ }
231
+
232
+ // Example 10: Error handling
233
+ try {
234
+ const invalidOutput = null;
235
+ const result = parseAgentOutput(invalidOutput);
236
+ console.log('Type:', result.type); // 'text'
237
+ console.log('Data:', result.data); // null
238
+ } catch (error) {
239
+ console.error('Parser error:', error);
240
+ }
241
+
242
+ // Example 11: Complex progress patterns
243
+ const complexProgress = `
244
+ Testing progress: 3 of 5 features complete
245
+ Coverage increased to 82%
246
+ 15% of integration tests still failing
247
+ `;
248
+
249
+ const prog1 = extractProgress(complexProgress);
250
+ console.log('Progress:', prog1.percentage); // 60 (from "3 of 5")
@@ -0,0 +1,458 @@
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
+ */