sourcecode 1.31.18__tar.gz → 1.31.21__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.21/.continue-here.md +87 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/PKG-INFO +3 -3
- {sourcecode-1.31.18 → sourcecode-1.31.21}/README.md +2 -2
- {sourcecode-1.31.18 → sourcecode-1.31.21}/pyproject.toml +1 -1
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/cache.py +193 -9
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/cli.py +155 -34
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/contract_pipeline.py +21 -1
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/mcp/server.py +26 -4
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/serializer.py +113 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_bug_fixes_v16.py +82 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_cache.py +168 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_mcp_tools.py +47 -15
- sourcecode-1.31.18/.continue-here.md +0 -134
- {sourcecode-1.31.18 → sourcecode-1.31.21}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/.gitignore +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/.ruff.toml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/.sourcecode-cache/snapshot-3b5997a-fa5c742c.json +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/AUDIT_REAL_REPOS.md +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/CHANGELOG.md +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/CONTRIBUTING.md +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/LICENSE +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/SECURITY.md +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/docs/PRODUCT_TIERS.md +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/docs/privacy.md +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/docs/schema.md +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/raw +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/run_cli.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/__init__.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/conftest.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/latin1_sample.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/latin1_sample_iso.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/lcov.info +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_architecture_summary.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_ast_extractor.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_audit_fixes.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_audit_sas_v2.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_block1_reliability.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_block2_coverage.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_block5_quality.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_broadleaf_fixes.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_bug_fixes_v1302.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_bug_fixes_v13115.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_bug_fixes_v1312.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_bug_fixes_v1313.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_bug_fixes_v2.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_canonical_ir.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_classifier.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_cli.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_context_scorer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_contract_pipeline.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_coverage_parser.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_cross_consistency.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_dependency_schema.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_detector_python.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_detectors_base.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_encoding_regression.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_enterprise_benchmarks.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_graph_schema.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_integration.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_integration_detection.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_integration_docs.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_integration_lqn.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_integration_metrics.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_integration_multistack.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_integration_semantics.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_integration_universal.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_java_spring_integration.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_mcp_runner.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_mcp_serve.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_output_ux.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_packaging.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_pipeline_integrity.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_real_projects.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_redactor.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_repository_ir.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_scanner.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_schema.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_schema_normalization.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_scoring_calibration.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_semantic_schema.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_signal_hierarchy.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_summarizer.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_surface_honesty.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_task_differentiation.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_telemetry.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_v131_improvements.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_v1_10_regressions.py +0 -0
- {sourcecode-1.31.18 → sourcecode-1.31.21}/tests/test_workspace_analyzer.py +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Handoff — sesión 22
|
|
2
|
+
**Date:** 2026-05-25T09:10:25Z
|
|
3
|
+
**Branch:** master
|
|
4
|
+
**Last commit:** `f1e80b6 feat(cache): introduce two-layer cache (L1 core + L2 view)`
|
|
5
|
+
**Suite:** 1510 passed, 3 skipped — all green
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What was done this session
|
|
10
|
+
|
|
11
|
+
### Credibility audit (`AUDIT_REAL_REPOS.md`)
|
|
12
|
+
Full adversarial audit of `sourcecode` against Keycloak (7885 Java files) and BroadleafCommerce (2985 Java files). Findings logged in `AUDIT_REAL_REPOS.md` at repo root. Key bugs fixed in prior sessions (P0-01 Spring DI bridging, P0-02 repo-ir boundedness).
|
|
13
|
+
|
|
14
|
+
### Two-layer cache architecture (this session)
|
|
15
|
+
|
|
16
|
+
**Problem:** cache key was `(commit, ALL_flags)` → N flag combos × M commits = N×M full snapshots. Same commit with `--compact` vs `--agent` ran the full analysis pipeline twice.
|
|
17
|
+
|
|
18
|
+
**Solution implemented:**
|
|
19
|
+
- **L1 core** `core-<sha>-<analysis_hash>.json.gz` → pre-computed view data, keyed by analysis-affecting flags only
|
|
20
|
+
- **L2 view** `view-<core_hash16>-<view_hash>.json.gz` → rendered string, keyed by view-affecting flags
|
|
21
|
+
- Lookup: L2 hit → L1 hit + view rebuild → full analysis (skip analysis on L1 hit)
|
|
22
|
+
|
|
23
|
+
**Files changed:**
|
|
24
|
+
| File | What |
|
|
25
|
+
|------|------|
|
|
26
|
+
| `src/sourcecode/serializer.py` | Added `core_view(sm)` + `build_view_from_core()` + `CORE_VIEW_VERSION` |
|
|
27
|
+
| `src/sourcecode/cache.py` | Added `read_core/write_core`, `read_view/write_view`, `_gc_views()`, updated `_gc()` |
|
|
28
|
+
| `src/sourcecode/cli.py` | Replaced single-key cache block with 2-layer lookup + write; split flags into core vs view |
|
|
29
|
+
| `tests/test_cache.py` | +12 new tests: TestCoreCache, TestViewCache, TestGCLayered (44 total cache tests) |
|
|
30
|
+
|
|
31
|
+
**Flag split:**
|
|
32
|
+
- Core (analysis): `dep/gm/docs/fm/sem/arch/gc/em/cn/mode/exclude/depth` + version
|
|
33
|
+
- View (presentation): `compact/agent/format/full/no_tree/tree/rank_by/symbol/entrypoints_only/no_redact/graph_detail/docs_depth/max_nodes/graph_edges/max_importers/emit_graph`
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Current state
|
|
38
|
+
|
|
39
|
+
Working tree is **clean** — everything committed.
|
|
40
|
+
|
|
41
|
+
GSD state: milestone v1.0, 13 phases all complete (100%). No pending todos in `.planning/STATE.md`.
|
|
42
|
+
|
|
43
|
+
Outstanding work from `AUDIT_REAL_REPOS.md` (NOT yet implemented — only audit findings logged):
|
|
44
|
+
|
|
45
|
+
### P1 remaining (from audit):
|
|
46
|
+
- **BUG-P1-01** Risk score inconsistency: `OrderServiceImpl` 0 callers → risk_level: low vs `OrderDaoImpl` 0 callers → risk_level: high. Same root cause, different heuristics fire.
|
|
47
|
+
- **BUG-P1-02** `project_summary` copies README blurb instead of generating from code structure
|
|
48
|
+
- **BUG-P1-03** `fix-bug` returns 426 relevant files for generic NPE symptom — no score field, no cap
|
|
49
|
+
- **BUG-P1-04** `indirect_callers: 0` for KeycloakSession (1992 direct callers) — BFS exhausts at level 1
|
|
50
|
+
|
|
51
|
+
### P2 remaining:
|
|
52
|
+
- BUG-P2-01 `bounded_contexts` detection wrong (uses utility packages, not Maven modules)
|
|
53
|
+
- BUG-P2-02 `role: unknown` for all modernize high_coupling_nodes
|
|
54
|
+
- BUG-P2-03 `no_security_signal` always 100% for filter-based security (JAX-RS/XML)
|
|
55
|
+
- BUG-P2-04 JAX-RS sub-resource paths not composed with parent `@Path`
|
|
56
|
+
- BUG-P2-05 Broadleaf admin paths mixed into REST endpoints
|
|
57
|
+
- BUG-P2-06 Architecture confidence inconsistent between `--compact` and `--agent`
|
|
58
|
+
- BUG-P2-07 `entry_points.controllers.methods: 21` vs endpoints finding 130 (unexplained gap)
|
|
59
|
+
- BUG-P2-08 `--format`/`--no-cache` inconsistently available across subcommands
|
|
60
|
+
|
|
61
|
+
### Cosmetic:
|
|
62
|
+
- Code notes URLs truncated (`s.webkit.org/...`)
|
|
63
|
+
- `truncated: None` vs `truncated: false` inconsistency
|
|
64
|
+
- `--deep` flag referenced in output but absent from `--help`
|
|
65
|
+
- `--compact --help` claims 1000-3000 tokens; measured 2856-4031
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Next session: where to start
|
|
70
|
+
|
|
71
|
+
**Option A** — tackle P1 bugs from audit (highest credibility impact):
|
|
72
|
+
1. P1-01: risk score consistency when direct_callers=0 (Spring impl detection)
|
|
73
|
+
2. P1-02: `project_summary` generation from code structure not README
|
|
74
|
+
|
|
75
|
+
**Option B** — write integration tests for 2-layer cache (verify cli.py actually hits L1 on second run with different view flags)
|
|
76
|
+
|
|
77
|
+
**Option C** — version bump + changelog (v1.32.0) with 2-layer cache as headline
|
|
78
|
+
|
|
79
|
+
**Recommended: Option A → P1-01 first** (user-visible correctness, shortest fix).
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Resume
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
cat .continue-here.md
|
|
87
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.31.
|
|
3
|
+
Version: 1.31.21
|
|
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
|
**AI-ready change intelligence for Java/Spring enterprise monoliths.**
|
|
227
227
|
|
|
228
|
-

|
|
229
229
|

|
|
230
230
|
|
|
231
231
|
---
|
|
@@ -263,7 +263,7 @@ pipx install sourcecode
|
|
|
263
263
|
|
|
264
264
|
```bash
|
|
265
265
|
sourcecode version
|
|
266
|
-
# sourcecode 1.31.
|
|
266
|
+
# sourcecode 1.31.21
|
|
267
267
|
```
|
|
268
268
|
|
|
269
269
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**AI-ready change intelligence for Java/Spring enterprise monoliths.**
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -40,7 +40,7 @@ pipx install sourcecode
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
sourcecode version
|
|
43
|
-
# sourcecode 1.31.
|
|
43
|
+
# sourcecode 1.31.21
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
---
|
|
@@ -69,6 +69,9 @@ from typing import Any, Optional
|
|
|
69
69
|
#: Bump this string to invalidate *all* existing cached snapshots.
|
|
70
70
|
SCHEMA_VERSION: str = "2"
|
|
71
71
|
|
|
72
|
+
#: Bump to invalidate all L1 core caches (independent of snapshot version).
|
|
73
|
+
CORE_SCHEMA_VERSION: str = "1"
|
|
74
|
+
|
|
72
75
|
#: Fields eligible for CAS deduplication (applied to top-level JSON dict keys).
|
|
73
76
|
_CAS_FIELDS: frozenset[str] = frozenset([
|
|
74
77
|
"file_paths",
|
|
@@ -93,11 +96,18 @@ _DEFAULT_KEEP_COMMITS: int = 5
|
|
|
93
96
|
# Matches "snapshot-<hex_commit>-<hex_flags>.json.gz"
|
|
94
97
|
_SNAPSHOT_RE = re.compile(r"^snapshot-([0-9a-f]+)-[0-9a-f]+\.json\.gz$")
|
|
95
98
|
|
|
99
|
+
# Matches "core-<hex_commit>-<hex_analysis>.json.gz"
|
|
100
|
+
_CORE_RE = re.compile(r"^core-([0-9a-f]+)-[0-9a-f]+\.json\.gz$")
|
|
101
|
+
|
|
102
|
+
# Matches "view-<hex_core_hash16>-<hex_view_flags>.json.gz"
|
|
103
|
+
_VIEW_RE = re.compile(r"^view-([0-9a-f]{16})-[0-9a-f]+\.json\.gz$")
|
|
104
|
+
|
|
96
105
|
|
|
97
106
|
# ---------------------------------------------------------------------------
|
|
98
107
|
# Public API — location helpers
|
|
99
108
|
# ---------------------------------------------------------------------------
|
|
100
109
|
|
|
110
|
+
|
|
101
111
|
def repo_id(repo_root: Path) -> str:
|
|
102
112
|
"""Stable 16-char hex identifier derived from the canonical repo path."""
|
|
103
113
|
return hashlib.sha256(str(repo_root.resolve()).encode()).hexdigest()[:16]
|
|
@@ -190,6 +200,138 @@ def write(
|
|
|
190
200
|
_gc(cache_d)
|
|
191
201
|
|
|
192
202
|
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
# Layer 1 — Core Analysis cache
|
|
205
|
+
# ---------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
def read_core(repo_root: Path, core_key: str) -> Optional[tuple[dict[str, Any], str]]:
|
|
208
|
+
"""Read core analysis artifacts from L1 cache.
|
|
209
|
+
|
|
210
|
+
Returns ``(core_dict, core_hash)`` on hit, or ``None`` on miss.
|
|
211
|
+
``core_hash`` is the 16-char SHA-256 of the stored core JSON, used as
|
|
212
|
+
the L2 view-key prefix so that different views of the same core share
|
|
213
|
+
a common ancestry without a full re-analysis.
|
|
214
|
+
"""
|
|
215
|
+
cache_d = cache_dir(repo_root)
|
|
216
|
+
gz_path = cache_d / f"core-{core_key}.json.gz"
|
|
217
|
+
if not gz_path.exists():
|
|
218
|
+
return None
|
|
219
|
+
try:
|
|
220
|
+
raw_bytes = gzip.decompress(gz_path.read_bytes())
|
|
221
|
+
envelope = json.loads(raw_bytes.decode("utf-8"))
|
|
222
|
+
except Exception:
|
|
223
|
+
_safe_unlink(gz_path)
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
if not isinstance(envelope, dict):
|
|
227
|
+
_safe_unlink(gz_path)
|
|
228
|
+
return None
|
|
229
|
+
if envelope.get("csv") != CORE_SCHEMA_VERSION:
|
|
230
|
+
_safe_unlink(gz_path) # schema mismatch — evict
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
core_data = envelope.get("data")
|
|
234
|
+
core_hash = envelope.get("hash", "")
|
|
235
|
+
if not isinstance(core_data, dict) or not core_hash:
|
|
236
|
+
_safe_unlink(gz_path)
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
return core_data, core_hash
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def write_core(repo_root: Path, core_key: str, core_data: dict[str, Any]) -> str:
|
|
243
|
+
"""Persist core analysis dict to L1 cache.
|
|
244
|
+
|
|
245
|
+
Returns the 16-char SHA-256 hash of the core JSON (the L2 key prefix).
|
|
246
|
+
Writes are always best-effort; failures are silently swallowed.
|
|
247
|
+
|
|
248
|
+
File layout::
|
|
249
|
+
|
|
250
|
+
~/.sourcecode/cache/<repo_id>/core-<core_key>.json.gz
|
|
251
|
+
|
|
252
|
+
Envelope schema::
|
|
253
|
+
|
|
254
|
+
{ "csv": "1", // CORE_SCHEMA_VERSION
|
|
255
|
+
"key": "...", // core_key passed in
|
|
256
|
+
"hash": "<h16>", // SHA-256[:16] of core JSON — used as L2 prefix
|
|
257
|
+
"ts": "...", // ISO-8601 UTC write time
|
|
258
|
+
"data": {...} } // core_view(sm) dict
|
|
259
|
+
"""
|
|
260
|
+
core_json = json.dumps(core_data, ensure_ascii=False)
|
|
261
|
+
core_hash = hashlib.sha256(core_json.encode()).hexdigest()[:16]
|
|
262
|
+
|
|
263
|
+
cache_d = cache_dir(repo_root)
|
|
264
|
+
dest = cache_d / f"core-{core_key}.json.gz"
|
|
265
|
+
try:
|
|
266
|
+
cache_d.mkdir(parents=True, exist_ok=True)
|
|
267
|
+
envelope: dict[str, Any] = {
|
|
268
|
+
"csv": CORE_SCHEMA_VERSION,
|
|
269
|
+
"key": core_key,
|
|
270
|
+
"hash": core_hash,
|
|
271
|
+
"ts": _now_iso(),
|
|
272
|
+
"data": core_data,
|
|
273
|
+
}
|
|
274
|
+
payload = gzip.compress(
|
|
275
|
+
json.dumps(envelope, ensure_ascii=False).encode("utf-8"),
|
|
276
|
+
compresslevel=6,
|
|
277
|
+
)
|
|
278
|
+
dest.write_bytes(payload)
|
|
279
|
+
except Exception:
|
|
280
|
+
pass
|
|
281
|
+
|
|
282
|
+
return core_hash
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
# Layer 2 — Derived View cache
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
def read_view(repo_root: Path, view_key: str) -> Optional[str]:
|
|
290
|
+
"""Read a rendered view string from L2 cache.
|
|
291
|
+
|
|
292
|
+
Views are stored as ``view-{view_key}.json.gz`` using the same
|
|
293
|
+
envelope+CAS format as snapshot files. Returns the content string
|
|
294
|
+
(JSON or YAML) or ``None`` on miss.
|
|
295
|
+
"""
|
|
296
|
+
cache_d = cache_dir(repo_root)
|
|
297
|
+
gz_path = cache_d / f"view-{view_key}.json.gz"
|
|
298
|
+
if not gz_path.exists():
|
|
299
|
+
return None
|
|
300
|
+
try:
|
|
301
|
+
result = _parse_envelope(gz_path.read_bytes(), cache_d)
|
|
302
|
+
if result is not None:
|
|
303
|
+
return result
|
|
304
|
+
except Exception:
|
|
305
|
+
pass
|
|
306
|
+
_safe_unlink(gz_path)
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def write_view(
|
|
311
|
+
repo_root: Path,
|
|
312
|
+
view_key: str,
|
|
313
|
+
content: str,
|
|
314
|
+
*,
|
|
315
|
+
fmt: str = "json",
|
|
316
|
+
layers: Optional[dict[str, str]] = None,
|
|
317
|
+
) -> None:
|
|
318
|
+
"""Persist a rendered view string to L2 cache as ``view-{view_key}.json.gz``.
|
|
319
|
+
|
|
320
|
+
Reuses the envelope+CAS infrastructure so large fields (file_paths,
|
|
321
|
+
graph, docs …) are automatically deduplicated with other snapshots/views.
|
|
322
|
+
Writes are always best-effort; GC is **not** triggered here — callers
|
|
323
|
+
that want eviction should invoke ``_gc(cache_dir(repo_root))`` explicitly.
|
|
324
|
+
"""
|
|
325
|
+
cache_d = cache_dir(repo_root)
|
|
326
|
+
dest = cache_d / f"view-{view_key}.json.gz"
|
|
327
|
+
try:
|
|
328
|
+
cache_d.mkdir(parents=True, exist_ok=True)
|
|
329
|
+
payload = _build_envelope(view_key, content, fmt, layers or {}, cache_d)
|
|
330
|
+
dest.write_bytes(payload)
|
|
331
|
+
except Exception:
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
|
|
193
335
|
# ---------------------------------------------------------------------------
|
|
194
336
|
# Envelope (de)serialisation
|
|
195
337
|
# ---------------------------------------------------------------------------
|
|
@@ -384,31 +526,39 @@ def _cas_restore(
|
|
|
384
526
|
# ---------------------------------------------------------------------------
|
|
385
527
|
|
|
386
528
|
def _gc(cache_d: Path) -> None:
|
|
387
|
-
"""
|
|
388
|
-
Evict old snapshots and sweep orphaned CAS blobs.
|
|
529
|
+
"""Evict old snapshots/cores/views and sweep orphaned CAS blobs.
|
|
389
530
|
|
|
390
|
-
Keeps snapshots from the last ``SOURCECODE_CACHE_KEEP_COMMITS``
|
|
391
|
-
git commits (determined by mtime
|
|
531
|
+
Keeps snapshots and cores from the last ``SOURCECODE_CACHE_KEEP_COMMITS``
|
|
532
|
+
distinct git commits (determined by newest mtime within each commit group).
|
|
533
|
+
Views are then pruned: a view survives only when its core-hash prefix
|
|
534
|
+
matches a core file in the surviving set.
|
|
392
535
|
"""
|
|
393
536
|
keep = int(os.environ.get("SOURCECODE_CACHE_KEEP_COMMITS", _DEFAULT_KEEP_COMMITS))
|
|
394
537
|
|
|
395
538
|
try:
|
|
396
539
|
all_snapshots = list(cache_d.glob("snapshot-*.json.gz"))
|
|
397
|
-
|
|
540
|
+
all_cores = list(cache_d.glob("core-*.json.gz"))
|
|
541
|
+
all_views = list(cache_d.glob("view-*.json.gz"))
|
|
542
|
+
|
|
543
|
+
if not all_snapshots and not all_cores and not all_views:
|
|
398
544
|
return
|
|
399
545
|
|
|
400
|
-
# Group snapshot files by commit SHA
|
|
546
|
+
# Group snapshot + core files by commit SHA
|
|
401
547
|
groups: dict[str, list[Path]] = {}
|
|
402
548
|
for f in all_snapshots:
|
|
403
549
|
m = _SNAPSHOT_RE.match(f.name)
|
|
404
550
|
if m:
|
|
405
551
|
groups.setdefault(m.group(1), []).append(f)
|
|
552
|
+
for f in all_cores:
|
|
553
|
+
m = _CORE_RE.match(f.name)
|
|
554
|
+
if m:
|
|
555
|
+
groups.setdefault(m.group(1), []).append(f)
|
|
406
556
|
|
|
407
557
|
surviving: list[Path]
|
|
408
558
|
|
|
409
559
|
if keep <= 0 or len(groups) <= keep:
|
|
410
|
-
# No eviction needed — but still sweep CAS
|
|
411
|
-
surviving = all_snapshots
|
|
560
|
+
# No eviction needed — but still sweep views + CAS
|
|
561
|
+
surviving = all_snapshots + all_cores
|
|
412
562
|
else:
|
|
413
563
|
def _newest_mtime(commit: str) -> float:
|
|
414
564
|
return max(p.stat().st_mtime for p in groups[commit])
|
|
@@ -422,12 +572,46 @@ def _gc(cache_d: Path) -> None:
|
|
|
422
572
|
for f in groups[commit]:
|
|
423
573
|
_safe_unlink(f)
|
|
424
574
|
|
|
425
|
-
|
|
575
|
+
# Prune view files whose core hash is no longer in the surviving set
|
|
576
|
+
_gc_views(cache_d, surviving, all_views)
|
|
577
|
+
|
|
578
|
+
# Sweep orphaned CAS blobs (surviving snapshots + view files may ref them)
|
|
579
|
+
surviving_with_views = surviving + [v for v in all_views if v.exists()]
|
|
580
|
+
_gc_cas(cache_d, surviving_with_views)
|
|
426
581
|
|
|
427
582
|
except Exception:
|
|
428
583
|
pass # GC failure is non-fatal
|
|
429
584
|
|
|
430
585
|
|
|
586
|
+
def _gc_views(cache_d: Path, surviving: list[Path], all_views: list[Path]) -> None:
|
|
587
|
+
"""Delete view files not traceable to a surviving core.
|
|
588
|
+
|
|
589
|
+
Collects the ``hash`` field from every surviving core envelope, then
|
|
590
|
+
deletes view files whose filename core-hash prefix is absent from that
|
|
591
|
+
set. View files with unrecognisable names are left untouched.
|
|
592
|
+
"""
|
|
593
|
+
if not all_views:
|
|
594
|
+
return
|
|
595
|
+
|
|
596
|
+
# Collect live core hashes from surviving core-*.json.gz files
|
|
597
|
+
live_hashes: set[str] = set()
|
|
598
|
+
for path in surviving:
|
|
599
|
+
if not path.name.startswith("core-"):
|
|
600
|
+
continue
|
|
601
|
+
try:
|
|
602
|
+
env = json.loads(gzip.decompress(path.read_bytes()).decode("utf-8"))
|
|
603
|
+
h = env.get("hash", "")
|
|
604
|
+
if h:
|
|
605
|
+
live_hashes.add(h)
|
|
606
|
+
except Exception:
|
|
607
|
+
pass # unreadable core — conservatively keep its views unknown
|
|
608
|
+
|
|
609
|
+
for vp in all_views:
|
|
610
|
+
m = _VIEW_RE.match(vp.name)
|
|
611
|
+
if m and m.group(1) not in live_hashes:
|
|
612
|
+
_safe_unlink(vp)
|
|
613
|
+
|
|
614
|
+
|
|
431
615
|
def _gc_cas(cache_d: Path, surviving_snapshots: list[Path]) -> None:
|
|
432
616
|
"""
|
|
433
617
|
Delete CAS blobs not referenced by any snapshot in *surviving_snapshots*.
|
|
@@ -876,16 +876,30 @@ def main(
|
|
|
876
876
|
architecture = True # agents need full architectural signal (M4)
|
|
877
877
|
graph_modules = True # IC-003: import graph needed for architecture confidence
|
|
878
878
|
|
|
879
|
-
# ──
|
|
880
|
-
#
|
|
881
|
-
#
|
|
882
|
-
#
|
|
879
|
+
# ── Two-layer cache ────────────────────────────────────────────────────────
|
|
880
|
+
# L1 (core): (repo, commit, analysis_flags) → pre-computed view data dict
|
|
881
|
+
# key = core-<git_sha>-<analysis_hash>.json.gz
|
|
882
|
+
# L2 (view): (core_hash, view_flags) → final rendered string
|
|
883
|
+
# key = view-<core_hash16>-<view_hash>.json.gz
|
|
884
|
+
#
|
|
885
|
+
# Lookup order: L2 exact hit → L1 hit + view rebuild → full analysis
|
|
886
|
+
# Write order: full analysis → write L1 core → write L2 view
|
|
887
|
+
#
|
|
888
|
+
# Flags split:
|
|
889
|
+
# core (analysis) — affect WHAT is analysed; same core for any view
|
|
890
|
+
# view — affect HOW it's presented; same view for any format variant
|
|
883
891
|
import hashlib as _hashlib
|
|
884
892
|
import subprocess as _sub
|
|
885
893
|
from sourcecode import cache as _cache_mod
|
|
894
|
+
|
|
886
895
|
_cache_hit_content: Optional[str] = None
|
|
887
896
|
_git_sha = ""
|
|
888
|
-
|
|
897
|
+
_core_key = ""
|
|
898
|
+
_view_key = ""
|
|
899
|
+
_core_hash = ""
|
|
900
|
+
_core_flags_str = ""
|
|
901
|
+
_view_flags_str = ""
|
|
902
|
+
|
|
889
903
|
if not no_cache:
|
|
890
904
|
try:
|
|
891
905
|
_sha_r = _sub.run(
|
|
@@ -896,37 +910,112 @@ def main(
|
|
|
896
910
|
# Only cache when target IS the git repo root (not a subdir of one),
|
|
897
911
|
# to avoid polluting sub-project directories used in tests.
|
|
898
912
|
if _git_sha and (target / ".git").exists():
|
|
899
|
-
# Include every output-affecting flag so different flag combos never collide
|
|
900
|
-
# Include version so cache is invalidated on sourcecode upgrades
|
|
901
913
|
from sourcecode import __version__ as _sc_version
|
|
902
|
-
# FIX-P0-1: cache key must include ALL analysis-affecting flags.
|
|
903
|
-
# Previously missing: exclude, depth, rank_by, symbol, entrypoints_only,
|
|
904
|
-
# no_redact, graph_detail, docs_depth, max_nodes, graph_edges,
|
|
905
|
-
# max_importers, emit_graph.
|
|
906
|
-
# Use effective_depth (not raw depth) so Java auto-adjustment is captured.
|
|
907
914
|
_excl_key = (
|
|
908
915
|
",".join(sorted(e.strip() for e in exclude.split(",") if e.strip()))
|
|
909
916
|
if exclude else ""
|
|
910
917
|
)
|
|
911
|
-
|
|
918
|
+
|
|
919
|
+
# ── Core (analysis) flags: affect which analyzers run + scan config ──
|
|
920
|
+
# Use effective_depth (not raw depth) so Java auto-adjustment is captured.
|
|
921
|
+
_core_flags_str = (
|
|
912
922
|
f"v={_sc_version},"
|
|
913
|
-
f"
|
|
914
|
-
f"co={changed_only},dep={dependencies},gm={graph_modules},"
|
|
923
|
+
f"dep={dependencies},gm={graph_modules},"
|
|
915
924
|
f"docs={docs},fm={full_metrics},sem={semantics},"
|
|
916
925
|
f"arch={architecture},gc={git_context},em={env_map},"
|
|
917
|
-
f"cn={code_notes},
|
|
918
|
-
f"ex={_excl_key},depth={effective_depth}
|
|
926
|
+
f"cn={code_notes},mode={mode},"
|
|
927
|
+
f"ex={_excl_key},depth={effective_depth}"
|
|
928
|
+
)
|
|
929
|
+
_core_h = _hashlib.md5(_core_flags_str.encode()).hexdigest()[:8]
|
|
930
|
+
_core_key = f"{_git_sha}-{_core_h}"
|
|
931
|
+
|
|
932
|
+
# ── View flags: output presentation only (no re-analysis needed) ──
|
|
933
|
+
_view_flags_str = (
|
|
934
|
+
f"c={compact},ag={agent},fmt={format},full={full},"
|
|
935
|
+
f"co={changed_only},tree={tree},nt={no_tree},"
|
|
919
936
|
f"rb={rank_by},sym={symbol},ep={entrypoints_only},"
|
|
920
937
|
f"nr={no_redact},gd={graph_detail},dd={docs_depth},"
|
|
921
938
|
f"mn={max_nodes},ge={graph_edges},mi={max_importers},"
|
|
922
939
|
f"eg={emit_graph}"
|
|
923
940
|
)
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
941
|
+
_view_h = _hashlib.md5(_view_flags_str.encode()).hexdigest()[:8]
|
|
942
|
+
|
|
943
|
+
# ── Lookup ──────────────────────────────────────────────────────
|
|
944
|
+
# Step 1: try L1 to obtain the core_hash needed for L2 key
|
|
945
|
+
_l1_result = _cache_mod.read_core(target, _core_key)
|
|
946
|
+
if _l1_result is not None:
|
|
947
|
+
_core_dict_l1, _core_hash = _l1_result
|
|
948
|
+
_view_key = f"{_core_hash}-{_view_h}"
|
|
949
|
+
|
|
950
|
+
# Step 2: try L2 (exact view match)
|
|
951
|
+
_cache_hit_content = _cache_mod.read_view(target, _view_key)
|
|
952
|
+
|
|
953
|
+
# Step 3: L1 hit but L2 miss → rebuild view from core dict
|
|
954
|
+
if _cache_hit_content is None:
|
|
955
|
+
try:
|
|
956
|
+
from sourcecode.serializer import build_view_from_core as _bvfc
|
|
957
|
+
_rebuilt = _bvfc(
|
|
958
|
+
_core_dict_l1,
|
|
959
|
+
compact=compact,
|
|
960
|
+
agent=agent,
|
|
961
|
+
full=full,
|
|
962
|
+
no_tree=no_tree,
|
|
963
|
+
tree=tree,
|
|
964
|
+
)
|
|
965
|
+
if _rebuilt is not None:
|
|
966
|
+
# Apply redaction
|
|
967
|
+
if not no_redact:
|
|
968
|
+
from sourcecode.redactor import redact_dict as _red_l1
|
|
969
|
+
_rebuilt = _red_l1(_rebuilt)
|
|
970
|
+
# Apply output budget
|
|
971
|
+
if agent:
|
|
972
|
+
from sourcecode.output_budget import (
|
|
973
|
+
trim_to_budget as _trim_l1,
|
|
974
|
+
BUDGET_AGENT,
|
|
975
|
+
)
|
|
976
|
+
_rebuilt = _trim_l1(_rebuilt, BUDGET_AGENT, label="agent")
|
|
977
|
+
elif compact:
|
|
978
|
+
from sourcecode.output_budget import (
|
|
979
|
+
trim_to_budget as _trim_l1c,
|
|
980
|
+
BUDGET_COMPACT,
|
|
981
|
+
)
|
|
982
|
+
_rebuilt = _trim_l1c(_rebuilt, BUDGET_COMPACT, label="compact")
|
|
983
|
+
# Serialize
|
|
984
|
+
if format == "yaml":
|
|
985
|
+
from io import StringIO as _SIO_L1
|
|
986
|
+
from ruamel.yaml import YAML as _YAML_L1
|
|
987
|
+
_yl1 = _YAML_L1()
|
|
988
|
+
_yl1.default_flow_style = False
|
|
989
|
+
_yl1.representer.add_representer(
|
|
990
|
+
type(None),
|
|
991
|
+
lambda d, v: d.represent_scalar(
|
|
992
|
+
"tag:yaml.org,2002:null", "null"
|
|
993
|
+
),
|
|
994
|
+
)
|
|
995
|
+
_sl1 = _SIO_L1()
|
|
996
|
+
_yl1.dump(_rebuilt, _sl1)
|
|
997
|
+
_cache_hit_content = _sl1.getvalue()
|
|
998
|
+
else:
|
|
999
|
+
import json as _json_l1
|
|
1000
|
+
_cache_hit_content = _json_l1.dumps(
|
|
1001
|
+
_rebuilt, indent=2, ensure_ascii=False
|
|
1002
|
+
)
|
|
1003
|
+
# Cache rebuilt view in L2
|
|
1004
|
+
if _cache_hit_content:
|
|
1005
|
+
_cache_mod.write_view(
|
|
1006
|
+
target,
|
|
1007
|
+
_view_key,
|
|
1008
|
+
_cache_hit_content,
|
|
1009
|
+
fmt=format,
|
|
1010
|
+
)
|
|
1011
|
+
except Exception:
|
|
1012
|
+
_cache_hit_content = None # rebuild failed → full analysis
|
|
1013
|
+
|
|
927
1014
|
except Exception:
|
|
928
1015
|
_git_sha = ""
|
|
929
|
-
|
|
1016
|
+
_core_key = ""
|
|
1017
|
+
_view_key = ""
|
|
1018
|
+
_core_hash = ""
|
|
930
1019
|
|
|
931
1020
|
if _cache_hit_content is not None:
|
|
932
1021
|
from sourcecode.serializer import write_output
|
|
@@ -1687,8 +1776,21 @@ def main(
|
|
|
1687
1776
|
if changed_only and _allowed_changed_files:
|
|
1688
1777
|
# GAP-5: preserve full entry_points for architecture context even in
|
|
1689
1778
|
# --changed-only mode. Only filter file_paths and code_notes.
|
|
1779
|
+
# ALWAYS-INCLUDE: security-const files must stay in file_paths even when
|
|
1780
|
+
# not in the git diff — they resolve Java constant references used in
|
|
1781
|
+
# @M3FiltroSeguridad annotations (read-only anchors, not diff output).
|
|
1782
|
+
def _is_always_include_ref(p: str) -> bool:
|
|
1783
|
+
name = p.rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
|
|
1784
|
+
if name.endswith("Const.java") or name.endswith("Constants.java"):
|
|
1785
|
+
return True
|
|
1786
|
+
parts = p.replace("\\", "/").lower().split("/")
|
|
1787
|
+
return any(seg in ("security", "seguridad", "constantes") for seg in parts)
|
|
1788
|
+
|
|
1690
1789
|
sm = _replace(sm,
|
|
1691
|
-
file_paths=[
|
|
1790
|
+
file_paths=[
|
|
1791
|
+
p for p in sm.file_paths
|
|
1792
|
+
if p in _allowed_changed_files or _is_always_include_ref(p)
|
|
1793
|
+
],
|
|
1692
1794
|
code_notes=[n for n in sm.code_notes if n.path in _allowed_changed_files],
|
|
1693
1795
|
)
|
|
1694
1796
|
data = compact_view(sm, no_tree=no_tree, full=full)
|
|
@@ -1760,18 +1862,37 @@ def main(
|
|
|
1760
1862
|
_progress.finish()
|
|
1761
1863
|
write_output(content, output=output)
|
|
1762
1864
|
|
|
1763
|
-
#
|
|
1764
|
-
#
|
|
1765
|
-
#
|
|
1766
|
-
#
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1865
|
+
# Persist to two-layer cache (git SHA unchanged → re-use on next run).
|
|
1866
|
+
#
|
|
1867
|
+
# L1 (core): stores pre-computed compact+agent+standard views at max
|
|
1868
|
+
# fidelity so any subsequent view can be derived without re-analysis.
|
|
1869
|
+
# L2 (view): stores the exact rendered string for this flag combination.
|
|
1870
|
+
#
|
|
1871
|
+
# GC runs after L2 write to evict old commits and orphaned blobs/views.
|
|
1872
|
+
if not no_cache and _core_key and not _pipeline_error:
|
|
1873
|
+
try:
|
|
1874
|
+
from sourcecode.serializer import core_view as _core_view_fn
|
|
1875
|
+
_core_dict_write = _core_view_fn(sm)
|
|
1876
|
+
_written_core_hash = _cache_mod.write_core(target, _core_key, _core_dict_write)
|
|
1877
|
+
|
|
1878
|
+
# Compute view key using the just-written core hash
|
|
1879
|
+
if _written_core_hash:
|
|
1880
|
+
if not _view_key:
|
|
1881
|
+
# _view_key not set (L1 was also a miss); compute it now
|
|
1882
|
+
_wvh = _hashlib.md5(_view_flags_str.encode()).hexdigest()[:8]
|
|
1883
|
+
_view_key = f"{_written_core_hash}-{_wvh}"
|
|
1884
|
+
_cache_mod.write_view(
|
|
1885
|
+
target,
|
|
1886
|
+
_view_key,
|
|
1887
|
+
content,
|
|
1888
|
+
fmt=format,
|
|
1889
|
+
layers=_compute_analyzer_fingerprints(),
|
|
1890
|
+
)
|
|
1891
|
+
# Trigger GC (evict old commits + orphaned views + CAS blobs)
|
|
1892
|
+
from sourcecode.cache import cache_dir as _cdir, _gc as _run_gc
|
|
1893
|
+
_run_gc(_cdir(target))
|
|
1894
|
+
except Exception:
|
|
1895
|
+
pass # non-fatal: cache write failure
|
|
1775
1896
|
|
|
1776
1897
|
if _pipeline_error:
|
|
1777
1898
|
raise typer.Exit(code=2)
|