diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index abed5af659..aff2f7ebd5 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -10,6 +10,23 @@ dotnet test dotnet run --project src/CodeIndex -- [options] ``` +CLI help is intentionally layered: `cdidx --help` stays brief, `cdidx --help-all` +prints the full command/flag/example reference, `cdidx --help-flags` prints only +shared flag tables, and `cdidx --help` prints one command's usage +line. Keep new commands visible in the brief summary only when they are a +primary user workflow; every command must remain present in the full help and +command-specific usage table. + +`cdidx validate` is the user-facing integrity scan for indexed content issues +such as replacement characters, BOMs, NUL bytes, mixed line endings, UTF-16 BOMs, +and likely non-UTF8 content. Keep its CLI usage, README entry, and help summary +in sync when adding validation issue kinds or filters. + +Generated shell completion scripts include a comment with the `cdidx` version +that produced them. When command or flag schema changes, update completion +tests and keep the README guidance that installed completions should be +regenerated after upgrades. + The production CLI and NuGet tool packaging target `net8.0`. The test project multi-targets `net8.0;net9.0`, and CI runs the test suite on both frameworks across Linux, Windows, and macOS. Use a .NET SDK that can restore and run both diff --git a/README.md b/README.md index fdb362a64b..123e70a65f 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ cdidx status --check --json cdidx search "handleRequest" cdidx definition UserService cdidx search "Handle" --project MyApp +cdidx validate cdidx mcp ``` @@ -81,6 +82,15 @@ Output controls: Use `cdidx` when a repository will be searched repeatedly from terminals, scripts, CI, or AI tools. Use `rg` when you only need a one-off text scan. +Help discovery: + +| Need | Command | +|---|---| +| Concise command overview | `cdidx --help` | +| Full command, flag, and example reference | `cdidx --help-all` or `cdidx --help-extended` | +| Shared flag reference only | `cdidx --help-flags` | +| One command's usage line | `cdidx --help` | + Install choice and network notes: | Need | Use | @@ -93,13 +103,24 @@ Install choice and network notes: See [DISTRIBUTION.md](DISTRIBUTION.md) for the full channel matrix and [isolated network install notes](USER_GUIDE.md#isolated-networks-and-proxies). +### Validate + +Run `cdidx validate [--db ] [--json] [--verbose] [--kind ] [--path ]` +to report indexed file issues such as replacement characters (`U+FFFD`), BOMs, +NUL bytes, mixed line endings, UTF-16 BOMs, and likely non-UTF8 content. +Validation findings are reported in the output and do not by themselves make +the command fail; the command exits non-zero when the DB cannot be read or the +command arguments are invalid. Use `--json` for machine-readable issue rows. + ### Shell Completion Generate completion scripts with `cdidx --completions `. The generated scripts complete subcommands, flags, and common flag values: `--lang` suggests supported languages, `--kind` suggests symbol/reference kinds, and path-like options such as `--db`, `--path`, and `--output` use shell -file completion. +file completion. Each generated script includes the `cdidx` version that +produced it; regenerate installed completion scripts after upgrading or +downgrading `cdidx`. ## Highlights @@ -271,6 +292,7 @@ cdidx status --check --json cdidx search "handleRequest" cdidx definition UserService cdidx search "Handle" --project MyApp +cdidx validate cdidx mcp ``` @@ -298,12 +320,31 @@ cdidx mcp ターミナル、スクリプト、CI、AI ツールから同じリポジトリを繰り返し検索する 場合は `cdidx` が向いています。1回限りのテキスト検索には `rg` が向いています。 +help の探し方: + +| 目的 | コマンド | +|---|---| +| 短いコマンド概要 | `cdidx --help` | +| 全コマンド、flag、例の完全版 | `cdidx --help-all` または `cdidx --help-extended` | +| 共有 flag だけの一覧 | `cdidx --help-flags` | +| 1 コマンドの usage 行 | `cdidx --help` | + +### Validate + +`cdidx validate [--db ] [--json] [--verbose] [--kind ] [--path ]` +は、index 済みファイルの replacement character (`U+FFFD`)、BOM、NUL byte、 +混在改行、UTF-16 BOM、非 UTF-8 らしい内容などを報告します。validation finding は +出力で報告され、それ自体では command failure になりません。DB を読めない場合や +引数が不正な場合は non-zero で終了します。機械処理には `--json` を使えます。 + ### シェル補完 `cdidx --completions ` で補完スクリプトを生成できます。 生成されたスクリプトは subcommand、flag、よく使う flag 値を補完します。 `--lang` は対応言語、`--kind` は symbol / reference kind を提示し、`--db`、 `--path`、`--output` など path 系 option は shell の file completion を使います。 +生成された script には生成元の `cdidx` version が含まれるため、`cdidx` の +upgrade / downgrade 後はインストール済み補完 script を再生成してください。 ## 特長 diff --git a/changelog.d/unreleased/1422.fixed.md b/changelog.d/unreleased/1422.fixed.md new file mode 100644 index 0000000000..98e421fd1c --- /dev/null +++ b/changelog.d/unreleased/1422.fixed.md @@ -0,0 +1,16 @@ +--- +category: fixed +issues: + - 1422 +affected: + - src/CodeIndex/Cli/ConsoleUi.cs + - tests/CodeIndex.Tests/ConsoleUiTests.cs +--- + +## English + +- **Per-command usage lines now show `--top` beside `--limit` (#1422)** — query commands that accept the result cap alias now document `[--limit |--top ]`, matching the parser and shell-completion schema. + +## 日本語 + +- **各 command の usage 行で `--limit` と並べて `--top` を表示するようになりました (#1422)** — result cap alias を受け付ける query command が `[--limit |--top ]` を記載し、parser と shell-completion schema と一致します。 diff --git a/changelog.d/unreleased/1694.docs.md b/changelog.d/unreleased/1694.docs.md new file mode 100644 index 0000000000..b8b6bac2d4 --- /dev/null +++ b/changelog.d/unreleased/1694.docs.md @@ -0,0 +1,17 @@ +--- +category: docs +issues: + - 1694 +affected: + - README.md + - DEVELOPER_GUIDE.md + - src/CodeIndex/Cli/ConsoleUi.cs +--- + +## English + +- **Documented `cdidx validate` in user-facing help and guides (#1694)** — `validate` now appears in the top-level command summaries, README, and developer guidance so indexed-content validation is discoverable without reading source. + +## 日本語 + +- **`cdidx validate` をユーザー向け help と guide に記載しました (#1694)** — `validate` が top-level command summary、README、developer guidance に表示されるようになり、source を読まなくても indexed content validation を見つけられます。 diff --git a/changelog.d/unreleased/1702.changed.md b/changelog.d/unreleased/1702.changed.md new file mode 100644 index 0000000000..0e1dc1cde5 --- /dev/null +++ b/changelog.d/unreleased/1702.changed.md @@ -0,0 +1,20 @@ +--- +category: changed +issues: + - 1702 +affected: + - src/CodeIndex/Cli/ConsoleUi.cs + - src/CodeIndex/Cli/ProgramRunner.cs + - tests/CodeIndex.Tests/ConsoleUiTests.cs + - tests/CodeIndex.Tests/ProgramCliTests.cs + - README.md + - DEVELOPER_GUIDE.md +--- + +## English + +- **Top-level help is now brief by default (#1702)** — `cdidx --help` prints a concise overview, while `--help-all` / `--help-extended` retain the full reference and `--help-flags` prints only shared flag tables. + +## 日本語 + +- **top-level help の既定表示を短くしました (#1702)** — `cdidx --help` は簡潔な概要を表示し、完全版は `--help-all` / `--help-extended`、共有 flag 一覧は `--help-flags` で確認できます。 diff --git a/changelog.d/unreleased/2013.fixed.md b/changelog.d/unreleased/2013.fixed.md new file mode 100644 index 0000000000..64bef01470 --- /dev/null +++ b/changelog.d/unreleased/2013.fixed.md @@ -0,0 +1,18 @@ +--- +category: fixed +issues: + - 2013 +affected: + - src/CodeIndex/Cli/ConsoleUi.cs + - tests/CodeIndex.Tests/ConsoleUiTests.cs + - README.md + - DEVELOPER_GUIDE.md +--- + +## English + +- **Generated completion scripts now carry the generating `cdidx` version (#2013)** — bash, zsh, fish, and PowerShell completion output includes a version marker and regeneration guidance so stale completions can be audited after upgrades. + +## 日本語 + +- **生成された補完 script に生成元の `cdidx` version を含めるようになりました (#2013)** — bash、zsh、fish、PowerShell の completion output に version marker と再生成案内を含め、upgrade 後に古い補完を確認できます。 diff --git a/src/CodeIndex/Cli/ConsoleUi.cs b/src/CodeIndex/Cli/ConsoleUi.cs index d12896ca83..d9b935293e 100644 --- a/src/CodeIndex/Cli/ConsoleUi.cs +++ b/src/CodeIndex/Cli/ConsoleUi.cs @@ -68,18 +68,18 @@ private static readonly (string Command, string Usage)[] CommandUsageLines = ("index-commits", "cdidx index --commits [id ...] [--db ] [--verbose] [--dry-run] [--json] [--duration-format ] [--max-file-bytes ] [--include-symbol-kind [,]] [--exclude-symbol-kind [,]]"), ("index-changed-between", "cdidx index --changed-between [--db ] [--verbose] [--dry-run] [--json] [--duration-format ] [--max-file-bytes ] [--include-symbol-kind [,]] [--exclude-symbol-kind [,]]"), ("index-files", "cdidx index --files [path ...] [--db ] [--verbose] [--dry-run] [--json] [--duration-format ] [--max-file-bytes ] [--include-symbol-kind [,]] [--exclude-symbol-kind [,]]"), - ("search", "cdidx search |--query |-- [--db ] [--json[=ndjson|array]] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--snippet-lines ] [--snippet-focus ] [--max-line-width ] [--fts] [--exact|--exact-substring] [--prefix] [--count] [--since ] [--no-dedup] [--no-visibility-rank]"), - ("definition", "cdidx definition |--query |-- [--db ] [--json] [--verbose] [--limit ] [--lang ] [--kind ] [--visibility ] [--exclude-visibility ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--exact|--exact-name] [--count] [--since ]"), - ("goto", "cdidx goto |--query |-- [--db ] [--json] [--limit ] [--lang ] [--kind ] [--path ] [--exclude-path ] [--exclude-tests] [--exact|--exact-name] [--all]"), - ("references", "cdidx references |--query |-- [--db ] [--json] [--verbose] [--limit ] [--lang ] [--kind ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--snippet-lines ] [--max-line-width ] [--exact|--exact-name] [--count]"), - ("callers", "cdidx callers |--query |-- [--db ] [--json] [--verbose] [--limit ] [--lang ] [--kind ] [--rank-by ] [--raw-kinds] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--snippet-lines ] [--max-line-width ] [--exact|--exact-name] [--count]"), - ("callees", "cdidx callees |--query |-- [--db ] [--json] [--verbose] [--limit ] [--lang ] [--kind ] [--rank-by ] [--raw-kinds] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--snippet-lines ] [--max-line-width ] [--exact|--exact-name] [--count]"), - ("symbols", "cdidx symbols [query|--query |-- ] [--name ] [--db ] [--json] [--verbose] [--limit ] [--lang ] [--kind ] [--visibility ] [--exclude-visibility ] [--path ] [--exclude-path ] [--exclude-tests] [--exact|--exact-name] [--count] [--since ]"), - ("files", "cdidx files [query|--query |-- ] [--db ] [--json] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count] [--since ] [--bytes]"), - ("find", "cdidx find --path [--db ] [--json] [--verbose] [--limit ] [--lang ] [--exclude-path ] [--exclude-tests] [--before ] [--after ] [--max-line-width ] [--exact] [--count]"), + ("search", "cdidx search |--query |-- [--db ] [--json[=ndjson|array]] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--snippet-lines ] [--snippet-focus ] [--max-line-width ] [--fts] [--exact|--exact-substring] [--prefix] [--count] [--since ] [--no-dedup] [--no-visibility-rank]"), + ("definition", "cdidx definition |--query |-- [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--kind ] [--visibility ] [--exclude-visibility ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--exact|--exact-name] [--count] [--since ]"), + ("goto", "cdidx goto |--query |-- [--db ] [--json] [--limit |--top ] [--lang ] [--kind ] [--path ] [--exclude-path ] [--exclude-tests] [--exact|--exact-name] [--all]"), + ("references", "cdidx references |--query |-- [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--kind ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--snippet-lines ] [--max-line-width ] [--exact|--exact-name] [--count]"), + ("callers", "cdidx callers |--query |-- [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--kind ] [--rank-by ] [--raw-kinds] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--snippet-lines ] [--max-line-width ] [--exact|--exact-name] [--count]"), + ("callees", "cdidx callees |--query |-- [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--kind ] [--rank-by ] [--raw-kinds] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--snippet-lines ] [--max-line-width ] [--exact|--exact-name] [--count]"), + ("symbols", "cdidx symbols [query|--query |-- ] [--name ] [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--kind ] [--visibility ] [--exclude-visibility ] [--path ] [--exclude-path ] [--exclude-tests] [--exact|--exact-name] [--count] [--since ]"), + ("files", "cdidx files [query|--query |-- ] [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count] [--since ] [--bytes]"), + ("find", "cdidx find --path [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--exclude-path ] [--exclude-tests] [--before ] [--after ] [--max-line-width ] [--exact] [--count]"), ("excerpt", "cdidx excerpt --start [--end ] [--before ] [--after ] [--max-line-width ] [--focus-line ] [--focus-column ] [--focus-length ] [--db ] [--json] [--verbose]"), - ("map", "cdidx map [--db ] [--json] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--bytes] [--min-entrypoint-confidence <0.0..1.0>]"), - ("inspect", "cdidx inspect |--query |-- [--db ] [--json] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--max-line-width ] [--exact|--exact-name]"), + ("map", "cdidx map [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--bytes] [--min-entrypoint-confidence <0.0..1.0>]"), + ("inspect", "cdidx inspect |--query |-- [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--max-line-width ] [--exact|--exact-name]"), ("outline", "cdidx outline [--db ] [--json] [--verbose]"), ("status", "cdidx status [--db ] [--json] [--verbose] [--check[=workspace,fold,graph,issues,hotspot,csharp,sql,newer]] [--stale-after ] [--explain ] [--log-path] [--config] [--check-updates]"), ("validate-config", "cdidx validate-config"), @@ -87,10 +87,10 @@ private static readonly (string Command, string Usage)[] CommandUsageLines = ("diff", "cdidx diff [--json] [--summary-only] [--detailed] [--limit ]"), ("report", "cdidx report --output [--db ] [--json] [--log-lines ] [--no-log] [--include-args]"), ("validate", "cdidx validate [--db ] [--json] [--verbose] [--kind ] [--path ]"), - ("impact", "cdidx impact |--query |-- [--db ] [--json] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--snippet-lines ] [--max-line-width ] [--max-hops ] [--count] [--with-paths]"), - ("deps", "cdidx deps [--db ] [--json] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--reverse]"), - ("unused", "cdidx unused [--db ] [--json] [--verbose] [--limit ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count]"), - ("hotspots", "cdidx hotspots [--db ] [--json] [--verbose] [--limit ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count] [--group-by ] [--group-by-name]"), + ("impact", "cdidx impact |--query |-- [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--snippet-lines ] [--max-line-width ] [--max-hops ] [--count] [--with-paths]"), + ("deps", "cdidx deps [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--reverse]"), + ("unused", "cdidx unused [--db ] [--json] [--verbose] [--limit |--top ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count]"), + ("hotspots", "cdidx hotspots [--db ] [--json] [--verbose] [--limit |--top ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count] [--group-by ] [--group-by-name]"), ("suggestions", "cdidx suggestions [id] [--db ] [--json] [--status ] [--language ] [--category ] [--since ] [--agent ] [--format ]"), ("languages", "cdidx languages [--json]"), ("batch", "cdidx batch [--db ] # reads JSON string arrays from stdin, one query command per line"), @@ -620,6 +620,50 @@ private static string ReadAssemblyMetadata(Assembly assembly, string key) /// 使い方を表示する。 /// public static void PrintUsage(bool showBanner = true) + => PrintUsageBrief(showBanner); + + public static void PrintUsageBrief(bool showBanner = true) + { + if (showBanner) + { + PrintBanner(); + } + + Console.WriteLine("Usage:"); + Console.WriteLine(" cdidx "); + Console.WriteLine(" cdidx [options]"); + Console.WriteLine(" cdidx --help-all"); + Console.WriteLine(" cdidx --help-flags"); + Console.WriteLine(); + Console.WriteLine("Commands:"); + Console.WriteLine(" index Build or update the index for a project"); + Console.WriteLine(" search Full-text search across indexed chunks"); + Console.WriteLine(" definition Resolve symbol definitions with extracted ranges"); + Console.WriteLine(" references Find indexed references for a symbol (--kind uses reference kind)"); + Console.WriteLine(" callers Find callers of a symbol (--kind uses reference kind)"); + Console.WriteLine(" callees Find callees used by a caller (--kind uses reference kind)"); + Console.WriteLine(" symbols [query] Search symbols (functions, classes, imports)"); + Console.WriteLine(" map Show a repo-level overview for AI orientation"); + Console.WriteLine(" inspect Bundle definition, graph, and nearby symbol context"); + Console.WriteLine(" status Show database statistics, freshness, config, and logs"); + Console.WriteLine(" validate Report encoding issues (U+FFFD, BOM, null bytes, mixed line endings, UTF-16 BOM, likely non-UTF8)"); + Console.WriteLine(" impact Show transitive callers; type queries may return heuristic file-level dependency hints"); + Console.WriteLine(" deps Show file-level dependency edges from the reference graph"); + Console.WriteLine(" unused Find symbols defined but never referenced (dead code)"); + Console.WriteLine(" hotspots Find high-impact symbols; duplicate-name families may fall back conservatively"); + Console.WriteLine(" batch Run newline-delimited JSON query commands with one DB connection"); + Console.WriteLine(" mcp Start MCP server (for AI tools: Claude, Cursor, etc.)"); + Console.WriteLine(" completions Generate shell completions for bash, zsh, fish, or PowerShell"); + Console.WriteLine(); + Console.WriteLine("Run `cdidx --help-all` for every command and option, `cdidx --help-flags` for shared flags, or `cdidx --help` for one command."); + Console.WriteLine(); + Console.WriteLine("Examples:"); + Console.WriteLine(" cdidx ./myproject"); + Console.WriteLine(" cdidx search \"authenticate\""); + Console.WriteLine(" cdidx inspect Run --body --exclude-tests"); + } + + public static void PrintUsageFull(bool showBanner = true) { if (showBanner) { @@ -644,6 +688,43 @@ void WriteHelpLine(string line = "") foreach (var (_, usage) in CommandUsageLines) WriteHelpLine($" {usage}"); Console.WriteLine(); + PrintCommandSummary(); + Console.WriteLine(); + PrintFlagReference(WriteHelpLine); + Console.WriteLine(); + PrintExamples(); + } + + public static void PrintFlagUsage(bool showBanner = true) + { + if (showBanner) + { + PrintBanner(); + } + + var helpWidth = ShouldUseInteractiveConsole() ? Math.Min(GetWindowWidth(), 120) : 0; + void WriteHelpLine(string line = "") + { + if (helpWidth <= 0) + { + Console.WriteLine(line); + return; + } + + foreach (var wrapped in WrapHelpLine(line, helpWidth)) + Console.WriteLine(wrapped); + } + + Console.WriteLine("Usage:"); + Console.WriteLine(" cdidx --help-flags"); + Console.WriteLine(); + PrintFlagReference(WriteHelpLine); + Console.WriteLine(); + Console.WriteLine("Run `cdidx --help-all` to show commands and examples."); + } + + private static void PrintCommandSummary() + { Console.WriteLine("Commands:"); Console.WriteLine(" index Build or update the index for a project"); Console.WriteLine(" backfill-fold Upgrade folded-name columns in an existing index DB"); @@ -679,6 +760,10 @@ void WriteHelpLine(string line = "") Console.WriteLine(" mcp Start MCP server (for AI tools: Claude, Cursor, etc.)"); Console.WriteLine(" completions Generate shell completions for bash, zsh, fish, or PowerShell"); Console.WriteLine(" license Show licensing, trademark, and commercial-use summary"); + } + + private static void PrintFlagReference(Action WriteHelpLine) + { Console.WriteLine(); Console.WriteLine("Index and update options:"); Console.WriteLine(" --db Database file path (default for index: /.cdidx/codeindex.db)"); @@ -763,7 +848,10 @@ void WriteHelpLine(string line = "") WriteHelpLine(" --with-paths impact: also emit `paths` per caller — the shortest call chains [root, ..., caller] (diamond graphs surface every converging route, capped per row)"); WriteHelpLine(" unused reflection note C# nameof/typeof and direct reflection member-name literals such as GetMethod(\"Foo\") are indexed; dynamically constructed reflection names may need manual review"); WriteHelpLine(" Note: if a query itself starts with '-', pass it with --query or -- ; for option values that start with '--', use --opt=."); - Console.WriteLine(); + } + + private static void PrintExamples() + { Console.WriteLine("Examples:"); Console.WriteLine(" cdidx ./myproject Index a project"); Console.WriteLine(" cdidx backfill-fold Upgrade folded-name columns in an existing DB"); @@ -1124,7 +1212,10 @@ private static string GetBashCompletions() var cmds = string.Join(" ", Commands); var langs = GetCompletionLangs(); var kinds = GetCompletionKinds(); + var version = LoadVersion(); var sb = new StringBuilder(); + sb.Append($"# cdidx bash completions generated for version {version}\n"); + sb.Append("# Regenerate this script after upgrading cdidx.\n"); sb.Append("_cdidx() {\n"); sb.Append(" local cur prev commands\n"); sb.Append(" local cmd\n"); @@ -1213,8 +1304,11 @@ private static string GetZshCompletions() var cmds = string.Join(" ", Commands.Select(c => $"'{c}:{c} command'")); var langs = GetCompletionLangs(); var kinds = GetCompletionKinds(); + var version = LoadVersion(); var sb = new StringBuilder(); sb.Append("#compdef cdidx\n"); + sb.Append($"# cdidx zsh completions generated for version {version}\n"); + sb.Append("# Regenerate this script after upgrading cdidx.\n"); sb.Append("_cdidx() {\n"); sb.Append(" local -a commands\n"); sb.Append(" commands=(\n"); @@ -1335,7 +1429,8 @@ private static string GetFishCompletions() var kinds = GetCompletionKinds(); var lines = new List { - "# cdidx fish completions", + $"# cdidx fish completions generated for version {LoadVersion()}", + "# Regenerate this script after upgrading cdidx.", }; foreach (var cmd in Commands) lines.Add($"complete -c cdidx -n '__fish_use_subcommand' -a '{cmd}' -d '{cmd} command'"); @@ -1384,7 +1479,8 @@ private static string GetPowerShellCompletions() var langs = FormatPowerShellArray(GetCompletionLangs().Split(' ', StringSplitOptions.RemoveEmptyEntries)); var kinds = FormatPowerShellArray(GetCompletionKinds().Split(' ', StringSplitOptions.RemoveEmptyEntries)); var sb = new StringBuilder(); - sb.AppendLine("# cdidx PowerShell completions"); + sb.AppendLine($"# cdidx PowerShell completions generated for version {LoadVersion()}"); + sb.AppendLine("# Regenerate this script after upgrading cdidx."); sb.AppendLine("Register-ArgumentCompleter -Native -CommandName cdidx -ScriptBlock {"); sb.AppendLine(" param($wordToComplete, $commandAst, $cursorPosition)"); sb.AppendLine($" $commands = @({cmds})"); diff --git a/src/CodeIndex/Cli/IndexCommandRunner.cs b/src/CodeIndex/Cli/IndexCommandRunner.cs index 5d29c8fc31..216e8af84d 100644 --- a/src/CodeIndex/Cli/IndexCommandRunner.cs +++ b/src/CodeIndex/Cli/IndexCommandRunner.cs @@ -55,7 +55,7 @@ internal static int Run(string[] indexArgs, JsonSerializerOptions jsonOptions, C if (options.ShowHelp) { - ConsoleUi.PrintUsage(); + ConsoleUi.PrintUsageFull(); return CommandExitCodes.Success; } diff --git a/src/CodeIndex/Cli/ProgramRunner.cs b/src/CodeIndex/Cli/ProgramRunner.cs index f7dcba0acb..5523efde4f 100644 --- a/src/CodeIndex/Cli/ProgramRunner.cs +++ b/src/CodeIndex/Cli/ProgramRunner.cs @@ -93,13 +93,29 @@ internal static int Run( if (args.Length == 0 || args[0] is "--help" or "-h") { - ConsoleUi.PrintUsage(showBanner: args.Length > 0); + ConsoleUi.PrintUsageBrief(showBanner: args.Length > 0); var helpExit = args.Length == 0 ? CommandExitCodes.UsageError : CommandExitCodes.Success; GlobalToolLog.Info($"command_complete exit_code={helpExit} help_or_usage=true"); EmitCommandMetric("help", args, commandStartTimestamp, commandStopwatch, helpExit); return helpExit; } + if (args[0] is "--help-all" or "--help-extended") + { + ConsoleUi.PrintUsageFull(showBanner: true); + GlobalToolLog.Info($"command_complete exit_code={CommandExitCodes.Success} help_all=true"); + EmitCommandMetric("help-all", args, commandStartTimestamp, commandStopwatch, CommandExitCodes.Success); + return CommandExitCodes.Success; + } + + if (args[0] == "--help-flags") + { + ConsoleUi.PrintFlagUsage(showBanner: true); + GlobalToolLog.Info($"command_complete exit_code={CommandExitCodes.Success} help_flags=true"); + EmitCommandMetric("help-flags", args, commandStartTimestamp, commandStopwatch, CommandExitCodes.Success); + return CommandExitCodes.Success; + } + if (args[0] is "--version" or "-V") { var versionExitCode = RunVersion(args[1..], jsonOptions, appVersion); diff --git a/src/CodeIndex/Cli/QueryCommandRunner.cs b/src/CodeIndex/Cli/QueryCommandRunner.cs index a86e79975a..64064641ad 100644 --- a/src/CodeIndex/Cli/QueryCommandRunner.cs +++ b/src/CodeIndex/Cli/QueryCommandRunner.cs @@ -187,7 +187,7 @@ private sealed record StatusReadinessField( private const string OutputFormatSarif = "sarif"; private static readonly HashSet InlineValueOptions = new(ValueTakingOptions.Concat(["--json"]), StringComparer.Ordinal); - private const string FindUsage = "Usage: cdidx find --path [--db ] [--json] [--verbose] [--limit ] [--lang ] [--exclude-path ] [--exclude-tests] [--before ] [--after ] [--max-line-width ] [--exact] [--count]\n cdidx find --query --path [...]\n cdidx find [options] -- "; + private const string FindUsage = "Usage: cdidx find --path [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--exclude-path ] [--exclude-tests] [--before ] [--after ] [--max-line-width ] [--exact] [--count]\n cdidx find --query --path [...]\n cdidx find [options] -- "; public static int RunBatch(string[] cmdArgs, JsonSerializerOptions jsonOptions) { diff --git a/tests/CodeIndex.Tests/ConsoleUiTests.cs b/tests/CodeIndex.Tests/ConsoleUiTests.cs index 04b4f79f9c..047c04ba8c 100644 --- a/tests/CodeIndex.Tests/ConsoleUiTests.cs +++ b/tests/CodeIndex.Tests/ConsoleUiTests.cs @@ -43,7 +43,7 @@ public void StartSpinner_BackgroundLoop_DoesNotBlockOnTaskWait() [Fact] public void PrintUsage_WithBanner_IncludesAsciiArt() { - var output = CaptureUsageOutput(); + var output = CaptureFullUsageOutput(); Assert.Contains("██████╗", output); Assert.Contains("Usage:", output); @@ -53,7 +53,7 @@ public void PrintUsage_WithBanner_IncludesAsciiArt() [Fact] public void PrintUsage_WithoutBanner_HidesAsciiArtAndEasterEggFlags() { - var output = CaptureUsageOutput(showBanner: false); + var output = CaptureFullUsageOutput(showBanner: false); Assert.DoesNotContain("██████╗", output); Assert.Contains("Usage:", output); @@ -69,10 +69,10 @@ public void PrintUsage_WithoutBanner_HidesAsciiArtAndEasterEggFlags() Assert.Contains("cdidx references |--query |-- ", output); Assert.Contains("cdidx callers |--query |-- ", output); Assert.Contains("cdidx callees |--query |-- ", output); - Assert.Contains("cdidx search |--query |-- [--db ] [--json[=ndjson|array]] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--snippet-lines ] [--snippet-focus ] [--max-line-width ] [--fts] [--exact|--exact-substring] [--prefix] [--count] [--since ] [--no-dedup] [--no-visibility-rank]", output); - Assert.Contains("cdidx definition |--query |-- [--db ] [--json] [--verbose] [--limit ] [--lang ] [--kind ] [--visibility ] [--exclude-visibility ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--exact|--exact-name] [--count] [--since ]", output); - Assert.Contains("cdidx references |--query |-- [--db ] [--json] [--verbose] [--limit ] [--lang ] [--kind ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--snippet-lines ] [--max-line-width ] [--exact|--exact-name] [--count]", output); - Assert.Contains("cdidx inspect |--query |-- [--db ] [--json] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--max-line-width ] [--exact|--exact-name]", output); + Assert.Contains("cdidx search |--query |-- [--db ] [--json[=ndjson|array]] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--snippet-lines ] [--snippet-focus ] [--max-line-width ] [--fts] [--exact|--exact-substring] [--prefix] [--count] [--since ] [--no-dedup] [--no-visibility-rank]", output); + Assert.Contains("cdidx definition |--query |-- [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--kind ] [--visibility ] [--exclude-visibility ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--exact|--exact-name] [--count] [--since ]", output); + Assert.Contains("cdidx references |--query |-- [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--kind ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--snippet-lines ] [--max-line-width ] [--exact|--exact-name] [--count]", output); + Assert.Contains("cdidx inspect |--query |-- [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--body] [--max-line-width ] [--exact|--exact-name]", output); Assert.Contains("--snippet-lines Search snippet length (1-20, default: 8)", output); Assert.Contains("--snippet-focus search only: long-line focus mode (leftmost|quality|proximity, default: quality)", output); Assert.Contains("--max-line-width search/references/callers/callees/find/excerpt/impact/inspect only: clamp very long single-line snippet/context/excerpt payloads (`0` disables clamping; default: 512)", output); @@ -101,14 +101,14 @@ public void PrintUsage_WithoutBanner_HidesAsciiArtAndEasterEggFlags() Assert.Contains("cdidx excerpt --start [--end ] [--before ] [--after ] [--max-line-width ] [--focus-line ] [--focus-column ] [--focus-length ] [--db ] [--json] [--verbose]", output); Assert.Contains("--focus-column excerpt: column to keep centered when clamping (must be within the focused line)", output); Assert.Contains("--focus-line excerpt: line whose focused column should stay visible", output); - Assert.Contains("cdidx map [--db ] [--json] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--bytes]", output); - Assert.Contains("cdidx symbols [query|--query |-- ] [--name ] [--db ] [--json] [--verbose] [--limit ] [--lang ] [--kind ] [--visibility ] [--exclude-visibility ] [--path ] [--exclude-path ] [--exclude-tests] [--exact|--exact-name] [--count] [--since ]", output); - Assert.Contains("cdidx files [query|--query |-- ] [--db ] [--json] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count] [--since ] [--bytes]", output); + Assert.Contains("cdidx map [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--bytes]", output); + Assert.Contains("cdidx symbols [query|--query |-- ] [--name ] [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--kind ] [--visibility ] [--exclude-visibility ] [--path ] [--exclude-path ] [--exclude-tests] [--exact|--exact-name] [--count] [--since ]", output); + Assert.Contains("cdidx files [query|--query |-- ] [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count] [--since ] [--bytes]", output); Assert.Contains("cdidx validate [--db ] [--json] [--verbose] [--kind ] [--path ]", output); Assert.Contains("Note: if a query itself starts with '-', pass it with --query or -- ", output); Assert.DoesNotContain("cdidx validate [--db ] [--json] [--limit ] [--lang ]", output); - Assert.Contains("cdidx unused [--db ] [--json] [--verbose] [--limit ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count]", output); - Assert.Contains("cdidx hotspots [--db ] [--json] [--verbose] [--limit ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count] [--group-by ] [--group-by-name]", output); + Assert.Contains("cdidx unused [--db ] [--json] [--verbose] [--limit |--top ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count]", output); + Assert.Contains("cdidx hotspots [--db ] [--json] [--verbose] [--limit |--top ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count] [--group-by ] [--group-by-name]", output); Assert.Contains("--json Output as JSON (search streams ndjson by default; use search --json=array for one array)", output); Assert.Contains("--lang Filter by language (aliases: bat, cmd, cshtml, razor, ts, tsx, cts, mts)", output); Assert.Contains("--bytes Show raw byte counts in human output for files/map instead of binary units; JSON always keeps raw integer bytes", output); @@ -143,7 +143,7 @@ public void PrintUsage_WithoutBanner_HidesAsciiArtAndEasterEggFlags() [Fact] public void PrintUsage_ExactMatchOptionLines_FitWithinEightyColumns() { - var output = CaptureUsageOutput(showBanner: false); + var output = CaptureFullUsageOutput(showBanner: false); var lines = output.Split(Environment.NewLine); var exactStart = Array.FindIndex(lines, line => line.StartsWith(" --exact ", StringComparison.Ordinal)); var exactSubstringStart = Array.FindIndex(lines, line => line.StartsWith(" --exact-substring", StringComparison.Ordinal)); @@ -166,7 +166,7 @@ public void PrintUsage_ExactMatchOptionLines_FitWithinEightyColumns() [Fact] public void PrintUsage_ShowsCommitUpdateWorkflowClearly() { - var output = CaptureUsageOutput(showBanner: false); + var output = CaptureFullUsageOutput(showBanner: false); Assert.Contains("Update workflows:", output); Assert.Contains("Use --commits with a project path after normal commits", output); @@ -191,7 +191,7 @@ public void WrapHelpLine_LongOptionDescription_PreservesOptionColumn() [Fact] public void WrapHelpLine_LongUsageLine_UsesDetectedWidth() { - const string line = " cdidx search |--query |-- [--db ] [--json[=ndjson|array]] [--verbose] [--limit ]"; + const string line = " cdidx search |--query |-- [--db ] [--json[=ndjson|array]] [--verbose] [--limit |--top ]"; var lines = ConsoleUi.WrapHelpLine(line, maxWidth: 72); @@ -216,13 +216,13 @@ public void WrapHelpLine_VeryNarrowWidth_DoesNotEmitEmptyOrOverwideLines() [Fact] public void PrintUsage_QueryLinesMatchImplementedOptions() { - var output = CaptureUsageOutput(showBanner: false); + var output = CaptureFullUsageOutput(showBanner: false); - Assert.Contains("cdidx search |--query |-- [--db ] [--json[=ndjson|array]] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--snippet-lines ] [--snippet-focus ] [--max-line-width ] [--fts] [--exact|--exact-substring] [--prefix] [--count] [--since ] [--no-dedup] [--no-visibility-rank]", output); - Assert.Contains("cdidx symbols [query|--query |-- ] [--name ] [--db ] [--json] [--verbose] [--limit ] [--lang ] [--kind ] [--visibility ] [--exclude-visibility ] [--path ] [--exclude-path ] [--exclude-tests] [--exact|--exact-name] [--count] [--since ]", output); - Assert.Contains("cdidx files [query|--query |-- ] [--db ] [--json] [--verbose] [--limit ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count] [--since ] [--bytes]", output); - Assert.Contains("cdidx hotspots [--db ] [--json] [--verbose] [--limit ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count]", output); - Assert.Contains("cdidx unused [--db ] [--json] [--verbose] [--limit ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count]", output); + Assert.Contains("cdidx search |--query |-- [--db ] [--json[=ndjson|array]] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--snippet-lines ] [--snippet-focus ] [--max-line-width ] [--fts] [--exact|--exact-substring] [--prefix] [--count] [--since ] [--no-dedup] [--no-visibility-rank]", output); + Assert.Contains("cdidx symbols [query|--query |-- ] [--name ] [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--kind ] [--visibility ] [--exclude-visibility ] [--path ] [--exclude-path ] [--exclude-tests] [--exact|--exact-name] [--count] [--since ]", output); + Assert.Contains("cdidx files [query|--query |-- ] [--db ] [--json] [--verbose] [--limit |--top ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count] [--since ] [--bytes]", output); + Assert.Contains("cdidx hotspots [--db ] [--json] [--verbose] [--limit |--top ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count]", output); + Assert.Contains("cdidx unused [--db ] [--json] [--verbose] [--limit |--top ] [--kind ] [--visibility ] [--exclude-visibility ] [--lang ] [--path ] [--exclude-path ] [--exclude-tests] [--count]", output); Assert.Contains("cdidx license", output); Assert.Contains("cdidx completions ", output); Assert.Contains("cdidx --completions ", output); @@ -620,6 +620,8 @@ public void PrintCompletions_PowerShellRegistersNativeCompleter() { var output = ConsoleUi.GetCompletionScript("powershell"); + Assert.Contains("generated for version", output); + Assert.Contains("Regenerate this script after upgrading cdidx.", output); Assert.Contains("Register-ArgumentCompleter -Native -CommandName cdidx", output); Assert.Contains("$commands = @('index', 'backfill-fold'", output); Assert.Contains("'--help', '--version', '--license'", output); @@ -633,6 +635,19 @@ public void PrintCompletions_PowerShellRegistersNativeCompleter() Assert.Contains("$tokens.Count -le 2 -and -not ([string]::IsNullOrEmpty($wordToComplete)) -and -not $afterLastToken", output); } + [Theory] + [InlineData("bash", "# cdidx bash completions generated for version")] + [InlineData("zsh", "# cdidx zsh completions generated for version")] + [InlineData("fish", "# cdidx fish completions generated for version")] + [InlineData("powershell", "# cdidx PowerShell completions generated for version")] + public void PrintCompletions_EmbedsVersionMarker(string shell, string marker) + { + var output = ConsoleUi.GetCompletionScript(shell); + + Assert.Contains(marker, output); + Assert.Contains("Regenerate this script after upgrading cdidx.", output); + } + [Fact] public void PrintCompletions_PowerShellIncludesSharedFlagValues() { @@ -865,13 +880,42 @@ public void PrintUsage_ShowsWorkingFindDashedLiteralExample() { using var capture = ConsoleCapture.Start(captureOut: true); - ConsoleUi.PrintUsage(); + ConsoleUi.PrintUsageFull(); var output = capture.Out!.ToString(); Assert.Contains("cdidx find --path README.md -- --path", output); Assert.DoesNotContain("cdidx find -- --path --path README.md", output); } + [Fact] + public void PrintUsageBrief_IsConciseAndPointsToExtendedHelp() + { + var output = CaptureBriefUsageOutput(showBanner: false); + var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + + Assert.True(lines.Length < 80, $"Brief help should stay under 80 non-empty lines but had {lines.Length}."); + Assert.Contains("cdidx --help-all", output); + Assert.Contains("cdidx --help-flags", output); + Assert.Contains("validate Report encoding issues", output); + Assert.DoesNotContain("Index and update options:", output); + Assert.DoesNotContain("cdidx index --commits ", output); + } + + [Fact] + public void PrintFlagUsage_ShowsFlagsWithoutCommands() + { + using var capture = ConsoleCapture.Start(captureOut: true); + + ConsoleUi.PrintFlagUsage(showBanner: false); + var output = capture.Out!.ToString(); + + Assert.Contains("Index and update options:", output); + Assert.Contains("Query options:", output); + Assert.Contains("--limit , --top ", output); + Assert.DoesNotContain("Commands:", output); + Assert.DoesNotContain("Examples:", output); + } + [Fact] public void PrintCompletions_UnknownShell_ReturnsFalse() { @@ -1378,7 +1422,7 @@ public void ColorizeKind_ColorModeNever_OmitsAnsi() [Fact] public void PrintUsage_DocumentsColorFlag() { - var output = CaptureUsageOutput(showBanner: false); + var output = CaptureFullUsageOutput(showBanner: false); Assert.Contains("--color ", output); Assert.Contains("`auto`", output); Assert.Contains("`always`", output); @@ -1390,7 +1434,7 @@ public void PrintUsage_DocumentsColorFlag() [Fact] public void PrintUsage_DocumentsPaletteFlag() { - var output = CaptureUsageOutput(showBanner: false); + var output = CaptureFullUsageOutput(showBanner: false); Assert.Contains("--palette ", output); Assert.Contains("`basic`", output); Assert.Contains("`256`", output); @@ -1726,10 +1770,17 @@ or OperandType.InlineType _ => throw new NotSupportedException($"Unsupported IL operand type: {opCode.OperandType}"), }; - private static string CaptureUsageOutput(bool showBanner = true) + private static string CaptureFullUsageOutput(bool showBanner = true) + { + using var capture = ConsoleCapture.Start(captureOut: true); + ConsoleUi.PrintUsageFull(showBanner); + return capture.Out!.ToString()!; + } + + private static string CaptureBriefUsageOutput(bool showBanner = true) { using var capture = ConsoleCapture.Start(captureOut: true); - ConsoleUi.PrintUsage(showBanner); + ConsoleUi.PrintUsageBrief(showBanner); return capture.Out!.ToString()!; } diff --git a/tests/CodeIndex.Tests/ProgramCliTests.cs b/tests/CodeIndex.Tests/ProgramCliTests.cs index 64dfa7d058..689b303198 100644 --- a/tests/CodeIndex.Tests/ProgramCliTests.cs +++ b/tests/CodeIndex.Tests/ProgramCliTests.cs @@ -223,6 +223,38 @@ public void SubcommandHelp_PrintsCommandSpecificUsage(string command, string exp Assert.DoesNotContain("██████╗", stdout); } + [Fact] + public void TopLevelHelp_DefaultIsBriefAndExtendedHelpKeepsFullReference() + { + var (briefExit, briefStdout, briefStderr) = RunCliInSubprocess(["--help"]); + var (fullExit, fullStdout, fullStderr) = RunCliInSubprocess(["--help-all"]); + + Assert.Equal(0, briefExit); + Assert.Equal(string.Empty, briefStderr); + Assert.Contains("cdidx --help-all", briefStdout); + Assert.Contains("cdidx --help-flags", briefStdout); + Assert.DoesNotContain("Index and update options:", briefStdout); + + Assert.Equal(0, fullExit); + Assert.Equal(string.Empty, fullStderr); + Assert.Contains("Index and update options:", fullStdout); + Assert.Contains("cdidx index --commits ", fullStdout); + Assert.Contains("--limit , --top ", fullStdout); + } + + [Fact] + public void HelpFlags_PrintsFlagReferenceOnly() + { + var (exitCode, stdout, stderr) = RunCliInSubprocess(["--help-flags"]); + + Assert.Equal(0, exitCode); + Assert.Equal(string.Empty, stderr); + Assert.Contains("Index and update options:", stdout); + Assert.Contains("Query options:", stdout); + Assert.Contains("--limit , --top ", stdout); + Assert.DoesNotContain("Commands:", stdout); + } + [Theory] [InlineData("completions")] [InlineData("completions", "--json")]