## 目的 `convert` 後檔案被手動刪除/就地改動時,提供正規的稽核與修復途徑;並修掉一個讓「還原後 re-hash 比對」在 Windows 失效的既有 bug。 ## 變更 - **`convert.py --verify`(純唯讀對帳)**:re-hash 全部登記檔、掃未登記檔,回報 missing / mismatch / unregistered,不一致以退出碼 1 表示(可當 CI/排程閘門)。`converted/assets/*` 與 `.gitkeep` 正確略過。 - **修 CRLF 使 hash 失準**:`write_text` 在 Windows 把 `\n`→`\r\n`,但登記的 SHA-256 算在 `\n` bytes 上 → 磁碟 bytes 與帳本永遠對不上(converted md + web 快照原始檔)。改為 `write_bytes` 寫入所登記的那份 bytes。 - **`.gitattributes`(`raw/** -text`)**:`core.autocrlf=true` 下 checkout 會在 git 層重新引入 CRLF,抵銷上一項修復;關閉 raw 的換行正規化,把「登記 hash == 磁碟 bytes」不變式延伸到 git checkin/checkout。 - **selfcheck**:補 clean / missing / mismatch / unregistered 四情境(全程唯讀斷言;clean 案例含未入帳本的 assets,順帶證明不誤報)。 - **AGENTS.md**:新增 §14「raw 完整性與修復(對帳)」,並於 §1.4 補「登記 hash == 磁碟 bytes」不變式與 `.gitattributes` 機制。 ## 驗證 `.venv/Scripts/python.exe tools/convert/selfcheck.py` → `ALL PASS`。 ## 備註 `--verify` 是唯讀稽核工具,不改任何檔(含 manifest),符合 §7「lint 絕不擅自刪改」精神。修復決策(可衍生 vs 信任根、先 `git restore` 不改帳本、動 manifest 走 PR)詳見 AGENTS.md §14。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: LittleYellow <crazytea@gmail.com> Reviewed-on: #1
348 lines
18 KiB
Markdown
348 lines
18 KiB
Markdown
# AGENTS.md — QA 知識庫 schema 正本
|
||
|
||
> 本檔是本專案**唯一**的 agent 規範正本。`CLAUDE.md` 與 `.github/copilot-instructions.md`
|
||
> 只是指向本檔的薄指標檔,禁止在任何其他檔案複製本檔內容(見 §11 雙棧規則)。
|
||
|
||
## 0. 專案定位
|
||
|
||
為金融業(電子支付)QA 測試部門建置的部門知識庫,採 **Karpathy LLM Wiki 模式**:
|
||
知識在文件**攝入時**由 LLM 編譯成 wiki 頁面(萃取、綜合、交叉引用),查詢時只讀已編譯
|
||
頁面。**這不是 RAG**——第一版不建向量庫、不做 chunk 檢索,檢索用 BM25 / 全文檢索。
|
||
最終透過一層薄的 MCP server(`mcp/server.py`)供團隊成員與內部 AI agents 查詢。
|
||
|
||
核心哲學:**知識編譯一次、持續維護,而非每次查詢重新推導。**
|
||
|
||
## 1. 硬性約束(不可協商,違反即為缺陷)
|
||
|
||
1. **資料落地**:受 FSC 法遵限制,所有文件內容的 LLM 處理一律走本地 Ollama,
|
||
任何情況下不得將文件內容送往雲端 API。格式轉換一律使用本地程式庫,
|
||
禁止任何雲端轉換服務。**禁止使用任何 `:cloud` 後綴的 Ollama model**(在遠端執行,
|
||
內容會離開本機)。
|
||
2. **模型設定外部化**:所有 Ollama 連線資訊與 model tags 一律讀取
|
||
`config/models.yaml`,不得寫死在程式碼中。程式不接受環境變數以外的其他覆寫來源;
|
||
設定缺欄位時報錯,而非使用隱含預設值。
|
||
3. **供應鏈限制**:禁止引入任何中國團隊開發或有中國關聯的模型與框架
|
||
(含 bge、Qwen、GLM、MiniMax、Jina 系列)。未來若需 embedding,預設候選為
|
||
EmbeddingGemma(Google)或 snowflake-arctic-embed(Snowflake)。
|
||
4. **raw 層不可變**:`raw/` 內的文件只讀不改。原始檔與轉換後 Markdown 皆登記
|
||
SHA-256 至 `raw/manifest.json`(結構見 §9),轉換檔條目必須回指原始檔 hash。
|
||
wiki 頁面引用來源必須帶 `source_ref`(格式見 §3.3)。登記的 hash 必須等於檔案在
|
||
磁碟上的實際 bytes(UTF-8,換行不轉換——轉換器一律以 write_bytes 寫入所登記的
|
||
那份 bytes,不受平台 CRLF 影響;git 亦以 `.gitattributes` 對 `raw/**` 關閉換行
|
||
正規化,避免 checkout 重新引入 CRLF);完整性以 §14 `--verify` 稽核。
|
||
5. **HITL 閘門**:所有 wiki 層的變更以 git branch + PR 形式提交,經人工審核後才
|
||
合併至 main。攝入腳本永遠不直接 commit 到 main。lint 絕不擅自刪檔,一律標記
|
||
待人工核准。
|
||
6. **Copilot 成本紀律**:Copilot agents 是薄編排層——文件內容的 LLM 處理一律委派給
|
||
本地 Ollama 腳本(shell out 至 `tools/`),Copilot 只讀取腳本回傳的精簡結果
|
||
(狀態、統計、報告路徑),不將文件全文拉進 context。
|
||
7. **最小詮釋原則**:遇到模糊需求時採取最小合理詮釋,並將所有假設以
|
||
`<!-- ASSUMPTION: ... -->` 註解標註在產出物內,不擅自擴大範圍。
|
||
8. **語言**:wiki 內容與註解以繁體中文為主;程式碼、識別字、專有名詞用英文。
|
||
|
||
## 2. 目錄結構
|
||
|
||
```text
|
||
pp-qa-knowledge/
|
||
├── AGENTS.md # 本檔:schema 正本
|
||
├── CLAUDE.md # 指標檔 → AGENTS.md
|
||
├── .github/
|
||
│ ├── copilot-instructions.md # 指標檔 → AGENTS.md
|
||
│ └── agents/ # Copilot Custom Agents(Phase 5)
|
||
├── config/models.yaml # Ollama 端點與 model tags(唯一設定來源)
|
||
├── raw/
|
||
│ ├── originals/ # 原始檔(docx/xlsx/pdf/pptx/html 快照),只讀
|
||
│ ├── converted/ # 轉換後 Markdown(攝入管線的實際輸入),只讀
|
||
│ └── manifest.json # SHA-256 登記 + 原始檔↔轉換檔對應
|
||
├── wiki/
|
||
│ ├── summaries/ # 每份來源一頁摘要
|
||
│ ├── entities/ # 實體頁:系統、模組、API、法規條目、專案
|
||
│ └── concepts/ # 概念頁:跨來源綜合
|
||
├── index.md # 目錄式導航:每頁一行
|
||
├── log.md # append-only 操作日誌
|
||
├── tools/ # convert/(Phase 2)、ingest.py(Phase 3)、
|
||
│ # lint.py(Phase 4)、search.py(Phase 5)
|
||
├── mcp/server.py # FastMCP 薄殼(Phase 5)
|
||
└── .gitea/workflows/ # lint 定時排程(Phase 4)
|
||
```
|
||
|
||
## 3. 頁面類型與 frontmatter schema
|
||
|
||
### 3.1 三種頁面類型
|
||
|
||
| type | 位置 | 用途 | 一份來源對應 |
|
||
|-----------|-------------------|--------------------------------------------|--------------|
|
||
| `summary` | `wiki/summaries/` | 每份來源文件一頁摘要 | 1:1 |
|
||
| `entity` | `wiki/entities/` | 系統、模組、API、法規條目、專案等具名實體 | n:m |
|
||
| `concept` | `wiki/concepts/` | 跨來源綜合:缺陷模式、測試策略、法遵準則 | n:m |
|
||
|
||
### 3.2 YAML frontmatter(所有欄位必填,lint 檢查)
|
||
|
||
```yaml
|
||
---
|
||
type: entity # summary | entity | concept
|
||
title: "支付閘道" # 頁面標題,繁體中文
|
||
description: "一句話摘要,會同步至 index.md 的條目"
|
||
tags: [payment, gateway] # 小寫英文 kebab-case,至少 1 個
|
||
timestamp: 2026-07-14 # 最後「實質內容」更新日 YYYY-MM-DD(改錯字不算)
|
||
sources: # source_ref 列表,至少 1 筆
|
||
- "raw/converted/2026-q2-release-report.md#a1b2c3d4"
|
||
status: draft # draft(攝入產出)| reviewed(人工審過)| stale(lint 標記過期)
|
||
---
|
||
```
|
||
|
||
<!-- ASSUMPTION: status 生命週期為 draft →(PR 審核合併後人工改)reviewed →(lint 偵測過期)stale;
|
||
ingest 產出一律 draft,reviewed 只能由人工設定。 -->
|
||
|
||
### 3.3 source_ref 格式
|
||
|
||
```text
|
||
<repo 相對路徑>#<sha256 前 8 碼>
|
||
```
|
||
|
||
例:`raw/converted/aml-test-guideline.md#3f9c01ab`。hash 以 `raw/manifest.json`
|
||
登記值為準;引用原始檔(罕見,如 needs_ocr 的掃描件)同格式指向 `raw/originals/`。
|
||
|
||
### 3.4 檔名規則
|
||
|
||
wiki 頁檔名用英文 kebab-case slug(如 `wiki/entities/payment-gateway.md`),
|
||
標題(`title`)用繁體中文。
|
||
<!-- ASSUMPTION: 檔名採英文 slug 以符合「識別字用英文」原則,並避免跨平台路徑編碼問題。 -->
|
||
|
||
## 4. 新頁 vs. 就地編輯
|
||
|
||
- **開新頁**:內容描述一個獨立實體或概念,且預期會被其他頁連結。
|
||
- **就地編輯**:既有實體的屬性更新、補充事實、修正錯誤——更新該頁並在
|
||
`sources` 追加新的 source_ref、更新 `timestamp`。
|
||
- 猶豫時傾向**就地編輯**,避免頁面碎片化;就地編輯後若頁面已涵蓋兩個獨立主題,
|
||
才拆分成新頁。
|
||
<!-- ASSUMPTION: 「猶豫時傾向就地編輯」為推論的決勝規則,原始需求未明定。 -->
|
||
|
||
## 5. 內容邊界(wiki 化判斷準則)
|
||
|
||
- **該 wiki 化**(綜合壓縮散落多來源的事實才有價值):跨專案缺陷模式、
|
||
需求與測項的追溯關係、法遵測試準則綜合(FSC/AML/KYC 相關測試要求)、
|
||
歷次結案報告的教訓、部門 SOP 的決策脈絡。
|
||
- **不該 wiki 化**(單檔可 grep 的內容做鏡像是負價值):測試案例的逐步驟內容、
|
||
單一 API 的規格細節。這些留在 raw 層,wiki 只放指向它們的索引條目與跨案例綜合。
|
||
|
||
## 6. Ingest 工作流
|
||
|
||
1. **轉換**:`tools/convert/convert.py` 將原始檔轉為 Markdown,原始檔與轉換檔
|
||
皆登記 `raw/manifest.json`(含 SHA-256 與對應關係)。
|
||
2. **建分支**:`ingest/<YYYYMMDD>-<slug>`,絕不在 main 上工作。
|
||
<!-- ASSUMPTION: 分支命名慣例為推論,原始需求只要求 branch + PR。 -->
|
||
3. **讀來源**:讀取 `raw/converted/` 下的 Markdown(超過
|
||
`limits.ingest_chunk_max_chars` 時分段)。
|
||
4. **寫 summary**:每份來源產出一頁 `wiki/summaries/`,status 為 `draft`。
|
||
5. **更新 entity / concept**:依 §4 判斷開新頁或就地編輯;單一來源可能觸及多頁。
|
||
6. **更新 index.md**:每個新頁 / 改頁同步一行條目(格式見 §10)。
|
||
7. **追加 log.md**:格式見 §10。
|
||
8. **開 PR**:推分支至 Gitea remote,PR 描述列出:來源檔案 + hash、
|
||
新增/修改的頁面清單、LLM 使用的 model tag。人工審核後合併。
|
||
|
||
## 7. Lint 工作流
|
||
|
||
`tools/lint.py` 檢查項目:
|
||
|
||
1. **schema 完整性**:frontmatter 欄位齊全、值合法、source_ref 在 manifest 中存在。
|
||
2. **過期主張(staleness)**:頁面 timestamp 過舊、或其 source 對應的原始檔已有
|
||
更新版本攝入。
|
||
3. **覆蓋缺口**:manifest 中已轉換但沒有對應 summary 頁的來源。
|
||
4. **孤兒頁**:不被 index.md 或任何其他頁連結的頁面。
|
||
5. **重複頁**:title / description 高度相似的頁面對。
|
||
|
||
產出 markdown 報告;**絕不擅自刪檔或改檔**,一律列為「待人工核准」項目。
|
||
排程:Gitea Actions cron(Phase 4)。
|
||
|
||
## 8. Query 工作流
|
||
|
||
1. 先讀 `index.md` 定位候選頁,**最多 10 頁**。
|
||
2. 只讀取候選頁內文(透過 `tools/search.py` BM25 排序輔助)。
|
||
3. 綜合回答,**必附 source_ref**。
|
||
4. **禁止全庫掃描**(遍歷 wiki/ 全部頁面或 raw/ 全文)。
|
||
|
||
## 9. raw/manifest.json 結構
|
||
|
||
```jsonc
|
||
{
|
||
"version": 1,
|
||
"files": {
|
||
"raw/originals/<檔名>": {
|
||
"kind": "original",
|
||
"sha256": "<64 hex>",
|
||
"media_type": "docx | xlsx | pdf | pptx | html",
|
||
"added_at": "<ISO-8601>",
|
||
"status": "pending | converted | needs_ocr",
|
||
"source_url": "<僅網頁快照:來源 URL>",
|
||
"fetched_at": "<僅網頁快照:抓取時間 ISO-8601>"
|
||
},
|
||
"raw/converted/<檔名>.md": {
|
||
"kind": "converted",
|
||
"sha256": "<64 hex>",
|
||
"original_path": "raw/originals/<檔名>",
|
||
"original_sha256": "<64 hex>",
|
||
"converter": "from_docx | from_xlsx | from_pdf | from_pptx | from_web",
|
||
"converted_at": "<ISO-8601>"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
<!-- ASSUMPTION: 以路徑為 key 的物件結構(而非陣列)便於查找與去重;
|
||
needs_ocr 登記在 original 條目的 status(該檔沒有 converted 條目)。 -->
|
||
|
||
## 10. index.md 與 log.md 格式
|
||
|
||
**index.md** — 目錄式導航,依頁面類型分節,每頁一行:
|
||
|
||
```markdown
|
||
- [標題](wiki/entities/payment-gateway.md) — 一句摘要 | #payment #gateway
|
||
```
|
||
|
||
**log.md** — append-only 操作日誌,新條目追加於檔案末尾,不修改既有條目:
|
||
|
||
```markdown
|
||
## [YYYY-MM-DD] <op> | <title>
|
||
```
|
||
|
||
`op` ∈ `bootstrap` / `ingest` / `edit` / `lint` / `review`。
|
||
<!-- ASSUMPTION: op 詞彙表為推論,原始需求只定義格式字串。 -->
|
||
|
||
## 11. 雙棧規則
|
||
|
||
- 本檔為唯一正本。`CLAUDE.md`(Claude Code 入口)與
|
||
`.github/copilot-instructions.md`(Copilot 入口)只是薄指標檔
|
||
(一行說明 + 引用本檔),禁止複製 schema 內容。
|
||
- Claude Code 與 Copilot 的行為差異(若有)以「附註」形式標明在本檔相關章節,
|
||
禁止另立分叉檔。
|
||
- Copilot Custom Agents(`.github/agents/*.agent.md`,Phase 5)遵守 §1.6
|
||
成本紀律:agent 是編排者不是處理者。
|
||
|
||
## 12. Meta-loop 紀律
|
||
|
||
審核者(人工 review PR 或 lint 報告時)發現**系統性**問題——同類錯誤重複出現——
|
||
修改的是本 schema 或 lint 規則,而非逐筆修正輸出(**change the ruler, not the
|
||
output**)。單筆修正只治標;規則修正才治本。
|
||
|
||
## 13. Agent 行為紀律
|
||
|
||
<!-- 併入自原 CLAUDE.md(Karpathy LLM coding-pitfall + Ponytail minimalism ladder),
|
||
依 Phase 0 決議 Q4(a)。權衡:偏向謹慎與極簡並重;瑣碎任務用判斷力,
|
||
答案明顯就不問,直接寫最小可行的東西。 -->
|
||
|
||
### 13.1 先想再寫(Think before coding)
|
||
|
||
不假設、不隱藏困惑、主動揭露權衡。
|
||
|
||
- 明確陳述假設。
|
||
- 詮釋真正分歧時,只問**一個**釐清問題;否則採最小詮釋、以行內註解標註假設、
|
||
繼續前進——不要停滯。
|
||
- 存在更簡單的做法時要說出來;該反駁就反駁。
|
||
|
||
### 13.2 寫最少能動的程式碼(the ladder)
|
||
|
||
動手前,停在**第一個**能解決問題的階梯:
|
||
|
||
1. **Skip it** — 這功能真的需要嗎?(YAGNI)
|
||
2. **Stdlib** — 標準函式庫是否已提供?
|
||
3. **Native** — 平台/執行環境是否內建?
|
||
4. **Existing dependency** — 已安裝的依賴能否勝任?
|
||
5. **One line** — 能不能一行解決?
|
||
6. **Minimum** — 到此才寫最少能動的程式碼。
|
||
|
||
不做超出要求的功能、不為單次使用寫抽象、不做投機的彈性與可配置性、
|
||
不為不可能發生的情境寫錯誤處理。寫了 200 行但 50 行能解決,就重寫。
|
||
|
||
檢驗:「資深工程師會不會說這寫得過度複雜?」會,就簡化。
|
||
|
||
### 13.3 不能偷懶的地方
|
||
|
||
13.2 **不適用**於以下情況——這些要做好做滿:
|
||
|
||
- 信任邊界的輸入驗證。
|
||
- 防止資料遺失的錯誤處理。
|
||
- 資安。
|
||
- 無障礙(accessibility)。
|
||
- 真實硬體需要的校準(時鐘漂移、感測器偏差——平台從來不是規格書上的理想值)。
|
||
- 使用者明確要求的任何事。
|
||
|
||
### 13.4 標記並驗證你的捷徑
|
||
|
||
沒有檢查的偷懶程式碼是未完成品。
|
||
|
||
- 每個刻意的簡化都以 `ponytail:` 註解標記;若捷徑有已知天花板
|
||
(global lock、O(n²) 掃描、naive heuristic),註解要寫明天花板與升級路徑。
|
||
- 非平凡邏輯要留下**一個**可執行的檢查——邏輯壞掉就會失敗的最小東西
|
||
(assert 式自檢或一個小測試檔;不用框架、不用 fixtures)。
|
||
- **測資照現實建,不是照「能過」建**:來源檔名用中文+空格(真實 QA 文件就長這樣)、
|
||
語料要有跨頁共用的核心詞。便利值會變成盲點的形狀——ASCII 檔名讓 `git status`
|
||
的路徑轉義 bug 溜過,互不重疊的語料讓 BM25 的 idf 退化 bug 溜過。
|
||
新增檢查時先問:**這組測資和真實輸入差在哪?差異處就是沒被測到的地方。**
|
||
- **斷言要驗「結果可用」,不是驗「字串存在」**:`assert "。
|
||
|
||
檢驗:每一行變更都能直接追溯到使用者的要求。
|
||
|
||
**有效的跡象**:diff 裡不必要的變更變少、因過度複雜而重寫的次數變少、
|
||
釐清問題發生在動手**之前**而非犯錯之後、刻意的捷徑是可見的(`ponytail:`)
|
||
而非沉默的。
|
||
|
||
## 14. raw 完整性與修復(對帳)
|
||
|
||
raw 層以 manifest 的 SHA-256 為信任根(§1.4、§9)。檔案被**手動刪除**或**就地改動**時,
|
||
下列為正規稽核與修復流程。核心原則:manifest 是 provenance **真相帳本**、不是 cache——
|
||
hash 的用途是讓刪除**可復原、可驗證**,故先**復原檔案**,而非改帳本去遷就殘缺的磁碟。
|
||
|
||
### 14.1 稽核
|
||
|
||
```text
|
||
python tools/convert/convert.py --verify
|
||
```
|
||
|
||
純唯讀(不改任何檔,含 manifest),re-hash 全部登記檔並掃描 `raw/`,回報三類意外、
|
||
有不一致以退出碼 1 表示(可當 CI/排程閘門):
|
||
|
||
- **missing** — 帳本有登記,磁碟上不見了(手動刪除)。
|
||
- **mismatch** — 檔案還在但 sha256 與登記值不符(raw 被就地改動,違反 §1.4)。
|
||
- **unregistered** — `raw/originals`/`raw/converted` 下有檔卻不在帳本。
|
||
`converted/assets/*`(圖片)由 markdown 連結追蹤而非帳本,故略過;`.gitkeep` 亦略過。
|
||
|
||
### 14.2 修復決策(可衍生 vs 信任根)
|
||
|
||
<!-- ASSUMPTION: 本決策樹為推論的正規流程,原始需求只定義 raw 不可變與 hash 登記;
|
||
依「可衍生(converted)優先還原、信任根(original)不可再生」的性質分流。 -->
|
||
|
||
1. **先還原,不改帳本**(多數「誤刪」到此為止):`raw/` 進版控,`git restore <path>`
|
||
取回精確 bytes(或從備份),再跑 §14.1 確認 hash == 登記值。帳本零改動。
|
||
2. **converted 救不回** → 從 original 重轉。注意兩個陷阱:(a) `convert.py` 以 **original
|
||
的 hash** 去重,original 條目還在會直接 skip、**不會**因 converted 不見而重生,需先
|
||
自帳本移除該 converted 條目;(b) 函式庫版本變動可能使重轉 bytes 不同 → converted
|
||
hash 變 → 連累所有指向它的 wiki `source_ref`。故**能還原就別重轉**;重轉屬實質變更,
|
||
連同 source_ref 一起走 PR。
|
||
3. **original 救不回** → 不可逆的 provenance 損失,**不得靜默刪條目**(會連鎖 orphan 掉
|
||
對應 converted、再斷所有 source_ref);據實記錄該檔遺失並上報人工(HITL,§5)。
|
||
|
||
### 14.3 動 manifest 的紀律
|
||
|
||
- 任何帳本變更走 **branch + PR**(§5)——manifest 是 provenance 帳本,改它需人工審核。
|
||
- `log.md` 追加一筆 `edit` 條目(§10),說明對了什麼、為什麼。
|
||
- 刪任何 `converted` 條目前,先確認沒有 wiki `source_ref` 引用它(否則 §7 的 schema
|
||
檢查會抓到斷鏈);有引用就改為還原檔案,或同 PR 一併更新引用。
|