codespine 1.0.6__tar.gz → 1.0.8__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.
- {codespine-1.0.6 → codespine-1.0.8}/PKG-INFO +1 -1
- {codespine-1.0.6 → codespine-1.0.8}/codespine/__init__.py +1 -1
- {codespine-1.0.6 → codespine-1.0.8}/codespine/cli.py +152 -4
- {codespine-1.0.6 → codespine-1.0.8}/codespine/indexer/engine.py +417 -212
- {codespine-1.0.6 → codespine-1.0.8}/codespine.egg-info/PKG-INFO +1 -1
- {codespine-1.0.6 → codespine-1.0.8}/codespine.egg-info/SOURCES.txt +1 -0
- {codespine-1.0.6 → codespine-1.0.8}/pyproject.toml +1 -1
- codespine-1.0.8/tests/test_parse_resilience.py +290 -0
- {codespine-1.0.6 → codespine-1.0.8}/LICENSE +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/README.md +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/analysis/__init__.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/analysis/community.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/analysis/context.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/analysis/coupling.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/analysis/crossmodule.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/analysis/deadcode.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/analysis/flow.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/analysis/impact.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/cache/__init__.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/cache/result_cache.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/config.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/db/__init__.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/db/_cypher_compat.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/db/duckdb_store.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/db/schema.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/db/store.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/diff/__init__.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/diff/branch_diff.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/guide.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/indexer/__init__.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/indexer/call_resolver.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/indexer/di_resolver.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/indexer/java_parser.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/indexer/symbol_builder.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/mcp/__init__.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/mcp/server.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/noise/__init__.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/noise/blocklist.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/overlay/__init__.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/overlay/git_state.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/overlay/merge.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/overlay/store.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/search/__init__.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/search/bm25.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/search/fuzzy.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/search/hybrid.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/search/rrf.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/search/vector.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/sharding/__init__.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/sharding/router.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/sharding/store.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/watch/__init__.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/watch/git_hook.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine/watch/watcher.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine.egg-info/dependency_links.txt +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine.egg-info/entry_points.txt +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine.egg-info/requires.txt +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/codespine.egg-info/top_level.txt +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/gindex.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/setup.cfg +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_branch_diff_normalize.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_call_resolver.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_community_detection.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_cypher_compat.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_deadcode.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_duckdb_store.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_index_and_hybrid.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_java_parser.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_multimodule_index.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_overlay.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_result_cache.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_search_ranking.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_sharding.py +0 -0
- {codespine-1.0.6 → codespine-1.0.8}/tests/test_store_recovery.py +0 -0
|
@@ -137,10 +137,18 @@ def _index_shard_group(
|
|
|
137
137
|
|
|
138
138
|
for mod_path, project_id in modules:
|
|
139
139
|
# Per-module progress state (local — no shared mutation).
|
|
140
|
-
parse_state: dict = {
|
|
141
|
-
|
|
140
|
+
parse_state: dict = {
|
|
141
|
+
"shown": False, "indexed": 0, "total": 0,
|
|
142
|
+
"last_ts": 0.0, "printed_zero": False,
|
|
143
|
+
"current_file": "", "elapsed": 0.0,
|
|
144
|
+
"last_done": -1, "frozen_since": 0.0, "stall_warned": False,
|
|
145
|
+
}
|
|
142
146
|
call_state: dict = {"shown": False, "count": 0, "last_ts": 0.0,
|
|
143
147
|
"started_at": 0.0}
|
|
148
|
+
db_state: dict = {
|
|
149
|
+
"shown": False, "done": 0, "total": 0, "last_ts": 0.0,
|
|
150
|
+
"started_at": 0.0,
|
|
151
|
+
}
|
|
144
152
|
|
|
145
153
|
def _progress(event: str, payload: dict) -> None:
|
|
146
154
|
now = time.perf_counter()
|
|
@@ -160,11 +168,61 @@ def _index_shard_group(
|
|
|
160
168
|
_phase(f"{prefix}Parsing code...", "0/0")
|
|
161
169
|
parse_state["printed_zero"] = True
|
|
162
170
|
return
|
|
171
|
+
if event == "parse_heartbeat":
|
|
172
|
+
# Fires every 2s from a daemon thread — keeps spinner alive
|
|
173
|
+
# even when all worker threads are busy or one is hanging.
|
|
174
|
+
done = int(payload.get("done", 0))
|
|
175
|
+
total = int(payload.get("total", 0))
|
|
176
|
+
current = str(payload.get("current_file", ""))
|
|
177
|
+
elapsed_s = float(payload.get("elapsed", 0.0))
|
|
178
|
+
parse_state["indexed"] = done
|
|
179
|
+
parse_state["total"] = total
|
|
180
|
+
parse_state["current_file"] = current
|
|
181
|
+
parse_state["elapsed"] = elapsed_s
|
|
182
|
+
if total > 0 and not parallel:
|
|
183
|
+
basename = os.path.basename(current) if current else ""
|
|
184
|
+
click.echo(
|
|
185
|
+
f"\r{_spinner_char()} {prefix}Parsing code... "
|
|
186
|
+
f"{_bar(done, total)} {done}/{total} "
|
|
187
|
+
f"{basename[:38]:<38} {elapsed_s:.0f}s ",
|
|
188
|
+
nl=False,
|
|
189
|
+
)
|
|
190
|
+
parse_state["shown"] = True
|
|
191
|
+
parse_state["last_ts"] = now
|
|
192
|
+
|
|
193
|
+
# ── Stall detection ──────────────────────────────────────
|
|
194
|
+
if done == parse_state["last_done"]:
|
|
195
|
+
if parse_state["frozen_since"] == 0.0:
|
|
196
|
+
parse_state["frozen_since"] = now
|
|
197
|
+
stalled_for = now - parse_state["frozen_since"]
|
|
198
|
+
if stalled_for >= 15.0 and not parse_state["stall_warned"]:
|
|
199
|
+
parse_state["stall_warned"] = True
|
|
200
|
+
basename = os.path.basename(current) if current else "unknown"
|
|
201
|
+
with output_lock:
|
|
202
|
+
click.echo() # break out of \r line
|
|
203
|
+
click.secho(
|
|
204
|
+
f" ⚠ Parsing stalled on {basename} for "
|
|
205
|
+
f"{stalled_for:.0f}s — file may be pathological.\n"
|
|
206
|
+
f" Timeout at {os.environ.get('CODESPINE_PARSE_TIMEOUT_SECS', '60')}s. "
|
|
207
|
+
f"To skip large files: "
|
|
208
|
+
f"export CODESPINE_MAX_FILE_BYTES=2097152",
|
|
209
|
+
fg="yellow",
|
|
210
|
+
)
|
|
211
|
+
else:
|
|
212
|
+
parse_state["last_done"] = done
|
|
213
|
+
parse_state["frozen_since"] = 0.0
|
|
214
|
+
parse_state["stall_warned"] = False
|
|
215
|
+
return
|
|
163
216
|
if event == "parse_progress":
|
|
164
217
|
indexed = int(payload.get("indexed", 0))
|
|
165
218
|
total = int(payload.get("total", 0))
|
|
166
219
|
parse_state["indexed"] = indexed
|
|
167
220
|
parse_state["total"] = total
|
|
221
|
+
# Reset stall tracker on actual progress
|
|
222
|
+
if indexed != parse_state["last_done"]:
|
|
223
|
+
parse_state["last_done"] = indexed
|
|
224
|
+
parse_state["frozen_since"] = 0.0
|
|
225
|
+
parse_state["stall_warned"] = False
|
|
168
226
|
if total == 0:
|
|
169
227
|
return
|
|
170
228
|
if indexed == total or (now - parse_state["last_ts"]) >= 0.2:
|
|
@@ -183,11 +241,101 @@ def _index_shard_group(
|
|
|
183
241
|
parse_state["shown"] = True
|
|
184
242
|
parse_state["last_ts"] = now
|
|
185
243
|
return
|
|
186
|
-
if event
|
|
244
|
+
if event == "db_write_start":
|
|
187
245
|
if parse_state["shown"]:
|
|
188
246
|
with output_lock:
|
|
189
247
|
click.echo()
|
|
190
248
|
parse_state["shown"] = False
|
|
249
|
+
total = int(payload.get("total", 0))
|
|
250
|
+
deleted = int(payload.get("deleted_files", 0))
|
|
251
|
+
db_state["done"] = 0
|
|
252
|
+
db_state["total"] = total
|
|
253
|
+
db_state["started_at"] = now
|
|
254
|
+
status = f"starting ({total} files"
|
|
255
|
+
if deleted:
|
|
256
|
+
status += f", {deleted} deleted"
|
|
257
|
+
status += ")"
|
|
258
|
+
with output_lock:
|
|
259
|
+
_phase(f"{prefix}Writing index...", status)
|
|
260
|
+
return
|
|
261
|
+
if event == "db_write_heartbeat":
|
|
262
|
+
done = int(payload.get("done", 0))
|
|
263
|
+
total = int(payload.get("total", 0))
|
|
264
|
+
classes = int(payload.get("classes", 0))
|
|
265
|
+
methods = int(payload.get("methods", 0))
|
|
266
|
+
phase = str(payload.get("phase", "writing"))
|
|
267
|
+
elapsed_s = float(payload.get("elapsed", 0.0))
|
|
268
|
+
db_state["done"] = done
|
|
269
|
+
db_state["total"] = total
|
|
270
|
+
if not parallel:
|
|
271
|
+
click.echo(
|
|
272
|
+
f"\r{_spinner_char()} {prefix}Writing index... "
|
|
273
|
+
f"{_bar(done, total)} {done}/{total} "
|
|
274
|
+
f"{classes} classes / {methods} methods "
|
|
275
|
+
f"{phase[:18]:<18} {elapsed_s:.0f}s ",
|
|
276
|
+
nl=False,
|
|
277
|
+
)
|
|
278
|
+
else:
|
|
279
|
+
with output_lock:
|
|
280
|
+
click.echo(
|
|
281
|
+
f"\r{prefix}Writing {done}/{total} "
|
|
282
|
+
f"({classes} classes, {methods} methods, {elapsed_s:.0f}s) ",
|
|
283
|
+
nl=False,
|
|
284
|
+
)
|
|
285
|
+
db_state["shown"] = True
|
|
286
|
+
db_state["last_ts"] = now
|
|
287
|
+
return
|
|
288
|
+
if event == "db_write_progress":
|
|
289
|
+
done = int(payload.get("done", 0))
|
|
290
|
+
total = int(payload.get("total", 0))
|
|
291
|
+
classes = int(payload.get("classes", 0))
|
|
292
|
+
methods = int(payload.get("methods", 0))
|
|
293
|
+
phase = str(payload.get("phase", "writing"))
|
|
294
|
+
db_state["done"] = done
|
|
295
|
+
db_state["total"] = total
|
|
296
|
+
if total == 0 and done == 0:
|
|
297
|
+
return
|
|
298
|
+
if done == total or (now - db_state["last_ts"]) >= 0.25:
|
|
299
|
+
elapsed_s = now - db_state["started_at"]
|
|
300
|
+
if not parallel:
|
|
301
|
+
click.echo(
|
|
302
|
+
f"\r{_spinner_char()} {prefix}Writing index... "
|
|
303
|
+
f"{_bar(done, total)} {done}/{total} "
|
|
304
|
+
f"{classes} classes / {methods} methods "
|
|
305
|
+
f"{phase[:18]:<18} {elapsed_s:.0f}s ",
|
|
306
|
+
nl=False,
|
|
307
|
+
)
|
|
308
|
+
else:
|
|
309
|
+
with output_lock:
|
|
310
|
+
click.echo(
|
|
311
|
+
f"\r{prefix}Writing {done}/{total} "
|
|
312
|
+
f"({classes} classes, {methods} methods, {elapsed_s:.0f}s) ",
|
|
313
|
+
nl=False,
|
|
314
|
+
)
|
|
315
|
+
db_state["shown"] = True
|
|
316
|
+
db_state["last_ts"] = now
|
|
317
|
+
return
|
|
318
|
+
if event == "db_write_done":
|
|
319
|
+
if db_state["shown"]:
|
|
320
|
+
with output_lock:
|
|
321
|
+
click.echo()
|
|
322
|
+
db_state["shown"] = False
|
|
323
|
+
files = int(payload.get("files_indexed", db_state["done"]))
|
|
324
|
+
classes = int(payload.get("classes", 0))
|
|
325
|
+
methods = int(payload.get("methods", 0))
|
|
326
|
+
elapsed_s = float(payload.get("elapsed", 0.0))
|
|
327
|
+
with output_lock:
|
|
328
|
+
_phase(
|
|
329
|
+
f"{prefix}Writing index...",
|
|
330
|
+
f"{files} files, {classes} classes, {methods} methods ({elapsed_s:.1f}s)",
|
|
331
|
+
)
|
|
332
|
+
return
|
|
333
|
+
if event in ("resolve_calls_start",):
|
|
334
|
+
if parse_state["shown"] or db_state["shown"]:
|
|
335
|
+
with output_lock:
|
|
336
|
+
click.echo()
|
|
337
|
+
parse_state["shown"] = False
|
|
338
|
+
db_state["shown"] = False
|
|
191
339
|
call_state["started_at"] = now
|
|
192
340
|
with output_lock:
|
|
193
341
|
_phase(f"{prefix}Tracing calls...", "starting...")
|
|
@@ -255,7 +403,7 @@ def _index_shard_group(
|
|
|
255
403
|
total_files += result.files_found
|
|
256
404
|
|
|
257
405
|
# Flush any dangling progress line.
|
|
258
|
-
if parse_state["shown"]:
|
|
406
|
+
if parse_state["shown"] or db_state["shown"]:
|
|
259
407
|
with output_lock:
|
|
260
408
|
click.echo()
|
|
261
409
|
|