diff --git a/AGENT_GUIDE.md b/AGENT_GUIDE.md index 32e2ca7df1..879629af89 100644 --- a/AGENT_GUIDE.md +++ b/AGENT_GUIDE.md @@ -131,8 +131,9 @@ CI watching must be bounded. Do not loop indefinitely. ## Status Contract -- `status --json` and related JSON/MCP payloads currently expose the trust fields documented in `README.md` and `DEVELOPER_GUIDE.md`, including `fold_ready`, `fold_ready_reason`, `graph_table_available`, `issues_table_available`, `sql_graph_contract_ready`, `sql_graph_contract_degraded_reason`, `hotspot_family_ready`, `hotspot_family_degraded_reason`, `csharp_symbol_name_ready`, `csharp_metadata_target_ready`, `csharp_metadata_target_degraded_reason`, `indexed_head_commit`, `worktree_head_changed`, `index_writer_version`, `index_newer_than_reader`, `index_newer_than_reader_reason`, `unknown_extension_file_count`, `path_case_sensitive`, `data_dir`, `data_dir_source`, `data_dir_mode`, `mac_profile`, `db_pragma_settings` (`journal_mode`, `synchronous`, `wal_autocheckpoint`, `page_count`, `freelist_count`, `page_size`), `hooks`, MCP-only `mcp_session`, and the `status --check`-only `stale_after_seconds` / `index_age_seconds` threshold audit fields. -- When `fold_ready` or `csharp_metadata_target_ready` is the only degraded readiness bit, the CLI also adds `degraded_reason`, `recommended_action`, and `alternative_action`. +- `status --json` and related JSON/MCP payloads currently expose the trust fields documented in `README.md` and `DEVELOPER_GUIDE.md`, including `fold_ready`, `fold_ready_reason`, `graph_table_available`, `issues_table_available`, `file_issues_data_current`, `migration_in_progress`, `sql_graph_contract_ready`, `sql_graph_contract_degraded_reason`, `hotspot_family_ready`, `hotspot_family_degraded_reason`, `csharp_symbol_name_ready`, `csharp_metadata_target_ready`, `csharp_metadata_target_degraded_reason`, `indexed_head_commit`, `worktree_head_changed`, `index_writer_version`, `index_newer_than_reader`, `index_newer_than_reader_reason`, `unknown_extension_file_count`, `path_case_sensitive`, `data_dir`, `data_dir_source`, `data_dir_mode`, `mac_profile`, `db_pragma_settings` (`journal_mode`, `synchronous`, `wal_autocheckpoint`, `page_count`, `freelist_count`, `page_size`), `hooks`, MCP-only `mcp_session`, and the `status --check`-only `stale_after_seconds` / `index_age_seconds` threshold audit fields. +- When any readiness field is degraded, the CLI adds `degraded_root_cause`, `degraded_reason`, `recommended_action`, `alternative_action`, and `readiness_degradations[]`. `degraded_root_cause` is the primary stable machine code; `readiness_degradations[]` lists every degraded field with `root_cause`, human reason, and remediation strings. +- `issues_table_available` reports physical `file_issues` table presence only. `file_issues_data_current` reports whether the table is also stamped current for the active index generation. - `index_writer_version` records the `cdidx` version that last wrote to the DB (stamped into `codeindex_meta` as `cdidx_writer_version` on every full scan, update, and MCP index). `index_newer_than_reader` flips to `true` whenever any persisted numeric contract stamp in `codeindex_meta` (or unknown `PRAGMA user_version` readiness bits) exceeds the current binary's compiled maximum, so an older CLI re-opening a DB written by a newer CLI degrades loudly with an audit trail instead of silently dropping back to text-search fallbacks. `index_newer_than_reader_reason` enumerates the specific newer-than-reader stamps. - `status` also surfaces indexed-HEAD freshness via `indexed_head_sha`, `indexed_head_branch`, `indexed_head_timestamp`, and `commits_ahead_of_indexed_head`. They are stamped by `cdidx index` on every successful run (full scan AND partial update, distinct from `indexed_head_commit` which is full-scan only) on a best-effort basis (never blocks an otherwise-successful index) and omitted on non-git workspaces, detached HEAD (branch only), or legacy DBs created before this contract. - `status` also surfaces unknown-extension scan coverage via `unknown_extension_file_count`, stamped by successful full-repository index runs (`cdidx index ` and MCP `index_project`) as the number of non-indexed files with non-empty extensions that do not map to a known language. It is omitted on legacy DBs or before a current full scan has stamped the value. diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index abed5af659..6936b29ad4 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -226,6 +226,8 @@ Do not add mutable static caches, shared `StringBuilder` instances, reused `Matc `status --check` keeps the DB/worktree checksum comparison in `IndexFreshnessChecker`, but the user-facing age hint threshold is resolved in `QueryCommandRunner`: CLI `--stale-after ` wins over `CDIDX_STALE_AFTER`, which wins over `.cdidxrc.json`'s `stale_after`, then the 24-hour default. Supported duration suffixes are `m`, `h`, and `d`. JSON output includes `stale_after_seconds` and `index_age_seconds` only for `--check`, so clients can confirm which threshold was applied without inferring it from text. +`status --json` emits structured readiness guidance whenever any trust field is degraded. The top-level `degraded_root_cause` is a stable machine-readable primary code, while `readiness_degradations[]` lists every degraded field with `root_cause`, human `degraded_reason`, `recommended_action`, and `alternative_action`. `migration_in_progress` is set from the active batch marker so clients can distinguish a temporary writer/migration window from a permanently degraded index. `issues_table_available` means the physical `file_issues` table exists; `file_issues_data_current` is the freshness/trust bit consumers should use before treating validate rows as authoritative. + ### Workspace version pinning On startup, `cdidx` walks up from the current directory looking for `.cdidx-version`. The first non-empty line is treated as the required CLI version for that workspace. A mismatch prints a warning and continues by default; `--strict-version` or `CDIDX_STRICT_VERSION=1` turns the mismatch into exit code `64` (`EX_USAGE`). This check is advisory and does not rewrite the file. Use it to keep teams on the same binary when index contracts or query behavior differ between releases. @@ -798,7 +800,7 @@ Adding `--json-envelope` to a query command (`search`, `definition`, `references Every top-level CLI/MCP JSON DTO (`StatusResult`, `RepoMapResult`, `SymbolAnalysisResult`, `ImpactAnalysisResult`, `OutlineResult`, `FileExcerptResult`, `CompactSearchResult`, `SymbolResult`, `DefinitionResult`, `UnusedSymbolResult`, `ReferenceResult`, `CallerResult`, `CalleeResult`, `FileResult`, `FileFindResult`) carries an `api_version` string field stamped from `JsonOutputContract.ApiVersion`. The same value is mirrored on the `--json-envelope` `metadata` block. This describes the JSON output contract, not the cdidx binary version (which is still surfaced via `version.json` and `cdidx --version`). Bump `JsonOutputContract.ApiVersion` only on **breaking** shape changes — renames, removals, or type changes of an existing field. Additive changes (new optional fields, new readiness flags, new enum values) keep the version stable so older consumers continue to parse the payload. Strict downstream consumers should pin against the major value and degrade gracefully when it changes. Issue #1555. -The documented `status --json` trust contract spans `fold_ready`, `fold_ready_reason`, `graph_table_available`, `issues_table_available`, `sql_graph_contract_ready`, `sql_graph_contract_degraded_reason`, `hotspot_family_ready`, `hotspot_family_degraded_reason`, `csharp_symbol_name_ready`, `csharp_metadata_target_ready`, `csharp_metadata_target_degraded_reason`, `indexed_head_commit`, `worktree_head_changed`, `indexed_head_sha`, `indexed_head_branch`, `indexed_head_timestamp`, `commits_ahead_of_indexed_head`, `index_writer_version`, `index_newer_than_reader`, `index_newer_than_reader_reason`, `unknown_extension_file_count`, `path_case_sensitive`, `data_dir_mode`, `mac_profile`, `stale_after_seconds`, `index_age_seconds`, plus the fold-only and C# metadata-target-only remediation fields `degraded_reason`, `recommended_action`, `alternative_action`, and MCP-only `mcp_session`. MCP `mcp_session` is session-scoped diagnostics, not persisted DB state, and contains `log_level`, `roots`, optional `client_info`, and optional `client_capabilities`. Keep this list synchronized with `README.md` and `AGENT_GUIDE.md`; `DocumentationStatusContractTests` fails when any required field is missing from one of those docs. +The documented `status --json` trust contract spans `fold_ready`, `fold_ready_reason`, `graph_table_available`, `issues_table_available`, `file_issues_data_current`, `migration_in_progress`, `sql_graph_contract_ready`, `sql_graph_contract_degraded_reason`, `hotspot_family_ready`, `hotspot_family_degraded_reason`, `csharp_symbol_name_ready`, `csharp_metadata_target_ready`, `csharp_metadata_target_degraded_reason`, `indexed_head_commit`, `worktree_head_changed`, `indexed_head_sha`, `indexed_head_branch`, `indexed_head_timestamp`, `commits_ahead_of_indexed_head`, `index_writer_version`, `index_newer_than_reader`, `index_newer_than_reader_reason`, `unknown_extension_file_count`, `path_case_sensitive`, `data_dir_mode`, `mac_profile`, `stale_after_seconds`, `index_age_seconds`, the remediation fields `degraded_root_cause`, `degraded_reason`, `recommended_action`, `alternative_action`, `readiness_degradations`, and MCP-only `mcp_session`. MCP `mcp_session` is session-scoped diagnostics, not persisted DB state, and contains `log_level`, `roots`, optional `client_info`, and optional `client_capabilities`. Keep this list synchronized with `README.md` and `AGENT_GUIDE.md`; `DocumentationStatusContractTests` fails when any required field is missing from one of those docs. `references` already prefixes each human-readable row with `reference_kind`, and `callers` does the same for its grouped caller rows. When one grouped container mixes kinds (for example `call` and `subscribe` on the same event member), the human-readable label joins the distinct kinds with `+` (for example `call+subscribe`) instead of collapsing to a single preferred label, and the reference-kind column widens dynamically to fit the longest label in the batch so mixed rows do not overrun the neighbouring column. JSON output for `callers` and `callees` keeps the scalar `reference_kind` for back-compat (it reports the preferred summary kind `instantiate` > `subscribe` > `MIN(call)`) and adds a sorted `reference_kinds` array plus a `has_mixed_reference_kinds` bool so consumers can detect mixed containers without trusting a single collapsed label. This lets terminal users distinguish `call` / `instantiate` / `subscribe` / mixed without re-running the command with `--json` and lets AI clients answer mixed-kind questions without chasing a second `--exact` query. @@ -1922,6 +1924,8 @@ editor integration は標準的な location 形状を直接要求できる。`de `status --check` の DB/worktree checksum 比較は `IndexFreshnessChecker` に置き、ユーザー向け age hint のしきい値は `QueryCommandRunner` で解決する。優先順位は CLI の `--stale-after `、`CDIDX_STALE_AFTER`、`.cdidxrc.json` の `stale_after`、24 時間の既定値。duration suffix は `m` / `h` / `d` をサポートする。JSON 出力では `--check` 時のみ `stale_after_seconds` と `index_age_seconds` を返し、クライアントが text を解析せずに適用しきい値を確認できるようにする。 +`status --json` は trust field のいずれかが degraded の場合に structured readiness guidance を出す。トップレベルの `degraded_root_cause` は primary の安定した machine-readable code で、`readiness_degradations[]` は degraded な各 field と `root_cause`、人間向け `degraded_reason`、`recommended_action`、`alternative_action` を列挙する。`migration_in_progress` は active batch marker から設定し、一時的な writer/migration window と恒久的な degraded index をクライアントが区別できるようにする。`issues_table_available` は物理的な `file_issues` table の存在を意味し、validate rows を authoritative として扱う前の freshness/trust bit は `file_issues_data_current` を使う。 + ### SQLite WAL durability policy `DbContext` は writable な index を WAL mode で開き、新規の空 DB では schema 作成前に `PRAGMA auto_vacuum=INCREMENTAL` を設定し、`PRAGMA synchronous=NORMAL`、`PRAGMA wal_autocheckpoint=1000` を固定する。WAL が有効な場合、永続化された SQLite index は `.db` ファイルと sibling の `.db-wal` / `.db-shm` ファイルの組で構成される。backup、diagnostics bundle、手動 copy では sibling が存在する場合に 3 ファイルすべてを含めるか、live connection から SQLite の `.backup` command/API を使う必要がある。`codeindex.db` だけを copy すると、committed page がまだ `codeindex.db-wal` に残っているため stale snapshot になる可能性がある。 @@ -2541,7 +2545,7 @@ USER_GUIDEの[終了コード](USER_GUIDE.md#終了コード)セクションを - **AI向けの軽量検索スニペット** — `search --json` と MCP の `search` は、チャンク全文ではなく snippet range、match line、highlight、context count、`truncated_line_count`、`truncation_context` を持つ一致中心スニペットを返す。`truncation_context.char_counts` と `truncation_context.total_chars` はクランプされた各スニペット行の省略文字数を公開し、truncated な highlight も `truncated_char_counts` を持つ。`--snippet-lines` でペイロード量と文脈量のバランスを取れ、`--max-line-width`(CLI)/ `maxLineWidth`(MCP)は `find` / `references` / `excerpt` / `inspect` と同じ共有 `LineWidthFormatter.ClampLine` 契約で各スニペット行を最初のマッチトークン周辺にクランプするため、minified / transpiled / 生成された 1 行ファイル内の 1 ヒットで数百 KB を返さなくなる。クランプされた行はスニペットに `...(+N)...` マーカーが入り、`highlights[].truncated` と `highlights[].original_line_length` で AI クライアントがクランプを検出できる。 - **初動向けの repo map** — `map` は、インデックス済みデータから言語、モジュール、主要ファイル、ホットスポット、推定エントリポイントを集約し、AIクライアントが精密検索前に見るべき場所を決めやすくする。シンボル抽出が `Main` 系シンボルを出さない場合でも、既知のトップレベル実行ファイルへフォールバックして入口候補を補う。 - **信用判断のための鮮度メタデータ** — `status` はワークスペース全体の鮮度と git 状態を返す。`map` は `indexed_at` / `latest_modified` を絞り込み結果の鮮度として維持しつつ、`workspace_indexed_at` / `workspace_latest_modified` でワークスペース全体の鮮度も返す。`inspect` も同じワークスペース鮮度と git フィールドを返すため、シンボル中心の AI フローで `status` を別途呼ばずに済む。さらに `status` は `sql_graph_contract_ready` / `sql_graph_contract_degraded_reason`、`hotspot_family_ready` / `hotspot_family_degraded_reason` に加えて、forward-compatibility 監査 (`index_writer_version`、`index_newer_than_reader`、`index_newer_than_reader_reason`、詳細は「リーダー側の forward-compatibility 監査」を参照)、および fold-only remediation 用の `fold_ready_reason`、`degraded_reason`、`recommended_action`、`alternative_action` も返すため、AI クライアントは SQL graph/dependency/impact、duplicate-name hotspot family、Unicode `--exact` のどれが authoritative か、また DB が現在の binary より新しい `cdidx` で書かれていないかを最初に判断できる。現行の全体 scan 後は `unknown_extension_file_count` も返すため、未知拡張子で index 対象外になった件数を `status` から確認できる。これらの fold-only remediation field は、明示的な read-only `file:///...?...` DB URI から導出された場合でも、失敗する read-only URI をそのままコマンドへ埋め込まず、writable な filesystem path に正規化して返す。さらに `impact` / MCP `impact_analysis` に加えて、`inspect` / MCP `analyze_symbol`、`references` / `callers` / `callees`、`deps` / `unused` / `hotspots` 系も、SQL ベースの graph/dependency read が実際に結果へ関与したときだけ `sql_graph_contract_ready` / `sql_graph_contract_degraded_reason` を反映するため、stale な SQL 行が authoritative なヒットや 0 件応答に見えてしまうのを防ぎつつ、mixed-language index 内の純粋な非SQL結果を誤って degraded 扱いしない。`files` はファイルごとの checksum・modified・indexed timestamp を返す。古いDBに対する file 列の移行は可能なら自動で行い、その場移行できない場合でも読み取り経路がクラッシュしないようにする。CLI と MCP の 0 件 JSON レスポンスは `indexed_file_count`、`indexed_at`、`freshness_available` を含む。`freshness_available=true` で `indexed_at:null` なら空インデックス、`freshness_available=false` なら legacy/read-only DB で鮮度 timestamp を取得できず、理由は `freshness_degraded_reason` に入る。**HEAD 起点の stale 検知**: `cdidx index` の full scan が成功するたびに、現時点の `git HEAD` を `codeindex_meta` に stamp し、後続実行で workspace HEAD と比較できるようにする。`--rebuild` 指定なしに両者が異なる場合、CLI は `cdidx index --rebuild` を勧める `head_changed` 警告を表示し、`index --json` に `head_changed` / `prior_indexed_head_commit` / `current_head_commit` / `head_change_notice` を出力する。`status --check` も同じ比較を `workspace_check.head_changed` として公開し、差分時には `indexed_head_commit` / `workspace_head_commit` も併記するため、鮮度 gate ですでに `status --check` を通している AI クライアントは `git switch ` 後の既定の incremental scan を別クエリなしで拒否できる。`--commits` / `--files` の部分更新は意図的に記録 HEAD を維持し、次の full scan が worktree を再インデックスするまで stale 通知が継続する。非 Git workspace と HEAD を記録していない legacy DB は比較自体をスキップし、false-positive な警告を出さない。 -文書化された `status --json` trust contract は `fold_ready`、`fold_ready_reason`、`graph_table_available`、`issues_table_available`、`sql_graph_contract_ready`、`sql_graph_contract_degraded_reason`、`hotspot_family_ready`、`hotspot_family_degraded_reason`、`csharp_symbol_name_ready`、`csharp_metadata_target_ready`、`indexed_head_commit`、`worktree_head_changed`、`indexed_head_sha`、`indexed_head_branch`、`indexed_head_timestamp`、`commits_ahead_of_indexed_head`、`index_writer_version`、`index_newer_than_reader`、`index_newer_than_reader_reason`、`unknown_extension_file_count`、`path_case_sensitive`、`stale_after_seconds`、`index_age_seconds`、および fold-only remediation field の `degraded_reason`、`recommended_action`、`alternative_action`、および MCP 専用の `mcp_session` を対象にします。MCP `mcp_session` は永続化された DB 状態ではなく、セッション単位の診断情報で、`log_level`、`roots`、任意の `client_capabilities` を含みます。この一覧は `README.md` と `AGENT_GUIDE.md` に同期してください。いずれかの必須 field がこれらの docs から漏れると `DocumentationStatusContractTests` が失敗します。 +文書化された `status --json` trust contract は `fold_ready`、`fold_ready_reason`、`graph_table_available`、`issues_table_available`、`file_issues_data_current`、`migration_in_progress`、`sql_graph_contract_ready`、`sql_graph_contract_degraded_reason`、`hotspot_family_ready`、`hotspot_family_degraded_reason`、`csharp_symbol_name_ready`、`csharp_metadata_target_ready`、`csharp_metadata_target_degraded_reason`、`indexed_head_commit`、`worktree_head_changed`、`indexed_head_sha`、`indexed_head_branch`、`indexed_head_timestamp`、`commits_ahead_of_indexed_head`、`index_writer_version`、`index_newer_than_reader`、`index_newer_than_reader_reason`、`unknown_extension_file_count`、`path_case_sensitive`、`stale_after_seconds`、`index_age_seconds`、remediation field の `degraded_root_cause`、`degraded_reason`、`recommended_action`、`alternative_action`、`readiness_degradations`、および MCP 専用の `mcp_session` を対象にします。MCP `mcp_session` は永続化された DB 状態ではなく、セッション単位の診断情報で、`log_level`、`roots`、任意の `client_info`、任意の `client_capabilities` を含みます。この一覧は `README.md` と `AGENT_GUIDE.md` に同期してください。いずれかの必須 field がこれらの docs から漏れると `DocumentationStatusContractTests` が失敗します。 - **再解析不要の folded-key アップグレード** — `backfill-fold` と MCP `backfill_fold` は、既存 DB 行から `name_folded` / `*_folded` を直接再計算し、必要な folded 値に NULL が残っていないことを検証してから `FoldReadyFlag` を stamp する。これにより、pre-#86 DB から AI クライアントやユーザーが低コストで Unicode `--exact` へ上がれる。さらに `fold_key_version` が未記録または不一致なら全 folded 行を再生成するため、将来の `NameFold.Version` 変更後に古い key を silent に再 stamp してしまうことも防ぐ。 - **まとめて取るシンボル分析** — `inspect` と MCP の `analyze_symbol` は、定義、近傍シンボル、参照、caller、callee、ファイルメタデータ、ワークスペース信頼メタデータ、graph 対応メタデータを1回で返し、AIクライアントが一般的なシンボル調査を少ない往復で終えやすくする。 - **言語考慮の参照抽出** — `references`、`callers`、`callees` は、正規表現ベースの call/reference 抽出が意味を持つ言語だけに対してインデックス化された参照テーブルで支える。未対応言語では、低信頼な疑似グラフ結果を返す代わりにテキスト検索へ戻る前提で設計する。**nested generic 呼び出し**: `new Dictionary>()` のような C#/Java のコンストラクタ呼び出しと、`Helper.DoWork>()` のような C# generic method call は、平坦な regex fast-path で `>>` を釣り合わせられなくても depth-aware fallback scanner で拾い直し、外側 target を参照テーブルへ残す。**コンストラクタ連鎖呼び出し**: C# の `: this(...)` / `: base(...)` イニシャライザと、Java のコンストラクタ本体冒頭文 `this(...)` / `super(...)` は、汎用 call regex とは別に検出し、呼び先が実際のコンストラクタとなるように書き換える(`this` は外側の class/record、`base` / `super` は外側クラスのシグネチャから解析した基底型)。C# のクロス行イニシャライザは外側クラスではなく、そのコンストラクタに紐付ける。基底型の解析は generic 引数、record のプライマリコンストラクタ引数、`where` 制約、`global::` やドット付きの namespace 修飾を剥がす。Java の `super.method()` は通常のメソッド呼び出しのまま扱う。**型位置の依存エッジ**: C#/Java の継承リスト、宣言型、generic 制約、`throws`、`is` / `as` / `instanceof`、および実際の C# XML doc `///` `cref` は `type_reference` 行として索引し、既定の `callers` / `callees` が見せる動的 call graph を汚さずに、`references` / `impact` から compile-time rename 依存を辿れるようにする。**SQL qualified-name alignment**: SQL の graph/dependency reader は、各 reference 行の source-line context、記録済み call 列位置、enclosing container から SQL 参照名を復元して定義と照合するため、qualified な `references` / `callers` / `impact` query は exact / non-exact を問わず sibling schema へ widen しない。source 側が genuinely unqualified な場合にだけ bare leaf fallback を許可するので、qualified call を含む `deps` / `unused` / `hotspots` も schema 単位で整合し、`EXEC dbo.fn_Target; EXEC sales.fn_Target;` のような同一行 multi-call も二重計上しない。列位置が記録されている row は、その列に qualified token が見つからなければ whole-line の別 qualified token へ昇格させないため、行末コメント・文字列リテラル・後続の別 call が先頭の unqualified edge を横取りすることもない。qualified な `callees` query でも caller query 自体が unqualified なとき以外は leaf fallback を無効化したため、`callees sales.Caller` が `dbo.Caller` へ広がらない。SQL extractor は qualified-name の `.` 前後空白も許容し、definition 系 reader は quoted qualified SQL name (`[dbo].[fn_X]` → `dbo.fn_X`) を正規化してから照合する。さらに exact SQL 定義照合は segment 数を保持し、SQL の exact graph leaf fallback は Unicode folded exact path を維持する。SQL CTE 本体内の source 行は raw `cte_body_reference` kind を使うため、`references --kind cte_body_reference` で anchor/recursive member 内部を outer query の table reference と区別できる。そのため、quoted single identifier の衝突や Unicode exact lookup の ASCII-only `NOCASE` 退行も防ぐ。 diff --git a/README.md b/README.md index fdb362a64b..8518716460 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ The documented `status --json` trust contract covers these fields: + @@ -149,6 +150,8 @@ The documented `status --json` trust contract covers these fields:
fold_readyfold_ready_reasongraph_table_availableissues_table_available
file_issues_data_currentmigration_in_progressdegraded_root_causereadiness_degradations
sql_graph_contract_readysql_graph_contract_degraded_reasonhotspot_family_readyhotspot_family_degraded_reason
csharp_symbol_name_readycsharp_metadata_target_readycsharp_metadata_target_degraded_reasonindexed_head_commit
worktree_head_changedindexed_head_shaindexed_head_branchindexed_head_timestamp
+When any readiness field is degraded, `degraded_root_cause` identifies the primary stable code and `readiness_degradations[]` lists every degraded field with `root_cause`, human `degraded_reason`, `recommended_action`, and `alternative_action`. `issues_table_available` reports physical table presence; use `file_issues_data_current` to decide whether `file_issues` rows are current for the index generation. + For MCP `status`, `mcp_session` is session-scoped diagnostic data rather than persisted index state. It includes `log_level`, `roots`, optional `client_info`, and optional `client_capabilities`. `hotspot_family_degraded_reason` uses these values: @@ -328,6 +331,7 @@ cdidx mcp + @@ -339,6 +343,8 @@ cdidx mcp
fold_readyfold_ready_reasongraph_table_availableissues_table_available
file_issues_data_currentmigration_in_progressdegraded_root_causereadiness_degradations
sql_graph_contract_readysql_graph_contract_degraded_reasonhotspot_family_readyhotspot_family_degraded_reason
csharp_symbol_name_readycsharp_metadata_target_readycsharp_metadata_target_degraded_reasonindexed_head_commit
worktree_head_changedindexed_head_shaindexed_head_branchindexed_head_timestamp
+readiness field のいずれかが degraded の場合、`degraded_root_cause` は primary の安定コードを示し、`readiness_degradations[]` は degraded な各 field と `root_cause`、人間向け `degraded_reason`、`recommended_action`、`alternative_action` を列挙します。`issues_table_available` は物理 table の有無を表し、`file_issues` 行が現在の index generation に対して current かどうかは `file_issues_data_current` を使って判定します。 + MCP `status` の `mcp_session` は永続化された index 状態ではなく、セッション単位の診断情報です。`log_level`、`roots`、任意の `client_info`、任意の `client_capabilities` を含みます。 `hotspot_family_degraded_reason` は次の値を使います。 diff --git a/changelog.d/unreleased/1484.fixed.md b/changelog.d/unreleased/1484.fixed.md new file mode 100644 index 0000000000..e1c881153d --- /dev/null +++ b/changelog.d/unreleased/1484.fixed.md @@ -0,0 +1,24 @@ +--- +category: fixed +issues: + - 1484 + - 1486 + - 1761 + - 1931 +affected: + - src/CodeIndex/Cli/QueryCommandRunner.cs + - src/CodeIndex/Database/DbReader.FilesStatus.cs + - src/CodeIndex/Models/QueryResults.cs +--- + +## English + +- **Status readiness degradation now has structured guidance (#1484, #1931)** — `status --json` now reports `degraded_root_cause` and `readiness_degradations[]` for every degraded readiness field, including multi-degraded states. +- **Status separates issue-table presence from issue-row freshness (#1486)** — `issues_table_available` now reports physical table presence, while `file_issues_data_current` reports whether validate issue rows are current for the index generation. +- **Status identifies active migration/write windows (#1761)** — `status --json` now exposes `migration_in_progress` so clients can distinguish temporary writer activity from permanent readiness degradation. + +## 日本語 + +- **status readiness degradation に構造化 guidance を追加しました (#1484, #1931)** — `status --json` は multi-degraded 状態を含むすべての degraded readiness field について `degraded_root_cause` と `readiness_degradations[]` を返すようになりました。 +- **status が issue table の存在と issue row の鮮度を分離しました (#1486)** — `issues_table_available` は物理 table の存在を表し、`file_issues_data_current` が validate issue row が現在の index generation に対して current かどうかを表します。 +- **status が active migration/write window を識別するようになりました (#1761)** — `status --json` は `migration_in_progress` を公開し、クライアントが一時的な writer activity と恒久的な readiness degradation を区別できるようになりました。 diff --git a/src/CodeIndex/Cli/JsonOutputContracts.cs b/src/CodeIndex/Cli/JsonOutputContracts.cs index 4de679feff..0159343e4c 100644 --- a/src/CodeIndex/Cli/JsonOutputContracts.cs +++ b/src/CodeIndex/Cli/JsonOutputContracts.cs @@ -345,6 +345,7 @@ internal sealed record VersionInfoJsonResult( [JsonSerializable(typeof(SearchTermOccurrence))] [JsonSerializable(typeof(SearchTruncationContext))] [JsonSerializable(typeof(StatusResult))] +[JsonSerializable(typeof(StatusReadinessDegradation))] [JsonSerializable(typeof(StatusDbPragmaSettings))] [JsonSerializable(typeof(SuggestionDetailJsonResult))] [JsonSerializable(typeof(SuggestionExportJsonResult))] diff --git a/src/CodeIndex/Cli/QueryCommandRunner.cs b/src/CodeIndex/Cli/QueryCommandRunner.cs index a86e79975a..2ab4090416 100644 --- a/src/CodeIndex/Cli/QueryCommandRunner.cs +++ b/src/CodeIndex/Cli/QueryCommandRunner.cs @@ -109,9 +109,21 @@ private sealed record StatusReadinessField( new( "issues_table_available", "Validation issues table", - "validate can read indexed file issue rows.", + "the file_issues table exists in this index.", "validate output degrades to empty because the file_issues table is missing.", "Run `cdidx index ` to rebuild the issue table."), + new( + "file_issues_data_current", + "Validation issues data", + "file_issues rows are stamped current for this index generation.", + "file_issues rows may be stale or partial for this index generation.", + "Run `cdidx index ` to refresh file issue rows."), + new( + "migration_in_progress", + "Migration/write state", + "no index write or migration is currently in progress.", + "an index write or migration is in progress, so readiness may be temporarily degraded.", + "Wait for the active `cdidx index` run to finish, then rerun `cdidx status --json`."), new( "sql_graph_contract_ready", "SQL graph contract", @@ -2651,20 +2663,7 @@ public static int RunStatus(string[] cmdArgs, JsonSerializerOptions jsonOptions, var topLangs = status.Languages.OrderByDescending(kv => kv.Value).Take(3).Select(kv => kv.Key); var freshness = BuildStatusFreshnessLabel(status); var dirty = status.GitIsDirty == true ? ", dirty" : ""; - if (IsFoldOnlyReadinessDegraded(status)) - { - status.DegradedReason = BuildFoldNotReadyExplanation(status.FoldReadyReason); - status.RecommendedAction = BuildFoldBackfillCommand(options.DbPath, options.DbPathExplicit); - status.AlternativeAction = BuildFoldRebuildRepairCommand(status.ProjectRoot, options.DbPath, options.DbPathExplicit); - } - else if (IsCSharpMetadataTargetOnlyReadinessDegraded(status)) - { - var metadata = DegradationReasonCodes.GetMetadata( - status.CSharpMetadataTargetDegradedReason ?? DegradationReasonCodes.CSharpMetadataTargetNotReady); - status.DegradedReason = metadata.Code; - status.RecommendedAction = metadata.RecommendedAction; - status.AlternativeAction = metadata.AlternativeAction; - } + ApplyStatusDegradationGuidance(status, options); var degraded = IsStatusDegraded(status) ? ", DEGRADED" @@ -2773,6 +2772,8 @@ public static int RunStatus(string[] cmdArgs, JsonSerializerOptions jsonOptions, Console.WriteLine(ConsoleUi.FormatSummaryLine("WARN", "symbol_references table missing — reference / caller / callee / unused counts are degraded to 0.")); if (!status.IssuesTableAvailable) Console.WriteLine(ConsoleUi.FormatSummaryLine("WARN", "file_issues table missing — validate output is degraded to empty.")); + else if (!status.FileIssuesDataCurrent) + Console.WriteLine(ConsoleUi.FormatSummaryLine("WARN", "file_issues table exists but its rows are not stamped current for this index generation.")); if (!status.SqlGraphContractReady) Console.WriteLine(ConsoleUi.FormatSummaryLine("WARN", $"SQL graph/dependency results may be stale. Run `{BuildSqlGraphContractRepairCommand(status.ProjectRoot, options.DbPath, options.DbPathExplicit)}` before trusting SQL references/callers/deps/unused/hotspots.")); if (!status.HotspotFamilyReady && status.HotspotFamilyDegradedReason != null) @@ -6419,6 +6420,8 @@ private static bool IsStatusReadinessFieldDegraded(StatusResult status, string f { "graph_table_available" => !status.GraphTableAvailable, "issues_table_available" => !status.IssuesTableAvailable, + "file_issues_data_current" => !status.FileIssuesDataCurrent, + "migration_in_progress" => status.MigrationInProgress, "sql_graph_contract_ready" => !status.SqlGraphContractReady, "hotspot_family_ready" => !status.HotspotFamilyReady, "csharp_symbol_name_ready" => !status.CSharpSymbolNameReady, @@ -6437,6 +6440,8 @@ private static string BuildStatusReadinessDegradedDetail(StatusResult status, Qu "index_newer_than_reader" => status.IndexNewerThanReaderReason ?? fallback, "graph_table_available" => DegradationReasonCodes.GetMetadata(DegradationReasonCodes.GraphTableMissing).HumanText, "issues_table_available" => DegradationReasonCodes.GetMetadata(DegradationReasonCodes.IssuesTableMissing).HumanText, + "file_issues_data_current" => DegradationReasonCodes.GetMetadata(DegradationReasonCodes.FileIssuesDataStale).HumanText, + "migration_in_progress" => DegradationReasonCodes.GetMetadata(DegradationReasonCodes.MigrationInProgress).HumanText, "csharp_symbol_name_ready" => DegradationReasonCodes.GetMetadata(DegradationReasonCodes.CSharpSymbolNameNotReady).HumanText, "csharp_metadata_target_ready" => DegradationReasonCodes.GetMetadata(status.CSharpMetadataTargetDegradedReason ?? DegradationReasonCodes.CSharpMetadataTargetNotReady).HumanText, _ => fallback, @@ -6449,19 +6454,84 @@ private static string BuildStatusReadinessRemediation(StatusResult status, Query "csharp_symbol_name_ready" => $"Run `{BuildCSharpCanonicalNameRepairCommand(status.ProjectRoot, options.DbPath, options.DbPathExplicit)}` to upgrade canonical C# symbol names in place.", "fold_ready" => $"Run `{BuildFoldBackfillCommand(options.DbPath, options.DbPathExplicit)}` to restamp folded-name columns in place, or `{BuildFoldRebuildRepairCommand(status.ProjectRoot, options.DbPath, options.DbPathExplicit)}` for a full rebuild.", "csharp_metadata_target_ready" => DegradationReasonCodes.GetMetadata(status.CSharpMetadataTargetDegradedReason ?? DegradationReasonCodes.CSharpMetadataTargetNotReady).RecommendedAction, + "file_issues_data_current" => DegradationReasonCodes.GetMetadata(DegradationReasonCodes.FileIssuesDataStale).RecommendedAction, + "migration_in_progress" => DegradationReasonCodes.GetMetadata(DegradationReasonCodes.MigrationInProgress).RecommendedAction, "index_newer_than_reader" => "Run status with a current cdidx binary, or rebuild the DB with the version you intend to use.", _ => fallback, }; + private static void ApplyStatusDegradationGuidance(StatusResult status, QueryCommandOptions options) + { + var degradations = BuildStatusReadinessDegradations(status, options); + if (degradations.Count == 0) + return; + + status.ReadinessDegradations = degradations; + var primary = degradations[0]; + status.DegradedRootCause = primary.RootCause; + status.DegradedReason = primary.DegradedReason; + status.RecommendedAction = primary.RecommendedAction; + status.AlternativeAction = primary.AlternativeAction; + } + + private static List BuildStatusReadinessDegradations(StatusResult status, QueryCommandOptions options) + { + var result = new List(); + if (status.MigrationInProgress) + result.Add(BuildStatusReadinessDegradation("migration_in_progress", DegradationReasonCodes.MigrationInProgress, options, status)); + if (!status.GraphTableAvailable) + result.Add(BuildStatusReadinessDegradation("graph_table_available", DegradationReasonCodes.GraphTableMissing, options, status)); + if (!status.IssuesTableAvailable) + result.Add(BuildStatusReadinessDegradation("issues_table_available", DegradationReasonCodes.IssuesTableMissing, options, status)); + else if (!status.FileIssuesDataCurrent) + result.Add(BuildStatusReadinessDegradation("file_issues_data_current", DegradationReasonCodes.FileIssuesDataStale, options, status)); + if (!status.SqlGraphContractReady) + result.Add(BuildStatusReadinessDegradation("sql_graph_contract_ready", DegradationReasonCodes.SqlGraphContractNotReady, options, status)); + if (!status.HotspotFamilyReady) + result.Add(BuildStatusReadinessDegradation("hotspot_family_ready", DegradationReasonCodes.HotspotFamilyNotReady, options, status)); + if (!status.CSharpSymbolNameReady) + result.Add(BuildStatusReadinessDegradation("csharp_symbol_name_ready", DegradationReasonCodes.CSharpSymbolNameNotReady, options, status)); + if (!status.CSharpMetadataTargetReady) + result.Add(BuildStatusReadinessDegradation("csharp_metadata_target_ready", status.CSharpMetadataTargetDegradedReason ?? DegradationReasonCodes.CSharpMetadataTargetNotReady, options, status)); + if (!status.FoldReady) + result.Add(BuildStatusReadinessDegradation("fold_ready", DegradationReasonCodes.NormalizeFoldReason(status.FoldReadyReason), options, status)); + if (status.IndexNewerThanReader) + result.Add(BuildStatusReadinessDegradation("index_newer_than_reader", DegradationReasonCodes.IndexNewerThanReader, options, status)); + return result; + } + + private static StatusReadinessDegradation BuildStatusReadinessDegradation(string field, string rootCause, QueryCommandOptions options, StatusResult status) + { + var metadata = DegradationReasonCodes.GetMetadata(rootCause); + return new StatusReadinessDegradation + { + Field = field, + RootCause = metadata.Code, + DegradedReason = metadata.HumanText, + RecommendedAction = field switch + { + "fold_ready" => BuildFoldBackfillCommand(options.DbPath, options.DbPathExplicit), + "sql_graph_contract_ready" => BuildSqlGraphContractRepairCommand(status.ProjectRoot, options.DbPath, options.DbPathExplicit), + "csharp_symbol_name_ready" => BuildCSharpCanonicalNameRepairCommand(status.ProjectRoot, options.DbPath, options.DbPathExplicit), + _ => metadata.RecommendedAction, + }, + AlternativeAction = field == "fold_ready" + ? BuildFoldRebuildRepairCommand(status.ProjectRoot, options.DbPath, options.DbPathExplicit) + : metadata.AlternativeAction, + }; + } + private static bool IsStatusDegraded(StatusResult status) => !status.GraphTableAvailable || !status.IssuesTableAvailable + || !status.FileIssuesDataCurrent || !status.SqlGraphContractReady || !status.HotspotFamilyReady || !status.CSharpSymbolNameReady || !status.CSharpMetadataTargetReady || !status.FoldReady - || status.IndexNewerThanReader; + || status.IndexNewerThanReader + || status.MigrationInProgress; private sealed record StatusCheckFailure(string Name, bool IsStale, string Diagnostic); @@ -6491,6 +6561,10 @@ private static IReadOnlyList BuildStatusCheckFailures(Status failures.Add(new StatusCheckFailure("graph_table_available", false, "[degraded] graph_table_available=false")); if (Includes("issues") && !status.IssuesTableAvailable) failures.Add(new StatusCheckFailure("issues_table_available", false, "[degraded] issues_table_available=false")); + if (Includes("issues") && status.IssuesTableAvailable && !status.FileIssuesDataCurrent) + failures.Add(new StatusCheckFailure("file_issues_data_current", false, "[degraded] file_issues_data_current=false")); + if (Includes("workspace") && status.MigrationInProgress) + failures.Add(new StatusCheckFailure("migration_in_progress", false, "[degraded] migration_in_progress=true")); if (Includes("sql") && !status.SqlGraphContractReady) failures.Add(new StatusCheckFailure("sql_graph_contract_ready", false, $"[degraded] sql_graph_contract_ready=false reason={status.SqlGraphContractDegradedReason ?? "unknown"}")); if (Includes("hotspot") && !status.HotspotFamilyReady) diff --git a/src/CodeIndex/Database/DbReader.FilesStatus.cs b/src/CodeIndex/Database/DbReader.FilesStatus.cs index c53274c6ff..2c3dd281fc 100644 --- a/src/CodeIndex/Database/DbReader.FilesStatus.cs +++ b/src/CodeIndex/Database/DbReader.FilesStatus.cs @@ -450,6 +450,10 @@ public StatusResult GetStatus() // #1546: case-sensitivity stamp も同 snapshot で読む。stamp 無し旧 DB は null。 var pathCaseSensitive = ParseMetaBool(TryGetMetaStringInternal(DbContext.WorkspacePathCaseSensitiveMetaKey)); var dbPragmaSettings = GetDbPragmaSettings(); + var batchInProgress = string.Equals( + TryGetMetaStringInternal(DbContext.BatchInProgressMetaKey), + "true", + StringComparison.OrdinalIgnoreCase); var result = new StatusResult { @@ -465,7 +469,9 @@ public StatusResult GetStatus() IndexedHeadTimestamp = indexedHeadTimestamp, Languages = langs, GraphTableAvailable = _hasReferencesTable, - IssuesTableAvailable = _hasIssuesTable, + IssuesTableAvailable = _hasIssuesPhysicalTable, + FileIssuesDataCurrent = _hasIssuesTable, + MigrationInProgress = batchInProgress, HotspotFamilyReady = hotspotFamilySignal.Ready, HotspotFamilyDegradedReason = hotspotFamilySignal.DegradedReason, CSharpSymbolNameReady = csharpSymbolNameReady, diff --git a/src/CodeIndex/Database/DbReader.cs b/src/CodeIndex/Database/DbReader.cs index 12eaa374df..12f1d64d6c 100644 --- a/src/CodeIndex/Database/DbReader.cs +++ b/src/CodeIndex/Database/DbReader.cs @@ -67,6 +67,7 @@ public partial class DbReader private Dictionary? _csharpGlobalUsingAliasesByName; internal readonly bool _hasReferencesTable; internal readonly bool _hasIssuesTable; + internal readonly bool _hasIssuesPhysicalTable; internal readonly bool _hasChunksTable; internal readonly bool _hasReferenceLinesTable; internal readonly bool _canUseReferenceLines; @@ -485,7 +486,8 @@ private DbReader(SqliteConnection connection, bool isReadOnly, DbSchemaCache? sc } _hasChunksTable = HasTable("chunks"); _hasReferencesTable = HasTable("symbol_references") && (userVersion & DbContext.GraphReadyFlag) != 0; - _hasIssuesTable = HasTable("file_issues") && (userVersion & DbContext.IssuesReadyFlag) != 0; + _hasIssuesPhysicalTable = HasTable("file_issues"); + _hasIssuesTable = _hasIssuesPhysicalTable && (userVersion & DbContext.IssuesReadyFlag) != 0; _hasReferenceLinesTable = HasTable("reference_lines"); _canUseReferenceLines = _hasReferencesTable && _hasReferenceLinesTable && _referenceColumns.Contains("reference_line_id"); _referenceIndexes = LoadIndexes("symbol_references"); diff --git a/src/CodeIndex/Database/DegradationReasonCodes.cs b/src/CodeIndex/Database/DegradationReasonCodes.cs index 203eb25eef..dc84f44f35 100644 --- a/src/CodeIndex/Database/DegradationReasonCodes.cs +++ b/src/CodeIndex/Database/DegradationReasonCodes.cs @@ -21,11 +21,13 @@ public static class DegradationReasonCodes public const string HotspotFamilyDisabledAtIndexTime = "hotspot_family_disabled_at_index_time"; public const string GraphTableMissing = "graph_table_available=false"; public const string IssuesTableMissing = "issues_table_available=false"; + public const string FileIssuesDataStale = "file_issues_data_current=false"; public const string CSharpSymbolNameNotReady = "csharp_symbol_name_ready=false"; public const string CSharpMetadataTargetNotReady = "csharp_metadata_target_ready=false"; public const string CSharpMetadataTargetMissingColumn = "csharp_metadata_target_missing_column"; public const string CSharpMetadataTargetStampOutdated = "csharp_metadata_target_stamp_outdated"; public const string IndexNewerThanReader = "index_newer_than_reader=true"; + public const string MigrationInProgress = "migration_in_progress"; public static readonly IReadOnlyList All = [ @@ -42,11 +44,13 @@ public static class DegradationReasonCodes HotspotFamilyDisabledAtIndexTime, GraphTableMissing, IssuesTableMissing, + FileIssuesDataStale, CSharpSymbolNameNotReady, CSharpMetadataTargetNotReady, CSharpMetadataTargetMissingColumn, CSharpMetadataTargetStampOutdated, IndexNewerThanReader, + MigrationInProgress, ]; private static readonly IReadOnlyDictionary MetadataByCode = @@ -152,6 +156,11 @@ private static DegradationReasonMetadata CreateMetadata(string code) "Validate output is degraded to empty because the file_issues table is missing.", "Run `cdidx index ` to rebuild the issue table.", "Run `cdidx index --rebuild` for a full rebuild."), + FileIssuesDataStale => new( + code, + "The file_issues table exists, but its data is not stamped current for this index generation.", + "Run `cdidx index ` to refresh file issue rows.", + "Run `cdidx index --rebuild` for a full rebuild."), CSharpSymbolNameNotReady => new( code, "C# exact-name for operators / conversion operators / indexers is degraded.", @@ -177,6 +186,11 @@ private static DegradationReasonMetadata CreateMetadata(string code) "This DB was written by a newer cdidx, so older readers may degrade instead of trusting newer contract stamps.", "Run status with a current cdidx binary.", "Rebuild the DB with the cdidx version you intend to use."), + MigrationInProgress => new( + code, + "An index write or migration is currently in progress; readiness may be temporarily degraded until the writer finishes.", + "Wait for the active `cdidx index` run to finish, then rerun `cdidx status --json`.", + "If no index process is running, run `cdidx index --rebuild` to recover from the interrupted batch."), _ => throw new ArgumentOutOfRangeException(nameof(code), code, "Unknown degradation reason code.") }; } diff --git a/src/CodeIndex/Models/QueryResults.cs b/src/CodeIndex/Models/QueryResults.cs index 41f6ca2888..6c9b2d7b71 100644 --- a/src/CodeIndex/Models/QueryResults.cs +++ b/src/CodeIndex/Models/QueryResults.cs @@ -594,6 +594,10 @@ public class StatusResult /// public bool GraphTableAvailable { get; set; } = true; public bool IssuesTableAvailable { get; set; } = true; + [JsonPropertyName("file_issues_data_current")] + public bool FileIssuesDataCurrent { get; set; } = true; + [JsonPropertyName("migration_in_progress")] + public bool MigrationInProgress { get; set; } /// /// True when authoritative cross-file hotspot-family grouping metadata is current for every /// marker-capable language currently indexed in this DB. False means `hotspots` can still @@ -660,12 +664,18 @@ public class StatusResult [JsonPropertyName("degraded_reason")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? DegradedReason { get; set; } + [JsonPropertyName("degraded_root_cause")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? DegradedRootCause { get; set; } [JsonPropertyName("recommended_action")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? RecommendedAction { get; set; } [JsonPropertyName("alternative_action")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? AlternativeAction { get; set; } + [JsonPropertyName("readiness_degradations")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? ReadinessDegradations { get; set; } /// /// The cdidx version string that wrote the most recent successful end-of-index pass /// for this DB, stamped from `codeindex_meta.cdidx_writer_version`. Null on legacy @@ -710,6 +720,19 @@ public class StatusResult public StatusDbPragmaSettings DbPragmaSettings { get; set; } = new(); } +public class StatusReadinessDegradation +{ + public string Field { get; set; } = string.Empty; + [JsonPropertyName("root_cause")] + public string RootCause { get; set; } = string.Empty; + [JsonPropertyName("degraded_reason")] + public string DegradedReason { get; set; } = string.Empty; + [JsonPropertyName("recommended_action")] + public string RecommendedAction { get; set; } = string.Empty; + [JsonPropertyName("alternative_action")] + public string AlternativeAction { get; set; } = string.Empty; +} + public class StatusDbPragmaSettings { public string? JournalMode { get; set; } diff --git a/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs b/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs index 1328b3d0ae..35da7038f0 100644 --- a/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs +++ b/tests/CodeIndex.Tests/IndexCommandRunnerTests.cs @@ -2274,7 +2274,8 @@ BEFORE UPDATE ON files var (_, statusJson) = RunStatusAndCaptureJson(["--db", dbPath, "--json"]); Assert.False(statusJson.GetProperty("graph_table_available").GetBoolean()); - Assert.False(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.True(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.False(statusJson.GetProperty("file_issues_data_current").GetBoolean()); Assert.False(statusJson.GetProperty("fold_ready").GetBoolean()); using var verify = OpenNonPoolingConnection(dbPath); @@ -4328,7 +4329,8 @@ public void Run_UpdateMode_WithCommits_SkipsMutationWhenIgnoreRulesAreUnreadable var (statusExitCode, statusJson) = RunStatusAndCaptureJson(["--db", dbPath, "--json"]); Assert.Equal(CommandExitCodes.Success, statusExitCode); Assert.False(statusJson.GetProperty("graph_table_available").GetBoolean()); - Assert.False(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.True(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.False(statusJson.GetProperty("file_issues_data_current").GetBoolean()); Assert.False(statusJson.GetProperty("fold_ready").GetBoolean()); } finally @@ -4379,7 +4381,8 @@ public void Run_UpdateMode_WithFiles_UnreadableIgnoreRulesDemoteReadinessForUnch var (statusExitCode, statusJson) = RunStatusAndCaptureJson(["--db", dbPath, "--json"]); Assert.Equal(CommandExitCodes.Success, statusExitCode); Assert.False(statusJson.GetProperty("graph_table_available").GetBoolean()); - Assert.False(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.True(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.False(statusJson.GetProperty("file_issues_data_current").GetBoolean()); Assert.False(statusJson.GetProperty("fold_ready").GetBoolean()); } finally @@ -4431,7 +4434,8 @@ public void Run_UpdateMode_WithFiles_DemotesReadinessWhenIgnoreFileChangedThenBe var (statusExitCode, statusJson) = RunStatusAndCaptureJson(["--db", dbPath, "--json"]); Assert.Equal(CommandExitCodes.Success, statusExitCode); Assert.False(statusJson.GetProperty("graph_table_available").GetBoolean()); - Assert.False(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.True(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.False(statusJson.GetProperty("file_issues_data_current").GetBoolean()); Assert.False(statusJson.GetProperty("fold_ready").GetBoolean()); } finally @@ -4484,7 +4488,8 @@ public void Run_UpdateMode_WithFiles_UnreadableIgnoreRulesDemoteReadinessForChan var (statusExitCode, statusJson) = RunStatusAndCaptureJson(["--db", dbPath, "--json"]); Assert.Equal(CommandExitCodes.Success, statusExitCode); Assert.False(statusJson.GetProperty("graph_table_available").GetBoolean()); - Assert.False(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.True(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.False(statusJson.GetProperty("file_issues_data_current").GetBoolean()); Assert.False(statusJson.GetProperty("fold_ready").GetBoolean()); } finally @@ -5576,7 +5581,8 @@ public void Run_UpdateMode_WithFiles_DoesNotRemoveUnreadableExtensionlessScript( var (statusExitCode, statusJson) = RunStatusAndCaptureJson(["--db", dbPath, "--json"]); Assert.Equal(CommandExitCodes.Success, statusExitCode); Assert.False(statusJson.GetProperty("graph_table_available").GetBoolean()); - Assert.False(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.True(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.False(statusJson.GetProperty("file_issues_data_current").GetBoolean()); Assert.False(statusJson.GetProperty("fold_ready").GetBoolean()); } finally @@ -5621,7 +5627,8 @@ public void Run_UpdateMode_WithFiles_DemotesReadinessForUnreadableKnownExtension var (statusExitCode, statusJson) = RunStatusAndCaptureJson(["--db", dbPath, "--json"]); Assert.Equal(CommandExitCodes.Success, statusExitCode); Assert.False(statusJson.GetProperty("graph_table_available").GetBoolean()); - Assert.False(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.True(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.False(statusJson.GetProperty("file_issues_data_current").GetBoolean()); Assert.False(statusJson.GetProperty("fold_ready").GetBoolean()); } finally @@ -5664,7 +5671,8 @@ public void Run_UpdateMode_WithFiles_DemotesReadinessForUnreadableNewKnownExtens var (statusExitCode, statusJson) = RunStatusAndCaptureJson(["--db", dbPath, "--json"]); Assert.Equal(CommandExitCodes.Success, statusExitCode); Assert.False(statusJson.GetProperty("graph_table_available").GetBoolean()); - Assert.False(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.True(statusJson.GetProperty("issues_table_available").GetBoolean()); + Assert.False(statusJson.GetProperty("file_issues_data_current").GetBoolean()); Assert.False(statusJson.GetProperty("fold_ready").GetBoolean()); } finally diff --git a/tests/CodeIndex.Tests/JsonOutputSnapshotTests.cs b/tests/CodeIndex.Tests/JsonOutputSnapshotTests.cs index d268a27d5b..fa52e51dfa 100644 --- a/tests/CodeIndex.Tests/JsonOutputSnapshotTests.cs +++ b/tests/CodeIndex.Tests/JsonOutputSnapshotTests.cs @@ -213,12 +213,23 @@ private static void MarkGraphAndFoldReady(string dbPath) private static IReadOnlyList<(string Original, string Placeholder)> BuildPathReplacements(string projectRoot) { var canonical = Path.GetFullPath(projectRoot); + var canonicalDbPath = Path.Combine(canonical, ".cdidx", "codeindex.db"); var replacements = new List<(string, string)> { + (canonicalDbPath, "/.cdidx/codeindex.db"), (canonical, ""), }; + var jsonEscapedCanonicalDbPath = canonicalDbPath.Replace("\\", "\\\\", StringComparison.Ordinal); + if (!string.Equals(jsonEscapedCanonicalDbPath, canonicalDbPath, StringComparison.Ordinal)) + replacements.Add((jsonEscapedCanonicalDbPath, "/.cdidx/codeindex.db")); + var jsonEscapedCanonical = canonical.Replace("\\", "\\\\", StringComparison.Ordinal); + if (!string.Equals(jsonEscapedCanonical, canonical, StringComparison.Ordinal)) + replacements.Add((jsonEscapedCanonical, "")); if (!string.Equals(projectRoot, canonical, StringComparison.Ordinal)) replacements.Add((projectRoot, "")); + var jsonEscapedProjectRoot = projectRoot.Replace("\\", "\\\\", StringComparison.Ordinal); + if (!string.Equals(jsonEscapedProjectRoot, projectRoot, StringComparison.Ordinal)) + replacements.Add((jsonEscapedProjectRoot, "")); return replacements; } diff --git a/tests/CodeIndex.Tests/LegacySchemaMigrationTests.cs b/tests/CodeIndex.Tests/LegacySchemaMigrationTests.cs index e6eac69e62..779be59dc3 100644 --- a/tests/CodeIndex.Tests/LegacySchemaMigrationTests.cs +++ b/tests/CodeIndex.Tests/LegacySchemaMigrationTests.cs @@ -267,7 +267,8 @@ public void TryMigrateForRead_LegacyDb_ReadPathsDoNotCrash() Assert.Empty(bundle.Callees); var status = reader.GetStatus(); Assert.False(status.GraphTableAvailable); - Assert.False(status.IssuesTableAvailable); + Assert.True(status.IssuesTableAvailable); + Assert.False(status.FileIssuesDataCurrent); } [Fact] @@ -686,7 +687,8 @@ public void InterruptedIndex_SchemaInitializedButNotCompleted_StaysDegraded() // 版印なしの DB で空テーブルは縮退として扱うこと。 var status = reader.GetStatus(); Assert.False(status.GraphTableAvailable); - Assert.False(status.IssuesTableAvailable); + Assert.True(status.IssuesTableAvailable); + Assert.False(status.FileIssuesDataCurrent); } } finally @@ -717,7 +719,8 @@ public void PartialIndex_GraphReadyStampedButNotIssuesReady_IsTreatedAsPartialTr var reader = new DbReader(db.Connection); var status = reader.GetStatus(); Assert.True(status.GraphTableAvailable); - Assert.False(status.IssuesTableAvailable); + Assert.True(status.IssuesTableAvailable); + Assert.False(status.FileIssuesDataCurrent); } } finally @@ -757,7 +760,8 @@ public void InterruptedRefresh_OnAlreadyStampedDb_DemotesTrustUntilCompletion() var reader = new DbReader(db.Connection); var status = reader.GetStatus(); Assert.False(status.GraphTableAvailable); - Assert.False(status.IssuesTableAvailable); + Assert.True(status.IssuesTableAvailable); + Assert.False(status.FileIssuesDataCurrent); } } finally @@ -1041,7 +1045,8 @@ public void UpdateMode_OnLegacyDb_MustNotStampReadiness() var reader = new DbReader(db.Connection); var status = reader.GetStatus(); Assert.False(status.GraphTableAvailable); - Assert.False(status.IssuesTableAvailable); + Assert.True(status.IssuesTableAvailable); + Assert.False(status.FileIssuesDataCurrent); } } finally @@ -1134,7 +1139,8 @@ public void Rebuild_ClearsReadyFlagsBeforeDroppingTables() var reader = new DbReader(db.Connection); var status = reader.GetStatus(); Assert.False(status.GraphTableAvailable); - Assert.False(status.IssuesTableAvailable); + Assert.True(status.IssuesTableAvailable); + Assert.False(status.FileIssuesDataCurrent); } } finally diff --git a/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs b/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs index 2879bc0753..2e5ebde75e 100644 --- a/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs +++ b/tests/CodeIndex.Tests/QueryCommandRunnerTests.cs @@ -30253,9 +30253,9 @@ public void RunStatus_HumanOutput_TranslatesReadinessFields() Assert.Equal(string.Empty, stderr); Assert.Contains("Readiness:", stdout); Assert.Contains("Reference graph table", stdout); + Assert.Contains("Validation issues data", stdout); Assert.Contains("Unicode exact-name fold contract", stdout); Assert.Contains("C# metadata target contract", stdout); - Assert.Contains("validate output is degraded to empty", stdout); Assert.Contains("cdidx backfill-fold", stdout); } finally @@ -30478,6 +30478,48 @@ public void RunStatus_Json_UsesSourceNewerThanIndexAsStale() } } + [Fact] + public void RunStatus_Json_ReportsStructuredGuidanceForMultipleReadinessDegradations() + { + var projectRoot = TestProjectHelper.CreateTempProject("cdidx_query_runner_status_multi_degraded"); + try + { + var dbPath = TestProjectHelper.CreateProjectDb(projectRoot); + TestProjectHelper.InsertIndexedFile(dbPath, "src/app.cs", "csharp", "class App {}\n"); + using (var db = new DbContext(dbPath)) + { + var writer = new DbWriter(db.Connection); + writer.MarkBatchInProgress(); + } + + var (exitCode, stdout, stderr) = CaptureConsole(() => QueryCommandRunner.RunStatus( + ["--db", dbPath, "--json"], + _jsonOptions)); + + using var document = ParseJsonOutput(stdout); + var json = document.RootElement; + var degradations = json.GetProperty("readiness_degradations"); + + Assert.Equal(CommandExitCodes.Success, exitCode); + Assert.Contains("Last batch did not complete", stderr); + Assert.True(json.GetProperty("migration_in_progress").GetBoolean()); + Assert.True(json.GetProperty("issues_table_available").GetBoolean()); + Assert.False(json.GetProperty("file_issues_data_current").GetBoolean()); + Assert.Equal("migration_in_progress", json.GetProperty("degraded_root_cause").GetString()); + Assert.Contains("DEGRADED", json.GetProperty("summary").GetString()); + Assert.Contains(degradations.EnumerateArray(), item => + item.GetProperty("field").GetString() == "migration_in_progress" + && item.GetProperty("root_cause").GetString() == "migration_in_progress"); + Assert.Contains(degradations.EnumerateArray(), item => + item.GetProperty("field").GetString() == "file_issues_data_current" + && item.GetProperty("root_cause").GetString() == "file_issues_data_current=false"); + } + finally + { + TestProjectHelper.DeleteDirectory(projectRoot); + } + } + [Fact] public void RunStatus_CheckJson_ReturnsSuccessWhenIndexMatchesWorkspace() { @@ -30906,7 +30948,7 @@ public void RunStatus_CheckJsonScopedFold_ReportsOnlyFoldDegradation() [Theory] [InlineData("graph", "graph_table_available")] - [InlineData("issues", "issues_table_available")] + [InlineData("issues", "file_issues_data_current")] [InlineData("hotspot", "hotspot_family_ready")] [InlineData("csharp", "csharp_symbol_name_ready")] [InlineData("sql", "sql_graph_contract_ready")] diff --git a/tests/CodeIndex.Tests/golden/status.json b/tests/CodeIndex.Tests/golden/status.json index b896921157..a058122de0 100644 --- a/tests/CodeIndex.Tests/golden/status.json +++ b/tests/CodeIndex.Tests/golden/status.json @@ -68,7 +68,9 @@ "version": null, "summary": "1 files, 3 symbols, 0 refs across 1 languages (csharp); index fresh, DEGRADED", "graph_table_available": false, - "issues_table_available": false, + "issues_table_available": true, + "file_issues_data_current": false, + "migration_in_progress": false, "hotspot_family_ready": false, "hotspot_family_degraded_reason": "cross-file hotspot family grouping is degraded (hotspot_family_support_not_indexed=csharp); Run \u0060cdidx index \u003CprojectPath\u003E\u0060 to rebuild and stamp authoritative hotspot families.", "csharp_symbol_name_ready": false, @@ -77,6 +79,54 @@ "sql_graph_contract_ready": true, "fold_ready": false, "fold_ready_reason": "missing_fold_backfill", + "degraded_reason": "Reference / caller / callee / unused counts are degraded to 0 because the symbol_references table is missing.", + "degraded_root_cause": "graph_table_available=false", + "recommended_action": "Run \u0060cdidx index \u003CprojectPath\u003E\u0060 to rebuild the graph-capable index.", + "alternative_action": "Run \u0060cdidx index \u003CprojectPath\u003E --rebuild\u0060 for a full rebuild.", + "readiness_degradations": [ + { + "field": "graph_table_available", + "root_cause": "graph_table_available=false", + "degraded_reason": "Reference / caller / callee / unused counts are degraded to 0 because the symbol_references table is missing.", + "recommended_action": "Run \u0060cdidx index \u003CprojectPath\u003E\u0060 to rebuild the graph-capable index.", + "alternative_action": "Run \u0060cdidx index \u003CprojectPath\u003E --rebuild\u0060 for a full rebuild." + }, + { + "field": "file_issues_data_current", + "root_cause": "file_issues_data_current=false", + "degraded_reason": "The file_issues table exists, but its data is not stamped current for this index generation.", + "recommended_action": "Run \u0060cdidx index \u003CprojectPath\u003E\u0060 to refresh file issue rows.", + "alternative_action": "Run \u0060cdidx index \u003CprojectPath\u003E --rebuild\u0060 for a full rebuild." + }, + { + "field": "hotspot_family_ready", + "root_cause": "hotspot_family_ready=false", + "degraded_reason": "Cross-file hotspot grouping may be degraded for one or more languages.", + "recommended_action": "Run \u0060cdidx index \u003CprojectPath\u003E\u0060 to restamp authoritative hotspot families.", + "alternative_action": "Run \u0060cdidx index \u003CprojectPath\u003E --rebuild\u0060 for a full rebuild." + }, + { + "field": "csharp_symbol_name_ready", + "root_cause": "csharp_symbol_name_ready=false", + "degraded_reason": "C# exact-name for operators / conversion operators / indexers is degraded.", + "recommended_action": "cdidx index --db /.cdidx/codeindex.db", + "alternative_action": "Run \u0060cdidx index \u003CprojectPath\u003E --rebuild\u0060 for a full rebuild." + }, + { + "field": "csharp_metadata_target_ready", + "root_cause": "csharp_metadata_target_stamp_outdated", + "degraded_reason": "C# metadata attribute dependency edges are degraded because the metadata-target version stamp is missing or stale.", + "recommended_action": "Run \u0060cdidx index \u003CprojectPath\u003E\u0060 to restamp authoritative C# metadata targets.", + "alternative_action": "Run \u0060cdidx index \u003CprojectPath\u003E --rebuild\u0060 for a full rebuild." + }, + { + "field": "fold_ready", + "root_cause": "missing_fold_backfill", + "degraded_reason": "--exact falls back to ASCII COLLATE NOCASE because legacy rows without \u0060name_folded\u0060 remain.", + "recommended_action": "cdidx backfill-fold --db /.cdidx/codeindex.db", + "alternative_action": "cdidx index --db /.cdidx/codeindex.db --rebuild" + } + ], "index_newer_than_reader": false, "db_pragma_settings": { "journal_mode": "wal",