--- name: cicd description: Use when touching .github/workflows/, release config (.releaserc*), mkdocs.yml, docs deploy, or the whats-new generator. Covers branch-to-release-channel mapping, semantic-release, docs deploy gating, and the agentic whats-new flow. --- # CI/CD in pytrendy ## Workflows | File | Purpose | |---|---| | `test.yaml` | Core tests → non-core (`--cov-append`) → Codecov. Runs on push to main/develop + PRs. | | `release.yaml` | Core tests → semantic-release → build & publish to PyPI. Runs on push to main/develop. | | `docs.yaml` | Build + deploy docs to GitHub Pages (`gh-pages` branch, per-subdirectory). main/develop. | | `docs-preview.yaml` | Per-PR docs preview at `russellsb.github.io/pytrendy/pr-/`. | | `whats-new.yaml` | Agentic: generates `docs/whats-new.md` entry via OpenCode CLI, opens PR back. | | `check-base-branch.yml` | Fails PRs not targeting `develop` (with documented exceptions). | | `lint-pr-title.yml` | Enforces Conventional Commits on PR titles. | | `codeql.yml` | GitHub code scanning. | ## Branch → release channel - `main` → stable. `.releaserc` config; `branches: ["main"]`; publishes to PyPI as `1.x.x`. - `develop` → prerelease `dev` channel. Release workflow does `cp .releaserc.dev.json .releaserc` before running; `branches` includes `{ "name": "develop", "prerelease": "dev", "channel": "dev" }`; publishes as `1.x.x.devN` (the `prepareCmd` rewrites `-dev.` → `.dev` for PEP 440 compliance). Version source of truth = semantic-release output, applied via `poetry version ${nextRelease.version}`. **Never hand-edit `pyproject.toml`'s version** — it desyncs the next release. ## Release flow (`release.yaml`) 1. `test-core` job: `pytest tests/ -m core`. 2. `semantic-release`: conventional-commits analyzer → bumps version → `poetry version` → commits `CHANGELOG.md` + `pyproject.toml` (main) or just `pyproject.toml` (develop) with `chore(release): [skip ci]`. Uses SSH deploy key (`RELEASE_SSH_KEY`). 3. `build-and-publish`: only if `released == 'true'` (or `force_publish` dispatch). `poetry build` → PyPI via OIDC trusted publishing (`id-token: write`, `environment: release`). ## Docs deploy gating (`docs.yaml`) The `check-should-deploy` job decides whether to build: - Skip if any commit message contains `[skip docs]`. - Deploy if any commit matches `^(feat|fix|docs|refactor|perf)(\([^)]*\))?!?:` (semantic types). - Deploy if any commit subject has `!` (breaking) or body has `BREAKING CHANGE`. - Else diff files: deploy if changes touch `docs/`, `mkdocs.yml`, or `pytrendy/`. Deploy writes to `gh-pages` under a per-branch subdirectory (`main/`, `develop/`) with `keep_files: true` so branches don't clobber each other. On `main`, also writes a root `index.html` redirecting to `main/`. The `offline` mkdocs plugin is stripped via `sed` for online deploys; `site_url` is injected per-branch. GitHub Pages setup is a one-time manual step (Settings → Pages → gh-pages / root) — noted in `docs.yaml` header comment. ## Docs preview (`docs-preview.yaml`) Triggers on PR open/sync/reopen/close touching `docs/**`, `mkdocs.yml`, or `pytrendy/**`. Skips fork PRs (no `gh-pages` write access). Bot comments URL with stable marker `` (updates existing comment rather than duplicating). On PR close, removes `pr-/` from `gh-pages` and updates the comment. ## What's New generator (`whats-new.yaml`) — agentic Triggers: on release publish, on Release workflow completion (main/develop), or manual dispatch. 1. Resolves release metadata (tag, name, body, prerelease flag, branch) from the event payload, falling back to the GitHub Releases API by tag, then `CHANGELOG.md`. 2. Checks out the release branch, installs the **OpenCode CLI** (see `whats-new.yaml` for the exact install step). 3. Runs `python scripts/generate_whats_new.py` with `OPENCODE_MODEL` (default `opencode-go/deepseek-v4-flash`) and a **deny-all permission block** (`bash/edit/webfetch/websearch/external_directory/task` all `deny`) — the agent can only read and write the whats-new file via the script. 4. The script prepends a user-friendly entry into `docs/whats-new.md` **between sentinel comments** `` / ``. Don't edit content inside those markers by hand — it gets regenerated. 5. Opens a PR (`docs/whats-new-`) back to the release branch via `peter-evans/create-pull-request@v7` using `DOCS_PREVIEW_PAT`. The PR's `base` is the release branch (develop or main) — these are the *only* PRs allowed to target `main` from a non-`develop` head (`check-base-branch.yml` whitelists `docs/whats-new-*`). 6. On stable releases (main, not prerelease), a `sync-to-develop` job re-generates the entry framed as stable and opens a follow-up PR to `develop` so both branches stay consistent. Script-level agent instructions are in `scripts/generate_whats_new.py` docstring: verify CSV URLs resolve (use develop-branch raw URLs for pre-releases, main for stable), derive before/after plot scenarios from `tests/tests_crashes_edgecases/` not synthetic data, and keep figsize/grid/legend/colors identical between before/after images by routing through the same `detect_trends()` + `plot_pytrendy()` pipeline. For manual before/after plot generation in PR bodies (fix/feature PRs), see the `pr-plots` skill. ## Secrets the workflows expect `CODECOV_TOKEN`, `RELEASE_SSH_KEY`, `OPENCODE_API_KEY`, `DOCS_PREVIEW_PAT`, plus `GITHUB_TOKEN`. `OPENCODE_MODEL` is a repo **variable** (vars context), not a secret. PyPI publish uses OIDC trusted publishing (no token). ## OpenCode App installation scopes The OpenCode GitHub App (`opencode-agent`) requires specific repository permissions for commits, issues, and PRs. Verify in GitHub Settings → Integrations → OpenCode App → Installations: - **Contents:** read/write (for commits) - **Issues:** read/write (for creating and commenting on issues) - **Pull requests:** read/write (for creating and commenting on PRs) **Fallback (user awareness only — do not attempt to apply):** If App scopes cannot be fixed, the user can set `use_github_token: true` in `.github/workflows/opencode.yml` (backed by the widened workflow `permissions:` block). This requires changes to the workflow on `main`, so the agent should not attempt to work around it.