diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index ff065dccd..902f8d43e 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -533,7 +533,7 @@ scans. | 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. | | Watch symlink reconciliation | Under `--watch --follow-symlinks internal`, an event for a lexical in-workspace symlink that now resolves outside the workspace remains a reconciliation input. The scoped update removes any stale indexed row without opening or indexing the disallowed target. The polling backend traverses allowed directory links under `internal` / `all` in the full scanner's depth-first order, preserves the scanner-selected lexical alias, deduplicates resolved directory identities to bound cycles, and excludes internal database artifacts reached through directory aliases. | | Explicit-file preflight | Explicit `--files` updates retain each directly supplied token and its normalization provenance, classify that complete list before any database write, and atomically return `UsageError` when a token is invalid, is a directory, or resolves to a duplicate canonical target. A bare `--files` is rejected before artifacts instead of becoming a full scan. `--project` expansion remains derived scope input in `UpdateFiles` and is not reclassified as a direct `--files` selection. Real and dry-run modes expose the same `rejected_paths` entries (`input_index`, support-safe `path`, stable `reason`) and bounded count/truncation metadata for outside-root, symlink-escaping or symlink-policy-disallowed, nonexistent, filtered, unsupported, and duplicate selections. Canonical duplicate checks use each resolved target's filesystem casing rather than the workspace-wide policy, and `none` mode detects actual symlink/reparse segments instead of treating native spelling normalization as a link. Windows 8.3 ancestor aliases canonicalize to the spelling already established in indexed-path membership (#5122). Indexed missing, filtered, or unsupported paths remain valid cleanup targets; indexed control files that become directories, FIFOs, or policy-disallowed symlinks remain tombstones and are classified without opening or following the object. Existing relevant ancestor ignore files remain valid reconciliation controls. In-workspace extractor/configuration inputs, including missing or deleted inputs used as reconciliation signals, also remain valid; generated code remains a valid explicit target. TypeScript path-alias configuration reads carry the active `none` / `internal` / `all` policy into both in-process and worker extraction, resolve only targets permitted by that policy, and secure-open only regular files. Failure to read indexed-path membership fails closed with `E008_DB_ERROR`. This validation does not change the successful zero-file contract for implicit full scans without `--files`. | -| 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. | +| 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. Human full-index completion reports the dangling-link count with the user-facing `skipped` label. | | 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 @@ -4471,7 +4471,7 @@ literal、`origin: decode_replacement` が不正 byte に対して decoder が | target identity | discovery、dry-run、C# preflight、content loading は同じ解決済み identity を使います。安定した許可済み外部 target は index し、preflight 後に retarget された link は source drift として拒否します。 | | watch の symlink reconciliation | `--watch --follow-symlinks internal` では、workspace 内にある lexical symlink が workspace 外へ解決されるようになっても、その event を reconciliation input として保持します。scoped update は禁止された target を open / index せず、残っている stale indexed row を削除します。polling backend は `internal` / `all` で許可された directory link を full scanner と同じ depth-first 順で辿り、scanner が選択した lexical alias を維持し、解決済み directory identity の重複排除で cycle を bounded に保ち、directory alias 経由で到達する内部 DB artifact を除外します。 | | 明示 file の preflight | 明示的な `--files` 更新では、直接指定された各 token と正規化 provenance を保持し、DB へ書き込む前にそのリスト全体を分類します。無効な token、directory、または同じ canonical target に解決される重複が 1 件でもあれば、要求全体を atomic に `UsageError` とします。path token のない `--files` は artifact 作成前に拒否し、full scan へ移行しません。`--project` の展開結果は `UpdateFiles` 内の導出済み scope input のままとし、直接指定された `--files` 選択として再分類しません。通常実行と dry-run は、project root 外、symlink escape、選択した symlink policy で許可されない path、存在しない path、filter 対象、未対応、重複について、同じ `rejected_paths` entry(`input_index`、support-safe な `path`、安定した `reason`)と上限・件数・truncation metadata を返します。canonical duplicate 判定には workspace 全体の policy ではなく解決済み target ごとの filesystem casing を使い、`none` mode は native spelling の正規化差を link とみなさず、実在する symlink/reparse segment を検出します。Windows の 8.3 ancestor alias は indexed-path membership に確立済みの spelling へ canonicalize します (#5122)。既に index 済みで現在は missing / filtered / unsupported となった path は cleanup 対象として有効なままであり、indexed control file が directory、FIFO、または policy で禁止された symlink に変わっても tombstone として保持し、その object を open / follow せず分類します。既存の関連 ancestor ignore file は reconciliation control として許可します。workspace 内の extractor/configuration input も、reconciliation signal として使う missing / deleted input を含めて許可し、generated code も正常な明示対象として扱います。TypeScript path alias の configuration read は in-process / worker extraction の両方へ active な `none` / `internal` / `all` policy を渡し、その policy で許可された target だけを解決して regular file のみ secure-open します。indexed path membership を読めない場合は `E008_DB_ERROR` で fail closed します。この検証は、`--files` のない暗黙の full scan が 0 件でも成功する契約を変更しません。 | -| symlink warning | dangling link と directory target の permission failure は scan warning です。dry-run は `warnings_total` / `warnings` に出し、成功終了を維持します。 | +| symlink warning | dangling link と directory target の permission failure は scan warning です。dry-run は `warnings_total` / `warnings` に出し、成功終了を維持します。human full-index completion は dangling link の件数をユーザー向けの `skipped` label とともに表示します。 | | Windows 属性 | Hidden / System path は言語検出前に拒否します。ignore rule では再包含できないため、project 所有 source では先に属性を外してください。 | ### メタデータ不変条件 diff --git a/TESTING_GUIDE.md b/TESTING_GUIDE.md index 9a24fa213..eeb196627 100644 --- a/TESTING_GUIDE.md +++ b/TESTING_GUIDE.md @@ -789,6 +789,7 @@ Candidate-ordered parallel-index recovery tests must prove that the fatal result - The `dotnet.yml` SDK setup has one conditional retry for transient SDK download failures. Keep the first attempt marked `continue-on-error` only while the retry is guarded by its failed outcome, so a second failure still fails the job. - `IndexCommandRunnerTests.cs` Numeric `index` option coverage owns inclusive boundaries, zero/negative/overflow/non-numeric forms, duplicate precedence, text and JSON `E010_USAGE_ERROR` output before database mutation, and structured environment fallback warnings with source/effective-value provenance. Keep every numeric option in the shared matrix when its range or parsing changes. + The dangling-symlink fixture reuses one link across dry-run JSON, full-scan JSON, and human full-scan completion output. Keep the human summary pinned to the `skipped` label and reject leaked implementation identifiers. - `DbRecoveryTests.cs` Database corruption recovery and graceful degradation behavior. Filesystem setup failures for `cdidx index` (read-only DB files and unwritable DB parent directories) are covered in `IndexCommandRunnerTests.cs` so they exercise the same CLI JSON/stderr boundary users see. - `Issue4857ManagedRestoreBackupTests.cs` @@ -1886,6 +1887,7 @@ dotnet test --filter "FullyQualifiedName~GitHelperTests" `dotnet.yml` と `release.yml` の Windows lane は、temp 固定と Defender 除外 setup をこのスクリプトで共有します。通常の `TMP` / `TEMP` は runner の高速な `RUNNER_TEMP\cdidx-temp` を使います。実行可能な plugin / hook / Git fixture だけは `USERPROFILE\cdidx-trusted-test-temp` を使い、current-user 限定の protected ACL と trusted な祖先 chain で production の executable-boundary contract を満たします。この専用 root は `CDIDX_TEST_TRUSTED_TEMP_ROOT` として helper へ渡します。Windows の実行時間を大きく増やすため、通常の SQLite / filesystem fixture を protected root へ移してはいけません。スクリプトは両 root を含む候補 path を正規化・重複排除して監査表示します。`WinDefend` service が稼働中なら、生成した string array を1回の `Add-MpPreference` 呼び出しで登録し、Defender preference を読み戻して欠けた path があれば失敗します。service が停止中または利用不能なら除外は意味を持たないため、warning を出して host setup を継続します。それ以外の Defender 設定失敗や検証失敗は引き続き fatal です。この split、batching、availability、audit、verification、または workflow 呼び出し contract を変更するときは `CiWorkflowTests` も更新してください。 - `IndexCommandRunnerTests.cs` 数値 `index` オプションの coverage は、両端を含む境界値、ゼロ / 負数 / overflow / 非数値、重複指定の優先順位、database mutation 前の text / JSON `E010_USAGE_ERROR`、および source / 実効値の provenance を持つ構造化環境変数 fallback warning を扱います。範囲や parse を変更するときは、共有 matrix にすべての数値オプションを残してください。 + dangling symlink の fixture は、1つの link を dry-run JSON、full-scan JSON、human full-scan completion output で再利用します。human summary は `skipped` label に固定し、実装識別子の露出を拒否してください。 - `DbRecoveryTests.cs` DB破損からの復旧とグレースフル劣化のテスト。`cdidx index` の filesystem setup failure(read-only DB file や書き込み不可の DB 親ディレクトリ)は、ユーザーが見る CLI JSON/stderr 境界を通すため `IndexCommandRunnerTests.cs` で扱います。 - `Issue4857ManagedRestoreBackupTests.cs` diff --git a/changelog.d/unreleased/5139.fixed.md b/changelog.d/unreleased/5139.fixed.md new file mode 100644 index 000000000..af91c90e3 --- /dev/null +++ b/changelog.d/unreleased/5139.fixed.md @@ -0,0 +1,18 @@ +--- +category: fixed +issues: + - 5139 +affected: + - src/CodeIndex/Cli/IndexCommandRunner.FullScan.Output.cs + - tests/CodeIndex.Tests/IndexCommandRunnerDryRunTests.cs + - DEVELOPER_GUIDE.md + - TESTING_GUIDE.md +--- + +## English + +- **Dangling-symlink summaries now use a user-facing skipped label (#5139)** — human full-index completion output reports counts such as `1 skipped` instead of leaking the literal implementation text `output.Skipped`. + +## 日本語 + +- **dangling symlink の summary がユーザー向けの skipped label を使うようになりました (#5139)** — human full-index completion output は、実装上の文字列 `output.Skipped` を露出せず、`1 skipped` のように件数を表示します。 diff --git a/src/CodeIndex/Cli/IndexCommandRunner.FullScan.Output.cs b/src/CodeIndex/Cli/IndexCommandRunner.FullScan.Output.cs index 87235398b..5e75b4447 100644 --- a/src/CodeIndex/Cli/IndexCommandRunner.FullScan.Output.cs +++ b/src/CodeIndex/Cli/IndexCommandRunner.FullScan.Output.cs @@ -237,7 +237,7 @@ private static int WriteFullScanFinalOutput(FullScanFinalOutputContext output) CommandOutputWriter.WriteLine(ConsoleUi.FormatSummaryLine("Symbols", ConsoleUi.FormatNumber(totalSymbols), indent: " ")); CommandOutputWriter.WriteLine(ConsoleUi.FormatSummaryLine("Refs", ConsoleUi.FormatNumber(totalReferences), indent: " ")); if (output.Skipped > 0) CommandOutputWriter.WriteLine(ConsoleUi.FormatSummaryLine("Skipped", $"{ConsoleUi.FormatNumber(output.Skipped)} (unchanged)", indent: " ")); - if (output.ScanResult.DanglingSymlinks.Count > 0) CommandOutputWriter.WriteLine(ConsoleUi.FormatSummaryLine("Dangling symlinks", $"{ConsoleUi.FormatNumber(output.ScanResult.DanglingSymlinks.Count)} output.Skipped", indent: " ")); + if (output.ScanResult.DanglingSymlinks.Count > 0) CommandOutputWriter.WriteLine(ConsoleUi.FormatSummaryLine("Dangling symlinks", $"{ConsoleUi.FormatNumber(output.ScanResult.DanglingSymlinks.Count)} skipped", indent: " ")); if (output.ScanResult.UnknownExtensionFiles.Count > 0) { CommandOutputWriter.WriteLine(ConsoleUi.FormatSummaryLine( diff --git a/tests/CodeIndex.Tests/IndexCommandRunnerDryRunTests.cs b/tests/CodeIndex.Tests/IndexCommandRunnerDryRunTests.cs index b18a23d0c..f31e1b89c 100644 --- a/tests/CodeIndex.Tests/IndexCommandRunnerDryRunTests.cs +++ b/tests/CodeIndex.Tests/IndexCommandRunnerDryRunTests.cs @@ -1952,7 +1952,7 @@ public void Run_DryRunAndFullScan_FollowSymlinksAllAgreeForExternalFileLink_Issu } [Fact] - public void Run_DryRunAndFullScan_ClassifyDanglingSymlinkAsWarning_Issue4829() + public void Run_DryRunAndFullScan_ClassifyDanglingSymlinkAsWarningAndUseHumanSkippedLabel_Issue4829_Issue5139() { var projectRoot = CreateTempProject(); try @@ -2002,6 +2002,16 @@ public void Run_DryRunAndFullScan_ClassifyDanglingSymlinkAsWarning_Issue4829() .GetProperty("message") .GetString(), StringComparison.OrdinalIgnoreCase); + + var (humanExitCode, humanOutput) = RunAndCaptureOutput([ + projectRoot, + "--follow-symlinks", + "all", + ]); + + Assert.Equal(CommandExitCodes.Success, humanExitCode); + Assert.Contains("Dangling symlinks: 1 skipped", humanOutput, StringComparison.Ordinal); + Assert.DoesNotContain("output.Skipped", humanOutput, StringComparison.Ordinal); } finally {