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
23 changes: 23 additions & 0 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2171,6 +2171,18 @@ A positional value containing an unescaped `*` or `?` is treated exactly like
for example `cdidx files '**/*.cs'`. Positionals without those glob
metacharacters remain filename-substring queries.

For `files` and `map`, `--exclude-tests` also activates the implicit production
source baseline. Its `src/**` include group is intersected with the user's
repeatable `--path` group; patterns within either group remain OR-combined.
The implicit source exclusions and every explicit `--exclude-path` are then
applied together. Therefore adding a broad or narrow `--path` cannot re-enable
files outside the baseline or files excluded by it. Generated-file policy,
`--since`, path matching semantics, and count/row scope remain unchanged.
Count, compact, summary, and map JSON expose this composition under
`query_context.effective_path_scope`: `include_groups` and `exclude_groups`
identify `implicit_source_baseline` versus `explicit_cli` provenance, while the
operator fields describe how the groups and their patterns are combined.

Output:

```
Expand Down Expand Up @@ -5853,6 +5865,17 @@ cdidx files --format compact --max-json-bytes 8000
`cdidx files '**/*.cs'` のように引用してください。これらの glob metacharacter を
含まない positional 値は、従来どおり filename substring query として扱われます。

`files` と `map` では、`--exclude-tests` によって暗黙の production source baseline
も有効になります。baseline の `src/**` include group は、繰り返し指定できるユーザーの
`--path` group と AND で交差し、各 group 内の pattern は従来どおり OR で結合されます。
その後、暗黙の source 除外とすべての明示 `--exclude-path` が合わせて適用されます。
したがって、広いまたは狭い `--path` を追加しても baseline 外や baseline 除外済みの file
が再び有効になることはありません。generated-file policy、`--since`、path match semantics、
count / row の scope は維持されます。count、compact、summary、map の JSON は、この合成を
`query_context.effective_path_scope` に公開します。`include_groups` と `exclude_groups` は
`implicit_source_baseline` / `explicit_cli` の由来を示し、operator field は group と pattern
の結合方法を示します。

出力:

```
Expand Down
23 changes: 23 additions & 0 deletions changelog.d/unreleased/5229.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
category: fixed
issues:
- 5229
affected:
- src/CodeIndex/Cli/SourceScopeDefaults.cs
- src/CodeIndex/Cli/QueryCommandRunner.Discovery.cs
- src/CodeIndex/Cli/QueryCommandRunner.Map.cs
- src/CodeIndex/Cli/QueryCommandRunner.ResultEnvelopes.cs
- src/CodeIndex/Database/DbReader.cs
- src/CodeIndex/Database/RepoMapBuilder.cs
- tests/CodeIndex.Tests/DbReaderTests.cs
- tests/CodeIndex.Tests/QueryCommandRunnerFilesTests.cs
- USER_GUIDE.md
---

## English

- **`files --exclude-tests` path filters are now monotonic (#5229)** — explicit `--path` values are intersected with the implicit production-source baseline instead of replacing it, so broad or narrow includes cannot re-enable baseline-excluded files. Count, row, and map scopes now share the same composition, and JSON query metadata reports effective include/exclude provenance.

## 日本語

- **`files --exclude-tests` の path filter が単調になりました (#5229)** — 明示した `--path` は暗黙の production-source baseline を置き換えず、その baseline と交差するようになったため、広いまたは狭い include で baseline 除外済みの file が再び有効になることはありません。count、row、map は同じ scope 合成を共有し、JSON query metadata は有効な include / exclude の由来を報告します。
4 changes: 2 additions & 2 deletions src/CodeIndex/Cli/QueryCommandRunner.ArgumentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,8 @@ private void ApplySearchSourceOptionDefaults()
}

if (pathPatterns.Count == 0)
AddDistinct(pathPatterns, SearchAuditRecipes.DefaultSourcePathPatterns);
AddDistinct(excludePaths, SearchAuditRecipes.DefaultSourceExcludePaths);
AddDistinct(pathPatterns, SourceScopeDefaults.IncludePaths);
AddDistinct(excludePaths, SourceScopeDefaults.ExcludePaths);
AddSourceOnlyDefaultExcludeOrigin(excludeOrigins, matchOrigins, SearchMatchClassifier.Comment);
AddSourceOnlyDefaultExcludeOrigin(excludeOrigins, matchOrigins, SearchMatchClassifier.HelpText);
AddSourceOnlyDefaultExcludeOrigin(excludeOrigins, matchOrigins, SearchMatchClassifier.SchemaDescription);
Expand Down
45 changes: 35 additions & 10 deletions src/CodeIndex/Cli/QueryCommandRunner.Discovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,14 @@ public static int RunFiles(string[] cmdArgs, JsonSerializerOptions jsonOptions)
{
if (options.CountOnly)
{
var counts = reader.CountListFiles(options.Query, options.Lang, filesScope.PathPatterns, filesScope.ExcludePaths, filesScope.ExcludeTests, options.Since);
var counts = reader.CountListFiles(
options.Query,
options.Lang,
filesScope.PathPatterns,
filesScope.ExcludePaths,
filesScope.ExcludeTests,
options.Since,
requiredPathPatterns: filesScope.RequiredPathPatterns);
var generatedFileCountExcluded = CountGeneratedFilesExcluded(reader, options, filesScope);
if (options.Json)
{
Expand Down Expand Up @@ -572,7 +579,8 @@ public static int RunFiles(string[] cmdArgs, JsonSerializerOptions jsonOptions)
filesScope.ExcludeTests,
options.Since,
orderBySize: options.RawBytes,
offset: JsonEnvelopeWrapper.GetBoundedResponseOffset("files"));
offset: JsonEnvelopeWrapper.GetBoundedResponseOffset("files"),
requiredPathPatterns: filesScope.RequiredPathPatterns);
Func<FileResult, JsonNode?> rowFactory =
result => ToFileDiscoveryJsonNode(result, jsonOptions, options.OutputFormat == OutputFormatCompact);
if (results.Count == 0)
Expand Down Expand Up @@ -610,7 +618,14 @@ public static int RunFiles(string[] cmdArgs, JsonSerializerOptions jsonOptions)

if (IsDiscoveryNdjson(options))
{
var counts = reader.CountListFiles(options.Query, options.Lang, filesScope.PathPatterns, filesScope.ExcludePaths, filesScope.ExcludeTests, options.Since);
var counts = reader.CountListFiles(
options.Query,
options.Lang,
filesScope.PathPatterns,
filesScope.ExcludePaths,
filesScope.ExcludeTests,
options.Since,
requiredPathPatterns: filesScope.RequiredPathPatterns);
var stream = WriteDiscoveryNdjson(
reader,
options,
Expand All @@ -625,7 +640,14 @@ public static int RunFiles(string[] cmdArgs, JsonSerializerOptions jsonOptions)

if (ShouldWriteBoundedDiscoveryJsonPayload(options))
{
var counts = reader.CountListFiles(options.Query, options.Lang, filesScope.PathPatterns, filesScope.ExcludePaths, filesScope.ExcludeTests, options.Since);
var counts = reader.CountListFiles(
options.Query,
options.Lang,
filesScope.PathPatterns,
filesScope.ExcludePaths,
filesScope.ExcludeTests,
options.Since,
requiredPathPatterns: filesScope.RequiredPathPatterns);
return WriteBoundedDiscoveryJsonPayload(
reader,
options,
Expand Down Expand Up @@ -693,24 +715,26 @@ internal static (List<string>? Queries, bool HadExplicitInput) BuildSymbolQueryL

private sealed record DiscoveryFileScopeFilters(
IReadOnlyList<string> PathPatterns,
IReadOnlyList<string> RequiredPathPatterns,
IReadOnlyList<string> ExcludePaths,
bool ExcludeTests);

private static DiscoveryFileScopeFilters BuildDiscoveryFileScopeFilters(QueryCommandOptions options)
{
if (!options.ExcludeTests || options.PathPatterns.Count > 0)
if (!options.ExcludeTests)
{
return new(
options.PathPatterns,
[],
options.ExcludePaths,
options.ExcludeTests);
}

var pathPatterns = new List<string>(options.PathPatterns);
AddDistinct(pathPatterns, SearchAuditRecipes.DefaultSourcePathPatterns);
var excludePaths = new List<string>(options.ExcludePaths);
AddDistinct(excludePaths, SearchAuditRecipes.DefaultSourceExcludePaths);
return new(pathPatterns, excludePaths, ExcludeTests: true);
AddDistinct(excludePaths, SourceScopeDefaults.ExcludePaths);
options.DiscoveryBaselineIncludePaths = SourceScopeDefaults.IncludePaths;
options.DiscoveryBaselineExcludePaths = SourceScopeDefaults.ExcludePaths;
return new(options.PathPatterns, SourceScopeDefaults.IncludePaths, excludePaths, ExcludeTests: true);
}

private static int? CountGeneratedFilesExcluded(DbReader reader, QueryCommandOptions options, DiscoveryFileScopeFilters filesScope)
Expand All @@ -725,7 +749,8 @@ private static DiscoveryFileScopeFilters BuildDiscoveryFileScopeFilters(QueryCom
filesScope.ExcludePaths,
filesScope.ExcludeTests,
options.Since,
generatedOnly: true).Count;
generatedOnly: true,
requiredPathPatterns: filesScope.RequiredPathPatterns).Count;

private static void AddGeneratedFileFilterJsonFields(
JsonObject payload,
Expand Down
9 changes: 7 additions & 2 deletions src/CodeIndex/Cli/QueryCommandRunner.Map.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ public static int RunMap(string[] cmdArgs, JsonSerializerOptions jsonOptions)
oversizedByteThreshold: evaluateIssueDraftCandidates ? MapIssueDraftByteThreshold : null,
offset: JsonEnvelopeWrapper.GetBoundedResponseOffset("map"),
requestedCollection: JsonEnvelopeWrapper.GetBoundedMapCollection(),
summaryProjection: JsonEnvelopeWrapper.IsBoundedMapScalarProjection());
summaryProjection: JsonEnvelopeWrapper.IsBoundedMapScalarProjection(),
requiredPathPatterns: filesScope.RequiredPathPatterns);
var generatedFileCountExcluded = options.IncludeGenerated
? 0
: !reader.GeneratedFileFilterAvailable
Expand All @@ -90,7 +91,8 @@ public static int RunMap(string[] cmdArgs, JsonSerializerOptions jsonOptions)
pathPatterns: filesScope.PathPatterns,
excludePathPatterns: filesScope.ExcludePaths,
excludeTests: filesScope.ExcludeTests,
generatedOnly: true).Count;
generatedOnly: true,
requiredPathPatterns: filesScope.RequiredPathPatterns).Count;
WorkspaceMetadataEnricher.Enrich(map, options.DbPath, options.DbPathExplicit);
var compactTruncation = options.Compact ? ApplyRepoMapCompactCaps(map, compactLimit, options) : null;

Expand Down Expand Up @@ -266,6 +268,8 @@ private static JsonObject BuildRepoMapJsonPayload(
options,
generatedFileCountExcluded,
generatedFileFilterAvailable);
if (options.DiscoveryBaselineIncludePaths.Count > 0 || options.DiscoveryBaselineExcludePaths.Count > 0)
payload["query_context"] = BuildQueryContextJson(options, jsonOptions);
if (options.MapSummaryOnly)
{
KeepRepoMapJsonProperties(payload, RepoMapSummaryJsonProperties);
Expand Down Expand Up @@ -582,6 +586,7 @@ private static string BuildRepoMapIssueDraftBody(RepoFileSummaryResult file, Jso
"worktree_head_changed",
"head_freshness",
"graph_table_available",
"query_context",
};

private static readonly IReadOnlyDictionary<string, string[]> RepoMapSectionJsonProperties = new Dictionary<string, string[]>(StringComparer.Ordinal)
Expand Down
51 changes: 51 additions & 0 deletions src/CodeIndex/Cli/QueryCommandRunner.ResultEnvelopes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ private static JsonObject BuildQueryContextJson(QueryCommandOptions options, Jso
query["sort"] = options.SymbolSortMode.ToString().ToLowerInvariant();
if (options.ExcludeTests)
query["exclude_tests"] = true;
if (options.DiscoveryBaselineIncludePaths.Count > 0 || options.DiscoveryBaselineExcludePaths.Count > 0)
query["effective_path_scope"] = BuildEffectiveDiscoveryPathScopeJson(options, jsonOptions);
if (options.ExcludeComments)
query["exclude_comments"] = true;
if (options.ExcludeStrings)
Expand Down Expand Up @@ -321,6 +323,55 @@ private static JsonObject BuildQueryContextJson(QueryCommandOptions options, Jso
return query;
}

private static JsonObject BuildEffectiveDiscoveryPathScopeJson(
QueryCommandOptions options,
JsonSerializerOptions jsonOptions)
{
var context = CliJsonSerializerContextFactory.Create(jsonOptions);
var includeGroups = new JsonArray
{
new JsonObject
{
["origin"] = "implicit_source_baseline",
["patterns"] = JsonSerializer.SerializeToNode(options.DiscoveryBaselineIncludePaths.ToList(), context.ListString),
},
};
if (options.PathPatterns.Count > 0)
{
includeGroups.Add(new JsonObject
{
["origin"] = "explicit_cli",
["patterns"] = JsonSerializer.SerializeToNode(options.PathPatterns, context.ListString),
});
}

var excludeGroups = new JsonArray
{
new JsonObject
{
["origin"] = "implicit_source_baseline",
["patterns"] = JsonSerializer.SerializeToNode(options.DiscoveryBaselineExcludePaths.ToList(), context.ListString),
},
};
if (options.ExcludePaths.Count > 0)
{
excludeGroups.Add(new JsonObject
{
["origin"] = "explicit_cli",
["patterns"] = JsonSerializer.SerializeToNode(options.ExcludePaths, context.ListString),
});
}

return new JsonObject
{
["include_group_operator"] = "and",
["patterns_within_include_group_operator"] = "or",
["include_groups"] = includeGroups,
["exclude_group_operator"] = "or",
["exclude_groups"] = excludeGroups,
};
}

private static void AddReferenceRankingQueryContextJson(
JsonObject payload,
QueryCommandOptions options,
Expand Down
2 changes: 2 additions & 0 deletions src/CodeIndex/Cli/QueryCommandRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ public sealed class QueryCommandOptions
public string? SolutionFilter { get; init; }
public List<string> ExcludePaths { get; init; } = [];
public bool ExcludeTests { get; init; }
internal IReadOnlyList<string> DiscoveryBaselineIncludePaths { get; set; } = [];
internal IReadOnlyList<string> DiscoveryBaselineExcludePaths { get; set; } = [];
public bool IncludeGenerated { get; init; }
public bool CountOnly { get; init; }
public bool GroupPartials { get; init; }
Expand Down
Loading
Loading