gdmutant 0.1.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.
- gdmutant-0.1.0/.gitignore +53 -0
- gdmutant-0.1.0/AGENTS.md +147 -0
- gdmutant-0.1.0/CHANGELOG.md +251 -0
- gdmutant-0.1.0/CODE_OF_CONDUCT.md +166 -0
- gdmutant-0.1.0/CONTRIBUTING.md +99 -0
- gdmutant-0.1.0/LICENSE +21 -0
- gdmutant-0.1.0/PKG-INFO +184 -0
- gdmutant-0.1.0/README.md +158 -0
- gdmutant-0.1.0/corpus/gut_test/test_independent_gut.gd +18 -0
- gdmutant-0.1.0/corpus/gut_test/test_turn_order_gut.gd +25 -0
- gdmutant-0.1.0/corpus/harness/run_tests.gd +73 -0
- gdmutant-0.1.0/corpus/project.godot +12 -0
- gdmutant-0.1.0/corpus/test/test_independent.gd +20 -0
- gdmutant-0.1.0/corpus/test/test_turn_order.gd +23 -0
- gdmutant-0.1.0/corpus/turn_order.gd +32 -0
- gdmutant-0.1.0/docs/credits.md +64 -0
- gdmutant-0.1.0/docs/decisions/0001-write-the-engine-in-python-not-gdscript.md +68 -0
- gdmutant-0.1.0/docs/decisions/0002-mutation-mechanism-source-span-editing.md +43 -0
- gdmutant-0.1.0/docs/decisions/0003-mutation-application-strategy.md +95 -0
- gdmutant-0.1.0/docs/decisions/0004-equivalent-mutant-ignore-annotation.md +63 -0
- gdmutant-0.1.0/docs/decisions/0005-exit-code-test-runner-convention.md +86 -0
- gdmutant-0.1.0/docs/decisions/0006-operator-scoped-ignore-and-ignored-status.md +51 -0
- gdmutant-0.1.0/docs/decisions/0007-statement-deletion-with-a-return-path-guard.md +70 -0
- gdmutant-0.1.0/docs/decisions/0008-method-body-mutation-manual-spotcheck.md +108 -0
- gdmutant-0.1.0/docs/decisions/0009-pre-commit-for-local-dev-checks.md +101 -0
- gdmutant-0.1.0/docs/decisions/0010-pypi-trusted-publishing.md +109 -0
- gdmutant-0.1.0/docs/decisions/0011-runner-agnostic-adapter-seam.md +216 -0
- gdmutant-0.1.0/docs/decisions/0012-merge-time-local-ship-time-cloud.md +133 -0
- gdmutant-0.1.0/docs/decisions/0013-windows-local-mutation-testing.md +135 -0
- gdmutant-0.1.0/docs/design/DESIGN.md +307 -0
- gdmutant-0.1.0/docs/gdmutant-guide.md +482 -0
- gdmutant-0.1.0/docs/mutation-testing.md +152 -0
- gdmutant-0.1.0/docs/releasing.md +264 -0
- gdmutant-0.1.0/docs/survivors/README.md +187 -0
- gdmutant-0.1.0/gdmutant/__init__.py +15 -0
- gdmutant-0.1.0/gdmutant/adapters/__init__.py +3 -0
- gdmutant-0.1.0/gdmutant/adapters/gdscript/__init__.py +505 -0
- gdmutant-0.1.0/gdmutant/adapters/gdscript/runner.py +546 -0
- gdmutant-0.1.0/gdmutant/cli.py +1786 -0
- gdmutant-0.1.0/gdmutant/engine/__init__.py +6 -0
- gdmutant-0.1.0/gdmutant/engine/adapter.py +30 -0
- gdmutant-0.1.0/gdmutant/engine/explain.py +466 -0
- gdmutant-0.1.0/gdmutant/engine/htmlreport.py +1465 -0
- gdmutant-0.1.0/gdmutant/engine/loop.py +931 -0
- gdmutant-0.1.0/gdmutant/engine/mutants.py +100 -0
- gdmutant-0.1.0/gdmutant/engine/operators/__init__.py +140 -0
- gdmutant-0.1.0/gdmutant/engine/report.py +318 -0
- gdmutant-0.1.0/gdmutant/engine/runner.py +217 -0
- gdmutant-0.1.0/gdmutant/engine/spans.py +88 -0
- gdmutant-0.1.0/gdmutant/engine/survivor_reference.py +292 -0
- gdmutant-0.1.0/gdmutant/examples/gdmutant-hello-world.gd +6 -0
- gdmutant-0.1.0/gdmutant/py.typed +0 -0
- gdmutant-0.1.0/pyproject.toml +231 -0
- gdmutant-0.1.0/tests/conftest.py +72 -0
- gdmutant-0.1.0/tests/js/harness.js +511 -0
- gdmutant-0.1.0/tests/test_action_pin.py +86 -0
- gdmutant-0.1.0/tests/test_check_licenses.py +162 -0
- gdmutant-0.1.0/tests/test_check_mutation_baseline_cap.py +105 -0
- gdmutant-0.1.0/tests/test_check_published_package.py +447 -0
- gdmutant-0.1.0/tests/test_check_readme_images.py +402 -0
- gdmutant-0.1.0/tests/test_check_release_tag.py +323 -0
- gdmutant-0.1.0/tests/test_cli.py +3732 -0
- gdmutant-0.1.0/tests/test_docs_frontmatter.py +159 -0
- gdmutant-0.1.0/tests/test_dogfood_gdunit4.py +106 -0
- gdmutant-0.1.0/tests/test_end_to_end.py +52 -0
- gdmutant-0.1.0/tests/test_explain.py +430 -0
- gdmutant-0.1.0/tests/test_flags_are_documented.py +56 -0
- gdmutant-0.1.0/tests/test_gdmutant_guide.py +98 -0
- gdmutant-0.1.0/tests/test_gdscript_adapter.py +669 -0
- gdmutant-0.1.0/tests/test_gdtoolkit_integration.py +57 -0
- gdmutant-0.1.0/tests/test_gdunit_runner.py +477 -0
- gdmutant-0.1.0/tests/test_gut_runner.py +471 -0
- gdmutant-0.1.0/tests/test_harden_github.py +696 -0
- gdmutant-0.1.0/tests/test_htmlreport.py +672 -0
- gdmutant-0.1.0/tests/test_htmlreport_behaviour.py +546 -0
- gdmutant-0.1.0/tests/test_local_check_parity.py +220 -0
- gdmutant-0.1.0/tests/test_loop.py +1489 -0
- gdmutant-0.1.0/tests/test_mutants.py +103 -0
- gdmutant-0.1.0/tests/test_mutation_baseline_inputs.py +251 -0
- gdmutant-0.1.0/tests/test_no_em_dashes_in_source.py +80 -0
- gdmutant-0.1.0/tests/test_operators.py +148 -0
- gdmutant-0.1.0/tests/test_packaging.py +139 -0
- gdmutant-0.1.0/tests/test_poodle_config.py +105 -0
- gdmutant-0.1.0/tests/test_public_readiness.py +1923 -0
- gdmutant-0.1.0/tests/test_report.py +484 -0
- gdmutant-0.1.0/tests/test_run_gitleaks.py +76 -0
- gdmutant-0.1.0/tests/test_runner.py +215 -0
- gdmutant-0.1.0/tests/test_selftest_live.py +459 -0
- gdmutant-0.1.0/tests/test_smoke.py +48 -0
- gdmutant-0.1.0/tests/test_spans.py +104 -0
- gdmutant-0.1.0/tests/test_step_summary.py +264 -0
- gdmutant-0.1.0/tests/test_survivor_reference.py +128 -0
- gdmutant-0.1.0/uv.lock +1480 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Secrets
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
*.pem
|
|
6
|
+
*.key
|
|
7
|
+
# Python (engine + gdtoolkit adapter are Python-natural)
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.pyc
|
|
10
|
+
.venv/
|
|
11
|
+
.venv-*/
|
|
12
|
+
dist/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
htmlcov/
|
|
19
|
+
.mutmut-cache
|
|
20
|
+
# mutmut v3 writes a generated copy of the project + its result DB here while it runs
|
|
21
|
+
mutants/
|
|
22
|
+
mutmut-results.db
|
|
23
|
+
mutmut-run.log
|
|
24
|
+
# poodle (local mutation testing, see docs/decisions/0013) writes its temp copies here; the
|
|
25
|
+
# trailing * also catches ad-hoc manual-testing dirs like .poodle-temp-manual/
|
|
26
|
+
poodle-run.log
|
|
27
|
+
.poodle-temp*/
|
|
28
|
+
# Godot (test fixtures may include a small Godot project)
|
|
29
|
+
.godot/
|
|
30
|
+
# Godot self-test fixture (tests/test_selftest_live.py): the GdUnit4 addon is downloaded
|
|
31
|
+
# (scripts/install_gdunit4.py), and reports + .uid import artifacts are regenerated by
|
|
32
|
+
# `godot --import` — none of them are committed.
|
|
33
|
+
corpus/addons/
|
|
34
|
+
corpus/reports/
|
|
35
|
+
*.uid
|
|
36
|
+
# Live self-test with --basetemp=.live-tmp (CI reuses the Stryker JSON it writes there)
|
|
37
|
+
.live-tmp/
|
|
38
|
+
# OS/editor
|
|
39
|
+
.DS_Store
|
|
40
|
+
Thumbs.db
|
|
41
|
+
.idea/
|
|
42
|
+
*.swp
|
|
43
|
+
|
|
44
|
+
# Agent scratch output — never commit ad-hoc dumps or agent-local config
|
|
45
|
+
*_dump.txt
|
|
46
|
+
.claude/
|
|
47
|
+
|
|
48
|
+
# The private word list tests/test_public_readiness.py scans for (GDMUTANT_PRIVATE_TERMS).
|
|
49
|
+
# It belongs outside this tree entirely, and committing it would publish the exact list of words
|
|
50
|
+
# the guard exists to keep unpublished. This is the belt to that guard's braces: a copy dropped
|
|
51
|
+
# in here by accident cannot be staged at all. tests/test_public_readiness.py pins that this
|
|
52
|
+
# pattern is here and that git really does honour it.
|
|
53
|
+
*private-terms*
|
gdmutant-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: how-to
|
|
3
|
+
status: active
|
|
4
|
+
created: 2026-07-11
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# gdmutant: contributor & AI-assistant guide
|
|
8
|
+
|
|
9
|
+
A language-agnostic mutation-testing tool for GDScript/Godot. It
|
|
10
|
+
mutates a project's source (flip `>`↔`>=`, `and`↔`or`, bump a number, …), reruns the tests per
|
|
11
|
+
mutant, and reports survivors: lines a bug could live on that no test catches. This is the
|
|
12
|
+
fast-orientation guide for anyone (human or AI) *contributing to* gdmutant's own source. The
|
|
13
|
+
product rationale is in [`README.md`](README.md), and the authoritative design is in
|
|
14
|
+
[`docs/design/DESIGN.md`](docs/design/DESIGN.md). Driving gdmutant as a tool (invoking the CLI,
|
|
15
|
+
not editing its code) is a different job: see
|
|
16
|
+
[`docs/gdmutant-guide.md`](docs/gdmutant-guide.md) instead.
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
gdmutant is a [uv](https://docs.astral.sh/uv/) project. Install uv, then:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
uv sync --frozen # fetches the pinned Python (via .python-version) + locked deps into .venv
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Run commands with `uv run`.
|
|
27
|
+
|
|
28
|
+
The live self-test suites below need a real Godot binary, which `uv sync` does not provide (`uv`
|
|
29
|
+
owns only Python here). `mise.toml` pins the same Godot release CI runs against. Install
|
|
30
|
+
[mise](https://mise.jdx.dev/), then `mise install` fetches it. `mise which godot` prints the path
|
|
31
|
+
to pass as `GDMUTANT_GODOT`. Never add Python to `mise.toml`. That stays `uv`'s alone.
|
|
32
|
+
|
|
33
|
+
## Build · test
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
uv run ruff check . # lint
|
|
37
|
+
uv run ruff format --check . # format
|
|
38
|
+
uv run mypy gdmutant # type check (strict)
|
|
39
|
+
uv run pytest # tests + coverage
|
|
40
|
+
uv run pip-audit # dependency audit
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Two suites are env-gated and auto-skip in a plain `uv run pytest` (so `verify` stays
|
|
44
|
+
Godot-free). Run them when touching the adapter, runners, or CLI file-handling:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
# Live self-test: drive the shipped CLI against a real Godot on the corpus.
|
|
48
|
+
GDMUTANT_GODOT=godot uv run pytest tests/test_selftest_live.py
|
|
49
|
+
# Dogfood harness: run gdmutant against a real GdUnit4 checkout — parse coverage + the
|
|
50
|
+
# whole-directory regression guard (Godot-free, ~5s). Point it at any GdUnit4 clone:
|
|
51
|
+
GDMUTANT_GDUNIT4_CLONE=<path-to-a-gdUnit4-checkout> uv run pytest tests/test_dogfood_gdunit4.py
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Tech stack (decided)
|
|
55
|
+
|
|
56
|
+
- Language: Python 3.12 (pinned via `.python-version`), managed with uv (hash-pinned
|
|
57
|
+
`uv.lock`, with uv itself floored in `pyproject.toml`'s `[tool.uv]`). Rationale for Python over GDScript:
|
|
58
|
+
[`docs/decisions/0001`](docs/decisions/0001-write-the-engine-in-python-not-gdscript.md).
|
|
59
|
+
- Runtime dependency: [`gdtoolkit`](https://github.com/Scony/godot-gdscript-toolkit), the
|
|
60
|
+
GDScript parser the adapter mutates.
|
|
61
|
+
- Test-runner adapters: GdUnit4 and GUT are peer JUnit-XML adapters over one runner contract
|
|
62
|
+
(both run via `godot --headless`, and neither is privileged in the engine), plus the framework-neutral
|
|
63
|
+
exit-code command runner for any harness without JUnit output. See
|
|
64
|
+
[`docs/decisions/0011`](docs/decisions/0011-runner-agnostic-adapter-seam.md).
|
|
65
|
+
- Report format: the `mutation-testing-elements` JSON schema (renders in its HTML viewer).
|
|
66
|
+
|
|
67
|
+
## Conventions
|
|
68
|
+
|
|
69
|
+
- `main` is protected, and new behavior comes with tests: this is a testing tool, so we hold
|
|
70
|
+
ourselves to it.
|
|
71
|
+
- CI gate: `ruff` + `mypy` + `pytest` + `pip-audit`, plus a gitleaks secret scan and zizmor
|
|
72
|
+
(workflow security). GitHub Actions are SHA-pinned (Dependabot bumps them).
|
|
73
|
+
- Windows is a deployment target, not just a dev machine. gdmutant is a cross-platform Python
|
|
74
|
+
CLI people run on Windows, so test on Windows for real, not just Linux. Two concrete traps to
|
|
75
|
+
watch for: console output can crash under the legacy `cp1252` code page, and `python3` can resolve
|
|
76
|
+
to a *different* interpreter than `python` (see `.pre-commit-config.yaml`'s header for the guard).
|
|
77
|
+
- `ci.yml` runs automatically on every pull request and push to `main` (restored 2026-08-04, ahead
|
|
78
|
+
of going public — why it was ever manual-only, and why it's back:
|
|
79
|
+
[ADR-0012](docs/decisions/0012-merge-time-local-ship-time-cloud.md)). The unbypassable gate is
|
|
80
|
+
still `publish.yml`'s release-time run of `verify` on both Linux and Windows, before every real
|
|
81
|
+
release. Run the same checks locally any time:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
uv run python scripts/verify_local.py
|
|
85
|
+
uv run python scripts/verify_local.py --job license-check # or any other ci.yml job
|
|
86
|
+
uv run python scripts/verify_local.py --list # show a job's commands without running them
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This script reads its commands out of `ci.yml` rather than restating them, so local, CI, and the
|
|
90
|
+
release gate can't drift apart.
|
|
91
|
+
|
|
92
|
+
If every tool fails with `uv trampoline failed to canonicalize script path`, the venv's
|
|
93
|
+
launcher shims are stale, usually after a `uv` version bump. Fix: `uv sync --frozen --reinstall`.
|
|
94
|
+
- Keep the engine language-neutral: no GDScript-specific assumptions in `gdmutant/engine/`.
|
|
95
|
+
Language specifics live only in `gdmutant/adapters/<lang>/`.
|
|
96
|
+
- The mutation-operator core is deterministic, the reproducible mode a CI check can trust. Any
|
|
97
|
+
future LLM-semantic mode stays out of it.
|
|
98
|
+
- **Recurring bug one: a gate that passes without checking anything.** Seen five times. A test that
|
|
99
|
+
skipped when `git` was missing. A mutation hook that ran zero mutants. A license check over an
|
|
100
|
+
empty package list. A script that exited 0 after all its writes failed. A mutation run scored
|
|
101
|
+
against an already-broken baseline, where every mutant "died" so the 100% meant nothing.
|
|
102
|
+
This is a list of shapes, not a list of things already fixed. Assume at least one is live in the
|
|
103
|
+
tree right now, because that has been true every time anyone checked.
|
|
104
|
+
Ask of any gate: what happens when its input is missing, empty, or unreadable? Silence is the
|
|
105
|
+
wrong answer. Make it fail, or make it say out loud that it did not run.
|
|
106
|
+
- **Recurring bug two: two paths that should agree, and one checks less.** A second entry point
|
|
107
|
+
running fewer checks than the first. A message fixed in one place but not its twin. Two things
|
|
108
|
+
writing to stdout at once. Coverage and mutation testing cannot catch these. Both ask "is this
|
|
109
|
+
path correct?", and the bug is "do these two paths match?". So when you change one of a pair, say
|
|
110
|
+
what every member of the pair does now, including the ones already right.
|
|
111
|
+
- Sensitive paths (CI, scripts, toolchain, the mutation-operator catalog, and the GDScript
|
|
112
|
+
adapter) are listed in `CODEOWNERS` for documentation only. It enforces no review (a sole
|
|
113
|
+
maintainer can't approve their own PR). `main` requires a pull request and, alongside
|
|
114
|
+
`Workflow security (zizmor)` (which reads the workflow files and nothing else), the checks
|
|
115
|
+
`ci.yml`'s now-restored triggers make possible: `Verify` on both platforms, `Secret scan
|
|
116
|
+
(gitleaks)`, and both `Self-test` gates — see `scripts/harden_github.py`'s `REQUIRED_JOBS` for
|
|
117
|
+
the exact, current list. `.github/CODEOWNERS` carries the full list of what branch protection
|
|
118
|
+
enforces. Read changes to these paths carefully before merging. The GDScript adapter is the real
|
|
119
|
+
technical risk, since a wrong mutant means a silently wrong survivor report.
|
|
120
|
+
|
|
121
|
+
## Design goals (keep these in mind)
|
|
122
|
+
|
|
123
|
+
- Ship fast: a working v0.1 that mutates one real module and prints survivors beats a framework.
|
|
124
|
+
- Standalone, no AI required: a normal CLI, usable from the README alone.
|
|
125
|
+
- Generic engine, per-language adapters: build the loop once. A new language = one small adapter.
|
|
126
|
+
|
|
127
|
+
## Docs: where things live
|
|
128
|
+
|
|
129
|
+
- [`README.md`](README.md): what it is and why.
|
|
130
|
+
- [`docs/design/DESIGN.md`](docs/design/DESIGN.md): authoritative design (goals, FG/NF requirements, architecture).
|
|
131
|
+
- [`CHANGELOG.md`](CHANGELOG.md): what's landed and in progress (scope / non-goals live in `DESIGN.md`).
|
|
132
|
+
- `docs/mutation-testing.md`: the suite is mutation-tested against itself.
|
|
133
|
+
- `docs/decisions/NNNN-*.md`: append-only ADRs (`ls` is the index).
|
|
134
|
+
- [`docs/releasing.md`](docs/releasing.md): the maintainer runbook for cutting a release to PyPI.
|
|
135
|
+
- [`docs/credits.md`](docs/credits.md): third-party licenses.
|
|
136
|
+
|
|
137
|
+
Live docs open with YAML frontmatter (`type` / `status` / `created`). Build only from
|
|
138
|
+
`status: active`. Two files are deliberately exempt, because they are rendered somewhere that has
|
|
139
|
+
no frontmatter support and would print it as a visible heading: `README.md` (it is the package's
|
|
140
|
+
PyPI description) and `.github/PULL_REQUEST_TEMPLATE.md` (it is pasted verbatim into every new pull
|
|
141
|
+
request's body). `tests/test_docs_frontmatter.py` pins both halves of that rule.
|
|
142
|
+
|
|
143
|
+
## Non-goals (v0.1)
|
|
144
|
+
|
|
145
|
+
Coverage-gated mutant selection, the optional LLM-semantic mutant mode, and any second-language
|
|
146
|
+
adapter (TypeScript is out of scope for v0.1).
|
|
147
|
+
Finishing the GDScript path on a real fixture beats breadth.
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: record
|
|
3
|
+
status: active
|
|
4
|
+
created: 2026-07-18
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Changelog
|
|
8
|
+
|
|
9
|
+
All notable changes to gdmutant are recorded here. The format follows
|
|
10
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
|
|
11
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
12
|
+
|
|
13
|
+
## [0.1.0] - 2026-08-04
|
|
14
|
+
|
|
15
|
+
gdmutant mutates real GDScript and reports survivors end-to-end via the standalone `gdmutant run`
|
|
16
|
+
CLI.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- AST-based mutation of GDScript via [gdtoolkit](https://github.com/Scony/godot-gdscript-toolkit),
|
|
21
|
+
with a re-parse validity guard so invalid mutants are never run.
|
|
22
|
+
- Operators: comparison, boolean, arithmetic, constant, numeric-literal, compound assignment,
|
|
23
|
+
modulo, unary-not, and statement-deletion.
|
|
24
|
+
- Generation-time exclusions for token positions the language itself rules out as meaningful
|
|
25
|
+
mutants, so they never reach the report as survivors: a `%` used for string formatting, a `+` that
|
|
26
|
+
is string concatenation (GDScript's `String` defines no `-`), a `+=` that appends to a string (same
|
|
27
|
+
reason), and a property declaration's initializer whose stored value no getter can read back.
|
|
28
|
+
These change the mutation score, and upward: an excluded mutant leaves the denominator instead
|
|
29
|
+
of counting as a survivor. That is deliberate and is the honest direction: the language already
|
|
30
|
+
settles all four, so none is a gap a test could ever have closed. The first three are invalid
|
|
31
|
+
GDScript, and the property initializer is valid GDScript whose mutant is inert, because the value
|
|
32
|
+
it stores can never be read back. It does mean, though, that a score is not comparable across a
|
|
33
|
+
version that added an exclusion. Each is recognised from the parse tree only
|
|
34
|
+
where the shape is certain (a `String`-typed *variable* is still mutated), because reporting noise
|
|
35
|
+
is a smaller failure than hiding a real gap. `docs/survivors/README.md` states the full list and
|
|
36
|
+
its effect on the score for users.
|
|
37
|
+
- Framework-agnostic test runners over one shared runner contract (a runner-agnostic adapter seam):
|
|
38
|
+
GdUnit4 and GUT as first-class peer JUnit-XML adapters (`--runner gdunit4` / `--runner gut`,
|
|
39
|
+
neither privileged in the engine), plus an exit-code runner (`--runner command`) for any headless
|
|
40
|
+
harness without JUnit output, no addon required. Every runner upholds a crash-safety guarantee: a
|
|
41
|
+
load/compile crash surfaces as a kill or error, never a silent zero-test pass. A whole-run
|
|
42
|
+
`tests == 0` check catches the empty-report shape, but GUT needs more: it skips a suite that fails
|
|
43
|
+
to compile, runs the rest green, and exits 0, so the adapter also treats any run whose test count
|
|
44
|
+
drops below the healthy baseline's as an error, with a non-determinism canary that warns (never
|
|
45
|
+
errors) when a later run reports more tests than the baseline.
|
|
46
|
+
- Multi-file and directory targets: mutate several files or a whole directory in one pass with a
|
|
47
|
+
per-file breakdown and one aggregate mutation score.
|
|
48
|
+
- `--jobs N` runs N mutants in parallel, each on its own copy of the project so in-place mutation
|
|
49
|
+
can't collide: same verdicts as a serial run (process isolation, the per-mutant timeout is scaled
|
|
50
|
+
by N so contention can't cause a false timeout), just faster (measured ~3× at `--jobs 4` on a real
|
|
51
|
+
GdUnit4 module).
|
|
52
|
+
- Test suites are skipped by default on directory targets (by `test/`/`tests/` folder, `test_*.gd` /
|
|
53
|
+
`*_test.gd` / `*Test.gd` name, or `extends GdUnitTestSuite` / `GutTest`), with an `--exclude` glob
|
|
54
|
+
(and a `.gdmutant.toml` `exclude` list) to skip anything else.
|
|
55
|
+
- Reports: a console survivor summary that explains each gap (what's untested, why it matters, where
|
|
56
|
+
to start a test), the
|
|
57
|
+
[`mutation-testing-elements`](https://github.com/stryker-mutator/mutation-testing-elements) JSON
|
|
58
|
+
schema (`--json`), and a genuinely self-contained HTML report (`--html`): one file with every
|
|
59
|
+
style, script and image inlined, so it opens with no network at all and works as a CI artifact or
|
|
60
|
+
an email attachment. It marks the exact changed characters in your source, groups mutants into
|
|
61
|
+
findings (one spot, one operator, the unit of work a single test closes), and inlines the
|
|
62
|
+
per-operator survivor reference so an offline reader can still look up what an operator means. A
|
|
63
|
+
multi-file run opens on a file index ordered by survivors. Replaces an earlier page that inlined
|
|
64
|
+
the report JSON but loaded the generic viewer from a CDN, and so rendered blank offline.
|
|
65
|
+
- Every finding in the HTML report has an address: `path:line:column:operator`, the tuple it
|
|
66
|
+
was grouped by, so it is the same string every time the report is regenerated from source that has
|
|
67
|
+
not moved. The selected finding lives in the URL, so a reload keeps your place and "look at this
|
|
68
|
+
survivor" is a link you can send. A link that no longer resolves falls back to the file it named,
|
|
69
|
+
or to the file index, and never to the wrong finding.
|
|
70
|
+
- The HTML report shows each file by its project-relative path. A report is made to travel, and an
|
|
71
|
+
absolute path from the machine that produced it carries that machine's username and directory
|
|
72
|
+
layout into every row while telling a reader nothing they can act on. A file genuinely outside the
|
|
73
|
+
project keeps its absolute path, because there is no shorter honest name for it. This changes what
|
|
74
|
+
the page *displays*, and the displayed path is what a deep link is keyed on. Note the limit: the
|
|
75
|
+
`--json` report and the copy of it embedded in the HTML file are both unchanged, because their
|
|
76
|
+
keys are identifiers other tooling resolves. A report file still contains the absolute paths, it
|
|
77
|
+
just no longer shows them.
|
|
78
|
+
- The embedded report is downloadable from the page, so the JSON in a report someone mailed you is
|
|
79
|
+
reachable without View Source. No request and no new data, the bytes are the page's own. The
|
|
80
|
+
toolbar button now says `JSON`, not just a bare down-arrow, and the download itself is indented
|
|
81
|
+
(`json.dumps(..., indent=2)`), matching what `--json <path>` writes to disk instead of one
|
|
82
|
+
unreadable line.
|
|
83
|
+
- The file index sorts on any column (score, file, survived, caught, mutants), ascending or
|
|
84
|
+
descending. Most survivors first stays the default, because it is the only order that answers
|
|
85
|
+
"where do I start". Score would rank 1 survivor in 5 mutants level with 100 in 500.
|
|
86
|
+
- The header's rare-status counts are clickable, and reach the mutants behind them through the
|
|
87
|
+
filter the file view already has. The three stay three things: a timeout is a kill and only a
|
|
88
|
+
performance signal, a compile error is the re-parse guard working, and a runtime error is the
|
|
89
|
+
actionable one, a valid mutant that ran and measured nothing because the harness fell over.
|
|
90
|
+
- The browser's back button returns to the file index instead of leaving the report. Structural
|
|
91
|
+
moves (opening a file, going back to the index) get a history entry. Stepping between findings
|
|
92
|
+
still does not, so a long file cannot bury the reader under a back press per finding.
|
|
93
|
+
- `.gdmutant.toml` for persisted per-project flags, and `--dry-run` to list mutants without running
|
|
94
|
+
Godot.
|
|
95
|
+
- Live self-test against real Godot in CI, pinning both runner paths to exact per-mutant outcomes.
|
|
96
|
+
- A GitHub Action, so a consumer's CI runs gdmutant from a few lines of `uses:` YAML instead of a
|
|
97
|
+
hand-rolled install step: it sets up Python and Godot, installs gdmutant, runs it against the
|
|
98
|
+
consumer's project, and writes the surviving mutants (with their explanations) to the job
|
|
99
|
+
summary, right where a reviewer already looks.
|
|
100
|
+
- `gdmutant example` writes a small bundled GDScript file (`gdmutant-hello-world.gd`) to try
|
|
101
|
+
`--dry-run` on, so the Quickstart no longer asks a first-time reader to hand-copy a snippet into
|
|
102
|
+
a file themselves. Refuses to overwrite an existing file of the same name.
|
|
103
|
+
|
|
104
|
+
### Changed
|
|
105
|
+
|
|
106
|
+
- `--progress none` now silences the whole progress stream, not just the heartbeat: the plan
|
|
107
|
+
line, the heartbeat, the per-mutant lines, the closing wall-clock line, and the "preparing the
|
|
108
|
+
project" / "running the unmutated (baseline) suite" notices are all gone. `auto` and `plain`
|
|
109
|
+
still print all of it. The console summary, survivor blocks, and the reports themselves are
|
|
110
|
+
unaffected.
|
|
111
|
+
- `--since <ref>` with no lines changed now writes a valid, empty report instead of leaving
|
|
112
|
+
stdout empty. The exit code and the stderr explanation are unchanged: still 0, still the same
|
|
113
|
+
"nothing to mutate" note, still no tests run and no Godot booted. New: the `--json` or `--html`
|
|
114
|
+
report is written (every given file listed, an empty `mutants` list, no score key), the job
|
|
115
|
+
summary is emitted if `--report step-summary` was asked for, `--project` and the report targets
|
|
116
|
+
are validated the same as on a real run, and a malformed `# gdmutant: ignore[...]` pragma warns.
|
|
117
|
+
- The HTML report now opens in dark mode by default. The toggle to switch to light is unchanged.
|
|
118
|
+
- The count badge on a mark with multiple findings sits further outside the mark's border, so it
|
|
119
|
+
no longer covers part of the character underneath.
|
|
120
|
+
|
|
121
|
+
### Fixed
|
|
122
|
+
|
|
123
|
+
- Six survivor explanations stated something that is not true. The text reaches every user on every
|
|
124
|
+
run, through the console block, the JSON report and the HTML report, and the docs page and the
|
|
125
|
+
shipped copy of it were pinned to each other, so all three said the same wrong thing in sync.
|
|
126
|
+
- Modulo blamed a clean multiple ("where `%`, `*`, and `/` can produce indistinguishable
|
|
127
|
+
results"). A clean multiple is where those three differ most: `6 % 3` is 0, `6 * 3` is 18,
|
|
128
|
+
`6 / 3` is 2. A reader was told the test that works cannot work. The reason is arithmetic's,
|
|
129
|
+
which is what the entry's own "assert the exact result" advice already implied: nothing pins
|
|
130
|
+
the result.
|
|
131
|
+
- Comparison said the swapped operators "differ on exactly one input", which is false for the
|
|
132
|
+
`==` ↔ `!=` pair the operator also produces, and which the entry names in its own example.
|
|
133
|
+
Those two are complements and differ on every input.
|
|
134
|
+
- The list of shapes that are never generated called all four "code GDScript rejects". Three
|
|
135
|
+
are, resting on `String` having no `-`. The fourth, a property initializer no getter can read
|
|
136
|
+
back, is valid GDScript that is merely inert, so an auditor was sent hunting a syntax error
|
|
137
|
+
that is not there.
|
|
138
|
+
- Numeric said "a numeric literal", but only bare decimal integers are mutated: `0.5`, `0xFF`
|
|
139
|
+
and `1_000` produce nothing, so a float bound was never covered.
|
|
140
|
+
- Enum member described only a `numeric` mutant on a member's value, though any mutant inside
|
|
141
|
+
an `enum` block is explained there, including an arithmetic one on `A = 1 + 0`.
|
|
142
|
+
- Compound assign said a string `+=` is "not mutated at all". It gets no compound-assign
|
|
143
|
+
mutant, but the line still gets a statement deletion.
|
|
144
|
+
- `# gdmutant: ignore[statement-deletion]` no longer draws a warning that it "suppresses nothing".
|
|
145
|
+
The annotation always worked. The validator checked names against the token operator catalog
|
|
146
|
+
alone, and statement deletion is structural, so it lives in the GDScript adapter instead. The
|
|
147
|
+
tool was contradicting its own documented advice to scope an ignore with the `mutatorName` from
|
|
148
|
+
the report.
|
|
149
|
+
- The "executable not found" message is mode-aware. Under `--runner command` the executable
|
|
150
|
+
comes from the `--command` string, not from `--godot`, so the message now says that, states that
|
|
151
|
+
`--godot` has no effect in that mode, and shows the user's own command back with the path slot
|
|
152
|
+
marked. It previously recommended `--godot`, which that mode does not read: setting it returned
|
|
153
|
+
the byte-identical error.
|
|
154
|
+
- `--runner command` says up front when the project has no Godot import cache (`.godot/`). On a
|
|
155
|
+
fresh checkout Godot imports every asset before it will run anything, minutes of silence that
|
|
156
|
+
reads as a hang. The JUnit runners do that warm-up themselves (and the "preparing the project"
|
|
157
|
+
notice now says it can take minutes). The exit-code runner cannot, so it names the one command
|
|
158
|
+
that fixes it instead.
|
|
159
|
+
- Survivors that are unkillable by where they sit explain themselves, instead of handing out
|
|
160
|
+
advice nobody can follow. Two places qualify:
|
|
161
|
+
- inside an `assert`: a failed assert aborts the Godot process, so no in-process test can
|
|
162
|
+
pass on the original and fail on the mutant. On defensive code these can be most of a file's
|
|
163
|
+
survivors.
|
|
164
|
+
- on an `enum` member's value: code that names the member moves with it, so nothing observes
|
|
165
|
+
the number. The generic numeric advice ("add a test at the boundary this number sets") is
|
|
166
|
+
meaningless for a tag that has no boundary.
|
|
167
|
+
|
|
168
|
+
Nothing is skipped and no score changes. Every one of these mutants is still generated, still
|
|
169
|
+
run, and still counted. Enum values are deliberately *not* suppressed: bitflag enums and any enum
|
|
170
|
+
that is serialised have values that really are read as numbers, and gdmutant reads one file at a
|
|
171
|
+
time, so it cannot see a numeric use in another file, a save format, or engine code. Suppressing
|
|
172
|
+
them would hide exactly the bugs that matter most. The reference explains what would make one
|
|
173
|
+
killable and leaves the call to you.
|
|
174
|
+
|
|
175
|
+
Every surface agrees: the console block, the JSON report, the HTML page and the job summary all
|
|
176
|
+
resolve their explanation and their link through one rule, so the page can never offer an
|
|
177
|
+
operator's reference beside a contradicting explanation. The `statement-deletion` reference also
|
|
178
|
+
now names the redundant initializer (`_cells = PackedByteArray()` where the declaration
|
|
179
|
+
already default-initialises it) as its commonest legitimate equivalent, with the check that
|
|
180
|
+
confirms one.
|
|
181
|
+
- Progress is measured, not predicted. The up-front `estimated ≈ 24s` figure is gone. It was
|
|
182
|
+
wrong in both directions at once: 1.7–3.4× *under* on a real project (it counted neither
|
|
183
|
+
gdmutant's own per-mutant work nor timeouts, which were four minutes of one 6m24s run) and, since
|
|
184
|
+
it never took `--jobs` into account, roughly N× *over* under `--jobs N`. In its place, three
|
|
185
|
+
lines that `--progress {auto,plain,none}` controls together (`none` silences all three, `auto`
|
|
186
|
+
and `plain` print all three):
|
|
187
|
+
- before, the facts: `18 mutants to run. Baseline suite 1.4s; each mutant is capped at 30s.`
|
|
188
|
+
The cap is the part that paces the wait, and it says how long silence is normal.
|
|
189
|
+
- during, a heartbeat: `… 7/18 done in 1m 12s — 2 survived, 1 timed out.` Every 30s on a
|
|
190
|
+
terminal, every 60s or 10% of mutants (whichever is rarer) in a log or under `CI=true`, and
|
|
191
|
+
always once at the end of each file.
|
|
192
|
+
- after, the wall-clock every other test runner prints and gdmutant did not:
|
|
193
|
+
`Done in 6m 32s — 18 mutants, 8 timed out (4m 0s of that). Baseline suite 1.4s.` The timeout
|
|
194
|
+
cost is broken out because it is the cost nobody can see.
|
|
195
|
+
|
|
196
|
+
No finish time is forecast anywhere. One was built and measured against a real Godot project
|
|
197
|
+
first: on an even workload it tracked the true finish to within 5%, but on the shape that
|
|
198
|
+
actually matters (hanging mutants arriving after the rate has settled), it read 3.2s at 25%
|
|
199
|
+
done for a run that took 58.0s, so it was dropped.
|
|
200
|
+
- `--json -` combined with `--html` no longer writes the `Wrote HTML report to ...`
|
|
201
|
+
confirmation into the JSON stream. It goes to stderr instead, so stdout stays valid JSON.
|
|
202
|
+
- `--json -` combined with `--report step-summary` and no `$GITHUB_STEP_SUMMARY` set is now
|
|
203
|
+
refused up front with exit 2, naming both flags and how to fix it, instead of printing the
|
|
204
|
+
report to stdout and poisoning the JSON.
|
|
205
|
+
- `--report-path` (or a cloned project's own `.gdmutant.toml`) could resolve outside `--project`
|
|
206
|
+
via an absolute override or `../` traversal. Every run deletes whatever sits at that path first,
|
|
207
|
+
so this was a delete-anything-on-disk primitive reachable with no flags at all. Now refused
|
|
208
|
+
unless the resolved path stays inside the project.
|
|
209
|
+
- `.gdmutant.toml`'s `project` key now requires `--trust-config`, alongside the existing
|
|
210
|
+
`command`/`godot` gate: it roots every subprocess's cwd and, under `--jobs`, what gets copied
|
|
211
|
+
once per worker, the same "this file decides what happens on your machine" shape those two keys
|
|
212
|
+
already guarded against.
|
|
213
|
+
- The HTML report's script-embedding escape only rewrote `</`, missing `<!--<script` (needs no
|
|
214
|
+
slash) which could push a browser's HTML parser into a state that swallows the report's own
|
|
215
|
+
closing tag, blanking the page past that point. Every `<` is escaped now.
|
|
216
|
+
|
|
217
|
+
### Safety
|
|
218
|
+
|
|
219
|
+
- Mutations are applied in place and restored after each mutant and on exit. gdmutant warns on
|
|
220
|
+
uncommitted changes, and `--require-clean` makes that a hard stop.
|
|
221
|
+
- `--require-clean` refuses anything it could not confirm, not just changes it could see. A file
|
|
222
|
+
git ignores, a file outside any repository, a machine with no git, and a symlink whose target
|
|
223
|
+
is unbacked all used to pass the check silently (the ignored file and the symlink being the
|
|
224
|
+
worst of them, since git holds no copy of either). Without the flag, a file gdmutant cannot
|
|
225
|
+
judge still says nothing. A gitignored one now warns, which it did not before, because that is
|
|
226
|
+
the case gdmutant can positively tell has no copy anywhere.
|
|
227
|
+
- A git command that fails for a reason other than "no repository here" (dubious ownership, a
|
|
228
|
+
corrupted repository) now reports what git actually said, including the fix it suggests,
|
|
229
|
+
instead of a generic "not inside a git working tree". When the source is a symlink, the message
|
|
230
|
+
names the file git was actually asked about, so a report about a target outside every repository
|
|
231
|
+
does not read as a report about the link.
|
|
232
|
+
- A source file is never left half-written. Each rewrite is staged in a temporary file beside the
|
|
233
|
+
target and renamed over it, so the path always holds one whole version or the other, never
|
|
234
|
+
something truncated or cut off mid-token. If that cannot be done (no room for the temporary
|
|
235
|
+
file, a failed flush, a lock that will not clear, or a file marked read-only), gdmutant stops
|
|
236
|
+
and says so instead of attempting a write that could truncate the file. That promise is about
|
|
237
|
+
the write, not the run: the same write happens twice per mutant, once to apply it and once to
|
|
238
|
+
restore the original, and a failure on the restore leaves the mutant on disk, not your
|
|
239
|
+
original. Every message names which of the two is actually there and points at git to recover
|
|
240
|
+
it.
|
|
241
|
+
- Under `--jobs N`, a worker only ever writes inside its own copy of the project. A source file
|
|
242
|
+
that is not under `--project` has no copy to mutate, so the run is refused with an explanation
|
|
243
|
+
instead of writing outside the copy, which used to report every mutant as a survivor because
|
|
244
|
+
the mutation never reached the project the tests ran against.
|
|
245
|
+
- `.gdmutant.toml` cannot decide what gdmutant executes. Its `command` and `godot` keys name a
|
|
246
|
+
program to run, and the file is read from the project directory, so in a project you cloned,
|
|
247
|
+
somebody else wrote it. Either key makes gdmutant refuse the whole run, with an explanation, and
|
|
248
|
+
exit 2 without running anything, unless you add `--trust-config` or name the program on the
|
|
249
|
+
command line yourself, which always wins over the file. The refusal fires only where the file
|
|
250
|
+
would change what happens, so a key repeating a value you already passed, or the one gdmutant
|
|
251
|
+
would have used anyway, goes through in silence.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: how-to
|
|
3
|
+
status: active
|
|
4
|
+
created: 2026-07-31
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Contributor Covenant Code of Conduct
|
|
8
|
+
|
|
9
|
+
## Our Pledge
|
|
10
|
+
|
|
11
|
+
We pledge to make our community welcoming, safe, and equitable for all.
|
|
12
|
+
|
|
13
|
+
We are committed to fostering an environment that respects and promotes the dignity, rights, and
|
|
14
|
+
contributions of all individuals, regardless of characteristics including race, ethnicity, caste,
|
|
15
|
+
color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or
|
|
16
|
+
expression, sexual orientation, language, philosophy or religion, national or social origin,
|
|
17
|
+
socio-economic position, level of education, or other status. The same privileges of participation
|
|
18
|
+
are extended to everyone who participates in good faith and in accordance with this Covenant.
|
|
19
|
+
|
|
20
|
+
## Encouraged Behaviors
|
|
21
|
+
|
|
22
|
+
While acknowledging differences in social norms, we all strive to meet our community's expectations
|
|
23
|
+
for positive behavior. We also understand that our words and actions may be interpreted differently
|
|
24
|
+
than we intend based on culture, background, or native language.
|
|
25
|
+
|
|
26
|
+
With these considerations in mind, we agree to behave mindfully toward each other and act in ways
|
|
27
|
+
that center our shared values, including:
|
|
28
|
+
|
|
29
|
+
1. Respecting the **purpose of our community**, our activities, and our ways of gathering.
|
|
30
|
+
2. Engaging **kindly and honestly** with others.
|
|
31
|
+
3. Respecting **different viewpoints** and experiences.
|
|
32
|
+
4. **Taking responsibility** for our actions and contributions.
|
|
33
|
+
5. Gracefully giving and accepting **constructive feedback**.
|
|
34
|
+
6. Committing to **repairing harm** when it occurs.
|
|
35
|
+
7. Behaving in other ways that promote and sustain the **well-being of our community**.
|
|
36
|
+
|
|
37
|
+
## Restricted Behaviors
|
|
38
|
+
|
|
39
|
+
We agree to restrict the following behaviors in our community. Instances, threats, and promotion of
|
|
40
|
+
these behaviors are violations of this Code of Conduct.
|
|
41
|
+
|
|
42
|
+
1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal
|
|
43
|
+
attention after any clear request to stop.
|
|
44
|
+
2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a
|
|
45
|
+
community member or group of people.
|
|
46
|
+
3. **Stereotyping or discrimination.** Characterizing anyone's personality or behavior on the basis
|
|
47
|
+
of immutable identities or traits.
|
|
48
|
+
4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate
|
|
49
|
+
in the context or purpose of the community.
|
|
50
|
+
5. **Violating confidentiality.** Sharing or acting on someone's personal or private information
|
|
51
|
+
without their permission.
|
|
52
|
+
6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person
|
|
53
|
+
or group.
|
|
54
|
+
7. Behaving in other ways that **threaten the well-being** of our community.
|
|
55
|
+
|
|
56
|
+
### Other Restrictions
|
|
57
|
+
|
|
58
|
+
1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone
|
|
59
|
+
else to evade enforcement actions.
|
|
60
|
+
2. **Failing to credit sources.** Not properly crediting the sources of content you contribute.
|
|
61
|
+
3. **Promotional materials.** Sharing marketing or other commercial content in a way that is outside
|
|
62
|
+
the norms of the community.
|
|
63
|
+
4. **Irresponsible communication.** Failing to responsibly present content which includes, links or
|
|
64
|
+
describes any other restricted behaviors.
|
|
65
|
+
|
|
66
|
+
## Reporting an Issue
|
|
67
|
+
|
|
68
|
+
Tensions can occur between community members even when they are trying their best to collaborate.
|
|
69
|
+
Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces
|
|
70
|
+
encouraged behaviors and norms that can help avoid conflicts and minimize harm.
|
|
71
|
+
|
|
72
|
+
When an incident does occur, it is important to report it promptly. To report a possible violation,
|
|
73
|
+
open a private report with
|
|
74
|
+
**[GitHub's "Report a vulnerability" form](https://github.com/kphutt/gdmutant/security/advisories/new)**
|
|
75
|
+
(repository → **Security** → **Advisories** → **Report a vulnerability**). That form is the same
|
|
76
|
+
private channel
|
|
77
|
+
[`SECURITY.md`](https://github.com/kphutt/gdmutant/blob/main/.github/SECURITY.md) names, and it is
|
|
78
|
+
the project's only private
|
|
79
|
+
inbox. It carries conduct reports as well as security ones, and a report sent there is visible only
|
|
80
|
+
to the maintainers. Please do not open a public issue for a conduct problem.
|
|
81
|
+
|
|
82
|
+
Where this Code of Conduct says **Community Moderators**, read *the maintainers of this project*.
|
|
83
|
+
gdmutant is maintained by one person, so a report about that person's own conduct has nowhere
|
|
84
|
+
neutral to go inside the project. Send those to
|
|
85
|
+
[GitHub's abuse reporting](https://github.com/contact/report-abuse) instead, which is handled by
|
|
86
|
+
GitHub rather than by anyone here.
|
|
87
|
+
|
|
88
|
+
Community Moderators take reports of violations seriously and will make every effort to respond in a
|
|
89
|
+
timely manner. They will investigate all reports of code of conduct violations, reviewing messages,
|
|
90
|
+
logs, and recordings, or interviewing witnesses and other participants. Community Moderators will
|
|
91
|
+
keep investigation and enforcement actions as transparent as possible while prioritizing safety and
|
|
92
|
+
confidentiality. In order to honor these values, enforcement actions are carried out in private with
|
|
93
|
+
the involved parties, but communicating to the whole community may be part of a mutually agreed upon
|
|
94
|
+
resolution.
|
|
95
|
+
|
|
96
|
+
## Addressing and Repairing Harm
|
|
97
|
+
|
|
98
|
+
This project has no enforcement process of its own, so it uses the Contributor Covenant's suggested
|
|
99
|
+
ladder as written, below.
|
|
100
|
+
|
|
101
|
+
If an investigation by the Community Moderators finds that this Code of Conduct has been violated,
|
|
102
|
+
the following enforcement ladder may be used to determine how best to repair harm, based on the
|
|
103
|
+
incident's impact on the individuals involved and the community as a whole. Depending on the
|
|
104
|
+
severity of a violation, lower rungs on the ladder may be skipped.
|
|
105
|
+
|
|
106
|
+
1. Warning
|
|
107
|
+
1. Event: A violation involving a single incident or series of incidents.
|
|
108
|
+
2. Consequence: A private, written warning from the Community Moderators.
|
|
109
|
+
3. Repair: Examples of repair include a private written apology, acknowledgement of
|
|
110
|
+
responsibility, and seeking clarification on expectations.
|
|
111
|
+
2. Temporarily Limited Activities
|
|
112
|
+
1. Event: A repeated incidence of a violation that previously resulted in a warning, or the first
|
|
113
|
+
incidence of a more serious violation.
|
|
114
|
+
2. Consequence: A private, written warning with a time-limited cooldown period designed to
|
|
115
|
+
underscore the seriousness of the situation and give the community members involved time to
|
|
116
|
+
process the incident. The cooldown period may be limited to particular communication channels
|
|
117
|
+
or interactions with particular community members.
|
|
118
|
+
3. Repair: Examples of repair may include making an apology, using the cooldown period to reflect
|
|
119
|
+
on actions and impact, and being thoughtful about re-entering community spaces after the
|
|
120
|
+
period is over.
|
|
121
|
+
3. Temporary Suspension
|
|
122
|
+
1. Event: A pattern of repeated violation which the Community Moderators have tried to address
|
|
123
|
+
with warnings, or a single serious violation.
|
|
124
|
+
2. Consequence: A private written warning with conditions for return from suspension. In general,
|
|
125
|
+
temporary suspensions give the person being suspended time to reflect upon their behavior and
|
|
126
|
+
possible corrective actions.
|
|
127
|
+
3. Repair: Examples of repair include respecting the spirit of the suspension, meeting the
|
|
128
|
+
specified conditions for return, and being thoughtful about how to reintegrate with the
|
|
129
|
+
community when the suspension is lifted.
|
|
130
|
+
4. Permanent Ban
|
|
131
|
+
1. Event: A pattern of repeated code of conduct violations that other steps on the ladder have
|
|
132
|
+
failed to resolve, or a violation so serious that the Community Moderators determine there is
|
|
133
|
+
no way to keep the community safe with this person as a member.
|
|
134
|
+
2. Consequence: Access to all community spaces, tools, and communication channels is removed. In
|
|
135
|
+
general, permanent bans should be rarely used, should have strong reasoning behind them, and
|
|
136
|
+
should only be resorted to if working through other remedies has failed to change the
|
|
137
|
+
behavior.
|
|
138
|
+
3. Repair: There is no possible repair in cases of this severity.
|
|
139
|
+
|
|
140
|
+
This enforcement ladder is intended as a guideline. It does not limit the ability of Community
|
|
141
|
+
Managers to use their discretion and judgment, in keeping with the best interests of our community.
|
|
142
|
+
|
|
143
|
+
## Scope
|
|
144
|
+
|
|
145
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is
|
|
146
|
+
officially representing the community in public or other spaces. Examples of representing our
|
|
147
|
+
community include using an official email address, posting via an official social media account, or
|
|
148
|
+
acting as an appointed representative at an online or offline event.
|
|
149
|
+
|
|
150
|
+
## Attribution
|
|
151
|
+
|
|
152
|
+
This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at
|
|
153
|
+
[https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
|
|
154
|
+
|
|
155
|
+
Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under
|
|
156
|
+
CC BY-SA 4.0. To view a copy of this license, visit
|
|
157
|
+
[https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)
|
|
158
|
+
|
|
159
|
+
For answers to common questions about Contributor Covenant, see the FAQ at
|
|
160
|
+
[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations
|
|
161
|
+
are provided at
|
|
162
|
+
[https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
|
|
163
|
+
Additional enforcement and community guideline resources can be found at
|
|
164
|
+
[https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The
|
|
165
|
+
enforcement ladder was inspired by the work of
|
|
166
|
+
[Mozilla's code of conduct team](https://github.com/mozilla/inclusion).
|