sourcecode 1.33.9__tar.gz → 1.33.11__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 (95) hide show
  1. {sourcecode-1.33.9 → sourcecode-1.33.11}/PKG-INFO +2 -2
  2. {sourcecode-1.33.9 → sourcecode-1.33.11}/README.md +1 -1
  3. {sourcecode-1.33.9 → sourcecode-1.33.11}/pyproject.toml +1 -1
  4. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/cli.py +92 -12
  6. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/mcp/server.py +3 -1
  7. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/prepare_context.py +4 -3
  8. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/repository_ir.py +58 -0
  9. {sourcecode-1.33.9 → sourcecode-1.33.11}/.github/workflows/build-windows.yml +0 -0
  10. {sourcecode-1.33.9 → sourcecode-1.33.11}/.gitignore +0 -0
  11. {sourcecode-1.33.9 → sourcecode-1.33.11}/.ruff.toml +0 -0
  12. {sourcecode-1.33.9 → sourcecode-1.33.11}/CHANGELOG.md +0 -0
  13. {sourcecode-1.33.9 → sourcecode-1.33.11}/CONTRIBUTING.md +0 -0
  14. {sourcecode-1.33.9 → sourcecode-1.33.11}/LICENSE +0 -0
  15. {sourcecode-1.33.9 → sourcecode-1.33.11}/SECURITY.md +0 -0
  16. {sourcecode-1.33.9 → sourcecode-1.33.11}/raw +0 -0
  17. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/adaptive_scanner.py +0 -0
  18. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/architecture_analyzer.py +0 -0
  19. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/architecture_summary.py +0 -0
  20. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/ast_extractor.py +0 -0
  21. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/cache.py +0 -0
  22. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/canonical_ir.py +0 -0
  23. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/classifier.py +0 -0
  24. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/code_notes_analyzer.py +0 -0
  25. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/confidence_analyzer.py +0 -0
  26. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/context_scorer.py +0 -0
  27. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/context_summarizer.py +0 -0
  28. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/contract_model.py +0 -0
  29. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/contract_pipeline.py +0 -0
  30. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/coverage_parser.py +0 -0
  31. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/dependency_analyzer.py +0 -0
  32. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/__init__.py +0 -0
  33. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/base.py +0 -0
  34. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/csproj_parser.py +0 -0
  35. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/dart.py +0 -0
  36. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/dotnet.py +0 -0
  37. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/elixir.py +0 -0
  38. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/go.py +0 -0
  39. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/heuristic.py +0 -0
  40. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/hybrid.py +0 -0
  41. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/java.py +0 -0
  42. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/jvm_ext.py +0 -0
  43. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/nodejs.py +0 -0
  44. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/parsers.py +0 -0
  45. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/php.py +0 -0
  46. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/project.py +0 -0
  47. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/python.py +0 -0
  48. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/ruby.py +0 -0
  49. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/rust.py +0 -0
  50. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/systems.py +0 -0
  51. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/terraform.py +0 -0
  52. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/detectors/tooling.py +0 -0
  53. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/doc_analyzer.py +0 -0
  54. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/entrypoint_classifier.py +0 -0
  55. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/env_analyzer.py +0 -0
  56. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/error_schema.py +0 -0
  57. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/file_classifier.py +0 -0
  58. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/flow_analyzer.py +0 -0
  59. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/git_analyzer.py +0 -0
  60. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/graph_analyzer.py +0 -0
  61. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/license.py +0 -0
  62. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/mcp/__init__.py +0 -0
  63. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  64. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  65. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  66. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  67. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  68. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/mcp/orchestrator.py +0 -0
  69. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/mcp/registry.py +0 -0
  70. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/mcp/runner.py +0 -0
  71. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/mcp_nudge.py +0 -0
  72. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/metrics_analyzer.py +0 -0
  73. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/output_budget.py +0 -0
  74. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/path_filters.py +0 -0
  75. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/pr_comment_renderer.py +0 -0
  76. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/progress.py +0 -0
  77. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/ranking_engine.py +0 -0
  78. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/redactor.py +0 -0
  79. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/relevance_scorer.py +0 -0
  80. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/repo_classifier.py +0 -0
  81. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/ris.py +0 -0
  82. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/runtime_classifier.py +0 -0
  83. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/scanner.py +0 -0
  84. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/schema.py +0 -0
  85. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/semantic_analyzer.py +0 -0
  86. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/serializer.py +0 -0
  87. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/summarizer.py +0 -0
  88. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/telemetry/__init__.py +0 -0
  89. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/telemetry/config.py +0 -0
  90. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/telemetry/consent.py +0 -0
  91. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/telemetry/events.py +0 -0
  92. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/telemetry/filters.py +0 -0
  93. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/telemetry/transport.py +0 -0
  94. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/tree_utils.py +0 -0
  95. {sourcecode-1.33.9 → sourcecode-1.33.11}/src/sourcecode/workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.33.9
3
+ Version: 1.33.11
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
- ![Version](https://img.shields.io/badge/version-1.33.9-blue)
42
+ ![Version](https://img.shields.io/badge/version-1.33.11-blue)
43
43
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
44
44
 
45
45
  ---
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
4
4
 
5
- ![Version](https://img.shields.io/badge/version-1.33.9-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.33.11-blue)
6
6
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
7
7
 
8
8
  ---
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.33.9"
7
+ version = "1.33.11"
8
8
  description = "Persistent structural context and ultra-fast repeated analysis for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Deterministic codebase context maps for AI coding agents."""
2
2
 
3
- __version__ = "1.33.9"
3
+ __version__ = "1.33.11"
@@ -2677,10 +2677,11 @@ def prepare_context_cmd(
2677
2677
  # relevant_files goal: untested SOURCE files. Test files belong in test_gaps.
2678
2678
  # Without this filter, high-churn test files rank above untested source files.
2679
2679
  _rfs = [f for f in _rfs if getattr(f, "role", None) != "test"]
2680
- out["relevant_files"] = [
2681
- _serialize_relevant_file(f)
2682
- for f in _rfs
2683
- ]
2680
+ _serialized_rfs = [_serialize_relevant_file(f) for f in _rfs]
2681
+ out["relevant_files"] = _serialized_rfs
2682
+ if task == "fix-bug":
2683
+ # ranked_files was the v1 name for this field — emit as backward-compat alias.
2684
+ out["ranked_files"] = _serialized_rfs
2684
2685
  if _task_include("key_dependencies") and output.key_dependencies:
2685
2686
  out["key_dependencies"] = output.key_dependencies
2686
2687
  if _task_include("gaps") and output.gaps:
@@ -3291,13 +3292,33 @@ def impact_cmd(
3291
3292
  )
3292
3293
  from sourcecode.output_budget import trim_to_budget as _trim, BUDGET_IMPACT
3293
3294
 
3295
+ import sys as _sys_ic
3296
+ # Legacy-compat: old syntax was `impact <path> <target>`.
3297
+ # Detect: target resolves to an existing directory (not a class name), and
3298
+ # the path arg is not a valid directory (looks like a class name).
3299
+ _target_as_path = Path(target)
3300
+ if _target_as_path.is_dir() and not path.resolve().is_dir():
3301
+ # Gate on isatty() — non-TTY (MCP, pipes) must not receive text mixed into JSON stdout.
3302
+ if getattr(_sys_ic.stderr, "isatty", lambda: False)():
3303
+ _sys_ic.stderr.write(
3304
+ f"[impact] Legacy argument order detected: '{target}' is a directory, not a class name.\n"
3305
+ f"[impact] Swapping: target='{path}', path='{target}'. "
3306
+ f"New syntax: sourcecode impact <target> [path]\n"
3307
+ )
3308
+ _sys_ic.stderr.flush()
3309
+ target, path = str(path), _target_as_path
3310
+
3294
3311
  root = path.resolve()
3295
3312
  if not root.is_dir():
3296
3313
  _emit_error_json(
3297
3314
  INVALID_INPUT_CODE,
3298
3315
  f"'{root}' is not a valid directory.",
3299
3316
  path=str(root),
3300
- hint="Pass an existing repository directory.",
3317
+ hint=(
3318
+ "Pass an existing repository directory as the second argument. "
3319
+ "New syntax: sourcecode impact <target> [path] — "
3320
+ "target is the class name, path is the repo root."
3321
+ ),
3301
3322
  expected="A directory path.",
3302
3323
  )
3303
3324
  raise typer.Exit(1)
@@ -3621,11 +3642,15 @@ def fix_bug_cmd(
3621
3642
  sourcecode onboard . — Full architecture context first
3622
3643
  """
3623
3644
  if not symptom:
3624
- typer.echo(
3625
- "[fix-bug] Results are significantly better with --symptom. "
3626
- "Example: --symptom 'NullPointerException in PaymentService'",
3627
- err=True,
3628
- )
3645
+ import sys as _sys_fb
3646
+ # Only emit advisory to interactive terminals — non-TTY (MCP, pipes, scripts)
3647
+ # must never receive informational text mixed into JSON stdout.
3648
+ if getattr(_sys_fb.stderr, "isatty", lambda: False)():
3649
+ typer.echo(
3650
+ "[fix-bug] Results are significantly better with --symptom. "
3651
+ "Example: --symptom 'NullPointerException in PaymentService'",
3652
+ err=True,
3653
+ )
3629
3654
  ctx.invoke(
3630
3655
  prepare_context_cmd,
3631
3656
  task="fix-bug",
@@ -3904,8 +3929,23 @@ def activate_cmd(
3904
3929
 
3905
3930
  @app.command("version")
3906
3931
  def version_cmd() -> None:
3907
- """Show version and exit."""
3908
- typer.echo(f"sourcecode {__version__}")
3932
+ """Show version and exit.
3933
+
3934
+ Outputs human-readable text on interactive terminals.
3935
+ Outputs structured JSON on non-TTY (MCP, pipes, scripts):
3936
+ {"cli_version": "1.33.11", "mcp_schema_version": "1.33.11",
3937
+ "compatibility_schema_version": "1.0"}
3938
+ """
3939
+ import sys as _sys_ver
3940
+ if getattr(_sys_ver.stdout, "isatty", lambda: False)():
3941
+ typer.echo(f"sourcecode {__version__}")
3942
+ else:
3943
+ import json as _json_ver
3944
+ typer.echo(_json_ver.dumps({
3945
+ "cli_version": __version__,
3946
+ "mcp_schema_version": __version__,
3947
+ "compatibility_schema_version": "1.0",
3948
+ }, ensure_ascii=False))
3909
3949
 
3910
3950
 
3911
3951
  # ── config ────────────────────────────────────────────────────────────────────
@@ -4351,6 +4391,46 @@ def mcp_remove(
4351
4391
  typer.echo(" Re-add: sourcecode mcp init")
4352
4392
 
4353
4393
 
4394
+ @mcp_app.command("list-tools")
4395
+ def mcp_list_tools(
4396
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
4397
+ ) -> None:
4398
+ """List all MCP tools exposed by the sourcecode server.
4399
+
4400
+ \b
4401
+ Shows each tool name, its description, and the CLI command it maps to.
4402
+ Useful for discovering capabilities when using sourcecode as an MCP server.
4403
+
4404
+ \b
4405
+ Examples:
4406
+ sourcecode mcp list-tools
4407
+ sourcecode mcp list-tools --json
4408
+ """
4409
+ import asyncio
4410
+ import json as _json
4411
+
4412
+ from sourcecode.mcp.server import mcp as _mcp
4413
+
4414
+ tools = asyncio.run(_mcp.list_tools())
4415
+ tools_sorted = sorted(tools, key=lambda t: t.name)
4416
+
4417
+ if json_output:
4418
+ payload = [
4419
+ {"name": t.name, "description": (t.description or "").strip()}
4420
+ for t in tools_sorted
4421
+ ]
4422
+ typer.echo(_json.dumps(payload, indent=2, ensure_ascii=False))
4423
+ return
4424
+
4425
+ typer.echo(f"sourcecode MCP tools ({len(tools_sorted)} available)\n")
4426
+ for t in tools_sorted:
4427
+ desc_first_line = (t.description or "").strip().splitlines()[0] if t.description else ""
4428
+ typer.echo(f" {t.name:<35} {desc_first_line}")
4429
+ typer.echo("")
4430
+ typer.echo("Use: sourcecode mcp serve — start MCP server on stdio")
4431
+ typer.echo("Use: sourcecode mcp init — configure MCP client")
4432
+
4433
+
4354
4434
  # ── Cache subcommands ─────────────────────────────────────────────────────────
4355
4435
 
4356
4436
 
@@ -959,9 +959,11 @@ _TELEMETRY_ACTIONS = frozenset({"status", "enable", "disable"})
959
959
 
960
960
  @mcp.tool()
961
961
  def version() -> dict:
962
- """Print sourcecode CLI version.
962
+ """Return sourcecode version and MCP compatibility metadata.
963
963
 
964
964
  Maps to: sourcecode version
965
+ Returns structured JSON: cli_version, mcp_schema_version, compatibility_schema_version.
966
+ cli_version and mcp_schema_version are always identical (released together).
965
967
  """
966
968
  return _execute(["version"])
967
969
 
@@ -1225,8 +1225,8 @@ class TaskContextBuilder:
1225
1225
  # Distinguish: no_staged_changes (CI, no --since) vs no_diff (empty range)
1226
1226
  if _pr_scope_source == "no_staged_changes":
1227
1227
  _no_diff_msg = (
1228
- "No --since ref provided and no staged changes found. "
1229
- "Use --since <ref>"
1228
+ "No --since ref provided and no staged/uncommitted changes found. "
1229
+ "Provide --since <ref> to specify the base commit for the diff."
1230
1230
  )
1231
1231
  return TaskOutput(
1232
1232
  task="review-pr", goal=spec.goal,
@@ -1239,7 +1239,8 @@ class TaskContextBuilder:
1239
1239
  error_message=_no_diff_msg,
1240
1240
  error_hints=[
1241
1241
  "Add --since <ref> to specify a base commit.",
1242
- "Examples: --since origin/main | --since HEAD~3 | --since main",
1242
+ "Common values: --since HEAD~1 (last commit) | --since origin/main | --since main",
1243
+ "If reviewing uncommitted changes: stage them first (git add), then run without --since.",
1243
1244
  ],
1244
1245
  gaps=[_no_diff_msg],
1245
1246
  ci_decision="no_diff_source",
@@ -232,6 +232,12 @@ _INJECT_ANNOTATIONS: frozenset[str] = frozenset({
232
232
  "@Autowired", "@Inject", "@Value", "@Qualifier", "@Resource",
233
233
  })
234
234
 
235
+ # Lombok annotations that generate constructors injecting fields
236
+ _LOMBOK_CTOR_ANNOTATIONS: frozenset[str] = frozenset({
237
+ "@RequiredArgsConstructor", # injects private final fields
238
+ "@AllArgsConstructor", # injects all non-static fields
239
+ })
240
+
235
241
  _JAVA_ROLE_MAP: dict[str, str] = {
236
242
  # Spring MVC / Spring Boot
237
243
  "@RestController": "controller",
@@ -934,6 +940,58 @@ def _build_relations(
934
940
  )},
935
941
  ))
936
942
 
943
+ # ── Constructor injection ─────────────────────────────────────────────────
944
+ # Spring 4.3+ omits @Autowired when there is a single constructor.
945
+ # Both annotated and bare constructors get injects edges from ClassName#<init>
946
+ # to each resolvable parameter type so the reverse graph can propagate impact.
947
+ for sym in symbols:
948
+ if sym.symbol_kind != "constructor" or not sym.param_types:
949
+ continue
950
+ for simple_type in sym.param_types:
951
+ base = re.sub(r'<.*', '', simple_type).strip()
952
+ fqn = import_map.get(base)
953
+ if fqn:
954
+ edges.append(RelationEdge(
955
+ from_symbol=sym.symbol,
956
+ to_symbol=fqn,
957
+ type="injects",
958
+ confidence="high",
959
+ evidence={"type": "constructor_param", "value": simple_type},
960
+ ))
961
+
962
+ # ── Lombok constructor injection ──────────────────────────────────────────
963
+ # @RequiredArgsConstructor: injects all private final fields.
964
+ # @AllArgsConstructor: injects all non-static fields.
965
+ # No explicit constructor symbol exists; edges are emitted from the class FQN.
966
+ for sym in symbols:
967
+ if sym.type not in ("class", "interface"):
968
+ continue
969
+ _has_req = "@RequiredArgsConstructor" in sym.annotations
970
+ _has_all = "@AllArgsConstructor" in sym.annotations
971
+ if not (_has_req or _has_all):
972
+ continue
973
+ _lombok_ann = "@RequiredArgsConstructor" if _has_req else "@AllArgsConstructor"
974
+ for _line in source.splitlines():
975
+ fld = _FIELD_DECL_RE.match(_line.strip())
976
+ if not fld:
977
+ continue
978
+ _mods = _parse_modifier_str(fld.group("modifiers") or "")
979
+ if "static" in _mods:
980
+ continue
981
+ if _has_req and "final" not in _mods:
982
+ continue
983
+ _ftype = fld.group("type").strip()
984
+ _base = re.sub(r'<.*', '', _ftype).strip()
985
+ _fqn = import_map.get(_base)
986
+ if _fqn:
987
+ edges.append(RelationEdge(
988
+ from_symbol=sym.symbol,
989
+ to_symbol=_fqn,
990
+ type="injects",
991
+ confidence="medium",
992
+ evidence={"type": "lombok_constructor", "value": _lombok_ann},
993
+ ))
994
+
937
995
  for m in re.finditer(
938
996
  r'(?:class|interface)\s+(\w+)(?:\s+extends\s+([\w.<>?,\s]+?))?'
939
997
  r'(?:\s+implements\s+([\w.<>?,\s]+?))?\s*\{',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes