Skip to content

feat: add opt-in update-check hook for claude and cursor targets#12

Open
steve-calvert-glean wants to merge 4 commits into
mainfrom
feat/plugin-update-check
Open

feat: add opt-in update-check hook for claude and cursor targets#12
steve-calvert-glean wants to merge 4 commits into
mainfrom
feat/plugin-update-check

Conversation

@steve-calvert-glean

Copy link
Copy Markdown
Contributor

Summary

Hosts don't reliably surface plugin updates: Claude Code's auto-update is off by default for third-party marketplaces, and Cursor has no update nudge at all. This adds an opt-in updateCheck target option that generates a session-start hook per emitted plugin (claude and cursor only — the only two targets that run plugin hooks).

  • Each emitted plugin gains scripts/pluginpack-update-check.sh plus a hooks/hooks.json registration, merged into a source-authored hooks/hooks.json when one exists rather than clobbering it.
  • The script compares the version stamped at build time against the latest stable semver git tag of the plugin repo, following update-notifier discipline: throttled to one git ls-remote per repo per 24h (cache shared across sibling plugins pointing at the same repo), fail-open on any error/offline/missing git, and skippable via CI or PLUGINPACK_NO_UPDATE_CHECK=1.
  • On Claude Code the nudge is a user-visible systemMessage with the /plugin update command to run. On Cursor (whose sessionStart hook has no user-facing output) it's injected as agent context instead.
  • Per-plugin opt-out via updateCheck: false; a config-load error if updateCheck is set on copilot/antigravity/codex.

Went through a two-axis code review (standards + spec-vs-plan) before opening this; the review-fixes commit addresses what came back (hardcoded path duplication, a repeated format branch, a duplicated opt-out guard, plus a missing sibling-repo-throttle test and a README gap around the components interaction).

Test plan

  • npm run check (format, lint, typecheck, unit/integration tests, build, docs) passes
  • New tests/update-check.test.ts executes the generated script under /bin/sh with a git shim — covers throttle/cache, corrupt-cache recovery, offline/no-git fallback, CI/env opt-outs, semver tag filtering, and the cross-plugin shared-cache case
  • tests/core.test.ts covers build-level injection, per-plugin version overrides, merging into a source-authored hooks/hooks.json, and all reject paths (unsupported target, missing repository, script collision, invalid hooks.json)
  • tests/conformance.test.ts validates the emitted cursor hooks manifest key against the vendored schema
  • Manually smoke-tested the generated script against the real gleanwork/pluginpack repo

🤖 Generated with Claude Code

steve-calvert-glean and others added 4 commits July 25, 2026 14:58
Hosts don't reliably surface plugin updates: Claude Code's auto-update
is off by default for third-party marketplaces, and Cursor has no
nudge at all. When a target sets `updateCheck`, pluginpack now
generates a session-start hook per emitted plugin that compares the
build-stamped version against the latest git tag of the plugin repo
and nudges the user when outdated — via Claude's user-visible
`systemMessage`, or via agent context on Cursor (whose sessionStart
hook has no user-facing output).

The generated script follows update-notifier discipline: throttled to
one `git ls-remote` per repo per 24h via a shared cache file,
fail-open on any error/offline/missing git, and skippable via CI or
PLUGINPACK_NO_UPDATE_CHECK=1. The hook registration merges into a
source-authored hooks/hooks.json rather than clobbering it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- core.test.ts: build-level coverage for hook injection, per-plugin
  version overrides, merging into a source-authored hooks.json, and
  the reject paths (unsupported target, missing repository, script
  path collision, invalid hooks.json).
- update-check.test.ts: unit tests for the hooks.json merge helpers,
  and end-to-end execution of the generated script under /bin/sh with
  a git shim, covering the throttle/cache, opt-out env vars, offline
  fallback, and semver tag filtering.
- conformance.test.ts: enables updateCheck in the fixture config so
  the emitted cursor `hooks` manifest key and hooks.json validate
  against the existing oracles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
README: new "Update Check" section and config-reference rows for
`updateCheck` (target-level) and the per-plugin opt-out.
CONFORMANCE.md: record the two upstream hook behaviors the feature
relies on (Claude SessionStart systemMessage, Cursor sessionStart
additional_context) with sources and verification date.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- validate.ts: import UPDATE_CHECK_SCRIPT_PATH instead of re-hardcoding
  the path, so validation can't silently desync from generation.
- update-check.ts: collapse the three separate `format === "claude"`
  branches into one per-format config table, and extract the
  duplicated per-plugin opt-out check into `pluginAllowsUpdateCheck`.
- README: document that the generated hook is wired in independently
  of a plugin's `components` selection (mirrors the MCP precedent),
  since Cursor's manifest needs the `hooks` pointer set even when
  `components` excludes "hooks".
- update-check.test.ts: add coverage for the throttle cache being
  shared across sibling plugins pointing at the same repository.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant