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 +1 -1
- sourcecode/_build_commit.py +1 -1
- sourcecode/_docs/USER_GUIDE.md +4 -4
- sourcecode/cache.py +39 -3
- sourcecode/cli.py +89 -19
- sourcecode/click_runtime.py +61 -0
- sourcecode/envelope.py +1 -1
- sourcecode/git_checkout.py +1 -1
- sourcecode/manifest_cache.py +11 -1
- sourcecode/mcp/registry.py +4 -2
- sourcecode/output_ceiling.py +2 -5
- sourcecode/packs.py +36 -5
- sourcecode/parallel.py +86 -32
- sourcecode/release_info.py +9 -11
- sourcecode/ris.py +9 -0
- sourcecode/serializer.py +49 -1
- {sourcecode-5.9.14.dist-info → sourcecode-5.9.16.dist-info}/METADATA +5 -5
- {sourcecode-5.9.14.dist-info → sourcecode-5.9.16.dist-info}/RECORD +22 -21
- {sourcecode-5.9.14.dist-info → sourcecode-5.9.16.dist-info}/WHEEL +0 -0
- {sourcecode-5.9.14.dist-info → sourcecode-5.9.16.dist-info}/entry_points.txt +0 -0
- {sourcecode-5.9.14.dist-info → sourcecode-5.9.16.dist-info}/licenses/LICENSE +0 -0
- {sourcecode-5.9.14.dist-info → sourcecode-5.9.16.dist-info}/licenses/NOTICE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/_build_commit.py
CHANGED
|
@@ -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 = '
|
|
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}
|
sourcecode/_docs/USER_GUIDE.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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) ->
|
|
1076
|
-
return max(p
|
|
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=
|
|
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
|
-
|
|
1170
|
+
children = command_children(node)
|
|
1171
|
+
if token.startswith("-") or not children:
|
|
1170
1172
|
break
|
|
1171
|
-
child =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3908
|
-
|
|
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
|
-
|
|
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
|
sourcecode/git_checkout.py
CHANGED
sourcecode/manifest_cache.py
CHANGED
|
@@ -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.
|
|
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
|
sourcecode/mcp/registry.py
CHANGED
|
@@ -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
|
-
|
|
195
|
-
|
|
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, ())
|
sourcecode/output_ceiling.py
CHANGED
|
@@ -226,12 +226,9 @@ def estimate_tokens(content: str) -> int:
|
|
|
226
226
|
|
|
227
227
|
|
|
228
228
|
def _click_context() -> Any:
|
|
229
|
-
|
|
230
|
-
import click
|
|
229
|
+
from sourcecode.click_runtime import current_context
|
|
231
230
|
|
|
232
|
-
|
|
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
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
207
|
+
rel_paths: Sequence[str],
|
|
151
208
|
*,
|
|
152
209
|
capture_sig: str,
|
|
153
|
-
extra_capture:
|
|
154
|
-
jobs:
|
|
155
|
-
progress:
|
|
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
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
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"]:
|
sourcecode/release_info.py
CHANGED
|
@@ -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
|
+
RELEASE_DATE = "2026-08-31"
|
|
31
31
|
|
|
32
|
-
#: Releases per day, measured over the 30 releases
|
|
33
|
-
#: computation the test re-runs
|
|
34
|
-
#:
|
|
35
|
-
#:
|
|
36
|
-
|
|
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
|
|
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
|
|
52
|
-
f"
|
|
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
|
|
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.
|
|
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
|
-

|
|
63
63
|

|
|
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.
|
|
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.
|
|
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.
|
|
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=
|
|
1
|
+
sourcecode/__init__.py,sha256=SnrVj4-zIsDEcbu3TSGMkFV4dWPA5JrfAgKZpkvLrJU,309
|
|
2
2
|
sourcecode/__main__.py,sha256=qDFVkobwsdZrX0B3stiRqsUNivVpybKO-egzAwCW4xw,165
|
|
3
|
-
sourcecode/_build_commit.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
116
|
-
sourcecode/parallel.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
280
|
-
sourcecode-5.9.
|
|
281
|
-
sourcecode-5.9.
|
|
282
|
-
sourcecode-5.9.
|
|
283
|
-
sourcecode-5.9.
|
|
284
|
-
sourcecode-5.9.
|
|
285
|
-
sourcecode-5.9.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|