Skip to content

feat: choose which VGV plugins each harness is installed from - #97

Merged
marcossevilla merged 9 commits into
mainfrom
select-plugin
Oct 28, 2026
Merged

marcossevilla merged 9 commits into
mainfrom
select-plugin

Conversation

@ryzizub

@ryzizub ryzizub commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Description

vgv_ai install --plugin <name> (repeatable) narrows an install to the pinned plugins you name. Without it, every pinned plugin is installed as before.

  • PluginAssets names the plugins it came from, so Claude Code's enabledPlugins lists only what was installed.
  • The manifest records plugin versions per harness, since --plugin can leave a plugin out of one harness while another is installed from it. The format never shipped, so it stays at version 1: a plugins map that is not per harness is dropped and the entries are read as before.
  • doctor plans each harness against the plugins it was installed from, reports their versions in that harness's own section, and notes a pinned plugin a harness wasn't installed from rather than calling it stale.
  • The install command doctor prints names those plugins, so following it cannot add one that was left out. Where harnesses were installed from different plugins, that section has no fix and the report has no closing command either.

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🛠️ Bug fix (non-breaking change which fixes an issue)
  • ❌ Breaking change (fix or feature that would cause existing functionality to change)
  • 🧹 Code refactor
  • ✅ Build configuration change
  • 📝 Documentation
  • 🗑️ Chore
  • 🧪 Test

🤖 Generated with Claude Code

Narrow an install to the pinned plugins it names: resolve only those,
translate from them, and enable only them for Claude Code. The manifest
keeps the version it recorded for a plugin a run skipped, and doctor
notes a pinned plugin that was never installed instead of calling it
stale.
Credit each plugin for what it contributes, so the Claude translator reads
the list off the assets it already gets. The other translators keep their
signatures instead of taking a plugin list none of them uses.
doctor resolved every pinned plugin, so after an install that named some
of them it planned the rest as content that was missing and exited 70. It
now resolves what the manifest records, and a plugin that was never
installed is a note rather than an install to run.

Also drop the PluginAssets copy that merge can do itself, record a
distinct version in the skipped-plugin test so it can fail, and fix the
install doc comment and the README example.
@ryzizub
ryzizub requested a review from marcossevilla October 21, 2026 12:49
@ryzizub
ryzizub marked this pull request as ready for review October 21, 2026 12:49
marcossevilla
marcossevilla previously approved these changes Oct 21, 2026
@marcossevilla

This comment was marked as outdated.

@unicoderbot

This comment was marked as outdated.

@marcossevilla

Copy link
Copy Markdown
Member

@vgvbot /review-pr

@unicoderbot

unicoderbot Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

I'll review the code and share my feedback shortly.

  • Preparing environment
    • Cloning repository
    • Setting up dev environment
  • Reviewing code
  • Posting review

Code review submitted.

@unicoderbot unicoderbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --plugin selection is threaded through cleanly. PluginAssets.plugins replaces ResolvedAssets.plugins, the resolver filters with only, and Claude's enabledPlugins follows the assets. I checked the reconciler. A partial install after a full one keeps the skipped plugin's enabledPlugins key and its manifest ownership, because _deepMerge merges tables key by key and Contribution.of keeps owning the untouched table. So the README is right that uninstall still removes it. _pluginVersions also keeps the version already recorded for a skipped plugin, as intended.

Two problems remain in doctor, and both come from the plugin selection being recorded once per home:

  1. Its fix suggestion undoes the selection. FixCommand never passes --plugin. If a home was installed with --plugin vgv-wingspan, any problem doctor finds is fixed with plain vgv_ai install --only claude, which installs every pinned plugin. That brings back the plugins the user chose to leave out.
  2. The recorded plugins are not per harness. Say a user does a full install for Claude, then runs install --only codex --plugin vgv-wingspan. The manifest still records both plugins, so doctor resolves both for Codex and reports the other plugin's Codex files as missing (stale, exit 70). That is the false alarm this PR set out to remove, in a different form.

A smaller point: a version 1 manifest that installed everything now reports each plugin as <name> <version> not installed, which isn't true for that home. The PR intends that behaviour, but the wording could be more careful.

install --plugin can leave a plugin out of one harness while another is
installed from it, so a manifest that records one set for the whole home
makes doctor plan both for both. It now records them per harness, plans
each harness against the plugins it was installed from, and reports the
versions in that harness's own section.

The fix doctor prints names those plugins too, so following it cannot
install one that was deliberately left out, and no combined fix is
printed when the harnesses need different plugins. Manifest version 3;
an older one reads as empty, as a malformed one does.
@ryzizub ryzizub changed the title feat(vgv_ai_cli): add --plugin to install feat: install only the VGV plugins you name Oct 22, 2026
@ryzizub

ryzizub commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Both review comments are addressed in c705bee.

Recorded plugins are not per harness. The manifest now records plugin versions per harness (version: 3): install writes them only for the harnesses a run configured, and uninstall drops them for a harness it takes everything back from. doctor groups the harnesses by the plugins they were installed from and plans each group on its own, so a plugin left out of one harness is no longer planned for it. FixCommand carries --plugin, and no combined fix is printed when the harnesses need different plugins. There is a test where the recorded plugins differ between harnesses.

unrecorded covers two cases. Version 3 has no "installed, but no versions recorded" case: an older manifest reads as empty, so not installed is literal. The versions are reported in each harness's own section now, not in one home-wide Plugins section.

@ryzizub

ryzizub commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

@vgvbot /review

@unicoderbot

unicoderbot Bot commented Oct 22, 2026

Copy link
Copy Markdown

Unknown slash command: /review. Available commands: /review-pr, /summarize, /address-review, /rebase, /merge, /preview-build, /help.

@ryzizub

ryzizub commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

@vgvbot /review-pr

@unicoderbot

unicoderbot Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

I'll review the code and share my feedback shortly.

  • Preparing environment
    • Cloning repository
    • Setting up dev environment
  • Reviewing code
  • Posting review

Code review submitted.

@unicoderbot unicoderbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both points from the last review are fixed.

  • The manifest now records plugin versions per harness. doctor groups harnesses by the plugins they were installed from and plans each group separately.
  • FixCommand passes --plugin when a harness has a strict subset of the pinned plugins, and combine returns null when the fixes need different plugins.
  • The auditor drops the recorded versions for a harness once it has no entries left.
  • There are tests where two harnesses record different plugins.

The point about how a version 1 manifest was worded no longer applies, because older manifests are no longer read.

Two problems remain:

  1. Upgrading loses track of every installed file (high). decode now matches only version: 3. A version 1 or 2 manifest falls through to InstallManifest(), which drops all its entries as well as its plugins. After upgrading, the next install treats every file VGV wrote earlier as the user's. It keeps those files and reports conflicts unless --force is passed. uninstall removes nothing, and doctor says nothing is installed. The new test asserts this behaviour (expect(manifest.entries, isEmpty)). Keep reading the entries from older manifests. For the plugins, either copy the home-wide map to each harness that owns entries, or record none for them.
  2. The Shared section's fix ignores --plugin (medium). That section is built without plugins:, so its FixCommand installs every pinned plugin for each harness in readsShared. That brings back the plugins the user left out, which is the problem this PR set out to fix.

The format never shipped, so it stays version 1 with its plugin versions
per harness: a plugins map that is not per harness is dropped and the
entries are read as before.

The Shared section now names the plugins its readers were installed
from, and prints no fix at all when they differ, since one install
cannot fix the skills they share without adding a plugin one of them was
left without.
@ryzizub

ryzizub commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Both points from the second review are addressed in cfc0257.

Upgrading loses track of every installed file. Nothing has shipped, so instead of migrating the format, the manifest is version 1 from this PR on: the plugin versions are recorded per harness, a plugins map that is not per harness is dropped, and the entries are read as before. A version 1 manifest therefore keeps every file it records, and only its plugin versions are forgotten, so those harnesses are checked against every pinned plugin and the fix installs all of them, which is what they had. A version 2 manifest from an earlier build is not read at all: decode returns null for it and the CLI reports it as written by a newer version, so delete ~/.vgv_ai/manifest.json if you have one.

The Shared section's fix ignores the plugin selection. ReportSection.plugins is now nullable. The Shared section passes the plugins its readers were installed from when they share them, and null when they differ, which prints no fix for that section at all. There are tests for both: a reader installed with --plugin gets install --only codex --plugin vgv-wingspan, and readers installed from different plugins get no command.

@ryzizub

ryzizub commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

@vgvbot /review-pr

@unicoderbot

unicoderbot Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

I'll review the code and share my feedback shortly.

  • Preparing environment
    • Cloning repository
    • Setting up dev environment
  • Reviewing code
  • Posting review

Code review submitted.

@unicoderbot unicoderbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both open points from the last review are fixed.

  • Upgrade path: decode stays at version 1 and keeps the entries. A plugins map that is not per harness is now dropped instead of the whole manifest. Those harnesses fall back to being checked against every pinned plugin, which matches what an earlier install wrote. The new test checks that the entries survive.
  • Shared fix: _shared passes the plugin set the reader harnesses have in common. When their sets differ, ReportSection.fix returns null, so the Shared section no longer suggests a command that would add back a plugin the user left out. Tests cover the case where the readers share a set and the case where they differ.

One new, smaller issue comes from that change. FixCommand.combine still drops null fixes with nonNulls. A null section fix used to mean "nothing to fix". Now it can also mean "no single command fixes this". As a result, the closing line "Run … to fix everything install can" can appear even though a Shared problem is not covered by it. See the inline comment.

Neither the flat document nor a plugins map that is not per harness is a
format this CLI ever wrote, so the tests say what decoding does with a
document it cannot read instead of naming an older format.
@ryzizub

ryzizub commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

@vgvbot /review-pr

@unicoderbot

unicoderbot Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

I'll review the code and share my feedback shortly.

  • Preparing environment
    • Cloning repository
    • Setting up dev environment
  • Reviewing code
  • Posting review

Code review submitted — PR approved.

unicoderbot[bot]
unicoderbot Bot previously approved these changes Oct 26, 2026

@unicoderbot unicoderbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only changes since my last review rename two tests in install_manifest_test.dart. The new names (a document with no version is not a manifest and keeps the entries when it cannot read the plugin versions) match what those tests check. I found nothing new.

Still open from the last review: FixCommand.combine still drops null fixes with nonNulls, and doctor_command.dart:96 still prints the result as the command that will "fix everything install can". A Shared section that needs install has a null fix when its reader harnesses were installed from different plugins. In that case, the closing command can leave the Shared problem unfixed. The fix and the test I suggested in my earlier inline comment on fix_command.dart:25 still apply.

The rest of the PR still looks correct:

  • Plugin versions are recorded per harness.
  • doctor plans each group of harnesses that share a plugin set separately.
  • enabledPlugins lists only the installed plugins.
  • uninstall forgets the versions of a harness it empties.

A section's fix is null both when install changes nothing it found and
when its harnesses were installed from different plugins, so the closing
line could promise to fix everything while leaving a shared skill out of
the command. A section that install can fix but that no one command
covers now leaves the report without that line.
@ryzizub ryzizub changed the title feat: install only the VGV plugins you name feat: choose which VGV plugins each harness is installed from Oct 26, 2026
Grouping harnesses by a joined string made the plugin names part of a
key format, and splitting it back assumed none of them holds a space.
An EqualityMap and an EqualitySet compare the selections directly, so
nothing is joined, split, or taken as the one to compare the rest with.

A harness the manifest records no plugins for now carries every pinned
name rather than null, which the resolver already treats the same way.

@unicoderbot unicoderbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All my review comments have been resolved. Approving this PR. If another review is needed, just re-request it.

@ryzizub
ryzizub requested a review from omartinma October 26, 2026 10:25
@marcossevilla
marcossevilla merged commit a3e0099 into main Oct 28, 2026
8 checks passed
@marcossevilla
marcossevilla deleted the select-plugin branch October 28, 2026 11:44
@vgvbot vgvbot mentioned this pull request Oct 29, 2026
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.

2 participants