sourcecode 5.9.14__py3-none-any.whl → 5.9.16__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__ = "5.9.14"
7
+ __version__ = "5.9.16"
@@ -1,5 +1,5 @@
1
1
  """Generated at build time by hatch_build.py. Do not edit (AUD-592-D04)."""
2
2
 
3
- BUILD_COMMIT = '653229e2baf68f37744139ef2573467a3d4f2f09'
3
+ BUILD_COMMIT = '5c373abf12626edd50d0a5a4018bd10421727a32'
4
4
  CLOSURE_PROVENANCE = {'AUD-611-B05': {'commit': '', 'status': 'uncited'}, 'AUD-611-B06': {'commit': '', 'status': 'uncited'}, 'AUD-611-B07': {'commit': '', 'status': 'uncited'}, 'AUD-611-B08': {'commit': '', 'status': 'uncited'}, 'AUD-611-B09': {'commit': '', 'status': 'uncited'}, 'AUD-611-B10': {'commit': '', 'status': 'uncited'}, 'AUD-611-B11': {'commit': '', 'status': 'uncited'}, 'AUD-612-07': {'commit': '', 'status': 'uncited'}, 'AUD-612-09': {'commit': '', 'status': 'uncited'}, 'AUD-612-14': {'commit': '', 'status': 'uncited'}, 'AUD-612-B03': {'commit': '', 'status': 'uncited'}, 'AUD-612-B07': {'commit': '', 'status': 'uncited'}, 'AUD-612-B13': {'commit': '', 'status': 'uncited'}, 'AUD-612-E': {'commit': '', 'status': 'uncited'}, 'AUD-612-G': {'commit': '', 'status': 'uncited'}, 'AUD-612-I': {'commit': '', 'status': 'uncited'}, 'AUD-612-K': {'commit': '', 'status': 'uncited'}, 'AUD-612-M': {'commit': '', 'status': 'uncited'}, 'AUD-612-R': {'commit': '', 'status': 'uncited'}, 'AUD-613-ROOT': {'commit': '', 'status': 'uncited'}, 'AUD-614-07': {'commit': '', 'status': 'uncited'}, 'AUD-614-E': {'commit': '', 'status': 'uncited'}, 'AUD-614-F': {'commit': '', 'status': 'uncited'}, 'AUD-614-G': {'commit': '', 'status': 'uncited'}, 'AUD-614-I': {'commit': '', 'status': 'uncited'}, 'AUD-614-INTENT-TIE': {'commit': '', 'status': 'uncited'}, 'AUD-614-JUNIT4': {'commit': '', 'status': 'uncited'}, 'AUD-614-RISK-L0': {'commit': '', 'status': 'uncited'}, 'AUD-614-SELFTEST-TIMEOUT': {'commit': '', 'status': 'uncited'}, 'AUD-615-07': {'commit': '', 'status': 'uncited'}, 'AUD-615-COLD-COMPACT': {'commit': '', 'status': 'uncited'}, 'AUD-615-DRIFT': {'commit': '', 'status': 'uncited'}, 'AUD-615-E': {'commit': '', 'status': 'uncited'}, 'AUD-615-F': {'commit': '', 'status': 'uncited'}, 'AUD-615-INTENT-TIE': {'commit': '', 'status': 'uncited'}, 'AUD-615-JUNIT4': {'commit': '', 'status': 'uncited'}, 'AUD-615-RISK-L0': {'commit': '', 'status': 'uncited'}, 'AUD-615-SELFTEST-TIMEOUT': {'commit': '', 'status': 'uncited'}, 'AUD-616-E-PROV': {'commit': '', 'status': 'uncited'}, 'AUD-616-F, -RISK-L0, -SELFTEST-TIMEOUT, -INTENT-TIE, -DRIFT, -COLD-COMPACT, -E, -07': {'commit': '', 'status': 'uncited'}, 'AUD-616-INTENT-TIE-COV': {'commit': '', 'status': 'uncited'}, 'AUD-616-JUNIT4-SELF': {'commit': '', 'status': 'uncited'}, 'AUD-616-KOTLIN': {'commit': '', 'status': 'uncited'}, 'AUD-616-O': {'commit': '', 'status': 'uncited'}, 'AUD-616-P': {'commit': '', 'status': 'uncited'}, 'Window': {'commit': '', 'status': 'uncited'}, '`ASK-FIELD-01`': {'commit': '829f9fa', 'status': 'present'}, '`ASK-FIELD-02`': {'commit': '6a659fa', 'status': 'present'}, '`ASK-FIELD-03`': {'commit': '55b0d51', 'status': 'present'}, '`AUD-588-F05`': {'commit': '470e4a4', 'status': 'present'}, '`AUD-590-B01` / `B-01`, `F-01`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B02` / `B-02`, `F-02`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B03` / `B-03`, `F-03`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B04` / `B-04`, `F-04`': {'commit': '', 'status': 'uncited'}, '`AUD-590-R01` / `R-01`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A01` / `A-1`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A02` / `A-2`, `A-4`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A03` / `A-3`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A05` / `A-5`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A06` / `A-6`, `A-7`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A08` / `A-8`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A09` / `A-9`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A10` / `A-10`': {'commit': '', 'status': 'uncited'}, '`AUD-592-A02` / `A-1` second half, `§12.5`': {'commit': '63f3d61', 'status': 'present'}, '`AUD-592-B01` / `B-8`': {'commit': '8329614', 'status': 'present'}, '`AUD-592-D01` / `D-1`': {'commit': '0f8e03f', 'status': 'present'}, '`AUD-592-D02` / `D-2`': {'commit': '1d3db4d', 'status': 'present'}, '`AUD-592-D03` / `D-3`': {'commit': 'f28fe40', 'status': 'present'}, '`AUD-592-R01` / `A-1`': {'commit': '216025f', 'status': 'present'}, '`AUD-592-R02` / `N-4`, reopens `AUD-513-N04`': {'commit': 'ec3050d', 'status': 'present'}, '`AUD-593-N01` / `N-01`': {'commit': 'dd6a706', 'status': 'present'}, '`AUD-593-N02` / `N-02`': {'commit': 'f539787', 'status': 'present'}, '`AUD-593-N03` / `N-03`': {'commit': '1dd0b1c', 'status': 'present'}, '`AUD-593-N05` / `N-05`': {'commit': 'aa89388', 'status': 'present'}, '`AUD-593-N06` / `N-06`': {'commit': 'aa584a6', 'status': 'present'}, '`AUD-594-N04` / `D-5`, disclosure half of `B-3`': {'commit': 'a0060b7', 'status': 'present'}, '`AUD-594-X01` / `A-1`, residual of `AUD-593-N03`': {'commit': 'dd222b9', 'status': 'present'}, '`AUD-594-X02` / `A-1` second half': {'commit': '1193937', 'status': 'present'}, '`AUD-594-X03` / `X-03`, residual of `AUD-593-N01`': {'commit': 'e89e8f0', 'status': 'present'}, '`AUD-594-X04`': {'commit': '05b8cc9', 'status': 'present'}, '`AUD-595-A02` / `N-5`, advisory half of `AUD-588-B11`': {'commit': '3e15227', 'status': 'present'}, '`AUD-595-A03` / `B-6` exit-code half': {'commit': 'ab7285f', 'status': 'present'}, '`AUD-595-A05`': {'commit': 'e0b3d10', 'status': 'present'}, '`AUD-595-A07`, narrow half of `AUD-592-A02`': {'commit': 'cc15177', 'status': 'present'}, '`AUD-595-B01` / §B': {'commit': 'b77a1ad', 'status': 'present'}, '`AUD-595-Q01`': {'commit': '91a6566', 'status': 'present'}, '`AUD-596-A01` / `ASK-DET-001`': {'commit': 'fa508b4', 'status': 'present'}, '`AUD-596-A02` / `ASK-SELF-001`': {'commit': '4dfadc8', 'status': 'present'}, '`AUD-596-A03` / `ASK-C1-001`': {'commit': 'bffa3a6', 'status': 'present'}, '`AUD-596-A04` / `ASK-UX-003` + `ASK-UX-004`': {'commit': 'f0aac3c', 'status': 'present'}, '`AUD-596-B01` / `B-01`, harder witness for `BUG-6`': {'commit': '567d09d', 'status': 'present'}, '`AUD-596-B02` / `B-02`, class residual of `R2`': {'commit': '', 'status': 'uncited'}, '`AUD-596-B03` / `B-03`': {'commit': 'a8cb814', 'status': 'present'}, '`AUD-596-B06` / `B-06`': {'commit': '8107927', 'status': 'present'}, '`AUD-596-B07` / `B-07`, class residual of `AUD-592-R02`': {'commit': 'e060485', 'status': 'present'}, '`AUD-596-B09` / `B-09`': {'commit': '5b8146c', 'status': 'present'}, '`AUD-596-B10` / `B-10`': {'commit': '5cb994d', 'status': 'present'}, '`AUD-596-B16` / `B-16`': {'commit': '1507f69', 'status': 'present'}, '`AUD-596-D01` / `B-13` + `B-14`': {'commit': '0b1bdf0', 'status': 'present'}, '`AUD-596-D02` / `B-08`, residual of `AUD-594-N04`': {'commit': 'e7f73c3', 'status': 'present'}, '`AUD-596-D03` / `ASK-AGT-001`': {'commit': '8c46967', 'status': 'present'}, '`AUD-596-D04` / `B-18`': {'commit': '040f027', 'status': 'present'}, '`AUD-596-D05` / `B-19`': {'commit': 'fd2bf68', 'status': 'present'}, '`AUD-596-D06` / `B-20`': {'commit': '784cd83', 'status': 'present'}, '`AUD-596-D07` / `B-17`, half refuted': {'commit': 'c3ff822', 'status': 'present'}, '`AUD-596-D08` / `B-22`, class residual of `AUD-591-A06`': {'commit': 'b396c6a', 'status': 'present'}, '`AUD-596-D09` / `B-23`, surviving half of `B26`': {'commit': 'fd3d63f', 'status': 'present'}, '`AUD-596-D10` / `B-29`': {'commit': '', 'status': 'uncited'}, '`AUD-596-D11` / `ASK-UX-005`': {'commit': 'f4b4f77', 'status': 'present'}, '`AUD-596-D12` — six hints that send the reader back to the error': {'commit': '498df44', 'status': 'present'}, '`AUD-596-D13` / `B-28`': {'commit': '1014c72', 'status': 'present'}, '`AUD-596-D14` / `B-15`, narrow half of `B20`': {'commit': '10f2827', 'status': 'present'}, '`AUD-596-R01` / `B-05`': {'commit': '', 'status': 'uncited'}, '`AUD-596-X01` / `ASK-GATE-001`, gating half of `B24`': {'commit': 'e59c655', 'status': 'present'}, '`AUD-596-X02` / `ASK-UX-001` + `ASK-UX-002` + `B-21`': {'commit': 'f6ebf50', 'status': 'present'}, '`AUD-597-A01` / `ASK-UX-001` + `N-01`': {'commit': 'f8cf061', 'status': 'present'}, '`AUD-597-A02` / `ASK-UX-008` + `ASK-DOC-002`': {'commit': '9d47a4c', 'status': 'present'}, '`AUD-597-A03` / `ASK-DET-001`': {'commit': '4c7622a', 'status': 'present'}, '`AUD-597-A04` / `ASK-LEDGER-001`': {'commit': '46fb870', 'status': 'present'}, '`AUD-597-D01` / `B-04`': {'commit': '1094e6a', 'status': 'present'}, '`AUD-597-D02` / `ASK-DOC-001`': {'commit': 'ecb1bb7', 'status': 'present'}, '`AUD-597-D03` / `N-06`': {'commit': '9784333', 'status': 'present'}, '`AUD-597-D04` / `N-05`': {'commit': 'e51c087', 'status': 'present'}, '`AUD-597-D05` / `N-04`, fourth appearance of `C4-27`': {'commit': '9cccc15', 'status': 'present'}, '`AUD-597-D06` / `ASK-CLI-001`': {'commit': '178aa6d', 'status': 'present'}, '`AUD-597-D07` / `N-03`': {'commit': '9245e35', 'status': 'present'}, '`AUD-597-R01` / `ASK-PERF-001`': {'commit': '6910054', 'status': 'present'}, '`AUD-597-R02` / `ASK-ENC-001`': {'commit': 'ce6dac1', 'status': 'present'}, '`AUD-597-R03` / `ASK-UX-002`': {'commit': 'c57e50b', 'status': 'present'}, '`AUD-597-X01` / `ASK-BUILD-001` + `ASK-META-001` / `B-12`': {'commit': '354338b', 'status': 'present'}, '`AUD-598-B01` / `ASK-META-001`': {'commit': 'b2f7c65', 'status': 'present'}, '`AUD-598-B02` / `ASK-UX-002`': {'commit': '56a4bf7', 'status': 'present'}, '`AUD-598-B03` / `B-11`': {'commit': 'c63cb24', 'status': 'present'}, '`AUD-598-B04` / `N-02`': {'commit': '187937f', 'status': 'present'}, '`AUD-598-R01` / `ASK-PERF-002`': {'commit': '', 'status': 'uncited'}, '`AUD-598-X01` / `N-01` + `B-13` + `B-14` + `B-29` + `N-06`': {'commit': '6f2f5a4', 'status': 'present'}, '`AUD-600-B01` / `P-01`': {'commit': '366ffed', 'status': 'present'}, '`AUD-600-B02` / `P-02` + `ASK-PACK-002`': {'commit': '2a814c3', 'status': 'present'}, '`AUD-600-B03` / `ASK-PACK-001`': {'commit': '452f2a0', 'status': 'present'}, '`AUD-600-D01`': {'commit': '1224e1b', 'status': 'present'}, '`AUD-600-D02`': {'commit': 'eab8172', 'status': 'present'}, '`AUD-600-R01` / `R-01`': {'commit': '88add64', 'status': 'present'}, '`AUD-601-B01` / `N-02`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-B02` / `ASK-PACK-004`': {'commit': '', 'status': 'uncited'}, '`AUD-601-D01` / `N-01`, `ASK-UX-001`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-D02` / `B-13`, `B-14`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-M01` / `B-26`, `B-27` retracted': {'commit': '', 'status': 'uncited'}, '`AUD-601-R01` / `R-02`': {'commit': '', 'status': 'uncited'}, '`AUD-619-WATCH`': {'commit': '', 'status': 'uncited'}, '`B-3`': {'commit': '773268d', 'status': 'present'}, '`B-6`': {'commit': '17c6e06', 'status': 'present'}, '`B-8`': {'commit': '8329614', 'status': 'present'}, '`BUG-3` / `443b345`': {'commit': '', 'status': 'uncited'}, '`BUG-4a` / `AUD-588-B11` residual': {'commit': '24b4185', 'status': 'present'}, '`BUG-4b` / `AUD-588-B11` residual': {'commit': '0376350', 'status': 'present'}, '`E-39`, class residual of `AUD-593-N06`': {'commit': '25fc11f', 'status': 'present'}, '`N-5`': {'commit': 'c68083f', 'status': 'present'}, '`N-7`': {'commit': '082e9de', 'status': 'present'}, '`R-1`': {'commit': '424fb1c', 'status': 'present'}, '`R-2`': {'commit': '5158b92', 'status': 'present'}, '`R-3`': {'commit': '88aec04', 'status': 'present'}, '`R-4`': {'commit': '04fd04f', 'status': 'present'}}
5
5
  CLOSURE_COVERAGE = {'closed_rows': 162, 'present': 91, 'absent': 0, 'unresolved': 0, 'uncited': 71, 'unknown': 0}
@@ -46,16 +46,16 @@ CLI commands — impact, endpoints, spring-audit, explain, … each a pro
46
46
  The key idea: the extraction is **content-addressed**. Commands reuse the parse cache and,
47
47
  where their analysed scope matches, the shared Canonical IR; `ask cache model` names what a
48
48
  warm buys for each command rather than implying that every projection costs the same. In
49
- 5.9.14, `validation` enters through that shared CIR and `data-exposure` reuses one semantic
49
+ 5.9.16, `validation` enters through that shared CIR and `data-exposure` reuses one semantic
50
50
  model across all declared label seeds. (The extraction and consumption contract is fixed in
51
51
  the architecture ADRs 0001–0004 under `docs/architecture/`.)
52
52
 
53
53
  The current external pack audit intake is tracked in
54
54
  [`AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md`](AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md);
55
- the two pack fixes are included in release 5.9.14, with release-artifact verification
55
+ the two pack fixes are included in release 5.9.16, with release-artifact verification
56
56
  remaining for detached execution and pack evidence.
57
57
 
58
- ### Reliability contracts in 5.9.14
58
+ ### Reliability contracts in 5.9.16
59
59
 
60
60
  **A deadline the root analysis honours.** `ASK_MAX_ANALYSIS_SECONDS=<seconds>` now
61
61
  stops `ask <repo>` at a measured phase boundary. A run that spends its budget
@@ -260,7 +260,7 @@ pipx install sourcecode # isolated install, no venv needed
260
260
 
261
261
  # Verify
262
262
  ask version
263
- # ask 5.9.14
263
+ # ask 5.9.16
264
264
  ```
265
265
 
266
266
  Requires Python 3.9+.
sourcecode/cache.py CHANGED
@@ -58,6 +58,7 @@ import json
58
58
  import os
59
59
  import re
60
60
  import subprocess
61
+ import threading
61
62
  import time
62
63
  import uuid
63
64
  from datetime import datetime, timezone
@@ -156,6 +157,12 @@ _DEFAULT_MAX_SIZE_MB: int = 50
156
157
  _REPLACE_RETRIES: int = 5
157
158
  _REPLACE_BACKOFF_S: float = 0.05
158
159
 
160
+ # Some Windows filesystems expose timestamps too coarsely to order consecutive
161
+ # atomic writes. Persist a process-monotonic stamp in each cache envelope so GC
162
+ # never falls back to directory enumeration order when file timestamps tie.
163
+ _WRITE_CLOCK_LOCK = threading.Lock()
164
+ _LAST_WRITE_NS: int = 0
165
+
159
166
  # Matches "snapshot-<hex_commit>-<hex_flags>.json.gz"
160
167
  _SNAPSHOT_RE = re.compile(r"^snapshot-([0-9a-f]+)-[0-9a-f]+\.json\.gz$")
161
168
 
@@ -777,6 +784,7 @@ def write_core(repo_root: Path, core_key: str, core_data: dict[str, Any]) -> str
777
784
  "key": core_key,
778
785
  "hash": core_hash,
779
786
  "ts": _now_iso(),
787
+ "written_ns": _next_write_ns(),
780
788
  "data": core_data,
781
789
  }
782
790
  payload = gzip.compress(
@@ -848,6 +856,14 @@ def _now_iso() -> str:
848
856
  return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
849
857
 
850
858
 
859
+ def _next_write_ns() -> int:
860
+ """Return a wall-clock nanosecond stamp that increases in this process."""
861
+ global _LAST_WRITE_NS
862
+ with _WRITE_CLOCK_LOCK:
863
+ _LAST_WRITE_NS = max(time.time_ns(), _LAST_WRITE_NS + 1)
864
+ return _LAST_WRITE_NS
865
+
866
+
851
867
  def _build_envelope(
852
868
  cache_key: str,
853
869
  content: str,
@@ -860,6 +876,7 @@ def _build_envelope(
860
876
  "sv": SCHEMA_VERSION,
861
877
  "key": cache_key,
862
878
  "ts": _now_iso(),
879
+ "written_ns": _next_write_ns(),
863
880
  "fmt": fmt,
864
881
  "layers": layers,
865
882
  }
@@ -1034,6 +1051,25 @@ def _cas_restore(
1034
1051
  # Eviction / GC
1035
1052
  # ---------------------------------------------------------------------------
1036
1053
 
1054
+ def _cache_recency(path: Path) -> tuple[int, int, int, str]:
1055
+ """Stable newest-first key for a cache envelope.
1056
+
1057
+ New envelopes carry ``written_ns`` because filesystem timestamps can tie on
1058
+ Windows. Older envelopes remain orderable through their native timestamps.
1059
+ The remaining fields make a genuine cross-process tie deterministic.
1060
+ """
1061
+ stat_result = path.stat()
1062
+ stamp = stat_result.st_mtime_ns
1063
+ try:
1064
+ envelope = json.loads(gzip.decompress(path.read_bytes()).decode("utf-8"))
1065
+ stored = envelope.get("written_ns") if isinstance(envelope, dict) else None
1066
+ if isinstance(stored, int) and stored > 0:
1067
+ stamp = stored
1068
+ except Exception:
1069
+ pass
1070
+ return stamp, stat_result.st_mtime_ns, stat_result.st_ctime_ns, path.name
1071
+
1072
+
1037
1073
  def _gc(cache_d: Path) -> None:
1038
1074
  """Evict old snapshots/cores/views and sweep orphaned CAS blobs.
1039
1075
 
@@ -1072,8 +1108,8 @@ def _gc(cache_d: Path) -> None:
1072
1108
  if keep <= 0 or len(groups) <= keep:
1073
1109
  surviving = all_snapshots + all_cores
1074
1110
  else:
1075
- def _newest_mtime(commit: str) -> float:
1076
- return max(p.stat().st_mtime for p in groups[commit])
1111
+ def _newest_mtime(commit: str) -> tuple[int, int, int, str]:
1112
+ return max(_cache_recency(p) for p in groups[commit])
1077
1113
 
1078
1114
  sorted_commits = sorted(groups, key=_newest_mtime, reverse=True)
1079
1115
  surviving = []
@@ -1088,7 +1124,7 @@ def _gc(cache_d: Path) -> None:
1088
1124
  if max_cores > 0:
1089
1125
  surviving_cores = [p for p in surviving if p.name.startswith("core-") and p.exists()]
1090
1126
  if len(surviving_cores) > max_cores:
1091
- surviving_cores.sort(key=lambda p: p.stat().st_mtime, reverse=True)
1127
+ surviving_cores.sort(key=_cache_recency, reverse=True)
1092
1128
  for evict in surviving_cores[max_cores:]:
1093
1129
  _safe_unlink(evict)
1094
1130
  surviving = [p for p in surviving if p != evict]
sourcecode/cli.py CHANGED
@@ -48,6 +48,7 @@ from sourcecode.caller_metrics import (
48
48
  CALLER_METRIC_RECONCILIATION,
49
49
  CYCLE_METRIC_RECONCILIATION,
50
50
  )
51
+ from sourcecode.click_runtime import command_children, current_context, is_argument
51
52
  from sourcecode.repository_ir import extract_java_endpoints as _extract_java_endpoints
52
53
  from sourcecode.path_display import portable_path
53
54
 
@@ -1054,6 +1055,7 @@ _OPTIONS_WITH_VALUE: frozenset[str] = frozenset({
1054
1055
  "--rank-by",
1055
1056
  "--symbol",
1056
1057
  "--max-importers",
1058
+ "--jobs",
1057
1059
  "--exclude",
1058
1060
  "--files",
1059
1061
  "--fail-on",
@@ -1160,15 +1162,15 @@ _REPOSITORY_ARGUMENT_NAMES: "frozenset[str]" = frozenset({"path", "repo", "dir"}
1160
1162
  def _leaf_for_args(args: "list[str]") -> "tuple[Any, int]":
1161
1163
  """(the command these args address, how many tokens named it). Never raises."""
1162
1164
  try:
1163
- import click
1164
1165
  import typer.main as _tm
1165
1166
 
1166
1167
  node: Any = _tm.get_command(app)
1167
1168
  consumed = 0
1168
1169
  for token in args:
1169
- if token.startswith("-") or not isinstance(node, click.Group):
1170
+ children = command_children(node)
1171
+ if token.startswith("-") or not children:
1170
1172
  break
1171
- child = node.commands.get(token)
1173
+ child = children.get(token)
1172
1174
  if child is None:
1173
1175
  break
1174
1176
  node, consumed = child, consumed + 1
@@ -1191,11 +1193,6 @@ def _normalise_repository_option(args: "list[str]") -> "list[str]":
1191
1193
  repository twice, which is a mistake to refuse rather than a spelling to
1192
1194
  accept, and the refusal now names what `plan` actually takes.
1193
1195
  """
1194
- try:
1195
- import click
1196
- except Exception: # pragma: no cover - click is a hard dependency
1197
- return args
1198
-
1199
1196
  leaf, consumed = _leaf_for_args(args)
1200
1197
  if leaf is None:
1201
1198
  return args
@@ -1206,7 +1203,7 @@ def _normalise_repository_option(args: "list[str]") -> "list[str]":
1206
1203
  }
1207
1204
  slots = [
1208
1205
  param for param in (getattr(leaf, "params", None) or ())
1209
- if isinstance(param, click.Argument)
1206
+ if is_argument(param)
1210
1207
  ]
1211
1208
  if not any(
1212
1209
  str(getattr(param, "name", "")) in _REPOSITORY_ARGUMENT_NAMES
@@ -1225,7 +1222,7 @@ def _normalise_repository_option(args: "list[str]") -> "list[str]":
1225
1222
  takes_value = {
1226
1223
  opt
1227
1224
  for param in (getattr(leaf, "params", None) or ())
1228
- if not isinstance(param, click.Argument)
1225
+ if not is_argument(param)
1229
1226
  and not getattr(param, "is_flag", False)
1230
1227
  and getattr(param, "nargs", 1) != 0
1231
1228
  for opt in (getattr(param, "opts", None) or ())
@@ -1306,6 +1303,11 @@ def _preprocess_args(args: list[str]) -> list[str]:
1306
1303
  # got wrong instead of swallowing it and erroring on the next one.
1307
1304
  if _has_trailing_nonsubcommand_positional(result, i):
1308
1305
  _reject_unknown_command(arg)
1306
+ # `ask missing-command --help` used to treat the unknown token as an
1307
+ # implicit path and render root help with exit 0. Help must not turn a
1308
+ # command typo into a successful invocation.
1309
+ if "--help" in result and not Path(arg).exists():
1310
+ _reject_unknown_command(arg)
1309
1311
  # A single missing token that is very close to a command is a command
1310
1312
  # typo, not a repository path. Existing directories retain the implicit
1311
1313
  # `ask <path>` grammar, including names that resemble commands.
@@ -1842,7 +1844,24 @@ def _admit_path(
1842
1844
  rejection_hint,
1843
1845
  )
1844
1846
 
1845
- admission = admit_repo_path(raw)
1847
+ raw_for_report: "Path | str" = raw
1848
+ if not isinstance(raw, str):
1849
+ # Click converts a Typer ``Path`` before this shared gate sees it. On
1850
+ # Windows that turns a Git-Bash-style ``/repo`` into ``\\repo`` and the
1851
+ # error envelope stops quoting what the user actually typed. Recover
1852
+ # the spelling from this invocation while using Path equality only to
1853
+ # identify the corresponding token; admission still resolves normally.
1854
+ for token in _tls.__dict__.get("invocation_args", ()):
1855
+ if token.startswith("-") or token in _SUBCOMMANDS:
1856
+ continue
1857
+ try:
1858
+ if Path(token) == raw:
1859
+ raw_for_report = token
1860
+ break
1861
+ except (OSError, TypeError, ValueError):
1862
+ continue
1863
+
1864
+ admission = admit_repo_path(raw_for_report)
1846
1865
  if admission.admitted:
1847
1866
  # F-AS: the one gate every command's target passes through is also the
1848
1867
  # one place that knows which tree the no-write guarantee is about. Declared
@@ -2212,9 +2231,7 @@ def _active_command_context() -> "tuple[str, Optional[Path]]":
2212
2231
  asks where a payload came from.
2213
2232
  """
2214
2233
  try:
2215
- import click
2216
-
2217
- ctx = click.get_current_context(silent=True)
2234
+ ctx = current_context()
2218
2235
  except Exception:
2219
2236
  ctx = None
2220
2237
  if ctx is None:
@@ -3897,15 +3914,15 @@ def commands_with_selection_flags() -> "dict[str, tuple[str, ...]]":
3897
3914
  renders, so a command cannot acquire one of these flags without joining the
3898
3915
  set that has to be asserted.
3899
3916
  """
3900
- import click
3901
3917
  import typer.main as _tm
3902
3918
 
3903
3919
  root = _tm.get_command(app)
3904
3920
  found: "dict[str, tuple[str, ...]]" = {}
3905
3921
 
3906
3922
  def walk(node, prefix: str = "") -> None:
3907
- if isinstance(node, click.Group):
3908
- for name, sub in node.commands.items():
3923
+ children = command_children(node)
3924
+ if children:
3925
+ for name, sub in children.items():
3909
3926
  walk(sub, f"{prefix} {name}".strip())
3910
3927
  return
3911
3928
  declared = tuple(
@@ -6406,6 +6423,13 @@ def main(
6406
6423
  dependency_records.extend(root_dependencies)
6407
6424
  dependency_summaries.append(root_summary)
6408
6425
  _root_timing["manifests_ms"] = round((time.perf_counter() - _manifest_t0) * 1000, 2)
6426
+ # AUD-619-CACHE-WRITE: the previous label remained "parsing manifests"
6427
+ # throughout every workspace analyzer below. On a 1,717-POM repository it
6428
+ # attributed 4.4 minutes to XML/filesystem work that an isolated scan did in
6429
+ # under four seconds. The manifest timer above is the measured boundary;
6430
+ # name the broader work that follows instead of leaving a completed stage on
6431
+ # screen.
6432
+ _progress.update("analyzing repository context")
6409
6433
  module_graphs = []
6410
6434
  if graph_analyzer is not None:
6411
6435
  root_graph_tree = (
@@ -7220,7 +7244,20 @@ def main(
7220
7244
  # join race on large repos where core_view(sm) itself takes seconds.
7221
7245
  # Background thread only does gzip + disk I/O (fast, bounded latency).
7222
7246
  #
7223
- _bg_core_dict: dict | None = _core_dict()
7247
+ from sourcecode.serializer import cache_core_view as _cache_core_view
7248
+
7249
+ _prepared_mode = "compact" if compact else ("agent" if agent else None)
7250
+ _prepared_view = data if _prepared_mode and isinstance(data, dict) else None
7251
+ _bg_core_dict: dict | None = _cache_core_view(
7252
+ sm,
7253
+ prepared_view=_prepared_view,
7254
+ view_mode=_prepared_mode,
7255
+ state_key=_core_key,
7256
+ )
7257
+ # RIS is the second writer of the same snapshot. Reuse the projection
7258
+ # decision as well as the dict; calling `_core_dict()` below must not
7259
+ # recreate the three-view tail this branch deliberately avoided.
7260
+ _core_dict_for_writers = _bg_core_dict
7224
7261
 
7225
7262
  if _bg_core_dict is not None:
7226
7263
  _bg_target = target
@@ -19659,7 +19696,7 @@ def cold_start_cmd(
19659
19696
  # `endpoints`. Identity is contract, not content; a section that is
19660
19697
  # already small is not where the weight is; and a list that was cut says
19661
19698
  # so. All three are the shared bound now, not a rule invented here.
19662
- from sourcecode.output_bounds import compact_document, declare_cap
19699
+ from sourcecode.output_bounds import bound_lists, compact_document, declare_cap
19663
19700
 
19664
19701
  # Keep the compact contract attributable even when there is no RIS yet.
19665
19702
  # There is no repository identity to recover in this case, so publish
@@ -19676,6 +19713,7 @@ def cold_start_cmd(
19676
19713
 
19677
19714
  _CS_ORIENTATION = (
19678
19715
  "summary", "entrypoints", "endpoints", "project_type", "validation",
19716
+ "snapshot_basis",
19679
19717
  )
19680
19718
  result = compact_document(result, keep=_CS_ORIENTATION)
19681
19719
  _CS_ENDPOINT_LIMIT = 30
@@ -19683,7 +19721,39 @@ def cold_start_cmd(
19683
19721
  _total_endpoints = len(result["endpoints"])
19684
19722
  result["endpoints"] = result["endpoints"][:_CS_ENDPOINT_LIMIT]
19685
19723
  declare_cap(result, "endpoints", _total_endpoints, _CS_ENDPOINT_LIMIT)
19724
+
19725
+ # AUD-619-I: the RIS stores whichever compact projection primed it. A
19726
+ # root run with `--output` deliberately bypasses the stdout budget, so
19727
+ # preserving its entire `summary` here made cold-start range from 3 KB
19728
+ # to 89 KB on one repository. Re-project that nested document from its
19729
+ # semantic orientation fields and cap every growing list recursively.
19730
+ # `--compact` is a shape contract, so an output file does not bypass it.
19731
+ _summary = result.get("summary")
19732
+ if isinstance(_summary, dict):
19733
+ _CS_SUMMARY_ORIENTATION = (
19734
+ "project_type", "project_summary", "summary_basis",
19735
+ "architecture_summary", "stacks", "entry_points",
19736
+ "dependency_summary", "key_dependencies", "confidence_summary",
19737
+ "analysis_gaps", "language_version", "deployment",
19738
+ )
19739
+ _summary = compact_document(
19740
+ _summary,
19741
+ keep=_CS_SUMMARY_ORIENTATION,
19742
+ small_section_bytes=512,
19743
+ )
19744
+ _summary = bound_lists(
19745
+ _summary,
19746
+ {
19747
+ "stacks", "entry_points", "key_dependencies", "analysis_gaps",
19748
+ "classes", "controllers", "active", "inactive", "unresolved",
19749
+ "evidence", "files", "items",
19750
+ },
19751
+ 10,
19752
+ )
19753
+ result["summary"] = _summary
19686
19754
  result["_meta"] = {**(result.get("_meta") or {}), "compact_mode": True,
19755
+ "compact_limit_tokens": 10_000,
19756
+ "priming_state_declared_by": "snapshot_basis",
19687
19757
  "full_available": "ask cold-start (without --compact)"}
19688
19758
  from sourcecode.token_estimate import estimate_tokens as _est_tokens, token_economy as _tok_econ
19689
19759
  _out = _json.dumps(result, indent=2, ensure_ascii=False)
@@ -0,0 +1,61 @@
1
+ """Compatibility seams for the command objects returned by supported Typer versions.
2
+
3
+ Typer 0.27 moved its runtime onto a bundled Click implementation and its group
4
+ and argument objects no longer inherit the public ``click.Group`` and
5
+ ``click.Argument`` classes. ASK only needs the stable runtime protocol: groups
6
+ publish ``commands`` and arguments publish ``param_type_name == 'argument'``.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Mapping
11
+ from typing import Any
12
+
13
+
14
+ def command_children(command: Any) -> Mapping[str, Any]:
15
+ children = getattr(command, "commands", None)
16
+ return children if isinstance(children, Mapping) else {}
17
+
18
+
19
+ def is_command_group(command: Any) -> bool:
20
+ return isinstance(getattr(command, "commands", None), Mapping)
21
+
22
+
23
+ def is_argument(param: Any) -> bool:
24
+ return getattr(param, "param_type_name", None) == "argument"
25
+
26
+
27
+ def command_context(command: Any, *, info_name: str, parent: Any = None) -> Any:
28
+ context_class = command.context_class
29
+ return context_class(command, info_name=info_name, parent=parent)
30
+
31
+
32
+ def is_usage_error(exc: BaseException) -> bool:
33
+ types: list[type[BaseException]] = []
34
+ try:
35
+ from typer._click.exceptions import UsageError
36
+
37
+ types.append(UsageError)
38
+ except ImportError:
39
+ pass
40
+ try:
41
+ from click import UsageError as ClassicUsageError
42
+
43
+ types.append(ClassicUsageError)
44
+ except ImportError:
45
+ pass
46
+ return bool(types) and isinstance(exc, tuple(types))
47
+
48
+
49
+ def current_context() -> Any:
50
+ """Return the active context from bundled or classic Click."""
51
+ try:
52
+ from typer._click.globals import get_current_context
53
+
54
+ return get_current_context(silent=True)
55
+ except Exception:
56
+ try:
57
+ import click
58
+
59
+ return click.get_current_context(silent=True)
60
+ except Exception:
61
+ return None
sourcecode/envelope.py CHANGED
@@ -76,7 +76,7 @@ def _tree_state(target: Path) -> "tuple[str, str, str]":
76
76
  the same working tree agree, and an edited working tree never reuses the
77
77
  clean commit's identity.
78
78
  """
79
- root = _git(["rev-parse", "--show-toplevel"], target)
79
+ root = _git(["rev-parse", "--show-toplevel"], target).replace("\\", "/")
80
80
  if not root:
81
81
  return "", "", ""
82
82
  # Git for Windows emits forward slashes while Path resolves to native
@@ -149,4 +149,4 @@ def _extract_safely(tar: tarfile.TarFile, dest: Path) -> None:
149
149
  resolved = (root / member.name).resolve()
150
150
  if not str(resolved).startswith(str(root)):
151
151
  continue
152
- tar.extract(member, dest)
152
+ tar.extract(member, dest, filter="data")
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
  import hashlib
5
5
  import json
6
6
  import os
7
+ import uuid
7
8
  from dataclasses import asdict
8
9
  from pathlib import Path
9
10
 
@@ -90,12 +91,21 @@ def load(root: Path) -> tuple[list[DependencyRecord], DependencySummary] | None:
90
91
 
91
92
 
92
93
  def save(root: Path, records: list[DependencyRecord], summary: DependencySummary) -> None:
94
+ tmp: Path | None = None
93
95
  try:
94
96
  path = _base() / "manifest-cache-v1" / f"{_key(root)}.json"
95
97
  path.parent.mkdir(parents=True, exist_ok=True)
96
98
  payload = {"records": [asdict(row) for row in records], "summary": asdict(summary)}
97
- tmp = path.with_suffix(".tmp")
99
+ tmp = path.with_name(
100
+ f"{path.name}.{os.getpid()}.{uuid.uuid4().hex[:8]}.tmp"
101
+ )
98
102
  tmp.write_text(json.dumps(payload, sort_keys=True), encoding="utf-8")
99
103
  tmp.replace(path)
100
104
  except OSError:
101
105
  return
106
+ finally:
107
+ if tmp is not None:
108
+ try:
109
+ tmp.unlink(missing_ok=True)
110
+ except OSError:
111
+ pass
@@ -25,6 +25,7 @@ with quiet_dependency_import():
25
25
  from typer.main import get_command
26
26
 
27
27
  from sourcecode.cli import app as _cli_app
28
+ from sourcecode.click_runtime import command_children
28
29
  from sourcecode.error_schema import (
29
30
  EXECUTION_FAILED_CODE,
30
31
  INTERNAL_ERROR_CODE,
@@ -191,8 +192,9 @@ def _discover_commands() -> list[RuntimeCommand]:
191
192
  docstring=docstring,
192
193
  )
193
194
  )
194
- if isinstance(command, click.Group):
195
- for name, child in command.commands.items():
195
+ children = command_children(command)
196
+ if children:
197
+ for name, child in children.items():
196
198
  walk(child, path + (name,))
197
199
 
198
200
  walk(root, ())
@@ -226,12 +226,9 @@ def estimate_tokens(content: str) -> int:
226
226
 
227
227
 
228
228
  def _click_context() -> Any:
229
- try:
230
- import click
229
+ from sourcecode.click_runtime import current_context
231
230
 
232
- return click.get_current_context(silent=True)
233
- except Exception:
234
- return None
231
+ return current_context()
235
232
 
236
233
 
237
234
  #: ASK-09. The command tree, for the derivation that does **not** depend on
sourcecode/packs.py CHANGED
@@ -18,20 +18,21 @@ Two facts about the shape of a pack payload, because they are load-bearing:
18
18
  """
19
19
  from __future__ import annotations
20
20
 
21
+ from contextlib import contextmanager
21
22
  import json
23
+ import shutil
22
24
  import subprocess
23
25
  import sys
24
26
  import tempfile
25
27
  import threading
26
28
  import time
27
29
  from pathlib import Path
28
- from typing import Any, Callable, Optional, Sequence
30
+ from typing import Any, Callable, Iterator, Optional, Sequence
29
31
 
30
- from sourcecode import pack_manifest
32
+ from sourcecode import cache, pack_manifest
31
33
  from sourcecode.partial_contract import PARTIAL_EXIT_CODE
32
34
  from sourcecode.verify_repo import EXIT_BLOCKED, EXIT_PASS, EXIT_UNVERIFIED
33
35
 
34
-
35
36
  ASSESSMENT_SCHEMA = "ask-pack-assessment-v1"
36
37
  GATE_SCHEMA = "ask-pack-gate-v1"
37
38
  CATALOG_SCHEMA = "ask-pack-catalog-v1"
@@ -477,6 +478,37 @@ def _bounded_component(command: str, payload: "Optional[dict[str, Any]]") -> "tu
477
478
  }
478
479
 
479
480
 
481
+ @contextmanager
482
+ def _pack_scratch(root: Path, pack: str) -> Iterator[Path]:
483
+ """Yield private pack scratch without letting cleanup replace the answer.
484
+
485
+ ``TemporaryDirectory`` uses the Windows user temp directory by default.
486
+ Antivirus, indexing and inherited subprocess handles can transiently make
487
+ its final recursive delete fail with WinError 5/32. That exception used to
488
+ escape *after every component had completed*, discarding the valid pack.
489
+
490
+ Keep scratch beside ASK's per-repository cache, whose location is already
491
+ configurable, and retry a bounded cleanup. A still-locked scratch folder
492
+ is harmless cache residue; it must never turn a measured result into a
493
+ traceback.
494
+ """
495
+ parent = cache.cache_dir(root) / "pack-scratch"
496
+ parent.mkdir(parents=True, exist_ok=True)
497
+ scratch = Path(tempfile.mkdtemp(prefix=f"{pack}-", dir=str(parent)))
498
+ try:
499
+ yield scratch
500
+ finally:
501
+ for attempt in range(3):
502
+ try:
503
+ shutil.rmtree(scratch)
504
+ break
505
+ except FileNotFoundError:
506
+ break
507
+ except OSError:
508
+ if attempt < 2:
509
+ time.sleep(0.05 * (attempt + 1))
510
+
511
+
480
512
  def _component_payload(data: dict[str, Any], command: str) -> dict[str, Any]:
481
513
  component = (data.get("artifacts") or {}).get(command) or {}
482
514
  payload = component.get("payload")
@@ -948,8 +980,7 @@ def run_gate(root: Path, *, since: str, pr: Optional[int] = None, profile: Optio
948
980
  output_dir = None
949
981
  results: dict[str, Any] = {}
950
982
  artifacts: dict[str, Optional[str]] = {}
951
- with tempfile.TemporaryDirectory(prefix="ask-gate-") as temp:
952
- temp_root = Path(temp)
983
+ with _pack_scratch(root, "gate") as temp_root:
953
984
  # `--allow-unverified` is deliberately NOT forwarded to `verify`. The
954
985
  # child would then exit 0 on a run it could not verify and the pack
955
986
  # would read that 0 as a pass — the silent conversion
sourcecode/parallel.py CHANGED
@@ -34,8 +34,10 @@ from __future__ import annotations
34
34
 
35
35
  import os
36
36
  import sys
37
+ from collections.abc import Iterable, Sequence
38
+ from contextlib import suppress
37
39
  from pathlib import Path
38
- from typing import Callable, Iterable, Optional, Sequence
40
+ from typing import Callable, Optional
39
41
 
40
42
  #: Reads the job count for a process that was not given one explicitly.
41
43
  ENV_JOBS = "ASK_JOBS"
@@ -50,9 +52,17 @@ ENV_WORKER = "ASK_PARALLEL_WORKER"
50
52
  #: is reported rather than hidden.
51
53
  MIN_FILES_FOR_POOL = 200
52
54
 
55
+ #: Windows ``spawn`` duplicates several pipe/semaphore handles per worker. The
56
+ #: field failure behind AUD-619-R started twelve workers together and lost every
57
+ #: one in ``_winapi.DuplicateHandle``. Leaving one CPU free is not a meaningful
58
+ #: bound for that resource on a 16/32-core Windows host; four concurrent parsers
59
+ #: retain real parallelism without creating the handle storm. An explicit
60
+ #: ``--jobs`` remains an informed override.
61
+ WINDOWS_DEFAULT_JOB_CAP = 4
62
+
53
63
  #: Process-global override set by `--jobs`. `None` means "nobody asked", which is
54
64
  #: not the same as "asked for 1" — the second disables the pool deliberately.
55
- _JOBS_OVERRIDE: "Optional[int]" = None
65
+ _JOBS_OVERRIDE: Optional[int] = None
56
66
 
57
67
 
58
68
  def default_jobs() -> int:
@@ -61,10 +71,13 @@ def default_jobs() -> int:
61
71
  One core is left for the parent: it is not idle while workers run, it is
62
72
  reading files, deserialising results and assembling the IR.
63
73
  """
64
- return max(1, (os.cpu_count() or 1) - 1)
74
+ available = max(1, (os.cpu_count() or 1) - 1)
75
+ if os.name == "nt":
76
+ return min(WINDOWS_DEFAULT_JOB_CAP, available)
77
+ return available
65
78
 
66
79
 
67
- def set_jobs(jobs: "Optional[int]") -> None:
80
+ def set_jobs(jobs: Optional[int]) -> None:
68
81
  """Record the process-wide job count a CLI flag asked for.
69
82
 
70
83
  Process-global because that is what it is — one invocation analyses one
@@ -75,7 +88,7 @@ def set_jobs(jobs: "Optional[int]") -> None:
75
88
  _JOBS_OVERRIDE = None if jobs is None else max(1, int(jobs))
76
89
 
77
90
 
78
- def resolve_jobs(requested: "Optional[int]" = None) -> int:
91
+ def resolve_jobs(requested: Optional[int] = None) -> int:
79
92
  """The job count in force: explicit argument, then `--jobs`, then env, then default.
80
93
 
81
94
  A worker always resolves to 1, whatever the environment says.
@@ -99,10 +112,54 @@ def _reset_for_tests() -> None:
99
112
  _JOBS_OVERRIDE = None
100
113
 
101
114
 
115
+ def _executor(max_workers: int):
116
+ """Construct the process executor through one injectable seam."""
117
+ from concurrent.futures import ProcessPoolExecutor
118
+
119
+ return ProcessPoolExecutor(max_workers=max_workers)
120
+
121
+
122
+ def _abort_executor(pool: object) -> None:
123
+ """Cancel a broken pool and reap its children without a blocking shutdown.
124
+
125
+ ``ProcessPoolExecutor.__exit__`` always performs ``shutdown(wait=True)``.
126
+ That is the wrong failure contract for a pool whose Windows workers failed
127
+ while duplicating startup handles: the parent can wait forever for a result
128
+ no surviving worker can deliver. Newer Pythons expose
129
+ ``terminate_workers``; older supported versions need the executor's process
130
+ handles captured before non-blocking shutdown. This is deliberately
131
+ best-effort: the unchanged sequential parser remains the answer path.
132
+ """
133
+ processes = list((getattr(pool, "_processes", None) or {}).values())
134
+ terminate_workers = getattr(pool, "terminate_workers", None)
135
+ if callable(terminate_workers):
136
+ try:
137
+ terminate_workers()
138
+ return
139
+ except Exception:
140
+ pass
141
+ try:
142
+ pool.shutdown(wait=False, cancel_futures=True) # type: ignore[attr-defined]
143
+ except TypeError: # Python 3.9 supports wait but not cancel_futures everywhere.
144
+ with suppress(Exception):
145
+ pool.shutdown(wait=False) # type: ignore[attr-defined]
146
+ except Exception:
147
+ pass
148
+ for process in processes:
149
+ try:
150
+ if process.is_alive():
151
+ process.terminate()
152
+ except Exception:
153
+ pass
154
+ for process in processes:
155
+ with suppress(Exception):
156
+ process.join(timeout=1.0)
157
+
158
+
102
159
  # ── The worker ──────────────────────────────────────────────────────────────
103
160
 
104
161
 
105
- def _parse_into_cache(task: "tuple[str, str, str, tuple[str, ...]]") -> "tuple[str, str]":
162
+ def _parse_into_cache(task: tuple[str, str, str, tuple[str, ...]]) -> tuple[str, str]:
106
163
  """Parse one file in a worker process and leave the result in the parse cache.
107
164
 
108
165
  Returns ``(rel_path, outcome)`` — a receipt, never a parse. The parent has no
@@ -147,12 +204,12 @@ def _parse_into_cache(task: "tuple[str, str, str, tuple[str, ...]]") -> "tuple[s
147
204
 
148
205
  def prefill_parse_cache(
149
206
  root: Path,
150
- rel_paths: "Sequence[str]",
207
+ rel_paths: Sequence[str],
151
208
  *,
152
209
  capture_sig: str,
153
- extra_capture: "Iterable[str]",
154
- jobs: "Optional[int]" = None,
155
- progress: "Optional[Callable[[int, int, str], None]]" = None,
210
+ extra_capture: Iterable[str],
211
+ jobs: Optional[int] = None,
212
+ progress: Optional[Callable[[int, int, str], None]] = None,
156
213
  ) -> dict:
157
214
  """Parse *rel_paths* across worker processes into the shared parse cache.
158
215
 
@@ -214,39 +271,36 @@ def prefill_parse_cache(
214
271
 
215
272
  # Retire dead generations once, here, rather than letting N workers race to
216
273
  # do it (C3-70's purge is idempotent, but doing it once is cheaper).
217
- try:
274
+ with suppress(Exception):
218
275
  _pc.purge_stale_generations()
219
- except Exception:
220
- pass
221
276
 
222
277
  markers = tuple(sorted(extra_capture or ()))
223
278
  tasks = [(str(root), rel, capture_sig, markers) for rel in rel_paths]
224
279
 
225
- try:
226
- from concurrent.futures import ProcessPoolExecutor
227
- except Exception:
228
- receipt["reason"] = "no process pool available on this platform"
229
- return receipt
230
-
231
280
  done = 0
281
+ pool = None
232
282
  try:
233
283
  # `chunksize` amortises the queue round-trip: each task is one file, and a
234
284
  # file is milliseconds of work.
235
- with ProcessPoolExecutor(max_workers=resolved) as pool:
236
- chunk = max(1, min(64, total // (resolved * 4) or 1))
237
- for _rel, outcome in pool.map(_parse_into_cache, tasks, chunksize=chunk):
238
- done += 1
239
- if outcome in ("parsed", "hit"):
240
- receipt[outcome] += 1
241
- elif outcome != "cache-disabled":
242
- receipt["failed"] += 1
243
- if progress is not None and (done % 50 == 0 or done == total):
244
- progress(done, total, "parsing")
285
+ pool = _executor(resolved)
286
+ chunk = max(1, min(64, total // (resolved * 4) or 1))
287
+ for _rel, outcome in pool.map(_parse_into_cache, tasks, chunksize=chunk):
288
+ done += 1
289
+ if outcome in ("parsed", "hit"):
290
+ receipt[outcome] += 1
291
+ elif outcome != "cache-disabled":
292
+ receipt["failed"] += 1
293
+ if progress is not None and (done % 50 == 0 or done == total):
294
+ progress(done, total, "parsing")
295
+ pool.shutdown(wait=True)
245
296
  receipt["parallel"] = True
246
297
  except Exception as exc:
247
- # A pool that cannot start (sandbox, frozen build, exhausted descriptors)
248
- # is not an error a user should see: the sequential loop still produces
249
- # the same IR, more slowly. The reason travels in the receipt.
298
+ # AUD-619-R: on Windows a worker can fail in DuplicateHandle after the
299
+ # executor object exists. A context-manager shutdown then waits for the
300
+ # broken pool and can turn a safe fallback into a hang. Abort/reap it;
301
+ # the caller's unchanged sequential loop produces the same IR.
302
+ if pool is not None:
303
+ _abort_executor(pool)
250
304
  receipt["reason"] = f"pool unavailable ({type(exc).__name__}) — parsing sequentially"
251
305
  return receipt
252
306
  if not receipt["reason"]:
@@ -27,18 +27,16 @@ from typing import Any, Optional
27
27
  #: The date `__version__` was released, from that version's CHANGELOG entry.
28
28
  #: `tests/test_release_info.py` fails the build if the two disagree, so this is
29
29
  #: a copy of a fact rather than a second authority for it.
30
- RELEASE_DATE = "2026-08-30"
30
+ RELEASE_DATE = "2026-08-31"
31
31
 
32
- #: Releases per day, measured over the 30 releases before this one (the same
33
- #: computation the test re-runs against the CHANGELOG). Stable across window
34
- #: sizes when it was taken — 4.5 over 10, 4.75 over 20, 4.14 over 30 — which is
35
- #: why a single figure is publishable at all. It was 2.07 through 5.6.x; the
36
- #: cadence itself doubled, so the constant follows the measurement rather than
37
- #: the measurement being reported against a stale constant.
38
- RELEASE_RATE_PER_DAY = 4.14
32
+ #: Releases per day, measured over the latest 30 CHANGELOG releases (the same
33
+ #: computation the test re-runs). The nine-day interval before 5.9.0 lowers
34
+ #: that window to 29 releases over 18 days, so the constant follows the current
35
+ #: measurement rather than preserving the former short-burst cadence.
36
+ RELEASE_RATE_PER_DAY = 1.61
39
37
 
40
38
  #: How many releases behind is worth telling the operator about. Four, which on
41
- #: the measured cadence is about two days: one release is half a day and warning
39
+ #: the measured cadence is about three days: one release is under a day and warning
42
40
  #: about it would put a line on almost every run, which is how an advisory stops
43
41
  #: being read. The field case — three days, an estimated six releases, eight in
44
42
  #: fact — is well past it. An assumption, not a measurement.
@@ -48,8 +46,8 @@ STALE_AFTER_RELEASES = 4
48
46
  #: measurement of the past, not a promise about the future, and the distinction
49
47
  #: is the whole reason this string is published beside the number.
50
48
  ESTIMATE_BASIS = (
51
- f"{RELEASE_RATE_PER_DAY} releases/day, measured over the 30 releases before "
52
- f"this one. An estimate of what has shipped since, not an observation: this "
49
+ f"{RELEASE_RATE_PER_DAY} releases/day, measured over the latest 30 CHANGELOG "
50
+ f"releases. An estimate of what has shipped since, not an observation: this "
53
51
  f"build has no index to read and never claims a newer release exists."
54
52
  )
55
53
 
sourcecode/ris.py CHANGED
@@ -632,6 +632,15 @@ def get_cold_start_context(repo_root: Path) -> dict:
632
632
  "endpoints": endpoints,
633
633
  "hotspots": ris.git_context_snapshot.get("hotspots", []),
634
634
  "validation": _validation,
635
+ # AUD-619-I: the compact snapshot is populated by the command that
636
+ # primed it. Publishing that state explains why a cache warm, a
637
+ # compact root run and an agent root run can expose different
638
+ # sections without pretending they are the same measurement.
639
+ "snapshot_basis": {
640
+ "source": (ris.metadata or {}).get("snapshot_source", "unknown"),
641
+ "partial": bool((ris.metadata or {}).get("partial", False)),
642
+ "confidence": (ris.metadata or {}).get("confidence"),
643
+ },
635
644
  # Fix 3: _cache wrapper for backward compat with CLI schema consumers.
636
645
  # CLI outputs inject _cache via _inject_cache_meta; MCP cold-start path
637
646
  # skips that step, leaving agents that read _cache.cache_source with None.
sourcecode/serializer.py CHANGED
@@ -2967,6 +2967,14 @@ def standard_view(sm: SourceMap, *, include_tree: bool = False) -> dict[str, Any
2967
2967
  #: Bump to invalidate all L1 core caches when the core format changes.
2968
2968
  CORE_VIEW_VERSION: str = "1"
2969
2969
 
2970
+ #: Above this population, eagerly rendering compact + agent + standard after
2971
+ #: the requested answer has already been emitted is more expensive than the
2972
+ #: analysis itself. The exact requested rendering is still persisted in L2;
2973
+ #: L1 receives a state-specific projection marker so the same view is a cache
2974
+ #: hit while another view honestly falls back to analysis. Measured trigger:
2975
+ #: tutorials at 24,074 Java / 35,142 total paths spent minutes in this tail.
2976
+ FULL_CORE_VIEW_FILE_LIMIT = 10_000
2977
+
2970
2978
  #: Fields that standard_view omits from file_tree when no_tree is active.
2971
2979
  _TREE_FIELDS: frozenset[str] = frozenset({"file_tree", "file_paths"})
2972
2980
 
@@ -2999,6 +3007,42 @@ def core_view(sm: SourceMap) -> dict[str, Any]:
2999
3007
  }
3000
3008
 
3001
3009
 
3010
+ def cache_core_view(
3011
+ sm: SourceMap,
3012
+ *,
3013
+ prepared_view: Optional[dict[str, Any]] = None,
3014
+ view_mode: Optional[str] = None,
3015
+ state_key: str = "",
3016
+ ) -> dict[str, Any]:
3017
+ """Build the L1 value without a minutes-long post-answer projection tail.
3018
+
3019
+ Small and medium repositories retain the cross-view L1 cache. For a large
3020
+ compact/agent run, the requested view has already been computed and is
3021
+ written to L2 by the caller; rebuilding all three views only to derive its
3022
+ L2 key delays process exit without improving that invocation. A projection
3023
+ marker gives the rendered view a state-specific ancestry. It deliberately
3024
+ cannot rebuild another view: an L2 miss must analyse rather than manufacture
3025
+ data from a bounded/redacted projection.
3026
+ """
3027
+ can_project = (
3028
+ len(sm.file_paths or []) > FULL_CORE_VIEW_FILE_LIMIT
3029
+ and isinstance(prepared_view, dict)
3030
+ and view_mode in {"compact", "agent"}
3031
+ and bool(state_key)
3032
+ )
3033
+ if not can_project:
3034
+ return core_view(sm)
3035
+ prepared = _deepcopy(prepared_view)
3036
+ prepared.pop("_meta", None)
3037
+ prepared.pop("_cache", None)
3038
+ return {
3039
+ "_cv": CORE_VIEW_VERSION,
3040
+ "_projection_only": True,
3041
+ "_state_key": state_key,
3042
+ f"_{view_mode}": prepared,
3043
+ }
3044
+
3045
+
3002
3046
  def build_view_from_core(
3003
3047
  core: dict[str, Any],
3004
3048
  *,
@@ -3027,7 +3071,11 @@ def build_view_from_core(
3027
3071
  no_tree / tree:
3028
3072
  Control file_tree / file_paths inclusion in standard / compact views.
3029
3073
  """
3030
- if not isinstance(core, dict) or core.get("_cv") != CORE_VIEW_VERSION:
3074
+ if (
3075
+ not isinstance(core, dict)
3076
+ or core.get("_cv") != CORE_VIEW_VERSION
3077
+ or core.get("_projection_only") is True
3078
+ ):
3031
3079
  return None # stale or unknown core format → full re-analysis
3032
3080
 
3033
3081
  if agent:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 5.9.14
3
+ Version: 5.9.16
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  Project-URL: Homepage, https://github.com/HarounDominique/sourcecode
6
6
  Project-URL: Repository, https://github.com/HarounDominique/sourcecode
@@ -59,7 +59,7 @@ Description-Content-Type: text/markdown
59
59
  > question. All analysis is local and deterministic; see [Privacy](#configuration--privacy)
60
60
  > before inspecting a repository whose telemetry policy you need to control.
61
61
 
62
- ![Version](https://img.shields.io/badge/version-5.9.14-blue)
62
+ ![Version](https://img.shields.io/badge/version-5.9.16-blue)
63
63
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
64
64
 
65
65
  Copyright (c) 2026 Dominique Haroun. All rights reserved. ASK Engine 5.7.0 and
@@ -196,7 +196,7 @@ set `ASK_MAX_RSS_MB=<megabytes>`; if the process exceeds it, ASK refuses the
196
196
  answer with `MEMORY_TOO_LARGE`, including the observed peak and limit. Leave it
197
197
  unset, or set it to `0`, to disable the ceiling explicitly.
198
198
 
199
- **Current performance status.** Release 5.9.14 retains the shared Canonical IR
199
+ **Current performance status.** Release 5.9.16 retains the shared Canonical IR
200
200
  path used by `validation` and now passes that same CIR into labelled `data-exposure`
201
201
  runs; the no-label path remains parse-free. These are implementation properties,
202
202
  not duration promises. It also removes two repeated walks of the same file tree in
@@ -233,7 +233,7 @@ in `overlapping_paths`; a missing or non-directory path is rejected as structure
233
233
  invalid input rather than treated as an empty repository. This prevents silent
234
234
  entitlement errors in CI and does not change per-repository gates or billing.
235
235
 
236
- Release 5.9.14 also carries the boundary-contract hardening: missing consumer repositories,
236
+ Release 5.9.16 also carries the boundary-contract hardening: missing consumer repositories,
237
237
  malformed CVE feeds and unresolved `pack gate --since` refs fail as structured
238
238
  input errors; duplicate same-commit baselines are never overwritten. `posture
239
239
  --diff` validates unknown profiles when the repository declares profiles. In
@@ -266,7 +266,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
266
266
  # pip / pipx
267
267
  pipx install sourcecode # or: pip install sourcecode
268
268
 
269
- ask version # ask 5.9.14 — and, on a build that has aged,
269
+ ask version # ask 5.9.16 — and, on a build that has aged,
270
270
  # how many releases have probably shipped since
271
271
  ```
272
272
 
@@ -1,6 +1,6 @@
1
- sourcecode/__init__.py,sha256=2ypO1p_pWELM3-rWz_uPIInUNDExNXgtsVluWWQP9D4,309
1
+ sourcecode/__init__.py,sha256=SnrVj4-zIsDEcbu3TSGMkFV4dWPA5JrfAgKZpkvLrJU,309
2
2
  sourcecode/__main__.py,sha256=qDFVkobwsdZrX0B3stiRqsUNivVpybKO-egzAwCW4xw,165
3
- sourcecode/_build_commit.py,sha256=v-XBk0k40s80RavYharbWhR895P3tNcLExDV-KoYaYg,11452
3
+ sourcecode/_build_commit.py,sha256=g0cw5Tdbh8qPyb-ktcpS_gFVMVOOQ7K-0wqScmP2LBg,11452
4
4
  sourcecode/access_drift.py,sha256=uYD8ipiEAaTnkz0IhG6ujU_KI3LghdY-6ycurs-BZnc,9355
5
5
  sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
6
6
  sourcecode/analysis_budget.py,sha256=ZuNEboPF8A6GK-_V7WNt92ezgriga7_Rbr-vvSN4UEE,4183
@@ -23,7 +23,7 @@ sourcecode/build_modules.py,sha256=rsT8reXoeOGztX28SNwem3yGdBhAC5VJx9RL2vRp_d0,1
23
23
  sourcecode/build_stamp.py,sha256=YWlnT8CEFzMre5pacRQkc244BfZNG5owySIyP9Dq4pw,7677
24
24
  sourcecode/bundled_docs.py,sha256=KymEkB7P8oeg0-PjcD8eRSDhqQbwQxDUbHv1i00LQOc,2663
25
25
  sourcecode/c4_render.py,sha256=kpCreVI3bRMkciQOrwl59LpyVDwP9MA5aVNHdWvbXlk,10157
26
- sourcecode/cache.py,sha256=lgaoGUwzwN43kJH6AELiP_j-gl4Pnzq8MsVBOvaIWms,49293
26
+ sourcecode/cache.py,sha256=mEvE4E59esJQMeNwOWwCgRN1llFU0XuMSlmlaYm_XnY,50746
27
27
  sourcecode/cache_model.py,sha256=UpyzW3Dq72N3-qDsb5CZ-vmKj-TbbQn4j-_pA92UMaI,75911
28
28
  sourcecode/cache_observation.py,sha256=5E2BeqiYE5wBCbvDglkc5A9fTNhXkT_OhcDuuCqRu4Q,5622
29
29
  sourcecode/call_surface.py,sha256=fiqYfHooxN1fX9oQoysq1LS3LoZcobhyUNjAGEZKpwk,4148
@@ -35,7 +35,8 @@ sourcecode/change_plan.py,sha256=drAoDsqFplutCLc6Y0xue7sYkTls0SpYh7aOXvttv3I,129
35
35
  sourcecode/ci_output.py,sha256=X4NRokWWMRF9Ex8OQrEO2g04GzJ00GDP5zYIoqI9YCc,4625
36
36
  sourcecode/cir_graphs.py,sha256=RytqSv1WF8kdlfXj2fPQz16LY4akS2kyThbdWL8beCQ,14835
37
37
  sourcecode/classifier.py,sha256=ft1RGPIChvO84q2QMrEH93x1J15AXNWMh26lnxvoRao,26476
38
- sourcecode/cli.py,sha256=Wm31fCzbSZUZ7ULMBGg4JpV7gQ4m8byn5nWNYirREDg,951186
38
+ sourcecode/cli.py,sha256=WEgaUcqa6B5nGZou38M1db9uk1cbgJjWnAJRz1m0tOs,954978
39
+ sourcecode/click_runtime.py,sha256=K2cUdzFhbd6uEUpucWptXE89_z-ZNZGsqgd-34usUXk,1890
39
40
  sourcecode/client_calls.py,sha256=YOPnfRZzRyqmdHvXSVeiunW8cuiuCuWXacNIQRgKMCw,22640
40
41
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
41
42
  sourcecode/compare.py,sha256=Lnfmm9I3k0H1jP1tV6ZJ-R7znjQPLPLCe5TzV1Jb_PM,15323
@@ -70,7 +71,7 @@ sourcecode/endpoint_literals.py,sha256=Qf4gTZzvNSFDGDuOvF0YRL9NvaYKKj24klhR5LIOa
70
71
  sourcecode/endpoint_metrics.py,sha256=sLSLUIgiIyvNdOynaxVgHd9SjRF_DbN3QxevaquykaU,2840
71
72
  sourcecode/entrypoint_classifier.py,sha256=jhTYlyqDJH2AtdEcLVaRU3lYRTJuF8DkxVzl4-W3zWE,5322
72
73
  sourcecode/env_analyzer.py,sha256=tU2CcW-Lkl__60DMd4ibYnXnxfmPbV5Z9GrToSWLdSA,22029
73
- sourcecode/envelope.py,sha256=rISj4NXJZTX0scP7O01fmxl6xnCr_D_c482LkpPzVc0,17407
74
+ sourcecode/envelope.py,sha256=GjmQ5l03QRirZ7qmrjk67t3QFY--pV7hNflpwJcKPCs,17426
74
75
  sourcecode/environment_resolution.py,sha256=e86HT3RNbIIUIRChvz1Pu9Um81UvYu22BkpHNnwbUQI,30650
75
76
  sourcecode/error_schema.py,sha256=DhxsIvpzAbEzluWFE4IWXYRyccECcJkq2mT2KJB5Tew,3485
76
77
  sourcecode/evidence_provider.py,sha256=GSSL44JEaouO5AHks2sB3d1YvC9xIKIld1yBYxZpXxo,4277
@@ -87,7 +88,7 @@ sourcecode/fqn_utils.py,sha256=XLU7zDkNBXz_RZkIUNfpPmp1nekWtqP-fxV92tDV1vg,2158
87
88
  sourcecode/functional_routes.py,sha256=KXMrDoBwj_GC80w10FvgA3zddIGE1iW9KjpDtCllSSs,16813
88
89
  sourcecode/gate_anchors.py,sha256=26v8wUtzOk1K1Un5uXdUxAu20mqSPX3jo82zuM05ByE,2066
89
90
  sourcecode/git_analyzer.py,sha256=irvEwddbd5_tbA_eYUZebf4mH7_nFhpjwaWAV1f7jUY,15755
90
- sourcecode/git_checkout.py,sha256=Vnv2OERMa5lUHx57dmefvEMfOzu3f72_RPnLv9zEBuo,6009
91
+ sourcecode/git_checkout.py,sha256=pYLQBMKHh43crR_XNRXux86pJ_kqS_mgnXlIsz7ItcI,6024
91
92
  sourcecode/github_diff.py,sha256=H1woYKuCjb6S9sofBPpMlOpn0L4LoVxuVP7ddnhsNaA,3848
92
93
  sourcecode/graph_analyzer.py,sha256=Gw2MzLp0P990TGx6D6Ie-WIPnjWVE6X6dNBQnoM9xPU,65857
93
94
  sourcecode/graph_evidence.py,sha256=rENNsYRZeNstX_ExNCLlbHJAruFQwxo5d00x6wO3xwI,15030
@@ -97,7 +98,7 @@ sourcecode/integration_coordinates.py,sha256=rvuApnS7WdRUbeQLg_oKj-Gi0koO2RKdw6H
97
98
  sourcecode/intent_router.py,sha256=hC_o0VYfSjzSXecFkpiD7vzFgvt44YAw6E_jrMeGiZ4,15555
98
99
  sourcecode/jdk_exports.py,sha256=fCrlwNAXUT9gge_joq6kMnY3zJxYB2pxqy-0w3o3MJI,874
99
100
  sourcecode/license.py,sha256=eIEuIVQXywl_S9LOIACOEpJ4M5RyaPvWrqg_jSNOrXA,31088
100
- sourcecode/manifest_cache.py,sha256=dUIdOtbRBYWFTFAyY8sds0t89NfrSahOFSjYe8159KI,3864
101
+ sourcecode/manifest_cache.py,sha256=qoBn7hPeaMjLHi3higfxsjo7oS7noommRqDnpKP0Ya8,4124
101
102
  sourcecode/mcp_nudge.py,sha256=beNjfyb7z09T1rnS4t6Q4qfbLzZQNaP6ddCA4XpmV0s,3256
102
103
  sourcecode/metrics_analyzer.py,sha256=gLoRWKygF18jLgwsqmGXSWopw-f5iO1VbM7Jjdif6ag,22809
103
104
  sourcecode/migrate_check.py,sha256=BJj_nTnZ0fAIh5X_buL9SR2i5YdilQR3a2mkTgJFwJc,171075
@@ -108,12 +109,12 @@ sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk
108
109
  sourcecode/openrewrite_recipe.py,sha256=0pS-61falE6rSfkos2MPnmoDamvoeTT-8Kmj7smj_DI,12987
109
110
  sourcecode/output_bounds.py,sha256=BvaRQY5nuytgfNUJHyO7JDLOHuSy0vdjqasISxmxLxI,15516
110
111
  sourcecode/output_budget.py,sha256=AHwxnfToUk9a3MJdcq0WDUiFbyNla9VWxdE0pC7UWfY,15617
111
- sourcecode/output_ceiling.py,sha256=N6S3GOAiVeJvSzZktPcnR3FO-XWgfOPhG3nETDD6cY4,34797
112
+ sourcecode/output_ceiling.py,sha256=dY007vP4V3T9AI09EIz_W36A3FWARQiege_BZ72uMs0,34757
112
113
  sourcecode/output_encoding.py,sha256=OxiZQtr7uWBDUtblNQviNHsTH1Xo9AZEpsegPlQWKFk,2139
113
114
  sourcecode/output_sink.py,sha256=Eya-PnXxqc3RGulJoLhuC9LUF73SZD0aO5O2B0xAMXU,8762
114
115
  sourcecode/pack_manifest.py,sha256=Qm6et6SQsFCLc-HJ-Uhz1XlcOa77ZDHgks4JVDEENss,20820
115
- sourcecode/packs.py,sha256=_f3bRzGIy_Cv2cSUMSco3e6nKxJbApT47PUYMEZwZEw,49506
116
- sourcecode/parallel.py,sha256=bf4NPQvit7ZHHFyIh1NTH6LbGarIGqgVUabWGqHgj-s,11165
116
+ sourcecode/packs.py,sha256=vfbX6C0Qg1nlOdWdsz14NUzrKNeumIu7mbpgFV-BZek,50750
117
+ sourcecode/parallel.py,sha256=y9-LuB6fi_zRsS_yb7jEw8CMg3x1ib6eLTw5iwIC2_Q,13435
117
118
  sourcecode/parse_cache.py,sha256=BaPzh-KygmY4OpJQm2YGh6PWztYsj58vHWTu2eSF4s8,38769
118
119
  sourcecode/partial_contract.py,sha256=FZUBACkClvRAmPeYVjxNWeQmP6Y22KW4fPeeTfmT80A,5951
119
120
  sourcecode/path_admission.py,sha256=5IBfmHvOrSwrVLlXXwj9KAEg-ciVaHYHjcpAS8mriPg,5898
@@ -138,7 +139,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
138
139
  sourcecode/reference_facts.py,sha256=mx7H-Ax5y8sKAQHrnJCXlgjkUzqZtQTByv9Zy2lxKQQ,15500
139
140
  sourcecode/regress.py,sha256=6rlFP2rsIzVKAA0spJ66To528GyBtnRI2Zg9vAGq8kQ,13264
140
141
  sourcecode/regulatory_pack.py,sha256=xVWd8hYg6MjGfkFP8UWcYeLmNMxEk8k2uSbTcnGMbhg,3421
141
- sourcecode/release_info.py,sha256=ZGxRGRPqrY_tvByTsMTOlF6EiOQYhvKGs5AW7VIQ2jE,6064
142
+ sourcecode/release_info.py,sha256=YKKYdufxyP8x0b4RvmWmnR-Rcm_8lCHn0D-x_BI1irA,5918
142
143
  sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
143
144
  sourcecode/remediation_diff.py,sha256=fGYqPmQF7bO54L8ivztX5PgmW4fVu54gNcG6OEQ192U,9730
144
145
  sourcecode/remediation_flow.py,sha256=OFlrNCD2dKw1WlX5cHqVl1yrQZHi2TjCThH8Q3X1PwI,10294
@@ -148,7 +149,7 @@ sourcecode/rename_refactor.py,sha256=oASZIf7TxcOBy3RQ0FuOe-4HlNW_JOu9RW9JqCxGNBE
148
149
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
149
150
  sourcecode/repository_ir.py,sha256=xW3ahhhejolAaQL-EMEjOPYmrxhEx5S_3BNGxTEX56U,523074
150
151
  sourcecode/resource_budget.py,sha256=Bo-XMvVK9XyHY_S06fVirZrhXgwmnr3XI38-Li55NWY,7767
151
- sourcecode/ris.py,sha256=zlns4esv1tK8VDIYzOzRmw5yM6VLx7oxaPNB_kN3it4,28617
152
+ sourcecode/ris.py,sha256=HsLVHZfseEHNqMyxrj8SvrzUK7jE_85lsWjHtsEw9ik,29196
152
153
  sourcecode/risk.py,sha256=98MhXPM4unAJZFTKKEeAbEjLq4NZ0Wy-66AHQfA3x7c,102506
153
154
  sourcecode/risk_model.py,sha256=RoC-G15qZP5BLEwwmcr4eJ0APgvIbh1WTqhTVT-5YFE,10895
154
155
  sourcecode/rule_catalog.py,sha256=QCx6ToFTMCtDt5uuX1uBMLuw3b9Cd92gGRsX34kud4U,5328
@@ -172,7 +173,7 @@ sourcecode/semantic_analyzer.py,sha256=xGwQvhU9xeOlZ4N-TPXFZT8HBHx4Rg0nPwu8nDFl0
172
173
  sourcecode/semantic_impact_engine.py,sha256=t09IirGC3JjQDy33JZd1_WKzQVKXkoNl3-XEUr5kjis,20563
173
174
  sourcecode/semantic_integration_engine.py,sha256=ZjyKJKOj-adScWlYBuWx8Ee_P0F4_OImUJVnCYgopuU,20952
174
175
  sourcecode/semantic_services.py,sha256=nbUuPv-F01USTt_9CHT8iy_ucCIw3fz4W3Aquea_pd4,10782
175
- sourcecode/serializer.py,sha256=T_AaYQniFFXald2vq1mXjfXz2bh7co6xNUqRRhwIUXc,157952
176
+ sourcecode/serializer.py,sha256=zcwsbkC84XKhoDEMv5YR4pP_CnH9iSB1_GlUUvfF8es,159830
176
177
  sourcecode/servlet_surface.py,sha256=CeiebkxLXKojUQxIf7HV7tdQAwLM32ggDUwBnVQIjdo,13499
177
178
  sourcecode/source_text.py,sha256=R52gmSljz7L6YVPbixv5W-LOZ5eKUVvSUP3S-QH_IWc,9047
178
179
  sourcecode/spring_event_topology.py,sha256=gm_1J7Dj9JQ1tXnkOzIfjPNFxPLmSN56Vcj8RisqB5I,18093
@@ -234,7 +235,7 @@ sourcecode/facts/registry.json,sha256=_N3iy2pfYfljtWtcwjKFZwYPNuOPJE8Evxy5WpUDG1
234
235
  sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
235
236
  sourcecode/mcp/dependency_import.py,sha256=wfvw1rmDE3n2mHDj-yHnCuy9HZ7TtPqsHG2BU-Scl1k,4146
236
237
  sourcecode/mcp/orchestrator.py,sha256=RUzx2uRfwk-rnEy1RKeXjTBPfUaQiBldo-b2FryL6yI,36826
237
- sourcecode/mcp/registry.py,sha256=kydzpUGGKckWM_F_GeGkWG1Z_qU3R8mZSbBWYKtDewU,69326
238
+ sourcecode/mcp/registry.py,sha256=xewjyZDr2A8Fdu1SUOT1QXJ86mww-pbqL4EFpXuRklo,69393
238
239
  sourcecode/mcp/runner.py,sha256=dRvI0xS2wa8DH0diYjJiAoaGeVdtf389PekWwwh8wRI,3083
239
240
  sourcecode/mcp/server.py,sha256=L3xxLaG-bLe3Asr4ZESiEIYldhvKHkfpCMoucndvLvY,76070
240
241
  sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
@@ -276,10 +277,10 @@ sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec
276
277
  sourcecode/_docs/DEFECT-LEDGER.md,sha256=Kh7hCbLaIgvm6qkr5atYhNmRXDy0kStA9rhRrcN-ev4,1161144
277
278
  sourcecode/_docs/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
278
279
  sourcecode/_docs/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
279
- sourcecode/_docs/USER_GUIDE.md,sha256=m1ljgTaPbWZSVmbO_A28IhG9x8fi1x2zNfkjcc1vGyU,106936
280
- sourcecode-5.9.14.dist-info/METADATA,sha256=D6CT_mTT_Z12bC_KDvB9ViVNTV0elbuoPlx28WIQMCI,65992
281
- sourcecode-5.9.14.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
282
- sourcecode-5.9.14.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
283
- sourcecode-5.9.14.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
284
- sourcecode-5.9.14.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
285
- sourcecode-5.9.14.dist-info/RECORD,,
280
+ sourcecode/_docs/USER_GUIDE.md,sha256=JvZI32C4JOWwea0DJUg8DjUeD7NfV3_0al1Hv650l0A,106936
281
+ sourcecode-5.9.16.dist-info/METADATA,sha256=9jih_cHlkq2w6C6pqv8M9udcfvMJZrZoL3oa8uQDypc,65992
282
+ sourcecode-5.9.16.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
283
+ sourcecode-5.9.16.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
284
+ sourcecode-5.9.16.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
285
+ sourcecode-5.9.16.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
286
+ sourcecode-5.9.16.dist-info/RECORD,,