sourcecode 1.31.12__tar.gz → 1.31.14__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sourcecode-1.31.12 → sourcecode-1.31.14}/PKG-INFO +3 -3
- {sourcecode-1.31.12 → sourcecode-1.31.14}/README.md +2 -2
- {sourcecode-1.31.12 → sourcecode-1.31.14}/pyproject.toml +1 -1
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/__init__.py +1 -1
- sourcecode-1.31.14/src/sourcecode/canonical_ir.py +583 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/cli.py +233 -4
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/server.py +44 -4
- sourcecode-1.31.14/src/sourcecode/output_budget.py +157 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/repository_ir.py +516 -58
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/serializer.py +41 -4
- sourcecode-1.31.14/tests/test_audit_sas_v2.py +506 -0
- sourcecode-1.31.14/tests/test_canonical_ir.py +893 -0
- sourcecode-1.31.14/tests/test_enterprise_benchmarks.py +967 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_repository_ir.py +327 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_v1_10_regressions.py +119 -7
- sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-0911b79e.json +0 -3825
- sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-37df4554.json +0 -266
- sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-624321f3.json +0 -12478
- sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-776b4676.json +0 -386
- sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-9770fba7.json +0 -377
- sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-c4e3c102.json +0 -266
- sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-e8bc5fb4.json +0 -122
- sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-e9801942.json +0 -223
- sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-ee60e0cd.json +0 -322
- sourcecode-1.31.12/.sourcecode-cache/snapshot-0778d0a-fdd9d3f7.json +0 -552
- sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-27b713ac.json +0 -386
- sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-28e558ac.json +0 -325
- sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-2d7eca5f.json +0 -12577
- sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-7cd91058.json +0 -226
- sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-8ea5b5bb.json +0 -380
- sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-bef1a526.json +0 -261
- sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-c69bcdc8.json +0 -122
- sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-d2dfc690.json +0 -3825
- sourcecode-1.31.12/.sourcecode-cache/snapshot-3b5997a-dc30daab.json +0 -552
- sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-20354ec3.json +0 -261
- sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-2c874f8c.json +0 -122
- sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-6c8157db.json +0 -226
- sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-93090ce0.json +0 -129
- sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-ce79b4bf.json +0 -3846
- sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-cf0bfa7a.json +0 -325
- sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-e73c6323.json +0 -261
- sourcecode-1.31.12/.sourcecode-cache/snapshot-7571a6f-ee60e0cd.json +0 -325
- {sourcecode-1.31.12 → sourcecode-1.31.14}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/.continue-here.md +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/.gitignore +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/.ruff.toml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/.sourcecode-cache/snapshot-3b5997a-fa5c742c.json +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/CHANGELOG.md +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/CONTRIBUTING.md +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/LICENSE +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/SECURITY.md +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/docs/privacy.md +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/docs/schema.md +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/raw +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/run_cli.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/__init__.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/conftest.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/latin1_sample.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/latin1_sample_iso.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/lcov.info +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_architecture_summary.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_ast_extractor.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_audit_fixes.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_block1_reliability.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_block2_coverage.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_block5_quality.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_broadleaf_fixes.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_bug_fixes_v1302.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_bug_fixes_v1312.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_bug_fixes_v1313.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_bug_fixes_v16.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_bug_fixes_v2.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_classifier.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_cli.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_context_scorer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_contract_pipeline.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_coverage_parser.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_cross_consistency.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_dependency_schema.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_python.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_detectors_base.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_encoding_regression.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_graph_schema.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_detection.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_docs.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_lqn.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_metrics.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_multistack.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_semantics.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_integration_universal.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_java_spring_integration.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_mcp_runner.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_mcp_serve.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_mcp_tools.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_output_ux.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_packaging.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_pipeline_integrity.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_real_projects.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_redactor.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_scanner.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_schema.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_schema_normalization.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_scoring_calibration.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_semantic_schema.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_signal_hierarchy.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_summarizer.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_surface_honesty.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_task_differentiation.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_telemetry.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_v131_improvements.py +0 -0
- {sourcecode-1.31.12 → sourcecode-1.31.14}/tests/test_workspace_analyzer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.31.
|
|
3
|
+
Version: 1.31.14
|
|
4
4
|
Summary: Deterministic codebase context for AI coding agents
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -225,7 +225,7 @@ Description-Content-Type: text/markdown
|
|
|
225
225
|
|
|
226
226
|
**Deterministic, behavior-aware codebase context for AI agents and PR review.**
|
|
227
227
|
|
|
228
|
-

|
|
229
229
|

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

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -38,7 +38,7 @@ pipx install sourcecode
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
sourcecode version
|
|
41
|
-
# sourcecode 1.31.
|
|
41
|
+
# sourcecode 1.31.14
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
---
|
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
"""canonical_ir.py — Canonical Repository IR contract (single source of truth).
|
|
2
|
+
|
|
3
|
+
Architecture:
|
|
4
|
+
build_canonical_ir() → CanonicalRepositoryIR
|
|
5
|
+
project_route_surface() → derives route_surface list from CIR
|
|
6
|
+
project_endpoint_surface() → derives endpoint surface dict from CIR
|
|
7
|
+
project_blast_radius() → derives blast-radius dict from CIR
|
|
8
|
+
validate_canonical_ir() → invariant checker; returns violation list
|
|
9
|
+
|
|
10
|
+
All external projections derive exclusively from CanonicalRepositoryIR.
|
|
11
|
+
No view reconstructs endpoint, security, or blast-radius data independently.
|
|
12
|
+
|
|
13
|
+
IR schema version: 1.0.0
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Optional
|
|
22
|
+
|
|
23
|
+
from sourcecode.repository_ir import (
|
|
24
|
+
build_repo_ir,
|
|
25
|
+
compute_blast_radius as _compute_blast_radius,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Schema version — single constant, embedded in every CIR
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
IR_SCHEMA_VERSION = "1.0.0"
|
|
33
|
+
|
|
34
|
+
# Edge types excluded from reverse graph (mirrors repository_ir._REVERSE_EXCLUDE)
|
|
35
|
+
_REVERSE_EXCLUDE: frozenset[str] = frozenset({"annotated_with", "mapped_to"})
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# CanonicalSecurity
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class CanonicalSecurity:
|
|
44
|
+
"""Canonical security policy for an endpoint.
|
|
45
|
+
|
|
46
|
+
Single source of truth for authorization metadata.
|
|
47
|
+
Never reconstructed from annotations in independent code paths.
|
|
48
|
+
|
|
49
|
+
source_scope: where the annotation lives —
|
|
50
|
+
"method" annotation on the handler method
|
|
51
|
+
"class" annotation on the controller class (method has none)
|
|
52
|
+
"inherited" endpoint inherited from parent class
|
|
53
|
+
"""
|
|
54
|
+
policy: str # deny_all|permit_all|roles_allowed|authenticated|...
|
|
55
|
+
source_scope: str # method|class|inherited
|
|
56
|
+
effective_roles: list[str] = field(default_factory=list)
|
|
57
|
+
expression: str = "" # SpEL for @PreAuthorize/@PostAuthorize
|
|
58
|
+
required_permission: str = "" # for @M3FiltroSeguridad
|
|
59
|
+
raw: dict = field(default_factory=dict) # full original policy dict
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> dict:
|
|
62
|
+
"""Serialize for external consumption (omits internal fields)."""
|
|
63
|
+
out: dict = {"policy": self.policy}
|
|
64
|
+
if self.effective_roles:
|
|
65
|
+
out["roles"] = self.effective_roles
|
|
66
|
+
if self.expression:
|
|
67
|
+
out["expression"] = self.expression
|
|
68
|
+
if self.required_permission:
|
|
69
|
+
out["required_permission"] = self.required_permission
|
|
70
|
+
return out
|
|
71
|
+
|
|
72
|
+
def to_full_dict(self) -> dict:
|
|
73
|
+
"""Full serialization including source_scope (for CIR audit/debug)."""
|
|
74
|
+
out = self.to_dict()
|
|
75
|
+
out["source_scope"] = self.source_scope
|
|
76
|
+
return out
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_policy_dict(
|
|
80
|
+
cls, d: dict, *, source_scope: str = "method"
|
|
81
|
+
) -> "CanonicalSecurity":
|
|
82
|
+
"""Build from the policy dict emitted by _route_security_from_sym."""
|
|
83
|
+
return cls(
|
|
84
|
+
policy=d.get("policy", ""),
|
|
85
|
+
source_scope=source_scope,
|
|
86
|
+
effective_roles=list(d.get("roles", [])),
|
|
87
|
+
expression=d.get("expression", ""),
|
|
88
|
+
required_permission=d.get("required_permission", ""),
|
|
89
|
+
raw=dict(d),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# CanonicalEndpoint
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class CanonicalEndpoint:
|
|
99
|
+
"""Canonical endpoint entity — single source of truth for REST endpoint data.
|
|
100
|
+
|
|
101
|
+
id is deterministic: METHOD:path:controller_class:handler_symbol
|
|
102
|
+
Field names are stable and typed — never loose dicts with optional fields.
|
|
103
|
+
|
|
104
|
+
No independent reconstruction: always derived from route_surface in IR.
|
|
105
|
+
"""
|
|
106
|
+
id: str # METHOD:path:controller_fqn:handler_symbol
|
|
107
|
+
path: str
|
|
108
|
+
method: str
|
|
109
|
+
controller_class: str # FQN of controller class
|
|
110
|
+
handler_symbol: str # FQN of handler method: pkg.Class#method
|
|
111
|
+
security: Optional[CanonicalSecurity]
|
|
112
|
+
source_file: str
|
|
113
|
+
stable_id: str
|
|
114
|
+
inheritance_depth: int = 0
|
|
115
|
+
dependent_symbols: list[str] = field(default_factory=list) # lazy, filled by blast-radius
|
|
116
|
+
|
|
117
|
+
def to_dict(self) -> dict:
|
|
118
|
+
out: dict = {
|
|
119
|
+
"id": self.id,
|
|
120
|
+
"path": self.path,
|
|
121
|
+
"method": self.method,
|
|
122
|
+
"controller_class": self.controller_class,
|
|
123
|
+
"handler_symbol": self.handler_symbol,
|
|
124
|
+
"source_file": self.source_file,
|
|
125
|
+
"stable_id": self.stable_id,
|
|
126
|
+
"inheritance_depth": self.inheritance_depth,
|
|
127
|
+
}
|
|
128
|
+
if self.security is not None:
|
|
129
|
+
out["security"] = self.security.to_dict()
|
|
130
|
+
if self.dependent_symbols:
|
|
131
|
+
out["dependent_symbols"] = self.dependent_symbols
|
|
132
|
+
return out
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def make_id(
|
|
136
|
+
method: str, path: str, controller_class: str, handler_symbol: str
|
|
137
|
+
) -> str:
|
|
138
|
+
"""Deterministic endpoint ID — stable across formatting/body changes."""
|
|
139
|
+
return f"{method}:{path}:{controller_class}:{handler_symbol}"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
# CanonicalRepositoryIR
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class CanonicalRepositoryIR:
|
|
148
|
+
"""Canonical Repository IR — single source of truth for all code intelligence.
|
|
149
|
+
|
|
150
|
+
Projections that MUST derive from this structure:
|
|
151
|
+
project_route_surface() route_surface list
|
|
152
|
+
project_endpoint_surface() endpoint surface dict (replaces extract_java_endpoints)
|
|
153
|
+
project_blast_radius() blast-radius dict
|
|
154
|
+
|
|
155
|
+
No view is allowed to independently reconstruct endpoints, security, or
|
|
156
|
+
blast-radius data — all must project from cir.endpoints / cir.reverse_graph.
|
|
157
|
+
"""
|
|
158
|
+
schema_version: str # always IR_SCHEMA_VERSION
|
|
159
|
+
cir_hash: str # sha256 fingerprint
|
|
160
|
+
files: list[str] # sorted relative file paths
|
|
161
|
+
symbols: list[str] # sorted symbol FQNs
|
|
162
|
+
call_graph: list[dict] # sorted forward edges
|
|
163
|
+
reverse_graph: dict[str, dict[str, list[str]]] # target → {type → [callers]}
|
|
164
|
+
dependencies: list[dict] # import/extends/implements edges
|
|
165
|
+
endpoints: list[CanonicalEndpoint] # canonical endpoint list
|
|
166
|
+
security_index: dict[str, CanonicalSecurity] # handler_symbol → security
|
|
167
|
+
metadata: dict[str, Any] # stats, gaps, subsystems, etc.
|
|
168
|
+
# Raw IR dict retained for projections that need full IR fields
|
|
169
|
+
# (e.g. project_blast_radius delegates to compute_blast_radius)
|
|
170
|
+
_raw_ir: dict = field(default_factory=dict, repr=False, compare=False)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# Fingerprint
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
def _compute_cir_hash(
|
|
178
|
+
schema_version: str,
|
|
179
|
+
files: list[str],
|
|
180
|
+
symbols: list[str],
|
|
181
|
+
endpoints: list[CanonicalEndpoint],
|
|
182
|
+
call_graph: list[dict],
|
|
183
|
+
) -> str:
|
|
184
|
+
"""Compute deterministic sha256 fingerprint of canonical IR content.
|
|
185
|
+
|
|
186
|
+
Identical repo + schema_version → identical hash.
|
|
187
|
+
Changes on: any symbol/endpoint added or removed, schema version bump.
|
|
188
|
+
Stable across: formatting, comments, ordering differences.
|
|
189
|
+
"""
|
|
190
|
+
edge_keys = sorted(
|
|
191
|
+
f"{e.get('from', '')}:{e.get('type', '')}:{e.get('to', '')}"
|
|
192
|
+
for e in call_graph
|
|
193
|
+
)
|
|
194
|
+
endpoint_keys = sorted(ep.id for ep in endpoints)
|
|
195
|
+
|
|
196
|
+
content = {
|
|
197
|
+
"schema_version": schema_version,
|
|
198
|
+
"files": sorted(files),
|
|
199
|
+
"symbols": sorted(symbols),
|
|
200
|
+
"edges": edge_keys,
|
|
201
|
+
"endpoints": endpoint_keys,
|
|
202
|
+
}
|
|
203
|
+
raw = json.dumps(content, sort_keys=True, ensure_ascii=False)
|
|
204
|
+
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
# Conversion helpers
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
def _route_to_canonical_endpoint(route: dict) -> CanonicalEndpoint:
|
|
212
|
+
"""Convert a route_surface entry to a CanonicalEndpoint.
|
|
213
|
+
|
|
214
|
+
Canonical field mapping:
|
|
215
|
+
route["effective_class"] | route["controller"] → controller_class (FQN)
|
|
216
|
+
route["symbol"] → handler_symbol (FQN)
|
|
217
|
+
route["security_annotations"] → security (CanonicalSecurity)
|
|
218
|
+
|
|
219
|
+
This is the ONLY place that reads route_surface dict fields.
|
|
220
|
+
All other code must read CanonicalEndpoint attributes.
|
|
221
|
+
"""
|
|
222
|
+
controller_class = (
|
|
223
|
+
route.get("effective_class")
|
|
224
|
+
or route.get("controller")
|
|
225
|
+
or route.get("declaring_class")
|
|
226
|
+
or ""
|
|
227
|
+
)
|
|
228
|
+
handler_symbol = route.get("symbol") or ""
|
|
229
|
+
method = route.get("method") or ""
|
|
230
|
+
path = route.get("path") or ""
|
|
231
|
+
|
|
232
|
+
security_dict = route.get("security_annotations")
|
|
233
|
+
security: Optional[CanonicalSecurity] = None
|
|
234
|
+
if security_dict:
|
|
235
|
+
# Determine source_scope from inheritance_depth:
|
|
236
|
+
# depth=0 → annotation on method or class (method takes precedence)
|
|
237
|
+
# depth>0 → inherited from parent class
|
|
238
|
+
depth = route.get("inheritance_depth") or 0
|
|
239
|
+
scope = "inherited" if depth > 0 else "method"
|
|
240
|
+
security = CanonicalSecurity.from_policy_dict(security_dict, source_scope=scope)
|
|
241
|
+
|
|
242
|
+
endpoint_id = CanonicalEndpoint.make_id(method, path, controller_class, handler_symbol)
|
|
243
|
+
|
|
244
|
+
return CanonicalEndpoint(
|
|
245
|
+
id=endpoint_id,
|
|
246
|
+
path=path,
|
|
247
|
+
method=method,
|
|
248
|
+
controller_class=controller_class,
|
|
249
|
+
handler_symbol=handler_symbol,
|
|
250
|
+
security=security,
|
|
251
|
+
source_file=route.get("source_file") or "",
|
|
252
|
+
stable_id=route.get("stable_id") or "",
|
|
253
|
+
inheritance_depth=route.get("inheritance_depth") or 0,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def ir_dict_to_canonical(
|
|
258
|
+
ir: dict,
|
|
259
|
+
*,
|
|
260
|
+
file_paths: Optional[list[str]] = None,
|
|
261
|
+
) -> CanonicalRepositoryIR:
|
|
262
|
+
"""Convert build_repo_ir output dict to CanonicalRepositoryIR.
|
|
263
|
+
|
|
264
|
+
Single entry point for CIR construction from an already-built IR dict.
|
|
265
|
+
All projections should use this rather than working with the raw IR dict.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
ir: Full IR dict from build_repo_ir.
|
|
269
|
+
file_paths: Optional explicit file list (used for files field).
|
|
270
|
+
If None, derived from graph node source_file fields.
|
|
271
|
+
"""
|
|
272
|
+
graph = ir.get("graph") or {}
|
|
273
|
+
nodes = graph.get("nodes") or []
|
|
274
|
+
edges = graph.get("edges") or []
|
|
275
|
+
|
|
276
|
+
# Sorted symbol FQNs — stable ordering
|
|
277
|
+
symbols = sorted(n["fqn"] for n in nodes if "fqn" in n)
|
|
278
|
+
|
|
279
|
+
# Sorted file paths — stable ordering
|
|
280
|
+
if file_paths is not None:
|
|
281
|
+
files = sorted(file_paths)
|
|
282
|
+
else:
|
|
283
|
+
file_set: set[str] = set()
|
|
284
|
+
for n in nodes:
|
|
285
|
+
sf = n.get("source_file") or ""
|
|
286
|
+
if sf:
|
|
287
|
+
file_set.add(sf)
|
|
288
|
+
files = sorted(file_set)
|
|
289
|
+
|
|
290
|
+
# Sorted call graph edges — stable ordering: from, type, to
|
|
291
|
+
call_graph = sorted(
|
|
292
|
+
edges,
|
|
293
|
+
key=lambda e: (e.get("from", ""), e.get("type", ""), e.get("to", "")),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# Dependency edges (structural subset of call_graph)
|
|
297
|
+
_dep_types = frozenset({"imports", "extends", "implements", "injects"})
|
|
298
|
+
dependencies = [e for e in call_graph if e.get("type") in _dep_types]
|
|
299
|
+
|
|
300
|
+
# Build canonical endpoints from route_surface — stable ordering
|
|
301
|
+
route_surface = ir.get("route_surface") or []
|
|
302
|
+
# Deduplicate by endpoint id (route_surface can have duplicates from multi-prefix)
|
|
303
|
+
_seen_ids: set[str] = set()
|
|
304
|
+
raw_endpoints: list[CanonicalEndpoint] = []
|
|
305
|
+
for r in route_surface:
|
|
306
|
+
ep = _route_to_canonical_endpoint(r)
|
|
307
|
+
if ep.id not in _seen_ids:
|
|
308
|
+
_seen_ids.add(ep.id)
|
|
309
|
+
raw_endpoints.append(ep)
|
|
310
|
+
|
|
311
|
+
endpoints = sorted(
|
|
312
|
+
raw_endpoints,
|
|
313
|
+
key=lambda ep: (ep.method, ep.path, ep.controller_class, ep.handler_symbol),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Security index: handler_symbol → CanonicalSecurity
|
|
317
|
+
# For handlers with security, record from the most-specific source
|
|
318
|
+
security_index: dict[str, CanonicalSecurity] = {}
|
|
319
|
+
for ep in endpoints:
|
|
320
|
+
if ep.security is not None and ep.handler_symbol:
|
|
321
|
+
security_index[ep.handler_symbol] = ep.security
|
|
322
|
+
|
|
323
|
+
# Metadata aggregation
|
|
324
|
+
metadata: dict[str, Any] = {
|
|
325
|
+
"schema_version": IR_SCHEMA_VERSION,
|
|
326
|
+
"symbol_count": len(symbols),
|
|
327
|
+
"endpoint_count": len(endpoints),
|
|
328
|
+
"file_count": len(files),
|
|
329
|
+
"edge_count": len(call_graph),
|
|
330
|
+
"subsystems": ir.get("subsystems") or [],
|
|
331
|
+
"analysis_gaps": ir.get("analysis_gaps") or [],
|
|
332
|
+
"spring_events": ir.get("spring_events") or {},
|
|
333
|
+
"score_basis": (ir.get("impact") or {}).get("score_basis", "none"),
|
|
334
|
+
"reverse_graph_size": len(ir.get("reverse_graph") or {}),
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
cir_hash = _compute_cir_hash(
|
|
338
|
+
IR_SCHEMA_VERSION, files, symbols, endpoints, call_graph
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
return CanonicalRepositoryIR(
|
|
342
|
+
schema_version=IR_SCHEMA_VERSION,
|
|
343
|
+
cir_hash=cir_hash,
|
|
344
|
+
files=files,
|
|
345
|
+
symbols=symbols,
|
|
346
|
+
call_graph=call_graph,
|
|
347
|
+
reverse_graph=ir.get("reverse_graph") or {},
|
|
348
|
+
dependencies=dependencies,
|
|
349
|
+
endpoints=endpoints,
|
|
350
|
+
security_index=security_index,
|
|
351
|
+
metadata=metadata,
|
|
352
|
+
_raw_ir=ir,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# ---------------------------------------------------------------------------
|
|
357
|
+
# Public builder
|
|
358
|
+
# ---------------------------------------------------------------------------
|
|
359
|
+
|
|
360
|
+
def build_canonical_ir(
|
|
361
|
+
file_paths: list[str],
|
|
362
|
+
root: Path,
|
|
363
|
+
*,
|
|
364
|
+
since: Optional[str] = None,
|
|
365
|
+
) -> CanonicalRepositoryIR:
|
|
366
|
+
"""Build CanonicalRepositoryIR from Java files.
|
|
367
|
+
|
|
368
|
+
Single source of truth builder — the canonical entry point for code intelligence.
|
|
369
|
+
Delegates Java IR extraction to build_repo_ir (5-phase pipeline unchanged).
|
|
370
|
+
Returns a CanonicalRepositoryIR from which all projections must derive.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
file_paths: Relative paths to Java files (from find_java_files).
|
|
374
|
+
root: Absolute repo root.
|
|
375
|
+
since: Git ref for symbol diff (e.g. "HEAD~1", "main").
|
|
376
|
+
"""
|
|
377
|
+
ir = build_repo_ir(file_paths, root, since=since)
|
|
378
|
+
return ir_dict_to_canonical(ir, file_paths=file_paths)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# ---------------------------------------------------------------------------
|
|
382
|
+
# Projection: route_surface
|
|
383
|
+
# ---------------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
def project_route_surface(cir: CanonicalRepositoryIR) -> list[dict]:
|
|
386
|
+
"""Project CIR endpoints to route_surface format.
|
|
387
|
+
|
|
388
|
+
Canonical derivation — no independent endpoint data reconstruction.
|
|
389
|
+
Output format compatible with existing route_surface consumers.
|
|
390
|
+
"""
|
|
391
|
+
routes: list[dict] = []
|
|
392
|
+
for ep in cir.endpoints:
|
|
393
|
+
entry: dict = {
|
|
394
|
+
"symbol": ep.handler_symbol,
|
|
395
|
+
"controller": ep.controller_class,
|
|
396
|
+
"declaring_class": ep.controller_class,
|
|
397
|
+
"effective_class": ep.controller_class,
|
|
398
|
+
"path": ep.path,
|
|
399
|
+
"method": ep.method,
|
|
400
|
+
"stable_id": ep.stable_id,
|
|
401
|
+
"inheritance_depth": ep.inheritance_depth,
|
|
402
|
+
}
|
|
403
|
+
if ep.security is not None:
|
|
404
|
+
# Keep security_annotations key for backward compat with route_surface consumers
|
|
405
|
+
entry["security_annotations"] = ep.security.to_dict()
|
|
406
|
+
routes.append(entry)
|
|
407
|
+
|
|
408
|
+
return sorted(routes, key=lambda r: (r["effective_class"], r["path"]))
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# ---------------------------------------------------------------------------
|
|
412
|
+
# Projection: endpoint_surface (canonical replacement for extract_java_endpoints)
|
|
413
|
+
# ---------------------------------------------------------------------------
|
|
414
|
+
|
|
415
|
+
def project_endpoint_surface(cir: CanonicalRepositoryIR) -> dict:
|
|
416
|
+
"""Project CIR to endpoint surface format.
|
|
417
|
+
|
|
418
|
+
Replaces extract_java_endpoints as the canonical endpoint extractor.
|
|
419
|
+
Output format is backward compatible with extract_java_endpoints.
|
|
420
|
+
|
|
421
|
+
Source: cir.endpoints — never independently parsed from Java source.
|
|
422
|
+
Security: cir.security_index — never independently re-extracted.
|
|
423
|
+
"""
|
|
424
|
+
endpoints: list[dict] = []
|
|
425
|
+
|
|
426
|
+
for ep in cir.endpoints:
|
|
427
|
+
# Simple names for backward compat (same as extract_java_endpoints output)
|
|
428
|
+
controller_simple = ep.controller_class.split(".")[-1]
|
|
429
|
+
handler_simple = (
|
|
430
|
+
ep.handler_symbol.split("#")[1]
|
|
431
|
+
if "#" in ep.handler_symbol
|
|
432
|
+
else ep.handler_symbol.rsplit(".", 1)[-1]
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
entry: dict = {
|
|
436
|
+
"method": ep.method,
|
|
437
|
+
"path": ep.path,
|
|
438
|
+
"controller": controller_simple,
|
|
439
|
+
"handler": handler_simple,
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if ep.security is not None:
|
|
443
|
+
entry["security"] = ep.security.to_dict()
|
|
444
|
+
# Backward compat: top-level required_permission for custom annotation
|
|
445
|
+
if ep.security.policy == "custom_permission":
|
|
446
|
+
entry["required_permission"] = ep.security.required_permission
|
|
447
|
+
|
|
448
|
+
endpoints.append(entry)
|
|
449
|
+
|
|
450
|
+
no_security_signal = sum(1 for e in endpoints if "security" not in e)
|
|
451
|
+
return {
|
|
452
|
+
"endpoints": endpoints,
|
|
453
|
+
"total": len(endpoints),
|
|
454
|
+
"no_security_signal": no_security_signal,
|
|
455
|
+
# Legacy field alias — same count, kept for backward compat
|
|
456
|
+
"undocumented": no_security_signal,
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
# ---------------------------------------------------------------------------
|
|
461
|
+
# Projection: blast_radius
|
|
462
|
+
# ---------------------------------------------------------------------------
|
|
463
|
+
|
|
464
|
+
def project_blast_radius(
|
|
465
|
+
cir: CanonicalRepositoryIR,
|
|
466
|
+
target: str,
|
|
467
|
+
*,
|
|
468
|
+
max_depth: int = 4,
|
|
469
|
+
) -> dict:
|
|
470
|
+
"""Project blast radius from CIR.
|
|
471
|
+
|
|
472
|
+
Delegates BFS logic to compute_blast_radius. Uses _raw_ir which has the
|
|
473
|
+
canonical route_surface already built from the same symbol extraction pass.
|
|
474
|
+
|
|
475
|
+
All endpoints in the result come from cir.endpoints (via route_surface).
|
|
476
|
+
No independent endpoint reconstruction.
|
|
477
|
+
"""
|
|
478
|
+
return _compute_blast_radius(cir._raw_ir, target, max_depth=max_depth)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
# ---------------------------------------------------------------------------
|
|
482
|
+
# Invariant validator
|
|
483
|
+
# ---------------------------------------------------------------------------
|
|
484
|
+
|
|
485
|
+
def validate_canonical_ir(cir: CanonicalRepositoryIR) -> list[str]:
|
|
486
|
+
"""Validate CIR invariants. Returns list of violation strings.
|
|
487
|
+
|
|
488
|
+
Empty list means CIR is valid.
|
|
489
|
+
|
|
490
|
+
Invariants:
|
|
491
|
+
1. Schema version matches IR_SCHEMA_VERSION
|
|
492
|
+
2. Endpoint consistency: no duplicate IDs, required fields present
|
|
493
|
+
3. Security index consistency: all keys are valid handler_symbols
|
|
494
|
+
4. Graph consistency: reverse_graph entries correspond to call_graph edges
|
|
495
|
+
5. Determinism: cir_hash recomputes identically
|
|
496
|
+
6. Blast radius endpoints: endpoints_affected ⊆ cir.endpoints (sampled)
|
|
497
|
+
"""
|
|
498
|
+
violations: list[str] = []
|
|
499
|
+
|
|
500
|
+
# ── 1. Schema version ────────────────────────────────────────────────────
|
|
501
|
+
if cir.schema_version != IR_SCHEMA_VERSION:
|
|
502
|
+
violations.append(
|
|
503
|
+
f"SCHEMA: version mismatch: stored='{cir.schema_version}' "
|
|
504
|
+
f"expected='{IR_SCHEMA_VERSION}'"
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# ── 2. Endpoint consistency ──────────────────────────────────────────────
|
|
508
|
+
endpoint_ids: set[str] = set()
|
|
509
|
+
handler_symbols: set[str] = set()
|
|
510
|
+
for ep in cir.endpoints:
|
|
511
|
+
# Duplicate IDs
|
|
512
|
+
if ep.id in endpoint_ids:
|
|
513
|
+
violations.append(f"ENDPOINT: duplicate id={ep.id!r}")
|
|
514
|
+
endpoint_ids.add(ep.id)
|
|
515
|
+
|
|
516
|
+
# Required fields
|
|
517
|
+
if not ep.method:
|
|
518
|
+
violations.append(f"ENDPOINT: id={ep.id!r} missing method")
|
|
519
|
+
if not ep.path:
|
|
520
|
+
violations.append(f"ENDPOINT: id={ep.id!r} missing path")
|
|
521
|
+
if not ep.controller_class:
|
|
522
|
+
violations.append(f"ENDPOINT: id={ep.id!r} missing controller_class")
|
|
523
|
+
if not ep.handler_symbol:
|
|
524
|
+
violations.append(f"ENDPOINT: id={ep.id!r} missing handler_symbol")
|
|
525
|
+
else:
|
|
526
|
+
handler_symbols.add(ep.handler_symbol)
|
|
527
|
+
|
|
528
|
+
# ID must be deterministically reconstructible
|
|
529
|
+
expected_id = CanonicalEndpoint.make_id(
|
|
530
|
+
ep.method, ep.path, ep.controller_class, ep.handler_symbol
|
|
531
|
+
)
|
|
532
|
+
if ep.id != expected_id:
|
|
533
|
+
violations.append(
|
|
534
|
+
f"ENDPOINT: id not deterministic: stored={ep.id!r} "
|
|
535
|
+
f"recomputed={expected_id!r}"
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
# ── 3. Security index consistency ────────────────────────────────────────
|
|
539
|
+
for sym in cir.security_index:
|
|
540
|
+
if sym not in handler_symbols:
|
|
541
|
+
violations.append(
|
|
542
|
+
f"SECURITY_INDEX: key {sym!r} not in endpoint handler_symbols"
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
# ── 4. Graph consistency (sampled — max 500 edges) ───────────────────────
|
|
546
|
+
# Build forward edge set from call_graph for reverse-graph validation
|
|
547
|
+
call_graph_edge_set: set[tuple[str, str, str]] = {
|
|
548
|
+
(e.get("from", ""), e.get("to", ""), e.get("type", ""))
|
|
549
|
+
for e in cir.call_graph
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
rg_checked = 0
|
|
553
|
+
for target_sym, by_type in sorted(cir.reverse_graph.items()):
|
|
554
|
+
for edge_type, callers in sorted(by_type.items()):
|
|
555
|
+
if edge_type in _REVERSE_EXCLUDE:
|
|
556
|
+
continue
|
|
557
|
+
for caller in sorted(callers):
|
|
558
|
+
if (caller, target_sym, edge_type) not in call_graph_edge_set:
|
|
559
|
+
violations.append(
|
|
560
|
+
f"GRAPH: reverse_graph edge "
|
|
561
|
+
f"{caller!r} →[{edge_type}]→ {target_sym!r} "
|
|
562
|
+
f"not in call_graph"
|
|
563
|
+
)
|
|
564
|
+
rg_checked += 1
|
|
565
|
+
if rg_checked >= 500:
|
|
566
|
+
break
|
|
567
|
+
if rg_checked >= 500:
|
|
568
|
+
break
|
|
569
|
+
if rg_checked >= 500:
|
|
570
|
+
break
|
|
571
|
+
|
|
572
|
+
# ── 5. Determinism: recompute hash ───────────────────────────────────────
|
|
573
|
+
recomputed = _compute_cir_hash(
|
|
574
|
+
cir.schema_version, cir.files, cir.symbols, cir.endpoints, cir.call_graph
|
|
575
|
+
)
|
|
576
|
+
if recomputed != cir.cir_hash:
|
|
577
|
+
violations.append(
|
|
578
|
+
f"DETERMINISM: cir_hash mismatch: "
|
|
579
|
+
f"stored='{cir.cir_hash[:16]}...' "
|
|
580
|
+
f"recomputed='{recomputed[:16]}...'"
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
return violations
|