跳至主要内容

文檔 Registry 規格

狀態:P0 設計已通過審閱(20261015-docs-registry-p0,第 3 輪,受審 revision r3-11470bae46a0d5bd;4 條 major 均處置)。P0 實作(docs.py、registry 投影、三專案基線)已完成並通過審閱(20261015-docs-registry-p0-impl,第 3 輪,受審 revision r3-4d58c60ef77aecc0;5 條意見均處置);基線結果見 ADR-023 附錄 B 依據:ADR-023 文檔位置與特性 Registry 的 D3、D4 與實作分期 P0;追蹤項目 FU-142 本頁定義:registry 檔案放在哪、格式與驗證、檔案如何對應類型,以及入口程式 docs.py 的介面。ADR 保存政策與取捨,本頁是操作規範,不重複 ADR 的理由。

1. P0 範圍​

做不做(後續分期)
共用層 registry 進 docs-site bundle,投影到各 docs 模組交換文件搬家、publish/close、revision(P1)
專案層 registry 的位置、合併規則與豁免(waiver)D2 審閱提交守衛、D7 編譯前排除(P1)
docs.py 的 lint、classify、audit(全部唯讀)resolve/create/register、retention 自動判定(P2)
/docs-site audit 同時執行 registry 對照gitignore、host 排除、scaffold 排除由 registry 產生(P1/P2)
三個專案跑 audit 定基線PreToolUse hook(P3)

P0 只新增唯讀能力,不改變任何既有 skill 的寫入位置;現行規則在後續分期通過前維持原狀。

2. 檔案位置​

檔案位置管理方式
共用層 registry(canonical).claude/skills/docs-site/assets/site/docs-registry.json方法論檔,/methodology publish 下行
共用層 registry(投影){docsRoot}/docs-registry.json/docs-site 受管檔,.docs-site.json 記錄雜湊,不可手改
專案層 registry{docsRoot}/docs-registry.project.json專案自有,比照 site.project.ts;不存在代表沒有專案擴充
入口程式.claude/skills/docs-site/scripts/docs.py方法論檔;其他 skill 以「共用 docs.py」引用,比照 /br 的 br_trace.py
測試.claude/skills/docs-site/scripts/test_docs.py方法論檔

為什麼投影到 docs 模組,而不是直接讀方法論目錄:

  • docs 模組的建置(P1 的 D7 編譯前排除、導覽分類)需要在 build 時讀 registry;投影讓 docs 模組自足,host 建置不依賴 .claude/。
  • 每個站台讀的是自己已套用的版本,與其他受管檔同一個版本單位;canonical 更新後由 /docs-site audit 報出投影落後,再經 plan/apply 套用。
  • docs.py 一律讀投影(站台實際使用的版本);/docs-site 負責投影與 canonical 是否一致。

bundle 的受管檔由 assets/site/ 下的實際檔案推導,新增 docs-registry.json 不需要改 update.py;各站台在方法論發布後執行一次 /docs-site plan/apply 即取得投影。

3. Registry 檔案格式(schema 1)​

兩層都是嚴格 JSON(不支援註解與尾逗號,與 package.json/tsconfig.json 的處理一致)。未知欄位一律拒絕,以抓出拼字錯誤。

3.1 頂層欄位​

欄位共用層專案層說明
schema必填 1必填 1不認得的 schema 版本直接拒絕
layer"common""project"—
defaults選填選填只套用到同一層宣告的類型(ADR D3)
scan必填不允許掃描範圍,見 §5
types必填選填類型清單,見 §3.2
extendCommon不允許選填[{id, legacy}],只能對共用類型追加舊位置
waivers不允許選填已知欠帳的豁免,見 §6

3.2 類型欄位​

欄位與值域沿用 ADR-023 Schema,另補三項 P0 實作需要的欄位:

欄位說明
id小寫 kebab-case,全部層級唯一
paths、legacy路徑樣式,語法見 §4
exchange交換容器類型專用:{kind, anchors}。kind 是交換種類(例如 peer-review),anchors 是錨點檔的完整路徑樣式(與 paths 同語法、必含 {id}),用來判定孤兒,見 §4.3
exchangeKind路徑含 {id} 的類型必填,宣告其檔案屬於哪一種交換;必須對應某個容器類型的 exchange.kind
inherit"container";索引/分類類型專用
priority整數,預設 0

其餘欄位(category、role、lifecycle、close、derivedFrom、retention、authority、writer、change、scope、presentation、distribution、downstream、git、gitReason、belongsTo、base、purpose、status、note、title)的值域見 ADR。

3.3 驗證順序​

  1. 結構:嚴格 JSON、必填欄位、值域、未知欄位、id 格式與唯一性。
  2. 合併:共用層先載入;專案層同 id 拒絕;extendCommon 只能有 id、legacy,目標必須是共用類型。
  3. 推導:git 由 lifecycle 推導,有 git 覆寫時必須有 gitReason。
  4. lint:ADR 的 R1–R11。

前三步失敗屬「registry 無效」,docs.py 回 exit 2 且不做任何對照;lint 問題回報在結果中。

4. 路徑語法與比對​

4.1 語法​

  • 相對於 workspace(含 project.json 的 modules/ 目錄),以 / 分隔,區分大小寫,不得含 .. 或絕對路徑。
  • 錨點:{docsRoot}=docs 模組目錄名;{workspace}=workspace 根(只用於 .claude/、output/ 等 workspace 層位置);{module}=project.json 中 docs 模組以外的每個模組。
  • 萬用字元:* 比對一個路徑段內任意字元;** 比對零或多個路徑段;{id} 比對一個路徑段中的一部分(可帶前後綴,例如 {id}-evidence*),並把值擷取為交換 id。一個樣式最多一個 {id}。
  • {id} 採最長比對:例如 x-evidence-review-evidence 對 {id}-evidence* 擷取為 x-evidence-review,不會因 id 本身含 -evidence 而截短。{id} 不得為空、不得含 /。

4.2 比對規則​

  1. 對每個檔案找出所有命中的樣式(含 legacy)。
  2. 分數=樣式的字面字元數+priority×1000;取最高分者。
  3. 最高分有兩個以上不同類型時,結果為 ambiguous,不猜。
  4. 命中類型有 inherit: "container" 時,從其餘命中中取最高分、且不是繼承類型的那一個當容器;保存與發布限制(lifecycle、retention、distribution、close、scope、downstream、git)取容器的值,role 與 presentation 保留索引類型自己的值。沒有容器時用索引類型自己的值。
  5. 命中 legacy 時標記 legacy: true,類型與特性照常判定。

4.3 交換 id 與孤兒​

分組鍵是 (交換種類, id):檔案所屬類型的 exchangeKind 加上從路徑擷取的 {id}。不同種類的交換使用相同 id 時互不影響(例如審閱 x 與 work x 是兩組)。

錨點解析:容器類型的 exchange.anchors 是一組完整路徑樣式,以 workspace 為根解析,不相對於任何容器目錄。判定某組 (kind, id) 時,把 {id} 代入該種類的每個錨點樣式(樣式中其他萬用字元照常比對),任一錨點存在即為有效交換;全部不存在、但這組有任何成員檔案時,判為孤兒。錨點只代入同一組的 id,不會借用其他交換的錨點。

新舊位置寫在同一個容器的 anchors 裡,因此不需要「容器相對」的關係也能涵蓋同層的舊簡報。審閱交換的例子:

{
"id": "peer-review-exchange",
"paths": ["{docsRoot}/docs/exchanges/reviews/{id}/**"],
"exchangeKind": "peer-review",
"exchange": {
"kind": "peer-review",
"anchors": [
"{docsRoot}/docs/exchanges/reviews/{id}/brief.md",
"{workspace}/.claude/peer-review/{id}.md"
]
}
}

搭配 peer-review-evidence 的舊位置 {workspace}/.claude/peer-review/{id}-evidence*/**、{workspace}/.claude/peer-review/{id}-reviewer*/**(exchangeKind: "peer-review"):證據目錄 20261005-report-section-summary-evidence-r2/ 擷取 id 20261005-report-section-summary,若 .claude/peer-review/20261005-report-section-summary.md 與新位置的 brief.md 都不存在,即為孤兒。

lint 另檢查:exchangeKind 必須有對應的容器;每個容器的 anchors 至少一條、每條含 {id};同一 kind 只能有一個容器。

5. 掃描範圍​

掃描範圍由共用層 scan 宣告,不寫死在程式:

{
"scan": {
"roots": [
"{docsRoot}",
"{workspace}/.claude/peer-review",
"{workspace}/.claude/work",
"{workspace}/.claude/docs",
"{workspace}/.claude/handoff.md",
"{workspace}/output",
"{module}/docs",
"{module}/FEATURE_CHANGELOG.md"
],
"excludeDirs": ["node_modules", "build", ".docusaurus", ".cache-loader", ".gradle", ".git"],
"excludeFiles": [".DS_Store", ".gitkeep"]
}
}
  • 不存在的根略過;符號連結不跟隨,遇到時列為 DR-SYMLINK 並略過。
  • .codex/ 與 /tmp 不在掃描範圍:前者是 agent 設定,後者不在工作樹內,audit 不宣稱能看見。D1 的文件用途義務仍由 rules/skills 與人審執行;不能只憑路徑判定這些位置寫入的是人類文件或合法設定/暫存。
  • P3 覆蓋澄清(2026-10-20 設計修訂):預定寫前 hook 限 Claude Code 的 Write/Edit 與 registry 掃描根;NotebookEdit、shell/MCP/外部編輯器及根外的語意文件不宣稱已攔截。詳見文件寫入守衛 §2/§4/§6。P3 設計已通過第 2 輪審閱(受審 revision r2-a2b1504b61f97ef6),結論見該規格 §13;P3 實作已通過第 2 輪審閱(受審 revision r2-38282cca1103fcfa,結論見該規格 §16),參考工作區尚未安裝/啟用;原生接線修訂及已採納裁決見該規格 §14~§15,不是全檔案系統存取控制。

6. 豁免(waiver)​

已知欠帳在處理前會一直被報出;用專案層 waivers 明確記下決定,避免基線被淹沒:

{
"waivers": [
{
"code": "DR-EVIDENCE-IN-PAGE-TREE",
"paths": ["{docsRoot}/docs/testing/reports/**"],
"reason": "ADR-023 待決 3:證據是否移出 docs/ 尚未決定",
"owner": "FU-142",
"review": "2026-12-31"
}
]
}
  • code、paths、reason、owner 必填;review 為下次檢視日期,過期時 audit 以 warning 提醒。
  • 只能豁免 §7 標為「可豁免」的代碼;registry 無效、多重比對不可豁免。
  • 被豁免的發現仍列在結果中(waived 欄寫原因),不計入 --fail-on 的判定。
  • 判定順序:先逐檔產生發現 → 逐檔比對豁免(code 相同且路徑命中 paths)→ 已豁免與未豁免分開彙整(見 §8.5)。同一目錄只有部分檔案被豁免時,其餘檔案的發現照常計入。
  • 沒有命中任何發現的豁免列為 DR-STALE-WAIVER,比照 doc-lint 的「未生效 ignore 視為錯誤」,避免豁免殘留吃掉日後的真問題。
  • 共用層不能宣告豁免。
  • 寫入與安裝不沿用 audit waiver:waiver 保留本節既有彙整/--fail-on 語意,不將未登記位置變成已登記。P3 設計要求既有/新建未登記檔同樣拒絕,安裝前完整列出所有受影響檔(含 waived/ignored);清單非空時不寫入 hook 設定,先依用途補類型或修正位置,不能只加 waiver/ignore 或刪除原件過關。詳見文件寫入守衛 §4.3/§7;這是設計與實作均已通過審閱的 P3 安裝契約,結論見該規格 §16;不改現行 audit,也不新增 UAT/retention/交付閘門。

7. 發現代碼​

代碼嚴重度可豁免判定
DR-REGISTRY-INVALIDerror否結構、合併或推導失敗(exit 2)
DR-LINTerror否R1–R11 任一條違反
DR-AMBIGUOUSerror否同一檔案同分命中兩個以上類型
DR-UNREGISTEREDwarning是掃描範圍內的檔案沒有對應類型
DR-GIT-TRACKEDerror是應為 ignored 卻已追蹤
DR-GIT-UNPROTECTEDwarning是應為 ignored 卻既未追蹤也未被 gitignore
DR-GIT-IGNOREDerror是應追蹤卻被 gitignore
DR-ORPHANerror是(kind, id) 組有成員檔案,但所有錨點都不存在(§4.3)
DR-LEGACYinfo—檔案在舊位置
DR-OUTSIDE-DOCSwarning是要呈現(presentation 不是 hidden)的檔案不在 docs 模組內(D1)
DR-UNREACHABLEwarning是要呈現的非 md 檔不在站台索引範圍(相依資源不計)
DR-UNRENDERABLEwarning是page 類型中混有非 md 檔
DR-EVIDENCE-IN-PAGE-TREEinfo是證據放在 {docsRoot}/docs/ 下(ADR 待決 3 決定前只提示)
DR-SYMLINKwarning是掃描時遇到符號連結
DR-STALE-WAIVERwarning否豁免沒有命中任何發現,或已過 review 日期

「尚未提交」(應追蹤、目前未追蹤也未忽略)是正常的工作中狀態,不列為發現,只在統計中呈現。

8. docs.py 介面​

8.1 共通約定​

python3 {docsSiteSkillRoot}/scripts/docs.py <verb> --workspace {workspaceRoot} [--docs-module {name}] [verb 參數]
  • {docsSiteSkillRoot}:/docs-site skill 目錄;其他 skill 以「共用 docs.py」引用。
  • docs 模組:從 project.json 找 type: docs 的模組;只有一個時自動採用,多個時必須給 --docs-module,不猜。
  • 只使用 Python 標準函式庫與 git CLI;不需要安裝套件。
  • 輸出一律為一個 JSON 物件(--format md 例外,見 audit),欄位依字母排序,比照 /progress native 腳本:
{
"schemaVersion": 1,
"tool": "docs.py",
"verb": "audit",
"status": "ok",
"readOnly": true,
"writes": 0,
"workspace": "/abs/path/modules",
"docsModule": "app-docs",
"registry": {"schema": 1, "commonSha256": "…", "projectSha256": null},
"errors": [],
"warnings": []
}
exit意義
0執行完成,且沒有達到 --fail-on 門檻的發現
1執行完成,但有達到 --fail-on 門檻的發現
2參數、registry 或 I/O 錯誤;status: "error",不輸出部分結果

P0 的動詞全部唯讀(readOnly: true、writes: 0)。

8.2 動詞與分期​

動詞分期寫入用途
lintP0否驗證兩層 registry
classifyP0否查詢路徑的類型與生效特性
auditP0否registry × 實際檔案 × git × 站台可達性
publish、closeP1是交換文件的呈現與收尾(D6、D2)
resolve、create、registerP2create/register 是類型+id 解析路徑、建立、登記導覽

P1 以後的動詞在各自分期另訂細節;P0 先保留名稱,避免日後改名。

8.3 lint​

docs.py lint --workspace {ws}

結果欄位:status(ok/issues)、issues: [{rule, type, message}]、typeCount: {common, project}。

8.4 classify​

docs.py classify --workspace {ws} --path {相對路徑} [--path …]

路徑不需存在,可在寫入前查詢(P3 的 hook 會用到)。每筆結果:

{
"path": "app-docs/docs/exchanges/reviews/demo/README.md",
"match": "matched",
"type": "section-index",
"layer": "common",
"legacy": false,
"exchangeKind": "peer-review",
"exchangeId": "demo",
"container": "peer-review-exchange",
"role": "document",
"presentation": "page",
"effective": {"lifecycle": "temporary", "retention": "remove-at-close", "distribution": "preview-only", "git": "tracked", "scope": "team", "downstream": null, "close": {"trigger": "/peer-review close", "owner": "作者"}},
"candidates": ["section-index", "peer-review-exchange"]
}

match 為 matched/unregistered/ambiguous;ambiguous 時 type 為 null,candidates 列出同分類型。

8.5 audit​

docs.py audit --workspace {ws} [--format json|md] [--fail-on none|error|warning] [--include-waived]
[--registry-common {path}] [--registry-project {path}] # 只供基線盤點(§10.1)
  • --fail-on 預設 none(人工檢視);CI 與 /docs-site audit 用 error。
  • --format md 輸出給人讀的報告,內容與 JSON 相同;仍不寫檔。

JSON 結果主要欄位:

欄位內容
summary掃描檔數、命中類型數、各嚴重度發現數、被豁免數
types每個命中類型:檔數、位元組、git 狀態分布、應有 git 狀態、繼承來源
findings[{code, severity, type, group, paths, pathsTruncated, count, bytes, exchangeId, message, waived}],彙整方式見下
lint同 lint 的 issues
exchanges交換清單:(kind, id)、成員檔數、找到的錨點路徑
registry實際採用的 registry 來源:每層的路徑、SHA-256,以及來自投影或命令列覆寫(§10)

彙整規則(避免部分豁免遮蔽整組):

  1. 每個發現先以單一檔案為單位產生,並逐檔決定是否被豁免。
  2. 彙整鍵為 (code, type, group, waived);group 是所在目錄。已豁免與未豁免永遠不會併在同一筆。
  3. count、bytes 是該筆的完整數量;paths 只是展示用,最多 20 個,超過時 pathsTruncated: true。
  4. --fail-on 以第 1 步的逐檔未豁免發現判定,不看展示用的截斷清單;summary 的各項計數也由逐檔結果計算。

9. 與 /docs-site 的整合​

  • /docs-site audit 在原有 bundle 對照後執行 docs.py audit --fail-on error,兩者結果分開報告;bundle 落後時 registry 對照讀的是舊投影,報告須註明。
  • plan/apply 不需新增邏輯:docs-registry.json 是一般受管檔。docs-registry.project.json 不由 bundle 建立,專案需要時自行新增。
  • m-docs-site.md 補「registry 投影與專案層」段落;/docs-site SKILL.md 補 docs.py 用法與工具回歸測試。

10. 三專案基線​

三個專案都還沒有專案層 registry(已核對三個 docs 模組都沒有 docs-registry*.json)。基線一律用同一套流程,區分「輸入差異」與「工具判定差異」。

10.1 輸入​

專案共用層專案層
參考實作(app-docs)方法論下行後經 /docs-site plan/apply 取得的投影唯讀覆寫 --registry-project(草稿)
florae-g1、nbsos唯讀覆寫 --registry-common(框架 canonical;下游尚未投影)唯讀覆寫 --registry-project(草稿)
  • 專案層草稿:把 ADR 附錄 B 三份專案擴充依 schema 1 重寫(base: other、必填 purpose),放在目標 repo 以外的本次 P0 實作審閱交換證據中。參考實作也一樣:它的擴充(設計語言對齊證據、參考實作稽核證據、UAT 證據來源對應、測試靜態證據)同樣只作基線輸入。
  • 參考實作暫不建立正式專案層:app-docs 是 scaffold 來源,正式的 docs-registry.project.json 會被 /scaffold-module 複製到新專案,把參考實作專屬的擴充帶到下游。要不要建立、以及 scaffold 時如何排除或清空,留給參考實作決定採用時一併處理(見 §13 註)。
  • 覆寫參數的適用範圍:
    • --registry-common {path}:只在目標站台尚未投影時使用。
    • --registry-project {path}:未投影或已投影站台都可使用,只供基線盤點;目標站台已有正式專案層時拒絕覆寫,避免兩份專案層並存。
    • 有覆寫時,結果的 registry 欄標明每層的 source(projection/override)、路徑與 SHA-256。/docs-site audit 不使用覆寫參數。

10.2 每個專案都跑兩組​

  1. common-only:只用共用層,呈現專案尚未宣告任何擴充時的實際狀態。
  2. common+專案層草稿:與 ADR 附錄 B 的輸入等價。

10.3 歸因規則​

  • 兩組之間的差異(主要是未登記數量)歸因於輸入差異:缺少專案擴充。
  • 與 ADR 附錄 B 對照時只用第 2 組;剩餘差異逐項歸因為工具判定差異(原型與正式工具的規則不同)或期間新增的檔案,不能把輸入差異算成工具差異。
  • 對照表記錄兩邊實際採用的 registry 來源與 SHA-256(附錄 B 的原型輸入以 ADR 附件的 registry.common.json 與當時的專案擴充為準)。

10.4 結果處置​

各專案的已知欠帳先列出建議的豁免清單與專案層草稿,由各專案決定是否採用;P0 不替任何專案(含參考實作)寫入豁免或正式專案層。

11. 測試​

test_docs.py 以暫存 workspace+git init 建立 fixture,至少涵蓋:

類別測試
格式嚴格 JSON、未知欄位、缺必填、值域錯誤、schema 版本不符都回 exit 2
合併專案 defaults 不影響共用類型;同 id 拒絕;extendCommon 只能追加 legacy
比對分數與 priority;同分不同類型為 ambiguous;{id} 擷取(含前後綴、最長比對、id 本身含 -evidence)
繼承審閱證據與審閱根目錄的 README/_category_.json;報表 run README;無容器時用自身值
lintR1–R11 各至少一個違反案例
gittracked/ignored/unprotected/尚未提交 四種狀態的判定
孤兒新位置錨點;舊位置證據對同層舊簡報;新舊並存;同一 kind 中有錨點與無錨點的兩組只判後者;不同 kind 使用相同 id 不互相借用錨點;-reviewer* 舊目錄
豁免生效、不可豁免代碼被拒、未命中的豁免、過期提醒;同目錄只有部分檔案被豁免時其餘照常計入;超過 20 檔且含未豁免檔時 --fail-on 仍觸發、count 完整
基線輸入覆寫時結果記錄每層來源(projection/override)與雜湊;已投影站台拒絕 --registry-common;已有正式專案層時拒絕 --registry-project;已投影站台可用 --registry-project;不寫入目標 workspace
介面JSON envelope 欄位、exit code、readOnly/writes、classify 對不存在路徑可用
唯讀執行前後 workspace 檔案與 git index 完全相同

12. 原型處置​

ADR 附件中的原型(/methodology/docs-registry/ 下的 registry.common.json、audit.py、test_audit.py)是審閱證據。P0 正式工具通過後保留原檔,ADR 附錄改註「已由 docs.py 取代」並連到本頁,不再更新原型。

13. P0 會修改的檔案​

檔案變更
.claude/skills/docs-site/assets/site/docs-registry.json新增:共用層 registry(由原型轉為 schema 1,補 scan、exchange/exchangeKind、{id})
.claude/skills/docs-site/scripts/docs.py、test_docs.py新增
.claude/skills/docs-site/SKILL.mdaudit 模式加 registry 對照、docs.py 用法與測試
.claude/rules/m-docs-site.mdregistry 投影與專案層段落
.claude/rules/m-methodology.md索引登錄 registry 與 docs.py
appfuse-docs/docs-methodology/decision-records/023-docs-registry.md附錄 A 指向本頁;變更歷史
app-docs(參考實作)/docs-site apply 後新增投影 docs-registry.json 並更新 .docs-site.json

註:P0 不建立任何正式專案層。日後參考實作若決定採用 docs-registry.project.json,須同時讓 /scaffold-module 排除或清空該檔,避免參考實作專屬擴充下行;此修改屬採用當時的變更範圍。

14. 審閱時請特別看​

  1. registry 投影到 docs 模組(而非直接讀方法論目錄)的取捨。
  2. (kind, id) 分組與 exchange.anchors 是否足以判定新舊位置的孤兒。
  3. 發現代碼的嚴重度與可否豁免,特別是 DR-EVIDENCE-IN-PAGE-TREE 先列 info。
  4. 豁免機制是否會被濫用,DR-STALE-WAIVER 與 review 日期是否足夠。
  5. 下游基線只做唯讀盤點、不寫入下游 repo 是否合適;--registry-common/--registry-project 是否只該在 P0 盤點時使用。