https://github.com/arnested/task2ci
https://github.com/arnested/task2ci
Last synced: 15 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/arnested/task2ci
- Owner: arnested
- License: mit
- Created: 2026-05-23T00:20:08.000Z (21 days ago)
- Default Branch: main
- Last Pushed: 2026-05-23T12:27:33.000Z (21 days ago)
- Last Synced: 2026-05-23T12:29:06.095Z (21 days ago)
- Language: Go
- Homepage: https://arnested.github.io/task2ci/
- Size: 135 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE.md
- Code of conduct: CODE_OF_CONDUCT.md
- Agents: AGENTS.md
Awesome Lists containing this project
README
# task2ci
> [!CAUTION]
> This project is still very much experimental. The CLI, template format,
> and generated workflow shape may change without notice. Pin a specific
> commit if you depend on it, and don't be surprised by breaking changes
> between versions until 1.0.
Generate GitHub Actions workflows from your `Taskfile.yaml`, so the commands you
run locally are the same ones CI runs — guaranteed, not by hand.
## What it does
You write a workflow **template** under `.task2ci/workflows/.yaml` —
plain GitHub Actions YAML with `# @ci: ` placeholder comments where you
want task-driven steps spliced in. You mark the [go-task] tasks that should
fill those slots with the same `# @ci: ` annotation in `Taskfile.yaml`.
Run `task2ci` and each template is rendered to `.github/workflows/.yaml`
with the matching task steps in place. A `task2ci -check` mode fails CI if the
on-disk workflow has drifted from the template/Taskfile, so the two can't
quietly diverge.
[go-task]: https://taskfile.dev/
## Why
Any project that uses [go-task] ends up with two parallel lists of commands:
- `Taskfile.yaml` — what developers run locally (e.g. `task test`,
`task lint`)
- `.github/workflows/ci.yaml` — what CI runs (the same commands, restated
by hand in YAML)
These drift. Someone adds a new check to the Taskfile but forgets the
workflow, or vice versa, and "works on my machine" creeps in.
`task2ci` keeps the **Taskfile** as the source of truth for _what_ runs, and
keeps the **template** as the source of truth for _the rest_ of the workflow
(triggers, runner image, setup steps, conditionals, environments). The
template is plain GitHub Actions YAML, so you get full validation from
yaml-language-server, actionlint, and any other GHA tooling — task2ci itself
does not model setup steps, runners, or any other workflow structure.
The tool is language-agnostic; the Quick start below uses a Go example
because that's the dogfood, but the only Go-specific behavior is an
[optional optimization](#how-it-adapts-to-your-toolchain) that uses
`go tool task` when `go-task` is registered as a Go tool dependency.
## Quick start
In a project that already has a `Taskfile.yaml`:
```sh
# Install task2ci. Go projects can register it as a tool dep:
go get -tool arnested.dk/go/task2ci
# Anywhere else, install the binary with `go install` or download a
# release build, then make sure `task2ci` is on the runner's PATH.
```
Create a template at `.task2ci/workflows/ci.yaml`. This is plain GitHub
Actions YAML; swap the setup steps for whatever your project needs
(setup-node, setup-python, system packages, …):
```yaml
---
name: ci
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
# Set up the toolchain your tasks need. This Go example uses
# actions/setup-go; replace with what your project needs.
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Check generated CI is up to date
run: go tool task2ci -check
# @ci: test
```
Annotate the tasks you want spliced in:
```yaml
# Taskfile.yaml
---
version: '3'
tasks:
# @ci: test
test:
desc: Run unit tests
cmd: go test ./...
# @ci: test
vet:
cmd: go vet ./...
# local-only — no annotation
tidy:
cmd: go mod tidy
```
Generate:
```sh
task2ci # or `go tool task2ci` if you registered it as a tool dep
```
You get `.github/workflows/ci.yaml`, identical to the template except the
`# @ci: test` line is replaced by:
```yaml
- name: Run unit tests
run: task test
- name: vet
run: task vet
```
Commit `Taskfile.yaml`, the template, and the generated workflow. CI runs
`task2ci -check` (which you put in the template) on every push, so any
drift fails the build.
## Annotation syntax
The same syntax works in two places:
- **In `Taskfile.yaml`**, on any task — opts that task into a tag:
```yaml
# @ci: # tag only
# @ci: | # tag plus display-name override
```
- **In a template**, in `.task2ci/workflows/.{yaml,yml}` — marks a
splice point. Templates do **not** accept the `| ` form; step
naming is owned by the task side.
### Step name resolution
The step's `name:` in the generated workflow is chosen in this order:
1. Override from the annotation (`| step name`) in `Taskfile.yaml`
2. The task's `desc:`
3. The task name itself
## Templates
- Live under `.task2ci/workflows/.yaml` (or `.yml` — both accepted).
- Are plain GitHub Actions workflow YAML; everything except `# @ci: `
placeholder comments is copied through verbatim.
- Render to `.github/workflows/.yaml` (output is always `.yaml`).
- Get a single autogenerated-header comment prepended to the output.
Anything that should run in CI **other** than the task-driven steps — setup
actions, environment variables, conditionals, the drift-check step itself —
goes directly into the template in GHA's native syntax.
### Multiple workflows
Multiple template files produce multiple workflow files:
```text
.task2ci/workflows/ci.yaml → .github/workflows/ci.yaml
.task2ci/workflows/release.yaml → .github/workflows/release.yaml
```
Each is independent.
### Warnings
- A `# @ci: ` annotation in `Taskfile.yaml` with no matching template
placeholder prints a warning and the task is left out of CI.
- A `# @ci: ` placeholder in a template with no matching task prints a
warning and the placeholder is removed from the generated output.
Both are warnings (stderr), not errors — generation still succeeds.
## CLI reference
```text
task2ci [flags]
```
- **_(no flags)_** — Render each template under `.task2ci/workflows/` to
`.github/workflows/.yaml`.
- **`-check`** — Compare what would be generated now against the on-disk
workflow files. Exit non-zero on any drift or orphan tag/placeholder.
Used in CI.
- **`-fix`** — Remove orphan `# @ci: ` placeholders (tags no task
uses) from templates in place. Doesn't regenerate workflows; run
`task2ci` after.
- **`-init`** — Write a minimal starter template at
`.task2ci/workflows/ci.yaml`. Refuses to overwrite.
- **`-license`** — Print the license (MIT) and exit.
- **`-taskfile `** — Path to a Taskfile. May be repeated to scan
multiple files. Default: auto-discover (`Taskfile.yml` →
`taskfile.yml` → `Taskfile.yaml` → `taskfile.yaml` → `.dist` variants).
`-check`, `-fix`, and `-init` are mutually exclusive.
## How it adapts to your toolchain
The default behavior is language-agnostic: inserted steps run
`task `, and the user's template installs `task` however it
wants (the [go-task/setup-task] action is the typical choice).
The one Go-specific optimization: if `task2ci` finds a `go.mod` in the
working directory that registers `github.com/go-task/task/v3/cmd/task` as
a `tool` directive (Go 1.24+ tool dependencies), the generated `run:` lines
use `go tool task ` instead. CI then uses the exact go-task version
pinned in your `go.mod` — no separate install step needed (though you
still need `actions/setup-go` in your template).
Non-Go projects: just include
`uses: go-task/setup-task@v2` in the template; the `run: task `
lines work the same way.
[go-task/setup-task]: https://github.com/go-task/setup-task
## Working with AI tools
If you use AI coding assistants (Claude Code, Cursor, Copilot, etc.) in
your project, paste the snippet below into your `AGENTS.md` /
`CLAUDE.md` / `.cursorrules` / whatever project-rules file your tool
reads. It keeps them from trying to hand-edit the generated workflow
when they should be editing the template instead.
```text
This project uses task2ci to generate GitHub Actions workflows.
- Source of truth:
- `.task2ci/workflows/.yaml` — workflow templates.
- `Taskfile.yaml` — task definitions, opted into CI via
`# @ci: ` annotations.
- Generated, do not hand-edit:
- `.github/workflows/.yaml` — regenerated from the templates
on every `task2ci` run.
- After changing a template or annotation, run `task2ci` to regenerate
the workflow files and commit the result.
- CI runs `task2ci -check` and fails on drift, orphan tag
annotations, or orphan placeholders.
- Use `task2ci -fix` to delete orphan placeholders from templates.
- Use `task2ci -init` to scaffold a starter template.
Full docs: https://arnested.github.io/task2ci/
```