Skip to content
2 changes: 1 addition & 1 deletion DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ case-sensitive equality so format drift is visible instead of silently accepted.
-- File metadata
files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
path TEXT NOT NULL UNIQUE, -- relative path from project root
path TEXT NOT NULL UNIQUE, -- relative path from project root, slash-normalized and Unicode NFC
lang TEXT, -- detected language (e.g. "python")
size INTEGER, -- file size in bytes
lines INTEGER, -- line count
Expand Down
4 changes: 2 additions & 2 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ By default, `cdidx index` stores the database in `<projectPath>/.cdidx/codeindex

`--watch` keeps the process alive after the initial scan and rebuilds the index incrementally as files are created, edited, renamed, or deleted. It uses `FileSystemWatcher` (FSEvents on macOS, inotify on Linux, ReadDirectoryChangesW on Windows), debounces bursts of events (`--debounce <ms>`, default 500 ms) into a single `--files` update, releases the per-DB index lock between batches so other `cdidx` commands can still query, and falls back to a full incremental rescan if the watcher buffer overflows. With `--json` it streams `status: "watching" / "updated" / "rescanned" / "overflow" / "stopped"` lifecycle events to stdout; otherwise it writes `[watch] …` summaries to stderr. Stop the loop with Ctrl+C (or SIGTERM); the final exit code is `0` for a clean stop. `--watch` cannot be combined with `--commits`, `--files`, or `--dry-run` — the loop already drives continuous incremental updates.

Indexing keeps the built-in skip lists (`node_modules`, `bin`, `obj`, lockfiles, etc.) and also honors user `.gitignore` plus optional `.cdidxignore` rules across full scans, `--files`, and `--commits` updates. Ignore files are read as UTF-8, so non-ASCII patterns behave the same across platforms. On Windows, paths marked with the Hidden or System attribute are skipped before language detection so broad scans do not enter OS-owned caches such as `System Volume Information` or `$Recycle.Bin`; clear those attributes before indexing project-owned source files because ignore rules only exclude additional paths. When the project is inside Git, ignore matching follows the repository's `core.ignorecase` setting, even when the indexed project path is a subdirectory inside that repo; repo-root and other ancestor `.gitignore` files above that subdirectory still apply, and `--commits` resolves changed paths from the repository root before narrowing them back to the indexed project root. `**` only gets Git-style special handling in the documented path forms rather than as an unrestricted cross-directory wildcard. If an update refresh includes ignore-file changes, cdidx automatically falls back to a full scan so newly ignored files are purged safely. Invalid ignore lines are skipped with a warning instead of aborting the whole run, while unreadable ignore files fail closed for that directory scope so cdidx does not index with incomplete rules.
Indexing keeps the built-in skip lists (`node_modules`, `bin`, `obj`, lockfiles, etc.) and also honors user `.gitignore` plus optional `.cdidxignore` rules across full scans, `--files`, and `--commits` updates. A project-root `.codeindex/.cdidxignore` is also loaded as a workspace-scoped ignore file, which lets multi-workspace manifests keep local cdidx-only ignore rules out of the repository root. Ignore files are read as UTF-8, so non-ASCII patterns behave the same across platforms. On Windows, paths marked with the Hidden or System attribute are skipped before language detection so broad scans do not enter OS-owned caches such as `System Volume Information` or `$Recycle.Bin`; clear those attributes before indexing project-owned source files because ignore rules only exclude additional paths. When the project is inside Git, ignore matching follows the repository's `core.ignorecase` setting, even when the indexed project path is a subdirectory inside that repo; repo-root and other ancestor `.gitignore` files above that subdirectory still apply, and `--commits` resolves changed paths from the repository root before narrowing them back to the indexed project root. Nested directories that contain their own `.git` directory or gitfile are treated as repository boundaries and skipped by default. Indexed file paths are stored in Unicode NFC form so composed and decomposed path spellings match across platforms. `**` only gets Git-style special handling in the documented path forms rather than as an unrestricted cross-directory wildcard. If an update refresh includes ignore-file changes, cdidx automatically falls back to a full scan so newly ignored files are purged safely. Invalid ignore lines are skipped with a warning instead of aborting the whole run, while unreadable ignore files fail closed for that directory scope so cdidx does not index with incomplete rules.

Default output:

Expand Down Expand Up @@ -2510,7 +2510,7 @@ cdidx ./myproject --json

インデックスの問題をデバッグしたり、どのファイルが実際に処理されたかを確認するのに便利です。

既定では `cdidx index` は DB を `<projectPath>/.cdidx/codeindex.db` に置きます。組み込みのスキップ対象 (`node_modules`、`bin`、`obj`、lockfile など) は常に除外され、さらにユーザーの `.gitignore` と任意の `.cdidxignore` もフルスキャン、`--files`、`--commits` の更新経路すべてで尊重されます。ignore ファイルは UTF-8 として読み込むため、非 ASCII のパターンも platform 間で同じように動作します。同じディレクトリでは `.gitignore` を先に読み、その後で `.cdidxignore` を読みます。後のルールは加算的に適用されるため、`.cdidxignore` の `!` パターンで同じディレクトリスコープの `.gitignore` が先に除外した path を再包含できます。Windows では Hidden または System 属性が付いたパスを言語検出前にスキップするため、広い範囲を走査しても `System Volume Information` や `$Recycle.Bin` のような OS 管理 cache には入りません。プロジェクト所有のソースを索引したい場合は、ignore ルールでは再包含できないため先にそれらの属性を外してください。Git 管理下では ignore の大文字小文字判定は OS 名ではなくリポジトリの `core.ignorecase` に従い、repo 配下の subdirectory を project root にした場合でも同じ設定を引き継ぎます。さらに、その subdirectory より上位にある repo-root などの `.gitignore` も有効で、`--commits` の changed path も一度リポジトリルート基準で解決してから project root 配下へ絞り込みます。`**` も無制限のクロスディレクトリ wildcard ではなく Git の path-form globstar でのみ特別扱いされます。`--commits` 実行中に ignore ファイル自体が変わっていた場合は、新しく無視対象になったファイルを安全にパージするため自動でフルスキャンへフォールバックします。不正な ignore 行は警告してスキップし、index 全体は中断しません。逆に ignore ファイル自体が読めない場合は、そのディレクトリ範囲を fail-closed で扱い、不完全なルールのまま index しません。
既定では `cdidx index` は DB を `<projectPath>/.cdidx/codeindex.db` に置きます。組み込みのスキップ対象 (`node_modules`、`bin`、`obj`、lockfile など) は常に除外され、さらにユーザーの `.gitignore` と任意の `.cdidxignore` もフルスキャン、`--files`、`--commits` の更新経路すべてで尊重されます。project root の `.codeindex/.cdidxignore` も workspace scope の ignore ファイルとして読み込むため、multi-workspace manifest 用の cdidx 専用ルールを repository root に置かずに管理できます。ignore ファイルは UTF-8 として読み込むため、非 ASCII のパターンも platform 間で同じように動作します。同じディレクトリでは `.gitignore` を先に読み、その後で `.cdidxignore` を読みます。後のルールは加算的に適用されるため、`.cdidxignore` の `!` パターンで同じディレクトリスコープの `.gitignore` が先に除外した path を再包含できます。Windows では Hidden または System 属性が付いたパスを言語検出前にスキップするため、広い範囲を走査しても `System Volume Information` や `$Recycle.Bin` のような OS 管理 cache には入りません。プロジェクト所有のソースを索引したい場合は、ignore ルールでは再包含できないため先にそれらの属性を外してください。Git 管理下では ignore の大文字小文字判定は OS 名ではなくリポジトリの `core.ignorecase` に従い、repo 配下の subdirectory を project root にした場合でも同じ設定を引き継ぎます。さらに、その subdirectory より上位にある repo-root などの `.gitignore` も有効で、`--commits` の changed path も一度リポジトリルート基準で解決してから project root 配下へ絞り込みます。独自の `.git` ディレクトリまたは gitfile を持つ nested directory は repository boundary として扱い、既定でスキップします。index に保存する file path は Unicode NFC へ正規化するため、合成済み・分解済みの path 表記が platform をまたいでも一致します。`**` も無制限のクロスディレクトリ wildcard ではなく Git の path-form globstar でのみ特別扱いされます。`--commits` 実行中に ignore ファイル自体が変わっていた場合は、新しく無視対象になったファイルを安全にパージするため自動でフルスキャンへフォールバックします。不正な ignore 行は警告してスキップし、index 全体は中断しません。逆に ignore ファイル自体が読めない場合は、そのディレクトリ範囲を fail-closed で扱い、不完全なルールのまま index しません。

古い `.cdidx/codeindex.db` を Unicode-aware な `--exact` に上げたいだけなら、フル rebuild は不要です:

Expand Down
15 changes: 15 additions & 0 deletions changelog.d/unreleased/1832.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
category: fixed
issues:
- 1832
affected:
- tests/CodeIndex.Tests/FileIndexerTests.cs
---

## English

- **Case-varied built-in skip directory names stay excluded on case-insensitive scans (#1832)** — regression coverage now locks in that directories such as `Node_Modules/` match the built-in `node_modules` skip rule.

## 日本語

- **大文字小文字が異なる組み込み skip directory 名も case-insensitive scan では除外され続けます (#1832)** — `Node_Modules/` のような directory が組み込みの `node_modules` skip ルールに一致することを回帰テストで固定しました。
21 changes: 21 additions & 0 deletions changelog.d/unreleased/2022.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
category: fixed
issues:
- 2022
- 2023
- 1947
affected:
- src/CodeIndex/Indexer/Scanning/FileIndexer.cs
- src/CodeIndex/Cli/DbPathResolver.cs
- src/CodeIndex/Cli/IndexCommandRunner.cs
- USER_GUIDE.md
- DEVELOPER_GUIDE.md
---

## English

- **Workspace-local ignore files, nested repository boundaries, and Unicode path spelling are now handled consistently (#2022, #2023, #1947)** — indexing loads `.codeindex/.cdidxignore`, skips nested `.git` repositories by default, and stores/looks up indexed paths in Unicode NFC form.

## 日本語

- **workspace local の ignore ファイル、nested repository 境界、Unicode path 表記を一貫して扱うようになりました (#2022, #2023, #1947)** — indexing は `.codeindex/.cdidxignore` を読み込み、nested `.git` repository を既定でスキップし、index path の保存・lookup を Unicode NFC 形式に統一します。
4 changes: 2 additions & 2 deletions src/CodeIndex/Cli/DbPathResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public static string ResolveQueryFilePath(string dbPath, string filePath, bool d
if (string.IsNullOrWhiteSpace(filePath))
return filePath;

var normalizedFilePath = FileIndexer.NormalizePathSeparators(filePath);
var normalizedFilePath = FileIndexer.NormalizeIndexPath(filePath);
if (!Path.IsPathRooted(filePath))
return normalizedFilePath;

Expand All @@ -122,7 +122,7 @@ public static string ResolveQueryFilePath(string dbPath, string filePath, bool d
if (!IsUnderDirectory(fullProjectRoot, fullFilePath))
return normalizedFilePath;

return FileIndexer.NormalizePathSeparators(Path.GetRelativePath(fullProjectRoot, fullFilePath));
return FileIndexer.NormalizeIndexPath(Path.GetRelativePath(fullProjectRoot, fullFilePath));
}

/// <summary>
Expand Down
Loading
Loading