sourcecode 1.31.3__py3-none-any.whl → 1.31.4__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 +64 -30
- sourcecode/prepare_context.py +67 -4
- {sourcecode-1.31.3.dist-info → sourcecode-1.31.4.dist-info}/METADATA +3 -3
- {sourcecode-1.31.3.dist-info → sourcecode-1.31.4.dist-info}/RECORD +8 -8
- {sourcecode-1.31.3.dist-info → sourcecode-1.31.4.dist-info}/WHEEL +0 -0
- {sourcecode-1.31.3.dist-info → sourcecode-1.31.4.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.31.3.dist-info → sourcecode-1.31.4.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -1849,6 +1849,11 @@ def prepare_context_cmd(
|
|
|
1849
1849
|
"--include-config",
|
|
1850
1850
|
help="(generate-tests) Include tooling config files (*.conf.js, .eslintrc*, etc.) in test_gaps. Excluded by default.",
|
|
1851
1851
|
),
|
|
1852
|
+
all_gaps: bool = typer.Option(
|
|
1853
|
+
False,
|
|
1854
|
+
"--all",
|
|
1855
|
+
help="(generate-tests) Return the full test_gaps list without truncating to top 20.",
|
|
1856
|
+
),
|
|
1852
1857
|
) -> None:
|
|
1853
1858
|
"""Task-specific context for AI coding agents.
|
|
1854
1859
|
|
|
@@ -1930,7 +1935,7 @@ def prepare_context_cmd(
|
|
|
1930
1935
|
_sys.stderr.flush()
|
|
1931
1936
|
_t0 = _time.perf_counter()
|
|
1932
1937
|
try:
|
|
1933
|
-
output = builder.build(task, since=since, symptom=symptom, fast=fast, include_config=include_config)
|
|
1938
|
+
output = builder.build(task, since=since, symptom=symptom, fast=fast, include_config=include_config, all_gaps=all_gaps)
|
|
1934
1939
|
finally:
|
|
1935
1940
|
_progress.finish()
|
|
1936
1941
|
_t_total = (_time.perf_counter() - _t0) * 1000
|
|
@@ -1957,8 +1962,8 @@ def prepare_context_cmd(
|
|
|
1957
1962
|
},
|
|
1958
1963
|
"explain": {
|
|
1959
1964
|
"project_summary": True, "architecture_summary": True,
|
|
1960
|
-
"relevant_files":
|
|
1961
|
-
"gaps":
|
|
1965
|
+
"relevant_files": False, "key_dependencies": True,
|
|
1966
|
+
"gaps": False, "confidence": True,
|
|
1962
1967
|
"suspected_areas": False, "improvement_opportunities": False,
|
|
1963
1968
|
"test_gaps": False, "code_notes_summary": False,
|
|
1964
1969
|
"changed_files": False, "affected_entry_points": False,
|
|
@@ -2467,6 +2472,9 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2467
2472
|
_CLASS_PATH_RE = _re.compile(
|
|
2468
2473
|
r'@RequestMapping\s*\(\s*(?:value\s*=\s*)?["\']([^"\']+)["\']',
|
|
2469
2474
|
)
|
|
2475
|
+
# Handles array syntax: @RequestMapping({"/v1/foo", "/v1/bar"})
|
|
2476
|
+
_CLASS_ARRAY_PATH_RE = _re.compile(r'@RequestMapping\s*\(\s*\{([^}]*)\}')
|
|
2477
|
+
_QUOTED_STR_RE = _re.compile(r'"([^"]+)"')
|
|
2470
2478
|
_REQUEST_METHOD_VERB_RE = _re.compile(r'method\s*=\s*RequestMethod\.([A-Z]+)')
|
|
2471
2479
|
_VALUE_PATH_RE = _re.compile(r'value\s*=\s*"([^"]+)"')
|
|
2472
2480
|
|
|
@@ -2505,7 +2513,6 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2505
2513
|
class_name = cls_m.group(1) if cls_m else java_file.stem
|
|
2506
2514
|
|
|
2507
2515
|
# Extract class-level base path and locate class body start
|
|
2508
|
-
class_base = ""
|
|
2509
2516
|
lines = content.splitlines()
|
|
2510
2517
|
|
|
2511
2518
|
# First pass: find class/interface declaration line index
|
|
@@ -2518,26 +2525,53 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2518
2525
|
class_body_start = i + 1
|
|
2519
2526
|
break
|
|
2520
2527
|
|
|
2521
|
-
# Second pass: extract class-level @RequestMapping path (only before class body)
|
|
2528
|
+
# Second pass: extract class-level @RequestMapping path (only before class body).
|
|
2529
|
+
# Supports both single-string and array syntax (Bug #1B).
|
|
2522
2530
|
search_end = class_body_start if class_body_start else len(lines)
|
|
2531
|
+
class_bases: list[str] = [""] # default: no prefix
|
|
2523
2532
|
for i in range(search_end):
|
|
2533
|
+
sl = lines[i].strip()
|
|
2534
|
+
if sl.startswith("//") or sl.startswith("*"):
|
|
2535
|
+
continue
|
|
2524
2536
|
if "@RequestMapping" in lines[i]:
|
|
2525
|
-
block
|
|
2537
|
+
# Cap block to search_end so method annotations inside the class body
|
|
2538
|
+
# cannot be matched as the class-level prefix (Bug #1B).
|
|
2539
|
+
block = "\n".join(lines[max(0, i - 1): min(i + 5, search_end + 1)])
|
|
2526
2540
|
if "class " in block or "interface " in block:
|
|
2527
2541
|
path_m = _CLASS_PATH_RE.search(block)
|
|
2528
2542
|
if path_m:
|
|
2529
|
-
|
|
2530
|
-
|
|
2543
|
+
class_bases = [path_m.group(1).rstrip("/")]
|
|
2544
|
+
else:
|
|
2545
|
+
arr_m = _CLASS_ARRAY_PATH_RE.search(block)
|
|
2546
|
+
if arr_m:
|
|
2547
|
+
paths = _QUOTED_STR_RE.findall(arr_m.group(1))
|
|
2548
|
+
if paths:
|
|
2549
|
+
class_bases = [p.rstrip("/") for p in paths]
|
|
2550
|
+
break
|
|
2531
2551
|
|
|
2532
|
-
# Extract method-level endpoints starting from inside class body
|
|
2533
|
-
#
|
|
2552
|
+
# Extract method-level endpoints starting from inside class body.
|
|
2553
|
+
# Bug #1A fix: track block comments and skip // lines before annotation checks.
|
|
2534
2554
|
pending_annotations: list[tuple[str, str]] = [] # (http_verb, path_suffix)
|
|
2535
2555
|
pending_filtro: Optional[str] = None
|
|
2556
|
+
in_block_comment = False
|
|
2536
2557
|
|
|
2537
2558
|
for i in range(class_body_start, len(lines)):
|
|
2538
2559
|
line = lines[i]
|
|
2539
2560
|
stripped = line.strip()
|
|
2540
2561
|
|
|
2562
|
+
# Block comment state tracking (Bug #1A)
|
|
2563
|
+
if in_block_comment:
|
|
2564
|
+
if "*/" in stripped:
|
|
2565
|
+
in_block_comment = False
|
|
2566
|
+
continue
|
|
2567
|
+
if stripped.startswith("/*"):
|
|
2568
|
+
if "*/" not in stripped:
|
|
2569
|
+
in_block_comment = True
|
|
2570
|
+
continue
|
|
2571
|
+
# Skip line comments (Bug #1A)
|
|
2572
|
+
if stripped.startswith("//"):
|
|
2573
|
+
continue
|
|
2574
|
+
|
|
2541
2575
|
# Check for @M3FiltroSeguridad
|
|
2542
2576
|
fm = _FILTRO_RE.search(stripped)
|
|
2543
2577
|
if fm:
|
|
@@ -2572,30 +2606,30 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2572
2606
|
handler = mm.group(1) if mm else ""
|
|
2573
2607
|
if handler and not handler.startswith("class"):
|
|
2574
2608
|
for http_verb, path_suffix in pending_annotations:
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
seen
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2609
|
+
for _cb in class_bases: # Bug #1B: one endpoint per class prefix
|
|
2610
|
+
full_path = (_cb + "/" + path_suffix).replace("//", "/").rstrip("/") or "/"
|
|
2611
|
+
if not full_path.startswith("/"):
|
|
2612
|
+
full_path = "/" + full_path
|
|
2613
|
+
key = (class_name, handler, http_verb, _cb)
|
|
2614
|
+
if key not in seen:
|
|
2615
|
+
seen.add(key)
|
|
2616
|
+
entry: dict[str, Any] = {
|
|
2617
|
+
"method": http_verb,
|
|
2618
|
+
"path": full_path,
|
|
2619
|
+
"controller": class_name,
|
|
2620
|
+
"handler": handler,
|
|
2621
|
+
}
|
|
2622
|
+
if pending_filtro:
|
|
2623
|
+
entry["required_permission"] = pending_filtro
|
|
2624
|
+
endpoints.append(entry)
|
|
2590
2625
|
pending_annotations = []
|
|
2591
2626
|
pending_filtro = None
|
|
2592
2627
|
continue
|
|
2593
2628
|
|
|
2594
|
-
# Non-annotation, non-method line — reset
|
|
2595
|
-
if stripped
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
pending_filtro = None
|
|
2629
|
+
# Non-annotation, non-method line — reset on closing brace
|
|
2630
|
+
if stripped == "}":
|
|
2631
|
+
pending_annotations = []
|
|
2632
|
+
pending_filtro = None
|
|
2599
2633
|
|
|
2600
2634
|
endpoints.sort(key=lambda e: (e.get("controller", ""), e.get("path", "")))
|
|
2601
2635
|
undocumented = sum(1 for e in endpoints if "required_permission" not in e)
|
sourcecode/prepare_context.py
CHANGED
|
@@ -721,7 +721,7 @@ class TaskContextBuilder:
|
|
|
721
721
|
def __init__(self, root: Path) -> None:
|
|
722
722
|
self.root = root
|
|
723
723
|
|
|
724
|
-
def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None, fast: bool = False, include_config: bool = False) -> TaskOutput:
|
|
724
|
+
def build(self, task_name: str, *, since: Optional[str] = None, symptom: Optional[str] = None, fast: bool = False, include_config: bool = False, all_gaps: bool = False) -> TaskOutput:
|
|
725
725
|
if task_name not in TASKS:
|
|
726
726
|
raise ValueError(
|
|
727
727
|
f"Unknown task '{task_name}'. Available: {', '.join(TASKS)}"
|
|
@@ -1763,14 +1763,18 @@ class TaskContextBuilder:
|
|
|
1763
1763
|
"_rank": _pub_count + _ann_count * 2,
|
|
1764
1764
|
})
|
|
1765
1765
|
|
|
1766
|
-
_java_candidates.sort(
|
|
1766
|
+
_java_candidates.sort(
|
|
1767
|
+
key=lambda x: -(x["public_method_count"] * (1.5 if x["has_spring_annotations"] else 1.0))
|
|
1768
|
+
)
|
|
1769
|
+
_top = _java_candidates if all_gaps else _java_candidates[:20]
|
|
1767
1770
|
test_gaps = [
|
|
1768
1771
|
{
|
|
1769
1772
|
"path": c["path"],
|
|
1770
1773
|
"public_method_count": c["public_method_count"],
|
|
1771
1774
|
"has_spring_annotations": c["has_spring_annotations"],
|
|
1775
|
+
"rank_score": round(c["public_method_count"] * (1.5 if c["has_spring_annotations"] else 1.0), 1),
|
|
1772
1776
|
}
|
|
1773
|
-
for c in
|
|
1777
|
+
for c in _top
|
|
1774
1778
|
]
|
|
1775
1779
|
else:
|
|
1776
1780
|
# Non-Java algorithm (unchanged)
|
|
@@ -2206,8 +2210,67 @@ class TaskContextBuilder:
|
|
|
2206
2210
|
"refactor": max(15, min(30, _repo_size // 120)),
|
|
2207
2211
|
}
|
|
2208
2212
|
_budget = _task_budget.get(task_name, 15)
|
|
2209
|
-
_selected = _ctx.select_subgraph(_ns, contracts=[], budget=_budget, min_score=0.15)
|
|
2213
|
+
_selected = list(_ctx.select_subgraph(_ns, contracts=[], budget=_budget, min_score=0.15))
|
|
2210
2214
|
_rf_map = {path: rf for _, path, rf in scored}
|
|
2215
|
+
|
|
2216
|
+
# Bug #3: onboard must cover ≥3 arch layers (controllers/services/domain/repositories).
|
|
2217
|
+
if task_name == "onboard":
|
|
2218
|
+
def _arch_layer(p: str) -> str:
|
|
2219
|
+
n = Path(p).name.lower()
|
|
2220
|
+
if "controller" in n:
|
|
2221
|
+
return "controllers"
|
|
2222
|
+
if "repository" in n or "mapper" in n or "dao" in n:
|
|
2223
|
+
return "repositories"
|
|
2224
|
+
if "service" in n:
|
|
2225
|
+
return "services"
|
|
2226
|
+
pn = p.replace("\\", "/")
|
|
2227
|
+
if "entity" in n or "/entity/" in pn or "/domain/" in pn or "/model/" in pn:
|
|
2228
|
+
return "domain"
|
|
2229
|
+
return "other"
|
|
2230
|
+
|
|
2231
|
+
_REQUIRED = {"controllers", "services", "repositories", "domain"}
|
|
2232
|
+
_covered = {_arch_layer(p) for p in _selected} & _REQUIRED
|
|
2233
|
+
_missing = _REQUIRED - _covered
|
|
2234
|
+
if len(_covered) < 3 and _missing:
|
|
2235
|
+
_sel_set = set(_selected)
|
|
2236
|
+
# First pass: inject from already-scored files
|
|
2237
|
+
for _, _p, _ in sorted(scored, key=lambda x: -x[0]):
|
|
2238
|
+
if len(_covered) >= 3:
|
|
2239
|
+
break
|
|
2240
|
+
if _p in _sel_set or _p not in _rf_map:
|
|
2241
|
+
continue
|
|
2242
|
+
_layer = _arch_layer(_p)
|
|
2243
|
+
if _layer in _missing:
|
|
2244
|
+
_selected.append(_p)
|
|
2245
|
+
_sel_set.add(_p)
|
|
2246
|
+
_covered.add(_layer)
|
|
2247
|
+
_missing.discard(_layer)
|
|
2248
|
+
# Second pass: fallback scan of all_paths when scored files
|
|
2249
|
+
# don't cover enough layers (e.g. all Java files scored ≤ 0
|
|
2250
|
+
# due to auxiliary/example package detection).
|
|
2251
|
+
if len(_covered) < 3 and _missing:
|
|
2252
|
+
_NON_TEST = ("/test/", "/tests/", "/spec/")
|
|
2253
|
+
for _p in all_paths:
|
|
2254
|
+
if len(_covered) >= 3:
|
|
2255
|
+
break
|
|
2256
|
+
if _p in _sel_set:
|
|
2257
|
+
continue
|
|
2258
|
+
if any(s in _p.replace("\\", "/") for s in _NON_TEST):
|
|
2259
|
+
continue
|
|
2260
|
+
_layer = _arch_layer(_p)
|
|
2261
|
+
if _layer not in _missing:
|
|
2262
|
+
continue
|
|
2263
|
+
_rf_map[_p] = RelevantFile(
|
|
2264
|
+
path=_p,
|
|
2265
|
+
role="source",
|
|
2266
|
+
score=0.1,
|
|
2267
|
+
reason="layer coverage (onboard)",
|
|
2268
|
+
)
|
|
2269
|
+
_selected.append(_p)
|
|
2270
|
+
_sel_set.add(_p)
|
|
2271
|
+
_covered.add(_layer)
|
|
2272
|
+
_missing.discard(_layer)
|
|
2273
|
+
|
|
2211
2274
|
return [_rf_map[p] for p in _selected if p in _rf_map]
|
|
2212
2275
|
except Exception:
|
|
2213
2276
|
return [f for _, _, f in scored[:15]]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.31.
|
|
3
|
+
Version: 1.31.4
|
|
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
|
**Deterministic, behavior-aware codebase context for AI agents and PR review.**
|
|
227
227
|
|
|
228
|
-

|
|
229
229
|

|
|
230
230
|
|
|
231
231
|
---
|
|
@@ -261,7 +261,7 @@ pipx install sourcecode
|
|
|
261
261
|
|
|
262
262
|
```bash
|
|
263
263
|
sourcecode version
|
|
264
|
-
# sourcecode 1.31.
|
|
264
|
+
# sourcecode 1.31.4
|
|
265
265
|
```
|
|
266
266
|
|
|
267
267
|
---
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=P8MbrmTVGyjN1Uy_38VxYTdrjIoEPOf9I4Xn71cCRs8,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=MyBa0Hf5HmkudZQDLKrjcWDKETXETXl0mQX1swtTwAA,39091
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
5
5
|
sourcecode/ast_extractor.py,sha256=XgrZg2DcWcUm9r87cRG3KGO7IK2TIL_N-CvhSbUmmh4,49901
|
|
6
6
|
sourcecode/classifier.py,sha256=-0t0HLc9L9UleMLfclfLM3AXhBjUb_AYyBPDbvgWtac,7755
|
|
7
|
-
sourcecode/cli.py,sha256=
|
|
7
|
+
sourcecode/cli.py,sha256=dcHIYDvDpcIzbExxKuemtElk3RClLRWaaO2RU9R8Hoc,123945
|
|
8
8
|
sourcecode/code_notes_analyzer.py,sha256=y1MJBnPZHYp4i6cQCXUb9ATIyifS_qMQWjw_8lPkpsU,9215
|
|
9
9
|
sourcecode/confidence_analyzer.py,sha256=ZUn-Nywi5TEQcuozqK_vfOyPT-a1dYYO42elAtVFV-k,16412
|
|
10
10
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
@@ -22,7 +22,7 @@ sourcecode/git_analyzer.py,sha256=0Gyj-vMpIIN4nfriKXVRouNYBeJ59s6pQDX2Xu9Pq-U,13
|
|
|
22
22
|
sourcecode/graph_analyzer.py,sha256=iUK-7pSV-cvGqqD2hENdYmhnm0wcXFEyK-xnu5ul8OU,62515
|
|
23
23
|
sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7c,22750
|
|
24
24
|
sourcecode/pr_comment_renderer.py,sha256=smHslxiG14lrytCkq5nFrFu-qTHgA-t-LFYfdrfjz2o,14423
|
|
25
|
-
sourcecode/prepare_context.py,sha256=
|
|
25
|
+
sourcecode/prepare_context.py,sha256=Eid3Wmh4lmtr20pmobcCU0yzjRXhFql2rEmfGUgkFpU,183214
|
|
26
26
|
sourcecode/progress.py,sha256=qn30sWaHOkjTgXsSBmiPkz7Rsbwc5oSlIe6JNEMYp_k,3149
|
|
27
27
|
sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
|
|
28
28
|
sourcecode/redactor.py,sha256=xuGcadGEHaPw4qZXlMDvzMCsr4VOkdp3oBQptHyJk8c,2884
|
|
@@ -72,8 +72,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
72
72
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
73
73
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
74
74
|
sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
|
|
75
|
-
sourcecode-1.31.
|
|
76
|
-
sourcecode-1.31.
|
|
77
|
-
sourcecode-1.31.
|
|
78
|
-
sourcecode-1.31.
|
|
79
|
-
sourcecode-1.31.
|
|
75
|
+
sourcecode-1.31.4.dist-info/METADATA,sha256=JZXcuZYPozOjtbIY_xnVgUNwMF1aEU12mV4m2T__E1A,29083
|
|
76
|
+
sourcecode-1.31.4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
77
|
+
sourcecode-1.31.4.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
78
|
+
sourcecode-1.31.4.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
79
|
+
sourcecode-1.31.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|