Auto-update the plugin's declared dependencies, not only the plugin (#605) - #606
Merged
Merged
Conversation
…605) `scripts/plugin_update.py` resolved one name off its own manifest and updated that one plugin. The status line already rendered currency for the loop plugin *and* every name in its manifest's `dependencies`, deriving that set from the manifest precisely so "absent because it is fine" and "absent because nothing looked" would not render alike -- so the actor's subject was narrower than the report's, and nothing anywhere said so. Measured before the change: `remember` at 0.21.0 against a published 0.22.0, through a session restart and a `/reload-plugins`, with a green three-plugin currency line beside it. `/reload-plugins` moves the registry to whatever is installed; it installs nothing. `update()` now refreshes the marketplace once, still fatally to the whole run, then calls a new `_update_one` per plugin. A dependency gets a fourth state the loop plugin cannot reach -- `not-installed` -- because `installed_scopes` returns `[]` both for "nothing is installed here" and for "the record carries no scope", and the loop plugin's `or ["user"]` fallback is right for the plugin whose own hook is running and wrong for a dependency this project may genuinely not have. Falling back there would have collected `Plugin "<name>" not found` and recorded a plugin this project never had as one it might silently have lost. A dependency's verdict never becomes the loop plugin's: the top-level `state`/`plugin`/`from`/`to` still answer about the loop plugin alone, because every existing reader of the receipt asks them that question. The dependencies are a sibling list, and `doctor.check_auto_update` reports them as their own row -- number 521 is the precedent for a second answer folded into a first, where it reached the receipt and stopped there. Three absences are kept apart rather than rounded to a pass: a receipt written before this change (says nothing about dependencies, and the row says so), a `dependencies` key that could not be read as a list of names, and a manifest that declares none. `declared_dependencies` returns `(names, status)` for the same reason -- `[]` alone cannot carry both of the first two. One level, not transitive: the manifest is the only declaration available here. Dogfooded on this machine rather than only in fixtures: the run moved `remember` 0.21.0 -> 0.22.0 at all three scopes, `doctor` printed the new WARN row naming it, and the status line went from `3 current, remember behind 0.22.0` to `4 current`. Three assertions in `tests/test_auto_update_receipt_484.py` and one in `tests/test_reload_plugins_wording_553.py` moved from `len(FINDINGS) == 1` to indexing row 0. Their subject -- the loop plugin's own row and its text -- is unchanged; only the count moved, because a second row now follows it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Closes #605.
What was wrong
scripts/plugin_update.pyresolved one name off its own manifest and kept that one plugin current.statusline.plugin_factsalready rendered currency for the loop plugin and every name in its manifest'sdependencies, deriving that set from the manifest for the stated reason that "absent because it is fine" and "absent because nothing looked" must not render alike. The actor's subject was narrower than the report's, and nothing said so.Measured before the change, in a session that had just been restarted and had run
/reload-plugins:rememberinstalled at 0.21.0 for this project against a published 0.22.0, with thedpt-pluginsmarketplace clone in sync withorigin/master, so the comparison was real and not a stale cache.What changed
update()refreshes the marketplace once — still fatal to the whole run — then calls a new_update_oneper plugin: the loop plugin, then each name fromdeclared_dependencies().A dependency gets a fourth state the loop plugin cannot reach,
not-installed.installed_scopesreturns[]both when nothing is installed for this project and when the install record carries noscopefield, so the loop plugin has always fallen back to["user"]— right for the plugin whose own hook is running, wrong for a dependency this project may genuinely not have. Falling back there would collectPlugin "<name>" not foundand record a plugin this project never had as one it might silently have lost.A dependency's verdict never becomes the loop plugin's. The top-level
state/plugin/from/tostill answer about the loop plugin alone, because every existing reader of the receipt asks them that question; the dependencies are a sibling list.doctor.check_auto_updategrew a second row in the same change. A failure recorded only in a receipt no row reads is #521 with an extra step — that issue's measured instance wasstate: currentprinted while one of two scopes had failed, named only indetail, which the row never looked at.Three absences are kept apart rather than rounded to a pass:
dependencieskey that could not be read as a list of names — WARN, and none were touched;declared_dependenciesreturns(names, status)for the same reason:[]alone cannot carry both of the first two.One level, not transitive. The manifest is the only declaration available here; a dependency's own dependencies would have to be read out of its installed copy, which is a different question.
Evidence
Red first. The 20 tests in
tests/test_plugin_update_dependencies_605.pywere written before the implementation:The 2 and the 14 that passed early are named: the marketplace-refresh-once assertions were already true of the old code, and the "no dependency row when the updater never ran" control is a must-not-fire.
Full suite:
3825 passed, 6 skipped, coverage 89.44% against the 85% floor.Dogfooded on the author's machine rather than only in fixtures.
python3 scripts/plugin_update.py --root . --printreturnedrememberupdated 0.21.0 -> 0.22.0at all three recorded scopes,supertoolandclaude-jit-contextcurrent.doctorthen printed:and the status line went from
3 current, remember behind 0.22.0to4 current.What this touches that it did not have to
Three assertions in
tests/test_auto_update_receipt_484.pyand one intests/test_reload_plugins_wording_553.pymoved fromlen(doctor.FINDINGS) == 1to indexing row 0, with a comment at each saying why. Their subject — the loop plugin's own row and its text — is unchanged and still asserted; only the count moved, because a second row now follows it. The alternative was to suppress the new row when the receipt has nodependencieskey, which is the defect class this repository is named after.Not claimed
Only the macOS/Python 3.13 leg was run locally. Nothing here is platform-conditional — no paths, no shell, no
os.pathsemantics — so the cross-platform claim is reasoned, and CI's 13 legs are what observe it.The live dogfood exercised
currentandupdated.not-installed,could-not-checkand the unreadable-manifest arm are covered by fixtures only; no machine here has a declared dependency missing.Whether a declared dependency should be installed is still owned by
doctor's existing declared-dependencies row, not by this one. This row only answers what the updater did.🤖 Generated with Claude Code