Skip to content

Release v1.5.2 — the stop gate is gone, and the release can no longer lie about it - #54

Merged
r-bart merged 6 commits into
mainfrom
develop
Aug 21, 2026
Merged

Release v1.5.2 — the stop gate is gone, and the release can no longer lie about it#54
r-bart merged 6 commits into
mainfrom
develop

Conversation

@r-bart

@r-bart r-bart commented Aug 21, 2026

Copy link
Copy Markdown
Owner

The ambient Stop hook ran typecheck + lint on every stop and blocked the turn on failure. It is gone. Five hook events remain: SessionStart, PostToolUse, StopFailure, SubagentStop, PreCompact.

Removing it took three attempts to get right, and the two failures are the more useful part of this PR.

Why the first removal did not take

The source stopped registering the hook. The published plugin kept firing it, because the release syncs templates/marketplace/ into the plugin repo by copying each script by name and deleting none. A hook retired in the source stays in every user's cache for good.

It would also have broken this release outright: cp on the deleted stop-guard.sh exits 1, and Actions steps run under bash -e.

Why the retirement code did nothing

retireOrphanPluginScripts() first lived inside the plugin-regeneration block of updateCommand. That block sits after the All files are up to date! and No changes to apply exits — so an install whose template files are current, which is exactly the install carrying the dead script, never reached it. It now runs before both exits.

What changed

  • feat(hooks) — the Stop hook and stop-guard.sh are gone: generator, marketplace template, tests, docs and the /converge coexistence prose. The ownership sentinel stays; its readers are now the clean-tree guard, --resume and the SessionStart sweep.
  • fix(update)devtronic update and devtronic regenerate --plugin delete the orphan script and drop it from the manifest. detectRemovedFiles() skips the plugin path by design, so nothing else would have. A script you edited is kept, with a warning that it no longer runs.
  • fix(release) — the sync mirrors instead of copying, and refuses to publish a hooks.json pointing at a script the plugin does not ship. Its file operations move out of the workflow into scripts/sync-plugin-repo.sh, because inline YAML cannot be run or tested — which is why the bug lived through three versions.
  • fix(doctor) — the hook check read .command off the root of the hooks file, where there is none, so it counted zero hooks and passed on every project. Had it worked, it would have named this bug two versions ago.

Verification

1108 → 1140 tests. Typecheck, lint and build clean.

  • 7 unit tests for the retirement, 7 end-to-end tests that run the real updateCommand() and regenerateCommand() — including the "everything already current" exit path the first attempt never reached.
  • 9 that run the real sync-plugin-repo.sh against a fixture of the published plugin, one asserting the workflow still calls it with the same arguments.
  • 4 parity tests that fail if the generator and the marketplace template ever register different hooks.
  • 5 for the repaired doctor check.
  • All mutation-checked: disabling the early retirement fails 3 tests, disabling the regenerate call fails 1.

Also run by hand against a copy of the real published plugin repo: stop-guard.sh removed, six hook events down to five, every remaining command hook pointing at a script that exists.

Not covered: the clone, commit, tag and push inside the sync job. They need a token and are unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Pw4CUCJ9qh7Rw34wotipED

r-bart added 6 commits August 20, 2026 07:42
The repository's own install had been sitting on 1.3.0 since March —
`.claude/rules/` still held the 1.3.0 templates, and `.claude/settings.json`
still carried the standalone-era inline hooks the plugin has been supplying
in parallel ever since.

This is the first real run of the three 1.5.1 fixes against an existing
install, and all three held: two files proposed and two written, no phantom
"New Files" section, and the duplicate hooks reported and removed
(SessionStart, PostToolUse, SubagentStop — Stop and PreCompact were already
empty). settings.json is now just the marketplace registration.
The `Stop` hook ran typecheck + lint on every stop and blocked the turn on
failure. A session that was reading code, answering a question, or half-way
through a refactor still paid a full quality gate before it could hand back
control, and a red tree you already knew about turned every stop into a
fight. Quality checks belong where the work is: `/post-review`, the
per-iteration Tier ① gate in `/converge`, and CI.

`stop-guard.sh` goes with it — the generator, the marketplace template and
its tests. Nothing invoked it once the hook was gone.

The ownership sentinel stays. Its readers are now the clean-tree guard,
`/converge --resume` and the `SessionStart` sweep, so the prose that framed
it as a coexistence device for the stop gate is rewritten rather than
deleted.

Four new tests hold the generator and the marketplace template to the same
hook set. They are maintained by hand, and a change applied to one and not
the other is exactly how this gate outlived an earlier attempt to remove it:
the published plugin kept firing a hook the source no longer had.
The generator stopping is not enough. `detectRemovedFiles()` skips everything
under the plugin path — plugin files are regenerated, not copied from a
template — so a retired script sat on disk, listed in the manifest, with no
hook left to run it.

`retireOrphanPluginScripts()` deletes it and drops the manifest entry, from
both `devtronic update` and `devtronic regenerate --plugin`. A script the
user edited is kept on disk with a warning that it no longer runs; their work
is not ours to throw away.

It runs before the "All files are up to date!" and "No changes to apply"
exits, not inside the plugin-regeneration block further down. An install
whose template files are current is precisely the one carrying the dead
script, and it never reached that block — the first version of this change
had it there and did nothing for the case it was written for.

The manifest is written as soon as something is retired, because those two
exits return without writing and the retirement would otherwise be reported
on every run.
The sync copied each marketplace script by name and deleted none, so a script
the templates stopped shipping stayed in the published plugin — and in every
user's cache — release after release. It also would have failed outright on
this release: `cp` on the deleted `stop-guard.sh` exits 1, and Actions steps
run under `bash -e`.

Skills, agents, hooks and scripts are now mirrored: cleared, then refilled
from the templates. The sync refuses to publish a `hooks.json` that points at
a script the plugin does not ship, which is the failure this whole release is
about, caught before it reaches anyone.

The file operations move out of the workflow into `scripts/sync-plugin-repo.sh`.
Inline YAML cannot be run or tested, and that is why the bug lived through
three versions. Nine tests now run the real script against a fixture of the
published plugin, one of them asserting the workflow still calls it with the
same arguments. `sync-plugin-repo` needs `publish-cli`, which needs `ci`, so
they gate the release.
The check verifies that every `command` hook points at a script that exists.
It read `.command` off the root of the hooks file, where there is none — the
commands sit two levels down, under `hooks` → event → matcher entry. So it
counted zero hooks and returned "No hook scripts to verify" on every project,
including one whose hook pointed at a script the plugin no longer shipped.

Had it worked, it would have named the bug in this release two versions ago.
@r-bart
r-bart merged commit d54c108 into main Aug 21, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant