From f89ccdebf362f966e58215b9c1b48ba1b0ae86a0 Mon Sep 17 00:00:00 2001 From: richblack Date: Tue, 21 Jul 2026 01:40:57 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20template=201.16.0=E2=80=94=E2=80=94wik?= =?UTF-8?q?i=20=E8=AE=80=E5=8F=96=E5=85=A9=E6=94=AF=20hook=EF=BC=88?= =?UTF-8?q?=E6=9F=A5=E8=A9=A2=E5=8D=B3=E6=90=9C=E5=B0=8B=20wiki=EF=BC=8Bsu?= =?UTF-8?q?bagent=20=E8=87=AA=E5=8B=95=E6=B3=A8=E5=85=A5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .claude/commands/issue-handle.md | 25 ++- .claude/commands/wiki-extract.md | 161 ++++++++++++++++++ .claude/commands/wiki-init.md | 37 ++-- .claude/hooks/subagent-wiki-guard.sh | 76 +++++++++ .claude/hooks/wiki-first-search.sh | 74 ++++++++ .claude/settings.json | 20 ++- system-dev/VERSION | 2 +- .../docs/3-specs/TEMPLATE-sdd/design.md | 6 +- system-dev/docs/4-guides/logseq-markers.md | 81 +++++++++ ...EQUEST-2026-05-29-upsert-block-endpoint.md | 0 ...26-05-29-patch-blocks-403-different-org.md | 0 system-dev/docs/SKILL.md | 36 +++- system-dev/scripts/install.sh | 56 +++++- system-dev/scripts/update.sh | 75 +++++++- system-dev/wiki/INDEX.md | 29 +--- 15 files changed, 625 insertions(+), 53 deletions(-) create mode 100644 .claude/commands/wiki-extract.md create mode 100755 .claude/hooks/subagent-wiki-guard.sh create mode 100755 .claude/hooks/wiki-first-search.sh create mode 100644 system-dev/docs/4-guides/logseq-markers.md rename {docs => system-dev/docs}/5-records/FEATURE-REQUEST-2026-05-29-upsert-block-endpoint.md (100%) rename {docs => system-dev/docs}/5-records/incidents/BUG-2026-05-29-patch-blocks-403-different-org.md (100%) diff --git a/.claude/commands/issue-handle.md b/.claude/commands/issue-handle.md index 8df58d3..4023c6f 100644 --- a/.claude/commands/issue-handle.md +++ b/.claude/commands/issue-handle.md @@ -8,7 +8,7 @@ description: 處理本 repo 的 GitHub issue(讀/回/結案),跨 repo 發 很多人不知道這件事——`gh` 已內建認證,零開發、零外部依賴。issue 同源於 repo, 比 Notion / Sheets 更適合做交辦與待辦,不必引入外部 SaaS。 -這份指引分三層,界線要守住。 +這份指引分四層,界線要守住。 --- @@ -20,12 +20,13 @@ description: 處理本 repo 的 GitHub issue(讀/回/結案),跨 repo 發 gh issue list --state open # 看有哪些待辦 gh issue view # 讀完整內容 # …實作… -gh issue comment --body "做了什麼、怎麼決定的、改了哪些檔" +gh issue comment --body "[<本 repo> CC] 做了什麼、怎麼決定的、改了哪些檔" gh issue close # 確認解決後結案 ``` 回覆要有料:說清楚**做了什麼、為什麼這樣決定、動了哪些檔**,而不是只回「done」。 issue 作者(可能是另一個 repo 的 CC,或人類)要靠你的回覆判斷對不對。 +跨 repo 的 issue/comment 開頭一律署名 `[<本 repo> CC]`(見第 3 節鐵律)。 --- @@ -41,7 +42,25 @@ issue 作者(可能是另一個 repo 的 CC,或人類)要靠你的回覆 --- -## 3. flag 安全界線(最重要 — 絕不可越) +## 3. 跨 repo 署名(鐵律 — 絕不可漏) + +所有 repo(mira / graph-plugin / ingest-plugin / Arcrun / template…)共用**同一個 GitHub 帳號**發 issue/comment, +所以 issue/comment 的 author **全顯示同一個帳號、看不出是哪個 repo 的 CC 發的**。 + +> **跨 repo 的 issue/comment 一律在開頭署名 `[<本 repo> CC]`**,靠內容署名溯源。 + +- 收件方 CC 回報:`[graph-plugin CC]` / `[mira CC]` / `[ingest CC]` / `[arcrun CC]`… +- 總管下令/追問:`[InkStoneCo 總管]` +- 署名放 comment **第一行或標題式開頭**(既有的「## 回報(graph CC)」即合格)。 + +為什麼只能這樣:GitHub issue/comment 的 author = 發送帳號,**沒有 per-repo 身份這設定**; +`git config user.name` 只影響 commit 作者、不影響 issue/comment author; +給每個 repo 開獨立帳號 = 多帳號自動化 = 踩下方第 4 節 flag 鐵律,**不可**。 +身份只能在**內容層自報**。本 repo 名稱 → 看 `git remote -v` 或 repo 根目錄名。 + +--- + +## 4. flag 安全界線(最重要 — 絕不可越) **「有事才讀」,禁止自動輪詢。** diff --git a/.claude/commands/wiki-extract.md b/.claude/commands/wiki-extract.md new file mode 100644 index 0000000..e8e35ed --- /dev/null +++ b/.claude/commands/wiki-extract.md @@ -0,0 +1,161 @@ +# /wiki-extract — vault 增量萃取(Logseq / Obsidian → system-dev/wiki) + +把**筆記 vault**(Logseq graph 如 `notes`/`kb`、或 Obsidian)的原始筆記,**增量、冪等**地 +萃成 `system-dev/wiki/` 的精耕卡+`[[wikilink]]`。這是知識一庫 ingest 的**前段**: +AI 只產卡片檔,下游 Arcrun ingest 再從 wikilink 機械拉三元組進 KBDB。 + +> **跟 `/wiki-init` 的分工**: +> - `/wiki-init` 是**首次**建結構 + 全庫首萃(一次性)。 +> - `/wiki-extract` 是**之後每次**的增量重萃——vault 會被 Syncthing/cron 持續灌新筆記, +> 這支負責「只萃變動的、沒變的不碰、不浪費 AI run」。給 Routine / cloud-worker 反覆跑。 +> - **跑它的是你(CC / Routine)=LLM 本人,不需任何 token**。 + +> **邊界(硬規矩,別越界)** +> - 只往 `system-dev/wiki/` 寫。**絕不寫入 KBDB、絕不拉三元組紀錄**——三元組是下游 +> Arcrun 從你產的 `[[wikilink]]` + `## 關聯` 機械映射(另一張 issue),不是這支的事。 +> - **原始筆記唯讀**:`journals/`、`pages/`、Obsidian 根 `.md` 是 leo 的手寫真身, +> 改了會被 Syncthing 推回他手機污染筆記 App。萃取=只讀原文、只寫 wiki。 +> - **D16 精耕非 RAG**:萃「知識點」成自包含原子卡 + 建 wikilink,**不地毯灌原文全文**。 + +--- + +## 執行流程 + +### 第一步:確認這是 vault repo,定位 raw source + +偵測邏輯**同 install.sh / wiki-init**: + +| 偵測到 | 型態 | raw source(要掃的原文) | +|--------|------|--------------------------| +| 根目錄有 `logseq/` | Logseq vault | `journals/*.md` + `pages/*.md` | +| 根目錄有 `.obsidian/` | Obsidian vault | vault 根下所有 `.md` | +| 都沒有 | **不是 vault** | → 停手。這支只處理 vault;一般 dev repo 開發時就手寫 `.claude`/`system-dev/wiki`,不需萃取 | + +沒有 `system-dev/wiki/`?→ 先跑 `/wiki-init`(首次建結構+首萃),再回來用這支做增量。 + +### 第二步:content_hash 冪等 —— 決定哪些檔要萃(省 run 的核心) + +讀萃取 manifest:`system-dev/wiki/.extract-manifest.json`(不存在=首次,視同全部要萃)。 +格式: + +```json +{ + "version": 1, + "algo": "sha256", + "sources": { + "journals/2026_07_01.md": { + "content_hash": "", + "extracted_at": "2026-07-06", + "cards": ["Prompt能力即拆解自己邏輯的能力", "程式化邏輯可圖解任何主題不限AI"], + "skipped_reason": null + }, + "journals/2026_06_25.md": { + "content_hash": "", + "extracted_at": "2026-07-06", + "cards": [], + "skipped_reason": "空檔/訊息量不足,無可萃知識點" + } + } +} +``` + +對每個 raw source 檔: + +1. 算目前 `content_hash`(`sha256sum `,取檔案 bytes 的 hash)。 +2. 跟 manifest 裡該檔的 `content_hash` 比: + - **相同 → skip,不讀不萃、不呼叫任何 AI 推理**(就算它上次 `cards: []` 也 skip——空檔沒變還是空)。 + - **不同或不在 manifest → 這檔要(重)萃**。 +3. manifest 有、但檔已不存在 → 該檔被刪,把它的 entry 從 manifest 移除(卡片是否連帶處理見第五步)。 + +> **這一步是「省 run」的重點**:vault 每天可能只動 1~2 個 journal,其餘幾十個檔 hash 沒變 +> 就整批跳過,AI 只對真正變動的檔動腦。**重跑一個沒變動的 vault = 零 AI 呼叫、零 diff。** + +### 第三步:對「要萃」的檔,抓知識點 + 任務 + +逐個變動檔讀原文,分兩類抽取: + +**(a) 知識點 → 概念原子卡** +判準與卡片格式**完全依 `/wiki-init` 第五步**(frontmatter `tags:`/`gloss:`、H1、麵包屑 +`← [[/00-INDEX]]`、`**來源**`、`## 摘要`、`## 重點`、`## 實體`、`## 關聯` 的 +typed-edge 三元組、TAXONOMY 受控標籤、硬自檢等)——**不在這裡重寫格式,一律回去讀那份**。 +廢話/訊息量薄的段落略過(在 manifest 記 `skipped_reason`,誠實留痕、不留卡)。 + +**(b) Logseq 任務 marker → 任務卡(task_status)** +解析**完全依** `system-dev/docs/4-guides/logseq-markers.md`(單一真相源,與 template#4 +tasks 投影共用同一套;**別自己另寫 mapping**)。摘要: + +- 任務行 regex:`^\s*- (TODO|DOING|NOW|LATER|WAITING|DONE|CANCELED|CANCELLED)\s+` +- 狀態正規化:TODO/LATER→`todo`、DOING/NOW→`in-progress`、WAITING→`blocked`、 + DONE→`done`、CANCELED/CANCELLED→`closed`。 +- 跳過 `:LOGBOOK:…:END:` 區塊與 `key:: value` 屬性行(`collapsed::`、`id::`、 + `SCHEDULED::`、`DEADLINE::`…),**別把 marker 或屬性當任務內文**。 + +有實質內容的任務 → 產一張任務卡進 `cards/tasks/` bucket,frontmatter 帶 `task_status`: + +```markdown +--- +tags: [<領域標籤,依 TAXONOMY>] +task_status: todo # ← 依上表正規名;這是任務卡才有的欄位 +gloss: 一句話定義這個任務要達成什麼(供下游 normalize) +--- +# <任務一句話標題(marker 後的內文,去掉 marker)> + +← [[tasks/00-INDEX]] + +**來源**:`journals/2026_07_01.md`(TODO block) +**最後更新**:YYYY-MM-DD + +## 摘要 +[任務要做什麼、脈絡] + +## 實體 +- **<關鍵實體正規名>**(<同義詞>)— <一句描述> + +## 關聯 +### 內文知識關係(端點=上方 `## 實體` 正規名,一字不差) +- <實體A> >> <謂詞> >> <實體B> +### 卡片關係(卡對卡) +- [[本任務卡]] >> 涉及 >> [[相關概念卡]] +``` + +> 純瑣事任務(「買菜」這種無知識量)不必成獨立卡——可在 `cards/tasks/00-INDEX.md` +> 列一行帶狀態即可,避免灌垃圾卡。判準同 D16:有沒有知識/專案價值。 + +### 第四步:更新桶索引與 INDEX + +- 每個動到的 bucket(如 `cards/notes/`、`cards/tasks/`)更新其 `00-INDEX.md` + (容器:只連不重寫,H2/H3 分節)。 +- 更新 `system-dev/wiki/INDEX.md` 的標籤視圖與卡片清單。 +- 任務卡可在 INDEX 開一個「任務視圖」按 `task_status` 聚類。 + +### 第五步:寫回 manifest + 驗證原文 0 動 + +1. 把這次萃過的每個檔的**新 `content_hash`**、`extracted_at`、產出的 `cards`、 + (或 `skipped_reason`)寫回 `system-dev/wiki/.extract-manifest.json`。 + **沒動到的檔的 entry 原樣保留**(別整檔重寫掉別人的 hash)。 +2. 驗證原文零異動(踩過的坑): + ``` + git status --short journals/ pages/ # Obsidian 則看根目錄 .md ——須 0 新增 0 修改 + ``` + 有任何原文變動 → 你誤寫了 raw source,回滾。 + +### 第六步:完成報告 + +``` +✅ wiki-extract 完成(增量) +掃描:N 個 raw source 檔 + 萃取:M 個(content_hash 變動)→ 產出 X 張概念卡 + Y 張任務卡 + 跳過:K 個(hash 未變,零 AI 呼叫) +任務狀態分布:todo A / in-progress B / done C / … +原文驗證:journals/ pages/ git status 0 異動 ✅ +manifest:system-dev/wiki/.extract-manifest.json 已更新 +``` + +--- + +## 冪等自檢(Routine 反覆跑必守) + +- [ ] 跑之前先讀 manifest,hash 相同的檔**完全不進 AI**(不是「讀了才發現一樣」,是靠 hash 先擋)。 +- [ ] 對「同一個沒變動的 vault」連跑兩次:第二次應是**零萃取、零卡片 diff、零 manifest 變化**。 +- [ ] 只有 `system-dev/wiki/` 有寫入;`journals/`、`pages/` git status 全乾淨。 +- [ ] 任務狀態用正規名,marker/屬性沒混進內文(照 `logseq-markers.md` 自檢)。 diff --git a/.claude/commands/wiki-init.md b/.claude/commands/wiki-init.md index 2fcca5e..3a913b2 100644 --- a/.claude/commands/wiki-init.md +++ b/.claude/commands/wiki-init.md @@ -158,9 +158,16 @@ gloss: 一句話定義這個概念是什麼(給下游語義 normalize 用, ## 重點 - [自包含改寫的要點,不依賴原文] +## 實體 +> 本卡內文的關鍵實體(也是 graph node)。名+描述供下游 embedding normalize。集中放、一行一個、不縮排、不重複。 +- **原子筆記**(atomic note/卡片原子化)— 每張卡只承載一個不可再分論點的知識記錄單元。 +- **傳統筆記**(大鍋炒筆記)— 把多主題混雜在同一篇、難精確引用的記錄方式。 + ## 關聯 +### 內文知識關係(內文實體間;端點=上方 `## 實體` 正規名,一字不差) +- 原子筆記 >> 對立於 >> 傳統筆記 +### 卡片關係(卡對卡) - [[本卡]] >> 謂詞(動詞短語) >> [[他卡]] -- [[原子筆記]] >> 是其最小單元 >> [[卡片盒筆記法]] ``` **麵包屑用帶路徑 wikilink**(issue #7):H1 次行放 `← [[/00-INDEX]]` 指回桶子索引。 @@ -175,20 +182,25 @@ gloss: 一句話定義這個概念是什麼(給下游語義 normalize 用, - 一般開發專案的軸可不同(如 子系統/層級/決策類型),由 AI 依專案性質提出、寫進 TAXONOMY.md。 - **遇到現有軸裝不下的內容**:先查是否只是現有標籤的同義詞;確實是新軸才加進 TAXONOMY.md(附定義)再用——**禁止繞過字典在卡片直接冒新標籤**。字典是 per-repo,跨 repo 不必共用。 -**typed-edge 規則**(issue #5,把「關係」也預編譯,下游 ingest 直接 parse 出帶類型的有向邊): +**typed-edge 規則**(issue #5/#11,把「關係」也預編譯,下游 ingest 直接 parse 出帶類型的有向邊): +- **重點抓內文實體關係,不只卡對卡**:卡對卡(`[[卡A]] >> 謂詞 >> [[卡B]]`)只是既有雙鏈加動詞、資訊量幾乎沒增加;價值在內文概念關係(`原子筆記 >> 對立於 >> 傳統筆記`,A/B 是內文概念非卡標題)。 1. **方向性**:`A >> 謂詞 >> B` 必須讀成「A(謂詞)B」一句通順的話;A、B 順序就是主→賓真實方向。 -2. **謂詞用動詞 / 動詞短語**(反駁、奠基於、是…的實作),動詞天然帶方向。 -3. **謂詞自由書寫,不受控詞彙**:下游對謂詞 embedding 時同義謂詞會自動聚類;但方向仍靠書寫順序保證。 -4. **向後相容**:純 `[[A]]` 仍合法(視為無類型邊),盡量補謂詞。 +2. **謂詞用動詞 / 動詞短語**(反駁、奠基於、犧牲)。**禁名詞當謂詞**——`>> 存儲格式 >>`、`>> 操作體驗 >>` 讀不通,是錯的。 +3. **謂詞自由但別太天馬行空**:「參考/參照」皆可(下游 embed 自動聚類),別寫「瞄了一眼」這種抓不到同義的。 +4. **內文三元組端點用裸文字**(非 `[[wikilink]]`),避免 Logseq 紅色斷鏈;卡對卡那層才用 `[[]]`。 +5. **向後相容**:純 `[[A]]` 仍合法(視為無類型邊),盡量補謂詞。 +> **★ 硬自檢(Haiku 量產必備)★** 內文三元組端點必須與 `## 實體` 某粗體正規名【一字不差】。**寫完逐條把 A、B 拿去 `## 實體` 比對**,沒有完全相同的 → 這條錯了,改用實體表已有的詞、或把端點補進 `## 實體` 再指它。禁止端點帶括號註解/整句補語/形容詞短語。(實證:光寫規則 Haiku 會略過,端點對不齊 14 條;寫成自檢動作後 14→0。跑 12 張才暴露。) > `>>` 是分隔語法,repo 可自選符號,但全程一致。 -**萃 gloss 規則**(issue #9,把「node 的一句說明」也預編譯,供下游 KBDB 語義 normalize): +**萃 gloss 規則**(issue #9/#11,把「node 的一句說明」也預編譯,供下游 KBDB 語義 normalize): - **gloss = 這個 entity / graph node 是什麼的一句話**。下游對「entity 名 + gloss」一起做 embedding 求相似度,自動歸一同義詞(比只對名字準、比手維護 alias 表自動)。 -- **在知識生產的當下、由 local CC 建**:gloss 跟三元組同階段萃,**不留給下游 ingest 臨時補**——下游只有單檔 / 跨庫視角,編不出貼合的 gloss(=胡扯)。local scope 才有完整脈絡寫對。 -- **選填、deep tier 才產**:淺萃(只要結構)時不浪費;deep 改寫時每張卡補一句 `gloss:`。 -- **gloss ≠ 摘要**:`gloss` 是 frontmatter 裡給機器 normalize 用的定義句(「X 是…」),求精準可 embedding;`## 摘要` 是給人讀的核心一句。可相近但分屬兩處、兩用途。 -- **格式對齊下游 envelope**:frontmatter `gloss:` 對應下游 ingest envelope 的 `nodes[].gloss` 欄位,ingest 直接取用、不再回頭補。 +- **兩層 gloss**:① frontmatter `gloss:` 描述卡標題這個 node;② `## 實體` 每行描述句描述內文實體 node。**內文實體也是 graph node、也需描述句**才能 normalize(`黃仁勳` vs `Jensen Huang` 靠描述拉近向量)。 +- **實體要描述、謂詞不用**:實體同義詞字面差遠需描述拉近;謂詞同義詞字面本就近,裸詞 embed 自動聚類。 +- **在知識生產的當下、由 local CC 建**:gloss 跟三元組同階段萃,**不留給下游 ingest 臨時補**——下游只有單檔 / 跨庫視角,編不出貼合的 gloss(=胡扯)。 +- **選填、deep tier 才產**:淺萃(只要結構)時不浪費;deep 改寫時每張卡補。 +- **gloss ≠ 摘要**:`gloss` 是給機器 normalize 的定義句(「X 是…」);`## 摘要` 是給人讀的核心一句。 +- **格式對齊下游 envelope**:frontmatter `gloss:` 與 `## 實體` 詞條對應下游 ingest envelope 的 `nodes[].gloss`,ingest 直接取用。 **INDEX.md 是標籤視圖**(非資料夾列表),`00-INDEX.md` 是桶內容器(只連不重寫,H2/H3 分節)。 頂層索引指桶子索引帶路徑:`[[pkm/00-INDEX]]`。 @@ -216,3 +228,8 @@ git status --short pages/ journals/ # 或一般專案的 docs/ ——須 0 原文驗證:pages/ journals/ git status 0 異動 ✅ 下一步:用 /wiki-capture 把重要決策存進 wiki ``` + +> **vault repo 首萃後的增量重萃**:Logseq / Obsidian vault 會被持續灌新筆記。首萃(本命令) +> 之後,改用 **`/wiki-extract`** 做增量——它靠 content_hash 只萃變動的檔(沒變=零 AI 呼叫), +> 並解析 Logseq 大寫任務 marker(TODO/DOING/DONE…→ `task_status`,見 +> `system-dev/docs/4-guides/logseq-markers.md`)。適合掛給 Routine / cloud-worker 反覆跑。 diff --git a/.claude/hooks/subagent-wiki-guard.sh b/.claude/hooks/subagent-wiki-guard.sh new file mode 100755 index 0000000..6819fb6 --- /dev/null +++ b/.claude/hooks/subagent-wiki-guard.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# subagent-wiki-guard.sh — PreToolUse(Task) hook:subagent 聽到「查」就自己先查 wiki +# +# 病根(2026-07-20):總管兩次派 agent 查 ENCRYPTION_KEY,prompt 都只叫它「去查 repo 程式碼」。 +# agent 於是從**稿子**推論出「這東西還活著、不能動」,總管照單全收去擋 leo 三輪。 +# +# 🔑 設計轉向(leo 2026-07-21): +# 第一版是「上游沒交代讀 wiki 就擋下」——但那**還是依賴上游記得寫**, +# 跟「我記得讀 wiki」是同一個病。leo 點破: +# 「subagent 的問題跟你一樣。你叫它去查,就算你沒說要先查 wiki, +# 但它**只要聽到查,就應該主動查 wiki**,因為每個 repo 都有維護自己的 wiki。」 +# → 改成 **注入式**:不擋、不要求上游改 prompt,直接把「先查 wiki」這條 +# 以 additionalContext 注入給 subagent,讓它自己做。零依賴任何人記得。 +# +# 行為:偵測到查證/實作類任務 → exit 0 並用 hookSpecificOutput 注入指示。 +# 已含 wiki 指示、或非查證類任務 → 靜默放行(不重複注入)。 +set -euo pipefail + +INPUT=$(cat) + +PROMPT=$(printf '%s' "$INPUT" | python3 -c " +import json,sys +try: + d=json.load(sys.stdin) + print(d.get('tool_input',{}).get('prompt','')) +except Exception: print('') +" 2>/dev/null || echo "") + +[ -z "$PROMPT" ] && exit 0 + +# 上游已經交代了 → 不必重複注入 +if printf '%s' "$PROMPT" | grep -qiE "wiki|agent-memory|mistakes\.md|decisions-summary"; then + exit 0 +fi + +# 只對「查證/實作」類任務注入(純寫作、計算、潤稿等不需要) +if ! printf '%s' "$PROMPT" | grep -qiE "查|盤點|核實|確認|調查|研究|找出|repo|程式碼|原始碼|source|實作|移除|刪除|重構|修|grep|codebase|\.ts|\.go|src/"; then + exit 0 +fi + +python3 - <<'PY' +import json + +guidance = """【自動注入:查任何東西之前,先查 wiki】 + +你所在的 repo 有維護自己的 wiki(通常在 `system-dev/wiki/`,舊結構在 `.claude/wiki/`)。 +**接到「查/盤點/核實/實作」類任務時,第一個動作是搜尋 wiki,不是翻程式碼。** + +做法(30 秒,省下大量白工): + grep -rin "<本題關鍵字>" system-dev/wiki/ 2>/dev/null || grep -rin "<關鍵字>" .claude/wiki/ + +為什麼這是划算的: + • wiki 是前人已經查過、驗證過、被負責人糾正過的結論——**判準**。 + • 程式碼與歷史文件是**稿子**:它反映「還沒清乾淨」,不等於「還在用」。 + 從稿子推論會系統性得出過時結論。 + • wiki 沒記載,才值得花力氣翻原文。 + +三條硬規則: + 1. **wiki 與程式碼衝突 → 以 wiki 為準**,並在回報中明確指出衝突, + 不要自行用 code 推翻 wiki。 + 2. wiki 寫「不可動/待廢除/進行中」→ **讀它的解除條件並逐條核對**。 + 那是當時狀態,不是永久禁令;條件已滿足就是可動。 + (2026-07-20 實際事故:agent 只看到「不可動」就回報不能動, + 實際上解除條件早已滿足,害負責人被擋三輪。) + 3. 翻原文後若得到**新結論**,回報時明講「wiki 該更新」——wiki 過時是債,要還。 +""" + +print(json.dumps({ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "additionalContext": guidance + } +}, ensure_ascii=False)) +PY + +exit 0 diff --git a/.claude/hooks/wiki-first-search.sh b/.claude/hooks/wiki-first-search.sh new file mode 100755 index 0000000..309d637 --- /dev/null +++ b/.claude/hooks/wiki-first-search.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# wiki-first-search.sh — PreToolUse hook:要去翻原文/程式碼前,先把 wiki 命中結果推到眼前 +# +# 病根(2026-07-20 leo 點破,mistakes 第一鐵律): +# 總管 session 開頭讀了 agent-memory 前 50 行就開工,關鍵那條在第 56 行 → 拿過期記憶擋了 leo 三輪。 +# leo:「如果你不是讀而是**搜尋** wiki 就不會只讀 50 行就下定論, +# 而是就像我直接在頁面 cmd+F,那些都會高亮。」 +# +# 設計要點(為什麼是這個形狀): +# 1. **搜尋 ≠ 通讀**:開場 push 全文(session-start-recall.sh)解決不了這題——量大必然只讀開頭。 +# 這支反過來:在「你正要去查 code/原文」的當下,用你自己的關鍵字 grep wiki,只推命中行。 +# 2. **時機是關鍵**:不是開場推、不是寫入時擋,而是**查詢動作發生的那一刻**介入。 +# 3. **提醒不阻擋**(exit 0):wiki 沒記載時本來就該去翻原文,擋下來反而礙事。 +# 唯一目的是消滅「不知道 wiki 有寫」這件事。 +# +# 觸發:Grep / Glob / Read 打向 code 或 docs 時(見下方 should_check)。 +# 輸出:stdout 注入 context(命中的 wiki 行 + 檔名:行號)。 +set -euo pipefail + +INPUT=$(cat) +TOOL=$(printf '%s' "$INPUT" | python3 -c "import json,sys;print(json.load(sys.stdin).get('tool_name',''))" 2>/dev/null || echo "") + +# 取出這次查詢的關鍵字:Grep 用 pattern,Glob/Read 用路徑的檔名部分 +QUERY=$(printf '%s' "$INPUT" | python3 -c " +import json,sys,os,re +try: + d=json.load(sys.stdin); ti=d.get('tool_input',{}) + q = ti.get('pattern') or '' + if not q: + p = ti.get('file_path') or ti.get('path') or '' + q = os.path.splitext(os.path.basename(p))[0] if p else '' + # grep pattern 常含 regex 元字元;取最長的英數/底線詞當搜尋詞 + words = re.findall(r'[A-Za-z_][A-Za-z0-9_]{3,}', q) + print(max(words, key=len) if words else '') +except Exception: + print('') +" 2>/dev/null || echo "") + +[ -z "$QUERY" ] && exit 0 + +WIKI_DIR="system-dev/wiki" +[ -d "$WIKI_DIR" ] || exit 0 + +# 只在「查程式碼/文件」時提醒;查 wiki 本身就不用了(已經在讀了) +TARGET=$(printf '%s' "$INPUT" | python3 -c " +import json,sys +try: + d=json.load(sys.stdin); ti=d.get('tool_input',{}) + print(ti.get('file_path') or ti.get('path') or '') +except Exception: print('') +" 2>/dev/null || echo "") +case "$TARGET" in + *system-dev/wiki*) exit 0 ;; +esac + +# grep wiki(不分大小寫、含行號),最多 12 行避免洗版 +HITS=$(grep -rin --include="*.md" -- "$QUERY" "$WIKI_DIR" 2>/dev/null | head -12 || true) +[ -z "$HITS" ] && exit 0 + +COUNT=$(printf '%s\n' "$HITS" | wc -l | tr -d ' ') + +echo "════════════════════════════════════════════════" +printf '📚 wiki 已有「%s」的記載(%s 處,先看這裡再翻原文)\n' "$QUERY" "$COUNT" +echo "════════════════════════════════════════════════" +printf '%s\n' "$HITS" | sed 's|^system-dev/wiki/| |' +echo "" +echo "⚠️ wiki 是判準,程式碼與歷史文件只是稿子(mistakes 第一鐵律)。" +echo " • 上面若與你將要查的原文衝突 → **以 wiki 為準**,別用 code 推翻 wiki。" +echo " • 看到「不可動/待廢除/進行中」→ 先讀它的**解除條件**並逐條核對," +echo " 那是當時狀態不是永久禁令;條件已滿足就是可動。" +echo " • wiki 沒答案才值得翻原文——翻完若得到新結論,**回頭更新 wiki**。" +echo "" + +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json index 65e45b5..549e5ef 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -25,7 +25,25 @@ "timeout": 5 } ] + }, + { + "matcher": "Grep|Glob|Read", + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/wiki-first-search.sh" + } + ] + }, + { + "matcher": "Task", + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/subagent-wiki-guard.sh" + } + ] } ] } -} +} \ No newline at end of file diff --git a/system-dev/VERSION b/system-dev/VERSION index ed21137..15b989e 100644 --- a/system-dev/VERSION +++ b/system-dev/VERSION @@ -1 +1 @@ -1.10.0 \ No newline at end of file +1.16.0 diff --git a/system-dev/docs/3-specs/TEMPLATE-sdd/design.md b/system-dev/docs/3-specs/TEMPLATE-sdd/design.md index 44bbc65..ce540d8 100644 --- a/system-dev/docs/3-specs/TEMPLATE-sdd/design.md +++ b/system-dev/docs/3-specs/TEMPLATE-sdd/design.md @@ -1,6 +1,10 @@ +--- +status: draft # active | draft | paused | closed(生命週期鐵律見 ../SDD-LIFECYCLE.md) +superseded_by: "" # closed 且被取代時填接替的 SDD 資料夾名 +--- + # [子系統名稱] — Design -> 狀態:[草稿 / 審核中 / 已採納 / 已廢棄] > 建立:[YYYY-MM-DD] | 最後更新:[YYYY-MM-DD] > 負責人:[名稱] diff --git a/system-dev/docs/4-guides/logseq-markers.md b/system-dev/docs/4-guides/logseq-markers.md new file mode 100644 index 0000000..3b32871 --- /dev/null +++ b/system-dev/docs/4-guides/logseq-markers.md @@ -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 graph(notes/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 graph(notes / 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「排程視圖」用的同義 marker(LATER≈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 字面。 diff --git a/docs/5-records/FEATURE-REQUEST-2026-05-29-upsert-block-endpoint.md b/system-dev/docs/5-records/FEATURE-REQUEST-2026-05-29-upsert-block-endpoint.md similarity index 100% rename from docs/5-records/FEATURE-REQUEST-2026-05-29-upsert-block-endpoint.md rename to system-dev/docs/5-records/FEATURE-REQUEST-2026-05-29-upsert-block-endpoint.md diff --git a/docs/5-records/incidents/BUG-2026-05-29-patch-blocks-403-different-org.md b/system-dev/docs/5-records/incidents/BUG-2026-05-29-patch-blocks-403-different-org.md similarity index 100% rename from docs/5-records/incidents/BUG-2026-05-29-patch-blocks-403-different-org.md rename to system-dev/docs/5-records/incidents/BUG-2026-05-29-patch-blocks-403-different-org.md diff --git a/system-dev/docs/SKILL.md b/system-dev/docs/SKILL.md index 4bc3d55..254fb1c 100644 --- a/system-dev/docs/SKILL.md +++ b/system-dev/docs/SKILL.md @@ -126,10 +126,23 @@ gloss: 一句話定義這個概念是什麼(給下游語義 normalize 用, - [自包含改寫的要點,不寫「詳見原文」] +## 實體 + +> 本卡內文的關鍵實體(也是 graph node)。名+描述一起供下游 embedding normalize。 +> AI 生產、人不必讀;集中放、一實體一行、不縮排、不重複。 +- **原子筆記**(atomic note/卡片原子化)— 每張卡只承載一個不可再分論點的知識記錄單元。 +- **傳統筆記**(大鍋炒筆記)— 把多主題混雜在同一篇、難精確引用的記錄方式。 + ## 關聯 +### 內文知識關係(內文實體間;端點=上方 `## 實體` 的正規名,一字不差) + +- 原子筆記 >> 對立於 >> 傳統筆記 +- 傳統筆記 >> 犧牲 >> 精確引用 + +### 卡片關係(卡對卡) + - [[本卡]] >> 謂詞(動詞短語) >> [[他卡]] -- [[原子筆記]] >> 是其最小單元 >> [[卡片盒筆記法]] ``` ### 架構:三層 + 標籤橫切(183 卡實證) @@ -147,15 +160,22 @@ cards// - **frontmatter `tags:` 而非行內 `#tag`**:內文常用 `#`(如 `#猜想`),行內標籤會讓 ingest 分不清「分類」與「內文範例」污染 graph;frontmatter 零歧義。標籤只能用 `TAXONOMY.md` 列出的;**禁止繞過字典在卡片直接冒新標籤**,但字典可受控擴充(遇新軸先查重、確認非同義詞,再登記進本 repo 的 TAXONOMY.md)。 - **麵包屑帶路徑**:H1 次行 `← [[/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// - **在知識生產的當下、由整理者(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//` 寫,事後驗 raw source 0 異動);② 檔名=卡片全名,冒號用全形「:」、斜線用全形「/」,全程一種字元避免斷鏈。 diff --git a/system-dev/scripts/install.sh b/system-dev/scripts/install.sh index 68ba741..c4db133 100755 --- a/system-dev/scripts/install.sh +++ b/system-dev/scripts/install.sh @@ -26,9 +26,9 @@ t() { if [ "$IS_ZH" = "yes" ]; then printf '%s\n' "$1"; else printf '%s\n' "$2"; # tn = 不換行版(給 prompt 用) tn() { if [ "$IS_ZH" = "yes" ]; then printf '%s' "$1"; else printf '%s' "$2"; fi; } -REPO_URL="https://raw.githubusercontent.com/uncle6me-web/system-dev-template/main/template" +REPO_URL="https://git.uncle6.me/Leo/system-dev-template/raw/branch/main/template" # install.sh / update.sh 住在 main/scripts/(不在 template/)。 -SCRIPTS_URL="https://raw.githubusercontent.com/uncle6me-web/system-dev-template/main/scripts" +SCRIPTS_URL="https://git.uncle6.me/Leo/system-dev-template/raw/branch/main/scripts" CREATED=() SKIPPED=() @@ -103,6 +103,25 @@ echo "" t "📦 安裝模組:$MODULE" "📦 Module: $MODULE" echo "" +# ── 重複安裝防呆(1.10.1):install 只管「全新安裝」,一切後續歸 update ── +# 判準是「裝過沒」,不分新版舊版: +# - 新結構 system-dev/ 已存在,或 +# - 舊結構 .claude/wiki/ 或 .claude/VERSION 存在(裝過舊版、待遷移) +# 裝過了還跑 install → 會重複建範本、甚至跟真資料並存(先 install 建空殼,遷移就被擋)。 +# 正解:偵測到裝過 → 不動任何東西,導去 update(更新/遷移/補新檔都由它處理)。 +if [ -d "system-dev" ] || [ -d ".claude/wiki" ] || [ -f ".claude/VERSION" ]; then + t "🛑 偵測到這個專案已經安裝過 system-dev-template。" \ + "🛑 system-dev-template is already installed in this project." + t " 後續的更新、遷移、補新檔,一律由「更新腳本」處理(不要重跑 install):" \ + " All updates, migrations, and new-file additions are handled by the UPDATER (don't re-run install):" + echo "" + echo " curl -sSL https://git.uncle6.me/Leo/system-dev-template/raw/branch/main/scripts/update.sh | bash" + echo "" + t " (重跑 install 可能建出空白範本、跟你的真資料並存,故在此停止。)" \ + " (Re-running install could create empty templates alongside your real data, so it stops here.)" + exit 0 +fi + # ── 偵測 vault 類型 → 決定 raw source(原始文件)路徑 ────────── # 為什麼:這個模板原本假設「原始文件在 docs/」,但 Logseq / Obsidian # 這種 PKM vault 有自己的目錄慣例,整理時不能照 docs/ 那套搬動, @@ -235,6 +254,10 @@ create_dir ".claude/commands" create_dir ".claude/hooks" download_if_missing "system-dev/docs/README.md" "$REPO_URL/system-dev/docs/README.md" +# Logseq 任務 marker 解析(單一真相源):vault 萃取(/wiki-extract)與 tasks→Project +# 投影(tasks-project-sync)共用同一套解析,別各寫一份。放共用區,兩模組都拿得到。 +download_if_missing "system-dev/docs/4-guides/logseq-markers.md" "$REPO_URL/system-dev/docs/4-guides/logseq-markers.md" + # 工具版號:放 system-dev/,不寄生 .claude/。 download_if_missing "system-dev/VERSION" "$REPO_URL/system-dev/VERSION" @@ -258,6 +281,8 @@ if $WANT_WIKI; then download_if_missing ".claude/commands/wiki-capture.md" "$REPO_URL/.claude/commands/wiki-capture.md" download_if_missing ".claude/commands/wiki-update.md" "$REPO_URL/.claude/commands/wiki-update.md" download_if_missing ".claude/commands/wiki-recall.md" "$REPO_URL/.claude/commands/wiki-recall.md" + # vault 增量萃取(Logseq/Obsidian → system-dev/wiki,冪等):給 Routine 反覆跑。 + download_if_missing ".claude/commands/wiki-extract.md" "$REPO_URL/.claude/commands/wiki-extract.md" # wiki 相關 hooks:接關 + 機敏掃描 download_if_missing ".claude/hooks/session-start-recall.sh" "$REPO_URL/.claude/hooks/session-start-recall.sh" @@ -277,6 +302,18 @@ if $WANT_SDD; then download_if_missing ".claude/commands/sdd-check.md" "$REPO_URL/.claude/commands/sdd-check.md" download_if_missing ".claude/hooks/sdd-guard.sh" "$REPO_URL/.claude/hooks/sdd-guard.sh" + + # SDD 生命週期鐵律(1.14,issue #6):規則真相源 + 規格變更緩衝區 + 獨立單一活性檢查 + download_if_missing "system-dev/docs/3-specs/SDD-LIFECYCLE.md" "$REPO_URL/system-dev/docs/3-specs/SDD-LIFECYCLE.md" + download_if_missing "system-dev/docs/3-specs/pending-changes.md" "$REPO_URL/system-dev/docs/3-specs/pending-changes.md" + download_if_missing "system-dev/scripts/sdd-active-check.sh" "$REPO_URL/scripts/sdd-active-check.sh" + + # ── tasks⇄Project 投影(optional,issue #16)────────────────── + # 帶檔 ≠ 啟用:workflow yaml 只是「留記錄+手動啟用素材」,啟用=對話答好且 acr push。 + # 投影邏輯依附 tasks.md(住 3-specs),故隨 SDD 模組帶下來;裝了不代表開。 + create_dir "system-dev/workflows" + download_if_missing "system-dev/workflows/tasks-project-sync.yaml" "$REPO_URL/system-dev/workflows/tasks-project-sync.yaml" + download_if_missing "system-dev/workflows/tasks-project-sync.local.sh" "$REPO_URL/system-dev/workflows/tasks-project-sync.local.sh" fi # ── 安裝/更新腳本:一開始就放進 system-dev/scripts/ ── @@ -293,6 +330,8 @@ download_if_missing ".claude/hooks/pre-write-guard.sh" "$REPO_URL/.claude/hooks/ download_if_missing ".claude/commands/issue-handle.md" "$REPO_URL/.claude/commands/issue-handle.md" chmod +x .claude/hooks/*.sh 2>/dev/null || true +chmod +x system-dev/workflows/*.sh 2>/dev/null || true +chmod +x system-dev/scripts/*.sh 2>/dev/null || true # ── 依模組產生 settings.json 的 hooks 區塊 ──────── # settings.json 因模組而異,不能直接下載單一靜態檔,改條件組裝。 @@ -447,6 +486,19 @@ fi if $WANT_SDD; then t " 動 code 前先在 system-dev/docs/3-specs/[子系統]/ 建 design.md(可用 /sdd-check 協助)" \ " Before touching code, create design.md under system-dev/docs/3-specs/[subsystem]/ (use /sdd-check to help)" + # ── tasks⇄Project 投影:裝/init 對話 + 一次性廣告(issue #16)── + # 判準=能力(arcrun 裝了沒)+意願,不掃檔。落地成「CC 問一句」,install 只是交代 CC 去問。 + echo "" + t " ❓ 待辦同步(optional,需 Arcrun):請你的 CC 問你一句——" \ + " ❓ Task sync (optional, needs Arcrun): have your CC ask you once —" + t " 「您需要把本專案的待辦事項(tasks.md)同步到 GitHub 嗎?」" \ + " \"Do you want this project's tasks (tasks.md) mirrored to GitHub?\"" + t " 答「好」→ CC 查環境有沒有 Arcrun(mcp / acr 在 PATH):有就設定同步、沒有就一次性告知" \ + " Yes → CC checks for Arcrun (mcp / acr in PATH): set it up if present, otherwise inform you once" + t " 「Arcrun 是免費的 AI-friendly 工作流套件,想裝跟 Claude 說就行;之後也可手動啟用」。" \ + " \"Arcrun is a free AI-friendly workflow toolkit — ask Claude to install it; you can also enable sync later.\"" + t " 答「不好」→ 不做、不再追問。投影 workflow 在 system-dev/workflows/(帶檔≠啟用)。" \ + " No → nothing happens, no nagging. The projection workflow sits in system-dev/workflows/ (shipped ≠ enabled)." fi t " GitHub issue:CC 可直接 /issue-handle 讀回自己 repo 的 issue(禁自動輪詢)" \ " GitHub issues: CC can use /issue-handle to read issues from its own repo (no auto-polling)" diff --git a/system-dev/scripts/update.sh b/system-dev/scripts/update.sh index 90e99d6..823ab46 100755 --- a/system-dev/scripts/update.sh +++ b/system-dev/scripts/update.sh @@ -23,7 +23,7 @@ esac t() { if [ "$IS_ZH" = "yes" ]; then printf '%s\n' "$1"; else printf '%s\n' "$2"; fi; } tn() { if [ "$IS_ZH" = "yes" ]; then printf '%s' "$1"; else printf '%s' "$2"; fi; } -REPO_RAW="https://raw.githubusercontent.com/uncle6me-web/system-dev-template/main" +REPO_RAW="https://git.uncle6.me/Leo/system-dev-template/raw/branch/main" TEMPLATE_URL="$REPO_RAW/template" UPDATED=() @@ -31,6 +31,7 @@ KEPT=() NEW=() TEMPLATED=() MIGRATED=() +COEXIST=() # ── 版本比對:先看本機 vs 遠端,給使用者「值不值得更新」的判斷 ── # VERSION 新位置在 system-dev/,舊位置在 .claude/(1.8.x 以前)。優先讀新、回退舊。 @@ -82,7 +83,12 @@ migrate_dir() { # $1=舊路徑 $2=新路徑 local from="$1" to="$2" [ -e "$from" ] || return 0 # 舊的不存在 → 無需遷移 if [ -e "$to" ]; then - return 0 # 新的已存在 → 冪等略過,不覆蓋 + # 目的地已存在。兩種可能: + # (a) 已遷移過 → 舊位置不該還在;冪等略過即可。 + # (b) 用戶先 install 建了空殼 → 舊位置仍有真資料,現在「並存」。 + # 不能靜默跳過 (b),也絕不自動合併(覆蓋風險)。→ 記為「並存待合併」,警告。 + COEXIST+=("$from ↔ $to") + return 0 fi mkdir -p "$(dirname "$to")" if mv "$from" "$to" 2>/dev/null; then @@ -211,15 +217,26 @@ keep_with_template ".claude/hooks/pre-write-guard.sh" "$TEMPLATE_URL/.claude/hoo update_file ".claude/commands/issue-handle.md" "$TEMPLATE_URL/.claude/commands/issue-handle.md" update_file "system-dev/VERSION" "$TEMPLATE_URL/system-dev/VERSION" +# Logseq 任務 marker 解析(單一真相源):vault 萃取(/wiki-extract)與 tasks→Project +# 投影共用同一套;任一模組在用就補/更新(邏輯檔,可覆蓋)。 +if $HAS_WIKI || $HAS_SDD; then + update_file "system-dev/docs/4-guides/logseq-markers.md" "$TEMPLATE_URL/system-dev/docs/4-guides/logseq-markers.md" +fi + if $HAS_WIKI; then # wiki 的「邏輯檔」:導航與 hooks,可覆蓋。wiki 資料在 system-dev/,hooks/commands 留 .claude/。 update_file "system-dev/wiki/INDEX.md" "$TEMPLATE_URL/system-dev/wiki/INDEX.md" update_file ".claude/hooks/session-start-recall.sh" "$TEMPLATE_URL/.claude/hooks/session-start-recall.sh" update_file ".claude/hooks/wiki-secret-scan.sh" "$TEMPLATE_URL/.claude/hooks/wiki-secret-scan.sh" + # 1.16.0:讓 wiki 真的被讀到的兩支(開場 push 全文解決不了「只讀開頭」,見 CHANGELOG) + update_file ".claude/hooks/wiki-first-search.sh" "$TEMPLATE_URL/.claude/hooks/wiki-first-search.sh" + update_file ".claude/hooks/subagent-wiki-guard.sh" "$TEMPLATE_URL/.claude/hooks/subagent-wiki-guard.sh" update_file ".claude/commands/wiki-init.md" "$TEMPLATE_URL/.claude/commands/wiki-init.md" update_file ".claude/commands/wiki-capture.md" "$TEMPLATE_URL/.claude/commands/wiki-capture.md" update_file ".claude/commands/wiki-update.md" "$TEMPLATE_URL/.claude/commands/wiki-update.md" update_file ".claude/commands/wiki-recall.md" "$TEMPLATE_URL/.claude/commands/wiki-recall.md" + # vault 增量萃取(Logseq/Obsidian → system-dev/wiki,冪等):邏輯檔,可覆蓋。舊版沒有 → 當新檔補。 + update_file ".claude/commands/wiki-extract.md" "$TEMPLATE_URL/.claude/commands/wiki-extract.md" # Cowork(claude.ai)的 wiki 整理 skill:規則檔,可覆蓋 update_file "system-dev/docs/SKILL.md" "$TEMPLATE_URL/system-dev/docs/SKILL.md" @@ -240,6 +257,18 @@ if $HAS_SDD; then update_file "system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md" "$TEMPLATE_URL/system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md" update_file ".claude/commands/sdd-check.md" "$TEMPLATE_URL/.claude/commands/sdd-check.md" update_file ".claude/hooks/sdd-guard.sh" "$TEMPLATE_URL/.claude/hooks/sdd-guard.sh" + + # SDD 生命週期鐵律(1.14,issue #6):規則檔+獨立檢查腳本=邏輯檔可覆蓋; + # pending-changes.md 裝著用戶的 proposal=用戶資料,只補不覆蓋。 + # (issue #13 教訓:update 不補新檔會造成結構斷層,新檔必須在這裡鋪。) + update_file "system-dev/docs/3-specs/SDD-LIFECYCLE.md" "$TEMPLATE_URL/system-dev/docs/3-specs/SDD-LIFECYCLE.md" + add_if_missing "system-dev/docs/3-specs/pending-changes.md" "$TEMPLATE_URL/system-dev/docs/3-specs/pending-changes.md" + update_file "system-dev/scripts/sdd-active-check.sh" "$TEMPLATE_URL/scripts/sdd-active-check.sh" + + # tasks⇄Project 投影(issue #16):邏輯檔,可覆蓋。舊版沒有 → add_if_missing 補。 + # 啟用狀態存遠端(acr push),不在這些檔裡,覆蓋不會關掉誰的同步。 + add_if_missing "system-dev/workflows/tasks-project-sync.yaml" "$TEMPLATE_URL/system-dev/workflows/tasks-project-sync.yaml" + add_if_missing "system-dev/workflows/tasks-project-sync.local.sh" "$TEMPLATE_URL/system-dev/workflows/tasks-project-sync.local.sh" fi # ── 自我更新:把最新的 update.sh / install.sh 抓到 system-dev/scripts/ ── @@ -247,7 +276,7 @@ fi update_file "system-dev/scripts/update.sh" "$REPO_RAW/scripts/update.sh" update_file "system-dev/scripts/install.sh" "$REPO_RAW/scripts/install.sh" -chmod +x .claude/hooks/*.sh system-dev/scripts/*.sh 2>/dev/null || true +chmod +x .claude/hooks/*.sh system-dev/scripts/*.sh system-dev/workflows/*.sh 2>/dev/null || true # ── 使用者資料檔:絕不碰,但提醒「設定可能有新欄位要手動補」── keep_file ".claude/settings.json" @@ -261,6 +290,18 @@ if [ ${#MIGRATED[@]} -gt 0 ]; then t "📦 結構遷移(已收進 system-dev/):" "📦 Layout migrated (moved into system-dev/):" for f in "${MIGRATED[@]}"; do echo " ⇒ $f"; done fi +if [ ${#COEXIST[@]} -gt 0 ]; then + echo "" + t "🛑 偵測到 wiki 並存(新舊位置都有資料,需要合併):" \ + "🛑 Coexisting wiki detected (both old and new locations have data — needs merging):" + for f in "${COEXIST[@]}"; do echo " ↔ $f"; done + t " 成因:先跑過 install(建了空殼)才遷移,舊位置真資料沒被搬。" \ + " Cause: install ran first (created an empty shell), so migration skipped your real data in the old location." + t " 不自動合併(避免覆蓋你的資料)。請叫你的 CC:" \ + " Not auto-merged (to avoid overwriting your data). Ask your CC:" + t " 「.claude/wiki/ 和 system-dev/wiki/ 並存,請逐檔比對、把真資料合進 system-dev/,再刪舊的」" \ + " \"There are two wikis (.claude/wiki/ and system-dev/wiki/) — diff each file, merge the real data into system-dev/, then delete the old one.\"" +fi if [ ${#NEW[@]} -gt 0 ]; then echo "" t "🆕 新功能(舊版沒有,已加入):" "🆕 New features (absent in the old version, now added):" @@ -299,6 +340,34 @@ if [ -f ".claude/settings.json" ]; then $HAS_WIKI && ! grep -q "session-start-recall.sh" .claude/settings.json && MISSING+=("SessionStart: session-start-recall.sh") $HAS_WIKI && ! grep -q "wiki-secret-scan.sh" .claude/settings.json && MISSING+=("PreToolUse(Write|Edit): wiki-secret-scan.sh") $HAS_SDD && ! grep -q "sdd-guard.sh" .claude/settings.json && MISSING+=("PreToolUse(Write|Edit): sdd-guard.sh") + + # ── 1.16.0:兩支 wiki 讀取 hook 自動註冊(不只提醒)── + # 理由:這兩支的整個存在意義就是「不依賴任何人記得」。 + # 若靠人看提醒去手動補 settings.json,等於把同一個病搬到安裝環節。 + if $HAS_WIKI && command -v python3 >/dev/null 2>&1; then + python3 - <<'PYEOF' 2>/dev/null || true +import json, os +p = ".claude/settings.json" +try: + with open(p) as f: d = json.load(f) +except Exception: + raise SystemExit(0) # 壞掉的 settings 不碰,交給下方提醒 +pre = d.setdefault("hooks", {}).setdefault("PreToolUse", []) +blob = json.dumps(pre) +added = [] +if "wiki-first-search" not in blob: + pre.append({"matcher": "Grep|Glob|Read", "hooks": [ + {"type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/wiki-first-search.sh"}]}) + added.append("wiki-first-search") +if "subagent-wiki-guard" not in blob: + pre.append({"matcher": "Task", "hooks": [ + {"type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/subagent-wiki-guard.sh"}]}) + added.append("subagent-wiki-guard") +if added: + with open(p, "w") as f: json.dump(d, f, ensure_ascii=False, indent=2) + print(" ✅ 已自動註冊 wiki 讀取 hook:" + "、".join(added)) +PYEOF + fi if [ ${#MISSING[@]} -gt 0 ]; then echo "" t "📌 settings.json 是你的設定(沒動),但偵測到缺以下 hook,請手動補上:" \ diff --git a/system-dev/wiki/INDEX.md b/system-dev/wiki/INDEX.md index eb34019..32300ff 100644 --- a/system-dev/wiki/INDEX.md +++ b/system-dev/wiki/INDEX.md @@ -39,21 +39,6 @@ INDEX 是**所有檢索角度的入口**,不只標籤。原文是唯讀 SSoT,wiki 是改寫過的記憶。 新增角度只要在這裡加一節(如「決策角度」「原則角度」),指向對應 cards 或 push 檔——**不必新增實體特殊檔**。 -### 快速導航(本專案速覽) - -**這個專案是什麼**:KBDB-graph —— KBDB 的 graph 插件(triplet 採集 + graph 查詢),類比 Apache AGE 之於 Postgres。已抽成獨立 public repo `uncle6me-web/kbdb-graph-plugin`(leo 產權)。基本盤(block CRUD,D1 三表)在 `arcrun/kbdb`,不在這。 - -**動工前必讀**: -- `docs/HANDOFF-kbdb-plugin.md` —— 本目錄專屬交棒。 -- 上游約束見 `CLAUDE.md` 最頂 + `github.com/uncle6me-web/InkStoneCo`。 - -**文件去哪找**: -- SDD(design+tasks)→ `docs/3-specs/`(現有:kbdb-graph-extraction、blocks-edit-api、plugin-install、arcrun-key-auth) -- 歷史記錄 / bug 復盤 → `docs/5-records/`(PATCH 403 bug、upsert feature request) -- 分類規則全表 → `docs/README.md` - -**絕對限制**:本目錄只做 graph 插件 / **API-as-Wall(插件絕不碰表、零 SQL、零 migration、零建表)** / 部署繞開 GitHub、禁跨 repo Actions / 樂高法(actions < 100 行)。 - ### 標籤角度(按 `TAXONOMY.md` 的軸聚類,指向桶子索引) ```markdown @@ -64,17 +49,11 @@ INDEX 是**所有檢索角度的入口**,不只標籤。原文是唯讀 SSoT - [[ai/00-INDEX]] — AI 協作(M 卡) ``` -(尚未建 cards,現有決策見下「決策角度」。) +### 決策角度(取代舊 decisions-summary.md 的視圖) -### 決策角度(取代舊 decisions-summary.md 的視圖;完整脈絡見 `decisions-summary.md` + `docs/2-architecture/decisions/`) - -- **KBDB-graph 定位**(2026-06-13)— 本 repo = KBDB 的 graph 插件,獨立成 repo,類比 AGE 之於 Postgres。 -- **🔒 KBDB 鐵律 + API-as-Wall**(2026-06-14,最高原則)— 插件絕不碰表、零 SQL、零 migration,讀寫全走基本盤 HTTP API;新類型=建 template+填 slot,永不建表。 -- **獨立 repo 名**(2026-06-14)— public `uncle6me-web/kbdb-graph-plugin`,無 Actions。 -- **掛載介面 = 基本盤 API(非共用 D1)**(2026-06-14,推翻原判斷)— 圖在插件層記憶體從 records 組裝,不直接 SQL、不建 VIEW。 -- **安裝契約:KBDB_BASE_URL 安裝時 AI 填**(2026-06-14)— AI 查 CF subdomain 拼 URL → `wrangler secret put` + `deploy`;本地測試用 `.dev.vars`。 -- **~~萬物皆 Block(v3)~~**(2026-02-28 提出,2026-06-14 淘汰)— 帶獨立 blocks 表的「v3」是違規殘留已刪;基本盤真身 = arcrun/kbdb 3 表。 -- **避免再被 GitHub flag**(上游鐵律)— 禁跨 repo 自動同步 Actions;部署繞開 GitHub。 +```markdown +- [[某決策卡]] — 一句話結論(YYYY-MM-DD) +``` > 結構:INDEX(多角度入口)→ `cards//00-INDEX.md`(桶子索引,固定名)→ 概念原子卡。 > 指 `00-INDEX` **一律帶路徑** `[[bucket/00-INDEX]]`(固定名跨桶撞名);卡片間用裸 `[[卡名]]`。