thread-archive 0.0.2__py3-none-any.whl
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.
- thread_archive/__init__.py +34 -0
- thread_archive/_api.py +2235 -0
- thread_archive/_config.py +126 -0
- thread_archive/_importers/__init__.py +81 -0
- thread_archive/_importers/_continuation.py +136 -0
- thread_archive/_importers/_cursor.py +103 -0
- thread_archive/_importers/_events.py +244 -0
- thread_archive/_importers/_line_stream.py +193 -0
- thread_archive/_importers/_read.py +82 -0
- thread_archive/_importers/_result.py +17 -0
- thread_archive/_importers/_sidecar.py +113 -0
- thread_archive/_importers/_state.py +240 -0
- thread_archive/_importers/_titles.py +179 -0
- thread_archive/_importers/antigravity.py +254 -0
- thread_archive/_importers/claude_code.py +293 -0
- thread_archive/_importers/claude_science.py +330 -0
- thread_archive/_importers/cloth.py +20 -0
- thread_archive/_importers/codex.py +324 -0
- thread_archive/_importers/cowork.py +107 -0
- thread_archive/_importers/cursor.py +432 -0
- thread_archive/_importers/exports.py +389 -0
- thread_archive/_importers/grok.py +600 -0
- thread_archive/_importers/opencode.py +538 -0
- thread_archive/_knowledge/__init__.py +67 -0
- thread_archive/_knowledge/_claims.py +116 -0
- thread_archive/_knowledge/_community.py +75 -0
- thread_archive/_knowledge/graph.py +208 -0
- thread_archive/_knowledge/materialize.py +212 -0
- thread_archive/_knowledge/write.py +438 -0
- thread_archive/_launchd.py +167 -0
- thread_archive/_mcp/__init__.py +1 -0
- thread_archive/_mcp/librarian.py +151 -0
- thread_archive/_mcp/server.py +307 -0
- thread_archive/_retrieval/__init__.py +280 -0
- thread_archive/_retrieval/_classify.py +80 -0
- thread_archive/_retrieval/_codex.py +157 -0
- thread_archive/_retrieval/_context.py +123 -0
- thread_archive/_retrieval/_extract.py +184 -0
- thread_archive/_retrieval/embed.py +186 -0
- thread_archive/_retrieval/format.py +149 -0
- thread_archive/_retrieval/fts.py +538 -0
- thread_archive/_retrieval/rank.py +228 -0
- thread_archive/_retrieval/read.py +908 -0
- thread_archive/_retrieval/rerank.py +143 -0
- thread_archive/_retrieval/vectors.py +611 -0
- thread_archive/_scripts/__init__.py +1 -0
- thread_archive/_scripts/backfill_recompute.py +328 -0
- thread_archive/_scripts/backfill_reconcile.py +296 -0
- thread_archive/_scripts/denamespace_dedup_keys.py +120 -0
- thread_archive/_scripts/recover_dropped_events.py +190 -0
- thread_archive/_scripts/repair_grok_tool_names.py +118 -0
- thread_archive/_scripts/repair_grok_tool_names_backup_20260704T155625Z.json +275 -0
- thread_archive/_scripts/repair_grok_tool_names_plan_20260704.json +435 -0
- thread_archive/_setup/__init__.py +12 -0
- thread_archive/_setup/clients.py +89 -0
- thread_archive/_setup/wizard.py +474 -0
- thread_archive/_store/__init__.py +45 -0
- thread_archive/_store/_base.py +173 -0
- thread_archive/_store/_defaults.py +57 -0
- thread_archive/_store/_types.py +38 -0
- thread_archive/_store/models.py +370 -0
- thread_archive/_store/schema.py +84 -0
- thread_archive/_thread_import/__init__.py +40 -0
- thread_archive/_thread_import/api.py +78 -0
- thread_archive/_thread_import/event_builder.py +810 -0
- thread_archive/_thread_import/exporters/__init__.py +9 -0
- thread_archive/_thread_import/exporters/_cursor_kv_mixin.py +166 -0
- thread_archive/_thread_import/exporters/_cursor_parse_mixin.py +149 -0
- thread_archive/_thread_import/exporters/cursor.py +582 -0
- thread_archive/_thread_import/exporters/cursor_parse.py +145 -0
- thread_archive/_thread_import/parsers/__init__.py +191 -0
- thread_archive/_thread_import/parsers/base.py +698 -0
- thread_archive/_thread_import/parsers/chatgpt.py +722 -0
- thread_archive/_thread_import/parsers/chatgpt_content.py +332 -0
- thread_archive/_thread_import/parsers/claude.py +443 -0
- thread_archive/_thread_import/parsers/claude_code.py +1035 -0
- thread_archive/_thread_import/parsers/claude_code_blocks.py +415 -0
- thread_archive/_thread_import/parsers/claude_code_ide.py +122 -0
- thread_archive/_thread_import/parsers/claude_code_sessions.py +90 -0
- thread_archive/_thread_import/parsers/config/__init__.py +26 -0
- thread_archive/_thread_import/parsers/config/base.py +220 -0
- thread_archive/_thread_import/parsers/cursor.py +538 -0
- thread_archive/_thread_import/parsers/cursor_blocks.py +365 -0
- thread_archive/_thread_import/parsers/pipeline/__init__.py +29 -0
- thread_archive/_thread_import/parsers/pipeline/base.py +251 -0
- thread_archive/_thread_import/parsers/pipeline/interfaces.py +191 -0
- thread_archive/_thread_import/parsers/transformers/__init__.py +22 -0
- thread_archive/_thread_import/parsers/transformers/active_path.py +87 -0
- thread_archive/_thread_import/parsers/transformers/coalescing.py +389 -0
- thread_archive/_thread_import/parsers/transformers/ide_context.py +158 -0
- thread_archive/_thread_import/parsers/transformers/thinking_merge.py +68 -0
- thread_archive/_thread_import/parsers/types/__init__.py +56 -0
- thread_archive/_thread_import/parsers/types/chatgpt.py +99 -0
- thread_archive/_thread_import/parsers/types/claude.py +120 -0
- thread_archive/_thread_import/parsers/types/claude_code.py +139 -0
- thread_archive/_thread_import/parsers/types/cursor.py +109 -0
- thread_archive/_thread_import/parsers/validators/__init__.py +83 -0
- thread_archive/_thread_import/parsers/validators/base.py +168 -0
- thread_archive/_thread_import/parsers/validators/content.py +80 -0
- thread_archive/_thread_import/parsers/validators/referential.py +57 -0
- thread_archive/_thread_import/parsers/validators/thinking.py +143 -0
- thread_archive/_thread_import/parsers/validators/types.py +87 -0
- thread_archive/_thread_import/schemas/__init__.py +231 -0
- thread_archive/_thread_import/schemas/chatgpt/v1.json +197 -0
- thread_archive/_thread_import/schemas/chatgpt/v2.json +203 -0
- thread_archive/_thread_import/schemas/claude/v1.json +188 -0
- thread_archive/_thread_import/schemas/claude_code/v1.json +183 -0
- thread_archive/_thread_import/schemas/cursor/v1.json +143 -0
- thread_archive/_thread_import/schemas/cursor/v2.json +200 -0
- thread_archive/_thread_import/timestamps.py +57 -0
- thread_archive/_thread_import/tool_names.py +48 -0
- thread_archive/_truth/__init__.py +40 -0
- thread_archive/_truth/jsonl_log.py +2340 -0
- thread_archive/_truth/repair.py +322 -0
- thread_archive/_watcher/__init__.py +57 -0
- thread_archive/_watcher/base.py +65 -0
- thread_archive/_watcher/daemon.py +289 -0
- thread_archive/_watcher/export_drop.py +203 -0
- thread_archive/_watcher/exthost.py +316 -0
- thread_archive/_watcher/lazy.py +117 -0
- thread_archive/_watcher/sources.py +616 -0
- thread_archive/_web/__init__.py +15 -0
- thread_archive/_web/server.py +299 -0
- thread_archive/_web/static/assets/index-DECSH1Mz.css +10 -0
- thread_archive/_web/static/assets/index-Djq0FK0y.js +101 -0
- thread_archive/_web/static/index.html +13 -0
- thread_archive/cli.py +746 -0
- thread_archive-0.0.2.dist-info/METADATA +296 -0
- thread_archive-0.0.2.dist-info/RECORD +132 -0
- thread_archive-0.0.2.dist-info/WHEEL +4 -0
- thread_archive-0.0.2.dist-info/entry_points.txt +6 -0
- thread_archive-0.0.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""In-process cross-encoder re-rank — a bge-reranker via sentence-transformers.
|
|
2
|
+
|
|
3
|
+
The in-process cross-encoder: model ``bge-reranker-v2-m3``, ``(query, doc)`` joint
|
|
4
|
+
scoring, fail-soft contract — run **in-process** through sentence-transformers'
|
|
5
|
+
``CrossEncoder`` rather than as an HTTP client to a llama-server, the same way
|
|
6
|
+
:mod:`.embed` runs the embedding model in-process. No daemon, no second process:
|
|
7
|
+
the model loads in this process, gated to the ``[embeddings]`` extra (torch).
|
|
8
|
+
|
|
9
|
+
Why it exists: the bi-encoder ANN (the vector arm) puts the true target in the
|
|
10
|
+
top-20 often but at rank 1 rarely — semantic look-alikes outrank it, and a
|
|
11
|
+
vocab-mismatch target has lexical density ~0 so the lexical scorer can't separate
|
|
12
|
+
them either. A cross-encoder scores (query, candidate) *jointly* and pulls the
|
|
13
|
+
target up the mid-list (measured found@1 0.21→0.285, MRR 0.37→0.45).
|
|
14
|
+
|
|
15
|
+
**Fail-soft by contract.** Every entry point returns ``None`` (caller keeps its
|
|
16
|
+
order) on any error — a missing extra, a failed model load, an encode error. The
|
|
17
|
+
re-rank is a precision booster on the head, never a correctness dependency: with
|
|
18
|
+
no ``[embeddings]`` extra the whole stage degrades out and search stays
|
|
19
|
+
lexical+vector. ``is_available()`` is cheap (does not load the model); a load
|
|
20
|
+
failure degrades at call time and is cached so it costs one attempt, not one per
|
|
21
|
+
query.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import importlib.util
|
|
27
|
+
import logging
|
|
28
|
+
import os
|
|
29
|
+
import threading
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
# Doc head fed to the cross-encoder. The relevance signal lives in the opening of a
|
|
34
|
+
# message; capping bounds each (query, doc) pair well under the model ctx and keeps
|
|
35
|
+
# latency sane (~1500 chars ≈ ~375 tokens).
|
|
36
|
+
RERANK_DOC_CHARS = 1500
|
|
37
|
+
_MODEL_NAME = os.environ.get("THREAD_ARCHIVE_RERANK_MODEL", "BAAI/bge-reranker-v2-m3")
|
|
38
|
+
|
|
39
|
+
_model = None
|
|
40
|
+
_load_failed = False
|
|
41
|
+
# Serializes _load() so a background warm (mcp.server) and a concurrent first conceptual
|
|
42
|
+
# query can't both construct this ~570M cross-encoder at once.
|
|
43
|
+
_load_lock = threading.Lock()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def model_name() -> str:
|
|
47
|
+
return _MODEL_NAME
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _device() -> str:
|
|
51
|
+
"""``$THREAD_ARCHIVE_RERANK_DEVICE`` (else the shared embed device) if set, else
|
|
52
|
+
the best accelerator (Apple ``mps`` / CUDA), falling back to ``cpu``."""
|
|
53
|
+
dev = os.environ.get("THREAD_ARCHIVE_RERANK_DEVICE") or os.environ.get("THREAD_ARCHIVE_EMBED_DEVICE")
|
|
54
|
+
if dev:
|
|
55
|
+
return dev
|
|
56
|
+
try:
|
|
57
|
+
import torch
|
|
58
|
+
|
|
59
|
+
if torch.backends.mps.is_available():
|
|
60
|
+
return "mps"
|
|
61
|
+
if torch.cuda.is_available():
|
|
62
|
+
return "cuda"
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
return "cpu"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def is_available() -> bool:
|
|
69
|
+
"""True when sentence-transformers is importable (the ``[embeddings]`` extra is
|
|
70
|
+
installed). Cheap — does not load the model; a load failure degrades at call time."""
|
|
71
|
+
try:
|
|
72
|
+
if importlib.util.find_spec("sentence_transformers") is None:
|
|
73
|
+
return False
|
|
74
|
+
except ImportError:
|
|
75
|
+
return False
|
|
76
|
+
return not _load_failed
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _load():
|
|
80
|
+
"""Lazily construct the cached CrossEncoder. None (cached) on any load failure.
|
|
81
|
+
Double-checked under ``_load_lock`` so a background warm and a first query race to a
|
|
82
|
+
single construction, not two concurrent heavy loads."""
|
|
83
|
+
global _model, _load_failed
|
|
84
|
+
if _model is not None:
|
|
85
|
+
return _model
|
|
86
|
+
if _load_failed:
|
|
87
|
+
return None
|
|
88
|
+
with _load_lock:
|
|
89
|
+
if _model is not None:
|
|
90
|
+
return _model
|
|
91
|
+
if _load_failed:
|
|
92
|
+
return None
|
|
93
|
+
try:
|
|
94
|
+
from sentence_transformers import CrossEncoder
|
|
95
|
+
|
|
96
|
+
device = _device()
|
|
97
|
+
_model = CrossEncoder(_MODEL_NAME, device=device)
|
|
98
|
+
logger.info("rerank: loaded %s (device=%s)", _MODEL_NAME, device)
|
|
99
|
+
return _model
|
|
100
|
+
except Exception as e: # noqa: BLE001
|
|
101
|
+
_load_failed = True
|
|
102
|
+
logger.warning("rerank: model load failed (%s) — re-rank stage degrades", e)
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def warm() -> bool:
|
|
107
|
+
"""Eagerly load the cross-encoder so it isn't cold-loaded inside the first conceptual
|
|
108
|
+
query (a >60s stall that can blow past an MCP client's request timeout). Fail-soft and
|
|
109
|
+
idempotent: returns False when the extra is absent or the load fails."""
|
|
110
|
+
if not is_available():
|
|
111
|
+
return False
|
|
112
|
+
return _load() is not None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def rerank_scores(query: str, docs: list[str]) -> list[float] | None:
|
|
116
|
+
"""Relevance score per doc (in input order), or ``None`` on any failure.
|
|
117
|
+
|
|
118
|
+
Length-capped and fail-soft. A ``None`` return means "reranker unavailable /
|
|
119
|
+
errored — keep the caller's order." Pure: no I/O beyond the in-process model."""
|
|
120
|
+
if not query or not docs:
|
|
121
|
+
return None
|
|
122
|
+
model = _load()
|
|
123
|
+
if model is None:
|
|
124
|
+
return None
|
|
125
|
+
try:
|
|
126
|
+
pairs = [[query, (d or "")[:RERANK_DOC_CHARS]] for d in docs]
|
|
127
|
+
scores = model.predict(pairs)
|
|
128
|
+
return [float(s) for s in scores]
|
|
129
|
+
except Exception as e: # noqa: BLE001
|
|
130
|
+
logger.debug("rerank failed (%s) — caller keeps original order", e)
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def rerank(query: str, items: list, get_text) -> list | None:
|
|
135
|
+
"""Reorder ``items`` by cross-encoder relevance to ``query``. ``get_text(item)``
|
|
136
|
+
yields the text to score. Returns a new list (best-first), or ``None`` if the
|
|
137
|
+
reranker is unavailable/errored (caller keeps its order). Pure helper."""
|
|
138
|
+
if not items:
|
|
139
|
+
return None
|
|
140
|
+
scores = rerank_scores(query, [get_text(it) for it in items])
|
|
141
|
+
if scores is None:
|
|
142
|
+
return None
|
|
143
|
+
return [it for _, it in sorted(zip(scores, items), key=lambda p: -p[0])]
|