From c336f54315f4d3a8f80c2b8c82a0055037e7bf76 Mon Sep 17 00:00:00 2001
From: Patrick Kollitsch <davidsneighbourdev+gh@gmail.com>
Date: Fri, 15 May 2026 01:50:22 +0000
Subject: [PATCH] ci: update branch setup/rules and CONTRIBUTING.md

---
 /dev/null                               |   25 ----
 .vscode/extensions.json                 |   14 +-
 CONTRIBUTING.md                         |   91 +++++++++++---
 .vscode/settings.json                   |    3 
 .yamllint.yml                           |   61 ++++++++++
 .github/dependabot.yml                  |   27 +++-
 .lintstagedrc.js                        |    6 
 .github/workflows/branch-protection.yml |   73 ++++++++++++
 .github/workflows/quickstart.yml        |    7 
 9 files changed, 236 insertions(+), 71 deletions(-)

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 6f4a854..931fe90 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -10,11 +10,14 @@
       day: friday
       time: "18:00"
       timezone: Asia/Bangkok
+    cooldown:
+      default-days: 14
+      semver-major-days: 14
+      semver-minor-days: 7
+      semver-patch-days: 4
     assignees:
       - davidsneighbour
-    reviewers:
-      - davidsneighbour
-    target-branch: development
+    target-branch: maintenance
     open-pull-requests-limit: 999
     pull-request-branch-name:
       separator: /
@@ -28,11 +31,14 @@
       day: friday
       time: "18:00"
       timezone: Asia/Bangkok
+    cooldown:
+      default-days: 14
+      semver-major-days: 14
+      semver-minor-days: 7
+      semver-patch-days: 4
     assignees:
       - davidsneighbour
-    reviewers:
-      - davidsneighbour
-    target-branch: development
+    target-branch: maintenance
     open-pull-requests-limit: 999
     pull-request-branch-name:
       separator: /
@@ -46,11 +52,14 @@
       day: friday
       time: "18:00"
       timezone: Asia/Bangkok
+    cooldown:
+      default-days: 14
+      semver-major-days: 14
+      semver-minor-days: 7
+      semver-patch-days: 4
     assignees:
       - davidsneighbour
-    reviewers:
-      - davidsneighbour
-    target-branch: development
+    target-branch: maintenance
     open-pull-requests-limit: 999
     pull-request-branch-name:
       separator: /
diff --git a/.github/workflows/branch-protection-main.yml b/.github/workflows/branch-protection-main.yml
deleted file mode 100644
index 117f713..0000000
--- a/.github/workflows/branch-protection-main.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Validate main branch source
-
-on:
-  pull_request:
-    branches:
-      - main
-
-permissions:
-  contents: read
-  pull-requests: read
-
-jobs:
-  validate-source-branch:
-    name: Require development as source branch
-    runs-on: ubuntu-latest
-
-    steps:
-      - name: Validate source branch
-        env:
-          HEAD_REF: ${{ github.head_ref }}
-        run: |
-          if [ "${HEAD_REF}" != "development" ]; then
-            echo "::error::Pull requests into main must come from development. Current source branch: ${HEAD_REF}"
-            exit 1
-          fi
\ No newline at end of file
diff --git a/.github/workflows/branch-protection.yml b/.github/workflows/branch-protection.yml
new file mode 100644
index 0000000..45499b7
--- /dev/null
+++ b/.github/workflows/branch-protection.yml
@@ -0,0 +1,73 @@
+name: Validate pull request rules
+
+on:
+  pull_request:
+
+permissions:
+  contents: read
+  pull-requests: read
+
+jobs:
+  validate-main-source-branch:
+    name: Require staging or maintenance as source branch for main
+    runs-on: ubuntu-latest
+    if: github.base_ref == 'main'
+
+    steps:
+      - name: Validate source branch
+        shell: bash
+        env:
+          HEAD_REF: ${{ github.head_ref }}
+        run: |
+          set -euo pipefail
+
+          if [ "${HEAD_REF}" != "staging" ] && [ "${HEAD_REF}" != "maintenance" ]; then
+            echo "::error::Pull requests into main must come from staging or maintenance. Current source branch: ${HEAD_REF}"
+            exit 1
+          fi
+
+  validate-staging-source-branch:
+    name: Require development or maintenance as source branch for staging
+    runs-on: ubuntu-latest
+    if: github.base_ref == 'staging'
+
+    steps:
+      - name: Validate source branch
+        shell: bash
+        env:
+          HEAD_REF: ${{ github.head_ref }}
+        run: |
+          set -euo pipefail
+
+          if [ "${HEAD_REF}" != "development" ] && [ "${HEAD_REF}" != "maintenance" ]; then
+            echo "::error::Pull requests into staging must come from development or maintenance. Current source branch: ${HEAD_REF}"
+            exit 1
+          fi
+
+  protect-package-lock:
+    name: Block package-lock.json outside maintenance
+    runs-on: ubuntu-latest
+    if: github.base_ref != 'maintenance'
+
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+        with:
+          fetch-depth: 0
+          persist-credentials: false
+
+      - name: Fail if package-lock.json changed outside maintenance
+        shell: bash
+        env:
+          BASE_SHA: ${{ github.event.pull_request.base.sha }}
+          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+          BASE_REF: ${{ github.base_ref }}
+        run: |
+          set -euo pipefail
+
+          changed_files=$(git diff --name-only "${BASE_SHA}...${HEAD_SHA}")
+
+          if echo "${changed_files}" | grep -Fxq "package-lock.json"; then
+            echo "::error file=package-lock.json::package-lock.json may only be changed in PRs targeting maintenance. Current target branch: ${BASE_REF}"
+            exit 1
+          fi
\ No newline at end of file
diff --git a/.github/workflows/quickstart.yml b/.github/workflows/quickstart.yml
index 6bff059..a9ece81 100644
--- a/.github/workflows/quickstart.yml
+++ b/.github/workflows/quickstart.yml
@@ -20,17 +20,18 @@
 
     steps:
       - name: Check out repository
-        uses: actions/checkout@v6
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
         with:
+          persist-credentials: false
           submodules: false
 
       - name: Set up Node.js
-        uses: actions/setup-node@v6
+        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
         with:
           node-version: "24"
 
       - name: Set up Hugo
-        uses: peaceiris/actions-hugo@v3
+        uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78
         with:
           hugo-version: "latest"
           extended: true
diff --git a/.lintstagedrc.js b/.lintstagedrc.js
index 70217bc..fe4f6f2 100644
--- a/.lintstagedrc.js
+++ b/.lintstagedrc.js
@@ -6,16 +6,14 @@
  */
 export default {
 	"*.{json,jsonc}": ["biome check --write --staged"],
-	// '.github/workflows/**/*.y(a?)ml': [
-	//   'zizmor --no-exit-codes',
-	// ],
+	".github/{workflows/**/*.y(a?)ml,dependabot.yml}": ["zizmor --no-exit-codes"],
 	"package-lock.json": [
 		"lockfile-lint --path package-lock.json --validate-https --allowed-hosts npm",
 	],
 	"*.{ts,tsx,(m|c)js,jsx}": (/** @type {string[]} */ files) => {
 		return [`biome check --write --no-errors-on-unmatched ${files.join(" ")}`];
 	},
-	// '*.yaml': ['yamllint -c .yamllint.yml'],
+	"*.yaml": ["yamllint -c .yamllint.yml"],
 	// '*.{scss,css}': ['stylelint --fix', "prettier --write"],
 	// '*.{png,jpeg,jpg,gif,svg}': [
 	//   'imagemin-lint-staged' // @davidsneighbour/imagemin-lint-staged
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 50aaa68..d438e75 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,7 +1,9 @@
 {
-    "recommendations": [
-        "pkief.material-icon-theme",
-        "yzhang.markdown-all-in-one",
-        "davidanson.vscode-markdownlint"
-    ]
-}
\ No newline at end of file
+	"recommendations": [
+		"pkief.material-icon-theme",
+		"yzhang.markdown-all-in-one",
+		"davidanson.vscode-markdownlint",
+		"zizmor.zizmor-vscode",
+		"redhat.vscode-yaml"
+	]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index e22b55e..6600c92 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -27,5 +27,6 @@
 		"theme.toml": "go.mod, go.sum",
 		"README.md": "CHANGELOG.md, LICENSE.md, CONTRIBUTING.md, RELEASES.md, DESIGN.md"
 	},
-	"window.autoDetectColorScheme": false
+	"window.autoDetectColorScheme": false,
+	"markdown.extension.tableFormatter.enabled": false
 }
diff --git a/.yamllint.yml b/.yamllint.yml
new file mode 100644
index 0000000..533f78e
--- /dev/null
+++ b/.yamllint.yml
@@ -0,0 +1,61 @@
+extends: default
+
+locale: en_US.UTF-8
+
+yaml-files:
+  - "*.yaml"
+  - "*.yml"
+  - "*.md"
+
+ignore: |
+  .github/workflows/update-netlify.yml
+  .github/workflows/daily-audit.yml
+  .github/workflows/codeql-analysis.yml
+  .github/dependabot.yml
+
+# https://yamllint.readthedocs.io/en/stable/rules.html
+rules:
+  braces: enable
+  brackets: enable
+  colons: enable
+  commas: enable
+  comments:
+    require-starting-space: true
+    ignore-shebangs: true
+    min-spaces-from-content: 1
+    level: warning
+  comments-indentation:
+    level: warning
+  document-end: disable
+  document-start: disable
+  empty-lines:
+    max: 1
+    max-start: 0
+    max-end: 1
+  empty-values: disable
+  hyphens: enable
+  indentation:
+    spaces: consistent
+    indent-sequences: consistent
+    check-multi-line-strings: true
+  key-duplicates: enable
+  key-ordering: disable
+  line-length:
+    max: 80
+    level: warning
+  new-line-at-end-of-file: enable
+  new-lines:
+    type: unix
+  octal-values:
+    forbid-implicit-octal: true
+    forbid-explicit-octal: true
+  quoted-strings:
+    quote-type: double
+    required: false
+    # required: only-when-needed
+    extra-required: ["^http://", "^https://", "^ftp://"]
+    extra-allowed: []
+  trailing-spaces: enable
+  truthy:
+    level: warning
+    check-keys: false
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index eade183..fbba7d8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,6 +6,9 @@
 * [Release Process](#release-process)
 * [Before You Start](#before-you-start)
 * [Reporting Bugs and Requesting Features](#reporting-bugs-and-requesting-features)
+* [Branch Strategy](#branch-strategy)
+  * [Long-lived branches](#long-lived-branches)
+  * [Branch naming](#branch-naming)
 * [Pull Request Workflow](#pull-request-workflow)
 * [Circumventing Git Hooks](#circumventing-git-hooks)
 * [Documentation Contributions](#documentation-contributions)
@@ -37,27 +40,27 @@
 1. Use a compatible Hugo version (see [`config/_default/module.toml`](https://github.com/gohugo-ananke/ananke/blob/main/config/_default/module.toml) for the current state).
 2. Install dependencies:
 
-   ```bash
-   npm install
-   ```
+ ```bash
+ npm install
+ ```
 
-3. Run a local preview via `npm run` instead of just calling `hugo server`:
+1. Run a local preview via `npm run` instead of just calling `hugo server`:
 
-   ```bash
-   npm run server
-   ```
+ ```bash
+ npm run server
+ ```
 
-   This runs the documentation site from `site/` using contents from `docs/` with local configuration.
+ This runs the documentation site from `site/` using contents from `docs/` with local configuration.
 
-4. Follow the coding style and format commit messages as described in the conventional commits specification (for example: `docs: add troubleshooting section` or `fix: correct hero image path`).
+1. Follow the coding style and format commit messages as described in the conventional commits specification (for example: `docs: add troubleshooting section` or `fix: correct hero image path`).
 
-5. Make sure to install git hooks for linting and testing before you push changes:
+2. Make sure to install git hooks for linting and testing before you push changes:
 
-   ```bash
-   npm run prepare
-   ```
+ ```bash
+ npm run prepare
+ ```
 
-   This command is run automatically after `npm install` but you can run it manually to set up hooks in an existing clone or update changed hooks. It uses `simple-git-hooks` to install a commit hook that runs `lint-staged` for markdown files, which in turn runs linting tasks on staged files.
+ This command is run automatically after `npm install` but you can run it manually to set up hooks in an existing clone or update changed hooks. It uses `simple-git-hooks` to install a commit hook that runs `lint-staged` for markdown files, which in turn runs linting tasks on staged files.
 
 ## Reporting Bugs and Requesting Features
 
@@ -65,6 +68,47 @@
 * Start feature or idea discussions in [GitHub Discussions](https://github.com/gohugo-ananke/ananke/discussions).
 * Include clear reproduction steps, expected behaviour, actual behaviour, and versions (`hugo version`, OS, browser if relevant).
 
+## Branch Strategy
+
+```mermaid
+flowchart LR
+ feature["feature/*, fix/*, docs/*, refactor/*"] --> development
+ development --> staging
+ maintenance --> staging
+ staging --> main
+ main --> staging
+ staging --> development
+```
+
+This repository uses a linear, rebase-based branch model. Long-lived branches MUST stay connected to `main`, and merge commits MUST NOT be introduced.
+
+### Long-lived branches
+
+| Branch  | Purpose  | Release role | Write policy | Merge  |
+| --- | --- | --- | --- | --- |
+| `main`  | Stable source of truth | releases | Protected. Only receives reviewed PRs from `staging` or `maintenance`. | Rebase |
+| `staging` | Pre-release integration | pre-releases | Protected. Only receives reviewed PRs from `development`.  | Rebase |
+| `development` | Active development | none | Feature, fix, chore, and documentation PRs target this branch. | Squash |
+| `maintenance` | Dependency maintenance  | none | Maintainer-only branch for dependency version updates. | Rebase |
+
+### Branch naming
+
+Use short-lived branches for regular work:
+
+* `feat/<topic>`
+* `fix/<topic>`
+* `docs/<topic>`
+* `chore/<topic>`
+* `refactor/<topic>`
+
+Dependency update branches MUST target `maintenance` unless the change is part of an intentional feature branch and does not touch lock files.
+
+After a successful rebase between those branches, push with lease:
+
+```bash
+git push --force-with-lease
+```
+
 ## Pull Request Workflow
 
 1. Fork the repository and create a focused branch.
@@ -72,16 +116,17 @@
 3. Update docs for all user-facing changes.
 4. Run quality checks locally:
 
-   ```bash
-   npm run lint:markdown
-   ```
+ ```bash
+ npm run lint:markdown
+ ```
 
-5. If your change affects behaviour, validate with Hugo locally (for example `hugo` or `hugo server` in the relevant project).
-6. Open a pull request with:
-   * a clear summary,
-   * motivation/context,
-   * screenshots when UI/visual output changes,
-   * linked issues (for example: `Fixes #123`).
+1. If your change affects behaviour, validate with Hugo locally (for example `hugo` or `hugo server` in the relevant project).
+2. Open a pull request with:
+
+* a clear summary,
+* motivation/context,
+* screenshots when UI/visual output changes,
+* linked issues (for example: `Fixes #123`).
 
 ## Circumventing Git Hooks
 

--
Gitblit v1.10.0