chore: template 1.16.0——wiki 讀取兩支 hook(查詢即搜尋 wiki+subagent 自動注入)

leo 2026-07-20:「花很多力氣去產生 wiki,最重要的就是要可以查詢,
結果要查的時候就跳過,那就白寫了」

- wiki-first-search.sh(Grep|Glob|Read):查 code 的當下用同組關鍵字 grep wiki,只推命中行
- subagent-wiki-guard.sh(Task):查證類任務自動注入「先查 wiki」給 subagent
- update.sh/install.sh 來源改指 Gitea(原指 GitHub uncle6me-web 已 suspend=自動更新早就死了)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-21 01:40:57 +08:00
parent d2618758e2
commit f89ccdebf3
15 changed files with 625 additions and 53 deletions
@@ -1,6 +1,10 @@
---
status: draft # active | draft | paused | closed(生命週期鐵律見 ../SDD-LIFECYCLE.md
superseded_by: "" # closed 且被取代時填接替的 SDD 資料夾名
---
# [子系統名稱] — Design
> 狀態:[草稿 / 審核中 / 已採納 / 已廢棄]
> 建立:[YYYY-MM-DD] | 最後更新:[YYYY-MM-DD]
> 負責人:[名稱]
@@ -0,0 +1,81 @@
# Logseq 任務 marker 解析(單一真相源)
> **這是「Logseq 原生任務語法」解析的唯一權威規格。** 任何要從 Logseq graph
> 抓任務狀態的功能,一律 import 這份、不得各寫一份自己的 mapping。
>
> **已知兩個消費者**(共用同一套解析,見各自 issue):
> 1. **vault 萃取**`/wiki-extract`template#5):marker → 卡片 frontmatter `task_status`。
> 2. **tasks→Project 投影**`system-dev/workflows/tasks-project-sync.*`template#4):
> 當投影來源是 Logseq graphnotes/kb)時,用這份判斷任務與狀態。
>
> 兩者**只共用「怎麼 parse」**(哪幾行是任務、marker 是什麼、正規狀態是什麼、跳過什麼);
> parse 完各自要「拿狀態做什麼」(寫卡 vs 投影 issue)不同,那部分各管各的。
---
## 為什麼不是 GFM checkbox(規格更正,leo 2026-07-04 發現)
Logseq 的原生任務**不是** GFM 的 `- [ ]` / `- [x]`,而是**大寫 marker 開頭的 block**
```
- TODO AI 查看 leo21c 內所有 Repo,找到本地 Repo 搬到 Gitea
- DOING 建立知識總庫,可查所有子庫
- DONE 手機和電腦 Logseq 可以被放進知識總庫
```
若照舊規格只抓 `- [ ]` checkbox**notes / kb 兩個 Logseq graph 的任務會全數漏抓**。
> 兩種源、兩套語法、同一條下游管線:
> - **SDD `tasks.md`**(各 repo `system-dev/docs/3-specs/**`)→ GFM checkbox(現行不變)。
> - **Logseq graphnotes / kb** → 本檔的大寫 marker。
---
## 解析規格
### 1. 任務行辨識(regex
```
^\s*- (TODO|DOING|NOW|LATER|WAITING|DONE|CANCELED|CANCELLED)\s+
```
- marker 必須是 block`-` bullet)的**開頭第一個 token**、全大寫、後接空白。
- `CANCELED` 與英式 `CANCELLED` 皆收(Logseq 兩種都產)。
- marker 後面到行尾(或到子 bullet 之前)是**任務內文**。
### 2. marker → 正規狀態(task_status
| Logseq marker | 正規 task_status |
|---------------|------------------|
| `TODO``LATER` | `todo` |
| `DOING``NOW` | `in-progress` |
| `WAITING` | `blocked` |
| `DONE` | `done` |
| `CANCELED``CANCELLED` | `closed` |
> `LATER`/`NOW` 是 Logseq「排程視圖」用的同義 markerLATER≈TODO、NOW≈DOING),
> 正規化後與 TODO/DOING 併軌,下游不必區分。
### 3. 必須跳過的東西(別當任務內文)
Logseq 的任務 block 底下常掛時間戳與屬性行,這些**不是內文**,解析時整段略過:
- **`:LOGBOOK:``:END:` 區塊**:marker 被點擊計時產生的時間戳紀錄。
遇到 `:LOGBOOK:` 那行起、到 `:END:` 那行止(含兩端),整塊丟掉。
- **屬性行 `key:: value`**:如 `collapsed:: true``id:: 65a...``SCHEDULED:: <...>`
`DEADLINE:: <...>`。凡符合 `^\s*[\w-]+:: ` 的行都是屬性,不是內文。
`SCHEDULED`/`DEADLINE` 的日期若下游要用可另抓,但**不得當任務描述文字**。)
### 4. 巢狀子 bullet
任務 block 底下縮排的子 bullet 是該任務的補充說明(非獨立任務,除非子 bullet 自己也帶 marker)。
萃取時可併入該任務的描述脈絡;投影時只取母 block 那行當任務標題。
---
## 自檢(實作或 LLM 執行前跑一遍)
- [ ] 用的是大寫 marker regex**不是** `- [ ]` checkbox。
- [ ] 八個 marker 全部覆蓋(含 `LATER`/`NOW`/`CANCELLED` 別漏)。
- [ ] `:LOGBOOK:...:END:``key:: value` 屬性行有跳過,沒混進任務文字。
- [ ] 狀態用上表**正規名**`todo`/`in-progress`/`blocked`/`done`/`closed`),不是原始 marker 字面。
@@ -0,0 +1,57 @@
# FEATURE REQUEST: KBDB 應提供 upsert block endpoint(目前只能 client 端拼 GET+PATCH/POST
> 日期:2026-05-29
> 來源:arcrun Phase 2(降級假零件成 recipe
> 類型:API 缺口 —— upsert 語義目前不存在於 KBDB,被迫由 client 端拼湊
> 關聯:[BUG-2026-05-29-patch-blocks-403-different-org.md](./BUG-2026-05-29-patch-blocks-403-different-org.md)(拼湊路徑的 PATCH 那段還壞著)
---
## 問題
arcrun 原本有一個 `kbdb_upsert_block` 零件,行為是「依 page_name + user_id 查找,有就更新、沒有就新建」。但它**完全是 client 端拼湊**
```
GET /blocks?page_name=X&limit=10 # 查找
→ client 端 filter user_id 找第一筆
→ 找到:PATCH /blocks/:id # 更新
→ 沒找到:POST /blocks # 新建
```
KBDB **沒有** upsert 語義的 endpoint。實測(同一把 key):
| 探測 | HTTP |
|---|---|
| `PUT /blocks` | 404 |
| `POST /blocks/upsert` | 404 |
| `PUT /blocks/upsert` | 404 |
## 為什麼這是 KBDB 該補的、不是 arcrun 該拼的
arcrun 的設計原則是**薄殼 / 薄 API**:arcrun 只幫既有 API 套一層 recipeendpoint + auth),**不無中生有功能**。
「先查再分支寫」這套 upsert 邏輯,是在 client 端**變出 KBDB 沒有的功能**。這有幾個壞處:
1. **競態(race condition**GET 和後續 PATCH/POST 之間,別人可能插入同 page_name 的 block,造成重複或覆寫。只有 KBDB server 端用單一交易(upsert / `ON CONFLICT`)才能正確。
2. **語義碎裂**:每個 clientarcrun / 其他 SDK)各自拼一套 upsert,filter 規則(怎麼算「同一筆」)可能不一致。
3. **拼湊路徑現在還壞著**:它依賴 PATCH /blocks/:id,而那個 endpoint 目前回 403(見關聯 bug)。
## 建議
KBDB 提供一個原生 upsert endpoint,例如:
```
POST /blocks/upsert
Body: { page_name, user_id, content, type, source, tags, ... }
語義:依 (page_name, user_id) 找唯一 block —— 存在則更新、不存在則建立(單一交易,server 端 ON CONFLICT
回應:{ id, action: "created" | "updated" }
```
有了它之後:
- arcrun 端只要建一個 `recipe:kbdb_upsert` 指向 `POST /blocks/upsert`,套殼即可,跟其他 5 個 KBDB recipe 一致。
- 競態由 KBDB server 端交易保證,client 不再拼湊。
## arcrun 端現狀(等 KBDB
- 其餘 5 個 KBDB 操作已降級成 recipe 並驗收:`kbdb_get`(200) / `kbdb_create_block`(201) / `kbdb_ingest`(201) / `kbdb_delete`(200) 綠;`kbdb_patch_block` 因上述 403 bug 待驗。
- `kbdb_upsert_block` **暫不降級、源碼暫留**,等 KBDB 出 `POST /blocks/upsert` 後改建 `recipe:kbdb_upsert` 套殼。
@@ -0,0 +1,68 @@
# BUG: PATCH /blocks/:id 回 403 "block belongs to different org"(同一把 key 能 create/get/delete 卻不能 patch
> 回報日期:2026-05-29
> 回報來源:arcrun Phase 2(把 kbdb_* 零件降級成 recipe,逐個驗收時發現)
> 嚴重度:中高 —— PATCH endpoint 對「自己剛建、且能刪的 block」拒絕更新,等於 update 能力全壞
> 影響:任何「查→改」或 upsert 流程(先 GET 找到 block,再 PATCH 更新)都無法完成
---
## 症狀
用**同一把 API key**、對**同一個 block**,四個操作的結果不一致:
| 操作 | endpoint | HTTP | 結果 |
|---|---|---|---|
| 建立 | `POST /blocks` | **201** | ✅ 建出 block,回 id |
| 讀取 | `GET /blocks/:id` | **200** | ✅ 讀得到 |
| **更新** | **`PATCH /blocks/:id`** | **403** | ❌ `{"error":"block belongs to different org"}` |
| 刪除 | `DELETE /blocks/:id` | **200** | ✅ `{"deleted":true}` |
**矛盾點**:同一把 key 能 create / get / delete 這個 block —— 代表 KBDB 認定我擁有它(org 一致)。但 PATCH 卻說「belongs to different org」。**create 寫進去的 org 判定,和 patch 讀出來比對的 org 判定不一致**,這是 KBDB 內部 org 歸屬邏輯的 bug。
## 重現(裸 curl,不經 arcrun
為排除是 arcrun 注入問題,直接用裸 curl + Bearer token 打 `https://kbdb.finally.click`
```bash
TOKEN="Bearer ak_402d…" # 同一把 key,全程不變
BASE=https://kbdb.finally.click
# 1. 建立 → 201
curl -X POST $BASE/blocks -H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{"content":"...","type":"note","page_name":"kbdb_bug_repro","source":"...","user_id":"arcrun_phase2"}'
# → {"id":"f39ea877-...","action":"created"} HTTP 201
# 2. 讀取 → 200
curl $BASE/blocks/f39ea877-... -H "Authorization: $TOKEN"
# → HTTP 200
# 3. 更新 → 403 ★ BUG
curl -X PATCH $BASE/blocks/f39ea877-... -H "Authorization: $TOKEN" -H 'Content-Type: application/json' \
-d '{"content":"patched"}'
# → {"error":"block belongs to different org"} HTTP 403
# 4. 刪除 → 200(證明我擁有此 block)
curl -X DELETE $BASE/blocks/f39ea877-... -H "Authorization: $TOKEN"
# → {"deleted":true} HTTP 200
```
經 arcruncypher-executor → auth_static_key 注入同一把 token → recipe 轉發)也是完全相同結果,所以**確定是 KBDB server 端 PATCH 路徑的問題,不是 client / arcrun 的問題**。
## 推測方向(給 KBDB 排查)
create / get / delete 的 org 判定路徑,和 PATCH 的 org 判定路徑不一致。可能:
1. **PATCH 用了不同的 org 解析來源**:例如 create 用 token → org_id 的某種映射寫入 block,但 PATCH 的 org-check 從另一個欄位 / 另一張表讀,兩邊算出的 org 不同。
2. **block 落地時的 org_id 與 token 的 org_id 不一致**create 時可能用了 default org 或 null org 寫入,PATCH 的 ownership 檢查卻嚴格比對 token org,導致「自己建的卻不是自己 org」。
3. **org-check 是 PATCH 獨有、其他三個 verb 沒做**:所以只有 PATCH 露出這個不一致。
建議從「create 時 block 實際寫入的 org_id」對比「PATCH org-check 讀的 org_id」兩個值下手,它們應該相等卻不等。
## 對 arcrun 的影響(已隔離,不阻擋 arcrun Phase 2
- arcrun 已把 `kbdb_patch_block` 降級成 reciperecipe 的轉發 + auth 注入經驗證**正確無誤**(請求成功打到 KBDB 的 PATCH handler,非 401)。
- 403 屬 KBDB 端行為,依 arcrun 原則「能不能打通由發 key 的服務裁決」,這不是 recipe 的 bug。
- 但 arcrun 的 `kbdb_upsert_block`GET 查找 → 分支 PATCH/POST)會用到 PATCH**此 bug 未解前,upsert 的 PATCH 分支無法驗收 2xx**。arcrun 端會把該分支標「未驗收:阻擋於 KBDB PATCH 403」。
KBDB 修好後請通知 arcrun,重跑 `kbdb_patch_block` recipe 驗收即可。
+29 -7
View File
@@ -126,10 +126,23 @@ gloss: 一句話定義這個概念是什麼(給下游語義 normalize 用,
- [自包含改寫的要點,不寫「詳見原文」]
## 實體
> 本卡內文的關鍵實體(也是 graph node)。名+描述一起供下游 embedding normalize。
> AI 生產、人不必讀;集中放、一實體一行、不縮排、不重複。
- **原子筆記**atomic note/卡片原子化)— 每張卡只承載一個不可再分論點的知識記錄單元。
- **傳統筆記**(大鍋炒筆記)— 把多主題混雜在同一篇、難精確引用的記錄方式。
## 關聯
### 內文知識關係(內文實體間;端點=上方 `## 實體` 的正規名,一字不差)
- 原子筆記 >> 對立於 >> 傳統筆記
- 傳統筆記 >> 犧牲 >> 精確引用
### 卡片關係(卡對卡)
- [[本卡]] >> 謂詞(動詞短語) >> [[他卡]]
- [[原子筆記]] >> 是其最小單元 >> [[卡片盒筆記法]]
```
### 架構:三層 + 標籤橫切(183 卡實證)
@@ -147,15 +160,22 @@ cards/<bucket>/
- **frontmatter `tags:` 而非行內 `#tag`**:內文常用 `#`(如 `#猜想`),行內標籤會讓 ingest 分不清「分類」與「內文範例」污染 graph;frontmatter 零歧義。標籤只能用 `TAXONOMY.md` 列出的;**禁止繞過字典在卡片直接冒新標籤**,但字典可受控擴充(遇新軸先查重、確認非同義詞,再登記進本 repo 的 TAXONOMY.md)。
- **麵包屑帶路徑**H1 次行 `← [[<bucket>/00-INDEX]]`。指 `00-INDEX` 因固定名跨桶撞名,**一律帶路徑**;卡片間連結用裸 `[[卡名]]`
### 使用 typed-edge 三元組(不只裸 `[[wikilink]]`
### 使用 typed-edge 三元組(抓內文實體關係,不只卡對卡
整理時,發現內容與其他頁面有關聯,用**帶語義的三元組**寫進 `## 關聯`,而非只列裸 `[[頁面]]`。裸 `[[A]]` 只說「有關」、沒說關係,下游要建 knowledge graph 還得回讀兩張卡;三元組把關係也預編譯,ingest 直接 parse 出帶類型的有向邊
用**帶語義的三元組** `A >> 謂詞 >> B` 寫進 `## 關聯`。**重點是抓內文裡的實體關係**——卡對卡(`[[卡A]] >> 謂詞 >> [[卡B]]`)只是把既有雙鏈加個動詞、資訊量幾乎沒增加;知識圖譜的價值在內文概念間的關係(`原子筆記 >> 對立於 >> 傳統筆記`,這些 A/B 是內文概念、不是卡標題)
格式 `A >> 謂詞 >> B`,規則:
1. **方向性**:必須讀成「A(謂詞)B」一句通順的話;A、B 順序=主→賓真實方向。
2. **謂詞用動詞 / 動詞短語**(反駁、奠基於、是…的實作),天然帶方向。
3. **謂詞自由書寫**,不受控詞彙;下游對謂詞 embedding 時同義謂詞會自動聚類,但方向仍靠書寫順序保證
4. **向後相容**:純 `[[A]]` 仍合法(無類型邊),盡量補謂詞
2. **謂詞用動詞 / 動詞短語**(反駁、奠基於、犧牲),天然帶方向。**禁名詞當謂詞**——`>> 存儲格式 >>``>> 操作體驗 >>` 讀不通,是錯的。
3. **謂詞自由書寫但別太天馬行空**:寫「參考/參照」皆可(下游 embed 自動聚類同義謂詞),別寫「瞄了一眼」這種抓不到同義的
4. **內文三元組端點用裸文字**(非 `[[wikilink]]`),避免在 Logseq 產生大量紅色斷鏈;卡對卡那層才用 `[[]]`
5. **向後相容**:純 `[[A]]` 仍合法(無類型邊),盡量補謂詞。
> **★ 硬自檢(Haiku 量產必備護欄)★** —— 內文三元組的「端點 = `## 實體` 詞條」
> `A >> 謂詞 >> B` 的 A、B 必須與 `## 實體` 某個粗體正規名【一字不差】。**寫完後逐條自檢**:把 A、B 拿去 `## 實體` 找有沒有完全相同的正規名,沒有 → 這條錯了。
> 修法擇一:(a) 改用實體表已有的詞;(b) 端點確是重要實體 → 補進 `## 實體` 再指它。
> 禁止:端點帶括號註解、端點是整句補語、端點是形容詞短語。
> (實證:光寫規則 Haiku 會略過,端點對不齊 14 條;寫成自檢動作後 14→0。跑 1-2 張看不出,跑 12 張才暴露。)
`>>` 為分隔語法,全程一致即可。這是 Karpathy LLM Wiki「知識互連」的強化版——連結不只存在,還帶類型與方向。
@@ -166,7 +186,9 @@ cards/<bucket>/
- **在知識生產的當下、由整理者(CC / Cowork)建**:gloss 跟三元組同階段萃,**不留給下游 ingest 臨時補**——下游只有單檔/跨庫視角,編不出貼合的 gloss。
- **選填、deep tier 才產**:淺萃不浪費。
- **gloss ≠ 摘要**`gloss` 是 frontmatter 給機器 normalize 的定義句(「X 是…」);`## 摘要` 是給人讀的核心句。
- **對齊下游 envelope**frontmatter `gloss:` 對應 ingest envelope 的 `nodes[].gloss`
- **兩層 gloss**frontmatter `gloss:` 描述「卡標題」這個 node;② `## 實體` 區塊的每行描述句,描述「內文實體」這些 node。**內文實體也是 graph node、也需描述句**才能被下游 embedding normalize`黃仁勳` vs `Jensen Huang` 靠描述拉近向量)
- **實體要描述、謂詞不用**:實體同義詞字面差遠需描述拉近;謂詞同義詞字面本就近,裸詞 embed 自動聚類。
- **對齊下游 envelope**frontmatter `gloss:``## 實體` 詞條對應 ingest envelope 的 `nodes[].gloss`
> **改寫時必守**:① 絕不寫入 raw source(只往 `cards/<bucket>/` 寫,事後驗 raw source 0 異動);② 檔名=卡片全名,冒號用全形「:」、斜線用全形「/」,全程一種字元避免斷鏈。