slopstopper-cli 0.3.0__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 (67) hide show
  1. slopstopper_cli-0.3.0/.gitignore +28 -0
  2. slopstopper_cli-0.3.0/LICENSE +21 -0
  3. slopstopper_cli-0.3.0/PKG-INFO +123 -0
  4. slopstopper_cli-0.3.0/README.md +89 -0
  5. slopstopper_cli-0.3.0/Taskfile.yml +218 -0
  6. slopstopper_cli-0.3.0/pyproject.toml +72 -0
  7. slopstopper_cli-0.3.0/slopstopper/__init__.py +1 -0
  8. slopstopper_cli-0.3.0/slopstopper/__main__.py +4 -0
  9. slopstopper_cli-0.3.0/slopstopper/checks/__init__.py +41 -0
  10. slopstopper_cli-0.3.0/slopstopper/checks/accessibility.py +133 -0
  11. slopstopper_cli-0.3.0/slopstopper/checks/broken_links.py +128 -0
  12. slopstopper_cli-0.3.0/slopstopper/checks/complexity.py +237 -0
  13. slopstopper_cli-0.3.0/slopstopper/checks/csp_exceptions.py +329 -0
  14. slopstopper_cli-0.3.0/slopstopper/checks/cwv.py +269 -0
  15. slopstopper_cli-0.3.0/slopstopper/checks/dast.py +331 -0
  16. slopstopper_cli-0.3.0/slopstopper/checks/dependencies.py +195 -0
  17. slopstopper_cli-0.3.0/slopstopper/checks/docs_accuracy.py +298 -0
  18. slopstopper_cli-0.3.0/slopstopper/checks/docs_size.py +245 -0
  19. slopstopper_cli-0.3.0/slopstopper/checks/docs_structure.py +258 -0
  20. slopstopper_cli-0.3.0/slopstopper/checks/entry_files.py +190 -0
  21. slopstopper_cli-0.3.0/slopstopper/checks/sast.py +227 -0
  22. slopstopper_cli-0.3.0/slopstopper/checks/secrets.py +148 -0
  23. slopstopper_cli-0.3.0/slopstopper/checks/seo.py +515 -0
  24. slopstopper_cli-0.3.0/slopstopper/checks/smoke.py +101 -0
  25. slopstopper_cli-0.3.0/slopstopper/cli.py +670 -0
  26. slopstopper_cli-0.3.0/slopstopper/config.py +193 -0
  27. slopstopper_cli-0.3.0/slopstopper/dast_gate.py +257 -0
  28. slopstopper_cli-0.3.0/slopstopper/data/lighthouserc.json +20 -0
  29. slopstopper_cli-0.3.0/slopstopper/data/lighthouserc.prod.json +24 -0
  30. slopstopper_cli-0.3.0/slopstopper/data/playwright.config.js +37 -0
  31. slopstopper_cli-0.3.0/slopstopper/data/server.js +208 -0
  32. slopstopper_cli-0.3.0/slopstopper/data/tests/accessibility.spec.ts +87 -0
  33. slopstopper_cli-0.3.0/slopstopper/data/tests/broken-links.spec.ts +54 -0
  34. slopstopper_cli-0.3.0/slopstopper/data/tests/smoke.spec.ts +77 -0
  35. slopstopper_cli-0.3.0/slopstopper/discovery.py +366 -0
  36. slopstopper_cli-0.3.0/slopstopper/emit.py +258 -0
  37. slopstopper_cli-0.3.0/slopstopper/headers_adapters/__init__.py +54 -0
  38. slopstopper_cli-0.3.0/slopstopper/headers_adapters/cloudflare_adapter.py +55 -0
  39. slopstopper_cli-0.3.0/slopstopper/headers_adapters/json_adapter.py +29 -0
  40. slopstopper_cli-0.3.0/slopstopper/output.py +127 -0
  41. slopstopper_cli-0.3.0/slopstopper/templates.py +134 -0
  42. slopstopper_cli-0.3.0/tests/__init__.py +0 -0
  43. slopstopper_cli-0.3.0/tests/checks/__init__.py +0 -0
  44. slopstopper_cli-0.3.0/tests/checks/test_accessibility.py +187 -0
  45. slopstopper_cli-0.3.0/tests/checks/test_broken_links.py +180 -0
  46. slopstopper_cli-0.3.0/tests/checks/test_complexity.py +154 -0
  47. slopstopper_cli-0.3.0/tests/checks/test_csp_exceptions.py +226 -0
  48. slopstopper_cli-0.3.0/tests/checks/test_cwv.py +358 -0
  49. slopstopper_cli-0.3.0/tests/checks/test_dast.py +242 -0
  50. slopstopper_cli-0.3.0/tests/checks/test_dependencies.py +202 -0
  51. slopstopper_cli-0.3.0/tests/checks/test_docs_accuracy.py +179 -0
  52. slopstopper_cli-0.3.0/tests/checks/test_docs_size.py +186 -0
  53. slopstopper_cli-0.3.0/tests/checks/test_docs_structure.py +180 -0
  54. slopstopper_cli-0.3.0/tests/checks/test_entry_files.py +150 -0
  55. slopstopper_cli-0.3.0/tests/checks/test_sast.py +200 -0
  56. slopstopper_cli-0.3.0/tests/checks/test_secrets.py +135 -0
  57. slopstopper_cli-0.3.0/tests/checks/test_seo.py +399 -0
  58. slopstopper_cli-0.3.0/tests/checks/test_smoke.py +166 -0
  59. slopstopper_cli-0.3.0/tests/conftest.py +57 -0
  60. slopstopper_cli-0.3.0/tests/test_cli.py +454 -0
  61. slopstopper_cli-0.3.0/tests/test_config.py +50 -0
  62. slopstopper_cli-0.3.0/tests/test_dast_gate.py +194 -0
  63. slopstopper_cli-0.3.0/tests/test_discovery.py +200 -0
  64. slopstopper_cli-0.3.0/tests/test_emit.py +282 -0
  65. slopstopper_cli-0.3.0/tests/test_headers_adapters.py +110 -0
  66. slopstopper_cli-0.3.0/tests/test_output.py +144 -0
  67. slopstopper_cli-0.3.0/tests/test_templates.py +199 -0
@@ -0,0 +1,28 @@
1
+ node_modules/
2
+ playwright-report/
3
+ test-results/
4
+ .ss/playwright-report/
5
+ .ss/test-results/
6
+ # SlopStopper-generated reports (all under .ss/reports/<category>/)
7
+ .ss/reports/
8
+ # Python bytecode under .ss/scripts/. .workflows-installed is deliberately
9
+ # NOT ignored — it's the install marker that tracks adopter deletions
10
+ # across re-runs and must be tracked.
11
+ .ss/scripts/**/__pycache__/
12
+ .ss/tests/**/__pycache__/
13
+ # CLI package build artefacts (in-tree CLI pivot — see cli/README.md)
14
+ cli/**/__pycache__/
15
+ cli/*.egg-info/
16
+ cli/build/
17
+ cli/dist/
18
+ cli/.venv/
19
+ cli/.pytest_cache/
20
+ cli/.task/
21
+ # Local Netlify folder
22
+ .netlify
23
+ # TypeScript compiled output (copy.js is hand-written runtime JS, not a tsc artefact)
24
+ app/*.js
25
+ !app/copy.js
26
+ # Local Claude Code state — but ship shared skills with the repo
27
+ .claude/*
28
+ !.claude/skills/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Richard Griffiths
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,123 @@
1
+ Metadata-Version: 2.4
2
+ Name: slopstopper-cli
3
+ Version: 0.3.0
4
+ Summary: Portable code-quality suite — security, hygiene, reliability.
5
+ Project-URL: Homepage, https://slopstopper.dev
6
+ Project-URL: Source, https://github.com/hungovercoders/slopstopper
7
+ Project-URL: Issues, https://github.com/hungovercoders/slopstopper/issues
8
+ Project-URL: Documentation, https://slopstopper.dev
9
+ Project-URL: Changelog, https://github.com/hungovercoders/slopstopper/blob/main/CHANGELOG.md
10
+ Project-URL: Acknowledgements, https://github.com/hungovercoders/slopstopper/blob/main/ATTRIBUTIONS.md
11
+ Author: Richard Griffiths
12
+ Maintainer-email: Richard Griffiths <griff182uk@googlemail.com>
13
+ License-Expression: MIT
14
+ License-File: LICENSE
15
+ Keywords: accessibility,checks,ci,lighthouse,linting,playwright,quality,security,slopstopper
16
+ Classifier: Development Status :: 4 - Beta
17
+ Classifier: Environment :: Console
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Operating System :: MacOS
21
+ Classifier: Operating System :: POSIX
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Security
26
+ Classifier: Topic :: Software Development :: Quality Assurance
27
+ Classifier: Topic :: Software Development :: Testing
28
+ Classifier: Topic :: System :: Software Distribution
29
+ Requires-Python: >=3.11
30
+ Provides-Extra: test
31
+ Requires-Dist: lizard>=1.17; extra == 'test'
32
+ Requires-Dist: pytest>=7; extra == 'test'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # slopstopper-cli
36
+
37
+ The SlopStopper quality suite, packaged as a `pipx`-installable CLI.
38
+
39
+ > **Status: Beta.** Every check in the [public catalogue](https://slopstopper.dev/features.html) runs through this package. The CI workflows under `.github/workflows/ss-*.yml` install `slopstopper-cli` and call `slopstopper run <category>:<check>` — same code path you use locally.
40
+
41
+ ## Install
42
+
43
+ End-user (most adopters):
44
+
45
+ ```bash
46
+ pipx install slopstopper-cli
47
+ slopstopper --version
48
+ slopstopper checks list
49
+ slopstopper doctor
50
+ ```
51
+
52
+ > Published to PyPI on every release tag. `pipx upgrade slopstopper-cli` pulls the latest. Each release is also attached to the [latest GitHub Release](https://github.com/hungovercoders/slopstopper/releases/latest) with a Sigstore build-provenance attestation — verify with `gh attestation verify <wheel> --owner hungovercoders`.
53
+
54
+ Development (editable, from a clone of this repo):
55
+
56
+ ```bash
57
+ pip install -e ./cli
58
+ slopstopper run hygiene:docs-size
59
+ ```
60
+
61
+ ## CLI surface
62
+
63
+ ```text
64
+ slopstopper run <category>:<check> # run a check, write reports under .ss/reports/
65
+ slopstopper emit <c>:<n> --target {pr-comment,issue}
66
+ slopstopper discover <check> --event <e> # resolve pages.<check> from sitemap / changed / list
67
+ slopstopper config get <key> [<default>] # read .slopstopper.yml
68
+ slopstopper templates {list, path <n>, eject <n>} # inspect / customise bundled templates
69
+ slopstopper serve # bundled static server (auto-detects worker/headers.json)
70
+ slopstopper checks list [--category <c>] [--json]
71
+ slopstopper doctor # verify external tools are installed
72
+ slopstopper --quiet … # suppress decorative output (CI logs)
73
+ ```
74
+
75
+ `slopstopper` with no args prints a banner of the commands above plus a quick-start block.
76
+
77
+ ## Run the tests
78
+
79
+ ```bash
80
+ # From the repo root:
81
+ task -t cli/Taskfile.yml test
82
+
83
+ # Or from cli/:
84
+ task test
85
+
86
+ # Filter to a single test:
87
+ task -t cli/Taskfile.yml test -- -k docs_size
88
+ ```
89
+
90
+ The same `task test` target is what CI runs (see [`.github/workflows/ci-cli.yml`](../.github/workflows/ci-cli.yml)), so local and CI invocations stay in sync. The Taskfile creates and reuses a project-local venv at `cli/.venv/` (gitignored) so it doesn't conflict with system Python under PEP 668.
91
+
92
+ This workflow is slopstopper-internal — it is **not** part of the distributed `ss-*-check.yml` suite and is never seeded into adopter repos.
93
+
94
+ ## Layout
95
+
96
+ - `slopstopper/cli.py` — argparse dispatcher + bare-invocation banner
97
+ - `slopstopper/output.py` — shared formatters (running / success / warn / error / footer / `--quiet`)
98
+ - `slopstopper/config.py` — `.slopstopper.yml` reader (stdlib-only YAML subset)
99
+ - `slopstopper/templates.py` — bundled-template resolver + `templates {list, path, eject}` API
100
+ - `slopstopper/emit.py` — `gh` CLI wrapper for PR comment + main-branch issue emission
101
+ - `slopstopper/discovery.py` — pages-to-audit resolver for reliability checks
102
+ - `slopstopper/checks/` — one module per check; registry in `__init__.py`
103
+ - `slopstopper/data/` — bundled Playwright specs, lighthouserc dev/prod, server.js
104
+
105
+ ## Adding a check
106
+
107
+ 1. Add `slopstopper/checks/<name>.py` exposing `run(args) -> int`. Start the module docstring with a one-line summary (`slopstopper checks list` reads it).
108
+ 2. Register it in `slopstopper/checks/__init__.py`'s `REGISTRY` dict.
109
+ 3. Write the report to `.ss/reports/<category>/<name>-report.md` in the CWD.
110
+ 4. Use `from slopstopper import output` for any user-facing print calls so `--quiet` and the consistent visual language come for free.
111
+ 5. If the check should be postable to a PR or issue, declare a `META` dict in the module — `emit.py` reads it.
112
+
113
+ ## Skills for agents
114
+
115
+ The trio under [`.claude/skills/slopstopper-{install,update,triage}/SKILL.md`](../.claude/skills/) is the long-form playbook for Claude Code agents working with this CLI. Update them when you add or rename a check, env var, or `task ss:*` target.
116
+
117
+ ## Acknowledgements
118
+
119
+ slopstopper-cli ships with no third-party Python dependencies — every check invokes its tool via `subprocess` only. Full credit, licences and upstream links for every tool we drive live in [`ATTRIBUTIONS.md`](../ATTRIBUTIONS.md).
120
+
121
+ ## License
122
+
123
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,89 @@
1
+ # slopstopper-cli
2
+
3
+ The SlopStopper quality suite, packaged as a `pipx`-installable CLI.
4
+
5
+ > **Status: Beta.** Every check in the [public catalogue](https://slopstopper.dev/features.html) runs through this package. The CI workflows under `.github/workflows/ss-*.yml` install `slopstopper-cli` and call `slopstopper run <category>:<check>` — same code path you use locally.
6
+
7
+ ## Install
8
+
9
+ End-user (most adopters):
10
+
11
+ ```bash
12
+ pipx install slopstopper-cli
13
+ slopstopper --version
14
+ slopstopper checks list
15
+ slopstopper doctor
16
+ ```
17
+
18
+ > Published to PyPI on every release tag. `pipx upgrade slopstopper-cli` pulls the latest. Each release is also attached to the [latest GitHub Release](https://github.com/hungovercoders/slopstopper/releases/latest) with a Sigstore build-provenance attestation — verify with `gh attestation verify <wheel> --owner hungovercoders`.
19
+
20
+ Development (editable, from a clone of this repo):
21
+
22
+ ```bash
23
+ pip install -e ./cli
24
+ slopstopper run hygiene:docs-size
25
+ ```
26
+
27
+ ## CLI surface
28
+
29
+ ```text
30
+ slopstopper run <category>:<check> # run a check, write reports under .ss/reports/
31
+ slopstopper emit <c>:<n> --target {pr-comment,issue}
32
+ slopstopper discover <check> --event <e> # resolve pages.<check> from sitemap / changed / list
33
+ slopstopper config get <key> [<default>] # read .slopstopper.yml
34
+ slopstopper templates {list, path <n>, eject <n>} # inspect / customise bundled templates
35
+ slopstopper serve # bundled static server (auto-detects worker/headers.json)
36
+ slopstopper checks list [--category <c>] [--json]
37
+ slopstopper doctor # verify external tools are installed
38
+ slopstopper --quiet … # suppress decorative output (CI logs)
39
+ ```
40
+
41
+ `slopstopper` with no args prints a banner of the commands above plus a quick-start block.
42
+
43
+ ## Run the tests
44
+
45
+ ```bash
46
+ # From the repo root:
47
+ task -t cli/Taskfile.yml test
48
+
49
+ # Or from cli/:
50
+ task test
51
+
52
+ # Filter to a single test:
53
+ task -t cli/Taskfile.yml test -- -k docs_size
54
+ ```
55
+
56
+ The same `task test` target is what CI runs (see [`.github/workflows/ci-cli.yml`](../.github/workflows/ci-cli.yml)), so local and CI invocations stay in sync. The Taskfile creates and reuses a project-local venv at `cli/.venv/` (gitignored) so it doesn't conflict with system Python under PEP 668.
57
+
58
+ This workflow is slopstopper-internal — it is **not** part of the distributed `ss-*-check.yml` suite and is never seeded into adopter repos.
59
+
60
+ ## Layout
61
+
62
+ - `slopstopper/cli.py` — argparse dispatcher + bare-invocation banner
63
+ - `slopstopper/output.py` — shared formatters (running / success / warn / error / footer / `--quiet`)
64
+ - `slopstopper/config.py` — `.slopstopper.yml` reader (stdlib-only YAML subset)
65
+ - `slopstopper/templates.py` — bundled-template resolver + `templates {list, path, eject}` API
66
+ - `slopstopper/emit.py` — `gh` CLI wrapper for PR comment + main-branch issue emission
67
+ - `slopstopper/discovery.py` — pages-to-audit resolver for reliability checks
68
+ - `slopstopper/checks/` — one module per check; registry in `__init__.py`
69
+ - `slopstopper/data/` — bundled Playwright specs, lighthouserc dev/prod, server.js
70
+
71
+ ## Adding a check
72
+
73
+ 1. Add `slopstopper/checks/<name>.py` exposing `run(args) -> int`. Start the module docstring with a one-line summary (`slopstopper checks list` reads it).
74
+ 2. Register it in `slopstopper/checks/__init__.py`'s `REGISTRY` dict.
75
+ 3. Write the report to `.ss/reports/<category>/<name>-report.md` in the CWD.
76
+ 4. Use `from slopstopper import output` for any user-facing print calls so `--quiet` and the consistent visual language come for free.
77
+ 5. If the check should be postable to a PR or issue, declare a `META` dict in the module — `emit.py` reads it.
78
+
79
+ ## Skills for agents
80
+
81
+ The trio under [`.claude/skills/slopstopper-{install,update,triage}/SKILL.md`](../.claude/skills/) is the long-form playbook for Claude Code agents working with this CLI. Update them when you add or rename a check, env var, or `task ss:*` target.
82
+
83
+ ## Acknowledgements
84
+
85
+ slopstopper-cli ships with no third-party Python dependencies — every check invokes its tool via `subprocess` only. Full credit, licences and upstream links for every tool we drive live in [`ATTRIBUTIONS.md`](../ATTRIBUTIONS.md).
86
+
87
+ ## License
88
+
89
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,218 @@
1
+ version: '3'
2
+
3
+ # Tasks for the slopstopper-cli Python package.
4
+ # Internal — this Taskfile is NOT part of the distributed slopstopper
5
+ # suite and is not seeded into adopter repos. CI (`.github/workflows/ci-cli.yml`)
6
+ # and local development must both call the targets here so versions and
7
+ # tooling stay in sync across contexts.
8
+ #
9
+ # Invoke from anywhere in the repo:
10
+ #
11
+ # task -t cli/Taskfile.yml test
12
+ # task -t cli/Taskfile.yml test -- -k docs_size # filter
13
+ #
14
+ # or from cli/:
15
+ #
16
+ # task test
17
+
18
+ tasks:
19
+ install:
20
+ desc: Create cli/.venv and install slopstopper-cli with test extras
21
+ cmds:
22
+ - test -d .venv || python3 -m venv .venv
23
+ - .venv/bin/pip install --quiet --upgrade pip
24
+ - .venv/bin/pip install --quiet -e '.[test]'
25
+ sources:
26
+ - pyproject.toml
27
+ generates:
28
+ - .venv/pyvenv.cfg
29
+
30
+ test:
31
+ desc: Run the pytest suite
32
+ deps: [install]
33
+ cmds:
34
+ - .venv/bin/pytest tests/ {{.CLI_ARGS}}
35
+
36
+ parity:
37
+ desc: Verify CLI reports are byte-identical to the bash scripts they replace
38
+ summary: |
39
+ For each check that has been ported to the CLI, runs both the
40
+ legacy bash/python implementation under .ss/scripts/ and the CLI
41
+ port, then diffs their reports (excluding the timestamp line).
42
+ Any divergence fails CI — this is the load-bearing proof that
43
+ the CLI hasn't drifted from the bash scripts it will eventually
44
+ replace.
45
+
46
+ As more checks land, add a new section to the script below.
47
+ When a check is fully cut over (workflow flipped, bash script
48
+ deleted), remove its section.
49
+ deps: [install]
50
+ dir: '{{.TASKFILE_DIR}}/..'
51
+ cmds:
52
+ - |
53
+ set -e
54
+ TMP=$(mktemp -d)
55
+ trap 'rm -rf "$TMP"' EXIT
56
+ # Strip timestamp lines from both .md reports and .json reports so
57
+ # parity isn't broken by clock drift between bash and CLI runs.
58
+ # Patterns observed across checks:
59
+ # **Generated:** YYYY-MM-DD ... (docs-size, entry-files, docs-structure, docs-accuracy)
60
+ # **Generated**: YYYY-MM-DD ... (complexity — asterisks before colon)
61
+ # "generated_at": "...", (every JSON report)
62
+ STRIP='^\*\*Generated|"generated_at"'
63
+ FAILED=0
64
+
65
+ # check_parity NAME BASH_CMD CLI_CMD REPORT_PATH [REPORT_PATH...]
66
+ # Each REPORT_PATH names a single file the check owns. Both
67
+ # implementations must write to the same paths; we diff every
68
+ # named file (timestamp-stripped). Explicit paths avoid
69
+ # accidentally diffing co-located reports owned by other checks.
70
+ check_parity() {
71
+ local name="$1"
72
+ local bash_cmd="$2"
73
+ local cli_cmd="$3"
74
+ shift 3
75
+ local report_paths=("$@")
76
+
77
+ echo ""
78
+ echo "=== ${name} ==="
79
+
80
+ # Bash side. Exit code may legitimately be non-zero (some checks
81
+ # gate CI on violations); the report diff is what we're judging.
82
+ eval "$bash_cmd" > /dev/null 2>&1 || true
83
+ local i=0
84
+ for report_path in "${report_paths[@]}"; do
85
+ cp "$report_path" "$TMP/${name}-bash-${i}" 2>/dev/null || true
86
+ i=$((i + 1))
87
+ done
88
+
89
+ # CLI side.
90
+ eval "$cli_cmd" > /dev/null 2>&1 || true
91
+ i=0
92
+ for report_path in "${report_paths[@]}"; do
93
+ cp "$report_path" "$TMP/${name}-cli-${i}" 2>/dev/null || true
94
+ i=$((i + 1))
95
+ done
96
+
97
+ local check_failed=0
98
+ i=0
99
+ for report_path in "${report_paths[@]}"; do
100
+ local label="$(basename "$report_path")"
101
+ local bash_file="$TMP/${name}-bash-${i}"
102
+ local cli_file="$TMP/${name}-cli-${i}"
103
+ i=$((i + 1))
104
+ if [ ! -f "$bash_file" ]; then
105
+ echo " ❌ ${label}: bash did not produce this report"
106
+ check_failed=1
107
+ continue
108
+ fi
109
+ if [ ! -f "$cli_file" ]; then
110
+ echo " ❌ ${label}: CLI did not produce this report"
111
+ check_failed=1
112
+ continue
113
+ fi
114
+ grep -vE "$STRIP" "$bash_file" > "$TMP/${name}-${i}.bash-stripped" || true
115
+ grep -vE "$STRIP" "$cli_file" > "$TMP/${name}-${i}.cli-stripped" || true
116
+ if diff -u "$TMP/${name}-${i}.bash-stripped" "$TMP/${name}-${i}.cli-stripped"; then
117
+ echo " ✅ ${label}"
118
+ else
119
+ echo " ❌ ${label}"
120
+ check_failed=1
121
+ fi
122
+ done
123
+
124
+ if [ "$check_failed" -eq 0 ]; then
125
+ echo "✅ ${name} parity OK"
126
+ else
127
+ echo "❌ ${name} parity FAILED"
128
+ FAILED=1
129
+ fi
130
+ }
131
+
132
+ # Hygiene parity rows were removed when the hygiene category's
133
+ # bash scripts were deleted — `.ss/scripts/check-docs-size.sh`,
134
+ # `check-entry-file-size.py`, `check-docs-structure.py` (+
135
+ # `generate-docs-structure-md.py`), `check-docs-accuracy.py` (+
136
+ # `generate-docs-accuracy-md.py`), `generate-complexity-md.py`,
137
+ # and `check-csp-exceptions.py` (+ `headers_adapters/`) no
138
+ # longer exist. The hygiene checks now run only via the CLI;
139
+ # see `cli/tests/checks/test_*.py` for the per-check coverage.
140
+
141
+ # Security parity rows were removed when the security category's
142
+ # bash scripts were deleted — `generate-secrets-md.py`,
143
+ # `generate-sast-md.py`, `generate-dependencies-md.py`,
144
+ # `generate-dast-md.py`, and `check-dast-alerts.py` no longer
145
+ # exist. The security checks now run only via the CLI; see
146
+ # `cli/tests/checks/test_*.py` (+ `tests/test_dast_gate.py`
147
+ # for the lifted DAST gate) for the per-check coverage.
148
+
149
+ # security:dast — intentionally NOT parity-tested. ZAP baseline
150
+ # scans actively probe a live server and the resulting alerts
151
+ # are non-deterministic between runs (probe ordering, instance
152
+ # counts, even alert presence for low-confidence rules), so a
153
+ # byte-diff between two consecutive scans would fail even at
154
+ # 100% logical parity. Each scan is also ~1m17s of Docker time
155
+ # — running both back-to-back costs more than the signal is
156
+ # worth. The MD generation logic in
157
+ # cli/slopstopper/checks/dast.py is a straight lift of
158
+ # .ss/scripts/generate-dast-md.py and is exhaustively unit-
159
+ # tested in cli/tests/checks/test_dast.py. Trust the unit tests
160
+ # for the build_md_report layer; trust the bash workflow for
161
+ # the live ZAP behaviour.
162
+
163
+ # reliability:smoke — intentionally NOT parity-tested. Playwright
164
+ # smoke runs hit a live URL and emit playwright's own HTML/list
165
+ # reporter output, not .ss/reports/*-report.{md,json} files we
166
+ # can diff. The CLI port is a thin subprocess wrapper around
167
+ # `npx playwright test`: args parsing, env construction and
168
+ # command building are exhaustively unit-tested in
169
+ # cli/tests/checks/test_smoke.py (18 tests). Trust the unit
170
+ # tests for the launch envelope; trust the bash workflow for
171
+ # the live playwright behaviour.
172
+
173
+ # reliability:cwv — intentionally NOT parity-tested. Lighthouse
174
+ # CI hits a live URL and emits its own HTML/JSON artifacts
175
+ # alongside thresholding output; nothing in .ss/reports/ to
176
+ # diff. CWV scores are also non-deterministic (TBT, LCP vary
177
+ # with network and CPU jitter on the runner). The CLI port is
178
+ # a thin subprocess wrapper around `npx lhci autorun`;
179
+ # args/url/config plumbing is unit-tested in
180
+ # cli/tests/checks/test_cwv.py (13 tests).
181
+
182
+ # reliability:accessibility — intentionally NOT parity-tested.
183
+ # Same shape as reliability:smoke (Playwright + axe-core,
184
+ # results in .ss/playwright-report/, no .ss/reports/*-report.*
185
+ # to diff; results can flicker on first paint timing). Port
186
+ # plumbs --url, ACCESSIBILITY_TEST_URL / SMOKE_TEST_URL
187
+ # fallback, and ACCESSIBILITY_PAGES via discover-pages.py
188
+ # subprocess. Args / env / discover-pages plumbing is unit-
189
+ # tested in cli/tests/checks/test_accessibility.py (23 tests).
190
+
191
+ # reliability:broken-links — intentionally NOT parity-tested.
192
+ # Same shape as reliability:accessibility (Playwright spec
193
+ # against a live URL). Live HEAD probes are network-dependent;
194
+ # results vary with target uptime, redirects, third-party
195
+ # outbound link availability. Port plumbs --url,
196
+ # BROKEN_LINKS_TEST_URL / SMOKE_TEST_URL fallback, and
197
+ # BROKEN_LINKS_PAGES via discover-pages.py subprocess. Args
198
+ # / env / discover-pages plumbing is unit-tested in
199
+ # cli/tests/checks/test_broken_links.py (22 tests).
200
+
201
+ # reliability:seo — intentionally NOT parity-tested. The CLI
202
+ # subprocess-invokes .ss/scripts/check-seo-metatags.py, which
203
+ # writes diffable .ss/reports/seo/*.{md,json} reports — but
204
+ # the underlying audit hits live URLs and HEAD-checks
205
+ # og:image hosts, so two back-to-back runs can disagree on
206
+ # transient network state. Args / env / discover-pages
207
+ # plumbing is unit-tested in cli/tests/checks/test_seo.py
208
+ # (18 tests). When the SEO script is lifted into the CLI
209
+ # package (next phase), this can become a true parity-eligible
210
+ # check if we standardise the URL fixture.
211
+
212
+ echo ""
213
+ if [ "$FAILED" -eq 0 ]; then
214
+ echo "All parity checks passed."
215
+ else
216
+ echo "One or more parity checks failed — see diff(s) above."
217
+ exit 1
218
+ fi
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "slopstopper-cli"
7
+ version = "0.3.0"
8
+ description = "Portable code-quality suite — security, hygiene, reliability."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Richard Griffiths" }]
14
+ maintainers = [{ name = "Richard Griffiths", email = "griff182uk@googlemail.com" }]
15
+ keywords = [
16
+ "ci",
17
+ "quality",
18
+ "checks",
19
+ "linting",
20
+ "security",
21
+ "accessibility",
22
+ "lighthouse",
23
+ "playwright",
24
+ "slopstopper",
25
+ ]
26
+ classifiers = [
27
+ "Development Status :: 4 - Beta",
28
+ "License :: OSI Approved :: MIT License",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Programming Language :: Python :: 3.13",
32
+ "Topic :: Software Development :: Quality Assurance",
33
+ "Topic :: Software Development :: Testing",
34
+ "Topic :: Security",
35
+ "Topic :: System :: Software Distribution",
36
+ "Environment :: Console",
37
+ "Intended Audience :: Developers",
38
+ "Operating System :: POSIX",
39
+ "Operating System :: MacOS",
40
+ ]
41
+ dependencies = []
42
+
43
+ [project.optional-dependencies]
44
+ test = [
45
+ "pytest>=7",
46
+ # `lizard` is invoked at runtime by hygiene:complexity via subprocess
47
+ # (`python -m lizard`). Not a runtime dep of slopstopper-cli itself —
48
+ # adopters install it themselves in production. Pulled into the test
49
+ # extra so CI parity + integration tests can exercise the real tool.
50
+ "lizard>=1.17",
51
+ ]
52
+
53
+ [project.scripts]
54
+ slopstopper = "slopstopper.cli:main"
55
+
56
+ [project.urls]
57
+ Homepage = "https://slopstopper.dev"
58
+ Source = "https://github.com/hungovercoders/slopstopper"
59
+ Issues = "https://github.com/hungovercoders/slopstopper/issues"
60
+ Documentation = "https://slopstopper.dev"
61
+ Changelog = "https://github.com/hungovercoders/slopstopper/blob/main/CHANGELOG.md"
62
+ Acknowledgements = "https://github.com/hungovercoders/slopstopper/blob/main/ATTRIBUTIONS.md"
63
+
64
+ [tool.hatch.build.targets.wheel]
65
+ # `packages = ["slopstopper"]` includes the slopstopper/ tree (Python +
66
+ # data files) automatically — no `force-include` needed; that block
67
+ # caused a double-include error when building the wheel under hatchling.
68
+ packages = ["slopstopper"]
69
+
70
+ [tool.pytest.ini_options]
71
+ testpaths = ["tests"]
72
+ addopts = "-q"
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -0,0 +1,4 @@
1
+ from slopstopper.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,41 @@
1
+ """Check registry. Maps `<category>:<name>` keys to check entrypoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Callable, Optional
6
+
7
+ from slopstopper.checks import (
8
+ accessibility,
9
+ broken_links,
10
+ complexity,
11
+ csp_exceptions,
12
+ cwv,
13
+ dast,
14
+ dependencies,
15
+ docs_accuracy,
16
+ docs_size,
17
+ docs_structure,
18
+ entry_files,
19
+ sast,
20
+ secrets,
21
+ seo,
22
+ smoke,
23
+ )
24
+
25
+ REGISTRY: dict[str, Callable[[Optional[list[str]]], int]] = {
26
+ "hygiene:complexity": complexity.run,
27
+ "hygiene:csp-exceptions": csp_exceptions.run,
28
+ "hygiene:docs-accuracy": docs_accuracy.run,
29
+ "hygiene:docs-size": docs_size.run,
30
+ "hygiene:docs-structure": docs_structure.run,
31
+ "hygiene:entry-files": entry_files.run,
32
+ "reliability:accessibility": accessibility.run,
33
+ "reliability:broken-links": broken_links.run,
34
+ "reliability:cwv": cwv.run,
35
+ "reliability:seo": seo.run,
36
+ "reliability:smoke": smoke.run,
37
+ "security:dast": dast.run,
38
+ "security:dependencies": dependencies.run,
39
+ "security:sast": sast.run,
40
+ "security:secrets": secrets.run,
41
+ }