Files
pp-qa-km/AGENTS.md
yellowadmin 356ae9367c feat: convert --verify 對帳 + 修復 CRLF 使 hash 失準 + raw/** .gitattributes (#1)
## 目的

`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
2026-07-23 02:27:47 +00:00

348 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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預設候選為
EmbeddingGemmaGoogle或 snowflake-arctic-embedSnowflake
4. **raw 層不可變**`raw/` 內的文件只讀不改。原始檔與轉換後 Markdown 皆登記
SHA-256 至 `raw/manifest.json`(結構見 §9轉換檔條目必須回指原始檔 hash。
wiki 頁面引用來源必須帶 `source_ref`(格式見 §3.3)。登記的 hash 必須等於檔案在
磁碟上的實際 bytesUTF-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 AgentsPhase 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.pyPhase 3
│ # lint.pyPhase 4、search.pyPhase 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人工審過| stalelint 標記過期)
---
```
<!-- ASSUMPTION: status 生命週期為 draft →PR 審核合併後人工改reviewed →lint 偵測過期stale
ingest 產出一律 draftreviewed 只能由人工設定。 -->
### 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 remotePR 描述列出:來源檔案 + 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 cronPhase 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.mdKarpathy 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 "![img](" in md` 只證明字串被
組出來,證明不了連結解得開——真正該做的是用解析器 render 後確認產出 `<img>`
且目標檔存在。測資照現實建了、斷言卻停在表面bug 一樣會溜過去。
- 把任務轉成可驗證的目標,迭代到通過:
- 「加驗證」→ 先寫無效輸入的測試,再讓它通過。
- 「修 bug」→ 先寫重現 bug 的測試,再讓它通過。
- 「重構 X」→ 重構前後測試都要通過。
多步驟任務要先列簡短計畫:`1. 步驟 → verify: 檢查方式`
### 13.5 手術式修改
只碰必須碰的,只清自己的垃圾。
- 不「順手改善」相鄰的程式碼、註解、排版。
- 不重構沒壞的東西;配合既有風格。
- 移除**因你的變更**而失效的 import/變數/函式。
- 既有的死程式碼:提出來,但不刪(除非被要求)。
檢驗:每一行變更都能直接追溯到使用者的要求。
**有效的跡象**diff 裡不必要的變更變少、因過度複雜而重寫的次數變少、
釐清問題發生在動手**之前**而非犯錯之後、刻意的捷徑是可見的(`ponytail:`
而非沉默的。
## 14. raw 完整性與修復(對帳)
raw 層以 manifest 的 SHA-256 為信任根§1.4、§9。檔案被**手動刪除**或**就地改動**時,
下列為正規稽核與修復流程。核心原則manifest 是 provenance **真相帳本**、不是 cache——
hash 的用途是讓刪除**可復原、可驗證**,故先**復原檔案**,而非改帳本去遷就殘缺的磁碟。
### 14.1 稽核
```text
python tools/convert/convert.py --verify
```
純唯讀(不改任何檔,含 manifestre-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 一併更新引用。