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.
- {slopmop-2.3.0/slopmop.egg-info → slopmop-2.3.2}/PKG-INFO +2 -2
- {slopmop-2.3.0 → slopmop-2.3.2}/README.md +1 -1
- {slopmop-2.3.0 → slopmop-2.3.2}/pyproject.toml +2 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/_version.py +1 -1
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/registry.py +5 -0
- slopmop-2.3.2/slopmop/agent_install/templates/gemini/.gemini/skills/slopmop/SKILL.md +15 -0
- slopmop-2.3.2/slopmop/agent_install/templates/roo/.roo/rules/01-slopmop.md +1 -0
- slopmop-2.3.2/slopmop/checks/python/_host_formatter.py +74 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/lint_format.py +180 -27
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/type_checking.py +57 -1
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/audit.py +6 -1
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/buff.py +98 -28
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/buff_narration.py +22 -8
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/buff_scan.py +20 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/doctor.py +6 -2
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/init.py +11 -8
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/parser_builders.py +1 -1
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/refit.py +9 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/sail.py +24 -1
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/status.py +4 -2
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/validate.py +14 -3
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/sm.py +3 -2
- slopmop-2.3.2/slopmop/utils/environment.py +35 -0
- {slopmop-2.3.0 → slopmop-2.3.2/slopmop.egg-info}/PKG-INFO +2 -2
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop.egg-info/SOURCES.txt +4 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/LICENSE +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/MANIFEST.in +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/NOTICE +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/setup.cfg +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/__main__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/installer.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/loader.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/_shared/core.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/aider/.aider.conf.yml +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/aider/CONVENTIONS.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/antigravity/.agents/rules/slopmop.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-barnacle.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-buff.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-init.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-refit.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-sail.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-scour.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/commands/sm-swab.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/.claude/skills/slopmop/SKILL.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/claude/CLAUDE.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cline/.clinerules/slopmop.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/copilot/.copilot/skills/slopmop/SKILL.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/copilot/.github/copilot-instructions.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-barnacle.mdc +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-buff.mdc +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-init.mdc +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-sail.mdc +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-scour.mdc +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/cursor/.cursor/rules/slopmop-swab.mdc +0 -0
- /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
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/agent_install/templates/windsurf/.windsurf/rules/slopmop.md +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/baseline.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/base.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/constants.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/custom.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/analyze.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/bogus_tests.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/common.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/coverage.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/format.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/generated_artifacts.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/dart/tests.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/general/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/general/interactive_assumptions.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/general/jinja2_templates.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/bogus_tests.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/coverage.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/dead_code.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/eslint_expect.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/eslint_quick.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/lint_format.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/tests.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/javascript/types.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/metadata.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/mixins.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/pr/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/pr/comments.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/coverage.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/static_analysis.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/python/tests.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/ambiguity_mines.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/bogus_tests.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/complexity.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/config_debt.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/dead_code.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/debugger_artifacts.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/duplicate_strings.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/duplication.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/gate_dodging.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/quality/loc_lock.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/security/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/security/_detect_secrets.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/workflow/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/checks/workflow/github_actions.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_buff_status.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_refit_formatting.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_refit_iterate_cmd.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_refit_iteration.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_refit_precheck.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/_refit_skip.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/agent.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/barnacle.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/buff_common.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/capabilities.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/captain.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/ci.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/config.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/detection.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/gang.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/help.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/hooks.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/scan_triage.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/schema.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/cli/upgrade.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/constants.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/cache.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/config.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/executor.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/lock.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/registry.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/core/result.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/data/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/data/command_mapping.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/data/git_wrapper.sh +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/base.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/gate_preflight.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/project_env.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/runtime.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/sm_env.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/doctor/state.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/exceptions.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/migrations/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/adapters.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/console.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/colors.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/config.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/dynamic.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/renderer.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/display/state.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/dynamic.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/envelope.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/rail.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/report.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/sarif.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/reporting/timings.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/audit.json +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/barnacle.json +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/buff.json +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/capabilities.json +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/doctor.json +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/refit.json +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/scour.json +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/status.json +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/swab.json +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/data/wake-angry-drunk-captain.json +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/schemas/v3/envelope.json +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/subprocess/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/subprocess/runner.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/subprocess/validator.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/utils/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/utils/gate_reasoning_docs.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/utils/generate_base_config.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/utils/jsonc.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/utils/readme_tables.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/workflow/__init__.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/workflow/hooks.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/workflow/state_machine.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop/workflow/state_store.py +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop.egg-info/dependency_links.txt +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop.egg-info/entry_points.txt +0 -0
- {slopmop-2.3.0 → slopmop-2.3.2}/slopmop.egg-info/requires.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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
|
]
|
|
@@ -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 @@
|
|
|
1
|
+
{{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
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
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")
|