Compare commits

...

22 Commits

Author SHA1 Message Date
tobin
dd53af2e4a Fix MCP URL probe: connection failure was reported as PASS
curl writes "000" to -w '%{http_code}' on a connection failure AND exits
nonzero. The previous fallback put the echo inside the command
substitution — both wrote, the captured value was "000000", and the
case statement's 000) arm didn't match, so dead hosts fell through to
PASS. Move the fallback assignment outside the substitution so the
captured value is exactly "000" and connection failures fail.

Also skip entries with an empty url field — those are placeholders
awaiting user config, not dead endpoints, and would false-fail.
2026-05-19 04:01:21 +00:00
Tobin South
9f0275ae44 Add convex-backend plugin (#1918) 2026-05-18 16:56:50 -07:00
Tobin South
0b9a622ecb Fix broken plugin source configs and bump their SHAs (#1915)
* Fix broken plugin source configs and bump their SHAs

Several external plugins had source configs that no longer matched the
upstream layout, so the automated SHA bump skipped them indefinitely.
Add the missing path field where the manifest moved into a subdirectory,
correct stale ref/commit metadata, and update the skills list for the
one strict:false skills-only entry.

- rc, revenuecat: upstream moved the plugin from repo root into
  revenuecat/. Add path and bump SHA.
- zilliz: plugin moved from repo root into plugins/zilliz/. Add path
  and bump SHA.
- sumup: plugin lives at providers/claude/plugin/ (declared by the
  upstream marketplace.json) but our entry never had a path. Add it
  and bump SHA.
- mintlify: pure SHA bump. Repo layout unchanged between SHAs; the
  upstream remains a marketplace-style repo with no plugin.json, same
  as the currently pinned SHA.
- netsuite-suitecloud (strict:false skills entry): bump SHA and add
  the four new skill directories upstream added since the last pin.
- 42crunch-api-security-testing: ref said v1.0.1 but the pinned SHA
  is actually v1.5.5. Correct the label; the SHA is already current.
- jfrog: commit and sha fields had drifted apart. Set both to
  upstream HEAD.

Each new SHA verified to be on the upstream default branch and the
referenced manifest validated with claude plugin validate.

* Revert mintlify and netsuite-suitecloud changes

The validate-plugins check requires a plugin manifest at the pinned SHA
even for strict:false entries. Neither repo has one at any SHA, so a
SHA bump fails CI. Leave them at the existing pin until either the
upstream adds a manifest or the validator learns to honor strict:false.
2026-05-18 23:33:38 +01:00
Tobin South
b7c0654137 Raise bump cap with verdict cache and skip-and-revert (#1913)
* Cache scan verdicts and drop policy-failing entries from bump PRs

Three changes that together let the nightly bump clear any backlog in a
single run without blocking on a single bad upstream or re-burning Claude
time on already-scanned SHAs:

- bump-plugin-shas.yml: raise max-bumps default 20 -> 130 (above the
  external entry count, so a single run can clear a full backlog) and add
  an explicit 60-min job timeout. The cap was the only thing bounding the
  blast radius of a single policy failure; the changes below take over
  that role so the cap can be lifted.

- scan-plugins.yml: add a verdict cache keyed on (plugin, sha, policy
  hash). The bump action force-resets bump/plugin-shas every night, which
  makes the same SHAs reappear in the diff on consecutive nights — without
  the cache the scan would re-burn ~90s of Claude time per entry per
  night. Cached verdicts (pass and fail) are served from disk; only
  uncached SHAs are scanned. The job still fails on cached failures so
  the required check stays honest.

- revert-failed-bumps.yml (new): after a Scan Plugins workflow_run on
  bump/plugin-shas concludes with a failure, drop just the failing
  entries' source.sha back to main's pin via a follow-up signed commit
  and re-dispatch the scan. The re-dispatch finds only cached-pass
  entries and goes green in seconds. Bounded at 3 passes/night, restricted
  to SHA-only diffs, and aborts if the bump branch was tampered with.

* Harden bump cache and revert workflows after review

- revert-failed-bumps: replace the time-based revert budget (anchored on
  the PR head, which a revert commit immediately replaces — never
  accumulating past 1) with a commit count: every nightly bump force-
  resets to one commit and every revert pass adds exactly one, so
  commits > MAX+1 is the budget without date math, pagination, or
  exposure to comment spoofing.
- revert-failed-bumps: filter the bump PR by head owner so a fork PR
  with a branch named bump/plugin-shas can't be selected.
- revert-failed-bumps: continue-on-error on the artifact download so a
  scan that died before uploading (infra error) doesn't fail the revert
  job — the missing-file guard downstream handles it.
- scan-plugins: add a per-ref concurrency group so concurrent scans
  don't lose one another's cache writes; key the cache on run_attempt
  so a re-run can save its own verdicts.
- scan-plugins: store the full source object in the cache and require
  source equality on lookup, so a repo/path change at the same SHA
  misses the cache instead of getting a stale verdict.
- scan-plugins / revert-failed-bumps: strip markdown control chars,
  wrap model-generated text in code spans (neutralizes auto-linked
  URLs), and redact key-shaped tokens before they reach the step
  summary, artifact, cache, or PR comment.
2026-05-18 20:55:20 +01:00
Tobin South
af4e1ad69e Bump 21 plugin SHA pins to upstream HEAD (#1911) 2026-05-18 20:55:03 +01:00
Tobin South
de2bcc9411 Bump 27 plugin SHA pins to upstream HEAD (#1912) 2026-05-18 20:52:54 +01:00
Tobin South
e98784f00e Run plugin SHA bump nightly instead of weekly (#1909)
Upstream plugins move daily; a weekly sweep with a 20-bump cap can fall
behind. Each run force-resets the bump branch, so stale unmerged PRs are
replaced rather than piling up.
2026-05-18 19:53:59 +01:00
Tobin South
237a6b9707 Add CI check for HTTP MCP server URL liveness (#1910)
Walks marketplace.json for vendored plugins, extracts http/sse MCP
server URLs from .mcp.json / mcp.json / plugin.json, and probes each
with HEAD then a JSON-RPC POST fallback. Fails on 404/410 and
connection errors; passes on auth/method errors (expected without
credentials). Runs on PR, daily schedule, and manual dispatch.

External (SHA-pinned) plugins are out of scope — their .mcp.json
isn't checked out here.
2026-05-18 13:24:31 -05:00
github-actions[bot]
0c54d4ac15 Bump 20 plugin SHA pin(s) to upstream HEAD (#1904)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-18 18:52:12 +01:00
Bryan Thompson
61b760aafc Add save-to-spotify plugin (#1905) 2026-05-18 08:59:59 -07:00
Bryan Thompson
f475d3ce58 Add zoominfo plugin (#1885) 2026-05-17 17:49:38 -07:00
Bryan Thompson
d7b273d2b4 Bump crowdstrike-falcon-foundry SHA to v1.0.0 (#1842)
Pins to the v1.0.0 tag (a6a500c) instead of pre-release HEAD (e7fa026).
2026-05-17 17:49:15 -07:00
Bryan Thompson
b5a156b6ec Add carta-cap-table plugin (#1876) 2026-05-16 07:55:29 -05:00
Tobin South
32b176e6aa Bump pagerduty plugin to latest upstream SHA (#1862)
Picks up v1.1.0: new /create-pagerduty-skill command (Early Access).
2026-05-16 07:55:14 -05:00
Bryan Thompson
d8e4105231 Bump vanta-mcp-plugin SHA to 345d86b5 (#1843) 2026-05-16 07:55:04 -05:00
Bryan Thompson
5dbfa0fade Bump box plugin SHA and enumerate skills (#1845) 2026-05-15 23:54:38 +01:00
Twisha Bansal
1a2f18b05c chore: modify data-agent-kit-starter-pack plugin details (#1826)
* chore: modify data-agent-kit-starter-pack plugin details

Updated the description and homepage of the data-agent-kit-starter-pack plugin, and changed the SHA.

* update sha for latest commit
2026-05-12 22:59:22 +01:00
Dickson Tsai
1cf022eba1 Fix servicenow-sdk ref: ServiceNow/sdk uses master, not main (#1830)
The ServiceNow/sdk repository's default branch is 'master' and there is
no 'main' branch. The pinned SHA (06adf37) is the current head of
'master'. Update the ref so future SHA bumps target the correct branch.
2026-05-12 18:05:45 +01:00
Morgan Lunt
573ecf32cd Merge pull request #1820 from anthropics/morganl/code-modernization-plugin
code-modernization: fix pipeline gaps, redesign harden, dry-run hardening
2026-05-12 09:58:41 -07:00
Morgan Lunt
5e4a45001d code-modernization: harden writes a patch instead of editing legacy; make map/security guidance language-agnostic
- modernize-harden: never edits legacy/ anymore. Writes findings plus a
  reviewed unified diff to analysis/<system>/security_remediation.patch.
  A second security-auditor pass reviews each hunk (RESOLVES / PARTIAL /
  INTRODUCES-RISK) before presenting. The user reviews and applies the
  patch deliberately, then re-runs to verify. This makes every command
  consistent with the recommended deny Edit(legacy/**) workspace setting,
  so the README's exception note is gone.
- modernize-map: restructure the parse-target list around three stack-
  agnostic principles (dispatcher targets are variables; code-storage
  joins live in config; entry points live in deployment descriptors), with
  COBOL/Java/web/CLI examples on equal footing rather than COBOL-dominant.
  Same protections against false dead-code findings, less stack-specific.
- security-auditor agent: rephrase coverage items in stack-neutral terms
  (record layouts/temp datasets, resource ACLs, deployment scripts/job
  definitions, batch input records) so the checklist reads naturally for
  COBOL, Java EE, .NET, and web targets alike.
- README: drop the harden exception note; describe the patch workflow.
2026-05-11 16:46:03 -07:00
Morgan Lunt
22a1b25977 Harden code-modernization plugin from a real CardDemo dry run
Fixes found by running the discovery workflow against the AWS CardDemo
mainframe sample (~50 KLOC of COBOL/CICS/JCL/BMS/VSAM):

- modernize-assess: add scc -> cloc -> find/wc fallback chain with the
  COCOMO-II formula so Step 1 works when scc isn't installed; same for
  portfolio-mode cloc/lizard. Drop the reference to a specific
  agent-spawning tool name (just "in parallel"). Sharpen the structural-
  map subagent prompt: 5-12 domains, subgraph clustering, ~40-edge cap,
  repo-relative paths, dangling-reference check.
- modernize-map: expand the parse-target list with the things a
  literal-minded reader would miss on a real mainframe codebase — CICS
  CSD DEFINE TRANSACTION/FILE for entry points and online file I/O,
  EXEC CICS file ops, SELECT...ASSIGN TO joined with JCL DD,
  EXEC SQL table refs (not JCL DD), SEND/RECEIVE MAP, dynamic
  data-name XCTL resolution, COBOL fixed-format column slicing. Without
  these the dead-code list is wrong (most CICS programs look unreachable).
  Also write a machine-readable topology.json alongside the summary.
- modernize-extract-rules: add a Priority (P0/P1/P2) field with a
  heuristic, and an optional Suspected-defect field. modernize-brief
  reads P0 rules to build the behavior contract, but the Rule Card had
  no priority slot — the chain was broken.
- modernize-brief: read the new P0 tags; flag low-confidence P0 rules as
  SME blockers.
- modernize-reimagine: drop "for the demo" wording.
- security-auditor agent: add mainframe/COBOL coverage items (RACF,
  JCL/PROC creds, BMS field validation, DB2 dynamic SQL, copybook PII)
  and mark web-only items as such so it adapts to the target stack.
- README: add Optional Tooling section and a symlink example for the
  expected layout.
2026-05-11 16:28:27 -07:00
Morgan Lunt
718818146e Fix code-modernization plugin: align README with commands, fix pipeline gaps
- modernize-brief: read TOPOLOGY.html (what modernize-map actually
  produces) instead of nonexistent TOPOLOGY.md, and tell the user which
  command produces each missing input.
- README: rewrite the Commands section to match actual command behavior —
  correct output filenames, ordering (brief is the synthesis/approval gate
  after discovery, not the first step), agent attributions, and required
  args. Add a workspace-layout note and an explicit callout that
  modernize-harden edits legacy/, which conflicts with the recommended
  deny rule. Reconcile the Overview and Typical Workflow sequences.
- modernize-assess: generalize the production-runtime overlay step so it
  no longer assumes a specific MCP server/tool; mark it optional. Fix
  app/jcl/ -> legacy/$1/jcl/ for layout consistency.
- modernize-map: make TOPOLOGY.html self-contained (load Mermaid from a
  CDN) so it renders in any browser; drop assumptions about an external
  artifact renderer. Generalize the telemetry annotation note.
- business-rules-extractor agent: fix command cross-reference to the
  actual command name.
- plugin.json: include the brief step in the workflow description.
2026-05-11 16:17:59 -07:00
15 changed files with 1115 additions and 192 deletions

View File

@@ -18,8 +18,8 @@
"source": "git-subdir",
"url": "https://github.com/42Crunch-AI/claude-plugins.git",
"path": "plugins/api-security-testing",
"ref": "v1.0.1",
"sha": "56273e0e20762d76640838300a7431c4260cad32"
"ref": "v1.5.5",
"sha": "faf5305385de8afed9468904e8639be737aff39e"
},
"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": "0f1ad97af8b4de2107c2417184fc4c3114bda9d3"
"sha": "9ca1da262869ca2fb5f6c3daae2f7eeb648c937d"
},
"homepage": "https://github.com/adobe/skills/tree/main/plugins/creative-cloud/adobe-for-creativity"
},
@@ -57,7 +57,7 @@
"source": {
"source": "url",
"url": "https://github.com/SalesforceAIResearch/agentforce-adlc.git",
"sha": "9ef4d9b1958d4ed21179017d0452a81ec13c1de2"
"sha": "d645d2c8ce0689a568224436061872ab9f0ab179"
},
"homepage": "https://github.com/SalesforceAIResearch/agentforce-adlc"
},
@@ -77,7 +77,7 @@
"source": {
"source": "url",
"url": "https://github.com/AikidoSec/aikido-claude-plugin.git",
"sha": "5d9c13d367218e9b43a11d4502f623ab98859225"
"sha": "79ac524f87c9faa9a356ff3d495b8a5b77e01bbd"
},
"homepage": "https://github.com/AikidoSec/aikido-claude-plugin"
},
@@ -107,7 +107,7 @@
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/alloydb.git",
"sha": "0723d3ada808fe8f33e1b2808fd7a843c3d63ad2"
"sha": "4a75653275b095fcacf1508796b0fee8cc758c07"
},
"homepage": "https://cloud.google.com/alloydb"
},
@@ -120,7 +120,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/amazon-location-service",
"ref": "main",
"sha": "6cfb70e55aa142a8eda66e6ef7966d5921bdf9a2"
"sha": "95381e8bcb92f58a28edb4f83eb7e163c7461a0a"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -165,7 +165,7 @@
"source": {
"source": "url",
"url": "https://github.com/astronomer/agents.git",
"sha": "5935c4330dea4dfb8e93568956b10a543ecdb3d1"
"sha": "535a040ca9e27aaed6da13f0f959625fb3294820"
},
"homepage": "https://github.com/astronomer/agents"
},
@@ -175,7 +175,7 @@
"source": {
"source": "url",
"url": "https://github.com/atlanhq/agent-toolkit.git",
"sha": "acdf284da6aa98b14f8dad90a9827006d8df425c"
"sha": "790398c87378f128bdc74c31bb7ecfb8e4695f29"
},
"homepage": "https://docs.atlan.com/"
},
@@ -217,7 +217,7 @@
"url": "https://github.com/auth0/agent-skills.git",
"path": "plugins/auth0",
"ref": "main",
"sha": "f7724bf7984c5b00496cac0f54526bb1cf505dcb"
"sha": "3aa943b620a640be8a04d462e2abce11671653c3"
},
"homepage": "https://auth0.com/docs/quickstart/agent-skills"
},
@@ -233,7 +233,7 @@
"url": "https://github.com/aws/agent-toolkit-for-aws.git",
"path": "plugins/aws-agents",
"ref": "main",
"sha": "750230758fbf23acd60d075dedd7ead4092127ce"
"sha": "ba1cc8ca4f063d88ca40c6acf3f670e6321b7a7f"
},
"homepage": "https://github.com/aws/agent-toolkit-for-aws"
},
@@ -246,7 +246,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/aws-amplify",
"ref": "main",
"sha": "6cfb70e55aa142a8eda66e6ef7966d5921bdf9a2"
"sha": "95381e8bcb92f58a28edb4f83eb7e163c7461a0a"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -262,7 +262,7 @@
"url": "https://github.com/aws/agent-toolkit-for-aws.git",
"path": "plugins/aws-core",
"ref": "main",
"sha": "750230758fbf23acd60d075dedd7ead4092127ce"
"sha": "ba1cc8ca4f063d88ca40c6acf3f670e6321b7a7f"
},
"homepage": "https://github.com/aws/agent-toolkit-for-aws"
},
@@ -278,7 +278,7 @@
"url": "https://github.com/aws/agent-toolkit-for-aws.git",
"path": "plugins/aws-data-analytics",
"ref": "main",
"sha": "750230758fbf23acd60d075dedd7ead4092127ce"
"sha": "ba1cc8ca4f063d88ca40c6acf3f670e6321b7a7f"
},
"homepage": "https://github.com/aws/agent-toolkit-for-aws"
},
@@ -307,7 +307,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/aws-serverless",
"ref": "main",
"sha": "6cfb70e55aa142a8eda66e6ef7966d5921bdf9a2"
"sha": "95381e8bcb92f58a28edb4f83eb7e163c7461a0a"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -318,7 +318,7 @@
"source": {
"source": "url",
"url": "https://github.com/microsoft/azure-skills.git",
"sha": "ed25b85a13ec001c53f538b07e0bfbe732673885"
"sha": "350e050ca30fe3464483f66193a8ff3a973b1d77"
},
"homepage": "https://github.com/microsoft/azure-skills"
},
@@ -327,7 +327,7 @@
"source": {
"source": "url",
"url": "https://github.com/AzureCosmosDB/cosmosdb-claude-code-plugin.git",
"sha": "23c168856e4435793bd27a72d4714f022a3a1e90"
"sha": "f1e0498579a9251e5f3179b92d25d6ce3409bae5"
},
"description": "Expert assistant for Azure Cosmos DB — data modeling, query optimization, performance tuning, and best practices.",
"category": "database",
@@ -340,7 +340,7 @@
"source": {
"source": "url",
"url": "https://github.com/base44/skills.git",
"sha": "c7039b37eca0e2916a565a7395040c00055bcf8b"
"sha": "ec420cf2edd2c7e9a523d5afe2e71498a6357fa4"
},
"homepage": "https://docs.base44.com"
},
@@ -356,7 +356,7 @@
"url": "https://github.com/Bigdata-com/bigdata-plugins-marketplace.git",
"path": "plugins/bigdata-com",
"ref": "main",
"sha": "274b5365bdc61130225de736d3f3ca5210c0e37d"
"sha": "c77a09caabdc8783adbcbf8bbe05a0f57da12b19"
},
"homepage": "https://docs.bigdata.com"
},
@@ -367,8 +367,15 @@
"source": {
"source": "url",
"url": "https://github.com/box/box-for-ai.git",
"sha": "0fb23244e3c35cd562206c80eff1e22c456046ea"
"sha": "16f1a0427710b0812519ea634cd5ce6830bde8fc"
},
"skills": [
"./skills/box",
"./skills/box-legal-workflows",
"./skills/box-legal-workflows-contract",
"./skills/box-legal-workflows-intake",
"./skills/box-legal-workflows-ma"
],
"homepage": "https://github.com/box/box-for-ai"
},
{
@@ -377,10 +384,26 @@
"source": {
"source": "url",
"url": "https://github.com/brightdata/skills.git",
"sha": "44b24797d82cfd535c5b97831d5c6ba86c9d60df"
"sha": "37145178dfc9b52e28dd224afeccc7184f7711fc"
},
"homepage": "https://docs.brightdata.com"
},
{
"name": "carta-cap-table",
"description": "Carta Cap Table plugin — skills and hooks for querying cap tables, grants, SAFEs, 409A valuations, waterfall scenarios, and more",
"author": {
"name": "Carta Engineering"
},
"category": "productivity",
"source": {
"source": "git-subdir",
"url": "https://github.com/carta/plugins.git",
"path": "plugins/carta-cap-table",
"ref": "main",
"sha": "49db52aa7d59fd4a855c6b17a1cf31c245f41e2c"
},
"homepage": "https://carta.com"
},
{
"name": "cds-mcp",
"description": "AI-assisted development of SAP Cloud Application Programming Model (CAP) projects. Search CDS models and CAP documentation.",
@@ -393,7 +416,7 @@
"source": {
"source": "url",
"url": "https://github.com/cap-js/mcp-server.git",
"sha": "4d59d7070a52761a9b8028cbe710c8d7477cbc92"
"sha": "ef840d4315fa34264be6b71d0077a3b5288cb5fa"
},
"homepage": "https://cap.cloud.sap/"
},
@@ -404,7 +427,7 @@
"source": {
"source": "url",
"url": "https://github.com/ChromeDevTools/chrome-devtools-mcp.git",
"sha": "a1612be8e01401cf1711c64bc2ef5da5763ba956"
"sha": "32dc50d59bdb87242c67391ddc755368ebe77104"
},
"homepage": "https://github.com/ChromeDevTools/chrome-devtools-mcp"
},
@@ -482,7 +505,7 @@
"source": {
"source": "url",
"url": "https://github.com/ClickHouse/clickhouse-claude-code-plugin.git",
"sha": "db1c108dde6e5c81a1ca65f3b6700d6fff288545"
"sha": "13a2df004af0df46661c9de2d4ef4e85eba2f040"
},
"homepage": "https://github.com/ClickHouse/clickhouse-claude-code-plugin"
},
@@ -496,7 +519,7 @@
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/cloud-sql-postgresql.git",
"sha": "69c0c820513d7f75a63eeb3ec84b01478037caeb"
"sha": "966f7b883998692d05389e4c3d3793412ca0659f"
},
"homepage": "https://cloud.google.com/sql"
},
@@ -505,7 +528,7 @@
"source": {
"source": "url",
"url": "https://github.com/cloudflare/skills.git",
"sha": "0397d7d88fa6ac7517a88389622eb0799e86ded2"
"sha": "60147cbb773649eadca89cee92b4e0caf02234b4"
},
"description": "Skills for the Cloudflare developer platform: Workers, Durable Objects, Agents SDK, MCP servers, Wrangler CLI, and web performance.",
"category": "deployment",
@@ -531,7 +554,7 @@
"source": {
"source": "url",
"url": "https://github.com/cockroachdb/claude-plugin.git",
"sha": "31d0cc99fac1c97614cc787a96720104ea642375"
"sha": "736bd11df55bac97e2a6c98be8e93503b125902c"
},
"homepage": "https://github.com/cockroachdb/claude-plugin"
},
@@ -600,6 +623,20 @@
"community-managed"
]
},
{
"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.",
"author": {
"name": "Convex"
},
"category": "development",
"source": {
"source": "url",
"url": "https://github.com/get-convex/convex-backend-skill.git",
"sha": "9acbc5495dd26749a5e6341dc2438146c4caa03b"
},
"homepage": "https://convex.dev"
},
{
"name": "crowdstrike-falcon-foundry",
"description": "CrowdStrike Falcon Foundry development skills for building cybersecurity applications on the Falcon platform. Includes UI development, collections, functions, workflows, API integration, security patterns, and debugging workflows.",
@@ -610,7 +647,7 @@
"source": {
"source": "url",
"url": "https://github.com/CrowdStrike/foundry-skills.git",
"sha": "e7fa0260b5a413d9a459d3afbc5ba427da6c6e04"
"sha": "4b517aa5729d5bb5e397ff779f98eb05c91d1b21"
},
"homepage": "https://github.com/CrowdStrike/foundry-skills"
},
@@ -656,7 +693,7 @@
"source": {
"source": "url",
"url": "https://github.com/dash0hq/dash0-agent-plugin.git",
"sha": "38c6d74e637bd7dbe1fa2c364de66d07efe88a9a"
"sha": "feae46e4099d31a1a76debe39f22aebb72a18ce5"
},
"homepage": "https://dash0.com/"
},
@@ -667,13 +704,13 @@
"source": {
"source": "url",
"url": "https://github.com/astronomer/agents.git",
"sha": "5935c4330dea4dfb8e93568956b10a543ecdb3d1"
"sha": "535a040ca9e27aaed6da13f0f959625fb3294820"
},
"homepage": "https://github.com/astronomer/agents"
},
{
"name": "data-agent-kit-starter-pack",
"description": "Specialized suite of skills for data engineers on Google Cloud — architect data pipelines, transform data with dbt, write Spark and BigQuery SQL notebooks, and orchestrate end-to-end workflows across BigQuery, Spanner, BigLake, and Dataproc.",
"description": "This plugin provides a specialized suite of skills for data engineers and database practitioners working on Google Cloud. It acts as an expert assistant, allowing you to use natural language prompts in your preferred coding agent to architect complex data pipelines, transform data with dbt, write Spark and BigQuery SQL notebooks, and orchestrate end-to-end workflows across GCP's data ecosystem.",
"author": {
"name": "Google LLC"
},
@@ -681,9 +718,9 @@
"source": {
"source": "url",
"url": "https://github.com/gemini-cli-extensions/data-agent-kit-starter-pack.git",
"sha": "7bcfcb77435ec6d544b1131333f2297ca09c3930"
"sha": "7bc75b5e53d6eaae103132fd1a47de26239e4ae4"
},
"homepage": "https://cloud.google.com/bigquery"
"homepage": "https://github.com/gemini-cli-extensions/data-agent-kit-starter-pack"
},
{
"name": "data-engineering",
@@ -691,7 +728,7 @@
"source": {
"source": "url",
"url": "https://github.com/astronomer/agents.git",
"sha": "5935c4330dea4dfb8e93568956b10a543ecdb3d1"
"sha": "535a040ca9e27aaed6da13f0f959625fb3294820"
},
"homepage": "https://github.com/astronomer/agents"
},
@@ -704,7 +741,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/databases-on-aws",
"ref": "main",
"sha": "6cfb70e55aa142a8eda66e6ef7966d5921bdf9a2"
"sha": "95381e8bcb92f58a28edb4f83eb7e163c7461a0a"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -718,7 +755,7 @@
"source": {
"source": "url",
"url": "https://github.com/datadog-labs/claude-code-plugin.git",
"sha": "95d38f561e3d5e4fe9fb66c3c0bb19fb75e0458a"
"sha": "eeb2f746a857f8d97f69cd0968fb63874541c112"
},
"homepage": "https://www.datadoghq.com/"
},
@@ -732,7 +769,7 @@
"source": {
"source": "url",
"url": "https://github.com/datarobot-oss/datarobot-agent-skills.git",
"sha": "b3e8fd33d7c36592c802359026c15f3e067a0646"
"sha": "6a13377ad0b3317c7c4133fce36b7fcc626334cd"
},
"homepage": "https://datarobot.com"
},
@@ -745,7 +782,7 @@
"url": "https://github.com/microsoft/Dataverse-skills.git",
"path": ".github/plugins/dataverse",
"ref": "main",
"sha": "b2f21c1eec233d1b20e89618c3ffcb25cfdd55e4"
"sha": "5f186bf8ab1a3d6e242492d982276bbd7443ee0f"
},
"homepage": "https://github.com/microsoft/Dataverse-skills"
},
@@ -758,7 +795,7 @@
"url": "https://github.com/awslabs/agent-plugins.git",
"path": "plugins/deploy-on-aws",
"ref": "main",
"sha": "6cfb70e55aa142a8eda66e6ef7966d5921bdf9a2"
"sha": "95381e8bcb92f58a28edb4f83eb7e163c7461a0a"
},
"homepage": "https://github.com/awslabs/agent-plugins"
},
@@ -774,7 +811,7 @@
"url": "https://github.com/wonderwhy-er/DesktopCommanderMCP.git",
"path": "plugins/claude",
"ref": "main",
"sha": "8c03d3392d1633923057f4492f2b5014e2c4a6bf"
"sha": "9c44119a480ec6460f82d59aeb90cf274bc3dd7b"
},
"homepage": "https://desktopcommander.app"
},
@@ -794,7 +831,7 @@
"source": {
"source": "url",
"url": "https://github.com/exa-labs/exa-mcp-server.git",
"sha": "bd2ccdd52ca7a35fbc2207ad266bb2a961c0e793"
"sha": "5ce6c53bae8baa3248a1d197a4e89b7e464227e3"
},
"homepage": "https://exa.ai/docs/reference/exa-mcp"
},
@@ -818,7 +855,7 @@
"url": "https://github.com/expo/skills.git",
"path": "plugins/expo",
"ref": "main",
"sha": "786398d3574f33eb6714380f44ec09355819516e"
"sha": "47f0ef64821f10e42a600758b5087bfe89c09474"
},
"homepage": "https://github.com/expo/skills/blob/main/plugins/expo/README.md"
},
@@ -834,7 +871,7 @@
"source": {
"source": "url",
"url": "https://github.com/fastly/fastly-agent-toolkit.git",
"sha": "329331c887512850f13e481b45c4298c0387a4d2"
"sha": "e0f4205723b843de0b07da4a2aea6c84a3bcb579"
},
"homepage": "https://github.com/fastly/fastly-agent-toolkit/blob/main/README.md"
},
@@ -855,7 +892,7 @@
"source": {
"source": "url",
"url": "https://github.com/voxel51/fiftyone-skills.git",
"sha": "02bd4ea170ca01a751c2d2dd6bf2df8f62e65626"
"sha": "a79e53c6fd1784e1476421185f3ed67637e642b4"
},
"homepage": "https://docs.voxel51.com/"
},
@@ -866,7 +903,7 @@
"source": {
"source": "url",
"url": "https://github.com/figma/mcp-server-guide.git",
"sha": "fabc1ca81d839602ba7c1ca0f445a64246b3870e"
"sha": "a742f0a700a7772ff5ed85f7c9fc1dad5afa9fcc"
},
"homepage": "https://github.com/figma/mcp-server-guide"
},
@@ -884,7 +921,7 @@
"source": {
"source": "url",
"url": "https://github.com/firecrawl/firecrawl-claude-plugin.git",
"sha": "122a6ae6cefb4393c2c30740aee55ba02532ccdc"
"sha": "48edd7943009eb4442a6f0102bbd0c251eecef3e"
},
"homepage": "https://github.com/firecrawl/firecrawl-claude-plugin.git"
},
@@ -1026,8 +1063,8 @@
"source": {
"source": "github",
"repo": "jfrog/claude-plugin",
"commit": "761921eaa12b845beba1688d699a2d45091dfe83",
"sha": "d80db066e219aab8190f3dc4a463b71a3a180250"
"commit": "259c8e718266c16e99b4f30ae9b1ed0f9f00d98d",
"sha": "259c8e718266c16e99b4f30ae9b1ed0f9f00d98d"
},
"homepage": "https://jfrog.com"
},
@@ -1365,7 +1402,7 @@
"source": {
"source": "url",
"url": "https://github.com/PagerDuty/claude-code-plugins.git",
"sha": "b16c23e0d790deceaa7a6182616d0e36673f2eae"
"sha": "761cba75bd50fd561405c3b173ecf36084432089"
},
"homepage": "https://github.com/PagerDuty/claude-code-plugins"
},
@@ -1631,7 +1668,8 @@
"source": {
"source": "url",
"url": "https://github.com/RevenueCat/rc-claude-code-plugin.git",
"sha": "af7cb77996aee4e7e3c109c5afec81f716139032"
"path": "revenuecat",
"sha": "407e4651ff74dbaf47c457948ab540e620403c2a"
},
"homepage": "https://www.revenuecat.com"
},
@@ -1641,7 +1679,7 @@
"source": {
"source": "url",
"url": "https://github.com/Digital-Process-Tools/claude-remember.git",
"sha": "914445ac5f06a164800ea90ba4db41a0486321ae"
"sha": "aa55ba3f553e23f4d84387f5d7ece1ba0ce68d93"
},
"homepage": "https://github.com/Digital-Process-Tools/claude-remember"
},
@@ -1652,7 +1690,8 @@
"source": {
"source": "url",
"url": "https://github.com/RevenueCat/rc-claude-code-plugin.git",
"sha": "af7cb77996aee4e7e3c109c5afec81f716139032"
"path": "revenuecat",
"sha": "407e4651ff74dbaf47c457948ab540e620403c2a"
},
"homepage": "https://www.revenuecat.com"
},
@@ -1710,7 +1749,7 @@
"source": {
"source": "url",
"url": "https://github.com/sanity-io/agent-toolkit.git",
"sha": "bc09fa9854507c538a856648aafbd4e1a775a95c"
"sha": "236348e29b31e834ce71e4e2e3072184dd1c1e27"
},
"homepage": "https://www.sanity.io"
},
@@ -1726,7 +1765,7 @@
"source": {
"source": "url",
"url": "https://github.com/cap-js/mcp-server.git",
"sha": "8ce2e13ac70bd78415aedeaab0061af9396d3372"
"sha": "ef840d4315fa34264be6b71d0077a3b5288cb5fa"
},
"homepage": "https://cap.cloud.sap/"
},
@@ -1744,7 +1783,7 @@
"url": "https://github.com/SAP/open-ux-tools.git",
"path": "packages/fiori-mcp-server",
"ref": "main",
"sha": "d9d4ab7e69fe453f8fd682304ff1e3ac40a216c6"
"sha": "157120fda8577fda6fb7546ed1b2305bfa65b9f5"
},
"homepage": "https://github.com/SAP/open-ux-tools/tree/main/packages/fiori-mcp-server"
},
@@ -1760,10 +1799,26 @@
"source": {
"source": "url",
"url": "https://github.com/SAP/mdk-mcp-server.git",
"sha": "af81fe6c2421c5748388c65241da6a1b319a2c8f"
"sha": "10ff6ccfee094b9fb3b3877a41f00fa278b1bcc4"
},
"homepage": "https://help.sap.com/docs/MDK"
},
{
"name": "save-to-spotify",
"description": "Create polished audio episodes with TTS narration, rich timelines, cover images, and save them to Spotify via the save-to-spotify CLI.",
"author": {
"name": "Spotify"
},
"category": "productivity",
"source": {
"source": "git-subdir",
"url": "https://github.com/spotify/save-to-spotify.git",
"path": "plugin",
"ref": "main",
"sha": "b3d362f7851d184098dcb220ba2fab10c996d1f2"
},
"homepage": "https://github.com/spotify/save-to-spotify"
},
{
"name": "security-guidance",
"description": "Security reminder hook that warns about potential security issues when editing files, including command injection, XSS, and unsafe code patterns",
@@ -1783,7 +1838,7 @@
"source": "git-subdir",
"url": "https://github.com/semgrep/mcp-marketplace.git",
"path": "plugin",
"sha": "3711c33ad790df16e67c911eca792c473ec9a2a4"
"sha": "274846f6f9da5f56be53b19170bc008d357142a7"
},
"homepage": "https://github.com/semgrep/mcp-marketplace.git"
},
@@ -1794,7 +1849,7 @@
"source": {
"source": "url",
"url": "https://github.com/getsentry/sentry-for-claude.git",
"sha": "fb398fdfff2055abc3d55917f6b6f0c0d5ad5e3b"
"sha": "cf7efd373069d6fb073413324fe313319fb54ad9"
},
"homepage": "https://github.com/getsentry/sentry-for-claude/tree/main"
},
@@ -1819,7 +1874,7 @@
"source": "git-subdir",
"url": "https://github.com/ServiceNow/sdk.git",
"path": "providers/claude/plugin",
"ref": "main",
"ref": "master",
"sha": "06adf37ca78c270a57f93e7b9dfbb7bf16e24611"
},
"homepage": "https://servicenow.github.io/sdk/"
@@ -1859,7 +1914,7 @@
"source": {
"source": "url",
"url": "https://github.com/Shopify/Shopify-AI-Toolkit.git",
"sha": "c5c18d86ce7b2a7ca51ebac7c4b1a4eda00c8e25"
"sha": "c164cf45c4bc1d17bbc105168d99a4f744cfaac2"
},
"homepage": "https://shopify.dev"
},
@@ -1897,7 +1952,7 @@
"url": "https://github.com/Snowflake-Labs/snowflake-ai-kit.git",
"path": "plugins/cortex-code",
"ref": "main",
"sha": "28192345cae4a758a909f5e510e24fea10666400"
"sha": "b16692d548e9c785be640c06f3f3220ddf46c065"
},
"homepage": "https://docs.snowflake.com/en/user-guide/cortex-code"
},
@@ -1911,7 +1966,7 @@
"source": {
"source": "url",
"url": "https://github.com/SonarSource/sonarqube-agent-plugins.git",
"sha": "91eb175d6cf5d47a3edadbe61bdf782c31f0a65a"
"sha": "c64e09af314406a8d8806d57cd11cda81578ce20"
},
"homepage": "https://www.sonarsource.com"
},
@@ -1944,7 +1999,7 @@
"source": {
"source": "url",
"url": "https://github.com/spotify/ads-claude-plugin.git",
"sha": "63585cc919da51dd24fab594d829869595301922"
"sha": "cc3db744f4a4c14f7265ef3e9fb50f44cf08e0e7"
},
"homepage": "https://github.com/spotify/ads-claude-plugin"
},
@@ -1957,7 +2012,7 @@
"url": "https://github.com/stripe/ai.git",
"path": "providers/claude/plugin",
"ref": "main",
"sha": "14623416d84fdfad0aea8744d4c6f838ebc87654"
"sha": "ec93d4c4b9ffdbc994ac45ce692d4ec1cdb755f0"
},
"homepage": "https://github.com/stripe/ai/tree/main/providers/claude/plugin"
},
@@ -1968,7 +2023,8 @@
"source": {
"source": "url",
"url": "https://github.com/sumup/sumup-skills.git",
"sha": "0fd0a911ecaffd7187fe35e914d8ead6de584ffd"
"path": "providers/claude/plugin",
"sha": "a4b5a9789e10e27fb375b68279bb0916074b8dd4"
},
"homepage": "https://www.sumup.com/"
},
@@ -2041,7 +2097,7 @@
"source": {
"source": "url",
"url": "https://github.com/twilio/ai.git",
"sha": "0713fb1f40b5e871cad4c1c99f603c812431692a"
"sha": "7d15b215240df28e86a0b7305520524a2c005005"
},
"homepage": "https://www.twilio.com"
},
@@ -2089,7 +2145,7 @@
"url": "https://github.com/UI5/plugins-claude.git",
"path": "plugins/ui5",
"ref": "main",
"sha": "cec940abd4b7b6866de8e7e4522f3dba0449379d"
"sha": "19b2fb384719425a25d55830d5dcdba75f13045c"
},
"homepage": "https://github.com/UI5/plugins-claude"
},
@@ -2107,7 +2163,7 @@
"url": "https://github.com/UI5/plugins-claude.git",
"path": "plugins/ui5-typescript-conversion",
"ref": "main",
"sha": "cec940abd4b7b6866de8e7e4522f3dba0449379d"
"sha": "19b2fb384719425a25d55830d5dcdba75f13045c"
},
"homepage": "https://github.com/UI5/plugins-claude"
},
@@ -2121,7 +2177,7 @@
"source": {
"source": "url",
"url": "https://github.com/VantaInc/vanta-mcp-plugin.git",
"sha": "a9dac8bef2ccda299b3a4ba7a1bc7e0dbb7195ac"
"sha": "345d86b55faa649e955b7ea5569cf52d8425c2d5"
},
"homepage": "https://help.vanta.com/en/articles/14094979-connecting-to-vanta-mcp#h_887ce3f337"
},
@@ -2132,7 +2188,7 @@
"source": {
"source": "url",
"url": "https://github.com/vercel/vercel-plugin.git",
"sha": "61f1903bed7b322c9745f6ba67095bc006de7e63"
"sha": "1edb125d13a29a1e6212f5ca5afcdf1b89b9b211"
},
"homepage": "https://github.com/vercel/vercel-plugin"
},
@@ -2157,7 +2213,7 @@
"source": {
"source": "url",
"url": "https://github.com/wix/skills.git",
"sha": "bf25b5a45b2413b3581f3dcbcd63f3737791a051"
"sha": "7ae38286b49e5e0cbf7069b6fd8cf6b5db2ba786"
},
"homepage": "https://dev.wix.com/docs/wix-cli/guides/development/about-wix-skills"
},
@@ -2181,7 +2237,7 @@
"source": {
"source": "url",
"url": "https://github.com/youdotcom-oss/agent-skills.git",
"sha": "362d510732362bd679e1647f72f734ca2d2fa710"
"sha": "4712250ae8e5ce3095cad3b43b62b33608888863"
},
"homepage": "https://you.com"
},
@@ -2208,7 +2264,8 @@
"source": {
"source": "url",
"url": "https://github.com/zilliztech/zilliz-plugin.git",
"sha": "17cf04e6a3c272320b707d429484e4c00b3bec0b"
"path": "plugins/zilliz",
"sha": "e960396da0bd0b1cb219fa97e3bcbb425ee1abbd"
},
"homepage": "https://docs.zilliz.com"
},
@@ -2219,10 +2276,24 @@
"source": {
"source": "url",
"url": "https://github.com/zoom/zoom-plugin.git",
"sha": "ab0f09b2ddc6682a7f69055c7861009ec6062775"
"sha": "88f6ca3529c2dca7a38db24359ecf6fd15a23379"
},
"homepage": "https://developers.zoom.us/"
},
{
"name": "zoominfo",
"description": "Search companies and contacts, enrich leads, find lookalikes, and get AI-ranked contact recommendations. Pre-built skills chain multiple ZoomInfo tools into complete B2B sales workflows.",
"author": {
"name": "ZoomInfo"
},
"category": "productivity",
"source": {
"source": "url",
"url": "https://github.com/Zoominfo/zoominfo-mcp-plugin.git",
"sha": "14752e4553312d8af3eb3a3264a97d76bb3e0215"
},
"homepage": "https://www.zoominfo.com"
},
{
"name": "zscaler",
"description": "Manage Zscaler cloud security platform including ZPA (private access), ZIA (internet access), ZDX (digital experience), ZCC (client connector), EASM (attack surface), and Z-Insights (analytics). Create and manage policies, troubleshoot connectivity, audit security configurations, and investigate incidents across the full Zscaler ecosystem.",
@@ -2233,7 +2304,7 @@
"source": {
"source": "url",
"url": "https://github.com/zscaler/zscaler-mcp-server.git",
"sha": "6cf365968eb3b1e11306c973c51c1e54e98e704a"
"sha": "246430c8d2d99726ad6cdcb00d1adc4e316cb966"
},
"homepage": "https://github.com/zscaler/zscaler-mcp-server"
}

View File

@@ -1,8 +1,10 @@
name: Bump Plugin SHAs
# Weekly sweep: for each external entry whose upstream HEAD has moved past
# Nightly sweep: for each external entry whose upstream HEAD has moved past
# its pinned SHA, validate at the new SHA with `claude plugin validate`
# inline, then open one PR with all passing bumps.
# inline, then open one PR with all passing bumps. Each run force-resets the
# bump/plugin-shas branch, so a previous night's unmerged PR is replaced (and
# its review state discarded) — review and merge same-day to avoid churn.
#
# Bot-free — uses the default GITHUB_TOKEN. PRs opened with GITHUB_TOKEN don't
# trigger on:pull_request workflows, so the policy scan (`Scan Plugins`, a
@@ -11,16 +13,24 @@ name: Bump Plugin SHAs
# the scan ourselves on the bump branch after the PR is opened. The check run
# lands on the branch HEAD — the same SHA as the PR head — and satisfies the
# required check.
#
# max-bumps is set above the external-entry count so a single run can clear
# any backlog. The cost-control mechanisms are downstream:
# - scan-plugins.yml caches verdicts by (plugin, sha) so an unchanged SHA
# is never re-scanned across nightly force-resets.
# - revert-failed-bumps.yml drops policy-failing entries from the bump PR
# so one bad upstream can't block the rest.
# See those files for details.
on:
schedule:
- cron: '23 7 * * 1' # Monday 07:23 UTC
- cron: '23 7 * * *' # Daily 07:23 UTC
workflow_dispatch:
inputs:
max_bumps:
description: Cap on plugins bumped this run
required: false
default: '20'
default: '130'
permissions:
contents: write
@@ -33,6 +43,10 @@ concurrency:
jobs:
bump:
runs-on: ubuntu-latest
# Per-bump cost is ~2s (ls-remote + shallow clone + validate); 130 entries
# is ~5 min. The 60 min ceiling absorbs slow upstreams without letting a
# pathological run consume the default 360 min budget.
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
@@ -42,7 +56,7 @@ jobs:
id: bump
with:
marketplace-path: .claude-plugin/marketplace.json
max-bumps: ${{ inputs.max_bumps || '20' }}
max-bumps: ${{ inputs.max_bumps || '130' }}
claude-cli-version: latest
# `bump/plugin-shas` is the action's default `pr-branch`. The scan diffs

137
.github/workflows/check-mcp-urls.yml vendored Normal file
View File

@@ -0,0 +1,137 @@
name: Check MCP URLs
# Liveness check for http/sse MCP server URLs declared by plugins vendored
# in this repo. Catches typos in new submissions and upstream endpoints that
# disappear after merge.
#
# Scope: only plugins whose files live in this working tree (marketplace
# entries with a string `source`, e.g. "./plugins/foo"). External entries
# are pinned to an upstream repo at a SHA — reading their .mcp.json would
# mean cloning every upstream on each run, which is slow and flaky. Those
# are out of scope for now.
#
# What counts as "alive": anything that proves the hostname/path resolves to
# a server. 401/403/405/5xx all pass — auth and method errors are expected
# without credentials. Only 404/410 and connection/DNS/TLS failures fail.
on:
pull_request:
paths:
- '.claude-plugin/marketplace.json'
- 'plugins/**'
- 'external_plugins/**'
- '.github/workflows/check-mcp-urls.yml'
schedule:
- cron: '0 6 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Discover and probe MCP server URLs
run: |
set -euo pipefail
MARKETPLACE=".claude-plugin/marketplace.json"
# Each line: "<plugin>\t<server>\t<url>". Marketplace entries with a
# string `source` are local paths; objects describe an external repo
# pinned at a SHA, which we don't have checked out — skip those.
discover() {
jq -r '.plugins[] | select(.source | type == "string") | "\(.name)\t\(.source)"' "$MARKETPLACE" |
while IFS=$'\t' read -r plugin src; do
dir="${src#./}"
[[ -d "$dir" ]] || continue
for cfg in "$dir/.mcp.json" "$dir/mcp.json" "$dir/.claude-plugin/plugin.json"; do
[[ -f "$cfg" ]] || continue
# MCP config comes in two shapes: a bare map of server name ->
# config, or wrapped under a top-level "mcpServers" key (also
# the shape inside plugin.json). Normalize, then keep entries
# with an http/sse type and a string url.
# Skip entries with empty url — those are placeholders awaiting
# user config, not dead endpoints, and would false-fail.
jq -r --arg plugin "$plugin" '
(if (type == "object" and has("mcpServers")) then .mcpServers else . end)
| to_entries[]
| select((.value | type) == "object")
| select(.value.type == "http" or .value.type == "sse")
| select(.value.url | type == "string" and . != "")
| "\($plugin)\t\(.key)\t\(.value.url)"
' "$cfg" 2>/dev/null || true
done
done | sort -u
}
# Returns 0 on pass, 1 on fail; prints "PASS|FAIL <code> <note>".
probe() {
local url="$1"
local code
# HEAD first — cheap and covers plain web endpoints. -L follows
# redirects so a permanent redirect to a live page still passes.
#
# On a connection-level failure curl writes "000" to -w AND exits
# nonzero. The fallback assignment must happen OUTSIDE the command
# substitution — `... || echo "000"` inside $() would *append* a
# second "000", producing "000000" which falls through the case
# statement and silently passes a dead host.
code="$(curl -sS -o /dev/null -w '%{http_code}' \
--connect-timeout 10 --max-time 10 \
--retry 2 --retry-delay 2 \
-L -I "$url" 2>/dev/null)" || code="000"
# MCP endpoints typically reject HEAD (404/405) but answer POST
# with a JSON-RPC body. Retry as a real MCP client would.
if [[ "$code" == "000" || "$code" == "404" || "$code" == "405" ]]; then
code="$(curl -sS -o /dev/null -w '%{http_code}' \
--connect-timeout 10 --max-time 10 \
--retry 2 --retry-delay 2 \
-L -X POST \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
--data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"ci","version":"0"}}}' \
"$url" 2>/dev/null)" || code="000"
fi
case "$code" in
000) echo "FAIL $code unreachable"; return 1 ;;
404|410) echo "FAIL $code gone"; return 1 ;;
*) echo "PASS $code"; return 0 ;;
esac
}
entries="$(discover)"
if [[ -z "$entries" ]]; then
echo "::notice::No http/sse MCP server URLs found in vendored plugins."
exit 0
fi
failures=0
printf '%-24s %-18s %-52s %s\n' "PLUGIN" "SERVER" "URL" "RESULT"
while IFS=$'\t' read -r plugin server url; do
# Skip URLs with template placeholders — they need user config
# and can't be probed as-is.
if [[ "$url" == *'${'* || "$url" == *'{{'* ]]; then
printf '%-24s %-18s %-52s %s\n' "$plugin" "$server" "$url" "SKIP templated"
continue
fi
result="$(probe "$url")" || true
printf '%-24s %-18s %-52s %s\n' "$plugin" "$server" "$url" "$result"
if [[ "$result" == FAIL* ]]; then
failures=$((failures + 1))
echo "::error::MCP server URL for plugin '$plugin' (server '$server') is unreachable: $url ($result)"
fi
done <<< "$entries"
echo
if (( failures > 0 )); then
echo "::error::$failures MCP server URL(s) failed liveness check."
exit 1
fi
echo "All MCP server URLs reachable."

View File

@@ -0,0 +1,284 @@
name: Revert Failed Bumps
# Drops policy-failing entries from a bump PR so one bad upstream can't
# block the rest. Runs after a Scan Plugins workflow_run on bump/plugin-shas
# concludes with a failure: read the per-entry verdicts the scan uploaded,
# revert just the failing entries' source.sha back to main's pin, push a
# follow-up signed commit, and re-dispatch the scan. The re-dispatched scan
# finds only cached-pass entries in the new diff and goes green in seconds.
#
# Scope and guardrails — this job has contents:write so it must be tight:
# - Only acts on bump/plugin-shas (literal branch match).
# - Only acts when the scan was dispatched (workflow_dispatch event), i.e.
# by bump-plugin-shas.yml. A scan on a regular PR never triggers this.
# - Only reverts source.sha. If any other field in a failing entry differs
# from main, the run aborts — that means the bump branch was tampered
# with and a human needs to look.
# - Bounded at MAX_REVERT_PASSES per night via a PR comment marker; a
# persistent loop means the cache or scan is broken and a human needs
# to look.
# - The revert commit is created with createCommitOnBranch (GitHub-signed,
# compare-and-swap via expectedHeadOid) — no signing key on the runner.
on:
workflow_run:
workflows: ["Scan Plugins"]
types: [completed]
permissions:
contents: read
env:
MARKETPLACE: .claude-plugin/marketplace.json
BUMP_BRANCH: bump/plugin-shas
MAX_REVERT_PASSES: '3'
REVERT_MARKER: '<!-- revert-failed-bumps -->'
jobs:
revert:
# Tight gate: the triggering scan must be a workflow_dispatch run on the
# bump branch (i.e. the one bump-plugin-shas.yml dispatched) that failed.
# A scan on a regular PR, a passing scan, or a manual dispatch on another
# branch must never reach this job.
if: >
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.event == 'workflow_dispatch' &&
github.event.workflow_run.head_branch == 'bump/plugin-shas'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write # createCommitOnBranch on bump/plugin-shas
pull-requests: write # comment on / close the bump PR
actions: write # gh workflow run scan-plugins.yml --ref bump/plugin-shas
concurrency:
group: revert-failed-bumps
cancel-in-progress: false
steps:
# The artifact carries run-failed.json (just plugin names) and
# run-verdicts.json (full per-entry verdicts for the PR comment). It is
# uploaded by scan-plugins.yml for every relevant run so we can tell
# "policy failures found" from "scan never ran" (infra error → no revert).
# The artifact won't exist when the scan died before the upload step
# (cache restore error, jq failure, timeout) — that is an infra error,
# not a policy failure, so the right move is to do nothing. The
# download must not fail the job; the next step handles the missing file.
- name: Download scan verdicts
continue-on-error: true
uses: actions/download-artifact@v4
with:
name: scan-verdicts
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
path: scan-out
- name: Determine revert set
id: plan
run: |
set -euo pipefail
if [[ ! -f scan-out/run-failed.json ]]; then
echo "::warning::No run-failed.json in scan artifact — nothing to revert."
echo "act=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! jq -e 'type == "array"' scan-out/run-failed.json >/dev/null 2>&1; then
echo "::warning::run-failed.json is not a JSON array — refusing to act."
echo "act=false" >> "$GITHUB_OUTPUT"
exit 0
fi
fail_count="$(jq 'length' scan-out/run-failed.json)"
if [[ "$fail_count" -eq 0 ]]; then
# The scan job failed but reported zero policy failures: that is
# an infra error (API key missing, clone failure, schema break).
# Reverting nothing is correct; surfacing the infra error is the
# scan job's responsibility.
echo "::notice::Scan failed with zero parsed policy failures — infra error, not a policy failure. Not reverting."
echo "act=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "act=true" >> "$GITHUB_OUTPUT"
echo "fail_count=$fail_count" >> "$GITHUB_OUTPUT"
echo "Failing entries:"
jq -r '.[]' scan-out/run-failed.json
- name: Locate bump PR and check revert budget
if: steps.plan.outputs.act == 'true'
id: pr
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# Resolve the bump PR by head ref. `gh pr list --head <ref>` matches
# by ref name across forks, so reject any PR whose head repo isn't
# ours — a fork PR named bump/plugin-shas must never reach the
# contents:write paths below.
pr_json="$(gh api "repos/$REPO/pulls?head=${REPO%%/*}:$BUMP_BRANCH&base=main&state=open&per_page=1" \
--jq '.[0] // empty')"
if [[ -z "$pr_json" ]]; then
echo "::warning::No open bump PR on $BUMP_BRANCH — nothing to revert."
echo "act=false" >> "$GITHUB_OUTPUT"
exit 0
fi
pr_number="$(jq -r '.number' <<<"$pr_json")"
head_repo="$(jq -r '.head.repo.full_name' <<<"$pr_json")"
head_sha="$(jq -r '.head.sha' <<<"$pr_json")"
# The list endpoint omits `commits`; the single-PR endpoint has it.
commit_count="$(gh api "repos/$REPO/pulls/$pr_number" --jq '.commits')"
if [[ "$head_repo" != "$REPO" ]]; then
echo "::error::Bump PR head is from $head_repo, not $REPO — refusing to act."
echo "act=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# Loop bound: every nightly bump force-resets the branch to a single
# commit and every revert pass adds exactly one. Counting commits is
# therefore the per-night pass count + 1, with no date math, no
# pagination, and no exposure to comment spoofing.
if [[ "$commit_count" -gt $(( MAX_REVERT_PASSES + 1 )) ]]; then
echo "::error::Revert budget exhausted ($((commit_count - 1))/$MAX_REVERT_PASSES passes on this PR). The cache or scan is likely broken — needs a human."
gh pr comment "$pr_number" --repo "$REPO" --body \
"$REVERT_MARKER"$'\n\n'"⚠️ Revert budget exhausted ($((commit_count - 1)) passes). The scan keeps failing after reverting — likely a cache or scan bug. Pausing automatic reverts until the next nightly bump."
echo "act=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Bump PR #$pr_number @ $head_sha ($commit_count commit(s))"
{
echo "act=true"
echo "number=$pr_number"
echo "head_sha=$head_sha"
} >> "$GITHUB_OUTPUT"
- name: Revert failing SHAs
if: steps.plan.outputs.act == 'true' && steps.pr.outputs.act == 'true'
id: revert
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
run: |
set -euo pipefail
mkdir -p work
gh api "repos/$REPO/contents/${MARKETPLACE}?ref=$HEAD_SHA" --jq '.content' | base64 -d > work/head.json
gh api "repos/$REPO/contents/${MARKETPLACE}?ref=main" --jq '.content' | base64 -d > work/base.json
# Build the reverted marketplace: for each failing plugin, restore
# source.sha to main's value. Refuse if anything else differs — a
# difference outside source.sha on a bump-branch entry means the
# branch was tampered with.
jq -c -s \
'.[0] as $head | .[1] as $base | (.[2] | map({(.): true}) | add // {}) as $fail
| ($base.plugins | map({(.name): .}) | add // {}) as $b
| $head | .plugins = [
.plugins[] |
if ($fail[.name] // false) and ($b[.name] // null) != null then
# Verify the only delta is source.sha — never silently
# accept a structural change masquerading as a bump.
if (. | del(.source.sha)) == ($b[.name] | del(.source.sha)) then
.source.sha = $b[.name].source.sha
else
error("entry \(.name) differs from main beyond source.sha — refusing to revert")
end
else . end
]' \
work/head.json work/base.json scan-out/run-failed.json > work/reverted.json.compact
# Match the marketplace's existing pretty-print so the diff is
# human-reviewable.
jq --indent 2 '.' work/reverted.json.compact > work/reverted.json
# Two no-action cases:
# - nothing actually reverted (failed names not in this PR's diff)
# - everything reverted (the file is back to main → PR is empty)
if cmp -s work/reverted.json.compact <(jq -c '.' work/head.json); then
echo "::notice::No entries to revert (failing names not in this PR)."
echo "committed=false" >> "$GITHUB_OUTPUT"
echo "empty=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if cmp -s work/reverted.json.compact <(jq -c '.' work/base.json); then
echo "::warning::Every bumped entry failed policy — the PR would be empty."
echo "committed=false" >> "$GITHUB_OUTPUT"
echo "empty=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# Vendored entries have a string `source` — restrict to object
# sources or `.source.sha` errors.
reverted="$(jq -c -s \
'.[0] as $head | .[1] as $rev
| ($head.plugins | map(select(.source | type == "object") | {(.name): .source.sha}) | add // {}) as $h
| [$rev.plugins[] | select(.source | type == "object")
| select(($h[.name] // null) != .source.sha) | .name]' \
work/head.json work/reverted.json.compact)"
echo "Reverted: $reverted"
echo "reverted=$reverted" >> "$GITHUB_OUTPUT"
msg="Drop $(jq 'length' <<<"$reverted") policy-failing entries from bump"
# createCommitOnBranch: GitHub-signed, expectedHeadOid CAS so a
# concurrent force-reset from the nightly bump fails this push
# loudly instead of being clobbered. The base64'd marketplace can
# exceed MAX_ARG_STRLEN, so the body travels via stdin.
oid="$(jq -n \
--rawfile content work/reverted.json \
--arg repo "$REPO" \
--arg branch "$BUMP_BRANCH" \
--arg oid "$HEAD_SHA" \
--arg msg "$msg" \
--arg path "$MARKETPLACE" \
'{
query: "mutation($repo:String!,$branch:String!,$oid:GitObjectID!,$msg:String!,$path:String!,$contents:Base64String!){createCommitOnBranch(input:{branch:{repositoryNameWithOwner:$repo,branchName:$branch},message:{headline:$msg},fileChanges:{additions:[{path:$path,contents:$contents}]},expectedHeadOid:$oid}){commit{oid}}}",
variables: { repo: $repo, branch: $branch, oid: $oid, msg: $msg, path: $path, contents: ($content | @base64) }
}' \
| gh api graphql --input - --jq '.data.createCommitOnBranch.commit.oid')"
[[ "$oid" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::createCommitOnBranch did not return a commit OID."; exit 1; }
echo "committed=true" >> "$GITHUB_OUTPUT"
echo "empty=false" >> "$GITHUB_OUTPUT"
echo "::notice::Pushed revert commit $oid to $BUMP_BRANCH."
- name: Close empty bump PR
if: steps.revert.outputs.empty == 'true'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ steps.pr.outputs.number }}
run: |
set -euo pipefail
gh pr comment "$PR" --repo "$REPO" --body \
"$REVERT_MARKER"$'\n\n'"Every bumped entry failed the policy scan. Closing — the next nightly run will retry."
gh pr close "$PR" --repo "$REPO"
- name: Comment with revert detail
if: steps.revert.outputs.committed == 'true'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ steps.pr.outputs.number }}
REVERTED: ${{ steps.revert.outputs.reverted }}
SCAN_RUN_URL: ${{ github.event.workflow_run.html_url }}
run: |
set -euo pipefail
{
printf '%s\n\n' "$REVERT_MARKER"
echo "Dropped $(jq 'length' <<<"$REVERTED") entrie(s) that failed the policy scan. The remaining bumps were unaffected."
echo
echo "| Plugin | Violations |"
echo "|---|---|"
# `violations` is model-generated text shaped by a cloned external
# repo. Strip markdown control characters and wrap in a code span
# so a prompt-injected upstream can't smuggle links/images/table
# breakouts into a public PR comment.
jq -r --argjson rev "$REVERTED" \
'def neutralize: gsub("[|\n\r\\[\\]<>`]"; " ");
.[] | select(.name as $n | $rev | index($n))
| "| \(.name) | `\(.violations | neutralize | .[0:200])` |"' \
scan-out/run-verdicts.json
echo
echo "These entries will be retried at their next upstream SHA. See the [scan run]($SCAN_RUN_URL) for full verdicts."
} > /tmp/comment.md
gh pr comment "$PR" --repo "$REPO" --body-file /tmp/comment.md
- name: Re-dispatch scan on revised bump branch
if: steps.revert.outputs.committed == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run scan-plugins.yml --ref "$BUMP_BRANCH"

View File

@@ -7,6 +7,19 @@ name: Scan Plugins
# PRs blocked forever — so this workflow runs on every PR and skips the heavy
# scan setup at the step level when nothing scan-relevant changed. The check
# always reports.
#
# Verdict cache: each (plugin, sha) pair is scanned at most once. The bump
# workflow force-resets bump/plugin-shas every night, which makes the same
# SHAs reappear in the diff on consecutive nights — without a cache, the
# scan would re-burn ~90s of Claude time per entry per night. The cache is
# keyed on the policy hash so a prompt or schema change invalidates all
# verdicts and triggers a clean re-scan.
#
# Failure handling: a cached `passes:false` verdict still fails the job. The
# Revert Failed Bumps workflow (revert-failed-bumps.yml) reacts to that by
# dropping the failing entries from the bump PR, so one bad upstream can't
# block the rest. After the revert, the re-dispatched scan finds only
# cached-pass entries and goes green in seconds.
on:
pull_request:
@@ -20,6 +33,18 @@ on:
permissions:
contents: read
# Serialize scans per ref so concurrent runs (a re-dispatch racing the
# original, or a manual dispatch) don't both restore the same cache, scan
# overlapping sets, and lose one another's verdicts on save.
concurrency:
group: scan-plugins-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: false
env:
MARKETPLACE: .claude-plugin/marketplace.json
CACHE_DIR: ${{ github.workspace }}/.scan-cache
CACHE_TTL_DAYS: '30'
jobs:
scan:
runs-on: ubuntu-latest
@@ -37,11 +62,14 @@ jobs:
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set -euo pipefail
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
echo "relevant=true" >> "$GITHUB_OUTPUT"
echo "base_ref=origin/main" >> "$GITHUB_OUTPUT"
exit 0
fi
if git diff --quiet "$BASE_SHA" HEAD -- .claude-plugin/marketplace.json .github/policy/; then
echo "base_ref=$BASE_SHA" >> "$GITHUB_OUTPUT"
if git diff --quiet "$BASE_SHA" HEAD -- "$MARKETPLACE" .github/policy/; then
echo "relevant=false" >> "$GITHUB_OUTPUT"
echo "::notice::No changes to marketplace.json or policy/ — skipping policy scan."
else
@@ -61,13 +89,292 @@ jobs:
exit 1
fi
# Blocking: policy failures fail the job. Loosen by removing
# fail-on-findings if the false-positive rate is too high.
- if: steps.changes.outputs.relevant == 'true'
# Verdict cache, keyed on the policy content hash. A prompt change
# invalidates every cached verdict — that is intentional. The save key
# includes run_id so each run writes a fresh cache; restore-keys picks
# the most recent one. Verdicts older than CACHE_TTL_DAYS are pruned on
# restore to bound cache size as the marketplace grows.
- name: Restore verdict cache
if: steps.changes.outputs.relevant == 'true'
id: cache-restore
uses: actions/cache/restore@v4
with:
path: .scan-cache
# run_attempt so a re-run can save its own verdicts (cache keys are
# immutable; without it a re-run would silently fail to save).
key: scan-verdicts-${{ hashFiles('.github/policy/**') }}-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: |
scan-verdicts-${{ hashFiles('.github/policy/**') }}-
# Split the diff into cached (skip) and uncached (scan) entries. The
# cache key is "<name>@<sha>" — a SHA is immutable, so a verdict for a
# given (plugin, sha) is permanent under a fixed policy.
- name: Filter scan targets against cache
if: steps.changes.outputs.relevant == 'true'
id: filter
env:
BASE_REF: ${{ steps.changes.outputs.base_ref }}
SCAN_ALL: ${{ inputs.scan_all || 'false' }}
TTL_DAYS: ${{ env.CACHE_TTL_DAYS }}
run: |
set -euo pipefail
mkdir -p "$CACHE_DIR"
# Initialize / prune the verdict map.
if [[ -f "$CACHE_DIR/verdicts.json" ]] && jq -e 'type == "object"' "$CACHE_DIR/verdicts.json" >/dev/null 2>&1; then
# Drop entries older than TTL. Verdicts are immutable per (plugin, sha)
# but pruning keeps the cache from accumulating forever.
cutoff="$(date -u -d "-${TTL_DAYS} days" +%Y-%m-%dT%H:%M:%SZ)"
jq --arg cutoff "$cutoff" \
'with_entries(select(.value.scanned_at >= $cutoff))' \
"$CACHE_DIR/verdicts.json" > "$CACHE_DIR/verdicts.json.tmp"
mv "$CACHE_DIR/verdicts.json.tmp" "$CACHE_DIR/verdicts.json"
else
echo '{}' > "$CACHE_DIR/verdicts.json"
fi
# Build the change set: entries in HEAD whose object differs from base.
# scan_all overrides to "every external entry" (full re-review).
if [[ "$SCAN_ALL" == "true" ]]; then
jq -c '[.plugins[] | select(.source | type == "object")]' "$MARKETPLACE" \
> "$CACHE_DIR/changed.json"
else
if git cat-file -e "${BASE_REF}:${MARKETPLACE}" 2>/dev/null; then
git show "${BASE_REF}:${MARKETPLACE}" > "$CACHE_DIR/base.json"
else
echo '{"plugins":[]}' > "$CACHE_DIR/base.json"
fi
jq -c -s \
'(.[0].plugins | map({(.name): .}) | add // {}) as $b
| [.[1].plugins[]
| select(.source | type == "object")
| select(($b[.name] // null) != .)]' \
"$CACHE_DIR/base.json" "$MARKETPLACE" > "$CACHE_DIR/changed.json"
fi
changed_count="$(jq 'length' "$CACHE_DIR/changed.json")"
# Split changed entries into cached vs uncached. A hit requires the
# *whole* source object (repo, sha, path, ref) to match the cached
# entry, not just name@sha — a repo migration or path change with the
# same SHA is different scan content and must miss the cache.
jq -c -s \
'.[0] as $cache
| (.[1] | map(. + {key: (.name + "@" + (.source.sha // "")) })) as $entries
| {
to_scan: [$entries[] | select(($cache[.key].source // null) != .source)],
cached: [$entries[] | select(($cache[.key].source // null) == .source)
| . + {verdict: $cache[.key]}]
}' \
"$CACHE_DIR/verdicts.json" "$CACHE_DIR/changed.json" > "$CACHE_DIR/split.json"
jq -c '.to_scan' "$CACHE_DIR/split.json" > "$CACHE_DIR/to-scan.json"
jq -c '.cached' "$CACHE_DIR/split.json" > "$CACHE_DIR/cached.json"
to_scan_count="$(jq 'length' "$CACHE_DIR/to-scan.json")"
cached_count="$(jq 'length' "$CACHE_DIR/cached.json")"
cached_fail_count="$(jq '[.[] | select(.verdict.passes == false)] | length' "$CACHE_DIR/cached.json")"
# Build a filtered marketplace containing only the uncached entries.
# Passing this as the action's marketplace-path means the action's own
# base diff (which can't resolve a path outside git) falls back to an
# empty base and scans everything in the file — which is exactly the
# to-scan set. Annotations point to the temp file rather than the real
# marketplace, but the per-entry verdicts still land in the artifact
# and the step summary.
jq -c '{plugins: .}' "$CACHE_DIR/to-scan.json" > "$CACHE_DIR/scan-targets.json"
{
echo "changed=$changed_count"
echo "to_scan=$to_scan_count"
echo "cached=$cached_count"
echo "cached_failures=$cached_fail_count"
} >> "$GITHUB_OUTPUT"
echo "::notice::$changed_count changed entrie(s): $cached_count cached ($cached_fail_count failing), $to_scan_count to scan."
- name: Scan uncached entries
if: steps.changes.outputs.relevant == 'true' && steps.filter.outputs.to_scan != '0'
id: scan
# Capture the action's per-entry outputs even when it exits nonzero.
# The verdict (cached + fresh) is what gates the job, not the action's
# exit code, and the revert workflow needs the artifact even on failure.
continue-on-error: true
uses: anthropics/claude-plugins-community/.github/actions/scan-plugins@b277757588871fe55b2620de8c6dfda470e2e9d8
with:
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
marketplace-path: .scan-cache/scan-targets.json
policy-prompt: .github/policy/prompt.md
fail-on-findings: "true"
scan-all-external: ${{ inputs.scan_all || 'false' }}
claude-cli-version: latest
# Merge fresh verdicts into the cache and assemble this run's full
# verdict set (cached + fresh) for downstream consumers. Runs even when
# the scan step failed so that fail verdicts are also cached — that is
# what lets the revert workflow drop them and what stops the same
# failing SHA from being re-scanned every night.
- name: Merge verdicts and assemble run report
if: steps.changes.outputs.relevant == 'true'
id: report
# The action's `scanned` output travels here via an env var, which is
# subject to the OS argv/envp size limit (~128 KiB on Linux). At ~300
# bytes/entry that is ~400 entries — an order of magnitude above the
# cold-start case, and steady state with the cache is ~10/night. If
# the limit is ever hit the runner fails the step before the script
# runs ("argument list too long") — the right response is to clear
# the cache key and lower max-bumps temporarily. Documented here so
# nobody has to rediscover it.
env:
SCANNED_JSON: ${{ steps.scan.outputs.scanned || '[]' }}
run: |
set -euo pipefail
mkdir -p "$CACHE_DIR"
[[ -f "$CACHE_DIR/cached.json" ]] || echo '[]' > "$CACHE_DIR/cached.json"
[[ -f "$CACHE_DIR/changed.json" ]] || echo '[]' > "$CACHE_DIR/changed.json"
# Defensive: a partial or unparseable action output must not poison
# the cache. Treat it as "scanned nothing".
printf '%s' "$SCANNED_JSON" > "$CACHE_DIR/scanned-raw.json"
if ! jq -e 'type == "array"' "$CACHE_DIR/scanned-raw.json" >/dev/null 2>&1; then
echo "::warning::scan action output is not a valid JSON array — treating as empty."
echo '[]' > "$CACHE_DIR/scanned-raw.json"
fi
# Defense in depth: the scan action runs Claude with Read access over
# a cloned external repo and ANTHROPIC_API_KEY in its process env. A
# successful prompt injection could coerce the model to put key
# material into `summary`/`violations`. The action's own step summary
# already carries that risk; this workflow adds an artifact and a PR
# comment, both public sinks. Scrub any key-shaped token here so it
# never reaches the cache, artifact, or comment.
jq -c '(.. | strings) |= gsub("sk-ant-[A-Za-z0-9_-]{8,}"; "[REDACTED]")' \
"$CACHE_DIR/scanned-raw.json" > "$CACHE_DIR/scanned-raw.json.tmp"
mv "$CACHE_DIR/scanned-raw.json.tmp" "$CACHE_DIR/scanned-raw.json"
now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
# The action's `scanned` output has no SHA or source — join it with
# the change set by name to recover both for the cache key + the
# source-equality lookup guard.
jq -c -s --arg now "$now" \
'.[0] as $changed
| (.[1] // []) as $scanned
| ($changed | map({(.name): .source}) | add // {}) as $srcs
| [$scanned[]
| . + {source: ($srcs[.name] // null), sha: ($srcs[.name].sha // ""), scanned_at: $now}]' \
"$CACHE_DIR/changed.json" "$CACHE_DIR/scanned-raw.json" \
> "$CACHE_DIR/fresh.json"
# Merge fresh verdicts into the cache, keyed by name@sha. The
# full source object is stored so a future repo/path change with the
# same SHA fails the lookup guard. summary/violations are model
# output — truncate to bound cache size (the artifact carries the
# full text for the run that produced it).
jq -c -s \
'.[0] + ([.[1][] | select(.sha != "") | {(.name + "@" + .sha): {
source: .source,
passes: .passes,
summary: ((.summary // "") | .[0:300]),
violations: ((.violations // "") | .[0:500]),
scanned_at: .scanned_at
}}] | add // {})' \
"$CACHE_DIR/verdicts.json" "$CACHE_DIR/fresh.json" \
> "$CACHE_DIR/verdicts.json.tmp"
mv "$CACHE_DIR/verdicts.json.tmp" "$CACHE_DIR/verdicts.json"
# The full per-entry verdict for THIS run's diff: cached verdicts
# plus freshly-scanned verdicts. The revert workflow consumes the
# `failed` list to know exactly which SHAs to drop.
jq -c -s \
'(.[0] | map({name, sha: .source.sha, passes: .verdict.passes,
summary: (.verdict.summary // ""),
violations: (.verdict.violations // ""),
source: "cache"}))
+ (.[1] | map({name, sha, passes,
summary: (.summary // ""),
violations: (.violations // ""),
source: "scan"}))' \
"$CACHE_DIR/cached.json" "$CACHE_DIR/fresh.json" \
> "$CACHE_DIR/run-verdicts.json"
jq -c '[.[] | select(.passes == false) | .name]' "$CACHE_DIR/run-verdicts.json" \
> "$CACHE_DIR/run-failed.json"
fail_count="$(jq 'length' "$CACHE_DIR/run-failed.json")"
total="$(jq 'length' "$CACHE_DIR/run-verdicts.json")"
{
echo "failed_count=$fail_count"
echo "total=$total"
} >> "$GITHUB_OUTPUT"
# `summary` and `violations` are model-generated text shaped by a
# cloned external repo. Strip markdown control characters AND wrap
# in code spans before they hit a publicly-rendered sink — code
# spans neutralize auto-linked bare URLs that a prompt-injected
# upstream could smuggle in. Stripping backticks first stops a
# breakout from the code span.
{
echo "## Policy scan (with verdict cache)"
echo
echo "Changed entries: ${total} · cached: $(jq 'length' "$CACHE_DIR/cached.json") · scanned fresh: $(jq 'length' "$CACHE_DIR/fresh.json") · failures: ${fail_count}"
echo
if [[ "$total" -gt 0 ]]; then
echo "| Plugin | SHA | Passes | Source | Summary |"
echo "|---|---|---|---|---|"
jq -r 'def neutralize: gsub("[|\n\r\\[\\]<>`]"; " ");
.[] | "| \(.name) | `\(.sha[0:8])` | \(if .passes then "✅" else "❌" end) | \(.source) | `\(.summary | neutralize | .[0:120])` |"' \
"$CACHE_DIR/run-verdicts.json"
fi
if [[ "$fail_count" -gt 0 ]]; then
echo
echo "### Violations"
jq -r 'def neutralize: gsub("[|\n\r\\[\\]<>`]"; " ");
.[] | select(.passes == false) | "- **\(.name)** — `\(.violations | neutralize | .[0:500])`"' "$CACHE_DIR/run-verdicts.json"
fi
} >> "$GITHUB_STEP_SUMMARY"
# Used by revert-failed-bumps.yml to know which entries to drop. Always
# uploaded when relevant so the revert workflow can distinguish "scan
# found policy failures" from "scan never ran" (infra error → no revert).
- name: Upload scan verdicts artifact
if: steps.changes.outputs.relevant == 'true'
uses: actions/upload-artifact@v4
with:
name: scan-verdicts
path: |
.scan-cache/run-verdicts.json
.scan-cache/run-failed.json
retention-days: 7
# Save even when the scan failed — fail verdicts are what stop us from
# re-burning Claude time on a known-bad SHA every night.
- name: Save verdict cache
if: always() && steps.changes.outputs.relevant == 'true'
uses: actions/cache/save@v4
with:
path: .scan-cache
key: scan-verdicts-${{ hashFiles('.github/policy/**') }}-${{ github.run_id }}-${{ github.run_attempt }}
# Required-check gate. Fails on either fresh or cached policy failures —
# a known-bad SHA must keep failing until it is reverted or upstream
# fixes it (a new SHA is a new cache key and gets a fresh scan).
- name: Gate on policy verdict
if: steps.changes.outputs.relevant == 'true'
env:
FAILED: ${{ steps.report.outputs.failed_count || '0' }}
SCAN_OUTCOME: ${{ steps.scan.outcome }}
run: |
set -euo pipefail
if [[ "$FAILED" != "0" ]]; then
echo "::error::$FAILED entrie(s) fail policy. See the run summary for verdicts."
exit 1
fi
# The action can also fail without a policy verdict (clone error,
# API error, schema mismatch). With zero parsed failures and a
# nonzero exit, that is an infra error — fail loudly so the revert
# workflow does NOT misread it as "everything passed".
if [[ "$SCAN_OUTCOME" == "failure" ]]; then
echo "::error::Scan step failed without a parseable policy verdict (likely an infra error)."
exit 1
fi

View File

@@ -1,6 +1,6 @@
{
"name": "code-modernization",
"description": "Modernize legacy codebases (COBOL, legacy Java/C++, monolith web apps) with a structured assess → map → extract-rules → reimaginetransform → harden workflow and specialist review agents",
"description": "Modernize legacy codebases (COBOL, legacy Java/C++, monolith web apps) with a structured assess → map → extract-rules → brief → reimagine/transform → harden workflow and specialist review agents",
"author": {
"name": "Anthropic",
"email": "support@anthropic.com"

View File

@@ -7,43 +7,55 @@ A structured workflow and set of specialist agents for modernizing legacy codeba
Legacy modernization fails most often not because the target technology is wrong, but because teams skip steps: they transform code before understanding it, reimagine architecture before extracting business rules, or ship without a harness that would catch behavior drift. This plugin enforces a sequence:
```
assess → map → extract-rules → reimagine transform → harden
assess → map → extract-rules → brief → reimagine | transform → harden
```
Each step has a dedicated slash command. Specialist agents (legacy analyst, business rules extractor, architecture critic, security auditor, test engineer) are invoked from within those commands — or directly — to keep the work honest.
The discovery commands (`assess`, `map`, `extract-rules`) build artifacts under `analysis/<system>/`. The `brief` command synthesizes them into an approval gate. The build commands (`reimagine`, `transform`) write new code under `modernized/`. The `harden` command audits the legacy system and produces a reviewable remediation patch. Each step has a dedicated slash command, and specialist agents (legacy analyst, business rules extractor, architecture critic, security auditor, test engineer) are invoked from within those commands — or directly — to keep the work honest.
## Expected layout
Commands take a `<system-dir>` argument and assume the system being modernized lives at `legacy/<system-dir>/`. Discovery artifacts go to `analysis/<system-dir>/`, transformed code to `modernized/<system-dir>/…`. If your codebase lives elsewhere, symlink it in:
```bash
mkdir -p legacy && ln -s /path/to/your/legacy/codebase legacy/billing
```
## Optional tooling
`/modernize-assess` works best with [`scc`](https://github.com/boyter/scc) (LOC + complexity + COCOMO) or [`cloc`](https://github.com/AlDanial/cloc), and falls back to `find`/`wc` if neither is installed. Portfolio mode also benefits from [`lizard`](https://github.com/terryyin/lizard) (cyclomatic complexity). The commands degrade gracefully without them, but the metrics will be coarser.
## Commands
The commands are designed to be run in order, but each produces a standalone artifact so you can stop, review, and resume.
### `/modernize-brief`
Capture the modernization brief: what's being modernized, why now, constraints (regulatory, data, runtime), non-goals, and success criteria. Produces `analysis/brief.md`. Run this first.
### `/modernize-assess <system-dir>` — or — `/modernize-assess --portfolio <parent-dir>`
Inventory the legacy codebase: languages, line counts, complexity, build system, integrations, technical debt, security posture, documentation gaps, and a COCOMO-derived effort estimate. Produces `analysis/<system>/ASSESSMENT.md` and `analysis/<system>/ARCHITECTURE.mmd`. Spawns `legacy-analyst` (×2) and `security-auditor` in parallel for deep reads. With `--portfolio`, sweeps every subdirectory of a parent directory and writes a sequencing heat-map to `analysis/portfolio.html`.
### `/modernize-assess`
Inventory the legacy codebase: languages, line counts, module boundaries, external integrations, build system, test coverage, known pain points. Produces `analysis/assessment.md`. Uses the `legacy-analyst` agent for deep reads on unfamiliar dialects.
### `/modernize-map <system-dir>`
Build a dependency and topology map of the **legacy** system: program/module call graph, data lineage (programs ↔ data stores), entry points, dead-end candidates, and one traced critical-path business flow. Writes a re-runnable extraction script and produces `analysis/<system>/topology.json` (machine-readable), `analysis/<system>/TOPOLOGY.html` (rendered Mermaid + architect observations), and standalone `call-graph.mmd`, `data-lineage.mmd`, and `critical-path.mmd`.
### `/modernize-map`
Map the legacy structure onto a target architecture: which legacy modules become which target services/packages, data-flow diagrams, migration sequencing. Produces `analysis/map.md`. Uses the `architecture-critic` agent to pressure-test the design.
### `/modernize-extract-rules <system-dir> [module-pattern]`
Mine the business rules embedded in the legacy code — calculations, validations, eligibility, state transitions, policies — into Given/When/Then "Rule Cards" with `file:line` citations and confidence ratings. Spawns three `business-rules-extractor` agents in parallel (calculations, validations, lifecycle). Produces `analysis/<system>/BUSINESS_RULES.md` and `analysis/<system>/DATA_OBJECTS.md`.
### `/modernize-extract-rules`
Extract business rules from the legacy code — the rules that are encoded in procedural logic, COBOL copybooks, stored procedures, or config files — into human-readable form with citations back to source. Produces `analysis/rules.md`. Uses the `business-rules-extractor` agent.
### `/modernize-brief <system-dir> [target-stack]`
Synthesize the discovery artifacts into a phased **Modernization Brief** — the single document a steering committee approves and engineering executes: target architecture, strangler-fig phase plan with entry/exit criteria, behavior contract, validation strategy, open questions, and an approval block. Reads `ASSESSMENT.md`, `TOPOLOGY.html`, and `BUSINESS_RULES.md` and **stops if any are missing** — run the discovery commands first. Produces `analysis/<system>/MODERNIZATION_BRIEF.md` and enters plan mode as a human-in-the-loop gate.
### `/modernize-reimagine`
Propose the target design: APIs, data model, runtime. Explicitly list what changes from legacy and what stays identical. Produces `analysis/design.md`. Uses the `architecture-critic` agent to challenge over-engineering.
### `/modernize-reimagine <system-dir> <target-vision>`
Greenfield rebuild from extracted intent rather than a structural port. Mines a spec (`analysis/<system>/AI_NATIVE_SPEC.md`), designs a target architecture and has it adversarially reviewed (`analysis/<system>/REIMAGINED_ARCHITECTURE.md`), then **scaffolds services with executable acceptance tests** under `modernized/<system>-reimagined/` and writes a `CLAUDE.md` knowledge handoff for the new system. Two human-in-the-loop checkpoints. Spawns `business-rules-extractor`, `legacy-analyst` (×2), `architecture-critic`, and general-purpose scaffolding agents.
### `/modernize-transform`
Do the actual code transformation — module by module. Writes to `modernized/`. Pairs each transformed module with a test suite that pins the pre-transform behavior.
### `/modernize-transform <system-dir> <module> <target-stack>`
Surgical, single-module strangler-fig rewrite. Plans first (HITL gate), then writes characterization tests via `test-engineer`, then an idiomatic target implementation under `modernized/<system>/<module>/`, proves equivalence by running the tests, and produces `TRANSFORMATION_NOTES.md` mapping legacy → modern with deliberate deviations called out. Reviewed by `architecture-critic`.
### `/modernize-harden`
Post-transform review pass: security audit, test coverage, error handling, observability. Uses `security-auditor` and `test-engineer` agents. Produces a findings report ranked Blocker / High / Medium / Nit.
### `/modernize-harden <system-dir>`
Security hardening pass on the **legacy** system: OWASP/CWE scan, dependency CVEs, secrets, injection. Spawns `security-auditor`. Produces `analysis/<system>/SECURITY_FINDINGS.md` ranked Critical / High / Medium / Low and a reviewed `analysis/<system>/security_remediation.patch` with minimal fixes for the Critical/High findings. The patch is reviewed by a second `security-auditor` pass before you see it. **Never edits `legacy/`** — you review and apply the patch yourself when ready, then re-run to verify. Useful as a pre-modernization step when the legacy system will keep running in production during the migration.
## Agents
- **`legacy-analyst`** — Reads legacy code (COBOL, legacy Java/C++, procedural PHP, classic ASP) and produces structured summaries. Good at spotting implicit dependencies, copybook inheritance, and "JOBOL" patterns (procedural code wearing a modern syntax).
- **`business-rules-extractor`** — Extracts business rules from procedural code with source citations. Each rule includes: what, where it's implemented, which conditions fire it, and any corner cases hidden in data.
- **`architecture-critic`** — Adversarial reviewer for target architectures and transformed code. Default stance is skeptical: asks "do we actually need this?" Flags microservices-for-the-resume, ceremonial error handling, abstractions with one implementation.
- **`security-auditor`** — Reviews transformed code for auth, input validation, secret handling, and dependency CVEs. Tuned for the kinds of issues that appear when translating security primitives across stacks (e.g., session handling from servlet to stateless JWT).
- **`test-engineer`** — Audits test suites for behavior-pinning vs. coverage-theater. Flags tests that exercise code paths without asserting outcomes.
- **`legacy-analyst`** — Reads legacy code (COBOL, legacy Java/C++, procedural PHP, classic ASP) and produces structured summaries. Good at spotting implicit dependencies, copybook inheritance, and "JOBOL" patterns (procedural code wearing a modern syntax). Used by `assess` and `reimagine`.
- **`business-rules-extractor`** — Extracts business rules from procedural code with source citations. Each rule includes: what, where it's implemented, which conditions fire it, and any corner cases hidden in data. Used by `extract-rules` and `reimagine`.
- **`architecture-critic`** — Adversarial reviewer for target architectures and transformed code. Default stance is skeptical: asks "do we actually need this?" Flags microservices-for-the-resume, ceremonial error handling, abstractions with one implementation. Used by `reimagine` and `transform`.
- **`security-auditor`** — Reviews code for auth, input validation, secret handling, and dependency CVEs. Tuned for the kinds of issues that appear when translating security primitives across stacks (e.g., session handling from servlet to stateless JWT). Used by `assess` and `harden`.
- **`test-engineer`** — Writes characterization, contract, and equivalence tests that pin legacy behavior so transformation can be proven correct. Flags tests that exercise code paths without asserting outcomes. Used by `transform`.
## Installation
@@ -75,31 +87,31 @@ This plugin ships commands and agents, but modernization projects benefit from a
}
```
Adjust `legacy/` and `modernized/` to match your actual layout. The key invariants: `Edit` under `legacy/` is denied, and writes are scoped to `analysis/` (for documents) and `modernized/` (for the new code).
Adjust `legacy/` and `modernized/` to match your actual layout. The key invariants: `Edit` under `legacy/` is denied, and writes are scoped to `analysis/` (for documents) and `modernized/` (for the new code). Every command in this plugin respects this — `/modernize-harden` writes a patch to `analysis/` rather than editing `legacy/` in place.
## Typical Workflow
```bash
# 1. Write the brief — what are we modernizing and why?
/modernize-brief
# 1. Inventory the legacy system (or sweep a portfolio of them)
/modernize-assess billing
# 2. Inventory the legacy code
/modernize-assess
# 2. Map call graph, data lineage, and the critical path
/modernize-map billing
# 3. Extract business rules before touching the code
/modernize-extract-rules
# 3. Extract business rules into testable Rule Cards
/modernize-extract-rules billing
# 4. Map legacy structure to target
/modernize-map
# 4. Synthesize the approved Modernization Brief (human-in-the-loop gate)
/modernize-brief billing java-spring
# 5. Propose the target design and review it
/modernize-reimagine
# 5a. Greenfield rebuild from the extracted spec…
/modernize-reimagine billing "event-driven services on Java 21 / Spring Boot"
# 6. Transform module by module
/modernize-transform
# 5b. …or transform module by module (strangler fig)
/modernize-transform billing interest-calc java-spring
# 7. Harden: security, tests, observability
/modernize-harden
# 6. Security-harden the legacy system that's still in production
/modernize-harden billing
```
## License

View File

@@ -42,5 +42,5 @@ of the technology, skip it.
## Output format
One "Rule Card" per rule (see the format in the modernize:extract-rules
One "Rule Card" per rule (see the format in the `/modernize-extract-rules`
command). Group by category. Lead with a summary table.

View File

@@ -11,20 +11,29 @@ engineer can fix.
## Coverage checklist
Work through systematically:
Adapt to the target stack — web items don't apply to a batch system,
terminal/screen items don't apply to a SPA. Work through what's relevant:
- **Injection** (SQL, NoSQL, OS command, LDAP, XPath, template) — trace every
user-controlled input to every sink
user-controlled input to every sink, including dynamic SQL and shell-outs
- **Authentication / session** — hardcoded creds, weak session handling,
missing auth checks on sensitive routes
- **Sensitive data exposure** — secrets in source, weak crypto, PII in logs
- **Access control** — IDOR, missing ownership checks, privilege escalation paths
- **XSS / CSRF** — unescaped output, missing tokens
- **Insecure deserialization** — pickle/yaml.load/ObjectInputStream on
untrusted data
missing auth checks on sensitive routes/transactions/jobs
- **Sensitive data exposure** — secrets in source, weak crypto, PII in logs,
cleartext sensitive data in record layouts, flat files, or temp datasets
- **Access control** — IDOR, missing ownership checks, privilege escalation;
missing/permissive resource ACLs (RACF profiles, IAM policies, file perms);
unguarded admin functions
- **XSS / CSRF** — unescaped output, missing tokens (web targets)
- **Insecure deserialization** — untrusted data into pickle/yaml.load/
`ObjectInputStream` or custom record parsers
- **Vulnerable dependencies** — run `npm audit` / `pip-audit` /
read manifests and flag versions with known CVEs
- **SSRF / path traversal / open redirect**
- **Security misconfiguration** — debug mode, verbose errors, default creds
- **SSRF / path traversal / open redirect** (web/network targets)
- **Input validation** — missing length/range/format checks at trust
boundaries (form/screen fields, API params, batch input records) before
persistence or downstream calls
- **Security misconfiguration** — debug mode, verbose errors, default creds,
hardcoded credentials in deployment scripts, job definitions, or config
## Tooling

View File

@@ -23,6 +23,10 @@ cloc --quiet --csv <parent>/<sys> # LOC by language
lizard -s cyclomatic_complexity <parent>/<sys> 2>/dev/null | tail -1
```
If `cloc`/`lizard` are not installed, fall back to `scc <parent>/<sys>`
(LOC + complexity) or `find` + `wc -l` grouped by extension, and estimate
complexity by counting decision keywords per file. Note which tool you used.
Capture: total SLOC, dominant language, file count, mean & max
cyclomatic complexity (CCN). For dependency freshness, locate the
manifest (`package.json`, `pom.xml`, `*.csproj`, `requirements*.txt`,
@@ -69,6 +73,17 @@ scc legacy/$1
Then run `scc --by-file -s complexity legacy/$1 | head -25` to identify the
highest-complexity files. Capture the COCOMO effort/cost estimate scc provides.
If `scc` is not installed, fall back in order:
1. `cloc legacy/$1` for the LOC table, then compute COCOMO-II effort
yourself: `PM = 2.94 × (KSLOC)^1.10` (nominal scale factors). Show the
inputs.
2. If `cloc` is also missing, use `find` + `wc -l` grouped by extension
for LOC, and rank file complexity by counting decision keywords
(`IF`/`EVALUATE`/`WHEN`/`PERFORM` for COBOL; `if`/`for`/`while`/`case`/
`catch` for C-family). Compute COCOMO from KSLOC as above.
Note in the assessment which tool was used so the figures are reproducible.
## Step 2 — Technology fingerprint
Identify, with file evidence:
@@ -80,12 +95,15 @@ Identify, with file evidence:
## Step 3 — Parallel deep analysis
Spawn three subagents **concurrently** using the Task tool:
Spawn three subagents **in parallel**:
1. **legacy-analyst** — "Build a structural map of legacy/$1: what are the
5-10 major functional domains, which source files belong to each, and how
do they depend on each other? Return a markdown table + a Mermaid
`graph TD` of domain-level dependencies. Cite file paths."
5-12 major functional domains (group optional/feature-gated subsystems
under one umbrella), which source files belong to each, and how do they
depend on each other (control flow + shared data)? Return a markdown
table + a Mermaid `graph TD` of domain-level dependencies — use
`subgraph` to cluster and cap at ~40 edges. Cite repo-relative file
paths. Flag dangling references (defined but no source, or unused)."
2. **legacy-analyst** — "Identify technical debt in legacy/$1: dead code,
deprecated APIs, copy-paste duplication, god objects/programs, missing
@@ -99,20 +117,21 @@ Spawn three subagents **concurrently** using the Task tool:
Wait for all three. Synthesize their findings.
## Step 4 — Production runtime overlay (observability)
## Step 4 — Production runtime overlay (optional)
If the system has batch jobs (e.g. JCL members under `app/jcl/`), call the
`observability` MCP tool `get_batch_runtimes` for each business-relevant
job name (interest, posting, statement, reporting). Use the returned
p50/p95/p99 and 90-day series to:
If production telemetry is available — an observability/APM MCP server, batch
job logs, or runtime exports the user can supply — gather p50/p95/p99
wall-clock for the system's key jobs/transactions (e.g. JCL members under
`legacy/$1/jcl/`, scheduled batches, top API routes). Use it to:
- Tag each functional domain from Step 3 with its production wall-clock
cost and **p99 variance** (p99/p50 ratio).
- Flag the highest-variance domain as the highest operational risk —
this is telemetry-grounded, not a static-analysis opinion.
Include a small **Batch Runtime** table (Job · Domain · p50 · p95 · p99 ·
p99/p50) in the assessment.
Include a small **Runtime Profile** table (Job/Route · Domain · p50 · p95 ·
p99 · p99/p50) in the assessment. If no telemetry is available, skip this
step and note the gap in the assessment.
## Step 5 — Documentation gap analysis
@@ -126,7 +145,7 @@ Create `analysis/$1/ASSESSMENT.md` with these sections:
- **Executive Summary** (3-4 sentences: what it is, how big, how risky, headline recommendation)
- **System Inventory** (the scc table + tech fingerprint)
- **Architecture-at-a-Glance** (the domain table; reference the diagram)
- **Production Runtime Profile** (the batch-runtime table from Step 4, with the highest-variance domain called out)
- **Production Runtime Profile** (the runtime table from Step 4 with the highest-variance domain called out — or "no telemetry available")
- **Technical Debt** (top 10, ranked)
- **Security Findings** (CWE table)
- **Documentation Gaps** (top 5)

View File

@@ -8,8 +8,10 @@ single document a steering committee approves and engineering executes.
Target stack: `$2` (if blank, recommend one based on the assessment findings).
Read `analysis/$1/ASSESSMENT.md`, `TOPOLOGY.md`, and `BUSINESS_RULES.md` first.
If any are missing, say so and stop.
Read `analysis/$1/ASSESSMENT.md`, `analysis/$1/TOPOLOGY.html` (and the `.mmd`
files alongside it), and `analysis/$1/BUSINESS_RULES.md` first. If any are
missing, say so and stop — they come from `/modernize-assess`, `/modernize-map`,
and `/modernize-extract-rules` respectively. Run those first.
## The Brief
@@ -35,8 +37,11 @@ fewest-dependencies first. For each phase:
Render the phases as a Mermaid `gantt` chart.
### 4. Behavior Contract
List the **P0 behaviors** from BUSINESS_RULES.md that MUST be proven
equivalent before any phase ships. These become the regression suite.
List the **P0 rules** from BUSINESS_RULES.md (the ones tagged `Priority: P0`
money, regulatory, data integrity) that MUST be proven equivalent before any
phase ships. These become the regression suite. Flag any P0 rule with
Confidence < High as a blocker requiring SME confirmation before its phase
starts.
### 5. Validation Strategy
State which combination applies: characterization tests, contract tests,

View File

@@ -38,6 +38,7 @@ Merge the three result sets. Deduplicate. For each distinct rule, write a
```
### RULE-NNN: <plain-English name>
**Category:** Calculation | Validation | Lifecycle | Policy
**Priority:** P0 | P1 | P2
**Source:** `path/to/file.ext:line-line`
**Plain English:** One sentence a business analyst would recognize.
**Specification:**
@@ -47,11 +48,18 @@ Merge the three result sets. Deduplicate. For each distinct rule, write a
[And <additional outcome>]
**Parameters:** <constants, rates, thresholds with their current values>
**Edge cases handled:** <list>
**Confidence:** High | Medium | Low — <why>
**Suspected defect:** <optional — legacy behavior that looks wrong; decide preserve-vs-fix during transform>
**Confidence:** High | Medium | Low — <why; if < High, state the exact SME question>
```
Priority heuristic — default to **P1**. Assign **P0** if the rule moves money,
enforces a regulatory/compliance requirement, or guards data integrity (and
flag P0 rules at <High confidence as SME-required). Assign **P2** for
display/formatting/convenience rules. The downstream `/modernize-brief`
behavior contract is built from the P0 rules, so assign deliberately.
Write all rule cards to `analysis/$1/BUSINESS_RULES.md` with:
- A summary table at top (ID, name, category, source, confidence)
- A summary table at top (ID, name, category, priority, source, confidence)
- Rule cards grouped by category
- A final **"Rules requiring SME confirmation"** section listing every
Medium/Low confidence rule with the specific question a human needs to answer

View File

@@ -1,23 +1,26 @@
---
description: Security vulnerability scan + remediation — OWASP, CVE, secrets, injection
description: Security vulnerability scan with a reviewable remediation patch — OWASP, CWE, CVE, secrets, injection
argument-hint: <system-dir>
---
Run a **security hardening pass** on `legacy/$1`: find vulnerabilities, rank
them, and fix the critical ones.
them, and produce a reviewable patch for the critical ones.
This command never edits `legacy/` — it writes findings and a proposed patch
to `analysis/$1/`. The user reviews and applies (or not).
## Scan
Spawn the **security-auditor** subagent:
"Adversarially audit legacy/$1 for security vulnerabilities. Cover:
OWASP Top 10 (injection, broken auth, XSS, SSRF, etc.), hardcoded secrets,
vulnerable dependency versions (check package manifests against known CVEs),
missing input validation, insecure deserialization, path traversal.
For each finding return: CWE ID, severity (Critical/High/Med/Low), file:line,
one-sentence exploit scenario, and recommended fix. Also run any available
SAST tooling (npm audit, pip-audit, OWASP dependency-check) and include
its raw output."
"Adversarially audit legacy/$1 for security vulnerabilities. Cover what's
relevant to the stack: injection (SQL/NoSQL/OS command/template), broken
auth, sensitive data exposure, access control gaps, insecure deserialization,
hardcoded secrets, vulnerable dependency versions, missing input validation,
path traversal. For each finding return: CWE ID, severity
(Critical/High/Med/Low), file:line, one-sentence exploit scenario, and
recommended fix. Run any available SAST tooling (npm audit, pip-audit,
OWASP dependency-check) and include its raw output."
## Triage
@@ -28,19 +31,34 @@ Write `analysis/$1/SECURITY_FINDINGS.md`:
## Remediate
For each **Critical** and **High** finding, fix it directly in the source.
Make minimal, targeted changes. After each fix, add a one-line entry under
"Remediation Log" in SECURITY_FINDINGS.md: finding ID → commit-style summary
of what changed.
For each **Critical** and **High** finding, draft a minimal, targeted fix.
Do **not** edit `legacy/` — write all fixes as a single unified diff to
`analysis/$1/security_remediation.patch`, with a comment line above each
hunk citing the finding ID it addresses (`# SEC-001: parameterize the query`).
Show the cumulative diff:
```bash
git -C legacy/$1 diff
```
Add a **Remediation Log** section to SECURITY_FINDINGS.md mapping each
finding ID → one-line summary of the proposed fix and the patch hunk that
implements it.
## Verify
Re-run the security-auditor against the patched code to confirm the
Critical/High findings are resolved. Update the scorecard with before/after.
Spawn the **security-auditor** again to **review the patch** against the
original code:
"Review analysis/$1/security_remediation.patch against legacy/$1. For each
hunk: does it fully remediate the cited finding? Does it introduce new
vulnerabilities or change behavior beyond the fix? Return one verdict per
hunk: RESOLVES / PARTIAL / INTRODUCES-RISK, with a one-line reason."
Add a **Patch Review** section to SECURITY_FINDINGS.md with the verdicts.
If any hunk is PARTIAL or INTRODUCES-RISK, revise the patch and re-review.
## Present
Tell the user the artifacts are ready:
- `analysis/$1/SECURITY_FINDINGS.md` — findings, remediation log, patch review
- `analysis/$1/security_remediation.patch` — review, then apply if appropriate
with `git -C legacy/$1 apply ../../analysis/$1/security_remediation.patch`
- Re-run `/modernize-harden $1` after applying to confirm resolution
Suggest: `glow -p analysis/$1/SECURITY_FINDINGS.md`

View File

@@ -11,31 +11,69 @@ connect? This is the map an engineer needs before touching anything.
## What to produce
Write a one-off analysis script (Python or shell — your choice) that parses
the source under `legacy/$1` and extracts:
the source under `legacy/$1` and extracts the four datasets below. Three
principles apply across stacks; getting them wrong produces a misleading map:
- **Program/module call graph** — who calls whom (for COBOL: `CALL` statements
and CICS `LINK`/`XCTL`; for Java: class-level imports/invocations; for Node:
`require`/`import`)
- **Data dependency graph** — which programs read/write which data stores
(COBOL: copybooks + VSAM/DB2 in JCL DD statements; Java: JPA entities/tables;
Node: model files)
- **Entry points** — batch jobs, transaction IDs, HTTP routes, CLI commands
- **Dead-end candidates** — modules with no inbound edges (potential dead code)
1. **Edges live in two places**direct calls in source, *and* dispatcher/
router calls whose targets are variables (config tables, route maps,
dependency injection, dynamic dispatch). Resolve variables against config
before declaring an edge unresolvable.
2. **The code↔storage join is usually external configuration**, not source —
job/deployment descriptors map logical names to physical stores.
3. **Entry points usually live in deployment config**, not source — without
parsing it, every top-level module looks unreachable.
Extract:
- **Program/module call graph** — direct calls (`CALL`, method invocations,
`import`/`require`) *and* dispatcher calls (`EXEC CICS LINK/XCTL`, DI
container wiring, framework routing, reflection/factory). Resolve variable
call targets against route tables, copybooks, config, or constant pools.
- **Data dependency graph** — which modules read/write which data stores,
joined through the relevant config: `SELECT…ASSIGN TO` ↔ JCL `DD` (batch
COBOL), `EXEC CICS READ/WRITE…FILE()` ↔ CSD `DEFINE FILE` (CICS online),
`EXEC SQL` table refs (embedded SQL), ORM annotations/mappings (Java/.NET),
model files (Node/Python/Ruby). Include UI/screen bindings (BMS maps, JSPs,
templates) — they're dependencies too.
- **Entry points** — whatever the stack's outermost invoker is, read from
where it's defined: JCL `EXEC PGM=` and CICS CSD `DEFINE TRANSACTION`
(mainframe), `web.xml`/route annotations/route files (web), `main()`/argv
parsing (CLI), queue/scheduler subscriptions (event-driven).
- **Dead-end candidates** — modules with no inbound edges. **Only meaningful
once all the entry-point and call-edge types above are in the graph.**
Suppress the dead claim for anything that could be the target of an
unresolved dynamic call. A grep-only graph will mark most dispatcher-driven
modules (CICS programs, Spring controllers, ORM-bound DAOs) dead when they
aren't.
If the source is fixed-column (COBOL columns 872, RPG, etc.), slice the
code area and strip comment lines before regex matching, or you'll match
sequence numbers and commented-out code.
Save the script as `analysis/$1/extract_topology.py` (or `.sh`) so it can be
re-run and audited. Run it. Show the raw output.
re-run and audited. Have it write a machine-readable
`analysis/$1/topology.json` and print a human summary. Run it; show the
summary (cap at ~200 lines for very large estates).
## Render
From the extracted data, generate **three Mermaid diagrams** and write them
to `analysis/$1/TOPOLOGY.html` so the artifact pane renders them live.
to `analysis/$1/TOPOLOGY.html` as a self-contained page that renders in any
browser.
The HTML page must use: dark `#1e1e1e` background, `#d4d4d4` text,
`#cc785c` for `<h2>`/accents, `system-ui` font, all CSS **inline** (no
external stylesheets). Each diagram goes in a
`<pre class="mermaid">...</pre>` block — the artifact server loads
mermaid.js and renders client-side. Do **not** wrap diagrams in
markdown ` ``` ` fences inside the HTML.
external stylesheets). Load Mermaid from a CDN in `<head>`:
```html
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true, theme: 'dark' });
</script>
```
Each diagram goes in a `<pre class="mermaid">...</pre>` block. Do **not**
wrap diagrams in markdown ` ``` ` fences inside the HTML.
1. **`graph TD` — Module call graph.** Cluster by domain (use `subgraph`).
Highlight entry points in a distinct style. Cap at ~40 nodes — if larger,
@@ -46,9 +84,9 @@ markdown ` ``` ` fences inside the HTML.
3. **`flowchart TD` — Critical path.** Trace ONE end-to-end business flow
(e.g., "monthly billing run" or "process payment") through every program
and data store it touches, in execution order. If the `observability`
MCP server is connected, annotate each batch step with its p50/p99
wall-clock from `get_batch_runtimes`.
and data store it touches, in execution order. If production telemetry is
available (see `/modernize-assess` Step 4), annotate each step with its
p50/p99 wall-clock.
Also export the three diagrams as standalone `.mmd` files for re-use:
`analysis/$1/call-graph.mmd`, `analysis/$1/data-lineage.mmd`,
@@ -63,4 +101,4 @@ touched by too many writers.
## Present
Tell the user to open `analysis/$1/TOPOLOGY.html` in the artifact pane.
Tell the user to open `analysis/$1/TOPOLOGY.html` in a browser.

View File

@@ -57,8 +57,9 @@ Enter plan mode. Present the architecture. Wait for approval.
## Phase E — Parallel scaffolding
For each service in the approved architecture (cap at 3 for the demo), spawn
a **general-purpose agent in parallel**:
For each service in the approved architecture (cap at 3 to keep the run
tractable; tell the user which you deferred), spawn a **general-purpose agent
in parallel**:
"Scaffold the <service-name> service per analysis/$1/REIMAGINED_ARCHITECTURE.md
and AI_NATIVE_SPEC.md. Create: project skeleton, domain model, API stubs