Repository navigation
feat(search): inline field:value query syntax + matched-text highlighting (GH-157) - #290
Conversation
…ting (pm-ldr1, GH-157) Completes the GH-157 remainder for `pm search`: 1. Inline `field:value` query syntax — parseInlineQueryFilters / applyInlineQueryFilters extract tag:/status:/type:/priority: tokens from the query string (value runs to end-of-token so tag:area:search parses; first occurrence per field wins). Explicit --flags win over a conflicting inline token, surfacing search_inline_filter_ignored:<field>:flag_takes_precedence. This complements the bootstrap parseBareKeyValueToken rewriter (UNQUOTED argv tokens only) by covering quoted multi-word CLI queries AND the pm_search MCP path where the whole query arrives as one string. 2. --highlight — per-field matched-text snippets (highlights:[{field, snippet}], «…» markers, windowed ±60 chars with … ellipsis), off by default for token efficiency; added to compact/fields projections when set. scoreDocument and the highlighter now share a single SEARCHABLE_FIELD_BUILDERS source of truth. Wiring: register-list-query, registration-helpers normalizeSearchOptions, SEARCH_FLAG_CONTRACTS, SEARCH_CONTRACT_PARAMETER_KEYS, MCP tool-parameter-tables (+description), zsh+fish completions, contracts golden file, COMMANDS.md. 100/100/100/100 coverage, 4607 tests green, static + contracts gates pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WPY4oWVsYs2FjZ6shgUyrL
|
@coderabbitai full review |
|
@gemini-code-assist review |
|
@codex review |
✅ Action performedFull review finished. |
|
To use Codex here, create a Codex account and connect to github. |
|
Warning Review limit reached
More reviews will be available in 38 minutes and 13 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (15)
📝 WalkthroughWalkthroughAdds two features to Changespm search: Inline Filter Syntax and --highlight Snippets
Agent PM Tracking Records
Sequence Diagram(s)sequenceDiagram
participant CLI as CLI / MCP caller
participant register as register-list-query
participant normalize as normalizeSearchOptions
participant runSearch
participant parseInline as parseInlineQueryFilters
participant applyInline as applyInlineQueryFilters
participant buildHL as buildHitHighlights
CLI->>register: pm search "tag:auth login" --highlight
register->>normalize: options with highlight=true
normalize-->>runSearch: SearchOptions{highlight:true}
runSearch->>parseInline: rawQuery="tag:auth login"
parseInline-->>runSearch: inlineMap={tag:"auth"}, residual="login"
runSearch->>applyInline: inlineMap + SearchOptions
applyInline-->>runSearch: merged options + inlineWarnings
Note over runSearch: score and rank documents
runSearch->>buildHL: limited hits + query tokens
buildHL-->>runSearch: SearchHitHighlight[] per hit
runSearch-->>CLI: hits with highlights + warnings
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request implements structured inline query syntax (parsing tag:, status:, type:, and priority: tokens directly inside the query string) and matched-text highlighting via --highlight for pm search. Feedback highlights that the explicit --priority option is missing from CLI registration, normalization, and contracts, making its precedence behavior impossible to trigger. Additionally, buildHitHighlights should lazily evaluate field builders for matched fields to improve performance, and tokens in markTokenRuns should be sorted by length descending to prevent shorter prefixes from eagerly matching and disrupting longer highlights.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| // colons (e.g. `tag:area:search`), so only the FIRST colon delimits field from | ||
| // value. Explicit --field flags take precedence over an inline token (see | ||
| // resolveInlineQueryFilters). | ||
| const INLINE_QUERY_FILTER_FIELDS = ["tag", "status", "type", "priority"] as const; |
There was a problem hiding this comment.
The inline query syntax includes priority as a recognized filter field, and the documentation in docs/COMMANDS.md explicitly references using --priority 1 as an explicit flag override. However, the explicit --priority option is completely missing from the CLI registration, option normalization, and SDK/MCP contracts. Specifically:
registerListQueryCommandsinsrc/cli/register-list-query.tsdoes not register.option("--priority <value>")for thesearchcommand.normalizeSearchOptionsinsrc/cli/registration-helpers.tsdoes not handle or preserve thepriorityoption.SEARCH_FLAG_CONTRACTSandSEARCH_CONTRACT_PARAMETER_KEYSinsrc/sdk/cli-contracts.tsdo not include--priority/priority.PM_TOOL_PARAMETER_PROPERTIESandPM_TOOL_PARAMETER_METADATAinsrc/sdk/cli-contracts/tool-parameter-tables.tsdo not definepriority.
This prevents users from using --priority as an explicit flag (which will throw an unknown option error on the CLI), prevents MCP clients from using priority as a search parameter, and makes the documented precedence behavior for priority impossible to trigger. Please add the missing CLI option, normalization, and contract definitions for priority to ensure consistency with tag, status, and type.
References
- Ensure MCP tool schemas and enums strictly align with the underlying CLI command contracts and implementation.
| function buildHitHighlights(document: ItemDocument, matchedFields: string[], tokens: string[]): SearchHitHighlight[] { | ||
| const fieldValues = new Map(SEARCHABLE_FIELD_BUILDERS.map((builder) => [builder.name, builder.value(document)])); | ||
| const highlights: SearchHitHighlight[] = []; | ||
| for (const field of matchedFields) { | ||
| const value = fieldValues.get(field); | ||
| if (value === undefined) { | ||
| continue; | ||
| } | ||
| const snippet = highlightFieldSnippet(value, tokens); | ||
| if (snippet !== null) { | ||
| highlights.push({ field, snippet }); | ||
| } | ||
| } | ||
| return highlights; | ||
| } |
There was a problem hiding this comment.
Evaluating all 12 field builders in SEARCHABLE_FIELD_BUILDERS for every single search hit is highly inefficient, especially since fields like comments, notes, learnings, and plan perform expensive array mapping, corpus building, and string joining. Since matchedFields typically contains only a few fields, we should lazily evaluate only the builders for the fields that actually matched.
| function buildHitHighlights(document: ItemDocument, matchedFields: string[], tokens: string[]): SearchHitHighlight[] { | |
| const fieldValues = new Map(SEARCHABLE_FIELD_BUILDERS.map((builder) => [builder.name, builder.value(document)])); | |
| const highlights: SearchHitHighlight[] = []; | |
| for (const field of matchedFields) { | |
| const value = fieldValues.get(field); | |
| if (value === undefined) { | |
| continue; | |
| } | |
| const snippet = highlightFieldSnippet(value, tokens); | |
| if (snippet !== null) { | |
| highlights.push({ field, snippet }); | |
| } | |
| } | |
| return highlights; | |
| } | |
| function buildHitHighlights(document: ItemDocument, matchedFields: string[], tokens: string[]): SearchHitHighlight[] { | |
| const highlights: SearchHitHighlight[] = []; | |
| for (const field of matchedFields) { | |
| const builder = SEARCHABLE_FIELD_BUILDERS.find((b) => b.name === field); | |
| if (builder === undefined) { | |
| continue; | |
| } | |
| const value = builder.value(document); | |
| const snippet = highlightFieldSnippet(value, tokens); | |
| if (snippet !== null) { | |
| highlights.push({ field, snippet }); | |
| } | |
| } | |
| return highlights; | |
| } |
| function markTokenRuns(text: string, tokens: string[]): string { | ||
| const escaped = tokens.filter((token) => token.length > 0).map(escapeRegExpLiteral); | ||
| if (escaped.length === 0) { | ||
| return text; | ||
| } | ||
| const pattern = new RegExp(escaped.join("|"), "gi"); | ||
| return text.replace(pattern, (match) => `${HIGHLIGHT_OPEN}${match}${HIGHLIGHT_CLOSE}`); | ||
| } |
There was a problem hiding this comment.
When joining tokens with | in a regular expression, shorter tokens that are prefixes of longer tokens (e.g., auth and authority) can eagerly match first and prevent the longer token from being fully highlighted (resulting in «auth»ority instead of «authority»). Sorting the escaped tokens by length in descending order ensures that the longest possible match is always preferred.
function markTokenRuns(text: string, tokens: string[]): string {
const escaped = tokens
.filter((token) => token.length > 0)
.map(escapeRegExpLiteral)
.sort((a, b) => b.length - a.length);
if (escaped.length === 0) {
return text;
}
const pattern = new RegExp(escaped.join("|"), "gi");
return text.replace(pattern, (match) => HIGHLIGHT_OPEN + match + HIGHLIGHT_CLOSE);
}|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/COMMANDS.md`:
- Around line 143-153: The section header for "Inline filter syntax and
matched-text highlighting (GH-157)" in COMMANDS.md is missing a tracker
reference that is present in the corresponding CHANGELOG.md entry. Add the
tracker reference (matching the one used in CHANGELOG.md) to the section header
in COMMANDS.md to align the documentation with the changelog and follow the
established pattern for new documentation sections that have associated pm
items.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: d2ee1ed2-04b7-4b62-aaa1-bfa10df607c0
📒 Files selected for processing (15)
.agents/pm/features/pm-ldr1.toon.agents/pm/history/pm-28cf.jsonl.agents/pm/history/pm-ldr1.jsonl.agents/pm/issues/pm-28cf.toonCHANGELOG.mddocs/COMMANDS.mdsrc/cli/commands/completion.tssrc/cli/commands/search.tssrc/cli/register-list-query.tssrc/cli/registration-helpers.tssrc/sdk/cli-contracts.tssrc/sdk/cli-contracts/tool-parameter-tables.tstests/fixtures/contracts/full.jsontests/unit/cli/registration-helpers.spec.tstests/unit/commands/search-command.spec.ts
- markTokenRuns: sort tokens length-descending before building the regex alternation so a longer token (authority) is never shadowed by a prefix token (auth) — fixes «auth»ority -> «authority» (Gemini). - buildHitHighlights: resolve matched fields via a module-level name->builder map and evaluate ONLY the matched fields' extractors, instead of materializing all twelve per hit (Gemini perf). - docs(COMMANDS.md): add pm-ldr1 tracker reference to the new section header (CodeRabbit). Declined Gemini's "explicit --priority missing" finding: --priority is already registered (register-list-query), normalized (normalizeSearchOptions), and in SEARCH_FLAG_CONTRACTS; precedence verified live (flag wins, warning emitted) — false positive. 100/100/100/100 coverage, 81 search tests + full suite green, static + contracts gates pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WPY4oWVsYs2FjZ6shgUyrL
|
Thanks for the reviews — pushed 936a199 addressing the feedback:
Coverage remains 100/100/100/100; static + contracts gates pass. @coderabbitai full review |
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
To use Codex here, create a Codex account and connect to github. |
|
✅ Action performedFull review finished. |
Documents that the 2026-06-18 Auto Release failure ran pre-fix code (push-retry landed in bf35af2 hours later), the current-main dry-run is fully green, the telemetry stack is healthy (15k events/24h), and Sentry shows no new errors from PR #290. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WPY4oWVsYs2FjZ6shgUyrL
Summary
Completes the GH-157 remainder for
pm search(the threshold/exact/count parts shipped in #238). Both additions reduce agent round-trips and token cost — directly serving project management = context management.1. Inline
field:valuequery syntaxEmbed filter tokens directly in the query string; recognized fields are
tag:,status:,type:,priority:. The value runs to the end of the token, so colon-bearing values liketag:area:searchparse correctly. Remaining words drive keyword/semantic matching.--flagalways wins over a conflicting inline token; the override is surfaced as asearch_inline_filter_ignored:<field>:flag_takes_precedencewarning (never silent).parseBareKeyValueTokenrewriter already handles unquoted argv tokens (tag:x→--tag x) on the CLI. The new in-runSearchparser covers the gap: quoted multi-word CLI queries and thepm_searchMCP path, where the entire query arrives as a single string (no argv normalization). Both yield identical applied filters.pm listfor pure filtering.2.
--highlightmatched-text snippetsOff by default for token efficiency. Emits per-field
highlights: [{ field, snippet }]with matching token runs wrapped in«…»and a…ellipsis where the field text was windowed (±60 chars around the first match).scoreDocumentand the highlighter now share oneSEARCHABLE_FIELD_BUILDERSsource of truth, so the highlighter can only ever produce snippets for fields the scorer actually inspected.Wiring
register-list-query(--highlight+ description),registration-helpers(normalizeSearchOptions),SEARCH_FLAG_CONTRACTS,SEARCH_CONTRACT_PARAMETER_KEYS, MCPtool-parameter-tables(property + description), zsh + fish completions, contracts golden file,docs/COMMANDS.md.Verification
pnpm buildcleanpnpm contracts:checkcurrent ·pnpm quality:staticpassed ·changelog:pm:checkno driftpm tracking
Closes pm-ldr1 (parent epic pm-ydp6 Search & semantic retrieval). Triage note added to pm-28cf (GH-244 — already resolved by #254; persist-on-read normalization declined).
🤖 Generated with Claude Code
https://claude.ai/code/session_01WPY4oWVsYs2FjZ6shgUyrL