@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.
- package/README.md +139 -10
- package/bin/cli.js +1032 -48
- package/bin/hooks/README.md +338 -82
- package/bin/hooks/analyze-trigger.js +69 -0
- package/bin/hooks/block-random-docs.js +77 -0
- package/bin/hooks/health-check.js +153 -0
- package/bin/hooks/post-agent.js +101 -0
- package/bin/hooks/post-feature.js +227 -0
- package/bin/hooks/pre-agent.js +118 -0
- package/bin/hooks/pre-feature.js +138 -0
- package/lib/VALIDATION_README.md +455 -0
- package/lib/atomic.js +350 -0
- package/lib/ci/claude-action-integration.js +641 -0
- package/lib/ci/index.js +10 -0
- package/lib/core/CLAUDE_EXECUTOR.md +371 -0
- package/lib/core/README.md +321 -0
- package/lib/core/analyzer.js +497 -0
- package/lib/core/claude-executor.example.js +210 -0
- package/lib/core/claude-executor.js +1046 -0
- package/lib/core/cli.js +141 -0
- package/lib/core/detectors/conventions.js +342 -0
- package/lib/core/detectors/framework.js +276 -0
- package/lib/core/detectors/index.js +15 -0
- package/lib/core/detectors/language.js +199 -0
- package/lib/core/detectors/patterns.js +356 -0
- package/lib/core/generator.js +626 -0
- package/lib/core/index.js +9 -0
- package/lib/core/output-parser.example.js +250 -0
- package/lib/core/output-parser.js +458 -0
- package/lib/core/storage.js +515 -0
- package/lib/core/templates.js +556 -0
- package/lib/index.js +32 -0
- package/lib/init.js +497 -25
- package/lib/pipeline/cli.js +423 -0
- package/lib/pipeline/engine.js +928 -0
- package/lib/pipeline/executor.js +440 -0
- package/lib/pipeline/index.js +33 -0
- package/lib/pipeline/integrations.js +559 -0
- package/lib/pipeline/schemas.js +288 -0
- package/lib/presets.js +207 -0
- package/lib/remote/client.js +361 -0
- package/lib/server/auth.js +286 -0
- package/lib/server/client-example.js +190 -0
- package/lib/server/executor.js +426 -0
- package/lib/server/index.js +469 -0
- package/lib/update-helpers.js +505 -0
- package/lib/validation.js +460 -0
- package/package.json +19 -2
- package/template/.claude/agents/architect.md +260 -0
- package/template/.claude/agents/backend.md +203 -0
- package/template/.claude/agents/fixer.md +244 -0
- package/template/.claude/agents/frontend.md +232 -0
- package/template/.claude/agents/orchestrator.md +528 -0
- package/template/.claude/agents/product-analyzer.md +1130 -0
- package/template/.claude/agents/reviewer.md +229 -0
- package/template/.claude/agents/tester.md +242 -0
- package/{.claude → template/.claude}/commands/agentful-analyze.md +151 -43
- package/template/.claude/commands/agentful-decide.md +470 -0
- package/{.claude → template/.claude}/commands/agentful-product.md +92 -8
- package/template/.claude/commands/agentful-start.md +432 -0
- package/{.claude → template/.claude}/commands/agentful-status.md +88 -3
- package/template/.claude/commands/agentful-update.md +402 -0
- package/template/.claude/commands/agentful-validate.md +369 -0
- package/{.claude → template/.claude}/commands/agentful.md +111 -195
- package/template/.claude/product/EXAMPLES.md +167 -0
- package/{.claude → template/.claude}/settings.json +9 -13
- package/{.claude → template/.claude}/skills/conversation/SKILL.md +13 -7
- package/template/.claude/skills/deployment/SKILL.md +116 -0
- package/template/.claude/skills/product-planning/SKILL.md +463 -0
- package/{.claude → template/.claude}/skills/product-tracking/SKILL.md +10 -21
- package/template/.claude/skills/testing/SKILL.md +228 -0
- package/template/.claude/skills/validation/SKILL.md +650 -0
- package/template/CLAUDE.md +84 -16
- package/template/bin/hooks/block-random-docs.js +121 -0
- package/version.json +1 -1
- package/.claude/agents/architect.md +0 -524
- package/.claude/agents/backend.md +0 -315
- package/.claude/agents/fixer.md +0 -263
- package/.claude/agents/frontend.md +0 -274
- package/.claude/agents/orchestrator.md +0 -283
- package/.claude/agents/product-analyzer.md +0 -799
- package/.claude/agents/reviewer.md +0 -332
- package/.claude/agents/tester.md +0 -410
- package/.claude/commands/agentful-decide.md +0 -214
- package/.claude/commands/agentful-start.md +0 -182
- package/.claude/commands/agentful-validate.md +0 -127
- package/.claude/product/EXAMPLES.md +0 -610
- package/.claude/product/README.md +0 -344
- package/.claude/skills/validation/SKILL.md +0 -271
- package/bin/hooks/analyze-trigger.sh +0 -57
- package/bin/hooks/health-check.sh +0 -36
- package/template/PRODUCT.md +0 -584
- /package/{.claude → template/.claude}/commands/agentful-generate.md +0 -0
- /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
|
+
*/
|