$npx -y skills add opendatahub-io/ai-helpers --skill module-scaffoldGiven a component name, generate a complete standalone ODH module operator repository. Produces Go module, CRD types implementing PlatformObject, controller skeleton with reconciler builder pattern, Helm chart, Makefile, CI config, singleton webhook, and AGENTS.md. Use when start
| 1 | # ODH Module Scaffold |
| 2 | |
| 3 | Generate a complete standalone module operator repository for the ODH platform. |
| 4 | |
| 5 | ## Step 1: Parse arguments |
| 6 | |
| 7 | Extract the component name from `$ARGUMENTS`. If not provided, ask the user. |
| 8 | |
| 9 | Derive these values from the component name (example: `model-registry`): |
| 10 | |
| 11 | | Variable | Example Value | |
| 12 | |----------|---------------| |
| 13 | | `COMPONENT` | `model-registry` | |
| 14 | | `COMPONENT_SNAKE` | `model_registry` | |
| 15 | | `COMPONENT_PASCAL` | `ModelRegistry` | |
| 16 | | `COMPONENT_LOWER` | `modelregistry` | |
| 17 | | `COMPONENT_LOWER_PLURAL` | `modelregistries` | |
| 18 | | `MODULE_REPO` | `<COMPONENT>-operator` | |
| 19 | | `API_GROUP` | `components.platform.opendatahub.io` | |
| 20 | | `API_VERSION` | `v1alpha1` | |
| 21 | | `SINGLETON_NAME` | `default-<COMPONENT>` | |
| 22 | |
| 23 | Ask the user: |
| 24 | 1. Which API group to use: `components.platform.opendatahub.io` or |
| 25 | `services.platform.opendatahub.io`? Default: `components`. |
| 26 | 2. Target directory? Default: `./<MODULE_REPO>`. |
| 27 | |
| 28 | ## Step 2: Create directory structure |
| 29 | |
| 30 | Generate this layout: |
| 31 | |
| 32 | ```text |
| 33 | <MODULE_REPO>/ |
| 34 | ├── api/<API_VERSION>/ |
| 35 | │ ├── <COMPONENT_SNAKE>_types.go |
| 36 | │ ├── <COMPONENT_SNAKE>_common.go |
| 37 | │ ├── groupversion_info.go |
| 38 | │ └── doc.go |
| 39 | ├── internal/ |
| 40 | │ ├── controller/ |
| 41 | │ │ ├── <COMPONENT_SNAKE>_controller.go |
| 42 | │ │ └── <COMPONENT_SNAKE>_controller_actions.go |
| 43 | │ └── webhook/ |
| 44 | │ └── singleton.go |
| 45 | ├── charts/<MODULE_REPO>/ |
| 46 | │ ├── Chart.yaml |
| 47 | │ ├── values.yaml |
| 48 | │ └── templates/ |
| 49 | │ ├── deployment.yaml |
| 50 | │ ├── serviceaccount.yaml |
| 51 | │ ├── clusterrole.yaml |
| 52 | │ ├── clusterrolebinding.yaml |
| 53 | │ ├── webhook-service.yaml |
| 54 | │ ├── webhook-certificate.yaml |
| 55 | │ └── _helpers.tpl |
| 56 | ├── cmd/ |
| 57 | │ └── main.go |
| 58 | ├── config/ |
| 59 | │ └── samples/ |
| 60 | │ └── <COMPONENT_SNAKE>.yaml |
| 61 | ├── go.mod |
| 62 | ├── Makefile |
| 63 | ├── Containerfile |
| 64 | ├── .github/workflows/ |
| 65 | │ └── ci.yaml |
| 66 | ├── .rules/ |
| 67 | │ ├── api-types.md |
| 68 | │ ├── controller.md |
| 69 | │ ├── helm-chart.md |
| 70 | │ ├── security.md |
| 71 | │ └── testing.md |
| 72 | ├── AGENTS.md |
| 73 | ├── README.md |
| 74 | └── .gitignore |
| 75 | ``` |
| 76 | |
| 77 | ## Step 3: Generate CRD types |
| 78 | |
| 79 | Use the templates from `${CLAUDE_SKILL_DIR}/references/templates.md` to |
| 80 | generate each file. Key requirements: |
| 81 | |
| 82 | ### `api/<API_VERSION>/<COMPONENT_SNAKE>_types.go` |
| 83 | |
| 84 | The CR type must: |
| 85 | - Embed `metav1.TypeMeta` and `metav1.ObjectMeta` |
| 86 | - Have `Spec` field using `<COMPONENT_PASCAL>Spec` |
| 87 | - Have `Status` field using `<COMPONENT_PASCAL>Status` |
| 88 | - Include kubebuilder markers: `+kubebuilder:object:root=true`, |
| 89 | `+kubebuilder:subresource:status`, `+kubebuilder:resource:scope=Cluster` |
| 90 | - Include CEL singleton validation for the name |
| 91 | - Include compile-time assertion: `var _ common.PlatformObject = (*<COMPONENT_PASCAL>)(nil)` |
| 92 | - Implement all PlatformObject methods (GetStatus, GetConditions, |
| 93 | SetConditions, GetReleaseStatus, SetReleaseStatus) |
| 94 | |
| 95 | The status struct must embed `common.Status` and |
| 96 | `common.ComponentReleaseStatus` both as **anonymous (unnamed) fields** with |
| 97 | `json:",inline"` tags. Use `common.ComponentReleaseStatus \`json:",inline"\`` |
| 98 | instead of a named field like |
| 99 | `Releases common.ComponentReleaseStatus json:"releases,omitempty"` because |
| 100 | a named field serializes as a nested object `{releases: {releases: [...]}}` |
| 101 | instead of the flat array `{releases: [...]}` the CRD expects, causing |
| 102 | status update failures. |
| 103 | |
| 104 | ### `api/<API_VERSION>/<COMPONENT_SNAKE>_common.go` |
| 105 | |
| 106 | Define `<COMPONENT_PASCAL>CommonSpec` with `common.ManagementSpec` embedded |
| 107 | inline. This struct is inlined into `<COMPONENT_PASCAL>Spec`. |
| 108 | |
| 109 | ### `api/<API_VERSION>/groupversion_info.go` |
| 110 | |
| 111 | Standard kubebuilder groupversion registration using the chosen API group |
| 112 | and version. |
| 113 | |
| 114 | ### `api/<API_VERSION>/doc.go` |
| 115 | |
| 116 | Package doc comment with `+groupName=<API_GROUP>` marker. |
| 117 | |
| 118 | ## Step 4: Generate controller |
| 119 | |
| 120 | ### `internal/controller/<COMPONENT_SNAKE>_controller.go` |
| 121 | |
| 122 | The controller setup must: |
| 123 | - Use `reconciler.NewReconciler` with functional options pattern |
| 124 | - Register render, deploy, and gc actions (gc MUST be last) |
| 125 | - Use `ReconcilerFor` builder pattern with `WithReconcilerOpts` for release |
| 126 | info. The default conditions (Ready + ProvisioningSucceeded) are sufficient |
| 127 | for the scaffold. Add `WithConditions("Degraded")` only when real actions |
| 128 | explicitly manage it via `rr.Conditions.MarkFalse`/`MarkTrue` |
| 129 | - Import from `odh-platform-utilities` packages: |
| 130 | - `framework/controller/reconciler` for reconciler setup |
| 131 | - `framework/controller/conditions` for condition management |
| 132 | - `framework/controller/actions/deploy` for resource deployment |
| 133 | - `framework/con |