M4: marketplaces + plugins - #9
Merged
Merged
Conversation
Add internal/marketplace package with Marketplace, PluginEntry, Source (polymorphic string/object UnmarshalJSON), PluginManifest, and ReservedMarketplaceNames. All 5 source types (relative, github, url, git-subdir, npm) are modelled. Tests cover string/object round-trips, full field parsing, and the GitSubdir + PluginManifest shapes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s (Task 2) Add Fetcher interface with Dispatch(); RelativeFetcher uses os.CopyFS- style manual walk; GitFetcher uses go-git/v5 PlainClone (shallow, depth=1) with idempotent re-pull and git-subdir extraction via copyDir+extractSubdir; NPMFetcher fetches tarball via registry HTTP API (no npm CLI). Tests cover all three fetchers including file:// fake repos and httptest.Server NPM registry. go-git/v5 added to module. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s (Task 3)
Add projection.go: Project() decomposes PluginManifest (strict) or
PluginEntry (non-strict) into canonical MCPServers/Skills/Subagents/
Commands/Hooks/LSPServers; resolves \${CLAUDE_PLUGIN_ROOT} in all
command/arg/url/env fields. Add plugin.go: install/upgrade/enable/disable/
remove/list subcommands that cache plugin source and write plugins/<id>.toml
with manifest SHA. Add marketplace.go: add/remove/list subcommands that
fetch marketplace into .state/cache/ and write marketplaces/<name>.toml.
Wire both into root command.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…model (Task 4) After Load() reads canonical files, LoadWithCache also reads each plugins/<id>.toml, resolves <cacheDir>/<id>/.claude-plugin/plugin.json via the afero FS, and merges the projection (MCPServers, LSPServers, etc.) into Canonical so downstream adapters see plugin components transparently. Avoids the source↔marketplace import cycle by inlining the minimal JSON parsing in the source package and introducing PluginProjection / PluginProjector types. marketplace.ProjectWithReader is added as a testability hook for callers that are not subject to the cycle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Task 5) marketplace add/remove/list were already implemented (Task 3); this commit makes them robust by also writing the marketplace's HeadSHA and FetchedAt timestamp into .state/targets.json so the upcoming update command can track fetch history without re-reading TOML files. remove clears the entry. Two new tests (TestMarketplace_AddUpdatesState, TestMarketplace_RemoveUpdatesState) verify the state.json round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…report pending bumps (Task 6) Adds `agentsync update` (the only network-touching command): - Re-fetches all registered marketplaces via marketplace.Dispatch - Builds a fresh plugin index from each marketplace.json - Calls marketplace.ComputePendingBumps to diff installed vs. available versions - Prints pending bumps (track-mode only); does NOT touch agent configs by default - Updates .state/targets.json with fresh HeadSHA + FetchedAt timestamps - --apply flag: upgrades bumped plugins then runs a full apply pass - --auto-safe flag placeholder for future non-lossy-only upgrades marketplace/update.go adds ComputePendingBumps + Bump types (pinned/manual modes are silently skipped; default "" → "track"). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ry (Task 7) - internal/render/report.go: TranslationReport + BuildReport + PrintText/PrintJSON - apply emits per-plugin per-agent table after apply and --dry-run - Tests: no-plugin base row, with-plugin rows, partial coverage, text/JSON output Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…uploaded versions (Task 8) - marketplace.DetectSHADrift: compare recorded manifest_sha to freshly-fetched sha256; return SHAWarning for same-version re-uploads. - update command: compute fresh SHAs from plugin cache, call DetectSHADrift, emit manifest-sha-mismatch warning lines before pending bumps. - computeFreshPluginSHAs: reads plugin.json from cache, hashes with sha256. - Tests: no-drift, drift, no-recorded-sha, no-fresh-sha. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…k 9) - apply uses LoadWithCache so plugin MCP servers project into both claude and opencode. - TestIntegration_M4_PluginFanoutClaudeAndOpenCode: local marketplace → install → apply → verify demo-mcp in .claude.json and opencode.json + translation report in output. - TestIntegration_M4_SHAPinning: install writes manifest_sha; re-uploaded bytes at same version trigger manifest-sha-mismatch warning on next update. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
spxrogers
force-pushed
the
claude/agentsync-m4
branch
from
May 7, 2026 02:55
0de0578 to
ab09554
Compare
spxrogers
pushed a commit
that referenced
this pull request
Sep 1, 2026
Closes round-3 review findings on PR #240. 1. The anti-vacuity check was still inferring. It matched substrings against cobra's error prose, which covers only the arg/flag layer — anything rejecting later scored as "it ran", including this repo's own enforceScopeStance, a PersistentPreRunE refusal that never reaches RunE. runBounded now WRAPS the resolved command's RunE, so "did the body start" is observed rather than deduced, and cannot drift with cobra's wording. Third version of this check; the first two were unable to fire at all. It is also falsifiable now. runBoundedE reports instead of failing, so TestRunBoundedDetectsACommandThatNeverRan can assert ran==false for a missing argument, an unknown command AND a PersistentPreRunE refusal — the case the substring list structurally could not catch — plus ran==true for a command that does run. Previously nothing failed if the check were reverted. 2. Subtest fixture bleed. The cleanup unlinked the FIFO but never restored the applied file, so the key-merge subtest ran against a home whose whole-file destination was missing, contradicting the test's own "a real applied home". Measured: with the skips deleted, `import claude` and `reconcile --auto-override` PASSED in a full run and HUNG in isolation — whoever closes #241/#242 would have inherited a row green for the wrong reason. The destination is now restored, and after the fix the row hangs both ways. 3. writeBackFileItem appended "remove or replace the non-regular file at that path" to EVERY read failure, including ENOENT. Deleting a managed file is itself drift and offers [w], so the common path produced "no such file or directory — remove or replace the non-regular file at that path": advice for a situation the user is not in. Gated on errors.Is(err, errDestNotRegular), with a test for the absent case. Third round running that this one message has been the site of a new defect. 4. Prose, instances #8 and #9. The CHANGELOG headline said a "directory" no longer hangs — a directory never hung (os.ReadFile fails it in ~18us with EISDIR); only the diagnosis changes. And destread.go justified leaving the symlink split by claiming AGENTSYNC_ALLOW_SYMLINK_DEST=1 would break, but that variable is read only in internal/iox, on the WRITE path, so a read gate cannot affect it. The real reason is that changing it changes what diff and reconcile have always reported (#229) — and the real consequence, now named, is that under that supported setup `status` reports drift no apply can clear. 5. The hash row computed its expectation with the function under test; salting hashContent left it green. Pinned to a literal digest, break-verified. Also: docs/components.md's "Enforced, not asserted" overstated a two-spelling text matcher whose own LIMITS exempt a read; softened, and the review-audit parenthetical that had survived into a website-mirrored contract page is gone. destread.go now documents that `diff` and readDestFile swallow the refusal and render a refused destination as empty — a poorer diagnosis than it deserves, left to #229 because fixing it changes what those commands print. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M4VNyoCuGXx7pYxNfLVbFG
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.
Summary
marketplace add/remove/list,plugin install/upgrade/enable/disable/remove/list,update [--apply].Test plan
Plan: docs/superpowers/plans/2026-05-04-agentsync-m4-marketplaces.md
🤖 Generated with Claude Code