Skip to content

feat(search): add --snippet-max-length flag - #61

Merged
IlyaGusev merged 1 commit into
mainfrom
feat/search-snippet-max-length
Aug 12, 2026
Merged

feat(search): add --snippet-max-length flag#61
IlyaGusev merged 1 commit into
mainfrom
feat/search-snippet-max-length

Conversation

@IlyaGusev

Copy link
Copy Markdown
Collaborator

Summary

  • Add --snippet-max-length to keenable search. It maps to the API's snippet_max_length body param (accepted range 180-10000; the API validates and returns a structured error with exit 1 on out-of-range values).
  • The param rides in the JSON body, so both the daemon and direct HTTP paths get it with no daemon changes.

Tests

  • Verified live: cap 180 gives ~186-char average snippets; cap 5000 gives ~4850. Out-of-range 50 exits 1 with Invalid parameter.
  • New e2e tests: test_snippet_max_length (loose comparative bounds, since caps overshoot ~10% per-fragment) and test_snippet_max_length_out_of_range.
  • Full test_search.py e2e file passes against the live API (25 passed).
  • cargo clippy and release build pass.

🤖 Generated with Claude Code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add --snippet-max-length flag to search CLI and pass through to API

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 10-20 Minutes

Grey Divider

AI Description

• Add --snippet-max-length option to keenable search CLI.
• Include snippet_max_length in the search request JSON body when provided.
• Add e2e coverage for in-range behavior and out-of-range API error handling.
Diagram

graph TD
  A["keenable CLI"] --> B["src/main.rs (clap)"] --> C["src/commands/search.rs"] --> D{{"Search API"}}
  E["README.md"] --> B
  F["tests/e2e/test_search.py"] --> A
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Client-side range validation (clap value parser)
  • ➕ Fail fast without an API call for clearly invalid values
  • ➕ More precise, immediate CLI error messaging
  • ➕ Reduces noise in API error logs for obvious mistakes
  • ➖ Duplicates validation rules already enforced server-side
  • ➖ Needs careful alignment if the API range changes
2. Accept human-friendly units (e.g., 2k/5k) and normalize
  • ➕ Improves UX for common values
  • ➕ Still sends the same numeric API parameter
  • ➖ Adds parsing complexity and more edge cases
  • ➖ Potential ambiguity/locale concerns; not requested by API

Recommendation: The current pass-through approach is appropriate because the API already validates and returns a structured error. Consider adding optional client-side range validation via clap’s value parser as a follow-up to improve UX and avoid unnecessary requests, but it’s not required for correctness.

Files changed (4) +42 / -3

Enhancement (2) +19 / -3
search.rsForward snippet_max_length into search JSON body +4/-0

Forward snippet_max_length into search JSON body

• Extends the search command handler to accept an optional 'snippet_max_length' parameter and conditionally include it as 'snippet_max_length' in the request JSON body.

src/commands/search.rs

main.rsAdd --snippet-max-length flag to CLI and wire into search() +15/-3

Add --snippet-max-length flag to CLI and wire into search()

• Introduces a new '--snippet-max-length' clap argument on the Search subcommand, updates help examples, and passes the parsed value through to 'commands::search::search'.

src/main.rs

Tests (1) +22 / -0
test_search.pyAdd e2e tests for snippet length cap and out-of-range error +22/-0

Add e2e tests for snippet length cap and out-of-range error

• Adds an e2e test that compares average snippet length for low vs high caps, and a second test that asserts the API returns exit code 1 with a structured 'Invalid parameter' error for out-of-range values.

tests/e2e/test_search.py

Documentation (1) +1 / -0
README.mdDocument --snippet-max-length in search examples +1/-0

Document --snippet-max-length in search examples

• Adds a CLI usage example showing '--snippet-max-length 2000' and notes the accepted range (180-10000).

README.md

@IlyaGusev
IlyaGusev merged commit 5f1ecde into main Aug 12, 2026
12 checks passed
@IlyaGusev
IlyaGusev deleted the feat/search-snippet-max-length branch August 12, 2026 10:21
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Snippet test divide-by-zero 🐞 Bug ☼ Reliability
Description
test_snippet_max_length computes an average as sum(lens)/len(lens) without guarding against an empty
results list, which can raise ZeroDivisionError and fail the e2e suite with an unhandled exception.
Code

tests/e2e/test_search.py[R50-53]

+def test_snippet_max_length(kn):
+    def avg_snippet(data):
+        lens = [len(r.get("snippet") or "") for r in results_of(data)]
+        return sum(lens) / len(lens)
Evidence
The new test divides by the number of results without checking for zero. Separately, results_of()
only asserts that results is a list (it may be empty), and elsewhere in the same test file the
suite explicitly skips tests when results are empty—showing empty results are a handled/expected
possibility in this e2e setup.

tests/e2e/test_search.py[50-53]
tests/e2e/conftest.py[155-158]
tests/e2e/test_search.py[108-112]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`test_snippet_max_length` calculates an average snippet length by dividing by `len(lens)`. If the API returns zero results (an empty `results` list), this becomes a division by zero and the test errors out (not a clean assertion failure / skip).

## Issue Context
The e2e suite already has a pattern for handling vacuous assertions when search returns no results (`_non_empty_results` uses `pytest.skip`). The new snippet-length test should follow a similar approach or at least assert non-empty results before averaging.

## Fix Focus Areas
- tests/e2e/test_search.py[50-61]

### Suggested change
Update `avg_snippet()` to either:
- `assert results_of(data), "no results returned"` before computing the average, or
- `pytest.skip(...)` when results are empty (preferred if empty results are acceptable for live API variability).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread tests/e2e/test_search.py
Comment on lines +50 to +53
def test_snippet_max_length(kn):
def avg_snippet(data):
lens = [len(r.get("snippet") or "") for r in results_of(data)]
return sum(lens) / len(lens)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Snippet test divide-by-zero 🐞 Bug ☼ Reliability

test_snippet_max_length computes an average as sum(lens)/len(lens) without guarding against an empty
results list, which can raise ZeroDivisionError and fail the e2e suite with an unhandled exception.
Agent Prompt
## Issue description
`test_snippet_max_length` calculates an average snippet length by dividing by `len(lens)`. If the API returns zero results (an empty `results` list), this becomes a division by zero and the test errors out (not a clean assertion failure / skip).

## Issue Context
The e2e suite already has a pattern for handling vacuous assertions when search returns no results (`_non_empty_results` uses `pytest.skip`). The new snippet-length test should follow a similar approach or at least assert non-empty results before averaging.

## Fix Focus Areas
- tests/e2e/test_search.py[50-61]

### Suggested change
Update `avg_snippet()` to either:
- `assert results_of(data), "no results returned"` before computing the average, or
- `pytest.skip(...)` when results are empty (preferred if empty results are acceptable for live API variability).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant