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
2 changes: 1 addition & 1 deletion .github/workflows/claude-responder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,6 @@ jobs:

- name: Render execution to step summary
if: always() && steps.claude.outputs.execution_file != ''
uses: lentago/shared-workflows/.github/actions/render-claude-summary@main
uses: lentago/shared-workflows/.github/actions/render-claude-summary@v1.0.0
with:
execution_file: ${{ steps.claude.outputs.execution_file }}
2 changes: 1 addition & 1 deletion .github/workflows/claude-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,6 @@ jobs:

- name: Render execution to step summary
if: always() && steps.claude.outputs.execution_file != ''
uses: lentago/shared-workflows/.github/actions/render-claude-summary@main
uses: lentago/shared-workflows/.github/actions/render-claude-summary@v1.0.0
with:
execution_file: ${{ steps.claude.outputs.execution_file }}
29 changes: 17 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@

When Claude initializes in this directory, open the first response with a
brief self-introduction as **Workflow Claude** — steward of the fleet's
reusable GitHub Actions workflows (changes here propagate to every caller
via `@main`, so version-pinning and breaking-change discipline matter).
reusable GitHub Actions workflows (callers pin to immutable semver tags, so
breaking-change discipline and the release process in RELEASING.md matter).
One sentence is plenty; don't make a meal of it.

## What this repo is

Reusable GitHub Actions workflows that the Lentago Labs fleet calls via
`uses: lentago/shared-workflows/.github/workflows/<name>.yml@main`:
`uses: lentago/shared-workflows/.github/workflows/<name>.yml@v1.0.0` (immutable
semver tag — see [ADR-0005](docs/adr/0005-immutable-semver-tags-replace-main-consumption.md)
and [RELEASING.md](RELEASING.md)):

| Workflow | Purpose | Callers (current) |
|---|---|---|
Expand All @@ -43,9 +45,9 @@ caller passes a thin `with:` block; the reusable workflow handles auth,
context, output format, and the heavy lifting. Callers should **never**
copy the workflow YAML into their own `.github/workflows/` — they `uses:` it.

**Floating `@main` is the default.** Callers use `@main` for the tip. Once
a workflow contract solidifies, tag a stable version (`v0.1.0`, `v1`) and
migrate callers to the tag — this isn't done yet (no tags shipped).
**Callers pin to immutable semver tags.** Callers use `@v1.0.0` (or the current
release). `@main` is not a supported consumption path. See [RELEASING.md](RELEASING.md)
for how to cut a release and how callers upgrade.

**`secrets: inherit`** is required on the caller side — these workflows
expect `ANTHROPIC_API_KEY` (and any model-routing PAT) to be in the org or
Expand All @@ -58,18 +60,21 @@ repo secrets store, passed through transparently.
next caller run). Add inputs as optional with sensible defaults.
- **Document new inputs in README.md.** This is the only documentation
callers see — no separate site, no schema export.
- **Test before publishing on `main`.** Push to a branch, point one repo's
workflow at `@<branch-name>` for one merge, then merge to `main`. Once
on `main`, every caller picks it up on their next workflow run.
- **Test before merging to `main`.** Push to a branch, point one caller repo's
workflow at `@<branch-name>` for one merge, then merge to `main`. Only after
that, cut a release tag — callers pick up changes only once they bump to the
new tag. See [RELEASING.md](RELEASING.md) for what must be green before
tagging.

## Gotchas

- **`paths-ignore` doesn't gate the reusable workflow itself.** The caller's
`on:` block decides what triggers the workflow; once triggered, the
reusable workflow always runs. Filter at the caller, not here.
- **`@main` floats.** A breaking change in this repo's `main` will break
every caller's next run. Treat `main` changes as if they were releases
until tagged versions exist.
- **`main` is not a release.** Callers pin to semver tags, not `@main`. A
change merged here is not live for callers until a new tag is cut and
callers bump their `uses:` ref. `@main` continues to work mechanically but
carries no stability guarantee.
- **`workflow_call` jobs don't show up in `gh workflow run` lists** on
caller repos — they appear as `Called by:` in the caller workflow's run
page, not as standalone runs in this repo's Actions tab.
Expand Down
42 changes: 24 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ This is the Lentago Labs fleet's reusable-CI hub: one repository of
[`workflow_call`](https://docs.github.com/en/actions/using-workflows/reusing-workflows)
definitions — an agentic `@claude` responder and reviewer, a docs-link checker,
and ShellCheck — that every Lentago Labs repo calls *by reference* rather than
copying into its own `.github/`. Because callers pin `@main`, a single merged PR
here changes CI behavior across the whole fleet on each repo's next run. If you
manage CI for more than a couple of repos, this is the "define the pipeline
once, everyone inherits it" pattern working end to end — the enterprise rhyme is
a central pipeline library.
copying into its own `.github/`. Callers pin to an immutable semver tag
(`@v1.0.0`); upgrading means a Dependabot-opened bump PR in the caller repo, not
silent propagation. If you manage CI for more than a couple of repos, this is the
"define the pipeline once, everyone inherits it" pattern working end to end —
the enterprise rhyme is a central pipeline library.

**Authorship:** The workflows and documentation in this repo are co-written with [Claude](https://claude.ai) (Anthropic). I direct the work and review the output; Claude writes the YAML. I'm an infrastructure operator, not a software engineer — please don't read this repo as a portfolio of coding ability.

**Architecture decisions:** [`docs/adr/`](docs/adr/) records the reasoning behind this repo's structural choices — canonical policy placement, `@main` floating, the advisory review gate, backwards-compatible contracts.
**Architecture decisions:** [`docs/adr/`](docs/adr/) records the reasoning behind this repo's structural choices — canonical policy placement, the `@main`→semver-tag migration, the advisory review gate, backwards-compatible contracts.

## 📚 Ask this codebase (DeepWiki)

Expand All @@ -45,7 +45,7 @@ actually runs here.

| Pattern | How it shows up here |
|---|---|
| **Reusable workflows, called by reference not copy-paste** — fix or extend the pipeline in one place, every consumer inherits it on its next run | Each definition is `on: workflow_call`; callers write `uses: lentago/shared-workflows/.github/workflows/<name>.yml@main` ([claude-responder.yml](.github/workflows/claude-responder.yml)) |
| **Reusable workflows, called by reference not copy-paste** — fix or extend the pipeline in one place, consumers upgrade via a Dependabot-opened bump PR | Each definition is `on: workflow_call`; callers write `uses: lentago/shared-workflows/.github/workflows/<name>.yml@v1.0.0` ([claude-responder.yml](.github/workflows/claude-responder.yml)) |
| **Agentic PR responder with label-gated model routing** — let an LLM act inside CI with human-controlled cost/capability tiers set by a label, not a per-repo hardcode | [`claude-responder.yml`](.github/workflows/claude-responder.yml) reads `model:opus` / `model:sonnet` / `model:haiku` off the issue or PR to pick `--model`, falling back to `default_model` |
| **Advisory (non-blocking) automated review** — decouple "useful signal" automation from "merge gate" automation so a flaky AI call never stalls delivery | [`claude-review.yml`](.github/workflows/claude-review.yml) runs the review step `continue-on-error`, always exits `0`, and posts a soft-fail comment instead of reddening the check |
| **A required check must be unconditional** — a required status check whose workflow never triggers is held "Expected" forever and deadlocks every non-matching PR | [`docs-check.yml`](.github/workflows/docs-check.yml) documents "no `paths:` filter"; `docs-check / docs-check` is the one required check on this repo's `main` today |
Expand Down Expand Up @@ -89,7 +89,7 @@ on:

jobs:
claude:
uses: lentago/shared-workflows/.github/workflows/claude-responder.yml@main
uses: lentago/shared-workflows/.github/workflows/claude-responder.yml@v1.0.0
secrets: inherit
with:
allowed_tools: '"Bash(git add:*)" "Bash(git commit:*)" "Read" "Edit" "Write"'
Expand Down Expand Up @@ -130,7 +130,7 @@ on:

jobs:
claude-review:
uses: lentago/shared-workflows/.github/workflows/claude-review.yml@main
uses: lentago/shared-workflows/.github/workflows/claude-review.yml@v1.0.0
secrets: inherit
with:
review_prompt: |
Expand Down Expand Up @@ -178,7 +178,7 @@ on:

jobs:
docs-check:
uses: lentago/shared-workflows/.github/workflows/docs-check.yml@main
uses: lentago/shared-workflows/.github/workflows/docs-check.yml@v1.0.0
with:
# ignore: | # optional, per-repo false positives
# */api-reference/*
Expand Down Expand Up @@ -207,7 +207,7 @@ on:

jobs:
shellcheck:
uses: lentago/shared-workflows/.github/workflows/shellcheck.yml@main
uses: lentago/shared-workflows/.github/workflows/shellcheck.yml@v1.0.0
with:
scripts: |
deploy.sh
Expand All @@ -217,10 +217,15 @@ jobs:

## Versioning

Callers reference `@main` for the floating tip — no version tags are shipped
yet, so every caller currently floats on `main` and a breaking change here
reaches them on their next run. Tag stable versions (`v0.1.0`, `v1`) once
contracts solidify and migrate callers to the tag.
Callers pin to an **immutable semver tag** — `@v1.0.0`, `@v1.1.0`, etc.
([ADR-0005](docs/adr/0005-immutable-semver-tags-replace-main-consumption.md)).
A full release process — what must be green before tagging, the semver policy
for reusable workflows (what counts as a breaking change vs. minor vs. patch),
and how callers upgrade — is in [RELEASING.md](RELEASING.md).

`@main` is not a supported consumption path. It continues to work mechanically
but carries no stability guarantee; a merge here may change caller CI instantly
and silently.

## 🛠️ Make a change yourself

Expand All @@ -244,9 +249,10 @@ reviewed](https://github.com/lentago/shared-workflows/pull/10).
**Ship a new reusable CI check to the whole fleet.** Add or edit a
`workflow_call` definition under `.github/workflows/`, test it by pointing one
caller repo's `uses:` at `@<branch-name>` for a single merge, then open a PR
here. After it merges to `main`, every caller repo picks up the change on its
next `@main`-pinned run — no per-repo redeploy, one PR changes CI everywhere.
**Proof this works:** [#28 Add reusable docs-check workflow (relative markdown
here. After it merges to `main`, cut a release tag — caller repos pick up the
change when they bump their `uses:` ref to the new tag (Dependabot opens those
bumps as reviewable PRs where it is enabled). See [RELEASING.md](RELEASING.md).
**Proof this works (historical, pre-versioning):** [#28 Add reusable docs-check workflow (relative markdown
links)](https://github.com/lentago/shared-workflows/pull/28),
[#29 Dogfood docs-check on this repo's own
markdown](https://github.com/lentago/shared-workflows/pull/29), and
Expand Down
120 changes: 120 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Release process

This file covers how to cut a new release of `lentago/shared-workflows` and
how callers upgrade. It is the operational complement to
[ADR-0005](docs/adr/0005-immutable-semver-tags-replace-main-consumption.md),
which records the decision to move from `@main` to immutable semver tags.

## What must be green before tagging

- `docs-check / docs-check` passes on `main` (this is the repo's one required
check; it runs the relative-link resolver on this repo's own markdown).
- No open PRs are waiting to merge that belong in this release — `main`
should reflect the intended release state.
- The workflows you are releasing have been validated against at least one
real caller repo on a branch ref (`uses: lentago/shared-workflows/...@<branch>`)
before landing on `main`. This is the test-before-publishing discipline from
[CLAUDE.md](CLAUDE.md); a release does not substitute for it.

## Semver policy for reusable workflows

**What counts as a breaking change (major bump):**

- Removing or renaming an input a caller could have been passing.
- Changing the *default* of an existing input in a way that alters behavior
for callers that rely on the default (adding a new optional input with a
sensible default is always backwards-compatible; changing an existing one
is not automatically so).
- Changing which context a status check reports to (e.g., renaming the job
so `required-checks.json` no longer matches).
- Tightening permissions beyond what callers have granted via `secrets: inherit`
or explicit `permissions:` blocks, in a way that causes a caller job to fail.
- Removing a workflow file entirely.

**What counts as a minor release:**

- New optional input with a sensible default.
- New workflow file added to the repo.
- Behavior improvements or new features inside a workflow that do not change
the caller-facing interface.

**What counts as a patch release:**

- Bug fixes that do not change the caller-facing interface.
- Documentation and comment changes inside workflow files (these don't affect
behavior but are included in the tag for completeness).
- Dependency updates inside a workflow (e.g., bumping a SHA-pinned third-party
action to a newer commit).

## How to cut a release

1. Ensure `main` is in the state you want to release and the green-gate above
is satisfied.
2. **Bump this repo's own internal references to the version you are about to
cut, and merge that first.** The reusable workflows here call this repo's
composite action by its full external form
(`uses: lentago/shared-workflows/.github/actions/render-claude-summary@<ref>`)
rather than a relative `./` path — inside a reusable workflow, `./` resolves
against the *caller's* repo, not this one, so the pinned form is required.

The consequence is easy to miss: if those refs still say `@v1.0.0` when
`v1.1.0` is tagged, then the workflows *inside* v1.1.0 keep running the
v1.0.0 action, and a fix to the composite action ships to nobody even though
a new release went out. Bump them to the version being cut, merge, then tag
that commit — the refs resolve once the tag exists.

```bash
grep -rn 'shared-workflows/.github/actions/.*@v' .github/ # all must be the new version
```
3. Create the GitHub release from the
[GitHub UI](https://github.com/lentago/shared-workflows/releases/new) or
via `gh`:
```bash
gh release create v<X.Y.Z> \
--title "v<X.Y.Z>" \
--notes "$(cat <<'EOF'
## What's new

<!-- Summarize changes since the previous release.
For breaking changes, call them out explicitly and
show the before/after caller snippet. -->

## Callers

Bump your `uses:` refs from `@v<PREV>` to `@v<X.Y.Z>`. Dependabot will
open these as PRs automatically where it is enabled.
EOF
)"
```
4. Do **not** create or move a floating `@vX` major tag. Immutable tags only —
see [ADR-0005](docs/adr/0005-immutable-semver-tags-replace-main-consumption.md)
for why.

## How callers upgrade

Each caller repo references a specific version in its workflow file:

```yaml
jobs:
claude:
uses: lentago/shared-workflows/.github/workflows/claude-responder.yml@v1.0.0
```

To upgrade, open a PR in the caller repo that bumps `@v1.0.0` → `@v1.1.0` (or
whatever the new version is) across its `.github/workflows/` files. Once merged,
that caller picks up the new behavior.

**Dependabot automates this.** Where `dependabot.yml` includes the
`github-actions` ecosystem, Dependabot opens bump PRs automatically when a new
release appears. The fleet-wide Dependabot rollout
([lentago/.github#114](https://github.com/lentago/.github/issues/114)) is in
flight; repos that are not yet enrolled handle bumps manually until they are.

## `@main` is not a supported consumption path

`@main` continues to work mechanically — GitHub does not enforce this — but it
is not a supported consumption path. A change merged to `main` may or may not be
production-ready, may or may not be reflected in release notes, and callers that
float on `@main` accept silent, instant propagation of any merge here with no
staging window. That was the original design (ADR-0002) and is now superseded
(ADR-0005). Migrate to a semver tag.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR-0002: Reusable workflows are called by reference and float on `@main`; tags deferred

**Status:** Accepted (2026-04-25; reconstructed 2026-08-13)
**Status:** Superseded by [ADR-0005](0005-immutable-semver-tags-replace-main-consumption.md) (2026-08-16) · Originally accepted (2026-04-25; reconstructed 2026-08-13)

## Context

Expand Down
Loading