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

18 KiB
Raw Blame History

AGENTS.md — QA 知識庫 schema 正本

本檔是本專案唯一的 agent 規範正本。CLAUDE.md.github/copilot-instructions.md 只是指向本檔的薄指標檔,禁止在任何其他檔案複製本檔內容(見 §11 雙棧規則)。

0. 專案定位

為金融業電子支付QA 測試部門建置的部門知識庫,採 Karpathy LLM Wiki 模式 知識在文件攝入時由 LLM 編譯成 wiki 頁面(萃取、綜合、交叉引用),查詢時只讀已編譯 頁面。這不是 RAG——第一版不建向量庫、不做 chunk 檢索,檢索用 BM25 / 全文檢索。 最終透過一層薄的 MCP servermcp/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 亦以 .gitattributesraw/** 關閉換行 正規化,避免 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. 目錄結構

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 檢查)

---
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 標記過期)
---

3.3 source_ref 格式

<repo 相對路徑>#<sha256 前 8 碼>

例:raw/converted/aml-test-guideline.md#3f9c01ab。hash 以 raw/manifest.json 登記值為準;引用原始檔(罕見,如 needs_ocr 的掃描件)同格式指向 raw/originals/

3.4 檔名規則

wiki 頁檔名用英文 kebab-case slugwiki/entities/payment-gateway.md 標題(title)用繁體中文。

4. 新頁 vs. 就地編輯

  • 開新頁:內容描述一個獨立實體或概念,且預期會被其他頁連結。
  • 就地編輯:既有實體的屬性更新、補充事實、修正錯誤——更新該頁並在 sources 追加新的 source_ref、更新 timestamp
  • 猶豫時傾向就地編輯,避免頁面碎片化;就地編輯後若頁面已涵蓋兩個獨立主題, 才拆分成新頁。

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 上工作。
  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 結構

{
  "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>"
    }
  }
}

10. index.md 與 log.md 格式

index.md — 目錄式導航,依頁面類型分節,每頁一行:

- [標題](wiki/entities/payment-gateway.md) — 一句摘要  #payment #gateway

log.md — append-only 操作日誌,新條目追加於檔案末尾,不修改既有條目:

## [YYYY-MM-DD] <op> | <title>

opbootstrap / ingest / edit / lint / review

11. 雙棧規則

  • 本檔為唯一正本。CLAUDE.mdClaude Code 入口)與 .github/copilot-instructions.mdCopilot 入口)只是薄指標檔 (一行說明 + 引用本檔),禁止複製 schema 內容。
  • Claude Code 與 Copilot 的行為差異(若有)以「附註」形式標明在本檔相關章節, 禁止另立分叉檔。
  • Copilot Custom Agents.github/agents/*.agent.mdPhase 5遵守 §1.6 成本紀律agent 是編排者不是處理者。

12. Meta-loop 紀律

審核者(人工 review PR 或 lint 報告時)發現系統性問題——同類錯誤重複出現—— 修改的是本 schema 或 lint 規則,而非逐筆修正輸出(change the ruler, not the output)。單筆修正只治標;規則修正才治本。

13. Agent 行為紀律

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 稽核

python tools/convert/convert.py --verify

純唯讀(不改任何檔,含 manifestre-hash 全部登記檔並掃描 raw/,回報三類意外、 有不一致以退出碼 1 表示(可當 CI排程閘門

  • missing — 帳本有登記,磁碟上不見了(手動刪除)。
  • mismatch — 檔案還在但 sha256 與登記值不符raw 被就地改動,違反 §1.4)。
  • unregisteredraw/originalsraw/converted 下有檔卻不在帳本。 converted/assets/*(圖片)由 markdown 連結追蹤而非帳本,故略過;.gitkeep 亦略過。

14.2 修復決策(可衍生 vs 信任根)

  1. 先還原,不改帳本(多數「誤刪」到此為止):raw/ 進版控,git restore <path> 取回精確 bytes或從備份再跑 §14.1 確認 hash == 登記值。帳本零改動。
  2. converted 救不回 → 從 original 重轉。注意兩個陷阱:(a) convert.pyoriginal 的 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 一併更新引用。