sourcecode 1.30.29__py3-none-any.whl → 1.30.30__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.30.29"
3
+ __version__ = "1.30.30"
sourcecode/cli.py CHANGED
@@ -188,6 +188,7 @@ _OPTIONS_WITH_VALUE: frozenset[str] = frozenset({
188
188
  "--rank-by",
189
189
  "--symbol",
190
190
  "--max-importers",
191
+ "--exclude",
191
192
  })
192
193
 
193
194
 
@@ -851,10 +852,18 @@ def main(
851
852
  _copy_to_clipboard(_cache_hit_content)
852
853
  return
853
854
 
854
- # BUG-2: parse --exclude into extra_excludes frozenset
855
855
  _extra_excludes: Optional[frozenset[str]] = None
856
856
  if exclude:
857
857
  _extra_excludes = frozenset(e.strip() for e in exclude.split(",") if e.strip())
858
+ # IMP-2: warn if the exclude value looks like it was swallowed as a path
859
+ # (BUG-2 symptom in older versions: --exclude value consumed as repo path).
860
+ import sys as _sys_warn
861
+ if len(_extra_excludes) == 1 and Path(list(_extra_excludes)[0]).is_dir():
862
+ _sys_warn.stderr.write(
863
+ f"[sourcecode] Warning: --exclude value '{list(_extra_excludes)[0]}' is a directory path. "
864
+ "If this was meant as a pattern, use --exclude=pattern or --exclude pattern (both are supported).\n"
865
+ )
866
+ _sys_warn.stderr.flush()
858
867
 
859
868
  _progress = Progress()
860
869
  _progress.start("scanning files")
@@ -1807,6 +1816,11 @@ def prepare_context_cmd(
1807
1816
  "--fast",
1808
1817
  help="Skip deep analysis (content search, test gap discovery, code annotations). Uses manifest/metadata only. Target: < 6 s.",
1809
1818
  ),
1819
+ include_config: bool = typer.Option(
1820
+ False,
1821
+ "--include-config",
1822
+ help="(generate-tests) Include tooling config files (*.conf.js, .eslintrc*, etc.) in test_gaps. Excluded by default.",
1823
+ ),
1810
1824
  ) -> None:
1811
1825
  """Task-specific context for AI coding agents.
1812
1826
 
@@ -1888,7 +1902,7 @@ def prepare_context_cmd(
1888
1902
  _sys.stderr.flush()
1889
1903
  _t0 = _time.perf_counter()
1890
1904
  try:
1891
- output = builder.build(task, since=since, symptom=symptom, fast=fast)
1905
+ output = builder.build(task, since=since, symptom=symptom, fast=fast, include_config=include_config)
1892
1906
  finally:
1893
1907
  _progress.finish()
1894
1908
  _t_total = (_time.perf_counter() - _t0) * 1000
@@ -2359,8 +2373,23 @@ def repo_ir_cmd(
2359
2373
  err=True,
2360
2374
  )
2361
2375
  else:
2362
- _sys.stdout.write(output)
2363
- _sys.stdout.write("\n")
2376
+ try:
2377
+ _sys.stdout.buffer.write(output.encode("utf-8"))
2378
+ _sys.stdout.buffer.write(b"\n")
2379
+ _sys.stdout.buffer.flush()
2380
+ except UnicodeEncodeError as _ue:
2381
+ # IMP-2: emit workaround before re-raising so the user knows what to do.
2382
+ _sys.stderr.write(
2383
+ f"[sourcecode] UnicodeEncodeError on stdout ({_ue.encoding}): "
2384
+ "your console codec cannot encode this output.\n"
2385
+ "Workaround: sourcecode repo-ir --output ir.json\n"
2386
+ )
2387
+ _sys.stderr.flush()
2388
+ raise
2389
+ except AttributeError:
2390
+ # Fallback for wrapped stdout without buffer (e.g. some test harnesses)
2391
+ _sys.stdout.write(output)
2392
+ _sys.stdout.write("\n")
2364
2393
 
2365
2394
 
2366
2395
  # ── version ───────────────────────────────────────────────────────────────────
@@ -2413,5 +2442,13 @@ def main_entry() -> None:
2413
2442
  can consume them as positional arguments (which would prevent subcommand
2414
2443
  routing for tokens like 'version' or 'config').
2415
2444
  """
2445
+ import sys as _sys
2446
+ # Force UTF-8 on stdout so Unicode characters (arrows, etc.) survive on
2447
+ # Windows where the default console codec is cp1252 (BUG-1).
2448
+ if hasattr(_sys.stdout, "reconfigure"):
2449
+ try:
2450
+ _sys.stdout.reconfigure(encoding="utf-8")
2451
+ except Exception:
2452
+ pass
2416
2453
  _preprocess_argv()
2417
2454
  app()
@@ -712,7 +712,7 @@ class TaskContextBuilder:
712
712
  def __init__(self, root: Path) -> None:
713
713
  self.root = root
714
714
 
715
- def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None, fast: bool = False) -> TaskOutput:
715
+ def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None, fast: bool = False, include_config: bool = False) -> TaskOutput:
716
716
  if task_name not in TASKS:
717
717
  raise ValueError(
718
718
  f"Unknown task '{task_name}'. Available: {', '.join(TASKS)}"
@@ -788,6 +788,14 @@ class TaskContextBuilder:
788
788
  # for behavioral_impact reverse lookups without scanning the whole repo).
789
789
  file_tree: dict = {}
790
790
  all_paths = self._expand_scope_for_analysis(_pr_scope_files or [])
791
+ elif fast and task_name == "onboard":
792
+ # Onboard fast: always use shallow scan so manifests and entry points
793
+ # are discoverable — git-changed-only mode would return only dirty files
794
+ # (e.g. .idea/vcs.xml) which yields no useful entry points (BUG-3).
795
+ scanner = AdaptiveScanner(self.root, base_depth=2)
796
+ file_tree = scanner.scan_tree()
797
+ manifests = scanner.find_manifests()
798
+ all_paths = [p.replace("\\", "/") for p in flatten_file_tree(file_tree)]
791
799
  elif fast and _count_files_bounded(self.root) > MAX_FILES_FAST:
792
800
  # Fast mode on large repo: git-index-only — only scan git-changed files.
793
801
  # Skips full AdaptiveScanner traversal which takes 35s+ on 7k+ file repos.
@@ -1652,11 +1660,26 @@ class TaskContextBuilder:
1652
1660
  # Python/JS: test_foo / foo_test
1653
1661
  return stem.removeprefix("test_").removesuffix("_test")
1654
1662
 
1663
+ # Patterns excluded from test_gaps by default (IMP-1): tooling config
1664
+ # files have no business logic to test. --include-config overrides.
1665
+ _CONFIG_EXCLUDE_PATTERNS = (
1666
+ ".eslintrc", ".prettierrc", "eslint.config",
1667
+ "karma.conf", "jest.config", "babel.config",
1668
+ "webpack.config", "vite.config", "rollup.config",
1669
+ "tsconfig", "angular.json", ".claude/",
1670
+ )
1671
+
1672
+ def _is_config_file(p: str) -> bool:
1673
+ name = Path(p).name.lower()
1674
+ norm = p.replace("\\", "/")
1675
+ return any(pat in name or pat in norm for pat in _CONFIG_EXCLUDE_PATTERNS)
1676
+
1655
1677
  test_stems = {_normalize_test_stem(Path(p).stem) for p in test_set}
1656
1678
  untested = [
1657
1679
  p for p in source_set
1658
1680
  if Path(p).stem not in test_stems
1659
1681
  and not any(pen in p for pen in spec.ranking_penalties)
1682
+ and (include_config or not _is_config_file(p))
1660
1683
  ]
1661
1684
  untested.sort(key=lambda p: (len(p.split("/")), p))
1662
1685
  test_gaps = untested[:15]
sourcecode/serializer.py CHANGED
@@ -1643,7 +1643,23 @@ def _angular_analysis(sm: "SourceMap") -> "Optional[dict[str, Any]]":
1643
1643
  continue
1644
1644
  component_count += content.count("@Component(")
1645
1645
  service_count += content.count("@Injectable(")
1646
- lazy_routes_count += content.count("loadChildren(")
1646
+ # Count lazy route patterns: `loadChildren:` (property syntax used in route
1647
+ # configs) and `loadComponent:` (standalone component lazy loading). The old
1648
+ # `loadChildren(` form counted zero because Angular uses property syntax, not
1649
+ # a function call (BUG-5).
1650
+ fname_lower = rel.replace("\\", "/").split("/")[-1].lower()
1651
+ _is_routing_file = (
1652
+ "routing" in fname_lower
1653
+ or fname_lower in ("app.routes.ts", "app-routing.module.ts")
1654
+ or fname_lower.endswith(".routes.ts")
1655
+ )
1656
+ lazy_routes_count += content.count("loadChildren:")
1657
+ lazy_routes_count += content.count("loadComponent:")
1658
+ if _is_routing_file:
1659
+ # Also count standalone dynamic imports that aren't already caught above
1660
+ _lc_imports = content.count("import(") - content.count("loadChildren:") - content.count("loadComponent:")
1661
+ if _lc_imports > 0:
1662
+ lazy_routes_count += _lc_imports
1647
1663
  akita_stores += content.count("@StoreConfig(")
1648
1664
  if not standalone_components and "bootstrapApplication(" in content:
1649
1665
  standalone_components = True
@@ -1661,7 +1677,13 @@ def _angular_analysis(sm: "SourceMap") -> "Optional[dict[str, Any]]":
1661
1677
  if pkg_json.exists():
1662
1678
  try:
1663
1679
  pkg = _json.loads(pkg_json.read_text(encoding="utf-8", errors="replace"))
1664
- deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
1680
+ # Use `or {}` so explicit `null` values in package.json don't
1681
+ # raise TypeError when unpacking (BUG-4).
1682
+ deps = {
1683
+ **(pkg.get("dependencies") or {}),
1684
+ **(pkg.get("devDependencies") or {}),
1685
+ **(pkg.get("peerDependencies") or {}),
1686
+ }
1665
1687
  av = deps.get("@angular/core")
1666
1688
  if av:
1667
1689
  angular_version = av.lstrip("^~>=")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.30.29
3
+ Version: 1.30.30
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -221,7 +221,7 @@ Description-Content-Type: text/markdown
221
221
 
222
222
  **Deterministic, behavior-aware codebase context for AI agents and PR review.**
223
223
 
224
- ![Version](https://img.shields.io/badge/version-1.30.29-blue)
224
+ ![Version](https://img.shields.io/badge/version-1.30.30-blue)
225
225
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
226
226
 
227
227
  ---
@@ -257,7 +257,7 @@ pipx install sourcecode
257
257
 
258
258
  ```bash
259
259
  sourcecode version
260
- # sourcecode 1.30.29
260
+ # sourcecode 1.30.30
261
261
  ```
262
262
 
263
263
  ---
@@ -1,10 +1,10 @@
1
- sourcecode/__init__.py,sha256=dlUu_PMRN5Q_pvsTBF3LbFJMORBN4BVQ0B9j6mA3GWQ,104
1
+ sourcecode/__init__.py,sha256=ANLn7vd3QuDTWulbelwm9eIp93h5ZEZKi-nKm911_so,104
2
2
  sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
3
3
  sourcecode/architecture_analyzer.py,sha256=MyBa0Hf5HmkudZQDLKrjcWDKETXETXl0mQX1swtTwAA,39091
4
4
  sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
5
5
  sourcecode/ast_extractor.py,sha256=XgrZg2DcWcUm9r87cRG3KGO7IK2TIL_N-CvhSbUmmh4,49901
6
6
  sourcecode/classifier.py,sha256=-0t0HLc9L9UleMLfclfLM3AXhBjUb_AYyBPDbvgWtac,7755
7
- sourcecode/cli.py,sha256=5hKnqahlEko1Z1GV1pg0uzEH2hES3RRqxJ95CRBgXwc,98320
7
+ sourcecode/cli.py,sha256=_WsbkJWvI_xv4aSh3mdPvTbh-eKGV5DZbPj2ZFuA2GI,100189
8
8
  sourcecode/code_notes_analyzer.py,sha256=y1MJBnPZHYp4i6cQCXUb9ATIyifS_qMQWjw_8lPkpsU,9215
9
9
  sourcecode/confidence_analyzer.py,sha256=H9VHYRzZhqMFlSCZffjtsMUGYLnDvrq1g5FjzyQ1hxE,16381
10
10
  sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
@@ -22,7 +22,7 @@ sourcecode/git_analyzer.py,sha256=0Gyj-vMpIIN4nfriKXVRouNYBeJ59s6pQDX2Xu9Pq-U,13
22
22
  sourcecode/graph_analyzer.py,sha256=iUK-7pSV-cvGqqD2hENdYmhnm0wcXFEyK-xnu5ul8OU,62515
23
23
  sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
24
24
  sourcecode/pr_comment_renderer.py,sha256=smHslxiG14lrytCkq5nFrFu-qTHgA-t-LFYfdrfjz2o,14423
25
- sourcecode/prepare_context.py,sha256=Too1ziyHGGvZfNhCMfgCvrTI01xYQeUrwA4veXtgTSI,172167
25
+ sourcecode/prepare_context.py,sha256=QNCl8uKk9PQpgXxPHBNSDXXkc1s2wTZwU6H0REC5Qms,173487
26
26
  sourcecode/progress.py,sha256=qn30sWaHOkjTgXsSBmiPkz7Rsbwc5oSlIe6JNEMYp_k,3149
27
27
  sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
28
28
  sourcecode/redactor.py,sha256=xuGcadGEHaPw4qZXlMDvzMCsr4VOkdp3oBQptHyJk8c,2884
@@ -33,7 +33,7 @@ sourcecode/runtime_classifier.py,sha256=zWX3r3HCKHc-qtIobErOa8aKMmaoPYREtJKvPcBG
33
33
  sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
34
34
  sourcecode/schema.py,sha256=fj3BZ3IcnNV4j21BFIEvz8Qnw_vZoqIbzzRg-qQ-nd0,24530
35
35
  sourcecode/semantic_analyzer.py,sha256=12TwXYkYbDcBdu0heX_EmfPM2EkO8a_r5osf0SaeQbs,88956
36
- sourcecode/serializer.py,sha256=JaEJonNIKaSV6dST3Jn8_Z6alYK9Ba1-M3luh8Xf-yw,110442
36
+ sourcecode/serializer.py,sha256=FM4xklb9Ywg9KNdNpo8QXR50izuml5FkbeQgL2uS1HY,111611
37
37
  sourcecode/summarizer.py,sha256=lPlKhMh28nueXkPo2xKeD3DUFYVGRlJMIdY-8TSM-ls,17486
38
38
  sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
39
39
  sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
@@ -64,8 +64,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
64
64
  sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
65
65
  sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
66
66
  sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
67
- sourcecode-1.30.29.dist-info/METADATA,sha256=39QsgfOfPa-UUti5nV0iLi-gH7KSIz48owb5RoyX12Q,28956
68
- sourcecode-1.30.29.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
69
- sourcecode-1.30.29.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
70
- sourcecode-1.30.29.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
71
- sourcecode-1.30.29.dist-info/RECORD,,
67
+ sourcecode-1.30.30.dist-info/METADATA,sha256=TRgm7Qpvohc3ofAWDRDEfmcF2lCQ3b-dqvEAn-Rd6qw,28956
68
+ sourcecode-1.30.30.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
69
+ sourcecode-1.30.30.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
70
+ sourcecode-1.30.30.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
71
+ sourcecode-1.30.30.dist-info/RECORD,,