mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
Merge v0.8.0rc into feat/cursor-cli
Resolved conflicts: - sdk-options.ts: kept HEAD (MCP & thinking level features) - auto-mode-service.ts: kept HEAD (MCP features + fallback code) - agent-output-modal.tsx: used v0.8.0rc (effectiveViewMode + pr-8 spacing) - feature-suggestions-dialog.tsx: accepted deletion - electron.ts: used v0.8.0rc (Ideation types) - package-lock.json: regenerated Fixed sdk-options.test.ts to expect 'default' permissionMode for read-only operations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,55 +39,118 @@ export function formatModelName(model: string): string {
|
||||
return model.split('-').slice(1, 3).join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to extract a balanced JSON object from a string starting at a given position
|
||||
*/
|
||||
function extractJsonObject(str: string, startIdx: number): string | null {
|
||||
if (str[startIdx] !== '{') return null;
|
||||
|
||||
let depth = 0;
|
||||
let inString = false;
|
||||
let escapeNext = false;
|
||||
|
||||
for (let i = startIdx; i < str.length; i++) {
|
||||
const char = str[i];
|
||||
|
||||
if (escapeNext) {
|
||||
escapeNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '\\' && inString) {
|
||||
escapeNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '"' && !escapeNext) {
|
||||
inString = !inString;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inString) continue;
|
||||
|
||||
if (char === '{') depth++;
|
||||
else if (char === '}') {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
return str.slice(startIdx, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts todos from the context content
|
||||
* Looks for TodoWrite tool calls in the format:
|
||||
* TodoWrite: [{"content": "...", "status": "..."}]
|
||||
* 🔧 Tool: TodoWrite
|
||||
* Input: {"todos": [{"content": "...", "status": "..."}]}
|
||||
*/
|
||||
function extractTodos(content: string): AgentTaskInfo['todos'] {
|
||||
const todos: AgentTaskInfo['todos'] = [];
|
||||
|
||||
// Look for TodoWrite tool inputs
|
||||
const todoMatches = content.matchAll(
|
||||
/TodoWrite.*?(?:"todos"\s*:\s*)?(\[[\s\S]*?\](?=\s*(?:\}|$|🔧|📋|⚡|✅|❌)))/g
|
||||
);
|
||||
// Find all occurrences of TodoWrite tool calls
|
||||
const todoWriteMarker = '🔧 Tool: TodoWrite';
|
||||
let searchStart = 0;
|
||||
|
||||
for (const match of todoMatches) {
|
||||
try {
|
||||
// Try to find JSON array in the match
|
||||
const jsonStr = match[1] || match[0];
|
||||
const arrayMatch = jsonStr.match(/\[[\s\S]*?\]/);
|
||||
if (arrayMatch) {
|
||||
const parsed = JSON.parse(arrayMatch[0]);
|
||||
if (Array.isArray(parsed)) {
|
||||
for (const item of parsed) {
|
||||
while (true) {
|
||||
const markerIdx = content.indexOf(todoWriteMarker, searchStart);
|
||||
if (markerIdx === -1) break;
|
||||
|
||||
// Look for "Input:" after the marker
|
||||
const inputIdx = content.indexOf('Input:', markerIdx);
|
||||
if (inputIdx === -1 || inputIdx > markerIdx + 100) {
|
||||
searchStart = markerIdx + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the start of the JSON object
|
||||
const jsonStart = content.indexOf('{', inputIdx);
|
||||
if (jsonStart === -1) {
|
||||
searchStart = markerIdx + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract the complete JSON object
|
||||
const jsonStr = extractJsonObject(content, jsonStart);
|
||||
if (jsonStr) {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonStr) as {
|
||||
todos?: Array<{ content: string; status: string }>;
|
||||
};
|
||||
if (parsed.todos && Array.isArray(parsed.todos)) {
|
||||
// Clear previous todos - we want the latest state
|
||||
todos.length = 0;
|
||||
for (const item of parsed.todos) {
|
||||
if (item.content && item.status) {
|
||||
// Check if this todo already exists (avoid duplicates)
|
||||
if (!todos.some((t) => t.content === item.content)) {
|
||||
todos.push({
|
||||
content: item.content,
|
||||
status: item.status,
|
||||
});
|
||||
}
|
||||
todos.push({
|
||||
content: item.content,
|
||||
status: item.status as 'pending' | 'in_progress' | 'completed',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
|
||||
searchStart = markerIdx + 1;
|
||||
}
|
||||
|
||||
// Also try to extract from markdown task lists
|
||||
const markdownTodos = content.matchAll(/- \[([ xX])\] (.+)/g);
|
||||
for (const match of markdownTodos) {
|
||||
const isCompleted = match[1].toLowerCase() === 'x';
|
||||
const content = match[2].trim();
|
||||
if (!todos.some((t) => t.content === content)) {
|
||||
todos.push({
|
||||
content,
|
||||
status: isCompleted ? 'completed' : 'pending',
|
||||
});
|
||||
// Also try to extract from markdown task lists as fallback
|
||||
if (todos.length === 0) {
|
||||
const markdownTodos = content.matchAll(/- \[([ xX])\] (.+)/g);
|
||||
for (const match of markdownTodos) {
|
||||
const isCompleted = match[1].toLowerCase() === 'x';
|
||||
const todoContent = match[2].trim();
|
||||
if (!todos.some((t) => t.content === todoContent)) {
|
||||
todos.push({
|
||||
content: todoContent,
|
||||
status: isCompleted ? 'completed' : 'pending',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,3 +153,37 @@ export async function apiDeleteRaw(
|
||||
): Promise<Response> {
|
||||
return apiFetch(endpoint, 'DELETE', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an authenticated image URL for use in <img> tags or CSS background-image
|
||||
* Adds authentication via query parameter since headers can't be set for image loads
|
||||
*
|
||||
* @param path - Image path
|
||||
* @param projectPath - Project path
|
||||
* @param version - Optional cache-busting version
|
||||
* @returns Full URL with auth credentials
|
||||
*/
|
||||
export function getAuthenticatedImageUrl(
|
||||
path: string,
|
||||
projectPath: string,
|
||||
version?: string | number
|
||||
): string {
|
||||
const serverUrl = getServerUrl();
|
||||
const params = new URLSearchParams({
|
||||
path,
|
||||
projectPath,
|
||||
});
|
||||
|
||||
if (version !== undefined) {
|
||||
params.set('v', String(version));
|
||||
}
|
||||
|
||||
// Add auth credential as query param (needed for image loads that can't set headers)
|
||||
const apiKey = getApiKey();
|
||||
if (apiKey) {
|
||||
params.set('apiKey', apiKey);
|
||||
}
|
||||
// Note: Session token auth relies on cookies which are sent automatically by the browser
|
||||
|
||||
return `${serverUrl}/api/fs/image?${params.toString()}`;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Type definitions for Electron IPC API
|
||||
import { createLogger } from '@automaker/utils/logger';
|
||||
import type { SessionListItem, Message } from '@/types/electron';
|
||||
import type { ClaudeUsageResponse } from '@/store/app-store';
|
||||
import type {
|
||||
@@ -11,10 +10,21 @@ import type {
|
||||
IssueValidationResponse,
|
||||
IssueValidationEvent,
|
||||
StoredValidation,
|
||||
ModelAlias,
|
||||
AgentModel,
|
||||
GitHubComment,
|
||||
IssueCommentsResult,
|
||||
ThinkingLevel,
|
||||
Idea,
|
||||
IdeaCategory,
|
||||
IdeationSession,
|
||||
IdeationMessage,
|
||||
IdeationPrompt,
|
||||
PromptCategory,
|
||||
ProjectAnalysisResult,
|
||||
AnalysisSuggestion,
|
||||
StartSessionOptions,
|
||||
CreateIdeaInput,
|
||||
UpdateIdeaInput,
|
||||
ConvertToFeatureOptions,
|
||||
} from '@automaker/types';
|
||||
import { getJSON, setJSON, removeItem } from './storage';
|
||||
|
||||
@@ -32,6 +42,104 @@ export type {
|
||||
IssueCommentsResult,
|
||||
};
|
||||
|
||||
// Re-export ideation types
|
||||
export type {
|
||||
Idea,
|
||||
IdeaCategory,
|
||||
IdeationSession,
|
||||
IdeationMessage,
|
||||
IdeationPrompt,
|
||||
PromptCategory,
|
||||
ProjectAnalysisResult,
|
||||
AnalysisSuggestion,
|
||||
StartSessionOptions,
|
||||
CreateIdeaInput,
|
||||
UpdateIdeaInput,
|
||||
ConvertToFeatureOptions,
|
||||
};
|
||||
|
||||
// Ideation API interface
|
||||
export interface IdeationAPI {
|
||||
// Session management
|
||||
startSession: (
|
||||
projectPath: string,
|
||||
options?: StartSessionOptions
|
||||
) => Promise<{ success: boolean; session?: IdeationSession; error?: string }>;
|
||||
getSession: (
|
||||
projectPath: string,
|
||||
sessionId: string
|
||||
) => Promise<{
|
||||
success: boolean;
|
||||
session?: IdeationSession;
|
||||
messages?: IdeationMessage[];
|
||||
error?: string;
|
||||
}>;
|
||||
sendMessage: (
|
||||
sessionId: string,
|
||||
message: string,
|
||||
options?: { imagePaths?: string[]; model?: string }
|
||||
) => Promise<{ success: boolean; error?: string }>;
|
||||
stopSession: (sessionId: string) => Promise<{ success: boolean; error?: string }>;
|
||||
|
||||
// Ideas CRUD
|
||||
listIdeas: (projectPath: string) => Promise<{ success: boolean; ideas?: Idea[]; error?: string }>;
|
||||
createIdea: (
|
||||
projectPath: string,
|
||||
idea: CreateIdeaInput
|
||||
) => Promise<{ success: boolean; idea?: Idea; error?: string }>;
|
||||
getIdea: (
|
||||
projectPath: string,
|
||||
ideaId: string
|
||||
) => Promise<{ success: boolean; idea?: Idea; error?: string }>;
|
||||
updateIdea: (
|
||||
projectPath: string,
|
||||
ideaId: string,
|
||||
updates: UpdateIdeaInput
|
||||
) => Promise<{ success: boolean; idea?: Idea; error?: string }>;
|
||||
deleteIdea: (
|
||||
projectPath: string,
|
||||
ideaId: string
|
||||
) => Promise<{ success: boolean; error?: string }>;
|
||||
|
||||
// Project analysis
|
||||
analyzeProject: (
|
||||
projectPath: string
|
||||
) => Promise<{ success: boolean; analysis?: ProjectAnalysisResult; error?: string }>;
|
||||
|
||||
// Generate suggestions from a prompt
|
||||
generateSuggestions: (
|
||||
projectPath: string,
|
||||
promptId: string,
|
||||
category: IdeaCategory,
|
||||
count?: number
|
||||
) => Promise<{ success: boolean; suggestions?: AnalysisSuggestion[]; error?: string }>;
|
||||
|
||||
// Convert to feature
|
||||
convertToFeature: (
|
||||
projectPath: string,
|
||||
ideaId: string,
|
||||
options?: ConvertToFeatureOptions
|
||||
) => Promise<{ success: boolean; feature?: any; featureId?: string; error?: string }>;
|
||||
|
||||
// Add suggestion directly to board as feature
|
||||
addSuggestionToBoard: (
|
||||
projectPath: string,
|
||||
suggestion: AnalysisSuggestion
|
||||
) => Promise<{ success: boolean; featureId?: string; error?: string }>;
|
||||
|
||||
// Get guided prompts (single source of truth from backend)
|
||||
getPrompts: () => Promise<{
|
||||
success: boolean;
|
||||
prompts?: IdeationPrompt[];
|
||||
categories?: PromptCategory[];
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
// Event subscriptions
|
||||
onStream: (callback: (event: any) => void) => () => void;
|
||||
onAnalysisEvent: (callback: (event: any) => void) => () => void;
|
||||
}
|
||||
|
||||
export interface FileEntry {
|
||||
name: string;
|
||||
isDirectory: boolean;
|
||||
@@ -96,8 +204,6 @@ import type {
|
||||
ProviderStatus,
|
||||
} from '@/types/electron';
|
||||
|
||||
const logger = createLogger('Electron');
|
||||
|
||||
// Import HTTP API client (ES module)
|
||||
import { getHttpApiClient, getServerUrlSync } from './http-api-client';
|
||||
|
||||
@@ -208,8 +314,7 @@ export interface GitHubAPI {
|
||||
validateIssue: (
|
||||
projectPath: string,
|
||||
issue: IssueValidationInput,
|
||||
model?: ModelAlias,
|
||||
thinkingLevel?: string
|
||||
model?: AgentModel
|
||||
) => Promise<{ success: boolean; message?: string; issueNumber?: number; error?: string }>;
|
||||
/** Check validation status for an issue or all issues */
|
||||
getValidationStatus: (
|
||||
@@ -283,9 +388,7 @@ export type SuggestionType = 'features' | 'refactoring' | 'security' | 'performa
|
||||
export interface SuggestionsAPI {
|
||||
generate: (
|
||||
projectPath: string,
|
||||
suggestionType?: SuggestionType,
|
||||
model?: string,
|
||||
thinkingLevel?: ThinkingLevel
|
||||
suggestionType?: SuggestionType
|
||||
) => Promise<{ success: boolean; error?: string }>;
|
||||
stop: () => Promise<{ success: boolean; error?: string }>;
|
||||
status: () => Promise<{
|
||||
@@ -565,10 +668,7 @@ export interface ElectronAPI {
|
||||
isMac: boolean;
|
||||
isLinux: boolean;
|
||||
}>;
|
||||
verifyClaudeAuth: (
|
||||
authMethod?: 'cli' | 'api_key',
|
||||
apiKey?: string
|
||||
) => Promise<{
|
||||
verifyClaudeAuth: (authMethod?: 'cli' | 'api_key') => Promise<{
|
||||
success: boolean;
|
||||
authenticated: boolean;
|
||||
error?: string;
|
||||
@@ -582,19 +682,6 @@ export interface ElectronAPI {
|
||||
user: string | null;
|
||||
error?: string;
|
||||
}>;
|
||||
getCursorStatus?: () => Promise<{
|
||||
success: boolean;
|
||||
installed?: boolean;
|
||||
version?: string | null;
|
||||
path?: string | null;
|
||||
auth?: {
|
||||
authenticated: boolean;
|
||||
method: string;
|
||||
};
|
||||
installCommand?: string;
|
||||
loginCommand?: string;
|
||||
error?: string;
|
||||
}>;
|
||||
onInstallProgress?: (callback: (progress: any) => void) => () => void;
|
||||
onAuthProgress?: (callback: (progress: any) => void) => () => void;
|
||||
};
|
||||
@@ -612,8 +699,7 @@ export interface ElectronAPI {
|
||||
message: string,
|
||||
workingDirectory?: string,
|
||||
imagePaths?: string[],
|
||||
model?: string,
|
||||
thinkingLevel?: string
|
||||
model?: string
|
||||
) => Promise<{ success: boolean; error?: string }>;
|
||||
getHistory: (sessionId: string) => Promise<{
|
||||
success: boolean;
|
||||
@@ -671,6 +757,7 @@ export interface ElectronAPI {
|
||||
error?: string;
|
||||
}>;
|
||||
};
|
||||
ideation?: IdeationAPI;
|
||||
}
|
||||
|
||||
// Note: Window interface is declared in @/types/electron.d.ts
|
||||
@@ -781,8 +868,8 @@ export const getCurrentApiMode = (): 'http' => {
|
||||
// Debug helpers
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).__checkApiMode = () => {
|
||||
logger.info('Current API mode:', getCurrentApiMode());
|
||||
logger.info('isElectron():', isElectron());
|
||||
console.log('Current API mode:', getCurrentApiMode());
|
||||
console.log('isElectron():', isElectron());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1023,7 +1110,7 @@ const getMockElectronAPI = (): ElectronAPI => {
|
||||
// Store the image data in mock file system for testing
|
||||
mockFileSystem[tempFilePath] = data;
|
||||
|
||||
logger.info('Mock saved image to temp:', tempFilePath);
|
||||
console.log('[Mock] Saved image to temp:', tempFilePath);
|
||||
return { success: true, path: tempFilePath };
|
||||
},
|
||||
|
||||
@@ -1068,7 +1155,7 @@ const getMockElectronAPI = (): ElectronAPI => {
|
||||
// Mock Claude API
|
||||
claude: {
|
||||
getUsage: async () => {
|
||||
logger.info('Mock getting Claude usage');
|
||||
console.log('[Mock] Getting Claude usage');
|
||||
return {
|
||||
sessionTokensUsed: 0,
|
||||
sessionLimit: 0,
|
||||
@@ -1150,10 +1237,7 @@ interface SetupAPI {
|
||||
isMac: boolean;
|
||||
isLinux: boolean;
|
||||
}>;
|
||||
verifyClaudeAuth: (
|
||||
authMethod?: 'cli' | 'api_key',
|
||||
apiKey?: string
|
||||
) => Promise<{
|
||||
verifyClaudeAuth: (authMethod?: 'cli' | 'api_key') => Promise<{
|
||||
success: boolean;
|
||||
authenticated: boolean;
|
||||
error?: string;
|
||||
@@ -1175,7 +1259,7 @@ interface SetupAPI {
|
||||
function createMockSetupAPI(): SetupAPI {
|
||||
return {
|
||||
getClaudeStatus: async () => {
|
||||
logger.info('Mock Getting Claude status');
|
||||
console.log('[Mock] Getting Claude status');
|
||||
return {
|
||||
success: true,
|
||||
status: 'not_installed',
|
||||
@@ -1192,7 +1276,7 @@ function createMockSetupAPI(): SetupAPI {
|
||||
},
|
||||
|
||||
installClaude: async () => {
|
||||
logger.info('Mock Installing Claude CLI');
|
||||
console.log('[Mock] Installing Claude CLI');
|
||||
// Simulate installation delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
return {
|
||||
@@ -1203,7 +1287,7 @@ function createMockSetupAPI(): SetupAPI {
|
||||
},
|
||||
|
||||
authClaude: async () => {
|
||||
logger.info('Mock Auth Claude CLI');
|
||||
console.log('[Mock] Auth Claude CLI');
|
||||
return {
|
||||
success: true,
|
||||
requiresManualAuth: true,
|
||||
@@ -1212,13 +1296,13 @@ function createMockSetupAPI(): SetupAPI {
|
||||
},
|
||||
|
||||
storeApiKey: async (provider: string, apiKey: string) => {
|
||||
logger.info('Mock Storing API key for:', provider);
|
||||
console.log('[Mock] Storing API key for:', provider);
|
||||
// In mock mode, we just pretend to store it (it's already in the app store)
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
getApiKeys: async () => {
|
||||
logger.info('Mock Getting API keys');
|
||||
console.log('[Mock] Getting API keys');
|
||||
return {
|
||||
success: true,
|
||||
hasAnthropicKey: false,
|
||||
@@ -1227,7 +1311,7 @@ function createMockSetupAPI(): SetupAPI {
|
||||
},
|
||||
|
||||
deleteApiKey: async (provider: string) => {
|
||||
logger.info('Mock Deleting API key for:', provider);
|
||||
console.log('[Mock] Deleting API key for:', provider);
|
||||
return { success: true, message: `API key for ${provider} deleted` };
|
||||
},
|
||||
|
||||
@@ -1243,12 +1327,8 @@ function createMockSetupAPI(): SetupAPI {
|
||||
};
|
||||
},
|
||||
|
||||
verifyClaudeAuth: async (authMethod?: 'cli' | 'api_key', apiKey?: string) => {
|
||||
logger.info(
|
||||
'Mock verifying Claude auth with method:',
|
||||
authMethod,
|
||||
apiKey ? '(with key)' : ''
|
||||
);
|
||||
verifyClaudeAuth: async (authMethod?: 'cli' | 'api_key') => {
|
||||
console.log('[Mock] Verifying Claude auth with method:', authMethod);
|
||||
// Mock always returns not authenticated
|
||||
return {
|
||||
success: true,
|
||||
@@ -1258,7 +1338,7 @@ function createMockSetupAPI(): SetupAPI {
|
||||
},
|
||||
|
||||
getGhStatus: async () => {
|
||||
logger.info('Mock Getting GitHub CLI status');
|
||||
console.log('[Mock] Getting GitHub CLI status');
|
||||
return {
|
||||
success: true,
|
||||
installed: false,
|
||||
@@ -1285,7 +1365,7 @@ function createMockSetupAPI(): SetupAPI {
|
||||
function createMockWorktreeAPI(): WorktreeAPI {
|
||||
return {
|
||||
mergeFeature: async (projectPath: string, featureId: string, options?: object) => {
|
||||
logger.info('Mock Merging feature:', {
|
||||
console.log('[Mock] Merging feature:', {
|
||||
projectPath,
|
||||
featureId,
|
||||
options,
|
||||
@@ -1294,7 +1374,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
getInfo: async (projectPath: string, featureId: string) => {
|
||||
logger.info('Mock Getting worktree info:', { projectPath, featureId });
|
||||
console.log('[Mock] Getting worktree info:', { projectPath, featureId });
|
||||
return {
|
||||
success: true,
|
||||
worktreePath: `/mock/worktrees/${featureId}`,
|
||||
@@ -1304,7 +1384,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
getStatus: async (projectPath: string, featureId: string) => {
|
||||
logger.info('Mock Getting worktree status:', {
|
||||
console.log('[Mock] Getting worktree status:', {
|
||||
projectPath,
|
||||
featureId,
|
||||
});
|
||||
@@ -1318,12 +1398,12 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
list: async (projectPath: string) => {
|
||||
logger.info('Mock Listing worktrees:', { projectPath });
|
||||
console.log('[Mock] Listing worktrees:', { projectPath });
|
||||
return { success: true, worktrees: [] };
|
||||
},
|
||||
|
||||
listAll: async (projectPath: string, includeDetails?: boolean) => {
|
||||
logger.info('Mock Listing all worktrees:', {
|
||||
console.log('[Mock] Listing all worktrees:', {
|
||||
projectPath,
|
||||
includeDetails,
|
||||
});
|
||||
@@ -1344,7 +1424,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
create: async (projectPath: string, branchName: string, baseBranch?: string) => {
|
||||
logger.info('Mock Creating worktree:', {
|
||||
console.log('[Mock] Creating worktree:', {
|
||||
projectPath,
|
||||
branchName,
|
||||
baseBranch,
|
||||
@@ -1360,7 +1440,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
delete: async (projectPath: string, worktreePath: string, deleteBranch?: boolean) => {
|
||||
logger.info('Mock Deleting worktree:', {
|
||||
console.log('[Mock] Deleting worktree:', {
|
||||
projectPath,
|
||||
worktreePath,
|
||||
deleteBranch,
|
||||
@@ -1375,7 +1455,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
commit: async (worktreePath: string, message: string) => {
|
||||
logger.info('Mock Committing changes:', { worktreePath, message });
|
||||
console.log('[Mock] Committing changes:', { worktreePath, message });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1388,7 +1468,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
push: async (worktreePath: string, force?: boolean) => {
|
||||
logger.info('Mock Pushing worktree:', { worktreePath, force });
|
||||
console.log('[Mock] Pushing worktree:', { worktreePath, force });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1400,7 +1480,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
createPR: async (worktreePath: string, options?: any) => {
|
||||
logger.info('Mock Creating PR:', { worktreePath, options });
|
||||
console.log('[Mock] Creating PR:', { worktreePath, options });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1415,7 +1495,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
getDiffs: async (projectPath: string, featureId: string) => {
|
||||
logger.info('Mock Getting file diffs:', { projectPath, featureId });
|
||||
console.log('[Mock] Getting file diffs:', { projectPath, featureId });
|
||||
return {
|
||||
success: true,
|
||||
diff: "diff --git a/src/feature.ts b/src/feature.ts\n+++ new file\n@@ -0,0 +1,10 @@\n+export function feature() {\n+ return 'hello';\n+}",
|
||||
@@ -1428,7 +1508,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
getFileDiff: async (projectPath: string, featureId: string, filePath: string) => {
|
||||
logger.info('Mock Getting file diff:', {
|
||||
console.log('[Mock] Getting file diff:', {
|
||||
projectPath,
|
||||
featureId,
|
||||
filePath,
|
||||
@@ -1441,7 +1521,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
pull: async (worktreePath: string) => {
|
||||
logger.info('Mock Pulling latest changes for:', worktreePath);
|
||||
console.log('[Mock] Pulling latest changes for:', worktreePath);
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1453,7 +1533,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
checkoutBranch: async (worktreePath: string, branchName: string) => {
|
||||
logger.info('Mock Creating and checking out branch:', {
|
||||
console.log('[Mock] Creating and checking out branch:', {
|
||||
worktreePath,
|
||||
branchName,
|
||||
});
|
||||
@@ -1468,7 +1548,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
listBranches: async (worktreePath: string) => {
|
||||
logger.info('Mock Listing branches for:', worktreePath);
|
||||
console.log('[Mock] Listing branches for:', worktreePath);
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1485,7 +1565,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
switchBranch: async (worktreePath: string, branchName: string) => {
|
||||
logger.info('Mock Switching to branch:', { worktreePath, branchName });
|
||||
console.log('[Mock] Switching to branch:', { worktreePath, branchName });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1497,7 +1577,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
openInEditor: async (worktreePath: string) => {
|
||||
logger.info('Mock Opening in editor:', worktreePath);
|
||||
console.log('[Mock] Opening in editor:', worktreePath);
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1508,7 +1588,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
getDefaultEditor: async () => {
|
||||
logger.info('Mock Getting default editor');
|
||||
console.log('[Mock] Getting default editor');
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1519,7 +1599,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
initGit: async (projectPath: string) => {
|
||||
logger.info('Mock Initializing git:', projectPath);
|
||||
console.log('[Mock] Initializing git:', projectPath);
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1530,7 +1610,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
startDevServer: async (projectPath: string, worktreePath: string) => {
|
||||
logger.info('Mock Starting dev server:', { projectPath, worktreePath });
|
||||
console.log('[Mock] Starting dev server:', { projectPath, worktreePath });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1543,7 +1623,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
stopDevServer: async (worktreePath: string) => {
|
||||
logger.info('Mock Stopping dev server:', worktreePath);
|
||||
console.log('[Mock] Stopping dev server:', worktreePath);
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1554,7 +1634,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
listDevServers: async () => {
|
||||
logger.info('Mock Listing dev servers');
|
||||
console.log('[Mock] Listing dev servers');
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1564,7 +1644,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
|
||||
getPRInfo: async (worktreePath: string, branchName: string) => {
|
||||
logger.info('Mock Getting PR info:', { worktreePath, branchName });
|
||||
console.log('[Mock] Getting PR info:', { worktreePath, branchName });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
@@ -1580,7 +1660,7 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
function createMockGitAPI(): GitAPI {
|
||||
return {
|
||||
getDiffs: async (projectPath: string) => {
|
||||
logger.info('Mock Getting git diffs for project:', { projectPath });
|
||||
console.log('[Mock] Getting git diffs for project:', { projectPath });
|
||||
return {
|
||||
success: true,
|
||||
diff: "diff --git a/src/feature.ts b/src/feature.ts\n+++ new file\n@@ -0,0 +1,10 @@\n+export function feature() {\n+ return 'hello';\n+}",
|
||||
@@ -1593,7 +1673,7 @@ function createMockGitAPI(): GitAPI {
|
||||
},
|
||||
|
||||
getFileDiff: async (projectPath: string, filePath: string) => {
|
||||
logger.info('Mock Getting git file diff:', { projectPath, filePath });
|
||||
console.log('[Mock] Getting git file diff:', { projectPath, filePath });
|
||||
return {
|
||||
success: true,
|
||||
diff: `diff --git a/${filePath} b/${filePath}\n+++ new file\n@@ -0,0 +1,5 @@\n+// New content`,
|
||||
@@ -1617,7 +1697,7 @@ function createMockAutoModeAPI(): AutoModeAPI {
|
||||
}
|
||||
|
||||
mockAutoModeRunning = true;
|
||||
logger.info(`Mock auto mode started with maxConcurrency: ${maxConcurrency || 3}`);
|
||||
console.log(`[Mock] Auto mode started with maxConcurrency: ${maxConcurrency || 3}`);
|
||||
const featureId = 'auto-mode-0';
|
||||
mockRunningFeatures.add(featureId);
|
||||
|
||||
@@ -1686,8 +1766,8 @@ function createMockAutoModeAPI(): AutoModeAPI {
|
||||
};
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Mock running feature ${featureId} with useWorktrees: ${useWorktrees}, worktreePath: ${worktreePath}`
|
||||
console.log(
|
||||
`[Mock] Running feature ${featureId} with useWorktrees: ${useWorktrees}, worktreePath: ${worktreePath}`
|
||||
);
|
||||
mockRunningFeatures.add(featureId);
|
||||
simulateAutoModeLoop(projectPath, featureId);
|
||||
@@ -1854,7 +1934,7 @@ function createMockAutoModeAPI(): AutoModeAPI {
|
||||
};
|
||||
}
|
||||
|
||||
logger.info('Mock Follow-up feature:', {
|
||||
console.log('[Mock] Follow-up feature:', {
|
||||
featureId,
|
||||
prompt,
|
||||
imagePaths,
|
||||
@@ -1871,7 +1951,7 @@ function createMockAutoModeAPI(): AutoModeAPI {
|
||||
},
|
||||
|
||||
commitFeature: async (projectPath: string, featureId: string, worktreePath?: string) => {
|
||||
logger.info('Mock Committing feature:', {
|
||||
console.log('[Mock] Committing feature:', {
|
||||
projectPath,
|
||||
featureId,
|
||||
worktreePath,
|
||||
@@ -1916,7 +1996,7 @@ function createMockAutoModeAPI(): AutoModeAPI {
|
||||
editedPlan?: string,
|
||||
feedback?: string
|
||||
) => {
|
||||
logger.info('Mock Plan approval:', {
|
||||
console.log('[Mock] Plan approval:', {
|
||||
projectPath,
|
||||
featureId,
|
||||
approved,
|
||||
@@ -2068,12 +2148,7 @@ let mockSuggestionsTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
function createMockSuggestionsAPI(): SuggestionsAPI {
|
||||
return {
|
||||
generate: async (
|
||||
projectPath: string,
|
||||
suggestionType: SuggestionType = 'features',
|
||||
model?: string,
|
||||
thinkingLevel?: ThinkingLevel
|
||||
) => {
|
||||
generate: async (projectPath: string, suggestionType: SuggestionType = 'features') => {
|
||||
if (mockSuggestionsRunning) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -2082,11 +2157,7 @@ function createMockSuggestionsAPI(): SuggestionsAPI {
|
||||
}
|
||||
|
||||
mockSuggestionsRunning = true;
|
||||
logger.info(
|
||||
`Mock generating ${suggestionType} suggestions for: ${projectPath}` +
|
||||
(model ? ` with model: ${model}` : '') +
|
||||
(thinkingLevel ? ` thinkingLevel: ${thinkingLevel}` : '')
|
||||
);
|
||||
console.log(`[Mock] Generating ${suggestionType} suggestions for: ${projectPath}`);
|
||||
|
||||
// Simulate async suggestion generation
|
||||
simulateSuggestionsGeneration(suggestionType);
|
||||
@@ -2310,8 +2381,8 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
|
||||
}
|
||||
|
||||
mockSpecRegenerationRunning = true;
|
||||
logger.info(
|
||||
`Mock creating initial spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}`
|
||||
console.log(
|
||||
`[Mock] Creating initial spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}`
|
||||
);
|
||||
|
||||
// Simulate async spec creation
|
||||
@@ -2335,8 +2406,8 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
|
||||
}
|
||||
|
||||
mockSpecRegenerationRunning = true;
|
||||
logger.info(
|
||||
`Mock regenerating spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}`
|
||||
console.log(
|
||||
`[Mock] Regenerating spec for: ${projectPath}, generateFeatures: ${generateFeatures}, maxFeatures: ${maxFeatures}`
|
||||
);
|
||||
|
||||
// Simulate async spec regeneration
|
||||
@@ -2354,8 +2425,8 @@ function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
|
||||
}
|
||||
|
||||
mockSpecRegenerationRunning = true;
|
||||
logger.info(
|
||||
`Mock generating features from existing spec for: ${projectPath}, maxFeatures: ${maxFeatures}`
|
||||
console.log(
|
||||
`[Mock] Generating features from existing spec for: ${projectPath}, maxFeatures: ${maxFeatures}`
|
||||
);
|
||||
|
||||
// Simulate async feature generation
|
||||
@@ -2620,7 +2691,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
|
||||
// Store features in mock file system using features/{id}/feature.json pattern
|
||||
return {
|
||||
getAll: async (projectPath: string) => {
|
||||
logger.info('Mock Getting all features for:', projectPath);
|
||||
console.log('[Mock] Getting all features for:', projectPath);
|
||||
|
||||
// Check if test has set mock features via global variable
|
||||
const testFeatures = (window as any).__mockFeatures;
|
||||
@@ -2645,7 +2716,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
|
||||
features.push(feature);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Mock Failed to parse feature:', error);
|
||||
console.error('[Mock] Failed to parse feature:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2658,7 +2729,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
|
||||
},
|
||||
|
||||
get: async (projectPath: string, featureId: string) => {
|
||||
logger.info('Mock Getting feature:', { projectPath, featureId });
|
||||
console.log('[Mock] Getting feature:', { projectPath, featureId });
|
||||
const featurePath = `${projectPath}/.automaker/features/${featureId}/feature.json`;
|
||||
const content = mockFileSystem[featurePath];
|
||||
if (content) {
|
||||
@@ -2668,7 +2739,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
|
||||
},
|
||||
|
||||
create: async (projectPath: string, feature: Feature) => {
|
||||
logger.info('Mock Creating feature:', {
|
||||
console.log('[Mock] Creating feature:', {
|
||||
projectPath,
|
||||
featureId: feature.id,
|
||||
});
|
||||
@@ -2678,7 +2749,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
|
||||
},
|
||||
|
||||
update: async (projectPath: string, featureId: string, updates: Partial<Feature>) => {
|
||||
logger.info('Mock Updating feature:', {
|
||||
console.log('[Mock] Updating feature:', {
|
||||
projectPath,
|
||||
featureId,
|
||||
updates,
|
||||
@@ -2694,7 +2765,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
|
||||
},
|
||||
|
||||
delete: async (projectPath: string, featureId: string) => {
|
||||
logger.info('Mock Deleting feature:', { projectPath, featureId });
|
||||
console.log('[Mock] Deleting feature:', { projectPath, featureId });
|
||||
const featurePath = `${projectPath}/.automaker/features/${featureId}/feature.json`;
|
||||
delete mockFileSystem[featurePath];
|
||||
// Also delete agent-output.md if it exists
|
||||
@@ -2704,14 +2775,14 @@ function createMockFeaturesAPI(): FeaturesAPI {
|
||||
},
|
||||
|
||||
getAgentOutput: async (projectPath: string, featureId: string) => {
|
||||
logger.info('Mock Getting agent output:', { projectPath, featureId });
|
||||
console.log('[Mock] Getting agent output:', { projectPath, featureId });
|
||||
const agentOutputPath = `${projectPath}/.automaker/features/${featureId}/agent-output.md`;
|
||||
const content = mockFileSystem[agentOutputPath];
|
||||
return { success: true, content: content || null };
|
||||
},
|
||||
|
||||
generateTitle: async (description: string) => {
|
||||
logger.info('Mock Generating title for:', description.substring(0, 50));
|
||||
console.log('[Mock] Generating title for:', description.substring(0, 50));
|
||||
// Mock title generation - just take first few words
|
||||
const words = description.split(/\s+/).slice(0, 6).join(' ');
|
||||
const title = words.length > 40 ? words.substring(0, 40) + '...' : words;
|
||||
@@ -2724,7 +2795,7 @@ function createMockFeaturesAPI(): FeaturesAPI {
|
||||
function createMockRunningAgentsAPI(): RunningAgentsAPI {
|
||||
return {
|
||||
getAll: async () => {
|
||||
logger.info('Mock Getting all running agents');
|
||||
console.log('[Mock] Getting all running agents');
|
||||
// Return running agents from mock auto mode state
|
||||
const runningAgents: RunningAgent[] = Array.from(mockRunningFeatures).map((featureId) => ({
|
||||
featureId,
|
||||
@@ -2749,7 +2820,7 @@ let mockValidationCallbacks: ((event: IssueValidationEvent) => void)[] = [];
|
||||
function createMockGitHubAPI(): GitHubAPI {
|
||||
return {
|
||||
checkRemote: async (projectPath: string) => {
|
||||
logger.info('Mock Checking GitHub remote for:', projectPath);
|
||||
console.log('[Mock] Checking GitHub remote for:', projectPath);
|
||||
return {
|
||||
success: true,
|
||||
hasGitHubRemote: false,
|
||||
@@ -2759,7 +2830,7 @@ function createMockGitHubAPI(): GitHubAPI {
|
||||
};
|
||||
},
|
||||
listIssues: async (projectPath: string) => {
|
||||
logger.info('Mock Listing GitHub issues for:', projectPath);
|
||||
console.log('[Mock] Listing GitHub issues for:', projectPath);
|
||||
return {
|
||||
success: true,
|
||||
openIssues: [],
|
||||
@@ -2767,15 +2838,15 @@ function createMockGitHubAPI(): GitHubAPI {
|
||||
};
|
||||
},
|
||||
listPRs: async (projectPath: string) => {
|
||||
logger.info('Mock Listing GitHub PRs for:', projectPath);
|
||||
console.log('[Mock] Listing GitHub PRs for:', projectPath);
|
||||
return {
|
||||
success: true,
|
||||
openPRs: [],
|
||||
mergedPRs: [],
|
||||
};
|
||||
},
|
||||
validateIssue: async (projectPath: string, issue: IssueValidationInput, model?: ModelAlias) => {
|
||||
logger.info('Mock Starting async validation:', { projectPath, issue, model });
|
||||
validateIssue: async (projectPath: string, issue: IssueValidationInput, model?: AgentModel) => {
|
||||
console.log('[Mock] Starting async validation:', { projectPath, issue, model });
|
||||
|
||||
// Simulate async validation in background
|
||||
setTimeout(() => {
|
||||
@@ -2816,7 +2887,7 @@ function createMockGitHubAPI(): GitHubAPI {
|
||||
};
|
||||
},
|
||||
getValidationStatus: async (projectPath: string, issueNumber?: number) => {
|
||||
logger.info('Mock Getting validation status:', { projectPath, issueNumber });
|
||||
console.log('[Mock] Getting validation status:', { projectPath, issueNumber });
|
||||
return {
|
||||
success: true,
|
||||
isRunning: false,
|
||||
@@ -2824,21 +2895,21 @@ function createMockGitHubAPI(): GitHubAPI {
|
||||
};
|
||||
},
|
||||
stopValidation: async (projectPath: string, issueNumber: number) => {
|
||||
logger.info('Mock Stopping validation:', { projectPath, issueNumber });
|
||||
console.log('[Mock] Stopping validation:', { projectPath, issueNumber });
|
||||
return {
|
||||
success: true,
|
||||
message: `Validation for issue #${issueNumber} stopped`,
|
||||
};
|
||||
},
|
||||
getValidations: async (projectPath: string, issueNumber?: number) => {
|
||||
logger.info('Mock Getting validations:', { projectPath, issueNumber });
|
||||
console.log('[Mock] Getting validations:', { projectPath, issueNumber });
|
||||
return {
|
||||
success: true,
|
||||
validations: [],
|
||||
};
|
||||
},
|
||||
markValidationViewed: async (projectPath: string, issueNumber: number) => {
|
||||
logger.info('Mock Marking validation as viewed:', { projectPath, issueNumber });
|
||||
console.log('[Mock] Marking validation as viewed:', { projectPath, issueNumber });
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
@@ -2850,7 +2921,7 @@ function createMockGitHubAPI(): GitHubAPI {
|
||||
};
|
||||
},
|
||||
getIssueComments: async (projectPath: string, issueNumber: number, cursor?: string) => {
|
||||
logger.info('Mock Getting issue comments:', { projectPath, issueNumber, cursor });
|
||||
console.log('[Mock] Getting issue comments:', { projectPath, issueNumber, cursor });
|
||||
return {
|
||||
success: true,
|
||||
comments: [],
|
||||
|
||||
@@ -27,6 +27,13 @@ import type {
|
||||
GitHubPR,
|
||||
IssueValidationInput,
|
||||
IssueValidationEvent,
|
||||
IdeationAPI,
|
||||
IdeaCategory,
|
||||
AnalysisSuggestion,
|
||||
StartSessionOptions,
|
||||
CreateIdeaInput,
|
||||
UpdateIdeaInput,
|
||||
ConvertToFeatureOptions,
|
||||
} from './electron';
|
||||
import type { Message, SessionListItem } from '@/types/electron';
|
||||
import type { Feature, ClaudeUsageResponse } from '@/store/app-store';
|
||||
@@ -371,7 +378,9 @@ type EventType =
|
||||
| 'suggestions:event'
|
||||
| 'spec-regeneration:event'
|
||||
| 'issue-validation:event'
|
||||
| 'backlog-plan:event';
|
||||
| 'backlog-plan:event'
|
||||
| 'ideation:stream'
|
||||
| 'ideation:analysis';
|
||||
|
||||
type EventCallback = (payload: unknown) => void;
|
||||
|
||||
@@ -1577,8 +1586,6 @@ export class HttpApiClient implements ElectronAPI {
|
||||
headers?: Record<string, string>;
|
||||
enabled?: boolean;
|
||||
}>;
|
||||
mcpAutoApproveTools?: boolean;
|
||||
mcpUnrestrictedTools?: boolean;
|
||||
};
|
||||
error?: string;
|
||||
}> => this.get('/api/settings/global'),
|
||||
@@ -1779,6 +1786,66 @@ export class HttpApiClient implements ElectronAPI {
|
||||
},
|
||||
};
|
||||
|
||||
// Ideation API - brainstorming and idea management
|
||||
ideation: IdeationAPI = {
|
||||
startSession: (projectPath: string, options?: StartSessionOptions) =>
|
||||
this.post('/api/ideation/session/start', { projectPath, options }),
|
||||
|
||||
getSession: (projectPath: string, sessionId: string) =>
|
||||
this.post('/api/ideation/session/get', { projectPath, sessionId }),
|
||||
|
||||
sendMessage: (
|
||||
sessionId: string,
|
||||
message: string,
|
||||
options?: { imagePaths?: string[]; model?: string }
|
||||
) => this.post('/api/ideation/session/message', { sessionId, message, options }),
|
||||
|
||||
stopSession: (sessionId: string) => this.post('/api/ideation/session/stop', { sessionId }),
|
||||
|
||||
listIdeas: (projectPath: string) => this.post('/api/ideation/ideas/list', { projectPath }),
|
||||
|
||||
createIdea: (projectPath: string, idea: CreateIdeaInput) =>
|
||||
this.post('/api/ideation/ideas/create', { projectPath, idea }),
|
||||
|
||||
getIdea: (projectPath: string, ideaId: string) =>
|
||||
this.post('/api/ideation/ideas/get', { projectPath, ideaId }),
|
||||
|
||||
updateIdea: (projectPath: string, ideaId: string, updates: UpdateIdeaInput) =>
|
||||
this.post('/api/ideation/ideas/update', { projectPath, ideaId, updates }),
|
||||
|
||||
deleteIdea: (projectPath: string, ideaId: string) =>
|
||||
this.post('/api/ideation/ideas/delete', { projectPath, ideaId }),
|
||||
|
||||
analyzeProject: (projectPath: string) => this.post('/api/ideation/analyze', { projectPath }),
|
||||
|
||||
generateSuggestions: (
|
||||
projectPath: string,
|
||||
promptId: string,
|
||||
category: IdeaCategory,
|
||||
count?: number
|
||||
) =>
|
||||
this.post('/api/ideation/suggestions/generate', { projectPath, promptId, category, count }),
|
||||
|
||||
convertToFeature: (projectPath: string, ideaId: string, options?: ConvertToFeatureOptions) =>
|
||||
this.post('/api/ideation/convert', { projectPath, ideaId, ...options }),
|
||||
|
||||
addSuggestionToBoard: (
|
||||
projectPath: string,
|
||||
suggestion: AnalysisSuggestion
|
||||
): Promise<{ success: boolean; featureId?: string; error?: string }> =>
|
||||
this.post('/api/ideation/add-suggestion', { projectPath, suggestion }),
|
||||
|
||||
getPrompts: () => this.get('/api/ideation/prompts'),
|
||||
|
||||
onStream: (callback: (event: any) => void): (() => void) => {
|
||||
return this.subscribeToEvent('ideation:stream', callback as EventCallback);
|
||||
},
|
||||
|
||||
onAnalysisEvent: (callback: (event: any) => void): (() => void) => {
|
||||
return this.subscribeToEvent('ideation:analysis', callback as EventCallback);
|
||||
},
|
||||
};
|
||||
|
||||
// MCP API - Test MCP server connections and list tools
|
||||
// SECURITY: Only accepts serverId, not arbitrary serverConfig, to prevent
|
||||
// drive-by command execution attacks. Servers must be saved first.
|
||||
|
||||
@@ -1196,6 +1196,53 @@ function mergeConsecutiveEntries(entries: LogEntry[]): LogEntry[] {
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts summary content from raw log output
|
||||
* Returns the summary text if found, or null if no summary exists
|
||||
*/
|
||||
export function extractSummary(rawOutput: string): string | null {
|
||||
if (!rawOutput || !rawOutput.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to find <summary> tags first (preferred format)
|
||||
const summaryTagMatch = rawOutput.match(/<summary>([\s\S]*?)<\/summary>/);
|
||||
if (summaryTagMatch) {
|
||||
return summaryTagMatch[1].trim();
|
||||
}
|
||||
|
||||
// Try to find markdown ## Summary section
|
||||
const summaryHeaderMatch = rawOutput.match(/^##\s+Summary\s*\n([\s\S]*?)(?=\n##\s+|$)/m);
|
||||
if (summaryHeaderMatch) {
|
||||
return summaryHeaderMatch[1].trim();
|
||||
}
|
||||
|
||||
// Try other summary formats (Feature, Changes, Implementation)
|
||||
const otherHeaderMatch = rawOutput.match(
|
||||
/^##\s+(Feature|Changes|Implementation)\s*\n([\s\S]*?)(?=\n##\s+|$)/m
|
||||
);
|
||||
if (otherHeaderMatch) {
|
||||
return `## ${otherHeaderMatch[1]}\n${otherHeaderMatch[2].trim()}`;
|
||||
}
|
||||
|
||||
// Try to find summary introduction lines
|
||||
const introMatch = rawOutput.match(
|
||||
/(^|\n)(All tasks completed[\s\S]*?)(?=\n🔧|\n📋|\n⚡|\n❌|$)/
|
||||
);
|
||||
if (introMatch) {
|
||||
return introMatch[2].trim();
|
||||
}
|
||||
|
||||
const completionMatch = rawOutput.match(
|
||||
/(^|\n)((I've|I have) (successfully |now )?(completed|finished|implemented)[\s\S]*?)(?=\n🔧|\n📋|\n⚡|\n❌|$)/
|
||||
);
|
||||
if (completionMatch) {
|
||||
return completionMatch[2].trim();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color classes for a log entry type
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user