Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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.0"
}
]
}
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 comments on a pruning PR, restore 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 comments on a pruning PR, restore 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.0"
}
]
}
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.0",
"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.
174 changes: 174 additions & 0 deletions plugins/marketplace-ops/commands/prune-update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
description: Process /save comments on a pruning PR, restore items, and update .pruneprotect
argument-hint: "[PR number or URL]"
---

## Name
marketplace-ops:prune-update

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

## Description
Reads comments on a pruning PR to find `/save <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.

## 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 Directives

Fetch all comments on the PR (both issue comments and review comments):

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

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

Parse each comment body for lines matching `/save <path>`. For each match, record:
- The path to save
- The GitHub username of the commenter

Deduplicate paths. If no `/save` directives are found, report this and stop.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

### Step 3: Validate Save Paths

Cross-reference each `/save <path>` against the removal manifest table in the PR body. The path must appear in the manifest — 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.

### Step 4: Checkout the PR Branch

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

### Step 5: Restore Saved Items

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

For each valid saved 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: Update .pruneprotect

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 -->


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

### Step 7: Sync and Commit

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

Create a new commit (never amend, never force-push):
```bash
git commit -m "$(cat <<'EOF'
chore: restore saved items from pruning PR

Restored and added to .pruneprotect:
- plugins/foo/ — saved by @username
- plugins/bar/commands/baz.md — saved by @otherperson

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

### Step 8: Push

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

### Step 9: Update PR Body

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

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~~ |
```

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

### Step 10: Comment on PR

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

- `plugins/foo/` — saved by @username
- `plugins/bar/commands/baz.md` — saved by @otherperson

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

### Step 11: Report Results

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

## Return Value
A summary of restored items and the updated PR state.

## Examples

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

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