Skip to content

Auto-update the plugin's declared dependencies, not only the plugin (#605) - #606

Merged
fdaviddpt merged 1 commit into
mainfrom
issue-605-auto-update-dependencies
Aug 28, 2026
Merged

Auto-update the plugin's declared dependencies, not only the plugin (#605)#606
fdaviddpt merged 1 commit into
mainfrom
issue-605-auto-update-dependencies

Conversation

@fdaviddpt

Copy link
Copy Markdown
Contributor

Closes #605.

What was wrong

scripts/plugin_update.py resolved one name off its own manifest and kept that one plugin current. statusline.plugin_facts already rendered currency for the loop plugin and every name in its manifest's dependencies, 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:

plug 3 current  remember behind 0.22.0

remember installed at 0.21.0 for this project against a published 0.22.0, with the dpt-plugins marketplace clone in sync with origin/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_one per plugin: the loop plugin, then each name from declared_dependencies().

A dependency gets a fourth state the loop plugin cannot reach, not-installed. installed_scopes returns [] both when nothing is installed for this project and when the install record carries no scope field, 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 collect Plugin "<name>" not found and 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/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.

doctor.check_auto_update grew 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 was state: current printed while one of two scopes had failed, named only in detail, which the row never looked at.

Three absences are kept apart rather than rounded to a pass:

  • a receipt written before this change, which says nothing about dependencies — the row states that, rather than reading it as "all current";
  • a dependencies key that could not be read as a list of names — WARN, and none were touched;
  • a manifest that declares none — OK, a fact.

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; 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.py were written before the implementation:

11 failed, 2 passed      # the plugin_update half, before _update_one existed
6 failed, 14 passed      # the doctor half, before _report_dependencies existed
20 passed                # after

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 . --print returned remember updated 0.21.0 -> 0.22.0 at all three recorded scopes, supertool and claude-jit-context current. doctor then printed:

OK   auto-update: oss already current (1 minute(s) ago)
WARN auto-update dependencies: supertool current; remember updated 0.21.0 to 0.22.0; claude-jit-context current. This session is still running the copies it started with: run /reload-plugins ...

and the status line went from 3 current, remember behind 0.22.0 to 4 current.

What this touches that it did not have to

Three assertions in tests/test_auto_update_receipt_484.py and one in tests/test_reload_plugins_wording_553.py moved from len(doctor.FINDINGS) == 1 to 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 no dependencies key, 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.path semantics — so the cross-platform claim is reasoned, and CI's 13 legs are what observe it.

The live dogfood exercised current and updated. not-installed, could-not-check and 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

…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>
@fdaviddpt
fdaviddpt merged commit 608060d into main Aug 28, 2026
14 checks passed
@fdaviddpt
fdaviddpt deleted the issue-605-auto-update-dependencies branch August 28, 2026 07:46
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.

Auto-update keeps only the loop plugin current; its three declared dependencies drift with nothing watching

1 participant