codespine 1.0.10__tar.gz → 1.0.11__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.10 → codespine-1.0.11}/PKG-INFO +5 -4
- {codespine-1.0.10 → codespine-1.0.11}/README.md +4 -3
- {codespine-1.0.10 → codespine-1.0.11}/codespine/__init__.py +1 -1
- {codespine-1.0.10 → codespine-1.0.11}/codespine/cli.py +101 -3
- {codespine-1.0.10 → codespine-1.0.11}/codespine/config.py +3 -3
- {codespine-1.0.10 → codespine-1.0.11}/codespine/indexer/engine.py +40 -18
- {codespine-1.0.10 → codespine-1.0.11}/codespine.egg-info/PKG-INFO +5 -4
- {codespine-1.0.10 → codespine-1.0.11}/pyproject.toml +1 -1
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_tasks.py +12 -0
- {codespine-1.0.10 → codespine-1.0.11}/LICENSE +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/analysis/__init__.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/analysis/community.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/analysis/context.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/analysis/coupling.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/analysis/crossmodule.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/analysis/deadcode.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/analysis/flow.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/analysis/impact.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/cache/__init__.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/cache/result_cache.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/db/__init__.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/db/_cypher_compat.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/db/duckdb_store.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/db/schema.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/db/store.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/diff/__init__.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/diff/branch_diff.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/guide.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/health.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/indexer/__init__.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/indexer/call_resolver.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/indexer/di_resolver.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/indexer/java_parser.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/indexer/symbol_builder.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/mcp/__init__.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/mcp/server.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/noise/__init__.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/noise/blocklist.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/overlay/__init__.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/overlay/git_state.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/overlay/merge.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/overlay/store.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/search/__init__.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/search/bm25.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/search/fuzzy.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/search/hybrid.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/search/rrf.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/search/vector.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/sharding/__init__.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/sharding/router.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/sharding/store.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/tasks.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/watch/__init__.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/watch/git_hook.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine/watch/watcher.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine.egg-info/SOURCES.txt +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine.egg-info/dependency_links.txt +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine.egg-info/entry_points.txt +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine.egg-info/requires.txt +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/codespine.egg-info/top_level.txt +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/gindex.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/setup.cfg +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_branch_diff_normalize.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_call_resolver.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_community_detection.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_cypher_compat.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_deadcode.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_duckdb_store.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_health.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_index_and_hybrid.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_java_parser.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_multimodule_index.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_overlay.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_parse_resilience.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_result_cache.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_search_ranking.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_sharding.py +0 -0
- {codespine-1.0.10 → codespine-1.0.11}/tests/test_store_recovery.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codespine
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.11
|
|
4
4
|
Summary: Local Java code intelligence indexer backed by a graph database
|
|
5
5
|
Author: CodeSpine contributors
|
|
6
6
|
License: MIT License
|
|
@@ -66,7 +66,7 @@ Dynamic: license-file
|
|
|
66
66
|
|
|
67
67
|
# CodeSpine
|
|
68
68
|
|
|
69
|
-
**v1.0.
|
|
69
|
+
**v1.0.11** — Local Java code intelligence for coding agents, backed by a graph database.
|
|
70
70
|
|
|
71
71
|
CodeSpine cuts token burn for coding agents working on Java codebases.
|
|
72
72
|
|
|
@@ -111,6 +111,7 @@ pip install "codespine[ui]"
|
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
The current lite UI is dependency-free and served locally by CodeSpine; the `ui` extra is the stable add-on install target for the browser explorer.
|
|
114
|
+
Quotes are required in zsh: use `pip install "codespine[ui]"`, not `pip install codespine[ui]`.
|
|
114
115
|
|
|
115
116
|
Everything at once (ml + community detection):
|
|
116
117
|
|
|
@@ -381,7 +382,7 @@ codespine force-reset # emergency: delete all data files
|
|
|
381
382
|
|
|
382
383
|
`analyse` defaults to incremental mode. Repeat runs only process changed files and are fast.
|
|
383
384
|
|
|
384
|
-
`analyse` runs in fast mode by default: it indexes the core graph,
|
|
385
|
+
`analyse` runs in fast mode by default: it indexes the core graph, honors the foreground time budget, publishes a read replica, then continues unfinished core indexing or enrichment in the background. Use `codespine background` or `codespine ui` to watch that work. Use `--complete --deep` when you want those passes refreshed before the command returns.
|
|
385
386
|
|
|
386
387
|
---
|
|
387
388
|
|
|
@@ -567,7 +568,7 @@ The deep analysis phase covers four passes that are expensive but optional:
|
|
|
567
568
|
| Dead code | Finds methods with no callers (Java-aware exemptions) | Cleanup audits |
|
|
568
569
|
| Change coupling | Analyses git history for co-changed file pairs | `get_change_coupling`, `related` |
|
|
569
570
|
|
|
570
|
-
**Fast default:** `codespine analyse` prioritizes a queryable core index. Communities, flows, dead-code, git coupling, and cross-module links are queued in a detached background enrichment job unless you use `--complete`.
|
|
571
|
+
**Fast default:** `codespine analyse` prioritizes a queryable core index and keeps the foreground run inside the configured budget. If core indexing or call/type resolution cannot finish in time, the partial index is published and a background continuation finishes the core graph. Communities, flows, dead-code, git coupling, and cross-module links are queued in a detached background enrichment job unless you use `--complete`.
|
|
571
572
|
|
|
572
573
|
**Health checks:** every analyse run now performs a small self-test query suite and reports index anomalies such as large projects with zero call edges. Use `codespine health` for the terminal dashboard or `codespine self-test --json` in CI.
|
|
573
574
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CodeSpine
|
|
2
2
|
|
|
3
|
-
**v1.0.
|
|
3
|
+
**v1.0.11** — Local Java code intelligence for coding agents, backed by a graph database.
|
|
4
4
|
|
|
5
5
|
CodeSpine cuts token burn for coding agents working on Java codebases.
|
|
6
6
|
|
|
@@ -45,6 +45,7 @@ pip install "codespine[ui]"
|
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
The current lite UI is dependency-free and served locally by CodeSpine; the `ui` extra is the stable add-on install target for the browser explorer.
|
|
48
|
+
Quotes are required in zsh: use `pip install "codespine[ui]"`, not `pip install codespine[ui]`.
|
|
48
49
|
|
|
49
50
|
Everything at once (ml + community detection):
|
|
50
51
|
|
|
@@ -315,7 +316,7 @@ codespine force-reset # emergency: delete all data files
|
|
|
315
316
|
|
|
316
317
|
`analyse` defaults to incremental mode. Repeat runs only process changed files and are fast.
|
|
317
318
|
|
|
318
|
-
`analyse` runs in fast mode by default: it indexes the core graph,
|
|
319
|
+
`analyse` runs in fast mode by default: it indexes the core graph, honors the foreground time budget, publishes a read replica, then continues unfinished core indexing or enrichment in the background. Use `codespine background` or `codespine ui` to watch that work. Use `--complete --deep` when you want those passes refreshed before the command returns.
|
|
319
320
|
|
|
320
321
|
---
|
|
321
322
|
|
|
@@ -501,7 +502,7 @@ The deep analysis phase covers four passes that are expensive but optional:
|
|
|
501
502
|
| Dead code | Finds methods with no callers (Java-aware exemptions) | Cleanup audits |
|
|
502
503
|
| Change coupling | Analyses git history for co-changed file pairs | `get_change_coupling`, `related` |
|
|
503
504
|
|
|
504
|
-
**Fast default:** `codespine analyse` prioritizes a queryable core index. Communities, flows, dead-code, git coupling, and cross-module links are queued in a detached background enrichment job unless you use `--complete`.
|
|
505
|
+
**Fast default:** `codespine analyse` prioritizes a queryable core index and keeps the foreground run inside the configured budget. If core indexing or call/type resolution cannot finish in time, the partial index is published and a background continuation finishes the core graph. Communities, flows, dead-code, git coupling, and cross-module links are queued in a detached background enrichment job unless you use `--complete`.
|
|
505
506
|
|
|
506
507
|
**Health checks:** every analyse run now performs a small self-test query suite and reports index anomalies such as large projects with zero call edges. Use `codespine health` for the terminal dashboard or `codespine self-test --json` in CI.
|
|
507
508
|
|
|
@@ -98,6 +98,32 @@ def _spawn_background_enrichment(path: str) -> bool:
|
|
|
98
98
|
return False
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
def _spawn_background_continuation(path: str) -> bool:
|
|
102
|
+
"""Continue a budget-paused core index in a detached process."""
|
|
103
|
+
task_id = create_task(
|
|
104
|
+
"indexing",
|
|
105
|
+
"Background core indexing",
|
|
106
|
+
path=path,
|
|
107
|
+
metadata={"trigger": "analyse-budget"},
|
|
108
|
+
)
|
|
109
|
+
try:
|
|
110
|
+
proc = subprocess.Popen(
|
|
111
|
+
[sys.executable, "-m", "codespine.cli", "continue-background", path, "--task-id", task_id],
|
|
112
|
+
stdin=subprocess.DEVNULL,
|
|
113
|
+
stdout=subprocess.DEVNULL,
|
|
114
|
+
stderr=subprocess.DEVNULL,
|
|
115
|
+
start_new_session=True,
|
|
116
|
+
cwd=os.getcwd(),
|
|
117
|
+
env=os.environ.copy(),
|
|
118
|
+
)
|
|
119
|
+
update_task(task_id, status="running", phase="spawned", pid=proc.pid)
|
|
120
|
+
return True
|
|
121
|
+
except Exception as exc: # noqa: BLE001
|
|
122
|
+
finish_task(task_id, "failed", str(exc))
|
|
123
|
+
LOGGER.warning("Unable to spawn background continuation: %s", exc)
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
|
|
101
127
|
def _db_size_bytes(path: str) -> int:
|
|
102
128
|
if os.path.isfile(path):
|
|
103
129
|
return os.path.getsize(path)
|
|
@@ -373,6 +399,16 @@ def _index_shard_group(
|
|
|
373
399
|
f"{files} files, {classes} classes, {methods} methods ({elapsed_s:.1f}s)",
|
|
374
400
|
)
|
|
375
401
|
return
|
|
402
|
+
if event == "budget_exhausted":
|
|
403
|
+
files = int(payload.get("files_indexed", 0))
|
|
404
|
+
total = int(payload.get("total", 0))
|
|
405
|
+
phase = str(payload.get("phase", "indexing"))
|
|
406
|
+
with output_lock:
|
|
407
|
+
_phase(
|
|
408
|
+
f"{prefix}Foreground budget...",
|
|
409
|
+
f"paused during {phase} ({files}/{total} files); continuing in background",
|
|
410
|
+
)
|
|
411
|
+
return
|
|
376
412
|
if event in ("resolve_calls_start",):
|
|
377
413
|
if parse_state["shown"] or db_state["shown"]:
|
|
378
414
|
with output_lock:
|
|
@@ -906,7 +942,16 @@ def analyse(
|
|
|
906
942
|
recycle = getattr(store, "_recycle_conn", None)
|
|
907
943
|
if callable(recycle):
|
|
908
944
|
recycle()
|
|
909
|
-
|
|
945
|
+
core_partial = any(bool(getattr(result, "partial", False)) for result in all_results)
|
|
946
|
+
if fast and core_partial:
|
|
947
|
+
_live_phase(snap_label, "copying partial core")
|
|
948
|
+
sg.snapshot_all(background=False)
|
|
949
|
+
_finish_phase(snap_label, "partial index visible")
|
|
950
|
+
if _spawn_background_continuation(abs_path):
|
|
951
|
+
_phase("Background indexing...", "core indexing continues; run 'codespine background'")
|
|
952
|
+
else:
|
|
953
|
+
_phase("Background indexing...", "not started; rerun 'codespine analyse' to continue")
|
|
954
|
+
elif fast and _spawn_background_enrichment(abs_path):
|
|
910
955
|
_phase(snap_label, "core snapshot now; enrichment continues in background")
|
|
911
956
|
else:
|
|
912
957
|
_live_phase(snap_label, "copying")
|
|
@@ -1023,6 +1068,50 @@ def enrich_background(path: str, task_id: str | None) -> None:
|
|
|
1023
1068
|
raise
|
|
1024
1069
|
|
|
1025
1070
|
|
|
1071
|
+
@main.command("continue-background", hidden=True)
|
|
1072
|
+
@click.argument("path", type=click.Path(exists=True))
|
|
1073
|
+
@click.option("--task-id", default=None, hidden=True)
|
|
1074
|
+
def continue_background(path: str, task_id: str | None) -> None:
|
|
1075
|
+
"""Continue core indexing after a foreground budget pause."""
|
|
1076
|
+
abs_path = os.path.abspath(path)
|
|
1077
|
+
if task_id is None:
|
|
1078
|
+
task_id = create_task("indexing", "Background core indexing", path=abs_path)
|
|
1079
|
+
update_task(
|
|
1080
|
+
task_id,
|
|
1081
|
+
status="running",
|
|
1082
|
+
phase="core indexing",
|
|
1083
|
+
pid=os.getpid(),
|
|
1084
|
+
progress=0.10,
|
|
1085
|
+
detail="Continuing analyse without the foreground budget",
|
|
1086
|
+
)
|
|
1087
|
+
try:
|
|
1088
|
+
with open(SETTINGS.log_file, "a", encoding="utf-8") as log:
|
|
1089
|
+
proc = subprocess.run(
|
|
1090
|
+
[
|
|
1091
|
+
sys.executable,
|
|
1092
|
+
"-m",
|
|
1093
|
+
"codespine.cli",
|
|
1094
|
+
"analyse",
|
|
1095
|
+
abs_path,
|
|
1096
|
+
"--budget",
|
|
1097
|
+
"0",
|
|
1098
|
+
"--allow-running",
|
|
1099
|
+
],
|
|
1100
|
+
cwd=os.getcwd(),
|
|
1101
|
+
stdout=log,
|
|
1102
|
+
stderr=subprocess.STDOUT,
|
|
1103
|
+
check=False,
|
|
1104
|
+
)
|
|
1105
|
+
if proc.returncode == 0:
|
|
1106
|
+
finish_task(task_id, "succeeded", "Background core indexing complete")
|
|
1107
|
+
return
|
|
1108
|
+
finish_task(task_id, "failed", f"Background analyse exited with code {proc.returncode}")
|
|
1109
|
+
raise click.ClickException(f"Background analyse exited with code {proc.returncode}")
|
|
1110
|
+
except Exception as exc: # noqa: BLE001
|
|
1111
|
+
finish_task(task_id, "failed", str(exc))
|
|
1112
|
+
raise
|
|
1113
|
+
|
|
1114
|
+
|
|
1026
1115
|
@main.command()
|
|
1027
1116
|
@click.argument("query")
|
|
1028
1117
|
@click.option("--k", default=20, show_default=True, type=int)
|
|
@@ -1363,7 +1452,13 @@ def tasks_cmd(include_finished: bool, limit: int, as_json: bool) -> None:
|
|
|
1363
1452
|
|
|
1364
1453
|
|
|
1365
1454
|
@main.command("background")
|
|
1366
|
-
@click.option(
|
|
1455
|
+
@click.option(
|
|
1456
|
+
"--all/--running-only",
|
|
1457
|
+
"include_finished",
|
|
1458
|
+
default=True,
|
|
1459
|
+
show_default=True,
|
|
1460
|
+
help="Include completed and failed tasks, or show only currently running work.",
|
|
1461
|
+
)
|
|
1367
1462
|
@click.option("--limit", default=20, show_default=True, type=int)
|
|
1368
1463
|
@click.option("--json", "as_json", is_flag=True)
|
|
1369
1464
|
def background_cmd(include_finished: bool, limit: int, as_json: bool) -> None:
|
|
@@ -1377,7 +1472,10 @@ def _show_background_tasks(include_finished: bool, limit: int, as_json: bool) ->
|
|
|
1377
1472
|
_echo_json(tasks, True)
|
|
1378
1473
|
return
|
|
1379
1474
|
if not tasks:
|
|
1380
|
-
|
|
1475
|
+
if include_finished:
|
|
1476
|
+
click.echo("No background tasks recorded.")
|
|
1477
|
+
else:
|
|
1478
|
+
click.echo("No running background tasks.")
|
|
1381
1479
|
return
|
|
1382
1480
|
now = time.time()
|
|
1383
1481
|
header = f"{'ID':<12} {'Status':<10} {'Progress':>8} {'Phase':<26} {'Age':>7} Path"
|
|
@@ -30,9 +30,9 @@ class Settings:
|
|
|
30
30
|
rrf_k: int = 60
|
|
31
31
|
semantic_candidate_pool: int = 2000
|
|
32
32
|
write_batch_size: int = 500
|
|
33
|
-
index_file_batch_size: int =
|
|
34
|
-
index_method_batch_size: int =
|
|
35
|
-
index_symbol_batch_size: int =
|
|
33
|
+
index_file_batch_size: int = 1000
|
|
34
|
+
index_method_batch_size: int = 10000
|
|
35
|
+
index_symbol_batch_size: int = 10000
|
|
36
36
|
edge_write_batch_size: int = 5000
|
|
37
37
|
default_coupling_days: int = 5
|
|
38
38
|
default_min_coupling_strength: float = 0.3
|
|
@@ -88,6 +88,7 @@ class IndexResult:
|
|
|
88
88
|
calls_resolved: int
|
|
89
89
|
type_relationships: int
|
|
90
90
|
embeddings_generated: int
|
|
91
|
+
partial: bool = False
|
|
91
92
|
|
|
92
93
|
|
|
93
94
|
class JavaIndexer:
|
|
@@ -429,6 +430,7 @@ class JavaIndexer:
|
|
|
429
430
|
total=_db_total,
|
|
430
431
|
deleted_files=len(deleted_file_ids),
|
|
431
432
|
)
|
|
433
|
+
budget_exhausted = False
|
|
432
434
|
|
|
433
435
|
try:
|
|
434
436
|
# ── Chunked DB writes ─────────────────────────────────────────────
|
|
@@ -644,26 +646,20 @@ class JavaIndexer:
|
|
|
644
646
|
with self.store.transaction():
|
|
645
647
|
self.store.clear_files_batch(clear_sub)
|
|
646
648
|
self.store._recycle_conn()
|
|
647
|
-
_db_phase_holder[0] = "writing
|
|
649
|
+
_db_phase_holder[0] = "writing rows"
|
|
648
650
|
with self.store.transaction():
|
|
649
651
|
self.store.upsert_files_batch(file_rows)
|
|
650
|
-
self.store._recycle_conn()
|
|
651
|
-
_db_phase_holder[0] = "writing classes"
|
|
652
|
-
with self.store.transaction():
|
|
653
652
|
self.store.upsert_classes_batch(class_rows)
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
for method_sub in self._chunked(method_rows, _METHOD_SUB_BATCH):
|
|
658
|
-
with self.store.transaction():
|
|
653
|
+
_METHOD_SUB_BATCH = max(1, int(getattr(SETTINGS, "index_method_batch_size", 10000)))
|
|
654
|
+
_db_phase_holder[0] = "writing methods"
|
|
655
|
+
for method_sub in self._chunked(method_rows, _METHOD_SUB_BATCH):
|
|
659
656
|
self.store.upsert_methods_batch(method_sub)
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
for symbol_sub in self._chunked(symbol_rows, _SYMBOL_SUB_BATCH):
|
|
664
|
-
with self.store.transaction():
|
|
657
|
+
_SYMBOL_SUB_BATCH = max(1, int(getattr(SETTINGS, "index_symbol_batch_size", 10000)))
|
|
658
|
+
_db_phase_holder[0] = "writing symbols"
|
|
659
|
+
for symbol_sub in self._chunked(symbol_rows, _SYMBOL_SUB_BATCH):
|
|
665
660
|
self.store.upsert_symbols_batch(symbol_sub)
|
|
666
|
-
|
|
661
|
+
self.store._recycle_conn()
|
|
662
|
+
_db_phase_holder[0] = "building rows"
|
|
667
663
|
self._emit(
|
|
668
664
|
progress,
|
|
669
665
|
"db_write_progress",
|
|
@@ -673,6 +669,16 @@ class JavaIndexer:
|
|
|
673
669
|
methods=methods_indexed,
|
|
674
670
|
phase=_db_phase_holder[0],
|
|
675
671
|
)
|
|
672
|
+
if deadline is not None and time.perf_counter() >= max(0.0, deadline - 8.0):
|
|
673
|
+
budget_exhausted = True
|
|
674
|
+
self._emit(
|
|
675
|
+
progress,
|
|
676
|
+
"budget_exhausted",
|
|
677
|
+
phase="db_write",
|
|
678
|
+
files_indexed=files_indexed,
|
|
679
|
+
total=_db_total,
|
|
680
|
+
)
|
|
681
|
+
break
|
|
676
682
|
_db_phase_holder[0] = "building rows"
|
|
677
683
|
finally:
|
|
678
684
|
_db_hb_stop.set()
|
|
@@ -688,7 +694,23 @@ class JavaIndexer:
|
|
|
688
694
|
)
|
|
689
695
|
|
|
690
696
|
def _deadline_expired() -> bool:
|
|
691
|
-
return deadline is not None and time.perf_counter() >= deadline
|
|
697
|
+
return budget_exhausted or (deadline is not None and time.perf_counter() >= deadline)
|
|
698
|
+
|
|
699
|
+
if budget_exhausted:
|
|
700
|
+
self._emit(progress, "resolve_calls_done", calls_resolved=0, partial=True)
|
|
701
|
+
self._emit(progress, "resolve_types_done", type_relationships=0, partial=True)
|
|
702
|
+
self._emit(progress, "di_done", injections=0, interface_bindings=0, partial=True)
|
|
703
|
+
return IndexResult(
|
|
704
|
+
project_id=project_id,
|
|
705
|
+
files_found=len(current_files),
|
|
706
|
+
files_indexed=files_indexed,
|
|
707
|
+
classes_indexed=classes_indexed,
|
|
708
|
+
methods_indexed=methods_indexed,
|
|
709
|
+
calls_resolved=0,
|
|
710
|
+
type_relationships=0,
|
|
711
|
+
embeddings_generated=classes_indexed + methods_indexed if embed else 0,
|
|
712
|
+
partial=True,
|
|
713
|
+
)
|
|
692
714
|
|
|
693
715
|
self._emit(progress, "resolve_calls_start")
|
|
694
716
|
|
|
@@ -770,8 +792,6 @@ class JavaIndexer:
|
|
|
770
792
|
if _deadline_expired():
|
|
771
793
|
self._emit(progress, "resolve_types_done", type_relationships=0, partial=True)
|
|
772
794
|
self._emit(progress, "di_done", injections=0, interface_bindings=0, partial=True)
|
|
773
|
-
self._prune_meta_cache(meta_cache, current_file_ids)
|
|
774
|
-
self._save_file_meta_cache(project_id, meta_cache)
|
|
775
795
|
return IndexResult(
|
|
776
796
|
project_id=project_id,
|
|
777
797
|
files_found=len(current_files),
|
|
@@ -781,6 +801,7 @@ class JavaIndexer:
|
|
|
781
801
|
calls_resolved=calls_resolved,
|
|
782
802
|
type_relationships=0,
|
|
783
803
|
embeddings_generated=classes_indexed + methods_indexed if embed else 0,
|
|
804
|
+
partial=True,
|
|
784
805
|
)
|
|
785
806
|
type_rows = self._build_inheritance_edges(
|
|
786
807
|
class_meta,
|
|
@@ -843,6 +864,7 @@ class JavaIndexer:
|
|
|
843
864
|
calls_resolved=calls_resolved,
|
|
844
865
|
type_relationships=type_relationships,
|
|
845
866
|
embeddings_generated=classes_indexed + methods_indexed if embed else 0,
|
|
867
|
+
partial=partial_calls,
|
|
846
868
|
)
|
|
847
869
|
|
|
848
870
|
@staticmethod
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codespine
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.11
|
|
4
4
|
Summary: Local Java code intelligence indexer backed by a graph database
|
|
5
5
|
Author: CodeSpine contributors
|
|
6
6
|
License: MIT License
|
|
@@ -66,7 +66,7 @@ Dynamic: license-file
|
|
|
66
66
|
|
|
67
67
|
# CodeSpine
|
|
68
68
|
|
|
69
|
-
**v1.0.
|
|
69
|
+
**v1.0.11** — Local Java code intelligence for coding agents, backed by a graph database.
|
|
70
70
|
|
|
71
71
|
CodeSpine cuts token burn for coding agents working on Java codebases.
|
|
72
72
|
|
|
@@ -111,6 +111,7 @@ pip install "codespine[ui]"
|
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
The current lite UI is dependency-free and served locally by CodeSpine; the `ui` extra is the stable add-on install target for the browser explorer.
|
|
114
|
+
Quotes are required in zsh: use `pip install "codespine[ui]"`, not `pip install codespine[ui]`.
|
|
114
115
|
|
|
115
116
|
Everything at once (ml + community detection):
|
|
116
117
|
|
|
@@ -381,7 +382,7 @@ codespine force-reset # emergency: delete all data files
|
|
|
381
382
|
|
|
382
383
|
`analyse` defaults to incremental mode. Repeat runs only process changed files and are fast.
|
|
383
384
|
|
|
384
|
-
`analyse` runs in fast mode by default: it indexes the core graph,
|
|
385
|
+
`analyse` runs in fast mode by default: it indexes the core graph, honors the foreground time budget, publishes a read replica, then continues unfinished core indexing or enrichment in the background. Use `codespine background` or `codespine ui` to watch that work. Use `--complete --deep` when you want those passes refreshed before the command returns.
|
|
385
386
|
|
|
386
387
|
---
|
|
387
388
|
|
|
@@ -567,7 +568,7 @@ The deep analysis phase covers four passes that are expensive but optional:
|
|
|
567
568
|
| Dead code | Finds methods with no callers (Java-aware exemptions) | Cleanup audits |
|
|
568
569
|
| Change coupling | Analyses git history for co-changed file pairs | `get_change_coupling`, `related` |
|
|
569
570
|
|
|
570
|
-
**Fast default:** `codespine analyse` prioritizes a queryable core index. Communities, flows, dead-code, git coupling, and cross-module links are queued in a detached background enrichment job unless you use `--complete`.
|
|
571
|
+
**Fast default:** `codespine analyse` prioritizes a queryable core index and keeps the foreground run inside the configured budget. If core indexing or call/type resolution cannot finish in time, the partial index is published and a background continuation finishes the core graph. Communities, flows, dead-code, git coupling, and cross-module links are queued in a detached background enrichment job unless you use `--complete`.
|
|
571
572
|
|
|
572
573
|
**Health checks:** every analyse run now performs a small self-test query suite and reports index anomalies such as large projects with zero call edges. Use `codespine health` for the terminal dashboard or `codespine self-test --json` in CI.
|
|
573
574
|
|
|
@@ -32,3 +32,15 @@ def test_background_command_shows_progress():
|
|
|
32
32
|
assert task_id in result.output
|
|
33
33
|
assert "40%" in result.output
|
|
34
34
|
assert "execution flows" in result.output
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_background_command_shows_recent_finished_tasks_by_default():
|
|
38
|
+
task_id = create_task("indexing", "Background core indexing", path="/tmp/project")
|
|
39
|
+
finish_task(task_id, "failed", "boom")
|
|
40
|
+
|
|
41
|
+
result = CliRunner().invoke(main, ["background"])
|
|
42
|
+
|
|
43
|
+
assert result.exit_code == 0
|
|
44
|
+
assert task_id in result.output
|
|
45
|
+
assert "failed" in result.output
|
|
46
|
+
assert "boom" in result.output
|
|
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
|
|
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
|
|
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
|