sourcecode 1.36.5__tar.gz → 1.39.0__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.
- {sourcecode-1.36.5 → sourcecode-1.39.0}/PKG-INFO +6 -3
- {sourcecode-1.36.5 → sourcecode-1.39.0}/README.md +5 -2
- {sourcecode-1.36.5 → sourcecode-1.39.0}/pyproject.toml +1 -1
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/cli.py +137 -89
- sourcecode-1.39.0/src/sourcecode/format_contract.py +87 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/mcp/registry.py +47 -0
- sourcecode-1.39.0/src/sourcecode/openapi_surface.py +463 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/repository_ir.py +75 -9
- sourcecode-1.39.0/src/sourcecode/validation_surface.py +305 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/.gitignore +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/.ruff.toml +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/CHANGELOG.md +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/CONTRIBUTING.md +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/LICENSE +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/SECURITY.md +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/raw +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/cache.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/explain.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/license.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/ris.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/serializer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/supabase/.temp/cli-latest +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/supabase/functions/README.md +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-1.36.5 → sourcecode-1.39.0}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.39.0
|
|
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
|
|
@@ -40,7 +40,7 @@ Description-Content-Type: text/markdown
|
|
|
40
40
|
|
|
41
41
|
**Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
|
|
42
42
|
|
|
43
|
-

|
|
44
44
|

|
|
45
45
|
|
|
46
46
|
---
|
|
@@ -114,7 +114,7 @@ pipx install sourcecode
|
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
116
|
sourcecode version
|
|
117
|
-
# sourcecode 1.
|
|
117
|
+
# sourcecode 1.39.0
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
---
|
|
@@ -149,6 +149,9 @@ sourcecode impact-chain OrderPlacedEvent /path/to/repo --type events
|
|
|
149
149
|
# REST endpoint surface
|
|
150
150
|
sourcecode endpoints /path/to/repo
|
|
151
151
|
|
|
152
|
+
# Request-body validation per endpoint: constraints + custom validators (free)
|
|
153
|
+
sourcecode validation /path/to/repo
|
|
154
|
+
|
|
152
155
|
# Onboard to an unfamiliar codebase
|
|
153
156
|
sourcecode onboard /path/to/repo
|
|
154
157
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -76,7 +76,7 @@ pipx install sourcecode
|
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
78
|
sourcecode version
|
|
79
|
-
# sourcecode 1.
|
|
79
|
+
# sourcecode 1.39.0
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
---
|
|
@@ -111,6 +111,9 @@ sourcecode impact-chain OrderPlacedEvent /path/to/repo --type events
|
|
|
111
111
|
# REST endpoint surface
|
|
112
112
|
sourcecode endpoints /path/to/repo
|
|
113
113
|
|
|
114
|
+
# Request-body validation per endpoint: constraints + custom validators (free)
|
|
115
|
+
sourcecode validation /path/to/repo
|
|
116
|
+
|
|
114
117
|
# Onboard to an unfamiliar codebase
|
|
115
118
|
sourcecode onboard /path/to/repo
|
|
116
119
|
|
|
@@ -230,6 +230,8 @@ _SUBCOMMANDS: frozenset[str] = frozenset(
|
|
|
230
230
|
"cold-start",
|
|
231
231
|
# Spring semantic audit
|
|
232
232
|
"spring-audit",
|
|
233
|
+
# Request-body validation surface
|
|
234
|
+
"validation",
|
|
233
235
|
# Spring impact chain
|
|
234
236
|
"impact-chain",
|
|
235
237
|
# PR blast-radius report
|
|
@@ -401,6 +403,28 @@ def _emit_error_json(error: str, message: str, **context: object) -> None:
|
|
|
401
403
|
sys.stderr.flush()
|
|
402
404
|
|
|
403
405
|
|
|
406
|
+
def _enforce_format(command: str, fmt: str) -> None:
|
|
407
|
+
"""Validate ``--format`` for ``command`` against the central contract.
|
|
408
|
+
|
|
409
|
+
Single validation path for every command's ``--format`` option (see
|
|
410
|
+
``sourcecode.format_contract``). On an invalid value it emits the
|
|
411
|
+
homogeneous JSON error envelope to stderr and exits with code 2
|
|
412
|
+
(argument-validation convention). Valid values are a no-op.
|
|
413
|
+
"""
|
|
414
|
+
from sourcecode.format_contract import (
|
|
415
|
+
FORMAT_ERROR_EXIT_CODE,
|
|
416
|
+
format_error_context,
|
|
417
|
+
is_valid_format,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
if is_valid_format(command, fmt):
|
|
421
|
+
return
|
|
422
|
+
ctx = format_error_context(command, fmt)
|
|
423
|
+
message = str(ctx.pop("message"))
|
|
424
|
+
_emit_error_json(INVALID_INPUT_CODE, message, **ctx)
|
|
425
|
+
raise typer.Exit(code=FORMAT_ERROR_EXIT_CODE)
|
|
426
|
+
|
|
427
|
+
|
|
404
428
|
def _safe_write_file(path: "Path", content: str) -> None:
|
|
405
429
|
"""Write content to path, emitting a clean JSON error on I/O failure."""
|
|
406
430
|
try:
|
|
@@ -631,7 +655,8 @@ def _active_flags(
|
|
|
631
655
|
if fmt != "json": flags.append("--format")
|
|
632
656
|
return flags
|
|
633
657
|
|
|
634
|
-
|
|
658
|
+
# Per-command output-format contracts now live in sourcecode.format_contract
|
|
659
|
+
# (validated via _enforce_format). No module-level FORMAT_CHOICES here.
|
|
635
660
|
GRAPH_DETAIL_CHOICES = ["high", "medium", "full"]
|
|
636
661
|
GRAPH_EDGE_CHOICES = {"imports", "calls", "contains", "extends"}
|
|
637
662
|
DOCS_DEPTH_CHOICES = ["module", "symbols", "full"]
|
|
@@ -1138,17 +1163,7 @@ def main(
|
|
|
1138
1163
|
)
|
|
1139
1164
|
|
|
1140
1165
|
# Validate format choices
|
|
1141
|
-
|
|
1142
|
-
_emit_error_json(
|
|
1143
|
-
INVALID_INPUT_CODE,
|
|
1144
|
-
f"Invalid value '{format}' for --format. Valid values: {', '.join(FORMAT_CHOICES)}.",
|
|
1145
|
-
flag="--format",
|
|
1146
|
-
value=format,
|
|
1147
|
-
valid_values=list(FORMAT_CHOICES),
|
|
1148
|
-
hint="Choose one of the supported --format values.",
|
|
1149
|
-
expected=f"One of: {', '.join(FORMAT_CHOICES)}",
|
|
1150
|
-
)
|
|
1151
|
-
raise typer.Exit(code=2) # FIX-P2-7: arg validation → exit 2
|
|
1166
|
+
_enforce_format("main", format)
|
|
1152
1167
|
if graph_detail not in GRAPH_DETAIL_CHOICES:
|
|
1153
1168
|
_emit_error_json(
|
|
1154
1169
|
INVALID_INPUT_CODE,
|
|
@@ -2834,19 +2849,9 @@ def prepare_context_cmd(
|
|
|
2834
2849
|
# Validate --format: only "json" and "github-comment" are valid for prepare-context.
|
|
2835
2850
|
# "yaml" is intentionally NOT supported here (use main command for yaml output).
|
|
2836
2851
|
# Invalid values must error loudly — silently falling through to JSON is a lie.
|
|
2837
|
-
|
|
2838
|
-
if format is not None
|
|
2839
|
-
|
|
2840
|
-
INVALID_INPUT_CODE,
|
|
2841
|
-
f"invalid value '{format}' for --format. "
|
|
2842
|
-
f"Valid options: {', '.join(_PC_FORMAT_CHOICES)}.",
|
|
2843
|
-
flag="--format",
|
|
2844
|
-
value=format,
|
|
2845
|
-
valid_values=list(_PC_FORMAT_CHOICES),
|
|
2846
|
-
hint="Choose one of the supported prepare-context output formats.",
|
|
2847
|
-
expected=f"One of: {', '.join(_PC_FORMAT_CHOICES)}",
|
|
2848
|
-
)
|
|
2849
|
-
raise typer.Exit(code=2)
|
|
2852
|
+
# None means "use default" (json); a concrete value is validated against the contract.
|
|
2853
|
+
if format is not None:
|
|
2854
|
+
_enforce_format("prepare-context", format)
|
|
2850
2855
|
# github-comment only renders for review-pr; warn and normalize for other tasks.
|
|
2851
2856
|
if format == "github-comment" and task != "review-pr":
|
|
2852
2857
|
typer.echo(
|
|
@@ -3479,14 +3484,7 @@ def repo_ir_cmd(
|
|
|
3479
3484
|
|
|
3480
3485
|
from sourcecode.repository_ir import apply_ir_size_limits, build_repo_ir, find_java_files
|
|
3481
3486
|
|
|
3482
|
-
|
|
3483
|
-
_emit_error_json(
|
|
3484
|
-
INVALID_INPUT_CODE,
|
|
3485
|
-
f"Invalid format '{format}'.",
|
|
3486
|
-
hint="Valid values: json, yaml.",
|
|
3487
|
-
expected="json | yaml",
|
|
3488
|
-
)
|
|
3489
|
-
raise typer.Exit(code=1)
|
|
3487
|
+
_enforce_format("repo-ir", format)
|
|
3490
3488
|
|
|
3491
3489
|
root = path.resolve()
|
|
3492
3490
|
if not root.is_dir():
|
|
@@ -3712,14 +3710,7 @@ def impact_cmd(
|
|
|
3712
3710
|
from sourcecode.license import require_repo_or_pro as _require_repo_or_pro
|
|
3713
3711
|
_require_repo_or_pro(str(path.resolve()), "impact")
|
|
3714
3712
|
|
|
3715
|
-
|
|
3716
|
-
_emit_error_json(
|
|
3717
|
-
INVALID_INPUT_CODE,
|
|
3718
|
-
f"Invalid format '{format}'.",
|
|
3719
|
-
hint="format must be: json or yaml.",
|
|
3720
|
-
expected="json | yaml",
|
|
3721
|
-
)
|
|
3722
|
-
raise typer.Exit(code=1)
|
|
3713
|
+
_enforce_format("impact", format)
|
|
3723
3714
|
|
|
3724
3715
|
from sourcecode.repository_ir import (
|
|
3725
3716
|
build_repo_ir, find_java_files, compute_blast_radius,
|
|
@@ -3875,14 +3866,7 @@ def endpoints_cmd(
|
|
|
3875
3866
|
sourcecode endpoints . --controller LiquidacionJornada
|
|
3876
3867
|
sourcecode endpoints . --limit 10
|
|
3877
3868
|
"""
|
|
3878
|
-
|
|
3879
|
-
_emit_error_json(
|
|
3880
|
-
INVALID_INPUT_CODE,
|
|
3881
|
-
f"Invalid format '{format}'.",
|
|
3882
|
-
hint="format must be: json or yaml.",
|
|
3883
|
-
expected="json | yaml",
|
|
3884
|
-
)
|
|
3885
|
-
raise typer.Exit(code=1)
|
|
3869
|
+
_enforce_format("endpoints", format)
|
|
3886
3870
|
|
|
3887
3871
|
target = path.resolve()
|
|
3888
3872
|
if not target.exists() or not target.is_dir():
|
|
@@ -3933,6 +3917,105 @@ def endpoints_cmd(
|
|
|
3933
3917
|
_nudge()
|
|
3934
3918
|
|
|
3935
3919
|
|
|
3920
|
+
@app.command("validation")
|
|
3921
|
+
def validation_cmd(
|
|
3922
|
+
path: Path = typer.Argument(
|
|
3923
|
+
Path("."),
|
|
3924
|
+
help="Repository path to scan for request-body validation (default: current directory)",
|
|
3925
|
+
),
|
|
3926
|
+
output_path: Optional[Path] = typer.Option(
|
|
3927
|
+
None, "--output", "-o",
|
|
3928
|
+
help="Write output to a file instead of stdout.",
|
|
3929
|
+
),
|
|
3930
|
+
format: str = typer.Option(
|
|
3931
|
+
"json",
|
|
3932
|
+
"--format",
|
|
3933
|
+
"-f",
|
|
3934
|
+
help="Output format: json (default) or yaml.",
|
|
3935
|
+
show_default=True,
|
|
3936
|
+
),
|
|
3937
|
+
copy: bool = typer.Option(
|
|
3938
|
+
False,
|
|
3939
|
+
"--copy",
|
|
3940
|
+
"-c",
|
|
3941
|
+
help="Copy output to system clipboard after a successful run.",
|
|
3942
|
+
),
|
|
3943
|
+
path_prefix: Optional[str] = typer.Option(
|
|
3944
|
+
None, "--path-prefix", "-p",
|
|
3945
|
+
help="Filter endpoints whose URL path starts with this prefix.",
|
|
3946
|
+
),
|
|
3947
|
+
gaps_only: bool = typer.Option(
|
|
3948
|
+
False, "--gaps-only",
|
|
3949
|
+
help="Report only endpoints/fields with no declared validation (the gaps section).",
|
|
3950
|
+
),
|
|
3951
|
+
) -> None:
|
|
3952
|
+
"""Map request-body validation per endpoint (constraints + custom validators).
|
|
3953
|
+
|
|
3954
|
+
\b
|
|
3955
|
+
Aggregates two sources of bean-validation truth so an agent knows exactly
|
|
3956
|
+
what a request body must satisfy before touching it:
|
|
3957
|
+
* declarative constraints on the DTOs (@Pattern/@Size/@NotNull, min/max,
|
|
3958
|
+
enum), recovered from the OpenAPI spec even when the DTOs are generated
|
|
3959
|
+
under target/generated-sources (not scanned);
|
|
3960
|
+
* hand-written custom validators (@Constraint + ConstraintValidator, e.g.
|
|
3961
|
+
PetAgeValidator), linked to fields via x-field-extra-annotation.
|
|
3962
|
+
|
|
3963
|
+
\b
|
|
3964
|
+
Output (JSON): per-endpoint validatedFields with their rules + custom
|
|
3965
|
+
validators, the discovered custom-validator catalog, and the set of body
|
|
3966
|
+
endpoints with no declared validation (gaps).
|
|
3967
|
+
|
|
3968
|
+
\b
|
|
3969
|
+
Examples:
|
|
3970
|
+
sourcecode validation .
|
|
3971
|
+
sourcecode validation . --gaps-only
|
|
3972
|
+
sourcecode validation . --path-prefix /owners
|
|
3973
|
+
sourcecode validation . --format yaml
|
|
3974
|
+
"""
|
|
3975
|
+
_enforce_format("validation", format)
|
|
3976
|
+
|
|
3977
|
+
target = path.resolve()
|
|
3978
|
+
if not target.exists() or not target.is_dir():
|
|
3979
|
+
_emit_error_json(
|
|
3980
|
+
INVALID_INPUT_CODE,
|
|
3981
|
+
f"'{target}' is not a valid directory.",
|
|
3982
|
+
path=str(target),
|
|
3983
|
+
hint="Pass an existing repository directory.",
|
|
3984
|
+
expected="A directory path.",
|
|
3985
|
+
)
|
|
3986
|
+
raise typer.Exit(code=1)
|
|
3987
|
+
|
|
3988
|
+
from sourcecode.validation_surface import build_validation_surface
|
|
3989
|
+
data = build_validation_surface(target)
|
|
3990
|
+
|
|
3991
|
+
if path_prefix:
|
|
3992
|
+
data["endpoints"] = [
|
|
3993
|
+
e for e in data.get("endpoints", [])
|
|
3994
|
+
if str(e.get("path", "")).startswith(path_prefix)
|
|
3995
|
+
]
|
|
3996
|
+
data["gaps"] = [
|
|
3997
|
+
g for g in data.get("gaps", [])
|
|
3998
|
+
if str(g.get("path", "")).startswith(path_prefix)
|
|
3999
|
+
]
|
|
4000
|
+
if gaps_only:
|
|
4001
|
+
data = {
|
|
4002
|
+
"gaps": data.get("gaps", []),
|
|
4003
|
+
"summary": data.get("summary", {}),
|
|
4004
|
+
}
|
|
4005
|
+
|
|
4006
|
+
output = _serialize_dict(data, format)
|
|
4007
|
+
_summary = data.get("summary", {})
|
|
4008
|
+
_emit_command_output(
|
|
4009
|
+
output, output_path, copy,
|
|
4010
|
+
success_msg=f"Validation surface written to {output_path} "
|
|
4011
|
+
f"({_summary.get('endpoints_with_body', 0)} body endpoints, "
|
|
4012
|
+
f"{_summary.get('gaps', 0)} gaps)",
|
|
4013
|
+
)
|
|
4014
|
+
|
|
4015
|
+
from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
|
|
4016
|
+
_nudge()
|
|
4017
|
+
|
|
4018
|
+
|
|
3936
4019
|
# ── Spring Semantic Audit ─────────────────────────────────────────────────────
|
|
3937
4020
|
|
|
3938
4021
|
|
|
@@ -4116,14 +4199,7 @@ def spring_audit_cmd(
|
|
|
4116
4199
|
)
|
|
4117
4200
|
raise typer.Exit(code=1)
|
|
4118
4201
|
|
|
4119
|
-
|
|
4120
|
-
_emit_error_json(
|
|
4121
|
-
INVALID_INPUT_CODE,
|
|
4122
|
-
f"Invalid format '{format}'.",
|
|
4123
|
-
hint="format must be one of: json, yaml, github-comment.",
|
|
4124
|
-
expected="json | yaml | github-comment",
|
|
4125
|
-
)
|
|
4126
|
-
raise typer.Exit(code=1)
|
|
4202
|
+
_enforce_format("spring-audit", format)
|
|
4127
4203
|
|
|
4128
4204
|
_file_limitations: list[str] = []
|
|
4129
4205
|
file_list = find_java_files(target, limitations=_file_limitations)
|
|
@@ -4274,14 +4350,7 @@ def migrate_check_cmd(
|
|
|
4274
4350
|
)
|
|
4275
4351
|
raise typer.Exit(code=1)
|
|
4276
4352
|
|
|
4277
|
-
|
|
4278
|
-
_emit_error_json(
|
|
4279
|
-
INVALID_INPUT_CODE,
|
|
4280
|
-
f"Invalid format '{format}'.",
|
|
4281
|
-
hint="format must be one of: json, text.",
|
|
4282
|
-
expected="json | text",
|
|
4283
|
-
)
|
|
4284
|
-
raise typer.Exit(code=1)
|
|
4353
|
+
_enforce_format("migrate-check", format)
|
|
4285
4354
|
|
|
4286
4355
|
if min_severity not in ("critical", "high", "medium", "low"):
|
|
4287
4356
|
_emit_error_json(
|
|
@@ -4426,14 +4495,7 @@ def impact_chain_cmd(
|
|
|
4426
4495
|
)
|
|
4427
4496
|
raise typer.Exit(code=1)
|
|
4428
4497
|
|
|
4429
|
-
|
|
4430
|
-
_emit_error_json(
|
|
4431
|
-
INVALID_INPUT_CODE,
|
|
4432
|
-
f"Invalid format '{format}'.",
|
|
4433
|
-
hint="format must be: json or yaml.",
|
|
4434
|
-
expected="json | yaml",
|
|
4435
|
-
)
|
|
4436
|
-
raise typer.Exit(code=1)
|
|
4498
|
+
_enforce_format("impact-chain", format)
|
|
4437
4499
|
|
|
4438
4500
|
file_list = find_java_files(target)
|
|
4439
4501
|
if not file_list:
|
|
@@ -4567,14 +4629,7 @@ def pr_impact_cmd(
|
|
|
4567
4629
|
)
|
|
4568
4630
|
raise typer.Exit(code=1)
|
|
4569
4631
|
|
|
4570
|
-
|
|
4571
|
-
_emit_error_json(
|
|
4572
|
-
INVALID_INPUT_CODE,
|
|
4573
|
-
f"Invalid format '{format}'.",
|
|
4574
|
-
hint="format must be: text or json.",
|
|
4575
|
-
expected="text | json",
|
|
4576
|
-
)
|
|
4577
|
-
raise typer.Exit(code=1)
|
|
4632
|
+
_enforce_format("pr-impact", format)
|
|
4578
4633
|
|
|
4579
4634
|
# Read changed-files list
|
|
4580
4635
|
changed_files = [
|
|
@@ -4699,14 +4754,7 @@ def explain_cmd(
|
|
|
4699
4754
|
)
|
|
4700
4755
|
raise typer.Exit(code=1)
|
|
4701
4756
|
|
|
4702
|
-
|
|
4703
|
-
_emit_error_json(
|
|
4704
|
-
INVALID_INPUT_CODE,
|
|
4705
|
-
f"Invalid format '{format}'.",
|
|
4706
|
-
hint="format must be: text or json.",
|
|
4707
|
-
expected="text | json",
|
|
4708
|
-
)
|
|
4709
|
-
raise typer.Exit(code=1)
|
|
4757
|
+
_enforce_format("explain", format)
|
|
4710
4758
|
|
|
4711
4759
|
file_list = find_java_files(target)
|
|
4712
4760
|
if not file_list:
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Single source of truth for per-command output-format contracts.
|
|
2
|
+
|
|
3
|
+
Every CLI command that emits machine-consumable output validates its
|
|
4
|
+
``--format`` option through this registry so that:
|
|
5
|
+
|
|
6
|
+
* the set of allowed formats for each command lives in exactly one place,
|
|
7
|
+
* ``-f json`` is a strict contract on every command (pure JSON to stdout),
|
|
8
|
+
* invalid-format errors share an identical envelope shape and exit code.
|
|
9
|
+
|
|
10
|
+
The registry value is an *ordered* tuple; element ``0`` is the command's
|
|
11
|
+
default and matches its Typer option default. Defaults are intentionally NOT
|
|
12
|
+
changed when centralizing — ``explain`` and ``pr-impact`` keep their
|
|
13
|
+
human-facing ``text`` default — to avoid breaking existing scripts. The strict
|
|
14
|
+
guarantee is on ``-f json``, which every command supports.
|
|
15
|
+
|
|
16
|
+
Exit-code policy: an invalid ``--format`` is an argument-validation error and
|
|
17
|
+
exits with code ``2`` for every command (matching the documented
|
|
18
|
+
``arg validation -> exit 2`` convention used by the root command).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
# Command name (as registered with ``@app.command``, or "main" for the root
|
|
24
|
+
# command) -> ordered tuple of allowed formats. Element 0 is the default.
|
|
25
|
+
FORMAT_REGISTRY: "dict[str, tuple[str, ...]]" = {
|
|
26
|
+
"main": ("json", "yaml"),
|
|
27
|
+
"repo-ir": ("json", "yaml"),
|
|
28
|
+
"impact": ("json", "yaml"),
|
|
29
|
+
"endpoints": ("json", "yaml"),
|
|
30
|
+
"validation": ("json", "yaml"),
|
|
31
|
+
"impact-chain": ("json", "yaml"),
|
|
32
|
+
"pr-impact": ("text", "json"),
|
|
33
|
+
"migrate-check": ("json", "text"),
|
|
34
|
+
"spring-audit": ("json", "yaml", "github-comment"),
|
|
35
|
+
"explain": ("text", "json"),
|
|
36
|
+
"prepare-context": ("json", "github-comment"),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Invalid --format is an argument-validation error.
|
|
40
|
+
FORMAT_ERROR_EXIT_CODE = 2
|
|
41
|
+
|
|
42
|
+
# The strict machine-readable format every command must support.
|
|
43
|
+
STRICT_FORMAT = "json"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def allowed_formats(command: str) -> "tuple[str, ...]":
|
|
47
|
+
"""Return the ordered tuple of allowed formats for ``command``.
|
|
48
|
+
|
|
49
|
+
Raises ``KeyError`` if the command has no registered contract — a
|
|
50
|
+
programming error, surfaced loudly rather than silently allowing anything.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
return FORMAT_REGISTRY[command]
|
|
54
|
+
except KeyError as exc:
|
|
55
|
+
raise KeyError(
|
|
56
|
+
f"No format contract registered for command '{command}'. "
|
|
57
|
+
f"Add it to FORMAT_REGISTRY in sourcecode/format_contract.py."
|
|
58
|
+
) from exc
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def default_format(command: str) -> str:
|
|
62
|
+
"""Return the default format for ``command`` (registry element 0)."""
|
|
63
|
+
return allowed_formats(command)[0]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def is_valid_format(command: str, fmt: str) -> bool:
|
|
67
|
+
"""True iff ``fmt`` is allowed for ``command``."""
|
|
68
|
+
return fmt in FORMAT_REGISTRY.get(command, ())
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def format_error_context(command: str, fmt: str) -> "dict[str, object]":
|
|
72
|
+
"""Build the homogeneous error-envelope fields for an invalid ``--format``.
|
|
73
|
+
|
|
74
|
+
Returns a dict whose ``message`` key is the human message and whose
|
|
75
|
+
remaining keys are passed verbatim as the error-envelope context, so every
|
|
76
|
+
command produces an identically shaped ``--format`` error.
|
|
77
|
+
"""
|
|
78
|
+
allowed = list(allowed_formats(command))
|
|
79
|
+
joined = ", ".join(allowed)
|
|
80
|
+
return {
|
|
81
|
+
"message": f"Invalid value '{fmt}' for --format. Valid values: {joined}.",
|
|
82
|
+
"flag": "--format",
|
|
83
|
+
"value": fmt,
|
|
84
|
+
"valid_values": allowed,
|
|
85
|
+
"hint": "Choose one of the supported --format values.",
|
|
86
|
+
"expected": f"One of: {joined}",
|
|
87
|
+
}
|
|
@@ -864,6 +864,31 @@ Returns: endpoints list with method, path, controller, handler fields;
|
|
|
864
864
|
"unknown" (no security signals detected).
|
|
865
865
|
Supports Spring MVC (@GetMapping etc.) and JAX-RS (@GET/@POST etc.).
|
|
866
866
|
repo_path: absolute path to the Java repository (default: current working directory).
|
|
867
|
+
"""
|
|
868
|
+
|
|
869
|
+
_GET_VALIDATION_DOC = """\
|
|
870
|
+
Request-body validation surface per endpoint. JAVA/SPRING ONLY.
|
|
871
|
+
|
|
872
|
+
Do NOT call this on non-Java repositories — it will return empty results.
|
|
873
|
+
|
|
874
|
+
Combines two sources of bean-validation truth so you know what a request body
|
|
875
|
+
must satisfy before generating a payload, a test, or reasoning about a 400:
|
|
876
|
+
* declarative constraints on the DTOs (@Pattern/@Size/@NotNull, minimum/maximum,
|
|
877
|
+
enum) — recovered from the OpenAPI spec even when DTOs are generated under
|
|
878
|
+
target/generated-sources (not scanned);
|
|
879
|
+
* hand-written custom validators (@Constraint + ConstraintValidator, e.g.
|
|
880
|
+
PetAgeValidator), linked to fields via x-field-extra-annotation.
|
|
881
|
+
|
|
882
|
+
Maps to: sourcecode validation <repo_path>
|
|
883
|
+
Returns: endpoints[] (method, path, controller, handler, schema, validatedFields[
|
|
884
|
+
{name, rules[{kind,value}], customValidators[{annotation,validators,message,resolved}]}]),
|
|
885
|
+
custom_validators[] (catalog: annotation, validators, message, validatedTypes, targets),
|
|
886
|
+
gaps[] (POST/PUT/PATCH endpoints with no declared validation),
|
|
887
|
+
summary, openapi_spec.
|
|
888
|
+
An unresolved custom annotation (referenced in the spec, no validator in source)
|
|
889
|
+
is reported with resolved=false.
|
|
890
|
+
repo_path: absolute path to the Java repository (default: current working directory).
|
|
891
|
+
gaps_only: when true, return only the gaps section (endpoints lacking validation).
|
|
867
892
|
"""
|
|
868
893
|
|
|
869
894
|
_CACHE_STATUS_DOC = """\
|
|
@@ -1083,6 +1108,27 @@ repo_path: absolute path to the repository (default: current working directory).
|
|
|
1083
1108
|
docstring_override=_GET_ENDPOINTS_DOC,
|
|
1084
1109
|
),
|
|
1085
1110
|
|
|
1111
|
+
# --- get_validation: clean alias replacing raw canonical (6 CLI params) ---
|
|
1112
|
+
_alias_spec(
|
|
1113
|
+
"get_validation",
|
|
1114
|
+
"Request-body validation surface per endpoint (constraints + custom validators). JAVA/SPRING ONLY.",
|
|
1115
|
+
("validation",),
|
|
1116
|
+
(
|
|
1117
|
+
ToolParamSpec("repo_path", "argument", str, required=False, default=".", is_path=True),
|
|
1118
|
+
ToolParamSpec("gaps_only", "option", bool, required=False, default=False,
|
|
1119
|
+
option_names=("--gaps-only",), is_flag=True,
|
|
1120
|
+
help="Return only endpoints/fields lacking validation."),
|
|
1121
|
+
),
|
|
1122
|
+
lambda inputs: (
|
|
1123
|
+
["validation", str(inputs.get("repo_path", "."))]
|
|
1124
|
+
+ (["--gaps-only"] if bool(inputs.get("gaps_only")) else [])
|
|
1125
|
+
),
|
|
1126
|
+
supported_targets=("repo_path",),
|
|
1127
|
+
unsupported_targets=("file_path",),
|
|
1128
|
+
validator=validate_repo_path,
|
|
1129
|
+
docstring_override=_GET_VALIDATION_DOC,
|
|
1130
|
+
),
|
|
1131
|
+
|
|
1086
1132
|
# --- cache management: curated aliases stripping CLI noise params ---
|
|
1087
1133
|
_alias_spec(
|
|
1088
1134
|
"cache_status",
|
|
@@ -1214,6 +1260,7 @@ _MCP_HIDDEN_CANONICAL_TOOLS: frozenset[str] = frozenset({
|
|
|
1214
1260
|
"modernize", # duplicate of modernize_context
|
|
1215
1261
|
# Raw CLI tools with output-format/noise params — clean alias with only repo_path exists
|
|
1216
1262
|
"endpoints", # 7 CLI params (output_path/format/copy/etc.); use get_endpoints
|
|
1263
|
+
"validation", # 6 CLI params (output_path/format/copy/path_prefix/gaps_only); use get_validation
|
|
1217
1264
|
"cache_status", # path + json_output flag; curated alias strips json_output, renames path→repo_path
|
|
1218
1265
|
"cache_warm", # path + compact/agent output flags; curated alias keeps only repo_path
|
|
1219
1266
|
"cache_clear", # path + yes/all_ destructive flags; curated alias keeps repo_path + include_ris only
|