kettle-jem 7.1.39 → 7.1.41

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 (42) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +117 -1
  4. data/README.md +43 -44
  5. data/lib/kettle/jem/templates/.duel.rb.example +25 -0
  6. data/lib/kettle/jem/templates/.github/FUNDING.yml.example +0 -1
  7. data/lib/kettle/jem/templates/.github/FUNDING.yml.no-osc.example +0 -1
  8. data/lib/kettle/jem/templates/.github/workflows/auto-assign.yml.example +1 -1
  9. data/lib/kettle/jem/templates/.github/workflows/coverage.yml.example +1 -0
  10. data/lib/kettle/jem/templates/.github/workflows/perf.yml.example +186 -0
  11. data/lib/kettle/jem/templates/.github/workflows/truffleruby-22.3.yml.example +3 -19
  12. data/lib/kettle/jem/templates/.structuredmerge/kettle-jem.yml.example +33 -2
  13. data/lib/kettle/jem/templates/AGENTS.md.example +1 -0
  14. data/lib/kettle/jem/templates/CHANGELOG.transfer.md +6 -1
  15. data/lib/kettle/jem/templates/CONTRIBUTING.md.example +1 -0
  16. data/lib/kettle/jem/templates/README.md.example +17 -40
  17. data/lib/kettle/jem/templates/Rakefile.example +1 -0
  18. data/lib/kettle/jem/templates/gem.gemspec.example +10 -6
  19. data/lib/kettle/jem/templates/gemfiles/modular/cgi/vHEAD.gemfile.example +1 -1
  20. data/lib/kettle/jem/templates/gemfiles/modular/coverage.gemfile.example +1 -1
  21. data/lib/kettle/jem/templates/gemfiles/modular/debug.gemfile.example +1 -1
  22. data/lib/kettle/jem/templates/gemfiles/modular/documentation.gemfile.example +1 -1
  23. data/lib/kettle/jem/templates/gemfiles/modular/duel.gemfile.example +18 -0
  24. data/lib/kettle/jem/templates/gemfiles/modular/optional.gemfile.example +2 -2
  25. data/lib/kettle/jem/templates/gemfiles/modular/recording/r3/recording.gemfile.example +2 -2
  26. data/lib/kettle/jem/templates/gemfiles/modular/recording/r4/recording.gemfile.example +2 -2
  27. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r2/v3.0.gemfile.example +1 -1
  28. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r2.4/v0.0.2.gemfile.example +1 -1
  29. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r3/v3.0.gemfile.example +1 -1
  30. data/lib/kettle/jem/templates/gemfiles/modular/stringio/r4/v3.0.gemfile.example +1 -1
  31. data/lib/kettle/jem/templates/gemfiles/modular/style.gemfile.example +1 -1
  32. data/lib/kettle/jem/templates/gemfiles/modular/templating.gemfile.example +1 -1
  33. data/lib/kettle/jem/templates/gemfiles/modular/webrick/vHEAD.gemfile.example +1 -1
  34. data/lib/kettle/jem/templates/mise.toml.example +1 -0
  35. data/lib/kettle/jem/templates/mise.toml.no-osc.example +1 -0
  36. data/lib/kettle/jem/templates/shim/Gemfile.example +1 -1
  37. data/lib/kettle/jem/templates/shim/gem.gemspec.example +2 -2
  38. data/lib/kettle/jem/version.rb +1 -1
  39. data/lib/kettle/jem.rb +527 -140
  40. data.tar.gz.sig +0 -0
  41. metadata +40 -16
  42. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15369ec624224409f52a0c1c6d97dfcf499f93ccdbb303ec00e942d7ab1dc36c
4
- data.tar.gz: 37e45d9d673ee8c8f68c5b23e4c80ae53d3d9a736dff2124a592dedc2feaf259
3
+ metadata.gz: f44a87b5964c590bf6eb74a9da0ae7900cac81230aea488043d27668c4339cee
4
+ data.tar.gz: 350675e9ec0c38f3104318288598c7bfc814c1a36c290ed1cb31ace8fd55093f
5
5
  SHA512:
6
- metadata.gz: ead539e8d2cb3dc6ceb188f99cd855529d31f4687d144120197f2452b1430ca7e7c3c00917bf978fe67d14d301c370a833edca352c2dea3321f3cffdee985419
7
- data.tar.gz: 1106de95b7e5292560c503f090827435183daf5a25fdc50357694bfd9b0580bdcc15b8f6a0985883ea7c39ca896e1a17afd066edc985602ba4baf445bfff0294
6
+ metadata.gz: 3b30a4785a25f30cff74549eb28b0c1ccc716d1f549572a385935f06d3191cd109df50589e48c50a401e47dcf4f4dec2715fd1b4ccfac58941ae71cf89f6ce40
7
+ data.tar.gz: 1c5335c6579f99a7a993d463287b3a7ffee8bff522dc58151ac35e3ed8c8e3fa37457117adb1b8f261449edd3179efd47e4fdd53a471f165e37fe986aba1e578
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -22,6 +22,22 @@ Please file a bug if you notice a violation of semantic versioning.
22
22
 
23
23
  ### Changed
24
24
 
25
+ - [kc] kettle-jem/prepare: updated 24 project files:
26
+ - configuration (1)
27
+ - dependencies (23)
28
+
29
+ - [kc] kettle-jem/template: updated 5 project files:
30
+ - code and tests (1)
31
+ - dependencies (2)
32
+ - documentation (1)
33
+ - other (1)
34
+
35
+ - [kc] kettle-jem-deps-floor: Update kettle-jem template dependency floors:
36
+ - addressable (>= 2.9.0 -> >= 2.9.1)
37
+
38
+ - [kc] kettle-jem-workflow-pins: Update pinned GitHub Actions in kettle-jem templates:
39
+ - ruby/setup-ruby v1.335.0 (7f797815d8adceb1c412e7abefdd1ab4abc47a5f) -> v1.336.0 (de492cf36cbab201753d720ce4f4e336cd688c94)
40
+
25
41
  ### Deprecated
26
42
 
27
43
  ### Removed
@@ -30,6 +46,102 @@ Please file a bug if you notice a violation of semantic versioning.
30
46
 
31
47
  ### Security
32
48
 
49
+ ## [7.1.41] - 2026-10-29
50
+
51
+ - TAG: [v7.1.41][7.1.41t]
52
+ - COVERAGE: 93.52% -- 11582/12384 lines in 17 files
53
+ - BRANCH COVERAGE: 77.20% -- 4998/6474 branches in 17 files
54
+ - 20.48% documented
55
+
56
+ ### Changed
57
+
58
+ - [kc] kettle-jem-deps-floor: Update kettle-jem template dependency floors:
59
+ - anonymous_loader (>= 0.1.3 -> >= 0.1.4)
60
+ - kettle-dev (>= 3.1.22 -> >= 3.1.24)
61
+ - kettle-family (>= 1.3.29 -> >= 1.3.32)
62
+ - version_gem (>= 1.1.15 -> >= 1.1.16)
63
+
64
+ - [kc] kettle-jem-workflow-pins: Update pinned GitHub Actions in kettle-jem templates:
65
+ - qltysh/qlty-action/coverage v2.4.0 (c9b09987143d1e4ac955f4803c7bea742102feb5) -> v2.5.0 (ebcd284bf32e36a030afe15f1e136c7be08b9506)
66
+ - ruby/setup-ruby v1.333.0 (e1bd6146574da5e0795075b5319b300edb30c5b0) -> v1.334.0 (26e5b7d197ebf60545ee9667bcbace0be11ccaec)
67
+
68
+ - [kc] kettle-jem/prepare: updated 13 project files:
69
+ - dependencies (13)
70
+ - [kc] kettle-jem/template: updated 10 project files:
71
+ - code and tests (2)
72
+ - dependencies (2)
73
+ - documentation (1)
74
+ - other (3)
75
+ - workflows (2)
76
+ - kettle-jem: cold-start bootstrap runs now materialize the first `.github/FUNDING.yml` structurally from discovered funding facts (GitHub org, Open Collective org, configured platform tokens) instead of relying on the removed unconditional `tidelift:` key; configured runs continue to receive FUNDING.yml from packaged template application
77
+
78
+ - [kc] kettle-jem-deps-floor: Update kettle-jem template dependency floors:
79
+ - kettle-family (>= 1.3.34 -> >= 1.3.35)
80
+ - duel (>= 0.1.0 -> >= 0.1.1)
81
+
82
+ - [kc] kettle-deps-floor-extraction: kettle-jem-deps-floor now delegates shared discovery, planning, AST edits, and CLI reporting to the kettle-deps-floor runtime gem while retaining Kettle Jem source defaults and changelog identity. Template sources with {KJ|...} placeholder tokens stay floor-managed through kettle-deps-floor's placeholder_pattern parsing.
83
+
84
+ - [kc] kettle-jem-workflow-pins: Update pinned GitHub Actions in kettle-jem templates:
85
+ - ruby/setup-ruby v1.334.0 (26e5b7d197ebf60545ee9667bcbace0be11ccaec) -> v1.335.0 (7f797815d8adceb1c412e7abefdd1ab4abc47a5f)
86
+
87
+ - [kc] kettle-jem-deps-floor: Update kettle-jem template dependency floors:
88
+ - nomono (>= 1.1.16 -> >= 1.1.17)
89
+ - kettle-dev (>= 3.1.25 -> >= 3.1.26)
90
+ - kettle-changelog (>= 1.0.14 -> >= 1.0.15)
91
+ - kettle-drift (>= 1.0.18 -> >= 1.0.19)
92
+ - kettle-family (>= 1.3.35 -> >= 1.3.36)
93
+ - kettle-test (>= 2.0.28 -> >= 2.0.29)
94
+ - kettle-soup-cover (>= 3.0.15 -> >= 3.0.16)
95
+
96
+ ### Fixed
97
+
98
+ - Gemspec template: the legacy (Ruby < 3.1) gem_version loader no longer require_relative's version.rb, which defined the gem's top-level namespace whenever the gemspec was evaluated (e.g. by bundler/setup), polluting the load path on old Rubies and breaking isolated-loading specs. It now loads version.rb anonymously via Module#module_eval (available on Ruby 2.2+); the Ruby < 2.2 load-path fallback is unchanged.
99
+
100
+ - Skip the generated coverage comment step for fork-originated pull requests, where GitHub's read-only token cannot create comments.
101
+
102
+ - Remove a stale `runtime_heads.gemfile` evaluation from existing `head` appraisals when the separate `dep-heads` appraisal already tests runtime dependency HEADs, avoiding conflicting duplicate requirements for extracted standard libraries.
103
+
104
+ - Curated x_std_lib modular Gemfiles by the shared Ruby-series bucket ranges; obsolete matrix files are removed when the destination raises its minimum Ruby.
105
+
106
+ - Cap the Ruby 2.x StringIO templates at the last compatible release lines, so dependency-floor updates cannot select incompatible releases.
107
+ - Limit automatic template dependency-floor updates to ordinary modular Gemfiles, their `_local` counterparts, and `vHEAD` lanes; Ruby-version compatibility matrix files remain curated.
108
+
109
+ - Legacy kettle-jem config handling: templating now fails closed with a clear error when both .structuredmerge/kettle-jem.yml and a legacy .kettle-jem.yml exist (the legacy file was previously ignored silently, dropping any settings it alone held, e.g. readme.corporate_sponsors), and a legacy-only .kettle-jem.yml is now moved to the canonical .structuredmerge/kettle-jem.yml path on apply (including prepare flows), with the legacy file removed.
110
+
111
+ - kettle-jem: Nomono bundler bootstrap payload now derives its activation floor from TEMPLATE_MANAGED_DEPENDENCIES (was a stale literal >= 1.1.16 while the managed floor is >= 1.1.17), so generated *_local.gemfile bootstraps can no longer drift from the managed nomono floor on floor bumps
112
+
113
+ ### Added
114
+
115
+ - kettle-jem: corporate sponsor entries now accept optional `since`/`until` ISO 8601 dates defining the sponsorship window; entries render only while active, so lapsed or future sponsors can be listed in static config without manual README edits
116
+
117
+ - kettle-jem: corporate sponsor entries now support umbrella sponsors via `members` (e.g. Alpha-Omega backed by AWS, Google, Microsoft): the umbrella renders in the top logo row and members render as linked names in a `backed by` caption in the new `{KJ|README:CORPORATE_SPONSORS_DETAIL}` FLOSS Funding block; members support the same `since`/`until` window and cannot nest further
118
+
119
+ - kettle-jem: corporate sponsor configuration now accepts the explicit target_href and image_src keys alongside legacy url and img_src, preserving existing configs while supporting direct, date-bounded static sponsors.
120
+ - kettle-jem: opt-in Duel performance regression guards (https://github.com/sferik/duel-ruby), off by default. With `workflows.duel: true`, the full template generates the Perf workflow (`.github/workflows/perf.yml`), `gemfiles/modular/duel.gemfile` (Duel and StackProf on Ruby 3.4 or later only), a `perf` appraisal, the root Gemfile wiring, a `rake duel` task with a stub for older Rubies, the RuboCop include for `.duel.rb`, and the Duel README badge. It creates `.duel.rb` only when it is missing. While Duel is off, a project's own `.duel.rb`, `perf.yml`, and `duel.gemfile` are neither written nor removed.
121
+ - kettle-jem: `actions/download-artifact` joins the template workflow pins, for the Perf workflow's Comment job.
122
+ - kettle-jem: the Duel template follows Duel 0.1.1, which it now requires. The Perf workflow's Comment job posts a new comment only when Duel says to (by default when a benchmark fails, as `c.comment` in `.duel.rb` chooses), and never edits one, so the pull request keeps every report. `.duel.rb` adds only the Perf workflow to what Duel watches, as Duel 0.1.1 watches the benchmarks, `.duel.rb`, and the version files itself.
123
+
124
+ ### Removed
125
+
126
+ - kettle-jem: Tidelift mentions removed from the generated README (subscription URLs are dead post-Sonar acquisition) and from the generated `.github/FUNDING.yml`, including stripping lingering `tidelift:` keys on retemplate; the README Enterprise Support section keeps only the `Lifted` package badge with the caption `Lifted through Tidelift, part of Sonar`; SECURITY.md keeps Tidelift as the security-report channel; the FLOSS Funding section is now split into Corporate Sponsorship (sponsor detail block + company pitch) and Individual Support subsections
127
+
128
+ ## [7.1.40] - 2026-10-23
129
+
130
+ - TAG: [v7.1.40][7.1.40t]
131
+ - COVERAGE: 93.45% -- 11397/12196 lines in 17 files
132
+ - BRANCH COVERAGE: 77.11% -- 4911/6369 branches in 17 files
133
+ - 19.89% documented
134
+
135
+ ### Changed
136
+
137
+ - [kc] kettle-jem-deps-floor: Update kettle-jem template dependency floors:
138
+ - kettle-dev (>= 3.1.21 -> >= 3.1.22)
139
+ - rubocop-lts-rspec (>= 1.0.7 -> >= 1.0.8)
140
+
141
+ - [kc] kettle-jem-workflow-pins: Update pinned GitHub Actions in kettle-jem templates:
142
+ - pozil/auto-assign-issue v4.0.2 (bec9ebf443592887b8f9bed643afafdfd3f62f1d) -> v4.1.0 (d6e511896e5f1d2408e74db74416647d8ff80c8c)
143
+ - ruby/setup-ruby v1.332.0 (b1da4809b005e0178fe08a1d57317bffc00b7b4c) -> v1.333.0 (e1bd6146574da5e0795075b5319b300edb30c5b0)
144
+
33
145
  ## [7.1.39] - 2026-10-21
34
146
 
35
147
  - TAG: [v7.1.39][7.1.39t]
@@ -2450,7 +2562,11 @@ Please file a bug if you notice a violation of semantic versioning.
2450
2562
  - Released kettle-jem as part of the initial StructuredMerge Ruby 7.0.0 gem set.
2451
2563
  - Included packaged templates and parser-backed merge support for Ruby gem templating.
2452
2564
 
2453
- [Unreleased]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.39...HEAD
2565
+ [Unreleased]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.41...HEAD
2566
+ [7.1.41]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.40...v7.1.41
2567
+ [7.1.41t]: https://github.com/structuredmerge/structuredmerge-ruby/releases/tag/v7.1.41
2568
+ [7.1.40]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.39...v7.1.40
2569
+ [7.1.40t]: https://github.com/structuredmerge/structuredmerge-ruby/releases/tag/v7.1.40
2454
2570
  [7.1.39]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.38...v7.1.39
2455
2571
  [7.1.39t]: https://github.com/structuredmerge/structuredmerge-ruby/releases/tag/v7.1.39
2456
2572
  [7.1.38]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.37...v7.1.38
data/README.md CHANGED
@@ -57,11 +57,12 @@ to build `kettle-jem` extension gems against the supported plugin seam.
57
57
 
58
58
  ### Compatibility
59
59
 
60
- Compatible with MRI Ruby 4.0.0+, and JRuby.
60
+ The gem's declared runtime compatibility floor is MRI Ruby 4.0.0+, with concordant releases of JRuby and TruffleRuby.
61
61
  CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
62
- This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
63
- may be higher than the gem's runtime compatibility floor when legacy Rubies are
64
- not practical for the current toolchain.
62
+ The current MRI CI lane runs on Linux and macOS.
63
+ The test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml`.
64
+ When it is higher than the runtime floor, older runtime-compatible Rubies are
65
+ not covered by the generated CI or Appraisal matrix.
65
66
 
66
67
  <a href="https://github.com/kettle-dev"><img alt="kettle-dev Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/kettle-dev/avatar-128px.svg" width="14%" align="right"/></a>
67
68
 
@@ -98,22 +99,6 @@ Merge analysis must enter parsing through `tree_haver`. Parser-specific gems reg
98
99
  [sm-family-rust]: https://github.com/structuredmerge/structuredmerge
99
100
  [sm-family-typescript]: https://github.com/structuredmerge/structuredmerge-typescript
100
101
 
101
- ### Runtime and Platform Support
102
-
103
- Support tiers describe what this project's CI actually exercises, not what may
104
- work in an untested environment:
105
-
106
- - **Parallel support**: CI runs the test suite with multiple workers available.
107
- - **Unsupported**: the engine/platform combination is not covered by CI and is
108
- not a project compatibility claim.
109
-
110
- | Ruby engine/version | Linux | macOS | Windows | Notes |
111
- |---------------------|-------|-------|---------|-------|
112
- | MRI 4.0-4.0 | Parallel support | Unsupported | Unsupported | Versioned Ruby workflows run on Linux. |
113
- | MRI current | Parallel support | Parallel support | Unsupported | Current Ruby workflow covers the declared OS platforms. |
114
- | MRI head | Parallel support | Unsupported | Unsupported | CI is allowed to fail while Ruby is in development. |
115
- | JRuby (all CI lanes) | Parallel support | Unsupported | Unsupported | Engine workflows run on Linux; macOS and Windows are not exercised by CI. |
116
-
117
102
  ### Federated DVCS
118
103
 
119
104
  <details markdown="1">
@@ -129,29 +114,12 @@ work in an untested environment:
129
114
 
130
115
  ### Enterprise Support
131
116
 
132
- [![Tidelift](https://tidelift.com/badges/package/rubygems/kettle-jem)](https://tidelift.com/subscription/pkg/rubygems-kettle-jem?utm_source=rubygems-kettle-jem&utm_medium=referral&utm_campaign=readme)
133
-
134
- Available as part of the Tidelift Subscription.
117
+ For enterprise support from the maintainer, use [Upwork][👨🏼‍🏫expsup-upwork] or [CodeMentor][👨🏼‍🏫expsup-codementor].
135
118
 
136
- <details markdown="1">
137
- <summary>Need enterprise-level guarantees?</summary>
138
-
139
- The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
140
-
141
- [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
142
-
143
- - 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies
144
- - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
145
- - 💡Tidelift pays maintainers to maintain the software you depend on!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers
146
-
147
- Alternatively:
119
+ Other support channels:
148
120
 
149
121
  - [![Ruby Users Forum][✉️ruby-forum-img]][✉️ruby-forum]
150
122
  - [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
151
- - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork]
152
- - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor]
153
-
154
- </details>
155
123
 
156
124
  ## ✨ Installation
157
125
 
@@ -379,6 +347,39 @@ workflows:
379
347
  Do not use freeze markers for any of these declarations. The configuration is
380
348
  the source of truth, and templating keeps the root bundle synchronized with it.
381
349
 
350
+ ### Duel Performance Regression Guards
351
+
352
+ [Duel](https://github.com/sferik/duel-ruby) runs a gem's benchmarks on the
353
+ baseline and on the change side by side, and fails if the change is slower.
354
+ It is off by default. To opt in, set:
355
+
356
+ ```yaml
357
+ workflows:
358
+ duel: true
359
+ ```
360
+
361
+ With Duel on, the full template generates:
362
+
363
+ | File | What it holds |
364
+ |------|---------------|
365
+ | `.github/workflows/perf.yml` | A read-only Duel job that compares each pull request with the main branch, each push to `main` with the commit before it, and, on a schedule, the last release. A separate Comment job, with only `pull-requests: write` and none of the branch's code, posts the report on the pull request as a new comment, when a benchmark fails by default (`c.comment` in `.duel.rb` chooses when), and again when a later run's outcome differs from its last comment's. When the baseline's version file names another Ruby, the workflow installs that Ruby too. |
366
+ | `gemfiles/modular/duel.gemfile` | Duel and StackProf, on Ruby 3.4 or later only, so older Rubies install neither. |
367
+ | `Appraisals` | A `perf` appraisal, whose generated `gemfiles/perf.gemfile` the Perf workflow bundles. |
368
+ | `Gemfile` | `eval_gemfile "gemfiles/modular/duel.gemfile"`, for `rake duel` in local development. |
369
+ | `Rakefile` | `rake duel`, with a stub task that only warns where Duel isn't installed. |
370
+ | `.rubocop.yml` | `.duel.rb` in `AllCops/Include` (RuboCop skips dotfiles otherwise), merged with RuboCop's own patterns. |
371
+ | `README.md` | The Duel badge, at the end of the badge row. |
372
+ | `.duel.rb` | Duel's configuration, created only when it is missing. It runs each version on the Ruby that its own version file names, and watches the Perf workflow on top of what Duel watches already: `lib/`, `ext/`, the benchmarks, `.duel.rb`, and the version files. After that it is the project's own. |
373
+
374
+ The benchmarks in `duel/` are the project's to write: see
375
+ [Write a benchmark](https://github.com/sferik/duel-ruby#write-a-benchmark).
376
+ The template has already done what `duel init` would, so there is no need to
377
+ run it.
378
+
379
+ While Duel is off, kettle-jem neither writes nor removes `.duel.rb`,
380
+ `.github/workflows/perf.yml`, or `gemfiles/modular/duel.gemfile`, so a
381
+ project that has its own keeps them as they are.
382
+
382
383
  ### Strategies
383
384
 
384
385
  | Strategy | Behavior |
@@ -776,6 +777,7 @@ See [LICENSE.md][📄license] for the official copyright notice.
776
777
  <details markdown="1">
777
778
  <summary>Copyright holders</summary>
778
779
 
780
+ - Required Notice: Copyright (c) 2026 Erik Berlin
779
781
  - Required Notice: Copyright (c) 2026 Peter H. Boling
780
782
 
781
783
  </details>
@@ -874,9 +876,6 @@ Thanks for RTFM. ☺️
874
876
  [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
875
877
  [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
876
878
  [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
877
- [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-kettle-jem?utm_source=rubygems-kettle-jem&utm_medium=referral&utm_campaign=readme
878
- [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
879
- [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
880
879
  [💁🏼‍♂️peterboling]: http://www.peterboling.com
881
880
  [🚂railsbling]: http://www.railsbling.com
882
881
  [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
@@ -923,7 +922,7 @@ Thanks for RTFM. ☺️
923
922
  [📌gitmoji]: https://gitmoji.dev
924
923
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
925
924
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
926
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-12.196-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
925
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-12.384-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
927
926
  [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
928
927
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
929
928
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -953,5 +952,5 @@ Thanks for RTFM. ☺️
953
952
  | Homepage | https://github.com/structuredmerge/structuredmerge-ruby |
954
953
  | Source | https://github.com/structuredmerge/structuredmerge-ruby |
955
954
  | License | `AGPL-3.0-only` OR `PolyForm-Small-Business-1.0.0` |
956
- | Funding | https://github.com/sponsors/pboling, https://ko-fi.com/pboling, https://liberapay.com/pboling/donate, https://opencollective.com/kettle-dev, https://thanks.dev/u/gh/pboling, https://tidelift.com/funding/github/rubygems/kettle-jem, https://www.buymeacoffee.com/pboling |
955
+ | Funding | https://github.com/sponsors/pboling, https://ko-fi.com/pboling, https://liberapay.com/pboling/donate, https://opencollective.com/kettle-dev, https://thanks.dev/u/gh/pboling, https://www.buymeacoffee.com/pboling |
957
956
  <!-- kettle-jem:metadata:end -->
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Duel's configuration: https://github.com/sferik/duel-ruby/blob/main/docs/configuration.md
4
+ #
5
+ # Duel follows conventions, so set only what differs: benchmarks are in
6
+ # duel/**/*_duel.rb, each version's lib/ is first on the load path, a
7
+ # change is compared when it touches lib/ or ext/ (or the benchmarks, this
8
+ # file, or the version files), and releases are tagged v*.
9
+ #
10
+ # kettle-jem creates this file when it is missing, and leaves it alone
11
+ # after that, so it is this project's to change.
12
+ Duel.configure do |c|
13
+ # Each version runs on the Ruby that its own version file names, so a
14
+ # branch that upgrades Ruby is compared on each version's own Ruby.
15
+ c.ruby = :version_file
16
+ # Compare a change to the Perf workflow too, which decides how the code
17
+ # is measured. (Duel already watches lib/, ext/, the benchmarks, this
18
+ # file, and the version files.)
19
+ c.watch += %w[.github/workflows/perf.yml]
20
+
21
+ # When the workflow comments on a pull request with the report: :always,
22
+ # :results (passed or failed), :failures (failed, or couldn't compare),
23
+ # or :never.
24
+ # c.comment = :failures
25
+ end
@@ -7,4 +7,3 @@ ko_fi: "{KJ|FUNDING:KOFI}" # Replace with a single Ko-fi username
7
7
  liberapay: "{KJ|FUNDING:LIBERAPAY}" # Replace with a single Liberapay username
8
8
  open_collective: "{KJ|OPENCOLLECTIVE_ORG}"
9
9
  thanks_dev: u/gh/{KJ|GH:USER}
10
- tidelift: rubygems/{KJ|GEM_NAME}
@@ -7,4 +7,3 @@ ko_fi: "{KJ|FUNDING:KOFI}" # Replace with a single Ko-fi username
7
7
  liberapay: "{KJ|FUNDING:LIBERAPAY}" # Replace with a single Liberapay username
8
8
  open_collective: # Replace with a single Open Collective slug e.g., orgs/cloud-foundry
9
9
  thanks_dev: u/gh/{KJ|GH:USER}
10
- tidelift: rubygems/{KJ|GEM_NAME}
@@ -12,7 +12,7 @@ jobs:
12
12
  pull-requests: write
13
13
  steps:
14
14
  - name: 'Auto-assign issue'
15
- uses: pozil/auto-assign-issue@bec9ebf443592887b8f9bed643afafdfd3f62f1d # v4.0.2
15
+ uses: pozil/auto-assign-issue@d6e511896e5f1d2408e74db74416647d8ff80c8c # v4.1.0
16
16
  with:
17
17
  repo-token: ${{ secrets.GITHUB_TOKEN }}
18
18
  assignees: "{KJ|GH:USER}"
@@ -11,6 +11,7 @@ env:
11
11
  K_SOUP_COV_MIN_BRANCH: 100
12
12
  K_SOUP_COV_MIN_LINE: 100
13
13
  K_SOUP_COV_MIN_HARD: true
14
+ K_SOUP_COV_MIN_STRICT: false
14
15
  K_SOUP_COV_FORMATTERS: "xml,rcov,lcov,tty"
15
16
  K_SOUP_COV_DO: true
16
17
  K_SOUP_COV_MULTI_FORMATTERS: true
@@ -0,0 +1,186 @@
1
+ name: Perf
2
+
3
+ # Performance regression guards: Duel runs the benchmarks in duel/ on the
4
+ # baseline and on this code side by side, and fails if this code is slower.
5
+ # See .duel.rb and https://github.com/sferik/duel-ruby
6
+
7
+ # The Duel job runs this branch's code, so it only reads. Commenting is a
8
+ # job of its own, which runs none of it.
9
+ permissions:
10
+ contents: read
11
+
12
+ on:
13
+ pull_request:
14
+ push:
15
+ branches:
16
+ - 'main'
17
+ schedule:
18
+ - cron: "0 4 * * *"
19
+ # Allow manually triggering the workflow.
20
+ workflow_dispatch:
21
+
22
+ # Cancels a pull request's previous runs that have not yet completed. Every
23
+ # other run is a group of its own, so none is cancelled: each push to main
24
+ # is compared with the commit before it, and a cancelled run would skip
25
+ # that comparison.
26
+ concurrency:
27
+ group: "${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}"
28
+ cancel-in-progress: true
29
+
30
+ jobs:
31
+ duel:
32
+ if: "github.event_name != 'push' || (!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]'))"
33
+ name: Duel
34
+ runs-on: ubuntu-latest
35
+ # Whether the Comment job comments (post, change or none), and the
36
+ # run's status (passed, failed or neutral), from Duel.
37
+ outputs:
38
+ comment: ${{ steps.compare.outputs.comment }}
39
+ status: ${{ steps.compare.outputs.status }}
40
+ env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps
41
+ # The perf appraisal: the gemspec's dependencies and Duel (Ruby 3.4 or later)
42
+ BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/perf.gemfile
43
+ steps:
44
+ - name: Checkout
45
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
46
+ with:
47
+ fetch-depth: 0 # the baseline comes from history, tags included
48
+ persist-credentials: false
49
+
50
+ # .duel.rb sets c.ruby = :version_file, so each version runs on the Ruby
51
+ # that its own version file names (.ruby-version, .tool-versions, or
52
+ # mise.toml, the first that names one, as Duel reads them). When the
53
+ # baseline's names another Ruby than this one's, install that Ruby too,
54
+ # and link it where Duel finds it. The baseline is the one the Compare
55
+ # step uses: the main branch for a pull request, the commit before a
56
+ # push, the last release on a schedule (the newest v* tag before HEAD,
57
+ # as Duel finds it), and where HEAD left the default branch when run by
58
+ # hand.
59
+ - name: Find the baseline's Ruby
60
+ id: baseline
61
+ env:
62
+ BEFORE: ${{ github.event.before }}
63
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
64
+ run: |
65
+ case "$GITHUB_EVENT_NAME" in
66
+ pull_request) base="origin/$GITHUB_BASE_REF" ;;
67
+ push) base="$BEFORE" ;;
68
+ schedule) base=$(git describe --tags --abbrev=0 --match 'v*' HEAD^ 2>/dev/null) || base= ;;
69
+ *) base=$(git merge-base HEAD "origin/$DEFAULT_BRANCH" 2>/dev/null) || base= ;;
70
+ esac
71
+ ruby_of() {
72
+ for file in .ruby-version .tool-versions mise.toml .mise.toml; do
73
+ text=$(git show "$1:$file" 2>/dev/null) || continue
74
+ case "$file" in
75
+ .ruby-version) version=$(printf '%s\n' "$text" | awk 'NF { print $1; exit }') ;;
76
+ .tool-versions) version=$(printf '%s\n' "$text" | awk '$1 == "ruby" { print $2; exit }') ;;
77
+ *) version=$(printf '%s\n' "$text" | awk '/^\[/ { tools = ($0 ~ /^\[tools\]/) } tools && match($0, /^[ \t]*"?ruby"?[ \t]*=[ \t]*[[{]?[ \t]*(version[ \t]*=[ \t]*)?"[^"]+"/) { s = substr($0, RSTART, RLENGTH); sub(/"$/, "", s); sub(/.*"/, "", s); print s; exit }') ;;
78
+ esac
79
+ if [ -n "$version" ]; then
80
+ echo "$version"
81
+ return
82
+ fi
83
+ done
84
+ }
85
+ ruby=
86
+ if [ -n "$base" ] && git cat-file -e "$base^{commit}" 2>/dev/null; then
87
+ ruby=$(ruby_of "$base")
88
+ [ "$ruby" = "$(ruby_of HEAD)" ] && ruby=
89
+ fi
90
+ echo "ruby=$ruby" >> "$GITHUB_OUTPUT"
91
+
92
+ - name: Setup the baseline's Ruby
93
+ id: baseline-ruby
94
+ if: steps.baseline.outputs.ruby != ''
95
+ uses: ruby/setup-ruby@de492cf36cbab201753d720ce4f4e336cd688c94 # v1.336.0
96
+ with:
97
+ ruby-version: ${{ steps.baseline.outputs.ruby }}
98
+ # The bundle for this Ruby too, which the baseline's workers load.
99
+ # (Gems installed for one Ruby version don't load on another.)
100
+ bundler-cache: true
101
+
102
+ - name: Link the baseline's Ruby where Duel finds it
103
+ if: steps.baseline.outputs.ruby != ''
104
+ env:
105
+ PREFIX: ${{ steps.baseline-ruby.outputs.ruby-prefix }}
106
+ run: |
107
+ mkdir -p ~/.rubies
108
+ ln -sfn "$PREFIX" ~/.rubies/ruby-"$("$PREFIX/bin/ruby" -e 'print RUBY_VERSION')"
109
+
110
+ # The candidate's Ruby, from its version file (the same one Duel reads)
111
+ - name: Setup Ruby & RubyGems
112
+ uses: ruby/setup-ruby@de492cf36cbab201753d720ce4f4e336cd688c94 # v1.336.0
113
+ with:
114
+ bundler-cache: true
115
+
116
+ # Against the main branch for a pull request, what was there before
117
+ # for a push, and the last release on a schedule (slow creep). Duel
118
+ # adds its report to the job's summary, writes it to duel.md for the
119
+ # comment, and sets the step's outputs to say whether to comment, as
120
+ # c.comment in .duel.rb says, whatever the outcome.
121
+ - name: Compare
122
+ id: compare
123
+ env:
124
+ DUEL_BASE: ${{ github.event_name == 'schedule' && 'last-release' || github.event_name == 'push' && github.event.before || '' }}
125
+ # (Run by hand, it compares anyway: with nothing changed, the code
126
+ # with itself, which checks the setup.)
127
+ DUEL_FORCE: ${{ github.event_name == 'workflow_dispatch' && '1' || '' }}
128
+ run: |
129
+ # (A branch's first push, or one that rewrote history, has no
130
+ # commit before it here to compare against.)
131
+ if [ "$GITHUB_EVENT_NAME" = push ] && ! git cat-file -e "$DUEL_BASE^{commit}" 2>/dev/null; then
132
+ echo "::notice::No commit before this push to compare against"
133
+ exit 0
134
+ fi
135
+ bundle exec duel --markdown "$RUNNER_TEMP/duel.md"
136
+
137
+ # The report, for the Comment job
138
+ - name: Keep the report
139
+ if: ${{ !cancelled() && github.event_name == 'pull_request' }}
140
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
141
+ with:
142
+ name: duel-report
143
+ path: ${{ runner.temp }}/duel.md
144
+ if-no-files-found: ignore # none when the comparison didn't run
145
+ retention-days: 1
146
+
147
+ # The report, as a new comment on the pull request, when Duel says to:
148
+ # always (post), or only when its last comment's status differs from
149
+ # this run's (change), so the latest comment is never stale. It never
150
+ # edits or deletes a comment. This job checks nothing out and runs none
151
+ # of the branch's code, so it alone can write. (A pull request from a
152
+ # fork gets no comment, as its token can't write one: the Duel job's
153
+ # summary has the report.)
154
+ comment:
155
+ name: Comment
156
+ needs: duel
157
+ if: ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && (needs.duel.outputs.comment == 'post' || needs.duel.outputs.comment == 'change') }}
158
+ runs-on: ubuntu-latest
159
+ permissions:
160
+ pull-requests: write # to comment on the pull request with the report
161
+ steps:
162
+ - name: Get the report
163
+ id: report
164
+ continue-on-error: true # the Duel job keeps no report when it fails before comparing
165
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
166
+ with:
167
+ name: duel-report
168
+ path: ${{ runner.temp }}/report
169
+
170
+ - name: Comment on the pull request
171
+ if: steps.report.outcome == 'success'
172
+ env:
173
+ GH_TOKEN: ${{ github.token }}
174
+ PR: ${{ github.event.pull_request.number }}
175
+ COMMENT: ${{ needs.duel.outputs.comment }}
176
+ STATUS: ${{ needs.duel.outputs.status }}
177
+ run: |
178
+ marker="<!-- duel"
179
+ # The first line of the last comment Duel posted here: its marker,
180
+ # with its status (none in Duel 0.1.0's, which counts as another).
181
+ last=$(gh api --paginate "repos/$GITHUB_REPOSITORY/issues/$PR/comments" \
182
+ --jq ".[] | select(.user.login == \"github-actions[bot]\") | .body | split(\"\n\")[0] | rtrimstr(\"\r\") | select(startswith(\"$marker status=\") or . == \"$marker -->\")" | tail -n 1)
183
+ if [ "$COMMENT" = post ] || { [ -n "$last" ] && [ "$last" != "$marker status=$STATUS -->" ]; }; then
184
+ { echo "$marker status=$STATUS -->"; cat "$RUNNER_TEMP/report/duel.md"; } > "$RUNNER_TEMP/comment.md"
185
+ gh api --method POST "repos/$GITHUB_REPOSITORY/issues/$PR/comments" -F body=@"$RUNNER_TEMP/comment.md" > /dev/null
186
+ fi
@@ -40,7 +40,8 @@ jobs:
40
40
  timeout-minutes: 30
41
41
  continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }}
42
42
  env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps
43
- BUNDLE_GEMFILE: ${{ github.workspace }}/Appraisal.root.gemfile
43
+ # Use the selected appraisal during setup so engine-specific dependency pins apply immediately.
44
+ BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/ruby_3_0.gemfile
44
45
  strategy:
45
46
  matrix:
46
47
  include:
@@ -71,23 +72,6 @@ jobs:
71
72
  manual-compatibility-bundle: true
72
73
  gem-install-retries: 7
73
74
 
74
- - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
75
- id: bundleAppraisalAttempt1
76
- if: ${{ !env.ACT }}
77
- run: bundle exec appraisal ${{ matrix.appraisal }} install
78
- # Continue to the next step on failure
79
- continue-on-error: true
80
-
81
- # Effectively an automatic retry of the previous step.
82
- - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
83
- id: bundleAppraisalAttempt2
84
- # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip.
85
- if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && !env.ACT }}
86
- run: |
87
- rm -f gemfiles/${{ matrix.appraisal }}.gemfile.lock
88
- rm -rf vendor/bundle "$HOME/.bundle/cache"
89
- bundle exec appraisal ${{ matrix.appraisal }} install
90
-
91
75
  - name: Restore RSpec status log
92
76
  if: "${{!env.ACT}}"
93
77
  uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
@@ -100,4 +84,4 @@ jobs:
100
84
 
101
85
  - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
102
86
  if: ${{ !env.ACT }}
103
- run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
87
+ run: bundle exec ${{ matrix.exec_cmd }}
@@ -265,6 +265,17 @@ workflows:
265
265
  # Include VCR/WebMock recording modular gemfiles in generated Appraisals.
266
266
  # Enable only for projects that actually require recording support.
267
267
  recording: false
268
+ # Duel performance regression guards (https://github.com/sferik/duel-ruby).
269
+ # Off by default. Set `duel: true` to generate the Perf workflow
270
+ # (.github/workflows/perf.yml), gemfiles/modular/duel.gemfile (loaded by
271
+ # the root Gemfile and a `perf` appraisal), the `rake duel` task, the
272
+ # RuboCop include for .duel.rb, and the Duel README badge. .duel.rb is
273
+ # created only when it is missing, and the benchmarks in duel/ are the
274
+ # project's own to write. Duel requires Ruby 3.4 or later: on older
275
+ # Rubies it is not installed, and `rake duel` only warns. While Duel is
276
+ # off, kettle-jem neither writes nor removes .duel.rb, perf.yml, or
277
+ # duel.gemfile, so a project's own copies stay as they are.
278
+ # duel: true
268
279
  # preset: framework
269
280
  # framework_matrix:
270
281
  # dimension: activerecord
@@ -346,8 +357,28 @@ readme:
346
357
  #
347
358
  # corporate_sponsors:
348
359
  # - name: Example Corp
349
- # url: https://example.com
350
- # img_src: https://example.com/logo.svg
360
+ # target_href: https://example.com
361
+ # image_src: https://example.com/logo.svg
362
+ # # Legacy aliases remain accepted: url and img_src.
363
+ # # Optional sponsorship window (ISO 8601 dates).
364
+ # # `since` is the first day shown; `until` is the last day shown.
365
+ # # Omit either bound for an open-ended period.
366
+ # # since: 2026-01-01
367
+ # # until: 2026-12-31
368
+ # An umbrella sponsor (an organization that itself is funded by companies,
369
+ # e.g. Alpha-Omega) may list its backers as members. The umbrella renders in
370
+ # the top logo row; members render as linked names in a "backed by" caption
371
+ # in the FLOSS Funding detail block. Members require name and target_href
372
+ # (legacy url is accepted), accept the same optional since/until window,
373
+ # and cannot themselves have members.
374
+ #
375
+ # corporate_sponsors:
376
+ # - name: Alpha-Omega
377
+ # target_href: https://alpha-omega.dev
378
+ # image_src: https://alpha-omega.dev/logo.svg
379
+ # members:
380
+ # - name: Example Corp
381
+ # target_href: https://example.com
351
382
  # Optional package-family README block.
352
383
  # Supported values: structuredmerge
353
384
  # Leave unset for standalone kettle-rb gems and most projects.
@@ -207,6 +207,7 @@ mise exec -C /path/to/project -- bin/kettle-soup-cover -d
207
207
  - `K_SOUP_COV_MIN_LINE` – Line coverage threshold
208
208
  - `K_SOUP_COV_MIN_BRANCH` – Branch coverage threshold
209
209
  - `K_SOUP_COV_MIN_HARD=true` – Fail if thresholds not met
210
+ - `K_SOUP_COV_MIN_STRICT=true` – Require 100% line, branch, and method coverage
210
211
 
211
212
  ### Code Quality
212
213