Project-side conventions for commits, branches, and pull requests in Cozystack.
- Commit message follows Conventional Commits format
- Commit is signed off with
--signoff - Branch is rebased on
upstream/main(no extra commits) - PR body includes description and release note
- Ran
make generatein every package whosevalues.yaml,values.schema.json,Chart.yaml, orREADME.mdwas touched, and committed the regenerated files
Several files in each package are produced by make generate from values.yaml + values.schema.json and must stay in sync with the hand-edited sources:
packages/(apps|extra)/<name>/README.md— regenerated bycozyvalues-gen(parameter table, formatting).packages/(apps|extra)/<name>/values.schema.json—cozyvalues-genrewrites ordering and derived fields.packages/system/<name>-rd/cozyrds/<name>.yaml— produced byhack/update-crd.sh, whichmake generateinvokes.
Before committing edits to any of those sources, run make generate inside the package and stage the full diff:
make -C packages/<apps-or-extra>/<name> generate
git add packages/<apps-or-extra>/<name>/ packages/system/<name>-rd/The repo's pre-commit CI job runs make generate in every package and then git diff --exit-code. Any unstaged generator output fails the job with exit code 123 and blocks the PR. Also rerun make generate after a git commit --amend if the amended change touched any of the sources above.
To locate packages a WIP branch likely needs to be regenerated:
git diff --name-only | xargs -n1 dirname | sort -u | grep ^packages/Follow Conventional Commits with --signoff:
git commit --signoff -m "type(scope): brief description"Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
Scopes (examples — not an exhaustive list; pick the most specific scope that describes the change, and introduce a new one if a genuinely new area needs its own):
- System, e.g.:
dashboard,platform,operator,cilium,kube-ovn,linstor,fluxcd,cluster-api - Apps, e.g.:
postgres,mariadb,redis,kafka,clickhouse,virtual-machine,kubernetes - Other, e.g.:
api,hack,tests,ci,docs,agents,maintenance
Breaking changes: append ! after type/scope (feat(api)!: ...) or add a BREAKING CHANGE: footer.
Examples:
git commit --signoff -m "feat(dashboard): add config hash annotations to restart pods on config changes"
git commit --signoff -m "fix(postgres): update operator to version 1.2.3"
git commit --signoff -m "docs(contributing): add installation guide".github/workflows/pr-labeler.yaml parses the PR title on opened, edited, reopened, and synchronize events and applies labels additively (never removes). The title is expected to follow Conventional Commits — same format as commit messages above.
Type → kind/*:
| type | label |
|---|---|
| feat | kind/feature |
| fix | kind/bug |
| docs | kind/documentation |
| chore | kind/cleanup |
| refactor | kind/cleanup |
| style, perf, test, build, ci, revert | (no kind label) |
Scope → area/* (full mapping in .github/workflows/pr-labeler.yaml):
| scope (examples) | label |
|---|---|
| agents, ai | area/ai |
| api, cozystack-api | area/api |
| build | area/build |
| ci | area/ci |
| dashboard | area/dashboard |
| postgres, mariadb, redis, etcd, kafka, clickhouse, postgres-operator, mariadb-operator | area/database |
| extra | area/extra |
| kubernetes | area/kubernetes |
| monitoring, vlogs, vmstack, grafana, workloadmonitor | area/monitoring |
| ingress, gateway, vpn, metallb, cilium, kube-ovn, cozy-proxy, ... | area/networking |
| platform, bundle, flux, fluxcd, cluster-api, talos, installer, cozyctl, cozystack-engine, cozy-lib | area/platform |
| backport, release | area/release |
| seaweedfs, bucket, linstor, velero, harbor, backups | area/storage |
| tests, e2e | area/testing |
| kubevirt, cdi, vmi, vm-import, virtual-machine, hami, gpu-operator | area/virtualization |
Special handling:
[Backport release-1.x]prefix is stripped before parsing;area/releaseandbackportlabels are added.- Composite scope (
feat(platform, system, apps): ...) — each comma-separated part is mapped independently. !after type orBREAKING CHANGE:footer in the body →kind/breaking-change.- Unmapped scope or non-conventional title →
area/uncategorized(signals the PR needs manual area selection). - Bracket-style fallback (
[scope] description) mapsscope→area/*but cannot inferkind/*.
When an AI agent authors or materially assists with a commit, add an Assisted-By: trailer naming the model:
Assisted-By: Claude <noreply@anthropic.com>
Assisted-By: GPT-5 <noreply@openai.com>
Assisted-By: Gemini <noreply@google.com>
This sits alongside the Signed-off-by: trailer produced by --signoff. Use one trailer per model if multiple contributed.
If the branch has extra commits, clean it up:
git fetch upstream
git checkout -b my-feature upstream/main
git cherry-pick <your-commit-hash>
git push -f origin my-featureFill in the template at .github/PULL_REQUEST_TEMPLATE.md. It includes the required release-note block.
Create the PR with gh pr create --title "type(scope): brief description" --body-file <file>.
Cozystack uses GitHub review threads with resolution status. Only unresolved threads are actionable — resolved threads are already handled.
The REST endpoint /pulls/{pr}/reviews returns review summaries, not individual review comments. Use the GraphQL API to access reviewThreads with isResolved status:
gh api graphql -F owner=cozystack -F repo=cozystack -F pr=<PR_NUMBER> -f query='
query($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviewThreads(first: 100) {
nodes {
isResolved
comments(first: 100) {
nodes {
id
path
line
author { login }
bodyText
url
createdAt
}
}
}
}
}
}
}' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | .comments.nodes[]'Compact one-line variant:
gh api graphql -F owner=cozystack -F repo=cozystack -F pr=<PR_NUMBER> -f query='
query($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviewThreads(first: 100) {
nodes {
isResolved
comments(first: 100) {
nodes {
path
line
author { login }
bodyText
}
}
}
}
}
}
}' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | .comments.nodes[] | "\(.path):\(.line // "N/A") - \(.author.login): \(.bodyText[:150])"'