slopmop 2.3.0__tar.gz → 2.3.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 (191) hide show
  1. {slopmop-2.3.0/slopmop.egg-info → slopmop-2.3.2}/PKG-INFO +2 -2
  2. {slopmop-2.3.0 → slopmop-2.3.2}/README.md +1 -1
  3. {slopmop-2.3.0 → slopmop-2.3.2}/pyproject.toml +2 -0
  4. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/_version.py +1 -1
  5. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/registry.py +5 -0
  6. slopmop-2.3.2/slopmop/agent_install/templates/gemini/.gemini/skills/slopmop/SKILL.md +15 -0
  7. slopmop-2.3.2/slopmop/agent_install/templates/roo/.roo/rules/01-slopmop.md +1 -0
  8. slopmop-2.3.2/slopmop/checks/python/_host_formatter.py +74 -0
  9. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/lint_format.py +180 -27
  10. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/type_checking.py +57 -1
  11. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/audit.py +6 -1
  12. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/buff.py +98 -28
  13. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/buff_narration.py +22 -8
  14. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/buff_scan.py +20 -0
  15. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/doctor.py +6 -2
  16. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/init.py +11 -8
  17. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/parser_builders.py +1 -1
  18. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/refit.py +9 -0
  19. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/sail.py +24 -1
  20. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/status.py +4 -2
  21. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/validate.py +14 -3
  22. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/sm.py +3 -2
  23. slopmop-2.3.2/slopmop/utils/environment.py +35 -0
  24. {slopmop-2.3.0 → slopmop-2.3.2/slopmop.egg-info}/PKG-INFO +2 -2
  25. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop.egg-info/SOURCES.txt +4 -0
  26. {slopmop-2.3.0 → slopmop-2.3.2}/LICENSE +0 -0
  27. {slopmop-2.3.0 → slopmop-2.3.2}/MANIFEST.in +0 -0
  28. {slopmop-2.3.0 → slopmop-2.3.2}/NOTICE +0 -0
  29. {slopmop-2.3.0 → slopmop-2.3.2}/setup.cfg +0 -0
  30. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/__init__.py +0 -0
  31. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/__main__.py +0 -0
  32. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/__init__.py +0 -0
  33. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/installer.py +0 -0
  34. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/loader.py +0 -0
  35. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/_shared/core.md +0 -0
  36. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/aider/.aider.conf.yml +0 -0
  37. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/aider/CONVENTIONS.md +0 -0
  38. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/antigravity/.agents/rules/slopmop.md +0 -0
  39. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-barnacle.md +0 -0
  40. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-buff.md +0 -0
  41. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-init.md +0 -0
  42. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-refit.md +0 -0
  43. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-sail.md +0 -0
  44. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-scour.md +0 -0
  45. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-swab.md +0 -0
  46. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/skills/slopmop/SKILL.md +0 -0
  47. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/CLAUDE.md +0 -0
  48. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cline/.clinerules/slopmop.md +0 -0
  49. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/copilot/.copilot/skills/slopmop/SKILL.md +0 -0
  50. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/copilot/.github/copilot-instructions.md +0 -0
  51. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-barnacle.mdc +0 -0
  52. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-buff.mdc +0 -0
  53. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-init.mdc +0 -0
  54. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-sail.mdc +0 -0
  55. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-scour.mdc +0 -0
  56. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-swab.mdc +0 -0
  57. /slopmop-2.3.0/slopmop/agent_install/templates/roo/.roo/rules/01-slopmop.md → /slopmop-2.3.2/slopmop/agent_install/templates/gemini/GEMINI.md +0 -0
  58. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/windsurf/.windsurf/rules/slopmop.md +0 -0
  59. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/baseline.py +0 -0
  60. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/__init__.py +0 -0
  61. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/base.py +0 -0
  62. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/constants.py +0 -0
  63. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/custom.py +0 -0
  64. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/__init__.py +0 -0
  65. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/analyze.py +0 -0
  66. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/bogus_tests.py +0 -0
  67. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/common.py +0 -0
  68. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/coverage.py +0 -0
  69. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/format.py +0 -0
  70. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/generated_artifacts.py +0 -0
  71. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/tests.py +0 -0
  72. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/general/__init__.py +0 -0
  73. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/general/interactive_assumptions.py +0 -0
  74. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/general/jinja2_templates.py +0 -0
  75. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/__init__.py +0 -0
  76. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/bogus_tests.py +0 -0
  77. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/coverage.py +0 -0
  78. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/dead_code.py +0 -0
  79. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/eslint_expect.py +0 -0
  80. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/eslint_quick.py +0 -0
  81. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/lint_format.py +0 -0
  82. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/tests.py +0 -0
  83. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/types.py +0 -0
  84. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/metadata.py +0 -0
  85. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/mixins.py +0 -0
  86. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/pr/__init__.py +0 -0
  87. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/pr/comments.py +0 -0
  88. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/__init__.py +0 -0
  89. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/coverage.py +0 -0
  90. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/static_analysis.py +0 -0
  91. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/tests.py +0 -0
  92. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/__init__.py +0 -0
  93. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/ambiguity_mines.py +0 -0
  94. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/bogus_tests.py +0 -0
  95. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/complexity.py +0 -0
  96. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/config_debt.py +0 -0
  97. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/dead_code.py +0 -0
  98. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/debugger_artifacts.py +0 -0
  99. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/duplicate_strings.py +0 -0
  100. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/duplication.py +0 -0
  101. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/gate_dodging.py +0 -0
  102. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/loc_lock.py +0 -0
  103. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/security/__init__.py +0 -0
  104. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/security/_detect_secrets.py +0 -0
  105. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/workflow/__init__.py +0 -0
  106. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/workflow/github_actions.py +0 -0
  107. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/__init__.py +0 -0
  108. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_buff_status.py +0 -0
  109. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_refit_formatting.py +0 -0
  110. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_refit_iterate_cmd.py +0 -0
  111. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_refit_iteration.py +0 -0
  112. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_refit_precheck.py +0 -0
  113. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_refit_skip.py +0 -0
  114. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/agent.py +0 -0
  115. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/barnacle.py +0 -0
  116. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/buff_common.py +0 -0
  117. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/capabilities.py +0 -0
  118. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/captain.py +0 -0
  119. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/ci.py +0 -0
  120. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/config.py +0 -0
  121. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/detection.py +0 -0
  122. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/gang.py +0 -0
  123. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/help.py +0 -0
  124. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/hooks.py +0 -0
  125. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/scan_triage.py +0 -0
  126. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/schema.py +0 -0
  127. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/upgrade.py +0 -0
  128. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/constants.py +0 -0
  129. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/__init__.py +0 -0
  130. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/cache.py +0 -0
  131. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/config.py +0 -0
  132. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/executor.py +0 -0
  133. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/lock.py +0 -0
  134. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/registry.py +0 -0
  135. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/result.py +0 -0
  136. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/data/__init__.py +0 -0
  137. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/data/command_mapping.py +0 -0
  138. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/data/git_wrapper.sh +0 -0
  139. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/__init__.py +0 -0
  140. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/base.py +0 -0
  141. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/gate_preflight.py +0 -0
  142. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/project_env.py +0 -0
  143. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/runtime.py +0 -0
  144. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/sm_env.py +0 -0
  145. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/state.py +0 -0
  146. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/exceptions.py +0 -0
  147. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/migrations/__init__.py +0 -0
  148. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/__init__.py +0 -0
  149. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/adapters.py +0 -0
  150. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/console.py +0 -0
  151. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/__init__.py +0 -0
  152. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/colors.py +0 -0
  153. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/config.py +0 -0
  154. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/dynamic.py +0 -0
  155. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/renderer.py +0 -0
  156. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/state.py +0 -0
  157. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/dynamic.py +0 -0
  158. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/envelope.py +0 -0
  159. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/rail.py +0 -0
  160. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/report.py +0 -0
  161. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/sarif.py +0 -0
  162. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/timings.py +0 -0
  163. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/__init__.py +0 -0
  164. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/__init__.py +0 -0
  165. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/audit.json +0 -0
  166. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/barnacle.json +0 -0
  167. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/buff.json +0 -0
  168. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/capabilities.json +0 -0
  169. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/doctor.json +0 -0
  170. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/refit.json +0 -0
  171. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/scour.json +0 -0
  172. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/status.json +0 -0
  173. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/swab.json +0 -0
  174. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/wake-angry-drunk-captain.json +0 -0
  175. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/envelope.json +0 -0
  176. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/subprocess/__init__.py +0 -0
  177. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/subprocess/runner.py +0 -0
  178. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/subprocess/validator.py +0 -0
  179. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/utils/__init__.py +0 -0
  180. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/utils/gate_reasoning_docs.py +0 -0
  181. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/utils/generate_base_config.py +0 -0
  182. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/utils/jsonc.py +0 -0
  183. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/utils/readme_tables.py +0 -0
  184. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/workflow/__init__.py +0 -0
  185. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/workflow/hooks.py +0 -0
  186. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/workflow/state_machine.py +0 -0
  187. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/workflow/state_store.py +0 -0
  188. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop.egg-info/dependency_links.txt +0 -0
  189. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop.egg-info/entry_points.txt +0 -0
  190. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop.egg-info/requires.txt +0 -0
  191. {slopmop-2.3.0 → slopmop-2.3.2}/slopmop.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slopmop
3
- Version: 2.3.0
3
+ Version: 2.3.2
4
4
  Summary: Quality gates for AI-assisted codebases — catch the slop LLMs leave behind.
5
5
  Author: ScienceIsNeato
6
6
  License: Apache-2.0
@@ -102,7 +102,7 @@ It is purposefully opinionated, as structure begets adherence to best practices.
102
102
 
103
103
  ## Project Status
104
104
 
105
- slop-mop is at version 2.3.0. The current public policy surface for release and
105
+ slop-mop is at version 2.3.2. The current public policy surface for release and
106
106
  stability expectations lives here:
107
107
 
108
108
  - [DOCS/COMPATIBILITY.md](https://github.com/ScienceIsNeato/slop-mop/blob/main/DOCS/COMPATIBILITY.md)
@@ -27,7 +27,7 @@ It is purposefully opinionated, as structure begets adherence to best practices.
27
27
 
28
28
  ## Project Status
29
29
 
30
- slop-mop is at version 2.3.0. The current public policy surface for release and
30
+ slop-mop is at version 2.3.2. The current public policy surface for release and
31
31
  stability expectations lives here:
32
32
 
33
33
  - [DOCS/COMPATIBILITY.md](https://github.com/ScienceIsNeato/slop-mop/blob/main/DOCS/COMPATIBILITY.md)
@@ -134,6 +134,8 @@ exclude = ["tests*"]
134
134
  "templates/copilot/.copilot/skills/slopmop/*",
135
135
  "templates/copilot/.github/*",
136
136
  "templates/cursor/.cursor/rules/*",
137
+ "templates/gemini/.gemini/skills/slopmop/*",
138
+ "templates/gemini/GEMINI.md",
137
139
  "templates/roo/.roo/rules/*",
138
140
  "templates/windsurf/.windsurf/rules/*",
139
141
  ]
@@ -11,4 +11,4 @@ automatically. Everything else derives from it:
11
11
  from this value, so a mismatch can never be merged.
12
12
  """
13
13
 
14
- __version__ = "2.3.0"
14
+ __version__ = "2.3.2"
@@ -57,6 +57,11 @@ TARGETS: Dict[str, InstallTarget] = {
57
57
  display_name="Google Antigravity workspace rules",
58
58
  template_dir="antigravity",
59
59
  ),
60
+ "gemini": InstallTarget(
61
+ key="gemini",
62
+ display_name="Gemini CLI instructions + skills",
63
+ template_dir="gemini",
64
+ ),
60
65
  }
61
66
 
62
67
  ALL_KEYS: Tuple[str, ...] = tuple(TARGETS.keys())
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: slopmop
3
+ description: >-
4
+ Substitutes pytest, gh, mypy, black, and every other tool in this
5
+ repo with a single `sm` CLI. Never run the underlying tools
6
+ directly — `sm swab` for fast iteration, `sm scour` before a PR,
7
+ `sm buff` after CI or review feedback, `sm doctor` when the
8
+ environment looks broken, `sm refit` for onboarding existing repos,
9
+ `sm sail` when unsure what to do next.
10
+ Use `sm barnacle file` (never `gh issue create`) when slop-mop
11
+ itself causes tool friction — the barnacle CLI auto-applies labels
12
+ and targets the right repo.
13
+ ---
14
+
15
+ {{CORE}}
@@ -0,0 +1,74 @@
1
+ """Detect the Python formatter already configured by the host project.
2
+
3
+ Used by sloppy-formatting.py to defer to the project's own formatter
4
+ instead of imposing slop-mop's defaults (autoflake + black + isort).
5
+
6
+ When a project already pins ruff (or black), slop-mop running a different
7
+ formatter produces churn: the gate-fix commit ends up in a style that the
8
+ host's CI then immediately undoes. Detecting the host formatter and using
9
+ it keeps the output in sync with what CI expects.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ from pathlib import Path
16
+ from typing import Optional
17
+
18
+
19
+ def detect_host_python_formatter(project_root: str) -> Optional[str]:
20
+ """Return the formatter the project has configured, or None.
21
+
22
+ Return values:
23
+ 'ruff' — project configured ruff; use ``ruff format`` + ``ruff check``
24
+ 'black' — project configured black explicitly (no ruff)
25
+ None — no host formatter detected; caller should use defaults
26
+
27
+ Detection priority:
28
+ 1. pyproject.toml [tool.ruff.*] → 'ruff'
29
+ 2. .ruff.toml or ruff.toml presence → 'ruff'
30
+ 3. .pre-commit-config.yaml containing a ruff hook → 'ruff'
31
+ 4. pyproject.toml [tool.black] (no ruff) → 'black'
32
+ 5. Nothing found → None
33
+ """
34
+ root = Path(project_root)
35
+ pyproject_has_black = False
36
+
37
+ # 1. pyproject.toml
38
+ pyproject = root / "pyproject.toml"
39
+ if pyproject.exists():
40
+ try:
41
+ content = pyproject.read_text(encoding="utf-8")
42
+ # Matches [tool.ruff], [tool.ruff.format], [tool.ruff.lint], etc.
43
+ if re.search(r"^\s*\[tool\.ruff[.\]]", content, re.MULTILINE):
44
+ return "ruff"
45
+ # Matches [tool.black] or [tool.black.*] — defer the return so that
46
+ # a standalone ruff config or pre-commit ruff hook can still win
47
+ # (e.g. a project that migrated from black to ruff but kept the
48
+ # legacy [tool.black] section for history).
49
+ if re.search(r"^\s*\[tool\.black[.\]]", content, re.MULTILINE):
50
+ pyproject_has_black = True
51
+ except OSError:
52
+ pass # Unreadable pyproject — fall through
53
+
54
+ # 2. Standalone ruff config files (checked before honouring pyproject black)
55
+ if (root / ".ruff.toml").exists() or (root / "ruff.toml").exists():
56
+ return "ruff"
57
+
58
+ # 3. .pre-commit-config.yaml with a ruff hook
59
+ precommit = root / ".pre-commit-config.yaml"
60
+ if precommit.exists():
61
+ try:
62
+ content = precommit.read_text(encoding="utf-8")
63
+ # Match `- id: ruff` or `- id: ruff-format` (actual hook IDs),
64
+ # not URLs or comments that merely mention "ruff".
65
+ if re.search(r"^\s*-\s+id:\s+ruff", content, re.MULTILINE):
66
+ return "ruff"
67
+ except OSError:
68
+ pass
69
+
70
+ # 4. Fall back to black if we saw it in pyproject but no ruff signal won
71
+ if pyproject_has_black:
72
+ return "black"
73
+
74
+ return None
@@ -1,10 +1,15 @@
1
1
  """Python lint and format check using black, isort, autoflake, and flake8.
2
2
 
3
3
  This check:
4
- 1. Auto-removes unused imports with autoflake
5
- 2. Auto-fixes formatting with black
6
- 3. Auto-fixes import order with isort
4
+ 1. Auto-removes unused imports with autoflake (or ruff when configured)
5
+ 2. Auto-fixes formatting with black (or ruff when configured)
6
+ 3. Auto-fixes import order with isort (or ruff when configured)
7
7
  4. Checks for critical lint errors with flake8
8
+
9
+ When the project already pins ruff (detected via pyproject.toml, ruff.toml,
10
+ or .pre-commit-config.yaml), the gate defers to ruff format + ruff check
11
+ instead of running black/isort/autoflake. This prevents formatting churn
12
+ when the host's CI would immediately reformat slop-mop's output.
8
13
  """
9
14
 
10
15
  import os
@@ -21,7 +26,9 @@ from slopmop.checks.base import (
21
26
  RemediationChurn,
22
27
  ToolContext,
23
28
  )
29
+ from slopmop.checks.constants import COMMAND_NOT_FOUND
24
30
  from slopmop.checks.mixins import PythonCheckMixin
31
+ from slopmop.checks.python._host_formatter import detect_host_python_formatter
25
32
  from slopmop.constants import ISSUES_FOUND_TEMPLATE
26
33
  from slopmop.core.result import CheckResult, CheckStatus, Finding, FindingLevel
27
34
 
@@ -33,6 +40,7 @@ _FLAKE8_RE = re.compile(r"^(.+?):(\d+):(\d+): (\w+) (.+)$")
33
40
  # a string (real formatting failure) so that run() can report the
34
41
  # skip without treating it as a pass.
35
42
  _BLACK_SKIPPED = "__BLACK_SKIPPED_BROKEN_INSTALL__"
43
+ _RUFF_SKIPPED = "__RUFF_SKIPPED_NOT_INSTALLED__"
36
44
 
37
45
  _DEFAULT_EXCLUDE_DIRS = [
38
46
  "venv",
@@ -51,6 +59,13 @@ _DEFAULT_EXCLUDE_DIRS = [
51
59
  "ephemeral",
52
60
  ]
53
61
 
62
+ # Black --extend-exclude regex built from _DEFAULT_EXCLUDE_DIRS so that
63
+ # recursive runs on a top-level package (e.g. "enterprise") don't descend
64
+ # into nested migration dirs like enterprise/migrations/versions/. (#263)
65
+ _BLACK_EXTEND_EXCLUDE = (
66
+ r"/(" + "|".join(re.escape(d) for d in _DEFAULT_EXCLUDE_DIRS) + r")/"
67
+ )
68
+
54
69
 
55
70
  def _is_import_error(output: str) -> bool:
56
71
  """True when output looks like a Python import/module-not-found error.
@@ -93,7 +108,7 @@ class PythonLintFormatCheck(BaseCheck, PythonCheckMixin):
93
108
  """
94
109
 
95
110
  tool_context = ToolContext.SM_TOOL
96
- required_tools = ["black", "isort", "autoflake", "flake8"]
111
+ required_tools = ["black", "isort", "autoflake", "flake8", "ruff"]
97
112
  role = CheckRole.FOUNDATION
98
113
  remediation_churn = RemediationChurn.DOWNSTREAM_CHANGES_VERY_UNLIKELY
99
114
  is_formatting_gate = True
@@ -128,8 +143,36 @@ class PythonLintFormatCheck(BaseCheck, PythonCheckMixin):
128
143
  description="Maximum line length for black",
129
144
  permissiveness="lower_is_stricter",
130
145
  ),
146
+ ConfigField(
147
+ name="formatter",
148
+ field_type="string",
149
+ default="auto",
150
+ choices=["auto", "ruff", "black", "none"],
151
+ description=(
152
+ "Python formatter to use. Options: 'auto' (detect from project "
153
+ "config — default), 'ruff' (always use ruff format + ruff check), "
154
+ "'black' (always use autoflake + black + isort), 'none' (skip "
155
+ "formatting entirely — flake8 syntax checks still run). "
156
+ "Auto-detection checks pyproject.toml, .ruff.toml, and "
157
+ ".pre-commit-config.yaml."
158
+ ),
159
+ ),
131
160
  ]
132
161
 
162
+ def _effective_formatter(self, project_root: str) -> Optional[str]:
163
+ """Return 'ruff', 'black', or None (use black defaults).
164
+
165
+ Respects an explicit 'formatter' config override; falls back to
166
+ auto-detection via the project's own config files.
167
+ """
168
+ override = self.config.get("formatter")
169
+ if override == "none":
170
+ return "none"
171
+ if override in ("ruff", "black"):
172
+ return override
173
+ # 'auto' or unset: detect from project
174
+ return detect_host_python_formatter(project_root)
175
+
133
176
  def is_applicable(self, project_root: str) -> bool:
134
177
  return self.is_python_project(project_root)
135
178
 
@@ -137,7 +180,46 @@ class PythonLintFormatCheck(BaseCheck, PythonCheckMixin):
137
180
  return True
138
181
 
139
182
  def auto_fix(self, project_root: str) -> bool:
140
- """Auto-fix formatting issues with autoflake, black, and isort."""
183
+ """Auto-fix formatting issues.
184
+
185
+ Defers to the project's own formatter when one is configured
186
+ (ruff), falling back to autoflake + black + isort otherwise.
187
+ """
188
+ formatter = self._effective_formatter(project_root)
189
+ if formatter == "none":
190
+ return False
191
+ if formatter == "ruff":
192
+ return self._auto_fix_ruff(project_root)
193
+ return self._auto_fix_black(project_root)
194
+
195
+ def _auto_fix_ruff(self, project_root: str) -> bool:
196
+ """Format with ruff — defers to the project's own ruff config."""
197
+ fixed = False
198
+
199
+ # ruff format replaces black
200
+ result = self._run_command(
201
+ ["ruff", "format", "."],
202
+ cwd=project_root,
203
+ timeout=60,
204
+ )
205
+ if result.success:
206
+ fixed = True
207
+
208
+ # ruff check --fix --select I replaces isort; F401 replaces autoflake.
209
+ # --select overrides pyproject config for this invocation so we touch
210
+ # only style (not logic-altering rules).
211
+ result = self._run_command(
212
+ ["ruff", "check", "--fix", "--select", "I,F401", "."],
213
+ cwd=project_root,
214
+ timeout=60,
215
+ )
216
+ if result.success:
217
+ fixed = True
218
+
219
+ return fixed
220
+
221
+ def _auto_fix_black(self, project_root: str) -> bool:
222
+ """Format with autoflake + black + isort (slop-mop defaults)."""
141
223
  fixed = False
142
224
 
143
225
  # Find Python source directories to format
@@ -161,13 +243,16 @@ class PythonLintFormatCheck(BaseCheck, PythonCheckMixin):
161
243
  if result.success:
162
244
  fixed = True
163
245
 
164
- # Run black on each target
246
+ # Run black on each target. --extend-exclude prevents recursive
247
+ # descent into nested migration/alembic dirs (#263).
165
248
  for target in targets:
166
249
  result = self._run_command(
167
250
  [
168
251
  "black",
169
252
  "--line-length",
170
253
  "88",
254
+ "--extend-exclude",
255
+ _BLACK_EXTEND_EXCLUDE,
171
256
  target,
172
257
  ],
173
258
  cwd=project_root,
@@ -207,30 +292,63 @@ class PythonLintFormatCheck(BaseCheck, PythonCheckMixin):
207
292
  return targets
208
293
 
209
294
  def run(self, project_root: str) -> CheckResult:
210
- """Run lint and format checks."""
295
+ """Run lint and format checks.
296
+
297
+ Uses ruff when the project has it configured; black/isort otherwise.
298
+ Flake8 runs in both cases for critical syntax/undefined-name errors.
299
+ """
211
300
  start_time = time.time()
301
+ formatter = self._effective_formatter(project_root)
212
302
  issues: List[str] = []
213
303
  output_parts: List[str] = []
214
304
 
215
- # Check 1: Black formatting
216
- black_result = self._check_black(project_root)
217
- if black_result == _BLACK_SKIPPED:
218
- output_parts.append("Black: ⚠️ Skipped (broken installation)")
219
- elif black_result:
220
- issues.append(black_result)
221
- output_parts.append(f"Black: {black_result}")
305
+ if formatter == "none":
306
+ output_parts.append("Formatting skipped (formatter: none)")
307
+ fix_hint = "Run: flake8 --select=E9,F63,F7,F82,F401 . to check syntax"
308
+ elif formatter == "ruff":
309
+ # Check 1: ruff format
310
+ fmt_result = self._check_ruff_format(project_root)
311
+ if fmt_result == _RUFF_SKIPPED:
312
+ output_parts.append("Ruff format: ⚠️ Skipped (ruff not installed)")
313
+ elif fmt_result:
314
+ issues.append(fmt_result)
315
+ output_parts.append(f"Ruff format: {fmt_result}")
316
+ else:
317
+ output_parts.append("Ruff format: ✅ Formatting OK")
318
+
319
+ # Check 2: ruff import order
320
+ import_result = self._check_ruff_imports(project_root)
321
+ if import_result == _RUFF_SKIPPED:
322
+ output_parts.append("Ruff imports: ⚠️ Skipped (ruff not installed)")
323
+ elif import_result:
324
+ issues.append(import_result)
325
+ output_parts.append(f"Ruff imports: {import_result}")
326
+ else:
327
+ output_parts.append("Ruff imports: ✅ Import order OK")
328
+
329
+ fix_hint = "Run: ruff format . && ruff check --fix --select I,F401 ."
222
330
  else:
223
- output_parts.append("Black: ✅ Formatting OK")
224
-
225
- # Check 2: Isort imports
226
- isort_result = self._check_isort(project_root)
227
- if isort_result:
228
- issues.append(isort_result)
229
- output_parts.append(f"Isort: {isort_result}")
230
- else:
231
- output_parts.append("Isort: ✅ Import order OK")
232
-
233
- # Check 3: Flake8 critical errors
331
+ # Check 1: Black formatting
332
+ black_result = self._check_black(project_root)
333
+ if black_result == _BLACK_SKIPPED:
334
+ output_parts.append("Black: ⚠️ Skipped (broken installation)")
335
+ elif black_result:
336
+ issues.append(black_result)
337
+ output_parts.append(f"Black: {black_result}")
338
+ else:
339
+ output_parts.append("Black: ✅ Formatting OK")
340
+
341
+ # Check 2: Isort imports
342
+ isort_result = self._check_isort(project_root)
343
+ if isort_result:
344
+ issues.append(isort_result)
345
+ output_parts.append(f"Isort: {isort_result}")
346
+ else:
347
+ output_parts.append("Isort: ✅ Import order OK")
348
+
349
+ fix_hint = "Run: black . && isort . to auto-fix formatting"
350
+
351
+ # Check 3: Flake8 critical errors (always)
234
352
  flake8_result, flake8_findings = self._check_flake8(project_root)
235
353
  if flake8_result:
236
354
  issues.append(flake8_result)
@@ -242,7 +360,6 @@ class PythonLintFormatCheck(BaseCheck, PythonCheckMixin):
242
360
 
243
361
  if issues:
244
362
  msg = ISSUES_FOUND_TEMPLATE.format(count=len(issues))
245
- # Prefer flake8_findings if available; if none, use generic message
246
363
  final_findings = (
247
364
  flake8_findings
248
365
  if flake8_findings
@@ -253,7 +370,7 @@ class PythonLintFormatCheck(BaseCheck, PythonCheckMixin):
253
370
  duration=duration,
254
371
  output="\n".join(output_parts),
255
372
  error=msg,
256
- fix_suggestion="Run: black . && isort . to auto-fix formatting",
373
+ fix_suggestion=fix_hint,
257
374
  findings=final_findings,
258
375
  )
259
376
 
@@ -263,6 +380,40 @@ class PythonLintFormatCheck(BaseCheck, PythonCheckMixin):
263
380
  output="\n".join(output_parts),
264
381
  )
265
382
 
383
+ def _check_ruff_format(self, project_root: str) -> Optional[str]:
384
+ """Check ruff formatting (equivalent of black --check)."""
385
+ result = self._run_command(
386
+ ["ruff", "format", "--check", "."],
387
+ cwd=project_root,
388
+ timeout=60,
389
+ )
390
+ if not result.success:
391
+ output = (result.output or "").strip()
392
+ if COMMAND_NOT_FOUND in output:
393
+ return _RUFF_SKIPPED
394
+ return output or "Ruff format check failed"
395
+ return None
396
+
397
+ def _check_ruff_imports(self, project_root: str) -> Optional[str]:
398
+ """Check import order with ruff (equivalent of isort --check-only)."""
399
+ result = self._run_command(
400
+ ["ruff", "check", "--select", "I", "."],
401
+ cwd=project_root,
402
+ timeout=60,
403
+ )
404
+ if not result.success:
405
+ output = (result.output or "").strip()
406
+ if COMMAND_NOT_FOUND in output:
407
+ return _RUFF_SKIPPED
408
+ if output:
409
+ lines = [line for line in output.splitlines() if line.strip()]
410
+ if len(lines) <= 5:
411
+ return "Import order issues:\n " + "\n ".join(lines)
412
+ shown = "\n ".join(lines[:5])
413
+ return f"Import order issues:\n {shown}\n ... and {len(lines)-5} more"
414
+ return "Import order issues found"
415
+ return None
416
+
266
417
  def _check_black(self, project_root: str) -> Optional[str]:
267
418
  """Check black formatting.
268
419
 
@@ -286,6 +437,8 @@ class PythonLintFormatCheck(BaseCheck, PythonCheckMixin):
286
437
  "--check",
287
438
  "--line-length",
288
439
  "88",
440
+ "--extend-exclude",
441
+ _BLACK_EXTEND_EXCLUDE,
289
442
  target,
290
443
  ],
291
444
  cwd=project_root,
@@ -55,6 +55,41 @@ TYPE_COMPLETENESS_RULES: Dict[str, str] = {
55
55
  MAX_ERRORS_TO_SHOW = 5
56
56
  MAX_FILES_TO_SHOW = 10
57
57
 
58
+ # Excludes injected when include falls back to ["."] (the whole-tree scan).
59
+ # All patterns use "**/" so nested subproject directories are excluded too —
60
+ # a bare "venv" only excludes the root-level dir in pyright. (#262)
61
+ # "env" and ".env" are intentionally omitted: "env" is a legitimate Python
62
+ # package name, and ".env" is nearly always a file rather than a directory.
63
+ _BROAD_SCAN_EXCLUDES: List[str] = [
64
+ "**/__pycache__",
65
+ "**/node_modules",
66
+ "**/venv",
67
+ "**/.venv",
68
+ "**/build",
69
+ "**/dist",
70
+ "**/*.egg-info",
71
+ "**/.eggs",
72
+ "**/.tox",
73
+ "**/.nox",
74
+ ]
75
+
76
+ # Safe minimal excludes applied for every scoped include (won't shadow real packages).
77
+ _MINIMAL_EXCLUDES: List[str] = ["**/__pycache__", "**/node_modules"]
78
+
79
+
80
+ def _fallback_excludes(venv_name: Optional[str]) -> List[str]:
81
+ """Build the exclude list for the whole-tree include:['.'] fallback.
82
+
83
+ Appends the detected venv name (with **/ prefix) when it isn't already
84
+ covered by _BROAD_SCAN_EXCLUDES, e.g. a non-standard name like 'myenv'.
85
+ """
86
+ excludes = list(_BROAD_SCAN_EXCLUDES)
87
+ if venv_name:
88
+ prefixed = f"**/{venv_name}"
89
+ if prefixed not in excludes:
90
+ excludes.append(prefixed)
91
+ return excludes
92
+
58
93
 
59
94
  def _fix_strategy_for_pyright(rule: str, message: str) -> Optional[str]:
60
95
  """Return a prescriptive remediation for common pyright rules."""
@@ -347,14 +382,29 @@ class PythonTypeCheckingCheck(BaseCheck, PythonCheckMixin):
347
382
  config["extends"] = base_config_file
348
383
  if isinstance(explicit_include_dirs, list) and explicit_include_dirs:
349
384
  config["include"] = include_dirs
385
+ # Explicit include:["."] still needs broad excludes — same guard
386
+ # as the auto-detect fallback below. (#262)
387
+ if include_dirs == ["."]:
388
+ config["exclude"] = _fallback_excludes(venv_name)
350
389
  elif not explicitly_configured and "include" not in base_cfg:
351
390
  # Auto-detected pyrightconfig.json that doesn't scope includes:
352
391
  # keep source-dir scoping so the gate doesn't balloon to the
353
392
  # whole tree. (An explicitly configured base is trusted as-is.)
354
393
  config["include"] = include_dirs
394
+ # Only inject broad excludes for the whole-tree fallback; a
395
+ # scoped include already limits the scan and broad excludes
396
+ # could silently shadow a real package with the same name. (#262)
397
+ if include_dirs == ["."]:
398
+ config["exclude"] = _fallback_excludes(venv_name)
355
399
  else:
356
400
  config["include"] = include_dirs
357
- config["exclude"] = ["**/__pycache__", "**/node_modules"]
401
+ if include_dirs == ["."]:
402
+ # Whole-tree fallback: guard venv/build/generated dirs. (#262)
403
+ config["exclude"] = _fallback_excludes(venv_name)
404
+ else:
405
+ # Scoped include: minimal excludes only — broad patterns could
406
+ # shadow a real package whose name happens to match (e.g. "env").
407
+ config["exclude"] = list(_MINIMAL_EXCLUDES)
358
408
  config["pythonVersion"] = python_version
359
409
 
360
410
  if venv_path and venv_name:
@@ -371,6 +421,12 @@ class PythonTypeCheckingCheck(BaseCheck, PythonCheckMixin):
371
421
  if rule not in owned_rules:
372
422
  config[rule] = level
373
423
 
424
+ # Copy any report* rules from base config to the generated config to prevent
425
+ # them from being overridden/reset by typeCheckingMode: standard.
426
+ for rule, level in base_cfg.items():
427
+ if rule.startswith("report") and rule not in config:
428
+ config[rule] = level
429
+
374
430
  return config
375
431
 
376
432
  def run(self, project_root: str) -> CheckResult:
@@ -569,7 +569,12 @@ def cmd_audit(args: argparse.Namespace) -> int:
569
569
  include_gates = not getattr(args, "no_gates", False)
570
570
  output_path_str: Optional[str] = getattr(args, "output", None)
571
571
  json_flag = getattr(args, "json_output", None)
572
- json_mode = json_flag is True or (json_flag is None and not sys.stdout.isatty())
572
+ if json_flag is None:
573
+ from slopmop.utils.environment import is_agent_environment
574
+
575
+ json_mode = not sys.stdout.isatty() or is_agent_environment()
576
+ else:
577
+ json_mode = json_flag
573
578
  quiet = getattr(args, "quiet", False)
574
579
 
575
580
  timestamp = datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")