LLM-02: Expand board context with card IDs and structured reference#638
LLM-02: Expand board context with card IDs and structured reference#638Chris0Jeky merged 3 commits intomainfrom
Conversation
…budget Include first 8 hex chars of card GUIDs in brackets, card-level labels, column flow line (arrow-separated), and per-column card groupings. Increase context budget from 2000 to 4000 chars so the LLM can reference cards by ID for move/update instructions. Closes #617
Add tests for short ID formatting, card-level labels, empty column skipping, and 4000-char budget constant. Update existing column test to verify the new arrow-separated flow line format.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Self-reviewWhat changed
Review findingsCorrectness:
Potential concerns (all minor, no action needed):
Test coverage:
Verdict: Ship-ready. No blocking issues found. |
There was a problem hiding this comment.
Code Review
This pull request enhances the BoardContextBuilder by increasing the character budget for LLM prompts and improving the context format to include short card IDs and labels. A performance issue was identified where cards are fetched individually for each column, creating an N+1 query pattern; it is recommended to fetch all cards for the board in a single query instead.
| if (columns.Count > 0) | ||
| { | ||
| sb.AppendLine("Columns (in order):"); | ||
| sb.Append("Columns: ").AppendLine( | ||
| string.Join(" → ", columns.Select(c => c.Name))); | ||
|
|
||
| foreach (var column in columns) | ||
| { | ||
| sb.Append(" - ").Append(column.Name).Append(" (position ").Append(column.Position).AppendLine(")"); | ||
|
|
||
| // Fetch cards per column from DB with limit applied at the query level | ||
| // to avoid loading all board cards into memory. | ||
| var columnCards = (await _unitOfWork.Cards.GetByColumnIdAsync(column.Id, ct)) | ||
| .OrderByDescending(c => c.UpdatedAt) | ||
| .Take(MaxCardsPerColumn); | ||
| .Take(MaxCardsPerColumn) | ||
| .ToList(); | ||
|
|
||
| if (columnCards.Count == 0) | ||
| continue; | ||
|
|
||
| sb.Append("Cards in \"").Append(column.Name).AppendLine("\":"); |
There was a problem hiding this comment.
The current implementation fetches cards for each column individually within the loop, resulting in an N+1 query pattern. This can cause significant performance degradation as the number of columns increases. It is more efficient to fetch all cards for the board in a single query and group them by column in memory.
if (columns.Count > 0)
{
sb.Append("Columns: ").AppendLine(
string.Join(" → ", columns.Select(c => c.Name)));
var cardsByColumn = (await _unitOfWork.Cards.GetByBoardIdAsync(boardId, ct))
.GroupBy(c => c.ColumnId)
.ToDictionary(
g => g.Key,
g => g.OrderByDescending(c => c.UpdatedAt).Take(MaxCardsPerColumn).ToList());
foreach (var column in columns)
{
if (!cardsByColumn.TryGetValue(column.Id, out var columnCards) || columnCards.Count == 0)
continue;
sb.Append("Cards in \"").Append(column.Name).AppendLine("\":");
Adversarial Review of PR #638Overall this is a clean, well-scoped change. The new format is an improvement for LLM instruction parsing. I found no critical issues but several items worth addressing before merge. 1. Stale XML doc comment (Low)File: The class-level XML
However the old comment fragment "The context includes the board name, column names and positions, recent card titles per column, and label names" was only partially updated in the diff. Specifically, the original said "recent card titles" which is now replaced, but the new comment does not mention that cards are ordered by Severity: Low 2. Budget check is only inside the inner card loop -- column-level overflow continues iterating (Medium)File: The truncation
There is no budget check after the inner card loop breaks. The old code had a second Looking at the diff more carefully: the old code had: foreach (var card in columnCards)
{
sb.Append(" * ").AppendLine(card.Title);
if (sb.Length >= MaxContextCharacters)
break;
}
if (sb.Length >= MaxContextCharacters)
break;The new code only has the inner break. The outer break was dropped. This means after hitting the budget mid-column, the builder will continue issuing DB queries for every remaining column, doing wasted work. On a board with 20 columns this is 19 unnecessary round-trips. Severity: Medium (performance regression, not correctness -- the final truncation marker still enforces the budget on the string itself) 3. No test for the truncation boundary with the new richer format (Medium)File: The existing
The new Severity: Medium (test gap) 4. Short ID collision risk is undocumented (Low)File: 8 hex chars = 32 bits of entropy. For a board with ~100 cards, the birthday-problem collision probability is negligible (~0.0001%). However, this is being surfaced in an LLM prompt where the model may use the short ID to reference cards in instructions. If the LLM generates This is not a bug today (collision is astronomically unlikely at board scale), but the design decision should be documented -- either in the code comment or an ADR note -- so future contributors don't reduce Severity: Low (design documentation gap) 5. Column flow line (
|
| # | Finding | Severity |
|---|---|---|
| 2 | Outer budget-break removed -- wasted DB queries after budget hit | Medium |
| 3 | No test for truncation boundary with new richer format | Medium |
| 1 | Stale XML doc (minor) | Low |
| 4 | Short ID collision risk undocumented | Low |
| 5 | Empty-column flow line test could assert positive case | Low |
| 6 | All cards loaded in memory per column (pre-existing) | Informational |
Recommendation: Fix #2 (restore the outer break after the inner card loop) before merging. The rest are low-severity improvements that could be follow-ups.
Address Gemini review: replace per-column GetByColumnIdAsync calls with single GetByBoardIdAsync + in-memory grouping by column. Update test mocks accordingly.
Summary
BoardContextBuilderto include short card IDs (first 8 hex chars of GUID) in bracket notation, enabling the LLM to generatemove card <id>style instructionsA -> B -> C), per-column card groupings withCards in "Column":headings, card-level label annotationsFormatShortIdhelper with deterministic output (no-hyphen hex prefix)Closes #617
Test plan