sourcecode 0.42.0__py3-none-any.whl → 0.43.0__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__ = "0.42.0"
3
+ __version__ = "0.43.0"
sourcecode/cli.py CHANGED
@@ -181,6 +181,7 @@ _OPTIONS_WITH_VALUE: frozenset[str] = frozenset({
181
181
  "--dependency-depth",
182
182
  "--rank-by",
183
183
  "--symbol",
184
+ "--max-importers",
184
185
  })
185
186
 
186
187
 
@@ -594,6 +595,17 @@ def main(
594
595
  "--symbol",
595
596
  help="Contract mode: extract localized context for a specific symbol name. Returns defining file + all importers.",
596
597
  ),
598
+ max_importers: int = typer.Option(
599
+ 50,
600
+ "--max-importers",
601
+ help=(
602
+ "Maximum importer files returned by --symbol (default: 50). "
603
+ "Popular symbols can have hundreds of importers — this prevents output explosion. "
604
+ "Defining files are never truncated. Override: --symbol Foo --max-importers 200."
605
+ ),
606
+ min=1,
607
+ max=10000,
608
+ ),
597
609
  copy: bool = typer.Option(
598
610
  False,
599
611
  "--copy",
@@ -770,6 +782,21 @@ def main(
770
782
  code_notes = True
771
783
  no_tree = True # agents never need the raw file tree
772
784
  typer.echo("[agent] dependencies env-map code-notes (no-tree)", err=True)
785
+ # Warn about flags that are computed but excluded from agent_view output
786
+ _agent_suppressed: list[str] = []
787
+ if full_metrics:
788
+ _agent_suppressed.append("--full-metrics")
789
+ if graph_modules:
790
+ _agent_suppressed.append("--graph-modules")
791
+ if docs:
792
+ _agent_suppressed.append("--docs")
793
+ if _agent_suppressed:
794
+ typer.echo(
795
+ f"[agent] warning: {', '.join(_agent_suppressed)} computed but excluded "
796
+ "from --agent output — agent_view does not include these sections. "
797
+ "Remove these flags to skip unnecessary computation.",
798
+ err=True,
799
+ )
773
800
 
774
801
  scanner = AdaptiveScanner(target, topology=_topology, base_depth=effective_depth)
775
802
  raw_tree = scanner.scan_tree()
@@ -1343,6 +1370,7 @@ def main(
1343
1370
  changed_only=changed_only,
1344
1371
  symbol=symbol,
1345
1372
  compress_types=compress_types,
1373
+ max_importers=max_importers,
1346
1374
  )
1347
1375
  sm = _replace(sm, file_contracts=_contracts, contract_summary=_contract_summary)
1348
1376
  if symbol is not None and len(_contracts) == 0:
@@ -109,3 +109,4 @@ class ContractSummary:
109
109
  method_breakdown: dict[str, int] = field(default_factory=dict)
110
110
  ranked_by: str = "relevance"
111
111
  limitations: list[str] = field(default_factory=list)
112
+ symbol_truncation: Optional[dict] = None # set when --symbol truncates importers
@@ -175,6 +175,7 @@ class ContractPipeline:
175
175
  changed_only: bool = False,
176
176
  symbol: Optional[str] = None,
177
177
  compress_types: bool = False,
178
+ max_importers: int = 50,
178
179
  ) -> tuple[list[FileContract], ContractSummary]:
179
180
  """Run the full extraction pipeline.
180
181
 
@@ -279,17 +280,19 @@ class ContractPipeline:
279
280
  contracts = self._rank(contracts, rank_by)
280
281
 
281
282
  # 8. Symbol filter — keep files that define or import the symbol
283
+ _symbol_truncation: Optional[dict] = None
282
284
  if symbol:
283
- contracts = _filter_by_symbol(contracts, symbol)
285
+ contracts, _symbol_truncation = _filter_by_symbol(contracts, symbol, max_importers=max_importers)
284
286
  # When shallow scan missed the defining file (deep monorepo), fall back
285
287
  # to a grep-based filesystem search over the full directory tree.
286
288
  if not contracts:
287
- contracts = self._symbol_deep_scan(
289
+ contracts, _symbol_truncation = self._symbol_deep_scan(
288
290
  root, symbol,
289
291
  known_paths=set(src_paths),
290
292
  entry_paths=entry_paths,
291
293
  changed_files=changed_files,
292
294
  engine=engine,
295
+ max_importers=max_importers,
293
296
  )
294
297
 
295
298
  # 9. Entrypoints-only filter
@@ -313,6 +316,7 @@ class ContractPipeline:
313
316
  method_breakdown=dict(method_counts),
314
317
  ranked_by=rank_by,
315
318
  limitations=limitations,
319
+ symbol_truncation=_symbol_truncation,
316
320
  )
317
321
  return contracts, summary
318
322
 
@@ -332,7 +336,8 @@ class ContractPipeline:
332
336
  entry_paths: set[str],
333
337
  changed_files: set[str],
334
338
  engine: RankingEngine,
335
- ) -> list[FileContract]:
339
+ max_importers: int = 50,
340
+ ) -> tuple[list[FileContract], dict]:
336
341
  """Grep-based fallback when the shallow scan missed the defining files.
337
342
 
338
343
  Searches the full directory tree for source files containing *symbol*,
@@ -356,7 +361,7 @@ class ContractPipeline:
356
361
  contract.ranking_reasons = fs.reasons
357
362
  extra.append(contract)
358
363
 
359
- return _filter_by_symbol(extra, symbol)
364
+ return _filter_by_symbol(extra, symbol, max_importers=max_importers)
360
365
 
361
366
 
362
367
  # ---------------------------------------------------------------------------
@@ -412,7 +417,11 @@ def _limit_symbols(contracts: list[FileContract], max_symbols: int) -> list[File
412
417
  # Symbol-aware filter
413
418
  # ---------------------------------------------------------------------------
414
419
 
415
- def _filter_by_symbol(contracts: list[FileContract], symbol: str) -> list[FileContract]:
420
+ def _filter_by_symbol(
421
+ contracts: list[FileContract],
422
+ symbol: str,
423
+ max_importers: int = 50,
424
+ ) -> tuple[list[FileContract], dict]:
416
425
  """Return contracts that define, import, or structurally reference *symbol*.
417
426
 
418
427
  Four tiers applied in order:
@@ -423,6 +432,8 @@ def _filter_by_symbol(contracts: list[FileContract], symbol: str) -> list[FileCo
423
432
  function signatures (word-boundary). Only used when tiers 1-3 fail.
424
433
 
425
434
  Defining contracts are ranked first; importers and references follow.
435
+ max_importers caps tier 3 results to prevent output explosion on popular symbols.
436
+ Returns (contracts, truncation_metadata).
426
437
  """
427
438
  sym_l = symbol.lower()
428
439
  word_re = re.compile(
@@ -466,8 +477,14 @@ def _filter_by_symbol(contracts: list[FileContract], symbol: str) -> list[FileCo
466
477
 
467
478
  # Tier 3: import matching (case-insensitive when no definers found)
468
479
  ci_imports = len(defining) == 0
469
- importer_paths = {c.path for c in contracts if _imports_sym(c, case=ci_imports)}
470
- importers = [c for c in contracts if c.path in importer_paths and c.path not in defining_paths]
480
+ all_importer_paths = {c.path for c in contracts if _imports_sym(c, case=ci_imports)}
481
+ all_importers = [c for c in contracts if c.path in all_importer_paths and c.path not in defining_paths]
482
+
483
+ # Apply importer cap — definers are never truncated
484
+ total_importers = len(all_importers)
485
+ truncated = total_importers > max_importers
486
+ importers = all_importers[:max_importers] if truncated else all_importers
487
+ importer_paths = {c.path for c in importers}
471
488
 
472
489
  # Tier 4: type-reference matching (only when tiers 1-3 yield nothing)
473
490
  references: list[FileContract] = []
@@ -483,12 +500,27 @@ def _filter_by_symbol(contracts: list[FileContract], symbol: str) -> list[FileCo
483
500
  seen.add(c.path)
484
501
  merged.append(c)
485
502
 
486
- return sorted(merged, key=lambda c: (
503
+ result = sorted(merged, key=lambda c: (
487
504
  c.path not in defining_paths,
488
505
  c.path not in importer_paths,
489
506
  -c.relevance_score,
490
507
  ))
491
508
 
509
+ truncation: dict = {
510
+ "symbol": symbol,
511
+ "definers_found": len(defining),
512
+ "importers_found": total_importers,
513
+ "importers_returned": len(importers),
514
+ "references_found": len(references),
515
+ "total_returned": len(result),
516
+ "truncated": truncated,
517
+ }
518
+ if truncated:
519
+ truncation["truncation_reason"] = "max_importers_limit"
520
+ truncation["override_hint"] = f"--symbol {symbol} --max-importers {total_importers}"
521
+
522
+ return result, truncation
523
+
492
524
 
493
525
  # ---------------------------------------------------------------------------
494
526
  # Deep symbol scan — grep-based fallback for shallow-scanned repos
sourcecode/serializer.py CHANGED
@@ -722,8 +722,10 @@ def agent_view(sm: SourceMap) -> dict[str, Any]:
722
722
  # production runtime is represented as entry_points=[], never by fallback.
723
723
  ep_groups = _entry_point_groups(sm.entry_points)
724
724
  result["entry_points"] = ep_groups["production"]
725
- result["development_entry_points"] = ep_groups["development"]
726
- result["auxiliary_entry_points"] = ep_groups["auxiliary"]
725
+ if ep_groups["development"]:
726
+ result["development_entry_points"] = ep_groups["development"]
727
+ if ep_groups["auxiliary"]:
728
+ result["auxiliary_entry_points"] = ep_groups["auxiliary"]
727
729
 
728
730
  # ── 3. Architecture ───────────────────────────────────────────────────────
729
731
  result["architecture"] = _architecture_context(sm)
@@ -888,6 +890,23 @@ def agent_view(sm: SourceMap) -> dict[str, Any]:
888
890
  if analysis_gaps:
889
891
  result["analysis_gaps"] = analysis_gaps
890
892
 
893
+ # ── 8. Agent mode metadata — explicit transparency about auto-enabled/suppressed flags ──
894
+ _auto_enabled: list[str] = ["--dependencies", "--env-map", "--code-notes"]
895
+ _suppressed: list[str] = []
896
+ if sm.metrics_summary is not None and sm.metrics_summary.requested:
897
+ _suppressed.append("--full-metrics")
898
+ if sm.module_graph is not None and sm.module_graph.summary.requested:
899
+ _suppressed.append("--graph-modules")
900
+ if sm.doc_summary is not None and sm.doc_summary.requested:
901
+ _suppressed.append("--docs")
902
+ agent_mode_meta: dict[str, Any] = {
903
+ "auto_enabled": _auto_enabled,
904
+ }
905
+ if _suppressed:
906
+ agent_mode_meta["suppressed_flags"] = _suppressed
907
+ agent_mode_meta["suppressed_note"] = "computed but excluded from agent_view"
908
+ result["agent_mode"] = agent_mode_meta
909
+
891
910
  return result
892
911
 
893
912
 
@@ -918,9 +937,11 @@ def standard_view(sm: SourceMap, *, include_tree: bool = False) -> dict[str, Any
918
937
  "architecture_summary": sm.architecture_summary,
919
938
  "stacks": [asdict(s) for s in sm.stacks],
920
939
  "entry_points": ep_groups["production"],
921
- "development_entry_points": ep_groups["development"],
922
- "auxiliary_entry_points": ep_groups["auxiliary"],
923
940
  }
941
+ if ep_groups["development"]:
942
+ result["development_entry_points"] = ep_groups["development"]
943
+ if ep_groups["auxiliary"]:
944
+ result["auxiliary_entry_points"] = ep_groups["auxiliary"]
924
945
 
925
946
  # Layer B — signals (only when the corresponding analyzer ran)
926
947
  if sm.dependency_summary is not None and sm.dependency_summary.requested:
@@ -1125,6 +1146,8 @@ def _contract_view_minimal(
1125
1146
  summary["degraded"] = True
1126
1147
  summary["degraded_hint"] = "install sourcecode[ast] for full TS/JS extraction"
1127
1148
  result["summary"] = summary
1149
+ if cs.symbol_truncation:
1150
+ result["symbol_query"] = cs.symbol_truncation
1128
1151
 
1129
1152
  return result
1130
1153
 
@@ -1404,6 +1427,8 @@ def _contract_view_standard(
1404
1427
  }
1405
1428
  if cs.limitations:
1406
1429
  result["contract_summary"]["limitations"] = cs.limitations
1430
+ if cs.symbol_truncation:
1431
+ result["symbol_query"] = cs.symbol_truncation
1407
1432
 
1408
1433
  return result
1409
1434
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 0.42.0
3
+ Version: 0.43.0
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -1,15 +1,15 @@
1
- sourcecode/__init__.py,sha256=K7shxEMemP2ulUio4YBuziIbKkDcIuDkcsLEFth5CwM,103
1
+ sourcecode/__init__.py,sha256=ktYEudER40ycgGKE7MG3VPlJ8UQQPhREtYx6twzDGUM,103
2
2
  sourcecode/adaptive_scanner.py,sha256=6dh34C2qZXyRbw-8xBhbEwDdXanM6CRFRWayVoYITnA,10190
3
3
  sourcecode/architecture_analyzer.py,sha256=O4AXc7l_WTzIXrcAzstqZy-TGKNaFa6p3MzpgVjaO8g,27749
4
4
  sourcecode/architecture_summary.py,sha256=rSY5MRiaz4N1YdG0pqDTDuFjSN7PO_Zplx-dtNzv2Yo,19985
5
5
  sourcecode/ast_extractor.py,sha256=0OHQwTUBBc9lmqPLryVeB1z8dGIC6NhLlar800CD9oI,41129
6
6
  sourcecode/classifier.py,sha256=GKTMN8qKZX7ponSwDJfN08RrasI4CVpq1_gFBgEopps,7093
7
- sourcecode/cli.py,sha256=BBAS66tCeNt48iZrykJZ-H0TpI3zmgrAs6P3H3NSIws,67589
7
+ sourcecode/cli.py,sha256=tsubK4RGYtqQEtknH5bKYgsJmeTWfEWk_DSujbZYb70,68783
8
8
  sourcecode/code_notes_analyzer.py,sha256=rRd8bFYV0krjlxxQV0wenwE9K7pVpUQSR7KvSvUQKw4,9226
9
9
  sourcecode/confidence_analyzer.py,sha256=HxJMPLI5ulqtkncnv98W4iVO6yMbpQo87VuxiuNbDmY,12167
10
10
  sourcecode/context_summarizer.py,sha256=CiQrfBEzun949bWvmLabWoj2HhPn6Lw62ofqnsy0FlQ,6503
11
- sourcecode/contract_model.py,sha256=wpYNWGzHAVnyGxniGqNMk96TCmWbVVOqNSc3Kauajrg,3348
12
- sourcecode/contract_pipeline.py,sha256=af30z1l4LiSOngawYkrpzQC-8huIJOgbQ8EJrq_PDSc,22967
11
+ sourcecode/contract_model.py,sha256=gCf9-Kj0G7l0lvRTAcRfFAfMgs1Rpizv4mKovQLYUkw,3434
12
+ sourcecode/contract_pipeline.py,sha256=dTOvoaJy-S_hLZtpqpLxjb0dmnPyGnKabLUzS3DlJ-s,24278
13
13
  sourcecode/coverage_parser.py,sha256=q0LeZJaX1bnntLu-ImksdBsMlpsVmk_iUfSaB4eaJGo,19702
14
14
  sourcecode/dependency_analyzer.py,sha256=Exq0BfInvfS5iAg9xAr6WI2uPNuotkIudTKcYJcRhB8,52757
15
15
  sourcecode/doc_analyzer.py,sha256=TttdS7mndKQhyJCfJnnAsyGCJrf-TIL7oXxDlTLUFKE,21248
@@ -28,7 +28,7 @@ sourcecode/runtime_classifier.py,sha256=zWX3r3HCKHc-qtIobErOa8aKMmaoPYREtJKvPcBG
28
28
  sourcecode/scanner.py,sha256=aM3h9-DCQ3xKpeHpHYdo2vX6T5P95HA_YwZbkAVNwmo,8288
29
29
  sourcecode/schema.py,sha256=ofEge9hTWHOTjeWt7ceCDQWzP-uhhenrYX2usjW2KVU,22759
30
30
  sourcecode/semantic_analyzer.py,sha256=16EFTgM7ooW0m5gNUKOlTSn7IEMLSzKmzQn-cWaSqjs,82604
31
- sourcecode/serializer.py,sha256=VUiBxA2w9CqlblXqhHQMXEUvysxTaNljgiATbw6MJ4A,56927
31
+ sourcecode/serializer.py,sha256=h7KuMcDi7K-BcnDbXZu8q5MTE3PwyIZcU8Is4_Vv32Q,58107
32
32
  sourcecode/summarizer.py,sha256=ZuzIdm3t8A-d5MuQL0TSNLrd-L0IQIuguIxeNXMNJf8,16070
33
33
  sourcecode/tree_utils.py,sha256=Fj9OIuUksBvgibNd3feog0sMDjVypJzPexp5lvMoYWI,1424
34
34
  sourcecode/workspace.py,sha256=fQlVoNx8S-fSHpKoJ0JBvEHCFkxszH0KZVJed1i3TRk,6845
@@ -59,8 +59,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
59
59
  sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
60
60
  sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
61
61
  sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
62
- sourcecode-0.42.0.dist-info/METADATA,sha256=-H--yzWSnQ5wpiUOXDmKirFowuaAGWb-LhUMSLYiTQ8,25209
63
- sourcecode-0.42.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
64
- sourcecode-0.42.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
65
- sourcecode-0.42.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
66
- sourcecode-0.42.0.dist-info/RECORD,,
62
+ sourcecode-0.43.0.dist-info/METADATA,sha256=7-5QNqOmUMepNrGq4TmK5JAy-QDIHyWyb8-RdxaRQ0k,25209
63
+ sourcecode-0.43.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
64
+ sourcecode-0.43.0.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
65
+ sourcecode-0.43.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
66
+ sourcecode-0.43.0.dist-info/RECORD,,