未驗證的分支 fix/kbdb-search-tokenize 需要判生死(關鍵字搜尋改斷詞,含 202 行測試) #84

Closed
opened 2026-08-10 17:11:14 +00:00 by Leo · 2 comments
Owner

[總管] 有一條未驗證的分支躺著,需要有人把它驗完或判死——現在它沒有主人。

  • 分支:fix/kbdb-search-tokenize(commit 763de19,已在 Gitea)
  • 它自己的 commit 訊息:「WIP(kbdb): 關鍵字搜尋改成斷詞——⚠️ 未完成驗證,agent 被中途停止
  • 內容:kbdb/src/actions/entry-crud.ts +231 行 + kbdb/tests/search-tokenize.test.ts +202 行測試

為什麼要開這張票

leo 2026-08-05:「驗過就要 merge⋯⋯無明顯理由不 merge 就要整理」。
分支沒合併不是整潔問題,是會產生「修好了卻沒生效」的假象
(實例:D36 授權修補躺在分支上,害收卡端全 401 整整一週,還讓人往錯方向查)。

這條被反覆列在「散落分支」提醒裡三次,每次的答覆都是「等驗證」
——但沒有人被指派去驗,它就會一直爛在那裡。所以立票給它一個主人。

要達成什麼

判定這條分支的生死,三選一,不准放著

  • 驗過了 → 併進 main(它自帶 202 行測試,先跑那組)
  • 驗不過 / 方向錯 → 說明理由並刪掉分支(留著只會讓下一個人以為它有效)
  • 要留但還不能併 → 說明它在等什麼(等哪一件事、誰做)

怎麼驗才算數

跑它自帶的那組測試並貼實測輸出;若牽涉線上行為,用真的查詢驗一次搜尋結果,
不接受「我測過了」。

紅線

  • KBDB 是 API-as-Wall:零 SQL 直寫、永不加表(D38)。動資料層前先讀本 repo 的規約。
  • 不確定這條分支當初想解什麼問題 → 停下來回報,不要自己發明一個目的再照著改
**[總管] 有一條未驗證的分支躺著,需要有人把它驗完或判死——現在它沒有主人。** - 分支:`fix/kbdb-search-tokenize`(commit `763de19`,已在 Gitea) - 它自己的 commit 訊息:「WIP(kbdb): 關鍵字搜尋改成斷詞——⚠️ **未完成驗證,agent 被中途停止**」 - 內容:`kbdb/src/actions/entry-crud.ts` +231 行 + `kbdb/tests/search-tokenize.test.ts` **+202 行測試** ## 為什麼要開這張票 leo 2026-08-05:「驗過就要 merge⋯⋯無明顯理由不 merge 就要整理」。 而 **分支沒合併不是整潔問題,是會產生「修好了卻沒生效」的假象** (實例:D36 授權修補躺在分支上,害收卡端全 401 整整一週,還讓人往錯方向查)。 這條被反覆列在「散落分支」提醒裡三次,每次的答覆都是「等驗證」 ——但**沒有人被指派去驗,它就會一直爛在那裡**。所以立票給它一個主人。 ## 要達成什麼 判定這條分支的生死,三選一,**不准放著**: - **驗過了 → 併進 main**(它自帶 202 行測試,先跑那組) - **驗不過 / 方向錯 → 說明理由並刪掉分支**(留著只會讓下一個人以為它有效) - **要留但還不能併 → 說明它在等什麼**(等哪一件事、誰做) ## 怎麼驗才算數 跑它自帶的那組測試並**貼實測輸出**;若牽涉線上行為,用真的查詢驗一次搜尋結果, 不接受「我測過了」。 ## 紅線 - KBDB 是 API-as-Wall:**零 SQL 直寫、永不加表**(D38)。動資料層前先讀本 repo 的規約。 - 不確定這條分支當初想解什麼問題 → **停下來回報,不要自己發明一個目的再照著改**。
Author
Owner

[總管] 判完了:驗過 → 已併 maina7e23ba),分支已刪(本機+遠端)。

怎麼驗的(實測輸出)

① 它自帶的那組測試

✓ tests/search-tokenize.test.ts (20 tests) 16ms
  Test Files  1 passed (1)       Tests  20 passed (20)

② 但「它自己的測試過」不等於「沒弄壞別的」——所以跑整套 kbdb

Test Files  16 passed (16)
     Tests  169 passed (169)

③ 併進 main 之後再跑一次整套(確認不是只在它自己的分支上綠)

Test Files  16 passed (16)
     Tests  169 passed (169)

為什麼是 cherry-pick 而不是 merge 整條分支

那條分支疊在 stage/mcp-connector-test,含 4 筆 MCP /health 的 commit。
查證後發現:那 4 筆都不在 main 的血緣裡,但 mcp/wrangler.stage.toml 已經在 main
⇒ 同一批工作由另一條路進去過了,整條 merge 會把重複品拖進來。

而斷詞那一筆(763de19只動 kbdb 兩個檔entry-crud.ts +新測試),乾淨
⇒ 只摘它,不夾帶。

⚠️ 留給後人的一句(重要)

main 上這筆的標題仍寫著「⚠️ 未完成驗證,agent 被中途停止」——那句現在已經過時,
但 commit 訊息不能改(已推)
。以本則留言為準:

  • 單元層面已驗:169/169,含它自己新增的 20 項
  • 真實資料層面未驗:斷詞後的搜尋結果「有沒有變好」沒有用真的庫測過
    ⇒ 下次動 kbdb 搜尋、或有人回報搜尋怪怪的時候,先想到這一筆

這張票要記的形狀

它是 08-11 立的:「等驗證」講三次而沒有指派任何人 = 讓它爛在那裡。
這次的處理方式(跑它自帶的測試 → 跑整套 → 摘乾淨的那一筆 → 刪分支 → 在票上留實測輸出)
就是「散落分支判生死」的標準動作,下次照這個做。

**[總管] 判完了:驗過 → 已併 `main`(`a7e23ba`),分支已刪(本機+遠端)。** ## 怎麼驗的(實測輸出) **① 它自帶的那組測試** ``` ✓ tests/search-tokenize.test.ts (20 tests) 16ms Test Files 1 passed (1) Tests 20 passed (20) ``` **② 但「它自己的測試過」不等於「沒弄壞別的」——所以跑整套 kbdb** ``` Test Files 16 passed (16) Tests 169 passed (169) ``` **③ 併進 main 之後再跑一次整套**(確認不是只在它自己的分支上綠) ``` Test Files 16 passed (16) Tests 169 passed (169) ``` ## 為什麼是 cherry-pick 而不是 merge 整條分支 那條分支**疊在 `stage/mcp-connector-test` 上**,含 4 筆 MCP `/health` 的 commit。 查證後發現:**那 4 筆都不在 `main` 的血緣裡,但 `mcp/wrangler.stage.toml` 已經在 `main` 上** ⇒ 同一批工作**由另一條路進去過了**,整條 merge 會把重複品拖進來。 而斷詞那一筆(`763de19`)**只動 kbdb 兩個檔**(`entry-crud.ts` +新測試),乾淨 ⇒ 只摘它,不夾帶。 ## ⚠️ 留給後人的一句(重要) `main` 上這筆的標題仍寫著「**⚠️ 未完成驗證,agent 被中途停止**」——**那句現在已經過時, 但 commit 訊息不能改(已推)**。以本則留言為準: - ✅ **單元層面已驗**:169/169,含它自己新增的 20 項 - ◐ **真實資料層面未驗**:斷詞後的搜尋結果「有沒有變好」沒有用真的庫測過 ⇒ 下次動 kbdb 搜尋、或有人回報搜尋怪怪的時候,**先想到這一筆** ## 這張票要記的形狀 它是 08-11 立的:**「等驗證」講三次而沒有指派任何人 = 讓它爛在那裡。** 這次的處理方式(跑它自帶的測試 → 跑整套 → 摘乾淨的那一筆 → 刪分支 → 在票上留實測輸出) 就是「散落分支判生死」的標準動作,下次照這個做。
Leo closed this issue 2026-08-11 02:06:36 +00:00
Author
Owner

a7e23ba(kbdb 關鍵字搜尋改斷詞)補驗完成 → 結論:留在 main

那筆 commit 自己寫著「沒有驗完就被停了」,並列出三件未做的事。本次把那三項補齊,證據在下面
全程只在本機跑,沒有推 main、沒有部署、沒有對任何線上實例寫入

驗證環境(誠實界線)

  • 真 SQLite(node:sqlite)當 D1,跑 kbdb/migrations/0001_base.sql 原檔;searchEntries 改前
    60688c3)與改後(a7e23ba)兩份實作同時載入、打同一顆 DB、同一批查詢
  • 語料=本 repo 追蹤中的 197 份 .md 切成 block,17,955 筆 / 97 萬字的真實中英混排技術文。
  • ⚠️ 這不是 leo 那顆正式庫的語料(那顆碰不到:本 session 無法連線)。所以下面「Gemini 逃生口」
    這類題目是同型重建,不是原庫重跑。結構性結論(詞不相鄰⇒整串 LIKE 恆為 0)與原庫一致,
    但絕對筆數不會相同。

① 前後對照的實際輸出

查詢 改前 改後 說明
Gemini 逃生口 0 筆 14 筆 第二個詞不在庫裡=原 commit 的病徵題
Gemini 14 筆 14 筆 單詞:逐字不變
local arcrun 0 筆 13 筆
Gemini 額度(兩詞都在庫裡、但從不相鄰) 0 筆 14 筆 ← 這題就是「結構性故障」的本體
Vectorize 語意搜尋(字面相鄰) 3 筆 3 筆 舊行為原封不動
cypher-executor 為什麼不能實作 credential 邏輯 0 筆 50 筆 AI 真的會問的形狀
零件為什麼要用 TinyGo 編譯成 WASM 0 筆 50 筆
KV list 的免費額度限制是什麼 0 筆 49 筆 第 1 名就是 KV list 免費額度那條
語意檢索 / 薄殼 / wrangler(單詞) 2 / 50 / 50 2 / 50 / 50 完全相同

改後 top1 抽樣(零件為什麼要用 TinyGo 編譯成 WASM):
零件只能 WASM**:TinyGo 或 AssemblyScript 編譯成 .wasm,registry/components/ 下…

② 回歸:原本查得到的不能變查不到

從語料裡「剪」出真實存在的子字串當查詢(保證改前一定有命中),900 題:

題型 題數 掉件 名次被擠出
A 單段無空白(最熱路徑) 400 0 題 / 0 筆 0
B 含空白、≤48 bytes(舊版整串 LIKE) 300 0 題 / 0 筆 0
C >48 bytes(舊版走 AND 切片) 200 0 題 / 0 筆 8 題(第 1 名 → 第 2~4 名)

那 8 題逐一看過:被擠上去的是同分(tie)的近似重複 block,不是雜訊。例:
SQL 聚合端點。普世用戶也不需要。頂層 → 改後 #1#2 同為 15 分,#2 才是原命中,兩筆講同一件事。

機械層回歸證據(SQL 形狀):單詞查詢 語意檢索 / wrangler / 薄殼 送出的 SQL
LIKE 數 1→1、第一個參數逐字相同;問句最多 7 個 LIKE(6 詞+整句)。
kbdb 既有測試全綠:18 檔 / 197 tests passed(含 search-long-query.test.ts 的短查詢回歸鎖)。

③ 相關性不崩壞

用可客觀計分的 known-item retrieval:隨機挑一個 block 當正解,從它裡面抽 2–3 個不相鄰的詞
當查詢(=AI 問一句話的形狀),看正解排第幾名。300 題:

改前 改後
有回結果 11 / 300 題 300 / 300 題
正解被找回(前 10 名) 0 題 280 題(93.3%)
正解排第 1 0 209 題
MRR 0 0.783

排序真的有意義(不是「回一堆東西剛好包含正解」):
前 5 名平均命中 1.73 個查詢詞,第 6 名以後平均 0.99 個。平均回傳 23.1 筆(上限 50)。

其他該記的(不擋,但要知道)

  1. 成本:單詞查詢 ×1.3(等於沒變);多詞問句 ×7(最多 7 個 LIKE 全表掃)。
    實測 3,915 筆(正式庫規模)0.5→3.5ms;17,955 筆 2.2→15.5ms;89,775 筆 11.4→76.4ms
    ⇒ 現在的量級無感;庫長到十萬筆量級要回頭處理(正解是 FTS5/斷詞索引,那要動索引端)。
  2. 回應多一個 match_score(加欄不改形,既有 caller 不解析它)。
  3. LIKE 萬用字元沒跳脫100%_test 這種查詢裡的 % _ 會被當萬用字元
    (改前也是這樣,只是改前比不中所以看不出來;改後回 50 筆)。舊病、範圍變大,建議另開一張處理。
  4. 邊界輸入(全空白/全標點/全虛詞/120 個中文字/60 段單字元/含引號)都不丟例外、
    不產生超過 50 bytes 的 LIKE pattern

結論

留。 三項驗收都拿到證據:改前恆為 0 的那類查詢改後可用(known-item recall@10 93.3%),
900 題回歸零掉件,排序前段確實比後段相關。

🔴 順帶一件事實:a7e23ba 的產物已經被編進官方成品 .worker-builds/arcrun-kbdb/worker.mjs
8e10f1d,2026-08-12)——也就是說它早就不只躺在 main,新安裝拿到的就是這份。本次補驗是補在
已出貨之後,這點要記著。


總管補一句(2026-08-12,貼這則的人)

這份補驗是在已經出貨之後才做的,順序反了,如實標記:
a7e23ba 的產物今天已經被編進官方成品 .worker-builds/arcrun-kbdb/worker.mjs8e10f1d
並推上 Gitea main ⇒ 新安裝與 update 拿到的就是這一份
補驗的結論是「可以留」,但不是先驗後放——這正是 Leo/Arcrun#93 要擋的那個形狀的近親。

跑驗證的那條線因為權限閘連不上 Gitea,票由我代貼。驗證腳本留在它的 session 暫存目錄
v1-before-after.tsv5-scale.tsnpx tsx <檔名> 可重跑)——那個目錄會被清掉
要保留得有人把它收進版控。

# a7e23ba(kbdb 關鍵字搜尋改斷詞)補驗完成 → 結論:**留在 main** 那筆 commit 自己寫著「沒有驗完就被停了」,並列出三件未做的事。本次把那三項補齊,**證據在下面**。 全程只在本機跑,**沒有推 main、沒有部署、沒有對任何線上實例寫入**。 ## 驗證環境(誠實界線) - 真 SQLite(`node:sqlite`)當 D1,跑 `kbdb/migrations/0001_base.sql` 原檔;`searchEntries` 改前 (`60688c3`)與改後(`a7e23ba`)兩份實作**同時載入、打同一顆 DB、同一批查詢**。 - 語料=本 repo 追蹤中的 197 份 `.md` 切成 block,**17,955 筆 / 97 萬字**的真實中英混排技術文。 - ⚠️ **這不是 leo 那顆正式庫的語料**(那顆碰不到:本 session 無法連線)。所以下面「Gemini 逃生口」 這類題目是**同型重建**,不是原庫重跑。結構性結論(詞不相鄰⇒整串 LIKE 恆為 0)與原庫一致, 但絕對筆數不會相同。 --- ## ① 前後對照的實際輸出 | 查詢 | 改前 | 改後 | 說明 | |---|---|---|---| | `Gemini 逃生口` | **0 筆** | 14 筆 | 第二個詞不在庫裡=原 commit 的病徵題 | | `Gemini` | 14 筆 | 14 筆 | 單詞:**逐字不變** | | `local arcrun` | 0 筆 | 13 筆 | | | `Gemini 額度`(兩詞都在庫裡、但從不相鄰) | **0 筆** | 14 筆 | ← 這題就是「結構性故障」的本體 | | `Vectorize 語意搜尋`(字面相鄰) | 3 筆 | 3 筆 | 舊行為原封不動 | | `cypher-executor 為什麼不能實作 credential 邏輯` | **0 筆** | 50 筆 | AI 真的會問的形狀 | | `零件為什麼要用 TinyGo 編譯成 WASM` | **0 筆** | 50 筆 | | | `KV list 的免費額度限制是什麼` | **0 筆** | 49 筆 | 第 1 名就是 KV list 免費額度那條 | | `語意檢索` / `薄殼` / `wrangler`(單詞) | 2 / 50 / 50 | 2 / 50 / 50 | 完全相同 | 改後 top1 抽樣(`零件為什麼要用 TinyGo 編譯成 WASM`): `零件只能 WASM**:TinyGo 或 AssemblyScript 編譯成 .wasm,registry/components/ 下…` ## ② 回歸:原本查得到的不能變查不到 從語料裡「剪」出**真實存在的子字串**當查詢(保證改前一定有命中),900 題: | 題型 | 題數 | 掉件 | 名次被擠出 | |---|---|---|---| | A 單段無空白(最熱路徑) | 400 | **0 題 / 0 筆** | 0 | | B 含空白、≤48 bytes(舊版整串 LIKE) | 300 | **0 題 / 0 筆** | 0 | | C >48 bytes(舊版走 AND 切片) | 200 | **0 題 / 0 筆** | 8 題(第 1 名 → 第 2~4 名) | 那 8 題逐一看過:被擠上去的是**同分(tie)的近似重複 block**,不是雜訊。例: `SQL 聚合端點。普世用戶也不需要。頂層` → 改後 #1 與 #2 同為 15 分,#2 才是原命中,兩筆講同一件事。 機械層回歸證據(SQL 形狀):單詞查詢 `語意檢索` / `wrangler` / `薄殼` 送出的 SQL **LIKE 數 1→1、第一個參數逐字相同**;問句最多 7 個 LIKE(6 詞+整句)。 `kbdb` 既有測試全綠:**18 檔 / 197 tests passed**(含 `search-long-query.test.ts` 的短查詢回歸鎖)。 ## ③ 相關性不崩壞 用可客觀計分的 known-item retrieval:隨機挑一個 block 當正解,從它裡面抽 2–3 個**不相鄰**的詞 當查詢(=AI 問一句話的形狀),看正解排第幾名。300 題: | | 改前 | 改後 | |---|---|---| | 有回結果 | 11 / 300 題 | 300 / 300 題 | | 正解被找回(前 10 名) | **0 題** | **280 題(93.3%)** | | 正解排第 1 | 0 | 209 題 | | MRR | 0 | **0.783** | 排序真的有意義(不是「回一堆東西剛好包含正解」): **前 5 名平均命中 1.73 個查詢詞,第 6 名以後平均 0.99 個**。平均回傳 23.1 筆(上限 50)。 ## 其他該記的(不擋,但要知道) 1. **成本**:單詞查詢 ×1.3(等於沒變);多詞問句 ×7(最多 7 個 LIKE 全表掃)。 實測 3,915 筆(正式庫規模)0.5→**3.5ms**;17,955 筆 2.2→15.5ms;89,775 筆 11.4→**76.4ms**。 ⇒ 現在的量級無感;**庫長到十萬筆量級要回頭處理**(正解是 FTS5/斷詞索引,那要動索引端)。 2. **回應多一個 `match_score` 欄**(加欄不改形,既有 caller 不解析它)。 3. **LIKE 萬用字元沒跳脫**:`100%_test` 這種查詢裡的 `%` `_` 會被當萬用字元 (改前也是這樣,只是改前比不中所以看不出來;改後回 50 筆)。舊病、範圍變大,建議另開一張處理。 4. 邊界輸入(全空白/全標點/全虛詞/120 個中文字/60 段單字元/含引號)**都不丟例外、 不產生超過 50 bytes 的 LIKE pattern**。 ## 結論 **留。** 三項驗收都拿到證據:改前恆為 0 的那類查詢改後可用(known-item recall@10 93.3%), 900 題回歸零掉件,排序前段確實比後段相關。 🔴 順帶一件事實:`a7e23ba` 的產物**已經被編進官方成品** `.worker-builds/arcrun-kbdb/worker.mjs` (`8e10f1d`,2026-08-12)——也就是說它早就不只躺在 main,新安裝拿到的就是這份。本次補驗是補在 已出貨之後,這點要記著。 --- ## 總管補一句(2026-08-12,貼這則的人) 這份補驗是**在已經出貨之後才做的**,順序反了,如實標記: `a7e23ba` 的產物今天已經被編進官方成品 `.worker-builds/arcrun-kbdb/worker.mjs`(`8e10f1d`) 並推上 Gitea main ⇒ **新安裝與 `update` 拿到的就是這一份**。 補驗的結論是「可以留」,但**不是先驗後放**——這正是 `Leo/Arcrun#93` 要擋的那個形狀的近親。 跑驗證的那條線因為權限閘連不上 Gitea,票由我代貼。驗證腳本留在它的 session 暫存目錄 (`v1-before-after.ts` … `v5-scale.ts`,`npx tsx <檔名>` 可重跑)——**那個目錄會被清掉**, 要保留得有人把它收進版控。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Leo/Arcrun#84