kettle-jem 7.1.27 → 7.1.29

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 (73) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +202 -1
  4. data/README.md +18 -2
  5. data/lib/kettle/jem/maintenance_changelog.rb +1 -1
  6. data/lib/kettle/jem/relative_paths.rb +61 -0
  7. data/lib/kettle/jem/tasks/install_task.rb +9 -5
  8. data/lib/kettle/jem/tasks/prepare_task.rb +29 -10
  9. data/lib/kettle/jem/tasks/template_task.rb +18 -3
  10. data/lib/kettle/jem/templates/.git-hooks/prepare-commit-msg.example +3 -4
  11. data/lib/kettle/jem/templates/.github/workflows/coverage.yml.example +1 -1
  12. data/lib/kettle/jem/templates/.github/workflows/current.yml.example +8 -13
  13. data/lib/kettle/jem/templates/.github/workflows/dep-heads.yml.example +5 -5
  14. data/lib/kettle/jem/templates/.github/workflows/framework-ci.yml.example +1 -1
  15. data/lib/kettle/jem/templates/.github/workflows/heads.yml.example +3 -63
  16. data/lib/kettle/jem/templates/.github/workflows/jruby-10.0.yml.example +2 -2
  17. data/lib/kettle/jem/templates/.github/workflows/jruby-9.1.yml.example +2 -2
  18. data/lib/kettle/jem/templates/.github/workflows/jruby-9.2.yml.example +2 -2
  19. data/lib/kettle/jem/templates/.github/workflows/jruby-9.3.yml.example +2 -2
  20. data/lib/kettle/jem/templates/.github/workflows/jruby-9.4.yml.example +2 -2
  21. data/lib/kettle/jem/templates/.github/workflows/jruby.yml.example +2 -2
  22. data/lib/kettle/jem/templates/.github/workflows/license-eye.yml.example +22 -1
  23. data/lib/kettle/jem/templates/.github/workflows/locked_deps.yml.example +1 -1
  24. data/lib/kettle/jem/templates/.github/workflows/opencollective.yml.example +1 -1
  25. data/lib/kettle/jem/templates/.github/workflows/ruby-2.3.yml.example +1 -1
  26. data/lib/kettle/jem/templates/.github/workflows/ruby-2.4.yml.example +1 -1
  27. data/lib/kettle/jem/templates/.github/workflows/ruby-2.5.yml.example +1 -1
  28. data/lib/kettle/jem/templates/.github/workflows/ruby-2.6.yml.example +1 -1
  29. data/lib/kettle/jem/templates/.github/workflows/ruby-2.7.yml.example +1 -1
  30. data/lib/kettle/jem/templates/.github/workflows/ruby-3.0.yml.example +1 -1
  31. data/lib/kettle/jem/templates/.github/workflows/ruby-3.1.yml.example +1 -1
  32. data/lib/kettle/jem/templates/.github/workflows/ruby-3.2.yml.example +1 -1
  33. data/lib/kettle/jem/templates/.github/workflows/ruby-3.3.yml.example +1 -1
  34. data/lib/kettle/jem/templates/.github/workflows/ruby-3.4.yml.example +1 -1
  35. data/lib/kettle/jem/templates/.github/workflows/style.yml.example +1 -1
  36. data/lib/kettle/jem/templates/.github/workflows/templating.yml.example +14 -1
  37. data/lib/kettle/jem/templates/.github/workflows/truffle.yml.example +2 -2
  38. data/lib/kettle/jem/templates/.github/workflows/truffleruby-22.3.yml.example +2 -2
  39. data/lib/kettle/jem/templates/.github/workflows/truffleruby-23.1.yml.example +2 -2
  40. data/lib/kettle/jem/templates/.github/workflows/truffleruby-24.2.yml.example +2 -2
  41. data/lib/kettle/jem/templates/.github/workflows/truffleruby-25.0.yml.example +2 -2
  42. data/lib/kettle/jem/templates/.github/workflows/truffleruby-33.0.yml.example +2 -2
  43. data/lib/kettle/jem/templates/.github/workflows/unlocked_deps.yml.example +1 -1
  44. data/lib/kettle/jem/templates/.structuredmerge/kettle-jem.yml.example +19 -0
  45. data/lib/kettle/jem/templates/Appraisal.root.gemfile.example +2 -1
  46. data/lib/kettle/jem/templates/Appraisals.example +4 -1
  47. data/lib/kettle/jem/templates/CHANGELOG.transfer.md +6 -0
  48. data/lib/kettle/jem/templates/Gemfile.example +12 -3
  49. data/lib/kettle/jem/templates/README.md.example +5 -5
  50. data/lib/kettle/jem/templates/Rakefile.example +2 -0
  51. data/lib/kettle/jem/templates/bin/turbo_tests2.example +14 -0
  52. data/lib/kettle/jem/templates/gem.gemspec.example +6 -6
  53. data/lib/kettle/jem/templates/gemfiles/modular/coverage.gemfile.example +1 -1
  54. data/lib/kettle/jem/templates/gemfiles/modular/coverage_local.gemfile.example +10 -12
  55. data/lib/kettle/jem/templates/gemfiles/modular/documentation.gemfile.example +5 -5
  56. data/lib/kettle/jem/templates/gemfiles/modular/ostruct/r4/v0.6.gemfile.example +2 -0
  57. data/lib/kettle/jem/templates/gemfiles/modular/style.gemfile.example +15 -8
  58. data/lib/kettle/jem/templates/gemfiles/modular/style_local.gemfile.example +7 -1
  59. data/lib/kettle/jem/templates/gemfiles/modular/templating.gemfile.example +3 -2
  60. data/lib/kettle/jem/templates/gemfiles/modular/templating_local.gemfile.example +18 -19
  61. data/lib/kettle/jem/templates/gemfiles/modular/x_std_libs/r4/libs.gemfile.example +1 -0
  62. data/lib/kettle/jem/templates/mise.toml.example +1 -1
  63. data/lib/kettle/jem/templates/mise.toml.no-osc.example +1 -1
  64. data/lib/kettle/jem/templates/readme/partials/configuration.md.example +4 -3
  65. data/lib/kettle/jem/templates/shim/Gemfile.example +9 -3
  66. data/lib/kettle/jem/templates/shim/gem.gemspec.example +3 -3
  67. data/lib/kettle/jem/version.rb +1 -1
  68. data/lib/kettle/jem.rb +1156 -705
  69. data.tar.gz.sig +0 -0
  70. metadata +28 -67
  71. metadata.gz.sig +0 -0
  72. data/lib/kettle/jem/templates/.github/workflows/truffleruby-23.0.yml.example +0 -92
  73. data/lib/kettle/jem/templates/gemfiles/modular/json/truffleruby_23_0.gemfile.example +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab2c1907a8126c336dce77925f7cf42e88e1822c6ca43bb3a30f3e3fe18d7aaf
4
- data.tar.gz: 5afb16ea45bb00233c4fab38e3ec978fd42c3ce48490958a201bdff61b07ce53
3
+ metadata.gz: cb5935979a968dd6116dca64f7103743cd61a7c4a82eba917a5fc10d96e6215f
4
+ data.tar.gz: fc3e4e4c387c415121b90fb92428951b360ef7e21166810e722a493f411495f4
5
5
  SHA512:
6
- metadata.gz: b225a3b68b05a3daf9334eb0ce98f17f426560d34be2f6a04031dce08bda14830571025f811a7972cf69cfa772fe5966fe8542dd9f6a37b068d61beaffd1509d
7
- data.tar.gz: e9f85742d4364ec4c4e1f05bb83906c79849cd5040c96de4ad83a377858801998d41a39ee4377557e250eae48aa8bd7d77c64c01b420866b357b0b11ab475c50
6
+ metadata.gz: 538d11740a16375ad1db36a60a59aca480f1a6eb5f92be4c9e63008332f23d3dbd73414e7d0f3ecc815a0ea966ebe7064e25340097321eba1a0f26ec3292608a
7
+ data.tar.gz: cfcc8775260d2dfd62d4a71ca8d7f005bb7815a4de1e6cd2f69c545d2159abe100c213f5570b0051bdb721d444c44214062a64bcbd364bee34bb466ffff01313
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,203 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [7.1.29] - 2026-10-11
34
+
35
+ - TAG: [v7.1.29][7.1.29t]
36
+ - COVERAGE: 93.49% -- 11342/12132 lines in 17 files
37
+ - BRANCH COVERAGE: 77.26% -- 4907/6351 branches in 17 files
38
+ - 19.68% documented
39
+
40
+ ### Added
41
+
42
+ - Engine-incompatible dev-dependency detection (case 3 of the dev-dependency
43
+ conflict policy, GEMSPEC_DEV_DEPENDENCY_PLAN.md Phase 4): a direct
44
+ development dependency with a `KNOWN_GEM_CONFLICT_RESOLUTIONS`
45
+ `:engine_incompatible` entry now surfaces a `dependency_conflicts.resolve`
46
+ review entry when it doesn't support one or more of the project's declared
47
+ `engines:` and has no template-managed modular home. Pilot registry entry:
48
+ `sqlite3` (no `jruby`-platform release ever published). Shares the same
49
+ `dependency_conflicts.resolve` list, decision-matching, and review gate as
50
+ the existing template-managed (case 1) conflicts, and the same
51
+ auto-resolution path for any future entry in this category with
52
+ `force_review: false`.
53
+ - `workflows.framework_matrix` gemfiles for gems tracked by kettle-rb's
54
+ `Kettle::Rb::GemFloors` (activemodel, activerecord, activesupport, sqlite3)
55
+ require the minor series' security floor when the version's requirement
56
+ still admits the series' first release (e.g.
57
+ `gem "activerecord", "~> 7.1.0", ">= 7.1.6"`), and carry a `# kettle-rb:`
58
+ warning comment when no release in the series fixes an advisory.
59
+
60
+ - kettle-jem-template-20260913-001 - Templating now also surfaces a review
61
+ entry in `dependency_conflicts.resolve` when a direct development
62
+ dependency doesn't support one or more of this project's declared
63
+ `engines:` and has no template-managed modular home (e.g. `sqlite3` on
64
+ `jruby`). Review each entry and pick a resolution per the project's own
65
+ engine support needs.
66
+
67
+ - Templating plan phases now prefetch the tree-sitter-language-pack grammars used by template merges (bash, go, html, json, json5, markdown, rbs, ruby, rust, toml, tsx, typescript, yaml) through the language pack's hot-load API before recipes run, so on-demand grammar downloads happen up front with attributable diagnostics instead of failing inside a merge. Configure via KJ_TSLP_PREFETCH_LANGUAGES (space/comma list, or false to disable).
68
+
69
+ - kettle-jem-deps-floor: add FLOOR_HOLDS registry to exclude a dependency floor from automatic raising when a newer release would produce an unresolvable dependency set in generated projects. Held floors are skipped during planning and reported separately (held_floors in JSON, a 'held' line in text output) so the exclusion is auditable rather than silent. First entry: rubocop-minitest (0.41.0 needs rubocop >= 1.89, but standard ~> 1.56 caps rubocop at ~> 1.88).
70
+
71
+ - Add Kettle::Rb::PlatformSupport: generated OS-platform support evidence for CI matrix generation. Introduces the CI OS-family vocabulary (linux, macos, windows) as a fourth, explicitly distinct platform vocabulary, with runner-label mapping and per-engine support claims that each carry a citation. TruffleRuby Windows support (zero Windows assets across all releases) and the RubyInstaller series (including ARM64 from 3.4) are derived from live APIs on regeneration; every other claim is documentation-sourced. Regenerate with rake kettle:rb:platform_support:generate.
72
+
73
+ - kettle-jem: first-class OS-platform support for the CI matrix (spec slice 1038). A new documented `platforms:` config key (CI OS families linux/macos/windows; default linux+macos, Windows opt-in only) drives generation of the current.yml OS matrix from facts via new KJ|CI:OS_MATRIX_ENTRIES and KJ|CI:OS_TEST_STEPS tokens, and a generated KJ|README:PLATFORM_SUPPORT_TABLE tier table under Runtime and Platform Support. Re-templating a repo with existing Windows CI detects and preserves windows via workflow evidence, both in the injected config key and in the facts themselves, so first-run templating cannot silently drop Windows coverage. Platform evidence lives in Kettle::Rb::PlatformSupport (kettle-rb 0.1.17+), with per-claim citations.
74
+
75
+ ### Changed
76
+
77
+ - Require kettle-rb `>= 0.1.14` (security floor data).
78
+ - The `sqlite3` `KNOWN_GEM_CONFLICT_RESOLUTIONS` reason now points at a
79
+ `platforms: [:ruby]` modular gemfile (kettle-jem-appraisals generates
80
+ `gemfiles/modular/activerecord_support/*.gemfile`) instead of saying sqlite3
81
+ has no modular home.
82
+
83
+ - [kc] kettle-jem-deps-floor: Update kettle-jem template dependency floors:
84
+ - gitmoji-regex (>= 2.0.13 -> >= 2.0.15)
85
+ - kettle-changelog (>= 1.0.8 -> >= 1.0.9)
86
+ - kettle-dev (>= 3.1.5 -> >= 3.1.10)
87
+ - kettle-drift (>= 1.0.15 -> >= 1.0.16)
88
+ - kettle-family (>= 1.3.3 -> >= 1.3.10)
89
+ - kettle-soup-cover (>= 3.0.11 -> >= 3.0.12)
90
+ - kettle-test (>= 2.0.23 -> >= 2.0.24)
91
+ - nomono (>= 1.1.6 -> >= 1.1.7)
92
+ - reek (>= 6.5.0 -> >= 6.6.0)
93
+ - rubocop-lts-rspec (>= 1.0.6 -> >= 1.0.7)
94
+ - rubocop-minitest (>= 0.40.0 -> >= 0.41.0)
95
+ - standard (>= 1.56.0 -> >= 1.57.0)
96
+ - turbo_tests2 (>= 3.2.12 -> >= 3.2.14)
97
+ - yaml-converter (>= 0.2.6 -> >= 0.2.7)
98
+ - yard-fence (>= 0.9.9 -> >= 0.9.10)
99
+ - yard-timekeeper (>= 0.2.7 -> >= 0.2.9)
100
+
101
+ - [kc] kettle-jem-workflow-pins: Update pinned GitHub Actions in kettle-jem templates:
102
+ - actions/upload-artifact v7.0.1 (043fb46d1a93c77aae656e7c1c64a875d1fc6a0a) -> v7.0.2 (cf430e030ddbb5b0abf93d22962f4752f3646cd9)
103
+ - appraisal-rb/setup-ruby-flash v2.7 (b7ed1caf57f7986cedd2acca8624960b0820bc9a) -> v2.14 (6f8ad36ba7488db591541483afb5464d01e8861b)
104
+ - github/codeql-action/analyze v4.38.2 (2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2) -> v4.38.3 (24c54180a607b1449ed407dd24f251e4e9147c8d)
105
+ - github/codeql-action/autobuild v4.38.2 (2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2) -> v4.38.3 (24c54180a607b1449ed407dd24f251e4e9147c8d)
106
+ - github/codeql-action/init v4.38.2 (2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2) -> v4.38.3 (24c54180a607b1449ed407dd24f251e4e9147c8d)
107
+ - ruby/setup-ruby v1.327.0 (14594264cd68ce8a2345dd349bc3d138a4ef85c8) -> v1.328.0 (e81a8fa391b11a595c1b7ed84177cc0fe02faf83)
108
+
109
+ - [kc] kettle-jem/prepare: updated 14 project files:
110
+ - dependencies (14)
111
+
112
+ - [kc] kettle-jem/template: updated 12 project files:
113
+ - code and tests (1)
114
+ - other (1)
115
+ - workflows (10)
116
+
117
+ - The templating workflow template now restores tree-sitter-language-pack grammar downloads from actions/cache keyed by the templating lockfiles, keeping the self-test off the network in the common case; a cache miss falls back to the in-run tslp_prefetch phase.
118
+
119
+ - [kc] license-eye-workflow-retry: The generated `.github/workflows/license-eye.yml` retries the license check once before failing. The pinned `apache/skywalking-eyes/dependency` action is a composite that builds license-eye from source on every run, and its own dependency cache is broken upstream: `dependency/action.yml` passes `cache-dependency-path: ${{ github.action_path }}/go.sum`, but `go.sum` lives at the action's repository root rather than in `dependency/` (404 versus 200). setup-go therefore logs "Restore cache failed: Some specified paths were not resolved", the module cache never restores, and each run re-downloads roughly 35 Go modules and re-runs `make`. Every templated repository is consequently exposed to a transient `proxy.golang.org` error on every push; one such run failed with `stream error: stream ID 133; INTERNAL_ERROR; received from peer` after eleven consecutive successes. The retry uses the same `[Attempt 1]`/`[Attempt 2]` idiom as the generated Ruby matrix workflows, and the action pin is unchanged.
120
+
121
+ - Drop TruffleRuby 23.0 and TruffleRuby HEAD from generated CI. Both deadlocked in the parallel test worker pool during the turbo_tests2 v3.2.14 release: 23.0 was cancelled after hitting its own 30-minute cap with orphan ruby processes terminated, and HEAD ran to GitHub's 6-hour ceiling before being cancelled. 23.0 already carried the serial-worker mitigation (TURBO_TESTS2_MAX_PROCESSES=1), and its log shows '1 processes for 13 specs' followed by 29 minutes of silence, so forcing a single worker did not resolve the deadlock. Removes the truffleruby-23.0 workflow and json gemfile templates, the truffleruby job from the heads workflow template, and the matching README compatibility badges and reference definitions. TruffleRuby 23.1, 24.2, 25.0, 33.0 and current all pass and remain generated; the heads workflow keeps its ruby-head and jruby-head jobs, which both succeed.
122
+
123
+ - [kc] kettle-jem-deps-floor: Update kettle-jem template dependency floors:
124
+ - kettle-dev (>= 3.1.10 -> >= 3.1.11)
125
+
126
+ ### Removed
127
+
128
+ - The `commonmarker-merge` and `kramdown-merge` runtime dependencies, and their
129
+ entries in the local sibling and `templating_local.gemfile` path lists.
130
+ kettle-jem merges Markdown with markly (via `ast-crispr-markdown-markly` and
131
+ `markly-merge`); other Markdown backends are still used when a project installs
132
+ them, but are no longer forced on every bundle. This also stops `commonmarker`'s
133
+ native extension from being compiled, which fails on ruby-head.
134
+
135
+ ### Fixed
136
+
137
+ - The packaged `Gemfile` no longer overrides `git_source(:github)` with an SSH
138
+ URL. The override was recorded in `Gemfile.lock` as `git@github.com` remotes,
139
+ which CI runners cannot clone; Bundler's built-in HTTPS source is used again.
140
+ Templating now also removes existing overrides of Bundler's built-in git
141
+ sources (`git_source(:github)`, `:gist`, `:bitbucket`) from destination
142
+ Gemfiles, including the managed monorepo root Gemfile.
143
+
144
+ - Generated Ruby 4 extracted-stdlib Gemfiles now include `ostruct` so suites
145
+ that require it can run on Rubies where it is no longer bundled by default.
146
+ - Regenerate `sig/*.rbs` signature stubs from the template instead of merging them, preventing a duplicate top-level declaration when the detected namespace kind (module vs. class) changes between runs.
147
+ - Gemspec `spec.files` merging now preserves a destination-only `enumerate_package_files.call("...")` splat for any project-specific directory, matching how `enumerate_package_glob.call(...)` splats were already preserved regardless of the glob pattern, instead of raising an unsupported-assignment error.
148
+
149
+ - Generated current-MRI workflows now use setup-ruby-flash v2.7, which validates rv-installed bundles before Appraisal and repairs incomplete native-extension installs through Bundler.
150
+
151
+ - Local template runs retain the released kettle-jem floor in remote templating Gemfiles.
152
+
153
+ - Run duplicate drift detection after all template orchestration mutations, and generate shared nomono resolver options only once in local Gemfiles.
154
+
155
+ - Template the bin/turbo_tests2 launcher required by Windows current-workflow tests.
156
+
157
+ - Refresh locked StructuredMerge sibling gems during local template preparation so Bundler cannot retain stale registry versions.
158
+
159
+ - Local template runs now preserve the released kettle-jem floor in gemspecs as well as remote Gemfiles.
160
+
161
+ - Local Gemfile bootstraps now load nomono's Bundler DSL from the path source recorded in Gemfile.lock, avoiding activation of an older installed release during local templating.
162
+
163
+ - Root and Kettle Changelog Gemfile bootstraps now load Nomono's Bundler DSL from a path-locked checkout instead of activating an installed Nomono release.
164
+
165
+ - Nomono bootstrap normalization is now idempotent and removes duplicate generated loader blocks after repeated template preparation.
166
+
167
+ - Kettle Changelog local Gemfile bootstraps now preserve their conditional block indentation when loading Nomono from a path-locked checkout.
168
+
169
+ - Kettle Changelog's modular bootstrap now reads the project-root Gemfile.lock, so path-locked Nomono is not mistaken for an installed release.
170
+
171
+ - Prevent commit hooks from resolving repository-local Bundler binstubs when checking commit messages.
172
+
173
+ - Keep local template dependencies out of generated Appraisal bundles.
174
+
175
+ - Use Kettle Dev's shared path identity logic when reconciling template and project paths.
176
+
177
+ - Update the generated appraisal style-toolchain gate to Ruby 3.3 so Ruby 3.2 builds do not resolve unsupported lint dependencies.
178
+
179
+ - Normalize local nomono bootstrap blocks as a single AST-bounded region, including Gemfiles whose local_gems assignment uses an array literal; keep appraisal style-gate comments attached to their Ruby-version condition.
180
+
181
+ - Preserve destination Markdown link definitions referenced by README sections retained during templating.
182
+
183
+ - Include files created by template orchestration steps in change accounting and maintenance changelog entries.
184
+
185
+ - Local style Gemfiles now include the configured rubocop-packaging plugin, matching the generated RuboCop configuration.
186
+
187
+ - Generated Gemfiles now include extracted standard-library dependencies on Windows, and only include rubocop-on-rbs on Ruby 3.3 and newer.
188
+
189
+ - Generated GitHub Actions workflows now pin setup-ruby-flash v2.13 so Windows native gems receive the MSYS2 dependency prefixes required by Psych and OpenSSL.
190
+
191
+ - Generated GitHub Actions workflows now pin setup-ruby-flash v2.14, which passes Windows-native MSYS2 prefixes and installs the CA trust bundle for OpenSSL.
192
+
193
+ - Avoid duplicate rubocop-packaging declarations when local style Gemfiles are evaluated.
194
+
195
+ - The StructuredMerge family link in generated READMEs now points to the consolidated StructuredMerge repository.
196
+
197
+ - Hold template style.gemfile rubocop-minitest floor at 0.40: version 0.41.0 requires rubocop >= 1.89.0 while standard (~> 1.56) caps rubocop at ~> 1.88.0, making the generated style bundle unsatisfiable and breaking family re-templating (kettle-family template --execute).
198
+
199
+ - Include modular dependency conflict decisions in template checksums so corrected Gemfile policies are reapplied.
200
+
201
+ - [kc] deps-floor-changelog-carry-forward: kettle-jem-deps-floor dropped changelog records for dependency floors raised earlier in the same Unreleased cycle that the current run did not touch, because its consolidated [kc] entry was rebuilt from only the current run's changes while the keyed upserter replaces the prior entry wholesale. Existing ranges are now parsed and carried forward, so each floor keeps its earliest baseline and its newest target.
202
+
203
+ - [kc] workflow-pins-changelog-carry-forward: kettle-jem-workflow-pins dropped changelog records for GitHub Actions re-pinned earlier in the same Unreleased cycle that the current run did not touch, for the same reason as kettle-jem-deps-floor. Existing pin ranges are now parsed and carried forward, keeping each action's earliest baseline ref and newest target ref.
204
+
205
+ - [kc] git-add-path-escaping-symlinked-home: kettle-jem-deps-floor and kettle-jem-workflow-pins could pass a parent-directory escaping path to git add -- when the project root and the git worktree root were two valid spellings of the same directory, as on hosts where /home is a symlink to /var/home and TMPDIR reports the symlink spelling while git rev-parse --show-toplevel reports the realpath spelling. Both tools now relativize through Kettle::Jem::RelativePaths, which canonicalizes both sides before comparing and refuses to return an escaping path.
206
+
207
+ - Always load the StructuredMerge sibling closure when `STRUCTUREDMERGE_DEV` is set, not only when `K_JEM_TEMPLATING` is set. StructuredMerge is no longer a templating-only concern: `kettle-changelog` reaches it at runtime through `ast-crispr-markdown-markly` -> `markdown-merge` -> `tree_haver`, so the test, lint, and release bundles resolve the family too. While the family is only partially released, the registry copies pin the last published version with an exact requirement, which cannot be satisfied next to a local lockstep version, and version solving failed for every command in the member bundle rather than only for lint. The generated main `Gemfile` and shim `Gemfile` now evaluate `gemfiles/modular/templating.gemfile` when either flag is set; neither flag set still skips it, so CI and released-mode resolution are unchanged.
208
+
209
+ - The templated `gemfiles/modular/style_local.gemfile` passed `root: ["src", "rubocop-lts"]` to `eval_nomono_gems`, which nomono resolves through `join_home` to `$HOME/src/rubocop-lts`. The rubocop-lts family actually lives at `$HOME/src/my/rubocop-lts`, so `RUBOCOP_LTS_DEV=true` silently selected a stale sibling checkout when one existed and a missing tree otherwise. Now `["src", "my", "rubocop-lts"]`, matching the other sibling templates and nomono's own default root. Already-templated projects keep the stale root until re-templated.
210
+
211
+ - kettle-jem: the packaged current.yml Windows test step hardcoded `bin/turbo_tests2`, correct for exactly one destination (and inconsistent even there, since its exec_cmd is kettle-test). The generated step now invokes the destination's own configured test command, resolved to its binstub from ci.exec_cmd. Also: the kettle-dev family local-gems wiring now includes kettle-rb, so destinations can develop against unreleased kettle-rb with KETTLE_DEV_DEV.
212
+
213
+ ## [7.1.28] - 2026-09-10
214
+
215
+ - TAG: [v7.1.28][7.1.28t]
216
+ - COVERAGE: 93.67% -- 11268/12030 lines in 16 files
217
+ - BRANCH COVERAGE: 77.10% -- 4794/6218 branches in 16 files
218
+ - 18.11% documented
219
+
220
+ ### Changed
221
+
222
+ - [kc] kettle-jem-deps-floor: Update kettle-jem template dependency floors:
223
+ - kettle-family (>= 1.3.0 -> >= 1.3.1)
224
+
225
+ - [kc] kettle-jem-workflow-pins: Update pinned GitHub Actions in kettle-jem templates:
226
+ - github/codeql-action/analyze v4.37.9 (cdf488f595d80d6e07e03d4674febd5ab45fa938) -> v4.38.0 (b96794f015dfd88f77b49b1c93e0fa7110f94c63)
227
+ - github/codeql-action/autobuild v4.37.9 (cdf488f595d80d6e07e03d4674febd5ab45fa938) -> v4.38.0 (b96794f015dfd88f77b49b1c93e0fa7110f94c63)
228
+ - github/codeql-action/init v4.37.9 (cdf488f595d80d6e07e03d4674febd5ab45fa938) -> v4.38.0 (b96794f015dfd88f77b49b1c93e0fa7110f94c63)
229
+
33
230
  ## [7.1.27] - 2026-09-10
34
231
 
35
232
  - TAG: [v7.1.27][7.1.27t]
@@ -2032,7 +2229,11 @@ Please file a bug if you notice a violation of semantic versioning.
2032
2229
  - Released kettle-jem as part of the initial StructuredMerge Ruby 7.0.0 gem set.
2033
2230
  - Included packaged templates and parser-backed merge support for Ruby gem templating.
2034
2231
 
2035
- [Unreleased]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.27...HEAD
2232
+ [Unreleased]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.29...HEAD
2233
+ [7.1.29]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.28...v7.1.29
2234
+ [7.1.29t]: https://github.com/structuredmerge/structuredmerge-ruby/releases/tag/v7.1.29
2235
+ [7.1.28]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.27...v7.1.28
2236
+ [7.1.28t]: https://github.com/structuredmerge/structuredmerge-ruby/releases/tag/v7.1.28
2036
2237
  [7.1.27]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.26...v7.1.27
2037
2238
  [7.1.27t]: https://github.com/structuredmerge/structuredmerge-ruby/releases/tag/v7.1.27
2038
2239
  [7.1.26]: https://github.com/structuredmerge/structuredmerge-ruby/compare/v7.1.25...v7.1.26
data/README.md CHANGED
@@ -95,9 +95,25 @@ Merge analysis must enter parsing through `tree_haver`. Parser-specific gems reg
95
95
  [sm-family-fixtures]: https://github.com/structuredmerge/structuredmerge-fixtures
96
96
  [sm-family-go]: https://github.com/structuredmerge/structuredmerge-go
97
97
  [sm-family-ruby]: https://github.com/structuredmerge/structuredmerge-ruby
98
- [sm-family-rust]: https://github.com/structuredmerge/structuredmerge-rust
98
+ [sm-family-rust]: https://github.com/structuredmerge/structuredmerge
99
99
  [sm-family-typescript]: https://github.com/structuredmerge/structuredmerge-typescript
100
100
 
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
+
101
117
  ### Federated DVCS
102
118
 
103
119
  <details markdown="1">
@@ -907,7 +923,7 @@ Thanks for RTFM. ☺️
907
923
  [📌gitmoji]: https://gitmoji.dev
908
924
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
909
925
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
910
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-12.030-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
926
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-12.132-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
911
927
  [🔐security]: https://github.com/structuredmerge/structuredmerge-ruby/blob/main/SECURITY.md
912
928
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
913
929
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -20,7 +20,7 @@ module Kettle
20
20
  path.start_with?("gemfiles/")
21
21
  },
22
22
  documentation: ->(path) { %w[README.md CONTRIBUTING.md SECURITY.md].include?(path) || path.start_with?("docs/") },
23
- configuration: ->(path) { path.start_with?(".structuredmerge/") || path == "mise.toml" || path == ".gitignore" },
23
+ configuration: ->(path) { path.start_with?(".structuredmerge/") || %w[.gitattributes .gitignore mise.toml].include?(path) },
24
24
  code_and_tests: ->(path) { path.start_with?("lib/", "spec/", "test/", "bin/") || path == "Rakefile" }
25
25
  }.freeze
26
26
  TEMPLATE_CHANGELOG_KEYS = {
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module Kettle
6
+ module Jem
7
+ # Relativizes a path under a root directory using filesystem identity rather
8
+ # than lexical string comparison.
9
+ #
10
+ # Pathname#relative_path_from is purely lexical, so when the path and the root
11
+ # are two valid spellings of the same directory it walks up past the root and
12
+ # returns a parent-directory escape instead of raising. On hosts where /home
13
+ # is a symlink to /var/home (Fedora Silverblue/Aurora), Dir.mktmpdir and
14
+ # TMPDIR report "/home/..." while `git rev-parse --show-toplevel` reports
15
+ # "/var/home/...". The escape then reaches `git add --`, which fails with
16
+ # "is outside repository at".
17
+ #
18
+ # Both sides are canonicalized before relativizing. File.realpath is safe
19
+ # here, unlike in Kettle::Paths where the result must preserve the caller's
20
+ # spelling because callers stat those paths and spawn processes from that
21
+ # working directory: these values feed only `git add`, and git itself reports
22
+ # the realpath spelling, so the canonical form is the one git expects.
23
+ module RelativePaths
24
+ class << self
25
+ # Returns the path of +path+ relative to +root+, or nil when root is not
26
+ # an ancestor. Never returns an escaping "../../.." path.
27
+ #
28
+ # Falls back to lexical comparison when either side does not exist yet,
29
+ # because generated and planned paths have no filesystem identity.
30
+ #
31
+ # @param path [String, Pathname, nil]
32
+ # @param root [String, Pathname, nil]
33
+ # @return [String, nil]
34
+ def relative_path_under(path, root)
35
+ return nil if blank?(path) || blank?(root)
36
+
37
+ absolute = File.expand_path(path.to_s)
38
+ boundary = File.expand_path(root.to_s)
39
+ relative = relativize(absolute, boundary)
40
+ relative.start_with?("..") ? nil : relative
41
+ rescue ArgumentError
42
+ nil
43
+ end
44
+
45
+ private
46
+
47
+ def relativize(absolute, boundary)
48
+ if File.exist?(absolute) && File.exist?(boundary)
49
+ Pathname(File.realpath(absolute)).relative_path_from(Pathname(File.realpath(boundary))).to_s
50
+ else
51
+ Pathname(absolute).relative_path_from(Pathname(boundary)).to_s
52
+ end
53
+ end
54
+
55
+ def blank?(value)
56
+ value.nil? || value.to_s.strip.empty?
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -28,6 +28,7 @@ module Kettle
28
28
  def run(project_root: Dir.pwd, env: ENV, run_options: {}, command_runner: method(:run_system_command))
29
29
  effective_run_options = install_run_options(env, run_options)
30
30
  config_migration_step = kettle_config_migration_step(project_root)
31
+ effective_run_options = effective_run_options.merge(defer_drift_check: true)
31
32
  report = Kettle::Jem.apply_project(project_root, env: env, run_options: effective_run_options)
32
33
  report = followup_apply_after_config_bootstrap(project_root, env: env, run_options: effective_run_options, report: report)
33
34
  install_steps = []
@@ -68,6 +69,11 @@ module Kettle
68
69
  report: final_report,
69
70
  run_options: effective_run_options
70
71
  )
72
+ final_report = Kettle::Jem.finalize_duplicate_drift(
73
+ project_root: project_root,
74
+ report: final_report,
75
+ run_options: effective_run_options
76
+ )
71
77
  bootstrap_commit = bootstrap_commit_step(project_root, run_options: effective_run_options)
72
78
  install_steps.concat(
73
79
  execute_orchestration_steps(
@@ -954,9 +960,7 @@ module Kettle
954
960
  end
955
961
 
956
962
  def same_path?(left, right)
957
- File.realpath(left.to_s) == File.realpath(right.to_s)
958
- rescue Errno::ENOENT
959
- File.expand_path(left.to_s) == File.expand_path(right.to_s)
963
+ Kettle::Dev::Paths.same?(left.to_s, right.to_s)
960
964
  end
961
965
 
962
966
  def rubocop_lts_local_root(env)
@@ -1025,7 +1029,7 @@ module Kettle
1025
1029
  destination_bin = File.join(project_root.to_s, "bin")
1026
1030
  destination_binstubs = binstub_files(destination_bin)
1027
1031
  parent_root = git_toplevel(project_root)
1028
- parent_binstubs = if parent_root && File.expand_path(parent_root) != File.expand_path(project_root.to_s)
1032
+ parent_binstubs = if parent_root && !same_path?(parent_root, project_root)
1029
1033
  binstub_files(File.join(parent_root, "bin"))
1030
1034
  else
1031
1035
  []
@@ -1788,7 +1792,7 @@ module Kettle
1788
1792
 
1789
1793
  bundle_gemfile = (env || {})["BUNDLE_GEMFILE"].to_s.strip
1790
1794
  project_gemfile = File.expand_path(File.join(project_root.to_s, "Gemfile"))
1791
- if !bundle_gemfile.empty? && File.expand_path(bundle_gemfile) == project_gemfile
1795
+ if !bundle_gemfile.empty? && same_path?(bundle_gemfile, project_gemfile)
1792
1796
  return {
1793
1797
  name: "bundled_handoff",
1794
1798
  status: "already_bundled",
@@ -28,7 +28,8 @@ module Kettle
28
28
  effective_run_options = Kettle::Jem::Tasks::TemplateTask.env_run_options(env || {}).merge(run_options || {})
29
29
  prepare_run_options = effective_run_options.merge(
30
30
  only: PREPARE_ONLY_PATHS,
31
- skip_lock_normalization: true
31
+ skip_lock_normalization: true,
32
+ defer_drift_check: true
32
33
  )
33
34
  events = Kettle::Jem.event_stream_from_options(effective_run_options)
34
35
  report = Kettle::Jem.apply_project(project_root, env: env, run_options: prepare_run_options)
@@ -37,7 +38,7 @@ module Kettle
37
38
  Kettle::Jem.apply_project(project_root, env: env, run_options: prepare_run_options)
38
39
  end
39
40
  report = merge_supplemental_prepare_report(report, supplemental_report)
40
- transition_step = reconcile_template_managed_dependencies_step(project_root, events: events)
41
+ transition_step = reconcile_template_managed_dependencies_step(project_root, env: env, events: events)
41
42
  nomono_bootstrap_step = normalize_existing_local_gemfile_bootstraps_step(project_root, events: events)
42
43
  setup_env = Kettle::Jem::Tasks::InstallTask.setup_command_env(project_root, env)
43
44
  setup_env["BUNDLE_DISABLE_CHECKSUM_VALIDATION"] = "true"
@@ -51,7 +52,7 @@ module Kettle
51
52
  )
52
53
  Kettle::Jem.emit_step_event(events, "command_step", reset_step, phase: "prepare")
53
54
  bootstrap_name = templating_bootstrap_step_name(project_root)
54
- bootstrap_command = templating_bootstrap_command(project_root)
55
+ bootstrap_command = templating_bootstrap_command(project_root, env: setup_env)
55
56
  Kettle::Jem.emit_step_event(
56
57
  events,
57
58
  "command_step",
@@ -98,6 +99,11 @@ module Kettle
98
99
  "updated critical templating gems, and ran bundle install."
99
100
  }]
100
101
  )
102
+ final_report = Kettle::Jem.finalize_duplicate_drift(
103
+ project_root: project_root,
104
+ report: final_report,
105
+ run_options: effective_run_options
106
+ )
101
107
  Kettle::Jem.emit_summary_event(events, final_report)
102
108
  final_report
103
109
  end
@@ -139,7 +145,7 @@ module Kettle
139
145
  step
140
146
  end
141
147
 
142
- def reconcile_template_managed_dependencies_step(project_root, events:)
148
+ def reconcile_template_managed_dependencies_step(project_root, events:, env: ENV)
143
149
  paths = [
144
150
  File.join(project_root.to_s, "Gemfile"),
145
151
  *Dir.glob(File.join(project_root.to_s, "*.gemfile")),
@@ -148,11 +154,12 @@ module Kettle
148
154
  ].select { |path| File.file?(path) }.sort
149
155
  changed_files = paths.filter_map do |path|
150
156
  before = File.read(path)
151
- after = Kettle::Jem.reconcile_template_managed_dependencies(before)
157
+ relative_path = Pathname.new(path).relative_path_from(Pathname.new(project_root.to_s)).to_s
158
+ after = Kettle::Jem.reconcile_template_managed_dependencies(before, env: env)
152
159
  next if after == before
153
160
 
154
161
  File.write(path, after)
155
- Pathname.new(path).relative_path_from(Pathname.new(project_root.to_s)).to_s
162
+ relative_path
156
163
  end
157
164
  step = {
158
165
  name: "reconcile_template_managed_dependencies",
@@ -217,14 +224,26 @@ module Kettle
217
224
  end
218
225
  end
219
226
 
220
- def bundle_update_templating_bootstrap_command(project_root = Dir.pwd)
221
- %w[bundle update] + managed_bootstrap_gems(project_root) + locked_templating_gems(project_root)
227
+ def bundle_update_templating_bootstrap_command(project_root = Dir.pwd, env: ENV)
228
+ # Bundler can retain registry resolutions for sibling gems even when
229
+ # the local path Gemfile is active; refresh the locked family graph.
230
+ local_structuredmerge_gems = if Kettle::Jem.local_structuredmerge_path_mode?(env)
231
+ Kettle::Jem::STRUCTUREDMERGE_GEMS & locked_gem_names(project_root)
232
+ else
233
+ []
234
+ end
235
+ (
236
+ %w[bundle update] +
237
+ managed_bootstrap_gems(project_root) +
238
+ locked_templating_gems(project_root) +
239
+ local_structuredmerge_gems
240
+ ).uniq
222
241
  end
223
242
 
224
- def templating_bootstrap_command(project_root = Dir.pwd)
243
+ def templating_bootstrap_command(project_root = Dir.pwd, env: ENV)
225
244
  return %w[bundle install] unless templating_bootstrap_lockfile_ready?(project_root)
226
245
 
227
- bundle_update_templating_bootstrap_command(project_root)
246
+ bundle_update_templating_bootstrap_command(project_root, env: env)
228
247
  end
229
248
 
230
249
  def templating_bootstrap_step_name(project_root = Dir.pwd)
@@ -18,7 +18,7 @@ module Kettle
18
18
  recipe_reports: []
19
19
  }
20
20
  else
21
- Kettle::Jem.apply_project(project_root, env: env, run_options: effective_run_options)
21
+ Kettle::Jem.apply_project(project_root, env: env, run_options: effective_run_options.merge(defer_drift_check: true))
22
22
  end
23
23
  setup_env = Kettle::Jem::Tasks::InstallTask.setup_command_env(project_root, env)
24
24
  hook_step = Kettle::Jem::Tasks::InstallTask.hook_templates_step(project_root, effective_run_options)
@@ -33,7 +33,22 @@ module Kettle
33
33
  command_runner: command_runner,
34
34
  event_phase: "template"
35
35
  )
36
- final_report = report.merge(mode: "template", template_steps: template_steps)
36
+ orchestration_changed_files = template_steps.flat_map do |step|
37
+ Array(step[:changed_files] || step["changed_files"])
38
+ end
39
+ changed_files = (Array(report[:changed_files]) + orchestration_changed_files).map(&:to_s).uniq.sort
40
+ final_report = report.merge(
41
+ mode: "template",
42
+ template_steps: template_steps,
43
+ changed_files: changed_files
44
+ )
45
+ if report.key?(:duplicate_drift)
46
+ final_report = Kettle::Jem.finalize_duplicate_drift(
47
+ project_root: project_root,
48
+ report: final_report,
49
+ run_options: effective_run_options
50
+ )
51
+ end
37
52
  Kettle::Jem.emit_summary_event(Kettle::Jem.event_stream_from_options(effective_run_options), final_report)
38
53
  final_report
39
54
  end
@@ -90,7 +105,7 @@ module Kettle
90
105
  return standalone_limit unless wave_jobs
91
106
 
92
107
  available_per_member = (cpu_count - wave_jobs) / wave_jobs
93
- [standalone_limit, [available_per_member, 1].max].min
108
+ available_per_member.clamp(1, standalone_limit)
94
109
  end
95
110
 
96
111
  def family_wave_jobs(env)
@@ -3,7 +3,6 @@
3
3
  # Fail on error and unset variables
4
4
  set -eu
5
5
 
6
- # Run the generated wrapper directly so hook execution does not depend on shell activation.
7
- # We are not using direnv exec here because mise and direnv can result in conflicting PATH settings:
8
- # See: https://mise.jdx.dev/direnv.html
9
- exec "kettle-commit-msg" "$@"
6
+ # Resolve the installed gem executable through RubyGems, not a repository-local
7
+ # Bundler binstub that may select a temporarily inconsistent project lockfile.
8
+ exec env RUBYOPT= ruby -e 'require "rubygems"; load Gem.bin_path("kettle-dev", "kettle-commit-msg")' "$@"
@@ -59,7 +59,7 @@ jobs:
59
59
  persist-credentials: false
60
60
 
61
61
  - name: Setup Ruby & RubyGems
62
- uses: appraisal-rb/setup-ruby-flash@925395edf973d2dc0a629919f407f3547a03d4b5 # v2.1 compatibility bundle installation
62
+ uses: appraisal-rb/setup-ruby-flash@6f8ad36ba7488db591541483afb5464d01e8861b # v2.14 compatibility bundle installation
63
63
  with:
64
64
  ruby-version: "${{ matrix.ruby }}"
65
65
  rubygems: "${{ matrix.rubygems }}"
@@ -29,19 +29,15 @@ concurrency:
29
29
  jobs:
30
30
  test:
31
31
  if: "github.event_name != 'push' || (!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]'))"
32
- name: Specs ${{ matrix.ruby }}@${{ matrix.appraisal }}
33
- runs-on: ubuntu-latest
32
+ name: Specs ${{ matrix.os }} ${{ matrix.ruby }}@${{ matrix.appraisal }}
33
+ runs-on: ${{ matrix.os }}
34
34
  env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps
35
35
  BUNDLE_GEMFILE: ${{ github.workspace }}/Appraisal.root.gemfile
36
36
  strategy:
37
37
  matrix:
38
38
  include:
39
39
  # Ruby 4.0
40
- - ruby: "ruby"
41
- appraisal: "current"
42
- exec_cmd: "{KJ|CI:EXEC_CMD}"
43
- rubygems: latest
44
- bundler: latest
40
+ {KJ|CI:OS_MATRIX_ENTRIES}
45
41
 
46
42
  steps:
47
43
  - name: Checkout
@@ -50,7 +46,7 @@ jobs:
50
46
  persist-credentials: false
51
47
 
52
48
  - name: Setup Ruby & RubyGems
53
- uses: appraisal-rb/setup-ruby-flash@925395edf973d2dc0a629919f407f3547a03d4b5 # v2.1 compatibility bundle installation
49
+ uses: appraisal-rb/setup-ruby-flash@6f8ad36ba7488db591541483afb5464d01e8861b # v2.14 bundle validation and compatibility installation
54
50
  with:
55
51
  ruby-version: ${{ matrix.ruby }}
56
52
  rubygems: ${{ matrix.rubygems }}
@@ -77,10 +73,9 @@ jobs:
77
73
  uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
78
74
  with:
79
75
  path: .rspec_status
80
- key: rspec-status-current-${{matrix.ruby}}-${{matrix.appraisal}}-${{hashFiles('**/Gemfile.lock','Appraisal.root.gemfile','gemfiles/**/*.gemfile')}}-${{github.run_id}}-${{github.run_attempt}}
76
+ key: rspec-status-current-${{matrix.os}}-${{matrix.ruby}}-${{matrix.appraisal}}-${{hashFiles('**/Gemfile.lock','Appraisal.root.gemfile','gemfiles/**/*.gemfile')}}-${{github.run_id}}-${{github.run_attempt}}
81
77
  restore-keys: |
82
- rspec-status-current-${{matrix.ruby}}-${{matrix.appraisal}}-${{hashFiles('**/Gemfile.lock','Appraisal.root.gemfile','gemfiles/**/*.gemfile')}}-
83
- rspec-status-current-${{matrix.ruby}}-${{matrix.appraisal}}-
78
+ rspec-status-current-${{matrix.os}}-${{matrix.ruby}}-${{matrix.appraisal}}-${{hashFiles('**/Gemfile.lock','Appraisal.root.gemfile','gemfiles/**/*.gemfile')}}-
79
+ rspec-status-current-${{matrix.os}}-${{matrix.ruby}}-${{matrix.appraisal}}-
84
80
 
85
- - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
86
- run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
81
+ {KJ|CI:OS_TEST_STEPS}