Skip to content

Commit c259e67

Browse files
committed
Address issue-draft summary review findings (#5058)
1 parent effc781 commit c259e67

6 files changed

Lines changed: 107 additions & 22 deletions

File tree

TESTING_GUIDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Use the full suite by default. Use targeted filters only while iterating locally
2020
## Test Stack
2121

2222
- `WorkspaceCheckTruncationIssue5055Tests` owns the bounded `workspace_check` list contract. Keep zero, below-limit, exact-limit, and over-limit coverage for all six categories, plus the 1260/20/1240 example and one shared filesystem fixture for raw JSON, list-only projection, compact output, byte-budget trimming, deterministic order, and human sample labeling.
23-
- Recipe issue-draft summary coverage in `QueryCommandRunnerSearchTests` keeps the dedicated compact DTO separate from full draft bodies, asserts returned/total/omitted row cardinality (including `count == drafts.length`), measures the complete UTF-8 document with multibyte paths and its final newline, verifies whole-row truncation and typed `E028` audit identity, and preserves the full-output contract.
23+
- Recipe issue-draft summary coverage in `QueryCommandRunnerSearchTests` keeps the dedicated compact DTO separate from full draft bodies, asserts returned/total/omitted row cardinality (including `count == drafts.length` and positive queries hidden by `--total-limit`), preserves pre-limit evidence-path counts with authority/lower-bound metadata, measures the complete UTF-8 document with multibyte paths and its final newline, verifies whole-row truncation and typed `E028` audit identity, and preserves the full-output contract.
2424
- Marker-gate reference coverage keeps cross-language positive syntax in `ReferenceExtractorMarkerGateTests.cs`, with dedicated GraphQL/HTML/Markdown and XAML/XML cases proving that markerless continuation lines still advance parser state. `ReferenceExtractorPerformanceBudgetTests` owns three complementary regressions: Kotlin infix detection must not enumerate a large known-name set, Docker instruction classification must allocate nothing and scale with the instruction prefix rather than irrelevant line length, and warmed C#/Java/Kotlin markerless-decoy extraction must produce no references while staying below 24 MiB of measured allocations and the 3-second primary-target runaway guard. Keep the broad allocation/time guards on `net8.0`; keep the focused positive and state-preservation tests cross-target.
2525
- Framework: xUnit
2626
- Target frameworks: `net8.0` and `net9.0`
@@ -1017,7 +1017,7 @@ dotnet test --filter "FullyQualifiedName~GitHelperTests"
10171017
## テストスタック
10181018

10191019
- `WorkspaceCheckTruncationIssue5055Tests` は上限付き `workspace_check` 一覧の契約を担当します。6カテゴリすべての0件・上限未満・上限一致・上限超過、1260/20/1240 の例、および raw JSON、一覧単独 projection、compact 出力、byte budget による短縮、決定的順序、human 向け sample 表示を1つの共有 filesystem fixture で検証する coverage を維持してください。
1020-
- `QueryCommandRunnerSearchTests` の recipe issue-draft summary coverage は、専用 compact DTO を完全版 draft body から分離し、返却 / total / omitted row の cardinality(`count == drafts.length` を含む)、multibyte path と末尾改行を含む UTF-8 document 全体の計測、完全な row 単位の truncation、型付き `E028` の audit identity、完全版 output contract の維持を検証します。
1020+
- `QueryCommandRunnerSearchTests` の recipe issue-draft summary coverage は、専用 compact DTO を完全版 draft body から分離し、返却 / total / omitted row の cardinality(`count == drafts.length` と `--total-limit` に隠れた positive query を含む)、limit 適用前の evidence-path count と authority / lower-bound metadata の維持、multibyte path と末尾改行を含む UTF-8 document 全体の計測、完全な row 単位の truncation、型付き `E028` の audit identity、完全版 output contract の維持を検証します。
10211021
- marker gate の reference coverage は、言語横断の正しい構文を `ReferenceExtractorMarkerGateTests.cs` にまとめ、GraphQL/HTML/Markdown と XAML/XML の専用 case で marker のない継続行でも parser state が更新されることを固定します。`ReferenceExtractorPerformanceBudgetTests` は相補的な3つの回帰契約を所有します。Kotlin infix 検出では大きな既知名集合を列挙しないこと、Docker instruction 分類では allocation が0で無関係な行長ではなく instruction prefix に応じて scale すること、warmup 済みの C#/Java/Kotlin markerless-decoy 抽出では reference が0件のまま計測 allocation 24 MiB未満かつ primary target の3秒 runaway guard内であることを検証します。広い allocation / time guard は `net8.0` に限定し、focused な正例とstate維持のテストはcross-targetのままにしてください。
10221022
- フレームワーク: xUnit
10231023
- メインのテストプロジェクト: `tests/CodeIndex.Tests/CodeIndex.Tests.csproj`

USER_GUIDE.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,7 +1677,10 @@ to recipe issue-draft export when agents only need compact top-level metadata:
16771677
the output uses a dedicated summary contract with one compact row per positive
16781678
query instead of embedding full issue bodies, source rows, or repeated recipe
16791679
metadata. Each row reports counts, severity/confidence, labels, at most five
1680-
evidence paths with explicit omission counts, and a full-detail replay command.
1680+
evidence paths with explicit omission counts, count authority/lower bounds when
1681+
the candidate window is incomplete, and a full-detail replay command. Positive
1682+
queries remain represented even when `--total-limit` leaves them with zero
1683+
returned results; the uncapped recovery command omits that run-wide limit.
16811684
The root reports total/returned/omitted row counts, whether the total is
16821685
authoritative, `query_freshness`, and an uncapped `recovery_command`. Combine it
16831686
with `--max-json-bytes <n>` to measure the complete UTF-8 document (including
@@ -5147,8 +5150,10 @@ JSON summary には `confidence` と `minimum_score` が出力されます。dra
51475150
top-level metadata だけを必要とする場合は、recipe issue-draft export に `--summary-only` を追加します。
51485151
この出力は専用の summary contract を使い、完全な Issue body、source row、query ごとに重複する
51495152
recipe metadata を埋め込まず、結果がある query ごとに compact な row を1件出力します。各 row は
5150-
count、severity / confidence、label、最大5件の evidence path と明示的な省略件数、完全な詳細を取得する
5151-
replay command を返します。root は total / returned / omitted row count、total が authoritative か、
5153+
count、severity / confidence、label、最大5件の evidence path、明示的な省略件数、candidate window が
5154+
不完全な場合の count authority / lower bound、完全な詳細を取得する replay command を返します。
5155+
`--total-limit` により返却 result が0件になった positive query も row として保持し、上限なしの recovery
5156+
command からは run 全体のその limit を除外します。root は total / returned / omitted row count、total が authoritative か、
51525157
`query_freshness`、上限なしの `recovery_command` を返します。`--max-json-bytes <n>` を併用すると、末尾の
51535158
改行を含む UTF-8 document 全体を計測し、上限に収まる完全な row だけを保持します。row 0件の envelope
51545159
さえ収まらない場合は `E028` で fail-closed にし、retry guidance でも実行元の `search` または `audit`

changelog.d/unreleased/5058.fixed.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ affected:
1414

1515
## English
1616

17-
- **Recipe issue-draft summaries now fit bounded automation payloads (#5058; follow-up to #4064, #4118, and #4308)**`--summary-only` now uses a dedicated compact contract with one whole row per positive query, bounded evidence paths, explicit omission and total-authority metadata, full-detail replay commands, and an uncapped recovery command. `--max-json-bytes` measures the complete UTF-8 document including its final newline, truncates only at row boundaries, and preserves the invoked `search` or `audit` identity in `E028` guidance; full issue-draft output remains unchanged.
17+
- **Recipe issue-draft summaries now fit bounded automation payloads (#5058; follow-up to #4064, #4118, and #4308)**`--summary-only` now uses a dedicated compact contract with one whole row per positive query, including queries hidden by `--total-limit`, bounded evidence paths with truthful authority/lower-bound metadata, full-detail replay commands, and an uncapped recovery command. `--max-json-bytes` measures the complete UTF-8 document including its final newline, truncates only at row boundaries, and preserves the invoked `search` or `audit` identity in `E028` guidance; full issue-draft output remains unchanged.
1818

1919
## 日本語
2020

21-
- **recipe issue-draft summary が automation の上限付き payload に収まるよう修正しました (#5058#4064#4118#4308 の follow-up)**`--summary-only` は、結果がある query ごとの完全な row、上限付き evidence path、明示的な省略件数と total の authority metadata、完全な詳細を取得する replay command、上限なしの recovery command を持つ専用 compact contract を使います。`--max-json-bytes` は末尾改行を含む UTF-8 document 全体を計測し、row 境界だけで truncate し、`E028` guidance でも実行元の `search` または `audit` identity を維持します。完全版 issue-draft output は変更しません。
21+
- **recipe issue-draft summary が automation の上限付き payload に収まるよう修正しました (#5058#4064#4118#4308 の follow-up)**`--summary-only` は、`--total-limit` に隠れた query を含む結果がある query ごとの完全な row、正確な authority / lower-bound metadata を伴う上限付き evidence path、完全な詳細を取得する replay command、上限なしの recovery command を持つ専用 compact contract を使います。`--max-json-bytes` は末尾改行を含む UTF-8 document 全体を計測し、row 境界だけで truncate し、`E028` guidance でも実行元の `search` または `audit` identity を維持します。完全版 issue-draft output は変更しません。

src/CodeIndex/Cli/QueryCommandRunner.SearchRecipes.cs

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,7 +1916,7 @@ private static int WriteSearchRecipeIssueDraftSummary(
19161916
{
19171917
const int evidencePathLimit = 5;
19181918
var summaries = queryResults
1919-
.Where(queryResult => queryResult.Count > 0)
1919+
.Where(queryResult => queryResult.MinimumMatchedCount > 0)
19201920
.Select(queryResult =>
19211921
{
19221922
var labels = queryResult.RecommendedLabels
@@ -1926,12 +1926,9 @@ private static int WriteSearchRecipeIssueDraftSummary(
19261926
.Distinct(StringComparer.OrdinalIgnoreCase)
19271927
.OrderBy(label => label, StringComparer.OrdinalIgnoreCase)
19281928
.ToList();
1929-
var fileCount = queryResult.Results
1930-
.Select(result => result.Path)
1931-
.Where(path => !string.IsNullOrWhiteSpace(path))
1932-
.Distinct(StringComparer.Ordinal)
1933-
.Count();
1934-
var evidencePaths = queryResult.TopFiles
1929+
var fileCount = queryResult.SummaryEvidencePathCount;
1930+
var fileCountAuthoritative = queryResult.SummaryEvidencePathCountAuthoritative;
1931+
var evidencePaths = queryResult.SummaryEvidencePaths
19351932
.Take(evidencePathLimit)
19361933
.ToList();
19371934
var omittedEvidencePathCount = Math.Max(0, fileCount - evidencePaths.Count);
@@ -1941,17 +1938,23 @@ private static int WriteSearchRecipeIssueDraftSummary(
19411938
BuildSearchIssueDraftTitle(recipe, queryResult),
19421939
queryResult.Count,
19431940
fileCount,
1941+
fileCountAuthoritative,
1942+
fileCountAuthoritative ? null : fileCount,
19441943
queryResult.MinimumMatchedCount,
19451944
queryResult.MinimumOmittedResultCount,
19461945
queryResult.Truncated,
19471946
evidencePaths,
19481947
fileCount,
1948+
fileCountAuthoritative,
1949+
fileCountAuthoritative ? null : fileCount,
19491950
evidencePaths.Count,
19501951
omittedEvidencePathCount,
1951-
omittedEvidencePathCount > 0,
1952+
fileCountAuthoritative,
1953+
fileCountAuthoritative ? null : omittedEvidencePathCount,
1954+
omittedEvidencePathCount > 0 || !fileCountAuthoritative,
19521955
labels,
19531956
queryResult.Severity,
1954-
GetSearchRecipeConfidence(queryResult.Count),
1957+
GetSearchRecipeConfidence(queryResult.MinimumMatchedCount),
19551958
queryResult.NextCursor,
19561959
BuildSearchRecipeReplayCommand(
19571960
recipe,
@@ -1971,7 +1974,8 @@ private static int WriteSearchRecipeIssueDraftSummary(
19711974
recipe,
19721975
options,
19731976
summaryOnly: true,
1974-
includeMaxJsonBytes: false);
1977+
includeMaxJsonBytes: false,
1978+
includeTotalLimit: false);
19751979
var totalCount = summaries.Count;
19761980
var totalCountAuthoritative = !hasFailures;
19771981
string? envelopeJson = null;
@@ -2324,6 +2328,12 @@ private static List<SearchRecipeQueryResultJsonResult> CollectSearchRecipeQueryR
23242328
results = ApplySearchRecipeFileRejectQueries(reader, results, options, recipeQuery);
23252329
var rows = BuildSearchDisplayRows(results, options, exact, recipeQuery.Query, rawFtsOverride: false, recipeQuery: recipeQuery);
23262330
rows = ApplySearchRecipeSemanticFilter(reader, options, recipeQuery, rows);
2331+
var summaryEvidencePaths = BuildSearchRecipeTopFiles(rows);
2332+
var summaryEvidencePathCount = rows
2333+
.Select(row => row.Result.Path)
2334+
.Where(path => !string.IsNullOrWhiteSpace(path))
2335+
.Distinct(StringComparer.Ordinal)
2336+
.Count();
23272337
var outputSelection = ApplySearchOutputSelection(rows, options, resultLimit, sourceTotalAuthoritative);
23282338
rows = outputSelection.Rows;
23292339
if (includeAuditClassifications)
@@ -2376,7 +2386,12 @@ private static List<SearchRecipeQueryResultJsonResult> CollectSearchRecipeQueryR
23762386
outputSelection.Returned,
23772387
outputSelection.SelectorOmittedCount,
23782388
outputSelection.LimitOmittedCount,
2379-
outputSelection.Selectors));
2389+
outputSelection.Selectors)
2390+
{
2391+
SummaryEvidencePaths = summaryEvidencePaths,
2392+
SummaryEvidencePathCount = summaryEvidencePathCount,
2393+
SummaryEvidencePathCountAuthoritative = sourceTotalAuthoritative,
2394+
});
23802395
if (freshnessContext != null)
23812396
{
23822397
freshnessObservations.Add(SuccessfulSearchQueryObservation(
@@ -5881,7 +5896,8 @@ private static string BuildSearchRecipeReplayCommand(
58815896
QueryCommandOptions options,
58825897
string? queryName = null,
58835898
bool summaryOnly = false,
5884-
bool includeMaxJsonBytes = true)
5899+
bool includeMaxJsonBytes = true,
5900+
bool includeTotalLimit = true)
58855901
{
58865902
var recipeSelector = string.IsNullOrWhiteSpace(queryName)
58875903
? recipe.Name
@@ -5942,7 +5958,7 @@ private static string BuildSearchRecipeReplayCommand(
59425958
AddReplayValueOption(args, "--exclude-origin", origin);
59435959
foreach (var kind in options.ResultKinds)
59445960
AddReplayValueOption(args, "--result-kind", kind);
5945-
if (options.TotalLimit.HasValue)
5961+
if (includeTotalLimit && options.TotalLimit.HasValue)
59465962
AddReplayValueOption(args, "--total-limit", options.TotalLimit.Value.ToString(CultureInfo.InvariantCulture));
59475963
AddReplayValueOption(args, "--snippet-lines", options.SnippetLines.ToString(CultureInfo.InvariantCulture));
59485964
AddReplayValueOption(args, "--snippet-focus", FormatSearchSnippetFocusMode(options.SnippetFocus));

src/CodeIndex/Cli/SearchAuditRecipes.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4718,7 +4718,17 @@ internal sealed record SearchRecipeQueryResultJsonResult(
47184718
[property: JsonPropertyName("returned")] int Returned,
47194719
[property: JsonPropertyName("selector_omitted_count")] int SelectorOmittedCount,
47204720
[property: JsonPropertyName("limit_omitted_count")] int LimitOmittedCount,
4721-
[property: JsonPropertyName("selectors")] List<SearchRowSelectorJsonResult> Selectors);
4721+
[property: JsonPropertyName("selectors")] List<SearchRowSelectorJsonResult> Selectors)
4722+
{
4723+
[JsonIgnore]
4724+
public List<SearchRecipeTopFileJsonResult> SummaryEvidencePaths { get; init; } = [];
4725+
4726+
[JsonIgnore]
4727+
public int SummaryEvidencePathCount { get; init; }
4728+
4729+
[JsonIgnore]
4730+
public bool SummaryEvidencePathCountAuthoritative { get; init; }
4731+
}
47224732

47234733
internal sealed record SearchNamedBatchCountSummaryRunJsonResult(
47244734
[property: JsonPropertyName("api_version")] string ApiVersion,
@@ -4919,13 +4929,25 @@ internal sealed record SearchIssueDraftSummaryJsonResult(
49194929
[property: JsonPropertyName("title")] string Title,
49204930
[property: JsonPropertyName("result_count")] int ResultCount,
49214931
[property: JsonPropertyName("file_count")] int FileCount,
4932+
[property: JsonPropertyName("file_count_authoritative")] bool FileCountAuthoritative,
4933+
[property: JsonPropertyName("file_count_lower_bound")]
4934+
[property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
4935+
int? FileCountLowerBound,
49224936
[property: JsonPropertyName("minimum_matched_count")] int MinimumMatchedCount,
49234937
[property: JsonPropertyName("minimum_omitted_result_count")] int MinimumOmittedResultCount,
49244938
[property: JsonPropertyName("results_truncated")] bool ResultsTruncated,
49254939
[property: JsonPropertyName("evidence_paths")] List<SearchRecipeTopFileJsonResult> EvidencePaths,
49264940
[property: JsonPropertyName("evidence_path_count")] int EvidencePathCount,
4941+
[property: JsonPropertyName("evidence_path_count_authoritative")] bool EvidencePathCountAuthoritative,
4942+
[property: JsonPropertyName("evidence_path_count_lower_bound")]
4943+
[property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
4944+
int? EvidencePathCountLowerBound,
49274945
[property: JsonPropertyName("evidence_paths_returned_count")] int EvidencePathsReturnedCount,
49284946
[property: JsonPropertyName("evidence_paths_omitted_count")] int EvidencePathsOmittedCount,
4947+
[property: JsonPropertyName("evidence_paths_omitted_count_authoritative")] bool EvidencePathsOmittedCountAuthoritative,
4948+
[property: JsonPropertyName("evidence_paths_omitted_count_lower_bound")]
4949+
[property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
4950+
int? EvidencePathsOmittedCountLowerBound,
49294951
[property: JsonPropertyName("evidence_paths_truncated")] bool EvidencePathsTruncated,
49304952
[property: JsonPropertyName("labels")] List<string> Labels,
49314953
[property: JsonPropertyName("severity")] string Severity,

0 commit comments

Comments
 (0)