mcpField
C
code-review

code-review

github.com/vectorize-ioテスト・品質56.6k171 回インストール今日更新
リポジトリ

code-review について

code-review は、プロジェクトの規約に沿って変更コードを確認するレビュー用スキルです。Python、TypeScript、API設計、ミドルウェア、HTTP通信などの基準を扱います。

主な機能

  1. 01Python のスタイルと型の確認
  2. 02Pydantic モデルの要件
  3. 03TypeScript とモバイルレイアウト規則
  4. 04API とページネーションの規約
  5. 05ASGI ミドルウェアの指針
  6. 06非同期 HTTP クライアント規則

活用シーン

  1. 01マージ前の変更コードのレビュー
  2. 02APIエンドポイントの規約確認
  3. 03非同期 HTTP 通信の監査
  4. 04コントロールプレーンのスマートフォン表示評価

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/vectorize-io/hindsight --skill code-review

インストール後、Claude Code で/code-reviewを入力して読み込まれたか確認します。

詳細

コードレビュー

プロジェクトの品質基準とコーディング規約に照らして、変更されたコードをすべてレビューします。

コード標準

コードを書く前に、これらの標準を読み、内容を理解してください。以下のレビュー手順では、準拠状況を確認します。

サポート対象のインタープリター

CPython 3.11 が基準です。これは docker/standalone/Dockerfile に含まれ、.python-version の固定バージョンと uv.lock が解決するバージョンです。このパッケージは 3.11 から 3.14 をサポートしています。CI の build-api-python-versions は各バージョンをインストールしてスモークテストを実施するため、これらのインタープリターのいずれかを除外する依存関係の最低バージョンは、後回しにできる問題ではなく、互換性を損なう変更です。

Python のスタイル
  • Python 3.11 以降を使用し、型ヒントを必須とします。上記の「サポート対象のインタープリター」も参照してください
  • 全体を非同期にします(asyncpg、非同期 FastAPI)
  • リクエスト/レスポンスには Pydantic モデルを使用します
  • リントには Ruff を使用します(行の長さは 120)
  • プロジェクトのルートに Python ファイルを置かず、ディレクトリ構成を整理します
  • 複数項目を持つタプルを戻り値に使わないでください。内部関数やプライベート関数であっても同様です。必ずデータクラスまたは Pydantic モデルを使用してください。例外はありません。「値が2つだけだから」といった近道も認められません。関数が複数の値を返す場合は、それらをまとめた名前付きの型を定義してください。
Pydantic モデルによる型安全性

構造化データに生の dict 型を使わないでください。内部ヘルパーやプライベート関数を含む、すべてのコードに適用されます。dict のキーが既知の場合は、データクラスまたは Pydantic モデルにしてください。

  • 関数間で受け渡すすべてのデータ構造には、Pydantic の BaseModel を使用します
  • Pydantic の検証が不要な軽量な内部データコンテナには、@dataclass を使用します
  • 型変換(例:日時がタイムゾーン対応であることの保証)には @field_validator を追加します
  • dict.get() を使うパターンは避け、型付きモデルの属性を使用します
  • 外部データ(JSON、API レスポンス)は境界で Pydantic モデルにパースします
  • これにより、ビジネスロジックの深い箇所ではなく、パース時に型エラーを検出できます
  • dict の使用が許容されるのは、真に動的/未知のキーを扱う場合(例:任意のメタデータ、固定スキーマのない JSON ブロブ)のみです
# BAD - error-prone dict access
def process(data: dict) -> str:
    return data.get("name", "")  # No validation, silent failures

# GOOD - typed and validated
class UserData(BaseModel):
    name: str
    created_at: datetime

    @field_validator("created_at", mode="before")
    @classmethod
    def ensure_tz_aware(cls, v):
        if isinstance(v, str):
            v = datetime.fromisoformat(v.replace("Z", "+00:00"))
        if v.tzinfo is None:
            return v.replace(tzinfo=timezone.utc)
        return v

def process(data: UserData) -> str:
    return data.name  # Type-safe, validated at construction
TypeScript のスタイル
  • コントロールプレーンには Next.js App Router を使用します
  • shadcn/ui コンポーネントと Tailwind CSS を使用します
スマートフォン(幅 390px)でのコントロールプレーン

globals.css では、ページの横スクロール防止、タッチ画面でのホバー専用コントロールの表示、iOS でフォームフィールドを拡大表示させない設定がすでに適用されています。以下のチェック項目は、共通ルールでは対応できない部分です。

  • ページ内のサブタブには、独自実装のボタン列ではなく components/ui/underline-tabs.tsx を使用します。
  • フィルター、トグル、ボタンを横に並べる場合は、折り返し(flex-wrap)を使うか、列の内側でスクロールさせます(max-w-full overflow-x-auto を指定し、子要素には shrink-0 whitespace-nowrap を指定します)。
  • スマートフォン向けの代替表示(w-full sm:w-[420px]、w-[min(420px,calc(100vw-1rem))])なしに、約 340px を超える固定幅を使わないでください。横並びのペインは md 未満で縦に積み重ねます。
  • すべてのテーブルで components/ui/table.tsx を使用します。このコンポーネントはテーブルを専用の枠内でスクロール可能にし、スマートフォンでも列がつぶれないよう、最低幅を 40rem に保ちます。
  • グリッドレイアウトのフォームセクションには min-w-0 が必要です(form-layout.tsx の共通 Section には設定済みです)。横並びの入力欄は sm 未満で縦に積み重ねます。
  • 全高レイアウトには h-screen/100vh ではなく h-dvh/100dvh を使用します(そうしないと iOS では URL バーの下に画面下部が隠れます)。
コードコメント
  • 重要な技術的判断には、その選択の理由を必ずコメントしてください。「なぜこの方法なのか」と尋ねられそうな場合は、説明するコメントが必要です。
  • コメントは変更の経緯に合わせて最新の状態に保ってください。実装方針を変更する場合は、以前の方法を試した理由と変更した理由を説明するよう、コメントも更新します。コメントは、問題があった可能性の高い過去の実装を記録する役割も果たします。
  • 自明なコードにはコメントを付けないでください。「なぜそうするのか」がコードから明らかでない場合にのみコメントします。
# BAD - no context for future readers
results = await asyncio.gather(*tasks, return_exceptions=True)

# GOOD - explains the non-obvious choice
# Use return_exceptions=True to avoid cancelling sibling tasks on failure.
# Previously we used TaskGroup but it cancelled all tasks when one failed,
# causing partial writes that left orphaned entity links (see #412).
results = await asyncio.gather(*tasks, return_exceptions=True)
API レイヤーとデータアクセス
  • api/http.py(およびその他の API ルーター)ではデータベースに直接アクセスしません。 HTTP ハンドラーは、SQL を組み立てたり、acquire_with_retry / conn.fetch / conn.fetchrow / conn.execute を呼び出したり、fq_table(...) を参照したりしてはいけません。永続化とクエリはすべて MemoryEngine(エンジンレイヤー)に存在します。ハンドラーはリクエストを解析・検証し、エンジンメソッドを呼び出し、HTTP レスポンスを整形し、ドメインの結果をステータスコードにマッピングします(例: None の返却 → 404)。
  • 認証/テナンシーはハンドラーで前提とされるのではなく、各エンジンメソッドの内部で強制されます。 銀行スコープのデータに触れるすべてのエンジンメソッドは、request_context を介して認証を行う必要があります — 通常は await self._authenticate_tenant(request_context)(多くの場合、get_bank_profile(...) を介して間接的に)— これにより、クエリが実行される前に正しいテナントスキーマが解決されます。ハンドラーは request_context をエンジンメソッドまで引き渡さなければなりません。スキーマがすでに設定されていると仮定して、テナントスコープのテーブルをクエリしないでください。
  • エンジンメソッドは生の dict ではなく、型付きモデル(Pydantic/dataclass)を返します(「型安全性」を参照)。
  • すべてのリストエンドポイントは、既存のものに倣ってページネーションを行います。 データ量に応じてサイズが増大するコレクション(banks、documents、memories、entities、operations、webhook deliveries、audit logs など)を返す GET は、limit/offset を受け取り、結果を上限付きにしなければなりません — 上限のないリストは、上限のないペイロードに加えて、上限のない行ごとの処理(項目ごとのカウント、設定の解決、埋め込みのハイドレーション)を意味します。list_documents が使っている形をそのまま踏襲し、新しい形を考案しないでください: ハンドラーに limit: int = Query(default=100, ge=0) と offset: int = Query(default=0, ge=0) を付け、エンジンメソッドに対応するキーワード引数を用意し、レスポンスにはページに加えて total、limit、offset を含めて、クライアントがいつ停止すべきかを把握できるようにします。コレクションが UI でユーザーが選択する対象である場合は、q 検索パラメータを追加してください — クライアント側のフィルタリングでは、読み込まれたページしか見えません。構造的に上限が保証されているエンドポイントは例外であり、原則ではありません: 設計上ツリー全体・エクスポート全体となるもの、あるいは書き込み時に上限が設定されるテーブル(例: observation_history / mental_model_history、挿入時に *_max_entries に切り詰められる)です。上限がない場合は、ページネーションを行ってください。
HTTP ミドルウェア
  • BaseHTTPMiddleware を決して追加しないでください — つまり、@app.middleware("http")(このデコレーターはこれをインストールします)も、add_middleware(SomethingSubclassingBaseHTTPMiddleware) も禁止です。Starlette はそのようなミドルウェアごとに、ダウンストリームのアプリを子タスクで実行し、リクエストとレスポンスを anyio のメモリオブジェクトストリームのペアを通じて流します。これにはリクエストごとにタスクの生成と複数回のスケジューリングホップのコストがかかり、そのコストはスケジューリングであるため、イベントループがすでに逼迫しているまさにそのときに増大します。API からこの 2 つを削除したところ、同じ同時実行数で /health/live が約 2.4k から約 7.9k rps に、p99 が約 107ms から約 18ms になりました(#4235)。また、その下にあるすべてに対して Request.is_disconnected() を壊し(http.disconnect イベントがルートに到達しません)(#2122)、BackgroundTask / ストリーミングのセマンティクスを微妙な形で飲み込んでしまいます。
  • 代わりにピュア ASGI ミドルウェアを書いてください: __init__(self, app) と async def __call__(self, scope, receive, send) を持つクラスで、scope["type"] != "http" の場合はそのまま通過させ、レスポンスを観察する必要がある場合は send をラップします(http.response.start メッセージからステータスを読み取り、message["headers"] に生のバイトペアとしてヘッダーを追加します)。同じタスク内での await が 1 回だけで、ホップはありません。hindsight_api/api/observability.py と api/disconnect.py がコピーすべきモデルです。
  • ルーティングコンテキストが必要な場合は、処理を 1 つ下の層に移すことを優先してください。 どのエンドポイントがヒットしたかを知る必要があるもの — そのパラメーター、シグネチャ、ボディモデル — は、app.routes をたどって route.matches() を呼び出してルートを再導出するミドルウェアではなく、APIRoute サブクラス(app.router.route_class = ...)に属します。そこではルートはすでに解決されており、ルートごとの情報はリクエストごとではなく起動時に一度だけ計算できます。api/unknown_params.py を参照してください。include_router はアプリの route_class をルーター自身のルートには適用しないことに注意してください — FastAPI <= 0.140 では各ソースルートのクラスが維持され、>= 0.141 ではソースルーターの route_class から具体化されます — そのため、インクルードされた/拡張ルーターからのルートには、インクルードの前に use_unknown_params_routes(router) が必要です。
  • クライアント入力から構築されたヘッダー値は、message["headers"] に到達する前にサニタイズする必要があります: クエリパラメーターとボディフィールドの名前はパーセントデコードされた攻撃者入力であるため、非 latin-1 の名前は send の途中で例外を発生させ(タイプミスによる 500)、CR/LF を含む名前はレスポンスを分割します。
アウトバウンド HTTP: aiohttp、非同期のみ
  • 本番コードでは HTTP 呼び出しに aiohttp を使用し、httpx は決して使用しません。 httpx の非同期クライアントはリクエストあたりのコストが aiohttp よりも明らかに大きく(httpcore プール、anyio レイヤー)、そのオーバーヘッドは私たちが持つ最もホットなパス — retain と recall のたびに実行される埋め込み、リランキング、LLM 呼び出し — にのしかかります。クライアントは hindsight_api/engine/aiohttp_session.py で構築してください: LoopLocalSession(イベントループごとに 1 つの ClientSession を遅延生成 — __init__ や、別スレッドの asyncio.run 配下で実行されうる initialize() の中でセッションを生成しないでください)、per_phase_timeout(httpx スタイルのフェーズごとのタイムアウト。aiohttp の total では長いストリーミングボディが途中で切られてしまいます)、そして raise_for_status(UpstreamHTTPError を送出し、これはボディと、remote_retry が分類に使用する status_code を保持します)。
  • 本番コードでは同期 HTTP は禁止です — requests、urllib.request、urllib3、http.client、httpx.Client、および SDK に非同期版がある場合の同期 SDK クライアント(openai.OpenAI、cohere.Client、litellm.embedding など)は使用しないでください。同期呼び出しはイベントループをブロックするか、リクエストごとにスレッドを必要とします。代わりにループ上で非同期版を使用してください。同期呼び出しを asyncio.to_thread / run_in_executor でラップすることは解決策にはなりません。唯一認められる例外は、非同期 API をまったく提供しない SDK(例: google.auth の認証情報リフレッシュ)です — これはスレッドで実行し、その理由をコメントに記述してください。
  • ruff によって強制されます(hindsight-api-slim/pyproject.toml の TID251、banned-api)。import httpx は、それに依存して構築されたサードパーティ SDK を設定または分類する場合にのみ許可されます(OpenAI/Anthropic SDK 向けの llm_transport.build_sdk_timeout、それらのエラーを分類する remote_retry)。その際は # noqa: TID251 と SDK 名を記したコメントを付けます。それ以外の noqa: TID251 は拒否してください。http_probe.py は唯一のファイル単位の例外です(標準ライブラリのみのレディネスプローブであり、独自のプロセスです)。
  • テストは対象外です。 FastAPI の TestClient / httpx.ASGITransport は問題ありません。aiohttp クライアント向けに上流をスタブするには、セッションをモックするのではなく tests/aiohttp_stub.py::stub_server から提供してください。
クエリにおけるバンク/テナント分離
  • バンク分離はハードなセキュリティ不変条件です。どのクエリも、他のバンクの行を読み取り、カウント、更新、削除してはなりません。 テナント分離はスキーマレベルで強制されます(解決された search_path / fq_table(...) 修飾子であり、_authenticate_tenant によってゲートされます)。バンク分離は、マルチバンクテーブルに触れるすべてのステートメントにおける bank_id 述語によって、スキーマ内で強制されます。
  • マルチバンクテーブルに対するすべての SQL ステートメントは、bank_id によって制約されなければなりません — WHERE 内で直接、または推移的に(以下を参照)。マルチバンクテーブルは bank_id カラムを持ちます: memory_units、documents、entities、mental_models、knowledge_pages、memory_links、observation_history などです。
  • 罠: bank_id なしで、呼び出し元が指定したグローバルに一意でないキーでフィルタリングすること。 document_id や mental_models.id のようなキーはバンクごとにのみ一意です(その PK は複合で、例えば (id, bank_id) です)。そのため、同じ id がすべてのバンクに合法的に存在します。UPDATE memory_units SET tags = $1 WHERE document_id = $2 のようなステートメントは — bank_id がなく — その id を共有するすべてのバンクの行を暗黙的に読み取り/書き込みします。これが #3429/#3430 の正確な欠陥です。AND bank_id = $n を追加すると修正されます。
  • ステートメントが正当にスコープされる 3 つの方法(これらは受け入れ、どれにも当てはまらないものは指摘してください):
    1. 明示的 WHERE ... AND bank_id = $n。
    2. グローバルに一意な単一カラム PK。 グローバル uuid PK(memory_units.id、entities.id、knowledge_pages.id)またはバンクエンコードされたキー(chunks.chunk_id、engine/chunk_ids.py によって構築)でフィルタリングすることは、バンク間で衝突しません。チャンク id が単射であるのは、そのヘルパーが各コンポーネント内の区切り文字をエスケープしているからにすぎないことに注意してください — それが置き換えた単純な {bank_id}_{document_id}_{idx} の連結では、('a', 'b_c') と ('a_b', 'c') が同じ行を指し示すことができました(#4244)。そのため、それ以前に書き込まれた id はバンクスコープとして扱うのは安全ではありません。複合 PK の id(documents.id/document_id、mental_models.id)と対比してください — これらは危険であり、bank_id を必ず伴わなければなりません。
    3. 推移的。 bank_id カラムを持たないジャンクションテーブル(unit_entities、entity_cooccurrences、observation_sources)は、同じ呼び出し内でバンクスコープのクエリから選択された、それ自体がグローバルに一意な unit/entity id を通じて到達される場合にのみ安全であり、エッジは構造上バンク内です。id セットに他のバンクの id が含まれる可能性がある場合、それはスコープされていません。
  • 2 つの兆候に注意してください: (a) WHERE で使用される呼び出し元指定の id に隣接する bank_id がなく、一方で同じメソッド内の隣接するステートメントには bank_id が伴っている場合(非対称性が手がかりです)、(b) bank_id 述語が if bank_id: の下でのみ適用され、bank_id: str | None = None がデフォルトになっている場合 — 現在のすべての呼び出し元が 1 つを渡していたとしても潜在的な問題です。
  • 設計上のクロスバンクでは bank_id を宛先に書き換える必要があります。 転送/インポート経路は、正当にバンクを越える唯一のものです。すべての書き込みが宛先の bank_id を固定し、ソース行の bank_id を継承しないことを確認してください。
データベースロック
  • PostgreSQL アドバイザリロックは決して使用しないでください(pg_advisory_lock、pg_try_advisory_lock、pg_advisory_xact_lock、pg_advisory_unlock など)。マイグレーション、エンジンコード、その他どこであっても同様です。Hindsight はコネクションプーラーやマネージド/PG 互換サービスに対して実行され、そこではアドバイザリロックは信頼できないか、サポートされていません。セッションレベルのロックは、プーラーがセッションを別のクライアントに引き渡したときに、黙ってリークしたり消えたりしますし、呼び出し元はサーバーが決して付与しないロックの上で永遠にブロックする可能性があります。「安全」に見えるもの(トランザクションスコープであるため)も含め、新たな使用箇所はすべて却下してください。
  • hindsight_api/migrations.py に既存の使用箇所がありますが、これは既得権として容認されているものであり、前例ではありません — 削除するために追跡されています。これをコピーしないでください。
  • ロックで囲うのではなく、並行性を設計で排除してください。各プロセスに書き込み用の独自のオブジェクトを持たせる(例: 共有の public. オブジェクトではなくスキーマごとの DDL)、操作を冪等にする、あるいは実際の行/テーブル制約を使用する(INSERT ... ON CONFLICT、固定された順序での SELECT ... FOR UPDATE)といった方法があります。pg_advisory_xact_lock に手を伸ばし、差し戻さざるを得なかったマイグレーションについては #2690 を参照してください。
並行性: asyncio と threading プリミティブ

Hindsight プロセスはスレッド(エグゼキュータ、asyncio.to_thread)を実行し、またテストやツーリング全体では複数のイベントループを実行します。以下のルールが破られても静かなものです — コードはテストに合格し、負荷がかかると失敗します。

どのロックを使うか。 これはスタイルではなく、所有権の問題です:

  • asyncio.Lock / Semaphore / Event / Condition / Future は、それらを最初に待機したイベントループにバインドされます。 これらは単一のループが所有するオブジェクトにのみ使用してください — リクエストごと、コネクションごと、あるいは 1 つのループが作成しそのループだけが触れるオブジェクトにぶら下がる状態です。RuntimeError: ... is bound to a different event loop は、このルールが破られていることを示します。
  • 複数のループから到達可能なものには threading.Lock を使用します: モジュールレベルのシングルトン、プロセス全体のキャッシュ、レジストリです。これはループに依存しません。await を含まないクリティカルセクションのみを保護できます — await をまたいで保持してはいけません。そうすると、処理を譲るのではなく、ループ全体をブロックしてしまいます。

クリティカルセクションが await する必要があり、かつ オブジェクトがプロセスグローバルである場合、それはロックの問題ではなく設計の問題です: 代わりに状態をループごとにスコープしてください。

インポート時に asyncio プリミティブを作成しないでください。 モジュールレベルの asyncio.Lock()/Semaphore() は、どのループも存在する前に構築され、その後すべてのループで共有されます。また、1 つのプロセス内に 2 つのループが現れた瞬間(テスト、スレッド内の asyncio.run)に、単一 ループのビルドでも失敗します。それらを所有するオブジェクトの内部で遅延的に構築してください。

合流する future はループごとです。 並行するロードを asyncio.Future の背後で重複排除するキャッシュは、その処理中マップを (running loop, key) でキー付けしなければなりません。キャッシュされた データ をループ間で共有するのは問題なく、望ましいことです。future を共有するのはそうではありません。

モジュールレベルの可変状態。 複数のスレッドから到達可能なプロセスグローバルの dict/set/list にはガードが必要であり、別のスレッドが変更する可能性がある間に反復してはいけません(最良でも RuntimeError: dictionary changed size during iteration、サイズ変更中の dict に C 拡張が借用参照を渡された場合はセグフォルトする可能性があります)。ホットパスをロックするよりも、ロックの下で一度だけウォームアップすることを優先してください。

ドキュメントのコード例
  • hindsight-docs/docs/ 内で Hindsight を呼び出すすべてのコード例は、テスト済みのサンプルファイルから取得する必要があり、インラインのフェンス付きブロックを使用してはいけません。 つまり、SDK 呼び出し (Python、Node.js、Go)、hindsight ... CLI コマンド、および /v1/... に対する curl が対象です。コードは hindsight-docs/examples/api/<page>.{py,mjs,sh,go} に # [docs:<section>] / # [/docs:<section>] マーカー (JS/Go では //) で囲んで配置し、<CodeSnippet code={...} section="<section>" language="..." /> で表示してください (docs/developer/api/memory-banks.mdx を参照)。CI はすべてのサンプルファイルを稼働中のサーバーに対して実行するため (scripts/test-doc-examples.sh)、メソッドやフラグの名前を変更すると、ドキュメントが黙って壊れるのではなくビルドが失敗します。
  • その呼び出しを SDK または CLI がサポートするすべての言語についてタブを用意してください。ページ内の類似セクションと同様にします。スニペットが必要な .md ページは .mdx にします。
  • インラインのままで問題ないもの: インストールコマンド、env/設定のスニペット、エンドポイントのシグネチャ行 (GET /v1/...)、JSON リクエスト/レスポンスの形式、および CI では実行できないインフラを必要とするコマンド (組み込みサーバーの起動、管理用 DB コマンド、FUSE マウント)。
ブランチの衛生管理
  • 新しい機能ブランチは常に origin/main から開始してください — クリーンなベースを確保するためにリベースします。
  • PR/ブランチ/機能に関連するコミットのみを含めてください — 無関係な変更は含めません。ブランチに属さないコミットが含まれている場合は、マージ前に削除する必要があります。
一般原則
  • 求められた範囲を超えて機能を追加したり、コードをリファクタリングしたり、「改善」を加えたりしない
  • 起こり得ないシナリオに対する不要なエラー処理を追加しない
  • 一度きりの操作のためにヘルパーや抽象化を作成しない
  • 後方互換性のためのハックは行わない (未使用の変数、再エクスポート、「removed」のコメント)
  • 似たようなコードを 3 行書くほうが、早すぎる抽象化よりも良い

レビューの手順

1. ブランチの衛生管理を確認する
  • git log --oneline main..HEAD を実行して、ブランチ上のすべてのコミットを一覧表示します。
  • すべてのコミットが機能/PR に関連していることを確認します。無関係なコミットがあれば指摘します。
  • ブランチが最近の origin/main に基づいていることを確認します (古いベースになっていないこと)。
2. 変更されたファイルを特定する

git diff --name-only HEAD (ステージングされていない変更) と git diff --cached --name-only (ステージング済みの変更) を実行して、変更されたすべてのファイルを取得します。ローカルに変更がない場合は、ベースブランチとの差分を git diff main...HEAD --name-only および git diff main...HEAD で取得し、現在のブランチ上のすべてのコミットをレビューします。

3. リンターを実行する
./scripts/hooks/lint.sh

失敗があれば報告してください。自分で修正しないでください — 報告のみです。

4. デッドコードを確認する

変更された各 Python ファイルについて、次を確認します:

  • 未使用のインポート (Ruff が検出するはずですが、確認してください)
  • 追加されたがどこからも呼び出されていない関数/メソッド/クラス
  • 代入されたが一度も読まれていない変数
  • 削除すべきコメントアウトされたコードブロック

変更された各 TypeScript ファイルについて、次を確認します:

  • 未使用のインポート
  • 未使用の変数または関数
  • コメントアウトされたコード
5. 型安全性を確認する (Python)

変更された各 Python ファイルについて、違反がないか確認します:

  • 構造化データに生の dict を使わない — 内部/プライベート関数であっても Pydantic モデルまたは dataclass を使用する必要があります (唯一の例外: 真に動的/不明なキー)
  • 複数項目のタプル戻り値を使わない — 内部/プライベート関数であっても dataclass または Pydantic モデルを使用する必要があります (例外なし)
  • 関数のパラメータと戻り値の型における型ヒントの欠落
  • タイムゾーン対応であるべき datetime フィールドにおける @field_validator の欠落
6. 欠落しているテストを確認する

新規または大幅に変更された関数/エンドポイント/クラスごとに、以下を確認します。

  • 対応するテストの追加または更新があるかどうかを確認する
  • 新しい API エンドポイントには、統合テストが必須です
  • 新しいユーティリティ関数には、単体テストが必須です
  • バグ修正には、回帰テストがあることが望ましいです(SHOULD)

テストカバレッジを欠いている新しいロジックがあれば指摘してください。

LLM の挙動の変更には、MockLLM ではなく実際の LLM によるジャッジテストが必要です。 その変更がモデルのプロンプト解釈の仕方を変える場合 — 事実/観察の抽出、fact_type(world/experience)の分類、発話者の帰属、指示追従、プロンプトの文言 — には、実際のパイプラインを実行し、文字列/enum の照合ではなく tests.llm_judge.assert_meets_criteria でアサートする pytest.mark.hs_llm_core を付けたテストが必須です。以下のものを指摘事項として報告してください。

  • プロンプト/分類の変更が MockLLM または文字列アサーションのみで検証されている(MockLLM は入力をエコーするため、そのようなテストは誤ってパスします)。修正すべきです。
  • fact_type == "world"/"experience"(またはその他のモデルが決定する出力)をジャッジせずにハードアサートしているテスト — 非決定的であり、プロバイダーや実行ごとにフレークします。修正すべきです(分類のチェックをジャッジの criteria に移し、真に決定的な構造的アサートのみを直接行うようにします)。
  • 決定的なメカニズム(プロンプトの組み立て、抑制/分岐ロジック)が遅い LLM テストのみでカバーされている — これらには高速な非 LLM の単体テストも必要です。注記。

完全なパターンについては、CLAUDE.md → Key Conventions → Testing を参照してください。

6a. テストが生の SQL ではなくエンジン API を介してメモリ状態をアサートしていることを確認する

テストは、retain / recall / consolidation が何を生成したかを、公開されている MemoryEngine の読み取り API — list_memory_units(ユニットとその metadata / tags、total によるカウント、document_id / fact_type / entity_id フィルター)、list_entities(正規名、言及数)、get_graph_data(ノード/エッジ)、get_bank_stats、recall_async — を呼び出して検証しなければならず、pool.acquire() / conn.fetch* を介した生の SQL でメモリテーブル(memory_units、memory_links、unit_entities)に直接アクセスして検証してはいけません。これらのテーブルに対してアサートすることは、テストをストレージ層の詳細に結合させ、観測可能なプロパティではなくプロキシをチェックすることになります(General Principles → テストはプロパティをアサートする、および 7b のハンドラーのルールを参照してください)。

追加または変更されたテストで、エンジンの読み取りメソッドが同じ事実を返すにもかかわらず、memory_units / memory_links / unit_entities に対して SELECT / COUNT を実行してアサートしているものは、修正すべきとして指摘してください。明白な兆候:テスト本体の中で async with pool.acquire() as conn: の後に SELECT ... FROM memory_units が続くもの、list_memory_units の ["total"] が返すはずの fetchval("SELECT count(*) FROM memory_units ...")、list_entities がカバーする canonical_name クエリ。

これらのテーブルに対する直接 SQL が正当化されるのは、公開 API では表現できない内部状態を強制または検査する場合のみです — 例えば、競合を捏造する UPDATE documents SET updated_at、または重複排除された get_graph_data のエッジリストでは再現できない生の memory_links の行数などです。これらには、直接アクセスが必要な理由を説明するコメントを必ず付けてください。付けていないものは指摘してください。

6b. ユーザー向け機能にシステムテストがあることを確認する

hindsight-system-tests/ には、公開されている Python クライアントを通じて実際の hindsight-api プロセスを駆動するブラックボックスのストーリーが置かれています。エンジンへのアクセスも、SQL も、内部インポートもありません。これらが存在するのは、約 500 ファイルの api-slim スイートがメカニズムごとに 1 つのテストという構成であり、メカニズムのバグは捕捉できてもコンポジションのバグを見逃すためです。すなわち、統合がその下のファクトを消し去ること、差分リフレッシュが遡及ウィンドウを取りこぼすこと、転送がメンタルモデルの証拠を落とすこと、再処理が黙って何もしないことです。これらのいずれも、単一メカニズムのテストではまたがらない複数のステップに及んでいました。追跡 issue: #4214。

変更にシステムストーリーが必要となるのは、それがユーザーが名前を挙げられる機能を追加する場合、または既存の 2 つの機能を初めて組み合わせる場合です。具体的には、次のような PR を should fix としてフラグを立てます:

  • 観測可能な挙動を変える API エンドポイント、retain/recall/reflect パラメーター、またはバンク設定フィールドを追加または変更する。
  • 新しい派生レイヤーやライフサイクルステップ (観測の種類、リフレッシュトリガー、バックグラウンド操作) を追加する。
  • 既存の機能を別の機能と初めて相互作用させる — 新しい組み合わせこそが、ユニットスイートには見えないものです。
  • コンポジションのバグを修正する。リグレッションテストは api-slim だけでなくここに置くべきです。なぜなら、そのバグはステップ間の継ぎ目に潜んでいたからです。

次の場合には必要ありません: 観測可能な変化を伴わない内部リファクタリング、パフォーマンス作業、既存のストーリーですでにカバーされているメカニズム、またはその唯一の表面がコントロールプレーンであるもの (このスイートは設計上 API のみです)。

追加または変更されたストーリーをレビューする際は、次を確認してください:

  • クライアントを通っていること。 httpx でも MemoryEngine でもなく、client.aretain(...) です。公開クライアントを迂回してアクセスすると、このスイートが明らかにするために存在する SDK の欠陥が隠されます — import_bank_template はあらゆる SDK から呼び出し不能で (#4232)、クライアント駆動のテストだけがそれを示せました。ストーリーが自らを通すためにクライアントを迂回している場合は Must fix です。
  • すべての LLM 呼び出しが宣言されていること。 スクリプト化されていない呼び出しは、貼り付けるべきルールとともにテストを失敗させます。もっともらしいデフォルトで応答するストーリーは何も証明しません。見逃しを黙らせるために catch-all ルールを決して追加しないでください。
  • バックグラウンド処理が無効化されず、待機されていること。 Retain は統合をエンキューします。決定論性を保つためにそれをオフにするストーリーは、コンポジションのバグが潜む半分を取り除いてしまっています。settled(bank_id) を使用してください。
  • キーワードの存在ではなく、決定論的なペイロード全体をアサートしていること。 LLM、embedder、reranker がすべてスタブ化されていれば、recall は入力の純粋関数です — ランキング、スコア、レンダリングされたテキストはすべて固定でき、「その単語がどこかに現れる」は、融合が反転したときでも同じように通ってしまいます。
  • 既知の欠陥が文書化されるのではなく、失敗すること。 PR が契約を満たさないままにする場合、ストーリーは私たちが望む挙動をアサートし、それが修正されるまでレッドになります — 実行をグリーンに保ち、誰も問題を提起しなくなる xfail でもなく、今日の誤った答えを固定し、誰かが修正した日に壊れて次の読者にテストを削除するよう教えるテストでもありません。どちらの形でも Must fix です。

スイートの README に規約が記載されています。tests/test_01_retain_and_recall.py が、アサーションがどれほど網羅的であるべきかの参照です。

7. API の一貫性を確認する

hindsight-api-slim/hindsight_api/api/ 内のファイルが変更された場合:

  • OpenAPI 仕様は再生成されましたか? (./scripts/generate-openapi.sh)
  • クライアント SDK は再生成されましたか? (./scripts/generate-clients.sh)
  • コントロールプレーンのプロキシルートは更新されましたか? (hindsight-control-plane/src/app/api/)
7a. TS/Python ラッパークライアントのパリティを確認する

生成された SDK のうち 2 つは、自動生成された低レベルクライアントの上に手書きで保守されている便利なラッパーを同梱しています。そしてそれはこの 2 つだけです:

  • TypeScript: hindsight-clients/typescript/src/index.ts (HindsightClient)
  • Python: hindsight-clients/python/hindsight_client/hindsight_client.py (Hindsight)

(Rust/Go などのクライアントは生成のみで、同期を保つべきラッパーはありません。)

これらのラッパーは、ほとんどのサードパーティ利用者が実際に呼び出すものであり、同じサーフェスを公開する必要があります。**ある変更が一方のラッパーのメソッドに触れる場合 — パラメータの追加/削除、デフォルト値の変更、新しいクエリ/ボディフィールドの転送 — は、もう一方のラッパーの同等のメソッドにも同じ変更を同じ PR (または直後の PR) で加える必要があります。**生成された SDK には存在するパラメータが、一方のラッパーで欠落していると、その言語のすべての利用者に対して黙って取り除かれてしまいます (これはまさに #2975 / #3042 が listMentalModels/getMentalModel の detail/tags_match/limit/offset に対して修正した内容です)。修正すべき — 一方の言語では機能が追加されているのにもう一方では追加されていないラッパーメソッドがあれば指摘し、両側に対応するマッピングのリグレッションテストを追加してください。

注: client-coverage-check CI ツールはリクエストボディのフィールドのみを検証し、GET のクエリパラメータは検証しません。そのため、クエリパラメータのパリティのギャップは自動的には検出されず、ここで手作業で確認する必要があります。

7b. API レイヤーのデータアクセス境界を確認する

hindsight-api-slim/hindsight_api/api/ 内の変更された各ハンドラー (例: http.py、mcp.py) について:

  • ハンドラー内での直接的な DB アクセスを指摘してください — acquire_with_retry、conn.fetch / fetchrow / execute、生の SQL 文字列、または fq_table(...)。これらは必須の修正です。クエリは型付きモデルを返す MemoryEngine のメソッドに移動し、ハンドラーはそのメソッドを呼び出す必要があります。
  • 認証がエンジンで強制されていることを確認してください — ハンドラーは、request_context を介して認証するエンジンメソッド (通常は get_bank_profile を通じて _authenticate_tenant) に委譲する必要があります。認証を強制するエンジンメソッドを介さずにテナントスコープのデータを読み書きするハンドラーは必須の修正です (テナントデータがスキーマ間で漏洩する可能性があります)。
7c. バンク/テナントのクエリスコープを確認する

diff で追加または変更されたすべての SQL ステートメントについて (diff を conn.fetch、conn.fetchrow、conn.fetchval、conn.execute、executemany、および生の SELECT/INSERT/UPDATE/DELETE の f-string (複数行のものも含む) で grep してください)、それが別のバンクの行に触れないことを検証してください — 上記のクエリにおけるバンク/テナント分離を参照してください。

マルチバンクテーブル (memory_units、documents、entities、mental_models、knowledge_pages、memory_links、observation_history、…) に対する各ステートメントについて、次の 3 つの正当なメカニズムのいずれかでスコープされていることを確認してください:

  1. 明示的な AND bank_id = $n;
  2. グローバルに一意な単一カラム PK (*.id uuid、またはバンクエンコードされた chunks.chunk_id) — documents.id/document_id や mental_models.id のような複合 PK の id ではありません;
  3. 同じ呼び出し内でそれ自体がバンクスコープのクエリから選択された、グローバルに一意な id の集合を介した推移的なスコープ。

呼び出し元が指定した、グローバルに一意ではないキー (document_id、mental_models.id、エンティティ名など) でマルチバンクテーブルをフィルタリングしつつ、bank_id 述語がないステートメントは必須の修正として指摘してください — 指摘する前に、具体的な 2 バンクのシナリオ (2 つのバンクが id を共有しており、そのステートメントが誤ったバンクの行を読み取り/カウント/更新/削除する、または過剰に報告する) を構築して、それが実在することを確認してください。主な兆候: bank_id を持たないステートメントのすぐ隣にある bank_id を持つ兄弟ステートメント; デフォルトが None の if bank_id: でガードされた WHERE bank_id; 転送先を固定せずに転送元の bank_id を継承するインポート/転送の書き込み。

7d. リストエンドポイントのページネーションを確認する

追加または変更された、コレクションを返すすべての GET ハンドラーについて、limit/offset を受け取り total を返すことを確認します。正確な形については上記の API レイヤーとデータアクセス を参照してください。そのうえで、修正がエンドツーエンドで本当に機能しているかを確認します。誰も強制しないパラメータは、存在しないよりも有害です。

  • 制限がレスポンスだけでなく実際の処理にまで届いていること。 ページサイズが本当に高コストな部分を制限しているか確認します。すなわち SQL の LIMIT/OFFSET、または(list_banks のように filter_bank_list 拡張フックが任意の bank を除外しうるため、プロセス内フィルターの後にページングを行う必要がある場合)ページ分に対してのみ実行される項目ごとの処理 — ライブストアのカウント、get_bank_configs、再埋め込み — を伴う明示的なスライスです。行を除外しうるフィルターの前に SQL でページングするのは 必ず修正 です。呼び出し元が参照できない行を数えた、不足したページや空のページ、そして total を返してしまうからです。
  • リポジトリ内のすべてのコンシューマーがページングすること。 新しいデフォルトの limit は、以前はすべてを取得していた呼び出し元を黙って切り捨てます。コントロールプレーン(src/lib/api.ts + src/app/api/ プロキシルート + リスト全体を保持する任意のコンテキスト/セレクター)、CLI(hindsight-cli/src/api.rs)、MCP ツール、および Zapier の動的ドロップダウンです。それぞれが完了するまでページングするか、UI でページングを公開する必要があります。デフォルトサイズの単一ページのままになっているコンシューマーがあれば指摘してください。
  • 検索もそれに伴ってサーバーサイドへ移行すること。 リスト全体に対してクライアントサイドでフィルタリングしていたピッカーは、読み込まれたページだけをフィルタリングするようになります。エンドポイントが q を取得したなら、UI はそれを送信する必要があります(そしてローカルフィルタリングを無効にします。例えば cmdk の shouldFilter={false})。取得していないなら、そのコレクションがそれを必要としないほど小さい理由を説明してください。
  • 自身の行を検索するテストは、ページ 1 に到達することに依存してはなりません — 検索するか、明示的な limit を渡すべきであり、デフォルトの並び順に頼ってはいけません。
8. コードコメントを確認する

自明でない変更それぞれについて:

  • 新しく自明でないロジック — その理由を説明するコメントがありますか?
  • 変更されたアプローチ — コメントに、以前何が行われていたか、なぜ変更されたかが含まれていますか?
  • 古くなったコメント — 変更されたコードの近くにある既存のコメントは、まだ動作を正確に説明していますか?
9. 統合の完全性を確認する

hindsight-integrations/ 内のファイルが追加または変更された場合は、以下を確認してください。

  • テストが存在する — 統合には、外部フレームワークをシミュレート/実行するテストが必要です(ヘルパーの純粋な単体テストだけでは不十分です)。意味のあるテストファイルを含む tests/ ディレクトリがあるか確認してください。
  • CI ジョブが存在する — .github/workflows/test.yml に対応する test-<name>-integration ジョブがあるか確認してください。存在しない場合は、指摘してください。
  • リリースプロセス — 統合名が scripts/release-integration.sh の VALID_INTEGRATIONS 配列に含まれていること、および hindsight-dev/hindsight_dev/generate_changelog.py の INTEGRATIONS 辞書に含まれていることを確認してください(チェンジログジェネレーターは独自のリストを保持しており、そこに名前がないとチェンジログのステップでリリースが失敗します)。どちらかが欠けている場合は、指摘してください。
  • ドキュメントギャラリー + サイドバーエントリ — 統合には hindsight-docs/src/data/integrations.json 内のエントリが必要です。このファイルは、統合ギャラリーとドキュメントサイドバーの両方を動かす唯一の信頼できる情報源です(サイドバーカテゴリは、レンダリング時にすべてのドキュメントバージョンにわたってこのファイルから注入されます)。エントリには内部リンク /sdks/integrations/<slug> の link と、それに対応するページ hindsight-docs/docs-integrations/<slug>.md(x) が必要です。hindsight-docs/scripts/check-integrations.mjs ビルドステップは両方向を強制します — 順方向: すべての内部 JSON エントリにドキュメントページがあること。逆方向: すべてのリリース済みタグ(integrations/<name>/vX.Y.Z)が JSON に含まれていること(cloudflare-oauth-proxy のようなプライベートインフラはスクリプトの EXCLUDED セットに含まれています)。リリース済み(またはリリース中)であるのに integrations.json に存在しない統合、およびドキュメントページのない JSON エントリを指摘してください。統合リンクを追加するために versioned_sidebars/*.json を手動で編集しないでください — これらは JSON から埋められる位置ベースのプレースホルダーです。
  • コード標準 — 統合コードは、すべての Python スタイルルール(型ヒント、生の辞書の禁止、タプルの戻り値の禁止など)に従う必要があります。
9a. 兄弟実装間のパリティを確認する

同じ機能が バリアント ごとに 1 回ずつ実装されている場合 — ハーネスごと、言語ごと、方言ごと、プロバイダーごとに 1 つずつ — 、新規または変更されたバリアントこそが、機能がひっそりと欠落する場所です。その欠陥は diff の中ではバグのようには見えません。間違っているコードとは そこに存在しない コードであり、既存のテストはすべて引き続きパスします。なぜなら、忘れた兄弟とは、定義上、誰もテストを書かなかったものだからです。これが、dsh がデーモンを一度も起動せずにデーモンモードで出荷された経緯です (#3524): ensureDaemon はフック専用ラッパーの中にあり、5 つの永続プラグインハーネスすべてでこれが欠けていました。

このリポジトリで既知の兄弟ファミリー (これは全リストではありません — このルールは 形 に関するものです):

ファミリー場所
コーディングエージェントハーネスhindsight-integrations/coding-agents/src/ (フックハーネスと永続プラグインハーネス: dsh、opencode、Kilo、Cline、Prime Agent)
ラッパー SDK クライアントTypeScript + Python ラッパー — ステップ 7a を参照
Alembic マイグレーションすべてのマイグレーションにおける _pg_upgrade / _oracle_upgrade
データプレーン ↔ コントロールプレーンapi/http.py のパラメータと hindsight-control-plane/src/app/api/** のプロキシルート + lib/api.ts
LLM プロバイダーengine/llm_wrapper.py のプロバイダーごとの分岐

手順 — これは手作業で行ってください。どんなリンターもこれを捕捉しません。 diff が新しい兄弟を追加したとき、またはファミリーのある兄弟を変更したとき:

  1. ファミリーを列挙する。 既存のすべての兄弟をリストアップします (ディレクトリを ls する、レジストリを grep する)。
  2. コードではなく機能リストを diff する。 他の 兄弟が持つ各機能 — 呼び出されるライフサイクルフック、実行されるセットアップ/ティアダウン、尊重される設定フラグ、尊重されるオプトアウト、レジストリ/インストーラー/ドキュメントのエントリ — について、変更された兄弟がそれを持っているか、あるいはその不在が意図的でありコメントされていることを確認します。grep がツールです: grep -rn ensureDaemon src は誰がそれを呼び出しているかを証明します。
  3. コピーよりも巻き上げを優先する。 その機能が N か所に存在するようになった場合、修正は通常、すべての兄弟がすでに共有している 1 つのパス (例: RuntimeCore、buildHookOutput) にそれを移動することであり、N+1 番目の兄弟がまた忘れるであろう N 番目のコピーを貼り付けることではありません。
  4. 単体テストだけでなく、構造的なガードを要求する。 忘れた兄弟のテストは構造上存在しないので、ファミリー全体に対して アサートするテストを求めます: ファイルシステム/レジストリから兄弟を列挙し、それぞれがコントラクトを満たすことをアサートし、明示的でコメントされた除外リストを付けます。前例: registry covers every installable harness (harness/registry.test.ts)、every harness entrypoint reaches a daemon (core/daemon.test.ts)、test_backup_tables_covers_entire_schema、test_migration_shape.py。

1 つを除くすべての兄弟に存在する機能は must fix としてフラグを立てます — どの兄弟がそれを持ち、どれが持っていないか、そしてユーザーに見える症状は何かを述べます (#3524 の場合: すべての hindsight_* ツール呼び出しが ECONNREFUSED で失敗し、デーモンを起動するものが何もない)。ファミリー全体のガードテストなしで新しい兄弟ファミリーメンバーが着地するのは should fix です。

10. MCP ツール登録の完全性を確認する

hindsight-api-slim/hindsight_api/mcp_tools.py で新しい MCP ツールが追加された、または既存のツールの名前が変更された場合:

  • mcp_tools.py の _ALL_TOOLS セット — 新しいツール名を含める必要があります
  • mcp_tools.py の register_mcp_tools() にある tools_to_register デフォルトセット — 新しいツール名を含める必要があります
  • hindsight-api-slim/hindsight_api/api/mcp.py の _SINGLE_BANK_TOOLS セット — 新しいツールがバンクスコープである場合 (list_banks/create_bank のようなバンク管理ツールではない場合)、これを含める必要があります
  • hindsight-control-plane/src/components/bank-config-view.tsx の MCP_TOOL_GROUPS — UI ツールセレクターの適切なグループに新しいツールを含める必要があります
  • テスト (例: test_mcp_tools.py) の ツール数のアサーション — 新しい数を反映するように更新する必要があります
11. バックアップ/リストアのテーブル網羅性を確認する

マイグレーションで新しい PostgreSQL テーブルが追加される場合(hindsight-api-slim/hindsight_api/alembic/versions/ 内の CREATE TABLE / op.create_table を探してください):

  • hindsight-api-slim/hindsight_api/admin/cli.py の BACKUP_TABLES — 新しいテーブルを含める必要があり、外部キーで参照するテーブルより後ろに配置します(親の後に子)。エントリの欠落はサイレントなデータ損失を招きます。そのテーブルは決してバックアップされず、リストア時の TRUNCATE banks CASCADE は、たとえ保存されていなくても banks への外部キーを持つ子テーブル(例: mental_models、directives)をリストア時に消去してしまいます。
  • tests/test_admin_backup_restore.py のガードテスト test_backup_tables_covers_entire_schema がこれを強制します — 新しいテーブルが BACKUP_TABLES に存在しない場合は must fix として指摘してください。
  • Oracle 専用のテーブル(例: observation_sources)は意図的に除外されています — 管理用のバックアップ/リストアは PostgreSQL 専用です。
11b. 新しい設定フラグが env テンプレートを更新しているか確認する

差分が新しい設定フィールドを追加する場合(hindsight-api-slim/hindsight_api/config.py 内の新しい ENV_* / HINDSIGHT_* 環境変数):

  • .env.example(リポジトリルート) — hindsight-docs/docs/developer/configuration.mdx のドキュメントエントリと並んで、その変数を追加する必要があります(任意の場合はコメントアウト)。config.py に追加されたフラグが .env.example に存在しない場合は should fix です。
  • hindsight-embed/hindsight_embed/env.example — バンドルされたコピーはリポジトリルートの .env.example とバイト単位で同一でなければなりません(embed/profile 設定のシードになります)。test_bundled_template_matches_repo_root 同期テストは差分があると失敗します。ルートファイルが再コピーされずに変更された場合は、must fix として指摘してください。
11c. アドバイザリロックを確認する

差分で advisory を grep してください(git diff main...HEAD | grep -in advisory)。新しい pg_advisory_lock / pg_try_advisory_lock / pg_advisory_xact_lock / pg_advisory_unlock 呼び出しは must fix です — 上記の「データベースロック」を参照してください。単にロックを外すよう求めるのではなく、代替案(プロセスごとのオブジェクト、冪等な DDL、行レベルの制約)を作者に示してください。

11e. BaseHTTPMiddleware を確認する
git diff main...HEAD -- '*.py' | grep -nE "@app\.middleware\(|BaseHTTPMiddleware"

新しい @app.middleware("http") または BaseHTTPMiddleware のサブクラスは must fix です — 上記の「HTTP ミドルウェア」を参照してください。純粋な ASGI ミドルウェア(またはロジックがルーティングコンテキストを必要とする場合は APIRoute のサブクラス)を求めてください。また、send ラッパーがクライアント入力に由来するヘッダー値をサニタイズしていることを確認してください。

11f. 送信 HTTP が aiohttp かつ非同期であることを確認する
git diff main...HEAD -- '*.py' ':!**/tests/**' | grep -nE "^\+.*(import httpx|from httpx|import requests|urllib\.request|http\.client|noqa: TID251|\.Client\(|asyncio\.to_thread|run_in_executor)"

本番コードでの新しい httpx の使用または同期 HTTP 呼び出しは must fix です — 上記の「送信 HTTP」を参照してください。ruff はインポートを検出しますが、検出できないものはレビューで確認します。非同期版が存在するのに同期 SDK クライアントを使っている、同期呼び出しを to_thread / run_in_executor に押し込んでいる、LoopLocalSession の外で aiohttp.ClientSession を作成している、そしてサードパーティ SDK の設定や分類を行っていない # noqa: TID251 です。

11g. ドキュメントのコード例がテスト済みスニペット由来か確認する

差分が hindsight-docs/docs/** に触れている場合、Hindsight を呼び出す新規または変更されたフェンス付きブロック(```python、```bash、```typescript、```go など)を探してください。SDK クライアントメソッド、hindsight <cmd> CLI コマンド、または /v1/... への curl です。それぞれが hindsight-docs/examples/api/ 内のセクションに裏付けられた <CodeSnippet> でなければなりません(「ドキュメントのコード例」を参照)。また、その例ファイルが呼び出しを実際に実行している(単に出力しているだけではない)こと、および自身の banks をクリーンアップしていることを確認してください。インラインの API 例は must fix として指摘してください。

公開中の /developer/... URL は docs/ ではなく hindsight-docs/versioned_docs/version-<latest>/ 内の最新スナップショットを配信します(docs/ は /developer/next/ です)。リリース時に docs/ がそこへ同期されます。次のリリース前に公開されるべきドキュメント修正は、そのスナップショットにもコピーする必要があります。

11d. 並行処理プリミティブを確認する

上記の「並行処理」を参照してください。diff を grep します:

git diff main...HEAD -- '*.py' | grep -nE "asyncio\.(Lock|Semaphore|Event|Condition|BoundedSemaphore|Future)\(|threading\.(Lock|RLock)\("

必ず修正:

  • モジュールスコープで作成された asyncio プリミティブ、またはプロセス全体のシングルトンに保存されたもの — これは最初にそれを待機したループにバインドされ、他のすべてのループを壊します。
  • await をまたいで保持される threading.Lock — これは制御を譲るのではなく、イベントループをブロックします。
  • 実行中のループをキーに含めずに asyncio.Future を保持する、処理中/合流用のマップ。

変更がインタープリターをひそかに脱落させていないかも確認してください:

  • 新しい依存関係、またはバージョンアップで、3.11〜3.14 のいずれかにホイールがない — build-api-python-versions CI マトリックスはそのすべてをインストールしスモークテストします。
  • 3.11 より新しい構文や標準ライブラリの使用 (uv run ty check がそのほとんどを検出します)。
  • プロセスごとに1つのイベントループを前提としているテストで、それが対象としているのが共有状態であるもの。

修正すべき:

  • ロックなしの新しいプロセスグローバルな可変状態 (dict/set/list、可変値に対する lru_cache)、または並行して変更されうる場所での反復処理。
12. 他のコーディング標準に照らしてレビューする

diff に上記の標準への違反がないか確認してください:

  • プロジェクトルートにある Python ファイル (許可されていません)
  • 非同期パターンの欠如 (全体を通して非同期であるべきです)
  • リクエスト/レスポンス用の Pydantic モデル
  • 行長が 120 文字超
  • 求められた以上の新機能/コード (過剰なエンジニアリング)
  • 不可能なシナリオに対する不要なエラー処理
  • 時期尚早な抽象化や投機的なヘルパー
  • 後方互換性のためのハック (未使用の変数、再エクスポート、「removed」コメント)
13. 調査結果を報告する

重大度別に整理した明確なサマリーを提示します。

必須修正 — CI を壊す、またはプロジェクトの厳格なルールに違反する問題:

  • ブランチ上の無関係なコミット
  • Lint の失敗
  • パブリック関数の型ヒントの欠如
  • 構造化データに対する生の dict の使用 (内部コードを含む)
  • 複数項目のタプル返却 (内部コードを含む)
  • 新しいエンドポイントに対するテストの欠如
  • ユーザー向けの新機能、または既存機能の新たな組み合わせで、hindsight-system-tests/ にストーリーがないもの (ステップ 6b を参照)
  • 公開されたクライアントをバイパスする、スクリプト化されていない LLM 呼び出しを無効化する、または決定論性を保つためにバックグラウンド処理を無効化するシステムストーリー
  • MemoryEngine メソッドではなく api/ ハンドラー内での直接 DB アクセス (生の SQL / acquire_with_retry / fq_table)
  • エンジンで認証が強制されていないテナントスコープのデータアクセス (_authenticate_tenant / get_bank_profile)
  • 呼び出し元が指定した、グローバルに一意でないキーでフィルタリングされたマルチバンクテーブルに対する SQL 文で、bank_id 述語がないもの (バンク間の読み書きリーク — ステップ 7c を参照)
  • 新しい統合にテスト、CI ジョブ、または release-integration.sh のエントリがないもの
  • リリース済み/追加済みの統合が hindsight-docs/src/data/integrations.json にない、または docs-integrations/<slug> ページのない JSON エントリ (check-integrations.mjs によりドキュメントビルドが失敗する)
  • admin/cli.py の BACKUP_TABLES にない新しい PostgreSQL テーブル (リストア時のサイレントなデータ損失)
  • diff 内の実装だけが持たない、他のすべての同種実装が備えている機能 (ステップ 9a を参照) — 他の実装が実行するライフサイクルステップをスキップするハーネス、ダイアレクト、プロバイダー、または言語バリアント
  • インポート時に作成される、またはプロセス全体のシングルトンが所有する asyncio ロック/セマフォ/イベント、あるいは await をまたいで保持される threading.Lock (ステップ 11d を参照)
  • テスト済みの hindsight-docs/examples/api/ ファイルの <CodeSnippet> ではなく、Hindsight を呼び出す (SDK、CLI、または curl /v1/...) インラインドキュメントのコード例 (ステップ 11g を参照)
  • サポートされているインタープリター 3.11 から 3.14 の一部でのみ動作する変更 (サポートされているインタープリターを参照)

修正推奨 — コード品質を損なう問題:

  • リンターが見逃したデッドコード / 未使用のインポート
  • 自明でないユーティリティ関数に対するテストの欠如
  • タスクスコープを超えた過剰な設計

注記 — 対応が必要かどうか判断が分かれる所見:

  • クライアントの再生成が必要になる可能性のある API 変更
  • 近隣のコードスタイルから逸脱するパターン

各所見には、ファイルパス、行番号、簡単な説明を含めてください。

問題を自動修正しないでください。すべての所見を報告し、何に対応するかはユーザーに判断を委ねてください。所見がない場合は、コードは問題ないと確認してください。

コメント