Merge main into massive-terminal-upgrade

Resolves merge conflicts:
- apps/server/src/routes/terminal/common.ts: Keep randomBytes import, use @automaker/utils for createLogger
- apps/ui/eslint.config.mjs: Use main's explicit globals list with XMLHttpRequest and MediaQueryListEvent additions
- apps/ui/src/components/views/terminal-view.tsx: Keep our terminal improvements (killAllSessions, beforeunload, better error handling)
- apps/ui/src/config/terminal-themes.ts: Keep our search highlight colors for all themes
- apps/ui/src/store/app-store.ts: Keep our terminal settings persistence improvements (merge function)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
SuperComboGamer
2025-12-21 20:27:44 -05:00
393 changed files with 32473 additions and 17974 deletions

View File

@@ -2,43 +2,43 @@
* Business logic for generating suggestions
*/
import { query } from "@anthropic-ai/claude-agent-sdk";
import type { EventEmitter } from "../../lib/events.js";
import { createLogger } from "../../lib/logger.js";
import { createSuggestionsOptions } from "../../lib/sdk-options.js";
import { query } from '@anthropic-ai/claude-agent-sdk';
import type { EventEmitter } from '../../lib/events.js';
import { createLogger } from '@automaker/utils';
import { createSuggestionsOptions } from '../../lib/sdk-options.js';
const logger = createLogger("Suggestions");
const logger = createLogger('Suggestions');
/**
* JSON Schema for suggestions output
*/
const suggestionsSchema = {
type: "object",
type: 'object',
properties: {
suggestions: {
type: "array",
type: 'array',
items: {
type: "object",
type: 'object',
properties: {
id: { type: "string" },
category: { type: "string" },
description: { type: "string" },
id: { type: 'string' },
category: { type: 'string' },
description: { type: 'string' },
steps: {
type: "array",
items: { type: "string" },
type: 'array',
items: { type: 'string' },
},
priority: {
type: "number",
priority: {
type: 'number',
minimum: 1,
maximum: 3,
},
reasoning: { type: "string" },
reasoning: { type: 'string' },
},
required: ["category", "description", "steps", "priority", "reasoning"],
required: ['category', 'description', 'steps', 'priority', 'reasoning'],
},
},
},
required: ["suggestions"],
required: ['suggestions'],
additionalProperties: false,
};
@@ -49,13 +49,10 @@ export async function generateSuggestions(
abortController: AbortController
): Promise<void> {
const typePrompts: Record<string, string> = {
features:
"Analyze this project and suggest new features that would add value.",
refactoring: "Analyze this project and identify refactoring opportunities.",
security:
"Analyze this project for security vulnerabilities and suggest fixes.",
performance:
"Analyze this project for performance issues and suggest optimizations.",
features: 'Analyze this project and suggest new features that would add value.',
refactoring: 'Analyze this project and identify refactoring opportunities.',
security: 'Analyze this project for security vulnerabilities and suggest fixes.',
performance: 'Analyze this project for performance issues and suggest optimizations.',
};
const prompt = `${typePrompts[suggestionType] || typePrompts.features}
@@ -71,8 +68,8 @@ For each suggestion, provide:
The response will be automatically formatted as structured JSON.`;
events.emit("suggestions:event", {
type: "suggestions_progress",
events.emit('suggestions:event', {
type: 'suggestions_progress',
content: `Starting ${suggestionType} analysis...\n`,
});
@@ -80,48 +77,48 @@ The response will be automatically formatted as structured JSON.`;
cwd: projectPath,
abortController,
outputFormat: {
type: "json_schema",
type: 'json_schema',
schema: suggestionsSchema,
},
});
const stream = query({ prompt, options });
let responseText = "";
let responseText = '';
let structuredOutput: { suggestions: Array<Record<string, unknown>> } | null = null;
for await (const msg of stream) {
if (msg.type === "assistant" && msg.message.content) {
if (msg.type === 'assistant' && msg.message.content) {
for (const block of msg.message.content) {
if (block.type === "text") {
if (block.type === 'text') {
responseText += block.text;
events.emit("suggestions:event", {
type: "suggestions_progress",
events.emit('suggestions:event', {
type: 'suggestions_progress',
content: block.text,
});
} else if (block.type === "tool_use") {
events.emit("suggestions:event", {
type: "suggestions_tool",
} else if (block.type === 'tool_use') {
events.emit('suggestions:event', {
type: 'suggestions_tool',
tool: block.name,
input: block.input,
});
}
}
} else if (msg.type === "result" && msg.subtype === "success") {
} else if (msg.type === 'result' && msg.subtype === 'success') {
// Check for structured output
const resultMsg = msg as any;
if (resultMsg.structured_output) {
structuredOutput = resultMsg.structured_output as {
suggestions: Array<Record<string, unknown>>;
};
logger.debug("Received structured output:", structuredOutput);
logger.debug('Received structured output:', structuredOutput);
}
} else if (msg.type === "result") {
} else if (msg.type === 'result') {
const resultMsg = msg as any;
if (resultMsg.subtype === "error_max_structured_output_retries") {
logger.error("Failed to produce valid structured output after retries");
throw new Error("Could not produce valid suggestions output");
} else if (resultMsg.subtype === "error_max_turns") {
logger.error("Hit max turns limit before completing suggestions generation");
if (resultMsg.subtype === 'error_max_structured_output_retries') {
logger.error('Failed to produce valid structured output after retries');
throw new Error('Could not produce valid suggestions output');
} else if (resultMsg.subtype === 'error_max_turns') {
logger.error('Hit max turns limit before completing suggestions generation');
logger.warn(`Response text length: ${responseText.length} chars`);
// Still try to parse what we have
}
@@ -132,49 +129,44 @@ The response will be automatically formatted as structured JSON.`;
try {
if (structuredOutput && structuredOutput.suggestions) {
// Use structured output directly
events.emit("suggestions:event", {
type: "suggestions_complete",
suggestions: structuredOutput.suggestions.map(
(s: Record<string, unknown>, i: number) => ({
...s,
id: s.id || `suggestion-${Date.now()}-${i}`,
})
),
events.emit('suggestions:event', {
type: 'suggestions_complete',
suggestions: structuredOutput.suggestions.map((s: Record<string, unknown>, i: number) => ({
...s,
id: s.id || `suggestion-${Date.now()}-${i}`,
})),
});
} else {
// Fallback: try to parse from text (for backwards compatibility)
logger.warn("No structured output received, attempting to parse from text");
logger.warn('No structured output received, attempting to parse from text');
const jsonMatch = responseText.match(/\{[\s\S]*"suggestions"[\s\S]*\}/);
if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[0]);
events.emit("suggestions:event", {
type: "suggestions_complete",
suggestions: parsed.suggestions.map(
(s: Record<string, unknown>, i: number) => ({
...s,
id: s.id || `suggestion-${Date.now()}-${i}`,
})
),
events.emit('suggestions:event', {
type: 'suggestions_complete',
suggestions: parsed.suggestions.map((s: Record<string, unknown>, i: number) => ({
...s,
id: s.id || `suggestion-${Date.now()}-${i}`,
})),
});
} else {
throw new Error("No valid JSON found in response");
throw new Error('No valid JSON found in response');
}
}
} catch (error) {
// Log the parsing error for debugging
logger.error("Failed to parse suggestions JSON from AI response:", error);
logger.error('Failed to parse suggestions JSON from AI response:', error);
// Return generic suggestions if parsing fails
events.emit("suggestions:event", {
type: "suggestions_complete",
events.emit('suggestions:event', {
type: 'suggestions_complete',
suggestions: [
{
id: `suggestion-${Date.now()}-0`,
category: "Analysis",
description: "Review the AI analysis output for insights",
steps: ["Review the generated analysis"],
category: 'Analysis',
description: 'Review the AI analysis output for insights',
steps: ['Review the generated analysis'],
priority: 1,
reasoning:
"The AI provided analysis but suggestions need manual review",
reasoning: 'The AI provided analysis but suggestions need manual review',
},
],
});