wogiflow 1.0.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.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- package/templates/trace.md +69 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Response Parser
|
|
5
|
+
*
|
|
6
|
+
* Cleans LLM responses to extract usable content.
|
|
7
|
+
* Handles common artifacts that cause "failures" which are actually fixable:
|
|
8
|
+
* - Thinking tags (<thinking>...</thinking>)
|
|
9
|
+
* - Markdown fences around code
|
|
10
|
+
* - Preambles before code ("Here's the code:", "Sure, I'll...", etc.)
|
|
11
|
+
* - Claude artifacts like <reflection> tags
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* const { parseResponse, cleanCodeBlock } = require('./flow-response-parser');
|
|
15
|
+
* const cleaned = parseResponse(llmResponse);
|
|
16
|
+
*
|
|
17
|
+
* Part of v1.8.0 - Council Review Fixes
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// ============================================================
|
|
21
|
+
// Core Parser Functions
|
|
22
|
+
// ============================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse and clean an LLM response
|
|
26
|
+
* @param {string} response - Raw LLM response
|
|
27
|
+
* @param {Object} options - Parsing options
|
|
28
|
+
* @returns {Object} - { content, artifacts, metadata }
|
|
29
|
+
*/
|
|
30
|
+
function parseResponse(response, options = {}) {
|
|
31
|
+
if (!response || typeof response !== 'string') {
|
|
32
|
+
return { content: '', artifacts: [], metadata: {} };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
stripThinking = true,
|
|
37
|
+
stripReflection = true,
|
|
38
|
+
extractCode = false,
|
|
39
|
+
removePreamble = true,
|
|
40
|
+
preserveMarkdown = false
|
|
41
|
+
} = options;
|
|
42
|
+
|
|
43
|
+
let content = response;
|
|
44
|
+
const artifacts = [];
|
|
45
|
+
const metadata = {};
|
|
46
|
+
|
|
47
|
+
// 1. Strip thinking tags
|
|
48
|
+
if (stripThinking) {
|
|
49
|
+
const thinkingMatch = content.match(/<thinking>([\s\S]*?)<\/thinking>/gi);
|
|
50
|
+
if (thinkingMatch) {
|
|
51
|
+
artifacts.push({ type: 'thinking', count: thinkingMatch.length });
|
|
52
|
+
content = content.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. Strip reflection tags
|
|
57
|
+
if (stripReflection) {
|
|
58
|
+
const reflectionMatch = content.match(/<reflection>([\s\S]*?)<\/reflection>/gi);
|
|
59
|
+
if (reflectionMatch) {
|
|
60
|
+
artifacts.push({ type: 'reflection', count: reflectionMatch.length });
|
|
61
|
+
content = content.replace(/<reflection>[\s\S]*?<\/reflection>/gi, '');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 3. Strip artifact tags
|
|
66
|
+
const artifactMatch = content.match(/<artifact[^>]*>([\s\S]*?)<\/artifact>/gi);
|
|
67
|
+
if (artifactMatch) {
|
|
68
|
+
artifacts.push({ type: 'artifact', count: artifactMatch.length });
|
|
69
|
+
// Extract content from within artifacts
|
|
70
|
+
const artifactContent = artifactMatch.map(a => {
|
|
71
|
+
const inner = a.match(/<artifact[^>]*>([\s\S]*?)<\/artifact>/i);
|
|
72
|
+
return inner ? inner[1].trim() : '';
|
|
73
|
+
}).join('\n\n');
|
|
74
|
+
content = artifactContent || content.replace(/<artifact[^>]*>[\s\S]*?<\/artifact>/gi, '');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4. Extract code from markdown fences if requested
|
|
78
|
+
if (extractCode) {
|
|
79
|
+
const codeBlocks = extractCodeBlocks(content);
|
|
80
|
+
if (codeBlocks.length > 0) {
|
|
81
|
+
metadata.codeBlocks = codeBlocks.length;
|
|
82
|
+
metadata.languages = [...new Set(codeBlocks.map(b => b.language).filter(Boolean))];
|
|
83
|
+
// Return just the code if only one block
|
|
84
|
+
if (codeBlocks.length === 1) {
|
|
85
|
+
content = codeBlocks[0].code;
|
|
86
|
+
} else {
|
|
87
|
+
// Multiple blocks - join with separators
|
|
88
|
+
content = codeBlocks.map(b => b.code).join('\n\n');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else if (!preserveMarkdown) {
|
|
92
|
+
// Just clean up the fences but keep structure
|
|
93
|
+
content = cleanMarkdownFences(content);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 5. Remove preambles
|
|
97
|
+
if (removePreamble) {
|
|
98
|
+
content = removePreambles(content);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 6. Clean up whitespace
|
|
102
|
+
content = content.trim();
|
|
103
|
+
content = content.replace(/\n{3,}/g, '\n\n'); // Max 2 consecutive newlines
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
content,
|
|
107
|
+
artifacts,
|
|
108
|
+
metadata,
|
|
109
|
+
wasModified: content !== response.trim()
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Extract code blocks from markdown
|
|
115
|
+
* @param {string} content - Content with potential code blocks
|
|
116
|
+
* @returns {Array} - Array of { language, code, raw }
|
|
117
|
+
*/
|
|
118
|
+
function extractCodeBlocks(content) {
|
|
119
|
+
const blocks = [];
|
|
120
|
+
const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
|
|
121
|
+
let match;
|
|
122
|
+
|
|
123
|
+
while ((match = codeBlockRegex.exec(content)) !== null) {
|
|
124
|
+
blocks.push({
|
|
125
|
+
language: match[1] || null,
|
|
126
|
+
code: match[2].trim(),
|
|
127
|
+
raw: match[0]
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return blocks;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Clean markdown fences while preserving code
|
|
136
|
+
* @param {string} content - Content with code fences
|
|
137
|
+
* @returns {string} - Content with cleaned fences
|
|
138
|
+
*/
|
|
139
|
+
function cleanMarkdownFences(content) {
|
|
140
|
+
// Keep the fence structure but clean up
|
|
141
|
+
return content
|
|
142
|
+
// Remove empty code blocks
|
|
143
|
+
.replace(/```\w*\n\s*```/g, '')
|
|
144
|
+
// Normalize code block spacing
|
|
145
|
+
.replace(/```(\w*)\n\n+/g, '```$1\n')
|
|
146
|
+
.replace(/\n\n+```/g, '\n```');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Remove common LLM preambles
|
|
151
|
+
* @param {string} content - Content with potential preambles
|
|
152
|
+
* @returns {string} - Content without preambles
|
|
153
|
+
*/
|
|
154
|
+
function removePreambles(content) {
|
|
155
|
+
// Common preamble patterns
|
|
156
|
+
const preamblePatterns = [
|
|
157
|
+
// Opening statements
|
|
158
|
+
/^(?:Here(?:'s| is) (?:the |your |a )?(?:code|implementation|solution|function|class|file)[^:]*?:?\s*\n+)/i,
|
|
159
|
+
/^(?:Sure[,!]?\s*(?:I(?:'ll| will| can)|let me)[^.]*?\.\s*\n+)/i,
|
|
160
|
+
/^(?:I(?:'ll| will| can)[^.]*?\.\s*\n+)/i,
|
|
161
|
+
/^(?:Let me[^.]*?\.\s*\n+)/i,
|
|
162
|
+
/^(?:Certainly[,!]?\s*(?:Here|I)[^.]*?\.\s*\n+)/i,
|
|
163
|
+
/^(?:Of course[,!]?\s*[^.]*?\.\s*\n+)/i,
|
|
164
|
+
/^(?:Absolutely[,!]?\s*[^.]*?\.\s*\n+)/i,
|
|
165
|
+
|
|
166
|
+
// Acknowledgments
|
|
167
|
+
/^(?:I understand[^.]*?\.\s*\n+)/i,
|
|
168
|
+
/^(?:Got it[,!]?\s*[^.]*?\.\s*\n+)/i,
|
|
169
|
+
/^(?:Understood[,!]?\s*[^.]*?\.\s*\n+)/i,
|
|
170
|
+
|
|
171
|
+
// Technical context
|
|
172
|
+
/^(?:Based on[^,]*?,\s*(?:here|I)[^.]*?\.\s*\n+)/i,
|
|
173
|
+
/^(?:Looking at[^,]*?,\s*[^.]*?\.\s*\n+)/i,
|
|
174
|
+
/^(?:After (?:reviewing|analyzing)[^,]*?,\s*[^.]*?\.\s*\n+)/i
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
let result = content;
|
|
178
|
+
|
|
179
|
+
for (const pattern of preamblePatterns) {
|
|
180
|
+
result = result.replace(pattern, '');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Clean a single code block response
|
|
188
|
+
* Specialized for when you expect just code
|
|
189
|
+
* @param {string} response - LLM response that should be code
|
|
190
|
+
* @param {string} expectedLanguage - Expected language (optional)
|
|
191
|
+
* @returns {string} - Clean code
|
|
192
|
+
*/
|
|
193
|
+
function cleanCodeBlock(response, expectedLanguage = null) {
|
|
194
|
+
if (!response) return '';
|
|
195
|
+
|
|
196
|
+
let content = response;
|
|
197
|
+
|
|
198
|
+
// Strip thinking/reflection first
|
|
199
|
+
content = content.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '');
|
|
200
|
+
content = content.replace(/<reflection>[\s\S]*?<\/reflection>/gi, '');
|
|
201
|
+
|
|
202
|
+
// Extract from markdown fence
|
|
203
|
+
const fenceMatch = content.match(/```(?:\w*)\n([\s\S]*?)```/);
|
|
204
|
+
if (fenceMatch) {
|
|
205
|
+
content = fenceMatch[1];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Remove preambles
|
|
209
|
+
content = removePreambles(content);
|
|
210
|
+
|
|
211
|
+
// Clean trailing explanations (after code)
|
|
212
|
+
const explanationPatterns = [
|
|
213
|
+
/\n+(?:This (?:code|function|implementation)[^.]*?\.|Note that[^.]*?\.|The above[^.]*?\.)[\s\S]*$/i,
|
|
214
|
+
/\n+(?:Key (?:changes|features|points)[^:]*?:[\s\S]*?)$/i,
|
|
215
|
+
/\n+(?:Explanation[^:]*?:[\s\S]*?)$/i
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
for (const pattern of explanationPatterns) {
|
|
219
|
+
content = content.replace(pattern, '');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return content.trim();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Detect if response needs parsing
|
|
227
|
+
* Quick check to avoid unnecessary processing
|
|
228
|
+
* @param {string} response - Raw response
|
|
229
|
+
* @returns {boolean} - True if parsing would modify the response
|
|
230
|
+
*/
|
|
231
|
+
function needsParsing(response) {
|
|
232
|
+
if (!response) return false;
|
|
233
|
+
|
|
234
|
+
// Check for artifacts that would be stripped
|
|
235
|
+
if (/<thinking>|<reflection>|<artifact/i.test(response)) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check for code fences
|
|
240
|
+
if (/```\w*\n/.test(response)) {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Check for common preambles
|
|
245
|
+
if (/^(?:Here's|Sure|I'll|Let me|Certainly)/i.test(response)) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Parse response on error retry (conservative mode)
|
|
254
|
+
* Only parses if there was an error and parsing might help
|
|
255
|
+
* @param {string} response - Raw response
|
|
256
|
+
* @param {Error} error - The error that occurred
|
|
257
|
+
* @returns {Object} - { content, shouldRetry }
|
|
258
|
+
*/
|
|
259
|
+
function parseOnRetry(response, error) {
|
|
260
|
+
// Check if this is a parse-fixable error
|
|
261
|
+
const parseFixableErrors = [
|
|
262
|
+
'SyntaxError',
|
|
263
|
+
'Unexpected token',
|
|
264
|
+
'Invalid JSON',
|
|
265
|
+
'Parse error'
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
const isParseFixable = parseFixableErrors.some(e =>
|
|
269
|
+
error?.message?.includes(e) || error?.name === e
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
if (!isParseFixable && !needsParsing(response)) {
|
|
273
|
+
return { content: response, shouldRetry: false };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const parsed = parseResponse(response, { extractCode: true });
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
content: parsed.content,
|
|
280
|
+
shouldRetry: parsed.wasModified,
|
|
281
|
+
artifacts: parsed.artifacts
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ============================================================
|
|
286
|
+
// Specialized Parsers
|
|
287
|
+
// ============================================================
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Parse JSON from LLM response
|
|
291
|
+
* @param {string} response - Response that should contain JSON
|
|
292
|
+
* @returns {Object|null} - Parsed JSON or null
|
|
293
|
+
*/
|
|
294
|
+
function parseJsonResponse(response) {
|
|
295
|
+
const cleaned = parseResponse(response, {
|
|
296
|
+
extractCode: true,
|
|
297
|
+
stripThinking: true,
|
|
298
|
+
stripReflection: true,
|
|
299
|
+
removePreamble: true
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
let content = cleaned.content;
|
|
303
|
+
|
|
304
|
+
// Try direct parse first
|
|
305
|
+
try {
|
|
306
|
+
return JSON.parse(content);
|
|
307
|
+
} catch {}
|
|
308
|
+
|
|
309
|
+
// Extract JSON from markdown fence
|
|
310
|
+
const jsonMatch = content.match(/```(?:json)?\n([\s\S]*?)```/);
|
|
311
|
+
if (jsonMatch) {
|
|
312
|
+
try {
|
|
313
|
+
return JSON.parse(jsonMatch[1].trim());
|
|
314
|
+
} catch {}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Look for JSON object pattern - find balanced braces
|
|
318
|
+
const objectStart = content.indexOf('{');
|
|
319
|
+
if (objectStart !== -1) {
|
|
320
|
+
const jsonStr = extractBalancedJson(content, objectStart, '{', '}');
|
|
321
|
+
if (jsonStr) {
|
|
322
|
+
try {
|
|
323
|
+
return JSON.parse(jsonStr);
|
|
324
|
+
} catch {}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Look for JSON array pattern - find balanced brackets
|
|
329
|
+
const arrayStart = content.indexOf('[');
|
|
330
|
+
if (arrayStart !== -1) {
|
|
331
|
+
const jsonStr = extractBalancedJson(content, arrayStart, '[', ']');
|
|
332
|
+
if (jsonStr) {
|
|
333
|
+
try {
|
|
334
|
+
return JSON.parse(jsonStr);
|
|
335
|
+
} catch {}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Extract a balanced JSON structure starting at the given index
|
|
344
|
+
* Handles nested structures correctly (won't match across unrelated objects)
|
|
345
|
+
*/
|
|
346
|
+
function extractBalancedJson(content, startIdx, openChar, closeChar) {
|
|
347
|
+
if (content[startIdx] !== openChar) return null;
|
|
348
|
+
|
|
349
|
+
let depth = 0;
|
|
350
|
+
let inString = false;
|
|
351
|
+
let escape = false;
|
|
352
|
+
|
|
353
|
+
for (let i = startIdx; i < content.length; i++) {
|
|
354
|
+
const char = content[i];
|
|
355
|
+
|
|
356
|
+
if (escape) {
|
|
357
|
+
escape = false;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (char === '\\' && inString) {
|
|
362
|
+
escape = true;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (char === '"' && !escape) {
|
|
367
|
+
inString = !inString;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (!inString) {
|
|
372
|
+
if (char === openChar) {
|
|
373
|
+
depth++;
|
|
374
|
+
} else if (char === closeChar) {
|
|
375
|
+
depth--;
|
|
376
|
+
if (depth === 0) {
|
|
377
|
+
return content.substring(startIdx, i + 1);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return null; // Unbalanced
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Parse file content from LLM response
|
|
388
|
+
* For when LLM generates file content
|
|
389
|
+
* @param {string} response - Response containing file content
|
|
390
|
+
* @param {string} filename - Expected filename (for language detection)
|
|
391
|
+
* @returns {string} - Clean file content
|
|
392
|
+
*/
|
|
393
|
+
function parseFileContent(response, filename = '') {
|
|
394
|
+
// Detect language from filename
|
|
395
|
+
const extMatch = filename.match(/\.(\w+)$/);
|
|
396
|
+
const ext = extMatch ? extMatch[1] : '';
|
|
397
|
+
|
|
398
|
+
const cleaned = parseResponse(response, {
|
|
399
|
+
extractCode: true,
|
|
400
|
+
stripThinking: true,
|
|
401
|
+
stripReflection: true,
|
|
402
|
+
removePreamble: true
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
let content = cleaned.content;
|
|
406
|
+
|
|
407
|
+
// If we got code blocks, use the one matching the expected language
|
|
408
|
+
if (cleaned.metadata.codeBlocks > 1) {
|
|
409
|
+
const blocks = extractCodeBlocks(response);
|
|
410
|
+
const languageMap = {
|
|
411
|
+
js: ['javascript', 'js'],
|
|
412
|
+
ts: ['typescript', 'ts'],
|
|
413
|
+
tsx: ['tsx', 'typescript'],
|
|
414
|
+
jsx: ['jsx', 'javascript'],
|
|
415
|
+
py: ['python', 'py'],
|
|
416
|
+
rs: ['rust', 'rs'],
|
|
417
|
+
go: ['go', 'golang']
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const expectedLangs = languageMap[ext] || [ext];
|
|
421
|
+
const matchingBlock = blocks.find(b =>
|
|
422
|
+
b.language && expectedLangs.includes(b.language.toLowerCase())
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
if (matchingBlock) {
|
|
426
|
+
content = matchingBlock.code;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return content;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ============================================================
|
|
434
|
+
// Exports
|
|
435
|
+
// ============================================================
|
|
436
|
+
|
|
437
|
+
module.exports = {
|
|
438
|
+
// Core functions
|
|
439
|
+
parseResponse,
|
|
440
|
+
extractCodeBlocks,
|
|
441
|
+
cleanMarkdownFences,
|
|
442
|
+
removePreambles,
|
|
443
|
+
cleanCodeBlock,
|
|
444
|
+
needsParsing,
|
|
445
|
+
parseOnRetry,
|
|
446
|
+
|
|
447
|
+
// Specialized parsers
|
|
448
|
+
parseJsonResponse,
|
|
449
|
+
parseFileContent
|
|
450
|
+
};
|