sourcecode 2.0.0__py3-none-any.whl → 2.1.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
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "2.0.0"
7
+ __version__ = "2.1.0"
sourcecode/cli.py CHANGED
@@ -4496,7 +4496,18 @@ def validation_cmd(
4496
4496
  from sourcecode.context_graph import ContextGraph
4497
4497
  from sourcecode.validation_surface import build_validation_surface
4498
4498
  # Structural facts come from the ContextGraph — the single access layer.
4499
- data = build_validation_surface(target, graph=ContextGraph.build_from_root(target))
4499
+ _graph = ContextGraph.build_from_root(target)
4500
+ data = build_validation_surface(target, graph=_graph)
4501
+
4502
+ # P1-C: classify the request-body validation *pattern* so a repo with a
4503
+ # validation framework on the classpath but no @Valid reads as
4504
+ # "present-but-unused" instead of a silent sea of zeros (DESIGN §2/§5).
4505
+ try:
4506
+ from sourcecode.validation_inference import infer_validation_pattern
4507
+
4508
+ data["validation_pattern"] = infer_validation_pattern(_graph.cir).to_dict()
4509
+ except Exception:
4510
+ pass
4500
4511
 
4501
4512
  if path_prefix:
4502
4513
  data["endpoints"] = [
@@ -4836,6 +4847,16 @@ def migrate_check_cmd(
4836
4847
  help="Minimum severity to include: critical, high, medium, or low (default).",
4837
4848
  show_default=True,
4838
4849
  ),
4850
+ compact: bool = typer.Option(
4851
+ False,
4852
+ "--compact",
4853
+ help=(
4854
+ "Bounded decision summary (JSON): readiness scores, headline blocker, "
4855
+ "effort, executive summary, Hibernate classification and aggregate "
4856
+ "counts, with large collections capped to their top items (+N more). "
4857
+ "No new analysis — same report, projected. Full detail is the default."
4858
+ ),
4859
+ ),
4839
4860
  no_cache: bool = typer.Option(
4840
4861
  False, "--no-cache",
4841
4862
  help="Accepted for compatibility; this command always reads fresh source (no snapshot cache). No-op.",
@@ -4867,6 +4888,7 @@ def migrate_check_cmd(
4867
4888
  \b
4868
4889
  Examples:
4869
4890
  ask migrate-check .
4891
+ ask migrate-check . --compact bounded decision summary
4870
4892
  ask migrate-check /path/to/repo --format text
4871
4893
  ask migrate-check . --min-severity high
4872
4894
  ask migrate-check . --output migration.json
@@ -4905,7 +4927,11 @@ def migrate_check_cmd(
4905
4927
  if format == "text":
4906
4928
  output = report.to_text(min_severity=min_severity)
4907
4929
  else:
4908
- output = _serialize_dict(report.to_dict(), "json")
4930
+ # --compact projects the SAME report into a bounded, decision-grade view
4931
+ # (output-shaping only). Default (full) output is unchanged — it is the
4932
+ # baseline-oracle form and must stay byte-identical.
4933
+ payload = report.to_compact_dict() if compact else report.to_dict()
4934
+ output = _serialize_dict(payload, "json")
4909
4935
 
4910
4936
  _total = report.summary.get("total_findings", 0)
4911
4937
  _emit_command_output(
@@ -1308,6 +1308,33 @@ def _dimension_score(
1308
1308
  return max(0, 100 - deduction)
1309
1309
 
1310
1310
 
1311
+ # --- Compact projection (output-shaping only) --------------------------------
1312
+ # `--compact` renders a bounded, decision-grade view of the SAME report: it caps
1313
+ # each large leaf collection to its top-N items and reports total/omitted counts.
1314
+ # It performs NO new analysis — it is a pure projection over the dict `to_dict()`
1315
+ # already produces (Semantic IR / ContextGraph / semantic engines untouched).
1316
+ # The default (full) output is unchanged and remains the baseline-oracle form.
1317
+ # FUTURE (not this increment): invert the default — emit compact by default and
1318
+ # add `--full` for the complete report — once the oracle baseline is recaptured.
1319
+ _COMPACT_TOP_N = 5
1320
+
1321
+
1322
+ def _cap_collection(items: list, top_n: int, key=None) -> dict:
1323
+ """Project a list into a bounded `{total, shown, omitted, items}` view. Items
1324
+ are optionally reordered by `key` (stable) so the retained slice is the most
1325
+ important; ordering falls back to the engine's own emission order. No values
1326
+ are recomputed — only selected and counted."""
1327
+ total = len(items)
1328
+ ordered = sorted(items, key=key) if key is not None else items
1329
+ shown = ordered[:top_n]
1330
+ return {
1331
+ "total": total,
1332
+ "shown": len(shown),
1333
+ "omitted": max(0, total - len(shown)),
1334
+ "items": shown,
1335
+ }
1336
+
1337
+
1311
1338
  @dataclass
1312
1339
  class MigrationReport:
1313
1340
  schema_version: str = "1.4"
@@ -1679,6 +1706,43 @@ class MigrationReport:
1679
1706
  "metadata": self.metadata,
1680
1707
  }
1681
1708
 
1709
+ def to_compact_dict(self, top_n: int = _COMPACT_TOP_N) -> dict:
1710
+ """Bounded, decision-grade projection of `to_dict()`.
1711
+
1712
+ Keeps every decision field verbatim (readiness_score, per-dimension
1713
+ readiness, headline_blocker, blocking_count, estimated_effort_days,
1714
+ effort_breakdown, summary, Hibernate classification, aggregate counts) and
1715
+ caps only the large leaf collections — top-level `findings` and the
1716
+ Hibernate `rewrite_targets` / `findings` / `critical_call_chains` /
1717
+ `golden_sql_hotspots` — to their top-`top_n` items, each wrapped as
1718
+ `{total, shown, omitted, items}`. It reuses the already-computed
1719
+ `to_dict()` output and recomputes nothing (output-shaping only)."""
1720
+ full = self.to_dict()
1721
+ compact = dict(full)
1722
+ compact["mode"] = "compact"
1723
+ compact["compact_note"] = (
1724
+ f"Bounded decision summary: large collections are capped to the top {top_n} "
1725
+ "items (see each collection's total/omitted). Run without --compact (JSON "
1726
+ "default) for the complete report. A future release will invert this — "
1727
+ "compact by default with --full for the full detail."
1728
+ )
1729
+ # Top-level findings: retain the most severe first, then engine order.
1730
+ compact["findings"] = _cap_collection(
1731
+ full["findings"], top_n,
1732
+ key=lambda f: SEVERITY_ORDER.get(f.get("severity"), 3),
1733
+ )
1734
+ hib = full.get("hibernate")
1735
+ if isinstance(hib, dict):
1736
+ h = dict(hib)
1737
+ for _col in (
1738
+ "rewrite_targets", "findings",
1739
+ "critical_call_chains", "golden_sql_hotspots",
1740
+ ):
1741
+ if isinstance(h.get(_col), list):
1742
+ h[_col] = _cap_collection(h[_col], top_n)
1743
+ compact["hibernate"] = h
1744
+ return compact
1745
+
1682
1746
  def to_text(self, min_severity: str = "low") -> str:
1683
1747
  min_order = SEVERITY_ORDER.get(min_severity, 3)
1684
1748
  visible = [f for f in self.findings if SEVERITY_ORDER.get(f.severity, 3) <= min_order]