code-memory 1.0.4__tar.gz → 1.0.5__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.
- {code_memory-1.0.4 → code_memory-1.0.5}/PKG-INFO +2 -1
- {code_memory-1.0.4 → code_memory-1.0.5}/db.py +142 -21
- {code_memory-1.0.4 → code_memory-1.0.5}/doc_parser.py +97 -63
- {code_memory-1.0.4 → code_memory-1.0.5}/logging_config.py +23 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/parser.py +66 -37
- {code_memory-1.0.4 → code_memory-1.0.5}/pyproject.toml +2 -1
- {code_memory-1.0.4 → code_memory-1.0.5}/server.py +5 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/uv.lock +71 -1
- {code_memory-1.0.4 → code_memory-1.0.5}/.github/workflows/ci.yml +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/.github/workflows/publish.yml +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/.gitignore +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/.python-version +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/CHANGELOG.md +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/CONTRIBUTING.md +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/LICENSE +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/Makefile +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/README.md +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/errors.py +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/git_search.py +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/prompts/milestone_1.xml +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/prompts/milestone_2.xml +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/prompts/milestone_3.xml +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/prompts/milestone_4.xml +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/prompts/milestone_5.xml +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/prompts/milestone_6.xml +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/queries.py +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/tests/__init__.py +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/tests/conftest.py +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/tests/test_errors.py +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/tests/test_logging.py +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/tests/test_tools.py +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/tests/test_validation.py +0 -0
- {code_memory-1.0.4 → code_memory-1.0.5}/validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-memory
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
4
4
|
Summary: A deterministic, high-precision code intelligence MCP server
|
|
5
5
|
Project-URL: Homepage, https://github.com/kapillamba4/code-memory
|
|
6
6
|
Project-URL: Documentation, https://github.com/kapillamba4/code-memory#readme
|
|
@@ -32,6 +32,7 @@ Requires-Dist: tree-sitter-ruby>=0.23.1
|
|
|
32
32
|
Requires-Dist: tree-sitter-rust>=0.24.0
|
|
33
33
|
Requires-Dist: tree-sitter-typescript>=0.23.2
|
|
34
34
|
Requires-Dist: tree-sitter>=0.25.2
|
|
35
|
+
Requires-Dist: xxhash>=3.6.0
|
|
35
36
|
Provides-Extra: dev
|
|
36
37
|
Requires-Dist: mypy>=1.13.0; extra == 'dev'
|
|
37
38
|
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
@@ -11,15 +11,19 @@ All writes use upsert semantics so re-indexing is idempotent.
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
-
import
|
|
14
|
+
import logging
|
|
15
15
|
import sqlite3
|
|
16
|
+
from contextlib import contextmanager
|
|
16
17
|
from typing import TYPE_CHECKING
|
|
17
18
|
|
|
18
19
|
import sqlite_vec
|
|
20
|
+
import xxhash
|
|
19
21
|
|
|
20
22
|
if TYPE_CHECKING:
|
|
21
23
|
pass
|
|
22
24
|
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
23
27
|
# ---------------------------------------------------------------------------
|
|
24
28
|
# Embedding model (lazy-loaded singleton)
|
|
25
29
|
# ---------------------------------------------------------------------------
|
|
@@ -41,10 +45,80 @@ def get_embedding_model():
|
|
|
41
45
|
def embed_text(text: str) -> list[float]:
|
|
42
46
|
"""Generate a 384-dim dense vector embedding for *text*."""
|
|
43
47
|
model = get_embedding_model()
|
|
44
|
-
vec = model.encode(text, normalize_embeddings=True)
|
|
48
|
+
vec = model.encode(text, normalize_embeddings=True, show_progress_bar=False)
|
|
45
49
|
return vec.tolist()
|
|
46
50
|
|
|
47
51
|
|
|
52
|
+
def embed_texts_batch(texts: list[str], batch_size: int = 32) -> list[list[float]]:
|
|
53
|
+
"""Generate embeddings for multiple texts at once.
|
|
54
|
+
|
|
55
|
+
This is significantly faster than calling embed_text() in a loop
|
|
56
|
+
because sentence-transformers is optimized for batch processing.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
texts: List of text strings to embed.
|
|
60
|
+
batch_size: Number of texts to process per batch (default 32).
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List of embedding vectors (same order as input texts).
|
|
64
|
+
"""
|
|
65
|
+
if not texts:
|
|
66
|
+
return []
|
|
67
|
+
|
|
68
|
+
model = get_embedding_model()
|
|
69
|
+
|
|
70
|
+
# Batch encode with normalization (same as single-text version)
|
|
71
|
+
vectors = model.encode(
|
|
72
|
+
texts,
|
|
73
|
+
batch_size=batch_size,
|
|
74
|
+
normalize_embeddings=True,
|
|
75
|
+
show_progress_bar=False,
|
|
76
|
+
convert_to_numpy=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return [v.tolist() for v in vectors]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def warmup_embedding_model() -> None:
|
|
83
|
+
"""Pre-load and warm up the embedding model.
|
|
84
|
+
|
|
85
|
+
Call this at server startup to avoid cold-start latency on first search.
|
|
86
|
+
The warmup encodes a dummy string to initialize internal tensors.
|
|
87
|
+
"""
|
|
88
|
+
model = get_embedding_model()
|
|
89
|
+
# Warmup encode to initialize lazy-loaded components
|
|
90
|
+
model.encode("warmup", normalize_embeddings=True, show_progress_bar=False)
|
|
91
|
+
logger.info("Embedding model warmed up")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# Transaction support
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@contextmanager
|
|
100
|
+
def transaction(db: sqlite3.Connection):
|
|
101
|
+
"""Context manager for explicit transaction control.
|
|
102
|
+
|
|
103
|
+
Disables autocommit, yields control, then commits on success.
|
|
104
|
+
On exception, rolls back automatically.
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
with transaction(db):
|
|
108
|
+
for item in items:
|
|
109
|
+
upsert_symbol(db, ..., auto_commit=False)
|
|
110
|
+
# Single commit here
|
|
111
|
+
"""
|
|
112
|
+
# Disable autocommit by starting a transaction
|
|
113
|
+
db.execute("BEGIN")
|
|
114
|
+
try:
|
|
115
|
+
yield db
|
|
116
|
+
db.commit()
|
|
117
|
+
except Exception:
|
|
118
|
+
db.rollback()
|
|
119
|
+
raise
|
|
120
|
+
|
|
121
|
+
|
|
48
122
|
# ---------------------------------------------------------------------------
|
|
49
123
|
# Database initialisation
|
|
50
124
|
# ---------------------------------------------------------------------------
|
|
@@ -209,15 +283,32 @@ def get_db(db_path: str = "code_memory.db") -> sqlite3.Connection:
|
|
|
209
283
|
|
|
210
284
|
|
|
211
285
|
def file_hash(filepath: str) -> str:
|
|
212
|
-
"""Compute
|
|
213
|
-
|
|
286
|
+
"""Compute fast non-cryptographic hash of a file's contents.
|
|
287
|
+
|
|
288
|
+
Uses xxHash (xxh64) which is ~10x faster than SHA-256 while still
|
|
289
|
+
providing excellent collision resistance for change detection.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
filepath: Path to the file to hash.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Hexadecimal string representation of the 64-bit hash.
|
|
296
|
+
"""
|
|
297
|
+
h = xxhash.xxh64()
|
|
214
298
|
with open(filepath, "rb") as f:
|
|
215
|
-
|
|
299
|
+
# Read in 64KB chunks for memory efficiency
|
|
300
|
+
for chunk in iter(lambda: f.read(65536), b""):
|
|
216
301
|
h.update(chunk)
|
|
217
302
|
return h.hexdigest()
|
|
218
303
|
|
|
219
304
|
|
|
220
|
-
def upsert_file(
|
|
305
|
+
def upsert_file(
|
|
306
|
+
db: sqlite3.Connection,
|
|
307
|
+
path: str,
|
|
308
|
+
last_modified: float,
|
|
309
|
+
fhash: str,
|
|
310
|
+
auto_commit: bool = True,
|
|
311
|
+
) -> int:
|
|
221
312
|
"""Insert or update a file record. Returns the file_id."""
|
|
222
313
|
db.execute(
|
|
223
314
|
"""
|
|
@@ -229,13 +320,14 @@ def upsert_file(db: sqlite3.Connection, path: str, last_modified: float, fhash:
|
|
|
229
320
|
""",
|
|
230
321
|
(path, last_modified, fhash),
|
|
231
322
|
)
|
|
232
|
-
|
|
323
|
+
if auto_commit:
|
|
324
|
+
db.commit()
|
|
233
325
|
# Fetch the id (needed because last_insert_rowid isn't reliable on update)
|
|
234
326
|
row = db.execute("SELECT id FROM files WHERE path = ?", (path,)).fetchone()
|
|
235
327
|
return row[0]
|
|
236
328
|
|
|
237
329
|
|
|
238
|
-
def delete_file_data(db: sqlite3.Connection, file_id: int) -> None:
|
|
330
|
+
def delete_file_data(db: sqlite3.Connection, file_id: int, auto_commit: bool = True) -> None:
|
|
239
331
|
"""Remove all symbols, embeddings, and references for a file.
|
|
240
332
|
|
|
241
333
|
This is called before re-indexing to guarantee idempotency.
|
|
@@ -250,7 +342,8 @@ def delete_file_data(db: sqlite3.Connection, file_id: int) -> None:
|
|
|
250
342
|
|
|
251
343
|
db.execute("DELETE FROM symbols WHERE file_id = ?", (file_id,))
|
|
252
344
|
db.execute("DELETE FROM references_ WHERE file_id = ?", (file_id,))
|
|
253
|
-
|
|
345
|
+
if auto_commit:
|
|
346
|
+
db.commit()
|
|
254
347
|
|
|
255
348
|
|
|
256
349
|
def upsert_symbol(
|
|
@@ -262,6 +355,7 @@ def upsert_symbol(
|
|
|
262
355
|
line_end: int,
|
|
263
356
|
parent_symbol_id: int | None,
|
|
264
357
|
source_text: str,
|
|
358
|
+
auto_commit: bool = True,
|
|
265
359
|
) -> int:
|
|
266
360
|
"""Insert or update a symbol record. Returns the symbol_id."""
|
|
267
361
|
db.execute(
|
|
@@ -276,7 +370,8 @@ def upsert_symbol(
|
|
|
276
370
|
""",
|
|
277
371
|
(name, kind, file_id, line_start, line_end, parent_symbol_id, source_text),
|
|
278
372
|
)
|
|
279
|
-
|
|
373
|
+
if auto_commit:
|
|
374
|
+
db.commit()
|
|
280
375
|
row = db.execute(
|
|
281
376
|
"SELECT id FROM symbols WHERE file_id = ? AND name = ? AND kind = ? AND line_start = ?",
|
|
282
377
|
(file_id, name, kind, line_start),
|
|
@@ -285,7 +380,11 @@ def upsert_symbol(
|
|
|
285
380
|
|
|
286
381
|
|
|
287
382
|
def upsert_reference(
|
|
288
|
-
db: sqlite3.Connection,
|
|
383
|
+
db: sqlite3.Connection,
|
|
384
|
+
symbol_name: str,
|
|
385
|
+
file_id: int,
|
|
386
|
+
line_number: int,
|
|
387
|
+
auto_commit: bool = True,
|
|
289
388
|
) -> None:
|
|
290
389
|
"""Insert or update a cross-reference record."""
|
|
291
390
|
db.execute(
|
|
@@ -296,10 +395,16 @@ def upsert_reference(
|
|
|
296
395
|
""",
|
|
297
396
|
(symbol_name, file_id, line_number),
|
|
298
397
|
)
|
|
299
|
-
|
|
398
|
+
if auto_commit:
|
|
399
|
+
db.commit()
|
|
300
400
|
|
|
301
401
|
|
|
302
|
-
def upsert_embedding(
|
|
402
|
+
def upsert_embedding(
|
|
403
|
+
db: sqlite3.Connection,
|
|
404
|
+
symbol_id: int,
|
|
405
|
+
embedding: list[float],
|
|
406
|
+
auto_commit: bool = True,
|
|
407
|
+
) -> None:
|
|
303
408
|
"""Insert or replace a symbol's dense vector embedding."""
|
|
304
409
|
import struct
|
|
305
410
|
|
|
@@ -310,7 +415,8 @@ def upsert_embedding(db: sqlite3.Connection, symbol_id: int, embedding: list[flo
|
|
|
310
415
|
"INSERT INTO symbol_embeddings (symbol_id, embedding) VALUES (?, ?)",
|
|
311
416
|
(symbol_id, blob),
|
|
312
417
|
)
|
|
313
|
-
|
|
418
|
+
if auto_commit:
|
|
419
|
+
db.commit()
|
|
314
420
|
|
|
315
421
|
|
|
316
422
|
# ---------------------------------------------------------------------------
|
|
@@ -319,7 +425,12 @@ def upsert_embedding(db: sqlite3.Connection, symbol_id: int, embedding: list[flo
|
|
|
319
425
|
|
|
320
426
|
|
|
321
427
|
def upsert_doc_file(
|
|
322
|
-
db: sqlite3.Connection,
|
|
428
|
+
db: sqlite3.Connection,
|
|
429
|
+
path: str,
|
|
430
|
+
last_modified: float,
|
|
431
|
+
fhash: str,
|
|
432
|
+
doc_type: str,
|
|
433
|
+
auto_commit: bool = True,
|
|
323
434
|
) -> int:
|
|
324
435
|
"""Insert or update a documentation file record. Returns doc_file_id."""
|
|
325
436
|
db.execute(
|
|
@@ -333,12 +444,13 @@ def upsert_doc_file(
|
|
|
333
444
|
""",
|
|
334
445
|
(path, last_modified, fhash, doc_type),
|
|
335
446
|
)
|
|
336
|
-
|
|
447
|
+
if auto_commit:
|
|
448
|
+
db.commit()
|
|
337
449
|
row = db.execute("SELECT id FROM doc_files WHERE path = ?", (path,)).fetchone()
|
|
338
450
|
return row[0]
|
|
339
451
|
|
|
340
452
|
|
|
341
|
-
def delete_doc_file_data(db: sqlite3.Connection, doc_file_id: int) -> None:
|
|
453
|
+
def delete_doc_file_data(db: sqlite3.Connection, doc_file_id: int, auto_commit: bool = True) -> None:
|
|
342
454
|
"""Remove all chunks and embeddings for a documentation file.
|
|
343
455
|
|
|
344
456
|
This is called before re-indexing to guarantee idempotency.
|
|
@@ -355,7 +467,8 @@ def delete_doc_file_data(db: sqlite3.Connection, doc_file_id: int) -> None:
|
|
|
355
467
|
db.execute(f"DELETE FROM doc_embeddings WHERE chunk_id IN ({placeholders})", chunk_ids)
|
|
356
468
|
|
|
357
469
|
db.execute("DELETE FROM doc_chunks WHERE doc_file_id = ?", (doc_file_id,))
|
|
358
|
-
|
|
470
|
+
if auto_commit:
|
|
471
|
+
db.commit()
|
|
359
472
|
|
|
360
473
|
|
|
361
474
|
def upsert_doc_chunk(
|
|
@@ -366,6 +479,7 @@ def upsert_doc_chunk(
|
|
|
366
479
|
content: str,
|
|
367
480
|
line_start: int,
|
|
368
481
|
line_end: int,
|
|
482
|
+
auto_commit: bool = True,
|
|
369
483
|
) -> int:
|
|
370
484
|
"""Insert or update a documentation chunk. Returns chunk_id."""
|
|
371
485
|
db.execute(
|
|
@@ -381,7 +495,8 @@ def upsert_doc_chunk(
|
|
|
381
495
|
""",
|
|
382
496
|
(doc_file_id, chunk_index, section_title, content, line_start, line_end),
|
|
383
497
|
)
|
|
384
|
-
|
|
498
|
+
if auto_commit:
|
|
499
|
+
db.commit()
|
|
385
500
|
row = db.execute(
|
|
386
501
|
"SELECT id FROM doc_chunks WHERE doc_file_id = ? AND chunk_index = ?",
|
|
387
502
|
(doc_file_id, chunk_index),
|
|
@@ -389,7 +504,12 @@ def upsert_doc_chunk(
|
|
|
389
504
|
return row[0]
|
|
390
505
|
|
|
391
506
|
|
|
392
|
-
def upsert_doc_embedding(
|
|
507
|
+
def upsert_doc_embedding(
|
|
508
|
+
db: sqlite3.Connection,
|
|
509
|
+
chunk_id: int,
|
|
510
|
+
embedding: list[float],
|
|
511
|
+
auto_commit: bool = True,
|
|
512
|
+
) -> None:
|
|
393
513
|
"""Insert or replace a documentation chunk's dense vector embedding."""
|
|
394
514
|
import struct
|
|
395
515
|
|
|
@@ -399,4 +519,5 @@ def upsert_doc_embedding(db: sqlite3.Connection, chunk_id: int, embedding: list[
|
|
|
399
519
|
"INSERT INTO doc_embeddings (chunk_id, embedding) VALUES (?, ?)",
|
|
400
520
|
(chunk_id, blob),
|
|
401
521
|
)
|
|
402
|
-
|
|
522
|
+
if auto_commit:
|
|
523
|
+
db.commit()
|
|
@@ -237,7 +237,7 @@ def index_doc_file(
|
|
|
237
237
|
overlap: int = DEFAULT_OVERLAP,
|
|
238
238
|
min_chunk_size: int = DEFAULT_MIN_CHUNK_SIZE,
|
|
239
239
|
) -> dict:
|
|
240
|
-
"""Index a documentation file.
|
|
240
|
+
"""Index a documentation file with batch embeddings and transaction.
|
|
241
241
|
|
|
242
242
|
Args:
|
|
243
243
|
filepath: Path to the documentation file.
|
|
@@ -257,7 +257,7 @@ def index_doc_file(
|
|
|
257
257
|
# Check if file has changed
|
|
258
258
|
stat = os.stat(abs_path)
|
|
259
259
|
last_modified = stat.st_mtime
|
|
260
|
-
fhash = db_mod.file_hash(abs_path)
|
|
260
|
+
fhash = db_mod.file_hash(abs_path) # Now uses xxHash
|
|
261
261
|
|
|
262
262
|
existing = db.execute(
|
|
263
263
|
"SELECT id, file_hash FROM doc_files WHERE path = ?", (abs_path,)
|
|
@@ -283,8 +283,9 @@ def index_doc_file(
|
|
|
283
283
|
# Parse and chunk
|
|
284
284
|
sections = parse_markdown_sections(abs_path)
|
|
285
285
|
|
|
286
|
-
|
|
287
|
-
|
|
286
|
+
# === BATCH PROCESSING ===
|
|
287
|
+
chunks_to_store: list[dict] = []
|
|
288
|
+
embed_inputs: list[str] = []
|
|
288
289
|
|
|
289
290
|
for section in sections:
|
|
290
291
|
content = section["content"]
|
|
@@ -298,22 +299,34 @@ def index_doc_file(
|
|
|
298
299
|
if len(sub_content) < min_chunk_size:
|
|
299
300
|
continue
|
|
300
301
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
section["
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
# Generate and store embedding
|
|
312
|
-
embedding = db_mod.embed_text(f"{section['section_title'] or ''}: {sub_content}")
|
|
313
|
-
db_mod.upsert_doc_embedding(db, chunk_id, embedding)
|
|
302
|
+
chunks_to_store.append({
|
|
303
|
+
"section_title": section["section_title"],
|
|
304
|
+
"content": sub_content,
|
|
305
|
+
"line_start": section["line_start"],
|
|
306
|
+
"line_end": section["line_end"],
|
|
307
|
+
})
|
|
308
|
+
embed_input = f"{section['section_title'] or ''}: {sub_content}"
|
|
309
|
+
embed_inputs.append(embed_input)
|
|
314
310
|
|
|
315
|
-
|
|
316
|
-
|
|
311
|
+
# Batch embed all chunks
|
|
312
|
+
chunks_indexed = 0
|
|
313
|
+
if embed_inputs:
|
|
314
|
+
embeddings = db_mod.embed_texts_batch(embed_inputs, batch_size=64)
|
|
315
|
+
|
|
316
|
+
with db_mod.transaction(db):
|
|
317
|
+
for i, chunk in enumerate(chunks_to_store):
|
|
318
|
+
chunk_id = db_mod.upsert_doc_chunk(
|
|
319
|
+
db,
|
|
320
|
+
doc_file_id,
|
|
321
|
+
i, # chunk_index
|
|
322
|
+
chunk["section_title"],
|
|
323
|
+
chunk["content"],
|
|
324
|
+
chunk["line_start"],
|
|
325
|
+
chunk["line_end"],
|
|
326
|
+
auto_commit=False,
|
|
327
|
+
)
|
|
328
|
+
db_mod.upsert_doc_embedding(db, chunk_id, embeddings[i], auto_commit=False)
|
|
329
|
+
chunks_indexed += 1
|
|
317
330
|
|
|
318
331
|
return {
|
|
319
332
|
"file": filepath,
|
|
@@ -354,8 +367,7 @@ def index_doc_directory(dirpath: str, db) -> list[dict]:
|
|
|
354
367
|
def extract_docstrings_from_code(db) -> list[dict]:
|
|
355
368
|
"""Extract docstrings from already-indexed code symbols.
|
|
356
369
|
|
|
357
|
-
|
|
358
|
-
docstrings from the source_text field.
|
|
370
|
+
Uses batch embedding generation for better performance.
|
|
359
371
|
|
|
360
372
|
Args:
|
|
361
373
|
db: Database connection.
|
|
@@ -375,6 +387,10 @@ def extract_docstrings_from_code(db) -> list[dict]:
|
|
|
375
387
|
"""
|
|
376
388
|
).fetchall()
|
|
377
389
|
|
|
390
|
+
# === BATCH PROCESSING ===
|
|
391
|
+
docstrings_to_store: list[dict] = []
|
|
392
|
+
embed_inputs: list[str] = []
|
|
393
|
+
|
|
378
394
|
for row in rows:
|
|
379
395
|
symbol_id, name, kind, file_path, line_start, line_end, source_text = row
|
|
380
396
|
|
|
@@ -396,50 +412,68 @@ def extract_docstrings_from_code(db) -> list[dict]:
|
|
|
396
412
|
if existing:
|
|
397
413
|
continue
|
|
398
414
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
"SELECT id FROM doc_files WHERE path = ?", (file_path,)
|
|
402
|
-
).fetchone()
|
|
403
|
-
|
|
404
|
-
if not doc_file:
|
|
405
|
-
# Get file stats
|
|
406
|
-
stat = os.stat(file_path) if os.path.exists(file_path) else None
|
|
407
|
-
doc_file_id = db_mod.upsert_doc_file(
|
|
408
|
-
db,
|
|
409
|
-
file_path,
|
|
410
|
-
stat.st_mtime if stat else 0,
|
|
411
|
-
db_mod.file_hash(file_path) if stat else "",
|
|
412
|
-
"docstring",
|
|
413
|
-
)
|
|
414
|
-
else:
|
|
415
|
-
doc_file_id = doc_file[0]
|
|
416
|
-
|
|
417
|
-
# Get next chunk index
|
|
418
|
-
max_idx = db.execute(
|
|
419
|
-
"SELECT COALESCE(MAX(chunk_index), -1) FROM doc_chunks WHERE doc_file_id = ?",
|
|
420
|
-
(doc_file_id,),
|
|
421
|
-
).fetchone()[0]
|
|
422
|
-
|
|
423
|
-
chunk_id = db_mod.upsert_doc_chunk(
|
|
424
|
-
db,
|
|
425
|
-
doc_file_id,
|
|
426
|
-
max_idx + 1,
|
|
427
|
-
name, # Use symbol name as section title
|
|
428
|
-
docstring,
|
|
429
|
-
line_start,
|
|
430
|
-
line_end,
|
|
431
|
-
)
|
|
432
|
-
|
|
433
|
-
# Generate and store embedding
|
|
434
|
-
embedding = db_mod.embed_text(f"{kind} {name}: {docstring}")
|
|
435
|
-
db_mod.upsert_doc_embedding(db, chunk_id, embedding)
|
|
436
|
-
|
|
437
|
-
results.append({
|
|
438
|
-
"symbol": name,
|
|
415
|
+
docstrings_to_store.append({
|
|
416
|
+
"name": name,
|
|
439
417
|
"kind": kind,
|
|
440
|
-
"
|
|
441
|
-
"
|
|
418
|
+
"file_path": file_path,
|
|
419
|
+
"line_start": line_start,
|
|
420
|
+
"line_end": line_end,
|
|
421
|
+
"docstring": docstring,
|
|
442
422
|
})
|
|
423
|
+
embed_inputs.append(f"{kind} {name}: {docstring}")
|
|
424
|
+
|
|
425
|
+
# Batch embed all docstrings
|
|
426
|
+
if embed_inputs:
|
|
427
|
+
embeddings = db_mod.embed_texts_batch(embed_inputs, batch_size=64)
|
|
428
|
+
|
|
429
|
+
with db_mod.transaction(db):
|
|
430
|
+
for i, doc_info in enumerate(docstrings_to_store):
|
|
431
|
+
file_path = doc_info["file_path"]
|
|
432
|
+
|
|
433
|
+
# Create a doc_file entry for the code file if needed
|
|
434
|
+
doc_file = db.execute(
|
|
435
|
+
"SELECT id FROM doc_files WHERE path = ?", (file_path,)
|
|
436
|
+
).fetchone()
|
|
437
|
+
|
|
438
|
+
if not doc_file:
|
|
439
|
+
# Get file stats
|
|
440
|
+
stat = os.stat(file_path) if os.path.exists(file_path) else None
|
|
441
|
+
doc_file_id = db_mod.upsert_doc_file(
|
|
442
|
+
db,
|
|
443
|
+
file_path,
|
|
444
|
+
stat.st_mtime if stat else 0,
|
|
445
|
+
db_mod.file_hash(file_path) if stat else "",
|
|
446
|
+
"docstring",
|
|
447
|
+
auto_commit=False,
|
|
448
|
+
)
|
|
449
|
+
else:
|
|
450
|
+
doc_file_id = doc_file[0]
|
|
451
|
+
|
|
452
|
+
# Get next chunk index
|
|
453
|
+
max_idx = db.execute(
|
|
454
|
+
"SELECT COALESCE(MAX(chunk_index), -1) FROM doc_chunks WHERE doc_file_id = ?",
|
|
455
|
+
(doc_file_id,),
|
|
456
|
+
).fetchone()[0]
|
|
457
|
+
|
|
458
|
+
chunk_id = db_mod.upsert_doc_chunk(
|
|
459
|
+
db,
|
|
460
|
+
doc_file_id,
|
|
461
|
+
max_idx + 1,
|
|
462
|
+
doc_info["name"], # Use symbol name as section title
|
|
463
|
+
doc_info["docstring"],
|
|
464
|
+
doc_info["line_start"],
|
|
465
|
+
doc_info["line_end"],
|
|
466
|
+
auto_commit=False,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
db_mod.upsert_doc_embedding(db, chunk_id, embeddings[i], auto_commit=False)
|
|
470
|
+
|
|
471
|
+
results.append({
|
|
472
|
+
"symbol": doc_info["name"],
|
|
473
|
+
"kind": doc_info["kind"],
|
|
474
|
+
"file": file_path,
|
|
475
|
+
"docstring_length": len(doc_info["docstring"]),
|
|
476
|
+
})
|
|
443
477
|
|
|
444
478
|
return results
|
|
445
479
|
|
|
@@ -10,6 +10,8 @@ from __future__ import annotations
|
|
|
10
10
|
import logging
|
|
11
11
|
import os
|
|
12
12
|
import sys
|
|
13
|
+
import time
|
|
14
|
+
from contextlib import contextmanager
|
|
13
15
|
from datetime import datetime
|
|
14
16
|
from typing import TextIO
|
|
15
17
|
|
|
@@ -24,6 +26,27 @@ DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
|
24
26
|
_initialized = False
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
@contextmanager
|
|
30
|
+
def log_timing(operation_name: str, logger: logging.Logger):
|
|
31
|
+
"""Context manager to log operation timing.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
operation_name: Name of the operation being timed.
|
|
35
|
+
logger: Logger instance to use for logging.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
with log_timing("Indexing myfile.py", logger):
|
|
39
|
+
# ... indexing code ...
|
|
40
|
+
"""
|
|
41
|
+
start = time.perf_counter()
|
|
42
|
+
logger.debug(f"{operation_name} started")
|
|
43
|
+
try:
|
|
44
|
+
yield
|
|
45
|
+
finally:
|
|
46
|
+
elapsed = time.perf_counter() - start
|
|
47
|
+
logger.info(f"{operation_name} completed in {elapsed:.2f}s")
|
|
48
|
+
|
|
49
|
+
|
|
27
50
|
def setup_logging(level: str = LOG_LEVEL, stream: TextIO = sys.stderr) -> logging.Logger:
|
|
28
51
|
"""Configure structured logging for code-memory.
|
|
29
52
|
|
|
@@ -241,6 +241,8 @@ def _extract_references(tree_root: Node, source: bytes) -> list[dict[str, Any]]:
|
|
|
241
241
|
def index_file(filepath: str, db) -> dict:
|
|
242
242
|
"""Parse a single source file and index its symbols + references.
|
|
243
243
|
|
|
244
|
+
Optimized version using batch embeddings and transaction-based writes.
|
|
245
|
+
|
|
244
246
|
Uses tree-sitter when a grammar is available for the file's language.
|
|
245
247
|
Falls back to indexing the whole file as a single symbol otherwise.
|
|
246
248
|
Skips the file if its ``last_modified`` timestamp has not changed.
|
|
@@ -270,7 +272,7 @@ def index_file(filepath: str, db) -> dict:
|
|
|
270
272
|
source_bytes = Path(filepath).read_bytes()
|
|
271
273
|
source_text = source_bytes.decode("utf-8", errors="replace")
|
|
272
274
|
|
|
273
|
-
fhash = db_mod.file_hash(filepath)
|
|
275
|
+
fhash = db_mod.file_hash(filepath) # Now uses xxHash
|
|
274
276
|
file_id = db_mod.upsert_file(db, filepath, mtime, fhash)
|
|
275
277
|
|
|
276
278
|
# Delete stale data before re-inserting
|
|
@@ -286,52 +288,63 @@ def index_file(filepath: str, db) -> dict:
|
|
|
286
288
|
parser = Parser(lang)
|
|
287
289
|
tree = parser.parse(source_bytes)
|
|
288
290
|
|
|
289
|
-
# Extract
|
|
291
|
+
# Extract symbols
|
|
290
292
|
raw_symbols = _extract_symbols(tree.root_node, source_bytes)
|
|
291
293
|
|
|
292
|
-
#
|
|
293
|
-
|
|
294
|
-
|
|
294
|
+
# === BATCH PROCESSING ===
|
|
295
|
+
# Collect all symbols (including nested) for batch embedding
|
|
296
|
+
all_symbols: list[tuple[dict, int | None]] = [] # (sym, parent_id)
|
|
297
|
+
all_embed_inputs: list[str] = []
|
|
298
|
+
|
|
299
|
+
def _collect_for_batch(sym_list, parent_id=None):
|
|
295
300
|
for sym in sym_list:
|
|
296
|
-
|
|
297
|
-
db, sym["name"], sym["kind"], file_id,
|
|
298
|
-
sym["line_start"], sym["line_end"],
|
|
299
|
-
parent_id, sym["source_text"],
|
|
300
|
-
)
|
|
301
|
-
symbols_indexed += 1
|
|
302
|
-
|
|
303
|
-
# Generate embedding
|
|
301
|
+
all_symbols.append((sym, parent_id))
|
|
304
302
|
embed_input = f"{sym['kind']} {sym['name']}: {sym['source_text'][:1000]}"
|
|
305
|
-
|
|
306
|
-
db_mod.upsert_embedding(db, sym_id, vec)
|
|
307
|
-
|
|
308
|
-
# Recurse into children
|
|
303
|
+
all_embed_inputs.append(embed_input)
|
|
309
304
|
if sym.get("children"):
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
#
|
|
305
|
+
_collect_for_batch(sym["children"], parent_id=None)
|
|
306
|
+
|
|
307
|
+
_collect_for_batch(raw_symbols)
|
|
308
|
+
|
|
309
|
+
# Batch embed all at once
|
|
310
|
+
if all_embed_inputs:
|
|
311
|
+
embeddings = db_mod.embed_texts_batch(all_embed_inputs, batch_size=64)
|
|
312
|
+
|
|
313
|
+
# Store all in single transaction
|
|
314
|
+
with db_mod.transaction(db):
|
|
315
|
+
for i, (sym, parent_id) in enumerate(all_symbols):
|
|
316
|
+
sym_id = db_mod.upsert_symbol(
|
|
317
|
+
db, sym["name"], sym["kind"], file_id,
|
|
318
|
+
sym["line_start"], sym["line_end"],
|
|
319
|
+
parent_id, sym["source_text"],
|
|
320
|
+
auto_commit=False
|
|
321
|
+
)
|
|
322
|
+
db_mod.upsert_embedding(db, sym_id, embeddings[i], auto_commit=False)
|
|
323
|
+
symbols_indexed += 1
|
|
324
|
+
|
|
325
|
+
# Extract and store references (also batched)
|
|
315
326
|
refs = _extract_references(tree.root_node, source_bytes)
|
|
316
|
-
|
|
317
|
-
db_mod.
|
|
318
|
-
|
|
327
|
+
if refs:
|
|
328
|
+
with db_mod.transaction(db):
|
|
329
|
+
for ref in refs:
|
|
330
|
+
db_mod.upsert_reference(db, ref["name"], file_id, ref["line"], auto_commit=False)
|
|
331
|
+
references_indexed += 1
|
|
319
332
|
|
|
320
333
|
else:
|
|
321
334
|
# ── Fallback: index entire file as one symbol ─────────────────
|
|
322
335
|
basename = os.path.basename(filepath)
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
336
|
+
embeddings = db_mod.embed_texts_batch([f"file {basename}: {source_text[:1000]}"])
|
|
337
|
+
|
|
338
|
+
with db_mod.transaction(db):
|
|
339
|
+
sym_id = db_mod.upsert_symbol(
|
|
340
|
+
db, basename, "file", file_id,
|
|
341
|
+
1, source_text.count("\n") + 1,
|
|
342
|
+
None, source_text[:5000],
|
|
343
|
+
auto_commit=False
|
|
344
|
+
)
|
|
345
|
+
db_mod.upsert_embedding(db, sym_id, embeddings[0], auto_commit=False)
|
|
346
|
+
symbols_indexed += 1
|
|
347
|
+
|
|
335
348
|
return {
|
|
336
349
|
"file": filepath,
|
|
337
350
|
"symbols_indexed": symbols_indexed,
|
|
@@ -357,8 +370,11 @@ def index_directory(dirpath: str, db) -> list[dict]:
|
|
|
357
370
|
Returns:
|
|
358
371
|
A list of per-file result dicts (see :func:`index_file`).
|
|
359
372
|
"""
|
|
373
|
+
import time
|
|
374
|
+
|
|
360
375
|
results: list[dict] = []
|
|
361
376
|
dirpath = os.path.abspath(dirpath)
|
|
377
|
+
total_start = time.perf_counter()
|
|
362
378
|
|
|
363
379
|
for root, dirs, files in os.walk(dirpath, topdown=True):
|
|
364
380
|
# Prune skipped directories in-place
|
|
@@ -385,4 +401,17 @@ def index_directory(dirpath: str, db) -> list[dict]:
|
|
|
385
401
|
"skipped": True,
|
|
386
402
|
"error": True,
|
|
387
403
|
})
|
|
404
|
+
|
|
405
|
+
# Log performance summary
|
|
406
|
+
total_elapsed = time.perf_counter() - total_start
|
|
407
|
+
total_symbols = sum(r.get("symbols_indexed", 0) for r in results)
|
|
408
|
+
total_refs = sum(r.get("references_indexed", 0) for r in results)
|
|
409
|
+
files_indexed = sum(1 for r in results if not r.get("skipped"))
|
|
410
|
+
files_skipped = sum(1 for r in results if r.get("skipped") and not r.get("error"))
|
|
411
|
+
|
|
412
|
+
logger.info(
|
|
413
|
+
"Indexed %d files (%d skipped) in %.2fs - %d symbols, %d references",
|
|
414
|
+
files_indexed, files_skipped, total_elapsed, total_symbols, total_refs
|
|
415
|
+
)
|
|
416
|
+
|
|
388
417
|
return results
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-memory"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.5"
|
|
8
8
|
description = "A deterministic, high-precision code intelligence MCP server"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -37,6 +37,7 @@ dependencies = [
|
|
|
37
37
|
"tree-sitter-ruby>=0.23.1",
|
|
38
38
|
"tree-sitter-rust>=0.24.0",
|
|
39
39
|
"tree-sitter-typescript>=0.23.2",
|
|
40
|
+
"xxhash>=3.6.0",
|
|
40
41
|
]
|
|
41
42
|
|
|
42
43
|
[project.optional-dependencies]
|
|
@@ -524,6 +524,11 @@ def search_history(
|
|
|
524
524
|
# ── Entrypoint ────────────────────────────────────────────────────────────
|
|
525
525
|
def main():
|
|
526
526
|
"""Entry point for the MCP server when installed as a package."""
|
|
527
|
+
# Warm up embedding model to avoid cold-start latency
|
|
528
|
+
logger.info("Warming up embedding model...")
|
|
529
|
+
db_mod.warmup_embedding_model()
|
|
530
|
+
logger.info("Embedding model ready")
|
|
531
|
+
|
|
527
532
|
mcp.run()
|
|
528
533
|
|
|
529
534
|
|
|
@@ -109,7 +109,7 @@ wheels = [
|
|
|
109
109
|
|
|
110
110
|
[[package]]
|
|
111
111
|
name = "code-memory"
|
|
112
|
-
version = "1.0.
|
|
112
|
+
version = "1.0.4"
|
|
113
113
|
source = { editable = "." }
|
|
114
114
|
dependencies = [
|
|
115
115
|
{ name = "gitpython" },
|
|
@@ -128,6 +128,7 @@ dependencies = [
|
|
|
128
128
|
{ name = "tree-sitter-ruby" },
|
|
129
129
|
{ name = "tree-sitter-rust" },
|
|
130
130
|
{ name = "tree-sitter-typescript" },
|
|
131
|
+
{ name = "xxhash" },
|
|
131
132
|
]
|
|
132
133
|
|
|
133
134
|
[package.optional-dependencies]
|
|
@@ -162,6 +163,7 @@ requires-dist = [
|
|
|
162
163
|
{ name = "tree-sitter-ruby", specifier = ">=0.23.1" },
|
|
163
164
|
{ name = "tree-sitter-rust", specifier = ">=0.24.0" },
|
|
164
165
|
{ name = "tree-sitter-typescript", specifier = ">=0.23.2" },
|
|
166
|
+
{ name = "xxhash", specifier = ">=3.6.0" },
|
|
165
167
|
]
|
|
166
168
|
provides-extras = ["dev"]
|
|
167
169
|
|
|
@@ -1919,3 +1921,71 @@ sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e
|
|
|
1919
1921
|
wheels = [
|
|
1920
1922
|
{ url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" },
|
|
1921
1923
|
]
|
|
1924
|
+
|
|
1925
|
+
[[package]]
|
|
1926
|
+
name = "xxhash"
|
|
1927
|
+
version = "3.6.0"
|
|
1928
|
+
source = { registry = "https://pypi.org/simple" }
|
|
1929
|
+
sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" }
|
|
1930
|
+
wheels = [
|
|
1931
|
+
{ url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" },
|
|
1932
|
+
{ url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" },
|
|
1933
|
+
{ url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" },
|
|
1934
|
+
{ url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" },
|
|
1935
|
+
{ url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" },
|
|
1936
|
+
{ url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" },
|
|
1937
|
+
{ url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" },
|
|
1938
|
+
{ url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" },
|
|
1939
|
+
{ url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" },
|
|
1940
|
+
{ url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" },
|
|
1941
|
+
{ url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" },
|
|
1942
|
+
{ url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" },
|
|
1943
|
+
{ url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" },
|
|
1944
|
+
{ url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" },
|
|
1945
|
+
{ url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" },
|
|
1946
|
+
{ url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" },
|
|
1947
|
+
{ url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" },
|
|
1948
|
+
{ url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" },
|
|
1949
|
+
{ url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" },
|
|
1950
|
+
{ url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" },
|
|
1951
|
+
{ url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" },
|
|
1952
|
+
{ url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" },
|
|
1953
|
+
{ url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" },
|
|
1954
|
+
{ url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" },
|
|
1955
|
+
{ url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" },
|
|
1956
|
+
{ url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" },
|
|
1957
|
+
{ url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" },
|
|
1958
|
+
{ url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" },
|
|
1959
|
+
{ url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" },
|
|
1960
|
+
{ url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" },
|
|
1961
|
+
{ url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" },
|
|
1962
|
+
{ url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" },
|
|
1963
|
+
{ url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" },
|
|
1964
|
+
{ url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" },
|
|
1965
|
+
{ url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" },
|
|
1966
|
+
{ url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" },
|
|
1967
|
+
{ url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" },
|
|
1968
|
+
{ url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" },
|
|
1969
|
+
{ url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" },
|
|
1970
|
+
{ url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" },
|
|
1971
|
+
{ url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" },
|
|
1972
|
+
{ url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" },
|
|
1973
|
+
{ url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" },
|
|
1974
|
+
{ url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" },
|
|
1975
|
+
{ url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" },
|
|
1976
|
+
{ url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" },
|
|
1977
|
+
{ url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" },
|
|
1978
|
+
{ url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" },
|
|
1979
|
+
{ url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" },
|
|
1980
|
+
{ url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" },
|
|
1981
|
+
{ url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" },
|
|
1982
|
+
{ url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" },
|
|
1983
|
+
{ url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" },
|
|
1984
|
+
{ url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" },
|
|
1985
|
+
{ url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" },
|
|
1986
|
+
{ url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" },
|
|
1987
|
+
{ url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" },
|
|
1988
|
+
{ url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" },
|
|
1989
|
+
{ url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" },
|
|
1990
|
+
{ url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" },
|
|
1991
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|