目的
索引が repo の issue / PR 履歴を網羅していない状態を埋める。設計意図は「過去履歴も含めすべて入れる」だが、実測では半分に届いていない。あわせて、欠落が積み上がる経路を閉じる。
確認済みの事実(2026-08-03 実測)
Liplus-Project/liplus-language:
種別
実物
索引済み(distinct number)
被覆率
issue
919(open 33 / closed 886)
419
46%
pull_request
698(すべて closed)
313
45%
番号帯別の分布(200 刻み、issue / PR):
0-199 : 15 / 13
200-399 : 7 / 3
400-599 : 26 / 17
600-799 : 24 / 10
800-999 : 22 / 19
1000-1199: 78 / 61
1200-1399: 111 / 89
1400-1599: 107 / 90
1600- : 29 / 11
古い番号帯ほど薄い。ただし連続した範囲が丸ごと欠けているのではなく散発 で、#600〜#630 を実物と全件突き合わせた結果は 31 番中 23 件が索引済み、欠落は #600 #601 #602 #603 #605 #606 #608 #626 の 8 件だった。
索引対象 repo 全件の被覆率(2026-08-03 実測。実数 = GitHub の全 state 件数、索引 = search_docs の distinct number):
repo
issue 実 / 索引
PR 実 / 索引
欠落計
Liplus-Project/liplus-language
919 / 419 (46%)
698 / 313 (45%)
885
Liplus-Project/github-webhook-mcp
135 / 58 (43%)
113 / 47 (42%)
143
Liplus-Project/github-rag-mcp
105 / 56 (53%)
106 / 40 (38%)
115
Liplus-Project/liplus-desktop
41 / 9 (22%)
48 / 9 (19%)
71
Liplus-Project/dipper_ai
34 / 25 (74%)
29 / 13 (45%)
25
Liplus-Project/neuron-graph-rag
13 / 13 (100%)
12 / 12 (100%)
0
計
1239
neuron-graph-rag だけが 100%。 件数が 1 run の embedding 上限に収まる規模で、上限に一度も当たっていない repo が唯一取りこぼしゼロという分布は、下記の原因と一致する。
原因(2026-08-03 実装から確定)
embedding budget を使い切った残りが、二度と取りに行かれない。
src/poller.ts processIssues は 1 run あたり MAX_EMBEDDINGS_PER_RUN = 50 件しか embed しない。上限に達した以降の item は bodyHash: "" の record を IssueStore へ書くだけで、search_docs への行は書かれない (FTS mirror は embedding 成功後の経路にしか無い)。設計意図は「空 hash を retry の印にして次 run で拾う」。
ところが pollRepo は watermark を無条件に前進させる:
capped = false → watermark = pollStartTime
capped = true → watermark = 最後に fetch した issue の updated_at
fetch は sort=updated&direction=asc なので、embed されずに残るのは常に「その batch の中で updated_at が最も新しい側」。どちらの分岐でも watermark はその残りの手前ではなく先に進むため、次 run の since 条件から漏れる。retry の印は付くが、印の付いた item が二度と fetch されない。 embed 自体が失敗した item(failed)も同じ経路で落ちる。
規模も噛み合う: MAX_PAGES_PER_RUN = 2 × PER_PAGE = 100 で 1 run 最大 200 件 fetch に対し embed は 50 件。初回同期時は 1 run ごとに 150 件が落ちる勘定になる。番号順ではなく updated_at 順に落ちるため、欠落は番号帯で見ると散発に見える(実測の分布と一致)。
同型の欠陥が既に別 surface で修正されている。 #179 「hold diff watermarks at the first uningested commit」(a7e7d54)が commit-diff 側で同じ構造を潰しており、DIFF_RETRY_BOUNDARY_BACKOFF_MS を含む pin の型が repo 内に既に在る。issue / PR surface へは適用されていなかった。
doc surface は break して blobSha 比較で拾い直すため同じ穴は無い(watermark 依存でないため)。release surface には別種の小さな穴があり、そちらは #211 に分離した。
修正方針
恒久 : embedding budget 到達 / embed 失敗で見送った最初の item を「未取り込み境界」として持ち、watermark をその手前で止める。fix(poller): hold diff watermarks at the first uningested commit [poller, admin, docs, tests] #179 の pin と同型に揃える(since の inclusive 境界に対する backoff margin も同様に必要)。これにより次 run が同じ範囲を取り直し、欠落は積み上がらなくなる。
backfill(既存の欠落分) : 再 index は embedding 呼び出しを伴う。上表のとおり全 repo で 1239 件。2026-08-03 に Master の go-sign を取得済み。 恒久修正だけを先に入れても既存の欠落は埋まらない(該当 item の updated_at が動かない限り fetch 対象に入らないため)。
backfill は admin endpoint とし、repo 指定・cursor・件数上限を取る形にする。1 run で 1239 件を流すのは Worker の subrequest 予算と Workers AI の rate limit に対して無理があるため、batch を跨げる形が要件。
実行順序は恒久修正 → deploy → backfill。逆順にすると backfill 後に入る新規 item が同じ経路で再び落ちる。
制約
対象ファイル
src/poller.ts(processIssues の未取り込み境界の返却、pollRepo の watermark 決定)
src/index.ts(backfill admin endpoint。go-sign 後)
docs/0-requirements.md / docs/0-requirements.ja.md
回帰テスト
背景
#209 の実測中に副次的に判明した。検索が沈黙したとき、それが「該当が無い」なのか「索引に届いていない」なのか区別できない状態であり、memory/promotion_tally.md の instrument-unvalidated-zero-read-as-fact(0 件を事実として読む失敗)が構造的に起きうる面になっている。
目的
索引が repo の issue / PR 履歴を網羅していない状態を埋める。設計意図は「過去履歴も含めすべて入れる」だが、実測では半分に届いていない。あわせて、欠落が積み上がる経路を閉じる。
確認済みの事実(2026-08-03 実測)
Liplus-Project/liplus-language:
番号帯別の分布(200 刻み、issue / PR):
古い番号帯ほど薄い。ただし連続した範囲が丸ごと欠けているのではなく散発で、
#600〜#630を実物と全件突き合わせた結果は 31 番中 23 件が索引済み、欠落は#600#601#602#603#605#606#608#626の 8 件だった。索引対象 repo 全件の被覆率(2026-08-03 実測。実数 = GitHub の全 state 件数、索引 =
search_docsの distinct number):neuron-graph-rag だけが 100%。 件数が 1 run の embedding 上限に収まる規模で、上限に一度も当たっていない repo が唯一取りこぼしゼロという分布は、下記の原因と一致する。
原因(2026-08-03 実装から確定)
embedding budget を使い切った残りが、二度と取りに行かれない。
src/poller.tsprocessIssuesは 1 run あたりMAX_EMBEDDINGS_PER_RUN = 50件しか embed しない。上限に達した以降の item はbodyHash: ""の record を IssueStore へ書くだけで、search_docsへの行は書かれない(FTS mirror は embedding 成功後の経路にしか無い)。設計意図は「空 hash を retry の印にして次 run で拾う」。ところが
pollRepoは watermark を無条件に前進させる:capped= false → watermark =pollStartTimecapped= true → watermark = 最後に fetch した issue のupdated_atfetch は
sort=updated&direction=ascなので、embed されずに残るのは常に「その batch の中でupdated_atが最も新しい側」。どちらの分岐でも watermark はその残りの手前ではなく先に進むため、次 run のsince条件から漏れる。retry の印は付くが、印の付いた item が二度と fetch されない。 embed 自体が失敗した item(failed)も同じ経路で落ちる。規模も噛み合う:
MAX_PAGES_PER_RUN = 2×PER_PAGE = 100で 1 run 最大 200 件 fetch に対し embed は 50 件。初回同期時は 1 run ごとに 150 件が落ちる勘定になる。番号順ではなくupdated_at順に落ちるため、欠落は番号帯で見ると散発に見える(実測の分布と一致)。同型の欠陥が既に別 surface で修正されている。 #179「hold diff watermarks at the first uningested commit」(
a7e7d54)が commit-diff 側で同じ構造を潰しており、DIFF_RETRY_BOUNDARY_BACKOFF_MSを含む pin の型が repo 内に既に在る。issue / PR surface へは適用されていなかった。doc surface は
breakして blobSha 比較で拾い直すため同じ穴は無い(watermark 依存でないため)。release surface には別種の小さな穴があり、そちらは #211 に分離した。修正方針
sinceの inclusive 境界に対する backoff margin も同様に必要)。これにより次 run が同じ範囲を取り直し、欠落は積み上がらなくなる。updated_atが動かない限り fetch 対象に入らないため)。制約
#600〜#630の実測では索引済み行の state は全て正しく、欠落と state ずれは別経路であることが確認済み。対象ファイル
src/poller.ts(processIssuesの未取り込み境界の返却、pollRepoの watermark 決定)src/index.ts(backfill admin endpoint。go-sign 後)docs/0-requirements.md/docs/0-requirements.ja.md背景
#209 の実測中に副次的に判明した。検索が沈黙したとき、それが「該当が無い」なのか「索引に届いていない」なのか区別できない状態であり、
memory/promotion_tally.mdのinstrument-unvalidated-zero-read-as-fact(0 件を事実として読む失敗)が構造的に起きうる面になっている。