From db79856b08307bd8211b49954681663f276b8625 Mon Sep 17 00:00:00 2001 From: Carlos Gutierrez Date: Wed, 4 Feb 2026 21:32:30 -0500 Subject: [PATCH] fixing imports --- src/api/ollama/core/chat.ts | 5 +- .../callbacks/on-learning-detected.ts | 2 +- src/commands/components/callbacks/on-log.ts | 2 +- .../components/callbacks/on-mode-change.ts | 2 +- .../components/callbacks/on-tool-call.ts | 2 +- .../components/callbacks/on-tool-result.ts | 2 +- .../components/chat/agents/switch-agent.ts | 6 +- .../chat/commands/commandsRegistry.ts | 2 +- .../components/chat/context/load-file.ts | 6 +- src/commands/components/chat/index.ts | 22 +- .../components/chat/mcp/handle-mcp.ts | 14 +- .../components/chat/mcp/show-mcp-status.ts | 2 +- .../components/chat/messages/send-message.ts | 6 +- .../components/chat/models/show-models.ts | 4 +- .../components/chat/models/show-providers.ts | 2 +- .../components/chat/models/switch-model.ts | 2 +- .../components/chat/models/switch-provider.ts | 10 +- .../chat/session/restore-messages.ts | 4 +- src/commands/components/chat/state.ts | 8 +- .../components/chat/usage/show-usage.ts | 2 +- .../components/execute/execute-solid.tsx | 2 +- src/commands/components/execute/execute.tsx | 5 +- src/commands/core/handlers.ts | 2 +- src/commands/handlers/chat.ts | 2 +- src/commands/handlers/classify.ts | 2 +- src/commands/handlers/config.ts | 2 +- src/commands/handlers/plan.ts | 2 +- src/commands/handlers/run.ts | 2 +- src/commands/handlers/serve.ts | 2 +- src/commands/handlers/validate.ts | 2 +- src/commands/mcp.ts | 8 +- src/commands/runner/create-plan.ts | 2 +- src/commands/runner/display-plan.ts | 2 +- src/commands/runner/execute-plan.ts | 8 +- src/commands/runner/execute.ts | 2 +- src/constants/apply-patch.ts | 3 +- src/constants/brain.ts | 3 +- src/constants/handlers.ts | 2 +- src/constants/mcp-registry.ts | 11 +- src/constants/multi-agent.ts | 22 +- src/constants/plugin.ts | 13 +- src/constants/pr-review.ts | 25 ++- src/constants/skills.ts | 7 +- src/constants/token.ts | 2 +- src/constants/tui-components.ts | 6 +- src/constants/vim.ts | 137 ++++++++++-- src/index.ts | 13 +- src/interfaces/AgentOptions.ts | 2 +- src/interfaces/AgentResult.ts | 2 +- src/interfaces/index.ts | 3 +- src/providers/copilot/core/chat.ts | 10 +- src/providers/ollama/stream.ts | 5 +- src/services/agent-definition-loader.ts | 92 +++++--- src/services/agent-stream.ts | 3 +- src/services/brain.ts | 20 +- src/services/brain/cloud-sync.ts | 4 +- src/services/brain/conflict-resolver.ts | 33 +-- src/services/brain/mcp-server.ts | 200 +++++++++++++----- src/services/brain/offline-queue.ts | 4 +- src/services/brain/project-service.ts | 65 ++++-- .../cascading-provider/orchestrator.ts | 12 +- src/services/chat-tui/auth.ts | 12 +- src/services/chat-tui/commands.ts | 6 +- src/services/chat-tui/files.ts | 2 +- src/services/chat-tui/initialize.ts | 6 +- src/services/chat-tui/message-handler.ts | 131 ++++++++---- src/services/chat-tui/models.ts | 6 +- src/services/chat-tui/permissions.ts | 2 +- src/services/chat-tui/streaming.ts | 2 +- src/services/chat-tui/usage.ts | 4 +- src/services/clipboard-service.ts | 33 ++- src/services/confidence-filter.ts | 105 ++++++--- src/services/context-gathering.ts | 13 +- src/services/core/agent.ts | 2 +- src/services/core/config.ts | 2 +- src/services/core/session.ts | 3 +- .../feature-dev/checkpoint-handler.ts | 11 +- src/services/feature-dev/context-builder.ts | 14 +- src/services/github-pr/fetch.ts | 4 +- src/services/github-pr/format.ts | 21 +- src/services/hooks-service.ts | 25 ++- src/services/lsp/client.ts | 76 ++++--- src/services/lsp/index.ts | 34 ++- src/services/lsp/language.ts | 8 +- src/services/lsp/server.ts | 29 ++- src/services/mcp/registry.ts | 50 +++-- src/services/model-routing.ts | 6 +- src/services/multi-agent/agent-manager.ts | 28 ++- src/services/multi-agent/conflict-handler.ts | 21 +- src/services/multi-agent/executor.ts | 20 +- src/services/multi-agent/tool-context.ts | 27 ++- src/services/parallel/conflict-detector.ts | 10 +- src/services/parallel/index.ts | 60 ++++-- src/services/parallel/resource-manager.ts | 6 +- src/services/parallel/result-aggregator.ts | 20 +- src/services/planner.ts | 4 +- src/services/plugin-loader.ts | 30 ++- src/services/plugin-service.ts | 11 +- src/services/pr-review/diff-parser.ts | 5 +- src/services/pr-review/index.ts | 21 +- src/services/pr-review/report-generator.ts | 14 +- src/services/pr-review/reviewers/logic.ts | 49 ++--- .../pr-review/reviewers/performance.ts | 19 +- src/services/pr-review/reviewers/security.ts | 15 +- src/services/pr-review/reviewers/style.ts | 43 ++-- src/services/project-setup-service.ts | 19 +- src/services/provider-quality/persistence.ts | 6 +- src/services/provider-quality/router.ts | 3 +- .../provider-quality/score-manager.ts | 11 +- .../provider-quality/task-detector.ts | 4 +- src/services/reasoning-agent.ts | 15 +- src/services/security-service.ts | 72 +++++-- src/services/session-compaction.ts | 12 +- src/services/session-fork-service.ts | 96 ++++++--- src/services/skill-loader.ts | 31 ++- src/services/skill-registry.ts | 23 +- src/services/snapshot-service.ts | 34 ++- src/stores/core/multi-agent-store.ts | 13 +- src/stores/core/vim-store.ts | 35 +-- src/tools/apply-patch/execute.ts | 35 +-- src/tools/apply-patch/index.ts | 20 +- src/tools/apply-patch/matcher.ts | 5 +- src/tools/apply-patch/params.ts | 8 +- src/tools/apply-patch/parser.ts | 10 +- src/tools/bash/execute.ts | 12 +- src/tools/core/registry.ts | 4 +- src/tools/edit/execute.ts | 5 +- src/tools/glob/definition.ts | 10 +- src/tools/grep/definition.ts | 9 +- src/tools/index.ts | 4 +- src/tools/lsp.ts | 20 +- src/tools/multi-edit/execute.ts | 5 +- src/tools/read/execute.ts | 5 +- src/tools/web-fetch/execute.ts | 5 +- src/tools/web-fetch/params.ts | 4 +- src/tools/web-search.ts | 5 +- src/tools/web-search/execute.ts | 10 +- src/tools/write/execute.ts | 5 +- src/tui-solid/app.tsx | 17 +- .../components/inputs/input-area.tsx | 9 +- .../components/inputs/mcp-add-form.tsx | 26 ++- src/tui-solid/components/layout/header.tsx | 5 +- .../components/layout/streaming-message.tsx | 12 +- src/tui-solid/components/menu/brain-menu.tsx | 35 ++- .../components/modals/conflict-resolver.tsx | 59 +++++- .../components/panels/help-detail.tsx | 14 +- .../components/panels/multi-agent-panel.tsx | 35 +-- .../components/submenu/mode-select.tsx | 2 +- .../components/submenu/provider-select.tsx | 8 +- src/tui-solid/context/app.tsx | 27 ++- src/tui-solid/routes/session.tsx | 34 ++- src/types/brain-cloud.ts | 6 +- src/types/brain-mcp.ts | 88 ++++++-- src/types/confidence-filter.ts | 5 +- src/types/feature-dev.ts | 14 +- src/types/github-pr.ts | 14 +- src/types/image.ts | 14 +- src/types/multi-agent.ts | 39 ++-- src/types/parallel.ts | 21 +- src/types/plugin.ts | 6 +- src/types/pr-review.ts | 6 +- src/types/skills.ts | 8 +- src/types/usage.ts | 6 +- src/ui/banner.test.ts | 34 ++- src/ui/input-editor/keypress.ts | 4 +- src/utils/core/string-helpers.ts | 6 +- 166 files changed, 1986 insertions(+), 982 deletions(-) diff --git a/src/api/ollama/core/chat.ts b/src/api/ollama/core/chat.ts index cb2229a..7e7f805 100644 --- a/src/api/ollama/core/chat.ts +++ b/src/api/ollama/core/chat.ts @@ -6,10 +6,7 @@ import got from "got"; import { OLLAMA_ENDPOINTS, OLLAMA_TIMEOUTS } from "@constants/ollama"; -import type { - OllamaChatRequest, - OllamaChatResponse, -} from "@/types/ollama"; +import type { OllamaChatRequest, OllamaChatResponse } from "@/types/ollama"; import type { StreamChunk } from "@/types/providers"; /** diff --git a/src/commands/components/callbacks/on-learning-detected.ts b/src/commands/components/callbacks/on-learning-detected.ts index ca96dde..d3ed444 100644 --- a/src/commands/components/callbacks/on-learning-detected.ts +++ b/src/commands/components/callbacks/on-learning-detected.ts @@ -1,5 +1,5 @@ import { v4 as uuidv4 } from "uuid"; -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; import type { LearningResponse } from "@/types/tui"; import type { LearningCandidate } from "@services/learning-service"; diff --git a/src/commands/components/callbacks/on-log.ts b/src/commands/components/callbacks/on-log.ts index 290dbc2..db8be19 100644 --- a/src/commands/components/callbacks/on-log.ts +++ b/src/commands/components/callbacks/on-log.ts @@ -1,4 +1,4 @@ -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; import type { LogType } from "@/types/log"; export const onLog = ( diff --git a/src/commands/components/callbacks/on-mode-change.ts b/src/commands/components/callbacks/on-mode-change.ts index 4523c12..53c9376 100644 --- a/src/commands/components/callbacks/on-mode-change.ts +++ b/src/commands/components/callbacks/on-mode-change.ts @@ -1,4 +1,4 @@ -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; export const onModeChange = (mode: string): void => { appStore.setMode(mode as Parameters[0]); diff --git a/src/commands/components/callbacks/on-tool-call.ts b/src/commands/components/callbacks/on-tool-call.ts index 769254e..92ab794 100644 --- a/src/commands/components/callbacks/on-tool-call.ts +++ b/src/commands/components/callbacks/on-tool-call.ts @@ -1,4 +1,4 @@ -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; import { isQuietTool } from "@utils/core/tools"; import type { ToolCallParams } from "@interfaces/ToolCallParams"; diff --git a/src/commands/components/callbacks/on-tool-result.ts b/src/commands/components/callbacks/on-tool-result.ts index 29f2807..c89790a 100644 --- a/src/commands/components/callbacks/on-tool-result.ts +++ b/src/commands/components/callbacks/on-tool-result.ts @@ -1,4 +1,4 @@ -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; import { truncateOutput, detectDiffContent } from "@services/chat-tui-service"; import { getThinkingMessage } from "@constants/status-messages"; diff --git a/src/commands/components/chat/agents/switch-agent.ts b/src/commands/components/chat/agents/switch-agent.ts index 85c5826..96b2d1a 100644 --- a/src/commands/components/chat/agents/switch-agent.ts +++ b/src/commands/components/chat/agents/switch-agent.ts @@ -3,7 +3,11 @@ */ import chalk from "chalk"; -import { errorMessage, infoMessage, warningMessage } from "@utils/core/terminal"; +import { + errorMessage, + infoMessage, + warningMessage, +} from "@utils/core/terminal"; import { agentLoader } from "@services/agent-loader"; import type { ChatState } from "@commands/components/chat/state"; diff --git a/src/commands/components/chat/commands/commandsRegistry.ts b/src/commands/components/chat/commands/commandsRegistry.ts index 4afc5e4..b2524bb 100644 --- a/src/commands/components/chat/commands/commandsRegistry.ts +++ b/src/commands/components/chat/commands/commandsRegistry.ts @@ -1,6 +1,6 @@ import { saveSession } from "@services/core/session"; import { clearConversation } from "@commands/components/chat/history/clear-conversation"; -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; import { showContextFiles } from "@commands/components/chat/context/show-context-files"; import { removeFile } from "@commands/components/chat/context/remove-file"; import { showContext } from "@commands/components/chat/history/show-context"; diff --git a/src/commands/components/chat/context/load-file.ts b/src/commands/components/chat/context/load-file.ts index f5780ec..77db34b 100644 --- a/src/commands/components/chat/context/load-file.ts +++ b/src/commands/components/chat/context/load-file.ts @@ -1,6 +1,10 @@ import { readFile, stat } from "fs/promises"; import { basename } from "path"; -import { warningMessage, successMessage, errorMessage } from "@utils/core/terminal"; +import { + warningMessage, + successMessage, + errorMessage, +} from "@utils/core/terminal"; import { addContextFile } from "@services/core/session"; export const loadFile = async ( diff --git a/src/commands/components/chat/index.ts b/src/commands/components/chat/index.ts index 06e092d..9749578 100644 --- a/src/commands/components/chat/index.ts +++ b/src/commands/components/chat/index.ts @@ -12,19 +12,15 @@ import { setWorkingDirectory, } from "@services/core/session"; import { getConfig } from "@services/core/config"; -import type { Provider as ProviderName, ChatSession } from "@/types/index"; -import { getProvider, getProviderStatus } from "@providers/index"; -import { - printWelcome, - formatTipLine, - Style, - Theme, - createInputEditor, -} from "@ui/index"; -import { - DEFAULT_SYSTEM_PROMPT, - buildSystemPromptWithRules, -} from "@prompts/index"; +import type { Provider as ProviderName, ChatSession } from "@/types/common"; +import { getProvider } from "@providers/core/registry"; +import { getProviderStatus } from "@providers/core/status"; +import { printWelcome } from "@ui/banner/menu/print"; +import { formatTipLine } from "@ui/tips/render"; +import { Style, Theme } from "@constants/styles"; +import { createInputEditor } from "@ui/input-editor/core/editor"; +import { DEFAULT_SYSTEM_PROMPT } from "@prompts/system/default"; +import { buildSystemPromptWithRules } from "@services/rules/prompt"; import type { ChatOptions } from "@interfaces/ChatOptions"; import { createInitialState, type ChatState } from "./state"; diff --git a/src/commands/components/chat/mcp/handle-mcp.ts b/src/commands/components/chat/mcp/handle-mcp.ts index f3d59cc..2b39919 100644 --- a/src/commands/components/chat/mcp/handle-mcp.ts +++ b/src/commands/components/chat/mcp/handle-mcp.ts @@ -10,17 +10,19 @@ import { connectAllServers, disconnectAllServers, getAllTools, +} from "@services/mcp/manager"; +import { searchServers, getPopularServers, installServerById, getCategoriesWithCounts, -} from "@services/mcp/index"; +} from "@services/mcp/registry"; import { MCP_CATEGORY_LABELS, MCP_CATEGORY_ICONS, } from "@constants/mcp-registry"; import { showMCPStatus } from "@commands/components/chat/mcp/show-mcp-status"; -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; /** * Handle MCP subcommands @@ -45,7 +47,9 @@ export const handleMCP = async (args: string[]): Promise => { if (!handler) { console.log(chalk.yellow(`Unknown MCP command: ${subcommand}`)); console.log( - chalk.gray("Available: status, connect, disconnect, tools, add, search, browse, install, popular, categories"), + chalk.gray( + "Available: status, connect, disconnect, tools, add, search, browse, install, popular, categories", + ), ); return; } @@ -277,7 +281,9 @@ const handleCategories = async (_args: string[]): Promise => { for (const { category, count } of categories) { const icon = MCP_CATEGORY_ICONS[category]; const label = MCP_CATEGORY_LABELS[category]; - console.log(`${icon} ${chalk.white(label)} ${chalk.gray(`(${count} servers)`)}`); + console.log( + `${icon} ${chalk.white(label)} ${chalk.gray(`(${count} servers)`)}`, + ); } console.log(); diff --git a/src/commands/components/chat/mcp/show-mcp-status.ts b/src/commands/components/chat/mcp/show-mcp-status.ts index b9e94f1..923e97b 100644 --- a/src/commands/components/chat/mcp/show-mcp-status.ts +++ b/src/commands/components/chat/mcp/show-mcp-status.ts @@ -8,7 +8,7 @@ import { getServerInstances, getAllTools, isMCPAvailable, -} from "@services/mcp/index"; +} from "@services/mcp/manager"; /** * Display MCP server status diff --git a/src/commands/components/chat/messages/send-message.ts b/src/commands/components/chat/messages/send-message.ts index 79458a7..643d43f 100644 --- a/src/commands/components/chat/messages/send-message.ts +++ b/src/commands/components/chat/messages/send-message.ts @@ -3,7 +3,11 @@ import { basename, extname } from "path"; import { addMessage } from "@services/core/session"; import { initializePermissions } from "@services/core/permissions"; import { createAgent } from "@services/core/agent"; -import { infoMessage, errorMessage, warningMessage } from "@utils/core/terminal"; +import { + infoMessage, + errorMessage, + warningMessage, +} from "@utils/core/terminal"; import { getThinkingMessage } from "@constants/status-messages"; import { detectDebuggingRequest, diff --git a/src/commands/components/chat/models/show-models.ts b/src/commands/components/chat/models/show-models.ts index 9676bb1..d1be9e5 100644 --- a/src/commands/components/chat/models/show-models.ts +++ b/src/commands/components/chat/models/show-models.ts @@ -1,6 +1,6 @@ import chalk from "chalk"; -import type { Provider as ProviderName } from "@/types/index"; -import { getProvider } from "@providers/index"; +import type { Provider as ProviderName } from "@/types/common"; +import { getProvider } from "@providers/core/registry"; export const showModels = async ( currentProvider: ProviderName, diff --git a/src/commands/components/chat/models/show-providers.ts b/src/commands/components/chat/models/show-providers.ts index 0e4e007..337aaf9 100644 --- a/src/commands/components/chat/models/show-providers.ts +++ b/src/commands/components/chat/models/show-providers.ts @@ -1,5 +1,5 @@ import { getConfig } from "@services/core/config"; -import { displayProvidersStatus } from "@providers/index"; +import { displayProvidersStatus } from "@providers/core/status"; export const showProviders = async (): Promise => { const config = await getConfig(); diff --git a/src/commands/components/chat/models/switch-model.ts b/src/commands/components/chat/models/switch-model.ts index 03a6776..d844e4d 100644 --- a/src/commands/components/chat/models/switch-model.ts +++ b/src/commands/components/chat/models/switch-model.ts @@ -5,7 +5,7 @@ import { errorMessage, } from "@utils/core/terminal"; import { getConfig } from "@services/core/config"; -import { getProvider } from "@providers/index"; +import { getProvider } from "@providers/core/registry"; import { showModels } from "./show-models"; import type { ChatState } from "../state"; diff --git a/src/commands/components/chat/models/switch-provider.ts b/src/commands/components/chat/models/switch-provider.ts index 273620d..76fa638 100644 --- a/src/commands/components/chat/models/switch-provider.ts +++ b/src/commands/components/chat/models/switch-provider.ts @@ -1,4 +1,4 @@ -import type { Provider as ProviderName } from "@/types/index"; +import type { Provider as ProviderName } from "@/types/common"; import { errorMessage, warningMessage, @@ -6,11 +6,9 @@ import { successMessage, } from "@utils/core/terminal"; import { getConfig } from "@services/core/config"; -import { - getProvider, - getProviderStatus, - getDefaultModel, -} from "@providers/index"; +import { getProvider } from "@providers/core/registry"; +import { getProviderStatus } from "@providers/core/status"; +import { getDefaultModel } from "@providers/core/chat"; import type { ChatState } from "../state"; export const switchProvider = async ( diff --git a/src/commands/components/chat/session/restore-messages.ts b/src/commands/components/chat/session/restore-messages.ts index 067749a..d649069 100644 --- a/src/commands/components/chat/session/restore-messages.ts +++ b/src/commands/components/chat/session/restore-messages.ts @@ -1,5 +1,5 @@ -import type { ChatSession } from "@/types/index"; -import type { Message } from "@providers/index"; +import type { ChatSession } from "@/types/common"; +import type { Message } from "@/types/providers"; export const restoreMessagesFromSession = ( session: ChatSession, diff --git a/src/commands/components/chat/state.ts b/src/commands/components/chat/state.ts index f9c926d..51ceaeb 100644 --- a/src/commands/components/chat/state.ts +++ b/src/commands/components/chat/state.ts @@ -1,7 +1,7 @@ -import type { Provider as ProviderName } from "@/types/index"; -import type { Message } from "@providers/index"; -import type { InputEditorInstance } from "@ui/index"; -import { DEFAULT_SYSTEM_PROMPT } from "@prompts/index"; +import type { Provider as ProviderName } from "@/types/common"; +import type { Message } from "@/types/providers"; +import type { InputEditorInstance } from "@ui/input-editor/core/editor"; +import { DEFAULT_SYSTEM_PROMPT } from "@prompts/system/default"; export interface ChatState { inputEditor: InputEditorInstance | null; diff --git a/src/commands/components/chat/usage/show-usage.ts b/src/commands/components/chat/usage/show-usage.ts index b18e771..a20652f 100644 --- a/src/commands/components/chat/usage/show-usage.ts +++ b/src/commands/components/chat/usage/show-usage.ts @@ -6,7 +6,7 @@ import chalk from "chalk"; import { usageStore } from "@stores/core/usage-store"; import { getUserInfo } from "@providers/copilot/auth/credentials"; import { getCopilotUsage } from "@providers/copilot/usage"; -import { getProvider } from "@providers/index"; +import { getProvider } from "@providers/core/registry"; import { renderUsageBar, renderUnlimitedBar } from "@utils/menu/progress-bar"; import type { ChatState } from "@commands/components/chat/state"; import type { CopilotQuotaDetail } from "@/types/copilot-usage"; diff --git a/src/commands/components/execute/execute-solid.tsx b/src/commands/components/execute/execute-solid.tsx index 16a45ba..2f70b80 100644 --- a/src/commands/components/execute/execute-solid.tsx +++ b/src/commands/components/execute/execute-solid.tsx @@ -1,4 +1,4 @@ -import { tui } from "@tui-solid/index"; +import { tui } from "@tui-solid/app"; import { getProviderInfo } from "@services/chat-tui-service"; import type { ChatServiceState } from "@services/chat-tui-service"; import type { AgentConfig } from "@/types/agent-config"; diff --git a/src/commands/components/execute/execute.tsx b/src/commands/components/execute/execute.tsx index 21ad779..bba614b 100644 --- a/src/commands/components/execute/execute.tsx +++ b/src/commands/components/execute/execute.tsx @@ -1,8 +1,7 @@ -import { tui, appStore } from "@tui/app"; - +import { tui } from "@tui-solid/app"; import { appStore } from "@tui-solid/context/app"; import { getProviderInfo } from "@services/chat-tui-service"; -import { addServer, connectServer } from "@services/mcp/index"; +import { addServer, connectServer } from "@services/mcp/manager"; import * as brainService from "@services/brain"; import type { ChatServiceState } from "@services/chat-tui-service"; import type { AgentConfig } from "@/types/agent-config"; diff --git a/src/commands/core/handlers.ts b/src/commands/core/handlers.ts index 8429c2c..1176e05 100644 --- a/src/commands/core/handlers.ts +++ b/src/commands/core/handlers.ts @@ -4,7 +4,7 @@ import { errorMessage } from "@utils/core/terminal"; import { COMMAND_REGISTRY, isValidCommand } from "@commands/handlers/registry"; -import type { CommandOptions } from "@/types/index"; +import type { CommandOptions } from "@/types/common"; export const handleCommand = async ( command: string, diff --git a/src/commands/handlers/chat.ts b/src/commands/handlers/chat.ts index 8b550c9..58654de 100644 --- a/src/commands/handlers/chat.ts +++ b/src/commands/handlers/chat.ts @@ -3,7 +3,7 @@ */ import { execute as executeChat } from "@commands/chat"; -import type { CommandOptions } from "@/types/index"; +import type { CommandOptions } from "@/types/common"; export const handleChat = async (options: CommandOptions): Promise => { await executeChat(options); diff --git a/src/commands/handlers/classify.ts b/src/commands/handlers/classify.ts index 425fc99..3e77990 100644 --- a/src/commands/handlers/classify.ts +++ b/src/commands/handlers/classify.ts @@ -18,7 +18,7 @@ import type { CommandOptions, IntentRequest, IntentResponse, -} from "@/types/index"; +} from "@/types/common"; const classifyIntent = async ( request: IntentRequest, diff --git a/src/commands/handlers/config.ts b/src/commands/handlers/config.ts index f4dff3f..39b850d 100644 --- a/src/commands/handlers/config.ts +++ b/src/commands/handlers/config.ts @@ -16,7 +16,7 @@ import { VALID_PROVIDERS, CONFIG_VALIDATION, } from "@constants/handlers"; -import type { CommandOptions, Provider } from "@/types/index"; +import type { CommandOptions, Provider } from "@/types/common"; import type { ConfigAction, ConfigKey } from "@/types/handlers"; type ConfigActionHandler = (key?: string, value?: string) => Promise; diff --git a/src/commands/handlers/plan.ts b/src/commands/handlers/plan.ts index 962b0ce..5540600 100644 --- a/src/commands/handlers/plan.ts +++ b/src/commands/handlers/plan.ts @@ -13,7 +13,7 @@ import { succeedSpinner, successMessage, } from "@utils/core/terminal"; -import type { CommandOptions } from "@/types/index"; +import type { CommandOptions } from "@/types/common"; export const handlePlan = async (options: CommandOptions): Promise => { const { intent, task, files = [], output } = options; diff --git a/src/commands/handlers/run.ts b/src/commands/handlers/run.ts index 09cc5d2..1460016 100644 --- a/src/commands/handlers/run.ts +++ b/src/commands/handlers/run.ts @@ -3,7 +3,7 @@ */ import { execute } from "@commands/core/runner"; -import type { CommandOptions } from "@/types/index"; +import type { CommandOptions } from "@/types/common"; export const handleRun = async (options: CommandOptions): Promise => { await execute(options); diff --git a/src/commands/handlers/serve.ts b/src/commands/handlers/serve.ts index 35286bd..47ff42c 100644 --- a/src/commands/handlers/serve.ts +++ b/src/commands/handlers/serve.ts @@ -3,7 +3,7 @@ */ import { boxMessage, warningMessage, infoMessage } from "@utils/core/terminal"; -import type { CommandOptions } from "@/types/index"; +import type { CommandOptions } from "@/types/common"; import { SERVER_INFO } from "@constants/serve"; export const handleServe = async (_options: CommandOptions): Promise => { diff --git a/src/commands/handlers/validate.ts b/src/commands/handlers/validate.ts index b530e97..0db4148 100644 --- a/src/commands/handlers/validate.ts +++ b/src/commands/handlers/validate.ts @@ -14,7 +14,7 @@ import { filePath, } from "@utils/core/terminal"; import { getConfig } from "@services/core/config"; -import type { CommandOptions } from "@/types/index"; +import type { CommandOptions } from "@/types/common"; export const handleValidate = async ( options: CommandOptions, diff --git a/src/commands/mcp.ts b/src/commands/mcp.ts index 6b807c2..33de913 100644 --- a/src/commands/mcp.ts +++ b/src/commands/mcp.ts @@ -12,7 +12,11 @@ */ import chalk from "chalk"; -import { errorMessage, infoMessage, successMessage } from "@utils/core/terminal"; +import { + errorMessage, + infoMessage, + successMessage, +} from "@utils/core/terminal"; import { initializeMCP, getMCPConfig, @@ -24,7 +28,7 @@ import { disconnectAllServers, getServerInstances, getAllTools, -} from "@services/mcp/index"; +} from "@services/mcp/manager"; /** * MCP command handler diff --git a/src/commands/runner/create-plan.ts b/src/commands/runner/create-plan.ts index 7c317bd..305d5b2 100644 --- a/src/commands/runner/create-plan.ts +++ b/src/commands/runner/create-plan.ts @@ -9,7 +9,7 @@ import { ESTIMATED_TIME_PER_STEP, } from "@constants/runner"; import { delay } from "@commands/runner/utils"; -import type { AgentType, ExecutionPlan, PlanStep } from "@/types/index"; +import type { AgentType, ExecutionPlan, PlanStep } from "@/types/common"; export const createPlan = async ( task: string, diff --git a/src/commands/runner/display-plan.ts b/src/commands/runner/display-plan.ts index e768812..80a7739 100644 --- a/src/commands/runner/display-plan.ts +++ b/src/commands/runner/display-plan.ts @@ -5,7 +5,7 @@ import chalk from "chalk"; import { filePath } from "@utils/core/terminal"; import { STEP_ICONS, DEFAULT_STEP_ICON } from "@constants/runner"; -import type { ExecutionPlan, PlanStep } from "@/types/index"; +import type { ExecutionPlan, PlanStep } from "@/types/common"; export const getStepIcon = (type: PlanStep["type"]): string => STEP_ICONS[type] ?? DEFAULT_STEP_ICON; diff --git a/src/commands/runner/execute-plan.ts b/src/commands/runner/execute-plan.ts index e4663fa..4c1f69f 100644 --- a/src/commands/runner/execute-plan.ts +++ b/src/commands/runner/execute-plan.ts @@ -2,11 +2,15 @@ * Plan execution utilities */ -import { failSpinner, succeedSpinner, startSpinner } from "@utils/core/terminal"; +import { + failSpinner, + succeedSpinner, + startSpinner, +} from "@utils/core/terminal"; import { RUNNER_DELAYS } from "@constants/runner"; import { getStepIcon } from "@commands/runner/display-plan"; import { delay } from "@commands/runner/utils"; -import type { ExecutionPlan, PlanStep } from "@/types/index"; +import type { ExecutionPlan, PlanStep } from "@/types/common"; import type { StepContext } from "@/types/runner"; const executeStep = async (context: StepContext): Promise => { diff --git a/src/commands/runner/execute.ts b/src/commands/runner/execute.ts index 1cfb652..579f62b 100644 --- a/src/commands/runner/execute.ts +++ b/src/commands/runner/execute.ts @@ -19,7 +19,7 @@ import { displayPlan } from "@commands/runner/display-plan"; import { createPlan } from "@commands/runner/create-plan"; import { executePlan } from "@commands/runner/execute-plan"; import { delay } from "@commands/runner/utils"; -import type { CommandOptions, AgentType } from "@/types/index"; +import type { CommandOptions, AgentType } from "@/types/common"; import type { RunnerOptions } from "@/types/runner"; const parseOptions = (options: CommandOptions): RunnerOptions | null => { diff --git a/src/constants/apply-patch.ts b/src/constants/apply-patch.ts index a1f54aa..bc401af 100644 --- a/src/constants/apply-patch.ts +++ b/src/constants/apply-patch.ts @@ -50,8 +50,7 @@ export const PATCH_ERRORS = { HUNK_FAILED: (index: number, reason: string) => `Hunk #${index + 1} failed: ${reason}`, FILE_NOT_FOUND: (path: string) => `Target file not found: ${path}`, - CONTEXT_MISMATCH: (line: number) => - `Context mismatch at line ${line}`, + CONTEXT_MISMATCH: (line: number) => `Context mismatch at line ${line}`, FUZZY_MATCH_FAILED: (hunk: number) => `Could not find match for hunk #${hunk + 1} even with fuzzy matching`, ALREADY_APPLIED: "Patch appears to be already applied", diff --git a/src/constants/brain.ts b/src/constants/brain.ts index 5fdb071..aa3d7c6 100644 --- a/src/constants/brain.ts +++ b/src/constants/brain.ts @@ -60,7 +60,8 @@ export const BRAIN_TIMEOUTS = { } as const; export const BRAIN_ERRORS = { - NOT_RUNNING: "Brain service not available. Start the API server at localhost:5001", + NOT_RUNNING: + "Brain service not available. Start the API server at localhost:5001", NOT_AUTHENTICATED: "Not authenticated. Please login or set an API key.", INVALID_API_KEY: "Invalid API key. Please check your credentials.", CONNECTION_FAILED: "Failed to connect to Brain service.", diff --git a/src/constants/handlers.ts b/src/constants/handlers.ts index 31f4477..a22c3ee 100644 --- a/src/constants/handlers.ts +++ b/src/constants/handlers.ts @@ -3,7 +3,7 @@ */ import type { ConfigKey, ConfigAction } from "@/types/handlers"; -import type { Provider } from "@/types/index"; +import type { Provider } from "@/types/common"; export const VALID_CONFIG_KEYS: readonly ConfigKey[] = [ "provider", diff --git a/src/constants/mcp-registry.ts b/src/constants/mcp-registry.ts index 9bd0fd1..458508f 100644 --- a/src/constants/mcp-registry.ts +++ b/src/constants/mcp-registry.ts @@ -4,14 +4,18 @@ * Constants for MCP server discovery and search */ -import type { MCPServerCategory, MCPRegistryServer } from "@/types/mcp-registry"; +import type { + MCPServerCategory, + MCPRegistryServer, +} from "@/types/mcp-registry"; /** * Default registry sources */ export const MCP_REGISTRY_SOURCES = { /** Official MCP servers GitHub */ - OFFICIAL: "https://raw.githubusercontent.com/modelcontextprotocol/servers/main/README.md", + OFFICIAL: + "https://raw.githubusercontent.com/modelcontextprotocol/servers/main/README.md", /** Smithery registry API */ SMITHERY: "https://registry.smithery.ai/servers", } as const; @@ -161,7 +165,8 @@ export const MCP_CURATED_SERVERS: MCPRegistryServer[] = [ popularity: 75, verified: true, envVars: ["GITLAB_PERSONAL_ACCESS_TOKEN", "GITLAB_API_URL"], - installHint: "Set GITLAB_PERSONAL_ACCESS_TOKEN and optionally GITLAB_API_URL", + installHint: + "Set GITLAB_PERSONAL_ACCESS_TOKEN and optionally GITLAB_API_URL", updatedAt: "2024-12-01", }, { diff --git a/src/constants/multi-agent.ts b/src/constants/multi-agent.ts index 85a8033..f2458c6 100644 --- a/src/constants/multi-agent.ts +++ b/src/constants/multi-agent.ts @@ -41,12 +41,13 @@ export const EXECUTION_MODE_DESCRIPTIONS: Record = { /** * Conflict strategy descriptions */ -export const CONFLICT_STRATEGY_DESCRIPTIONS: Record = { - serialize: "Wait for conflicting agent to complete before proceeding", - "abort-newer": "Abort the agent that started later when conflict detected", - "merge-results": "Attempt to merge changes from both agents", - isolated: "Each agent works in isolated context, merge at end", -} as const; +export const CONFLICT_STRATEGY_DESCRIPTIONS: Record = + { + serialize: "Wait for conflicting agent to complete before proceeding", + "abort-newer": "Abort the agent that started later when conflict detected", + "merge-results": "Attempt to merge changes from both agents", + isolated: "Each agent works in isolated context, merge at end", + } as const; /** * Error messages @@ -56,17 +57,14 @@ export const MULTI_AGENT_ERRORS = { `Cannot spawn more than ${max} agents in a single request`, MAX_CONCURRENT_EXCEEDED: (max: number) => `Maximum concurrent agents (${max}) reached`, - AGENT_NOT_FOUND: (name: string) => - `Agent "${name}" not found in registry`, - AGENT_ALREADY_RUNNING: (id: string) => - `Agent "${id}" is already running`, + AGENT_NOT_FOUND: (name: string) => `Agent "${name}" not found in registry`, + AGENT_ALREADY_RUNNING: (id: string) => `Agent "${id}" is already running`, EXECUTION_TIMEOUT: (agentId: string, timeout: number) => `Agent "${agentId}" timed out after ${timeout}ms`, CONFLICT_RESOLUTION_FAILED: (filePath: string) => `Failed to resolve conflict for file: ${filePath}`, EXECUTION_ABORTED: "Execution aborted by user", - INVALID_EXECUTION_MODE: (mode: string) => - `Invalid execution mode: ${mode}`, + INVALID_EXECUTION_MODE: (mode: string) => `Invalid execution mode: ${mode}`, INVALID_CONFLICT_STRATEGY: (strategy: string) => `Invalid conflict strategy: ${strategy}`, TOO_MANY_CONFLICTS: (count: number) => diff --git a/src/constants/plugin.ts b/src/constants/plugin.ts index 41f3499..a7fa807 100644 --- a/src/constants/plugin.ts +++ b/src/constants/plugin.ts @@ -68,12 +68,13 @@ export const PLUGIN_CAPABILITY_LABELS: Record = { /** * Plugin capability descriptions */ -export const PLUGIN_CAPABILITY_DESCRIPTIONS: Record = { - filesystem: "Can read and write files on disk", - network: "Can make network requests", - shell: "Can execute shell commands", - mcp: "Can interact with MCP servers", -}; +export const PLUGIN_CAPABILITY_DESCRIPTIONS: Record = + { + filesystem: "Can read and write files on disk", + network: "Can make network requests", + shell: "Can execute shell commands", + mcp: "Can interact with MCP servers", + }; /** * Command file extension diff --git a/src/constants/pr-review.ts b/src/constants/pr-review.ts index af86452..ff219ac 100644 --- a/src/constants/pr-review.ts +++ b/src/constants/pr-review.ts @@ -23,7 +23,12 @@ export const DEFAULT_REVIEW_CONFIG: PRReviewConfig = { minConfidence: MIN_CONFIDENCE_THRESHOLD, reviewers: [ { name: "security", type: "security", enabled: true, minConfidence: 80 }, - { name: "performance", type: "performance", enabled: true, minConfidence: 80 }, + { + name: "performance", + type: "performance", + enabled: true, + minConfidence: 80, + }, { name: "style", type: "style", enabled: true, minConfidence: 85 }, { name: "logic", type: "logic", enabled: true, minConfidence: 80 }, ], @@ -167,9 +172,21 @@ export const RATING_THRESHOLDS = { */ export const RECOMMENDATION_THRESHOLDS = { approve: { maxCritical: 0, maxWarning: 0, maxSuggestion: 5 }, - approve_with_suggestions: { maxCritical: 0, maxWarning: 3, maxSuggestion: Infinity }, - request_changes: { maxCritical: 1, maxWarning: Infinity, maxSuggestion: Infinity }, - needs_discussion: { maxCritical: Infinity, maxWarning: Infinity, maxSuggestion: Infinity }, + approve_with_suggestions: { + maxCritical: 0, + maxWarning: 3, + maxSuggestion: Infinity, + }, + request_changes: { + maxCritical: 1, + maxWarning: Infinity, + maxSuggestion: Infinity, + }, + needs_discussion: { + maxCritical: Infinity, + maxWarning: Infinity, + maxSuggestion: Infinity, + }, } as const; /** diff --git a/src/constants/skills.ts b/src/constants/skills.ts index fb1ef70..6d0b3b3 100644 --- a/src/constants/skills.ts +++ b/src/constants/skills.ts @@ -129,4 +129,9 @@ export const SKILL_TRIGGER_PATTERNS = { /** * Required fields in skill frontmatter */ -export const SKILL_REQUIRED_FIELDS = ["id", "name", "description", "triggers"] as const; +export const SKILL_REQUIRED_FIELDS = [ + "id", + "name", + "description", + "triggers", +] as const; diff --git a/src/constants/token.ts b/src/constants/token.ts index 9fff32b..dbf4498 100644 --- a/src/constants/token.ts +++ b/src/constants/token.ts @@ -10,7 +10,7 @@ export const TOKENS_PER_CHAR = 0.25; // Context warning thresholds export const TOKEN_WARNING_THRESHOLD = 0.75; // 75% - yellow warning -export const TOKEN_CRITICAL_THRESHOLD = 0.90; // 90% - red warning +export const TOKEN_CRITICAL_THRESHOLD = 0.9; // 90% - red warning export const TOKEN_OVERFLOW_THRESHOLD = 0.95; // 95% - trigger compaction // Pruning thresholds (following OpenCode pattern) diff --git a/src/constants/tui-components.ts b/src/constants/tui-components.ts index a4e8c65..9c8b357 100644 --- a/src/constants/tui-components.ts +++ b/src/constants/tui-components.ts @@ -190,7 +190,11 @@ export const SLASH_COMMANDS: SlashCommand[] = [ // Settings commands { name: "model", description: "Select AI model", category: "settings" }, { name: "agent", description: "Select agent", category: "settings" }, - { name: "mode", description: "Switch interaction mode", category: "settings" }, + { + name: "mode", + description: "Switch interaction mode", + category: "settings", + }, { name: "provider", description: "Switch LLM provider", diff --git a/src/constants/vim.ts b/src/constants/vim.ts index 1ba7335..2c345ed 100644 --- a/src/constants/vim.ts +++ b/src/constants/vim.ts @@ -41,27 +41,101 @@ export const VIM_MODE_HINTS: Record = { */ export const VIM_DEFAULT_BINDINGS: VimKeyBinding[] = [ // Normal mode - Navigation - { key: "j", mode: "normal", action: "scroll_down", description: "Scroll down" }, + { + key: "j", + mode: "normal", + action: "scroll_down", + description: "Scroll down", + }, { key: "k", mode: "normal", action: "scroll_up", description: "Scroll up" }, - { key: "d", mode: "normal", action: "scroll_half_down", ctrl: true, description: "Half page down" }, - { key: "u", mode: "normal", action: "scroll_half_up", ctrl: true, description: "Half page up" }, - { key: "g", mode: "normal", action: "goto_top", description: "Go to top (gg)" }, - { key: "G", mode: "normal", action: "goto_bottom", shift: true, description: "Go to bottom" }, + { + key: "d", + mode: "normal", + action: "scroll_half_down", + ctrl: true, + description: "Half page down", + }, + { + key: "u", + mode: "normal", + action: "scroll_half_up", + ctrl: true, + description: "Half page up", + }, + { + key: "g", + mode: "normal", + action: "goto_top", + description: "Go to top (gg)", + }, + { + key: "G", + mode: "normal", + action: "goto_bottom", + shift: true, + description: "Go to bottom", + }, // Normal mode - Mode switching - { key: "i", mode: "normal", action: "enter_insert", description: "Enter insert mode" }, - { key: "a", mode: "normal", action: "enter_insert", description: "Append (enter insert)" }, - { key: ":", mode: "normal", action: "enter_command", description: "Enter command mode" }, - { key: "v", mode: "normal", action: "enter_visual", description: "Enter visual mode" }, + { + key: "i", + mode: "normal", + action: "enter_insert", + description: "Enter insert mode", + }, + { + key: "a", + mode: "normal", + action: "enter_insert", + description: "Append (enter insert)", + }, + { + key: ":", + mode: "normal", + action: "enter_command", + description: "Enter command mode", + }, + { + key: "v", + mode: "normal", + action: "enter_visual", + description: "Enter visual mode", + }, // Normal mode - Search - { key: "/", mode: "normal", action: "search_start", description: "Start search" }, - { key: "n", mode: "normal", action: "search_next", description: "Next search match" }, - { key: "N", mode: "normal", action: "search_prev", shift: true, description: "Previous search match" }, + { + key: "/", + mode: "normal", + action: "search_start", + description: "Start search", + }, + { + key: "n", + mode: "normal", + action: "search_next", + description: "Next search match", + }, + { + key: "N", + mode: "normal", + action: "search_prev", + shift: true, + description: "Previous search match", + }, // Normal mode - Word navigation - { key: "w", mode: "normal", action: "word_forward", description: "Next word" }, - { key: "b", mode: "normal", action: "word_backward", description: "Previous word" }, + { + key: "w", + mode: "normal", + action: "word_forward", + description: "Next word", + }, + { + key: "b", + mode: "normal", + action: "word_backward", + description: "Previous word", + }, { key: "0", mode: "normal", action: "line_start", description: "Line start" }, { key: "$", mode: "normal", action: "line_end", description: "Line end" }, @@ -72,16 +146,41 @@ export const VIM_DEFAULT_BINDINGS: VimKeyBinding[] = [ { key: "r", mode: "normal", action: "redo", ctrl: true, description: "Redo" }, // Insert mode - { key: "escape", mode: "insert", action: "exit_mode", description: "Exit to normal mode" }, + { + key: "escape", + mode: "insert", + action: "exit_mode", + description: "Exit to normal mode", + }, // Command mode - { key: "escape", mode: "command", action: "cancel", description: "Cancel command" }, - { key: "return", mode: "command", action: "execute_command", description: "Execute command" }, + { + key: "escape", + mode: "command", + action: "cancel", + description: "Cancel command", + }, + { + key: "return", + mode: "command", + action: "execute_command", + description: "Execute command", + }, // Visual mode - { key: "escape", mode: "visual", action: "exit_mode", description: "Exit visual mode" }, + { + key: "escape", + mode: "visual", + action: "exit_mode", + description: "Exit visual mode", + }, { key: "y", mode: "visual", action: "yank", description: "Yank selection" }, - { key: "d", mode: "visual", action: "delete", description: "Delete selection" }, + { + key: "d", + mode: "visual", + action: "delete", + description: "Delete selection", + }, ]; /** diff --git a/src/index.ts b/src/index.ts index 180cbef..9570edf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,12 +4,10 @@ import { Command } from "commander"; import { handleCommand } from "@commands/core/handlers"; import { execute } from "@commands/chat-tui"; import versionData from "@/version.json"; -import { - initializeProviders, - loginProvider, - getProviderNames, - displayProvidersStatus, -} from "@providers/index"; +import { initializeProviders } from "@providers/login/core/initialize"; +import { loginProvider } from "@providers/login/handlers"; +import { getProviderNames } from "@providers/core/registry"; +import { displayProvidersStatus } from "@providers/core/status"; import { getConfig } from "@services/core/config"; import { deleteSession, getSessionSummaries } from "@services/core/session"; import { @@ -206,7 +204,8 @@ program const config = await getConfig(); const targetProvider = (provider || config.get("provider")) as any; - const { getProvider, getProviderStatus } = await import("@providers/index"); + const { getProvider } = await import("@providers/core/registry"); + const { getProviderStatus } = await import("@providers/core/status"); const providerInstance = getProvider(targetProvider); const status = await getProviderStatus(targetProvider); diff --git a/src/interfaces/AgentOptions.ts b/src/interfaces/AgentOptions.ts index 3e80319..f548f2f 100644 --- a/src/interfaces/AgentOptions.ts +++ b/src/interfaces/AgentOptions.ts @@ -3,7 +3,7 @@ */ import type { ProviderName } from "@/types/providers"; -import type { ToolCall, ToolResult } from "@tools/index"; +import type { ToolCall, ToolResult } from "@/types/tools"; export interface AgentOptions { provider: ProviderName; diff --git a/src/interfaces/AgentResult.ts b/src/interfaces/AgentResult.ts index 8ede9c3..5db3207 100644 --- a/src/interfaces/AgentResult.ts +++ b/src/interfaces/AgentResult.ts @@ -2,7 +2,7 @@ * Agent Result Interface */ -import type { ToolCall, ToolResult } from "@tools/index"; +import type { ToolCall, ToolResult } from "@/types/tools"; export interface AgentResult { success: boolean; diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 9ca37b2..56a0e3c 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,4 +1,5 @@ -import { AgentType, IntentType, Provider } from "@/types/index"; +import type { AgentType, IntentType, Provider } from "@/types/common"; +import type { ProviderModel } from "@/types/providers"; export interface TuiInput { sessionId?: string; diff --git a/src/providers/copilot/core/chat.ts b/src/providers/copilot/core/chat.ts index 4b2008b..c442b2b 100644 --- a/src/providers/copilot/core/chat.ts +++ b/src/providers/copilot/core/chat.ts @@ -9,7 +9,10 @@ import { COPILOT_UNLIMITED_MODEL, } from "@constants/copilot"; import { refreshToken, buildHeaders } from "@providers/copilot/auth/token"; -import { getDefaultModel, isModelUnlimited } from "@providers/copilot/core/models"; +import { + getDefaultModel, + isModelUnlimited, +} from "@providers/copilot/core/models"; import { sleep, isRateLimitError, @@ -246,7 +249,10 @@ const executeStream = ( if (delta?.tool_calls) { for (const tc of delta.tool_calls) { addDebugLog("api", `Tool call chunk: ${JSON.stringify(tc)}`); - console.log("Debug: Tool call chunk received:", JSON.stringify(tc)); + console.log( + "Debug: Tool call chunk received:", + JSON.stringify(tc), + ); onChunk({ type: "tool_call", toolCall: tc }); } } diff --git a/src/providers/ollama/stream.ts b/src/providers/ollama/stream.ts index a4c45c5..232575b 100644 --- a/src/providers/ollama/stream.ts +++ b/src/providers/ollama/stream.ts @@ -68,7 +68,10 @@ export const ollamaChatStream = async ( ): Promise => { const baseUrl = getOllamaBaseUrl(); const body = buildChatRequest(messages, options, true); - addDebugLog("api", `Ollama stream request: ${messages.length} msgs, model=${body.model}`); + addDebugLog( + "api", + `Ollama stream request: ${messages.length} msgs, model=${body.model}`, + ); const stream = got.stream.post(`${baseUrl}${OLLAMA_ENDPOINTS.CHAT}`, { json: body, diff --git a/src/services/agent-definition-loader.ts b/src/services/agent-definition-loader.ts index 68c95b7..f537c90 100644 --- a/src/services/agent-definition-loader.ts +++ b/src/services/agent-definition-loader.ts @@ -16,10 +16,19 @@ import type { AgentTier, AgentColor, } from "@/types/agent-definition"; -import { DEFAULT_AGENT_DEFINITION, AGENT_DEFINITION_SCHEMA } from "@/types/agent-definition"; -import { AGENT_DEFINITION, AGENT_DEFINITION_PATHS, AGENT_MESSAGES } from "@constants/agent-definition"; +import { + DEFAULT_AGENT_DEFINITION, + AGENT_DEFINITION_SCHEMA, +} from "@/types/agent-definition"; +import { + AGENT_DEFINITION, + AGENT_DEFINITION_PATHS, + AGENT_MESSAGES, +} from "@constants/agent-definition"; -const parseFrontmatter = (content: string): { frontmatter: Record; body: string } | null => { +const parseFrontmatter = ( + content: string, +): { frontmatter: Record; body: string } | null => { const delimiter = AGENT_DEFINITION.FRONTMATTER_DELIMITER; const lines = content.split("\n"); @@ -27,13 +36,18 @@ const parseFrontmatter = (content: string): { frontmatter: Record index > 0 && line.trim() === delimiter); + const endIndex = lines.findIndex( + (line, index) => index > 0 && line.trim() === delimiter, + ); if (endIndex === -1) { return null; } const frontmatterLines = lines.slice(1, endIndex); - const body = lines.slice(endIndex + 1).join("\n").trim(); + const body = lines + .slice(endIndex + 1) + .join("\n") + .trim(); // Simple YAML parser for frontmatter const frontmatter: Record = {}; @@ -85,7 +99,9 @@ const parseFrontmatter = (content: string): { frontmatter: Record): AgentFrontmatter | null => { +const validateFrontmatter = ( + frontmatter: Record, +): AgentFrontmatter | null => { const { required } = AGENT_DEFINITION_SCHEMA; for (const field of required) { @@ -98,7 +114,11 @@ const validateFrontmatter = (frontmatter: Record): AgentFrontma const description = frontmatter.description; const tools = frontmatter.tools; - if (typeof name !== "string" || typeof description !== "string" || !Array.isArray(tools)) { + if ( + typeof name !== "string" || + typeof description !== "string" || + !Array.isArray(tools) + ) { return null; } @@ -108,7 +128,8 @@ const validateFrontmatter = (frontmatter: Record): AgentFrontma tools: tools as ReadonlyArray, tier: (frontmatter.tier as AgentTier) || DEFAULT_AGENT_DEFINITION.tier, color: (frontmatter.color as AgentColor) || DEFAULT_AGENT_DEFINITION.color, - maxTurns: (frontmatter.maxTurns as number) || DEFAULT_AGENT_DEFINITION.maxTurns, + maxTurns: + (frontmatter.maxTurns as number) || DEFAULT_AGENT_DEFINITION.maxTurns, triggerPhrases: (frontmatter.triggerPhrases as ReadonlyArray) || [], capabilities: (frontmatter.capabilities as ReadonlyArray) || [], allowedPaths: frontmatter.allowedPaths as ReadonlyArray | undefined, @@ -116,7 +137,10 @@ const validateFrontmatter = (frontmatter: Record): AgentFrontma }; }; -const frontmatterToDefinition = (frontmatter: AgentFrontmatter, content: string): AgentDefinition => ({ +const frontmatterToDefinition = ( + frontmatter: AgentFrontmatter, + content: string, +): AgentDefinition => ({ name: frontmatter.name, description: frontmatter.description, tools: frontmatter.tools, @@ -132,19 +156,29 @@ const frontmatterToDefinition = (frontmatter: AgentFrontmatter, content: string) }, }); -export const loadAgentDefinitionFile = async (filePath: string): Promise => { +export const loadAgentDefinitionFile = async ( + filePath: string, +): Promise => { try { const content = await readFile(filePath, "utf-8"); const parsed = parseFrontmatter(content); if (!parsed) { - return { success: false, error: AGENT_MESSAGES.INVALID_FRONTMATTER, filePath }; + return { + success: false, + error: AGENT_MESSAGES.INVALID_FRONTMATTER, + filePath, + }; } const frontmatter = validateFrontmatter(parsed.frontmatter); if (!frontmatter) { - return { success: false, error: AGENT_MESSAGES.MISSING_REQUIRED, filePath }; + return { + success: false, + error: AGENT_MESSAGES.MISSING_REQUIRED, + filePath, + }; } const agent = frontmatterToDefinition(frontmatter, parsed.body); @@ -157,7 +191,7 @@ export const loadAgentDefinitionFile = async (filePath: string): Promise> => { const resolvedPath = directoryPath.replace("~", homedir()); @@ -168,11 +202,11 @@ export const loadAgentDefinitionsFromDirectory = async ( try { const files = await readdir(resolvedPath); const mdFiles = files.filter( - (file) => extname(file) === AGENT_DEFINITION.FILE_EXTENSION + (file) => extname(file) === AGENT_DEFINITION.FILE_EXTENSION, ); const results = await Promise.all( - mdFiles.map((file) => loadAgentDefinitionFile(join(resolvedPath, file))) + mdFiles.map((file) => loadAgentDefinitionFile(join(resolvedPath, file))), ); return results; @@ -182,7 +216,7 @@ export const loadAgentDefinitionsFromDirectory = async ( }; export const loadAllAgentDefinitions = async ( - projectPath: string + projectPath: string, ): Promise => { const agents = new Map(); const byTrigger = new Map(); @@ -225,7 +259,7 @@ export const loadAllAgentDefinitions = async ( export const findAgentByTrigger = ( registry: AgentRegistry, - text: string + text: string, ): AgentDefinition | undefined => { const normalized = text.toLowerCase(); @@ -240,23 +274,28 @@ export const findAgentByTrigger = ( export const findAgentsByCapability = ( registry: AgentRegistry, - capability: string + capability: string, ): ReadonlyArray => { const agentNames = registry.byCapability.get(capability) || []; return agentNames .map((name: string) => registry.agents.get(name)) - .filter((a: AgentDefinition | undefined): a is AgentDefinition => a !== undefined); + .filter( + (a: AgentDefinition | undefined): a is AgentDefinition => a !== undefined, + ); }; export const getAgentByName = ( registry: AgentRegistry, - name: string + name: string, ): AgentDefinition | undefined => registry.agents.get(name); -export const listAllAgents = (registry: AgentRegistry): ReadonlyArray => - Array.from(registry.agents.values()); +export const listAllAgents = ( + registry: AgentRegistry, +): ReadonlyArray => Array.from(registry.agents.values()); -export const createAgentDefinitionContent = (agent: AgentDefinition): string => { +export const createAgentDefinitionContent = ( + agent: AgentDefinition, +): string => { const frontmatter = [ "---", `name: ${agent.name}`, @@ -272,7 +311,9 @@ export const createAgentDefinitionContent = (agent: AgentDefinition): string => if (agent.triggerPhrases && agent.triggerPhrases.length > 0) { frontmatter.push("triggerPhrases:"); - agent.triggerPhrases.forEach((phrase: string) => frontmatter.push(` - ${phrase}`)); + agent.triggerPhrases.forEach((phrase: string) => + frontmatter.push(` - ${phrase}`), + ); } if (agent.capabilities && agent.capabilities.length > 0) { @@ -282,7 +323,8 @@ export const createAgentDefinitionContent = (agent: AgentDefinition): string => frontmatter.push("---"); - const content = agent.systemPrompt || `# ${agent.name}\n\n${agent.description}`; + const content = + agent.systemPrompt || `# ${agent.name}\n\n${agent.description}`; return `${frontmatter.join("\n")}\n\n${content}`; }; diff --git a/src/services/agent-stream.ts b/src/services/agent-stream.ts index 42eb2e5..e221403 100644 --- a/src/services/agent-stream.ts +++ b/src/services/agent-stream.ts @@ -88,7 +88,8 @@ const processStreamChunk = ( // OpenAI streaming format includes index in each chunk // Use index from chunk if available, otherwise find by id or default to 0 - const chunkIndex = tc.index ?? (tc.id ? getToolCallIndex(tc.id, accumulator) : 0); + const chunkIndex = + tc.index ?? (tc.id ? getToolCallIndex(tc.id, accumulator) : 0); // Get or create partial tool call let partial = accumulator.toolCalls.get(chunkIndex); diff --git a/src/services/brain.ts b/src/services/brain.ts index dc90a05..ff3a178 100644 --- a/src/services/brain.ts +++ b/src/services/brain.ts @@ -332,7 +332,10 @@ export const connect = async (): Promise => { // Try to get stats to verify credentials are valid const projectId = brainState.projectId ?? BRAIN_DEFAULTS.PROJECT_ID; - const statsResponse = await brainApi.getKnowledgeStats(projectId, authToken); + const statsResponse = await brainApi.getKnowledgeStats( + projectId, + authToken, + ); if (statsResponse.success && statsResponse.data) { updateState({ @@ -539,7 +542,9 @@ export const extractAndLearn = async ( if (response.success) { // Update knowledge count const newCount = - brainState.knowledgeCount + response.data.stored + response.data.updated; + brainState.knowledgeCount + + response.data.stored + + response.data.updated; updateState({ knowledgeCount: newCount }); return response; } @@ -563,7 +568,9 @@ export const extractAndLearn = async ( export const searchMemories = async ( query: string, limit = 10, -): Promise<{ memories: Array<{ content: string; similarity: number }> } | null> => { +): Promise<{ + memories: Array<{ content: string; similarity: number }>; +} | null> => { if (!isConnected()) { return null; } @@ -599,7 +606,12 @@ export const searchMemories = async ( */ export const storeMemory = async ( content: string, - type: "fact" | "pattern" | "correction" | "preference" | "context" = "context", + type: + | "fact" + | "pattern" + | "correction" + | "preference" + | "context" = "context", ): Promise => { if (!isConnected()) { return false; diff --git a/src/services/brain/cloud-sync.ts b/src/services/brain/cloud-sync.ts index 52aa931..f968979 100644 --- a/src/services/brain/cloud-sync.ts +++ b/src/services/brain/cloud-sync.ts @@ -448,9 +448,7 @@ const pullFromCloud = async ( /** * Check if pulled item conflicts with local changes */ -const checkLocalConflict = async ( - _item: SyncItem, -): Promise => { +const checkLocalConflict = async (_item: SyncItem): Promise => { // Check if we have pending changes for this item const queued = await hasQueuedItems(); return queued; diff --git a/src/services/brain/conflict-resolver.ts b/src/services/brain/conflict-resolver.ts index 9028672..e79f211 100644 --- a/src/services/brain/conflict-resolver.ts +++ b/src/services/brain/conflict-resolver.ts @@ -4,9 +4,7 @@ * Handles sync conflicts between local and remote brain data. */ -import { - CONFLICT_LABELS, -} from "@constants/brain-cloud"; +import { CONFLICT_LABELS } from "@constants/brain-cloud"; import type { SyncConflict, ConflictStrategy, @@ -83,21 +81,22 @@ export const resolveAllConflicts = ( /** * Conflict resolution strategies */ -const resolvers: Record unknown> = { - "local-wins": (conflict) => conflict.localData, +const resolvers: Record unknown> = + { + "local-wins": (conflict) => conflict.localData, - "remote-wins": (conflict) => conflict.remoteData, + "remote-wins": (conflict) => conflict.remoteData, - manual: (_conflict) => { - // Manual resolution returns null - requires user input - return null; - }, + manual: (_conflict) => { + // Manual resolution returns null - requires user input + return null; + }, - merge: (conflict) => { - // Attempt to merge the data - return mergeData(conflict.localData, conflict.remoteData); - }, -}; + merge: (conflict) => { + // Attempt to merge the data + return mergeData(conflict.localData, conflict.remoteData); + }, + }; /** * Attempt to merge two data objects @@ -119,7 +118,9 @@ const mergeData = (local: unknown, remote: unknown): unknown => { // Use most recent timestamp const localTime = (localObj.updatedAt ?? localObj.timestamp ?? 0) as number; - const remoteTime = (remoteObj.updatedAt ?? remoteObj.timestamp ?? 0) as number; + const remoteTime = (remoteObj.updatedAt ?? + remoteObj.timestamp ?? + 0) as number; merged.updatedAt = Math.max(localTime, remoteTime); return merged; diff --git a/src/services/brain/mcp-server.ts b/src/services/brain/mcp-server.ts index d1519af..cd3ebda 100644 --- a/src/services/brain/mcp-server.ts +++ b/src/services/brain/mcp-server.ts @@ -3,7 +3,12 @@ * Exposes Brain as an MCP server for external tools */ -import { createServer, type Server, type IncomingMessage, type ServerResponse } from "node:http"; +import { + createServer, + type Server, + type IncomingMessage, + type ServerResponse, +} from "node:http"; import type { BrainMcpServerConfig, @@ -20,16 +25,26 @@ import { BRAIN_MCP_TOOLS, MCP_ERROR_CODES, } from "@/types/brain-mcp"; -import { - BRAIN_MCP_MESSAGES, - BRAIN_MCP_ERRORS, -} from "@constants/brain-mcp"; +import { BRAIN_MCP_MESSAGES, BRAIN_MCP_ERRORS } from "@constants/brain-mcp"; type BrainService = { recall: (query: string, limit?: number) => Promise; - learn: (name: string, whatItDoes: string, options?: unknown) => Promise; - searchMemories: (query: string, limit?: number, type?: string) => Promise; - relate: (source: string, target: string, type: string, weight?: number) => Promise; + learn: ( + name: string, + whatItDoes: string, + options?: unknown, + ) => Promise; + searchMemories: ( + query: string, + limit?: number, + type?: string, + ) => Promise; + relate: ( + source: string, + target: string, + type: string, + weight?: number, + ) => Promise; getContext: (query: string, maxConcepts?: number) => Promise; getStats: () => Promise; isConnected: () => boolean; @@ -59,7 +74,11 @@ const state: McpServerState = { apiKeys: new Set(), }; -const createMcpError = (code: number, message: string, data?: unknown): McpError => ({ +const createMcpError = ( + code: number, + message: string, + data?: unknown, +): McpError => ({ code, message, data, @@ -68,7 +87,7 @@ const createMcpError = (code: number, message: string, data?: unknown): McpError const createMcpResponse = ( id: string | number, content?: ReadonlyArray, - error?: McpError + error?: McpError, ): BrainMcpResponse => { if (error) { return { id, error }; @@ -111,7 +130,9 @@ const checkRateLimit = (clientIp: string): boolean => { const validateApiKey = (req: IncomingMessage): boolean => { if (!state.config.enableAuth) return true; - const apiKey = req.headers[state.config.apiKeyHeader.toLowerCase()] as string | undefined; + const apiKey = req.headers[state.config.apiKeyHeader.toLowerCase()] as + | string + | undefined; if (!apiKey) return false; @@ -123,45 +144,62 @@ const validateApiKey = (req: IncomingMessage): boolean => { const handleToolCall = async ( toolName: BrainMcpToolName, - args: Record + args: Record, ): Promise => { if (!state.brainService) { - throw createMcpError(MCP_ERROR_CODES.BRAIN_UNAVAILABLE, BRAIN_MCP_MESSAGES.SERVER_NOT_RUNNING); + throw createMcpError( + MCP_ERROR_CODES.BRAIN_UNAVAILABLE, + BRAIN_MCP_MESSAGES.SERVER_NOT_RUNNING, + ); } if (!state.brainService.isConnected()) { - throw createMcpError(MCP_ERROR_CODES.BRAIN_UNAVAILABLE, "Brain service not connected"); + throw createMcpError( + MCP_ERROR_CODES.BRAIN_UNAVAILABLE, + "Brain service not connected", + ); } const tool = BRAIN_MCP_TOOLS.find((t: BrainMcpTool) => t.name === toolName); if (!tool) { - throw createMcpError(MCP_ERROR_CODES.TOOL_NOT_FOUND, `Tool not found: ${toolName}`); + throw createMcpError( + MCP_ERROR_CODES.TOOL_NOT_FOUND, + `Tool not found: ${toolName}`, + ); } let result: unknown; const toolHandlers: Record Promise> = { - brain_recall: () => state.brainService!.recall(args.query as string, args.limit as number | undefined), - brain_learn: () => state.brainService!.learn( - args.name as string, - args.whatItDoes as string, - { keywords: args.keywords, patterns: args.patterns, files: args.files } - ), - brain_search: () => state.brainService!.searchMemories( - args.query as string, - args.limit as number | undefined, - args.type as string | undefined - ), - brain_relate: () => state.brainService!.relate( - args.sourceConcept as string, - args.targetConcept as string, - args.relationType as string, - args.weight as number | undefined - ), - brain_context: () => state.brainService!.getContext( - args.query as string, - args.maxConcepts as number | undefined - ), + brain_recall: () => + state.brainService!.recall( + args.query as string, + args.limit as number | undefined, + ), + brain_learn: () => + state.brainService!.learn( + args.name as string, + args.whatItDoes as string, + { keywords: args.keywords, patterns: args.patterns, files: args.files }, + ), + brain_search: () => + state.brainService!.searchMemories( + args.query as string, + args.limit as number | undefined, + args.type as string | undefined, + ), + brain_relate: () => + state.brainService!.relate( + args.sourceConcept as string, + args.targetConcept as string, + args.relationType as string, + args.weight as number | undefined, + ), + brain_context: () => + state.brainService!.getContext( + args.query as string, + args.maxConcepts as number | undefined, + ), brain_stats: () => state.brainService!.getStats(), brain_projects: async () => { // Import dynamically to avoid circular dependency @@ -172,7 +210,10 @@ const handleToolCall = async ( const handler = toolHandlers[toolName]; if (!handler) { - throw createMcpError(MCP_ERROR_CODES.TOOL_NOT_FOUND, `No handler for tool: ${toolName}`); + throw createMcpError( + MCP_ERROR_CODES.TOOL_NOT_FOUND, + `No handler for tool: ${toolName}`, + ); } result = await handler(); @@ -180,19 +221,26 @@ const handleToolCall = async ( return [ { type: "text", - text: typeof result === "string" ? result : JSON.stringify(result, null, 2), + text: + typeof result === "string" ? result : JSON.stringify(result, null, 2), }, ]; }; const handleRequest = async ( req: IncomingMessage, - res: ServerResponse + res: ServerResponse, ): Promise => { // Set CORS headers - res.setHeader("Access-Control-Allow-Origin", state.config.allowedOrigins.join(",")); + res.setHeader( + "Access-Control-Allow-Origin", + state.config.allowedOrigins.join(","), + ); res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", `Content-Type, ${state.config.apiKeyHeader}`); + res.setHeader( + "Access-Control-Allow-Headers", + `Content-Type, ${state.config.apiKeyHeader}`, + ); // Handle preflight if (req.method === "OPTIONS") { @@ -203,7 +251,11 @@ const handleRequest = async ( if (req.method !== "POST") { res.writeHead(405); - res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.INVALID_REQUEST))); + res.end( + JSON.stringify( + createMcpResponse("", undefined, BRAIN_MCP_ERRORS.INVALID_REQUEST), + ), + ); return; } @@ -213,14 +265,22 @@ const handleRequest = async ( // Check rate limit if (!checkRateLimit(clientIp)) { res.writeHead(429); - res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.RATE_LIMITED))); + res.end( + JSON.stringify( + createMcpResponse("", undefined, BRAIN_MCP_ERRORS.RATE_LIMITED), + ), + ); return; } // Validate API key if (!validateApiKey(req)) { res.writeHead(401); - res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.UNAUTHORIZED))); + res.end( + JSON.stringify( + createMcpResponse("", undefined, BRAIN_MCP_ERRORS.UNAUTHORIZED), + ), + ); return; } @@ -240,7 +300,11 @@ const handleRequest = async ( mcpRequest = JSON.parse(body) as BrainMcpRequest; } catch { res.writeHead(400); - res.end(JSON.stringify(createMcpResponse("", undefined, BRAIN_MCP_ERRORS.PARSE_ERROR))); + res.end( + JSON.stringify( + createMcpResponse("", undefined, BRAIN_MCP_ERRORS.PARSE_ERROR), + ), + ); return; } @@ -258,21 +322,37 @@ const handleRequest = async ( inputSchema: tool.inputSchema, })); res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ - id: mcpRequest.id, - result: { tools }, - })); + res.end( + JSON.stringify({ + id: mcpRequest.id, + result: { tools }, + }), + ); } else { res.writeHead(400); - res.end(JSON.stringify(createMcpResponse(mcpRequest.id, undefined, BRAIN_MCP_ERRORS.METHOD_NOT_FOUND))); + res.end( + JSON.stringify( + createMcpResponse( + mcpRequest.id, + undefined, + BRAIN_MCP_ERRORS.METHOD_NOT_FOUND, + ), + ), + ); } } catch (error) { - const mcpError = error instanceof Object && "code" in error - ? error as McpError - : createMcpError(MCP_ERROR_CODES.INTERNAL_ERROR, error instanceof Error ? error.message : "Unknown error"); + const mcpError = + error instanceof Object && "code" in error + ? (error as McpError) + : createMcpError( + MCP_ERROR_CODES.INTERNAL_ERROR, + error instanceof Error ? error.message : "Unknown error", + ); res.writeHead(500); - res.end(JSON.stringify(createMcpResponse(mcpRequest.id, undefined, mcpError))); + res.end( + JSON.stringify(createMcpResponse(mcpRequest.id, undefined, mcpError)), + ); } }); }; @@ -281,7 +361,7 @@ const handleRequest = async ( export const start = async ( brainService: BrainService, - config?: Partial + config?: Partial, ): Promise => { if (state.server) { throw new Error(BRAIN_MCP_MESSAGES.SERVER_ALREADY_RUNNING); @@ -348,5 +428,11 @@ export const updateConfig = (config: Partial): void => { state.config = { ...state.config, ...config }; }; -export const getAvailableTools = (): ReadonlyArray<{ name: string; description: string }> => - BRAIN_MCP_TOOLS.map((t: BrainMcpTool) => ({ name: t.name, description: t.description })); +export const getAvailableTools = (): ReadonlyArray<{ + name: string; + description: string; +}> => + BRAIN_MCP_TOOLS.map((t: BrainMcpTool) => ({ + name: t.name, + description: t.description, + })); diff --git a/src/services/brain/offline-queue.ts b/src/services/brain/offline-queue.ts index 6dbca88..bddeaaa 100644 --- a/src/services/brain/offline-queue.ts +++ b/src/services/brain/offline-queue.ts @@ -128,7 +128,9 @@ export const enqueueBatch = async (items: SyncItem[]): Promise => { /** * Get items from queue for processing */ -export const dequeue = async (limit: number = SYNC_CONFIG.MAX_BATCH_SIZE): Promise => { +export const dequeue = async ( + limit: number = SYNC_CONFIG.MAX_BATCH_SIZE, +): Promise => { await loadQueue(); // Get items that haven't exceeded retry limit diff --git a/src/services/brain/project-service.ts b/src/services/brain/project-service.ts index 5b091fe..5243799 100644 --- a/src/services/brain/project-service.ts +++ b/src/services/brain/project-service.ts @@ -39,7 +39,13 @@ interface ProjectServiceState { const state: ProjectServiceState = { projects: new Map(), activeProjectId: null, - configPath: join(homedir(), ".local", "share", "codetyper", BRAIN_PROJECT_STORAGE.CONFIG_FILE), + configPath: join( + homedir(), + ".local", + "share", + "codetyper", + BRAIN_PROJECT_STORAGE.CONFIG_FILE, + ), initialized: false, }; @@ -116,7 +122,9 @@ export const initialize = async (): Promise => { state.initialized = true; }; -export const createProject = async (input: BrainProjectCreateInput): Promise => { +export const createProject = async ( + input: BrainProjectCreateInput, +): Promise => { await initialize(); // Validate name @@ -130,7 +138,7 @@ export const createProject = async (input: BrainProjectCreateInput): Promise p.name.toLowerCase() === input.name.toLowerCase() + (p) => p.name.toLowerCase() === input.name.toLowerCase(), ); if (existingProject) { @@ -161,7 +169,7 @@ export const createProject = async (input: BrainProjectCreateInput): Promise => { await initialize(); @@ -205,7 +213,9 @@ export const deleteProject = async (projectId: number): Promise => { return true; }; -export const switchProject = async (projectId: number): Promise => { +export const switchProject = async ( + projectId: number, +): Promise => { await initialize(); const newProject = state.projects.get(projectId); @@ -219,7 +229,10 @@ export const switchProject = async (projectId: number): Promise => { +export const getProject = async ( + projectId: number, +): Promise => { await initialize(); return state.projects.get(projectId); }; export const getActiveProject = async (): Promise => { await initialize(); - return state.activeProjectId ? state.projects.get(state.activeProjectId) : undefined; + return state.activeProjectId + ? state.projects.get(state.activeProjectId) + : undefined; }; export const listProjects = async (): Promise => { await initialize(); return { - projects: Array.from(state.projects.values()).sort((a, b) => b.updatedAt - a.updatedAt), + projects: Array.from(state.projects.values()).sort( + (a, b) => b.updatedAt - a.updatedAt, + ), activeProjectId: state.activeProjectId ?? undefined, total: state.projects.size, }; }; -export const findProjectByPath = async (rootPath: string): Promise => { +export const findProjectByPath = async ( + rootPath: string, +): Promise => { await initialize(); - return Array.from(state.projects.values()).find((p) => p.rootPath === rootPath); + return Array.from(state.projects.values()).find( + (p) => p.rootPath === rootPath, + ); }; export const updateProjectStats = async ( projectId: number, - stats: Partial + stats: Partial, ): Promise => { await initialize(); @@ -280,7 +303,9 @@ export const updateProjectStats = async ( await saveProjectsToConfig(); }; -export const exportProject = async (projectId: number): Promise => { +export const exportProject = async ( + projectId: number, +): Promise => { await initialize(); const project = state.projects.get(projectId); @@ -307,7 +332,7 @@ export const exportProject = async (projectId: number): Promise => { await initialize(); @@ -352,7 +377,9 @@ export const importProject = async ( } }; -export const getProjectSettings = async (projectId: number): Promise => { +export const getProjectSettings = async ( + projectId: number, +): Promise => { await initialize(); const project = state.projects.get(projectId); @@ -361,13 +388,15 @@ export const getProjectSettings = async (projectId: number): Promise + settings: Partial, ): Promise => { const project = await updateProject(projectId, { settings }); return project.settings; }; -export const setActiveProjectByPath = async (rootPath: string): Promise => { +export const setActiveProjectByPath = async ( + rootPath: string, +): Promise => { const project = await findProjectByPath(rootPath); if (project) { diff --git a/src/services/cascading-provider/orchestrator.ts b/src/services/cascading-provider/orchestrator.ts index c49d0b4..c73bec1 100644 --- a/src/services/cascading-provider/orchestrator.ts +++ b/src/services/cascading-provider/orchestrator.ts @@ -12,13 +12,13 @@ import type { } from "@/types/provider-quality"; import { PROVIDER_IDS } from "@constants/provider-quality"; import { parseAuditResponse } from "@prompts/audit-prompt"; +import { detectTaskType } from "@services/provider-quality/task-detector"; +import { determineRoute } from "@services/provider-quality/router"; import { - detectTaskType, - determineRoute, recordAuditResult, recordApproval, recordRejection, -} from "@services/provider-quality"; +} from "@services/provider-quality/score-manager"; import { checkOllamaAvailability, checkCopilotAvailability, @@ -39,7 +39,11 @@ export interface CascadeOptions { } export interface ProviderCallFn { - (prompt: string, provider: "ollama" | "copilot", isAudit?: boolean): Promise; + ( + prompt: string, + provider: "ollama" | "copilot", + isAudit?: boolean, + ): Promise; } export const executeCascade = async ( diff --git a/src/services/chat-tui/auth.ts b/src/services/chat-tui/auth.ts index 0ac3ff4..9413598 100644 --- a/src/services/chat-tui/auth.ts +++ b/src/services/chat-tui/auth.ts @@ -3,15 +3,15 @@ */ import { AUTH_MESSAGES } from "@constants/chat-service"; +import { getProviderStatus } from "@providers/core/status"; +import { getCopilotUserInfo } from "@providers/copilot/user-info"; +import { logoutProvider } from "@providers/login/handlers"; import { - getProviderStatus, - getCopilotUserInfo, - logoutProvider, initiateDeviceFlow, pollForAccessToken, - completeCopilotLogin, -} from "@providers/index"; -import { appStore } from "@tui/index"; +} from "@providers/copilot/auth/auth"; +import { completeCopilotLogin } from "@providers/login/core/initialize"; +import { appStore } from "@tui-solid/context/app"; import { loadModels } from "@services/chat-tui/models"; import type { ChatServiceState, diff --git a/src/services/chat-tui/commands.ts b/src/services/chat-tui/commands.ts index 139b617..235a67d 100644 --- a/src/services/chat-tui/commands.ts +++ b/src/services/chat-tui/commands.ts @@ -3,7 +3,7 @@ */ import { saveSession as saveSessionSession } from "@services/core/session"; -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; import { CHAT_MESSAGES, type CommandName } from "@constants/chat-service"; import { handleLogin, handleLogout, showWhoami } from "@services/chat-tui/auth"; import { @@ -14,8 +14,8 @@ import { showUsageStats } from "@services/chat-tui/usage"; import { checkOllamaAvailability, checkCopilotAvailability, -} from "@services/cascading-provider"; -import { getOverallScore } from "@services/provider-quality"; +} from "@services/cascading-provider/availability"; +import { getOverallScore } from "@services/provider-quality/score-manager"; import { PROVIDER_IDS } from "@constants/provider-quality"; import type { ChatServiceState, diff --git a/src/services/chat-tui/files.ts b/src/services/chat-tui/files.ts index d9de607..30d69a2 100644 --- a/src/services/chat-tui/files.ts +++ b/src/services/chat-tui/files.ts @@ -16,7 +16,7 @@ import { BINARY_EXTENSIONS, type BinaryExtension, } from "@constants/file-picker"; -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; import type { ChatServiceState } from "@/types/chat-service"; const isBinaryFile = (filePath: string): boolean => { diff --git a/src/services/chat-tui/initialize.ts b/src/services/chat-tui/initialize.ts index 63bfb28..a5fe7e4 100644 --- a/src/services/chat-tui/initialize.ts +++ b/src/services/chat-tui/initialize.ts @@ -11,8 +11,8 @@ import { } from "@services/core/session"; import { getConfig } from "@services/core/config"; import { initializePermissions } from "@services/core/permissions"; -import { getProviderStatus } from "@providers/index"; -import { appStore } from "@tui/index"; +import { getProviderStatus } from "@providers/core/status"; +import { appStore } from "@tui-solid/context/app"; import { themeActions } from "@stores/core/theme-store"; import { buildBaseContext, @@ -23,7 +23,7 @@ import * as brainService from "@services/brain"; import { BRAIN_DISABLED } from "@constants/brain"; import { addContextFile } from "@services/chat-tui/files"; import type { ProviderName, Message } from "@/types/providers"; -import type { ChatSession } from "@/types/index"; +import type { ChatSession } from "@/types/common"; import type { ChatTUIOptions } from "@interfaces/ChatTUIOptions"; import type { ChatServiceState } from "@/types/chat-service"; import type { InteractionMode } from "@/types/tui"; diff --git a/src/services/chat-tui/message-handler.ts b/src/services/chat-tui/message-handler.ts index 2c9a7e8..0722fbf 100644 --- a/src/services/chat-tui/message-handler.ts +++ b/src/services/chat-tui/message-handler.ts @@ -6,14 +6,13 @@ import { addMessage, saveSession } from "@services/core/session"; import { createStreamingAgent } from "@services/agent-stream"; import { CHAT_MESSAGES } from "@constants/chat-service"; import { enrichMessageWithIssues } from "@services/github-issue-service"; +import { checkGitHubCLI } from "@services/github-pr/cli"; +import { extractPRUrls } from "@services/github-pr/url"; +import { fetchPR, fetchPRComments } from "@services/github-pr/fetch"; import { - checkGitHubCLI, - extractPRUrls, - fetchPR, - fetchPRComments, formatPRContext, formatPendingComments, -} from "@services/github-pr"; +} from "@services/github-pr/format"; import { analyzeFileChange, clearSuggestions, @@ -33,21 +32,25 @@ import { getModelCompactionConfig, checkCompactionNeeded, } from "@services/auto-compaction"; +import { detectTaskType } from "@services/provider-quality/task-detector"; import { - detectTaskType, determineRoute, - recordAuditResult, - isCorrection, getRoutingExplanation, -} from "@services/provider-quality"; +} from "@services/provider-quality/router"; +import { recordAuditResult } from "@services/provider-quality/score-manager"; +import { isCorrection } from "@services/provider-quality/feedback-detector"; import { checkOllamaAvailability, checkCopilotAvailability, -} from "@services/cascading-provider"; +} from "@services/cascading-provider/availability"; import { chat, getDefaultModel } from "@providers/core/chat"; -import { AUDIT_SYSTEM_PROMPT, createAuditPrompt, parseAuditResponse } from "@prompts/audit-prompt"; +import { + AUDIT_SYSTEM_PROMPT, + createAuditPrompt, + parseAuditResponse, +} from "@prompts/audit-prompt"; import { PROVIDER_IDS } from "@constants/provider-quality"; -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; import type { StreamCallbacks } from "@/types/streaming"; import type { TaskType } from "@/types/provider-quality"; import type { @@ -62,10 +65,7 @@ import { detectCommand, executeDetectedCommand, } from "@services/command-detection"; -import { - detectSkillCommand, - executeSkill, -} from "@services/skill-service"; +import { detectSkillCommand, executeSkill } from "@services/skill-service"; // Track last response for feedback learning let lastResponseContext: { @@ -101,7 +101,10 @@ const createToolCallHandler = ) => (call: { id: string; name: string; arguments?: Record }) => { const args = call.arguments; - if ((FILE_MODIFYING_TOOLS as readonly string[]).includes(call.name) && args?.path) { + if ( + (FILE_MODIFYING_TOOLS as readonly string[]).includes(call.name) && + args?.path + ) { toolCallRef.current = { name: call.name, path: String(args.path) }; } else { toolCallRef.current = { name: call.name }; @@ -152,7 +155,10 @@ const createStreamCallbacks = (): StreamCallbacksWithState => { const callbacks: StreamCallbacks = { onContentChunk: (content: string) => { chunkCount++; - addDebugLog("stream", `Chunk #${chunkCount}: "${content.substring(0, 30)}${content.length > 30 ? "..." : ""}"`); + addDebugLog( + "stream", + `Chunk #${chunkCount}: "${content.substring(0, 30)}${content.length > 30 ? "..." : ""}"`, + ); appStore.appendStreamContent(content); }, @@ -187,7 +193,10 @@ const createStreamCallbacks = (): StreamCallbacksWithState => { // Note: Don't call completeStreaming() here! // The agent loop may have multiple iterations (tool calls + final response) // Streaming will be completed manually after the entire agent finishes - addDebugLog("stream", `Stream iteration done (${chunkCount} chunks total)`); + addDebugLog( + "stream", + `Stream iteration done (${chunkCount} chunks total)`, + ); }, onError: (error: string) => { @@ -213,13 +222,20 @@ const runAudit = async ( userPrompt: string, ollamaResponse: string, callbacks: ChatServiceCallbacks, -): Promise<{ approved: boolean; hasMajorIssues: boolean; correctedResponse?: string }> => { +): Promise<{ + approved: boolean; + hasMajorIssues: boolean; + correctedResponse?: string; +}> => { try { callbacks.onLog("system", "Auditing response with Copilot..."); const auditMessages = [ { role: "system" as const, content: AUDIT_SYSTEM_PROMPT }, - { role: "user" as const, content: createAuditPrompt(userPrompt, ollamaResponse) }, + { + role: "user" as const, + content: createAuditPrompt(userPrompt, ollamaResponse), + }, ]; const auditResponse = await chat("copilot", auditMessages, {}); @@ -237,7 +253,8 @@ const runAudit = async ( return { approved: parsed.approved, - hasMajorIssues: parsed.severity === "major" || parsed.severity === "critical", + hasMajorIssues: + parsed.severity === "major" || parsed.severity === "critical", correctedResponse: parsed.correctedResponse, }; } catch (error) { @@ -285,10 +302,7 @@ export const handleMessage = async ( const skillMatch = await detectSkillCommand(message); if (skillMatch) { addDebugLog("info", `Detected skill: /${skillMatch.skill.command}`); - callbacks.onLog( - "system", - `Running skill: ${skillMatch.skill.name}`, - ); + callbacks.onLog("system", `Running skill: ${skillMatch.skill.name}`); // Execute the skill and get the expanded prompt const { expandedPrompt } = executeSkill(skillMatch.skill, skillMatch.args); @@ -302,7 +316,10 @@ export const handleMessage = async ( // Process the expanded prompt as the actual message // Fall through to normal processing with the expanded prompt message = expandedPrompt; - addDebugLog("info", `Expanded skill prompt: ${expandedPrompt.substring(0, 100)}...`); + addDebugLog( + "info", + `Expanded skill prompt: ${expandedPrompt.substring(0, 100)}...`, + ); } // Detect explicit command requests and execute directly @@ -328,7 +345,10 @@ export const handleMessage = async ( }); appStore.setMode("tool_execution"); - const result = await executeDetectedCommand(detected.command, process.cwd()); + const result = await executeDetectedCommand( + detected.command, + process.cwd(), + ); appStore.setMode("idle"); // Show result @@ -351,15 +371,13 @@ export const handleMessage = async ( // Get interaction mode and cascade setting from app store const { interactionMode, cascadeEnabled } = appStore.getState(); - const isReadOnlyMode = interactionMode === "ask" || interactionMode === "code-review"; + const isReadOnlyMode = + interactionMode === "ask" || interactionMode === "code-review"; // Rebuild system prompt if mode has changed if (state.currentMode !== interactionMode) { await rebuildSystemPromptForMode(state, interactionMode); - callbacks.onLog( - "system", - `Switched to ${interactionMode} mode`, - ); + callbacks.onLog("system", `Switched to ${interactionMode} mode`); } if (isReadOnlyMode) { @@ -494,7 +512,10 @@ export const handleMessage = async ( cascadeEnabled: true, }); - const explanation = await getRoutingExplanation(routingDecision, taskType); + const explanation = await getRoutingExplanation( + routingDecision, + taskType, + ); callbacks.onLog("system", explanation); if (routingDecision === "ollama_only") { @@ -518,8 +539,14 @@ export const handleMessage = async ( : getDefaultModel(effectiveProvider); // Start streaming UI - addDebugLog("state", `Starting request: provider=${effectiveProvider}, model=${effectiveModel}`); - addDebugLog("state", `Mode: ${appStore.getState().interactionMode}, Cascade: ${cascadeEnabled}`); + addDebugLog( + "state", + `Starting request: provider=${effectiveProvider}, model=${effectiveModel}`, + ); + addDebugLog( + "state", + `Mode: ${appStore.getState().interactionMode}, Cascade: ${cascadeEnabled}`, + ); appStore.setMode("thinking"); appStore.startThinking(); appStore.startStreaming(); @@ -554,20 +581,33 @@ export const handleMessage = async ( currentAgent = agent; try { - addDebugLog("api", `Agent.run() started with ${state.messages.length} messages`); + addDebugLog( + "api", + `Agent.run() started with ${state.messages.length} messages`, + ); const result = await agent.run(state.messages); - addDebugLog("api", `Agent.run() completed: success=${result.success}, iterations=${result.iterations}`); + addDebugLog( + "api", + `Agent.run() completed: success=${result.success}, iterations=${result.iterations}`, + ); // Stop thinking timer appStore.stopThinking(); if (result.finalResponse) { - addDebugLog("info", `Final response length: ${result.finalResponse.length} chars`); + addDebugLog( + "info", + `Final response length: ${result.finalResponse.length} chars`, + ); let finalResponse = result.finalResponse; // Run audit if cascade mode with Ollama if (shouldAudit && effectiveProvider === "ollama") { - const auditResult = await runAudit(message, result.finalResponse, callbacks); + const auditResult = await runAudit( + message, + result.finalResponse, + callbacks, + ); // Record quality score based on audit await recordAuditResult( @@ -599,7 +639,10 @@ export const handleMessage = async ( // Check if streaming content was received - if not, add the response as a log // This handles cases where streaming didn't work or content was all in final response if (!streamState.hasReceivedContent() && finalResponse) { - addDebugLog("info", "No streaming content received, adding fallback log"); + addDebugLog( + "info", + "No streaming content received, adding fallback log", + ); // Streaming didn't receive content, manually add the response appStore.cancelStreaming(); // Remove empty streaming log appStore.addLog({ @@ -616,11 +659,7 @@ export const handleMessage = async ( addMessage("assistant", finalResponse); await saveSession(); - await processLearningsFromExchange( - message, - finalResponse, - callbacks, - ); + await processLearningsFromExchange(message, finalResponse, callbacks); const suggestions = getPendingSuggestions(); if (suggestions.length > 0) { diff --git a/src/services/chat-tui/models.ts b/src/services/chat-tui/models.ts index 2b64b1e..9a31afa 100644 --- a/src/services/chat-tui/models.ts +++ b/src/services/chat-tui/models.ts @@ -4,12 +4,12 @@ import { MODEL_MESSAGES } from "@constants/chat-service"; import { getConfig } from "@services/core/config"; +import { getProvider } from "@providers/core/registry"; import { - getProvider, getDefaultModel, getModels as getProviderModels, -} from "@providers/index"; -import { appStore } from "@tui/index"; +} from "@providers/core/chat"; +import { appStore } from "@tui-solid/context/app"; import type { ProviderName, ProviderModel } from "@/types/providers"; import type { ChatServiceState, diff --git a/src/services/chat-tui/permissions.ts b/src/services/chat-tui/permissions.ts index abf3535..badaac9 100644 --- a/src/services/chat-tui/permissions.ts +++ b/src/services/chat-tui/permissions.ts @@ -9,7 +9,7 @@ import type { PermissionPromptRequest, PermissionPromptResponse, } from "@/types/permissions"; -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; export const createPermissionHandler = (): (( request: PermissionPromptRequest, diff --git a/src/services/chat-tui/streaming.ts b/src/services/chat-tui/streaming.ts index 18269c9..be6ba9d 100644 --- a/src/services/chat-tui/streaming.ts +++ b/src/services/chat-tui/streaming.ts @@ -15,7 +15,7 @@ import type { } from "@/types/streaming"; import type { ToolCall, ToolResult } from "@/types/tools"; import { createStreamingAgent } from "@services/agent-stream"; -import { appStore } from "@tui/index"; +import { appStore } from "@tui-solid/context/app"; // Re-export for convenience export type { StreamingChatOptions } from "@interfaces/StreamingChatOptions"; diff --git a/src/services/chat-tui/usage.ts b/src/services/chat-tui/usage.ts index ed55ab4..86cb8ef 100644 --- a/src/services/chat-tui/usage.ts +++ b/src/services/chat-tui/usage.ts @@ -55,7 +55,9 @@ const formatQuotaBar = ( if (quota.unlimited) { lines.push(name); - lines.push(PROGRESS_BAR.FILLED_CHAR.repeat(PROGRESS_BAR.WIDTH) + " Unlimited"); + lines.push( + PROGRESS_BAR.FILLED_CHAR.repeat(PROGRESS_BAR.WIDTH) + " Unlimited", + ); return lines; } diff --git a/src/services/clipboard-service.ts b/src/services/clipboard-service.ts index 31bada1..03258ad 100644 --- a/src/services/clipboard-service.ts +++ b/src/services/clipboard-service.ts @@ -58,7 +58,12 @@ const runCommand = ( const detectImageType = (buffer: Buffer): ImageMediaType | null => { // PNG: 89 50 4E 47 - if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) { + if ( + buffer[0] === 0x89 && + buffer[1] === 0x50 && + buffer[2] === 0x4e && + buffer[3] === 0x47 + ) { return "image/png"; } // JPEG: FF D8 FF @@ -66,12 +71,27 @@ const detectImageType = (buffer: Buffer): ImageMediaType | null => { return "image/jpeg"; } // GIF: 47 49 46 38 - if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) { + if ( + buffer[0] === 0x47 && + buffer[1] === 0x49 && + buffer[2] === 0x46 && + buffer[3] === 0x38 + ) { return "image/gif"; } // WebP: 52 49 46 46 ... 57 45 42 50 - if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46) { - if (buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) { + if ( + buffer[0] === 0x52 && + buffer[1] === 0x49 && + buffer[2] === 0x46 && + buffer[3] === 0x46 + ) { + if ( + buffer[8] === 0x57 && + buffer[9] === 0x45 && + buffer[10] === 0x42 && + buffer[11] === 0x50 + ) { return "image/webp"; } } @@ -134,7 +154,10 @@ const readClipboardImageMacOS = async (): Promise => { const readClipboardImageLinux = async (): Promise => { // Try xclip first, then wl-paste for Wayland const commands = [ - { cmd: "xclip", args: ["-selection", "clipboard", "-t", "image/png", "-o"] }, + { + cmd: "xclip", + args: ["-selection", "clipboard", "-t", "image/png", "-o"], + }, { cmd: "wl-paste", args: ["--type", "image/png"] }, ]; diff --git a/src/services/confidence-filter.ts b/src/services/confidence-filter.ts index 16e1766..d26aaac 100644 --- a/src/services/confidence-filter.ts +++ b/src/services/confidence-filter.ts @@ -16,15 +16,24 @@ import { CONFIDENCE_LEVELS, DEFAULT_CONFIDENCE_FILTER_CONFIG, } from "@/types/confidence-filter"; -import { CONFIDENCE_FILTER, CONFIDENCE_WEIGHTS } from "@constants/confidence-filter"; +import { + CONFIDENCE_FILTER, + CONFIDENCE_WEIGHTS, +} from "@constants/confidence-filter"; export const calculateConfidenceLevel = (score: number): ConfidenceLevel => { - const levels = Object.entries(CONFIDENCE_LEVELS) as Array<[ConfidenceLevel, { min: number; max: number }]>; - const found = levels.find(([, range]) => score >= range.min && score <= range.max); + const levels = Object.entries(CONFIDENCE_LEVELS) as Array< + [ConfidenceLevel, { min: number; max: number }] + >; + const found = levels.find( + ([, range]) => score >= range.min && score <= range.max, + ); return found ? found[0] : "low"; }; -export const calculateConfidenceScore = (factors: ReadonlyArray): ConfidenceScore => { +export const calculateConfidenceScore = ( + factors: ReadonlyArray, +): ConfidenceScore => { const totalWeight = factors.reduce((sum, f) => sum + f.weight, 0); const weightedSum = factors.reduce((sum, f) => sum + f.score * f.weight, 0); const value = totalWeight > 0 ? Math.round(weightedSum / totalWeight) : 0; @@ -40,7 +49,7 @@ export const createConfidenceFactor = ( name: string, score: number, weight: number, - reason: string + reason: string, ): ConfidenceFactor => ({ name, score: Math.max(0, Math.min(100, score)), @@ -48,51 +57,67 @@ export const createConfidenceFactor = ( reason, }); -export const createPatternMatchFactor = (matchCount: number, expectedCount: number): ConfidenceFactor => +export const createPatternMatchFactor = ( + matchCount: number, + expectedCount: number, +): ConfidenceFactor => createConfidenceFactor( "Pattern Match", Math.min(100, (matchCount / Math.max(1, expectedCount)) * 100), CONFIDENCE_WEIGHTS.PATTERN_MATCH, - `Matched ${matchCount}/${expectedCount} expected patterns` + `Matched ${matchCount}/${expectedCount} expected patterns`, ); -export const createContextRelevanceFactor = (relevanceScore: number): ConfidenceFactor => +export const createContextRelevanceFactor = ( + relevanceScore: number, +): ConfidenceFactor => createConfidenceFactor( "Context Relevance", relevanceScore, CONFIDENCE_WEIGHTS.CONTEXT_RELEVANCE, - `Context relevance score: ${relevanceScore}%` + `Context relevance score: ${relevanceScore}%`, ); -export const createSeverityFactor = (severity: "low" | "medium" | "high" | "critical"): ConfidenceFactor => { - const severityScores: Record = { low: 40, medium: 60, high: 80, critical: 95 }; +export const createSeverityFactor = ( + severity: "low" | "medium" | "high" | "critical", +): ConfidenceFactor => { + const severityScores: Record = { + low: 40, + medium: 60, + high: 80, + critical: 95, + }; return createConfidenceFactor( "Severity Level", severityScores[severity] ?? 50, CONFIDENCE_WEIGHTS.SEVERITY_LEVEL, - `Issue severity: ${severity}` + `Issue severity: ${severity}`, ); }; -export const createCodeAnalysisFactor = (analysisScore: number): ConfidenceFactor => +export const createCodeAnalysisFactor = ( + analysisScore: number, +): ConfidenceFactor => createConfidenceFactor( "Code Analysis", analysisScore, CONFIDENCE_WEIGHTS.CODE_ANALYSIS, - `Static analysis confidence: ${analysisScore}%` + `Static analysis confidence: ${analysisScore}%`, ); -export const createHistoricalAccuracyFactor = (accuracy: number): ConfidenceFactor => +export const createHistoricalAccuracyFactor = ( + accuracy: number, +): ConfidenceFactor => createConfidenceFactor( "Historical Accuracy", accuracy, CONFIDENCE_WEIGHTS.HISTORICAL_ACCURACY, - `Historical accuracy for similar issues: ${accuracy}%` + `Historical accuracy for similar issues: ${accuracy}%`, ); export const filterByConfidence = ( items: ReadonlyArray<{ item: T; confidence: ConfidenceScore }>, - config: ConfidenceFilterConfig = DEFAULT_CONFIDENCE_FILTER_CONFIG + config: ConfidenceFilterConfig = DEFAULT_CONFIDENCE_FILTER_CONFIG, ): ReadonlyArray> => items.map(({ item, confidence }) => ({ item, @@ -100,11 +125,12 @@ export const filterByConfidence = ( passed: confidence.value >= config.minThreshold, })); -export const filterPassedOnly = (results: ReadonlyArray>): ReadonlyArray => - results.filter((r) => r.passed).map((r) => r.item); +export const filterPassedOnly = ( + results: ReadonlyArray>, +): ReadonlyArray => results.filter((r) => r.passed).map((r) => r.item); export const groupByConfidenceLevel = ( - results: ReadonlyArray> + results: ReadonlyArray>, ): Record>> => ({ low: results.filter((r) => r.confidence.level === "low"), medium: results.filter((r) => r.confidence.level === "medium"), @@ -112,10 +138,15 @@ export const groupByConfidenceLevel = ( critical: results.filter((r) => r.confidence.level === "critical"), }); -export const calculateFilterStats = (results: ReadonlyArray>): ConfidenceFilterStats => { +export const calculateFilterStats = ( + results: ReadonlyArray>, +): ConfidenceFilterStats => { const passed = results.filter((r) => r.passed).length; const grouped = groupByConfidenceLevel(results); - const totalConfidence = results.reduce((sum, r) => sum + r.confidence.value, 0); + const totalConfidence = results.reduce( + (sum, r) => sum + r.confidence.value, + 0, + ); return { total: results.length, @@ -127,24 +158,33 @@ export const calculateFilterStats = (results: ReadonlyArray high: grouped.high.length, critical: grouped.critical.length, }, - averageConfidence: results.length > 0 ? Math.round(totalConfidence / results.length) : 0, + averageConfidence: + results.length > 0 ? Math.round(totalConfidence / results.length) : 0, }; }; export const validateConfidence = async ( confidence: ConfidenceScore, - validatorFn: (factors: ReadonlyArray) => Promise<{ validated: boolean; adjustment: number; notes: string }> + validatorFn: ( + factors: ReadonlyArray, + ) => Promise<{ validated: boolean; adjustment: number; notes: string }>, ): Promise => { const result = await validatorFn(confidence.factors); return { validated: result.validated, - adjustedConfidence: Math.max(0, Math.min(100, confidence.value + result.adjustment)), + adjustedConfidence: Math.max( + 0, + Math.min(100, confidence.value + result.adjustment), + ), validatorNotes: result.notes, }; }; -export const formatConfidenceScore = (confidence: ConfidenceScore, showFactors: boolean = false): string => { +export const formatConfidenceScore = ( + confidence: ConfidenceScore, + showFactors: boolean = false, +): string => { const levelColors: Record = { low: "\x1b[90m", medium: "\x1b[33m", @@ -158,7 +198,10 @@ export const formatConfidenceScore = (confidence: ConfidenceScore, showFactors: if (showFactors && confidence.factors.length > 0) { const factorLines = confidence.factors - .map((f: ConfidenceFactor) => ` - ${f.name}: ${f.score}% (weight: ${f.weight})`) + .map( + (f: ConfidenceFactor) => + ` - ${f.name}: ${f.score}% (weight: ${f.weight})`, + ) .join("\n"); result += `\n${factorLines}`; } @@ -168,7 +211,7 @@ export const formatConfidenceScore = (confidence: ConfidenceScore, showFactors: export const mergeConfidenceFactors = ( existing: ReadonlyArray, - additional: ReadonlyArray + additional: ReadonlyArray, ): ReadonlyArray => { const factorMap = new Map(); @@ -191,7 +234,11 @@ export const mergeConfidenceFactors = ( export const adjustThreshold = ( baseThreshold: number, - context: { isCritical: boolean; isAutomated: boolean; userPreference?: number } + context: { + isCritical: boolean; + isAutomated: boolean; + userPreference?: number; + }, ): number => { let threshold = context.userPreference ?? baseThreshold; diff --git a/src/services/context-gathering.ts b/src/services/context-gathering.ts index d3ddd71..1a25f9a 100644 --- a/src/services/context-gathering.ts +++ b/src/services/context-gathering.ts @@ -37,7 +37,7 @@ const PROJECT_MARKERS: Record = { "pyproject.toml": { type: "Python", language: "Python" }, "setup.py": { type: "Python", language: "Python" }, "requirements.txt": { type: "Python", language: "Python" }, - "Gemfile": { type: "Ruby", language: "Ruby" }, + Gemfile: { type: "Ruby", language: "Ruby" }, "composer.json": { type: "PHP", language: "PHP" }, ".csproj": { type: ".NET", language: "C#" }, }; @@ -73,7 +73,9 @@ const IGNORED_DIRS = new Set([ "coverage", ]); -const detectProjectType = (workingDir: string): { type: string; language: string } => { +const detectProjectType = ( + workingDir: string, +): { type: string; language: string } => { for (const [marker, info] of Object.entries(PROJECT_MARKERS)) { if (existsSync(join(workingDir, marker))) { return info; @@ -132,7 +134,12 @@ const getDirectoryStructure = ( if (stat.isDirectory()) { entries.push(`${indent}${item}/`); - const subEntries = getDirectoryStructure(fullPath, baseDir, depth + 1, maxDepth); + const subEntries = getDirectoryStructure( + fullPath, + baseDir, + depth + 1, + maxDepth, + ); entries.push(...subEntries); } else if (depth < 2) { entries.push(`${indent}${item}`); diff --git a/src/services/core/agent.ts b/src/services/core/agent.ts index 2d1484c..f15c601 100644 --- a/src/services/core/agent.ts +++ b/src/services/core/agent.ts @@ -19,7 +19,7 @@ import type { ToolCallMessage, ToolResultMessage, } from "@/types/agent"; -import { chat as providerChat } from "@providers/index"; +import { chat as providerChat } from "@providers/core/chat"; import { getTool, getToolsForApi, refreshMCPTools } from "@tools/index"; import type { ToolContext, ToolCall, ToolResult } from "@/types/tools"; import { initializePermissions } from "@services/core/permissions"; diff --git a/src/services/core/config.ts b/src/services/core/config.ts index 3914f4b..ea63e51 100644 --- a/src/services/core/config.ts +++ b/src/services/core/config.ts @@ -4,7 +4,7 @@ import fs from "fs/promises"; import path from "path"; -import type { Config, Provider } from "@/types/index"; +import type { Config, Provider } from "@/types/common"; import { DIRS, FILES } from "@constants/paths"; /** diff --git a/src/services/core/session.ts b/src/services/core/session.ts index 73c5fd3..fcbfe84 100644 --- a/src/services/core/session.ts +++ b/src/services/core/session.ts @@ -1,7 +1,6 @@ import fs from "fs/promises"; import path from "path"; -import type { AgentType } from "@/types/common"; -import { ChatSession, ChatMessage, AgentType } from "@interafaces/index"; +import type { AgentType, ChatSession, ChatMessage } from "@/types/common"; import type { SessionInfo } from "@/types/session"; import { DIRS } from "@constants/paths"; diff --git a/src/services/feature-dev/checkpoint-handler.ts b/src/services/feature-dev/checkpoint-handler.ts index d4be102..c5ee243 100644 --- a/src/services/feature-dev/checkpoint-handler.ts +++ b/src/services/feature-dev/checkpoint-handler.ts @@ -4,10 +4,7 @@ * Manages user approval checkpoints during feature development. */ -import { - PHASE_CHECKPOINTS, - FEATURE_DEV_ERRORS, -} from "@constants/feature-dev"; +import { PHASE_CHECKPOINTS, FEATURE_DEV_ERRORS } from "@constants/feature-dev"; import type { FeatureDevPhase, FeatureDevState, @@ -80,7 +77,9 @@ const buildCheckpointSummary = ( }, review: () => { - const issues = state.reviewFindings.filter((f) => f.type === "issue").length; + const issues = state.reviewFindings.filter( + (f) => f.type === "issue", + ).length; const suggestions = state.reviewFindings.filter( (f) => f.type === "suggestion", ).length; @@ -145,7 +144,7 @@ export const processCheckpointDecision = ( > = { approve: () => ({ proceed: true }), reject: () => ({ proceed: false, action: "rejected" }), - modify: () => ({ proceed: false, action: "modify", }), + modify: () => ({ proceed: false, action: "modify" }), skip: () => ({ proceed: true, action: "skipped" }), abort: () => ({ proceed: false, action: "aborted" }), }; diff --git a/src/services/feature-dev/context-builder.ts b/src/services/feature-dev/context-builder.ts index 5eaf4a0..9287fba 100644 --- a/src/services/feature-dev/context-builder.ts +++ b/src/services/feature-dev/context-builder.ts @@ -4,14 +4,8 @@ * Builds context for each phase of feature development. */ -import { - PHASE_PROMPTS, - PHASE_DESCRIPTIONS, -} from "@constants/feature-dev"; -import type { - FeatureDevPhase, - FeatureDevState, -} from "@/types/feature-dev"; +import { PHASE_PROMPTS, PHASE_DESCRIPTIONS } from "@constants/feature-dev"; +import type { FeatureDevPhase, FeatureDevState } from "@/types/feature-dev"; /** * Build the full context for a phase execution @@ -220,7 +214,9 @@ const buildStateContext = ( // Test status if (state.testResults) { - const status = state.testResults.passed ? "✓ All tests passing" : "✗ Tests failing"; + const status = state.testResults.passed + ? "✓ All tests passing" + : "✗ Tests failing"; lines.push(`### Test Status: ${status}`); } diff --git a/src/services/github-pr/fetch.ts b/src/services/github-pr/fetch.ts index e5f136f..bc676ce 100644 --- a/src/services/github-pr/fetch.ts +++ b/src/services/github-pr/fetch.ts @@ -50,9 +50,7 @@ const runGitCommand = ( /** * Fetch PR details using gh CLI */ -export const fetchPR = async ( - parts: PRUrlParts, -): Promise => { +export const fetchPR = async (parts: PRUrlParts): Promise => { const { owner, repo, prNumber } = parts; const result = await executeGHCommand([ diff --git a/src/services/github-pr/format.ts b/src/services/github-pr/format.ts index 533df04..e1f359e 100644 --- a/src/services/github-pr/format.ts +++ b/src/services/github-pr/format.ts @@ -48,7 +48,9 @@ export const formatPRComments = (comments: GitHubPRComment[]): string => { for (const comment of comments) { lines.push(`### Comment by ${comment.author}`); if (comment.path) { - lines.push(`**File:** ${comment.path}${comment.line ? `:${comment.line}` : ""}`); + lines.push( + `**File:** ${comment.path}${comment.line ? `:${comment.line}` : ""}`, + ); } lines.push(`**Date:** ${new Date(comment.createdAt).toLocaleDateString()}`); lines.push(""); @@ -90,7 +92,9 @@ export const formatPRReviews = (reviews: GitHubPRReview[]): string => { for (const review of reviews) { const emoji = stateEmojis[review.state] || ""; lines.push(`### ${emoji} ${review.state} by ${review.author}`); - lines.push(`**Date:** ${new Date(review.submittedAt).toLocaleDateString()}`); + lines.push( + `**Date:** ${new Date(review.submittedAt).toLocaleDateString()}`, + ); if (review.body) { lines.push(""); @@ -154,7 +158,9 @@ export const formatCommentForSolving = (comment: GitHubPRComment): string => { } lines.push(""); - lines.push("Please address this comment by making the necessary changes to the code."); + lines.push( + "Please address this comment by making the necessary changes to the code.", + ); return lines.join("\n"); }; @@ -167,17 +173,16 @@ export const formatPendingComments = (comments: GitHubPRComment[]): string => { return "No pending comments to address."; } - const lines: string[] = [ - `## ${comments.length} Comment(s) to Address`, - "", - ]; + const lines: string[] = [`## ${comments.length} Comment(s) to Address`, ""]; for (let i = 0; i < comments.length; i++) { const comment = comments[i]; lines.push(`### ${i + 1}. Comment by ${comment.author}`); if (comment.path) { - lines.push(`**File:** ${comment.path}${comment.line ? `:${comment.line}` : ""}`); + lines.push( + `**File:** ${comment.path}${comment.line ? `:${comment.line}` : ""}`, + ); } lines.push(""); diff --git a/src/services/hooks-service.ts b/src/services/hooks-service.ts index f73cfdb..cbd6619 100644 --- a/src/services/hooks-service.ts +++ b/src/services/hooks-service.ts @@ -46,7 +46,9 @@ const hooksCache: HooksCache = { /** * Load hooks configuration from a file */ -const loadHooksFromFile = async (filePath: string): Promise => { +const loadHooksFromFile = async ( + filePath: string, +): Promise => { try { await access(filePath, constants.R_OK); const content = await readFile(filePath, "utf-8"); @@ -57,7 +59,7 @@ const loadHooksFromFile = async (filePath: string): Promise => } return config.hooks.filter( - (hook) => hook.enabled !== false && hook.event && hook.script + (hook) => hook.enabled !== false && hook.event && hook.script, ); } catch { return []; @@ -117,7 +119,7 @@ const resolveScriptPath = (script: string, workingDir: string): string => { const executeHookScript = async ( hook: HookDefinition, input: HookInput, - workingDir: string + workingDir: string, ): Promise => { const scriptPath = resolveScriptPath(hook.script, workingDir); const timeout = hook.timeout ?? DEFAULT_HOOK_TIMEOUT; @@ -201,7 +203,8 @@ const executeHookScript = async ( } else if (exitCode === HOOK_EXIT_CODES.BLOCK) { resolvePromise({ action: "block", - message: stderr.trim() || `Blocked by hook: ${hook.name || hook.script}`, + message: + stderr.trim() || `Blocked by hook: ${hook.name || hook.script}`, }); } else { resolvePromise({ @@ -231,7 +234,7 @@ const executeHookScript = async ( const executeHooks = async ( event: HookEventType, input: HookInput, - workingDir: string + workingDir: string, ): Promise => { const hooks = getHooksForEvent(event); @@ -288,7 +291,7 @@ export const executePreToolUseHooks = async ( sessionId: string, toolName: string, toolArgs: Record, - workingDir: string + workingDir: string, ): Promise => { if (!hooksCache.loaded) { await loadHooks(workingDir); @@ -312,7 +315,7 @@ export const executePostToolUseHooks = async ( toolName: string, toolArgs: Record, result: ToolResult, - workingDir: string + workingDir: string, ): Promise => { if (!hooksCache.loaded) { await loadHooks(workingDir); @@ -341,7 +344,7 @@ export const executeSessionStartHooks = async ( sessionId: string, workingDir: string, provider: string, - model: string + model: string, ): Promise => { if (!hooksCache.loaded) { await loadHooks(workingDir); @@ -364,7 +367,7 @@ export const executeSessionEndHooks = async ( sessionId: string, workingDir: string, duration: number, - messageCount: number + messageCount: number, ): Promise => { if (!hooksCache.loaded) { await loadHooks(workingDir); @@ -386,7 +389,7 @@ export const executeSessionEndHooks = async ( export const executeUserPromptSubmitHooks = async ( sessionId: string, prompt: string, - workingDir: string + workingDir: string, ): Promise => { if (!hooksCache.loaded) { await loadHooks(workingDir); @@ -407,7 +410,7 @@ export const executeUserPromptSubmitHooks = async ( export const executeStopHooks = async ( sessionId: string, workingDir: string, - reason: "interrupt" | "complete" | "error" + reason: "interrupt" | "complete" | "error", ): Promise => { if (!hooksCache.loaded) { await loadHooks(workingDir); diff --git a/src/services/lsp/client.ts b/src/services/lsp/client.ts index 028ed5b..706bdf9 100644 --- a/src/services/lsp/client.ts +++ b/src/services/lsp/client.ts @@ -49,7 +49,10 @@ export interface DocumentSymbol { } export interface Hover { - contents: string | { kind: string; value: string } | Array; + contents: + | string + | { kind: string; value: string } + | Array; range?: Range; } @@ -163,7 +166,10 @@ export class LSPClient extends EventEmitter { private handleNotification(method: string, params: unknown): void { if (method === "textDocument/publishDiagnostics") { - const { uri, diagnostics } = params as { uri: string; diagnostics: Diagnostic[] }; + const { uri, diagnostics } = params as { + uri: string; + diagnostics: Diagnostic[]; + }; this.diagnosticsMap.set(uri, diagnostics); this.emit("diagnostics", uri, diagnostics); } @@ -213,7 +219,9 @@ export class LSPClient extends EventEmitter { async initialize(): Promise { if (this.initialized) return; - const result = await this.request<{ capabilities: Record }>("initialize", { + const result = await this.request<{ + capabilities: Record; + }>("initialize", { processId: process.pid, rootUri: `file://${this.root}`, rootPath: this.root, @@ -319,45 +327,60 @@ export class LSPClient extends EventEmitter { } } - async getDefinition(filePath: string, position: Position): Promise { + async getDefinition( + filePath: string, + position: Position, + ): Promise { const uri = `file://${filePath}`; try { - return await this.request("textDocument/definition", { - textDocument: { uri }, - position, - }); + return await this.request( + "textDocument/definition", + { + textDocument: { uri }, + position, + }, + ); } catch { return null; } } - async getReferences(filePath: string, position: Position, includeDeclaration = true): Promise { + async getReferences( + filePath: string, + position: Position, + includeDeclaration = true, + ): Promise { const uri = `file://${filePath}`; try { - const result = await this.request("textDocument/references", { - textDocument: { uri }, - position, - context: { includeDeclaration }, - }); + const result = await this.request( + "textDocument/references", + { + textDocument: { uri }, + position, + context: { includeDeclaration }, + }, + ); return result ?? []; } catch { return []; } } - async getCompletions(filePath: string, position: Position): Promise { + async getCompletions( + filePath: string, + position: Position, + ): Promise { const uri = `file://${filePath}`; try { - const result = await this.request<{ items: CompletionItem[] } | CompletionItem[] | null>( - "textDocument/completion", - { - textDocument: { uri }, - position, - }, - ); + const result = await this.request< + { items: CompletionItem[] } | CompletionItem[] | null + >("textDocument/completion", { + textDocument: { uri }, + position, + }); if (!result) return []; return Array.isArray(result) ? result : result.items; @@ -370,9 +393,12 @@ export class LSPClient extends EventEmitter { const uri = `file://${filePath}`; try { - const result = await this.request("textDocument/documentSymbol", { - textDocument: { uri }, - }); + const result = await this.request( + "textDocument/documentSymbol", + { + textDocument: { uri }, + }, + ); return result ?? []; } catch { return []; diff --git a/src/services/lsp/index.ts b/src/services/lsp/index.ts index 76fc173..5e4c2c9 100644 --- a/src/services/lsp/index.ts +++ b/src/services/lsp/index.ts @@ -152,7 +152,10 @@ export const openFile = async (filePath: string): Promise => { } }; -export const updateFile = async (filePath: string, content: string): Promise => { +export const updateFile = async ( + filePath: string, + content: string, +): Promise => { const absolutePath = path.resolve(filePath); const clients = await getClientsForFile(absolutePath); @@ -176,7 +179,10 @@ export const closeFile = async (filePath: string): Promise => { } }; -export const getHover = async (filePath: string, position: Position): Promise => { +export const getHover = async ( + filePath: string, + position: Position, +): Promise => { const absolutePath = path.resolve(filePath); const clients = await getClientsForFile(absolutePath); @@ -213,7 +219,11 @@ export const getReferences = async ( const allRefs: Location[] = []; for (const client of clients) { - const refs = await client.getReferences(absolutePath, position, includeDeclaration); + const refs = await client.getReferences( + absolutePath, + position, + includeDeclaration, + ); allRefs.push(...refs); } @@ -243,7 +253,9 @@ export const getCompletions = async ( return allCompletions; }; -export const getDocumentSymbols = async (filePath: string): Promise => { +export const getDocumentSymbols = async ( + filePath: string, +): Promise => { const absolutePath = path.resolve(filePath); const clients = await getClientsForFile(absolutePath); @@ -255,7 +267,9 @@ export const getDocumentSymbols = async (filePath: string): Promise => { +export const getDiagnostics = ( + filePath?: string, +): Map => { const allDiagnostics = new Map(); for (const client of state.clients.values()) { @@ -278,7 +292,9 @@ export const getStatus = (): { connected: Array<{ serverId: string; root: string }>; broken: string[]; } => { - const connected = Array.from(state.clients.values()).map((client) => client.getInfo()); + const connected = Array.from(state.clients.values()).map((client) => + client.getInfo(), + ); const broken = Array.from(state.broken); return { connected, broken }; @@ -303,7 +319,11 @@ export const shutdown = (): void => { }; export const onDiagnostics = ( - callback: (data: { uri: string; diagnostics: Diagnostic[]; serverId: string }) => void, + callback: (data: { + uri: string; + diagnostics: Diagnostic[]; + serverId: string; + }) => void, ): (() => void) => { events.on("diagnostics", callback); return () => events.off("diagnostics", callback); diff --git a/src/services/lsp/language.ts b/src/services/lsp/language.ts index 05ff15b..c2c2463 100644 --- a/src/services/lsp/language.ts +++ b/src/services/lsp/language.ts @@ -166,9 +166,13 @@ export const LANGUAGE_EXTENSIONS: Record = { export const getLanguageId = (filePath: string): string | null => { const ext = filePath.includes(".") ? "." + filePath.split(".").pop() - : filePath.split("/").pop() ?? ""; + : (filePath.split("/").pop() ?? ""); - return LANGUAGE_EXTENSIONS[ext] ?? LANGUAGE_EXTENSIONS[filePath.split("/").pop() ?? ""] ?? null; + return ( + LANGUAGE_EXTENSIONS[ext] ?? + LANGUAGE_EXTENSIONS[filePath.split("/").pop() ?? ""] ?? + null + ); }; export const getExtensionsForLanguage = (languageId: string): string[] => { diff --git a/src/services/lsp/server.ts b/src/services/lsp/server.ts index 8dea474..fd8a4ea 100644 --- a/src/services/lsp/server.ts +++ b/src/services/lsp/server.ts @@ -54,8 +54,12 @@ const findProjectRoot = async ( const findBinary = async (name: string): Promise => { try { - const command = process.platform === "win32" ? `where ${name}` : `which ${name}`; - const result = execSync(command, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }); + const command = + process.platform === "win32" ? `where ${name}` : `which ${name}`; + const result = execSync(command, { + encoding: "utf-8", + stdio: ["pipe", "pipe", "pipe"], + }); return result.trim().split("\n")[0] || null; } catch { return null; @@ -83,7 +87,12 @@ export const SERVERS: Record = { id: "python", name: "Pyright", extensions: [".py", ".pyi"], - rootPatterns: ["pyproject.toml", "setup.py", "requirements.txt", "pyrightconfig.json"], + rootPatterns: [ + "pyproject.toml", + "setup.py", + "requirements.txt", + "pyrightconfig.json", + ], command: "pyright-langserver", args: ["--stdio"], }, @@ -160,7 +169,12 @@ export const SERVERS: Record = { id: "eslint", name: "ESLint Language Server", extensions: [".ts", ".tsx", ".js", ".jsx"], - rootPatterns: [".eslintrc", ".eslintrc.js", ".eslintrc.json", "eslint.config.js"], + rootPatterns: [ + ".eslintrc", + ".eslintrc.js", + ".eslintrc.json", + "eslint.config.js", + ], command: "vscode-eslint-language-server", args: ["--stdio"], }, @@ -212,8 +226,7 @@ export const getServersForFile = (filePath: string): ServerInfo[] => { return Object.values(SERVERS).filter((server) => { return ( - server.extensions.includes(ext) || - server.extensions.includes(fileName) + server.extensions.includes(ext) || server.extensions.includes(fileName) ); }); }; @@ -249,7 +262,9 @@ export const spawnServer = async ( return { process: proc }; }; -export const isServerAvailable = async (server: ServerInfo): Promise => { +export const isServerAvailable = async ( + server: ServerInfo, +): Promise => { const binary = await findBinary(server.command); return binary !== null; }; diff --git a/src/services/mcp/registry.ts b/src/services/mcp/registry.ts index 44905e2..79f58ae 100644 --- a/src/services/mcp/registry.ts +++ b/src/services/mcp/registry.ts @@ -154,7 +154,7 @@ const mapCategory = (category: string): MCPServerCategory => { * Get all servers (curated + external) */ export const getAllServers = async ( - forceRefresh = false + forceRefresh = false, ): Promise => { // Check in-memory cache first if (!forceRefresh && registryCache && isCacheValid(registryCache)) { @@ -202,7 +202,7 @@ export const getCuratedServers = (): MCPRegistryServer[] => { * Search for MCP servers */ export const searchServers = async ( - options: MCPSearchOptions = {} + options: MCPSearchOptions = {}, ): Promise => { const { query = "", @@ -227,7 +227,9 @@ export const searchServers = async ( server.description, server.author, ...server.tags, - ].join(" ").toLowerCase(); + ] + .join(" ") + .toLowerCase(); return searchableText.includes(lowerQuery); }); @@ -243,9 +245,9 @@ export const searchServers = async ( filtered = filtered.filter((server) => tags.some((tag) => server.tags.some((serverTag) => - serverTag.toLowerCase().includes(tag.toLowerCase()) - ) - ) + serverTag.toLowerCase().includes(tag.toLowerCase()), + ), + ), ); } @@ -255,10 +257,14 @@ export const searchServers = async ( } // Sort - const sortFunctions: Record number> = { + const sortFunctions: Record< + string, + (a: MCPRegistryServer, b: MCPRegistryServer) => number + > = { popularity: (a, b) => b.popularity - a.popularity, name: (a, b) => a.name.localeCompare(b.name), - updated: (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(), + updated: (a, b) => + new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(), }; filtered.sort(sortFunctions[sortBy] || sortFunctions.popularity); @@ -279,7 +285,7 @@ export const searchServers = async ( * Get server by ID */ export const getServerById = async ( - id: string + id: string, ): Promise => { const allServers = await getAllServers(); return allServers.find((server) => server.id === id); @@ -289,7 +295,7 @@ export const getServerById = async ( * Get servers by category */ export const getServersByCategory = async ( - category: MCPServerCategory + category: MCPServerCategory, ): Promise => { const allServers = await getAllServers(); return allServers.filter((server) => server.category === category); @@ -300,9 +306,10 @@ export const getServersByCategory = async ( */ export const isServerInstalled = (serverId: string): boolean => { const instances = getServerInstances(); - return Array.from(instances.values()).some((instance) => - instance.config.name === serverId || - instance.config.name.toLowerCase() === serverId.toLowerCase() + return Array.from(instances.values()).some( + (instance) => + instance.config.name === serverId || + instance.config.name.toLowerCase() === serverId.toLowerCase(), ); }; @@ -315,7 +322,7 @@ export const installServer = async ( global?: boolean; connect?: boolean; customArgs?: string[]; - } = {} + } = {}, ): Promise => { const { global = false, connect = true, customArgs } = options; @@ -339,7 +346,7 @@ export const installServer = async ( transport: server.transport, enabled: true, }, - global + global, ); let connected = false; @@ -363,7 +370,10 @@ export const installServer = async ( return { success: false, serverName: server.id, - error: error instanceof Error ? error.message : MCP_REGISTRY_ERRORS.INSTALL_FAILED, + error: + error instanceof Error + ? error.message + : MCP_REGISTRY_ERRORS.INSTALL_FAILED, connected: false, }; } @@ -378,7 +388,7 @@ export const installServerById = async ( global?: boolean; connect?: boolean; customArgs?: string[]; - } = {} + } = {}, ): Promise => { const server = await getServerById(serverId); @@ -398,12 +408,10 @@ export const installServerById = async ( * Get popular servers */ export const getPopularServers = async ( - limit = 10 + limit = 10, ): Promise => { const allServers = await getAllServers(); - return allServers - .sort((a, b) => b.popularity - a.popularity) - .slice(0, limit); + return allServers.sort((a, b) => b.popularity - a.popularity).slice(0, limit); }; /** diff --git a/src/services/model-routing.ts b/src/services/model-routing.ts index 085e054..03ab4c6 100644 --- a/src/services/model-routing.ts +++ b/src/services/model-routing.ts @@ -39,11 +39,7 @@ export const MODEL_TIER_MAPPING: Record = { "gpt-4.1", ], // Thorough tier: Best quality, higher cost (3x multiplier) - thorough: [ - "claude-opus-4.5", - "gpt-5.2-codex", - "gpt-5.1-codex-max", - ], + thorough: ["claude-opus-4.5", "gpt-5.2-codex", "gpt-5.1-codex-max"], }; /** diff --git a/src/services/multi-agent/agent-manager.ts b/src/services/multi-agent/agent-manager.ts index bee75e7..3a4f4c5 100644 --- a/src/services/multi-agent/agent-manager.ts +++ b/src/services/multi-agent/agent-manager.ts @@ -26,14 +26,18 @@ let agentRegistry: Map = new Map(); /** * Set the agent registry (called during initialization) */ -export const setAgentRegistry = (registry: Map): void => { +export const setAgentRegistry = ( + registry: Map, +): void => { agentRegistry = registry; }; /** * Get agent definition by name */ -export const getAgentDefinition = (name: string): AgentDefinition | undefined => { +export const getAgentDefinition = ( + name: string, +): AgentDefinition | undefined => { return agentRegistry.get(name); }; @@ -50,7 +54,11 @@ export const createAgentInstance = ( const activeCount = multiAgentStore.getActiveInstances().length; if (activeCount >= MULTI_AGENT_LIMITS.maxConcurrentRequests) { - return { error: MULTI_AGENT_ERRORS.MAX_CONCURRENT_EXCEEDED(MULTI_AGENT_LIMITS.maxConcurrentRequests) }; + return { + error: MULTI_AGENT_ERRORS.MAX_CONCURRENT_EXCEEDED( + MULTI_AGENT_LIMITS.maxConcurrentRequests, + ), + }; } const conversation: AgentConversation = { @@ -122,7 +130,19 @@ export const completeAgent = ( ...(result.success ? { result, timestamp: Date.now() } : { error: result.error ?? "Unknown error", timestamp: Date.now() }), - } as { type: "agent_completed"; agentId: string; result: AgentExecutionResult; timestamp: number } | { type: "agent_error"; agentId: string; error: string; timestamp: number }); + } as + | { + type: "agent_completed"; + agentId: string; + result: AgentExecutionResult; + timestamp: number; + } + | { + type: "agent_error"; + agentId: string; + error: string; + timestamp: number; + }); }; /** diff --git a/src/services/multi-agent/conflict-handler.ts b/src/services/multi-agent/conflict-handler.ts index 5feabff..e62ca21 100644 --- a/src/services/multi-agent/conflict-handler.ts +++ b/src/services/multi-agent/conflict-handler.ts @@ -11,10 +11,7 @@ import type { AgentInstance, } from "@/types/multi-agent"; import { multiAgentStore } from "@stores/core/multi-agent-store"; -import { - MULTI_AGENT_ERRORS, - FILE_LOCK, -} from "@/constants/multi-agent"; +import { MULTI_AGENT_ERRORS, FILE_LOCK } from "@/constants/multi-agent"; import { pauseAgentForConflict, resumeAgent, @@ -28,10 +25,13 @@ const fileLocks: Map = new Map(); // filePath -> agentId /** * Pending lock requests */ -const pendingLocks: Map void; -}>> = new Map(); +const pendingLocks: Map< + string, + Array<{ + agentId: string; + resolve: (acquired: boolean) => void; + }> +> = new Map(); /** * Acquire a file lock for an agent @@ -256,7 +256,10 @@ const waitForAgentCompletion = (agentId: string): Promise => { return new Promise((resolve) => { const checkInterval = setInterval(() => { const agent = multiAgentStore.getState().instances.get(agentId); - if (!agent || ["completed", "error", "cancelled"].includes(agent.status)) { + if ( + !agent || + ["completed", "error", "cancelled"].includes(agent.status) + ) { clearInterval(checkInterval); resolve(); } diff --git a/src/services/multi-agent/executor.ts b/src/services/multi-agent/executor.ts index 66c96b3..fa716a2 100644 --- a/src/services/multi-agent/executor.ts +++ b/src/services/multi-agent/executor.ts @@ -100,7 +100,9 @@ const validateRequest = ( } if (request.agents.length > MULTI_AGENT_LIMITS.maxAgentsPerRequest) { - return MULTI_AGENT_ERRORS.MAX_AGENTS_EXCEEDED(MULTI_AGENT_LIMITS.maxAgentsPerRequest); + return MULTI_AGENT_ERRORS.MAX_AGENTS_EXCEEDED( + MULTI_AGENT_LIMITS.maxAgentsPerRequest, + ); } // Validate each agent config @@ -146,7 +148,8 @@ const executeParallel = async ( results: AgentInstance[], conflicts: FileConflict[], ): Promise => { - const maxConcurrent = request.maxConcurrent ?? MULTI_AGENT_DEFAULTS.maxConcurrent; + const maxConcurrent = + request.maxConcurrent ?? MULTI_AGENT_DEFAULTS.maxConcurrent; const chunks = chunkArray(request.agents, maxConcurrent); for (const chunk of chunks) { @@ -182,7 +185,8 @@ const executeAdaptive = async ( results: AgentInstance[], conflicts: FileConflict[], ): Promise => { - const maxConcurrent = request.maxConcurrent ?? MULTI_AGENT_DEFAULTS.maxConcurrent; + const maxConcurrent = + request.maxConcurrent ?? MULTI_AGENT_DEFAULTS.maxConcurrent; let conflictCount = 0; let useSequential = false; @@ -260,12 +264,7 @@ const executeSingleAgent = async ( const instance = instanceOrError; // Create tool context - createToolContext( - instance.id, - process.cwd(), - config.contextFiles, - [], - ); + createToolContext(instance.id, process.cwd(), config.contextFiles, []); // Start agent startAgent(instance.id); @@ -353,7 +352,8 @@ const aggregateResults = ( cancelled, conflicts, totalDuration: Date.now() - startTime, - aggregatedOutput: outputs.length > 0 ? outputs.join("\n\n---\n\n") : undefined, + aggregatedOutput: + outputs.length > 0 ? outputs.join("\n\n---\n\n") : undefined, }; }; diff --git a/src/services/multi-agent/tool-context.ts b/src/services/multi-agent/tool-context.ts index 73a7a00..a6740e3 100644 --- a/src/services/multi-agent/tool-context.ts +++ b/src/services/multi-agent/tool-context.ts @@ -51,10 +51,7 @@ export const getToolContext = (agentId: string): AgentToolContext | null => { /** * Check if a path is allowed for an agent */ -export const isPathAllowed = ( - agentId: string, - filePath: string, -): boolean => { +export const isPathAllowed = (agentId: string, filePath: string): boolean => { const context = activeContexts.get(agentId); if (!context) return false; @@ -101,13 +98,21 @@ export const requestWriteAccess = async ( // Detect conflicts with other agents const conflict = detectConflict(agentId, filePath); if (conflict) { - return { granted: false, conflict: true, reason: "File locked by another agent" }; + return { + granted: false, + conflict: true, + reason: "File locked by another agent", + }; } // Acquire file lock const acquired = await acquireFileLock(agentId, filePath); if (!acquired) { - return { granted: false, conflict: true, reason: "Could not acquire file lock" }; + return { + granted: false, + conflict: true, + reason: "Could not acquire file lock", + }; } // Track locked file @@ -118,10 +123,7 @@ export const requestWriteAccess = async ( /** * Record a file modification */ -export const recordModification = ( - agentId: string, - filePath: string, -): void => { +export const recordModification = (agentId: string, filePath: string): void => { const context = activeContexts.get(agentId); if (!context) return; @@ -132,10 +134,7 @@ export const recordModification = ( /** * Release write access to a file */ -export const releaseWriteAccess = ( - agentId: string, - filePath: string, -): void => { +export const releaseWriteAccess = (agentId: string, filePath: string): void => { const context = activeContexts.get(agentId); if (!context) return; diff --git a/src/services/parallel/conflict-detector.ts b/src/services/parallel/conflict-detector.ts index 7ede153..3891d70 100644 --- a/src/services/parallel/conflict-detector.ts +++ b/src/services/parallel/conflict-detector.ts @@ -5,7 +5,11 @@ * and task types. Read-only tasks don't conflict with each other. */ -import { CONFLICT_CONFIG, READ_ONLY_TASK_TYPES, MODIFYING_TASK_TYPES } from "@constants/parallel"; +import { + CONFLICT_CONFIG, + READ_ONLY_TASK_TYPES, + MODIFYING_TASK_TYPES, +} from "@constants/parallel"; import type { ParallelTask, ConflictCheckResult, @@ -143,7 +147,9 @@ export const checkConflicts = (task: ParallelTask): ConflictCheckResult => { const hasConflict = conflictingTaskIds.length > 0; // Suggest resolution - const resolution = hasConflict ? suggestResolution(task, conflictingTaskIds) : undefined; + const resolution = hasConflict + ? suggestResolution(task, conflictingTaskIds) + : undefined; return { hasConflict, diff --git a/src/services/parallel/index.ts b/src/services/parallel/index.ts index 43a4731..1dad010 100644 --- a/src/services/parallel/index.ts +++ b/src/services/parallel/index.ts @@ -5,7 +5,11 @@ * Coordinates conflict detection, resource management, and result aggregation. */ -import { PARALLEL_DEFAULTS, PARALLEL_ERRORS, TASK_TIMEOUTS } from "@constants/parallel"; +import { + PARALLEL_DEFAULTS, + PARALLEL_ERRORS, + TASK_TIMEOUTS, +} from "@constants/parallel"; import { registerActiveTask, unregisterActiveTask, @@ -50,7 +54,10 @@ const executeTask = async ( options: ParallelExecutorOptions, ): Promise> => { const startedAt = Date.now(); - const timeout = task.timeout ?? TASK_TIMEOUTS[task.type] ?? PARALLEL_DEFAULTS.defaultTimeout; + const timeout = + task.timeout ?? + TASK_TIMEOUTS[task.type] ?? + PARALLEL_DEFAULTS.defaultTimeout; try { // Notify task start @@ -87,7 +94,10 @@ const executeTask = async ( completedAt, }; - options.onTaskError?.(task, error instanceof Error ? error : new Error(String(error))); + options.onTaskError?.( + task, + error instanceof Error ? error : new Error(String(error)), + ); return executionResult; } }; @@ -134,7 +144,10 @@ export const executeParallel = async ( // Track results const results: ParallelExecutionResult[] = []; - const pendingTasks = new Map>>(); + const pendingTasks = new Map< + string, + Promise> + >(); // Check if executor was aborted const checkAbort = (): boolean => { @@ -209,9 +222,15 @@ const executeWithConflictHandling = async ( const conflicts = checkConflicts(task); if (conflicts.hasConflict) { - const resolution = options.onConflict?.(task, conflicts) ?? conflicts.resolution ?? "wait"; + const resolution = + options.onConflict?.(task, conflicts) ?? conflicts.resolution ?? "wait"; - const handled = await handleConflict(task, conflicts, resolution, options); + const handled = await handleConflict( + task, + conflicts, + resolution, + options, + ); if (!handled.continue) { releaseResources(task, 0, false); return handled.result; @@ -244,7 +263,10 @@ const handleConflict = async ( resolution: ConflictResolution, _options: ParallelExecutorOptions, ): Promise<{ continue: boolean; result: ParallelExecutionResult }> => { - const createFailResult = (status: "conflict" | "cancelled", error: string) => ({ + const createFailResult = ( + status: "conflict" | "cancelled", + error: string, + ) => ({ continue: false, result: { taskId: task.id, @@ -258,14 +280,25 @@ const handleConflict = async ( const resolutionHandlers: Record< ConflictResolution, - () => Promise<{ continue: boolean; result: ParallelExecutionResult }> + () => Promise<{ + continue: boolean; + result: ParallelExecutionResult; + }> > = { wait: async () => { - const resolved = await waitForConflictResolution(conflicts.conflictingTaskIds); + const resolved = await waitForConflictResolution( + conflicts.conflictingTaskIds, + ); if (resolved) { - return { continue: true, result: {} as ParallelExecutionResult }; + return { + continue: true, + result: {} as ParallelExecutionResult, + }; } - return createFailResult("conflict", PARALLEL_ERRORS.CONFLICT(task.id, conflicts.conflictingPaths)); + return createFailResult( + "conflict", + PARALLEL_ERRORS.CONFLICT(task.id, conflicts.conflictingPaths), + ); }, cancel: async () => { @@ -282,7 +315,10 @@ const handleConflict = async ( }, abort: async () => { - return createFailResult("conflict", PARALLEL_ERRORS.CONFLICT(task.id, conflicts.conflictingPaths)); + return createFailResult( + "conflict", + PARALLEL_ERRORS.CONFLICT(task.id, conflicts.conflictingPaths), + ); }, }; diff --git a/src/services/parallel/resource-manager.ts b/src/services/parallel/resource-manager.ts index 8e3c282..cd37e56 100644 --- a/src/services/parallel/resource-manager.ts +++ b/src/services/parallel/resource-manager.ts @@ -160,7 +160,11 @@ export const acquireResources = async (task: ParallelTask): Promise => { /** * Release resources after task completion */ -export const releaseResources = (task: ParallelTask, duration: number, success: boolean): void => { +export const releaseResources = ( + task: ParallelTask, + duration: number, + success: boolean, +): void => { if (!globalSemaphore) return; // Release global permit diff --git a/src/services/parallel/result-aggregator.ts b/src/services/parallel/result-aggregator.ts index 445b0a3..355b7d1 100644 --- a/src/services/parallel/result-aggregator.ts +++ b/src/services/parallel/result-aggregator.ts @@ -24,7 +24,9 @@ export const collectResults = ( results: ParallelExecutionResult[], ): AggregatedResults => { const successful = results.filter((r) => r.status === "completed").length; - const failed = results.filter((r) => r.status === "error" || r.status === "timeout").length; + const failed = results.filter( + (r) => r.status === "error" || r.status === "timeout", + ).length; const cancelled = results.filter((r) => r.status === "cancelled").length; const totalDuration = results.reduce((sum, r) => sum + r.duration, 0); @@ -97,7 +99,9 @@ export const deduplicateFileResults = ( /** * Deduplicate search results (by path and content) */ -export const deduplicateSearchResults = ( +export const deduplicateSearchResults = < + T extends { path: string; match?: string }, +>( results: T[], ): DeduplicationResult => { return deduplicateResults(results, (item) => ({ @@ -137,9 +141,13 @@ export const mergeByPriority = ( const sorted = [...results].sort((a, b) => a.completedAt - b.completedAt); // Return the most recent successful result - const successful = sorted.filter((r) => r.status === "completed" && r.result !== undefined); + const successful = sorted.filter( + (r) => r.status === "completed" && r.result !== undefined, + ); - return successful.length > 0 ? successful[successful.length - 1].result : undefined; + return successful.length > 0 + ? successful[successful.length - 1].result + : undefined; }; // ============================================================================ @@ -274,7 +282,5 @@ export const aggregateAll = ( export const aggregateAny = ( results: ParallelExecutionResult[], ): boolean => { - return results.some( - (r) => r.status === "completed" && r.result === true, - ); + return results.some((r) => r.status === "completed" && r.result === true); }; diff --git a/src/services/planner.ts b/src/services/planner.ts index 71756de..4beeefe 100644 --- a/src/services/planner.ts +++ b/src/services/planner.ts @@ -3,7 +3,7 @@ */ import chalk from "chalk"; -import { chat as providerChat } from "@providers/index"; +import { chat as providerChat } from "@providers/core/chat"; import type { Message, ProviderName } from "@/types/providers"; import type { Plan, @@ -11,7 +11,7 @@ import type { PlanStepStatus, PlanStepType, } from "@/types/planner"; -import { PLAN_SYSTEM_PROMPT } from "@prompts/index"; +import { PLAN_SYSTEM_PROMPT } from "@prompts/system/planner"; /** * Status icon mapping diff --git a/src/services/plugin-loader.ts b/src/services/plugin-loader.ts index 0c71e46..e6f244a 100644 --- a/src/services/plugin-loader.ts +++ b/src/services/plugin-loader.ts @@ -28,7 +28,7 @@ import { DIRS, LOCAL_CONFIG_DIR } from "@constants/paths"; * Discover plugins in a directory */ const discoverPluginsInDir = async ( - baseDir: string + baseDir: string, ): Promise => { const pluginsPath = join(baseDir, PLUGINS_DIR); const results: PluginDiscoveryResult[] = []; @@ -66,7 +66,7 @@ const discoverPluginsInDir = async ( * Discover all plugins from global and local directories */ export const discoverPlugins = async ( - workingDir: string + workingDir: string, ): Promise => { const [globalPlugins, localPlugins] = await Promise.all([ discoverPluginsInDir(DIRS.config), @@ -91,7 +91,7 @@ export const discoverPlugins = async ( * Parse plugin manifest */ export const parseManifest = async ( - manifestPath: string + manifestPath: string, ): Promise => { try { const content = await readFile(manifestPath, "utf-8"); @@ -112,7 +112,7 @@ export const parseManifest = async ( * Parse command file with frontmatter */ export const parseCommandFile = async ( - filePath: string + filePath: string, ): Promise => { try { const content = await readFile(filePath, "utf-8"); @@ -157,7 +157,10 @@ export const parseCommandFile = async ( } // Rest is the prompt - const prompt = lines.slice(endIndex + 1).join("\n").trim(); + const prompt = lines + .slice(endIndex + 1) + .join("\n") + .trim(); const name = frontmatter.name || basename(filePath, COMMAND_FILE_EXTENSION); const description = frontmatter.description || `Custom command: ${name}`; @@ -176,7 +179,7 @@ export const parseCommandFile = async ( * Load tool module dynamically */ export const loadToolModule = async ( - filePath: string + filePath: string, ): Promise => { try { // For Bun, we can use dynamic import @@ -184,7 +187,12 @@ export const loadToolModule = async ( const toolDef = module.default || module; // Validate tool definition - if (!toolDef.name || !toolDef.description || !toolDef.parameters || !toolDef.execute) { + if ( + !toolDef.name || + !toolDef.description || + !toolDef.parameters || + !toolDef.execute + ) { return null; } @@ -199,7 +207,7 @@ export const loadToolModule = async ( */ export const loadPluginHooks = async ( pluginPath: string, - manifest: PluginManifest + manifest: PluginManifest, ): Promise => { const hooks: HookDefinition[] = []; @@ -253,7 +261,7 @@ export const loadPluginHooks = async ( if (baseName.toLowerCase().includes(eventType.toLowerCase())) { // Check if already added from manifest const alreadyAdded = hooks.some( - (h) => h.script === scriptPath && h.event === eventType + (h) => h.script === scriptPath && h.event === eventType, ); if (!alreadyAdded) { @@ -279,7 +287,7 @@ export const loadPluginHooks = async ( */ export const loadPluginCommands = async ( pluginPath: string, - manifest: PluginManifest + manifest: PluginManifest, ): Promise> => { const commands = new Map(); @@ -329,7 +337,7 @@ export const loadPluginCommands = async ( */ export const loadPluginTools = async ( pluginPath: string, - manifest: PluginManifest + manifest: PluginManifest, ): Promise> => { const tools = new Map(); diff --git a/src/services/plugin-service.ts b/src/services/plugin-service.ts index 905ed0e..98e6aee 100644 --- a/src/services/plugin-service.ts +++ b/src/services/plugin-service.ts @@ -19,10 +19,7 @@ import { loadPluginCommands, loadPluginHooks, } from "@services/plugin-loader"; -import { - PLUGIN_TOOL_SEPARATOR, - PLUGIN_ERRORS, -} from "@constants/plugin"; +import { PLUGIN_TOOL_SEPARATOR, PLUGIN_ERRORS } from "@constants/plugin"; /** * Plugin registry singleton @@ -40,7 +37,7 @@ const registry: PluginRegistry = { const loadPlugin = async ( _name: string, path: string, - manifestPath: string + manifestPath: string, ): Promise => { const manifest = await parseManifest(manifestPath); @@ -86,7 +83,7 @@ export const initializePlugins = async (workingDir: string): Promise => { const result = await loadPlugin( discovered.name, discovered.path, - discovered.manifestPath + discovered.manifestPath, ); if (result.success && result.plugin) { @@ -173,7 +170,7 @@ export const getPluginToolsForApi = (): { * Get a plugin command by name */ export const getPluginCommand = ( - name: string + name: string, ): PluginCommandDefinition | undefined => { return registry.commands.get(name); }; diff --git a/src/services/pr-review/diff-parser.ts b/src/services/pr-review/diff-parser.ts index 1f77d0f..509b19f 100644 --- a/src/services/pr-review/diff-parser.ts +++ b/src/services/pr-review/diff-parser.ts @@ -185,7 +185,10 @@ export const parseDiff = (diffContent: string): ParsedDiff => { /** * Create empty file diff structure */ -const createEmptyFileDiff = (oldPath: string, newPath: string): ParsedFileDiff => ({ +const createEmptyFileDiff = ( + oldPath: string, + newPath: string, +): ParsedFileDiff => ({ oldPath: cleanPath(oldPath), newPath: cleanPath(newPath), hunks: [], diff --git a/src/services/pr-review/index.ts b/src/services/pr-review/index.ts index 588faa7..35a221f 100644 --- a/src/services/pr-review/index.ts +++ b/src/services/pr-review/index.ts @@ -9,8 +9,15 @@ import { PR_REVIEW_ERRORS, PR_REVIEW_MESSAGES, } from "@constants/pr-review"; -import { parseDiff, filterFiles, getFilePath } from "@services/pr-review/diff-parser"; -import { generateReport, formatReportMarkdown } from "@services/pr-review/report-generator"; +import { + parseDiff, + filterFiles, + getFilePath, +} from "@services/pr-review/diff-parser"; +import { + generateReport, + formatReportMarkdown, +} from "@services/pr-review/report-generator"; import * as securityReviewer from "@services/pr-review/reviewers/security"; import * as performanceReviewer from "@services/pr-review/reviewers/performance"; import * as logicReviewer from "@services/pr-review/reviewers/logic"; @@ -120,7 +127,8 @@ const runReviewers = async ( onProgress?.(PR_REVIEW_MESSAGES.REVIEWING(reviewerConfig.name)); const startTime = Date.now(); - const reviewerModule = reviewers[reviewerConfig.name as keyof typeof reviewers]; + const reviewerModule = + reviewers[reviewerConfig.name as keyof typeof reviewers]; if (!reviewerModule) { return { @@ -180,7 +188,12 @@ export const quickReview = async ( { config: { reviewers: [ - { name: "security", type: "security", enabled: true, minConfidence: 90 }, + { + name: "security", + type: "security", + enabled: true, + minConfidence: 90, + }, { name: "logic", type: "logic", enabled: true, minConfidence: 90 }, ], }, diff --git a/src/services/pr-review/report-generator.ts b/src/services/pr-review/report-generator.ts index 54a5c60..ed866b9 100644 --- a/src/services/pr-review/report-generator.ts +++ b/src/services/pr-review/report-generator.ts @@ -235,7 +235,8 @@ const calculateRecommendation = ( if ( bySeverity.critical === 0 && - bySeverity.warning <= RECOMMENDATION_THRESHOLDS.approve_with_suggestions.maxWarning + bySeverity.warning <= + RECOMMENDATION_THRESHOLDS.approve_with_suggestions.maxWarning ) { return "approve_with_suggestions"; } @@ -264,7 +265,9 @@ const generateSummary = ( // Count by severity const critical = findings.filter((f) => f.severity === "critical").length; const warnings = findings.filter((f) => f.severity === "warning").length; - const suggestions = findings.filter((f) => f.severity === "suggestion").length; + const suggestions = findings.filter( + (f) => f.severity === "suggestion", + ).length; if (critical > 0) { parts.push(`${critical} critical issue(s) must be addressed`); @@ -318,7 +321,12 @@ export const formatReportMarkdown = (report: PRReviewReport): string => { // Findings by severity lines.push("| Severity | Count |"); lines.push("|----------|-------|"); - for (const severity of ["critical", "warning", "suggestion", "nitpick"] as const) { + for (const severity of [ + "critical", + "warning", + "suggestion", + "nitpick", + ] as const) { const count = report.findingsBySeverity[severity]; if (count > 0) { lines.push( diff --git a/src/services/pr-review/reviewers/logic.ts b/src/services/pr-review/reviewers/logic.ts index e66bfd5..3d227ed 100644 --- a/src/services/pr-review/reviewers/logic.ts +++ b/src/services/pr-review/reviewers/logic.ts @@ -4,7 +4,10 @@ * Analyzes code for logical errors and edge cases. */ -import { MIN_CONFIDENCE_THRESHOLD, REVIEWER_PROMPTS } from "@constants/pr-review"; +import { + MIN_CONFIDENCE_THRESHOLD, + REVIEWER_PROMPTS, +} from "@constants/pr-review"; import type { PRReviewFinding, ParsedFileDiff, @@ -17,8 +20,8 @@ import type { const LOGIC_PATTERNS = { MISSING_NULL_CHECK: { patterns: [ - /\w+\.\w+\.\w+/, // Deep property access without optional chaining - /(\w+)\[['"][^'"]+['"]\]\.\w+/, // Object property followed by method + /\w+\.\w+\.\w+/, // Deep property access without optional chaining + /(\w+)\[['"][^'"]+['"]\]\.\w+/, // Object property followed by method ], message: "Potential null/undefined reference", suggestion: "Use optional chaining (?.) or add null checks", @@ -27,7 +30,7 @@ const LOGIC_PATTERNS = { OPTIONAL_CHAIN_MISSING: { patterns: [ - /if\s*\([^)]*\)\s*\{[^}]*\w+\./, // Variable used after if check without ?. + /if\s*\([^)]*\)\s*\{[^}]*\w+\./, // Variable used after if check without ?. ], message: "Consider using optional chaining", suggestion: "Replace conditional access with ?. operator", @@ -35,10 +38,7 @@ const LOGIC_PATTERNS = { }, EMPTY_CATCH: { - patterns: [ - /catch\s*\([^)]*\)\s*\{\s*\}/, - /catch\s*\{\s*\}/, - ], + patterns: [/catch\s*\([^)]*\)\s*\{\s*\}/, /catch\s*\{\s*\}/], message: "Empty catch block - errors silently ignored", suggestion: "Log the error or handle it appropriately", confidence: 90, @@ -54,40 +54,28 @@ const LOGIC_PATTERNS = { }, FLOATING_PROMISE: { - patterns: [ - /^\s*\w+\s*\.\s*then\s*\(/m, - /^\s*\w+\([^)]*\)\.then\s*\(/m, - ], + patterns: [/^\s*\w+\s*\.\s*then\s*\(/m, /^\s*\w+\([^)]*\)\.then\s*\(/m], message: "Floating promise - missing await or error handling", suggestion: "Use await or add .catch() for error handling", confidence: 80, }, ARRAY_INDEX_ACCESS: { - patterns: [ - /\[\d+\]/, - /\[0\]/, - /\[-1\]/, - ], + patterns: [/\[\d+\]/, /\[0\]/, /\[-1\]/], message: "Direct array index access without bounds check", suggestion: "Consider using .at() or add bounds checking", confidence: 60, }, EQUALITY_TYPE_COERCION: { - patterns: [ - /[^=!]==[^=]/, - /[^!]!=[^=]/, - ], + patterns: [/[^=!]==[^=]/, /[^!]!=[^=]/], message: "Using == instead of === (type coercion)", suggestion: "Use strict equality (===) to avoid type coercion bugs", confidence: 85, }, ASYNC_IN_FOREACH: { - patterns: [ - /\.forEach\s*\(\s*async/, - ], + patterns: [/\.forEach\s*\(\s*async/], message: "Async callback in forEach - won't await properly", suggestion: "Use for...of loop or Promise.all with .map()", confidence: 90, @@ -104,19 +92,14 @@ const LOGIC_PATTERNS = { }, RACE_CONDITION: { - patterns: [ - /let\s+\w+\s*=[^;]+;\s*await\s+[^;]+;\s*\w+\s*=/, - ], + patterns: [/let\s+\w+\s*=[^;]+;\s*await\s+[^;]+;\s*\w+\s*=/], message: "Potential race condition with shared state", suggestion: "Use atomic operations or proper synchronization", confidence: 70, }, INFINITE_LOOP_RISK: { - patterns: [ - /while\s*\(\s*true\s*\)/, - /for\s*\(\s*;\s*;\s*\)/, - ], + patterns: [/while\s*\(\s*true\s*\)/, /for\s*\(\s*;\s*;\s*\)/], message: "Infinite loop without clear exit condition", suggestion: "Ensure there's a clear break condition", confidence: 75, @@ -204,7 +187,9 @@ const getAllAddedLines = ( /** * Deduplicate findings with same message on adjacent lines */ -const deduplicateFindings = (findings: PRReviewFinding[]): PRReviewFinding[] => { +const deduplicateFindings = ( + findings: PRReviewFinding[], +): PRReviewFinding[] => { const seen = new Map(); for (const finding of findings) { diff --git a/src/services/pr-review/reviewers/performance.ts b/src/services/pr-review/reviewers/performance.ts index 8fb028c..1da8ca6 100644 --- a/src/services/pr-review/reviewers/performance.ts +++ b/src/services/pr-review/reviewers/performance.ts @@ -4,7 +4,10 @@ * Analyzes code for performance issues. */ -import { MIN_CONFIDENCE_THRESHOLD, REVIEWER_PROMPTS } from "@constants/pr-review"; +import { + MIN_CONFIDENCE_THRESHOLD, + REVIEWER_PROMPTS, +} from "@constants/pr-review"; import type { PRReviewFinding, ParsedFileDiff, @@ -23,7 +26,8 @@ const PERFORMANCE_PATTERNS = { /while\s*\([^)]+\)\s*\{[^}]*while\s*\([^)]+\)/, ], message: "Nested loops detected - potential O(n²) complexity", - suggestion: "Consider using a Map/Set for O(1) lookups or restructuring the algorithm", + suggestion: + "Consider using a Map/Set for O(1) lookups or restructuring the algorithm", confidence: 75, }, @@ -47,7 +51,8 @@ const PERFORMANCE_PATTERNS = { /style\s*=\s*\{\s*\{/, ], message: "Potential unnecessary re-render in React component", - suggestion: "Use useMemo/useCallback for objects/arrays, extract styles outside component", + suggestion: + "Use useMemo/useCallback for objects/arrays, extract styles outside component", confidence: 70, }, @@ -90,7 +95,8 @@ const PERFORMANCE_PATTERNS = { /require\s*\(\s*['"]lodash['"]\s*\)/, ], message: "Large library import may increase bundle size", - suggestion: "Use specific imports (lodash/get) or smaller alternatives (date-fns)", + suggestion: + "Use specific imports (lodash/get) or smaller alternatives (date-fns)", confidence: 80, }, @@ -102,7 +108,8 @@ const PERFORMANCE_PATTERNS = { /existsSync\s*\(/, ], message: "Synchronous file operation may block event loop", - suggestion: "Use async versions (readFile, writeFile) for better performance", + suggestion: + "Use async versions (readFile, writeFile) for better performance", confidence: 80, }, } as const; @@ -120,7 +127,7 @@ export const reviewFile = ( const addedLines = getAllAddedLines(diff); // Combine lines for multi-line pattern matching - const combinedContent = addedLines.map(l => l.content).join("\n"); + const combinedContent = addedLines.map((l) => l.content).join("\n"); // Check each pattern for (const [patternName, config] of Object.entries(PERFORMANCE_PATTERNS)) { diff --git a/src/services/pr-review/reviewers/security.ts b/src/services/pr-review/reviewers/security.ts index 476e081..df7f853 100644 --- a/src/services/pr-review/reviewers/security.ts +++ b/src/services/pr-review/reviewers/security.ts @@ -4,7 +4,10 @@ * Analyzes code for security vulnerabilities. */ -import { MIN_CONFIDENCE_THRESHOLD, REVIEWER_PROMPTS } from "@constants/pr-review"; +import { + MIN_CONFIDENCE_THRESHOLD, + REVIEWER_PROMPTS, +} from "@constants/pr-review"; import type { PRReviewFinding, ParsedFileDiff, @@ -51,7 +54,8 @@ const SECURITY_PATTERNS = { /\$\(.* \+ /, ], message: "Potential command injection vulnerability", - suggestion: "Avoid string concatenation in shell commands, use argument arrays", + suggestion: + "Avoid string concatenation in shell commands, use argument arrays", confidence: 90, }, @@ -82,11 +86,10 @@ const SECURITY_PATTERNS = { }, INSECURE_RANDOM: { - patterns: [ - /Math\.random\s*\(\)/, - ], + patterns: [/Math\.random\s*\(\)/], message: "Insecure random number generation", - suggestion: "Use crypto.randomBytes or crypto.getRandomValues for security-sensitive operations", + suggestion: + "Use crypto.randomBytes or crypto.getRandomValues for security-sensitive operations", confidence: 70, }, diff --git a/src/services/pr-review/reviewers/style.ts b/src/services/pr-review/reviewers/style.ts index 4f8bbe3..99f3081 100644 --- a/src/services/pr-review/reviewers/style.ts +++ b/src/services/pr-review/reviewers/style.ts @@ -4,7 +4,10 @@ * Analyzes code for style and consistency issues. */ -import { MIN_CONFIDENCE_THRESHOLD, REVIEWER_PROMPTS } from "@constants/pr-review"; +import { + MIN_CONFIDENCE_THRESHOLD, + REVIEWER_PROMPTS, +} from "@constants/pr-review"; import type { PRReviewFinding, ParsedFileDiff, @@ -16,9 +19,7 @@ import type { */ const STYLE_PATTERNS = { CONSOLE_LOG: { - patterns: [ - /console\.(log|debug|info)\s*\(/, - ], + patterns: [/console\.(log|debug|info)\s*\(/], message: "Console statement left in code", suggestion: "Remove console statements before committing or use a logger", confidence: 85, @@ -46,36 +47,28 @@ const STYLE_PATTERNS = { }, LONG_LINE: { - patterns: [ - /.{121,}/, - ], + patterns: [/.{121,}/], message: "Line exceeds 120 characters", suggestion: "Break long lines for better readability", confidence: 75, }, INCONSISTENT_QUOTES: { - patterns: [ - /["'][^"']*["']/, - ], + patterns: [/["'][^"']*["']/], message: "Inconsistent quote style", suggestion: "Use consistent quotes (single or double) throughout the file", confidence: 60, }, VAR_DECLARATION: { - patterns: [ - /\bvar\s+\w+/, - ], + patterns: [/\bvar\s+\w+/], message: "Using 'var' instead of 'let' or 'const'", suggestion: "Prefer 'const' for immutable values, 'let' for mutable", confidence: 85, }, NESTED_TERNARY: { - patterns: [ - /\?[^:]+\?[^:]+:/, - ], + patterns: [/\?[^:]+\?[^:]+:/], message: "Nested ternary operator - hard to read", suggestion: "Use if-else statements or extract to a function", confidence: 80, @@ -92,20 +85,14 @@ const STYLE_PATTERNS = { }, ANY_TYPE: { - patterns: [ - /:\s*any\b/, - //, - /as\s+any\b/, - ], + patterns: [/:\s*any\b/, //, /as\s+any\b/], message: "Using 'any' type reduces type safety", suggestion: "Use specific types or 'unknown' with type guards", confidence: 75, }, SINGLE_LETTER_VAR: { - patterns: [ - /\b(?:const|let|var)\s+[a-z]\s*=/, - ], + patterns: [/\b(?:const|let|var)\s+[a-z]\s*=/], message: "Single-letter variable name", suggestion: "Use descriptive variable names for clarity", confidence: 65, @@ -122,9 +109,7 @@ const STYLE_PATTERNS = { }, DUPLICATE_IMPORT: { - patterns: [ - /import\s+\{[^}]+\}\s+from\s+['"]([^'"]+)['"]/, - ], + patterns: [/import\s+\{[^}]+\}\s+from\s+['"]([^'"]+)['"]/], message: "Check for duplicate or unused imports", suggestion: "Consolidate imports from the same module", confidence: 60, @@ -158,7 +143,9 @@ export const reviewFile = ( // For some patterns, only report once per file if (shouldReportOncePerFile(patternName)) { if (!foundInFile) { - findings.push(createFinding(path, lineNumber, config, patternName)); + findings.push( + createFinding(path, lineNumber, config, patternName), + ); foundInFile = true; } } else { diff --git a/src/services/project-setup-service.ts b/src/services/project-setup-service.ts index 21147b5..3fb9b82 100644 --- a/src/services/project-setup-service.ts +++ b/src/services/project-setup-service.ts @@ -58,9 +58,10 @@ const addToGitignore = async (workingDir: string): Promise => { } // Add .codetyper to gitignore - const newContent = content.endsWith("\n") || content === "" - ? `${content}${GITIGNORE_ENTRY}\n` - : `${content}\n${GITIGNORE_ENTRY}\n`; + const newContent = + content.endsWith("\n") || content === "" + ? `${content}${GITIGNORE_ENTRY}\n` + : `${content}\n${GITIGNORE_ENTRY}\n`; await fs.writeFile(gitignorePath, newContent, "utf-8"); return true; @@ -340,7 +341,9 @@ const createDefaultAgents = async (workingDir: string): Promise => { return created; }; -export const setupProject = async (workingDir: string): Promise => { +export const setupProject = async ( + workingDir: string, +): Promise => { const result: SetupResult = { gitignoreUpdated: false, agentsCreated: [], @@ -365,13 +368,17 @@ export const setupProject = async (workingDir: string): Promise => return result; }; -export const getSetupStatus = async (workingDir: string): Promise<{ +export const getSetupStatus = async ( + workingDir: string, +): Promise<{ hasGit: boolean; hasCodetyperDir: boolean; agentCount: number; }> => { const hasGit = await isGitRepository(workingDir); - const hasCodetyperDir = await fileExists(path.join(workingDir, CODETYPER_DIR)); + const hasCodetyperDir = await fileExists( + path.join(workingDir, CODETYPER_DIR), + ); let agentCount = 0; if (hasCodetyperDir) { diff --git a/src/services/provider-quality/persistence.ts b/src/services/provider-quality/persistence.ts index 4c38d76..80b2854 100644 --- a/src/services/provider-quality/persistence.ts +++ b/src/services/provider-quality/persistence.ts @@ -6,7 +6,11 @@ import { join } from "path"; import { homedir } from "os"; -import type { ProviderQualityData, TaskType, QualityScore } from "@/types/provider-quality"; +import type { + ProviderQualityData, + TaskType, + QualityScore, +} from "@/types/provider-quality"; import { QUALITY_THRESHOLDS } from "@constants/provider-quality"; const QUALITY_DATA_DIR = join(homedir(), ".config", "codetyper"); diff --git a/src/services/provider-quality/router.ts b/src/services/provider-quality/router.ts index bc205b8..0e7b53b 100644 --- a/src/services/provider-quality/router.ts +++ b/src/services/provider-quality/router.ts @@ -18,7 +18,8 @@ export interface RoutingContext { export const determineRoute = async ( context: RoutingContext, ): Promise => { - const { taskType, ollamaAvailable, copilotAvailable, cascadeEnabled } = context; + const { taskType, ollamaAvailable, copilotAvailable, cascadeEnabled } = + context; if (!ollamaAvailable && !copilotAvailable) { throw new Error("No providers available"); diff --git a/src/services/provider-quality/score-manager.ts b/src/services/provider-quality/score-manager.ts index 69a600f..7b2eeb8 100644 --- a/src/services/provider-quality/score-manager.ts +++ b/src/services/provider-quality/score-manager.ts @@ -11,7 +11,12 @@ import { calculateOverallScore, } from "./persistence"; -export type Outcome = "approved" | "corrected" | "rejected" | "minor_issue" | "major_issue"; +export type Outcome = + | "approved" + | "corrected" + | "rejected" + | "minor_issue" + | "major_issue"; export interface ScoreUpdate { providerId: string; @@ -19,7 +24,9 @@ export interface ScoreUpdate { outcome: Outcome; } -export const updateQualityScore = async (update: ScoreUpdate): Promise => { +export const updateQualityScore = async ( + update: ScoreUpdate, +): Promise => { const { providerId, taskType, outcome } = update; const data = await getProviderQuality(providerId); const score = data.scores[taskType]; diff --git a/src/services/provider-quality/task-detector.ts b/src/services/provider-quality/task-detector.ts index 8e657b7..327a468 100644 --- a/src/services/provider-quality/task-detector.ts +++ b/src/services/provider-quality/task-detector.ts @@ -44,7 +44,9 @@ export const getTaskTypeConfidence = ( return 0.3; } - const matchCount = patterns.filter((p) => p.test(prompt.toLowerCase())).length; + const matchCount = patterns.filter((p) => + p.test(prompt.toLowerCase()), + ).length; const confidence = Math.min(0.5 + matchCount * 0.15, 1.0); return confidence; diff --git a/src/services/reasoning-agent.ts b/src/services/reasoning-agent.ts index b3a7ff5..4d1ab25 100644 --- a/src/services/reasoning-agent.ts +++ b/src/services/reasoning-agent.ts @@ -21,7 +21,7 @@ import type { ToolCallMessage, ToolResultMessage, } from "@/types/agent"; -import { chat as providerChat } from "@providers/index"; +import { chat as providerChat } from "@providers/core/chat"; import { getTool, getToolsForApi, refreshMCPTools } from "@tools/index"; import type { ToolContext, ToolCall, ToolResult } from "@/types/tools"; import { initializePermissions } from "@services/core/permissions"; @@ -37,18 +37,19 @@ import type { import { createInitialState, + evaluateResponseQuality, + decideRetry, + checkTermination, createMemoryStore, addMemory, createMemoryItem, - evaluateResponseQuality, - decideRetry, +} from "@services/reasoning/orchestrator"; +import { compressContext, markMessagesWithAge, getPreservationCandidates, - checkTermination, - estimateTokens, - createTimestamp, -} from "@services/reasoning"; +} from "@services/reasoning/context-compression"; +import { estimateTokens, createTimestamp } from "@services/reasoning/utils"; // ============================================================================= // TYPES diff --git a/src/services/security-service.ts b/src/services/security-service.ts index 75664fb..a0b6a8d 100644 --- a/src/services/security-service.ts +++ b/src/services/security-service.ts @@ -45,7 +45,10 @@ const COMMAND_INJECTION_PATTERNS = [ { pattern: /\x00/, description: "Null byte detected" }, // Environment variable expansion { pattern: /\$\{[^}]+\}/, description: "Environment variable expansion" }, - { pattern: /\$[A-Za-z_][A-Za-z0-9_]*/, description: "Variable reference detected" }, + { + pattern: /\$[A-Za-z_][A-Za-z0-9_]*/, + description: "Variable reference detected", + }, ]; // XSS patterns @@ -57,7 +60,10 @@ const XSS_PATTERNS = [ // JavaScript protocol { pattern: /javascript:/i, description: "JavaScript protocol detected" }, // Data URLs with script content - { pattern: /data:[^,]*;base64/i, description: "Data URL with base64 encoding" }, + { + pattern: /data:[^,]*;base64/i, + description: "Data URL with base64 encoding", + }, // Expression/eval { pattern: /expression\s*\(/i, description: "CSS expression detected" }, // SVG with script @@ -84,9 +90,15 @@ const DANGEROUS_CALLS_PATTERNS = [ { pattern: /exec\s*\(/i, description: "exec() usage detected" }, { pattern: /system\s*\(/i, description: "system() call detected" }, { pattern: /os\.system\s*\(/i, description: "os.system() call detected" }, - { pattern: /subprocess\.call\s*\(/i, description: "subprocess.call() detected" }, + { + pattern: /subprocess\.call\s*\(/i, + description: "subprocess.call() detected", + }, { pattern: /child_process/i, description: "child_process module usage" }, - { pattern: /pickle\.loads?\s*\(/i, description: "Pickle deserialization detected" }, + { + pattern: /pickle\.loads?\s*\(/i, + description: "Pickle deserialization detected", + }, { pattern: /yaml\.unsafe_load\s*\(/i, description: "Unsafe YAML loading" }, { pattern: /unserialize\s*\(/i, description: "PHP unserialize() detected" }, ]; @@ -105,18 +117,31 @@ const TOKEN_PATTERNS = [ // Generic API keys { pattern: /api[_-]?key[=:]["']?[a-zA-Z0-9_-]{20,}["']?/i, type: "API Key" }, // OAuth tokens - { pattern: /bearer\s+[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/i, type: "JWT Token" }, - { pattern: /oauth[_-]?token[=:]["']?[a-zA-Z0-9_-]{20,}["']?/i, type: "OAuth Token" }, + { + pattern: /bearer\s+[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/i, + type: "JWT Token", + }, + { + pattern: /oauth[_-]?token[=:]["']?[a-zA-Z0-9_-]{20,}["']?/i, + type: "OAuth Token", + }, // AWS credentials { pattern: /AKIA[0-9A-Z]{16}/i, type: "AWS Access Key" }, - { pattern: /aws[_-]?secret[_-]?access[_-]?key[=:]["']?[a-zA-Z0-9/+=]{40}["']?/i, type: "AWS Secret Key" }, + { + pattern: + /aws[_-]?secret[_-]?access[_-]?key[=:]["']?[a-zA-Z0-9/+=]{40}["']?/i, + type: "AWS Secret Key", + }, // GitHub tokens { pattern: /gh[pousr]_[A-Za-z0-9_]{36,}/i, type: "GitHub Token" }, // Generic secrets { pattern: /password[=:]["']?[^\s"']{8,}["']?/i, type: "Password" }, { pattern: /secret[=:]["']?[^\s"']{8,}["']?/i, type: "Secret" }, // Private keys - { pattern: /-----BEGIN\s+(?:RSA|DSA|EC|OPENSSH)?\s*PRIVATE\s+KEY-----/i, type: "Private Key" }, + { + pattern: /-----BEGIN\s+(?:RSA|DSA|EC|OPENSSH)?\s*PRIVATE\s+KEY-----/i, + type: "Private Key", + }, ]; const checkPatterns = ( @@ -156,11 +181,21 @@ export const detectXSS = (content: string): SecurityIssue[] => { }; export const detectSQLInjection = (content: string): SecurityIssue[] => { - return checkPatterns(content, SQL_INJECTION_PATTERNS, "sql_injection", "critical"); + return checkPatterns( + content, + SQL_INJECTION_PATTERNS, + "sql_injection", + "critical", + ); }; export const detectDangerousCalls = (code: string): SecurityIssue[] => { - return checkPatterns(code, DANGEROUS_CALLS_PATTERNS, "dangerous_call", "high"); + return checkPatterns( + code, + DANGEROUS_CALLS_PATTERNS, + "dangerous_call", + "high", + ); }; export const detectShellContinuation = (command: string): SecurityIssue[] => { @@ -200,7 +235,9 @@ export const filterSensitiveTokens = (content: string): string => { for (const { pattern } of TOKEN_PATTERNS) { filtered = filtered.replace(new RegExp(pattern, "gi"), (match) => { if (match.length > 12) { - return match.slice(0, 4) + "*".repeat(match.length - 8) + match.slice(-4); + return ( + match.slice(0, 4) + "*".repeat(match.length - 8) + match.slice(-4) + ); } return "*".repeat(match.length); }); @@ -262,7 +299,9 @@ export const explainPermission = ( return { explanation: `Execute shell command: ${command.slice(0, 100)}${command.length > 100 ? "..." : ""}`, - risks: report.issues.map((i) => `${i.risk.toUpperCase()}: ${i.description}`), + risks: report.issues.map( + (i) => `${i.risk.toUpperCase()}: ${i.description}`, + ), recommendation: report.hasCritical ? "DENY - Critical security risk detected" : report.hasHigh @@ -272,7 +311,8 @@ export const explainPermission = ( }, write: (args) => { - const filePath = (args.path as string) ?? (args.file_path as string) ?? ""; + const filePath = + (args.path as string) ?? (args.file_path as string) ?? ""; const content = (args.content as string) ?? ""; const tokens = findSensitiveTokens(content); @@ -292,7 +332,8 @@ export const explainPermission = ( }, edit: (args) => { - const filePath = (args.path as string) ?? (args.file_path as string) ?? ""; + const filePath = + (args.path as string) ?? (args.file_path as string) ?? ""; return { explanation: `Edit file: ${filePath}`, @@ -304,7 +345,8 @@ export const explainPermission = ( }, read: (args) => { - const filePath = (args.path as string) ?? (args.file_path as string) ?? ""; + const filePath = + (args.path as string) ?? (args.file_path as string) ?? ""; return { explanation: `Read file: ${filePath}`, diff --git a/src/services/session-compaction.ts b/src/services/session-compaction.ts index 332d25e..e8e35f0 100644 --- a/src/services/session-compaction.ts +++ b/src/services/session-compaction.ts @@ -111,8 +111,8 @@ export const pruneToolOutputs = ( // Check for tool messages if (msg.role === "tool") { // Extract tool name from tool_call_id if possible - const toolName = (msg as { tool_call_id?: string }).tool_call_id - ?.split("-")[0] ?? ""; + const toolName = + (msg as { tool_call_id?: string }).tool_call_id?.split("-")[0] ?? ""; // Skip protected tools if (protectedTools.has(toolName)) { @@ -189,7 +189,10 @@ export const performSessionCompaction = async ( const pruneResult = pruneToolOutputs(messages); if (pruneResult.prunedCount > 0) { - options?.onPruneComplete?.(pruneResult.prunedCount, pruneResult.tokensSaved); + options?.onPruneComplete?.( + pruneResult.prunedCount, + pruneResult.tokensSaved, + ); // Check if pruning was enough const afterPruneCheck = checkCompactionNeeded(pruneResult.messages, config); @@ -236,7 +239,8 @@ export const createCompactionMiddleware = ( ) => Promise<{ messages: Message[]; summary: string }>; } => { return { - shouldCompact: (messages: Message[]) => isContextOverflow(messages, modelId), + shouldCompact: (messages: Message[]) => + isContextOverflow(messages, modelId), compact: async (messages: Message[]) => { // Notify UI that compaction is starting diff --git a/src/services/session-fork-service.ts b/src/services/session-fork-service.ts index 42f6221..4f3521b 100644 --- a/src/services/session-fork-service.ts +++ b/src/services/session-fork-service.ts @@ -62,9 +62,10 @@ const generateCommitMessage = (messages: SessionMessage[]): string => { const count = messages.length; if (userMessages.length === 0) { - return COMMIT_MESSAGE_TEMPLATES.DEFAULT - .replace("{summary}", "session checkpoint") - .replace("{count}", String(count)); + return COMMIT_MESSAGE_TEMPLATES.DEFAULT.replace( + "{summary}", + "session checkpoint", + ).replace("{count}", String(count)); } // Get first user message as summary base @@ -77,7 +78,10 @@ const generateCommitMessage = (messages: SessionMessage[]): string => { for (const [type, keywords] of Object.entries(COMMIT_TYPE_KEYWORDS)) { for (const keyword of keywords) { if (allContent.includes(keyword)) { - const template = COMMIT_MESSAGE_TEMPLATES[type as keyof typeof COMMIT_MESSAGE_TEMPLATES]; + const template = + COMMIT_MESSAGE_TEMPLATES[ + type as keyof typeof COMMIT_MESSAGE_TEMPLATES + ]; return template .replace("{summary}", summary || keyword) .replace("{count}", String(count)); @@ -85,9 +89,10 @@ const generateCommitMessage = (messages: SessionMessage[]): string => { } } - return COMMIT_MESSAGE_TEMPLATES.DEFAULT - .replace("{summary}", summary || "session changes") - .replace("{count}", String(count)); + return COMMIT_MESSAGE_TEMPLATES.DEFAULT.replace( + "{summary}", + summary || "session changes", + ).replace("{count}", String(count)); }; /** @@ -124,7 +129,7 @@ const createEmptyForkFile = (sessionId: string): SessionForkFile => { */ const loadForkFile = async ( sessionId: string, - workingDir: string + workingDir: string, ): Promise => { const filePath = getForkFilePath(sessionId, workingDir); @@ -142,7 +147,7 @@ const loadForkFile = async ( */ const saveForkFile = async ( file: SessionForkFile, - filePath: string + filePath: string, ): Promise => { const dir = dirname(filePath); @@ -160,7 +165,7 @@ const saveForkFile = async ( */ export const initializeForkService = async ( sessionId: string, - workingDir: string + workingDir: string, ): Promise => { const filePath = getForkFilePath(sessionId, workingDir); const file = await loadForkFile(sessionId, workingDir); @@ -176,7 +181,9 @@ export const initializeForkService = async ( */ const getCurrentFork = (): SessionFork | null => { if (!state.file) return null; - return state.file.forks.find((f) => f.id === state.file?.currentForkId) || null; + return ( + state.file.forks.find((f) => f.id === state.file?.currentForkId) || null + ); }; /** @@ -186,8 +193,13 @@ export const createSnapshot = async ( messages: SessionMessage[], todoItems: TodoItem[], contextFiles: string[], - metadata: { provider: string; model: string; agent: string; workingDir: string }, - options: SnapshotOptions = {} + metadata: { + provider: string; + model: string; + agent: string; + workingDir: string; + }, + options: SnapshotOptions = {}, ): Promise => { if (!state.file || !state.filePath) { return { success: false, error: FORK_ERRORS.SESSION_NOT_FOUND }; @@ -203,7 +215,8 @@ export const createSnapshot = async ( } // Generate snapshot name - const name = options.name || `${DEFAULT_SNAPSHOT_PREFIX}-${fork.snapshots.length + 1}`; + const name = + options.name || `${DEFAULT_SNAPSHOT_PREFIX}-${fork.snapshots.length + 1}`; // Check for duplicate name if (fork.snapshots.some((s) => s.name === name)) { @@ -213,7 +226,8 @@ export const createSnapshot = async ( const snapshotState: SessionSnapshotState = { messages: [...messages], todoItems: options.includeTodos !== false ? [...todoItems] : [], - contextFiles: options.includeContextFiles !== false ? [...contextFiles] : [], + contextFiles: + options.includeContextFiles !== false ? [...contextFiles] : [], metadata, }; @@ -240,19 +254,31 @@ export const createSnapshot = async ( * Rewind to a snapshot */ export const rewindToSnapshot = async ( - target: string | number + target: string | number, ): Promise => { if (!state.file || !state.filePath) { - return { success: false, messagesRestored: 0, error: FORK_ERRORS.SESSION_NOT_FOUND }; + return { + success: false, + messagesRestored: 0, + error: FORK_ERRORS.SESSION_NOT_FOUND, + }; } const fork = getCurrentFork(); if (!fork) { - return { success: false, messagesRestored: 0, error: FORK_ERRORS.FORK_NOT_FOUND }; + return { + success: false, + messagesRestored: 0, + error: FORK_ERRORS.FORK_NOT_FOUND, + }; } if (fork.snapshots.length === 0) { - return { success: false, messagesRestored: 0, error: FORK_ERRORS.NO_SNAPSHOTS_TO_REWIND }; + return { + success: false, + messagesRestored: 0, + error: FORK_ERRORS.NO_SNAPSHOTS_TO_REWIND, + }; } let snapshot: SessionSnapshot | undefined; @@ -260,7 +286,7 @@ export const rewindToSnapshot = async ( if (typeof target === "number") { // Rewind by count (e.g., 1 = previous snapshot) const currentIndex = fork.snapshots.findIndex( - (s) => s.id === fork.currentSnapshotId + (s) => s.id === fork.currentSnapshotId, ); const targetIndex = currentIndex - target; @@ -275,7 +301,11 @@ export const rewindToSnapshot = async ( } if (!snapshot) { - return { success: false, messagesRestored: 0, error: FORK_ERRORS.SNAPSHOT_NOT_FOUND }; + return { + success: false, + messagesRestored: 0, + error: FORK_ERRORS.SNAPSHOT_NOT_FOUND, + }; } fork.currentSnapshotId = snapshot.id; @@ -295,7 +325,7 @@ export const rewindToSnapshot = async ( * Create a new fork */ export const createFork = async ( - options: ForkOptions = {} + options: ForkOptions = {}, ): Promise => { if (!state.file || !state.filePath) { return { success: false, error: FORK_ERRORS.SESSION_NOT_FOUND }; @@ -322,7 +352,7 @@ export const createFork = async ( let branchFromId = currentFork.currentSnapshotId; if (options.fromSnapshot) { const snapshot = currentFork.snapshots.find( - (s) => s.name === options.fromSnapshot || s.id === options.fromSnapshot + (s) => s.name === options.fromSnapshot || s.id === options.fromSnapshot, ); if (!snapshot) { return { success: false, error: FORK_ERRORS.SNAPSHOT_NOT_FOUND }; @@ -331,11 +361,15 @@ export const createFork = async ( } // Copy snapshots up to branch point - const branchIndex = currentFork.snapshots.findIndex((s) => s.id === branchFromId); - const copiedSnapshots = currentFork.snapshots.slice(0, branchIndex + 1).map((s) => ({ - ...s, - id: uuidv4(), // New IDs for copied snapshots - })); + const branchIndex = currentFork.snapshots.findIndex( + (s) => s.id === branchFromId, + ); + const copiedSnapshots = currentFork.snapshots + .slice(0, branchIndex + 1) + .map((s) => ({ + ...s, + id: uuidv4(), // New IDs for copied snapshots + })); const newFork: SessionFork = { id: uuidv4(), @@ -385,7 +419,7 @@ export const listForks = (): ForkSummary[] => { return state.file.forks.map((fork) => { const currentSnapshot = fork.snapshots.find( - (s) => s.id === fork.currentSnapshotId + (s) => s.id === fork.currentSnapshotId, ); return { @@ -434,7 +468,9 @@ export const getSnapshot = (nameOrId: string): SessionSnapshot | null => { const fork = getCurrentFork(); if (!fork) return null; - return fork.snapshots.find((s) => s.name === nameOrId || s.id === nameOrId) || null; + return ( + fork.snapshots.find((s) => s.name === nameOrId || s.id === nameOrId) || null + ); }; /** diff --git a/src/services/skill-loader.ts b/src/services/skill-loader.ts index 9b78401..af273dc 100644 --- a/src/services/skill-loader.ts +++ b/src/services/skill-loader.ts @@ -31,7 +31,9 @@ import type { /** * Parse YAML-like frontmatter from SKILL.md content */ -const parseFrontmatter = (content: string): { frontmatter: string; body: string } => { +const parseFrontmatter = ( + content: string, +): { frontmatter: string; body: string } => { const delimiter = SKILL_FILE.FRONTMATTER_DELIMITER; const lines = content.split("\n"); @@ -52,7 +54,10 @@ const parseFrontmatter = (content: string): { frontmatter: string; body: string } const frontmatter = lines.slice(1, endIndex).join("\n"); - const body = lines.slice(endIndex + 1).join("\n").trim(); + const body = lines + .slice(endIndex + 1) + .join("\n") + .trim(); return { frontmatter, body }; }; @@ -141,7 +146,9 @@ const validateFrontmatter = ( // Validate triggers is an array if (!Array.isArray(data.triggers)) { - throw new Error(SKILL_ERRORS.MISSING_REQUIRED_FIELD("triggers (array)", filePath)); + throw new Error( + SKILL_ERRORS.MISSING_REQUIRED_FIELD("triggers (array)", filePath), + ); } return { @@ -151,7 +158,8 @@ const validateFrontmatter = ( version: data.version ? String(data.version) : undefined, triggers: data.triggers as string[], triggerType: data.triggerType as SkillFrontmatter["triggerType"], - autoTrigger: typeof data.autoTrigger === "boolean" ? data.autoTrigger : undefined, + autoTrigger: + typeof data.autoTrigger === "boolean" ? data.autoTrigger : undefined, requiredTools: Array.isArray(data.requiredTools) ? (data.requiredTools as string[]) : undefined, @@ -206,7 +214,9 @@ const parseExamples = (body: string): SkillExample[] => { /** * Load and parse a SKILL.md file */ -export const loadSkillFile = async (filePath: string): Promise => { +export const loadSkillFile = async ( + filePath: string, +): Promise => { try { const stat = await fs.stat(filePath); if (stat.size > SKILL_LOADING.MAX_FILE_SIZE_BYTES) { @@ -247,7 +257,8 @@ export const toSkillMetadata = (parsed: ParsedSkillFile): SkillMetadata => ({ triggers: parsed.frontmatter.triggers, triggerType: parsed.frontmatter.triggerType ?? SKILL_DEFAULTS.TRIGGER_TYPE, autoTrigger: parsed.frontmatter.autoTrigger ?? SKILL_DEFAULTS.AUTO_TRIGGER, - requiredTools: parsed.frontmatter.requiredTools ?? SKILL_DEFAULTS.REQUIRED_TOOLS, + requiredTools: + parsed.frontmatter.requiredTools ?? SKILL_DEFAULTS.REQUIRED_TOOLS, tags: parsed.frontmatter.tags, }); @@ -272,7 +283,9 @@ export const toSkillDefinition = (parsed: ParsedSkillFile): SkillDefinition => { /** * Parse skill body to extract system prompt and instructions */ -const parseSkillBody = (body: string): { systemPrompt: string; instructions: string } => { +const parseSkillBody = ( + body: string, +): { systemPrompt: string; instructions: string } => { // Look for ## System Prompt section const systemPromptMatch = body.match( /## System Prompt([\s\S]*?)(?=## Instructions|## Examples|$)/i, @@ -285,7 +298,9 @@ const parseSkillBody = (body: string): { systemPrompt: string; instructions: str // If no sections found, use the whole body as instructions const systemPrompt = systemPromptMatch ? systemPromptMatch[1].trim() : ""; - const instructions = instructionsMatch ? instructionsMatch[1].trim() : body.trim(); + const instructions = instructionsMatch + ? instructionsMatch[1].trim() + : body.trim(); return { systemPrompt, instructions }; }; diff --git a/src/services/skill-registry.ts b/src/services/skill-registry.ts index 538b1a5..01819b8 100644 --- a/src/services/skill-registry.ts +++ b/src/services/skill-registry.ts @@ -5,15 +5,8 @@ * Uses progressive disclosure to load skills on demand. */ -import { - SKILL_MATCHING, - SKILL_LOADING, - SKILL_ERRORS, -} from "@constants/skills"; -import { - loadAllSkills, - loadSkillById, -} from "@services/skill-loader"; +import { SKILL_MATCHING, SKILL_LOADING, SKILL_ERRORS } from "@constants/skills"; +import { loadAllSkills, loadSkillById } from "@services/skill-loader"; import type { SkillDefinition, SkillMatch, @@ -175,7 +168,9 @@ const matchTrigger = ( /** * Find matching skills for user input */ -export const findMatchingSkills = async (input: string): Promise => { +export const findMatchingSkills = async ( + input: string, +): Promise => { await refreshIfNeeded(); const matches: SkillMatch[] = []; @@ -213,7 +208,9 @@ export const findMatchingSkills = async (input: string): Promise = /** * Find the best matching skill for input */ -export const findBestMatch = async (input: string): Promise => { +export const findBestMatch = async ( + input: string, +): Promise => { const matches = await findMatchingSkills(input); return matches.length > 0 ? matches[0] : null; }; @@ -382,8 +379,8 @@ export const getAutoTriggerSkills = (): SkillDefinition[] => { * Get skills by tag */ export const getSkillsByTag = (tag: string): SkillDefinition[] => { - return Array.from(registryState.skills.values()).filter( - (skill) => skill.tags?.includes(tag), + return Array.from(registryState.skills.values()).filter((skill) => + skill.tags?.includes(tag), ); }; diff --git a/src/services/snapshot-service.ts b/src/services/snapshot-service.ts index b15a5c0..a03b339 100644 --- a/src/services/snapshot-service.ts +++ b/src/services/snapshot-service.ts @@ -185,7 +185,8 @@ export const createSnapshot = async ( const id = uuidv4(); const timestamp = Date.now(); - const snapshotMessage = message ?? `Snapshot ${new Date(timestamp).toISOString()}`; + const snapshotMessage = + message ?? `Snapshot ${new Date(timestamp).toISOString()}`; // Get current state const currentCommit = getCurrentCommitHash(workingDir); @@ -253,7 +254,11 @@ export const getSnapshot = async ( workingDir: string, snapshotId: string, ): Promise => { - const snapshotPath = path.join(workingDir, SNAPSHOTS_DIR, `${snapshotId}.json`); + const snapshotPath = path.join( + workingDir, + SNAPSHOTS_DIR, + `${snapshotId}.json`, + ); try { const content = await fs.readFile(snapshotPath, "utf-8"); @@ -263,7 +268,9 @@ export const getSnapshot = async ( } }; -export const listSnapshots = async (workingDir: string): Promise => { +export const listSnapshots = async ( + workingDir: string, +): Promise => { const snapshotsDir = path.join(workingDir, SNAPSHOTS_DIR); if (!(await fileExists(snapshotsDir))) { @@ -278,7 +285,10 @@ export const listSnapshots = async (workingDir: string): Promise => { - const snapshotPath = path.join(workingDir, SNAPSHOTS_DIR, `${snapshotId}.json`); + const snapshotPath = path.join( + workingDir, + SNAPSHOTS_DIR, + `${snapshotId}.json`, + ); try { await fs.unlink(snapshotPath); @@ -312,7 +326,9 @@ export const deleteSnapshot = async ( } }; -export const pruneOldSnapshots = async (workingDir: string): Promise => { +export const pruneOldSnapshots = async ( + workingDir: string, +): Promise => { const cutoff = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1000; const snapshots = await listSnapshots(workingDir); let deleted = 0; @@ -351,7 +367,11 @@ export const validatePatch = async ( patch: string, ): Promise<{ valid: boolean; errors: string[] }> => { // Write patch to temp file - const tempPatchPath = path.join(workingDir, SNAPSHOTS_DIR, `temp-${Date.now()}.patch`); + const tempPatchPath = path.join( + workingDir, + SNAPSHOTS_DIR, + `temp-${Date.now()}.patch`, + ); try { await fs.writeFile(tempPatchPath, patch); diff --git a/src/stores/core/multi-agent-store.ts b/src/stores/core/multi-agent-store.ts index 0d6bda1..62e1bd9 100644 --- a/src/stores/core/multi-agent-store.ts +++ b/src/stores/core/multi-agent-store.ts @@ -15,10 +15,7 @@ import type { ConflictResolutionResult, } from "@/types/multi-agent"; import type { ChatMessage } from "@/types/common"; -import { - AGENT_ID_PREFIX, - REQUEST_ID_PREFIX, -} from "@/constants/multi-agent"; +import { AGENT_ID_PREFIX, REQUEST_ID_PREFIX } from "@/constants/multi-agent"; /** * Multi-agent store state @@ -255,7 +252,9 @@ const resolveConflict = ( return { conflicts: updatedConflicts }; }); - const conflict = store.getState().conflicts.find((c) => c.filePath === filePath); + const conflict = store + .getState() + .conflicts.find((c) => c.filePath === filePath); if (conflict) { addEvent({ type: "conflict_resolved", @@ -327,7 +326,9 @@ const isFileBeingModified = (filePath: string): boolean => { */ const getAgentModifyingFile = (filePath: string): AgentInstance | null => { const activeInstances = getActiveInstances(); - return activeInstances.find((i) => i.modifiedFiles.includes(filePath)) ?? null; + return ( + activeInstances.find((i) => i.modifiedFiles.includes(filePath)) ?? null + ); }; /** diff --git a/src/stores/core/vim-store.ts b/src/stores/core/vim-store.ts index e0dafdf..94157e6 100644 --- a/src/stores/core/vim-store.ts +++ b/src/stores/core/vim-store.ts @@ -6,12 +6,7 @@ */ import { createStore } from "zustand/vanilla"; -import type { - VimState, - VimMode, - VimSearchMatch, - VimConfig, -} from "@/types/vim"; +import type { VimState, VimMode, VimSearchMatch, VimConfig } from "@/types/vim"; import { DEFAULT_VIM_CONFIG } from "@constants/vim"; /** @@ -190,9 +185,7 @@ export const vimStore = createStore((set, get) => ({ if (searchMatches.length === 0) return; const prevIndex = - currentMatchIndex <= 0 - ? searchMatches.length - 1 - : currentMatchIndex - 1; + currentMatchIndex <= 0 ? searchMatches.length - 1 : currentMatchIndex - 1; set({ currentMatchIndex: prevIndex }); }, @@ -223,23 +216,31 @@ export const vimActions = { enable: () => vimStore.getState().enable(), disable: () => vimStore.getState().disable(), toggle: () => vimStore.getState().toggle(), - setSearchPattern: (pattern: string) => vimStore.getState().setSearchPattern(pattern), - setCommandBuffer: (buffer: string) => vimStore.getState().setCommandBuffer(buffer), - appendCommandBuffer: (char: string) => vimStore.getState().appendCommandBuffer(char), + setSearchPattern: (pattern: string) => + vimStore.getState().setSearchPattern(pattern), + setCommandBuffer: (buffer: string) => + vimStore.getState().setCommandBuffer(buffer), + appendCommandBuffer: (char: string) => + vimStore.getState().appendCommandBuffer(char), clearCommandBuffer: () => vimStore.getState().clearCommandBuffer(), - setVisualStart: (position: number | null) => vimStore.getState().setVisualStart(position), + setVisualStart: (position: number | null) => + vimStore.getState().setVisualStart(position), setCount: (count: number) => vimStore.getState().setCount(count), resetCount: () => vimStore.getState().resetCount(), - setPendingOperator: (operator: string | null) => vimStore.getState().setPendingOperator(operator), + setPendingOperator: (operator: string | null) => + vimStore.getState().setPendingOperator(operator), setSearchDirection: (direction: "forward" | "backward") => vimStore.getState().setSearchDirection(direction), setRegister: (content: string) => vimStore.getState().setRegister(content), - setSearchMatches: (matches: VimSearchMatch[]) => vimStore.getState().setSearchMatches(matches), - setCurrentMatchIndex: (index: number) => vimStore.getState().setCurrentMatchIndex(index), + setSearchMatches: (matches: VimSearchMatch[]) => + vimStore.getState().setSearchMatches(matches), + setCurrentMatchIndex: (index: number) => + vimStore.getState().setCurrentMatchIndex(index), nextMatch: () => vimStore.getState().nextMatch(), prevMatch: () => vimStore.getState().prevMatch(), clearSearch: () => vimStore.getState().clearSearch(), - setConfig: (config: Partial) => vimStore.getState().setConfig(config), + setConfig: (config: Partial) => + vimStore.getState().setConfig(config), reset: () => vimStore.getState().reset(), getState: () => vimStore.getState(), subscribe: vimStore.subscribe, diff --git a/src/tools/apply-patch/execute.ts b/src/tools/apply-patch/execute.ts index 68a6ca8..383ab3f 100644 --- a/src/tools/apply-patch/execute.ts +++ b/src/tools/apply-patch/execute.ts @@ -12,8 +12,17 @@ import { PATCH_MESSAGES, PATCH_TITLES, } from "@constants/apply-patch"; -import { parsePatch, validatePatch, getTargetPath, reversePatch } from "@tools/apply-patch/parser"; -import { findHunkPosition, isHunkApplied, previewHunkApplication } from "@tools/apply-patch/matcher"; +import { + parsePatch, + validatePatch, + getTargetPath, + reversePatch, +} from "@tools/apply-patch/parser"; +import { + findHunkPosition, + isHunkApplied, + previewHunkApplication, +} from "@tools/apply-patch/matcher"; import type { ApplyPatchParams } from "@tools/apply-patch/params"; import type { FilePatchResult, @@ -79,14 +88,10 @@ export const executeApplyPatch = async ( : join(ctx.workingDir, targetPath); // Apply the file patch - const result = await applyFilePatch( - filePatch, - absolutePath, - { - fuzz: params.fuzz ?? PATCH_DEFAULTS.FUZZ, - dryRun: params.dryRun ?? false, - }, - ); + const result = await applyFilePatch(filePatch, absolutePath, { + fuzz: params.fuzz ?? PATCH_DEFAULTS.FUZZ, + dryRun: params.dryRun ?? false, + }); results.push(result); @@ -210,7 +215,9 @@ const applyFilePatch = async ( } // Find position with fuzzy matching - const position = findHunkPosition(currentContent, hunk, { fuzz: options.fuzz }); + const position = findHunkPosition(currentContent, hunk, { + fuzz: options.fuzz, + }); if (!position.found) { hunkResults.push({ @@ -223,7 +230,11 @@ const applyFilePatch = async ( } // Apply the hunk - const preview = previewHunkApplication(currentContent, hunk, position.lineNumber); + const preview = previewHunkApplication( + currentContent, + hunk, + position.lineNumber, + ); if (!preview.success) { hunkResults.push({ diff --git a/src/tools/apply-patch/index.ts b/src/tools/apply-patch/index.ts index 4601c85..cf6d4fa 100644 --- a/src/tools/apply-patch/index.ts +++ b/src/tools/apply-patch/index.ts @@ -9,9 +9,23 @@ import { applyPatchParams } from "@tools/apply-patch/params"; import { executeApplyPatch } from "@tools/apply-patch/execute"; export { applyPatchParams } from "@tools/apply-patch/params"; -export { executeApplyPatch, rollbackPatch, getAvailableRollbacks, clearRollbacks } from "@tools/apply-patch/execute"; -export { parsePatch, validatePatch, getTargetPath, reversePatch } from "@tools/apply-patch/parser"; -export { findHunkPosition, isHunkApplied, previewHunkApplication } from "@tools/apply-patch/matcher"; +export { + executeApplyPatch, + rollbackPatch, + getAvailableRollbacks, + clearRollbacks, +} from "@tools/apply-patch/execute"; +export { + parsePatch, + validatePatch, + getTargetPath, + reversePatch, +} from "@tools/apply-patch/parser"; +export { + findHunkPosition, + isHunkApplied, + previewHunkApplication, +} from "@tools/apply-patch/matcher"; /** * Tool description diff --git a/src/tools/apply-patch/matcher.ts b/src/tools/apply-patch/matcher.ts index 923ab60..b04b0c8 100644 --- a/src/tools/apply-patch/matcher.ts +++ b/src/tools/apply-patch/matcher.ts @@ -23,10 +23,7 @@ const DEFAULT_MATCH_OPTIONS: ContextMatchOptions = { /** * Normalize line for comparison */ -const normalizeLine = ( - line: string, - options: ContextMatchOptions, -): string => { +const normalizeLine = (line: string, options: ContextMatchOptions): string => { let normalized = line; if (options.ignoreWhitespace) { diff --git a/src/tools/apply-patch/params.ts b/src/tools/apply-patch/params.ts index bff997e..4f90554 100644 --- a/src/tools/apply-patch/params.ts +++ b/src/tools/apply-patch/params.ts @@ -9,9 +9,7 @@ import { PATCH_DEFAULTS } from "@constants/apply-patch"; * Zod schema for apply_patch tool parameters */ export const applyPatchParams = z.object({ - patch: z - .string() - .describe("The unified diff patch content to apply"), + patch: z.string().describe("The unified diff patch content to apply"), targetFile: z .string() @@ -31,7 +29,9 @@ export const applyPatchParams = z.object({ .max(PATCH_DEFAULTS.MAX_FUZZ) .optional() .default(PATCH_DEFAULTS.FUZZ) - .describe(`Context line tolerance for fuzzy matching (0-${PATCH_DEFAULTS.MAX_FUZZ})`), + .describe( + `Context line tolerance for fuzzy matching (0-${PATCH_DEFAULTS.MAX_FUZZ})`, + ), reverse: z .boolean() diff --git a/src/tools/apply-patch/parser.ts b/src/tools/apply-patch/parser.ts index 1f4949f..ad1ed79 100644 --- a/src/tools/apply-patch/parser.ts +++ b/src/tools/apply-patch/parser.ts @@ -36,7 +36,10 @@ export const parsePatch = (patchContent: string): ParsedPatch => { // Git diff header const gitDiffMatch = line.match(PATCH_PATTERNS.GIT_DIFF); if (gitDiffMatch) { - if (currentFile && (currentFile.hunks.length > 0 || currentFile.isBinary)) { + if ( + currentFile && + (currentFile.hunks.length > 0 || currentFile.isBinary) + ) { files.push(currentFile); } currentFile = createEmptyFilePatch(gitDiffMatch[1], gitDiffMatch[2]); @@ -174,7 +177,10 @@ export const parsePatch = (patchContent: string): ParsedPatch => { /** * Create empty file patch structure */ -const createEmptyFilePatch = (oldPath: string, newPath: string): ParsedFilePatch => ({ +const createEmptyFilePatch = ( + oldPath: string, + newPath: string, +): ParsedFilePatch => ({ oldPath: cleanPath(oldPath), newPath: cleanPath(newPath), hunks: [], diff --git a/src/tools/bash/execute.ts b/src/tools/bash/execute.ts index 53ac3a6..ff7f0bc 100644 --- a/src/tools/bash/execute.ts +++ b/src/tools/bash/execute.ts @@ -109,13 +109,10 @@ const executeCommand = ( args: BashParams, ctx: ToolContext, ): Promise => { - const { - command, - workdir, - timeout = BASH_DEFAULTS.TIMEOUT, - } = args; + const { command, workdir, timeout = BASH_DEFAULTS.TIMEOUT } = args; // Provide default description if not specified - const description = args.description ?? `Running: ${command.substring(0, 50)}`; + const description = + args.description ?? `Running: ${command.substring(0, 50)}`; const cwd = workdir ?? ctx.workingDir; updateRunningStatus(ctx, description); @@ -179,7 +176,8 @@ export const executeBash = async ( } // Provide default description if not specified - const description = args.description ?? `Running: ${command.substring(0, 50)}`; + const description = + args.description ?? `Running: ${command.substring(0, 50)}`; const allowed = await checkPermission( command, diff --git a/src/tools/core/registry.ts b/src/tools/core/registry.ts index 95f7685..0b7c046 100644 --- a/src/tools/core/registry.ts +++ b/src/tools/core/registry.ts @@ -16,8 +16,8 @@ import { todoReadTool } from "@/tools/todo-read"; import { globToolDefinition } from "@/tools/glob/definition"; import { grepToolDefinition } from "@/tools/grep/definition"; import { webSearchTool } from "@/tools/web-search"; -import { webFetchTool } from "@/tools/web-fetch"; -import { multiEditTool } from "@/tools/multi-edit"; +import { webFetchTool } from "@tools/web-fetch/execute"; +import { multiEditTool } from "@tools/multi-edit/execute"; import { lspTool } from "@/tools/lsp"; import { applyPatchTool } from "@/tools/apply-patch"; import { diff --git a/src/tools/edit/execute.ts b/src/tools/edit/execute.ts index d124b6a..569c801 100644 --- a/src/tools/edit/execute.ts +++ b/src/tools/edit/execute.ts @@ -6,7 +6,10 @@ import fs from "fs/promises"; import path from "path"; import { EDIT_MESSAGES, EDIT_TITLES, EDIT_DESCRIPTION } from "@constants/edit"; -import { isFileOpAllowed, promptFilePermission } from "@services/core/permissions"; +import { + isFileOpAllowed, + promptFilePermission, +} from "@services/core/permissions"; import { formatDiff } from "@utils/diff/format"; import { generateDiff } from "@utils/diff/generate"; import { editParams } from "@tools/edit/params"; diff --git a/src/tools/glob/definition.ts b/src/tools/glob/definition.ts index 6d1db3e..591d77a 100644 --- a/src/tools/glob/definition.ts +++ b/src/tools/glob/definition.ts @@ -7,11 +7,17 @@ import { executeGlob } from "@tools/glob/execute"; import type { ToolDefinition, ToolContext, ToolResult } from "@/types/tools"; export const globParams = z.object({ - pattern: z.string().describe("The glob pattern to match files against (e.g., '**/*.ts', 'src/**/*.tsx')"), + pattern: z + .string() + .describe( + "The glob pattern to match files against (e.g., '**/*.ts', 'src/**/*.tsx')", + ), path: z .string() .optional() - .describe("The directory to search in. Defaults to current working directory."), + .describe( + "The directory to search in. Defaults to current working directory.", + ), }); type GlobParams = z.infer; diff --git a/src/tools/grep/definition.ts b/src/tools/grep/definition.ts index af64fd5..3df9595 100644 --- a/src/tools/grep/definition.ts +++ b/src/tools/grep/definition.ts @@ -13,15 +13,14 @@ export const grepParams = z.object({ path: z .string() .optional() - .describe("File or directory to search in. Defaults to current working directory."), + .describe( + "File or directory to search in. Defaults to current working directory.", + ), glob: z .string() .optional() .describe("Glob pattern to filter files (e.g., '*.ts', '**/*.tsx')"), - case_insensitive: z - .boolean() - .optional() - .describe("Case insensitive search"), + case_insensitive: z.boolean().optional().describe("Case insensitive search"), context_lines: z .number() .optional() diff --git a/src/tools/index.ts b/src/tools/index.ts index 1ddeb40..8e782db 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -9,8 +9,8 @@ import { todoReadTool } from "@tools/todo-read"; import { globToolDefinition } from "@tools/glob/definition"; import { grepToolDefinition } from "@tools/grep/definition"; import { webSearchTool } from "@tools/web-search"; -import { webFetchTool } from "@tools/web-fetch"; -import { multiEditTool } from "@tools/multi-edit"; +import { webFetchTool } from "@tools/web-fetch/execute"; +import { multiEditTool } from "@tools/multi-edit/execute"; import { lspTool } from "@tools/lsp"; import { applyPatchTool } from "@tools/apply-patch"; import { diff --git a/src/tools/lsp.ts b/src/tools/lsp.ts index 261cf82..6375a95 100644 --- a/src/tools/lsp.ts +++ b/src/tools/lsp.ts @@ -183,10 +183,16 @@ Examples: // Open file in LSP await lspService.openFile(file); - const operationHandlers: Record Promise<{ title: string; output: string }>> = { + const operationHandlers: Record< + string, + () => Promise<{ title: string; output: string }> + > = { hover: async () => { if (!position) { - return { title: "Error", output: "Position required for hover operation" }; + return { + title: "Error", + output: "Position required for hover operation", + }; } const hover = await lspService.getHover(file, position); return { title: "Hover Info", output: formatHover(hover) }; @@ -194,7 +200,10 @@ Examples: definition: async () => { if (!position) { - return { title: "Error", output: "Position required for definition operation" }; + return { + title: "Error", + output: "Position required for definition operation", + }; } const definition = await lspService.getDefinition(file, position); return { title: "Definition", output: formatLocations(definition) }; @@ -202,7 +211,10 @@ Examples: references: async () => { if (!position) { - return { title: "Error", output: "Position required for references operation" }; + return { + title: "Error", + output: "Position required for references operation", + }; } const references = await lspService.getReferences(file, position); return { diff --git a/src/tools/multi-edit/execute.ts b/src/tools/multi-edit/execute.ts index bc725e4..635e3a5 100644 --- a/src/tools/multi-edit/execute.ts +++ b/src/tools/multi-edit/execute.ts @@ -13,7 +13,10 @@ import { MULTI_EDIT_TITLES, MULTI_EDIT_DESCRIPTION, } from "@constants/multi-edit"; -import { isFileOpAllowed, promptFilePermission } from "@services/core/permissions"; +import { + isFileOpAllowed, + promptFilePermission, +} from "@services/core/permissions"; import { formatDiff } from "@utils/diff/format"; import { generateDiff } from "@utils/diff/generate"; import { multiEditParams } from "@tools/multi-edit/params"; diff --git a/src/tools/read/execute.ts b/src/tools/read/execute.ts index d473114..8ff9a33 100644 --- a/src/tools/read/execute.ts +++ b/src/tools/read/execute.ts @@ -11,7 +11,10 @@ import { READ_TITLES, READ_DESCRIPTION, } from "@constants/read"; -import { isFileOpAllowed, promptFilePermission } from "@services/core/permissions"; +import { + isFileOpAllowed, + promptFilePermission, +} from "@services/core/permissions"; import { readParams } from "@tools/read/params"; import { processLines } from "@tools/read/format"; import type { diff --git a/src/tools/web-fetch/execute.ts b/src/tools/web-fetch/execute.ts index a86a6cb..abb80aa 100644 --- a/src/tools/web-fetch/execute.ts +++ b/src/tools/web-fetch/execute.ts @@ -318,10 +318,7 @@ export const executeWebFetch = async ( // Check content length if (content.length > WEB_FETCH_DEFAULTS.MAX_CONTENT_LENGTH) { - content = truncateContent( - content, - WEB_FETCH_DEFAULTS.MAX_CONTENT_LENGTH, - ); + content = truncateContent(content, WEB_FETCH_DEFAULTS.MAX_CONTENT_LENGTH); } // Process content based on type diff --git a/src/tools/web-fetch/params.ts b/src/tools/web-fetch/params.ts index cb2cf07..a4f595b 100644 --- a/src/tools/web-fetch/params.ts +++ b/src/tools/web-fetch/params.ts @@ -9,7 +9,9 @@ export const webFetchParams = z.object({ prompt: z .string() .optional() - .describe("Optional prompt to extract specific information from the content"), + .describe( + "Optional prompt to extract specific information from the content", + ), timeout: z .number() .optional() diff --git a/src/tools/web-search.ts b/src/tools/web-search.ts index 3c269c7..db1c579 100644 --- a/src/tools/web-search.ts +++ b/src/tools/web-search.ts @@ -2,6 +2,9 @@ * Web Search tool for searching the web */ -export { webSearchParams, type WebSearchParamsSchema } from "@tools/web-search/params"; +export { + webSearchParams, + type WebSearchParamsSchema, +} from "@tools/web-search/params"; export { executeWebSearch, webSearchTool } from "@tools/web-search/execute"; export type { SearchResult } from "@tools/web-search/execute"; diff --git a/src/tools/web-search/execute.ts b/src/tools/web-search/execute.ts index 93d89aa..fd56e1c 100644 --- a/src/tools/web-search/execute.ts +++ b/src/tools/web-search/execute.ts @@ -38,10 +38,7 @@ const createSuccessResult = ( query: string, ): ToolResult => { const formattedResults = results - .map( - (r, i) => - `${i + 1}. **${r.title}**\n ${r.url}\n ${r.snippet}`, - ) + .map((r, i) => `${i + 1}. **${r.title}**\n ${r.url}\n ${r.snippet}`) .join("\n\n"); return { @@ -93,7 +90,10 @@ const parseRssResults = (rss: string, maxResults: number): SearchResult[] => { const itemPattern = /([\s\S]*?)<\/item>/gi; let match: RegExpExecArray | null; - while ((match = itemPattern.exec(rss)) !== null && results.length < maxResults) { + while ( + (match = itemPattern.exec(rss)) !== null && + results.length < maxResults + ) { const itemContent = match[1]; const titleMatch = itemContent.match(/([^<]+)<\/title>/); diff --git a/src/tools/write/execute.ts b/src/tools/write/execute.ts index 29d9a7d..05403bf 100644 --- a/src/tools/write/execute.ts +++ b/src/tools/write/execute.ts @@ -10,7 +10,10 @@ import { WRITE_TITLES, WRITE_DESCRIPTION, } from "@constants/write"; -import { isFileOpAllowed, promptFilePermission } from "@services/core/permissions"; +import { + isFileOpAllowed, + promptFilePermission, +} from "@services/core/permissions"; import { formatDiff } from "@utils/diff/format"; import { generateDiff } from "@utils/diff/generate"; import { writeParams } from "@tools/write/params"; diff --git a/src/tui-solid/app.tsx b/src/tui-solid/app.tsx index 931ce0d..f2120d5 100644 --- a/src/tui-solid/app.tsx +++ b/src/tui-solid/app.tsx @@ -11,23 +11,20 @@ import { batch } from "solid-js"; import { getFiles } from "@services/file-picker/files"; import { abortCurrentOperation } from "@services/chat-tui-service"; import versionData from "@/version.json"; +import { ExitProvider, useExit } from "@tui-solid/context/exit"; +import { RouteProvider, useRoute } from "@tui-solid/context/route"; import { - ExitProvider, - useExit, - RouteProvider, - useRoute, AppStoreProvider, useAppStore, setAppStoreRef, - ThemeProvider, - useTheme, - KeybindProvider, - DialogProvider, -} from "@tui-solid/context"; +} from "@tui-solid/context/app"; +import { ThemeProvider, useTheme } from "@tui-solid/context/theme"; +import { KeybindProvider } from "@tui-solid/context/keybind"; +import { DialogProvider } from "@tui-solid/context/dialog"; import { ToastProvider, Toast, useToast } from "@tui-solid/ui/toast"; import { Home } from "@tui-solid/routes/home"; import { Session } from "@tui-solid/routes/session"; -import type { TuiInput, TuiOutput } from "@tui-solid/types"; +import type { TuiInput, TuiOutput } from "@interfaces/index"; import type { MCPServerDisplay } from "@/types/tui"; import type { PermissionScope, LearningScope } from "@/types/tui"; import type { MCPAddFormData } from "@/types/mcp"; diff --git a/src/tui-solid/components/inputs/input-area.tsx b/src/tui-solid/components/inputs/input-area.tsx index 7ca4150..1f17a27 100644 --- a/src/tui-solid/components/inputs/input-area.tsx +++ b/src/tui-solid/components/inputs/input-area.tsx @@ -170,14 +170,19 @@ export function InputArea(props: InputAreaProps) { } // Normalize line endings (Windows ConPTY sends CR-only newlines) - const normalizedText = event.text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); + const normalizedText = event.text + .replace(/\r\n/g, "\n") + .replace(/\r/g, "\n"); const pastedContent = normalizedText.trim(); if (!pastedContent) return; // Check if paste should be summarized const lineCount = (pastedContent.match(/\n/g)?.length ?? 0) + 1; - if (lineCount >= MIN_PASTE_LINES || pastedContent.length > MIN_PASTE_CHARS) { + if ( + lineCount >= MIN_PASTE_LINES || + pastedContent.length > MIN_PASTE_CHARS + ) { event.preventDefault(); pasteText(pastedContent, `[Pasted ~${lineCount} lines]`); } diff --git a/src/tui-solid/components/inputs/mcp-add-form.tsx b/src/tui-solid/components/inputs/mcp-add-form.tsx index ab198b6..7db7271 100644 --- a/src/tui-solid/components/inputs/mcp-add-form.tsx +++ b/src/tui-solid/components/inputs/mcp-add-form.tsx @@ -24,7 +24,7 @@ const FIELD_LABELS: Record<FormField, string> = { const FIELD_PLACEHOLDERS: Record<FormField, string> = { name: "e.g., filesystem", command: "e.g., npx", - args: "e.g., -y @modelcontextprotocol/server-filesystem \"/path/to/dir\"", + args: 'e.g., -y @modelcontextprotocol/server-filesystem "/path/to/dir"', scope: "", }; @@ -206,21 +206,31 @@ export function MCPAddForm(props: MCPAddFormProps) { <box flexDirection="row" marginBottom={1}> <text fg={isCurrentField ? theme.colors.primary : theme.colors.text} - attributes={isCurrentField ? TextAttributes.BOLD : TextAttributes.NONE} + attributes={ + isCurrentField ? TextAttributes.BOLD : TextAttributes.NONE + } > {isCurrentField ? "> " : " "} {FIELD_LABELS[field]}:{" "} </text> <text fg={!isGlobal() ? theme.colors.success : theme.colors.textDim} - attributes={!isGlobal() && isCurrentField ? TextAttributes.BOLD : TextAttributes.NONE} + attributes={ + !isGlobal() && isCurrentField + ? TextAttributes.BOLD + : TextAttributes.NONE + } > [Local] </text> <text fg={theme.colors.textDim}> / </text> <text fg={isGlobal() ? theme.colors.warning : theme.colors.textDim} - attributes={isGlobal() && isCurrentField ? TextAttributes.BOLD : TextAttributes.NONE} + attributes={ + isGlobal() && isCurrentField + ? TextAttributes.BOLD + : TextAttributes.NONE + } > [Global] </text> @@ -232,7 +242,9 @@ export function MCPAddForm(props: MCPAddFormProps) { <box flexDirection="row" marginBottom={1}> <text fg={isCurrentField ? theme.colors.primary : theme.colors.text} - attributes={isCurrentField ? TextAttributes.BOLD : TextAttributes.NONE} + attributes={ + isCurrentField ? TextAttributes.BOLD : TextAttributes.NONE + } > {isCurrentField ? "> " : " "} {FIELD_LABELS[field]}:{" "} @@ -287,9 +299,7 @@ export function MCPAddForm(props: MCPAddFormProps) { <text fg={theme.colors.textDim}> Tab/Enter next | Shift+Tab prev | ↑↓ navigate | Esc cancel </text> - <text fg={theme.colors.textDim}> - Enter on Scope to submit - </text> + <text fg={theme.colors.textDim}>Enter on Scope to submit</text> </box> </box> ); diff --git a/src/tui-solid/components/layout/header.tsx b/src/tui-solid/components/layout/header.tsx index 6bb2734..0c7fe1a 100644 --- a/src/tui-solid/components/layout/header.tsx +++ b/src/tui-solid/components/layout/header.tsx @@ -155,7 +155,10 @@ export function Header(props: HeaderProps) { [{MODE_LABELS[app.interactionMode()]}] </text> <Show when={app.currentAgent() !== "default"}> - <text fg={theme.colors.secondary} attributes={TextAttributes.BOLD}> + <text + fg={theme.colors.secondary} + attributes={TextAttributes.BOLD} + > {" "} @{app.currentAgent()} </text> diff --git a/src/tui-solid/components/layout/streaming-message.tsx b/src/tui-solid/components/layout/streaming-message.tsx index 44049c1..40bddc3 100644 --- a/src/tui-solid/components/layout/streaming-message.tsx +++ b/src/tui-solid/components/layout/streaming-message.tsx @@ -18,11 +18,14 @@ export function StreamingMessage(props: StreamingMessageProps) { // This ensures proper reactivity with the store const [displayContent, setDisplayContent] = createSignal(props.entry.content); const [isActiveStreaming, setIsActiveStreaming] = createSignal( - props.entry.metadata?.isStreaming ?? false + props.entry.metadata?.isStreaming ?? false, ); onMount(() => { - addDebugLog("render", `StreamingMessage mounted for entry: ${props.entry.id}`); + addDebugLog( + "render", + `StreamingMessage mounted for entry: ${props.entry.id}`, + ); }); // Effect to sync content from store's streamingLog @@ -36,7 +39,10 @@ export function StreamingMessage(props: StreamingMessageProps) { // Check if this entry is the currently streaming log const isCurrentLog = logId === props.entry.id; - addDebugLog("render", `Effect: logId=${logId}, entryId=${props.entry.id}, isActive=${isActive}, contentLen=${storeContent?.length ?? 0}`); + addDebugLog( + "render", + `Effect: logId=${logId}, entryId=${props.entry.id}, isActive=${isActive}, contentLen=${storeContent?.length ?? 0}`, + ); if (isCurrentLog && isActive) { setDisplayContent(storeContent); diff --git a/src/tui-solid/components/menu/brain-menu.tsx b/src/tui-solid/components/menu/brain-menu.tsx index acce57d..bb9399b 100644 --- a/src/tui-solid/components/menu/brain-menu.tsx +++ b/src/tui-solid/components/menu/brain-menu.tsx @@ -146,13 +146,17 @@ export function BrainMenu(props: BrainMenuProps) { // Main menu navigation if (view() === "main") { if (evt.name === "up") { - setSelectedIndex((prev) => (prev > 0 ? prev - 1 : menuItems().length - 1)); + setSelectedIndex((prev) => + prev > 0 ? prev - 1 : menuItems().length - 1, + ); evt.preventDefault(); return; } if (evt.name === "down") { - setSelectedIndex((prev) => (prev < menuItems().length - 1 ? prev + 1 : 0)); + setSelectedIndex((prev) => + prev < menuItems().length - 1 ? prev + 1 : 0, + ); evt.preventDefault(); return; } @@ -267,7 +271,8 @@ export function BrainMenu(props: BrainMenuProps) { <text fg={getStatusColor()}>{getStatusText()}</text> <Show when={isConnected()}> <text fg={theme.colors.textDim}> - {" "}({app.brain().knowledgeCount}K / {app.brain().memoryCount}M) + {" "} + ({app.brain().knowledgeCount}K / {app.brain().memoryCount}M) </text> </Show> </box> @@ -299,13 +304,17 @@ export function BrainMenu(props: BrainMenuProps) { <box flexDirection="row"> <text fg={isSelected() ? theme.colors.accent : undefined} - attributes={isSelected() ? TextAttributes.BOLD : TextAttributes.NONE} + attributes={ + isSelected() ? TextAttributes.BOLD : TextAttributes.NONE + } > {isSelected() ? "> " : " "} </text> <text fg={isSelected() ? theme.colors.accent : undefined} - attributes={isSelected() ? TextAttributes.BOLD : TextAttributes.NONE} + attributes={ + isSelected() ? TextAttributes.BOLD : TextAttributes.NONE + } > {item.label} </text> @@ -320,7 +329,9 @@ export function BrainMenu(props: BrainMenuProps) { </box> <box marginTop={1} flexDirection="column"> - <text fg={theme.colors.info}>{BRAIN_BANNER.CTA}: {BRAIN_BANNER.URL}</text> + <text fg={theme.colors.info}> + {BRAIN_BANNER.CTA}: {BRAIN_BANNER.URL} + </text> <text fg={theme.colors.textDim}> Arrow keys navigate | Enter select | Esc close </text> @@ -339,17 +350,19 @@ export function BrainMenu(props: BrainMenuProps) { </text> </box> <box marginBottom={1}> - <text fg={theme.colors.text}>2. After logging in, copy your JWT token</text> + <text fg={theme.colors.text}> + 2. After logging in, copy your JWT token + </text> </box> <box marginBottom={1}> - <text fg={theme.colors.text}>3. Press Enter to input your token</text> + <text fg={theme.colors.text}> + 3. Press Enter to input your token + </text> </box> </box> <box marginTop={1}> - <text fg={theme.colors.textDim}> - Enter continue | Esc back - </text> + <text fg={theme.colors.textDim}>Enter continue | Esc back</text> </box> </Show> diff --git a/src/tui-solid/components/modals/conflict-resolver.tsx b/src/tui-solid/components/modals/conflict-resolver.tsx index f998177..1a5fd57 100644 --- a/src/tui-solid/components/modals/conflict-resolver.tsx +++ b/src/tui-solid/components/modals/conflict-resolver.tsx @@ -4,7 +4,14 @@ * UI component for displaying and resolving file conflicts between agents. */ -import { For, Show, createSignal, createMemo, onMount, onCleanup } from "solid-js"; +import { + For, + Show, + createSignal, + createMemo, + onMount, + onCleanup, +} from "solid-js"; import { TextAttributes } from "@opentui/core"; import { useTheme } from "@tui-solid/context/theme"; import { multiAgentStore } from "@stores/core/multi-agent-store"; @@ -46,7 +53,9 @@ export function ConflictResolver(props: ConflictResolverProps) { onCleanup(unsubscribe); }); - const currentConflict = createMemo(() => conflicts()[selectedConflictIndex()]); + const currentConflict = createMemo( + () => conflicts()[selectedConflictIndex()], + ); const getAgentNames = (agentIds: string[]): string[] => { const state = multiAgentStore.getState(); @@ -61,7 +70,9 @@ export function ConflictResolver(props: ConflictResolverProps) { return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); }; - const selectedStrategy = createMemo(() => STRATEGY_OPTIONS[selectedStrategyIndex()]); + const selectedStrategy = createMemo( + () => STRATEGY_OPTIONS[selectedStrategyIndex()], + ); return ( <Show when={visible() && conflicts().length > 0}> @@ -73,7 +84,11 @@ export function ConflictResolver(props: ConflictResolverProps) { backgroundColor={theme.colors.background} > {/* Header */} - <box flexDirection="row" justifyContent="space-between" marginBottom={1}> + <box + flexDirection="row" + justifyContent="space-between" + marginBottom={1} + > <text fg={theme.colors.warning} attributes={TextAttributes.BOLD}> ⚠ File Conflict Detected </text> @@ -95,7 +110,9 @@ export function ConflictResolver(props: ConflictResolverProps) { <box flexDirection="row" gap={1}> <text fg={theme.colors.textDim}>Agents:</text> <text fg={theme.colors.text}> - {getAgentNames(currentConflict()!.conflictingAgentIds).join(" vs ")} + {getAgentNames(currentConflict()!.conflictingAgentIds).join( + " vs ", + )} </text> </box> @@ -119,15 +136,33 @@ export function ConflictResolver(props: ConflictResolverProps) { <box flexDirection="row" gap={1} - backgroundColor={index() === selectedStrategyIndex() ? theme.colors.bgHighlight : undefined} + backgroundColor={ + index() === selectedStrategyIndex() + ? theme.colors.bgHighlight + : undefined + } paddingLeft={1} > - <text fg={index() === selectedStrategyIndex() ? theme.colors.primary : theme.colors.textDim}> + <text + fg={ + index() === selectedStrategyIndex() + ? theme.colors.primary + : theme.colors.textDim + } + > {index() === selectedStrategyIndex() ? "▸" : " "} </text> <text - fg={index() === selectedStrategyIndex() ? theme.colors.text : theme.colors.textDim} - attributes={index() === selectedStrategyIndex() ? TextAttributes.BOLD : TextAttributes.NONE} + fg={ + index() === selectedStrategyIndex() + ? theme.colors.text + : theme.colors.textDim + } + attributes={ + index() === selectedStrategyIndex() + ? TextAttributes.BOLD + : TextAttributes.NONE + } > {option.label} </text> @@ -152,7 +187,7 @@ export function ConflictResolver(props: ConflictResolverProps) { {/* Actions */} <box flexDirection="row" gap={2} justifyContent="flex-end"> <text fg={theme.colors.textDim}> - [↑/↓] Select [Enter] Resolve [Esc] Dismiss + [↑/↓] Select [Enter] Resolve [Esc] Dismiss </text> </box> </box> @@ -169,7 +204,9 @@ export function ConflictIndicator() { onMount(() => { const unsubscribe = multiAgentStore.subscribe((state) => { - const unresolvedCount = state.conflicts.filter((c) => !c.resolution).length; + const unresolvedCount = state.conflicts.filter( + (c) => !c.resolution, + ).length; setConflictCount(unresolvedCount); }); diff --git a/src/tui-solid/components/panels/help-detail.tsx b/src/tui-solid/components/panels/help-detail.tsx index 17f17cd..085301b 100644 --- a/src/tui-solid/components/panels/help-detail.tsx +++ b/src/tui-solid/components/panels/help-detail.tsx @@ -78,7 +78,7 @@ export function HelpDetail(props: HelpDetailProps) { <text fg={theme.colors.warning} attributes={TextAttributes.BOLD}> Usage </text> - <text fg={theme.colors.success}> {currentTopic.usage}</text> + <text fg={theme.colors.success}> {currentTopic.usage}</text> </Show> <Show when={currentTopic.examples && currentTopic.examples.length > 0}> @@ -87,9 +87,7 @@ export function HelpDetail(props: HelpDetailProps) { Examples </text> <For each={currentTopic.examples}> - {(example) => ( - <text fg={theme.colors.text}> • {example}</text> - )} + {(example) => <text fg={theme.colors.text}> • {example}</text>} </For> </Show> @@ -99,17 +97,13 @@ export function HelpDetail(props: HelpDetailProps) { Shortcuts </text> <For each={currentTopic.shortcuts}> - {(shortcut) => ( - <text fg={theme.colors.primary}> {shortcut}</text> - )} + {(shortcut) => <text fg={theme.colors.primary}> {shortcut}</text>} </For> </Show> <box height={1} /> - <text fg={theme.colors.textDim}> - Esc/Backspace back | Enter close - </text> + <text fg={theme.colors.textDim}>Esc/Backspace back | Enter close</text> </box> ); } diff --git a/src/tui-solid/components/panels/multi-agent-panel.tsx b/src/tui-solid/components/panels/multi-agent-panel.tsx index f4f2bac..341cf38 100644 --- a/src/tui-solid/components/panels/multi-agent-panel.tsx +++ b/src/tui-solid/components/panels/multi-agent-panel.tsx @@ -4,7 +4,14 @@ * Displays active agents, their status, and execution progress. */ -import { For, Show, createMemo, createSignal, onMount, onCleanup } from "solid-js"; +import { + For, + Show, + createMemo, + createSignal, + onMount, + onCleanup, +} from "solid-js"; import { TextAttributes } from "@opentui/core"; import { useTheme } from "@tui-solid/context/theme"; import { multiAgentStore } from "@stores/core/multi-agent-store"; @@ -45,7 +52,9 @@ export function MultiAgentPanel(props: MultiAgentPanelProps) { running: all.filter((i) => i.status === "running").length, waiting: all.filter((i) => i.status === "waiting_conflict").length, completed: all.filter((i) => i.status === "completed").length, - failed: all.filter((i) => i.status === "error" || i.status === "cancelled").length, + failed: all.filter( + (i) => i.status === "error" || i.status === "cancelled", + ).length, total: all.length, }; }); @@ -107,24 +116,16 @@ export function MultiAgentPanel(props: MultiAgentPanelProps) { {/* Status Summary */} <box flexDirection="row" gap={1} marginBottom={1}> <Show when={stats().running > 0}> - <text fg={theme.colors.info}> - ● {stats().running} - </text> + <text fg={theme.colors.info}>● {stats().running}</text> </Show> <Show when={stats().waiting > 0}> - <text fg={theme.colors.warning}> - ⏸ {stats().waiting} - </text> + <text fg={theme.colors.warning}>⏸ {stats().waiting}</text> </Show> <Show when={stats().completed > 0}> - <text fg={theme.colors.success}> - ✓ {stats().completed} - </text> + <text fg={theme.colors.success}>✓ {stats().completed}</text> </Show> <Show when={stats().failed > 0}> - <text fg={theme.colors.error}> - ✗ {stats().failed} - </text> + <text fg={theme.colors.error}>✗ {stats().failed}</text> </Show> </box> @@ -136,7 +137,11 @@ export function MultiAgentPanel(props: MultiAgentPanelProps) { <box flexDirection="column" marginBottom={1} - backgroundColor={index() === selectedIndex() ? theme.colors.bgHighlight : undefined} + backgroundColor={ + index() === selectedIndex() + ? theme.colors.bgHighlight + : undefined + } paddingLeft={1} paddingRight={1} > diff --git a/src/tui-solid/components/submenu/mode-select.tsx b/src/tui-solid/components/submenu/mode-select.tsx index 212b8fa..fc536b1 100644 --- a/src/tui-solid/components/submenu/mode-select.tsx +++ b/src/tui-solid/components/submenu/mode-select.tsx @@ -131,7 +131,7 @@ export function ModeSelect(props: ModeSelectProps) { )} </box> <box> - <text fg={theme.colors.textDim}> {mode.description}</text> + <text fg={theme.colors.textDim}> {mode.description}</text> </box> </box> ); diff --git a/src/tui-solid/components/submenu/provider-select.tsx b/src/tui-solid/components/submenu/provider-select.tsx index 9d424b2..9018db6 100644 --- a/src/tui-solid/components/submenu/provider-select.tsx +++ b/src/tui-solid/components/submenu/provider-select.tsx @@ -3,7 +3,7 @@ import { useKeyboard } from "@opentui/solid"; import { TextAttributes } from "@opentui/core"; import { useTheme } from "@tui-solid/context/theme"; import { useAppStore } from "@tui-solid/context/app"; -import type { ProviderStatus } from "@services/cascading-provider"; +import type { ProviderStatus } from "@services/cascading-provider/availability"; interface ProviderOption { id: string; @@ -217,7 +217,11 @@ export function ProviderSelect(props: ProviderSelectProps) { <box flexDirection="row" marginLeft={4}> <text fg={theme.colors.textDim}>{provider.description}</text> </box> - <Show when={provider.id === "ollama" && provider.score !== undefined}> + <Show + when={ + provider.id === "ollama" && provider.score !== undefined + } + > <box flexDirection="row" marginLeft={4}> <text fg={theme.colors.textDim}>Quality Score: </text> <text fg={getScoreColor(provider.score)}> diff --git a/src/tui-solid/context/app.tsx b/src/tui-solid/context/app.tsx index 25a9eed..9f7a4eb 100644 --- a/src/tui-solid/context/app.tsx +++ b/src/tui-solid/context/app.tsx @@ -190,7 +190,10 @@ interface AppContextValue { // MCP actions setMcpServers: (servers: MCPServerDisplay[]) => void; addMcpServer: (server: MCPServerDisplay) => void; - updateMcpServerStatus: (id: string, status: MCPServerDisplay["status"]) => void; + updateMcpServerStatus: ( + id: string, + status: MCPServerDisplay["status"], + ) => void; // Computed isInputLocked: () => boolean; @@ -309,7 +312,8 @@ export const { provider: AppStoreProvider, use: useAppStore } = // Individual property accessors for fine-grained reactivity const streamingLogId = (): string | null => store.streamingLog.logId; const streamingLogContent = (): string => store.streamingLog.content; - const streamingLogIsActive = (): boolean => store.streamingLog.isStreaming; + const streamingLogIsActive = (): boolean => + store.streamingLog.isStreaming; const suggestions = (): SuggestionState => store.suggestions; const cascadeEnabled = (): boolean => store.cascadeEnabled; const mcpServers = (): MCPServerDisplay[] => store.mcpServers; @@ -520,7 +524,10 @@ export const { provider: AppStoreProvider, use: useAppStore } = setStore("brain", { ...store.brain, user }); }; - const setBrainCounts = (knowledgeCount: number, memoryCount: number): void => { + const setBrainCounts = ( + knowledgeCount: number, + memoryCount: number, + ): void => { setStore("brain", { ...store.brain, knowledgeCount, memoryCount }); }; @@ -541,7 +548,9 @@ export const { provider: AppStoreProvider, use: useAppStore } = setStore( produce((s) => { // Replace if exists, otherwise add - const existingIndex = s.mcpServers.findIndex((srv) => srv.id === server.id); + const existingIndex = s.mcpServers.findIndex( + (srv) => srv.id === server.id, + ); if (existingIndex !== -1) { s.mcpServers[existingIndex] = server; } else { @@ -551,7 +560,10 @@ export const { provider: AppStoreProvider, use: useAppStore } = ); }; - const updateMcpServerStatus = (id: string, status: MCPServerDisplay["status"]): void => { + const updateMcpServerStatus = ( + id: string, + status: MCPServerDisplay["status"], + ): void => { setStore( produce((s) => { const server = s.mcpServers.find((srv) => srv.id === id); @@ -1193,7 +1205,10 @@ export const appStore = { storeRef.addMcpServer(server); }, - updateMcpServerStatus: (id: string, status: MCPServerDisplay["status"]): void => { + updateMcpServerStatus: ( + id: string, + status: MCPServerDisplay["status"], + ): void => { if (!storeRef) return; storeRef.updateMcpServerStatus(id, status); }, diff --git a/src/tui-solid/routes/session.tsx b/src/tui-solid/routes/session.tsx index 87c6005..561595c 100644 --- a/src/tui-solid/routes/session.tsx +++ b/src/tui-solid/routes/session.tsx @@ -1,4 +1,11 @@ -import { Show, Switch, Match, createSignal, createMemo, onMount } from "solid-js"; +import { + Show, + Switch, + Match, + createSignal, + createMemo, + onMount, +} from "solid-js"; import { useTheme } from "@tui-solid/context/theme"; import { useAppStore } from "@tui-solid/context/app"; import { Header } from "@tui-solid/components/layout/header"; @@ -23,8 +30,13 @@ import { CenteredModal } from "@tui-solid/components/modals/centered-modal"; import { DebugLogPanel } from "@tui-solid/components/logs/debug-log-panel"; import { BrainMenu } from "@tui-solid/components/menu/brain-menu"; import { BRAIN_DISABLED } from "@constants/brain"; -import { initializeMCP, getServerInstances } from "@services/mcp"; -import type { PermissionScope, LearningScope, InteractionMode, MCPServerDisplay } from "@/types/tui"; +import { initializeMCP, getServerInstances } from "@services/mcp/manager"; +import type { + PermissionScope, + LearningScope, + InteractionMode, + MCPServerDisplay, +} from "@/types/tui"; import type { MCPAddFormData } from "@/types/mcp"; interface AgentOption { @@ -93,8 +105,12 @@ export function Session(props: SessionProps) { servers.push({ id, name: instance.config.name || id, - status: instance.state === "connected" ? "connected" : - instance.state === "error" ? "error" : "disconnected", + status: + instance.state === "connected" + ? "connected" + : instance.state === "error" + ? "error" + : "disconnected", description: instance.config.command, }); } @@ -112,7 +128,7 @@ export function Session(props: SessionProps) { // Local state for help menu const [selectedHelpTopic, setSelectedHelpTopic] = createSignal<string | null>( - null + null, ); const handleCommandSelect = (command: string): void => { @@ -142,7 +158,11 @@ export function Session(props: SessionProps) { app.transitionFromCommandMenu("provider_select"); return; } - if (lowerCommand === "help" || lowerCommand === "h" || lowerCommand === "?") { + if ( + lowerCommand === "help" || + lowerCommand === "h" || + lowerCommand === "?" + ) { app.transitionFromCommandMenu("help_menu"); return; } diff --git a/src/types/brain-cloud.ts b/src/types/brain-cloud.ts index b0dbc17..725923a 100644 --- a/src/types/brain-cloud.ts +++ b/src/types/brain-cloud.ts @@ -32,11 +32,7 @@ export type SyncDirection = "push" | "pull" | "both"; /** * Sync operation type */ -export type SyncOperationType = - | "create" - | "update" - | "delete" - | "conflict"; +export type SyncOperationType = "create" | "update" | "delete" | "conflict"; /** * Brain sync state diff --git a/src/types/brain-mcp.ts b/src/types/brain-mcp.ts index df1ac2a..e20d2c9 100644 --- a/src/types/brain-mcp.ts +++ b/src/types/brain-mcp.ts @@ -124,12 +124,20 @@ export const DEFAULT_BRAIN_MCP_SERVER_CONFIG: BrainMcpServerConfig = { export const BRAIN_MCP_TOOLS: ReadonlyArray<BrainMcpTool> = [ { name: "brain_recall", - description: "Retrieve relevant concepts from the knowledge graph based on a query", + description: + "Retrieve relevant concepts from the knowledge graph based on a query", inputSchema: { type: "object", properties: { - query: { type: "string", description: "The search query to find relevant concepts" }, - limit: { type: "number", description: "Maximum number of concepts to return", default: 5 }, + query: { + type: "string", + description: "The search query to find relevant concepts", + }, + limit: { + type: "number", + description: "Maximum number of concepts to return", + default: 5, + }, }, required: ["query"], }, @@ -142,10 +150,25 @@ export const BRAIN_MCP_TOOLS: ReadonlyArray<BrainMcpTool> = [ type: "object", properties: { name: { type: "string", description: "The name of the concept" }, - whatItDoes: { type: "string", description: "Description of what the concept does" }, - keywords: { type: "array", items: { type: "string", description: "Keyword item" }, description: "Keywords for the concept" }, - patterns: { type: "array", items: { type: "string", description: "Pattern item" }, description: "Code patterns related to the concept" }, - files: { type: "array", items: { type: "string", description: "File path item" }, description: "Files related to the concept" }, + whatItDoes: { + type: "string", + description: "Description of what the concept does", + }, + keywords: { + type: "array", + items: { type: "string", description: "Keyword item" }, + description: "Keywords for the concept", + }, + patterns: { + type: "array", + items: { type: "string", description: "Pattern item" }, + description: "Code patterns related to the concept", + }, + files: { + type: "array", + items: { type: "string", description: "File path item" }, + description: "Files related to the concept", + }, }, required: ["name", "whatItDoes"], }, @@ -158,8 +181,16 @@ export const BRAIN_MCP_TOOLS: ReadonlyArray<BrainMcpTool> = [ type: "object", properties: { query: { type: "string", description: "The search query" }, - limit: { type: "number", description: "Maximum number of memories to return", default: 10 }, - type: { type: "string", description: "Memory type filter", enum: ["fact", "pattern", "correction", "preference", "context"] }, + limit: { + type: "number", + description: "Maximum number of memories to return", + default: 10, + }, + type: { + type: "string", + description: "Memory type filter", + enum: ["fact", "pattern", "correction", "preference", "context"], + }, }, required: ["query"], }, @@ -171,10 +202,32 @@ export const BRAIN_MCP_TOOLS: ReadonlyArray<BrainMcpTool> = [ inputSchema: { type: "object", properties: { - sourceConcept: { type: "string", description: "Name of the source concept" }, - targetConcept: { type: "string", description: "Name of the target concept" }, - relationType: { type: "string", description: "Type of relationship", enum: ["depends_on", "uses", "extends", "similar_to", "part_of", "implements", "contradicts"] }, - weight: { type: "number", description: "Strength of the relationship (0-1)", default: 0.5 }, + sourceConcept: { + type: "string", + description: "Name of the source concept", + }, + targetConcept: { + type: "string", + description: "Name of the target concept", + }, + relationType: { + type: "string", + description: "Type of relationship", + enum: [ + "depends_on", + "uses", + "extends", + "similar_to", + "part_of", + "implements", + "contradicts", + ], + }, + weight: { + type: "number", + description: "Strength of the relationship (0-1)", + default: 0.5, + }, }, required: ["sourceConcept", "targetConcept", "relationType"], }, @@ -182,12 +235,17 @@ export const BRAIN_MCP_TOOLS: ReadonlyArray<BrainMcpTool> = [ }, { name: "brain_context", - description: "Build a context string from relevant knowledge for prompt injection", + description: + "Build a context string from relevant knowledge for prompt injection", inputSchema: { type: "object", properties: { query: { type: "string", description: "The context query" }, - maxConcepts: { type: "number", description: "Maximum concepts to include", default: 5 }, + maxConcepts: { + type: "number", + description: "Maximum concepts to include", + default: 5, + }, }, required: ["query"], }, diff --git a/src/types/confidence-filter.ts b/src/types/confidence-filter.ts index 2b3cc0c..1e9ac64 100644 --- a/src/types/confidence-filter.ts +++ b/src/types/confidence-filter.ts @@ -46,7 +46,10 @@ export interface ConfidenceFilterStats { readonly averageConfidence: number; } -export const CONFIDENCE_LEVELS: Record<ConfidenceLevel, { min: number; max: number; color: string }> = { +export const CONFIDENCE_LEVELS: Record< + ConfidenceLevel, + { min: number; max: number; color: string } +> = { low: { min: 0, max: 49, color: "gray" }, medium: { min: 50, max: 74, color: "yellow" }, high: { min: 75, max: 89, color: "green" }, diff --git a/src/types/feature-dev.ts b/src/types/feature-dev.ts index bb9d9c7..50ec247 100644 --- a/src/types/feature-dev.ts +++ b/src/types/feature-dev.ts @@ -8,13 +8,13 @@ * Feature development phases */ export type FeatureDevPhase = - | "understand" // Clarify requirements - | "explore" // Find relevant code (parallel agents) - | "plan" // Design implementation - | "implement" // Write code - | "verify" // Run tests - | "review" // Self-review changes - | "finalize"; // Commit and cleanup + | "understand" // Clarify requirements + | "explore" // Find relevant code (parallel agents) + | "plan" // Design implementation + | "implement" // Write code + | "verify" // Run tests + | "review" // Self-review changes + | "finalize"; // Commit and cleanup /** * Phase status diff --git a/src/types/github-pr.ts b/src/types/github-pr.ts index 463f1bd..f6218ec 100644 --- a/src/types/github-pr.ts +++ b/src/types/github-pr.ts @@ -14,13 +14,23 @@ export interface GitHubPRComment { line?: number; createdAt: string; diffHunk?: string; - state?: "PENDING" | "COMMENTED" | "APPROVED" | "CHANGES_REQUESTED" | "DISMISSED"; + state?: + | "PENDING" + | "COMMENTED" + | "APPROVED" + | "CHANGES_REQUESTED" + | "DISMISSED"; } export interface GitHubPRReview { id: number; author: string; - state: "PENDING" | "COMMENTED" | "APPROVED" | "CHANGES_REQUESTED" | "DISMISSED"; + state: + | "PENDING" + | "COMMENTED" + | "APPROVED" + | "CHANGES_REQUESTED" + | "DISMISSED"; body: string; submittedAt: string; comments: GitHubPRComment[]; diff --git a/src/types/image.ts b/src/types/image.ts index 83bf4fb..77fe886 100644 --- a/src/types/image.ts +++ b/src/types/image.ts @@ -2,7 +2,11 @@ * Image types for multimodal message support */ -export type ImageMediaType = "image/png" | "image/jpeg" | "image/gif" | "image/webp"; +export type ImageMediaType = + | "image/png" + | "image/jpeg" + | "image/gif" + | "image/webp"; export interface ImageContent { type: "image"; @@ -43,11 +47,15 @@ export interface PastedImage { timestamp: number; } -export const isImageContent = (content: MessageContent): content is ImageContent => { +export const isImageContent = ( + content: MessageContent, +): content is ImageContent => { return content.type === "image"; }; -export const isTextContent = (content: MessageContent): content is TextContent => { +export const isTextContent = ( + content: MessageContent, +): content is TextContent => { return content.type === "text"; }; diff --git a/src/types/multi-agent.ts b/src/types/multi-agent.ts index 56884ae..7ad0682 100644 --- a/src/types/multi-agent.ts +++ b/src/types/multi-agent.ts @@ -12,29 +12,29 @@ import type { ChatMessage } from "@/types/common"; * Agent execution modes */ export type AgentExecutionMode = - | "sequential" // Execute agents one after another - | "parallel" // Execute all agents concurrently - | "adaptive"; // Start parallel, serialize on conflict + | "sequential" // Execute agents one after another + | "parallel" // Execute all agents concurrently + | "adaptive"; // Start parallel, serialize on conflict /** * Conflict resolution strategies */ export type ConflictStrategy = - | "serialize" // Wait for conflicting agent to complete - | "abort-newer" // Abort the newer agent - | "merge-results" // Attempt to merge both results - | "isolated"; // Each agent works in isolated context + | "serialize" // Wait for conflicting agent to complete + | "abort-newer" // Abort the newer agent + | "merge-results" // Attempt to merge both results + | "isolated"; // Each agent works in isolated context /** * Agent instance status */ export type AgentInstanceStatus = - | "pending" // Waiting to start - | "running" // Actively executing - | "waiting_conflict" // Paused due to conflict - | "completed" // Successfully finished - | "error" // Failed with error - | "cancelled"; // Cancelled by user or system + | "pending" // Waiting to start + | "running" // Actively executing + | "waiting_conflict" // Paused due to conflict + | "completed" // Successfully finished + | "error" // Failed with error + | "cancelled"; // Cancelled by user or system /** * Tool call record for agent conversation @@ -168,11 +168,20 @@ export interface MultiAgentState { */ export type MultiAgentEvent = | { type: "agent_started"; agentId: string; timestamp: number } - | { type: "agent_completed"; agentId: string; result: AgentExecutionResult; timestamp: number } + | { + type: "agent_completed"; + agentId: string; + result: AgentExecutionResult; + timestamp: number; + } | { type: "agent_error"; agentId: string; error: string; timestamp: number } | { type: "conflict_detected"; conflict: FileConflict; timestamp: number } | { type: "conflict_resolved"; conflict: FileConflict; timestamp: number } - | { type: "execution_completed"; result: MultiAgentResult; timestamp: number }; + | { + type: "execution_completed"; + result: MultiAgentResult; + timestamp: number; + }; /** * Multi-agent executor options diff --git a/src/types/parallel.ts b/src/types/parallel.ts index d17536d..90e50ea 100644 --- a/src/types/parallel.ts +++ b/src/types/parallel.ts @@ -9,10 +9,10 @@ * Task type for parallel execution */ export type ParallelTaskType = - | "explore" // Read-only exploration - | "analyze" // Analysis without modification - | "execute" // May modify files - | "search"; // Search operations + | "explore" // Read-only exploration + | "analyze" // Analysis without modification + | "execute" // May modify files + | "search"; // Search operations /** * Task priority levels @@ -86,10 +86,10 @@ export interface ConflictCheckResult { * Conflict resolution strategy */ export type ConflictResolution = - | "wait" // Wait for conflicting task to complete - | "cancel" // Cancel conflicting task - | "merge" // Attempt to merge results - | "abort"; // Abort this task + | "wait" // Wait for conflicting task to complete + | "cancel" // Cancel conflicting task + | "merge" // Attempt to merge results + | "abort"; // Abort this task /** * Resource limits for parallel execution @@ -132,7 +132,10 @@ export interface ParallelExecutorOptions { onTaskStart?: (task: ParallelTask) => void; onTaskComplete?: (result: ParallelExecutionResult) => void; onTaskError?: (task: ParallelTask, error: Error) => void; - onConflict?: (task: ParallelTask, conflict: ConflictCheckResult) => ConflictResolution; + onConflict?: ( + task: ParallelTask, + conflict: ConflictCheckResult, + ) => ConflictResolution; abortSignal?: AbortSignal; } diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 539d40b..ffe6734 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -65,11 +65,7 @@ export interface PluginHookReference { /** * Plugin capabilities */ -export type PluginCapability = - | "filesystem" - | "network" - | "shell" - | "mcp"; +export type PluginCapability = "filesystem" | "network" | "shell" | "mcp"; /** * Tool definition from a plugin diff --git a/src/types/pr-review.ts b/src/types/pr-review.ts index 48d6583..9ab0170 100644 --- a/src/types/pr-review.ts +++ b/src/types/pr-review.ts @@ -18,11 +18,7 @@ export type ReviewFindingType = /** * Review finding severity */ -export type ReviewSeverity = - | "critical" - | "warning" - | "suggestion" - | "nitpick"; +export type ReviewSeverity = "critical" | "warning" | "suggestion" | "nitpick"; /** * Confidence level for findings diff --git a/src/types/skills.ts b/src/types/skills.ts index e71b4a7..c16161c 100644 --- a/src/types/skills.ts +++ b/src/types/skills.ts @@ -14,10 +14,10 @@ export type SkillLoadLevel = "metadata" | "body" | "full"; * Skill trigger types */ export type SkillTriggerType = - | "command" // /commit, /review - | "pattern" // "commit changes", "review PR" - | "auto" // Automatically triggered based on context - | "explicit"; // Only when explicitly invoked + | "command" // /commit, /review + | "pattern" // "commit changes", "review PR" + | "auto" // Automatically triggered based on context + | "explicit"; // Only when explicitly invoked /** * Example for a skill diff --git a/src/types/usage.ts b/src/types/usage.ts index f57e4b7..b9f865a 100644 --- a/src/types/usage.ts +++ b/src/types/usage.ts @@ -29,7 +29,11 @@ export interface ContextWindowState { model: string; } -export type ContextWindowStatus = "normal" | "warning" | "critical" | "compacting"; +export type ContextWindowStatus = + | "normal" + | "warning" + | "critical" + | "compacting"; /** * Calculate context window state from usage stats diff --git a/src/ui/banner.test.ts b/src/ui/banner.test.ts index 602a836..3931988 100644 --- a/src/ui/banner.test.ts +++ b/src/ui/banner.test.ts @@ -2,7 +2,11 @@ import { getBannerLines } from "./banner/lines"; import { renderBanner, renderBannerWithSubtitle } from "./banner/render"; import { printBanner, printWelcome } from "./banner/print"; import { getInlineLogo } from "./banner/logo"; -import { BANNER_STYLE_MAP, BANNER_LINES, GRADIENT_COLORS } from "@constants/banner"; +import { + BANNER_STYLE_MAP, + BANNER_LINES, + GRADIENT_COLORS, +} from "@constants/banner"; import { Style } from "@ui/styles"; describe("Banner Utilities", () => { @@ -39,11 +43,13 @@ describe("Banner Utilities", () => { it("should render banner with a specific style", () => { const style = "blocks"; const banner = renderBanner(style); - const expectedLines = BANNER_STYLE_MAP[style].map((line, index) => { - const colorIndex = Math.min(index, GRADIENT_COLORS.length - 1); - const color = GRADIENT_COLORS[colorIndex]; - return color + line + Style.RESET; - }).join("\n"); + const expectedLines = BANNER_STYLE_MAP[style] + .map((line, index) => { + const colorIndex = Math.min(index, GRADIENT_COLORS.length - 1); + const color = GRADIENT_COLORS[colorIndex]; + return color + line + Style.RESET; + }) + .join("\n"); expect(banner).toBe(expectedLines); }); @@ -63,7 +69,9 @@ describe("Banner Utilities", () => { describe("printBanner", () => { it("should print the banner to the console", () => { - const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); + const consoleSpy = jest + .spyOn(console, "log") + .mockImplementation(() => {}); const style = "default"; printBanner(style); @@ -75,7 +83,9 @@ describe("Banner Utilities", () => { describe("printWelcome", () => { it("should print the welcome message to the console", () => { - const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {}); + const consoleSpy = jest + .spyOn(console, "log") + .mockImplementation(() => {}); const version = "1.0.0"; const provider = "OpenAI"; const model = "GPT-4"; @@ -84,10 +94,12 @@ describe("Banner Utilities", () => { expect(consoleSpy).toHaveBeenCalledWith("\n" + renderBanner("blocks")); expect(consoleSpy).toHaveBeenCalledWith(""); - expect(consoleSpy).toHaveBeenCalledWith(Style.DIM + " AI Coding Assistant" + Style.RESET); + expect(consoleSpy).toHaveBeenCalledWith( + Style.DIM + " AI Coding Assistant" + Style.RESET, + ); expect(consoleSpy).toHaveBeenCalledWith(""); expect(consoleSpy).toHaveBeenCalledWith( - Style.DIM + ` v${version} | ${provider} | ${model}` + Style.RESET + Style.DIM + ` v${version} | ${provider} | ${model}` + Style.RESET, ); expect(consoleSpy).toHaveBeenCalledWith(""); @@ -102,4 +114,4 @@ describe("Banner Utilities", () => { expect(logo).toBe(expectedLogo); }); }); -}); \ No newline at end of file +}); diff --git a/src/ui/input-editor/keypress.ts b/src/ui/input-editor/keypress.ts index 73f5781..4998010 100644 --- a/src/ui/input-editor/keypress.ts +++ b/src/ui/input-editor/keypress.ts @@ -142,7 +142,9 @@ const getKeyName = (key: readline.Key): string => { /** * Handle bracketed paste content - called from editor when paste ends */ -export const handleBracketedPaste = (state: InputEditorState): KeypressResult => { +export const handleBracketedPaste = ( + state: InputEditorState, +): KeypressResult => { if (state.bracketedPasteBuffer.length === 0) { return { type: "none" }; } diff --git a/src/utils/core/string-helpers.ts b/src/utils/core/string-helpers.ts index 0029975..703db1c 100644 --- a/src/utils/core/string-helpers.ts +++ b/src/utils/core/string-helpers.ts @@ -1,4 +1,6 @@ // Utility function to capitalize the first letter of each word in a string export function capitalizeWords(input: string): string { - return input.replace(/\b\w/g, (char) => char.toUpperCase()).replace(/_\w/g, (char) => char.toUpperCase()); -} \ No newline at end of file + return input + .replace(/\b\w/g, (char) => char.toUpperCase()) + .replace(/_\w/g, (char) => char.toUpperCase()); +}