sourcecode 1.31.27__tar.gz → 1.31.29__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.29/.continue-here.md +98 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/PKG-INFO +3 -3
- {sourcecode-1.31.27 → sourcecode-1.31.29}/README.md +2 -2
- {sourcecode-1.31.27 → sourcecode-1.31.29}/pyproject.toml +1 -1
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/cli.py +26 -6
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/mcp/server.py +30 -0
- sourcecode-1.31.29/src/sourcecode/mcp_nudge.py +84 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/prepare_context.py +99 -17
- sourcecode-1.31.29/tests/test_mcp_nudge.py +153 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_v1_10_regressions.py +5 -4
- sourcecode-1.31.27/.continue-here.md +0 -73
- {sourcecode-1.31.27 → sourcecode-1.31.29}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/.gitignore +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/.ruff.toml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/.sourcecode-cache/snapshot-3b5997a-fa5c742c.json +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/AUDIT_REAL_REPOS.md +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/AUDIT_v1.31.23.md +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/CHANGELOG.md +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/CONTRIBUTING.md +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/LICENSE +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/SECURITY.md +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/docs/PRODUCT_TIERS.md +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/docs/privacy.md +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/docs/schema.md +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/raw +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/run_cli.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/cache.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/serializer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/src/sourcecode/workspace.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/__init__.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/conftest.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/latin1_sample.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/latin1_sample_iso.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/lcov.info +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_architecture_summary.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_ast_extractor.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_audit_fixes.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_audit_sas_v2.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_block1_reliability.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_block2_coverage.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_block5_quality.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_broadleaf_fixes.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_bug_fixes_v1302.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_bug_fixes_v13115.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_bug_fixes_v1312.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_bug_fixes_v13122.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_bug_fixes_v1313.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_bug_fixes_v1321.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_bug_fixes_v16.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_bug_fixes_v2.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_cache.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_canonical_ir.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_classifier.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_cli.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_context_scorer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_contract_pipeline.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_coverage_parser.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_cross_consistency.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_dependency_schema.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_detector_python.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_detectors_base.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_encoding_regression.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_enterprise_benchmarks.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_graph_schema.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_integration.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_integration_detection.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_integration_docs.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_integration_lqn.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_integration_metrics.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_integration_multistack.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_integration_semantics.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_integration_universal.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_java_spring_integration.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_mcp_runner.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_mcp_serve.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_mcp_tools.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_output_ux.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_packaging.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_pipeline_integrity.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_real_projects.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_redactor.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_repository_ir.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_scanner.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_schema.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_schema_normalization.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_scoring_calibration.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_semantic_schema.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_signal_hierarchy.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_summarizer.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_surface_honesty.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_task_differentiation.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_telemetry.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_v131_improvements.py +0 -0
- {sourcecode-1.31.27 → sourcecode-1.31.29}/tests/test_workspace_analyzer.py +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Handoff — Sesión 33 (2026-05-26)
|
|
2
|
+
|
|
3
|
+
## Position
|
|
4
|
+
|
|
5
|
+
**Project:** sourcecode (atlas-cli)
|
|
6
|
+
**Activity:** Post-audit bug fixing — four correctness fixes (P0/P1/P2)
|
|
7
|
+
**Branch:** master
|
|
8
|
+
**Last commit:** f3d44c3 — fix(prepare-context,cli,mcp): four correctness fixes — fast fallback, flag errors, review-pr CI, modernize MCP
|
|
9
|
+
**Tests:** 1590 passed, 3 skipped ✅
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Work Done This Session
|
|
14
|
+
|
|
15
|
+
### Session 33 (this session)
|
|
16
|
+
**Four bugs fixed from new audit batch + backlog:**
|
|
17
|
+
|
|
18
|
+
**FIX 1 — P0: `--fast` returns `relevant_files: []`** (`prepare_context.py`)
|
|
19
|
+
- When `fast=True` on large repo, `_rank_files` received only noise/IDE files → returned `[]`
|
|
20
|
+
- Added fast-mode fallback block after `_rank_files`: injects entry points + git log -10 files +
|
|
21
|
+
symptom path matches (up to 20) when `relevant_files` is empty and `fast=True`
|
|
22
|
+
- Validated: `sourcecode prepare-context fix-bug . --fast --symptom "spinner"` → 3 results
|
|
23
|
+
|
|
24
|
+
**FIX 2 — P1: Incompatible flags → stderr plain text** (`cli.py`)
|
|
25
|
+
- `--compact --full` wrote plain text to stderr; agents couldn't parse it
|
|
26
|
+
- Replaced `typer.echo(err=True)` with `sys.stdout.write(json.dumps({...}))` for `incompatible_flags`
|
|
27
|
+
- Validated: `sourcecode . --compact --full 2>/dev/null | jq .error` → `"incompatible_flags"`
|
|
28
|
+
|
|
29
|
+
**FIX 3 — P1: `review-pr` without `--since` exits 0 silently in CI** (`prepare_context.py`)
|
|
30
|
+
- Clean tree (CI) → previously fell back to HEAD~1 diff silently, masking missing --since
|
|
31
|
+
- Removed HEAD~1 fallback: clean tree + no `--since` → `_get_pr_scope_files` returns
|
|
32
|
+
`([], "no_staged_changes", [], [])` sentinel
|
|
33
|
+
- Step 5d detects sentinel → `no_diff_source` error (exit 1)
|
|
34
|
+
- Staged/unstaged changes still proceed normally (local dev use case)
|
|
35
|
+
- Validated: clean repo → `error: no_diff_source`, `exit_code=1`
|
|
36
|
+
- Test `test_review_pr_requires_git_diff` updated: added `no_diff_source` to expected error set
|
|
37
|
+
|
|
38
|
+
**FIX 4 — P2: `modernize` missing from MCP server** (`mcp/server.py`)
|
|
39
|
+
- Added `modernize_context` as MCP tool #17 (was #16 before)
|
|
40
|
+
- Mirrors CLI `sourcecode modernize <repo_path>` exactly
|
|
41
|
+
- Returns: hotspot_candidates, dead_zones, high_coupling_nodes, subsystem_summary, recommendation
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Remaining from AUDIT_v1.31.23.md
|
|
46
|
+
|
|
47
|
+
### P1 backlog (from HANDOFF.json session 28)
|
|
48
|
+
| # | Issue | File | Effort |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| P1-5 | `project_summary` quality — picks license/badges/marketing blurbs | `summarizer.py` | Medium |
|
|
51
|
+
| P1-6 | `relevant_files.score` ranking quality audit (score exposed, ranking weak) | `ranking_engine.py` | Medium |
|
|
52
|
+
|
|
53
|
+
### P2 unfixed
|
|
54
|
+
| # | Issue | File | Effort |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| P2-13 | `--no-cache` flag inconsistent across subcommands | `cli.py` | Medium |
|
|
57
|
+
| P2-14 | URLs in code_notes truncated (needs repro) | `code_notes_analyzer.py` | Low |
|
|
58
|
+
| P2-16 | `entry_points.controllers.methods` mismatches `endpoints` count | `cli.py` | Medium |
|
|
59
|
+
| P2-17 | `--compact --help` token claim stale (now runs arch analyzer too) | `cli.py` | Trivial |
|
|
60
|
+
| P2-18 | `impact <file_path>` fails FQN resolution | `repository_ir.py` | Low |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Key Files Modified (this session)
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
src/sourcecode/prepare_context.py — FIX 1: fast-mode fallback; FIX 3: no_diff_source sentinel
|
|
68
|
+
src/sourcecode/cli.py — FIX 2: incompatible_flags to stdout JSON
|
|
69
|
+
src/sourcecode/mcp/server.py — FIX 4: modernize_context tool
|
|
70
|
+
tests/test_v1_10_regressions.py — test: no_diff_source added to expected error set
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Decisions This Session
|
|
76
|
+
|
|
77
|
+
| Decision | Rationale |
|
|
78
|
+
|---|---|
|
|
79
|
+
| Fast-mode fallback injects after `_rank_files`, not before | Keeps ranking path clean; fallback only fires when needed |
|
|
80
|
+
| `no_diff_source` check via sentinel in `_get_pr_scope_files` | Allows test mocks to bypass (mock overrides entire method); inline check before mock fires broke tests |
|
|
81
|
+
| HEAD~1 fallback removed entirely for `since=None` clean tree | CI pipelines always commit first; HEAD~1 fallback was silently hiding missing `--since` |
|
|
82
|
+
| `modernize_context` MCP `format` param is cosmetic (only json) | CLI `modernize` has no `--format` flag; param included per spec schema, yaml rejected with INVALID_ARGUMENT |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Next Session
|
|
87
|
+
|
|
88
|
+
**Recommended:** P2-17 (trivial) → P2-18 (low) → P2-14 (low, needs repro) → P1-5 (medium) → P1-6 (medium) → P2-13 (medium) → P2-16 (medium)
|
|
89
|
+
|
|
90
|
+
**P2-17:** `--compact --help` says token count — now that `--compact` also runs ArchitectureAnalyzer (P2-10 fix from session 32), verify claim still accurate. Update if stale.
|
|
91
|
+
|
|
92
|
+
**P2-18:** `sourcecode impact <file_path>` — resolve file path to FQN before IR lookup. Currently passes raw path to `_resolve_target()`, fails to match.
|
|
93
|
+
|
|
94
|
+
**P1-5:** Audit `summarizer.py`. Prefer: first real descriptive paragraph, architecture, domain/stack description. Exclude: 'Important:', license, badges, install snippets, TOC, link collections, sponsorship, release notes.
|
|
95
|
+
|
|
96
|
+
**Resume command:** `/gsd:resume-work`
|
|
97
|
+
|
|
98
|
+
**Test baseline:** `python3 -m pytest tests/ → 1590 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.29
|
|
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.29
|
|
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.29
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
---
|
|
@@ -739,12 +739,15 @@ def main(
|
|
|
739
739
|
# compact is designed to be a bounded summary; --full removes truncation limits,
|
|
740
740
|
# which contradicts compact's purpose. Use --agent --full for expanded output.
|
|
741
741
|
if compact and full:
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
"
|
|
745
|
-
"
|
|
746
|
-
|
|
747
|
-
|
|
742
|
+
import json as _json_flags, sys as _sys_flags
|
|
743
|
+
_sys_flags.stdout.write(_json_flags.dumps({
|
|
744
|
+
"error": "incompatible_flags",
|
|
745
|
+
"message": "--compact and --full are mutually exclusive. "
|
|
746
|
+
"--compact produces a bounded summary; --full removes truncation limits "
|
|
747
|
+
"and is meant for --agent mode. Use --agent --full for expanded output.",
|
|
748
|
+
"exit_code": 1,
|
|
749
|
+
}, ensure_ascii=False) + "\n")
|
|
750
|
+
_sys_flags.stdout.flush()
|
|
748
751
|
raise typer.Exit(code=1)
|
|
749
752
|
|
|
750
753
|
# P0-2 FIX: --full without --compact or --agent has no effect in contract/raw mode.
|
|
@@ -1949,6 +1952,10 @@ def main(
|
|
|
1949
1952
|
if _copy_to_clipboard(content):
|
|
1950
1953
|
typer.echo("✓ copied to clipboard", err=True)
|
|
1951
1954
|
|
|
1955
|
+
# 8. One-time MCP setup nudge (stderr only — does not affect exit code or stdout)
|
|
1956
|
+
from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
|
|
1957
|
+
_nudge()
|
|
1958
|
+
|
|
1952
1959
|
|
|
1953
1960
|
# ── prepare-context output helpers ────────────────────────────────────────────
|
|
1954
1961
|
|
|
@@ -2551,6 +2558,9 @@ def prepare_context_cmd(
|
|
|
2551
2558
|
if _copy_to_clipboard(_pc_content):
|
|
2552
2559
|
typer.echo("✓ copied to clipboard", err=True)
|
|
2553
2560
|
|
|
2561
|
+
from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
|
|
2562
|
+
_nudge()
|
|
2563
|
+
|
|
2554
2564
|
|
|
2555
2565
|
# ── Telemetry commands ────────────────────────────────────────────────────────
|
|
2556
2566
|
|
|
@@ -2896,6 +2906,9 @@ def impact_cmd(
|
|
|
2896
2906
|
if result.get("resolution") == "not_found":
|
|
2897
2907
|
raise typer.Exit(code=1)
|
|
2898
2908
|
|
|
2909
|
+
from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
|
|
2910
|
+
_nudge()
|
|
2911
|
+
|
|
2899
2912
|
|
|
2900
2913
|
# ── endpoints ─────────────────────────────────────────────────────────────────
|
|
2901
2914
|
|
|
@@ -2966,6 +2979,9 @@ def endpoints_cmd(
|
|
|
2966
2979
|
if _copy_to_clipboard(output):
|
|
2967
2980
|
typer.echo("✓ copied to clipboard", err=True)
|
|
2968
2981
|
|
|
2982
|
+
from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
|
|
2983
|
+
_nudge()
|
|
2984
|
+
|
|
2969
2985
|
|
|
2970
2986
|
# ── Enterprise Workflow Commands ──────────────────────────────────────────────
|
|
2971
2987
|
#
|
|
@@ -3571,6 +3587,10 @@ def mcp_init(
|
|
|
3571
3587
|
typer.echo("")
|
|
3572
3588
|
typer.echo(" Remove: sourcecode mcp remove")
|
|
3573
3589
|
|
|
3590
|
+
# Clear nudge flag: next run finds is_installed=True → no nudge.
|
|
3591
|
+
from sourcecode.mcp_nudge import clear_nudge_flag as _clear_nudge
|
|
3592
|
+
_clear_nudge()
|
|
3593
|
+
|
|
3574
3594
|
|
|
3575
3595
|
@mcp_app.command("status")
|
|
3576
3596
|
def mcp_status() -> None:
|
|
@@ -453,6 +453,36 @@ def get_impact_context(repo_path: str = ".", target: str = "", depth: int = 4) -
|
|
|
453
453
|
)
|
|
454
454
|
|
|
455
455
|
|
|
456
|
+
@mcp.tool()
|
|
457
|
+
def modernize_context(repo_path: str = ".", format: str = "json") -> dict:
|
|
458
|
+
"""Analyzes codebase for modernization opportunities: dead zones, hotspot scores, upgrade candidates.
|
|
459
|
+
|
|
460
|
+
Maps to: sourcecode modernize <repo_path>
|
|
461
|
+
Returns: hotspot_candidates (high fan-in + git churn), dead_zone_candidates (isolated classes),
|
|
462
|
+
high_coupling_nodes, subsystem_summary, cross_module_tangles, recommendation.
|
|
463
|
+
|
|
464
|
+
Best for: refactor planning, identifying where to start, finding safe removal candidates.
|
|
465
|
+
Use get_compact_context or get_agent_context first for project orientation.
|
|
466
|
+
|
|
467
|
+
repo_path: absolute path to the Java repository (default: current working directory).
|
|
468
|
+
format: output format — "json" (default). Only json is supported; yaml is not available
|
|
469
|
+
for modernize output.
|
|
470
|
+
"""
|
|
471
|
+
_raw = repo_path
|
|
472
|
+
try:
|
|
473
|
+
if not isinstance(repo_path, str):
|
|
474
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
475
|
+
if not isinstance(format, str) or format not in ("json", "yaml"):
|
|
476
|
+
return _err("format must be 'json' or 'yaml'", "INVALID_ARGUMENT")
|
|
477
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
478
|
+
return _execute(["modernize", repo_path])
|
|
479
|
+
except Exception as exc:
|
|
480
|
+
return _err(
|
|
481
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path recibido: {_raw}",
|
|
482
|
+
"INTERNAL_ERROR",
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
|
|
456
486
|
_TELEMETRY_ACTIONS = frozenset({"status", "enable", "disable"})
|
|
457
487
|
|
|
458
488
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""MCP setup nudge — one-time stderr hint after successful analysis commands.
|
|
2
|
+
|
|
3
|
+
Fires when:
|
|
4
|
+
1. At least one known MCP client (Claude Desktop, Cursor) is installed
|
|
5
|
+
2. sourcecode is NOT yet registered in that client's config
|
|
6
|
+
3. The nudge hasn't been shown this session (~/.sourcecode/nudge_shown flag)
|
|
7
|
+
|
|
8
|
+
Cleared by: a successful `sourcecode mcp init` (deletes the flag so the
|
|
9
|
+
post-init detection finds is_installed=True and never nudges again).
|
|
10
|
+
|
|
11
|
+
Side effects: writes only to stderr — stdout (JSON/YAML output) is untouched.
|
|
12
|
+
Exit code of the calling command is unaffected.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
# Stable path used as a session-level "already shown" sentinel.
|
|
20
|
+
_FLAG: Path = Path.home() / ".sourcecode" / "nudge_shown"
|
|
21
|
+
|
|
22
|
+
_MSG = (
|
|
23
|
+
"→ Claude Desktop detected. "
|
|
24
|
+
"Run `sourcecode mcp init` to enable agent integration.\n"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Module-level imports so names are patchable in tests.
|
|
28
|
+
# Falls back to no-op stubs if onboarding package is unavailable.
|
|
29
|
+
try:
|
|
30
|
+
from sourcecode.mcp.onboarding.detector import detect_clients # noqa: PLC0415
|
|
31
|
+
from sourcecode.mcp.onboarding.applier import is_installed, read_config # noqa: PLC0415
|
|
32
|
+
_IMPORTS_OK = True
|
|
33
|
+
except Exception: # pragma: no cover
|
|
34
|
+
_IMPORTS_OK = False
|
|
35
|
+
|
|
36
|
+
def detect_clients() -> list: # type: ignore[misc]
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
def is_installed(config: dict) -> bool: # type: ignore[misc]
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
def read_config(path: Path) -> dict: # type: ignore[misc]
|
|
43
|
+
return {}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def nudge_mcp_if_needed() -> None:
|
|
47
|
+
"""Print MCP setup nudge to stderr at most once (until mcp init succeeds)."""
|
|
48
|
+
# Fast path: already shown this session.
|
|
49
|
+
if _FLAG.exists():
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
clients = detect_clients()
|
|
54
|
+
except Exception: # pragma: no cover
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
needs_nudge = any(
|
|
58
|
+
c.app_installed and not is_installed(read_config(c.config_path))
|
|
59
|
+
for c in clients
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if not needs_nudge:
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
# Write nudge and persist flag.
|
|
66
|
+
sys.stderr.write(_MSG)
|
|
67
|
+
sys.stderr.flush()
|
|
68
|
+
try:
|
|
69
|
+
_FLAG.parent.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
_FLAG.touch()
|
|
71
|
+
except OSError:
|
|
72
|
+
pass # Non-fatal: nudge will fire again next run, which is acceptable.
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def clear_nudge_flag() -> None:
|
|
76
|
+
"""Delete the session flag so post-mcp-init runs don't re-show the nudge.
|
|
77
|
+
|
|
78
|
+
Called by `mcp init` after a successful installation. On the next run,
|
|
79
|
+
detection finds is_installed=True → needs_nudge=False → no nudge shown.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
_FLAG.unlink(missing_ok=True)
|
|
83
|
+
except OSError:
|
|
84
|
+
pass
|
|
@@ -1134,6 +1134,27 @@ class TaskContextBuilder:
|
|
|
1134
1134
|
# No-git and invalid-ref cases were already handled in step 0 (early returns).
|
|
1135
1135
|
if task_name == "review-pr":
|
|
1136
1136
|
if not _pr_scope_files:
|
|
1137
|
+
# Distinguish: no_staged_changes (CI, no --since) vs no_diff (empty range)
|
|
1138
|
+
if _pr_scope_source == "no_staged_changes":
|
|
1139
|
+
_no_diff_msg = (
|
|
1140
|
+
"No --since ref provided and no staged changes found. "
|
|
1141
|
+
"Use --since <ref>"
|
|
1142
|
+
)
|
|
1143
|
+
return TaskOutput(
|
|
1144
|
+
task="review-pr", goal=spec.goal,
|
|
1145
|
+
project_summary=None, architecture_summary=None,
|
|
1146
|
+
relevant_files=[], suspected_areas=[],
|
|
1147
|
+
improvement_opportunities=[], test_gaps=[],
|
|
1148
|
+
key_dependencies=[], code_notes_summary=None,
|
|
1149
|
+
limitations=[], confidence="low",
|
|
1150
|
+
error_code="no_diff_source",
|
|
1151
|
+
error_message=_no_diff_msg,
|
|
1152
|
+
gaps=[_no_diff_msg],
|
|
1153
|
+
ci_decision="no_diff_source",
|
|
1154
|
+
scope_source=_pr_scope_source,
|
|
1155
|
+
scope_files=[],
|
|
1156
|
+
repo_root=str(_pr_git_root),
|
|
1157
|
+
)
|
|
1137
1158
|
_no_diff_hint = "review-pr requires changed files or --since <ref>."
|
|
1138
1159
|
return TaskOutput(
|
|
1139
1160
|
task="review-pr", goal=spec.goal,
|
|
@@ -1221,6 +1242,76 @@ class TaskContextBuilder:
|
|
|
1221
1242
|
symptom=symptom if task_name == "fix-bug" else None,
|
|
1222
1243
|
)
|
|
1223
1244
|
|
|
1245
|
+
# ── Fast-mode fallback: never return empty relevant_files when source files exist ──
|
|
1246
|
+
# When --fast is active on a large repo, all_paths may be restricted to a handful of
|
|
1247
|
+
# changed/noise files that all get filtered out by _rank_files. Inject fallback signals:
|
|
1248
|
+
# 1. detected entry points (already computed, zero I/O cost)
|
|
1249
|
+
# 2. recently committed files (git log -10 --name-only)
|
|
1250
|
+
# 3. files matching symptom keywords in path (when fix-bug + --symptom)
|
|
1251
|
+
if fast and not relevant_files and task_name not in ("delta", "review-pr"):
|
|
1252
|
+
import subprocess as _sp_fb
|
|
1253
|
+
_fb_seen: set[str] = set()
|
|
1254
|
+
_fb_candidates: list[RelevantFile] = []
|
|
1255
|
+
|
|
1256
|
+
# 1. Entry points from detection
|
|
1257
|
+
for _ep in entry_points:
|
|
1258
|
+
_ep_path = _ep.path.replace("\\", "/")
|
|
1259
|
+
if _ep_path not in _fb_seen and (self.root / _ep_path).exists():
|
|
1260
|
+
_fb_candidates.append(RelevantFile(
|
|
1261
|
+
path=_ep_path,
|
|
1262
|
+
role="entrypoint",
|
|
1263
|
+
score=0.5,
|
|
1264
|
+
reason="fast-mode fallback: detected entry point",
|
|
1265
|
+
why="entry_point signal from manifest/annotation detection",
|
|
1266
|
+
))
|
|
1267
|
+
_fb_seen.add(_ep_path)
|
|
1268
|
+
|
|
1269
|
+
# 2. Recently committed files (git log -10 --name-only)
|
|
1270
|
+
try:
|
|
1271
|
+
_gl_r = _sp_fb.run(
|
|
1272
|
+
["git", "log", "--name-only", "--pretty=format:", "-10"],
|
|
1273
|
+
capture_output=True, text=True, cwd=str(self.root), timeout=5,
|
|
1274
|
+
)
|
|
1275
|
+
for _gl_f in _gl_r.stdout.splitlines():
|
|
1276
|
+
_gl_f = _gl_f.strip().replace("\\", "/")
|
|
1277
|
+
if (not _gl_f or _gl_f in _fb_seen):
|
|
1278
|
+
continue
|
|
1279
|
+
if Path(_gl_f).suffix.lower() not in _ALL_EXTENSIONS:
|
|
1280
|
+
continue
|
|
1281
|
+
if not (self.root / _gl_f).exists():
|
|
1282
|
+
continue
|
|
1283
|
+
_fb_candidates.append(RelevantFile(
|
|
1284
|
+
path=_gl_f,
|
|
1285
|
+
role="source",
|
|
1286
|
+
score=0.3,
|
|
1287
|
+
reason="fast-mode fallback: recently committed file (git log -10)",
|
|
1288
|
+
why="recent commit history signal",
|
|
1289
|
+
))
|
|
1290
|
+
_fb_seen.add(_gl_f)
|
|
1291
|
+
except Exception:
|
|
1292
|
+
pass
|
|
1293
|
+
|
|
1294
|
+
# 3. Symptom keyword path matches (fix-bug only)
|
|
1295
|
+
if task_name == "fix-bug" and symptom:
|
|
1296
|
+
import re as _re_fb
|
|
1297
|
+
_fb_kws = [w.lower() for w in _re_fb.split(r"[\s\W]+", symptom) if len(w) > 2]
|
|
1298
|
+
for _fb_p in all_paths:
|
|
1299
|
+
if _fb_p in _fb_seen:
|
|
1300
|
+
continue
|
|
1301
|
+
if Path(_fb_p).suffix.lower() not in _ALL_EXTENSIONS:
|
|
1302
|
+
continue
|
|
1303
|
+
if any(kw in _fb_p.lower() for kw in _fb_kws):
|
|
1304
|
+
_fb_candidates.append(RelevantFile(
|
|
1305
|
+
path=_fb_p,
|
|
1306
|
+
role="source",
|
|
1307
|
+
score=0.2,
|
|
1308
|
+
reason=f"fast-mode fallback: path matches symptom ({symptom!r})",
|
|
1309
|
+
why="symptom keyword in file path",
|
|
1310
|
+
))
|
|
1311
|
+
_fb_seen.add(_fb_p)
|
|
1312
|
+
|
|
1313
|
+
relevant_files = _fb_candidates[:20]
|
|
1314
|
+
|
|
1224
1315
|
# ── IC-006: fix-bug suspected_areas — recompute from ranked files + bug notes ──
|
|
1225
1316
|
# relevant_files is now ranked by RankingEngine (git churn, fan_in, centrality, notes).
|
|
1226
1317
|
# suspected_areas should reflect that ranking, not raw comment count.
|
|
@@ -2569,24 +2660,15 @@ class TaskContextBuilder:
|
|
|
2569
2660
|
if DiffSourceType.WORKTREE_STAGED.value not in sources:
|
|
2570
2661
|
sources.append(DiffSourceType.WORKTREE_STAGED.value)
|
|
2571
2662
|
|
|
2572
|
-
# ──
|
|
2573
|
-
#
|
|
2574
|
-
#
|
|
2663
|
+
# ── FIX-P1: no --since + clean working tree → no_staged_changes signal ──
|
|
2664
|
+
# Previously: silently fell back to HEAD~1 diff, masking a missing --since.
|
|
2665
|
+
# Now: emit "no_staged_changes" scope so the caller can return no_diff_source
|
|
2666
|
+
# (exit 1) and prompt the user to provide --since.
|
|
2667
|
+
# Rationale: in CI, the tree is always clean; without --since there is no
|
|
2668
|
+
# meaningful diff to review. Local dev should stage changes before review-pr.
|
|
2575
2669
|
if since is None and not committed_files and not uncommitted_files:
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
head_committed = _run("git", "diff", "--name-only", "--relative", "HEAD~1..HEAD")
|
|
2579
|
-
if head_committed:
|
|
2580
|
-
committed_files = head_committed
|
|
2581
|
-
sources.append(DiffSourceType.HEAD_MINUS_1.value)
|
|
2582
|
-
else:
|
|
2583
|
-
# First commit — no HEAD~1; diff against git empty tree
|
|
2584
|
-
_GIT_EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
|
2585
|
-
first_committed = _run("git", "diff", "--name-only", "--relative",
|
|
2586
|
-
_GIT_EMPTY_TREE, "HEAD")
|
|
2587
|
-
if first_committed:
|
|
2588
|
-
committed_files = first_committed
|
|
2589
|
-
sources.append("initial_commit")
|
|
2670
|
+
# Return sentinel — step 5d converts this to no_diff_source (exit 1).
|
|
2671
|
+
return [], "no_staged_changes", [], []
|
|
2590
2672
|
|
|
2591
2673
|
# ── Drop paths outside self.root ──────────────────────────────────────
|
|
2592
2674
|
def _drop_outside(lst: list[str]) -> list[str]:
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Tests for sourcecode.mcp_nudge — one-time MCP setup nudge."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from unittest.mock import MagicMock, patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
import sourcecode.mcp_nudge as nudge_mod
|
|
11
|
+
from sourcecode.mcp_nudge import nudge_mcp_if_needed, clear_nudge_flag
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ── helpers ───────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
def _make_client(app_installed: bool, config_path: Path) -> MagicMock:
|
|
17
|
+
client = MagicMock()
|
|
18
|
+
client.app_installed = app_installed
|
|
19
|
+
client.config_path = config_path
|
|
20
|
+
return client
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ── nudge_mcp_if_needed ───────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
class TestNudgeMcpIfNeeded:
|
|
26
|
+
"""Core nudge logic — isolation via tmp_path flag dir."""
|
|
27
|
+
|
|
28
|
+
def _run(self, flag_path: Path, clients, is_installed_val: bool, capsys) -> str:
|
|
29
|
+
"""Invoke nudge_mcp_if_needed with patched flag, clients and is_installed."""
|
|
30
|
+
with (
|
|
31
|
+
patch.object(nudge_mod, "_FLAG", flag_path),
|
|
32
|
+
patch("sourcecode.mcp_nudge.detect_clients", return_value=clients),
|
|
33
|
+
patch("sourcecode.mcp_nudge.is_installed", return_value=is_installed_val),
|
|
34
|
+
patch("sourcecode.mcp_nudge.read_config", return_value={}),
|
|
35
|
+
):
|
|
36
|
+
nudge_mcp_if_needed()
|
|
37
|
+
return capsys.readouterr().err
|
|
38
|
+
|
|
39
|
+
def test_nudge_shown_when_desktop_installed_not_configured(self, tmp_path, capsys):
|
|
40
|
+
"""Client installed, not in config → nudge printed."""
|
|
41
|
+
flag = tmp_path / "nudge_shown"
|
|
42
|
+
client = _make_client(app_installed=True, config_path=tmp_path / "config.json")
|
|
43
|
+
stderr = self._run(flag, [client], is_installed_val=False, capsys=capsys)
|
|
44
|
+
assert "sourcecode mcp init" in stderr
|
|
45
|
+
assert flag.exists(), "Flag must be created after nudge"
|
|
46
|
+
|
|
47
|
+
def test_no_nudge_when_already_configured(self, tmp_path, capsys):
|
|
48
|
+
"""Client installed AND already in config → no nudge."""
|
|
49
|
+
flag = tmp_path / "nudge_shown"
|
|
50
|
+
client = _make_client(app_installed=True, config_path=tmp_path / "config.json")
|
|
51
|
+
stderr = self._run(flag, [client], is_installed_val=True, capsys=capsys)
|
|
52
|
+
assert stderr == ""
|
|
53
|
+
assert not flag.exists()
|
|
54
|
+
|
|
55
|
+
def test_no_nudge_when_client_not_installed(self, tmp_path, capsys):
|
|
56
|
+
"""Client NOT installed → no nudge even if config absent."""
|
|
57
|
+
flag = tmp_path / "nudge_shown"
|
|
58
|
+
client = _make_client(app_installed=False, config_path=tmp_path / "config.json")
|
|
59
|
+
stderr = self._run(flag, [client], is_installed_val=False, capsys=capsys)
|
|
60
|
+
assert stderr == ""
|
|
61
|
+
assert not flag.exists()
|
|
62
|
+
|
|
63
|
+
def test_no_nudge_when_flag_exists(self, tmp_path, capsys):
|
|
64
|
+
"""Flag already present → no nudge (second run in same session)."""
|
|
65
|
+
flag = tmp_path / "nudge_shown"
|
|
66
|
+
flag.touch()
|
|
67
|
+
client = _make_client(app_installed=True, config_path=tmp_path / "config.json")
|
|
68
|
+
stderr = self._run(flag, [client], is_installed_val=False, capsys=capsys)
|
|
69
|
+
assert stderr == ""
|
|
70
|
+
|
|
71
|
+
def test_no_nudge_when_no_clients(self, tmp_path, capsys):
|
|
72
|
+
"""No clients detected → no nudge."""
|
|
73
|
+
flag = tmp_path / "nudge_shown"
|
|
74
|
+
stderr = self._run(flag, [], is_installed_val=False, capsys=capsys)
|
|
75
|
+
assert stderr == ""
|
|
76
|
+
assert not flag.exists()
|
|
77
|
+
|
|
78
|
+
def test_second_call_no_repeat(self, tmp_path, capsys):
|
|
79
|
+
"""Nudge fires once; second call (flag now exists) is silent."""
|
|
80
|
+
flag = tmp_path / "nudge_shown"
|
|
81
|
+
client = _make_client(app_installed=True, config_path=tmp_path / "config.json")
|
|
82
|
+
|
|
83
|
+
with (
|
|
84
|
+
patch.object(nudge_mod, "_FLAG", flag),
|
|
85
|
+
patch("sourcecode.mcp_nudge.detect_clients", return_value=[client]),
|
|
86
|
+
patch("sourcecode.mcp_nudge.is_installed", return_value=False),
|
|
87
|
+
patch("sourcecode.mcp_nudge.read_config", return_value={}),
|
|
88
|
+
):
|
|
89
|
+
nudge_mcp_if_needed()
|
|
90
|
+
capsys.readouterr() # consume first write
|
|
91
|
+
nudge_mcp_if_needed()
|
|
92
|
+
|
|
93
|
+
second_stderr = capsys.readouterr().err
|
|
94
|
+
assert second_stderr == ""
|
|
95
|
+
|
|
96
|
+
def test_nudge_message_exact_text(self, tmp_path, capsys):
|
|
97
|
+
"""Message matches spec exactly."""
|
|
98
|
+
flag = tmp_path / "nudge_shown"
|
|
99
|
+
client = _make_client(app_installed=True, config_path=tmp_path / "config.json")
|
|
100
|
+
stderr = self._run(flag, [client], is_installed_val=False, capsys=capsys)
|
|
101
|
+
assert stderr == (
|
|
102
|
+
"→ Claude Desktop detected. "
|
|
103
|
+
"Run `sourcecode mcp init` to enable agent integration.\n"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def test_import_error_is_silent(self, tmp_path, capsys):
|
|
107
|
+
"""If onboarding modules can't be imported, nudge fails silently."""
|
|
108
|
+
flag = tmp_path / "nudge_shown"
|
|
109
|
+
with (
|
|
110
|
+
patch.object(nudge_mod, "_FLAG", flag),
|
|
111
|
+
patch.dict("sys.modules", {
|
|
112
|
+
"sourcecode.mcp.onboarding.detector": None, # type: ignore[dict-item]
|
|
113
|
+
"sourcecode.mcp.onboarding.applier": None, # type: ignore[dict-item]
|
|
114
|
+
}),
|
|
115
|
+
):
|
|
116
|
+
nudge_mcp_if_needed() # must not raise
|
|
117
|
+
assert capsys.readouterr().err == ""
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ── clear_nudge_flag ──────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
class TestClearNudgeFlag:
|
|
123
|
+
def test_clears_existing_flag(self, tmp_path):
|
|
124
|
+
flag = tmp_path / "nudge_shown"
|
|
125
|
+
flag.touch()
|
|
126
|
+
with patch.object(nudge_mod, "_FLAG", flag):
|
|
127
|
+
clear_nudge_flag()
|
|
128
|
+
assert not flag.exists()
|
|
129
|
+
|
|
130
|
+
def test_noop_when_flag_absent(self, tmp_path):
|
|
131
|
+
flag = tmp_path / "nudge_shown"
|
|
132
|
+
with patch.object(nudge_mod, "_FLAG", flag):
|
|
133
|
+
clear_nudge_flag() # must not raise
|
|
134
|
+
assert not flag.exists()
|
|
135
|
+
|
|
136
|
+
def test_after_clear_nudge_fires_again_if_not_configured(self, tmp_path, capsys):
|
|
137
|
+
"""After mcp init clears flag, if somehow not configured, nudge re-fires."""
|
|
138
|
+
flag = tmp_path / "nudge_shown"
|
|
139
|
+
flag.touch()
|
|
140
|
+
client = _make_client(app_installed=True, config_path=tmp_path / "config.json")
|
|
141
|
+
|
|
142
|
+
with patch.object(nudge_mod, "_FLAG", flag):
|
|
143
|
+
clear_nudge_flag()
|
|
144
|
+
|
|
145
|
+
with (
|
|
146
|
+
patch.object(nudge_mod, "_FLAG", flag),
|
|
147
|
+
patch("sourcecode.mcp_nudge.detect_clients", return_value=[client]),
|
|
148
|
+
patch("sourcecode.mcp_nudge.is_installed", return_value=False),
|
|
149
|
+
patch("sourcecode.mcp_nudge.read_config", return_value={}),
|
|
150
|
+
):
|
|
151
|
+
nudge_mcp_if_needed()
|
|
152
|
+
|
|
153
|
+
assert "mcp init" in capsys.readouterr().err
|
|
@@ -537,15 +537,16 @@ class TestReviewPrSuspectedAreas:
|
|
|
537
537
|
|
|
538
538
|
def test_review_pr_requires_git_diff(self):
|
|
539
539
|
# FIXTURE has no uncommitted changes (or no git repo) — returns structured error.
|
|
540
|
-
# When running inside the atlas-cli git tree with no staged changes
|
|
541
|
-
#
|
|
540
|
+
# When running inside the atlas-cli git tree with no staged changes:
|
|
541
|
+
# - no_diff_source: no --since and no staged/unstaged changes (clean tree) → exit 1
|
|
542
|
+
# - no_diff: scope resolved but empty → exit 0
|
|
542
543
|
# When running outside any git repo, error is "no_git_repo" (exit 1 — true error).
|
|
543
544
|
result = _invoke("prepare-context", "review-pr", str(FIXTURE))
|
|
544
545
|
data = _json(result)
|
|
545
|
-
_git_errors = {"no_git_repo", "no_diff", "git_ref_not_found"}
|
|
546
|
+
_git_errors = {"no_git_repo", "no_diff", "no_diff_source", "git_ref_not_found"}
|
|
546
547
|
assert data.get("error") in _git_errors, f"Expected git error, got: {data}"
|
|
547
548
|
assert "ci_decision" in data
|
|
548
|
-
# Exit code: 0 for no_diff (no changes = success), 1 for
|
|
549
|
+
# Exit code: 0 for no_diff (no changes = success), 1 for all other errors
|
|
549
550
|
if data.get("error") == "no_diff":
|
|
550
551
|
assert result.exit_code == 0, f"no_diff must exit 0, got {result.exit_code}"
|
|
551
552
|
else:
|