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
13 changes: 9 additions & 4 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ content.
```bash
cdidx unused --lang csharp --exclude-tests
cdidx unused --kind function --path src/ --limit 50
cdidx unused --bucket likely_unused_private --min-confidence medium
cdidx unused --json --count
cdidx unused --json --by-bucket
```
Expand All @@ -438,8 +439,10 @@ confidence. JSON output includes `summary.by_bucket`, `summary.by_confidence`,
and `bucket_taxonomy` for the `likely_unused_private`,
`maybe_unused_nonpublic`, `public_or_exported_no_refs`, and
`reflection_or_config_suspect` buckets; `--by-bucket` also groups returned
symbols under those bucket keys. Public APIs, framework entrypoints, generated
hooks, reflection, and configuration-based usage can be false positives. C#
symbols under those bucket keys. Use `--bucket <name>` to return only one
bucket, and `--min-confidence <medium|low>` to omit lower-confidence classes.
Public APIs, framework entrypoints, generated hooks, reflection, and
configuration-based usage can be false positives. C#
`nameof(...)`, `typeof(...)`, and direct reflection member-name literals such as
`GetMethod("Foo")` are indexed, but dynamically constructed names still require
manual review.
Expand Down Expand Up @@ -2625,6 +2628,7 @@ object ではなく bare issue array を期待する場合は `--json=array` を
```bash
cdidx unused --lang csharp --exclude-tests
cdidx unused --kind function --path src/ --limit 50
cdidx unused --bucket likely_unused_private --min-confidence medium
cdidx unused --json --count
cdidx unused --json --by-bucket
```
Expand All @@ -2633,8 +2637,9 @@ cdidx unused --json --by-bucket
分類します。JSON 出力には `likely_unused_private`、`maybe_unused_nonpublic`、
`public_or_exported_no_refs`、`reflection_or_config_suspect` bucket 用の
`summary.by_bucket`、`summary.by_confidence`、`bucket_taxonomy` が含まれます。
`--by-bucket` は返却された symbols も bucket key ごとに grouped します。Public API、
framework entrypoint、generated hook、reflection、config 経由の使用は false positive
`--by-bucket` は返却された symbols も bucket key ごとに grouped します。
`--bucket <name>` で単一 bucket だけを返し、`--min-confidence <medium|low>` で
より低い confidence class を除外できます。Public API、framework entrypoint、generated hook、reflection、config 経由の使用は false positive
になりえます。C# の `nameof(...)`、`typeof(...)`、`GetMethod("Foo")` のような
直接的な reflection member-name literal は indexed されますが、動的に組み立てられる
名前は手動確認が必要です。
Expand Down
19 changes: 19 additions & 0 deletions changelog.d/unreleased/3001.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
category: fixed
issues:
- 3001
affected:
- src/CodeIndex/Cli/QueryCommandRunner.cs
- src/CodeIndex/Database/DbSymbolReader.cs
- src/CodeIndex/Mcp/McpToolDefinitions.cs
- src/CodeIndex/Mcp/McpToolHandlers.cs
- USER_GUIDE.md
---

## English

- **`unused` can now filter by bucket and confidence (#3001)** - CLI users can pass `--bucket <name>` or `--min-confidence <medium|low>`, and MCP `unused_symbols` accepts matching `bucket` and `minConfidence` arguments for focused dead-code audits.

## 日本語

- **`unused` が bucket と confidence で絞り込めるようになりました (#3001)** - CLI では `--bucket <name>` または `--min-confidence <medium|low>` を指定でき、MCP `unused_symbols` も対応する `bucket` / `minConfidence` 引数で dead-code audit の対象を絞り込めます。
3 changes: 3 additions & 0 deletions src/CodeIndex/Cli/CliFlagSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ internal static class CliFlagSchema
private static readonly string[] RawKindsCommands = ["callers", "callees"];
private static readonly string[] RankByCommands = ["callers", "callees"];
private static readonly string[] ByBucketCommands = ["unused"];
private static readonly string[] UnusedFilterCommands = ["unused"];
private static readonly string[] AllResultCommands = ["goto"];

private static readonly string[] SinceCommands = ["search", "definition", "symbols", "files"];
Expand Down Expand Up @@ -235,6 +236,8 @@ private static IReadOnlyList<CliFlag> BuildAll()
new() { Name = "--visibility", ValuePlaceholder = "<visibility[,visibility]>", Description = "Filter by symbol visibility", Commands = Set(VisibilityCommands) },
new() { Name = "--exclude-visibility", ValuePlaceholder = "<visibility[,visibility]>", Description = "Exclude symbol visibility", Commands = Set(VisibilityCommands) },
new() { Name = "--by-bucket", Description = "Unused: include per-bucket grouped result arrays in JSON output", Commands = Set(ByBucketCommands) },
new() { Name = "--bucket", ValuePlaceholder = "<bucket>", Description = "Unused: return only one confidence bucket", Commands = Set(UnusedFilterCommands) },
new() { Name = "--min-confidence", ValuePlaceholder = "<medium|low>", Description = "Unused: return symbols at or above this confidence", Commands = Set(UnusedFilterCommands) },
new() { Name = "--all", Description = "goto: return all matching LSP locations instead of requiring a single target", Commands = Set(AllResultCommands) },
new() { Name = "--rank-by", ValuePlaceholder = "<weighted|count|kind>", Description = "Rank callers/callees by weighted structural score, raw count, or kind bucket", Commands = Set(RankByCommands) },
new() { Name = "--raw-kinds", Description = "Show raw reference kinds instead of logical graph kinds", Commands = Set(RawKindsCommands) },
Expand Down
2 changes: 1 addition & 1 deletion src/CodeIndex/Cli/ConsoleUi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private static readonly (string Command, string Usage)[] CommandUsageLines =
("validate", "cdidx validate [--db <path>] [--json[=array]] [--format <text|json|count|compact|csv|tsv|lsp|qf|sarif>] [--verbose] [--limit <n>|--top <n>] [--kind <kind>] [--severity <info|warning|error>] [--path <glob>]"),
("impact", "cdidx impact <query>|--query <query>|-- <query> [--db <path>] [--json] [--verbose] [--limit <n>|--top <n>] [--lang <lang>] [--path <glob>] [--exclude-path <glob>] [--exclude-tests] [--body] [--snippet-lines <n>] [--max-line-width <n>] [--max-hops <n>] [--count] [--with-paths]"),
("deps", "cdidx deps [--db <path>] [--json] [--format <dot|graphml|json-graph|edgelist>] [--verbose] [--limit <n>|--top <n>] [--lang <lang>] [--path <glob>] [--exclude-path <glob>] [--exclude-tests] [--reverse] [--cycles]"),
("unused", "cdidx unused [--db <path>] [--json] [--verbose] [--limit <n>|--top <n>] [--kind <kind>] [--visibility <v[,v]>] [--exclude-visibility <v[,v]>] [--lang <lang>] [--path <glob>] [--exclude-path <glob>] [--exclude-tests] [--count]"),
("unused", "cdidx unused [--db <path>] [--json] [--verbose] [--limit <n>|--top <n>] [--kind <kind>] [--bucket <bucket>] [--min-confidence <medium|low>] [--visibility <v[,v]>] [--exclude-visibility <v[,v]>] [--lang <lang>] [--path <glob>] [--exclude-path <glob>] [--exclude-tests] [--count]"),
("hotspots", "cdidx hotspots [--db <path>] [--json] [--verbose] [--limit <n>|--top <n>] [--kind <kind>] [--visibility <v[,v]>] [--exclude-visibility <v[,v]>] [--lang <lang>] [--path <glob>] [--exclude-path <glob>] [--exclude-tests] [--count] [--group-by <symbol|file|statement>] [--group-by-name]"),
("suggestions", "cdidx suggestions <list|show|export> [id] [--db <path>] [--json] [--status <all|submitted|unsubmitted>] [--language <lang>] [--category <category>] [--since <datetime>] [--agent <name>] [--format <json|markdown|issue-drafts>] [--open-issues <path>]"),
("export", "cdidx export <archive> [--db <path>] [--json]"),
Expand Down
90 changes: 88 additions & 2 deletions src/CodeIndex/Cli/QueryCommandRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ public static class QueryCommandRunner
"--top",
"--lang",
"--kind",
"--bucket",
"--min-confidence",
"--severity",
"--visibility",
"--exclude-visibility",
Expand Down Expand Up @@ -4655,6 +4657,8 @@ public static int RunUnused(string[] cmdArgs, JsonSerializerOptions jsonOptions)
return CommandExitCodes.UsageError;
if (TryWriteInvalidKindFilterError(options, "unused", KnownSymbolKindFilters))
return CommandExitCodes.InvalidArgument;
if (TryWriteInvalidUnusedFilterError(options))
return CommandExitCodes.InvalidArgument;
if (TryWriteUnexpectedPositionals("unused", options))
return CommandExitCodes.UsageError;

Expand All @@ -4672,7 +4676,16 @@ public static int RunUnused(string[] cmdArgs, JsonSerializerOptions jsonOptions)
reader.ScopeMayIncludeSqlSymbols(options.Kind, options.Lang, options.PathPatterns, options.ExcludePaths, options.ExcludeTests));
if (options.CountOnly)
{
var countSummary = reader.CountUnusedSymbols(options.Kind, options.Lang, options.PathPatterns, options.ExcludePaths, options.ExcludeTests, visibilityFilters: options.VisibilityFilters, excludeVisibilityFilters: options.ExcludeVisibilityFilters);
var countSummary = reader.CountUnusedSymbols(
options.Kind,
options.Lang,
options.PathPatterns,
options.ExcludePaths,
options.ExcludeTests,
visibilityFilters: options.VisibilityFilters,
excludeVisibilityFilters: options.ExcludeVisibilityFilters,
bucketFilter: options.UnusedBucket,
minConfidence: options.MinUnusedConfidence);
var effectiveSqlGraphSignal = countSummary.Count == 0
? zeroResultSqlGraphSignal
: NarrowSqlGraphContractSignal(
Expand Down Expand Up @@ -4704,7 +4717,17 @@ public static int RunUnused(string[] cmdArgs, JsonSerializerOptions jsonOptions)
return CommandExitCodes.Success;
}

var results = reader.GetUnusedSymbols(options.Limit, options.Kind, options.Lang, options.PathPatterns, options.ExcludePaths, options.ExcludeTests, visibilityFilters: options.VisibilityFilters, excludeVisibilityFilters: options.ExcludeVisibilityFilters);
var results = reader.GetUnusedSymbols(
options.Limit,
options.Kind,
options.Lang,
options.PathPatterns,
options.ExcludePaths,
options.ExcludeTests,
visibilityFilters: options.VisibilityFilters,
excludeVisibilityFilters: options.ExcludeVisibilityFilters,
bucketFilter: options.UnusedBucket,
minConfidence: options.MinUnusedConfidence);
var sqlGraphSignal = results.Count == 0
? zeroResultSqlGraphSignal
: NarrowSqlGraphContractSignalByLanguages(
Expand Down Expand Up @@ -5159,6 +5182,8 @@ public static QueryCommandOptions ParseArgs(
int limit = ResolveDefaultPositiveInt(DefaultLimitEnvironmentVariable, DefaultQueryLimit, "--limit", out var defaultLimitError);
string? lang = null;
string? kind = null;
string? unusedBucket = null;
string? minUnusedConfidence = null;
string? severity = null;
string? query = null;
bool rawFts = false;
Expand Down Expand Up @@ -5514,6 +5539,24 @@ void WarnIfDuplicateSingleValueOption(string canonicalName, string newValue)
else
AddParseError(kindError!);
break;
case "--bucket":
if (TryReadStringOptionValue(args, ref i, "--bucket", inlineValue, allowSeparatedDashPrefixedLiteralValue: false, out var unusedBucketValue, out var unusedBucketError))
{
WarnIfDuplicateSingleValueOption("--bucket", unusedBucketValue!);
unusedBucket = unusedBucketValue?.ToLowerInvariant();
}
else
AddParseError(unusedBucketError!);
break;
case "--min-confidence":
if (TryReadStringOptionValue(args, ref i, "--min-confidence", inlineValue, allowSeparatedDashPrefixedLiteralValue: false, out var minUnusedConfidenceValue, out var minUnusedConfidenceError))
{
WarnIfDuplicateSingleValueOption("--min-confidence", minUnusedConfidenceValue!);
minUnusedConfidence = minUnusedConfidenceValue?.ToLowerInvariant();
}
else
AddParseError(minUnusedConfidenceError!);
break;
case "--severity":
if (TryReadStringOptionValue(args, ref i, "--severity", inlineValue, allowSeparatedDashPrefixedLiteralValue: false, out var severityValue, out var severityError))
{
Expand Down Expand Up @@ -5986,6 +6029,8 @@ void WarnIfDuplicateSingleValueOption(string canonicalName, string newValue)
Limit = limit,
Lang = lang,
Kind = kind,
UnusedBucket = unusedBucket,
MinUnusedConfidence = minUnusedConfidence,
Severity = severity,
Query = query,
RawFts = rawFts,
Expand Down Expand Up @@ -6917,6 +6962,35 @@ private static bool TryWriteInvalidKindFilterError(QueryCommandOptions options,
return false;
}

internal static bool IsKnownUnusedBucket(string value)
=> OrderedUnusedBuckets.Contains(value, StringComparer.Ordinal);

internal static bool IsKnownUnusedConfidence(string value)
=> value is "medium" or "low";

private static bool TryWriteInvalidUnusedFilterError(QueryCommandOptions options)
{
if (options.UnusedBucket != null && !IsKnownUnusedBucket(options.UnusedBucket))
{
CommandErrorWriter.Write(
$"invalid --bucket value `{options.UnusedBucket}`.",
$"use one of: {string.Join(", ", OrderedUnusedBuckets)}.",
GetUsageLineOrThrow("unused"));
return true;
}

if (options.MinUnusedConfidence != null && !IsKnownUnusedConfidence(options.MinUnusedConfidence))
{
CommandErrorWriter.Write(
$"invalid --min-confidence value `{options.MinUnusedConfidence}`.",
"use one of: medium, low.",
GetUsageLineOrThrow("unused"));
return true;
}

return false;
}

private static bool TryWriteUnsupportedOptionError(string commandName, string[] cmdArgs, IEnumerable<string> supportedOptions, string? queryLiteral = null)
{
var supported = supportedOptions.ToHashSet(StringComparer.Ordinal);
Expand Down Expand Up @@ -7228,6 +7302,10 @@ private static IEnumerable<string> BuildQueryContextParts(QueryCommandOptions op
yield return $"lang: {options.Lang}";
if (options.Kind != null)
yield return $"kind: {options.Kind}";
if (options.UnusedBucket != null)
yield return $"bucket: {options.UnusedBucket}";
if (options.MinUnusedConfidence != null)
yield return $"min-confidence: {options.MinUnusedConfidence}";
if (options.RankMode != ReferenceRankMode.Weighted)
yield return $"rank-by: {FormatReferenceRankMode(options.RankMode)}";
if (options.ExcludeTests)
Expand Down Expand Up @@ -7268,6 +7346,10 @@ private static JsonObject BuildQueryContextJson(QueryCommandOptions options, Jso
query["lang"] = options.Lang;
if (options.Kind != null)
query["kind"] = options.Kind;
if (options.UnusedBucket != null)
query["bucket"] = options.UnusedBucket;
if (options.MinUnusedConfidence != null)
query["min_confidence"] = options.MinUnusedConfidence;
if (options.RankMode != ReferenceRankMode.Weighted)
query["rank_by"] = FormatReferenceRankMode(options.RankMode);
if (options.ExcludeTests)
Expand Down Expand Up @@ -8438,6 +8520,8 @@ private static void WriteSqlGraphContractWarningIfNeeded(bool json, SqlGraphCont
["--lang"] = "pass a language identifier, e.g. `--lang csharp`. Run `cdidx languages` for the supported set.",
["--query"] = "pass a search literal, e.g. `--query \"authenticate\"`. Use the `--query` form when the literal starts with `-`.",
["--kind"] = "pass a kind identifier, e.g. `--kind function`. definition/symbols/hotspots/unused take a symbol kind; references/callers/callees take a reference kind such as `call`, `instantiate`, or `subscribe`. Run the command's `--help` for the kind list.",
["--bucket"] = "pass one unused-symbol bucket: likely_unused_private, maybe_unused_nonpublic, public_or_exported_no_refs, or reflection_or_config_suspect.",
["--min-confidence"] = "pass one unused-symbol confidence threshold: medium or low.",
["--visibility"] = "pass one or more of public, protected, internal, private, e.g. `--visibility public,internal`.",
["--exclude-visibility"] = "pass one or more of public, protected, internal, private to exclude, e.g. `--exclude-visibility private`.",
["--rank-by"] = "pass `weighted`, `count`, or `kind` (callers/callees only).",
Expand Down Expand Up @@ -8791,6 +8875,8 @@ public sealed class QueryCommandOptions
public int Limit { get; init; } = 20;
public string? Lang { get; init; }
public string? Kind { get; init; }
public string? UnusedBucket { get; init; }
public string? MinUnusedConfidence { get; init; }
public string? Severity { get; init; }
public List<string> VisibilityFilters { get; init; } = [];
public List<string> ExcludeVisibilityFilters { get; init; } = [];
Expand Down
Loading
Loading