Skip to content

Instantly share code, notes, and snippets.

@Benjaminsson
Created March 4, 2026 15:10
Show Gist options
  • Select an option

  • Save Benjaminsson/4687439e06d9136fea3cf7d625df994b to your computer and use it in GitHub Desktop.

Select an option

Save Benjaminsson/4687439e06d9136fea3cf7d625df994b to your computer and use it in GitHub Desktop.
Claude Code skill: Standardize CI, commitlint, and commit tooling for an Advisa repo
name standardize-repo
description Standardize CI, commitlint, and commit tooling for an Advisa repo
disable-model-invocation true

Standardize CI, commitlint, and commit tooling

Migrate this repo to use Sambla Group shared CI workflows, add commitlint for commit message enforcement, and add commit message tooling for Claude Code, Codex, and GitHub Copilot. The tooling derives the Jira ticket ID from the branch name when possible (e.g. UCL-804 from UCL-804-Derive-jira-id-in-skill), falling back to asking the developer only when the branch name doesn't contain a Jira ID.


Phase 0: Auto-detect repo characteristics

Detect the following values automatically:

  1. REPO_NAME — Run git remote get-url origin and extract the repo name (e.g. onboarding-api from git@github.com:Advisa/onboarding-api.git). Fall back to the current directory name.
  2. PACKAGE_MANAGER — Check for lock files: package-lock.json → npm, yarn.lock → yarn, pnpm-lock.yaml → pnpm.
  3. DEFAULT_BRANCH — Try git symbolic-ref refs/remotes/origin/HEAD (strip refs/remotes/origin/). Fall back to the current branch name.
  4. NODE_VERSION — Check .nvmrc first, then engines.node in package.json, then any node-version in existing CI workflow files. Default to 22 if not found.
  5. JIRA_PREFIXES — Hardcoded: UCL,INS,MYS,NV

Present the detected values to the user in a summary table and ask for confirmation before proceeding. Also ask whether the repo needs enable-test: true (shared workflow runs tests) or enable-test: false (repo needs custom test infrastructure like Redis, matrix strategies, etc). Check existing CI workflows to make a recommendation.

VPN reminder: If npm install fails with E401 authentication errors against npm.advisa.se, remind the user to connect to the VPN and retry.

Always use latest versions: When installing packages, always run npm info <package> version first to verify the latest version. Never add version numbers from memory to package.json.


Step 0.5: Add .node-version (if needed)

If the repo does not already have a .node-version or .nvmrc file, and does not have engines.node defined in package.json, create a .node-version file to align local development with CI:

22

Step 1: Replace CI workflows

Delete existing CI/test workflow(s) in .github/workflows/ that are being replaced. Keep unrelated workflows (e.g. manual build/deploy workflows, utility workflows like npm_install.yml).

Create: .github/workflows/verify.yml

name: Verify

on:
  pull_request:
    branches: [<DEFAULT_BRANCH>]

jobs:
  verify:
    uses: Advisa/gha-workflow-templates/.github/workflows/ucl-verify.yml@main
    with:
      node-version: '<NODE_VERSION>'
      jira-project-key: 'UCL,INS,MYS,NV'
      enable-test: <true|false>
    secrets: inherit

The shared workflow defaults to Node 22. Only pass node-version if the repo requires a different version. If enable-test: false, either add custom test jobs to verify.yml or create a separate test.yml.

Create: .github/workflows/release.yml

name: Release

on:
  push:
    branches: [<DEFAULT_BRANCH>]

jobs:
  release:
    uses: Advisa/gha-workflow-templates/.github/workflows/ucl-release.yml@main
    secrets: inherit

If enable-test: false, create .github/workflows/test.yml with the repo-specific test setup. Preserve any existing infrastructure needs (Redis, databases, matrix strategies, etc.) from the old workflow. Improvements to apply:

  • Use matrix strategy for parallel test runs where applicable
  • Use pre-installed tools (e.g. jq) instead of installing them
  • Keep JSON/config validation as a separate job if the repo has it
  • Use actions/checkout@v4 and actions/setup-node@v4
  • Configure the Advisa NPM registry:
    - name: Configure NPM Registry & Install Dependencies
      env:
        ADVISA_NPM_TOKEN: ${{ secrets.ADVISA_NPM_TOKEN }}
      run: |
        npm config set @advisa:registry https://npm.advisa.se/
        npm config set //npm.advisa.se/:_authToken=$ADVISA_NPM_TOKEN
        npm ci

Step 2: Add semantic-release config

Run: <PACKAGE_MANAGER> install --save-dev semantic-release @semantic-release/changelog @semantic-release/git conventional-changelog-conventionalcommits (Adjust command for yarn: yarn add --dev ..., pnpm: pnpm add -D ...)

Create: .releaserc.json

{
  "branches": ["<DEFAULT_BRANCH>"],
  "plugins": [
    [
      "@semantic-release/commit-analyzer",
      {
        "preset": "conventionalcommits",
        "releaseRules": [
          { "type": "feat", "release": "minor" },
          { "type": "fix", "release": "patch" },
          { "type": "perf", "release": "patch" },
          { "type": "refactor", "release": "patch" },
          { "type": "style", "release": "patch" },
          { "type": "docs", "release": "patch" },
          { "type": "chore", "release": "patch" },
          { "type": "ci", "release": "patch" },
          { "type": "test", "release": "patch" },
          { "type": "build", "release": "patch" }
        ]
      }
    ],
    [
      "@semantic-release/release-notes-generator",
      {
        "preset": "conventionalcommits"
      }
    ],
    [
      "@semantic-release/changelog",
      {
        "changelogFile": "CHANGELOG.md"
      }
    ],
    [
      "@semantic-release/npm",
      {
        "npmPublish": false
      }
    ],
    [
      "@semantic-release/git",
      {
        "assets": ["package.json", "<LOCKFILE>", "CHANGELOG.md"],
        "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
      }
    ],
    "@semantic-release/github"
  ]
}

Replace <LOCKFILE> with package-lock.json (npm), yarn.lock (yarn), or pnpm-lock.yaml (pnpm). This ensures all conventional commit types trigger a release. The plugins must be installed as devDependencies because the shared workflow runs npx semantic-release which only provides the core package.


Step 3: Add commitlint

Run: <PACKAGE_MANAGER> install --save-dev @commitlint/cli @commitlint/config-conventional (Adjust for yarn/pnpm as above)

Create: commitlint.config.js

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'subject-case': [2, 'always', ['sentence-case']],
    'header-max-length': [2, 'always', 72],
  },
  helpUrl:
    'https://github.com/Advisa/<REPO_NAME>/blob/<DEFAULT_BRANCH>/.github/instructions/commit-messages.instructions.md',
};

Step 4: Add GitHub Copilot commit message instructions

Create: .github/instructions/commit-messages.instructions.md

---
applyTo: '**'
---

# Commit Message Guidelines

This project follows the [Conventional Commits](https://www.conventionalcommits.org/) specification.

## Format

<pre>
&lt;type&gt;(&lt;scope&gt;): &lt;subject&gt;

[optional body]

[optional footer(s)]
&lt;JIRA-ID&gt;
</pre>

## Jira ID Derivation

- **Derive the Jira ID from the current branch name** by matching the pattern `(UCL|INS|MYS|NV)-\d+`
  - Example: branch `UCL-804-Derive-jira-id-in-skill` -> `UCL-804`
  - Example: branch `INS-123-fix-validation` -> `INS-123`
- If no Jira ID can be extracted from the branch name, **omit it** from the commit message
- **Never invent or guess** Jira issue numbers

## Types

| Type       | Description                                            |
| ---------- | ------------------------------------------------------ |
| `feat`     | A new feature                                          |
| `fix`      | A bug fix                                              |
| `docs`     | Documentation only changes                             |
| `style`    | Formatting, missing semi-colons, etc. (no code change) |
| `refactor` | Code change that neither fixes a bug nor adds feature  |
| `perf`     | Performance improvement                                |
| `test`     | Adding or correcting tests                             |
| `build`    | Changes to build system or external dependencies       |
| `ci`       | Changes to CI configuration files and scripts          |
| `chore`    | Other changes that don't modify src or test files      |
| `revert`   | Reverts a previous commit                              |

## Rules

1. **Subject** must use sentence case (first word capitalized, rest lowercase unless proper nouns)
2. **Subject** must use imperative mood (e.g. "Add feature" not "Added feature")
3. **Header** (type + scope + subject) must not exceed 72 characters
4. **Header** should be understandable by a non-technical stakeholder
5. Do not end the subject with a period
6. Separate subject from body with a blank line
7. Wrap the body at 72 characters
8. Use the body to explain _what_ and _why_, not _how_
9. **No Co-Authored-By** -- do not add any co-authored-by lines

## Breaking Changes

Indicate breaking changes by adding `!` after the type/scope or by including a `BREAKING CHANGE:` footer:

<pre>
feat(api)!: Remove deprecated endpoints

BREAKING CHANGE: The /v1/users endpoint has been removed.
UCL-456
</pre>

## Atomic Commits

- Each commit should represent a single logical change
- Keep commits small and focused
- If a change touches multiple concerns, split it into separate commits

## Example

<pre>
feat(auth): Add JWT token refresh endpoint

Implement automatic token refresh to improve session handling.
The refresh endpoint validates the existing token and issues
a new one with an extended expiry.

UCL-804
</pre>

Step 5: Add VS Code settings for Copilot

Modify: .gitignore — find the .vscode line and change it to:

.vscode/*
!.vscode/settings.json

If there is no .vscode line, add these two lines.

Create or merge into .vscode/settings.json — if the file already exists, add this key to the existing object without overwriting other settings:

{
  "github.copilot.chat.commitMessageGeneration.instructions": [
    {
      "file": ".github/instructions/commit-messages.instructions.md"
    }
  ]
}

Step 6: Add AI coding tool commit skills

Delete: .agents/skills/commit/SKILL.md if it exists (old location).

Create: .claude/skills/commit/SKILL.md and .codex/skills/commit/SKILL.md (identical content):

First, determine the Jira ticket ID from the current branch:

1. Run `git rev-parse --abbrev-ref HEAD` to get the current branch name.
2. Look for a Jira ticket ID matching the pattern `(UCL|INS|MYS|NV)-\d+` in the branch name.
3. If found, use it as the Jira ID (e.g. `UCL-804` from branch `UCL-804-Derive-jira-id-in-skill`).
4. If **no match is found**, ask me for the Jira ticket ID before proceeding.

Then generate the commit:

5. Run `git diff --cached` to see the staged changes.
6. Analyze the diff and generate a commit message following the [Conventional Commits](https://www.conventionalcommits.org/) format:

<pre>
&lt;type&gt;(&lt;scope&gt;): &lt;subject&gt;

[optional body]

[optional footer(s)]
&lt;JIRA-ID&gt;
</pre>

## Types

| Type       | Description                                            |
| ---------- | ------------------------------------------------------ |
| `feat`     | A new feature                                          |
| `fix`      | A bug fix                                              |
| `docs`     | Documentation only changes                             |
| `style`    | Formatting, missing semi-colons, etc. (no code change) |
| `refactor` | Code change that neither fixes a bug nor adds feature  |
| `perf`     | Performance improvement                                |
| `test`     | Adding or correcting tests                             |
| `build`    | Changes to build system or external dependencies       |
| `ci`       | Changes to CI configuration files and scripts          |
| `chore`    | Other changes that don't modify src or test files      |
| `revert`   | Reverts a previous commit                              |

## Rules

- **scope**: Optional, describes the area of the codebase affected
- **subject**: Sentence case (first word capitalized, rest lowercase unless proper nouns), imperative mood (e.g. "Add feature" not "Added feature"), no trailing period, max 72 chars for the full header, should be understandable by a non-technical stakeholder
- **body**: Explain _what_ and _why_ (not _how_), wrap at 72 chars, separated from subject by a blank line
- **Jira ID**: Just the ID on its own line (e.g. `UCL-804`), no "Refs:" prefix. **Never invent or guess** Jira issue numbers
- **No Co-Authored-By**: Do not add any co-authored-by lines

## Breaking Changes

Indicate breaking changes by adding `!` after the type/scope or by including a `BREAKING CHANGE:` footer:

<pre>
feat(api)!: Remove deprecated endpoints

BREAKING CHANGE: The /v1/users endpoint has been removed.
UCL-456
</pre>

## Atomic Commits

- Each commit should represent a single logical change
- Keep commits small and focused
- If a change touches multiple concerns, split it into separate commits

7. Show me the commit message for approval before committing.
8. After approval, create the commit.

Step 7: Update README

  • Remove any stale CI badges (CircleCI, Travis, etc.)
  • Add a ## Commit Messages section to the README (or update existing one) with this content:
## Commit Messages

We follow [Conventional Commits](https://www.conventionalcommits.org/). Commit messages are enforced by [commitlint](https://commitlint.js.org/) in CI.

To generate a commit message with Claude Code, run `/commit` after staging your changes. GitHub Copilot also follows these conventions automatically via the instructions in `.github/instructions/`.

### Format

<pre>
&lt;type&gt;(&lt;optional scope&gt;): &lt;description&gt;

&lt;optional body&gt;

&lt;issue reference&gt;
</pre>

### Example

<pre>
feat(form): Add co-applicant income validation

The validation now checks that co-applicant income is within acceptable
range before submission. This prevents invalid applications from being
sent to the backend and provides immediate user feedback.

UCL-123
</pre>

### Rules

- Keep header within 72 characters (aim for 50 when possible)
- Word wrap body at 72 characters
- Use imperative mood: "Add feature" not "Added feature"
- Capitalize the first letter of the description
- Add a body only when the change needs explanation (what and why)
- One clear purpose per commit -- each commit should do one thing
- End with a Jira issue reference on its own line

### Types

| Type     | Description                                                           | Version Bump |
| -------- | --------------------------------------------------------------------- | ------------ |
| feat     | Add or remove a feature                                               | Minor        |
| fix      | A bug fix                                                             | Patch        |
| docs     | Documentation only changes                                            | Patch        |
| style    | Changes that do not affect the meaning of the code (formatting, etc.) | Patch        |
| refactor | A code change that neither fixes a bug nor adds a feature             | Patch        |
| perf     | A code change that improves performance                               | Patch        |
| test     | Adding missing tests or correcting existing tests                     | Patch        |
| build    | Changes that affect the build system or external dependencies         | Patch        |
| ci       | Changes to CI configuration files and scripts                         | Patch        |
| chore    | Changes which don't change source code or tests                       | Patch        |
| revert   | Revert a previous commit                                              | Varies       |

### Breaking Changes

Indicate breaking changes with `!` after the type/scope or by adding `BREAKING CHANGE:` in the commit body. This triggers a major version bump.

### Jira Issue Reference

Every commit must reference a Jira issue. Accepted project prefixes: **UCL**, **INS**, **MYS**, **NV**.

Step 8: Verify

  1. Run npx commitlint --from HEAD~1 --to HEAD --verbose to confirm the commitlint config loads correctly
  2. Run git status to review all changes
  3. Present a summary of everything that was created/modified

Commit ordering

When creating commits for this work, ensure every commit passes CI independently. The recommended order:

  1. Infrastructure commits first (e.g. .node-version) — no CI workflow exists yet, so nothing runs
  2. Tooling/lint commits next (e.g. ESLint, Prettier, code formatting) — still no CI workflow to fail
  3. CI workflow commit lastverify.yml and release.yml are added only after everything they validate (lint, commitlint) is already in place

This ensures that if someone bisects, every commit is green.

Validate your own commit messages against the rules in the commit skill (.claude/skills/commit/SKILL.md). In particular, ensure every header is 72 characters or fewer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment