sourcecode 1.35.32__py3-none-any.whl → 1.35.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sourcecode/__init__.py +1 -1
- sourcecode/architecture_analyzer.py +1 -1
- sourcecode/classifier.py +4 -0
- sourcecode/cli.py +276 -200
- sourcecode/dependency_analyzer.py +22 -1
- sourcecode/detectors/java.py +30 -1
- sourcecode/migrate_check.py +23 -8
- sourcecode/path_filters.py +2 -1
- sourcecode/prepare_context.py +1 -1
- sourcecode/rename_refactor.py +15 -2
- sourcecode/repository_ir.py +60 -8
- sourcecode/spring_impact.py +2 -2
- sourcecode/spring_model.py +7 -0
- sourcecode/spring_security_audit.py +3 -6
- sourcecode/spring_tx_analyzer.py +1 -1
- {sourcecode-1.35.32.dist-info → sourcecode-1.35.34.dist-info}/METADATA +149 -9
- {sourcecode-1.35.32.dist-info → sourcecode-1.35.34.dist-info}/RECORD +20 -20
- {sourcecode-1.35.32.dist-info → sourcecode-1.35.34.dist-info}/WHEEL +0 -0
- {sourcecode-1.35.32.dist-info → sourcecode-1.35.34.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.35.32.dist-info → sourcecode-1.35.34.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
|
@@ -237,7 +237,7 @@ class ArchitectureAnalyzer:
|
|
|
237
237
|
return ArchitectureAnalysis(
|
|
238
238
|
requested=True,
|
|
239
239
|
pattern="unknown",
|
|
240
|
-
limitations=["
|
|
240
|
+
limitations=["Architecture not inferred: insufficient source files in project"],
|
|
241
241
|
evidence=[{"type": "none", "paths": [], "reason": "insufficient source files", "confidence": "high"}],
|
|
242
242
|
tentative=False,
|
|
243
243
|
)
|
sourcecode/classifier.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -1227,159 +1227,164 @@ def main(
|
|
|
1227
1227
|
except Exception:
|
|
1228
1228
|
pass
|
|
1229
1229
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
)
|
|
1230
|
+
_excl_key = (
|
|
1231
|
+
",".join(sorted(e.strip() for e in exclude.split(",") if e.strip()))
|
|
1232
|
+
if exclude else ""
|
|
1233
|
+
)
|
|
1235
1234
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1235
|
+
# ── Core (analysis) flags: affect which analyzers run + scan config ──
|
|
1236
|
+
# Use effective_depth (not raw depth) so Java auto-adjustment is captured.
|
|
1237
|
+
# acv = ANALYZER_CACHE_VERSION — bumped only when analysis logic/schema
|
|
1238
|
+
# changes, NOT on every package release. Prevents patch-bump cache wipes.
|
|
1239
|
+
_core_flags_str = (
|
|
1240
|
+
f"acv={_cache_mod.ANALYZER_CACHE_VERSION},"
|
|
1241
|
+
f"dep={dependencies},gm={graph_modules},"
|
|
1242
|
+
f"docs={docs},fm={full_metrics},sem={semantics},"
|
|
1243
|
+
f"arch={architecture},gc={git_context},em={env_map},"
|
|
1244
|
+
f"cn={code_notes},"
|
|
1245
|
+
f"ex={_excl_key},depth={effective_depth}"
|
|
1246
|
+
)
|
|
1247
|
+
_core_h = _hashlib.sha256(_core_flags_str.encode()).hexdigest()[:8]
|
|
1248
|
+
if _git_sha and _git_root_str:
|
|
1249
1249
|
_core_key = f"{_git_sha}-{_core_h}"
|
|
1250
|
-
|
|
1251
|
-
#
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1250
|
+
else:
|
|
1251
|
+
# No git history (untracked/no-commit repo) — stable synthetic key
|
|
1252
|
+
# scoped per repo path via cache_dir(); invalidated by --no-cache or cache clear.
|
|
1253
|
+
_core_key = f"nogit-{_core_h}"
|
|
1254
|
+
|
|
1255
|
+
# ── View flags: output presentation only (no re-analysis needed) ──
|
|
1256
|
+
_view_flags_str = (
|
|
1257
|
+
f"c={compact},ag={agent},mode={mode},fmt={format},full={full},"
|
|
1258
|
+
f"co={changed_only},tree={tree},nt={no_tree},"
|
|
1259
|
+
f"rb={rank_by},sym={symbol},ep={entrypoints_only},"
|
|
1260
|
+
f"nr={no_redact},gd={graph_detail},dd={docs_depth},"
|
|
1261
|
+
f"mn={max_nodes},ge={graph_edges},mi={max_importers},"
|
|
1262
|
+
f"eg={emit_graph}"
|
|
1263
|
+
)
|
|
1264
|
+
_view_h = _hashlib.sha256(_view_flags_str.encode()).hexdigest()[:8]
|
|
1265
|
+
|
|
1266
|
+
# ── Lookup ──────────────────────────────────────────────────────
|
|
1267
|
+
# Step 1: try L1 to obtain the core_hash needed for L2 key
|
|
1268
|
+
_l1_result = _cache_mod.read_core(target, _core_key)
|
|
1269
|
+
|
|
1270
|
+
# P1-A: --env-map misses L1 when base (em=False) exists.
|
|
1271
|
+
# Try the base key so env analysis can be injected lazily (<1 s)
|
|
1272
|
+
# instead of triggering a 17 s full rescan.
|
|
1273
|
+
_l1_needs_env_inject = False
|
|
1274
|
+
if _l1_result is None and env_map:
|
|
1275
|
+
_base_flags = _core_flags_str.replace(",em=True,", ",em=False,")
|
|
1276
|
+
_base_h8 = _hashlib.sha256(_base_flags.encode()).hexdigest()[:8]
|
|
1277
|
+
_sha_prefix = _git_sha if _git_sha else "nogit"
|
|
1278
|
+
_base_key = f"{_sha_prefix}-{_base_h8}"
|
|
1279
|
+
_base_result = _cache_mod.read_core(target, _base_key)
|
|
1280
|
+
if _base_result is not None:
|
|
1281
|
+
_l1_result = _base_result
|
|
1282
|
+
_l1_needs_env_inject = True
|
|
1283
|
+
|
|
1284
|
+
if _l1_result is not None:
|
|
1285
|
+
_core_dict_l1, _core_hash = _l1_result
|
|
1286
|
+
_view_key = f"{_core_hash}-{_view_h}"
|
|
1287
|
+
|
|
1288
|
+
# Step 2: try L2 (exact view match).
|
|
1289
|
+
# Skip L2 for --changed-only: the stored view is a previous
|
|
1290
|
+
# diff snapshot that is stale for the current diff.
|
|
1291
|
+
if not changed_only:
|
|
1292
|
+
_cache_hit_content = _cache_mod.read_view(target, _view_key)
|
|
1293
|
+
|
|
1294
|
+
# Step 3: L1 hit but L2 miss → rebuild view from core dict
|
|
1295
|
+
if _cache_hit_content is None:
|
|
1296
|
+
try:
|
|
1297
|
+
from sourcecode.serializer import build_view_from_core as _bvfc
|
|
1298
|
+
_rebuilt = _bvfc(
|
|
1299
|
+
_core_dict_l1,
|
|
1300
|
+
compact=compact,
|
|
1301
|
+
agent=agent,
|
|
1302
|
+
full=full,
|
|
1303
|
+
no_tree=no_tree,
|
|
1304
|
+
tree=tree,
|
|
1305
|
+
)
|
|
1306
|
+
# P1-A: inject env analysis when base L1 (em=False) was used.
|
|
1307
|
+
# EnvAnalyzer walks only env/config files — typically <1 s.
|
|
1308
|
+
if _rebuilt is not None and _l1_needs_env_inject and compact:
|
|
1309
|
+
try:
|
|
1310
|
+
from sourcecode.env_analyzer import EnvAnalyzer as _EnvA_p1a
|
|
1311
|
+
_env_r_p1a, _env_s_p1a = _EnvA_p1a().analyze(target, {})
|
|
1312
|
+
if _env_s_p1a and (getattr(_env_s_p1a, "total", 0) or _env_r_p1a):
|
|
1313
|
+
_es_p1a: dict = {
|
|
1314
|
+
"total": getattr(_env_s_p1a, "total", 0),
|
|
1315
|
+
"required": getattr(_env_s_p1a, "required_count", 0),
|
|
1316
|
+
}
|
|
1317
|
+
_cats = getattr(_env_s_p1a, "categories", None)
|
|
1318
|
+
if _cats:
|
|
1319
|
+
_es_p1a["categories"] = _cats
|
|
1320
|
+
_rebuilt = dict(_rebuilt)
|
|
1321
|
+
_rebuilt["env_summary"] = _es_p1a
|
|
1322
|
+
if _env_r_p1a:
|
|
1323
|
+
_sorted_er = sorted(
|
|
1324
|
+
_env_r_p1a,
|
|
1325
|
+
key=lambda e: (
|
|
1326
|
+
not getattr(e, "required", False),
|
|
1327
|
+
getattr(e, "key", ""),
|
|
1328
|
+
),
|
|
1329
|
+
)
|
|
1330
|
+
_rebuilt["env_map"] = [
|
|
1331
|
+
{
|
|
1332
|
+
"key": getattr(e, "key", ""),
|
|
1333
|
+
**({"required": True} if getattr(e, "required", False) else {}),
|
|
1334
|
+
**({"category": getattr(e, "category", None)} if getattr(e, "category", None) else {}),
|
|
1335
|
+
}
|
|
1336
|
+
for e in _sorted_er[:15]
|
|
1337
|
+
]
|
|
1338
|
+
except Exception:
|
|
1339
|
+
pass # env inject failed — continue without env data
|
|
1340
|
+
if _rebuilt is not None:
|
|
1341
|
+
# Apply redaction
|
|
1342
|
+
if not no_redact:
|
|
1343
|
+
from sourcecode.redactor import redact_dict as _red_l1
|
|
1344
|
+
_rebuilt = _red_l1(_rebuilt)
|
|
1345
|
+
# Apply output budget
|
|
1346
|
+
if agent:
|
|
1347
|
+
from sourcecode.output_budget import (
|
|
1348
|
+
trim_to_budget as _trim_l1,
|
|
1349
|
+
BUDGET_AGENT,
|
|
1350
|
+
)
|
|
1351
|
+
_rebuilt = _trim_l1(_rebuilt, BUDGET_AGENT, label="agent")
|
|
1352
|
+
elif compact:
|
|
1353
|
+
from sourcecode.output_budget import (
|
|
1354
|
+
trim_to_budget as _trim_l1c,
|
|
1355
|
+
BUDGET_COMPACT,
|
|
1356
|
+
)
|
|
1357
|
+
_rebuilt = _trim_l1c(_rebuilt, BUDGET_COMPACT, label="compact")
|
|
1358
|
+
# Serialize
|
|
1359
|
+
if format == "yaml":
|
|
1360
|
+
from io import StringIO as _SIO_L1
|
|
1361
|
+
from ruamel.yaml import YAML as _YAML_L1
|
|
1362
|
+
_yl1 = _YAML_L1()
|
|
1363
|
+
_yl1.default_flow_style = False
|
|
1364
|
+
_yl1.representer.add_representer(
|
|
1365
|
+
type(None),
|
|
1366
|
+
lambda d, v: d.represent_scalar(
|
|
1367
|
+
"tag:yaml.org,2002:null", "null"
|
|
1368
|
+
),
|
|
1369
|
+
)
|
|
1370
|
+
_sl1 = _SIO_L1()
|
|
1371
|
+
_yl1.dump(_rebuilt, _sl1)
|
|
1372
|
+
_cache_hit_content = _sl1.getvalue()
|
|
1373
|
+
else:
|
|
1374
|
+
import json as _json_l1
|
|
1375
|
+
_cache_hit_content = _json_l1.dumps(
|
|
1376
|
+
_rebuilt, indent=2, ensure_ascii=False
|
|
1377
|
+
)
|
|
1378
|
+
# Cache rebuilt view in L2 (skip for --changed-only: stale diff)
|
|
1379
|
+
if _cache_hit_content and not changed_only:
|
|
1380
|
+
_cache_mod.write_view(
|
|
1381
|
+
target,
|
|
1382
|
+
_view_key,
|
|
1383
|
+
_cache_hit_content,
|
|
1384
|
+
fmt=format,
|
|
1385
|
+
)
|
|
1386
|
+
except Exception:
|
|
1387
|
+
_cache_hit_content = None # rebuild failed → full analysis
|
|
1383
1388
|
|
|
1384
1389
|
except Exception:
|
|
1385
1390
|
_core_key = ""
|
|
@@ -2290,6 +2295,11 @@ def main(
|
|
|
2290
2295
|
try:
|
|
2291
2296
|
from sourcecode.ris import _has_uncommitted_changes as _huc_fresh
|
|
2292
2297
|
_uncommitted_fresh = _huc_fresh(target)
|
|
2298
|
+
# _huc_fresh uses --untracked-files=no — it misses repos where all
|
|
2299
|
+
# files are untracked (no staged/unstaged changes to tracked files).
|
|
2300
|
+
# Promote to True when _allowed_changed_files contains untracked files.
|
|
2301
|
+
if not _uncommitted_fresh and _allowed_changed_files:
|
|
2302
|
+
_uncommitted_fresh = True
|
|
2293
2303
|
except Exception:
|
|
2294
2304
|
_uncommitted_fresh = False
|
|
2295
2305
|
import datetime as _dt
|
|
@@ -2335,44 +2345,51 @@ def main(
|
|
|
2335
2345
|
import atexit as _atexit
|
|
2336
2346
|
import threading as _threading
|
|
2337
2347
|
|
|
2338
|
-
#
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
_bg_target,
|
|
2363
|
-
_vk,
|
|
2364
|
-
_bg_content,
|
|
2365
|
-
fmt=_bg_format,
|
|
2366
|
-
layers=_compute_analyzer_fingerprints(),
|
|
2348
|
+
# Pre-compute core dict in the main thread — avoids the 5-second atexit
|
|
2349
|
+
# join race on large repos where core_view(sm) itself takes seconds.
|
|
2350
|
+
# Background thread only does gzip + disk I/O (fast, bounded latency).
|
|
2351
|
+
_bg_core_dict: dict | None = None
|
|
2352
|
+
try:
|
|
2353
|
+
from sourcecode.serializer import core_view as _core_view_fn
|
|
2354
|
+
_bg_core_dict = _core_view_fn(sm)
|
|
2355
|
+
except Exception:
|
|
2356
|
+
pass
|
|
2357
|
+
|
|
2358
|
+
if _bg_core_dict is not None:
|
|
2359
|
+
_bg_target = target
|
|
2360
|
+
_bg_core_key = _core_key
|
|
2361
|
+
_bg_view_key = _view_key
|
|
2362
|
+
_bg_view_flags_str = _view_flags_str
|
|
2363
|
+
_bg_content = content
|
|
2364
|
+
_bg_format = format
|
|
2365
|
+
_bg_hashlib = _hashlib
|
|
2366
|
+
_bg_cache_mod = _cache_mod
|
|
2367
|
+
|
|
2368
|
+
def _write_cache_async() -> None:
|
|
2369
|
+
try:
|
|
2370
|
+
_written_core_hash = _bg_cache_mod.write_core(
|
|
2371
|
+
_bg_target, _bg_core_key, _bg_core_dict
|
|
2367
2372
|
)
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2373
|
+
if _written_core_hash:
|
|
2374
|
+
_vk = _bg_view_key
|
|
2375
|
+
if not _vk:
|
|
2376
|
+
_wvh = _bg_hashlib.sha256(_bg_view_flags_str.encode()).hexdigest()[:8]
|
|
2377
|
+
_vk = f"{_written_core_hash}-{_wvh}"
|
|
2378
|
+
_bg_cache_mod.write_view(
|
|
2379
|
+
_bg_target,
|
|
2380
|
+
_vk,
|
|
2381
|
+
_bg_content,
|
|
2382
|
+
fmt=_bg_format,
|
|
2383
|
+
layers=_compute_analyzer_fingerprints(),
|
|
2384
|
+
)
|
|
2385
|
+
from sourcecode.cache import cache_dir as _cdir, _gc as _run_gc
|
|
2386
|
+
_run_gc(_cdir(_bg_target))
|
|
2387
|
+
except Exception:
|
|
2388
|
+
pass
|
|
2372
2389
|
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2390
|
+
_cache_write_thread = _threading.Thread(target=_write_cache_async, daemon=True)
|
|
2391
|
+
_cache_write_thread.start()
|
|
2392
|
+
_atexit.register(_cache_write_thread.join, 30.0)
|
|
2376
2393
|
|
|
2377
2394
|
# Update RIS with aggregated snapshot data (non-fatal side-effect).
|
|
2378
2395
|
# Update RIS whenever git is available, even when L1/L2 cache is skipped
|
|
@@ -3449,21 +3466,34 @@ def repo_ir_cmd(
|
|
|
3449
3466
|
_ir_tokens_est = _ir_size // 4
|
|
3450
3467
|
# P1-C: abort when estimated tokens > 50K unless --force or --output is given.
|
|
3451
3468
|
if _ir_tokens_est > 50_000 and not force:
|
|
3469
|
+
if summary_only:
|
|
3470
|
+
_hint = (
|
|
3471
|
+
"Use --max-nodes N --max-edges N to cap graph size, "
|
|
3472
|
+
"--output FILE to save to disk, or --force to bypass this guard."
|
|
3473
|
+
)
|
|
3474
|
+
else:
|
|
3475
|
+
_hint = (
|
|
3476
|
+
"Use --summary-only (~5K tokens), --max-nodes N --max-edges N, "
|
|
3477
|
+
"--output FILE to save to disk, or --force to bypass this guard."
|
|
3478
|
+
)
|
|
3452
3479
|
_emit_error_json(
|
|
3453
3480
|
"OUTPUT_TOO_LARGE",
|
|
3454
3481
|
f"Estimated output is ~{_ir_tokens_est // 1000}K tokens — too large for most LLM context windows.",
|
|
3455
|
-
hint=
|
|
3456
|
-
"Use --summary-only (~5K tokens), --max-nodes N --max-edges N, "
|
|
3457
|
-
"--output FILE to save to disk, or --force to bypass this guard."
|
|
3458
|
-
),
|
|
3482
|
+
hint=_hint,
|
|
3459
3483
|
expected="Output under 50K estimated tokens.",
|
|
3460
3484
|
)
|
|
3461
3485
|
raise typer.Exit(1)
|
|
3462
3486
|
if _ir_tokens_est > 10_000:
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3487
|
+
if summary_only:
|
|
3488
|
+
sys.stderr.write(
|
|
3489
|
+
f"[repo-ir] ~{_ir_tokens_est // 1000}K tokens — "
|
|
3490
|
+
"use --max-nodes N --max-edges N or --output FILE for smaller output.\n"
|
|
3491
|
+
)
|
|
3492
|
+
else:
|
|
3493
|
+
sys.stderr.write(
|
|
3494
|
+
f"[repo-ir] ~{_ir_tokens_est // 1000}K tokens — "
|
|
3495
|
+
"use --summary-only or --output FILE for smaller output.\n"
|
|
3496
|
+
)
|
|
3467
3497
|
sys.stderr.flush()
|
|
3468
3498
|
try:
|
|
3469
3499
|
sys.stdout.buffer.write(output.encode("utf-8"))
|
|
@@ -3573,6 +3603,15 @@ def impact_cmd(
|
|
|
3573
3603
|
sys.stderr.flush()
|
|
3574
3604
|
target, path = str(path), _target_as_path
|
|
3575
3605
|
|
|
3606
|
+
if not target.strip():
|
|
3607
|
+
_emit_error_json(
|
|
3608
|
+
INVALID_INPUT_CODE,
|
|
3609
|
+
"Class name must not be empty.",
|
|
3610
|
+
hint="Pass a class name or FQN. Example: sourcecode impact OrderService .",
|
|
3611
|
+
expected="A non-empty class name or FQN.",
|
|
3612
|
+
)
|
|
3613
|
+
raise typer.Exit(1)
|
|
3614
|
+
|
|
3576
3615
|
root = path.resolve()
|
|
3577
3616
|
if not root.is_dir():
|
|
3578
3617
|
_emit_error_json(
|
|
@@ -3701,6 +3740,15 @@ def endpoints_cmd(
|
|
|
3701
3740
|
sourcecode endpoints . --controller LiquidacionJornada
|
|
3702
3741
|
sourcecode endpoints . --limit 10
|
|
3703
3742
|
"""
|
|
3743
|
+
if format not in ("json", "yaml"):
|
|
3744
|
+
_emit_error_json(
|
|
3745
|
+
INVALID_INPUT_CODE,
|
|
3746
|
+
f"Invalid format '{format}'.",
|
|
3747
|
+
hint="format must be: json or yaml.",
|
|
3748
|
+
expected="json | yaml",
|
|
3749
|
+
)
|
|
3750
|
+
raise typer.Exit(code=1)
|
|
3751
|
+
|
|
3704
3752
|
target = path.resolve()
|
|
3705
3753
|
if not target.exists() or not target.is_dir():
|
|
3706
3754
|
_emit_error_json(
|
|
@@ -4241,6 +4289,15 @@ def impact_chain_cmd(
|
|
|
4241
4289
|
from sourcecode.spring_impact import run_impact_chain
|
|
4242
4290
|
from sourcecode.spring_findings import SpringAuditResult
|
|
4243
4291
|
|
|
4292
|
+
if not symbol.strip():
|
|
4293
|
+
_emit_error_json(
|
|
4294
|
+
INVALID_INPUT_CODE,
|
|
4295
|
+
"Symbol name must not be empty.",
|
|
4296
|
+
hint="Pass a class name or FQN. Example: sourcecode impact-chain OrderService .",
|
|
4297
|
+
expected="A non-empty class name or FQN.",
|
|
4298
|
+
)
|
|
4299
|
+
raise typer.Exit(code=1)
|
|
4300
|
+
|
|
4244
4301
|
_VALID_TYPES = ("impact", "events")
|
|
4245
4302
|
if query_type not in _VALID_TYPES:
|
|
4246
4303
|
_emit_error_json(
|
|
@@ -4410,10 +4467,10 @@ def pr_impact_cmd(
|
|
|
4410
4467
|
)
|
|
4411
4468
|
raise typer.Exit(code=1)
|
|
4412
4469
|
|
|
4413
|
-
if not files.exists():
|
|
4470
|
+
if not files.exists() or files.is_dir():
|
|
4414
4471
|
_emit_error_json(
|
|
4415
4472
|
INVALID_INPUT_CODE,
|
|
4416
|
-
f"--files '{files}' does not exist. Expected a text file listing changed file paths (one per line)
|
|
4473
|
+
f"--files '{files}' does not exist or is a directory. Expected a text file listing changed file paths (one per line).",
|
|
4417
4474
|
path=str(files),
|
|
4418
4475
|
hint=(
|
|
4419
4476
|
"Create a file with one changed Java file path per line, then pass it with --files. "
|
|
@@ -4551,6 +4608,15 @@ def explain_cmd(
|
|
|
4551
4608
|
from sourcecode.spring_model import SpringSemanticModel
|
|
4552
4609
|
from sourcecode.explain import explain_class
|
|
4553
4610
|
|
|
4611
|
+
if not class_name.strip():
|
|
4612
|
+
_emit_error_json(
|
|
4613
|
+
INVALID_INPUT_CODE,
|
|
4614
|
+
"Class name must not be empty.",
|
|
4615
|
+
hint="Pass a class name. Example: sourcecode explain UserService .",
|
|
4616
|
+
expected="A non-empty class name.",
|
|
4617
|
+
)
|
|
4618
|
+
raise typer.Exit(code=1)
|
|
4619
|
+
|
|
4554
4620
|
target = path.resolve()
|
|
4555
4621
|
if not target.exists() or not target.is_dir():
|
|
4556
4622
|
_emit_error_json(
|
|
@@ -5105,7 +5171,7 @@ def rename_class_cmd(
|
|
|
5105
5171
|
help="Output format: json (default) or yaml.",
|
|
5106
5172
|
),
|
|
5107
5173
|
) -> None:
|
|
5108
|
-
"""Rename a Java class throughout the repository
|
|
5174
|
+
"""Rename a Java class throughout the repository.
|
|
5109
5175
|
|
|
5110
5176
|
\b
|
|
5111
5177
|
Renames a Java class safely:
|
|
@@ -5116,7 +5182,7 @@ def rename_class_cmd(
|
|
|
5116
5182
|
- Updates extends / implements
|
|
5117
5183
|
- Updates generics, casts, Spring @Qualifier names
|
|
5118
5184
|
- Renames the physical .java file
|
|
5119
|
-
- Emits a structured change audit trail
|
|
5185
|
+
- Emits a structured change audit trail
|
|
5120
5186
|
|
|
5121
5187
|
\b
|
|
5122
5188
|
Examples:
|
|
@@ -5227,7 +5293,7 @@ def chunk_file_cmd(
|
|
|
5227
5293
|
help="Copy output to clipboard after a successful run.",
|
|
5228
5294
|
),
|
|
5229
5295
|
) -> None:
|
|
5230
|
-
"""Split a large Java file into semantic chunks for AI agent consumption
|
|
5296
|
+
"""Split a large Java file into semantic chunks for AI agent consumption.
|
|
5231
5297
|
|
|
5232
5298
|
\b
|
|
5233
5299
|
Splits a Java file at method/class boundaries so AI agents can read
|
|
@@ -5261,6 +5327,16 @@ def chunk_file_cmd(
|
|
|
5261
5327
|
)
|
|
5262
5328
|
raise typer.Exit(1)
|
|
5263
5329
|
|
|
5330
|
+
if abs_file.suffix != ".java":
|
|
5331
|
+
_emit_error_json(
|
|
5332
|
+
INVALID_INPUT_CODE,
|
|
5333
|
+
f"'{abs_file.name}' is not a Java file. chunk-file only supports .java files.",
|
|
5334
|
+
path=str(abs_file),
|
|
5335
|
+
hint="Pass a .java source file.",
|
|
5336
|
+
expected="A .java file path.",
|
|
5337
|
+
)
|
|
5338
|
+
raise typer.Exit(1)
|
|
5339
|
+
|
|
5264
5340
|
result = chunk_java_file(abs_file, max_lines=max_lines, include_content=not metadata_only)
|
|
5265
5341
|
|
|
5266
5342
|
if chunk_id is not None:
|