refactor(server): decompose semantic.py and cookbook.py - #1442
Conversation
Part 3 of 3 splitting the S3776 complexity work (was #1437). Unlike parts 1 and 2, this one is not purely mechanical — it is the only part that changes code shape rather than just where functions are defined, which is why it is separated. **semantic.py** (cognitive complexity 80). `nc_semantic_search` was an 850-line function, so lifting it to module level alone would have relocated the finding onto the tool rather than fixing it. Five cohesive pieces it was carrying are now their own functions: candidate retrieval, result mapping, context expansion, the ADR-027 date-bound guards, and the rerank pass. Each is named for what it does and documented with why it moved — for nesting, not reuse. **cookbook.py** (153, the worst of the five). The lift alone was not enough here either: `nc_cookbook_create_recipe` (16) and `nc_cookbook_update_recipe` (20) were over threshold on their own, so stopping at extraction would have traded one finding for two new ones. Ten sequential `if field:` assignments became one field-mapping table, and the HTTP-status if/elif chains a status->message dict. The create/update guard difference is preserved and now stated explicitly rather than left implicit in two near-identical blocks: create keeps only truthy values, so an omitted optional is simply not sent, while update keeps anything that is not None, so passing an empty string *clears* a field rather than being ignored. That distinction was the easiest thing to lose in this refactor. Result across both files: max cognitive complexity 13, from 153 and 80. Refs card 1203. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iCzZAuCMsZS7UJYfy3htd
|
Claude finished @cbcoutinho's task in 3m 22s —— View job Review of PR #1442 — decompose
|
Review nit on #1442. `_expand_results_with_context` took `client` and `accessible_owners` unannotated: as a nested closure they were captured from the enclosing scope and carried no annotation of their own, and lifting them to parameters is exactly where that becomes a gap. CLAUDE.md requires typing all function signatures. `client: "NextcloudClient"` behind TYPE_CHECKING, matching the forward-ref pattern already used in server/webdav.py and server/deck.py (the import is cycle-guarded there for the same reason). `accessible_owners: list[str]`, verified against AccessibleScope.owners in search/access_filter.py rather than taken from the review. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iCzZAuCMsZS7UJYfy3htd
|
Round-1 finding addressed in
You were right about both the gap and the types, and I verified rather than took them: Worth noting why it happened, since it's a general hazard of this kind of refactor: as a nested closure those two were captured from the enclosing scope and carried no annotation of their own. Lifting them to parameters is precisely the point where that becomes a signature gap — and nothing flags it, because On your tooling note — Also: your inability to run |
…signatures Round-2 review on #1442 caught a real loss, not just a style point: the extraction dropped the `TODO(ADR-019): expose VERIFICATION_OVERFETCH` comment that lived on the `doc_types is None` branch, and `grep` confirms it existed nowhere else in the tree afterwards. Restored in `_retrieve_candidates`, with the surrounding rationale it was attached to (why 2x, what high ghost density costs, that the index self-heals via lazy eviction). That is the failure mode worth naming for this whole refactor: a moved block keeps its code but can silently shed the comments explaining it, and nothing — not ty, not the tests, not Sonar — notices. Also tightens the loose annotations the extraction introduced: `list[Any]` -> `list[SearchResult]` on _retrieve_candidates / _rerank_pool / _to_semantic_results / _log_top_results, and `search_algo: Any` -> `BM25HybridSearchAlgorithm`. Checked against the convention rather than taken from the review: rerank_results, filter_by_relevance and verify_search_results all type these as `list[SearchResult]`, and this file already imports the algorithm class. `settings: Any` is left as-is deliberately — it is a dynaconf object, not a declared type, and the rest of the file passes it around untyped for that reason. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019iCzZAuCMsZS7UJYfy3htd
|
Round-2 findings addressed in
Left as-is:
On your verification note: SonarCloud's gate passing with 0 new issues is the right thing to have leaned on, and the |
|



Part 3 of 3, splitting the SonarCloud S3776 work that was #1437. Stacked on
#1441.
This is the part that isn't mechanical. Parts 1 and 2 only change where
functions are defined; this one changes code shape, which is why it is separated
— it deserves the closest reading of the three.
semantic.py— 80nc_semantic_searchwas an 850-line function, so lifting it to module levelalone would have relocated the finding onto the tool rather than fixing it.
Five cohesive pieces it was carrying are now their own functions:
_retrieve_candidatesdoc_types is None/ per-type dispatch + merge_to_semantic_resultsfile_urlternary sat 5 levels deep)_expand_results_with_contextinclude_contextfan-out and its task group_parse_modified_bounds_rerank_pool_fetch_limit/_log_top_resultsEach is documented with why it moved (nesting, not reuse), so the next reader
doesn't mistake them for shared utilities.
cookbook.py— 153, the worst of the fiveThe lift alone was not enough here either:
nc_cookbook_create_recipe(16)and
nc_cookbook_update_recipe(20) were over threshold on their own, sostopping at extraction would have traded one finding for two new ones.
Ten sequential
if field:assignments became one field-mapping table, and theHTTP-status
if/elifchains a status→message dict.The one thing worth reviewing closely: the create/update guard difference is
preserved and now stated explicitly rather than left implicit in two
near-identical blocks —
That asymmetry was the easiest thing to lose in this refactor, and losing it
would silently change what an empty-string update does.
Result
Max cognitive complexity across both files: 13, from 153 and 80.
Verification
ruff,ruff format,tyclean. Tests run in CI per the repo owner'sinstruction. The three parts together are byte-identical to the #1437 tree that
passed the full 28-check matrix and on which SonarCloud reported "No issues
found".
Refs Deck card 1203.
This PR was generated with the help of AI, and reviewed by a Human
🤖 Generated with Claude Code
https://claude.ai/code/session_019iCzZAuCMsZS7UJYfy3htd