Skip to content
ForumViriumHelsinkiPublic

About

Organization profile, community health files, and reusable workflow templates

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

ForumViriumHelsinki/.github

Organization-wide GitHub configuration for Forum Virium Helsinki.

What's here

Path Purpose
profile/README.md Org profile shown on github.com/ForumViriumHelsinki
CODE_OF_CONDUCT.md Default code of conduct for all repos
CONTRIBUTING.md Default contribution guidelines for all repos
SECURITY.md Security vulnerability reporting policy
SUPPORT.md Where to get help
.github/ISSUE_TEMPLATE/ Default issue templates for all repos
.github/PULL_REQUEST_TEMPLATE.md Default PR template for all repos
.github/workflows/reusable-*.yml Reusable workflows callable from any FVH repo
workflow-templates/ Starter workflows shown in Actions tab for new repos
.rulesync/rules/ Canonical AI coding rules for FVH repos; the only place to edit them (how to sync)
rulesync.jsonc rulesync targets and features for regenerating this repo; reusable-sync-ai-rules.yml passes its features as flags and accepts a subset of its targets through the targets input, so a consuming repo needs no copy
.claude/rules/, .agents/rules/, .cursor/rules/, .github/instructions/ Tool-specific rule copies generated by rulesync generate; do not edit

Reusable Workflows

Call these from any FVH repo using uses: ForumViriumHelsinki/.github/.github/workflows/<name>.yml@main.

Infrastructure

Workflow Description
reusable-container-build.yml PR phase: build and push :next-{version} pre-release image to GHCR
reusable-container-release.yml Release phase: promote pre-built image to semver tags (or fallback rebuild) + Trivy scan
reusable-release-please.yml Automated releases via release-please
reusable-auto-merge-image-updater.yml Auto-merge ArgoCD Image Updater PRs (single-job, push-triggered)
reusable-fix-release-conflicts.yml Auto-resolve release-please merge conflicts
reusable-clear-autorelease-labels.yml Manual escape hatch: strip a stale autorelease: pending label from closed PRs to unclog release-please
reusable-renovate.yml Dependency updates via Renovate — called centrally from the infrastructure repo only; application repos must not call it
reusable-enforce-conventional-commits.yml Auto-fix PR titles to conventional-commit format (github-script, no AI)
reusable-npm-publish.yml Publish an npm package via OIDC trusted publishing (no NPM_TOKEN; the caller's job grants id-token: write)
reusable-bun-ci.yml Pull-request gate for bun/TypeScript repos: bun install --frozen-lockfile, then the repo's own typecheck (opt-in), test and build commands; optional coverage artifact; no secrets
reusable-gitleaks.yml For public repos: gitleaks over the checked-out tree with the default rules plus the FVH GCP-identifier rule from .gitleaks.toml (embedded, drift-tested); enforcement: block|warn; release binary, no secrets
reusable-claude.yml Claude Code @-mention support in issues and PRs

Automation (Claude-powered)

Workflow Description
reusable-claude-review.yml Automated PR review
reusable-auto-fix.yml Analyze failed workflow runs, then auto-fix (commit + push) or open an issue
reusable-auto-resolve-conflicts.yml Resolve PR merge conflicts with Claude Code; stale automated PRs are closed instead

AI coding rules

Workflow Description
reusable-sync-ai-rules.yml Open a PR in the calling repo that syncs rules from .rulesync/rules/ via rulesync (the caller sets the trigger)

Security (Claude-powered)

Workflow Description
reusable-security-secrets.yml Detect leaked secrets and credentials
reusable-security-deps.yml Dependency vulnerability audit
reusable-security-owasp.yml OWASP Top 10 static analysis

Quality (Claude-powered)

Workflow Description
reusable-quality-code-smell.yml Code smell and anti-pattern detection
reusable-quality-async.yml Async pattern validation
reusable-quality-typescript.yml TypeScript strictness analysis

Accessibility (Claude-powered)

Workflow Description
reusable-a11y-aria.yml ARIA pattern correctness
reusable-a11y-wcag.yml WCAG 2.1 compliance check

Where the Security, Quality and Accessibility findings appear

These eight workflows never post a PR comment; Claude is granted no GitHub write tool. Each finding goes to the run's job summary and to a file annotation on the PR's Files changed tab (error level for the workflow's blocking severities, warning otherwise). GitHub shows at most 10 annotations per level per step, so the job summary is the complete list. A run that analysed nothing (no changed file matched, or the diff exceeded max-diff-lines) says so in its summary, so its green check does not read as a clean scan.

A run in which Claude finishes but returns no structured output ends green with a warning that there is no verdict; re-run it to get one. The job goes red when the analysis itself failed (an API error, the max-turns limit, or the max-budget-usd ceiling, which defaults to 5 USD), and, when enabled, on blocking findings: reusable-security-owasp.yml with fail-on-critical (default true) and reusable-security-deps.yml with fail-on-high (default false) fail when a blocking finding is itemised or the reported count of them is above zero.

Usage Example

The container workflows use a build-once/promote pattern: images are built during the release-please PR and promoted (manifest-only retag) on tag push — no redundant rebuilds.

# In your repo's .github/workflows/container-build.yml
name: Build and release container

on:
  push:
    tags: ['my-app-v*.*.*']
  pull_request:
    branches: [main]

jobs:
  # PR phase: build :next-{version} image for later promotion
  build:
    if: startsWith(github.head_ref, 'release-please--branches--main')
    uses: ForumViriumHelsinki/.github/.github/workflows/reusable-container-build.yml@main
    with:
      image-name: forumviriumhelsinki/my-app

  # Release phase: promote pre-built image to semver tags (seconds, not minutes)
  release:
    if: github.event_name == 'push'
    uses: ForumViriumHelsinki/.github/.github/workflows/reusable-container-release.yml@main
    with:
      image-name: forumviriumhelsinki/my-app
      tag-prefix: my-app-v

  # Security scan on PRs
  security:
    if: github.event_name == 'pull_request'
    uses: ForumViriumHelsinki/.github/.github/workflows/reusable-security-owasp.yml@main
    secrets:
      CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

CLAUDE_CODE_OAUTH_TOKEN is an organization secret granted only to repositories that carry the claude topic. A Claude-powered caller in a repository without that topic gets no token and fails.

tag-prefix must prefix every release tag the caller triggers on: the release workflow strips it and derives all image tags from what remains (my-app-v1.2.3 → 1.2.3, 1.2, 1, latest; a pre-release such as my-app-v1.2.3-rc.1 → 1.2.3-rc.1 only). A tag the prefix does not match fails the run with an ::error::.

Validating changes

Every FVH repo calls these workflows at @main, so .github/workflows/lint.yml checks each PR and push to main: every workflow and template parses as YAML, actionlint and gitleaks pass, the generated rule copies match .rulesync/rules/, the Python blocks in .rulesync/rules/ are in ruff 0.16.9 style (--isolated --line-length 88, as the consuming repos that run ruff format --check see them), the shipped fetch and generate steps of reusable-sync-ai-rules.yml produce one file per rule for every target in a scratch repo (the path consumers run, which reads no rulesync.jsonc), and every test under .github/tests/ passes. just lint and just test run the same commands locally, and just sync-smoke (network and a GitHub token) the consumer path.

The tools that gate fetches run at exact versions (yq and Node come with the runner image): rulesync in lint.yml, reusable-sync-ai-rules.yml, the justfile and the commands in this README, and actionlint, gitleaks and ruff in lint.yml (with a sha256; gitleaks also in reusable-gitleaks.yml) and the justfile. renovate.json bumps each one across its files in a single PR, and the tool-pins test fails when any of them disagree or a Renovate regex stops matching. Renovate cannot recompute a checksum, so an actionlint, gitleaks or ruff bump stays red at sha256sum -c until the matching *_SHA256 is updated by hand; the PR body says how. The test convention is in .github/tests/README.md.

AI Coding Rules — syncing with rulesync

The org's coding rules for AI assistants live in .rulesync/rules/*.md in this repository, one Markdown file per topic with YAML frontmatter naming its targets and path globs. rulesync compiles those sources into the per-tool formats each assistant reads. Every other directory of rules — here and in consuming repos — is generated output and must not be edited by hand.

Source Generated for Output
.rulesync/rules/*.md Claude Code .claude/rules/*.md
GitHub Copilot .github/instructions/*.instructions.md
Antigravity (agy), via rulesync's antigravity-ide target .agents/rules/*.md with trigger: frontmatter
Cursor .cursor/rules/*.mdc
.rulesync/.aiignore Claude Code, Cursor a permissions.deny block in .claude/settings.json, plus .cursorignore

Editing the rules

Edit the file under .rulesync/rules/, regenerate, and commit source and output together:

npx rulesync@40.2.0 generate

To check generated files are current without writing anything (this is what CI asserts — it exits 1 when regeneration would change something):

npx rulesync@40.2.0 generate --check

Adopting the rules in another FVH repo

A consuming repo needs one file, committed to the consuming repo: .github/workflows/sync-ai-rules.yml, a caller for reusable-sync-ai-rules.yml. The reusable workflow declares only workflow_call, so the caller owns the trigger. The starter workflow in workflow-templates/sync-ai-rules.yml (also offered in the repo's Actions → New workflow page) runs weekly and on demand:

name: Sync AI Rules

on:
  workflow_dispatch:
  schedule:
    - cron: "0 6 * * 1" # Weekly Monday 6am UTC

jobs:
  sync:
    uses: ForumViriumHelsinki/.github/.github/workflows/reusable-sync-ai-rules.yml@main

Each run fetches .rulesync/ from this repository into the calling repo, regenerates the per-tool files, and opens a PR on branch chore/sync-ai-rules when anything changed. The fetched .rulesync/ copy is committed in the consuming repo too — rulesync generates from a local source tree, so the rules have to land there before generate can read them.

By default the PR is opened with GITHUB_TOKEN. In a repo where "Allow GitHub Actions to create and approve pull requests" is off (GitHub's default), that fails with GitHub Actions is not permitted to create or approve pull requests, and a PR GITHUB_TOKEN does open triggers no CI. Pass a GitHub App to open it with an App token instead:

jobs:
  sync:
    uses: ForumViriumHelsinki/.github/.github/workflows/reusable-sync-ai-rules.yml@main
    with:
      app-id: ${{ vars.CI_APP_ID }}
    secrets:
      APP_PRIVATE_KEY: ${{ secrets.CI_APP_PRIVATE_KEY }}

Only Claude Code files are generated by default. The targets input takes a comma-separated subset of claudecode, copilot, antigravity-ide and cursor, and defaults to claudecode. Add a target only for a tool the repo actually uses; any other value fails the run before generation. In the #111 pilot, all four targets wrote 78 files into each of google-chat-agent and podio-mcp, and 46 of them landed in Copilot, Cursor and Antigravity trees that neither repo used.

    with:
      targets: claudecode,copilot

The workflow does not read the consuming repo's rulesync.jsonc for targets or features. fetch pulls only .rulesync/, never the config, and a bare generate with no config resolves to the agentsmd target alone — which no rule's frontmatter lists — so it writes zero files at exit 0. The generate step therefore always passes --targets (from the input) and --features rules,ignore. CLI options take precedence over rulesync.jsonc and rulesync.local.jsonc, so a consuming repo's own config does not change the targets. Options the command line does not set, such as delete, are still read from a config if one exists. With the default target, a sync of this repository's rules writes the fifteen .claude/rules/ files and .claude/settings.json, plus the fetched .rulesync/ copy. In an existing .claude/settings.json, generation replaces every Read(...) entry in permissions.deny with the fetched .aiignore set and keeps the other deny entries, allow and enabledPlugins, so a consuming repo's own Read(...) denies are dropped on each sync.

Running a sync by hand

The same two steps the workflow runs, from the root of the consuming repo:

npx rulesync@40.2.0 fetch ForumViriumHelsinki/.github --features rules,ignore --conflict overwrite --path .rulesync
npx rulesync@40.2.0 generate --targets claudecode,copilot,antigravity-ide,cursor --features rules,ignore

A consuming repo that commits a copy of this repository's rulesync.jsonc can drop both flags. --path .rulesync is required: rulesync 9 and later resolve features against the source repo's root, so without it the fetch looks for rules/ at the top level, 404s, and returns zero files at exit 0.

The generated deny block

Each line of .rulesync/.aiignore becomes a Read(...) entry in the permissions.deny block of the generated .claude/settings.json, and a line in .cursorignore. The file lists rulesync.local.jsonc, rulesync.lock and node_modules/: untracked, machine-local paths. It does not list .rulesync/ or rulesync.jsonc, because a Claude Code Read deny also blocks Edit, Write and Bash commands that name the path (permissions docs), which would make the rule sources and the config uneditable in a Claude Code session. Claude Code does not auto-load .rulesync/, so the sources are not read twice; Cursor and Antigravity CLI index them as ordinary files.

Generation replaces every Read(...) deny in an existing .claude/settings.json with the .aiignore set and leaves the rest of the file — non-Read denies, allow, enabledPlugins, everything else — untouched. A Read(...) deny added to settings.json by hand is dropped on the next generate; add the pattern to .aiignore instead. A consuming repo that fetches --features rules,ignore receives this .aiignore too, so the same replacement applies to its settings.json.

Container Signing

Release images are signed with Sigstore cosign using keyless mode (OIDC identity from GitHub Actions). Signatures are recorded in the Rekor public transparency log.

Verifying signatures

cosign verify \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  --certificate-identity-regexp='https://github.com/ForumViriumHelsinki/' \
  ghcr.io/forumviriumhelsinki/my-app:1.2.3

Opting out

Pass enable-signing: false to the release workflow:

uses: ForumViriumHelsinki/.github/.github/workflows/reusable-container-release.yml@main
with:
  image-name: forumviriumhelsinki/my-app
  tag-prefix: my-app-v
  enable-signing: false

Community Health Files

Files at the root of this repo serve as org-wide defaults. Individual repos can override any of them by providing their own copy.

Files that cannot be org defaults (must be per-repo): LICENSE

About

Organization profile, community health files, and reusable workflow templates

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages