sourcecode 5.9.18__py3-none-any.whl → 5.9.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sourcecode might be problematic. Click here for more details.
- sourcecode/__init__.py +1 -1
- sourcecode/_build_commit.py +1 -1
- sourcecode/_docs/DEFECT-LEDGER.md +4 -2
- sourcecode/_docs/USER_GUIDE.md +13 -8
- sourcecode/canonical_ir.py +11 -4
- sourcecode/cli.py +13 -11
- sourcecode/context_graph.py +24 -18
- sourcecode/git_checkout.py +8 -1
- sourcecode/jvm_inventory.py +294 -0
- sourcecode/migrate_check.py +7 -0
- sourcecode/parallel.py +3 -11
- sourcecode/release_info.py +1 -1
- sourcecode/repository_ir.py +93 -20
- sourcecode/risk.py +3 -2
- sourcecode/schema_registry.py +2 -0
- {sourcecode-5.9.18.dist-info → sourcecode-5.9.22.dist-info}/METADATA +19 -10
- {sourcecode-5.9.18.dist-info → sourcecode-5.9.22.dist-info}/RECORD +21 -20
- {sourcecode-5.9.18.dist-info → sourcecode-5.9.22.dist-info}/WHEEL +0 -0
- {sourcecode-5.9.18.dist-info → sourcecode-5.9.22.dist-info}/entry_points.txt +0 -0
- {sourcecode-5.9.18.dist-info → sourcecode-5.9.22.dist-info}/licenses/LICENSE +0 -0
- {sourcecode-5.9.18.dist-info → sourcecode-5.9.22.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 = '64c8b3e429eadd7a12d34eb1e5eefa0c08e430e4'
|
|
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'}, '`AUD-620-COMMAND-SURFACE`': {'commit': '', 'status': 'uncited'}, '`AUD-620-REVIEW-PR`': {'commit': '', 'status': 'uncited'}, '`AUD-621-REVIEW-PR`': {'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': 165, 'present': 91, 'absent': 0, 'unresolved': 0, 'uncited': 74, 'unknown': 0}
|
|
@@ -16,7 +16,7 @@ The facts these rows are keyed to are published: `ask schema facts-v1` prints th
|
|
|
16
16
|
|
|
17
17
|
## Current Synchronization — External Audit `AUD-621`
|
|
18
18
|
|
|
19
|
-
**Release `5.9.
|
|
19
|
+
**Release `5.9.19` remediation.** The stderr-decoding fix for `AUD-621-PACK-STDERR` remains in code, and the distinct `pack gate --since` crash found by the post-remediation re-audit is corrected with a Python 3.9–3.11-compatible tar extraction path. `AUD-621-R` has a second Windows barrier at process-pool construction, complementing the sequential fallback. The broader findings remain open until their required native Windows evidence is recorded; `AUD-621-CONTEXT` and `AUD-621-WIN-PROC` are unchanged.
|
|
20
20
|
|
|
21
21
|
**Re-audit of release `5.9.17`, received 2026-08-31.** Four independent
|
|
22
22
|
Windows measurements overturn the apparent partial recovery of the shared
|
|
@@ -31,7 +31,9 @@ The audit also isolates a new, high-confidence Windows defect in `pack gate`:
|
|
|
31
31
|
the `_forward_stderr` thread decodes child output using the active codepage,
|
|
32
32
|
raises `UnicodeDecodeError` on UTF-8 bytes, then stops draining the pipe. The
|
|
33
33
|
child can block once that pipe fills. Two independent forks captured the same
|
|
34
|
-
traceback.
|
|
34
|
+
traceback. A separate re-audit then found that `pack gate --since` can still
|
|
35
|
+
fail earlier in `git_checkout.py` under Python 3.10, so the stderr fix is
|
|
36
|
+
necessary but not sufficient. The full evidence is
|
|
35
37
|
[`AUDIT-2026-08-31-EXTERNAL-5.9.17.md`](AUDIT-2026-08-31-EXTERNAL-5.9.17.md).
|
|
36
38
|
|
|
37
39
|
| ID | Severity | Status | Evidence and required closure |
|
sourcecode/_docs/USER_GUIDE.md
CHANGED
|
@@ -46,16 +46,18 @@ 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.19, `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
|
|
56
|
-
|
|
55
|
+
the two pack fixes were tracked in release 5.9.18 as intended changes, and
|
|
56
|
+
release 5.9.19 adds the tarfile compatibility correction found by the later
|
|
57
|
+
re-audit. Release-artifact verification remains for detached execution and pack
|
|
58
|
+
evidence, and native Windows reliability evidence is still required.
|
|
57
59
|
|
|
58
|
-
### Reliability contracts in 5.9.
|
|
60
|
+
### Reliability contracts in 5.9.19
|
|
59
61
|
|
|
60
62
|
**Failures must be visible.** `risk`, `verify`, `audit-report` and
|
|
61
63
|
`migrate-check` turn unexpected analysis failures into structured
|
|
@@ -67,8 +69,11 @@ closing platform-specific findings.
|
|
|
67
69
|
**Current Windows limitation.** The `5.9.17` external audit reproduced the
|
|
68
70
|
shared worker-start failure in `risk`, `verify`, `audit-report`, `spring-audit`,
|
|
69
71
|
`migrate-check` and `migrate-apply`. Do not use these commands as a production
|
|
70
|
-
CI gate on Windows until the native matrix closes them.
|
|
71
|
-
|
|
72
|
+
CI gate on Windows until the native matrix closes them. A 2026-08-31 re-audit
|
|
73
|
+
also found a separate `pack gate --since` crash in the git-ref materialization
|
|
74
|
+
path when the subprocess resolves to Python 3.10 and hits the Python 3.12-only
|
|
75
|
+
`TarFile.extract(..., filter="data")` API. Release 5.9.19 adds a compatible
|
|
76
|
+
fallback; the command still requires native repeat evidence. See the
|
|
72
77
|
[development roadmap](DEVELOPMENT-ROADMAP.md#external-audit-intake--aud-621-re-audit-release-5917).
|
|
73
78
|
|
|
74
79
|
**A deadline the root analysis honours.** `ASK_MAX_ANALYSIS_SECONDS=<seconds>` now
|
|
@@ -274,7 +279,7 @@ pipx install sourcecode # isolated install, no venv needed
|
|
|
274
279
|
|
|
275
280
|
# Verify
|
|
276
281
|
ask version
|
|
277
|
-
# ask 5.9.
|
|
282
|
+
# ask 5.9.19
|
|
278
283
|
```
|
|
279
284
|
|
|
280
285
|
Requires Python 3.9+.
|
|
@@ -327,7 +332,7 @@ value in field use, and they are the ones to learn first:
|
|
|
327
332
|
| `ask posture . --diff dev:prod` | The only command that answers *"what changes about effective access between two profile sets, across every endpoint at once"*. Experimental, and the most differentiated capability in the product. |
|
|
328
333
|
| `ask endpoints .` | Every REST endpoint with its **effective path** (context-path + `spring.mvc.servlet.path` resolved), inferred security policy and per-endpoint confidence. Turns "audit the API" into a list. |
|
|
329
334
|
| `ask spring-audit .` | Transactional anomalies that a compiler cannot see — `@Transactional` on a private method is a silent CGLIB no-op — plus the security surface. |
|
|
330
|
-
| `ask migrate-check . --compact` | Boot 2→3 readiness plus Java LTS/licensing inventory: blockers, effort, target JDK evidence and explicit licensing-review signals. |
|
|
335
|
+
| `ask migrate-check . --compact` | Boot 2→3 readiness plus Java LTS/licensing and local JVM ecosystem inventory: blockers, effort, target JDK evidence, framework/runtime signals and explicit licensing-review signals. |
|
|
331
336
|
|
|
332
337
|
Everything else is either a projection of the same model (`impact`, `export`, `explain`) or an
|
|
333
338
|
agent-facing packaging of it (`--compact`, `--agent`, `prepare-context`). The full surface is
|
sourcecode/canonical_ir.py
CHANGED
|
@@ -528,10 +528,17 @@ def ir_dict_to_canonical(
|
|
|
528
528
|
files = sorted(file_set)
|
|
529
529
|
|
|
530
530
|
# Sorted call graph edges — stable ordering: from, type, to
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
)
|
|
531
|
+
# The raw graph is owned by this CIR conversion and its edge order is not
|
|
532
|
+
# observable until after conversion. Sort in place instead of creating a
|
|
533
|
+
# second repository-sized list during the raw/CIR overlap peak.
|
|
534
|
+
if isinstance(edges, list):
|
|
535
|
+
edges.sort(key=lambda e: (e.get("from", ""), e.get("type", ""), e.get("to", "")))
|
|
536
|
+
call_graph = edges
|
|
537
|
+
else:
|
|
538
|
+
call_graph = sorted(
|
|
539
|
+
edges,
|
|
540
|
+
key=lambda e: (e.get("from", ""), e.get("type", ""), e.get("to", "")),
|
|
541
|
+
)
|
|
535
542
|
|
|
536
543
|
# Dependency edges (structural subset of call_graph)
|
|
537
544
|
_dep_types = frozenset({"imports", "extends", "implements", "injects"})
|
sourcecode/cli.py
CHANGED
|
@@ -4404,7 +4404,9 @@ app = typer.Typer(
|
|
|
4404
4404
|
name="ask",
|
|
4405
4405
|
help=_HELP,
|
|
4406
4406
|
add_completion=False,
|
|
4407
|
-
|
|
4407
|
+
# Rich's Windows help renderer can drop options from long command panels;
|
|
4408
|
+
# plain Click help keeps the complete option contract visible there.
|
|
4409
|
+
rich_markup_mode=None if os.name == "nt" else "rich",
|
|
4408
4410
|
no_args_is_help=False,
|
|
4409
4411
|
)
|
|
4410
4412
|
|
|
@@ -7531,6 +7533,7 @@ def prepare_context_cmd(
|
|
|
7531
7533
|
Path("."),
|
|
7532
7534
|
help="Repository path to analyze (default: current directory)",
|
|
7533
7535
|
),
|
|
7536
|
+
jobs: Optional[int] = _jobs_option(),
|
|
7534
7537
|
since: Optional[str] = typer.Option(
|
|
7535
7538
|
None,
|
|
7536
7539
|
"--since",
|
|
@@ -7605,7 +7608,6 @@ def prepare_context_cmd(
|
|
|
7605
7608
|
),
|
|
7606
7609
|
),
|
|
7607
7610
|
progress_mode: Optional[str] = _progress_option(),
|
|
7608
|
-
jobs: Optional[int] = _jobs_option(),
|
|
7609
7611
|
) -> None:
|
|
7610
7612
|
"""Task-specific context for AI coding agents.
|
|
7611
7613
|
|
|
@@ -8426,6 +8428,7 @@ def repo_ir_cmd(
|
|
|
8426
8428
|
Path("."),
|
|
8427
8429
|
help="Repository root to analyze (default: current directory)",
|
|
8428
8430
|
),
|
|
8431
|
+
jobs: Optional[int] = _jobs_option(),
|
|
8429
8432
|
since: Optional[str] = typer.Option(
|
|
8430
8433
|
None,
|
|
8431
8434
|
"--since",
|
|
@@ -8491,7 +8494,6 @@ def repo_ir_cmd(
|
|
|
8491
8494
|
help="Compress output with gzip. Requires --output. Reduces large IR files by ~70-80%.",
|
|
8492
8495
|
),
|
|
8493
8496
|
progress_mode: Optional[str] = _progress_option(),
|
|
8494
|
-
jobs: Optional[int] = _jobs_option(),
|
|
8495
8497
|
) -> None:
|
|
8496
8498
|
"""Deterministic symbol-level IR for Java repositories.
|
|
8497
8499
|
|
|
@@ -9185,6 +9187,7 @@ def endpoints_cmd(
|
|
|
9185
9187
|
Path("."),
|
|
9186
9188
|
help="Repository path to scan for REST endpoints (default: current directory)",
|
|
9187
9189
|
),
|
|
9190
|
+
jobs: Optional[int] = _jobs_option(),
|
|
9188
9191
|
output_path: Optional[Path] = typer.Option(
|
|
9189
9192
|
None, "--output", "-o",
|
|
9190
9193
|
help="Write output to a file instead of stdout.",
|
|
@@ -9251,7 +9254,6 @@ def endpoints_cmd(
|
|
|
9251
9254
|
"than protect).",
|
|
9252
9255
|
),
|
|
9253
9256
|
progress_mode: Optional[str] = _progress_option(),
|
|
9254
|
-
jobs: Optional[int] = _jobs_option(),
|
|
9255
9257
|
) -> None:
|
|
9256
9258
|
"""Extract REST API endpoint surface from Java source files.
|
|
9257
9259
|
|
|
@@ -9806,6 +9808,7 @@ def export_cmd(
|
|
|
9806
9808
|
Path("."),
|
|
9807
9809
|
help="Repository path to export (default: current directory)",
|
|
9808
9810
|
),
|
|
9811
|
+
jobs: Optional[int] = _jobs_option(),
|
|
9809
9812
|
output_path: Optional[Path] = typer.Option(
|
|
9810
9813
|
None, "--output", "-o",
|
|
9811
9814
|
help="Write output to a file instead of stdout.",
|
|
@@ -9848,7 +9851,6 @@ def export_cmd(
|
|
|
9848
9851
|
"written on the diagram whenever it bites.",
|
|
9849
9852
|
),
|
|
9850
9853
|
progress_mode: Optional[str] = _progress_option(),
|
|
9851
|
-
jobs: Optional[int] = _jobs_option(),
|
|
9852
9854
|
) -> None:
|
|
9853
9855
|
"""Export structured, tool-agnostic codebase views for downstream tooling.
|
|
9854
9856
|
|
|
@@ -10015,6 +10017,7 @@ def validation_cmd(
|
|
|
10015
10017
|
Path("."),
|
|
10016
10018
|
help="Repository path to scan for request-body validation (default: current directory)",
|
|
10017
10019
|
),
|
|
10020
|
+
jobs: Optional[int] = _jobs_option(),
|
|
10018
10021
|
output_path: Optional[Path] = typer.Option(
|
|
10019
10022
|
None, "--output", "-o",
|
|
10020
10023
|
help="Write output to a file instead of stdout.",
|
|
@@ -10045,7 +10048,6 @@ def validation_cmd(
|
|
|
10045
10048
|
help=_NO_CACHE_SUBCOMMAND_HELP,
|
|
10046
10049
|
),
|
|
10047
10050
|
progress_mode: Optional[str] = _progress_option(),
|
|
10048
|
-
jobs: Optional[int] = _jobs_option(),
|
|
10049
10051
|
) -> None:
|
|
10050
10052
|
"""Map request-body validation per endpoint (constraints + custom validators).
|
|
10051
10053
|
|
|
@@ -12173,6 +12175,7 @@ def spring_audit_cmd(
|
|
|
12173
12175
|
Path("."),
|
|
12174
12176
|
help="Repository path to audit (default: current directory)",
|
|
12175
12177
|
),
|
|
12178
|
+
jobs: Optional[int] = _jobs_option(),
|
|
12176
12179
|
output_path: Optional[Path] = typer.Option(
|
|
12177
12180
|
None, "--output", "-o",
|
|
12178
12181
|
help="Write output to a file instead of stdout.",
|
|
@@ -12304,7 +12307,6 @@ def spring_audit_cmd(
|
|
|
12304
12307
|
),
|
|
12305
12308
|
detach: bool = _detach_option(),
|
|
12306
12309
|
progress_mode: Optional[str] = _progress_option(),
|
|
12307
|
-
jobs: Optional[int] = _jobs_option(),
|
|
12308
12310
|
) -> None:
|
|
12309
12311
|
"""Spring semantic audit: {{RULE_SUMMARY}}.
|
|
12310
12312
|
|
|
@@ -13483,6 +13485,7 @@ def risk_cmd(
|
|
|
13483
13485
|
Path("."),
|
|
13484
13486
|
help="Repository path (default: current directory).",
|
|
13485
13487
|
),
|
|
13488
|
+
jobs: Optional[int] = _jobs_option(),
|
|
13486
13489
|
limit: int = typer.Option(
|
|
13487
13490
|
50, "--limit", help="How many composed risks to publish (highest first).", min=0,
|
|
13488
13491
|
),
|
|
@@ -13542,7 +13545,6 @@ def risk_cmd(
|
|
|
13542
13545
|
allow_partial: bool = _allow_partial_option(),
|
|
13543
13546
|
detach: bool = _detach_option(),
|
|
13544
13547
|
progress_mode: Optional[str] = _progress_option(),
|
|
13545
|
-
jobs: Optional[int] = _jobs_option(),
|
|
13546
13548
|
) -> None:
|
|
13547
13549
|
"""What each defect actually costs, once reach and access are in it.
|
|
13548
13550
|
|
|
@@ -14796,6 +14798,7 @@ def posture_cmd(
|
|
|
14796
14798
|
Path("."),
|
|
14797
14799
|
help="Repository path (default: current directory).",
|
|
14798
14800
|
),
|
|
14801
|
+
jobs: Optional[int] = _jobs_option(),
|
|
14799
14802
|
profile: Optional[str] = typer.Option(
|
|
14800
14803
|
None,
|
|
14801
14804
|
"--profile",
|
|
@@ -14877,7 +14880,6 @@ def posture_cmd(
|
|
|
14877
14880
|
format: str = _format_option(help="Output format: json or yaml."),
|
|
14878
14881
|
copy: bool = _copy_option(),
|
|
14879
14882
|
progress_mode: Optional[str] = _progress_option(),
|
|
14880
|
-
jobs: Optional[int] = _jobs_option(),
|
|
14881
14883
|
) -> None:
|
|
14882
14884
|
"""Which beans are wired under a profile set — and which cannot be decided.
|
|
14883
14885
|
|
|
@@ -15300,6 +15302,7 @@ def migrate_check_cmd(
|
|
|
15300
15302
|
Path("."),
|
|
15301
15303
|
help="Repository path to scan (default: current directory)",
|
|
15302
15304
|
),
|
|
15305
|
+
jobs: Optional[int] = _jobs_option(),
|
|
15303
15306
|
output_path: Optional[Path] = typer.Option(
|
|
15304
15307
|
None, "--output", "-o",
|
|
15305
15308
|
help="Write output to a file instead of stdout.",
|
|
@@ -15441,7 +15444,6 @@ def migrate_check_cmd(
|
|
|
15441
15444
|
),
|
|
15442
15445
|
detach: bool = _detach_option(),
|
|
15443
15446
|
progress_mode: Optional[str] = _progress_option(),
|
|
15444
|
-
jobs: Optional[int] = _jobs_option(),
|
|
15445
15447
|
) -> None:
|
|
15446
15448
|
"""Spring Boot 2→3 migration readiness + Java LTS/licensing inventory.
|
|
15447
15449
|
|
|
@@ -15847,6 +15849,7 @@ def impact_chain_cmd(
|
|
|
15847
15849
|
Path("."),
|
|
15848
15850
|
help="Repository root (default: current directory)",
|
|
15849
15851
|
),
|
|
15852
|
+
jobs: Optional[int] = _jobs_option(),
|
|
15850
15853
|
depth: int = typer.Option(
|
|
15851
15854
|
4,
|
|
15852
15855
|
"--depth",
|
|
@@ -15886,7 +15889,6 @@ def impact_chain_cmd(
|
|
|
15886
15889
|
),
|
|
15887
15890
|
),
|
|
15888
15891
|
progress_mode: Optional[str] = _progress_option(),
|
|
15889
|
-
jobs: Optional[int] = _jobs_option(),
|
|
15890
15892
|
) -> None:
|
|
15891
15893
|
"""Spring impact-chain: systemic blast radius of a symbol; TX/SEC findings are opt-in.
|
|
15892
15894
|
|
sourcecode/context_graph.py
CHANGED
|
@@ -405,7 +405,7 @@ class ContextGraph:
|
|
|
405
405
|
"""
|
|
406
406
|
|
|
407
407
|
__slots__ = (
|
|
408
|
-
"_cir", "_nodes_by_fqn", "_nodes", "_build_ms",
|
|
408
|
+
"_cir", "_raw_nodes", "_nodes_by_fqn", "_nodes", "_build_ms",
|
|
409
409
|
"_body_index", "_literal_index", "_guard_index", "_span_index",
|
|
410
410
|
"_class_type_index", "_relations_all", "_symbols_sorted",
|
|
411
411
|
"_symbols_by_file", "_relations_by_source",
|
|
@@ -427,11 +427,9 @@ class ContextGraph:
|
|
|
427
427
|
self._class_type_index: Optional[_ClassTypeIndex] = None
|
|
428
428
|
# Index the rich node dicts once (the IR already produced them). This is
|
|
429
429
|
# a lookup index over existing data, not a second copy of the model.
|
|
430
|
-
|
|
431
|
-
self._nodes: tuple[Symbol, ...] =
|
|
432
|
-
|
|
433
|
-
)
|
|
434
|
-
self._nodes_by_fqn: dict[str, Symbol] = {s.fqn: s for s in self._nodes}
|
|
430
|
+
self._raw_nodes = ((cir._raw_ir.get("graph") or {}).get("nodes")) or []
|
|
431
|
+
self._nodes: Optional[tuple[Symbol, ...]] = None
|
|
432
|
+
self._nodes_by_fqn: Optional[dict[str, Symbol]] = None
|
|
435
433
|
# Memoized full projections (the CIR is immutable for this graph's lifetime).
|
|
436
434
|
# `relations()`/`symbols()` are called once PER FILE by per-file consumers such as
|
|
437
435
|
# `_java_contract_from_graph`; without these caches each call re-converted every
|
|
@@ -447,6 +445,13 @@ class ContextGraph:
|
|
|
447
445
|
self._symbols_by_file: Optional[dict[str, list[Symbol]]] = None
|
|
448
446
|
self._relations_by_source: Optional[dict[str, list[Relation]]] = None
|
|
449
447
|
|
|
448
|
+
def _ensure_nodes(self) -> tuple[Symbol, ...]:
|
|
449
|
+
"""Materialize the typed node view only for symbol-oriented queries."""
|
|
450
|
+
if self._nodes is None:
|
|
451
|
+
self._nodes = tuple(_node_to_symbol(n) for n in self._raw_nodes)
|
|
452
|
+
self._nodes_by_fqn = {s.fqn: s for s in self._nodes}
|
|
453
|
+
return self._nodes
|
|
454
|
+
|
|
450
455
|
# -- construction -------------------------------------------------------
|
|
451
456
|
|
|
452
457
|
@classmethod
|
|
@@ -501,7 +506,8 @@ class ContextGraph:
|
|
|
501
506
|
|
|
502
507
|
def symbol(self, fqn: str) -> Optional[Symbol]:
|
|
503
508
|
"""Look up a single symbol by fully-qualified name. O(1)."""
|
|
504
|
-
|
|
509
|
+
self._ensure_nodes()
|
|
510
|
+
return self._nodes_by_fqn.get(fqn) # type: ignore[union-attr]
|
|
505
511
|
|
|
506
512
|
def symbols(
|
|
507
513
|
self,
|
|
@@ -517,8 +523,9 @@ class ContextGraph:
|
|
|
517
523
|
set. Results are sorted by FQN for determinism. This is the primitive
|
|
518
524
|
the role-convenience methods below are thin sugar over.
|
|
519
525
|
"""
|
|
526
|
+
nodes = self._ensure_nodes()
|
|
520
527
|
if self._symbols_sorted is None:
|
|
521
|
-
self._symbols_sorted = tuple(sorted(
|
|
528
|
+
self._symbols_sorted = tuple(sorted(nodes, key=lambda s: s.fqn))
|
|
522
529
|
# Filter over the memoized fqn-sorted set (filtering preserves that order, so the
|
|
523
530
|
# result is identical to sorting after filtering — but the sort happens once).
|
|
524
531
|
return [
|
|
@@ -537,7 +544,7 @@ class ContextGraph:
|
|
|
537
544
|
"""
|
|
538
545
|
if self._symbols_by_file is None:
|
|
539
546
|
if self._symbols_sorted is None:
|
|
540
|
-
self._symbols_sorted = tuple(sorted(self.
|
|
547
|
+
self._symbols_sorted = tuple(sorted(self._ensure_nodes(), key=lambda s: s.fqn))
|
|
541
548
|
idx: dict[str, list[Symbol]] = {}
|
|
542
549
|
for s in self._symbols_sorted:
|
|
543
550
|
idx.setdefault(s.source_file, []).append(s)
|
|
@@ -560,9 +567,7 @@ class ContextGraph:
|
|
|
560
567
|
|
|
561
568
|
def types(self) -> list[Symbol]:
|
|
562
569
|
"""All class/interface/enum/annotation symbols, sorted by FQN."""
|
|
563
|
-
return sorted(
|
|
564
|
-
(s for s in self._nodes if s.is_type), key=lambda s: s.fqn
|
|
565
|
-
)
|
|
570
|
+
return sorted((s for s in self._ensure_nodes() if s.is_type), key=lambda s: s.fqn)
|
|
566
571
|
|
|
567
572
|
def fields_of(self, type_fqn: str) -> list[Symbol]:
|
|
568
573
|
"""Annotated field symbols declared directly on a type (the IR emits a
|
|
@@ -572,7 +577,7 @@ class ContextGraph:
|
|
|
572
577
|
prefix = type_fqn + "."
|
|
573
578
|
out = [
|
|
574
579
|
s
|
|
575
|
-
for s in self.
|
|
580
|
+
for s in self._ensure_nodes()
|
|
576
581
|
if s.kind == "field"
|
|
577
582
|
and s.fqn.startswith(prefix)
|
|
578
583
|
and "." not in s.fqn[len(prefix):]
|
|
@@ -1010,17 +1015,18 @@ class ContextGraph:
|
|
|
1010
1015
|
def metrics(self) -> dict:
|
|
1011
1016
|
"""Structural size + composition of the graph. For Phase 1 measurement;
|
|
1012
1017
|
derived entirely from already-built data."""
|
|
1013
|
-
|
|
1014
|
-
|
|
1018
|
+
nodes = self._ensure_nodes()
|
|
1019
|
+
role_hist = Counter(s.role for s in nodes)
|
|
1020
|
+
kind_hist = Counter(s.kind for s in nodes)
|
|
1015
1021
|
edge_hist = Counter(e.get("type", "") for e in self._cir.call_graph)
|
|
1016
1022
|
grounded = sum(
|
|
1017
1023
|
1
|
|
1018
|
-
for s in
|
|
1024
|
+
for s in nodes
|
|
1019
1025
|
if s.in_degree or s.out_degree
|
|
1020
1026
|
)
|
|
1021
1027
|
return {
|
|
1022
1028
|
"cir_hash": self._cir.cir_hash,
|
|
1023
|
-
"node_count": len(
|
|
1029
|
+
"node_count": len(nodes),
|
|
1024
1030
|
"relation_count": len(self._cir.call_graph),
|
|
1025
1031
|
"endpoint_count": len(self._cir.endpoints),
|
|
1026
1032
|
"grounded_node_count": grounded,
|
|
@@ -1033,7 +1039,7 @@ class ContextGraph:
|
|
|
1033
1039
|
|
|
1034
1040
|
def __repr__(self) -> str: # pragma: no cover - debug aid
|
|
1035
1041
|
return (
|
|
1036
|
-
f"ContextGraph(nodes={len(self.
|
|
1042
|
+
f"ContextGraph(nodes={len(self._ensure_nodes())}, "
|
|
1037
1043
|
f"relations={len(self._cir.call_graph)}, "
|
|
1038
1044
|
f"endpoints={len(self._cir.endpoints)}, "
|
|
1039
1045
|
f"hash={self._cir.cir_hash[:12]})"
|
sourcecode/git_checkout.py
CHANGED
|
@@ -27,6 +27,7 @@ after it borrowed a parent's ref once (C3-27).
|
|
|
27
27
|
from __future__ import annotations
|
|
28
28
|
|
|
29
29
|
import subprocess
|
|
30
|
+
import sys
|
|
30
31
|
import tarfile
|
|
31
32
|
import tempfile
|
|
32
33
|
from contextlib import contextmanager
|
|
@@ -149,4 +150,10 @@ def _extract_safely(tar: tarfile.TarFile, dest: Path) -> None:
|
|
|
149
150
|
resolved = (root / member.name).resolve()
|
|
150
151
|
if not str(resolved).startswith(str(root)):
|
|
151
152
|
continue
|
|
152
|
-
|
|
153
|
+
# `filter` was added in Python 3.12 (PEP 706). The explicit path and
|
|
154
|
+
# link checks above provide the compatibility-safe extraction policy
|
|
155
|
+
# for the Python versions still supported by this project.
|
|
156
|
+
if sys.version_info >= (3, 12):
|
|
157
|
+
tar.extract(member, dest, filter="data")
|
|
158
|
+
else:
|
|
159
|
+
tar.extract(member, dest)
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""Deterministic, local JVM ecosystem evidence for ``migrate-check``.
|
|
2
|
+
|
|
3
|
+
This module deliberately reports observations, not recommendations. It never
|
|
4
|
+
executes a build, resolves dependencies, or accesses a network service.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Iterable, Optional
|
|
13
|
+
|
|
14
|
+
_SKIP_DIRS = {
|
|
15
|
+
".git", ".gradle", ".mvn", ".idea", ".vscode", "build", "target",
|
|
16
|
+
"out", "dist", "bin", "node_modules", "__pycache__", "generated-sources",
|
|
17
|
+
}
|
|
18
|
+
_MAX_FILES = 800
|
|
19
|
+
_MAX_BYTES = 256 * 1024
|
|
20
|
+
_MAX_EVIDENCE = 160
|
|
21
|
+
_MAX_SBOM_COMPONENTS = 400
|
|
22
|
+
_TEXT_NAMES = {
|
|
23
|
+
"pom.xml", "build.gradle", "build.gradle.kts", "gradle.properties",
|
|
24
|
+
"settings.gradle", "settings.gradle.kts", "Dockerfile", "docker-compose.yml",
|
|
25
|
+
"docker-compose.yaml", "Jenkinsfile", ".gitlab-ci.yml", "config.yml",
|
|
26
|
+
".java-version", ".sdkmanrc", "jvm.config",
|
|
27
|
+
}
|
|
28
|
+
_TEXT_SUFFIXES = {
|
|
29
|
+
".java", ".xml", ".gradle", ".kts", ".properties", ".yml", ".yaml",
|
|
30
|
+
".sh", ".bat", ".cmd", ".ps1",
|
|
31
|
+
}
|
|
32
|
+
_SBOM_NAMES = {"bom.json", "bom.cdx.json", "bom.cyclonedx.json", "sbom.json", "sbom.spdx.json"}
|
|
33
|
+
|
|
34
|
+
_FRAMEWORK_COORDINATES = {
|
|
35
|
+
"spring-boot": ("org.springframework.boot", re.compile(r"spring-boot(?:-[\w-]+)?")),
|
|
36
|
+
"spring-framework": ("org.springframework", re.compile(r"spring-(?:core|context|web|beans)")),
|
|
37
|
+
"hibernate": ("org.hibernate", re.compile(r"hibernate-[\w-]+")),
|
|
38
|
+
"jakarta-ee": ("jakarta.platform", re.compile(r"jakarta(?:\.\w+)?")),
|
|
39
|
+
"quarkus": ("io.quarkus", re.compile(r"quarkus(?:-[\w-]+)?")),
|
|
40
|
+
"micronaut": ("io.micronaut", re.compile(r"micronaut(?:-[\w-]+)?")),
|
|
41
|
+
}
|
|
42
|
+
_COORD_RE = re.compile(
|
|
43
|
+
r"(?P<group>org\.springframework(?:\.boot)?|org\.hibernate|jakarta\.platform|"
|
|
44
|
+
r"io\.quarkus(?:\.platform)?|io\.micronaut)[:\"'](?P<artifact>[\w.-]+)"
|
|
45
|
+
r"(?:[:\"'](?P<version>[\w.${}+-]+))?"
|
|
46
|
+
)
|
|
47
|
+
_MAVEN_COORD_RE = re.compile(
|
|
48
|
+
r"<groupId>\s*(?P<group>[^<]+)\s*</groupId>\s*"
|
|
49
|
+
r"<artifactId>\s*(?P<artifact>[^<]+)\s*</artifactId>"
|
|
50
|
+
r"(?:\s*<version>\s*(?P<version>[^<]+)\s*</version>)?",
|
|
51
|
+
re.DOTALL,
|
|
52
|
+
)
|
|
53
|
+
_IMPORT_RE = re.compile(r"^\s*import\s+(?P<package>org\.springframework(?:\.boot)?|org\.hibernate|jakarta\.|io\.quarkus|io\.micronaut)")
|
|
54
|
+
_PATTERNS = {
|
|
55
|
+
"module_info": re.compile(r"\bmodule\s+[\w.]+\s*\{"),
|
|
56
|
+
"virtual_threads": re.compile(r"\b(?:Thread\.ofVirtual|Executors\.newVirtualThreadPerTaskExecutor)\b"),
|
|
57
|
+
"scoped_value": re.compile(r"\bScopedValue\b"),
|
|
58
|
+
"native_image": re.compile(r"\bnative-image\b"),
|
|
59
|
+
"jlink": re.compile(r"\bjlink\b"),
|
|
60
|
+
"jpackage": re.compile(r"\bjpackage\b"),
|
|
61
|
+
}
|
|
62
|
+
_JAVA_VERSION_RE = re.compile(
|
|
63
|
+
r"(?:java-version|JAVA_VERSION|jdk|JDK|temurin|corretto|zulu|openjdk)[^\n\r:=-]*[:= -]+(?:['\"]?)(?:Java[- ]?)?(?P<version>\d{1,2})(?:['\"]|\b)",
|
|
64
|
+
re.IGNORECASE,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _line_evidence(path: str, line: int, text: str, *, kind: str, detail: str) -> dict:
|
|
69
|
+
return {"kind": kind, "path": path, "line": line, "detail": detail, "text": text.strip()[:240]}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _sbom_inventory(files: list[tuple[Path, str]], limitations: list[str]) -> dict:
|
|
73
|
+
"""Read local CycloneDX/SPDX JSON without treating it as a security verdict."""
|
|
74
|
+
documents: list[dict] = []
|
|
75
|
+
for path, rel in files:
|
|
76
|
+
if path.suffix.lower() != ".json":
|
|
77
|
+
continue
|
|
78
|
+
try:
|
|
79
|
+
if path.stat().st_size > _MAX_BYTES:
|
|
80
|
+
continue
|
|
81
|
+
raw = path.read_text(encoding="utf-8", errors="replace")
|
|
82
|
+
data = json.loads(raw)
|
|
83
|
+
except (OSError, ValueError):
|
|
84
|
+
continue
|
|
85
|
+
if not isinstance(data, dict):
|
|
86
|
+
continue
|
|
87
|
+
if data.get("bomFormat") == "CycloneDX":
|
|
88
|
+
fmt, version, entries = "cyclonedx", data.get("specVersion"), data.get("components", [])
|
|
89
|
+
elif isinstance(data.get("spdxVersion"), str) and data["spdxVersion"].startswith("SPDX-"):
|
|
90
|
+
fmt, version, entries = "spdx", data.get("spdxVersion"), data.get("packages", [])
|
|
91
|
+
else:
|
|
92
|
+
continue
|
|
93
|
+
if not isinstance(entries, list):
|
|
94
|
+
limitations.append(f"{rel}: SBOM component list is not an array")
|
|
95
|
+
continue
|
|
96
|
+
components: list[dict] = []
|
|
97
|
+
for entry in entries[:_MAX_SBOM_COMPONENTS]:
|
|
98
|
+
if not isinstance(entry, dict):
|
|
99
|
+
continue
|
|
100
|
+
if fmt == "cyclonedx":
|
|
101
|
+
name = entry.get("name")
|
|
102
|
+
version_value = entry.get("version")
|
|
103
|
+
group = entry.get("group")
|
|
104
|
+
purl = entry.get("purl")
|
|
105
|
+
else:
|
|
106
|
+
name = entry.get("name")
|
|
107
|
+
version_value = entry.get("versionInfo")
|
|
108
|
+
group = None
|
|
109
|
+
purl = next((ref.get("referenceLocator") for ref in entry.get("externalRefs", [])
|
|
110
|
+
if isinstance(ref, dict) and ref.get("referenceType") == "purl"), None)
|
|
111
|
+
if not isinstance(name, str):
|
|
112
|
+
continue
|
|
113
|
+
components.append({
|
|
114
|
+
"name": name,
|
|
115
|
+
"group": group if isinstance(group, str) else None,
|
|
116
|
+
"version": version_value if isinstance(version_value, str) else None,
|
|
117
|
+
"purl": purl if isinstance(purl, str) else None,
|
|
118
|
+
})
|
|
119
|
+
if len(entries) > _MAX_SBOM_COMPONENTS:
|
|
120
|
+
limitations.append(f"{rel}: SBOM components capped at {_MAX_SBOM_COMPONENTS}")
|
|
121
|
+
documents.append({
|
|
122
|
+
"format": fmt,
|
|
123
|
+
"spec_version": version if isinstance(version, str) else None,
|
|
124
|
+
"path": rel,
|
|
125
|
+
"component_count": len(components),
|
|
126
|
+
"components": components,
|
|
127
|
+
})
|
|
128
|
+
return {
|
|
129
|
+
"status": "observed" if documents else "unobserved",
|
|
130
|
+
"documents": sorted(documents, key=lambda item: item["path"]),
|
|
131
|
+
"policy": "local CycloneDX/SPDX JSON parsing only; no CVE, license, signature, or network validation",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _files(root: Path, source_paths: Optional[Iterable[str]] = None) -> tuple[list[tuple[Path, str]], list[str]]:
|
|
136
|
+
found: list[tuple[Path, str]] = []
|
|
137
|
+
limitations: list[str] = []
|
|
138
|
+
explicit: set[str] = set()
|
|
139
|
+
for rel in source_paths or ():
|
|
140
|
+
normalized = Path(rel).as_posix()
|
|
141
|
+
path = root / normalized
|
|
142
|
+
if path.is_file():
|
|
143
|
+
found.append((path, normalized))
|
|
144
|
+
explicit.add(normalized)
|
|
145
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
146
|
+
dirnames[:] = sorted(d for d in dirnames if d not in _SKIP_DIRS)
|
|
147
|
+
for name in sorted(filenames):
|
|
148
|
+
path = Path(dirpath) / name
|
|
149
|
+
rel = path.relative_to(root).as_posix()
|
|
150
|
+
if rel in explicit:
|
|
151
|
+
continue
|
|
152
|
+
if (name in _TEXT_NAMES or path.suffix.lower() in _TEXT_SUFFIXES or
|
|
153
|
+
name in _SBOM_NAMES or "cyclonedx" in name.lower() or
|
|
154
|
+
"spdx" in name.lower() or ".github/workflows/" in f"/{rel}" or
|
|
155
|
+
rel.startswith(".circleci/")):
|
|
156
|
+
found.append((path, rel))
|
|
157
|
+
priority = []
|
|
158
|
+
regular = []
|
|
159
|
+
for item in found:
|
|
160
|
+
rel = item[1]
|
|
161
|
+
if (Path(rel).parent == Path(".") or rel.startswith((".github/", ".circleci/", ".gitlab/")) or
|
|
162
|
+
Path(rel).name in _TEXT_NAMES or Path(rel).name in _SBOM_NAMES):
|
|
163
|
+
priority.append(item)
|
|
164
|
+
else:
|
|
165
|
+
regular.append(item)
|
|
166
|
+
ordered = sorted(priority, key=lambda item: item[1]) + sorted(regular, key=lambda item: item[1])
|
|
167
|
+
if source_paths is None and len(ordered) > _MAX_FILES:
|
|
168
|
+
limitations.append(f"file scan capped at {_MAX_FILES} text files")
|
|
169
|
+
ordered = ordered[:_MAX_FILES]
|
|
170
|
+
return ordered, limitations
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def scan_jvm_inventory(
|
|
174
|
+
root: Path,
|
|
175
|
+
build_sources: Iterable[tuple[str, str]] = (),
|
|
176
|
+
*,
|
|
177
|
+
source_paths: Optional[Iterable[str]] = None,
|
|
178
|
+
) -> dict:
|
|
179
|
+
"""Return bounded JVM facts with local file/line provenance."""
|
|
180
|
+
files, limitations = _files(root, source_paths)
|
|
181
|
+
sbom = _sbom_inventory(files, limitations)
|
|
182
|
+
declared: dict[str, list[dict]] = {name: [] for name in _FRAMEWORK_COORDINATES}
|
|
183
|
+
observed: dict[str, list[dict]] = {name: [] for name in _FRAMEWORK_COORDINATES}
|
|
184
|
+
patterns: dict[str, list[dict]] = {name: [] for name in _PATTERNS}
|
|
185
|
+
java_runtime: list[dict] = []
|
|
186
|
+
build_text_map = {path: text for path, text in build_sources}
|
|
187
|
+
|
|
188
|
+
texts: list[tuple[str, str]] = []
|
|
189
|
+
for path, rel in files:
|
|
190
|
+
text = build_text_map.get(rel)
|
|
191
|
+
if text is None:
|
|
192
|
+
try:
|
|
193
|
+
if path.stat().st_size > _MAX_BYTES:
|
|
194
|
+
limitations.append(f"{rel}: file exceeds {_MAX_BYTES} byte read limit")
|
|
195
|
+
continue
|
|
196
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
197
|
+
except OSError:
|
|
198
|
+
limitations.append(f"{rel}: could not be read")
|
|
199
|
+
continue
|
|
200
|
+
texts.append((rel, text))
|
|
201
|
+
|
|
202
|
+
for rel, text in texts:
|
|
203
|
+
is_build = rel.endswith(("pom.xml", "build.gradle", "build.gradle.kts", "gradle.properties"))
|
|
204
|
+
java_block_comment = False
|
|
205
|
+
if rel.endswith("pom.xml"):
|
|
206
|
+
for match in _MAVEN_COORD_RE.finditer(text):
|
|
207
|
+
group, artifact = match.group("group").strip(), match.group("artifact").strip()
|
|
208
|
+
for name, (prefix, artifact_re) in _FRAMEWORK_COORDINATES.items():
|
|
209
|
+
if group.startswith(prefix) and artifact_re.fullmatch(artifact):
|
|
210
|
+
line_no = text[:match.start()].count("\n") + 1
|
|
211
|
+
declared[name].append(_line_evidence(
|
|
212
|
+
rel, line_no, match.group(0), kind="declared_coordinate",
|
|
213
|
+
detail=f"{group}:{artifact}" + (f":{match.group('version').strip()}" if match.group("version") else ""),
|
|
214
|
+
))
|
|
215
|
+
for line_no, line in enumerate(text.splitlines(), 1):
|
|
216
|
+
detection_line = line
|
|
217
|
+
if rel.endswith(".java"):
|
|
218
|
+
pieces: list[str] = []
|
|
219
|
+
rest = line
|
|
220
|
+
while rest:
|
|
221
|
+
if java_block_comment:
|
|
222
|
+
end = rest.find("*/")
|
|
223
|
+
if end < 0:
|
|
224
|
+
rest = ""
|
|
225
|
+
break
|
|
226
|
+
java_block_comment = False
|
|
227
|
+
rest = rest[end + 2:]
|
|
228
|
+
else:
|
|
229
|
+
start = rest.find("/*")
|
|
230
|
+
if start < 0:
|
|
231
|
+
pieces.append(rest)
|
|
232
|
+
break
|
|
233
|
+
pieces.append(rest[:start])
|
|
234
|
+
java_block_comment = True
|
|
235
|
+
rest = rest[start + 2:]
|
|
236
|
+
detection_line = "".join(pieces).split("//", 1)[0]
|
|
237
|
+
if Path(rel).name == "module-info.java" and detection_line.strip().startswith("module "):
|
|
238
|
+
patterns["module_info"].append(_line_evidence(
|
|
239
|
+
rel, line_no, line, kind="observed_pattern", detail="module-info.java"
|
|
240
|
+
))
|
|
241
|
+
if is_build:
|
|
242
|
+
for match in _COORD_RE.finditer(line):
|
|
243
|
+
group, artifact = match.group("group"), match.group("artifact")
|
|
244
|
+
for name, (prefix, artifact_re) in _FRAMEWORK_COORDINATES.items():
|
|
245
|
+
if group.startswith(prefix) and artifact_re.fullmatch(artifact):
|
|
246
|
+
declared[name].append(_line_evidence(
|
|
247
|
+
rel, line_no, line, kind="declared_coordinate",
|
|
248
|
+
detail=f"{group}:{artifact}" + (f":{match.group('version')}" if match.group("version") else ""),
|
|
249
|
+
))
|
|
250
|
+
import_match = _IMPORT_RE.match(detection_line)
|
|
251
|
+
if import_match:
|
|
252
|
+
package = import_match.group("package")
|
|
253
|
+
name = (
|
|
254
|
+
"spring-boot" if package == "org.springframework.boot" else
|
|
255
|
+
"spring-framework" if package == "org.springframework" else
|
|
256
|
+
"hibernate" if package == "org.hibernate" else
|
|
257
|
+
"jakarta-ee" if package == "jakarta." else
|
|
258
|
+
"quarkus" if package == "io.quarkus" else "micronaut"
|
|
259
|
+
)
|
|
260
|
+
observed[name].append(_line_evidence(rel, line_no, line, kind="observed_import", detail=package))
|
|
261
|
+
for name, pattern in _PATTERNS.items():
|
|
262
|
+
if name != "module_info" or Path(rel).name != "module-info.java":
|
|
263
|
+
if pattern.search(detection_line):
|
|
264
|
+
patterns[name].append(_line_evidence(rel, line_no, line, kind="observed_pattern", detail=name))
|
|
265
|
+
if (".github/workflows/" in f"/{rel}" or rel.startswith((".circleci/", ".gitlab/")) or
|
|
266
|
+
Path(rel).name in {"Jenkinsfile", ".gitlab-ci.yml"} or rel.startswith("Dockerfile")):
|
|
267
|
+
match = _JAVA_VERSION_RE.search(line)
|
|
268
|
+
if match:
|
|
269
|
+
java_runtime.append(_line_evidence(
|
|
270
|
+
rel, line_no, line, kind="declared_java_runtime", detail=f"java:{match.group('version')}"
|
|
271
|
+
))
|
|
272
|
+
|
|
273
|
+
def capped(items: list[dict]) -> list[dict]:
|
|
274
|
+
if len(items) > _MAX_EVIDENCE:
|
|
275
|
+
limitations.append(f"evidence capped at {_MAX_EVIDENCE} items per category")
|
|
276
|
+
return items[:_MAX_EVIDENCE]
|
|
277
|
+
|
|
278
|
+
frameworks = {}
|
|
279
|
+
for name in _FRAMEWORK_COORDINATES:
|
|
280
|
+
frameworks[name] = {
|
|
281
|
+
"declared": capped(sorted(declared[name], key=lambda x: (x["path"], x["line"]))),
|
|
282
|
+
"observed": capped(sorted(observed[name], key=lambda x: (x["path"], x["line"]))),
|
|
283
|
+
"status": "declared_and_observed" if declared[name] and observed[name]
|
|
284
|
+
else "declared_only" if declared[name] else "observed_only" if observed[name] else "unobserved",
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
"schema_version": "jvm-inventory-v1",
|
|
288
|
+
"frameworks": frameworks,
|
|
289
|
+
"patterns": {name: capped(sorted(items, key=lambda x: (x["path"], x["line"]))) for name, items in patterns.items()},
|
|
290
|
+
"java_runtime": capped(sorted(java_runtime, key=lambda x: (x["path"], x["line"]))),
|
|
291
|
+
"sbom": sbom,
|
|
292
|
+
"limitations": sorted(set(limitations)),
|
|
293
|
+
"analysis_policy": "local text evidence only; no build, dependency resolution, network, or recommendations",
|
|
294
|
+
}
|
sourcecode/migrate_check.py
CHANGED
|
@@ -26,6 +26,7 @@ from typing import Any, Iterable, Iterator, Optional, TYPE_CHECKING
|
|
|
26
26
|
from sourcecode.degradation import cap_effect
|
|
27
27
|
from sourcecode.path_filters import is_test_or_fixture_path
|
|
28
28
|
from sourcecode.jdk_exports import JDK_UNCONDITIONAL_EXPORTS
|
|
29
|
+
from sourcecode.jvm_inventory import scan_jvm_inventory
|
|
29
30
|
|
|
30
31
|
if TYPE_CHECKING:
|
|
31
32
|
from sourcecode.hibernate_strat import HibernateStratification
|
|
@@ -1905,6 +1906,8 @@ class MigrationReport:
|
|
|
1905
1906
|
# F-P: Java LTS/licensing inventory, derived from build/runtime hints. Additive
|
|
1906
1907
|
# field: it informs migration planning but does not participate in readiness_score.
|
|
1907
1908
|
java_lts_inventory: dict = field(default_factory=dict)
|
|
1909
|
+
# JVM ecosystem evidence. Additive and advisory: it never changes readiness.
|
|
1910
|
+
jvm_inventory: dict = field(default_factory=dict)
|
|
1908
1911
|
evidence_manifest: dict = field(default_factory=dict)
|
|
1909
1912
|
|
|
1910
1913
|
def finalize(self) -> "MigrationReport":
|
|
@@ -2572,6 +2575,7 @@ class MigrationReport:
|
|
|
2572
2575
|
"spring_boot_version_detected": self.spring_boot_version_detected,
|
|
2573
2576
|
"boot3_axis_completed": self.boot3_axis_completed,
|
|
2574
2577
|
"java_lts_inventory": self.java_lts_inventory,
|
|
2578
|
+
"jvm_inventory": self.jvm_inventory,
|
|
2575
2579
|
**({"evidence_manifest": self.evidence_manifest} if self.evidence_manifest else {}),
|
|
2576
2580
|
"summary": self.summary,
|
|
2577
2581
|
"findings": [f.to_dict() for f in _listed],
|
|
@@ -3328,6 +3332,8 @@ def run_migrate_check(
|
|
|
3328
3332
|
spring_boot_version=spring_boot_version,
|
|
3329
3333
|
target_jdk=target_jdk,
|
|
3330
3334
|
)
|
|
3335
|
+
with _phase("jvm_inventory"):
|
|
3336
|
+
jvm_inventory = scan_jvm_inventory(root, _build_sources, source_paths=file_paths)
|
|
3331
3337
|
|
|
3332
3338
|
# BUG #8: classify each finding's code context (main / test / generated) so the
|
|
3333
3339
|
# report can keep test fixtures and autogenerated sources out of blocking_count.
|
|
@@ -3381,6 +3387,7 @@ def run_migrate_check(
|
|
|
3381
3387
|
findings=all_findings,
|
|
3382
3388
|
hibernate=hibernate_strat,
|
|
3383
3389
|
java_lts_inventory=java_lts_inventory,
|
|
3390
|
+
jvm_inventory=jvm_inventory,
|
|
3384
3391
|
limitations=limitations,
|
|
3385
3392
|
metadata={
|
|
3386
3393
|
"java_files_scanned": len(file_paths),
|
sourcecode/parallel.py
CHANGED
|
@@ -266,16 +266,6 @@ def prefill_parse_cache(
|
|
|
266
266
|
# a build that forks itself on every analysis.
|
|
267
267
|
receipt["reason"] = "frozen build — worker processes would re-enter the CLI"
|
|
268
268
|
return receipt
|
|
269
|
-
if os.name == "nt":
|
|
270
|
-
# Windows workers use ``spawn`` and must duplicate the parent's IPC
|
|
271
|
-
# handles. A denied DuplicateHandle leaves a ProcessPoolExecutor with no
|
|
272
|
-
# usable workers; depending on which consumer waits for its cache entry,
|
|
273
|
-
# that used to become either silent success or an unbounded wait. The
|
|
274
|
-
# sequential loop below is the designed fallback and produces the same
|
|
275
|
-
# IR, so prefer a reliable answer to speculative parse parallelism.
|
|
276
|
-
receipt["reason"] = "Windows — parsing sequentially to avoid spawn worker-handle failures"
|
|
277
|
-
return receipt
|
|
278
|
-
|
|
279
269
|
# Retire dead generations once, here, rather than letting N workers race to
|
|
280
270
|
# do it (C3-70's purge is idempotent, but doing it once is cheaper).
|
|
281
271
|
with suppress(Exception):
|
|
@@ -308,7 +298,9 @@ def prefill_parse_cache(
|
|
|
308
298
|
# the caller's unchanged sequential loop produces the same IR.
|
|
309
299
|
if pool is not None:
|
|
310
300
|
_abort_executor(pool)
|
|
311
|
-
receipt["reason"] =
|
|
301
|
+
receipt["reason"] = (
|
|
302
|
+
f"pool unavailable ({type(exc).__name__}: {exc}) — parsing sequentially"
|
|
303
|
+
)
|
|
312
304
|
return receipt
|
|
313
305
|
if not receipt["reason"]:
|
|
314
306
|
receipt["reason"] = f"{receipt['parsed']} parsed and {receipt['hit']} already cached"
|
sourcecode/release_info.py
CHANGED
|
@@ -33,7 +33,7 @@ RELEASE_DATE = "2026-08-31"
|
|
|
33
33
|
#: computation the test re-runs). The nine-day interval before 5.9.0 lowers
|
|
34
34
|
#: that window to 29 releases over 18 days, so the constant follows the current
|
|
35
35
|
#: measurement rather than preserving the former short-burst cadence.
|
|
36
|
-
RELEASE_RATE_PER_DAY =
|
|
36
|
+
RELEASE_RATE_PER_DAY = 3.22
|
|
37
37
|
|
|
38
38
|
#: How many releases behind is worth telling the operator about. Four, which on
|
|
39
39
|
#: the measured cadence is about three days: one release is under a day and warning
|
sourcecode/repository_ir.py
CHANGED
|
@@ -6114,23 +6114,29 @@ def _build_route_surface(
|
|
|
6114
6114
|
# Phase 3: inheritance projection — subclasses with a class-level @RequestMapping
|
|
6115
6115
|
# prefix inherit parent methods that they do not override (same HTTP verb + path suffix).
|
|
6116
6116
|
if extends_map:
|
|
6117
|
+
# Candidate resolution used to scan every class for every child. On large
|
|
6118
|
+
# repositories this made route projection quadratic in the number of types.
|
|
6119
|
+
# The module prefix is the same discriminator used by the old predicate,
|
|
6120
|
+
# so indexing it preserves its ambiguity rule exactly.
|
|
6121
|
+
def _route_module_key(data: dict) -> tuple[str, str]:
|
|
6122
|
+
source_file = data["source_file"]
|
|
6123
|
+
module = source_file.split("src/", 1)[0] if "src/" in source_file else ""
|
|
6124
|
+
return module, data["fqn"].split(".")[-1]
|
|
6125
|
+
|
|
6126
|
+
_classes_by_module_name: dict[tuple[str, str], list[str]] = {}
|
|
6127
|
+
for _unit, _data in class_info.items():
|
|
6128
|
+
_classes_by_module_name.setdefault(
|
|
6129
|
+
_route_module_key(_data), []
|
|
6130
|
+
).append(_unit)
|
|
6117
6131
|
simple_extends: dict[str, str] = {}
|
|
6118
6132
|
for child_unit, child_data in class_info.items():
|
|
6119
6133
|
parent_simple = extends_map.get(child_data["fqn"])
|
|
6120
6134
|
if not parent_simple:
|
|
6121
6135
|
continue
|
|
6122
|
-
child_module = (
|
|
6123
|
-
|
|
6124
|
-
|
|
6136
|
+
child_module = _route_module_key(child_data)[0]
|
|
6137
|
+
candidates = _classes_by_module_name.get(
|
|
6138
|
+
(child_module, parent_simple), []
|
|
6125
6139
|
)
|
|
6126
|
-
candidates = [
|
|
6127
|
-
unit for unit, data in class_info.items()
|
|
6128
|
-
if data["fqn"].split(".")[-1] == parent_simple
|
|
6129
|
-
and (
|
|
6130
|
-
data["source_file"].split("src/", 1)[0]
|
|
6131
|
-
if "src/" in data["source_file"] else ""
|
|
6132
|
-
) == child_module
|
|
6133
|
-
]
|
|
6134
6140
|
if len(candidates) == 1:
|
|
6135
6141
|
simple_extends[child_unit] = candidates[0]
|
|
6136
6142
|
|
|
@@ -6694,7 +6700,12 @@ def build_repo_ir(
|
|
|
6694
6700
|
# shared store publishes per repository. Collected in the loop that computes
|
|
6695
6701
|
# the keys anyway, so it costs a dictionary.
|
|
6696
6702
|
_repo_keys: "dict[str, str]" = {}
|
|
6697
|
-
|
|
6703
|
+
# Keep parse metadata, not source text. Retaining every file body here made
|
|
6704
|
+
# full CIR builds hold a second copy of large repositories until assembly.
|
|
6705
|
+
# Pass 2 rereads the source only for relation/body extraction.
|
|
6706
|
+
_per_file: list[tuple[str, str, list[str], list[SymbolRecord]]] = []
|
|
6707
|
+
_repo_constants: dict[str, str] = {}
|
|
6708
|
+
_ambiguous_constants: set[str] = set()
|
|
6698
6709
|
# (package, imports, annotation-ref-pairs) for the reference-graph post-pass,
|
|
6699
6710
|
# collected from BOTH fully-parsed and pre-scan-skipped files (BUG #2).
|
|
6700
6711
|
_ann_ref_sources: list[tuple[str, list[str], list[tuple[str, str]]]] = []
|
|
@@ -6758,6 +6769,15 @@ def build_repo_ir(
|
|
|
6758
6769
|
_pc.put(_pkey, package, raw_imports, symbols, _excluded)
|
|
6759
6770
|
_meta_symbols_excluded += _excluded
|
|
6760
6771
|
all_symbols.extend(symbols)
|
|
6772
|
+
for _name, _value in _collect_file_constants(source).items():
|
|
6773
|
+
if _name in _ambiguous_constants:
|
|
6774
|
+
continue
|
|
6775
|
+
_previous = _repo_constants.get(_name)
|
|
6776
|
+
if _previous is None:
|
|
6777
|
+
_repo_constants[_name] = _value
|
|
6778
|
+
elif _previous != _value:
|
|
6779
|
+
del _repo_constants[_name]
|
|
6780
|
+
_ambiguous_constants.add(_name)
|
|
6761
6781
|
# P1-E (parse-coverage reconciliation): a file that declares a type but
|
|
6762
6782
|
# yielded zero symbols is silently invisible to the graph. Record the
|
|
6763
6783
|
# candidate here (source is already in memory; masking only runs for the
|
|
@@ -6773,7 +6793,7 @@ def build_repo_ir(
|
|
|
6773
6793
|
_masked_src = _mask_java(source)
|
|
6774
6794
|
if _masked_src.count("{") != _masked_src.count("}"):
|
|
6775
6795
|
_unbalanced_files.append(rel_path)
|
|
6776
|
-
_per_file.append((rel_path,
|
|
6796
|
+
_per_file.append((rel_path, package, raw_imports, symbols))
|
|
6777
6797
|
if "@" in source:
|
|
6778
6798
|
_parsed_refs = _extract_annotation_type_refs(source, package)
|
|
6779
6799
|
if _parsed_refs:
|
|
@@ -6786,19 +6806,62 @@ def build_repo_ir(
|
|
|
6786
6806
|
|
|
6787
6807
|
# Build {package: {simple_name: FQN}} from every class/interface found.
|
|
6788
6808
|
_same_pkg_map: dict[str, dict[str, str]] = _build_same_package_map(all_symbols)
|
|
6809
|
+
_type_map_material = repr(
|
|
6810
|
+
sorted(
|
|
6811
|
+
(pkg, name, fqn)
|
|
6812
|
+
for pkg, names in _same_pkg_map.items()
|
|
6813
|
+
for name, fqn in names.items()
|
|
6814
|
+
)
|
|
6815
|
+
).encode("utf-8", "replace")
|
|
6816
|
+
_relation_resolution_sig = hashlib.sha256(_type_map_material).hexdigest()[:16]
|
|
6789
6817
|
|
|
6790
6818
|
# Pass 2: build relations with same-package type resolution available.
|
|
6791
|
-
for _file_index, (rel_path,
|
|
6819
|
+
for _file_index, (rel_path, package, raw_imports, symbols) in enumerate(
|
|
6792
6820
|
_per_file, start=1
|
|
6793
6821
|
):
|
|
6794
6822
|
if progress is not None:
|
|
6795
6823
|
progress(_file_index, _total_files, "linking")
|
|
6796
6824
|
same_pkg_types = _same_pkg_map.get(package, {})
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6825
|
+
try:
|
|
6826
|
+
source = (root / rel_path).read_text(encoding="utf-8", errors="replace")
|
|
6827
|
+
except OSError:
|
|
6828
|
+
continue
|
|
6829
|
+
_relation_key = hashlib.sha256(
|
|
6830
|
+
f"relations:{_pc.file_key(rel_path, source, _capture_sig)}:"
|
|
6831
|
+
f"{_relation_resolution_sig}".encode("utf-8", "replace")
|
|
6832
|
+
).hexdigest()
|
|
6833
|
+
_relation_blob = _pc.get_blob(_relation_key)
|
|
6834
|
+
relations: list[RelationEdge]
|
|
6835
|
+
_relation_cache_valid = False
|
|
6836
|
+
if isinstance(_relation_blob, dict) and isinstance(
|
|
6837
|
+
_relation_blob.get("relations"), list
|
|
6838
|
+
):
|
|
6839
|
+
try:
|
|
6840
|
+
relations = [
|
|
6841
|
+
RelationEdge(
|
|
6842
|
+
from_symbol=str(edge["from"]),
|
|
6843
|
+
to_symbol=str(edge["to"]),
|
|
6844
|
+
type=str(edge["type"]),
|
|
6845
|
+
confidence=str(edge.get("confidence", "high")),
|
|
6846
|
+
evidence=dict(edge.get("evidence") or {}),
|
|
6847
|
+
)
|
|
6848
|
+
for edge in _relation_blob["relations"]
|
|
6849
|
+
]
|
|
6850
|
+
_relation_cache_valid = True
|
|
6851
|
+
except (AttributeError, KeyError, TypeError, ValueError):
|
|
6852
|
+
relations = []
|
|
6853
|
+
else:
|
|
6854
|
+
relations = []
|
|
6855
|
+
if not _relation_cache_valid:
|
|
6856
|
+
relations = _build_relations(
|
|
6857
|
+
symbols, raw_imports, source, package, rel_path,
|
|
6858
|
+
same_pkg_types=same_pkg_types,
|
|
6859
|
+
pkg_type_map=_same_pkg_map,
|
|
6860
|
+
)
|
|
6861
|
+
_pc.put_blob(
|
|
6862
|
+
_relation_key,
|
|
6863
|
+
{"relations": [_edge_to_dict(edge) for edge in relations]},
|
|
6864
|
+
)
|
|
6802
6865
|
if emit_body_facts:
|
|
6803
6866
|
# V4b: the per-file body-fact extractors are pure functions of the file's
|
|
6804
6867
|
# bytes, so their combined output is memoised under the same content key.
|
|
@@ -6912,6 +6975,11 @@ def build_repo_ir(
|
|
|
6912
6975
|
)
|
|
6913
6976
|
)
|
|
6914
6977
|
|
|
6978
|
+
# These pass-local containers are no longer needed after linking. Drop their
|
|
6979
|
+
# references before _assemble creates the canonical projections, reducing the
|
|
6980
|
+
# overlap between the raw-build and canonical-build working sets.
|
|
6981
|
+
del _per_file, _repo_keys, _ann_ref_sources, _ambiguous_constants
|
|
6982
|
+
|
|
6915
6983
|
# C3-85: from here to the end of the build there is no per-file loop, so the
|
|
6916
6984
|
# heartbeat kept `linking n/n` on screen — a counter that finished, reading as
|
|
6917
6985
|
# a stage still running. Measured on keycloak (5 486 files, warm), the stretch
|
|
@@ -6931,6 +6999,11 @@ def build_repo_ir(
|
|
|
6931
6999
|
seen.add(key)
|
|
6932
7000
|
unique_relations.append(e)
|
|
6933
7001
|
|
|
7002
|
+
# Linking and the cross-file resolution passes are complete. Keep only the
|
|
7003
|
+
# deduplicated relation set for assembly; the pre-dedup list and type closure
|
|
7004
|
+
# otherwise overlap with the canonical projection at the memory peak.
|
|
7005
|
+
del all_relations, _same_pkg_map, _supertypes
|
|
7006
|
+
|
|
6934
7007
|
# When since is given, route_surface is a diff view (empty [] or list of changes).
|
|
6935
7008
|
# When since is None, pass None so _build_route_surface builds the full route surface.
|
|
6936
7009
|
route_diffs_arg: Optional[list[dict]] = (
|
|
@@ -6943,7 +7016,7 @@ def build_repo_ir(
|
|
|
6943
7016
|
unbalanced_files=_unbalanced_files,
|
|
6944
7017
|
# Cross-file constant folding for annotation paths: per-file folding only
|
|
6945
7018
|
# sees constants declared in the same file (see _collect_repo_constants).
|
|
6946
|
-
constants=
|
|
7019
|
+
constants=_repo_constants,
|
|
6947
7020
|
root=root,
|
|
6948
7021
|
)
|
|
6949
7022
|
|
sourcecode/risk.py
CHANGED
|
@@ -1244,7 +1244,8 @@ class RiskComposer:
|
|
|
1244
1244
|
progress: "Optional[Callable[..., Any]]" = None,
|
|
1245
1245
|
) -> None:
|
|
1246
1246
|
from sourcecode.context_cache import shared_cir
|
|
1247
|
-
from sourcecode.repository_ir import
|
|
1247
|
+
from sourcecode.repository_ir import find_java_files
|
|
1248
|
+
from sourcecode.canonical_ir import project_endpoint_surface
|
|
1248
1249
|
from sourcecode.security_posture import (
|
|
1249
1250
|
endpoint_security_surface,
|
|
1250
1251
|
infer_security_posture,
|
|
@@ -1420,7 +1421,7 @@ class RiskComposer:
|
|
|
1420
1421
|
_inside(
|
|
1421
1422
|
"indexing the validation surface",
|
|
1422
1423
|
lambda: _constraint_index(
|
|
1423
|
-
self.root, self.cir,
|
|
1424
|
+
self.root, self.cir, project_endpoint_surface(self.cir)
|
|
1424
1425
|
),
|
|
1425
1426
|
)
|
|
1426
1427
|
if _named("indexing the validation surface") else None
|
sourcecode/schema_registry.py
CHANGED
|
@@ -294,6 +294,8 @@ SCHEMAS: "tuple[SchemaEntry, ...]" = (
|
|
|
294
294
|
SchemaEntry("ask-run-v1", "one recorded run", "runs.py"),
|
|
295
295
|
SchemaEntry("java-lts-inventory-v1", "the Java LTS inventory in a migration report",
|
|
296
296
|
"migrate_check.py", emitted_by=("migrate-check",)),
|
|
297
|
+
SchemaEntry("jvm-inventory-v1", "local JVM ecosystem evidence in a migration report",
|
|
298
|
+
"jvm_inventory.py", emitted_by=("migrate-check",)),
|
|
297
299
|
SchemaEntry("facts-v1", "the fact registry: one fact, one authority (ADR-0008 R11)",
|
|
298
300
|
"registry.json", note="a registry rather than a payload shape; the "
|
|
299
301
|
"document itself, under `facts/`"),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 5.9.
|
|
3
|
+
Version: 5.9.22
|
|
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.22 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
|
|
@@ -221,6 +221,12 @@ there. Use `ask cache model <repo>`, `--progress stderr`, `audit-report
|
|
|
221
221
|
[current reliability queue](docs/DEVELOPMENT-ROADMAP.md#external-audit-intake--aud-616-re-audit-release-5910)
|
|
222
222
|
before treating a historical timing or bounded flag as a current guarantee.
|
|
223
223
|
|
|
224
|
+
A post-remediation re-audit of `5.9.18` also found a separate `pack gate --since`
|
|
225
|
+
regression on Windows: the git-ref materialization path could resolve to Python
|
|
226
|
+
3.10 and fail in `tarfile` before the stderr-forwarding fix mattered. Release
|
|
227
|
+
`5.9.19` adds the version-compatible extraction path; native Windows repetition
|
|
228
|
+
is still required before treating `pack gate` as production CI evidence.
|
|
229
|
+
|
|
224
230
|
**CI delivery and workspace licensing.** `migrate-check --format sarif` emits
|
|
225
231
|
ASK-owned migration findings with source locations and provenance; it does not
|
|
226
232
|
re-emit imported scanner findings. `ask auth workspace <repo>...` previews the
|
|
@@ -236,11 +242,14 @@ entitlement errors in CI and does not change per-repository gates or billing.
|
|
|
236
242
|
|
|
237
243
|
Release 5.9.17 also carries the boundary-contract hardening: missing consumer repositories,
|
|
238
244
|
malformed CVE feeds and unresolved `pack gate --since` refs fail as structured
|
|
239
|
-
input errors; duplicate same-commit baselines are never overwritten.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
245
|
+
input errors; duplicate same-commit baselines are never overwritten. That
|
|
246
|
+
contract is still not enough to make `pack gate` CI-safe on Windows, because the
|
|
247
|
+
materialization path can still hit a Python 3.10 tarfile mismatch in mixed
|
|
248
|
+
environments. `posture --diff` validates unknown profiles when the repository
|
|
249
|
+
declares profiles. In `endpoints --compact`, an empty `endpoints` list is
|
|
250
|
+
explicitly marked as an intentional omission, not an empty API; unresolved
|
|
251
|
+
`impact` and `impact-chain` queries exit non-zero, including on repositories
|
|
252
|
+
without Java sources.
|
|
244
253
|
|
|
245
254
|
**What a warm actually covers.** `ask cache warm` rebuilds the declared root views and their
|
|
246
255
|
shared structural layers (L1/L2 + the Repository Intelligence Snapshot + the shared Canonical
|
|
@@ -267,11 +276,11 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
267
276
|
# pip / pipx
|
|
268
277
|
pipx install sourcecode # or: pip install sourcecode
|
|
269
278
|
|
|
270
|
-
ask version # ask 5.9.
|
|
279
|
+
ask version # ask 5.9.22 — and, on a build that has aged,
|
|
271
280
|
# how many releases have probably shipped since
|
|
272
281
|
```
|
|
273
282
|
|
|
274
|
-
> **Package vs. command.** The install package is named `sourcecode` this release
|
|
283
|
+
> **Package vs. command.** The install package is named `sourcecode` in this release
|
|
275
284
|
> (renaming the distribution is a separate, breaking change). Installing it gives you the
|
|
276
285
|
> canonical **`ask`** command plus the deprecated **`sourcecode`** alias.
|
|
277
286
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=rlhdFHgHxU-_wnswNtAc3jkUHnsGhX1taqMHDQJ5P6c,309
|
|
2
2
|
sourcecode/__main__.py,sha256=qDFVkobwsdZrX0B3stiRqsUNivVpybKO-egzAwCW4xw,165
|
|
3
|
-
sourcecode/_build_commit.py,sha256=
|
|
3
|
+
sourcecode/_build_commit.py,sha256=h33fClasWi1xoqBmi7TKbd8bGOaY-_NiL3paWe84zFg,11638
|
|
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
|
|
@@ -29,13 +29,13 @@ sourcecode/cache_observation.py,sha256=5E2BeqiYE5wBCbvDglkc5A9fTNhXkT_OhcDuuCqRu
|
|
|
29
29
|
sourcecode/call_surface.py,sha256=fiqYfHooxN1fX9oQoysq1LS3LoZcobhyUNjAGEZKpwk,4148
|
|
30
30
|
sourcecode/caller_metrics.py,sha256=zGc3sHRRwEJgAGmyfqEiH69mD9w5rOqMUhzby8Vmm_w,12599
|
|
31
31
|
sourcecode/caller_reach.py,sha256=M7Ffs5CtlimihfdSy3HtC4WpDP97bRFX15QYOlOfMJw,16626
|
|
32
|
-
sourcecode/canonical_ir.py,sha256=
|
|
32
|
+
sourcecode/canonical_ir.py,sha256=KcbR6Vnmo8UbwSbIn6RDAGdNtfPoxz1c_0IVCaoIuss,38779
|
|
33
33
|
sourcecode/chain_rules.py,sha256=Bi6UHfgd-GxWswmnHRcPz5jdbAuqka3Zkz_P-MTvqhw,12767
|
|
34
34
|
sourcecode/change_plan.py,sha256=drAoDsqFplutCLc6Y0xue7sYkTls0SpYh7aOXvttv3I,12934
|
|
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=2d-S0OQQnoF8bHll_MfIuFUSyAVtRl_Nopb1WEBd8Vo,956694
|
|
39
39
|
sourcecode/click_runtime.py,sha256=K2cUdzFhbd6uEUpucWptXE89_z-ZNZGsqgd-34usUXk,1890
|
|
40
40
|
sourcecode/client_calls.py,sha256=YOPnfRZzRyqmdHvXSVeiunW8cuiuCuWXacNIQRgKMCw,22640
|
|
41
41
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
@@ -45,7 +45,7 @@ sourcecode/constraint_diff.py,sha256=hWIO5vsxvHjsgQx23K3r_JMy1W6wQ2Ca4DqB9R_iMnY
|
|
|
45
45
|
sourcecode/consumer_join.py,sha256=ERwx9SOu0ZE3NgCBYvB0l67iDLnWMdMpVhlCmbA_3Sw,19618
|
|
46
46
|
sourcecode/container_wiring.py,sha256=207Oh4ugGiF0JN-toOCfqTEY2S-30zzZE2r1o0K0Cpo,27511
|
|
47
47
|
sourcecode/context_cache.py,sha256=lIjEGtoZy2eFDvYUVGbG3yhzlIlpdQd-Q5MldXwi0dA,36787
|
|
48
|
-
sourcecode/context_graph.py,sha256
|
|
48
|
+
sourcecode/context_graph.py,sha256=-l7XHL7m3GRPXLUps5Be7lSCAQWsQMyA8lR__ejJbr8,47726
|
|
49
49
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
50
50
|
sourcecode/context_summarizer.py,sha256=8a2chqDFvosLrfB2RnK96cnyDMOiuOpi4oK9Rm8Nsfc,6878
|
|
51
51
|
sourcecode/contract_diff.py,sha256=pOvjLm-2YQ4KWD8G9HsPCs6Lkofkk2e2wve_y_QXjtU,10990
|
|
@@ -88,7 +88,7 @@ sourcecode/fqn_utils.py,sha256=XLU7zDkNBXz_RZkIUNfpPmp1nekWtqP-fxV92tDV1vg,2158
|
|
|
88
88
|
sourcecode/functional_routes.py,sha256=KXMrDoBwj_GC80w10FvgA3zddIGE1iW9KjpDtCllSSs,16813
|
|
89
89
|
sourcecode/gate_anchors.py,sha256=26v8wUtzOk1K1Un5uXdUxAu20mqSPX3jo82zuM05ByE,2066
|
|
90
90
|
sourcecode/git_analyzer.py,sha256=irvEwddbd5_tbA_eYUZebf4mH7_nFhpjwaWAV1f7jUY,15755
|
|
91
|
-
sourcecode/git_checkout.py,sha256=
|
|
91
|
+
sourcecode/git_checkout.py,sha256=aY2lxHfKb5cWcyXGoOrg1dXSH47LGlFBkTcrQgkUPgk,6352
|
|
92
92
|
sourcecode/github_diff.py,sha256=H1woYKuCjb6S9sofBPpMlOpn0L4LoVxuVP7ddnhsNaA,3848
|
|
93
93
|
sourcecode/graph_analyzer.py,sha256=Gw2MzLp0P990TGx6D6Ie-WIPnjWVE6X6dNBQnoM9xPU,65857
|
|
94
94
|
sourcecode/graph_evidence.py,sha256=rENNsYRZeNstX_ExNCLlbHJAruFQwxo5d00x6wO3xwI,15030
|
|
@@ -97,11 +97,12 @@ sourcecode/identity_fallback.py,sha256=uRC_j_80uaI4Ue4tf-OhKX4h472dmufYcjEFGSxY2
|
|
|
97
97
|
sourcecode/integration_coordinates.py,sha256=rvuApnS7WdRUbeQLg_oKj-Gi0koO2RKdw6HenXNgrDo,12443
|
|
98
98
|
sourcecode/intent_router.py,sha256=hC_o0VYfSjzSXecFkpiD7vzFgvt44YAw6E_jrMeGiZ4,15555
|
|
99
99
|
sourcecode/jdk_exports.py,sha256=fCrlwNAXUT9gge_joq6kMnY3zJxYB2pxqy-0w3o3MJI,874
|
|
100
|
+
sourcecode/jvm_inventory.py,sha256=06mlOWSwfs9jBk4lGkCSZHRbapOG41dQ5gopvOVf7dE,14182
|
|
100
101
|
sourcecode/license.py,sha256=eIEuIVQXywl_S9LOIACOEpJ4M5RyaPvWrqg_jSNOrXA,31088
|
|
101
102
|
sourcecode/manifest_cache.py,sha256=qoBn7hPeaMjLHi3higfxsjo7oS7noommRqDnpKP0Ya8,4124
|
|
102
103
|
sourcecode/mcp_nudge.py,sha256=beNjfyb7z09T1rnS4t6Q4qfbLzZQNaP6ddCA4XpmV0s,3256
|
|
103
104
|
sourcecode/metrics_analyzer.py,sha256=gLoRWKygF18jLgwsqmGXSWopw-f5iO1VbM7Jjdif6ag,22809
|
|
104
|
-
sourcecode/migrate_check.py,sha256=
|
|
105
|
+
sourcecode/migrate_check.py,sha256=tUfTpsMUv06JaKpHAbW_TX3ejO_L4iugYCb__lidq4U,171476
|
|
105
106
|
sourcecode/migration_apply.py,sha256=36E9IUq7RbpGVt6RZ8PlSZKgDoEMHorZrXw6UVWPnpw,8692
|
|
106
107
|
sourcecode/migration_blast.py,sha256=OPryWkM6-PbvcG3eJFdcWUnlpXBje41nu2BkrZnE2hg,9359
|
|
107
108
|
sourcecode/non_coverage.py,sha256=Tl04OemPnxnOuWISMF6gAhDYx-P0LP6_l-PbpyvBJb4,30634
|
|
@@ -114,7 +115,7 @@ sourcecode/output_encoding.py,sha256=OxiZQtr7uWBDUtblNQviNHsTH1Xo9AZEpsegPlQWKFk
|
|
|
114
115
|
sourcecode/output_sink.py,sha256=Eya-PnXxqc3RGulJoLhuC9LUF73SZD0aO5O2B0xAMXU,8762
|
|
115
116
|
sourcecode/pack_manifest.py,sha256=Qm6et6SQsFCLc-HJ-Uhz1XlcOa77ZDHgks4JVDEENss,20820
|
|
116
117
|
sourcecode/packs.py,sha256=Oewt8fOkxvr1wwZJc_SOjc6sP_9cmKUzi-WOyVLmb7k,51344
|
|
117
|
-
sourcecode/parallel.py,sha256=
|
|
118
|
+
sourcecode/parallel.py,sha256=tXh5TYJMDD7FDwx1uS4kMB1jTWTvHgCC-HCYJllnwls,13173
|
|
118
119
|
sourcecode/parse_cache.py,sha256=BaPzh-KygmY4OpJQm2YGh6PWztYsj58vHWTu2eSF4s8,38769
|
|
119
120
|
sourcecode/partial_contract.py,sha256=FZUBACkClvRAmPeYVjxNWeQmP6Y22KW4fPeeTfmT80A,5951
|
|
120
121
|
sourcecode/path_admission.py,sha256=5IBfmHvOrSwrVLlXXwj9KAEg-ciVaHYHjcpAS8mriPg,5898
|
|
@@ -139,7 +140,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
|
139
140
|
sourcecode/reference_facts.py,sha256=mx7H-Ax5y8sKAQHrnJCXlgjkUzqZtQTByv9Zy2lxKQQ,15500
|
|
140
141
|
sourcecode/regress.py,sha256=6rlFP2rsIzVKAA0spJ66To528GyBtnRI2Zg9vAGq8kQ,13264
|
|
141
142
|
sourcecode/regulatory_pack.py,sha256=xVWd8hYg6MjGfkFP8UWcYeLmNMxEk8k2uSbTcnGMbhg,3421
|
|
142
|
-
sourcecode/release_info.py,sha256=
|
|
143
|
+
sourcecode/release_info.py,sha256=Z6QP0_dEiEOHdrThbQQqFisSjBj_tku0pjjUyKXXsjM,5918
|
|
143
144
|
sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
|
|
144
145
|
sourcecode/remediation_diff.py,sha256=fGYqPmQF7bO54L8ivztX5PgmW4fVu54gNcG6OEQ192U,9730
|
|
145
146
|
sourcecode/remediation_flow.py,sha256=OFlrNCD2dKw1WlX5cHqVl1yrQZHi2TjCThH8Q3X1PwI,10294
|
|
@@ -147,10 +148,10 @@ sourcecode/remediation_report.py,sha256=z28x-u618feU2d7ol_FW7IfhUOq6iETCfdpl7zJC
|
|
|
147
148
|
sourcecode/remedies.py,sha256=V2_cD4pLq8bKRkzF-3RGFoiu1xWYZS384WDVp1MrlZw,10225
|
|
148
149
|
sourcecode/rename_refactor.py,sha256=oASZIf7TxcOBy3RQ0FuOe-4HlNW_JOu9RW9JqCxGNBE,17225
|
|
149
150
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
150
|
-
sourcecode/repository_ir.py,sha256=
|
|
151
|
+
sourcecode/repository_ir.py,sha256=GLYSuPuFqwWdIUtZsUNFctl5DgUaOsDKEK_rwnS-F9g,526567
|
|
151
152
|
sourcecode/resource_budget.py,sha256=Bo-XMvVK9XyHY_S06fVirZrhXgwmnr3XI38-Li55NWY,7767
|
|
152
153
|
sourcecode/ris.py,sha256=HsLVHZfseEHNqMyxrj8SvrzUK7jE_85lsWjHtsEw9ik,29196
|
|
153
|
-
sourcecode/risk.py,sha256=
|
|
154
|
+
sourcecode/risk.py,sha256=rG7fqDIjyhtvB0Qje0d2ZEUl5kemMBwbuOJG8ZJxqrQ,102552
|
|
154
155
|
sourcecode/risk_model.py,sha256=RoC-G15qZP5BLEwwmcr4eJ0APgvIbh1WTqhTVT-5YFE,10895
|
|
155
156
|
sourcecode/rule_catalog.py,sha256=QCx6ToFTMCtDt5uuX1uBMLuw3b9Cd92gGRsX34kud4U,5328
|
|
156
157
|
sourcecode/rule_pass.py,sha256=DTNxD-nAecMJhIzI1LCd9FyRumInQSZjitacNh4Zoc8,14671
|
|
@@ -161,7 +162,7 @@ sourcecode/sarif_emit.py,sha256=miFi_DiN58UZyId7DlYzcv-eYLFAA8xdX1AGpoiF4XQ,9353
|
|
|
161
162
|
sourcecode/scanner.py,sha256=z3CV0rcGunu0Y8mpNgp07wI7nxT0pxw1BkXRRtI0Rpo,9609
|
|
162
163
|
sourcecode/schema.py,sha256=61Ux_qRITs_orcrZM20ctV2k1ZfzauqqMgJlZJYAEbE,25087
|
|
163
164
|
sourcecode/schema_producers.py,sha256=4XzW4wZXnFHOaJT7XenirXGCo0pPZhkdhb7zcDtqFnM,10390
|
|
164
|
-
sourcecode/schema_registry.py,sha256=
|
|
165
|
+
sourcecode/schema_registry.py,sha256=UYyHwtruOk6TThyf-SXw36n_qFZAOJF-IyuMulWrT6I,29029
|
|
165
166
|
sourcecode/security_chain.py,sha256=1YZVeFLwy65kntMBhMNvz1cUpOoKCmvEqYICgxs66Gw,6823
|
|
166
167
|
sourcecode/security_config.py,sha256=LUppRDYQK0oKM4qdEOLObaeU3frFTS-paEZo7dMgtY8,5467
|
|
167
168
|
sourcecode/security_config_scan.py,sha256=LU8gfNUBB6wjKR8jkK-qRKtUrbkHZ301_Xsk6hSp4XU,35916
|
|
@@ -274,13 +275,13 @@ sourcecode/telemetry/consent.py,sha256=-Wb8BFGXEexOs8M0Eja7F-T6ZtLNPeRoZnHcHvlP7
|
|
|
274
275
|
sourcecode/telemetry/events.py,sha256=l3Z2SQUO4_C2D2ItXOOdVNimAmHedUHnUxqYFTxnsAE,2900
|
|
275
276
|
sourcecode/telemetry/filters.py,sha256=-lqT2oorECuqEIdeHb--7AZ6BCaUx0bya720a-Z5x6w,7335
|
|
276
277
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
277
|
-
sourcecode/_docs/DEFECT-LEDGER.md,sha256=
|
|
278
|
+
sourcecode/_docs/DEFECT-LEDGER.md,sha256=27Td56IIsaXPz9udDpLMiMDP4RMeaQTh5-FUqucpee4,1167577
|
|
278
279
|
sourcecode/_docs/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
|
|
279
280
|
sourcecode/_docs/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
|
|
280
|
-
sourcecode/_docs/USER_GUIDE.md,sha256=
|
|
281
|
-
sourcecode-5.9.
|
|
282
|
-
sourcecode-5.9.
|
|
283
|
-
sourcecode-5.9.
|
|
284
|
-
sourcecode-5.9.
|
|
285
|
-
sourcecode-5.9.
|
|
286
|
-
sourcecode-5.9.
|
|
281
|
+
sourcecode/_docs/USER_GUIDE.md,sha256=A__FLktxJ8S-VMUAQ5OxPRfUkKQ7yKy6FQbWxiZvc6c,108275
|
|
282
|
+
sourcecode-5.9.22.dist-info/METADATA,sha256=Plv_fedlIoI6bKsHB5Q5zFlOlC_nK-KN7gLa34KVsb8,66673
|
|
283
|
+
sourcecode-5.9.22.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
284
|
+
sourcecode-5.9.22.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
|
|
285
|
+
sourcecode-5.9.22.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
|
|
286
|
+
sourcecode-5.9.22.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
|
|
287
|
+
sourcecode-5.9.22.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|