Compare commits

..

16 Commits

Author SHA1 Message Date
Mohamed Hegazy
a67587c816 security-guidance: enable LLM review on default macOS Python 3.9
Fixes anthropics/claude-plugins-official#2071 — on macOS where the
default `python3` is Apple's Command Line Tools Python 3.9.6, the
plugin's agentic commit reviewer silently does not run, even when the
user has a newer Python installed.

Three compounding factors in the bug:

1. `sg-python.sh` only checks the major version (`3`), so it always
   picks 3.9 even when 3.10+ is on PATH.
2. `claude_agent_sdk` requires Python >=3.10 — pip install on 3.9
   returns "No matching distribution" -> bootstrap returns BUILD_FAILED.
3. Even with a hand-built 3.12 venv, `llm.py` imports the SDK
   in-process into the hook's interpreter (still 3.9), which raises
   SyntaxError. The existing venv-probe in `ensure_agent_sdk.py` uses
   the venv's own Python (3.12) so it reports NOOP_VENV (healthy) while
   the consumer fails — misleading telemetry on top of silent feature
   degradation.

Per BQ telemetry, 14,073 external macOS users hit
sdk_bootstrap=BUILD_FAILED in the past 4 days (the default-macOS
cohort), out of ~86K total external installed users. Combined with
~20K other users in similar broken-bootstrap states (Windows pre-#2055,
Linux <3.10), about half the installed base has a silently-broken
agentic reviewer.

This PR implements the reporter's items #1, #3, and #4. Item #2
(running the SDK out-of-process) is deferred as a bigger refactor.

Item #1 — hooks/sg-python.sh — prefer >=3.10 binaries via 3-pass probe:

  Pass 1: python3.13 / 3.12 / 3.11 / 3.10 (>=3.10 by name, highest wins)
  Pass 2: bare python3 / python / py -3 (accept only if reported >=3.10)
  Pass 3: bare python3 / python / py -3 (any Python 3, FALLBACK so
          pattern checks still work on macOS-default 3.9 — no regression
          vs today; SDK-dependent paths detect the version mismatch
          inside Python and degrade cleanly via item #4)

Item #4 — ensure_agent_sdk.py — health-check honesty:

Added HOOK_PY_INCOMPATIBLE=6 outcome with short-circuit at top of main():

  if sys.version_info < (3, 10):
      return HOOK_PY_INCOMPATIBLE, "hook_py", f"py_{...}"

Telemetry consequences after rollout: sdk_bootstrap=6 is a new clean
bucket; some users currently miscounted in sdk_bootstrap=3 BUILD_FAILED
(wasted pip cycles) and sdk_bootstrap=1 NOOP_VENV (falsely-healthy)
move to sdk_bootstrap=6. The remaining NOOP_VENV count becomes
trustworthy.

Item #3 — ensure_agent_sdk.py — one-time user-visible notice:

When outcome == HOOK_PY_INCOMPATIBLE and a marker file at
`~/.claude/security/.agentic_unavailable_notice_v<pv>` doesn't exist,
the SessionStart response includes hookSpecificOutput.additionalContext
+ systemMessage explaining the situation. Marker file is plugin-
version-keyed so a future fix (e.g. shipping out-of-process SDK) can
bump pv and re-notify users.

BUILD_FAILED is intentionally excluded from the notice — it covers
transient causes where a permanent banner would mislead.

Verified locally on macOS Python 3.13:
  - py_compile clean on both files.
  - Existing 45-test smoke + extensibility suite: 45/45 PASS in 2.50s.
  - Unit test of simulated 3.9 path: HOOK_PY_INCOMPATIBLE returned with
    correct phase/kind; notice shown on first call, suppressed on
    second, reshown on bumped pv; BUILD_FAILED correctly does NOT
    trigger notice.

NOT verified: actual Python 3.9 behavior end-to-end (would need a 3.9
install). Worth a follow-up smoke test in a 3.9 venv before next
release. The unit test simulating 3.9 covers the logic but not the
runtime invocation through the shim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 22:58:01 -07:00
Bryan Thompson
502de97746 Add vibe-prospecting plugin (#1997) 2026-05-28 15:30:04 -07:00
Bryan Thompson
679f52da9e feat(scan): emit per-entry sticky verdict comments (#2009)
Adds an `emit-verdict` job to scan-plugins.yml that posts a sticky
comment per scanned entry to the corresponding bump PR, with marker
`<!-- bump-pr-verdict:<name> -->`. The body is a schema_v1 JSON block,
the same shape `anthropics/claude-plugins-community-internal`'s
`scan-external-plugins.yml` already emits, so any consumer that already
reads verdicts from that schema works uniformly across both repos.

What this enables
-----------------

Lets downstream consumers (label automation, dashboards, anything that
wants per-entry verdict signal) read verdicts directly from the PR
rather than scraping job logs or downloading artifacts. The current
options are log-scraping (truncated after log retention) or fetching
the `scan-verdicts` artifact (retention-limited and only after upload
succeeds).

What does NOT change
--------------------

- The `scan` required check is unaffected (emit-verdict is
  `continue-on-error: true` at the job level — failures here MUST NOT
  block the required gate).
- Verdict cache, scan flow, and revert-failed-bumps.yml are unchanged.
- No new permission scopes (uses `pull-requests: write` at the job
  level, identical to other PR-commenting jobs in this repo).

Schema notes
------------

- `scan.*` axes (clone, schema, binaries, etc.) emit as "skipped" —
  this workflow runs the policy review only, not per-entry static
  checks. Shape kept compatible with -internal's schema_v1 so the
  same consumers work uniformly on both repos.
- `policy.has_broad_scope_hooks`, `has_undisclosed_telemetry`,
  `description_matches_behavior` emit as null — those granular axes
  aren't surfaced by this workflow's per-entry artifact yet. Consumers
  that map `null → "?"` for display already handle this gracefully.
- `policy.status` is execution state (not outcome). Map source →
  status: scan-action-run → "ran"; cache-served → "cached". Outcome
  lives in `policy.passes`. policy.status vocabulary matches the
  `ran|cached|missing|gated_out|infra_error` convention from
  -internal's emit-verdict.

PR resolution
-------------

`pull_request` events carry the PR number directly. The bump workflow
creates bump PRs via GITHUB_TOKEN (which doesn't fire `pull_request`
triggers — recursion guard) and dispatches this scan via
`workflow_dispatch` on the bump branch; in that case the job looks up
the open PR by head ref via REST. No PR found (scan_all dispatch on
main, etc.) → no-op with notice.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 15:29:59 -07:00
Bryan Thompson
13a0208f38 Add Skill-bundle plugins section to README (#2067)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 15:29:53 -07:00
Noah Zweben
e9b54375b8 Add Apache 2.0 LICENSE to repo root (#1999)
Each internal plugin already carries an Apache 2.0 LICENSE file at its root.
This adds the same license at the repository root for clarity.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-28 08:21:09 -05:00
Bryan Thompson
fc49e6815f recommender: add Convex to database/backend MCP coverage (#1981)
Picked up from sethconvex's PR #1966 (auto-closed by membership gate),
split off from #1980 (Convex plugin entry refresh) so the editorial
addition to claude-automation-recommender gets its own review.

Changes:

- SKILL.md: add `convex` to the package.json dep-detection grep, update
  the Database row in the indicator table to name Convex, and add a
  Convex MCP row to the MCP recommendation table.

- references/mcp-servers.md: new "Convex MCP" section in the Databases
  group (Supabase / Convex / PostgreSQL / Neon / Turso), and a row in
  the Detection Patterns quick reference.

Convex publishes its MCP server via the `convex` npm package
(`npx convex mcp start`), exposing tables, function-spec, data,
run-once-query, logs, env list/set/get. Same row pattern as the
existing database/backend MCP entries.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 07:59:33 -05:00
Bryan Thompson
06b6d5b96f Refresh Convex plugin: rename to convex, bump SHA to v1.0.1, richer metadata (#1980)
* Refresh Convex plugin: rename to `convex`, bump SHA to v1.0.1, richer metadata

Picked up from sethconvex's PR #1966 (auto-closed by membership gate).
Original entry added by Tobin in PR #1918 (2026-05-18).

Changes to the Convex marketplace.json entry:

- **Rename slug** `convex-backend` → `convex` to match the single-brand-word
  convention used by every peer in the database/backend neighborhood
  (`supabase`, `firebase`, `mongodb`, `prisma`, `clickhouse`, `cockroachdb`,
  `cloud-sql-postgresql`, `alloydb`). New `displayName: "Convex"` keeps the
  directory UI label unchanged.

- **Bump SHA pin to `59663a5`** (plugin v1.0.1) — current HEAD of
  `get-convex/convex-backend-skill` `main`. New SHA adds:
  - `agents/convex-expert.md` — subagent encoding non-negotiable Convex code
    rules (object-form syntax, validator requirements, index naming,
    internal-vs-public, schema evolution, resource limits). Loaded only
    when delegated to.
  - `monitors/monitors.json` — runtime-error monitor streaming
    `npx convex logs`, surfacing matched errors as notifications. Self-guards
    on unlinked projects. `when: on-skill-invoke:design` so it only starts
    after the skill is invoked.
  - `.mcp.json` — auto-wires the Convex MCP server
    (`npx -y convex@latest mcp start`, local stdio).
  - Public-facing README (install / how-to-use / what's bundled / capabilities).
  - `paths` gate on the skill — `[convex/**, convex.json, package.json]` for
    auto-invocation precision.
  - `description` / `when_to_use` split on the skill frontmatter.

- **Refresh marketplace entry metadata** — `displayName`, `keywords` (15
  discovery tags), `author.url`, expanded `description`, category changed from
  `development` to `database` (matches every peer), `homepage` repointed at the
  plugin repo (matches the `supabase` pattern).

Verified locally:
- Author affiliation confirmed: `seth@convex.dev` commit email, write access
  to the canonical `get-convex/` org.
- `claude plugin validate`: PASS.
- Static audit: PASS @ 92 (manifest 96, security 93, quality 80, docs 100).
- MCP server is local stdio (`has_remote_mcp=false`) — passes the -official
  add-official Phase 2e gate.

Recommender skill changes from the original PR are split into a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Re-pin Convex to 5e59870 (post upstream fix merge)

Upstream PR get-convex/convex-backend-skill#1 merged 2026-05-23. The
agents-field array-shape fix now applies; claude plugin validate passes
on both the full plugin (with marketplace.json) and the isolated
plugin.json — including the external-validator gate this PR previously
failed on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 03:54:06 +01:00
Mohamed Hegazy
2a3dd81146 Merge pull request #2055 from anthropics/fix-windows-agentic-reviewer
security-guidance: enable agentic commit reviewer on Windows
2026-05-27 15:43:11 -07:00
Mohamed Hegazy
c11244778d Address Windows verification: --prefer-binary + pywin32 bootstrap
The first round of this PR removed SKIP_WIN32, fixed venv_py to use
Scripts/python.exe, and added Lib/site-packages to the consumer glob —
all necessary. Windows verification (Win11 ARM64, Py 3.13, Git Bash)
showed two more blockers, both addressed here.

1. Pip dependency resolver picks unbuildable cryptography on ARM64.

   Without --prefer-binary, pip picks a cryptography version with no
   published ARM64 wheel and tries to build it from source. That needs
   Rust/Cargo, almost never present on user machines → BUILD_FAILED
   with err_kind=other:cryptography. A binary wheel exists for an
   adjacent version (cryptography-46.0.3-cp311-abi3-win_arm64.whl);
   --prefer-binary tells pip to pick it. Cross-platform safe (no-op
   where the latest version already has a wheel).

2. pywin32 .pth files aren't processed by sys.path.insert().

   With the venv built, ensure_agent_sdk.py's post-build probe passes
   (it runs from venv_py, where Python's site.py at startup processes
   pywin32.pth and registers win32/, win32/lib/ plus runs
   pywin32_bootstrap.py to set the DLL search dir). But llm.py runs in
   the hook's SYSTEM Python and adds the venv via sys.path.insert(),
   which doesn't trigger site.py at all. Without the bootstrap, the
   SDK's mcp.client.stdio → mcp.os.win32.utilities chain raises
   ModuleNotFoundError: pywintypes and the agentic reviewer falls back
   to single-shot silently — exactly the symptom this PR is trying to
   fix. The probe says NOOP_VENV; the actual consumer fails. Probe and
   consumer use different Pythons.

   Replicate what site.py would do: after inserting site-packages,
   also insert win32/ and win32/lib/, then exec pywin32_bootstrap.py.
   Pulled into a shared helper _inject_agent_sdk_venv_into_syspath()
   so both consumer sites (3P SDK fallback, agentic_review fallback)
   call the same code — Windows handling stays in one place.

Verified on macOS (POSIX path unchanged):
- Helper end-to-end test: POSIX-layout venv detected + fake package
  imports successfully via the injected path
- Windows-layout venv also detected; win32 branch correctly skipped
  via sys.platform check
- Both files pass py_compile

Credit: @mhegazy verified the previous commit on Win11 ARM64 / Py 3.13
/ Git Bash, surfaced both issues end-to-end, and provided the exact
fix patterns. This commit applies them with the pywin32 part factored
into a shared helper (vs. inlining at both consumer sites).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 15:07:33 -07:00
Mohamed Hegazy
4decd2e3b2 Enable agentic commit reviewer on Windows
The agentic reviewer is silently no-op on Windows today. SessionStart
bootstrap (ensure_agent_sdk.py) short-circuits with SKIP_WIN32 because
the consumer glob in llm.py only matches POSIX venv layout
(lib/pythonX.Y/site-packages). On Windows, venvs use Lib/site-packages
(capital L, no pythonX.Y subdir), so even if a venv got built the
glob wouldn't find its contents.

Result: Windows users on default installs (no system-wide
claude_agent_sdk) get layer 1 (pattern warnings) and layer 2 (single-
shot LLM diff review) but not layer 3 — the cross-file agentic review
that catches IDOR, auth-bypass, cross-file SSRF, and other things that
need to read related files. Plugin description claims layer 3 but it
silently doesn't run.

Three changes:

1. llm.py — extend the consumer glob (2 sites: 3P SDK fallback at
   ~L297, agentic_review fallback at ~L1090) to also match the Windows
   Lib/site-packages layout, so a venv built on Windows is actually
   discoverable.

2. ensure_agent_sdk.py — remove the sys.platform == 'win32' early-exit
   so the SessionStart bootstrap builds the venv on Windows too.
   Outcome code 4 (formerly SKIP_WIN32) is retired but not reused so
   pre-fix telemetry rows still decode correctly.

3. ensure_agent_sdk.py — venv_py path now branches on sys.platform:
   Windows venvs put the interpreter at Scripts\python.exe; POSIX
   uses bin/python. Previously assumed POSIX, so even with the glob
   fix, the post-build SDK-importability probe would fail on Windows.

Verified locally on macOS:
- glob test: both layouts now match (POSIX venv detected, simulated
  Windows venv also detected via the new Lib/site-packages branch)
- both files pass py_compile
- POSIX path unchanged (sys.platform != 'win32' so old branch runs)

Not verified on Windows in this commit — needs an actual Windows
runner to confirm the venv build + SDK import + subprocess plumbing
all work end-to-end. The SDK spawns a child claude.exe; Windows
process plumbing has its own quirks (shell semantics, path escaping)
that may surface separately. Worth a controlled rollout (one-week
soak under env-var opt-in before flipping default).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:50:51 -07:00
Mohamed Hegazy
e77ff913ad Merge pull request #2054 from anthropics/fix-2043-windows-posix-path
security-guidance: convert POSIX script paths to Windows form on Git Bash
2026-05-27 14:49:38 -07:00
Mohamed Hegazy
390c2fe785 Convert POSIX script paths to Windows form before exec on Git Bash
Fixes #2043. On Git Bash for Windows, Claude Code hands script paths to
the shim in POSIX form (`/c/Users/...`). We exec a Windows `python.exe`
(the `python3` Microsoft Store stub fails the probe), and Windows Python
interprets the leading `/` as the root of the current drive — `/c/...`
becomes `C:\c\Users\...` or `D:\c\Users\...` depending on which
drive the shell happens to be on, fails with ENOENT, and every
Edit/Write/MultiEdit blocks until the session restarts.

Convert absolute path args via `cygpath -w` (a Git Bash builtin) before
exec. Guarded by `command -v cygpath` so macOS/Linux fall straight
through unchanged; `cygpath -w` is idempotent on already-Windows paths
so the rare mixed-form case is safe. Only `/*` paths are converted —
Windows-form paths reaching the shim are already openable by python.exe.

Verified locally:
- cygpath absent on macOS → guard skips → POSIX behavior unchanged
- end-to-end shim invocation with a POSIX path on macOS exits 0
- stubbed cygpath -w on /c/Users/test/hook.py produces C:\Users\test\hook.py

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:04:09 -07:00
github-actions[bot]
1109c43a9d Bump 68 plugin SHA pin(s) to upstream HEAD (#2049)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-27 18:09:45 +01:00
Noah Zweben
4c4b3009e0 ci: validate Apache 2.0 LICENSE file exists in every plugin (#2028)
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-27 17:25:23 +01:00
Bryan Thompson
fd06e9957e Bump carta-* SHA pins (3 plugins) to upstream HEAD (#2052) 2026-05-27 17:23:47 +01:00
Mohamed Hegazy
a8f5f1b3c9 Merge pull request #2041 from anthropics/security-guidance-update
Update security-guidance plugin
2026-05-26 14:07:55 -07:00
10 changed files with 799 additions and 104 deletions

View File

@@ -19,7 +19,7 @@
"url": "https://github.com/42Crunch-AI/claude-plugins.git",
"path": "plugins/api-security-testing",
"ref": "v1.5.5",
"sha": "a175b24f7b34852b70c78c21545cce8037eb3112"
"sha": "5c8074d846b852c21da23bbf6effbfdabb18ba2d"
},
"homepage": "https://42crunch.com"
},
@@ -35,7 +35,7 @@
"url": "https://github.com/adobe/skills.git",
"path": "plugins/creative-cloud/adobe-for-creativity",
"ref": "main",
"sha": "dedb9597f878072ec2f6b1fd051900ccb913d653"
"sha": "ecd1e2b2c493ba0627774f36a897bd44d47fef1d"
},
"homepage": "https://github.com/adobe/skills/tree/main/plugins/creative-cloud/adobe-for-creativity"
},
@@ -93,7 +93,7 @@
"url": "https://github.com/Airtable/skills.git",
"path": "plugins/airtable",
"ref": "main",
"sha": "1a8db588c72d31550ef6ee39b716598111840583"
"sha": "21d2fe52774d861e2f2f997eeac2bf965e8590b8"
},
"homepage": "https://www.airtable.com"
},
@@ -120,7 +120,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/amazon-location-service",
"ref": "main",
"sha": "95381e8bcb92f58a28edb4f83eb7e163c7461a0a"
"sha": "5d982e8a5f1e0b06545adac69ff0348141587725"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -161,7 +161,7 @@
"source": {
"source": "url",
"url": "https://github.com/apollographql/skills.git",
"sha": "64d4087d629e413da1bac780abeaefd653847440"
"sha": "e1d07720e9bcfbf867fa2907192c94ec2ed421e1"
},
"homepage": "https://www.apollographql.com"
},
@@ -226,7 +226,7 @@
"source": "url",
"url": "https://github.com/BrainBlend-AI/atomic-agents.git",
"path": "claude-plugin/atomic-agents",
"sha": "f849087b26bbb6fb5e63acb60f2b566ce874aaa7"
"sha": "c4e905c49884747be65e7ed42ccfb118c67f57ac"
},
"homepage": "https://github.com/BrainBlend-AI/atomic-agents",
"tags": [
@@ -274,7 +274,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/aws-amplify",
"ref": "main",
"sha": "95381e8bcb92f58a28edb4f83eb7e163c7461a0a"
"sha": "5d982e8a5f1e0b06545adac69ff0348141587725"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -335,7 +335,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/aws-serverless",
"ref": "main",
"sha": "95381e8bcb92f58a28edb4f83eb7e163c7461a0a"
"sha": "5d982e8a5f1e0b06545adac69ff0348141587725"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -346,7 +346,7 @@
"source": {
"source": "url",
"url": "https://github.com/microsoft/azure-skills.git",
"sha": "350e050ca30fe3464483f66193a8ff3a973b1d77"
"sha": "d02fd24f151f5133650eaa78e7da3cac2cedd72f"
},
"homepage": "https://github.com/microsoft/azure-skills"
},
@@ -412,7 +412,7 @@
"source": {
"source": "url",
"url": "https://github.com/brightdata/skills.git",
"sha": "37145178dfc9b52e28dd224afeccc7184f7711fc"
"sha": "071e9d4db77c8561e333799f25ea85f11f7b667d"
},
"homepage": "https://docs.brightdata.com"
},
@@ -426,7 +426,7 @@
"source": {
"source": "url",
"url": "https://github.com/buildkite/skills.git",
"sha": "252476999d4813b8f7412215e47328e1b60ae51c"
"sha": "a43e944f2017146d0a6b7d8ea2bf21b02484e1d3"
},
"homepage": "https://buildkite.com"
},
@@ -442,7 +442,7 @@
"url": "https://github.com/carta/plugins.git",
"path": "plugins/carta-cap-table",
"ref": "main",
"sha": "b17fbfb0331e3903e5235b2fe21eb7a65c1bc394"
"sha": "5e6c9d1cfa3bff9b91138e7906c6eb088fd9a66a"
},
"homepage": "https://carta.com"
},
@@ -458,7 +458,7 @@
"url": "https://github.com/carta/plugins.git",
"path": "plugins/carta-crm",
"ref": "main",
"sha": "b17fbfb0331e3903e5235b2fe21eb7a65c1bc394"
"sha": "5e6c9d1cfa3bff9b91138e7906c6eb088fd9a66a"
},
"homepage": "https://carta.com"
},
@@ -474,7 +474,7 @@
"url": "https://github.com/carta/plugins.git",
"path": "plugins/carta-investors",
"ref": "main",
"sha": "b17fbfb0331e3903e5235b2fe21eb7a65c1bc394"
"sha": "5e6c9d1cfa3bff9b91138e7906c6eb088fd9a66a"
},
"homepage": "https://carta.com"
},
@@ -490,7 +490,7 @@
"source": {
"source": "url",
"url": "https://github.com/cap-js/mcp-server.git",
"sha": "7d477ed55bbf3dd302a45d2adbd9072bcb512e87"
"sha": "92dc99f5ba0c56957ed5d390484693a69ebd1206"
},
"homepage": "https://cap.cloud.sap/"
},
@@ -501,7 +501,7 @@
"source": {
"source": "url",
"url": "https://github.com/ChromeDevTools/chrome-devtools-mcp.git",
"sha": "8e8e83e3f8d150689ffb58c3b977eb72016c7b3f"
"sha": "60be3e6bc157bd1121ea1d4b6ad59e37a73cac3e"
},
"homepage": "https://github.com/ChromeDevTools/chrome-devtools-mcp"
},
@@ -517,7 +517,7 @@
"url": "https://github.com/circlefin/skills.git",
"path": "plugins/circle",
"ref": "master",
"sha": "d076fe3ff992a1a9c30e2770031970b31429e905"
"sha": "8ee9281f6e5ab737236ce969348adc463e6c2f79"
},
"homepage": "https://www.circle.com"
},
@@ -595,7 +595,7 @@
"source": {
"source": "url",
"url": "https://github.com/ClickHouse/clickhouse-claude-code-plugin.git",
"sha": "13a2df004af0df46661c9de2d4ef4e85eba2f040"
"sha": "36889764f504cb92ab71ffe54b4c55488290ed7f"
},
"homepage": "https://github.com/ClickHouse/clickhouse-claude-code-plugin"
},
@@ -609,7 +609,7 @@
"source": {
"source": "url",
"url": "https://github.com/ClickHouse/agent-skills.git",
"sha": "67b45c5666b6999677ab3bbba4a27a7f532853af"
"sha": "46ef08ccf32fa28587b64e0c79106ff437dc8fcb"
},
"homepage": "https://clickhouse.com"
},
@@ -623,7 +623,7 @@
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/cloud-sql-postgresql.git",
"sha": "966f7b883998692d05389e4c3d3793412ca0659f"
"sha": "5b9bc21c13324282e50183326709c533b49a97f3"
},
"homepage": "https://cloud.google.com/sql"
},
@@ -716,7 +716,7 @@
"source": {
"source": "url",
"url": "https://github.com/CodSpeedHQ/codspeed.git",
"sha": "4eac647797a5b836ef780d498e494c34f001ede2"
"sha": "ecf3c2ebf959479126d631ad39d317738d559388"
},
"homepage": "https://codspeed.io"
},
@@ -742,18 +742,37 @@
]
},
{
"name": "convex-backend",
"description": "Convex backend skill for building reactive, type-safe, production-grade backends. Helps Claude design schemas, server functions, auth, file storage, scheduled jobs, and real-time multiplayer features on Convex.",
"name": "convex",
"displayName": "Convex",
"description": "Official Convex plugin for Claude Code with bundled Convex skills, the convex-expert subagent for code-writing, a runtime-error monitor, and MCP access for backend development, schema design, real-time features, auth, file storage, scheduled jobs, and AI agents.",
"author": {
"name": "Convex"
"name": "Convex",
"url": "https://convex.dev"
},
"category": "development",
"category": "database",
"source": {
"source": "url",
"url": "https://github.com/get-convex/convex-backend-skill.git",
"sha": "9acbc5495dd26749a5e6341dc2438146c4caa03b"
"sha": "5e59870cda2a5892e18a7164d1a46fcf57b70bea"
},
"homepage": "https://convex.dev"
"homepage": "https://github.com/get-convex/convex-backend-skill",
"keywords": [
"convex",
"backend",
"database",
"realtime",
"reactive",
"websocket",
"auth",
"storage",
"scheduler",
"cron",
"agent",
"rag",
"mobile",
"typescript",
"mcp"
]
},
{
"name": "crowdstrike-falcon-foundry",
@@ -765,7 +784,7 @@
"source": {
"source": "url",
"url": "https://github.com/CrowdStrike/foundry-skills.git",
"sha": "4b517aa5729d5bb5e397ff779f98eb05c91d1b21"
"sha": "99edea095f4e32ed008706b55257d0893fb93387"
},
"homepage": "https://github.com/CrowdStrike/foundry-skills"
},
@@ -811,7 +830,7 @@
"source": {
"source": "url",
"url": "https://github.com/dash0hq/dash0-agent-plugin.git",
"sha": "025a02ba35a7ecc72a8010aaae2e6152308224f4"
"sha": "2909be7ebc2804af464e0d7f660ccc2b62d94623"
},
"homepage": "https://dash0.com/"
},
@@ -859,7 +878,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/databases-on-aws",
"ref": "main",
"sha": "95381e8bcb92f58a28edb4f83eb7e163c7461a0a"
"sha": "5d982e8a5f1e0b06545adac69ff0348141587725"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -887,7 +906,7 @@
"source": {
"source": "url",
"url": "https://github.com/datahub-project/datahub-skills.git",
"sha": "f7c7c53648b71dc0841742781e108051d46fa360"
"sha": "68585b1710601c8195eda1e7690218cc0a31d81d"
},
"homepage": "https://datahub.com"
},
@@ -901,7 +920,7 @@
"source": {
"source": "url",
"url": "https://github.com/datarobot-oss/datarobot-agent-skills.git",
"sha": "79bccc455df03d880a90d6076e4e5683b1f3288c"
"sha": "8124faae2154117382b1046aa74d8901a3ffe930"
},
"homepage": "https://datarobot.com"
},
@@ -914,7 +933,7 @@
"url": "https://github.com/microsoft/Dataverse-skills.git",
"path": ".github/plugins/dataverse",
"ref": "main",
"sha": "5f186bf8ab1a3d6e242492d982276bbd7443ee0f"
"sha": "ab906c960db0f2da83c2cb92a3fd162ccaba9cb9"
},
"homepage": "https://github.com/microsoft/Dataverse-skills"
},
@@ -927,7 +946,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/deploy-on-aws",
"ref": "main",
"sha": "95381e8bcb92f58a28edb4f83eb7e163c7461a0a"
"sha": "5d982e8a5f1e0b06545adac69ff0348141587725"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -963,7 +982,7 @@
"source": {
"source": "url",
"url": "https://github.com/dominodatalab/domino-claude-plugin.git",
"sha": "3cc932f7a5a29ad6fab2a514df71513b1b0e0557"
"sha": "47c6e0a7daa11b21eb6e12779c9d679569e8ffe2"
},
"homepage": "https://www.domino.ai"
},
@@ -1029,7 +1048,7 @@
"url": "https://github.com/expo/skills.git",
"path": "plugins/expo",
"ref": "main",
"sha": "434c935cfdce54e02b6164148e52cd151b2bc0c0"
"sha": "510373b50956ef4dc84c20bb4c9cce70b618aa06"
},
"homepage": "https://github.com/expo/skills/blob/main/plugins/expo/README.md"
},
@@ -1045,7 +1064,7 @@
"source": {
"source": "url",
"url": "https://github.com/fastly/fastly-agent-toolkit.git",
"sha": "e0f4205723b843de0b07da4a2aea6c84a3bcb579"
"sha": "6bd17d685a1b361a2b368bf0236f39efb1be62d6"
},
"homepage": "https://github.com/fastly/fastly-agent-toolkit/blob/main/README.md"
},
@@ -1066,7 +1085,7 @@
"source": {
"source": "url",
"url": "https://github.com/voxel51/fiftyone-skills.git",
"sha": "8b987ade2d04b85ea82c109f48d7234838b28b82"
"sha": "6c002d680529e35a2e04adc34c03b564a3991728"
},
"homepage": "https://docs.voxel51.com/"
},
@@ -1095,7 +1114,7 @@
"source": {
"source": "url",
"url": "https://github.com/firecrawl/firecrawl-claude-plugin.git",
"sha": "48edd7943009eb4442a6f0102bbd0c251eecef3e"
"sha": "01d11b30ace699a27f9ea7decf6ce6c9857f71ff"
},
"homepage": "https://github.com/firecrawl/firecrawl-claude-plugin.git"
},
@@ -1198,7 +1217,7 @@
"source": {
"source": "url",
"url": "https://github.com/huggingface/skills.git",
"sha": "5e3b4d2226d1beaa6a8a4df3739b6f68bd36521b"
"sha": "7a493b09c81aae09a41bd2e1fa33dfc0f68acd75"
},
"homepage": "https://github.com/huggingface/skills.git"
},
@@ -1212,7 +1231,7 @@
"source": {
"source": "url",
"url": "https://github.com/hunter-io/claude-plugin.git",
"sha": "592cd27476935013d3652c2e2810f5267bd65a02"
"sha": "c67942395cde155e9ad4ed8e3a137926f9992fb8"
},
"homepage": "https://hunter.io"
},
@@ -1226,7 +1245,7 @@
"source": {
"source": "url",
"url": "https://github.com/heygen-com/hyperframes.git",
"sha": "114b83bbf6bfece44480828acd14118d2854af8e"
"sha": "7ea4d1c1314bd60d5273efa92626bd1d0f9c621d"
},
"homepage": "https://hyperframes.heygen.com"
},
@@ -1280,7 +1299,7 @@
"source": "github",
"repo": "jfrog/claude-plugin",
"commit": "259c8e718266c16e99b4f30ae9b1ed0f9f00d98d",
"sha": "2387bffa924a3cb8fd99f67b3bf09976d5f0c6b5"
"sha": "8324c7fc9a5561398fe57b8a56db53bdbf1e2cda"
},
"homepage": "https://jfrog.com"
},
@@ -1391,7 +1410,7 @@
"url": "https://github.com/pydantic/skills.git",
"path": "plugins/logfire",
"ref": "main",
"sha": "a332dc8bd9215d2ee6deb2304af78cd71fba3bb2"
"sha": "0c38c5bb5679f6cc41956bbbf811396a0d108ac9"
},
"homepage": "https://github.com/pydantic/skills/tree/main/plugins/logfire"
},
@@ -1504,7 +1523,7 @@
"url": "https://github.com/mercadopago/mercadopago-claude-marketplace.git",
"path": "plugins/mercadopago",
"ref": "main",
"sha": "63ff263c40e1eda642ae2038e87adaa5781f4939"
"sha": "f52c138924d8035b39e8fe02d41c6712fc41ceb4"
},
"homepage": "https://github.com/mercadopago/mercadopago-claude-marketplace/tree/main/plugins/mercadopago"
},
@@ -1566,7 +1585,7 @@
"url": "https://github.com/neondatabase/agent-skills.git",
"path": "plugins/neon-postgres",
"ref": "main",
"sha": "f8281c1dbe55914b223ef15c7131d334435ed298"
"sha": "bd9ec7ff273ce54bdd3ebe581d5b0802a3479618"
},
"homepage": "https://github.com/neondatabase/agent-skills/tree/main/plugins/neon-postgres"
},
@@ -1619,7 +1638,7 @@
"source": {
"source": "url",
"url": "https://github.com/Nimbleway/agent-skills.git",
"sha": "b3ab05d3c7c88857940c575e0f46752297b2249b"
"sha": "95ed06468957ddc9de609b25c390b30c3864eac8"
},
"homepage": "https://docs.nimbleway.com/integrations/agent-skills/plugin-installation"
},
@@ -1662,7 +1681,7 @@
"url": "https://github.com/growthxai/output.git",
"path": "coding_assistants/claude/plugins/outputai",
"ref": "main",
"sha": "a45094aac1badfa9a3dba0b2cdccdd7a14cfdc45"
"sha": "93dd22ee568a97911a332b5aa0d9cebb2b6f7da1"
},
"homepage": "https://output.ai"
},
@@ -1710,7 +1729,7 @@
"source": {
"source": "url",
"url": "https://github.com/gopigment/ai-plugins.git",
"sha": "ea85aea2ecf9ce761d32b8f6d32ffe0be503f7e1"
"sha": "4bf16c80558416b9d69fa6531af8588fb2fcbe27"
},
"homepage": "https://www.pigment.com"
},
@@ -1721,7 +1740,7 @@
"source": {
"source": "url",
"url": "https://github.com/pinecone-io/pinecone-claude-code-plugin.git",
"sha": "53f52059f9ff9bb064f3dc9f299934e8773a642f"
"sha": "9af99dc1dc10ce291ec67dc51d46199544a0cd4f"
},
"homepage": "https://github.com/pinecone-io/pinecone-claude-code-plugin"
},
@@ -1772,7 +1791,7 @@
"source": {
"source": "url",
"url": "https://github.com/PostHog/ai-plugin.git",
"sha": "ecc5244bb70e30532da9559b93740527e08761ca"
"sha": "1b743cdbc568de81da5f41503e5c7caa35a4b270"
},
"homepage": "https://posthog.com/docs/model-context-protocol"
},
@@ -1827,7 +1846,7 @@
"url": "https://github.com/pydantic/skills.git",
"path": "plugins/ai",
"ref": "main",
"sha": "a332dc8bd9215d2ee6deb2304af78cd71fba3bb2"
"sha": "0c38c5bb5679f6cc41956bbbf811396a0d108ac9"
},
"homepage": "https://github.com/pydantic/skills/tree/main/plugins/ai"
},
@@ -1865,7 +1884,7 @@
"source": {
"source": "url",
"url": "https://github.com/qdrant/skills.git",
"sha": "f108892268ea7518b528889f4604d5689f731747"
"sha": "1390c811e03922b822dc9e12b832ba4dc82e0bf0"
},
"homepage": "https://skills.qdrant.tech"
},
@@ -1904,7 +1923,7 @@
"source": {
"source": "url",
"url": "https://github.com/quarkusio/quarkus-agent-mcp.git",
"sha": "d0925a0b761773f55fabdaf27d5dc9cf9232cfd2"
"sha": "77fd36284a80b3ed1bde3d2fe48a0b2f99e4941e"
},
"homepage": "https://quarkus.io"
},
@@ -1917,7 +1936,7 @@
"url": "https://github.com/railwayapp/railway-skills.git",
"path": "plugins/railway",
"ref": "main",
"sha": "6ef46dde395e7e6f64179bbaa41bac420adca346"
"sha": "7718b39037adb6fb33948ff751be7f7086f2da83"
},
"homepage": "https://docs.railway.com/ai/claude-code-plugin"
},
@@ -1940,7 +1959,7 @@
"source": "url",
"url": "https://github.com/RevenueCat/rc-claude-code-plugin.git",
"path": "revenuecat",
"sha": "407e4651ff74dbaf47c457948ab540e620403c2a"
"sha": "81262a339601c4b64b909c370225cbd7917ade1f"
},
"homepage": "https://www.revenuecat.com"
},
@@ -1956,7 +1975,7 @@
"url": "https://github.com/redis/agent-skills.git",
"path": "plugins/redis-development",
"ref": "main",
"sha": "6edba11904bec94b0e2a35b220476ac53ad6df50"
"sha": "18da4e42371f7eee0dcfafd8461effd41de351e9"
},
"homepage": "https://redis.io"
},
@@ -1966,7 +1985,7 @@
"source": {
"source": "url",
"url": "https://github.com/Digital-Process-Tools/claude-remember.git",
"sha": "aa55ba3f553e23f4d84387f5d7ece1ba0ce68d93"
"sha": "c9b34417a8132f0416411a0ca51d009a256a3acc"
},
"homepage": "https://github.com/Digital-Process-Tools/claude-remember"
},
@@ -1992,7 +2011,7 @@
"source": "url",
"url": "https://github.com/RevenueCat/rc-claude-code-plugin.git",
"path": "revenuecat",
"sha": "407e4651ff74dbaf47c457948ab540e620403c2a"
"sha": "81262a339601c4b64b909c370225cbd7917ade1f"
},
"homepage": "https://www.revenuecat.com"
},
@@ -2094,7 +2113,7 @@
"source": {
"source": "url",
"url": "https://github.com/cap-js/mcp-server.git",
"sha": "7d477ed55bbf3dd302a45d2adbd9072bcb512e87"
"sha": "92dc99f5ba0c56957ed5d390484693a69ebd1206"
},
"homepage": "https://cap.cloud.sap/"
},
@@ -2112,7 +2131,7 @@
"url": "https://github.com/SAP/open-ux-tools.git",
"path": "packages/fiori-mcp-server",
"ref": "main",
"sha": "cb5a5b2cd1572828229f510ec11ba6ac4e631960"
"sha": "d2a6fce818f3c046c5bbb041507be4632f926602"
},
"homepage": "https://github.com/SAP/open-ux-tools/tree/main/packages/fiori-mcp-server"
},
@@ -2144,7 +2163,7 @@
"url": "https://github.com/spotify/save-to-spotify.git",
"path": "plugin",
"ref": "main",
"sha": "af2f6faeb4139fd33a97aefcbadae17f792216e8"
"sha": "35527660378c769bcbcfba89d8086d8b9fc4fccb"
},
"homepage": "https://github.com/spotify/save-to-spotify"
},
@@ -2179,7 +2198,7 @@
"source": {
"source": "url",
"url": "https://github.com/getsentry/sentry-for-claude.git",
"sha": "cf7efd373069d6fb073413324fe313319fb54ad9"
"sha": "ed0875684192bb8a050297a896657ff9db1ffdf5"
},
"homepage": "https://github.com/getsentry/sentry-for-claude/tree/main"
},
@@ -2195,7 +2214,7 @@
"url": "https://github.com/getsentry/cli.git",
"path": "plugins/sentry-cli",
"ref": "main",
"sha": "1c97cbf6d8fb2ad2f76b22cfdb687b4d504abfd0"
"sha": "d9bcd70eaa467fb3ddf591bfbfb0686fd1e9c016"
},
"homepage": "https://sentry.io"
},
@@ -2370,7 +2389,7 @@
"source": "url",
"url": "https://github.com/sumup/sumup-skills.git",
"path": "providers/claude/plugin",
"sha": "a4b5a9789e10e27fb375b68279bb0916074b8dd4"
"sha": "715464b459def2d16e930e9ec8008f60e18a8b4d"
},
"homepage": "https://www.sumup.com/"
},
@@ -2381,7 +2400,7 @@
"source": {
"source": "url",
"url": "https://github.com/supabase-community/supabase-plugin.git",
"sha": "693a17a9970ba96e01afb9bef060d1dca48463ba"
"sha": "1b910c021aee8c9c054196f0e840b3a65e1a7c63"
},
"homepage": "https://github.com/supabase-community/supabase-plugin"
},
@@ -2426,7 +2445,7 @@
"source": {
"source": "url",
"url": "https://github.com/JetBrains/teamcity-cli.git",
"sha": "af537a9f4db0f3b7367522a9e00e4af284c94941"
"sha": "7f8419738b452108ff181365be30c1fab0a6905e"
},
"homepage": "https://www.jetbrains.com/teamcity/"
},
@@ -2457,7 +2476,7 @@
"source": {
"source": "url",
"url": "https://github.com/togethercomputer/skills.git",
"sha": "67a7e91ccd71b2989ed9921a03f79a86056d018c"
"sha": "a1277729f7914d886df213de922865d30a214a9d"
},
"homepage": "https://www.together.ai"
},
@@ -2519,7 +2538,7 @@
"url": "https://github.com/UI5/plugins-coding-agents.git",
"path": "plugins/ui5",
"ref": "main",
"sha": "5eca5d066dc7d936e1bc978cc43438dca18b3013"
"sha": "78f657e6a5004b5cdd1b998aabea616023eeabbb"
},
"homepage": "https://github.com/UI5/plugins-coding-agents"
},
@@ -2537,7 +2556,7 @@
"url": "https://github.com/UI5/plugins-coding-agents.git",
"path": "plugins/ui5-typescript-conversion",
"ref": "main",
"sha": "5eca5d066dc7d936e1bc978cc43438dca18b3013"
"sha": "78f657e6a5004b5cdd1b998aabea616023eeabbb"
},
"homepage": "https://github.com/UI5/plugins-coding-agents"
},
@@ -2566,6 +2585,20 @@
},
"homepage": "https://github.com/vercel/vercel-plugin"
},
{
"name": "vibe-prospecting",
"description": "Vibe Prospecting connects Claude to live B2B company and contact data so users can search, match, enrich, filter, and export prospects at scale. It turns natural-language requests into structured GTM workflows for lead generation, CRM enrichment, company research, executive discovery, and multi-step prospecting automation inside Claude Cowork and Claude Code.",
"author": {
"name": "vibeprospecting.ai"
},
"category": "productivity",
"source": {
"source": "url",
"url": "https://github.com/explorium-ai/vibeprospecting-plugin.git",
"sha": "ada4d569dbf70194fe18750ecbc5170e9a3f120a"
},
"homepage": "https://www.vibeprospecting.ai/product/claude-plugin"
},
{
"name": "windsor-ai",
"description": "Connect Claude Code to 325+ business data sources via Windsor.ai. Query marketing, sales, CRM, ecommerce, finance, and analytics data from Google Ads, Meta, HubSpot, Salesforce, Shopify, Stripe, and hundreds more — directly from your terminal.",
@@ -2587,7 +2620,7 @@
"source": {
"source": "url",
"url": "https://github.com/wix/skills.git",
"sha": "5d22db3370c198db8db959b52d1e66cabbb5f202"
"sha": "5da7e749a466ef9ddcdb2822099b940b9a1bc151"
},
"homepage": "https://dev.wix.com/docs/wix-cli/guides/development/about-wix-skills"
},
@@ -2666,7 +2699,7 @@
"source": {
"source": "url",
"url": "https://github.com/zoom/zoom-plugin.git",
"sha": "88f6ca3529c2dca7a38db24359ecf6fd15a23379"
"sha": "1f86a61604c39f853df901767059256250191c43"
},
"homepage": "https://developers.zoom.us/"
},
@@ -2694,7 +2727,7 @@
"source": {
"source": "url",
"url": "https://github.com/zscaler/zscaler-mcp-server.git",
"sha": "bc56b110199294de58e6a9abf0569c49bd948670"
"sha": "8409e1661b7f7171bfbb9297e1ecfc61c28b6d92"
},
"homepage": "https://github.com/zscaler/zscaler-mcp-server"
}

View File

@@ -381,3 +381,166 @@ jobs:
echo "::error::Scan step failed without a parseable policy verdict (likely an infra error)."
exit 1
fi
# ─────────────────────────────────────────────────────────────────────────────
# emit-verdict: post a sticky comment per entry to the bump PR with the
# structured verdict, so downstream tooling (label automation, delist
# authoring) can read verdicts directly instead of scraping job logs.
# Sticky comment marker: `<!-- bump-pr-verdict:<name> -->`.
#
# Mirrors the schema_v1 contract from
# anthropics/claude-plugins-community-internal#3908 so the triage scripts
# in mcp-local-directory/scripts/triage/ work uniformly across both repos.
# -official doesn't run per-entry static checks (zombie, schema, binaries,
# etc.) so the `scan.*` axes are emitted as "skipped". The granular policy
# booleans (`has_broad_scope_hooks`, `has_undisclosed_telemetry`,
# `description_matches_behavior`) aren't surfaced by this workflow's
# per-entry artifact yet, so they're emitted as null; the triage
# `triage_bool_to_str` helper maps null → "?" so display is graceful.
# Status describes the execution state, not the outcome — `ran` when the
# scan action evaluated this SHA fresh, `cached` when a prior verdict was
# reused (cf. run-verdicts.json's `source` field). Outcome lives in
# `policy.passes`. policy-sweep.sh dispatches on this exact vocabulary.
#
# PR resolution: pull_request events carry the PR number directly. The
# bump workflow creates bump PRs via GITHUB_TOKEN (which doesn't fire
# pull_request triggers — recursion guard) and dispatches this scan via
# workflow_dispatch on the bump branch. In that case we look up the
# open PR by head ref. No PR (scan_all dispatch on main, etc.) → no-op.
#
# continue-on-error at the job level: emit failure must NOT block the
# `scan` required check. Consumers fall back to log-scraping if the
# comment is absent (gradual migration; no flag day).
# ─────────────────────────────────────────────────────────────────────────────
emit-verdict:
needs: [scan]
if: always() && needs.scan.result != 'skipped' && needs.scan.result != 'cancelled'
runs-on: ubuntu-latest
continue-on-error: true
permissions:
contents: read
pull-requests: write
steps:
- name: Download scan verdicts
uses: actions/download-artifact@v4
with:
name: scan-verdicts
path: /tmp/scan-verdicts
continue-on-error: true
- name: Resolve PR number for this ref
id: pr
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
PR_FROM_EVENT: ${{ github.event.pull_request.number }}
REF: ${{ github.ref_name }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
if [[ "$EVENT_NAME" == "pull_request" && -n "$PR_FROM_EVENT" ]]; then
echo "number=$PR_FROM_EVENT" >> "$GITHUB_OUTPUT"
exit 0
fi
# workflow_dispatch on the bump branch: find the open PR for it.
# head filter takes the form owner:branch.
owner="${REPO%%/*}"
pr=$(gh api "/repos/${REPO}/pulls?state=open&head=${owner}:${REF}&per_page=1" \
--jq '.[0].number // ""')
if [[ -z "$pr" ]]; then
echo "::notice::No open PR for ref ${REF} — sticky comments skipped (verdicts still in scan-verdicts artifact)"
fi
echo "number=$pr" >> "$GITHUB_OUTPUT"
- name: Build and post sticky comments
if: steps.pr.outputs.number != ''
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ steps.pr.outputs.number }}
RUN_ID: ${{ github.run_id }}
run: |
set -euo pipefail
verdicts_path=/tmp/scan-verdicts/run-verdicts.json
# Missing/empty artifact: scan job ran but didn't produce verdicts
# (e.g. the relevance gate said "no changes"). Nothing to comment;
# exit clean.
if [[ ! -s "$verdicts_path" ]]; then
echo "::notice::No run-verdicts.json artifact — nothing to emit"
exit 0
fi
count=$(jq 'length' "$verdicts_path")
if [[ "$count" == "0" ]]; then
echo "::notice::run-verdicts.json is empty — nothing to emit"
exit 0
fi
ran_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
# scan.* axes: -official doesn't run per-entry static checks; emit
# "skipped" for each so the schema is shape-compatible with -internal.
scan_stub='{"clone":"skipped","subpath_missing":"skipped","schema":"skipped","zombie":"skipped","tool_allowlist":"skipped","binaries":"skipped","unique":"skipped","mcp":"skipped"}'
# Pre-fetch all PR comments once (paginated) for the marker lookup.
gh api --paginate "/repos/$REPO/issues/$PR/comments" \
--jq '.[] | {id, body}' > /tmp/comments.ndjson
jq -c '.[]' "$verdicts_path" | while read -r entry; do
name=$(jq -r '.name' <<< "$entry")
passes=$(jq -r '.passes' <<< "$entry")
summary=$(jq -r '.summary // ""' <<< "$entry")
violations=$(jq -r '.violations // ""' <<< "$entry")
source=$(jq -r '.source // "scan"' <<< "$entry")
# status = execution state (cf. -internal#3908 vocabulary).
# Outcome is in `passes`. Map source → status: scan-action-run
# → "ran"; cache-served → "cached". Anything else falls through
# as "ran" (only those two values appear in run-verdicts.json).
case "$source" in
cache) status="cached" ;;
scan) status="ran" ;;
*) status="ran" ;;
esac
policy=$(jq -n \
--argjson passes "$passes" \
--arg summary "$summary" \
--arg violations "$violations" \
--arg source "$source" \
--arg status "$status" \
'{passes: $passes,
has_broad_scope_hooks: null,
has_undisclosed_telemetry: null,
description_matches_behavior: null,
summary: $summary,
violations: $violations,
source: $source,
status: $status}')
verdict=$(jq -n \
--argjson scan "$scan_stub" \
--argjson policy "$policy" \
--arg ran_at "$ran_at" \
--arg run_id "$RUN_ID" \
'{schema_version: 1, ran_at: $ran_at, run_id: $run_id, scan: $scan, policy: $policy}')
marker="<!-- bump-pr-verdict:$name -->"
body=$(printf '%s\n```json\n%s\n```' "$marker" "$verdict")
# jq's first() short-circuits and avoids SIGPIPE under pipefail if
# duplicate markers exist (shouldn't, but a prior buggy run could
# double-post). -s slurps NDJSON; `// empty` yields no output when
# no match.
existing=$(jq -rs --arg m "$marker" \
'first(.[] | select(.body | startswith($m)) | .id) // empty' \
/tmp/comments.ndjson)
if [[ -n "$existing" ]]; then
gh api -X PATCH "/repos/$REPO/issues/comments/$existing" -f body="$body" >/dev/null
echo "Updated comment $existing for $name"
else
gh api -X POST "/repos/$REPO/issues/$PR/comments" -f body="$body" >/dev/null
echo "Created comment for $name"
fi
done

38
.github/workflows/validate-licenses.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Validate Plugin Licenses
on:
pull_request:
paths:
- 'plugins/**'
push:
branches: [main]
paths:
- 'plugins/**'
permissions:
contents: read
jobs:
validate-licenses:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check every plugin has an Apache 2.0 LICENSE file
run: |
set -euo pipefail
missing=()
for plugin_dir in plugins/*/; do
plugin="${plugin_dir%/}"
if [[ ! -f "$plugin/LICENSE" ]]; then
missing+=("$plugin")
fi
done
if [[ "${#missing[@]}" -gt 0 ]]; then
echo "::error::The following plugins are missing a LICENSE file:"
for p in "${missing[@]}"; do
echo " - $p"
done
exit 1
fi
echo "All $(ls -d plugins/*/ | wc -l) plugins have a LICENSE file."

202
LICENSE Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -42,6 +42,37 @@ plugin-name/
└── README.md # Documentation
```
## Skill-bundle plugins
When a plugin's source repository ships skills (`SKILL.md` files) without a `.claude-plugin/plugin.json` manifest, the marketplace entry can declare the skills directly using `strict: false` and an explicit `skills` array.
```json
{
"name": "example-bundle",
"description": "Brief description of the bundled skills.",
"author": { "name": "Author Name" },
"category": "development",
"source": {
"source": "git-subdir",
"url": "https://github.com/example-org/sdk.git",
"path": "packages/agent-skills",
"ref": "main",
"sha": "<commit sha>"
},
"strict": false,
"skills": [
"./skill-a",
"./skill-b",
"./skill-c"
],
"homepage": "https://github.com/example-org/sdk"
}
```
Each path in `skills` is relative to `source.path` and points at a directory containing a `SKILL.md`. Paths can reach deeper than a single level — for example, `["./libA/skill-1", "./libB/skill-2"]` exposes a curated subset across multiple library subdirectories. Each skill is registered as `<plugin-name>:<skill-name>` in Claude Code.
For the underlying schema, see [Strict mode](https://code.claude.com/docs/en/plugin-marketplaces) in the marketplace documentation.
## License
Please see each linked plugin for the relevant LICENSE file.

View File

@@ -39,7 +39,7 @@ ls -la package.json pyproject.toml Cargo.toml go.mod pom.xml 2>/dev/null
cat package.json 2>/dev/null | head -50
# Check dependencies for MCP server recommendations
cat package.json 2>/dev/null | grep -E '"(react|vue|angular|next|express|fastapi|django|prisma|supabase|stripe)"'
cat package.json 2>/dev/null | grep -E '"(react|vue|angular|next|express|fastapi|django|prisma|supabase|convex|stripe)"'
# Check for existing Claude Code config
ls -la .claude/ CLAUDE.md 2>/dev/null
@@ -55,7 +55,7 @@ ls -la src/ app/ lib/ tests/ components/ pages/ api/ 2>/dev/null
| Language/Framework | package.json, pyproject.toml, import patterns | Hooks, MCP servers |
| Frontend stack | React, Vue, Angular, Next.js | Playwright MCP, frontend skills |
| Backend stack | Express, FastAPI, Django | API documentation tools |
| Database | Prisma, Supabase, raw SQL | Database MCP servers |
| Database | Prisma, Supabase, Convex, raw SQL | Database / backend MCP servers |
| External APIs | Stripe, OpenAI, AWS SDKs | context7 MCP for docs |
| Testing | Jest, pytest, Playwright configs | Testing hooks, subagents |
| CI/CD | GitHub Actions, CircleCI | GitHub MCP server |
@@ -75,6 +75,7 @@ See [references/mcp-servers.md](references/mcp-servers.md) for detailed patterns
| Uses popular libraries (React, Express, etc.) | **context7** - Live documentation lookup |
| Frontend with UI testing needs | **Playwright** - Browser automation/testing |
| Uses Supabase | **Supabase MCP** - Direct database operations |
| Uses Convex | **Convex MCP** - Live deployment introspection, run queries/mutations, manage env vars and logs |
| PostgreSQL/MySQL database | **Database MCP** - Query and schema tools |
| GitHub repository | **GitHub MCP** - Issues, PRs, actions |
| Uses Linear for issues | **Linear MCP** - Issue management |

View File

@@ -72,6 +72,18 @@ MCP (Model Context Protocol) servers extend Claude's capabilities by connecting
**Value**: Claude can query tables, manage auth, and interact with Supabase storage directly.
### Convex MCP
**Best for**: Projects using Convex as the backend (reactive database + server functions + auth + storage + scheduling, all on one platform)
| Recommend When | Examples |
|----------------|----------|
| Convex project detected | `convex` in deps, `convex/` directory present, `convex.json` at repo root |
| Real-time / reactive UI | `useQuery` / `useMutation` / `useAction` from `convex/react` |
| Mobile + Convex | `convex/react-native` in deps |
| AI / chat / agent features on Convex | `@convex-dev/agent` in deps |
**Value**: Claude can introspect the live deployment (tables, function specs, env vars, logs) and execute queries/mutations against it via tools like `tables`, `function-spec`, `data`, `run-once-query`, `logs`, `env list/set/get`. Run via `npx convex mcp start`.
### PostgreSQL MCP
**Best for**: Direct PostgreSQL database access
@@ -253,6 +265,7 @@ MCP (Model Context Protocol) servers extend Claude's capabilities by connecting
| Popular npm packages | context7 |
| React/Vue/Next.js | Playwright MCP |
| `@supabase/supabase-js` | Supabase MCP |
| `convex` in deps, `convex/` directory, or `convex.json` | Convex MCP |
| `pg` or `postgres` | PostgreSQL MCP |
| GitHub remote | GitHub MCP |
| `.linear` or Linear refs | Linear MCP |

View File

@@ -28,8 +28,12 @@ NOOP_SYSTEM = 0 # claude_agent_sdk already importable in system python
NOOP_VENV = 1 # venv already built and SDK imports from it
BUILT = 2 # venv created + SDK pip-installed this run
BUILD_FAILED = 3 # venv create or pip install raised/timed out
SKIP_WIN32 = 4 # Windows; consumer glob doesn't handle Lib/ layout
# Outcome 4 was previously SKIP_WIN32; retired now that the consumer glob in
# llm.py also matches Windows venv layout (Lib/site-packages). Don't reuse the
# value — telemetry rows from older plugin builds still emit 4.
SKIP_SENTINEL = 5 # another SessionStart is currently building
HOOK_PY_INCOMPATIBLE = 6 # hook interpreter is <3.10 — SDK syntax can't load
# here no matter how the venv was built. See #2071.
def _sdk_on_syspath() -> bool:
@@ -60,12 +64,28 @@ def main() -> tuple[int, str, str]:
err_phase / err_kind are non-empty only on BUILD_FAILED — they let
telemetry split bootstrap failures by root cause.
"""
# Windows venv layout (Lib/site-packages, no python* subdir) isn't
# handled by the consumer's glob in security_reminder_hook.py; skip the
# bootstrap entirely rather than build a venv that's never read.
if sys.platform == "win32":
return SKIP_WIN32, "", ""
# Honesty check (fixes the misleading NOOP_VENV in #2071): the SDK
# requires Python >=3.10 and uses 3.10+ syntax (match statements,
# PEP 604 unions). On a 3.9 hook interpreter we CANNOT import it no
# matter how the venv was built — llm.py runs in this same interpreter
# and the syntax-level import will SyntaxError. macOS ships 3.9.6 as
# the default `python3` and `/usr/bin` precedes Homebrew in PATH, so
# this case is the default state for a large share of macOS users.
#
# sg-python.sh now prefers python3.10+ binaries so most users won't
# reach this branch; the fallback to 3.9 is preserved for the
# pattern-warning hooks that don't need the SDK. Reporting
# HOOK_PY_INCOMPATIBLE here:
# (a) avoids 30-60s of wasted pip install,
# (b) avoids the lie where the venv_py probe says NOOP_VENV but the
# consumer import fails, and
# (c) gives telemetry a clean bucket to size the affected fleet.
if sys.version_info < (3, 10):
return (
HOOK_PY_INCOMPATIBLE,
"hook_py",
f"py_{sys.version_info[0]}.{sys.version_info[1]}",
)
if _sdk_on_syspath():
return NOOP_SYSTEM, "", ""
@@ -75,7 +95,11 @@ def main() -> tuple[int, str, str]:
or os.path.expanduser("~/.claude/security")
)
venv = state_dir / "agent-sdk-venv"
venv_py = venv / "bin" / "python"
# Windows venvs put the interpreter at Scripts\python.exe; POSIX uses bin/python.
if sys.platform == "win32":
venv_py = venv / "Scripts" / "python.exe"
else:
venv_py = venv / "bin" / "python"
# Another SessionStart (concurrent CC instance, same plugin) may already
# be building. The sentinel lives NEXT TO the venv, not inside it —
@@ -125,10 +149,20 @@ def main() -> tuple[int, str, str]:
# the user's machine, pip's own default registry applies — that's the same
# exposure the user would have running `pip install` themselves, so
# we're not widening the supply-chain surface.
#
# --prefer-binary: on ARM64 Windows, pip's default resolver picks a
# `cryptography` version with no published binary wheel and tries to
# build from source, which needs Rust/Cargo (almost never present
# on user machines). The build fails and the whole bootstrap returns
# BUILD_FAILED. A binary wheel exists on PyPI for an adjacent
# version (`cryptography-46.0.3-cp311-abi3-win_arm64.whl`);
# --prefer-binary tells pip to pick it. Cross-platform safe: no-op
# on platforms where the latest version already has a wheel.
err_phase = "pip"
subprocess.run(
[str(venv_py), "-m", "pip", "install", "--quiet",
"--disable-pip-version-check", "claude-agent-sdk"],
"--disable-pip-version-check", "--prefer-binary",
"claude-agent-sdk"],
capture_output=True, timeout=120, check=True,
)
return BUILT, "", ""
@@ -186,6 +220,56 @@ def main() -> tuple[int, str, str]:
sentinel.unlink(missing_ok=True)
def _maybe_emit_user_notice(outcome: int, pv: int) -> str | None:
"""Return a one-time user-visible notice when the agentic reviewer is
in a persistent broken state on this machine, or None if we've already
shown the notice for this plugin version (or shouldn't show one).
The marker file is plugin-version-keyed: a future plugin update can
re-notify if behavior changes (e.g. we ship out-of-process SDK in v3
and want to tell affected users it's fixed). Failures to write the
marker degrade to "skip the notice this session" so we don't spam
every SessionStart on a read-only home dir.
Currently only HOOK_PY_INCOMPATIBLE qualifies. BUILD_FAILED is
intentionally excluded — it covers transient causes (network failure,
pip registry hiccup, in-flight rebuild) where the next session may
succeed and a permanent notice would mislead.
"""
if outcome != HOOK_PY_INCOMPATIBLE:
return None
try:
state_dir = Path(
os.environ.get("SECURITY_WARNINGS_STATE_DIR")
or os.path.expanduser("~/.claude/security")
)
marker = state_dir / f".agentic_unavailable_notice_v{pv or 0}"
if marker.exists():
return None
state_dir.mkdir(parents=True, exist_ok=True)
# Write timestamp + Python version so the marker is self-documenting
# if a user goes looking. O_EXCL would be racier with no real win
# (two concurrent SessionStarts both showing the notice once is fine).
marker.write_text(
f"{time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())} "
f"py={sys.version_info[0]}.{sys.version_info[1]}\n"
)
except OSError:
return None
return (
f"⚠ security-guidance plugin: the cross-file commit reviewer "
f"(layer 3 of 3 — catches IDOR, auth-bypass, cross-file SSRF) "
f"is unavailable in this environment. It requires Python ≥3.10, "
f"but the hook is running on "
f"{sys.version_info[0]}.{sys.version_info[1]}.\n\n"
f"Pattern checks and the single-shot LLM diff review are still "
f"active. To enable the deeper reviewer, install Python 3.10+ "
f"(e.g. `brew install python` on macOS) and restart Claude Code.\n\n"
f"This notice is shown once per plugin version. "
f"See: github.com/anthropics/claude-plugins-official/issues/2071"
)
if __name__ == "__main__":
# Tell the harness this is async — venv create + pip install can take
# 30-60s on a cold cache, well past the default sync hook timeout.
@@ -222,4 +306,18 @@ if __name__ == "__main__":
pv = _plugin_version_int()
if pv:
metrics["pv"] = pv
print(json.dumps({"metrics": metrics}), flush=True)
response: dict[str, object] = {"metrics": metrics}
# One-time user-visible notice when the agentic reviewer is dead on
# arrival. Uses hookSpecificOutput.additionalContext (SessionStart's
# supported channel for surfacing text to both the model and the user)
# plus systemMessage as a belt-and-suspenders. Marker-file-gated so
# this fires exactly once per plugin version per install — see
# _maybe_emit_user_notice.
notice = _maybe_emit_user_notice(outcome, pv)
if notice:
response["hookSpecificOutput"] = {
"hookEventName": "SessionStart",
"additionalContext": notice,
}
response["systemMessage"] = notice
print(json.dumps(response), flush=True)

View File

@@ -31,6 +31,67 @@ from _base import debug_log, _record_usage, _PV, PROVENANCE_TAG # noqa: F401
from session_state import with_locked_state
def _inject_agent_sdk_venv_into_syspath(state_dir):
"""Prepend the agent-SDK venv's site-packages to sys.path so the SDK
import below picks it up when the user's system Python doesn't have it.
Called from two fallback sites (3P SDK + agentic_review); shared here so
Windows pywin32 handling stays in one place.
Returns True if any path was added.
POSIX venv layout: `agent-sdk-venv/lib/pythonX.Y/site-packages`
Windows venv layout: `agent-sdk-venv/Lib/site-packages` (capital L, no
pythonX.Y subdir). The SDK transitively imports pywin32 on Windows, and
pywin32's `.pth` files (which add `win32/`, `win32/lib/` to sys.path and
register the DLL search dir via `pywin32_bootstrap.py`) are processed
ONLY by Python's `site.py` at interpreter startup — not when we manually
insert a path here. Without the bootstrap, the SDK's
`mcp.client.stdio → mcp.os.win32.utilities → pywintypes` import chain
fails with `ModuleNotFoundError: pywintypes` and the agentic reviewer
falls back to single-shot silently. Replicate what site.py would do.
"""
venv_root = os.path.join(state_dir, "agent-sdk-venv")
candidates = (
glob.glob(os.path.join(venv_root, "lib", "python*", "site-packages"))
+ glob.glob(os.path.join(venv_root, "Lib", "site-packages"))
)
added = False
for sp in candidates:
if not os.path.isdir(sp) or sp in sys.path:
continue
sys.path.insert(0, sp)
added = True
if sys.platform == "win32":
_bootstrap_pywin32(sp)
return added
def _bootstrap_pywin32(site_packages_dir):
"""Manually replicate the pywin32 `.pth` bootstrap so a venv added via
`sys.path.insert()` (not site.py) can still import `pywintypes`. No-op
when the venv doesn't include pywin32. Failures are swallowed — the
SDK import below will raise its own ImportError and the caller's
fallback path handles it cleanly."""
try:
win32 = os.path.join(site_packages_dir, "win32")
win32_lib = os.path.join(win32, "lib")
for d in (win32, win32_lib):
if os.path.isdir(d) and d not in sys.path:
sys.path.insert(0, d)
bootstrap = os.path.join(win32_lib, "pywin32_bootstrap.py")
if os.path.isfile(bootstrap):
import importlib.util
spec = importlib.util.spec_from_file_location(
"pywin32_bootstrap", bootstrap,
)
if spec and spec.loader:
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
except Exception as e:
debug_log(f"pywin32 bootstrap failed (may break SDK import on Windows): {e}")
# Plan Security Check Configuration
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
# OAuth access token — Claude Code passes this for /login users.
@@ -298,12 +359,7 @@ def _call_claude_via_sdk(prompt, output_schema, *, max_tokens=16000, model=None)
"SECURITY_WARNINGS_STATE_DIR",
os.path.expanduser("~/.claude/security"),
)
for _sp in glob.glob(
os.path.join(_state_dir, "agent-sdk-venv", "lib",
"python*", "site-packages")
):
if os.path.isdir(_sp) and _sp not in sys.path:
sys.path.insert(0, _sp)
_inject_agent_sdk_venv_into_syspath(_state_dir)
try:
import asyncio as _asyncio # noqa: F811
from claude_agent_sdk import ( # noqa: F401,F811
@@ -1089,18 +1145,11 @@ def agentic_review(
# ~/.claude/security/ with the SDK installed; try that as a fallback
# before giving up. The system import is attempted first so users
# who DO have it never touch the venv.
_venv_tried = False
_state_dir = os.environ.get(
"SECURITY_WARNINGS_STATE_DIR",
os.path.expanduser("~/.claude/security"),
)
for _sp in glob.glob(
os.path.join(_state_dir, "agent-sdk-venv", "lib",
"python*", "site-packages")
):
if os.path.isdir(_sp) and _sp not in sys.path:
sys.path.insert(0, _sp)
_venv_tried = True
_venv_tried = _inject_agent_sdk_venv_into_syspath(_state_dir)
try:
import asyncio as _asyncio # noqa: F811

View File

@@ -22,23 +22,90 @@
# "${CLAUDE_PLUGIN_ROOT}/hooks/security_reminder_hook.py"
set -e
# Git Bash / MSYS on Windows hands script paths to this shim in POSIX form
# (`/c/Users/...`). When we exec a Windows `python.exe` (which we do on
# Windows since `python3` is the Microsoft Store stub), python interprets the
# leading `/` as the root of the current drive — e.g. `/c/Users/...` becomes
# `C:\c\Users\...` or `D:\c\Users\...` (whichever drive the shell is on),
# fails with ENOENT, and every Edit/Write/MultiEdit tool use blocks until the
# session restarts. See anthropics/claude-plugins-official#2043.
#
# Fix: convert absolute path args to native Windows form via `cygpath -w`
# before exec. `cygpath` is a Git Bash builtin; it's absent on macOS/Linux,
# where the `command -v` guard makes this a no-op. `cygpath -w` is idempotent
# for already-Windows paths so the rare mixed-form case is safe.
if command -v cygpath >/dev/null 2>&1; then
converted=()
for a in "$@"; do
case "$a" in
/*) converted+=("$(cygpath -w "$a")") ;;
*) converted+=("$a") ;;
esac
done
set -- "${converted[@]}"
fi
probe() {
# $1..N: the interpreter command (may be multi-word like `py -3`)
# Probe writes the major version to stdout and exits 0 iff it's >=3.
"$@" -c 'import sys; print(sys.version_info[0])' 2>/dev/null
# Writes "<major>.<minor>" to stdout and exits 0 iff at least Python 3.
"$@" -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")' 2>/dev/null
}
# True iff arg is a "M.m" version string >= 3.10. claude_agent_sdk requires
# Python >= 3.10; below that, pip install fails ("No matching distribution")
# and the LLM-powered review (Stop / commit / push) silently no-ops while
# pattern checks (PostToolUse regex) keep working. macOS ships 3.9.6 as the
# default `python3` on current versions, so this guard matters in practice.
# See anthropics/claude-plugins-official#2071.
is_sdk_compatible() {
case "$1" in
3.1[0-9]|3.[2-9][0-9]|[4-9].*|[1-9][0-9].*) return 0 ;;
*) return 1 ;;
esac
}
# Pass 1 — try minor-versioned binaries in descending order. These are only
# present if the user explicitly installed them (Homebrew / python.org / pyenv),
# so picking one here always upgrades over the system `python3`. Highest
# available wins; the user doesn't have to PATH-prefer it.
for cmd in "python3.13" "python3.12" "python3.11" "python3.10"; do
v=$(probe "$cmd") || continue
if is_sdk_compatible "$v"; then
exec "$cmd" "$@"
fi
done
# Pass 2 — bare interpreters, but only if SDK-compatible. Covers Linux distros
# that ship 3.10+ as the default `python3`, and Windows where `python` /
# `py -3` resolves to the user's python.org install.
for cmd in "python3" "python" "py -3"; do
# Word-split intentionally so `py -3` works
# shellcheck disable=SC2086
v=$(probe $cmd) || continue
if [ "$v" = "3" ]; then
if is_sdk_compatible "$v"; then
# shellcheck disable=SC2086
exec $cmd "$@"
fi
done
# Pass 3 — fallback to any Python 3, even <3.10. Pattern-based checks
# (PostToolUse regex on Edit/Write) only need 3.6+ and are useful on their
# own; the SDK-dependent paths will detect the version mismatch and degrade
# inside the Python code. Without this fallback, the entire plugin would
# stop working on default macOS, which is a regression vs today.
for cmd in "python3" "python" "py -3"; do
# shellcheck disable=SC2086
v=$(probe $cmd) || continue
# Accept anything that successfully reported a "M.m" string.
case "$v" in
[0-9]*.[0-9]*)
# shellcheck disable=SC2086
exec $cmd "$@"
;;
esac
done
echo "security-guidance: no working Python 3 interpreter found." >&2
echo " tried: python3, python, py -3" >&2
echo " tried: python3.13, python3.12, python3.11, python3.10, python3, python, py -3" >&2
echo " on Windows, install Python from https://python.org (NOT the Microsoft Store)" >&2
echo " on macOS, install Python 3.10+ via Homebrew (\`brew install python\`)" >&2
exit 1