sourcecode 4.6.0__py3-none-any.whl → 4.7.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.

Potentially problematic release.


This version of sourcecode might be problematic. Click here for more details.

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__ = "4.6.0"
7
+ __version__ = "4.7.0"
sourcecode/cli.py CHANGED
@@ -354,7 +354,7 @@ of files) in minutes. Semantic analysis itself is sub-second; repo indexing domi
354
354
  ask --agent [dim]# full structured JSON for AI agents[/dim]
355
355
 
356
356
  [bold]Change and risk:[/bold]
357
- impact-chain <Class> . [dim]# blast radius w/ TX + security per hop[/dim]
357
+ impact-chain <Class> . [dim]# blast radius; TX/SEC findings opt-in[/dim]
358
358
  impact <Class> . [dim]# reverse deps → endpoints reached[/dim]
359
359
  pr-impact . --files - [dim]# same, scoped to a diff on stdin; gating codes[/dim]
360
360
  posture . --since main [dim]# did this branch open an endpoint? (exp.)[/dim]
@@ -8419,8 +8419,16 @@ def impact_chain_cmd(
8419
8419
  "surfaces). Totals stay exact in metadata/truncated_lists. "
8420
8420
  "0 disables the cap.",
8421
8421
  ),
8422
+ with_findings: bool = typer.Option(
8423
+ False, "--with-findings",
8424
+ help=(
8425
+ "Also run the repository-wide TX + security audit and filter findings "
8426
+ "to this chain. Off by default so query cost follows the queried "
8427
+ "subgraph rather than the whole repository."
8428
+ ),
8429
+ ),
8422
8430
  ) -> None:
8423
- """Spring impact-chain: systemic blast radius of a symbol with TX/SEC enrichment.
8431
+ """Spring impact-chain: systemic blast radius of a symbol; TX/SEC findings are opt-in.
8424
8432
 
8425
8433
  \b
8426
8434
  Given a symbol (class or method), returns:
@@ -8431,6 +8439,7 @@ def impact_chain_cmd(
8431
8439
  - security_surfaces — per-endpoint security verdict + confidence
8432
8440
  (security_posture authority) + declared policy + SEC findings
8433
8441
  - impact_findings — TX/SEC audit findings touching the call chain
8442
+ (only with --with-findings)
8434
8443
  - risk_level — critical | high | medium | low
8435
8444
 
8436
8445
  \b
@@ -8537,7 +8546,10 @@ def impact_chain_cmd(
8537
8546
  )
8538
8547
  return
8539
8548
 
8540
- result = run_impact_chain(cir, symbol, depth=depth, root=target, model=_model)
8549
+ result = run_impact_chain(
8550
+ cir, symbol, depth=depth, root=target, model=_model,
8551
+ include_findings=with_findings,
8552
+ )
8541
8553
 
8542
8554
  data = result.to_dict()
8543
8555
  # Same rule as `impact`: a file is a request for the whole answer (C2-2). An
@@ -457,6 +457,10 @@ class JavaDetector(AbstractDetector):
457
457
  or "resteasy" in text # RESTEasy — a JAX-RS implementation
458
458
  ):
459
459
  frameworks.append(FrameworkDetection(name="JAX-RS", source=source))
460
+ if "com.sun.jersey" in text or "org.glassfish.jersey" in text:
461
+ frameworks.append(FrameworkDetection(name="Jersey", source=source))
462
+ if "com.netflix.governator" in text:
463
+ frameworks.append(FrameworkDetection(name="Governator", source=source))
460
464
  if "mybatis" in text:
461
465
  frameworks.append(FrameworkDetection(name="MyBatis", source=source))
462
466
  if "thymeleaf" in text:
@@ -1324,13 +1324,14 @@ min_severity: "low" (default) | "medium" | "high" | "critical"
1324
1324
  """
1325
1325
 
1326
1326
  _IMPACT_CHAIN_DOC = """\
1327
- Spring impact-chain: blast radius of a symbol with TX/SEC semantic enrichment. JAVA/SPRING ONLY.
1327
+ Spring impact-chain: blast radius of a symbol. TX/SEC findings are opt-in. JAVA/SPRING ONLY.
1328
1328
 
1329
1329
  Do NOT call on non-Java repositories — returns resolution=not_found.
1330
1330
 
1331
1331
  Two query modes via query_type:
1332
1332
  "impact" (default) — BFS call graph: direct_callers, indirect_callers, endpoints_affected,
1333
- transaction_boundary, security_surfaces, impact_findings (TX/SEC patterns in call chain).
1333
+ transaction_boundary, security_surfaces. impact_findings are populated only when
1334
+ with_findings=true, because that runs a repository-wide TX + security audit.
1334
1335
  "events" — event topology: publishers, consumers, propagation graph for an event class
1335
1336
  or event publisher. Use when symbol is an event class (e.g. OrderPlacedEvent).
1336
1337
 
@@ -1344,6 +1345,7 @@ symbol: FQN, class name, or Class#method.
1344
1345
  repo_path: absolute path to the Java repository (default: current working directory).
1345
1346
  depth: BFS traversal depth 1–8 (default 4).
1346
1347
  query_type: "impact" (default) | "events"
1348
+ with_findings: false (default) | true to run the repository-wide TX + security audit
1347
1349
  """
1348
1350
 
1349
1351
  spring_audit = _alias_spec(
@@ -1374,7 +1376,7 @@ query_type: "impact" (default) | "events"
1374
1376
 
1375
1377
  impact_chain = _alias_spec(
1376
1378
  "impact_chain",
1377
- "Spring impact-chain: blast radius + TX/SEC enrichment. JAVA/SPRING ONLY.",
1379
+ "Spring impact-chain: blast radius; TX/SEC findings opt-in. JAVA/SPRING ONLY.",
1378
1380
  ("impact-chain",),
1379
1381
  (
1380
1382
  ToolParamSpec("symbol", "argument", str, required=True, default=None,
@@ -1386,6 +1388,9 @@ query_type: "impact" (default) | "events"
1386
1388
  ToolParamSpec("query_type", "option", str, required=False, default="impact",
1387
1389
  option_names=("--type",), choices=("impact", "events"),
1388
1390
  help="impact (default) = call-chain blast radius; events = event topology"),
1391
+ ToolParamSpec("with_findings", "option", bool, required=False, default=False,
1392
+ option_names=("--with-findings",), is_flag=True,
1393
+ help="Run TX + security audit and filter findings to this chain."),
1389
1394
  ),
1390
1395
  lambda inputs: [
1391
1396
  "impact-chain",
@@ -1393,7 +1398,7 @@ query_type: "impact" (default) | "events"
1393
1398
  str(inputs.get("repo_path", ".")),
1394
1399
  "--depth", str(inputs.get("depth", 4)),
1395
1400
  "--type", str(inputs.get("query_type", "impact")),
1396
- ],
1401
+ ] + (["--with-findings"] if inputs.get("with_findings") else []),
1397
1402
  supported_targets=("repo_path", "class_name"),
1398
1403
  unsupported_targets=("file_path",),
1399
1404
  validator=validate_repo_path,
sourcecode/mcp/server.py CHANGED
@@ -883,24 +883,31 @@ def get_migration_readiness(repo_path: str = ".", min_severity: str = "low") ->
883
883
 
884
884
 
885
885
  @mcp.tool()
886
- def get_impact_chain(repo_path: str = ".", symbol: str = "", depth: int = 4) -> dict:
887
- """Spring impact-chain: systemic blast radius of a symbol with TX/SEC semantic enrichment. JAVA/SPRING ONLY.
886
+ def get_impact_chain(
887
+ repo_path: str = ".",
888
+ symbol: str = "",
889
+ depth: int = 4,
890
+ with_findings: bool = False,
891
+ ) -> dict:
892
+ """Spring impact-chain: systemic blast radius of a symbol. TX/SEC findings are opt-in. JAVA/SPRING ONLY.
888
893
 
889
894
  Do NOT call this on non-Java repositories — it will return resolution=not_found.
890
895
 
891
- Maps to: ask impact-chain <symbol> <repo_path> [--depth <depth>]
896
+ Maps to: ask impact-chain <symbol> <repo_path> [--depth <depth>] [--with-findings]
892
897
  Returns: ImpactChainResult with schema_version, symbol, resolution,
893
898
  direct_callers, indirect_callers, endpoints_affected,
894
899
  transaction_boundary (propagation/isolation/read_only),
895
900
  security_surfaces (per-endpoint verdict/confidence from the
896
901
  security_posture authority + declared policy + finding IDs),
897
- impact_findings (TX-001..005 + SEC-001..003 findings in call chain),
902
+ impact_findings (TX/SEC/DEAD findings in call chain, only when
903
+ with_findings=true),
898
904
  analysis_warnings, risk_level, confidence, metadata.
899
905
 
900
906
  symbol: FQN, class name, or Class#method. Examples:
901
907
  "OrderService", "com.example.OrderService#placeOrder"
902
908
  repo_path: absolute path to the Java repository (default: current working directory).
903
909
  depth: BFS depth for indirect caller traversal (1–8, default: 4).
910
+ with_findings: run repository-wide TX + security audit and filter findings to this chain.
904
911
  """
905
912
  _raw = repo_path
906
913
  try:
@@ -915,6 +922,8 @@ def get_impact_chain(repo_path: str = ".", symbol: str = "", depth: int = 4) ->
915
922
  if _path_err is not None:
916
923
  return _path_err
917
924
  args = ["impact-chain", symbol.strip(), repo_path, "--depth", str(depth)]
925
+ if with_findings:
926
+ args.append("--with-findings")
918
927
  timeout_ms = int(os.environ.get("SOURCECODE_IMPACT_TIMEOUT_MS", str(_DEFAULT_IMPACT_TIMEOUT_MS)))
919
928
  timeout_s = timeout_ms / 1000.0
920
929
  _exec = concurrent.futures.ThreadPoolExecutor(max_workers=1)
@@ -1472,6 +1472,11 @@ class MigrationReport:
1472
1472
  # dimension scores it aggregates) so the headline number is fully traceable.
1473
1473
  readiness_aggregate: dict = field(default_factory=dict)
1474
1474
  blocking_count: int = 0
1475
+ # Critical/high product findings on dimensions that participate in the
1476
+ # migration-readiness headline. `blocking_count` remains the broad product
1477
+ # blocker count for backward compatibility; this one excludes orthogonal JDK
1478
+ # upkeep that never feeds readiness_score.
1479
+ migration_blocking_count: int = 0
1475
1480
  # C1-12: null when the total is not a point (a range slice contributes to it).
1476
1481
  # The span always exists — read estimated_effort_range_days for the number.
1477
1482
  estimated_effort_days: Optional[float] = 0.0
@@ -1567,6 +1572,11 @@ class MigrationReport:
1567
1572
  self.blocking_count = sum(
1568
1573
  1 for f in main_findings if f.severity in ("critical", "high")
1569
1574
  )
1575
+ self.migration_blocking_count = sum(
1576
+ 1 for f in main_findings
1577
+ if f.severity in ("critical", "high")
1578
+ and f.migration_target in _BOOT3_MIGRATION_TARGETS
1579
+ )
1570
1580
 
1571
1581
  # Per-dimension readiness — independent severity-weighted scores (MAIN only).
1572
1582
  self.jakarta_readiness = _dimension_score(main_findings, _JAKARTA_TARGETS)
@@ -1941,6 +1951,7 @@ class MigrationReport:
1941
1951
  "by_rule": by_rule,
1942
1952
  "by_migration_target": by_target,
1943
1953
  "main_findings": len(main_findings),
1954
+ "migration_blocking_findings": self.migration_blocking_count,
1944
1955
  "non_blocking_findings": self.non_blocking["count"],
1945
1956
  "hygiene_findings": self.hygiene_findings,
1946
1957
  }
@@ -1993,10 +2004,21 @@ class MigrationReport:
1993
2004
  "(jakarta / boot3 / hibernate); see readiness_aggregate for the exact "
1994
2005
  "inputs. N/A dimensions are excluded (never counted as 0); "
1995
2006
  "jdk_modernization is orthogonal upkeep and is NOT in the aggregate. "
1996
- "For decisions read the per-dimension breakdown + blocking_count."
2007
+ "For decisions read the per-dimension breakdown + "
2008
+ "migration_blocking_count."
1997
2009
  ),
1998
2010
  "headline_blocker": self.headline_blocker,
1999
2011
  "blocking_count": self.blocking_count,
2012
+ "blocking_count_basis": (
2013
+ "critical/high findings in product code across every target, including "
2014
+ "orthogonal JDK modernization"
2015
+ ),
2016
+ "migration_blocking_count": self.migration_blocking_count,
2017
+ "migration_blocking_count_basis": (
2018
+ "critical/high product-code findings whose targets feed the migration "
2019
+ "readiness aggregate (jakarta / boot3 / spring_security_6). Excludes "
2020
+ "orthogonal JDK modernization."
2021
+ ),
2000
2022
  "estimated_effort_days": self.estimated_effort_days,
2001
2023
  "estimated_effort_range_days": self.estimated_effort_range_days,
2002
2024
  "estimated_effort_note": (
@@ -2116,6 +2138,7 @@ class MigrationReport:
2116
2138
  f"(readiness_score reflects jakarta/Boot3 only — Hibernate is a separate rewrite axis)"]
2117
2139
  if self.headline_blocker else []),
2118
2140
  f"Spring present: {self.spring_present} Spring Boot 2 detected: {_boot}",
2141
+ f"Migration blockers (readiness dimensions): {self.migration_blocking_count}",
2119
2142
  f"Blocking issues (product code): {self.blocking_count} "
2120
2143
  f"(critical: {main_crit}, high: {main_high})"
2121
2144
  + (f" [+{nb} in test/generated, non-blocking]" if nb else ""),
sourcecode/perf.py CHANGED
@@ -37,6 +37,9 @@ SCHEMA_VERSION = "perf-baseline-v1"
37
37
  #: guesses: repo discovery, what-is-this-repo detection+enrichment, model
38
38
  #: assembly + semantic/contract extraction, and output rendering.
39
39
  PHASES: tuple[str, ...] = ("discovery", "detection", "analysis", "serialize")
40
+ SCALE_DIMENSIONS: tuple[str, ...] = (
41
+ "files", "symbols", "classes", "methods", "endpoints", "findings",
42
+ )
40
43
 
41
44
  #: Env var that arms the in-process recorder.
42
45
  ENV_ENABLE = "SOURCECODE_PERF"
@@ -248,12 +251,24 @@ def build_baseline_cell(
248
251
  phase_ms[ph] = None
249
252
  phase_ms["instrumentation"] = "absent"
250
253
 
254
+ repo_dict = dict(repo)
255
+ scale_dimensions = {
256
+ key: repo_dict[key]
257
+ for key in SCALE_DIMENSIONS
258
+ if key in repo_dict
259
+ }
260
+
251
261
  return {
252
262
  "schema_version": SCHEMA_VERSION,
253
263
  "tool_version": tool_version,
254
264
  "captured_at": captured_at or datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
255
265
  "command": command,
256
- "repo": dict(repo),
266
+ "repo": repo_dict,
267
+ "scale_dimensions": scale_dimensions,
268
+ "scale_dimensions_basis": (
269
+ "The repository axes this timing is expected to scale over. A performance "
270
+ "claim without these dimensions is not comparable across repositories."
271
+ ),
257
272
  "mode": mode,
258
273
  "runs": len(wall_ms_runs),
259
274
  "wall_ms": {
@@ -266,3 +281,99 @@ def build_baseline_cell(
266
281
  "rss_peak_bytes": _stat_block(rss_bytes_runs) if rss_bytes_runs else None,
267
282
  "env": dict(env) if env is not None else collect_env(),
268
283
  }
284
+
285
+
286
+ def regression_gate(
287
+ baseline: Mapping[str, object],
288
+ current: Mapping[str, object],
289
+ *,
290
+ max_regression: float = 0.25,
291
+ allow_incomparable: bool = False,
292
+ ) -> dict[str, object]:
293
+ """Compare two perf cells and decide whether the current run regressed.
294
+
295
+ Same-host comparability is mandatory by default. Timing deltas compare p50
296
+ and p95 wall time; either one breaching the threshold fails the gate.
297
+ """
298
+ comparable, reasons = _perf_comparability(baseline, current)
299
+ if not comparable and not allow_incomparable:
300
+ return {
301
+ "status": "incomparable",
302
+ "passed": False,
303
+ "reasons": reasons,
304
+ "threshold": max_regression,
305
+ "basis": (
306
+ "Perf regressions are gated only between comparable cells: same "
307
+ "schema, command, repo, mode and hostname_hash."
308
+ ),
309
+ }
310
+
311
+ def _wall(cell: Mapping[str, object], key: str) -> float | None:
312
+ wall = cell.get("wall_ms")
313
+ if not isinstance(wall, Mapping):
314
+ return None
315
+ val = wall.get(key)
316
+ return float(val) if isinstance(val, (int, float)) else None
317
+
318
+ deltas: dict[str, object] = {}
319
+ failures: list[str] = []
320
+ for key in ("p50", "p95"):
321
+ base = _wall(baseline, key)
322
+ head = _wall(current, key)
323
+ if base is None or head is None or base <= 0:
324
+ deltas[key] = {"status": "unmeasured", "baseline": base, "current": head}
325
+ continue
326
+ ratio = (head - base) / base
327
+ deltas[key] = {
328
+ "baseline": base,
329
+ "current": head,
330
+ "delta": round(head - base, 3),
331
+ "regression_ratio": round(ratio, 4),
332
+ "regression_percent": round(ratio * 100.0, 2),
333
+ }
334
+ if ratio > max_regression:
335
+ failures.append(key)
336
+
337
+ return {
338
+ "status": "fail" if failures else "pass",
339
+ "passed": not failures,
340
+ "threshold": max_regression,
341
+ "failed_metrics": failures,
342
+ "deltas": deltas,
343
+ "scale_dimensions": {
344
+ "baseline": baseline.get("scale_dimensions")
345
+ or (
346
+ baseline.get("repo")
347
+ if isinstance(baseline.get("repo"), Mapping) else {}
348
+ ),
349
+ "current": current.get("scale_dimensions")
350
+ or (
351
+ current.get("repo")
352
+ if isinstance(current.get("repo"), Mapping) else {}
353
+ ),
354
+ },
355
+ "basis": (
356
+ "Fails when current wall_ms.p50 or wall_ms.p95 exceeds the comparable "
357
+ f"baseline by more than {max_regression:.0%}. Unexplained regressions "
358
+ "must be attributed or accepted by updating the baseline deliberately."
359
+ ),
360
+ }
361
+
362
+
363
+ def _perf_comparability(
364
+ baseline: Mapping[str, object],
365
+ current: Mapping[str, object],
366
+ ) -> tuple[bool, list[str]]:
367
+ reasons: list[str] = []
368
+ for key in ("schema_version", "command", "mode"):
369
+ if baseline.get(key) != current.get(key):
370
+ reasons.append(f"{key} differs")
371
+ base_repo = baseline.get("repo") if isinstance(baseline.get("repo"), Mapping) else {}
372
+ cur_repo = current.get("repo") if isinstance(current.get("repo"), Mapping) else {}
373
+ if base_repo.get("name") != cur_repo.get("name"):
374
+ reasons.append("repo.name differs")
375
+ base_env = baseline.get("env") if isinstance(baseline.get("env"), Mapping) else {}
376
+ cur_env = current.get("env") if isinstance(current.get("env"), Mapping) else {}
377
+ if base_env.get("hostname_hash") != cur_env.get("hostname_hash"):
378
+ reasons.append("env.hostname_hash differs")
379
+ return not reasons, reasons
@@ -34,7 +34,7 @@ class Rule:
34
34
  """One audit rule, as a reader meets it."""
35
35
 
36
36
  id: str
37
- family: str # "TX" or "SEC" — the scope flag that selects it
37
+ family: str # "TX", "SEC", "DEAD" — the help grouping
38
38
  summary: str # one line, no wrapping games
39
39
 
40
40
  def to_dict(self) -> dict:
@@ -55,9 +55,16 @@ RULES: tuple[Rule, ...] = (
55
55
  Rule("SEC-005", "SEC", "CSRF disabled under session-bearing authentication"),
56
56
  Rule("SEC-006", "SEC", "Cookie created without the Secure attribute"),
57
57
  Rule("SEC-007", "SEC", "Credential stored in a deployment descriptor"),
58
+ Rule("SEC-008", "SEC", "SQL built by MyBatis string interpolation"),
59
+ Rule("DEAD-001", "DEAD", "Security control present but disabled"),
58
60
  )
59
61
 
60
- FAMILY_ORDER: tuple[str, ...] = ("TX", "SEC")
62
+ FAMILY_ORDER: tuple[str, ...] = ("TX", "SEC", "DEAD")
63
+ _FAMILY_LABELS = {
64
+ "TX": "TX anomalies",
65
+ "SEC": "security surface",
66
+ "DEAD": "disabled controls",
67
+ }
61
68
 
62
69
 
63
70
  def ids() -> tuple[str, ...]:
@@ -84,7 +91,7 @@ def range_label(family: str) -> str:
84
91
  def summary_line() -> str:
85
92
  """The command's one-liner: the families and how far each one reaches."""
86
93
  return " + ".join(
87
- f"{'TX anomalies' if fam == 'TX' else 'security surface'} "
94
+ f"{_FAMILY_LABELS.get(fam, fam)} "
88
95
  f"({range_label(fam)})"
89
96
  for fam in FAMILY_ORDER
90
97
  if for_family(fam)
@@ -121,6 +121,71 @@ _DESCRIPTOR_VALUE = re.compile(
121
121
  _DESCRIPTOR_NAMES = ("web.xml",)
122
122
  _DESCRIPTOR_SUFFIXES = ("-context.xml",)
123
123
 
124
+ # ── SEC-008: MyBatis string interpolation in mapper SQL ──────────────────────
125
+ # F-B. In MyBatis, `#{param}` binds a parameter and `${param}` splices text into
126
+ # the SQL. That distinction is local, syntactic and mapper-specific: a `${}` in
127
+ # another XML dialect is not this rule's evidence.
128
+ _MYBATIS_MAPPER = re.compile(r"<mapper\b", re.IGNORECASE)
129
+ _MYBATIS_SQL_TAG = re.compile(
130
+ r"<(?P<tag>select|insert|update|delete)\b[^>]*>(?P<body>.*?)</(?P=tag)>",
131
+ re.IGNORECASE | re.DOTALL,
132
+ )
133
+ _MYBATIS_INTERPOLATION = re.compile(r"\$\{\s*(?P<param>[^}]+?)\s*\}")
134
+
135
+ # ── DEAD-001: security control present but switched off in a comment ─────────
136
+ # F-A. E-2 taught the forward half: commented-out code must not be reported as
137
+ # live. The reverse half is product signal: a protection written and then
138
+ # commented out is not "absent"; somebody already knew it was needed.
139
+ _COMMENTED_JAVA_CONTROLS: tuple[tuple[re.Pattern, str, str], ...] = (
140
+ (
141
+ re.compile(r"\bsetSecure\s*\(\s*true\s*\)"),
142
+ "secure_cookie_attribute",
143
+ "A cookie Secure-attribute control is present only in a Java comment. "
144
+ "The running code can still create cookies without that transport guard.",
145
+ ),
146
+ (
147
+ re.compile(r"\bcsrfTokenRepository\s*\(|\bcsrf\s*\([^)]*\)\s*;"),
148
+ "csrf_control",
149
+ "A CSRF control is present only in a Java comment. Commented security "
150
+ "configuration is a disabled mitigation, not evidence the route is protected.",
151
+ ),
152
+ )
153
+ _COMMENTED_XML_CONTROLS: tuple[tuple[re.Pattern, str, str], ...] = (
154
+ (
155
+ re.compile(
156
+ r"<transport-guarantee>\s*CONFIDENTIAL\s*</transport-guarantee>",
157
+ re.IGNORECASE,
158
+ ),
159
+ "transport_guarantee_confidential",
160
+ "A CONFIDENTIAL transport guarantee is present only in an XML comment. "
161
+ "The descriptor documents a TLS-only control that the container will not enforce.",
162
+ ),
163
+ (
164
+ re.compile(r"<security-constraint\b", re.IGNORECASE),
165
+ "security_constraint",
166
+ "A servlet security constraint is present only in an XML comment. The "
167
+ "container ignores that block, so it cannot protect the declared URL patterns.",
168
+ ),
169
+ (
170
+ re.compile(r"<intercept-url\b[^>]*\baccess\s*=", re.IGNORECASE),
171
+ "spring_security_intercept_url",
172
+ "A Spring Security intercept-url rule is present only in an XML comment. "
173
+ "The filter chain will not enforce a commented authorization rule.",
174
+ ),
175
+ )
176
+ _COMMENTED_CONFIG_CONTROLS: tuple[tuple[re.Pattern, str, str], ...] = (
177
+ (
178
+ re.compile(
179
+ r"^[ \t]*#[ \t]*(?:server\.servlet\.session\.cookie\.secure|secure)"
180
+ r"\s*[:=]\s*true\s*$",
181
+ re.IGNORECASE,
182
+ ),
183
+ "secure_cookie_property",
184
+ "A cookie Secure-attribute property is present only in a comment. The "
185
+ "configuration records the intended control but does not apply it.",
186
+ ),
187
+ )
188
+
124
189
 
125
190
  @dataclass(frozen=True)
126
191
  class SecurityConfigObservation:
@@ -292,6 +357,96 @@ def _scan_descriptor(text: str, rel: str) -> "list[SecurityConfigObservation]":
292
357
  return out
293
358
 
294
359
 
360
+ def _scan_mybatis_mapper(text: str, rel: str) -> "list[SecurityConfigObservation]":
361
+ """SEC-008 — SQL text built with MyBatis `${}` interpolation."""
362
+ if not _MYBATIS_MAPPER.search(text):
363
+ return []
364
+ out: "list[SecurityConfigObservation]" = []
365
+ for statement in _MYBATIS_SQL_TAG.finditer(text):
366
+ body = statement.group("body")
367
+ for match in _MYBATIS_INTERPOLATION.finditer(body):
368
+ absolute = statement.start("body") + match.start()
369
+ param = " ".join(match.group("param").split())
370
+ out.append(SecurityConfigObservation(
371
+ rule="SEC-008",
372
+ file=rel,
373
+ line=_line_of(text, absolute),
374
+ symbol=rel,
375
+ evidence=f"${{{param}}}",
376
+ detail=(
377
+ "A MyBatis mapper builds SQL with `${...}` interpolation. MyBatis "
378
+ "`#{...}` binds a value as a parameter; `${...}` splices text into "
379
+ "the SQL statement, so request-controlled values can change the "
380
+ "query structure unless this value is strictly allowlisted."
381
+ ),
382
+ ))
383
+ return out
384
+
385
+
386
+ def _comment_excerpt(text: str) -> str:
387
+ """Compact comment text for evidence; strips comment delimiters, not meaning."""
388
+ cleaned = re.sub(r"^\s*(?://|/\*+|<!--|#)", "", text.strip())
389
+ cleaned = re.sub(r"(?:\*/|-->)\s*$", "", cleaned.strip())
390
+ cleaned = " ".join(cleaned.split())
391
+ return cleaned[:120] + ("..." if len(cleaned) > 120 else "")
392
+
393
+
394
+ def _scan_commented_controls(
395
+ source: str,
396
+ rel: str,
397
+ *,
398
+ xml: bool = False,
399
+ ) -> "list[SecurityConfigObservation]":
400
+ """DEAD-001 — security controls visible only inside comments."""
401
+ from sourcecode.source_text import commented_spans
402
+
403
+ rules = _COMMENTED_XML_CONTROLS if xml else _COMMENTED_JAVA_CONTROLS
404
+ out: "list[SecurityConfigObservation]" = []
405
+ seen: set[tuple[str, int]] = set()
406
+ for start, end in commented_spans(source, xml=xml):
407
+ text = source[start:end]
408
+ for pattern, symbol, detail in rules:
409
+ if not pattern.search(text):
410
+ continue
411
+ line = _line_of(source, start)
412
+ key = (symbol, line)
413
+ if key in seen:
414
+ continue
415
+ seen.add(key)
416
+ out.append(SecurityConfigObservation(
417
+ rule="DEAD-001",
418
+ file=rel,
419
+ line=line,
420
+ symbol=symbol,
421
+ evidence=_comment_excerpt(text),
422
+ detail=detail,
423
+ ))
424
+ break
425
+ return out
426
+
427
+
428
+ def _scan_commented_config_controls(
429
+ source: str,
430
+ rel: str,
431
+ ) -> "list[SecurityConfigObservation]":
432
+ """DEAD-001 for whole-line commented properties/YAML controls."""
433
+ out: "list[SecurityConfigObservation]" = []
434
+ for number, line in enumerate(source.splitlines(), start=1):
435
+ for pattern, symbol, detail in _COMMENTED_CONFIG_CONTROLS:
436
+ if not pattern.match(line):
437
+ continue
438
+ out.append(SecurityConfigObservation(
439
+ rule="DEAD-001",
440
+ file=rel,
441
+ line=number,
442
+ symbol=symbol,
443
+ evidence=_comment_excerpt(line),
444
+ detail=detail,
445
+ ))
446
+ break
447
+ return out
448
+
449
+
295
450
  def _shared_across_environments(
296
451
  observations: "list[SecurityConfigObservation]",
297
452
  ) -> "list[SecurityConfigObservation]":
@@ -368,6 +523,7 @@ def scan_security_configuration(
368
523
  source = (root / rel).read_text(encoding="utf-8", errors="replace")
369
524
  except OSError:
370
525
  continue
526
+ out.extend(_scan_commented_controls(source, rel, xml=False))
371
527
  out.extend(_scan_java(blank_java_comments(source), rel))
372
528
 
373
529
  if config_files is None:
@@ -382,6 +538,7 @@ def scan_security_configuration(
382
538
  text = (root / rel).read_text(encoding="utf-8", errors="replace")
383
539
  except OSError:
384
540
  continue
541
+ out.extend(_scan_commented_config_controls(text, rel))
385
542
  out.extend(_scan_config(blank_hash_comments(text), rel))
386
543
 
387
544
  # CL-10 — the descriptors. Discovered the same way `environment_resolution`
@@ -401,8 +558,23 @@ def scan_security_configuration(
401
558
  text = (root / rel).read_text(encoding="utf-8", errors="replace")
402
559
  except OSError:
403
560
  continue
561
+ out.extend(_scan_commented_controls(text, rel, xml=True))
404
562
  out.extend(_scan_descriptor(blank_xml_comments(text), rel))
405
563
 
564
+ mappers: "list[str]" = []
565
+ try:
566
+ for path in root.rglob("*.xml"):
567
+ if path.name.endswith("Mapper.xml"):
568
+ mappers.append(path.relative_to(root).as_posix())
569
+ except OSError:
570
+ pass
571
+ for rel in sorted(set(mappers) - set(descriptors)):
572
+ try:
573
+ text = (root / rel).read_text(encoding="utf-8", errors="replace")
574
+ except OSError:
575
+ continue
576
+ out.extend(_scan_mybatis_mapper(blank_xml_comments(text), rel))
577
+
406
578
  return sorted(
407
579
  _shared_across_environments(out), key=lambda o: (o.rule, o.file, o.line)
408
580
  )
sourcecode/source_text.py CHANGED
@@ -91,9 +91,8 @@ def commented_spans(source: str, *, xml: bool) -> "list[tuple[int, int]]":
91
91
  delete. A control that is present but commented out is not noise: somebody
92
92
  decided it was needed and then turned it off, which is the most actionable
93
93
  thing a reader can be told about a security surface. E-2's fix removes those
94
- matches from the findings; this is what a future rule reports them *as*
95
- (DEAD-001, queued as F-A). Nothing consumes it yet — it exists so the two
96
- halves cannot drift apart when it does.
94
+ matches from the live findings; `security_config_scan` consumes this inverse
95
+ view as DEAD-001, so the two halves cannot drift apart.
97
96
  """
98
97
  pattern = _XML_COMMENT if xml else _JAVA_TOKENS
99
98
  spans: "list[tuple[int, int]]" = []
@@ -841,6 +841,7 @@ class ImpactOrchestrator:
841
841
  depth: int = _BFS_DEFAULT_DEPTH,
842
842
  root: Optional[Path] = None,
843
843
  prebuilt_findings: Optional[list[SpringFinding]] = None,
844
+ include_findings: bool = False,
844
845
  ) -> ImpactChainResult:
845
846
  """Execute impact-chain query for symbol.
846
847
 
@@ -851,7 +852,10 @@ class ImpactOrchestrator:
851
852
  depth: BFS depth for indirect caller traversal.
852
853
  root: Repo root path (for TX-005 source reading).
853
854
  prebuilt_findings: Pre-run audit findings (avoids double audit).
854
- If None, runs TX + SEC audit internally.
855
+ When supplied, impact findings are computed from it.
856
+ include_findings: Run the repository-wide TX + SEC audit when no
857
+ prebuilt findings are supplied. Defaults false so a
858
+ chain query cost follows the chain, not the full repo.
855
859
  """
856
860
  t0 = time.monotonic()
857
861
  depth = max(1, min(depth, _BFS_HARD_LIMIT))
@@ -1067,20 +1071,28 @@ class ImpactOrchestrator:
1067
1071
  pass
1068
1072
 
1069
1073
  # ── 5. TX + SEC audit findings, filtered to call chain ────────────
1070
- # The security audit already derives the posture projection; take it from
1071
- # there rather than deriving the same answer a second time (ADR-0008 R1).
1074
+ # C3-42-b: findings are now an explicit enrichment. A chain query whose
1075
+ # caller only asked "what reaches this symbol?" must not pay for a full
1076
+ # repository TX + security audit and then filter almost all of it away.
1077
+ # If a caller needs findings, it either supplies a prebuilt audit or opts in.
1072
1078
  posture_dict: dict = {}
1073
- # C3-42. This is a **repository-wide** audit, run inside what the payload
1074
- # then publishes as `query_time_ms` — so a reader comparing that figure
1075
- # against `model_build_time_ms` is comparing a model build against a model
1076
- # build plus a full audit of every file. The share is measured and named
1077
- # rather than hidden: the cost is real, and which part of it is the walk is
1078
- # the first thing anyone reporting a slow query needs to know.
1079
+ findings_status = "not_computed"
1079
1080
  _audit_t0 = time.monotonic()
1080
1081
  if prebuilt_findings is not None:
1081
1082
  all_findings = prebuilt_findings
1082
- else:
1083
+ findings_status = "prebuilt"
1084
+ elif include_findings:
1085
+ # The security audit already derives the posture projection; take it
1086
+ # from there rather than deriving the same answer a second time
1087
+ # (ADR-0008 R1).
1088
+ #
1089
+ # C3-42. This is a **repository-wide** audit, run inside what the
1090
+ # payload then publishes as `query_time_ms`. It is now opt-in and its
1091
+ # share remains measured so a slow enriched query names the paid term.
1083
1092
  all_findings, posture_dict = _run_audit_for_chain(cir, model, root)
1093
+ findings_status = "computed"
1094
+ else:
1095
+ all_findings = []
1084
1096
  audit_ms = round((time.monotonic() - _audit_t0) * 1000, 2)
1085
1097
 
1086
1098
  impact_findings_raw = _filter_findings(
@@ -1448,22 +1460,28 @@ class ImpactOrchestrator:
1448
1460
  "caller_metric_note": CALLER_METRIC_RECONCILIATION,
1449
1461
  "endpoints_total": len(endpoints_affected),
1450
1462
  "findings_in_chain": len(impact_findings),
1463
+ "findings_status": findings_status,
1464
+ "findings_status_note": (
1465
+ "impact_findings were not computed; by default impact-chain skips "
1466
+ "the repository-wide TX + security audit so query cost correlates "
1467
+ "with the queried chain. Pass --with-findings or prebuilt findings "
1468
+ "to compute this enrichment."
1469
+ if findings_status == "not_computed" else
1470
+ "impact_findings were filtered from prebuilt audit findings."
1471
+ if findings_status == "prebuilt" else
1472
+ "impact_findings were computed by running the repository-wide TX + "
1473
+ "security audit and filtering it to this chain."
1474
+ ),
1451
1475
  "risk_score": risk_score,
1452
1476
  "model_build_time_ms": model.build_time_ms,
1453
1477
  "query_time_ms": elapsed_ms,
1454
- # What that number is made of. A field report of
1455
- # `query_time_ms: 1 187 063` beside `model_build_time_ms: 516`
1456
- # reads as a 2 300× graph walk; the walk is the difference between
1457
- # these two figures, and on every repository measured here the
1458
- # repository-wide audit is the larger part (C3-42).
1459
1478
  "audit_time_ms": audit_ms,
1460
1479
  "query_time_basis": (
1461
- "wall time of the whole call, which includes a repository-wide "
1462
- "TX + security audit (`audit_time_ms`) whose findings are then "
1463
- "filtered to this chain. The graph walk itself is "
1480
+ "wall time of the whole call. The repository-wide TX + security "
1481
+ "audit is skipped by default; when findings_status is `computed`, "
1482
+ "`audit_time_ms` is the opt-in audit share and the graph walk is "
1464
1483
  "`query_time_ms - audit_time_ms`. `model_build_time_ms` covers "
1465
- "the semantic model only, so the two are not comparable as they "
1466
- "stand"
1484
+ "the semantic model only."
1467
1485
  ),
1468
1486
  "blind_spots": blind_spots,
1469
1487
  "reconciliation": reconciliation_findings,
@@ -1491,6 +1509,11 @@ def _run_audit_for_chain(
1491
1509
  from sourcecode.spring_security_audit import run_security_audit
1492
1510
  from sourcecode.spring_tx_analyzer import run_tx_audit
1493
1511
 
1512
+ root_key = str(Path(root).resolve()) if root is not None else ""
1513
+ cache = getattr(cir, _AUDIT_CACHE_ATTR, None)
1514
+ if isinstance(cache, dict) and root_key in cache:
1515
+ return cache[root_key]
1516
+
1494
1517
  findings: list[SpringFinding] = []
1495
1518
  posture: dict = {}
1496
1519
  try:
@@ -1504,11 +1527,20 @@ def _run_audit_for_chain(
1504
1527
  posture = getattr(sec_result, "security_posture", None) or {}
1505
1528
  except Exception:
1506
1529
  pass
1507
- return findings, posture
1530
+ answer = (findings, posture)
1531
+ try:
1532
+ if not isinstance(cache, dict):
1533
+ cache = {}
1534
+ setattr(cir, _AUDIT_CACHE_ATTR, cache)
1535
+ cache[root_key] = answer
1536
+ except Exception:
1537
+ pass
1538
+ return answer
1508
1539
 
1509
1540
 
1510
1541
  # Per-CIR memo of the endpoint security authority's answer (one derivation per repo).
1511
1542
  _SURFACE_CACHE_ATTR = "_impact_endpoint_security_surface"
1543
+ _AUDIT_CACHE_ATTR = "_impact_chain_audit_findings"
1512
1544
 
1513
1545
 
1514
1546
  def _endpoint_security_authority(
@@ -1562,6 +1594,7 @@ def run_impact_chain(
1562
1594
  root: Optional[Path] = None,
1563
1595
  model: Optional[SpringSemanticModel] = None,
1564
1596
  prebuilt_findings: Optional[list[SpringFinding]] = None,
1597
+ include_findings: bool = False,
1565
1598
  ) -> ImpactChainResult:
1566
1599
  """Run impact-chain query from a CIR.
1567
1600
 
@@ -1573,6 +1606,8 @@ def run_impact_chain(
1573
1606
  model: Pre-built SpringSemanticModel. Built internally if None.
1574
1607
  prebuilt_findings: Pre-run SpringFinding list. Avoids double audit if
1575
1608
  impact-chain runs after spring-audit in the same session.
1609
+ include_findings: Run TX + security audit internally when no prebuilt
1610
+ findings are supplied. Defaults false for query cost.
1576
1611
 
1577
1612
  Returns ImpactChainResult — always JSON-serializable, never raises.
1578
1613
  """
@@ -1584,6 +1619,7 @@ def run_impact_chain(
1584
1619
  cir, model, symbol,
1585
1620
  depth=depth, root=root,
1586
1621
  prebuilt_findings=prebuilt_findings,
1622
+ include_findings=include_findings,
1587
1623
  )
1588
1624
  except Exception as exc:
1589
1625
  return ImpactChainResult(
@@ -8,6 +8,8 @@ Patterns implemented (Phase 3):
8
8
  SEC-005 CSRF disabled under session-bearing authentication (CL-9)
9
9
  SEC-006 Cookie created without the Secure attribute (CL-9)
10
10
  SEC-007 Credential stored in a deployment descriptor (CL-10)
11
+ SEC-008 SQL built by MyBatis string interpolation (F-B)
12
+ DEAD-001 Security control present but disabled (F-A)
11
13
 
12
14
  All patterns are deterministic and never raise.
13
15
  """
@@ -387,8 +389,10 @@ def _controller_source_file(cir: "CanonicalRepositoryIR", controller_fqn: str) -
387
389
 
388
390
  class _SEC00XConfigurationWeaknesses:
389
391
  """The textbook rules (CL-9): SEC-004 password storage, SEC-005 CSRF under
390
- session authentication, SEC-006 a cookie without the Secure attribute — and
391
- SEC-007 (CL-10), a credential stored in a deployment descriptor.
392
+ session authentication, SEC-006 a cookie without the Secure attribute,
393
+ SEC-007 (CL-10), a credential stored in a deployment descriptor,
394
+ SEC-008 (F-B), SQL built by mapper interpolation, and DEAD-001 (F-A), a
395
+ security control present but disabled in a comment.
392
396
 
393
397
  One pattern for three rules because they share a scan of the same files, and
394
398
  because the alternative — three passes over the source per audit — is how a
@@ -406,12 +410,16 @@ class _SEC00XConfigurationWeaknesses:
406
410
  "SEC-004": "high", "SEC-005": "medium", "SEC-006": "medium",
407
411
  # A credential every reader of the repository already holds.
408
412
  "SEC-007": "high",
413
+ "SEC-008": "high",
414
+ "DEAD-001": "high",
409
415
  }
410
416
  _TITLE = {
411
417
  "SEC-004": "Password stored under a fast unsalted digest",
412
418
  "SEC-005": "CSRF protection disabled under session-bearing authentication",
413
419
  "SEC-006": "Cookie created without the Secure attribute",
414
420
  "SEC-007": "Credential stored in a deployment descriptor",
421
+ "SEC-008": "SQL built by MyBatis string interpolation",
422
+ "DEAD-001": "Security control present but disabled",
415
423
  }
416
424
  #: A pattern that emits more than one rule id declares them all, so a catalogue
417
425
  #: reader is never left with the group's identity standing for the group.
@@ -439,6 +447,16 @@ class _SEC00XConfigurationWeaknesses:
439
447
  "retire it — and give each environment its own, so the least protected "
440
448
  "deployment stops deciding the strength of the others."
441
449
  ),
450
+ "SEC-008": (
451
+ "Replace `${...}` with `#{...}` for values. If the interpolated token is "
452
+ "an identifier such as an ORDER BY column, map it through a closed "
453
+ "allowlist before it reaches the mapper."
454
+ ),
455
+ "DEAD-001": (
456
+ "Decide explicitly: either restore the commented control and verify it in "
457
+ "the active profile, or delete the dead block with a note explaining the "
458
+ "replacement control. Do not leave security posture encoded as comments."
459
+ ),
442
460
  }
443
461
 
444
462
  def analyze(
@@ -232,7 +232,7 @@ def _audit_findings(cir: Any, root: "Optional[Path]" = None) -> list[Any]:
232
232
  ten contracts over the same repository run the audit once.
233
233
 
234
234
  **`root` is not optional in practice, and C1-24 is why.** Half of the shipped
235
- security rules — SEC-004…SEC-007 — are produced by `security_config_scan`,
235
+ security rules — SEC-004…SEC-008 and DEAD-001 — are produced by `security_config_scan`,
236
236
  which reads source and descriptor *text*; `SecurityScanner.analyze` returns
237
237
  an empty list when it has no root to read from. This function used to call
238
238
  `run_security_audit(cir, model=model)` with no root, so it saw the CIR
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 4.6.0
3
+ Version: 4.7.0
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
42
42
 
43
43
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
44
44
 
45
- ![Version](https://img.shields.io/badge/version-4.6.0-blue)
45
+ ![Version](https://img.shields.io/badge/version-4.7.0-blue)
46
46
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
47
47
 
48
48
  > **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
@@ -97,7 +97,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
97
97
  # pip / pipx
98
98
  pipx install sourcecode # or: pip install sourcecode
99
99
 
100
- ask version # ask 4.6.0 — and, on a build that has aged,
100
+ ask version # ask 4.7.0 — and, on a build that has aged,
101
101
  # how many releases have probably shipped since
102
102
  ```
103
103
 
@@ -211,7 +211,7 @@ Bounded, noise-free repo context designed to drop straight into an agent's conte
211
211
 
212
212
  ### 2 · Impact Analysis
213
213
  Blast radius from a class or interface — reverse dependencies, through Spring DI, to the HTTP endpoints a change reaches.
214
- `ask impact` · `ask impact-chain` (TX/SEC-enriched) · `ask pr-impact`
214
+ `ask impact` · `ask impact-chain` (blast radius; `--with-findings` for TX/SEC audit findings) · `ask pr-impact`
215
215
  → [reference](docs/USER_GUIDE.md#core-commands)
216
216
 
217
217
  ### 3 · Architecture Intelligence
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=4dXthmwMtG3-Py64d-gE1ZDK9uCes0_GCsrNgSGBt3I,308
1
+ sourcecode/__init__.py,sha256=gF1pcXBiZrYfXSksXAH0CHm4BhYKU2-GNCDgZSMnIPM,308
2
2
  sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
3
3
  sourcecode/archetype.py,sha256=CZvRLpkHot_D8D3JFQVorr-EHDJyh0BbS7RnSTqBigM,40499
4
4
  sourcecode/architectural_baseline.py,sha256=agDSwGEakdkgLLh5xnd3hiD_TC6pRujWt4klnuYF1wo,18689
@@ -17,7 +17,7 @@ sourcecode/chain_rules.py,sha256=Bi6UHfgd-GxWswmnHRcPz5jdbAuqka3Zkz_P-MTvqhw,127
17
17
  sourcecode/change_plan.py,sha256=kFjjp16XYbupgkv1CPkfqo39_SiZPMRQ8OOfC-Vy9eg,7929
18
18
  sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
19
19
  sourcecode/classifier.py,sha256=JBzPwSSrDG-tUHAbcKB678HRbjLpD-ohzbzzO62mgpo,20114
20
- sourcecode/cli.py,sha256=iTTJJyP4yugJ37xOA-G7W0WslC31d7fL4If-Gx4mIpM,492713
20
+ sourcecode/cli.py,sha256=BY4rr4FXnTwsNOygW59U1GipD_NyNy5icdfcn24ewKQ,493157
21
21
  sourcecode/client_calls.py,sha256=daRTgbXNUOfkzGXJpVb6A737R_Thhka8vw1_YgxCaLg,13548
22
22
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
23
23
  sourcecode/compare.py,sha256=xq3zsqAOAw4AWkoD9khb9xDP_O3KvwqO9k-pf8sbi3g,10951
@@ -65,7 +65,7 @@ sourcecode/jdk_exports.py,sha256=fCrlwNAXUT9gge_joq6kMnY3zJxYB2pxqy-0w3o3MJI,874
65
65
  sourcecode/license.py,sha256=vvgE1vBUWh8ozWI7USYuteh6noxntnjuzDQIPF-l4rA,27795
66
66
  sourcecode/mcp_nudge.py,sha256=lKemOqK_wny2u7Ymcr2Idi5Kx8pXY02jCi-_nJYLGMg,2992
67
67
  sourcecode/metrics_analyzer.py,sha256=gLoRWKygF18jLgwsqmGXSWopw-f5iO1VbM7Jjdif6ag,22809
68
- sourcecode/migrate_check.py,sha256=jMv1GMhMthpHCnoEtkKWHiIC2Not2kdbU4UaA9sWX7I,134643
68
+ sourcecode/migrate_check.py,sha256=ktNAI24Og-8IJo5lzveuTxmjasK2CJpUyv5LLEUVxK8,135908
69
69
  sourcecode/migration_blast.py,sha256=OPryWkM6-PbvcG3eJFdcWUnlpXBje41nu2BkrZnE2hg,9359
70
70
  sourcecode/non_coverage.py,sha256=lQZCdvHz4c_Gr0PIugkybOvSVwiaWvpCesq2p_9HtNk,11823
71
71
  sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
@@ -74,7 +74,7 @@ sourcecode/output_budget.py,sha256=__DQrIg7MGsYrd0_S3lyd3AG0a0jWNGr55v5Gg9kK0U,1
74
74
  sourcecode/parse_cache.py,sha256=SnHOhNTvAqHm_PXImPVBuj7NEoOQRn8dfIZHEqOFENs,7565
75
75
  sourcecode/path_admission.py,sha256=OGNSoluhVh8YYOBZBnACUrkmH4Tp_yxjGLifkcsxQpo,5838
76
76
  sourcecode/path_filters.py,sha256=LmTYq735orssKTIuxTX1mE1FHAXF7dVEJEse35xE1RA,12371
77
- sourcecode/perf.py,sha256=GAcEoouPIlPMCQIcHNToxK6K3WdIR-lj9aFg4prOYJI,9743
77
+ sourcecode/perf.py,sha256=2YlJTXjyur1S7hecOMz_joUHAXE5bkcZ85a_LXBEcHQ,13931
78
78
  sourcecode/pipe_contract.py,sha256=PML0Er5d8uDyec4OrdUXuyqHbGPUYndjeaKUjkFz2u4,8369
79
79
  sourcecode/posture.py,sha256=nmvlJOBwnwV5aAOmO7GMfU35hNgArCQFSuESsYzcS8s,76101
80
80
  sourcecode/pr_comment_renderer.py,sha256=239PmJdf95av_ZW236C7_tvq_ahECeuF9AZxrheJ4OQ,15573
@@ -94,13 +94,13 @@ sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ
94
94
  sourcecode/repository_ir.py,sha256=yrQd8yB9NO0M2SLkI0bQ5ty1RjTldQOhVbr3IoNkXJk,357437
95
95
  sourcecode/ris.py,sha256=SjjDNMcc9Zr9us9G8ikBVkoPXKK3BpM-dBodSGMh_g4,24783
96
96
  sourcecode/risk.py,sha256=xYzXMINAkA1lGdlTiBpK9ODhor3c_UrNtL33P88mGJs,39850
97
- sourcecode/rule_catalog.py,sha256=buU6qZI1j1EjqAWOIzeDhZ9kO_6Tp_Ma-dmexNWkMe0,4324
97
+ sourcecode/rule_catalog.py,sha256=SEFLMOdngn2Oo4giWb9wX-k_5ZrH5fFdEbhjEAlotYs,4555
98
98
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
99
99
  sourcecode/sarif.py,sha256=_3ggbtV0O2iK5zeUiJSS36KxpPRVAtGy0hzXAFBmww8,25590
100
100
  sourcecode/scanner.py,sha256=z3CV0rcGunu0Y8mpNgp07wI7nxT0pxw1BkXRRtI0Rpo,9609
101
101
  sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
102
102
  sourcecode/security_config.py,sha256=KblMEoRiEjrIE68YsPaUAFebxFp8UM7MS7lAk5CGD8U,3531
103
- sourcecode/security_config_scan.py,sha256=9MC3N9G3627jOsP9SI2et_TQEUbdlmxuHq1Ycm-msBg,16920
103
+ sourcecode/security_config_scan.py,sha256=FFI16W982cDokc5nf2zwuB8DcilEUR_rQa6eIu7gg4A,23949
104
104
  sourcecode/security_posture.py,sha256=CjJ2Qm87HhKXepnaWx1ncJLW-SxI18udCmUDIkCyAsY,50270
105
105
  sourcecode/semantic_analyzer.py,sha256=bpgdC6m0_ftVtRf3rSdwhbhWjnZnGxRXaZVcfe4BbcQ,95414
106
106
  sourcecode/semantic_impact_engine.py,sha256=t09IirGC3JjQDy33JZd1_WKzQVKXkoNl3-XEUr5kjis,20563
@@ -108,14 +108,14 @@ sourcecode/semantic_integration_engine.py,sha256=7a0WqAInOv39f0Yr_94TYo_JP_8QpeI
108
108
  sourcecode/semantic_services.py,sha256=nbUuPv-F01USTt_9CHT8iy_ucCIw3fz4W3Aquea_pd4,10782
109
109
  sourcecode/serializer.py,sha256=T1-Ybi6aPczxiz11N_nfuZ_jUMQVBMExeWuYHCUjKPc,139222
110
110
  sourcecode/servlet_surface.py,sha256=kwIBS6aT-DJHu8mqzvhlYrhaqAq_005SDAeVZ7yx8Y0,8165
111
- sourcecode/source_text.py,sha256=Q5v5kAq2QFZ-HcaK5UXp_xZTbiLiFW5p09v7r_hQHnU,4878
111
+ sourcecode/source_text.py,sha256=5ZOqD04Vpdc6Jl6tUlkpeXtM3wXYC297hczZ71iksVk,4819
112
112
  sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
113
113
  sourcecode/spring_findings.py,sha256=qgWz4LLL5TFe3eG-mTttlrDTx-6ps3EOuLA-S2HvQCM,16945
114
- sourcecode/spring_impact.py,sha256=SW7gyjnzf1f9dLH296s1-EmxgZvitl1MjHAnTQlUfmQ,78145
114
+ sourcecode/spring_impact.py,sha256=MFx0I6ElRTpjWPGgX5C8XY1gZmR3-bk6Ky9FJHpKbAk,79822
115
115
  sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16594
116
116
  sourcecode/spring_profiles.py,sha256=-kwrCK0O-MRjrCp6SA1d31t--o4Tg87iqPVrTwxoIUs,19605
117
117
  sourcecode/spring_properties.py,sha256=kPTk5qAJxdbHc1hclhHAP0O-hCIzBn8PTh3lzl-I4a4,8400
118
- sourcecode/spring_security_audit.py,sha256=KIjshNuS-cQSUBstqCuvDnp5i1J4_qAwXYKJArL2QpI,28720
118
+ sourcecode/spring_security_audit.py,sha256=BHKgEQCKlAgwzY3xnRm1HYYfVpwY6sAVGp1tRnkFQUg,29687
119
119
  sourcecode/spring_semantic.py,sha256=UrmLg_4gmBVmMqvSnUMgSReVEERVGv3X7LYDBEs6k_o,16598
120
120
  sourcecode/spring_tx_analyzer.py,sha256=_wqjRktqdPS6PiXXxtTkCD1t6BaE2uhqx6ioYY-ULAw,44153
121
121
  sourcecode/summarizer.py,sha256=0aD4x3vgPngqBCEBKGuES1J2Vk5f7mqCm_ZWErwm3js,27025
@@ -129,7 +129,7 @@ sourcecode/validation_inference.py,sha256=-oWJqE6PqkqcZbCFDJeWCQHgI9Dbuv5ggJWD3L
129
129
  sourcecode/validation_surface.py,sha256=jYL-hkDjaaRKkAt7ZUcbxm3Dydl8o6KVRhTkuPXKPVc,31460
130
130
  sourcecode/verify_edit.py,sha256=Toqx4z47gLUmcj5F673eTFq1dE_6e4T3mXVesjkPx6g,35955
131
131
  sourcecode/verify_repo.py,sha256=ltSP1nFc726k2aTHCu5dfhxQgxQwz3nrUNDMDN-aDa8,12158
132
- sourcecode/verify_rules.py,sha256=kqo-tuqOfxSti3jIVS-bJtbSV5b1KxdigOR0j5Wr_XM,19388
132
+ sourcecode/verify_rules.py,sha256=YxG5JkDXBVpwbU1_tIKVtU8_eaF3zjpsq05tjpN4Z7Y,19401
133
133
  sourcecode/version_check.py,sha256=CHp6ZxTIfo8kyHPCBgJA1uFC0xQCoXMuuOfrW8QTL8o,4942
134
134
  sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
135
135
  sourcecode/detectors/__init__.py,sha256=A0AACJFF6HWf_RgatNtWu3PUzstcKtIGM9f1PoFcJug,1987
@@ -141,7 +141,7 @@ sourcecode/detectors/elixir.py,sha256=jCpvt5Yi6jvplc80ovRtWh17q-11ZGo9qX7o8b57TJ
141
141
  sourcecode/detectors/go.py,sha256=2r66uRQfeTWsqxr4HDhT6vExZErby0t46QXLHVBRv9w,2782
142
142
  sourcecode/detectors/heuristic.py,sha256=7cRxrip4yIaggYzZJB6ef8yHKh-gHgiH_pXMFcjlyFU,3723
143
143
  sourcecode/detectors/hybrid.py,sha256=IGFRUVsAZ1ooRlFdznCeJAV6vy1yVDx-VyghvLtddXc,9101
144
- sourcecode/detectors/java.py,sha256=PmCbiUDDnnJ7jjv6BuQQBDnyzeEBXrA_YPXrdnZW7vU,44471
144
+ sourcecode/detectors/java.py,sha256=Xr9n9GXBAtFB9EE9SMWG7Ix1qi6sweTGpDfLbLgAr4k,44751
145
145
  sourcecode/detectors/jvm_ext.py,sha256=EgHJ5W8EE-ZTN9V607mVzohyKgZE8Mc2jCi-DF8RAZU,2616
146
146
  sourcecode/detectors/nodejs.py,sha256=Hg3Gmr7yIMJFiLoDwOTk2wtu00wxIs6kZf-oQujTFUA,13187
147
147
  sourcecode/detectors/parsers.py,sha256=ug9K31tyHqinmv0HkIVQVjdTZpBv67FYKAEf52YXOSM,3178
@@ -157,9 +157,9 @@ sourcecode/facts/__init__.py,sha256=JpMjDb7BSaM1a9UQU91YYKr2HJ5tOOuNRCIeQwN8_2o,
157
157
  sourcecode/facts/registry.json,sha256=_N3iy2pfYfljtWtcwjKFZwYPNuOPJE8Evxy5WpUDG1c,14084
158
158
  sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
159
159
  sourcecode/mcp/orchestrator.py,sha256=wTZ47WgFYpbDGlKMofVSdCJU5TAatD6YVGlut2FlT34,36737
160
- sourcecode/mcp/registry.py,sha256=Cf_ftaby5XfGZU4zq0oqBzyvSher65vDAKIRXz5bIWQ,68582
160
+ sourcecode/mcp/registry.py,sha256=p9GJtQLeiPaUWlSnlL0o7wo4A0Jjw9-LXVRWqTkJXj4,69061
161
161
  sourcecode/mcp/runner.py,sha256=RGimCwhLyHxjCvH7q72y12HWEnATXZLC-NBWhkzlu0I,2852
162
- sourcecode/mcp/server.py,sha256=l1-eU9C0Sljz38llzgPmPaF1Q-A1CAFF_WN1M6DsL2g,65705
162
+ sourcecode/mcp/server.py,sha256=0lN5G4MkyRm57pGjmmiU1zKVHUFK-OLnbTNxDf1HrkA,65958
163
163
  sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
164
164
  sourcecode/mcp/onboarding/applier.py,sha256=Sx9vHTaXr_M1Bs4uMjO7qa_a2_p4J9oz93oJsKQ7ds4,3298
165
165
  sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
@@ -190,8 +190,8 @@ sourcecode/telemetry/consent.py,sha256=pQdl-QeLl6Gcibn0eWHSKZrm-HYSsjpVqOnjrgFp8
190
190
  sourcecode/telemetry/events.py,sha256=4_yeO58U-Cwc1Qb27VB0_EjhmroY0k91n3_VGxeALB8,2776
191
191
  sourcecode/telemetry/filters.py,sha256=RzxauTz8HliO4BllQnXEXc7zTeqdCZi5MgqGEDuW7OQ,6570
192
192
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
193
- sourcecode-4.6.0.dist-info/METADATA,sha256=6XkofNasppQ6luCtFPesmVOf4wVVd-JWSACEyVw85r4,34161
194
- sourcecode-4.6.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
195
- sourcecode-4.6.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
196
- sourcecode-4.6.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
197
- sourcecode-4.6.0.dist-info/RECORD,,
193
+ sourcecode-4.7.0.dist-info/METADATA,sha256=LuAnOSnbticxEPgM0-gzikRwKa5oeorlKWtzsTJk26U,34203
194
+ sourcecode-4.7.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
195
+ sourcecode-4.7.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
196
+ sourcecode-4.7.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
197
+ sourcecode-4.7.0.dist-info/RECORD,,