fixing imports

This commit is contained in:
2026-02-04 21:32:30 -05:00
parent 74b0a0dbab
commit db79856b08
166 changed files with 1986 additions and 982 deletions

View File

@@ -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";
/**

View File

@@ -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";

View File

@@ -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 = (

View File

@@ -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<typeof appStore.setMode>[0]);

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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 (

View File

@@ -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";

View File

@@ -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<void> => {
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<void> => {
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();

View File

@@ -8,7 +8,7 @@ import {
getServerInstances,
getAllTools,
isMCPAvailable,
} from "@services/mcp/index";
} from "@services/mcp/manager";
/**
* Display MCP server status

View File

@@ -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,

View File

@@ -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,

View File

@@ -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<void> => {
const config = await getConfig();

View File

@@ -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";

View File

@@ -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 (

View File

@@ -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,

View File

@@ -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;

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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,

View File

@@ -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<void> => {
await executeChat(options);

View File

@@ -18,7 +18,7 @@ import type {
CommandOptions,
IntentRequest,
IntentResponse,
} from "@/types/index";
} from "@/types/common";
const classifyIntent = async (
request: IntentRequest,

View File

@@ -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<void>;

View File

@@ -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<void> => {
const { intent, task, files = [], output } = options;

View File

@@ -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<void> => {
await execute(options);

View File

@@ -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<void> => {

View File

@@ -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,

View File

@@ -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

View File

@@ -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,

View File

@@ -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;

View File

@@ -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<void> => {

View File

@@ -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 => {

View File

@@ -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",

View File

@@ -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.",

View File

@@ -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",

View File

@@ -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",
},
{

View File

@@ -41,12 +41,13 @@ export const EXECUTION_MODE_DESCRIPTIONS: Record<AgentExecutionMode, string> = {
/**
* Conflict strategy descriptions
*/
export const CONFLICT_STRATEGY_DESCRIPTIONS: Record<ConflictStrategy, string> = {
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<ConflictStrategy, string> =
{
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) =>

View File

@@ -68,12 +68,13 @@ export const PLUGIN_CAPABILITY_LABELS: Record<PluginCapability, string> = {
/**
* Plugin capability descriptions
*/
export const PLUGIN_CAPABILITY_DESCRIPTIONS: Record<PluginCapability, string> = {
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<PluginCapability, string> =
{
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

View File

@@ -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;
/**

View File

@@ -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;

View File

@@ -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)

View File

@@ -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",

View File

@@ -41,27 +41,101 @@ export const VIM_MODE_HINTS: Record<VimMode, string> = {
*/
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",
},
];
/**

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 });
}
}

View File

@@ -68,7 +68,10 @@ export const ollamaChatStream = async (
): Promise<void> => {
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,

View File

@@ -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<string, unknown>; body: string } | null => {
const parseFrontmatter = (
content: string,
): { frontmatter: Record<string, unknown>; body: string } | null => {
const delimiter = AGENT_DEFINITION.FRONTMATTER_DELIMITER;
const lines = content.split("\n");
@@ -27,13 +36,18 @@ const parseFrontmatter = (content: string): { frontmatter: Record<string, unknow
return null;
}
const endIndex = lines.findIndex((line, index) => 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<string, unknown> = {};
@@ -85,7 +99,9 @@ const parseFrontmatter = (content: string): { frontmatter: Record<string, unknow
return { frontmatter, body };
};
const validateFrontmatter = (frontmatter: Record<string, unknown>): AgentFrontmatter | null => {
const validateFrontmatter = (
frontmatter: Record<string, unknown>,
): AgentFrontmatter | null => {
const { required } = AGENT_DEFINITION_SCHEMA;
for (const field of required) {
@@ -98,7 +114,11 @@ const validateFrontmatter = (frontmatter: Record<string, unknown>): 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<string, unknown>): AgentFrontma
tools: tools as ReadonlyArray<string>,
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<string>) || [],
capabilities: (frontmatter.capabilities as ReadonlyArray<string>) || [],
allowedPaths: frontmatter.allowedPaths as ReadonlyArray<string> | undefined,
@@ -116,7 +137,10 @@ const validateFrontmatter = (frontmatter: Record<string, unknown>): 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<AgentLoadResult> => {
export const loadAgentDefinitionFile = async (
filePath: string,
): Promise<AgentLoadResult> => {
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<AgentLo
};
export const loadAgentDefinitionsFromDirectory = async (
directoryPath: string
directoryPath: string,
): Promise<ReadonlyArray<AgentLoadResult>> => {
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<AgentRegistry> => {
const agents = new Map<string, AgentDefinition>();
const byTrigger = new Map<string, string>();
@@ -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<AgentDefinition> => {
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<AgentDefinition> =>
Array.from(registry.agents.values());
export const listAllAgents = (
registry: AgentRegistry,
): ReadonlyArray<AgentDefinition> => 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}`;
};

View File

@@ -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);

View File

@@ -332,7 +332,10 @@ export const connect = async (): Promise<boolean> => {
// 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<boolean> => {
if (!isConnected()) {
return false;

View File

@@ -448,9 +448,7 @@ const pullFromCloud = async (
/**
* Check if pulled item conflicts with local changes
*/
const checkLocalConflict = async (
_item: SyncItem,
): Promise<boolean> => {
const checkLocalConflict = async (_item: SyncItem): Promise<boolean> => {
// Check if we have pending changes for this item
const queued = await hasQueuedItems();
return queued;

View File

@@ -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<ConflictStrategy, (conflict: SyncConflict) => unknown> = {
"local-wins": (conflict) => conflict.localData,
const resolvers: Record<ConflictStrategy, (conflict: SyncConflict) => 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;

View File

@@ -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<unknown>;
learn: (name: string, whatItDoes: string, options?: unknown) => Promise<unknown>;
searchMemories: (query: string, limit?: number, type?: string) => Promise<unknown>;
relate: (source: string, target: string, type: string, weight?: number) => Promise<unknown>;
learn: (
name: string,
whatItDoes: string,
options?: unknown,
) => Promise<unknown>;
searchMemories: (
query: string,
limit?: number,
type?: string,
) => Promise<unknown>;
relate: (
source: string,
target: string,
type: string,
weight?: number,
) => Promise<unknown>;
getContext: (query: string, maxConcepts?: number) => Promise<string>;
getStats: () => Promise<unknown>;
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<McpContent>,
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<string, unknown>
args: Record<string, unknown>,
): Promise<McpContent[]> => {
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<BrainMcpToolName, () => Promise<unknown>> = {
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<void> => {
// 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<BrainMcpServerConfig>
config?: Partial<BrainMcpServerConfig>,
): Promise<void> => {
if (state.server) {
throw new Error(BRAIN_MCP_MESSAGES.SERVER_ALREADY_RUNNING);
@@ -348,5 +428,11 @@ export const updateConfig = (config: Partial<BrainMcpServerConfig>): 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,
}));

View File

@@ -128,7 +128,9 @@ export const enqueueBatch = async (items: SyncItem[]): Promise<number> => {
/**
* Get items from queue for processing
*/
export const dequeue = async (limit: number = SYNC_CONFIG.MAX_BATCH_SIZE): Promise<OfflineQueueItem[]> => {
export const dequeue = async (
limit: number = SYNC_CONFIG.MAX_BATCH_SIZE,
): Promise<OfflineQueueItem[]> => {
await loadQueue();
// Get items that haven't exceeded retry limit

View File

@@ -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<void> => {
state.initialized = true;
};
export const createProject = async (input: BrainProjectCreateInput): Promise<BrainProject> => {
export const createProject = async (
input: BrainProjectCreateInput,
): Promise<BrainProject> => {
await initialize();
// Validate name
@@ -130,7 +138,7 @@ export const createProject = async (input: BrainProjectCreateInput): Promise<Bra
// Check for duplicate names
const existingProject = Array.from(state.projects.values()).find(
(p) => 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<Bra
export const updateProject = async (
projectId: number,
input: BrainProjectUpdateInput
input: BrainProjectUpdateInput,
): Promise<BrainProject> => {
await initialize();
@@ -205,7 +213,9 @@ export const deleteProject = async (projectId: number): Promise<boolean> => {
return true;
};
export const switchProject = async (projectId: number): Promise<BrainProjectSwitchResult> => {
export const switchProject = async (
projectId: number,
): Promise<BrainProjectSwitchResult> => {
await initialize();
const newProject = state.projects.get(projectId);
@@ -219,7 +229,10 @@ export const switchProject = async (projectId: number): Promise<BrainProjectSwit
// Update active status
if (previousProject) {
state.projects.set(previousProject.id, { ...previousProject, isActive: false });
state.projects.set(previousProject.id, {
...previousProject,
isActive: false,
});
}
state.projects.set(projectId, { ...newProject, isActive: true });
@@ -235,35 +248,45 @@ export const switchProject = async (projectId: number): Promise<BrainProjectSwit
};
};
export const getProject = async (projectId: number): Promise<BrainProject | undefined> => {
export const getProject = async (
projectId: number,
): Promise<BrainProject | undefined> => {
await initialize();
return state.projects.get(projectId);
};
export const getActiveProject = async (): Promise<BrainProject | undefined> => {
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<BrainProjectListResult> => {
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<BrainProject | undefined> => {
export const findProjectByPath = async (
rootPath: string,
): Promise<BrainProject | undefined> => {
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<BrainProjectStats>
stats: Partial<BrainProjectStats>,
): Promise<void> => {
await initialize();
@@ -280,7 +303,9 @@ export const updateProjectStats = async (
await saveProjectsToConfig();
};
export const exportProject = async (projectId: number): Promise<BrainProjectExport> => {
export const exportProject = async (
projectId: number,
): Promise<BrainProjectExport> => {
await initialize();
const project = state.projects.get(projectId);
@@ -307,7 +332,7 @@ export const exportProject = async (projectId: number): Promise<BrainProjectExpo
"codetyper",
"brain",
"exports",
`${project.name}-${Date.now()}${BRAIN_PROJECT_STORAGE.EXPORT_EXTENSION}`
`${project.name}-${Date.now()}${BRAIN_PROJECT_STORAGE.EXPORT_EXTENSION}`,
);
await writeFile(exportPath, JSON.stringify(exportData, null, 2));
@@ -316,7 +341,7 @@ export const exportProject = async (projectId: number): Promise<BrainProjectExpo
};
export const importProject = async (
exportData: BrainProjectExport
exportData: BrainProjectExport,
): Promise<BrainProjectImportResult> => {
await initialize();
@@ -352,7 +377,9 @@ export const importProject = async (
}
};
export const getProjectSettings = async (projectId: number): Promise<BrainProjectSettings | undefined> => {
export const getProjectSettings = async (
projectId: number,
): Promise<BrainProjectSettings | undefined> => {
await initialize();
const project = state.projects.get(projectId);
@@ -361,13 +388,15 @@ export const getProjectSettings = async (projectId: number): Promise<BrainProjec
export const updateProjectSettings = async (
projectId: number,
settings: Partial<BrainProjectSettings>
settings: Partial<BrainProjectSettings>,
): Promise<BrainProjectSettings> => {
const project = await updateProject(projectId, { settings });
return project.settings;
};
export const setActiveProjectByPath = async (rootPath: string): Promise<BrainProject | undefined> => {
export const setActiveProjectByPath = async (
rootPath: string,
): Promise<BrainProject | undefined> => {
const project = await findProjectByPath(rootPath);
if (project) {

View File

@@ -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<string>;
(
prompt: string,
provider: "ollama" | "copilot",
isAudit?: boolean,
): Promise<string>;
}
export const executeCascade = async (

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 => {

View File

@@ -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";

View File

@@ -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<string, unknown> }) => {
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) {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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";

View File

@@ -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;
}

View File

@@ -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<PastedImage | null> => {
const readClipboardImageLinux = async (): Promise<PastedImage | null> => {
// 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"] },
];

View File

@@ -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<ConfidenceFactor>): ConfidenceScore => {
export const calculateConfidenceScore = (
factors: ReadonlyArray<ConfidenceFactor>,
): 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<string, number> = { low: 40, medium: 60, high: 80, critical: 95 };
export const createSeverityFactor = (
severity: "low" | "medium" | "high" | "critical",
): ConfidenceFactor => {
const severityScores: Record<string, number> = {
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 = <T>(
items: ReadonlyArray<{ item: T; confidence: ConfidenceScore }>,
config: ConfidenceFilterConfig = DEFAULT_CONFIDENCE_FILTER_CONFIG
config: ConfidenceFilterConfig = DEFAULT_CONFIDENCE_FILTER_CONFIG,
): ReadonlyArray<FilteredResult<T>> =>
items.map(({ item, confidence }) => ({
item,
@@ -100,11 +125,12 @@ export const filterByConfidence = <T>(
passed: confidence.value >= config.minThreshold,
}));
export const filterPassedOnly = <T>(results: ReadonlyArray<FilteredResult<T>>): ReadonlyArray<T> =>
results.filter((r) => r.passed).map((r) => r.item);
export const filterPassedOnly = <T>(
results: ReadonlyArray<FilteredResult<T>>,
): ReadonlyArray<T> => results.filter((r) => r.passed).map((r) => r.item);
export const groupByConfidenceLevel = <T>(
results: ReadonlyArray<FilteredResult<T>>
results: ReadonlyArray<FilteredResult<T>>,
): Record<ConfidenceLevel, ReadonlyArray<FilteredResult<T>>> => ({
low: results.filter((r) => r.confidence.level === "low"),
medium: results.filter((r) => r.confidence.level === "medium"),
@@ -112,10 +138,15 @@ export const groupByConfidenceLevel = <T>(
critical: results.filter((r) => r.confidence.level === "critical"),
});
export const calculateFilterStats = <T>(results: ReadonlyArray<FilteredResult<T>>): ConfidenceFilterStats => {
export const calculateFilterStats = <T>(
results: ReadonlyArray<FilteredResult<T>>,
): 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 = <T>(results: ReadonlyArray<FilteredResult<T>
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<ConfidenceFactor>) => Promise<{ validated: boolean; adjustment: number; notes: string }>
validatorFn: (
factors: ReadonlyArray<ConfidenceFactor>,
) => Promise<{ validated: boolean; adjustment: number; notes: string }>,
): Promise<ValidationResult> => {
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<ConfidenceLevel, string> = {
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<ConfidenceFactor>,
additional: ReadonlyArray<ConfidenceFactor>
additional: ReadonlyArray<ConfidenceFactor>,
): ReadonlyArray<ConfidenceFactor> => {
const factorMap = new Map<string, ConfidenceFactor>();
@@ -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;

View File

@@ -37,7 +37,7 @@ const PROJECT_MARKERS: Record<string, { type: string; language: string }> = {
"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}`);

View File

@@ -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";

View File

@@ -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";
/**

View File

@@ -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";

View File

@@ -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" }),
};

View File

@@ -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}`);
}

View File

@@ -50,9 +50,7 @@ const runGitCommand = (
/**
* Fetch PR details using gh CLI
*/
export const fetchPR = async (
parts: PRUrlParts,
): Promise<GitHubPR | null> => {
export const fetchPR = async (parts: PRUrlParts): Promise<GitHubPR | null> => {
const { owner, repo, prNumber } = parts;
const result = await executeGHCommand([

View File

@@ -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("");

View File

@@ -46,7 +46,9 @@ const hooksCache: HooksCache = {
/**
* Load hooks configuration from a file
*/
const loadHooksFromFile = async (filePath: string): Promise<HookDefinition[]> => {
const loadHooksFromFile = async (
filePath: string,
): Promise<HookDefinition[]> => {
try {
await access(filePath, constants.R_OK);
const content = await readFile(filePath, "utf-8");
@@ -57,7 +59,7 @@ const loadHooksFromFile = async (filePath: string): Promise<HookDefinition[]> =>
}
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<HookResult> => {
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<HookResult> => {
const hooks = getHooksForEvent(event);
@@ -288,7 +291,7 @@ export const executePreToolUseHooks = async (
sessionId: string,
toolName: string,
toolArgs: Record<string, unknown>,
workingDir: string
workingDir: string,
): Promise<HookResult> => {
if (!hooksCache.loaded) {
await loadHooks(workingDir);
@@ -312,7 +315,7 @@ export const executePostToolUseHooks = async (
toolName: string,
toolArgs: Record<string, unknown>,
result: ToolResult,
workingDir: string
workingDir: string,
): Promise<void> => {
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<void> => {
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<void> => {
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<HookResult> => {
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<void> => {
if (!hooksCache.loaded) {
await loadHooks(workingDir);

View File

@@ -49,7 +49,10 @@ export interface DocumentSymbol {
}
export interface Hover {
contents: string | { kind: string; value: string } | Array<string | { kind: string; value: string }>;
contents:
| string
| { kind: string; value: string }
| Array<string | { kind: string; value: string }>;
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<void> {
if (this.initialized) return;
const result = await this.request<{ capabilities: Record<string, unknown> }>("initialize", {
const result = await this.request<{
capabilities: Record<string, unknown>;
}>("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<Location | Location[] | null> {
async getDefinition(
filePath: string,
position: Position,
): Promise<Location | Location[] | null> {
const uri = `file://${filePath}`;
try {
return await this.request<Location | Location[] | null>("textDocument/definition", {
textDocument: { uri },
position,
});
return await this.request<Location | Location[] | null>(
"textDocument/definition",
{
textDocument: { uri },
position,
},
);
} catch {
return null;
}
}
async getReferences(filePath: string, position: Position, includeDeclaration = true): Promise<Location[]> {
async getReferences(
filePath: string,
position: Position,
includeDeclaration = true,
): Promise<Location[]> {
const uri = `file://${filePath}`;
try {
const result = await this.request<Location[] | null>("textDocument/references", {
textDocument: { uri },
position,
context: { includeDeclaration },
});
const result = await this.request<Location[] | null>(
"textDocument/references",
{
textDocument: { uri },
position,
context: { includeDeclaration },
},
);
return result ?? [];
} catch {
return [];
}
}
async getCompletions(filePath: string, position: Position): Promise<CompletionItem[]> {
async getCompletions(
filePath: string,
position: Position,
): Promise<CompletionItem[]> {
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<DocumentSymbol[] | null>("textDocument/documentSymbol", {
textDocument: { uri },
});
const result = await this.request<DocumentSymbol[] | null>(
"textDocument/documentSymbol",
{
textDocument: { uri },
},
);
return result ?? [];
} catch {
return [];

View File

@@ -152,7 +152,10 @@ export const openFile = async (filePath: string): Promise<void> => {
}
};
export const updateFile = async (filePath: string, content: string): Promise<void> => {
export const updateFile = async (
filePath: string,
content: string,
): Promise<void> => {
const absolutePath = path.resolve(filePath);
const clients = await getClientsForFile(absolutePath);
@@ -176,7 +179,10 @@ export const closeFile = async (filePath: string): Promise<void> => {
}
};
export const getHover = async (filePath: string, position: Position): Promise<Hover | null> => {
export const getHover = async (
filePath: string,
position: Position,
): Promise<Hover | null> => {
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<DocumentSymbol[]> => {
export const getDocumentSymbols = async (
filePath: string,
): Promise<DocumentSymbol[]> => {
const absolutePath = path.resolve(filePath);
const clients = await getClientsForFile(absolutePath);
@@ -255,7 +267,9 @@ export const getDocumentSymbols = async (filePath: string): Promise<DocumentSymb
return [];
};
export const getDiagnostics = (filePath?: string): Map<string, Diagnostic[]> => {
export const getDiagnostics = (
filePath?: string,
): Map<string, Diagnostic[]> => {
const allDiagnostics = new Map<string, Diagnostic[]>();
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);

View File

@@ -166,9 +166,13 @@ export const LANGUAGE_EXTENSIONS: Record<string, string> = {
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[] => {

View File

@@ -54,8 +54,12 @@ const findProjectRoot = async (
const findBinary = async (name: string): Promise<string | null> => {
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<string, ServerInfo> = {
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<string, ServerInfo> = {
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<boolean> => {
export const isServerAvailable = async (
server: ServerInfo,
): Promise<boolean> => {
const binary = await findBinary(server.command);
return binary !== null;
};

View File

@@ -154,7 +154,7 @@ const mapCategory = (category: string): MCPServerCategory => {
* Get all servers (curated + external)
*/
export const getAllServers = async (
forceRefresh = false
forceRefresh = false,
): Promise<MCPRegistryServer[]> => {
// 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<MCPSearchResult> => {
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<string, (a: MCPRegistryServer, b: MCPRegistryServer) => 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<MCPRegistryServer | undefined> => {
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<MCPRegistryServer[]> => {
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<MCPInstallResult> => {
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<MCPInstallResult> => {
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<MCPRegistryServer[]> => {
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);
};
/**

View File

@@ -39,11 +39,7 @@ export const MODEL_TIER_MAPPING: Record<ModelTier, string[]> = {
"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"],
};
/**

View File

@@ -26,14 +26,18 @@ let agentRegistry: Map<string, AgentDefinition> = new Map();
/**
* Set the agent registry (called during initialization)
*/
export const setAgentRegistry = (registry: Map<string, AgentDefinition>): void => {
export const setAgentRegistry = (
registry: Map<string, AgentDefinition>,
): 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;
});
};
/**

View File

@@ -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<string, string> = new Map(); // filePath -> agentId
/**
* Pending lock requests
*/
const pendingLocks: Map<string, Array<{
agentId: string;
resolve: (acquired: boolean) => 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<void> => {
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();
}

View File

@@ -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<void> => {
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<void> => {
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,
};
};

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 <TInput, TOutput>(
options: ParallelExecutorOptions,
): Promise<ParallelExecutionResult<TOutput>> => {
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 <TInput, TOutput>(
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 <TInput, TOutput>(
// Track results
const results: ParallelExecutionResult<TOutput>[] = [];
const pendingTasks = new Map<string, Promise<ParallelExecutionResult<TOutput>>>();
const pendingTasks = new Map<
string,
Promise<ParallelExecutionResult<TOutput>>
>();
// Check if executor was aborted
const checkAbort = (): boolean => {
@@ -209,9 +222,15 @@ const executeWithConflictHandling = async <TInput, TOutput>(
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 <TInput, TOutput>(
resolution: ConflictResolution,
_options: ParallelExecutorOptions,
): Promise<{ continue: boolean; result: ParallelExecutionResult<TOutput> }> => {
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 <TInput, TOutput>(
const resolutionHandlers: Record<
ConflictResolution,
() => Promise<{ continue: boolean; result: ParallelExecutionResult<TOutput> }>
() => Promise<{
continue: boolean;
result: ParallelExecutionResult<TOutput>;
}>
> = {
wait: async () => {
const resolved = await waitForConflictResolution(conflicts.conflictingTaskIds);
const resolved = await waitForConflictResolution(
conflicts.conflictingTaskIds,
);
if (resolved) {
return { continue: true, result: {} as ParallelExecutionResult<TOutput> };
return {
continue: true,
result: {} as ParallelExecutionResult<TOutput>,
};
}
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 <TInput, TOutput>(
},
abort: async () => {
return createFailResult("conflict", PARALLEL_ERRORS.CONFLICT(task.id, conflicts.conflictingPaths));
return createFailResult(
"conflict",
PARALLEL_ERRORS.CONFLICT(task.id, conflicts.conflictingPaths),
);
},
};

View File

@@ -160,7 +160,11 @@ export const acquireResources = async (task: ParallelTask): Promise<void> => {
/**
* 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

View File

@@ -24,7 +24,9 @@ export const collectResults = <TOutput>(
results: ParallelExecutionResult<TOutput>[],
): AggregatedResults<TOutput> => {
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 = <T extends { path: string; match?: string }>(
export const deduplicateSearchResults = <
T extends { path: string; match?: string },
>(
results: T[],
): DeduplicationResult<T> => {
return deduplicateResults(results, (item) => ({
@@ -137,9 +141,13 @@ export const mergeByPriority = <T>(
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>[],
): boolean => {
return results.some(
(r) => r.status === "completed" && r.result === true,
);
return results.some((r) => r.status === "completed" && r.result === true);
};

View File

@@ -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

View File

@@ -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<PluginDiscoveryResult[]> => {
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<PluginDiscoveryResult[]> => {
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<PluginManifest | null> => {
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<PluginCommandDefinition | null> => {
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<PluginToolDefinition | null> => {
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<HookDefinition[]> => {
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<Map<string, PluginCommandDefinition>> => {
const commands = new Map<string, PluginCommandDefinition>();
@@ -329,7 +337,7 @@ export const loadPluginCommands = async (
*/
export const loadPluginTools = async (
pluginPath: string,
manifest: PluginManifest
manifest: PluginManifest,
): Promise<Map<string, PluginToolDefinition>> => {
const tools = new Map<string, PluginToolDefinition>();

View File

@@ -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<PluginLoadResult> => {
const manifest = await parseManifest(manifestPath);
@@ -86,7 +83,7 @@ export const initializePlugins = async (workingDir: string): Promise<void> => {
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);
};

View File

@@ -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: [],

View File

@@ -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 },
],
},

Some files were not shown because too many files have changed in this diff Show More