sourcecode 1.31.0__tar.gz → 1.31.2__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 (216) hide show
  1. {sourcecode-1.31.0 → sourcecode-1.31.2}/PKG-INFO +4 -3
  2. {sourcecode-1.31.0 → sourcecode-1.31.2}/README.md +2 -2
  3. {sourcecode-1.31.0 → sourcecode-1.31.2}/pyproject.toml +2 -1
  4. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/__init__.py +1 -1
  5. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/cli.py +287 -26
  6. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/confidence_analyzer.py +1 -1
  7. sourcecode-1.31.2/src/sourcecode/mcp/onboarding/detector.py +104 -0
  8. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/mcp/runner.py +10 -3
  9. sourcecode-1.31.2/src/sourcecode/mcp/server.py +156 -0
  10. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/prepare_context.py +165 -35
  11. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/serializer.py +1 -1
  12. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_mcp_runner.py +3 -3
  13. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_mcp_serve.py +0 -8
  14. sourcecode-1.31.2/tests/test_mcp_tools.py +232 -0
  15. sourcecode-1.31.0/.sourcecode-cache/snapshot-0e430c1-37df4554.json +0 -262
  16. sourcecode-1.31.0/.sourcecode-cache/snapshot-0e430c1-e9801942.json +0 -219
  17. sourcecode-1.31.0/.sourcecode-cache/snapshot-0e430c1-ee60e0cd.json +0 -326
  18. sourcecode-1.31.0/src/sourcecode/mcp/onboarding/detector.py +0 -59
  19. sourcecode-1.31.0/src/sourcecode/mcp/server.py +0 -147
  20. sourcecode-1.31.0/tests/test_mcp_tools.py +0 -242
  21. {sourcecode-1.31.0 → sourcecode-1.31.2}/.agents/skills/source-command-gsd-join-discord/SKILL.md +0 -0
  22. {sourcecode-1.31.0 → sourcecode-1.31.2}/.agents/skills/source-command-gsd-review-backlog/SKILL.md +0 -0
  23. {sourcecode-1.31.0 → sourcecode-1.31.2}/.agents/skills/source-command-gsd-workstreams/SKILL.md +0 -0
  24. {sourcecode-1.31.0 → sourcecode-1.31.2}/.continue-here.md +0 -0
  25. {sourcecode-1.31.0 → sourcecode-1.31.2}/.github/workflows/build-windows.yml +0 -0
  26. {sourcecode-1.31.0 → sourcecode-1.31.2}/.gitignore +0 -0
  27. {sourcecode-1.31.0 → sourcecode-1.31.2}/.ruff.toml +0 -0
  28. {sourcecode-1.31.0 → sourcecode-1.31.2}/CHANGELOG.md +0 -0
  29. {sourcecode-1.31.0 → sourcecode-1.31.2}/CONTRIBUTING.md +0 -0
  30. {sourcecode-1.31.0 → sourcecode-1.31.2}/LICENSE +0 -0
  31. {sourcecode-1.31.0 → sourcecode-1.31.2}/SECURITY.md +0 -0
  32. {sourcecode-1.31.0 → sourcecode-1.31.2}/docs/privacy.md +0 -0
  33. {sourcecode-1.31.0 → sourcecode-1.31.2}/docs/schema.md +0 -0
  34. {sourcecode-1.31.0 → sourcecode-1.31.2}/raw +0 -0
  35. {sourcecode-1.31.0 → sourcecode-1.31.2}/run_cli.py +0 -0
  36. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/adaptive_scanner.py +0 -0
  37. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/architecture_analyzer.py +0 -0
  38. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/architecture_summary.py +0 -0
  39. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/ast_extractor.py +0 -0
  40. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/classifier.py +0 -0
  41. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/code_notes_analyzer.py +0 -0
  42. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/context_scorer.py +0 -0
  43. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/context_summarizer.py +0 -0
  44. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/contract_model.py +0 -0
  45. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/contract_pipeline.py +0 -0
  46. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/coverage_parser.py +0 -0
  47. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/dependency_analyzer.py +0 -0
  48. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/__init__.py +0 -0
  49. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/base.py +0 -0
  50. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/csproj_parser.py +0 -0
  51. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/dart.py +0 -0
  52. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/dotnet.py +0 -0
  53. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/elixir.py +0 -0
  54. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/go.py +0 -0
  55. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/heuristic.py +0 -0
  56. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/hybrid.py +0 -0
  57. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/java.py +0 -0
  58. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/jvm_ext.py +0 -0
  59. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/nodejs.py +0 -0
  60. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/parsers.py +0 -0
  61. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/php.py +0 -0
  62. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/project.py +0 -0
  63. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/python.py +0 -0
  64. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/ruby.py +0 -0
  65. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/rust.py +0 -0
  66. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/systems.py +0 -0
  67. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/terraform.py +0 -0
  68. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/detectors/tooling.py +0 -0
  69. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/doc_analyzer.py +0 -0
  70. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/entrypoint_classifier.py +0 -0
  71. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/env_analyzer.py +0 -0
  72. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/file_classifier.py +0 -0
  73. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/flow_analyzer.py +0 -0
  74. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/git_analyzer.py +0 -0
  75. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/graph_analyzer.py +0 -0
  76. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/mcp/__init__.py +0 -0
  77. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
  78. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/mcp/onboarding/applier.py +0 -0
  79. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/mcp/onboarding/backup.py +0 -0
  80. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/mcp/onboarding/planner.py +0 -0
  81. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/metrics_analyzer.py +0 -0
  82. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/pr_comment_renderer.py +0 -0
  83. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/progress.py +0 -0
  84. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/ranking_engine.py +0 -0
  85. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/redactor.py +0 -0
  86. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/relevance_scorer.py +0 -0
  87. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/repo_classifier.py +0 -0
  88. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/repository_ir.py +0 -0
  89. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/runtime_classifier.py +0 -0
  90. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/scanner.py +0 -0
  91. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/schema.py +0 -0
  92. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/semantic_analyzer.py +0 -0
  93. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/summarizer.py +0 -0
  94. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/telemetry/__init__.py +0 -0
  95. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/telemetry/config.py +0 -0
  96. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/telemetry/consent.py +0 -0
  97. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/telemetry/events.py +0 -0
  98. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/telemetry/filters.py +0 -0
  99. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/telemetry/transport.py +0 -0
  100. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/tree_utils.py +0 -0
  101. {sourcecode-1.31.0 → sourcecode-1.31.2}/src/sourcecode/workspace.py +0 -0
  102. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/__init__.py +0 -0
  103. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/conftest.py +0 -0
  104. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/coverage.xml +0 -0
  105. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  106. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/fastapi_app/src/main.py +0 -0
  107. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  108. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/go_service/go.mod +0 -0
  109. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/jacoco.xml +0 -0
  110. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/latin1_sample.java +0 -0
  111. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/latin1_sample_iso.java +0 -0
  112. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/lcov.info +0 -0
  113. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  114. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/nextjs_app/package.json +0 -0
  115. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  116. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  117. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  118. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  119. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  120. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  121. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/pom.xml +0 -0
  122. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/application/service/FindAusenteService.java +0 -0
  123. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/domain/entities/Ausente.java +0 -0
  124. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/ausente/infrastructure/rest/AusenteRestController.java +0 -0
  125. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/application/service/FindAutocoberturasService.java +0 -0
  126. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/domain/entities/Autocoberturas.java +0 -0
  127. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/autocoberturas/infrastructure/rest/AutocoberturasRestController.java +0 -0
  128. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/application/service/FindCalendarioTrabajadorService.java +0 -0
  129. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/domain/entities/CalendarioTrabajador.java +0 -0
  130. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/calendarioTrabajador/infrastructure/rest/CalendarioTrabajadorRestController.java +0 -0
  131. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/application/service/FindDepartamentoService.java +0 -0
  132. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/domain/entities/Departamento.java +0 -0
  133. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/departamento/infrastructure/rest/DepartamentoRestController.java +0 -0
  134. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/application/service/FindEmpleadoService.java +0 -0
  135. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/domain/entities/Empleado.java +0 -0
  136. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/ddd/empleado/infrastructure/rest/EmpleadoRestController.java +0 -0
  137. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/DemoApplication.java +0 -0
  138. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/config/FilterConfig.java +0 -0
  139. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/domain/Health.java +0 -0
  140. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/mapper/HealthMapper.java +0 -0
  141. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/repository/HealthRepository.java +0 -0
  142. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/service/HealthService.java +0 -0
  143. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/HealthRestController.java +0 -0
  144. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/java/com/example/demo/web/NominaRestController.java +0 -0
  145. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/resources/application-dev.yml +0 -0
  146. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/resources/application.yml +0 -0
  147. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/fixtures/spring_boot_minimal/src/main/resources/mapper/HealthMapper.xml +0 -0
  148. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_architecture_analyzer.py +0 -0
  149. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_architecture_summary.py +0 -0
  150. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_ast_extractor.py +0 -0
  151. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_block1_reliability.py +0 -0
  152. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_block2_coverage.py +0 -0
  153. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_block5_quality.py +0 -0
  154. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_bug_fixes_v1302.py +0 -0
  155. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_bug_fixes_v16.py +0 -0
  156. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_bug_fixes_v2.py +0 -0
  157. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_classifier.py +0 -0
  158. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_cli.py +0 -0
  159. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_code_notes_analyzer.py +0 -0
  160. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_context_scorer.py +0 -0
  161. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_contract_pipeline.py +0 -0
  162. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_coverage_parser.py +0 -0
  163. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_cross_consistency.py +0 -0
  164. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_dependency_analyzer_node_python.py +0 -0
  165. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_dependency_analyzer_polyglot.py +0 -0
  166. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_dependency_schema.py +0 -0
  167. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_detector_dotnet.py +0 -0
  168. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_detector_go_rust_java.py +0 -0
  169. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_detector_nodejs.py +0 -0
  170. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_detector_php_ruby_dart.py +0 -0
  171. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_detector_python.py +0 -0
  172. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_detector_universal_managed.py +0 -0
  173. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_detector_universal_systems.py +0 -0
  174. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_detectors_base.py +0 -0
  175. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_doc_analyzer_jsdom.py +0 -0
  176. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_doc_analyzer_python.py +0 -0
  177. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_encoding_regression.py +0 -0
  178. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_graph_analyzer_polyglot.py +0 -0
  179. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_graph_analyzer_python_node.py +0 -0
  180. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_graph_schema.py +0 -0
  181. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_hybrid_inference.py +0 -0
  182. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_integration.py +0 -0
  183. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_integration_dependencies.py +0 -0
  184. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_integration_detection.py +0 -0
  185. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_integration_docs.py +0 -0
  186. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_integration_graph_modules.py +0 -0
  187. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_integration_lqn.py +0 -0
  188. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_integration_metrics.py +0 -0
  189. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_integration_multistack.py +0 -0
  190. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_integration_semantics.py +0 -0
  191. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_integration_universal.py +0 -0
  192. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_java_spring_integration.py +0 -0
  193. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_metrics_analyzer.py +0 -0
  194. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_output_ux.py +0 -0
  195. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_packaging.py +0 -0
  196. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_phase1_improvements.py +0 -0
  197. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_pipeline_integrity.py +0 -0
  198. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_real_projects.py +0 -0
  199. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_redactor.py +0 -0
  200. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_repository_ir.py +0 -0
  201. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_scanner.py +0 -0
  202. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_schema.py +0 -0
  203. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_schema_normalization.py +0 -0
  204. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_scoring_calibration.py +0 -0
  205. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_semantic_analyzer_node.py +0 -0
  206. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_semantic_analyzer_python.py +0 -0
  207. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_semantic_import_resolution.py +0 -0
  208. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_semantic_schema.py +0 -0
  209. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_signal_hierarchy.py +0 -0
  210. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_summarizer.py +0 -0
  211. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_surface_honesty.py +0 -0
  212. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_task_differentiation.py +0 -0
  213. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_telemetry.py +0 -0
  214. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_v131_improvements.py +0 -0
  215. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_v1_10_regressions.py +0 -0
  216. {sourcecode-1.31.0 → sourcecode-1.31.2}/tests/test_workspace_analyzer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 1.31.0
3
+ Version: 1.31.2
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -203,6 +203,7 @@ Classifier: Programming Language :: Python :: 3.12
203
203
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
204
204
  Classifier: Topic :: Utilities
205
205
  Requires-Python: >=3.9
206
+ Requires-Dist: mcp>=1.0.0
206
207
  Requires-Dist: pathspec>=1.0
207
208
  Requires-Dist: ruamel-yaml>=0.18
208
209
  Requires-Dist: tomli>=2.0; python_version < '3.11'
@@ -224,7 +225,7 @@ Description-Content-Type: text/markdown
224
225
 
225
226
  **Deterministic, behavior-aware codebase context for AI agents and PR review.**
226
227
 
227
- ![Version](https://img.shields.io/badge/version-1.31.0-blue)
228
+ ![Version](https://img.shields.io/badge/version-1.31.2-blue)
228
229
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
229
230
 
230
231
  ---
@@ -260,7 +261,7 @@ pipx install sourcecode
260
261
 
261
262
  ```bash
262
263
  sourcecode version
263
- # sourcecode 1.31.0
264
+ # sourcecode 1.31.2
264
265
  ```
265
266
 
266
267
  ---
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Deterministic, behavior-aware codebase context for AI agents and PR review.**
4
4
 
5
- ![Version](https://img.shields.io/badge/version-1.31.0-blue)
5
+ ![Version](https://img.shields.io/badge/version-1.31.2-blue)
6
6
  ![Python](https://img.shields.io/badge/python-3.10%2B-green)
7
7
 
8
8
  ---
@@ -38,7 +38,7 @@ pipx install sourcecode
38
38
 
39
39
  ```bash
40
40
  sourcecode version
41
- # sourcecode 1.31.0
41
+ # sourcecode 1.31.2
42
42
  ```
43
43
 
44
44
  ---
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "1.31.0"
7
+ version = "1.31.2"
8
8
  description = "Deterministic codebase context for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -29,6 +29,7 @@ dependencies = [
29
29
  "pathspec>=1.0",
30
30
  "ruamel.yaml>=0.18",
31
31
  "tomli>=2.0; python_version < '3.11'",
32
+ "mcp>=1.0.0",
32
33
  ]
33
34
 
34
35
  [project.scripts]
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Deterministic codebase context maps for AI coding agents."""
2
2
 
3
- __version__ = "1.31.0"
3
+ __version__ = "1.31.2"
@@ -165,7 +165,7 @@ Compressed AI-ready context for Java/Spring enterprise codebases.
165
165
  # Known subcommand names — tokens matching these are routed as subcommands,
166
166
  # not consumed as a repository path.
167
167
  _SUBCOMMANDS: frozenset[str] = frozenset(
168
- {"telemetry", "prepare-context", "version", "config", "analyze", "repo-ir", "mcp"}
168
+ {"telemetry", "prepare-context", "version", "config", "analyze", "repo-ir", "mcp", "endpoints"}
169
169
  )
170
170
 
171
171
  # Mutable container holding the path extracted by _preprocess_argv().
@@ -2037,6 +2037,27 @@ def prepare_context_cmd(
2037
2037
  out["changed_files"] = output.changed_files
2038
2038
  if _task_include("affected_entry_points") and output.affected_entry_points:
2039
2039
  out["affected_entry_points"] = output.affected_entry_points
2040
+ # compact_base fields — included for all non-delta/review-pr tasks (Fix #1)
2041
+ if task not in ("delta", "review-pr"):
2042
+ if output.entry_points_structured:
2043
+ out["entry_points"] = output.entry_points_structured
2044
+ if output.deployment:
2045
+ out["deployment"] = output.deployment
2046
+ if output.deployment_risks:
2047
+ out["deployment_risks"] = output.deployment_risks
2048
+ if output.security_surface:
2049
+ out["security_surface"] = output.security_surface
2050
+ if output.mybatis:
2051
+ out["mybatis"] = output.mybatis
2052
+ if output.transactional_boundaries:
2053
+ out["transactional_boundaries"] = output.transactional_boundaries
2054
+ if output.spring_profiles_info:
2055
+ out["spring_profiles"] = output.spring_profiles_info
2056
+ if output.angular_analysis and (
2057
+ output.angular_analysis.get("component_count", 0) > 0
2058
+ or output.angular_analysis.get("angular_version")
2059
+ ):
2060
+ out["angular_analysis"] = output.angular_analysis
2040
2061
  # Delta-specific impact fields
2041
2062
  if task == "delta":
2042
2063
  if output.error_code:
@@ -2420,6 +2441,190 @@ def repo_ir_cmd(
2420
2441
  _sys.stdout.write("\n")
2421
2442
 
2422
2443
 
2444
+ # ── endpoints ─────────────────────────────────────────────────────────────────
2445
+
2446
+ def _extract_java_endpoints(root: "Path") -> "dict[str, Any]":
2447
+ """Extract REST endpoint surface from Java source files.
2448
+
2449
+ Scans all .java files for @RequestMapping/@GetMapping/@PostMapping/@PutMapping/
2450
+ @DeleteMapping/@PatchMapping and @M3FiltroSeguridad annotations.
2451
+ Returns JSON-serializable dict with endpoints list, total, and undocumented count.
2452
+ """
2453
+ import re as _re
2454
+ from pathlib import Path as _Path
2455
+
2456
+ _HTTP_MAPPING_RE = _re.compile(
2457
+ r'@(Get|Post|Put|Delete|Patch|Request)Mapping\s*'
2458
+ r'(?:\(\s*(?:value\s*=\s*)?(?:"([^"]*)"|\{[^}]*\}|[^)]*)\s*\))?',
2459
+ )
2460
+ _CLASS_RE = _re.compile(r'(?:class|interface)\s+(\w+)')
2461
+ _METHOD_RE = _re.compile(
2462
+ r'(?:public|protected|private)\s+\S+\s+(\w+)\s*\(',
2463
+ )
2464
+ _FILTRO_RE = _re.compile(
2465
+ r'@M3FiltroSeguridad\s*\(\s*(?:nombreRecurso\s*=\s*)?["\']([^"\']+)["\']',
2466
+ )
2467
+ _CLASS_PATH_RE = _re.compile(
2468
+ r'@RequestMapping\s*\(\s*(?:value\s*=\s*)?["\']([^"\']+)["\']',
2469
+ )
2470
+
2471
+ _HTTP_METHOD_MAP = {
2472
+ "Get": "GET", "Post": "POST", "Put": "PUT",
2473
+ "Delete": "DELETE", "Patch": "PATCH", "Request": "GET",
2474
+ }
2475
+
2476
+ endpoints: list[dict] = []
2477
+ seen: set[tuple] = set()
2478
+
2479
+ java_files = [
2480
+ p for p in root.rglob("*.java")
2481
+ if "/test/" not in str(p).replace("\\", "/")
2482
+ and "/tests/" not in str(p).replace("\\", "/")
2483
+ and "target/" not in str(p).replace("\\", "/")
2484
+ ]
2485
+
2486
+ for java_file in java_files:
2487
+ try:
2488
+ content = java_file.read_text(encoding="utf-8", errors="replace")
2489
+ except OSError:
2490
+ continue
2491
+
2492
+ # Only process files with REST controller or mapping annotations
2493
+ if not any(x in content for x in ("@RestController", "@Controller", "@RequestMapping")):
2494
+ continue
2495
+
2496
+ try:
2497
+ rel_path = str(java_file.relative_to(root)).replace("\\", "/")
2498
+ except ValueError:
2499
+ rel_path = str(java_file).replace("\\", "/")
2500
+
2501
+ # Extract class name
2502
+ cls_m = _CLASS_RE.search(content)
2503
+ class_name = cls_m.group(1) if cls_m else java_file.stem
2504
+
2505
+ # Extract class-level base path from @RequestMapping on the class
2506
+ class_base = ""
2507
+ lines = content.splitlines()
2508
+ for i, line in enumerate(lines):
2509
+ if "@RequestMapping" in line and i < len(lines) - 1:
2510
+ # Check if next non-blank line is class declaration or it's on same block
2511
+ block = "\n".join(lines[max(0, i - 1): i + 5])
2512
+ if "class " in block or "interface " in block:
2513
+ path_m = _CLASS_PATH_RE.search(block)
2514
+ if path_m:
2515
+ class_base = path_m.group(1).rstrip("/")
2516
+ break
2517
+
2518
+ # Extract method-level endpoints
2519
+ # Parse line-by-line to associate annotations with methods
2520
+ pending_annotations: list[tuple[str, str]] = [] # (http_verb, path_suffix)
2521
+ pending_filtro: Optional[str] = None
2522
+
2523
+ for i, line in enumerate(lines):
2524
+ stripped = line.strip()
2525
+
2526
+ # Check for @M3FiltroSeguridad
2527
+ fm = _FILTRO_RE.search(stripped)
2528
+ if fm:
2529
+ pending_filtro = fm.group(1)
2530
+ continue
2531
+
2532
+ # Check for HTTP mapping annotations
2533
+ hm = _HTTP_MAPPING_RE.search(stripped)
2534
+ if hm:
2535
+ verb_key = hm.group(1)
2536
+ http_verb = _HTTP_METHOD_MAP.get(verb_key, "GET")
2537
+ path_suffix = (hm.group(2) or "").strip()
2538
+ pending_annotations.append((http_verb, path_suffix))
2539
+ continue
2540
+
2541
+ # Check for method declaration — flush pending annotations
2542
+ if pending_annotations and ("public " in stripped or "protected " in stripped):
2543
+ mm = _METHOD_RE.search(stripped)
2544
+ handler = mm.group(1) if mm else ""
2545
+ if handler and not handler.startswith("class"):
2546
+ for http_verb, path_suffix in pending_annotations:
2547
+ full_path = (class_base + "/" + path_suffix).replace("//", "/").rstrip("/") or "/"
2548
+ if not full_path.startswith("/"):
2549
+ full_path = "/" + full_path
2550
+ key = (class_name, handler, http_verb)
2551
+ if key not in seen:
2552
+ seen.add(key)
2553
+ entry: dict[str, Any] = {
2554
+ "method": http_verb,
2555
+ "path": full_path,
2556
+ "controller": class_name,
2557
+ "handler": handler,
2558
+ }
2559
+ if pending_filtro:
2560
+ entry["required_permission"] = pending_filtro
2561
+ endpoints.append(entry)
2562
+ pending_annotations = []
2563
+ pending_filtro = None
2564
+ continue
2565
+
2566
+ # Non-annotation, non-method line — reset if it's a closing brace or blank
2567
+ if stripped in ("}", "{", "") or stripped.startswith("//") or stripped.startswith("*"):
2568
+ if stripped == "}":
2569
+ pending_annotations = []
2570
+ pending_filtro = None
2571
+
2572
+ endpoints.sort(key=lambda e: (e.get("controller", ""), e.get("path", "")))
2573
+ undocumented = sum(1 for e in endpoints if "required_permission" not in e)
2574
+
2575
+ return {
2576
+ "endpoints": endpoints,
2577
+ "total": len(endpoints),
2578
+ "undocumented": undocumented,
2579
+ }
2580
+
2581
+
2582
+ @app.command("endpoints")
2583
+ def endpoints_cmd(
2584
+ path: Path = typer.Argument(
2585
+ Path("."),
2586
+ help="Repository path to scan for REST endpoints (default: current directory)",
2587
+ ),
2588
+ output_path: Optional[Path] = typer.Option(
2589
+ None, "--output", "-o",
2590
+ help="Write output to a file instead of stdout.",
2591
+ ),
2592
+ ) -> None:
2593
+ """Extract REST API endpoint surface from Java source files.
2594
+
2595
+ \b
2596
+ Scans all @GetMapping/@PostMapping/@PutMapping/@DeleteMapping/@PatchMapping
2597
+ and @RequestMapping annotations. Extracts HTTP method, path, controller class,
2598
+ handler method, and @M3FiltroSeguridad permission resource name.
2599
+
2600
+ \b
2601
+ Examples:
2602
+ sourcecode endpoints .
2603
+ sourcecode endpoints /path/to/repo
2604
+ sourcecode endpoints . --output endpoints.json
2605
+ """
2606
+ import sys as _sys
2607
+
2608
+ target = path.resolve()
2609
+ if not target.exists() or not target.is_dir():
2610
+ typer.echo(f"Error: '{target}' is not a valid directory.", err=True)
2611
+ raise typer.Exit(code=1)
2612
+
2613
+ data = _extract_java_endpoints(target)
2614
+ output = json.dumps(data, indent=2, ensure_ascii=False)
2615
+
2616
+ if output_path is not None:
2617
+ output_path.write_text(output, encoding="utf-8")
2618
+ typer.echo(
2619
+ f"Endpoints written to {output_path} ({data['total']} endpoints)",
2620
+ err=True,
2621
+ )
2622
+ else:
2623
+ _sys.stdout.buffer.write(output.encode("utf-8"))
2624
+ _sys.stdout.buffer.write(b"\n")
2625
+ _sys.stdout.buffer.flush()
2626
+
2627
+
2423
2628
  # ── version ───────────────────────────────────────────────────────────────────
2424
2629
 
2425
2630
  @app.command("version")
@@ -2466,10 +2671,6 @@ def analyze_cmd(
2466
2671
  def mcp_serve() -> None:
2467
2672
  """Start the MCP server on stdio for AI agent integration.
2468
2673
 
2469
- \b
2470
- Requires the 'mcp' extra:
2471
- pip install sourcecode[mcp]
2472
-
2473
2674
  \b
2474
2675
  Configure in your MCP client (e.g. Claude Desktop):
2475
2676
  {
@@ -2487,15 +2688,7 @@ def mcp_serve() -> None:
2487
2688
  level=logging.INFO,
2488
2689
  format="[sourcecode-mcp] %(levelname)s %(message)s",
2489
2690
  )
2490
- try:
2491
- from sourcecode.mcp.server import mcp as _mcp
2492
- except ImportError:
2493
- typer.echo(
2494
- "MCP support not available. Install with:\n"
2495
- " pip install sourcecode[mcp]",
2496
- err=True,
2497
- )
2498
- raise typer.Exit(code=1)
2691
+ from sourcecode.mcp.server import mcp as _mcp
2499
2692
 
2500
2693
  log = logging.getLogger(__name__)
2501
2694
  log.info("sourcecode-mcp starting (stdio transport)")
@@ -2513,21 +2706,44 @@ def mcp_serve() -> None:
2513
2706
  @mcp_app.command("init")
2514
2707
  def mcp_init(
2515
2708
  yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
2709
+ target: Optional[str] = typer.Option(
2710
+ None,
2711
+ "--target",
2712
+ "-t",
2713
+ help="Target client: claude-desktop | cursor. Default: auto-detect all.",
2714
+ ),
2516
2715
  ) -> None:
2517
2716
  """Setup MCP integration for Claude Desktop, Cursor, and other clients.
2518
2717
 
2519
2718
  \b
2520
2719
  Detects installed MCP clients, backs up their config files, and safely
2521
2720
  inserts the sourcecode server entry. Fully idempotent — safe to re-run.
2721
+
2722
+ \b
2723
+ Examples:
2724
+ sourcecode mcp init
2725
+ sourcecode mcp init --target claude-desktop
2726
+ sourcecode mcp init --target cursor --yes
2522
2727
  """
2523
- from sourcecode.mcp.onboarding.detector import detect_clients
2728
+ from sourcecode.mcp.onboarding.detector import detect_clients, is_client_running
2524
2729
  from sourcecode.mcp.onboarding.planner import build_install_plan
2525
2730
  from sourcecode.mcp.onboarding import backup, applier
2526
2731
 
2527
2732
  typer.echo("Detecting MCP clients...")
2528
2733
  typer.echo("")
2529
2734
 
2530
- clients = detect_clients()
2735
+ all_clients = detect_clients()
2736
+
2737
+ if target:
2738
+ target_slug = target.lower()
2739
+ clients = [c for c in all_clients if c.slug == target_slug]
2740
+ if not clients:
2741
+ valid = ", ".join(c.slug for c in all_clients)
2742
+ typer.echo(f"Unknown target '{target}'. Valid: {valid}", err=True)
2743
+ raise typer.Exit(code=1)
2744
+ else:
2745
+ clients = all_clients
2746
+
2531
2747
  if not clients:
2532
2748
  typer.echo("No MCP clients found on this system.")
2533
2749
  typer.echo("")
@@ -2603,36 +2819,81 @@ def mcp_init(
2603
2819
  typer.echo("MCP integration active.")
2604
2820
  typer.echo("")
2605
2821
 
2606
- restart_needed = [a.client.name for a in actionable if not a.will_create_file]
2607
- if restart_needed:
2608
- typer.echo(f" Restart {', '.join(restart_needed)} to apply changes.")
2822
+ # Post-write: validate config and warn if client not running
2823
+ for a in actionable:
2824
+ if not is_client_running(a.client):
2825
+ typer.echo(
2826
+ f" ⚠ Config written but {a.client.name} is not running. "
2827
+ f"Start {a.client.name} and run sourcecode mcp status to verify.",
2828
+ err=False,
2829
+ )
2830
+ else:
2831
+ restart_msg = "" if a.will_create_file else f" Restart {a.client.name} to apply."
2832
+ typer.echo(f" ✓ {a.client.name} is running.{restart_msg}")
2833
+
2834
+ typer.echo("")
2609
2835
  typer.echo(" Remove: sourcecode mcp remove")
2610
2836
 
2611
2837
 
2612
2838
  @mcp_app.command("status")
2613
2839
  def mcp_status() -> None:
2614
- """Show MCP integration status for all detected clients."""
2615
- from sourcecode.mcp.onboarding.detector import detect_clients
2840
+ """Show MCP integration status: dependencies, config files, and connectivity."""
2841
+ from sourcecode.mcp.onboarding.detector import detect_clients, is_client_running
2616
2842
  from sourcecode.mcp.onboarding import applier
2617
2843
 
2618
- clients = detect_clients()
2619
- typer.echo("MCP Integration Status")
2844
+ sep = "─" * 46
2845
+
2846
+ typer.echo("MCP Status")
2847
+ typer.echo(sep)
2848
+
2849
+ # Stage 1: Dependencies
2850
+ try:
2851
+ import mcp as _mcp_pkg # noqa: F401
2852
+ typer.echo("Dependencies ✓ installed")
2853
+ except ImportError:
2854
+ typer.echo("Dependencies ✗ missing")
2855
+ typer.echo(" Fix: pip install sourcecode[mcp]")
2620
2856
  typer.echo("")
2621
2857
 
2858
+ clients = detect_clients()
2622
2859
  if not clients:
2623
2860
  typer.echo(" No MCP clients detected on this system.")
2861
+ typer.echo(sep)
2862
+ typer.echo(" Setup: sourcecode mcp init")
2624
2863
  raise typer.Exit(code=0)
2625
2864
 
2865
+ # Stage 2: Config files
2866
+ typer.echo("Config files")
2626
2867
  for client in clients:
2627
2868
  if not client.app_installed:
2628
- typer.echo(f" {client.name:<18} not found")
2869
+ typer.echo(f" {client.name:<20} not found")
2870
+ typer.echo(f" Expected: {client.config_path}")
2871
+ typer.echo(f" Fix: sourcecode mcp init --target {client.slug}")
2629
2872
  continue
2630
2873
  config = applier.read_config(client.config_path)
2631
2874
  if applier.is_installed(config):
2632
- typer.echo(f" {client.name:<18} configured {client.config_path}")
2875
+ typer.echo(f" {client.name:<20} configured {client.config_path}")
2633
2876
  else:
2634
- typer.echo(f" {client.name:<18} not configured")
2877
+ typer.echo(f" {client.name:<20} not configured")
2878
+ typer.echo(f" Fix: sourcecode mcp init --target {client.slug}")
2635
2879
  typer.echo("")
2880
+
2881
+ # Stage 3: Connectivity
2882
+ typer.echo("Connectivity")
2883
+ any_installed = any(c.app_installed for c in clients)
2884
+ if not any_installed:
2885
+ typer.echo(" (no clients to check)")
2886
+ else:
2887
+ for client in clients:
2888
+ if not client.app_installed:
2889
+ continue
2890
+ if is_client_running(client):
2891
+ typer.echo(f" {client.name:<20} ✓ running")
2892
+ else:
2893
+ typer.echo(f" {client.name:<20} ✗ not running")
2894
+ typer.echo(f" Fix: open {client.name}, then run sourcecode mcp status")
2895
+
2896
+ typer.echo(sep)
2636
2897
  typer.echo(" Setup: sourcecode mcp init")
2637
2898
  typer.echo(" Remove: sourcecode mcp remove")
2638
2899
 
@@ -175,7 +175,7 @@ class ConfidenceAnalyzer:
175
175
  if dep_summary is None or not dep_summary.requested:
176
176
  gaps.append(AnalysisGap(
177
177
  area="dependencies",
178
- reason="Dependencies not analyzed — run with --dependencies for full context",
178
+ reason="Dependencies not analyzed — use the full analyze command with dependency flags for complete context",
179
179
  impact="medium",
180
180
  ))
181
181
  elif dep_summary.requested and dep_summary.total_count == 0:
@@ -0,0 +1,104 @@
1
+ """Detect MCP-capable clients installed on the current machine."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List
10
+
11
+
12
+ _CLIENT_REGISTRY: List[Dict[str, Any]] = [
13
+ {
14
+ "name": "Claude Desktop",
15
+ "slug": "claude-desktop",
16
+ "paths": {
17
+ "darwin": "~/Library/Application Support/Claude/claude_desktop_config.json",
18
+ "linux": "~/.config/Claude/claude_desktop_config.json",
19
+ "win32": "{APPDATA}/Claude/claude_desktop_config.json",
20
+ },
21
+ "process": {
22
+ "darwin": "Claude",
23
+ "linux": "claude-desktop",
24
+ "win32": "Claude",
25
+ },
26
+ },
27
+ {
28
+ "name": "Cursor",
29
+ "slug": "cursor",
30
+ "paths": {
31
+ "darwin": "~/.cursor/mcp.json",
32
+ "linux": "~/.cursor/mcp.json",
33
+ "win32": "{USERPROFILE}/.cursor/mcp.json",
34
+ },
35
+ "process": {
36
+ "darwin": "Cursor",
37
+ "linux": "cursor",
38
+ "win32": "Cursor",
39
+ },
40
+ },
41
+ ]
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class MCPClient:
46
+ name: str
47
+ config_path: Path
48
+ app_installed: bool # True if the config file (or its parent dir) exists
49
+ process_name: str # OS process name for connectivity check
50
+ slug: str # --target identifier (e.g. "claude-desktop")
51
+
52
+
53
+ def _resolve(template: str) -> Path:
54
+ """Expand env vars in Windows-style {VAR} templates, then expanduser."""
55
+ result = template
56
+ for var in ("APPDATA", "LOCALAPPDATA", "USERPROFILE"):
57
+ val = os.environ.get(var, "")
58
+ if val:
59
+ result = result.replace(f"{{{var}}}", val)
60
+ return Path(result).expanduser()
61
+
62
+
63
+ def detect_clients() -> list[MCPClient]:
64
+ """Return all known MCP clients with their resolved config paths."""
65
+ plat = sys.platform
66
+ clients: list[MCPClient] = []
67
+ for entry in _CLIENT_REGISTRY:
68
+ paths: Dict[str, str] = entry["paths"]
69
+ processes: Dict[str, str] = entry["process"]
70
+ template = paths.get(plat) or paths.get("linux", "")
71
+ if not template:
72
+ continue
73
+ config_path = _resolve(template)
74
+ app_installed = config_path.exists() or config_path.parent.exists()
75
+ process_name = processes.get(plat) or processes.get("linux", "")
76
+ clients.append(MCPClient(
77
+ name=entry["name"],
78
+ config_path=config_path,
79
+ app_installed=app_installed,
80
+ process_name=process_name,
81
+ slug=entry["slug"],
82
+ ))
83
+ return clients
84
+
85
+
86
+ def is_client_running(client: MCPClient) -> bool:
87
+ """True if the client process is currently running."""
88
+ if not client.process_name:
89
+ return False
90
+ try:
91
+ if sys.platform == "win32":
92
+ result = subprocess.run(
93
+ ["tasklist", "/fi", f"imagename eq {client.process_name}.exe"],
94
+ capture_output=True, text=True, timeout=5,
95
+ )
96
+ return client.process_name.lower() in result.stdout.lower()
97
+ else:
98
+ result = subprocess.run(
99
+ ["pgrep", "-x", client.process_name],
100
+ capture_output=True, timeout=5,
101
+ )
102
+ return result.returncode == 0
103
+ except Exception:
104
+ return False
@@ -6,14 +6,18 @@ lookup, no process fork, no stdout encoding issues.
6
6
  """
7
7
  from __future__ import annotations
8
8
 
9
+ import json
10
+ from typing import Any
11
+
9
12
  from typer.testing import CliRunner
10
13
 
11
14
  _runner = CliRunner()
12
15
 
13
16
 
14
- def run_command(args: list[str]) -> str:
15
- """Invoke a sourcecode CLI command in-process and return stdout.
17
+ def run_command(args: list[str]) -> Any:
18
+ """Invoke a sourcecode CLI command in-process and return parsed output.
16
19
 
20
+ Returns parsed JSON dict when output is valid JSON, else the raw string.
17
21
  Raises RuntimeError on non-zero exit or empty output.
18
22
  """
19
23
  from sourcecode.cli import _detected_path, _preprocess_args, app
@@ -37,4 +41,7 @@ def run_command(args: list[str]) -> str:
37
41
  f"Args: {args}"
38
42
  )
39
43
 
40
- return output
44
+ try:
45
+ return json.loads(output)
46
+ except json.JSONDecodeError:
47
+ return output