sourcecode 1.31.27__py3-none-any.whl → 1.31.28__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/cli.py +9 -6
- sourcecode/mcp/server.py +30 -0
- sourcecode/prepare_context.py +99 -17
- {sourcecode-1.31.27.dist-info → sourcecode-1.31.28.dist-info}/METADATA +3 -3
- {sourcecode-1.31.27.dist-info → sourcecode-1.31.28.dist-info}/RECORD +9 -9
- {sourcecode-1.31.27.dist-info → sourcecode-1.31.28.dist-info}/WHEEL +0 -0
- {sourcecode-1.31.27.dist-info → sourcecode-1.31.28.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.31.27.dist-info → sourcecode-1.31.28.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -739,12 +739,15 @@ def main(
|
|
|
739
739
|
# compact is designed to be a bounded summary; --full removes truncation limits,
|
|
740
740
|
# which contradicts compact's purpose. Use --agent --full for expanded output.
|
|
741
741
|
if compact and full:
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
"
|
|
745
|
-
"
|
|
746
|
-
|
|
747
|
-
|
|
742
|
+
import json as _json_flags, sys as _sys_flags
|
|
743
|
+
_sys_flags.stdout.write(_json_flags.dumps({
|
|
744
|
+
"error": "incompatible_flags",
|
|
745
|
+
"message": "--compact and --full are mutually exclusive. "
|
|
746
|
+
"--compact produces a bounded summary; --full removes truncation limits "
|
|
747
|
+
"and is meant for --agent mode. Use --agent --full for expanded output.",
|
|
748
|
+
"exit_code": 1,
|
|
749
|
+
}, ensure_ascii=False) + "\n")
|
|
750
|
+
_sys_flags.stdout.flush()
|
|
748
751
|
raise typer.Exit(code=1)
|
|
749
752
|
|
|
750
753
|
# P0-2 FIX: --full without --compact or --agent has no effect in contract/raw mode.
|
sourcecode/mcp/server.py
CHANGED
|
@@ -453,6 +453,36 @@ def get_impact_context(repo_path: str = ".", target: str = "", depth: int = 4) -
|
|
|
453
453
|
)
|
|
454
454
|
|
|
455
455
|
|
|
456
|
+
@mcp.tool()
|
|
457
|
+
def modernize_context(repo_path: str = ".", format: str = "json") -> dict:
|
|
458
|
+
"""Analyzes codebase for modernization opportunities: dead zones, hotspot scores, upgrade candidates.
|
|
459
|
+
|
|
460
|
+
Maps to: sourcecode modernize <repo_path>
|
|
461
|
+
Returns: hotspot_candidates (high fan-in + git churn), dead_zone_candidates (isolated classes),
|
|
462
|
+
high_coupling_nodes, subsystem_summary, cross_module_tangles, recommendation.
|
|
463
|
+
|
|
464
|
+
Best for: refactor planning, identifying where to start, finding safe removal candidates.
|
|
465
|
+
Use get_compact_context or get_agent_context first for project orientation.
|
|
466
|
+
|
|
467
|
+
repo_path: absolute path to the Java repository (default: current working directory).
|
|
468
|
+
format: output format — "json" (default). Only json is supported; yaml is not available
|
|
469
|
+
for modernize output.
|
|
470
|
+
"""
|
|
471
|
+
_raw = repo_path
|
|
472
|
+
try:
|
|
473
|
+
if not isinstance(repo_path, str):
|
|
474
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
475
|
+
if not isinstance(format, str) or format not in ("json", "yaml"):
|
|
476
|
+
return _err("format must be 'json' or 'yaml'", "INVALID_ARGUMENT")
|
|
477
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
478
|
+
return _execute(["modernize", repo_path])
|
|
479
|
+
except Exception as exc:
|
|
480
|
+
return _err(
|
|
481
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path recibido: {_raw}",
|
|
482
|
+
"INTERNAL_ERROR",
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
|
|
456
486
|
_TELEMETRY_ACTIONS = frozenset({"status", "enable", "disable"})
|
|
457
487
|
|
|
458
488
|
|
sourcecode/prepare_context.py
CHANGED
|
@@ -1134,6 +1134,27 @@ class TaskContextBuilder:
|
|
|
1134
1134
|
# No-git and invalid-ref cases were already handled in step 0 (early returns).
|
|
1135
1135
|
if task_name == "review-pr":
|
|
1136
1136
|
if not _pr_scope_files:
|
|
1137
|
+
# Distinguish: no_staged_changes (CI, no --since) vs no_diff (empty range)
|
|
1138
|
+
if _pr_scope_source == "no_staged_changes":
|
|
1139
|
+
_no_diff_msg = (
|
|
1140
|
+
"No --since ref provided and no staged changes found. "
|
|
1141
|
+
"Use --since <ref>"
|
|
1142
|
+
)
|
|
1143
|
+
return TaskOutput(
|
|
1144
|
+
task="review-pr", goal=spec.goal,
|
|
1145
|
+
project_summary=None, architecture_summary=None,
|
|
1146
|
+
relevant_files=[], suspected_areas=[],
|
|
1147
|
+
improvement_opportunities=[], test_gaps=[],
|
|
1148
|
+
key_dependencies=[], code_notes_summary=None,
|
|
1149
|
+
limitations=[], confidence="low",
|
|
1150
|
+
error_code="no_diff_source",
|
|
1151
|
+
error_message=_no_diff_msg,
|
|
1152
|
+
gaps=[_no_diff_msg],
|
|
1153
|
+
ci_decision="no_diff_source",
|
|
1154
|
+
scope_source=_pr_scope_source,
|
|
1155
|
+
scope_files=[],
|
|
1156
|
+
repo_root=str(_pr_git_root),
|
|
1157
|
+
)
|
|
1137
1158
|
_no_diff_hint = "review-pr requires changed files or --since <ref>."
|
|
1138
1159
|
return TaskOutput(
|
|
1139
1160
|
task="review-pr", goal=spec.goal,
|
|
@@ -1221,6 +1242,76 @@ class TaskContextBuilder:
|
|
|
1221
1242
|
symptom=symptom if task_name == "fix-bug" else None,
|
|
1222
1243
|
)
|
|
1223
1244
|
|
|
1245
|
+
# ── Fast-mode fallback: never return empty relevant_files when source files exist ──
|
|
1246
|
+
# When --fast is active on a large repo, all_paths may be restricted to a handful of
|
|
1247
|
+
# changed/noise files that all get filtered out by _rank_files. Inject fallback signals:
|
|
1248
|
+
# 1. detected entry points (already computed, zero I/O cost)
|
|
1249
|
+
# 2. recently committed files (git log -10 --name-only)
|
|
1250
|
+
# 3. files matching symptom keywords in path (when fix-bug + --symptom)
|
|
1251
|
+
if fast and not relevant_files and task_name not in ("delta", "review-pr"):
|
|
1252
|
+
import subprocess as _sp_fb
|
|
1253
|
+
_fb_seen: set[str] = set()
|
|
1254
|
+
_fb_candidates: list[RelevantFile] = []
|
|
1255
|
+
|
|
1256
|
+
# 1. Entry points from detection
|
|
1257
|
+
for _ep in entry_points:
|
|
1258
|
+
_ep_path = _ep.path.replace("\\", "/")
|
|
1259
|
+
if _ep_path not in _fb_seen and (self.root / _ep_path).exists():
|
|
1260
|
+
_fb_candidates.append(RelevantFile(
|
|
1261
|
+
path=_ep_path,
|
|
1262
|
+
role="entrypoint",
|
|
1263
|
+
score=0.5,
|
|
1264
|
+
reason="fast-mode fallback: detected entry point",
|
|
1265
|
+
why="entry_point signal from manifest/annotation detection",
|
|
1266
|
+
))
|
|
1267
|
+
_fb_seen.add(_ep_path)
|
|
1268
|
+
|
|
1269
|
+
# 2. Recently committed files (git log -10 --name-only)
|
|
1270
|
+
try:
|
|
1271
|
+
_gl_r = _sp_fb.run(
|
|
1272
|
+
["git", "log", "--name-only", "--pretty=format:", "-10"],
|
|
1273
|
+
capture_output=True, text=True, cwd=str(self.root), timeout=5,
|
|
1274
|
+
)
|
|
1275
|
+
for _gl_f in _gl_r.stdout.splitlines():
|
|
1276
|
+
_gl_f = _gl_f.strip().replace("\\", "/")
|
|
1277
|
+
if (not _gl_f or _gl_f in _fb_seen):
|
|
1278
|
+
continue
|
|
1279
|
+
if Path(_gl_f).suffix.lower() not in _ALL_EXTENSIONS:
|
|
1280
|
+
continue
|
|
1281
|
+
if not (self.root / _gl_f).exists():
|
|
1282
|
+
continue
|
|
1283
|
+
_fb_candidates.append(RelevantFile(
|
|
1284
|
+
path=_gl_f,
|
|
1285
|
+
role="source",
|
|
1286
|
+
score=0.3,
|
|
1287
|
+
reason="fast-mode fallback: recently committed file (git log -10)",
|
|
1288
|
+
why="recent commit history signal",
|
|
1289
|
+
))
|
|
1290
|
+
_fb_seen.add(_gl_f)
|
|
1291
|
+
except Exception:
|
|
1292
|
+
pass
|
|
1293
|
+
|
|
1294
|
+
# 3. Symptom keyword path matches (fix-bug only)
|
|
1295
|
+
if task_name == "fix-bug" and symptom:
|
|
1296
|
+
import re as _re_fb
|
|
1297
|
+
_fb_kws = [w.lower() for w in _re_fb.split(r"[\s\W]+", symptom) if len(w) > 2]
|
|
1298
|
+
for _fb_p in all_paths:
|
|
1299
|
+
if _fb_p in _fb_seen:
|
|
1300
|
+
continue
|
|
1301
|
+
if Path(_fb_p).suffix.lower() not in _ALL_EXTENSIONS:
|
|
1302
|
+
continue
|
|
1303
|
+
if any(kw in _fb_p.lower() for kw in _fb_kws):
|
|
1304
|
+
_fb_candidates.append(RelevantFile(
|
|
1305
|
+
path=_fb_p,
|
|
1306
|
+
role="source",
|
|
1307
|
+
score=0.2,
|
|
1308
|
+
reason=f"fast-mode fallback: path matches symptom ({symptom!r})",
|
|
1309
|
+
why="symptom keyword in file path",
|
|
1310
|
+
))
|
|
1311
|
+
_fb_seen.add(_fb_p)
|
|
1312
|
+
|
|
1313
|
+
relevant_files = _fb_candidates[:20]
|
|
1314
|
+
|
|
1224
1315
|
# ── IC-006: fix-bug suspected_areas — recompute from ranked files + bug notes ──
|
|
1225
1316
|
# relevant_files is now ranked by RankingEngine (git churn, fan_in, centrality, notes).
|
|
1226
1317
|
# suspected_areas should reflect that ranking, not raw comment count.
|
|
@@ -2569,24 +2660,15 @@ class TaskContextBuilder:
|
|
|
2569
2660
|
if DiffSourceType.WORKTREE_STAGED.value not in sources:
|
|
2570
2661
|
sources.append(DiffSourceType.WORKTREE_STAGED.value)
|
|
2571
2662
|
|
|
2572
|
-
# ──
|
|
2573
|
-
#
|
|
2574
|
-
#
|
|
2663
|
+
# ── FIX-P1: no --since + clean working tree → no_staged_changes signal ──
|
|
2664
|
+
# Previously: silently fell back to HEAD~1 diff, masking a missing --since.
|
|
2665
|
+
# Now: emit "no_staged_changes" scope so the caller can return no_diff_source
|
|
2666
|
+
# (exit 1) and prompt the user to provide --since.
|
|
2667
|
+
# Rationale: in CI, the tree is always clean; without --since there is no
|
|
2668
|
+
# meaningful diff to review. Local dev should stage changes before review-pr.
|
|
2575
2669
|
if since is None and not committed_files and not uncommitted_files:
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
head_committed = _run("git", "diff", "--name-only", "--relative", "HEAD~1..HEAD")
|
|
2579
|
-
if head_committed:
|
|
2580
|
-
committed_files = head_committed
|
|
2581
|
-
sources.append(DiffSourceType.HEAD_MINUS_1.value)
|
|
2582
|
-
else:
|
|
2583
|
-
# First commit — no HEAD~1; diff against git empty tree
|
|
2584
|
-
_GIT_EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
|
2585
|
-
first_committed = _run("git", "diff", "--name-only", "--relative",
|
|
2586
|
-
_GIT_EMPTY_TREE, "HEAD")
|
|
2587
|
-
if first_committed:
|
|
2588
|
-
committed_files = first_committed
|
|
2589
|
-
sources.append("initial_commit")
|
|
2670
|
+
# Return sentinel — step 5d converts this to no_diff_source (exit 1).
|
|
2671
|
+
return [], "no_staged_changes", [], []
|
|
2590
2672
|
|
|
2591
2673
|
# ── Drop paths outside self.root ──────────────────────────────────────
|
|
2592
2674
|
def _drop_outside(lst: list[str]) -> list[str]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.31.
|
|
3
|
+
Version: 1.31.28
|
|
4
4
|
Summary: Deterministic codebase context for AI coding agents
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -225,7 +225,7 @@ Description-Content-Type: text/markdown
|
|
|
225
225
|
|
|
226
226
|
**AI-ready change intelligence for Java/Spring enterprise monoliths.**
|
|
227
227
|
|
|
228
|
-

|
|
229
229
|

|
|
230
230
|
|
|
231
231
|
---
|
|
@@ -263,7 +263,7 @@ pipx install sourcecode
|
|
|
263
263
|
|
|
264
264
|
```bash
|
|
265
265
|
sourcecode version
|
|
266
|
-
# sourcecode 1.31.
|
|
266
|
+
# sourcecode 1.31.28
|
|
267
267
|
```
|
|
268
268
|
|
|
269
269
|
---
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=u-6FWeYcsHBD4kgFtNzOZPQeoIf-YlQrC_JzWDvNiaA,104
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=Ry3aYT9dc7XuLmWLT5IZ93RkCf_P14Qtew0nGPvUl_8,42184
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
@@ -6,7 +6,7 @@ sourcecode/ast_extractor.py,sha256=_btmeOJIe3t-NicF94D5ZAesa2YIJ0_QNExGnbHxGFE,5
|
|
|
6
6
|
sourcecode/cache.py,sha256=TiYa3ECjBKtvlfCk7GvQ9v6gZkAITpH3ow9PubA7sUo,22946
|
|
7
7
|
sourcecode/canonical_ir.py,sha256=_HM3AUmKSdna9u4dCoU6rpgSA6HdF8gzOKZykIUCNGY,23277
|
|
8
8
|
sourcecode/classifier.py,sha256=yWeq6agTjkFa3zuNa-gdVIHtjoBoPoVlJnX-b7tdVJs,7851
|
|
9
|
-
sourcecode/cli.py,sha256=
|
|
9
|
+
sourcecode/cli.py,sha256=YRHo7R58Nf-NYASQNCW39AjsJnOi5mrl5iziaPdMUxk,152204
|
|
10
10
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
11
11
|
sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
|
|
12
12
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
@@ -26,7 +26,7 @@ sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7
|
|
|
26
26
|
sourcecode/output_budget.py,sha256=43307mJEyUPU3MI-QEQoVxrcAvNyUzdzF_SAPgisBQE,6603
|
|
27
27
|
sourcecode/path_filters.py,sha256=ROFRQ8eSLBEMiixK9f45-RO7um4VEEcjoD5AA4I427I,3739
|
|
28
28
|
sourcecode/pr_comment_renderer.py,sha256=smHslxiG14lrytCkq5nFrFu-qTHgA-t-LFYfdrfjz2o,14423
|
|
29
|
-
sourcecode/prepare_context.py,sha256=
|
|
29
|
+
sourcecode/prepare_context.py,sha256=RxzyMoxgGmMdwqxMwd9CoEsOJARgKJ6YrZte_FeH68A,200168
|
|
30
30
|
sourcecode/progress.py,sha256=qn30sWaHOkjTgXsSBmiPkz7Rsbwc5oSlIe6JNEMYp_k,3149
|
|
31
31
|
sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
|
|
32
32
|
sourcecode/redactor.py,sha256=xuGcadGEHaPw4qZXlMDvzMCsr4VOkdp3oBQptHyJk8c,2884
|
|
@@ -64,7 +64,7 @@ sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG6
|
|
|
64
64
|
sourcecode/detectors/tooling.py,sha256=8CKbtxwQoABP-WyBRNmdAmHDOvAH57AR1cF4UKuWEdQ,2074
|
|
65
65
|
sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
|
|
66
66
|
sourcecode/mcp/runner.py,sha256=7PnFjKYbgxFeDnqVeSntXHxZX7ZtK3-krDkEuVjI24M,1386
|
|
67
|
-
sourcecode/mcp/server.py,sha256=
|
|
67
|
+
sourcecode/mcp/server.py,sha256=DVXWcmGiAjNQe3fxJS-AUH03rsA_VFjS6048f8RMkt0,21540
|
|
68
68
|
sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
|
|
69
69
|
sourcecode/mcp/onboarding/applier.py,sha256=yfSMT0NKdZsjavtLkC8yQ7OtkfepOl5IXGByqg6bdEY,1894
|
|
70
70
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
@@ -76,8 +76,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
76
76
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
77
77
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
78
78
|
sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
|
|
79
|
-
sourcecode-1.31.
|
|
80
|
-
sourcecode-1.31.
|
|
81
|
-
sourcecode-1.31.
|
|
82
|
-
sourcecode-1.31.
|
|
83
|
-
sourcecode-1.31.
|
|
79
|
+
sourcecode-1.31.28.dist-info/METADATA,sha256=IopWHYcmGBKmum7GTpRUesF4DHPc1bSC4kVW3-BDYuU,31103
|
|
80
|
+
sourcecode-1.31.28.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
81
|
+
sourcecode-1.31.28.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
82
|
+
sourcecode-1.31.28.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
83
|
+
sourcecode-1.31.28.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|