# Contributing How changes to the brand land when more than one person is working on it. The goal is that anyone can improve the guide without the brand drifting. ## Roles - **Brand owners** - Aurimas Adomavicius and Alex Cadice. Final say on anything that changes how the brand looks or sounds; either one can approve a breaking change or a release. - **Maintainers** - review PRs, keep the guide, templates and skill in sync. - **Contributors** - anyone at Gritmind. Propose changes by PR, or raise an issue in the Azure Boards backlog if you are not sure what the change should be. ## The flow 1. Branch from `master`: `brand/`, one topic per branch. 2. Make the change, including every place it touches (token, guideline, asset, template, skill reference). A change that updates the guideline but not the template is half a change. 3. Add an entry to `CHANGELOG.md` under "Unreleased", below the heading for its change class: `### Breaking`, `### Added` or `### Fixed` (classes below). Why: the release takes its version from these headings, so the class you pick is the version bump. Never write a version number by hand. Write it short: a bold title of a few words and a one-line description (`- **What changed** - Why it matters`), then the details as one-line sub-bullets, each under about 100 characters; a Breaking entry adds a `Consumers:` sub-bullet. Why: the reference site's Releases page shows the entries as they are, and people scan them before upgrading. `check-plugin.mjs` fails an entry without a bold title and description, or with wrapped text instead of sub-bullets. 4. Open a PR into `master`. Say which change class it is (below) and attach a before/after image for anything visual. 5. Reviewer checks it against the pre-flight list in `CHECKLIST.md`. 6. Squash-merge. Delete the branch. Releases: when `master` has a coherent set of changes, a brand owner releases it by merging one pull request; nobody writes or runs a version bump. The pipeline works out the version after the merge from the Unreleased headings (Breaking is major, Added minor, Fixed patch), as gritmind-os does. `node scripts/release.mjs check` shows what the next version will be. 1. Open a PR from `master` into `main`, titled "Release X.Y.Z" with the version `check` shows (`node scripts/release.mjs to-main` opens it for you, after `az login`). Its checks need entries under "Unreleased" and no version heading written by hand. Squash-merge it (the only merge type `main` allows) with that title, and keep `master`. That is the only step a person takes. 2. The merge into `main` starts the pipeline `build-and-deploy-gritmind-brand`. After its checks, its Release stage: - stamps the version: moves "Unreleased" under `## X.Y.Z - `, sets `version` in `plugin/.claude-plugin/plugin.json`, rebuilds `reference/data.js`, and pushes that to `main` as "Release X.Y.Z". Why the plugin version: Claude Code only offers the plugin update when it changes. The commit message ends in `***NO_CI***`, so it does not start the pipeline again; - deploys the reference site: it calls Netlify's build hook, which builds the stamped `main`. Why a hook: Netlify holds deploys of commits pushed by the build service for review, and approving them could add a billed contributor. Leave that pending request unapproved; - tags `vX.Y.Z`; - publishes the marketplace to the GitHub repo `gritmindinc/gritmind-brand-marketplace` (the plugin reaches people from there); - merges `main` back into `master` and pushes it ("Sync main into master after X.Y.Z"), so `master` has the stamped changelog and `plugin.json` too. Why: without it the next release would clash on `CHANGELOG.md`. Watch the run if you like; there is nothing to approve. If a step fails, rerun it: each step skips what is already done, including the stamp. Only if `CHANGELOG.md` changed on `master` between the release and the back-merge, or the merge conflicts, or its push is refused, the pipeline opens a PR from a copy branch `brand/sync-main-X.Y.Z` into `master` instead. Why the changelog: a newer entry would merge in under the new version heading and be listed as released. Resolve it so new entries stay under "Unreleased", and complete it as "Merge (no fast-forward)", not a squash. If the pipeline cannot do a step, do it by hand: tag with `git tag -a vX.Y.Z -m "Release X.Y.Z" origin/main` and push the tag; build the marketplace with `node scripts/build-marketplace.mjs` and replace the GitHub repo's content with `tmp/marketplace/` by PR there; run `node scripts/release.mjs back-merge`. The stamp and the back-merge push need the build service's "Bypass policies when pushing" on `main` and `master`. ## Change classes and versioning The brand follows semantic versioning, applied to what consumers depend on. | Class | Examples | Version | Changelog heading | Needs | | --- | --- | --- | --- | --- | | **Breaking** | Change a token value; change logo geometry or colourways; change or drop a typeface; change a core rule (one accent per view, mono for numbers); rename or remove an asset or token | Major | `### Breaking` | Decision record, owner approval, list of consumers to update | | **Additive** | New guideline, template, asset, colourway, token or production spec that does not alter existing ones | Minor | `### Added` | One maintainer review | | **Editorial** | Clarify wording, fix a typo, add an example, re-export an asset with identical output | Patch | `### Fixed` | One maintainer review | When in doubt, treat it as the higher class. ## Decision records Any breaking change, and any judgment call someone is likely to relitigate, gets a short record in `decisions/`: ``` decisions/0003-us-english.md # 0003 US English for all brand copy Date: 2026-10-14 · Status: accepted · Approved by: ## Context What prompted it, and what the options were. ## Decision The rule, stated so it can be copied into a guideline. ## Consequences What changes, what has to be updated, what we gave up. ``` Number them in sequence and never delete one. If a decision is reversed, write a new record that supersedes it and mark the old one `superseded by 00NN`. ## Keeping things in sync The usual way a brand guide decays is copies drifting. To avoid it: - **One value, one place.** Values live in `tokens/tokens.json`. Prose, templates and the skill reference tokens; they never restate a new value. - **Generated files say so.** Anything produced by a script carries a "generated - do not edit" header and names its source and the command that regenerates it. - **Consumers pin a version.** The website and any other repo that uses brand files pins a release tag and upgrades deliberately, reading the changelog. - **Breaking changes list their consumers.** The PR lists every place that has to change (website, live templates, social profiles, signatures) and who will do it. - **Quarterly review.** Once a quarter a maintainer walks the guide against what actually shipped that quarter: anything shipped that breaks a rule is either a bug in the asset or a sign the rule needs to change. Record which in the changelog. ## Review standards A reviewer should be able to say yes to each: - Does it follow the writing rules in `CLAUDE.md` (no em dashes, sentence case, concrete values)? - Does every value trace to a token? - Is the change made everywhere it applies, including the skill? - Is each rule paired with its reason and a "do instead"? - For assets: does it pass the pre-flight list? - For breaking changes: is there a decision record and a consumer list? ## Azure DevOps setup These are configured in the repo's branch policies, not in files: - `main`: require PR, require brand owner as reviewer, no direct pushes (org policy), except the pipeline's own "Release X.Y.Z" commit. Squash merge only, set by a repository-wide merge strategy policy. - `master`: require PR with at least one reviewer, require linked work item optional. Squash merge for `brand/*` branches. The one plain merge is the pipeline's "Sync main into master", pushed after each release. - Keep `main` as the Azure default branch. The project's protections apply to the default branch, so switching it would leave `main` unprotected. - Automatically included reviewers by path: brand owner on `tokens/`, `assets/logo/` and `assets/fonts/`. - Build validation on `master` and `main`: the pipeline `build-and-deploy-gritmind-brand` (`.azuredevops/build-and-deploy.yml`), required. It checks that generated files are committed, runs `scripts/check-plugin.mjs` (with `--release` on PRs into `main`) and `claude plugin validate --strict` on the plugin and on the marketplace that `scripts/build-marketplace.mjs` builds. The same pipeline runs on every merge into `main`, where its Release stage stamps the version, deploys the reference site, tags, publishes to the GitHub marketplace repo and merges `main` back into `master`. It reads the GitHub App credentials and the Netlify build hook URL (`netlify-build-hook`, secret) from the variable group `gritmind-brand-marketplace-github-app`. - Netlify: a build hook named `azure-release` for `main` (Project configuration > Build & deploy > Continuous deployment > Build hooks). Its URL is the secret above; anyone with it can start builds. - The build service ("gritmind-apps Build Service") may Contribute, Create tag and Contribute to pull requests on this repo, and "Bypass policies when pushing" on `main` and `master` only (Repos > Branches > the branch > Branch security), for the Release stage's release commit and back-merge. Force push and "Bypass policies when completing pull requests" stay off.