skill-portability

2

Make any plugin fully portable across all platforms. Accepts Claude, Cursor, Gemini, OpenCode, or bare SKILL.md repos as input. Emits every missing platform artifact.

3 skills

assessing-plugin-portability

Assess a plugin repo and report portability gaps across all platforms. Just generates a portability report. Makes no changes to your code.

# Assessing Plugin Portability Assess a plugin repo and report portability gaps across all platforms. Makes no changes. **Input:** `plugin_path` (string, required) — Path to the plugin root directory. **Output:** Complete portability assessment with per-platform scores and recommendation. > **Detection Algorithm:** `lib/patterns/detection-algorithm.md` > **Rubric Framework:** `lib/patterns/rubric-framework.md` > **Platform Rules:** `lib/patterns/platforms/<platform>.md` > **Injection Checks:** `lib/patterns/injection-checks.md` --- ## Overview | Phase | Description | | ----- | ----------- | | **Phase 1: Detect** | Scan metadata, elect canonical, build model, classify shape | | **Phase 2: Inventory** | Discover all assets across all platform conventions | | **Phase 3: Score** | Run per-platform rubric, detect blockers | | **Phase 4: Recommend** | Choose uplift target, per-platform recommendations | | **Phase 5: Report** | Print full assessment report | **Minimum starting state:** At least one `skills/*/SKILL.md` with frontmatter, or any platform manifest file. --- ## Phase 1: Detect ### Step 1.1: Scan and Infer ```pseudocode DETECT(plugin_path): computed.sources = scan_metadata_sources(plugin_path) # D1 IF len(computed.sources) == 0: DISPLAY "No recognisable plugin signals found in {plugin_path}." EXIT computed.canonical = elect_canonical(computed.sources) # D2 computed.metadata = build_metadata_model(computed.sources) # D3 print_inference_summary(computed.metadata, computed.canonical) # D4 computed.shape = classify_shape(computed.sources) # D5 ``` --- ## Phase 2: Inventory ### Step 2.1: Check Platform Manifests Check all 10 manifest paths across 6 platforms, recording `{ platform, path, status }`: ```pseudocode INVENTORY_MANIFESTS(computed): manifest_checks = [ { platform: "claude-code", path: ".claude-plugin/plugin.json" }, { platform: "claude-code", path: ".claude-plugin/marketplace.json" }, { platform: "cursor", path: ".cursor-plugin/plugin.json" }, { platform: "gemini-cli", path: "gemini-extension.json" }, { platform: "gemini-cli", path: "GEMINI.md" }, { platform: "opencode", path: ".opencode/plugins/" + computed.metadata.name + ".js" }, { platform: "codex", path: ".codex-plugin/plugin.json" }, { platform: "codex", path: ".agents/plugins/marketplace.json" }, { platform: "copilot-cli", path: "package.json" }, { platform: "copilot-cli", path: ".github/copilot-instructions.md" } ] computed.manifest_results = [] FOR check IN manifest_checks: status = IF file_exists(plugin_path + "/" + check.path) THEN "PRESENT" ELSE "MISSING" computed.manifest_results.append({ platform: check.platform, path: check.path, status: status }) ``` ### Step 2.2: Check Context Files ```pseudocode INVENTORY_CONTEXT_FILES(computed): context_checks = [ "CLAUDE.md", "AGENTS.md", "GEMINI.md", ".github/copilot-instructions.md", ".codex/INSTALL.md" ] computed.context_results = [] FOR path IN context_checks: status = IF file_exists(plugin_path + "/" + path) THEN "PRESENT" ELSE "MISSING" computed.context_results.append({ path: path, status: status }) ``` ### Step 2.3: Check Per-Skill Sidecars Use `skill.dir` (directory basename) — not `skill.name` (frontmatter value) — for path construction. ```pseudocode INVENTORY_SIDECARS(computed): computed.skills = Glob(plugin_path + "/skills/*/SKILL.md") sidecar_files = ["copilot-tools.md", "codex-tools.md", "gemini-tools.md"] computed.sidecar_results = [] FOR skill IN computed.skills: FOR sidecar IN sidecar_files: target = "skills/" + skill.dir + "/references/" + sidecar status = IF file_exists(plugin_path + "/" + target) THEN "PRESENT" ELSE "MISSING" computed.sidecar_results.append({ skill: skill.dir, file: sidecar, status: status }) ``` ### Step 2.4: Check Hooks ```pseudocode INVENTORY_HOOKS(computed): hook_checks = [ "hooks/hooks.json", "hooks/hooks-cursor.json", ".github/hooks/", "hooks/run-hook.cmd" ] computed.hook_results = [] FOR path IN hook_checks: status = IF file_exists(plugin_path + "/" + path) THEN "PRESENT" ELSE "MISSING" computed.hook_results.append({ path: path, status: status }) ``` ### Step 2.5: Check Frontmatter Compatibility ```pseudocode INVENTORY_FRONTMATTER(computed): computed.frontmatter_results = [] FOR skill IN computed.skills: frontmatter = parse_yaml_frontmatter(skill.content) IF frontmatter.name AND frontmatter.description: status = "COMPATIBLE" ELSE: status = "MISSING FRONTMATTER" computed.frontmatter_results.append({ skill: skill.dir, status: status }) ``` ### Step 2.6: Check Session-Start Injection See `lib/patterns/injection-checks.md` for the 8-component verification. ```pseudocode INVENTORY_INJECTION(computed): using_path = "skills/using-" + computed.metadata.name + "/SKILL.md" IF NOT file_exists(plugin_path + "/" + using_path): computed.injection_status = "NOT CONFIGURED" RETURN computed.injection_results = check_injection_components(computed) computed.injection_summary = compute_injection_summary(computed.injection_results) ``` ### Step 2.7: Check Context File Completeness ```pseudocode INVENTORY_CONTEXT_COMPLETENESS(computed): computed.completeness_issues = [] IF file_exists(plugin_path + "/GEMINI.md"): content = Read(plugin_path + "/GEMINI.md") FOR skill IN computed.skills: IF "@./skills/" + skill.dir + "/SKILL.md" NOT IN content: computed.completeness_issues.append("GEMINI.md missing @include for " + skill.dir) IF "@./skills/" + skill.dir + "/references/gemini-tools.md" NOT IN content: computed.completeness_issues.append("GEMINI.md missing gemini-tools include for " + skill.dir) ELSE: computed.completeness_issues.append("GEMINI.md: MISSING — cannot check includes") IF file_exists(plugin_path + "/AGENTS.md"): content = Read(plugin_path + "/AGENTS.md") FOR skill IN computed.skills: IF "skills/" + skill.dir + "/SKILL.md" NOT IN content: computed.completeness_issues.append("AGENTS.md missing reference for " + skill.dir) ELSE: computed.completeness_issues.append("AGENTS.md: MISSING — cannot check skill references") # Copilot-specific computed.copilot_checks = [ Glob(plugin_path + "/.github/instructions/*.instructions.md"), Glob(plugin_path + "/.github/agents/*.agent.md") ] # Gemini-specific computed.gemini_checks = [ Glob(plugin_path + "/commands/*.toml"), Glob(plugin_path + "/policies/*.toml") ] # MCP configs computed.mcp_checks = [] FOR path IN [".mcp.json", "mcp.json"]: IF file_exists(plugin_path + "/" + path): computed.mcp_checks.append({ path: path, status: "PRESENT" }) ``` --- ## Phase 3: Score ### Step 3.1: Run Per-Platform Rubric See `lib/patterns/rubric-framework.md` for category definitions and scoring scale (0-3 per category, max 21 per platform). ```pseudocode SCORE(computed): platforms = ["claude-code", "cursor", "gemini-cli", "opencode", "copilot-cli", "codex"] FOR platform IN platforms: rules = load_platform_rules(platform) # from lib/patterns/platforms/ computed.scores[platform] = {} FOR category IN rules.categories: computed.scores[platform][category.name] = evaluate(category, computed) computed.scores[platform].total = sum(computed.scores[platform][c] for c IN rules.categories) computed.scores[platform].band = classify_band(computed.scores[platform].total) ``` ### Step 3.2: Detect Blockers ```pseudocode DETECT_BLOCKERS(computed): computed.blockers = [] # Critical: no trustworthy metadata IF all metadata fields derived from hard fallbacks only: computed.blockers.append({ severity: "critical", description: "No trustworthy metadata source — all fields from hard fallbacks" }) # Major: unresolved tool assumptions FOR skill IN computed.skills: IF skill references platform-specific tools AND skill.dir not in sidecar_results with status PRESENT: computed.blockers.append({ severity: "major", description: "Unresolved tool assumptions in skills/" + skill.dir }) # Major: hook env hard-coding FOR hook_file IN ["hooks/hooks.json", "hooks/hooks-cursor.json"]: IF file_exists(hook_file): content = Read(plugin_path + "/" + hook_file) IF "CLAUDE_PLUGIN_ROOT" IN content AND env_branching_absent(content): computed.blockers.append({ severity: "major", description: "Hook env hard-coding in " + hook_file + " (no env branching)" }) # Major: docs/structure mismatch IF install_docs_reference_paths_that_dont_exist(computed): computed.blockers.append({ severity: "major", description: "Install docs describe paths that don't exist in repo" }) # Minor: GEMINI.md import gaps gemini_gaps = [i for i IN computed.completeness_issues if "GEMINI.md missing" IN i] IF len(gemini_gaps) > 0: computed.blockers.append({ severity: "minor", description: "GEMINI.md import gaps: " + str(len(gemini_gaps)) + " missing includes" }) ``` --- ## Phase 4: Recommend ### Step 4.1: Choose Uplift Target ```pseudocode RECOMMEND_TARGET(computed): IF computed.shape == "bare-skill-repo": IF len(computed.skills) <= 3: computed.recommendation = "skill-first" ELSE: computed.recommendation = "full-portable-plugin" ELIF computed.shape == "single-platform-plugin": computed.recommendation = "full-portable-plugin" ELIF computed.shape == "multi-platform-source": computed.recommendation = "full-portable-plugin" ELIF computed.shape == "curated-distribution": computed.recommendation = "curated-note-only" ELSE: computed.recommendation = "full-portable-plugin" ``` ### Step 4.2: Choose Codex Path ```pseudocode RECOMMEND_CODEX(computed): IF ".codex-plugin/plugin.json" PRESENT in computed.manifest_results: computed.codex_rec = "native-plugin-packaging" ELIF computed.shape == "bare-skill-repo" AND len(computed.skills) > 0: computed.codex_rec = "native-skill-discovery" ELIF computed.shape IN ["single-platform-plugin", "multi-platform-source"]: computed.codex_rec = "native-plugin-packaging" ELIF computed.shape == "curated-distribution": computed.codex_rec = "curated-package-note" ELSE: computed.codex_rec = "native-plugin-packaging" ``` ### Step 4.3: Per-Platform Action Summary ```pseudocode SUMMARISE_ACTIONS(computed): computed.actions = {} FOR platform IN platforms: band = computed.scores[platform].band IF band == "strong": computed.actions[platform] = "No action required" ELIF band == "viable": computed.actions[platform] = "Minor gaps — review category detail" ELIF band == "partial": computed.actions[platform] = "Significant gaps — uplift recommended" ELSE: # weak computed.actions[platform] = "Full uplift required" ``` --- ## Phase 5: Report ### Step 5.1: Print Assessment ```text # Portability Assessment: {name} v{version} ## Repo Shape {shape} Metadata inferred from: {canonical.path} ## Platform Scores | Platform | Score | Band | Action | |-------------|-------|---------|------------------------------------| | claude-code | X/21 | {band} | {action} | | cursor | X/21 | {band} | {action} | | gemini-cli | X/21 | {band} | {action} | | opencode | X/21 | {band} | {action} | | copilot-cli | X/21 | {band} | {action} | | codex | X/21 | {band} | {action} | ### Per-Platform Detail #### {platform} | Category | Score | |---------------------|-------| | Manifest packaging | X/3 | | Skill compatibility | X/3 | | Context delivery | X/3 | | Hook portability | X/3 | | Tool mapping | X/3 | | Install readiness | X/3 | | Runtime adapters | X/3 | | **Total** | X/21 | (repeat for each platform) ## Blockers {severity}: {description} (one entry per blocker; "None detected." if empty) ## Uplift Recommendation Target: {recommendation} Codex path: {codex_rec} ## Required Artifacts (per platform where band != strong: list missing artifacts) ### {platform} — {band} - {missing artifact path} - ... ## Session-Start Injection {status} (IF configured: component-by-component status table) ## Summary Run the uplifting-a-plugin skill to generate all missing artifacts automatically. ``` --- ## State Flow ```text Phase 1 Phase 2 Phase 3 ───────────────────────────────────────────────────────────── computed computed.manifest_results computed.scores[platform] .sources .context_results .blockers .canonical .sidecar_results .metadata .hook_results .shape .frontmatter_results .injection_results .completeness_issues .copilot_checks .gemini_checks .mcp_checks Phase 4 Phase 5 ────────────────────────────── computed Report .recommendation (displayed) .codex_rec .actions ``` --- ## Reference Documentation - **Detection Algorithm:** `lib/patterns/detection-algorithm.md` (shared) - **Rubric Framework:** `lib/patterns/rubric-framework.md` - **Platform Rules:** `lib/patterns/platforms/claude-code.md` - **Platform Rules:** `lib/patterns/platforms/cursor.md` - **Platform Rules:** `lib/patterns/platforms/gemini-cli.md` - **Platform Rules:** `lib/patterns/platforms/opencode.md` - **Platform Rules:** `lib/patterns/platforms/copilot-cli.md` - **Platform Rules:** `lib/patterns/platforms/codex.md` - **Injection Checks:** `lib/patterns/injection-checks.md` --- ## Related Skills - **Uplift plugin:** `skills/uplifting-a-plugin/SKILL.md`

uplifting-a-plugin

Transform any plugin into a fully portable plugin. Any supported plugin platform can act as the starting point.

# Uplifting a Plugin to Multi-Platform Portability Transform any plugin into a fully portable plugin. No platform is assumed. **Inputs:** - `plugin_path` (string, required) — Path to the plugin root directory. - `platforms` (string, optional) — Comma-separated list of target platforms. If omitted, presents an interactive checklist. Valid: claude-code, cursor, gemini-cli, opencode, copilot-cli, codex, all. **Output:** Summary of created, skipped, and flagged files. > **Detection Algorithm:** `lib/patterns/detection-algorithm.md` > **Manifest Schemas:** `lib/patterns/manifest-generation.md` > **Manifest Templates:** `lib/templates/manifests/` > **Context File Templates:** `lib/templates/context-files/` > **Hook Merging:** `lib/patterns/hook-merging.md` > **Bootstrapping:** `lib/patterns/bootstrapping.md` > **Platform References:** `lib/references/copilot-tools.md`, `codex-tools.md`, `gemini-tools.md` > **Hook Templates:** `lib/templates/hooks/session-start.sh`, `run-hook.cmd` > **Install Doc Templates:** `lib/templates/install-docs/` ## Overview | Phase | Description | | ----- | ----------- | | **Phase 1: Detect** | Scan metadata, elect canonical, build model, classify shape | | **Phase 2: Inventory** | Discover assets, init tracking, check conflicts | | **Phase 3: Recommend** | Recommend uplift target, confirm with user, select target platforms | | **Phase 4: Generate** | Write missing manifests, context files, sidecars per platform | | **Phase 5: Port** | Adapt hooks across platforms | | **Phase 6: Document** | Generate install docs per platform in target repo | | **Phase 7: Bootstrap** | Opt-in session-start injection | | **Phase 8: Report** | Summary of created, skipped, flagged files | **Minimum starting state:** At least one `skills/*/SKILL.md` with `name` + `description` frontmatter, or any platform manifest file. **Idempotent:** Running twice on the same repo produces no diff on the second run. ## 1. Phase 1: Detect Run the shared detection algorithm. See `lib/patterns/detection-algorithm.md` for full detail. ### 1.1 Scan and Infer ```pseudocode DETECT(plugin_path): computed.sources = scan_metadata_sources(plugin_path) IF len(computed.sources) == 0: DISPLAY "No recognisable plugin signals found in {plugin_path}." DISPLAY "Provide at least one platform manifest or one skills/*/SKILL.md" DISPLAY "with name and description frontmatter." EXIT computed.canonical = elect_canonical(computed.sources) computed.metadata = build_metadata_model(computed.sources) computed.shape = classify_shape(computed.sources) print_inference_summary(computed.metadata, computed.canonical) ``` ## 2. Phase 2: Inventory ### 2.1 Discover Assets ```pseudocode INVENTORY(plugin_path): raw_skills = Glob(plugin_path + "/skills/*/SKILL.md") computed.skills = [ { path: s, dir: dirname(s), name: basename(dirname(s)) } FOR s IN raw_skills ] computed.commands = Glob(plugin_path + "/commands/*.md") computed.agents = Glob(plugin_path + "/agents/*.md") computed.existing_hooks = read_json_if_exists(plugin_path + "/hooks/hooks.json") computed.created = [] # list of { path, platform } computed.skipped = [] # list of { path, platform } computed.flagged = [] # list of strings ``` ### 2.2 Check Conflicts ```pseudocode CHECK_CONFLICTS(computed): conflict_checks = [ { path: ".claude-plugin/plugin.json", platform: "claude-code" }, { path: ".claude-plugin/marketplace.json", platform: "claude-code" }, { path: ".cursor-plugin/plugin.json", platform: "cursor" }, { path: ".codex-plugin/plugin.json", platform: "codex" }, { path: ".agents/plugins/marketplace.json", platform: "codex" }, { path: "gemini-extension.json", platform: "gemini-cli" }, { path: "GEMINI.md", platform: "gemini-cli" }, { path: "AGENTS.md", platform: "cross" }, { path: "CLAUDE.md", platform: "claude-code" }, { path: "package.json", platform: "opencode" }, { path: ".opencode/plugins/" + computed.metadata.name + ".js", platform: "opencode" }, { path: ".github/copilot-instructions.md", platform: "copilot-cli" }, { path: "hooks/hooks-cursor.json", platform: "cursor" }, ] FOR check IN conflict_checks: IF file_exists(check.path): computed.skipped.append({ path: check.path, platform: check.platform }) ``` ## 3. Phase 3: Recommend Interactive uplift target recommendation and platform selection. Uses `computed.shape` from Phase 1 to derive a recommendation, then asks the user to confirm and select target platforms. ### 3.1 Recommend and Confirm Uplift Target ```pseudocode RECOMMEND_AND_CONFIRM(computed): # Derive recommendation from shape IF computed.shape == "bare-skill-repo": IF len(computed.skills) <= 3: recommended = "skill-first" rationale = "This repo has " + len(computed.skills) + " skill(s) and no platform manifests. Skill-first generates sidecars and context files without full plugin packaging." ELSE: recommended = "full-portable-plugin" rationale = "This repo has " + len(computed.skills) + " skills. Full plugin packaging gives each platform a native manifest for better discoverability." ELIF computed.shape == "single-platform-plugin": recommended = "full-portable-plugin" rationale = "This repo already has one platform manifest. Full plugin packaging adds the remaining platforms." ELIF computed.shape == "multi-platform-source": recommended = "full-portable-plugin" rationale = "This repo already targets multiple platforms. Full plugin packaging fills the remaining gaps." ELIF computed.shape == "curated-distribution": recommended = "curated-note-only" rationale = "This repo is a marketplace distribution without upstream skills. Only install documentation and notes will be generated." ELSE: recommended = "full-portable-plugin" rationale = "Repo shape could not be classified. Defaulting to full plugin packaging." # If platforms input was provided, auto-confirm IF inputs.platforms IS PROVIDED: computed.uplift_target = recommended RETURN # Present to user DISPLAY "## Uplift Target" DISPLAY "Shape: " + computed.shape DISPLAY "Recommendation: **" + recommended + "**" DISPLAY rationale DISPLAY "" DISPLAY "Options:" DISPLAY " 1. skill-first — sidecars, context files, AGENTS.md only (no platform manifests)" DISPLAY " 2. full-portable-plugin — all platform manifests + context files + sidecars + install docs" DISPLAY " 3. curated-note-only — install notes only" response = ASK "Accept recommendation (" + recommended + "), or choose 1/2/3?" IF response confirms recommendation: computed.uplift_target = recommended ELSE: computed.uplift_target = parse_choice(response) ``` ### 3.2 Select Target Platforms ```pseudocode SELECT_PLATFORMS(computed): all_platforms = ["claude-code", "cursor", "gemini-cli", "opencode", "copilot-cli", "codex"] # If platforms input was provided, use it directly IF inputs.platforms IS PROVIDED: IF inputs.platforms == "all": computed.target_platforms = all_platforms ELSE: computed.target_platforms = parse_csv(inputs.platforms) validate_platform_names(computed.target_platforms) RETURN # Pre-select based on uplift target and existing state IF computed.uplift_target == "skill-first": preselected = all_platforms ELIF computed.uplift_target == "curated-note-only": preselected = [p FOR p IN all_platforms IF any(s.platform == p FOR s IN computed.skipped)] IF len(preselected) == 0: preselected = all_platforms ELSE: preselected = all_platforms # Present checklist DISPLAY "## Target Platforms" DISPLAY "" FOR p IN all_platforms: marker = "[x]" IF p IN preselected ELSE "[ ]" existing = " (manifest exists)" IF any(s.platform == p FOR s IN computed.skipped) ELSE "" DISPLAY " " + marker + " " + p + existing DISPLAY "" response = ASK "Confirm platforms, or list the ones you want (e.g. 'claude-code, cursor, gemini-cli')?" IF response confirms: computed.target_platforms = preselected ELSE: computed.target_platforms = parse_platform_list(response) # Validate: at least one platform required IF len(computed.target_platforms) == 0: DISPLAY "At least one platform must be selected." GOTO SELECT_PLATFORMS ``` ### 3.3 Derive Codex Path ```pseudocode DERIVE_CODEX_PATH(computed): IF "codex" NOT IN computed.target_platforms: computed.codex_rec = None RETURN IF computed.uplift_target == "skill-first": computed.codex_rec = "native-skill-discovery" ELIF computed.uplift_target == "curated-note-only": computed.codex_rec = "curated-package-note" ELSE: computed.codex_rec = "native-plugin-packaging" ``` ### 3.4 Early Exit for Curated-Note-Only ```pseudocode IF computed.uplift_target == "curated-note-only": SKIP Phase 4 (Generate) SKIP Phase 5 (Port) RUN Phase 6 (Document) — install docs only, for selected platforms SKIP Phase 7 (Bootstrap) RUN Phase 8 (Report) RETURN ``` ## 4. Phase 4: Generate ### 4.1 Write Platform Manifests Table-driven generation. See `lib/patterns/manifest-generation.md` for all schemas. ```pseudocode GENERATE_MANIFESTS(computed): manifests = [ { target: ".claude-plugin/plugin.json", platform: "claude-code", schema: "claude-plugin" }, { target: ".claude-plugin/marketplace.json", platform: "claude-code", schema: "claude-marketplace" }, { target: "CLAUDE.md", platform: "claude-code", schema: "claude-context" }, { target: ".cursor-plugin/plugin.json", platform: "cursor", schema: "cursor-plugin" }, { target: "gemini-extension.json", platform: "gemini-cli", schema: "gemini-extension" }, { target: "GEMINI.md", platform: "gemini-cli", schema: "gemini-context" }, { target: "package.json", platform: "opencode", schema: "opencode-package" }, { target: ".opencode/plugins/{{name}}.js", platform: "opencode", schema: "opencode-shim" }, { target: ".codex-plugin/plugin.json", platform: "codex", schema: "codex-plugin", condition: "computed.codex_rec == 'native-plugin-packaging'" }, { target: ".agents/plugins/marketplace.json", platform: "codex", schema: "codex-marketplace", condition: "computed.codex_rec == 'native-plugin-packaging'" }, { target: "AGENTS.md", platform: "cross", schema: "agents-context" }, { target: ".github/copilot-instructions.md", platform: "copilot-cli", schema: "copilot-instructions" }, ] FOR manifest IN manifests: # Skip if platform not targeted (cross-platform always included) IF manifest.platform != "cross" AND manifest.platform NOT IN computed.target_platforms: CONTINUE IF manifest.condition AND NOT eval(manifest.condition): CONTINUE resolved = substitute(manifest.target, computed.metadata) IF any(s.path == resolved FOR s IN computed.skipped): CONTINUE # Skill-first: skip platform manifests, only generate context files IF computed.uplift_target == "skill-first" AND is_manifest(manifest.schema): CONTINUE template_path = schema_to_template_path(manifest.schema) mode = schema_to_mode(manifest.schema) template = Read(template_path) IF mode == "plain": content = substitute(template, computed.metadata) ELIF mode == "conditional": content = render_with_conditionals(template, computed.metadata, computed) ELIF mode == "builder": content = render_with_builder(template, computed.metadata, computed) Write(resolved, content) computed.created.append({ path: resolved, platform: manifest.platform }) ``` The `is_manifest()` predicate classifies schemas as packaging vs context: ```pseudocode MANIFEST_SCHEMAS = [ "claude-plugin", "claude-marketplace", "cursor-plugin", "gemini-extension", "opencode-package", "opencode-shim", "codex-plugin", "codex-marketplace" ] CONTEXT_SCHEMAS = [ "claude-context", "gemini-context", "agents-context", "copilot-instructions" ] FUNCTION is_manifest(schema): RETURN schema IN MANIFEST_SCHEMAS ``` Under `skill-first`, only context schemas are generated (plus sidecars). Under `full-portable-plugin`, both manifest and context schemas are generated. ### 4.2 Seed Tool-Mapping Sidecars ```pseudocode GENERATE_SIDECARS(computed): sidecar_platform_map = { "copilot-tools.md": "copilot-cli", "codex-tools.md": "codex", "gemini-tools.md": "gemini-cli", } FOR skill IN computed.skills: FOR sidecar, platform IN sidecar_platform_map: IF platform NOT IN computed.target_platforms: CONTINUE target = skill.dir + "/references/" + sidecar IF NOT file_exists(target): source = Read("lib/references/" + sidecar) Write(target, source) computed.created.append({ path: target, platform: platform }) ``` ### 4.3 Validate npx Skills Frontmatter ```pseudocode VALIDATE_FRONTMATTER(computed): FOR skill IN computed.skills: frontmatter = parse_yaml_frontmatter(Read(skill.path)) IF NOT frontmatter.name OR NOT frontmatter.description: computed.flagged.append( skill.path + " — missing frontmatter field(s). Add name: and description: in YAML frontmatter." ) ``` Do NOT auto-write — frontmatter descriptions require human authorship. ## 5. Phase 5: Port Adapt hooks from any source platform to all target platforms. See `lib/patterns/hook-merging.md` for event mapping and merge logic. Hook porting is filtered by `computed.target_platforms`. Each subsection only runs if its target platform is selected. ### 5.1 Claude Code → Cursor Hooks ```pseudocode PORT_CURSOR_HOOKS(computed): IF "cursor" NOT IN computed.target_platforms: RETURN IF any(s.path == "hooks/hooks-cursor.json" FOR s IN computed.skipped): RETURN IF computed.existing_hooks: generate_cursor_hooks(computed.existing_hooks) ELSE: Write("hooks/hooks-cursor.json", '{ "version": 1, "hooks": {} }') computed.created.append({ path: "hooks/hooks-cursor.json", platform: "cursor" }) ``` ### 5.2 Claude Code → Copilot Hooks Copilot uses separate `bash` / `powershell` fields, no `matcher`, stored under `.github/hooks/`. ```pseudocode PORT_COPILOT_HOOKS(computed): IF "copilot-cli" NOT IN computed.target_platforms: RETURN IF NOT computed.existing_hooks: RETURN copilot_hooks = adapt_hooks_copilot(computed.existing_hooks) # see hook-merging.md FOR hook IN copilot_hooks: target = ".github/hooks/" + hook.event + ".sh" IF NOT file_exists(target): Write(target, hook.bash) computed.created.append({ path: target, platform: "copilot-cli" }) win_target = ".github/hooks/" + hook.event + ".ps1" IF NOT file_exists(win_target): Write(win_target, hook.powershell) computed.created.append({ path: win_target, platform: "copilot-cli" }) ``` ### 5.3 Gemini Hook Guidance Gemini CLI hooks are configured interactively by users via `GEMINI.md` instructions — they cannot be written as files. Capture guidance text to include in install docs. ```pseudocode GEMINI_HOOK_GUIDANCE(computed): IF "gemini-cli" NOT IN computed.target_platforms: RETURN IF computed.existing_hooks: computed.gemini_hook_text = render_gemini_hook_instructions(computed.existing_hooks) ELSE: computed.gemini_hook_text = NULL ``` ### 5.4 Windows Support ```pseudocode PORT_WINDOWS_HOOKS(computed): IF file_exists("lib/templates/hooks/run-hook.cmd"): source = Read("lib/templates/hooks/run-hook.cmd") Write("hooks/run-hook.cmd", source) computed.created.append({ path: "hooks/run-hook.cmd", platform: "cross" }) ``` ## 6. Phase 6: Document Generate install documentation for every platform that received artifacts. See `lib/templates/install-docs/` for section templates. ### 6.1 Determine Platforms With Artifacts ```pseudocode DETERMINE_PLATFORMS(computed): platforms_with_artifacts = computed.target_platforms ``` ### 6.2 Render Per-Platform Install Sections ```pseudocode RENDER_INSTALL_SECTIONS(computed, platforms_with_artifacts): sections = {} FOR platform IN platforms_with_artifacts: template = Read("lib/templates/install-docs/" + platform + ".md") sections[platform] = render(template, computed.metadata) IF computed.gemini_hook_text: sections["gemini-cli"] += "\n\n### Hook Setup\n\n" + computed.gemini_hook_text RETURN sections ``` ### 6.3 Write Install Docs ```pseudocode WRITE_INSTALL_DOCS(computed, sections, platforms_with_artifacts): # Whole-repo note: only include when plugin has shared assets that require # whole-repo install (hooks, session-start bootstrapping, root context files, # or platform manifests). Bare skill repos without these can use npx skills. has_shared_assets = ( computed.existing_hooks OR file_exists("skills/using-" + computed.metadata.name + "/SKILL.md") OR any(file_exists(p) FOR p IN ["CLAUDE.md", "AGENTS.md", "GEMINI.md"]) OR computed.uplift_target == "full-portable-plugin" ) IF has_shared_assets: whole_repo_note = render(Read("lib/templates/install-docs/whole-repo-note.md"), computed.metadata) ELSE: whole_repo_note = "" fresh_install = "" adding_platform = "" FOR platform IN platforms_with_artifacts: fresh_install += sections[platform] + "\n\n" adding_tmpl = read_if_exists("lib/templates/install-docs/adding-platform/" + platform + ".md") IF adding_tmpl: adding_platform += render(adding_tmpl, computed.metadata) + "\n\n" content = "# Installation\n\n" IF whole_repo_note: content += whole_repo_note + "\n\n" content += "## Fresh Install\n\n" + fresh_install content += "## Adding Another Platform\n\n" content += "Already have the repo cloned for one platform? Add others by pointing them at the same checkout.\n\n" content += adding_platform Write("INSTALL.md", content) computed.created.append({ path: "INSTALL.md", platform: "cross" }) # Platform-specific pointers (not full docs) IF "copilot-cli" IN platforms_with_artifacts: Write(".github/INSTALL.md", "See [INSTALL.md](../INSTALL.md) for installation instructions.\n") computed.created.append({ path: ".github/INSTALL.md", platform: "copilot-cli" }) IF "codex" IN platforms_with_artifacts: Write(".codex/INSTALL.md", "See [INSTALL.md](../INSTALL.md) for installation instructions.\n") computed.created.append({ path: ".codex/INSTALL.md", platform: "codex" }) # Flag missing Installation and Publishing sections in README IF file_exists("README.md"): readme = Read("README.md") IF "## Installation" NOT IN readme AND "## Install" NOT IN readme: computed.flagged.append( "README.md — no Installation section found. Add install instructions or link to INSTALL.md." ) IF "PUBLISHING.md" NOT IN readme: computed.flagged.append( "README.md — no link to PUBLISHING.md. Add a link so plugin authors can find publishing guidance." ) ``` ### 6.4 Write Publishing Docs ```pseudocode WRITE_PUBLISHING_DOCS(computed, platforms_with_artifacts): header = render(Read("lib/templates/install-docs/publishing.md"), computed.metadata) sections = "" FOR platform IN platforms_with_artifacts: template = read_if_exists("lib/templates/install-docs/publishing/" + platform + ".md") IF template: sections += render(template, computed.metadata) + "\n\n" IF sections: content = header + "\n\n" + sections Write("PUBLISHING.md", content) computed.created.append({ path: "PUBLISHING.md", platform: "cross" }) ``` ## 7. Phase 7: Bootstrap (opt-in) Session-start injection. See `lib/patterns/bootstrapping.md` for full generation logic. ### 7.1 Prompt and Execute ```pseudocode BOOTSTRAP(computed): IF file_exists("skills/using-" + computed.metadata.name + "/SKILL.md"): computed.bootstrap_status = "already-configured" RETURN response = ASK "Generate session-start bootstrapping hooks? (y/n)" IF response == "no": computed.bootstrap_status = "declined" RETURN generate_using_skill(computed) generate_using_sidecars(computed) # filtered by target_platforms (same as Phase 4.2) generate_session_start(computed) generate_run_hook_cmd(computed) # Hook merging gated on platform targeting IF "claude-code" IN computed.target_platforms: merge_session_start_hooks_claude(computed) IF "cursor" IN computed.target_platforms: merge_session_start_hooks_cursor(computed) # Platform-specific enhancements IF "opencode" IN computed.target_platforms: enhance_opencode_plugin(computed) IF "gemini-cli" IN computed.target_platforms: update_gemini_md(computed) computed.bootstrap_status = "configured" ``` ## 8. Phase 8: Report Emit the final uplift report. See `lib/patterns/report-template.md` for the full report format and state flow diagram. ## Related Skills - **Assess portability:** `skills/assessing-plugin-portability/SKILL.md`

using-skill-portability

Use when starting a session with the skill-portability plugin. Session-start bootstrapping that lists available skills and platform-specific invocation instructions.

# Using Skill Portability This plugin provides the following skills: | Skill | Description | | ----- | ----------- | | `uplifting-a-plugin` | Add multi-platform portability to any plugin. Accepts any starting state — Claude, Cursor, Gemini, OpenCode, Copilot, Codex, or bare SKILL.md files. Detects what exists, infers canonical metadata, generates every missing platform artifact, ports hooks, produces install documentation, and optionally configures session-start bootstrapping. | | `assessing-plugin-portability` | Assess a plugin for multi-platform portability. Classifies repo shape, scores readiness per platform using a 7-category rubric, detects structural blockers, and recommends an uplift target. Read-only — makes no changes. | ## How to Invoke Skills **Claude Code / Cursor:** Use the `Skill` tool with the skill name. **Copilot CLI:** Use the `skill` tool with the skill name. **Gemini CLI:** Use the `activate_skill` tool with the skill name. **Codex / Other:** Skills are auto-discovered. Follow the SKILL.md instructions directly. ## Tool Name Mapping Skills use Claude Code tool names. See `lib/references/` for platform-specific equivalents.