> ## 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.

# Maintainers handbook

> Triage, label discipline, review, and the release flow for maintainers.

# Maintainer's handbook

This handbook is for HAI-Co² maintainers: the people with write access to the repository who triage issues, review PRs, cut releases, and make the judgement calls that keep the project moving. If you are an external contributor, the document you want is [community-guide.md](/docs/community-guide.md).

This is reference material, not a runbook for one-off setup. For setup instructions, see [`docs/repository-foundation.md`](/docs/repository-foundation).

***

## 1. The triage loop

### Cadence

The metric contributors notice is: **every issue triaged within 7 days of being opened.**

### The Triage view

Open the *Triage* view on the [community Project board](https://github.com/petrosrapto/HAICO/projects); it filters to `Status = Backlog AND label = needs-triage`. Walk the queue top-to-bottom.

### For each issue

1. **Read it.** If it's a duplicate or wontfix → label and close with a kind message.
2. **Under-specified?** Add `needs-info`, ask the question, await reply. Set a calendar reminder to follow up if the reporter goes silent for 14 days.
3. **Otherwise, apply labels:**
   * Exactly one `type:*` (`type:bug`, `type:enhancement`, …).
   * Exactly one `area:*`.
   * One `priority:*` and one `effort:*`.
   * **Pathway decision** (see §2 below).
   * `good first issue` if a newcomer can finish it in under 2 hours without reading internal docs.
4. **Add to a milestone** if the issue is on the near-term roadmap.
5. **Remove `needs-triage`.**

### Pathway decision

The pathway decision is the most consequential triage call. See the [pathway guidance](/docs/repository-foundation.md#2-what-trivial-means--when-to-use-the-ai-pathway) and [ADR-0002](/docs/adr/0002-dual-development-pathways.md). The label is the entry signal, not a hard gate: once you apply `path:ai-automation`, Coco plans the issue, warns in the plan about any higher-risk traits, and you authorize the work by commenting `/approve-plan` on the PR.

Route an issue to the AI pathway when it is well-scoped and you are comfortable reviewing Coco's diff. The plan surfaces these so you can weigh them before approving:

* Size and layer span.
* DB schema changes or migrations.
* New runtime dependencies.
* Public-API contract changes.
* Whether an architectural decision is involved (an ADR may be warranted).

Coco raises a ⚠️ warning in the plan only for a security-sensitive surface (auth, crypto, uploads, deserialization) or a destructive or irreversible migration. A warning is informational: approve with `/approve-plan` to implement, or relabel `path:traditional` to send it to a human. Prefer traditional when the design is the hard part, or when you would not want an agent authoring the change even with a warning.

**Pick the engine with the label.** Apply exactly one pathway label: `path:ai-automation` (or `path:ai-automation-copilot`) runs **Copilot** (default; requires a Copilot seat); `path:ai-automation-claude` runs **Claude** (seat-free); `path:ai-automation-codex` runs **Codex / OpenAI** (seat-free). The chosen engine drives the whole PR through. See [ai-pathway.md](/docs/ai-pathway.md) §2.4 and §2.6.

***

## 2. Labels: what each one means

The full taxonomy lives in [`docs/labels-reference.md`](/docs/labels-reference) and is applied by [`.github/scripts/init-labels.sh`](https://github.com/petrosrapto/HAICO/blob/main/.github/scripts/init-labels.sh). The short version for triage:

* **Every issue gets exactly one `type:` label.**
* **Every issue gets exactly one `area:` label.** (Labeler workflow auto-applies the area on PRs from changed paths.)
* **`needs-triage` must be removed once triage is done.** This is the signal to the rest of the world that the issue is actionable.
* **`good first issue` is sacred.** Reserve it for issues a newcomer can finish in under 2 hours without reading internal docs. Putting it on harder issues kills trust faster than anything else.
* **`path:ai-automation` (or an engine-specific `path:ai-automation-{claude,codex,copilot}`) is added explicitly by you, or automatically when you assign `HAI-Coco` to the issue.** Default is traditional (absence of any `path:*` label means traditional). The bare label is the Copilot alias (default engine; requires a seat).
* **`stage:*` labels are managed automatically** by [`coco-stage-labels.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/coco-stage-labels.yml); never set them by hand. Each AI-pathway issue and PR shows its lifecycle stage (`stage:planning` … `stage:awaiting-merge`) in the Issues and Pull-requests lists, so you can see where everything is at a glance; warm colors mean it is your turn to act. See the state diagram in [ai-pathway.md](/docs/ai-pathway.md).

***

## 3. Reviewing PRs

### The same bar for both pathways

A PR's review effort does not depend on its author. Coco is bounded by **human approval** (`/approve-plan`), not by a triviality gate: the review bar stays constant regardless of who authored the change; see [ADR-0002](/docs/adr/0002-dual-development-pathways.md).

### What to check

* **Target branch is `develop`.** PRs to `main` are release promotions only.
* **PR title is Conventional Commits.** Reject anything else, since the title becomes the squash-commit message and the Release Drafter category.
* **`Closes #...` in the body.** Without this, the issue does not auto-close.
* **CI is green.** Lint, tests, build.
* **CODEOWNERS approval.** You are usually the CODEOWNER; review accordingly.
* **No secrets committed.** Push protection catches the obvious ones; you catch the rest.
* **ADR for architectural changes.** If the diff changes how modules compose, how data flows, or which framework owns what, ask for an ADR in the same PR. See [`docs/adr/README.md`](/docs/adr/index).

### Responding to Coco

On `path:ai-automation` PRs, the reviewer iterates with Coco using the `/coco` slash command (gh-aw has no `@mention` trigger):

```
/coco the dev container fails health-check at /api/health.
The cause looks like a missing env var. Please add a default to settings.py
and a test that covers the missing-env path.
```

Coco picks up the command, treats the entire PR thread + AGENTS.md as context, and pushes new commits to the PR branch (which re-runs CI).
These iteration pushes do not trigger a review; when you are satisfied, mark the PR **Ready for review** to run Coco's automated review once (or comment `/review` for an extra pass on demand).

If a Coco PR repeatedly misses the mark, the change may be a poor fit for an agent: re-label it `path:traditional` and assign a human implementer (or do it yourself).

### Merging

* **Squash merge.** Always. The PR title becomes the commit; the body appears below.
* The branch auto-deletes.
* The linked issue auto-closes on merge into `develop`.

***

## 4. Releases

### Two channels, one CI workflow

| Tag pattern | Built from | Deploys to | Release Drafter config |
| - | - | - | - |
| `dev-vX.Y.Z` | `develop` | Dev environment | [`.github/release-drafter-dev.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/release-drafter-dev.yml) |
| `vX.Y.Z` | `main` | Production | [`.github/release-drafter-prod.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/release-drafter-prod.yml) |

[`ci.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/ci.yml) handles both: tag-driven.

The end-to-end release flow, from a merged PR to production:

```mermaid theme={null}
flowchart TD
    A["PRs squash-merged to develop"] --> B["dev draft release accumulates"]
    B --> W["Optional: add the whats-new.json entry on develop"]
    W --> C["Push dev-vX.Y.Z tag from develop"]
    C --> D["ci.yml builds :dev images, runs dev deploy"]
    D --> E{"Validated on dev?"}
    E -->|"no"| A
    E -->|"yes"| F["Fast-forward develop to main (git merge --ff-only)"]
    F --> G["Push main, then push vX.Y.Z tag"]
    G --> H["ci.yml builds :latest images, runs prod deploy"]
    H --> I["Discord #announcements post (automatic)"]
    H -.->|"optional, manual, never deploys"| J["Publish prod draft"]
    H -.->|"optional, manual"| K["Open Announcement discussion"]
```

### Cutting a dev release

1. The dev draft release accumulates as PRs merge to `develop`. Review the
   draft at [Releases → Draft (dev)](https://github.com/petrosrapto/HAICO/releases)
   for any title cleanups. The version in its title is a suggestion that
   Release Drafter computes from the merged PRs' `type:*` labels; the tag you
   push is what counts.

2. From `develop`, push the next dev tag:

   ```bash theme={null}
   git checkout develop
   git pull
   git tag dev-v0.3.1
   git push origin dev-v0.3.1
   ```

3. The tag push triggers `ci.yml`, which builds and pushes the `:dev` +
   `:X.Y.Z` images (stamped with that version), runs the dev deploy job, and
   asserts that the running stack reports that version.

4. Optionally, publish the draft release (its `tag-template` matches your
   pushed tag; if you tagged a different number, pick the existing tag in the
   release dropdown). This is a courtesy for people reading the Releases tab:
   it is not needed for deployment and never triggers one.

### Curated release notes (optional, before the prod tag)

Release Drafter's notes are written for contributors: PR titles, labels,
compare links. What the HAI-Co² community is told lives in a different file,
[`frontend/src/data/whats-new.json`](https://github.com/petrosrapto/HAICO/blob/main/frontend/src/data/whats-new.json):
hand-written, newest first, at most one entry per prod release. It is the
single source for three surfaces that all read the same entries: the Discord
announcement `ci.yml` posts after a successful prod deploy, the in-app "What's
new" dialog (which opens itself once per release for signed-in users and is
always available from the notification bell), and the public
[`/whats-new`](https://haico.gr/whats-new) page the announcement links to (see
[ADR-0008](/docs/adr/0008-user-facing-release-notes.md)). Writing the entry is
therefore what tells the community what shipped, not just Discord readers.

Before you cut a prod release with a Studio change or research milestone worth announcing:

1. Scaffold the entry. The script prints the commit subjects since the last
   prod tag, grouped by Conventional-Commit type, as a reminder of what
   shipped, then a skeleton entry for the version you name. With `--write` it
   prepends that skeleton to the JSON instead of printing it:

   ```bash theme={null}
   node .github/scripts/whats-new.mjs 1.4.0            # print only
   node .github/scripts/whats-new.mjs 1.4.0 --write    # prepend to the JSON
   ```

2. Write one to three plain sentences per item, one item per thing worth
   announcing (at most five per entry), each tagged with a `kind`:
   `added` (something new), `improved` (existing behaviour got better),
   `fixed` (a bug users could hit), `changed` (behaviour differs and users may
   need to adjust), `removed` (something is gone). No markdown, no `#123`
   issue refs, no `@handles`: the text is rendered outside GitHub. An optional
   `href` must be an absolute `https://` link, typically a page on haico.gr.
   Set `scope: "research"` for researcher or operator tooling that is not in
   the Studio; it is then visibly labelled Research in the app, public page,
   and Discord, and its text must say that it is not a Studio workflow.
   Give the entry a short title; the Discord embed is titled
   `Co-Construction Studio vX.Y.Z: <title>`. `npm test` in `frontend/` runs the
   schema check (`whats-new.test.ts`) that CI also runs.

3. Add **nothing** for a release with nothing worth announcing (dependency
   bumps, internal refactors). The announcement then says so in one neutral
   sentence instead of inventing a changelog.

The entry is an ordinary change on `develop`, so land it before the
`dev-vX.Y.Z` tag you intend to promote: the prod tag must point at the
validated dev commit.

### Cutting a production release

1. The prod draft release accumulates as `develop` is fast-forwarded into
   `main`. Review it. Its proposed version is derived from the `dev-vX.Y.Z`
   tag on the promoted commit, so it already carries the number you validated
   on dev.

2. Promote `develop → main` and push the prod tag. **Version rule:** the prod
   tag carries the same number as the validated dev tag (`v0.3.1` and
   `dev-v0.3.1` point at the same commit):

   ```bash theme={null}
   git checkout main
   git merge --ff-only develop       # locally; or via a PR develop → main
   git push origin main
   git tag v0.3.1
   git push origin v0.3.1
   ```

3. The tag push triggers `ci.yml`, which builds and pushes the `:latest` +
   `:X.Y.Z` images, runs the prod deploy job, and asserts that the running
   backend (`/api/version`) and frontend (`<html data-app-*>`) report the
   tag's version and commit.

4. Optionally, publish the prod draft release. This is a courtesy for people
   reading the Releases tab: it is not needed for deployment and never
   triggers one. If you ever choose a different number from the one the
   draft proposes, pick the existing tag in the release dropdown and make the
   draft's body follow (its compare link and image refs were rendered with the
   derived number).

5. **The Discord announcement posts itself.** Once the prod deploy job has
   succeeded, the `announce-release` job in `ci.yml` reads the entry for this
   version from `frontend/src/data/whats-new.json` (as committed at the tag)
   and posts it to `#announcements` on the "Co-Construction Studio" Discord
   server through a channel webhook, linking to `https://haico.gr/whats-new`;
   a dev tag posts a one-line ops note to the dev channel instead. Each tag is
   announced once: after a successful post the job records a marker ref
   (`refs/discord-announced/<tag>`), so a re-run of the workflow does not post
   twice. The job's run summary shows the message id (and the `PATCH` URL for
   editing that message later) or, when the version had no entry, a ⚠️ note
   that the neutral maintenance wording went out. A webhook cannot crosspost to
   following servers; if you want that, it stays a manual click in Discord. Opening a 📣 Announcement
   discussion remains an optional extra for people who follow the repository
   rather than the Discord.

### Re-sending an announcement

[`discord-resend.yml`](https://github.com/petrosrapto/HAICO/blob/main/.github/workflows/discord-resend.yml)
is the manual escape hatch: **Actions → "Re-send a Discord release
announcement" → Run workflow**, give it the tag (`v1.4.0` or `dev-v1.4.0`)
and leave `dry_run` on to see the payload in the run summary without posting
anything. It reads `whats-new.json` as committed at that tag (the script
itself runs from the branch you dispatch from, so tags older than the script
work too); set `notes_ref` (for example `develop`) to read the file from
another ref instead. Use it when:

* the `announce-release` job failed (Discord outage, an expired webhook) and
  you do not want to re-run the whole release workflow: run it with `dry_run`
  off. The marker ref is only written after a successful post, so it posts
  normally.
* a message went out wrong and you deleted it in Discord: run it with
  `force` on, which ignores the marker ref and posts again. It always posts a
  new message, never edits the old one, and the content is whatever the tag
  carries unless you point `notes_ref` elsewhere; to change the words, edit
  the message in place with the `PATCH` URL from the original run summary,
  or carry the correction into the next release's entry.
* a prod tag went out as the maintenance notice because `whats-new.json` had
  no entry for it: commit the entry on `develop`, then run with `force` on
  and `notes_ref` set to `develop`, so the post uses the entry you just
  committed rather than the file as of the tag.
* you want to see what a payload looks like: a `v*` tag with `dry_run` on
  shows the prod embed in the run summary without posting; a `dev-v*` tag
  with `dry_run` off posts the short dev line to the dev channel.

### First-time setup

1. In Discord, open `#announcements` → **Channel settings → Integrations →
   Webhooks → New Webhook**, name it "Co-Construction Studio" and copy the
   webhook URL.
2. Store it as the **repository** secret `DISCORD_RELEASE_WEBHOOK_URL`
   (Settings → Secrets and variables → Actions → Repository secrets). It must
   be a repository secret, not an Environment secret: the announce job runs
   outside the `dev`/`prod` Environments, where an Environment secret resolves
   to empty without any error. Optionally repeat for a private dev channel
   (`#dev-builds`) as `DISCORD_DEV_WEBHOOK_URL`; without it, dev tags simply
   skip the post.
3. Before the first prod tag relies on it, run the re-send workflow once with
   `dry_run` on (any existing tag) to check the payload, then once for real
   against a `dev-v*` tag (needs `DISCORD_DEV_WEBHOOK_URL`) to watch a message
   land in the dev channel.

Until the secrets exist, the job exits green with a note in the run summary
saying that no webhook is configured.

### Why two Drafters

Release Drafter only watches one branch. Two configs and two workflows let us have running notes for both channels without one polluting the other. Since release-drafter v7 the autolabeler is its own action, so each workflow gates its steps on the event: the autolabeler runs on `pull_request_target` and the drafter on `push` (the prod workflow first derives the version from the dev tag, see above). Keep both configs in sync, especially the `autolabeler` rules.

### Identifying a running build

`ci.yml` stamps both images with the tag they were built from (the
`APP_VERSION`, `APP_CHANNEL`, `APP_COMMIT` and `APP_BUILT_AT` build args), and
the stamp surfaces in several places:

* `GET https://haico.gr/api/version` (and `https://dev.haico.gr/api/version`)
  returns `{name, version, channel, commit, built_at, api_contract}`.
* Every backend response carries `X-App-Version` and `X-App-Commit` headers.
* The site footer shows a version chip (`vX.Y.Z`; hover it for the channel and
  short commit, click it for `/whats-new`). An image built outside CI shows
  `dev build` instead of a number.
* On the dev environment the navbar shows a `DEV` chip beside the wordmark and
  the browser tab title is prefixed `[DEV]`.
* Inside the Studio (`/app`), which has no footer, the version and short commit
  sit at the foot of the account menu.
* The notification bell (signed in) has a permanent "What's new" item showing
  the same version label; opening it shows the release notes for that build.
  `https://haico.gr/whats-new` lists the same history and is public, so a
  reporter without an account can still say which release they mean.

When someone reports a bug, ask for the version line from the account menu (or
the footer chip) and which host they were on; together with the short commit
that pins the report to one image and one tag. `curl -s https://haico.gr/api/version`
gives you the same answer from the server side.

***

## 5. ADRs: the bright line

If a change *needs* an [Architecture Decision Record](/docs/adr/index.md), the issue is `path:traditional` by construction. Coco does not propose architecture.

Write an ADR when:

* The decision shapes the structure of more than one module or service.
* The decision picks one of several reasonable approaches, and a future contributor would reasonably ask *"why this?"*.
* The decision constrains future work (framework, database, wire format, topology).
* The decision touches the dual-pathway model, the release workflow, or any governance question.

Don't ADR cosmetic choices, reversible-in-a-day decisions, or things the code adequately documents.

To override an existing ADR: open a new one, link forward, and change the old ADR's `status:` to `superseded by NNNN-...`. **Do not edit the old decision**, since that is the historical record.

***

## 6. Reading the Project board

The community Project board has at minimum these views (defined in [`docs/repository-foundation.md` §6.10](/docs/repository-foundation)):

| View | Filter | Audience |
| - | - | - |
| **Roadmap** (default) | `Status in (Backlog, In Progress)`, grouped by milestone | Visitors, contributors browsing |
| **Triage** | `Status = Backlog AND label = needs-triage` | Maintainers, weekly triage |
| **Good first issues** | `label = "good first issue" AND Status = Backlog` | First-time contributors |
| **In progress** | `Status = In Progress` | Maintainers, daily glance |

Add the richer views (`Ready for AI`, `On Dev`, `Approved`) only when you turn on the AI-automation pathway. See [`docs/ai-pathway.md`](/docs/ai-pathway).

***

## 7. The community standards page

GitHub maintains an [Insights → Community Standards](https://github.com/petrosrapto/HAICO/community) page listing the files it considers community-essential. Keep every box green:

* Description.
* README.
* Code of Conduct.
* Contributing.
* License.
* Security policy.
* Issue templates.
* PR template.

Visitors look at this page once the repo is public; an unticked box is a signal that the project is not yet serious. The page is publicly visible.

***

## 8. Recurring chores

The CI-runs-it-for-you ones:

* **Dependabot**: security-update PRs arrive as soon as a fix exists, one PR per vulnerable package: squash-merge them the week they appear once CI is green. Weekly grouped minor/patch PRs are routine; majors for the framework and toolchain are ignored by `dependabot.yml` and done by hand (see [Dependency updates](https://github.com/petrosrapto/HAICO/blob/main/CONTRIBUTING.md#dependency-updates)). The non-blocking `Dependency Audit` CI job flags new advisories on every PR.
* **Release Drafter (dev/prod)**: runs on every merge; keep an eye on the draft, fix titles before publishing.
* **Labeler**: runs on every PR open; auto-applies `area:*`.
* **Stale bot**: Monday 06:00 UTC weekly; conservative (180-day idle, 21-day warning). Watch the first run after enabling and confirm nothing important was swept.
* **CodeQL + Autofix**: runs on PRs; surface findings in Security tab.
* **Discord announcement**: `announce-release` in `ci.yml` posts to `#announcements` after every successful prod deploy, from `frontend/src/data/whats-new.json`. If nothing appeared, read that job's run summary (no webhook configured, already announced, or a Discord error).

The human-driven ones:

* Weekly triage (§1).
* Monthly: glance at the Discussions tab for unanswered Q\&A.
* Quarterly: re-read [`docs/repository-foundation.md`](/docs/repository-foundation) and tune the issue forms based on which fields contributors skip.
* On release: add the `whats-new.json` entry when there is a Studio update or research milestone worth announcing, cut tags, optionally publish the drafts. The Discord announcement is automatic.

***

## 9. Sources and related docs

* [`docs/repository-foundation.md`](/docs/repository-foundation): the long-form setup guide; rationale for every convention here.
* [`docs/community-guide.md`](/docs/community-guide): the contributor-facing view of the same conventions.
* [`docs/labels-reference.md`](/docs/labels-reference): full taxonomy.
* [`docs/adr/README.md`](/docs/adr/index): how ADRs work.
* [`docs/going-public.md`](https://github.com/petrosrapto/HAICO/blob/main/docs/going-public.md): public-launch checklist.
* [`docs/ai-pathway.md`](/docs/ai-pathway): the as-built AI-pathway automation (Coco, gh-aw). [`docs/github-projects-copilot-automation.md`](/docs/github-projects-copilot-automation) is the original Copilot-based design, kept as background.
* [`MAINTAINERS.md`](https://github.com/petrosrapto/HAICO/blob/main/MAINTAINERS.md): who maintains what and how to add a maintainer.


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