2 Commits

Author SHA1 Message Date
Leo 5d762440b7 docs(wiki): session 末更新 status + 補兩條 mistakes
- status.md:T0.5–T5 實作完成、PR #3/#4 拆分、issue #2 close、下次 merge 順序 + 部署待驗
- mistakes.md:① 功能 PR 別混 template 遷移(拆 PR)② 跨 repo 契約漂移先查總管裁定別自決

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 20:55:13 +08:00
Leo 06e901f590 chore: template 1.9.x 知識庫遷移 → system-dev/
把 system-dev-template 1.9.x 的知識庫基建搬進 git(從功能 PR 拆出,獨立成筆):
- system-dev/wiki/:LLM 記憶系統(principles 鐵律 + 5 張 ingest 卡 + INDEX/TAXONOMY + status/mistakes)
- system-dev/docs/:SDD 新家(3-specs/ + 2-architecture/ + README/SKILL);ingest-pipeline SDD 從 docs/3-specs/ 搬來
- system-dev/scripts/:install/update
- .claude/:wiki/SDD harness(commands + hooks:session-recall / sdd-guard / wiki-secret-scan)

SDD 位置統一:docs/3-specs/ingest-pipeline → system-dev/docs/3-specs/ingest-pipeline
(對齊 SDD guard hook 預期路徑 + template 1.9.x 規約)。

純基建遷移,不含任何功能程式碼(src/tests/contracts 在功能 PR #3)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 20:45:18 +08:00
36 changed files with 2647 additions and 45 deletions
+80
View File
@@ -0,0 +1,80 @@
---
description: 處理本 repo 的 GitHub issue(讀/回/結案),跨 repo 發要先問人
---
# /issue-handle — GitHub issue 處理指引
你(CC)可以、也該主動用內建的 `gh` CLI 讀寫**自己 repo** 的 GitHub issue。
很多人不知道這件事——`gh` 已內建認證,零開發、零外部依賴。issue 同源於 repo,
比 Notion / Sheets 更適合做交辦與待辦,不必引入外部 SaaS。
這份指引分四層,界線要守住。
---
## 1. 讀 / 回 / 結案(普世基本功 — 直接做,不用問)
對**自己這個 repo**,主動處理 open issue
```bash
gh issue list --state open # 看有哪些待辦
gh issue view <n> # 讀完整內容
# …實作…
gh issue comment <n> --body "[<本 repo> CC] 做了什麼、怎麼決定的、改了哪些檔"
gh issue close <n> # 確認解決後結案
```
回覆要有料:說清楚**做了什麼、為什麼這樣決定、動了哪些檔**,而不是只回「done」。
issue 作者(可能是另一個 repo 的 CC,或人類)要靠你的回覆判斷對不對。
跨 repo 的 issue/comment 開頭一律署名 `[<本 repo> CC]`(見第 3 節鐵律)。
---
## 2. 發 issue 給「別的 repo」(要先問人 — 不可擅自)
當你發現**別的 repo** 有值得修正的地方時:
> ❌ 不要擅自 `gh issue create -R other/repo …`
> ✅ 先問人類:「我發現 X repo 有 Y 問題,要我幫你去那邊發 issue 嗎?」得到同意才發。
理由:通用 template 不知道使用者對那個 repo 有沒有權限、想不想發。留一道人類確認最安全。
(對**自己 repo** 開 issue 記待辦則可直接做——那是自己的 repo。)
---
## 3. 跨 repo 署名(鐵律 — 絕不可漏)
所有 repomira / 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 安全界線(最重要 — 絕不可越)
**「有事才讀」,禁止自動輪詢。**
- ✅ 想看 issue → 當下主動 `gh issue list`
- 🚫 **禁止**掛 GitHub Actions / cron / webhook 去**自動輪詢** issue。
為什麼這條是硬底線:自動輪詢 + 事件 fan-out 正是會觸發 GitHub 異常偵測、
害你的 token 被 rate limit 砍掉的流量模式。template 給很多人用,這條界線必須守住,
保護使用者不踩雷。需要「定期檢查」就由人類主動跑這個指令,不要自動化。
---
## (可選)用 label 分來源
若想用同一套流程同時管「內部交辦」與「外部回報」,可建兩個 label:
`internal`(協作/交辦)、`user`(外部使用者回報),靠 label 分流。
這對通用 template 不預設——你的 repo 需要再建。
+55
View File
@@ -0,0 +1,55 @@
# /sdd-check — 確認當前任務有沒有對應 SDD
動手前執行。確保 CC 有全局觀,不會在沒有設計文件的情況下猛衝。
---
## 執行流程
### 第一步:理解任務
確認使用者要做什麼:
- 涉及哪個子系統?
- 是新功能還是修改現有功能?
- 影響範圍?
### 第二步:尋找對應 SDD
`system-dev/docs/3-specs/` 下尋找對應的子系統目錄,確認有沒有:
- `design.md`(設計文件)
- `tasks.md`(任務清單)
### 第三步:根據結果回應
**情況 A:找到對應 SDD**
```
✅ 找到 SDDsystem-dev/docs/3-specs/[子系統]/
📋 design.md[確認]
📋 tasks.md[確認,列出相關 task]
🎯 對應 task[編號和描述]
繼續嗎?
```
**情況 B:找不到 SDD,任務明確**
```
⚠️ 找不到對應 SDD
任務:[描述]
建議在 system-dev/docs/3-specs/[建議子系統名]/ 建立 SDD
要我幫你起草 design.md 嗎?(需要你確認後才動手)
```
**情況 C:找不到 SDD,任務模糊**
```
⚠️ 找不到對應 SDD,而且任務範圍不夠清楚
請先回答:
1. 這個功能屬於哪個子系統?
2. 完成的標準是什麼?
3. 有沒有不能動的邊界?
```
### 注意
- 找不到 SDD **不等於可以直接動手**
- 小修改(修 bug、改文字)可以豁免,但要明確說「這是小修改,範圍是 X」
- 新功能、架構變動、跨模組的修改 → 一定要有 SDD
+69
View File
@@ -0,0 +1,69 @@
# /wiki-capture — 把對話結論存進 wiki
把這次對話中產生的決策、誤解釐清、或重要結論存入 wiki。
解決「討論過了但知識消失」的問題。
---
## 執行流程
### 第零步:機敏檢查(寫入前一律先過)
把任何內容寫進 wiki 前,先確認**不含**密碼 / API 金鑰 / 私鑰 / 連線字串帳密 / 個資(身分證、信用卡)。
- 命中 → 不要記「值」,改記「位置」(例:「DB 密碼放 `.env`,不入 wiki」)
- 來源整檔機敏 → 提醒使用者加進 `system-dev/wiki/.wikiignore`
- 真要保留示範格式 → 該行尾加 `wiki-secret-ok` 標記
> 這是協議層自律。最後一道 `wiki-secret-scan.sh` hook 會在寫入 `system-dev/wiki/` 時機械攔截,但別依賴它兜底——當場就不要把機敏值帶進來。
### 第一步:辨識對話中的可記錄內容
掃描當前對話,找出:
| 類型 | 判斷標準 | 存到哪 |
|------|---------|-------|
| 架構決策 | 「為什麼選A不選B」「我們決定用X」 | `decisions-summary.md` + `system-dev/docs/2-architecture/decisions/` |
| CC 的誤解被糾正 | CC 說了某件事,使用者說「不是,是...」 | `mistakes.md` |
| 重要狀態更新 | 完成了某件事、阻擋了某件事 | `status.md` |
| 技術發現 | 踩到坑、找到解法、重要行為確認 | `mistakes.md` 或對應 SDD |
### 第二步:列出清單給使用者確認
格式:
```
這次對話我整理了以下內容要存入 wiki:
1. [MISTAKE] CC 誤解了 X,正確是 Y
2. [DECISION] 決定用 A 不用 B,原因是 C
3. [STATUS] 完成了 task 2.3,下一步是 2.4
確認後存入,有需要修改的嗎?
```
**停下來等確認。**
### 第三步:寫入
確認後,依照格式寫入對應檔案:
**mistakes.md 格式:**
```
⚠️ MISTAKE: [錯誤描述]
症狀: [CC 的表現]
正確做法: [應該怎麼做]
原因: [背景]
日期: [YYYY-MM-DD]
```
**decisions-summary.md 格式:**
```
## [主題] — [YYYY-MM-DD]
**結論**[一句話]
**原因**[簡短說明]
**詳細**system-dev/docs/2-architecture/decisions/[檔名]
```
重大決策同時在 `system-dev/docs/2-architecture/decisions/` 建立 ADR 檔案。
### 第四步:確認
告知存到哪些檔案,共幾條記錄。
+230
View File
@@ -0,0 +1,230 @@
# /wiki-init — 初始化或接入 LLM Wiki 系統
初始化這個專案的 LLM Wiki 記憶系統。
新專案建立空白結構,已有專案掃描現有文件並**改寫**成 wiki。
---
## 核心概念:wiki 是 AI 改寫過的記憶,不是原文索引
記憶系統的目的,是讓 AI **之後讀得快**。但人類寫的原始文件——不管是 vault 的隨手記、開發專案的會議記錄、規格草稿、散落的 `.md`——天生是亂的:重複、流水帳、半成品、口語。
如果 wiki 只是一份 `[[原文檔名]]` 指回原文的**索引**,那每次未來要用都得重新解析那團亂,等於沒省到。**wiki 的價值在於「改寫一次,之後每次讀都便宜」**。
所以原則對**所有專案**一致(不分 vault 或一般開發):
> 人類寫的原文是 **SSoT**(真理來源,永遠唯讀)。
> 但實際要長期保存、被 AI 反覆讀的是 **AI 改寫整理過的 wiki**。
> **AI 是總編輯**——把原文改寫成自包含、概念原子化、互相連結、適於 AI 讀的知識條目。
唯一例外:原文是**不可改動的正式文件**(簽署過的規格、法規、合約),必須逐字讀原文——這種才在 wiki 裡用指針指回去,並註明「逐字依原文」。除此之外,一律改寫。
**raw source 永遠唯讀**:所有產出只往 `system-dev/wiki/` 寫,絕不改動、搬移、重新命名原文。
---
## 執行流程
### 第一步:偵測專案狀態
檢查以下項目,判斷是新專案還是已有專案:
- 根目錄有沒有 `system-dev/wiki/`
- 根目錄有沒有 `docs/`(或 vault 的 `pages/``journals/`、根目錄 `.md`
- 有沒有散落的 `.md` 檔案
同時**偵測 raw source 路徑**(同 install.sh 邏輯):
- 根目錄有 `logseq/` → Logseq vaultraw source = `pages/` + `journals/`
- 根目錄有 `.obsidian/` → Obsidian vaultraw source = 根目錄所有 `.md`
- 都沒有 → 一般專案,raw source = `docs/` 下所有 `.md`(及散落的 `.md`
**新專案**(幾乎空的)→ 直接建立結構,跳到第三步
**已有專案**(有文件)→ 執行第二步
### 第二步:已有專案的掃描(已有專案才執行)
1. 遞迴找出 raw source 裡所有 `.md` 檔案
2. **先套用 `system-dev/wiki/.wikiignore`**:命中 pattern 的檔案整個排除,不讀不編入。
-`.wikiignore` 不存在,從範本建立一份(預設排除 `.env`/`*.pem`/`*secret*` 等)
- 被排除的檔案在清單裡標「🚫 .wikiignore 排除」,**不可被覆蓋**
3. 對其餘檔案標注**改寫計畫**:會萃取成哪些 wiki 條目。一份原文可能拆成多個概念原子條目,多份相關原文也可能合併成一條。
4. 列出清單給使用者確認,**停下來等確認**
> **量大時建議用 Haiku 改寫**:逐份原文「改寫成 wiki 格式」是重複、機械、判斷成本低的工作——正適合 Haiku。原文數量多(如數十、上百份)時,主動建議:
> 「共 N 份原文要改寫,這類逐份萃取很適合用 Haiku 並行處理(便宜、夠快)。要我派 Haiku subagent 改寫嗎?」
> 得同意後,用 Task / subagent 把每份原文(或每批)丟給 Haiku 改寫,主模型只負責切分概念、定條目邊界、最後審稿與互連。
> 機敏防護(三層):
> - **L1 .wikiignore**:整檔排除(這一步)
> - **L2 行內標記**:檔案要編入但某段不要 → 遇到 `<!-- wiki:ignore -->` … `<!-- wiki:end -->` 之間的內容**略過**,只留「(此處機敏,已略過)」
> - **L3 hook**:萬一機敏值仍被寫進 wiki`wiki-secret-scan.sh` 會 exit 2 擋下
> 編入任何檔案前,先檢查是否含密碼/金鑰/個資——有就改記「位置」而非「值」。
### 第三步:建立缺少的結構
只建立不存在的目錄和檔案,**已有的一律不動**。
wiki 採**三層 + 標籤橫切**架構(183 卡實證,issue #8):
```
system-dev/wiki/
├── INDEX.md ← 索引:多角度視圖的家(標籤角度、決策角度、…)
├── TAXONOMY.md ← 標籤字典(cards 的分類元資料,受控擴充)
├── status.md ← [push] 時態狀態:當前進度、下一步
├── mistakes.md ← [push] 踩過的坑、被糾正的誤解(防不自覺盲區)
├── principles.md ← [push] 跨全局的設計原則(行動前必服從)
└── cards/ ← [pull] 一切知識內容:原文摘要、AI 筆記、決策、概念…
└── <bucket>/ ← 儲存桶(分類由 frontmatter 標籤承載)
├── 00-INDEX.md ← 桶子索引(固定名,容器:只連不重寫,H2/H3 分節)
└── <概念全名>.md ← 概念原子卡(一概念一檔,自包含)
```
> **[push] / [pull] 是這套 wiki 的核心判準——因為 wiki 主要是給 AI(CC)看的。**
> 見下方「核心判準:push vs pull」。`decisions-summary.md` 已**降級為 cards + INDEX 決策視圖**(決策是知識內容=card);既有的 decisions-summary 若存在,保留為相容,不刪。
關鍵原則:**資料夾只是儲存桶,分類由 frontmatter 標籤承載**。資料夾名不該硬繼承原稿目錄——原稿目錄是「人為了整理草稿」分的,wiki 連分類都該由 AI 重新組織。
> **桶子索引固定叫 `00-INDEX.md`**issue #6):`00-` 前綴讓它排序最前、一眼可辨(像 README 之於資料夾),AI 載入任何 `cards/<bucket>/` 一律先讀它,不必猜。檔內 H1 仍寫主題名(如 `# PKM 知識管理`),語意不丟。
一般專案仍可同時建 `system-dev/docs/` 分類樹(SDD 等):
```
system-dev/docs/{1-vision,2-architecture/decisions,3-specs,4-guides,5-records/{incidents,test-reports},6-user}
```
(純 PKM vault 不需要 `system-dev/docs/` 分類樹時,只建 `system-dev/wiki/`。)
檔案(不存在才建):
- `system-dev/wiki/INDEX.md``TAXONOMY.md`
- `system-dev/wiki/status.md``mistakes.md``principles.md`(三個 push 檔)
- `system-dev/docs/README.md`(一般專案才需要)
---
## 核心判準:push vs pullwiki 是給 AI 看的)
整理任何內容前,先判斷它該 **push****pull**——判準是「**CC 做事時會不會被動看見**」:
- **push**CC 行動前必須主動出現在 contextsession 開始就由 hook 注入)。給「CC 不會主動去查、但不看就出事」的東西。
- **pull**:CC 想到要查、或載入相關卡時才看見。給「CC 面對它時自然會查」的知識。
**為什麼這是核心**mistakes 防的是 CC「不自覺的盲區」——一個你不知道存在的錯,你不會主動去檢索它。靠 CC 自覺去查自己沒自覺的盲區是自相矛盾的,所以 pull 對盲區失效,**必須 push**。原則同理:沒被推到眼前的準繩,CC 設計時很可能沒想到要服從就做了。
| 內容 | push/pull | 注入形態(hook|
|------|-----------|----------------|
| **status** | push | **全文**——CC 必須知道精確的下一步,摘要會漏 task 編號 |
| **principles** | push | **全文(一行一條)**——短而硬的約束,漏一條就違反;≤15 條,超過代表該下放成 card |
| **mistakes** | push | **標題清單 + 一行症狀**,全文按需 pull——量可能大,摘要足以觸發「我正撞到某條」的認出 |
| **decisions、原文摘要、概念知識、一切其餘** | pull | 寫成 cardsCC 面對時自然會查,INDEX 提供角度入口 |
**principles 維護規則**:一行一條精煉準繩(如「不污染用戶根目錄」「目標用戶 low-code」「wiki 主要給 AI 看」)。發現新的跨全局原則 → append 一行;超過 ~15 條代表某些該合併或下放成 card。**累積原則只改 principles.md,不必問用戶開新檔。**
### 第四步:訪談(每次一個問題)
依序問:
1. 這個專案做什麼?(一句話)
2. 有哪些絕對不能違反的限制?(技術棧、架構原則等)
3. 現在進行到哪個階段?
4. 有沒有 CC 曾經犯過的錯要先記下來?
把答案填進 `CLAUDE.md`(如果存在)或建立新的。
### 第五步:改寫成 wiki(AI 當總編輯)
(第二步確認後執行)
**不搬動原文**。逐份讀 raw source,改寫萃取成 `cards/<bucket>/` 裡的自包含原子卡:
- **概念原子化**:一張卡講一個概念,不是一篇原文對一張卡。原文太雜就拆,多份相關原文就合。
- **自包含**:讀卡就懂,不必回去翻原文。把口語、重複、流水帳改寫成結構化知識,**不寫「詳見原文」**。
- **保留來源指針**:每卡標 `**來源**:原文相對路徑`,為可追溯,不是要使用者回去讀。
- **frontmatter 標籤分類**(見下方):分類走 frontmatter `tags:`,不靠資料夾、不靠行內 `#tag`
- **互相連結(typed-edge 三元組)**`## 關聯` 不只列裸 `[[頁面]]`,改寫成帶語義的三元組(見下方)。
- **萃 glossnode 一句說明)**frontmatter 放 `gloss:` —— 這張卡(= 一個 entity / graph node)的一句話定義,供下游語義 normalize(見下方)。
卡片格式(每張卡):
```markdown
---
tags: [知識管理, AI協作, 方法論]
gloss: 一句話定義這個概念是什麼(給下游語義 normalize 用,選填、deep tier 才產)
---
# 概念全名
← [[<bucket>/00-INDEX]]
**來源**`[raw source 相對路徑]`
**最後更新**YYYY-MM-DD
## 摘要
[一句話核心]
## 重點
- [自包含改寫的要點,不依賴原文]
## 實體
> 本卡內文的關鍵實體(也是 graph node)。名+描述供下游 embedding normalize。集中放、一行一個、不縮排、不重複。
- **原子筆記**atomic note/卡片原子化)— 每張卡只承載一個不可再分論點的知識記錄單元。
- **傳統筆記**(大鍋炒筆記)— 把多主題混雜在同一篇、難精確引用的記錄方式。
## 關聯
### 內文知識關係(內文實體間;端點=上方 `## 實體` 正規名,一字不差)
- 原子筆記 >> 對立於 >> 傳統筆記
### 卡片關係(卡對卡)
- [[本卡]] >> 謂詞(動詞短語) >> [[他卡]]
```
**麵包屑用帶路徑 wikilink**issue #7):H1 次行放 `← [[<bucket>/00-INDEX]]` 指回桶子索引。
桶子索引固定名 `00-INDEX` 跨桶會撞名,故**指 00-INDEX 一律帶路徑**`[[pkm/00-INDEX]]`Logseq 原生支援、下游 ingest 也能對應到具體檔)。普通卡片間連結仍用裸 `[[卡名]]`(卡名唯一,不需路徑)。
**frontmatter 標籤分類**issue #8):
- **用 frontmatter `tags:` 而非行內 `#tag`**:卡片內文常大量用 `#`(講筆記法時的 `#猜想``#book100`),分類標籤若也行內 `#`,下游 ingest 無法區分「分類」與「內文範例」會污染 graph。frontmatter 與內文完全分開,零歧義。
- **用標籤而非資料夾分類**:資料夾=強制單一歸屬;標籤=多重歸屬。一張卡可同時屬知識管理+AI協作+架構設計,硬塞一個資料夾會在其他檢索角度漏掉。
- **雙軸 taxonomy**(寫進 `TAXONOMY.md` 當字典;**受控擴充**,非凍結):
- 領域(主軸,1-3 個):如 知識管理/學習認知/AI協作/生產力/系統設計/工具教學
- 形態(副軸,0-2 個):方法論/工具實作/觀點主張/架構設計/案例經驗
- 一般開發專案的軸可不同(如 子系統/層級/決策類型),由 AI 依專案性質提出、寫進 TAXONOMY.md。
- **遇到現有軸裝不下的內容**:先查是否只是現有標籤的同義詞;確實是新軸才加進 TAXONOMY.md(附定義)再用——**禁止繞過字典在卡片直接冒新標籤**。字典是 per-repo,跨 repo 不必共用。
**typed-edge 規則**issue #5/#11,把「關係」也預編譯,下游 ingest 直接 parse 出帶類型的有向邊):
- **重點抓內文實體關係,不只卡對卡**:卡對卡(`[[卡A]] >> 謂詞 >> [[卡B]]`)只是既有雙鏈加動詞、資訊量幾乎沒增加;價值在內文概念關係(`原子筆記 >> 對立於 >> 傳統筆記`A/B 是內文概念非卡標題)。
1. **方向性**`A >> 謂詞 >> B` 必須讀成「A(謂詞)B」一句通順的話;A、B 順序就是主→賓真實方向。
2. **謂詞用動詞 / 動詞短語**(反駁、奠基於、犧牲)。**禁名詞當謂詞**——`>> 存儲格式 >>``>> 操作體驗 >>` 讀不通,是錯的。
3. **謂詞自由但別太天馬行空**:「參考/參照」皆可(下游 embed 自動聚類),別寫「瞄了一眼」這種抓不到同義的。
4. **內文三元組端點用裸文字**(非 `[[wikilink]]`),避免 Logseq 紅色斷鏈;卡對卡那層才用 `[[]]`
5. **向後相容**:純 `[[A]]` 仍合法(視為無類型邊),盡量補謂詞。
> **★ 硬自檢(Haiku 量產必備)★** 內文三元組端點必須與 `## 實體` 某粗體正規名【一字不差】。**寫完逐條把 A、B 拿去 `## 實體` 比對**,沒有完全相同的 → 這條錯了,改用實體表已有的詞、或把端點補進 `## 實體` 再指它。禁止端點帶括號註解/整句補語/形容詞短語。(實證:光寫規則 Haiku 會略過,端點對不齊 14 條;寫成自檢動作後 14→0。跑 12 張才暴露。)
> `>>` 是分隔語法,repo 可自選符號,但全程一致。
**萃 gloss 規則**issue #9/#11,把「node 的一句說明」也預編譯,供下游 KBDB 語義 normalize):
- **gloss = 這個 entity / graph node 是什麼的一句話**。下游對「entity 名 + gloss」一起做 embedding 求相似度,自動歸一同義詞(比只對名字準、比手維護 alias 表自動)。
- **兩層 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]]`
> 與 claude.ai Cowork 的 `system-dev/docs/SKILL.md` 改寫邏輯一致,兩條路徑(CC / Cowork)產出同一種 wiki。
### 第六步:完成報告 + 驗證
完成後**驗證原文 0 動**(踩過的坑,issue #8):
```
git status --short pages/ journals/ # 或一般專案的 docs/ ——須 0 新增 0 修改
```
> **改寫時必守**subagent 尤其):
> 1. **絕不寫入 raw source**subagent 目標一律給絕對路徑到 `cards/<bucket>/`,明寫「絕不寫入 pages/journals/docs 原稿」;事後用上面的 `git status` 驗。
> 2. **檔名 = 卡片全名**,否則 `[[全名]]` 對不到檔。冒號用全形「:」、斜線用全形「/」,**全程一種字元**,避免 `/`、`∕`、`:` 混用斷鏈。
> 3. **量大用 Haiku 並行改寫**,主模型只切概念邊界+審稿+修跨資料夾斷鏈。
告知:
```
✅ wiki-init 完成
建立了:[列出新建的目錄和檔案]
跳過了:[列出已有因此不動的]
改寫了:[N 份原文 → M 張原子卡、K 條 typed-edge、M 條 glossdeep tier]
原文驗證:pages/ journals/ git status 0 異動 ✅
下一步:用 /wiki-capture 把重要決策存進 wiki
```
+58
View File
@@ -0,0 +1,58 @@
# /wiki-recall — Session 開始,手動接關
開新對話時接上次進度。**Fallback 命令**SessionStart hook 沒啟動時手動接關;要完整脈絡時也用。
> 主路徑是 SessionStart hook 自動注入 status 重點,不靠你打命令。
> 這支命令應對 hook 失效,以及需要比「status 重點」更完整脈絡的時候。
---
## 命名閉環
init(建) → update(存,session 末) ↔ **recall(接,session 初)** → capture(隨時存結論)
---
## 執行流程
### 第一步:讀 status.md(當前進度)
`system-dev/wiki/status.md`,掌握:
- 正在做什麼、阻擋點
- 下次 session 第一件事
- 待負責人確認、已知問題
### 第二步:讀 decisions-summary.md(為什麼這樣做)
`system-dev/wiki/decisions-summary.md`,掌握相關的架構決策——避免重新討論已定案的事。
### 第三步:讀 mistakes.md(別重犯)
`system-dev/wiki/mistakes.md`,掌握已知誤解 + 快速檢查清單。
### 第四步:掃 wishlist / HANDOFF(如果有)
- `docs/wishlist.md`:待補功能
- 任何 `HANDOFF.md` / 交接note:上一棒留下的脈絡
### 第五步:回報接關結果
```
📍 接關完成
🔄 上次正在做:[status 的「正在做」]
🎯 下次第一件事:[status 的「下次 session 第一件事」]
⚠️ 待確認:[如有]
```
---
## 鐵律:快照非即時狀態
status / wiki 是 **point-in-time 快照,不是即時狀態**
接關 讀快照 ** 核實快照****不盲信**。
> 實例:某專案 status 曾寫「待 A 收尾 X」,實際 X 早已完成。
> 照舊資訊行動會去催一件已完成的事。
動手前,先用當前 code / git / 檔案核實快照寫的事項是否仍成立。發現落差 → 先更新 status,再動手。
+50
View File
@@ -0,0 +1,50 @@
# /wiki-update — Session 結束,更新狀態
每次 session 結束時執行。更新 status.md,確保下次 session 能無縫接上。
---
## 執行流程
### 第一步:整理這次 session 的結果
從對話中提取:
- 完成了哪些 tasks(標記為 [x])
- 進行中但未完成的(標記為 [🔄]
- 遇到什麼問題或阻擋
- 下次應該從哪裡開始
### 第二步:更新 tasks.md
把對應 SDD 的 tasks.md 狀態更新(如果這次有動到的話)。
### 第三步:更新 status.md
用以下格式覆蓋 status.md
```markdown
# 當前狀態
> 更新時間:[YYYY-MM-DD]
## 正在做
- [🔄] [task 描述] — 阻擋點:[如果有]
## 下次 session 第一件事
[具體的第一個動作,越具體越好]
## 待負責人確認
- [描述] — 等待:[什麼決定]
## 已知問題
| 問題 | 優先級 | 狀態 |
|------|--------|------|
| [問題] | 🔴/🟡/⚪ | [狀態] |
```
### 第四步:如果有新的誤解或決策
順帶執行 `/wiki-capture` 的邏輯,把這次的誤解和決策也存進去。
### 第五步:確認
告知 status.md 更新完成,下次 session 從哪裡開始。
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
# PreToolUse hook 範本骨架 —— 專案自訂禁令(預設空殼,不攔任何東西)
#
# ⚠️ 定位(讀清楚再用):
# 這支跟其他三支 hook 不同——它不是「裝上就生效的警察」,而是一個「按需手填的
# 空插槽」。預設狀態下 FORBIDDEN_PATTERNS 是空的,它【不攔任何東西】。
# 別誤以為裝了它就有保護——空殼 = 沒保護。
#
# 🤖 有 CC 在場的話,通常不需要這個範本:
# 直接叫你的 CC「幫我寫一支 guard hook,禁止改 X」。CC 現寫的條件邏輯,
# 表達力遠勝這裡的 glob FORBIDDEN_PATTERNS(例如「禁子 repo 的 code 但放行 .md」
# 這種細緻規則,glob 寫不出來,CC 的條件判斷寫得出來)。
# 這個範本只對「不靠 CC、想自己手動 DIY bash」的用戶有價值。
#
# 要啟用(手動 DIY 路線):
# 1. 在下面 FORBIDDEN_PATTERNS 填禁改的路徑/檔名 pattern
# 2. 到 .claude/settings.json 的 PreToolUse 加掛這支
#
# 掛在 PreToolUsematcher: Write|Edit)。stdin 收 JSON{ tool_name, tool_input:{ file_path } }
# 命中禁令 → exit 2 擋。
#
# 誠實限制:只擋直接寫檔。bash 繞道、helper 間接改動擋不到。留痕可審 ≠ 技術防偽。
set -euo pipefail
# ── 專案自訂:禁改的 pattern(一行一個,case glob 語法)──────
# 範例(已註解,啟用前請改成自己的):
# "*/db/schema.sql" # 禁手改 schema
# "*/migrations/*" # migration 一旦建立不可改
FORBIDDEN_PATTERNS=(
# "*/your/protected/path/*"
)
# 沒設任何禁令 → 空殼狀態,安靜放行。
# (不在這裡 print——PreToolUse 每次 Write/Edit 都會跑,每次喊話會洗版。
# 「這是空殼」的提醒改由 install.sh / update.sh 安裝時告知,那裡用戶一定看得到。)
[ ${#FORBIDDEN_PATTERNS[@]} -eq 0 ] && exit 0
INPUT=$(cat)
if command -v jq >/dev/null 2>&1; then
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
else
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
fi
[ -z "$FILE_PATH" ] && exit 0
for pattern in "${FORBIDDEN_PATTERNS[@]}"; do
# shellcheck disable=SC2254
case "$FILE_PATH" in
$pattern)
cat >&2 <<EOF
🚫 專案禁令攔截:$FILE_PATH 命中禁改規則($pattern)。
這是本專案 .claude/hooks/pre-write-guard.sh 設定的硬底線。
要動 → 先和負責人確認,並更新禁令設定。
EOF
exit 2
;;
esac
done
exit 0
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
# PreToolUse hook — 動 code 前檢查有沒有對應 SDD
# wishlist §2:把 /sdd-check 從「命令要人打」升級成「hook 自動攔」。
#
# 掛在 settings.json 的 PreToolUsematcher: Write|Edit)。
# stdin 收到 JSON{ tool_name, tool_input: { file_path, ... } }
# 行為:動到 code 檔(.ts/.go/...)但 system-dev/docs/3-specs/ 下沒有任何 SDD → 警告(exit 2 擋)。
#
# 誠實限制(抄 arcrun):只擋語法層明顯違規(直接寫 code 檔)。
# 藏在 helper 裡、用 bash 繞道的改動擋不到。
# 價值是「想跳過會被抓到 + 留痕可審」,不是技術防偽。絕不聲稱「不可能繞過」。
set -euo pipefail
INPUT=$(cat)
# 解析 file_path。優先用 jq,沒有 jq 退回 grep(容錯)。
if command -v jq >/dev/null 2>&1; then
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
else
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
fi
# 拿不到路徑 → 不擋(容錯,寧可放過也不誤殺)
[ -z "$FILE_PATH" ] && exit 0
# 只管 code 檔。docs/markdown/設定檔等放行。
case "$FILE_PATH" in
*.ts|*.tsx|*.js|*.jsx|*.go|*.py|*.rs|*.java|*.rb|*.php|*.c|*.cpp|*.h|*.hpp|*.swift|*.kt) ;;
*) exit 0 ;;
esac
# 改 SDD 自己 / 測試檔 → 放行
case "$FILE_PATH" in
*system-dev/docs/3-specs/*) exit 0 ;;
*_test.*|*.test.*|*.spec.*|*/tests/*|*/test/*) exit 0 ;;
esac
# system-dev/docs/3-specs/ 下完全沒有 design.md → 攔
SDD_COUNT=0
if [ -d "system-dev/docs/3-specs" ]; then
SDD_COUNT=$(find system-dev/docs/3-specs -name 'design.md' -not -path '*TEMPLATE*' 2>/dev/null | wc -l | tr -d ' ')
fi
if [ "$SDD_COUNT" -eq 0 ]; then
cat >&2 <<EOF
🚫 SDD 協議攔截:要動 code 檔 ($FILE_PATH),但 system-dev/docs/3-specs/ 下找不到任何 SDD。
絕對鐵律:任何 code 變動前必須有對應 SDDdesign.md)。
請先:
1. 確認這個改動屬於哪個子系統
2. 在 system-dev/docs/3-specs/[子系統]/ 建立 design.md(可用 /sdd-check 協助)
3. 在回覆開頭宣告已讀 SDD + 對應 task
小修改(修 bug、改文字)若確定豁免,請明確說明範圍後由人放行。
EOF
exit 2
fi
# 有 SDD:放行,但留痕提醒要宣告(stderr 警告,不擋)
echo "📋 提醒:system-dev/docs/3-specs/ 下有 SDD。動手前請確認已讀對應 design.md 並在回覆宣告。" >&2
exit 0
+86
View File
@@ -0,0 +1,86 @@
#!/bin/bash
# SessionStart hook — 開 session 自動注入 status.md 重點
# wishlist §1 主路徑:不靠 CC 自覺、不用人說,開 session 就把進度推到眼前。
#
# 掛在 settings.json 的 SessionStartmatcher: startup|resume|clear)。
# stdout 會被當成 context 注入給 CC。
#
# 鐵律:status 是 point-in-time 快照,非即時狀態。
# 這個 hook 只負責「把快照推到眼前」,核實快照是 CC 的責任——下面的提醒就是要它別盲信。
set -euo pipefail
STATUS_FILE="system-dev/wiki/status.md"
PRINCIPLES_FILE="system-dev/wiki/principles.md"
MISTAKES_FILE="system-dev/wiki/mistakes.md"
# ── 舊結構防呆(1.9.0 遷移第 2 層保險)──
# 新版 wiki 收進 system-dev/。若偵測到舊位置 .claude/wiki/ 還在、但新位置沒 status
# 代表使用者升級了規則卻沒跑遷移(low-code 使用者常見)→ 出聲提示,讓 CC 當場可代為遷移,
# 不要默默用不到新結構而出錯。
if [ -d ".claude/wiki" ] && [ ! -f "$STATUS_FILE" ]; then
echo "⚠️ 偵測到舊版 wiki 結構(.claude/wiki/),尚未遷移到 system-dev/wiki/。"
echo " 請跑:bash system-dev/scripts/update.sh"
echo " 或直接叫我(CC):「幫我把 wiki 遷移到 system-dev/」——我可以代為搬移。"
echo " (未遷移時接關與 /wiki-init 會找錯位置。)"
exit 0
fi
# 三個 push 檔都沒有 → 安靜退出,不干擾還沒 /wiki-init 的專案
if [ ! -f "$STATUS_FILE" ] && [ ! -f "$PRINCIPLES_FILE" ] && [ ! -f "$MISTAKES_FILE" ]; then
exit 0
fi
# ── push 1/3principles(全文,行動前必服從)──
# 放最前:原則是「會被遺忘的盲區」,要第一眼看見。全文成本低(一行一條、≤15 條)。
if [ -f "$PRINCIPLES_FILE" ] && grep -q '^- ' "$PRINCIPLES_FILE" 2>/dev/null; then
echo "════════════════════════════════════════════════"
echo "📐 設計原則(行動前必服從,來自 principles.md"
echo "════════════════════════════════════════════════"
grep '^- ' "$PRINCIPLES_FILE" # 只注入原則條目本身,不含說明區
# context 保護:原則應 ≤15 條(push 全文)。超過 → 提示該合併或下放成 card,不截斷(截斷會漏原則)。
P_COUNT=$(grep -c '^- ' "$PRINCIPLES_FILE")
if [ "$P_COUNT" -gt 15 ]; then
echo ""
echo "(⚠️ principles 已 ${P_COUNT} 條 > 15:請考慮合併相近原則、或把較細的下放成 card)"
fi
echo ""
fi
# ── push 2/3:status(全文,當前進度)──
if [ -f "$STATUS_FILE" ]; then
echo "════════════════════════════════════════════════"
echo "📍 接關:上次進度(來自 $STATUS_FILE 快照)"
echo "════════════════════════════════════════════════"
echo ""
cat "$STATUS_FILE"
fi
# ── push 3/3mistakes(標題清單 + 一行症狀,全文按需展開)──
# 不全文注入(可能累積很多、含長 context)。只給「標題行」讓 CC 認出「我正撞到某條」→ 再去讀全文。
if [ -f "$MISTAKES_FILE" ] && grep -q 'MISTAKE' "$MISTAKES_FILE" 2>/dev/null; then
echo ""
echo "────────────────────────────────────────────────"
echo "⚠️ 已知踩坑(標題;撞到時讀 $MISTAKES_FILE 全文)"
grep -E 'MISTAKE' "$MISTAKES_FILE" | sed 's/^/ /'
fi
echo ""
echo "────────────────────────────────────────────────"
echo "⚠️ 以上是 point-in-time 快照,非即時狀態。"
echo " 動手前先核實:快照寫的事項是否真的還沒做完?"
echo " 需要完整脈絡(decisions / mistakes / SDD)→ 執行 /wiki-recall"
echo "════════════════════════════════════════════════"
# ── 遷移殘留偵測(1.9.x):wiki 已搬到 system-dev/,但 CLAUDE.md 內容裡可能還寫舊路徑 ──
# update.sh 鐵則「絕不碰 CLAUDE.md」(用戶資料),所以遷移不會自動改它內容裡的死引用。
# 這裡只「偵測 + 提示 CC 代修」——由 CC(看得懂語義、不誤傷用戶自寫內容)來改,不讓腳本盲改。
if [ -f "CLAUDE.md" ] && grep -q '\.claude/wiki' CLAUDE.md 2>/dev/null; then
echo ""
echo "🔧 偵測到 CLAUDE.md 內仍有舊路徑 \`.claude/wiki/\`wiki 已遷移到 system-dev/wiki/)。"
echo " 這些是遷移後的死引用,會讓你(CC)找錯位置。"
echo " 請主動幫使用者把 CLAUDE.md 裡的 \`.claude/wiki/\` 改成 \`system-dev/wiki/\`、"
echo " 工具 docs 路徑改成 \`system-dev/docs/\`(但 raw source 宣告的 \`docs/\` 保留不動)。"
fi
exit 0
+113
View File
@@ -0,0 +1,113 @@
#!/bin/bash
# PreToolUse hook — 寫入 wiki 前掃機敏資訊(L3 硬攔截)
#
# 為什麼存在:wiki 的 ignore 規則(.wikiignore + 行內標記)是「協議層」,靠 CC 遵守。
# 但密碼/金鑰/個資外洩是「不可逆」後果——只靠口頭約束太危險。
# 這支 hook 是機械式底線:CC 真的把機敏資訊寫進 system-dev/wiki/ 的那一刻 → exit 2 擋下。
#
# 掛在 settings.json 的 PreToolUsematcher: Write|Edit)。
# stdin 收到 JSON{ tool_name, tool_input: { file_path, content?, new_string? } }
# 行為:只在目標路徑是 system-dev/wiki/** 時啟動,掃要寫入的內容,命中機敏特徵 → exit 2。
#
# 誠實限制(抄 sdd-guard):regex 偵測有偽陰/偽陽。
# 擋的是「明顯特徵的機敏字串被自動抄進 wiki」,擋不了刻意混淆/編碼的繞道。
# 價值是「意外外洩的機械底線 + 留痕可審」,不是技術防偽。絕不聲稱「不可能繞過」。
set -euo pipefail
INPUT=$(cat)
# ── 解析 file_path 與要寫入的內容。優先 jq,無 jq 退回 grep(容錯)──────
if command -v jq >/dev/null 2>&1; then
FILE_PATH=$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')
# Write 用 contentEdit 用 new_string。兩個都抓,合起來掃。
CONTENT=$(printf '%s' "$INPUT" | jq -r '[.tool_input.content, .tool_input.new_string] | map(select(. != null)) | join("\n")')
else
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//;s/"$//')
# 無 jq 時內容解析不可靠(JSON 跳脫),退回掃整包 INPUT,寧可多掃不漏掃
CONTENT="$INPUT"
fi
# 拿不到路徑 → 不擋(容錯,寧可放過也不誤殺)
[ -z "$FILE_PATH" ] && exit 0
# 只管寫進 wiki 的動作。其他路徑放行(這支專責 wiki 洩漏,不是全域 secret scanner
case "$FILE_PATH" in
*system-dev/wiki/*) ;;
*) exit 0 ;;
esac
[ -z "$CONTENT" ] && exit 0
# 行內豁免:若該段內容已被標記為刻意保留(例:範例文件要示範格式),略過該行
# 標記:行尾加 # wiki-secret-ok (或 <!-- wiki-secret-ok -->
# 先把標記過的行抽掉再掃。
SCAN=$(printf '%s' "$CONTENT" | grep -v -E 'wiki-secret-ok' || true)
[ -z "$SCAN" ] && exit 0
# ── 機敏特徵 pattern。一行一類,命中即攔。──────────────────────────
# 設計取捨:偏向高訊號 pattern(有明確結構的金鑰/標記),降低偽陽。
# 純「password=xxx」這類也納入,因為那正是使用者最擔心的場景。
HITS=""
check() {
local label="$1" regex="$2"
# -e 讓以 - 開頭的 pattern(如 PEM 的 -----BEGIN)不被當成選項。
# grep 無命中回傳 1,在 set -e 下會中止 → 用 if 包住吸收掉。
if printf '%s' "$SCAN" | grep -qiE -e "$regex"; then
HITS="${HITS}
${label}"
fi
}
# 密碼/密鑰賦值(password = ..., secret: ..., api_key=...
check "密碼/密鑰賦值 (password/secret/api_key/token = ...)" \
'(pass(word)?|secret|api[_-]?key|access[_-]?key|auth[_-]?token|priv(ate)?[_-]?key)[[:space:]]*[:=][[:space:]]*[^[:space:]<>"'"'"']{6,}'
# 私鑰 PEM 區塊
check "私鑰檔內容 (BEGIN ... PRIVATE KEY)" \
'-----BEGIN[[:space:]].*PRIVATE KEY-----'
# 常見雲端/服務金鑰前綴
check "服務金鑰特徵 (AWS/GitHub/Slack/Google/Stripe 等)" \
'(AKIA[0-9A-Z]{16}|gh[pousr]_[0-9A-Za-z]{20,}|xox[baprs]-[0-9A-Za-z-]{10,}|AIza[0-9A-Za-z_-]{20,}|sk_(live|test)_[0-9A-Za-z]{16,})'
# JWT
check "JWT token" \
'eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}'
# 連線字串內嵌帳密 (proto://user:pass@host)
check "連線字串內嵌帳密 (proto://user:pass@host)" \
'[a-z][a-z0-9+.-]*://[^[:space:]:/@]+:[^[:space:]:/@]+@'
# 台灣身分證字號(個資)。BSD/GNU grep 都支援 ERE,避免 \b(BSD 不認),改用字元類邊界。
check "台灣身分證字號 (個資)" \
'(^|[^A-Za-z0-9])[A-Z][12][0-9]{8}([^0-9]|$)'
# 信用卡號(個資,粗略 13-16 連續數字,可含空格/連字號分隔)。避免 PCRE,用 ERE 近似。
check "疑似信用卡號 (個資)" \
'(^|[^0-9])[0-9]{4}[ -]?[0-9]{4}[ -]?[0-9]{4}[ -]?[0-9]{0,4}([^0-9]|$)'
# Email 不擋(wiki 常需記聯絡人),手機號也不擋(偽陽太高)——刻意留白。
if [ -n "$HITS" ]; then
cat >&2 <<EOF
🚫 Wiki 機敏攔截:偵測到可能的機敏資訊要寫進 ${FILE_PATH}。
命中特徵:${HITS}
wiki 是會被 CC 反覆讀取、可能進版控的記憶空間。
密碼 / 金鑰 / 個資寫進去 = 不可逆外洩風險。
請改成下列任一做法:
1. 不要把機敏值寫進 wiki,改記「位置」(例:「DB 密碼放 1Password / .env,不入 wiki」)
2. 確定是誤判(例:在示範格式)→ 該行尾加註記 wiki-secret-ok 後重寫
3. 整個來源檔本就機敏 → 加進 system-dev/wiki/.wikiignore,別讓它被編入
誠實限制:本掃描靠特徵比對,有偽陽/偽陰,是「意外外洩的機械底線」而非保險箱。
真正的密鑰本就不該進版控。
EOF
exit 2
fi
exit 0
+12
View File
@@ -0,0 +1,12 @@
{
"hooks": {
"SessionStart": [
{ "matcher": "startup|resume|clear",
"hooks": [ { "type": "command", "command": ".claude/hooks/session-start-recall.sh" } ] }
],
"PreToolUse": [
{ "matcher": "Write|Edit",
"hooks": [ { "type": "command", "command": ".claude/hooks/sdd-guard.sh" },{ "type": "command", "command": ".claude/hooks/pre-write-guard.sh" },{ "type": "command", "command": ".claude/hooks/wiki-secret-scan.sh" } ] }
]
}
}
-45
View File
@@ -1,45 +0,0 @@
# ingest pipeline — Tasks
> 唯一進度來源。狀態:[ ] 未開始 [🔄] 進行中 [x] 完成 [⏸] 卡住
> 跨專案藍圖:InkStoneCo `docs/3-specs/mira-dissolve/`。
## T0 repo 骨架(本輪)
- [x] 0.1 建 public repo `uncle6me-web/kbdb-ingest-plugin`
- [x] 0.2 CLAUDE.md(上游指針 + ingest 鐵律)+ README + .gitignore
- [x] 0.3 `contracts/ingest-candidate.json`(從頂層 SDD 複製,凍結契約)
- [x] 0.4 SDD 三件式骨架
- [ ] 0.5 package.json / tsconfig / wrangler.toml(參考 kbdb-graph-plugin
## T1 SourceAdapterR1
- [ ] 1.1 GitHub 拉 reporuntime API/clone,非 Actions
- [ ] 1.2 content-hashper-filesource.uri = github:owner/repo@path
- [ ] 1.3 被 KBDB MCP `refresh` 代轉觸發的接口
## T2 採取(R2,路徑 A 優先)
- [ ] 2.1 拉本地 CC 已建三元組 + gloss(用了 system-dev-template 的 repo
- [ ] 2.2 cherry-pick `polaris/mira/tools/_kbdb_client.py` → 改純餵食器(POST envelope,不寫 KBDB
## T3 extractR3,路徑 B fallback
- [ ] 3.1 cherry-pick `wiki_synthesis.yaml` classify / 兩 skill block
- [ ] 3.2 模型用戶可選 + 品質門檻白名單(預設 Haiku,深萃 Claude via CC
- [ ] 3.3 模型測試集(中文 + 人類暗示樣本,轉回歸測試)— deferred,先跑預設
- [ ] 3.4 JSON-fail 升級閘(淺萃失敗升 deep)
- [ ] 3.5 第一版不 embedembed 等 base vectorizeInkStoneCo T2.4
## T4 跨 repo 織網(R4,主職)
- [ ] 4.1 匯總多 repo 三元組
## T5 輸出 + CLIR5/R6
- [ ] 5.1 POST envelope 給 graph `POST /triplets/ingest`(嚴格符合 contract)⏸ 待 graph 寫入端(InkStoneCo T3.3
- [ ] 5.2 薄 ops CLI(手動重萃);不帶查詢 MCP
## 阻擋項
1. ⏸ T5.1 依賴 graph `POST /triplets/ingest`InkStoneCo T3,待 graph repo 實作)。
2. ⏸ embed 依賴 base vectorizeInkStoneCo T2.4)。第一版不 embed 可先動。
+1
View File
@@ -0,0 +1 @@
1.12.0
@@ -0,0 +1,30 @@
# [主題] — Architecture Decision Record
> 日期:[YYYY-MM-DD]
> 狀態:[提議中 / 已採納 / 已廢棄]
> 影響範圍:[哪些子系統 / 模組]
---
## 背景
[遇到了什麼問題,需要做這個決定?]
## 決定
**[結論,一句話。]**
## 原因
[詳細說明為什麼這樣決定。]
## 放棄的選項
| 選項 | 放棄原因 |
|------|---------|
| [選項 A] | [原因] |
| [選項 B] | [原因] |
## 影響與後續
[這個決定影響哪些地方?有什麼技術債或需要注意的事?]
@@ -0,0 +1,76 @@
# [子系統名稱] — Design
> 狀態:[草稿 / 審核中 / 已採納 / 已廢棄]
> 建立:[YYYY-MM-DD] | 最後更新:[YYYY-MM-DD]
> 負責人:[名稱]
---
## 一句話說明
[這個子系統做什麼,一句話。]
---
## 背景與問題
[為什麼需要這個子系統?解決了什麼問題?]
---
## 範圍
### 包含(In Scope
- [這個 SDD 涵蓋的功能]
### 不包含(Out of Scope
- [明確排除的功能,避免 CC 自行延伸]
---
## 設計
### 架構概覽
[用文字或 ASCII 描述系統結構]
```
[元件 A] → [元件 B] → [元件 C]
```
### 關鍵決策
| 決策 | 選擇 | 原因 | 放棄的選項 |
|------|------|------|----------|
| [問題] | [選擇] | [原因] | [其他選項] |
### API / 介面定義
[端點、資料格式、輸入輸出規格]
### 資料模型
[資料結構、欄位說明]
---
## 技術限制
- [不能用什麼]
- [必須相容什麼]
- [效能要求]
---
## 驗收標準
完成的定義(CC 完成任何 task 前必須確認):
- [ ] [可客觀驗證的條件,例如:POST /api/xxx 回傳 200]
- [ ] [...]
---
## 相關文件
- [連結到相關 ADR]
- [連結到相關 SDD]
@@ -0,0 +1,50 @@
# [子系統名稱] — Tasks
> 權威來源:此檔案是進度真相,不是 CLAUDE.md 或對話。
> 規則:動手前標 [🔄],完成立刻標 [x],不批次更新。
---
## Phase 1[Phase 名稱]
### 前置條件
- [ ] [這個 Phase 開始前必須完成的事]
### Tasks
- [ ] 1.1 [task 描述]
- 驗收:[客觀可驗證的完成標準]
- 注意:[CC 容易犯的錯,可選]
- [ ] 1.2 [task 描述]
- 驗收:[...]
---
## Phase 2[Phase 名稱]
> 前置條件:Phase 1 全部完成
- [ ] 2.1 [task 描述]
- 驗收:[...]
---
## 完成定義
整個 SDD 完成 = 以下全部達成:
- [ ] 所有 tasks 標 [x]
- [ ] 驗收標準通過(有客觀證據)
- [ ] design.md 與實作一致(如有出入需更新)
---
## 狀態說明
| 標記 | 意義 |
|------|------|
| `[ ]` | 未開始 |
| `[🔄]` | 進行中(當前 session|
| `[x]` | 完成(有驗收證據)|
| `[~]` | 暫緩(說明原因)|
| `[!]` | 阻擋中(說明阻擋原因)|
@@ -0,0 +1,48 @@
# ingest pipeline — Tasks
> 唯一進度來源。狀態:[ ] 未開始 [🔄] 進行中 [x] 完成 [⏸] 卡住
> 跨專案藍圖:InkStoneCo `docs/3-specs/mira-dissolve/`。
> 實作分支:`claude/ingest-t1-t5-implementation`vitest 28 passed / tsc clean / dry-run 乾淨)。
## T0 repo 骨架
- [x] 0.1 建 public repo `uncle6me-web/kbdb-ingest-plugin`
- [x] 0.2 CLAUDE.md(上游指針 + ingest 鐵律)+ README + .gitignore
- [x] 0.3 `contracts/ingest-candidate.json`(從頂層 SDD 複製,凍結契約)
- [x] 0.4 SDD 三件式骨架(本輪搬到 `system-dev/docs/3-specs/`,對齊 SDD guard hook
- [x] 0.5 package.json / tsconfig / wrangler.toml / vitest.config(參考 kbdb-graph-pluginHono + zod-openapi,無 D1/Vectorize/AI 綁定)
## T1 SourceAdapterR1)— `src/lib/source-adapter.ts`
- [x] 1.1 GitHub 拉 reporuntime git/trees + contents API,非 Actions);GitHubFetcher 介面(測試走 mock
- [x] 1.2 content-hashper-file sha256source.uri = github:owner/repo@pathmakeSourceUri/parseSourceUri round-trip
- [x] 1.3 被 graph `POST /graph/refresh` 代轉觸發的受理端:`POST /refresh``src/index.ts`,被動代轉、無排程)
## T2 採取(R2,路徑 A 優先)— `src/lib/harvest.ts`
- [x] 2.1 採取本地 CC 已建三元組 + glosstemplate 1.8.0+ 格式:frontmatter gloss、`## 實體``## 關聯` typed-edge;卡對卡 vs 內文端點分流)
- [x] 2.2 cherry-pick `_kbdb_client.py` → 改純餵食器 `src/lib/graph-client.ts`POST envelope**不寫 KBDB/base**
## T3 extractR3,路徑 B fallback)— `src/lib/extract.ts`
- [x] 3.1 cherry-pick `wiki_synthesis.yaml` classify 模式 → extract promptJSON nodes[]+triplets[]
- [x] 3.2 模型用戶可選(意圖非型號,LlmCaller 介面,預設 shallow/Haiku、deep/Claude via CC
- [ ] 3.3 模型測試集(中文 + 人類暗示樣本,轉回歸測試)— **deferred**(先跑預設;護欄 + parse 已有單元測試)
- [x] 3.4 JSON-fail 升級閘(淺萃 fail/過稀 → 升 deep 一次)
- [x] 3.5 第一版不 embed(仍【打標】embed/predicate_embed 供未來 base 讀標;embed 動作等 Arcrun #7
- [x] 3.x 端點對齊硬自檢護欄(`src/lib/endpoint-check.ts`leo 壓測 14→0;自檢 + autoAlign 補齊)
## T4 跨 repo 織網(R4,主職)— `src/lib/weave.ts`
- [x] 4.1 匯總多 repo 三元組 → 偵測跨庫橋(同名 node 跨 ≥2 repo)+ 異見(同 s/o 對、不同謂詞);**不算 bridge_score**graph 領域,禁送)
## T5 輸出 + CLIR5/R6
- [x] 5.1 POST envelope 給 graph `POST /triplets/ingest`(嚴格符合 contractbuildEnvelope strict + 顯式禁送欄位自檢提早攔)。對齊【full contract】(含 embed/id/aliases/predicate_embed,總管裁定 ingest 不退)
- [x] 5.2 薄 ops CLI`scripts/ingest-cli.mjs`refresh 經 Worker / pull dry-run);**不帶查詢 MCP**
## 阻擋項 / 誠實標記
1.**端到端 ingest→graph 走通**:依賴 graph receiver 補對齊 full contractgraph#1 補對齊任務,**現 .strict() 會 422 新向量化欄位**+ ingest 部署 + `GRAPH_BASE_URL` 設定 → **待部署驗**,未假綠。
2. ⏸ embed 依賴 base vectorizeArcrun #7)。第一版不 embed(只打標)已動。
3. T3.3 模型測試集 deferredrefresh 端 extractWorkers AI)第一版只走採取,深萃留 CLI/CC。
+56
View File
@@ -0,0 +1,56 @@
# 文件分類索引
> CC 整理文件時的分類依據。找不到分類就問,不要猜。
---
## 分類規則
| 目錄 | 放什麼 | 判斷標準 |
|------|--------|---------|
| **1-vision/** | 為什麼做這個 | 產品願景、北極星、設計哲學 |
| **2-architecture/** | 系統怎麼設計的 | 架構圖、技術棧、元件關係 |
| **2-architecture/decisions/** | 為什麼這樣設計 | ADR,選A不選B的原因 |
| **3-specs/** | 要做什麼 | SDD,每個子系統一個目錄 |
| **4-guides/** | 怎麼做 | 部署、開發流程、CLI 用法 |
| **5-records/** | 發生過什麼 | 歷史記錄,不修改只增加 |
| **5-records/incidents/** | 生產問題復盤 | 故障原因、時間線、改進方案 |
| **5-records/test-reports/** | 測試結果 | 壓測報告、驗收記錄 |
| **6-user/** | 給使用者看的 | README、安裝教學、FAQ |
---
## CC 整理文件時的判斷流程
```
這個文件是...
├── 有明確子系統 + 設計內容? → docs/3-specs/[子系統]/
├── 解釋為什麼做某個決定? → docs/2-architecture/decisions/
├── 說明怎麼操作? → docs/4-guides/
├── 記錄發生過的事? → docs/5-records/
├── 給外部使用者看的? → docs/6-user/
└── 以上都不確定? → 列為「待確認」,問負責人
```
---
## SDD 結構(docs/3-specs/ 下每個子系統)
```
docs/3-specs/[子系統名]/
├── design.md ← 設計文件(要做什麼、怎麼做、邊界在哪)
└── tasks.md ← 任務清單([ ] 未開始 [🔄] 進行中 [x] 完成)
```
CC 動手前必須有這兩個檔案。找不到就停手。
---
## .claude/wiki/ — CC 的記憶空間(CC 維護,人不手動編輯)
| 檔案 | 用途 | 更新時機 |
|------|------|---------|
| `INDEX.md` | wiki 導引 | 新增 wiki 檔案時 |
| `mistakes.md` | CC 已知誤解 + 避坑 | 每次被糾正後 |
| `status.md` | 當前進度 + 下一步 | 每次 session 結束 |
| `decisions-summary.md` | 架構決策摘要 | 重大決策後 |
+260
View File
@@ -0,0 +1,260 @@
---
name: wiki-cowork-scan
description: "掃描本機 Documents 下所有裝了 system-dev-template 的資料夾,自動整理 LLM Wiki。支援一般專案、Logseq vault、Obsidian vault 三種結構,偵測方式與 install.sh 一致。觸發時機:使用者說「整理 wiki」「幫我掃 wiki」「更新我的 wiki」「wiki 掃描」,或 Cowork cron 定期觸發。"
---
# Wiki Cowork Scan
## 核心原則
這個 skill 與 Claude Code 的 `/wiki-init` `/wiki-capture` 共用同一套規則:
| 層 | 規則 |
|------------|-------------------------------------------|
| raw source | 只讀,不動 |
| `system-dev/wiki/` | 唯一輸出地點,只增不覆 |
| `CLAUDE.md` | 不動 |
| `logseq/``.obsidian/``assets/` | 絕對不動 |
**CC 和 Cowork 輸出格式相同,任何一方整理過的內容,另一方看到就跳過或補充,不覆蓋。**
---
## 第一步:發現所有目標資料夾
掃描 `~/Documents`(遞迴深度 3 層),找出所有含 `system-dev/wiki/` 的資料夾。
```
~/Documents/
project-a/system-dev/wiki/ ← ✅ 目標
Logseq/system-dev/wiki/ ← ✅ 目標
其他資料夾/ ← ❌ 跳過
```
找到後列出清單,告訴使用者:「找到 N 個 wiki 資料夾,開始整理。」
---
## 第二步:對每個資料夾偵測 vault 類型
進入每個目標資料夾的**根目錄**`system-dev/wiki/` 的上兩層),依序判斷:
### 判斷順序(與 install.sh 一致)
```
if 根目錄有 logseq/ 資料夾
→ vault 類型:Logseq
→ raw sourcepages/、journals/
→ 忽略:logseq/、assets/
else if 根目錄有 .obsidian/ 資料夾
→ vault 類型:Obsidian
→ raw source:根目錄下所有 .md(排除 .obsidian/ 內的檔案)
else
→ vault 類型:一般專案
→ raw sourcedocs/ 下所有 .md
```
---
## 第三步:讀取現有 wiki 狀態
進入 `system-dev/wiki/`,讀取:
- `INDEX.md`:目前已有哪些 wiki 頁面(多角度視圖入口)
- `status.md`:上次整理時間、進度
- `principles.md`(如果有):本專案跨全局的設計原則——整理時必須服從
目的:**知道哪些已整理過,只處理新增或有變動的 raw source**,不重複整理。
---
## 第四步:整理規則
### 核心判準:push vs pullwiki 是給 AI 看的)
整理任何內容前,先判斷它該進 **push 檔****cardspull**——判準是「**CC 做事時會不會被動看見**」:
- **push 檔**`status.md` / `mistakes.md` / `principles.md`):CC session 開始就被 hook 注入。給「CC 不會主動查、但不看就出事」的東西。
- **pull**`cards/`):CC 想到要查才看見。一切知識內容(原文摘要、AI 筆記、決策、概念…)都寫成 cards。
| 內容 | 去哪 | 理由 |
|------|------|------|
| 當前進度、下一步 | `status.md`(push 全文) | 時態狀態,不看會重做 |
| 跨全局設計原則(一行一條,≤15) | `principles.md`(push 全文) | 會被遺忘的盲區,CC 設計時必服從 |
| 踩坑、被糾正的誤解 | `mistakes.md`(push 摘要+按需展開) | 防 CC 不自覺的盲區 |
| 決策、原文摘要、概念知識、其餘一切 | `cards/<bucket>/`(pull) | 知識內容;CC 面對時自然會查 |
> `decisions-summary.md` 已**降級為 cards + INDEX 決策視圖**(決策=知識內容)。既有的保留為相容,不刪。
> CC 與 Cowork **共用此判準**,產出一致:任一方寫進 push 檔或 cards,另一方看到就跳過或補充,不覆蓋。
### 讀 raw source
逐一讀取 raw source 的 `.md` 檔。跳過:
- 檔名以 `.` 開頭的隱藏檔
- `.wikiignore` 裡列出的 glob pattern(如果存在)
- 含有 `<!-- wiki:ignore -->` 標記的區段
### 整理邏輯
每個 raw source 檔案,判斷:
1. **INDEX.md 裡已有對應條目,且 raw source 未修改** → 跳過
2. **INDEX.md 裡已有條目,但 raw source 有新內容** → 更新對應 wiki 頁面,補充新資訊,不刪舊內容
3. **INDEX.md 裡沒有對應條目** → 新建 wiki 頁面
### Wiki 卡片格式(概念原子卡,存到 `cards/<bucket>/`
```markdown
---
tags: [知識管理, AI協作, 方法論]
gloss: 一句話定義這個概念是什麼(給下游語義 normalize 用,選填、deep tier 才產)
---
# 概念全名
← [[<bucket>/00-INDEX]]
**來源**`[raw source 相對路徑]`
**最後更新**YYYY-MM-DD
## 摘要
[一句話核心]
## 重點
- [自包含改寫的要點,不寫「詳見原文」]
## 實體
> 本卡內文的關鍵實體(也是 graph node)。名+描述一起供下游 embedding normalize。
> AI 生產、人不必讀;集中放、一實體一行、不縮排、不重複。
- **原子筆記**atomic note/卡片原子化)— 每張卡只承載一個不可再分論點的知識記錄單元。
- **傳統筆記**(大鍋炒筆記)— 把多主題混雜在同一篇、難精確引用的記錄方式。
## 關聯
### 內文知識關係(內文實體間;端點=上方 `## 實體` 的正規名,一字不差)
- 原子筆記 >> 對立於 >> 傳統筆記
- 傳統筆記 >> 犧牲 >> 精確引用
### 卡片關係(卡對卡)
- [[本卡]] >> 謂詞(動詞短語) >> [[他卡]]
```
### 架構:三層 + 標籤橫切(183 卡實證)
```
INDEX.md ← 頂層:標籤視圖(非資料夾列表)
TAXONOMY.md ← 標籤字典(受控擴充:先查重再登記)
cards/<bucket>/
├── 00-INDEX.md ← 桶子索引(固定名,容器:只連不重寫)
└── <概念全名>.md ← 概念原子卡
```
- **資料夾只是儲存桶,分類由 frontmatter `tags:` 承載**——不繼承原稿目錄,由 AI 重新組織。
- **桶子索引固定名 `00-INDEX.md`**`00-` 排序最前、一眼可辨,載入任何桶先讀它。
- **frontmatter `tags:` 而非行內 `#tag`**:內文常用 `#`(如 `#猜想`),行內標籤會讓 ingest 分不清「分類」與「內文範例」污染 graph;frontmatter 零歧義。標籤只能用 `TAXONOMY.md` 列出的;**禁止繞過字典在卡片直接冒新標籤**,但字典可受控擴充(遇新軸先查重、確認非同義詞,再登記進本 repo 的 TAXONOMY.md)。
- **麵包屑帶路徑**H1 次行 `← [[<bucket>/00-INDEX]]`。指 `00-INDEX` 因固定名跨桶撞名,**一律帶路徑**;卡片間連結用裸 `[[卡名]]`
### 使用 typed-edge 三元組(抓內文實體關係,不只卡對卡)
用**帶語義的三元組** `A >> 謂詞 >> B` 寫進 `## 關聯`。**重點是抓內文裡的實體關係**——卡對卡(`[[卡A]] >> 謂詞 >> [[卡B]]`)只是把既有雙鏈加個動詞、資訊量幾乎沒增加;知識圖譜的價值在內文概念間的關係(`原子筆記 >> 對立於 >> 傳統筆記`,這些 A/B 是內文概念、不是卡標題)。
格式 `A >> 謂詞 >> B`,規則:
1. **方向性**:必須讀成「A(謂詞)B」一句通順的話;A、B 順序=主→賓真實方向。
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「知識互連」的強化版——連結不只存在,還帶類型與方向。
### 萃 glossnode 一句說明,供下游語義 normalize)
每張卡=一個 entity / graph node。deep tier 改寫時,frontmatter 補一句 `gloss:`——這個 node 是什麼的一句定義。下游 KBDB 對「entity 名 + gloss」一起做 embedding 求相似度,自動歸一同義詞(比只對名字準、比手維護 alias 表自動)。
- **在知識生產的當下、由整理者(CC / Cowork)建**:gloss 跟三元組同階段萃,**不留給下游 ingest 臨時補**——下游只有單檔/跨庫視角,編不出貼合的 gloss。
- **選填、deep tier 才產**:淺萃不浪費。
- **gloss ≠ 摘要**`gloss` 是 frontmatter 給機器 normalize 的定義句(「X 是…」);`## 摘要` 是給人讀的核心句。
- **兩層 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 異動);② 檔名=卡片全名,冒號用全形「:」、斜線用全形「/」,全程一種字元避免斷鏈。
---
## 第五步:更新 INDEX.md 和 status.md
### INDEX.md 格式(頂層 = 標籤視圖)
頂層 INDEX 按 `TAXONOMY.md` 的軸聚類,指向各桶子索引(帶路徑),不是平鋪頁面列表:
```markdown
# Wiki Index
> 最後更新:YYYY-MM-DD HH:MM | 來源:cowork-scan | 總卡數:N
### 知識管理
- [[pkm/00-INDEX]] — PKM 知識管理(N 卡)
### AI 協作
- [[ai/00-INDEX]] — AI 協作(M 卡)
```
桶子索引 `cards/<bucket>/00-INDEX.md` 是容器(只連不重寫,H2/H3 分節列出該桶卡片)。
### status.md 更新
在現有內容**末尾追加**(不覆蓋):
```markdown
## YYYY-MM-DD HH:MMcowork-scan
- vault 類型:[Logseq / Obsidian / 一般專案]
- 掃描檔案:N 個
- 新增頁面:N 個
- 更新頁面:N 個
- 跳過:N 個(未變動)
```
---
## 第六步:回報結果
整理完所有資料夾後,輸出摘要:
```
✅ Wiki 整理完成
資料夾 1~/Documents/project-a
類型:一般專案
新增:3 頁,更新:1 頁,跳過:12 頁
資料夾 2~/Documents/Logseq
類型:Logseq vault
新增:5 頁,更新:2 頁,跳過:47 頁
總計:8 頁新增,3 頁更新
```
---
## 絕對禁止
- ❌ 修改任何 raw source 檔案
- ❌ 修改 `CLAUDE.md`
- ❌ 動 `logseq/``.obsidian/``assets/` 資料夾
- ❌ 刪除 `system-dev/wiki/` 裡已有的頁面(只增補,不刪除)
- ❌ 把機敏資訊(密碼、金鑰、個資)寫進 wiki(遇到跳過並記錄)
- ❌ 整理沒有 `system-dev/wiki/` 的資料夾(那不是這個 skill 的目標)
+472
View File
@@ -0,0 +1,472 @@
#!/bin/bash
# system-dev-template installer
# 已有專案接入腳本——只建立缺少的東西,已有的一律不動。
#
# 模組化安裝:
# --wiki 只裝 LLM Wiki(記憶系統 + 機敏防護)
# --sdd 只裝 SDD 系統(動 code 前必須有 design.md
# --all 兩個都裝(預設)
# 無參數 互動式詢問
#
# 為什麼留在同一個 repo 用參數選,而不是 fork:
# 使用者多半非專業,最怕「我要去哪個 repo」。一個入口 + 選單最友善。
# 等未來功能多到 3+ 個再演進成「模板組合器」。模組邊界先在這裡劃好。
set -euo pipefail
# ── i18n:依 locale 選語言,預設英文 ──────────────────
# 為什麼預設英文:curl | bash 常是 LANG=C,外國人預設就該看得懂;
# 台灣使用者 locale 多為 zh_TW,會自動切回繁中。
case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in
zh*|*Hant*|*Hans*) IS_ZH="yes" ;;
*) IS_ZH="no" ;;
esac
# t "中文" "English" → 依語系印出對應字串
t() { if [ "$IS_ZH" = "yes" ]; then printf '%s\n' "$1"; else printf '%s\n' "$2"; fi; }
# 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"
# install.sh / update.sh 住在 main/scripts/(不在 template/)。
SCRIPTS_URL="https://raw.githubusercontent.com/uncle6me-web/system-dev-template/main/scripts"
CREATED=()
SKIPPED=()
# ── 解析模組參數 ──────────────────────────────────
MODULE=""
for arg in "$@"; do
case "$arg" in
--wiki|--wiki-only) MODULE="wiki" ;;
--sdd|--sdd-only) MODULE="sdd" ;;
--all) MODULE="all" ;;
-h|--help)
if [ "$IS_ZH" = "yes" ]; then
cat <<'HELP'
用法:install.sh [--wiki | --sdd | --all]
--wiki 只裝 LLM WikiCC 記憶系統 + 機敏防護)
--sdd 只裝 SDD 系統(動 code 前強制要有設計文件)
--all 兩個都裝(預設)
無參數 互動式詢問要裝哪個
HELP
else
cat <<'HELP'
Usage: install.sh [--wiki | --sdd | --all]
--wiki Install LLM Wiki only (CC memory system + secret protection)
--sdd Install SDD system only (require a design doc before touching code)
--all Install both (default)
no flag Interactively ask which to install
HELP
fi
exit 0 ;;
esac
done
echo ""
echo "🔧 system-dev-template installer"
echo "================================="
t "只建立缺少的目錄和檔案,已有的不動。" \
"Only creates missing dirs and files; never touches what already exists."
echo ""
# ── 無參數 → 互動式詢問(給非專業使用者)──────────
if [ -z "$MODULE" ]; then
if [ -t 0 ]; then
t "要安裝哪一塊?" "Which part do you want to install?"
t " 1) LLM Wiki —— 讓 CC 記住決策、不重複犯錯(含機敏防護)" \
" 1) LLM Wiki — let CC remember decisions and avoid repeating mistakes (with secret protection)"
t " 2) SDD —— 動 code 前強制先有設計文件" \
" 2) SDD — require a design doc before touching code"
t " 3) 兩個都裝(推薦)" " 3) Install both (recommended)"
echo ""
tn "請輸入 1 / 2 / 3 [預設 3]" "Enter 1 / 2 / 3 [default 3]: "
read -r choice || choice=3
case "$choice" in
1) MODULE="wiki" ;;
2) MODULE="sdd" ;;
*) MODULE="all" ;;
esac
else
# 非互動環境(如 curl | bash 無 tty)→ 預設全裝
MODULE="all"
fi
fi
WANT_WIKI=false
WANT_SDD=false
case "$MODULE" in
wiki) WANT_WIKI=true ;;
sdd) WANT_SDD=true ;;
all) WANT_WIKI=true; WANT_SDD=true ;;
esac
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://raw.githubusercontent.com/uncle6me-web/system-dev-template/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/ 那套搬動,
# 否則會破壞 vault 結構、讓筆記變不可讀。
# 偵測結果寫進 CLAUDE.md,讓 CC 和未來的 Cowork skill 都知道
# 「該讀/該整理哪裡」而不是亂動。
# 必須在建立 CLAUDE.md 之前跑完。
VAULT_TYPE=""
RAW_SOURCE=""
IS_VAULT="no" # 只有 logseq/obsidian 這種「筆記軟體 vault」才算 yes
if [ -d "logseq" ]; then
VAULT_TYPE="logseq"
RAW_SOURCE="pages/, journals/"
IS_VAULT="yes"
elif [ -d ".obsidian" ]; then
VAULT_TYPE="obsidian"
RAW_SOURCE="$(tn './ (整個 vault 根目錄的 .md' './ (all .md under the vault root)')"
IS_VAULT="yes"
else
VAULT_TYPE="docs"
RAW_SOURCE="docs/"
fi
# 偵測到是筆記 vault → 出聲告訴使用者「我看到了,會小心、不破壞你的筆記結構」。
# 不是筆記(一般開發案等)→ 不囉嗦,默默把 docs/ 當原始文件夾安裝完成。
if [ "$IS_VAULT" = "yes" ]; then
t "🗂️ 偵測到 ${VAULT_TYPE} 筆記庫 → 原始文件:${RAW_SOURCE}" \
"🗂️ Detected a ${VAULT_TYPE} note vault → raw source: ${RAW_SOURCE}"
t " (會保留你筆記軟體的目錄/檔名結構,不搬動、不改名)" \
" (your note app's directory/file structure is preserved — nothing is moved or renamed)"
echo ""
fi
# 把「raw source 宣告區塊」吐出來,給新建的 CLAUDE.md append 或
# 給已存在的 CLAUDE.md 當手動補貼的提示。內容對 CC / Cowork 都是
# 機器可讀的指令(明確路徑 + 不可破壞 vault 結構的約束)。
# 寫進 CLAUDE.md 的 raw source 宣告區塊。給人也給 AI 看:
# 依 locale 只寫「一種語言」進 CLAUDE.md(雙語會讓每個 session 的 context 更滿)。
emit_raw_source_block() {
local source_kind
if [ "$IS_ZH" = "yes" ]; then
if [ "$IS_VAULT" = "yes" ]; then source_kind="${VAULT_TYPE} 筆記庫"
else source_kind="一般專案(原始文件放 raw source 路徑)"; fi
cat <<BLOCK
---
## 原始文件空間(raw source
> 安裝時偵測到的來源型態:**${source_kind}**
> CC 與 Cowork 整理/讀取「人寫的原始文件」時,**只在這裡找、只在這裡動**。
| 項目 | 值 |
|------|----|
| 來源型態 | \`${source_kind}\` |
| raw source | \`${RAW_SOURCE}\` |
**約束(CC 與 Cowork 都必須遵守)**
- 整理 wiki/知識時,原始文件**一律從上方 raw source 路徑讀取**,不要假設是 \`docs/\`。
BLOCK
if [ "$IS_VAULT" = "yes" ]; then
cat <<BLOCK
- 這是 **${VAULT_TYPE} 筆記庫**:保留它原本的目錄與檔名慣例,**不得搬動、改名、重新分類** \`.md\` 檔,
以免破壞筆記軟體結構造成筆記不可讀。整理只在 \`system-dev/wiki/\` 產出,**不動 raw source 本身**。
BLOCK
fi
else
if [ "$IS_VAULT" = "yes" ]; then source_kind="${VAULT_TYPE} note vault"
else source_kind="regular project (raw source lives at the path below)"; fi
cat <<BLOCK
---
## Raw source space
> Source type detected at install time: **${source_kind}**
> When CC and Cowork curate/read human-written raw source, **look only here and act only here**.
| Item | Value |
|------|-------|
| Source type | \`${source_kind}\` |
| raw source | \`${RAW_SOURCE}\` |
**Constraints (both CC and Cowork must obey)**
- When curating the wiki/knowledge, **always read raw source from the path above** — don't assume \`docs/\`.
BLOCK
if [ "$IS_VAULT" = "yes" ]; then
cat <<BLOCK
- This is a **${VAULT_TYPE} note vault**: keep its original directory and file-naming conventions. **Do not move, rename, or re-classify** \`.md\` files,
or you'll break the note-app structure and make notes unreadable. Curation output goes only into \`system-dev/wiki/\`; **never touch the raw source itself**.
BLOCK
fi
fi
}
# ── 工具函式 ──────────────────────────────────────
create_dir() {
if [ ! -d "$1" ]; then
mkdir -p "$1"
CREATED+=("$1/")
else
SKIPPED+=("$1/ $(tn '(已存在)' '(already exists)')")
fi
}
download_if_missing() {
local dest="$1" src="$2"
if [ ! -f "$dest" ]; then
mkdir -p "$(dirname "$dest")"
curl -sSL "$src" -o "$dest"
CREATED+=("$dest")
else
SKIPPED+=("$dest $(tn '(已存在,跳過)' '(already exists, skipped)')")
fi
}
# ── 共用結構 ──────────────────────────────────────
# 工具自己的文件骨架收進 system-dev/docs/(不污染用戶根目錄、不跟用戶自己的 docs/ 混)。
# 注意語義分離:這裡的 system-dev/docs/ 是「工具文件」;用戶的 raw source(原始文件)
# 另有其處(見上方 vault 偵測),工具只讀、不搬。
# .claude/ 只留 CC 死綁的 commands/ + hooks/,工具資料一律不放這。
create_dir "system-dev/docs/1-vision"
create_dir "system-dev/docs/2-architecture/decisions"
create_dir "system-dev/docs/4-guides"
create_dir "system-dev/docs/5-records/incidents"
create_dir "system-dev/docs/5-records/test-reports"
create_dir "system-dev/docs/6-user"
create_dir ".claude/commands"
create_dir ".claude/hooks"
download_if_missing "system-dev/docs/README.md" "$REPO_URL/system-dev/docs/README.md"
# 工具版號:放 system-dev/,不寄生 .claude/。
download_if_missing "system-dev/VERSION" "$REPO_URL/system-dev/VERSION"
# ── WIKI 模組 ─────────────────────────────────────
# wiki 是工具資料 → 放 system-dev/wiki/(不放 .claude/)。
# commands/ 與 hooks/ 是 CC 機制檔 → 維持 .claude/。
if $WANT_WIKI; then
create_dir "system-dev/wiki"
download_if_missing "system-dev/wiki/INDEX.md" "$REPO_URL/system-dev/wiki/INDEX.md"
download_if_missing "system-dev/wiki/TAXONOMY.md" "$REPO_URL/system-dev/wiki/TAXONOMY.md"
download_if_missing "system-dev/wiki/status.md" "$REPO_URL/system-dev/wiki/status.md"
download_if_missing "system-dev/wiki/mistakes.md" "$REPO_URL/system-dev/wiki/mistakes.md"
download_if_missing "system-dev/wiki/principles.md" "$REPO_URL/system-dev/wiki/principles.md"
download_if_missing "system-dev/wiki/.wikiignore" "$REPO_URL/system-dev/wiki/.wikiignore"
# wiki 改寫產物(AI 自讀定稿卡片)的正式落點:由工具建好,不靠用戶自救。
create_dir "system-dev/wiki/cards"
[ -f "system-dev/wiki/cards/.gitkeep" ] || { : > "system-dev/wiki/cards/.gitkeep"; CREATED+=("system-dev/wiki/cards/.gitkeep"); }
download_if_missing ".claude/commands/wiki-init.md" "$REPO_URL/.claude/commands/wiki-init.md"
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"
# wiki 相關 hooks:接關 + 機敏掃描
download_if_missing ".claude/hooks/session-start-recall.sh" "$REPO_URL/.claude/hooks/session-start-recall.sh"
download_if_missing ".claude/hooks/wiki-secret-scan.sh" "$REPO_URL/.claude/hooks/wiki-secret-scan.sh"
# Coworkclaude.ai)整理 wiki 用的 skill:與 CC 的 /wiki-init 共用同一套規則
# (含 typed-edge、frontmatter 標籤、gloss)。沒這支 → claude.ai 來掃時身上沒規則。
download_if_missing "system-dev/docs/SKILL.md" "$REPO_URL/system-dev/docs/SKILL.md"
fi
# ── SDD 模組 ──────────────────────────────────────
if $WANT_SDD; then
create_dir "system-dev/docs/3-specs"
download_if_missing "system-dev/docs/3-specs/TEMPLATE-sdd/design.md" "$REPO_URL/system-dev/docs/3-specs/TEMPLATE-sdd/design.md"
download_if_missing "system-dev/docs/3-specs/TEMPLATE-sdd/tasks.md" "$REPO_URL/system-dev/docs/3-specs/TEMPLATE-sdd/tasks.md"
download_if_missing "system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md" "$REPO_URL/system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md"
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"
fi
# ── 安裝/更新腳本:一開始就放進 system-dev/scripts/ ──
# 為什麼一開始就裝:之後要更新,用戶(或 CC)直接 `bash system-dev/scripts/update.sh`
# 不必每次都記那串 curl。腳本來源在 main/scripts/(不在 template/)。
create_dir "system-dev/scripts"
download_if_missing "system-dev/scripts/install.sh" "$SCRIPTS_URL/install.sh"
download_if_missing "system-dev/scripts/update.sh" "$SCRIPTS_URL/update.sh"
# ── 共用 hook:專案自訂禁令骨架(預設停用)────────
download_if_missing ".claude/hooks/pre-write-guard.sh" "$REPO_URL/.claude/hooks/pre-write-guard.sh"
# ── 共用指引:GitHub issue 處理(讀/回普世,跨 repo 發要先問,禁自動輪詢)──
download_if_missing ".claude/commands/issue-handle.md" "$REPO_URL/.claude/commands/issue-handle.md"
chmod +x .claude/hooks/*.sh 2>/dev/null || true
# ── 依模組產生 settings.json 的 hooks 區塊 ────────
# settings.json 因模組而異,不能直接下載單一靜態檔,改條件組裝。
build_hooks_json() {
local session_hooks="" pretool_hooks=""
if $WANT_WIKI; then
session_hooks='{ "type": "command", "command": ".claude/hooks/session-start-recall.sh" }'
fi
# PreToolUse 依模組疊加
local pt=()
$WANT_SDD && pt+=('{ "type": "command", "command": ".claude/hooks/sdd-guard.sh" }')
pt+=('{ "type": "command", "command": ".claude/hooks/pre-write-guard.sh" }')
$WANT_WIKI && pt+=('{ "type": "command", "command": ".claude/hooks/wiki-secret-scan.sh" }')
local IFS=,
pretool_hooks="${pt[*]}"
printf '{\n "hooks": {\n'
if [ -n "$session_hooks" ]; then
printf ' "SessionStart": [\n { "matcher": "startup|resume|clear",\n "hooks": [ %s ] }\n ],\n' "$session_hooks"
fi
printf ' "PreToolUse": [\n { "matcher": "Write|Edit",\n "hooks": [ %s ] }\n ]\n' "$pretool_hooks"
printf ' }\n}\n'
}
if [ ! -f ".claude/settings.json" ]; then
build_hooks_json > .claude/settings.json
CREATED+=(".claude/settings.json $(tn "(依 $MODULE 模組產生)" "(generated for module: $MODULE)")")
else
SKIPPED+=(".claude/settings.json $(tn '(已存在,請手動合併 hooks)' '(already exists — merge hooks manually)')")
fi
# ── CLAUDE.md:只在完全不存在時建立 ────────────────
# 新建時把偵測到的 raw source 宣告 append 進去(在建立的當下寫入,
# 不回頭改使用者既有的 CLAUDE.md,維持「已有不覆蓋」原則)。
if [ ! -f "CLAUDE.md" ]; then
download_if_missing "CLAUDE.md" "$REPO_URL/CLAUDE.md"
if [ -f "CLAUDE.md" ]; then
emit_raw_source_block >> CLAUDE.md
CREATED+=("CLAUDE.md $(tn "← 已寫入 raw source 宣告(${VAULT_TYPE}" "← raw source declaration written (${VAULT_TYPE})")")
fi
else
SKIPPED+=("CLAUDE.md $(tn '(已存在,請手動加入對應區塊)' '(already exists — add the block manually)')")
fi
# ── 輸出結果 ──────────────────────────────────────
echo ""
t "✅ 建立了:" "✅ Created:"
# 注意:macOS bash 3.2 在 set -u 下展開「空陣列」會炸 unbound variable
# 所以這裡先確認有元素才展開(SKIPPED 區塊在下方本來就有守,CREATED 補上)。
if [ ${#CREATED[@]} -gt 0 ]; then
for item in "${CREATED[@]}"; do echo " + $item"; done
fi
if [ ${#SKIPPED[@]} -gt 0 ]; then
echo ""
t "⚠️ 跳過(已存在):" "⚠️ Skipped (already exists):"
for item in "${SKIPPED[@]}"; do echo " - $item"; done
fi
echo ""
echo "─────────────────────────────────"
# CLAUDE.md 已存在 → 依模組提醒手動加區塊
if [ -f "CLAUDE.md" ]; then
if ! grep -q "raw source" CLAUDE.md; then
echo ""
t "📌 CLAUDE.md 已存在但缺少 raw source 宣告。" \
"📌 CLAUDE.md exists but lacks a raw source declaration."
t " 請手動把以下區塊貼進去,讓 CC 與 Cowork 知道原始文件在哪、不要亂動既有結構:" \
" Paste the block below in so CC and Cowork know where the raw source is and won't disturb your structure:"
emit_raw_source_block | sed 's/^/ /'
fi
if $WANT_WIKI && ! grep -q "wiki/status.md" CLAUDE.md; then
echo ""
t "📌 CLAUDE.md 已存在但缺少 wiki 讀取順序,請手動加入:" \
"📌 CLAUDE.md exists but lacks the wiki reading order — please add it manually:"
echo ""
if [ "$IS_ZH" = "yes" ]; then
cat <<'SNIP'
## Wiki 讀取順序(pushhook 開 session 自動注入)
| 檔案 | 時機 | 用途 |
|------|------|------|
| `system-dev/wiki/status.md` | session 開始第一件事 | 當前進度 |
| `system-dev/wiki/principles.md` | 設計任何東西前 | 跨全局原則,必服從 |
| `system-dev/wiki/mistakes.md` | 做新功能前 | 已知踩坑 |
SNIP
else
cat <<'SNIP'
## Wiki reading order (push: auto-injected at session start)
| File | When | Purpose |
|------|------|---------|
| `system-dev/wiki/status.md` | first thing at session start | current progress |
| `system-dev/wiki/principles.md` | before designing anything | global principles, must obey |
| `system-dev/wiki/mistakes.md` | before building a new feature | known pitfalls |
SNIP
fi
fi
if $WANT_SDD && ! grep -q "system-dev/docs/3-specs" CLAUDE.md; then
echo ""
t "📌 CLAUDE.md 已存在但缺少 SDD 鐵律,請手動加入:" \
"📌 CLAUDE.md exists but lacks the SDD iron rule — please add it manually:"
echo ""
if [ "$IS_ZH" = "yes" ]; then
cat <<'SNIP'
## 絕對鐵律
1. 任何 code 變動前必須有對應 SDDsystem-dev/docs/3-specs/[子系統]/design.md
找不到 → 停手問負責人,不要自行建立。
SNIP
else
cat <<'SNIP'
## Iron rule
1. Every code change must have a matching SDD (system-dev/docs/3-specs/[subsystem]/design.md).
Not found → stop and ask the owner; do not create one on your own.
SNIP
fi
fi
fi
# settings.json 已存在 → 依模組提醒要合併哪些 hook
if [ -f ".claude/settings.json" ]; then
MISSING_HOOKS=()
$WANT_WIKI && ! grep -q "session-start-recall.sh" .claude/settings.json && MISSING_HOOKS+=("SessionStart: session-start-recall.sh")
$WANT_WIKI && ! grep -q "wiki-secret-scan.sh" .claude/settings.json && MISSING_HOOKS+=("PreToolUse(Write|Edit): wiki-secret-scan.sh")
$WANT_SDD && ! grep -q "sdd-guard.sh" .claude/settings.json && MISSING_HOOKS+=("PreToolUse(Write|Edit): sdd-guard.sh")
if [ ${#MISSING_HOOKS[@]} -gt 0 ]; then
echo ""
t "📌 .claude/settings.json 已存在,請手動把以下 hooks 合併進去(保留既有設定):" \
"📌 .claude/settings.json exists — merge the hooks below in manually (keep your existing settings):"
for h in "${MISSING_HOOKS[@]}"; do echo "$h"; done
fi
fi
# pre-write-guard 是空殼,提醒它預設不攔(避免「以為有保護其實沒有」的安全錯覺)
echo ""
t "️ .claude/hooks/pre-write-guard.sh 是「按需手填的空插槽」,預設不攔任何東西。" \
"️ .claude/hooks/pre-write-guard.sh is an empty slot to fill on demand — by default it blocks nothing."
t " 需要專案禁令?最簡單是叫你的 CC 寫一支貼合的 guard hook(比範本表達力強);" \
" Need project-specific bans? Easiest is to ask your CC to write a tailored guard hook (more expressive than the template);"
t " 或自己填 FORBIDDEN_PATTERNS 並到 settings.json 掛上才會生效。" \
" or fill in FORBIDDEN_PATTERNS yourself and wire it into settings.json to take effect."
echo ""
t "🚀 下一步:" "🚀 Next steps:"
if $WANT_WIKI; then
t " 在 Claude Code 對話裡執行 /wiki-init" \
" In a Claude Code conversation, run /wiki-init"
t " CC 會掃描現有文件、套用 .wikiignore、建立 wiki。" \
" CC will scan your existing docs, apply .wikiignore, and build the wiki."
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)"
fi
t " GitHub issueCC 可直接 /issue-handle 讀回自己 repo 的 issue(禁自動輪詢)" \
" GitHub issues: CC can use /issue-handle to read issues from its own repo (no auto-polling)"
echo ""
+332
View File
@@ -0,0 +1,332 @@
#!/bin/bash
# system-dev-template updater
# 已安裝舊版的人,一鍵更新到新版。
#
# 核心安全原則:只覆蓋「模板/邏輯檔」,絕不碰「使用者資料檔」。
# ✅ 可覆蓋:hooks/*.sh、commands/*.md、TEMPLATE-*、wiki/INDEX.md
# ——這些由模板維護,使用者不會手改,新版直接換掉。
# 🔒 絕不碰:wiki/status.md、mistakes.md、decisions-summary.md、TAXONOMY.md、.wikiignore、
# settings.json、CLAUDE.md
# ——這些是使用者自己填的內容,覆蓋=清空他的記憶與設定。
#
# 「第一次更新」的雞生蛋問題:
# 舊版本機沒有 update.sh。所以第一次靠 README 那行 curl 從遠端抓這支腳本來跑。
# 跑完它會把自己也更新進 scripts/update.sh,之後就能直接跑本機的 `bash scripts/update.sh`。
set -euo pipefail
# ── i18n:依 locale 選語言,預設英文(curl | bash 常為 LANG=C)──
case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in
zh*|*Hant*|*Hans*) IS_ZH="yes" ;;
*) IS_ZH="no" ;;
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"
TEMPLATE_URL="$REPO_RAW/template"
UPDATED=()
KEPT=()
NEW=()
TEMPLATED=()
MIGRATED=()
COEXIST=()
# ── 版本比對:先看本機 vs 遠端,給使用者「值不值得更新」的判斷 ──
# VERSION 新位置在 system-dev/,舊位置在 .claude/(1.8.x 以前)。優先讀新、回退舊。
LOCAL_VER="$(tn '(未知)' '(unknown)')"
if [ -f "system-dev/VERSION" ]; then
LOCAL_VER="$(tr -d '[:space:]' < system-dev/VERSION)"
elif [ -f ".claude/VERSION" ]; then
LOCAL_VER="$(tr -d '[:space:]' < .claude/VERSION)"
fi
REMOTE_VER="$(curl -sSL "$TEMPLATE_URL/system-dev/VERSION" 2>/dev/null | tr -d '[:space:]' || echo '')"
# 容錯:curl 對 404 會把「404:NotFound」當內容輸出(非空),舊版誤把它寫進 VERSION。
# 這裡驗證必須像版號(X.Y.Z),否則一律視為取不到,避免污染 VERSION 檔。
case "$REMOTE_VER" in
[0-9]*.[0-9]*.[0-9]*) : ;; # 形如 1.9.0 → 合法
*) REMOTE_VER="" ;; # 404 / HTML 錯誤頁 / 其他 → 當作沒抓到
esac
echo ""
echo "🔄 system-dev-template updater"
echo "================================="
t " 本機版本:${LOCAL_VER}" " Local version: ${LOCAL_VER}"
t " 最新版本:${REMOTE_VER:-取不到(檢查網路)}" \
" Latest version: ${REMOTE_VER:-unavailable (check network)}"
echo ""
if [ -z "$REMOTE_VER" ]; then
t "❌ 取不到遠端版本,可能是網路問題。請稍後再試。" \
"❌ Could not fetch the remote version (likely a network issue). Please try again later."
exit 1
fi
if [ "$LOCAL_VER" = "$REMOTE_VER" ]; then
t "✅ 已是最新版(${LOCAL_VER}),不需更新。" \
"✅ Already up to date (${LOCAL_VER}), nothing to update."
t " (仍會同步模板邏輯檔,確保 hooks/commands 與最新一致。)" \
" (Template logic files will still be synced to keep hooks/commands in line with the latest.)"
echo ""
fi
# ── 結構遷移(1.9.0):舊版把 wiki/VERSION 放 .claude/、工具 docs 放根 docs/ ──
# 新版一律收進 system-dev/。這裡冪等遷移:偵測舊位置 → 搬到 system-dev/,已搬過則略過。
# 必須在「模組偵測」之前跑(偵測靠目錄存在與否判斷,搬完才看得到新位置)。
#
# 安全原則:
# - wiki 整包搬(含 cards/ 與可能的 wiki/.git),用 mv 保留內含 .git。
# - docs 只搬「工具自己鋪的白名單」子目錄;用戶自填在 docs/ 的其他內容一律不動。
# - 目的地已存在同名 → 不覆蓋(保留用戶在新位置的東西),略過該項。
migrate_dir() { # $1=舊路徑 $2=新路徑
local from="$1" to="$2"
[ -e "$from" ] || return 0 # 舊的不存在 → 無需遷移
if [ -e "$to" ]; then
# 目的地已存在。兩種可能:
# (a) 已遷移過 → 舊位置不該還在;冪等略過即可。
# (b) 用戶先 install 建了空殼 → 舊位置仍有真資料,現在「並存」。
# 不能靜默跳過 (b),也絕不自動合併(覆蓋風險)。→ 記為「並存待合併」,警告。
COEXIST+=("$from$to")
return 0
fi
mkdir -p "$(dirname "$to")"
if mv "$from" "$to" 2>/dev/null; then
MIGRATED+=("$from$to")
fi
}
# 任一舊位置還在 → 需要遷移(遷移本身冪等:已搬的項目會被 migrate_dir 略過)。
NEEDS_MIGRATE="no"
if [ -d ".claude/wiki" ] || [ -f ".claude/VERSION" ] \
|| [ -d "docs/3-specs" ] || [ -f "docs/SKILL.md" ] || [ -f "docs/README.md" ]; then
NEEDS_MIGRATE="yes"
fi
if [ "$NEEDS_MIGRATE" = "yes" ]; then
t "🔧 偵測到舊版結構,遷移到 system-dev/ …" "🔧 Old layout detected — migrating into system-dev/ …"
mkdir -p system-dev
# wiki(含 cards/ 與內含的 .git)整包搬
migrate_dir ".claude/wiki" "system-dev/wiki"
# 工具版號
migrate_dir ".claude/VERSION" "system-dev/VERSION"
# 工具文件白名單(只搬工具鋪的,用戶自填的 docs 內容不動)
migrate_dir "docs/SKILL.md" "system-dev/docs/SKILL.md"
migrate_dir "docs/README.md" "system-dev/docs/README.md"
migrate_dir "docs/1-vision" "system-dev/docs/1-vision"
migrate_dir "docs/2-architecture" "system-dev/docs/2-architecture"
migrate_dir "docs/3-specs" "system-dev/docs/3-specs"
migrate_dir "docs/4-guides" "system-dev/docs/4-guides"
migrate_dir "docs/5-records" "system-dev/docs/5-records"
migrate_dir "docs/6-user" "system-dev/docs/6-user"
echo ""
fi
# ── 工具函式 ───────────────────────────────────────
# 覆蓋更新:模板/邏輯檔,無條件抓最新版蓋掉。
update_file() {
local dest="$1" src="$2"
mkdir -p "$(dirname "$dest")"
if [ -f "$dest" ]; then
if curl -sSL "$src" -o "$dest.tmp" 2>/dev/null && [ -s "$dest.tmp" ]; then
if cmp -s "$dest" "$dest.tmp"; then
rm -f "$dest.tmp" # 內容相同,不算更新
else
mv "$dest.tmp" "$dest"
UPDATED+=("$dest")
fi
else
rm -f "$dest.tmp"
t " ⚠️ 抓取失敗,保留原檔:$dest" " ⚠️ Download failed, keeping the original: $dest"
fi
else
if curl -sSL "$src" -o "$dest" 2>/dev/null && [ -s "$dest" ]; then
NEW+=("$dest") # 新功能:舊版沒有的檔
else
rm -f "$dest"
t " ⚠️ 抓取失敗:$dest" " ⚠️ Download failed: $dest"
fi
fi
}
# 保留:使用者資料檔,只記錄「有保留」,永遠不動。
keep_file() {
[ -f "$1" ] && KEPT+=("$1") || true
}
# 補新檔:舊版沒有、新版才有的「使用者資料檔」(如 principles.md)。
# 不存在 → 抓範本下來(之後由使用者/CC 填);已存在 → 當用戶資料保留,絕不覆蓋。
add_if_missing() {
local dest="$1" src="$2"
if [ -f "$dest" ]; then
KEPT+=("$dest")
elif curl -sSL "$src" -o "$dest" 2>/dev/null && [ -s "$dest" ]; then
NEW+=("$dest")
else
rm -f "$dest"
fi
}
# 客製檔:使用者一定會手填內容(如 pre-write-guard.sh)。
# - 已存在 → 絕不覆蓋,但把最新模板版抓到 <檔名>.template.sh 旁邊,供使用者自行 diff 採納。
# - 不存在 → 視同新檔,直接抓本體(第一次安裝才會走這條)。
keep_with_template() {
local dest="$1" src="$2"
if [ -f "$dest" ]; then
KEPT+=("$dest")
local tmpl="${dest%.sh}.template.sh"
if curl -sSL "$src" -o "$tmpl.tmp" 2>/dev/null && [ -s "$tmpl.tmp" ]; then
if [ -f "$tmpl" ] && cmp -s "$tmpl" "$tmpl.tmp"; then
rm -f "$tmpl.tmp" # 模板版沒變,不重複提示
else
mv "$tmpl.tmp" "$tmpl"
TEMPLATED+=("$tmpl")
fi
else
rm -f "$tmpl.tmp"
fi
else
update_file "$dest" "$src" # 還沒裝過 → 當新檔處理
fi
}
# ── 偵測已安裝哪些模組(依現有檔案判斷,更新只動已裝的)──
# 遷移已在上面跑完,這裡看新位置 system-dev/。
HAS_WIKI=false
HAS_SDD=false
[ -d "system-dev/wiki" ] && HAS_WIKI=true
if [ -f ".claude/hooks/sdd-guard.sh" ] || [ -d "system-dev/docs/3-specs/TEMPLATE-sdd" ]; then HAS_SDD=true; fi
t "📦 偵測到已安裝模組:" "📦 Detected installed modules:"
$HAS_WIKI && echo " • LLM Wiki"
$HAS_SDD && echo " • SDD"
{ $HAS_WIKI || $HAS_SDD; } || \
t " (未偵測到任何模組——這裡可能還沒安裝,請改跑 install.sh" \
" (No modules detected — nothing installed here yet; run install.sh instead.)"
echo ""
# ── 客製檔:使用者手填的 guardrail,永不覆蓋(issue #3)──
# pre-write-guard.sh 的定位是「空白客製模板,使用者沒配置前不提供保護」(CHANGELOG 1.2.0)。
# 下游通常已塞滿自己的 enforcement,直接覆蓋=無聲關掉整套 guardrail。
# 改為:保留原檔不動,新版範本另存 pre-write-guard.template.sh,由使用者自行 diff 採納。
keep_with_template ".claude/hooks/pre-write-guard.sh" "$TEMPLATE_URL/.claude/hooks/pre-write-guard.sh"
# ── 模板/邏輯檔:覆蓋更新 ──────────────────────────
# 共用 hook 與指引
update_file ".claude/commands/issue-handle.md" "$TEMPLATE_URL/.claude/commands/issue-handle.md"
update_file "system-dev/VERSION" "$TEMPLATE_URL/system-dev/VERSION"
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"
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"
# Coworkclaude.ai)的 wiki 整理 skill:規則檔,可覆蓋
update_file "system-dev/docs/SKILL.md" "$TEMPLATE_URL/system-dev/docs/SKILL.md"
# wiki 的「使用者資料」:絕不碰
keep_file "system-dev/wiki/status.md"
keep_file "system-dev/wiki/mistakes.md"
# principles.md1.10):舊版沒有 → 補範本;已有 → 當用戶資料保留
add_if_missing "system-dev/wiki/principles.md" "$TEMPLATE_URL/system-dev/wiki/principles.md"
keep_file "system-dev/wiki/decisions-summary.md"
keep_file "system-dev/wiki/TAXONOMY.md"
keep_file "system-dev/wiki/.wikiignore"
fi
if $HAS_SDD; then
# SDD 範本與 hook:可覆蓋
update_file "system-dev/docs/3-specs/TEMPLATE-sdd/design.md" "$TEMPLATE_URL/system-dev/docs/3-specs/TEMPLATE-sdd/design.md"
update_file "system-dev/docs/3-specs/TEMPLATE-sdd/tasks.md" "$TEMPLATE_URL/system-dev/docs/3-specs/TEMPLATE-sdd/tasks.md"
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"
fi
# ── 自我更新:把最新的 update.sh / install.sh 抓到 system-dev/scripts/ ──
# 這兩支在 main/scripts/ 下(不在 template/);落地位置新版收進 system-dev/scripts/。
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
# ── 使用者資料檔:絕不碰,但提醒「設定可能有新欄位要手動補」──
keep_file ".claude/settings.json"
keep_file "CLAUDE.md"
# ── 結果輸出 ───────────────────────────────────────
echo ""
echo "─────────────────────────────────"
if [ ${#MIGRATED[@]} -gt 0 ]; then
echo ""
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):"
for f in "${NEW[@]}"; do echo " + $f"; done
fi
if [ ${#UPDATED[@]} -gt 0 ]; then
echo ""
t "⬆️ 已更新(覆蓋成新版):" "⬆️ Updated (overwritten with the new version):"
for f in "${UPDATED[@]}"; do echo " ~ $f"; done
fi
if [ ${#NEW[@]} -eq 0 ] && [ ${#UPDATED[@]} -eq 0 ]; then
echo ""
t "✨ 模板邏輯檔已全部最新,無需變動。" \
"✨ All template logic files are already up to date — no changes needed."
fi
if [ ${#KEPT[@]} -gt 0 ]; then
echo ""
t "🔒 完整保留(你的內容/設定,從未碰過):" \
"🔒 Fully preserved (your content/settings, never touched):"
for f in "${KEPT[@]}"; do echo " = $f"; done
fi
if [ ${#TEMPLATED[@]} -gt 0 ]; then
echo ""
t "📋 客製檔有新版範本(你的原檔沒動,新版另存旁邊,請自行 diff 採納):" \
"📋 Custom files have a new template version (your original is untouched; the new one is saved alongside — diff and adopt as you like):"
for f in "${TEMPLATED[@]}"; do
echo "$f"
t " 比對:diff \"${f%.template.sh}.sh\" \"$f\"" \
" compare: diff \"${f%.template.sh}.sh\" \"$f\""
done
fi
# ── settings.json 提醒:新模組 hook 可能要手動補 ──
if [ -f ".claude/settings.json" ]; then
MISSING=()
$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")
if [ ${#MISSING[@]} -gt 0 ]; then
echo ""
t "📌 settings.json 是你的設定(沒動),但偵測到缺以下 hook,請手動補上:" \
"📌 settings.json is yours (untouched), but these hooks are missing — please add them manually:"
for h in "${MISSING[@]}"; do echo "$h"; done
fi
fi
echo ""
t "🚀 更新完成:${LOCAL_VER}${REMOTE_VER}" "🚀 Update complete: ${LOCAL_VER}${REMOTE_VER}"
t " 下次更新直接跑:bash system-dev/scripts/update.sh" " Next time, just run: bash system-dev/scripts/update.sh"
t " 改了什麼看:CHANGELOG.md" " See what changed: CHANGELOG.md"
echo ""
+36
View File
@@ -0,0 +1,36 @@
# .wikiignore — 不想被編入 wiki 的內容(像 .gitignore
#
# 三層防護的 L1(檔案層)。CC 在 /wiki-init、/wiki-capture 掃描文件時,
# 命中這裡 pattern 的「整個檔案」不讀、不編入 wiki。
#
# 語法:一行一個 glob pattern,相對專案根目錄。# 開頭是註解。
#
# ── 同場另兩層 ──────────────────────────────────────
# L2 行內標記:檔案要編入,但某段不要 → 在該段前後包:
# <!-- wiki:ignore -->
# 這幾行不會被編入 wiki
# <!-- wiki:end -->
# L3 機械底線:萬一機敏值仍被寫進 .claude/wiki/wiki-secret-scan.sh 會 exit 2 擋下。
#
# 三層的分工:L1 整檔排除(你主動列)|L2 局部遮蔽(你標記)|L3 兜底攔截(自動掃)。
# ────────────────────────────────────────────────────
# ── 機敏檔案(預設就該排除)──────────────────────
.env
.env.*
*.pem
*.key
*.p12
*.pfx
*credentials*
*secret*
**/secrets/**
# ── 個資 / 客戶資料(依專案調整)────────────────
# customers/**
# *personal-data*
# ── 草稿 / 暫存(不值得進記憶)──────────────────
# **/draft/**
# *.tmp.md
# SCRATCH.md
+62
View File
@@ -0,0 +1,62 @@
# system-dev/wiki/ — LLM 記憶系統
> 新 session 開始時從這裡導航。
> 目的:讓 CC 不需要重新學習已知的事。
> 維護者:CC(人不手動編輯這裡)
---
## push 檔(session 開始由 hook 主動注入,CC 行動前必看見)
| 檔案 | 注入形態 | 內容 |
|------|---------|------|
| `status.md` | 全文 | 當前進度、下一步(時態狀態)|
| `principles.md` | 全文(一行一條)| 跨全局設計原則,行動前必服從 |
| `mistakes.md` | 標題+一行症狀,全文按需展開 | 踩過的坑、被糾正的誤解(防不自覺盲區)|
> 為什麼這三個 push 而非 pull:它們是「CC 不會主動查、但不看就出事」的盲區。詳見 `/wiki-init` 的「push vs pull」。
---
## pullcards/CC 按需檢索)
一切知識內容——原文摘要、AI 筆記、決策、概念知識——都寫成 `cards/<bucket>/` 的概念原子卡。
`decisions-summary.md` 已降級為 cards(決策=知識內容);既有的保留為相容。
---
## 維護規則
1. 只增不刪——記錄 append,內容改了加新條目說明「舊的已更新」
2. status.md 每次 session 結束更新;mistakes/principles 一發現就 append
3. principles 一行一條、≤15 條(超過代表該合併或下放成 card)
4. **新增一個檢索角度 = 在下方「多角度視圖」加一節,不開新實體檔、不問用戶**
---
## 多角度視圖(由 /wiki-init、/wiki-capture 填入)
INDEX 是**所有檢索角度的入口**,不只標籤。原文是唯讀 SSoT,wiki 是改寫過的記憶。
新增角度只要在這裡加一節(如「決策角度」「原則角度」),指向對應 cards 或 push 檔——**不必新增實體特殊檔**。
### 標籤角度(按 `TAXONOMY.md` 的軸聚類,指向桶子索引)
#### 掛載架構
- [[ingest/00-INDEX]] — ingest 餵食器(5 卡)→ [[掛載架構]]、[[跨repo織網]]
#### 契約邊界
- [[envelope-契約]] — ingest→graph 唯一耦合面
#### 萃取策略
- [[採取優先於萃取]] — 路徑 A 採取 vs 路徑 B 萃取
- [[extract-模型策略]] — 模型選擇、品質門檻、升級閘
### 決策角度(取代舊 decisions-summary.md 的視圖)
- [[envelope-契約]] — 向量化分工升格成契約:ingest 打標、base 讀標執行(leo 2026-06-26
- [[採取優先於萃取]] — 採取>萃取:gloss 由 local CC 在生產當下建,不留給 ingest 補(leo 2026-06-26
- 純餵食器五鐵律見 `principles.md`leo 2026-06-26 拍板)
> 結構:INDEX(多角度入口)→ `cards/<bucket>/00-INDEX.md`(桶子索引,固定名)→ 概念原子卡。
> 指 `00-INDEX` **一律帶路徑** `[[bucket/00-INDEX]]`(固定名跨桶撞名);卡片間用裸 `[[卡名]]`。
> 分類由卡片 frontmatter `tags:` 承載,標籤字典見 `TAXONOMY.md`。詳見 `/wiki-init` 規範。
+46
View File
@@ -0,0 +1,46 @@
# TAXONOMY.md — 標籤字典(本 repo 專屬)
> wiki 卡片的 frontmatter `tags:` **只能用這裡列出的標籤**。
> 這不是凍結——字典**可以擴充**,只是**禁止繞過字典在卡片裡直接冒新標籤**。
> 維護者:CC(由 /wiki-init 初始化、隨 wiki 演進受控擴充)。
>
> **遇到現有軸都裝不下的內容時,照這個流程(先查、後擴、登記)**:
> 1. **先查既有**:現有標籤真的都不合,還是只是同義詞?(`知識管理` vs `KM` vs `筆記管理` 是同一軸,別重造)
> 2. **確實是新軸** → 把新標籤加進本檔(附一句定義),再用。不必停下來問人,但「先登記再使用」這道查核不能省。
> 3. 自由增生才是要防的——同義標籤散開會讓同類卡片分散、下游聚類失準。受控擴充(先查重、再登記)不會。
>
> **字典是每個 repo 各自的**:跨 repo 引擎靠各 repo 自己一致的 taxonomy 接合,不是逼所有 repo 共用一份。
> 知識型 vault 的領域軸(知識管理/學習認知)和開發 repo(子系統/基礎設施)本來就該不同。
---
## 分類採雙軸(一張卡可多重歸屬)
分類由 **frontmatter `tags:`** 承載,不靠資料夾、不靠行內 `#tag`
一張卡同時掛「領域 1-3 個 + 形態 0-2 個」,可從任一軸過濾。
### 領域(主軸,每卡 1-3 個)
> kbdb-ingest-plugin(餵食器)的子系統軸。/wiki-init 2026-06-26 依本 repo 性質定。
- `ingest管線` — 採取/萃取/匯總/輸出的資料流本身
- `契約邊界` — ingest↔graph 的 envelope 契約、職責切割
- `萃取策略` — extract 路徑(模型選擇、品質門檻、升級閘)
- `掛載架構` — base→graph→ingest 堆疊、API 邊界、三守則
- `部署` — wrangler 部署、繞 Actions、觸發機制
### 形態(副軸,每卡 0-2 個)
- `架構設計` — 結構/邊界/資料流的設計判斷
- `鐵律約束` — 不可違反的硬規則(與 principles.md 呼應)
- `模型策略` — LLM 萃取的模型/tier 選擇
- `契約規格` — 逐字依凍結契約的形式規格
---
## 規則
1. **先查重、再登記、才使用**:禁止繞過字典在卡片直接冒新標籤;新標籤先確認非現有同義詞,加進本檔(附定義)再用。
2. **領域 vs 形態分開**:領域是「講什麼主題」,形態是「以什麼形式呈現」,不要混。
3. **頂層 INDEX.md 的標籤視圖依本字典的軸聚類**——字典改了,INDEX 視圖跟著更新。
4. **新增領域軸要慎**:領域是檢索骨架,動它影響全庫聚類;形態軸(呈現形式)擴充較安全。不確定就先用現有最接近的,並在卡片或本檔註記「待人類複核此分類」。
View File
+17
View File
@@ -0,0 +1,17 @@
# ingest 餵食器
> 桶子索引(容器:只連不重寫)。kbdb-ingest-plugin 的核心知識卡。
> 跨專案藍圖:InkStoneCo `docs/3-specs/mira-dissolve/`。SDD`docs/3-specs/ingest-pipeline/`
## 架構與定位
- [[掛載架構]] — base→graph→ingest 三層堆疊、API 邊界、無環
- [[採取優先於萃取]] — 路徑 A(採取本地三元組)vs 路徑 B(裸原文 extract
## 契約
- [[envelope-契約]] — ingest→graph 唯一耦合面:送什麼、禁送什麼、向量化打標分工
## 萃取
- [[extract-模型策略]] — 用戶選意圖、品質門檻白名單、Haiku/Claude 分層、升級閘
## 主職
- [[跨repo織網]] — 匯總多 repo 三元組,織單一 repo 看不到的橋與異見
@@ -0,0 +1,43 @@
---
tags: [契約邊界, 契約規格, 架構設計]
gloss: ingest→graph 的唯一耦合面——一個來源檔一次萃取產出的候選 envelope,規定 ingest 能送什麼、禁送什麼。
---
# envelope 契約
← [[ingest/00-INDEX]]
**來源**`contracts/ingest-candidate.json`(凍結形式契約,逐字依原文)、`docs/3-specs/ingest-pipeline/design.md`
**最後更新**2026-06-26
> 這是**凍結的形式契約**。欄位的精確 schema 以 `contracts/ingest-candidate.json` 為唯一真相,**逐字依原文**;本卡只距離化「設計意圖與職責切割」,不取代 schema。
## 摘要
`IngestCandidateEnvelope` 是 ingest 與 graph 之間唯一的耦合面:一個 envelope =一個 canonical MD 一次萃取的候選產物。
## 重點
- **頂層三必填**`source`(來自哪個檔 + 快照鍵)、`extractor`(哪個模型/tier 萃的)、`triplets`(邊,至少一條)。另有選填 `nodes[]`(節點層附帶資訊)。`additionalProperties: false` 全程鎖死。
- **source = 指回原文的指標 + append-only 快照鍵**`uri``github:<owner>/<repo>@<path>`+ `content_hash`。同一 uri 後續 envelope **取代**前一批(latest-wins),不疊加;同 hash → graph no-op 跳過。
- **取代語意歸 graphingest 一無所知**:同 uri 出新 hash 時,graph 把舊 active 實例 PATCH 成 deprecated、append 新批 active(可查/rollback/清)。ingest 只管送新批。
- **向量化分工(leo 2026-06-26 升格成契約)**ingest 在 `nodes[].embed` / `triplets[].predicate_embed` **打標**「要不要向量化、embed 什麼(名+gloss / 謂詞裸詞)」;base/KBDB embed 模組**讀標執行**實際 embedding。**ingest 自己不算向量**(呼應 [[掛載架構]]「准運算不准碰儲存」、principles「不算向量 normalize」)。
- **ingest 能送**:原始 s/p/o + `source.*` + `extractor.*` + `nodes[].gloss/aliases/entity_type/embed` + `confidence` + 謂詞向量打標。
- **ingest 禁送(graph 領域,絕不可送)**`id`record id/ `clusters` / `bridge_score` / `created_at` / `updated_at` / triplet 上的 `subject_entity_type|object_entity_type`(類型只走 `nodes[]`)。結構符號(`>>`/`←`)與給人讀的散文(`## 摘要`)也不進 envelope。
- 這是**【輸入候選】不是【已存三元組】**(後者見 graph 的 triplet.json)。
## 實體
- **IngestCandidateEnvelope**envelope/凍結契約)— ingest→graph 唯一耦合面,一個來源檔一次萃取的候選產物。
- **向量化打標**embed/predicate_embed 標記)— ingest 標哪些 node/謂詞要 embed、embed 什麼,自己不執行 embedding。
- **讀標執行**base embed 模組)— base/KBDB 讀 ingest 的標去做實際 embedding 的分工角色。
- **content_hash**(快照鍵)— 來源檔內容 hash,graph 據此判 no-op 或寫新快照。
- **禁送欄位**graph 領域)— id/clusters/bridge_score/時間戳/邊上 type,由 graph 算、ingest 絕不送。
## 關聯
### 內文知識關係(端點=上方 `## 實體` 正規名)
- 向量化打標 >> 配對分工 >> 讀標執行
- IngestCandidateEnvelope >> 禁止攜帶 >> 禁送欄位
- content_hash >> 充當 >> IngestCandidateEnvelope
### 卡片關係(卡對卡)
- [[envelope-契約]] >> 約束 >> [[掛載架構]]
- [[envelope-契約]] >> 承載產出於 >> [[extract-模型策略]]
@@ -0,0 +1,41 @@
---
tags: [萃取策略, 模型策略]
gloss: ingest 走路徑 B 萃裸原文時的模型選擇與品質把關策略——用戶選意圖、品質門檻白名單、淺萃預設 Haiku、深萃 Claude、JSON-fail 升級閘。
---
# extract 模型策略
← [[ingest/00-INDEX]]
**來源**`docs/3-specs/ingest-pipeline/{requirements,design}.md``CLAUDE.md``contracts/ingest-candidate.json`extractor.tier
**最後更新**2026-06-26
> 跨專案完整版在 InkStoneCo `docs/3-specs/mira-dissolve/design.md §6.6`。本卡距離 ingest 內部要點。
## 摘要
只在路徑 B(裸原文萃取)才用。模型由用戶選「意圖」非「型號」,有品質門檻白名單把關,預設淺萃、必要時升級深萃。
## 重點
- **用戶選意圖非型號**:像 `/model` 那樣選意圖(要快/要深),不是選具體型號。
- **品質門檻白名單**:模型要入白名單須過門檻——萃得出「深層暗示」+「中文過關」。難度隨來源分層:AI 報告弱模型夠、人類 vault(口語、暗示多)須好模型。
- **兩 tier**(對應 envelope `extractor.tier`):`shallow` = Workers AI 淺萃(預設 Haiku,走 API 便宜);`deep` = Claude 深萃 via CC(走月費不走 API)。
- **JSON-fail 升級閘**:淺萃 JSON 解析失敗 / 萃出過稀 → 自動升級到 deep 重萃。
- **第一版不 embed**embed 等 base vectorize 就緒(InkStoneCo T2.4);ingest 本來就不算向量(見 [[envelope-契約]] 打標分工),第一版連打標執行端都還沒接,先跑萃取本身。
- 模型測試集(中文 + 人類暗示樣本、轉回歸測試)為 deferred(SDD T3.3),先跑預設。
## 實體
- **品質門檻白名單** — 模型須萃得出深層暗示+中文過關才入選的把關機制。
- **淺萃**shallow tierWorkers AI/預設 Haiku)— 便宜快速的預設萃取層,走 API。
- **深萃**deep tierClaude via CC)— 較貴較準的萃取層,淺萃失敗時升級,走月費。
- **JSON-fail 升級閘** — 淺萃解析失敗或過稀時自動升 deep 的閘門。
- **選意圖非型號** — 用戶像 /model 那樣選萃取意圖、而非指定具體模型的選擇方式。
## 關聯
### 內文知識關係(端點=上方 `## 實體` 正規名)
- JSON-fail 升級閘 >> 觸發升級到 >> 深萃
- 淺萃 >> 升級為 >> 深萃
- 品質門檻白名單 >> 把關 >> 選意圖非型號
### 卡片關係(卡對卡)
- [[extract-模型策略]] >> 屬於路徑B於 >> [[採取優先於萃取]]
- [[extract-模型策略]] >> 產物送進 >> [[envelope-契約]]
@@ -0,0 +1,41 @@
---
tags: [掛載架構, 架構設計]
gloss: kbdb-ingest-plugin 在 KBDB 堆疊裡的位置——掛在 graph 上、graph 掛在 base 上的最薄餵食器層。
---
# 掛載架構
← [[ingest/00-INDEX]]
**來源**`CLAUDE.md``README.md``docs/3-specs/ingest-pipeline/design.md`
**最後更新**2026-06-26
## 摘要
KBDB 是三層堆疊:base 管儲存、graph 管三元組領域與查詢、ingest 是最薄的餵食器,只透過 API 往上一層 POST 候選。
## 重點
- 三層由下而上:`base KBDBarcrun/kbdb` 管儲存(D1 三表 + CRUD API)→ `kbdb-graph-plugin` 管三元組領域 + 圖查詢,並開 `POST /triplets/ingest` 寫入端 → `kbdb-ingest-plugin`(本 repo)純餵食器:拉 + 採取/萃取 + 跨庫織網。
- 每層只透過上一層的 **HTTP API** 互動,不碰其內部結構。ingest 只打 graph 的寫入端,從不直連 base。
- ingest 內部資料流:SourceAdapterGitHub 拉 + content-hash`source.uri = github:owner/repo@path`)→ 採取或 extract → 跨 repo 匯總織網 → POST envelope。
- **無環**graph 永不回呼 ingest。觸發只能從下游發起方向之外(webhook/排程,或 KBDB MCP `refresh` 代轉),下游圖層不主動驅動上游餵食器。
- 命名只養一個品牌 Arcrun,本元件功能名當水管:`ingest`
## 實體
> 本卡關鍵實體(graph node)。
- **kbdb-ingest-plugin**(本 repo/餵食器)— KBDB 堆疊最薄一層,純 POST 候選 envelope 給 graph、不碰儲存。
- **kbdb-graph-plugin**(graph 層)— 三元組領域與圖查詢層,對 ingest 開 `POST /triplets/ingest` 寫入端。
- **base KBDB**arcrun/kbdb/基本盤)— 最底儲存層,D1 三表 + CRUD APIingest 與 graph 都不繞過它直碰表。
- **SourceAdapter** — ingest 內部從 GitHub 拉 repo 並算 content-hash 的元件。
## 關聯
### 內文知識關係(端點=上方 `## 實體` 正規名)
- kbdb-ingest-plugin >> 掛載於 >> kbdb-graph-plugin
- kbdb-graph-plugin >> 掛載於 >> base KBDB
- kbdb-ingest-plugin >> POST候選給 >> kbdb-graph-plugin
- SourceAdapter >> 隸屬於 >> kbdb-ingest-plugin
### 卡片關係(卡對卡)
- [[掛載架構]] >> 受約束於 >> [[envelope-契約]]
- [[掛載架構]] >> 展開為 >> [[採取優先於萃取]]
@@ -0,0 +1,39 @@
---
tags: [ingest管線, 萃取策略, 架構設計]
gloss: ingest 取得三元組的兩條路徑——優先「採取」本地 CC 已建好的三元組,無則才自己「萃取」裸原文。
---
# 採取優先於萃取
← [[ingest/00-INDEX]]
**來源**`CLAUDE.md``README.md``docs/3-specs/ingest-pipeline/{requirements,design}.md`
**最後更新**2026-06-26
## 摘要
ingest 拿三元組有兩條路:路徑 A 採取(拉本地已建好的)優先,路徑 B 萃取(自己萃裸原文)是 fallback。
## 重點
- **路徑 A 採取(優先)**repo 用了 system-dev-template、本地 CC 已建好三元組 + gloss → ingest 直接拉取,不重萃。
- **路徑 B 萃取(fallback**:裸原文、本地沒三元組 → ingest 自己 extract 出候選 (s,p,o) + gloss。
- **為什麼採取優先**:知識連結長在「生產的當下」(local CC 有 LLM Wiki 指引、有完整脈絡),本地萃成效更好;下游 ingest 只有單檔/跨庫視角,臨時補的 gloss 會貼不合(=胡扯)。所以 gloss 與三元組同階段由 local CC 建,不留給 ingest 補。
- 兩路共同收斂到:跨 repo 匯總 → 織跨庫關聯 → POST envelope。**跨庫織網才是 ingest 不可取代的主職**(見 [[跨repo織網]]),採取/萃取只是取得素材的前段。
- extract 的模型與品質細節見 [[extract-模型策略]]。
## 實體
- **採取**(路徑 A/拉)— ingest 直接拉本地 CC 已建好三元組+gloss、不重萃的取得方式。
- **萃取**(路徑 Bextractfallback)— 本地無三元組時,ingest 自己從裸原文萃出候選 (s,p,o)+gloss。
- **gloss** — 一句話描述某 node 是什麼,供下游 base embed normalize 同義詞用。
- **system-dev-template**(精耕模板)— 讓 local CC 在知識生產當下建好三元組+gloss 的模板,決定一個 repo 走採取或萃取。
## 關聯
### 內文知識關係(端點=上方 `## 實體` 正規名)
- 採取 >> 優先於 >> 萃取
- 採取 >> 依賴於 >> system-dev-template
- gloss >> 產出於 >> 採取
- 萃取 >> 退而求其次補 >> gloss
### 卡片關係(卡對卡)
- [[採取優先於萃取]] >> 細節見 >> [[extract-模型策略]]
- [[採取優先於萃取]] >> 服務於 >> [[跨repo織網]]
@@ -0,0 +1,37 @@
---
tags: [ingest管線, 掛載架構, 觀點主張]
gloss: ingest 的主職與護城河——匯總多 repo 三元組、織出單一 repo 本地看不到的跨庫橋與異見。
---
# 跨 repo 織網
← [[ingest/00-INDEX]]
**來源**`CLAUDE.md``README.md``docs/3-specs/ingest-pipeline/requirements.md`R4
**最後更新**2026-06-26
## 摘要
採取/萃取只是取素材;ingest 真正不可取代的價值,是匯總多個 repo 的三元組、織出單一 repo 在本地看不到的跨庫關聯。
## 重點
- **為什麼本地做不到**:單一 repo 的 local CC 只看得到自己庫內的知識;跨庫的「橋」(A 庫概念連到 B 庫概念)和「異見」(兩庫對同一概念說法相左)唯有把多 repo 三元組匯到一處才浮現。這是 ingest 的主職,不是採取/萃取的附屬。
- **命題(護城河)**:不是「更好的 notes RAG」(紅海),而是**跨所有 vault 的圖**——Obsidian/Logseq 結構上做不到的那層。護城河是累積的圖,不是這份程式碼。
- 織網發生在採取/萃取**之後**、POST envelope **之前**的匯總階段(見 [[掛載架構]] 資料流)。
- 與「無環」「純 API」並存:織網是 ingest 端的運算(准),結果仍只透過 [[envelope-契約]] POST 給 graph,不碰儲存。
## 實體
- **跨repo織網**(跨庫織網/主職)— 匯總多 repo 三元組、織出跨庫橋與異見的 ingest 核心職責。
- **跨庫橋**bridge)— 一個 repo 概念連到另一 repo 概念的跨庫關聯,單庫本地看不到。
- **跨庫異見** — 兩個庫對同一概念說法相左之處,匯總後才浮現。
- **跨所有 vault 的圖**(護城河)— ingest 的命題:累積的跨庫圖,而非程式碼本身,才是護城河。
## 關聯
### 內文知識關係(端點=上方 `## 實體` 正規名)
- 跨repo織網 >> 浮現出 >> 跨庫橋
- 跨repo織網 >> 浮現出 >> 跨庫異見
- 跨repo織網 >> 構成 >> 跨所有 vault 的圖
### 卡片關係(卡對卡)
- [[跨repo織網]] >> 接續於 >> [[採取優先於萃取]]
- [[跨repo織網]] >> 輸出走 >> [[envelope-契約]]
+28
View File
@@ -0,0 +1,28 @@
# CC 已知誤解 + 避坑方法
> 做新功能前讀一遍。
> 格式:每條必須有症狀 + 正確做法 + 原因。
---
## 快速檢查清單(做任何事前)
- [ ] 有對應 SDD 嗎?沒有 → 停手
- [ ] 這次修改會影響哪些模組?有沒有連帶破壞?
- [ ] 驗收標準是什麼?有客觀證據嗎?
---
## 誤解記錄
⚠️ MISTAKE: 功能 PR 混進 template 1.9.x 知識庫遷移(system-dev/ + .claude/
症狀: 開功能分支時順手把 wiki-init / template 遷移的 system-dev/、.claude/ 一起 commit 進同一筆,PR 同時含「功能改動」+「基建遷移」。
正確做法: 功能與基建分兩個 PR。功能 PR 只留 src/tests/contracts/config/SDD-taskstemplate 遷移(system-dev/ + .claude/ + SDD 位置搬移)獨立成筆、本 repo 自行 merge。從乾淨分支重建:reset 回 main → 只 stage 核心 → force-with-lease 蓋功能 PR;遷移另開分支。
原因: ① 淹沒真正改動、審查難 ② 多 repo 同跑 1.9.x 遷移會撞衝突(graph 撞過)③ 可能夾私稿。system-dev/ 該進 git,但走自己一筆、不混功能、別 gitignore 它。
日期: 2026-06-26
⚠️ MISTAKE: 抓到跨 repo 契約漂移時自己拍板對齊方向
症狀: 發現 graph 部署端 .strict() Zod 比凍結 contract 窄(會 422 新欄位)→ 想直接決定 ingest 該送哪個版本。
正確做法: 契約是雙邊事 → 先查總管在相關 issue 的裁定(graph#1 已裁「contract 是 SSoT、實作追它、graph receiver 補對齊、ingest 不退」),照裁定做、別自決。跨 repo 修正要先問人。
原因: contract 是凍結單一真相源(三守則);單邊擅自退讓或改 contract 會破壞雙邊共識。見 [[envelope-契約]]。
日期: 2026-06-26
+20
View File
@@ -0,0 +1,20 @@
# principles — 跨全局設計原則(push:CC 行動前必服從)
> 這個檔由 hook 在 session 開始**全文注入**,讓 CC 設計任何東西前都先看見這些準繩。
> 為什麼 push 而非寫成 card:原則是「會被遺忘的盲區」——沒推到眼前,CC 設計時很可能沒想到要服從就做了。
>
> 規則:**一行一條**,精煉成準繩(不是長篇論述)。≤15 條;超過代表某些該合併、或下放成 card。
> 發現新的跨全局原則 → append 一行。累積原則只改這個檔,**不必問用戶開新檔**。
> 區分:原則 = 反覆適用的準繩(這裡);單次選擇 = 決策(寫成 card);踩過的坑 = mistakes.md。
---
## 原則
- **純餵食器,不碰儲存**ingest 只 POST 候選 envelope 給 graph 寫入 API;不直連 base、不碰 D1/Vectorize/任何表。牆是「儲存」不是「運算」(准 LLM 萃取、不准碰儲存)。
- **不算向量 normalize**ingest 只「打標」哪些 node/謂詞要 embed,實際 embedding 歸 base embed 模組「讀標執行」(單檔視角算不準跨庫同義)。
- **不帶查詢 MCP**ingest 是 ambient 餵食器,沒人互動「問」它;只帶薄 ops CLI(人手動觸發重萃),重萃觸發由 KBDB MCP `refresh` 代轉。
- **三守則(無環/純 API/凍結契約)**graph 永不回呼 ingest;只走 graph HTTP 寫入端不碰其內部;`contracts/ingest-candidate.json` 是唯一耦合面。
- **部署繞 GitHub Actions**:走 wrangler 部署(被 flag 教訓);runtime 用 GitHub API/webhook 拉 repo 是 runtime 行為、不衝突。
- **採取優先於萃取**:用了 system-dev-template 的 repo 有本地已建三元組+gloss → 直接拉取,不重萃(本地生產當下建的連結品質更好)。
- **不污染用戶根目錄**:工具產物收進專屬資料夾,不在用戶根目錄撒檔。
+32
View File
@@ -0,0 +1,32 @@
# 當前狀態
> 更新時間:2026-06-26
> 每次 session 結束必須更新此檔。
---
## 正在做
T0.5T5 ingest 純餵食器管線**實作完成**issue #2,已 close)。程式碼在兩個待 merge 的 PR:
- **PR #3**(核心管線):`src/**`index/source-adapter/harvest/extract/graph-client/weave/pipeline/endpoint-check/envelope/types+ `tests/**`28 passed+ contract + CLI + config。mergeable/CLEAN,待總管 merge。
- **PR #4**template 1.9.x 遷移,本分支):`system-dev/**` + `.claude/**` + SDD 搬 `docs/3-specs/``system-dev/docs/3-specs/`。本 repo 自行 merge。
gate 全綠:vitest 28 / tsc clean / wrangler dry-run 只 env-var 綁定 / 零直連 base·SQL·migration。
## 下次 session 第一件事
1. **merge 順序**:先請總管 merge PR #3(核心),再 rebase + merge PR #4(遷移)——#4 對 tasks.md 做位置搬移,#3 在原位更新它,先後 merge 免衝突。
2. merge 後進**部署待驗**:部署 ingest Workerwrangler,繞 Actions+ 設 `GRAPH_BASE_URL` → 跑端到端 `GitHub→ingest→graph`(需 leo21c)。
## 待負責人確認
- PR #3 merge(總管)|PR #4 merge(本 repo / leo)。
## 已知問題
| 問題 | 優先級 | 狀態 |
|------|--------|------|
| 端到端 ingest→graph 未實證 | 🟡 | 待部署 + `GRAPH_BASE_URL`graph receiver 已補對齊 full contract |
| refresh 端 extractWorkers AI)未接 | ⚪ | 第一版只走採取(路徑 A);深萃留 CLI/CC |
| embed 實際 embedding | ⚪ | 只打標;等 base vectorizeArcrun #7 |
| T3.3 模型測試集(中文+暗示樣本) | ⚪ | deferred;護欄 + parse 已單元測試 |