From 255d2d48daa71ead7d6b681b2929299d447e99dc Mon Sep 17 00:00:00 2001
From: zavier <765324639@qq.com>
Date: Sat, 1 Aug 2026 20:08:01 +0800
Subject: [PATCH 1/3] =?UTF-8?q?refactor(db):=20=E5=88=A0=E9=99=A4=20ready(?=
=?UTF-8?q?)=20=E5=B9=B6=E8=A1=A5=E9=BD=90=20db=5Ftables=20=E6=88=AA?=
=?UTF-8?q?=E6=96=AD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
删除 ready() 影子守卫,readiness 由 resolveTarget 单一持有:
- 六个工具统一 getWorkspace() 后走 facade 入口 resolve-and-throw,
修掉 connection-only 显式目标被误拒的 bug
- db_relation 缺目标时改抛与 resolveTarget 一致的文案
db_tables list 模式补 truncate()(上万张表不再原样灌进上下文),
并按场景定制截断提示语——表列表/表结构不再是 query 专用的
"add a LIMIT" 文案。
---
tools/db-tools.ts | 49 ++++++++++++++++++++++-------------------------
1 file changed, 23 insertions(+), 26 deletions(-)
diff --git a/tools/db-tools.ts b/tools/db-tools.ts
index 949a27d..c1a3e98 100644
--- a/tools/db-tools.ts
+++ b/tools/db-tools.ts
@@ -28,13 +28,13 @@ import { showMutationConfirm } from "../commands/mutate-confirm";
import { LOADER_TOOL_NAME, LAZY_TOOL_INFO, matchDbTools } from "./db-tool-catalog";
export { applyInitialToolSet } from "./db-tool-catalog";
-function truncate(text: string): string {
+function truncate(text: string, hint = "Narrow the query or add a LIMIT."): string {
const t = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
if (!t.truncated) return t.content;
return (
t.content +
`\n\n[Output truncated: ${t.outputLines} of ${t.totalLines} lines` +
- ` (${formatSize(t.outputBytes)} of ${formatSize(t.totalBytes)}). Narrow the query or add a LIMIT.]`
+ ` (${formatSize(t.outputBytes)} of ${formatSize(t.totalBytes)}). ${hint}]`
);
}
@@ -58,21 +58,6 @@ export function registerDbTools(
pi: ExtensionAPI,
getWorkspace: () => DatabaseWorkspaceService,
): void {
- /**
- * 当未选择数据库且调用方未传显式目标时抛错(→ isError 工具结果),
- * 除非显式目标本身不需要工作空间选择即可工作。
- */
- const ready = (explicitTargetOk = false): DatabaseWorkspaceService => {
- const ws = getWorkspace();
- if (!ws.isReady && !explicitTargetOk) {
- throw new Error(
- "No database selected. Ask the user to run /db switch first, " +
- "or pass connection + database explicitly.",
- );
- }
- return ws;
- };
-
// ── Loader:按需启用懒加载工具 ────────────────────────────
pi.registerTool({
name: LOADER_TOOL_NAME,
@@ -153,7 +138,7 @@ export function registerDbTools(
...targetParams,
}),
async execute(_toolCallId, params) {
- const ws = ready(!!(params.connection && params.database));
+ const ws = getWorkspace();
const result = await ws.executeQuery(params.sql, {
connectionId: params.connection,
database: params.database,
@@ -206,7 +191,7 @@ export function registerDbTools(
),
}),
async execute(_toolCallId, params) {
- const ws = ready(!!params.connection);
+ const ws = getWorkspace();
const conns = ws.listConnections();
const lines = [
`Connections (${conns.length}):`,
@@ -247,7 +232,7 @@ export function registerDbTools(
...targetParams,
}),
async execute(_toolCallId, params) {
- const ws = ready(!!(params.connection && params.database));
+ const ws = getWorkspace();
const target = ws.resolveTarget({
connectionId: params.connection,
database: params.database,
@@ -266,7 +251,10 @@ export function registerDbTools(
content: [
{
type: "text",
- text: truncate(formatSchemaMarkdown(params.table, target.database, columns, indexes)),
+ text: truncate(
+ formatSchemaMarkdown(params.table, target.database, columns, indexes),
+ "Table has too many columns to display. Use db_query to select specific columns.",
+ ),
},
],
details,
@@ -274,11 +262,15 @@ export function registerDbTools(
}
const tables = await ws.getTables(target);
details.tables = tables;
+ const tableList = `Tables in ${target.connectionId}/${target.database} (${tables.length}):\n${tables.join("\n")}`;
return {
content: [
{
type: "text",
- text: `Tables in ${target.connectionId}/${target.database} (${tables.length}):\n${tables.join("\n")}`,
+ text: truncate(
+ tableList,
+ "Too many tables to display. Inspect a specific table with table= instead.",
+ ),
},
],
details,
@@ -302,7 +294,7 @@ export function registerDbTools(
),
}),
async execute(_toolCallId, params) {
- const ws = ready(!!params.database);
+ const ws = getWorkspace();
const rows = ws.listRelations(params.table, params.database);
const lines = rows.map(
(r) =>
@@ -359,7 +351,7 @@ export function registerDbTools(
}
// 2. 解析目标
- const ws = ready(!!(params.connection && params.database));
+ const ws = getWorkspace();
const target = ws.resolveTarget({
connectionId: params.connection,
database: params.database,
@@ -458,8 +450,13 @@ export function registerDbTools(
),
}),
async execute(_toolCallId, params, _signal, _onUpdate) {
- const ws = ready(!!params.database);
- const schema = params.database ?? ws.current!.database;
+ const ws = getWorkspace();
+ const schema = params.database ?? ws.current?.database;
+ if (!schema) {
+ throw new Error(
+ "No database selected. Run /db switch first, or pass connection + database explicitly.",
+ );
+ }
if (params.action === "register") {
const row = ws.upsertRelation(
From 2a5b139dcb4bcd02000d6d0187de9a646ac51f32 Mon Sep 17 00:00:00 2001
From: zavier <765324639@qq.com>
Date: Sat, 1 Aug 2026 20:08:04 +0800
Subject: [PATCH 2/3] =?UTF-8?q?refactor(db):=20=E6=B6=88=E9=99=A4=E9=9A=90?=
=?UTF-8?q?=E5=BC=8F=E7=8A=B6=E6=80=81=E9=80=9A=E9=81=93=EF=BC=8C=E6=94=B6?=
=?UTF-8?q?=E6=95=9B=20LLM=20=E4=B8=8A=E4=B8=8B=E6=96=87=E6=B6=88=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
候选 8 的两半:
- lastRelatedCache 从 module 级单例改为注入式 RelatedBrowserCacheStore
(db.ts 注册时创建,经调用链传入 query 路径写入、related 路径读取),
消除显示函数副作用与测试顺序依赖
- 新建 commands/llm-context.ts 作为 LLM 英文消息的唯一构建点;
index.ts(session_start)/ db.ts(仪表盘)/ switch.ts(切换后)
三处复用,active 文案三处统一
---
commands/db.ts | 51 ++++++++++------------------
commands/llm-context.ts | 66 +++++++++++++++++++++++++++++++++++++
commands/query.ts | 22 +++++--------
commands/related-browser.ts | 16 +++++++++
commands/switch.ts | 19 ++---------
index.ts | 23 ++-----------
6 files changed, 113 insertions(+), 84 deletions(-)
create mode 100644 commands/llm-context.ts
diff --git a/commands/db.ts b/commands/db.ts
index 3635379..ce9596c 100644
--- a/commands/db.ts
+++ b/commands/db.ts
@@ -20,8 +20,8 @@ import { handleQuery } from "./query";
import { handleHistory } from "./history";
import { handleFavorite } from "./favorites";
import { handleRelations } from "./relations";
-import { getLastRelatedCache } from "./query";
-import { openRelatedBrowser } from "./related-browser";
+import { RelatedBrowserCacheStore, openRelatedBrowser } from "./related-browser";
+import { sendDbStatus } from "./llm-context";
import { writeToggle } from "../state/extension-toggle";
// ====== 自动补全项类型(结构上匹配 pi-tui AutocompleteItem)======
@@ -76,6 +76,10 @@ export function registerDbCommand(
return;
}
+ // 最近关联查询缓存——注册时创建一次,注入 query 路径写入、
+ // related 路径读取(替代 module 级单例,见 AGENTS.md 注入接缝)。
+ const relatedCache = new RelatedBrowserCacheStore();
+
pi.registerCommand("db", {
description:
"Database workspace: /db (panel) | switch | add | tables | schema
| query [table] | history [kw] | favorite | relations | related | on | off",
@@ -94,10 +98,10 @@ export function registerDbCommand(
switch (sub) {
case undefined: {
// 在展示交互式仪表盘之前发送静默 LLM 上下文
- sendLLMContext(ws, pi);
+ sendDbStatus(pi, ws);
const action = await showDashboard(ctx, ws);
if (!action) return;
- await dispatchAction(action, ctx, ws, pi, rest);
+ await dispatchAction(action, ctx, ws, pi, rest, relatedCache);
break;
}
case "switch":
@@ -113,7 +117,7 @@ export function registerDbCommand(
await handleSchema(ctx, ws, pi, rest[0]);
break;
case "query":
- await handleQuery(ctx, ws, pi, rest.join(" ") || undefined);
+ await handleQuery(ctx, ws, pi, rest.join(" ") || undefined, relatedCache);
break;
case "history":
await handleHistory(ctx, ws, pi, rest[0]);
@@ -125,7 +129,7 @@ export function registerDbCommand(
await handleRelations(ctx, ws, pi, rest);
break;
case "related":
- await handleRelatedBrowser(ctx);
+ await handleRelatedBrowser(ctx, relatedCache);
break;
case "on":
await handleToggle(ctx, true, toggleBaseDir);
@@ -143,8 +147,11 @@ export function registerDbCommand(
// ====== 关联表浏览器 ================================================
/** 打开最近一次关联查询的浏览器;无缓存时提示引导。 */
-async function handleRelatedBrowser(ctx: ExtensionCommandContext): Promise {
- const cache = getLastRelatedCache();
+async function handleRelatedBrowser(
+ ctx: ExtensionCommandContext,
+ relatedCache: RelatedBrowserCacheStore,
+): Promise {
+ const cache = relatedCache.get();
if (!cache) {
ctx.ui.notify(
"没有可浏览的关联表——先执行关联表查询(/db query 选表后选择「📎 是,一起查询关联表」)",
@@ -239,29 +246,6 @@ const DASHBOARD_ACTIONS: DashboardAction[] = [
{ value: "related", label: "📎 关联表浏览器", needsConnection: false },
];
-/** 发送静默上下文消息,让 LLM 知道数据库状态。 */
-function sendLLMContext(ws: DatabaseWorkspaceService, pi: ExtensionAPI): void {
- if (ws.isReady) {
- pi.sendMessage(
- {
- customType: "db-active-db",
- content: `Current database: ${ws.current!.database} (connection: ${ws.current!.connectionId}, environment: ${ws.current!.environment}). Config file: ${ws.configPath}.`,
- display: false,
- },
- { deliverAs: "followUp", triggerTurn: false },
- );
- } else if (ws.isConfigured) {
- pi.sendMessage(
- {
- customType: "db-hint",
- content: `Database connections are configured but no database is selected. Tell the user to run /db switch to connect. Config file: ${ws.configPath}.`,
- display: false,
- },
- { deliverAs: "followUp", triggerTurn: false },
- );
- }
-}
-
/** 构建交互式仪表盘组件。 */
async function showDashboard(
ctx: ExtensionCommandContext,
@@ -357,6 +341,7 @@ async function dispatchAction(
ws: DatabaseWorkspaceService,
pi: ExtensionAPI,
rest: string[],
+ relatedCache: RelatedBrowserCacheStore,
): Promise {
switch (action) {
case "switch":
@@ -372,7 +357,7 @@ async function dispatchAction(
await handleSchema(ctx, ws, pi, rest[0]);
break;
case "query":
- await handleQuery(ctx, ws, pi, rest.join(" ") || undefined);
+ await handleQuery(ctx, ws, pi, rest.join(" ") || undefined, relatedCache);
break;
case "history":
await handleHistory(ctx, ws, pi, rest[0]);
@@ -384,7 +369,7 @@ async function dispatchAction(
await handleRelations(ctx, ws, pi, rest);
break;
case "related":
- await handleRelatedBrowser(ctx);
+ await handleRelatedBrowser(ctx, relatedCache);
break;
}
}
diff --git a/commands/llm-context.ts b/commands/llm-context.ts
new file mode 100644
index 0000000..9259e41
--- /dev/null
+++ b/commands/llm-context.ts
@@ -0,0 +1,66 @@
+/**
+ * 发给 LLM 的静默上下文消息 —— 唯一构建点。
+ *
+ * AGENTS.md 要求这些字符串保持英文且稳定;本模块是它们的单一归属。
+ * index.ts(session_start)、db.ts(仪表盘)、switch.ts(切换后)共用。
+ */
+
+import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
+import type { DatabaseWorkspaceService } from "../state/workspace";
+
+/** 已选择数据库:告知当前目标、配置文件与可用工具。 */
+export function sendActiveDb(pi: ExtensionAPI, ws: DatabaseWorkspaceService): void {
+ const db = ws.current!;
+ pi.sendMessage(
+ {
+ customType: "db-active-db",
+ content:
+ `Current database: ${db.database} (connection: ${db.connectionId}, ` +
+ `environment: ${db.environment}). Config file: ${ws.configPath}. ` +
+ `Use db_query and db_tables to query this database.`,
+ display: false,
+ },
+ { deliverAs: "followUp", triggerTurn: false },
+ );
+}
+
+/** 已配置但未选择数据库:引导用户运行 /db switch。 */
+export function sendConfiguredHint(pi: ExtensionAPI, ws: DatabaseWorkspaceService): void {
+ pi.sendMessage(
+ {
+ customType: "db-hint",
+ content:
+ `Database connections are configured but no database is selected. ` +
+ `Tell the user to run /db switch to connect. Config file: ${ws.configPath}.`,
+ display: false,
+ },
+ { deliverAs: "followUp", triggerTurn: false },
+ );
+}
+
+/**
+ * 按工作空间状态发送对应消息(active 或 hint);两者皆无时不发。
+ * 会话启动与仪表盘入口共用。
+ */
+export function sendDbStatus(pi: ExtensionAPI, ws: DatabaseWorkspaceService): void {
+ if (ws.isReady) {
+ sendActiveDb(pi, ws);
+ } else if (ws.isConfigured) {
+ 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/query.ts b/commands/query.ts
index 5b619db..96c12bc 100644
--- a/commands/query.ts
+++ b/commands/query.ts
@@ -20,7 +20,7 @@ import { READONLY_SQL_RE } from "../connection/sql-policy";
import { formatTableCompact } from "../formatting/result-table";
import { pickTableFuzzy, withLoader } from "./utils";
import type { QueryResultEntryData, RelatedTuiData } from "./renderers";
-import type { RelatedBrowserCache } from "./related-browser";
+import type { RelatedBrowserCacheStore } from "./related-browser";
interface ExecutedResult {
columns: string[];
@@ -77,15 +77,6 @@ export function toRelatedTuiData(related: RelatedResult[]): RelatedTuiData[] {
}));
}
-// ── 最近关联查询缓存(/db related 浏览器入口用)──
-
-let lastRelatedCache: RelatedBrowserCache | null = null;
-
-/** 最近一次关联查询的结果缓存;无关联查询时返回 null。 */
-export function getLastRelatedCache(): RelatedBrowserCache | null {
- return lastRelatedCache;
-}
-
// ── 展示(双受众:TUI 条目 + LLM 上下文)──────────
async function displayQueryResult(
@@ -94,6 +85,7 @@ async function displayQueryResult(
pi: ExtensionAPI,
result: ExecutedResult,
related: RelatedResult[] = [],
+ relatedCache?: RelatedBrowserCacheStore,
): Promise {
const database = ws.current!.database;
try {
@@ -106,7 +98,7 @@ async function displayQueryResult(
const relatedTui = toRelatedTuiData(related);
if (relatedTui.length > 0) {
- lastRelatedCache = { database, sql: result.sql, related: relatedTui };
+ relatedCache?.set({ database, sql: result.sql, related: relatedTui });
}
pi.appendEntry("db-query-result", {
@@ -173,6 +165,7 @@ async function queryByTable(
ws: DatabaseWorkspaceService,
pi: ExtensionAPI,
preSelectedTable?: string,
+ relatedCache?: RelatedBrowserCacheStore,
): Promise {
const table = preSelectedTable ?? (await pickTableFuzzy(ctx, ws, "选择数据表"));
if (!table) return;
@@ -206,7 +199,7 @@ async function queryByTable(
ctx.ui.notify(`查询出错:${err.message}`, "error");
return;
}
- await displayQueryResult(ctx, ws, pi, result, result.related);
+ await displayQueryResult(ctx, ws, pi, result, result.related, relatedCache);
} else {
await executeAndDisplay(ctx, ws, pi, sql);
}
@@ -233,6 +226,7 @@ export async function handleQuery(
ws: DatabaseWorkspaceService,
pi: ExtensionAPI,
tableArg?: string,
+ relatedCache?: RelatedBrowserCacheStore,
): Promise {
if (!ws.isReady) {
ctx.ui.notify("未选择数据库,请先执行 /db switch", "warning");
@@ -247,7 +241,7 @@ export async function handleQuery(
tables = [];
}
if (tables.includes(tableArg)) {
- return await queryByTable(ctx, ws, pi, tableArg);
+ return await queryByTable(ctx, ws, pi, tableArg, relatedCache);
}
if (READONLY_SQL_RE.test(tableArg)) {
return await executeAndDisplay(ctx, ws, pi, tableArg);
@@ -264,5 +258,5 @@ export async function handleQuery(
if (choice === "__sql__") {
return await queryRaw(ctx, ws, pi);
}
- return await queryByTable(ctx, ws, pi, choice);
+ return await queryByTable(ctx, ws, pi, choice, relatedCache);
}
diff --git a/commands/related-browser.ts b/commands/related-browser.ts
index 2418bf9..f21740a 100644
--- a/commands/related-browser.ts
+++ b/commands/related-browser.ts
@@ -33,6 +33,22 @@ export interface RelatedBrowserCache {
related: RelatedTuiData[];
}
+/**
+ * 最近关联查询缓存的持有者——经调用链注入(db.ts 注册时创建、
+ * 传给 query 路径写入、related 路径读取),替代 module 级单例。
+ */
+export class RelatedBrowserCacheStore {
+ private current: RelatedBrowserCache | null = null;
+
+ set(cache: RelatedBrowserCache): void {
+ this.current = cache;
+ }
+
+ get(): RelatedBrowserCache | null {
+ return this.current;
+ }
+}
+
// ── 表切换 reducer(纯函数,可单测)────────────────
export interface BrowserNav {
diff --git a/commands/switch.ts b/commands/switch.ts
index 65d4d7e..3686b64 100644
--- a/commands/switch.ts
+++ b/commands/switch.ts
@@ -5,6 +5,7 @@
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";
export const STATUS_KEY = "db-workspace";
@@ -81,14 +82,7 @@ export async function showWorkspacePanel(
// display: false 让它不进聊天;triggerTurn 让 AI
// 主动提议帮助建立第一个连接。
if (!ws.isConfigured && pi) {
- pi.sendMessage(
- {
- customType: "db-hint",
- content: `No database connections are configured yet. Help the user create their first connection in ${ws.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 },
- );
+ sendUnconfiguredHint(pi, ws.configPath);
}
}
@@ -180,14 +174,7 @@ export async function handleSwitch(
// 告知 LLM 当前激活的数据库,避免它猜测。
// display: false 避免冗余消息污染聊天。
- pi.sendMessage(
- {
- customType: "db-active-db",
- content: `Current database: ${database} (connection: ${connectionId}, environment: ${env}). Use db_query and db_tables to query this database.`,
- display: false,
- },
- { deliverAs: "followUp", triggerTurn: false },
- );
+ sendActiveDb(pi, ws);
ctx.ui.notify(`已连接:${env}/${database} @ ${connectionId}`, "info");
}
diff --git a/index.ts b/index.ts
index 7fac5b5..ceac39c 100644
--- a/index.ts
+++ b/index.ts
@@ -7,6 +7,7 @@ import { readToggle } from "./state/extension-toggle";
import { registerDbCommand, restoreStatusBar } from "./commands/db";
import { registerRenderers } from "./commands/renderers";
import { registerDbTools, applyInitialToolSet } from "./tools/db-tools";
+import { sendDbStatus } from "./commands/llm-context";
const baseDir = dirname(fileURLToPath(import.meta.url));
@@ -57,27 +58,7 @@ export default function (pi: ExtensionAPI) {
restoreStatusBar(ws, ctx);
// 恢复会话时告知 LLM 当前激活的数据库,
// 避免它通过一次失败的调用去发现。
- if (ws.isReady) {
- const db = ws.current!;
- pi.sendMessage(
- {
- customType: "db-active-db",
- content: `Current database: ${db.database} (connection: ${db.connectionId}, environment: ${db.environment}). Config file: ${ws.configPath}.`,
- display: false,
- },
- { deliverAs: "followUp", triggerTurn: false },
- );
- } else if (ws.isConfigured) {
- // 已配置但未切换——让 AI 知道,以便协助用户选择数据库。
- pi.sendMessage(
- {
- customType: "db-hint",
- content: `Database connections are configured but no database is selected. Tell the user to run /db switch to connect. Config file: ${ws.configPath}.`,
- display: false,
- },
- { deliverAs: "followUp", triggerTurn: false },
- );
- }
+ sendDbStatus(pi, ws);
});
// 关闭时清理
From f433e2ca94cf9c12c7fbecb8a2e57a1713557d85 Mon Sep 17 00:00:00 2001
From: zavier <765324639@qq.com>
Date: Sat, 1 Aug 2026 20:08:08 +0800
Subject: [PATCH 3/3] =?UTF-8?q?chore(docs):=20=E4=BF=AE=E6=AD=A3=E5=B7=B2?=
=?UTF-8?q?=E5=AE=9E=E6=96=BD=E6=96=87=E6=A1=A3=E8=A1=A8=E5=A4=B4=EF=BC=8C?=
=?UTF-8?q?=E5=88=A0=E9=99=A4=E8=BF=87=E6=9C=9F=E5=A4=8D=E7=8E=B0=E8=84=9A?=
=?UTF-8?q?=E6=9C=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
table-tool-consolidation / extension-toggle 两份文档表头更新为已实施。
删除与 getCompletions 逐字复制的 repro-autocomplete.mjs——真实函数
已由 __tests__/db-command.test.ts 覆盖,保留只会静默漂移。
---
docs/extension-toggle.md | 2 +-
docs/table-tool-consolidation.md | 2 +-
scripts/repro-autocomplete.mjs | 81 --------------------------------
3 files changed, 2 insertions(+), 83 deletions(-)
delete mode 100644 scripts/repro-autocomplete.mjs
diff --git a/docs/extension-toggle.md b/docs/extension-toggle.md
index 779292f..a209195 100644
--- a/docs/extension-toggle.md
+++ b/docs/extension-toggle.md
@@ -1,6 +1,6 @@
# 扩展开关设计:Extension Toggle
-> 状态:设计稿(待评审)
+> 状态:已实施(`/db on|off` 位于 `commands/db.ts`)
> 目标版本:0.9.0
## 1. 背景与目标
diff --git a/docs/table-tool-consolidation.md b/docs/table-tool-consolidation.md
index 40d1c6a..6ca7ef5 100644
--- a/docs/table-tool-consolidation.md
+++ b/docs/table-tool-consolidation.md
@@ -1,6 +1,6 @@
# 表工具合并方案:db_list_tables + db_table_schema → db_tables
-> 状态:待实施(目标版本 v0.8.0,与 tool-loading 合并发布)
+> 状态:已实施(`db_tables` 位于 `tools/db-tools.ts`)
> 前置:v0.8.0 Dynamic Tool Loading 已完成(`docs/tool-loading-redesign.md`)
## 1. 背景与目标
diff --git a/scripts/repro-autocomplete.mjs b/scripts/repro-autocomplete.mjs
deleted file mode 100644
index eaae4f5..0000000
--- a/scripts/repro-autocomplete.mjs
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * 临时复现脚本:模拟 pi 编辑器对 /db 命令参数补全的完整链路。
- * 用 pi-tui 真实的 CombinedAutocompleteProvider + db.ts 的补全逻辑。
- * 用法:node scripts/repro-autocomplete.mjs
- */
-import { CombinedAutocompleteProvider } from "@earendil-works/pi-tui";
-
-// 从 commands/db.ts 复制 getCompletions 核心逻辑(保持逐行一致)
-const SUBCOMMANDS = [
- "switch",
- "add",
- "tables",
- "schema",
- "query",
- "history",
- "favorite",
- "relations",
- "on",
- "off",
-];
-
-function getCompletions(prefix) {
- const parts = prefix.trim().split(/\s+/);
- const hasTrailingSpace = prefix.endsWith(" ");
- const sub = parts[0];
- const partial = hasTrailingSpace ? "" : (parts[1] ?? "");
-
- const subSubs = {
- favorite: ["add"],
- relations: ["add", "remove", "discover", "er-diagram"],
- };
-
- if (parts.length === 1 && SUBCOMMANDS.includes(sub) && sub in subSubs) {
- return subSubs[sub]
- .filter((s) => s.startsWith(partial))
- .map((s) => ({ value: `${sub} ${s} `, label: s }));
- }
-
- if (parts.length === 1 && !hasTrailingSpace) {
- return SUBCOMMANDS.filter((s) => s.startsWith(sub)).map((s) => ({
- value: s + " ",
- label: s,
- }));
- }
-
- if (subSubs[sub] && parts.length === 2 && !hasTrailingSpace) {
- const filtered = subSubs[sub].filter((s) => s.startsWith(partial));
- if (filtered.length > 0) {
- return filtered.map((s) => ({ value: `${sub} ${s} `, label: s }));
- }
- }
-
- return null;
-}
-
-const provider = new CombinedAutocompleteProvider(
- [{ name: "db", getArgumentCompletions: getCompletions }],
- "/tmp",
- null,
-);
-
-const cases = [
- ["/db ", 4, false, "自然输入(空格后)"],
- ["/db o", 5, false, "自然输入 o"],
- ["/db on", 6, false, "自然输入 on"],
- ["/db s", 5, false, "自然输入 s(对照)"],
- ["/db switch", 9, false, "自然输入 switch(对照)"],
- ["/db o", 5, true, "Tab 强制(force)"],
- ["/db on", 6, true, "Tab 强制 on(force)"],
-];
-
-for (const [text, col, force, label] of cases) {
- const res = await provider.getSuggestions([text], 0, col, {
- signal: new AbortController().signal,
- force,
- });
- console.log(
- `${label.padEnd(22)} "${text}" force=${force} →`,
- res ? JSON.stringify(res.items.map((i) => i.value)) : "null(无候选)",
- );
-}