From d0a67285b165309dbd0de5196f50f36f0d044538 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Tue, 4 Aug 2026 06:57:52 +0900 Subject: [PATCH] Improve guide readability --- DEVELOPER_GUIDE.md | 272 ++++++++++++++++++++++++++++++++++----------- README.md | 2 +- USER_GUIDE.md | 129 ++++++++++++++++++++- 3 files changed, 330 insertions(+), 73 deletions(-) diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 7e0b69118..9386c348b 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -282,9 +282,62 @@ valid encoded U+FFFD literal, while `origin: decode_replacement` means the decoder inserted U+FFFD for invalid bytes. `severity: info` is used for source literals, and `severity: warning` is used for likely encoding damage. -Scoped `--files` / `--commits` refreshes reuse the same path filter as full scans. Before scanning a nested project root, `FileIndexer` loads ignore files from the resolved ignore-rule root through each existing ancestor directory down to the project root's parent, then loads the project directory's own rules during the normal walk. Within each directory, `FileIndexer` loads `.gitignore` before `.cdidxignore`, appends both rule sets in that order, and honors later `!` patterns as re-includes. If an ancestor ignore directory cannot be read, scanning fails closed with a scan error instead of silently skipping those rules; `ScanFilesResult.AncestorIgnoreDirectories` records the resolved ancestor list for troubleshooting. If a commit-scoped refresh includes `.gitignore` or `.cdidxignore` changes, `IndexCommandRunner` falls back to a full scan so newly ignored files are purged safely. Malformed ignore lines are reported as scan errors and skipped instead of aborting the whole run. Symlinks default to `--follow-symlinks none`; `internal` follows file and directory targets that resolve under the workspace root, and `all` follows all resolvable targets. Discovery, dry-run, C# workspace preflight, and content loading use the same resolved file target identity, so a stable allowed external file target is indexed while a link retargeted after preflight is rejected as source drift. Dangling symlinks are counted and warned separately; index dry-run reports them through `warnings_total` / `warnings`, matching execution severity and successful exit behavior. Permission failures resolving directory targets are also reported as scan warnings. On Windows, files and directories with Hidden or System attributes are rejected before language detection; clear those attributes before indexing project-owned sources because ignore rules cannot re-include them. +### Scoped refresh path rules -Incremental refreshes that mutate `fts_chunks` increment both `codeindex_meta.fts_incremental_writes_since_merge` and `codeindex_meta.fts_incremental_writes_since_optimize`. When the merge counter reaches 25 writes, index runners issue `INSERT INTO fts_chunks(fts_chunks, rank) VALUES('merge', -1000)`: 1,000 pages is a minimum work target, and SQLite's complete-segment granularity may process more pages. The merge resets only its dedicated counter, while the optimize counter continues to support the `cdidx optimize --dry-run` recommendation. Full CLI scans and MCP refreshes switch to trigger-free bulk rewrite, FTS rebuild, and full optimize when dirty source bytes are at least three-fifths of known workspace source bytes; fresh indexes and explicit rebuilds always use that path. Dirty bytes include the larger of the current and persisted sizes for each file that will be rewritten, plus persisted byte sizes for indexed rows planned for deletion, including the old side of a rename. The comparison total includes known readable current-workspace bytes, those planned-deletion bytes, and the positive persisted-minus-current excess for rewritten files that shrank, so both sides describe the same pre-update footprint. A scan error, invalid persisted size, or byte-count overflow makes the estimate incomplete and conservatively keeps trigger synchronization. Stale-file IDs are planned without mutation before selecting the FTS policy, then deleted inside the selected bulk guard. The plan keeps IDs ascending so the C# static-interface workspace prepass can skip purge-planned rows with a binary search and no duplicate deletion set; the reusable-stat snapshot instead loads the IDs into an indexed temporary SQL filter before running eligibility subqueries. This prevents a path that reappears between MCP planning and scanning from reusing a row that the same run will purge. The separate pre-purge contract-presence query runs only for a non-empty plan and still forces C# re-extraction when deletion may remove implicit implementation references. When such contracts exist, MCP also invalidates the C# symbol-name contract at its first mutation; if a scan error leaves an implementer unprocessed after the purge, the next clean run disables stat reuse, repairs its implicit references, and only then restamps the contract. The batched delete transaction checks cancellation throughout and rolls back every delete if cancellation arrives before commit; if cancellation arrives after a committed bulk purge, guard abandonment rebuilds FTS from the surviving chunks and restores its triggers before the run exits. Full scans and MCP refreshes filter reusable-row snapshots with a current-target path set only when non-purged indexed rows unused by the current target set outnumber the current targets; other runs use sorted-ID exclusion without duplicating every current path in a second large set. Scoped `--files` / `--commits` refreshes stay on trigger synchronization and incremental merge maintenance. `cdidx optimize --db ` and `cdidx index --optimize` still run an explicit full optimize, reset both counters, and stamp `fts_last_optimized_at`; this may briefly hold the writer lock on large indexes. +Scoped `--files` and `--commits` refreshes use the same path policy as full +scans. + +| Area | Contract | +|---|---| +| Nested project roots | `FileIndexer` loads ignore files from the resolved rule root through each existing ancestor to the project root's parent. Project-directory rules are loaded during the normal walk. | +| Rule order | Each directory loads `.gitignore` before `.cdidxignore`; later `!` patterns can re-include paths. | +| Unreadable ancestor | Scanning fails closed with a scan error. `ScanFilesResult.AncestorIgnoreDirectories` records the resolved ancestor list for diagnostics. | +| Changed ignore file | A commit-scoped refresh that includes `.gitignore` or `.cdidxignore` falls back to a full scan so newly ignored files are purged. | +| Malformed rule | Reports a scan error, skips that line, and continues the run. | +| Symlink modes | `none` is the default. `internal` follows targets under the workspace root; `all` follows every resolvable target. | +| Target identity | Discovery, dry-run, C# preflight, and content loading share one resolved identity. Stable allowed external targets are indexed; links retargeted after preflight are rejected as source drift. | +| Symlink warnings | Dangling links and directory-target permission failures are scan warnings. Dry-run exposes them through `warnings_total` and `warnings` while retaining successful exit behavior. | +| Windows attributes | Hidden or System paths are rejected before language detection. Clear those attributes on project-owned source because ignore rules cannot re-include the path. | + +### FTS maintenance during indexing + +| Situation | FTS policy | +|---|---| +| Every incremental write | Increment both `fts_incremental_writes_since_merge` and `fts_incremental_writes_since_optimize`. | +| Merge counter reaches 25 | Run `INSERT INTO fts_chunks(fts_chunks, rank) VALUES('merge', -1000)`. The 1,000-page value is a minimum work target; SQLite may process complete segments beyond it. Reset only the merge counter; the optimize counter keeps accumulating for the `cdidx optimize --dry-run` recommendation. | +| Dirty bytes reach 3/5 of known workspace bytes | Full CLI scans and MCP refreshes use a trigger-free bulk rewrite, FTS rebuild, and full optimize. | +| Fresh index or explicit rebuild | Always use the bulk path. | +| Scoped `--files` / `--commits` refresh | Keep trigger synchronization and incremental merge maintenance. | +| Explicit optimize | `cdidx optimize --db ` and `cdidx index --optimize` run a full optimize, reset both counters, and stamp `fts_last_optimized_at`. This may briefly hold the writer lock on large indexes. | + +Bulk-path estimation and purge safety follow these rules: + +- Dirty bytes use the larger current/persisted size for each rewritten file, + plus persisted sizes for planned deletions, including the old side of a + rename. +- The comparison total includes readable current-workspace bytes, planned + deletion bytes, and the positive persisted-minus-current difference for + rewritten files that shrank. Both sides therefore describe the same + pre-update footprint. +- A scan error, invalid persisted size, or byte-count overflow makes the + estimate incomplete and conservatively keeps trigger synchronization. +- Stale-file IDs are planned without mutation before policy selection and are + deleted only inside the selected bulk guard. Sorted IDs let the C# prepass + exclude purge-planned rows by binary search; reusable-stat eligibility uses + an indexed temporary SQL filter instead. +- A path that reappears between MCP planning and scanning cannot reuse a row + that the same run plans to purge. +- The pre-purge contract-presence query runs only for a non-empty plan. If a + deletion may remove implicit implementation references, C# is re-extracted + and MCP invalidates the symbol-name contract at its first mutation. After a + purge followed by a scan error, the next clean run disables stat reuse, + repairs those references, and only then restamps the contract. +- Cancellation before the batched delete commits rolls back every deletion. + Cancellation after a committed bulk purge makes guard abandonment rebuild + FTS from surviving chunks and restore triggers before exit. +- Full scans and MCP refreshes use a current-target path filter for reusable + rows only when unused, non-purged indexed rows outnumber current targets. + Other runs use sorted-ID exclusion to avoid duplicating every current path. The C# pre-purge contract preflight probes sorted planned IDs in uncached batches of at most 500 SQLite parameters and applies managed keyword-boundary validation before forcing re-extraction. A prior symbol-kind policy that could have omitted a contract-member kind makes an interface declaration conservative evidence. For scoped updates, a false source-evidence marker is authoritative, so the hot path does not restore a repository-wide exact-member scan; transition-path probes instead start from exact indexed `files(path)` rows, join through `symbols(file_id, kind)`, and remain cancellation-aware and cache-neutral in batches of at most 500 paths. Plain interfaces and LIKE-only decoys therefore do not invalidate reusable C# rows. Persisted workspace materialization likewise starts from `files(lang)` and probes only member-capable `symbols(file_id, kind)` rows, validates exact signatures in managed code, then loads interface declarations only for retained contract container names through bounded, cache-neutral `symbols(name)` batches. A negative or LIKE-decoy-only read stops after the first phase and never materializes the repository's plain interfaces. A tri-state source-evidence marker observes built-in C# symbols before post-extraction hooks, kind filters, and row caps, so a hook-hidden contract still forces safe implicit-reference refreshes. Full CLI and MCP scans preserve either authoritative true or false source evidence without rereading source only when the prior index is explicitly complete, GraphReady was already stamped, symbols-only omission and filter/version/root/hotspot contracts remain compatible, no persisted C# path changed language, and every C# target is stat-reusable. This strict known-evidence no-op also skips persisted C# symbol loading and workspace-lookup construction; missing legacy completeness or readiness metadata deliberately falls back to a raw workspace prepass. @@ -3334,41 +3387,59 @@ test or constant that proves the maximum byte budget. ## Custom Language Extraction Downstream users can add lightweight language support without rebuilding -`cdidx`: - -- extension aliases are read from `~/.config/cdidx/langmap.yaml` and the first - workspace ancestor `.cdidx-langmap.yaml`; workspace entries override user - entries. A trusted suffix override is evaluated before built-in exact-filename, - filename-prefix, and extension rules. If the closest workspace map cannot be - probed or read, ancestor workspace lookup stops for that subtree instead of - reusing a parent map; `languages --json` and the MCP `languages` tool expose - the sanitized failure in `language_map_diagnostics` and publish the effective - order in `detection_policy.precedence`; -- regex-backed symbol patterns are read from `.cdidx/patterns/*.yaml` and - `~/.config/cdidx/patterns/*.yaml`; sidecars must be regular files under - non-symlink pattern directories, discovery accepts at most 128 candidates per - pattern directory, each file is capped at 64 KiB / 128 rules, each immutable - workspace snapshot loads at most 128 configured rules total, and regex matches use a 100 ms - timeout. Each sidecar is parsed, compiled, and checked against - `SymbolKindCatalog` before its path, rules, or budget are committed. Rejected - content is fingerprinted to suppress duplicate diagnostics, while content or - metadata changes and transient read recovery are retried without restarting. - Workspace discovery requires an explicit trust root and stops after checking - that root; it never probes ancestors above it. Nested sidecars inside that - boundary are loaded for the current file in the bounded extraction worker. - Path identity follows the - active filesystem's case-sensitivity, so case-distinct sidecars remain - distinct on case-sensitive volumes. `status --json` reports accepted files in - `extractors.pattern_configs[]` with sanitized path, workspace/user provenance, - normalized language, and rule count. Reindexing atomically replaces the - workspace snapshot so the old rule budget and timeout state become - collectible without changing other workspaces. A timed-out rule is suppressed - by a bounded one-minute cooldown in its owning workspace snapshot and emits a - workspace-scoped diagnostic; -- `cdidx test-extractor --language --file --json` runs symbol - extraction without building an index, and `--expect-symbols ` compares - the extracted JSON to a fixture. The source and expectation files are capped - at 4 MiB each. +`cdidx`. + +| Capability | Configuration | +|---|---| +| Extension aliases | `~/.config/cdidx/langmap.yaml` and the nearest workspace-ancestor `.cdidx-langmap.yaml` | +| Regex-backed symbols | Workspace `.cdidx/patterns/*.yaml` and user `~/.config/cdidx/patterns/*.yaml` | +| Standalone verification | `cdidx test-extractor --language --file --json` | + +### Extension alias precedence + +- Workspace entries override user entries. +- A trusted suffix override is evaluated before built-in exact-filename, + filename-prefix, and extension rules. +- If the closest workspace map cannot be probed or read, lookup stops for that + subtree instead of reusing a parent map. +- `languages --json` and the MCP `languages` tool expose sanitized failures in + `language_map_diagnostics` and the effective order in + `detection_policy.precedence`. + +### Pattern sidecar safeguards + +| Limit | Value | +|---|---| +| Discovery candidates | 128 per pattern directory | +| Sidecar size | 64 KiB per file | +| Rules per sidecar | 128 | +| Configured rules | 128 per immutable workspace snapshot | +| Regex match timeout | 100 ms | +| Timed-out rule cooldown | At most one minute in the owning workspace snapshot | + +- Sidecars must be regular files inside non-symlink pattern directories. +- Each sidecar is parsed, compiled, and checked against `SymbolKindCatalog` + before its path, rules, or budget are committed. +- Rejected content is fingerprinted to suppress duplicate diagnostics. Content + or metadata changes and recovery from a transient read failure trigger a retry + without restarting the process. +- Workspace discovery requires an explicit trust root and never probes above + it. Nested sidecars inside that boundary are loaded for the current file by + the bounded extraction worker. +- Path identity follows the active filesystem's case-sensitivity, so + case-distinct sidecars remain distinct on case-sensitive volumes. +- `status --json` reports accepted files in `extractors.pattern_configs[]`, + including sanitized path, workspace/user provenance, normalized language, + and rule count. +- Reindexing atomically replaces the workspace snapshot. The old rule budget + and timeout state can then be collected without affecting other workspaces. + A timed-out rule emits a workspace-scoped diagnostic before entering cooldown. + +### Extractor testing + +`cdidx test-extractor --language --file --json` runs extraction +without building an index. Add `--expect-symbols ` to compare the result +with a fixture. Source and expectation files are each capped at 4 MiB. Query-side `--lang` resolution uses this same workspace-aware extension and extractor registry rather than a separate built-in list. Registered language @@ -3741,7 +3812,21 @@ literal、`origin: decode_replacement` が不正 byte に対して decoder が を意味する。source literal は `severity: info`、エンコーディング破損の可能性は `severity: warning` として返す。 -`--files` / `--commits` の部分更新も、フルスキャンと同じパスフィルタを再利用する。各ディレクトリでは `FileIndexer` が `.gitignore` を `.cdidxignore` より先に読み、この順序でルールを追加し、後続の `!` パターンを再包含として扱う。commit 単位更新に `.gitignore` または `.cdidxignore` の変更が含まれる場合、`IndexCommandRunner` は newly ignored file を安全に purge するため自動でフルスキャンへフォールバックする。malformed な ignore 行は走査エラーとして報告し、その行だけをスキップして index 全体は継続する。symlink は既定で `--follow-symlinks none` とし、`internal` は workspace root 内へ解決される file / directory target、`all` は解決可能なすべての target を追跡する。discovery、dry-run、C# workspace preflight、content loading は同じ解決済み file target identity を使うため、許可された静的な外部 file target は索引し、preflight 後に retarget された link は source drift として拒否する。dangling symlink は個別に集計して warning とし、index dry-run も実行時と同じく `warnings_total` / `warnings` で報告して成功終了する。directory target の解決時に発生した permission failure も scan warning として報告する。Windows では Hidden または System 属性が付いたファイルとディレクトリを言語検出前に拒否する。プロジェクト所有のソースを索引したい場合、ignore ルールでは再包含できないため先にそれらの属性を外す。 +### 部分更新の path rule + +`--files` / `--commits` の部分更新は、full scan と同じ path policy を使います。 + +| 項目 | 契約 | +|---|---| +| nested project root | `FileIndexer` は解決済み rule root から project root の parent まで、既存 ancestor の ignore file を読みます。project directory 自身の rule は通常 walk 中に読みます。 | +| rule 順序 | 各 directory で `.gitignore`、`.cdidxignore` の順に読み、後続の `!` pattern による再包含を認めます。 | +| 読めない ancestor | rule を黙って落とさず scan error で fail closed します。`ScanFilesResult.AncestorIgnoreDirectories` が解決済み ancestor list を診断用に保持します。 | +| ignore file の変更 | commit-scoped refresh に `.gitignore` または `.cdidxignore` の変更が含まれる場合、newly ignored file を purge するため full scan へ fallback します。 | +| malformed rule | scan error を報告してその行だけを skip し、run は継続します。 | +| symlink mode | 既定は `none` です。`internal` は workspace root 内の target、`all` は解決可能な全 target を追跡します。 | +| target identity | discovery、dry-run、C# preflight、content loading は同じ解決済み identity を使います。安定した許可済み外部 target は index し、preflight 後に retarget された link は source drift として拒否します。 | +| symlink warning | dangling link と directory target の permission failure は scan warning です。dry-run は `warnings_total` / `warnings` に出し、成功終了を維持します。 | +| Windows 属性 | Hidden / System path は言語検出前に拒否します。ignore rule では再包含できないため、project 所有 source では先に属性を外してください。 | ### メタデータ不変条件 @@ -5184,7 +5269,41 @@ source membership は `FileIndexer` で共有し、full scan、workspace freshne watcher は startup reconciliation scan より先に有効化する。`FileChangeBatcher.TryDrainImmediately` は通常の debounce interval を待たずに buffer 済み startup generation を閉じ、その path を `watching` event より前に適用する一方、snapshot 後に到着した event は通常の live update として queue に残す。すべての startup reconciliation sub-run が成功した場合だけ `watching` を出力し、失敗した generation は batch を捨てて ready を宣言せず non-zero exit を返す。この generation boundary により、初回 scan と subscribe の間の gap と、変更が連続する workspace で ready が無期限に遅れる問題の両方を防ぐ。 -FTS5 を変更する差分更新は `codeindex_meta.fts_incremental_writes_since_merge` と `codeindex_meta.fts_incremental_writes_since_optimize` の両方を増やします。merge counter が 25 write に達すると、index runner は `INSERT INTO fts_chunks(fts_chunks, rank) VALUES('merge', -1000)` を実行します。1,000 page は最小 work target であり、SQLite が完全な segment 単位で処理するため実際の page 数は target を超える場合があります。merge では専用 counter のみをリセットし、optimize counter は `cdidx optimize --dry-run` の推奨判定用に累積を続けます。CLI の full scan と MCP refresh は dirty source byte が既知 workspace source byte の 5 分の 3 以上なら、trigger を停止した bulk rewrite、FTS rebuild、full optimize に切り替えます。fresh index と明示的 rebuild は常にこの経路を使います。dirty byte は今回書き換える各 file の current size と永続化済み size の大きい方に、rename の旧 path を含む削除予定 indexed row の永続化済み byte size を加算します。比較対象の total には読み取り可能と判明した current workspace byte、削除予定 byte、および縮小した書き換え file の永続化済み size が current size を上回る差分を含め、更新前 footprint と同じ基準で比較します。scan error、永続化 size の不正値、または byte 加算 overflow がある場合は estimate を incomplete として保守的に trigger 同期を維持します。stale file ID は FTS policy の選択前に mutation なしで plan し、選択した bulk guard の内側で削除します。plan の ID は昇順に保つため、C# static-interface workspace prepass は削除 set を複製せず二分探索で purge 予定 row を除外します。一方、reusable-stat snapshot は eligibility subquery より前に ID を index 付き一時 SQL filter へ読み込みます。これにより MCP の plan 後から scan までに同じ path が再出現しても、この run が purge する旧 row を reuse せず、現存 file を再indexします。purge 前の contract 存在 query は plan が非空の場合だけ実行し、削除によって obsolete になる implicit implementation reference を除くための C# 再抽出判定に使用します。そのような contract が存在する場合、MCP は最初の mutation で C# symbol-name contract も invalid にします。purge 後の scan error で implementer を未処理のまま残しても、次の clean run は stat reuse を無効化して implicit reference を修復し、その後にだけ contract を再 stamp します。batch delete transaction は処理中も cancellation を確認し、commit 前の cancellation では全削除を rollback します。bulk purge の commit 後に cancellation された場合は、guard の abandon 処理が残存 chunk から FTS を rebuild し、trigger を復元してから run を終了します。full scan と MCP refresh は、current target に使われない非 purge indexed row の数が current target 数を上回る場合だけ、current-target path set で reusable-row snapshot を filter します。それ以外は全 current path を第2の大きな set に複製せず、昇順 ID 除外を使います。scoped `--files` / `--commits` refresh は trigger 同期と incremental merge maintenance を維持します。`cdidx optimize --db ` と `cdidx index --optimize` は引き続き明示的 full optimize を実行し、両 counter をリセットして `fts_last_optimized_at` を記録します。大きな index では短時間 writer lock を保持する可能性があります。 +### Index 中の FTS maintenance + +| 状況 | FTS policy | +|---|---| +| 差分 write ごと | `fts_incremental_writes_since_merge` と `fts_incremental_writes_since_optimize` の両方を増やします。 | +| merge counter が 25 に到達 | `INSERT INTO fts_chunks(fts_chunks, rank) VALUES('merge', -1000)` を実行します。1,000 page は最小 work target で、SQLite は完全な segment 単位でさらに処理する場合があります。merge counter だけを reset し、optimize counter は `cdidx optimize --dry-run` の推奨判定用に累積を続けます。 | +| dirty byte が既知 workspace byte の 3/5 以上 | CLI full scan と MCP refresh は trigger-free bulk rewrite、FTS rebuild、full optimize を使います。 | +| fresh index / 明示的 rebuild | 常に bulk path を使います。 | +| scoped `--files` / `--commits` refresh | trigger 同期と incremental merge maintenance を維持します。 | +| 明示的 optimize | `cdidx optimize --db ` と `cdidx index --optimize` は full optimize を実行し、両 counter を reset して `fts_last_optimized_at` を記録します。大きな index では短時間 writer lock を保持する場合があります。 | + +bulk path の見積もりと purge の安全性は次の規則に従います。 + +- dirty byte は書き換える各 file の current / persisted size の大きい方に、rename の + 旧 path を含む削除予定 row の persisted size を加えます。 +- 比較対象の total は、読み取り可能な current workspace byte、削除予定 byte、縮小した + file の persisted-minus-current の正の差分を含みます。両辺を同じ更新前 footprint で + 比較するためです。 +- scan error、persisted size の不正値、byte 加算 overflow がある場合は estimate を + incomplete とし、保守的に trigger 同期を維持します。 +- stale file ID は policy 選択前に mutation なしで plan し、選択した bulk guard 内だけで + 削除します。昇順 ID により C# prepass は二分探索で purge 予定 row を除外し、 + reusable-stat eligibility は代わりに index 付き一時 SQL filter を使います。 +- MCP の plan 後から scan までに path が再出現しても、同じ run が purge 予定の row は + reuse しません。 +- purge 前の contract 存在 query は plan が非空の場合だけ実行します。削除で implicit + implementation reference が失われる可能性があれば C# を再抽出し、MCP は最初の + mutation で symbol-name contract を invalid にします。purge 後の scan error では、次の + clean run が stat reuse を無効化して reference を修復し、その後に contract を stamp します。 +- batch delete の commit 前に cancellation された場合は全削除を rollback します。bulk + purge の commit 後なら、guard の abandon 処理が残存 chunk から FTS を rebuild し、 + trigger を復元してから終了します。 +- full scan と MCP refresh は、current target に使われない非 purge row が current target + より多い場合だけ current-target path filter を使います。それ以外は全 current path の + 複製を避け、昇順 ID 除外を使います。 C# の purge 前 contract preflight は、昇順の削除予定 ID を SQLite parameter 最大 500 件の uncached batch で調べ、managed 側の keyword-boundary 判定を通してから再抽出を強制する。以前の symbol-kind policy が contract member kind を落とし得る場合は interface 宣言を保守的 evidence とする。scoped update では false の source-evidence marker を authoritative とし、repository 全体の exact-member scan を hot path に戻さない。transition path の probe は正確な `files(path)` row から開始して `symbols(file_id, kind)` へ join し、最大 500 path の cancellation-aware かつ cache-neutral な batch で実行するため、通常の interface と LIKE だけ一致する decoy で再利用可能な C# row を無効化しない。永続 workspace の materialize も `files(lang)` から開始して member 候補 kind の `symbols(file_id, kind)` だけを probe し、managed 側で signature を厳密検証してから、保持 contract の container 名に一致する interface 宣言だけを bounded かつ cache-neutral な `symbols(name)` batch で取得する。negative または LIKE decoy だけの読込は第1段階で終了し、repository 内の通常 interface を materialize しない。tri-state の source-evidence marker は post-extraction hook、kind filter、row cap より前の built-in C# symbol を観測するため、hook で隠された contract も安全な implicit-reference refresh を強制できる。CLI full scan と MCP は、以前の index が明示的に complete、GraphReady 済みで、symbols-only omission、filter/version/root/hotspot の contract が互換、永続 C# path の language transition がなく、全 C# target が stat-reusable な場合だけ authoritative な true / false の source evidence を source 再読込なしで維持する。この厳密な known-evidence no-op は永続 C# symbol の load と workspace lookup 構築も省略し、legacy completeness/readiness metadata が欠ける場合は保守的に raw workspace prepass へ戻る。 @@ -6189,33 +6308,54 @@ cleared range を証明するテストが必要です。Bounded accumulation pat 下流ユーザーは `cdidx` を再ビルドせずに軽量な言語対応を追加できます。 -- 拡張子 alias は `~/.config/cdidx/langmap.yaml` と、最初に見つかった workspace - 祖先の `.cdidx-langmap.yaml` から読み込まれ、workspace 側が user 側を上書きします。 - 信頼済み suffix override は built-in の完全一致 filename、filename-prefix、extension rule - より先に評価されます。最も近い workspace map を probe または read できない場合、その subtree - では親 map を再利用せず ancestor workspace 探索を停止します。`languages --json` と MCP の - `languages` tool は sanitization 済み失敗を `language_map_diagnostics` に公開し、実効順序を - `detection_policy.precedence` で示します。 -- regex ベースのシンボルパターンは `.cdidx/patterns/*.yaml` と - `~/.config/cdidx/patterns/*.yaml` から読み込まれます。sidecar は symlink ではない - pattern directory 配下の通常ファイルのみが対象で、探索候補は pattern directory ごとに - 128 件まで、各ファイルは 64 KiB / 128 ルール、immutable な workspace snapshot ごとに - configured rule 128 件に制限され、 - regex match には 100 ms の timeout が付きます。各 sidecar は path・rule・budget を commit する前に - 一時状態で parse / compile され、`SymbolKindCatalog` に対して kind が検証されます。拒否された内容は - fingerprint によって重複診断を抑制し、内容または metadata の変更時、および一時的な read failure の - 回復後にはプロセスを再起動せず再試行されます。workspace 探索には明示的な trust root が必要で、 - その root を確認した時点で停止し、それより上の ancestor は探索しません。その境界内の nested - sidecar は、対象 file の上限付き extraction worker 内で読み込まれます。path identity は実際の - filesystem の case-sensitivity に従うため、case-sensitive volume では大小文字だけが異なる sidecar も - 別々に扱われます。`status --json` の `extractors.pattern_configs[]` は、受理済み file の - sanitization 済み path、workspace/user provenance、正規化済み language、rule count を報告します。 - reindex は workspace snapshot を atomically に置換するため、以前の rule budget と timeout state は - 他 workspace を変更せず回収可能になります。timeout した rule は所有する workspace snapshot 内だけで - 上限付きの1分間 cooldown に入り、workspace-scoped diagnostic を出します。 -- `cdidx test-extractor --language --file --json` は index を作らずに - symbol extraction だけを実行し、`--expect-symbols ` で fixture JSON と比較できます。 - source と expectation file はそれぞれ 4 MiB に制限されます。 +| 機能 | 設定 | +|---|---| +| 拡張子 alias | `~/.config/cdidx/langmap.yaml` と、最も近い workspace ancestor の `.cdidx-langmap.yaml` | +| regex ベースの symbol | workspace の `.cdidx/patterns/*.yaml` と user の `~/.config/cdidx/patterns/*.yaml` | +| 単独検証 | `cdidx test-extractor --language --file --json` | + +### 拡張子 alias の優先順位 + +- workspace entry が user entry を上書きします。 +- 信頼済み suffix override は built-in の完全一致 filename、filename-prefix、 + extension rule より先に評価されます。 +- 最も近い workspace map を probe または read できない場合、その subtree では + parent map を再利用せず探索を停止します。 +- `languages --json` と MCP の `languages` tool は sanitization 済み失敗を + `language_map_diagnostics`、実効順序を `detection_policy.precedence` に公開します。 + +### Pattern sidecar の安全策 + +| 上限 | 値 | +|---|---| +| 探索候補 | pattern directory ごとに 128 件 | +| sidecar size | 1 file あたり 64 KiB | +| sidecar 内の rule | 128 件 | +| configured rule | immutable workspace snapshot ごとに 128 件 | +| regex match timeout | 100 ms | +| timeout rule の cooldown | 所有する workspace snapshot 内で最大 1 分 | + +- sidecar は symlink ではない pattern directory 配下の通常 file に限定します。 +- 各 sidecar は path・rule・budget を commit する前に parse / compile し、 + `SymbolKindCatalog` に対して kind を検証します。 +- 拒否された内容は fingerprint で重複診断を抑制します。内容や metadata の変更、 + 一時的な read failure からの回復後は、process を再起動せず再試行します。 +- workspace 探索には明示的な trust root が必要で、それより上は探索しません。 + 境界内の nested sidecar は対象 file の上限付き extraction worker で読み込みます。 +- path identity は実際の filesystem の case-sensitivity に従うため、case-sensitive + volume では大小文字だけが異なる sidecar も別々に扱います。 +- `status --json` の `extractors.pattern_configs[]` は、受理済み file の + sanitization 済み path、workspace/user provenance、正規化済み language、rule count を + 報告します。 +- reindex は workspace snapshot を atomically に置換します。以前の rule budget と + timeout state は他 workspace に影響せず回収でき、timeout rule は + workspace-scoped diagnostic を出してから cooldown に入ります。 + +### Extractor のテスト + +`cdidx test-extractor --language --file --json` は index を作らずに +extraction を実行します。`--expect-symbols ` を加えると fixture と比較できます。 +source と expectation file はそれぞれ 4 MiB が上限です。 query 側の `--lang` 解決は、別の組み込み一覧ではなく、この workspace-aware な extension / extractor registry を共有します。登録済み language ID、alias、拡張子形式の diff --git a/README.md b/README.md index 653a25e6a..2c0a117a3 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ text / symbol だけを先に検索する場合、`cdidx . --symbols-only` で | 鮮度管理 | `status --check`、`--files`、`--commits`、`--changed-between`、`--watch` で DB と workspace を揃えます。 | | validation | `cdidx validate` が encoding / line-ending 問題を報告します。詳細は [Indexed files を validate する](USER_GUIDE.md#indexed-files-を-validate-する)。 | | 対応言語 | `cdidx languages --json` が live capability probe です。詳細は [対応言語](USER_GUIDE.md#対応言語)。 | -| custom extraction | 拡張子 alias と regex-backed pattern は [Custom Language Extraction](DEVELOPER_GUIDE.md#custom-language-extraction) を参照してください。 | +| custom extraction | 拡張子 alias と regex-backed pattern は [カスタム言語抽出](DEVELOPER_GUIDE.md#カスタム言語抽出) を参照してください。 | | 運用 | install、upgrade、release 検証、troubleshooting、output control は [ユーザーガイド](USER_GUIDE.md#cdidx日本語) にあります。 | ## ドキュメント diff --git a/USER_GUIDE.md b/USER_GUIDE.md index a1cf1dfc4..4fb40ff76 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -1161,7 +1161,30 @@ small repositories, and minutes or longer on very large monorepos with around By default, `cdidx index` stores the database in `/.cdidx/codeindex.db`, even if you run the command from another directory. -`--watch` starts `FileSystemWatcher` (FSEvents on macOS, inotify on Linux, ReadDirectoryChangesW on Windows) before the one required baseline scan, then keeps the process alive and rebuilds the index incrementally as files are created, edited, renamed, or deleted. A recoverable macOS EventStream startup failure, or a later fatal EventStream error, switches to a polling backend without repeating a valid baseline. If the failure arrives while the baseline is running or after readiness, the valid baseline is retained and one recovery scan reconciles the backend handoff; stale callbacks from the replaced backend are ignored. Polling applies the same ignored-directory and internal-artifact pruning as indexing instead of repeatedly walking `.git`, `.cdidx`, build outputs, dependencies, or ignored trees. Events buffered during the baseline are drained before `watching`; genuine event loss after the backend is active is coalesced into at most one justified full incremental recovery scan per generation. Bursts of ordinary events are debounced (`--debounce `, default 500 ms) into a single `--files` update, the per-DB index lock is released between batches so other `cdidx` commands can still query, and a pending path batch that reaches its safety cap also requests one recovery scan. Subdirectory watches monitor ancestor `.gitignore` / `.cdidxignore` files along the repository path. The baseline and startup reconciliation must succeed before `watching` is emitted; a failed startup generation exits instead of declaring a stale watcher ready. With `--json` it streams `status: "backend_fallback" / "watching" / "updated" / "rescanned" / "overflow" / "failed" / "stopped"` lifecycle events to stdout; startup and recovery events expose `backend` (`fsevents` or `polling` on macOS) and machine-readable `recovery_reason`, while update/rescan events include `exit_code`. Human output includes the same backend/recovery context in `[watch] …` summaries. Stop the loop with Ctrl+C (or SIGTERM); cancellation during backend fallback or an active sub-run still emits the terminal `stopped` event, the first Ctrl+C requests cooperative cancellation, and a second Ctrl+C remains available for force exit. The final exit code is `0` when every batch succeeds, or the most recent non-zero sub-run exit code if a watch update/rescan failed before stop. `--watch` cannot be combined with `--commits`, `--files`, or `--dry-run` — the loop already drives continuous incremental updates. +#### Watch mode + +`--watch` starts the platform watcher before the required baseline scan, then +keeps the process alive and applies file creates, edits, renames, and deletes +incrementally. + +| Stage | Behavior | +|---|---| +| Backend | Uses `FileSystemWatcher`: FSEvents on macOS, inotify on Linux, and ReadDirectoryChangesW on Windows. | +| Startup | Buffers events during the baseline and drains them before emitting `watching`. The baseline and startup reconciliation must both succeed; otherwise the command exits without declaring a stale index ready. | +| Normal updates | Debounces event bursts into one `--files` update (`--debounce `, default 500 ms). The per-DB index lock is released between batches, so other `cdidx` commands can query the index. | +| Recovery | Coalesces genuine event loss or a pending-path safety-cap overflow into at most one justified full incremental recovery scan per generation. | +| Ignore changes | Subdirectory watches also monitor ancestor `.gitignore` and `.cdidxignore` files along the repository path. Polling uses the same pruning as indexing and avoids `.git`, `.cdidx`, build outputs, dependencies, and ignored trees. | +| JSON output | Streams `backend_fallback`, `watching`, `updated`, `rescanned`, `overflow`, `failed`, and `stopped` lifecycle statuses. Startup/recovery events include `backend` (`fsevents` or `polling` on macOS) and machine-readable `recovery_reason`; update/rescan events include `exit_code`. | +| Human output | Includes the same backend and recovery context in `[watch] …` summaries. | +| Shutdown | Ctrl+C or SIGTERM requests cooperative cancellation and still emits `stopped`, including during fallback or an active sub-run. A second Ctrl+C forces exit. | +| Exit status | Returns `0` when every batch succeeded; otherwise it returns the latest non-zero update/rescan exit code observed before shutdown. | +| Incompatible options | Cannot be combined with `--commits`, `--files`, or `--dry-run`; watch mode already drives continuous incremental updates. | + +On macOS, a recoverable EventStream startup failure or a later fatal EventStream +error switches to polling without repeating a valid baseline. A failure during +the baseline or after readiness keeps that baseline and schedules one recovery +scan for the backend handoff; callbacks arriving from the replaced backend are +ignored. On macOS, a subproject watch running on .NET 8 keeps FSEvents for the project tree and additionally polls only the exact ancestor `.gitignore` / `.cdidxignore` paths because that runtime can silently miss those ancestor events. Full-project polling remains reserved for backend failure recovery; top-level .NET 8 watches, .NET 9 subproject watches, and Linux / Windows backend selection are unchanged. @@ -1837,9 +1860,45 @@ cdidx definition QueryCommandRunner --exact-name --group-partials --count --json `definition` uses indexed symbol ranges plus chunk reconstruction to return the actual declaration text, and optional body content when the language extractor can infer a body range. -For C# partial types and partial methods, add `--group-partials` to `definition`, `symbols`, or symbol-mode `inspect` to collapse actual `partial` declarations with the same persisted, qualified family identity into one logical family. The default remains one row per physical declaration, and unrelated non-partial or merely same-named types—including nested non-partial types inside a partial host—are never collapsed. A `file partial` type and its partial members remain scoped to their source file, so matching file-local declarations in different files form distinct families. Family identity preserves a partial type's own generic arity, each containing type's generic arity, user-type casing, and meaningful `global::` root qualification, while normalizing C# predefined aliases (including the `dynamic` / `object` runtime identity), explicitly global-rooted `System` predefined aliases (including verbatim `@System` / `@Int32` segments), nullable value-type equivalents such as `int?` / `global::System.Nullable`, predefined reference-type nullable annotations, verbatim identifier escapes, declaration/type-position comments, parameter attributes and names, default values, and comment trivia between a method identifier, its generic parameters, and its parameter list. Method type parameters normalize by ordinal only when used as unqualified type variables; qualified leaves such as `N.T` remain concrete types even when the method declares ``. Unrooted spellings such as `System.Int32` remain distinct from `int` because an enclosing namespace or `using` alias can shadow `System`. Extraction-owned declaration metadata survives post-extraction hook cloning and preserves a `partial` modifier split onto preceding modifier-only lines across blank or comment trivia, including modifiers that trail a balanced leading attribute list. Leading attributes are bound to the declaration occurrence they prefix when multiple declarations share one line, while only adjacent lexer-confirmed XML documentation outside the stored signature contributes semantic rank; documentation-like text inside block comments or strings, and XML documentation detached by a blank line, does not affect representative rank. Repeated same-name partial declarations on one line retain distinct identifier columns for family navigation. The family-key contract is versioned so older C# rows are not interpreted with current grouping rules: a missing or stale contract conservatively returns physical rows until a full reindex republishes current family metadata, while LSP position resolution may still reconstruct a partial-type identity locally to keep type and constructor targets separate without collapsing query output. The canonical representative is chosen deterministically from the matched family: an implementation-bearing partial method precedes a declaration-only method, non-generated source precedes generated/designer source, declarations whose extraction metadata records leading attributes or XML documentation—or whose lexed indexed signature retains attributes, base lists, or constraints—precede otherwise equivalent declarations, and comment-insensitive normalized declaration identity is considered before ordinal path and source position. Generated sites participate when `--include-generated` is set; legacy databases without generated-file metadata fall back to generated/designer filename conventions. +For C# partial types and partial methods, `--group-partials` collapses matching +physical declarations into one logical family. -Grouped structured rows report the physical declaration count in `definition_sites` and expose `partial_family_id`, `representative_reason`, and up to 50 stable `family_members`; the bounded list always retains the representative and uses normalized identifier-aligned columns (after a verbatim `@` escape), and `family_members_truncated` is true when more sites exist. `goto` uses the same canonical representative by default and includes that family metadata in its LSP-shaped JSON, while `goto --all` intentionally returns every matching physical location. Grouped count JSON reports both `logical_count` and `physical_count` (plus `physical_file_count`). Human summaries distinguish the logical rows shown after `--limit` from query-wide logical and physical totals. Audit-sorted `symbols` rows use the family's maximum rank metric while retaining the canonical representative, so `--sort` remains monotonic before `--limit` is applied. `impact` uses the same family key and representative ordering automatically, reports `logical_definition_count`, and counts every matching physical site while materializing only the bounded logical representatives needed for output. File-mode `inspect`, whether selected by a positional path or `--path ... --line ...`, remains a physical lookup and rejects `--group-partials`. +| Area | Contract | +|---|---| +| Supported commands | Available on `definition`, `symbols`, and symbol-mode `inspect`. File-mode `inspect` remains a physical lookup and rejects the option. | +| Default behavior | Without the option, each physical declaration remains a separate row. Non-partial, merely same-named, and nested non-partial types are never grouped. | +| File-local declarations | A `file partial` type and its partial members are scoped to one source file; same-named declarations in other files form different families. | +| Identity preserved | The family key retains the partial type's arity, containing-type arities, user-type casing, and meaningful `global::` root qualification. | +| Equivalent forms normalized | Predefined aliases (including `dynamic` / `object` runtime identity), explicitly global-rooted `System` aliases, nullable value-type equivalents, predefined reference-type nullable annotations, verbatim escapes, declaration/type comments, parameter attributes/names/defaults, and method-signature comment trivia. | +| Deliberate distinctions | An unrooted `System.Int32` stays distinct from `int` because `System` can be shadowed. Method type parameters normalize by ordinal only as unqualified variables; a qualified leaf such as `N.T` remains a concrete type. | +| Extraction metadata | Post-extraction hook cloning preserves declaration metadata, including split modifier-only `partial` lines and modifiers following a balanced attribute list. Repeated same-name declarations on one line retain separate identifier columns. | +| Documentation ranking | A leading attribute binds only to the declaration it prefixes. Only adjacent, lexer-confirmed XML documentation outside the stored signature affects semantic rank; comment/string lookalikes and documentation separated by a blank line do not. | + +The family-key contract is versioned. A missing or stale contract returns +physical rows until a full reindex publishes current metadata. LSP position +resolution may still rebuild local partial identity to distinguish type and +constructor targets, but it does not group query output. + +The canonical representative is selected in this order: + +1. An implementation-bearing partial method before a declaration-only method. +2. Non-generated source before generated or designer source. +3. Declarations with recorded attributes or XML documentation, or indexed + signatures retaining attributes, base lists, or constraints. +4. Comment-insensitive normalized declaration identity, then ordinal path and + source position. + +Generated sites participate with `--include-generated`. Legacy databases that +lack generated-file metadata use generated/designer filename conventions. + +| Grouped output | Meaning | +|---|---| +| Family metadata | `definition_sites` is the physical declaration count. Rows also expose `partial_family_id`, `representative_reason`, and up to 50 stable `family_members`. | +| Member cap | The bounded member list always retains the representative and uses identifier-aligned columns after a verbatim `@`; `family_members_truncated` marks additional sites. | +| `goto` | Uses the canonical representative and returns family metadata in LSP-shaped JSON by default. Use `goto --all` for every physical location. | +| Counts | JSON returns `logical_count`, `physical_count`, and `physical_file_count`. Human summaries distinguish rows shown after `--limit` from query-wide logical and physical totals. | +| Sorted symbols | Uses the family's maximum rank metric while retaining the canonical representative, keeping `--sort` monotonic before `--limit`. | +| `impact` | Reuses the same family key and representative order, reports `logical_definition_count`, counts every physical site, and materializes only the bounded representatives needed for output. | ### Inspect one symbol in one round-trip @@ -4593,7 +4652,29 @@ interactive terminal では spinner と progress bar が動き続けます。待 `cdidx index` は、別ディレクトリから実行しても、デフォルトでは `/.cdidx/codeindex.db` にDBを保存します。 -`--watch` は必要な baseline scan 1 回より先に `FileSystemWatcher`(macOS は FSEvents、Linux は inotify、Windows は ReadDirectoryChangesW)を開始し、その後もプロセスを残してファイルの作成・編集・リネーム・削除を差分反映します。回復可能な macOS EventStream 起動失敗、または ready 後の致命的な EventStream error は polling backend へ切り替え、有効な baseline を繰り返しません。失敗通知が baseline 実行中または ready 後に届いた場合もその baseline を保持し、backend handoff のための recovery scan を1回だけ実行します。置換済み backend から遅れて届いた callback は無視します。polling は index と同じ ignore-directory / internal-artifact policy で `.git`、`.cdidx`、build output、dependency、ignored tree を剪定し、周期ごとの不要な全ツリー走査を避けます。baseline 中に buffer された event は `watching` の前に drain し、backend 有効化後の本当の event loss だけを generation ごとに最大 1 回の根拠付きフル差分 recovery scan へ集約します。通常 event は `--debounce `(既定 500 ms)の窓で 1 つの `--files` 更新にまとめ、batch 間ではデータベースごとの index lock を解放するため別の `cdidx` コマンドからの問い合わせも可能です。pending path batch が安全上限に達した場合も recovery scan を 1 回要求します。subdirectory の watch は repository path 上の ancestor `.gitignore` / `.cdidxignore` も監視します。baseline と startup reconciliation が成功するまで `watching` は出力せず、startup generation が失敗した場合は stale なまま ready を宣言せず終了します。`--json` 時は `status: "backend_fallback" / "watching" / "updated" / "rescanned" / "overflow" / "failed" / "stopped"` のライフサイクルイベントを stdout に流します。startup / recovery event は `backend`(macOS では `fsevents` または `polling`)と機械可読な `recovery_reason` を公開し、update/rescan event は `exit_code` を含みます。human 出力も `[watch] …` 要約に同じ backend / recovery context を含めます。backend fallback 中または実行中の sub-run を cancellation した場合も terminal `stopped` event を出力します。最初の Ctrl+C(または SIGTERM)は協調的な cancellation を要求し、2 回目の Ctrl+C は強制終了に利用できます。すべての batch が成功していれば終了コードは `0`、停止前に watch update/rescan が失敗していれば直近の non-zero sub-run exit code です。`--watch` は連続的な差分更新を内蔵しているため `--commits` / `--files` / `--dry-run` と併用できません。 +#### Watch モード + +`--watch` は必要な baseline scan より先に platform watcher を開始し、その後も +プロセスを残して、ファイルの作成・編集・リネーム・削除を差分反映します。 + +| 段階 | 挙動 | +|---|---| +| backend | `FileSystemWatcher` を使用します。macOS は FSEvents、Linux は inotify、Windows は ReadDirectoryChangesW です。 | +| startup | baseline 中の event を buffer し、`watching` を出す前に drain します。baseline と startup reconciliation の両方が成功するまで ready を宣言せず、失敗時は stale な index のまま終了します。 | +| 通常更新 | event burst を `--debounce `(既定 500 ms)で 1 回の `--files` 更新にまとめます。batch 間ではデータベースごとの index lock を解放するため、ほかの `cdidx` コマンドから問い合わせできます。 | +| recovery | backend 有効化後の実際の event loss、または pending path の安全上限到達を、generation ごとに最大 1 回の根拠付きフル差分 recovery scan へ集約します。 | +| ignore 変更 | subdirectory watch でも repository path 上の ancestor `.gitignore` / `.cdidxignore` を監視します。polling は index と同じ policy を使い、`.git`、`.cdidx`、build output、dependency、ignored tree を剪定します。 | +| JSON 出力 | `backend_fallback`、`watching`、`updated`、`rescanned`、`overflow`、`failed`、`stopped` を lifecycle status として流します。startup/recovery event は `backend`(macOS では `fsevents` または `polling`)と機械可読な `recovery_reason`、update/rescan event は `exit_code` を含みます。 | +| human 出力 | `[watch] …` 要約に同じ backend / recovery context を含めます。 | +| 停止 | Ctrl+C または SIGTERM は協調的 cancellation を要求し、fallback 中や sub-run 実行中でも `stopped` を出します。2 回目の Ctrl+C で強制終了できます。 | +| 終了コード | 全 batch が成功した場合は `0`、失敗があった場合は停止前の直近の non-zero update/rescan exit code です。 | +| 併用不可 | 連続的な差分更新を内蔵するため、`--commits`、`--files`、`--dry-run` とは併用できません。 | + +macOS で回復可能な EventStream 起動失敗または ready 後の致命的な +EventStream error が起きた場合は、有効な baseline を繰り返さず polling へ +切り替えます。baseline 実行中または ready 後に失敗通知が届いた場合も baseline を +保持し、backend handoff 用の recovery scan を 1 回だけ実行します。置換済み +backend から遅れて届いた callback は無視します。 macOS では、.NET 8 の subproject watch は project tree の FSEvents を維持しつつ、この runtime が黙って見落とす可能性のある ancestor `.gitignore` / `.cdidxignore` の exact path だけを追加で polling します。project 全体の polling は backend failure recovery に限定したままです。.NET 8 の top-level watch、.NET 9 の subproject watch、Linux / Windows の backend 選択は変わりません。 @@ -5224,9 +5305,45 @@ cdidx definition QueryCommandRunner --exact-name --group-partials --count --json `definition` は、インデックス済みシンボル範囲とチャンク再構成を使って実際の宣言テキストを返します。言語抽出器が本体範囲を推論できる場合は、`--body` で本体内容も返します。 -C# の partial type と partial method では、`definition`、`symbols`、または symbol mode の `inspect` に `--group-partials` を付けると、persist 済みの qualified family identity が同じ実際の `partial` 宣言を1つの論理 family に集約できます。既定は従来どおり物理宣言ごとに1行で、無関係な non-partial type、単に同名の type、partial host 内のネストした non-partial type は集約しません。`file partial` type とその partial member は source file 内に限定されるため、別ファイルにある同名の file-local 宣言は別 family になります。family identity は partial type 自身と各外側 generic type の arity、user type の大文字小文字、意味のある `global::` root 修飾を保持しつつ、`dynamic` / `object` の runtime identity を含む C# predefined alias、明示的に global root を持つ `System` predefined alias(verbatim な `@System` / `@Int32` segment を含む)、`int?` / `global::System.Nullable` のような nullable value type の同値表記、predefined reference type の nullable annotation、verbatim identifier escape、declaration / type 内の comment、parameter attribute・名前・default value、method identifier・generic parameter・parameter list の間にある comment trivia を正規化します。method type parameter は unqualified な type variable として使われた場合だけ ordinal で正規化し、method が `` を宣言していても `N.T` のような qualified leaf は実型として保持します。外側 namespace や `using` alias が `System` を shadow できるため、root のない `System.Int32` は `int` と区別します。抽出器が所有する declaration metadata は post-extraction hook の clone 後も維持され、空行や comment trivia をまたいで modifier-only 行へ分割された `partial` 修飾子を保持します。先行 attribute list の閉じ括弧に続く modifier も認識します。同一行に複数宣言がある場合、先行 attribute は直後の declaration occurrence だけに関連付け、保存済み signature の外側にある lexer 確認済み XML documentation は空行を挟まず隣接する場合だけ semantic rank に使います。block comment・string 内の documentation 風 text や、空行で宣言から切り離された XML documentation は representative rank に影響しません。同一行で反復する同名 partial 宣言は、family navigation 用に別々の identifier column を保持します。family-key 契約は version 管理され、旧 C# row を現行 grouping rule で解釈しません。契約が未登録または stale の場合は、full reindex が現行 family metadata を再公開するまで物理 row を保守的に返します。ただし LSP の位置解決は query 出力を集約せず、type と constructor の target を分離するためだけに partial-type identity を局所的に再構築できます。一致した family 内の canonical representative は決定的に選ばれます。本体を持つ partial method は宣言だけの method より先、非生成 source は generated / designer source より先、抽出 metadata が先行 attribute / XML documentation を記録した宣言、または lexer で解析した indexed signature に attribute・base list・constraint が保持された宣言は、それ以外が同等の宣言より先となり、その後に comment を無視して正規化した declaration identity、ordinal path、source position を使います。generated site は `--include-generated` 指定時に候補へ入り、generated-file metadata を持たない旧 database では generated / designer の filename 規約へ fallback します。 +C# の partial type と partial method では、`--group-partials` により、対応する +物理宣言を 1 つの論理 family に集約できます。 -集約した structured row は、family 内の物理宣言数を `definition_sites` で返し、`partial_family_id`、`representative_reason`、安定順で最大50件の `family_members` を公開します。上限付き list には必ず representative が残り、column は正規化後の identifier(verbatim escape の `@` より後ろ)に揃います。site がさらにある場合は `family_members_truncated` が true になります。`goto` は既定で同じ canonical representative を使い、その family metadata を LSP 形式の JSON に含めます。全物理 location を意図的に取得する場合は `goto --all` を使います。grouped count JSON は `logical_count` と `physical_count`(および `physical_file_count`)を併記し、human summary は `--limit` 適用後に表示した論理行数と query 全体の論理・物理総数を区別します。audit sort を使う `symbols` は family 内の rank metric の最大値で並べつつ canonical representative を維持するため、`--limit` 適用前の `--sort` 順序も単調です。`impact` は同じ family key と代表順を自動的に使い、`logical_definition_count` を返し、出力に必要な上限付き論理代表だけを materialize しながら一致した全物理 site を数えます。positional path または `--path ... --line ...` で選ぶ file mode の `inspect` は物理位置の lookup のままで、`--group-partials` を拒否します。 +| 項目 | 契約 | +|---|---| +| 対応コマンド | `definition`、`symbols`、symbol mode の `inspect` で使えます。file mode の `inspect` は物理 lookup のままで、この option を拒否します。 | +| 既定動作 | option を付けない場合は物理宣言ごとに 1 row です。non-partial、単に同名の type、partial host 内の nested non-partial type は集約しません。 | +| file-local 宣言 | `file partial` type とその partial member は 1 source file に限定され、別 file の同名宣言は別 family になります。 | +| 保持する identity | partial type 自身と外側 type の arity、user type の大文字小文字、意味のある `global::` root 修飾を family key に保持します。 | +| 正規化する同値表記 | `dynamic` / `object` の runtime identity を含む predefined alias、明示的な global-root `System` alias、nullable value type の同値表記、predefined reference type の nullable annotation、verbatim escape、declaration/type comment、parameter attribute・名前・default、method signature 内の comment trivia。 | +| 意図的に区別する表記 | `System` は shadow 可能なので root のない `System.Int32` と `int` は区別します。method type parameter は unqualified variable の場合だけ ordinal で正規化し、`N.T` のような qualified leaf は実型として保持します。 | +| extraction metadata | post-extraction hook の clone 後も declaration metadata を維持し、modifier-only 行に分割された `partial` や balanced attribute list 後の modifier を保持します。同一行の反復宣言は別々の identifier column を持ちます。 | +| documentation rank | 先行 attribute は直後の宣言だけに結び付けます。保存済み signature 外の隣接した lexer 確認済み XML documentation だけが rank に影響し、comment/string 内の類似 text や空行で切り離された documentation は影響しません。 | + +family-key 契約は version 管理されます。契約が未登録または stale の場合は、full +reindex が現行 metadata を公開するまで物理 row を返します。LSP の位置解決は type と +constructor を区別するために局所的な partial identity を再構築できますが、query 出力は +集約しません。 + +canonical representative は次の順序で決定します。 + +1. 本体を持つ partial method を、宣言だけの method より先にします。 +2. 非生成 source を generated / designer source より先にします。 +3. attribute / XML documentation の metadata、または attribute・base list・constraint + を保持する indexed signature がある宣言を優先します。 +4. comment を無視した正規化 declaration identity、ordinal path、source position の + 順で決定します。 + +generated site は `--include-generated` 指定時に候補へ入ります。generated-file +metadata がない旧 database では generated/designer filename 規約へ fallback します。 + +| 集約後の出力 | 意味 | +|---|---| +| family metadata | `definition_sites` は物理宣言数です。row は `partial_family_id`、`representative_reason`、安定順で最大 50 件の `family_members` も公開します。 | +| member 上限 | 上限付き list は representative を必ず残し、verbatim `@` より後ろの identifier に column を揃えます。追加 site がある場合は `family_members_truncated` が true です。 | +| `goto` | 既定では canonical representative と family metadata を LSP 形式の JSON で返します。全物理 location には `goto --all` を使います。 | +| count | JSON は `logical_count`、`physical_count`、`physical_file_count` を返します。human summary は `--limit` 後の表示行数と query 全体の論理・物理総数を区別します。 | +| sorted `symbols` | family 内の最大 rank metric と canonical representative を使い、`--limit` 前の `--sort` 順序を単調に保ちます。 | +| `impact` | 同じ family key と代表順を使い、`logical_definition_count` を返します。必要な上限付き論理代表だけを materialize しつつ、全物理 site を数えます。 | ### 1往復でシンボルを精査する