gd-tools-cli 0.6.0__tar.gz → 0.8.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 (92) hide show
  1. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/PKG-INFO +85 -18
  2. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/README.md +83 -17
  3. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/pyproject.toml +7 -2
  4. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/gd-tools-coverage/coverage.gd +269 -2
  5. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/gd-tools-test/gd_tools_mock.gd +50 -3
  6. gd_tools_cli-0.8.0/src/gd_tools/addons/gd-tools-test/gd_tools_native_coverage.gd +514 -0
  7. gd_tools_cli-0.8.0/src/gd_tools/addons/gd-tools-test/gd_tools_snapshot_serializer.gd +153 -0
  8. gd_tools_cli-0.8.0/src/gd_tools/addons/gd-tools-test/gd_tools_snapshot_store.gd +132 -0
  9. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/gd-tools-test/gd_tools_test.gd +339 -3
  10. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/gd-tools-test/gd_tools_test_preflight.gd +2 -2
  11. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/gd-tools-test/gd_tools_test_runner.gd +80 -5
  12. gd_tools_cli-0.8.0/src/gd_tools/atomic_io.py +78 -0
  13. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/changes.py +82 -0
  14. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/clean.py +8 -3
  15. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/cli.py +435 -81
  16. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/config.py +17 -70
  17. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/diff_reporter.py +2 -2
  18. gd_tools_cli-0.8.0/src/gd_tools/coverage/html_reporter.py +283 -0
  19. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/orchestrator.py +184 -8
  20. gd_tools_cli-0.8.0/src/gd_tools/coverage/patch.py +350 -0
  21. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/plan_generator.py +556 -30
  22. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/playtest.py +27 -14
  23. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/reporter.py +215 -24
  24. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/templates/file.html +40 -3
  25. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/templates/index.html +58 -6
  26. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/errors.py +2 -6
  27. gd_tools_cli-0.8.0/src/gd_tools/gh_annotations.py +36 -0
  28. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/godot.py +3 -39
  29. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/init.py +41 -28
  30. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/lint_runner.py +142 -3
  31. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/native_test/artifacts.py +11 -2
  32. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/native_test/command.py +82 -5
  33. gd_tools_cli-0.8.0/src/gd_tools/native_test/flaky.py +63 -0
  34. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/native_test/orchestrator.py +149 -15
  35. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/native_test/preflight.py +3 -3
  36. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/native_test/protocol.py +13 -35
  37. gd_tools_cli-0.8.0/src/gd_tools/pre_commit.py +283 -0
  38. gd_tools_cli-0.8.0/src/gd_tools/test_results.py +420 -0
  39. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/version.py +4 -12
  40. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/watch/session.py +11 -1
  41. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools_cli.egg-info/PKG-INFO +85 -18
  42. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools_cli.egg-info/SOURCES.txt +8 -3
  43. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools_cli.egg-info/requires.txt +1 -0
  44. gd_tools_cli-0.6.0/src/gd_tools/addons/gd-tools-coverage/post_run_hook.gd +0 -116
  45. gd_tools_cli-0.6.0/src/gd_tools/addons/gd-tools-coverage/pre_run_hook.gd +0 -10
  46. gd_tools_cli-0.6.0/src/gd_tools/addons/gd-tools-test/gd_tools_native_coverage.gd +0 -211
  47. gd_tools_cli-0.6.0/src/gd_tools/coverage/html_reporter.py +0 -142
  48. gd_tools_cli-0.6.0/src/gd_tools/test_runner.py +0 -175
  49. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/setup.cfg +0 -0
  50. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/__init__.py +0 -0
  51. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/__main__.py +0 -0
  52. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addon_check.py +0 -0
  53. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/__init__.py +0 -0
  54. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/gd-tools-editor/coverage_overlay.gd +0 -0
  55. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/gd-tools-editor/dock.gd +0 -0
  56. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/gd-tools-editor/plugin.cfg +0 -0
  57. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/gd-tools-editor/plugin.gd +0 -0
  58. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/gd-tools-test/gd_tools_parameter_naming.gd +0 -0
  59. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/addons/gd-tools-test/gd_tools_test_context.gd +0 -0
  60. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/__init__.py +0 -0
  61. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/cobertura_reporter.py +0 -0
  62. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/lcov_reporter.py +0 -0
  63. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/omissions.py +0 -0
  64. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/coverage/terminal_reporter.py +0 -0
  65. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/doctor.py +0 -0
  66. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/file_discovery.py +0 -0
  67. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/format_runner.py +0 -0
  68. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/migration/__init__.py +0 -0
  69. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/migration/apply.py +0 -0
  70. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/migration/gutconfig.py +0 -0
  71. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/migration/reporter.py +0 -0
  72. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/migration/rewrite.py +0 -0
  73. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/migration/scan.py +0 -0
  74. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/migration/translate.py +0 -0
  75. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/native_test/__init__.py +0 -0
  76. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/native_test/bridge_scan.py +0 -0
  77. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/native_test/discovery.py +0 -0
  78. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/native_test/import_cache.py +0 -0
  79. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/native_test/preflight_cache.py +0 -0
  80. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/output.py +0 -0
  81. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/schema.py +0 -0
  82. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/update_check.py +0 -0
  83. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/verbosity.py +0 -0
  84. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/watch/__init__.py +0 -0
  85. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/watch/coalescer.py +0 -0
  86. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/watch/loop.py +0 -0
  87. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/watch/mapping.py +0 -0
  88. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/watch/observer.py +0 -0
  89. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools/watch/scope.py +0 -0
  90. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools_cli.egg-info/dependency_links.txt +0 -0
  91. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools_cli.egg-info/entry_points.txt +0 -0
  92. {gd_tools_cli-0.6.0 → gd_tools_cli-0.8.0}/src/gd_tools_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gd-tools-cli
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: A modern development workflow CLI for GDScript projects in Godot 4.5+
5
5
  Author-email: mansyar <m.ansyarafi@gmail.com>
6
6
  License: MIT
@@ -35,6 +35,7 @@ Requires-Dist: watchdog
35
35
  Provides-Extra: dev
36
36
  Requires-Dist: pytest; extra == "dev"
37
37
  Requires-Dist: pytest-cov; extra == "dev"
38
+ Requires-Dist: pytest-xdist; extra == "dev"
38
39
  Requires-Dist: ruff==0.15.21; extra == "dev"
39
40
  Requires-Dist: black; extra == "dev"
40
41
  Requires-Dist: commitizen; extra == "dev"
@@ -74,10 +75,11 @@ One install, one config, one mental model.
74
75
  | Feature | Description |
75
76
  |---------|-------------|
76
77
  | **Unified workflow** | One install, one config (`gd-tools.toml`), one mental model for test, lint, format, and coverage. Consistent terminal output with colored markers and summary footers across all commands. |
77
- | **Native test runtime** | Tests extend `GdToolsTest` and run inside Godot itself. Scene and resource integration tests reach the real scene tree through an explicit context object rather than a proxy. |
78
+ | **Native test runtime** | Tests extend `GdToolsTest` and run inside Godot itself. Scene and resource integration tests reach the real scene tree through an explicit context object rather than a proxy. Doubles, partial doubles, and stubbing isolate collaborators; spy assertions cover calls, argument wildcards, call order, and property values. |
79
+ | **Snapshot testing** | `assert_snapshot(value)` pins rendered values -- including object property dumps and node trees -- against versioned, diffable `.snap` files. First run writes and passes; mismatches fail with a unified diff, and `--snapshot-update` accepts the new output. |
78
80
  | **Zero-friction bootstrap** | `gd-tools init` gets a project fully set up in under a minute -- native test and coverage addons deployed, configs generated. |
79
- | **Coverage gap-filling** | Production-quality line and branch coverage for GDScript -- HTML, LCOV, and Cobertura reports that integrate with CI and code review tools. `# gd-tools: no cover` annotations exclude debug-only code from the numbers. `coverage run` collects coverage during manual playtest sessions, not just automated tests. |
80
- | **CI/CD friendly** | Exit codes, `--check` flags, machine-readable output (JSON, JUnit XML, LCOV, Cobertura), no interactive prompts in CI mode. |
81
+ | **Coverage gap-filling** | Production-quality line and branch coverage for GDScript -- HTML, LCOV, Cobertura, and JSON reports, plus GitHub Actions annotations that surface failures inline on pull requests. Independent line (`--min`) and branch (`--min-branch`) gates. `# gd-tools: no cover` annotations exclude debug-only code from the numbers. `coverage run` collects coverage during manual playtest sessions, not just automated tests. Patch coverage (`coverage diff --patch`) reports coverage over only the lines a pull request changes, with its own gate and inline annotations. |
82
+ | **CI/CD friendly** | Exit codes, `--check` flags, machine-readable output (JSON, JUnit XML, LCOV, Cobertura), and GitHub Actions annotations for lint violations and coverage gates. No interactive prompts in CI mode. |
81
83
  | **Standalone compatibility** | gdlint and gdformat continue to work if invoked directly. `gd-tools` is a layer on top, not a lock-in. |
82
84
 
83
85
  ## 3. Installation
@@ -140,13 +142,13 @@ func test_health_starts_at_full() -> void:
140
142
  |---------|-------------|
141
143
  | `gd-tools init` | Bootstrap a Godot project -- deploy the native test and coverage addons, generate configs. |
142
144
  | `gd-tools doctor` | Diagnose the development environment -- Godot, native test addon, coverage addon, tooling; reports legacy GUT artifacts as migration advice. |
143
- | `gd-tools test` | Run tests with optional coverage, thresholds, and JUnit XML output. Suites must extend `GdToolsTest`; `GutTest` suites are rejected with exit 2 and migration guidance. Accepts optional path arguments to override configured test directories. `--parallel N` runs suites through a bounded worker pool. `--changed` runs only the suites mapped from git-changed files. `--watch` re-runs affected suites on `.gd` file changes. Every run publishes a machine-readable artifact index under `.gd-tools/artifacts/<run_id>/`. |
145
+ | `gd-tools test` | Run tests with optional coverage, thresholds, and JUnit XML output. Suites must extend `GdToolsTest`; `GutTest` suites are rejected with exit 2 and migration guidance. Accepts optional path arguments to override configured test directories. `--parallel N` runs suites through a bounded worker pool. `--changed` runs only the suites mapped from git-changed files. `--watch` re-runs affected suites on `.gd` file changes. `--durations N` reports the N slowest tests after the run. `--exitfirst` (`-x`) stops dispatching new suites after the first failing suite; `--shard K/N` runs one round-robin shard for CI matrix splitting. Every run publishes a machine-readable artifact index under `.gd-tools/artifacts/<run_id>/`. |
144
146
  | `gd-tools migrate` | Guided GUT-to-native migration. Default: read-only report with unsupported-construct inventory and proposed base-class rewrites. `--apply` renames clean suites to `GdToolsTest` and translates `.gutconfig.json` into `gd-tools.toml` (merge, never clobber). `--config-only` translates config only. |
145
- | `gd-tools lint` | Lint GDScript files using gdlint with text or JSON output. Accepts one or more file or directory paths. |
147
+ | `gd-tools lint` | Lint GDScript files using gdlint with text, JSON, or GitHub Actions annotation output. Accepts one or more file or directory paths. |
146
148
  | `gd-tools format` | Format GDScript files using gdformat with check and diff modes. Accepts one or more file or directory paths. |
147
- | `gd-tools coverage` | Coverage subcommands -- `report`, `merge`, `show`, `save-baseline`, `diff` (baseline comparison for CI regression gates), and `run` (collect coverage during a manual playtest session). |
149
+ | `gd-tools coverage` | Coverage subcommands -- `report`, `merge`, `show`, `save-baseline`, `diff` (baseline comparison for CI regression gates; with `--patch`, PR-scoped coverage over changed lines), and `run` (collect coverage during a manual playtest session). |
148
150
  | `gd-tools config` | Configuration management -- `show` (display resolved config), `validate` (check config validity), `schema` (print or write the JSON Schema for `gd-tools.toml`). |
149
- | `gd-tools version` | Display versions of all gd-tools components (gd-tools, Godot, gdtoolkit, Python, and GUT when installed) in a table or JSON. |
151
+ | `gd-tools version` | Display versions of all gd-tools components (gd-tools, Godot, gdtoolkit, Python) in a table or JSON. |
150
152
  | `gd-tools completion` | Generate shell completion scripts for bash, zsh, fish, or PowerShell. |
151
153
 
152
154
  ### Selecting and Filtering Tests
@@ -158,8 +160,11 @@ gd-tools test --test test_takes_damage # one test method
158
160
  gd-tools test --tag smoke # suites with that class-level tag
159
161
  gd-tools test --test-timeout 30 # per-test timeout, in seconds
160
162
  gd-tools test --parallel 4 # run suites with 4 concurrent workers
163
+ gd-tools test --durations 10 # report the 10 slowest tests
161
164
  gd-tools test --changed # only suites mapped from git-changed files
162
165
  gd-tools test --changed --base main # PR/CI mode: diff from merge-base with main
166
+ gd-tools test --exitfirst # stop dispatching after the first failing suite
167
+ gd-tools test --shard 2/4 # CI sharding: run suite shard 2 of 4
163
168
  ```
164
169
 
165
170
  `--tag` is repeatable, and `--test-timeout` (per test) is separate from
@@ -168,6 +173,13 @@ worker pool (1-32 workers; bare `--parallel` means 4) while keeping results,
168
173
  JUnit XML, and merged coverage identical to a sequential run. Persist the
169
174
  choice with `parallel = 4` under `[test]` in `gd-tools.toml`.
170
175
 
176
+ `--durations N` prints a "Slowest Tests" table after the run, sorted
177
+ slowest-first, covering pass, fail, and skip outcomes. Bare `--durations`
178
+ means 10; `--durations 0` lists every executed test (pytest parity). Persist
179
+ the choice with `durations = 10` under `[test]` in `gd-tools.toml`; the flag
180
+ overrides the config for one invocation. The table renders identically under
181
+ `--watch` re-runs and `--parallel` runs.
182
+
171
183
  `--changed` selects suites from git instead of running everything: uncommitted
172
184
  changes (staged, unstaged, and untracked) map to suites by the same
173
185
  file→suite convention as `--watch` — `src/enemy.gd` selects
@@ -181,6 +193,29 @@ pull-request CI. `--changed` composes with `--parallel`, `--coverage`, and
181
193
  the `--suite`/`--test`/`--tag` filters (the active filters bound the
182
194
  mapping domain); an empty change set exits 0 without launching Godot.
183
195
 
196
+ `--exitfirst` (short form `-x`) stops dispatching **new** suites after the
197
+ first suite whose final result is a failure — a test failure or an
198
+ infrastructure error. In-flight suites finish and are collected; suites that
199
+ were never started are reported as skipped (`not_run`) and summarized in a
200
+ `Stopped early: fail-fast after suite <id> (K of M suites skipped)` line.
201
+ Passes and skips never trigger the gate, and a suite that passes after a
202
+ retry does not either — only final suite results decide. The exit code stays
203
+ 1 for test failures. `--exitfirst` composes with `--parallel` (the pool
204
+ drains instead of killing workers), with `--coverage` (the report covers the
205
+ suites that ran, labeled partial when plan targets went unmeasured), and with
206
+ `--watch` (the gate resets for every re-run).
207
+
208
+ `--shard K/N` runs only the suites assigned to shard `K` of `N` — suite *i*
209
+ of the deterministic plan order belongs to shard `(i mod N) + 1` — for
210
+ splitting one test run across CI jobs. Selection happens after `--changed`
211
+ filtering and before `--parallel`, so each flag keeps exactly one job.
212
+ `--shard 1/1` is valid and equivalent to no sharding; malformed values
213
+ (`--shard 4/3`, `--shard 3`) exit 2 before any work. Each shard produces its
214
+ own coverage report; merge the shards with `gd-tools coverage merge`.
215
+ `--shard` and `--watch` are rejected together (exit 2). See the
216
+ [sharding recipe](./docs/USER_GUIDE.md#sharding-a-test-run-across-ci-jobs)
217
+ for a GitHub Actions matrix example.
218
+
184
219
  Repeat runs are faster: the `godot --headless --import` step is cached under
185
220
  `.gd-tools/native/import-cache/` and skipped when `project.godot`, any
186
221
  project source/resource file (scripts, scenes, import sidecars, addons), or
@@ -319,7 +354,41 @@ eval "$(gd-tools)"
319
354
  See the [User Guide](./docs/USER_GUIDE.md) for detailed per-shell
320
355
  instructions.
321
356
 
322
- ## 7. Configuration
357
+ ## 7. Pre-commit Hooks
358
+
359
+ Wire gd-tools into the [pre-commit](https://pre-commit.com) framework so
360
+ every commit runs your format check, lint, and (optionally) your test suite:
361
+
362
+ ```bash
363
+ gd-tools install-hooks
364
+ ```
365
+
366
+ The command generates `.pre-commit-hooks.yaml` and merges a `repos: local:`
367
+ entry into `.pre-commit-config.yaml`. Existing hooks — including your own —
368
+ are preserved; re-runs update only the gd-tools entries in place.
369
+
370
+ Interactive by default: confirm each hook (`format`, `lint`, `test`). For
371
+ scripted or CI use, pass flags instead:
372
+
373
+ ```bash
374
+ gd-tools install-hooks --non-interactive # format + lint (defaults)
375
+ gd-tools install-hooks --all # format + lint + test
376
+ gd-tools install-hooks --hooks format,lint # explicit selection
377
+ ```
378
+
379
+ | Flag | Behavior |
380
+ |---|---|
381
+ | `--all` | Enable every hook (format, lint, test). |
382
+ | `--hooks HOOKS` | Comma-separated selection, e.g. `--hooks format,lint`. |
383
+ | `--non-interactive` | Never prompt; use defaults or explicit flags. |
384
+
385
+ Exit codes follow the gd-tools convention: `0` installed, `1` nothing to do,
386
+ `2` config/environment error.
387
+
388
+ See the [User Guide](./docs/USER_GUIDE.md#8-pre-commit-hooks) for the
389
+ generated file contents and per-hook details.
390
+
391
+ ## 8. Configuration
323
392
 
324
393
  `gd-tools` uses a single `gd-tools.toml` file as the source of truth for
325
394
  all tool configuration. `gd-tools init` generates this file with sensible
@@ -333,10 +402,9 @@ binary = "" # Optional -- auto-detected if unset
333
402
  test_dirs = ["test", "tests"]
334
403
  prefix = "test_"
335
404
  suffix = ".gd"
336
- gutconfig = ".gutconfig.json" # legacy; not read by any runtime
337
405
  runtime = "native" # native (default); "gut" is no longer a runnable runtime
338
406
  timeout_seconds = 5.0 # default per-test timeout for async native tests
339
- retries = 0 # opt-in retries per native test
407
+ retries = 0 # opt-in retries per native test; retried passes are flagged as flaky
340
408
  # parallel = 4 # opt-in parallel suite execution (1-32; unset runs sequentially)
341
409
  tags = [] # native suite tag filters
342
410
 
@@ -384,7 +452,7 @@ version's config model.
384
452
  See the [User Guide](./docs/USER_GUIDE.md) for a full configuration reference
385
453
  with all keys, defaults, and examples.
386
454
 
387
- ## 8. Documentation
455
+ ## 9. Documentation
388
456
 
389
457
  | Document | Description |
390
458
  |----------|-------------|
@@ -395,7 +463,7 @@ with all keys, defaults, and examples.
395
463
  | [Roadmap](./docs/ROADMAP.md) | Release phases and milestones. |
396
464
  | [Testing Strategy](./docs/TESTING_STRATEGY.md) | Test pyramid, coverage targets, and CI integration. |
397
465
 
398
- ## 9. Development
466
+ ## 10. Development
399
467
 
400
468
  ```bash
401
469
  # Clone and install in editable mode with dev dependencies
@@ -425,15 +493,14 @@ See [Testing Strategy](./docs/TESTING_STRATEGY.md) for the full testing
425
493
  guide and [Contributing Guide](./docs/CONTRIBUTING.md) for development
426
494
  setup details.
427
495
 
428
- ## 10. License
496
+ ## 11. License
429
497
 
430
498
  MIT
431
499
 
432
- ## 11. Acknowledgements
500
+ ## 12. Acknowledgements
433
501
 
434
- `gd-tools` stands on the shoulders of two excellent community tools. It
435
- wraps them; it does not replace them. Full credit to their authors for the
436
- hard parts.
502
+ `gd-tools` stands on the shoulders of excellent community tools, past and
503
+ present. Full credit to their authors for the hard parts.
437
504
 
438
505
  - **[GUT (Godot Unit Test)](https://github.com/bitwes/Gut)** by
439
506
  [bitwes](https://github.com/bitwes): the GDScript test framework whose
@@ -33,10 +33,11 @@ One install, one config, one mental model.
33
33
  | Feature | Description |
34
34
  |---------|-------------|
35
35
  | **Unified workflow** | One install, one config (`gd-tools.toml`), one mental model for test, lint, format, and coverage. Consistent terminal output with colored markers and summary footers across all commands. |
36
- | **Native test runtime** | Tests extend `GdToolsTest` and run inside Godot itself. Scene and resource integration tests reach the real scene tree through an explicit context object rather than a proxy. |
36
+ | **Native test runtime** | Tests extend `GdToolsTest` and run inside Godot itself. Scene and resource integration tests reach the real scene tree through an explicit context object rather than a proxy. Doubles, partial doubles, and stubbing isolate collaborators; spy assertions cover calls, argument wildcards, call order, and property values. |
37
+ | **Snapshot testing** | `assert_snapshot(value)` pins rendered values -- including object property dumps and node trees -- against versioned, diffable `.snap` files. First run writes and passes; mismatches fail with a unified diff, and `--snapshot-update` accepts the new output. |
37
38
  | **Zero-friction bootstrap** | `gd-tools init` gets a project fully set up in under a minute -- native test and coverage addons deployed, configs generated. |
38
- | **Coverage gap-filling** | Production-quality line and branch coverage for GDScript -- HTML, LCOV, and Cobertura reports that integrate with CI and code review tools. `# gd-tools: no cover` annotations exclude debug-only code from the numbers. `coverage run` collects coverage during manual playtest sessions, not just automated tests. |
39
- | **CI/CD friendly** | Exit codes, `--check` flags, machine-readable output (JSON, JUnit XML, LCOV, Cobertura), no interactive prompts in CI mode. |
39
+ | **Coverage gap-filling** | Production-quality line and branch coverage for GDScript -- HTML, LCOV, Cobertura, and JSON reports, plus GitHub Actions annotations that surface failures inline on pull requests. Independent line (`--min`) and branch (`--min-branch`) gates. `# gd-tools: no cover` annotations exclude debug-only code from the numbers. `coverage run` collects coverage during manual playtest sessions, not just automated tests. Patch coverage (`coverage diff --patch`) reports coverage over only the lines a pull request changes, with its own gate and inline annotations. |
40
+ | **CI/CD friendly** | Exit codes, `--check` flags, machine-readable output (JSON, JUnit XML, LCOV, Cobertura), and GitHub Actions annotations for lint violations and coverage gates. No interactive prompts in CI mode. |
40
41
  | **Standalone compatibility** | gdlint and gdformat continue to work if invoked directly. `gd-tools` is a layer on top, not a lock-in. |
41
42
 
42
43
  ## 3. Installation
@@ -99,13 +100,13 @@ func test_health_starts_at_full() -> void:
99
100
  |---------|-------------|
100
101
  | `gd-tools init` | Bootstrap a Godot project -- deploy the native test and coverage addons, generate configs. |
101
102
  | `gd-tools doctor` | Diagnose the development environment -- Godot, native test addon, coverage addon, tooling; reports legacy GUT artifacts as migration advice. |
102
- | `gd-tools test` | Run tests with optional coverage, thresholds, and JUnit XML output. Suites must extend `GdToolsTest`; `GutTest` suites are rejected with exit 2 and migration guidance. Accepts optional path arguments to override configured test directories. `--parallel N` runs suites through a bounded worker pool. `--changed` runs only the suites mapped from git-changed files. `--watch` re-runs affected suites on `.gd` file changes. Every run publishes a machine-readable artifact index under `.gd-tools/artifacts/<run_id>/`. |
103
+ | `gd-tools test` | Run tests with optional coverage, thresholds, and JUnit XML output. Suites must extend `GdToolsTest`; `GutTest` suites are rejected with exit 2 and migration guidance. Accepts optional path arguments to override configured test directories. `--parallel N` runs suites through a bounded worker pool. `--changed` runs only the suites mapped from git-changed files. `--watch` re-runs affected suites on `.gd` file changes. `--durations N` reports the N slowest tests after the run. `--exitfirst` (`-x`) stops dispatching new suites after the first failing suite; `--shard K/N` runs one round-robin shard for CI matrix splitting. Every run publishes a machine-readable artifact index under `.gd-tools/artifacts/<run_id>/`. |
103
104
  | `gd-tools migrate` | Guided GUT-to-native migration. Default: read-only report with unsupported-construct inventory and proposed base-class rewrites. `--apply` renames clean suites to `GdToolsTest` and translates `.gutconfig.json` into `gd-tools.toml` (merge, never clobber). `--config-only` translates config only. |
104
- | `gd-tools lint` | Lint GDScript files using gdlint with text or JSON output. Accepts one or more file or directory paths. |
105
+ | `gd-tools lint` | Lint GDScript files using gdlint with text, JSON, or GitHub Actions annotation output. Accepts one or more file or directory paths. |
105
106
  | `gd-tools format` | Format GDScript files using gdformat with check and diff modes. Accepts one or more file or directory paths. |
106
- | `gd-tools coverage` | Coverage subcommands -- `report`, `merge`, `show`, `save-baseline`, `diff` (baseline comparison for CI regression gates), and `run` (collect coverage during a manual playtest session). |
107
+ | `gd-tools coverage` | Coverage subcommands -- `report`, `merge`, `show`, `save-baseline`, `diff` (baseline comparison for CI regression gates; with `--patch`, PR-scoped coverage over changed lines), and `run` (collect coverage during a manual playtest session). |
107
108
  | `gd-tools config` | Configuration management -- `show` (display resolved config), `validate` (check config validity), `schema` (print or write the JSON Schema for `gd-tools.toml`). |
108
- | `gd-tools version` | Display versions of all gd-tools components (gd-tools, Godot, gdtoolkit, Python, and GUT when installed) in a table or JSON. |
109
+ | `gd-tools version` | Display versions of all gd-tools components (gd-tools, Godot, gdtoolkit, Python) in a table or JSON. |
109
110
  | `gd-tools completion` | Generate shell completion scripts for bash, zsh, fish, or PowerShell. |
110
111
 
111
112
  ### Selecting and Filtering Tests
@@ -117,8 +118,11 @@ gd-tools test --test test_takes_damage # one test method
117
118
  gd-tools test --tag smoke # suites with that class-level tag
118
119
  gd-tools test --test-timeout 30 # per-test timeout, in seconds
119
120
  gd-tools test --parallel 4 # run suites with 4 concurrent workers
121
+ gd-tools test --durations 10 # report the 10 slowest tests
120
122
  gd-tools test --changed # only suites mapped from git-changed files
121
123
  gd-tools test --changed --base main # PR/CI mode: diff from merge-base with main
124
+ gd-tools test --exitfirst # stop dispatching after the first failing suite
125
+ gd-tools test --shard 2/4 # CI sharding: run suite shard 2 of 4
122
126
  ```
123
127
 
124
128
  `--tag` is repeatable, and `--test-timeout` (per test) is separate from
@@ -127,6 +131,13 @@ worker pool (1-32 workers; bare `--parallel` means 4) while keeping results,
127
131
  JUnit XML, and merged coverage identical to a sequential run. Persist the
128
132
  choice with `parallel = 4` under `[test]` in `gd-tools.toml`.
129
133
 
134
+ `--durations N` prints a "Slowest Tests" table after the run, sorted
135
+ slowest-first, covering pass, fail, and skip outcomes. Bare `--durations`
136
+ means 10; `--durations 0` lists every executed test (pytest parity). Persist
137
+ the choice with `durations = 10` under `[test]` in `gd-tools.toml`; the flag
138
+ overrides the config for one invocation. The table renders identically under
139
+ `--watch` re-runs and `--parallel` runs.
140
+
130
141
  `--changed` selects suites from git instead of running everything: uncommitted
131
142
  changes (staged, unstaged, and untracked) map to suites by the same
132
143
  file→suite convention as `--watch` — `src/enemy.gd` selects
@@ -140,6 +151,29 @@ pull-request CI. `--changed` composes with `--parallel`, `--coverage`, and
140
151
  the `--suite`/`--test`/`--tag` filters (the active filters bound the
141
152
  mapping domain); an empty change set exits 0 without launching Godot.
142
153
 
154
+ `--exitfirst` (short form `-x`) stops dispatching **new** suites after the
155
+ first suite whose final result is a failure — a test failure or an
156
+ infrastructure error. In-flight suites finish and are collected; suites that
157
+ were never started are reported as skipped (`not_run`) and summarized in a
158
+ `Stopped early: fail-fast after suite <id> (K of M suites skipped)` line.
159
+ Passes and skips never trigger the gate, and a suite that passes after a
160
+ retry does not either — only final suite results decide. The exit code stays
161
+ 1 for test failures. `--exitfirst` composes with `--parallel` (the pool
162
+ drains instead of killing workers), with `--coverage` (the report covers the
163
+ suites that ran, labeled partial when plan targets went unmeasured), and with
164
+ `--watch` (the gate resets for every re-run).
165
+
166
+ `--shard K/N` runs only the suites assigned to shard `K` of `N` — suite *i*
167
+ of the deterministic plan order belongs to shard `(i mod N) + 1` — for
168
+ splitting one test run across CI jobs. Selection happens after `--changed`
169
+ filtering and before `--parallel`, so each flag keeps exactly one job.
170
+ `--shard 1/1` is valid and equivalent to no sharding; malformed values
171
+ (`--shard 4/3`, `--shard 3`) exit 2 before any work. Each shard produces its
172
+ own coverage report; merge the shards with `gd-tools coverage merge`.
173
+ `--shard` and `--watch` are rejected together (exit 2). See the
174
+ [sharding recipe](./docs/USER_GUIDE.md#sharding-a-test-run-across-ci-jobs)
175
+ for a GitHub Actions matrix example.
176
+
143
177
  Repeat runs are faster: the `godot --headless --import` step is cached under
144
178
  `.gd-tools/native/import-cache/` and skipped when `project.godot`, any
145
179
  project source/resource file (scripts, scenes, import sidecars, addons), or
@@ -278,7 +312,41 @@ eval "$(gd-tools)"
278
312
  See the [User Guide](./docs/USER_GUIDE.md) for detailed per-shell
279
313
  instructions.
280
314
 
281
- ## 7. Configuration
315
+ ## 7. Pre-commit Hooks
316
+
317
+ Wire gd-tools into the [pre-commit](https://pre-commit.com) framework so
318
+ every commit runs your format check, lint, and (optionally) your test suite:
319
+
320
+ ```bash
321
+ gd-tools install-hooks
322
+ ```
323
+
324
+ The command generates `.pre-commit-hooks.yaml` and merges a `repos: local:`
325
+ entry into `.pre-commit-config.yaml`. Existing hooks — including your own —
326
+ are preserved; re-runs update only the gd-tools entries in place.
327
+
328
+ Interactive by default: confirm each hook (`format`, `lint`, `test`). For
329
+ scripted or CI use, pass flags instead:
330
+
331
+ ```bash
332
+ gd-tools install-hooks --non-interactive # format + lint (defaults)
333
+ gd-tools install-hooks --all # format + lint + test
334
+ gd-tools install-hooks --hooks format,lint # explicit selection
335
+ ```
336
+
337
+ | Flag | Behavior |
338
+ |---|---|
339
+ | `--all` | Enable every hook (format, lint, test). |
340
+ | `--hooks HOOKS` | Comma-separated selection, e.g. `--hooks format,lint`. |
341
+ | `--non-interactive` | Never prompt; use defaults or explicit flags. |
342
+
343
+ Exit codes follow the gd-tools convention: `0` installed, `1` nothing to do,
344
+ `2` config/environment error.
345
+
346
+ See the [User Guide](./docs/USER_GUIDE.md#8-pre-commit-hooks) for the
347
+ generated file contents and per-hook details.
348
+
349
+ ## 8. Configuration
282
350
 
283
351
  `gd-tools` uses a single `gd-tools.toml` file as the source of truth for
284
352
  all tool configuration. `gd-tools init` generates this file with sensible
@@ -292,10 +360,9 @@ binary = "" # Optional -- auto-detected if unset
292
360
  test_dirs = ["test", "tests"]
293
361
  prefix = "test_"
294
362
  suffix = ".gd"
295
- gutconfig = ".gutconfig.json" # legacy; not read by any runtime
296
363
  runtime = "native" # native (default); "gut" is no longer a runnable runtime
297
364
  timeout_seconds = 5.0 # default per-test timeout for async native tests
298
- retries = 0 # opt-in retries per native test
365
+ retries = 0 # opt-in retries per native test; retried passes are flagged as flaky
299
366
  # parallel = 4 # opt-in parallel suite execution (1-32; unset runs sequentially)
300
367
  tags = [] # native suite tag filters
301
368
 
@@ -343,7 +410,7 @@ version's config model.
343
410
  See the [User Guide](./docs/USER_GUIDE.md) for a full configuration reference
344
411
  with all keys, defaults, and examples.
345
412
 
346
- ## 8. Documentation
413
+ ## 9. Documentation
347
414
 
348
415
  | Document | Description |
349
416
  |----------|-------------|
@@ -354,7 +421,7 @@ with all keys, defaults, and examples.
354
421
  | [Roadmap](./docs/ROADMAP.md) | Release phases and milestones. |
355
422
  | [Testing Strategy](./docs/TESTING_STRATEGY.md) | Test pyramid, coverage targets, and CI integration. |
356
423
 
357
- ## 9. Development
424
+ ## 10. Development
358
425
 
359
426
  ```bash
360
427
  # Clone and install in editable mode with dev dependencies
@@ -384,15 +451,14 @@ See [Testing Strategy](./docs/TESTING_STRATEGY.md) for the full testing
384
451
  guide and [Contributing Guide](./docs/CONTRIBUTING.md) for development
385
452
  setup details.
386
453
 
387
- ## 10. License
454
+ ## 11. License
388
455
 
389
456
  MIT
390
457
 
391
- ## 11. Acknowledgements
458
+ ## 12. Acknowledgements
392
459
 
393
- `gd-tools` stands on the shoulders of two excellent community tools. It
394
- wraps them; it does not replace them. Full credit to their authors for the
395
- hard parts.
460
+ `gd-tools` stands on the shoulders of excellent community tools, past and
461
+ present. Full credit to their authors for the hard parts.
396
462
 
397
463
  - **[GUT (Godot Unit Test)](https://github.com/bitwes/Gut)** by
398
464
  [bitwes](https://github.com/bitwes): the GDScript test framework whose
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gd-tools-cli"
7
- version = "0.6.0"
7
+ version = "0.8.0"
8
8
  description = "A modern development workflow CLI for GDScript projects in Godot 4.5+"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -49,6 +49,7 @@ Documentation = "https://github.com/mansyar/gd-tools#readme"
49
49
  dev = [
50
50
  "pytest",
51
51
  "pytest-cov",
52
+ "pytest-xdist",
52
53
  "ruff==0.15.21",
53
54
  "black",
54
55
  "commitizen",
@@ -71,6 +72,10 @@ where = ["src"]
71
72
 
72
73
  [tool.pytest.ini_options]
73
74
  testpaths = ["tests"]
75
+ # Import gd_tools from this checkout's src/ rather than any globally
76
+ # installed (editable) copy, so worktrees and CI checkouts always test
77
+ # their own tree.
78
+ pythonpath = ["src"]
74
79
  markers = [
75
80
  "unit: Unit tests for individual modules.",
76
81
  "integration: Integration tests for command flows.",
@@ -105,7 +110,7 @@ target-version = ["py310"]
105
110
 
106
111
  [tool.commitizen]
107
112
  name = "cz_conventional_commits"
108
- version = "0.6.0"
113
+ version = "0.8.0"
109
114
  version_files = ["pyproject.toml:version"]
110
115
  changelog_file = "CHANGELOG.md"
111
116
  tag_format = "v$version"