opencode-ruby-upgrader 0.1.0

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.
@@ -0,0 +1,186 @@
1
+ # End-to-End Fixture Evidence
2
+
3
+ This file records reproducible validation of the packaged plugin against a disposable, publicly licensed Rails fixture. It is not a claim that arbitrary Ruby or Rails upgrades are safe.
4
+
5
+ ## At a glance
6
+
7
+ - **Outcome:** the fixture now runs at the pinned target **Ruby 3.4.10 / Rails 7.1.6** (from Ruby 2.4.10 / Rails 4.2.11.3).
8
+ - **How:** 15 receipt-backed hops (8 Ruby, 7 Rails-bridge), each validated with `bundle exec rspec` in an isolated Docker container before its local checkpoint commit.
9
+ - **Where:** branch `ruby-upgrade/e2e-3.4` of the fixture repo, as [pull request #1](https://github.com/lilla021/ruby2-rails4-bootstrap-heroku/pull/1) — GitHub Actions lint and Ruby-spec checks currently pass.
10
+ - **Baseline:** fixture commit `bad95e2be88687f5d185c29a2361526fa05b8f54` (BSD-2-Clause).
11
+
12
+ The sections below record each stage of that work, including the deliberate stops, defects found in the agent itself, and the fixes. What this does and does not prove is summarised in [Evidence limits](#evidence-limits).
13
+
14
+ ## Fixture contract
15
+
16
+ - **Repository:** `https://github.com/lilla021/ruby2-rails4-bootstrap-heroku` (fork of `diowa/ruby2-rails4-bootstrap-heroku`)
17
+ - **Fixture commit:** `bad95e2be88687f5d185c29a2361526fa05b8f54`
18
+ - **License:** BSD-2-Clause
19
+ - **Baseline:** Ruby `2.4.10`, Rails `4.2.11.3`, Bundler lockfile `1.17.3`
20
+ - **Disposable worktree branch:** `ruby-upgrade/e2e-3.4`
21
+ - **Plugin under test:** record the reviewed plugin commit SHA and packed tarball integrity before each run.
22
+
23
+ ## 2026-09-17 host preflight
24
+
25
+ | Check | Result |
26
+ | --- | --- |
27
+ | Linked-worktree preflight | Passed after configuring `opencode-ruby-upgrader.defaultBranch=main` locally in the fixture repository. |
28
+ | Plugin dry-run inventory | Passed: Rails/RSpec detected; Ruby `2.4.10`; Rails `4.2.11.3`; 118 locked dependencies; no detected private sources. |
29
+ | Host runtime | Blocked as expected: host Ruby is `2.6.10`, but fixture requires `2.4.10`; installed Bundler is `1.17.2`, while the lockfile specifies `1.17.3`. |
30
+ | Dependency install / test suite | Not run. A container or version manager providing the exact historical runtime is required. |
31
+ | Container runtime | Blocked: Docker CLI is installed, but the local Docker daemon was unavailable at `unix:///Users/lilla/.docker/run/docker.sock`; no containers or fixture code were started. |
32
+
33
+ ## 2026-09-18 container baseline — in progress
34
+
35
+ | Check | Result |
36
+ | --- | --- |
37
+ | Ruby container | In progress using the pinned `ruby:2.4.10` image against the disposable fixture worktree. |
38
+ | Bundler | `1.17.3` was installed in the container and dependency setup proceeded to the database task. |
39
+ | `bundle exec rake db:create` | Blocked before database creation: Rails/ExecJS reported `Could not find a JavaScript runtime.` The fixture has asset gems that require ExecJS but declares no JavaScript runtime. |
40
+ | Runtime-package setup | Initial `apt-get update` failed as expected for the end-of-life Debian Buster base: the former `security.debian.org` and `deb.debian.org` Buster release endpoints returned HTTP 404. No packages were installed. |
41
+ | Remediation | Pending: point APT in this disposable container only at Debian's signed Buster archive, install a JavaScript runtime (Node.js), record its exact version, then rerun the unchanged database command. No fixture source or lockfile change is authorized for this environment prerequisite. |
42
+
43
+ Do not treat this as an application or plugin failure until the pinned runtime prerequisite has been supplied and the command has been rerun. Record the command result, timestamps, exit code, sanitized output digest, and worktree fingerprint after the rerun.
44
+
45
+ ## 2026-09-18 baseline RSpec — in progress
46
+
47
+ | Check | Result |
48
+ | --- | --- |
49
+ | Initial RSpec execution | Reached RSpec after the JavaScript-runtime remediation, then stopped in `before(:suite)` before any examples ran. |
50
+ | DatabaseCleaner safeguard | Blocked truncation because the container database URL uses the Docker service hostname rather than `localhost`/`127.0.0.1`. Result: 0 examples, 0 example failures, 1 setup error. This is an expected topology-recognition safeguard, not a test assertion result. |
51
+ | Scoped remediation | The database hostname was verified as the expected isolated Docker service. RSpec was rerun for that process only with `DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL=true`; fixture specs and persistent configuration were unchanged. |
52
+ | Baseline RSpec result | Passed: 1 example, 0 failures; randomized seed `26029`; total execution 26.17 seconds after 18.04 seconds of file loading. The `Welcome Index has application name in title` feature example took 25.98 seconds. |
53
+ | Coverage result | SimpleCov generated `/app/coverage` and reported 100.0% line coverage (8/8) for the exercised fixture code. |
54
+
55
+ The terminal transcript did not preserve start/finish timestamps or a sanitized-output SHA-256 for this manually executed baseline. Treat the result as successful baseline evidence with that metadata gap; capture the complete receipt metadata through the plugin executor before any checkpoint.
56
+
57
+ ## Receipt-executor environment gap
58
+
59
+ The current plugin executor intentionally permits only fixed, host-executed argv such as `bundle exec rspec`; it does not support Docker execution. The host Ruby/Bundler do not satisfy this fixture's pinned Ruby `2.4.10` / Bundler `1.17.3` baseline, while the passing baseline command ran inside an isolated container. Therefore this manual baseline must not be represented as a plugin-generated validation receipt and cannot authorize a checkpoint. A completed package E2E must either provide a constrained, receipt-producing container executor or run the fixed executor in a host environment that exactly satisfies the fixture runtime contract.
60
+
61
+ The local package worktree now contains a constrained `docker-bundle-rspec` executor and regression coverage. It inspects a named running container, requires the fixture worktree to be bind-mounted at `/app`, requires `RAILS_ENV=test` plus `DATABASE_URL`, and executes only fixed Docker argv ending in `bundle exec rspec`. It records container image metadata and a redacted output digest in the receipt. `npm test` passed 32/32 after this change. This is implementation evidence only; the adapter has not yet been exercised against the fixture through the OpenCode runtime, so no plugin-generated fixture receipt exists yet.
62
+
63
+ ## 2026-09-18 OpenCode preflight — blocked, recoverable
64
+
65
+ An explicit local-plugin preflight returned `ok: false` with `reason: "dirty-worktree"` for branch `ruby-upgrade/e2e-3.4` at fixture commit `bad95e2be88687f5d185c29a2361526fa05b8f54`; its configured default branch was correctly detected as `main`. The only observed worktree change was an untracked `LEARNING_PATH.md` created by the OpenCode session, not fixture work. Remove that generated file and rerun preflight before any plugin lifecycle action. The package was also corrected so its injected agent invokes the package-local Node CLI rather than assuming the package binary is globally on `PATH`; `npm test` remained 32/32 and `git diff --check` passed after that correction.
66
+
67
+ ## Fixture reset attempt — in progress
68
+
69
+ The disposable worktree was recreated at the pinned baseline and the PostgreSQL/Ruby containers were recreated. The first rerun of `bundle exec rake db:create` again stopped at `ExecJS::RuntimeUnavailable`. This is not yet evidence of a fixture change or database failure: the container must first prove that the installed Node.js package exposes an executable name discoverable by ExecJS (`node`). The next diagnostic records only runtime command availability/version; no Gemfile, lockfile, or application configuration change is authorized.
70
+
71
+ After installing Node.js in the recreated container, `bundle exec rake db:create` completed. Rails emitted the legacy `PGconn`, `PGresult`, and `PGError` deprecation warning from the pinned Rails 4.2 / PostgreSQL adapter stack; the test database `ruby2-rails4-bootstrap-heroku_test` was confirmed to already exist. Treat this as a successful environment/database prerequisite with a known historical dependency warning, not as an application test result.
72
+
73
+ The rebuilt baseline RSpec command then passed with the Docker-only DatabaseCleaner safeguard override: 1 example, 0 failures, seed `10316`, 25.08 seconds total after 17.49 seconds loading files. The `Welcome Index has application name in title` feature example took 24.89 seconds. SimpleCov generated `/app/coverage` and reported 100.0% line coverage (8/8) for the exercised fixture code. The Rails 4.2 PostgreSQL-constant deprecation warning repeated during test loading; no test assertion failed.
74
+
75
+ After the reset, local plugin preflight passed with `ok: true` in linked-worktree mode for `ruby-upgrade/e2e-3.4` at `bad95e2be88687f5d185c29a2361526fa05b8f54`. The fixture worktree was clean; its Git worktree metadata was distinct from the primary checkout's common Git directory.
76
+
77
+ ## 2026-09-18 plugin no-write plan
78
+
79
+ The `/ruby-upgrade --dry-run --target 3.4` workflow completed without creating files, locks, or commits. It detected Rails `4.2.11.3`, Ruby `2.4.10`, RSpec (`bundle exec rspec`), 118 Rubygems-only dependencies, and standard Git topology (no shallow clone, sparse checkout, submodules, or LFS). It pinned Ruby `3.4.10` for the requested Ruby 3.4 target and planned contiguous Ruby series `2.5 → 2.6 → 2.7 → 3.0 → 3.1 → 3.2 → 3.3 → 3.4`. The plan correctly requires official Rails/Ruby compatibility research and warns that Rails 4.2 may require a separately approved Rails bridge. This is no-write planning evidence, not a validation receipt or checkpoint.
80
+
81
+ ## Research lifecycle defect and correction
82
+
83
+ A durable research-only run was created and safely paused without fixture edits, dependency changes, tests, or checkpoints. It correctly retained official Ruby/Rails citations but exposed a state-machine defect: the Ruby ladder validator rejected the valid published `2.7 → 3.0` transition by assuming every hop increments only the minor number. The package now permits the known Ruby major-series boundaries `2.7 → 3.0` and `3.4 → 4.0`, while retaining rejection of an invalid `2.6 → 3.0` skip. A regression test covers both the valid and invalid cases; `npm test` passed 33/33 and `git diff --check` passed. The paused fixture report must be resumed under the reloaded local plugin before recording its ladder.
84
+
85
+ The first resume attempt exposed a second lifecycle defect: preflight treated the plugin's own uncommitted `.ruby-upgrades/runs/...` durable report as a dirty worktree and therefore prevented resume. Preflight now excludes only `.ruby-upgrades` from its worktree-dirt check, matching the evidence exclusion already used by the receipt fingerprint. It continues to reject ordinary project or unrelated untracked changes. A regression test proves both behaviors; `npm test` again passed 33/33 with `git diff --check` clean. Reload the local plugin before resuming the paused fixture report.
86
+
87
+ After reload, the fixture run resumed successfully and was paused again at `research_complete`. Its durable report is `.ruby-upgrades/runs/2026-09-19T02-11-30-339Z-e27e872c-89d8-45e6-912a-246275fc00fc.json`; it records the full Ruby ladder `2.4 → 2.5 → 2.6 → 2.7 → 3.0 → 3.1 → 3.2 → 3.3 → 3.4`, official Ruby release citations, and the endoflife.date cross-check. No project file edits, test executions, or checkpoints occurred in this research-only lifecycle.
88
+
89
+ The paused run then recorded a Rails compatibility decision for Ruby `2.4 → 2.5`: Rails `4.2.11.3` does not declare an upper Ruby bound and its gemspec declares Ruby `>= 1.9.3`, so no Rails bridge is required for this first hop. Evidence links the Rails 4.2.11.3 gemspec, Rails 4.2 release notes, and Rails maintenance policy. Rails 4.2 end-of-life remains a later migration risk. No application files, tests, or checkpoints were created by this decision.
90
+
91
+ When authorized to begin the first hop, the agent paused before edits because the selected validation container was still pinned to Ruby `2.4.10`, not the proposed target Ruby `2.5.9`. It correctly refused to use the baseline runtime as target-runtime validation evidence. No project files, dependency changes, validation receipts, or checkpoints were created. A separate Ruby 2.5.9 test container, bound to the same fixture worktree and isolated PostgreSQL service, is required before the hop can proceed.
92
+
93
+ ## Guided Docker runtime preparation — implementation verified
94
+
95
+ Manual target-container setup was identified as unacceptable normal-user UX. The local package now provides a confirmation-gated `prepare-target-runtime --ruby <x.y.z>` path that derives the single active/paused run, creates per-run labelled Docker network, PostgreSQL, and Ruby containers with fixed argv, bootstraps Node.js, Bundler `1.17.3`, dependencies, and the isolated Rails test database, then persists only non-secret runtime identity and preparation digests in `.ruby-upgrades/runtime.json`. It attests the exact Ruby version and resolved image ID, and the Docker RSpec executor revalidates that manifest rather than accepting container names or environment variables from the user. Regression coverage covers provisioning, secret exclusion, Ruby/image attestation, mutable-image drift, manifest-backed validation, ambiguity, and removal of the environment-variable route. `npm test` passed 37/37; both staged and unstaged diff checks passed. This feature has not yet been exercised in the fixture runtime.
96
+
97
+ After reset, the normal `/ruby-upgrade --target 3.4` entry point completed preflight, inventory, target pinning, and ladder planning, then paused with a single plain-language approval request for isolated Ruby `2.5.9` runtime preparation. It created report `.ruby-upgrades/runs/2026-09-19T03-01-16-089Z-b8999214-ad0e-4cea-abb1-8f3501e4f1c7.json`; no user-supplied report path, container name, or Docker environment variable was required. Guided runtime preparation remains pending fixture execution.
98
+
99
+ The first guided preparation attempt paused safely before fixture edits when PostgreSQL startup and historical dependency bootstrap exceeded the agent's default 120-second command limit. This exposed a UX defect: preparation must own long-running readiness/bootstrap behavior rather than delegate Docker troubleshooting to the user. The local implementation now derives the image Debian codename for its archived Node.js setup instead of assuming Stretch, and directs the agent to use a preparation timeout of at least 15 minutes. `npm test` remained 37/37 with a clean diff check. The fixture preparation must be retried under the reloaded local plugin.
100
+
101
+ A later retry session found no durable report under `.ruby-upgrades/runs/`, so preparation correctly made no Docker or fixture change but incorrectly asked the user to restore/provide an internal report path. Agent guidance now treats absent local evidence as a normal restart condition: it directs the user to the public `/ruby-upgrade --target <version>` entry point instead of exposing internal state-management details.
102
+
103
+ On the subsequent normal entry-point retry, preflight, version pinning, exact patch ladder, research, and a new paused durable report succeeded. The agent then incorrectly treated the report it had just created under `.ruby-upgrades` as an untracked-worktree blocker. Agent guidance now explicitly excludes only this plugin-owned evidence directory from its clean-tree review and continues to stop for all other project changes.
104
+
105
+ When preparation was approved, multiple paused reports caused another internal-state leak. Target-runtime preparation now accepts the report path held by the agent internally and validates it is active/paused; agent guidance retains stale evidence but selects the most recently started paused report matching the current branch and target. The user is never asked to identify a report, run ID, container, or manifest. Package regression tests passed 37/37 after this correction.
106
+
107
+ ## Receipt-backed Ruby 2.5.9 hop — validated, not committed
108
+
109
+ The newest paused run was resumed and selected internally. Real fixture execution exposed and corrected four preparation defects: PostgreSQL readiness was checked only once; retry without a manifest replaced the app container and discarded native-gem build progress; dependency bootstrap ran before changing the mounted Gemfile's Ruby declaration; and Docker RSpec omitted the process-scoped DatabaseCleaner override required for the verified isolated database hostname. Regression coverage now includes retry reuse; the package suite passes 38/38 with a clean diff check.
110
+
111
+ After changing only `Gemfile` (`2.4.10` → `2.5.9`) and the lockfile `RUBY VERSION` (`ruby 2.4.10p364` → `ruby 2.5.9p229`), guided preparation completed. It attested image `ruby:2.5.9` at `sha256:ecc3e4f5da13d881a415c9692bb52d2b85b090f38f4ad99ae94f932b3598444b`; Node, Bundler 1.17.3, dependency installation, database creation, and Ruby-version outputs are represented only by SHA-256 and byte-count metadata in `.ruby-upgrades/runtime.json`.
112
+
113
+ Plugin-generated validation receipt `8fbc107c-ed9f-4324-800d-10856a57a114` passed with fixed command ID `docker-bundle-rspec`: 1 example, 0 failures, 33.977 seconds, exit code 0, no timeout, redacted-output SHA-256 `7377bab5fb106b6c2fb5069e9dba35a3330a11fa1a4f2a3f60c54733c481aeaf`, and worktree diff fingerprint `1f2d8c27dce4994cffa4b02419e7933b8605e53bac5d6cdec59d9cf90ebef12d`. The existing browser-backed Welcome feature supplied the smoke coverage. The report reached `hop_validated` and was paused; no checkpoint commit was created.
114
+
115
+ The user then approved a local checkpoint. Real gate execution exposed two further defects: unrelated local `.ruby-upgrades` artifacts were staged alongside active evidence, and the official `https://rubygems.org/` lockfile remote was falsely classified as private due to regex backtracking. The gate now unstages machine-local/stale evidence while retaining the active report and still rejecting undeclared project changes; lockfile remotes are explicitly extracted and exactly allowlisted. Regression coverage raised the package suite to 40/40. The guarded checkpoint succeeded at `e0fb0d2f7494d10cf22a73512efa1b7584bf89d0` with validation receipt digest `c77987568ea05855e451b179892376f0ec751ff459c0dba53588edfd47e11821`; no push occurred.
116
+
117
+ The next Ruby `2.5.9 → 2.6.10` hop then prepared image `ruby:2.6.10` at `sha256:a79c8ddb7f3d3748427e2d3a45dcae6d42f1d80d9ae3b98959b3a27b220bf434`. Only `Gemfile` and lockfile Ruby-version metadata changed. Receipt `a34b712a-beba-40d5-ac14-0ced29f24221` passed: 1 example, 0 failures, 38.87 seconds, exit code 0, no timeout, redacted-output SHA-256 `f79604368abd773589f733c5a408f73a4eab37ddf99c93a81dd79ebc1ee78db5`, and worktree diff fingerprint `2c2f9b3a8c0609c4f0b3562bbfd91826c699ab9c3724fa5a5b7fc9c7f95a75dc`. The run is paused at `hop_validated`; this second hop is not committed.
118
+
119
+ After approval, the second guarded checkpoint succeeded at `e8e1ce821033336c7ac68dc9082f595ff43453e5` with validation receipt digest `e55f6f03c0c34891e74e8f305ddba05149255c907cd9ea20ee78d9500eed34ab`; no push occurred. The next Ruby `2.6.10 → 2.7.8` preparation installed dependencies but failed during Rails test-database initialization: Rails `4.2.11.3` invokes removed `BigDecimal.new` while booting under Ruby 2.7.8. No validation receipt or checkpoint was created for Ruby 2.7.8. The attempted Gemfile and lockfile runtime declarations were reverted to checkpointed Ruby 2.6.10, and the run was safely paused pending an explicitly scoped Rails bridge decision.
120
+
121
+ The user approved a separate Rails `4.2.11.3 → 5.2.8.1` bridge through `5.0.7.2` and `5.1.7`. The Ruby report was marked terminal `blocked`, and Rails bridge report `.ruby-upgrades/runs/2026-09-23T04-47-06-275Z-e4485288-4c7f-4271-9345-e6dd7066618f.json` was initialized with the official Rails upgrade guide and Rails 5.2.8.1 gemspec citations. Package lifecycle changes allowing post-checkpoint bridge creation and Docker-executed `app:update` passed 41/41 tests.
122
+
123
+ For the first Rails hop, focused `bundle update rails` resolved Rails `5.0.7.2` but also updated expected transitive framework dependencies. Receipt `53b0e092-d6a4-43af-88b6-6df300535bfa` records successful Docker execution of `bin/rails app:update` in 13.99 seconds, output digest `c288def7b0293a656dafec571d3a4a6413c72ec02042ca466403a47e96ee732a`, and worktree fingerprint `85214e8f9bed2f509fb7cc136890da453c1171e188f64e008e46d142e2d9846c`. Review found unsafe noninteractive overwrites across routes, production SSL/logging, secrets, Puma, environments, initializers, and generated framework files (24 tracked files plus new files). No app-update review approval, final test receipt, or Rails checkpoint exists. This demonstrates that noninteractive conflict handling must be redesigned before Rails bridge automation can be considered safe.
124
+
125
+ The unsafe project diff was fully reverted, and the bridge report durably retained that receipt under `discardedAppUpdates` with the review reason rather than silently replacing the evidence. The package now exposes a confirmation-gated discard command. Rails 5.0 implements `app:update` as a Rake task and rejects a generic `--skip` option, so the executor now invokes the same Rails app generator directly with Thor `behavior: :skip`. This preserves every existing application file while generating only missing framework files. Regression assertions cover the fixed Docker argv, skip behavior, discard permission, and package suite; `npm test` passes 41/41 and `git diff --check` is clean.
126
+
127
+ The clean Rails `4.2.11.3 → 5.0.7.2` retry produced app-update receipt `da7a0510-6cac-4ac4-9a83-a6b517f140d9`, exit code 0, 7.363 seconds, redacted-output SHA-256 `db58f370856adee17daa5cf88a85855a7f9e0774714e56e4862f5a0041761edf`, and review fingerprint `aefd04b0f1ea83f6b191cb5e865108b5879a175b82eb1c7f47646091036e5f5b`. Review accepted only five missing Rails 5 files: `bin/update`, `config/cable.yml`, `config/spring.rb`, `config/initializers/application_controller_renderer.rb`, and `config/initializers/new_framework_defaults.rb`; no existing application configuration was overwritten. Because the newly resolved Sprockets 4 requires an explicit asset graph that the older Rails 5.0 template predates, the hop also adds `app/assets/config/manifest.js`.
128
+
129
+ Final receipt `faff9f6d-5771-4cf9-9d68-7e138a6b0504` then passed in the attested Ruby `2.6.10` container: 1 example, 0 failures, 57.247 seconds, exit code 0, no timeout, redacted-output SHA-256 `e09e7eeaa0e55f9e2204046ddce004933d906140b5a8ab3e40243f92c2b4c257`, and final worktree fingerprint `7e2c0cd085e3ba91bf9a058b5a4282c6b3ca57b5c99564ff34eb18c5ee68031b`.
130
+
131
+ The first commit attempt was correctly blocked because compatibility and license findings for the changed lockfile had not been durably recorded. The package now provides a lock-bound `record-dependency-review` operation that is valid only for the latest validated, uncommitted hop; regression coverage raised the suite to 42/42. After recording fixture-scoped compatibility findings, the public Rubygems-only source review, and the absence of an automated license-audit adapter, the guarded local checkpoint succeeded at `44f92460c11c05a4e907c9960cb65fbbe72debc5` with validation receipt digest `91ed1c19bd7bb5d6ff90a43c8312918aea6d15b7b37ae7b295a493c4427995a3`. The bridge is now at `committed`; no push occurred.
132
+
133
+ The Rails `5.0.7.2 → 5.1.7` hop resolved the contiguous framework set and ran conflict-skipping app update receipt `8485c0d2-b62c-4f7e-b6e7-95656a47c60d`: exit code 0, 7.510 seconds, output digest `2da549a660f7e340732998573f4fa2df663a241cb142a4faf40b7131d102cd9d`, and review fingerprint `cd240f05337001bd7a8f3bb596aa65c52e9ec8e24f19e01a8feb6676f23178bf`. Review accepted only the missing `bin/yarn` and `config/initializers/new_framework_defaults_5_1.rb`; all existing configuration was preserved. The first final test exposed the Rails 5.1 removal of `ActiveRecord::Base.raise_in_transactional_callbacks=`. Removing that obsolete fixture setting from `config/application.rb` restored boot.
134
+
135
+ Final receipt `3a207665-b8dd-4f9b-8343-8607f4a12e61` passed: 1 example, 0 failures, 69.413 seconds, exit code 0, no timeout, output digest `f3a91b6d4fec5e9e22e4e77c277e22782c68d033391deccf50b2b5d22ee185ff`, and final fingerprint `253a03fc5ef01166f73668793c9bea730fe7554fd522f06544880ca2a55c7e9a`. Dependency compatibility and fixture-scoped license findings were recorded. The guarded local checkpoint succeeded at `a7225de6a5daed2137da51277248ace2eced3872` with receipt digest `411552bed8bb71e809fde666def39889931edea812aef37bfde00ffe3d33c945`; no push occurred.
136
+
137
+ The Rails `5.1.7 → 5.2.8.1` app-update receipt `53e7bed3-7590-49df-8ef1-e05a876a1ead` completed in 10.169 seconds with output digest `27c41213cbfd9bb86ec11ddf3fb69cbe7fba21c7cc31f2fd405827d649d0d6e9` and review fingerprint `34dd27ab4bb00fab5b2ec940754d24d1dc98388e06343e6f7c6574eb414e3c1b`. Review accepted only new CSP, Rails 5.2 defaults, and Active Storage templates. Initial final tests exposed and then removed two obsolete Rails 5.0 compatibility settings: `halt_callback_chains_on_return_false` and `raise_on_unfiltered_parameters`. No existing configuration was overwritten by the generator.
138
+
139
+ Final receipt `89309588-9032-428d-b05a-895f2c54445d` passed: 1 example, 0 failures, 44.382 seconds, exit code 0, no timeout, output digest `085418c713d7941e6bf4b2b9456bb4a17baa98b3c9f9cb376a7d8396e09a7ca7`, and final fingerprint `f3a755912c3bd311d067d74017822a067e18ddafcc79b9296afdda6a34ece969`. Dependency findings were recorded. The final guarded checkpoint succeeded at `9cc80923ea7a08fcaa8bcf8cf627ca38a72324b5` with receipt digest `b71e2c9f82cc7b4b7d3ff12eb640843d8adffa005a3cc2357cfae531e6822f53`. The Rails bridge is terminal `complete` at Rails `5.2.8.1`; no push occurred.
140
+
141
+ ## 2026-09-24 Rails bridge completion: 5.2.8.1 → 7.1.6
142
+
143
+ The Ruby run previously blocked at Ruby 3.0.7 because Rails 5.2 cannot boot on Ruby 3.0. A separate Rails bridge run (`2026-09-24T02-48-20-249Z-3047fe86-7e3d-4d49-9226-2836c7418441.json`, runId `41f40bc2-9321-483a-afc7-3e1266a5e99d`) carried the framework set to `7.1.6` in four receipt-backed hops, all validated in the Ruby 2.7.8 container.
144
+
145
+ **Hop 5.2.8.1 → 6.0.6.1** (checkpoint `cbb0805dc037ad3f40fb8e157ac543768a623b25`): `app:update` exposed a real Rails-6 load-order defect — `ActiveSupport::LoggerThreadSafeLevel` evaluates `Logger::Severity` before `require "logger"`. Fixed via `gem "logger", "~> 1.4"` plus `require "logger"` in `config/application.rb` (Rails 7.1 later made `logger` a permanent default). Only `new_framework_defaults_6_0.rb` was added; app-update review receipt `89dc71f7-8fc4-4b39-8b63-b04d1c09f3dc`.
146
+
147
+ **Hop 6.0.6.1 → 6.1.7.10** (checkpoint `d3cbd3bc89d72581aeb9d86a5519f24d40534d16`): Rails 6.1's pg adapter requires `pg ~> 1.1`; the fixture pinned `0.21.0`. Bumped to `~> 1.5.0` (1.6.x needs Ruby ≥ 3.0, correctly rejected). Receipts `c09af52b…` and final `e398645d-4768-491d-a1f4-13386e74a4fb` (35.445s PASS).
148
+
149
+ **Hop 6.1.7.10 → 7.0.10** (checkpoint `8aeac2a4bb8b`): forced a theme-gem swap. `twbs_sass_rails` caps at `rails < 6.2` and is unmaintained; replaced with `bootstrap-sass 3.4.1` (no Rails cap, needs `sassc ≥ 2.0`) plus `font-awesome-rails 4.7`, renaming imports (`twbs/` → bootstrap-sass conventions) and fixing an `@import "bootstrap"` self-loop by naming the local manifest `bootstrap-manifest.scss`. Rubygems.org retired its legacy Dependency API, so Bundler was raised from `1.17.3` to `2.4.22` in the container (compact-index resolution of the new gems). Final receipt c291 / `…` PASS 43.0s.
150
+
151
+ **Hop 7.0.10 → 7.1.6** (checkpoint `1579d1073c21`, final): final bridge target. `app:update` added only `new_framework_defaults_7_1.rb`. Rails 7.1 removed `ActiveRecord::SchemaMigration.table_name`, breaking `database_cleaner` 1.99; replaced with `database_cleaner-active_record 2.2.2` and explicit `require "database_cleaner/active_record"` in spec support. loofah pin loosened `~> 2.20.0` → `~> 2.25` per rails-html-sanitizer 1.6. Final receipt PASS 37.8s.
152
+
153
+ **Two genuine gate recoveries this run (both required plugin fixes, not workarounds):**
154
+ 1. A stale `font-awesome-rails-4.7.0.9.gem` fetch artifact was removed from the worktree after final validation, tripping `validation-fingerprint-mismatch` — correctly, since the tree no longer matched the attested state. The state machine previously had no way back from `hop_validated` with a recorded-but-uncommitted iteration. Added a confirmation-gated, evidence-preserving `discard-last-rails-iteration` command that moves the iteration into a `discardedIterations` log and returns to `committed`; re-executing the hop re-validated cleanly and committed. Test coverage raised the suite to 43/43.
155
+ 2. The `stagedSecretPaths` gate misclassified staged *deletions* as credential material (it scanned deletion entries, whose blobs no longer exist, as hits) — a false positive that would have blocked any hop deleting a file. The scanner now parses `--name-status` tokens, skips deletions, and scans only the surviving path of renames.
156
+
157
+ The Rails bridge run is terminal `complete` at Rails `7.1.6` with 4 committed checkpoints; no push occurred.
158
+
159
+ ## 2026-09-24 E2E completion: Ruby 2.7.8 → 3.4.10
160
+
161
+ With Rails 7.1.6 committed, a fresh Ruby run (`2026-09-24T04-45-54-774Z-8c1fffdf-e098-4f8d-b8de-acb4dc5bf1e0.json`, runId `2cedd40f-8746-4762-9a4d-4a5557877993`) resumed the blocked ladder from the committed Ruby 2.7.8 checkpoint and carried it to the pinned E2E target **Ruby 3.4.10** in five receipt-backed hops. Every hop prepared a fresh attested container (`ruby:<ver>`, exact resolved image ID, Node.js, Bundler 2.4.22, dependency bootstrap, Rails test-database creation) and ran the Docker RSpec validation in the bind-mounted worktree.
162
+
163
+ - **2.7.8 → 3.0.7** (`ec9a1074`): no gem-set change; lockfile Ruby declaration only. Final receipt PASS.
164
+ - **3.0.7 → 3.1.7** (`f83fa8e6`): Ruby 3.1 demoted the `matrix` default gem; Capybara's `selector_query` requires it, so `matrix ~> 0.4` (0.4.3) was added to the test group.
165
+ - **3.1.7 → 3.2.11** (`1933961f`): Ruby 3.2 broke `Pry::Code`'s `=~` operator; dev/test group moved to the maintained `pry 0.14.2` line (pry-byebug 3.10.1, pry-rails 0.3.11).
166
+ - **3.2.11 → 3.3.12** (`8c30cf21`): no gem-set change; lockfile Ruby declaration only.
167
+ - **3.3.12 → 3.4.10** (`64c0b0b2`, final): Ruby 3.4 removed the `observer` default gem; `factory_bot 5.2.0` evaluation requires it, so `observer ~> 0.1` (0.1.2) was added to the dev/test group. Final Docker RSpec PASS 42.0s on the pinned target.
168
+
169
+ **Runtime-adapter fixes required during this run (with regression coverage, suite 43/43):** `prepare-target-runtime` still pinned Bundler `1.17.3`, which cannot resolve the modern compact index under Ruby 3.x; the bootstrap now installs and invokes `bundler 2.4.22` explicitly (`bundle _2.4.22_ install`). Its Debian Node.js setup hardcoded `archive.debian.org`, which serves only EOL releases; bookworm-based Ruby images (3.1.7+) fail against it, so the script now tries the live `deb.debian.org` mirror first and falls back to the archive.
170
+
171
+ The fresh Ruby run is terminal `complete` at **Ruby 3.4.10 / Rails 7.1.6** with 5 committed checkpoints; no push occurred. The E2E fixture has reached its pinned target.
172
+
173
+ ## Required proof for a completed fixture run
174
+
175
+ 1. Use an isolated container/VM with Ruby `2.4.10`, Bundler `1.17.3`, PostgreSQL, and the browser dependencies required by the fixture.
176
+ 2. Record image digest, OS, Ruby, Bundler, Node, PostgreSQL, and plugin tarball SHA-512.
177
+ 3. Run plugin preflight, inventory, supply-chain inspection, and the no-write plan.
178
+ 4. Capture a baseline executed validation receipt.
179
+ 5. Complete one reviewed, receipt-backed checkpoint hop; retain its report path, receipt digest, commit SHA, and trailers.
180
+ 6. For a Rails bridge, retain `app:update` receipt, reviewed diff manifest, final test receipt, bridge report, and Rails checkpoint SHA.
181
+ 7. Run the dashboard and confirm it renders Ruby/Rails targets, receipt metadata, and local commits without exposing sensitive values.
182
+ 8. Attach only sanitized report excerpts and command metadata here. Never commit fixture credentials, raw validation output, database dumps, or browser artifacts.
183
+
184
+ ## Evidence limits
185
+
186
+ The fixture proves only the recorded workflow against this pinned source tree and environment. It does not certify production behavior, security, dependency trust, deployment safety, or outcomes for another application.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 opencode-ruby-upgrader contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PRIVACY.md ADDED
@@ -0,0 +1,9 @@
1
+ # Privacy and Local Evidence
2
+
3
+ This package has no telemetry, analytics, or report-upload feature. It reads and writes migration evidence only in the current project worktree under `.ruby-upgrades/runs/` and serves the dashboard only on `127.0.0.1`.
4
+
5
+ Reports can contain target versions, branch names, commit SHAs, changed-file names, dependency source origins, citations, and bounded validation metadata. Absolute local paths and recognized credentials are redacted, but redaction is best-effort. Do not place secrets, customer data, database dumps, or raw command output in report fields.
6
+
7
+ Reports may be staged into local checkpoint commits. Review them before committing, pushing, sharing, or opening the dashboard on a shared machine. Delete `.ruby-upgrades/` when evidence retention is no longer needed. The dashboard has no authentication; other local processes able to reach your loopback interface may read its displayed report data.
8
+
9
+ Dependency installation, tests, and Rails tooling execute project-controlled code with your local user permissions after confirmation. Use an isolated environment for repositories you do not trust.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # opencode-ruby-upgrader
2
+
3
+ An evidence-driven Ruby and Rails migration agent for [OpenCode](https://opencode.ai). It upgrades a project one Ruby minor series at a time toward a researched latest-stable or explicitly pinned Ruby target, researches compatibility guidance, updates affected code and dependencies, and leaves a reviewable migration trail.
4
+
5
+ ## Quick start
6
+
7
+ From a checkout of the project you want to upgrade (shown with `main` as the default branch):
8
+
9
+ ```bash
10
+ # 1. Declare your default branch (the agent fails closed without this — it never guesses)
11
+ git config opencode-ruby-upgrader.defaultBranch main
12
+
13
+ # 2. Create the linked worktree the agent is allowed to work in
14
+ git branch ruby-upgrade/ruby-3.4
15
+ git worktree add ../<repo>-ruby-3.4 ruby-upgrade/ruby-3.4
16
+
17
+ # 3. Launch OpenCode from that worktree and start the migration
18
+ cd ../<repo>-ruby-3.4
19
+ opencode
20
+ ```
21
+
22
+ Then run `/ruby-upgrade` (or add `--dry-run` to get a no-write assessment first). The agent inventories the project, researches an official-source compatibility ladder, and proposes each validated hop as a local checkpoint commit for your review. See [Safety model](#safety-model) for what it will and will not do automatically.
23
+
24
+ > Requires Git 2.5+ (linked-worktree safety model) and, for legacy Ruby hops, Docker for the isolated validation container (see [Security boundaries](#security-boundaries)).
25
+
26
+ | Step | Who does it | Result |
27
+ |---|---|---|
28
+ | Set up worktree + config | You | Linked worktree, agent-ready |
29
+ | `/ruby-upgrade` | Agent | Inventories, researches, plans ladder |
30
+ | Validate a hop | Agent + Docker | Isolated `bundle exec rspec`, receipt recorded |
31
+ | Commit a hop | Agent proposes, **you approve** | Local checkpoint commit with receipt digest |
32
+ | Repeat | Loop | One minor series per hop |
33
+ | Review & push | **You** | `git log`/dashboard then push the validated branch |
34
+
35
+ ## Proof of work
36
+
37
+ The agent has completed a real end-to-end migration against a public fixture: [`ruby2-rails4-bootstrap-heroku`](https://github.com/lilla021/ruby2-rails4-bootstrap-heroku) (BSD-2-Clause) moved from **Ruby 2.4.10 / Rails 4.2.11.3** to **Ruby 3.4.10 / Rails 7.1.6** across 15 receipt-backed hops. Every hop was validated by `bundle exec rspec` in an isolated Docker container, then committed as a local checkpoint before the next hop began.
38
+
39
+ - [Upgrade pull request](https://github.com/lilla021/ruby2-rails4-bootstrap-heroku/pull/1) — the full migration: 19 commits, one per reviewed step, with lint and spec checks currently passing on GitHub Actions.
40
+ - [Evidence ledger](E2E_EVIDENCE.md) — every hop's validation receipt, commit SHA, and the fixes the migration required.
41
+
42
+ This proves the workflow works on a genuinely old, real-world Rails stack. It does not claim every upgrade is safe — see [Product limits](#product-limits).
43
+
44
+ ## Safety model
45
+
46
+ For Git repositories, the agent runs **only** from a linked Git worktree created by the user. Before starting, explicitly configure the repository default branch with `git config opencode-ruby-upgrader.defaultBranch main` (replace `main` as needed) — see [Quick start](#quick-start) for the three setup commands, which the agent also shows verbatim if you invoke it from a primary checkout. The upgrader fails closed if this configuration is absent and never guesses `main`, `master`, or a remote default. This keeps your normal checkout free for other work. In a non-Git project, it asks for confirmation before proceeding without worktree isolation or Git checkpoints. It never creates, switches, deletes, merges, pushes, or reconfigures branches/remotes. It also never publishes, deploys, or runs destructive database commands.
47
+
48
+ After every routine Ruby minor-version hop with passing validation, the agent proposes a **local** checkpoint commit through a guarded commit gate and OpenCode asks for confirmation. The gate verifies the linked worktree and non-default branch, exact expected Git history, an empty initial staging area, a complete passing report iteration, and scans staged content for likely credentials. It cannot push, fetch, alter remotes, switch branches, merge, rebase, reset, or amend history. You can review and push any validated checkpoint; a run becomes `complete` only once it reaches its pinned target.
49
+
50
+ The agent pauses—not guesses—when a migration involves data changes, authentication/authorization, payments, secrets, production configuration, framework-major upgrades, private dependencies, native extensions, or failed validation. Each pause includes evidence and practical options for continuing safely.
51
+
52
+ Every hop declares its expected changed files before commit. The commit gate blocks undeclared changes, credential-like material, executable Git hooks, non-RubyGems dependency sources, and large lockfile churn unless the user has explicitly reviewed and permitted that specific concern. Any changed lockfile also requires a recorded compatibility and license review. The target Ruby version is pinned with the research timestamp at run start, so a new upstream release cannot silently change the target mid-run. If Git author or commit-signing configuration prevents a commit, the agent reports the exact local setup issue and stops; it never changes Git configuration for you.
53
+
54
+ ## Install
55
+
56
+ During development:
57
+
58
+ ```json
59
+ { "plugin": ["file:///absolute/path/to/opencode-ruby-upgrader"] }
60
+ ```
61
+
62
+ After publishing:
63
+
64
+ ```json
65
+ { "plugin": ["opencode-ruby-upgrader"] }
66
+ ```
67
+
68
+ Restart OpenCode, then run `/ruby-upgrade` or select `@ruby-upgrade`.
69
+
70
+ Git 2.5 or newer is required for the linked-worktree safety model.
71
+
72
+ ## Evidence and dashboard
73
+
74
+ Every run has a generated JSON record and Markdown companion under `.ruby-upgrades/runs/`. Reports contain citations, version hops, dependency and code fixes, test/coverage metrics, smoke-test evidence, risks, and approved local commits. The directory is intentionally versionable and can be opened directly as an Obsidian vault.
75
+
76
+ Launch the local-only dashboard from the repository worktree:
77
+
78
+ ```bash
79
+ npx opencode-ruby-upgrader dashboard
80
+ ```
81
+
82
+ It binds exclusively to `127.0.0.1` on an ephemeral port and remains in the foreground until you stop it with Ctrl-C. The read-only dashboard displays valid Ruby and Rails-bridge reports plus locally discoverable checkpoint commits; it never changes reports, Git state, or uploads code.
83
+
84
+ The dashboard identifies local checkpoint commits from trailers embedded in those commits. Before the final push, inspect them locally with `git log`, `git show`, and the dashboard; after you push, the same individual commits are available for GitHub review.
85
+
86
+ ## Controls and recovery
87
+
88
+ Use `/ruby-upgrade --dry-run` for a no-write inventory and proposed migration assessment; it creates no report, lock, checkpoint, or durable research evidence. Use `/ruby-upgrade --target 3.4` to pin an explicit final Ruby version, or `/ruby-upgrade --stop-after-hop` to validate and commit one hop before stopping.
89
+
90
+ Each active run holds a local lock. To stop for review or manual work, transition it to `paused`; that releases the lock without marking the migration complete. Resume the existing report rather than starting a second migration:
91
+
92
+ ```bash
93
+ opencode-ruby-upgrader resume --report .ruby-upgrades/runs/<run>.json
94
+ ```
95
+
96
+ `complete`, `blocked`, and `paused` runs release their lock. A Ruby run blocked by an approved Rails bridge cannot be resumed: complete its linked Rails bridge, then start a fresh Ruby run. For other blockers, inspect the report and use the documented transition/resume path. To undo a completed hop, use the reviewable local history: `git revert <hop-sha>`. Do not use reset, rebase, or force-push as routine migration recovery.
97
+
98
+ If a resolved Rails version blocks the next Ruby hop, record the user-approved bridge, then transition the Ruby run to `blocked`. That Ruby report is terminal: complete the linked Rails lifecycle and start a fresh Ruby run. Every Rails iteration executes `bin/rails app:update` first, records its receipt, reviews that exact working-tree fingerprint, and only then runs final tests. Validate and checkpoint each one with `commit-rails-hop`.
99
+
100
+ ## Security boundaries
101
+
102
+ The agent defaults unknown shell commands to an OpenCode confirmation prompt. Git inspection is allowed, while direct Git mutation, GitHub CLI, publishing, and shell chaining/pipes/substitutions are denied. Dependency installation/updates, recognized tests, state writes, `commit-hop`, and `commit-rails-hop` require confirmation. This protects against accidental agent actions, not malicious project code: dependency installation and tests execute project-controlled code with your local user permissions. Use an isolated environment for repositories you do not trust, and review any command OpenCode asks you to approve.
103
+
104
+ New reports require `record-executed-iteration --validation <id>` or `record-executed-rails-iteration --validation <test-id>`; asserted results cannot be recorded or committed. The accepted IDs map to fixed no-shell commands: `bundle-rspec`, `bundle-rails-test`, `bundle-rake-test`, `bin-rails-test`, and, for Rails bridges, `rails-app-update`. For a legacy Ruby hop, the agent runs `prepare-target-runtime --ruby <x.y.z>` after selecting the exact target patch release. One confirmation provisions labelled per-run Ruby and isolated PostgreSQL Docker resources, installs Node, installs Bundler 1.17.3, runs `bundle install`, and, for Rails, creates the isolated test database. It writes nonsecret `.ruby-upgrades/runtime.json` with only safe preparation digests and the resolved image ID; raw output and `DATABASE_URL` are never persisted. `docker-bundle-rspec` reuses and verifies that manifest, including the exact requested Ruby execution and resolved image ID, before executing the fixed `docker exec --env DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL=true <container> bundle exec rspec`. The safeguard override is scoped to the verified isolated test process; no container name, report path, or environment value is needed from the user. Receipts persist only an output digest and byte count, plus structured test metrics; raw validation output is deliberately not committed. Each receipt also binds to a non-evidence working-tree fingerprint, which the commit gate rechecks after final validation. A checkpoint commit carries the receipt digest. This is tamper-evident provenance for a committed report, not protection against the same local user rewriting both evidence and Git history.
105
+
106
+ ## Product limits
107
+
108
+ The upgrader automates evidence collection and compatibility-oriented edits; it cannot prove production behavior, security correctness, deployment safety, or semantic equivalence. It intentionally pauses instead of modifying database behavior, authorization, payments, secrets, and production configuration without a user decision. Supported automatic adapters currently recognize Bundler projects using Rails, RSpec, or Minitest; other stacks receive an inventory and require a user-supplied validation command.
109
+
110
+ The credential scanner is heuristic: it recognizes common token formats and quoted credential-like assignments, but may miss other forms such as arbitrary unquoted YAML values. Run reports are local mutable JSON evidence, so their integrity is bounded by the user and local filesystem permissions rather than a tamper-proof store. Credential-bearing source URLs are redacted from supply-chain evidence. Gemfile source detection is static and may not resolve dynamically computed sources; review those manually and explicitly approve private sources.
111
+
112
+ Run the self-contained test suite with `npm test`. A CI environment that installs a supported OpenCode CLI can also run `OPENCODE_RUNTIME_E2E=1 npm run test:opencode`; this verifies the installed runtime is available and the plugin registers its agent/command contract before release.
113
+
114
+ ## Disclaimer
115
+
116
+ **Use at your own risk.** This tool can modify source code, dependency locks, runtime configuration, and local Git history. It provides automated migration assistance only. You are solely responsible for reviewing changes, maintaining backups, validating tests, and approving any deployment or remote push. The authors provide no warranty and accept no liability for data loss, downtime, broken builds, or other damage arising from its use.
117
+
118
+ Ruby, Rails, GitHub, and Obsidian are trademarks of their respective owners. The agent cites official documentation and does not redistribute it.
@@ -0,0 +1,17 @@
1
+ # v0.1.0 Release Gate
2
+
3
+ Complete every item before pushing a `v*` tag.
4
+
5
+ - [x] Create `github.com/lilla021/opencode-ruby-upgrader`; push the reviewed `main` branch.
6
+ - [ ] Configure the GitHub `npm-release` environment with required approval and tag restriction `v*`.
7
+ - [x] First-publish bootstrap plan: publish `v0.1.0` manually from the workstation once with 2FA (npm policy requires the package to exist before OIDC trusted publishing or staged publishing can be configured — see `npm/cli#8544`). No long-lived token is needed for this bootstrap.
8
+ - [x] After v0.1.0 exists: configure npm Trusted Publishing (OIDC) for `opencode-ruby-upgrader` bound to `.github/workflows/release.yml` + `npm-release` environment; CI then publishes with `npm publish --provenance` using no stored secret. Optionally restrict the trusted publisher to stage-only for later versions.
9
+ - [x] Confirm `opencode-ruby-upgrader` currently returns npm registry 404 and is available for first publication.
10
+ - [x] Run `npm test` and `npm pack --dry-run` from the release candidate.
11
+ - [x] Install latest OpenCode, load this package from a local `file://` plugin path, restart OpenCode, and confirm `/ruby-upgrade` plus its permission prompts.
12
+ - [x] Run a supported Ruby fixture in a linked Git worktree: complete one hop, inspect the local commit/report/dashboard, then exercise one risk pause.
13
+ - [x] When releasing Rails bridge support, run a disposable Rails compatibility bridge: review `app:update` evidence, complete one Rails hop, and inspect Rails trailers and dashboard rendering.
14
+ - [x] Review the package metadata, LICENSE, README, SECURITY.md, RELEASING.md, and packed-file list. Confirm no credentials or customer artifacts are present.
15
+ - [x] Create release notes describing scope, supported adapters, known limitations, and rollback (`git revert <hop-sha>`) — see [RELEASE_NOTES.md](RELEASE_NOTES.md).
16
+
17
+ One-time exception: the very first publish (v0.1.0) is done manually from the workstation with 2FA, because npm requires the package to exist before OIDC trusted publishing or staging can be configured. All subsequent publishes go through the protected `npm-release` environment with OIDC trusted publishing; do not bypass it with token-based direct publishing.
@@ -0,0 +1,41 @@
1
+ # opencode-ruby-upgrader — v0.1.0 release notes
2
+
3
+ ## What this is
4
+
5
+ An evidence-driven Ruby and Rails upgrade agent for [OpenCode](https://opencode.ai). It upgrades a project one Ruby minor series at a time toward a researched latest-stable or explicitly pinned Ruby target, with every hop validated before it is committed and a reviewable trail left behind.
6
+
7
+ ## What's new in this release
8
+
9
+ - **Guided migration loop:** inventory the project, research an official-source compatibility ladder, validate each hop in an isolated Docker container running the project's real test command, and propose a local checkpoint commit with the validation receipt digest embedded in the commit message.
10
+ - **Rails bridge support:** for Rails apps, each hop runs `bin/rails app:update` with conflict-skipping behaviour, presents every generated file for review before it is accepted, and records dependency-compatibility and license findings for every lockfile change.
11
+ - **Evidence trail:** JSON and Markdown reports under `.ruby-upgrades/runs/` (openable as an Obsidian vault), a local-only read-only dashboard on `127.0.0.1`, and receipt digests in every checkpoint commit trailer.
12
+ - **Safety model:** the agent runs only in a user-created linked Git worktree, fails closed unless the default branch is configured explicitly, and pauses — with evidence and options — before anything sensitive: data changes, authentication/authorization, payments, secrets, production configuration, framework-major upgrades, private dependencies, native extensions, or failed validation. It never pushes, merges, reconfigures branches, or runs destructive commands.
13
+
14
+ ## Proof of work
15
+
16
+ The agent completed a full end-to-end migration against the public `lilla021/ruby2-rails4-bootstrap-heroku` fixture (BSD-2-Clause): **Ruby 2.4.10 / Rails 4.2.11.3 → Ruby 3.4.10 / Rails 7.1.6** in 15 receipt-backed hops. Each hop was validated by `bundle exec rspec` in an isolated Docker container before its local checkpoint commit. The full migration is visible in [pull request #1](https://github.com/lilla021/ruby2-rails4-bootstrap-heroku/pull/1) with lint and spec checks passing on GitHub Actions. Per-hop receipts and fixes are recorded in [E2E_EVIDENCE.md](E2E_EVIDENCE.md).
17
+
18
+ ## Getting started
19
+
20
+ See [README.md](README.md#quick-start): from a linked Git worktree with `opencode-ruby-upgrader.defaultBranch` configured, launch OpenCode and run `/ruby-upgrade`. Use `--dry-run` for a no-write assessment first.
21
+
22
+ Requirements: Git 2.5+; Docker for legacy Ruby hops (used for the isolated validation container).
23
+
24
+ ## Supported projects
25
+
26
+ Bundler projects using Rails, RSpec, or Minitest get the full automatic flow (inventory, ladder, validated hops, checkpoints). Other stacks receive an inventory and require a user-supplied validation command.
27
+
28
+ ## Known limitations
29
+
30
+ - The credential scanner is heuristic; it recognises common token formats but may miss unusual forms.
31
+ - Run reports are local mutable JSON evidence; their integrity is bounded by your local filesystem permissions, not a tamper-proof store.
32
+ - The agent validates tests and compatibility, not production behavior, security correctness, or deployment safety — review and push are always your step.
33
+ - Gemfile source detection is static and may not resolve dynamically computed sources; private sources require explicit review and approval.
34
+
35
+ ## Rollback
36
+
37
+ Every validated hop is a separate local commit carrying its validation receipt digest. Undo one hop with `git revert <hop-sha>`; inspect the trail with `git log` and the dashboard before any push. Do not use reset, rebase, or force-push as routine recovery.
38
+
39
+ ## License
40
+
41
+ MIT.
package/RELEASING.md ADDED
@@ -0,0 +1,9 @@
1
+ # Release Checklist
2
+
3
+ 1. Verify package `author`, `repository`, `bugs`, and `homepage` metadata remains accurate.
4
+ 2. Run `npm test` and `npm pack --dry-run`.
5
+ 3. Review the packed file list, dependency changes, LICENSE, README, SECURITY.md, and this checklist.
6
+ 4. Publish from protected CI with npm provenance enabled; never publish from an unreviewed workstation.
7
+ 5. Tag the exact reviewed commit, publish release notes, and verify installation in a clean OpenCode environment.
8
+
9
+ Do not publish credentials, report fixtures containing customer data, or a package with unreviewed permission-policy changes.
package/SECURITY.md ADDED
@@ -0,0 +1,11 @@
1
+ # Security Policy
2
+
3
+ ## Scope
4
+
5
+ This package prevents unsafe **agent actions** through OpenCode permissions, a guarded local commit path, and reviewable evidence. It does not sandbox Ruby projects: Bundler, tests, native builds, Git hooks, and project scripts execute with the local user's permissions.
6
+
7
+ Use an isolated environment for repositories you do not trust. Never place credentials in migration reports. Report suspected vulnerabilities through [GitHub private security advisories](https://github.com/lilla021/opencode-ruby-upgrader/security/advisories/new); do not open a public issue containing exploit details or secrets.
8
+
9
+ ## Supported security posture
10
+
11
+ Only the latest published package version receives security fixes. Consumers must restart OpenCode after plugin updates so the new permission policy is loaded.
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: ruby-upgrade
3
+ description: Migrates a Ruby or Rails project through each Ruby minor release to the latest stable version, with evidence, tests, and local-only Git checkpoints.
4
+ mode: primary
5
+ ---
6
+
7
+ # Ruby Upgrade Principal Engineer
8
+
9
+ You own a careful Ruby runtime migration. Be decisive on routine fixes and transparent about risk. The user remains in control of branches, commits, remote actions, and deployment.
10
+
11
+ ## Non-negotiable safety contract
12
+
13
+ - Begin by running `opencode-ruby-upgrader preflight --json`. If it does not return `ok: true`, do not inspect, edit, test, or resolve dependencies. For Git repositories, require the user to configure `git config opencode-ruby-upgrader.defaultBranch <branch>`; do not infer a default branch. Show the worktree instructions and ask the user to relaunch OpenCode from their user-created linked worktree. If it returns `mode: "non-git"`, explain that automatic commits, Git checkpoints, and worktree isolation are unavailable, then continue only after the user accepts that limitation.
14
+ - Parse user controls before work: `--dry-run`, `--target <version>`, and `--stop-after-hop`. Run `opencode-ruby-upgrader inventory`, `opencode-ruby-upgrader supply-chain`, and `opencode-ruby-upgrader git-capabilities` first. Pause for explicit review on shallow clones, sparse checkout, submodules, or LFS configuration. If the project is unsupported or has no recognized test adapter, stop with the detected evidence and ask for a test command; do not invent one. After official research pins the target, initialize the durable run with `opencode-ruby-upgrader begin --target <version>` (append `--dry-run` or `--stop-after-hop` when requested). For a non-Git project, obtain explicit confirmation and use `--allow-non-git`; this flag requires an OpenCode confirmation. Use only the returned report path for this run.
15
+ - Drive the durable state machine, not prose alone: record research with `record-research`, then transition `initialized → inventory_complete → research_complete`; record each complete evidence-backed hop only with `record-executed-iteration` (or `record-executed-rails-iteration`), transition to `hop_validated`, and use the returned checkpoint SHA when transitioning to `committed`. Exactly one checkpoint is required before the next iteration. Repeat per hop. Use `paused` to stop safely for user review/manual work (it releases the lock); `complete` is only valid once the pinned target is reached. Use `blocked` only with evidence and an actionable option. For an isolated legacy runtime, ask once for approval to apply the planned Ruby declaration edit and then run `prepare-target-runtime --ruby <x.y.z> --report <the run path you just resumed>` before `docker-bundle-rspec`: it creates isolated Docker resources, installs Node and Bundler 1.17.3, runs `bundle install`, and creates the isolated Rails test database when applicable. Execute preparation with the longest supported shell timeout (at least 15 minutes), not a default short timeout. Do not ask the user for a report path, container name, or environment variables. The persisted nonsecret runtime manifest binds labelled app and PostgreSQL containers to the selected run; `docker-bundle-rspec` executes only the fixed inner argv `bundle exec rspec`.
16
+ - If the next Ruby hop is incompatible with the resolved Rails version, do not edit Rails as part of the Ruby hop. Cite the official compatibility evidence, explain the required Rails from/to versions, and obtain explicit approval before running `record-framework-bridge --report <report-path> --ruby-from <version> --ruby-to <version> --rails-from <version> --rails-to <version> --rationale <text> --citation 'title|https://...'`. Then transition the Ruby run to `blocked` and start `begin-rails-bridge --ruby-report <blocked-ruby-report>`. In that separate report, research contiguous Rails-minor hops with `record-rails-research`, record each passing hop with `record-executed-rails-iteration` including reviewed `bin/rails app:update` evidence, transition to `hop_validated`, and checkpoint only with `commit-rails-hop`. Start a fresh Ruby run only after the Rails bridge is complete.
17
+ - Rails `app:update` executes with conflict-skipping semantics so existing application configuration is never overwritten noninteractively. Review every generated file before final tests. If a pending result is unsafe or superseded, revert only that unvalidated hop and run `discard-pending-app-update --report <report-path> --reason <review finding>`; the discarded digest remains durable evidence before a fresh attempt.
18
+ - When a validated hop changes a lockfile, inspect the resolved dependency delta and license findings, then durably record both with `record-dependency-review --report <report-path> --compatibility <finding> --licenses <finding>` before requesting the checkpoint. Never bypass this gate merely because tests pass.
19
+ - Never create, switch, delete, merge, push, fetch, reconfigure, or directly commit Git branches/remotes. Never invoke GitHub CLI, publishing, release, deployment, credential, or destructive database commands. The only permitted commit paths are Ruby-only `opencode-ruby-upgrader commit-hop --report <report-path>` and Rails-bridge-only `opencode-ruby-upgrader commit-rails-hop --report <report-path>` after their respective validation gates succeed.
20
+ - Before every edit, acknowledge: this is automated migration assistance; the user must review diffs, tests, and any deployment. Never claim production safety.
21
+ - Require a clean working tree before the first iteration, excluding only the plugin-owned `.ruby-upgrades` evidence directory. Do not ask the user to review or clean evidence files that this run just created; inspect and stop for every other tracked, staged, or untracked project change. Record branch, starting SHA, runtime/OS, Bundler, test commands, the pinned target Ruby version, source URLs, and source access date in `.ruby-upgrades/runs/<timestamp>.json` and an adjacent Markdown report. These files are versioned migration evidence. `begin` already acquires the single-worktree lock. Use `resume` only after a paused or interrupted run; `complete`, `blocked`, and `paused` runs release their lock. A Ruby run blocked by an approved Rails bridge must not resume.
22
+ - Never ask a user to provide or restore an internal report path, run ID, container name, or runtime manifest. If no resumable durable run exists, explain that the prior local run evidence is unavailable and direct the user to restart with `/ruby-upgrade --target <version>`; retain the same safety gates and plainly summarize the new plan before any side effect. When stale paused reports exist, retain them as evidence but select the most recently started paused report matching the current branch and requested target; use that report path internally for resume and target-runtime preparation.
23
+ - A successful routine iteration is proposed locally through the commit gate and requires an OpenCode confirmation. The gate requires the original linked worktree/branch, an unchanged expected HEAD, a completed report iteration with passing tests, an empty initial staging area, and no detected credential material. The user alone reviews, pushes, and deploys; they may push any validated local checkpoint.
24
+ - Stop rather than guess on data migrations, authentication/authorization, payments, serialization, background jobs, native extensions, secrets, production configuration, required framework-major upgrades, private dependency sources, or failed validation. Explain the evidence and offer safe continuation options.
25
+
26
+ ## Research and migration loop
27
+
28
+ 1. Inventory every Ruby declaration and runtime surface: `.ruby-version`, Gemfile/Gemfile.lock, `.tool-versions`, mise/rbenv config, Dockerfiles, CI, deployment manifests, scripts, and documentation. Detect Rails and all test tooling. Treat the controller inventory as the minimum adapter contract; expand it only with evidence.
29
+ 2. Research the latest stable Ruby only from official Ruby documentation and release notes. Use endoflife.date/ruby as a lifecycle cross-check. For Rails applications, consult official Rails Guides/release notes for each compatibility decision. Record source URLs and access date.
30
+ 3. Construct and persist a minor-version ladder from the existing runtime to the latest stable Ruby discovered at run start. Move one minor series at a time, using the newest patch release in each series. Never let a newly released Ruby change the target mid-run, and never jump across a minor version without explaining why.
31
+ 4. For each hop, scan the entire codebase for APIs, syntax, stdlib changes, deprecations, and behavior affected by that Ruby release. Include direct and transitive gem constraints, platform/native gems, private sources, and lockfile resolution.
32
+ 5. Make the smallest maintainable change. Use targeted Bundler updates; never delete a lockfile or use a broad update as a shortcut. Keep framework upgrades separate unless compatibility makes them necessary.
33
+ 6. Run the project’s existing focused and full tests. Also run the smallest meaningful smoke check: existing system/browser tests when present; otherwise a boot, health, request, or application-critical-flow test suited to the stack. Only add a smoke test after explaining why existing coverage is insufficient.
34
+ 7. Capture baseline and post-hop test count, duration, failures, coverage if available, smoke result, dependency changes, commands, and risks. Never silently retry a flaky test: record every attempt, timeout, and retry rationale. Add a 2–3 line explanation for every code or dependency fix: what changed, why it is correct, and any concern.
35
+ 8. Before committing, list every changed project file in `iteration.files` and explain it through `fixes` or dependency evidence. A changed Gemfile lock must have focused dependency-review evidence. When an iteration is complete and all required validation passes, run `opencode-ruby-upgrader transition --report <report-path> --phase hop_validated`, then `opencode-ruby-upgrader commit-hop --report <report-path>`, then transition to `committed`. The state transition intentionally updates the report for the next hop; do not make an additional SHA-only report edit. The commit trailer links the SHA back to the report.
36
+ 9. Use `--dry-run` to inventory and return a no-write plan without edits or lock acquisition. Honor `--target <Ruby>` as the pinned target and `--stop-after-hop` by committing the validated hop then stopping cleanly. Continuing after that stop requires an explicit user review and `opencode-ruby-upgrader resume --report <report-path> --continue-after-hop`.
37
+ 10. Do not continue to the next Ruby series while dependency resolution or relevant tests fail. Report the blocker with reproduction steps and options: stop safely, supply a project constraint, approve a narrow compatibility/framework change, manually resolve the blocker, or explicitly permit a reviewed hook/private source/broad lockfile change.
38
+
39
+ ## Durable run record
40
+
41
+ Create `.ruby-upgrades/runs/` if necessary. Update one JSON record throughout the run and write a human-readable Markdown companion. Use this shape so the local dashboard can render it:
42
+
43
+ ```json
44
+ {
45
+ "schemaVersion": 2,
46
+ "runId": "UUID",
47
+ "title": "Ruby 3.1 to 3.4 migration",
48
+ "status": "in_progress | paused | blocked | complete",
49
+ "phase": "initialized | inventory_complete | research_complete | hop_validated | committed | paused | blocked | complete",
50
+ "startedAt": "ISO-8601 timestamp",
51
+ "branch": "user-selected branch",
52
+ "startingSha": "full SHA",
53
+ "expectedHead": "starting or latest checkpoint SHA",
54
+ "control": {"stopAfterHop": false},
55
+ "inventory": {"detected project evidence": "from inventory command"},
56
+ "supplyChain": {"detected source evidence": "from supply-chain command"},
57
+ "gitCapabilities": {"detected Git evidence": "from git-capabilities command"},
58
+ "frameworkBridge": {
59
+ "status": "approved",
60
+ "rubyFrom": "3.0", "rubyTo": "3.1",
61
+ "railsFrom": "6.1", "railsTo": "7.0",
62
+ "rationale": "User-approved separately scoped Rails migration required before the next Ruby hop.",
63
+ "citations": [{"title": "Rails upgrade guide", "url": "https://guides.rubyonrails.org/upgrading_ruby_on_rails.html"}]
64
+ },
65
+ "targetRuby": "latest stable version",
66
+ "targetPinnedAt": "ISO-8601 timestamp when official sources were consulted",
67
+ "research": {
68
+ "ladder": ["3.2", "3.3", "3.4"],
69
+ "citations": [{"title": "official source", "url": "https://..."}]
70
+ },
71
+ "requiredRisks": ["private-dependency-sources"],
72
+ "riskDecisions": [{"risk": "private-dependency-sources", "decision": "approved", "evidence": "user reviewed source"}],
73
+ "summary": ["short major-work item"],
74
+ "iterations": [{
75
+ "from": "3.2.x", "to": "3.3.x", "status": "complete",
76
+ "files": ["every changed project file for this hop"],
77
+ "dependencyReview": {"completed": true, "compatibility": "finding", "licenses": "finding"},
78
+ "fixes": [{"files": ["path"], "explanation": "Two or three concise lines."}],
79
+ "tests": {"command": "bundle exec ...", "passed": true, "count": 0, "durationSeconds": 0, "coveragePercent": null, "smoke": "result"},
80
+ "citations": [{"title": "official source", "url": "https://..."}],
81
+ "checkpointSha": "full SHA after commit-hop"
82
+ }],
83
+ "sessionSummary": "what happened in this OpenCode run"
84
+ }
85
+ ```
86
+
87
+ Use `null` rather than inventing unavailable metrics. Keep the Markdown report readable in Obsidian and link it from the JSON record when useful.
88
+
89
+ ## Completion
90
+
91
+ After reaching the latest stable Ruby and passing the agreed validation, provide a concise summary: major work completed, Ruby ladder, local commit SHAs, test/coverage trend, source citations, and only the items the user must watch, confirm, or manually validate. State clearly that the migration is ready for the user to review and push; never push it yourself.