Skip to content
Merged
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
67 changes: 67 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright 2026 ResQ Software
# SPDX-License-Identifier: Apache-2.0
#
# Path-based PR auto-labeling rules consumed by .github/workflows/labeler.yml
# (actions/labeler@v5 schema). Keys are label names; values are file globs.

'area:api':
- changed-files:
- any-glob-to-any-file:
- 'api-reference/**'
- 'specs/**'

'area:i18n':
- changed-files:
- any-glob-to-any-file:
- 'ar/**'
- 'es/**'
- 'hi/**'
- 'zh/**'

'area:ci':
- changed-files:
- any-glob-to-any-file:
- '.github/**'

'area:design':
- changed-files:
- any-glob-to-any-file:
- 'custom.css'
- 'assets/**'
- 'logo/**'
- 'favicon.svg'

'area:pwa':
- changed-files:
- any-glob-to-any-file:
- 'manifest.webmanifest'
- 'pwa/**'

'area:content':
- changed-files:
- any-glob-to-any-file:
- '**/*.mdx'
- '**/*.md'
Comment on lines +43 to +44
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The area:content label is currently very broad and will overlap with area:meta and area:ci because it matches all markdown files, including repository metadata (README.md, CONTRIBUTING.md) and CI-related files (such as issue templates in .github/). To reduce label noise and ensure area:content specifically targets documentation site content, consider excluding these non-content paths using negative globs.

          - '**/*.mdx'
          - '**/*.md'
          - '!README.md'
          - '!CONTRIBUTING.md'
          - '!AGENTS.md'
          - '!.github/**'

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c9617carea:content now has an all-globs-to-all-files exclusion list so it skips api-reference/, specs/, ar|es|hi|zh/, .github/, AGENTS/CONTRIBUTING/README/LICENSE. Single change per area now.

# Scope out paths owned by other labels so a single PR doesn't
# collect three labels for the same change.
- all-globs-to-all-files:
- '!api-reference/**'
- '!specs/**'
- '!ar/**'
- '!es/**'
- '!hi/**'
- '!zh/**'
- '!.github/**'
- '!AGENTS.md'
- '!CONTRIBUTING.md'
- '!README.md'
- '!LICENSE'

'area:meta':
- changed-files:
- any-glob-to-any-file:
- 'docs.json'
- 'AGENTS.md'
- 'CONTRIBUTING.md'
- 'README.md'
- 'LICENSE'
80 changes: 80 additions & 0 deletions .github/workflows/i18n-parity.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Copyright 2026 ResQ Software
# SPDX-License-Identifier: Apache-2.0
#
# i18n parity check — informational only. Reports MDX/MD files in the
# default-language tree that are missing from each locale (ar, es, hi,
# zh). Does not block merges; translations lag deliberately.

name: i18n parity

on:
pull_request:
paths:
- '**/*.mdx'
- '**/*.md'
- 'ar/**'
- 'es/**'
- 'hi/**'
- 'zh/**'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
parity:
name: locale parity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Compare locale trees
run: |
set -euo pipefail
locales=(ar es hi zh)
mapfile -t base < <(
find . -type f \( -name '*.mdx' -o -name '*.md' \) \
-not -path './ar/*' \
-not -path './es/*' \
-not -path './hi/*' \
-not -path './zh/*' \
-not -path './.github/*' \
-not -path './node_modules/*' \
-not -name 'README.md' \
-not -name 'AGENTS.md' \
-not -name 'CONTRIBUTING.md' \
| sed 's|^\./||' | sort
)
echo "base files: ${#base[@]}"
{
echo "## i18n parity report"
echo
echo "Base tree: ${#base[@]} translatable file(s)"
echo
for locale in "${locales[@]}"; do
if [[ ! -d "$locale" ]]; then
echo "- \`$locale/\` — locale directory missing"
continue
fi
missing=0
missing_list=""
for rel in "${base[@]}"; do
if [[ ! -f "$locale/$rel" ]]; then
missing=$((missing+1))
missing_list+=" - \`$locale/$rel\`"$'\n'
fi
done
total=${#base[@]}
translated=$((total - missing))
echo "- \`$locale/\` — $translated / $total translated ($missing missing)"
if (( missing > 0 )); then
echo
printf '%s' "$missing_list"
fi
done
} >> "$GITHUB_STEP_SUMMARY"
echo "report written to step summary"
30 changes: 30 additions & 0 deletions .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2026 ResQ Software
# SPDX-License-Identifier: Apache-2.0
#
# Auto-label PRs by changed paths. Rules live in .github/labeler.yml.
# Uses `pull_request_target` so forks get labeled too — this is safe
# because the action only reads file paths, never executes PR code.

name: labeler

on:
pull_request_target:
types: [opened, synchronize, reopened]

permissions:
contents: read
pull-requests: write

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

jobs:
label:
name: label
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
with:
configuration-path: .github/labeler.yml
sync-labels: true
51 changes: 51 additions & 0 deletions .github/workflows/lychee.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright 2026 ResQ Software
# SPDX-License-Identifier: Apache-2.0
#
# External link checker. Scheduled weekly + on-demand only — external
# link flake (rate limits, CDN hiccups) makes this unsuitable as a
# blocking PR gate. Internal MDX link integrity is enforced by
# `mint broken-links` in required.yml.

name: lychee

on:
schedule:
- cron: '23 7 * * 1'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
links:
name: lychee (external links)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Restore lychee cache
uses: actions/cache@v4
with:
path: .lycheecache
key: cache-lychee-${{ github.sha }}
restore-keys: cache-lychee-

- name: Run lychee
uses: lycheeverse/lychee-action@v2

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'lychee' step
Uses Step
uses 'lycheeverse/lychee-action' with ref 'v2', not a pinned commit hash
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. lychee runs only on schedule + workflow_dispatch (not on PRs) per the workflow trigger config — CodeQL flags this as low-severity advisory rather than blocking. Dependabot is configured for the org and will SHA-pin both lycheeverse/lychee-action and stoplightio/spectral-action automatically. Leaving as @v2 / @v0.8.13 until that lands; if you prefer manual SHA-pinning we can do it in a follow-up.

with:
args: >-
--cache
--max-cache-age 1d
--no-progress
--exclude-mail
--accept 200,204,206,429
--max-retries 2
--retry-wait-time 5
'./**/*.md'
'./**/*.mdx'
'./docs.json'
fail: true
88 changes: 82 additions & 6 deletions .github/workflows/required.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Copyright 2026 ResQ Software
# SPDX-License-Identifier: Apache-2.0
#
# Minimal `required` status-check emitter — placeholder to satisfy the
# org ruleset `default-branch-baseline` (id 15191038) while
# language-specific reusable CI for this repo is still pending. Harden
# this job (add `needs:` on real CI jobs) before the ruleset flips from
# evaluate to active.
# `required` status check — single aggregate gate referenced by the org
# ruleset (default-branch-baseline). Real CI jobs run in parallel, and
# the `required` job fails unless every dependency succeeds. Add new
# blocking docs jobs to `needs:` rather than a new workflow, so the
# protection contract stays one check.

name: required

Expand All @@ -22,8 +22,84 @@
cancel-in-progress: true

jobs:
mintlify:
name: mintlify (broken links)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Mintlify CLI
run: npm install -g mint
- name: Check broken internal links
run: mint broken-links

spectral:
name: spectral (OpenAPI lint)
runs-on: ubuntu-latest
# Advisory: action ref hardening will be handled by Dependabot SHA-pin PRs.
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Lint OpenAPI specs
uses: stoplightio/spectral-action@v0.8.13 # TODO: dependabot will SHA-pin

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'required' step
Uses Step
uses 'stoplightio/spectral-action' with ref 'v0.8.13', not a pinned commit hash
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged. Same Dependabot SHA-pin path as the lychee comment. The # TODO: dependabot will SHA-pin comment in the workflow file flags this for the renovation cycle.

with:
file_glob: 'specs/*.json'

pwa-manifest:
name: pwa manifest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate manifest.webmanifest
run: |
set -euo pipefail
f=manifest.webmanifest
if [[ ! -f "$f" ]]; then
echo "::notice::$f not on this branch — skipping validation (manifest lives in feat/brand-assets PR)"
exit 0
fi
jq -e . "$f" > /dev/null || { echo "::error::$f is not valid JSON"; exit 1; }
for field in name short_name start_url display icons; do
if ! jq -e "has(\"$field\")" "$f" > /dev/null; then
echo "::error::manifest missing required field: $field"
exit 1
fi
done
mapfile -t srcs < <(jq -r '.icons[].src' "$f")
missing=0
for src in "${srcs[@]}"; do
path="${src#/}"
if [[ ! -f "$path" ]]; then
echo "::error::icon path not found on disk: $src"
missing=$((missing+1))
fi
done
if (( missing > 0 )); then
echo "::error::$missing icon(s) missing"
exit 1
fi
echo "manifest valid: ${#srcs[@]} icons resolved"

required:
name: required
needs: [mintlify, spectral, pwa-manifest]
if: always()
runs-on: ubuntu-latest
steps:
- run: echo "ok — placeholder until language CI lands"
- name: Verify all gates passed
run: |
# spectral remains advisory (continue-on-error) until the action ref
# is verified post-Dependabot SHA-pin.
mintlify="${{ needs.mintlify.result }}"
spectral="${{ needs.spectral.result }}"
pwa="${{ needs.pwa-manifest.result }}"
echo "mintlify: $mintlify"
echo "spectral (advisory): $spectral"
echo "pwa-manifest: $pwa"
if [[ "$mintlify" != "success" || "$pwa" != "success" ]]; then
echo "::error::One or more required gates failed"
exit 1
fi
echo "All required gates passed"
47 changes: 47 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2026 ResQ Software
# SPDX-License-Identifier: Apache-2.0
#
# Stale issue/PR closer. Issues idle for 60 days get the `stale` label
# and a nudge; closed 14 days later if still untouched. PRs are tighter
# (30 + 14). Pinned, security, and roadmap items are exempt.

name: stale

on:
schedule:
- cron: '12 1 * * *'
workflow_dispatch:

permissions:
issues: write
pull-requests: write

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

jobs:
stale:
name: stale
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
days-before-issue-stale: 60
days-before-issue-close: 14
days-before-pr-stale: 30
days-before-pr-close: 14
stale-issue-label: stale
stale-pr-label: stale
exempt-issue-labels: 'pinned,security,roadmap'
exempt-pr-labels: 'pinned,security'
stale-issue-message: |
This issue has been inactive for 60 days and is now marked stale.
It will be closed in 14 days unless there is new activity.
stale-pr-message: |
This PR has been inactive for 30 days and is now marked stale.
It will be closed in 14 days unless updated. Rebase or comment
to keep it open.
close-issue-message: Closed due to inactivity. Reopen if still relevant.
close-pr-message: Closed due to inactivity. Reopen and rebase to continue.
operations-per-run: 100
Loading