831 lines
34 KiB
Markdown
831 lines
34 KiB
Markdown
# free-code .NET 10 迁移 — 综合架构分析与优化指南
|
||
|
||
> **文档性质**: 合并 `free-code-.NET10迁移代码审查与架构分析报告.md` 与 `架构设计评估与改进建议.md` 两份文档,并基于实际源码交叉验证后的综合分析
|
||
> **审查版本**: 0.1.0 (初始迁移)
|
||
> **审查日期**: 2026-04-06
|
||
> **审查范围**: `src/` 目录下全部 16 个项目、~200 个 .cs 文件
|
||
> **对照基准**: 原始 TypeScript 项目 (512,834 行代码)
|
||
> **审查人角色**: 多语言编码架构师,以 .NET 平台最佳实践为评审标准
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
- [一、总体评估](#一总体评估)
|
||
- [二、架构设计全景分析](#二架构设计全景分析)
|
||
- [三、关键设计问题详解](#三关键设计问题详解)
|
||
- [四、设计模式缺陷与改进](#四设计模式缺陷与改进)
|
||
- [五、性能优化方案](#五性能优化方案)
|
||
- [六、.NET 惯用法与代码质量](#六net-惯用法与代码质量)
|
||
- [七、可扩展性与插件体系](#七可扩展性与插件体系)
|
||
- [八、测试体系评估](#八测试体系评估)
|
||
- [九、功能对等性分析](#九功能对等性分析)
|
||
- [十、综合优化路线图](#十综合优化路线图)
|
||
- [十一、总结](#十一总结)
|
||
|
||
---
|
||
|
||
## 一、总体评估
|
||
|
||
### 综合评分:6.5 / 10
|
||
|
||
| 维度 | 评分 | 说明 |
|
||
|------|------|------|
|
||
| **架构分层** | 7/10 | 五层架构清晰,但项目拆分过细(16 个项目,其中 5 个仅有 3~5 个文件) |
|
||
| **接口设计** | 5/10 | 接口驱动思路正确(28 个接口),但 IAppStateStore 弱类型 `object`、ITool 职责过多 |
|
||
| **DI 实践** | 7/10 | 构造函数注入完整,但 16 个 ServiceCollectionExtensions 注册分散、顺序敏感 |
|
||
| **异步模式** | 7/10 | `IAsyncEnumerable<SDKMessage>` + CancellationToken 使用得当 |
|
||
| **设计模式** | 4/10 | 缺少责任链、中间件、策略模式;工具执行依赖巨型 switch |
|
||
| **性能考量** | 4/10 | JSON write→parse→clone 反模式、HttpClient 未池化、git 进程频繁创建 |
|
||
| **.NET 惯用法** | 5/10 | 未充分利用 C# 13/.NET 10 特性,部分代码像"翻译的 TypeScript" |
|
||
| **测试覆盖** | 6/10 | 9 个测试项目结构完整,但缺少 IProcessExecutor 等关键抽象影响可测性 |
|
||
| **功能对等** | 5/10 | 骨架代码完整,但 compact、PKCE、prompt-cache 等核心特性缺失 |
|
||
|
||
### 总体判断
|
||
|
||
迁移工作完成了 **架构骨架搭建**:16 个项目的分层、核心接口定义、DI 注册管道、查询引擎主循环均已实现。但在以下方面存在显著不足:
|
||
|
||
1. **架构设计问题**: 项目划分过细、缺少 DDD/垂直切片、状态管理臃肿、工具系统缺少责任链
|
||
2. **代码质量问题**: ToolRegistry 48-case switch、IAppStateStore 弱类型、QueryEngine 神类
|
||
3. **性能问题**: JSON 序列化的 write→parse→clone 反模式、HttpClient 未池化、进程创建频繁
|
||
4. **功能缺失**: 上下文压缩、OAuth PKCE、prompt-cache 优化、中间件管道、Terminal.Gui 完整 REPL
|
||
5. **.NET 化不足**: 大量 `lock(object)` 同步、Console.Error 替代 ILogger、同步方法包装为异步
|
||
|
||
---
|
||
|
||
## 二、架构设计全景分析
|
||
|
||
### 2.1 当前架构分层
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 表现层 FreeCode.TerminalUI │
|
||
│ Terminal.Gui v2 (REPL/Components/Theme) │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ 入口层 FreeCode │
|
||
│ Program.cs → QuickPathHandler → Host → IAppRunner │
|
||
│ ServiceCollectionExtensions (桥接 16 个模块的 DI 注册) │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ 应用层 FreeCode.Engine │ FreeCode.Commands │
|
||
│ QueryEngine (423行) │ 95+ Command 类 │
|
||
│ SystemPromptBuilder (167行) │ │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ 领域层 FreeCode.Tools │ FreeCode.Services │
|
||
│ 45+ Tool 类 + ToolRegistry │ Auth/Memory/Voice/... │
|
||
│ (365行) │ │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ 集成层 FreeCode.ApiProviders │ FreeCode.Mcp │ FreeCode.Lsp │ FreeCode.Bridge │
|
||
│ 5 个 LLM Provider │ 自研 MCP SDK │ LSP Client │ IDE 桥接 │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ 基础层 FreeCode.Core (28 接口 / 19 模型 / 18 枚举) │
|
||
│ FreeCode.State (AppState record — 49 属性) │
|
||
│ FreeCode.Features (FeatureFlags) │
|
||
│ FreeCode.Skills / FreeCode.Plugins / FreeCode.Tasks │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 项目粒度问题
|
||
|
||
当前 16 个源码项目中,有 5 个仅有 2~5 个文件:
|
||
|
||
| 项目 | 文件数 | 说明 |
|
||
|------|--------|------|
|
||
| FreeCode.Engine | 3 | QueryEngine + PromptBuilder + ServiceCollectionExtensions |
|
||
| FreeCode.Features | 3 | FeatureFlags + FeatureFlagService + ServiceCollectionExtensions |
|
||
| FreeCode.Tasks | 3 | BackgroundTaskManager + ... + ServiceCollectionExtensions |
|
||
| FreeCode.Skills | 4 | SkillLoader + SkillTypes + ... + ServiceCollectionExtensions |
|
||
| FreeCode.Bridge | 5 | BridgeService + BridgeApiClient + ... + ServiceCollectionExtensions |
|
||
|
||
每个项目都需要独立的 `.csproj`、`ServiceCollectionExtensions`、NuGet 引用管理。这种粒度适合大型微服务架构,但对于一个 CLI 工具来说过于分散。
|
||
|
||
**建议合并方案**(16 → 8 个项目):
|
||
|
||
```
|
||
FreeCode/ # 主入口 + CLI 解析
|
||
FreeCode.Core/ # 接口 + 模型 + 枚举(保持不变)
|
||
FreeCode.Engine/ # QueryEngine + PromptBuilder + Tools + Commands + State + Features
|
||
FreeCode.Integrations/ # MCP + LSP + Bridge + ApiProviders
|
||
FreeCode.Services/ # 所有业务服务 (Auth/Memory/Voice/Permission/Coordinator/...)
|
||
FreeCode.Extensions/ # Skills + Plugins + Tasks
|
||
FreeCode.UI/ # TerminalUI + Components
|
||
FreeCode.Tests/ # 测试项目(保持不变)
|
||
```
|
||
|
||
### 2.3 按技术划分 vs 按领域划分
|
||
|
||
**当前**: 按技术类型划分(Tools/Commands/Services/Mcp)
|
||
**原始 TypeScript**: 按功能领域划分(services/api/ + services/mcp/ + services/oauth/)
|
||
|
||
两者各有优劣。按技术划分的优势是关注点分离明确,劣势是跨领域修改需要改多个项目。考虑到 CLI 工具的特性,建议在现有技术分层基础上,通过命名空间和目录结构体现领域边界:
|
||
|
||
```csharp
|
||
// FreeCode.Tools 内部按领域组织目录
|
||
FreeCode.Tools/
|
||
├── FileSystem/ (Read, Write, Edit, Glob)
|
||
├── Shell/ (Bash, PowerShell)
|
||
├── Search/ (Grep, ToolSearch)
|
||
├── Agent/ (AgentTool, SendMessageTool)
|
||
├── Web/ (WebFetch, WebSearch, WebBrowser)
|
||
├── Mcp/ (MCPTool, McpAuthTool)
|
||
├── PlanMode/ (EnterPlanMode, ExitPlanMode)
|
||
├── Tasks/ (TaskCreate, TaskList, ...)
|
||
└── Cron/ (CronCreate, CronDelete, CronList)
|
||
```
|
||
|
||
### 2.4 数据流
|
||
|
||
```
|
||
用户输入
|
||
→ Program.Main() / QuickPathHandler
|
||
→ IAppRunner.RunAsync()
|
||
→ IQueryEngine.SubmitMessageAsync(content)
|
||
→ IPromptBuilder.BuildAsync() // 6 段式 System Prompt
|
||
→ IToolRegistry.GetToolsAsync() // 工具描述
|
||
→ ICommandRegistry.GetEnabledCommandsAsync() // 命令列表
|
||
→ ISessionMemoryService.GetCurrentMemoryAsync() // 会话记忆
|
||
→ BuildContextInfoAsync() // git 信息(2 次进程调用)
|
||
→ IToolRegistry.GetToolsAsync() // 获取可用工具列表
|
||
→ IApiProviderRouter.GetActiveProvider() // 路由到 API 提供商
|
||
→ IApiProvider.StreamAsync(request) // 流式 LLM 响应
|
||
→ while (shouldContinue) { ... } // 工具调用循环
|
||
→ ExecuteToolAsync() // 执行工具(switch 分发)
|
||
→ AppendMessage() // 追加消息到历史
|
||
→ PostQueryProcessing() // 记忆提取
|
||
→ TerminalUI 渲染输出
|
||
```
|
||
|
||
---
|
||
|
||
## 三、关键设计问题详解
|
||
|
||
### 问题 3.1 [Critical] ToolRegistry 巨型 switch 分发
|
||
|
||
**位置**: `src/FreeCode.Tools/ToolRegistry.cs:162-213`
|
||
|
||
**问题描述**:
|
||
`ExecuteToolAsync` 方法包含一个 48 个 case 的 switch 表达式。每添加一个新工具需要同时修改三处:`GetBaseTools()` 注册列表(第 64-110 行)、switch 表达式(第 162-213 行)、Input/Output DTO 类。这严重违反 Open-Closed 原则。
|
||
|
||
**根因**: `ToolBase` 缺少统一的 `ExecuteAsync(JsonElement, ToolExecutionContext, CancellationToken)` 方法,导致 ToolRegistry 必须知道每个工具的具体类型才能调用泛型 `ExecuteAsync<TInput, TOutput>`。
|
||
|
||
**建议方案**: 责任链 + 统一执行入口
|
||
|
||
```csharp
|
||
// 方案一:ToolBase 添加通用执行方法(最小改动)
|
||
public abstract class ToolBase : ITool
|
||
{
|
||
public virtual Task<(string Output, bool IsAllowed, bool ShouldContinue)> ExecuteFromJsonAsync(
|
||
JsonElement input, ToolExecutionContext context, CancellationToken ct)
|
||
{
|
||
// 默认实现:子类可以 override 或保持自动反序列化
|
||
throw new NotImplementedException($"Tool '{Name}' must implement ExecuteFromJsonAsync");
|
||
}
|
||
}
|
||
|
||
// ToolRegistry 简化为:
|
||
public async Task<(string, bool, bool)> ExecuteToolAsync(...)
|
||
{
|
||
// ...
|
||
if (tool is ToolBase toolBase)
|
||
return await toolBase.ExecuteFromJsonAsync(input, executionContext, ct);
|
||
if (tool is McpToolWrapper mcpTool)
|
||
return await ExecuteMcpToolAsync(mcpTool, input, ct);
|
||
throw new NotSupportedException($"No executor for tool: {tool.Name}");
|
||
}
|
||
```
|
||
|
||
```csharp
|
||
// 方案二:责任链模式(更灵活,适合插件扩展)
|
||
public interface IToolExecutor
|
||
{
|
||
bool CanExecute(ITool tool);
|
||
Task<(string Output, bool IsAllowed, bool ShouldContinue)> ExecuteAsync(
|
||
ITool tool, JsonElement input, ToolExecutionContext context, CancellationToken ct);
|
||
}
|
||
|
||
public class ToolExecutorPipeline
|
||
{
|
||
private readonly IToolExecutor[] _executors;
|
||
|
||
public async Task<(string, bool, bool)> ExecuteAsync(ITool tool, JsonElement input, ...)
|
||
{
|
||
foreach (var executor in _executors)
|
||
{
|
||
if (executor.CanExecute(tool))
|
||
return await executor.ExecuteAsync(tool, input, context, ct);
|
||
}
|
||
throw new NotSupportedException($"No executor for tool: {tool.Name}");
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 3.2 [Critical] IAppStateStore 弱类型 `object` 接口
|
||
|
||
**位置**: `src/FreeCode.Core/Interfaces/IAppStateStore.cs` (全文 11 行)
|
||
|
||
**问题描述**:
|
||
核心状态接口使用 `object` 作为状态类型,迫使所有消费者进行强制类型转换。在 C# 的强类型系统中,这是不可接受的。
|
||
|
||
**连锁影响**:
|
||
| 消费者 | 位置 | 问题 |
|
||
|--------|------|------|
|
||
| AppStateStore | `AppStateStore.cs:33` | `as AppState ?? throw new InvalidCastException()` |
|
||
| ToolRegistry | `ToolRegistry.cs:288-295` | `GetProperty("PermissionMode")` 反射 |
|
||
| 所有消费者 | 各处 | `(AppState)store.GetState()` 强转 |
|
||
| Update 重载 | `AppStateStore.cs:91-95` | `Func<object, object>` 包装为 `Func<AppState, AppState>` |
|
||
|
||
**建议方案**: 直接强类型化(推荐,改动最小)
|
||
|
||
```csharp
|
||
// 直接暴露 AppState
|
||
public interface IAppStateStore
|
||
{
|
||
AppState GetState(); // 强类型返回
|
||
void Update(Func<AppState, AppState> updater); // 强类型更新
|
||
IDisposable Subscribe(Action<AppState> listener); // 强类型通知
|
||
event EventHandler<StateChangedEventArgs>? StateChanged;
|
||
}
|
||
```
|
||
|
||
**消除反射**: `ToolRegistry.ResolvePermissionMode` 从反射改为直接调用:
|
||
|
||
```csharp
|
||
// 改前(反射)
|
||
private static PermissionMode ResolvePermissionMode(object appState)
|
||
=> appState.GetType().GetProperty("PermissionMode")?.GetValue(appState) is PermissionMode pm
|
||
? pm : PermissionMode.Default;
|
||
|
||
// 改后(直接访问)
|
||
private static PermissionMode ResolvePermissionMode(AppState appState)
|
||
=> appState.PermissionMode;
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 3.3 [Critical] QueryEngine 神类 + Func 委托注入
|
||
|
||
**位置**: `src/FreeCode.Engine/QueryEngine.cs` (全文 423 行)
|
||
|
||
**问题描述**:
|
||
1. 构造函数接收 **9 个参数**(第 12-20 行),其中 `toolExecutor` 是一个复杂的 `Func<...>` 委托
|
||
2. 单一类承担 6+ 职责:流式响应、工具执行、消息管理、JSON 序列化、Token 估算、CancellationToken 管理
|
||
|
||
```csharp
|
||
public sealed class QueryEngine(
|
||
IApiProviderRouter apiProviderRouter,
|
||
IToolRegistry toolRegistry, // 为什么既注入 IToolRegistry 又注入 toolExecutor?
|
||
IPermissionEngine permissionEngine, // 同样的冗余
|
||
IPromptBuilder promptBuilder,
|
||
ISessionMemoryService sessionMemoryService,
|
||
IFeatureFlagService featureFlagService,
|
||
Func<string, JsonElement, IPermissionEngine, ToolPermissionContext?, CancellationToken,
|
||
Task<(string Output, bool IsAllowed, bool ShouldContinue)>>? toolExecutor, // ← 反模式
|
||
ILogger<QueryEngine> logger) : IQueryEngine
|
||
```
|
||
|
||
**建议拆分方案**:
|
||
|
||
```csharp
|
||
// 1. IToolExecutor — 从 IToolRegistry 中提取工具执行职责
|
||
public interface IToolExecutor
|
||
{
|
||
Task<(string Output, bool IsAllowed, bool ShouldContinue)> ExecuteAsync(
|
||
string toolName, JsonElement input, ToolPermissionContext? context, CancellationToken ct);
|
||
}
|
||
|
||
// 2. IMessageStore — 消息管理职责
|
||
public interface IMessageStore
|
||
{
|
||
void Append(Message message);
|
||
IReadOnlyList<Message> GetAll();
|
||
TokenUsage EstimateUsage();
|
||
}
|
||
|
||
// 3. 重构后的 QueryEngine
|
||
public sealed class QueryEngine(
|
||
IApiProviderRouter apiProviderRouter,
|
||
IToolExecutor toolExecutor, // 替代 Func + IToolRegistry
|
||
IPromptBuilder promptBuilder,
|
||
IMessageStore messageStore, // 替代内部 _messages
|
||
ISessionMemoryService sessionMemoryService,
|
||
IFeatureFlagService featureFlagService,
|
||
ILogger<QueryEngine> logger) : IQueryEngine
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 3.4 [High] AppState 巨型 Record — 单体状态
|
||
|
||
**位置**: `src/FreeCode.State/AppState.cs` (49 个属性)
|
||
|
||
**问题描述**:
|
||
单个 `AppState` record 包含 49 个属性,涵盖配置、权限、任务、MCP、插件、远程、UI、Agent 等所有领域。任何属性变更都触发 `with {}` 重建,所有订阅者都收到通知。
|
||
|
||
**建议方案**: 分片状态管理
|
||
|
||
```csharp
|
||
// 按领域拆分状态切片
|
||
public sealed record AppState
|
||
{
|
||
public ConversationState Conversation { get; init; } = new();
|
||
public McpState Mcp { get; init; } = McpState.Empty;
|
||
public TaskState Tasks { get; init; } = new();
|
||
public UiState Ui { get; init; } = new();
|
||
public PluginState Plugins { get; init; } = PluginState.Empty;
|
||
}
|
||
|
||
// 支持选择性订阅
|
||
public interface IStateSlice<T>
|
||
{
|
||
T Value { get; }
|
||
IObservable<T> Observe();
|
||
void Update(Func<T, T> updater);
|
||
}
|
||
|
||
// 组合管理器
|
||
public class AppStateManager
|
||
{
|
||
public IStateSlice<ConversationState> Conversation { get; }
|
||
public IStateSlice<McpState> Mcp { get; }
|
||
public IStateSlice<TaskState> Tasks { get; }
|
||
public IStateSlice<UiState> Ui { get; }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 3.5 [High] PermissionEngine 从 IServiceProvider 获取工具
|
||
|
||
**位置**: `src/FreeCode.Services/PermissionEngine.cs:13`
|
||
|
||
```csharp
|
||
var tool = context.Services.GetService(typeof(ITool)) as ITool;
|
||
var isReadOnly = tool?.IsReadOnly(input) ?? false;
|
||
```
|
||
|
||
`context.Services` 实际上是 `ToolRegistry.ToolExecutionServiceProvider`(第 297-301 行),一个自定义 IServiceProvider,唯一目的是返回当前工具实例。语义极其混乱。
|
||
|
||
**建议**: 在 `ToolExecutionContext` 中直接携带 `IsReadOnly`:
|
||
|
||
```csharp
|
||
public record ToolExecutionContext(
|
||
string WorkingDirectory,
|
||
PermissionMode PermissionMode,
|
||
bool IsToolReadOnly, // ← 直接传入
|
||
IPermissionEngine PermissionEngine,
|
||
ILspClientManager LspManager,
|
||
IBackgroundTaskManager TaskManager,
|
||
IServiceProvider Services)
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 3.6 [High] DI 注册分散 + 顺序敏感
|
||
|
||
**位置**: `src/FreeCode/Program.cs:39-54`
|
||
|
||
16 个 `AddXxx()` 扩展方法,注册顺序隐含依赖关系但无文档说明。
|
||
|
||
**建议**: 模块化 DI 设计
|
||
|
||
```csharp
|
||
public interface IFreeCodeModule
|
||
{
|
||
string Name { get; }
|
||
int Priority { get; } // 控制注册顺序
|
||
void ConfigureServices(IServiceCollection services, IConfiguration configuration);
|
||
}
|
||
|
||
// 内置模块
|
||
public sealed class CoreModule : IFreeCodeModule { /* Core + State + Features */ }
|
||
public sealed class EngineModule : IFreeCodeModule { /* Engine + Tools + Commands */ }
|
||
public sealed class IntegrationModule : IFreeCodeModule { /* MCP + LSP + Bridge + ApiProviders */ }
|
||
public sealed class ServiceModule : IFreeCodeModule { /* Auth + Memory + Voice + ... */ }
|
||
|
||
// Program.cs
|
||
var modules = hostBuilder.CreateModules();
|
||
foreach (var module in modules.OrderBy(m => m.Priority))
|
||
module.ConfigureServices(services, configuration);
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 3.7 [High] ToolBase 静态 JsonDocument 内存泄漏
|
||
|
||
**位置**: `src/FreeCode.Tools/ToolBase.cs:9`
|
||
|
||
```csharp
|
||
private static readonly JsonElement EmptySchema = JsonDocument.Parse("{}").RootElement.Clone();
|
||
```
|
||
|
||
`JsonDocument.Parse` 创建的 `JsonDocument` 永远不会被 Dispose。
|
||
|
||
**建议**: 使用 `JsonNode` API 或缓存 `byte[]`:
|
||
|
||
```csharp
|
||
private static readonly byte[] EmptySchemaBytes = "{}"u8.ToArray();
|
||
public virtual JsonElement GetInputSchema()
|
||
{
|
||
using var doc = JsonDocument.Parse(EmptySchemaBytes);
|
||
return doc.RootElement.Clone();
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、设计模式缺陷与改进
|
||
|
||
### 4.1 缺少工具执行中间件管道
|
||
|
||
**问题**: 工具执行无预处理/后处理钩子,原始 TypeScript 有完整的 hooks 系统。
|
||
|
||
**建议**: ASP.NET Core 风格的中间件管道
|
||
|
||
```csharp
|
||
public interface IToolMiddleware
|
||
{
|
||
Task<ToolResult> ExecuteAsync(ToolContext context, ToolDelegate next);
|
||
}
|
||
|
||
public delegate Task<ToolResult> ToolDelegate(ToolContext context);
|
||
|
||
// 内置中间件示例
|
||
public class LoggingMiddleware : IToolMiddleware
|
||
{
|
||
public async Task<ToolResult> ExecuteAsync(ToolContext context, ToolDelegate next)
|
||
{
|
||
_logger.LogInformation("Executing tool: {ToolName}", context.ToolName);
|
||
var result = await next(context);
|
||
_logger.LogInformation("Tool completed: {ToolName}, Error: {IsError}", context.ToolName, result.IsError);
|
||
return result;
|
||
}
|
||
}
|
||
|
||
public class PermissionMiddleware : IToolMiddleware { /* 权限检查 */ }
|
||
public class RateLimitMiddleware : IToolMiddleware { /* 速率限制 */ }
|
||
public class MetricsMiddleware : IToolMiddleware { /* 指标收集 */ }
|
||
```
|
||
|
||
### 4.2 缺少 API 提供商策略模式
|
||
|
||
**位置**: `src/FreeCode.ApiProviders/ApiProviderRouter.cs:26-34`
|
||
|
||
当前通过 switch 硬编码路由到 5 个提供商。新增提供商需要修改此 switch。
|
||
|
||
**建议**: 策略注册模式
|
||
|
||
```csharp
|
||
public class ApiProviderRegistry
|
||
{
|
||
private readonly Dictionary<ApiProviderType, IApiProvider> _providers;
|
||
|
||
public ApiProviderRegistry(IEnumerable<IApiProvider> providers)
|
||
{
|
||
// 自动发现注册
|
||
foreach (var provider in providers)
|
||
_providers[provider.Type] = provider;
|
||
}
|
||
|
||
public IApiProvider GetActiveProvider()
|
||
{
|
||
var activeType = DetectProvider();
|
||
return _providers.TryGetValue(activeType, out var provider)
|
||
? provider
|
||
: throw new InvalidOperationException($"Unknown provider: {activeType}");
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.3 缺少消息事件溯源
|
||
|
||
**位置**: `src/FreeCode.Engine/QueryEngine.cs:407-413`
|
||
|
||
消息直接 `_messages.Add()`,无事件发布。
|
||
|
||
**建议**: 事件驱动消息存储
|
||
|
||
```csharp
|
||
public abstract record MessageEvent
|
||
{
|
||
public sealed record MessageAdded(Message Message) : MessageEvent;
|
||
public sealed record MessagesCompacted(string Reason, int RemovedCount) : MessageEvent;
|
||
}
|
||
|
||
public class MessageStore : IMessageStore
|
||
{
|
||
private readonly List<Message> _messages = new();
|
||
private readonly Channel<MessageEvent> _events = Channel.CreateUnbounded<MessageEvent>();
|
||
|
||
public ChannelReader<MessageEvent> Events => _events.Reader;
|
||
|
||
public void Append(Message message)
|
||
{
|
||
lock (_gate) { _messages.Add(message); }
|
||
_events.Writer.TryWrite(new MessageEvent.MessageAdded(message));
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 五、性能优化方案
|
||
|
||
### 5.1 JSON 处理优化
|
||
|
||
| 问题 | 位置 | 优化方案 | 预期收益 |
|
||
|------|------|----------|----------|
|
||
| write→parse→clone 反模式 | `QueryEngine.cs:263-397`(3 处) | 直接返回 JsonDocument 不 Clone | 减少每次请求 3 次内存分配 |
|
||
| McpClient.ParseMessage Clone | `McpClient.cs:170-204` | 使用 `ReadOnlySpan<byte>` + 流式解析 | 高频消息场景减少 GC 压力 |
|
||
| ToolBase EmptySchema 泄漏 | `ToolBase.cs:9` | 缓存 `byte[]` 或使用 JsonNode | 消除静态泄漏 |
|
||
| 未使用 Source Generator | `SessionMemoryService.cs:195`, `ToolRegistry.cs:303-313` | 统一使用 SourceGenerationContext | AOT 兼容 + 减少反射 |
|
||
| EstimateTokens 重复实现 | `QueryEngine.cs:415` + `SessionMemoryService.cs:148` | 提取到 `FreeCode.Core/Utilities/TokenEstimator.cs` | DRY |
|
||
|
||
### 5.2 网络连接优化
|
||
|
||
| 问题 | 位置 | 优化方案 | 预期收益 |
|
||
|------|------|----------|----------|
|
||
| HttpClient 未池化 | 所有 5 个 Provider + AuthService | `IHttpClientFactory` + 命名客户端 | TCP 连接复用 |
|
||
| MCP 连接每次新建 HttpClient | `McpClientManager.cs` | `IHttpClientFactory` + SocketsHttpHandler 池 | 连接复用 |
|
||
| git 进程每次创建 | `SystemPromptBuilder.cs:107-139` | 缓存 + 2 秒 TTL + 并行两个命令 | 减少进程创建 |
|
||
| SSE 流缓冲区固定 4096 | `AnthropicProvider.cs:156` | 可配置 bufferSize | 大消息场景 |
|
||
|
||
### 5.3 内存优化
|
||
|
||
| 问题 | 位置 | 优化方案 | 预期收益 |
|
||
|------|------|----------|----------|
|
||
| Message.Content 是 `object?` | `Message.cs:9` | tagged union 或 `OneOf<string, JsonElement>` | 避免 boxing |
|
||
| AppState 全量更新 | `AppStateStore.cs:29-35` | 分片状态 + 选择性通知 | 减少 GC 压力 |
|
||
| ToolRegistry 缓存永不失效 | `ToolRegistry.cs:56-136` | Feature flag 变更时清缓存 | 功能正确性 |
|
||
| 消息渲染全量重绘 | `REPLScreen.cs` (推测) | 增量渲染只重绘新行 | UI 响应性 |
|
||
|
||
### 5.4 BashTool.IsReadOnly 精度不足
|
||
|
||
**位置**: `src/FreeCode.Tools/BashTool.cs:44-55`
|
||
|
||
```csharp
|
||
// 当前:简单前缀匹配,lsblk/catdoc 误判,复合命令漏检
|
||
var readOnlyPrefixes = new[] { "ls", "cat", "grep", ... };
|
||
return readOnlyPrefixes.Any(prefix => trimmed.StartsWith(prefix, StringComparison.Ordinal));
|
||
```
|
||
|
||
**建议**: 使用单词边界 + 管道检测:
|
||
|
||
```csharp
|
||
private static readonly string[] ReadOnlyCommands = ["ls", "cat", "grep", "find", "pwd", "head", "tail", "stat", "wc", "du", "which", "whoami", "git"];
|
||
|
||
public override bool IsReadOnly(object input)
|
||
{
|
||
var command = input is BashToolInput t ? t.Command : input as string;
|
||
if (string.IsNullOrWhiteSpace(command)) return false;
|
||
|
||
if (command.Contains('|') || command.Contains(';') || command.Contains("&&") || command.Contains("||"))
|
||
return false;
|
||
|
||
var firstWord = command.TrimStart().Split(' ', 2)[0];
|
||
var baseCommand = firstWord.Split('/', StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? firstWord;
|
||
|
||
return ReadOnlyCommands.Contains(baseCommand, StringComparer.Ordinal)
|
||
|| (baseCommand == "git" && command.TrimStart().StartsWith("git status", StringComparison.Ordinal));
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 六、.NET 惯用法与代码质量
|
||
|
||
### 6.1 文件范围命名空间
|
||
|
||
当前所有文件使用 block-scoped namespace。建议统一改为 C# 10+ file-scoped namespace。
|
||
|
||
### 6.2 替换 `lock(object)` 为现代同步原语
|
||
|
||
至少 6 个类使用 `private readonly object _gate = new(); lock(_gate)` 模式:
|
||
- AppStateStore、QueryEngine、SessionMemoryService、RateLimitService、ToolRegistry、CoordinatorService
|
||
|
||
建议按场景替换:
|
||
- **读多写少**: `ReaderWriterLockSlim` 或 `ImmutableInterlocked`
|
||
- **生产者-消费者**: `Channel<T>`(McpClient 已使用)
|
||
- **信号量**: `SemaphoreSlim`(RemoteSessionManager 已使用)
|
||
|
||
### 6.3 硬编码配置值集中管理
|
||
|
||
| 位置 | 硬编码值 | 说明 |
|
||
|------|----------|------|
|
||
| `AnthropicProvider.cs:31` | `"2023-06-01"` | API 版本号 |
|
||
| `AnthropicProvider.cs:145` | `4096` | max_tokens 限制 |
|
||
| `BashTool.cs:128` | `"/bin/zsh"` | 默认 shell |
|
||
| `AuthService.cs:95` | `38465` | OAuth 端口 |
|
||
| `SessionMemoryService.cs:10-11` | `4000`, `8` | 记忆阈值 |
|
||
| `SystemPromptBuilder.cs:157-166` | BaseInstructions | 系统提示词 |
|
||
|
||
建议统一使用 `IOptions<T>` + `appsettings.json`。
|
||
|
||
### 6.4 Console.Error.WriteLine → ILogger
|
||
|
||
在 SessionMemoryService、AuthService、KeychainTokenStorage、Program.cs 中使用 `Console.Error.WriteLine`。应统一替换为 `ILogger<T>` 结构化日志。
|
||
|
||
### 6.5 同步方法包装为 Task
|
||
|
||
SessionMemoryService、PermissionEngine 等的公开方法返回 `Task` 但内部完全同步。建议使用 `ValueTask` 减少分配。
|
||
|
||
### 6.6 Primary Constructors 风格统一
|
||
|
||
QueryEngine 和 SystemPromptBuilder 使用 C# 12 primary constructors,但 AppStateStore、SessionMemoryService 等使用传统构造函数。建议统一。
|
||
|
||
---
|
||
|
||
## 七、可扩展性与插件体系
|
||
|
||
### 7.1 插件系统仅支持命令扩展
|
||
|
||
**位置**: `src/FreeCode.Plugins/PluginManager.cs`
|
||
|
||
当前只扫描 `ICommand` 类型。建议支持多种扩展点:
|
||
|
||
```csharp
|
||
public interface IPluginExtension
|
||
{
|
||
string ExtensionPoint { get; } // "tools", "commands", "services", "hooks"
|
||
}
|
||
|
||
public interface IToolExtension : IPluginExtension
|
||
{
|
||
IEnumerable<ITool> GetTools();
|
||
}
|
||
|
||
public interface IHookExtension : IPluginExtension
|
||
{
|
||
Task OnBeforeToolExecute(string toolName, object input);
|
||
Task OnAfterToolExecute(string toolName, object output);
|
||
}
|
||
```
|
||
|
||
### 7.2 缺少 ITool 接口隔离
|
||
|
||
**位置**: `src/FreeCode.Core/Interfaces/ITool.cs`
|
||
|
||
当前 `ITool` 有 9 个成员,混合了信息、模式、行为三类职责。
|
||
|
||
**建议拆分**:
|
||
|
||
```csharp
|
||
public interface IToolInfo { string Name { get; } string[]? Aliases { get; } ToolCategory Category { get; } }
|
||
public interface IToolSchema { JsonElement GetInputSchema(); Task<string> GetDescriptionAsync(object? input = null); }
|
||
public interface IToolBehavior { bool IsEnabled(); bool IsConcurrencySafe(object input); bool IsReadOnly(object input); }
|
||
public interface ITool : IToolInfo, IToolSchema, IToolBehavior { }
|
||
```
|
||
|
||
### 7.3 缺少进程执行抽象
|
||
|
||
**位置**: `src/FreeCode.Tools/BashTool.cs:126-152`
|
||
|
||
BashTool 直接 `new Process()`,无法在单元测试中替换。
|
||
|
||
**建议**: 抽象进程执行
|
||
|
||
```csharp
|
||
public interface IProcessExecutor
|
||
{
|
||
Task<ProcessResult> ExecuteAsync(ProcessStartInfo info, CancellationToken ct);
|
||
}
|
||
|
||
// 测试替身
|
||
public class FakeProcessExecutor : IProcessExecutor
|
||
{
|
||
public Task<ProcessResult> ExecuteAsync(...)
|
||
=> Task.FromResult(new ProcessResult { ExitCode = 0, Stdout = "fake output" });
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 八、测试体系评估
|
||
|
||
### 8.1 测试项目结构
|
||
|
||
9 个测试项目覆盖核心业务逻辑,遵循一对一映射原则。使用 xUnit 2.x + FluentAssertions 6.x + Moq 4.x。
|
||
|
||
### 8.2 测试可测性问题
|
||
|
||
| 问题 | 影响 | 解决方案 |
|
||
|------|------|----------|
|
||
| BashTool 直接依赖 `Process` | 无法单元测试 | 引入 `IProcessExecutor` |
|
||
| IAppStateStore 返回 `object` | 测试中需要强转 | 强类型化 |
|
||
| QueryEngine 注入 Func 委托 | 难以 Mock | 提取 IToolExecutor 接口 |
|
||
| SystemPromptBuilder 直接调 git | 测试依赖 git 安装 | 引入 `IGitInfoProvider` |
|
||
| AnthropicProvider 构造函数可选 HttpClient | 需要真实网络或手动 Mock | IHttpClientFactory |
|
||
|
||
---
|
||
|
||
## 九、功能对等性分析
|
||
|
||
### 9.1 已实现功能
|
||
|
||
| 功能 | 实现位置 | 对等程度 |
|
||
|------|----------|----------|
|
||
| 查询引擎主循环 | FreeCode.Engine/QueryEngine.cs | ~80% |
|
||
| 工具注册与执行 | FreeCode.Tools/ToolRegistry.cs | ~70% |
|
||
| 命令系统 | FreeCode.Commands/ (95+ files) | ~40%(大量可能是空壳) |
|
||
| 5 个 API 提供商 | FreeCode.ApiProviders/ | ~75% |
|
||
| MCP 协议 | FreeCode.Mcp/ | ~70% |
|
||
| OAuth 认证 | FreeCode.Services/AuthService.cs | ~60% |
|
||
| 状态管理 | FreeCode.State/ | ~70% |
|
||
| 插件系统 | FreeCode.Plugins/ | ~50% |
|
||
| 技能系统 | FreeCode.Skills/ | ~30% |
|
||
|
||
### 9.2 缺失关键功能
|
||
|
||
| 功能 | 原始实现 | 影响 | 优先级 |
|
||
|------|----------|------|--------|
|
||
| **上下文压缩 (compact)** | src/services/compact/ | 长会话无法管理 token 窗口 | P0 |
|
||
| **OAuth PKCE** | src/services/oauth/crypto.ts | 安全漏洞 | P0 |
|
||
| **中间件管道** | hooks/ 目录 | 无预处理/后处理钩子 | P1 |
|
||
| **Prompt Cache 优化** | cache_control 注解 | API 成本倍增 | P1 |
|
||
| **System.CommandLine** | 文档声称使用,实际手动解析 | 子命令/自动补全缺失 | P1 |
|
||
| **Terminal.Gui 完整 REPL** | 原始 REPL.tsx 5000+ 行 | UI 体验不完整 | P1 |
|
||
| **工具延迟加载** | ToolSearch tool | Context window 浪费 | P2 |
|
||
| **多扩展点插件** | 完整扩展系统 | 仅支持命令扩展 | P2 |
|
||
| **IDE Bridge 完整协议** | src/bridge/ (32 files) | 远程 IDE 控制不完整 | P2 |
|
||
|
||
---
|
||
|
||
## 十、综合优化路线图
|
||
|
||
### Phase 1: 关键修复 — 1 周
|
||
|
||
| # | 任务 | 影响范围 | 工作量 | 收益 |
|
||
|---|------|----------|--------|------|
|
||
| 1 | IAppStateStore 强类型化 | Core + State + Tools + Engine | 0.5天 | 消除反射、强类型安全 |
|
||
| 2 | ToolRegistry 消除 switch(添加 ExecuteFromJsonAsync) | Tools | 1天 | OCP 合规、新增工具零改动 |
|
||
| 3 | QueryEngine 拆分(提取 IToolExecutor, IMessageStore) | Engine + Tools | 1天 | SRP、可测试性 |
|
||
| 4 | HttpClient 池化(IHttpClientFactory) | ApiProviders + Services | 0.5天 | 连接复用、性能 |
|
||
| 5 | 添加 OAuth PKCE | Services/AuthService | 0.5天 | 安全性 |
|
||
|
||
### Phase 2: 架构优化 — 2 周
|
||
|
||
| # | 任务 | 影响范围 | 工作量 | 收益 |
|
||
|---|------|----------|--------|------|
|
||
| 6 | AppState 分片 + IStateSlice<T> | State + 所有消费者 | 2天 | 性能、选择性更新 |
|
||
| 7 | JSON 序列化优化(消除 write→parse→clone) | Engine + Mcp | 2天 | 减少内存分配 |
|
||
| 8 | 工具执行中间件管道 | Tools | 2天 | 可扩展性、日志/权限/限流 |
|
||
| 9 | 模块化 DI(IFreeCodeModule) | FreeCode (入口) | 1天 | 可维护性、注册顺序透明 |
|
||
| 10 | 结构化日志(消除 Console.Error) | Services + Engine | 0.5天 | 可观测性 |
|
||
| 11 | IOptions pattern(消除硬编码) | 全局 | 1天 | 配置管理 |
|
||
| 12 | 上下文压缩 (compact) | 新增 Engine 功能 | 3天 | Token 窗口管理 |
|
||
|
||
### Phase 3: 深度优化 — 持续
|
||
|
||
| # | 任务 | 影响范围 | 工作量 | 收益 |
|
||
|---|------|----------|--------|------|
|
||
| 13 | 项目合并(16→8~10) | 解决方案级 | 1天 | 构建速度 |
|
||
| 14 | ITool 接口隔离 | Core + Tools | 1天 | 可测试性 |
|
||
| 15 | IProcessExecutor 抽象 | Tools (Bash/PowerShell) | 1天 | 可测试性 |
|
||
| 16 | 工具延迟加载 | Tools | 2天 | Context window 优化 |
|
||
| 17 | Prompt Cache 优化 | Engine | 1天 | API 成本 |
|
||
| 18 | 事件溯源消息存储 | Engine | 2天 | 可追溯性 |
|
||
| 19 | Terminal.Gui 完整 REPL | TerminalUI | 5天 | UI 功能对等 |
|
||
| 20 | 命令功能逐个验证与完善 | Commands | 5天+ | 功能对等 |
|
||
| 21 | 多扩展点插件系统 | Plugins | 2天 | 可扩展性 |
|
||
| 22 | 集成测试完善 | Tests | 持续 | 可靠性 |
|
||
|
||
---
|
||
|
||
## 十一、总结
|
||
|
||
### 优点
|
||
|
||
1. **分层架构清晰**: 五层架构(基础→基础设施→核心→应用→表现)边界明确
|
||
2. **接口驱动设计**: 28 个核心接口定义了完整的契约层
|
||
3. **DI 覆盖完整**: 所有服务通过构造函数注入,16 个模块独立注册
|
||
4. **异步管道正确**: `IAsyncEnumerable<SDKMessage>` 流式响应、CancellationToken 贯穿全链路
|
||
5. **不可变状态**: AppState 使用 record + init 属性,避免意外修改
|
||
6. **JSON 高级 API**: Utf8JsonWriter + ArrayBufferWriter 优于手动字符串拼接
|
||
7. **工具池稳定排序**: 按名称字典序排序,最大化 prompt cache 命中潜力
|
||
8. **MCP 四传输层**: Stdio、SSE、WebSocket、StreamableHttp 均已实现
|
||
|
||
### 核心不足(按影响排序)
|
||
|
||
| # | 不足 | 影响 |
|
||
|---|------|------|
|
||
| 1 | **ToolRegistry 48-case switch** | 每增加工具需改 3 处,OCP 违反 |
|
||
| 2 | **IAppStateStore 弱类型 `object`** | 反射 + 强转贯穿全链路 |
|
||
| 3 | **QueryEngine 神类** | 423 行 6+ 职责,Func 委托注入 |
|
||
| 4 | **缺少中间件管道** | 无预处理/后处理钩子,原始 TS 有完整实现 |
|
||
| 5 | **AppState 49 属性单体** | 任何变更触发全量更新 |
|
||
| 6 | **JSON write→parse→clone** | 每次请求 3 次冗余内存分配 |
|
||
| 7 | **功能缺失** | compact、PKCE、prompt-cache 三项关键功能未实现 |
|
||
| 8 | **HttpClient 未管理** | 5 个 Provider + AuthService 各自 new HttpClient() |
|
||
| 9 | **项目划分过细** | 16 个项目,其中 5 个仅有 3~5 个文件 |
|
||
| 10 | **插件仅支持命令** | 缺少工具/钩子/服务扩展点 |
|
||
|
||
### 一句话总结
|
||
|
||
> 架构骨架搭建完成度约 70%,但存在 **3 个 Critical 级设计问题**(switch 分发、弱类型、神类)和 **4 个 High 级架构缺陷**(状态臃肿、DI 分散、缺少中间件、性能反模式),建议按 Phase 1→2→3 路线图推进,优先解决类型安全、OCP 合规、职责拆分三大基础问题,再逐步完善功能对等性和性能优化。
|
||
|
||
---
|
||
|
||
*报告生成日期:2026-04-06*
|
||
*审查范围:src/ 目录下 16 个项目全部 .cs 文件*
|
||
*文档性质:综合 `代码审查报告` 与 `架构设计评估` 两份文档的合并分析*
|