# Wiki Token Discipline
Navigation: [[conventions]] | [[index]]
> [!important] ingest / query / retrieve / lint を実行するエージェントは、`wiki/index.md`・各 `_index.md`・`wiki/log.md`・`wiki/hot.md` の全文を読んではならない。発見はスクリプト、本文は節抽出、カタログ更新は追記コマンドに任せる。
カタログ類は人間と Obsidian 向けの派生物である。エージェントの入力にしてはいけない。
## 禁止する読み出し
次を `Read` してはならない。存在確認・追記・検索もスクリプトか `rg` で行う。
- `wiki/index.md`
- `wiki/log.md`
- `wiki/{sources,entities,concepts}/_index.md`
- `wiki/hot.md` の全文(クイック query だけ、後述の窓を `wiki-catalog.py trim-hot` 済み前提で読んでよい)
例外: カタログ自体を直す保守作業(`wiki-refactor` の索引同期など)で、対象ファイルだけを読むとき。
## 発見: `wiki-resolve.py`
既存ページの有無は索引ではなく名前解決で確認する。
```bash
python3 scripts/wiki-resolve.py "Hellerstein" --type entity --top 8
python3 scripts/wiki-resolve.py "RDMA" --type concept
python3 scripts/wiki-resolve.py "RoCEv2" --type any
```
標準出力は JSON。`candidates[].path` だけを見て、必要なページを `wiki-excerpt.py` で開く。0 件なら新規作成候補とする。aliases とファイル名を優先し、必要なら BM25 を足す。
## 本文: `wiki-excerpt.py`
厚い concept / entity を全文読まない。定義・主題節の outline・受信箱の末尾だけで積み増しできる。
```bash
python3 scripts/wiki-excerpt.py "wiki/concepts/異常検知.md" \
--sections 定義,子概念,未解決の問い,未編纂の観察 --tail 15 --budget-tokens 1800
# 主題節の地図(## / ### 見出しと、各箇条書きの太字命題行だけ)
python3 scripts/wiki-excerpt.py "wiki/concepts/異常検知.md" --outline
```
- concept の既定節: `定義` / `子概念` / `未解決の問い` / `未編纂の観察`(旧名 `横断的知見` は別名解決され、どちらの名前で指定しても実在する方が返る)
- `未編纂の観察`(旧名含む)と `未解決の問い` は末尾 `--tail` 件だけ
- `--outline` は主題節(固定 6 節以外の `##`)の見出しと、その配下の太字命題行を 1 行ずつ返す。本文は返さない。ingest は追記の前にこれで既存命題を見て、補強・反証する観察なら冒頭に `[節名]` を付ける
- `--budget-tokens`(既定 1800)を超えたら、先に `## 定義` を先頭段落だけにし、なお足りなければ古い箇条書きを落とす。それでも超えた場合は文字数で切り、`excerpt-truncated` 行が付く
- frontmatter は title / type / aliases / address / entity_tier / related 先頭 8 / sources 先頭 8 に圧縮する
ingest の追記先は `## 未編纂の観察`(無ければ旧名 `## 横断的知見`、どちらも無ければ `## 未編纂の観察` を `## 関連` の直前に新設)と `## 未解決の問い` の末尾だけ。主題節と `## 定義` は ingest から書き換えない(conventions §8 更新ルール 2・4)。ページ全体を Edit 用に再読しない。
## カタログ更新: `wiki-catalog.py`
オーケストレータは巨大 Markdown を読まず、追記コマンドだけを呼ぶ。`wiki-catalog.py` は RMW のあいだ `wiki-lock.sh` を自分で取る(競合は同じプリミティブで待ち、取れなければ失敗する。スクリプトが無いときだけ fcntl)。カタログコマンドを `wiki-lock.sh acquire` で包まない。`wiki-lock.sh` は `WIKI_LOCK_VAULT` と `WIKI_VAULT_ROOT` のどちらも見る。
```bash
python3 scripts/wiki-catalog.py prepend-log --text "$(cat <<'EOF'
## [YYYY-MM-DD] ingest-paper | タイトル
- Source: `.raw/papers/<slug>.pdf`
- Summary: [[@YYYY__SOURCE__Title]]
- Pages created: [[A]]
- Pages updated: [[B]]
- Key insight: 一文。
EOF
)"
python3 scripts/wiki-catalog.py prepend-hot --text "$(cat <<'EOF'
## YYYY-MM-DD | ingest-paper | タイトル
- Focus: [[@YYYY__SOURCE__Title]]。要点。
- Key insight: 一文。
- New: [[A]]
- Updated: [[B]]
EOF
)"
python3 scripts/wiki-catalog.py prepend-changelog \
--file wiki/concepts/_index.md --text "### YYYY-MM-DD ingest-paper | タイトル\n- 新規 concept: [[A]]\n"
python3 scripts/wiki-catalog.py add-catalog-line \
--file wiki/concepts/_index.md \
--section "現行コンセプトカタログ" \
--line "- [[A]]"
python3 scripts/wiki-catalog.py prepend-master --text "### YYYY-MM-DD ingest-paper | タイトル\n- Source: [[@...]]\n"
```
`prepend-hot` はトークン予算(既定 2000、最大 5 エントリ)で古い窓を落とす。落ちたエントリは `wiki/log.md` に残っている前提であり、`trim-hot` 単体は log へコピーしない。ingest は必ず `prepend-log` してから `prepend-hot` する。
## query の経路
1. **クイック**: trim 済み `wiki/hot.md` だけ。足りなければ「standard で retrieve する」と返す。`index.md` は開かない。
2. **標準 / 深掘り**: `python3 scripts/retrieve.py "<質問>" --top 5` を第一経路にする。ヒットしたページは `wiki-excerpt.py` で読む。concept のハブなら子概念を 1〜2 枚足す。
3. retrieve が exit 10 のときだけ、`wiki-resolve.py` と `rg` でフォールバックする。`index.md` 全文はフォールバックにも使わない。
## ingest の作業集合
1. `wiki-resolve.py` で既存 entity / concept を特定する。
2. 更新するページだけ `wiki-excerpt.py` する。
3. **1 回の取り込みで concept は新規最大 3、更新最大 5。** 溢れた候補は log の `Deferred:` に残し、次の関連ソースで育てる。
4. ハブ concept(おおよそ 50KB 超、または `## 子概念` があるページ)は地図として扱い、具体的な知見は最も近い子へ書く。ハブへは子へのリンクと、子をまたぐ観察だけを足す。
4a. 追記先は `## 未編纂の観察`(旧名 `## 横断的知見`)と `## 未解決の問い` の末尾だけ。主題節は再編纂(`wiki-refactor`)の領分で、ingest は触らない。
5. 書き終わったら `wiki-catalog.py` で log / hot / changelog / catalog line を更新する。
6. 作成・更新したページを `python3 scripts/wiki-retrieve-refresh.py --pages <path> ... --no-llm` に渡し、BM25 を差分更新する。
## entity の stub / full
- `entity_tier: stub` — 初出の共著者・一度きりの言及。frontmatter + 所属/役割 2〜3 行。本文を育てない。
- `entity_tier: full` — 2 ソース目以降、またはハブ(書籍・thesis・繰り返し登場する人/組織/製品)。
- 既存の本文ページは `full` とみなす。`entity_tier` が無いページを遡及改名しない。
- stub の存在確認も `wiki-resolve.py` で行う。本文は excerpt せず frontmatter だけで更新してよい。
## concept の新設閾値
- 単一ソースで閉じる用語は source ページに留める。
- concept を新設するのは、(a) 既存 concept が resolve でヒットしない、かつ (b) 2 ソース以上にまたがるか、今後またがることが明らかなハブ、のときだけ。
- 書籍・thesis で章ごとに用語が出ても、章のたびに新設しない。文書全体で 3 件までを目安にし、残りはハブ entity と章 source に置く。
## 子概念(親子化)
厚い concept は親を地図、子を論題にする。分割の単位は行数ではなく主題の独立性(conventions §7)。
- 親: `## 定義` と `## 子概念` と、子をまたぐ主題節だけを厚くする。
- 子: 独立した手法・層・評価設計などを切り出す。
- 既存の厚い親を今すぐ全部分割しなくてよい。ingest 時は excerpt で親を薄く読み、stats が示すハブは子へ寄せる。
- 機械的な子候補の確認: `python3 scripts/wiki-concept-stats.py --json`。related 先がハブ規模ならピア(子にしない)。相互参照は問わない。
## 再編纂(recompile)の負債
受信箱に観察が溜まったページは、読者が自分で合成し直さないと全体像が得られない。compile 負債の列挙は次で行う。
```bash
python3 scripts/wiki-concept-stats.py --compile-debt # 受信箱 5 件以上、または主題節なしで 15 件以上
python3 scripts/wiki-concept-stats.py --compile-debt --min-inbox 10
```
出力の各行は `path` / `inbox_bullets`(受信箱の箇条書き数、旧名含む)/ `topic_sections`(固定 6 節以外の `##` の数)/ `legacy_heading`(旧名 `## 横断的知見` が残っているか)を持つ。再編纂は `wiki-refactor` の再編纂様式で 1 ページずつ行い、ingest と混ぜない。
## retrieve の鮮度
`.vault-meta/bm25` が取り込みより古いと、query が外れる。ingest の最後に必ず `wiki-retrieve-refresh.py --pages` を呼ぶ。prefix か BM25 が非 0 なら refresh も非 0。全件作り直しは `wiki-retrieve-refresh.py --all`(wiki 全ページを歩くので、日常の ingest では使わない)。子プロセスは `WIKI_VAULT_ROOT` を見る。