Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/pr-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: PR Lint

# Commit convention gate feeding Linear releases (changie retirement).
# Validates PR commits AND the PR title (the PR title becomes the commit once we
# squash-merge). Conventional Commits + a Linear issue key on release-note types
# (see commitlint.config.js). No secrets, no self-hosted runner needed — runs on
# GitHub-hosted ubuntu-latest (free for this trust-safe lint).

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
contents: read
pull-requests: read

concurrency:
group: pr-lint-${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
commitlint:
name: Commit convention
runs-on: ubuntu-latest
# Same-repo PRs only; skip dependency bots (they carry no Linear key and are
# exempt "Maintenance" changes). Fork PRs are skipped here (contributors have no
# Linear access); a maintainer re-runs from an internal branch if needed.
if: >-
github.event.pull_request.head.repo.full_name == github.repository &&
github.actor != 'dependabot[bot]' &&
github.actor != 'renovate[bot]'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Lint PR commits
uses: wagoid/commitlint-github-action@v6
with:
configFile: commitlint.config.mjs

- name: Lint PR title (becomes the squash commit)
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
printf '%s\n' "$PR_TITLE" | npx --yes \
-p @commitlint/cli -p @commitlint/config-conventional \
commitlint --config commitlint.config.mjs
1 change: 1 addition & 0 deletions .omc/state/hud-stdin-cache.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"session_id":"69d8b816-f4fa-4732-852a-b55361c3b234","transcript_path":"/home/stammfrei/.claude/projects/-home-stammfrei-projects-cy-legacy/69d8b816-f4fa-4732-852a-b55361c3b234.jsonl","cwd":"/home/stammfrei/projects/cy-legacy/wt-cycloid-cli-ops1541","prompt_id":"853511e5-bcc6-4a41-80ae-c238653aa434","effort":{"level":"high"},"session_name":"orchestrator","model":{"id":"claude-opus-4-8","display_name":"Opus 4.8"},"workspace":{"current_dir":"/home/stammfrei/projects/cy-legacy/wt-cycloid-cli-ops1541","project_dir":"/home/stammfrei/projects/cy-legacy","added_dirs":[],"git_worktree":"wt-cycloid-cli-ops1541"},"version":"2.1.198","output_style":{"name":"default"},"cost":{"total_cost_usd":301.84669425000055,"total_duration_ms":247097298,"total_api_duration_ms":24876351,"total_lines_added":5016,"total_lines_removed":1169},"context_window":{"total_input_tokens":551221,"total_output_tokens":488,"context_window_size":1000000,"current_usage":{"input_tokens":2,"output_tokens":488,"cache_creation_input_tokens":2249,"cache_read_input_tokens":548970},"used_percentage":55,"remaining_percentage":45},"exceeds_200k_tokens":true,"fast_mode":false,"thinking":{"enabled":true},"rate_limits":{"five_hour":{"used_percentage":21,"resets_at":1783016400},"seven_day":{"used_percentage":17,"resets_at":1783288800}},"vim":{"mode":"INSERT"}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"timestamp": "2026-07-02T13:56:14.061Z",
"backgroundTasks": [],
"sessionStartTimestamp": "2026-07-01T09:18:11.998Z",
"sessionId": "69d8b816-f4fa-4732-852a-b55361c3b234"
}
37 changes: 37 additions & 0 deletions commitlint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Cycloid commit convention — feeds Linear releases (changie is being retired).
//
// Conventional Commits + a Linear issue key on release-note-bearing commits.
// Release-note types (feat/fix/perf) MUST reference a Linear issue key (e.g. BE-123)
// in the subject or body, so the release pipeline can link the issue to the Linear
// release. Maintenance types (chore/build/ci/docs/style/test/refactor/revert) are
// exempt — they land in the "Maintenance" bucket and need no key.
//
// ESM (.mjs) is required by wagoid/commitlint-github-action@v6.
// NOTE: intended to be extracted into a shared @cycloid/commitlint-config package
// once the monorepo lands; kept inline per-repo for now.

const RELEASE_NOTE_TYPES = ['feat', 'fix', 'perf'];
const LINEAR_KEY = /\b[A-Z]{2,}-\d+\b/;

export default {
extends: ['@commitlint/config-conventional'],
plugins: [
{
rules: {
'linear-key-on-release-types': (parsed) => {
const { type, header, body } = parsed;
if (!type || !RELEASE_NOTE_TYPES.includes(type)) return [true];
const hasKey = LINEAR_KEY.test(`${header || ''}\n${body || ''}`);
return [
hasKey,
`a "${type}" commit must reference a Linear issue key (e.g. BE-123) ` +
`in the subject or body so it can be linked to the Linear release`,
];
},
},
},
],
rules: {
'linear-key-on-release-types': [2, 'always'],
},
};
Loading