sourcecode 1.32.4__py3-none-any.whl → 1.32.5__py3-none-any.whl

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.
sourcecode/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Deterministic codebase context maps for AI coding agents."""
2
2
 
3
- __version__ = "1.32.4"
3
+ __version__ = "1.32.5"
sourcecode/cache.py CHANGED
@@ -72,6 +72,11 @@ SCHEMA_VERSION: str = "2"
72
72
  #: Bump to invalidate all L1 core caches (independent of snapshot version).
73
73
  CORE_SCHEMA_VERSION: str = "1"
74
74
 
75
+ #: Bump when analysis logic or output schema changes — NOT on every package release.
76
+ #: This is the stable part of the L1 core cache key. Package version bumps (patch,
77
+ #: minor) must NOT bump this value unless the cached data format actually changed.
78
+ ANALYZER_CACHE_VERSION: str = "1"
79
+
75
80
  #: Fields eligible for CAS deduplication (applied to top-level JSON dict keys).
76
81
  _CAS_FIELDS: frozenset[str] = frozenset([
77
82
  "file_paths",
@@ -511,7 +516,7 @@ def _cas_store_blob(cache_d: Path, serialised: str) -> str:
511
516
  path = _cas_path(cache_d, blob_hash)
512
517
  if not path.exists():
513
518
  path.parent.mkdir(parents=True, exist_ok=True)
514
- path.write_bytes(gzip.compress(raw, compresslevel=6))
519
+ _atomic_write(path, gzip.compress(raw, compresslevel=6))
515
520
  return blob_hash
516
521
 
517
522
 
@@ -642,7 +647,11 @@ def _gc(cache_d: Path) -> None:
642
647
  # ── Pass 3: total size cap ──────────────────────────────────────────
643
648
  if max_size_bytes > 0:
644
649
  size_candidates = [p for p in surviving if p.exists()]
650
+ # Include CAS blobs in the size budget calculation
651
+ cas_d_sz = _cas_dir(cache_d)
652
+ cas_files = list(cas_d_sz.glob("*.gz")) if cas_d_sz.exists() else []
645
653
  total = sum(p.stat().st_size for p in size_candidates if not p.name.startswith("view-"))
654
+ total += sum(p.stat().st_size for p in cas_files if p.exists())
646
655
  if total > max_size_bytes:
647
656
  # Sort oldest-first; evict core+snapshot files until under budget
648
657
  size_candidates.sort(key=lambda p: p.stat().st_mtime)
sourcecode/cli.py CHANGED
@@ -208,6 +208,8 @@ _SUBCOMMANDS: frozenset[str] = frozenset(
208
208
  "onboard", "modernize", "fix-bug", "review-pr",
209
209
  # License
210
210
  "activate",
211
+ # Cache observability
212
+ "cache",
211
213
  }
212
214
  )
213
215
 
@@ -412,6 +414,9 @@ app.add_typer(telemetry_app, name="telemetry")
412
414
  mcp_app = typer.Typer(help="MCP integration: setup, status, serve, remove.", rich_markup_mode="rich")
413
415
  app.add_typer(mcp_app, name="mcp")
414
416
 
417
+ cache_app = typer.Typer(help="Cache inspection and management.", rich_markup_mode="rich")
418
+ app.add_typer(cache_app, name="cache")
419
+
415
420
 
416
421
  def _maybe_ask_consent() -> None:
417
422
  """Show first-run consent prompt once, on interactive TTYs only."""
@@ -1064,7 +1069,6 @@ def main(
1064
1069
  # Only cache when target IS the git repo root (not a subdir of one),
1065
1070
  # to avoid polluting sub-project directories used in tests.
1066
1071
  if _git_sha and (target / ".git").exists():
1067
- from sourcecode import __version__ as _sc_version
1068
1072
  _excl_key = (
1069
1073
  ",".join(sorted(e.strip() for e in exclude.split(",") if e.strip()))
1070
1074
  if exclude else ""
@@ -1072,8 +1076,10 @@ def main(
1072
1076
 
1073
1077
  # ── Core (analysis) flags: affect which analyzers run + scan config ──
1074
1078
  # Use effective_depth (not raw depth) so Java auto-adjustment is captured.
1079
+ # acv = ANALYZER_CACHE_VERSION — bumped only when analysis logic/schema
1080
+ # changes, NOT on every package release. Prevents patch-bump cache wipes.
1075
1081
  _core_flags_str = (
1076
- f"v={_sc_version},"
1082
+ f"acv={_cache_mod.ANALYZER_CACHE_VERSION},"
1077
1083
  f"dep={dependencies},gm={graph_modules},"
1078
1084
  f"docs={docs},fm={full_metrics},sem={semantics},"
1079
1085
  f"arch={architecture},gc={git_context},em={env_map},"
@@ -4124,6 +4130,70 @@ def mcp_remove(
4124
4130
  typer.echo(" Re-add: sourcecode mcp init")
4125
4131
 
4126
4132
 
4133
+ # ── Cache subcommands ─────────────────────────────────────────────────────────
4134
+
4135
+
4136
+ @cache_app.command("status")
4137
+ def cache_status_cmd(
4138
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
4139
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
4140
+ ) -> None:
4141
+ """Show cache statistics for a repository."""
4142
+ from sourcecode import cache as _cm
4143
+ target = Path(path).resolve()
4144
+ stats = _cm.status(target)
4145
+ if json_output:
4146
+ import json as _j
4147
+ typer.echo(_j.dumps(stats, indent=2, ensure_ascii=False))
4148
+ else:
4149
+ typer.echo(f"Cache dir: {stats['cache_dir']}")
4150
+ typer.echo(f"Cores: {stats['cores']}")
4151
+ typer.echo(f"Snapshots: {stats['snapshots']}")
4152
+ typer.echo(f"Views: {stats['views']}")
4153
+ typer.echo(f"CAS blobs: {stats['cas_blobs']}")
4154
+ typer.echo(f"Total size: {stats['total_size_mb']} MB")
4155
+
4156
+
4157
+ @cache_app.command("clear")
4158
+ def cache_clear_cmd(
4159
+ path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
4160
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
4161
+ ) -> None:
4162
+ """Delete all cached snapshots for a repository."""
4163
+ from sourcecode import cache as _cm
4164
+ target = Path(path).resolve()
4165
+ if not yes:
4166
+ typer.confirm(f"Delete all cache files for {target}?", abort=True)
4167
+ removed = _cm.clear(target)
4168
+ typer.echo(f"Removed {removed} file(s).")
4169
+
4170
+
4171
+ @cache_app.command("warm")
4172
+ def cache_warm_cmd(
4173
+ path: Path = typer.Argument(Path("."), help="Repository path to warm (default: current directory)"),
4174
+ compact: bool = typer.Option(True, "--compact/--no-compact", help="Warm compact view (default: on)."),
4175
+ agent: bool = typer.Option(False, "--agent", help="Also warm agent view."),
4176
+ ) -> None:
4177
+ """Pre-populate the cache by running a fresh analysis."""
4178
+ import subprocess as _sub
4179
+ import sys as _sys
4180
+ target = Path(path).resolve()
4181
+ typer.echo(f"Warming cache for {target} …", err=True)
4182
+ cmd = [_sys.executable, "-m", "sourcecode", str(target)]
4183
+ if compact:
4184
+ cmd.append("--compact")
4185
+ if agent:
4186
+ cmd.append("--agent")
4187
+ result = _sub.run(cmd, capture_output=True, text=True)
4188
+ if result.returncode == 0:
4189
+ typer.echo("Cache warmed.", err=True)
4190
+ else:
4191
+ typer.echo(f"Warm failed (exit {result.returncode}).", err=True)
4192
+ if result.stderr:
4193
+ typer.echo(result.stderr.strip(), err=True)
4194
+ raise typer.Exit(code=result.returncode)
4195
+
4196
+
4127
4197
  # ── Entry point ───────────────────────────────────────────────────────────────
4128
4198
 
4129
4199
  def main_entry() -> None:
sourcecode/ris.py CHANGED
@@ -329,10 +329,15 @@ def update_ris_api_surface(repo_root: Path, endpoints_data: dict) -> None:
329
329
  # ---------------------------------------------------------------------------
330
330
 
331
331
  def _current_git_head(repo_root: Path) -> str:
332
- """Return current HEAD SHA. Returns '' on any error or non-git directory."""
332
+ """Return current HEAD short SHA. Returns '' on any error or non-git directory.
333
+
334
+ Uses --short to match the format stored in the RIS and used by cli.py
335
+ cache key computation — both sides must use the same format or staleness
336
+ checks will always return True.
337
+ """
333
338
  try:
334
339
  result = subprocess.run(
335
- ["git", "-C", str(repo_root), "rev-parse", "HEAD"],
340
+ ["git", "-C", str(repo_root), "rev-parse", "--short", "HEAD"],
336
341
  capture_output=True,
337
342
  text=True,
338
343
  timeout=2,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.32.4
3
+ Version: 1.32.5
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -1,13 +1,13 @@
1
- sourcecode/__init__.py,sha256=ekW8VutI9sqMXBEN-MSRy8p8fHOhat9jl5sCtrKrygc,103
1
+ sourcecode/__init__.py,sha256=OcIqvxMtCqwdofwolfD8-rpWZk-fh2PbDUp8V3xRur4,103
2
2
  sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
3
3
  sourcecode/architecture_analyzer.py,sha256=qh749a7ykPtGmQI1MR9y6j8TtL_jBdVYFx9YRsLqOMw,44121
4
4
  sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
5
5
  sourcecode/ast_extractor.py,sha256=_btmeOJIe3t-NicF94D5ZAesa2YIJ0_QNExGnbHxGFE,50578
6
- sourcecode/cache.py,sha256=dvXt8HsU-SyO0a0UXY1n-wt6F2ozGv9VnKR0XydjxCY,27502
6
+ sourcecode/cache.py,sha256=h1BT-9PG_7HK---ZzH0j5u3PN0dz2s6IRAUOjQIPYH4,28055
7
7
  sourcecode/cache.tmp_new,sha256=-IvV7CojiZjqeKMln1m-lqI0QVA2uFGWmYir4XRFOUk,27970
8
8
  sourcecode/canonical_ir.py,sha256=_HM3AUmKSdna9u4dCoU6rpgSA6HdF8gzOKZykIUCNGY,23277
9
9
  sourcecode/classifier.py,sha256=2lYoSH3vOTkXZYPU7Go2WIet1-IuNzTWVhc-ULnXtgw,8024
10
- sourcecode/cli.py,sha256=RdJ1F_sjmqTJ6zcv3eY2Tokg9H5dJe6zSuK3ownUGcA,166617
10
+ sourcecode/cli.py,sha256=-iUcg3FA8K0xgG1xvXJRCF4Xrleyf04QEOJho8bF5mw,169600
11
11
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
12
12
  sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
13
13
  sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
@@ -36,7 +36,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
36
36
  sourcecode/relevance_scorer.py,sha256=MYF4FFkveAQps9SmTeTlh6ODiBz2F--_hWNeHMLtUHQ,8405
37
37
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
38
38
  sourcecode/repository_ir.py,sha256=-NjBQUT7zyya4ng8Hq0-ChoiHZkUif9lr-Q878gmj8M,153163
39
- sourcecode/ris.py,sha256=iy1O04SiQ3cFIK5Qn8iHt3fZTiXIgbXvWkjbBG0fTBo,14000
39
+ sourcecode/ris.py,sha256=CqNfiIbzfHZQZx0mWLjyVD4o8u5ZLnbv3Ia7D7gYhQY,14212
40
40
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
41
41
  sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
42
42
  sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
@@ -80,8 +80,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
80
80
  sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
81
81
  sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
82
82
  sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
83
- sourcecode-1.32.4.dist-info/METADATA,sha256=SJ09ABP688xA3bbWRuseGyJyVK-mYQmR7CgodrIjmDE,19120
84
- sourcecode-1.32.4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
85
- sourcecode-1.32.4.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
86
- sourcecode-1.32.4.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
87
- sourcecode-1.32.4.dist-info/RECORD,,
83
+ sourcecode-1.32.5.dist-info/METADATA,sha256=kuVwifYAr-Xr4ME4ak-9zWuZqYS-fFMYfFcpx4YJugs,19120
84
+ sourcecode-1.32.5.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
85
+ sourcecode-1.32.5.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
86
+ sourcecode-1.32.5.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
87
+ sourcecode-1.32.5.dist-info/RECORD,,