codespine 0.7.1__tar.gz → 0.7.3__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-0.7.1 → codespine-0.7.3}/PKG-INFO +1 -1
- {codespine-0.7.1 → codespine-0.7.3}/codespine/__init__.py +1 -1
- {codespine-0.7.1 → codespine-0.7.3}/codespine/analysis/coupling.py +8 -8
- {codespine-0.7.1 → codespine-0.7.3}/codespine/cli.py +5 -5
- {codespine-0.7.1 → codespine-0.7.3}/codespine/config.py +1 -1
- {codespine-0.7.1 → codespine-0.7.3}/codespine/db/schema.py +6 -1
- {codespine-0.7.1 → codespine-0.7.3}/codespine/db/store.py +3 -3
- {codespine-0.7.1 → codespine-0.7.3}/codespine/guide.py +1 -1
- {codespine-0.7.1 → codespine-0.7.3}/codespine/mcp/server.py +3 -3
- {codespine-0.7.1 → codespine-0.7.3}/codespine.egg-info/PKG-INFO +1 -1
- {codespine-0.7.1 → codespine-0.7.3}/pyproject.toml +1 -1
- {codespine-0.7.1 → codespine-0.7.3}/LICENSE +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/README.md +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/analysis/__init__.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/analysis/community.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/analysis/context.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/analysis/crossmodule.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/analysis/deadcode.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/analysis/flow.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/analysis/impact.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/db/__init__.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/diff/__init__.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/diff/branch_diff.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/indexer/__init__.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/indexer/call_resolver.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/indexer/engine.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/indexer/java_parser.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/indexer/symbol_builder.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/mcp/__init__.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/noise/__init__.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/noise/blocklist.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/overlay/__init__.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/overlay/git_state.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/overlay/merge.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/overlay/store.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/search/__init__.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/search/bm25.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/search/fuzzy.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/search/hybrid.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/search/rrf.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/search/vector.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/watch/__init__.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine/watch/watcher.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine.egg-info/SOURCES.txt +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine.egg-info/dependency_links.txt +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine.egg-info/entry_points.txt +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine.egg-info/requires.txt +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/codespine.egg-info/top_level.txt +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/gindex.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/setup.cfg +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/tests/test_branch_diff_normalize.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/tests/test_call_resolver.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/tests/test_community_detection.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/tests/test_deadcode.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/tests/test_index_and_hybrid.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/tests/test_java_parser.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/tests/test_multimodule_index.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/tests/test_overlay.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/tests/test_search_ranking.py +0 -0
- {codespine-0.7.1 → codespine-0.7.3}/tests/test_store_recovery.py +0 -0
|
@@ -9,7 +9,7 @@ from codespine.config import SETTINGS
|
|
|
9
9
|
from codespine.indexer.symbol_builder import file_id
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def _git_changed_file_sets(repo_path: str,
|
|
12
|
+
def _git_changed_file_sets(repo_path: str, days: int) -> list[set[str]]:
|
|
13
13
|
cmd = [
|
|
14
14
|
"git",
|
|
15
15
|
"-C",
|
|
@@ -17,7 +17,7 @@ def _git_changed_file_sets(repo_path: str, months: int) -> list[set[str]]:
|
|
|
17
17
|
"log",
|
|
18
18
|
"--name-only",
|
|
19
19
|
"--pretty=format:__COMMIT__",
|
|
20
|
-
f"--since={
|
|
20
|
+
f"--since={days}.days",
|
|
21
21
|
]
|
|
22
22
|
proc = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
23
23
|
if proc.returncode != 0:
|
|
@@ -43,7 +43,7 @@ def compute_coupling(
|
|
|
43
43
|
store,
|
|
44
44
|
repo_path: str,
|
|
45
45
|
project_id: str,
|
|
46
|
-
|
|
46
|
+
days: int = SETTINGS.default_coupling_days,
|
|
47
47
|
min_strength: float = SETTINGS.default_min_coupling_strength,
|
|
48
48
|
min_cochanges: int = SETTINGS.default_min_cochanges,
|
|
49
49
|
progress=None,
|
|
@@ -53,7 +53,7 @@ def compute_coupling(
|
|
|
53
53
|
progress(msg)
|
|
54
54
|
|
|
55
55
|
_ping("reading git history")
|
|
56
|
-
changesets = _git_changed_file_sets(repo_path,
|
|
56
|
+
changesets = _git_changed_file_sets(repo_path, days)
|
|
57
57
|
if not changesets:
|
|
58
58
|
return []
|
|
59
59
|
|
|
@@ -77,7 +77,7 @@ def compute_coupling(
|
|
|
77
77
|
|
|
78
78
|
aid = file_id(project_id, a)
|
|
79
79
|
bid = file_id(project_id, b)
|
|
80
|
-
store.upsert_coupling(aid, bid, strength, pair_count,
|
|
80
|
+
store.upsert_coupling(aid, bid, strength, pair_count, days)
|
|
81
81
|
results.append(
|
|
82
82
|
{
|
|
83
83
|
"file_a": a,
|
|
@@ -91,7 +91,7 @@ def compute_coupling(
|
|
|
91
91
|
return results
|
|
92
92
|
|
|
93
93
|
|
|
94
|
-
def get_coupling(store, symbol: str | None = None,
|
|
94
|
+
def get_coupling(store, symbol: str | None = None, days: int = 5, min_strength: float = 0.3, min_cochanges: int = 3) -> dict:
|
|
95
95
|
if symbol:
|
|
96
96
|
recs = store.query_records(
|
|
97
97
|
"""
|
|
@@ -113,13 +113,13 @@ def get_coupling(store, symbol: str | None = None, months: int = 6, min_strength
|
|
|
113
113
|
recs = store.query_records(
|
|
114
114
|
"""
|
|
115
115
|
MATCH (f:File)-[r:CO_CHANGED_WITH]-(f2:File)
|
|
116
|
-
WHERE r.
|
|
116
|
+
WHERE r.days = $days AND r.strength >= $min_strength AND r.cochanges >= $min_cochanges
|
|
117
117
|
RETURN f.path as file, f2.path as coupled_file, r.strength as strength, r.cochanges as cochanges
|
|
118
118
|
ORDER BY strength DESC, cochanges DESC
|
|
119
119
|
LIMIT 500
|
|
120
120
|
""",
|
|
121
121
|
{
|
|
122
|
-
"
|
|
122
|
+
"days": days,
|
|
123
123
|
"min_strength": min_strength,
|
|
124
124
|
"min_cochanges": min_cochanges,
|
|
125
125
|
},
|
|
@@ -314,7 +314,7 @@ def analyse(path: str, full: bool, deep: bool, embed: bool, allow_running: bool)
|
|
|
314
314
|
store,
|
|
315
315
|
coupling_root,
|
|
316
316
|
coupling_project,
|
|
317
|
-
|
|
317
|
+
days=SETTINGS.default_coupling_days,
|
|
318
318
|
min_strength=SETTINGS.default_min_coupling_strength,
|
|
319
319
|
min_cochanges=SETTINGS.default_min_cochanges,
|
|
320
320
|
progress=lambda s: _live_phase(coup_label, s),
|
|
@@ -467,20 +467,20 @@ def community(symbol: str | None, as_json: bool) -> None:
|
|
|
467
467
|
|
|
468
468
|
|
|
469
469
|
@main.command()
|
|
470
|
-
@click.option("--
|
|
470
|
+
@click.option("--days", default=5, show_default=True, type=int)
|
|
471
471
|
@click.option("--min-strength", default=0.3, show_default=True, type=float)
|
|
472
472
|
@click.option("--min-cochanges", default=3, show_default=True, type=int)
|
|
473
473
|
@click.option("--json", "as_json", is_flag=True)
|
|
474
|
-
def coupling(
|
|
474
|
+
def coupling(days: int, min_strength: float, min_cochanges: int, as_json: bool) -> None:
|
|
475
475
|
"""Compute and query git change coupling."""
|
|
476
476
|
store = GraphStore(read_only=False)
|
|
477
477
|
project = store.query_records("MATCH (p:Project) RETURN p.id as id LIMIT 1")
|
|
478
478
|
project_id = project[0]["id"] if project else os.path.basename(os.getcwd())
|
|
479
|
-
compute_coupling(store, os.getcwd(), project_id,
|
|
479
|
+
compute_coupling(store, os.getcwd(), project_id, days=days, min_strength=min_strength, min_cochanges=min_cochanges)
|
|
480
480
|
result = get_coupling(
|
|
481
481
|
store,
|
|
482
482
|
symbol=None,
|
|
483
|
-
|
|
483
|
+
days=days,
|
|
484
484
|
min_strength=min_strength,
|
|
485
485
|
min_cochanges=min_cochanges,
|
|
486
486
|
)
|
|
@@ -18,7 +18,7 @@ class Settings:
|
|
|
18
18
|
write_batch_size: int = 500
|
|
19
19
|
index_file_batch_size: int = 20
|
|
20
20
|
edge_write_batch_size: int = 500
|
|
21
|
-
|
|
21
|
+
default_coupling_days: int = 5
|
|
22
22
|
default_min_coupling_strength: float = 0.3
|
|
23
23
|
default_min_cochanges: int = 3
|
|
24
24
|
default_global_interval_s: int = 30
|
|
@@ -49,7 +49,7 @@ REL_TABLES: Iterable[tuple[str, str]] = [
|
|
|
49
49
|
("IN_FLOW", "CREATE REL TABLE IN_FLOW(FROM Symbol TO Flow, depth INT64)"),
|
|
50
50
|
(
|
|
51
51
|
"CO_CHANGED_WITH",
|
|
52
|
-
"CREATE REL TABLE CO_CHANGED_WITH(FROM File TO File, strength DOUBLE, cochanges INT64,
|
|
52
|
+
"CREATE REL TABLE CO_CHANGED_WITH(FROM File TO File, strength DOUBLE, cochanges INT64, days INT64)",
|
|
53
53
|
),
|
|
54
54
|
]
|
|
55
55
|
|
|
@@ -86,3 +86,8 @@ def ensure_schema(conn) -> None:
|
|
|
86
86
|
|
|
87
87
|
_safe_execute(conn, "ALTER TABLE Project ADD indexed_commit STRING DEFAULT ''")
|
|
88
88
|
_safe_execute(conn, "ALTER TABLE Project ADD overlay_dirty BOOL DEFAULT false")
|
|
89
|
+
|
|
90
|
+
# v0.7.3: renamed CO_CHANGED_WITH.months → days (days-based window).
|
|
91
|
+
# ALTER TABLE is a no-op on fresh DBs that already have 'days'; safe_execute
|
|
92
|
+
# swallows the error if the column already exists or the table doesn't yet.
|
|
93
|
+
_safe_execute(conn, "ALTER TABLE CO_CHANGED_WITH ADD days INT64 DEFAULT 0")
|
|
@@ -648,18 +648,18 @@ class GraphStore:
|
|
|
648
648
|
)
|
|
649
649
|
self._recycle_conn()
|
|
650
650
|
|
|
651
|
-
def upsert_coupling(self, file_a: str, file_b: str, strength: float, cochanges: int,
|
|
651
|
+
def upsert_coupling(self, file_a: str, file_b: str, strength: float, cochanges: int, days: int) -> None:
|
|
652
652
|
self.execute(
|
|
653
653
|
"""
|
|
654
654
|
MATCH (a:File {id: $a}), (b:File {id: $b})
|
|
655
|
-
MERGE (a)-[:CO_CHANGED_WITH {strength: $strength, cochanges: $cochanges,
|
|
655
|
+
MERGE (a)-[:CO_CHANGED_WITH {strength: $strength, cochanges: $cochanges, days: $days}]->(b)
|
|
656
656
|
""",
|
|
657
657
|
{
|
|
658
658
|
"a": file_a,
|
|
659
659
|
"b": file_b,
|
|
660
660
|
"strength": strength,
|
|
661
661
|
"cochanges": int(cochanges),
|
|
662
|
-
"
|
|
662
|
+
"days": int(days),
|
|
663
663
|
},
|
|
664
664
|
)
|
|
665
665
|
|
|
@@ -60,7 +60,7 @@ GUIDE_SECTIONS: list[dict] = [
|
|
|
60
60
|
{"name": "detect_dead_code", "one_liner": "Methods with no callers (Java-aware exemptions). strict=True for thorough audit."},
|
|
61
61
|
{"name": "trace_execution_flows", "one_liner": "Execution paths from entry points (main methods, tests, controllers)."},
|
|
62
62
|
{"name": "get_symbol_community", "one_liner": "Architectural community cluster a symbol belongs to."},
|
|
63
|
-
{"name": "get_change_coupling", "one_liner": "Files that
|
|
63
|
+
{"name": "get_change_coupling", "one_liner": "Files that changed together in the last N days (default 5). git co-change analysis."},
|
|
64
64
|
],
|
|
65
65
|
},
|
|
66
66
|
{
|
|
@@ -522,15 +522,15 @@ def build_mcp_server(store, repo_path_provider):
|
|
|
522
522
|
@mcp.tool()
|
|
523
523
|
def get_change_coupling(
|
|
524
524
|
symbol: str | None = None,
|
|
525
|
-
|
|
525
|
+
days: int = 5,
|
|
526
526
|
min_strength: float = 0.3,
|
|
527
527
|
min_cochanges: int = 3,
|
|
528
528
|
):
|
|
529
529
|
"""
|
|
530
|
-
Files that
|
|
530
|
+
Files that changed together in the last N days (git co-change coupling).
|
|
531
531
|
Requires 'codespine analyse --deep' to have been run.
|
|
532
532
|
"""
|
|
533
|
-
result = get_coupling(store, symbol=symbol,
|
|
533
|
+
result = get_coupling(store, symbol=symbol, days=days, min_strength=min_strength, min_cochanges=min_cochanges)
|
|
534
534
|
if not result:
|
|
535
535
|
return {
|
|
536
536
|
"available": False,
|
|
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
|