diff --git a/.gitignore b/.gitignore index 9f5ad44..564279d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ # Private maintainer-only files — never commit _Internal/ CLAUDE.local.md +node_modules \ No newline at end of file diff --git a/.opencode/plugins/pm-skills.js b/.opencode/plugins/pm-skills.js new file mode 100644 index 0000000..700b479 --- /dev/null +++ b/.opencode/plugins/pm-skills.js @@ -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"); + }, + }; +}; diff --git a/CLAUDE.md b/CLAUDE.md index d346c47..34a295f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 @@ -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 @@ -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. @@ -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). diff --git a/README.md b/README.md index 275de0a..e6d2208 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/opencode-auto-discovery-plugin.md b/docs/opencode-auto-discovery-plugin.md new file mode 100644 index 0000000..510b2e1 --- /dev/null +++ b/docs/opencode-auto-discovery-plugin.md @@ -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. diff --git a/package.json b/package.json new file mode 100644 index 0000000..a1f4550 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "pm-skills", + "type": "module", + "private": true, + "main": ".opencode/plugins/pm-skills.js", + "dependencies": { + "gray-matter": "^4.0.3" + } +}