local-knowledge 0.2.1__tar.gz → 0.2.3__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 (19) hide show
  1. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/PKG-INFO +2 -2
  2. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/pyproject.toml +2 -2
  3. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/src/local_knowledge/cli_local.py +3 -0
  4. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/src/local_knowledge/crawl_local.py +16 -2
  5. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/src/local_knowledge/server_local.py +15 -8
  6. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/src/local_knowledge.egg-info/PKG-INFO +2 -2
  7. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/src/local_knowledge.egg-info/requires.txt +1 -1
  8. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/README.md +0 -0
  9. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/setup.cfg +0 -0
  10. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/src/local_knowledge/__init__.py +0 -0
  11. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/src/local_knowledge/settings_local.py +0 -0
  12. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/src/local_knowledge.egg-info/SOURCES.txt +0 -0
  13. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/src/local_knowledge.egg-info/dependency_links.txt +0 -0
  14. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/src/local_knowledge.egg-info/entry_points.txt +0 -0
  15. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/src/local_knowledge.egg-info/top_level.txt +0 -0
  16. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/tests/test_agent_doc_consistency.py +0 -0
  17. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/tests/test_crawl_local.py +0 -0
  18. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/tests/test_extract_files.py +0 -0
  19. {local_knowledge-0.2.1 → local_knowledge-0.2.3}/tests/test_server_flow_local.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: local-knowledge
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Local file/folder indexer + grounded-Q&A MCP service — same engine as confluence-knowledge, no credentials needed
5
5
  License: MIT
6
6
  Project-URL: Repository, https://github.com/nkunal1992/agents
@@ -20,7 +20,7 @@ Requires-Dist: openpyxl>=3.1
20
20
  Requires-Dist: mcp<2,>=1.6
21
21
  Requires-Dist: pip-system-certs>=5.0
22
22
  Provides-Extra: models
23
- Requires-Dist: confluence-knowledge[models]>=0.5.1; extra == "models"
23
+ Requires-Dist: confluence-knowledge[models]>=0.5.3; extra == "models"
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: pytest>=8.0; extra == "dev"
26
26
  Requires-Dist: pytest-cov>=5.0; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "local-knowledge"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  description = "Local file/folder indexer + grounded-Q&A MCP service — same engine as confluence-knowledge, no credentials needed"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -27,7 +27,7 @@ dependencies = [
27
27
 
28
28
  [project.optional-dependencies]
29
29
  models = [
30
- "confluence-knowledge[models]>=0.5.1",
30
+ "confluence-knowledge[models]>=0.5.3",
31
31
  ]
32
32
  dev = [
33
33
  "pytest>=8.0",
@@ -79,6 +79,9 @@ def main() -> None:
79
79
  import subprocess
80
80
  cmd = [sys.executable, "-m", "pip", "install", "-U",
81
81
  "local-knowledge[models]"]
82
+ # a running MCP server holds open files — quit the IDE first
83
+ print("note: quit the IDE (or stop the MCP server) before updating —"
84
+ " a running server can't be replaced cleanly", flush=True)
82
85
  if os.name == "nt":
83
86
  # this exe is the running process on Windows — pip can't replace
84
87
  # a file that's in use; hand the user the equivalent command
@@ -32,7 +32,7 @@ def resolve_root(path: str) -> Path:
32
32
  return p.resolve()
33
33
 
34
34
 
35
- from confluence_knowledge.retrieval import EMBED_MODEL_ID as _MODEL_ID
35
+ from confluence_knowledge.retrieval import MODEL_MARKER as _MODEL_ID
36
36
 
37
37
 
38
38
  class LocalCrawler:
@@ -133,6 +133,19 @@ class LocalCrawler:
133
133
 
134
134
  queue: list[str] = job["queue"]
135
135
  errors: list[str] = job["errors"]
136
+ keep_failed: set[str] = set() # transient read fails keep old content
137
+ # Mid-crawl model change: already-embedded pages live in the OLD
138
+ # vector space — finishing would publish mixed dims. Wipe + fail;
139
+ # start_crawl rebuilds under the new model.
140
+ stamped = self.index.model_id()
141
+ if stamped is not None and stamped != _MODEL_ID and queue:
142
+ self.index.clear_scope()
143
+ errors = errors + [
144
+ (f"model changed mid-crawl ({stamped} → {_MODEL_ID}) — "
145
+ "index cleared; run start_crawl")]
146
+ self.index.save_job(job_id, "error", [], errors)
147
+ return {"job_id": job_id, "state": "error",
148
+ "remaining": 0, "errors": errors}
136
149
  seen_raw = self.index.meta_get(f"seen:{job_id}")
137
150
  seen: set[str] = set(json.loads(seen_raw)) if seen_raw else set()
138
151
  levels_raw = self.index.meta_get(f"levels:{job_id}")
@@ -174,6 +187,7 @@ class LocalCrawler:
174
187
  page, chunks = self._page(path)
175
188
  except SetupError as e:
176
189
  errors.append(str(e))
190
+ keep_failed.add(self._rel(path))
177
191
  continue
178
192
  rel = page["page_id"]
179
193
  if rel in seen:
@@ -196,7 +210,7 @@ class LocalCrawler:
196
210
  # persist final seen/levels/warns too — the in-loop checkpoint runs
197
211
  # before each item is processed, so the last item would undercount
198
212
  self._checkpoint(job_id, queue, errors, seen, levels, warns)
199
- self.index.publish(seen)
213
+ self.index.publish(seen | keep_failed)
200
214
  self.index.set_model_id(_MODEL_ID)
201
215
  self.index.save_job(job_id, "done" if not queue else "running",
202
216
  queue, errors)
@@ -14,7 +14,9 @@ import threading
14
14
  from confluence_knowledge.client import AccessError, SecurityException
15
15
  from confluence_knowledge.index import Index
16
16
  from confluence_knowledge.retrieval import (
17
- EMBED_MODEL_ID,
17
+ MODEL_MARKER as EMBED_MODEL_ID,
18
+ )
19
+ from confluence_knowledge.retrieval import (
18
20
  LocalEmbedder,
19
21
  LocalReranker,
20
22
  LocalSparseEmbedder,
@@ -30,7 +32,7 @@ from .settings_local import APP_NAME, LocalProfile, home, index_root, workdir
30
32
  mcp = FastMCP("local-knowledge")
31
33
 
32
34
  _scopes: dict[str, dict] = {}
33
- _evidence_sets: dict[str, list[dict]] = {}
35
+ _evidence_sets: dict[str, dict] = {} # set_id → {scope, ev}
34
36
  _indexes: dict[str, Index] = {}
35
37
  _EMBEDDER = None
36
38
  _RERANKER = None
@@ -267,7 +269,10 @@ def search(scope_handle: str, question: str) -> dict:
267
269
  "— run start_crawl once to rebuild it, then search again"}
268
270
  ev = retrieve(idx, question, _embedder(), _reranker(), sparse=_sparse())
269
271
  set_id = f"ev:{len(_evidence_sets)}:{s['key'][:8]}"
270
- _evidence_sets[set_id] = ev
272
+ # bound the session cache — sets are never reused after the turn ends
273
+ while len(_evidence_sets) >= 32:
274
+ _evidence_sets.pop(next(iter(_evidence_sets)))
275
+ _evidence_sets[set_id] = {"scope": s["key"], "ev": ev}
271
276
  return {
272
277
  "evidence_set": set_id,
273
278
  "coverage": _coverage(idx),
@@ -275,7 +280,9 @@ def search(scope_handle: str, question: str) -> dict:
275
280
  "evidence": [
276
281
  {"chunk_id": e["chunk_id"], "page_title": e["page_title"],
277
282
  "page_url": e["page_url"], "heading": e["heading"],
278
- "score": e.get("rerank_score"), "text": e["text"][:2000]}
283
+ "score": e.get("rerank_score"), "text": e["text"][:12000],
284
+ "low_confidence": e.get("low_confidence", False),
285
+ "truncated": len(e["text"]) > 12000}
279
286
  for e in ev
280
287
  ],
281
288
  }
@@ -301,12 +308,12 @@ def get_evidence(scope_handle: str, chunk_ids: list[str]) -> dict:
301
308
  @mcp.tool()
302
309
  def verify_citations(scope_handle: str, evidence_set: str,
303
310
  mappings: list[dict]) -> dict:
304
- _scope(scope_handle)
305
- ev = _evidence_sets.get(evidence_set)
306
- if ev is None or not evidence_set.endswith(_scopes[scope_handle]["key"][:8]):
311
+ s = _scope(scope_handle)
312
+ rec = _evidence_sets.get(evidence_set)
313
+ if rec is None or rec["scope"] != s["key"]:
307
314
  raise SecurityException(
308
315
  "unknown or cross-scope evidence set — run search again and cite its evidence_set")
309
- return check_citations(ev, mappings)
316
+ return check_citations(rec["ev"], mappings)
310
317
 
311
318
 
312
319
  def main() -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: local-knowledge
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Local file/folder indexer + grounded-Q&A MCP service — same engine as confluence-knowledge, no credentials needed
5
5
  License: MIT
6
6
  Project-URL: Repository, https://github.com/nkunal1992/agents
@@ -20,7 +20,7 @@ Requires-Dist: openpyxl>=3.1
20
20
  Requires-Dist: mcp<2,>=1.6
21
21
  Requires-Dist: pip-system-certs>=5.0
22
22
  Provides-Extra: models
23
- Requires-Dist: confluence-knowledge[models]>=0.5.1; extra == "models"
23
+ Requires-Dist: confluence-knowledge[models]>=0.5.3; extra == "models"
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: pytest>=8.0; extra == "dev"
26
26
  Requires-Dist: pytest-cov>=5.0; extra == "dev"
@@ -12,4 +12,4 @@ pytest-cov>=5.0
12
12
  ruff>=0.6
13
13
 
14
14
  [models]
15
- confluence-knowledge[models]>=0.5.1
15
+ confluence-knowledge[models]>=0.5.3