From 453e13c88dd1f5f964a72ef5881647921d06d7d3 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 12 Jun 2026 02:30:09 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(ai):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=83=AD=E7=82=B9=E8=AF=8A=E6=96=AD=E6=8E=A2?= =?UTF-8?q?=E9=92=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 注册 inspect_codebase_hotspots 内置工具并接入本地执行器 - 在工具目录、系统提示和斜杠菜单中暴露大文件治理入口 - 增加工具注册、执行器、目录和斜杠菜单测试 --- .../ai/AIBuiltinToolsCatalog.test.tsx | 3 + .../ai/aiCodebaseHotspotInsights.ts | 180 ++++++++++++++++++ ...LocalToolExecutor.codebaseHotspots.test.ts | 52 +++++ .../src/components/ai/aiSlashCommands.test.ts | 7 + frontend/src/components/ai/aiSlashCommands.ts | 1 + ...apshotInspectionDiagnosticsToolExecutor.ts | 11 ++ .../ai/aiSnapshotInspectionToolExecutor.ts | 1 + .../ai/aiSystemContextMessages.test.ts | 3 +- .../ai/aiSystemInspectionGuidance.ts | 6 + .../messageBubble/AIMessageStatusBlocks.tsx | 1 + .../aiBuiltinInspectionDiagnosticsToolInfo.ts | 25 +++ .../src/utils/aiBuiltinToolCatalog.test.ts | 4 + frontend/src/utils/aiBuiltinToolCatalog.ts | 5 + frontend/src/utils/aiToolRegistry.test.ts | 9 + 14 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/ai/aiCodebaseHotspotInsights.ts create mode 100644 frontend/src/components/ai/aiLocalToolExecutor.codebaseHotspots.test.ts diff --git a/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx b/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx index c608475..c9df203 100644 --- a/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx +++ b/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx @@ -104,6 +104,9 @@ describe('AIBuiltinToolsCatalog', () => { expect(markup).toContain('诊断 AI 上下文体量'); expect(markup).toContain('inspect_ai_context_budget'); expect(markup).toContain('messageLimit'); + expect(markup).toContain('治理前端大文件'); + expect(markup).toContain('inspect_codebase_hotspots'); + expect(markup).toContain('大文件和拆分热点'); expect(markup).toContain('复用历史 SQL'); expect(markup).toContain('inspect_saved_queries'); expect(markup).toContain('回看 AI 历史对话'); diff --git a/frontend/src/components/ai/aiCodebaseHotspotInsights.ts b/frontend/src/components/ai/aiCodebaseHotspotInsights.ts new file mode 100644 index 0000000..7aac86b --- /dev/null +++ b/frontend/src/components/ai/aiCodebaseHotspotInsights.ts @@ -0,0 +1,180 @@ +export interface CodebaseHotspotEntry { + path: string; + lines: number; + area: string; + riskLevel: 'medium' | 'high' | 'critical'; + why: string; + suggestedSlices: string[]; + testTargets: string[]; +} + +export interface CodebaseHotspotSnapshotOptions { + keyword?: string; + minLines?: number; + limit?: number; + includeRecommendations?: boolean; +} + +const CODEBASE_HOTSPOT_SNAPSHOT: CodebaseHotspotEntry[] = [ + { + path: 'frontend/src/components/Sidebar.tsx', + lines: 8910, + area: 'workspace-navigation', + riskLevel: 'critical', + why: '左侧树、命令面板、上下文菜单和连接动作集中在单文件,修改入口多且回归面大。', + suggestedSlices: ['V2 命令面板', '外部 SQL 目录弹窗', '连接树动作', '批量操作弹窗'], + testTargets: ['Sidebar.locate-toolbar.test.tsx', 'sidebarV2Utils.test.ts'], + }, + { + path: 'frontend/src/components/DataGrid.tsx', + lines: 8080, + area: 'result-grid', + riskLevel: 'critical', + why: '结果展示、编辑、DDL、导出和列操作耦合,容易让单点修复影响查询结果区。', + suggestedSlices: ['结果导出工具栏', '列头菜单', 'DDL 视图', '单元格编辑事务提示'], + testTargets: ['DataGrid.layout.test.tsx', 'DataGrid.ddl.test.tsx'], + }, + { + path: 'frontend/src/components/ConnectionModal.tsx', + lines: 7462, + area: 'connection-form', + riskLevel: 'critical', + why: '多数据源连接表单仍集中在一个组件,新增数据源或密钥规则时容易互相影响。', + suggestedSlices: ['SSH/代理配置区', 'TLS 配置区', 'MongoDB 配置区', 'JVM 配置区'], + testTargets: ['ConnectionModal.edit-password.test.tsx', 'connectionModalPresentation.test.ts'], + }, + { + path: 'frontend/src/components/QueryEditor.tsx', + lines: 5367, + area: 'sql-editor', + riskLevel: 'critical', + why: 'SQL 编辑、执行、事务、结果区布局和快捷键状态集中,事务和结果区回归风险高。', + suggestedSlices: ['结果区工具栏', '事务状态条', '执行日志提示', '编辑器快捷键绑定'], + testTargets: ['QueryEditor.result-panel.test.tsx', 'useSqlEditorTransactionController.test.ts'], + }, + { + path: 'frontend/src/components/TableDesigner.tsx', + lines: 3549, + area: 'table-designer', + riskLevel: 'high', + why: '字段编辑、索引、外键、分区和 DDL 生成集中,数据库方言差异容易扩散。', + suggestedSlices: ['字段编辑表格', '索引配置面板', '外键配置面板', '方言 DDL 预览'], + testTargets: ['TableDesigner.*.test.tsx', 'tableDesignerSchemaSql.test.ts'], + }, + { + path: 'frontend/src/components/RedisViewer.tsx', + lines: 2120, + area: 'redis-browser', + riskLevel: 'high', + why: 'Key 浏览、不同数据结构编辑、TTL、编码显示和新增弹窗集中,Redis Cluster/Sentinel 后续验证面较宽。', + suggestedSlices: ['Key 搜索栏', 'String/List/Set/ZSet/Hash/Stream 编辑器', '新增 Key 弹窗'], + testTargets: ['redisViewerTree.test.ts', 'RedisViewer.*.test.tsx'], + }, + { + path: 'frontend/src/components/DriverManagerModal.tsx', + lines: 1729, + area: 'driver-manager', + riskLevel: 'high', + why: '驱动安装、状态展示、下载和可选代理逻辑较多,适合继续拆出状态卡片和操作区。', + suggestedSlices: ['驱动状态列表', '安装操作区', '下载日志区'], + testTargets: ['DriverManagerModal.*.test.tsx'], + }, + { + path: 'frontend/src/components/DataSyncModal.tsx', + lines: 1526, + area: 'data-sync', + riskLevel: 'high', + why: '数据同步连接、表映射、预检查和执行结果集中,数据库方言问题容易隐藏。', + suggestedSlices: ['连接选择区', '表映射区', '同步预检查结果', '执行日志区'], + testTargets: ['DataSyncModal.*.test.tsx'], + }, + { + path: 'frontend/src/components/JVMDiagnosticConsole.tsx', + lines: 1146, + area: 'jvm-diagnostics', + riskLevel: 'medium', + why: '诊断命令、输出块、权限提示和会话状态可继续拆分,降低 JVM 诊断回归面。', + suggestedSlices: ['命令输入区', '诊断输出区', '权限提示区'], + testTargets: ['JVMDiagnosticConsole.*.test.tsx'], + }, +]; + +const normalizeKeyword = (value: unknown): string => String(value || '').trim().toLowerCase(); + +const clampNumber = (value: unknown, fallback: number, min: number, max: number): number => { + const parsed = Number(value); + if (!Number.isFinite(parsed)) { + return fallback; + } + return Math.max(min, Math.min(max, Math.floor(parsed))); +}; + +const matchesKeyword = (entry: CodebaseHotspotEntry, keyword: string): boolean => { + if (!keyword) { + return true; + } + return [ + entry.path, + entry.area, + entry.riskLevel, + entry.why, + ...entry.suggestedSlices, + ...entry.testTargets, + ].some((item) => item.toLowerCase().includes(keyword)); +}; + +export const buildCodebaseHotspotSnapshot = ({ + keyword, + minLines, + limit, + includeRecommendations = true, +}: CodebaseHotspotSnapshotOptions = {}) => { + const normalizedKeyword = normalizeKeyword(keyword); + const normalizedMinLines = clampNumber(minLines, 1000, 1, 20000); + const normalizedLimit = clampNumber(limit, 8, 1, 30); + const matched = CODEBASE_HOTSPOT_SNAPSHOT + .filter((entry) => entry.lines >= normalizedMinLines) + .filter((entry) => matchesKeyword(entry, normalizedKeyword)) + .slice(0, normalizedLimit); + const criticalCount = matched.filter((entry) => entry.riskLevel === 'critical').length; + const highCount = matched.filter((entry) => entry.riskLevel === 'high').length; + + return { + kind: 'codebase_hotspots', + source: 'static_maintainability_snapshot', + evidence: { + measuredAt: '2026-06-12', + note: '基于当前仓库前端文件行数热点快照;提交前仍应用 git diff 和定向测试核对具体改动。', + }, + filters: { + keyword: normalizedKeyword || undefined, + minLines: normalizedMinLines, + limit: normalizedLimit, + includeRecommendations, + }, + summary: { + totalKnownHotspots: CODEBASE_HOTSPOT_SNAPSHOT.length, + totalMatched: matched.length, + maxLines: matched[0]?.lines || 0, + criticalCount, + highCount, + }, + hotspots: matched.map((entry, index) => ({ + rank: index + 1, + path: entry.path, + lines: entry.lines, + area: entry.area, + riskLevel: entry.riskLevel, + why: entry.why, + suggestedSlices: includeRecommendations ? entry.suggestedSlices : [], + testTargets: includeRecommendations ? entry.testTargets : [], + })), + nextActions: includeRecommendations + ? [ + '优先选择已有测试覆盖的 slice 做小步拆分,避免直接重写整个大组件。', + '拆分后至少运行对应组件测试、相关 utils 测试和 npm --prefix frontend run build。', + '涉及可见 UI 的拆分需要用浏览器打开真实页面做一次冒烟验证。', + ] + : [], + }; +}; diff --git a/frontend/src/components/ai/aiLocalToolExecutor.codebaseHotspots.test.ts b/frontend/src/components/ai/aiLocalToolExecutor.codebaseHotspots.test.ts new file mode 100644 index 0000000..1232ec8 --- /dev/null +++ b/frontend/src/components/ai/aiLocalToolExecutor.codebaseHotspots.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it, vi } from 'vitest'; + +import type { AIToolCall, SavedConnection } from '../../types'; +import { executeLocalAIToolCall } from './aiLocalToolExecutor'; + +const buildToolCall = (name: string, args: Record): AIToolCall => ({ + id: `call-${name}`, + type: 'function', + function: { + name, + arguments: JSON.stringify(args), + }, +}); + +const buildConnection = (): SavedConnection => ({ + id: 'conn-1', + name: '本地开发库', + config: { + type: 'mysql', + host: '127.0.0.1', + port: 3306, + user: 'root', + }, +}); + +describe('aiLocalToolExecutor inspect_codebase_hotspots', () => { + it('returns frontend large-file hotspots and refactor test targets without source content', async () => { + const result = await executeLocalAIToolCall({ + toolCall: buildToolCall('inspect_codebase_hotspots', { + keyword: 'QueryEditor', + minLines: 1000, + limit: 5, + }), + connections: [buildConnection()], + mcpTools: [], + toolContextMap: new Map(), + runtime: { + getDatabases: vi.fn(), + getTables: vi.fn(), + }, + }); + + expect(result.success).toBe(true); + expect(result.toolName).toBe('inspect_codebase_hotspots'); + expect(result.content).toContain('"kind":"codebase_hotspots"'); + expect(result.content).toContain('frontend/src/components/QueryEditor.tsx'); + expect(result.content).toContain('"riskLevel":"critical"'); + expect(result.content).toContain('事务状态条'); + expect(result.content).toContain('QueryEditor.result-panel.test.tsx'); + expect(result.content).not.toContain('import React'); + }); +}); diff --git a/frontend/src/components/ai/aiSlashCommands.test.ts b/frontend/src/components/ai/aiSlashCommands.test.ts index 822abab..3e3fae3 100644 --- a/frontend/src/components/ai/aiSlashCommands.test.ts +++ b/frontend/src/components/ai/aiSlashCommands.test.ts @@ -14,6 +14,7 @@ describe('aiSlashCommands', () => { expect(commands.some((command) => command.cmd === '/health')).toBe(true); expect(commands.some((command) => command.cmd === '/tools')).toBe(true); expect(commands.some((command) => command.cmd === '/budget')).toBe(true); + expect(commands.some((command) => command.cmd === '/hotspots')).toBe(true); expect(commands.some((command) => command.cmd === '/mcp')).toBe(true); expect(commands.some((command) => command.cmd === '/mcpfail')).toBe(true); expect(commands.some((command) => command.cmd === '/mcpadd')).toBe(true); @@ -45,6 +46,12 @@ describe('aiSlashCommands', () => { expect(filterAISlashCommands('/bud').map((command) => command.cmd)).toContain('/budget'); }); + it('supports filtering code hotspot diagnostics by keyword and command prefix', () => { + expect(filterAISlashCommands('大文件').map((command) => command.cmd)).toContain('/hotspots'); + expect(filterAISlashCommands('拆分').map((command) => command.cmd)).toContain('/hotspots'); + expect(filterAISlashCommands('/hot').map((command) => command.cmd)).toContain('/hotspots'); + }); + it('supports filtering shortcut diagnostics by chinese keyword and command prefix', () => { expect(filterAISlashCommands('快捷键').map((command) => command.cmd)).toContain('/shortcuts'); expect(filterAISlashCommands('/sho').map((command) => command.cmd)).toContain('/shortcuts'); diff --git a/frontend/src/components/ai/aiSlashCommands.ts b/frontend/src/components/ai/aiSlashCommands.ts index 5017fe9..86c1c72 100644 --- a/frontend/src/components/ai/aiSlashCommands.ts +++ b/frontend/src/components/ai/aiSlashCommands.ts @@ -50,6 +50,7 @@ export const DEFAULT_AI_SLASH_COMMANDS: AISlashCommandDefinition[] = [ { cmd: '/health', label: '🩺 AI 配置体检', desc: '调用体检探针总览当前 AI 配置', prompt: '请先调用 inspect_ai_setup_health,对当前 GoNavi AI 配置做一次完整体检,然后总结 blockers、warnings 和 nextActions。', category: 'diagnose', featured: true, keywords: ['health', '体检', 'ai配置', '探针'] }, { cmd: '/tools', label: '🧰 工具目录', desc: '按关键词选择该用哪个内置探针', prompt: '请先调用 inspect_ai_tool_catalog,按我的问题关键词筛选推荐流程、内置工具参数提示和当前 MCP 工具摘要,再告诉我下一步应该调用哪个工具。关键词:', category: 'diagnose', keywords: ['工具目录', '内置工具', 'toolcatalog', '参数提示', 'arguments', '探针路线'] }, { cmd: '/budget', label: '🧠 上下文体量', desc: '诊断消息、DDL、MCP schema 和 Skills 体量', prompt: '请先调用 inspect_ai_context_budget,检查当前会话消息、工具结果、DDL、MCP schema、提示词和 Skills 的体量风险,并给出应该收窄哪些上下文。', category: 'diagnose', keywords: ['上下文', 'context', '体量', '预算', '变慢', '乱答', 'schema太大', '工具结果'] }, + { cmd: '/hotspots', label: '🧱 代码热点', desc: '查看大文件拆分候选和测试范围', prompt: '请先调用 inspect_codebase_hotspots,读取当前 GoNavi 前端大文件热点、建议拆分切片和测试目标,再告诉我下一步最适合拆哪个文件、拆到什么边界,以及需要跑哪些验证。关键词:', category: 'diagnose', keywords: ['大文件', '臃肿', '拆分', '重构', 'hotspots', '代码热点', '几千行'] }, { cmd: '/mcp', label: '🪛 排查 MCP 接入', desc: '检查 MCP 服务和外部客户端状态', prompt: '请先调用 inspect_mcp_setup,帮我盘点当前 MCP 服务、工具发现结果,以及 Claude Code / Codex 本机客户端和 OpenClaw / Hermans 远程 Agent 的接入状态。', category: 'diagnose', featured: true, keywords: ['mcp', 'codex', 'claude', 'openclaw', 'hermans', '外部客户端'] }, { cmd: '/mcpfail', label: '🧯 MCP 运行失败', desc: '读取 MCP 启动、发现和调用失败日志', prompt: '请先调用 inspect_mcp_runtime_failures,读取最近 MCP 启动、工具发现、工具调用、stdio、Docker 或 HTTP MCP 失败日志,结合当前 MCP 服务配置判断原因和 nextActions。关键词或服务名:', category: 'diagnose', keywords: ['mcpfail', 'mcp失败', '运行期失败', '工具发现0个', 'stdio', 'docker mcp', 'http mcp', '启动失败', '调用失败'] }, { cmd: '/mcpadd', label: '🧭 新增 MCP 指引', desc: '查看 command、args、env 和模板怎么填', prompt: '请先调用 inspect_mcp_authoring_guide;如果我贴了完整启动命令或草稿,再调用 inspect_mcp_draft 试算字段和校验问题;最后结合 inspect_mcp_setup,告诉我新增 GoNavi MCP 服务时 command、args、env、timeout 应该怎么填,以及最接近的模板应该选哪个。', category: 'diagnose', featured: true, keywords: ['mcp新增', 'command', 'args', 'env', '模板'] }, diff --git a/frontend/src/components/ai/aiSnapshotInspectionDiagnosticsToolExecutor.ts b/frontend/src/components/ai/aiSnapshotInspectionDiagnosticsToolExecutor.ts index 8b0fe91..45daed5 100644 --- a/frontend/src/components/ai/aiSnapshotInspectionDiagnosticsToolExecutor.ts +++ b/frontend/src/components/ai/aiSnapshotInspectionDiagnosticsToolExecutor.ts @@ -15,6 +15,7 @@ import { buildAIMessageFlowSnapshot, } from './aiChatSessionInsights'; import { buildAIContextBudgetSnapshot } from './aiContextBudgetInsights'; +import { buildCodebaseHotspotSnapshot } from './aiCodebaseHotspotInsights'; import { buildSavedQueriesSnapshot, buildSqlSnippetsSnapshot, @@ -114,6 +115,16 @@ export async function executeDiagnosticsSnapshotToolCall({ })), success: true, }; + case 'inspect_codebase_hotspots': + return { + content: JSON.stringify(buildCodebaseHotspotSnapshot({ + keyword: args.keyword, + minLines: args.minLines, + limit: args.limit, + includeRecommendations: args.includeRecommendations !== false, + })), + success: true, + }; case 'inspect_recent_sql_logs': return { content: JSON.stringify(buildRecentSqlLogsSnapshot({ diff --git a/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts b/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts index 0170345..afc2d55 100644 --- a/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts +++ b/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts @@ -180,6 +180,7 @@ export async function executeSnapshotInspectionToolCall( inspect_ai_last_render_error: '读取最近一次 AI 渲染异常失败', inspect_ai_message_flow: '读取 AI 消息流诊断失败', inspect_ai_context_budget: '读取 AI 上下文体量诊断失败', + inspect_codebase_hotspots: '读取代码热点诊断失败', inspect_saved_queries: '读取已保存查询失败', inspect_sql_snippets: '读取 SQL 片段失败', inspect_shortcuts: '读取快捷键配置失败', diff --git a/frontend/src/components/ai/aiSystemContextMessages.test.ts b/frontend/src/components/ai/aiSystemContextMessages.test.ts index 3e03318..f6eb013 100644 --- a/frontend/src/components/ai/aiSystemContextMessages.test.ts +++ b/frontend/src/components/ai/aiSystemContextMessages.test.ts @@ -68,7 +68,7 @@ describe('buildAISystemContextMessages', () => { connections: [connections[0]], tabs: [], activeTabId: null, - availableToolNames: ['inspect_workspace_tabs', 'inspect_app_health', 'inspect_ai_support_bundle', 'inspect_ai_setup_health', 'inspect_ai_runtime', 'inspect_ai_safety', 'inspect_ai_providers', 'inspect_ai_chat_readiness', 'inspect_ai_upstream_logs', 'inspect_ai_tool_catalog', 'inspect_mcp_setup', 'inspect_mcp_runtime_failures', 'inspect_mcp_authoring_guide', 'inspect_mcp_draft', 'inspect_mcp_tool_schema', 'inspect_ai_guidance', 'inspect_ai_context', 'inspect_current_connection', 'inspect_connection_capabilities', 'inspect_saved_connections', 'inspect_external_sql_directories', 'inspect_external_sql_file', 'inspect_recent_sql_activity', 'inspect_sql_editor_transaction', 'inspect_sql_risk', 'inspect_recent_connection_failures', 'inspect_app_logs', 'inspect_ai_last_render_error', 'inspect_ai_message_flow', 'inspect_ai_context_budget', 'inspect_saved_queries', 'inspect_ai_sessions', 'inspect_sql_snippets', 'inspect_shortcuts', 'get_columns'], + availableToolNames: ['inspect_workspace_tabs', 'inspect_app_health', 'inspect_ai_support_bundle', 'inspect_ai_setup_health', 'inspect_ai_runtime', 'inspect_ai_safety', 'inspect_ai_providers', 'inspect_ai_chat_readiness', 'inspect_ai_upstream_logs', 'inspect_ai_tool_catalog', 'inspect_mcp_setup', 'inspect_mcp_runtime_failures', 'inspect_mcp_authoring_guide', 'inspect_mcp_draft', 'inspect_mcp_tool_schema', 'inspect_ai_guidance', 'inspect_ai_context', 'inspect_current_connection', 'inspect_connection_capabilities', 'inspect_saved_connections', 'inspect_external_sql_directories', 'inspect_external_sql_file', 'inspect_recent_sql_activity', 'inspect_sql_editor_transaction', 'inspect_sql_risk', 'inspect_recent_connection_failures', 'inspect_app_logs', 'inspect_ai_last_render_error', 'inspect_ai_message_flow', 'inspect_ai_context_budget', 'inspect_codebase_hotspots', 'inspect_saved_queries', 'inspect_ai_sessions', 'inspect_sql_snippets', 'inspect_shortcuts', 'get_columns'], skills, userPromptSettings, }); @@ -104,6 +104,7 @@ describe('buildAISystemContextMessages', () => { expect(joined).toContain('inspect_ai_last_render_error 读取最近一次被隔离的前端渲染异常记录'); expect(joined).toContain('inspect_ai_message_flow 读取当前会话的真实消息结构'); expect(joined).toContain('inspect_ai_context_budget 读取消息、DDL、MCP schema、提示词和 Skills 的体量风险'); + expect(joined).toContain('inspect_codebase_hotspots 读取大文件热点'); expect(joined).toContain('inspect_saved_queries'); expect(joined).toContain('inspect_ai_sessions'); expect(joined).toContain('inspect_sql_snippets'); diff --git a/frontend/src/components/ai/aiSystemInspectionGuidance.ts b/frontend/src/components/ai/aiSystemInspectionGuidance.ts index 18147bd..377622e 100644 --- a/frontend/src/components/ai/aiSystemInspectionGuidance.ts +++ b/frontend/src/components/ai/aiSystemInspectionGuidance.ts @@ -170,6 +170,12 @@ export const appendDatabaseInspectionGuidanceMessages = ( 'inspect_ai_context_budget', '如果用户提到“AI 变慢”“上下文太大”“表结构挂太多”“工具结果太长”“模型开始乱答”或复杂任务前需要判断是否该拆小上下文,优先调用 inspect_ai_context_budget 读取消息、DDL、MCP schema、提示词和 Skills 的体量风险,再决定收窄上下文或拆任务。', ); + appendGuidanceIfToolAvailable( + messages, + availableToolNames, + 'inspect_codebase_hotspots', + '如果用户提到“几千行文件太臃肿”“继续拆分大组件”“下一步该拆哪个文件”“AI/MCP/UI 改动风险大不大”,优先调用 inspect_codebase_hotspots 读取大文件热点、建议拆分切片和测试目标,再制定改动范围。', + ); appendGuidanceIfToolAvailable( messages, availableToolNames, diff --git a/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx b/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx index f95993f..ac340a9 100644 --- a/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx +++ b/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx @@ -62,6 +62,7 @@ const TOOL_ACTION_LABELS: Record = { inspect_ai_last_render_error: '读取最近一次 AI 渲染异常', inspect_ai_message_flow: '诊断当前 AI 消息流', inspect_ai_context_budget: '诊断 AI 上下文体量风险', + inspect_codebase_hotspots: '读取代码大文件热点', inspect_saved_queries: '检索本地已保存查询', inspect_sql_snippets: '读取 SQL 片段模板', inspect_shortcuts: '读取当前快捷键配置', diff --git a/frontend/src/utils/aiBuiltinInspectionDiagnosticsToolInfo.ts b/frontend/src/utils/aiBuiltinInspectionDiagnosticsToolInfo.ts index 6d471b9..7d6dc03 100644 --- a/frontend/src/utils/aiBuiltinInspectionDiagnosticsToolInfo.ts +++ b/frontend/src/utils/aiBuiltinInspectionDiagnosticsToolInfo.ts @@ -163,6 +163,31 @@ export const BUILTIN_AI_INSPECTION_DIAGNOSTICS_TOOL_INFO: AIBuiltinToolInfo[] = }, }, }, + { + name: "inspect_codebase_hotspots", + icon: "🧱", + desc: "查看前端大文件和拆分热点", + detail: + "返回当前 GoNavi 前端代码中的大文件热点、行数、风险等级、建议拆分切片和应该运行的回归测试。适合用户要求继续治理几千行大文件、评估下一步该拆哪个组件,或 AI 在修改前需要先判断改动风险时调用。", + params: "keyword?, minLines?(默认 1000), limit?(默认 8), includeRecommendations?(默认 true)", + tool: { + type: "function", + function: { + name: "inspect_codebase_hotspots", + description: + "读取 GoNavi 前端大文件和拆分热点快照,返回文件路径、行数、风险等级、建议拆分切片和测试目标。适用于用户提到几千行文件太臃肿、需要继续拆分组件、评估下一个重构切入点或在改 UI/AI/MCP 前需要先判断代码热点风险时优先调用。", + parameters: { + type: "object", + properties: { + keyword: { type: "string", description: "可选,按路径、模块、风险、拆分切片或测试目标过滤,例如 Sidebar、DataGrid、Redis、事务、连接" }, + minLines: { type: "number", description: "可选,只返回不少于多少行的热点文件,默认 1000,最大 20000" }, + limit: { type: "number", description: "可选,最多返回多少个热点,默认 8,最大 30" }, + includeRecommendations: { type: "boolean", description: "可选,是否返回 suggestedSlices、testTargets 和 nextActions,默认 true" }, + }, + }, + }, + }, + }, { name: "inspect_sql_snippets", icon: "🧩", diff --git a/frontend/src/utils/aiBuiltinToolCatalog.test.ts b/frontend/src/utils/aiBuiltinToolCatalog.test.ts index 13caafb..d7e25ae 100644 --- a/frontend/src/utils/aiBuiltinToolCatalog.test.ts +++ b/frontend/src/utils/aiBuiltinToolCatalog.test.ts @@ -89,5 +89,9 @@ describe('describeBuiltinToolParameters', () => { const mcpFlows = filterBuiltinToolFlows(BUILTIN_TOOL_FLOWS, '运行期失败日志') .map((flow) => flow.title); expect(mcpFlows).toContain('排查 MCP 接入状态'); + + const codebaseFlows = filterBuiltinToolFlows(BUILTIN_TOOL_FLOWS, '拆分热点') + .map((flow) => flow.title); + expect(codebaseFlows).toContain('治理前端大文件'); }); }); diff --git a/frontend/src/utils/aiBuiltinToolCatalog.ts b/frontend/src/utils/aiBuiltinToolCatalog.ts index 923a61e..436d9c1 100644 --- a/frontend/src/utils/aiBuiltinToolCatalog.ts +++ b/frontend/src/utils/aiBuiltinToolCatalog.ts @@ -207,6 +207,11 @@ export const BUILTIN_TOOL_FLOWS: AIBuiltinToolFlow[] = [ steps: 'inspect_ai_context_budget -> inspect_ai_context / inspect_ai_message_flow / inspect_ai_tool_catalog', description: '适合用户反馈 AI 变慢、乱答、上下文太大、工具结果过长或表结构挂太多时,先看消息、DDL、MCP schema、提示词和 Skills 的体量来源,再决定收窄上下文或拆任务。', }, + { + title: '治理前端大文件', + steps: 'inspect_codebase_hotspots -> inspect_ai_tool_catalog', + description: '适合用户要求继续拆分几千行组件、评估下一步重构切入点,或 AI 修改 UI/AI/MCP 前先判断大文件拆分热点、风险和验证范围。', + }, { title: '复用历史 SQL', steps: 'inspect_saved_queries -> get_columns / execute_sql', diff --git a/frontend/src/utils/aiToolRegistry.test.ts b/frontend/src/utils/aiToolRegistry.test.ts index 5239887..40cd97e 100644 --- a/frontend/src/utils/aiToolRegistry.test.ts +++ b/frontend/src/utils/aiToolRegistry.test.ts @@ -204,6 +204,14 @@ describe('aiToolRegistry', () => { expect(info?.tool.function.parameters?.properties?.messageLimit?.description).toContain('最大 120'); }); + it('registers the codebase-hotspots inspector as a builtin tool', () => { + const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_codebase_hotspots'); + expect(info).toBeTruthy(); + expect(info?.desc).toContain('前端大文件'); + expect(info?.tool.function.description).toContain('拆分热点快照'); + expect(info?.tool.function.parameters?.properties?.minLines?.description).toContain('默认 1000'); + }); + it('registers the recent-sql-activity, saved-query, and sql-snippet inspectors as builtin tools', () => { const recentActivityTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_recent_sql_activity'); const sqlEditorTransactionTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_sql_editor_transaction'); @@ -282,6 +290,7 @@ describe('aiToolRegistry', () => { expect(tools.some((item) => item.function.name === 'inspect_ai_last_render_error')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_ai_message_flow')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_ai_context_budget')).toBe(true); + expect(tools.some((item) => item.function.name === 'inspect_codebase_hotspots')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_saved_queries')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_ai_sessions')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_sql_snippets')).toBe(true);