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.
- slopstopper_cli-0.3.0/.gitignore +28 -0
- slopstopper_cli-0.3.0/LICENSE +21 -0
- slopstopper_cli-0.3.0/PKG-INFO +123 -0
- slopstopper_cli-0.3.0/README.md +89 -0
- slopstopper_cli-0.3.0/Taskfile.yml +218 -0
- slopstopper_cli-0.3.0/pyproject.toml +72 -0
- slopstopper_cli-0.3.0/slopstopper/__init__.py +1 -0
- slopstopper_cli-0.3.0/slopstopper/__main__.py +4 -0
- slopstopper_cli-0.3.0/slopstopper/checks/__init__.py +41 -0
- slopstopper_cli-0.3.0/slopstopper/checks/accessibility.py +133 -0
- slopstopper_cli-0.3.0/slopstopper/checks/broken_links.py +128 -0
- slopstopper_cli-0.3.0/slopstopper/checks/complexity.py +237 -0
- slopstopper_cli-0.3.0/slopstopper/checks/csp_exceptions.py +329 -0
- slopstopper_cli-0.3.0/slopstopper/checks/cwv.py +269 -0
- slopstopper_cli-0.3.0/slopstopper/checks/dast.py +331 -0
- slopstopper_cli-0.3.0/slopstopper/checks/dependencies.py +195 -0
- slopstopper_cli-0.3.0/slopstopper/checks/docs_accuracy.py +298 -0
- slopstopper_cli-0.3.0/slopstopper/checks/docs_size.py +245 -0
- slopstopper_cli-0.3.0/slopstopper/checks/docs_structure.py +258 -0
- slopstopper_cli-0.3.0/slopstopper/checks/entry_files.py +190 -0
- slopstopper_cli-0.3.0/slopstopper/checks/sast.py +227 -0
- slopstopper_cli-0.3.0/slopstopper/checks/secrets.py +148 -0
- slopstopper_cli-0.3.0/slopstopper/checks/seo.py +515 -0
- slopstopper_cli-0.3.0/slopstopper/checks/smoke.py +101 -0
- slopstopper_cli-0.3.0/slopstopper/cli.py +670 -0
- slopstopper_cli-0.3.0/slopstopper/config.py +193 -0
- slopstopper_cli-0.3.0/slopstopper/dast_gate.py +257 -0
- slopstopper_cli-0.3.0/slopstopper/data/lighthouserc.json +20 -0
- slopstopper_cli-0.3.0/slopstopper/data/lighthouserc.prod.json +24 -0
- slopstopper_cli-0.3.0/slopstopper/data/playwright.config.js +37 -0
- slopstopper_cli-0.3.0/slopstopper/data/server.js +208 -0
- slopstopper_cli-0.3.0/slopstopper/data/tests/accessibility.spec.ts +87 -0
- slopstopper_cli-0.3.0/slopstopper/data/tests/broken-links.spec.ts +54 -0
- slopstopper_cli-0.3.0/slopstopper/data/tests/smoke.spec.ts +77 -0
- slopstopper_cli-0.3.0/slopstopper/discovery.py +366 -0
- slopstopper_cli-0.3.0/slopstopper/emit.py +258 -0
- slopstopper_cli-0.3.0/slopstopper/headers_adapters/__init__.py +54 -0
- slopstopper_cli-0.3.0/slopstopper/headers_adapters/cloudflare_adapter.py +55 -0
- slopstopper_cli-0.3.0/slopstopper/headers_adapters/json_adapter.py +29 -0
- slopstopper_cli-0.3.0/slopstopper/output.py +127 -0
- slopstopper_cli-0.3.0/slopstopper/templates.py +134 -0
- slopstopper_cli-0.3.0/tests/__init__.py +0 -0
- slopstopper_cli-0.3.0/tests/checks/__init__.py +0 -0
- slopstopper_cli-0.3.0/tests/checks/test_accessibility.py +187 -0
- slopstopper_cli-0.3.0/tests/checks/test_broken_links.py +180 -0
- slopstopper_cli-0.3.0/tests/checks/test_complexity.py +154 -0
- slopstopper_cli-0.3.0/tests/checks/test_csp_exceptions.py +226 -0
- slopstopper_cli-0.3.0/tests/checks/test_cwv.py +358 -0
- slopstopper_cli-0.3.0/tests/checks/test_dast.py +242 -0
- slopstopper_cli-0.3.0/tests/checks/test_dependencies.py +202 -0
- slopstopper_cli-0.3.0/tests/checks/test_docs_accuracy.py +179 -0
- slopstopper_cli-0.3.0/tests/checks/test_docs_size.py +186 -0
- slopstopper_cli-0.3.0/tests/checks/test_docs_structure.py +180 -0
- slopstopper_cli-0.3.0/tests/checks/test_entry_files.py +150 -0
- slopstopper_cli-0.3.0/tests/checks/test_sast.py +200 -0
- slopstopper_cli-0.3.0/tests/checks/test_secrets.py +135 -0
- slopstopper_cli-0.3.0/tests/checks/test_seo.py +399 -0
- slopstopper_cli-0.3.0/tests/checks/test_smoke.py +166 -0
- slopstopper_cli-0.3.0/tests/conftest.py +57 -0
- slopstopper_cli-0.3.0/tests/test_cli.py +454 -0
- slopstopper_cli-0.3.0/tests/test_config.py +50 -0
- slopstopper_cli-0.3.0/tests/test_dast_gate.py +194 -0
- slopstopper_cli-0.3.0/tests/test_discovery.py +200 -0
- slopstopper_cli-0.3.0/tests/test_emit.py +282 -0
- slopstopper_cli-0.3.0/tests/test_headers_adapters.py +110 -0
- slopstopper_cli-0.3.0/tests/test_output.py +144 -0
- 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,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
|
+
}
|