背景

Issue 棚卸しの「一部解決」A群(engine 済→統合)・C群(局所 UI 修正)を並列実装し、 6 件を PR 化した(issue-backlog-audit-2026-10-12より)。全 PR とも CI green・各 issue に self-assign・REVIEW_REQUIRED で BLOCKED。人間レビュー前の品質確認として、 6 本を 6 並列 subagent で correctness 中心に code-review した(2026-10-12)。

対象 PR(すべて codex/issue-* → main):

PR対応 Issue概要領域
#982#143クラスタ品質評価ハーネスの再現可能化(--skip-llm・決定論サンプル同梱)experiments(隔離)
#983#52散布図のクラスタ選択を連動文章・一覧へ反映public-viewer
#984#450埋め込みモデルをチャットと独立に選択admin + api
#985#537OpenRouter 無料モデル向け structured outputs fallbackanalysis-core(隔離)
#986#306散布図の操作ヒント+濃い意見グループの帰属可視化public-viewer
#987#176作成 UI から LLM 直接グルーピングを選択admin + api

レビュー結果(verdict)

PR判定要点
#982READY-WITH-NITS--skip-llm の end-to-end を実行で確認、決定論再現も実証。shell 補間の非 quote(空白パスで破綻)等 polish のみ
#983READY-WITH-NITS件数・機構は正、回帰なし。再クリック no-op/mode 切替で stale selection 自動スクロール等 minor UX
#984READY-WITH-NITSkind 後方互換・指定時のみ注入・local/Azure 無効化・duplicate 継承すべて PASS。submit gate が embeddingModelError 未参照(stale 値がジョブ起動後に RuntimeError)が最重 minor
#985READY-WITH-NITSfallback chain 正。BadRequestError 一律誤分類は follow-up。temperature=0 の reasoning-guard 欠落は既存バグで別 issue
#986READY-WITH-NITSparent-chain の cycle guard 正・凡例色は plot と同一 Map 参照・grey-out パリティ証明済み。jest.config.js の lucide-react パス直書きが依存 bump でsuite 全崩れうる nit が最重
#987BLOCKER-FOUND → 追修正済み作成+初回実行は OK だが、編集→再集計で crash(下記)。commit 8a09108e で修正し PR ブランチへ push

5/6 は nits のみで merge 可。blocker は #987 のみ。approve / merge は人間判断のため、 レビューでは何も approve / merge / コメント投稿していない。

#987 の BLOCKER と追修正(commit 8a09108e)

症状

analysis_mode=llm_grouping のレポートを作成後、admin の設定編集(タイトル/問い変更) → 再集計で TypeError: 'NoneType' object is not iterable により集計が error status。 hierarchical レポートは無影響。

原因(なぜ既存の prune では防げなかったか)

PR #987 は ReportConfig に hierarchical 専用・llm_grouping 専用セクションを 両方 known field として持たせ、prune_config_for_mode(commit 73756f21)で mode 非該当セクションを落とす設計を入れた。しかし prune は 「非該当 mode の セクション」だけを削除し、active mode 自身の None/空セクションを残していた。

  • 作成フローの _build_config は llm_grouping セクションを省略して analysis-core の 既定に委ねる → 初回 config file には llm_grouping キーが無く、初回実行は validate OK。
  • 編集で ConfigRepository.update_json が read_from_json().model_dump() を通すと、 ReportConfig.llm_grouping の既定 None が顕在化して llm_grouping: null を書き戻す。
  • prune(mode=llm_grouping)は hierarchical 専用セクションのみ落とし llm_grouping: null を残す。
  • analysis-core の core/orchestration.py:273 for key in config.get(step_spec["step"], {}) は、llm_grouping が step 名に含まれ値が present-but-None のため、 config.get("llm_grouping", {}) が {} ではなく None を返し → for key in None で TypeError。

修正

prune で active mode 自身のセクションも値が None/空なら削除し、analysis-core の 既定に委ねる挙動へ戻す(値入りセクションは温存)。値入り判定で keep/drop を分ける ため ALL_MODE_SECTIONS を導入。

回帰テスト(この gap が blocker を見逃した原因=「llm_grouping レポートの None round-trip」が未カバーだった):

  • prune の unit(llm_grouping / hierarchical 両方の null・空 active セクション削除、 値入りは温存)
  • llm_grouping レポートの update_json round-trip が llm_grouping: null を 書き戻さず、実 analysis_core.validate_config を通過すること

検証(before/after)

隔離 venv で analysis-core を editable install し確認:

  • 旧 prune → validate_config が TypeError(blocker 再現)
  • 新 prune → llm_grouping キーを drop → validate_config 通過
  • test_analysis_config.py 13 passed、prune consumer 系(launcher/duplicate)31 passed、 ruff check / format クリーン

follow-up 候補(本 PR 外): analysis-core 側 core/orchestration.py:273 を config.get(step, {}) or {} にすれば、present-but-None セクション全般に対する 防御が analysis-core 単体でも効く。今回は PR スコープを API 境界の prune に限定した。

merge 順(衝突最小化)

第1波(独立・そのまま merge 可)
  #982 experiments 隔離 / #985 analysis-core 隔離

第2波・ペアB(散布図)── 直列
  #983 を先 merge → #986 を rebase → CI → merge
    ⚠ #983 が非選択クラスタを dim する場合、#986 の凡例 swatch(full-saturation) と
       plot 色の parity を rebase 後に再確認

第2波・ペアA(admin+api)── 直列
  #984 を先 merge → #987 は追修正済みを rebase → CI → merge
    ⚠ `embedding` は**両 mode の step**。#987 が hierarchical セクションを else 枝へ
       寄せたため、#984 の embedding config は**共通枝**に置く(else 枝に入れると
       llm_grouping で埋め込みモデル設定が消える)

ペア内は必ず「片方 merge → 相方 rebase → CI → merge」の直列。並行 merge で auto-merge 不能な衝突(ScatterChart.tsx の customdata/onClick、admin create の共有 8 ファイル) を踏まないため。

Open Questions / follow-up

  • #985: BadRequestError の一律 fallback を code/message で絞るか(無関係 400 で無駄呼び+誤ログ)
  • #985 / analysis-core: OpenRouter 経由 reasoning モデルの temperature=0 guard 欠落(既存・別 issue 化候補)
  • #986: jest.config.js の lucide-react パス直書きを依存 bump 耐性のある形へ
  • #984: submit gate に embeddingModelError を通す(chat model と非対称)
  • #987: cluster 数設定 UI が llm_grouping 時に効かないのに表示される(nit)
  • 全 PR とも approve / merge は人間判断。本ラウンドでは実施していない

Updates

2026-10-12 — #987 を admin squash merge

  • nishio 指示で PR #987 を admin squash merge(merge commit c33d264a、CI 10 green を確認後)。REVIEW_REQUIRED を admin でバイパス。repo 慣例の squash 方式(#981=single-parent を踏襲)。
  • Issue #176 は OPEN のまま:PR 本文に closing keyword が無く「一部解決」スライドのため自動 close せず。残スコープの完了判断は未実施(related PR merge だけで close しない方針)。
  • knock-on: #987 先行 merge により #984 が rebase 必要化(base 更新で mergeable 再計算)。rebase 時は embedding config を共通枝に維持(本文 merge 順の注意点どおり)。
  • まだ open の残り 4 PR = #982 / #983 / #984 / #985(#986 は #983 の後に rebase)。

2026-10-13 — 独立 PR #982 / #985 を admin squash merge

  • nishio 指示で第1波(独立 PR)を merge:#982(9410636)/ #985(490a0bd3)。いずれも MERGEABLE・CI green(?=CodeRabbit は非必須)を確認後、admin squash。
  • 関連 Issue #143(#982)/ #537(#985)は OPEN のまま(closing keyword なし=「一部解決」スライド。完了判断は別途)。
  • 残 open = #983 / #984 / #986。次はペアB(#983→#986)とペアA(#984 の #987 への rebase→merge)。

2026-10-13 — #983 merge・#984 を rebase

  • nishio 指示で #983(#52 散布図選択連動)を admin squash merge(88c592a1)。
  • #984 を main(#987 込み)へ rebase(28dd78f1 を --force-with-lease で push、MERGEABLE・CI 実行中)。
    • 衝突は 2 箇所のみ=useAISettings.ts(DEFAULT_ANALYSIS_MODE/handleAnalysisModeChange と isEmbeddingRow/handleEmbeddingModelChange の both-added)と test_report_launcher.py(mode 系テストと embedding 系テストの both-added)。いずれも両採用。
    • semantic trap 回避を実証: report_launcher.py の auto-merge で embedding config が mode 分岐前の共通 config に残り、analysis_mode=llm_grouping + embedding_model でも embedding が温存される(hierarchical_clustering 不在・llm_grouping: null 漏れなし)。隔離 venv の実 validate_config(llm_grouping) 通過・API 56 tests pass で確認。admin tsc/jest は CI で確認。
  • 関連 Issue #52 は OPEN のまま(closing keyword なし)。残 open PR = #984(rebase 済・CI 待ち)/ #986。

2026-10-13 — #975 / #984 を admin squash merge(残 open は #986 のみ)

  • nishio 指示で CI 全 green の2件を admin squash merge:#975(意見ごとの侮辱・ヘイト/政策提案ラベルの任意ステップ, analysis-core, 1e7fdae5)/ #984(#450 埋め込みモデルをチャットと独立に選択, 8f402d2e)。
  • 通常 merge は ruleset(必須承認1)で base branch policy prohibits the merge となるため admin override。classifier が AI 自身の --admin を阻むので人間が ! で実行。
  • #984 は前日 rebase 済み(28dd78f1)のまま green を維持して merge 完了。embedding 共通枝温存は維持。
  • 残 open PR = #986(#306 散布図, DIRTY/CONFLICTING=要 rebase)/ #891(Windows スタンドアロン・Draft)。マージ可能なものは今回で払い出し完了。

2026-10-13 — #986 を main へ rebase(CONFLICTING 解消)

  • nishio 指示で #986(#306 散布図の操作ヒント+濃い意見グループ帰属可視化)を現 main へ rebase。専用 worktree work/kouchou-ai-986(rebase/986-scatter)で作業、本体 HEAD は main 維持(二分原則)。
  • 衝突は 2 ファイルの both-added プロップ追加のみ=ScatterChart.tsx(型定義+分割代入で onSelectCluster〈#983 由来・main 側〉と showParentAttribution〈#306〉)と scatter.tsx(<ScatterChart> への両プロップ受け渡し)。両採用。両プロップとも本体で参照済み(onSelectCluster: L639 クリック通知 / showParentAttribution: L144,163 凡例ロジック)を確認。
  • ローカル検証 green: biome lint・tsc --noEmit(exit 0)・jest ScatterChartLegend.test.tsx(3 pass)。65c2a264 を --force-with-lease で push。
  • 結果 MERGEABLE に回復(BLOCKED は必須チェック実行中+承認待ち)。残 open PR = #986(rebase 済・CI 待ち)/ #891(Draft)。

2026-10-13 — #986 を admin squash merge(ラウンド全消化)

  • rebase 後 CI 全 9 チェック green を確認し、nishio 指示で #986 を admin squash merge(merge commit c1c9e917)。通常 merge は ruleset(必須承認1)で拒否のため admin override(人間が ! 実行)。
  • Issue #306 は OPEN のまま:PR はサブ改善(操作ヒント+帰属可視化)でタイトルに closing keyword なし=一部解決。related PR merge だけで close しない方針を踏襲。
  • 後片付け: rebase worktree work/kouchou-ai-986(rebase/986-scatter)削除、ついでに merge 済み #984 の stale worktree work/kouchou-ai-984 も削除。本体 HEAD は main。
  • #982–987 ラウンドの PR はこれで全て merge 完了(#982/#983/#984/#985/#986/#987 + 独立の #975)。残 open PR は #891(Windows スタンドアロン・Draft・2026-06 停滞)のみ。

Issue 完了・close 判断の棚卸し(2026-10-13)

6 PR 全 merge 後、対応 6 Issue を issue-backlog-audit-2026-10-12 / issue-backlog-audit-2026-10-11 の「合意要件・残件」baseline(PR 投入前の main 照合)× 本ラウンドの実コードレビュー結果(= 必要な動作確認)× 現 main c1c9e917 の再確認 で照合した。各 Issue は OPEN のまま(PR 本文に closing keyword なし=一部解決スライス)。本棚卸しでは close 実行・Issue 本文編集はしていない(close は人間 attention 判断として nishio 確認待ち)。

IssuePRbaseline 残件PR で達成(確認根拠)現状の残り判定
#176 LLM ベース分類#987プロダクト統合未達(admin UI・API が analysis_mode 非受理、engine は実装済)作成 UI から LLM grouping 選択+API が analysis_mode 受理、編集→再集計 BLOCKER も修正(レビューで end-to-end 確認)nit: cluster 数設定 UI が llm_grouping 時に no-op なのに表示close 推奨(engine+統合とも充足、nit は別 follow-up)
#450 埋め込みモデル選択#984provider 別 embedding 独立リスト無・保存/実呼出経路無(Serverless は別スロット)専用 embedding selector(catalog.models を isEmbeddingRow で絞込・local/azure で無効化)+保存+_build_config で実注入(現 main AISettingsSection.tsx:241-248 / useAISettings.ts:101 確認)Serverless 別スロット(本 Issue 主目的外)、submit gate が embeddingModelError 未参照(nit)close 推奨(主要残件充足。Serverless は別 Issue 化)
#143 クラスタ品質自動評価#982同梱 dataset/harness 無・検証結果未コミット・プロダクト統合無(意図的に後段)--skip-llm+決定論サンプル同梱で再現可能化(レビューで end-to-end 実行・決定論再現を実証)評価結果の継続蓄積・プロダクト統合は意図的に後段スコープclose 推奨(本 Issue の主眼=再現可能化は達成。後段は別管理)
#52 チャート連動文章(散布図)#983散布図のクラスタ選択連動が未実装(treemap 連動は #927 済)散布図選択を連動文章・一覧へ反映(onSelectCluster→ClientContainer→ClusterBreadcrumb、scatterLink.test 確認)散布図一覧の「属性絞込件数」反映は要確認、再クリック no-op 等 minor UXclose 候補(主目的の選択連動は達成。件数反映の残要否を確認後 close)
#306 全体図/濃い意見グループ UI#986操作ヒントアイコン未確認・濃いグループ→親クラスタ帰属可視化未確認・wishlist 完了基準曖昧操作ヒント+帰属可視化の両方を実装(凡例色 parity 証明済)wishlist 他項目の有無が未棚卸し(元 Issue が複数要望の束)close 候補(特定された 2 残件は達成。残 wishlist を棚卸し後に close 可否判断)
#537 OpenRouter 無料モデル#985無料モデルで実際に動く(エラー解消)の実検証が未・parse 経路の fallback 未structured outputs fallback 機構を実装(fallback chain をレビューで確認)「無料モデルが実際に動く」最終確認が核心残件で未達(実モデル検証は #912/#913 へ分離)。BadRequestError 一律 fallback 絞込・temperature=0 reasoning-guard(既存バグ)も残OPEN 維持推奨(機構は入ったが Issue の主眼=実動作確認が #912/#913 依存で未完)

判断の要約

  • close 推奨 3 件: #176 / #450 / #143(合意要件の主眼を満たし、残りは nit・別スコープ・意図的後段)。
  • close 候補 2 件: #52 / #306(主目的は達成だが、小残件〈一覧件数 / wishlist 残〉の要否確認後に close)。
  • OPEN 維持 1 件: #537(実無料モデルでの動作確認という核心が #912/#913 依存で未完)。
  • close する場合は CLAUDE.md の方針どおり、各 Issue 本文に日付つきで「満たした範囲/残件/統合先」を追記してから close し、残件(Serverless embedding スロット・散布図一覧件数・#306 wishlist 残・#537 実検証)は存続 Issue(#912/#913 等)または新 Issue へ引き継ぐ。

2026-10-13 — close 推奨 3 件を実行(本文追記つき)

  • nishio 指示(選択肢 A)で #176 / #450 / #143 を completed で close(closedAt 2026-10-13T03:57Z)。書込直前に updatedAt 照合(3 件とも 2026-10-12 から不変)→ 元本文を保持したまま各末尾に「2026-10-13 完了判断(満たした範囲/残件)」を追記 → close コメントを添えて close。
  • 本文確認で判明したスコープ補正(棚卸し表からの追加発見):
    • #176: 提案は 2 方向あり、#987 が満たすのは (1)「分類トピックを LLM で自動構築(= LLM 直接グルーピング)」のみ。(2)「人間が付与した所与トピックへ分類(predetermined topics)」は #987 対象外で未実装。残件として本文に明記し、必要なら別 Issue 化とした(nishio は A=新 Issue を今は立てない選択)。
    • #450: Serverless 別スロット・実モデル動作確認フラグは Issue 本文(2026-09-09 追記)自体が「分ける」としていた残件。本文に再掲。
    • #143: Issue 本文の進め方自体が「実験スクリプトの再現可能化」を主眼とし製品統合は別 Issue 想定。#982 で主眼達成として close。
  • #52 / #306 は close 候補のまま保留(小残件の要否確認後)、#537 は OPEN 維持(無料モデル実動作が #912/#913 依存で未完)。

2026-10-13 — #52 / #306 の小残件を現 main c1c9e917 で確認 → 両件とも close 可

nishio 指示で保留 2 件の小残件を実コード照合。両件とも完了基準を満たし、material な残件なし(棚卸し表の残件懸念は PR 投入前 main 基準で、#983/#986 が既に充足していた)。

  • #52(#983): Issue 本文 2026-09-09 の完了基準4点を現 main で確認。
    • 「属性で絞った件数」✅ ClientContainer.tsx:111-120 の clusterCounts(filtered-aware、treemap と同じ数え方)→ :268 count prop → ClusterOverview.tsx:66 が「N件(フィルター後)」表示。audit が未反映としていたが #983 で充足済み。
    • 「全体/詳細/濃い意見の説明の連動」✅ scatter 各モードで clustersToDisplay 再計算 → ClusterOverview(takeaway 表示)。選択で isSelected ハイライト+該当項目へ smooth scroll(ClusterOverview.tsx:23-27)。
    • 「階層移動と戻る」✅ scatter 一覧 → treemap ナビ(:271-277)+ treemap breadcrumb(#927)。
    • 「0件からの復帰」✅ getDenseClusters の isEmpty で dense-group 空時は scatterDensity モードを無効化(:98-99,:244)=空ビューに入らせず復帰自動。
    • 残る nit はレビュー既知の UX polish のみ=再クリックで選択解除されない(no-op)/mode 切替時に stale 選択へ auto-scroll しうる(useEffect([isSelected]) が再マウントで発火)。いずれも非ブロッキング。Serverless(#25)・元コメント表示(#56/#250)は本文どおり明示的に対象外。
  • #306(#986): 本文 wishlist 5 項目すべて現 main で対応。
    • ①ヒントアイコン ✅ ScatterChart.tsx:602-631(ドラッグ=パン / ホイール=拡縮 / 右上ツールバー=初期表示へリセット=背景のダブルクリック戻し相当 / hover=内容 / click=ソース)。
    • ②ズーム in/out ✅ displayModeBar:'hover'+scrollZoom:true(plotly ツールバー+ホイール)。③移動ボタン ✅ dragmode:'pan' で「ドラッグでパンできない」前提自体を解消(専用ボタン不要)。④表示モード/全画面で状態保持 ✅ uirevision:'scatter'(#290 相当)。⑤濃いグループの帰属 ✅ showParentAttribution で第一階層クラスタへの帰属を色/凡例可視化(parent チェーン解決・凡例色 parity 証明済)。

結論: #52 / #306 とも close 推奨へ格上げ可。close 時は #176/#450/#143 と同様に「満たした範囲/残 nit(#52 の UX polish)/対象外(Serverless・元コメント)」を本文追記してから close する。

2026-10-13 — #52 / #306 を本文追記つき close(棚卸し完了)

  • nishio 指示で #52 / #306 を completed close(closedAt 2026-10-13T04:29Z)。updatedAt 照合(不変)→ 元本文保持のまま「2026-10-13 完了判断(満たした範囲/残件)」追記 → close コメント添付。
  • #52 は残 nit(再クリック no-op・mode 切替 auto-scroll)と対象外(Serverless #25・元コメント #56/#250)を本文明記。#306 は wishlist 5 項目充足を明記。
  • #982–987 対応 6 Issue の完了判断はこれで収束: close 5(#176/#450/#143/#52/#306)/ OPEN 維持 1(#537=無料モデル実動作が #912/#913 依存で未完)。#176 方向性(2)〈所与トピック分類〉・#450 Serverless embedding スロット等の残件は各本文に記録済み(必要時に別 Issue 化)。

2026-10-13 — #537 残件を #991 へ移管して close(6 Issue 全消化)

  • 移管先確認で #912/#913 は GPT-5.6 / Gemini 3.8 専用の実 API 検証 Issue(#906/#909 カタログ検証シリーズの model 別 sibling)で、OpenRouter とは無関係と判明([TEST] 全検索でも OpenRouter 用は不在)。audit の「#537 は #912/#913 依存」は不正確だった(訂正)。
  • nishio 判断で #912/#913 と同テンプレの新 [TEST] Issue #991「OpenRouter無料モデルの実API動作を検証する」を作成し、#537 残件を移管(fallback 経路の実動作確認・BadRequestError 絞り込み・temperature=0 guard・学習オプトアウト前提)。実 API 検証は人間実施方針を踏襲。
  • #537 を completed close(fallback 機構は #985 で実装済、実検証は #991 へ)。本文に移管内容を追記。
  • これで #982–987 対応 6 Issue はすべて close(#176/#450/#143/#52/#306/#537)。派生 open = #991(実 API 検証・人間実施)、および既存の #912/#913。

2026-10-13 — #176 方向性(2) を #994 として起票(CLI-first・「その他」必須)

  • #176 の残件として本文記録していた**方向性(2)「人間が付与した所与トピックへの意見分類(supervised / 指定カテゴリ分類)」**を、nishio 指示で新 Issue #994 化。#176 に breadcrumb コメントを追加。
  • 設計の要点(nishio 入力): (a) engine は steps/llm_grouping.py の _discover_groups をスキップし固定枠を _assign_groups へ渡す拡張、(b) 「その他(Other)」カテゴリを必ずシステム側で自動付与(指定枠に当てはまらない意見の押し込みを防ぎ、枠の過不足のシグナルにする。シン東京2050 等で所与枠分類の需要あり)、(c) 広聴AI の niche 機能方針どおり CLI/core 先行で、WebUI 統合は動作確認後に別 Issue。
  • 「その他」必須は設計原則として memory 化(supervised-classification-other-bucket)。

2026-10-13 — #994 を CLI 実装(PR #996・進行中)

  • nishio 指示で #994 に self-assign し実装着手。analysis-core の LLM grouping に supervised モードを追加(llm_grouping.predefined_groups 指定で _discover_groups をスキップ、割当のみ LLM)。
  • 「その他」受け皿を必ず自動付与(_ensure_other_group)。非該当意見・無効応答を先頭枠でなく「その他」へ寄せ、割当プロンプトにも誘導。other_group_label でラベル変更可・既存同等枠は流用。
  • 配線: specs/llm_grouping_specs.json に predefined_groups/other_group_label 追加(validate_config 通過)、plugin はフル config から読む(list 値の workflow 補間回避)、docs/user-guide/cli-quickstart.md に利用例追記。
  • 検証: analysis-core 325 passed(新規 4 ケース)・ruff clean・CLI validate-config/dry-run で受理確認。実 LLM フル実行はプロバイダ鍵要のため未実施。WebUI は別 Issue。
  • 2026-10-13 nishio 指示で PR #996 を admin squash merge(42bba8ea・single-parent 確認)。必要 CI 全 green(Pytest/Ruff/test/build/Analyze×4/CodeQL)後に承認待ち(BLOCKED)を admin override。worktree/branch 撤去、本体 HEAD は main。#994 に満たした範囲/残件(WebUI 統合・実 LLM フル検証)を記録。Issue #994 は OPEN 維持(CLI スコープ達成・WebUI 別 Issue 化は nishio 判断待ち)。
  • 機能の正本(設計・使い方・「その他」必須の理由)は concept ページ llm-grouping-supervised-classification に切り出した。