feat: make the library the source of truth; CLI becomes a presentation surface - #172
feat: make the library the source of truth; CLI becomes a presentation surface#172nwaughachukwuma wants to merge 18 commits into
Conversation
… re-exports - Add mm/results.py (WcStats, GrepMatch, CatResult) and mm/stats.py (compute_wc) - Context.wc/peek delegate to library compute; wc_cmd is now presentation-only - Re-export FileMetadata, list_strategies/list_encoders_detail, list_pipelines/print_pipeline, config/profile readers from mm - Add data-returning list_pipelines()/print_pipeline() beside the Rich printers Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
- New mm/cat_utils/extract.py owns the full encode→generate pipeline (passthrough detection, caching, fast/accurate dispatch, dry-run). Returns a typed CatResult; commands/cat.py + semantic.py call it. - Context.cat gains mode=metadata|fast|accurate (+ DI via opts=) and Context.to_md(fast|accurate) now works (no more NotImplementedError). - Context.filter gains name/ignore_case/depth/sort/reverse/limit; find_cmd delegates all row selection to the library. - Context.print_tree implements paths/kind/flat/hybrid layouts (Python/Rich). - Repoint whitebox tests at the relocated library functions. Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
- New mm/search.py owns smart-case compilation, text/document line scanning, and FTS + semantic chunk merge. Returns a typed GrepResult. - Context.grep gains ext/ignore_case/context_lines/count/semantic/pre_index and DI hooks (files=, regex=) so the CLI passes its deduped file list and pre-compiled pattern in (no redundant work). - grep_cmd becomes a thin surface: collect files, compile, call search_content, render. - results.py: add GrepResult aggregate + GrepMatch.context. Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
- MmDatabase.list_tables() is the source of truth for 'mm sql --list-tables'; sql_cmd renders it. Adds mm.store.list_tables() convenience. - Re-export list_tables and the typed result objects (WcStats, GrepMatch, GrepResult, CatResult) from the top-level mm namespace. Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
- Context.to_records() exports plain dict records for both directory-scan and incremental contexts so callers persist to any backend they choose. - Context.save() is reframed as the mm CLI's own SQLite workflow (delegating to MmDatabase), not a library mandate. Incremental save() now points users to to_records() instead of promising a future built-in DB writer. Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
⚙️ Control Options:
|
There was a problem hiding this comment.
Code Review
This pull request refactors the codebase to decouple core computational logic from the CLI presentation layer, moving capabilities like content extraction, content search, and word count aggregation into library modules. It introduces structured result types in python/mm/results.py and enhances the Context class with new APIs and tree layout options. Feedback on the changes highlights two issues: a potential ValueError and infinite recursion loop in _build_tree_view when handling paths across different roots, and an inconsistency in _scan_files where absolute paths are used instead of relativized paths, leading to duplicate keys in the search results.
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.
- grep_cmd now passes ignore_case through so the semantic-search hint command includes --ignore-case (Devin Review). - _build_tree_view guards os.path.commonpath against ValueError (paths on different roots) and adds a filesystem-root recursion base case (gemini). Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
|
Hi Devin, please address all the comments from Gemini and Devin bots. |
Direct line-scan matches now key on a root-relative path (falling back to the raw path when outside root), so they collapse onto the same file_counts entry as the relativized FTS/semantic chunk hits instead of splitting into duplicate absolute+relative keys (gemini). Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
…ary-source-of-truth
Hi Devin, the above is wrong. A true decoupling should remove the CLI code from the library/context. We don't want to burden anyone else using the library with CLI related stuff. The library/context should not be aware of the CLI internals. The CLI should call the library/context, get the required data or record and write to its DB self contained within the CLI surface. In summary, all users of the library surface should export with While you've done much of this, please self-review the code end-to-end based to ensure that the library/context surface is truly self contained, independent and completely decoupled from the CLI surface. I hope this is clear? |
The library no longer owns persistence: Context exports storage-agnostic records via to_records() and the caller owns the storage backend. Removes Context.save()/resolve()/get() DB resolution and the _db field; refs are minted in-memory per context. MmDatabase gains upsert_records()/resolve() as the store-side counterpart, and the mm CLI (commands/sql.py) writes via that workflow. Tests updated to the caller-owned persist/resolve flow via a new persist_ctx fixture. Also pin the ty-check pre-commit hook to the project ty (uv run --frozen) instead of unpinned uvx, so the hook matches make/CI and stops breaking on new ty releases. Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
|
Done — the library/ What changed
End-to-end self-review result
So the invariant now holds: every consumer of the library exports with Also pinned the All 1175 tests pass; lint + typecheck green. |
Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
…ntext methods callable) Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
…e, tree layouts, save) Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
…uth surface - drop stale deferred-save()/Context.get-classmethod/Context.resolve refs - document caller-owned persistence via to_records() + MmDatabase().resolve() - add CHANGELOG entries; list new mm.results/peek/stats/search modules in layout Co-Authored-By: Chukwuma Nwaugha <nwaughac@gmail.com>
…ary-source-of-truth
…ary-source-of-truth
Summary
Restructures
mmso the library (mm.Context+ support modules) owns all core computation and the CLI becomes a thin presentation surface (option parsing, stdin/pipe resolution, loading/success/error messages, Rich rendering, exit codes). This closes the documented feature-parity gaps betweenmm <cmd>and the public library surface, and makes the library self-contained / pluggable into any caller (notebook, web service, other agent) — not just the CLI.The contract for every command is now:
Two hard constraints from the design discussion are honored:
Performance via dependency injection, not regression. Hot-path commands keep their lean, pyarrow-free fast path by passing pre-built heavy objects into the library instead of having the library rebuild them. Library methods accept optional injected objects and construct a default only when the caller passes
None:Verified:
mm find … --format jsonstill imports 0 pyarrow / rich modules on the fast path (checked withpython -X importtime).Persistence is fully decoupled from the library. The core library never mandates a DB and
Contexthas no DB awareness at all.Context.save()and the_dbfield were removed; the only persistence entry point isContext.to_records(), which exports plain dict records (directory-scan and incremental) so each caller writes to whatever backend it wants. The mm CLI writes those records to its own SQLite store entirely within the CLI/store surface:What moved into the library
mm.stats.compute_wc→WcStatsWcStatsmm.peek.FileMetadata.from_path(alsoContext.peek)mm.cat_utils.extract.extract→CatResult;Context.cat(mode=…),Context.to_md(mode=…)extractContext.filter(...),Context.print_tree(layout=…)mm.search.search_content→GrepResult;Context.grep(...)MmDatabase.list_tables()/mm.store.list_tables()New typed result objects live in
mm/results.py(WcStats,GrepMatch,GrepFileCount,GrepResult,CatResult) — each withto_dict()so every--formatserializes identically. These pluslist_tablesare re-exported from the top-levelmmnamespace.config/profile/benchwere already thin overmm.config/mm.profile/ the benchmark harness and stay as-is (benchis intentionally CLI-only/operational).One intentional behavior change:
Context.grepis now smart-case (matching the CLI's single code path), withregex=/ uppercase-pattern escape hatches.Using the library
The library is the source of truth, so anything the CLI does is reproducible from Python. The same
Contextpowers the CLI, a web service, and ad-hoc scripts.1. Inside the CLI (presentation only)
The CLI parses options, calls the library, renders the typed result, and owns persistence — it never computes anything itself. Sketch of
mm grep:Persistence (
mm sql,--pre-index) is also caller-owned, kept entirely in the CLI/store surface:2. In a FastAPI app
A web service can expose the same capabilities. Typed results have
to_dict(), so they serialize directly to JSON. The service owns its own storage (here: none — pure compute).3. Generic Python (scripts, notebooks, agents)
Directory-scan mode for analyzing a corpus, plus DataFrame interop:
Incremental role-aware mode for building a multimodal prompt and calling a VLM:
Caller-owned persistence (any backend you like — example uses mm's SQLite store):
Link to Devin session: https://app.devin.ai/sessions/206deaa0c2c447ac9dc579ec4b5aa9ca
Requested by: @nwaughachukwuma