diff --git a/commands/add.ts b/commands/add.ts index 324b73f..154744a 100644 --- a/commands/add.ts +++ b/commands/add.ts @@ -177,7 +177,7 @@ export async function handleAdd( // --- 写入 --- try { - ws.createConnection(env, connName, { + ws.createConnection(connName, { environment: env, type: "mysql", host, diff --git a/commands/favorites.ts b/commands/favorites.ts index 9408cf5..04451cb 100644 --- a/commands/favorites.ts +++ b/commands/favorites.ts @@ -56,7 +56,7 @@ export async function handleFavorite( async function handleFavoriteAdd( ctx: ExtensionCommandContext, ws: DatabaseWorkspaceService, - pi: ExtensionAPI, + _pi: ExtensionAPI, args: string[], ): Promise { let name: string | undefined; diff --git a/commands/llm-context.ts b/commands/llm-context.ts index 9259e41..e78dc5e 100644 --- a/commands/llm-context.ts +++ b/commands/llm-context.ts @@ -25,7 +25,7 @@ export function sendActiveDb(pi: ExtensionAPI, ws: DatabaseWorkspaceService): vo } /** 已配置但未选择数据库:引导用户运行 /db switch。 */ -export function sendConfiguredHint(pi: ExtensionAPI, ws: DatabaseWorkspaceService): void { +function sendConfiguredHint(pi: ExtensionAPI, ws: DatabaseWorkspaceService): void { pi.sendMessage( { customType: "db-hint", @@ -49,18 +49,3 @@ export function sendDbStatus(pi: ExtensionAPI, ws: DatabaseWorkspaceService): vo sendConfiguredHint(pi, ws); } } - -/** 未配置任何连接:triggerTurn 让 AI 主动提议建立第一个连接。 */ -export function sendUnconfiguredHint(pi: ExtensionAPI, configPath: string): void { - pi.sendMessage( - { - customType: "db-hint", - content: - `No database connections are configured yet. Help the user create their first ` + - `connection in ${configPath}. Ask for host, port, username, password, and default ` + - `database name. After the config file is written, tell the user to run /db switch to connect.`, - display: false, - }, - { deliverAs: "followUp", triggerTurn: true }, - ); -} diff --git a/commands/mutate-confirm.ts b/commands/mutate-confirm.ts index 0c1582a..e4c5957 100644 --- a/commands/mutate-confirm.ts +++ b/commands/mutate-confirm.ts @@ -47,7 +47,7 @@ export async function showMutationConfirm( const warningText = params.warning ?? DEFAULT_WARNING; const result = await ctx.ui.custom( - (tui, theme, _kb, done) => { + (_tui, theme, _kb, done) => { const container = new Container(); // ── 上边框 ── diff --git a/commands/query.ts b/commands/query.ts index 7241473..5a43c2f 100644 --- a/commands/query.ts +++ b/commands/query.ts @@ -47,7 +47,7 @@ export function sanitizeRows(rows: SqlRow[]): Record[] { // ── 关联结果转换 ────────────────────────────────── /** 将 RelatedResult 转为 TUI entry 可用的 RelatedTuiData(行值清洗为 string|null)。 */ -export function toRelatedTuiData(related: RelatedResult[]): RelatedTuiData[] { +function toRelatedTuiData(related: RelatedResult[]): RelatedTuiData[] { return related.map((r) => ({ schema: r.schema, table: r.table, @@ -62,7 +62,7 @@ export function toRelatedTuiData(related: RelatedResult[]): RelatedTuiData[] { // ── 展示(双受众:TUI 条目 + LLM 上下文)────────── async function displayQueryResult( - ctx: ExtensionCommandContext, + _ctx: ExtensionCommandContext, ws: DatabaseWorkspaceService, pi: ExtensionAPI, result: ExecutedResult, diff --git a/commands/related-browser.ts b/commands/related-browser.ts index f21740a..f1e61b1 100644 --- a/commands/related-browser.ts +++ b/commands/related-browser.ts @@ -98,7 +98,7 @@ export interface BrowserTab { } /** 浏览器内容的分段结构——组件渲染时对每段套不同样式。 */ -export interface BrowserContent { +interface BrowserContent { /** 副标题(数据库名 + 关联表数量),如 `shop(2 个关联表)`。 */ title: string; /** 表切换行(当前表 active=true)。 */ diff --git a/commands/renderers.ts b/commands/renderers.ts index fc1807e..b6e98c8 100644 --- a/commands/renderers.ts +++ b/commands/renderers.ts @@ -9,15 +9,11 @@ * 文档文本由 formatting/result-document.ts 统一装配(纯函数), * 本模块只做两件事:把 DocLine 的 style 映射为 theme 颜色、 * 追加依赖 pi keyHint 的交互提示行。 - * - * 面板(`db-workspace-panel`)保持为消息渲染器——其内容是 - * 对 LLM 也有用的短预格式化文本。 */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { keyHint } from "@earendil-works/pi-coding-agent"; import type { Component } from "@earendil-works/pi-tui"; -import { Text } from "@earendil-works/pi-tui"; import type { QueryResultDoc } from "../formatting/result-document"; import { renderQueryDocument } from "../formatting/result-document"; @@ -65,13 +61,6 @@ export function registerRenderers(pi: ExtensionAPI): void { invalidate() {}, } satisfies Component; }); - - // ── db-workspace-panel:短预格式化文本——保持为消息渲染器 ── - - pi.registerMessageRenderer("db-workspace-panel", (message, _options, _theme) => { - if (typeof message.content !== "string") return undefined; - return new Text(message.content, 1, 0); - }); } // ====== 内部辅助 ====== diff --git a/commands/switch.ts b/commands/switch.ts index 3686b64..af4ba53 100644 --- a/commands/switch.ts +++ b/commands/switch.ts @@ -5,87 +5,10 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent"; import type { DatabaseWorkspaceService } from "../state/workspace"; import { withLoader } from "./utils"; -import { sendActiveDb, sendUnconfiguredHint } from "./llm-context"; +import { sendActiveDb } from "./llm-context"; export const STATUS_KEY = "db-workspace"; -export async function showWorkspacePanel( - ctx: ExtensionCommandContext, - ws: DatabaseWorkspaceService, - pi?: ExtensionAPI, -): Promise { - const lines: string[] = []; - - lines.push("═══ 数据库工作区 ═══"); - lines.push(""); - - if (ws.current) { - const conn = ws.getCurrentConnection(); - lines.push(`环境 :${ws.current.environment}`); - lines.push(`连接 :${ws.current.connectionId}(${conn?.host ?? "?"})`); - lines.push(`数据库 :${ws.current.database}`); - } else { - lines.push("未选择数据库,使用 /db switch 连接"); - lines.push(""); - if (ws.isConfigured) { - lines.push(`可用环境:${ws.getEnvironments().join(", ")}`); - } else { - lines.push("⚠️ 尚未配置数据库连接"); - lines.push(""); - lines.push(`配置文件:${ws.configPath}`); - lines.push(""); - lines.push("配置示例:"); - lines.push(" connections:"); - lines.push(" my-db:"); - lines.push(" environment: prod"); - lines.push(" type: mysql"); - lines.push(" host: 127.0.0.1"); - lines.push(" port: 3306"); - lines.push(" username: root"); - lines.push(" password: ${DB_PASSWORD}"); - } - } - - // 显示配置警告(如未解析的 env 变量) - const warnings = ws.getConfigWarnings(); - if (warnings.length > 0) { - lines.push(""); - for (const w of warnings) { - lines.push(`⚠ ${w}`); - } - } - - lines.push(""); - lines.push("命令:"); - lines.push(" /db switch 选择环境和数据库"); - lines.push(" /db add 添加新连接"); - lines.push(" /db tables 列出所有表"); - lines.push(" /db schema <表名> 查看表结构"); - lines.push(" /db query [表名] 选表 → WHERE → 查询"); - lines.push(" /db history 查询历史"); - lines.push(" /db favorite 收藏的查询模板"); - lines.push(" /db relations 表关联关系管理"); - - // 用户可见的面板(纯文本渲染器)。 - if (pi) { - pi.sendMessage( - { - customType: "db-workspace-panel", - content: lines.join("\n"), - display: true, - }, - { deliverAs: "followUp", triggerTurn: false }, - ); - } - - // 未配置连接时给 LLM 的静默提示。 - // display: false 让它不进聊天;triggerTurn 让 AI - // 主动提议帮助建立第一个连接。 - if (!ws.isConfigured && pi) { - sendUnconfiguredHint(pi, ws.configPath); - } -} - export async function handleSwitch( ctx: ExtensionCommandContext, ws: DatabaseWorkspaceService, diff --git a/connection/db-config.ts b/connection/db-config.ts index 543d9b2..6b53b09 100644 --- a/connection/db-config.ts +++ b/connection/db-config.ts @@ -13,7 +13,7 @@ import { homedir } from "node:os"; // ====== 配置文件类型 ====== -export interface ConnectionConfig { +interface ConnectionConfig { environment: string; type: "mysql"; host: string; @@ -24,7 +24,7 @@ export interface ConnectionConfig { queryLimit?: number; // row cap for SELECTs without LIMIT (default: DEFAULT_QUERY_LIMIT) } -export interface ConnectionsFile { +interface ConnectionsFile { connections: Record; } @@ -59,7 +59,7 @@ function resolveEnvVars(value: string): { resolved: string; unresolved: string[] return { resolved, unresolved }; } -export interface ConfigLoadResult { +interface ConfigLoadResult { connections: ResolvedConnectionConfig[]; /** 被跳过的连接或未解析的 env 变量的警告。 */ warnings: string[]; diff --git a/connection/db-manager.ts b/connection/db-manager.ts index 1ea968c..05a0e77 100644 --- a/connection/db-manager.ts +++ b/connection/db-manager.ts @@ -57,18 +57,18 @@ const defaultPoolFactory: PoolFactory = (cfg) => // ====== 选项与输出 ====== -export interface QueryOptions { +interface QueryOptions { params?: unknown[]; // bound values for ? placeholders } -export interface QueryOutput { +interface QueryOutput { columns: string[]; rows: SqlRow[]; elapsed: string; sql: string; // final SQL after policy (LIMIT may have been appended) } -export interface MutationOutput { +interface MutationOutput { affectedRows: number; elapsed: string; sql: string; diff --git a/connection/sql-policy.ts b/connection/sql-policy.ts index 624d7aa..11edde2 100644 --- a/connection/sql-policy.ts +++ b/connection/sql-policy.ts @@ -14,7 +14,7 @@ export const MUTATION_SQL_RE = /^(INSERT|UPDATE|DELETE|REPLACE)\b/i; const WHERE_RE = /\bWHERE\b/i; -export interface MutationValidation { +interface MutationValidation { sql: string; operation: "INSERT" | "UPDATE" | "DELETE" | "REPLACE"; hasWhere: boolean; diff --git a/docs/architecture.md b/docs/architecture.md index d5e2ce2..95eeabf 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -90,7 +90,7 @@ class DatabaseWorkspaceService { saveFavorite(name, sql, desc): FavoriteEntry; // ── 关系图 ── - upsertRelation(srcTable, srcCol, refTable, refCol, opts?): RelationRow; + upsertRelation(srcTable, srcCol, refTable, refCol, opts?): StoredRelation; removeRelationByColumns(database, srcTable, srcCol, refTable, refCol): boolean; async discoverForeignKeys(): Promise; } @@ -114,7 +114,7 @@ executeQuery(connId, db, sql) **为什么需要专用连接**:MySQL 的 `USE database` 是连接级状态。如果使用连接池的 `pool.query()`,两次调用可能落在不同连接上,导致 `USE` 和 `query` 分离。检出专用连接并在 finally 中归还,保证了原子性。 -**延迟连接**:MySQL 连接池在第一次使用时才创建(`getPool()` 懒初始化),按 connection ID 缓存。`destroy()` 遍历关闭所有池。 +**延迟连接**:MySQL 连接池在第一次使用时才懒创建,按 connection ID 缓存。`destroy()` 遍历关闭所有池。 ### 3.3 SQL 安全策略 — sql-policy.ts diff --git a/docs/mutation-tool-design.md b/docs/mutation-tool-design.md index c88c880..2d93dcd 100644 --- a/docs/mutation-tool-design.md +++ b/docs/mutation-tool-design.md @@ -144,7 +144,6 @@ async executeMutation( connectionId: string, database: string, sql: string, - opts: { timeout?: number } = {}, ): Promise { const pool = this.getPool(connectionId); const conn = await pool.getConnection(); @@ -152,7 +151,7 @@ async executeMutation( await conn.query(`USE \`${database}\``); const start = Date.now(); const [result] = await conn.query( - { sql, timeout: opts.timeout ?? 30000 }, + { sql, timeout: 30000 }, ) as [mysql.ResultSetHeader, any]; const elapsed = `${((Date.now() - start) / 1000).toFixed(3)}s`; return { diff --git a/docs/relation-tool-redesign.md b/docs/relation-tool-redesign.md index d674835..456496e 100644 --- a/docs/relation-tool-redesign.md +++ b/docs/relation-tool-redesign.md @@ -190,7 +190,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_relations_unique `RelationStore.insert()` 改为 `RelationStore.upsert()`: ```typescript -upsert(rel: Omit): RelationRow { +upsert(rel: Omit): StoredRelation { const stmt = this.db.prepare(` INSERT INTO table_relations (schema, table_name, column_name, condition, ref_schema, ref_table, ref_column, relation_type) @@ -219,7 +219,7 @@ upsert(rel: Omit): RelationRow { findByColumns( schema: string, table: string, column: string, condition: string, refSchema: string, refTable: string, refColumn: string, -): RelationRow | undefined { +): StoredRelation | undefined { const row = this.db.prepare(` SELECT * FROM table_relations WHERE schema = ? AND table_name = ? AND column_name = ? AND condition = ? @@ -254,7 +254,7 @@ deleteByColumns( ### 5.1 `register` → `upsert` ```typescript -upsert(source: ColumnRef, target: ColumnRef, relationType = "MANY_TO_ONE"): RelationRow { +upsert(source: ColumnRef, target: ColumnRef, relationType = "MANY_TO_ONE"): StoredRelation { const rel: Omit = { schema: source.schema, table: source.table, @@ -307,7 +307,7 @@ upsertRelation( refTable: string, refColumn: string, opts?: { condition?: string; relationType?: string; database?: string }, -): RelationRow { +): StoredRelation { const schema = opts?.database ?? this.current?.database; if (!schema) throw new Error("No database selected"); return this.relationGraph.upsert( @@ -359,7 +359,7 @@ removeRelationByColumns( ### 7.3 其余子命令不变 `/db relations remove` 仍通过 id 选择 → `ws.removeRelation(id)`。 -`/db relations er-diagram` 不变。 +`/db relations er-diagram` 已移除(候选 5:pi 不渲染 mermaid 图,命令删除;discover 的 AI 消息改为表结构文本)。 --- diff --git a/docs/testing/ai-runner.md b/docs/testing/ai-runner.md index 4560135..9bfef12 100644 --- a/docs/testing/ai-runner.md +++ b/docs/testing/ai-runner.md @@ -114,7 +114,6 @@ Step 4 收尾(人验证完成后): H3 重建或 init-env.mjs --cleanup 销毁, | G8 | G7 中选择"📎 一起查询关联表" | 主表 + 关联 customers/coupons/order_items/products 区块, 含关联路径 | 关联区块出现 | | G9 | /db query t_customers → WHERE id=1 → 关联表 | 关联 profiles(1)/orders(3)/coupons(1) | 各关联行数 | | G10 | /db relations discover | 0 条(无系统外键) | 提示 0 | -| G11 | /db relations er-diagram | 已注册关系图 | 图示渲染 | | G12 | /db favorite add(任一 SQL) → /db favorite | 收藏成功并按库分组列出 | 列表出现 | | G13 | /db history / /db history 订单 | 最近 20 条 / 关键词过滤 | 列表过滤 | | G14 | /db off → /db on | 状态栏移除/恢复; 工具报错提示未启用 | 状态栏变化 | diff --git a/docs/testing/test-plan.md b/docs/testing/test-plan.md index 598c087..b423117 100644 --- a/docs/testing/test-plan.md +++ b/docs/testing/test-plan.md @@ -228,9 +228,8 @@ node docs/testing/init-env.mjs --cleanup | G7 | 选表查询 | `/db query t_orders` → 输入 `status='paid'` | 2 行; **有注册关系时询问"查询关联表?"** | | G8 | **BFS 关联查询** | G7 选择"📎 一起查询关联表" | 主表结果 + 摘要行"关联表(N 个)— 详情见 LLM 上下文"(实测: TUI 不渲染完整关联表格, 完整内容在 LLM 上下文可见); 关联区块含 customers/coupons/order_items/products 等 | | G9 | BFS 多跳 | `/db query t_customers` → WHERE `id=1` → 关联表 | 修复后(见 §7.2)提示关联查询; 关联 profiles(1)/orders(3)/coupons(1)/archive(1)等 8 个区块 | -| G10 | 关系发现 | `/db relations discover` | 同步外键 0 条(本环境无系统外键) → 之后**总会询问**"是否使用 AI 分析表关系?"(选 ⏭ 跳过即可; 选 🤖 会生成 ER 图交 AI 分析并自动注册) | -| G11 | ER 图 | `/db relations er-diagram` | 展示已注册关系图 | -| G12 | 收藏 | `/db favorite add` + `/db favorite` | 收藏成功并按库分组列出 | +| G10 | 关系发现 | `/db relations discover` | 同步外键 0 条(本环境无系统外键) → 之后**总会询问**"是否使用 AI 分析表关系?"(选 ⏭ 跳过即可; 选 🤖 会发送表结构文本交 AI 分析并自动注册) | +| G11 | 收藏 | `/db favorite add` + `/db favorite` | 收藏成功并按库分组列出 | | G13 | 历史 | `/db history` / `/db history t_orders` | 最近 20 条 / 关键词过滤(匹配 **SQL 文本**, 中文表名搜不到; 无匹配时提示"未找到") | | G14 | 扩展开关 | `/db off` → `/db on` | 状态栏移除/恢复; 工具调用报错提示未启用 | diff --git a/formatting/result-document.ts b/formatting/result-document.ts index 69dca74..36ab7d1 100644 --- a/formatting/result-document.ts +++ b/formatting/result-document.ts @@ -36,9 +36,9 @@ export interface QueryResultDoc { related?: RelatedResult[]; } -export type DocumentAudience = "tui-zh" | "llm-zh"; +type DocumentAudience = "tui-zh" | "llm-zh"; -export interface RenderOptions { +interface RenderOptions { audience: DocumentAudience; /** 仅 tui-zh 使用:自适应终端宽度。 */ width?: number; diff --git a/history/store.ts b/history/store.ts index 7c96047..6a3fdd3 100644 --- a/history/store.ts +++ b/history/store.ts @@ -151,7 +151,7 @@ export interface FavoriteEntry { updatedTime: string; } -export interface FavoriteFilter { +interface FavoriteFilter { database?: string; // also includes global (database = '') keyword?: string; limit?: number; diff --git a/state/workspace.ts b/state/workspace.ts index 575dbd4..11bf72c 100644 --- a/state/workspace.ts +++ b/state/workspace.ts @@ -36,7 +36,7 @@ interface WorkspaceState { } /** 调用的有效目标:默认工作空间选择,可逐调用覆盖。 */ -export interface QueryTarget { +interface QueryTarget { connectionId: string; database: string; } @@ -51,7 +51,7 @@ export interface MutationApprovalRequest { } /** 写操作结果:用户拒绝是正常结果(rejected),非异常。 */ -export type MutationOutcome = +type MutationOutcome = | { status: "rejected"; sql: string } | { status: "executed"; @@ -144,7 +144,6 @@ export class DatabaseWorkspaceService { * 文件或父目录不存在时自动创建。 */ createConnection( - env: string, name: string, cfg: { environment: string;