# GitHub Workflow Templates

> [crefft](/index.md) > Configs > GitHub

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119).

Reusable GitHub Actions workflows for projects that adopt crefft. These are
**templates**: they live here rather than in `.github/workflows/` so GitHub
cannot schedule them inside crefft itself.

## Quick Reference

| Template                                                               | Purpose                                                             | Trigger                                       | Needs                  |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------- | --------------------------------------------- | ---------------------- |
| [`workflows/sync-crefft.yml`][sync]                                    | Agents, commands, and config                                        | Weekly, manual                                | [^scopes]              |
| [`workflows/doc-sync.yml`](/raw/configs/github/workflows/doc-sync.yml) | Remind pull-request authors which documents a source change touches | Pull requests touching `src/`, `lib/`, `app/` | `pull-requests: write` |

[sync]: /raw/configs/github/workflows/sync-crefft.yml

[^scopes]: Requires `contents: write` and `pull-requests: write` to propose
    updates to crefft-owned Claude configuration files in your project.

## Why these are templates, not live workflows

A workflow placed in `.github/workflows/` runs in the repository that holds
it. `sync-crefft.yml` previously sat in crefft's own workflow directory
and therefore ran against crefft every Monday, copying `configs/claude/*`
into a second `.claude/` tree and attempting a self-referential pull request.
`doc-sync.yml` triggers on `src/**`, `lib/**`, and `app/**`, none of which
exist in a documentation repository.

Templates **MUST NOT** be placed in `.github/workflows/` of the repository
that publishes them. Copy them into the consuming project instead.

## Adopting `sync-crefft.yml`

Keeps a downstream project's `.claude/` tree in step with crefft.

### Install

```bash
mkdir -p .github/workflows
curl -fsSL -o .github/workflows/sync-crefft.yml \
  https://raw.githubusercontent.com/agh/crefft/v2026.10.1/configs/github/workflows/sync-crefft.yml
```

### Configure

Set `CREFFT_REF` to the release tag you have reviewed. It **MUST NOT** be a
branch such as `main`. The default is `v2026.10.1`. Optionally set
`CREFFT_COMMIT` to the independently reviewed full commit SHA:

```yaml
env:
  CREFFT_REPO: agh/crefft
  CREFFT_REF: v2026.10.1
  CREFFT_COMMIT: ''
```

The template checks out that release into `.crefft-upstream/` and invokes
`.crefft-upstream/scripts/sync-claude-config.sh --ref "${CREFFT_REF}"`, adding
`--commit` when `CREFFT_COMMIT` is set. There is no separate copying logic
or configurable `SYNC_PATHS` mapping. The runner needs Bash, Git, and Python 3.

The applier verifies the checkout against the release tag and optional
commit SHA before writing. It installs these fixed mappings:

| Source in crefft                 | Destination in the project |
| -------------------------------- | -------------------------- |
| `agents/`                        | `.claude/agents/`          |
| `commands/`                      | `.claude/commands/`        |
| `configs/claude/skills/`         | `.claude/skills/`          |
| `configs/claude/infrastructure/` | `.claude/infrastructure/`  |
| `configs/claude/settings.json`   | `.claude/settings.json`    |

Every source component is required; a missing source fails before any write.
Existing settings are preserved. Replacing them requires an explicit
`--overwrite-settings` argument to the script; the template does not pass it.

#### Why

One applier keeps automated and manual installs consistent. A release pin
prevents weekly jobs from following an unreviewed branch, and an independent
commit pin detects a moved tag.

### Ownership and adoption

Consumers **MUST** retain and commit `.claude/crefft-receipt.json` with the
installed files. It records the release, resolved commit, and SHA-256 of each
installed file. Only previously recorded files can be replaced or removed.
Project-specific files beside them survive.

An unowned conflicting path stops the entire sync with a list of conflicts
and no writes. For an earlier installation without a receipt, consumers
**MAY** run the pinned script manually with `--adopt` to claim identical
files. Adoption compares contents only; differing files still conflict.
Locally modified owned files and symlinks also block the sync. Review
conflicts rather than deleting project additions to make a run pass.

**Do:** use the pinned script for adoption and keep its receipt.
**Don't:** mirror a whole directory or move project-specific agents into a
user's home directory merely to protect them from sync.

See [manual sync](/guides/ai/claude-code.md#manual-sync) for the same
applier outside GitHub Actions.

#### Why

File-level ownership makes upstream deletion safe without granting crefft
ownership of the project's entire `.claude/` tree. Preserving settings
protects project-specific permissions and hooks.

### Repository settings

The workflow opens a pull request, so the project **MUST** enable
**Settings → Actions → General → Allow GitHub Actions to create and approve
pull requests**. Without it `peter-evans/create-pull-request` fails with a
permissions error — this is what caused all fourteen historical failures of
this workflow in crefft.

Pull requests authored with the default `GITHUB_TOKEN` deliberately do not
trigger further workflow runs. If the sync pull request must run your CI, set
a `CREFFT_SYNC_TOKEN` secret holding a fine-grained personal access token
with `contents: write` and `pull-requests: write` on the project; the
template prefers it when present.

### Dry runs

Run the workflow manually with **Preview changes without creating a pull
request** ticked. The dry run reports `git status --porcelain` to the job
summary after running the applier in a temporary project clone. It neither
changes the project's `.claude/` tree nor opens a pull request; there is no
workspace-wide restore or clean step. `dry_run` is a Boolean input, so the
conditions compare `inputs.dry_run == true`.

The workflow validates `VERSION` as one calendar version before exporting it.
Preview outputs enter the shell through `env:` and are printed with
`printf '%s\n'`, never interpolated into shell source.

#### Why

A preview needs the same ownership checks as an install, without applying
changes to the working project. Treating version and report values as data
prevents filenames or upstream text from becoming shell commands.

## Adopting `doc-sync.yml`

Posts one comment per pull request listing the documents that correspond to
the changed source files.

### Install

```bash
mkdir -p .github/workflows
curl -fsSL -o .github/workflows/doc-sync.yml \
  https://raw.githubusercontent.com/agh/crefft/main/configs/github/workflows/doc-sync.yml
```

### Configure

Set `DOC_ROOT` to the directory holding your prose documentation, and adjust
the `paths` filter to match your source layout. The mapping strips any
extension, so `lib/parser.py` maps to `docs/lib/parser.md`; the original
template assumed TypeScript and produced `docs/lib/parser.py.md`.

### Scope

This check is advisory. It **MUST NOT** be treated as proof that
documentation is current — it only reports which documents exist for the
files that changed. It never fails the build.

## Pinning policy

Every `uses:` reference in these templates is pinned to a full commit SHA
with the release tag in a trailing comment, per
[GitHub's secure-use guidance](https://docs.github.com/en/actions/reference/security/secure-use).
A tag is a mutable pointer; `tj-actions/changed-files` had its tags
retargeted during the March 2025 supply-chain incident, which a SHA pin would
have contained.

crefft has no Dependabot configuration. These templates are **not** updated
automatically: when the pins in `configs/tool-versions.json` change, update
the templates, `.forgejo/workflows/`, and `.github/workflows/` in the same commit.

Current pins:

| Action                            | Version | SHA                                        |
| --------------------------------- | ------- | ------------------------------------------ |
| `actions/checkout`                | v7.0.1  | `3d3c42e5aac5ba805825da76410c181273ba90b1` |
| `actions/github-script`           | v9.0.0  | `3a2844b7e9c422d3c10d287c895573f7108da1b3` |
| `peter-evans/create-pull-request` | v8.1.1  | `5f6978faf089d4d20b00c7766989d076bb2fc7f1` |
| `tj-actions/changed-files`        | v47.0.6 | `9426d40962ed5378910ee2e21d5f8c6fcbf2dd96` |

## See Also

* [CI/CD Style Guide](/guides/process/ci.md)
* [GitHub Templates](/guides/process/github-templates.md)
* [Claude Code Guide](/guides/ai/claude-code.md)

---

Source version: 2026.10.5. [Source file](https://github.com/agh/crefft/blob/6e6481840e68a89874e0d90eaae3cf23e83379fb/configs/github/README.md).
