sourcecode 1.31.6__tar.gz → 1.31.8__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.31.6 → sourcecode-1.31.8}/PKG-INFO +3 -3
- {sourcecode-1.31.6 → sourcecode-1.31.8}/README.md +2 -2
- {sourcecode-1.31.6 → sourcecode-1.31.8}/pyproject.toml +1 -1
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/architecture_analyzer.py +1 -1
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/cli.py +56 -1
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/prepare_context.py +35 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/repository_ir.py +193 -7
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/runtime_classifier.py +79 -1
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/serializer.py +58 -0
- sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-0911b79e.json +0 -3825
- sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-37df4554.json +0 -266
- sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-624321f3.json +0 -12401
- sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-776b4676.json +0 -386
- sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-9770fba7.json +0 -373
- sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-c4e3c102.json +0 -266
- sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-e8bc5fb4.json +0 -122
- sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-e9801942.json +0 -219
- sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-ee60e0cd.json +0 -318
- sourcecode-1.31.6/.sourcecode-cache/snapshot-565fabf-fdd9d3f7.json +0 -552
- {sourcecode-1.31.6 → sourcecode-1.31.8}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/.continue-here.md +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/.gitignore +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/.ruff.toml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/CHANGELOG.md +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/CONTRIBUTING.md +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/LICENSE +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/SECURITY.md +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/docs/privacy.md +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/docs/schema.md +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/raw +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/run_cli.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/__init__.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/conftest.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/latin1_sample.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/latin1_sample_iso.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/lcov.info +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_architecture_summary.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_ast_extractor.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_block1_reliability.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_block2_coverage.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_block5_quality.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_broadleaf_fixes.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_bug_fixes_v1302.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_bug_fixes_v1312.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_bug_fixes_v1313.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_bug_fixes_v16.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_bug_fixes_v2.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_classifier.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_cli.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_context_scorer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_contract_pipeline.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_coverage_parser.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_cross_consistency.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_dependency_schema.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_python.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_detectors_base.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_encoding_regression.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_graph_schema.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_detection.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_docs.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_lqn.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_metrics.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_multistack.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_semantics.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_integration_universal.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_java_spring_integration.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_mcp_runner.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_mcp_serve.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_mcp_tools.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_output_ux.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_packaging.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_pipeline_integrity.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_real_projects.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_redactor.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_repository_ir.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_scanner.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_schema.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_schema_normalization.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_scoring_calibration.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_semantic_schema.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_signal_hierarchy.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_summarizer.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_surface_honesty.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_task_differentiation.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_telemetry.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_v131_improvements.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_v1_10_regressions.py +0 -0
- {sourcecode-1.31.6 → sourcecode-1.31.8}/tests/test_workspace_analyzer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.31.
|
|
3
|
+
Version: 1.31.8
|
|
4
4
|
Summary: Deterministic codebase context for AI coding agents
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -225,7 +225,7 @@ Description-Content-Type: text/markdown
|
|
|
225
225
|
|
|
226
226
|
**Deterministic, behavior-aware codebase context for AI agents and PR review.**
|
|
227
227
|
|
|
228
|
-

|
|
229
229
|

|
|
230
230
|
|
|
231
231
|
---
|
|
@@ -261,7 +261,7 @@ pipx install sourcecode
|
|
|
261
261
|
|
|
262
262
|
```bash
|
|
263
263
|
sourcecode version
|
|
264
|
-
# sourcecode 1.31.
|
|
264
|
+
# sourcecode 1.31.8
|
|
265
265
|
```
|
|
266
266
|
|
|
267
267
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Deterministic, behavior-aware codebase context for AI agents and PR review.**
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -38,7 +38,7 @@ pipx install sourcecode
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
sourcecode version
|
|
41
|
-
# sourcecode 1.31.
|
|
41
|
+
# sourcecode 1.31.8
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
---
|
|
@@ -330,7 +330,7 @@ class ArchitectureAnalyzer:
|
|
|
330
330
|
tentative = True
|
|
331
331
|
if not _hard_evidence:
|
|
332
332
|
limitations.append(
|
|
333
|
-
"Pattern
|
|
333
|
+
"Pattern inferred from directory structure; import graph not available — structural dependency direction unverified"
|
|
334
334
|
)
|
|
335
335
|
if not _hard_evidence:
|
|
336
336
|
matched_dirs = sorted({
|
|
@@ -836,6 +836,7 @@ def main(
|
|
|
836
836
|
code_notes = True
|
|
837
837
|
no_tree = True # agents never need the raw file tree
|
|
838
838
|
architecture = True # agents need full architectural signal (M4)
|
|
839
|
+
graph_modules = True # IC-003: import graph needed for architecture confidence
|
|
839
840
|
|
|
840
841
|
# ── GAP-9: Cache check — serve from .sourcecode-cache when git SHA unchanged ──
|
|
841
842
|
import hashlib as _hashlib
|
|
@@ -2495,6 +2496,10 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2495
2496
|
|
|
2496
2497
|
endpoints: list[dict] = []
|
|
2497
2498
|
seen: set[tuple] = set()
|
|
2499
|
+
# Fix 1: inheritance projection — tracks class data so controllers with ONLY
|
|
2500
|
+
# inherited endpoints (no own @RequestMapping methods) are not silently dropped.
|
|
2501
|
+
_class_info: dict[str, dict] = {}
|
|
2502
|
+
_EXTENDS_RE_LOCAL = _re.compile(r'\bextends\s+(\w+)')
|
|
2498
2503
|
|
|
2499
2504
|
from sourcecode.path_filters import is_test_path as _is_test_path
|
|
2500
2505
|
|
|
@@ -2579,14 +2584,18 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2579
2584
|
# Extract class-level base path and locate class body start
|
|
2580
2585
|
lines = content.splitlines()
|
|
2581
2586
|
|
|
2582
|
-
# First pass: find class/interface declaration line index
|
|
2587
|
+
# First pass: find class/interface declaration line index and extends clause.
|
|
2583
2588
|
class_body_start = 0
|
|
2589
|
+
extends_class_name: Optional[str] = None
|
|
2584
2590
|
for i, line in enumerate(lines):
|
|
2585
2591
|
stripped_l = line.strip()
|
|
2586
2592
|
if (not stripped_l.startswith("//") and not stripped_l.startswith("*")
|
|
2587
2593
|
and ("class " in stripped_l or "interface " in stripped_l)
|
|
2588
2594
|
and _CLASS_RE.search(stripped_l)):
|
|
2589
2595
|
class_body_start = i + 1
|
|
2596
|
+
_ext_m = _EXTENDS_RE_LOCAL.search(stripped_l)
|
|
2597
|
+
if _ext_m:
|
|
2598
|
+
extends_class_name = _ext_m.group(1)
|
|
2590
2599
|
break
|
|
2591
2600
|
|
|
2592
2601
|
# Second pass: extract class-level @RequestMapping path (only before class body).
|
|
@@ -2656,6 +2665,7 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2656
2665
|
pending_annotations: list[tuple[str, str]] = [] # (http_verb, path_suffix)
|
|
2657
2666
|
pending_filtro: Optional[str] = None
|
|
2658
2667
|
in_block_comment = False
|
|
2668
|
+
file_own_endpoints: list[tuple] = [] # (http_verb, path_suffix, handler, filtro)
|
|
2659
2669
|
|
|
2660
2670
|
for i in range(class_body_start, len(lines)):
|
|
2661
2671
|
line = lines[i]
|
|
@@ -2708,6 +2718,7 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2708
2718
|
handler = mm.group(1) if mm else ""
|
|
2709
2719
|
if handler and not handler.startswith("class"):
|
|
2710
2720
|
for http_verb, path_suffix in pending_annotations:
|
|
2721
|
+
file_own_endpoints.append((http_verb, path_suffix, handler, pending_filtro))
|
|
2711
2722
|
for _cb in class_bases: # Bug #1B: one endpoint per class prefix
|
|
2712
2723
|
full_path = (_cb + "/" + path_suffix).replace("//", "/").rstrip("/") or "/"
|
|
2713
2724
|
if not full_path.startswith("/"):
|
|
@@ -2733,6 +2744,50 @@ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
|
|
|
2733
2744
|
pending_annotations = []
|
|
2734
2745
|
pending_filtro = None
|
|
2735
2746
|
|
|
2747
|
+
# Store per-class data for inheritance projection
|
|
2748
|
+
_class_info[class_name] = {
|
|
2749
|
+
"base_paths": class_bases,
|
|
2750
|
+
"extends_class": extends_class_name,
|
|
2751
|
+
"own_endpoints": file_own_endpoints,
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
# Fix 1: Inheritance projection — controllers whose methods are 100% inherited from a
|
|
2755
|
+
# base class emit 0 endpoints above because no method-level annotations exist in their
|
|
2756
|
+
# file. Walk the inheritance chain and project parent suffixes with the child's base path.
|
|
2757
|
+
for _cls, _data in _class_info.items():
|
|
2758
|
+
if _data["own_endpoints"]:
|
|
2759
|
+
continue
|
|
2760
|
+
_bases = _data["base_paths"]
|
|
2761
|
+
if not _bases or _bases == [""]:
|
|
2762
|
+
continue
|
|
2763
|
+
_chain = _data.get("extends_class")
|
|
2764
|
+
_visited: set[str] = {_cls}
|
|
2765
|
+
while _chain and _chain not in _visited:
|
|
2766
|
+
_visited.add(_chain)
|
|
2767
|
+
_parent = _class_info.get(_chain)
|
|
2768
|
+
if not _parent:
|
|
2769
|
+
break
|
|
2770
|
+
if _parent["own_endpoints"]:
|
|
2771
|
+
for _verb, _suffix, _handler, _filtro in _parent["own_endpoints"]:
|
|
2772
|
+
for _cb in _bases:
|
|
2773
|
+
_fp = (_cb + "/" + _suffix).replace("//", "/").rstrip("/") or "/"
|
|
2774
|
+
if not _fp.startswith("/"):
|
|
2775
|
+
_fp = "/" + _fp
|
|
2776
|
+
_key = (_cls, _handler, _verb, _cb)
|
|
2777
|
+
if _key not in seen:
|
|
2778
|
+
seen.add(_key)
|
|
2779
|
+
_entry: dict[str, Any] = {
|
|
2780
|
+
"method": _verb,
|
|
2781
|
+
"path": _fp,
|
|
2782
|
+
"controller": _cls,
|
|
2783
|
+
"handler": _handler,
|
|
2784
|
+
}
|
|
2785
|
+
if _filtro:
|
|
2786
|
+
_entry["required_permission"] = _filtro
|
|
2787
|
+
endpoints.append(_entry)
|
|
2788
|
+
break
|
|
2789
|
+
_chain = _parent.get("extends_class")
|
|
2790
|
+
|
|
2736
2791
|
endpoints.sort(key=lambda e: (e.get("controller", ""), e.get("path", "")))
|
|
2737
2792
|
undocumented = sum(1 for e in endpoints if "required_permission" not in e)
|
|
2738
2793
|
|
|
@@ -1212,6 +1212,41 @@ class TaskContextBuilder:
|
|
|
1212
1212
|
symptom=symptom if task_name == "fix-bug" else None,
|
|
1213
1213
|
)
|
|
1214
1214
|
|
|
1215
|
+
# ── IC-006: fix-bug suspected_areas — recompute from ranked files + bug notes ──
|
|
1216
|
+
# relevant_files is now ranked by RankingEngine (git churn, fan_in, centrality, notes).
|
|
1217
|
+
# suspected_areas should reflect that ranking, not raw comment count.
|
|
1218
|
+
if task_name == "fix-bug" and relevant_files:
|
|
1219
|
+
_bug_kinds = {"FIXME", "BUG", "HACK", "XXX"}
|
|
1220
|
+
_bug_counts: dict[str, int] = {}
|
|
1221
|
+
for _note in cn_notes_for_ranking:
|
|
1222
|
+
if _note.kind in _bug_kinds:
|
|
1223
|
+
_bug_counts[_note.path] = _bug_counts.get(_note.path, 0) + 1
|
|
1224
|
+
|
|
1225
|
+
# Primary: top-ranked RelevantFile objects (dataclass, use .path/.reason)
|
|
1226
|
+
_ranked_paths = [rf.path for rf in relevant_files[:30]]
|
|
1227
|
+
_primary: list[str] = []
|
|
1228
|
+
for rf in relevant_files[:30]:
|
|
1229
|
+
p = rf.path
|
|
1230
|
+
if not p:
|
|
1231
|
+
continue
|
|
1232
|
+
n = _bug_counts.get(p, 0)
|
|
1233
|
+
reason_str = str(rf.reason) if rf.reason else ""
|
|
1234
|
+
note_str = f" ({n} bug annotation{'s' if n > 1 else ''})" if n else ""
|
|
1235
|
+
_primary.append(f"{p}{note_str}" + (f" — {reason_str}" if reason_str else ""))
|
|
1236
|
+
if len(_primary) >= 5:
|
|
1237
|
+
break
|
|
1238
|
+
|
|
1239
|
+
# Secondary: remaining high-note files not already in primary
|
|
1240
|
+
_ranked_set = set(_ranked_paths[:len(_primary)])
|
|
1241
|
+
_secondary = [
|
|
1242
|
+
f"{p} ({n} annotation{'s' if n > 1 else ''})"
|
|
1243
|
+
for p, n in sorted(_bug_counts.items(), key=lambda x: -x[1])
|
|
1244
|
+
if p not in _ranked_set
|
|
1245
|
+
][:3]
|
|
1246
|
+
|
|
1247
|
+
if _primary or _secondary:
|
|
1248
|
+
suspected_areas = _primary + _secondary
|
|
1249
|
+
|
|
1215
1250
|
# ── 6b. review-pr: derive PR-specific impact sections from delta analysis ──
|
|
1216
1251
|
_pr_security_impact: dict = {}
|
|
1217
1252
|
_pr_transactional_impact: dict = {}
|
|
@@ -178,8 +178,11 @@ _SPRING_OTHER: frozenset[str] = frozenset({
|
|
|
178
178
|
"@PutMapping", "@DeleteMapping", "@PatchMapping", "@Autowired",
|
|
179
179
|
"@Inject", "@Value", "@Qualifier", "@EnableWebSecurity",
|
|
180
180
|
"@SpringBootApplication", "@EnableAutoConfiguration",
|
|
181
|
+
"@EventListener", "@Async", "@Scheduled", "@Cacheable", "@CacheEvict",
|
|
181
182
|
})
|
|
182
183
|
|
|
184
|
+
_PUBLISH_EVENT_RE = re.compile(r'\.publishEvent\s*\(\s*new\s+(\w+)\s*[(\{]')
|
|
185
|
+
|
|
183
186
|
_HTTP_METHOD_MAP: dict[str, str] = {
|
|
184
187
|
"@GetMapping": "GET",
|
|
185
188
|
"@PostMapping": "POST",
|
|
@@ -787,6 +790,33 @@ def _build_relations(
|
|
|
787
790
|
evidence={"type": "structural", "value": f"member of {enclosing}"},
|
|
788
791
|
))
|
|
789
792
|
|
|
793
|
+
# Fix 5: Event flow edges — publishEvent publishers and @EventListener subscribers.
|
|
794
|
+
# listens_to_event: method with @EventListener → resolved event parameter type(s).
|
|
795
|
+
for sym in symbols:
|
|
796
|
+
if sym.type == "method" and "@EventListener" in sym.annotations:
|
|
797
|
+
for imp_fqn in sym.imports_used:
|
|
798
|
+
edges.append(RelationEdge(
|
|
799
|
+
from_symbol=sym.symbol,
|
|
800
|
+
to_symbol=imp_fqn,
|
|
801
|
+
type="listens_to_event",
|
|
802
|
+
confidence="high",
|
|
803
|
+
evidence={"type": "annotation", "value": "@EventListener"},
|
|
804
|
+
))
|
|
805
|
+
|
|
806
|
+
# publishes_event: class that calls publishEvent(new XxxEvent(...)) → event type FQN.
|
|
807
|
+
_class_syms = [s for s in symbols if s.type in ("class", "interface") and "#" not in s.symbol]
|
|
808
|
+
for m in _PUBLISH_EVENT_RE.finditer(source):
|
|
809
|
+
event_simple = m.group(1)
|
|
810
|
+
event_fqn = import_map.get(event_simple, event_simple)
|
|
811
|
+
for cls_sym in _class_syms:
|
|
812
|
+
edges.append(RelationEdge(
|
|
813
|
+
from_symbol=cls_sym.symbol,
|
|
814
|
+
to_symbol=event_fqn,
|
|
815
|
+
type="publishes_event",
|
|
816
|
+
confidence="medium",
|
|
817
|
+
evidence={"type": "method_call", "value": f"publishEvent(new {event_simple})"},
|
|
818
|
+
))
|
|
819
|
+
|
|
790
820
|
seen: set[tuple[str, str, str]] = set()
|
|
791
821
|
unique: list[RelationEdge] = []
|
|
792
822
|
for e in edges:
|
|
@@ -1190,6 +1220,8 @@ _EDGE_REASON_TEMPLATES: dict[str, str] = {
|
|
|
1190
1220
|
"contained_in": "{from_sym} is a member of {to_sym}",
|
|
1191
1221
|
"annotated_with": "{from_sym} is annotated with {to_sym}",
|
|
1192
1222
|
"mapped_to": "Route {to_sym} depends on {from_sym}",
|
|
1223
|
+
"publishes_event": "{from_sym} publishes event {to_sym}",
|
|
1224
|
+
"listens_to_event": "{from_sym} listens for event {to_sym}",
|
|
1193
1225
|
}
|
|
1194
1226
|
|
|
1195
1227
|
# Edge types to exclude from reverse impact traversal (too noisy / non-dependency semantics)
|
|
@@ -1505,6 +1537,18 @@ def _assemble(
|
|
|
1505
1537
|
by_type.setdefault(e.type, []).append(e.from_symbol)
|
|
1506
1538
|
reverse_graph_out[target] = by_type
|
|
1507
1539
|
|
|
1540
|
+
# IC-005: aggregate event flow edges already built in _build_relations
|
|
1541
|
+
_listen_edges = [e for e in sorted_rels if e.type == "listens_to_event"]
|
|
1542
|
+
_publish_edges = [e for e in sorted_rels if e.type == "publishes_event"]
|
|
1543
|
+
_spring_events: Optional[dict] = None
|
|
1544
|
+
if _listen_edges or _publish_edges:
|
|
1545
|
+
_spring_events = {
|
|
1546
|
+
"listeners": sorted({e.from_symbol for e in _listen_edges}),
|
|
1547
|
+
"publishers": sorted({e.from_symbol for e in _publish_edges}),
|
|
1548
|
+
"event_types": sorted({e.to_symbol for e in _listen_edges + _publish_edges}),
|
|
1549
|
+
"flow_count": len(_listen_edges) + len(_publish_edges),
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1508
1552
|
return {
|
|
1509
1553
|
"schema_version": "final-v1",
|
|
1510
1554
|
"graph": {
|
|
@@ -1526,13 +1570,136 @@ def _assemble(
|
|
|
1526
1570
|
},
|
|
1527
1571
|
"subsystems": subsystems,
|
|
1528
1572
|
"change_set": change_set_out,
|
|
1529
|
-
"route_surface": route_diffs
|
|
1573
|
+
"route_surface": _build_route_surface(sorted_syms, route_diffs, extends_map={
|
|
1574
|
+
e.from_symbol: e.to_symbol.split(".")[-1]
|
|
1575
|
+
for e in sorted_rels if e.type == "extends"
|
|
1576
|
+
}),
|
|
1577
|
+
**({"spring_events": _spring_events} if _spring_events else {}),
|
|
1530
1578
|
"audit": {
|
|
1531
1579
|
"dropped_fields": dropped_fields,
|
|
1532
1580
|
},
|
|
1533
1581
|
}
|
|
1534
1582
|
|
|
1535
1583
|
|
|
1584
|
+
# ---------------------------------------------------------------------------
|
|
1585
|
+
# Route surface helper (Fix 4)
|
|
1586
|
+
# ---------------------------------------------------------------------------
|
|
1587
|
+
|
|
1588
|
+
def _build_route_surface(
|
|
1589
|
+
symbols: list[SymbolRecord],
|
|
1590
|
+
route_diffs: Optional[list[dict]],
|
|
1591
|
+
extends_map: Optional[dict[str, str]] = None,
|
|
1592
|
+
) -> list[dict]:
|
|
1593
|
+
"""Return route surface with inheritance projection.
|
|
1594
|
+
|
|
1595
|
+
extends_map: child_fqn → parent_simple_name derived from RelationEdge extends edges.
|
|
1596
|
+
Projects inherited endpoints onto subclasses that have a class-level @RequestMapping
|
|
1597
|
+
prefix but zero own method-level endpoints (IC-001 fix).
|
|
1598
|
+
"""
|
|
1599
|
+
if route_diffs:
|
|
1600
|
+
return route_diffs
|
|
1601
|
+
|
|
1602
|
+
# Phase 1: build per-class metadata (prefix) and own endpoint list
|
|
1603
|
+
class_info: dict[str, dict] = {} # simple_name → {fqn, prefix, own_endpoints}
|
|
1604
|
+
for sym in symbols:
|
|
1605
|
+
if sym.type not in ("class", "interface"):
|
|
1606
|
+
continue
|
|
1607
|
+
simple = sym.symbol.split(".")[-1]
|
|
1608
|
+
prefix = ""
|
|
1609
|
+
if "@RequestMapping" in sym.annotations:
|
|
1610
|
+
args = sym.annotation_values.get("@RequestMapping", "")
|
|
1611
|
+
prefix = _parse_route_path(args)
|
|
1612
|
+
class_info[simple] = {"fqn": sym.symbol, "prefix": prefix, "own_endpoints": []}
|
|
1613
|
+
|
|
1614
|
+
routes: list[dict] = []
|
|
1615
|
+
seen: set[tuple] = set()
|
|
1616
|
+
|
|
1617
|
+
# Phase 2: emit own endpoint symbols and record them per class
|
|
1618
|
+
for sym in symbols:
|
|
1619
|
+
if sym.symbol_kind != "endpoint":
|
|
1620
|
+
continue
|
|
1621
|
+
ann_name = next((a for a in sym.annotations if a in _ENDPOINT_ANNOTATIONS), None)
|
|
1622
|
+
if not ann_name:
|
|
1623
|
+
continue
|
|
1624
|
+
cls_fqn = _enclosing_class(sym.symbol)
|
|
1625
|
+
cls_simple = cls_fqn.split(".")[-1]
|
|
1626
|
+
args = sym.annotation_values.get(ann_name, "")
|
|
1627
|
+
suffix = _parse_route_path(args)
|
|
1628
|
+
method = _parse_route_http_method(ann_name, args) or "GET"
|
|
1629
|
+
|
|
1630
|
+
if cls_simple in class_info:
|
|
1631
|
+
class_info[cls_simple]["own_endpoints"].append(
|
|
1632
|
+
(method, suffix, sym.symbol, sym.stable_id)
|
|
1633
|
+
)
|
|
1634
|
+
|
|
1635
|
+
prefix = class_info.get(cls_simple, {}).get("prefix", "")
|
|
1636
|
+
full_path = (prefix + "/" + suffix).replace("//", "/").rstrip("/") or "/"
|
|
1637
|
+
if not full_path.startswith("/"):
|
|
1638
|
+
full_path = "/" + full_path
|
|
1639
|
+
|
|
1640
|
+
key = (sym.symbol, method, prefix)
|
|
1641
|
+
if key not in seen:
|
|
1642
|
+
seen.add(key)
|
|
1643
|
+
routes.append({
|
|
1644
|
+
"symbol": sym.symbol,
|
|
1645
|
+
"controller": cls_fqn,
|
|
1646
|
+
"declaring_class": cls_fqn,
|
|
1647
|
+
"effective_class": cls_fqn,
|
|
1648
|
+
"path": full_path,
|
|
1649
|
+
"method": method,
|
|
1650
|
+
"stable_id": sym.stable_id,
|
|
1651
|
+
"inheritance_depth": 0,
|
|
1652
|
+
})
|
|
1653
|
+
|
|
1654
|
+
# Phase 3: inheritance projection — subclasses with zero own endpoints
|
|
1655
|
+
# but with a class-level @RequestMapping prefix inherit parent methods.
|
|
1656
|
+
if extends_map:
|
|
1657
|
+
fqn_to_simple: dict[str, str] = {d["fqn"]: s for s, d in class_info.items()}
|
|
1658
|
+
simple_extends: dict[str, str] = {
|
|
1659
|
+
fqn_to_simple.get(child_fqn, child_fqn.split(".")[-1]): parent_simple
|
|
1660
|
+
for child_fqn, parent_simple in extends_map.items()
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
for cls_simple, data in class_info.items():
|
|
1664
|
+
if data["own_endpoints"]:
|
|
1665
|
+
continue
|
|
1666
|
+
if not data["prefix"]:
|
|
1667
|
+
continue
|
|
1668
|
+
|
|
1669
|
+
chain = simple_extends.get(cls_simple)
|
|
1670
|
+
visited: set[str] = {cls_simple}
|
|
1671
|
+
depth = 1
|
|
1672
|
+
while chain and chain not in visited:
|
|
1673
|
+
visited.add(chain)
|
|
1674
|
+
parent = class_info.get(chain)
|
|
1675
|
+
if not parent:
|
|
1676
|
+
break
|
|
1677
|
+
if parent["own_endpoints"]:
|
|
1678
|
+
for verb, suffix, declaring_sym, stable_id in parent["own_endpoints"]:
|
|
1679
|
+
prefix = data["prefix"]
|
|
1680
|
+
full_path = (prefix + "/" + suffix).replace("//", "/").rstrip("/") or "/"
|
|
1681
|
+
if not full_path.startswith("/"):
|
|
1682
|
+
full_path = "/" + full_path
|
|
1683
|
+
key = (cls_simple, declaring_sym, verb, prefix)
|
|
1684
|
+
if key not in seen:
|
|
1685
|
+
seen.add(key)
|
|
1686
|
+
routes.append({
|
|
1687
|
+
"symbol": declaring_sym,
|
|
1688
|
+
"controller": data["fqn"],
|
|
1689
|
+
"declaring_class": parent["fqn"],
|
|
1690
|
+
"effective_class": data["fqn"],
|
|
1691
|
+
"path": full_path,
|
|
1692
|
+
"method": verb,
|
|
1693
|
+
"stable_id": stable_id,
|
|
1694
|
+
"inheritance_depth": depth,
|
|
1695
|
+
})
|
|
1696
|
+
break
|
|
1697
|
+
chain = simple_extends.get(chain)
|
|
1698
|
+
depth += 1
|
|
1699
|
+
|
|
1700
|
+
return sorted(routes, key=lambda r: (r["effective_class"], r["path"]))
|
|
1701
|
+
|
|
1702
|
+
|
|
1536
1703
|
# ---------------------------------------------------------------------------
|
|
1537
1704
|
# Public API
|
|
1538
1705
|
# ---------------------------------------------------------------------------
|
|
@@ -1675,7 +1842,17 @@ def apply_ir_size_limits(
|
|
|
1675
1842
|
"remove --summary-only to restore full graph"
|
|
1676
1843
|
),
|
|
1677
1844
|
}
|
|
1678
|
-
|
|
1845
|
+
# Fix 3: keep bounded reverse graph instead of wiping it.
|
|
1846
|
+
full_rg: dict = ir.get("reverse_graph") or {}
|
|
1847
|
+
if full_rg:
|
|
1848
|
+
_rg_sorted = sorted(
|
|
1849
|
+
full_rg.items(),
|
|
1850
|
+
key=lambda x: sum(len(v) for v in x[1].values()),
|
|
1851
|
+
reverse=True,
|
|
1852
|
+
)
|
|
1853
|
+
out["reverse_graph"] = dict(_rg_sorted[:50])
|
|
1854
|
+
else:
|
|
1855
|
+
out["reverse_graph"] = {}
|
|
1679
1856
|
out["impact"] = {
|
|
1680
1857
|
"global_score": (ir.get("impact") or {}).get("global_score", 0),
|
|
1681
1858
|
"ranked_nodes": ranked[:20],
|
|
@@ -1703,10 +1880,19 @@ def apply_ir_size_limits(
|
|
|
1703
1880
|
|
|
1704
1881
|
if kept_fqns is not None or max_edges is not None:
|
|
1705
1882
|
if kept_fqns is not None:
|
|
1706
|
-
#
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1883
|
+
# Fix 2: type-aware priority so semantic edges survive node truncation.
|
|
1884
|
+
# Annotation strings (@Service etc.) and field FQNs are never in kept_fqns,
|
|
1885
|
+
# so "both endpoints kept" drops all injects/annotated_with edges.
|
|
1886
|
+
_SEMANTIC_TYPES = frozenset({"extends", "implements", "injects",
|
|
1887
|
+
"publishes_event", "listens_to_event"})
|
|
1888
|
+
_ANNOTATION_TYPES = frozenset({"annotated_with"})
|
|
1889
|
+
tier1 = [e for e in edges if e["from"] in kept_fqns and e["type"] in _SEMANTIC_TYPES]
|
|
1890
|
+
tier2 = [e for e in edges if e["from"] in kept_fqns and e["type"] in _ANNOTATION_TYPES]
|
|
1891
|
+
tier3 = [e for e in edges
|
|
1892
|
+
if e["from"] in kept_fqns and e["to"] in kept_fqns and e["type"] == "imports"]
|
|
1893
|
+
_seen_e = {(e["from"], e["to"], e["type"]) for e in tier1 + tier2 + tier3}
|
|
1894
|
+
tier4 = [e for e in edges if (e["from"], e["to"], e["type"]) not in _seen_e]
|
|
1895
|
+
edges = tier1 + tier2 + tier3 + tier4
|
|
1710
1896
|
if max_edges is not None:
|
|
1711
1897
|
edges = edges[:max_edges]
|
|
1712
1898
|
|
|
@@ -1722,7 +1908,7 @@ def apply_ir_size_limits(
|
|
|
1722
1908
|
# Convenience: find Java files in a repo
|
|
1723
1909
|
# ---------------------------------------------------------------------------
|
|
1724
1910
|
|
|
1725
|
-
def find_java_files(root: Path, *, max_files: int =
|
|
1911
|
+
def find_java_files(root: Path, *, max_files: int = 3000) -> list[str]:
|
|
1726
1912
|
"""Return relative paths to Java files under root, excluding test dirs and vendor."""
|
|
1727
1913
|
results: list[str] = []
|
|
1728
1914
|
for p in sorted(root.rglob("*.java")):
|
|
@@ -141,6 +141,11 @@ class RuntimeClassifier:
|
|
|
141
141
|
raw.append((ws_path, self._load_pkg_json(pkg_root)))
|
|
142
142
|
|
|
143
143
|
fan_in = self._compute_fan_in(raw)
|
|
144
|
+
# IC-004: merge Maven pom.xml cross-module fan_in when no JS deps detected
|
|
145
|
+
maven_fan_in = self._compute_maven_fan_in(root, workspace_paths)
|
|
146
|
+
for ws_path, count in maven_fan_in.items():
|
|
147
|
+
fan_in[ws_path] = fan_in.get(ws_path, 0) + count
|
|
148
|
+
|
|
144
149
|
results: list[MonorepoPackageInfo] = []
|
|
145
150
|
|
|
146
151
|
for ws_path, pkg_json in raw:
|
|
@@ -280,6 +285,9 @@ class RuntimeClassifier:
|
|
|
280
285
|
scores["infrastructure_layer"] = scores.get("infrastructure_layer", 0) + fan_boost
|
|
281
286
|
|
|
282
287
|
if not scores:
|
|
288
|
+
# IC-004: Java/Maven fallback — no JS signals but module may have a pom.xml
|
|
289
|
+
if not pkg_json:
|
|
290
|
+
return self._classify_maven_module(ws_path, name, fan_in)
|
|
283
291
|
return "unknown", signals, "low"
|
|
284
292
|
|
|
285
293
|
best_role = max(scores, key=lambda r: (scores[r], _ROLE_PRIORITY.get(r, 0)))
|
|
@@ -326,6 +334,76 @@ class RuntimeClassifier:
|
|
|
326
334
|
def _criticality(self, role: str, fan_in: int) -> str:
|
|
327
335
|
if role in {"runtime_core", "plugin_host"}:
|
|
328
336
|
return "high"
|
|
329
|
-
|
|
337
|
+
# IC-004: infrastructure_layer with high fan_in = shared foundation = high criticality
|
|
338
|
+
if role == "infrastructure_layer" and fan_in >= 3:
|
|
339
|
+
return "high"
|
|
340
|
+
if role in {"backend_runtime", "frontend_runtime", "composition_layer",
|
|
341
|
+
"infrastructure_layer"} or fan_in >= 3:
|
|
330
342
|
return "medium"
|
|
331
343
|
return "low"
|
|
344
|
+
|
|
345
|
+
def _compute_maven_fan_in(self, root: Path, workspace_paths: list[str]) -> dict[str, int]:
|
|
346
|
+
"""IC-004: Compute cross-module fan_in from all pom.xml files under each workspace.
|
|
347
|
+
|
|
348
|
+
Scans all pom.xml files (top-level and sub-module) within each workspace path.
|
|
349
|
+
Counts distinct modules that declare a dependency containing another module's leaf name.
|
|
350
|
+
Uses 1 hit per source module (not per pom file) to avoid inflation.
|
|
351
|
+
"""
|
|
352
|
+
fan_in: dict[str, int] = {}
|
|
353
|
+
leaf_to_path: dict[str, str] = {ws.split("/")[-1]: ws for ws in workspace_paths}
|
|
354
|
+
|
|
355
|
+
for ws_path in workspace_paths:
|
|
356
|
+
ws_dir = root / ws_path
|
|
357
|
+
if not ws_dir.is_dir():
|
|
358
|
+
continue
|
|
359
|
+
deps_found: set[str] = set()
|
|
360
|
+
for pom in ws_dir.rglob("pom.xml"):
|
|
361
|
+
if "target/" in str(pom):
|
|
362
|
+
continue
|
|
363
|
+
try:
|
|
364
|
+
content = pom.read_text(encoding="utf-8", errors="replace")
|
|
365
|
+
except OSError:
|
|
366
|
+
continue
|
|
367
|
+
for dep_leaf, target_path in leaf_to_path.items():
|
|
368
|
+
if target_path != ws_path and dep_leaf in content:
|
|
369
|
+
deps_found.add(target_path)
|
|
370
|
+
for target_path in deps_found:
|
|
371
|
+
fan_in[target_path] = fan_in.get(target_path, 0) + 1
|
|
372
|
+
|
|
373
|
+
return fan_in
|
|
374
|
+
|
|
375
|
+
def _classify_maven_module(
|
|
376
|
+
self, ws_path: str, name: str, fan_in: int
|
|
377
|
+
) -> tuple[str, list[str], str]:
|
|
378
|
+
"""IC-004: Classify a Java/Maven module by path conventions and fan_in.
|
|
379
|
+
|
|
380
|
+
Called when no JS signals exist (no package.json). Uses path name patterns
|
|
381
|
+
and pom fan_in (cross-module dependency count) to determine role/criticality.
|
|
382
|
+
"""
|
|
383
|
+
signals: list[str] = ["maven:module"]
|
|
384
|
+
leaf = ws_path.lower().rstrip("/").split("/")[-1]
|
|
385
|
+
|
|
386
|
+
if re.search(r"\b(common|shared|util|utils|lib|base|foundation)\b", leaf):
|
|
387
|
+
signals.append("maven:shared_library")
|
|
388
|
+
return "infrastructure_layer", signals, "high" if fan_in >= 3 else "medium"
|
|
389
|
+
|
|
390
|
+
if re.search(r"\b(integration|it|test|tests|e2e)\b", leaf):
|
|
391
|
+
signals.append("maven:test_module")
|
|
392
|
+
return "test_layer", signals, "low"
|
|
393
|
+
|
|
394
|
+
if re.search(r"\b(admin|web|api|rest|controller|ui)\b", leaf):
|
|
395
|
+
signals.append("maven:web_module")
|
|
396
|
+
return "backend_runtime", signals, "medium"
|
|
397
|
+
|
|
398
|
+
if re.search(r"\b(core|framework|domain|service|profile)\b", leaf):
|
|
399
|
+
signals.append("maven:core_module")
|
|
400
|
+
return "backend_runtime", signals, "high" if fan_in >= 2 else "medium"
|
|
401
|
+
|
|
402
|
+
if fan_in >= 3:
|
|
403
|
+
signals.append(f"maven:high_fan_in({fan_in})")
|
|
404
|
+
return "infrastructure_layer", signals, "high"
|
|
405
|
+
if fan_in >= 1:
|
|
406
|
+
signals.append(f"maven:fan_in({fan_in})")
|
|
407
|
+
return "infrastructure_layer", signals, "medium"
|
|
408
|
+
|
|
409
|
+
return "unknown", signals, "low"
|
|
@@ -379,6 +379,59 @@ def _spring_boot_version(sm: "SourceMap") -> "Optional[str]":
|
|
|
379
379
|
return None
|
|
380
380
|
|
|
381
381
|
|
|
382
|
+
def _spring_event_signal(sm: "SourceMap") -> "Optional[dict[str, Any]]":
|
|
383
|
+
"""IC-005: Surface @EventListener and publishEvent from Java source files.
|
|
384
|
+
|
|
385
|
+
Scans sm.file_paths for Java files containing Spring event annotations.
|
|
386
|
+
Only runs on Java/Spring projects. Lightweight — path heuristics first,
|
|
387
|
+
then targeted content scan on candidate files only.
|
|
388
|
+
"""
|
|
389
|
+
import re as _re
|
|
390
|
+
java_paths = [p for p in sm.file_paths if p.endswith(".java") and "target/" not in p]
|
|
391
|
+
if not java_paths:
|
|
392
|
+
return None
|
|
393
|
+
_frameworks = [f.name for s in (sm.stacks or []) for f in s.frameworks]
|
|
394
|
+
if not any("Spring" in fw for fw in _frameworks):
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
analyzed_path = getattr(sm.metadata, "analyzed_path", None) if sm.metadata else None
|
|
398
|
+
root = Path(analyzed_path) if analyzed_path else None
|
|
399
|
+
|
|
400
|
+
listeners: list[str] = []
|
|
401
|
+
publishers: list[str] = []
|
|
402
|
+
event_types: set[str] = set()
|
|
403
|
+
_publish_re = _re.compile(r"\.publishEvent\s*\(\s*new\s+(\w+)")
|
|
404
|
+
|
|
405
|
+
for rel_path in java_paths:
|
|
406
|
+
if root is None:
|
|
407
|
+
break
|
|
408
|
+
try:
|
|
409
|
+
content = (root / rel_path).read_text(encoding="utf-8", errors="replace")
|
|
410
|
+
except OSError:
|
|
411
|
+
continue
|
|
412
|
+
if "@EventListener" in content:
|
|
413
|
+
cls_m = _re.search(r"class\s+(\w+)", content)
|
|
414
|
+
cls_name = cls_m.group(1) if cls_m else Path(rel_path).stem
|
|
415
|
+
if cls_name not in listeners:
|
|
416
|
+
listeners.append(cls_name)
|
|
417
|
+
for m in _publish_re.finditer(content):
|
|
418
|
+
event_types.add(m.group(1))
|
|
419
|
+
cls_m = _re.search(r"class\s+(\w+)", content)
|
|
420
|
+
cls_name = cls_m.group(1) if cls_m else Path(rel_path).stem
|
|
421
|
+
if cls_name not in publishers:
|
|
422
|
+
publishers.append(cls_name)
|
|
423
|
+
|
|
424
|
+
if not listeners and not publishers:
|
|
425
|
+
return None
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
"listeners": sorted(set(listeners))[:10],
|
|
429
|
+
"publishers": sorted(set(publishers))[:10],
|
|
430
|
+
"event_types": sorted(event_types)[:10],
|
|
431
|
+
"flow_count": len(listeners) + len(publishers),
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
|
|
382
435
|
def _spring_profiles_context(sm: "SourceMap") -> "Optional[dict[str, Any]]":
|
|
383
436
|
"""Build structured spring_profiles block: detected names + per-profile file variants."""
|
|
384
437
|
# Gather profile names from env_summary (populated by env_analyzer scanning
|
|
@@ -1949,6 +2002,11 @@ def agent_view(sm: SourceMap, *, full: bool = False) -> dict[str, Any]:
|
|
|
1949
2002
|
if _txn:
|
|
1950
2003
|
signals["transactional_boundaries"] = _txn
|
|
1951
2004
|
|
|
2005
|
+
# IC-005: Spring event flows (@EventListener / publishEvent)
|
|
2006
|
+
_evf = _spring_event_signal(sm)
|
|
2007
|
+
if _evf:
|
|
2008
|
+
signals["event_flows"] = _evf
|
|
2009
|
+
|
|
1952
2010
|
if signals:
|
|
1953
2011
|
result["signals"] = signals
|
|
1954
2012
|
|