Skip to content
Closed
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
72 changes: 72 additions & 0 deletions .github/workflows/spec-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Spec sync

# Enforces that a change to a header sub-domain (include/morph/<sub>/**) is
# accompanied by a change to the matching design-spec folder (docs/spec/<sub>/**),
# so the specs cannot silently drift behind the code they describe.
#
# The gate is skipped when the PR carries the label "no docs update" — a visible,
# reviewable opt-out for changes that genuinely need no spec update.

on:
pull_request:
branches:
- master
# `labeled`/`unlabeled` so adding or removing the escape-hatch label re-runs
# the gate instead of leaving a stale result.
types: [opened, synchronize, reopened, labeled, unlabeled]

permissions:
contents: read

jobs:
spec-sync:
name: Header ↔ spec sync
runs-on: ubuntu-24.04
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no docs update') }}
steps:
- name: Checkout (full history)
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Enforce header ↔ spec synchronization
env:
BASE_REF: ${{ github.base_ref }}
run: |
set -euo pipefail

git fetch --no-tags --depth=1 origin "$BASE_REF"
base="$(git merge-base HEAD "origin/$BASE_REF")"
echo "Comparing against merge-base $base (origin/$BASE_REF)"

changed="$(git diff --name-only "$base" HEAD)"
echo "── Changed files ──"
echo "$changed"
echo "───────────────────"

# Sub-domains that have a mirrored spec folder. include/morph/detail/**
# and include/morph/qt/** have no specs and are intentionally exempt.
subdomains="core journal offline session forms util"

missing=""
for sub in $subdomains; do
if echo "$changed" | grep -qE "^include/morph/$sub/"; then
if ! echo "$changed" | grep -qE "^docs/spec/$sub/"; then
missing="$missing $sub"
fi
fi
done

if [ -n "$missing" ]; then
echo ""
echo "::error::Header changes without a matching spec update in:$missing"
for sub in $missing; do
echo " - include/morph/$sub/** changed but docs/spec/$sub/** did not."
done
echo ""
echo "Update the design spec(s) for the affected sub-domain(s), or add the"
echo "label 'no docs update' to this PR if no spec change is warranted."
exit 1
fi

echo "Spec sync OK: every touched header sub-domain has a matching spec change."
3 changes: 3 additions & 0 deletions include/morph/core/_gate_selftest.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
// Throwaway fixture to exercise the spec-sync CI gate. Not part of the library.
Loading