sourcecode 1.33.2__py3-none-any.whl → 1.33.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 +114 -13
- sourcecode/mcp/server.py +61 -6
- sourcecode/prepare_context.py +4 -0
- sourcecode/ris.py +46 -11
- sourcecode/serializer.py +7 -7
- {sourcecode-1.33.2.dist-info → sourcecode-1.33.4.dist-info}/METADATA +3 -3
- {sourcecode-1.33.2.dist-info → sourcecode-1.33.4.dist-info}/RECORD +11 -11
- {sourcecode-1.33.2.dist-info → sourcecode-1.33.4.dist-info}/WHEEL +0 -0
- {sourcecode-1.33.2.dist-info → sourcecode-1.33.4.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.33.2.dist-info → sourcecode-1.33.4.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -658,7 +658,6 @@ def main(
|
|
|
658
658
|
env_map: bool = typer.Option(
|
|
659
659
|
False,
|
|
660
660
|
"--env-map",
|
|
661
|
-
hidden=True,
|
|
662
661
|
help="Map environment variables referenced across the codebase.",
|
|
663
662
|
),
|
|
664
663
|
code_notes: bool = typer.Option(
|
|
@@ -1880,8 +1879,9 @@ def main(
|
|
|
1880
1879
|
if _gc_early and not (_bad_gc & set(_gc_early.limitations)):
|
|
1881
1880
|
_uc = _gc_early.uncommitted_changes
|
|
1882
1881
|
if _uc:
|
|
1883
|
-
#
|
|
1884
|
-
|
|
1882
|
+
# Include untracked (new files not yet staged) so new source files
|
|
1883
|
+
# are analyzed under --changed-only, not silently treated as "clean".
|
|
1884
|
+
_allowed_changed_files = set(_uc.staged) | set(_uc.unstaged) | set(_uc.untracked)
|
|
1885
1885
|
if not _allowed_changed_files:
|
|
1886
1886
|
# Git is available and confirms no uncommitted changes.
|
|
1887
1887
|
# Do NOT fall back to a full scan — that would silently produce
|
|
@@ -1900,10 +1900,10 @@ def main(
|
|
|
1900
1900
|
changed_only = False
|
|
1901
1901
|
if _git_confirmed_clean:
|
|
1902
1902
|
_nc_payload = json.dumps({
|
|
1903
|
-
"status": "working_tree_clean",
|
|
1904
|
-
"no_changes": True,
|
|
1905
1903
|
"changed_files": [],
|
|
1906
|
-
"message": "
|
|
1904
|
+
"message": "no uncommitted changes detected",
|
|
1905
|
+
"analysis_scope": "empty",
|
|
1906
|
+
"_meta": {"changed_only": True},
|
|
1907
1907
|
}, ensure_ascii=False)
|
|
1908
1908
|
write_output(_nc_payload, output=output)
|
|
1909
1909
|
raise typer.Exit()
|
|
@@ -2626,7 +2626,14 @@ def prepare_context_cmd(
|
|
|
2626
2626
|
if _task_include("improvement_opportunities") and output.improvement_opportunities:
|
|
2627
2627
|
out["improvement_opportunities"] = output.improvement_opportunities
|
|
2628
2628
|
if _task_include("test_gaps") and output.test_gaps:
|
|
2629
|
-
|
|
2629
|
+
# Emit both the canonical name (untested_sources) and the compat alias (test_gaps)
|
|
2630
|
+
# so agents can use either. untested_sources is the correct semantic name.
|
|
2631
|
+
out["untested_sources"] = output.test_gaps
|
|
2632
|
+
out["test_gaps"] = output.test_gaps # backward compat alias
|
|
2633
|
+
if task == "generate-tests":
|
|
2634
|
+
_et_count = getattr(output, "existing_test_count", None)
|
|
2635
|
+
if _et_count is not None:
|
|
2636
|
+
out["existing_test_count"] = _et_count
|
|
2630
2637
|
# P0-2: fast-mode truncation transparency — always emit when truncated, even if test_gaps is []
|
|
2631
2638
|
# Use `is True` (strict) so MagicMock objects in tests don't trigger this branch.
|
|
2632
2639
|
if getattr(output, "truncated", False) is True:
|
|
@@ -3543,6 +3550,12 @@ def fix_bug_cmd(
|
|
|
3543
3550
|
sourcecode impact <target> — Propagate impact from a specific class
|
|
3544
3551
|
sourcecode onboard . — Full architecture context first
|
|
3545
3552
|
"""
|
|
3553
|
+
if not symptom:
|
|
3554
|
+
typer.echo(
|
|
3555
|
+
"[fix-bug] Results are significantly better with --symptom. "
|
|
3556
|
+
"Example: --symptom 'NullPointerException in PaymentService'",
|
|
3557
|
+
err=True,
|
|
3558
|
+
)
|
|
3546
3559
|
ctx.invoke(
|
|
3547
3560
|
prepare_context_cmd,
|
|
3548
3561
|
task="fix-bug",
|
|
@@ -4298,19 +4311,21 @@ def cache_status_cmd(
|
|
|
4298
4311
|
def cache_clear_cmd(
|
|
4299
4312
|
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
|
|
4300
4313
|
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
|
|
4301
|
-
include_ris: bool = typer.Option(False, "--include-ris", help="
|
|
4314
|
+
include_ris: bool = typer.Option(False, "--include-ris", hidden=True, help="Alias for --all. Preserved for backward compatibility."),
|
|
4315
|
+
all_: bool = typer.Option(False, "--all", help="Also delete the RIS snapshot (ris.json.gz). By default, RIS is preserved across clears."),
|
|
4302
4316
|
) -> None:
|
|
4303
4317
|
"""Delete cached snapshots for a repository.
|
|
4304
4318
|
|
|
4305
4319
|
By default, RIS (ris.json.gz) is preserved — it is the persistent structural
|
|
4306
|
-
index used for cold-start bootstrapping. Use --
|
|
4320
|
+
index used for cold-start bootstrapping. Use --all to also clear it.
|
|
4307
4321
|
"""
|
|
4308
4322
|
from sourcecode import cache as _cm
|
|
4309
4323
|
target = Path(path).resolve()
|
|
4324
|
+
_clear_ris = include_ris or all_
|
|
4310
4325
|
if not yes:
|
|
4311
|
-
_ris_note = " (including RIS)" if
|
|
4326
|
+
_ris_note = " (including RIS)" if _clear_ris else " (RIS preserved — use --all to also clear it)"
|
|
4312
4327
|
typer.confirm(f"Delete all cache files for {target}{_ris_note}?", abort=True)
|
|
4313
|
-
removed = _cm.clear(target, clear_ris=
|
|
4328
|
+
removed = _cm.clear(target, clear_ris=_clear_ris)
|
|
4314
4329
|
typer.echo(f"Removed {removed} file(s).")
|
|
4315
4330
|
|
|
4316
4331
|
|
|
@@ -4320,7 +4335,11 @@ def cache_warm_cmd(
|
|
|
4320
4335
|
compact: bool = typer.Option(True, "--compact/--no-compact", help="Warm compact view (default: on)."),
|
|
4321
4336
|
agent: bool = typer.Option(False, "--agent", help="Also warm agent view."),
|
|
4322
4337
|
) -> None:
|
|
4323
|
-
"""Pre-populate the cache by running a fresh analysis.
|
|
4338
|
+
"""Pre-populate the cache by running a fresh analysis.
|
|
4339
|
+
|
|
4340
|
+
Runs a full analysis to populate L1/L2 caches and rebuild the RIS
|
|
4341
|
+
(Repository Intelligence Snapshot). Useful after a merge/pull in CI.
|
|
4342
|
+
"""
|
|
4324
4343
|
import shutil as _shutil
|
|
4325
4344
|
import subprocess as _sub
|
|
4326
4345
|
import sys as _sys
|
|
@@ -4334,7 +4353,7 @@ def cache_warm_cmd(
|
|
|
4334
4353
|
cmd.append("--agent")
|
|
4335
4354
|
result = _sub.run(cmd, capture_output=True, text=True)
|
|
4336
4355
|
if result.returncode == 0:
|
|
4337
|
-
typer.echo("Cache warmed.", err=True)
|
|
4356
|
+
typer.echo("Cache warmed (L1/L2 + RIS rebuilt).", err=True)
|
|
4338
4357
|
else:
|
|
4339
4358
|
typer.echo(f"Warm failed (exit {result.returncode}).", err=True)
|
|
4340
4359
|
if result.stderr:
|
|
@@ -4342,6 +4361,88 @@ def cache_warm_cmd(
|
|
|
4342
4361
|
raise typer.Exit(code=result.returncode)
|
|
4343
4362
|
|
|
4344
4363
|
|
|
4364
|
+
@cache_app.command("freshness")
|
|
4365
|
+
def cache_freshness_cmd(
|
|
4366
|
+
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
|
|
4367
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
|
|
4368
|
+
) -> None:
|
|
4369
|
+
"""Report RIS freshness relative to the current git HEAD.
|
|
4370
|
+
|
|
4371
|
+
Answers: is the cached snapshot current? How many commits behind is it?
|
|
4372
|
+
|
|
4373
|
+
\b
|
|
4374
|
+
Output fields:
|
|
4375
|
+
fresh — True when RIS HEAD matches current HEAD and no uncommitted changes
|
|
4376
|
+
current_git_head — Current repo HEAD (short SHA)
|
|
4377
|
+
ris_git_head — HEAD stored in RIS when it was last built
|
|
4378
|
+
delta_commits — Number of commits between ris_git_head and HEAD (0 = in sync)
|
|
4379
|
+
has_uncommitted_changes — Working tree has staged/unstaged changes
|
|
4380
|
+
ris_exists — False when no RIS has been built yet
|
|
4381
|
+
ris_last_updated_at — ISO-8601 timestamp of last RIS write
|
|
4382
|
+
"""
|
|
4383
|
+
import json as _json
|
|
4384
|
+
import subprocess as _sub
|
|
4385
|
+
from sourcecode import cache as _cm
|
|
4386
|
+
from sourcecode.ris import _has_uncommitted_changes as _huc
|
|
4387
|
+
from sourcecode.ris import load_ris as _lris
|
|
4388
|
+
|
|
4389
|
+
target = Path(path).resolve()
|
|
4390
|
+
current_head = _cm._get_git_head(target)
|
|
4391
|
+
ris = _lris(target)
|
|
4392
|
+
|
|
4393
|
+
if ris is None:
|
|
4394
|
+
result: dict = {
|
|
4395
|
+
"fresh": False,
|
|
4396
|
+
"ris_exists": False,
|
|
4397
|
+
"current_git_head": current_head,
|
|
4398
|
+
"ris_git_head": None,
|
|
4399
|
+
"delta_commits": None,
|
|
4400
|
+
"has_uncommitted_changes": _huc(target),
|
|
4401
|
+
"ris_last_updated_at": None,
|
|
4402
|
+
}
|
|
4403
|
+
else:
|
|
4404
|
+
ris_head = ris.git_head
|
|
4405
|
+
head_matches = bool(current_head and ris_head and current_head == ris_head)
|
|
4406
|
+
uncommitted = _huc(target)
|
|
4407
|
+
|
|
4408
|
+
# Count commits between ris_head and current HEAD
|
|
4409
|
+
delta = None
|
|
4410
|
+
if ris_head and current_head and ris_head != current_head:
|
|
4411
|
+
try:
|
|
4412
|
+
_r = _sub.run(
|
|
4413
|
+
["git", "-C", str(target), "rev-list", "--count", f"{ris_head}..HEAD"],
|
|
4414
|
+
capture_output=True, text=True, timeout=5,
|
|
4415
|
+
)
|
|
4416
|
+
if _r.returncode == 0:
|
|
4417
|
+
delta = int(_r.stdout.strip())
|
|
4418
|
+
except Exception:
|
|
4419
|
+
pass
|
|
4420
|
+
elif head_matches:
|
|
4421
|
+
delta = 0
|
|
4422
|
+
|
|
4423
|
+
result = {
|
|
4424
|
+
"fresh": head_matches and not uncommitted,
|
|
4425
|
+
"ris_exists": True,
|
|
4426
|
+
"current_git_head": current_head,
|
|
4427
|
+
"ris_git_head": ris_head,
|
|
4428
|
+
"delta_commits": delta,
|
|
4429
|
+
"has_uncommitted_changes": uncommitted,
|
|
4430
|
+
"ris_last_updated_at": ris.last_updated_at,
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
if json_output:
|
|
4434
|
+
typer.echo(_json.dumps(result, indent=2, ensure_ascii=False))
|
|
4435
|
+
else:
|
|
4436
|
+
_fresh_tag = "FRESH" if result["fresh"] else "STALE"
|
|
4437
|
+
typer.echo(f"Status: {_fresh_tag}")
|
|
4438
|
+
typer.echo(f"Current HEAD: {result['current_git_head'] or '(unknown)'}")
|
|
4439
|
+
typer.echo(f"RIS HEAD: {result.get('ris_git_head') or '(none)'}")
|
|
4440
|
+
if result.get("delta_commits") is not None:
|
|
4441
|
+
typer.echo(f"Delta: {result['delta_commits']} commit(s) behind")
|
|
4442
|
+
typer.echo(f"Uncommitted: {result['has_uncommitted_changes']}")
|
|
4443
|
+
typer.echo(f"RIS updated: {result.get('ris_last_updated_at') or 'never'}")
|
|
4444
|
+
|
|
4445
|
+
|
|
4345
4446
|
# ── Entry point ───────────────────────────────────────────────────────────────
|
|
4346
4447
|
|
|
4347
4448
|
def main_entry() -> None:
|
sourcecode/mcp/server.py
CHANGED
|
@@ -215,7 +215,10 @@ def get_agent_context(repo_path: str = ".", git_context: bool = False) -> dict:
|
|
|
215
215
|
|
|
216
216
|
@mcp.tool()
|
|
217
217
|
def get_endpoints(repo_path: str = ".") -> dict:
|
|
218
|
-
"""REST API endpoint surface extraction from Java source files.
|
|
218
|
+
"""REST API endpoint surface extraction from Java source files. JAVA ONLY.
|
|
219
|
+
|
|
220
|
+
Do NOT call this on non-Java repositories — it will return empty results.
|
|
221
|
+
Use get_compact_context or get_agent_context for non-Java repos.
|
|
219
222
|
|
|
220
223
|
Maps to: sourcecode endpoints <repo_path>
|
|
221
224
|
Returns: endpoints list with method, path, controller, handler fields;
|
|
@@ -230,7 +233,7 @@ def get_endpoints(repo_path: str = ".") -> dict:
|
|
|
230
233
|
Supports Spring MVC (@GetMapping etc.) and JAX-RS (@GET/@POST etc.).
|
|
231
234
|
Security annotations detected: @RolesAllowed, @PermitAll, @DenyAll,
|
|
232
235
|
@Authenticated, @PreAuthorize, @Secured, @SecurityRequirement, @M3FiltroSeguridad.
|
|
233
|
-
repo_path: absolute path to the repository (default: current working directory).
|
|
236
|
+
repo_path: absolute path to the Java repository (default: current working directory).
|
|
234
237
|
"""
|
|
235
238
|
_raw = repo_path
|
|
236
239
|
try:
|
|
@@ -275,25 +278,43 @@ def get_module_context(repo_path: str = ".", module: str = "") -> dict:
|
|
|
275
278
|
)
|
|
276
279
|
|
|
277
280
|
|
|
281
|
+
def _auto_since(repo_path: str) -> str:
|
|
282
|
+
"""Detect best merge-base for delta: origin/main > origin/master > HEAD~1."""
|
|
283
|
+
import subprocess as _sp
|
|
284
|
+
for base in ("origin/main", "origin/master"):
|
|
285
|
+
try:
|
|
286
|
+
r = _sp.run(
|
|
287
|
+
["git", "-C", repo_path, "merge-base", "HEAD", base],
|
|
288
|
+
capture_output=True, text=True, timeout=5,
|
|
289
|
+
)
|
|
290
|
+
if r.returncode == 0 and r.stdout.strip():
|
|
291
|
+
return r.stdout.strip()
|
|
292
|
+
except Exception:
|
|
293
|
+
pass
|
|
294
|
+
return "HEAD~1"
|
|
295
|
+
|
|
296
|
+
|
|
278
297
|
@mcp.tool()
|
|
279
|
-
def get_delta(repo_path: str = ".", since: str = "
|
|
298
|
+
def get_delta(repo_path: str = ".", since: str = "") -> dict:
|
|
280
299
|
"""Incremental context: git-changed files since a reference commit.
|
|
281
300
|
|
|
282
301
|
Maps to: sourcecode prepare-context delta <repo_path> --since <since>
|
|
283
302
|
repo_path: absolute path to the repository (default: current working directory).
|
|
284
303
|
since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
304
|
+
If empty or omitted, auto-detects merge-base with origin/main (or
|
|
305
|
+
origin/master). Falls back to HEAD~1 if no remote branch found.
|
|
306
|
+
Pass "HEAD~1" explicitly to force single-commit diff.
|
|
285
307
|
"""
|
|
286
308
|
_raw = repo_path
|
|
287
309
|
try:
|
|
288
310
|
if not isinstance(repo_path, str):
|
|
289
311
|
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
290
|
-
if not isinstance(since, str) or not since.strip():
|
|
291
|
-
return _err("since must be a non-empty git ref", "INVALID_ARGUMENT")
|
|
292
312
|
repo_path = _normalize_repo_path(repo_path)
|
|
293
313
|
_path_err = _check_repo_path(repo_path)
|
|
294
314
|
if _path_err is not None:
|
|
295
315
|
return _path_err
|
|
296
|
-
|
|
316
|
+
_since = since.strip() if isinstance(since, str) and since.strip() else _auto_since(repo_path)
|
|
317
|
+
return _execute(["prepare-context", "delta", repo_path, "--since", _since])
|
|
297
318
|
except Exception as exc:
|
|
298
319
|
return _err(
|
|
299
320
|
f"Internal error: {type(exc).__name__}: {exc} — repo_path recibido: {_raw}",
|
|
@@ -301,6 +322,40 @@ def get_delta(repo_path: str = ".", since: str = "HEAD~1") -> dict:
|
|
|
301
322
|
)
|
|
302
323
|
|
|
303
324
|
|
|
325
|
+
@mcp.tool()
|
|
326
|
+
def check_freshness(repo_path: str = ".") -> dict:
|
|
327
|
+
"""Report RIS freshness relative to the current git HEAD.
|
|
328
|
+
|
|
329
|
+
Answers instantly: is the cached snapshot current? How many commits behind?
|
|
330
|
+
Use before deciding whether to call get_compact_context for a refresh.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
fresh (bool) — True when RIS HEAD == current HEAD and no uncommitted changes
|
|
334
|
+
current_git_head (str) — Current repo HEAD (short SHA)
|
|
335
|
+
ris_git_head (str|null) — HEAD stored in RIS at last build
|
|
336
|
+
delta_commits (int|null) — Commits between ris_git_head and HEAD (0 = in sync)
|
|
337
|
+
has_uncommitted_changes — Working tree has staged or unstaged changes
|
|
338
|
+
ris_exists (bool) — False when no RIS built yet
|
|
339
|
+
ris_last_updated_at (str) — ISO-8601 timestamp of last RIS write
|
|
340
|
+
|
|
341
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
342
|
+
"""
|
|
343
|
+
_raw = repo_path
|
|
344
|
+
try:
|
|
345
|
+
if not isinstance(repo_path, str):
|
|
346
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
347
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
348
|
+
_path_err = _check_repo_path(repo_path)
|
|
349
|
+
if _path_err is not None:
|
|
350
|
+
return _path_err
|
|
351
|
+
return _execute(["cache", "freshness", repo_path, "--json"])
|
|
352
|
+
except Exception as exc:
|
|
353
|
+
return _err(
|
|
354
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path: {_raw}",
|
|
355
|
+
"INTERNAL_ERROR",
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
304
359
|
@mcp.tool()
|
|
305
360
|
def get_ir_summary(repo_path: str = ".") -> dict:
|
|
306
361
|
"""Deterministic symbol-level IR summary for Java repositories. Java only.
|
sourcecode/prepare_context.py
CHANGED
|
@@ -391,6 +391,8 @@ class TaskOutput:
|
|
|
391
391
|
# P0-2: fast-mode truncation transparency
|
|
392
392
|
truncated: bool = False
|
|
393
393
|
truncated_reason: Optional[str] = None
|
|
394
|
+
# generate-tests: count of existing test files found (complements untested_sources)
|
|
395
|
+
existing_test_count: Optional[int] = None
|
|
394
396
|
|
|
395
397
|
|
|
396
398
|
@dataclass
|
|
@@ -2237,6 +2239,8 @@ class TaskContextBuilder:
|
|
|
2237
2239
|
# P0-2: fast-mode truncation transparency
|
|
2238
2240
|
truncated=_fast_truncated,
|
|
2239
2241
|
truncated_reason=_fast_truncated_reason,
|
|
2242
|
+
# generate-tests: count of test files found alongside untested_sources
|
|
2243
|
+
existing_test_count=len(test_set) if task_name == "generate-tests" else None,
|
|
2240
2244
|
)
|
|
2241
2245
|
|
|
2242
2246
|
def render_prompt(self, output: TaskOutput) -> str:
|
sourcecode/ris.py
CHANGED
|
@@ -384,30 +384,65 @@ def get_cold_start_context(repo_root: Path) -> dict:
|
|
|
384
384
|
uncommitted = _has_uncommitted_changes(repo_root)
|
|
385
385
|
|
|
386
386
|
endpoints = ris.api_surface.get("endpoints", [])
|
|
387
|
+
_is_java = (
|
|
388
|
+
(repo_root / "pom.xml").exists()
|
|
389
|
+
or (repo_root / "build.gradle").exists()
|
|
390
|
+
or (repo_root / "build.gradle.kts").exists()
|
|
391
|
+
)
|
|
392
|
+
# api_surface_complete: False when this is a Java repo but endpoints are absent.
|
|
393
|
+
# An empty list does NOT mean "no endpoints exist" — it means the endpoint
|
|
394
|
+
# index has not been built yet. Agents must call get_endpoints to populate.
|
|
395
|
+
_api_complete = not _is_java or bool(endpoints)
|
|
396
|
+
|
|
397
|
+
# Build structural validation for Java/Spring repos.
|
|
398
|
+
# Detects when the RIS snapshot is structurally incomplete (controllers found
|
|
399
|
+
# but endpoint index was never built), so agents can decide whether to rebuild.
|
|
400
|
+
_controllers_in_map = ris.structural_map.get("controllers", [])
|
|
401
|
+
_controllers_in_api = ris.api_surface.get("controllers_index", [])
|
|
402
|
+
_controllers_found = len(_controllers_in_map) or len(_controllers_in_api)
|
|
403
|
+
_endpoints_found = len(endpoints)
|
|
404
|
+
# Spring is detected when controllers exist in structural map or api surface.
|
|
405
|
+
_spring_detected = bool(_controllers_found) or bool(_controllers_in_api)
|
|
406
|
+
_validation_status = (
|
|
407
|
+
"incomplete_snapshot"
|
|
408
|
+
if _is_java and _spring_detected and _endpoints_found == 0
|
|
409
|
+
else "valid"
|
|
410
|
+
)
|
|
411
|
+
_validation: dict = {
|
|
412
|
+
"spring_detected": _spring_detected,
|
|
413
|
+
"controllers_found": _controllers_found,
|
|
414
|
+
"endpoints_found": _endpoints_found,
|
|
415
|
+
"status": _validation_status,
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
# When the snapshot is structurally incomplete, downgrade status so agents
|
|
419
|
+
# don't assume cold_start_ready when critical sections are missing.
|
|
420
|
+
_status_base = "cold_start_stale" if stale else "cold_start_ready"
|
|
421
|
+
if _validation_status == "incomplete_snapshot" and not stale:
|
|
422
|
+
_status_base = "cold_start_incomplete"
|
|
423
|
+
|
|
387
424
|
result: dict = {
|
|
388
|
-
"status":
|
|
425
|
+
"status": _status_base,
|
|
389
426
|
"repo_id": ris.repo_id,
|
|
390
427
|
"git_head": ris.git_head,
|
|
391
428
|
"current_git_head": current_head,
|
|
392
|
-
"stale": stale,
|
|
429
|
+
"stale": stale or (_validation_status == "incomplete_snapshot"),
|
|
393
430
|
"has_uncommitted_changes": uncommitted,
|
|
394
431
|
"last_updated_at": ris.last_updated_at,
|
|
395
432
|
"cache_source": "RIS",
|
|
396
433
|
"data_scope": "RIS_BOOTSTRAP",
|
|
434
|
+
"api_surface_complete": _api_complete,
|
|
397
435
|
"summary": ris.compact_summary,
|
|
398
436
|
"entrypoints": ris.structural_map.get("entrypoints", []),
|
|
399
437
|
"endpoints": endpoints,
|
|
400
438
|
"hotspots": ris.git_context_snapshot.get("hotspots", []),
|
|
439
|
+
"validation": _validation,
|
|
401
440
|
}
|
|
402
|
-
if not endpoints:
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
result["endpoints_hint"] = (
|
|
408
|
-
"Java repo detected but no endpoint index found. "
|
|
409
|
-
"Call get_endpoints (or: sourcecode endpoints <path>) to populate."
|
|
410
|
-
)
|
|
441
|
+
if not endpoints and _is_java:
|
|
442
|
+
result["endpoints_hint"] = (
|
|
443
|
+
"Java repo detected but no endpoint index found. "
|
|
444
|
+
"Call get_endpoints (or: sourcecode endpoints <path>) to populate."
|
|
445
|
+
)
|
|
411
446
|
return result
|
|
412
447
|
except Exception:
|
|
413
448
|
return {"status": "no_ris"}
|
sourcecode/serializer.py
CHANGED
|
@@ -631,9 +631,12 @@ def _bootstrap_structured(eps: list) -> "Optional[dict[str, Any]]":
|
|
|
631
631
|
if security:
|
|
632
632
|
result["security"] = security
|
|
633
633
|
if controllers:
|
|
634
|
-
#
|
|
634
|
+
# Each controller file generates one EntryPoint regardless of how many
|
|
635
|
+
# handler methods it contains. controller_classes == len(controllers)
|
|
636
|
+
# always (deduplicated by path in _scan_java_file_for_entry_points).
|
|
637
|
+
# "methods" is therefore removed from the note — use `sourcecode endpoints`
|
|
638
|
+
# for per-method HTTP surface.
|
|
635
639
|
controller_classes = len({c["path"] for c in controllers})
|
|
636
|
-
controller_methods = len(controllers)
|
|
637
640
|
|
|
638
641
|
# Extract all DDD module names from controller paths and group by domain area.
|
|
639
642
|
# Path pattern: .../ddd/{module}/infrastructure/rest/*Controller.java
|
|
@@ -655,9 +658,8 @@ def _bootstrap_structured(eps: list) -> "Optional[dict[str, Any]]":
|
|
|
655
658
|
module_names.append(module)
|
|
656
659
|
|
|
657
660
|
_ctrl_note = (
|
|
658
|
-
f"{
|
|
659
|
-
f"
|
|
660
|
-
f" (use 'sourcecode endpoints' for full surface)"
|
|
661
|
+
f"{controller_classes} controller classes detected"
|
|
662
|
+
f" (use 'sourcecode endpoints' for per-method HTTP surface)"
|
|
661
663
|
)
|
|
662
664
|
if len(module_names) > 30:
|
|
663
665
|
# Group by first path segment under ddd/ (inferred domain area)
|
|
@@ -678,14 +680,12 @@ def _bootstrap_structured(eps: list) -> "Optional[dict[str, Any]]":
|
|
|
678
680
|
domain_groups[domain_prefix or "other"].append(module)
|
|
679
681
|
result["controllers"] = {
|
|
680
682
|
"classes": controller_classes,
|
|
681
|
-
"methods": controller_methods,
|
|
682
683
|
"note": _ctrl_note,
|
|
683
684
|
"modules": {k: sorted(v) for k, v in sorted(domain_groups.items())},
|
|
684
685
|
}
|
|
685
686
|
else:
|
|
686
687
|
result["controllers"] = {
|
|
687
688
|
"classes": controller_classes,
|
|
688
|
-
"methods": controller_methods,
|
|
689
689
|
"note": _ctrl_note,
|
|
690
690
|
"modules": sorted(module_names),
|
|
691
691
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.33.
|
|
3
|
+
Version: 1.33.4
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -39,7 +39,7 @@ Description-Content-Type: text/markdown
|
|
|
39
39
|
|
|
40
40
|
**Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
|
|
41
41
|
|
|
42
|
-

|
|
43
43
|

|
|
44
44
|
|
|
45
45
|
---
|
|
@@ -113,7 +113,7 @@ pipx install sourcecode
|
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
115
|
sourcecode version
|
|
116
|
-
# sourcecode 1.33.
|
|
116
|
+
# sourcecode 1.33.4
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
---
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=oG8RdsJRmrxoLhAnBA3rq7bWu7SPvQTfWedYeipB42k,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=qh749a7ykPtGmQI1MR9y6j8TtL_jBdVYFx9YRsLqOMw,44121
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
@@ -7,7 +7,7 @@ sourcecode/cache.py,sha256=wAyPrXN5DqiGivnMpeEuun2xHDKfBer2_oBsh6kj_vc,30447
|
|
|
7
7
|
sourcecode/cache.tmp_new,sha256=-IvV7CojiZjqeKMln1m-lqI0QVA2uFGWmYir4XRFOUk,27970
|
|
8
8
|
sourcecode/canonical_ir.py,sha256=_HM3AUmKSdna9u4dCoU6rpgSA6HdF8gzOKZykIUCNGY,23277
|
|
9
9
|
sourcecode/classifier.py,sha256=2lYoSH3vOTkXZYPU7Go2WIet1-IuNzTWVhc-ULnXtgw,8024
|
|
10
|
-
sourcecode/cli.py,sha256=
|
|
10
|
+
sourcecode/cli.py,sha256=K2FT1wLNZuG_-9nKSOGdYbk-5GQkn4u7C2NR6IxdePY,180950
|
|
11
11
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
12
12
|
sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
|
|
13
13
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
@@ -29,19 +29,19 @@ sourcecode/metrics_analyzer.py,sha256=m0ENgtqKeBL17kUIK3fmGkgo7UfXBNHxCMj0H_Y5K7
|
|
|
29
29
|
sourcecode/output_budget.py,sha256=43307mJEyUPU3MI-QEQoVxrcAvNyUzdzF_SAPgisBQE,6603
|
|
30
30
|
sourcecode/path_filters.py,sha256=ROFRQ8eSLBEMiixK9f45-RO7um4VEEcjoD5AA4I427I,3739
|
|
31
31
|
sourcecode/pr_comment_renderer.py,sha256=smHslxiG14lrytCkq5nFrFu-qTHgA-t-LFYfdrfjz2o,14423
|
|
32
|
-
sourcecode/prepare_context.py,sha256=
|
|
32
|
+
sourcecode/prepare_context.py,sha256=aL4WS62wozw9iG_v3UrU00Qc7lGgckUB5RY5ApPblo8,205618
|
|
33
33
|
sourcecode/progress.py,sha256=qn30sWaHOkjTgXsSBmiPkz7Rsbwc5oSlIe6JNEMYp_k,3149
|
|
34
34
|
sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
|
|
35
35
|
sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
36
36
|
sourcecode/relevance_scorer.py,sha256=MYF4FFkveAQps9SmTeTlh6ODiBz2F--_hWNeHMLtUHQ,8405
|
|
37
37
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
38
38
|
sourcecode/repository_ir.py,sha256=-NjBQUT7zyya4ng8Hq0-ChoiHZkUif9lr-Q878gmj8M,153163
|
|
39
|
-
sourcecode/ris.py,sha256=
|
|
39
|
+
sourcecode/ris.py,sha256=vkIe_v-jjceeb0Adhn-Kw5eFXE_nIkGHflOnQYPycTk,17411
|
|
40
40
|
sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
|
|
41
41
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
42
42
|
sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
|
|
43
43
|
sourcecode/semantic_analyzer.py,sha256=TDuC3wzZR2DPm1mgrAg1YSLk2QzJoueS3TZAmyGGpCU,89417
|
|
44
|
-
sourcecode/serializer.py,sha256=
|
|
44
|
+
sourcecode/serializer.py,sha256=ooNZW2_fqx__BXII25eAWq-BomodvqQ6opUT_niQYCA,123403
|
|
45
45
|
sourcecode/summarizer.py,sha256=YspHEVeYJVmltq0FMtGZF8kIP3qiR2KLcanGL6Y7uTI,20747
|
|
46
46
|
sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
|
|
47
47
|
sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
|
|
@@ -68,7 +68,7 @@ sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG6
|
|
|
68
68
|
sourcecode/detectors/tooling.py,sha256=8CKbtxwQoABP-WyBRNmdAmHDOvAH57AR1cF4UKuWEdQ,2074
|
|
69
69
|
sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
|
|
70
70
|
sourcecode/mcp/runner.py,sha256=YSw2DXEICau6mCBr3Gfia3D_tKxMbRvIIXEh4cHC1SY,1390
|
|
71
|
-
sourcecode/mcp/server.py,sha256=
|
|
71
|
+
sourcecode/mcp/server.py,sha256=dAmoUbnu4kPPWeRH2tcOn9WJPG8Zfzvlu0nRmyiok70,27925
|
|
72
72
|
sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
|
|
73
73
|
sourcecode/mcp/onboarding/applier.py,sha256=B9CneieWTpaDSDIyW3S5nrlRlBpvfqUcgi93-mm_ApQ,2135
|
|
74
74
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
@@ -80,8 +80,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
80
80
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
81
81
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
82
82
|
sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
|
|
83
|
-
sourcecode-1.33.
|
|
84
|
-
sourcecode-1.33.
|
|
85
|
-
sourcecode-1.33.
|
|
86
|
-
sourcecode-1.33.
|
|
87
|
-
sourcecode-1.33.
|
|
83
|
+
sourcecode-1.33.4.dist-info/METADATA,sha256=Q9XlsPNiuahPxhrDZUU8OqtH1U_DH77rv22bnvblazA,16440
|
|
84
|
+
sourcecode-1.33.4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
85
|
+
sourcecode-1.33.4.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
86
|
+
sourcecode-1.33.4.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
87
|
+
sourcecode-1.33.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|