Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@
"source": "./plugins/ai-sbom",
"description": "Generate AI Software Bill of Materials (SBOM) declarations for PR descriptions",
"version": "0.0.1"
},
{
"name": "marketplace-ops",
"source": "./plugins/marketplace-ops",
"description": "Maintenance commands for Claude Code plugin marketplaces",
"version": "0.1.2"
}
]
}
12 changes: 12 additions & 0 deletions .pruneprotect
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Plugins protected from automated pruning
# One path per line. Lines starting with # are comments.

# Canonical example plugin
plugins/hello-world/

# Infrastructure plugins (hook-only, by design)
plugins/metrics/
plugins/native-notifications/

# Marketplace operations plugin
plugins/marketplace-ops/
11 changes: 11 additions & 0 deletions PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This document lists all available Claude Code plugins and their commands in the
- [Hello World](#hello-world-plugin)
- [Jira](#jira-plugin)
- [Lvms](#lvms-plugin)
- [Marketplace Ops](#marketplace-ops-plugin)
- [Must Gather](#must-gather-plugin)
- [Node](#node-plugin)
- [Node Tuning](#node-tuning-plugin)
Expand Down Expand Up @@ -236,6 +237,16 @@ LVMS (Logical Volume Manager Storage) plugin for troubleshooting and debugging s

See [plugins/lvms/README.md](plugins/lvms/README.md) for detailed documentation.

### Marketplace Ops Plugin

Maintenance commands for Claude Code plugin marketplaces

**Commands:**
- **`/marketplace-ops:prune-update` `[PR number or URL]`** - Process /save and /drop comments on a pruning PR, restore or remove items, and update .pruneprotect
- **`/marketplace-ops:prune` `[--dry-run]`** - Analyze and prune stale plugins, commands, and skills from the marketplace

See [plugins/marketplace-ops/README.md](plugins/marketplace-ops/README.md) for detailed documentation.

### Must Gather Plugin

A plugin to analyze and report on must-gather data
Expand Down
22 changes: 22 additions & 0 deletions docs/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -1764,6 +1764,28 @@
}
],
"version": "0.0.1"
},
{
"commands": [
{
"argument_hint": "[PR number or URL]",
"description": "Process /save and /drop comments on a pruning PR, restore or remove items, and update .pruneprotect",
"name": "prune-update",
"synopsis": "/marketplace-ops:prune-update [PR number or URL]"
},
{
"argument_hint": "[--dry-run]",
"description": "Analyze and prune stale plugins, commands, and skills from the marketplace",
"name": "prune",
"synopsis": "/marketplace-ops:prune [--dry-run]"
}
],
"description": "Maintenance commands for Claude Code plugin marketplaces",
"has_readme": true,
"hooks": [],
"name": "marketplace-ops",
"skills": [],
"version": "0.1.2"
}
]
}
8 changes: 8 additions & 0 deletions plugins/marketplace-ops/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "marketplace-ops",
"description": "Maintenance commands for Claude Code plugin marketplaces",
"version": "0.1.2",
"author": {
"name": "github.com/openshift-eng"
}
}
29 changes: 29 additions & 0 deletions plugins/marketplace-ops/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# marketplace-ops

Maintenance commands for Claude Code plugin marketplaces. Identifies stale or low-value plugins, commands, and skills, then opens a PR to remove them with a structured review workflow.

## Commands

### `/marketplace-ops:prune`

Analyzes the repository for inactive content using git history, structural signals, and LLM judgment. Creates a branch removing candidates and opens a PR with a removal manifest.

Use `--dry-run` to see what would be pruned without creating a branch or PR.

### `/marketplace-ops:prune-update`

Processes `/save <path>` comments on a pruning PR. Restores saved items, adds them to `.pruneprotect` permanently, and pushes a new commit to the PR branch.

## Protection

Create a `.pruneprotect` file at the repo root to permanently exclude paths from pruning:

```
# Canonical example plugin
plugins/hello-world/

# Saved by @username on 2026-05-05
plugins/foo/
```

Lines starting with `#` are comments. Each non-comment line is a path prefix that protects everything under it.
243 changes: 243 additions & 0 deletions plugins/marketplace-ops/commands/prune-update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
---
description: Process /save and /drop comments on a pruning PR, restore or remove items, and update .pruneprotect
argument-hint: "[PR number or URL]"
---

## Name
marketplace-ops:prune-update

## Synopsis
```text
/marketplace-ops:prune-update [PR number or URL]
```

## Description
Reads comments on a pruning PR to find `/save <path>` and `/drop <path>` directives.

For each **saved** item:
1. Restores the files from the base branch.
2. Adds the path to `.pruneprotect` permanently, with a comment noting who requested it and when.
3. Pushes a new commit to the PR branch (never force-pushes).
4. Updates the PR body to mark saved items.

For each **dropped** item:
1. If the item was previously `/save`d: removes its files again, removes it from `.pruneprotect`.
2. If the item is a new addition (not in the original manifest): removes its files and adds a new row to the manifest.
3. For surviving plugins that lose commands or skills, bumps the patch version in `plugin.json`.
4. Pushes a new commit to the PR branch (never force-pushes).
5. Updates the PR body to reflect the drop.

## Arguments
- `$1`: (Optional) PR number or URL. If omitted, searches for the most recent open pruning PR by the current user.

## Implementation

### Step 1: Find the Pruning PR

If a PR number or URL was provided, use it directly. Otherwise, find the most recent open pruning PR:

```bash
gh pr list --author="@me" --state=open --search="prune stale marketplace" --json number,title,url,headRefName --limit 5
```

Select the first result. If no pruning PR is found, report this to the user and stop.

### Step 2: Read PR Comments for /save and /drop Directives

Fetch all comments on the PR (both issue comments and review comments), including the author's association to the repository:

```bash
# Issue comments
gh api repos/{owner}/{repo}/issues/{pr_number}/comments \
--jq '.[] | {author: .user.login, association: .author_association, body: .body}'

# Review comments
gh api repos/{owner}/{repo}/pulls/{pr_number}/comments \
--jq '.[] | {author: .user.login, association: .author_association, body: .body}'
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Parse each comment body for lines matching `/save <path>` or `/drop <path>`. **Only accept directives from trusted participants** — those with `author_association` of `OWNER`, `MEMBER`, or `COLLABORATOR`. Skip directives from other associations and log a warning (e.g., "Ignoring `/drop` from @user — not a repository collaborator").

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
For each accepted match, record:
- The directive type (`save` or `drop`)
- The path
- The GitHub username of the commenter

Deduplicate paths. If a path has both `/save` and `/drop` from different comments, the **latest comment wins** (last-writer-wins). If no valid directives are found, report this and stop.

### Step 3: Validate Paths

**For `/save` paths:** Cross-reference against the removal manifest table in the PR body. The path must appear in the manifest (and not already be marked `[SAVED]`) — if it does not, it was either already saved, was not part of this pruning cycle, or is a typo. Report invalid paths to the user but continue processing valid ones.

**For `/drop` paths — two valid cases:**
1. **Undo a previous save:** The path appears in the manifest with `[SAVED]` strikethrough markup. This reverses the save.
2. **New drop:** The path does NOT appear in the manifest but exists on the base branch. The reviewer is requesting an additional removal beyond what the automated pruning flagged. Verify the path exists on the base branch before accepting. Also verify the path is not listed in `.pruneprotect` — if it is, warn that the item is protected and skip it unless the `/drop` comment explicitly says to override (e.g., `/drop --force plugins/foo/`).

Report paths that fail validation but continue processing valid ones.

### Step 4: Checkout the PR Branch

```bash
gh pr checkout {pr_number}
```

### Step 5: Process Saved Items

Get the base branch from the PR:
```bash
base_branch=$(gh pr view {pr_number} --json baseRefName --jq '.baseRefName')
```

For each valid `/save` path, restore from the base branch:
```bash
git checkout {upstream_remote}/{base_branch} -- {path}
```

Use the upstream remote (not origin) to ensure the base branch is current.

Comment thread
stbenjam marked this conversation as resolved.
### Step 6: Process Dropped Items

For each valid `/drop` path:

**If undoing a previous save:**
1. Remove the files from the working tree:
```bash
# Full plugin removal
git rm -rf {path}
# Individual file removal
git rm {path}
```
2. Remove the path's entry from `.pruneprotect` (including its comment line).

**If adding a new drop:**
1. Remove the files from the working tree (same `git rm` commands as above).
2. For surviving plugins that lose commands or skills (not a full plugin removal), bump the patch version in `plugins/{plugin-name}/.claude-plugin/plugin.json` (e.g., `0.0.5` → `0.0.6`).

### Step 7: Update .pruneprotect

For `/save` items: append each saved path to `.pruneprotect` with a comment indicating who requested the save:

```
# Saved by @username on 2026-05-05
plugins/foo/
```
Comment on lines +121 to +124
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language tags to fenced blocks to satisfy markdownlint (MD040).

Several fenced snippets are unlabeled; adding text preserves rendering and removes lint noise.

Proposed doc patch
-```
+```text
 # Saved by `@username` on 2026-05-05
 plugins/foo/

- +text
| plugin | plugins/foo/ | No commits in 7 months, v0.0.1 |


-```
+```text
| ~~plugin~~ | ~~`plugins/foo/`~~ | ~~SAVED by `@username`~~ |

- +text
| plugin | plugins/foo/ | Dropped by @username |


-```
+```text
| command | `plugins/bar/commands/baz.md` | Manually dropped by `@username` |
</details>

 


Also applies to: 169-171, 175-177, 181-183, 187-189

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 120-120: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @plugins/marketplace-ops/commands/prune-update.md around lines 120 - 123, The
Markdown file contains unlabeled fenced code blocks that trigger markdownlint
MD040; update each triple-backtick fence in
plugins/marketplace-ops/commands/prune-update.md to include the language tag
"text" (e.g., change totext) for the blocks that include the snippet
beginning "# Saved by @username on 2026-05-05" and the subsequent table rows
(the blocks showing "| plugin | plugins/foo/ ..." and the other table lines),
and also apply the same change to the other occurrences called out (around the
blocks at the other ranges noted such as 169-171, 175-177, 181-183, 187-189) so
all unlabeled fences become ```text.


</details>

<!-- fingerprinting:phantom:poseidon:hawk -->

<!-- d98c2f50 -->

<!-- This is an auto-generated comment by CodeRabbit -->


For `/drop` items that undo a save: remove the path and its comment from `.pruneprotect`.

If `.pruneprotect` does not exist, create it with the saved entries.

### Step 8: Sync and Commit

Run `make update` to regenerate marketplace.json and docs after changes:
```bash
make update
git add -A
```

Create a new commit (never amend, never force-push):
```bash
git commit -m "$(cat <<'EOF'
chore: process save/drop directives from pruning PR

Restored and added to .pruneprotect:
- plugins/foo/ — saved by @username

Dropped:
- plugins/bar/commands/baz.md — dropped by @otherperson

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EOF
)"
```

### Step 9: Push

Push the new commit with a regular push:
```bash
git push
```

### Step 10: Update PR Body

Read the current PR body:
```bash
gh pr view {pr_number} --json body --jq '.body'
```

**For `/save` paths:** In the removal manifest table, find the rows for saved paths and apply strikethrough with a `[SAVED]` tag. For example, change:

```
| plugin | `plugins/foo/` | No commits in 7 months, v0.0.1 |
```

To:

```
| ~~plugin~~ | ~~`plugins/foo/`~~ | ~~SAVED by @username~~ |
```

**For `/drop` paths that undo a save:** Remove the strikethrough and `[SAVED]` tag, restoring the row to its original state with the original reason (if available from git history of the PR body), or use a new reason:

```
| plugin | `plugins/foo/` | Dropped by @username |
```

**For new `/drop` paths:** Add a new row to the manifest table:

```
| command | `plugins/bar/commands/baz.md` | Manually dropped by @username |
```

Update the PR body:
```bash
gh pr edit {pr_number} --body "{updated_body}"
```

### Step 11: Comment on PR

Add a summary comment:
```bash
gh pr comment {pr_number} --body "$(cat <<'EOF'
Processed `/save` and `/drop` comments.

**Saved** (restored and added to `.pruneprotect`):
- `plugins/foo/` — saved by @username

**Dropped** (removed):
- `plugins/bar/commands/baz.md` — dropped by @otherperson

Remaining removals: N items.
EOF
)"
```

Omit the **Saved** or **Dropped** section if there are no items for that category.

### Step 12: Report Results

Print a summary to the user: what was restored, what was dropped, what remains in the PR, and the updated PR URL.

## Return Value
A summary of saved/dropped items and the updated PR state.

## Examples

1. **Process saves and drops on a specific PR:**
```text
/marketplace-ops:prune-update 42
```

2. **Auto-detect the pruning PR:**
```text
/marketplace-ops:prune-update
```

## Comment Format Reference

On the pruning PR, trusted collaborators can comment:

```text
/save plugins/foo/ # Restore and permanently protect
/drop plugins/bar/commands/baz.md # Undo a /save, or manually add a removal
/drop --force plugins/protected/ # Drop even if listed in .pruneprotect
```
Loading