|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +This file provides guidance for AI agents and developers working with the |
| 4 | +`gfw-helper-github-app` repository. |
| 5 | + |
| 6 | +## Repository Purpose |
| 7 | + |
| 8 | +This repository implements the **GitForWindowsHelper GitHub App**, an |
| 9 | +[Azure Function](https://learn.microsoft.com/en-us/azure/azure-functions/) |
| 10 | +that serves the automation needs of the |
| 11 | +[Git for Windows](https://github.com/git-for-windows/git) project. It |
| 12 | +receives GitHub webhook events, performs quick tasks itself, and hands off |
| 13 | +more complex tasks to GitHub workflows in the |
| 14 | +[`git-for-windows-automation`](https://github.com/git-for-windows/git-for-windows-automation) |
| 15 | +repository (which has its own `AGENTS.md` describing the other side of |
| 16 | +this relationship). |
| 17 | + |
| 18 | +The most visible feature is "slash commands": commands such as `/deploy` |
| 19 | +that maintainers issue via comments on GitHub Issues and Pull Requests. |
| 20 | + |
| 21 | +## Architecture Overview |
| 22 | + |
| 23 | +``` |
| 24 | +GitHub webhook (issue_comment, check_run, push, workflow_run) |
| 25 | + | |
| 26 | + v |
| 27 | +GitForWindowsHelper (this Azure Function) |
| 28 | + | quick tasks done inline (e.g., /hi, queue check-runs) |
| 29 | + | |
| 30 | + v |
| 31 | +Dispatches a workflow BY FILENAME in git-for-windows-automation |
| 32 | +(or build-extra / git-sdk-*) via workflow_dispatch |
| 33 | + | |
| 34 | + v |
| 35 | +That workflow runs and "mirrors" check-runs back to the source PR; |
| 36 | +the App reacts to those check-run completions to drive cascades |
| 37 | +``` |
| 38 | + |
| 39 | +The App is intentionally thin: it validates the webhook, decides what to |
| 40 | +do, and either answers directly or triggers a workflow elsewhere. The |
| 41 | +heavy lifting (building packages, tagging Git, building installers) lives |
| 42 | +in `git-for-windows-automation`. |
| 43 | + |
| 44 | +## Critical Contracts with git-for-windows-automation |
| 45 | + |
| 46 | +This App is the counterpart to `git-for-windows-automation`. Two contracts |
| 47 | +must stay in sync across both repositories: |
| 48 | + |
| 49 | +1. **Workflows are dispatched by filename.** Renaming a workflow there |
| 50 | + requires a matching change here. The App dispatches: |
| 51 | + |
| 52 | + | Filename | Target repo | Triggered by | |
| 53 | + |----------|-------------|--------------| |
| 54 | + | `open-pr.yml` | git-for-windows-automation | `/open pr` | |
| 55 | + | `updpkgsums.yml` | git-for-windows-automation | `/updpkgsums` | |
| 56 | + | `build-and-deploy.yml` | git-for-windows-automation | `/deploy` | |
| 57 | + | `tag-git.yml` | git-for-windows-automation | `/snapshot`, `/git-artifacts` | |
| 58 | + | `git-artifacts.yml` | git-for-windows-automation | cascading after `tag-git` | |
| 59 | + | `release-git.yml` | git-for-windows-automation | `/release` | |
| 60 | + | `upload-snapshot.yml` | git-for-windows-automation | cascading after `git-artifacts` | |
| 61 | + | `add-release-note.yml` | build-extra | `/add release note` | |
| 62 | + | `sync.yml` | git-sdk-32 / git-sdk-64 / git-sdk-arm64 | `/synchronize-sdks` | |
| 63 | + |
| 64 | +2. **Check-run names and summaries are a parsing contract.** The App |
| 65 | + queues check-runs with specific names and later parses the names, |
| 66 | + summaries and `text` of completed check-runs to drive cascades (see |
| 67 | + `cascading-runs.js`). These patterns must remain stable on both sides: |
| 68 | + |
| 69 | + - Check-run names: `deploy`, `deploy_<arch>` (e.g. `deploy_x86_64`, |
| 70 | + `deploy_ucrt64`, `deploy_aarch64`), `tag-git`, |
| 71 | + `git-artifacts-<arch>` (`x86_64`/`i686`/`aarch64`), `upload-snapshot`. |
| 72 | + - Summary patterns parsed in `cascading-runs.js`: |
| 73 | + `Tag Git <version> @<sha>` and |
| 74 | + `Build Git <version> artifacts from commit <sha> (tag-git run #<id>)`. |
| 75 | + - The `text` field is parsed for |
| 76 | + `For details, see [this run](<.../actions/runs/<id>>)` to recover the |
| 77 | + dispatched workflow run id. |
| 78 | + |
| 79 | +Changing any of these without updating `git-for-windows-automation` will |
| 80 | +break the automation. |
| 81 | + |
| 82 | +## Slash Commands |
| 83 | + |
| 84 | +Implemented in `slash-commands.js`; documented for users in `README.md`. |
| 85 | + |
| 86 | +| Command | Where | What it does | |
| 87 | +|---------|-------|--------------| |
| 88 | +| `/hi` | any repo the App is installed in | Replies "Hi @<login>" | |
| 89 | +| `/add release note <type> <message>` (alias `/add relnote`) | git, build-extra, MINGW-packages, MSYS2-packages | Dispatches `add-release-note.yml` in build-extra | |
| 90 | +| `/open pr` | git issues | Dispatches `open-pr.yml` to open a component-update PR | |
| 91 | +| `/updpkgsums` | build-extra, MINGW-packages, MSYS2-packages PRs | Dispatches `updpkgsums.yml` to refresh PKGBUILD checksums | |
| 92 | +| `/deploy [<package>]` | build-extra, MINGW-packages, MSYS2-packages PRs | Dispatches one or more `build-and-deploy.yml` runs to build & deploy Pacman packages | |
| 93 | +| `/synchronize-sdks` | any G4W repo | Dispatches `sync.yml` in each `git-sdk-*` repo | |
| 94 | +| `/snapshot` | git PRs | Builds a snapshot from the PR merge commit (no upload) | |
| 95 | +| `/git-artifacts` | git PRs | Builds all release artifacts | |
| 96 | +| `/release` | git PRs | Publishes the artifacts as a GitHub Release | |
| 97 | + |
| 98 | +### How `/deploy` maps packages to architectures |
| 99 | + |
| 100 | +`/deploy` decides which `build-and-deploy.yml` runs to dispatch based on |
| 101 | +the package, using `isMSYSPackage` and `buildsAllArchitecturesInOneRun` |
| 102 | +from `component-updates.js`: |
| 103 | + |
| 104 | +- **MSYS packages**: separate `x86_64` and/or `i686` runs. |
| 105 | +- **`mingw-w64-git-credential-manager`, `mingw-w64-git-lfs`, |
| 106 | + `mingw-w64-wintoast`**: a single combined run (no explicit |
| 107 | + architecture). These are cross-compiled via Visual Studio or downloaded |
| 108 | + as pre-built artifacts for every architecture (including `clangarm64`) |
| 109 | + at once, so they must not be split. |
| 110 | +- **`mingw-w64-llvm`**: only the `aarch64` run. |
| 111 | +- **All other MINGW packages (and `git-extra`)**: separate `i686`, |
| 112 | + `x86_64`, `ucrt64` and `aarch64` runs, so they build in parallel. The |
| 113 | + set of `MINGW_ARCH`s the dispatched workflow builds for each pseudo |
| 114 | + architecture lives in `build-and-deploy.yml` over in |
| 115 | + `git-for-windows-automation`. |
| 116 | + |
| 117 | +## Event Routing |
| 118 | + |
| 119 | +`index.js` is the Azure Function entry point. After validating the webhook |
| 120 | +signature (`validate-github-webhook.js`) it routes by `x-github-event`: |
| 121 | + |
| 122 | +| Event | Condition | Handler | |
| 123 | +|-------|-----------|---------| |
| 124 | +| `issue_comment` | `action == created` and body starts with `/` | `slash-commands.js` | |
| 125 | +| `workflow_run` | completed `release-git.yml` run on `main` | `finalize-g4w-release.js` | |
| 126 | +| `check_run` | completed, app is the active bot, repo is `git` | `cascadingRuns` in `cascading-runs.js` | |
| 127 | +| `push` | to `git-for-windows/git` | `handlePush` in `cascading-runs.js` | |
| 128 | + |
| 129 | +## Directory Structure |
| 130 | + |
| 131 | +``` |
| 132 | +GitForWindowsHelper/ # the Azure Function code |
| 133 | +├── index.js # entry point / webhook router |
| 134 | +├── function.json # Azure Functions binding |
| 135 | +├── slash-commands.js # /deploy, /release, /snapshot, ... handlers |
| 136 | +├── cascading-runs.js # tag-git -> git-artifacts -> upload-snapshot cascade |
| 137 | +├── component-updates.js # package classification + release-note helpers |
| 138 | +├── finalize-g4w-release.js # post-release follow-up |
| 139 | +├── check-runs.js # queue/update/list check-runs in other repos |
| 140 | +├── issues.js # comment helpers (append, react, ...) |
| 141 | +├── trigger-workflow-dispatch.js |
| 142 | +├── github-api-request.js / github-api-request-as-app.js |
| 143 | +├── get-installation-access-token.js / get-installation-id-for-repo.js |
| 144 | +├── get-collaborator-permissions.js / get-user-access-token.js |
| 145 | +├── https-request.js / search.js / gently.js / org.js |
| 146 | +└── validate-github-webhook.js |
| 147 | +
|
| 148 | +__tests__/ # Jest tests for the App (index + component-updates) |
| 149 | +.github/workflows/deploy.yml # deploys the Function to Azure on push to main |
| 150 | +embargoed-builds/ # SEPARATE git worktree (see below) -- do not edit here |
| 151 | +``` |
| 152 | + |
| 153 | +## The `embargoed-builds` Worktree |
| 154 | + |
| 155 | +`embargoed-builds/` is **not** part of the `main` branch. It is a separate |
| 156 | +long-lived branch (`embargoed-builds`) that is typically checked out as a |
| 157 | +[git worktree](https://git-scm.com/docs/git-worktree) nested in the |
| 158 | +working directory. It is a parallel variant of this App used to build |
| 159 | +**embargoed security releases**: it deploys to private Azure Blob Storage |
| 160 | +(`wingit.blob.core.windows.net`) instead of the public `pacman-repo`, and |
| 161 | +relies on self-hosted Windows/ARM64 runners. |
| 162 | + |
| 163 | +Consequences for agents: |
| 164 | + |
| 165 | +- When working on `main`, **do not edit files under `embargoed-builds/`**. |
| 166 | + It belongs to a different branch and is maintained separately; the two |
| 167 | + variants are reconciled deliberately, not by editing both at once. |
| 168 | +- Because Jest's default `testMatch` is `**/__tests__/**`, running |
| 169 | + `npm test` from the repo root also collects |
| 170 | + `embargoed-builds/__tests__/` when that worktree is present (you will |
| 171 | + see four suites instead of two). To run only this App's tests, scope it: |
| 172 | + `npx jest __tests__/`. |
| 173 | + |
| 174 | +## Building, Testing and Linting |
| 175 | + |
| 176 | +There is no build step; the Function is deployed as-is. Verified commands: |
| 177 | + |
| 178 | +- `npm run lint` — ESLint over `**/*.js` (config in `eslint.config.js`). |
| 179 | +- `npm run lint:fix` — ESLint with `--fix`. |
| 180 | +- `npm test` — Jest. To restrict to this App (excluding the |
| 181 | + `embargoed-builds` worktree), use `npx jest __tests__/`. |
| 182 | + |
| 183 | +Always run lint and tests before committing. |
| 184 | + |
| 185 | +## Testing Conventions |
| 186 | + |
| 187 | +The tests in `__tests__/index.test.js` exercise the webhook handler |
| 188 | +end-to-end with heavily mocked dependencies. When adding `/deploy`-style |
| 189 | +tests, keep these harness facts in mind: |
| 190 | + |
| 191 | +- `afterEach` calls `jest.clearAllMocks()` and empties `dispatchedWorkflows`, |
| 192 | + so `toHaveBeenCalledTimes(...)` counts are per-test. |
| 193 | +- The GitHub API is mocked in `mockGitHubApiRequest`. Looking up a PR's |
| 194 | + head SHA goes through `GET .../pulls/<number>`, so **each PR number used |
| 195 | + in a test needs its own `pulls/<number>` entry** returning |
| 196 | + `{ head: { sha: ... } }`, or the mock throws "Unhandled GET ...". |
| 197 | +- `dispatchedWorkflows` is built with `unshift`, so it is in **reverse** |
| 198 | + dispatch order. A `/deploy` that dispatches `i686, x86_64, ucrt64, |
| 199 | + aarch64` yields `['aarch64', 'ucrt64', 'x86_64', 'i686']`. |
| 200 | +- The user-facing comment text is assembled in `slash-commands.js`: a |
| 201 | + single dispatch reads "The workflow run [was started]"; multiple |
| 202 | + dispatches read "The [<arch>](...), the [<arch>](...) and the |
| 203 | + [<arch>](...) workflow runs were started." `displayArchitecture` |
| 204 | + overrides the shown label (e.g. `arm64` for the `aarch64` run). |
| 205 | + |
| 206 | +## Coding Conventions |
| 207 | + |
| 208 | +- CommonJS modules (`require()` / `module.exports`); `async`/`await`. |
| 209 | +- Handlers `require()` their dependencies lazily, inside the function |
| 210 | + body, rather than at the top of the file. |
| 211 | +- Modules are written to be usable both from the Azure Function and from |
| 212 | + the command line (see the `test-*.js` and `get-*.js` helper scripts at |
| 213 | + the repo root), with a `context`/`console` argument used for logging. |
| 214 | +- Errors are surfaced by throwing; `index.js` turns thrown errors into 500 |
| 215 | + responses. |
| 216 | +- Follow the surrounding style: small, single-purpose modules; minimal, |
| 217 | + surgical changes; no duplicated API/auth logic (reuse the existing |
| 218 | + `github-api-request*` / token modules). |
| 219 | +- Commit subjects follow `<area>: <imperative>`, e.g. |
| 220 | + `slash-commands: ...`, `component-updates: ...`. |
| 221 | + |
| 222 | +## Deployment |
| 223 | + |
| 224 | +`.github/workflows/deploy.yml` deploys the Function to Azure on every push |
| 225 | +to `main` that touches `deploy.yml` or `GitForWindowsHelper/**`. It |
| 226 | +authenticates with Azure via OIDC (`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, |
| 227 | +`AZURE_SUBSCRIPTION_ID`) in the `deploy-to-azure` environment and publishes |
| 228 | +with `Azure/functions-action`, respecting `.funcignore`. The GitHub App |
| 229 | +secrets (`GITHUB_APP_*`, `GITHUB_WEBHOOK_SECRET`) are configured on the |
| 230 | +Azure Function itself, not in this repository; see `README.md` for the |
| 231 | +one-time setup. |
| 232 | + |
| 233 | +## Validating Changes |
| 234 | + |
| 235 | +1. **Lint and test**: `npm run lint` and `npx jest __tests__/`. |
| 236 | +2. **Dispatch contracts**: if you change a dispatched workflow filename or |
| 237 | + a queued check-run name/summary, update `git-for-windows-automation` |
| 238 | + (and re-check its `AGENTS.md`) in lockstep. |
| 239 | +3. **Leave `embargoed-builds/` alone** unless you are deliberately working |
| 240 | + on that branch. |
0 commit comments