Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The ambient
Stophook 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:
cpon the deletedstop-guard.shexits 1, and Actions steps run underbash -e.Why the retirement code did nothing
retireOrphanPluginScripts()first lived inside the plugin-regeneration block ofupdateCommand. That block sits after theAll files are up to date!andNo changes to applyexits — 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)— theStophook andstop-guard.share gone: generator, marketplace template, tests, docs and the/convergecoexistence prose. The ownership sentinel stays; its readers are now the clean-tree guard,--resumeand theSessionStartsweep.fix(update)—devtronic updateanddevtronic regenerate --plugindelete 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 ahooks.jsonpointing at a script the plugin does not ship. Its file operations move out of the workflow intoscripts/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.commandoff 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.
updateCommand()andregenerateCommand()— including the "everything already current" exit path the first attempt never reached.sync-plugin-repo.shagainst a fixture of the published plugin, one asserting the workflow still calls it with the same arguments.doctorcheck.regeneratecall fails 1.Also run by hand against a copy of the real published plugin repo:
stop-guard.shremoved, six hook events down to five, every remainingcommandhook 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