Skip to content

Commit 03f5099

Browse files
authored
Merge pull request #2734 from Widthdom/codex/fix-issue1614-1796-1921-1940
Fix suggest_improvement storage safety
2 parents e9ff08f + 0a79dff commit 03f5099

14 files changed

Lines changed: 492 additions & 5 deletions

USER_GUIDE.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,8 @@ Supported schema (top-level keys are snake_case; nested indexing kind keys keep
13231323
"global_tool_log_dir": "./.cdidx/logs", // → CDIDX_GLOBAL_TOOL_LOG_DIR
13241324
"stale_after": "2h", // → CDIDX_STALE_AFTER
13251325
"suggestion_dedup_threshold": 0.85, // → CDIDX_SUGGESTION_DEDUP_THRESHOLD
1326+
"suggestion_max_age_days": 365, // → CDIDX_SUGGESTION_MAX_AGE_DAYS
1327+
"suggestion_max_count": 5000, // → CDIDX_SUGGESTION_MAX_COUNT
13261328
"indexing": {
13271329
"includeKinds": ["class"], // → CDIDX_INDEX_INCLUDE_SYMBOL_KINDS
13281330
"excludeKinds": ["test_method"] // → CDIDX_INDEX_EXCLUDE_SYMBOL_KINDS
@@ -1340,7 +1342,7 @@ Supported schema (top-level keys are snake_case; nested indexing kind keys keep
13401342
}
13411343
```
13421344

1343-
JSON5-style line comments (`//`) and trailing commas are accepted so the file stays human-editable. The optional `$schema` key is ignored at runtime; it is honored only so editors that recognize JSON Schema references can offer completion. Setting `disable_persistent_log` to `false` is a no-op (absence already means "logging enabled") — only `true` exports `CDIDX_DISABLE_PERSISTENT_LOG=1`. `stale_after` uses the same compact duration format as `status --check --stale-after`: `30m`, `2h`, or `7d`. `suggestion_dedup_threshold` sets the MCP suggestion fuzzy-deduplication cutoff as a number from `0` to `1`; the built-in default is `0.85`, and `cdidx mcp --suggestion-dedup-threshold <0..1>` overrides it for one MCP session. `indexing.includeKinds` and `indexing.excludeKinds` set the default symbol-kind filter for `cdidx index`; CLI flags `--include-symbol-kind <kind>[,<kind>]` and `--exclude-symbol-kind <kind>[,<kind>]` override those env-backed defaults for a single run.
1345+
JSON5-style line comments (`//`) and trailing commas are accepted so the file stays human-editable. The optional `$schema` key is ignored at runtime; it is honored only so editors that recognize JSON Schema references can offer completion. Setting `disable_persistent_log` to `false` is a no-op (absence already means "logging enabled") — only `true` exports `CDIDX_DISABLE_PERSISTENT_LOG=1`. `stale_after` uses the same compact duration format as `status --check --stale-after`: `30m`, `2h`, or `7d`. `suggestion_dedup_threshold` sets the MCP suggestion fuzzy-deduplication cutoff as a number from `0` to `1`; the built-in default is `0.85`, and `cdidx mcp --suggestion-dedup-threshold <0..1>` overrides it for one MCP session. `suggestion_max_age_days` and `suggestion_max_count` bound the live `.cdidx/suggestions-*.json` store; pruned records are appended to `.cdidx/suggestions-*.archive.jsonl`. `indexing.includeKinds` and `indexing.excludeKinds` set the default symbol-kind filter for `cdidx index`; CLI flags `--include-symbol-kind <kind>[,<kind>]` and `--exclude-symbol-kind <kind>[,<kind>]` override those env-backed defaults for a single run.
13441346

13451347
## How it works
13461348

@@ -3302,6 +3304,8 @@ MCP ツールで catch-all まで突き抜けた例外(想定外の SQLite 例
33023304
"global_tool_log_dir": "./.cdidx/logs", // → CDIDX_GLOBAL_TOOL_LOG_DIR
33033305
"stale_after": "2h", // → CDIDX_STALE_AFTER
33043306
"suggestion_dedup_threshold": 0.85, // → CDIDX_SUGGESTION_DEDUP_THRESHOLD
3307+
"suggestion_max_age_days": 365, // → CDIDX_SUGGESTION_MAX_AGE_DAYS
3308+
"suggestion_max_count": 5000, // → CDIDX_SUGGESTION_MAX_COUNT
33053309
"indexing": {
33063310
"includeKinds": ["class"], // → CDIDX_INDEX_INCLUDE_SYMBOL_KINDS
33073311
"excludeKinds": ["test_method"] // → CDIDX_INDEX_EXCLUDE_SYMBOL_KINDS
@@ -3319,7 +3323,7 @@ MCP ツールで catch-all まで突き抜けた例外(想定外の SQLite 例
33193323
}
33203324
```
33213325

3322-
人手で編集しやすいよう JSON5 形式の行コメント(`//`)と末尾カンマを許容します。任意の `$schema` キーはランタイムでは無視され、JSON Schema 参照をサポートするエディタが補完を提供するためだけに認識されます。`disable_persistent_log` を `false` に設定しても何も起きません(不在のままで "ログ有効" が既定)— `true` の場合のみ `CDIDX_DISABLE_PERSISTENT_LOG=1` を export します。`stale_after` は `status --check --stale-after` と同じ compact duration 形式(`30m` / `2h` / `7d`)です。`suggestion_dedup_threshold` は MCP suggestion の fuzzy deduplication しきい値を `0` から `1` の数値で設定します。組み込み既定値は `0.85` で、`cdidx mcp --suggestion-dedup-threshold <0..1>` は 1 回の MCP session だけこの値を上書きします。`indexing.includeKinds` と `indexing.excludeKinds` は `cdidx index` の symbol-kind filter 既定値を設定し、CLI フラグ `--include-symbol-kind <kind>[,<kind>]` / `--exclude-symbol-kind <kind>[,<kind>]` はその env 経由の既定値を 1 回の実行だけ上書きします。
3326+
人手で編集しやすいよう JSON5 形式の行コメント(`//`)と末尾カンマを許容します。任意の `$schema` キーはランタイムでは無視され、JSON Schema 参照をサポートするエディタが補完を提供するためだけに認識されます。`disable_persistent_log` を `false` に設定しても何も起きません(不在のままで "ログ有効" が既定)— `true` の場合のみ `CDIDX_DISABLE_PERSISTENT_LOG=1` を export します。`stale_after` は `status --check --stale-after` と同じ compact duration 形式(`30m` / `2h` / `7d`)です。`suggestion_dedup_threshold` は MCP suggestion の fuzzy deduplication しきい値を `0` から `1` の数値で設定します。組み込み既定値は `0.85` で、`cdidx mcp --suggestion-dedup-threshold <0..1>` は 1 回の MCP session だけこの値を上書きします。`suggestion_max_age_days` と `suggestion_max_count` は live の `.cdidx/suggestions-*.json` store の上限を設定し、prune された record は `.cdidx/suggestions-*.archive.jsonl` に追記されます。`indexing.includeKinds` と `indexing.excludeKinds` は `cdidx index` の symbol-kind filter 既定値を設定し、CLI フラグ `--include-symbol-kind <kind>[,<kind>]` / `--exclude-symbol-kind <kind>[,<kind>]` はその env 経由の既定値を 1 回の実行だけ上書きします。
33233327

33243328
## 動作の仕組み
33253329

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 1614
5+
affected:
6+
- src/CodeIndex/Cli/GitHubIssueReporter.cs
7+
- tests/CodeIndex.Tests/GitHubIssueReporterTests.cs
8+
---
9+
10+
## English
11+
12+
- **Sanitized `suggest_improvement` GitHub issue titles (#1614)** — outbound suggestion issue titles now strip markdown link/image syntax characters and defensively cap category text before posting.
13+
14+
## 日本語
15+
16+
- **`suggest_improvement` の GitHub Issue タイトルを sanitize しました (#1614)** — 外部送信用の提案 Issue タイトルから markdown のリンク/画像構文文字を除去し、category 文字列も防御的に上限を設けてから投稿します。
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 1796
5+
affected:
6+
- src/CodeIndex/Cli/SuggestionStore.cs
7+
- tests/CodeIndex.Tests/SuggestionStoreTests.cs
8+
---
9+
10+
## English
11+
12+
- **Redacted sensitive suggestion text before persistence (#1796)**`SuggestionStore` now masks common credential and token patterns before writing local suggestion history or submitting to GitHub, with stderr warnings that name only the redaction types.
13+
14+
## 日本語
15+
16+
- **提案テキストを保存前に redaction するようにしました (#1796)**`SuggestionStore` はローカルの提案履歴保存や GitHub 送信の前に一般的な credential/token パターンをマスクし、stderr には redaction 種別だけを警告します。
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 1921
5+
affected:
6+
- src/CodeIndex/Mcp/McpToolHandlers.cs
7+
- src/CodeIndex/Mcp/McpToolDefinitions.cs
8+
- tests/CodeIndex.Tests/McpServerTests.cs
9+
---
10+
11+
## English
12+
13+
- **Validated `suggest_improvement` write targets before locking (#1921)** — the MCP tool now probes the resolved `.cdidx` directory for writability before entering the suggestion-store lock and includes `cdidx_dir` in success and duplicate payloads.
14+
15+
## 日本語
16+
17+
- **`suggest_improvement` の書き込み先を lock 前に検証するようにしました (#1921)** — MCP tool は suggestion-store lock に入る前に解決済み `.cdidx` ディレクトリへ書き込み probe を行い、成功/重複 payload に `cdidx_dir` を含めます。
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 1940
5+
affected:
6+
- src/CodeIndex/Cli/SuggestionStore.cs
7+
- src/CodeIndex/Cli/CdidxConfigFile.cs
8+
- tests/CodeIndex.Tests/SuggestionStoreTests.cs
9+
- tests/CodeIndex.Tests/CdidxConfigFileTests.cs
10+
- USER_GUIDE.md
11+
---
12+
13+
## English
14+
15+
- **Bounded live suggestion history (#1940)**`SuggestionStore` now prunes stale or over-limit records into `.cdidx/suggestions-*.archive.jsonl`, with configurable `suggestion_max_age_days` and `suggestion_max_count` settings.
16+
17+
## 日本語
18+
19+
- **live の提案履歴に上限を設けました (#1940)**`SuggestionStore` は古いまたは上限超過の record を `.cdidx/suggestions-*.archive.jsonl` に退避し、`suggestion_max_age_days``suggestion_max_count` で設定できます。

src/CodeIndex/Cli/CdidxConfigFile.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ internal static class CdidxConfigFile
4141
"graph",
4242
"folding",
4343
"suggestion_dedup_threshold",
44+
"suggestion_max_age_days",
45+
"suggestion_max_count",
4446
"mcp",
4547
};
4648

@@ -165,6 +167,20 @@ internal static LoadResult LoadAndApply(
165167
pending.Add((SuggestionStore.DedupThresholdEnvironmentVariable, value!));
166168
}
167169

170+
if (root.TryGetProperty("suggestion_max_age_days", out var suggestionMaxAgeDays))
171+
{
172+
if (!TryReadPositiveIntegerAsString(suggestionMaxAgeDays, "suggestion_max_age_days", path, out var value, out var err))
173+
return new LoadResult(Path: path, Error: err);
174+
pending.Add((SuggestionStore.MaxAgeDaysEnvironmentVariable, value!));
175+
}
176+
177+
if (root.TryGetProperty("suggestion_max_count", out var suggestionMaxCount))
178+
{
179+
if (!TryReadPositiveIntegerAsString(suggestionMaxCount, "suggestion_max_count", path, out var value, out var err))
180+
return new LoadResult(Path: path, Error: err);
181+
pending.Add((SuggestionStore.MaxCountEnvironmentVariable, value!));
182+
}
183+
168184
if (root.TryGetProperty("indexing", out var indexing))
169185
{
170186
if (indexing.ValueKind != JsonValueKind.Object)
@@ -443,6 +459,26 @@ private static bool TryReadNumberAsString(JsonElement element, string key, strin
443459
return true;
444460
}
445461

462+
private static bool TryReadPositiveIntegerAsString(JsonElement element, string key, string path, out string? value, out string? error)
463+
{
464+
value = null;
465+
error = null;
466+
if (element.ValueKind != JsonValueKind.Number)
467+
{
468+
error = $"[cdidx] {path}: `{key}` must be a number.";
469+
return false;
470+
}
471+
472+
if (!element.TryGetInt32(out var parsed) || parsed <= 0)
473+
{
474+
error = $"[cdidx] {path}: `{key}` must be a positive integer.";
475+
return false;
476+
}
477+
478+
value = parsed.ToString(System.Globalization.CultureInfo.InvariantCulture);
479+
return true;
480+
}
481+
446482
private static bool TryReadSearchInteger(JsonElement element, string key, string optionName, bool allowZero, string path, out string? value, out string? error)
447483
{
448484
value = null;

src/CodeIndex/Cli/GitHubIssueReporter.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,11 +437,15 @@ internal static string ScrubInlineCode(string text)
437437

438438
internal static string BuildIssueTitle(string category, string description)
439439
{
440-
var prefix = $"[AI Suggestion] {category}: ";
440+
var sanitizedCategory = SanitizeIssueTitleText(category);
441+
if (sanitizedCategory.Length > 40)
442+
sanitizedCategory = sanitizedCategory[..40].TrimEnd();
443+
444+
var prefix = $"[AI Suggestion] {sanitizedCategory}: ";
441445
if (prefix.Length >= MaxGitHubIssueTitleLength)
442446
return prefix[..MaxGitHubIssueTitleLength];
443447

444-
var scrubbedForTitle = ScrubInlineCode(description).Replace("\r", " ").Replace("\n", " ").Trim();
448+
var scrubbedForTitle = SanitizeIssueTitleText(ScrubInlineCode(description));
445449
var maxDescriptionLength = MaxGitHubIssueTitleLength - prefix.Length;
446450
var shortDesc = TruncateWithEllipsis(scrubbedForTitle, Math.Min(63, maxDescriptionLength));
447451
var title = prefix + shortDesc;
@@ -450,6 +454,22 @@ internal static string BuildIssueTitle(string category, string description)
450454
: title[..MaxGitHubIssueTitleLength];
451455
}
452456

457+
internal static string SanitizeIssueTitleText(string value)
458+
{
459+
if (string.IsNullOrEmpty(value))
460+
return string.Empty;
461+
462+
var builder = new StringBuilder(value.Length);
463+
foreach (var c in value.Replace("\r", " ").Replace("\n", " "))
464+
{
465+
if (c is '[' or ']' or '(' or ')' or '`')
466+
continue;
467+
builder.Append(c);
468+
}
469+
470+
return builder.ToString().Trim();
471+
}
472+
453473
private static string TruncateWithEllipsis(string value, int maxLength)
454474
{
455475
if (value.Length <= maxLength)

0 commit comments

Comments
 (0)