sourcecode 4.9.0__py3-none-any.whl → 4.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sourcecode might be problematic. Click here for more details.
- sourcecode/__init__.py +1 -1
- sourcecode/audit_report.py +21 -2
- sourcecode/cache_model.py +6 -0
- sourcecode/canonical_ir.py +19 -0
- sourcecode/cli.py +148 -10
- sourcecode/migrate_check.py +307 -1
- sourcecode/progress.py +21 -6
- sourcecode/provenance.py +202 -0
- sourcecode/release_info.py +1 -1
- sourcecode/risk.py +11 -0
- sourcecode/runs.py +136 -0
- sourcecode/sarif.py +12 -0
- sourcecode/security_config_scan.py +55 -0
- {sourcecode-4.9.0.dist-info → sourcecode-4.10.0.dist-info}/METADATA +23 -10
- {sourcecode-4.9.0.dist-info → sourcecode-4.10.0.dist-info}/RECORD +18 -16
- {sourcecode-4.9.0.dist-info → sourcecode-4.10.0.dist-info}/WHEEL +0 -0
- {sourcecode-4.9.0.dist-info → sourcecode-4.10.0.dist-info}/entry_points.txt +0 -0
- {sourcecode-4.9.0.dist-info → sourcecode-4.10.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/audit_report.py
CHANGED
|
@@ -39,13 +39,19 @@ def build_audit_report(
|
|
|
39
39
|
profiles: Optional[set[str]] = None,
|
|
40
40
|
sign_key: Optional[bytes] = None,
|
|
41
41
|
risk_limit: int = 10,
|
|
42
|
+
risk_payload: Optional[dict[str, Any]] = None,
|
|
42
43
|
) -> dict[str, Any]:
|
|
43
44
|
"""Build the buyer-readable audit bundle from existing command payloads."""
|
|
44
45
|
from sourcecode.posture import build_posture
|
|
45
|
-
from sourcecode.risk import build_risk
|
|
46
46
|
|
|
47
47
|
root = Path(root).resolve()
|
|
48
|
-
|
|
48
|
+
if risk_payload is None:
|
|
49
|
+
from sourcecode.risk import build_risk
|
|
50
|
+
risk = build_risk(root, limit=risk_limit, min_band="low", profiles=profiles)
|
|
51
|
+
risk_source = "computed"
|
|
52
|
+
else:
|
|
53
|
+
risk = risk_payload
|
|
54
|
+
risk_source = "from-risk"
|
|
49
55
|
posture = build_posture(root, profiles or set())
|
|
50
56
|
top_risks = risk.get("risks", [])[:risk_limit]
|
|
51
57
|
access = ((posture.get("endpoints") or {}).get("effective_access") or {})
|
|
@@ -58,6 +64,7 @@ def build_audit_report(
|
|
|
58
64
|
**({"profile_set": sorted(profiles)} if profiles else {}),
|
|
59
65
|
"summary": {
|
|
60
66
|
"risk_model": risk.get("model"),
|
|
67
|
+
"risk_source": risk_source,
|
|
61
68
|
"total_defects": risk.get("total_defects"),
|
|
62
69
|
"total_findings": risk.get("total_findings"),
|
|
63
70
|
"risk_bands": risk.get("by_band", {}),
|
|
@@ -78,6 +85,18 @@ def build_audit_report(
|
|
|
78
85
|
"non_coverage": (risk.get("non_coverage") or {}).get("items", []),
|
|
79
86
|
},
|
|
80
87
|
}
|
|
88
|
+
from sourcecode.provenance import build_evidence_manifest
|
|
89
|
+
unsigned["evidence_manifest"] = build_evidence_manifest(
|
|
90
|
+
unsigned,
|
|
91
|
+
command="audit-report",
|
|
92
|
+
root=root,
|
|
93
|
+
inputs={
|
|
94
|
+
"profiles": sorted(profiles) if profiles else None,
|
|
95
|
+
"risk_limit": risk_limit,
|
|
96
|
+
"risk_source": risk_source,
|
|
97
|
+
"signed": bool(sign_key),
|
|
98
|
+
},
|
|
99
|
+
)
|
|
81
100
|
# Sign and publish the same JSON-normalized material. Some embedded evidence
|
|
82
101
|
# may contain tuples or other JSON-coercible values; normalizing before signing
|
|
83
102
|
# avoids a signature over an in-memory shape the user never receives.
|
sourcecode/cache_model.py
CHANGED
|
@@ -144,6 +144,12 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
144
144
|
"repository side, so what it buys is what it buys `risk`.",
|
|
145
145
|
"not measured on the battery yet — bounded by the `risk` composition, plus "
|
|
146
146
|
"reading one JSON file"),
|
|
147
|
+
CommandCache("audit-report", ("cir", "parse"), "shared", False,
|
|
148
|
+
"Packages `risk` and `posture` evidence. A warm helps the repository side; "
|
|
149
|
+
"`--from-risk risk.json` skips risk recomputation entirely and only builds "
|
|
150
|
+
"the posture/report projection.",
|
|
151
|
+
"not measured on the battery yet — bounded by `risk` + `posture`, or by "
|
|
152
|
+
"`posture` when `--from-risk` is supplied"),
|
|
147
153
|
CommandCache("migrate-recipe", ("parse",), "shared", False,
|
|
148
154
|
"Runs the same scan as `migrate-check` and projects its findings into an "
|
|
149
155
|
"OpenRewrite recipe, so it buys exactly what a warm buys `migrate-check`: "
|
sourcecode/canonical_ir.py
CHANGED
|
@@ -43,6 +43,23 @@ _REVERSE_EXCLUDE: frozenset[str] = frozenset({"annotated_with", "mapped_to"})
|
|
|
43
43
|
# the CIR endpoint count reconciles with the `endpoints` command). See BUG #7.
|
|
44
44
|
_FQN_PATH_RE: re.Pattern = re.compile(r"/(org|com|net|io|edu)\.[a-z][a-z0-9]*\.[a-zA-Z]")
|
|
45
45
|
|
|
46
|
+
_JAVA_RESERVED_HANDLER_NAMES: frozenset[str] = frozenset({
|
|
47
|
+
"if", "else", "for", "while", "do", "switch", "case", "break", "continue",
|
|
48
|
+
"return", "new", "throw", "try", "catch", "finally", "instanceof",
|
|
49
|
+
"this", "super", "void", "class", "interface", "enum", "extends", "implements",
|
|
50
|
+
"import", "package", "static", "final", "abstract", "synchronized", "native",
|
|
51
|
+
"true", "false", "null",
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _route_has_reserved_handler(route: dict) -> bool:
|
|
56
|
+
"""Reject parser-corrupt endpoint symbols such as ``pkg.Controller#if``."""
|
|
57
|
+
symbol = str(route.get("symbol") or "")
|
|
58
|
+
if "#" not in symbol:
|
|
59
|
+
return False
|
|
60
|
+
handler = symbol.rsplit("#", 1)[1].split("(", 1)[0].strip()
|
|
61
|
+
return handler in _JAVA_RESERVED_HANDLER_NAMES
|
|
62
|
+
|
|
46
63
|
|
|
47
64
|
# ---------------------------------------------------------------------------
|
|
48
65
|
# CanonicalSecurity
|
|
@@ -438,6 +455,8 @@ def ir_dict_to_canonical(
|
|
|
438
455
|
for r in route_surface:
|
|
439
456
|
if r.get("scope") == "test_util":
|
|
440
457
|
continue
|
|
458
|
+
if _route_has_reserved_handler(r):
|
|
459
|
+
continue
|
|
441
460
|
if _FQN_PATH_RE.search(r.get("path", "") or ""):
|
|
442
461
|
continue
|
|
443
462
|
ep = _route_to_canonical_endpoint(r)
|
sourcecode/cli.py
CHANGED
|
@@ -338,14 +338,16 @@ def _build_help_text() -> str:
|
|
|
338
338
|
Deterministic Java/Spring semantics and reusable structural context for AI coding agents.
|
|
339
339
|
|
|
340
340
|
Cache warms on first scan; later calls reuse pre-built context instead of rescanning.
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
Performance depends on command class and repo size: per-symbol queries are the fast path,
|
|
342
|
+
inventory commands scale with files, and repo-wide/deep compositions can take
|
|
343
|
+
minutes on multi-thousand-endpoint repositories. Use deep jobs nightly or with
|
|
344
|
+
ASK_MAX_ANALYSIS_SECONDS/ASK_PROGRESS when CI needs an explicit budget.
|
|
343
345
|
|
|
344
346
|
[bold]Start here — Java/Spring analysis:[/bold]
|
|
345
347
|
posture . --diff dev:prod [dim]# effective access, two profile sets (exp.)[/dim]
|
|
346
348
|
endpoints . [dim]# endpoints + effective path + policy[/dim]
|
|
347
349
|
spring-audit . [dim]# TX anomalies + security surface[/dim]
|
|
348
|
-
migrate-check . --compact [dim]# Boot 2→3
|
|
350
|
+
migrate-check . --compact [dim]# Boot 2→3 + Java LTS/licensing inventory[/dim]
|
|
349
351
|
risk . [dim]# defects ranked by reach × access (exp.)[/dim]
|
|
350
352
|
|
|
351
353
|
{_remedy_help_block()}
|
|
@@ -881,9 +883,24 @@ def _enforce_format(command: str, fmt: str) -> None:
|
|
|
881
883
|
|
|
882
884
|
|
|
883
885
|
def _safe_write_file(path: "Path", content: str) -> None:
|
|
884
|
-
"""
|
|
886
|
+
"""Atomically write content to path, emitting a clean JSON error on I/O failure."""
|
|
885
887
|
try:
|
|
886
|
-
path
|
|
888
|
+
path = Path(path)
|
|
889
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
890
|
+
tmp = path.with_name(f".{path.name}.tmp-{os.getpid()}-{time.time_ns()}")
|
|
891
|
+
tmp.write_text(content, encoding="utf-8")
|
|
892
|
+
tmp.replace(path)
|
|
893
|
+
try:
|
|
894
|
+
from sourcecode.runs import active_run
|
|
895
|
+
run = active_run()
|
|
896
|
+
if run is not None:
|
|
897
|
+
run.write(
|
|
898
|
+
status="running",
|
|
899
|
+
output_path=path,
|
|
900
|
+
output_size=path.stat().st_size,
|
|
901
|
+
)
|
|
902
|
+
except Exception:
|
|
903
|
+
pass
|
|
887
904
|
except OSError as _exc:
|
|
888
905
|
_emit_error_json(
|
|
889
906
|
INVALID_INPUT_CODE,
|
|
@@ -968,6 +985,50 @@ def _active_command_context() -> "tuple[str, Optional[Path]]":
|
|
|
968
985
|
|
|
969
986
|
|
|
970
987
|
_EXPENSIVE_ANALYSIS_TTL_SECONDS = 6 * 60 * 60
|
|
988
|
+
_ANALYSIS_CLASSES: dict[str, tuple[str, int, str]] = {
|
|
989
|
+
"spring-audit": ("repo-wide", 600, "use nightly/deep tier on large repositories"),
|
|
990
|
+
"risk": ("deep", 1200, "run `ask spring-audit` or a saved `risk.json` first"),
|
|
991
|
+
"audit-report": ("deep", 1200, "reuse `--from-risk risk.json` or schedule nightly"),
|
|
992
|
+
"verify --init": ("repo-wide", 600, "derive contracts outside pre-commit"),
|
|
993
|
+
"verify": ("core", 60, "run with a larger CI budget or narrow the contract set"),
|
|
994
|
+
"migrate-check": ("repo-wide", 600, "run inventory in CI and deep migration checks nightly"),
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
def _analysis_budget(command: str) -> dict[str, Any]:
|
|
999
|
+
cls, recommended, fallback = _ANALYSIS_CLASSES.get(
|
|
1000
|
+
command, ("repo-wide", 600, "run this command in a supervised or nightly job")
|
|
1001
|
+
)
|
|
1002
|
+
configured: Optional[float] = None
|
|
1003
|
+
raw = os.environ.get("ASK_MAX_ANALYSIS_SECONDS")
|
|
1004
|
+
if raw:
|
|
1005
|
+
try:
|
|
1006
|
+
configured = float(raw)
|
|
1007
|
+
except ValueError:
|
|
1008
|
+
configured = None
|
|
1009
|
+
return {
|
|
1010
|
+
"class": cls,
|
|
1011
|
+
"recommended_min_seconds": recommended,
|
|
1012
|
+
"configured_max_seconds": configured,
|
|
1013
|
+
"fallback": fallback,
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
def _enforce_analysis_budget(command: str, budget: dict[str, Any]) -> None:
|
|
1018
|
+
configured = budget.get("configured_max_seconds")
|
|
1019
|
+
recommended = float(budget.get("recommended_min_seconds") or 0)
|
|
1020
|
+
if configured is None or float(configured) >= recommended:
|
|
1021
|
+
return
|
|
1022
|
+
_emit_error_json(
|
|
1023
|
+
INVALID_INPUT_CODE,
|
|
1024
|
+
(
|
|
1025
|
+
f"{command} is a {budget.get('class')} analysis and the configured "
|
|
1026
|
+
f"budget ({configured:g}s) is below its safe floor ({recommended:g}s)."
|
|
1027
|
+
),
|
|
1028
|
+
hint=str(budget.get("fallback") or "raise ASK_MAX_ANALYSIS_SECONDS"),
|
|
1029
|
+
expected=f"ASK_MAX_ANALYSIS_SECONDS >= {recommended:g}",
|
|
1030
|
+
)
|
|
1031
|
+
raise typer.Exit(code=2)
|
|
971
1032
|
|
|
972
1033
|
|
|
973
1034
|
def _analysis_lock_path(path: Path) -> Path:
|
|
@@ -1051,8 +1112,14 @@ def _emit_analysis_contention_warning(command: str, path: Path, phase: str) -> N
|
|
|
1051
1112
|
@contextlib.contextmanager
|
|
1052
1113
|
def _expensive_analysis_scope(command: str, path: Path, phase: str):
|
|
1053
1114
|
"""Register a long repository-wide analysis and warn on concurrent runs."""
|
|
1115
|
+
from sourcecode.runs import RunRecord, reset_active_run, set_active_run
|
|
1116
|
+
|
|
1054
1117
|
lock_path = _analysis_lock_path(path)
|
|
1055
1118
|
_emit_analysis_contention_warning(command, path, phase)
|
|
1119
|
+
budget = _analysis_budget(command)
|
|
1120
|
+
_enforce_analysis_budget(command, budget)
|
|
1121
|
+
run_record = RunRecord.start(path, command, phase, budget=budget)
|
|
1122
|
+
run_token = set_active_run(run_record)
|
|
1056
1123
|
token = f"{os.getpid()}:{time.time_ns()}"
|
|
1057
1124
|
owns_lock = False
|
|
1058
1125
|
if _active_analysis_record(lock_path) is None:
|
|
@@ -1077,6 +1144,12 @@ def _expensive_analysis_scope(command: str, path: Path, phase: str):
|
|
|
1077
1144
|
owns_lock = False
|
|
1078
1145
|
try:
|
|
1079
1146
|
yield
|
|
1147
|
+
if run_record is not None:
|
|
1148
|
+
run_record.write(status="complete", exit_code=0)
|
|
1149
|
+
except BaseException as exc:
|
|
1150
|
+
if run_record is not None:
|
|
1151
|
+
run_record.write(status="failed", exit_code=1, error=str(exc))
|
|
1152
|
+
raise
|
|
1080
1153
|
finally:
|
|
1081
1154
|
if owns_lock:
|
|
1082
1155
|
try:
|
|
@@ -1088,6 +1161,7 @@ def _expensive_analysis_scope(command: str, path: Path, phase: str):
|
|
|
1088
1161
|
lock_path.unlink()
|
|
1089
1162
|
except OSError:
|
|
1090
1163
|
pass
|
|
1164
|
+
reset_active_run(run_token)
|
|
1091
1165
|
|
|
1092
1166
|
|
|
1093
1167
|
def _emit_command_output(
|
|
@@ -3875,6 +3949,11 @@ def prepare_context_cmd(
|
|
|
3875
3949
|
ask prepare-context review-pr . --since main --output review.json
|
|
3876
3950
|
ask prepare-context onboard --llm-prompt
|
|
3877
3951
|
ask prepare-context --task-help
|
|
3952
|
+
|
|
3953
|
+
\b
|
|
3954
|
+
Provenance:
|
|
3955
|
+
JSON output includes evidence_manifest: claim IDs for context files and
|
|
3956
|
+
source-backed rows, plus command inputs, ASK version, git HEAD and freshness.
|
|
3878
3957
|
"""
|
|
3879
3958
|
from sourcecode.prepare_context import TASKS, TaskContextBuilder
|
|
3880
3959
|
|
|
@@ -4419,6 +4498,20 @@ def prepare_context_cmd(
|
|
|
4419
4498
|
_skipped.append("test_gap_discovery")
|
|
4420
4499
|
out["skipped_analyzers"] = _skipped
|
|
4421
4500
|
|
|
4501
|
+
from sourcecode.provenance import build_evidence_manifest as _build_evidence_manifest
|
|
4502
|
+
out["evidence_manifest"] = _build_evidence_manifest(
|
|
4503
|
+
out,
|
|
4504
|
+
command="prepare-context",
|
|
4505
|
+
root=target,
|
|
4506
|
+
inputs={
|
|
4507
|
+
"task": task,
|
|
4508
|
+
"since": since,
|
|
4509
|
+
"format": format or "json",
|
|
4510
|
+
"fast": fast,
|
|
4511
|
+
"llm_prompt": llm_prompt,
|
|
4512
|
+
},
|
|
4513
|
+
)
|
|
4514
|
+
|
|
4422
4515
|
# P0-1: Apply output budget per task — safety net for large repos.
|
|
4423
4516
|
_pc_budget = _prepare_context_budget(task)
|
|
4424
4517
|
|
|
@@ -8007,6 +8100,11 @@ def audit_report_cmd(
|
|
|
8007
8100
|
format: str = typer.Option(
|
|
8008
8101
|
"json", "--format", "-f", help="Output format: json, yaml or markdown."
|
|
8009
8102
|
),
|
|
8103
|
+
from_risk: Optional[Path] = typer.Option(
|
|
8104
|
+
None,
|
|
8105
|
+
"--from-risk",
|
|
8106
|
+
help="Reuse an existing `ask risk -o risk.json` payload instead of recomputing risk.",
|
|
8107
|
+
),
|
|
8010
8108
|
copy: bool = _copy_option(),
|
|
8011
8109
|
) -> None:
|
|
8012
8110
|
"""[EXPERIMENTAL] Human audit bundle over existing ASK evidence, optionally signed.
|
|
@@ -8015,7 +8113,8 @@ def audit_report_cmd(
|
|
|
8015
8113
|
This packages the same evidence `risk` and `posture` already publish into a
|
|
8016
8114
|
report a buyer can read: top risks, effective runtime posture, source-linked
|
|
8017
8115
|
evidence and non-coverage. It does not certify compliance, choose fixes or
|
|
8018
|
-
assign ROI.
|
|
8116
|
+
assign ROI. JSON includes evidence_manifest: claim IDs mapped to source spans,
|
|
8117
|
+
command inputs, ASK version and freshness.
|
|
8019
8118
|
|
|
8020
8119
|
\b
|
|
8021
8120
|
`--sign-key` adds an HMAC-SHA256 signature over the canonical JSON payload.
|
|
@@ -8024,6 +8123,7 @@ def audit_report_cmd(
|
|
|
8024
8123
|
\b
|
|
8025
8124
|
Examples:
|
|
8026
8125
|
ask audit-report .
|
|
8126
|
+
ask risk . -o risk.json && ask audit-report . --from-risk risk.json
|
|
8027
8127
|
ask audit-report . --profile prod --format markdown
|
|
8028
8128
|
ask audit-report . --sign-key audit.key -o audit-report.json
|
|
8029
8129
|
"""
|
|
@@ -8059,12 +8159,42 @@ def audit_report_cmd(
|
|
|
8059
8159
|
expected="a non-empty key file",
|
|
8060
8160
|
)
|
|
8061
8161
|
raise typer.Exit(code=1)
|
|
8162
|
+
risk_payload = None
|
|
8163
|
+
if from_risk is not None:
|
|
8164
|
+
risk_path = Path(from_risk).expanduser()
|
|
8165
|
+
if not risk_path.is_file():
|
|
8166
|
+
_emit_error_json(
|
|
8167
|
+
INVALID_INPUT_CODE,
|
|
8168
|
+
f"--from-risk must name a readable risk JSON file (got {str(from_risk)!r}).",
|
|
8169
|
+
hint="Example: ask risk . -o risk.json && ask audit-report . --from-risk risk.json",
|
|
8170
|
+
expected="an existing JSON file produced by `ask risk`",
|
|
8171
|
+
)
|
|
8172
|
+
raise typer.Exit(code=1)
|
|
8173
|
+
try:
|
|
8174
|
+
risk_payload = json.loads(risk_path.read_text(encoding="utf-8"))
|
|
8175
|
+
except Exception as exc:
|
|
8176
|
+
_emit_error_json(
|
|
8177
|
+
INVALID_INPUT_CODE,
|
|
8178
|
+
f"--from-risk is not valid JSON: {exc}",
|
|
8179
|
+
hint="Regenerate it with: ask risk . -o risk.json",
|
|
8180
|
+
expected="a JSON object produced by `ask risk`",
|
|
8181
|
+
)
|
|
8182
|
+
raise typer.Exit(code=1)
|
|
8183
|
+
if not isinstance(risk_payload, dict) or "risks" not in risk_payload:
|
|
8184
|
+
_emit_error_json(
|
|
8185
|
+
INVALID_INPUT_CODE,
|
|
8186
|
+
"--from-risk does not look like an `ask risk` payload.",
|
|
8187
|
+
hint="Regenerate it with: ask risk . -o risk.json",
|
|
8188
|
+
expected="a JSON object with a `risks` array",
|
|
8189
|
+
)
|
|
8190
|
+
raise typer.Exit(code=1)
|
|
8062
8191
|
|
|
8063
8192
|
_prog = Progress()
|
|
8064
8193
|
_prog.start("packaging audit evidence")
|
|
8065
8194
|
try:
|
|
8066
8195
|
data = build_audit_report(
|
|
8067
8196
|
path, profiles=_profile_set(profile), sign_key=key_bytes,
|
|
8197
|
+
risk_payload=risk_payload,
|
|
8068
8198
|
)
|
|
8069
8199
|
finally:
|
|
8070
8200
|
_prog.stop()
|
|
@@ -8724,9 +8854,9 @@ def migrate_check_cmd(
|
|
|
8724
8854
|
None,
|
|
8725
8855
|
"--target-jdk",
|
|
8726
8856
|
help=(
|
|
8727
|
-
"The JDK you are actually moving to (e.g. 11, 17, 21). Rules
|
|
8728
|
-
"later JDK are not your blockers and are not reported.
|
|
8729
|
-
"
|
|
8857
|
+
"The JDK you are actually moving to (e.g. 11, 17, 21, 25). Rules "
|
|
8858
|
+
"for a later JDK are not your blockers and are not reported. Also "
|
|
8859
|
+
"sets java_lts_inventory.target_lts."
|
|
8730
8860
|
),
|
|
8731
8861
|
),
|
|
8732
8862
|
keep_boot: bool = typer.Option(
|
|
@@ -8786,7 +8916,7 @@ def migrate_check_cmd(
|
|
|
8786
8916
|
help="Caller BFS depth for --blast-radius (1-8, default: 4).",
|
|
8787
8917
|
),
|
|
8788
8918
|
) -> None:
|
|
8789
|
-
"""Spring Boot 2→3 migration readiness
|
|
8919
|
+
"""Spring Boot 2→3 migration readiness + Java LTS/licensing inventory.
|
|
8790
8920
|
|
|
8791
8921
|
\b
|
|
8792
8922
|
Detects:
|
|
@@ -8815,6 +8945,7 @@ def migrate_check_cmd(
|
|
|
8815
8945
|
ask migrate-check . --compact bounded decision summary
|
|
8816
8946
|
ask migrate-check /path/to/repo --format text
|
|
8817
8947
|
ask migrate-check . --min-severity high
|
|
8948
|
+
ask migrate-check . --target-jdk 25 Java LTS target inventory
|
|
8818
8949
|
ask migrate-check . --table --rule MIG-001 --band critical --top-n 20
|
|
8819
8950
|
ask migrate-check . --output migration.json
|
|
8820
8951
|
ask migrate-check . --snapshot --ref sprint-12 persist a readiness point
|
|
@@ -8837,6 +8968,13 @@ def migrate_check_cmd(
|
|
|
8837
8968
|
<repo>/.ask/readiness-history). --trend reads that series and reports
|
|
8838
8969
|
first→last movement — no improving/degrading label, and readiness_score is
|
|
8839
8970
|
flagged not-comparable when the applicable dimension set changed.
|
|
8971
|
+
|
|
8972
|
+
\b
|
|
8973
|
+
Java LTS/licensing inventory:
|
|
8974
|
+
The JSON report includes java_lts_inventory: Java 8/11/17/21/25 evidence
|
|
8975
|
+
from Maven/Gradle, local runtime hints and container files; target-LTS
|
|
8976
|
+
blockers; and explicit Oracle JDK/licensing-review signals. This is
|
|
8977
|
+
inventory evidence, not legal advice, and it never changes readiness_score.
|
|
8840
8978
|
"""
|
|
8841
8979
|
from sourcecode.repository_ir import find_java_files
|
|
8842
8980
|
from sourcecode.migrate_check import run_migrate_check
|