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.
Files changed (132) hide show
  1. thread_archive/__init__.py +34 -0
  2. thread_archive/_api.py +2235 -0
  3. thread_archive/_config.py +126 -0
  4. thread_archive/_importers/__init__.py +81 -0
  5. thread_archive/_importers/_continuation.py +136 -0
  6. thread_archive/_importers/_cursor.py +103 -0
  7. thread_archive/_importers/_events.py +244 -0
  8. thread_archive/_importers/_line_stream.py +193 -0
  9. thread_archive/_importers/_read.py +82 -0
  10. thread_archive/_importers/_result.py +17 -0
  11. thread_archive/_importers/_sidecar.py +113 -0
  12. thread_archive/_importers/_state.py +240 -0
  13. thread_archive/_importers/_titles.py +179 -0
  14. thread_archive/_importers/antigravity.py +254 -0
  15. thread_archive/_importers/claude_code.py +293 -0
  16. thread_archive/_importers/claude_science.py +330 -0
  17. thread_archive/_importers/cloth.py +20 -0
  18. thread_archive/_importers/codex.py +324 -0
  19. thread_archive/_importers/cowork.py +107 -0
  20. thread_archive/_importers/cursor.py +432 -0
  21. thread_archive/_importers/exports.py +389 -0
  22. thread_archive/_importers/grok.py +600 -0
  23. thread_archive/_importers/opencode.py +538 -0
  24. thread_archive/_knowledge/__init__.py +67 -0
  25. thread_archive/_knowledge/_claims.py +116 -0
  26. thread_archive/_knowledge/_community.py +75 -0
  27. thread_archive/_knowledge/graph.py +208 -0
  28. thread_archive/_knowledge/materialize.py +212 -0
  29. thread_archive/_knowledge/write.py +438 -0
  30. thread_archive/_launchd.py +167 -0
  31. thread_archive/_mcp/__init__.py +1 -0
  32. thread_archive/_mcp/librarian.py +151 -0
  33. thread_archive/_mcp/server.py +307 -0
  34. thread_archive/_retrieval/__init__.py +280 -0
  35. thread_archive/_retrieval/_classify.py +80 -0
  36. thread_archive/_retrieval/_codex.py +157 -0
  37. thread_archive/_retrieval/_context.py +123 -0
  38. thread_archive/_retrieval/_extract.py +184 -0
  39. thread_archive/_retrieval/embed.py +186 -0
  40. thread_archive/_retrieval/format.py +149 -0
  41. thread_archive/_retrieval/fts.py +538 -0
  42. thread_archive/_retrieval/rank.py +228 -0
  43. thread_archive/_retrieval/read.py +908 -0
  44. thread_archive/_retrieval/rerank.py +143 -0
  45. thread_archive/_retrieval/vectors.py +611 -0
  46. thread_archive/_scripts/__init__.py +1 -0
  47. thread_archive/_scripts/backfill_recompute.py +328 -0
  48. thread_archive/_scripts/backfill_reconcile.py +296 -0
  49. thread_archive/_scripts/denamespace_dedup_keys.py +120 -0
  50. thread_archive/_scripts/recover_dropped_events.py +190 -0
  51. thread_archive/_scripts/repair_grok_tool_names.py +118 -0
  52. thread_archive/_scripts/repair_grok_tool_names_backup_20260704T155625Z.json +275 -0
  53. thread_archive/_scripts/repair_grok_tool_names_plan_20260704.json +435 -0
  54. thread_archive/_setup/__init__.py +12 -0
  55. thread_archive/_setup/clients.py +89 -0
  56. thread_archive/_setup/wizard.py +474 -0
  57. thread_archive/_store/__init__.py +45 -0
  58. thread_archive/_store/_base.py +173 -0
  59. thread_archive/_store/_defaults.py +57 -0
  60. thread_archive/_store/_types.py +38 -0
  61. thread_archive/_store/models.py +370 -0
  62. thread_archive/_store/schema.py +84 -0
  63. thread_archive/_thread_import/__init__.py +40 -0
  64. thread_archive/_thread_import/api.py +78 -0
  65. thread_archive/_thread_import/event_builder.py +810 -0
  66. thread_archive/_thread_import/exporters/__init__.py +9 -0
  67. thread_archive/_thread_import/exporters/_cursor_kv_mixin.py +166 -0
  68. thread_archive/_thread_import/exporters/_cursor_parse_mixin.py +149 -0
  69. thread_archive/_thread_import/exporters/cursor.py +582 -0
  70. thread_archive/_thread_import/exporters/cursor_parse.py +145 -0
  71. thread_archive/_thread_import/parsers/__init__.py +191 -0
  72. thread_archive/_thread_import/parsers/base.py +698 -0
  73. thread_archive/_thread_import/parsers/chatgpt.py +722 -0
  74. thread_archive/_thread_import/parsers/chatgpt_content.py +332 -0
  75. thread_archive/_thread_import/parsers/claude.py +443 -0
  76. thread_archive/_thread_import/parsers/claude_code.py +1035 -0
  77. thread_archive/_thread_import/parsers/claude_code_blocks.py +415 -0
  78. thread_archive/_thread_import/parsers/claude_code_ide.py +122 -0
  79. thread_archive/_thread_import/parsers/claude_code_sessions.py +90 -0
  80. thread_archive/_thread_import/parsers/config/__init__.py +26 -0
  81. thread_archive/_thread_import/parsers/config/base.py +220 -0
  82. thread_archive/_thread_import/parsers/cursor.py +538 -0
  83. thread_archive/_thread_import/parsers/cursor_blocks.py +365 -0
  84. thread_archive/_thread_import/parsers/pipeline/__init__.py +29 -0
  85. thread_archive/_thread_import/parsers/pipeline/base.py +251 -0
  86. thread_archive/_thread_import/parsers/pipeline/interfaces.py +191 -0
  87. thread_archive/_thread_import/parsers/transformers/__init__.py +22 -0
  88. thread_archive/_thread_import/parsers/transformers/active_path.py +87 -0
  89. thread_archive/_thread_import/parsers/transformers/coalescing.py +389 -0
  90. thread_archive/_thread_import/parsers/transformers/ide_context.py +158 -0
  91. thread_archive/_thread_import/parsers/transformers/thinking_merge.py +68 -0
  92. thread_archive/_thread_import/parsers/types/__init__.py +56 -0
  93. thread_archive/_thread_import/parsers/types/chatgpt.py +99 -0
  94. thread_archive/_thread_import/parsers/types/claude.py +120 -0
  95. thread_archive/_thread_import/parsers/types/claude_code.py +139 -0
  96. thread_archive/_thread_import/parsers/types/cursor.py +109 -0
  97. thread_archive/_thread_import/parsers/validators/__init__.py +83 -0
  98. thread_archive/_thread_import/parsers/validators/base.py +168 -0
  99. thread_archive/_thread_import/parsers/validators/content.py +80 -0
  100. thread_archive/_thread_import/parsers/validators/referential.py +57 -0
  101. thread_archive/_thread_import/parsers/validators/thinking.py +143 -0
  102. thread_archive/_thread_import/parsers/validators/types.py +87 -0
  103. thread_archive/_thread_import/schemas/__init__.py +231 -0
  104. thread_archive/_thread_import/schemas/chatgpt/v1.json +197 -0
  105. thread_archive/_thread_import/schemas/chatgpt/v2.json +203 -0
  106. thread_archive/_thread_import/schemas/claude/v1.json +188 -0
  107. thread_archive/_thread_import/schemas/claude_code/v1.json +183 -0
  108. thread_archive/_thread_import/schemas/cursor/v1.json +143 -0
  109. thread_archive/_thread_import/schemas/cursor/v2.json +200 -0
  110. thread_archive/_thread_import/timestamps.py +57 -0
  111. thread_archive/_thread_import/tool_names.py +48 -0
  112. thread_archive/_truth/__init__.py +40 -0
  113. thread_archive/_truth/jsonl_log.py +2340 -0
  114. thread_archive/_truth/repair.py +322 -0
  115. thread_archive/_watcher/__init__.py +57 -0
  116. thread_archive/_watcher/base.py +65 -0
  117. thread_archive/_watcher/daemon.py +289 -0
  118. thread_archive/_watcher/export_drop.py +203 -0
  119. thread_archive/_watcher/exthost.py +316 -0
  120. thread_archive/_watcher/lazy.py +117 -0
  121. thread_archive/_watcher/sources.py +616 -0
  122. thread_archive/_web/__init__.py +15 -0
  123. thread_archive/_web/server.py +299 -0
  124. thread_archive/_web/static/assets/index-DECSH1Mz.css +10 -0
  125. thread_archive/_web/static/assets/index-Djq0FK0y.js +101 -0
  126. thread_archive/_web/static/index.html +13 -0
  127. thread_archive/cli.py +746 -0
  128. thread_archive-0.0.2.dist-info/METADATA +296 -0
  129. thread_archive-0.0.2.dist-info/RECORD +132 -0
  130. thread_archive-0.0.2.dist-info/WHEEL +4 -0
  131. thread_archive-0.0.2.dist-info/entry_points.txt +6 -0
  132. 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])]