CursorPool
← 返回首页

skill-portability

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.

cursor.directory·2
Skill

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

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

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.
规则

hooks

Event hooks configuration

{
  "hooks": {
    "SessionStart": [
      {
        "command": "hooks/run-hook.cmd session-start",
        "matcher": "startup|clear|compact"
      }
    ]
  }
}

来源:https://github.com/hiivmind/skill-portability