sourcecode 1.19.0__tar.gz → 1.21.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sourcecode-1.19.0 → sourcecode-1.21.0}/PKG-INFO +3 -3
- {sourcecode-1.19.0 → sourcecode-1.21.0}/README.md +2 -2
- {sourcecode-1.19.0 → sourcecode-1.21.0}/pyproject.toml +1 -1
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/cli.py +16 -2
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/prepare_context.py +489 -17
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/serializer.py +54 -1
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_signal_hierarchy.py +3 -1
- {sourcecode-1.19.0 → sourcecode-1.21.0}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/.continue-here.md +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/.gitignore +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/.ruff.toml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/CONTRIBUTING.md +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/LICENSE +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/SECURITY.md +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/docs/privacy.md +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/docs/schema.md +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/raw +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/run_cli.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/__init__.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/conftest.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/latin1_sample.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/latin1_sample_iso.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/lcov.info +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_architecture_summary.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_ast_extractor.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_block1_reliability.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_block2_coverage.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_block5_quality.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_bug_fixes_v16.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_classifier.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_cli.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_context_scorer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_contract_pipeline.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_coverage_parser.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_cross_consistency.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_dependency_schema.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_detector_python.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_detectors_base.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_encoding_regression.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_graph_schema.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_integration.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_integration_detection.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_integration_docs.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_integration_lqn.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_integration_metrics.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_integration_multistack.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_integration_semantics.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_integration_universal.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_java_spring_integration.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_packaging.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_pipeline_integrity.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_real_projects.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_redactor.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_scanner.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_schema.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_schema_normalization.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_semantic_schema.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_summarizer.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_surface_honesty.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_task_differentiation.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_telemetry.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_v1_10_regressions.py +0 -0
- {sourcecode-1.19.0 → sourcecode-1.21.0}/tests/test_workspace_analyzer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.21.0
|
|
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
|
**Compressed AI-ready context for Java/Spring enterprise codebases.**
|
|
223
223
|
|
|
224
|
-

|
|
225
225
|

|
|
226
226
|
|
|
227
227
|
---
|
|
@@ -255,7 +255,7 @@ pipx install sourcecode
|
|
|
255
255
|
|
|
256
256
|
```bash
|
|
257
257
|
sourcecode version
|
|
258
|
-
# sourcecode 1.
|
|
258
|
+
# sourcecode 1.21.0
|
|
259
259
|
```
|
|
260
260
|
|
|
261
261
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Compressed AI-ready context for Java/Spring enterprise codebases.**
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -36,7 +36,7 @@ pipx install sourcecode
|
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
sourcecode version
|
|
39
|
-
# sourcecode 1.
|
|
39
|
+
# sourcecode 1.21.0
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
---
|
|
@@ -1727,9 +1727,9 @@ def prepare_context_cmd(
|
|
|
1727
1727
|
"changed_files": False, "affected_entry_points": False,
|
|
1728
1728
|
},
|
|
1729
1729
|
"delta": {
|
|
1730
|
-
"project_summary":
|
|
1730
|
+
"project_summary": False, "architecture_summary": False,
|
|
1731
1731
|
"relevant_files": True, "key_dependencies": False,
|
|
1732
|
-
"gaps":
|
|
1732
|
+
"gaps": True, "confidence": True,
|
|
1733
1733
|
"suspected_areas": False, "improvement_opportunities": False,
|
|
1734
1734
|
"test_gaps": False, "code_notes_summary": False,
|
|
1735
1735
|
"changed_files": True, "affected_entry_points": True,
|
|
@@ -1771,12 +1771,26 @@ def prepare_context_cmd(
|
|
|
1771
1771
|
out["changed_files"] = output.changed_files
|
|
1772
1772
|
if _task_include("affected_entry_points") and output.affected_entry_points:
|
|
1773
1773
|
out["affected_entry_points"] = output.affected_entry_points
|
|
1774
|
+
# Delta-specific impact fields
|
|
1775
|
+
if task == "delta":
|
|
1776
|
+
if output.since:
|
|
1777
|
+
out["since"] = output.since
|
|
1778
|
+
if output.impact_summary:
|
|
1779
|
+
out["impact_summary"] = output.impact_summary
|
|
1780
|
+
if output.affected_modules:
|
|
1781
|
+
out["affected_modules"] = output.affected_modules
|
|
1782
|
+
if output.risk_areas:
|
|
1783
|
+
out["risk_areas"] = output.risk_areas
|
|
1784
|
+
if output.why_these_files:
|
|
1785
|
+
out["reasoning"] = output.why_these_files
|
|
1774
1786
|
if output.limitations:
|
|
1775
1787
|
out["limitations"] = output.limitations
|
|
1776
1788
|
if output.symptom:
|
|
1777
1789
|
out["symptom"] = output.symptom
|
|
1778
1790
|
if output.related_notes:
|
|
1779
1791
|
out["related_notes"] = output.related_notes
|
|
1792
|
+
if output.symptom_note:
|
|
1793
|
+
out["symptom_note"] = output.symptom_note
|
|
1780
1794
|
if llm_prompt:
|
|
1781
1795
|
out["llm_prompt"] = builder.render_prompt(output)
|
|
1782
1796
|
|
|
@@ -323,6 +323,12 @@ class TaskOutput:
|
|
|
323
323
|
affected_entry_points: list[str] = field(default_factory=list) # delta task only
|
|
324
324
|
symptom: Optional[str] = None # fix-bug only
|
|
325
325
|
related_notes: list[dict] = field(default_factory=list) # fix-bug + symptom only
|
|
326
|
+
symptom_note: Optional[str] = None # fix-bug: cross-layer synonym note
|
|
327
|
+
# delta-specific impact fields
|
|
328
|
+
impact_summary: Optional[str] = None
|
|
329
|
+
affected_modules: list[str] = field(default_factory=list)
|
|
330
|
+
risk_areas: list[dict] = field(default_factory=list)
|
|
331
|
+
since: Optional[str] = None
|
|
326
332
|
|
|
327
333
|
|
|
328
334
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -382,6 +388,22 @@ _ALL_EXTENSIONS: frozenset[str] = _SOURCE_EXTENSIONS | frozenset({
|
|
|
382
388
|
".md", ".toml", ".yaml", ".yml", ".json", ".xml",
|
|
383
389
|
})
|
|
384
390
|
|
|
391
|
+
# Maps frontend symptom keywords → backend terms likely to contain the root cause.
|
|
392
|
+
# Used to boost service/interceptor files when the symptom is UI-only.
|
|
393
|
+
_FRONTEND_SYMPTOM_MAP: dict[str, list[str]] = {
|
|
394
|
+
"spinner": ["loading", "setloading", "finalize", "httpinterceptor", "interceptor", "service"],
|
|
395
|
+
"loading": ["loading", "setloading", "finalize", "httpinterceptor", "interceptor", "service"],
|
|
396
|
+
"login": ["authcontroller", "securityconfig", "filterconfig", "jwtfilter", "auth", "authentication"],
|
|
397
|
+
"logout": ["authcontroller", "securityconfig", "jwtfilter", "auth", "session"],
|
|
398
|
+
"dropdown": ["getmapping", "findall", "obtenertodos", "listall", "findby"],
|
|
399
|
+
"modal": ["controller", "getmapping", "findby", "search"],
|
|
400
|
+
"popup": ["controller", "getmapping", "findby", "search"],
|
|
401
|
+
"table": ["paginated", "findby", "search", "getmapping", "listall"],
|
|
402
|
+
"grid": ["paginated", "findby", "search", "getmapping"],
|
|
403
|
+
"button": ["postmapping", "putmapping", "deletemapping", "controller", "service"],
|
|
404
|
+
"form": ["postmapping", "putmapping", "controller", "service", "dto"],
|
|
405
|
+
}
|
|
406
|
+
|
|
385
407
|
|
|
386
408
|
class TaskContextBuilder:
|
|
387
409
|
def __init__(self, root: Path) -> None:
|
|
@@ -623,18 +645,42 @@ class TaskContextBuilder:
|
|
|
623
645
|
test_set = {p for p in all_paths if self._is_test(p)}
|
|
624
646
|
source_set = {p for p in all_paths if not self._is_test(p) and self._is_source(p)}
|
|
625
647
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
648
|
+
# Delta uses a dedicated impact-analysis path — never the generic ranker.
|
|
649
|
+
_delta_impact_summary: Optional[str] = None
|
|
650
|
+
_delta_affected_modules: list[str] = []
|
|
651
|
+
_delta_risk_areas: list[dict] = []
|
|
652
|
+
_delta_why: dict[str, str] = {}
|
|
653
|
+
_delta_analysis_gaps: list[str] = []
|
|
654
|
+
|
|
655
|
+
if task_name == "delta":
|
|
656
|
+
_delta_changed_list: list[str] = sorted(_delta_files) if _delta_files else []
|
|
657
|
+
(
|
|
658
|
+
relevant_files,
|
|
659
|
+
_delta_impact_summary,
|
|
660
|
+
_delta_affected_modules,
|
|
661
|
+
_delta_risk_areas,
|
|
662
|
+
_delta_why,
|
|
663
|
+
_delta_analysis_gaps,
|
|
664
|
+
) = self._build_delta_impact(
|
|
665
|
+
changed_files=_delta_changed_list,
|
|
666
|
+
all_paths=all_paths,
|
|
667
|
+
entry_points=entry_points,
|
|
668
|
+
since=since,
|
|
669
|
+
)
|
|
670
|
+
else:
|
|
671
|
+
relevant_files = self._rank_files(
|
|
672
|
+
task_name, spec, all_paths, entry_set, test_set,
|
|
673
|
+
monorepo_packages=sm.monorepo_packages if sm.monorepo_packages else None,
|
|
674
|
+
git_hotspots=git_hotspots,
|
|
675
|
+
uncommitted_files=uncommitted_files,
|
|
676
|
+
code_notes=cn_notes_for_ranking if cn_notes_for_ranking else None,
|
|
677
|
+
delta_files=None,
|
|
678
|
+
)
|
|
634
679
|
|
|
635
680
|
# ── 6b. Symptom keyword boost + related notes (fix-bug + --symptom) ──
|
|
636
681
|
symptom_keywords: list[str] = []
|
|
637
682
|
related_notes: list[dict] = []
|
|
683
|
+
symptom_note: Optional[str] = None
|
|
638
684
|
if task_name == "fix-bug" and symptom:
|
|
639
685
|
import re as _re
|
|
640
686
|
_camel_expanded = _re.sub(r'([a-z])([A-Z])', r'\1 \2', symptom)
|
|
@@ -708,6 +754,41 @@ class TaskContextBuilder:
|
|
|
708
754
|
))
|
|
709
755
|
relevant_files = sorted(_content_boosted, key=lambda rf: -rf.score)
|
|
710
756
|
|
|
757
|
+
# Cross-layer synonym boost: frontend keywords → backend equivalents
|
|
758
|
+
_synonym_note: Optional[str] = None
|
|
759
|
+
_frontend_kws = [kw for kw in symptom_keywords if kw in _FRONTEND_SYMPTOM_MAP]
|
|
760
|
+
if _frontend_kws:
|
|
761
|
+
_backend_terms: list[str] = []
|
|
762
|
+
for _fkw in _frontend_kws:
|
|
763
|
+
_backend_terms.extend(_FRONTEND_SYMPTOM_MAP[_fkw])
|
|
764
|
+
_backend_terms_set = list(dict.fromkeys(_backend_terms)) # dedup, preserve order
|
|
765
|
+
_synonym_boosted: list[RelevantFile] = []
|
|
766
|
+
for _rf in relevant_files:
|
|
767
|
+
_extra_syn = 0.0
|
|
768
|
+
if Path(_rf.path).suffix.lower() in _src_exts:
|
|
769
|
+
try:
|
|
770
|
+
_lines_syn = (self.root / _rf.path).read_text(
|
|
771
|
+
encoding="utf-8", errors="replace"
|
|
772
|
+
).splitlines()[:300]
|
|
773
|
+
_body_syn = "\n".join(_lines_syn).lower()
|
|
774
|
+
_hits_syn = sum(_body_syn.count(t) for t in _backend_terms_set)
|
|
775
|
+
_extra_syn = min(0.20, _hits_syn * 0.02)
|
|
776
|
+
except OSError:
|
|
777
|
+
pass
|
|
778
|
+
_synonym_boosted.append(RelevantFile(
|
|
779
|
+
path=_rf.path,
|
|
780
|
+
role=_rf.role,
|
|
781
|
+
score=round(min(_rf.score + _extra_syn, 1.0), 2),
|
|
782
|
+
reason=_rf.reason + (f", synonym-match backend (+{_extra_syn:.2f})" if _extra_syn > 0 else ""),
|
|
783
|
+
why=_rf.why,
|
|
784
|
+
))
|
|
785
|
+
relevant_files = sorted(_synonym_boosted, key=lambda rf: -rf.score)
|
|
786
|
+
_synonym_note = (
|
|
787
|
+
f"Frontend concept detected ({', '.join(_frontend_kws)}). "
|
|
788
|
+
"Boosted backend service-layer and interceptor files as likely root cause."
|
|
789
|
+
)
|
|
790
|
+
symptom_note = _synonym_note
|
|
791
|
+
|
|
711
792
|
# ── 7. Test gaps (generate-tests only) ────────────────────────────
|
|
712
793
|
test_gaps: list[str] = []
|
|
713
794
|
if task_name == "generate-tests":
|
|
@@ -752,22 +833,30 @@ class TaskContextBuilder:
|
|
|
752
833
|
|
|
753
834
|
conf_summary, analysis_gaps = ConfidenceAnalyzer().analyze(sm_for_conf)
|
|
754
835
|
confidence = conf_summary.overall
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
836
|
+
if task_name == "delta":
|
|
837
|
+
# Use delta-specific gaps; ConfidenceAnalyzer gaps are about full-repo
|
|
838
|
+
# detection quality and are not meaningful for an incremental diff.
|
|
839
|
+
gaps = _delta_analysis_gaps
|
|
840
|
+
if _mybatis_warning:
|
|
841
|
+
gaps.append(_mybatis_warning["reason"])
|
|
842
|
+
else:
|
|
843
|
+
gaps = [g.reason for g in analysis_gaps]
|
|
844
|
+
if _mybatis_warning:
|
|
845
|
+
gaps.append(_mybatis_warning["reason"])
|
|
758
846
|
|
|
759
847
|
# ── 9. why_these_files ────────────────────────────────────────────────
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
848
|
+
if task_name == "delta":
|
|
849
|
+
why_these_files = _delta_why
|
|
850
|
+
else:
|
|
851
|
+
why_these_files = {rf.path: rf.reason for rf in relevant_files}
|
|
763
852
|
|
|
764
|
-
# ── 10. Delta: git changed files
|
|
853
|
+
# ── 10. Delta: git changed files + entry points ───────────────────────
|
|
765
854
|
changed_files: list[str] = []
|
|
766
855
|
affected_entry_points: list[str] = []
|
|
767
856
|
if task_name == "delta":
|
|
768
857
|
changed_files = sorted(_delta_files) if _delta_files else self._get_git_changed_files(since=since)
|
|
769
|
-
|
|
770
|
-
affected_entry_points = [f for f in changed_files if f in
|
|
858
|
+
_ep_set = {ep.path for ep in entry_points}
|
|
859
|
+
affected_entry_points = [f for f in changed_files if f in _ep_set]
|
|
771
860
|
|
|
772
861
|
return TaskOutput(
|
|
773
862
|
task=task_name,
|
|
@@ -788,6 +877,11 @@ class TaskContextBuilder:
|
|
|
788
877
|
affected_entry_points=affected_entry_points,
|
|
789
878
|
symptom=symptom if task_name == "fix-bug" and symptom else None,
|
|
790
879
|
related_notes=related_notes,
|
|
880
|
+
symptom_note=symptom_note,
|
|
881
|
+
impact_summary=_delta_impact_summary,
|
|
882
|
+
affected_modules=_delta_affected_modules,
|
|
883
|
+
risk_areas=_delta_risk_areas,
|
|
884
|
+
since=since if task_name == "delta" else None,
|
|
791
885
|
)
|
|
792
886
|
|
|
793
887
|
def render_prompt(self, output: TaskOutput) -> str:
|
|
@@ -1079,6 +1173,384 @@ class TaskContextBuilder:
|
|
|
1079
1173
|
def _is_source(self, path: str) -> bool:
|
|
1080
1174
|
return Path(path).suffix.lower() in _SOURCE_EXTENSIONS
|
|
1081
1175
|
|
|
1176
|
+
# ── Delta impact analysis ─────────────────────────────────────────────────
|
|
1177
|
+
|
|
1178
|
+
@staticmethod
|
|
1179
|
+
def _classify_changed_file(path: str) -> dict[str, Any]:
|
|
1180
|
+
"""Classify a changed file by artifact type, risk areas, and impact level.
|
|
1181
|
+
|
|
1182
|
+
Returns dict: artifact_type, risk_areas, impact_level, is_noise, module.
|
|
1183
|
+
Pure path/name heuristics — no file reads, fully deterministic.
|
|
1184
|
+
"""
|
|
1185
|
+
norm = path.replace("\\", "/")
|
|
1186
|
+
name = Path(path).name
|
|
1187
|
+
stem = Path(path).stem
|
|
1188
|
+
suffix = Path(path).suffix.lower()
|
|
1189
|
+
norm_lower = norm.lower()
|
|
1190
|
+
stem_lower = stem.lower()
|
|
1191
|
+
name_lower = name.lower()
|
|
1192
|
+
|
|
1193
|
+
_CODE_EXTS = frozenset({
|
|
1194
|
+
".py", ".js", ".ts", ".tsx", ".jsx", ".java", ".kt", ".go",
|
|
1195
|
+
".rs", ".rb", ".php", ".cs", ".dart", ".mjs", ".cjs", ".scala",
|
|
1196
|
+
})
|
|
1197
|
+
_CONFIG_EXTS = frozenset({
|
|
1198
|
+
".yml", ".yaml", ".json", ".xml", ".toml", ".properties",
|
|
1199
|
+
".env", ".cfg", ".ini", ".conf",
|
|
1200
|
+
})
|
|
1201
|
+
|
|
1202
|
+
# IDE/hidden-tool directories → noise, skip impact analysis
|
|
1203
|
+
_IDE_DIR_NAMES = frozenset({
|
|
1204
|
+
".idea", ".vscode", ".eclipse", ".fleet", ".git", ".github",
|
|
1205
|
+
".circleci", ".travis", ".teamcity", ".gradle", ".mvn",
|
|
1206
|
+
})
|
|
1207
|
+
path_dir_parts = norm_lower.split("/")[:-1] # all components except filename
|
|
1208
|
+
if any(part in _IDE_DIR_NAMES for part in path_dir_parts):
|
|
1209
|
+
return {
|
|
1210
|
+
"artifact_type": "ide_noise",
|
|
1211
|
+
"risk_areas": [],
|
|
1212
|
+
"impact_level": "noise",
|
|
1213
|
+
"is_noise": True,
|
|
1214
|
+
"module": "",
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
module = _extract_ddd_domain(path)
|
|
1218
|
+
|
|
1219
|
+
# Tests (before other checks to avoid misclassifying TestFoo as service etc.)
|
|
1220
|
+
_is_test = (
|
|
1221
|
+
(stem_lower.startswith("test") and len(stem_lower) > 4)
|
|
1222
|
+
or (stem_lower.endswith("test") and len(stem_lower) > 4)
|
|
1223
|
+
or stem_lower.endswith("tests")
|
|
1224
|
+
or stem_lower.endswith("spec")
|
|
1225
|
+
or any(t in f"/{norm_lower}/" for t in (
|
|
1226
|
+
"/test/", "/tests/", "/spec/", "/specs/", "/__tests__/", "/it/",
|
|
1227
|
+
))
|
|
1228
|
+
)
|
|
1229
|
+
if _is_test:
|
|
1230
|
+
return {"artifact_type": "test", "risk_areas": ["tests"], "impact_level": "low", "is_noise": False, "module": module}
|
|
1231
|
+
|
|
1232
|
+
# Security surface
|
|
1233
|
+
_SECURITY_KW = ("security", "auth", "jwt", "token", "permission", "role",
|
|
1234
|
+
"credential", "encrypt", "decrypt", "oauth", "saml", "ldap",
|
|
1235
|
+
"password", "secret")
|
|
1236
|
+
if suffix in _CODE_EXTS and any(kw in stem_lower for kw in _SECURITY_KW):
|
|
1237
|
+
impact = "critical" if any(kw in stem_lower for kw in ("security", "auth", "jwt")) else "high"
|
|
1238
|
+
return {"artifact_type": "security", "risk_areas": ["security"], "impact_level": impact, "is_noise": False, "module": module}
|
|
1239
|
+
|
|
1240
|
+
# API / controller layer
|
|
1241
|
+
_API_KW = ("controller", "restcontroller", "resource", "handler",
|
|
1242
|
+
"router", "route", "endpoint", "servlet")
|
|
1243
|
+
if suffix in _CODE_EXTS and any(kw in stem_lower for kw in _API_KW):
|
|
1244
|
+
return {"artifact_type": "api_endpoint", "risk_areas": ["api"], "impact_level": "high", "is_noise": False, "module": module}
|
|
1245
|
+
|
|
1246
|
+
# Business logic / services
|
|
1247
|
+
if suffix in _CODE_EXTS and "service" in stem_lower:
|
|
1248
|
+
return {"artifact_type": "business_logic", "risk_areas": ["transactions", "business_logic"], "impact_level": "high", "is_noise": False, "module": module}
|
|
1249
|
+
|
|
1250
|
+
# Data access
|
|
1251
|
+
_DAO_KW = ("repository", "repositoryimpl", "dao", "daoimpl", "store")
|
|
1252
|
+
if suffix in _CODE_EXTS and any(kw in stem_lower for kw in _DAO_KW):
|
|
1253
|
+
return {"artifact_type": "data_access", "risk_areas": ["persistence"], "impact_level": "high", "is_noise": False, "module": module}
|
|
1254
|
+
if "mapper" in stem_lower:
|
|
1255
|
+
atype = "mybatis_mapper" if suffix == ".xml" else "data_access"
|
|
1256
|
+
return {"artifact_type": atype, "risk_areas": ["persistence"], "impact_level": "high", "is_noise": False, "module": module}
|
|
1257
|
+
|
|
1258
|
+
# Spring / app config files (by canonical name)
|
|
1259
|
+
if name_lower in ("application.yml", "application.yaml", "application.properties",
|
|
1260
|
+
"bootstrap.yml", "bootstrap.yaml", "bootstrap.properties"):
|
|
1261
|
+
return {"artifact_type": "spring_config", "risk_areas": ["config"], "impact_level": "high", "is_noise": False, "module": module}
|
|
1262
|
+
if name_lower.startswith("application-") and suffix in (".yml", ".yaml", ".properties"):
|
|
1263
|
+
return {"artifact_type": "spring_profile", "risk_areas": ["config"], "impact_level": "medium", "is_noise": False, "module": module}
|
|
1264
|
+
if name_lower in ("pom.xml", "build.gradle", "build.gradle.kts",
|
|
1265
|
+
"settings.gradle", "settings.gradle.kts"):
|
|
1266
|
+
return {"artifact_type": "build_manifest", "risk_areas": ["config", "dependencies"], "impact_level": "medium", "is_noise": False, "module": module}
|
|
1267
|
+
|
|
1268
|
+
# Configuration classes / files
|
|
1269
|
+
_CONFIG_STEM_KW = ("config", "configuration", "properties", "settings")
|
|
1270
|
+
if suffix in _CODE_EXTS and any(kw in stem_lower for kw in _CONFIG_STEM_KW):
|
|
1271
|
+
return {"artifact_type": "configuration", "risk_areas": ["config"], "impact_level": "medium", "is_noise": False, "module": module}
|
|
1272
|
+
|
|
1273
|
+
# DB migrations / SQL
|
|
1274
|
+
if suffix == ".sql" or any(kw in norm_lower for kw in ("migration", "flyway", "liquibase", "changelog")):
|
|
1275
|
+
return {"artifact_type": "db_migration", "risk_areas": ["persistence"], "impact_level": "high", "is_noise": False, "module": module}
|
|
1276
|
+
|
|
1277
|
+
# Domain models / entities
|
|
1278
|
+
_ENTITY_KW = ("entity", "model", "domain", "aggregate", "valueobject")
|
|
1279
|
+
if suffix in _CODE_EXTS and any(kw in stem_lower for kw in _ENTITY_KW):
|
|
1280
|
+
return {"artifact_type": "domain_model", "risk_areas": ["persistence"], "impact_level": "medium", "is_noise": False, "module": module}
|
|
1281
|
+
|
|
1282
|
+
# DTOs / request-response objects
|
|
1283
|
+
_DTO_KW = ("dto", "request", "response", "payload", "command", "query", "event")
|
|
1284
|
+
if suffix in _CODE_EXTS and any(kw in stem_lower for kw in _DTO_KW):
|
|
1285
|
+
return {"artifact_type": "dto", "risk_areas": [], "impact_level": "low", "is_noise": False, "module": module}
|
|
1286
|
+
|
|
1287
|
+
# Generic source code
|
|
1288
|
+
if suffix in _CODE_EXTS:
|
|
1289
|
+
return {"artifact_type": "unknown_source", "risk_areas": [], "impact_level": "medium", "is_noise": False, "module": module}
|
|
1290
|
+
|
|
1291
|
+
# Config / data files
|
|
1292
|
+
if suffix in _CONFIG_EXTS:
|
|
1293
|
+
return {"artifact_type": "unknown_config", "risk_areas": ["config"], "impact_level": "low", "is_noise": False, "module": module}
|
|
1294
|
+
|
|
1295
|
+
# Docs
|
|
1296
|
+
if suffix in (".md", ".rst", ".txt", ".adoc"):
|
|
1297
|
+
return {"artifact_type": "documentation", "risk_areas": [], "impact_level": "low", "is_noise": False, "module": module}
|
|
1298
|
+
|
|
1299
|
+
return {"artifact_type": "binary_or_unknown", "risk_areas": [], "impact_level": "noise", "is_noise": True, "module": module}
|
|
1300
|
+
|
|
1301
|
+
def _build_delta_impact(
|
|
1302
|
+
self,
|
|
1303
|
+
changed_files: list[str],
|
|
1304
|
+
all_paths: list[str],
|
|
1305
|
+
entry_points: list,
|
|
1306
|
+
since: Optional[str],
|
|
1307
|
+
) -> tuple[list[RelevantFile], str, list[str], list[dict[str, Any]], dict[str, str], list[str]]:
|
|
1308
|
+
"""Build incremental impact analysis for changed files.
|
|
1309
|
+
|
|
1310
|
+
Returns:
|
|
1311
|
+
(relevant_files, impact_summary, affected_modules, risk_areas,
|
|
1312
|
+
why_these_files, analysis_gaps)
|
|
1313
|
+
|
|
1314
|
+
Changed files are always included in relevant_files (never dropped by score).
|
|
1315
|
+
Related files from the same module/directory are appended with lower scores.
|
|
1316
|
+
"""
|
|
1317
|
+
_IMPACT_SCORE: dict[str, float] = {
|
|
1318
|
+
"critical": 1.00,
|
|
1319
|
+
"high": 0.85,
|
|
1320
|
+
"medium": 0.65,
|
|
1321
|
+
"low": 0.40,
|
|
1322
|
+
"noise": 0.10,
|
|
1323
|
+
}
|
|
1324
|
+
_SEV_ORDER = ["noise", "low", "medium", "high", "critical"]
|
|
1325
|
+
|
|
1326
|
+
if not changed_files:
|
|
1327
|
+
return (
|
|
1328
|
+
[],
|
|
1329
|
+
"No changes detected — verify the git ref passed to --since",
|
|
1330
|
+
[],
|
|
1331
|
+
[],
|
|
1332
|
+
{},
|
|
1333
|
+
["No changed files found. Check that --since ref exists and the diff is non-empty."],
|
|
1334
|
+
)
|
|
1335
|
+
|
|
1336
|
+
ep_paths = {ep.path for ep in entry_points}
|
|
1337
|
+
|
|
1338
|
+
# ── Step 1: classify every changed file ───────────────────────────────
|
|
1339
|
+
classifications: dict[str, dict[str, Any]] = {
|
|
1340
|
+
f: self._classify_changed_file(f) for f in changed_files
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
# ── Step 2: build relevant_files from the changed set ─────────────────
|
|
1344
|
+
relevant: list[RelevantFile] = []
|
|
1345
|
+
why: dict[str, str] = {}
|
|
1346
|
+
affected_modules_set: set[str] = set()
|
|
1347
|
+
changed_dirs: set[str] = set()
|
|
1348
|
+
risk_acc: dict[str, dict[str, Any]] = {} # area → {files, severity}
|
|
1349
|
+
ref_label = since or "HEAD~1"
|
|
1350
|
+
|
|
1351
|
+
for path, cls in classifications.items():
|
|
1352
|
+
score = _IMPACT_SCORE.get(cls["impact_level"], 0.50)
|
|
1353
|
+
module = cls["module"]
|
|
1354
|
+
|
|
1355
|
+
if module:
|
|
1356
|
+
affected_modules_set.add(module)
|
|
1357
|
+
if not cls["is_noise"]:
|
|
1358
|
+
parent = str(Path(path).parent).replace("\\", "/")
|
|
1359
|
+
if parent and parent != ".":
|
|
1360
|
+
changed_dirs.add(parent)
|
|
1361
|
+
|
|
1362
|
+
for area in cls["risk_areas"]:
|
|
1363
|
+
if area not in risk_acc:
|
|
1364
|
+
risk_acc[area] = {"files": [], "severity": "noise"}
|
|
1365
|
+
risk_acc[area]["files"].append(path)
|
|
1366
|
+
cur_idx = _SEV_ORDER.index(risk_acc[area]["severity"])
|
|
1367
|
+
new_idx = _SEV_ORDER.index(cls["impact_level"])
|
|
1368
|
+
if new_idx > cur_idx:
|
|
1369
|
+
risk_acc[area]["severity"] = cls["impact_level"]
|
|
1370
|
+
|
|
1371
|
+
artifact_display = cls["artifact_type"].replace("_", " ")
|
|
1372
|
+
reason_parts = [f"changed since {ref_label}", f"artifact: {cls['artifact_type']}"]
|
|
1373
|
+
if cls["risk_areas"]:
|
|
1374
|
+
reason_parts.append(f"risk: {', '.join(cls['risk_areas'])}")
|
|
1375
|
+
reason = ", ".join(reason_parts)
|
|
1376
|
+
|
|
1377
|
+
why_parts = [f"Changed {artifact_display}"]
|
|
1378
|
+
if module:
|
|
1379
|
+
why_parts.append(f"in module '{module}'")
|
|
1380
|
+
if cls["risk_areas"]:
|
|
1381
|
+
why_parts.append(f"Risk: {', '.join(cls['risk_areas'])}")
|
|
1382
|
+
why_str = ". ".join(why_parts) + "."
|
|
1383
|
+
|
|
1384
|
+
role = "entrypoint" if path in ep_paths else ("source" if not cls["is_noise"] else "noise")
|
|
1385
|
+
relevant.append(RelevantFile(path=path, role=role, score=round(score, 2), reason=reason, why=why_str))
|
|
1386
|
+
why[path] = why_str
|
|
1387
|
+
|
|
1388
|
+
relevant.sort(key=lambda f: (-f.score, f.path))
|
|
1389
|
+
|
|
1390
|
+
# ── Step 3: expand to related files (same module or same directory) ───
|
|
1391
|
+
existing_paths = {rf.path for rf in relevant}
|
|
1392
|
+
|
|
1393
|
+
_HIGH_IMPACT_STEMS = frozenset({
|
|
1394
|
+
"controller", "restcontroller", "resource", "handler",
|
|
1395
|
+
"service", "serviceimpl", "servicefacade",
|
|
1396
|
+
"repository", "repositoryimpl", "dao", "daoimpl",
|
|
1397
|
+
"mapper", "security", "securityconfig",
|
|
1398
|
+
"config", "configuration", "filter", "authcontroller",
|
|
1399
|
+
})
|
|
1400
|
+
|
|
1401
|
+
related: list[tuple[float, str, RelevantFile]] = []
|
|
1402
|
+
for path in all_paths:
|
|
1403
|
+
if path in existing_paths:
|
|
1404
|
+
continue
|
|
1405
|
+
suffix = Path(path).suffix.lower()
|
|
1406
|
+
if suffix not in _ALL_EXTENSIONS:
|
|
1407
|
+
continue
|
|
1408
|
+
stem_lower = Path(path).stem.lower()
|
|
1409
|
+
if not any(s in stem_lower for s in _HIGH_IMPACT_STEMS):
|
|
1410
|
+
continue
|
|
1411
|
+
|
|
1412
|
+
parent = str(Path(path).parent).replace("\\", "/")
|
|
1413
|
+
path_module = _extract_ddd_domain(path)
|
|
1414
|
+
|
|
1415
|
+
in_same_module = bool(path_module and path_module in affected_modules_set)
|
|
1416
|
+
in_same_dir = parent in changed_dirs
|
|
1417
|
+
|
|
1418
|
+
if not (in_same_module or in_same_dir):
|
|
1419
|
+
continue
|
|
1420
|
+
|
|
1421
|
+
rel_cls = self._classify_changed_file(path)
|
|
1422
|
+
if rel_cls["is_noise"]:
|
|
1423
|
+
continue
|
|
1424
|
+
|
|
1425
|
+
rel_score = _IMPACT_SCORE.get(rel_cls["impact_level"], 0.50) * 0.50
|
|
1426
|
+
ctx_type = "module" if in_same_module else "directory"
|
|
1427
|
+
ctx_val = path_module if in_same_module else parent
|
|
1428
|
+
|
|
1429
|
+
triggers = [
|
|
1430
|
+
Path(f).name for f in changed_files
|
|
1431
|
+
if (
|
|
1432
|
+
(_extract_ddd_domain(f) == path_module if in_same_module
|
|
1433
|
+
else str(Path(f).parent).replace("\\", "/") == parent)
|
|
1434
|
+
)
|
|
1435
|
+
]
|
|
1436
|
+
reason = f"related: same {ctx_type} '{ctx_val}', artifact: {rel_cls['artifact_type']}"
|
|
1437
|
+
why_str = (
|
|
1438
|
+
f"In changed {ctx_type} '{ctx_val}'. "
|
|
1439
|
+
f"May be affected by: {', '.join(triggers[:3])}"
|
|
1440
|
+
)
|
|
1441
|
+
role = "entrypoint" if path in ep_paths else "source"
|
|
1442
|
+
related.append((rel_score, path, RelevantFile(
|
|
1443
|
+
path=path, role=role, score=round(rel_score, 2), reason=reason, why=why_str
|
|
1444
|
+
)))
|
|
1445
|
+
why[path] = why_str
|
|
1446
|
+
|
|
1447
|
+
related.sort(key=lambda x: (-x[0], x[1]))
|
|
1448
|
+
relevant.extend(rf for _, _, rf in related[:10])
|
|
1449
|
+
|
|
1450
|
+
# ── Step 4: impact summary ─────────────────────────────────────────────
|
|
1451
|
+
type_counts: dict[str, int] = {}
|
|
1452
|
+
all_risk_areas: set[str] = set()
|
|
1453
|
+
noise_count = 0
|
|
1454
|
+
for cls in classifications.values():
|
|
1455
|
+
t = cls["artifact_type"]
|
|
1456
|
+
type_counts[t] = type_counts.get(t, 0) + 1
|
|
1457
|
+
all_risk_areas.update(cls["risk_areas"])
|
|
1458
|
+
if cls["is_noise"]:
|
|
1459
|
+
noise_count += 1
|
|
1460
|
+
meaningful = len(changed_files) - noise_count
|
|
1461
|
+
|
|
1462
|
+
_SUMMARY_LABELS: dict[str, str] = {
|
|
1463
|
+
"security": "security file(s)",
|
|
1464
|
+
"api_endpoint": "API endpoint(s)",
|
|
1465
|
+
"business_logic": "service(s)",
|
|
1466
|
+
"data_access": "data access file(s)",
|
|
1467
|
+
"mybatis_mapper": "MyBatis mapper(s)",
|
|
1468
|
+
"spring_config": "Spring config file(s)",
|
|
1469
|
+
"spring_profile": "Spring profile config(s)",
|
|
1470
|
+
"configuration": "configuration file(s)",
|
|
1471
|
+
"build_manifest": "build manifest(s)",
|
|
1472
|
+
"db_migration": "database migration(s)",
|
|
1473
|
+
"domain_model": "domain model(s)",
|
|
1474
|
+
"dto": "DTO(s)",
|
|
1475
|
+
"test": "test file(s)",
|
|
1476
|
+
"unknown_source": "source file(s)",
|
|
1477
|
+
"unknown_config": "config file(s)",
|
|
1478
|
+
"documentation": "documentation file(s)",
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
if meaningful == 0:
|
|
1482
|
+
impact_summary = (
|
|
1483
|
+
f"{noise_count} IDE/tooling file(s) changed"
|
|
1484
|
+
" — no semantic impact on application logic"
|
|
1485
|
+
)
|
|
1486
|
+
else:
|
|
1487
|
+
_sev_rank = {"critical": 4, "high": 3, "medium": 2, "low": 1, "noise": 0}
|
|
1488
|
+
parts = []
|
|
1489
|
+
for atype, count in sorted(
|
|
1490
|
+
type_counts.items(),
|
|
1491
|
+
key=lambda kv: -_sev_rank.get(
|
|
1492
|
+
classifications[next(
|
|
1493
|
+
(f for f in changed_files if classifications[f]["artifact_type"] == kv[0]),
|
|
1494
|
+
changed_files[0],
|
|
1495
|
+
)]["impact_level"], 0,
|
|
1496
|
+
),
|
|
1497
|
+
):
|
|
1498
|
+
if atype in ("ide_noise", "binary_or_unknown"):
|
|
1499
|
+
continue
|
|
1500
|
+
label = _SUMMARY_LABELS.get(atype, f"source file(s) ({atype})")
|
|
1501
|
+
parts.append(f"{count} {label}")
|
|
1502
|
+
impact_summary = "; ".join(parts) if parts else f"{meaningful} source file(s) changed"
|
|
1503
|
+
if all_risk_areas:
|
|
1504
|
+
impact_summary += f" — risk areas: {', '.join(sorted(all_risk_areas))}"
|
|
1505
|
+
if noise_count > 0:
|
|
1506
|
+
impact_summary += f" ({noise_count} IDE/tooling file(s) excluded)"
|
|
1507
|
+
|
|
1508
|
+
# ── Step 5: risk_areas output list ─────────────────────────────────────
|
|
1509
|
+
risk_areas_out: list[dict[str, Any]] = sorted(
|
|
1510
|
+
[
|
|
1511
|
+
{
|
|
1512
|
+
"area": area,
|
|
1513
|
+
"severity": info["severity"],
|
|
1514
|
+
"affected_files": sorted(info["files"])[:5],
|
|
1515
|
+
}
|
|
1516
|
+
for area, info in risk_acc.items()
|
|
1517
|
+
],
|
|
1518
|
+
key=lambda x: (-_SEV_ORDER.index(x["severity"]), x["area"]),
|
|
1519
|
+
)
|
|
1520
|
+
|
|
1521
|
+
# ── Step 6: analysis gaps ──────────────────────────────────────────────
|
|
1522
|
+
analysis_gaps: list[str] = [
|
|
1523
|
+
"Related file expansion uses module/package and directory heuristics — import graph not traced",
|
|
1524
|
+
]
|
|
1525
|
+
if noise_count > 0 and meaningful > 0:
|
|
1526
|
+
analysis_gaps.append(
|
|
1527
|
+
f"{noise_count} IDE/tooling file(s) in diff excluded from impact analysis"
|
|
1528
|
+
)
|
|
1529
|
+
elif noise_count > 0 and meaningful == 0:
|
|
1530
|
+
analysis_gaps.append(
|
|
1531
|
+
"All changed files are IDE/tooling — no actionable semantic impact detected"
|
|
1532
|
+
)
|
|
1533
|
+
unknown_sources = [f for f, cls in classifications.items() if cls["artifact_type"] == "unknown_source"]
|
|
1534
|
+
if unknown_sources:
|
|
1535
|
+
analysis_gaps.append(
|
|
1536
|
+
f"{len(unknown_sources)} source file(s) could not be classified by artifact type: "
|
|
1537
|
+
+ ", ".join(Path(f).name for f in unknown_sources[:3])
|
|
1538
|
+
)
|
|
1539
|
+
if not affected_modules_set and any(not cls["is_noise"] for cls in classifications.values()):
|
|
1540
|
+
analysis_gaps.append(
|
|
1541
|
+
"DDD module/package structure not detected in changed paths"
|
|
1542
|
+
" — related file expansion uses directory proximity only"
|
|
1543
|
+
)
|
|
1544
|
+
|
|
1545
|
+
return (
|
|
1546
|
+
relevant,
|
|
1547
|
+
impact_summary,
|
|
1548
|
+
sorted(affected_modules_set),
|
|
1549
|
+
risk_areas_out,
|
|
1550
|
+
why,
|
|
1551
|
+
analysis_gaps,
|
|
1552
|
+
)
|
|
1553
|
+
|
|
1082
1554
|
def _get_git_changed_files(self, since: Optional[str] = None) -> list[str]:
|
|
1083
1555
|
"""Get files changed since a git ref (default: HEAD~1) relative to self.root.
|
|
1084
1556
|
|