sourcecode 1.31.30__tar.gz → 1.31.32__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.30 → sourcecode-1.31.32}/PKG-INFO +3 -3
- {sourcecode-1.31.30 → sourcecode-1.31.32}/README.md +2 -2
- {sourcecode-1.31.30 → sourcecode-1.31.32}/pyproject.toml +1 -1
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/cache.py +1 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/cli.py +167 -16
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/env_analyzer.py +6 -1
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/git_analyzer.py +3 -0
- sourcecode-1.31.32/src/sourcecode/license.py +229 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/mcp/onboarding/applier.py +14 -6
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/mcp/runner.py +2 -2
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/mcp/server.py +80 -14
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/mcp_nudge.py +6 -1
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/redactor.py +4 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/repository_ir.py +39 -3
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/serializer.py +1 -1
- sourcecode-1.31.32/tests/test_bug_fixes_v13130.py +449 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_enterprise_benchmarks.py +5 -4
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_mcp_tools.py +10 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/.continue-here.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/.gitignore +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/.ruff.toml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/.sourcecode-cache/snapshot-3b5997a-fa5c742c.json +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/AUDIT_REAL_REPOS.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/AUDIT_v1.31.23.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/CHANGELOG.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/CONTRIBUTING.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/LICENSE +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/SECURITY.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/docs/PRODUCT_TIERS.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/docs/privacy.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/docs/schema.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/raw +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/run_cli.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/__init__.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/conftest.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/latin1_sample.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/latin1_sample_iso.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/lcov.info +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_architecture_summary.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_ast_extractor.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_audit_fixes.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_audit_sas_v2.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_block1_reliability.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_block2_coverage.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_block5_quality.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_broadleaf_fixes.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_bug_fixes_v1302.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_bug_fixes_v13115.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_bug_fixes_v1312.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_bug_fixes_v13122.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_bug_fixes_v1313.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_bug_fixes_v1321.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_bug_fixes_v16.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_bug_fixes_v2.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_cache.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_canonical_ir.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_cli.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_context_scorer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_contract_pipeline.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_coverage_parser.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_cross_consistency.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_dependency_schema.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_detector_python.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_detectors_base.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_encoding_regression.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_graph_schema.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_integration.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_integration_detection.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_integration_docs.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_integration_lqn.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_integration_metrics.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_integration_multistack.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_integration_semantics.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_integration_universal.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_java_spring_integration.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_mcp_nudge.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_mcp_runner.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_mcp_serve.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_output_ux.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_packaging.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_pipeline_integrity.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_real_projects.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_redactor.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_repository_ir.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_scanner.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_schema.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_schema_normalization.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_scoring_calibration.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_semantic_schema.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_signal_hierarchy.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_summarizer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_surface_honesty.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_task_differentiation.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_telemetry.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_v131_improvements.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/tests/test_v1_10_regressions.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.32}/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.32
|
|
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.32
|
|
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.32
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
---
|
|
@@ -469,6 +469,7 @@ def _cas_load_blob(cache_d: Path, blob_hash: str) -> Optional[str]:
|
|
|
469
469
|
try:
|
|
470
470
|
return gzip.decompress(path.read_bytes()).decode("utf-8")
|
|
471
471
|
except Exception:
|
|
472
|
+
_safe_unlink(path) # evict corrupted blob so it doesn't block future reads
|
|
472
473
|
return None
|
|
473
474
|
|
|
474
475
|
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import hashlib
|
|
4
4
|
import json
|
|
5
|
+
import threading
|
|
5
6
|
import time
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any, Optional, cast
|
|
@@ -140,7 +141,7 @@ def _check_pipeline_coherence(sm: "SourceMap") -> list[str]: # type: ignore[nam
|
|
|
140
141
|
return issues
|
|
141
142
|
|
|
142
143
|
_HELP = """\
|
|
143
|
-
|
|
144
|
+
Deterministic codebase context for AI coding agents.
|
|
144
145
|
|
|
145
146
|
[bold]Primary usage:[/bold]
|
|
146
147
|
sourcecode --compact high-signal summary (~600-800 tokens)
|
|
@@ -171,12 +172,25 @@ _SUBCOMMANDS: frozenset[str] = frozenset(
|
|
|
171
172
|
"repo-ir", "mcp", "endpoints", "impact",
|
|
172
173
|
# Enterprise workflow commands
|
|
173
174
|
"onboard", "modernize", "fix-bug", "review-pr",
|
|
175
|
+
# License
|
|
176
|
+
"activate",
|
|
174
177
|
}
|
|
175
178
|
)
|
|
176
179
|
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
|
|
180
|
+
# Thread-local storage for the path extracted by _preprocess_argv().
|
|
181
|
+
# Using threading.local() prevents concurrent MCP tool calls from clobbering
|
|
182
|
+
# each other's target path (the old module-level list was a shared mutable global).
|
|
183
|
+
_tls = threading.local()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _get_detected_path() -> str:
|
|
187
|
+
"""Return the thread-local detected path, defaulting to '.'."""
|
|
188
|
+
return _tls.__dict__.get("detected_path", ".")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _set_detected_path(value: str) -> None:
|
|
192
|
+
"""Set the thread-local detected path."""
|
|
193
|
+
_tls.detected_path = value
|
|
180
194
|
|
|
181
195
|
|
|
182
196
|
# Options that take a value token — their next arg must not be treated as a path.
|
|
@@ -212,6 +226,7 @@ def _preprocess_args(args: list[str]) -> list[str]:
|
|
|
212
226
|
"""
|
|
213
227
|
result = list(args)
|
|
214
228
|
skip_next = False
|
|
229
|
+
_path_index: int = -1
|
|
215
230
|
for i, arg in enumerate(result):
|
|
216
231
|
if skip_next:
|
|
217
232
|
skip_next = False
|
|
@@ -225,9 +240,11 @@ def _preprocess_args(args: list[str]) -> list[str]:
|
|
|
225
240
|
if arg in _SUBCOMMANDS:
|
|
226
241
|
return result # known subcommand — leave for Click to dispatch
|
|
227
242
|
# First genuine positional: treat as repository path
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
243
|
+
_set_detected_path(arg)
|
|
244
|
+
_path_index = i
|
|
245
|
+
break
|
|
246
|
+
if _path_index >= 0:
|
|
247
|
+
result.pop(_path_index)
|
|
231
248
|
return result
|
|
232
249
|
|
|
233
250
|
|
|
@@ -253,6 +270,35 @@ def _emit_error_json(error: str, message: str, **context: object) -> None:
|
|
|
253
270
|
_sys.stderr.flush()
|
|
254
271
|
|
|
255
272
|
|
|
273
|
+
# H-06: Intercept Click-level UsageError (unknown options, bad args) and emit JSON.
|
|
274
|
+
# Click's default show() writes "Error: No such option: --foo" as plain text.
|
|
275
|
+
# Automation consumers need JSON on stderr regardless of how the error originated.
|
|
276
|
+
try:
|
|
277
|
+
import click.exceptions as _click_exc
|
|
278
|
+
|
|
279
|
+
def _json_click_usage_error_show(self: Any, file: Any = None) -> None: # type: ignore[override]
|
|
280
|
+
import json as _je
|
|
281
|
+
import sys as _jse
|
|
282
|
+
_code_map = {
|
|
283
|
+
"NoSuchOption": "invalid_option",
|
|
284
|
+
"BadOptionUsage": "invalid_option",
|
|
285
|
+
"BadParameter": "bad_parameter",
|
|
286
|
+
"MissingParameter": "missing_required",
|
|
287
|
+
"BadArgumentUsage": "bad_argument",
|
|
288
|
+
}
|
|
289
|
+
code = _code_map.get(type(self).__name__, "invalid_option")
|
|
290
|
+
payload: dict[str, object] = {"error": code, "message": self.format_message()}
|
|
291
|
+
_opt = getattr(self, "option_name", None) or getattr(self, "param_hint", None)
|
|
292
|
+
if _opt:
|
|
293
|
+
payload["flag"] = str(_opt).strip("'\"")
|
|
294
|
+
_jse.stderr.write(_je.dumps(payload, ensure_ascii=False) + "\n")
|
|
295
|
+
_jse.stderr.flush()
|
|
296
|
+
|
|
297
|
+
_click_exc.UsageError.show = _json_click_usage_error_show # type: ignore[method-assign]
|
|
298
|
+
except Exception:
|
|
299
|
+
pass # click unavailable — plain-text fallback
|
|
300
|
+
|
|
301
|
+
|
|
256
302
|
def _copy_to_clipboard(content: str) -> bool:
|
|
257
303
|
"""Copy text to system clipboard. Returns True on success, False otherwise (never raises)."""
|
|
258
304
|
import subprocess
|
|
@@ -303,7 +349,7 @@ def _get_command_with_preprocessing(typer_instance: Any) -> Any:
|
|
|
303
349
|
def _cmd_main(args: Optional[list[str]] = None, **kwargs: Any) -> Any:
|
|
304
350
|
if args is not None:
|
|
305
351
|
# CliRunner / programmatic call: preprocess the explicit args list.
|
|
306
|
-
|
|
352
|
+
_set_detected_path(".")
|
|
307
353
|
args = _preprocess_args(list(args))
|
|
308
354
|
# args=None → Click reads sys.argv; _preprocess_argv() in main_entry handled it.
|
|
309
355
|
return _orig_cmd_main(args=args, **kwargs)
|
|
@@ -332,7 +378,7 @@ app.add_typer(mcp_app, name="mcp")
|
|
|
332
378
|
def _maybe_ask_consent() -> None:
|
|
333
379
|
"""Show first-run consent prompt once, on interactive TTYs only."""
|
|
334
380
|
try:
|
|
335
|
-
from sourcecode.telemetry.config import has_been_asked,
|
|
381
|
+
from sourcecode.telemetry.config import has_been_asked, set_enabled
|
|
336
382
|
from sourcecode.telemetry.consent import ask_for_consent
|
|
337
383
|
if not has_been_asked():
|
|
338
384
|
enabled = ask_for_consent()
|
|
@@ -802,7 +848,7 @@ def main(
|
|
|
802
848
|
# Path was extracted from argv by _preprocess_argv() before Click ran.
|
|
803
849
|
# FIX-P2-8: preserve original user input in error messages (Windows Git Bash
|
|
804
850
|
# rewrites "/nonexistent" → "C:\Program Files\Git\nonexistent" via Path.resolve()).
|
|
805
|
-
_raw_path_input =
|
|
851
|
+
_raw_path_input = _get_detected_path()
|
|
806
852
|
target = Path(_raw_path_input).resolve()
|
|
807
853
|
if not target.exists():
|
|
808
854
|
_emit_error_json(
|
|
@@ -821,6 +867,7 @@ def main(
|
|
|
821
867
|
|
|
822
868
|
# Normalize mode aliases
|
|
823
869
|
_CONTRACT_MODES = frozenset({"contract", "minimal", "standard"})
|
|
870
|
+
_user_mode_explicit = mode not in ("contract",) # track if user passed a non-default value
|
|
824
871
|
if mode == "minimal":
|
|
825
872
|
mode = "contract" # minimal is a documented alias for contract
|
|
826
873
|
elif mode not in _CONTRACT_MODES and mode != "raw":
|
|
@@ -841,6 +888,20 @@ def main(
|
|
|
841
888
|
or docs or semantics or full_metrics or architecture
|
|
842
889
|
)
|
|
843
890
|
if mode in ("contract", "standard") and _legacy_flags_active:
|
|
891
|
+
if _user_mode_explicit:
|
|
892
|
+
_overriding_flags = [
|
|
893
|
+
f for f, v in [
|
|
894
|
+
("--compact", compact), ("--tree", tree),
|
|
895
|
+
("--trace-pipeline", trace_pipeline), ("--docs", docs),
|
|
896
|
+
("--semantics", semantics), ("--full-metrics", full_metrics),
|
|
897
|
+
("--architecture", architecture),
|
|
898
|
+
] if v
|
|
899
|
+
]
|
|
900
|
+
typer.echo(
|
|
901
|
+
f"[warning] --mode {mode} was overridden to raw because legacy flags "
|
|
902
|
+
f"({', '.join(_overriding_flags)}) require raw output mode.",
|
|
903
|
+
err=True,
|
|
904
|
+
)
|
|
844
905
|
mode = "raw"
|
|
845
906
|
|
|
846
907
|
# Map mode to contract_view depth
|
|
@@ -964,7 +1025,7 @@ def main(
|
|
|
964
1025
|
f"cn={code_notes},mode={mode},"
|
|
965
1026
|
f"ex={_excl_key},depth={effective_depth}"
|
|
966
1027
|
)
|
|
967
|
-
_core_h = _hashlib.
|
|
1028
|
+
_core_h = _hashlib.sha256(_core_flags_str.encode()).hexdigest()[:8]
|
|
968
1029
|
_core_key = f"{_git_sha}-{_core_h}"
|
|
969
1030
|
|
|
970
1031
|
# ── View flags: output presentation only (no re-analysis needed) ──
|
|
@@ -976,7 +1037,7 @@ def main(
|
|
|
976
1037
|
f"mn={max_nodes},ge={graph_edges},mi={max_importers},"
|
|
977
1038
|
f"eg={emit_graph}"
|
|
978
1039
|
)
|
|
979
|
-
_view_h = _hashlib.
|
|
1040
|
+
_view_h = _hashlib.sha256(_view_flags_str.encode()).hexdigest()[:8]
|
|
980
1041
|
|
|
981
1042
|
# ── Lookup ──────────────────────────────────────────────────────
|
|
982
1043
|
# Step 1: try L1 to obtain the core_hash needed for L2 key
|
|
@@ -1930,7 +1991,7 @@ def main(
|
|
|
1930
1991
|
if _written_core_hash:
|
|
1931
1992
|
if not _view_key:
|
|
1932
1993
|
# _view_key not set (L1 was also a miss); compute it now
|
|
1933
|
-
_wvh = _hashlib.
|
|
1994
|
+
_wvh = _hashlib.sha256(_view_flags_str.encode()).hexdigest()[:8]
|
|
1934
1995
|
_view_key = f"{_written_core_hash}-{_wvh}"
|
|
1935
1996
|
_cache_mod.write_view(
|
|
1936
1997
|
target,
|
|
@@ -2165,6 +2226,12 @@ def prepare_context_cmd(
|
|
|
2165
2226
|
)
|
|
2166
2227
|
raise typer.Exit(code=1)
|
|
2167
2228
|
|
|
2229
|
+
# Pro gate: generate-tests and delta require an active Pro license.
|
|
2230
|
+
_PRO_TASKS: frozenset[str] = frozenset({"generate-tests", "delta"})
|
|
2231
|
+
if task in _PRO_TASKS:
|
|
2232
|
+
from sourcecode.license import require_pro as _require_pro
|
|
2233
|
+
_require_pro(task)
|
|
2234
|
+
|
|
2168
2235
|
# Validate --format: only "json" and "github-comment" are valid for prepare-context.
|
|
2169
2236
|
# "yaml" is intentionally NOT supported here (use main command for yaml output).
|
|
2170
2237
|
# Invalid values must error loudly — silently falling through to JSON is a lie.
|
|
@@ -2222,7 +2289,49 @@ def prepare_context_cmd(
|
|
|
2222
2289
|
_sys.stderr.flush()
|
|
2223
2290
|
_t0 = _time.perf_counter()
|
|
2224
2291
|
try:
|
|
2225
|
-
|
|
2292
|
+
# H-02: apply timeout for generate-tests — large repos can stall indefinitely.
|
|
2293
|
+
# Mirrors SOURCECODE_TESTS_TIMEOUT_MS used by the MCP generate_tests_context tool.
|
|
2294
|
+
if task == "generate-tests" and not fast:
|
|
2295
|
+
import concurrent.futures as _cf
|
|
2296
|
+
import os as _os_gt
|
|
2297
|
+
_timeout_ms = int(_os_gt.environ.get("SOURCECODE_TESTS_TIMEOUT_MS", "30000"))
|
|
2298
|
+
_timeout_s = _timeout_ms / 1000.0
|
|
2299
|
+
_ex = _cf.ThreadPoolExecutor(max_workers=1)
|
|
2300
|
+
_fut = _ex.submit(
|
|
2301
|
+
builder.build, task,
|
|
2302
|
+
since=since, symptom=symptom, fast=fast,
|
|
2303
|
+
include_config=include_config, all_gaps=all_gaps,
|
|
2304
|
+
)
|
|
2305
|
+
_done_set, _nd_set = _cf.wait([_fut], timeout=_timeout_s)
|
|
2306
|
+
_ex.shutdown(wait=False)
|
|
2307
|
+
if _nd_set:
|
|
2308
|
+
import sys as _sys_gt
|
|
2309
|
+
if _sys_gt.stderr.isatty():
|
|
2310
|
+
_sys_gt.stderr.write(
|
|
2311
|
+
f"[generate-tests] timeout after {_timeout_ms}ms — returning partial result\n"
|
|
2312
|
+
)
|
|
2313
|
+
_sys_gt.stderr.flush()
|
|
2314
|
+
from sourcecode.prepare_context import TaskOutput as _TO
|
|
2315
|
+
output = _TO(
|
|
2316
|
+
task=task,
|
|
2317
|
+
goal=TASKS[task].goal,
|
|
2318
|
+
project_summary=None,
|
|
2319
|
+
architecture_summary=None,
|
|
2320
|
+
relevant_files=[],
|
|
2321
|
+
suspected_areas=[],
|
|
2322
|
+
improvement_opportunities=[],
|
|
2323
|
+
test_gaps=[],
|
|
2324
|
+
key_dependencies=[],
|
|
2325
|
+
code_notes_summary=None,
|
|
2326
|
+
limitations=[f"generate-tests timed out after {_timeout_ms}ms"],
|
|
2327
|
+
truncated=True,
|
|
2328
|
+
truncated_reason=f"timeout_{_timeout_ms}ms",
|
|
2329
|
+
confidence="low",
|
|
2330
|
+
)
|
|
2331
|
+
else:
|
|
2332
|
+
output = _fut.result()
|
|
2333
|
+
else:
|
|
2334
|
+
output = builder.build(task, since=since, symptom=symptom, fast=fast, include_config=include_config, all_gaps=all_gaps)
|
|
2226
2335
|
finally:
|
|
2227
2336
|
_progress.finish()
|
|
2228
2337
|
_t_total = (_time.perf_counter() - _t0) * 1000
|
|
@@ -2532,6 +2641,19 @@ def prepare_context_cmd(
|
|
|
2532
2641
|
if llm_prompt:
|
|
2533
2642
|
out["llm_prompt"] = builder.render_prompt(output)
|
|
2534
2643
|
|
|
2644
|
+
# H-01: fast-mode analysis transparency — consumer must not confuse "not analyzed"
|
|
2645
|
+
# with "analyzed and found nothing". Fields that were never computed are absent or null,
|
|
2646
|
+
# not zero. analysis_mode and skipped_analyzers make the omission explicit.
|
|
2647
|
+
if fast:
|
|
2648
|
+
out["analysis_mode"] = "fast"
|
|
2649
|
+
_skipped: list[str] = ["deep_content_scan"]
|
|
2650
|
+
_spec = TASKS.get(task)
|
|
2651
|
+
if _spec and _spec.enable_code_notes:
|
|
2652
|
+
_skipped.append("code_notes")
|
|
2653
|
+
if task == "generate-tests":
|
|
2654
|
+
_skipped.append("test_gap_discovery")
|
|
2655
|
+
out["skipped_analyzers"] = _skipped
|
|
2656
|
+
|
|
2535
2657
|
# P0-1: Apply output budget per task — safety net for large repos.
|
|
2536
2658
|
from sourcecode.output_budget import (
|
|
2537
2659
|
trim_to_budget as _pc_trim,
|
|
@@ -2864,6 +2986,9 @@ def impact_cmd(
|
|
|
2864
2986
|
sourcecode impact org.keycloak.services.DefaultKeycloakSession /path/to/keycloak
|
|
2865
2987
|
sourcecode impact UserService --depth 6 --output impact.json
|
|
2866
2988
|
"""
|
|
2989
|
+
from sourcecode.license import require_pro as _require_pro
|
|
2990
|
+
_require_pro("impact")
|
|
2991
|
+
|
|
2867
2992
|
import json as _json
|
|
2868
2993
|
import sys as _sys
|
|
2869
2994
|
|
|
@@ -2924,9 +3049,11 @@ def impact_cmd(
|
|
|
2924
3049
|
if _copy_to_clipboard(output):
|
|
2925
3050
|
typer.echo("✓ copied to clipboard", err=True)
|
|
2926
3051
|
|
|
2927
|
-
#
|
|
3052
|
+
# H-03: resolution=not_found is a valid structured answer, not an infra failure.
|
|
3053
|
+
# Exit 0 so pipelines can parse the JSON without treating it as an error.
|
|
3054
|
+
# Exit 1 is reserved for path-not-found, I/O failures, and real infra errors.
|
|
2928
3055
|
if result.get("resolution") == "not_found":
|
|
2929
|
-
raise typer.Exit(code=
|
|
3056
|
+
raise typer.Exit(code=0)
|
|
2930
3057
|
|
|
2931
3058
|
from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
|
|
2932
3059
|
_nudge()
|
|
@@ -3246,6 +3373,9 @@ def modernize_cmd(
|
|
|
3246
3373
|
sourcecode onboard . — Architecture overview first
|
|
3247
3374
|
sourcecode impact <target> — Verify impact before touching a hotspot
|
|
3248
3375
|
"""
|
|
3376
|
+
from sourcecode.license import require_pro as _require_pro
|
|
3377
|
+
_require_pro("modernize")
|
|
3378
|
+
|
|
3249
3379
|
import json as _json
|
|
3250
3380
|
import sys as _sys
|
|
3251
3381
|
from sourcecode.repository_ir import build_repo_ir, find_java_files, apply_ir_size_limits
|
|
@@ -3418,6 +3548,24 @@ def modernize_cmd(
|
|
|
3418
3548
|
|
|
3419
3549
|
# ── version ───────────────────────────────────────────────────────────────────
|
|
3420
3550
|
|
|
3551
|
+
@app.command("activate")
|
|
3552
|
+
def activate_cmd(
|
|
3553
|
+
license_key: str = typer.Argument(..., help="Your Pro license key"),
|
|
3554
|
+
) -> None:
|
|
3555
|
+
"""Activate a Pro license key.
|
|
3556
|
+
|
|
3557
|
+
\b
|
|
3558
|
+
Validates the key against the license server and writes
|
|
3559
|
+
~/.sourcecode/license.json.
|
|
3560
|
+
|
|
3561
|
+
\b
|
|
3562
|
+
Examples:
|
|
3563
|
+
sourcecode activate SC-XXXX-XXXX-XXXX
|
|
3564
|
+
"""
|
|
3565
|
+
from sourcecode.license import activate_license as _activate
|
|
3566
|
+
_activate(license_key)
|
|
3567
|
+
|
|
3568
|
+
|
|
3421
3569
|
@app.command("version")
|
|
3422
3570
|
def version_cmd() -> None:
|
|
3423
3571
|
"""Show version and exit."""
|
|
@@ -3471,6 +3619,9 @@ def mcp_serve() -> None:
|
|
|
3471
3619
|
}
|
|
3472
3620
|
}
|
|
3473
3621
|
"""
|
|
3622
|
+
from sourcecode.license import require_pro as _require_pro
|
|
3623
|
+
_require_pro("mcp serve")
|
|
3624
|
+
|
|
3474
3625
|
import logging
|
|
3475
3626
|
import sys as _sys
|
|
3476
3627
|
|
|
@@ -478,8 +478,13 @@ class EnvAnalyzer:
|
|
|
478
478
|
example_files_found: list,
|
|
479
479
|
limitations: list,
|
|
480
480
|
profiles_scanned: list,
|
|
481
|
+
depth: int = 0,
|
|
482
|
+
max_depth: int = 12,
|
|
481
483
|
) -> int:
|
|
482
484
|
"""Walk the directory tree accumulating env var findings. Returns spring_candidates count."""
|
|
485
|
+
if depth >= max_depth:
|
|
486
|
+
return 0
|
|
487
|
+
|
|
483
488
|
try:
|
|
484
489
|
entries = sorted(current.iterdir())
|
|
485
490
|
except PermissionError:
|
|
@@ -496,7 +501,7 @@ class EnvAnalyzer:
|
|
|
496
501
|
continue
|
|
497
502
|
total_spring_candidates += self._walk(
|
|
498
503
|
root, entry, findings, example_entries, example_files_found,
|
|
499
|
-
limitations, profiles_scanned,
|
|
504
|
+
limitations, profiles_scanned, depth + 1, max_depth,
|
|
500
505
|
)
|
|
501
506
|
elif entry.is_file():
|
|
502
507
|
rel = entry.relative_to(root).as_posix()
|
|
@@ -326,6 +326,9 @@ def _parse_uncommitted(output: str) -> "UncommittedChanges":
|
|
|
326
326
|
continue
|
|
327
327
|
x, y = line[0], line[1]
|
|
328
328
|
filepath = line[3:].strip()
|
|
329
|
+
# Renamed files: git porcelain v1 produces "old -> new"; keep only the new path.
|
|
330
|
+
if x == "R" and " -> " in filepath:
|
|
331
|
+
filepath = filepath.split(" -> ", 1)[1]
|
|
329
332
|
if x == "?" and y == "?":
|
|
330
333
|
untracked.append(filepath)
|
|
331
334
|
else:
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""License activation and enforcement for the sourcecode CLI.
|
|
2
|
+
|
|
3
|
+
Flow:
|
|
4
|
+
1. Module imported → _init() loads ~/.sourcecode/license.json (if present)
|
|
5
|
+
2. is_pro set globally (True when plan == "pro")
|
|
6
|
+
3. Pro commands call require_pro(feature_name) at entry — exits 1 if not Pro
|
|
7
|
+
4. `sourcecode activate <key>` calls activate_license(key) — validates via
|
|
8
|
+
Edge Function, writes ~/.sourcecode/license.json, exits 0 on success
|
|
9
|
+
5. Cached license is re-validated every 24 h (online); network errors keep
|
|
10
|
+
cached state (offline-first). Server-side invalidity clears cache.
|
|
11
|
+
|
|
12
|
+
Supabase credentials (baked in; override via env vars for testing):
|
|
13
|
+
SOURCECODE_SUPABASE_URL — project Edge Function base URL
|
|
14
|
+
SOURCECODE_SUPABASE_ANON_KEY — public anon key (not a secret)
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Optional
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Supabase endpoint config — hardcoded for production; override via env for dev
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
_SUPABASE_URL: str = os.environ.get(
|
|
29
|
+
"SOURCECODE_SUPABASE_URL",
|
|
30
|
+
"https://qkndlmyekvujjdgthtmz.supabase.co",
|
|
31
|
+
)
|
|
32
|
+
_SUPABASE_ANON_KEY: str = os.environ.get(
|
|
33
|
+
"SOURCECODE_SUPABASE_ANON_KEY",
|
|
34
|
+
"", # Set SOURCECODE_SUPABASE_ANON_KEY to your project anon key
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
_LICENSE_DIR: Path = Path.home() / ".sourcecode"
|
|
38
|
+
_LICENSE_FILE: Path = _LICENSE_DIR / "license.json"
|
|
39
|
+
_CACHE_TTL_SECONDS: int = 86400 # 24 hours
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Global license state — loaded once at import time
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
_license_data: Optional[dict] = None
|
|
45
|
+
is_pro: bool = False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _load_license_file() -> Optional[dict]:
|
|
49
|
+
"""Read ~/.sourcecode/license.json. Returns parsed dict or None."""
|
|
50
|
+
try:
|
|
51
|
+
if _LICENSE_FILE.exists():
|
|
52
|
+
raw = _LICENSE_FILE.read_text(encoding="utf-8")
|
|
53
|
+
return json.loads(raw)
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _call_get_license(license_key: str) -> Optional[dict]:
|
|
60
|
+
"""POST to /get-license edge function. Returns parsed dict or None on network error."""
|
|
61
|
+
import urllib.error
|
|
62
|
+
import urllib.request
|
|
63
|
+
|
|
64
|
+
if not _SUPABASE_ANON_KEY:
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
url = f"{_SUPABASE_URL}/functions/v1/get-license"
|
|
68
|
+
body = json.dumps({"license_key": license_key}).encode("utf-8")
|
|
69
|
+
req = urllib.request.Request(url, data=body, method="POST")
|
|
70
|
+
req.add_header("apikey", _SUPABASE_ANON_KEY)
|
|
71
|
+
req.add_header("Authorization", f"Bearer {_SUPABASE_ANON_KEY}")
|
|
72
|
+
req.add_header("Content-Type", "application/json")
|
|
73
|
+
req.add_header("Accept", "application/json")
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
with urllib.request.urlopen(req, timeout=8) as resp:
|
|
77
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
78
|
+
except urllib.error.HTTPError as exc:
|
|
79
|
+
try:
|
|
80
|
+
return json.loads(exc.read().decode("utf-8", errors="replace"))
|
|
81
|
+
except Exception:
|
|
82
|
+
return {"valid": False, "error": f"HTTP {exc.code}"}
|
|
83
|
+
except Exception:
|
|
84
|
+
return None # Network error — caller decides what to do
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _maybe_revalidate() -> None:
|
|
88
|
+
"""Re-validate cached license if stale. Mutates globals; never raises."""
|
|
89
|
+
global _license_data, is_pro
|
|
90
|
+
|
|
91
|
+
if not _license_data:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
validated_at_str = _license_data.get("validated_at") or _license_data.get("activated_at")
|
|
95
|
+
if validated_at_str:
|
|
96
|
+
try:
|
|
97
|
+
validated_at = datetime.fromisoformat(validated_at_str)
|
|
98
|
+
if validated_at.tzinfo is None:
|
|
99
|
+
validated_at = validated_at.replace(tzinfo=timezone.utc)
|
|
100
|
+
age = (datetime.now(timezone.utc) - validated_at).total_seconds()
|
|
101
|
+
if age < _CACHE_TTL_SECONDS:
|
|
102
|
+
return
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
key = _license_data.get("license_key")
|
|
107
|
+
if not key:
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
result = _call_get_license(key)
|
|
111
|
+
if result is None:
|
|
112
|
+
return # Network error — keep cached data (offline-first)
|
|
113
|
+
|
|
114
|
+
if not result.get("valid"):
|
|
115
|
+
_license_data = None
|
|
116
|
+
is_pro = False
|
|
117
|
+
try:
|
|
118
|
+
if _LICENSE_FILE.exists():
|
|
119
|
+
_LICENSE_FILE.unlink()
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
_license_data["plan"] = result.get("plan", "pro")
|
|
125
|
+
_license_data["features"] = result.get("features", [])
|
|
126
|
+
_license_data["validated_at"] = datetime.now(timezone.utc).isoformat()
|
|
127
|
+
is_pro = _license_data.get("plan") == "pro"
|
|
128
|
+
try:
|
|
129
|
+
_LICENSE_FILE.write_text(
|
|
130
|
+
json.dumps(_license_data, indent=2, ensure_ascii=False),
|
|
131
|
+
encoding="utf-8",
|
|
132
|
+
)
|
|
133
|
+
except Exception:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _init() -> None:
|
|
138
|
+
global _license_data, is_pro
|
|
139
|
+
_license_data = _load_license_file()
|
|
140
|
+
is_pro = (
|
|
141
|
+
_license_data is not None
|
|
142
|
+
and _license_data.get("plan") == "pro"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
_init()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
# Enforcement
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
def require_pro(feature_name: str) -> None:
|
|
154
|
+
"""Exit with structured JSON error when not Pro.
|
|
155
|
+
|
|
156
|
+
Re-validates stale cached license before gating (once per 24 h, online).
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
from sourcecode.license import require_pro
|
|
160
|
+
require_pro("impact")
|
|
161
|
+
"""
|
|
162
|
+
if is_pro:
|
|
163
|
+
_maybe_revalidate()
|
|
164
|
+
|
|
165
|
+
if not is_pro:
|
|
166
|
+
payload = {
|
|
167
|
+
"error": "pro_required",
|
|
168
|
+
"feature": feature_name,
|
|
169
|
+
"message": "Run: sourcecode activate <license_key>",
|
|
170
|
+
}
|
|
171
|
+
sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
172
|
+
sys.stdout.flush()
|
|
173
|
+
sys.exit(1)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
# Activation
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
def activate_license(license_key: str) -> None:
|
|
181
|
+
"""Validate license_key via Edge Function, write ~/.sourcecode/license.json.
|
|
182
|
+
|
|
183
|
+
Outputs JSON to stdout; exits 0 on success, 1 on any failure.
|
|
184
|
+
Never raises — all error paths emit JSON and call sys.exit(1).
|
|
185
|
+
"""
|
|
186
|
+
if not _SUPABASE_ANON_KEY:
|
|
187
|
+
_fail("configuration_error", "SOURCECODE_SUPABASE_ANON_KEY not set. Contact support.")
|
|
188
|
+
|
|
189
|
+
result = _call_get_license(license_key)
|
|
190
|
+
|
|
191
|
+
if result is None:
|
|
192
|
+
_fail("network_error", "Could not reach license server. Check your internet connection.")
|
|
193
|
+
|
|
194
|
+
if not result.get("valid"):
|
|
195
|
+
_fail("invalid_license", result.get("error", "License key is not valid or subscription is inactive."))
|
|
196
|
+
|
|
197
|
+
if result.get("plan") != "pro":
|
|
198
|
+
_fail("not_pro", "This license is not a Pro license.")
|
|
199
|
+
|
|
200
|
+
_LICENSE_DIR.mkdir(parents=True, exist_ok=True)
|
|
201
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
202
|
+
data = {
|
|
203
|
+
"license_key": license_key,
|
|
204
|
+
"plan": result["plan"],
|
|
205
|
+
"features": result.get("features", []),
|
|
206
|
+
"email": result.get("email", ""),
|
|
207
|
+
"activated_at": now,
|
|
208
|
+
"validated_at": now,
|
|
209
|
+
}
|
|
210
|
+
_LICENSE_FILE.write_text(
|
|
211
|
+
json.dumps(data, indent=2, ensure_ascii=False),
|
|
212
|
+
encoding="utf-8",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
output = {"status": "activated", "plan": "pro", "features": data["features"]}
|
|
216
|
+
sys.stdout.write(json.dumps(output, ensure_ascii=False) + "\n")
|
|
217
|
+
sys.stdout.flush()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# ---------------------------------------------------------------------------
|
|
221
|
+
# Internal helper
|
|
222
|
+
# ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
def _fail(error: str, message: str) -> None:
|
|
225
|
+
"""Emit JSON error to stdout and exit 1. Never returns."""
|
|
226
|
+
payload = {"error": error, "message": message}
|
|
227
|
+
sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
228
|
+
sys.stdout.flush()
|
|
229
|
+
sys.exit(1)
|