Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Private maintainer-only files — never commit
_Internal/
CLAUDE.local.md
node_modules
118 changes: 118 additions & 0 deletions .opencode/plugins/pm-skills.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import path from "path";
import fs from "fs";
import matter from "gray-matter";

function safeRead(file) {
try {
return fs.readFileSync(file, "utf8");
} catch {
return null;
}
}

const logHelper = (client, message, level = "info") => {
client.app.log({
body: {
service: "opencode-pm-skills",
level: level,
message,
},
});
};

export const PmSkillsPlugin = async (input, _options) => {
const { client } = input;

logHelper(client, "PM Skills plugin initialized");

const rootDir = path.resolve(__dirname, "../..");
logHelper(client, `Scanning root directory: ${rootDir}`);

const pluginDirs = fs.readdirSync(rootDir).filter((d) => d.startsWith("pm-"));
logHelper(
client,
`Found ${pluginDirs.length} plugin directories: ${pluginDirs.join(", ") || "none"}`,
);

const skillDirs = pluginDirs.map((d) => path.resolve(rootDir, d, "skills"));
logHelper(client, `Discovered ${skillDirs.length} skill directories`);

const commandDirs = pluginDirs.map((d) =>
path.resolve(rootDir, d, "commands"),
);
logHelper(client, `Discovered ${commandDirs.length} command directories`);

const commands = [];
const commandNames = [];
for (const dir of commandDirs) {
if (!fs.existsSync(dir)) {
logHelper(client, `Command directory missing, skipping: ${dir}`, "warn");
continue;
}
const files = fs.readdirSync(dir);
logHelper(client, `Scanning ${dir}: ${files.length} files found`);
for (const file of files) {
if (!file.endsWith(".md")) {
logHelper(client, `Skipping non-markdown file: ${file}`, "debug");
continue;
}
const filePath = path.resolve(dir, file);
const raw = safeRead(filePath);
if (!raw) {
logHelper(client, `Failed to read command file: ${filePath}`, "warn");
continue;
}
const parsed = matter(raw);
const commandName = path.basename(file, ".md");
commands.push({
name: commandName,
template: parsed.content,
data: parsed.data,
});
commandNames.push(commandName);
logHelper(client, `Loaded command: ${commandName}`);
}
}

logHelper(client, `Total commands loaded: ${commands.length}`);

return {
config: async (config) => {
logHelper(client, "Configuring OpenCode with PM Skills...");

config.skills = config.skills || {};
config.skills.paths = config.skills.paths || [];
const addedPaths = [];
for (const dir of skillDirs) {
if (!config.skills.paths.includes(dir)) {
config.skills.paths.push(dir);
addedPaths.push(dir);
}
}
if (addedPaths.length > 0) {
logHelper(
client,
`Injected ${addedPaths.length} skill path(s): ${addedPaths.map((p) => path.basename(path.dirname(p))).join(", ")}`,
);
}

config.command = config.command || {};

let registeredCount = 0;
for (const cmd of commands) {
if (!config.command[cmd.name]) {
registeredCount++;
}
config.command[cmd.name] = {
template: cmd.template,
...cmd.data,
};
}
logHelper(
client,
`Registered ${registeredCount} new command(s), total: ${commands.length} (${commandNames.join(", ")})`,
);
logHelper(client, "PM Skills plugin configuration complete");
},
};
};
19 changes: 18 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Guidance for AI agents (Claude Code, Cowork, and others) working in this reposit

## Project Overview

**PM Skills** (`phuryn/pm-skills`) — a marketplace of **9 independent plugins** (68 skills, 42 commands) that bring structured product-management workflows to AI coding assistants. Built for Claude Code and Claude Cowork; the skills are also compatible with other agents (Gemini CLI, Cursor, Codex CLI).
**PM Skills** (`phuryn/pm-skills`) — a marketplace of **9 independent plugins** (68 skills, 42 commands) that bring structured product-management workflows to AI coding assistants. Built for Claude Code and Claude Cowork; the skills are also compatible with other agents (Gemini CLI, Cursor, Codex CLI). OpenCode is supported as a first-class target via a bundled auto-discovery plugin (see [OpenCode Support](#opencode-support)).

Owner: Paweł Huryn — pawel@productcompass.pm — https://www.productcompass.pm

Expand All @@ -16,6 +16,9 @@ pm-skills/ <- repo root
├── .docs/images/ <- images used by README (webp, gif)
├── .gitattributes
├── .gitignore
├── .opencode/ <- OpenCode auto-discovery plugin + npm deps
│ ├── package.json <- npm manifest (declares gray-matter)
│ └── plugins/pm-skills.js <- plugin that registers all PM skills + commands
├── CLAUDE.md <- this file (agent guidance, single source of truth)
├── AGENTS.md <- pointer to CLAUDE.md (for non-Claude agents)
├── CONTRIBUTING.md <- contributor guidelines
Expand Down Expand Up @@ -67,6 +70,19 @@ pm-skills/ <- repo root

Descriptions in `plugin.json` and the repo `README.md` should stay aligned (identical text).

## OpenCode Support

This repo ships a bundled OpenCode plugin (`.opencode/plugins/pm-skills.js`) so the same skills and commands work in OpenCode with no manual copying.

At startup it:
- Scans the repo root for `pm-*` directories.
- Injects each plugin's `skills/` path into `config.skills.paths` — OpenCode then lazy-loads skills from those paths.
- Parses every `commands/*.md` file (via `gray-matter` frontmatter) and registers it under `config.command`, so the same `/slash` commands are available.

It is self-bootstrapping: adding, removing, or editing skills and commands anywhere under `pm-*/skills/` or `pm-*/commands/` is picked up automatically on the next OpenCode start — no `.opencode/` edits required.

Runtime dependency: `gray-matter` (declared in `.opencode/package.json`). Install once with `npm install` inside `.opencode/`; `node_modules/` is gitignored.

## Versioning

- All versions are currently **2.0.0** — `marketplace.json` and all 9 `plugin.json` files.
Expand All @@ -87,6 +103,7 @@ Descriptions in `plugin.json` and the repo `README.md` should stay aligned (iden
2. If skills/commands were added or removed, update the counts in `README.md`.
3. If totals changed, update the count in the `marketplace.json` description.
4. Bump versions across all manifests (see Versioning).
5. No OpenCode sync needed — the bundled `.opencode/plugins/pm-skills.js` re-scans `pm-*/skills/` and `pm-*/commands/` on startup (just ensure `npm install` has run in `.opencode/` once).

### After a description change
- A `plugin.json` description changed → check whether `README.md` needs the same edit (they stay aligned).
Expand Down
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,24 +110,31 @@ codex plugin add pm-ai-shipping@pm-skills

This is a best-effort, model-driven conversion (some Claude-specific command syntax won't translate), but it's a quick way to get the guided workflows on Codex without leaving the CLI.

### Other AI assistants (skills only)
### OpenCode

The `skills/*/SKILL.md` files follow the universal skill format and work with any tool that reads it. Commands (`/slash-commands`) are Claude-specific.
OpenCode ships with a native plugin bundled in this repo (`.opencode/plugins/pm-skills.js`), which auto-registers every skill and `/slash` command.

```jsonc
# Add the plugin to your opencode plugin config
{
"plugin": ["pm-skills@git+https://github.com/phuryn/pm-skills.git"]
}
# Then restart opencode.
```

**What you get:** every skill (the PM frameworks) **and** every `/slash` command (`/discover`, `/write-prd`, …), both registered automatically by the bundled plugin.

### Other AI assistants

The `skills/*/SKILL.md` files follow the universal skill format and work with any tool that reads it. `/slash-commands` are Claude-specific everywhere except OpenCode, where the bundled plugin below registers them automatically.

| Tool | How to use | What works |
|------|-----------|------------|
| **Gemini CLI** | Copy skill folders to `.gemini/skills/` | Skills only |
| **OpenCode** | Copy skill folders to `.opencode/skills/` | Skills only |
| **Cursor** | Copy skill folders to `.cursor/skills/` | Skills only |
| **Kiro** | Copy skill folders to `.kiro/skills/` | Skills only |

```bash
# Example: copy all skills for OpenCode (project-level)
for plugin in pm-*/; do
mkdir -p .opencode/skills/
cp -r "$plugin/skills/"* .opencode/skills/ 2>/dev/null
done

# Example: copy all skills for Gemini CLI (global)
for plugin in pm-*/; do
cp -r "$plugin/skills/"* ~/.gemini/skills/ 2>/dev/null
Expand Down
38 changes: 38 additions & 0 deletions docs/opencode-auto-discovery-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Proposal: a bundled OpenCode plugin so PM Skills just works

OpenCode is the one first-class target with no native PM Skills path. This proposes shipping a small plugin inside the repo (`.opencode/plugins/pm-skills.js`) that auto-registers every skill and `/slash` command at runtime — one-line install, nothing to copy, nothing to keep in sync.

## Today's gap

OpenCode can't read the Claude-style `marketplace.json`, so right now its users get the worst deal of any supported assistant. Skills have to be hand-copied into OpenCode's skill paths with no source of truth — every plugin update means re-copying and risking drift. The 42 guided workflows (`/discover`, `/write-prd`, `/red-team-prd`, …) don't register at all, since nothing turns `commands/*.md` into real OpenCode commands. And there's no incremental story: add a skill upstream, and every OpenCode user has to redo their setup.

## What I'm proposing

A repo-bundled plugin (npm manifest in `.opencode/package.json`, depends on `gray-matter`) that, on init, does the boring work itself:

1. Scans the repo root for `pm-*/` dirs (the 9 plugins).
2. Pushes each `pm-*/skills/` path into `config.skills.paths` — OpenCode then lazily discovers every `SKILL.md`. No symlinks, no config edits. This works because `Config.get()` is a cached singleton, so the mutation is visible when skills get resolved later.
3. Frontmatter-parses every `pm-*/commands/*.md` and registers it as `config.command[name] = { template, ...data }`.
4. Logs init / scan / load / totals under `opencode-pm-skills` so setup is observable instead of silent.

The skill and command files stay the single source of truth — on `git pull`, new ones just show up.

### Install

```jsonc
{
"plugin": ["pm-skills@git+https://github.com/phuryn/pm-skills.git"]
}
```

Then restart OpenCode.

## What you get

All 68 skills and 42 commands, native; one-line install; stays in sync.

## Verification

- Plugin logs `Found 9 plugin directories`.
- 9 paths land in `config.skills.paths`; 42 commands register in `config.command`.
- `/write-prd` and the `create-prd` skill resolve in a fresh session.
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "pm-skills",
"type": "module",
"private": true,
"main": ".opencode/plugins/pm-skills.js",
"dependencies": {
"gray-matter": "^4.0.3"
}
}