doclex-cli 0.1.1__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. doclex_cli-0.2.1/CHANGELOG.md +75 -0
  2. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/PKG-INFO +49 -13
  3. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/README.md +45 -9
  4. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/pyproject.toml +47 -47
  5. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/cli.py +542 -430
  6. doclex_cli-0.2.1/src/doclex/config.py +76 -0
  7. doclex_cli-0.2.1/src/doclex/engine.py +188 -0
  8. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/extraction/extractor.py +20 -1
  9. doclex_cli-0.2.1/src/doclex/index/bm25_index.py +200 -0
  10. doclex_cli-0.2.1/src/doclex/index/title_index.py +106 -0
  11. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/metadata/db.py +321 -250
  12. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/metadata/models.py +49 -4
  13. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/pipeline.py +172 -23
  14. doclex_cli-0.2.1/src/doclex/results.py +54 -0
  15. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/segmentation/segmenter.py +22 -13
  16. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/tests/conftest.py +75 -80
  17. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/tests/test_cli.py +143 -1
  18. doclex_cli-0.2.1/tests/test_config.py +64 -0
  19. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/tests/test_db.py +320 -290
  20. doclex_cli-0.2.1/tests/test_engine.py +130 -0
  21. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/tests/test_index.py +79 -4
  22. doclex_cli-0.2.1/tests/test_index_version.py +48 -0
  23. doclex_cli-0.2.1/tests/test_no_text.py +39 -0
  24. doclex_cli-0.2.1/tests/test_search_grouping.py +125 -0
  25. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/tests/test_segmentation.py +83 -3
  26. doclex_cli-0.2.1/tests/test_title_persistence.py +56 -0
  27. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/uv.lock +1639 -1639
  28. doclex_cli-0.1.1/CHANGELOG.md +0 -41
  29. doclex_cli-0.1.1/src/doclex/config.py +0 -43
  30. doclex_cli-0.1.1/src/doclex/index/bm25_index.py +0 -120
  31. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/.gitignore +0 -0
  32. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/CLAUDE.md +0 -0
  33. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/LICENSE +0 -0
  34. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/PLAN.md +0 -0
  35. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/README.es.md +0 -0
  36. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/benchmarks/beir_eval.py +0 -0
  37. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/benchmarks/compare.py +0 -0
  38. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/__init__.py +0 -0
  39. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/cache/__init__.py +0 -0
  40. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/cache/store.py +0 -0
  41. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/extraction/__init__.py +0 -0
  42. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/index/__init__.py +0 -0
  43. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/ingestion/__init__.py +0 -0
  44. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/ingestion/scanner.py +0 -0
  45. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/ingestion/watcher.py +0 -0
  46. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/metadata/__init__.py +0 -0
  47. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/registry.py +0 -0
  48. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/src/doclex/segmentation/__init__.py +0 -0
  49. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/tests/__init__.py +0 -0
  50. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/tests/test_cache.py +0 -0
  51. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/tests/test_extraction.py +0 -0
  52. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/tests/test_registry.py +0 -0
  53. {doclex_cli-0.1.1 → doclex_cli-0.2.1}/tests/test_scanner.py +0 -0
@@ -0,0 +1,75 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/).
7
+
8
+ ## [0.2.1] - 2026-11-08
9
+
10
+ ### Fixed
11
+
12
+ - Search scanned the whole segments table on every query to map index ids back to segments, so its cost grew with the corpus: on 25,000 segments that lookup took 94 ms of a 110 ms search. The lookup is now indexed and search takes 10-13 ms. Existing indexes pick this up when next opened; no reindex is needed.
13
+
14
+ ## [0.2.0] - 2026-09-14
15
+
16
+ Search returns **documents, not fragments**, cited by page. Upgrading requires a full reindex (`doclex index --project <name> --force`); Doclex warns when it finds an index built by an older version.
17
+
18
+ ### Added
19
+
20
+ - Results are grouped by document: a file matching in many places is listed once, with its best passages beneath it. Candidate passages are over-fetched so grouping still fills the result page.
21
+ - `doclex search --passages N` — how many excerpts to show per document.
22
+ - `doclex search --json` — machine-readable results with match offsets, for other frontends.
23
+ - File names and their containing folders are scored in their own BM25 index, so a document whose title states its subject outranks one that merely quotes the words.
24
+ - Document scores are scaled by how much of the query their best passage covers, each term weighted by its rarity in the corpus, so matching a rare term outweighs repeating a common one.
25
+ - `doclex status --all --filter no-text` — finds scanned PDFs, which index cleanly but hold no searchable text and can never be found.
26
+ - Index format versioning: `status` and `search` report an index built by an older Doclex instead of silently searching stale segments.
27
+
28
+ ### Changed
29
+
30
+ - PDFs are segmented by page, taken from the extractor's per-page text. Citations read "Page 12" instead of a chunk index that named nothing. The old form-feed split never ran, since the extractor emits none.
31
+ - Structural units are kept whole. The 1024-character limit is now a 8192-character safety valve for pathological input rather than the retrieval strategy; the reference corpus went from 3054 segments to 725.
32
+ - Snippets carry match offsets instead of pre-rendered markup, letting each frontend highlight its own way.
33
+ - Search no longer loads every segment in the corpus to map index ids back to segments.
34
+ - Search startup dropped from 2.1s to 190ms: the file-name index is persisted rather than rebuilt per process, and extraction is imported lazily.
35
+
36
+ ### Fixed
37
+
38
+ - Query terms matched as substrings when building snippets, so "me" landed inside "conforme" and steered results to the wrong passage. Terms now match as words.
39
+ - Overlapping matches ("ande" and "de" inside "ANDE") spliced broken markup and crashed the renderer.
40
+ - Document text containing square brackets was parsed as console markup.
41
+
42
+ ## [0.1.1] - 2026-08-02
43
+
44
+ ### Fixed
45
+
46
+ - Search result snippets now anchor on the window with the highest density of query-term matches instead of the first occurrence of the first term, so the snippet favors where the query actually clusters rather than an early, isolated mention.
47
+
48
+ ## [0.1.0] - 2026-08-02
49
+
50
+ First release — CLI MVP (Fase 1): local, embeddings-free document search over PDF, DOCX, XLSX, CSV, Markdown and TXT files.
51
+
52
+ ### Added
53
+
54
+ - Local document search CLI (`doclex`) — BM25 lexical search, no embeddings, no vector DB, no cloud.
55
+ - Ingestion pipeline: folder scanning with hash-based change detection, Kreuzberg-based text extraction, structural segmentation per file type (pages, sheets, sections, blocks), BM25X indexing, SQLite metadata store, and a local extraction cache.
56
+ - Real-time incremental updates via Watchdog file monitoring, plus full reconciliation on startup.
57
+ - `doclex add`/`projects`/`remove` — named project registry mapping folder paths to project names, rejecting overlapping/nested folders.
58
+ - `doclex index --project <name> [--file ...] [--force]` — incremental or forced full reindexing, with per-file targeting.
59
+ - `doclex search <query> --project <name> [--top] [--full]` — top-k BM25 search with citations (file, location, score, snippet).
60
+ - `doclex status --project <name> [--file ...] [--all] [--filter]` — corpus/index status, file listing, and per-file inspection.
61
+ - OCR support (`--ocr`) for scanned PDFs, and layout-aware segmentation via Kreuzberg.
62
+ - Recursive character-based splitter keeping segments within a 1024-character limit while preserving semantic boundaries.
63
+
64
+ ### Changed
65
+
66
+ - CLI output migrated to Rich (progress bars, tables) instead of plain text.
67
+ - Segmentation cache replaced with a unified page-index-style tree; split sub-segments now nest under their extracted heading parent.
68
+ - `list`/`reindex` merged into `index`/`status` flags; cache directory renamed from `.cache` to `.doclex`.
69
+ - Layout-aware PDF segmentation made the default (removed the `--precise` flag).
70
+
71
+ ### Fixed
72
+
73
+ - Ghost BM25 index entries left behind after a forced reindex (index and SQLite are now rebuilt fully in sync).
74
+ - `status`/`search` no longer create the index directory before a project has actually been indexed.
75
+ - Search result snippets now tokenize the query the same way BM25X tokenizes documents (splitting on punctuation, not just whitespace) and snap to word boundaries instead of cutting words mid-way.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: doclex-cli
3
- Version: 0.1.1
3
+ Version: 0.2.1
4
4
  Summary: Local document search — BM25 lexical search over PDFs, DOCX, XLSX, CSV, Markdown and TXT files. No embeddings, no vector DB, no cloud.
5
5
  Project-URL: Repository, https://github.com/EnPaiva93/doclex-cli
6
6
  Author-email: EnPaiva_93 <enpaiva93@gmail.com>
@@ -64,13 +64,21 @@ uv run doclex search "quarterly budget" --project mydocs
64
64
  ```
65
65
 
66
66
  ```
67
- [1] budget_2024.pdf — page 3 (score: 4.812)
68
- "...the quarterly budget was approved with a 12% increase in..."
69
-
70
- [2] notes.md — Planning (score: 2.107)
71
- "...need to revisit the budget assumptions before..."
67
+ [1] budget_2024.pdf 4.812
68
+ finance/2024
69
+ Page 3
70
+ ...the quarterly budget was approved with a 12% increase in...
71
+ Page 11
72
+ ...budget variance is reviewed at the end of each quarter...
73
+
74
+ [2] notes.md 2.107
75
+ planning
76
+ Planning
77
+ ...need to revisit the budget assumptions before...
72
78
  ```
73
79
 
80
+ Results are **documents, not fragments**: a file that matches in twenty places is listed once, with its best passages beneath it. Use `--passages` to show more excerpts per document, and `--json` to get the same results with match offsets for your own highlighting.
81
+
74
82
  ## Walkthrough
75
83
 
76
84
  A typical day-to-day flow, from an empty project to keeping it in sync:
@@ -91,11 +99,15 @@ Processing: 42 new, 0 modified, 0 deleted
91
99
  doclex search "termination clause" --project contracts
92
100
  ```
93
101
  ```
94
- [1] MSA_2023_AcmeCorp.pdf — Section 9.2 (score: 6.104)
95
- "...either party may terminate this Agreement upon 30 days written notice..."
96
-
97
- [2] vendor_agreement_v3.docx — Termination (score: 5.887)
98
- "...termination for convenience requires 60 days advance notice to..."
102
+ [1] MSA_2023_AcmeCorp.pdf 6.104
103
+ vendors/acme
104
+ Section 9.2
105
+ ...either party may terminate this Agreement upon 30 days written notice...
106
+
107
+ [2] vendor_agreement_v3.docx 5.887
108
+ vendors
109
+ Termination
110
+ ...termination for convenience requires 60 days advance notice to...
99
111
  ```
100
112
  ```bash
101
113
  # 4. check what's indexed
@@ -137,6 +149,21 @@ doclex index --project hr-policies
137
149
  doclex search "vacation days" --project hr-policies --top 5
138
150
  ```
139
151
 
152
+ ## Search latency
153
+
154
+ `uv run` re-resolves the environment on every invocation, which costs about two
155
+ seconds before Doclex runs at all. For searching, install the command once and
156
+ call it directly:
157
+
158
+ ```bash
159
+ uv tool install .
160
+ doclex search "quarterly budget" --project mydocs
161
+ ```
162
+
163
+ A search then costs roughly a second, most of it Python importing its own
164
+ dependencies. Repeated searches inside one process — a future server or MCP
165
+ frontend — cost about 70 ms.
166
+
140
167
  ## Projects
141
168
 
142
169
  Every command operates on a **registered project** — a name bound to a folder — instead of a raw `--folder` flag. This keeps folder resolution unambiguous and lets Doclex reject overlapping folders (a subfolder registered as its own project would silently duplicate indexing and caching):
@@ -156,7 +183,7 @@ doclex remove mydocs # deregisters and purges its cache/index
156
183
  | `doclex list` | List registered projects |
157
184
  | `doclex remove <name>` | Remove a project and purge its cache/index |
158
185
  | `doclex index --project <name>` | Scan and index (incremental by default) |
159
- | `doclex search <query> --project <name>` | Search and print results with citations |
186
+ | `doclex search <query> --project <name>` | Search and print matching documents with citations |
160
187
  | `doclex status --project <name>` | Show corpus/index status |
161
188
 
162
189
  Run `doclex <command> --help` for all flags and examples.
@@ -173,8 +200,17 @@ doclex index --project mydocs --file report.pdf --file notes.docx
173
200
  # more results, full segment text instead of a snippet
174
201
  doclex search "risk assessment" --project mydocs --top 20 --full
175
202
 
203
+ # show up to three matching passages per document
204
+ doclex search "risk assessment" --project mydocs --passages 3
205
+
206
+ # machine-readable output, with match offsets for highlighting
207
+ doclex search "risk assessment" --project mydocs --json
208
+
176
209
  # list every indexed file and its status
177
210
  doclex status --project mydocs --all --filter error
211
+
212
+ # find scanned PDFs: they index cleanly but hold no searchable text
213
+ doclex status --project mydocs --all --filter no-text
178
214
  ```
179
215
 
180
216
  ## Architecture
@@ -44,13 +44,21 @@ uv run doclex search "quarterly budget" --project mydocs
44
44
  ```
45
45
 
46
46
  ```
47
- [1] budget_2024.pdf — page 3 (score: 4.812)
48
- "...the quarterly budget was approved with a 12% increase in..."
47
+ [1] budget_2024.pdf 4.812
48
+ finance/2024
49
+ Page 3
50
+ ...the quarterly budget was approved with a 12% increase in...
51
+ Page 11
52
+ ...budget variance is reviewed at the end of each quarter...
49
53
 
50
- [2] notes.md — Planning (score: 2.107)
51
- "...need to revisit the budget assumptions before..."
54
+ [2] notes.md 2.107
55
+ planning
56
+ Planning
57
+ ...need to revisit the budget assumptions before...
52
58
  ```
53
59
 
60
+ Results are **documents, not fragments**: a file that matches in twenty places is listed once, with its best passages beneath it. Use `--passages` to show more excerpts per document, and `--json` to get the same results with match offsets for your own highlighting.
61
+
54
62
  ## Walkthrough
55
63
 
56
64
  A typical day-to-day flow, from an empty project to keeping it in sync:
@@ -71,11 +79,15 @@ Processing: 42 new, 0 modified, 0 deleted
71
79
  doclex search "termination clause" --project contracts
72
80
  ```
73
81
  ```
74
- [1] MSA_2023_AcmeCorp.pdf — Section 9.2 (score: 6.104)
75
- "...either party may terminate this Agreement upon 30 days written notice..."
82
+ [1] MSA_2023_AcmeCorp.pdf 6.104
83
+ vendors/acme
84
+ Section 9.2
85
+ ...either party may terminate this Agreement upon 30 days written notice...
76
86
 
77
- [2] vendor_agreement_v3.docx — Termination (score: 5.887)
78
- "...termination for convenience requires 60 days advance notice to..."
87
+ [2] vendor_agreement_v3.docx 5.887
88
+ vendors
89
+ Termination
90
+ ...termination for convenience requires 60 days advance notice to...
79
91
  ```
80
92
  ```bash
81
93
  # 4. check what's indexed
@@ -117,6 +129,21 @@ doclex index --project hr-policies
117
129
  doclex search "vacation days" --project hr-policies --top 5
118
130
  ```
119
131
 
132
+ ## Search latency
133
+
134
+ `uv run` re-resolves the environment on every invocation, which costs about two
135
+ seconds before Doclex runs at all. For searching, install the command once and
136
+ call it directly:
137
+
138
+ ```bash
139
+ uv tool install .
140
+ doclex search "quarterly budget" --project mydocs
141
+ ```
142
+
143
+ A search then costs roughly a second, most of it Python importing its own
144
+ dependencies. Repeated searches inside one process — a future server or MCP
145
+ frontend — cost about 70 ms.
146
+
120
147
  ## Projects
121
148
 
122
149
  Every command operates on a **registered project** — a name bound to a folder — instead of a raw `--folder` flag. This keeps folder resolution unambiguous and lets Doclex reject overlapping folders (a subfolder registered as its own project would silently duplicate indexing and caching):
@@ -136,7 +163,7 @@ doclex remove mydocs # deregisters and purges its cache/index
136
163
  | `doclex list` | List registered projects |
137
164
  | `doclex remove <name>` | Remove a project and purge its cache/index |
138
165
  | `doclex index --project <name>` | Scan and index (incremental by default) |
139
- | `doclex search <query> --project <name>` | Search and print results with citations |
166
+ | `doclex search <query> --project <name>` | Search and print matching documents with citations |
140
167
  | `doclex status --project <name>` | Show corpus/index status |
141
168
 
142
169
  Run `doclex <command> --help` for all flags and examples.
@@ -153,8 +180,17 @@ doclex index --project mydocs --file report.pdf --file notes.docx
153
180
  # more results, full segment text instead of a snippet
154
181
  doclex search "risk assessment" --project mydocs --top 20 --full
155
182
 
183
+ # show up to three matching passages per document
184
+ doclex search "risk assessment" --project mydocs --passages 3
185
+
186
+ # machine-readable output, with match offsets for highlighting
187
+ doclex search "risk assessment" --project mydocs --json
188
+
156
189
  # list every indexed file and its status
157
190
  doclex status --project mydocs --all --filter error
191
+
192
+ # find scanned PDFs: they index cleanly but hold no searchable text
193
+ doclex status --project mydocs --all --filter no-text
158
194
  ```
159
195
 
160
196
  ## Architecture
@@ -1,47 +1,47 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "doclex-cli"
7
- version = "0.1.1"
8
- description = "Local document search — BM25 lexical search over PDFs, DOCX, XLSX, CSV, Markdown and TXT files. No embeddings, no vector DB, no cloud."
9
- readme = "README.md"
10
- license = "MIT"
11
- authors = [
12
- { name = "EnPaiva_93", email = "enpaiva93@gmail.com" },
13
- ]
14
- requires-python = ">=3.12"
15
- dependencies = [
16
- "kreuzberg==4.8.2",
17
- "bm25x",
18
- "watchdog",
19
- "aiosqlite",
20
- "typer",
21
- ]
22
-
23
- [project.urls]
24
- Repository = "https://github.com/EnPaiva93/doclex-cli"
25
-
26
- [project.scripts]
27
- doclex = "doclex.cli:app"
28
-
29
- [project.optional-dependencies]
30
- dev = [
31
- "pytest",
32
- "pytest-asyncio",
33
- "ranx",
34
- ]
35
-
36
- [tool.pytest.ini_options]
37
- asyncio_mode = "auto"
38
- testpaths = ["tests"]
39
-
40
- [tool.hatch.build.targets.wheel]
41
- packages = ["src/doclex"]
42
-
43
- [dependency-groups]
44
- dev = [
45
- "ranx>=0.3.21",
46
- "tqdm>=4.0",
47
- ]
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "doclex-cli"
7
+ version = "0.2.1"
8
+ description = "Local document search — BM25 lexical search over PDFs, DOCX, XLSX, CSV, Markdown and TXT files. No embeddings, no vector DB, no cloud."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [
12
+ { name = "EnPaiva_93", email = "enpaiva93@gmail.com" },
13
+ ]
14
+ requires-python = ">=3.12"
15
+ dependencies = [
16
+ "kreuzberg==4.8.2",
17
+ "bm25x",
18
+ "watchdog",
19
+ "aiosqlite",
20
+ "typer",
21
+ ]
22
+
23
+ [project.urls]
24
+ Repository = "https://github.com/EnPaiva93/doclex-cli"
25
+
26
+ [project.scripts]
27
+ doclex = "doclex.cli:app"
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "pytest",
32
+ "pytest-asyncio",
33
+ "ranx",
34
+ ]
35
+
36
+ [tool.pytest.ini_options]
37
+ asyncio_mode = "auto"
38
+ testpaths = ["tests"]
39
+
40
+ [tool.hatch.build.targets.wheel]
41
+ packages = ["src/doclex"]
42
+
43
+ [dependency-groups]
44
+ dev = [
45
+ "ranx>=0.3.21",
46
+ "tqdm>=4.0",
47
+ ]