Repository navigation
v0.22.1
[0.22.1] - 2026-08-07
Highlight: Skill 一覧が古いバージョンをインストール済みとして表示し、そこからの更新が必ず失敗する問題を修正する(#1753)。一覧 API は索引を、更新 API は receipt を真実として読んでおり、両者が食い違うと UI は旧版を出して更新導線を描き、押すと
SKILL_UPDATE_VERSION_NOT_ELIGIBLEを返していた。エラー文言は利用者に「もう最新です」としか読めず、UI に索引を作り直す導線も無いため、一度ずれると回復できない状態だった。読み取り時の索引修復を「欠落行の復元」から「receipt と食い違う行の収束」まで広げ、コストは索引が既に持つreceipt_sha256との比較で抑えている(一致する行は parse も書き込みもしない)。このリリースを適用すると、ずれている索引は一覧を開いた時点で自動的に直る。
Fixed
- skills: 一覧 API が古い版を表示し、そこからの更新が必ず
SKILL_UPDATE_VERSION_NOT_ELIGIBLEで失敗する問題を修正 (#1753)GET /api/worktrees/[id]/skillsは応答前にrestoreSkillInstallationIndexで索引を修復するが、これはgapsOnly: trueで呼ばれており、行が既にある skill を receipt を読む前に除外していた。したがって「行が無い」(#1709 が対象にしたもの)は直るが、「行はあるが version が古い」は構造的に永久に直らない。一方update-planは receipt を正とするため、同一サーバ内で一覧 API は索引を、更新 API は receipt を真実としていた。実機では 3 件(cmate-task-contract0.1.0/0.2.2、cmate-verify-advisor0.1.1/0.2.0、cmate-verify0.3.1/0.4.2)が不一致で、その 3 件だけが更新に失敗していた。利用者にはエラー文言が「もう最新です」としか読めないため表示が古いことに気づけず、UI に rebuild の導線も無い- 読み取りが行を挿入はするのに更新はしない非対称に独立した根拠が無いため、read-through を「欠落行の復元」から「receipt と食い違う行の収束」まで広げた。
pruneは #1709 の判断どおり維持する(行を消すことはドリフトを隠すが、古い版を新しい版に直すことは何も隠さない) - コスト設計: 索引行が既に持つ
receipt_sha256と、disk の receipt bytes の sha256 を比較する。一致する行は parse も DB 書き込みもしない(呼び出し回数で検証済み)。receipt 全体の digest なので version だけでなく source commit・artifact digest・root 集合(#1460)の drift も同じ 1 回の検査で捕まる。mtime/size を索引に持つ案は migration が必要なうえ、実機の 3 件は receipt mtime が同一秒だったため証拠として弱い。TTL 間引きは「一覧を開いて更新を押す」という失敗する操作が窓の内側で完結するため却下した - 実測(macOS /
:memory:DB / 実 receipt 相当 3KB / 両 root、200 回平均): 修正前 N=110.103ms→ 修正後0.367ms、N=100(scanner の worktree 毎上限)0.425ms→2.452ms。全件 parse+write に倒した場合は N=110.737ms/ N=1006.087msなので digest gate はその約 1/2.5。導入数 1 件あたり約 32µs で、既に dashboard のstatus-scannerが導入済み全ファイルを hash していることを踏まえれば読み取り経路の追加として妥当 update-planの判定ロジックは変更なし(receipt を正とするのは正しい)。エラーメッセージのみ、要求された版と receipt が記録している版の両方を示すようにした- #1709 が現行挙動を pin していた 2 件のテストは削除せず期待値を反転して残した(
reindex.test.tsのleaves a row the index already has exactly as it was→converges a row the index has onto the receipt that disagrees with it、worktree-skills-readthrough.test.tsのkeeps serving the indexed version when the receipt on disk disagrees→serves the version on disk when the receipt disagrees with the index)。何を決め、なぜ覆したかを本文に残してある。pruneを固定するテスト(never deletes an index row whose payload is absent等)は緑のまま - 回帰テスト:
tests/unit/lib/skills/reindex-drift-cost.test.ts(新規、一致行の parse/upsert 呼び出し回数が 0 であること・drift/欠落は 1 回だけであること)、tests/integration/skills-update.test.tsに実 route での再現(install → update → 行だけ巻き戻すことで実機の「索引=旧版 / disk=新版」を再現 → 一覧が receipt の版を返しhasSkillUpdateが false になる) - 変異注入で非空振りを証明済み(2026-08-07): digest 比較を
indexedDigest !== nullに戻す(=#1709 の「行があれば正しい」仮定)と unit 6 件+integration 1 件が赤。エラーメッセージを旧文言に戻すと integration 1 件が赤 npm run test:unitexit 0(817 files / 14948 tests)、npm run test:integrationexit 0(79 files / 1135 passed・2 skipped)をcmd > log; echo $?で実測