# CI/CD Workflows

AIquila uses GitHub Actions for continuous integration and deployment.

## Workflows Overview

| Workflow | Trigger | Purpose |
|----------|---------|---------|
| `test.yml` | Push/PR to main | Run tests |
| `lint.yml` | Push/PR to main | Code quality checks |
| `mcp-release.yml` | Version change in `mcp-server/` | Auto-release MCP server (GitHub Release + Docker + npm + MCP Registry) |
| `nc-release.yml` | Version change in `nextcloud-app/` | Auto-release & publish NC app (nightly + stable) |
| `hetzner-release.yml` | Version change in `hetzner/` | Auto-release Hetzner CLI (GitHub Release + cosign) |
| `hetzner-integration-test.yml` | Manual dispatch | Full two-server E2E integration test |

## Test Workflow (`test.yml`)

Runs on every push and pull request to `main`.

**MCP Server:**
- Installs dependencies
- Type checks with TypeScript
- Runs Vitest tests
- Builds the project

**Nextcloud App:**
- Sets up PHP 8.4
- Installs Composer dependencies
- Runs PHPUnit tests

## Lint Workflow (`lint.yml`)

Ensures code quality on every push and PR.

**Checks:**
- ESLint for TypeScript errors
- Prettier for formatting consistency

**Fix locally before pushing:**
```bash
cd mcp-server
npm run lint:fix
npm run format
```

## Automated Release Workflows

AIquila uses automated release workflows that trigger when you bump the version number and push to `main`.

### MCP Server Release (`mcp-release.yml`)

**Triggers:** Automatically when version changes in `mcp-server/package.json` on push to `main`

**What it does:**
1. Detects version change in `package.json`
2. Runs tests and builds the project
3. Creates tag `mcp-vX.X.X`
4. Packages: `dist/`, `package.json`, `README.md`
5. Creates GitHub release with `aiquila-mcp-vX.X.X.tar.gz`
6. In parallel (after release):
   - Pushes Docker image to GHCR (`ghcr.io/elgorro/aiquila-mcp`)
   - Publishes npm package (`aiquila-mcp` on npmjs.com)
   - Publishes to MCP Registry (`io.github.elgorro/aiquila-mcp`)

**How to release:**
```bash
# 1. Update version in mcp-server/package.json
vim mcp-server/package.json  # Change version to 0.1.1

# 2. Commit and push to main
git add mcp-server/package.json
git commit -m "Bump MCP server to v0.1.1"
git push origin main

# 3. Workflow runs automatically and creates:
#    - Tag: mcp-v0.1.1
#    - GitHub Release with tarball
```

### Nextcloud App Release (`nc-release.yml`)

**Triggers:** Automatically when version changes in `nextcloud-app/appinfo/info.xml` on push to `main`

**What it does:**
1. Detects version change in `info.xml`
2. Runs composer tests
3. Builds frontend (`npm run build`)
4. Creates tag `nc-vX.X.X`
5. Packages app (excludes: tests/, vendor/, node_modules/, src/)
6. Signs package (if `NC_SIGN_KEY` secret exists)
7. Creates GitHub release with `aiquila.tar.gz` and signature
8. **Publishes nightly** to the Nextcloud App Store beta channel (`"nightly": true`) — no approval needed
9. **Waits for manual approval** (via GitHub environment protection)
10. Publishes stable release to Nextcloud App Store

**How to release:**
```bash
# 1. Update version in BOTH files
vim nextcloud-app/appinfo/info.xml     # Change <version>0.1.1</version>
vim nextcloud-app/package.json         # Change version to 0.1.1

# 2. Update CHANGELOG.md
vim nextcloud-app/CHANGELOG.md

# 3. Commit and push to main
git add nextcloud-app/
git commit -m "Bump Nextcloud app to v0.1.1"
git push origin main

# 4. Workflow runs automatically:
#    - Creates tag: nc-v0.1.1
#    - Creates GitHub Release
#    - Publishes nightly to App Store beta channel (automatic)
#    - Waits for your approval to publish stable to App Store

# 5. Approve stable app store publishing:
#    Go to: https://github.com/YOUR-REPO/actions
#    Click on the workflow run
#    Click "Review deployments" and approve "nextcloud-appstore"
```

### Hetzner CLI Release (`hetzner-release.yml`)

**Triggers:** Automatically when version changes in `hetzner/VERSION` on push to `main`

**What it does:**
1. Detects version change in `hetzner/VERSION`
2. Builds Linux amd64/arm64 Go binaries
3. Generates SHA-256 checksums
4. Signs all artifacts with cosign (GitHub OIDC — no secrets needed)
5. Creates tag `hetzner-vX.X.X` and GitHub Release

**How to release:**
```bash
# 1. Bump version in hetzner/VERSION
echo "1.2.0" > hetzner/VERSION

# 2. Commit and push to main
git add hetzner/VERSION
git commit -m "chore(hetzner): bump CLI to v1.2.0"
git push origin main

# 3. Workflow runs automatically and creates:
#    - Tag: hetzner-v1.2.0
#    - GitHub Release with signed binaries + checksums
```

### Hetzner Integration Test (`hetzner-integration-test.yml`)

**Triggers:** Manual dispatch (`workflow_dispatch`)

**What it does:**
1. Provisions two Hetzner cloud servers:
   - Nextcloud server (`cpx21`) — Nextcloud with the AIquila app installed
   - MCP server (`cpx11`) — MCP server pointing at the Nextcloud instance
2. Runs 6 test groups: `oauth`, `tools`, `mcp_protocol`, `nc_app`, `connector` (off by default), `infra`
3. Always destroys servers on completion (orphan cleanup ensures no leaked resources)

**Required secrets:** `HCLOUD_TOKEN`, `ANTHROPIC_API_KEY`

See [`docs/hetzner/ci-flow.md`](../hetzner/ci-flow.md) for the detailed timeline and cost breakdown.

### Secrets

#### For MCP Server Publishing (One-time setup — no secrets required)

Both the npm publish and MCP Registry publish jobs authenticate via **GitHub OIDC** — no
long-lived tokens to store or rotate.

**npm Trusted Publisher (one-time setup on npmjs.com)**
- Go to: [npmjs.com](https://www.npmjs.com) → sign in → package `aiquila-mcp` → Settings →
  Trusted Publishers → Add a publisher
- Provider: **GitHub Actions**
- Repository: `elgorro/aiquila`
- Workflow filename: `mcp-release.yml`
- Environment: *(leave blank)*

Once configured, the `npm-publish` job authenticates via GitHub OIDC automatically.
No token to rotate or store.

The MCP Registry publish step also uses **GitHub OIDC** (`id-token: write` permission) — no extra secret needed.

### Setup Required GitHub Secrets

Navigate to: **Repository Settings → Secrets and variables → Actions → New repository secret**

#### For Nextcloud App Signing (Required)

1. **`NC_SIGN_KEY`** - Your private key content
   ```bash
   # Copy your private key content
   cat ~/.nextcloud/certificates/aiquila.key
   # Paste entire content (including BEGIN/END lines) into secret
   ```

2. **`NC_SIGN_CERT`** - Your certificate content (if needed)
   ```bash
   cat ~/.nextcloud/certificates/aiquila.crt
   ```

#### For Nextcloud App Store Publishing (Required for auto-publish)

3. **`NEXTCLOUD_APPSTORE_USERNAME`** - Your app store username

4. **`NEXTCLOUD_APPSTORE_TOKEN`** - Your app store API token
   - Get from: [apps.nextcloud.com](https://apps.nextcloud.com) → Account Settings → API Token

#### For Hetzner Integration Test (Required for E2E tests)

5. **`HCLOUD_TOKEN`** — Hetzner Cloud API token (used to provision/destroy test servers and DNS)

6. **`ANTHROPIC_API_KEY`** — Anthropic API key (used by the connector test group)

### Setup Manual Approval Gate

To enable manual approval before publishing the **stable** release to the app store
(nightly publishes automatically without approval):

1. Go to **Repository Settings → Environments**
2. Create new environment: `nextcloud-appstore`
3. Enable "Required reviewers"
4. Add yourself as a required reviewer
5. Save

Now the workflow will publish to the nightly/beta channel automatically, then wait
for your approval before publishing the stable release.

### Version Requirements

**Keep versions in sync:**
- MCP Server: Update `mcp-server/package.json`
- Nextcloud App: Update BOTH:
  - `nextcloud-app/appinfo/info.xml` (e.g., `<version>0.1.1</version>`)
  - `nextcloud-app/package.json` (e.g., `"version": "0.1.1"`)

## Local Development

### Running Tests Locally

**MCP Server:**
```bash
cd mcp-server
npm test           # Run once
npm run test:watch # Watch mode
```

**Nextcloud App:**
```bash
cd nextcloud-app
composer install
composer test
```

### Running Linters Locally

```bash
cd mcp-server
npm run lint       # Check for errors
npm run lint:fix   # Auto-fix errors
npm run format     # Format with Prettier
```

### Pre-commit Checklist

Before pushing:
1. `npm run lint` passes
2. `npm test` passes
3. `npx tsc --noEmit` passes (no type errors)

## Troubleshooting

### Tests Failing in CI

- Check Node.js version matches (24)
- Check PHP version matches (8.4)
- Run tests locally to reproduce

### Lint Errors

```bash
# See what would change
npx prettier --check src/

# Auto-fix
npm run lint:fix && npm run format
```

### Release Not Triggering

- Ensure version actually changed in the correct file:
  - MCP: `mcp-server/package.json`
  - Nextcloud: `nextcloud-app/appinfo/info.xml`
- Check you pushed to `main` branch
- Check workflow runs in Actions tab
- Ensure changes are in the correct directory path
- Workflow ignores changes to `*.md` files and `tests/` directories

### npm Publish Fails

- Verify the **Trusted Publisher** is configured on npmjs.com for the `aiquila-mcp` package
  (see "For MCP Server Publishing" under Secrets above)
- If the job reports a 401 or "no authentication" error, confirm `permissions: id-token: write`
  is present in the `npm-publish` job and the Trusted Publisher entry matches the repository
  and workflow filename exactly
- Check that the version being published doesn't already exist on npmjs.com (re-publishing the same version fails)
- Run `npm pack` locally in `mcp-server/` to confirm the package builds cleanly before pushing

### MCP Registry Publish Fails

- The job uses GitHub OIDC (`id-token: write`) — no extra secret required, but the repository must be in the `io.github.elgorro` namespace for the OIDC claim to match
- If `mcp-publisher login github-oidc` fails, check that the job's `permissions` block includes `id-token: write`
- If `mcp-publisher publish` reports a version conflict, confirm `server.json` version update step ran successfully in the workflow logs

### App Store Publishing Fails

- Verify secrets are set: `NC_SIGN_KEY`, `NEXTCLOUD_APPSTORE_USERNAME`, `NEXTCLOUD_APPSTORE_TOKEN`
- Check signature is valid (download .tar.gz and .sig, verify locally)
- Ensure app is registered at apps.nextcloud.com
- Check app store API response in workflow logs

## Customization

### Adding New Test Jobs

Edit `.github/workflows/test.yml`:

```yaml
jobs:
  new-job:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # Add your steps
```

### Changing Node.js Version

Update in all workflows:
```yaml
- name: Setup Node.js
  uses: actions/setup-node@v4
  with:
    node-version: '22'  # Change version here
```
