sourcecode 1.31.30__tar.gz → 1.31.31__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.31}/PKG-INFO +3 -3
- {sourcecode-1.31.30 → sourcecode-1.31.31}/README.md +2 -2
- {sourcecode-1.31.30 → sourcecode-1.31.31}/pyproject.toml +1 -1
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/cli.py +124 -3
- sourcecode-1.31.31/src/sourcecode/license.py +166 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/mcp/server.py +63 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/repository_ir.py +38 -2
- sourcecode-1.31.31/tests/test_bug_fixes_v13130.py +449 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_enterprise_benchmarks.py +5 -4
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_mcp_tools.py +10 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/.continue-here.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/.gitignore +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/.ruff.toml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/.sourcecode-cache/snapshot-3b5997a-fa5c742c.json +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/AUDIT_REAL_REPOS.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/AUDIT_v1.31.23.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/CHANGELOG.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/CONTRIBUTING.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/LICENSE +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/SECURITY.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/docs/PRODUCT_TIERS.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/docs/privacy.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/docs/schema.md +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/raw +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/run_cli.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/cache.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/serializer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/__init__.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/conftest.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/latin1_sample.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/latin1_sample_iso.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/lcov.info +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/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.31}/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.31}/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.31}/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.31}/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.31}/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.31}/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.31}/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.31}/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.31}/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.31}/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.31}/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.31}/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.31}/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.31}/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.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_architecture_summary.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_ast_extractor.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_audit_fixes.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_audit_sas_v2.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_block1_reliability.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_block2_coverage.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_block5_quality.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_broadleaf_fixes.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_bug_fixes_v1302.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_bug_fixes_v13115.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_bug_fixes_v1312.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_bug_fixes_v13122.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_bug_fixes_v1313.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_bug_fixes_v1321.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_bug_fixes_v16.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_bug_fixes_v2.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_cache.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_canonical_ir.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_classifier.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_cli.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_context_scorer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_contract_pipeline.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_coverage_parser.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_cross_consistency.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_dependency_schema.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_detector_python.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_detectors_base.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_encoding_regression.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_graph_schema.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_integration.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_integration_detection.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_integration_docs.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_integration_lqn.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_integration_metrics.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_integration_multistack.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_integration_semantics.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_integration_universal.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_java_spring_integration.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_mcp_nudge.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_mcp_runner.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_mcp_serve.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_output_ux.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_packaging.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_pipeline_integrity.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_real_projects.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_redactor.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_repository_ir.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_scanner.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_schema.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_schema_normalization.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_scoring_calibration.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_semantic_schema.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_signal_hierarchy.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_summarizer.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_surface_honesty.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_task_differentiation.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_telemetry.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_v131_improvements.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/tests/test_v1_10_regressions.py +0 -0
- {sourcecode-1.31.30 → sourcecode-1.31.31}/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.31
|
|
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.31
|
|
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.31
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
---
|
|
@@ -171,6 +171,8 @@ _SUBCOMMANDS: frozenset[str] = frozenset(
|
|
|
171
171
|
"repo-ir", "mcp", "endpoints", "impact",
|
|
172
172
|
# Enterprise workflow commands
|
|
173
173
|
"onboard", "modernize", "fix-bug", "review-pr",
|
|
174
|
+
# License
|
|
175
|
+
"activate",
|
|
174
176
|
}
|
|
175
177
|
)
|
|
176
178
|
|
|
@@ -253,6 +255,35 @@ def _emit_error_json(error: str, message: str, **context: object) -> None:
|
|
|
253
255
|
_sys.stderr.flush()
|
|
254
256
|
|
|
255
257
|
|
|
258
|
+
# H-06: Intercept Click-level UsageError (unknown options, bad args) and emit JSON.
|
|
259
|
+
# Click's default show() writes "Error: No such option: --foo" as plain text.
|
|
260
|
+
# Automation consumers need JSON on stderr regardless of how the error originated.
|
|
261
|
+
try:
|
|
262
|
+
import click.exceptions as _click_exc
|
|
263
|
+
|
|
264
|
+
def _json_click_usage_error_show(self: Any, file: Any = None) -> None: # type: ignore[override]
|
|
265
|
+
import json as _je
|
|
266
|
+
import sys as _jse
|
|
267
|
+
_code_map = {
|
|
268
|
+
"NoSuchOption": "invalid_option",
|
|
269
|
+
"BadOptionUsage": "invalid_option",
|
|
270
|
+
"BadParameter": "bad_parameter",
|
|
271
|
+
"MissingParameter": "missing_required",
|
|
272
|
+
"BadArgumentUsage": "bad_argument",
|
|
273
|
+
}
|
|
274
|
+
code = _code_map.get(type(self).__name__, "invalid_option")
|
|
275
|
+
payload: dict[str, object] = {"error": code, "message": self.format_message()}
|
|
276
|
+
_opt = getattr(self, "option_name", None) or getattr(self, "param_hint", None)
|
|
277
|
+
if _opt:
|
|
278
|
+
payload["flag"] = str(_opt).strip("'\"")
|
|
279
|
+
_jse.stderr.write(_je.dumps(payload, ensure_ascii=False) + "\n")
|
|
280
|
+
_jse.stderr.flush()
|
|
281
|
+
|
|
282
|
+
_click_exc.UsageError.show = _json_click_usage_error_show # type: ignore[method-assign]
|
|
283
|
+
except Exception:
|
|
284
|
+
pass # click unavailable — plain-text fallback
|
|
285
|
+
|
|
286
|
+
|
|
256
287
|
def _copy_to_clipboard(content: str) -> bool:
|
|
257
288
|
"""Copy text to system clipboard. Returns True on success, False otherwise (never raises)."""
|
|
258
289
|
import subprocess
|
|
@@ -2165,6 +2196,12 @@ def prepare_context_cmd(
|
|
|
2165
2196
|
)
|
|
2166
2197
|
raise typer.Exit(code=1)
|
|
2167
2198
|
|
|
2199
|
+
# Pro gate: generate-tests and delta require an active Pro license.
|
|
2200
|
+
_PRO_TASKS: frozenset[str] = frozenset({"generate-tests", "delta"})
|
|
2201
|
+
if task in _PRO_TASKS:
|
|
2202
|
+
from sourcecode.license import require_pro as _require_pro
|
|
2203
|
+
_require_pro(task)
|
|
2204
|
+
|
|
2168
2205
|
# Validate --format: only "json" and "github-comment" are valid for prepare-context.
|
|
2169
2206
|
# "yaml" is intentionally NOT supported here (use main command for yaml output).
|
|
2170
2207
|
# Invalid values must error loudly — silently falling through to JSON is a lie.
|
|
@@ -2222,7 +2259,49 @@ def prepare_context_cmd(
|
|
|
2222
2259
|
_sys.stderr.flush()
|
|
2223
2260
|
_t0 = _time.perf_counter()
|
|
2224
2261
|
try:
|
|
2225
|
-
|
|
2262
|
+
# H-02: apply timeout for generate-tests — large repos can stall indefinitely.
|
|
2263
|
+
# Mirrors SOURCECODE_TESTS_TIMEOUT_MS used by the MCP generate_tests_context tool.
|
|
2264
|
+
if task == "generate-tests" and not fast:
|
|
2265
|
+
import concurrent.futures as _cf
|
|
2266
|
+
import os as _os_gt
|
|
2267
|
+
_timeout_ms = int(_os_gt.environ.get("SOURCECODE_TESTS_TIMEOUT_MS", "30000"))
|
|
2268
|
+
_timeout_s = _timeout_ms / 1000.0
|
|
2269
|
+
_ex = _cf.ThreadPoolExecutor(max_workers=1)
|
|
2270
|
+
_fut = _ex.submit(
|
|
2271
|
+
builder.build, task,
|
|
2272
|
+
since=since, symptom=symptom, fast=fast,
|
|
2273
|
+
include_config=include_config, all_gaps=all_gaps,
|
|
2274
|
+
)
|
|
2275
|
+
_done_set, _nd_set = _cf.wait([_fut], timeout=_timeout_s)
|
|
2276
|
+
_ex.shutdown(wait=False)
|
|
2277
|
+
if _nd_set:
|
|
2278
|
+
import sys as _sys_gt
|
|
2279
|
+
if _sys_gt.stderr.isatty():
|
|
2280
|
+
_sys_gt.stderr.write(
|
|
2281
|
+
f"[generate-tests] timeout after {_timeout_ms}ms — returning partial result\n"
|
|
2282
|
+
)
|
|
2283
|
+
_sys_gt.stderr.flush()
|
|
2284
|
+
from sourcecode.prepare_context import TaskOutput as _TO
|
|
2285
|
+
output = _TO(
|
|
2286
|
+
task=task,
|
|
2287
|
+
goal=TASKS[task].goal,
|
|
2288
|
+
project_summary=None,
|
|
2289
|
+
architecture_summary=None,
|
|
2290
|
+
relevant_files=[],
|
|
2291
|
+
suspected_areas=[],
|
|
2292
|
+
improvement_opportunities=[],
|
|
2293
|
+
test_gaps=[],
|
|
2294
|
+
key_dependencies=[],
|
|
2295
|
+
code_notes_summary=None,
|
|
2296
|
+
limitations=[f"generate-tests timed out after {_timeout_ms}ms"],
|
|
2297
|
+
truncated=True,
|
|
2298
|
+
truncated_reason=f"timeout_{_timeout_ms}ms",
|
|
2299
|
+
confidence="low",
|
|
2300
|
+
)
|
|
2301
|
+
else:
|
|
2302
|
+
output = _fut.result()
|
|
2303
|
+
else:
|
|
2304
|
+
output = builder.build(task, since=since, symptom=symptom, fast=fast, include_config=include_config, all_gaps=all_gaps)
|
|
2226
2305
|
finally:
|
|
2227
2306
|
_progress.finish()
|
|
2228
2307
|
_t_total = (_time.perf_counter() - _t0) * 1000
|
|
@@ -2532,6 +2611,19 @@ def prepare_context_cmd(
|
|
|
2532
2611
|
if llm_prompt:
|
|
2533
2612
|
out["llm_prompt"] = builder.render_prompt(output)
|
|
2534
2613
|
|
|
2614
|
+
# H-01: fast-mode analysis transparency — consumer must not confuse "not analyzed"
|
|
2615
|
+
# with "analyzed and found nothing". Fields that were never computed are absent or null,
|
|
2616
|
+
# not zero. analysis_mode and skipped_analyzers make the omission explicit.
|
|
2617
|
+
if fast:
|
|
2618
|
+
out["analysis_mode"] = "fast"
|
|
2619
|
+
_skipped: list[str] = ["deep_content_scan"]
|
|
2620
|
+
_spec = TASKS.get(task)
|
|
2621
|
+
if _spec and _spec.enable_code_notes:
|
|
2622
|
+
_skipped.append("code_notes")
|
|
2623
|
+
if task == "generate-tests":
|
|
2624
|
+
_skipped.append("test_gap_discovery")
|
|
2625
|
+
out["skipped_analyzers"] = _skipped
|
|
2626
|
+
|
|
2535
2627
|
# P0-1: Apply output budget per task — safety net for large repos.
|
|
2536
2628
|
from sourcecode.output_budget import (
|
|
2537
2629
|
trim_to_budget as _pc_trim,
|
|
@@ -2864,6 +2956,9 @@ def impact_cmd(
|
|
|
2864
2956
|
sourcecode impact org.keycloak.services.DefaultKeycloakSession /path/to/keycloak
|
|
2865
2957
|
sourcecode impact UserService --depth 6 --output impact.json
|
|
2866
2958
|
"""
|
|
2959
|
+
from sourcecode.license import require_pro as _require_pro
|
|
2960
|
+
_require_pro("impact")
|
|
2961
|
+
|
|
2867
2962
|
import json as _json
|
|
2868
2963
|
import sys as _sys
|
|
2869
2964
|
|
|
@@ -2924,9 +3019,11 @@ def impact_cmd(
|
|
|
2924
3019
|
if _copy_to_clipboard(output):
|
|
2925
3020
|
typer.echo("✓ copied to clipboard", err=True)
|
|
2926
3021
|
|
|
2927
|
-
#
|
|
3022
|
+
# H-03: resolution=not_found is a valid structured answer, not an infra failure.
|
|
3023
|
+
# Exit 0 so pipelines can parse the JSON without treating it as an error.
|
|
3024
|
+
# Exit 1 is reserved for path-not-found, I/O failures, and real infra errors.
|
|
2928
3025
|
if result.get("resolution") == "not_found":
|
|
2929
|
-
raise typer.Exit(code=
|
|
3026
|
+
raise typer.Exit(code=0)
|
|
2930
3027
|
|
|
2931
3028
|
from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
|
|
2932
3029
|
_nudge()
|
|
@@ -3246,6 +3343,9 @@ def modernize_cmd(
|
|
|
3246
3343
|
sourcecode onboard . — Architecture overview first
|
|
3247
3344
|
sourcecode impact <target> — Verify impact before touching a hotspot
|
|
3248
3345
|
"""
|
|
3346
|
+
from sourcecode.license import require_pro as _require_pro
|
|
3347
|
+
_require_pro("modernize")
|
|
3348
|
+
|
|
3249
3349
|
import json as _json
|
|
3250
3350
|
import sys as _sys
|
|
3251
3351
|
from sourcecode.repository_ir import build_repo_ir, find_java_files, apply_ir_size_limits
|
|
@@ -3418,6 +3518,24 @@ def modernize_cmd(
|
|
|
3418
3518
|
|
|
3419
3519
|
# ── version ───────────────────────────────────────────────────────────────────
|
|
3420
3520
|
|
|
3521
|
+
@app.command("activate")
|
|
3522
|
+
def activate_cmd(
|
|
3523
|
+
license_key: str = typer.Argument(..., help="Your Pro license key"),
|
|
3524
|
+
) -> None:
|
|
3525
|
+
"""Activate a Pro license key.
|
|
3526
|
+
|
|
3527
|
+
\b
|
|
3528
|
+
Validates the key against the license server and writes
|
|
3529
|
+
~/.sourcecode/license.json.
|
|
3530
|
+
|
|
3531
|
+
\b
|
|
3532
|
+
Examples:
|
|
3533
|
+
sourcecode activate SC-XXXX-XXXX-XXXX
|
|
3534
|
+
"""
|
|
3535
|
+
from sourcecode.license import activate_license as _activate
|
|
3536
|
+
_activate(license_key)
|
|
3537
|
+
|
|
3538
|
+
|
|
3421
3539
|
@app.command("version")
|
|
3422
3540
|
def version_cmd() -> None:
|
|
3423
3541
|
"""Show version and exit."""
|
|
@@ -3471,6 +3589,9 @@ def mcp_serve() -> None:
|
|
|
3471
3589
|
}
|
|
3472
3590
|
}
|
|
3473
3591
|
"""
|
|
3592
|
+
from sourcecode.license import require_pro as _require_pro
|
|
3593
|
+
_require_pro("mcp serve")
|
|
3594
|
+
|
|
3474
3595
|
import logging
|
|
3475
3596
|
import sys as _sys
|
|
3476
3597
|
|
|
@@ -0,0 +1,166 @@
|
|
|
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
|
+
Supabase REST, writes ~/.sourcecode/license.json, exits 0 on success
|
|
9
|
+
|
|
10
|
+
Supabase credentials:
|
|
11
|
+
SOURCECODE_SUPABASE_URL — project REST endpoint
|
|
12
|
+
SOURCECODE_SUPABASE_ANON_KEY — public anon key (not a secret)
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Supabase endpoint config — override via env vars
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
_SUPABASE_URL: str = os.environ.get(
|
|
27
|
+
"SOURCECODE_SUPABASE_URL",
|
|
28
|
+
"https://YOUR_PROJECT.supabase.co",
|
|
29
|
+
)
|
|
30
|
+
_SUPABASE_ANON_KEY: str = os.environ.get(
|
|
31
|
+
"SOURCECODE_SUPABASE_ANON_KEY",
|
|
32
|
+
"",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
_LICENSE_DIR: Path = Path.home() / ".sourcecode"
|
|
36
|
+
_LICENSE_FILE: Path = _LICENSE_DIR / "license.json"
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Global license state — loaded once at import time
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
_license_data: Optional[dict] = None
|
|
42
|
+
is_pro: bool = False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _load_license_file() -> Optional[dict]:
|
|
46
|
+
"""Read ~/.sourcecode/license.json. Returns parsed dict or None."""
|
|
47
|
+
try:
|
|
48
|
+
if _LICENSE_FILE.exists():
|
|
49
|
+
raw = _LICENSE_FILE.read_text(encoding="utf-8")
|
|
50
|
+
return json.loads(raw)
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _init() -> None:
|
|
57
|
+
global _license_data, is_pro
|
|
58
|
+
_license_data = _load_license_file()
|
|
59
|
+
is_pro = (
|
|
60
|
+
_license_data is not None
|
|
61
|
+
and _license_data.get("plan") == "pro"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
_init()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# Enforcement
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
def require_pro(feature_name: str) -> None:
|
|
73
|
+
"""Exit with structured JSON error when not Pro.
|
|
74
|
+
|
|
75
|
+
Call at the very top of every Pro-gated command, before any work.
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
from sourcecode.license import require_pro
|
|
79
|
+
require_pro("impact")
|
|
80
|
+
"""
|
|
81
|
+
if not is_pro:
|
|
82
|
+
payload = {
|
|
83
|
+
"error": "pro_required",
|
|
84
|
+
"feature": feature_name,
|
|
85
|
+
"message": "Run sourcecode activate <license_key>",
|
|
86
|
+
}
|
|
87
|
+
sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
88
|
+
sys.stdout.flush()
|
|
89
|
+
sys.exit(1)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# Activation
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
def activate_license(license_key: str) -> None:
|
|
97
|
+
"""Validate license_key via Supabase, write ~/.sourcecode/license.json.
|
|
98
|
+
|
|
99
|
+
Outputs JSON to stdout; exits 0 on success, 1 on any failure.
|
|
100
|
+
Never raises — all error paths emit JSON and call sys.exit(1).
|
|
101
|
+
"""
|
|
102
|
+
import urllib.error
|
|
103
|
+
import urllib.request
|
|
104
|
+
|
|
105
|
+
# Bail early when Supabase isn't configured yet
|
|
106
|
+
if not _SUPABASE_ANON_KEY or _SUPABASE_URL == "https://YOUR_PROJECT.supabase.co":
|
|
107
|
+
_fail("configuration_error", "SOURCECODE_SUPABASE_URL / SOURCECODE_SUPABASE_ANON_KEY not configured.")
|
|
108
|
+
|
|
109
|
+
url = (
|
|
110
|
+
f"{_SUPABASE_URL}/rest/v1/users"
|
|
111
|
+
f"?license_key=eq.{license_key}"
|
|
112
|
+
f"&select=license_key,plan,email"
|
|
113
|
+
)
|
|
114
|
+
req = urllib.request.Request(url)
|
|
115
|
+
req.add_header("apikey", _SUPABASE_ANON_KEY)
|
|
116
|
+
req.add_header("Authorization", f"Bearer {_SUPABASE_ANON_KEY}")
|
|
117
|
+
req.add_header("Accept", "application/json")
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
121
|
+
body = resp.read().decode("utf-8")
|
|
122
|
+
except urllib.error.HTTPError as exc:
|
|
123
|
+
_fail("network_error", f"Supabase returned HTTP {exc.code}")
|
|
124
|
+
except Exception as exc:
|
|
125
|
+
_fail("network_error", str(exc))
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
rows = json.loads(body)
|
|
129
|
+
except Exception:
|
|
130
|
+
_fail("network_error", "Invalid JSON response from Supabase")
|
|
131
|
+
|
|
132
|
+
if not rows:
|
|
133
|
+
_fail("invalid_license", "License key not found")
|
|
134
|
+
|
|
135
|
+
user = rows[0]
|
|
136
|
+
if user.get("plan") != "pro":
|
|
137
|
+
_fail("not_pro", "This license is not Pro")
|
|
138
|
+
|
|
139
|
+
# Write license file
|
|
140
|
+
_LICENSE_DIR.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
data = {
|
|
142
|
+
"license_key": license_key,
|
|
143
|
+
"plan": "pro",
|
|
144
|
+
"email": user.get("email", ""),
|
|
145
|
+
"activated_at": datetime.now(timezone.utc).isoformat(),
|
|
146
|
+
}
|
|
147
|
+
_LICENSE_FILE.write_text(
|
|
148
|
+
json.dumps(data, indent=2, ensure_ascii=False),
|
|
149
|
+
encoding="utf-8",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
result = {"status": "activated", "plan": "pro"}
|
|
153
|
+
sys.stdout.write(json.dumps(result, ensure_ascii=False) + "\n")
|
|
154
|
+
sys.stdout.flush()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
# Internal helper
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
def _fail(error: str, message: str) -> None:
|
|
162
|
+
"""Emit JSON error to stdout and exit 1. Never returns."""
|
|
163
|
+
payload = {"error": error, "message": message}
|
|
164
|
+
sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
165
|
+
sys.stdout.flush()
|
|
166
|
+
sys.exit(1)
|
|
@@ -88,6 +88,27 @@ def _normalize_repo_path(path: str) -> str:
|
|
|
88
88
|
return path
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def _check_repo_path(path: str) -> "CallToolResult | None":
|
|
92
|
+
"""H-05: Validate repo_path exists and is a directory before executing.
|
|
93
|
+
|
|
94
|
+
Returns a structured CallToolResult(isError=True) when the path is invalid,
|
|
95
|
+
or None when the path is valid. Must be called after _normalize_repo_path().
|
|
96
|
+
Early validation prevents the MCP server from hanging when the CLI exits
|
|
97
|
+
non-zero with an empty stdout (error went to stderr, not captured by runner).
|
|
98
|
+
"""
|
|
99
|
+
if not os.path.exists(path):
|
|
100
|
+
return _err(
|
|
101
|
+
f"directory_not_found: '{path}' does not exist.",
|
|
102
|
+
"DIRECTORY_NOT_FOUND",
|
|
103
|
+
)
|
|
104
|
+
if not os.path.isdir(path):
|
|
105
|
+
return _err(
|
|
106
|
+
f"not_a_directory: '{path}' is not a directory.",
|
|
107
|
+
"NOT_A_DIRECTORY",
|
|
108
|
+
)
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
|
|
91
112
|
@mcp.tool()
|
|
92
113
|
def get_compact_context(repo_path: str = ".", git_context: bool = False) -> dict:
|
|
93
114
|
"""Compact human/LLM summary of a repository (~1000-3000 tokens). USE THIS FIRST.
|
|
@@ -108,6 +129,9 @@ def get_compact_context(repo_path: str = ".", git_context: bool = False) -> dict
|
|
|
108
129
|
if not isinstance(git_context, bool):
|
|
109
130
|
return _err("git_context must be boolean", "INVALID_ARGUMENT")
|
|
110
131
|
repo_path = _normalize_repo_path(repo_path)
|
|
132
|
+
_path_err = _check_repo_path(repo_path)
|
|
133
|
+
if _path_err is not None:
|
|
134
|
+
return _path_err
|
|
111
135
|
args = [repo_path, "--compact"]
|
|
112
136
|
if git_context:
|
|
113
137
|
args.append("--git-context")
|
|
@@ -139,6 +163,9 @@ def get_agent_context(repo_path: str = ".", git_context: bool = False) -> dict:
|
|
|
139
163
|
if not isinstance(git_context, bool):
|
|
140
164
|
return _err("git_context must be boolean", "INVALID_ARGUMENT")
|
|
141
165
|
repo_path = _normalize_repo_path(repo_path)
|
|
166
|
+
_path_err = _check_repo_path(repo_path)
|
|
167
|
+
if _path_err is not None:
|
|
168
|
+
return _path_err
|
|
142
169
|
args = [repo_path, "--agent"]
|
|
143
170
|
if git_context:
|
|
144
171
|
args.append("--git-context")
|
|
@@ -174,6 +201,9 @@ def get_endpoints(repo_path: str = ".") -> dict:
|
|
|
174
201
|
if not isinstance(repo_path, str):
|
|
175
202
|
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
176
203
|
repo_path = _normalize_repo_path(repo_path)
|
|
204
|
+
_path_err = _check_repo_path(repo_path)
|
|
205
|
+
if _path_err is not None:
|
|
206
|
+
return _path_err
|
|
177
207
|
return _execute(["endpoints", repo_path])
|
|
178
208
|
except Exception as exc:
|
|
179
209
|
return _err(
|
|
@@ -197,6 +227,9 @@ def get_module_context(repo_path: str = ".", module: str = "") -> dict:
|
|
|
197
227
|
if not isinstance(module, str) or not module.strip():
|
|
198
228
|
return _err("module must be a non-empty string", "INVALID_ARGUMENT")
|
|
199
229
|
repo_path = _normalize_repo_path(repo_path)
|
|
230
|
+
_path_err = _check_repo_path(repo_path)
|
|
231
|
+
if _path_err is not None:
|
|
232
|
+
return _path_err
|
|
200
233
|
module_path = repo_path.rstrip("/") + "/" + module.strip("/")
|
|
201
234
|
return _execute([module_path, "--compact"])
|
|
202
235
|
except Exception as exc:
|
|
@@ -221,6 +254,9 @@ def get_delta(repo_path: str = ".", since: str = "HEAD~1") -> dict:
|
|
|
221
254
|
if not isinstance(since, str) or not since.strip():
|
|
222
255
|
return _err("since must be a non-empty git ref", "INVALID_ARGUMENT")
|
|
223
256
|
repo_path = _normalize_repo_path(repo_path)
|
|
257
|
+
_path_err = _check_repo_path(repo_path)
|
|
258
|
+
if _path_err is not None:
|
|
259
|
+
return _path_err
|
|
224
260
|
return _execute(["prepare-context", "delta", repo_path, "--since", since])
|
|
225
261
|
except Exception as exc:
|
|
226
262
|
return _err(
|
|
@@ -248,6 +284,9 @@ def get_ir_summary(repo_path: str = ".") -> dict:
|
|
|
248
284
|
if not isinstance(repo_path, str):
|
|
249
285
|
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
250
286
|
repo_path = _normalize_repo_path(repo_path)
|
|
287
|
+
_path_err = _check_repo_path(repo_path)
|
|
288
|
+
if _path_err is not None:
|
|
289
|
+
return _path_err
|
|
251
290
|
return _execute(["repo-ir", repo_path, "--summary-only"])
|
|
252
291
|
except Exception as exc:
|
|
253
292
|
return _err(
|
|
@@ -271,6 +310,9 @@ def fix_bug_context(repo_path: str = ".", symptom: str = "") -> dict:
|
|
|
271
310
|
if not isinstance(repo_path, str):
|
|
272
311
|
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
273
312
|
repo_path = _normalize_repo_path(repo_path)
|
|
313
|
+
_path_err = _check_repo_path(repo_path)
|
|
314
|
+
if _path_err is not None:
|
|
315
|
+
return _path_err
|
|
274
316
|
args = ["prepare-context", "fix-bug", repo_path]
|
|
275
317
|
if symptom and isinstance(symptom, str) and symptom.strip():
|
|
276
318
|
args.extend(["--symptom", symptom.strip()])
|
|
@@ -297,6 +339,9 @@ def review_pr_context(repo_path: str = ".", since: str = "") -> dict:
|
|
|
297
339
|
if not isinstance(repo_path, str):
|
|
298
340
|
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
299
341
|
repo_path = _normalize_repo_path(repo_path)
|
|
342
|
+
_path_err = _check_repo_path(repo_path)
|
|
343
|
+
if _path_err is not None:
|
|
344
|
+
return _path_err
|
|
300
345
|
args = ["prepare-context", "review-pr", repo_path]
|
|
301
346
|
if since and isinstance(since, str) and since.strip():
|
|
302
347
|
args.extend(["--since", since.strip()])
|
|
@@ -320,6 +365,9 @@ def onboard_context(repo_path: str = ".") -> dict:
|
|
|
320
365
|
if not isinstance(repo_path, str):
|
|
321
366
|
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
322
367
|
repo_path = _normalize_repo_path(repo_path)
|
|
368
|
+
_path_err = _check_repo_path(repo_path)
|
|
369
|
+
if _path_err is not None:
|
|
370
|
+
return _path_err
|
|
323
371
|
return _execute(["prepare-context", "onboard", repo_path])
|
|
324
372
|
except Exception as exc:
|
|
325
373
|
return _err(
|
|
@@ -341,6 +389,9 @@ def explain_context(repo_path: str = ".") -> dict:
|
|
|
341
389
|
if not isinstance(repo_path, str):
|
|
342
390
|
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
343
391
|
repo_path = _normalize_repo_path(repo_path)
|
|
392
|
+
_path_err = _check_repo_path(repo_path)
|
|
393
|
+
if _path_err is not None:
|
|
394
|
+
return _path_err
|
|
344
395
|
return _execute(["prepare-context", "explain", repo_path])
|
|
345
396
|
except Exception as exc:
|
|
346
397
|
return _err(
|
|
@@ -362,6 +413,9 @@ def refactor_context(repo_path: str = ".") -> dict:
|
|
|
362
413
|
if not isinstance(repo_path, str):
|
|
363
414
|
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
364
415
|
repo_path = _normalize_repo_path(repo_path)
|
|
416
|
+
_path_err = _check_repo_path(repo_path)
|
|
417
|
+
if _path_err is not None:
|
|
418
|
+
return _path_err
|
|
365
419
|
return _execute(["prepare-context", "refactor", repo_path])
|
|
366
420
|
except Exception as exc:
|
|
367
421
|
return _err(
|
|
@@ -388,6 +442,9 @@ def generate_tests_context(repo_path: str = ".", include_all: bool = False) -> d
|
|
|
388
442
|
if not isinstance(include_all, bool):
|
|
389
443
|
return _err("include_all must be boolean", "INVALID_ARGUMENT")
|
|
390
444
|
repo_path = _normalize_repo_path(repo_path)
|
|
445
|
+
_path_err = _check_repo_path(repo_path)
|
|
446
|
+
if _path_err is not None:
|
|
447
|
+
return _path_err
|
|
391
448
|
args = ["prepare-context", "generate-tests", repo_path]
|
|
392
449
|
if include_all:
|
|
393
450
|
args.append("--all")
|
|
@@ -444,6 +501,9 @@ def get_impact_context(repo_path: str = ".", target: str = "", depth: int = 4) -
|
|
|
444
501
|
if not isinstance(depth, int) or depth < 1 or depth > 8:
|
|
445
502
|
return _err("depth must be an integer between 1 and 8", "INVALID_ARGUMENT")
|
|
446
503
|
repo_path = _normalize_repo_path(repo_path)
|
|
504
|
+
_path_err = _check_repo_path(repo_path)
|
|
505
|
+
if _path_err is not None:
|
|
506
|
+
return _path_err
|
|
447
507
|
args = ["impact", target.strip(), repo_path, "--depth", str(depth)]
|
|
448
508
|
return _execute(args)
|
|
449
509
|
except Exception as exc:
|
|
@@ -475,6 +535,9 @@ def modernize_context(repo_path: str = ".", format: str = "json") -> dict:
|
|
|
475
535
|
if not isinstance(format, str) or format not in ("json", "yaml"):
|
|
476
536
|
return _err("format must be 'json' or 'yaml'", "INVALID_ARGUMENT")
|
|
477
537
|
repo_path = _normalize_repo_path(repo_path)
|
|
538
|
+
_path_err = _check_repo_path(repo_path)
|
|
539
|
+
if _path_err is not None:
|
|
540
|
+
return _path_err
|
|
478
541
|
return _execute(["modernize", repo_path])
|
|
479
542
|
except Exception as exc:
|
|
480
543
|
return _err(
|
|
@@ -1524,6 +1524,31 @@ def _get_git_old_content(git_root: Path, rel_path: str, since: str) -> Optional[
|
|
|
1524
1524
|
return None
|
|
1525
1525
|
|
|
1526
1526
|
|
|
1527
|
+
def _get_git_changed_files(root: "Path", since: str) -> "Optional[frozenset[str]]":
|
|
1528
|
+
"""H-04: Return set of paths changed between `since` and HEAD, or None on failure.
|
|
1529
|
+
|
|
1530
|
+
One `git diff --name-only` call replaces O(n) `git show` calls — only files
|
|
1531
|
+
in the returned set need old-content fetched for symbol diff computation.
|
|
1532
|
+
Returns None when git is unavailable or the ref cannot be resolved; the
|
|
1533
|
+
caller must fall back to the original per-file fetch in that case.
|
|
1534
|
+
"""
|
|
1535
|
+
try:
|
|
1536
|
+
result = subprocess.run(
|
|
1537
|
+
["git", "diff", "--name-only", since, "HEAD"],
|
|
1538
|
+
cwd=str(root),
|
|
1539
|
+
capture_output=True,
|
|
1540
|
+
text=True,
|
|
1541
|
+
encoding="utf-8",
|
|
1542
|
+
errors="replace",
|
|
1543
|
+
timeout=10,
|
|
1544
|
+
)
|
|
1545
|
+
if result.returncode == 0:
|
|
1546
|
+
return frozenset(p.strip() for p in result.stdout.splitlines() if p.strip())
|
|
1547
|
+
except (subprocess.TimeoutExpired, OSError, FileNotFoundError):
|
|
1548
|
+
pass
|
|
1549
|
+
return None
|
|
1550
|
+
|
|
1551
|
+
|
|
1527
1552
|
# ---------------------------------------------------------------------------
|
|
1528
1553
|
# Phase 5 — Evidence Engine
|
|
1529
1554
|
# ---------------------------------------------------------------------------
|
|
@@ -2511,6 +2536,12 @@ def build_repo_ir(
|
|
|
2511
2536
|
all_changed: list[ChangedSymbol] = []
|
|
2512
2537
|
all_route_diffs: list[dict] = []
|
|
2513
2538
|
|
|
2539
|
+
# H-04: prefetch changed-file list once; avoids O(n) `git show` calls.
|
|
2540
|
+
# _since_changed=None means git unavailable → fall back to per-file fetch.
|
|
2541
|
+
_since_changed: "Optional[frozenset[str]]" = None
|
|
2542
|
+
if since:
|
|
2543
|
+
_since_changed = _get_git_changed_files(root, since)
|
|
2544
|
+
|
|
2514
2545
|
for rel_path in sorted(file_paths):
|
|
2515
2546
|
abs_path = root / rel_path
|
|
2516
2547
|
try:
|
|
@@ -2520,7 +2551,11 @@ def build_repo_ir(
|
|
|
2520
2551
|
|
|
2521
2552
|
old_source: Optional[str] = None
|
|
2522
2553
|
if since:
|
|
2523
|
-
|
|
2554
|
+
# Only fetch old content for files known to have changed.
|
|
2555
|
+
# Unchanged files have no diff entries — skip git show entirely.
|
|
2556
|
+
_file_changed = _since_changed is None or rel_path in _since_changed
|
|
2557
|
+
if _file_changed:
|
|
2558
|
+
old_source = _get_git_old_content(root, rel_path, since)
|
|
2524
2559
|
|
|
2525
2560
|
package, symbols, raw_imports = _extract_symbols(source, rel_path)
|
|
2526
2561
|
relations = _build_relations(symbols, raw_imports, source, package, rel_path)
|
|
@@ -2529,7 +2564,8 @@ def build_repo_ir(
|
|
|
2529
2564
|
_, old_symbols, _ = _extract_symbols(old_source, rel_path)
|
|
2530
2565
|
all_changed.extend(_diff_symbols(old_symbols, symbols))
|
|
2531
2566
|
all_route_diffs.extend(_diff_routes(old_symbols, symbols))
|
|
2532
|
-
elif since:
|
|
2567
|
+
elif since and (_since_changed is None or rel_path in _since_changed):
|
|
2568
|
+
# File is new in since..HEAD (not in old ref) — treat as added.
|
|
2533
2569
|
for sym in symbols:
|
|
2534
2570
|
all_changed.append(ChangedSymbol(
|
|
2535
2571
|
symbol=sym.symbol,
|