Skip to content

Commit dbc92a6

Browse files
fix(workspace): make cross-repo co-change a bounded session-share signal (#758)
* fix(workspace): make cross-repo co-change a bounded session-share signal The cross-repo co-change list was dominated by noise: a progress diary paired with everything (103 of 200 edges), strengths were unbounded sums (up to 91.6 against an API contract of 0..1), one repo pair consumed the whole edge budget, and same-person commits under different git emails never matched at all. Reworked detect_cross_repo_co_changes: - Chain each author's commits into work sessions (gap or span > window starts a new one). A session credits each cross-repo file pair once, killing the quadratic commit-pair inflation that let one diary file reach frequency 322 from 38 commits. - Match authors by normalized author name with email fallback, so the same person committing as 'Jane Doe' and 'JaneDoe' with different emails still links their repos. - Drop ubiquitous files (>20% of a repo's scanned commits, only when >=30 commits of history) plus changelogs via the static noise list. - strength = decayed co-sessions / (less-active file's decayed sessions + 1), a bounded [0, 1) share; require >=2 co-sessions. frequency now means distinct co-sessions. - Cap sessions at 20 files per side selected by session centrality, not alphabetically (which systematically evicted src/** in busy sessions), and cap edges per repo pair (50) before the global cap (200). Overlay version bumped to 2; load_overlay and CrossRepoEnricher ignore v1 files so stale unbounded scores never reach consumers. The CLAUDE.md workspace generator now reads through load_overlay instead of parsing the JSON three times. UI shows strength as a percentage with an updated tooltip, and the min_strength API bound (le=1.0) is now truthful. Dogfood validation across repowise+backend+frontend: 0 diary edges (was 116/200), strengths 0.52-0.78, all three repo pairs represented (was 1). * fix(upgrade): sync bundled changelog with docs
1 parent 732b105 commit dbc92a6

10 files changed

Lines changed: 763 additions & 353 deletions

File tree

packages/cli/src/repowise/cli/commands/claude_md_cmd.py

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ def _generate_workspace(
203203
WORKSPACE_DATA_DIR,
204204
WorkspaceConfig,
205205
)
206-
from repowise.core.workspace.cross_repo import CROSS_REPO_EDGES_FILENAME
207206
from repowise.core.workspace.contracts import CONTRACTS_FILENAME
207+
from repowise.core.workspace.cross_repo import load_overlay
208208

209209
ws_root = find_workspace_root(start_path)
210210
if ws_root is None:
@@ -220,14 +220,14 @@ def _generate_workspace(
220220
# ------------------------------------------------------------------
221221
co_changes: list[dict] = []
222222
package_deps: list[dict] = []
223-
edges_file = data_dir / CROSS_REPO_EDGES_FILENAME
224-
if edges_file.exists():
225-
try:
226-
overlay = json.loads(edges_file.read_text(encoding="utf-8"))
227-
co_changes = overlay.get("co_changes", [])
228-
package_deps = overlay.get("package_deps", [])
229-
except Exception:
230-
pass # non-fatal; workspace data may not exist yet
223+
overlay_summaries: dict[str, dict] = {}
224+
overlay = load_overlay(ws_root) # None when absent, corrupt, or stale-versioned
225+
if overlay is not None:
226+
from dataclasses import asdict
227+
228+
co_changes = [asdict(c) for c in overlay.co_changes]
229+
package_deps = [asdict(d) for d in overlay.package_deps]
230+
overlay_summaries = overlay.repo_summaries
231231

232232
# Sort co-changes by frequency descending so the top entries are most useful
233233
co_changes = sorted(co_changes, key=lambda c: c.get("frequency", 0), reverse=True)
@@ -257,25 +257,9 @@ def _generate_workspace(
257257
abs_path = (ws_root / entry.path).resolve()
258258
file_count, symbol_count = _query_repo_counts(abs_path)
259259

260-
# Hotspot count: try to read from the overlay's repo_summaries if available
261-
hotspot_count = 0
262-
if edges_file.exists():
263-
try:
264-
overlay_data = json.loads(edges_file.read_text(encoding="utf-8"))
265-
repo_sum = overlay_data.get("repo_summaries", {}).get(entry.alias, {})
266-
hotspot_count = repo_sum.get("hotspot_count", 0)
267-
except Exception:
268-
pass
269-
270-
# Entry points: read from overlay repo_summaries if present, else empty
271-
entry_points: list[str] = []
272-
if edges_file.exists():
273-
try:
274-
overlay_data = json.loads(edges_file.read_text(encoding="utf-8"))
275-
repo_sum = overlay_data.get("repo_summaries", {}).get(entry.alias, {})
276-
entry_points = repo_sum.get("entry_points", [])
277-
except Exception:
278-
pass
260+
repo_sum = overlay_summaries.get(entry.alias, {})
261+
hotspot_count = repo_sum.get("hotspot_count", 0)
262+
entry_points = repo_sum.get("entry_points", [])
279263

280264
repo_summaries.append(
281265
WorkspaceRepoSummary(

0 commit comments

Comments
 (0)