---
name: smaqit.infrastructure-cicd-generate
description: Use when manually generating or updating the canonical 3-workflow GitHub Actions CI/CD set (deploy.yml, provision.yml, post-merge-deploy.yml) for a Node.js + React application deployed to a VM via Terraform and Docker Compose. Also use when an operator invokes /cicd.generate, or when updating existing workflows to match project conventions. In `smaqit.new-greenfield-project`, the deployment agent generates workflows during Phase 5 using these patterns as reference — invoke this skill directly only when generating CI/CD outside the zero-to-prod flow. Note: `copilot-setup-steps.yml` is provided by the smaqit framework and is not generated by this skill.
metadata:
  version: "1.0.0"
---

# CI/CD Workflow Generator

## Steps

1. **Read existing workflows** from `.github/workflows/`. If workflows exist and are substantially complete, offer to diff rather than overwrite.

2. **Resolve configuration values** from Infrastructure specs and `copilot-instructions.md`. Use these defaults if not found:

   | Reference | Default |
   |-----------|---------|
   | VM host | `${{ secrets.VM_HOST }}` |
   | SSH key secret | `VM_SSH_KEY` |
   | Terraform backend | `TF_BACKEND_ACCESS_KEY`, `TF_BACKEND_SECRET_KEY` |
   | Cloud credentials | `TF_VAR_APP_CREDENTIAL_ID`, `TF_VAR_APP_CREDENTIAL_SECRET` |
   | GitHub token (Terraform) | `GH_TERRAFORM_TOKEN` |
   | Terraform path filter | `deployment/terraform/**` |

3. **Generate `deploy.yml`** with two sequential jobs:
   - `provision` job: runs `terraform apply`. Env MUST include `TF_VAR_github_token: ${{ secrets.GH_TERRAFORM_TOKEN }}` — NOT `GITHUB_TOKEN` (reserved; runner overwrites it before steps execute).
   - `deploy` job: `needs: [provision]`. Steps in order: checkout → setup Node.js → install deps → build backend → build frontend (with `VITE_DEMO_MODE: ${{ vars.VITE_DEMO_MODE }}` on the build step) → rsync backend dist → rsync frontend dist → install node_modules on VM via Docker → write deploy stamps → docker compose restart → nginx reload.

4. **Generate `provision.yml`** with:
   - Trigger: `pull_request`, paths filter `deployment/terraform/**`
   - Single `plan` job: `terraform plan` only — NO `terraform apply`
   - Posts plan output as a PR comment
   - Same env vars as the `provision` job in `deploy.yml`, including `TF_VAR_github_token`

5. **Generate `post-merge-deploy.yml`** with:
   - Trigger: `pull_request`, `types: [closed]`
   - Condition: `if: github.event.pull_request.merged == true && contains(github.event.pull_request.body, 'smaqit:deploy')`
   - Action: trigger `deploy.yml` via `workflow_dispatch`
   - Do NOT use a label trigger — Copilot Coding Agent tokens lack `issues:write` and cannot apply labels.

6. **Write all 3 files to `.github/workflows/`**.

7. **Report** the list of files written with a brief description of each.

## Output

- `.github/workflows/deploy.yml` — provision + deploy sequential jobs
- `.github/workflows/provision.yml` — PR-only terraform plan with PR comment
- `.github/workflows/post-merge-deploy.yml` — body sentinel trigger for deploy.yml

## Scope

- Does NOT set up GitHub Secrets or Variables — use `smaqit.infrastructure-repo-config` for that.
- Does NOT provision infrastructure — Terraform runs inside the generated workflow.
- Does NOT handle matrix builds, monorepos, or container registry pushes.

## Examples

**Input:** New project with no CI/CD workflows. Operator invokes `/cicd.generate`.
**Output:** 4 files written to `.github/workflows/`. `deploy.yml` has sequential provision→deploy jobs; `provision.yml` is PR-only plan with PR comment; `post-merge-deploy.yml` uses body sentinel; `copilot-setup-steps.yml` has checkout.

## Gotchas

- **`GITHUB_TOKEN` is reserved** — GitHub Actions runner injects its own short-lived installation token under `GITHUB_TOKEN` before any step executes, overwriting any `env.GITHUB_TOKEN` set by the workflow. Use `TF_VAR_github_token`; Terraform auto-maps `TF_VAR_*` → `var.*`.
- **PR body sentinel, not label** — Copilot Coding Agent tokens lack `issues:write`. `post-merge-deploy.yml` MUST use `contains(github.event.pull_request.body, 'smaqit:deploy')`, not a label check.
- **`provision.yml` also needs `TF_VAR_github_token`** — omitting it causes interactive prompt hangs in PR preview plans.
- **`copilot-setup-steps.yml` must include `actions/checkout@v4`** — without checkout, the Coding Agent cannot read the codebase of a private repository.
- **`VITE_*` vars are build-time** — pass `VITE_DEMO_MODE` and any other `VITE_*` vars to the `npm run build` step, not the deploy step. Vite bakes them at compile time; they are not available at runtime.
- **`.terraform.lock.hcl` must be committed** — do NOT add it to `.gitignore`. If CI fails with provider version errors, check that this file is tracked in git.

## Completion

- [ ] Existing workflows read (or confirmed absent)
- [ ] Configuration values resolved
- [ ] `deploy.yml` written (provision + deploy jobs, sequential, no GITHUB_TOKEN collision)
- [ ] `provision.yml` written (PR-only, plan only, path filter, `TF_VAR_github_token` present)
- [ ] `post-merge-deploy.yml` written (body sentinel, no label dependency)
- [ ] Report delivered

## Failure Handling

| Situation | Action |
|-----------|--------|
| Required input not provided | Request the missing information before proceeding |
| Gathered input is ambiguous | Flag the ambiguity and ask for clarification |
| Subagent invocation fails | Report the failure with context; do not silently retry |
| Output artifact already exists | Confirm with user before overwriting |
| Workflows already exist and differ | Show a diff summary; ask whether to overwrite or merge |
| Infrastructure spec not available | Use defaults for all VM and Terraform references; note which values need to be updated post-generation |
| Configuration value ambiguous | Apply the documented default; annotate the generated file with a `# TODO: verify` comment |
