sourcecode 1.31.28__tar.gz → 1.31.30__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.
Files changed (232) hide show
  1. sourcecode-1.31.30/.continue-here.md +89 -0
  2. {sourcecode-1.31.28 → sourcecode-1.31.30}/PKG-INFO +3 -3
  3. {sourcecode-1.31.28 → sourcecode-1.31.30}/README.md +2 -2
  4. {sourcecode-1.31.28 → sourcecode-1.31.30}/pyproject.toml +1 -1
  5. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/__init__.py +1 -1
  6. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/cli.py +84 -10
  7. sourcecode-1.31.30/src/sourcecode/mcp_nudge.py +84 -0
  8. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/prepare_context.py +24 -0
  9. sourcecode-1.31.30/tests/test_mcp_nudge.py +153 -0
  10. sourcecode-1.31.28/.continue-here.md +0 -98
  11. {sourcecode-1.31.28 → sourcecode-1.31.30}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  12. {sourcecode-1.31.28 → sourcecode-1.31.30}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  13. {sourcecode-1.31.28 → sourcecode-1.31.30}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  14. {sourcecode-1.31.28 → sourcecode-1.31.30}/.github/workflows/build-windows.yml +0 -0
  15. {sourcecode-1.31.28 → sourcecode-1.31.30}/.gitignore +0 -0
  16. {sourcecode-1.31.28 → sourcecode-1.31.30}/.ruff.toml +0 -0
  17. {sourcecode-1.31.28 → sourcecode-1.31.30}/.sourcecode-cache/snapshot-3b5997a-fa5c742c.json +0 -0
  18. {sourcecode-1.31.28 → sourcecode-1.31.30}/AUDIT_REAL_REPOS.md +0 -0
  19. {sourcecode-1.31.28 → sourcecode-1.31.30}/AUDIT_v1.31.23.md +0 -0
  20. {sourcecode-1.31.28 → sourcecode-1.31.30}/CHANGELOG.md +0 -0
  21. {sourcecode-1.31.28 → sourcecode-1.31.30}/CONTRIBUTING.md +0 -0
  22. {sourcecode-1.31.28 → sourcecode-1.31.30}/LICENSE +0 -0
  23. {sourcecode-1.31.28 → sourcecode-1.31.30}/SECURITY.md +0 -0
  24. {sourcecode-1.31.28 → sourcecode-1.31.30}/docs/PRODUCT_TIERS.md +0 -0
  25. {sourcecode-1.31.28 → sourcecode-1.31.30}/docs/privacy.md +0 -0
  26. {sourcecode-1.31.28 → sourcecode-1.31.30}/docs/schema.md +0 -0
  27. {sourcecode-1.31.28 → sourcecode-1.31.30}/raw +0 -0
  28. {sourcecode-1.31.28 → sourcecode-1.31.30}/run_cli.py +0 -0
  29. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/adaptive_scanner.py +0 -0
  30. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/architecture_analyzer.py +0 -0
  31. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/architecture_summary.py +0 -0
  32. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/ast_extractor.py +0 -0
  33. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/cache.py +0 -0
  34. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/canonical_ir.py +0 -0
  35. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/classifier.py +0 -0
  36. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/code_notes_analyzer.py +0 -0
  37. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/confidence_analyzer.py +0 -0
  38. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/context_scorer.py +0 -0
  39. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/context_summarizer.py +0 -0
  40. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/contract_model.py +0 -0
  41. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/contract_pipeline.py +0 -0
  42. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/coverage_parser.py +0 -0
  43. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/dependency_analyzer.py +0 -0
  44. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/__init__.py +0 -0
  45. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/base.py +0 -0
  46. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/csproj_parser.py +0 -0
  47. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/dart.py +0 -0
  48. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/dotnet.py +0 -0
  49. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/elixir.py +0 -0
  50. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/go.py +0 -0
  51. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/heuristic.py +0 -0
  52. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/hybrid.py +0 -0
  53. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/java.py +0 -0
  54. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/jvm_ext.py +0 -0
  55. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/nodejs.py +0 -0
  56. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/parsers.py +0 -0
  57. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/php.py +0 -0
  58. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/project.py +0 -0
  59. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/python.py +0 -0
  60. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/ruby.py +0 -0
  61. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/rust.py +0 -0
  62. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/systems.py +0 -0
  63. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/terraform.py +0 -0
  64. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/detectors/tooling.py +0 -0
  65. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/doc_analyzer.py +0 -0
  66. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/entrypoint_classifier.py +0 -0
  67. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/env_analyzer.py +0 -0
  68. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/file_classifier.py +0 -0
  69. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/flow_analyzer.py +0 -0
  70. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/git_analyzer.py +0 -0
  71. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/graph_analyzer.py +0 -0
  72. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/mcp/__init__.py +0 -0
  73. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  74. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  75. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  76. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/mcp/onboarding/detector.py +0 -0
  77. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  78. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/mcp/runner.py +0 -0
  79. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/mcp/server.py +0 -0
  80. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/metrics_analyzer.py +0 -0
  81. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/output_budget.py +0 -0
  82. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/path_filters.py +0 -0
  83. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/pr_comment_renderer.py +0 -0
  84. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/progress.py +0 -0
  85. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/ranking_engine.py +0 -0
  86. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/redactor.py +0 -0
  87. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/relevance_scorer.py +0 -0
  88. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/repo_classifier.py +0 -0
  89. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/repository_ir.py +0 -0
  90. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/runtime_classifier.py +0 -0
  91. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/scanner.py +0 -0
  92. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/schema.py +0 -0
  93. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/semantic_analyzer.py +0 -0
  94. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/serializer.py +0 -0
  95. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/summarizer.py +0 -0
  96. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/telemetry/__init__.py +0 -0
  97. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/telemetry/config.py +0 -0
  98. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/telemetry/consent.py +0 -0
  99. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/telemetry/events.py +0 -0
  100. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/telemetry/filters.py +0 -0
  101. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/telemetry/transport.py +0 -0
  102. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/tree_utils.py +0 -0
  103. {sourcecode-1.31.28 → sourcecode-1.31.30}/src/sourcecode/workspace.py +0 -0
  104. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/__init__.py +0 -0
  105. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/conftest.py +0 -0
  106. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/coverage.xml +0 -0
  107. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  108. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/fastapi_app/src/main.py +0 -0
  109. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  110. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/go_service/go.mod +0 -0
  111. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/jacoco.xml +0 -0
  112. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/latin1_sample.java +0 -0
  113. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/latin1_sample_iso.java +0 -0
  114. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/lcov.info +0 -0
  115. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  116. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/nextjs_app/package.json +0 -0
  117. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  118. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  119. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  120. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  121. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  122. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  123. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  124. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  125. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  126. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  127. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  128. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  129. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  130. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  131. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  132. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  133. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  134. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  135. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  136. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  137. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  138. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  139. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  140. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  141. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  142. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  143. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  144. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  145. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  146. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  147. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  148. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  149. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  150. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_architecture_analyzer.py +0 -0
  151. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_architecture_summary.py +0 -0
  152. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_ast_extractor.py +0 -0
  153. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_audit_fixes.py +0 -0
  154. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_audit_sas_v2.py +0 -0
  155. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_block1_reliability.py +0 -0
  156. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_block2_coverage.py +0 -0
  157. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_block5_quality.py +0 -0
  158. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_broadleaf_fixes.py +0 -0
  159. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_bug_fixes_v1302.py +0 -0
  160. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_bug_fixes_v13115.py +0 -0
  161. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_bug_fixes_v1312.py +0 -0
  162. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_bug_fixes_v13122.py +0 -0
  163. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_bug_fixes_v1313.py +0 -0
  164. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_bug_fixes_v1321.py +0 -0
  165. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_bug_fixes_v16.py +0 -0
  166. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_bug_fixes_v2.py +0 -0
  167. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_cache.py +0 -0
  168. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_canonical_ir.py +0 -0
  169. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_classifier.py +0 -0
  170. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_cli.py +0 -0
  171. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_code_notes_analyzer.py +0 -0
  172. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_context_scorer.py +0 -0
  173. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_contract_pipeline.py +0 -0
  174. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_coverage_parser.py +0 -0
  175. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_cross_consistency.py +0 -0
  176. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_dependency_analyzer_node_python.py +0 -0
  177. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_dependency_analyzer_polyglot.py +0 -0
  178. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_dependency_schema.py +0 -0
  179. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_detector_dotnet.py +0 -0
  180. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_detector_go_rust_java.py +0 -0
  181. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_detector_nodejs.py +0 -0
  182. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_detector_php_ruby_dart.py +0 -0
  183. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_detector_python.py +0 -0
  184. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_detector_universal_managed.py +0 -0
  185. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_detector_universal_systems.py +0 -0
  186. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_detectors_base.py +0 -0
  187. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_doc_analyzer_jsdom.py +0 -0
  188. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_doc_analyzer_python.py +0 -0
  189. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_encoding_regression.py +0 -0
  190. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_enterprise_benchmarks.py +0 -0
  191. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_graph_analyzer_polyglot.py +0 -0
  192. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_graph_analyzer_python_node.py +0 -0
  193. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_graph_schema.py +0 -0
  194. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_hybrid_inference.py +0 -0
  195. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_integration.py +0 -0
  196. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_integration_dependencies.py +0 -0
  197. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_integration_detection.py +0 -0
  198. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_integration_docs.py +0 -0
  199. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_integration_graph_modules.py +0 -0
  200. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_integration_lqn.py +0 -0
  201. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_integration_metrics.py +0 -0
  202. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_integration_multistack.py +0 -0
  203. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_integration_semantics.py +0 -0
  204. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_integration_universal.py +0 -0
  205. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_java_spring_integration.py +0 -0
  206. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_mcp_runner.py +0 -0
  207. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_mcp_serve.py +0 -0
  208. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_mcp_tools.py +0 -0
  209. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_metrics_analyzer.py +0 -0
  210. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_output_ux.py +0 -0
  211. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_packaging.py +0 -0
  212. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_phase1_improvements.py +0 -0
  213. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_pipeline_integrity.py +0 -0
  214. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_real_projects.py +0 -0
  215. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_redactor.py +0 -0
  216. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_repository_ir.py +0 -0
  217. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_scanner.py +0 -0
  218. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_schema.py +0 -0
  219. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_schema_normalization.py +0 -0
  220. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_scoring_calibration.py +0 -0
  221. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_semantic_analyzer_node.py +0 -0
  222. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_semantic_analyzer_python.py +0 -0
  223. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_semantic_import_resolution.py +0 -0
  224. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_semantic_schema.py +0 -0
  225. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_signal_hierarchy.py +0 -0
  226. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_summarizer.py +0 -0
  227. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_surface_honesty.py +0 -0
  228. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_task_differentiation.py +0 -0
  229. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_telemetry.py +0 -0
  230. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_v131_improvements.py +0 -0
  231. {sourcecode-1.31.28 → sourcecode-1.31.30}/tests/test_v1_10_regressions.py +0 -0
  232. {sourcecode-1.31.28 → sourcecode-1.31.30}/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.28
3
+ Version: 1.31.30
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
- ![Version](https://img.shields.io/badge/version-1.31.28-blue)
228
+ ![Version](https://img.shields.io/badge/version-1.31.30-blue)
229
229
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
230
230
 
231
231
  ---
@@ -263,7 +263,7 @@ pipx install sourcecode
263
263
 
264
264
  ```bash
265
265
  sourcecode version
266
- # sourcecode 1.31.28
266
+ # sourcecode 1.31.30
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
- ![Version](https://img.shields.io/badge/version-1.31.28-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.31.30-blue)
6
6
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
7
7
 
8
8
  ---
@@ -40,7 +40,7 @@ pipx install sourcecode
40
40
 
41
41
  ```bash
42
42
  sourcecode version
43
- # sourcecode 1.31.28
43
+ # sourcecode 1.31.30
44
44
  ```
45
45
 
46
46
  ---
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.31.28"
7
+ version = "1.31.30"
8
8
  description = "Deterministic codebase context for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Deterministic codebase context maps for AI coding agents."""
2
2
 
3
- __version__ = "1.31.28"
3
+ __version__ = "1.31.30"
@@ -760,8 +760,11 @@ def main(
760
760
  err=True,
761
761
  )
762
762
 
763
- # P0-2 FIX: --changed-only silently implies --compact; inform the user.
764
- if changed_only and not compact and not agent:
763
+ # P1-2 FIX: --changed-only silently implies --compact; inform only on TTY.
764
+ # PowerShell 5.1 interprets any stderr write (even with exit 0) as NativeCommandError.
765
+ # Gate on isatty() so pipeline consumers never see informational noise on stderr.
766
+ import sys as _sys_tty
767
+ if changed_only and not compact and not agent and _sys_tty.stderr.isatty():
765
768
  typer.echo(
766
769
  "[info] --changed-only implies --compact (bounding output to changed files).",
767
770
  err=True,
@@ -1952,6 +1955,10 @@ def main(
1952
1955
  if _copy_to_clipboard(content):
1953
1956
  typer.echo("✓ copied to clipboard", err=True)
1954
1957
 
1958
+ # 8. One-time MCP setup nudge (stderr only — does not affect exit code or stdout)
1959
+ from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
1960
+ _nudge()
1961
+
1955
1962
 
1956
1963
  # ── prepare-context output helpers ────────────────────────────────────────────
1957
1964
 
@@ -2180,7 +2187,11 @@ def prepare_context_cmd(
2180
2187
 
2181
2188
  target = path.resolve()
2182
2189
  if not target.exists() or not target.is_dir():
2183
- typer.echo(f"Error: '{target}' is not a valid directory.", err=True)
2190
+ _emit_error_json(
2191
+ "invalid_path",
2192
+ f"'{target}' is not a valid directory.",
2193
+ path=str(target),
2194
+ )
2184
2195
  raise typer.Exit(code=1)
2185
2196
 
2186
2197
  if dry_run:
@@ -2312,6 +2323,13 @@ def prepare_context_cmd(
2312
2323
  out["improvement_opportunities"] = output.improvement_opportunities
2313
2324
  if _task_include("test_gaps") and output.test_gaps:
2314
2325
  out["test_gaps"] = output.test_gaps
2326
+ # P0-2: fast-mode truncation transparency — always emit when truncated, even if test_gaps is []
2327
+ # Use `is True` (strict) so MagicMock objects in tests don't trigger this branch.
2328
+ if getattr(output, "truncated", False) is True:
2329
+ out["truncated"] = True
2330
+ _tr = getattr(output, "truncated_reason", None)
2331
+ if isinstance(_tr, str) and _tr:
2332
+ out["truncated_reason"] = _tr
2315
2333
  if _task_include("code_notes_summary") and output.code_notes_summary:
2316
2334
  out["code_notes_summary"] = output.code_notes_summary
2317
2335
  if _task_include("changed_files") and output.changed_files:
@@ -2554,6 +2572,9 @@ def prepare_context_cmd(
2554
2572
  if _copy_to_clipboard(_pc_content):
2555
2573
  typer.echo("✓ copied to clipboard", err=True)
2556
2574
 
2575
+ from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
2576
+ _nudge()
2577
+
2557
2578
 
2558
2579
  # ── Telemetry commands ────────────────────────────────────────────────────────
2559
2580
 
@@ -2697,7 +2718,11 @@ def repo_ir_cmd(
2697
2718
 
2698
2719
  root = path.resolve()
2699
2720
  if not root.is_dir():
2700
- typer.echo(f"Error: {root} is not a directory", err=True)
2721
+ _emit_error_json(
2722
+ "invalid_path",
2723
+ f"'{root}' is not a valid directory.",
2724
+ path=str(root),
2725
+ )
2701
2726
  raise typer.Exit(1)
2702
2727
 
2703
2728
  if files:
@@ -2849,7 +2874,11 @@ def impact_cmd(
2849
2874
 
2850
2875
  root = path.resolve()
2851
2876
  if not root.is_dir():
2852
- typer.echo(f"Error: {root} is not a directory", err=True)
2877
+ _emit_error_json(
2878
+ "invalid_path",
2879
+ f"'{root}' is not a valid directory.",
2880
+ path=str(root),
2881
+ )
2853
2882
  raise typer.Exit(1)
2854
2883
 
2855
2884
  file_list = find_java_files(root)
@@ -2899,6 +2928,9 @@ def impact_cmd(
2899
2928
  if result.get("resolution") == "not_found":
2900
2929
  raise typer.Exit(code=1)
2901
2930
 
2931
+ from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
2932
+ _nudge()
2933
+
2902
2934
 
2903
2935
  # ── endpoints ─────────────────────────────────────────────────────────────────
2904
2936
 
@@ -2949,7 +2981,11 @@ def endpoints_cmd(
2949
2981
 
2950
2982
  target = path.resolve()
2951
2983
  if not target.exists() or not target.is_dir():
2952
- typer.echo(f"Error: '{target}' is not a valid directory.", err=True)
2984
+ _emit_error_json(
2985
+ "invalid_path",
2986
+ f"'{target}' is not a valid directory.",
2987
+ path=str(target),
2988
+ )
2953
2989
  raise typer.Exit(code=1)
2954
2990
 
2955
2991
  data = _extract_java_endpoints(target)
@@ -2969,6 +3005,9 @@ def endpoints_cmd(
2969
3005
  if _copy_to_clipboard(output):
2970
3006
  typer.echo("✓ copied to clipboard", err=True)
2971
3007
 
3008
+ from sourcecode.mcp_nudge import nudge_mcp_if_needed as _nudge
3009
+ _nudge()
3010
+
2972
3011
 
2973
3012
  # ── Enterprise Workflow Commands ──────────────────────────────────────────────
2974
3013
  #
@@ -3062,7 +3101,10 @@ def review_pr_cmd(
3062
3101
  help="Copy output to clipboard after a successful run.",
3063
3102
  ),
3064
3103
  ) -> None:
3065
- """[Pro] PR review: blast radius, risk ranking, execution paths, security/txn impact.
3104
+ """[Pro*] PR review: blast radius, risk ranking, execution paths, security/txn impact.
3105
+
3106
+ Note: [Pro*] label is reserved for a future licensing gate. This command currently
3107
+ runs without authentication. Behavior may change in a future version.
3066
3108
 
3067
3109
  \b
3068
3110
  Answers: "What does this PR break and how risky is it?"
@@ -3122,7 +3164,10 @@ def fix_bug_cmd(
3122
3164
  help="Copy output to clipboard after a successful run.",
3123
3165
  ),
3124
3166
  ) -> None:
3125
- """[Pro] Bug triage: risk-ranked files, suspected areas, related annotations.
3167
+ """[Pro*] Bug triage: risk-ranked files, suspected areas, related annotations.
3168
+
3169
+ Note: [Pro*] label is reserved for a future licensing gate. This command currently
3170
+ runs without authentication. Behavior may change in a future version.
3126
3171
 
3127
3172
  \b
3128
3173
  Answers: "Where in this codebase should I look to fix this symptom?"
@@ -3174,7 +3219,10 @@ def modernize_cmd(
3174
3219
  help="Copy output to clipboard after a successful run.",
3175
3220
  ),
3176
3221
  ) -> None:
3177
- """[Pro] Modernization planning: coupling, dead zones, risky modules, refactor candidates.
3222
+ """[Pro*] Modernization planning: coupling, dead zones, risky modules, refactor candidates.
3223
+
3224
+ Note: [Pro*] label is reserved for a future licensing gate. This command currently
3225
+ runs without authentication. Behavior may change in a future version.
3178
3226
 
3179
3227
  \b
3180
3228
  Answers: "Where should I refactor first, and what's safest to touch?"
@@ -3205,7 +3253,11 @@ def modernize_cmd(
3205
3253
 
3206
3254
  root = path.resolve()
3207
3255
  if not root.is_dir():
3208
- typer.echo(f"Error: {root} is not a directory", err=True)
3256
+ _emit_error_json(
3257
+ "invalid_path",
3258
+ f"'{root}' is not a valid directory.",
3259
+ path=str(root),
3260
+ )
3209
3261
  raise typer.Exit(1)
3210
3262
 
3211
3263
  file_list = find_java_files(root)
@@ -3430,6 +3482,24 @@ def mcp_serve() -> None:
3430
3482
  from sourcecode.mcp.server import mcp as _mcp
3431
3483
 
3432
3484
  log = logging.getLogger(__name__)
3485
+
3486
+ # P0-1: Strip UTF-8 BOM from stdin.buffer before the MCP server reads it.
3487
+ # PowerShell 5.1 on Windows writes \xEF\xBB\xBF at the start of stdin,
3488
+ # which breaks JSON parsing at line 1 column 1.
3489
+ # peek(3) loads bytes into BufferedReader's internal buffer without consuming;
3490
+ # read(3) discards only if the prefix is the UTF-8 BOM sequence.
3491
+ # No-op on Linux/macOS/Git Bash where stdin never starts with a BOM.
3492
+ # Guard: CliRunner / test stubs replace sys.stdin with StringIO (no .buffer).
3493
+ try:
3494
+ _stdin_buf = getattr(_sys.stdin, "buffer", None)
3495
+ if _stdin_buf is not None and hasattr(_stdin_buf, "peek"):
3496
+ _bom_prefix = _stdin_buf.peek(3)[:3]
3497
+ if _bom_prefix == b"\xef\xbb\xbf":
3498
+ _stdin_buf.read(3)
3499
+ log.info("sourcecode-mcp stripped UTF-8 BOM from stdin (PowerShell 5.1 workaround)")
3500
+ except Exception:
3501
+ pass # Never abort server startup over BOM detection
3502
+
3433
3503
  log.info("sourcecode-mcp starting (stdio transport)")
3434
3504
  try:
3435
3505
  _mcp.run()
@@ -3574,6 +3644,10 @@ def mcp_init(
3574
3644
  typer.echo("")
3575
3645
  typer.echo(" Remove: sourcecode mcp remove")
3576
3646
 
3647
+ # Clear nudge flag: next run finds is_installed=True → no nudge.
3648
+ from sourcecode.mcp_nudge import clear_nudge_flag as _clear_nudge
3649
+ _clear_nudge()
3650
+
3577
3651
 
3578
3652
  @mcp_app.command("status")
3579
3653
  def mcp_status() -> None:
@@ -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
@@ -388,6 +388,9 @@ class TaskOutput:
388
388
  deployment_risks: list[str] = field(default_factory=list)
389
389
  deployment: Optional[dict] = None
390
390
  entry_points_structured: Optional[dict] = None
391
+ # P0-2: fast-mode truncation transparency
392
+ truncated: bool = False
393
+ truncated_reason: Optional[str] = None
391
394
 
392
395
 
393
396
  @dataclass
@@ -1991,6 +1994,21 @@ class TaskContextBuilder:
1991
1994
  untested.sort(key=lambda p: (len(p.split("/")), p))
1992
1995
  test_gaps = untested[:15]
1993
1996
 
1997
+ # P0-2: fast mode truncation transparency for generate-tests.
1998
+ # When --fast is active the test-gap discovery block is skipped entirely,
1999
+ # so test_gaps stays []. Without a signal the receiver interprets [] as
2000
+ # "no gaps", which is incorrect. Emit explicit truncation metadata and
2001
+ # downgrade confidence so the agent knows the analysis is incomplete.
2002
+ _fast_truncated = fast and task_name == "generate-tests"
2003
+ _fast_truncated_reason = "fast mode skips test gap discovery" if _fast_truncated else None
2004
+ if _fast_truncated:
2005
+ import sys as _sys_warn
2006
+ _sys_warn.stderr.write(
2007
+ "[warn] prepare-context generate-tests --fast: test gap discovery skipped. "
2008
+ "Output will contain truncated=true and confidence=low.\n"
2009
+ )
2010
+ _sys_warn.stderr.flush()
2011
+
1994
2012
  # ── 8. Confidence + gaps ──────────────────────────────────────────────
1995
2013
  from sourcecode.confidence_analyzer import ConfidenceAnalyzer
1996
2014
  from dataclasses import asdict as _asdict
@@ -2012,6 +2030,9 @@ class TaskContextBuilder:
2012
2030
 
2013
2031
  conf_summary, analysis_gaps = ConfidenceAnalyzer().analyze(sm_for_conf)
2014
2032
  confidence = conf_summary.overall
2033
+ # P0-2: fast-mode truncation overrides confidence to signal incomplete analysis
2034
+ if _fast_truncated:
2035
+ confidence = "low"
2015
2036
  _has_mybatis = any(
2016
2037
  f.name == "MyBatis"
2017
2038
  for s in stacks
@@ -2130,6 +2151,9 @@ class TaskContextBuilder:
2130
2151
  deployment_risks=_cb_deploy_risks,
2131
2152
  deployment=_cb_deployment,
2132
2153
  entry_points_structured=_cb_bootstrap,
2154
+ # P0-2: fast-mode truncation transparency
2155
+ truncated=_fast_truncated,
2156
+ truncated_reason=_fast_truncated_reason,
2133
2157
  )
2134
2158
 
2135
2159
  def render_prompt(self, output: TaskOutput) -> 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