sourcecode 1.52.0__tar.gz → 1.53.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.52.0 → sourcecode-1.53.0}/PKG-INFO +25 -4
- {sourcecode-1.52.0 → sourcecode-1.53.0}/README.md +24 -3
- {sourcecode-1.52.0 → sourcecode-1.53.0}/pyproject.toml +1 -1
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/cli.py +390 -1
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/format_contract.py +1 -0
- sourcecode-1.53.0/src/sourcecode/integration_detector.py +163 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/repository_ir.py +45 -6
- {sourcecode-1.52.0 → sourcecode-1.53.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/.gitignore +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/.ruff.toml +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/CHANGELOG.md +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/CONTRIBUTING.md +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/LICENSE +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/SECURITY.md +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/raw +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/cache.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/explain.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/license.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/ris.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/security_config.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/serializer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/version_check.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/supabase/functions/README.md +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.0}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-1.52.0 → sourcecode-1.53.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.53.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.53.0
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
---
|
|
@@ -364,9 +364,10 @@ sourcecode impact OrderService . --depth 2 # limit BFS depth
|
|
|
364
364
|
```bash
|
|
365
365
|
sourcecode endpoints /path/to/repo
|
|
366
366
|
sourcecode endpoints /path/to/repo --output endpoints.json
|
|
367
|
+
sourcecode endpoints /path/to/repo --by-controller
|
|
367
368
|
```
|
|
368
369
|
|
|
369
|
-
Extracts all Spring MVC (`@GetMapping`, `@PostMapping`, `@RequestMapping`, etc.) and JAX-RS (`@GET`, `@POST`, `@Path`) endpoint methods. Returns HTTP method, path, controller class, and handler method.
|
|
370
|
+
Extracts all Spring MVC (`@GetMapping`, `@PostMapping`, `@RequestMapping`, etc.) and JAX-RS (`@GET`, `@POST`, `@Path`) endpoint methods. Returns HTTP method, path, controller class, and handler method. Each endpoint also carries its `return_type`. `--by-controller` groups the surface per controller (`{by_controller, controller_count, total}`) for an API-surface view.
|
|
370
371
|
|
|
371
372
|
**Functional / WebFlux routing (honest limitation).** Routes registered via the functional DSL — `route().GET("/path", handler)` / `RouterFunction` / `CustomEndpoint`, common in reactive Spring apps — are **not** modeled (their real paths depend on `nest()`/group-version prefixes that can't be resolved statically). Rather than emit partial paths that would mislead, the output reports a `functional_routing` block (`files`, `route_registrations`, `modeled: false`) plus a warning. When the annotation surface is empty but functional routes exist, the warning explicitly tells you not to read it as "no endpoints". Annotation-based (MVC/JAX-RS) repos are unaffected.
|
|
372
373
|
|
|
@@ -387,6 +388,26 @@ Extracts all Spring MVC (`@GetMapping`, `@PostMapping`, `@RequestMapping`, etc.)
|
|
|
387
388
|
|
|
388
389
|
Matching endpoints then report `policy: "custom"` with `annotation`, `resourceName`, and `requiredLevel`, and are no longer counted in `no_security_signal`. Repos without the config behave exactly as before.
|
|
389
390
|
|
|
391
|
+
### `export` — architecture views for downstream tooling
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
sourcecode export /path/to/repo --by-directory # code map, path:line refs
|
|
395
|
+
sourcecode export /path/to/repo --module-graph # module→module dependencies
|
|
396
|
+
sourcecode export /path/to/repo --integrations # outbound HTTP/LDAP/JMS clients
|
|
397
|
+
sourcecode export /path/to/repo --c4 # unified architecture + manifest
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Emits **structured, tool-agnostic** codebase views as plain JSON/YAML — the kind of input an architecture-doc generator, diagram renderer, or code-search agent can consume directly instead of walking the tree file by file. Section labels map to the open [C4 model](https://c4model.com) (an open architecture notation, not a product); the schema is vendor-neutral.
|
|
401
|
+
|
|
402
|
+
| Flag | Output |
|
|
403
|
+
|------|--------|
|
|
404
|
+
| `--by-directory` | One group per source directory, each symbol with a `source_file:line` reference. |
|
|
405
|
+
| `--module-graph` | `{nodes, edges, summary}` — directories as modules, inter-module dependencies rolled up from class-level relation edges with hit counts + edge types. |
|
|
406
|
+
| `--integrations` | Outbound integrations (`RestTemplate`, `WebClient`, `@FeignClient`, `LdapTemplate`, `JmsTemplate`, ActiveMQ) with `file:line` evidence and a literal `target` URL/name when present. |
|
|
407
|
+
| `--c4` | Unified document: `c4.{context, containers, components, code}` + `api_surface` + a `manifest` with per-directory content hashes for **incremental** consumers (skip directories whose hash is unchanged). |
|
|
408
|
+
|
|
409
|
+
The section flags compose (pass several for one multi-section document); `--c4` assembles the full export on its own. URLs assembled at runtime yield `target: null` (honest absence, never a guess); containers are derived from build files (Maven/Gradle) and reported as a limitation when none are found.
|
|
410
|
+
|
|
390
411
|
### `spring-audit` — Spring semantic audit [free]
|
|
391
412
|
|
|
392
413
|
```bash
|
|
@@ -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.53.0
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
---
|
|
@@ -326,9 +326,10 @@ sourcecode impact OrderService . --depth 2 # limit BFS depth
|
|
|
326
326
|
```bash
|
|
327
327
|
sourcecode endpoints /path/to/repo
|
|
328
328
|
sourcecode endpoints /path/to/repo --output endpoints.json
|
|
329
|
+
sourcecode endpoints /path/to/repo --by-controller
|
|
329
330
|
```
|
|
330
331
|
|
|
331
|
-
Extracts all Spring MVC (`@GetMapping`, `@PostMapping`, `@RequestMapping`, etc.) and JAX-RS (`@GET`, `@POST`, `@Path`) endpoint methods. Returns HTTP method, path, controller class, and handler method.
|
|
332
|
+
Extracts all Spring MVC (`@GetMapping`, `@PostMapping`, `@RequestMapping`, etc.) and JAX-RS (`@GET`, `@POST`, `@Path`) endpoint methods. Returns HTTP method, path, controller class, and handler method. Each endpoint also carries its `return_type`. `--by-controller` groups the surface per controller (`{by_controller, controller_count, total}`) for an API-surface view.
|
|
332
333
|
|
|
333
334
|
**Functional / WebFlux routing (honest limitation).** Routes registered via the functional DSL — `route().GET("/path", handler)` / `RouterFunction` / `CustomEndpoint`, common in reactive Spring apps — are **not** modeled (their real paths depend on `nest()`/group-version prefixes that can't be resolved statically). Rather than emit partial paths that would mislead, the output reports a `functional_routing` block (`files`, `route_registrations`, `modeled: false`) plus a warning. When the annotation surface is empty but functional routes exist, the warning explicitly tells you not to read it as "no endpoints". Annotation-based (MVC/JAX-RS) repos are unaffected.
|
|
334
335
|
|
|
@@ -349,6 +350,26 @@ Extracts all Spring MVC (`@GetMapping`, `@PostMapping`, `@RequestMapping`, etc.)
|
|
|
349
350
|
|
|
350
351
|
Matching endpoints then report `policy: "custom"` with `annotation`, `resourceName`, and `requiredLevel`, and are no longer counted in `no_security_signal`. Repos without the config behave exactly as before.
|
|
351
352
|
|
|
353
|
+
### `export` — architecture views for downstream tooling
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
sourcecode export /path/to/repo --by-directory # code map, path:line refs
|
|
357
|
+
sourcecode export /path/to/repo --module-graph # module→module dependencies
|
|
358
|
+
sourcecode export /path/to/repo --integrations # outbound HTTP/LDAP/JMS clients
|
|
359
|
+
sourcecode export /path/to/repo --c4 # unified architecture + manifest
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Emits **structured, tool-agnostic** codebase views as plain JSON/YAML — the kind of input an architecture-doc generator, diagram renderer, or code-search agent can consume directly instead of walking the tree file by file. Section labels map to the open [C4 model](https://c4model.com) (an open architecture notation, not a product); the schema is vendor-neutral.
|
|
363
|
+
|
|
364
|
+
| Flag | Output |
|
|
365
|
+
|------|--------|
|
|
366
|
+
| `--by-directory` | One group per source directory, each symbol with a `source_file:line` reference. |
|
|
367
|
+
| `--module-graph` | `{nodes, edges, summary}` — directories as modules, inter-module dependencies rolled up from class-level relation edges with hit counts + edge types. |
|
|
368
|
+
| `--integrations` | Outbound integrations (`RestTemplate`, `WebClient`, `@FeignClient`, `LdapTemplate`, `JmsTemplate`, ActiveMQ) with `file:line` evidence and a literal `target` URL/name when present. |
|
|
369
|
+
| `--c4` | Unified document: `c4.{context, containers, components, code}` + `api_surface` + a `manifest` with per-directory content hashes for **incremental** consumers (skip directories whose hash is unchanged). |
|
|
370
|
+
|
|
371
|
+
The section flags compose (pass several for one multi-section document); `--c4` assembles the full export on its own. URLs assembled at runtime yield `target: null` (honest absence, never a guess); containers are derived from build files (Maven/Gradle) and reported as a limitation when none are found.
|
|
372
|
+
|
|
352
373
|
### `spring-audit` — Spring semantic audit [free]
|
|
353
374
|
|
|
354
375
|
```bash
|
|
@@ -219,7 +219,7 @@ _HELP = _build_help_text()
|
|
|
219
219
|
_SUBCOMMANDS: frozenset[str] = frozenset(
|
|
220
220
|
{
|
|
221
221
|
"telemetry", "prepare-context", "version", "config",
|
|
222
|
-
"repo-ir", "mcp", "endpoints", "impact",
|
|
222
|
+
"repo-ir", "mcp", "endpoints", "impact", "export",
|
|
223
223
|
# Enterprise workflow commands
|
|
224
224
|
"onboard", "modernize", "fix-bug", "review-pr",
|
|
225
225
|
# License / auth
|
|
@@ -3804,6 +3804,30 @@ def impact_cmd(
|
|
|
3804
3804
|
# canonical single-source-of-truth endpoint extractor.
|
|
3805
3805
|
|
|
3806
3806
|
|
|
3807
|
+
def _group_endpoints_by_controller(endpoints: "list[dict]") -> "dict":
|
|
3808
|
+
"""Group endpoints by their controller FQN into a structured API surface.
|
|
3809
|
+
|
|
3810
|
+
Returns ``{"by_controller": {fqn: [{method, path, return_type}, ...]},
|
|
3811
|
+
"controller_count": int, "total": int}``. Controllers and their routes are
|
|
3812
|
+
ordered deterministically (controllers by name, routes by path then method).
|
|
3813
|
+
"""
|
|
3814
|
+
by_ctrl: "dict[str, list[dict]]" = {}
|
|
3815
|
+
for ep in endpoints:
|
|
3816
|
+
ctrl = ep.get("controller", "") or "<unknown>"
|
|
3817
|
+
by_ctrl.setdefault(ctrl, []).append({
|
|
3818
|
+
"method": ep.get("method", ""),
|
|
3819
|
+
"path": ep.get("path", ""),
|
|
3820
|
+
"return_type": ep.get("return_type", "void"),
|
|
3821
|
+
})
|
|
3822
|
+
for ctrl in by_ctrl:
|
|
3823
|
+
by_ctrl[ctrl].sort(key=lambda r: (r["path"], r["method"]))
|
|
3824
|
+
ordered = {k: by_ctrl[k] for k in sorted(by_ctrl)}
|
|
3825
|
+
return {
|
|
3826
|
+
"by_controller": ordered,
|
|
3827
|
+
"controller_count": len(ordered),
|
|
3828
|
+
"total": len(endpoints),
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3807
3831
|
|
|
3808
3832
|
@app.command("endpoints")
|
|
3809
3833
|
def endpoints_cmd(
|
|
@@ -3844,6 +3868,10 @@ def endpoints_cmd(
|
|
|
3844
3868
|
False, "--no-cache",
|
|
3845
3869
|
help="Accepted for compatibility; this command always reads fresh source (no snapshot cache). No-op.",
|
|
3846
3870
|
),
|
|
3871
|
+
by_controller: bool = typer.Option(
|
|
3872
|
+
False, "--by-controller",
|
|
3873
|
+
help="Group endpoints by controller class (structured API surface for C4/Container synthesis).",
|
|
3874
|
+
),
|
|
3847
3875
|
) -> None:
|
|
3848
3876
|
"""Extract REST API endpoint surface from Java source files.
|
|
3849
3877
|
|
|
@@ -3929,6 +3957,11 @@ def endpoints_cmd(
|
|
|
3929
3957
|
"undocumented_before_filter": _undoc_before,
|
|
3930
3958
|
}
|
|
3931
3959
|
|
|
3960
|
+
if by_controller:
|
|
3961
|
+
_grouped = _group_endpoints_by_controller(data.get("endpoints", []))
|
|
3962
|
+
data["by_controller"] = _grouped["by_controller"]
|
|
3963
|
+
data["controller_count"] = _grouped["controller_count"]
|
|
3964
|
+
|
|
3932
3965
|
output = _serialize_dict(data, format)
|
|
3933
3966
|
|
|
3934
3967
|
_emit_command_output(output, output_path, copy,
|
|
@@ -3938,6 +3971,362 @@ def endpoints_cmd(
|
|
|
3938
3971
|
_nudge()
|
|
3939
3972
|
|
|
3940
3973
|
|
|
3974
|
+
# ── export ──────────────────────────────────────────────────────────────────
|
|
3975
|
+
|
|
3976
|
+
def _group_symbols_by_directory(nodes: "list[dict]") -> "dict":
|
|
3977
|
+
"""Group repo-ir graph nodes by source directory with path:line refs.
|
|
3978
|
+
|
|
3979
|
+
Returns ``{dir: [{symbol, kind, ref}]}`` ordered deterministically (dirs by
|
|
3980
|
+
name, symbols by ref). ``ref`` is ``source_file:line`` when the line is known,
|
|
3981
|
+
otherwise just ``source_file``. This is the per-directory, file:line-anchored
|
|
3982
|
+
code-level export — the file:line-anchored input an architecture/code-map
|
|
3983
|
+
consumer needs to proceed by file write instead of per-directory LLM reads.
|
|
3984
|
+
"""
|
|
3985
|
+
import posixpath
|
|
3986
|
+
by_dir: "dict[str, list[dict]]" = {}
|
|
3987
|
+
for n in nodes:
|
|
3988
|
+
sf = n.get("source_file") or ""
|
|
3989
|
+
if not sf:
|
|
3990
|
+
continue
|
|
3991
|
+
d = posixpath.dirname(sf) or "."
|
|
3992
|
+
ln = n.get("line")
|
|
3993
|
+
ref = f"{sf}:{ln}" if ln else sf
|
|
3994
|
+
by_dir.setdefault(d, []).append({
|
|
3995
|
+
"symbol": n.get("canonical_name") or n.get("fqn"),
|
|
3996
|
+
"kind": n.get("symbol_kind"),
|
|
3997
|
+
"ref": ref,
|
|
3998
|
+
})
|
|
3999
|
+
for d in by_dir:
|
|
4000
|
+
by_dir[d].sort(key=lambda r: r["ref"])
|
|
4001
|
+
return {k: by_dir[k] for k in sorted(by_dir)}
|
|
4002
|
+
|
|
4003
|
+
|
|
4004
|
+
def _build_module_graph(nodes: "list[dict]", edges: "list[dict]") -> "dict":
|
|
4005
|
+
"""Roll class-level relation edges up into a module→module dependency graph.
|
|
4006
|
+
|
|
4007
|
+
A *module* is the source directory of a symbol (same grouping key as
|
|
4008
|
+
``--by-directory``), giving C4 a container/component-level dependency view.
|
|
4009
|
+
Every node FQN maps to its module; each edge whose endpoints resolve to two
|
|
4010
|
+
*different* modules contributes one inter-module dependency, aggregated by
|
|
4011
|
+
``(from, to)`` with a hit count and the set of underlying edge types.
|
|
4012
|
+
|
|
4013
|
+
Edges whose endpoints are not internal nodes (e.g. imports of external
|
|
4014
|
+
library types) are skipped — only resolvable, internal module→module
|
|
4015
|
+
dependencies are reported. Returns ``{nodes, edges, summary}`` deterministically.
|
|
4016
|
+
"""
|
|
4017
|
+
import posixpath
|
|
4018
|
+
|
|
4019
|
+
fqn_to_module: "dict[str, str]" = {}
|
|
4020
|
+
module_symbols: "dict[str, int]" = {}
|
|
4021
|
+
for n in nodes:
|
|
4022
|
+
sf = n.get("source_file") or ""
|
|
4023
|
+
if not sf:
|
|
4024
|
+
continue
|
|
4025
|
+
mod = posixpath.dirname(sf) or "."
|
|
4026
|
+
fqn = n.get("fqn")
|
|
4027
|
+
if fqn:
|
|
4028
|
+
fqn_to_module[fqn] = mod
|
|
4029
|
+
module_symbols[mod] = module_symbols.get(mod, 0) + 1
|
|
4030
|
+
|
|
4031
|
+
# (from_mod, to_mod) -> {"count": int, "types": set[str]}
|
|
4032
|
+
agg: "dict[tuple[str, str], dict]" = {}
|
|
4033
|
+
for e in edges:
|
|
4034
|
+
fm = fqn_to_module.get(e.get("from"))
|
|
4035
|
+
tm = fqn_to_module.get(e.get("to"))
|
|
4036
|
+
if fm is None or tm is None or fm == tm:
|
|
4037
|
+
continue
|
|
4038
|
+
slot = agg.setdefault((fm, tm), {"count": 0, "types": set()})
|
|
4039
|
+
slot["count"] += 1
|
|
4040
|
+
et = e.get("type")
|
|
4041
|
+
if et:
|
|
4042
|
+
slot["types"].add(et)
|
|
4043
|
+
|
|
4044
|
+
graph_edges = [
|
|
4045
|
+
{
|
|
4046
|
+
"from": fm,
|
|
4047
|
+
"to": tm,
|
|
4048
|
+
"count": slot["count"],
|
|
4049
|
+
"types": sorted(slot["types"]),
|
|
4050
|
+
}
|
|
4051
|
+
for (fm, tm), slot in sorted(agg.items())
|
|
4052
|
+
]
|
|
4053
|
+
out_deg: "dict[str, int]" = {}
|
|
4054
|
+
in_deg: "dict[str, int]" = {}
|
|
4055
|
+
for ed in graph_edges:
|
|
4056
|
+
out_deg[ed["from"]] = out_deg.get(ed["from"], 0) + 1
|
|
4057
|
+
in_deg[ed["to"]] = in_deg.get(ed["to"], 0) + 1
|
|
4058
|
+
graph_nodes = [
|
|
4059
|
+
{
|
|
4060
|
+
"module": mod,
|
|
4061
|
+
"symbol_count": module_symbols[mod],
|
|
4062
|
+
"out_degree": out_deg.get(mod, 0),
|
|
4063
|
+
"in_degree": in_deg.get(mod, 0),
|
|
4064
|
+
}
|
|
4065
|
+
for mod in sorted(module_symbols)
|
|
4066
|
+
]
|
|
4067
|
+
return {
|
|
4068
|
+
"nodes": graph_nodes,
|
|
4069
|
+
"edges": graph_edges,
|
|
4070
|
+
"summary": {
|
|
4071
|
+
"module_count": len(graph_nodes),
|
|
4072
|
+
"edge_count": len(graph_edges),
|
|
4073
|
+
},
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
|
|
4077
|
+
# Build-system markers that identify a deployable/buildable unit (C4 container).
|
|
4078
|
+
_BUILD_MARKERS: "tuple[str, ...]" = (
|
|
4079
|
+
"pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle",
|
|
4080
|
+
)
|
|
4081
|
+
|
|
4082
|
+
|
|
4083
|
+
def _detect_containers(root: "Path") -> "list[dict]":
|
|
4084
|
+
"""Detect build-module roots as C4 containers (deployable/buildable units).
|
|
4085
|
+
|
|
4086
|
+
A *container* is a directory holding a recognized build file
|
|
4087
|
+
(Maven/Gradle). Detection is purely structural — no build is run. Returns
|
|
4088
|
+
``[{root, build_file}]`` (relative paths, deterministic order). Empty when no
|
|
4089
|
+
build files are found; the caller records that as a limitation rather than
|
|
4090
|
+
fabricating containers.
|
|
4091
|
+
"""
|
|
4092
|
+
import os
|
|
4093
|
+
found: "list[dict]" = []
|
|
4094
|
+
seen: "set[str]" = set()
|
|
4095
|
+
_SKIP = {".git", "node_modules", "target", "build", ".gradle", ".idea", "dist"}
|
|
4096
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
4097
|
+
dirnames[:] = [d for d in dirnames if d not in _SKIP and not d.startswith(".")]
|
|
4098
|
+
for marker in _BUILD_MARKERS:
|
|
4099
|
+
if marker in filenames:
|
|
4100
|
+
rel = os.path.relpath(dirpath, root).replace(os.sep, "/")
|
|
4101
|
+
rel = "." if rel == "." else rel
|
|
4102
|
+
if rel in seen:
|
|
4103
|
+
continue
|
|
4104
|
+
seen.add(rel)
|
|
4105
|
+
found.append({"root": rel, "build_file": marker})
|
|
4106
|
+
break
|
|
4107
|
+
found.sort(key=lambda c: c["root"])
|
|
4108
|
+
return found
|
|
4109
|
+
|
|
4110
|
+
|
|
4111
|
+
def _directory_hashes(file_list: "list[str]", root: "Path") -> "dict[str, str]":
|
|
4112
|
+
"""Content-addressed sha256 per source directory for incremental consumers.
|
|
4113
|
+
|
|
4114
|
+
Hash inputs are the directory's source files as ``(relpath, bytes)`` in
|
|
4115
|
+
sorted order, so the digest is stable across runs and changes iff a file in
|
|
4116
|
+
that directory changes. A consumer compares hashes to skip unchanged
|
|
4117
|
+
directories. Tool-agnostic: just a map ``{dir: sha256[:16]}``.
|
|
4118
|
+
"""
|
|
4119
|
+
import hashlib
|
|
4120
|
+
import posixpath
|
|
4121
|
+
by_dir: "dict[str, list[str]]" = {}
|
|
4122
|
+
for rel in file_list:
|
|
4123
|
+
d = posixpath.dirname(rel) or "."
|
|
4124
|
+
by_dir.setdefault(d, []).append(rel)
|
|
4125
|
+
out: "dict[str, str]" = {}
|
|
4126
|
+
for d in sorted(by_dir):
|
|
4127
|
+
h = hashlib.sha256()
|
|
4128
|
+
for rel in sorted(by_dir[d]):
|
|
4129
|
+
h.update(rel.encode("utf-8"))
|
|
4130
|
+
h.update(b"\0")
|
|
4131
|
+
try:
|
|
4132
|
+
h.update((root / rel).read_bytes())
|
|
4133
|
+
except OSError:
|
|
4134
|
+
h.update(b"<unreadable>")
|
|
4135
|
+
h.update(b"\0")
|
|
4136
|
+
out[d] = h.hexdigest()[:16]
|
|
4137
|
+
return out
|
|
4138
|
+
|
|
4139
|
+
|
|
4140
|
+
def _build_c4_export(
|
|
4141
|
+
root: "Path",
|
|
4142
|
+
file_list: "list[str]",
|
|
4143
|
+
nodes: "list[dict]",
|
|
4144
|
+
edges: "list[dict]",
|
|
4145
|
+
endpoints: "list[dict]",
|
|
4146
|
+
integrations: "dict",
|
|
4147
|
+
) -> "dict":
|
|
4148
|
+
"""Assemble a unified, tool-agnostic C4 architecture export + incremental manifest.
|
|
4149
|
+
|
|
4150
|
+
Maps the four already-built views onto the open C4 model (a public notation,
|
|
4151
|
+
not a product): code (L4), components (L3/L2), context external systems (L1),
|
|
4152
|
+
plus build-module containers and an interface-contract API surface. The
|
|
4153
|
+
``manifest`` carries per-directory content hashes so a downstream consumer can
|
|
4154
|
+
process incrementally. No third-party tool or format is hardcoded.
|
|
4155
|
+
"""
|
|
4156
|
+
by_directory = _group_symbols_by_directory(nodes)
|
|
4157
|
+
module_graph = _build_module_graph(nodes, edges)
|
|
4158
|
+
api_surface = _group_endpoints_by_controller(endpoints)
|
|
4159
|
+
containers = _detect_containers(root)
|
|
4160
|
+
|
|
4161
|
+
limitations: "list[str]" = []
|
|
4162
|
+
if not containers:
|
|
4163
|
+
limitations.append(
|
|
4164
|
+
"No build files (Maven/Gradle) found; containers not derived. "
|
|
4165
|
+
"Treat the repository as a single implicit container."
|
|
4166
|
+
)
|
|
4167
|
+
|
|
4168
|
+
return {
|
|
4169
|
+
"schema_version": "c4-v1",
|
|
4170
|
+
"c4": {
|
|
4171
|
+
"context": {
|
|
4172
|
+
"system": {"name": root.name, "file_count": len(file_list)},
|
|
4173
|
+
"external_systems": integrations,
|
|
4174
|
+
},
|
|
4175
|
+
"containers": containers,
|
|
4176
|
+
"components": module_graph,
|
|
4177
|
+
"code": by_directory,
|
|
4178
|
+
},
|
|
4179
|
+
"api_surface": api_surface,
|
|
4180
|
+
"manifest": {
|
|
4181
|
+
"directory_hashes": _directory_hashes(file_list, root),
|
|
4182
|
+
"generated": {
|
|
4183
|
+
"tool": "sourcecode",
|
|
4184
|
+
"schema": "c4-v1",
|
|
4185
|
+
"file_count": len(file_list),
|
|
4186
|
+
},
|
|
4187
|
+
},
|
|
4188
|
+
"limitations": limitations,
|
|
4189
|
+
}
|
|
4190
|
+
|
|
4191
|
+
|
|
4192
|
+
@app.command("export")
|
|
4193
|
+
def export_cmd(
|
|
4194
|
+
path: Path = typer.Argument(
|
|
4195
|
+
Path("."),
|
|
4196
|
+
help="Repository path to export (default: current directory)",
|
|
4197
|
+
),
|
|
4198
|
+
output_path: Optional[Path] = typer.Option(
|
|
4199
|
+
None, "--output", "-o",
|
|
4200
|
+
help="Write output to a file instead of stdout.",
|
|
4201
|
+
),
|
|
4202
|
+
format: str = typer.Option(
|
|
4203
|
+
"json", "--format", "-f",
|
|
4204
|
+
help="Output format: json (default) or yaml.",
|
|
4205
|
+
),
|
|
4206
|
+
copy: bool = typer.Option(
|
|
4207
|
+
False, "--copy", "-c",
|
|
4208
|
+
help="Copy output to system clipboard after a successful run.",
|
|
4209
|
+
),
|
|
4210
|
+
by_directory: bool = typer.Option(
|
|
4211
|
+
False, "--by-directory",
|
|
4212
|
+
help="Group symbols by source directory with path:line refs (C4 code-level export).",
|
|
4213
|
+
),
|
|
4214
|
+
module_graph: bool = typer.Option(
|
|
4215
|
+
False, "--module-graph",
|
|
4216
|
+
help="Emit a module→module dependency graph (C4 container/component level).",
|
|
4217
|
+
),
|
|
4218
|
+
integrations: bool = typer.Option(
|
|
4219
|
+
False, "--integrations",
|
|
4220
|
+
help="Detect outbound integrations (HTTP/LDAP/JMS clients) with file:line evidence.",
|
|
4221
|
+
),
|
|
4222
|
+
c4: bool = typer.Option(
|
|
4223
|
+
False, "--c4",
|
|
4224
|
+
help="Unified C4 architecture export (context/containers/components/code) "
|
|
4225
|
+
"+ per-directory incremental manifest. Vendor-neutral.",
|
|
4226
|
+
),
|
|
4227
|
+
) -> None:
|
|
4228
|
+
"""Export structured, tool-agnostic codebase views for downstream tooling.
|
|
4229
|
+
|
|
4230
|
+
Output is plain JSON/YAML that any consumer (architecture-doc generators,
|
|
4231
|
+
diagram renderers, code-search agents) can ingest. Section labels map to the
|
|
4232
|
+
open C4 model (an open architecture notation, not a product) but the schema
|
|
4233
|
+
is vendor-neutral.
|
|
4234
|
+
|
|
4235
|
+
\b
|
|
4236
|
+
--by-directory One group per source directory, each symbol carrying a
|
|
4237
|
+
path:line reference — the file:line-anchored code map that
|
|
4238
|
+
lets a consumer proceed by file write instead of per-dir reads.
|
|
4239
|
+
--module-graph Module→module dependency graph (container/component level)
|
|
4240
|
+
rolled up from class-level relation edges.
|
|
4241
|
+
--integrations Outbound integrations (RestTemplate/WebClient/Feign/LDAP/JMS)
|
|
4242
|
+
with file:line evidence — external-system dependency arrows.
|
|
4243
|
+
--c4 Unified architecture document mapped onto the open C4 model
|
|
4244
|
+
(context/containers/components/code) + an API surface and a
|
|
4245
|
+
per-directory content-hash manifest for incremental consumers.
|
|
4246
|
+
|
|
4247
|
+
The section flags compose; pass several to emit multiple sections in one
|
|
4248
|
+
document. --c4 assembles the full architecture export on its own.
|
|
4249
|
+
"""
|
|
4250
|
+
from sourcecode.repository_ir import build_repo_ir, find_java_files
|
|
4251
|
+
|
|
4252
|
+
_enforce_format("export", format)
|
|
4253
|
+
|
|
4254
|
+
root = path.resolve()
|
|
4255
|
+
if not root.is_dir():
|
|
4256
|
+
_emit_error_json(
|
|
4257
|
+
INVALID_INPUT_CODE,
|
|
4258
|
+
f"'{root}' is not a valid directory.",
|
|
4259
|
+
path=str(root),
|
|
4260
|
+
hint="Pass an existing repository directory.",
|
|
4261
|
+
expected="A directory path.",
|
|
4262
|
+
)
|
|
4263
|
+
raise typer.Exit(1)
|
|
4264
|
+
|
|
4265
|
+
if not (by_directory or module_graph or integrations or c4):
|
|
4266
|
+
_emit_error_json(
|
|
4267
|
+
INVALID_INPUT_CODE,
|
|
4268
|
+
"export requires a mode flag.",
|
|
4269
|
+
path=str(root),
|
|
4270
|
+
hint="Pass --c4 for the full architecture export, or one of "
|
|
4271
|
+
"--by-directory / --module-graph / --integrations for a section.",
|
|
4272
|
+
expected="--c4 | --by-directory | --module-graph | --integrations",
|
|
4273
|
+
)
|
|
4274
|
+
raise typer.Exit(1)
|
|
4275
|
+
|
|
4276
|
+
file_list = [
|
|
4277
|
+
f for f in find_java_files(root)
|
|
4278
|
+
if "/test/" not in f and "/tests/" not in f
|
|
4279
|
+
]
|
|
4280
|
+
|
|
4281
|
+
if c4:
|
|
4282
|
+
# Unified architecture export: assembles every section + manifest.
|
|
4283
|
+
from sourcecode.integration_detector import detect_integrations
|
|
4284
|
+
from sourcecode.repository_ir import extract_java_endpoints
|
|
4285
|
+
ir = build_repo_ir(file_list, root)
|
|
4286
|
+
graph = ir.get("graph", {})
|
|
4287
|
+
endpoints = extract_java_endpoints(root).get("endpoints", [])
|
|
4288
|
+
data = _build_c4_export(
|
|
4289
|
+
root,
|
|
4290
|
+
file_list,
|
|
4291
|
+
graph.get("nodes", []),
|
|
4292
|
+
graph.get("edges", []),
|
|
4293
|
+
endpoints,
|
|
4294
|
+
detect_integrations(file_list, root),
|
|
4295
|
+
)
|
|
4296
|
+
output = _serialize_dict(data, format)
|
|
4297
|
+
_emit_command_output(output, output_path, copy,
|
|
4298
|
+
success_msg=f"C4 architecture export written to {output_path}")
|
|
4299
|
+
from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
|
|
4300
|
+
_nudge()
|
|
4301
|
+
return
|
|
4302
|
+
|
|
4303
|
+
data: "dict" = {}
|
|
4304
|
+
# IR is only needed for the symbol/graph-derived views, not for the
|
|
4305
|
+
# source-scanned integration detector.
|
|
4306
|
+
if by_directory or module_graph:
|
|
4307
|
+
ir = build_repo_ir(file_list, root)
|
|
4308
|
+
graph = ir.get("graph", {})
|
|
4309
|
+
nodes = graph.get("nodes", [])
|
|
4310
|
+
if by_directory:
|
|
4311
|
+
grouped = _group_symbols_by_directory(nodes)
|
|
4312
|
+
data["by_directory"] = grouped
|
|
4313
|
+
data["directory_count"] = len(grouped)
|
|
4314
|
+
data["symbol_count"] = sum(len(v) for v in grouped.values())
|
|
4315
|
+
if module_graph:
|
|
4316
|
+
data["module_graph"] = _build_module_graph(nodes, graph.get("edges", []))
|
|
4317
|
+
|
|
4318
|
+
if integrations:
|
|
4319
|
+
from sourcecode.integration_detector import detect_integrations
|
|
4320
|
+
data["integrations"] = detect_integrations(file_list, root)
|
|
4321
|
+
|
|
4322
|
+
output = _serialize_dict(data, format)
|
|
4323
|
+
_emit_command_output(output, output_path, copy,
|
|
4324
|
+
success_msg=f"Export written to {output_path}")
|
|
4325
|
+
|
|
4326
|
+
from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
|
|
4327
|
+
_nudge()
|
|
4328
|
+
|
|
4329
|
+
|
|
3941
4330
|
@app.command("validation")
|
|
3942
4331
|
def validation_cmd(
|
|
3943
4332
|
path: Path = typer.Argument(
|
|
@@ -27,6 +27,7 @@ FORMAT_REGISTRY: "dict[str, tuple[str, ...]]" = {
|
|
|
27
27
|
"repo-ir": ("json", "yaml"),
|
|
28
28
|
"impact": ("json", "yaml"),
|
|
29
29
|
"endpoints": ("json", "yaml"),
|
|
30
|
+
"export": ("json", "yaml"),
|
|
30
31
|
"validation": ("json", "yaml"),
|
|
31
32
|
"impact-chain": ("json", "yaml"),
|
|
32
33
|
"pr-impact": ("text", "json"),
|