> ## Documentation Index
> Fetch the complete documentation index at: https://dev.haico.gr/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Automations

> The live, always-on GitHub automations: issue forms, the area labeler, Release Drafter, CI/CD, the stale bot, and Dependabot.

# Repository Automations

This document is a functional overview of the **always-on GitHub automations** that run on the HAI-Co² repository: what fires, when, what it does, and where each one is configured. It follows the lifecycle of a change, from filing an issue to deploying a release.

These are the automations that are **live today**. The separate, label-gated **AI-assisted pathway** (an AI agent, Coco, run by `gh-aw` that plans a labelled issue, warns about higher-risk traits, and implements it after a human comments `/approve-plan`) is documented in [ai-pathway.md](/docs/ai-pathway.md); this document covers the rest.

For the label vocabulary these automations depend on, see [labels-reference.md](/docs/labels-reference.md). For the day-to-day operational view (what a maintainer watches and tunes), see [maintainers-handbook.md](/docs/maintainers-handbook.md).

How the live automations chain along the lifecycle of a change:

```mermaid theme={null}
flowchart TD
    A["New issue"] -->|"Issue forms"| B["type:* + needs-triage applied"]
    B --> C["PR opened against develop"]
    C -->|"Branch-name check"| D["Naming convention enforced"]
    C -->|"Area labeler"| E["area:* applied from changed paths"]
    C -->|"CI lint + test"| F["ci-complete gate"]
    F --> G["Squash-merge to develop"]
    G -->|"Release Drafter"| H["dev draft notes rebuilt"]
    H -->|"Push dev-vX.Y.Z / vX.Y.Z tag"| I["CI builds, pushes to GHCR, deploys"]
    I -->|"deploy succeeded"| M["Discord announcement from whats-new.json"]
    J["Weekly schedule"] -->|"Stale bot"| K["Long-idle items marked stale, then closed"]
    J -->|"Dependabot"| L["Grouped dependency-update PRs"]
    L --> C
```

## At a glance

| Trigger | Automation | What happens | Config |
| - | - | - | - |
| New issue | Issue forms | Structured form; auto-applies `type:*` + `needs-triage` | [`.github/ISSUE_TEMPLATE/`](https://github.com/petrosrapto/HAICO/blob/main/.github/ISSUE_TEMPLATE) |
| PR opened against `main`/`develop` | Branch-name check | Fails the PR if the head branch breaks the naming convention | [`branch-name.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/branch-name.yml) |
| PR opened/updated | Area labeler | Adds `area:*` labels from the changed paths (add-only) | [`labeler.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/labeler.yml) + [`.github/labeler.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/labeler.yml) |
| Any push or PR | CI lint + test | Frontend build/lint, frontend tests, backend tests, consolidated gate | [`ci.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/ci.yml) |
| Merge to `develop` / `main` | Release Drafter | Rebuilds the running draft release notes for that channel | [`release-drafter-dev.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/release-drafter-dev.yml) / [`-prod.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/release-drafter-prod.yml) |
| Push `dev-vX.Y.Z` / `vX.Y.Z` tag | Build + deploy | Builds images, pushes to GHCR, deploys to the dev/prod server, then announces the release on Discord | [`ci.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/ci.yml) |
| Weekly (Mon 06:00 UTC) | Stale bot | Marks long-inactive issues/PRs stale, later closes them | [`stale.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/stale.yml) |
| Weekly | Dependabot | Opens grouped dependency-update PRs across all ecosystems | [`dependabot.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/dependabot.yml) |

***

## 1. Filing an issue: issue forms

**Trigger:** a user clicks **New issue**.

The [issue chooser](https://github.com/petrosrapto/HAICO/blob/main/.github/ISSUE_TEMPLATE/config.yml) offers four structured forms (Bug, Feature, Docs, Refactor) plus contact links that route usage questions and security reports elsewhere. Blank issues are disabled, so every issue starts from a form. Each form auto-applies its `type:*` label and `needs-triage` (the refactor form also pre-applies `path:traditional`).

**Note:** GitHub reads these forms from the **default branch only**, so they appear in the UI once merged there.

**Config:** [`.github/ISSUE_TEMPLATE/`](https://github.com/petrosrapto/HAICO/blob/main/.github/ISSUE_TEMPLATE).

## 2. Opening a PR: branch-name validation

**Trigger:** a PR is opened, edited, reopened, or synchronized against `main` or `develop`.

The workflow checks the PR's head branch against `<type>/<short-kebab-description>` (allowed types: `feature`, `bugfix`, `fix`, `hotfix`, `release`, `chore`, `refactor`, `docs`, `test`, `experiment`). If it doesn't match, the check fails with a message listing the convention. `main`, `develop`, and bot branches (`dependabot/*`, `renovate/*`, `github-actions/*`) are exempt, so release PRs (`develop → main`) and dependency-bump PRs pass.

**Config:** [`branch-name.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/branch-name.yml). The same rule is enforced locally (opt-in) by [`.githooks/pre-push`](https://github.com/petrosrapto/HAICO/blob/main/.githooks/pre-push).

## 3. Opening a PR: area labeler

**Trigger:** a PR is opened, reopened, synchronized, or marked ready for review.

`actions/labeler` adds `area:*` labels based on which paths the PR touches (`backend/**` → `area:backend`, `docs/**` and `*.md` → `area:docs`, `.github/**` → `area:ci`, and so on). It is **add-only** (`sync-labels: false`), so a label a triager added by hand is never stripped. It runs as `pull_request_target`, so it also works on fork PRs.

**Config:** [`labeler.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/labeler.yml) (workflow) + [`.github/labeler.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/labeler.yml) (path rules).

## 4. Every push or PR: CI (lint + test)

**Trigger:** any push to any branch, and every pull request.

Three jobs run in parallel: frontend build + lint, frontend tests, and backend tests (with coverage). A consolidated **`ci-complete`** gate then passes only if all three succeeded, which is the single status to mark as a required check in branch protection.

**Config:** [`ci.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/ci.yml) (the lint/test jobs and the gate).

## 5. Merges to `develop` / `main`: Release Drafter

**Trigger:** a push (merge) to `develop` or `main` runs the drafter; `pull_request_target` open/edit/sync events against those branches run the autolabeler.

Release Drafter maintains a **continuously-updated draft GitHub Release**: every merged PR is listed, grouped into categories by its `type:*` label, and the next SemVer version is computed from those labels. You never write release notes by hand. Since release-drafter v7 the autolabeler is a separate action (`release-drafter/release-drafter/autolabeler@v7`), so each workflow has two steps: the autolabeler runs on `pull_request_target` and the drafter (the root action) runs on `push`. Two channels run independently so they don't pollute each other:

* **dev channel:** watches `develop`, drafts `dev-vX.Y.Z` notes ([`release-drafter-dev.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/release-drafter-dev.yml)).
* **prod channel:** watches `main`, drafts `vX.Y.Z` notes and lists the published container images ([`release-drafter-prod.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/release-drafter-prod.yml)). Its version is not guessed from labels: a push-only step looks up the `dev-vX.Y.Z` tag that points at the promoted commit and passes that number to the drafter, so the prod draft's tag, title and body carry the version validated on dev (if no dev tag points at `HEAD`, the step warns and the label-based resolver guesses instead).

Three config blocks do the work:

* `categories:` maps each `type:*` label to a notes section through a `when:` condition (`type:enhancement` → "✨ Features", `type:bug` → "🐞 Bug fixes", …), and a `type: pre-exclude` category honours the `skip-changelog` label, dropping such PRs before the notes and the version are computed. This mapping is mirrored in the table in [labels-reference.md §2](/docs/labels-reference.md).
* `semver-increment` on each category chooses the bump (v7 replaced the separate `version-resolver:` block): `breaking`/`type:breaking` → major, `type:enhancement`/`type:refactor` → minor, everything else → patch, with a trailing `type: version-resolver` category as the patch fallback for unlabelled PRs.
* `autolabeler:` (read by the autolabeler action) infers the `type:*` label from a Conventional-Commits PR title (`feat:` → `type:enhancement`, `fix:`/`hotfix:` → `type:bug`, …), so even an unlabelled PR is categorised correctly. This is why the PR template asks for a Conventional-Commits title.

Release Drafter only writes the **draft**; it never deploys, and neither does clicking **Publish** on the draft: no workflow listens for a published release. What builds and deploys is the tag push in step 6; publishing the draft is a separate, manual step, a courtesy for people reading the Releases tab (see [maintainers-handbook.md §4](/docs/maintainers-handbook.md)). The PR-triggered step runs as `pull_request_target` so it reads the workflow from the base branch, and Release Drafter expects its config file to live on the default branch.

**Config:** the two `release-drafter-*.yml` configs and their workflows. Operational detail (keeping the two `autolabeler` blocks in sync, watching the draft) is in [maintainers-handbook.md](/docs/maintainers-handbook.md).

## 6. Pushing a tag: build, push, deploy

**Trigger:** pushing a SemVer tag, `dev-vX.Y.Z` or `vX.Y.Z`, after CI is green.

The same [`ci.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/ci.yml) pipeline resolves the tag to a channel, then builds and ships:

1. **`resolve-release`** classifies the tag: `dev-vX.Y.Z` → the `dev` GitHub Environment; `vX.Y.Z` → the `prod` GitHub Environment.
2. **`build-and-push`** builds the backend and frontend images and pushes them to GHCR, tagged with both the version and the channel tag (`:dev` or `:latest`). It also bakes a build stamp into both images as build args: `APP_VERSION` (the tag's `X.Y.Z`, from `resolve-release`), `APP_CHANNEL` (`dev` or `prod`), `APP_COMMIT` (the tagged SHA) and `APP_BUILT_AT` (UTC ISO-8601, taken once by `resolve-release` so both images carry the same stamp), so every image knows which release it is (`GET /api/version` and the frontend footer report it).
3. **`deploy`** connects to the target server over VPN + SSH, generates `.env` files from the channel's `.env.example` (filling values from GitHub Environment secrets), ships the compose tree, and runs the deploy script. A post-deploy assertion step then polls the freshly deployed stack over SSH (for up to 90 s): the backend's `GET /api/version` and the frontend's served `<html data-app-version data-app-commit>` must both report the tag's version and commit, otherwise the job fails. A required reviewer on the `prod` environment can gate production behind a human.
4. **`release-summary`** writes the published image refs and deploy result to the run summary.
5. **`announce-release`** runs only after `deploy` has succeeded (it `needs` that job) and posts the release to Discord through a channel webhook, via [`.github/scripts/discord-release.sh`](https://github.com/petrosrapto/HAICO/blob/main/.github/scripts/discord-release.sh). The text comes from the committed [`frontend/src/data/whats-new.json`](https://github.com/petrosrapto/HAICO/blob/main/frontend/src/data/whats-new.json), as checked out at the tag, never from the GitHub Release body (still a draft at this point, and the repository is private, so its links would be dead ends for Discord readers). A prod tag posts an embed with that version's entry to `#announcements` (or a one-sentence maintenance notice when the file has no entry for it); its title and its "All updates" line both link to the public `/whats-new` page, which renders the same entries (`NOTES_URL` overrides that target). A dev tag posts a one-line ops note to the dev channel. The webhook URLs are the **repository** secrets `DISCORD_RELEASE_WEBHOOK_URL` and `DISCORD_DEV_WEBHOOK_URL`, repository-level on purpose: the job declares no `environment:`, so an Environment secret would resolve to empty there without an error. When the secret for the channel is unset, the step writes a note to the run summary and exits 0. Each tag is announced once: after a successful post the script creates the git ref `refs/discord-announced/<tag>` (the job's only reason for `contents: write`), and a later run for the same tag sees the ref and skips, which makes a re-run after a transient failure safe. The run summary records the Discord message id.

So the full release chain is: **merge PRs (Release Drafter accumulates notes) → push the tag → `ci.yml` builds, stamps, pushes, deploys, and announces on Discord.** Publishing the draft release is a separate, manual step that never deploys anything and is not what triggers the announcement; the tag push alone is what builds, deploys, and announces.

**Config:** [`ci.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/ci.yml). The branching/release procedure is in [CONTRIBUTING.md](https://github.com/petrosrapto/HAICO/blob/main/CONTRIBUTING.md); the two-branch rationale is [ADR-0001](/docs/adr/0001-two-branch-model.md); the curated-notes rationale is [ADR-0008](/docs/adr/0008-user-facing-release-notes.md).

### 6a. Manual dispatch: re-send a Discord announcement

**Trigger:** manual (`workflow_dispatch`) only.

[`discord-resend.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/discord-resend.yml) runs the same announcement script for a tag you name, outside the release pipeline: to re-post after a Discord outage, to force a corrected post (`force: true` ignores the marker ref), or to smoke-test the payload (`dry_run`, on by default, prints the JSON that would be sent to the run summary and posts nothing). It reads `whats-new.json` as of the tag (the script itself comes from the branch you dispatch from, so tags that predate it still work), or as of `notes_ref` when that input is set, for an entry committed after the tag was cut; a `v*` tag goes to `#announcements`, a `dev-v*` tag to the dev channel. The operational how-to (first-time webhook setup, when to re-send) is in [maintainers-handbook.md §4](/docs/maintainers-handbook.md).

**Config:** [`discord-resend.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/discord-resend.yml) and [`.github/scripts/discord-release.sh`](https://github.com/petrosrapto/HAICO/blob/main/.github/scripts/discord-release.sh).

## 7. Weekly: stale bot

**Trigger:** a weekly schedule (Monday 06:00 UTC), or manual dispatch.

Deliberately conservative: an issue/PR with no activity for **180 days** is labelled `status:stale` and warned, then closed after a further **21 days** unless someone comments. It processes at most 30 items per run, and exempts `blocked`, `help wanted`, `good first issue`, high-priority, and `path:ai-automation` items. This avoids the well-known anti-pattern of an aggressive stale bot driving contributors away.

**Config:** [`stale.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/stale.yml).

## 8. Weekly: Dependabot

**Trigger:** a weekly schedule, per ecosystem.

Dependabot opens dependency-update PRs across eight streams: `github-actions`, `pip` (backend, bumping the compiled, hash-pinned `requirements.txt`), `npm` (frontend), `docker` for the `backend` and `frontend` base images, and `docker-compose` for the `deployment/dev`, `deployment/prod` and `deployment/local` stacks. Minor and patch **version updates** are grouped into one PR per ecosystem and wait out a 7-day cooldown; **security updates** are grouped separately and open as soon as a fix exists. Majors for the framework and toolchain (`next`, `react`, `eslint`, `vitest`, `vite`, `tailwindcss`, the `langgraph` trio, the Node and Python base images) are ignored on purpose and upgraded by hand (see [Dependency updates](https://github.com/petrosrapto/HAICO/blob/main/CONTRIBUTING.md#dependency-updates) in CONTRIBUTING). Each PR is labelled `type:chore` plus the relevant `area:*`, so it flows through the labeler/Release Drafter machinery like any other change. Independently, the `Dependency Audit` job in CI runs `npm audit --omit=dev` and `pip-audit` on every PR and reports high/critical advisories without blocking the merge.

**Config:** [`dependabot.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/dependabot.yml).

***

## How labels tie it all together

A single `type:*` and `area:*` pair drives most of the chain:

* **Issue forms** apply `type:*` at creation.
* **The labeler** applies `area:*` on the PR from changed paths.
* **Release Drafter** reads `type:*` to place the PR in the right notes section and to compute the version bump, falling back to the PR title via `autolabeler`.
* **The stale bot** reads status/priority labels to decide what to leave alone.

Because of this, the label taxonomy in [`.github/labels.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/labels.yml) is the connective tissue, and the [`release-drafter-*.yml` categories](https://github.com/petrosrapto/HAICO/blob/main/.github/release-drafter-dev.yml) and [labeler rules](https://github.com/petrosrapto/HAICO/blob/main/.github/labeler.yml) must stay in sync with it. Apply the taxonomy to a repo with [`.github/scripts/init-labels.sh`](https://github.com/petrosrapto/HAICO/blob/main/.github/scripts/init-labels.sh).

## The AI-assisted pathway

A second, opt-in pathway dispatches labelled issues to a **provider-agnostic AI agent** (run by [`gh-aw`](https://github.github.com/gh-aw/) on the repo's own API key), gated by a `path:ai-automation[-<engine>]` label. The engine is chosen by label: the default `path:ai-automation` (Copilot, needs a seat) or explicit `path:ai-automation-copilot`; `path:ai-automation-claude` and `path:ai-automation-codex` are seat-free. Assigning the `HAI-Coco` bot account to an issue automatically adds `path:ai-automation` (via `coco-assign-label.yml`). Adding the label opens a **draft PR with the plan as its first comment**; `/coco <change>` before `/approve-plan` revises the plan (no code written); `/approve-plan` implements the plan; CI runs; `/promote dev` deploys to dev; `/coco X` after approval iterates on the code; marking the PR ready for review runs Coco's automated review; a human approves and merges. The workflows live in [`.github/workflows/`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows) as `ai-{plan,implement,iterate,review,review-ondemand}-{claude,codex,copilot}.md` (gh-aw sources sharing prompt bodies from [`.github/aw/shared/`](https://github.com/petrosrapto/HAICO/blob/main/.github/aw/shared), compiled to `*.lock.yml`), plus the classic `coco-plan-comment.yml`, `coco-approve-ack.yml`, `coco-assign-label.yml`, `coco-stage-labels.yml`, `promote-dev.yml`, `notify-dev-ready.yml`, and `notify-approved.yml`. A `stage:*` label (managed by `coco-stage-labels.yml`) tracks each AI-pathway issue and PR through the lifecycle, so the Issues and Pull-requests lists show where every item is at a glance. The full how-it-works + setup is in [ai-pathway.md](/docs/ai-pathway.md); the rationale for having two pathways is [ADR-0002](/docs/adr/0002-dual-development-pathways.md).

## Where to go deeper

* [labels-reference.md](/docs/labels-reference.md): the canonical label taxonomy these automations key off.
* [maintainers-handbook.md](/docs/maintainers-handbook.md): the operational view (release channels, what to watch, what to tune).
* [repository-foundation.md §11](/docs/repository-foundation.md): how the labeler, Release Drafter, and stale bot are wired (config listings).
* [ai-pathway.md](/docs/ai-pathway.md): the as-built AI-assisted pathway (gh-aw, provider-agnostic).
* [github-projects-copilot-automation.md](/docs/github-projects-copilot-automation.md): the original Copilot-based design, kept as background.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.