sourcecode 1.30.18__tar.gz → 1.30.20__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.30.18 → sourcecode-1.30.20}/PKG-INFO +3 -3
- {sourcecode-1.30.18 → sourcecode-1.30.20}/README.md +2 -2
- {sourcecode-1.30.18 → sourcecode-1.30.20}/pyproject.toml +1 -1
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/cli.py +77 -5
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/git_analyzer.py +7 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/prepare_context.py +112 -59
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/repository_ir.py +93 -1
- sourcecode-1.30.20/tests/test_v131_improvements.py +474 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/.continue-here.md +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/.gitignore +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/.ruff.toml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/CONTRIBUTING.md +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/LICENSE +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/SECURITY.md +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/docs/privacy.md +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/docs/schema.md +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/raw +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/run_cli.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/serializer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/__init__.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/conftest.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/latin1_sample.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/latin1_sample_iso.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/lcov.info +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_architecture_summary.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_ast_extractor.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_block1_reliability.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_block2_coverage.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_block5_quality.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_bug_fixes_v16.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_classifier.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_cli.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_context_scorer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_contract_pipeline.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_coverage_parser.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_cross_consistency.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_dependency_schema.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_detector_python.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_detectors_base.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_encoding_regression.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_graph_schema.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_integration.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_integration_detection.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_integration_docs.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_integration_lqn.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_integration_metrics.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_integration_multistack.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_integration_semantics.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_integration_universal.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_java_spring_integration.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_packaging.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_pipeline_integrity.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_real_projects.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_redactor.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_repository_ir.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_scanner.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_schema.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_schema_normalization.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_semantic_schema.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_signal_hierarchy.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_summarizer.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_surface_honesty.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_task_differentiation.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_telemetry.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_v1_10_regressions.py +0 -0
- {sourcecode-1.30.18 → sourcecode-1.30.20}/tests/test_workspace_analyzer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.30.
|
|
3
|
+
Version: 1.30.20
|
|
4
4
|
Summary: Deterministic codebase context for AI coding agents
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -221,7 +221,7 @@ Description-Content-Type: text/markdown
|
|
|
221
221
|
|
|
222
222
|
**Deterministic, behavior-aware codebase context for AI agents and PR review.**
|
|
223
223
|
|
|
224
|
-

|
|
225
225
|

|
|
226
226
|
|
|
227
227
|
---
|
|
@@ -257,7 +257,7 @@ pipx install sourcecode
|
|
|
257
257
|
|
|
258
258
|
```bash
|
|
259
259
|
sourcecode version
|
|
260
|
-
# sourcecode 1.30.
|
|
260
|
+
# sourcecode 1.30.20
|
|
261
261
|
```
|
|
262
262
|
|
|
263
263
|
---
|
|
@@ -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.30.
|
|
41
|
+
# sourcecode 1.30.20
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
---
|
|
@@ -1648,6 +1648,12 @@ def prepare_context_cmd(
|
|
|
1648
1648
|
"--format",
|
|
1649
1649
|
help="Output format: json (default) | github-comment (Markdown PR comment for review-pr task)",
|
|
1650
1650
|
),
|
|
1651
|
+
debug_perf: bool = typer.Option(
|
|
1652
|
+
False,
|
|
1653
|
+
"--debug-perf",
|
|
1654
|
+
help="Emit per-phase timing to stderr (git scan ms, symptom scoring ms, total ms)",
|
|
1655
|
+
hidden=True,
|
|
1656
|
+
),
|
|
1651
1657
|
) -> None:
|
|
1652
1658
|
"""Task-specific context for AI coding agents.
|
|
1653
1659
|
|
|
@@ -1714,9 +1720,29 @@ def prepare_context_cmd(
|
|
|
1714
1720
|
raise typer.Exit()
|
|
1715
1721
|
|
|
1716
1722
|
from dataclasses import asdict
|
|
1723
|
+
import time as _time
|
|
1717
1724
|
|
|
1718
1725
|
builder = TaskContextBuilder(target)
|
|
1719
|
-
|
|
1726
|
+
_progress = Progress()
|
|
1727
|
+
_phase = f"analyzing ({task})"
|
|
1728
|
+
if since:
|
|
1729
|
+
_phase += f" since {since}"
|
|
1730
|
+
_progress.start(_phase)
|
|
1731
|
+
_t0 = _time.perf_counter()
|
|
1732
|
+
try:
|
|
1733
|
+
output = builder.build(task, since=since, symptom=symptom)
|
|
1734
|
+
finally:
|
|
1735
|
+
_progress.finish()
|
|
1736
|
+
_t_total = (_time.perf_counter() - _t0) * 1000
|
|
1737
|
+
|
|
1738
|
+
if debug_perf:
|
|
1739
|
+
_perf = getattr(output, "_perf_ms", {}) or {}
|
|
1740
|
+
typer.echo(
|
|
1741
|
+
f"[debug-perf] total={_t_total:.0f}ms"
|
|
1742
|
+
+ (f" git_scan={_perf.get('git_scan_ms', '?')}ms" if "git_scan_ms" in _perf else "")
|
|
1743
|
+
+ (f" symptom={_perf.get('symptom_ms', '?')}ms" if "symptom_ms" in _perf else ""),
|
|
1744
|
+
err=True,
|
|
1745
|
+
)
|
|
1720
1746
|
|
|
1721
1747
|
# Task-specific content-filter: each task emphasizes different output fields.
|
|
1722
1748
|
# Fields marked False are suppressed from this task's output to reduce noise.
|
|
@@ -1918,6 +1944,8 @@ def prepare_context_cmd(
|
|
|
1918
1944
|
out["related_notes"] = output.related_notes
|
|
1919
1945
|
if output.symptom_note:
|
|
1920
1946
|
out["symptom_note"] = output.symptom_note
|
|
1947
|
+
if output.symptom_explain:
|
|
1948
|
+
out["symptom_explain"] = output.symptom_explain
|
|
1921
1949
|
if llm_prompt:
|
|
1922
1950
|
out["llm_prompt"] = builder.render_prompt(output)
|
|
1923
1951
|
|
|
@@ -2012,12 +2040,33 @@ def repo_ir_cmd(
|
|
|
2012
2040
|
"--include-tests",
|
|
2013
2041
|
help="Include test files in analysis (excluded by default)",
|
|
2014
2042
|
),
|
|
2043
|
+
max_nodes: Optional[int] = typer.Option(
|
|
2044
|
+
None,
|
|
2045
|
+
"--max-nodes",
|
|
2046
|
+
help="Limit graph.nodes to top N by impact score (reduces output size)",
|
|
2047
|
+
),
|
|
2048
|
+
max_edges: Optional[int] = typer.Option(
|
|
2049
|
+
None,
|
|
2050
|
+
"--max-edges",
|
|
2051
|
+
help="Limit graph.edges to N (priority: edges between kept nodes)",
|
|
2052
|
+
),
|
|
2053
|
+
summary_only: bool = typer.Option(
|
|
2054
|
+
False,
|
|
2055
|
+
"--summary-only",
|
|
2056
|
+
help="Omit full graph.nodes/edges; keep analysis summary, impact, and change_set (<300KB typical)",
|
|
2057
|
+
),
|
|
2015
2058
|
) -> None:
|
|
2016
2059
|
"""Deterministic symbol-level IR for Java repositories.
|
|
2017
2060
|
|
|
2018
2061
|
\b
|
|
2019
2062
|
Extracts symbols, relations, Spring roles, and (with --since) symbol-level diffs.
|
|
2020
|
-
Output is JSON:
|
|
2063
|
+
Output is JSON: graph{nodes,edges}, analysis, impact, subsystems, change_set.
|
|
2064
|
+
|
|
2065
|
+
\b
|
|
2066
|
+
Size control:
|
|
2067
|
+
--summary-only Omit full graph; keep analysis + impact (smallest output)
|
|
2068
|
+
--max-nodes N Keep top N nodes by score
|
|
2069
|
+
--max-edges N Keep top N edges (priority: both endpoints kept)
|
|
2021
2070
|
|
|
2022
2071
|
\b
|
|
2023
2072
|
Examples:
|
|
@@ -2025,11 +2074,13 @@ def repo_ir_cmd(
|
|
|
2025
2074
|
sourcecode repo-ir /path/to/repo --since HEAD~1
|
|
2026
2075
|
sourcecode repo-ir --files src/main/java/UserService.java
|
|
2027
2076
|
sourcecode repo-ir --since main --output ir.json
|
|
2077
|
+
sourcecode repo-ir --since HEAD~3 --summary-only --output ir-small.json
|
|
2078
|
+
sourcecode repo-ir --max-nodes 200 --max-edges 500
|
|
2028
2079
|
"""
|
|
2029
2080
|
import json as _json
|
|
2030
2081
|
import sys as _sys
|
|
2031
2082
|
|
|
2032
|
-
from sourcecode.repository_ir import build_repo_ir, find_java_files
|
|
2083
|
+
from sourcecode.repository_ir import apply_ir_size_limits, build_repo_ir, find_java_files
|
|
2033
2084
|
|
|
2034
2085
|
root = path.resolve()
|
|
2035
2086
|
if not root.is_dir():
|
|
@@ -2062,12 +2113,33 @@ def repo_ir_cmd(
|
|
|
2062
2113
|
)
|
|
2063
2114
|
return
|
|
2064
2115
|
|
|
2065
|
-
|
|
2116
|
+
_ir_phase = f"extracting IR ({len(file_list)} files)"
|
|
2117
|
+
if since:
|
|
2118
|
+
_ir_phase += f" since {since}"
|
|
2119
|
+
_ir_progress = Progress()
|
|
2120
|
+
_ir_progress.start(_ir_phase)
|
|
2121
|
+
try:
|
|
2122
|
+
ir = build_repo_ir(file_list, root, since=since)
|
|
2123
|
+
finally:
|
|
2124
|
+
_ir_progress.finish()
|
|
2125
|
+
ir = apply_ir_size_limits(
|
|
2126
|
+
ir,
|
|
2127
|
+
max_nodes=max_nodes,
|
|
2128
|
+
max_edges=max_edges,
|
|
2129
|
+
summary_only=summary_only,
|
|
2130
|
+
)
|
|
2066
2131
|
output = _json.dumps(ir, indent=2, ensure_ascii=False)
|
|
2067
2132
|
|
|
2068
2133
|
if output_path:
|
|
2069
2134
|
output_path.write_text(output, encoding="utf-8")
|
|
2070
|
-
|
|
2135
|
+
n_nodes = len((ir.get("graph") or {}).get("nodes") or [])
|
|
2136
|
+
n_edges = len((ir.get("graph") or {}).get("edges") or [])
|
|
2137
|
+
size_kb = len(output.encode("utf-8")) // 1024
|
|
2138
|
+
typer.echo(
|
|
2139
|
+
f"IR written to {output_path} "
|
|
2140
|
+
f"({size_kb}KB, {n_nodes} nodes, {n_edges} edges)",
|
|
2141
|
+
err=True,
|
|
2142
|
+
)
|
|
2071
2143
|
else:
|
|
2072
2144
|
_sys.stdout.write(output)
|
|
2073
2145
|
_sys.stdout.write("\n")
|
|
@@ -52,8 +52,15 @@ _HOTSPOT_AUX_DIRS: frozenset[str] = frozenset({
|
|
|
52
52
|
"sandbox", "sandboxes",
|
|
53
53
|
"ci", "translations", "locales", "locale", "i18n", "l10n",
|
|
54
54
|
".planning",
|
|
55
|
+
# vendor / build dirs: high path count, zero operational signal
|
|
56
|
+
"vendor", "node_modules", "dist", "target", "build",
|
|
57
|
+
".gradle", ".mvn", "generated-sources", "generated-resources",
|
|
55
58
|
})
|
|
56
59
|
|
|
60
|
+
# When git diff returns more changed files than this, degrade gracefully:
|
|
61
|
+
# truncate hotspot analysis depth and emit a structured warning.
|
|
62
|
+
_CHANGED_FILES_DEGRADATION_THRESHOLD = 200
|
|
63
|
+
|
|
57
64
|
|
|
58
65
|
def _run_git(args: list[str], cwd: Path, timeout: int = 15) -> tuple[str, int]:
|
|
59
66
|
result = subprocess.run(
|
|
@@ -333,6 +333,7 @@ class TaskOutput:
|
|
|
333
333
|
symptom: Optional[str] = None # fix-bug only
|
|
334
334
|
related_notes: list[dict] = field(default_factory=list) # fix-bug + symptom only
|
|
335
335
|
symptom_note: Optional[str] = None # fix-bug: cross-layer synonym note
|
|
336
|
+
symptom_explain: Optional[dict] = None # fix-bug: structured evidence breakdown
|
|
336
337
|
# delta-specific impact fields
|
|
337
338
|
impact_summary: Optional[str] = None
|
|
338
339
|
affected_modules: list[str] = field(default_factory=list)
|
|
@@ -818,7 +819,7 @@ class TaskContextBuilder:
|
|
|
818
819
|
if not self._is_source(_p) or self._is_test(_p):
|
|
819
820
|
continue
|
|
820
821
|
try:
|
|
821
|
-
_loc = len((self.root / _p).read_text(errors="replace").splitlines())
|
|
822
|
+
_loc = len((self.root / _p).read_text(encoding="utf-8", errors="replace").splitlines())
|
|
822
823
|
if _loc > 200:
|
|
823
824
|
_large.append((_loc, _p))
|
|
824
825
|
except OSError:
|
|
@@ -1207,6 +1208,7 @@ class TaskContextBuilder:
|
|
|
1207
1208
|
symptom_keywords: list[str] = []
|
|
1208
1209
|
related_notes: list[dict] = []
|
|
1209
1210
|
symptom_note: Optional[str] = None
|
|
1211
|
+
symptom_explain: Optional[dict] = None
|
|
1210
1212
|
if task_name == "fix-bug" and symptom:
|
|
1211
1213
|
import re as _re
|
|
1212
1214
|
_camel_expanded = _re.sub(r'([a-z])([A-Z])', r'\1 \2', symptom)
|
|
@@ -1216,12 +1218,24 @@ class TaskContextBuilder:
|
|
|
1216
1218
|
if len(w) > 2
|
|
1217
1219
|
]
|
|
1218
1220
|
if symptom_keywords:
|
|
1221
|
+
# Pre-compile combined keyword pattern for fast content scanning
|
|
1222
|
+
_kw_re = _re.compile(
|
|
1223
|
+
"|".join(_re.escape(kw) for kw in symptom_keywords),
|
|
1224
|
+
_re.IGNORECASE,
|
|
1225
|
+
)
|
|
1226
|
+
|
|
1227
|
+
# Structured evidence collectors for symptom_explain
|
|
1228
|
+
_sx_direct_path: list[str] = []
|
|
1229
|
+
_sx_content: list[str] = []
|
|
1230
|
+
_sx_commits: list[dict] = []
|
|
1231
|
+
_sx_synonyms: list[str] = []
|
|
1232
|
+
_sx_boosts: list[dict] = []
|
|
1233
|
+
|
|
1219
1234
|
# Pass 1: surface code notes whose text contains any keyword
|
|
1220
|
-
# Also track which file paths have matching notes (for score boost below).
|
|
1221
1235
|
_note_matched_paths: dict[str, int] = {} # path → count of matching notes
|
|
1222
1236
|
for _n in cn_notes_for_ranking:
|
|
1223
1237
|
_text = (getattr(_n, "text", "") or "").lower()
|
|
1224
|
-
if
|
|
1238
|
+
if _kw_re.search(_text):
|
|
1225
1239
|
_np = getattr(_n, "path", "")
|
|
1226
1240
|
related_notes.append({
|
|
1227
1241
|
"kind": getattr(_n, "kind", ""),
|
|
@@ -1231,17 +1245,22 @@ class TaskContextBuilder:
|
|
|
1231
1245
|
})
|
|
1232
1246
|
_note_matched_paths[_np] = _note_matched_paths.get(_np, 0) + 1
|
|
1233
1247
|
|
|
1234
|
-
# Pass 2: build commit message index —
|
|
1235
|
-
# message matches a symptom keyword get
|
|
1236
|
-
#
|
|
1237
|
-
# that
|
|
1248
|
+
# Pass 2: build commit message index — cap at 60 most-recent commits.
|
|
1249
|
+
# Files touched in commits whose message matches a symptom keyword get
|
|
1250
|
+
# a strong recency signal. Primary signal for domain keywords ("sesiones")
|
|
1251
|
+
# that appear in commit messages but not file paths.
|
|
1238
1252
|
_commit_file_hits: dict[str, int] = {} # path → n matching commits
|
|
1239
|
-
|
|
1253
|
+
_commits_scanned = _recent_commits_for_symptom[:60]
|
|
1254
|
+
for _cr in _commits_scanned:
|
|
1240
1255
|
_msg_lower = (_cr.message or "").lower()
|
|
1241
|
-
if
|
|
1256
|
+
if _kw_re.search(_msg_lower):
|
|
1242
1257
|
for _cf in (_cr.files_changed or []):
|
|
1243
1258
|
_cf_norm = _cf.replace("\\", "/")
|
|
1244
1259
|
_commit_file_hits[_cf_norm] = _commit_file_hits.get(_cf_norm, 0) + 1
|
|
1260
|
+
_sx_commits.append({
|
|
1261
|
+
"message": (_cr.message or "")[:80],
|
|
1262
|
+
"files": list((_cr.files_changed or [])[:5]),
|
|
1263
|
+
})
|
|
1245
1264
|
|
|
1246
1265
|
# Pass 3: inject files from commit index not yet in candidate pool
|
|
1247
1266
|
_existing_paths = {rf.path for rf in relevant_files}
|
|
@@ -1261,7 +1280,6 @@ class TaskContextBuilder:
|
|
|
1261
1280
|
_existing_paths.add(_cp)
|
|
1262
1281
|
|
|
1263
1282
|
# Pass 4: inject files whose path matches symptom keywords
|
|
1264
|
-
# but weren't in the candidate pool (no structural/git signals).
|
|
1265
1283
|
for _p in all_paths:
|
|
1266
1284
|
if _p in _existing_paths:
|
|
1267
1285
|
continue
|
|
@@ -1282,13 +1300,29 @@ class TaskContextBuilder:
|
|
|
1282
1300
|
why=f"symptom injection: {', '.join(_matching_kws)}",
|
|
1283
1301
|
))
|
|
1284
1302
|
_existing_paths.add(_p)
|
|
1303
|
+
_sx_direct_path.append(_p)
|
|
1285
1304
|
|
|
1286
|
-
# Pass 5:
|
|
1287
|
-
#
|
|
1305
|
+
# Pass 5+6 combined: single file read per candidate.
|
|
1306
|
+
# Limit content scan to top 80 candidates by current score to bound I/O.
|
|
1288
1307
|
_src_exts = frozenset({".java", ".py", ".ts", ".js", ".kt", ".go"})
|
|
1308
|
+
_frontend_kws = [kw for kw in symptom_keywords if kw in _FRONTEND_SYMPTOM_MAP]
|
|
1309
|
+
_backend_terms_set: list[str] = []
|
|
1310
|
+
if _frontend_kws:
|
|
1311
|
+
_bt: list[str] = []
|
|
1312
|
+
for _fkw in _frontend_kws:
|
|
1313
|
+
_bt.extend(_FRONTEND_SYMPTOM_MAP[_fkw])
|
|
1314
|
+
_backend_terms_set = list(dict.fromkeys(_bt))
|
|
1315
|
+
|
|
1316
|
+
# Sort before content scan so top candidates get read first
|
|
1317
|
+
relevant_files = sorted(relevant_files, key=lambda rf: -rf.score)
|
|
1318
|
+
_CONTENT_SCAN_LIMIT = 80
|
|
1319
|
+
_scan_candidates = relevant_files[:_CONTENT_SCAN_LIMIT]
|
|
1320
|
+
_no_scan_candidates = relevant_files[_CONTENT_SCAN_LIMIT:]
|
|
1321
|
+
|
|
1289
1322
|
_boosted: list[RelevantFile] = []
|
|
1290
|
-
for _rf in
|
|
1323
|
+
for _rf in _scan_candidates:
|
|
1291
1324
|
_extra = 0.0
|
|
1325
|
+
_extra_syn = 0.0
|
|
1292
1326
|
_reasons: list[str] = []
|
|
1293
1327
|
_p_lower = _rf.path.lower()
|
|
1294
1328
|
|
|
@@ -1298,6 +1332,7 @@ class TaskContextBuilder:
|
|
|
1298
1332
|
_cb = min(0.40, _c_hits * 0.25)
|
|
1299
1333
|
_extra += _cb
|
|
1300
1334
|
_reasons.append(f"commit-msg symptom ×{_c_hits} (+{_cb:.2f})")
|
|
1335
|
+
_sx_boosts.append({"type": "commit_message", "value": round(_cb, 3), "evidence": _rf.path})
|
|
1301
1336
|
|
|
1302
1337
|
# Code note boost: +0.20/note, cap +0.30
|
|
1303
1338
|
_n_hits = _note_matched_paths.get(_rf.path, 0)
|
|
@@ -1305,77 +1340,94 @@ class TaskContextBuilder:
|
|
|
1305
1340
|
_nb = min(0.30, _n_hits * 0.20)
|
|
1306
1341
|
_extra += _nb
|
|
1307
1342
|
_reasons.append(f"note-match symptom ×{_n_hits} (+{_nb:.2f})")
|
|
1343
|
+
_sx_boosts.append({"type": "code_note", "value": round(_nb, 3), "evidence": _rf.path})
|
|
1308
1344
|
|
|
1309
|
-
# Path keyword boost
|
|
1310
|
-
# files; re-apply for pre-existing candidates whose path matches.
|
|
1345
|
+
# Path keyword boost for pre-existing candidates
|
|
1311
1346
|
_path_kws = [kw for kw in symptom_keywords if kw in _p_lower]
|
|
1312
1347
|
if _path_kws and _rf.role != "symptom_match":
|
|
1313
1348
|
_pb = 0.20 * len(_path_kws)
|
|
1314
1349
|
_extra += _pb
|
|
1315
1350
|
_reasons.append(f"path-kw symptom ({', '.join(_path_kws)}) (+{_pb:.2f})")
|
|
1351
|
+
_sx_boosts.append({"type": "path_match", "value": round(_pb, 3), "evidence": _rf.path})
|
|
1316
1352
|
|
|
1317
|
-
#
|
|
1353
|
+
# Single file read — covers both content scan and synonym scan
|
|
1354
|
+
_body_lower = ""
|
|
1318
1355
|
if Path(_rf.path).suffix.lower() in _src_exts:
|
|
1319
1356
|
try:
|
|
1320
|
-
|
|
1357
|
+
_body_lower = (self.root / _rf.path).read_text(
|
|
1321
1358
|
encoding="utf-8", errors="replace"
|
|
1322
|
-
).
|
|
1323
|
-
_body = "\n".join(_lines).lower()
|
|
1324
|
-
_hits = sum(_body.count(kw) for kw in symptom_keywords)
|
|
1325
|
-
_content_b = min(0.50, _hits * 0.05)
|
|
1326
|
-
if _content_b > 0:
|
|
1327
|
-
_extra += _content_b
|
|
1328
|
-
_reasons.append(f"content-match symptom ×{_hits} (+{_content_b:.2f})")
|
|
1359
|
+
)[:12000].lower() # ~300 lines avg
|
|
1329
1360
|
except OSError:
|
|
1330
1361
|
pass
|
|
1331
1362
|
|
|
1363
|
+
# Content scan boost: +0.05/hit, cap +0.50
|
|
1364
|
+
if _body_lower:
|
|
1365
|
+
_hits = len(_kw_re.findall(_body_lower))
|
|
1366
|
+
_content_b = min(0.50, _hits * 0.05)
|
|
1367
|
+
if _content_b > 0:
|
|
1368
|
+
_extra += _content_b
|
|
1369
|
+
_reasons.append(f"content-match symptom ×{_hits} (+{_content_b:.2f})")
|
|
1370
|
+
_sx_boosts.append({"type": "content_match", "value": round(_content_b, 3), "evidence": _rf.path})
|
|
1371
|
+
_sx_content.append(_rf.path)
|
|
1372
|
+
|
|
1373
|
+
# Synonym scan (Pass 6): only apply when file has prior non-synonym
|
|
1374
|
+
# evidence (commit hit, note, path, or content) — prevents boosting
|
|
1375
|
+
# arbitrary interceptors/configs with no other signal.
|
|
1376
|
+
if _backend_terms_set and _body_lower:
|
|
1377
|
+
_prior_boost = _extra # boost accumulated before synonym
|
|
1378
|
+
if _prior_boost >= 0.10: # min threshold: must have real prior signal
|
|
1379
|
+
_hits_syn = sum(_body_lower.count(t) for t in _backend_terms_set)
|
|
1380
|
+
_extra_syn = min(0.20, _hits_syn * 0.02)
|
|
1381
|
+
if _extra_syn > 0:
|
|
1382
|
+
_sx_synonyms.append(_rf.path)
|
|
1383
|
+
_sx_boosts.append({"type": "synonym_match", "value": round(_extra_syn, 3), "evidence": _rf.path})
|
|
1384
|
+
|
|
1385
|
+
_total_extra = _extra + _extra_syn
|
|
1332
1386
|
_new_reason = _rf.reason
|
|
1333
1387
|
if _reasons:
|
|
1334
|
-
|
|
1388
|
+
_syn_suffix = f", synonym-match backend (+{_extra_syn:.2f})" if _extra_syn > 0 else ""
|
|
1389
|
+
_new_reason = _rf.reason + ", " + ", ".join(_reasons) + _syn_suffix
|
|
1390
|
+
elif _extra_syn > 0:
|
|
1391
|
+
_new_reason = _rf.reason + f", synonym-match backend (+{_extra_syn:.2f})"
|
|
1392
|
+
|
|
1335
1393
|
_boosted.append(RelevantFile(
|
|
1336
1394
|
path=_rf.path,
|
|
1337
1395
|
role=_rf.role,
|
|
1338
|
-
score=round(min(_rf.score +
|
|
1396
|
+
score=round(min(_rf.score + _total_extra, 1.0), 2),
|
|
1339
1397
|
reason=_new_reason,
|
|
1340
1398
|
why=_rf.why,
|
|
1341
1399
|
))
|
|
1342
|
-
relevant_files = sorted(_boosted, key=lambda rf: -rf.score)
|
|
1343
1400
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
if _frontend_kws:
|
|
1348
|
-
|
|
1349
|
-
for _fkw in _frontend_kws:
|
|
1350
|
-
_backend_terms.extend(_FRONTEND_SYMPTOM_MAP[_fkw])
|
|
1351
|
-
_backend_terms_set = list(dict.fromkeys(_backend_terms))
|
|
1352
|
-
_synonym_boosted: list[RelevantFile] = []
|
|
1353
|
-
for _rf in relevant_files:
|
|
1354
|
-
_extra_syn = 0.0
|
|
1355
|
-
if Path(_rf.path).suffix.lower() in _src_exts:
|
|
1356
|
-
try:
|
|
1357
|
-
_lines_syn = (self.root / _rf.path).read_text(
|
|
1358
|
-
encoding="utf-8", errors="replace"
|
|
1359
|
-
).splitlines()[:300]
|
|
1360
|
-
_body_syn = "\n".join(_lines_syn).lower()
|
|
1361
|
-
_hits_syn = sum(_body_syn.count(t) for t in _backend_terms_set)
|
|
1362
|
-
_extra_syn = min(0.20, _hits_syn * 0.02)
|
|
1363
|
-
except OSError:
|
|
1364
|
-
pass
|
|
1365
|
-
_synonym_boosted.append(RelevantFile(
|
|
1366
|
-
path=_rf.path,
|
|
1367
|
-
role=_rf.role,
|
|
1368
|
-
score=round(min(_rf.score + _extra_syn, 1.0), 2),
|
|
1369
|
-
reason=_rf.reason + (f", synonym-match backend (+{_extra_syn:.2f})" if _extra_syn > 0 else ""),
|
|
1370
|
-
why=_rf.why,
|
|
1371
|
-
))
|
|
1372
|
-
relevant_files = sorted(_synonym_boosted, key=lambda rf: -rf.score)
|
|
1373
|
-
_synonym_note = (
|
|
1401
|
+
relevant_files = sorted(_boosted + _no_scan_candidates, key=lambda rf: -rf.score)
|
|
1402
|
+
|
|
1403
|
+
# Synonym note (only when synonyms actually fired)
|
|
1404
|
+
if _frontend_kws and _sx_synonyms:
|
|
1405
|
+
symptom_note = (
|
|
1374
1406
|
f"Frontend concept detected ({', '.join(_frontend_kws)}). "
|
|
1375
|
-
"Backend service-layer
|
|
1407
|
+
"Backend service-layer files boosted by synonym match "
|
|
1376
1408
|
"[INFERRED (LOW CONFIDENCE) — pattern heuristic, not structural proof]."
|
|
1377
1409
|
)
|
|
1378
|
-
|
|
1410
|
+
|
|
1411
|
+
# Confidence: based on richest signal type present
|
|
1412
|
+
if _commit_file_hits:
|
|
1413
|
+
_sx_confidence = "HIGH"
|
|
1414
|
+
elif _sx_direct_path or _sx_content:
|
|
1415
|
+
_sx_confidence = "MEDIUM"
|
|
1416
|
+
else:
|
|
1417
|
+
_sx_confidence = "LOW"
|
|
1418
|
+
|
|
1419
|
+
symptom_explain = {
|
|
1420
|
+
"keywords": symptom_keywords,
|
|
1421
|
+
"confidence": _sx_confidence,
|
|
1422
|
+
"direct_path_matches": _sx_direct_path[:10],
|
|
1423
|
+
"content_matches": _sx_content[:10],
|
|
1424
|
+
"commit_matches": _sx_commits[:10],
|
|
1425
|
+
"synonym_matches": _sx_synonyms[:10],
|
|
1426
|
+
"boosts": _sx_boosts[:30],
|
|
1427
|
+
"final_boost": round(
|
|
1428
|
+
sum(b["value"] for b in _sx_boosts), 3
|
|
1429
|
+
),
|
|
1430
|
+
}
|
|
1379
1431
|
|
|
1380
1432
|
# ── 7. Test gaps (generate-tests only) ────────────────────────────
|
|
1381
1433
|
test_gaps: list[str] = []
|
|
@@ -1474,6 +1526,7 @@ class TaskContextBuilder:
|
|
|
1474
1526
|
symptom=symptom if task_name == "fix-bug" and symptom else None,
|
|
1475
1527
|
related_notes=related_notes,
|
|
1476
1528
|
symptom_note=symptom_note,
|
|
1529
|
+
symptom_explain=symptom_explain if task_name == "fix-bug" else None,
|
|
1477
1530
|
impact_summary=_delta_impact_summary,
|
|
1478
1531
|
affected_modules=_delta_affected_modules,
|
|
1479
1532
|
risk_areas=_delta_risk_areas,
|
|
@@ -1544,12 +1544,99 @@ def build_repo_ir(
|
|
|
1544
1544
|
return _assemble(all_symbols, unique_relations, all_changed, spring_summary, all_route_diffs_sorted)
|
|
1545
1545
|
|
|
1546
1546
|
|
|
1547
|
+
# ---------------------------------------------------------------------------
|
|
1548
|
+
# Output size limits
|
|
1549
|
+
# ---------------------------------------------------------------------------
|
|
1550
|
+
|
|
1551
|
+
# Vendor/generated dirs to skip when finding Java files and in git analysis.
|
|
1552
|
+
_VENDOR_DIRS: frozenset[str] = frozenset({
|
|
1553
|
+
"vendor", "node_modules", "dist", "target", "build",
|
|
1554
|
+
".gradle", ".mvn", "generated", "generated-sources",
|
|
1555
|
+
"generated-resources",
|
|
1556
|
+
})
|
|
1557
|
+
|
|
1558
|
+
|
|
1559
|
+
def apply_ir_size_limits(
|
|
1560
|
+
ir: dict,
|
|
1561
|
+
*,
|
|
1562
|
+
max_nodes: Optional[int] = None,
|
|
1563
|
+
max_edges: Optional[int] = None,
|
|
1564
|
+
summary_only: bool = False,
|
|
1565
|
+
) -> dict:
|
|
1566
|
+
"""Apply size limits to a repo-ir output dict. Non-destructive: returns new dict.
|
|
1567
|
+
|
|
1568
|
+
Node ordering: top-ranked (by impact score) nodes are kept first.
|
|
1569
|
+
Edge priority: edges connecting two kept nodes over cross-boundary edges.
|
|
1570
|
+
"""
|
|
1571
|
+
if not max_nodes and not max_edges and not summary_only:
|
|
1572
|
+
return ir
|
|
1573
|
+
|
|
1574
|
+
out = dict(ir)
|
|
1575
|
+
graph = ir.get("graph") or {}
|
|
1576
|
+
nodes: list[dict] = list(graph.get("nodes") or [])
|
|
1577
|
+
edges: list[dict] = list(graph.get("edges") or [])
|
|
1578
|
+
ranked: list[dict] = list((ir.get("impact") or {}).get("ranked_nodes") or [])
|
|
1579
|
+
analysis: dict = ir.get("analysis") or {}
|
|
1580
|
+
|
|
1581
|
+
if summary_only:
|
|
1582
|
+
n_nodes, n_edges = len(nodes), len(edges)
|
|
1583
|
+
out["graph"] = {
|
|
1584
|
+
"nodes": [],
|
|
1585
|
+
"edges": [],
|
|
1586
|
+
"_omitted": (
|
|
1587
|
+
f"{n_nodes} nodes and {n_edges} edges omitted — "
|
|
1588
|
+
"remove --summary-only to restore full graph"
|
|
1589
|
+
),
|
|
1590
|
+
}
|
|
1591
|
+
out["reverse_graph"] = {}
|
|
1592
|
+
out["impact"] = {
|
|
1593
|
+
"global_score": (ir.get("impact") or {}).get("global_score", 0),
|
|
1594
|
+
"ranked_nodes": ranked[:20],
|
|
1595
|
+
}
|
|
1596
|
+
out["analysis"] = {
|
|
1597
|
+
"changed_entities": analysis.get("changed_entities") or [],
|
|
1598
|
+
"impacted_entities": (analysis.get("impacted_entities") or [])[:20],
|
|
1599
|
+
"isolated_changes": analysis.get("isolated_changes") or [],
|
|
1600
|
+
"validated_changes": analysis.get("validated_changes") or [],
|
|
1601
|
+
}
|
|
1602
|
+
return out
|
|
1603
|
+
|
|
1604
|
+
# Build score map from ranked_nodes (already sorted -score, fqn)
|
|
1605
|
+
score_map: dict[str, float] = {rn["entity"]: rn["score"] for rn in ranked}
|
|
1606
|
+
kept_fqns: Optional[set[str]] = None
|
|
1607
|
+
|
|
1608
|
+
if max_nodes is not None and len(nodes) > max_nodes:
|
|
1609
|
+
nodes_sorted = sorted(
|
|
1610
|
+
nodes,
|
|
1611
|
+
key=lambda n: (-score_map.get(n["fqn"], 0.0), n["fqn"]),
|
|
1612
|
+
)
|
|
1613
|
+
nodes = nodes_sorted[:max_nodes]
|
|
1614
|
+
kept_fqns = {n["fqn"] for n in nodes}
|
|
1615
|
+
ranked = [rn for rn in ranked if rn["entity"] in kept_fqns]
|
|
1616
|
+
|
|
1617
|
+
if kept_fqns is not None or max_edges is not None:
|
|
1618
|
+
if kept_fqns is not None:
|
|
1619
|
+
# Priority: edges where both endpoints are kept nodes
|
|
1620
|
+
priority = [e for e in edges if e["from"] in kept_fqns and e["to"] in kept_fqns]
|
|
1621
|
+
rest = [e for e in edges if not (e["from"] in kept_fqns and e["to"] in kept_fqns)]
|
|
1622
|
+
edges = priority + rest
|
|
1623
|
+
if max_edges is not None:
|
|
1624
|
+
edges = edges[:max_edges]
|
|
1625
|
+
|
|
1626
|
+
out["graph"] = {"nodes": nodes, "edges": edges}
|
|
1627
|
+
out["impact"] = {
|
|
1628
|
+
"global_score": (ir.get("impact") or {}).get("global_score", 0),
|
|
1629
|
+
"ranked_nodes": ranked,
|
|
1630
|
+
}
|
|
1631
|
+
return out
|
|
1632
|
+
|
|
1633
|
+
|
|
1547
1634
|
# ---------------------------------------------------------------------------
|
|
1548
1635
|
# Convenience: find Java files in a repo
|
|
1549
1636
|
# ---------------------------------------------------------------------------
|
|
1550
1637
|
|
|
1551
1638
|
def find_java_files(root: Path, *, max_files: int = 500) -> list[str]:
|
|
1552
|
-
"""Return relative paths to Java files under root, excluding test dirs."""
|
|
1639
|
+
"""Return relative paths to Java files under root, excluding test dirs and vendor."""
|
|
1553
1640
|
results: list[str] = []
|
|
1554
1641
|
for p in sorted(root.rglob("*.java")):
|
|
1555
1642
|
if len(results) >= max_files:
|
|
@@ -1558,7 +1645,12 @@ def find_java_files(root: Path, *, max_files: int = 500) -> list[str]:
|
|
|
1558
1645
|
rel = str(p.relative_to(root)).replace("\\", "/")
|
|
1559
1646
|
except ValueError:
|
|
1560
1647
|
continue
|
|
1648
|
+
parts = rel.split("/")
|
|
1649
|
+
# Skip test dirs
|
|
1561
1650
|
if "/test/" in rel or "/tests/" in rel or rel.startswith("test/"):
|
|
1562
1651
|
continue
|
|
1652
|
+
# Skip vendor/generated/build dirs
|
|
1653
|
+
if any(part in _VENDOR_DIRS for part in parts[:-1]):
|
|
1654
|
+
continue
|
|
1563
1655
|
results.append(rel)
|
|
1564
1656
|
return results
|