readystack-jdk25-upgrade-blocker-lint 1.0.3__tar.gz

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.
Files changed (16) hide show
  1. readystack_jdk25_upgrade_blocker_lint-1.0.3/LICENSE.txt +16 -0
  2. readystack_jdk25_upgrade_blocker_lint-1.0.3/PKG-INFO +79 -0
  3. readystack_jdk25_upgrade_blocker_lint-1.0.3/README.md +60 -0
  4. readystack_jdk25_upgrade_blocker_lint-1.0.3/pyproject.toml +28 -0
  5. readystack_jdk25_upgrade_blocker_lint-1.0.3/setup.cfg +4 -0
  6. readystack_jdk25_upgrade_blocker_lint-1.0.3/src/readystack_jdk25_upgrade_blocker_lint/__init__.py +1 -0
  7. readystack_jdk25_upgrade_blocker_lint-1.0.3/src/readystack_jdk25_upgrade_blocker_lint/cli.py +135 -0
  8. readystack_jdk25_upgrade_blocker_lint-1.0.3/src/readystack_jdk25_upgrade_blocker_lint/engine.py +30 -0
  9. readystack_jdk25_upgrade_blocker_lint-1.0.3/src/readystack_jdk25_upgrade_blocker_lint/license.py +61 -0
  10. readystack_jdk25_upgrade_blocker_lint-1.0.3/src/readystack_jdk25_upgrade_blocker_lint/rules.json +110 -0
  11. readystack_jdk25_upgrade_blocker_lint-1.0.3/src/readystack_jdk25_upgrade_blocker_lint/strings.json +10 -0
  12. readystack_jdk25_upgrade_blocker_lint-1.0.3/src/readystack_jdk25_upgrade_blocker_lint.egg-info/PKG-INFO +79 -0
  13. readystack_jdk25_upgrade_blocker_lint-1.0.3/src/readystack_jdk25_upgrade_blocker_lint.egg-info/SOURCES.txt +14 -0
  14. readystack_jdk25_upgrade_blocker_lint-1.0.3/src/readystack_jdk25_upgrade_blocker_lint.egg-info/dependency_links.txt +1 -0
  15. readystack_jdk25_upgrade_blocker_lint-1.0.3/src/readystack_jdk25_upgrade_blocker_lint.egg-info/entry_points.txt +2 -0
  16. readystack_jdk25_upgrade_blocker_lint-1.0.3/src/readystack_jdk25_upgrade_blocker_lint.egg-info/top_level.txt +1 -0
@@ -0,0 +1,16 @@
1
+ JDK 25 Upgrade Blocker Lint — Licence
2
+
3
+ Free scope
4
+ Scans every .java file you open or the whole workspace and names each JDK 21/25 blocker on its line with the JEP that removed it and the replacement call. is free for any use, personal or commercial, for as long as you keep the
5
+ extension installed, and the online check at the product page is free without registration.
6
+
7
+ Paid scope
8
+ Exports the whole repository as one dated migration report — every file, blocker, JEP and replacement in Markdown and CSV — to hand to the team or attach to the upgrade ticket, and covers commercial and team-seat use. is the paid part. It asks for a licence key issued at purchase (one seat, one key;
9
+ 7-day full refund, no questions). The key is checked against the payment provider and
10
+ cached locally for 30 days so it keeps working offline.
11
+
12
+ Redistribution
13
+ You may not resell, re-host or bundle this extension or its rule set. The rule set and the
14
+ engine are provided as-is; check results are advisory and do not constitute legal advice.
15
+
16
+ (c) ReadyStack — https://getreadystack.com
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: readystack-jdk25-upgrade-blocker-lint
3
+ Version: 1.0.3
4
+ Summary: Finds the Java lines the new LTS removed — 18 rules; 23 findings in one 64-line fixture
5
+ Author-email: ReadyStack <support@getreadystack.com>
6
+ License: Proprietary - see LICENSE.txt
7
+ Project-URL: Homepage, https://getreadystack.com/tools/jdk25-upgrade-blocker-lint?ref=pypi
8
+ Project-URL: Documentation, https://getreadystack.com/tools/jdk25-upgrade-blocker-lint?ref=pypi
9
+ Project-URL: Support, https://getreadystack.com/support
10
+ Keywords: java,jdk,migration,jakarta,linter,upgrade
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Environment :: Console
13
+ Classifier: Topic :: Software Development :: Quality Assurance
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.txt
18
+ Dynamic: license-file
19
+
20
+ # JDK 25 Upgrade Blocker Lint
21
+
22
+ Finds the Java lines the new LTS removed — 18 rules; 23 findings in one 64-line fixture
23
+
24
+ ## Install
25
+
26
+ ```
27
+ jdk25-upgrade-blocker-lint file
28
+ ```
29
+
30
+ Node 18+. The same 18 rules as the VS Code extension, from a terminal or CI.
31
+
32
+ ## Free
33
+
34
+ - Lints the .java file you have open with all 18 rules, no key and no cap — the bundled 64-line fixture returns 23 findings, 15 errors and 8 warnings; the fixed copy returns 0.
35
+ - `--rules` lists every rule
36
+
37
+ ## With a licence ($29 once)
38
+
39
+ - Sweeps the whole workspace and writes the dated migration report file — every file, blocker, JEP and replacement — for the upgrade ticket; free for the first 7 days, then one key, commercial and CI-seat use included.
40
+
41
+ ```
42
+ @readystack/jdk25-upgrade-blocker-lint --dir ./templates --report html --out report.html
43
+ ```
44
+
45
+ A Java migration consultant bills $120-$200 an hour; a two-day audit of one repo is over $2,000.
46
+
47
+ ## Use from an AI agent (MCP)
48
+
49
+ Claude Code · Cursor · Windsurf · any MCP client - add to your MCP config:
50
+
51
+ ```json
52
+ { "mcpServers": { "jdk25-upgrade-blocker-lint": { "command": "npx", "args": ["-y", "@readystack/jdk25-upgrade-blocker-lint", "--mcp"] } } }
53
+ ```
54
+
55
+ Tools: `check_text` and `check_file` (free) · `check_dir` (licence; the full sweep is free for 7 days). The agent gets every finding with the line number.
56
+
57
+ ## Use in CI
58
+
59
+ ```yaml
60
+ - name: JDK 25 Upgrade Blocker Lint
61
+ run: npx -y @readystack/jdk25-upgrade-blocker-lint --dir . --ci
62
+ ```
63
+
64
+ (container: `docker run --rm -v "$PWD:/work" getreadystack/jdk25-upgrade-blocker-lint --dir /work --ci`)
65
+
66
+ Try the full run free for 7 days — no key needed. Then one licence, 7-day refund, no questions. Set `READYSTACK_LICENSE=<key>` or run `--license <key>` once.
67
+
68
+ [Get a licence](https://buy.polar.sh/polar_cl_YK2CXL7Hd4TEoxQ76oOVQpVAsrLK9zUpcK7oV2MKwJs)
69
+
70
+
71
+ <!-- jdk25 upgrade blocker lint -->
72
+
73
+
74
+ ## Install (PyPI)
75
+
76
+ ```
77
+ pip install readystack-jdk25-upgrade-blocker-lint
78
+ jdk25-upgrade-blocker-lint --help
79
+ ```
@@ -0,0 +1,60 @@
1
+ # JDK 25 Upgrade Blocker Lint
2
+
3
+ Finds the Java lines the new LTS removed — 18 rules; 23 findings in one 64-line fixture
4
+
5
+ ## Install
6
+
7
+ ```
8
+ jdk25-upgrade-blocker-lint file
9
+ ```
10
+
11
+ Node 18+. The same 18 rules as the VS Code extension, from a terminal or CI.
12
+
13
+ ## Free
14
+
15
+ - Lints the .java file you have open with all 18 rules, no key and no cap — the bundled 64-line fixture returns 23 findings, 15 errors and 8 warnings; the fixed copy returns 0.
16
+ - `--rules` lists every rule
17
+
18
+ ## With a licence ($29 once)
19
+
20
+ - Sweeps the whole workspace and writes the dated migration report file — every file, blocker, JEP and replacement — for the upgrade ticket; free for the first 7 days, then one key, commercial and CI-seat use included.
21
+
22
+ ```
23
+ @readystack/jdk25-upgrade-blocker-lint --dir ./templates --report html --out report.html
24
+ ```
25
+
26
+ A Java migration consultant bills $120-$200 an hour; a two-day audit of one repo is over $2,000.
27
+
28
+ ## Use from an AI agent (MCP)
29
+
30
+ Claude Code · Cursor · Windsurf · any MCP client - add to your MCP config:
31
+
32
+ ```json
33
+ { "mcpServers": { "jdk25-upgrade-blocker-lint": { "command": "npx", "args": ["-y", "@readystack/jdk25-upgrade-blocker-lint", "--mcp"] } } }
34
+ ```
35
+
36
+ Tools: `check_text` and `check_file` (free) · `check_dir` (licence; the full sweep is free for 7 days). The agent gets every finding with the line number.
37
+
38
+ ## Use in CI
39
+
40
+ ```yaml
41
+ - name: JDK 25 Upgrade Blocker Lint
42
+ run: npx -y @readystack/jdk25-upgrade-blocker-lint --dir . --ci
43
+ ```
44
+
45
+ (container: `docker run --rm -v "$PWD:/work" getreadystack/jdk25-upgrade-blocker-lint --dir /work --ci`)
46
+
47
+ Try the full run free for 7 days — no key needed. Then one licence, 7-day refund, no questions. Set `READYSTACK_LICENSE=<key>` or run `--license <key>` once.
48
+
49
+ [Get a licence](https://buy.polar.sh/polar_cl_YK2CXL7Hd4TEoxQ76oOVQpVAsrLK9zUpcK7oV2MKwJs)
50
+
51
+
52
+ <!-- jdk25 upgrade blocker lint -->
53
+
54
+
55
+ ## Install (PyPI)
56
+
57
+ ```
58
+ pip install readystack-jdk25-upgrade-blocker-lint
59
+ jdk25-upgrade-blocker-lint --help
60
+ ```
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "readystack-jdk25-upgrade-blocker-lint"
7
+ version = "1.0.3"
8
+ description = "Finds the Java lines the new LTS removed \u2014 18 rules; 23 findings in one 64-line fixture"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "Proprietary - see LICENSE.txt"}
12
+ keywords = ["java", "jdk", "migration", "jakarta", "linter", "upgrade"]
13
+ authors = [{name = "ReadyStack", email = "support@getreadystack.com"}]
14
+ classifiers = ["Programming Language :: Python :: 3", "Environment :: Console", "Topic :: Software Development :: Quality Assurance", "Operating System :: OS Independent"]
15
+
16
+ [project.urls]
17
+ Homepage = "https://getreadystack.com/tools/jdk25-upgrade-blocker-lint?ref=pypi"
18
+ Documentation = "https://getreadystack.com/tools/jdk25-upgrade-blocker-lint?ref=pypi"
19
+ Support = "https://getreadystack.com/support"
20
+
21
+ [project.scripts]
22
+ "jdk25-upgrade-blocker-lint" = "readystack_jdk25_upgrade_blocker_lint.cli:main"
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["src"]
26
+
27
+ [tool.setuptools.package-data]
28
+ "readystack_jdk25_upgrade_blocker_lint" = ["rules.json", "strings.json"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,135 @@
1
+ # cli.py — the same rules as the VS Code extension, from a terminal, a container or an MCP client. Generated by pypi_build.py.
2
+ import json, os, sys, time, datetime
3
+ from . import engine as ENGINE, license as lic
4
+ _HERE = os.path.dirname(os.path.abspath(__file__))
5
+ S = json.load(open(os.path.join(_HERE, 'strings.json'), encoding='utf-8'))
6
+ def scan(text, file=''):
7
+ r = ENGINE.check(str(text), {'today': datetime.date.today().isoformat(), 'path': file})
8
+ return [{'line': int(f.get('line') or 1), 'msg': str(f.get('msg') or f.get('check') or ''), 'fix': f.get('fix'), 'sev': 'error' if str(f.get('sev', 'error')).lower().startswith('err') else 'warn'} for f in r.get('findings', [])]
9
+ def is_text(p):
10
+ try:
11
+ with open(p, 'rb') as h: b = h.read(4096)
12
+ return os.path.getsize(p) <= 2 * 1024 * 1024 and b'\x00' not in b
13
+ except Exception: return False
14
+ def walk(d, exts, out):
15
+ try: ents = list(os.scandir(d))
16
+ except Exception: return out
17
+ for e in ents:
18
+ if e.name in ('node_modules', '.git') or e.name.startswith('.'): continue
19
+ if e.is_dir(): walk(e.path, exts, out)
20
+ elif (not exts or os.path.splitext(e.name)[1].lower() in exts) and is_text(e.path): out.append(e.path)
21
+ return out
22
+ def esc(s): return str(s).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
23
+ def csvq(s): return '"' + str('' if s is None else s).replace('"', '""') + '"'
24
+ RULE_N = len(ENGINE.RULES)
25
+ def render(rows, fmt):
26
+ if fmt == 'json': return json.dumps({'tool': S['name'], 'rules': RULE_N, 'files': rows}, indent=1)
27
+ if fmt == 'csv':
28
+ o = ['file,line,severity,message,fix']
29
+ for r in rows:
30
+ for h in r['hits']: o.append(','.join([csvq(r['file']), str(h['line']), h['sev'], csvq(h['msg']), csvq(h['fix'])]))
31
+ return '\n'.join(o)
32
+ if fmt == 'html':
33
+ n = sum(len(r['hits']) for r in rows)
34
+ o = '<!doctype html><meta charset="utf-8"><title>' + esc(S['name']) + ' report</title><style>body{font:14px system-ui;margin:24px}table{border-collapse:collapse}td,th{border:1px solid #ddd;padding:4px 8px}</style>'
35
+ o += '<h1>' + esc(S['name']) + '</h1><p>' + str(len(rows)) + ' files · ' + str(n) + ' findings · ' + str(RULE_N) + ' rules</p><table><tr><th>file</th><th>line</th><th>sev</th><th>message</th><th>fix</th></tr>'
36
+ for r in rows:
37
+ for h in r['hits']: o += '<tr><td><code>' + esc(r['file']) + '</code></td><td>' + str(h['line']) + '</td><td>' + esc(h['sev']) + '</td><td>' + esc(h['msg']) + '</td><td>' + esc(h['fix'] or '') + '</td></tr>'
38
+ return o + '</table>'
39
+ o = ''
40
+ for r in rows:
41
+ o += r['file'] + '\n'
42
+ for h in r['hits']: o += ' ' + str(h['line']).rjust(5) + ' ' + h['sev'].ljust(5) + ' ' + h['msg'] + ('\n -> ' + h['fix'] if h['fix'] else '') + '\n'
43
+ return o
44
+ def trial_state():
45
+ if os.environ.get('READYSTACK_NO_TRIAL'): return {'active': False}
46
+ try:
47
+ p = os.path.join(os.path.dirname(lic.store_path()), S['bin'] + '.trial.json'); t = None
48
+ try: t = json.load(open(p, encoding='utf-8'))
49
+ except Exception: pass
50
+ if not t or not t.get('until'):
51
+ t = {'until': (datetime.date.today() + datetime.timedelta(days=7)).isoformat()}; os.makedirs(os.path.dirname(p), exist_ok=True); json.dump(t, open(p, 'w', encoding='utf-8'))
52
+ return {'active': datetime.date.fromisoformat(t['until']) >= datetime.date.today(), 'until': t['until']}
53
+ except Exception: return {'active': False}
54
+ def mcp_serve():
55
+ os.environ['READYSTACK_MCP'] = '1'
56
+ def send(o): sys.stdout.write(json.dumps(o) + '\n'); sys.stdout.flush()
57
+ tools = [
58
+ {'name': 'check_text', 'description': S['name'] + ' - run all ' + str(RULE_N) + ' checks on a text (free)', 'inputSchema': {'type': 'object', 'properties': {'text': {'type': 'string', 'description': 'file contents'}, 'path': {'type': 'string'}}, 'required': ['text']}},
59
+ {'name': 'check_file', 'description': S['name'] + ' - run all checks on one file by path (free)', 'inputSchema': {'type': 'object', 'properties': {'path': {'type': 'string'}}, 'required': ['path']}},
60
+ {'name': 'check_dir', 'description': S['name'] + ' - sweep a folder and return every finding (licence; the full run is free for 7 days)', 'inputSchema': {'type': 'object', 'properties': {'dir': {'type': 'string'}, 'ext': {'type': 'string'}}, 'required': ['dir']}},
61
+ ]
62
+ def result(i, rows): send({'jsonrpc': '2.0', 'id': i, 'result': {'content': [{'type': 'text', 'text': render(rows, 'text')}], 'structuredContent': {'tool': S['name'], 'rules': RULE_N, 'files': rows}}})
63
+ def fail(i, text): send({'jsonrpc': '2.0', 'id': i, 'result': {'content': [{'type': 'text', 'text': text}], 'isError': True}})
64
+ for line in sys.stdin:
65
+ try: m = json.loads(line)
66
+ except Exception: continue
67
+ i, method = m.get('id'), m.get('method')
68
+ if method == 'initialize': send({'jsonrpc': '2.0', 'id': i, 'result': {'protocolVersion': '2025-06-18', 'capabilities': {'tools': {}}, 'serverInfo': {'name': 'readystack-' + S['bin'], 'version': '1.0.3'}}}); continue
69
+ if method in ('notifications/initialized', 'ping'):
70
+ if i is not None: send({'jsonrpc': '2.0', 'id': i, 'result': {}})
71
+ continue
72
+ if method == 'tools/list': send({'jsonrpc': '2.0', 'id': i, 'result': {'tools': tools}}); continue
73
+ if method == 'tools/call':
74
+ name = (m.get('params') or {}).get('name'); args = (m.get('params') or {}).get('arguments') or {}
75
+ try:
76
+ if name == 'check_text': result(i, [{'file': args.get('path') or '(text)', 'hits': scan(str(args.get('text') or ''), args.get('path') or '')}]); continue
77
+ if name == 'check_file': result(i, [{'file': args['path'], 'hits': scan(open(args['path'], encoding='utf-8', errors='replace').read(), args['path'])}]); continue
78
+ if name == 'check_dir':
79
+ r = lic.ensure()
80
+ if not r['ok'] and not trial_state()['active']: fail(i, S['need_key'] + ' Get a licence ($' + str(S['price']) + ', once, 7-day refund): ' + lic.BUY_URL); continue
81
+ files = walk(args['dir'], [args['ext']] if args.get('ext') else (S.get('exts') or []), [])
82
+ rows = []
83
+ for f in files:
84
+ try: rows.append({'file': f, 'hits': scan(open(f, encoding='utf-8', errors='replace').read(), f)})
85
+ except Exception as e: rows.append({'file': f, 'hits': [], 'error': str(e)})
86
+ result(i, rows); continue
87
+ send({'jsonrpc': '2.0', 'id': i, 'error': {'code': -32601, 'message': 'unknown tool ' + str(name)}})
88
+ except Exception as e: fail(i, str(e))
89
+ continue
90
+ if i is not None: send({'jsonrpc': '2.0', 'id': i, 'error': {'code': -32601, 'message': 'method not found: ' + str(method)}})
91
+ def help_text():
92
+ b = S['bin']
93
+ return '\n'.join([S['name'] + ' - ' + S['subtitle'], '', 'Usage: ' + b + ' <file> [more files] check the files you name (free, every rule)',
94
+ ' ' + b + ' --dir <folder> [--ext .py] scan a whole folder (licence)',
95
+ ' ' + b + ' ... --report csv|json|html [--out file] export a report (licence)',
96
+ ' ' + b + ' ... --ci exit 1 when an error-level finding exists (licence)',
97
+ ' ' + b + ' --license <key> store your licence key (or set READYSTACK_LICENSE)',
98
+ ' ' + b + ' --rules list the ' + str(RULE_N) + ' rules',
99
+ ' ' + b + ' --mcp run as an MCP server (stdio) for Claude Code / Cursor / Windsurf - free checks, folder sweep needs a licence', '',
100
+ 'Free: ' + S['free'], 'Licence ($' + str(S['price']) + ', once, 7-day refund): ' + S['paid'], 'Get a licence: ' + lic.BUY_URL, ''])
101
+ def main(argv=None):
102
+ a = list(sys.argv[1:] if argv is None else argv)
103
+ try:
104
+ feed = lic.pull_feed()
105
+ if feed and isinstance(feed.get('rules'), list): ENGINE.RULES.extend(feed['rules'])
106
+ except Exception: pass
107
+ def get(k):
108
+ return a[a.index(k) + 1] if k in a and a.index(k) + 1 < len(a) else None
109
+ if '--mcp' in a: mcp_serve(); return 0
110
+ if not a or '--help' in a or '-h' in a: sys.stdout.write(help_text()); return 0
111
+ if '--rules' in a:
112
+ sys.stdout.write('\n'.join(str(i + 1).rjust(3) + ' [' + str(r.get('sev', 'warn')) + '] ' + str(r.get('msg') or r.get('id') or '') for i, r in enumerate(ENGINE.RULES)) + '\n'); return 0
113
+ if '--license' in a:
114
+ r = lic.ensure(get('--license')); sys.stdout.write(('Licence stored: ' + lic.store_path() if r['ok'] else 'Licence not accepted (' + str(r.get('why')) + ')') + '\n'); return 0 if r['ok'] else 1
115
+ d, fmt, out, ci = get('--dir'), get('--report'), get('--out'), '--ci' in a
116
+ exts = [get('--ext')] if '--ext' in a else (S.get('exts') or [])
117
+ if d or fmt or ci:
118
+ r = lic.ensure()
119
+ if not r['ok']:
120
+ t = trial_state()
121
+ if t['active']: sys.stderr.write('Trial: the full run is free until ' + t['until'] + ' - after that $' + str(S['price']) + ' once (7-day refund). Get a licence: ' + lic.BUY_URL + '\n')
122
+ else: sys.stderr.write(S['need_key'] + '\n set READYSTACK_LICENSE=<key> or ' + S['bin'] + ' --license <key>\n Get a licence ($' + str(S['price']) + ', once): ' + lic.BUY_URL + '\n'); return 2
123
+ flags = {'--dir', '--report', '--out', '--ext', '--license'}
124
+ files = walk(d, exts, []) if d else [x for i, x in enumerate(a) if not x.startswith('--') and (i == 0 or a[i - 1] not in flags)]
125
+ if not files: sys.stderr.write('No files. ' + S['bin'] + ' --help\n'); return 2
126
+ rows = []
127
+ for f in files:
128
+ try: rows.append({'file': f, 'hits': scan(open(f, encoding='utf-8', errors='replace').read(), f)})
129
+ except Exception as e: rows.append({'file': f, 'hits': [], 'error': str(e)})
130
+ text = render(rows, fmt or 'text')
131
+ if out: open(out, 'w', encoding='utf-8').write(text)
132
+ else: sys.stdout.write(text if text.endswith('\n') else text + '\n')
133
+ errors = sum(1 for r in rows for h in r['hits'] if h['sev'] == 'error')
134
+ return 1 if (ci and errors) else 0
135
+ if __name__ == '__main__': sys.exit(main())
@@ -0,0 +1,30 @@
1
+ # engine.py — same rules as the VS Code extension and the npm CLI (rules.json), pure Python. Generated by pypi_build.py.
2
+ import json, os, re
3
+ _HERE = os.path.dirname(os.path.abspath(__file__))
4
+ RULES = json.load(open(os.path.join(_HERE, 'rules.json'), encoding='utf-8'))
5
+ def _py(p): return re.sub(r'\(\?<(?![=!])', '(?P<', p)
6
+ def _c(r):
7
+ if '_re' not in r:
8
+ r['_re'] = re.compile(_py(r['re'])); r['_ctx'] = re.compile(_py(r['ctx']), re.M) if r.get('ctx') else None
9
+ r['_prev'] = re.compile(_py(r['prev_not'])) if r.get('prev_not') else None; r['_next'] = re.compile(_py(r['next_re'])) if r.get('next_re') else None
10
+ return r
11
+ def _code(s): t = s.strip(); return t != '' and not t.startswith('#')
12
+ def check(text, opts=None):
13
+ opts = opts or {}; src = '' if text is None else str(text); lines = src.split('\n'); lines = [l.rstrip('\r') for l in lines]; out = []
14
+ for r in RULES:
15
+ r = _c(r)
16
+ if r['_ctx'] and not r['_ctx'].search(src): continue
17
+ for n, line in enumerate(lines):
18
+ if not _code(line) or not r['_re'].search(line): continue
19
+ if r['_prev']:
20
+ p = n - 1
21
+ while p >= 0 and lines[p].strip() == '': p -= 1
22
+ if p >= 0 and r['_prev'].search(lines[p]): continue
23
+ if r['_next']:
24
+ q = n + 1
25
+ while q < len(lines) and lines[q].strip() == '': q += 1
26
+ if q >= len(lines) or not r['_next'].search(lines[q]): continue
27
+ out.append({'check': r['id'], 'sev': r.get('sev', 'warn'), 'line': n + 1, 'msg': r.get('msg', ''), 'fix': r.get('fix'), 'text': line.strip()[:160]})
28
+ out.sort(key=lambda f: f['line'])
29
+ return {'findings': out, 'scanned_lines': len(lines), 'rule_count': len(RULES),
30
+ 'errors': sum(1 for f in out if f['sev'] == 'error'), 'warnings': sum(1 for f in out if f['sev'] == 'warn'), 'today': opts.get('today', '')}
@@ -0,0 +1,61 @@
1
+ # license.py — Polar licence check (same contract as license.js). Generated by pypi_build.py.
2
+ import json, os, sys, time, urllib.request
3
+ ORG_ID = 'a5cdf664-d8e7-4f87-8895-056717aaba17'; BENEFIT_ID = 'ecc7be06-5c7b-418a-abf2-9204bdc20568'; BUY_URL = 'https://buy.polar.sh/polar_cl_YK2CXL7Hd4TEoxQ76oOVQpVAsrLK9zUpcK7oV2MKwJs'; ALL_BENEFIT_ID = '22692551-5203-4467-b1a3-e33cdba6589d'; SLUG = 'jdk25-upgrade-blocker-lint'
4
+ GRACE = 30 * 86400; RECHECK = 7 * 86400
5
+ def store_path():
6
+ return os.path.join(os.environ.get('READYSTACK_HOME') or os.path.join(os.path.expanduser('~'), '.config', 'readystack'), SLUG + '.json')
7
+ def load():
8
+ try: return json.load(open(store_path(), encoding='utf-8'))
9
+ except Exception: return {}
10
+ def save(o):
11
+ try:
12
+ os.makedirs(os.path.dirname(store_path()), exist_ok=True); json.dump(o, open(store_path(), 'w', encoding='utf-8'))
13
+ except Exception: pass
14
+ def _post(url, body, headers, timeout):
15
+ req = urllib.request.Request(url, data=json.dumps(body).encode(), headers=dict({'content-type': 'application/json'}, **headers), method='POST')
16
+ return urllib.request.urlopen(req, timeout=timeout)
17
+ def _validate1(key, ben):
18
+ if not ORG_ID: return {'ok': False, 'offline': False}
19
+ body = {'key': key, 'organization_id': ORG_ID}
20
+ if len(ben) == 36: body['benefit_id'] = ben
21
+ try:
22
+ r = _post('https://api.polar.sh/v1/customer-portal/license-keys/validate', body, {'polar-version': '2026-04', 'user-agent': 'readystack-py/' + SLUG}, 8)
23
+ if r.status != 200: return {'ok': False, 'offline': False}
24
+ j = json.loads(r.read().decode() or '{}'); return {'ok': bool(j and (j.get('status') == 'granted' or j.get('valid') is True or j.get('id'))), 'offline': False}
25
+ except urllib.error.HTTPError: return {'ok': False, 'offline': False}
26
+ except Exception: return {'ok': False, 'offline': True}
27
+ def validate(key):
28
+ r = _validate1(key, BENEFIT_ID)
29
+ if r['ok'] or r['offline'] or len(ALL_BENEFIT_ID) != 36: return r
30
+ return _validate1(key, ALL_BENEFIT_ID)
31
+ _pinged = [False]
32
+ def ping_paywall(why):
33
+ try:
34
+ if _pinged[0]: return
35
+ _pinged[0] = True
36
+ if os.environ.get('DO_NOT_TRACK') == '1' or os.environ.get('READYSTACK_NO_TELEMETRY') or os.environ.get('CI'): return
37
+ if not (sys.stdout.isatty() or sys.stdin.isatty() or os.environ.get('READYSTACK_MCP') == '1'): return
38
+ _post('https://getreadystack.com/api/ev', {'t': 'paywall', 'slug': SLUG, 'src': 'mcp' if os.environ.get('READYSTACK_MCP') == '1' else 'cli', 'why': why or 'no_key'}, {'user-agent': 'readystack-py/' + SLUG}, 3).read()
39
+ except Exception: pass
40
+ def ensure(explicit=None):
41
+ st = load(); key = explicit or os.environ.get('READYSTACK_LICENSE') or st.get('key')
42
+ if not key: ping_paywall('no_key'); return {'ok': False, 'why': 'no_key'}
43
+ age = time.time() - (st.get('okAt') or 0)
44
+ if not explicit and st.get('key') == key and age < RECHECK: return {'ok': True, 'cached': True}
45
+ r = validate(str(key).strip())
46
+ if r['ok']: save({'key': str(key).strip(), 'okAt': time.time()}); return {'ok': True}
47
+ if r['offline'] and st.get('key') == key and age < GRACE: return {'ok': True, 'offline': True}
48
+ if not r['offline']: ping_paywall('invalid')
49
+ return {'ok': False, 'why': 'offline' if r['offline'] else 'invalid'}
50
+ def pull_feed():
51
+ st = load(); key = os.environ.get('READYSTACK_LICENSE') or st.get('key'); cached = st.get('feed')
52
+ if not key: return cached
53
+ if cached and time.time() - (st.get('feedAt') or 0) < RECHECK: return cached
54
+ try:
55
+ import urllib.parse
56
+ r = urllib.request.urlopen(urllib.request.Request('https://getreadystack.com/api/rules/' + urllib.parse.quote(SLUG) + '?key=' + urllib.parse.quote(key), headers={'user-agent': 'readystack-py'}), timeout=8)
57
+ if r.status != 200: return cached
58
+ j = json.loads(r.read().decode() or '{}')
59
+ if not isinstance(j, dict) or not isinstance(j.get('rules'), list): return cached
60
+ s2 = load(); s2['feed'] = j; s2['feedAt'] = time.time(); save(s2); return j
61
+ except Exception: return cached
@@ -0,0 +1,110 @@
1
+ [
2
+ {
3
+ "id": "security_manager_set",
4
+ "re": "System\\s*\\.\\s*setSecurityManager\\s*\\(",
5
+ "sev": "error",
6
+ "msg": "System.setSecurityManager() throws UnsupportedOperationException on JDK 24 and later — JEP 486 permanently disabled the Security Manager. Delete the call and move the check into your own authorization layer."
7
+ },
8
+ {
9
+ "id": "access_controller",
10
+ "re": "AccessController\\s*\\.\\s*doPrivileged|import\\s+java\\.security\\.AccessController",
11
+ "sev": "error",
12
+ "msg": "AccessController.doPrivileged is terminally deprecated and grants nothing (JEP 486, JDK 24). The privileged block now runs with the caller's own access. Inline the body and drop the wrapper."
13
+ },
14
+ {
15
+ "id": "security_policy",
16
+ "re": "java\\.security\\.Policy|Policy\\s*\\.\\s*setPolicy\\s*\\(|SecurityManager\\s+[A-Za-z_]",
17
+ "sev": "error",
18
+ "msg": "java.security.Policy and SecurityManager are terminally deprecated with JEP 486 (JDK 24). Your .policy file grants nothing at run time — the permissions in it are no longer enforced."
19
+ },
20
+ {
21
+ "id": "unsafe_memory",
22
+ "re": "sun\\.misc\\.Unsafe|Unsafe\\s*\\.\\s*getUnsafe\\s*\\(",
23
+ "sev": "error",
24
+ "msg": "sun.misc.Unsafe memory-access methods are deprecated for removal (JEP 471, JDK 23) and warn at run time by default since JDK 24 (JEP 498). Replace with java.lang.invoke.VarHandle, or the FFM API in java.lang.foreign for off-heap memory."
25
+ },
26
+ {
27
+ "id": "javax_to_jakarta",
28
+ "re": "import\\s+javax\\.(persistence|servlet|ws\\.rs|enterprise|inject|validation|transaction|ejb|jms|mail|annotation\\.(Resource|PostConstruct|PreDestroy))",
29
+ "sev": "error",
30
+ "msg": "Jakarta EE 9 renamed this package to jakarta.*. A Jakarta EE 10/11 runtime (Spring Boot 3, Quarkus 3, Payara 6, WildFly 27+) never scans javax.* here, so the bean or endpoint silently disappears. Rename the import to jakarta.*"
31
+ },
32
+ {
33
+ "id": "jaxb_removed",
34
+ "re": "javax\\.xml\\.bind|com\\.sun\\.xml\\.bind",
35
+ "sev": "error",
36
+ "msg": "javax.xml.bind (JAXB) was removed from the JDK in Java 11 (JEP 320). On JDK 21/25 this compiles only if you add the jakarta.xml.bind-api and a runtime yourself — and the package is jakarta.xml.bind now."
37
+ },
38
+ {
39
+ "id": "applet_api",
40
+ "re": "java\\.applet|\\bextends\\s+(J?Applet)\\b",
41
+ "sev": "error",
42
+ "msg": "The Applet API was removed outright in JDK 25 (JEP 504). There is no replacement class — this file cannot compile on the new LTS."
43
+ },
44
+ {
45
+ "id": "thread_stop",
46
+ "re": "(?:[Tt]hread[A-Za-z0-9_]*|[a-z][A-Za-z0-9_]*Thread)\\s*\\.\\s*(?:stop|suspend|resume)\\s*\\(\\s*\\)",
47
+ "sev": "error",
48
+ "msg": "Thread.stop() throws UnsupportedOperationException since JDK 20, and suspend()/resume() are terminally deprecated and throw on current JDKs. Use interrupt() plus a volatile flag the worker loop reads."
49
+ },
50
+ {
51
+ "id": "finalize_override",
52
+ "re": "(?:protected|public)\\s+void\\s+finalize\\s*\\(\\s*\\)",
53
+ "sev": "error",
54
+ "msg": "Overriding finalize() is deprecated for removal (JEP 421) and finalization is already disabled by default in some runs via --finalization=disabled — your cleanup may never execute. Move it to try-with-resources or java.lang.ref.Cleaner."
55
+ },
56
+ {
57
+ "id": "jdk_internal_import",
58
+ "re": "import\\s+(?:sun\\.(?!misc\\.Unsafe)|com\\.sun\\.(?!xml\\.bind)|jdk\\.internal\\.)",
59
+ "sev": "error",
60
+ "msg": "JDK-internal packages are strongly encapsulated since JDK 17 (JEP 403). This import throws IllegalAccessError at run time unless every launch carries a matching --add-exports flag."
61
+ },
62
+ {
63
+ "id": "run_finalization",
64
+ "re": "(?:System|Runtime\\s*\\.\\s*getRuntime\\s*\\(\\s*\\))\\s*\\.\\s*runFinalization\\s*\\(",
65
+ "sev": "error",
66
+ "msg": "System.runFinalization() and Runtime.runFinalization() are deprecated for removal (JEP 421). They are no-ops once finalization is disabled — the resource you expect to be released is not released."
67
+ },
68
+ {
69
+ "id": "subject_do_as",
70
+ "re": "Subject\\s*\\.\\s*doAs(?:Privileged)?\\s*\\(",
71
+ "sev": "warn",
72
+ "msg": "Subject.doAs is deprecated for removal together with the Security Manager. Use Subject.callAs, added in JDK 18, which takes a Callable and keeps the Subject on the current thread."
73
+ },
74
+ {
75
+ "id": "native_access",
76
+ "re": "System\\s*\\.\\s*(?:loadLibrary|load)\\s*\\(|\\bnative\\s+(?:[A-Za-z_$][\\w$<>\\[\\]\\.]*)\\s+[A-Za-z_$]",
77
+ "sev": "warn",
78
+ "msg": "Loading native code prints a warning on JDK 24+ unless the module is named in --enable-native-access (JEP 472), and that warning is planned to become an error. Add the flag or an Enable-Native-Access manifest entry now."
79
+ },
80
+ {
81
+ "id": "set_accessible",
82
+ "re": "setAccessible\\s*\\(\\s*true\\s*\\)",
83
+ "sev": "warn",
84
+ "msg": "setAccessible(true) against a java.* class throws InaccessibleObjectException under the strong encapsulation of JDK 17+ (JEP 403). Check the target is your own class, or add an explicit --add-opens for the exact package."
85
+ },
86
+ {
87
+ "id": "url_constructor",
88
+ "re": "new\\s+URL\\s*\\(",
89
+ "sev": "warn",
90
+ "msg": "The java.net.URL constructors are deprecated since JDK 20 because they accept malformed input without validating it. Use URI.create(s).toURL(), which rejects the bad string instead of carrying it."
91
+ },
92
+ {
93
+ "id": "wrapper_constructor",
94
+ "re": "new\\s+(?:Integer|Long|Double|Float|Short|Byte|Character|Boolean)\\s*\\(",
95
+ "sev": "warn",
96
+ "msg": "Primitive wrapper constructors are deprecated for removal since JDK 9. Use Integer.valueOf(x) or Integer.parseInt(s) — valueOf caches small values, the constructor allocates every time."
97
+ },
98
+ {
99
+ "id": "class_new_instance",
100
+ "re": "(?:\\.class|getClass\\(\\s*\\)|Class\\.forName\\([^)]*\\))\\s*\\.\\s*newInstance\\s*\\(\\s*\\)",
101
+ "sev": "warn",
102
+ "msg": "Class.newInstance() is deprecated since JDK 9 because it rethrows checked exceptions the compiler cannot see. Use getDeclaredConstructor().newInstance()."
103
+ },
104
+ {
105
+ "id": "locale_constructor",
106
+ "re": "new\\s+Locale\\s*\\(",
107
+ "sev": "warn",
108
+ "msg": "The java.util.Locale constructors are deprecated since JDK 19. Use Locale.of(language, country), which validates and interns the tag."
109
+ }
110
+ ]
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "JDK 25 Upgrade Blocker Lint",
3
+ "subtitle": "Finds the Java lines the new LTS removed — 18 rules; 23 findings in one 64-line fixture",
4
+ "bin": "jdk25-upgrade-blocker-lint",
5
+ "price": 29,
6
+ "free": "Lints the .java file you have open with all 18 rules, no key and no cap — the bundled 64-line fixture returns 23 findings, 15 errors and 8 warnings; the fixed copy returns 0.",
7
+ "paid": "Sweeps the whole workspace and writes the dated migration report file — every file, blocker, JEP and replacement — for the upgrade ticket; free for the first 7 days, then one key, commercial and CI-seat use included.",
8
+ "need_key": "This option needs a licence (JDK 25 Upgrade Blocker Lint).",
9
+ "exts": []
10
+ }
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: readystack-jdk25-upgrade-blocker-lint
3
+ Version: 1.0.3
4
+ Summary: Finds the Java lines the new LTS removed — 18 rules; 23 findings in one 64-line fixture
5
+ Author-email: ReadyStack <support@getreadystack.com>
6
+ License: Proprietary - see LICENSE.txt
7
+ Project-URL: Homepage, https://getreadystack.com/tools/jdk25-upgrade-blocker-lint?ref=pypi
8
+ Project-URL: Documentation, https://getreadystack.com/tools/jdk25-upgrade-blocker-lint?ref=pypi
9
+ Project-URL: Support, https://getreadystack.com/support
10
+ Keywords: java,jdk,migration,jakarta,linter,upgrade
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Environment :: Console
13
+ Classifier: Topic :: Software Development :: Quality Assurance
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.txt
18
+ Dynamic: license-file
19
+
20
+ # JDK 25 Upgrade Blocker Lint
21
+
22
+ Finds the Java lines the new LTS removed — 18 rules; 23 findings in one 64-line fixture
23
+
24
+ ## Install
25
+
26
+ ```
27
+ jdk25-upgrade-blocker-lint file
28
+ ```
29
+
30
+ Node 18+. The same 18 rules as the VS Code extension, from a terminal or CI.
31
+
32
+ ## Free
33
+
34
+ - Lints the .java file you have open with all 18 rules, no key and no cap — the bundled 64-line fixture returns 23 findings, 15 errors and 8 warnings; the fixed copy returns 0.
35
+ - `--rules` lists every rule
36
+
37
+ ## With a licence ($29 once)
38
+
39
+ - Sweeps the whole workspace and writes the dated migration report file — every file, blocker, JEP and replacement — for the upgrade ticket; free for the first 7 days, then one key, commercial and CI-seat use included.
40
+
41
+ ```
42
+ @readystack/jdk25-upgrade-blocker-lint --dir ./templates --report html --out report.html
43
+ ```
44
+
45
+ A Java migration consultant bills $120-$200 an hour; a two-day audit of one repo is over $2,000.
46
+
47
+ ## Use from an AI agent (MCP)
48
+
49
+ Claude Code · Cursor · Windsurf · any MCP client - add to your MCP config:
50
+
51
+ ```json
52
+ { "mcpServers": { "jdk25-upgrade-blocker-lint": { "command": "npx", "args": ["-y", "@readystack/jdk25-upgrade-blocker-lint", "--mcp"] } } }
53
+ ```
54
+
55
+ Tools: `check_text` and `check_file` (free) · `check_dir` (licence; the full sweep is free for 7 days). The agent gets every finding with the line number.
56
+
57
+ ## Use in CI
58
+
59
+ ```yaml
60
+ - name: JDK 25 Upgrade Blocker Lint
61
+ run: npx -y @readystack/jdk25-upgrade-blocker-lint --dir . --ci
62
+ ```
63
+
64
+ (container: `docker run --rm -v "$PWD:/work" getreadystack/jdk25-upgrade-blocker-lint --dir /work --ci`)
65
+
66
+ Try the full run free for 7 days — no key needed. Then one licence, 7-day refund, no questions. Set `READYSTACK_LICENSE=<key>` or run `--license <key>` once.
67
+
68
+ [Get a licence](https://buy.polar.sh/polar_cl_YK2CXL7Hd4TEoxQ76oOVQpVAsrLK9zUpcK7oV2MKwJs)
69
+
70
+
71
+ <!-- jdk25 upgrade blocker lint -->
72
+
73
+
74
+ ## Install (PyPI)
75
+
76
+ ```
77
+ pip install readystack-jdk25-upgrade-blocker-lint
78
+ jdk25-upgrade-blocker-lint --help
79
+ ```
@@ -0,0 +1,14 @@
1
+ LICENSE.txt
2
+ README.md
3
+ pyproject.toml
4
+ src/readystack_jdk25_upgrade_blocker_lint/__init__.py
5
+ src/readystack_jdk25_upgrade_blocker_lint/cli.py
6
+ src/readystack_jdk25_upgrade_blocker_lint/engine.py
7
+ src/readystack_jdk25_upgrade_blocker_lint/license.py
8
+ src/readystack_jdk25_upgrade_blocker_lint/rules.json
9
+ src/readystack_jdk25_upgrade_blocker_lint/strings.json
10
+ src/readystack_jdk25_upgrade_blocker_lint.egg-info/PKG-INFO
11
+ src/readystack_jdk25_upgrade_blocker_lint.egg-info/SOURCES.txt
12
+ src/readystack_jdk25_upgrade_blocker_lint.egg-info/dependency_links.txt
13
+ src/readystack_jdk25_upgrade_blocker_lint.egg-info/entry_points.txt
14
+ src/readystack_jdk25_upgrade_blocker_lint.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ jdk25-upgrade-blocker-lint = readystack_jdk25_upgrade_blocker_lint.cli:main