
Description
End-to-end Cordova-to-Capacitor migration orchestrator. Analyzes the Cordova plugin (plugin.xml, native iOS/Android source, JS bridge, hooks, third-party dependencies), produces a structured migration plan as YAML conforming to capacitor-plugin-generator's input contract, invokes the generator skill in structured mode at a user checkpoint, then consolidates intermediate notes into a single MIGRATION.md. Use when the user says "migrate this cordova plugin", "convert cordova to capacitor", "assess migration feasibility", "what blocks this migration", "port this cordova plugin to capacitor", or "estimate the effort to migrate". Do not use for generating new Capacitor plugins from scratch (use capacitor-plugin-generator instead), debugging runtime issues in an already-migrated plugin, or migrating an entire Cordova application; scope is plugin-level only.
SKILL.md
Cordova Plugin Migrator
End-to-end orchestrator that takes a Cordova plugin source tree and
produces a candidate Capacitor plugin plus a consolidated MIGRATION.md,
via a single skill invocation. Internally it analyzes the Cordova plugin,
emits a structured YAML plan conforming to
capacitor-plugin-generator/references/input-contract.md, hits a user
checkpoint, then invokes the capacitor-plugin-generator skill in
structured mode with that plan. This skill never re-implements what the
generator already does; the generator owns scaffolding and native code
emission.
When to Use This Skill
✅ Use this skill when:
- Migrating an existing Cordova plugin to Capacitor.
- Assessing migration complexity, effort, and blockers before committing.
- Producing the structured handoff YAML for
capacitor-plugin-generator. - Comparing the official Cordova API to an existing or planned Capacitor API.
- Auditing a Cordova plugin's hooks, native dependencies, or
<config-file>modifications for portability.
❌ Do NOT use this skill for:
- Generating a Capacitor plugin scaffold or implementation, pass the plan to
capacitor-plugin-generator. - Designing a brand-new plugin without prior Cordova source.
- Migrating entire Cordova apps. Scope is plugin-level only.
- Debugging runtime issues in an already-migrated plugin.
- Publishing or releasing the resulting Capacitor plugin.
Prerequisites
| Requirement | Use |
|---|---|
| Cordova plugin source (local clone or accessible repo) | Read plugin.xml, native source, and JS bridge. |
| Node.js LTS and npm | Inspect dependencies, run scripts referenced by hooks. |
capacitor-plugin-generator skill | Receives the YAML plan this skill produces. |
capacitor-plugin-generator/references/input-contract.md | Authoritative shape for the handoff YAML. |
| Read access to the official Capacitor equivalent (if any) | Reuse wire-format names and types when porting a plugin that already exists in Capacitor. |
| Xcode and Android Studio (optional) | Confirm SDK availability for detected native dependencies. |
Agent Behavior
- Run as an orchestrator. Phases 1–10 analyze the Cordova source and
produce a YAML plan; Phase 11 invokes
capacitor-plugin-generatorvia the Skill tool, passing the plan in structured mode; Phase 12 consolidates documentation. Do not emit native Capacitor code from this skill. The generator owns that. - Read
capacitor-plugin-generator/references/input-contract.mdbefore producing YAML. The generator's contract is authoritative; do not invent fields or rename existing ones. - Resolve the plugin's identity from
plugin.xmlfirst: id, name, version, declared platforms, declared frameworks, hooks, and config-file targets. - Inspect every native dependency declaration before asserting platform
support. Mirror what
plugin.xmlactually says, not what the README claims. - Classify each
<hook>into Tier 1 (Capacitor hooks), Tier 2 (npm scripts/manual steps), or Tier 3 (blocker) using the rules inreferences/hooks-migration.md. A single Tier 3 hook is enough to block generator handoff. - When an official Capacitor equivalent exists (
@capacitor/<name>or a Capawesome/community port), read itsdefinitions.tsand native source. Reuse its wire-format strings, enum values, method names, and event names verbatim. Do not invent parallel APIs that "look similar." - Detect hybrid plugins. If
package.jsonalready declaresscripts.capacitor:*hooks, the plugin is partially Capacitor-aware. Record the existing hooks as Tier 1 "already converted" and ask the user whether to reuse, rewrite, or merge them. Do not silently drop them. - A JS method whose Cordova-side argument is a stringified JSON blob
(
JSON.parse/Gson.fromJson/JSONObject(args.getString(0))in the native handler) must be mapped to a strongly-typed TypeScript interface inapi.types, not astringparameter. Capture the interface shape from the native parsing site. - When the Cordova native source uses runtime permission APIs
(
cordova.requestPermission,requestPermissions(...),AVCaptureDevice.requestAccess,PHPhotoLibrary.requestAuthorization,CLLocationManagerdelegates,CNContactStore.requestAccess, etc.), addcheckPermissions()andrequestPermissions()methods to the YAML even if the Cordova JS surface did not expose them. Capacitor's convention is explicit permission methods, and the official equivalent (if any) almost always exposes them. - When the Cordova native source extracts metadata in helper classes
(
ExifHelper,MimeTypeHelper,ImageMetadata, etc.) and includes it in the response payload, capture those fields in the correspondingapi.typesinterface, even if the Cordova JS docs did not document them. Read the native code, not the README. - When mirroring an official Capacitor equivalent, sanity-check the
official's native LOC against the Cordova source's native LOC. If the
official is more than 3× larger, add a
migration.notesentry warning the reviewer that a literal port will leave behavior the official handles (e.g., iOSCHHapticEnginefor duration-accurate vibration, AndroidVibrationEffectpatterns for impact / notification) on the floor. The generator's SDK-adapter rule may or may not catch this. - Distinguish three downstream destinations when emitting setup notes:
- Plugin's own packaging. Gradle
implementation '...'lines, CocoaPodss.dependency '...'lines, custom Maven repo URLs,s.weak_framework,s.vendored_frameworks, AndroidManifest<uses-permission>/<meta-data>/<queries>entries, and@CapacitorPlugin(permissions = [...])declarations all belong in the plugin's ownandroid/build.gradle,.podspec,android/src/main/AndroidManifest.xml, or annotations. Gradle's manifest merger and CocoaPods transitively propagate these to consumers, so no host-app step and noMIGRATION.mdentry is required. - Host-app build configuration. Items that cannot live in the
plugin package and must be applied to the consuming app: Info.plist
privacy strings (
NSCameraUsageDescription,NSLocationWhenInUseUsageDescription, etc.), iOS capabilities and entitlements (Apple Pay merchant ID,aps-environment,com.apple.security.application-groups), and any other manifest mutation that must live on the host app rather than the plugin. Record undermigration.notesand document inMIGRATION.mdwith copy-paste-ready snippets per platform. - Consumer runtime config / code. Apple Developer-side credential provisioning, host-app-supplied JSON config values (merchant numbers, API keys), and consumer JS call-site migration (callback → Promise, positional → named). No tool can automate these because the inputs are the consumer's own data.
Record eachplugin.xmldirective under one of those three buckets inmigration.notes. Never write aMIGRATION.mdstep that asks consumers to addimplementation '...'for a dep the plugin should bundle. Seereferences/dependency-migration.md"Ownership Model" for the dep-specific table. - Plugin's own packaging. Gradle
- When the Cordova plugin used
<preference name="VAR" default="...">install-time placeholders inside<config-file target="*-Info.plist">entries (Apple Pay merchant config, OAuth client IDs, analytics keys, etc.), detect it and recommend the runtime config JSON file pattern inmigration.warnings: aPluginConfig.jsonconsumed by a hybridcapacitor:sync:afterscript that copies the file into native projects at build time. The Cordova plugin may already ship this script (look for it inhooks/capacitor*.js); if so, reuse it. Otherwise propose a one-screen template. This pattern keeps consumer values out of static plist / manifest entries entirely. - Detect SPM markers in
plugin.xml:<platform name="ios" package="swift">and<pod ... nospm="true">indicate the Cordova plugin already ships a siblingPackage.swift. When present: (a) read thatPackage.swiftand lift its dependencies into the YAML'sdependencies.ios.spm, (b) move any pod withoutnospm="true"todependencies.ios.cocoapods, (c) add amigration.notesentry that the generated Capacitor plugin must ship bothPackage.swiftand.podspec(Capacitor 8 supports both, and SPM is the default for new plugins). Seereferences/dependency-migration.md"Swift Package Manager (SPM)" for the schema. - Default output mode is Mode B (side-by-side directory). Switch to Mode A
(in-place with
.cordova-archive/) only when the user explicitly asks and the source is in a writable working copy. - Stop and request user input when blockers, Tier 3 hooks, unresolvable proprietary SDKs, or missing source files would force you to guess. Do not emit a YAML plan that hides the gap.
- Distinguish between resolvable warnings (documented manual steps) and blockers (cannot be auto-migrated). Both go in the plan; only blockers stop handoff.
- A vendored binary checked into the Cordova plugin's tree (e.g.,
src/ios/frameworks/Foo.xcframework) is not a blocker on its own. Carry the binary forward and record its path undermigration.source_files.ios(or.android). Treat as blocker only when the binary requires runtime credentials the user does not have or fails AndroidX / 64-bit / simulator requirements. - Report what was actually inspected. Cite file paths and line numbers when flagging blockers or unsupported patterns.
- Compare iOS and Android findings against each other before declaring
Phase 9. If one platform exposes a method the other does not, surface the
mismatch in
migration.warningsso the generator can pick a strategy (mirror both, expose conditionally, or drop). Do not silently emit a contract that only one platform can fulfil. - Default response shape is architecture and plan, not code. Do not
include full before/after code blocks unless the user explicitly asks for
"show me the code" or "deep dive". Per-method one-line mappings in
migration.cordova_to_capacitor_mapare not "code" in this sense, they are part of the plan. - Quote every value in
migration.cordova_to_capacitor_mapand any other YAML string that contains JS syntax ((,),{,},:,[,]). Unquoted JS expressions likeFoo.bar({ x: 1 })are unparseable YAML and the generator will reject the plan at load time. - Invoke the generator only after the Phase 10 user checkpoint
approves. If the user rejects, halts, or has unanswered blockers,
stop. Never invoke the generator with a YAML that has non-empty
migration.blockersor non-emptymigration.hooks.tier_3. - When
odc_target: true, Phase 11 splits into two sequential sub-phases (11a and 11b). Phase 11a invokesbuild-actions-generator; Phase 11b invokescapacitor-plugin-generator. Seereferences/using-plugin-generator.md"ODC Path" for the full sequence. Build actions are written to the Capacitor plugin folder. Hooks and migration notes that are fully covered by build actions must be excluded from the YAML passed to the generator (Phase 11b), to avoid the generator emitting redundant Capacitor hook scripts for config-level work already handled by build actions.
Procedures
Phase 1: Determine the Task and Output Mode
Confirm the user has a Cordova plugin to migrate. If the user wants a
brand-new Capacitor plugin from scratch with no Cordova source to
convert, stop here and redirect to capacitor-plugin-generator
directly, this skill has nothing to add.
Otherwise, pick the output mode:
- Mode B (default, side-by-side), generator scaffolds a new Capacitor plugin into a sibling directory of the Cordova repo. The Cordova repo is never touched.
- Mode A (opt-in, in-place), generator scaffolds into a temp
directory. After Phase 11 succeeds, Phase 12 relocates the generator's
output into the Cordova repo root and moves the original Cordova
source under
.cordova-archive/. Preserves the original repo's npm package name and git history. Requires explicit user opt-in and a clean writable git working copy.
See references/output-modes.md for layouts, pre-flight checks, and
the exact relocation sequence.
ODC confirmation: Check whether the user mentioned ODC or OutSystems
Developer Cloud anywhere in the request. If yes, set odc_target: true and
proceed. If not, ask: "Will this Capacitor plugin be consumed by an ODC
(OutSystems Developer Cloud) app? If yes, build actions will be generated
alongside the Capacitor plugin to handle native configuration in MABS."
Record the answer as odc_target: true/false for use in Phase 11.
Phase 2: Read plugin.xml
A typical Cordova plugin layout:
my-plugin/
├── plugin.xml # Plugin manifest and configuration
├── package.json # NPM metadata (and any capacitor:* scripts)
├── www/ # JavaScript bridge
├── src/
│ ├── ios/ # CDVPlugin subclasses (.h/.m/.swift)
│ └── android/ # CordovaPlugin subclasses (.java/.kt)
└── hooks/ # Optional lifecycle scripts
Open plugin.xml. Extract: plugin id, name, version, declared platforms
(<platform name="...">), <source-file>/<header-file> mappings,
<framework> entries, <podspec> blocks, <config-file> targets,
<edit-config> targets, <hook> declarations, <dependency> entries,
<preference> tags, and <js-module> exposure (clobbers / merges /
runs). Record file paths and line numbers for every blocker candidate.
Phase 3: Analyze Native Dependencies
Apply references/dependency-migration.md. For each <framework> and
<podspec> pod, decide: direct migration, version update, alternative
library, or blocker. For each Android Gradle coord, check AndroidX vs Support
Library and Capacitor's minimum compile SDK. Record manual installation steps
required after migration.
Phase 4: Analyze Hooks
Apply the three-tier rule in references/hooks-migration.md:
- Tier 1, convertible to a Capacitor plugin npm hook
(
capacitor:{sync,copy,update}:{before,after},capacitor:{android,ios}:add:{before,after}). - Tier 2, convertible to an npm lifecycle script (
postinstall,preuninstall) or a documented manual step. - Tier 3, interactive prompts, plugin.xml/config.xml mutation, Cordova CLI internals, or anything else with no Capacitor equivalent.
Read each referenced script. Do not classify on filename alone.
Phase 5: Analyze the JavaScript Bridge
Apply references/api-mappings.md. From www/*.js, list every public method,
its positional argument shape, whether it uses success/error callbacks or
returns a promise, and whether exec() is paired with a cordova.exec()
action name. Map each method to a Capacitor methodName(options): Promise<R>
signature.
Phase 6: Analyze iOS Implementation
Apply references/api-mappings.md. From src/ios/*.{h,m,swift}, identify the
CDVPlugin subclass, each method matching a JS action, argument extraction
from command.arguments, response construction (CDVPluginResult), permission
flows, and any system frameworks or SDK adapters used. Note where Objective-C
must be modernized to Swift.
Phase 7: Analyze Android Implementation
Apply references/api-mappings.md. From src/android/**/*.{java,kt},
identify the CordovaPlugin subclass, the execute() router, action
strings, argument extraction (args.getString(i)), response paths
(CallbackContext.success/error), permission flows, and any Activity Result
patterns. Note where Java must be modernized to Kotlin.
Phase 8: Assess Complexity
Apply references/complexity-assessment.md. Score the plugin on method count,
LOC, dependency footprint, hook tier mix, language modernization, blocker
count, and Capacitor-equivalent reuse. Output one of: simple, moderate,
complex, blocked. blocked means the user must resolve issues before
generator handoff.
Phase 9: Produce the Migration YAML
Apply references/using-plugin-generator.md. Build the YAML against the
generator's references/input-contract.md, base block (plugin,
platforms, api, permissions, dependencies) plus the optional
migration: block (source, complexity, output_mode, blockers,
warnings, language_modernization, source_files, hooks,
cordova_to_capacitor_map). Pin wire-format strings to the official
Capacitor equivalent when one exists.
Phase 10: User Checkpoint
Present a short human summary alongside the YAML: complexity, blockers, warnings, manual setup, recommended output mode, and any Capacitor equivalent being mirrored. Stop and wait for confirmation if blockers or Tier 3 hooks are non-empty.
Phase 11: Invoke Downstream Skills
The downstream invocation path depends on whether ODC was confirmed in Phase 1.
Non-ODC path: Apply references/using-plugin-generator.md. Invoke
capacitor-plugin-generator via the Skill tool in structured mode with the
Phase 9 YAML. The generator runs its own playbook; this skill does not
re-inspect Cordova source. For Complex plugins use incremental mode
(one platform at a time with user checkpoints). If the generator rejects the
YAML, return to Phase 9 and fix it — never hand-edit generator output.
ODC path: Follow the two-sub-phase sequence in
references/using-plugin-generator.md "ODC Path":
- Phase 11a — Invoke
build-actions-generatorwith the Phase 9 YAML and the Cordova plugin path; wait forbuild-actions/output. Note which hooks/elements it covered. - Phase 11b — Remove build-action-covered items from
migration.hooks.tier_1(or markstatus: handled_by_build_actions); add amigration.notesentry documenting this. Then invokecapacitor-plugin-generatorwith the annotated YAML (standard or incremental per complexity).
Phase 12: Post-Migration Cleanup
Apply references/post-migration-cleanup.md. After the generator produces a
working scaffold, consolidate intermediate notes into a single MIGRATION.md
at the plugin root, archive or remove the original Cordova source per the
chosen output mode, and update the README with the consumer-facing breaking
changes (callbacks → promises, positional → named arguments, manual native
setup).
Cross-check that every migration.notes and migration.warnings
entry is bucketed (plugin packaging / host-app build configuration /
consumer runtime config) per the Agent Behavior rule. The
MIGRATION.md should list only items that fall in buckets 2 or 3.
Plugin-packaging items are transparent to consumers and do not belong
in the migration trail.
Best Practices
DO
- ✅ Read
plugin.xmlfirst and ground every claim in actual XML, source paths, or hook script content. Cite line numbers for blockers. - ✅ Treat the generator's input contract as authoritative. Build the YAML
against
capacitor-plugin-generator/references/input-contract.md. - ✅ Reuse wire-format strings from an official Capacitor equivalent when one exists. Mirror enum casing, event names, and method names exactly.
- ✅ Classify hooks by reading the referenced script, not by filename.
- ✅ Default to Mode B (side-by-side). Move to Mode A only on explicit user opt-in with a writable working copy.
- ✅ Stop at the Phase 10 checkpoint if blockers or Tier 3 hooks exist.
- ✅ Capture both blockers and warnings; only blockers stop handoff.
- ✅ Note manual native setup steps consumers will need after migration (Info.plist keys, AndroidManifest entries, Podfile / Gradle additions).
DON'T
- ❌ Emit native Capacitor code (Swift, Kotlin, Java, TypeScript) from this skill. The generator owns code emission.
- ❌ Invent YAML fields or rename ones the generator already defines.
- ❌ Re-derive wire-format strings from human-friendly names when an
official Capacitor equivalent exists. Read its
definitions.ts. - ❌ Classify a hook as Tier 1/2 without opening the referenced script.
- ❌ Emit a YAML plan that lists
complexity: simplewhile hiding a proprietary AAR or interactive setup hook. - ❌ Add Capacitor-version-specific guidance here, the generator skill owns
generator-side rules (name parity, Java filename,
notifyListenersvisibility, etc.). Reference, don't duplicate. - ❌ Invoke the generator silently. Phase 10 is a mandatory user checkpoint; do not skip it.
- ❌ Invoke the generator with blockers or Tier 3 hooks unresolved.
- ❌ Commit, push, or publish generator output from this skill.
Error Handling
| Symptom | Fix |
|---|---|
plugin.xml missing or malformed | Stop. Ask for the canonical plugin source. Do not infer a contract from README marketing copy. |
Source files referenced by plugin.xml are not present on disk | Stop. Report missing files by path. The plan is incomplete without them; do not proceed to Phase 9. |
| Hooks reference scripts that cannot be opened or are obfuscated | Classify as Tier 3 and add to migration.blockers with the script path. Do not guess based on the hook type. |
Plugin declares <framework> for a private/proprietary AAR or .framework requiring runtime credentials the user does not have | Record as a blocker. Note vendor contact requirement. Do not silently swap for a public alternative. |
Plugin vendors a binary in-tree (e.g., src/ios/frameworks/Foo.xcframework, src/android/libs/Foo.aar) but it is publicly distributed | Not a blocker. Carry the binary forward and record its path under migration.source_files.{ios,android}. The generator copies it into the Capacitor plugin's ios/ or android/libs/ directory. |
| Plugin pulls Android deps from a custom Maven repository (Azure DevOps, JFrog, etc.) | Record the URL under migration.dependencies.android.maven_repos. Warning, not blocker, when the URL is reachable without auth. Blocker when auth is required and credentials are not provided. |
Plugin uses Support Library (android.support.*) coordinates | Record as a blocker unless Jetifier acceptance is confirmed by the user. Capacitor expects AndroidX. |
Cordova plugin has an official Capacitor equivalent (@capacitor/<name>) | Read the equivalent's definitions.ts and native source. Pin method names, enum values, event names, and error codes in the YAML. Add the equivalent's package as migration.notes for the reviewer. |
Plugin declares both <hook> entries and package.json scripts.capacitor:* entries (hybrid plugin) | The Capacitor scripts already exist. Record them as Tier 1 "already converted" hooks. Ask the user whether to reuse, rewrite, or merge them with any newly migrated Cordova hooks. |
Plugin uses <config-file> to mutate AndroidManifest.xml with <uses-permission>, <meta-data>, <queries>, or <provider> | Goes in the plugin's own android/src/main/AndroidManifest.xml, Gradle manifest merger merges into the host app automatically. No host-app step. Record under migration.notes for the generator to emit. |
Plugin uses <config-file> to mutate Info.plist with an Apple-required privacy string (NSCameraUsageDescription, NSLocationWhenInUseUsageDescription, NSMicrophoneUsageDescription, etc.) | Host-app Info.plist, Apple App Store requires these on the host app's plist, not the plugin's. Record under migration.notes and document a copy-paste snippet in MIGRATION.md. |
Plugin uses <config-file> to mutate Info.plist with consumer-specific values (Apple Pay merchant ID, OAuth client ID, analytics key) via $VAR install-time placeholders | Runtime config JSON file pattern is strongly preferred: a <Plugin>Configuration.json consumed by a hybrid capacitor:sync:after script. Reuse an existing script from the Cordova plugin if present, else propose a one-screen template. Keeps consumer-specific values out of the plist entirely. |
Plugin uses <config-file> to mutate entitlements plists (*-Debug.plist, *-Release.plist, *-Entitlements.plist) for capabilities like com.apple.developer.in-app-payments, aps-environment, com.apple.security.application-groups | Host-app capability + entitlement. Record under migration.notes; document the exact Xcode capability the consumer must enable (Apple Pay, Push Notifications, App Groups, etc.) and the entitlement value in MIGRATION.md. |
Plugin declares <framework> with weak="true" for iOS | Plugin's own podspec. Record in dependencies.ios.system_frameworks and add a migration.notes line so the generator emits s.weak_framework instead of s.framework. No host-app step. |
Plugin uses <edit-config> with mode="merge" to add an android:requestLegacyExternalStorage or similar attribute to the host <application> element | Host-app AndroidManifest if the attribute applies to the host app; otherwise plugin's own AndroidManifest. Record in migration.notes with the exact attribute and parent so MIGRATION.md can include a copy-paste snippet. |
Plugin uses <js-module runs="true"> | Add to migration.warnings. Recommend an explicit initialize() method or constructor-side init in the web layer. |
Plugin has multiple <js-module> entries with multiple <clobbers> targets (e.g., constants module + main module) | Collapse into a single registerPlugin() registration. Export constants from definitions.ts next to the plugin interface. Capacitor has no analog to multi-clobber. |
Plugin includes Android resource files via <source-file target-dir="res/..."> | Record under migration.source_files.android with the destination res/ subpath. The generator copies them into android/src/main/res/<subpath>/. Common for FileProvider paths and themes. |
Plugin uses <preference name="..." default="..."> with install-time variable substitution (${VAR} in plugin.xml) referenced inside <config-file target="*-Info.plist"> or AndroidManifest.xml | Prefer the runtime config JSON file pattern (consumer drops their values into a <Plugin>Configuration.json consumed by a hybrid capacitor:sync:after script). Reuse an existing script from the Cordova plugin if present. Fallback: capacitor.config.json runtime config under plugins.<PluginJSName>. |
Plugin uses <preference name="ANDROIDX_CORE_VERSION" default="1.18.0"> (or similar build-time-only version pins) referenced inside the plugin's own build.gradle | Plugin's own build.gradle, pin the version literally in the generated Gradle file. No host-app step, no runtime config. Consumer never sees this. |
Plugin's native handler parses a stringified JSON blob (Gson.fromJson, JSONObject(args.getString(0))) | Map the parsed shape to a strongly-typed api.types interface, never string. Capture the schema from the native parsing site (Kotlin data class or Swift struct). |
One JS method dispatches to multiple cordova.exec() action names based on typeof param (e.g., vibrate(num) vs vibrate([...])) | Split into multiple typed Capacitor methods, vibrate({ duration }), vibrateWithPattern({ pattern, repeat }), cancelVibration(). Record the split in migration.cordova_to_capacitor_map and add to migration.warnings as a consumer-facing breaking change. |
| Generator rejects YAML with "bad indentation" or "mapping entry" parse error | A cordova_to_capacitor_map entry contains unquoted JS syntax ((, {, :, etc.). Quote every cordova: and capacitor: value as a YAML string. The plan must be re-emitted; the generator cannot load it as-is. |
| Cordova native source requests runtime permissions but the Cordova JS surface has no permission methods | Add checkPermissions() (Promise<PermissionStatus>) and requestPermissions(options?) (Promise<PermissionStatus>) to api.methods, and a PermissionStatus interface to api.types. Without these the migrated plugin will violate Capacitor convention and consumers will expect them. |
Cordova native source has a helper class for metadata extraction (ExifHelper, MimeTypeHelper, etc.) but the Cordova JS docs don't list the resulting fields | Read the native response-construction site. Add the metadata fields (typically exif?: any, mimeType?, size?) to the result type. The Cordova JS docs frequently understate what the native side actually returns. |
| Official Capacitor equivalent's native source is more than 3× the LOC of the Cordova source | Add a migration.notes entry warning the reviewer that a literal port will leave platform-specific behavior unimplemented (e.g., iOS CHHapticEngine over AudioServicesPlaySystemSound, Android pattern-based haptics over single-shot vibration). Recommend reviewing whether to retarget consumers to the official package. |
Cordova plugin.xml has <platform name="ios" package="swift"> or any <pod ... nospm="true"> | Plugin already ships SPM support alongside CocoaPods. Lift its Package.swift dependencies into dependencies.ios.spm; lift only pods without nospm="true" into dependencies.ios.cocoapods. Capacitor port must ship both Package.swift and .podspec. |
Plugin declares <dependency> on another Cordova plugin | Resolve the dependency target separately. If it lacks a Capacitor equivalent or migration plan, treat as blocker for the current plugin. |
| Plugin's iOS source is Objective-C only | Record migration.language_modernization.ios: { from: objective_c, to: swift }. Note bridging headers consumers may still need. |
| Plugin's Android source is Java only | Record migration.language_modernization.android: { from: java, to: kotlin }. The generator will still produce Java if the user asks, but Kotlin is the default recommendation. |
| YAML rejected by the generator | Re-read capacitor-plugin-generator/references/input-contract.md. Fix the YAML in this skill, not in the generator. Do not work around the contract. |
| Generator flags missing wire-format strings | Re-read the official Capacitor equivalent's definitions.ts. Update api.types values verbatim. Do not "translate" from the human-friendly names. |
| User requests Mode A but the working copy is not under version control or is read-only | Refuse Mode A. Recommend Mode B. Recovery from a botched in-place move without VCS is manual. |
| Plugin advertises features the source does not implement | Trust the source. Record the advertised-but-unimplemented features under migration.warnings. Do not fabricate API methods to match documentation. |
| Hook classified as Tier 1 by filename but actually interactive | Read the script source, not just the <hook> name attribute. Anything that prompts via stdin, opens a TTY, or shells to read / prompt is Tier 3. |
| Dep marked "direct migration" but the pod / Gradle artifact is abandoned | Cross-check the latest release date and Swift / AndroidX compatibility before marking as direct. Anything not updated in 3+ years moves to "replace" or "blocker". |
| Complexity assessed "Simple" but plugin has 15+ public API methods | Always count public API methods. Any plugin with more than 10 public methods is at least Moderate, regardless of other signals. |
| Blocker missed during analysis | Always scan plugin.xml for <config-file>, <edit-config>, <js-module runs="true">, <hook>, and <dependency>, these are the non-negotiable blocker candidates. Re-scan before Phase 9 if anything in the YAML looks too tidy. |
| Generator re-reads Cordova source during Phase 11 | The YAML plan is incomplete. Re-validate against capacitor-plugin-generator/references/input-contract.md; include JS API signatures, native method mappings, permissions, dependencies, and blockers inline so the generator never has to look at the Cordova tree. |
| User halts at checkpoint due to a blocker they will not accept | Document the blocker in MIGRATION.md and stop. Do not invoke the generator. Capture the rejection reason so the next attempt can address it. |
| Complex plugin overwhelms generator context on a single invocation | Use incremental mode: invoke the generator once per platform (web → iOS → Android → final) with user checkpoints between each, not once for the whole plugin. See references/using-plugin-generator.md. |
| Mode A relocation conflicts with files in the Cordova repo (top-level name collision) | Halt the chain. Either resolve manually with the user (rename, delete, or move conflicting files), or fall back to Mode B by re-running Phase 11 against a sibling directory. |
Related Skills
capacitor-plugin-generator(required downstream dependency): Phase 11 invokes this skill via the Skill tool in structured mode with the YAML plan produced in Phase 9. The generator'sreferences/input-contract.mdis the authoritative shape for handoff. This skill conforms and cites it but does not duplicate any generator-side rules.build-actions-generator(optional downstream dependency, ODC path only): Phase 11a invokes this skill whenodc_target: true. It generatesbuildAction.jsonfor the Capacitor plugin'sbuild-actions/directory, covering config-level native setup (manifest, plist, Gradle deps, entitlements) so the generator does not need to emit Capacitor hook equivalents for those items.
References
references/output-modes.md: Mode A (in-place with.cordova-archive/) vs Mode B (side-by-side) directory layouts andgit mvpatterns.references/unsupported-patterns.md:<config-file>,<edit-config>,<hook>,<js-module runs>, preferences, permissions, and per-pattern blocker thresholds.references/dependency-migration.md: CocoaPods, SPM, system frameworks, Gradle coordinates, AAR/JAR, custom Maven repos, and per-dependency blocker thresholds.references/hooks-migration.md: Three-tier hook classification (Tier 1 Capacitor hooks, Tier 2 npm scripts, Tier 3 blocker) with script analysis workflow.references/api-mappings.md: JavaScript bridge, iOS (CDVPlugin→CAPPlugin), Android (CordovaPlugin→Plugin), and plugin.xml → package.json conversion.references/migration-patterns.md: Callback → Promise, permission handling, multi-platform configuration, and consistent error handling across platforms.references/complexity-assessment.md: Scoring rubric forsimple/moderate/complex/blockedand the inputs that move a plugin between buckets.references/using-plugin-generator.md: Building the YAML againstcapacitor-plugin-generator/references/input-contract.md, themigration:optional block, and the Phase 11 invocation pattern (standard vs incremental mode).references/post-migration-cleanup.md: Consolidating intermediate notes intoMIGRATION.md, the Mode A relocation flow, archiving Cordova source, updating README, and the consumer-facing breaking-change checklist.references/example-analysis.md: Full worked example end-to-end, plugin.xml read, dependency analysis, hooks classification, YAML output, and generator handoff.