diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index b7ec61c36d..7d6caa88bb 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -179,7 +179,7 @@ jobs:
}
- name: Verify formatting
- run: dotnet format CodeIndex.sln --verify-no-changes --no-restore --verbosity minimal
+ run: dotnet format whitespace CodeIndex.sln --verify-no-changes --no-restore --verbosity minimal
- name: Verify developer task wrapper
if: matrix.os == 'ubuntu-latest' && matrix.test-framework == 'net8.0'
diff --git a/Directory.Build.props b/Directory.Build.props
index a2ba2e8677..a462a46534 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -14,7 +14,7 @@
-->
true
true
- $(WarningsNotAsErrors);IL2026;IL2067;IL2072;IL2075
+ $(WarningsNotAsErrors);IL2026;IL2067;IL2072;IL2075;IL3050
false
diff --git a/USER_GUIDE.md b/USER_GUIDE.md
index 081b6fa211..7f69069914 100644
--- a/USER_GUIDE.md
+++ b/USER_GUIDE.md
@@ -1872,6 +1872,8 @@ The MCP `tools/list` response includes an `examples` array for every registered
| `backfill_fold` | Upgrade folded-name keys in an existing DB without reparsing source files |
| `suggest_improvement` | Submit structured improvement suggestions or error reports |
+`suggest_improvement` always stores accepted suggestions locally. Its response includes `submitted_to_github` and `github_submission_reason` so clients can distinguish `submitted`, `token_not_configured`, `repo_not_configured`, `network_error`, and `api_error`; failed GitHub attempts also include `github_submission_error`.
+
For `callers`, `impact_analysis`, and `deps`, the [`reference_kind` filtering matrix](DEVELOPER_GUIDE.md#reference-kind-filtering-matrix) explains which edge kinds each command walks and how to reconcile count differences with `references --kind attribute` or `--kind annotation`.
No CLAUDE.md hacks or SQL templates needed — the AI interacts with cdidx natively.
@@ -3928,6 +3930,8 @@ OpenAI Codex CLI (`codex.json` または `~/.codex/config.json`):
| `backfill_fold` | 既存 DB の folded-name key をソース再解析なしで更新 |
| `suggest_improvement` | 構造化された改善提案またはエラー報告を送信 |
+`suggest_improvement` は受理した提案を常にローカル保存します。応答には `submitted_to_github` と `github_submission_reason` が含まれ、クライアントは `submitted`、`token_not_configured`、`repo_not_configured`、`network_error`、`api_error` を区別できます。GitHub 送信に失敗した場合は `github_submission_error` も含まれます。
+
`callers`、`impact_analysis`、`deps` については、[`reference_kind` フィルタの対応表](DEVELOPER_GUIDE.md#reference-kind-filtering-matrix)で各コマンドが辿る edge kind と、`references --kind attribute` または `--kind annotation` による件数差の照合方法を確認できます。
CLAUDE.mdの設定やSQLテンプレートは不要 — AIがcdidxとネイティブに連携します。
diff --git a/changelog.d/unreleased/1460.fixed.md b/changelog.d/unreleased/1460.fixed.md
new file mode 100644
index 0000000000..fa7580cf97
--- /dev/null
+++ b/changelog.d/unreleased/1460.fixed.md
@@ -0,0 +1,13 @@
+---
+category: fixed
+issues:
+ - 1460
+---
+
+## English
+
+- Added `github_submission_reason` to MCP `suggest_improvement` responses so clients can tell why a local suggestion was not forwarded to GitHub.
+
+## 日本語
+
+- MCP `suggest_improvement` の応答に `github_submission_reason` を追加し、ローカル保存された提案が GitHub に転送されなかった理由をクライアントが判別できるようにしました。
diff --git a/changelog.d/unreleased/1640.changed.md b/changelog.d/unreleased/1640.changed.md
new file mode 100644
index 0000000000..b1da48b12a
--- /dev/null
+++ b/changelog.d/unreleased/1640.changed.md
@@ -0,0 +1,13 @@
+---
+category: changed
+issues:
+ - 1640
+---
+
+## English
+
+- Shortened the MCP `search` tool description advertised during `tools/list`, moving detailed tokenizer and ranking guidance to `USER_GUIDE.md`.
+
+## 日本語
+
+- `tools/list` で公開される MCP `search` ツール説明を短縮し、tokenizer や ranking の詳細案内は `USER_GUIDE.md` に寄せました。
diff --git a/changelog.d/unreleased/2752.fixed.md b/changelog.d/unreleased/2752.fixed.md
new file mode 100644
index 0000000000..5b14b1e06b
--- /dev/null
+++ b/changelog.d/unreleased/2752.fixed.md
@@ -0,0 +1,13 @@
+---
+category: fixed
+issues:
+ - 2752
+---
+
+## English
+
+- Fixed the MCP `index` tool so the advertised `maxFileBytes` argument is accepted instead of rejected as unknown.
+
+## 日本語
+
+- MCP `index` ツールで、公開済みの `maxFileBytes` 引数が unknown argument として拒否されないよう修正しました。
diff --git a/changelog.d/unreleased/2786.internal.md b/changelog.d/unreleased/2786.internal.md
new file mode 100644
index 0000000000..5d36ef84a9
--- /dev/null
+++ b/changelog.d/unreleased/2786.internal.md
@@ -0,0 +1,17 @@
+---
+category: internal
+issues:
+ - 2786
+affected:
+ - Directory.Build.props
+ - .github/workflows/dotnet.yml
+ - dev.sh
+---
+
+## English
+
+- **Native AOT analyzer diagnostics no longer fail regular CI builds (#2786)** — current IL3050 diagnostics remain visible as warnings while format verification focuses on whitespace and follow-up AOT compatibility work is tracked separately.
+
+## 日本語
+
+- **Native AOT analyzer diagnostics で通常 CI build が失敗しなくなりました (#2786)** — 現在の IL3050 diagnostics は warning として表示しつつ、format verification は whitespace に絞り、AOT compatibility 対応は別途追跡します。
diff --git a/dev.sh b/dev.sh
index 43bf32ff6f..b06f69f378 100755
--- a/dev.sh
+++ b/dev.sh
@@ -37,10 +37,10 @@ case "$task" in
--blame-hang-timeout 5m
;;
lint)
- dotnet format CodeIndex.sln --verify-no-changes --verbosity minimal
+ dotnet format whitespace CodeIndex.sln --verify-no-changes --verbosity minimal
;;
format)
- dotnet format CodeIndex.sln --verbosity minimal
+ dotnet format whitespace CodeIndex.sln --verbosity minimal
;;
coverage)
dotnet test tests/CodeIndex.Tests/CodeIndex.Tests.csproj \
diff --git a/src/CodeIndex/Cli/SuggestionStore.cs b/src/CodeIndex/Cli/SuggestionStore.cs
index b686b2216d..c7e5d0b28e 100644
--- a/src/CodeIndex/Cli/SuggestionStore.cs
+++ b/src/CodeIndex/Cli/SuggestionStore.cs
@@ -169,6 +169,7 @@ public record AddAndSubmitResult(
bool AlreadySubmitted,
SuggestionStatus Status,
string? UpstreamUrl,
+ string? SubmissionError = null,
string? DuplicateOfHash = null,
double? DuplicateScore = null);
@@ -275,6 +276,7 @@ record = RedactRecordForPersistence(record);
reservation.AlreadySubmitted,
reservation.Status,
reservation.UpstreamUrl,
+ null,
reservation.DuplicateOfHash,
reservation.DuplicateScore);
}
@@ -302,6 +304,7 @@ record = RedactRecordForPersistence(record);
reservation.AlreadySubmitted,
reservation.Status,
reservation.UpstreamUrl,
+ null,
reservation.DuplicateOfHash,
reservation.DuplicateScore);
}
@@ -317,6 +320,7 @@ record = RedactRecordForPersistence(record);
reservation.AlreadySubmitted,
found.Status,
issueUrl ?? found.UpstreamUrl,
+ submitResult.Error,
reservation.DuplicateOfHash,
reservation.DuplicateScore);
});
diff --git a/src/CodeIndex/Mcp/McpServer.cs b/src/CodeIndex/Mcp/McpServer.cs
index c534e5ff03..d19e057117 100644
--- a/src/CodeIndex/Mcp/McpServer.cs
+++ b/src/CodeIndex/Mcp/McpServer.cs
@@ -3187,7 +3187,9 @@ private static string AppendLanguageSupportClause(string name, string descriptio
=> $"Language support: Supports graph/reference extraction for: {GraphLanguageList()}. Unsupported `lang` values are reported with graph-support metadata when the tool returns graph-support fields; use `search`, `definition`, `excerpt`, or `files` for non-graph languages.",
"definition" or "symbols" or "outline" or "analyze_symbol"
=> $"Language support: Supports symbol extraction for: {SymbolLanguageList()}. Search-only languages can still be indexed and filtered by file tools but may have no symbol rows.",
- "search" or "find_in_file" or "files" or "map"
+ "search"
+ => "Language support: Supports indexed file/content filters for every detected language; call `languages` for the full catalog.",
+ "find_in_file" or "files" or "map"
=> $"Language support: Supports indexed file/content filters for every detected language listed by `languages`: {DetectedLanguageList()}. Symbol and graph fields are available only for the languages whose capabilities are advertised by `languages`.",
"excerpt" or "status" or "validate"
=> $"Language support: Language-agnostic over indexed files and diagnostics for every detected language listed by `languages`: {DetectedLanguageList()}. This tool does not interpret a `lang` filter.",
diff --git a/src/CodeIndex/Mcp/McpToolDefinitions.cs b/src/CodeIndex/Mcp/McpToolDefinitions.cs
index 4a9de77b69..f3304c8e4d 100644
--- a/src/CodeIndex/Mcp/McpToolDefinitions.cs
+++ b/src/CodeIndex/Mcp/McpToolDefinitions.cs
@@ -20,7 +20,7 @@ private JsonNode HandleToolsList(JsonNode? id)
{
CreateToolDefinition(
"search",
- "Full-text search across indexed code chunks using FTS5. Returns compact match-centered snippets with line metadata. Non-empty responses include `next_step_suggestion` for the obvious follow-up read, and empty responses include `recovery_hint`. The literal-safe path quotes each whitespace-separated token as an FTS5 phrase and combines multiple tokens with implicit AND semantics (`foo bar` requires both terms). For CJK that means `search 計算` no longer also matches `計算する`/`計算機`, because unicode61 keeps adjacent CJK codepoints in one token. Two opt-ins enable FTS5 prefix expansion: (1) trailing `*` on a single token in the `query` string (`search 計算*` to match `計算する`); (2) the `prefix` flag, which promotes every token in the query to a prefix phrase. Use `exactSubstring` for case-sensitive exact-substring matching that bypasses FTS5 entirely; `exact` is the backward-compatible alias documented in USER_GUIDE.md's flag compatibility table. Non-CJK tokens follow the same rule — ASCII identifiers also no longer auto-prefix, so use `--prefix` or trailing `*` to widen. Emoji-mixed tokens cannot be distinguished from their plain ASCII counterpart at the FTS layer (unicode61 drops the emoji on both index and query side — `foo🎉` is FTS-equivalent to `foo`), and pure emoji substring search is 0-result for the same reason; use `exactSubstring` when emoji identity matters. Examples: `search {\"query\":\"handleRequest\",\"lang\":\"csharp\"}`; `search {\"query\":\"Authenticate\",\"lang\":\"csharp\",\"path\":\"src/Auth\",\"prefix\":true}`. / FTS5を使ったコードチャンクの全文検索。一致中心の軽量スニペットと行メタデータを返す。非空レスポンスには自然な次の読み取りを示す `next_step_suggestion`、空レスポンスには `recovery_hint` を含める。literal-safe 経路は空白区切りの各トークンを FTS5 phrase として引用し、複数 token は implicit AND として結合する(`foo bar` は両方の term を要求する)。CJK の場合、unicode61 は隣接 CJK コードポイントを一語として扱うため、`search 計算` は `計算する`/`計算機` にはマッチしない。FTS5 prefix への昇格は 2 通りでオプトイン: (1) `query` 文字列内のトークン末尾に `*` を付ける(`search 計算*` で `計算する` にマッチ)。(2) `prefix` フラグで、クエリの全トークンを prefix phrase に昇格させる。`exactSubstring` を使うと FTS5 を経由せず大小文字区別の厳密部分文字列マッチになり、`exact` は USER_GUIDE.md の flag compatibility table に記載された後方互換 alias。CJK 以外(ASCII 識別子等)も同じルールで、自動 prefix は行わないため、広げたい場合は `--prefix` か末尾 `*` を使う。絵文字混在トークンは、unicode61 が indexing とクエリの両側で絵文字を削ぐため FTS 層で素の ASCII トークンと区別できず(`foo🎉` は FTS 上 `foo` と等価)、絵文字単独の部分一致も同じ理由で 0 件になる。絵文字の同一性が必要な場合は `exactSubstring` を使う。例: `search {\"query\":\"handleRequest\",\"lang\":\"csharp\"}`; `search {\"query\":\"Authenticate\",\"lang\":\"csharp\",\"path\":\"src/Auth\",\"prefix\":true}`。",
+ "Full-text search across indexed code chunks. Returns match-centered snippets with line metadata plus `next_step_suggestion` or `recovery_hint`. Use `prefix` or trailing `*` to widen token matching, `rawQuery` for FTS5 syntax, and `exactSubstring` for case-sensitive text identity. Details and examples: USER_GUIDE.md#search. / インデックス済みコードチャンクの全文検索。`prefix` / 末尾 `*` / `rawQuery` / `exactSubstring` の詳細と例は USER_GUIDE.md#search を参照。",
new JsonObject
{
["type"] = "object",
@@ -486,10 +486,11 @@ private JsonNode HandleToolsList(JsonNode? id)
+ "Call this when you notice a gap (e.g. missing language support, poor ranking) or encounter an unexpected error. "
+ "Never include source code — describe the gap in natural language only. "
+ "The tool writes to the resolved .cdidx directory, which must be writable; responses include cdidx_dir for diagnostics. "
+ + "Responses also include github_submission_reason: submitted, token_not_configured, repo_not_configured, network_error, or api_error. "
+ "/ cdidxへの構造化された改善提案またはエラー報告を送信する。"
+ "ギャップ(言語サポート不足、ランキング不良等)に気づいたとき、または予期せぬエラーに遭遇したときに呼び出す。"
+ "ソースコードを含めないこと — 自然言語でのみギャップを記述する。"
- + "解決された .cdidx ディレクトリへ書き込むため、そのディレクトリは書き込み可能である必要がある。応答には診断用の cdidx_dir が含まれる。",
+ + "解決された .cdidx ディレクトリへ書き込むため、そのディレクトリは書き込み可能である必要がある。応答には診断用の cdidx_dir と github_submission_reason が含まれる。",
new JsonObject
{
["type"] = "object",
diff --git a/src/CodeIndex/Mcp/McpToolHandlers.cs b/src/CodeIndex/Mcp/McpToolHandlers.cs
index d8a76e0273..ef2aa6148c 100644
--- a/src/CodeIndex/Mcp/McpToolHandlers.cs
+++ b/src/CodeIndex/Mcp/McpToolHandlers.cs
@@ -511,7 +511,7 @@ private static string DescribeJsonType(JsonNode? node)
"validate" => new HashSet(StringComparer.Ordinal) { "path", "lang", "limit", "excludePaths", "excludeTests", "project", "solution" },
"unused_symbols" => new HashSet(StringComparer.Ordinal) { "kind", "lang", "limit", "path", "excludePaths", "excludeTests", "project", "solution" },
"symbol_hotspots" => new HashSet(StringComparer.Ordinal) { "kind", "lang", "limit", "groupBy", "path", "excludePaths", "excludeTests", "project", "solution" },
- "index" => new HashSet(StringComparer.Ordinal) { "path", "db", "rebuild", "parallelism", "files", "commits", "changedBetween", "dryRun", "optimize" },
+ "index" => new HashSet(StringComparer.Ordinal) { "path", "db", "rebuild", "parallelism", "maxFileBytes", "files", "commits", "changedBetween", "dryRun", "optimize" },
"backfill_fold" => new HashSet(StringComparer.Ordinal) { "dry_run", "dryRun", "force" },
"suggest_improvement" => new HashSet(StringComparer.Ordinal) { "category", "language", "description", "context", "toolInvocationContext" },
_ => new HashSet(StringComparer.Ordinal),
@@ -3993,7 +3993,8 @@ private async Task ExecuteSuggestImprovementAsync(JsonNode? id, JsonNo
// Build GitHub submission callback (null if no token configured).
// GitHub 送信コールバックを構築(トークン未設定なら null)。
Func>? githubCallback = null;
- if (GitHubIssueReporter.ResolveToken() != null)
+ var githubTokenConfigured = GitHubIssueReporter.ResolveToken() != null;
+ if (githubTokenConfigured)
{
var version = _version;
var cancellationToken = _currentRequestToken.Value;
@@ -4014,9 +4015,12 @@ private async Task ExecuteSuggestImprovementAsync(JsonNode? id, JsonNo
? "This suggestion was already recorded. GitHub submission retried successfully."
: "This suggestion has already been recorded.",
["submitted_to_github"] = result.AlreadySubmitted || result.UpstreamUrl != null,
+ ["github_submission_reason"] = ResolveGitHubSubmissionReason(result, githubTokenConfigured),
["lifecycle_status"] = JsonNamingPolicy.SnakeCaseLower.ConvertName(result.Status.ToString()),
["cdidx_dir"] = cdidxDir,
};
+ if (result.SubmissionError != null)
+ dupPayload["github_submission_error"] = result.SubmissionError;
if (result.DuplicateOfHash != null)
dupPayload["duplicate_of"] = result.DuplicateOfHash;
if (result.DuplicateScore != null)
@@ -4038,9 +4042,12 @@ private async Task ExecuteSuggestImprovementAsync(JsonNode? id, JsonNo
["language"] = language,
["stored_locally"] = true,
["submitted_to_github"] = result.UpstreamUrl != null,
+ ["github_submission_reason"] = ResolveGitHubSubmissionReason(result, githubTokenConfigured),
["lifecycle_status"] = JsonNamingPolicy.SnakeCaseLower.ConvertName(result.Status.ToString()),
["cdidx_dir"] = cdidxDir,
};
+ if (result.SubmissionError != null)
+ payload["github_submission_error"] = result.SubmissionError;
if (result.UpstreamUrl != null)
{
payload["upstream_url"] = result.UpstreamUrl;
@@ -4053,6 +4060,26 @@ private async Task ExecuteSuggestImprovementAsync(JsonNode? id, JsonNo
return CreateToolResult(id, "Suggestion recorded. Thank you for the feedback.", payload);
}
+ private static string ResolveGitHubSubmissionReason(SuggestionStore.AddAndSubmitResult result, bool githubTokenConfigured)
+ {
+ if (result.AlreadySubmitted || result.UpstreamUrl != null)
+ return "submitted";
+ if (!githubTokenConfigured)
+ return "token_not_configured";
+ if (result.SubmissionError != null)
+ return StartsWithHttpStatusCode(result.SubmissionError) ? "api_error" : "network_error";
+ return "repo_not_configured";
+ }
+
+ private static bool StartsWithHttpStatusCode(string value)
+ {
+ return value.Length >= 4
+ && char.IsDigit(value[0])
+ && char.IsDigit(value[1])
+ && char.IsDigit(value[2])
+ && value[3] == ':';
+ }
+
private sealed record SuggestionSamplingResult(string? Title, string[]? Tags);
private async Task TrySampleSuggestionMetadataAsync(
diff --git a/tests/CodeIndex.Tests/McpServerTests.cs b/tests/CodeIndex.Tests/McpServerTests.cs
index 4dc6e7bfe7..ac41a3fe48 100644
--- a/tests/CodeIndex.Tests/McpServerTests.cs
+++ b/tests/CodeIndex.Tests/McpServerTests.cs
@@ -2572,6 +2572,20 @@ public void ToolsCall_UnknownArgument_ReturnsInvalidParams()
Assert.Equal("limt", structured["unknown_argument"]!.GetValue());
}
+ [Fact]
+ public void ToolsCall_IndexAllowsAdvertisedMaxFileBytesArgument()
+ {
+ var request = JsonNode.Parse("""{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"index","arguments":{"path":"/","maxFileBytes":1024}}}""")!;
+
+ var response = _server.HandleMessage(request)!;
+
+ var result = response["result"]!;
+ Assert.True(result["isError"]!.GetValue());
+ var text = result["content"]![0]!["text"]!.GetValue();
+ Assert.DoesNotContain("Unknown argument 'maxFileBytes'", text, StringComparison.Ordinal);
+ Assert.Contains("Path must be within the current working directory", text, StringComparison.Ordinal);
+ }
+
[Fact]
public void ToolsList_SearchIncludesPathFilterParams()
{
@@ -2587,6 +2601,20 @@ public void ToolsList_SearchIncludesPathFilterParams()
Assert.NotNull(properties["excludeTests"]);
}
+ [Fact]
+ public void ToolsList_SearchDescriptionStaysCompact()
+ {
+ var request = JsonNode.Parse("""{"jsonrpc":"2.0","id":1,"method":"tools/list"}""")!;
+ var response = _server.HandleMessage(request)!;
+
+ var tools = response["result"]!["tools"]!.AsArray();
+ var searchTool = tools.First(t => t!["name"]!.GetValue() == "search")!;
+ var description = searchTool["description"]!.GetValue();
+
+ Assert.True(description.Length < 1000);
+ Assert.Contains("USER_GUIDE.md#search", description, StringComparison.Ordinal);
+ }
+
[Fact]
public void ToolsList_CommonSchemasAdvertiseClientSideConstraints()
{
@@ -2634,7 +2662,7 @@ public void ToolsList_NavigationDescriptionsIncludeConcreteExamples()
var tools = response["result"]!["tools"]!.AsArray();
var expectedExamples = new Dictionary
{
- ["search"] = ["Examples:", "例:", "search {\"query\":\"handleRequest\",\"lang\":\"csharp\"}", "\"prefix\":true"],
+ ["search"] = ["USER_GUIDE.md#search", "prefix", "exactSubstring"],
["definition"] = ["Examples:", "例:", "definition {\"query\":\"McpServer\"}", "\"includeBody\":true", "\"exactName\":true"],
["references"] = ["Examples:", "例:", "references {\"query\":\"Run\"}", "\"kind\":\"type_reference\""],
["callers"] = ["Examples:", "例:", "callers {\"query\":\"HandleRequest\"}", "\"rankBy\":\"weighted\""],
@@ -9179,6 +9207,8 @@ public void ToolsCall_Search_DbNotFound_ReturnsError()
[Fact]
public void SuggestImprovement_ValidInput_ReturnsSuccess()
{
+ using var env = EnvironmentVariableScope.Capture("CDIDX_GITHUB_TOKEN");
+ env.Set("CDIDX_GITHUB_TOKEN", null);
// Use unique description to avoid dedup collision with other test runs
// 他テスト実行との重複排除衝突を避けるため一意な description を使用
var uniqueDesc = $"Arrow functions are not detected as symbols {Guid.NewGuid():N}";
@@ -9201,6 +9231,8 @@ public void SuggestImprovement_ValidInput_ReturnsSuccess()
Assert.Equal("draft", structured["lifecycle_status"]!.GetValue());
Assert.NotNull(structured["hash"]);
Assert.True(structured["stored_locally"]!.GetValue());
+ Assert.False(structured["submitted_to_github"]!.GetValue());
+ Assert.Equal("token_not_configured", structured["github_submission_reason"]!.GetValue());
Assert.Equal(Path.GetFullPath(Path.GetDirectoryName(_dbPath)!), structured["cdidx_dir"]!.GetValue());
}