Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 では先に属性を外してください。 |

### メタデータ不変条件
Expand Down
2 changes: 2 additions & 0 deletions TESTING_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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`
Expand Down
18 changes: 18 additions & 0 deletions changelog.d/unreleased/5139.fixed.md
Original file line number Diff line number Diff line change
@@ -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` のように件数を表示します。
2 changes: 1 addition & 1 deletion src/CodeIndex/Cli/IndexCommandRunner.FullScan.Output.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 11 additions & 1 deletion tests/CodeIndex.Tests/IndexCommandRunnerDryRunTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
{
Expand Down
Loading