diff --git a/.claude/commands/issue-handle.md b/.claude/commands/issue-handle.md new file mode 100644 index 0000000..8df58d3 --- /dev/null +++ b/.claude/commands/issue-handle.md @@ -0,0 +1,61 @@ +--- +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 # 讀完整內容 +# …實作… +gh issue comment --body "做了什麼、怎麼決定的、改了哪些檔" +gh issue close # 確認解決後結案 +``` + +回覆要有料:說清楚**做了什麼、為什麼這樣決定、動了哪些檔**,而不是只回「done」。 +issue 作者(可能是另一個 repo 的 CC,或人類)要靠你的回覆判斷對不對。 + +--- + +## 2. 發 issue 給「別的 repo」(要先問人 — 不可擅自) + +當你發現**別的 repo** 有值得修正的地方時: + +> ❌ 不要擅自 `gh issue create -R other/repo …` +> ✅ 先問人類:「我發現 X repo 有 Y 問題,要我幫你去那邊發 issue 嗎?」得到同意才發。 + +理由:通用 template 不知道使用者對那個 repo 有沒有權限、想不想發。留一道人類確認最安全。 +(對**自己 repo** 開 issue 記待辦則可直接做——那是自己的 repo。) + +--- + +## 3. flag 安全界線(最重要 — 絕不可越) + +**「有事才讀」,禁止自動輪詢。** + +- ✅ 想看 issue → 當下主動 `gh issue list`。 +- 🚫 **禁止**掛 GitHub Actions / cron / webhook 去**自動輪詢** issue。 + +為什麼這條是硬底線:自動輪詢 + 事件 fan-out 正是會觸發 GitHub 異常偵測、 +害你的 token 被 rate limit 砍掉的流量模式。template 給很多人用,這條界線必須守住, +保護使用者不踩雷。需要「定期檢查」就由人類主動跑這個指令,不要自動化。 + +--- + +## (可選)用 label 分來源 + +若想用同一套流程同時管「內部交辦」與「外部回報」,可建兩個 label: +`internal`(協作/交辦)、`user`(外部使用者回報),靠 label 分流。 +這對通用 template 不預設——你的 repo 需要再建。 diff --git a/.claude/commands/sdd-check.md b/.claude/commands/sdd-check.md index 54c97d2..fb4bfde 100644 --- a/.claude/commands/sdd-check.md +++ b/.claude/commands/sdd-check.md @@ -15,7 +15,7 @@ ### 第二步:尋找對應 SDD -在 `docs/3-specs/` 下尋找對應的子系統目錄,確認有沒有: +在 `system-dev/docs/3-specs/` 下尋找對應的子系統目錄,確認有沒有: - `design.md`(設計文件) - `tasks.md`(任務清單) @@ -23,7 +23,7 @@ **情況 A:找到對應 SDD** ``` -✅ 找到 SDD:docs/3-specs/[子系統]/ +✅ 找到 SDD:system-dev/docs/3-specs/[子系統]/ 📋 design.md:[確認] 📋 tasks.md:[確認,列出相關 task] 🎯 對應 task:[編號和描述] @@ -34,7 +34,7 @@ ``` ⚠️ 找不到對應 SDD 任務:[描述] -建議在 docs/3-specs/[建議子系統名]/ 建立 SDD +建議在 system-dev/docs/3-specs/[建議子系統名]/ 建立 SDD 要我幫你起草 design.md 嗎?(需要你確認後才動手) ``` diff --git a/.claude/commands/wiki-capture.md b/.claude/commands/wiki-capture.md index 5b00230..d10e13b 100644 --- a/.claude/commands/wiki-capture.md +++ b/.claude/commands/wiki-capture.md @@ -7,13 +7,21 @@ ## 執行流程 +### 第零步:機敏檢查(寫入前一律先過) + +把任何內容寫進 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` + `docs/2-architecture/decisions/` | +| 架構決策 | 「為什麼選A不選B」「我們決定用X」 | `decisions-summary.md` + `system-dev/docs/2-architecture/decisions/` | | CC 的誤解被糾正 | CC 說了某件事,使用者說「不是,是...」 | `mistakes.md` | | 重要狀態更新 | 完成了某件事、阻擋了某件事 | `status.md` | | 技術發現 | 踩到坑、找到解法、重要行為確認 | `mistakes.md` 或對應 SDD | @@ -51,10 +59,10 @@ ## [主題] — [YYYY-MM-DD] **結論**:[一句話] **原因**:[簡短說明] -**詳細**:docs/2-architecture/decisions/[檔名] +**詳細**:system-dev/docs/2-architecture/decisions/[檔名] ``` -重大決策同時在 `docs/2-architecture/decisions/` 建立 ADR 檔案。 +重大決策同時在 `system-dev/docs/2-architecture/decisions/` 建立 ADR 檔案。 ### 第四步:確認 diff --git a/.claude/commands/wiki-init.md b/.claude/commands/wiki-init.md index 4cb7bfd..2fcca5e 100644 --- a/.claude/commands/wiki-init.md +++ b/.claude/commands/wiki-init.md @@ -1,7 +1,25 @@ # /wiki-init — 初始化或接入 LLM Wiki 系統 初始化這個專案的 LLM Wiki 記憶系統。 -新專案建立空白結構,已有專案掃描現有文件並建立 wiki。 +新專案建立空白結構,已有專案掃描現有文件並**改寫**成 wiki。 + +--- + +## 核心概念:wiki 是 AI 改寫過的記憶,不是原文索引 + +記憶系統的目的,是讓 AI **之後讀得快**。但人類寫的原始文件——不管是 vault 的隨手記、開發專案的會議記錄、規格草稿、散落的 `.md`——天生是亂的:重複、流水帳、半成品、口語。 + +如果 wiki 只是一份 `[[原文檔名]]` 指回原文的**索引**,那每次未來要用都得重新解析那團亂,等於沒省到。**wiki 的價值在於「改寫一次,之後每次讀都便宜」**。 + +所以原則對**所有專案**一致(不分 vault 或一般開發): + +> 人類寫的原文是 **SSoT**(真理來源,永遠唯讀)。 +> 但實際要長期保存、被 AI 反覆讀的是 **AI 改寫整理過的 wiki**。 +> **AI 是總編輯**——把原文改寫成自包含、概念原子化、互相連結、適於 AI 讀的知識條目。 + +唯一例外:原文是**不可改動的正式文件**(簽署過的規格、法規、合約),必須逐字讀原文——這種才在 wiki 裡用指針指回去,並註明「逐字依原文」。除此之外,一律改寫。 + +**raw source 永遠唯讀**:所有產出只往 `system-dev/wiki/` 寫,絕不改動、搬移、重新命名原文。 --- @@ -10,45 +28,93 @@ ### 第一步:偵測專案狀態 檢查以下項目,判斷是新專案還是已有專案: -- 根目錄有沒有 `.claude/wiki/` -- 根目錄有沒有 `docs/` +- 根目錄有沒有 `system-dev/wiki/` +- 根目錄有沒有 `docs/`(或 vault 的 `pages/`、`journals/`、根目錄 `.md`) - 有沒有散落的 `.md` 檔案 +同時**偵測 raw source 路徑**(同 install.sh 邏輯): +- 根目錄有 `logseq/` → Logseq vault,raw source = `pages/` + `journals/` +- 根目錄有 `.obsidian/` → Obsidian vault,raw source = 根目錄所有 `.md` +- 都沒有 → 一般專案,raw source = `docs/` 下所有 `.md`(及散落的 `.md`) + **新專案**(幾乎空的)→ 直接建立結構,跳到第三步 **已有專案**(有文件)→ 執行第二步 ### 第二步:已有專案的掃描(已有專案才執行) -1. 遞迴找出所有 `.md` 檔案 -2. 對每個檔案標注建議位置和信心度 -3. 列出清單給使用者確認,**停下來等確認** +1. 遞迴找出 raw source 裡所有 `.md` 檔案 +2. **先套用 `system-dev/wiki/.wikiignore`**:命中 pattern 的檔案整個排除,不讀不編入。 + - 若 `.wikiignore` 不存在,從範本建立一份(預設排除 `.env`/`*.pem`/`*secret*` 等) + - 被排除的檔案在清單裡標「🚫 .wikiignore 排除」,**不可被覆蓋** +3. 對其餘檔案標注**改寫計畫**:會萃取成哪些 wiki 條目。一份原文可能拆成多個概念原子條目,多份相關原文也可能合併成一條。 +4. 列出清單給使用者確認,**停下來等確認** -分類規則: -``` -有明確子系統 + 設計內容 → docs/3-specs/[子系統]/ -解釋為什麼做某個決定 → docs/2-architecture/decisions/ -說明怎麼操作 → docs/4-guides/ -記錄發生過的事 → docs/5-records/ -給外部使用者看的 → docs/6-user/ -不確定 → 列為「待確認」,問使用者 -``` +> **量大時建議用 Haiku 改寫**:逐份原文「改寫成 wiki 格式」是重複、機械、判斷成本低的工作——正適合 Haiku。原文數量多(如數十、上百份)時,主動建議: +> 「共 N 份原文要改寫,這類逐份萃取很適合用 Haiku 並行處理(便宜、夠快)。要我派 Haiku subagent 改寫嗎?」 +> 得同意後,用 Task / subagent 把每份原文(或每批)丟給 Haiku 改寫,主模型只負責切分概念、定條目邊界、最後審稿與互連。 + +> 機敏防護(三層): +> - **L1 .wikiignore**:整檔排除(這一步) +> - **L2 行內標記**:檔案要編入但某段不要 → 遇到 `` … `` 之間的內容**略過**,只留「(此處機敏,已略過)」 +> - **L3 hook**:萬一機敏值仍被寫進 wiki,`wiki-secret-scan.sh` 會 exit 2 擋下 +> 編入任何檔案前,先檢查是否含密碼/金鑰/個資——有就改記「位置」而非「值」。 ### 第三步:建立缺少的結構 -只建立不存在的目錄和檔案,**已有的一律不動**: +只建立不存在的目錄和檔案,**已有的一律不動**。 + +wiki 採**三層 + 標籤橫切**架構(183 卡實證,issue #8): -目錄: ``` -docs/{1-vision,2-architecture/decisions,3-specs,4-guides,5-records/{incidents,test-reports},6-user} -.claude/wiki/ +system-dev/wiki/ +├── INDEX.md ← 索引:多角度視圖的家(標籤角度、決策角度、…) +├── TAXONOMY.md ← 標籤字典(cards 的分類元資料,受控擴充) +├── status.md ← [push] 時態狀態:當前進度、下一步 +├── mistakes.md ← [push] 踩過的坑、被糾正的誤解(防不自覺盲區) +├── principles.md ← [push] 跨全局的設計原則(行動前必服從) +└── cards/ ← [pull] 一切知識內容:原文摘要、AI 筆記、決策、概念… + └── / ← 儲存桶(分類由 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//` 一律先讀它,不必猜。檔內 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/`。) + 檔案(不存在才建): -- `.claude/wiki/INDEX.md` -- `.claude/wiki/status.md` -- `.claude/wiki/mistakes.md` -- `.claude/wiki/decisions-summary.md` -- `docs/README.md` +- `system-dev/wiki/INDEX.md`、`TAXONOMY.md` +- `system-dev/wiki/status.md`、`mistakes.md`、`principles.md`(三個 push 檔) +- `system-dev/docs/README.md`(一般專案才需要) + +--- + +## 核心判準:push vs pull(wiki 是給 AI 看的) + +整理任何內容前,先判斷它該 **push** 還 **pull**——判準是「**CC 做事時會不會被動看見**」: + +- **push**:CC 行動前必須主動出現在 context(session 開始就由 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 | 寫成 cards;CC 面對時自然會查,INDEX 提供角度入口 | + +**principles 維護規則**:一行一條精煉準繩(如「不污染用戶根目錄」「目標用戶 low-code」「wiki 主要給 AI 看」)。發現新的跨全局原則 → append 一行;超過 ~15 條代表某些該合併或下放成 card。**累積原則只改 principles.md,不必問用戶開新檔。** ### 第四步:訪談(每次一個問題) @@ -60,18 +126,93 @@ docs/{1-vision,2-architecture/decisions,3-specs,4-guides,5-records/{incidents,te 把答案填進 `CLAUDE.md`(如果存在)或建立新的。 -### 第五步:已有專案的文件歸檔 +### 第五步:改寫成 wiki(AI 當總編輯) (第二步確認後執行) -按照確認好的分類移動檔案,完成後更新 `CLAUDE.md` 的路徑引用。 +**不搬動原文**。逐份讀 raw source,改寫萃取成 `cards//` 裡的自包含原子卡: -### 第六步:完成報告 +- **概念原子化**:一張卡講一個概念,不是一篇原文對一張卡。原文太雜就拆,多份相關原文就合。 +- **自包含**:讀卡就懂,不必回去翻原文。把口語、重複、流水帳改寫成結構化知識,**不寫「詳見原文」**。 +- **保留來源指針**:每卡標 `**來源**:原文相對路徑`,為可追溯,不是要使用者回去讀。 +- **frontmatter 標籤分類**(見下方):分類走 frontmatter `tags:`,不靠資料夾、不靠行內 `#tag`。 +- **互相連結(typed-edge 三元組)**:`## 關聯` 不只列裸 `[[頁面]]`,改寫成帶語義的三元組(見下方)。 +- **萃 gloss(node 一句說明)**:frontmatter 放 `gloss:` —— 這張卡(= 一個 entity / graph node)的一句話定義,供下游語義 normalize(見下方)。 + +卡片格式(每張卡): +```markdown +--- +tags: [知識管理, AI協作, 方法論] +gloss: 一句話定義這個概念是什麼(給下游語義 normalize 用,選填、deep tier 才產) +--- +# 概念全名 + +← [[/00-INDEX]] + +**來源**:`[raw source 相對路徑]` +**最後更新**:YYYY-MM-DD + +## 摘要 +[一句話核心] + +## 重點 +- [自包含改寫的要點,不依賴原文] + +## 關聯 +- [[本卡]] >> 謂詞(動詞短語) >> [[他卡]] +- [[原子筆記]] >> 是其最小單元 >> [[卡片盒筆記法]] +``` + +**麵包屑用帶路徑 wikilink**(issue #7):H1 次行放 `← [[/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,把「關係」也預編譯,下游 ingest 直接 parse 出帶類型的有向邊): +1. **方向性**:`A >> 謂詞 >> B` 必須讀成「A(謂詞)B」一句通順的話;A、B 順序就是主→賓真實方向。 +2. **謂詞用動詞 / 動詞短語**(反駁、奠基於、是…的實作),動詞天然帶方向。 +3. **謂詞自由書寫,不受控詞彙**:下游對謂詞 embedding 時同義謂詞會自動聚類;但方向仍靠書寫順序保證。 +4. **向後相容**:純 `[[A]]` 仍合法(視為無類型邊),盡量補謂詞。 + +> `>>` 是分隔語法,repo 可自選符號,但全程一致。 + +**萃 gloss 規則**(issue #9,把「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 直接取用、不再回頭補。 + +**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//`,明寫「絕不寫入 pages/journals/docs 原稿」;事後用上面的 `git status` 驗。 +> 2. **檔名 = 卡片全名**,否則 `[[全名]]` 對不到檔。冒號用全形「:」、斜線用全形「/」,**全程一種字元**,避免 `/`、`∕`、`:` 混用斷鏈。 +> 3. **量大用 Haiku 並行改寫**,主模型只切概念邊界+審稿+修跨資料夾斷鏈。 告知: ``` ✅ wiki-init 完成 建立了:[列出新建的目錄和檔案] 跳過了:[列出已有因此不動的] +改寫了:[N 份原文 → M 張原子卡、K 條 typed-edge、M 條 gloss(deep tier)] +原文驗證:pages/ journals/ git status 0 異動 ✅ 下一步:用 /wiki-capture 把重要決策存進 wiki ``` diff --git a/.claude/commands/wiki-recall.md b/.claude/commands/wiki-recall.md new file mode 100644 index 0000000..2159a5e --- /dev/null +++ b/.claude/commands/wiki-recall.md @@ -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,再動手。 diff --git a/.claude/hooks/pre-write-guard.sh b/.claude/hooks/pre-write-guard.sh new file mode 100755 index 0000000..abe5030 --- /dev/null +++ b/.claude/hooks/pre-write-guard.sh @@ -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 加掛這支 +# +# 掛在 PreToolUse(matcher: 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 </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 </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 <&2 +exit 0 diff --git a/.claude/hooks/session-start-recall.sh b/.claude/hooks/session-start-recall.sh new file mode 100755 index 0000000..16df4c0 --- /dev/null +++ b/.claude/hooks/session-start-recall.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# SessionStart hook — 開 session 自動注入 status.md 重點 +# wishlist §1 主路徑:不靠 CC 自覺、不用人說,開 session 就把進度推到眼前。 +# +# 掛在 settings.json 的 SessionStart(matcher: 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/3:principles(全文,行動前必服從)── +# 放最前:原則是「會被遺忘的盲區」,要第一眼看見。全文成本低(一行一條、≤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/3:mistakes(標題清單 + 一行症狀,全文按需展開)── +# 不全文注入(可能累積很多、含長 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 diff --git a/.claude/hooks/wiki-secret-scan.sh b/.claude/hooks/wiki-secret-scan.sh new file mode 100755 index 0000000..91355f0 --- /dev/null +++ b/.claude/hooks/wiki-secret-scan.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# PreToolUse hook — 寫入 wiki 前掃機敏資訊(L3 硬攔截) +# +# 為什麼存在:wiki 的 ignore 規則(.wikiignore + 行內標記)是「協議層」,靠 CC 遵守。 +# 但密碼/金鑰/個資外洩是「不可逆」後果——只靠口頭約束太危險。 +# 這支 hook 是機械式底線:CC 真的把機敏資訊寫進 system-dev/wiki/ 的那一刻 → exit 2 擋下。 +# +# 掛在 settings.json 的 PreToolUse(matcher: 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 用 content;Edit 用 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 (或 ) +# 先把標記過的行抽掉再掃。 +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 < 新 session 開始時從這裡導航。 -> 目的:讓 CC 不需要重新學習已知的事。 -> 維護者:CC(人不手動編輯這裡) - ---- - -## 核心檔案 - -| 檔案 | 何時讀 | 內容 | -|------|-------|------| -| `status.md` | session 開始第一件事 | 當前進度、下一步 | -| `mistakes.md` | 做新功能前 | 已知誤解、快速檢查清單 | -| `decisions-summary.md` | 遇到設計判斷時 | 架構決策摘要 | - ---- - -## 維護規則 - -1. 只增不刪——記錄 append,決策改了加新條目說明「舊決策已更新」 -2. status.md 每次 session 結束更新 -3. mistakes.md 每次被糾正後 append -4. 發現新的重要決策 → 同時更新 decisions-summary.md 和 docs/2-architecture/decisions/ - ---- - -## 快速導航 - -**這個專案是什麼**:KBDB-graph —— KBDB 的 graph 插件(triplet 採集 + graph 查詢),類比 Apache AGE 之於 Postgres。要抽成獨立 repo(leo 產權)。基本盤(block CRUD)在 `arcrun/kbdb`,不在這。 - -**動工前必讀**: -- `docs/HANDOFF-kbdb-plugin.md` —— 本目錄專屬交棒(待辦:確認邊界 / 改名+git init+推 GitHub / 定義掛載介面)。 -- 上游約束見 `CLAUDE.md` 最頂 + `github.com/uncle6me-web/InkStoneCo`。 - -**文件去哪找**: -- SDD(design+tasks)→ `docs/3-specs/`(現有:arcrun-key-auth、blocks-edit-api) -- 歷史記錄 / bug 復盤 → `docs/5-records/`(現有:PATCH 403 bug、upsert feature request) -- 分類規則全表 → `docs/README.md` - -**絕對限制**:本目錄只做 graph 插件 / 萬物皆 Block(禁 CREATE/ALTER TABLE)/ API-as-Wall / 部署繞開 GitHub、禁跨 repo Actions / 樂高法(actions < 100 行)。 diff --git a/system-dev/VERSION b/system-dev/VERSION new file mode 100644 index 0000000..ed21137 --- /dev/null +++ b/system-dev/VERSION @@ -0,0 +1 @@ +1.10.0 \ No newline at end of file diff --git a/system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md b/system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md new file mode 100644 index 0000000..2212924 --- /dev/null +++ b/system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md @@ -0,0 +1,30 @@ +# [主題] — Architecture Decision Record + +> 日期:[YYYY-MM-DD] +> 狀態:[提議中 / 已採納 / 已廢棄] +> 影響範圍:[哪些子系統 / 模組] + +--- + +## 背景 + +[遇到了什麼問題,需要做這個決定?] + +## 決定 + +**[結論,一句話。]** + +## 原因 + +[詳細說明為什麼這樣決定。] + +## 放棄的選項 + +| 選項 | 放棄原因 | +|------|---------| +| [選項 A] | [原因] | +| [選項 B] | [原因] | + +## 影響與後續 + +[這個決定影響哪些地方?有什麼技術債或需要注意的事?] diff --git a/system-dev/docs/3-specs/TEMPLATE-sdd/design.md b/system-dev/docs/3-specs/TEMPLATE-sdd/design.md new file mode 100644 index 0000000..44bbc65 --- /dev/null +++ b/system-dev/docs/3-specs/TEMPLATE-sdd/design.md @@ -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] diff --git a/system-dev/docs/3-specs/TEMPLATE-sdd/tasks.md b/system-dev/docs/3-specs/TEMPLATE-sdd/tasks.md new file mode 100644 index 0000000..96df8c9 --- /dev/null +++ b/system-dev/docs/3-specs/TEMPLATE-sdd/tasks.md @@ -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]` | 完成(有驗收證據)| +| `[~]` | 暫緩(說明原因)| +| `[!]` | 阻擋中(說明阻擋原因)| diff --git a/system-dev/docs/README.md b/system-dev/docs/README.md new file mode 100644 index 0000000..6a37c58 --- /dev/null +++ b/system-dev/docs/README.md @@ -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` | 架構決策摘要 | 重大決策後 | diff --git a/system-dev/docs/SKILL.md b/system-dev/docs/SKILL.md new file mode 100644 index 0000000..4bc3d55 --- /dev/null +++ b/system-dev/docs/SKILL.md @@ -0,0 +1,238 @@ +--- +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 source:pages/、journals/ + → 忽略:logseq/、assets/ + +else if 根目錄有 .obsidian/ 資料夾 + → vault 類型:Obsidian + → raw source:根目錄下所有 .md(排除 .obsidian/ 內的檔案) + +else + → vault 類型:一般專案 + → raw source:docs/ 下所有 .md +``` + +--- + +## 第三步:讀取現有 wiki 狀態 + +進入 `system-dev/wiki/`,讀取: + +- `INDEX.md`:目前已有哪些 wiki 頁面(多角度視圖入口) +- `status.md`:上次整理時間、進度 +- `principles.md`(如果有):本專案跨全局的設計原則——整理時必須服從 + +目的:**知道哪些已整理過,只處理新增或有變動的 raw source**,不重複整理。 + +--- + +## 第四步:整理規則 + +### 核心判準:push vs pull(wiki 是給 AI 看的) + +整理任何內容前,先判斷它該進 **push 檔** 還 **cards(pull)**——判準是「**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//`(pull) | 知識內容;CC 面對時自然會查 | + +> `decisions-summary.md` 已**降級為 cards + INDEX 決策視圖**(決策=知識內容)。既有的保留為相容,不刪。 +> CC 與 Cowork **共用此判準**,產出一致:任一方寫進 push 檔或 cards,另一方看到就跳過或補充,不覆蓋。 + +### 讀 raw source + +逐一讀取 raw source 的 `.md` 檔。跳過: +- 檔名以 `.` 開頭的隱藏檔 +- `.wikiignore` 裡列出的 glob pattern(如果存在) +- 含有 `` 標記的區段 + +### 整理邏輯 + +每個 raw source 檔案,判斷: + +1. **INDEX.md 裡已有對應條目,且 raw source 未修改** → 跳過 +2. **INDEX.md 裡已有條目,但 raw source 有新內容** → 更新對應 wiki 頁面,補充新資訊,不刪舊內容 +3. **INDEX.md 裡沒有對應條目** → 新建 wiki 頁面 + +### Wiki 卡片格式(概念原子卡,存到 `cards//`) + +```markdown +--- +tags: [知識管理, AI協作, 方法論] +gloss: 一句話定義這個概念是什麼(給下游語義 normalize 用,選填、deep tier 才產) +--- +# 概念全名 + +← [[/00-INDEX]] + +**來源**:`[raw source 相對路徑]` +**最後更新**:YYYY-MM-DD + +## 摘要 + +[一句話核心] + +## 重點 + +- [自包含改寫的要點,不寫「詳見原文」] + +## 關聯 + +- [[本卡]] >> 謂詞(動詞短語) >> [[他卡]] +- [[原子筆記]] >> 是其最小單元 >> [[卡片盒筆記法]] +``` + +### 架構:三層 + 標籤橫切(183 卡實證) + +``` +INDEX.md ← 頂層:標籤視圖(非資料夾列表) +TAXONOMY.md ← 標籤字典(受控擴充:先查重再登記) +cards// + ├── 00-INDEX.md ← 桶子索引(固定名,容器:只連不重寫) + └── <概念全名>.md ← 概念原子卡 +``` + +- **資料夾只是儲存桶,分類由 frontmatter `tags:` 承載**——不繼承原稿目錄,由 AI 重新組織。 +- **桶子索引固定名 `00-INDEX.md`**:`00-` 排序最前、一眼可辨,載入任何桶先讀它。 +- **frontmatter `tags:` 而非行內 `#tag`**:內文常用 `#`(如 `#猜想`),行內標籤會讓 ingest 分不清「分類」與「內文範例」污染 graph;frontmatter 零歧義。標籤只能用 `TAXONOMY.md` 列出的;**禁止繞過字典在卡片直接冒新標籤**,但字典可受控擴充(遇新軸先查重、確認非同義詞,再登記進本 repo 的 TAXONOMY.md)。 +- **麵包屑帶路徑**:H1 次行 `← [[/00-INDEX]]`。指 `00-INDEX` 因固定名跨桶撞名,**一律帶路徑**;卡片間連結用裸 `[[卡名]]`。 + +### 使用 typed-edge 三元組(不只裸 `[[wikilink]]`) + +整理時,發現內容與其他頁面有關聯,用**帶語義的三元組**寫進 `## 關聯`,而非只列裸 `[[頁面]]`。裸 `[[A]]` 只說「有關」、沒說關係,下游要建 knowledge graph 還得回讀兩張卡;三元組把關係也預編譯,ingest 直接 parse 出帶類型的有向邊。 + +格式 `A >> 謂詞 >> B`,規則: +1. **方向性**:必須讀成「A(謂詞)B」一句通順的話;A、B 順序=主→賓真實方向。 +2. **謂詞用動詞 / 動詞短語**(反駁、奠基於、是…的實作),天然帶方向。 +3. **謂詞自由書寫**,不受控詞彙;下游對謂詞 embedding 時同義謂詞會自動聚類,但方向仍靠書寫順序保證。 +4. **向後相容**:純 `[[A]]` 仍合法(無類型邊),盡量補謂詞。 + +`>>` 為分隔語法,全程一致即可。這是 Karpathy LLM Wiki「知識互連」的強化版——連結不只存在,還帶類型與方向。 + +### 萃 gloss(node 一句說明,供下游語義 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 是…」);`## 摘要` 是給人讀的核心句。 +- **對齊下游 envelope**:frontmatter `gloss:` 對應 ingest envelope 的 `nodes[].gloss`。 + +> **改寫時必守**:① 絕不寫入 raw source(只往 `cards//` 寫,事後驗 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//00-INDEX.md` 是容器(只連不重寫,H2/H3 分節列出該桶卡片)。 + +### status.md 更新 + +在現有內容**末尾追加**(不覆蓋): + +```markdown +## YYYY-MM-DD HH:MM|cowork-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 的目標) diff --git a/system-dev/scripts/install.sh b/system-dev/scripts/install.sh new file mode 100755 index 0000000..68ba741 --- /dev/null +++ b/system-dev/scripts/install.sh @@ -0,0 +1,453 @@ +#!/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 Wiki(CC 記憶系統 + 機敏防護) + --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 "" + +# ── 偵測 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 < 安裝時偵測到的來源型態:**${source_kind}** +> CC 與 Cowork 整理/讀取「人寫的原始文件」時,**只在這裡找、只在這裡動**。 + +| 項目 | 值 | +|------|----| +| 來源型態 | \`${source_kind}\` | +| raw source | \`${RAW_SOURCE}\` | + +**約束(CC 與 Cowork 都必須遵守)** + +- 整理 wiki/知識時,原始文件**一律從上方 raw source 路徑讀取**,不要假設是 \`docs/\`。 +BLOCK + if [ "$IS_VAULT" = "yes" ]; then + cat < 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 < "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" + + # Cowork(claude.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 讀取順序(push:hook 開 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 變動前必須有對應 SDD(system-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 issue:CC 可直接 /issue-handle 讀回自己 repo 的 issue(禁自動輪詢)" \ + " GitHub issues: CC can use /issue-handle to read issues from its own repo (no auto-polling)" +echo "" diff --git a/system-dev/scripts/update.sh b/system-dev/scripts/update.sh new file mode 100755 index 0000000..90e99d6 --- /dev/null +++ b/system-dev/scripts/update.sh @@ -0,0 +1,314 @@ +#!/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=() + +# ── 版本比對:先看本機 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 + 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" + # Cowork(claude.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.md(1.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 [ ${#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 "" diff --git a/system-dev/wiki/.wikiignore b/system-dev/wiki/.wikiignore new file mode 100644 index 0000000..ba02528 --- /dev/null +++ b/system-dev/wiki/.wikiignore @@ -0,0 +1,36 @@ +# .wikiignore — 不想被編入 wiki 的內容(像 .gitignore) +# +# 三層防護的 L1(檔案層)。CC 在 /wiki-init、/wiki-capture 掃描文件時, +# 命中這裡 pattern 的「整個檔案」不讀、不編入 wiki。 +# +# 語法:一行一個 glob pattern,相對專案根目錄。# 開頭是註解。 +# +# ── 同場另兩層 ────────────────────────────────────── +# L2 行內標記:檔案要編入,但某段不要 → 在該段前後包: +# +# 這幾行不會被編入 wiki +# +# 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 diff --git a/system-dev/wiki/INDEX.md b/system-dev/wiki/INDEX.md new file mode 100644 index 0000000..eb34019 --- /dev/null +++ b/system-dev/wiki/INDEX.md @@ -0,0 +1,81 @@ +# 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」。 + +--- + +## pull:cards/(CC 按需檢索) + +一切知識內容——原文摘要、AI 筆記、決策、概念知識——都寫成 `cards//` 的概念原子卡。 +`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 檔——**不必新增實體特殊檔**。 + +### 快速導航(本專案速覽) + +**這個專案是什麼**: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 +#### 知識管理 +- [[pkm/00-INDEX]] — PKM 知識管理(N 卡) + +#### AI 協作 +- [[ai/00-INDEX]] — AI 協作(M 卡) +``` + +(尚未建 cards,現有決策見下「決策角度」。) + +### 決策角度(取代舊 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。 + +> 結構:INDEX(多角度入口)→ `cards//00-INDEX.md`(桶子索引,固定名)→ 概念原子卡。 +> 指 `00-INDEX` **一律帶路徑** `[[bucket/00-INDEX]]`(固定名跨桶撞名);卡片間用裸 `[[卡名]]`。 +> 分類由卡片 frontmatter `tags:` 承載,標籤字典見 `TAXONOMY.md`。詳見 `/wiki-init` 規範。 diff --git a/system-dev/wiki/TAXONOMY.md b/system-dev/wiki/TAXONOMY.md new file mode 100644 index 0000000..7fb61e1 --- /dev/null +++ b/system-dev/wiki/TAXONOMY.md @@ -0,0 +1,50 @@ +# 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 個) + +> 由 /wiki-init 依專案性質提出。以下為 PKM / 知識型 vault 的實證一組,請按你的專案調整: + +- `知識管理` +- `學習認知` +- `AI協作` +- `生產力` +- `系統設計` +- `工具教學` + +(一般開發專案的領域軸可能是:`子系統A` / `子系統B` / `基礎設施` / `前端` / `後端` …) + +### 形態(副軸,每卡 0-2 個) + +- `方法論` +- `工具實作` +- `觀點主張` +- `架構設計` +- `案例經驗` + +--- + +## 規則 + +1. **先查重、再登記、才使用**:禁止繞過字典在卡片直接冒新標籤;新標籤先確認非現有同義詞,加進本檔(附定義)再用。 +2. **領域 vs 形態分開**:領域是「講什麼主題」,形態是「以什麼形式呈現」,不要混。 +3. **頂層 INDEX.md 的標籤視圖依本字典的軸聚類**——字典改了,INDEX 視圖跟著更新。 +4. **新增領域軸要慎**:領域是檢索骨架,動它影響全庫聚類;形態軸(呈現形式)擴充較安全。不確定就先用現有最接近的,並在卡片或本檔註記「待人類複核此分類」。 diff --git a/system-dev/wiki/cards/.gitkeep b/system-dev/wiki/cards/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.claude/wiki/decisions-summary.md b/system-dev/wiki/decisions-summary.md similarity index 100% rename from .claude/wiki/decisions-summary.md rename to system-dev/wiki/decisions-summary.md diff --git a/.claude/wiki/mistakes.md b/system-dev/wiki/mistakes.md similarity index 100% rename from .claude/wiki/mistakes.md rename to system-dev/wiki/mistakes.md diff --git a/system-dev/wiki/principles.md b/system-dev/wiki/principles.md new file mode 100644 index 0000000..794745f --- /dev/null +++ b/system-dev/wiki/principles.md @@ -0,0 +1,18 @@ +# principles — 跨全局設計原則(push:CC 行動前必服從) + +> 這個檔由 hook 在 session 開始**全文注入**,讓 CC 設計任何東西前都先看見這些準繩。 +> 為什麼 push 而非寫成 card:原則是「會被遺忘的盲區」——沒推到眼前,CC 設計時很可能沒想到要服從就做了。 +> +> 規則:**一行一條**,精煉成準繩(不是長篇論述)。≤15 條;超過代表某些該合併、或下放成 card。 +> 發現新的跨全局原則 → append 一行。累積原則只改這個檔,**不必問用戶開新檔**。 +> 區分:原則 = 反覆適用的準繩(這裡);單次選擇 = 決策(寫成 card);踩過的坑 = mistakes.md。 + +--- + +## 原則 + + + +(尚未填入。由 /wiki-init 或 /wiki-capture 依本專案累積。) diff --git a/.claude/wiki/status.md b/system-dev/wiki/status.md similarity index 100% rename from .claude/wiki/status.md rename to system-dev/wiki/status.md