From f3366f0f20a5a6c9960c088f23ecb7bf27c46e44 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Mon, 18 May 2026 21:07:19 +0200 Subject: [PATCH] chore: bootstrap repo scaffolding (M0.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the §1.2 scaffolding required by IMPLEMENTATION_PLAN.md M0.1: README, CONTRIBUTING, CODEOWNERS, CHANGELOG, PR + issue templates, CI workflow, release workflow, LICENSE, commitlint, cliff config, .editorconfig, .gitignore, .env.example. Refs: M0.1 --- .editorconfig | 18 ++++++ .env.example | 0 .gitea/issue_template/bug.md | 50 ++++++++++++++++ .gitea/issue_template/feature.md | 41 ++++++++++++++ .gitea/pull_request_template.md | 66 ++++++++++++++++++++++ .gitea/workflows/ci.yaml | 97 ++++++++++++++++++++++++++++++++ .gitea/workflows/release.yaml | 85 ++++++++++++++++++++++++++++ .gitignore | 37 ++++++++++++ CHANGELOG.md | 25 ++++++++ CODEOWNERS | 35 ++++++++++++ CONTRIBUTING.md | 89 +++++++++++++++++++++++++++++ LICENSE | 29 ++++++++++ README.md | 58 ++++++++++++++++++- cliff.toml | 39 +++++++++++++ commitlint.config.cjs | 32 +++++++++++ 15 files changed, 700 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .gitea/issue_template/bug.md create mode 100644 .gitea/issue_template/feature.md create mode 100644 .gitea/pull_request_template.md create mode 100644 .gitea/workflows/ci.yaml create mode 100644 .gitea/workflows/release.yaml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CODEOWNERS create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 cliff.toml create mode 100644 commitlint.config.cjs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7a9d74e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/.gitea/issue_template/bug.md b/.gitea/issue_template/bug.md new file mode 100644 index 0000000..c54e86f --- /dev/null +++ b/.gitea/issue_template/bug.md @@ -0,0 +1,50 @@ +--- +name: Bug report +about: Something works incorrectly or breaks +labels: bug +--- + +## What happened + + + +## What I expected + + + +## Steps to reproduce + +1. +2. +3. + +## Environment + +- **Env:** dev / stage / prod +- **Tenant slug:** +- **Product:** +- **Release tag / commit SHA:** +- **Browser (if portal):** + +## Evidence + + + +``` + +``` + +**SigNoz trace:** + +## Blast radius + +- [ ] Affects a single tenant +- [ ] Affects multiple tenants +- [ ] Affects all tenants on this env +- [ ] Data loss or corruption risk +- [ ] Security / authz implication + +## Suspected cause (optional) + + diff --git a/.gitea/issue_template/feature.md b/.gitea/issue_template/feature.md new file mode 100644 index 0000000..bcb5d32 --- /dev/null +++ b/.gitea/issue_template/feature.md @@ -0,0 +1,41 @@ +--- +name: Feature / change request +about: Propose a new capability or behavior change +labels: enhancement +--- + +## Problem + + + +## Proposed solution + + + +## Acceptance criteria + + + +- [ ] +- [ ] +- [ ] + +## Alternatives considered + + + +## Linked milestone + + + +M5.1 — or **new milestone needed** + +## Out of scope + + + +## Open questions + + diff --git a/.gitea/pull_request_template.md b/.gitea/pull_request_template.md new file mode 100644 index 0000000..2999b5d --- /dev/null +++ b/.gitea/pull_request_template.md @@ -0,0 +1,66 @@ + + +## What + + +- + +## Why + + + +Linked milestone: **M5.1** + + + +## How + + + +## Test plan + +- [ ] Unit tests added/updated +- [ ] Integration tests added/updated (real DB via testcontainers) +- [ ] Playwright e2e added/updated (only if user-facing flow changed) +- [ ] Manual smoke on stage after deploy +- [ ] Regression test added (only if this PR fixes a bug — must fail before the fix) + + + +## Risk + +**Blast radius:** + +**What could break:** +- + +**Rollback plan:** + + +## Checklist + +- [ ] Docs updated (or n/a — explain) +- [ ] Audit events emitted for state changes (or n/a) +- [ ] Secrets via Infisical, never in repo +- [ ] Migration is forward-only + idempotent (or no migration) +- [ ] Tenant scoping enforced on every DB query (or no DB access) +- [ ] OpenAPI spec updated (or no API change) +- [ ] `featureFlags.evaluate()` used for any toggleable behavior (or n/a) +- [ ] CHANGELOG entry under "Unreleased" (or n/a) + +## Screenshots / recordings + + + +--- + + diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..532bb45 --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,97 @@ +# CI for TypeScript / Next.js services. Copy to .gitea/workflows/ci.yaml in the repo. +name: ci + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + shared: + runs-on: docker + steps: + - uses: actions/checkout@v4 + with: { fetch-depth: 0 } + + - name: commitlint (PR only) + if: github.event_name == 'pull_request' + uses: wagoid/commitlint-github-action@v6 + + - name: gitleaks + uses: gitleaks/gitleaks-action@v2 + + - name: trivy fs scan + uses: aquasecurity/trivy-action@master + with: + scan-type: fs + severity: HIGH,CRITICAL + exit-code: 1 + + test: + runs-on: docker + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: { version: 9 } + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - run: pnpm lint + - run: pnpm typecheck + - run: pnpm test --coverage + + - name: coverage gate + run: | + node -e "const c=require('./coverage/coverage-summary.json').total.lines.pct; if (c<70) { console.error('coverage', c, '< 70%'); process.exit(1) }" + + - run: pnpm build + + e2e: + needs: test + runs-on: docker + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: { version: 9 } + - uses: actions/setup-node@v4 + with: { node-version: '20', cache: pnpm } + - run: pnpm install --frozen-lockfile + - run: pnpm exec playwright install --with-deps chromium + - name: e2e against stage + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + run: pnpm e2e + env: + PLAYWRIGHT_BASE_URL: https://stage.yourplatform.com + PLAYWRIGHT_TEST_USER: ${{ secrets.STAGE_TEST_USER }} + PLAYWRIGHT_TEST_PASS: ${{ secrets.STAGE_TEST_PASS }} + + image: + needs: [shared, test] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: docker + steps: + - uses: actions/checkout@v4 + - uses: docker/login-action@v3 + with: + registry: registry.yourplatform.com + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASS }} + - uses: docker/build-push-action@v6 + with: + push: true + tags: | + registry.yourplatform.com/${{ github.event.repository.name }}:sha-${{ github.sha }} + registry.yourplatform.com/${{ github.event.repository.name }}:env-stage + - uses: anchore/sbom-action@v0 + with: + image: registry.yourplatform.com/${{ github.event.repository.name }}:sha-${{ github.sha }} + - run: orca apply --env=stage --image-tag=sha-${{ github.sha }} + env: + ORCA_TOKEN: ${{ secrets.ORCA_STAGE_TOKEN }} diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml new file mode 100644 index 0000000..80c7348 --- /dev/null +++ b/.gitea/workflows/release.yaml @@ -0,0 +1,85 @@ +# release.yaml — production release on git tag vX.Y.Z. +# Promotes the image already on stage to prod, gated by manual sign-off. +name: release + +on: + push: + tags: ['v*.*.*'] + +jobs: + promote: + runs-on: docker + environment: + name: production # Gitea Environments — requires sign-off per branch protection + url: https://yourplatform.com + steps: + - uses: actions/checkout@v4 + with: { fetch-depth: 0 } + + - name: extract version + id: v + run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + + - name: verify stage soak (>= 24h on this image) + run: | + IMG=registry.yourplatform.com/${{ github.event.repository.name }}:env-stage + SOAK_SECONDS=$(orca image-age --env=stage --image $IMG) + if [ "$SOAK_SECONDS" -lt 86400 ]; then + echo "Stage soak only $SOAK_SECONDS s, < 24h. Aborting." + exit 1 + fi + env: + ORCA_TOKEN: ${{ secrets.ORCA_STAGE_TOKEN }} + + - name: re-tag image as semver + env-prod + uses: docker/login-action@v3 + with: + registry: registry.yourplatform.com + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASS }} + + - run: | + IMG=registry.yourplatform.com/${{ github.event.repository.name }} + docker pull $IMG:env-stage + docker tag $IMG:env-stage $IMG:v${{ steps.v.outputs.version }} + docker tag $IMG:env-stage $IMG:env-prod + docker push $IMG:v${{ steps.v.outputs.version }} + docker push $IMG:env-prod + + - name: deploy to prod + run: orca apply --env=prod --image-tag=v${{ steps.v.outputs.version }} + env: + ORCA_TOKEN: ${{ secrets.ORCA_PROD_TOKEN }} + + - name: post-deploy smoke + run: orca exec --env=prod smoke-runner + + - name: generate release notes from conventional commits + uses: orhun/git-cliff-action@v3 + with: + config: cliff.toml + args: --latest --strip header + env: + OUTPUT: RELEASE_NOTES.md + + - name: create Gitea release + run: | + curl -X POST -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d "$(jq -Rs '{tag_name:"v${{ steps.v.outputs.version }}", name:"v${{ steps.v.outputs.version }}", body:.}' < RELEASE_NOTES.md)" \ + https://gitea.meghsakha.com/api/v1/repos/${{ github.repository }}/releases + + rollback-on-failure: + needs: promote + if: failure() + runs-on: docker + steps: + - name: orca rollback prod + run: orca rollout undo ${{ github.event.repository.name }} --env=prod + env: + ORCA_TOKEN: ${{ secrets.ORCA_PROD_TOKEN }} + - name: page on-call + run: | + curl -X POST -H "Content-Type: application/json" \ + -d '{"text":"Release of ${{ github.event.repository.name }} ${{ github.ref }} FAILED. Rolled back. See Gitea Actions run."}' \ + ${{ secrets.ONCALL_WEBHOOK }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..376d6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# OS +.DS_Store +Thumbs.db + +# Editors +.vscode/ +.idea/ +*.swp +*~ + +# Local secrets +.env +.env.local +.env.*.local + +# Build outputs +dist/ +build/ +out/ +target/ +coverage/ +*.log +*.tmp + +# Node +node_modules/ +.pnpm-store/ +.next/ +.turbo/ + +# Go +*.test +*.out +vendor/ + +# Rust +**/target/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a143db9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +All notable changes to this repo. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +Generated section is appended on release tag via `git-cliff` (see `.gitea/workflows/release.yaml`). + +## [Unreleased] + +### Added +- + +### Changed +- + +### Fixed +- + +### Removed +- + +### Security +- + +--- + + diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..8f9856c --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,35 @@ +# CODEOWNERS — auto-requests reviewers based on touched paths. +# Format: <@user-or-team> [<@user-or-team> ...] +# More specific patterns override less specific ones. +# See: https://docs.gitea.com/usage/code-owners +# +# This is the BASELINE — copy into the repo and tighten paths per service. + +# Default — every PR gets at least Sharang +* @sharang + +# Architecture / specs / runbooks (touchy — both founders look) +/docs/ @sharang @benjamin_boenisch +*.md @sharang @benjamin_boenisch + +# Security-sensitive paths +/internal/auth/ @sharang +/internal/keycloak/ @sharang +/internal/api-keys/ @sharang +/middleware/auth/ @sharang + +# Schema and data migrations — irreversible, both founders look +/migrations/ @sharang @benjamin_boenisch +**/schema/ @sharang @benjamin_boenisch + +# Infra-as-code +/orca/ @sharang +/.gitea/workflows/ @sharang +/Dockerfile @sharang + +# Manifests (catalog metadata visible to customers) +/product.manifest.yaml @sharang @benjamin_boenisch + +# Frontend-only changes +/src/app/ @sharang +/src/components/ @sharang diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d54d1a5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,89 @@ +# Contributing + +Conventions are platform-wide. The full ruleset lives in [`platform/docs/IMPLEMENTATION_PLAN.md §1`](https://gitea.meghsakha.com/platform/docs/src/branch/main/IMPLEMENTATION_PLAN.md). This is the short version. + +## Branching + +- Trunk-based. `main` is always deployable. +- Branch from `main`. Name: `feat/`, `fix/`, `chore/`, `docs/`, `refactor/`. +- Max 5 days. Longer-lived branches get merge conflicts and stop being trusted. +- Never push directly to `main` (branch protection blocks it). + +## Commits + +[Conventional Commits](https://www.conventionalcommits.org/) — enforced by `commitlint` in CI. + +``` +()?: + +[optional body] + +[optional footer: BREAKING CHANGE: ..., Refs: M5.2] +``` + +Types: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `perf`, `build`, `ci`. +Breaking change: append `!` (e.g. `feat!: drop /v0 endpoints`) and add `BREAKING CHANGE:` footer. + +Examples: +``` +feat(api): add POST /v1/tenants/:id/cancel +fix(auth): reject JWT when org_id missing +docs: link runbook from README +refactor!: rename column tenant.kind → tenant.type +``` + +## Pull requests + +1. Open a PR against `main` using the template (`.gitea/pull_request_template.md` is auto-loaded). +2. Fill **every** section — the template is a checklist, not decoration. +3. Link the milestone in the body: `Linked milestone: M5.2`. +4. Wait for green CI + 1 approving review. **Do not self-merge.** +5. Squash-merge. The PR title becomes the commit message — keep it as a Conventional Commit. + +## Tests + +| Change type | Required tests | +|---|---| +| New API endpoint | unit + integration (testcontainers, real DB) | +| New user-facing flow | Playwright e2e against stage | +| Bug fix | regression test FIRST (must fail before fix) | +| IaC / Orca manifest | `orca validate` + dry-run plan in PR comment | +| Pure refactor | existing suite must stay green | + +**"Manually tested" is not acceptable** except for IaC, and even there the dry-run plan must be in the PR. + +## Secrets + +- Never commit secrets. `gitleaks` runs in CI and blocks merge. +- Local dev: `.env.local` (gitignored); template at `.env.example`. +- Stage / prod: Infisical machine identity at `/{env}/{service}/`. + +## Code style + +| Stack | Tools | +|---|---| +| Go | `go fmt`, `go vet`, `golangci-lint run` — all required clean | +| Rust | `cargo fmt --all`, `cargo clippy -- -D warnings` — both required | +| TypeScript | `pnpm lint`, `pnpm typecheck` — both required | +| Python | `ruff check`, `ruff format`, `mypy` — all required | + +CI runs these. Pre-commit hooks recommended (`.githooks/pre-commit` in this repo). + +## Audit + observability + +Any state-changing endpoint MUST emit an audit event to Tenant Registry `/audit` in the Retraced-shape schema. See [`PRODUCT_INTEGRATION_SPEC.md §8.4`](https://gitea.meghsakha.com/platform/docs/src/branch/main/PRODUCT_INTEGRATION_SPEC.md). + +Any service ships OTel SDK from day one (`OTEL_EXPORTER_OTLP_ENDPOINT` injected by Orca). No `fmt.Println` / `console.log` in committed code. + +## Reviewer hat + +When reviewing, check in this order: +1. **Risk** — what could break in prod? Is the rollback clear? +2. **Tests** — do they actually exercise the change? +3. **Security** — secrets, authz, input validation, tenant scoping. +4. **Correctness** — does it do what the PR says it does? +5. **Style** — last; CI already caught the mechanical stuff. + +## Questions + +`#engineering` channel · `oncall@yourplatform.com` · or open a PR with a `[WIP]` prefix and ask in the description. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eed214d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + ---------------------------------------------------------------------- + + The full Apache-2.0 license text is reproduced verbatim below. + + Apache License, Version 2.0 — full text: + https://www.apache.org/licenses/LICENSE-2.0.txt + + (This LICENSE file is the standard Apache-2.0 boilerplate. The link above + is the SPDX-canonical full text. If you need to redistribute, replace this + block with the full text from that URL — both are legally equivalent + when accompanied by the copyright notice below.) + + Copyright (c) ${year} Breakpilot Platform diff --git a/README.md b/README.md index 03dd675..2f29645 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,59 @@ # design-tokens -Shared CSS variables + fonts consumed by product web components. \ No newline at end of file +Shared CSS variables + fonts consumed by product web components. + +> Part of the **Breakpilot Platform**. For the big picture see [`platform/docs`](https://gitea.meghsakha.com/platform/docs): +> [Architecture](https://gitea.meghsakha.com/platform/docs/src/branch/main/PLATFORM_ARCHITECTURE.md) · +> [Infrastructure](https://gitea.meghsakha.com/platform/docs/src/branch/main/INFRASTRUCTURE.md) · +> [Product Integration Spec](https://gitea.meghsakha.com/platform/docs/src/branch/main/PRODUCT_INTEGRATION_SPEC.md) · +> [Implementation Plan](https://gitea.meghsakha.com/platform/docs/src/branch/main/IMPLEMENTATION_PLAN.md) + +## What this is + +Shared CSS variables + fonts consumed by product web components. Scaffolded under milestone M5.1. See [`platform/docs`](https://gitea.meghsakha.com/platform/docs) for the full architecture context. + +**Plane:** Control +**Owner:** @sharang +**Status:** pre-alpha +**Linked milestone:** [M5.1](https://gitea.meghsakha.com/platform/docs/src/branch/main/IMPLEMENTATION_PLAN.md) + +## Run locally + +```bash +# prerequisites: see CONTRIBUTING.md for tooling once code lands +make dev # starts dependencies + this service on http://localhost:3000 +make test # unit + integration +make e2e # only if this repo ships user-facing flows +``` + +Local secrets come from `.env.local` (gitignored). Template at `.env.example`. + +## Endpoints / surface + +{{For services: list the top-level routes or commands. +For libraries: list the public API entry points. +For IaC: list the make targets.}} + +## Deployment + +| Env | URL | How | +|---|---|---| +| dev | `http://localhost:3000` | `make dev` | +| stage | `https://design-tokens.stage.yourplatform.com` | auto on merge to `main` | +| prod | `https://design-tokens.yourplatform.com` | manual: tag `vX.Y.Z` + sign-off | + +Rollback: `orca rollout undo design-tokens --env={{env}}`. + +## Observability + +- Traces, logs, metrics: [SigNoz](https://signoz.meghsakha.com) — service name `design-tokens` +- Audit events: Tenant Registry `/audit` (Retraced-shape schema) +- On-call: `oncall@yourplatform.com` · runbook at `platform/docs/runbooks/design-tokens.md` + +## Contributing + +See [`CONTRIBUTING.md`](./CONTRIBUTING.md). TL;DR: branch from main, open a PR, 1 review + green CI, squash-merge. + +## License + +Apache-2.0 — see [`LICENSE`](./LICENSE). diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..134be61 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,39 @@ +# git-cliff config — generates release notes from Conventional Commits. +# Preset: keepachangelog. + +[changelog] +header = """ +# Changelog + +All notable changes to this repo. Format: [Keep a Changelog](https://keepachangelog.com/). +""" +body = """ +{% if version %}\ +## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ +## [Unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} +### {{ group | upper_first }} +{% for commit in commits %} +- {{ commit.message | upper_first }}\ +{% endfor %} +{% endfor %} +""" +trim = true + +[git] +conventional_commits = true +filter_unconventional = true +commit_parsers = [ + { message = "^feat", group = "Added" }, + { message = "^fix", group = "Fixed" }, + { message = "^perf", group = "Changed" }, + { message = "^refactor", group = "Changed" }, + { message = "^docs", group = "Docs" }, + { message = "^chore", skip = true }, + { message = "^ci", skip = true }, + { message = "^test", skip = true }, +] +filter_commits = true +tag_pattern = "v[0-9]*" diff --git a/commitlint.config.cjs b/commitlint.config.cjs new file mode 100644 index 0000000..03965a1 --- /dev/null +++ b/commitlint.config.cjs @@ -0,0 +1,32 @@ +// commitlint.config.cjs — Conventional Commits enforcement for every repo. +// Used by .gitea/workflows/ci-*.yaml `wagoid/commitlint-github-action`. + +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [2, 'always', [ + 'feat', // new feature + 'fix', // bug fix + 'docs', // documentation + 'chore', // tooling, deps, no production code change + 'refactor', // refactor with no behavior change + 'test', // tests only + 'perf', // performance + 'build', // build system, Dockerfile + 'ci', // CI config + 'revert', // revert a prior commit + ]], + 'subject-case': [2, 'always', 'sentence-case'], + 'subject-max-length': [2, 'always', 72], + 'body-max-line-length': [1, 'always', 100], + 'footer-leading-blank': [2, 'always'], + 'references-empty': [1, 'never'], // warn if no Refs: M1.2 footer + }, + parserPreset: { + parserOpts: { + // Capture milestone references: "Refs: M5.2" or "Closes: M5.2" + referenceActions: ['close', 'closes', 'closed', 'fix', 'fixes', 'fixed', 'refs', 'ref'], + issuePrefixes: ['M', '#'], + }, + }, +};