Skip to content

feat(skills): add cozy-external-app skill for scaffolding external apps#3

Open
kitsunoff wants to merge 31 commits intocozystack:mainfrom
kitsunoff:feat/cozy-external-app-skill
Open

feat(skills): add cozy-external-app skill for scaffolding external apps#3
kitsunoff wants to merge 31 commits intocozystack:mainfrom
kitsunoff:feat/cozy-external-app-skill

Conversation

@kitsunoff
Copy link
Copy Markdown
Collaborator

@kitsunoff kitsunoff commented Apr 16, 2026

Summary

  • Add cozy-external-app skill that scaffolds a complete Cozystack external app package
  • Generates chart skeleton (Chart.yaml, Makefile, values.yaml with cozyvalues-gen annotations, templates)
  • Registers the app in core/platform (namespace, HelmRelease, CozystackResourceDefinition)
  • Handles dependency integration with two patterns: managed CNPG Postgres clusters provisioned in-chart, and external secret references for pre-existing services
  • CNPG integration patterns verified against cozystack harbor/keycloak implementations
  • Register the new plugin in marketplace.json and update README

Changes

  • skills/cozy-external-app/.claude-plugin/plugin.json — plugin metadata
  • skills/cozy-external-app/skills/cozy-external-app/SKILL.md — 10-phase skill with Guardrails and References
  • .claude-plugin/marketplace.json — added plugin entry
  • README.md — added table row

Summary by CodeRabbit

  • New Features

    • Added a new cozy-external-app plugin that scaffolds external Cozystack application packages via an interactive, multi-phase generate-only workflow with pre-flight checks, confirmation gates, and multiple dependency-integration patterns (e.g., operator-managed, in-chart, or external wiring).
  • Documentation

    • Updated Plugins list with an entry describing the new cozy-external-app skill.

Add a new skill that generates the complete package structure for
Cozystack external apps, including chart skeleton, values with
cozyvalues-gen annotations, CozystackResourceDefinition registration,
and dependency integration via managed CNPG clusters or external
secret references.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

Warning

Rate limit exceeded

@kitsunoff has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 28 minutes and 3 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 28 minutes and 3 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac6ee68d-dfb6-4dc8-ba0e-0127f20b5350

📥 Commits

Reviewing files that changed from the base of the PR and between fccbbd5 and 34c04ad.

📒 Files selected for processing (3)
  • .claude-plugin/marketplace.json
  • README.md
  • skills/cozy-external-app/skills/cozy-external-app/SKILL.md
📝 Walkthrough

Walkthrough

Adds a new cozy-external-app Claude plugin: marketplace registration, plugin metadata, README entry, and a comprehensive generate-only skill spec that defines a multi-phase CLI workflow for scaffolding Cozystack external app packages, dependency contract resolution, chart/template generation, and platform registration.

Changes

Cohort / File(s) Summary
Marketplace & Plugin metadata
​.claude-plugin/marketplace.json, skills/cozy-external-app/.claude-plugin/plugin.json
Register new plugin cozy-external-app with source ./skills/cozy-external-app, metadata (name, version 1.0.0, description, author).
Documentation
README.md
Add README table entry for cozy-external-app under Skills.
Skill specification
skills/cozy-external-app/skills/cozy-external-app/SKILL.md
Add full generate-only skill spec: CLI args, preflight checks (local/gh/kubectl), interactive prompts, dependency contract resolution (three integration patterns), deterministic Helm chart skeleton and templates, platform registration updates, validation, and guardrails.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as CLI Parser
    participant Preflight as Preflight Validator
    participant DepResolver as Dependency Resolver
    participant Generator as Template Generator
    participant FS as File System
    participant SchemaVal as Schema Validator

    User->>CLI: invoke cozy-external-app (app-name, flags)
    CLI->>Preflight: parse args & run repo/tool checks
    Preflight-->>CLI: preflight result

    CLI->>DepResolver: resolve dependencies (local/gh/kubectl)
    DepResolver-->>CLI: dependency contracts & wiring options

    CLI->>User: interactive prompts for app & wiring
    User-->>CLI: approve plan

    CLI->>Generator: generate chart skeleton & templates
    Generator->>FS: write Chart.yaml, values, templates, platform entries
    FS-->>Generator: confirm files written

    Generator->>SchemaVal: validate schemas & manifests
    SchemaVal-->>CLI: validation results
    CLI->>User: summary & next steps
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hop with charts and tiny maps,
Scaffolding apps in tidy scraps.
With contracts checked and files in place,
Cozy packages spring to grace.
Hooray — a new app finds its space!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(skills): add cozy-external-app skill for scaffolding external apps' clearly and accurately summarizes the main change—adding a new skill for scaffolding external apps, matching the comprehensive changeset that includes the skill definition, plugin metadata, and documentation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the cozy-external-app skill, which provides a structured workflow for scaffolding external application packages within the Cozystack ecosystem. The skill automates the creation of Helm charts, templates, and platform registration files while handling dependency patterns like managed Postgres clusters. The review feedback highlights several opportunities to strengthen the skill's logic, specifically by ensuring all required metadata for resource definitions is collected, clarifying the process for gathering operator chart details, providing explicit template examples for upstream Helm charts, and maintaining correct YAML formatting and variable consistency during the registration phase.

2. **Container image**: image reference (e.g., `ghcr.io/immich-app/immich-server:v1.120.0`).
3. **Public port**: does the app expose an HTTP port? If yes, which port number? Should an Ingress template be generated?
4. **Persistent storage**: does the app need a PVC? If yes, default size (e.g., `10Gi`).
5. **Icon**: path to an SVG file for the dashboard. If not available yet, note it — Phase 6 will create a `logos/` placeholder and Phase 9 will fail until the user provides one.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Phase 3 is missing several metadata fields required for the CozystackResourceDefinition in Phase 8. Specifically, the skill needs to gather the app's display name, description, category, tags, and the Kubernetes Kind/Plural names for the resource.

Suggested change
5. **Icon**: path to an SVG file for the dashboard. If not available yet, note it — Phase 6 will create a `logos/` placeholder and Phase 9 will fail until the user provides one.
5. **Icon**: path to an SVG file for the dashboard. If not available yet, note it — Phase 6 will create a `logos/` placeholder and Phase 9 will fail until the user provides one.
6. **Metadata**: Display Name (e.g., `Immich`), Description (e.g., `Self-hosted photo and video management solution`), Category (e.g., `Media`), and Tags (e.g., `photo, video`).
7. **Resource Definition**: Kind (e.g., `Immich`) and Plural (e.g., `immichs`) for the CozystackResourceDefinition.


Skip if `--operator` was not passed and the app does not need a custom operator.

If an operator is needed, create `packages/system/$APP_NAME-operator/`:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Phase 5 uses $OPERATOR_CHART_NAME and $OPERATOR_CHART_VERSION in the generated Makefile, but these variables are never gathered from the user. If an operator is required, the skill should ask for the specific chart name and version within the repository.

Suggested change
If an operator is needed, create `packages/system/$APP_NAME-operator/`:
If an operator is needed, use `AskUserQuestion` to collect the operator's chart name and version from the provided repository URL. Then create `packages/system/$APP_NAME-operator/`:


### Main workload — $APP_NAME.yaml

Generate the primary workload template. If the user chose "upstream Helm chart", wrap it in a Flux HelmRelease (like harbor does). If "custom templates", create a Deployment or StatefulSet directly.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The skill mentions wrapping upstream charts in a Flux HelmRelease but only provides a template for a direct Deployment. Providing a HelmRelease template example will help the AI generate the correct structure for the 'upstream chart' case.

Suggested change
Generate the primary workload template. If the user chose "upstream Helm chart", wrap it in a Flux HelmRelease (like harbor does). If "custom templates", create a Deployment or StatefulSet directly.
**For a Flux HelmRelease wrapper example:**
```yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: {{ .Release.Name }}
spec:
interval: 1h
chart:
spec:
chart: $UPSTREAM_CHART_NAME
version: $UPSTREAM_CHART_VERSION
sourceRef:
kind: HelmRepository
name: $UPSTREAM_REPO_NAME
values:
# Map values from .Values to upstream chart values

chart: ./packages/system/$APP_NAME-operator
sourceRef:
kind: GitRepository
name: external-apps
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The sourceRef.name is hardcoded to external-apps, but Phase 8 later includes logic to extract the actual name from init.yaml. The template should use the variable extracted from init.yaml to ensure consistency if the user has renamed their repository.

Suggested change
name: external-apps
name: $GIT_REPO_NAME

application:
kind: $APP_KIND
openAPISchema: |
<contents of values.schema.json>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When injecting the values.schema.json content into the CozystackResourceDefinition, the AI must be instructed to indent the JSON content correctly to maintain valid YAML structure.

Suggested change
<contents of values.schema.json>
<contents of values.schema.json, indented by 6 spaces>

kitsunoff and others added 23 commits April 18, 2026 14:18
…ase 3

Phase 8 renders a CozystackResourceDefinition that requires display name, description, category, tags, Kind, and Plural. Phase 3 previously collected none of these, forcing the skill to improvise in a later phase.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
…ase 5

The generated Makefile interpolates $OPERATOR_CHART_NAME and $OPERATOR_CHART_VERSION when pulling the operator chart, but the skill never asked the user for them. Add an explicit AskUserQuestion step so the variables are populated before the Makefile is written.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
Phase 7 instructs the skill to wrap upstream charts in a Flux HelmRelease but only showed a direct Deployment example. Add an explicit HelmRelease template so the upstream chart case has a concrete shape to follow.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
The operator HelmRelease template hardcoded sourceRef.name to "external-apps" while the CozystackResourceDefinition already used $GIT_REPO_NAME. Extract the value from init.yaml once at the top of phase 8 and reuse it in both templates so the skill works for users who renamed their repository.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
The CozystackResourceDefinition embeds values.schema.json under a YAML literal block scalar. Without indenting the JSON by six spaces the YAML parser silently absorbs the schema as plain text. Call out the rule and provide a sed one-liner for the manual path.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
The CRD kind CozystackResourceDefinition does not exist in Cozystack v1.x — it was renamed to ApplicationDefinition. The skill, plugin metadata, and marketplace entry all referenced the stale name, so every generated manifest would have been rejected by the API server. Switch to the current kind everywhere. The filename cozyrds.yaml is kept unchanged, matching upstream convention in external-apps-example.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
Three schema-level issues made the generated manifest invalid: metadata.namespace was set on a cluster-scoped kind; release.chart.sourceRef does not exist in the CRD (required field is release.chartRef with kind HelmChart/ExternalArtifact/OCIRepository); keysOrder was missing and the dashboard UI would render fields in arbitrary order. Align the template with the CRD schema and the minecraft-server reference in external-apps-example.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
…ackage

The reference external-apps layout registers operator charts via a flux HelmRepository (typically oci://) and references them from the HelmRelease with sourceRef.kind: HelmRepository. The skill instead created a stub packages/system/<app>-operator/ chart with a make update target that pre-pulled the upstream into charts/ — a pattern that diverges from the reference and leaves the HelmRelease pointing at an empty stub Chart.yaml. Rewrite Phase 5 to gather operator specification only (type, URL, chart name, version) and switch the Phase 8 operator HelmRelease template to sourceRef.kind: HelmRepository. Also fix the misplaced dependsOn paragraph: the application HelmRelease is controller-generated from the ApplicationDefinition and is not user-authored, so dependsOn only makes sense on the operator HelmRelease.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
The reference layout has five platform template files, not three. helmcharts.yaml must have one entry per app (the source referenced by ApplicationDefinition.spec.release.chartRef), and helmrepositories.yaml must have one entry per operator chart. Without these the chartRef has no source and flux cannot produce the chart artifact. Add both subsections to phase 8, expand the phase 2 pre-flight to assert all five files, and update summary/guardrails to match.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
Two problems made the generated app Makefile unusable against external-apps repos. It lacked export NAME and export NAMESPACE, so every downstream target inherited from scripts/package.mk failed with "env NAME is not set!". And it invoked ../../../hack/update-crd.sh, a script that only exists in the cozystack monorepo — not in external-apps-example. Add the required exports, drop the hack/update-crd.sh call, switch cozyvalues-gen to its long flags, and strip the phase 8 paragraphs that promoted a script users do not have.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
Phase 4 previously hardcoded only the CNPG/postgres pattern. A redis or mongodb dependency would have left the assistant to improvise CR kinds, secret naming, and credential keys — a guaranteed source of silent wiring bugs. Introduce a dependency catalog documenting four common patterns verified against the cozystack monorepo (CNPG for postgres, Spotahome RedisFailover for redis, Percona PSMDB for mongodb, Strimzi for kafka) plus a research procedure for anything not listed. The catalog explicitly distinguishes operator-emitted secrets (CNPG) from chart-created ones (RedisFailover), since the wiring differs.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
… wiring

Split Phase 4 Pattern A into three explicit steps: research the dependency (CR kind, output Secret owner/name/keys, app-side wiring), collect CR-spec values, collect env mapping. The earlier flow jumped straight to hardcoded CNPG defaults and would have silently produced wrong Secret references for redis or mongodb. Env mapping now uses facts captured during research — catalog-driven examples show how wiring differs between operator-emitted secrets (CNPG) and chart-created ones (Spotahome RedisFailover).

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
…ase 7

Phase 7 previously shipped a single hardcoded database.yaml for postgres/CNPG, implying the same wiring for every dependency. Rework the section into "per-dep creation templates": one file per Pattern A dependency, shape dictated by the facts captured during Phase 4 research. Add a full redis.yaml example (Secret + RedisFailover with auth.secretPath) to illustrate that chart-created secrets require a fundamentally different wiring from CNPG. Tighten guardrails to forbid copying postgres wiring onto other dependencies.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
…chart source

Phase 5 was scoped to operator-only chart sources, but flux HelmRepository registration is equally needed when the app wraps an upstream helm chart (Gitea, Immich, Nextcloud — all have first-party maintained charts). Rewrite the phase around a $SOURCE_ROLE (main | operator) so either case registers a HelmRepository; phase 8 can then reuse one code path for both.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
For apps with a maintained first-party helm chart, wrapping in a Flux HelmRelease is strictly better than hand-rolling a Deployment: upgrades, init jobs, probes, PDBs, and CVE mitigations all stay the responsibility of the upstream maintainers. Rewrite Phase 3 question 1 to bias toward the wrapper pattern, and rework Phase 7 main workload so the HelmRelease example is the primary shape — complete with dependsOn against pattern C sibling HelmReleases, bundled-subchart disablement, and valuesFrom lifting passwords from cozystack-created Secrets into the upstream chart value schema. The custom Deployment example is demoted to a fallback for apps without a viable upstream chart.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
…apps

Pattern A (in-chart operator CR) bypasses the cozystack abstraction — dependencies never appear in the dashboard, WorkloadMonitor/backup conventions get reinvented per app, and platform upgrades do not propagate. For tenant-facing external apps the correct default is Pattern C: the app chart creates a sibling cozystack CR (Postgres, Redis, MariaDB, etc.), the cozystack controller reconciles it into its own HelmRelease, and the app reads the Secret/Service that downstream chart produces. Add pattern C to phase 4 as the recommended path and demote pattern A to a system-style escape hatch.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
…tgres and redis

Extract the sibling-CR naming contracts (HelmRelease prefix, credentials Secret template, Service names) from cozystack packages/system/postgres-rd and redis-rd cozyrds files and publish them as catalog entries. Each entry ships a ready-to-adapt templates/<dep>.yaml snippet and a matching main-HelmRelease values/valuesFrom block — the two pieces a user needs to wire a pattern C dependency end to end. For mariadb, mongodb, kafka, and friends, point readers at packages/system/<dep>-rd/cozyrds/<dep>.yaml as the authoritative source rather than publishing unverified copies.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
Phase 7 now ships ready-to-adapt pattern C templates (apps.cozystack.io Postgres and Redis) as the preferred shape, with pattern A CNPG and RedisFailover examples kept for the escape-hatch case. Phase 10 summary surfaces the chosen pattern per dependency (A/B/C) plus the chart-source mode (upstream wrapper vs custom templates) so a quick glance at the summary tells a reader exactly how the generated package is wired.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
After phase 5 was generalized, phase 8 still referenced the old $OPERATOR_* variable names and assumed a single chart source per app. Rewrite the namespaces, helmrepositories, and helmreleases subsections to iterate over every $SOURCE_* entry gathered in phase 5 (main and/or operator) and emit the corresponding resource once per source. Also call out that the main upstream chart HelmRelease lives inside the app chart itself (one per tenant instance) and must not appear in the platform-level helmreleases.yaml.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
…k at runtime

Replace the "ask user to list deps" flow with a chart-driven, dynamically-resolved procedure. Step 1 introspects the upstream chart (helm show chart/values/readme + keyword scan of value paths) to surface candidate deps along with the exact targetPath the app expects. Step 2 resolves each dep against cozystack — local checkout, then gh api, then live cluster via kubectl — and records a $DEP_CONTRACT with kind, prefix, secret/service templates, and openAPISchema. Steps 3-5 pick the integration pattern, collect spec values from the resolved schema, and write the wiring record. Patterns A/B/C are preserved but now consume $DEP_CONTRACT instead of ad-hoc lookups, and pattern C is only offered when resolution succeeded.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
Phase 4 now resolves dependency contracts at runtime from cozystack itself, so the catalog appendix stops trying to enumerate every supported dep. Rewrite the intro as an interpretation guide that shows how to read an ApplicationDefinition into a $DEP_CONTRACT record, keep postgres and redis as worked examples so the assistant can self-check its parsing, and delete the "other dependencies" hand-wave that had become dead weight.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
…ight

Phase 2 now detects which of the three contract sources (local cozystack checkout, gh api, live cluster) is available and records $COZYSTACK_CONTRACT_SOURCE. Pattern C is marked unavailable up front when no source is reachable, preventing the skill from silently falling back to invented contracts. Tighten guardrails to forbid skipping either resolution step, and add the source-priority pointer to the References section.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
The skill previously gated file writes with a per-phase AskUserQuestion in phases 6, 7, and 8 — the user never saw the full picture before files started landing on disk. Phase 5.5 assembles everything decided in phases 1-5 (app summary, chart source, contract source, exact file list, wiring table, open items) into one plan and asks for a single approve/edit/abort. Once approved via $PLAN_APPROVED, phases 6-8 skip their individual confirmations and run through without extra prompts. The per-phase gates remain as a fallback for runs where 5.5 was not reached or the plan was rejected.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
Copy link
Copy Markdown
Collaborator Author

@kitsunoff kitsunoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review after two parallel analyses. Three concrete correctness issues will bite anyone running the skill end-to-end and should be fixed before merge; four smaller items are non-blocking. Overall architecture (contract-driven Pattern C, runtime resolution, Phase 5.5 plan gate) checks out against the actual cozystack and external-apps-example sources.

Blocker scope: generated charts would fail helm template (missing values), silently rotate DB passwords on every reconcile (random regeneration without lookup), or pick the wrong GitRepository name from init.yaml. All three are one-line fixes.

{{- end }}
users:
{{ .Values.database.user }}:
password: {{ .Values.database.password | default (randAlphaNum 32) | quote }}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker: randAlphaNum 32 produces a fresh random value every helm template or Flux reconcile. When .Values.database.password is empty (the default — Phase 6 never declares the field), each render writes a different password into the Postgres CR, which cascades into the downstream postgres-<cr>-credentials Secret and breaks the running app whenever its password rotates.

Evidence: cozystack/packages/apps/postgres/templates/init-script.yaml solves this with Sprig's lookup — pulls the existing password from the already-rendered Secret and reuses it on subsequent renders. The Pattern A redis example in this skill uses the same idiom.

Fix: wrap the password with lookup so subsequent renders reuse the previously-rendered value:

{{- $existing := lookup "v1" "Secret" .Release.Namespace (printf "postgres-%s-db-credentials" .Release.Name) }}
{{- $pwd := "" }}
{{- if $existing }}
{{-   $pwd = index $existing.data .Values.database.user | b64dec }}
{{- end }}
{{- $pwd = $pwd | default (.Values.database.password | default (randAlphaNum 32)) }}
...
    password: {{ $pwd | quote }}

Same fix needed in the Dependency catalog Pattern C postgres snippet around line 1024.

host: ""
port: 5432
secretName: ""
```
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker: Phase 7 Pattern C postgres.yaml (line ~581) reads .Values.database.{size,replicas,user,name,password} and Pattern C redis.yaml (line ~620) reads .Values.redis.{size,replicas}. This values.yaml example only documents Pattern A (database.{size,replicas} without user/name) and Pattern B. A user who follows the skill end-to-end on Pattern C — the documented default for external apps — ends up with a chart that fails helm template: <no value> in every wired gitea.config.database.* path, plus the Postgres CR spec renders with empty users: and databases: maps.

Fix: add a Pattern C values section alongside Pattern A and Pattern B. Minimum fields for postgres: user, name, size, replicas with sane defaults. For redis: size, replicas. Document that password is optional and generated when empty (ties to blocker #1).

Test: generate a chart for /cozy-external-app gitea --depends-on=postgres,redis on Pattern C defaults and run helm template test . — must render without <no value> anywhere.

Before generating any YAML in this phase, extract the GitRepository name from `init.yaml` — it is referenced as `sourceRef.name` in both the operator HelmRelease and the ApplicationDefinition below:

```bash
GIT_REPO_NAME=$(yq -r '.metadata.name' $REPO_DIR/init.yaml | head -1)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker: yq -r '.metadata.name' $REPO_DIR/init.yaml | head -1 treats any metadata.name from any document as authoritative. The reference init.yaml in cozystack/external-apps-example contains both a GitRepository and a HelmRelease both named external-apps — the extraction works by coincidence. If a user renames their repo so the two differ (e.g., GitRepository my-apps, HelmRelease my-apps-release, which is a valid layout), head -1 silently grabs the HelmRelease name and every Phase 8 sourceRef points at a non-existent source.

The skill already documents the safe form directly below (line 758) as a "fallback for multi-document init.yaml":

yq -r 'select(.kind == "GitRepository") | .metadata.name' $REPO_DIR/init.yaml

Fix: promote that form to the only/primary path. It handles the single-document case equally well.


Generate via:
```bash
cd $REPO_DIR/packages/apps/$APP_NAME && cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation (non-blocking): this bash snippet uses short flags (-v -s -r) while the Makefile generated a few lines up (line 311) uses long flags (--values --schema --readme). The global convention is long-flag-only. Since users literally copy this command, consistency with the Makefile matters. Normalize to long form here.

spec:
interval: 5m
url: $SOURCE_REPO_URL
# If $SOURCE_REPO_TYPE is `oci`, uncomment the next line:
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation (non-blocking): a literal-minded assistant may emit the # type: oci line as a comment rather than uncommenting it. The reference external-apps-example's helmrepositories.yaml has type: oci set plainly when the source is OCI. Consider two explicit variants in the skill (HTTPS source vs OCI source) or a Go-template conditional so the emitted YAML never needs post-processing.


For each dependency from Step 1, resolve a `$DEP_CONTRACT` from cozystack. Try these sources in order; stop at the first success:

1. **Local cozystack checkout** — when `$COZYSTACK_REPO` is set (or detectable as a sibling dir of `$REPO_DIR`). Read:
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation (non-blocking): the clause "or detectable as a sibling dir of $REPO_DIR" is never defined. Phase 2 Step 4 only reads $COZYSTACK_REPO from env. Either specify the probe (e.g., check ../cozystack/packages/system/ for cozyrds directories) or drop the clause.

redis:
host: rfs-redis-{{ .Release.Name }}-redis
port: 26379
sentinelMaster: mymaster
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation (non-blocking): functionally correct — mymaster is Spotahome's default sentinel monitor name and cozystack's packages/apps/redis/templates/redisfailover.yaml does not override it. But the skill's own guardrail says "never guess a dependency's Secret/config names" and this value is not derivable from $DEP_CONTRACT. Add a one-line comment citing the Spotahome default so an assistant or reviewer can tell verified from guessed.

kitsunoff and others added 3 commits April 18, 2026 16:08
The pattern C Postgres CR used randAlphaNum 32 as the default password, which regenerates on every helm template or flux reconcile. Since the downstream cozystack apps/postgres chart already preserves passwords via Sprig lookup against its own postgres-<release>-credentials Secret (templates/init-script.yaml), the external chart should simply not pass a password unless the user explicitly supplied one. Omitting the field lets the downstream chart own password lifecycle end-to-end.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
Phase 7 pattern C templates read .Values.database.{user,name,password,size,replicas} and .Values.redis.{size,replicas}, but phase 6 only documented pattern A and pattern B values sections. A user following the skill end-to-end on pattern C (the documented default for external apps) ended up with a chart that fails helm template — <no value> in every wired gitea.config.database.* path and an empty users map in the Postgres CR. Add a pattern C section ahead of A and B so the values.yaml skeleton actually covers the recommended path.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
…parse

The phase 8 yq extraction used .metadata.name + head -1 as primary path, picking whichever kind happened to come first in init.yaml. The external-apps-example layout has a GitRepository and a HelmRelease both named external-apps, so the old extraction worked by coincidence; if a user ever renames the repo and the HelmRelease ends up with a different name (a valid layout), head -1 silently grabs the HelmRelease name and every phase 8 sourceRef points at nonexistent source. Make kind=GitRepository the deterministic primary selector.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
@kitsunoff kitsunoff marked this pull request as ready for review April 18, 2026 13:13
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@skills/cozy-external-app/skills/cozy-external-app/SKILL.md`:
- Around line 474-484: Replace the hardcoded dependsOn and subchart toggles (the
dependsOn block plus values keys postgresql, postgresql-ha, redis,
redis-cluster) with logic that constructs those entries from the Phase 4
resolved dependency list and the detected upstream subcharts: iterate the
resolved dependencies and emit a dependsOn entry for each backing service (using
the release name/namespace templating currently used), and iterate detected
upstream subcharts to set values.<chart>.enabled = false only for the subcharts
that actually exist; skip others so HelmRelease output matches actual deps.
Ensure the code that builds the HelmRelease uses the Phase 4
resolvedDependencies data structure and the upstreamSubcharts detection result
rather than fixed names.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 27c70102-e082-451b-b46d-8b402aa35531

📥 Commits

Reviewing files that changed from the base of the PR and between 1d748bc and 5c8aaf1.

📒 Files selected for processing (4)
  • .claude-plugin/marketplace.json
  • README.md
  • skills/cozy-external-app/.claude-plugin/plugin.json
  • skills/cozy-external-app/skills/cozy-external-app/SKILL.md

Comment on lines +474 to +484
dependsOn:
- name: postgres-{{ .Release.Name }}-db
namespace: {{ .Release.Namespace }}
- name: redis-{{ .Release.Name }}-redis
namespace: {{ .Release.Namespace }}
# Disable the upstream chart's bundled subcharts — we provide backing services via Pattern C.
values:
postgresql: { enabled: false }
postgresql-ha: { enabled: false }
redis: { enabled: false }
redis-cluster: { enabled: false }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make dependency wiring dynamic instead of hardcoded postgres/redis.

Lines 474-484 hardcode dependsOn and subchart toggles for postgres/redis. For apps without those deps (or with different deps), this generates incorrect HelmRelease output. Build these blocks from the Phase 4 resolved dependency list and detected upstream subcharts.

Suggested adjustment
-  dependsOn:
-    - name: postgres-{{ .Release.Name }}-db
-      namespace: {{ .Release.Namespace }}
-    - name: redis-{{ .Release.Name }}-redis
-      namespace: {{ .Release.Namespace }}
+  # Emit dependsOn entries only for dependencies selected in Phase 4 (Pattern C/A),
+  # using each dependency's resolved release prefix and CR name template.

-  values:
-    postgresql:    { enabled: false }
-    postgresql-ha: { enabled: false }
-    redis:         { enabled: false }
-    redis-cluster: { enabled: false }
+  values:
+    # Disable only subcharts that were actually detected in Phase 4 Step 1
+    # and mapped to external/sibling dependency wiring.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@skills/cozy-external-app/skills/cozy-external-app/SKILL.md` around lines 474
- 484, Replace the hardcoded dependsOn and subchart toggles (the dependsOn block
plus values keys postgresql, postgresql-ha, redis, redis-cluster) with logic
that constructs those entries from the Phase 4 resolved dependency list and the
detected upstream subcharts: iterate the resolved dependencies and emit a
dependsOn entry for each backing service (using the release name/namespace
templating currently used), and iterate detected upstream subcharts to set
values.<chart>.enabled = false only for the subcharts that actually exist; skip
others so HelmRelease output matches actual deps. Ensure the code that builds
the HelmRelease uses the Phase 4 resolvedDependencies data structure and the
upstreamSubcharts detection result rather than fixed names.

- normalize cozyvalues-gen invocation to long flags, matching Makefile
- split helmrepositories.yaml into explicit HTTPS and OCI variants so
  the skill never emits a commented-out type line
- drop undefined sibling-dir detection clause from Phase 4 Step 2;
  Phase 2 Step 4 only reads $COZYSTACK_REPO
- cite Spotahome RedisFailover default next to every sentinelMaster:
  mymaster so the value is traceable, not a guess
- frame the Phase 7 HelmRelease example as a two-dep illustration and
  warn against copying unrelated dependsOn or subchart toggles

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@skills/cozy-external-app/skills/cozy-external-app/SKILL.md`:
- Around line 214-215: The contract for $SOURCE_REPO_TYPE is inconsistent: Phase
5 documentation says non-OCI should be left empty while Phase 8 (and the
HelmRepository template condition) expects "https", which causes wrong variant
selection; update the Phase 8 text/examples and any condition checks that
reference $SOURCE_REPO_TYPE (the Phase 8 rendering logic and the HelmRepository
template branch) to treat non-OCI as empty (or conversely make Phase 5 use
"https" everywhere) so both collection and rendering agree—pick the
empty-as-default approach and replace "https" examples/conditions in Phase 8 and
related template branches with empty/default handling for $SOURCE_REPO_TYPE.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 18cdd063-21b3-4ebf-8db6-8669b1769f83

📥 Commits

Reviewing files that changed from the base of the PR and between 5c8aaf1 and fccbbd5.

📒 Files selected for processing (1)
  • skills/cozy-external-app/skills/cozy-external-app/SKILL.md

Comment thread skills/cozy-external-app/skills/cozy-external-app/SKILL.md Outdated
kitsunoff and others added 3 commits April 22, 2026 15:18
Blockers:
- main HelmRelease no longer bakes $APP_DB_USER / $APP_DB_NAME at
  generation time; use {{ .Values.database.user }} / .name so the
  values block and valuesFrom.valuesKey stay in sync with templates/
  postgres.yaml when a tenant overrides database.user at deploy time
- Pattern A Spotahome RedisFailover Secret now inlines the full
  lookup-then-randAlphaNum idiom from cozystack/packages/apps/redis/
  templates/redisfailover.yaml, preventing the same password-churn
  class previously fixed for Pattern C postgres

Observations:
- Phase 3 collects Display Name plural separately; Phase 8 cozyrds
  now renders plural: $APP_DISPLAY_PLURAL instead of reusing the
  singular, matching the cozystack external-apps-example reference
- cozyrds tags template shows two placeholders with a comment
  requiring one list item per collected tag
- $SOURCE_REPO_TYPE is now https (not empty) for HTTPS sources so
  Phase 8's variant selector reads the exact value it documents
- Phase 3 icon note and Phase 5.5 open-items line reference Phase 8
  base64 as the actual failure point, not Phase 9 validation

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
Main picked up drbd-recovery (PR cozystack#4) while this branch was open. Both
plugins coexist in alphabetical order: cozy-deploy, cozy-external-app,
drbd-recovery.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
…e number

Line-number ranges like `sts.yaml:142-168` drift on any upstream refactor.
Anchor the three cozystack references by the named template block they
point at (`KC_DB_*` env block in keycloak sts.yaml; `database.external`
values block in harbor.yaml) so the guidance stays valid across
cozystack releases.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant