sourcecode 1.31.29__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.31/.continue-here.md +89 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/PKG-INFO +3 -3
- {sourcecode-1.31.29 → sourcecode-1.31.31}/README.md +2 -2
- {sourcecode-1.31.29 → sourcecode-1.31.31}/pyproject.toml +1 -1
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/cli.py +191 -13
- sourcecode-1.31.31/src/sourcecode/license.py +166 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/mcp/server.py +63 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/prepare_context.py +24 -0
- {sourcecode-1.31.29 → 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.29 → sourcecode-1.31.31}/tests/test_enterprise_benchmarks.py +5 -4
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_mcp_tools.py +10 -0
- sourcecode-1.31.29/.continue-here.md +0 -98
- {sourcecode-1.31.29 → sourcecode-1.31.31}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/.gitignore +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/.ruff.toml +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/.sourcecode-cache/snapshot-3b5997a-fa5c742c.json +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/AUDIT_REAL_REPOS.md +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/AUDIT_v1.31.23.md +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/CHANGELOG.md +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/CONTRIBUTING.md +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/LICENSE +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/SECURITY.md +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/docs/PRODUCT_TIERS.md +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/docs/privacy.md +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/docs/schema.md +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/raw +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/run_cli.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/cache.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/serializer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/__init__.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/conftest.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/latin1_sample.java +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/latin1_sample_iso.java +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/lcov.info +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
- {sourcecode-1.31.29 → 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.29 → 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.29 → 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.29 → 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.29 → 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.29 → 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.29 → 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.29 → 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.29 → 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.29 → 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.29 → 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.29 → 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.29 → 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.29 → 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.29 → 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.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_architecture_summary.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_ast_extractor.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_audit_fixes.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_audit_sas_v2.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_block1_reliability.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_block2_coverage.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_block5_quality.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_broadleaf_fixes.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_bug_fixes_v1302.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_bug_fixes_v13115.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_bug_fixes_v1312.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_bug_fixes_v13122.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_bug_fixes_v1313.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_bug_fixes_v1321.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_bug_fixes_v16.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_bug_fixes_v2.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_cache.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_canonical_ir.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_classifier.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_cli.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_context_scorer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_contract_pipeline.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_coverage_parser.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_cross_consistency.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_dependency_schema.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_detector_python.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_detectors_base.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_encoding_regression.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_graph_schema.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_integration.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_integration_detection.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_integration_docs.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_integration_lqn.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_integration_metrics.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_integration_multistack.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_integration_semantics.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_integration_universal.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_java_spring_integration.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_mcp_nudge.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_mcp_runner.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_mcp_serve.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_output_ux.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_packaging.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_pipeline_integrity.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_real_projects.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_redactor.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_repository_ir.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_scanner.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_schema.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_schema_normalization.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_scoring_calibration.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_semantic_schema.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_signal_hierarchy.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_summarizer.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_surface_honesty.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_task_differentiation.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_telemetry.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_v131_improvements.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_v1_10_regressions.py +0 -0
- {sourcecode-1.31.29 → sourcecode-1.31.31}/tests/test_workspace_analyzer.py +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Handoff — Sesión 34 (2026-05-26)
|
|
2
|
+
|
|
3
|
+
## Position
|
|
4
|
+
|
|
5
|
+
**Project:** sourcecode (atlas-cli)
|
|
6
|
+
**Activity:** Post-audit bug fixing + new feature (MCP nudge)
|
|
7
|
+
**Branch:** master
|
|
8
|
+
**Last commit:** 11d23b0 — feat(mcp): add one-time MCP setup nudge after successful commands
|
|
9
|
+
**Tests:** 1601 passed, 3 skipped ✅
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Work Done This Session
|
|
14
|
+
|
|
15
|
+
### Session 34 (this session)
|
|
16
|
+
**New feature: one-time MCP setup nudge**
|
|
17
|
+
|
|
18
|
+
**FEAT — `sourcecode mcp_nudge.py` (new module)**
|
|
19
|
+
- `nudge_mcp_if_needed()`: fires after first successful analysis command when Claude Desktop/Cursor installed but sourcecode not yet in MCP config. Writes to stderr only. Creates `~/.sourcecode/nudge_shown` flag to fire at most once per session.
|
|
20
|
+
- `clear_nudge_flag()`: called by `mcp init` on success — next run finds `is_installed=True` → no nudge.
|
|
21
|
+
- Module-level imports for testability; `_IMPORTS_OK=False` stubs if onboarding package missing.
|
|
22
|
+
|
|
23
|
+
**CLI hooks (4 call sites in `cli.py`):**
|
|
24
|
+
| Command | Location |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `sourcecode . --compact` | `main()` — after clipboard copy |
|
|
27
|
+
| `sourcecode prepare-context` / `onboard` | `prepare_context_cmd()` — end |
|
|
28
|
+
| `sourcecode endpoints` | `endpoints_cmd()` — end |
|
|
29
|
+
| `sourcecode impact` | `impact_cmd()` — success path only (after not_found check) |
|
|
30
|
+
|
|
31
|
+
**`mcp init` success** → `clear_nudge_flag()` called.
|
|
32
|
+
|
|
33
|
+
**Tests:** `tests/test_mcp_nudge.py` — 11 tests, all pass.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Remaining from Bug Audit Backlog
|
|
38
|
+
|
|
39
|
+
### P1 backlog
|
|
40
|
+
| # | Issue | File | Effort |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| P1-5 | `project_summary` quality — picks license/badges/marketing blurbs | `summarizer.py` | Medium |
|
|
43
|
+
| P1-6 | `relevant_files.score` ranking quality audit (score exposed, ranking weak) | `ranking_engine.py` | Medium |
|
|
44
|
+
|
|
45
|
+
### P2 unfixed
|
|
46
|
+
| # | Issue | File | Effort |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| P2-13 | `--no-cache` flag inconsistent across subcommands | `cli.py` | Medium |
|
|
49
|
+
| P2-14 | URLs in code_notes truncated (needs repro) | `code_notes_analyzer.py` | Low |
|
|
50
|
+
| P2-16 | `entry_points.controllers.methods` mismatches `endpoints` count | `cli.py` | Medium |
|
|
51
|
+
| P2-17 | `--compact --help` token claim stale (now runs arch analyzer too) | `cli.py` | Trivial |
|
|
52
|
+
| P2-18 | `impact <file_path>` fails FQN resolution | `repository_ir.py` | Low |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Key Files Modified (this session)
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
src/sourcecode/mcp_nudge.py — NEW: nudge_mcp_if_needed, clear_nudge_flag
|
|
60
|
+
src/sourcecode/cli.py — 4 nudge call sites + clear_nudge_flag in mcp_init
|
|
61
|
+
tests/test_mcp_nudge.py — NEW: 11 tests
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Decisions This Session
|
|
67
|
+
|
|
68
|
+
| Decision | Rationale |
|
|
69
|
+
|---|---|
|
|
70
|
+
| Module-level imports in mcp_nudge.py (not lazy) | Enables `patch("sourcecode.mcp_nudge.detect_clients", ...)` in tests |
|
|
71
|
+
| Flag at `~/.sourcecode/nudge_shown` | Reuses existing state root; simple touch file |
|
|
72
|
+
| Nudge on impact only when resolution != not_found | not_found = exit 1 = failure; no nudge on failed commands |
|
|
73
|
+
| `clear_nudge_flag()` in mcp_init success, not in detection | Separation: flag reset is explicit intent (user ran init), not a side effect of checking |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Next Session
|
|
78
|
+
|
|
79
|
+
**Recommended:** P2-17 (trivial) → P2-18 (low) → P2-14 (low, needs repro) → P1-5 (medium) → P1-6 (medium) → P2-13 (medium) → P2-16 (medium)
|
|
80
|
+
|
|
81
|
+
**P2-17:** `--compact --help` says token count — verify claim still accurate after P2-10 fix (compact now runs ArchitectureAnalyzer). Update if stale.
|
|
82
|
+
|
|
83
|
+
**P2-18:** `sourcecode impact <file_path>` — resolve file path to FQN before IR lookup. Currently passes raw path to `_resolve_target()`, fails to match.
|
|
84
|
+
|
|
85
|
+
**P1-5:** Audit `summarizer.py`. Prefer: first real descriptive paragraph, architecture description, domain/stack. Exclude: 'Important:', license, badges, install snippets, TOC, link collections, sponsorship, release notes.
|
|
86
|
+
|
|
87
|
+
**Resume command:** `/gsd:resume-work`
|
|
88
|
+
|
|
89
|
+
**Test baseline:** `python3 -m pytest tests/ → 1601 passed, 3 skipped`
|
|
@@ -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
|
|
@@ -760,8 +791,11 @@ def main(
|
|
|
760
791
|
err=True,
|
|
761
792
|
)
|
|
762
793
|
|
|
763
|
-
#
|
|
764
|
-
|
|
794
|
+
# P1-2 FIX: --changed-only silently implies --compact; inform only on TTY.
|
|
795
|
+
# PowerShell 5.1 interprets any stderr write (even with exit 0) as NativeCommandError.
|
|
796
|
+
# Gate on isatty() so pipeline consumers never see informational noise on stderr.
|
|
797
|
+
import sys as _sys_tty
|
|
798
|
+
if changed_only and not compact and not agent and _sys_tty.stderr.isatty():
|
|
765
799
|
typer.echo(
|
|
766
800
|
"[info] --changed-only implies --compact (bounding output to changed files).",
|
|
767
801
|
err=True,
|
|
@@ -2162,6 +2196,12 @@ def prepare_context_cmd(
|
|
|
2162
2196
|
)
|
|
2163
2197
|
raise typer.Exit(code=1)
|
|
2164
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
|
+
|
|
2165
2205
|
# Validate --format: only "json" and "github-comment" are valid for prepare-context.
|
|
2166
2206
|
# "yaml" is intentionally NOT supported here (use main command for yaml output).
|
|
2167
2207
|
# Invalid values must error loudly — silently falling through to JSON is a lie.
|
|
@@ -2184,7 +2224,11 @@ def prepare_context_cmd(
|
|
|
2184
2224
|
|
|
2185
2225
|
target = path.resolve()
|
|
2186
2226
|
if not target.exists() or not target.is_dir():
|
|
2187
|
-
|
|
2227
|
+
_emit_error_json(
|
|
2228
|
+
"invalid_path",
|
|
2229
|
+
f"'{target}' is not a valid directory.",
|
|
2230
|
+
path=str(target),
|
|
2231
|
+
)
|
|
2188
2232
|
raise typer.Exit(code=1)
|
|
2189
2233
|
|
|
2190
2234
|
if dry_run:
|
|
@@ -2215,7 +2259,49 @@ def prepare_context_cmd(
|
|
|
2215
2259
|
_sys.stderr.flush()
|
|
2216
2260
|
_t0 = _time.perf_counter()
|
|
2217
2261
|
try:
|
|
2218
|
-
|
|
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)
|
|
2219
2305
|
finally:
|
|
2220
2306
|
_progress.finish()
|
|
2221
2307
|
_t_total = (_time.perf_counter() - _t0) * 1000
|
|
@@ -2316,6 +2402,13 @@ def prepare_context_cmd(
|
|
|
2316
2402
|
out["improvement_opportunities"] = output.improvement_opportunities
|
|
2317
2403
|
if _task_include("test_gaps") and output.test_gaps:
|
|
2318
2404
|
out["test_gaps"] = output.test_gaps
|
|
2405
|
+
# P0-2: fast-mode truncation transparency — always emit when truncated, even if test_gaps is []
|
|
2406
|
+
# Use `is True` (strict) so MagicMock objects in tests don't trigger this branch.
|
|
2407
|
+
if getattr(output, "truncated", False) is True:
|
|
2408
|
+
out["truncated"] = True
|
|
2409
|
+
_tr = getattr(output, "truncated_reason", None)
|
|
2410
|
+
if isinstance(_tr, str) and _tr:
|
|
2411
|
+
out["truncated_reason"] = _tr
|
|
2319
2412
|
if _task_include("code_notes_summary") and output.code_notes_summary:
|
|
2320
2413
|
out["code_notes_summary"] = output.code_notes_summary
|
|
2321
2414
|
if _task_include("changed_files") and output.changed_files:
|
|
@@ -2518,6 +2611,19 @@ def prepare_context_cmd(
|
|
|
2518
2611
|
if llm_prompt:
|
|
2519
2612
|
out["llm_prompt"] = builder.render_prompt(output)
|
|
2520
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
|
+
|
|
2521
2627
|
# P0-1: Apply output budget per task — safety net for large repos.
|
|
2522
2628
|
from sourcecode.output_budget import (
|
|
2523
2629
|
trim_to_budget as _pc_trim,
|
|
@@ -2704,7 +2810,11 @@ def repo_ir_cmd(
|
|
|
2704
2810
|
|
|
2705
2811
|
root = path.resolve()
|
|
2706
2812
|
if not root.is_dir():
|
|
2707
|
-
|
|
2813
|
+
_emit_error_json(
|
|
2814
|
+
"invalid_path",
|
|
2815
|
+
f"'{root}' is not a valid directory.",
|
|
2816
|
+
path=str(root),
|
|
2817
|
+
)
|
|
2708
2818
|
raise typer.Exit(1)
|
|
2709
2819
|
|
|
2710
2820
|
if files:
|
|
@@ -2846,6 +2956,9 @@ def impact_cmd(
|
|
|
2846
2956
|
sourcecode impact org.keycloak.services.DefaultKeycloakSession /path/to/keycloak
|
|
2847
2957
|
sourcecode impact UserService --depth 6 --output impact.json
|
|
2848
2958
|
"""
|
|
2959
|
+
from sourcecode.license import require_pro as _require_pro
|
|
2960
|
+
_require_pro("impact")
|
|
2961
|
+
|
|
2849
2962
|
import json as _json
|
|
2850
2963
|
import sys as _sys
|
|
2851
2964
|
|
|
@@ -2856,7 +2969,11 @@ def impact_cmd(
|
|
|
2856
2969
|
|
|
2857
2970
|
root = path.resolve()
|
|
2858
2971
|
if not root.is_dir():
|
|
2859
|
-
|
|
2972
|
+
_emit_error_json(
|
|
2973
|
+
"invalid_path",
|
|
2974
|
+
f"'{root}' is not a valid directory.",
|
|
2975
|
+
path=str(root),
|
|
2976
|
+
)
|
|
2860
2977
|
raise typer.Exit(1)
|
|
2861
2978
|
|
|
2862
2979
|
file_list = find_java_files(root)
|
|
@@ -2902,9 +3019,11 @@ def impact_cmd(
|
|
|
2902
3019
|
if _copy_to_clipboard(output):
|
|
2903
3020
|
typer.echo("✓ copied to clipboard", err=True)
|
|
2904
3021
|
|
|
2905
|
-
#
|
|
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.
|
|
2906
3025
|
if result.get("resolution") == "not_found":
|
|
2907
|
-
raise typer.Exit(code=
|
|
3026
|
+
raise typer.Exit(code=0)
|
|
2908
3027
|
|
|
2909
3028
|
from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
|
|
2910
3029
|
_nudge()
|
|
@@ -2959,7 +3078,11 @@ def endpoints_cmd(
|
|
|
2959
3078
|
|
|
2960
3079
|
target = path.resolve()
|
|
2961
3080
|
if not target.exists() or not target.is_dir():
|
|
2962
|
-
|
|
3081
|
+
_emit_error_json(
|
|
3082
|
+
"invalid_path",
|
|
3083
|
+
f"'{target}' is not a valid directory.",
|
|
3084
|
+
path=str(target),
|
|
3085
|
+
)
|
|
2963
3086
|
raise typer.Exit(code=1)
|
|
2964
3087
|
|
|
2965
3088
|
data = _extract_java_endpoints(target)
|
|
@@ -3075,7 +3198,10 @@ def review_pr_cmd(
|
|
|
3075
3198
|
help="Copy output to clipboard after a successful run.",
|
|
3076
3199
|
),
|
|
3077
3200
|
) -> None:
|
|
3078
|
-
"""[Pro] PR review: blast radius, risk ranking, execution paths, security/txn impact.
|
|
3201
|
+
"""[Pro*] PR review: blast radius, risk ranking, execution paths, security/txn impact.
|
|
3202
|
+
|
|
3203
|
+
Note: [Pro*] label is reserved for a future licensing gate. This command currently
|
|
3204
|
+
runs without authentication. Behavior may change in a future version.
|
|
3079
3205
|
|
|
3080
3206
|
\b
|
|
3081
3207
|
Answers: "What does this PR break and how risky is it?"
|
|
@@ -3135,7 +3261,10 @@ def fix_bug_cmd(
|
|
|
3135
3261
|
help="Copy output to clipboard after a successful run.",
|
|
3136
3262
|
),
|
|
3137
3263
|
) -> None:
|
|
3138
|
-
"""[Pro] Bug triage: risk-ranked files, suspected areas, related annotations.
|
|
3264
|
+
"""[Pro*] Bug triage: risk-ranked files, suspected areas, related annotations.
|
|
3265
|
+
|
|
3266
|
+
Note: [Pro*] label is reserved for a future licensing gate. This command currently
|
|
3267
|
+
runs without authentication. Behavior may change in a future version.
|
|
3139
3268
|
|
|
3140
3269
|
\b
|
|
3141
3270
|
Answers: "Where in this codebase should I look to fix this symptom?"
|
|
@@ -3187,7 +3316,10 @@ def modernize_cmd(
|
|
|
3187
3316
|
help="Copy output to clipboard after a successful run.",
|
|
3188
3317
|
),
|
|
3189
3318
|
) -> None:
|
|
3190
|
-
"""[Pro] Modernization planning: coupling, dead zones, risky modules, refactor candidates.
|
|
3319
|
+
"""[Pro*] Modernization planning: coupling, dead zones, risky modules, refactor candidates.
|
|
3320
|
+
|
|
3321
|
+
Note: [Pro*] label is reserved for a future licensing gate. This command currently
|
|
3322
|
+
runs without authentication. Behavior may change in a future version.
|
|
3191
3323
|
|
|
3192
3324
|
\b
|
|
3193
3325
|
Answers: "Where should I refactor first, and what's safest to touch?"
|
|
@@ -3211,6 +3343,9 @@ def modernize_cmd(
|
|
|
3211
3343
|
sourcecode onboard . — Architecture overview first
|
|
3212
3344
|
sourcecode impact <target> — Verify impact before touching a hotspot
|
|
3213
3345
|
"""
|
|
3346
|
+
from sourcecode.license import require_pro as _require_pro
|
|
3347
|
+
_require_pro("modernize")
|
|
3348
|
+
|
|
3214
3349
|
import json as _json
|
|
3215
3350
|
import sys as _sys
|
|
3216
3351
|
from sourcecode.repository_ir import build_repo_ir, find_java_files, apply_ir_size_limits
|
|
@@ -3218,7 +3353,11 @@ def modernize_cmd(
|
|
|
3218
3353
|
|
|
3219
3354
|
root = path.resolve()
|
|
3220
3355
|
if not root.is_dir():
|
|
3221
|
-
|
|
3356
|
+
_emit_error_json(
|
|
3357
|
+
"invalid_path",
|
|
3358
|
+
f"'{root}' is not a valid directory.",
|
|
3359
|
+
path=str(root),
|
|
3360
|
+
)
|
|
3222
3361
|
raise typer.Exit(1)
|
|
3223
3362
|
|
|
3224
3363
|
file_list = find_java_files(root)
|
|
@@ -3379,6 +3518,24 @@ def modernize_cmd(
|
|
|
3379
3518
|
|
|
3380
3519
|
# ── version ───────────────────────────────────────────────────────────────────
|
|
3381
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
|
+
|
|
3382
3539
|
@app.command("version")
|
|
3383
3540
|
def version_cmd() -> None:
|
|
3384
3541
|
"""Show version and exit."""
|
|
@@ -3432,6 +3589,9 @@ def mcp_serve() -> None:
|
|
|
3432
3589
|
}
|
|
3433
3590
|
}
|
|
3434
3591
|
"""
|
|
3592
|
+
from sourcecode.license import require_pro as _require_pro
|
|
3593
|
+
_require_pro("mcp serve")
|
|
3594
|
+
|
|
3435
3595
|
import logging
|
|
3436
3596
|
import sys as _sys
|
|
3437
3597
|
|
|
@@ -3443,6 +3603,24 @@ def mcp_serve() -> None:
|
|
|
3443
3603
|
from sourcecode.mcp.server import mcp as _mcp
|
|
3444
3604
|
|
|
3445
3605
|
log = logging.getLogger(__name__)
|
|
3606
|
+
|
|
3607
|
+
# P0-1: Strip UTF-8 BOM from stdin.buffer before the MCP server reads it.
|
|
3608
|
+
# PowerShell 5.1 on Windows writes \xEF\xBB\xBF at the start of stdin,
|
|
3609
|
+
# which breaks JSON parsing at line 1 column 1.
|
|
3610
|
+
# peek(3) loads bytes into BufferedReader's internal buffer without consuming;
|
|
3611
|
+
# read(3) discards only if the prefix is the UTF-8 BOM sequence.
|
|
3612
|
+
# No-op on Linux/macOS/Git Bash where stdin never starts with a BOM.
|
|
3613
|
+
# Guard: CliRunner / test stubs replace sys.stdin with StringIO (no .buffer).
|
|
3614
|
+
try:
|
|
3615
|
+
_stdin_buf = getattr(_sys.stdin, "buffer", None)
|
|
3616
|
+
if _stdin_buf is not None and hasattr(_stdin_buf, "peek"):
|
|
3617
|
+
_bom_prefix = _stdin_buf.peek(3)[:3]
|
|
3618
|
+
if _bom_prefix == b"\xef\xbb\xbf":
|
|
3619
|
+
_stdin_buf.read(3)
|
|
3620
|
+
log.info("sourcecode-mcp stripped UTF-8 BOM from stdin (PowerShell 5.1 workaround)")
|
|
3621
|
+
except Exception:
|
|
3622
|
+
pass # Never abort server startup over BOM detection
|
|
3623
|
+
|
|
3446
3624
|
log.info("sourcecode-mcp starting (stdio transport)")
|
|
3447
3625
|
try:
|
|
3448
3626
|
_mcp.run()
|
|
@@ -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)
|