{"id":50324719,"url":"https://github.com/arnested/task2ci","last_synced_at":"2026-05-29T05:04:07.257Z","repository":{"id":359772265,"uuid":"1247139678","full_name":"arnested/task2ci","owner":"arnested","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-23T12:27:33.000Z","size":138,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-23T12:29:06.095Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://arnested.github.io/task2ci/","language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arnested.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-23T00:20:08.000Z","updated_at":"2026-05-23T12:18:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/arnested/task2ci","commit_stats":null,"previous_names":["arnested/task2ci"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/arnested/task2ci","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnested%2Ftask2ci","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnested%2Ftask2ci/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnested%2Ftask2ci/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnested%2Ftask2ci/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arnested","download_url":"https://codeload.github.com/arnested/task2ci/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arnested%2Ftask2ci/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33637486,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-29T05:03:50.057Z","updated_at":"2026-05-29T05:04:07.244Z","avatar_url":"https://github.com/arnested.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# task2ci\n\n\u003e [!CAUTION]\n\u003e This project is still very much experimental. The CLI, template format,\n\u003e and generated workflow shape may change without notice. Pin a specific\n\u003e commit if you depend on it, and don't be surprised by breaking changes\n\u003e between versions until 1.0.\n\nGenerate GitHub Actions workflows from your `Taskfile.yaml`, so the commands you\nrun locally are the same ones CI runs — guaranteed, not by hand.\n\n## What it does\n\nYou write a workflow **template** under `.task2ci/workflows/\u003cname\u003e.yaml` —\nplain GitHub Actions YAML with `# @ci: \u003ctag\u003e` placeholder comments where you\nwant task-driven steps spliced in. You mark the [go-task] tasks that should\nfill those slots with the same `# @ci: \u003ctag\u003e` annotation in `Taskfile.yaml`.\n\nRun `task2ci` and each template is rendered to `.github/workflows/\u003cname\u003e.yaml`\nwith the matching task steps in place. A `task2ci -check` mode fails CI if the\non-disk workflow has drifted from the template/Taskfile, so the two can't\nquietly diverge.\n\n[go-task]: https://taskfile.dev/\n\n## Why\n\nAny project that uses [go-task] ends up with two parallel lists of commands:\n\n- `Taskfile.yaml` — what developers run locally (e.g. `task test`,\n  `task lint`)\n- `.github/workflows/ci.yaml` — what CI runs (the same commands, restated\n  by hand in YAML)\n\nThese drift. Someone adds a new check to the Taskfile but forgets the\nworkflow, or vice versa, and \"works on my machine\" creeps in.\n\n`task2ci` keeps the **Taskfile** as the source of truth for _what_ runs, and\nkeeps the **template** as the source of truth for _the rest_ of the workflow\n(triggers, runner image, setup steps, conditionals, environments). The\ntemplate is plain GitHub Actions YAML, so you get full validation from\nyaml-language-server, actionlint, and any other GHA tooling — task2ci itself\ndoes not model setup steps, runners, or any other workflow structure.\n\nThe tool is language-agnostic; the Quick start below uses a Go example\nbecause that's the dogfood, but the only Go-specific behavior is an\n[optional optimization](#how-it-adapts-to-your-toolchain) that uses\n`go tool task` when `go-task` is registered as a Go tool dependency.\n\n## Quick start\n\nIn a project that already has a `Taskfile.yaml`:\n\n```sh\n# Install task2ci. Go projects can register it as a tool dep:\ngo get -tool arnested.dk/go/task2ci\n\n# Anywhere else, install the binary with `go install` or download a\n# release build, then make sure `task2ci` is on the runner's PATH.\n```\n\nCreate a template at `.task2ci/workflows/ci.yaml`. This is plain GitHub\nActions YAML; swap the setup steps for whatever your project needs\n(setup-node, setup-python, system packages, …):\n\n```yaml\n---\nname: ci\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: actions/checkout@v4\n\n      # Set up the toolchain your tasks need. This Go example uses\n      # actions/setup-go; replace with what your project needs.\n      - uses: actions/setup-go@v5\n        with:\n          go-version-file: go.mod\n\n      - name: Check generated CI is up to date\n        run: go tool task2ci -check\n      # @ci: test\n```\n\nAnnotate the tasks you want spliced in:\n\n```yaml\n# Taskfile.yaml\n---\nversion: '3'\n\ntasks:\n  # @ci: test\n  test:\n    desc: Run unit tests\n    cmd: go test ./...\n\n  # @ci: test\n  vet:\n    cmd: go vet ./...\n\n  # local-only — no annotation\n  tidy:\n    cmd: go mod tidy\n```\n\nGenerate:\n\n```sh\ntask2ci    # or `go tool task2ci` if you registered it as a tool dep\n```\n\nYou get `.github/workflows/ci.yaml`, identical to the template except the\n`# @ci: test` line is replaced by:\n\n```yaml\n      - name: Run unit tests\n        run: task test\n\n      - name: vet\n        run: task vet\n```\n\nCommit `Taskfile.yaml`, the template, and the generated workflow. CI runs\n`task2ci -check` (which you put in the template) on every push, so any\ndrift fails the build.\n\n## Annotation syntax\n\nThe same syntax works in two places:\n\n- **In `Taskfile.yaml`**, on any task — opts that task into a tag:\n\n  ```yaml\n  # @ci: \u003ctag\u003e              # tag only\n  # @ci: \u003ctag\u003e | \u003cstep name\u003e  # tag plus display-name override\n  ```\n\n- **In a template**, in `.task2ci/workflows/\u003cname\u003e.{yaml,yml}` — marks a\n  splice point. Templates do **not** accept the `| \u003cstep name\u003e` form; step\n  naming is owned by the task side.\n\n### Step name resolution\n\nThe step's `name:` in the generated workflow is chosen in this order:\n\n1. Override from the annotation (`| step name`) in `Taskfile.yaml`\n2. The task's `desc:`\n3. The task name itself\n\n## Templates\n\n- Live under `.task2ci/workflows/\u003cname\u003e.yaml` (or `.yml` — both accepted).\n- Are plain GitHub Actions workflow YAML; everything except `# @ci: \u003ctag\u003e`\n  placeholder comments is copied through verbatim.\n- Render to `.github/workflows/\u003cname\u003e.yaml` (output is always `.yaml`).\n- Get a single autogenerated-header comment prepended to the output.\n\nAnything that should run in CI **other** than the task-driven steps — setup\nactions, environment variables, conditionals, the drift-check step itself —\ngoes directly into the template in GHA's native syntax.\n\n### Multiple workflows\n\nMultiple template files produce multiple workflow files:\n\n```text\n.task2ci/workflows/ci.yaml       → .github/workflows/ci.yaml\n.task2ci/workflows/release.yaml  → .github/workflows/release.yaml\n```\n\nEach is independent.\n\n### Warnings\n\n- A `# @ci: \u003ctag\u003e` annotation in `Taskfile.yaml` with no matching template\n  placeholder prints a warning and the task is left out of CI.\n- A `# @ci: \u003ctag\u003e` placeholder in a template with no matching task prints a\n  warning and the placeholder is removed from the generated output.\n\nBoth are warnings (stderr), not errors — generation still succeeds.\n\n## CLI reference\n\n```text\ntask2ci [flags]\n```\n\n- **_(no flags)_** — Render each template under `.task2ci/workflows/` to\n  `.github/workflows/\u003cname\u003e.yaml`.\n- **`-check`** — Compare what would be generated now against the on-disk\n  workflow files. Exit non-zero on any drift or orphan tag/placeholder.\n  Used in CI.\n- **`-fix`** — Remove orphan `# @ci: \u003ctag\u003e` placeholders (tags no task\n  uses) from templates in place. Doesn't regenerate workflows; run\n  `task2ci` after.\n- **`-init`** — Write a minimal starter template at\n  `.task2ci/workflows/ci.yaml`. Refuses to overwrite.\n- **`-license`** — Print the license (MIT) and exit.\n- **`-taskfile \u003cpath\u003e`** — Path to a Taskfile. May be repeated to scan\n  multiple files. Default: auto-discover (`Taskfile.yml` →\n  `taskfile.yml` → `Taskfile.yaml` → `taskfile.yaml` → `.dist` variants).\n\n`-check`, `-fix`, and `-init` are mutually exclusive.\n\n## How it adapts to your toolchain\n\nThe default behavior is language-agnostic: inserted steps run\n`task \u003ctask-name\u003e`, and the user's template installs `task` however it\nwants (the [go-task/setup-task] action is the typical choice).\n\nThe one Go-specific optimization: if `task2ci` finds a `go.mod` in the\nworking directory that registers `github.com/go-task/task/v3/cmd/task` as\na `tool` directive (Go 1.24+ tool dependencies), the generated `run:` lines\nuse `go tool task \u003cname\u003e` instead. CI then uses the exact go-task version\npinned in your `go.mod` — no separate install step needed (though you\nstill need `actions/setup-go` in your template).\n\nNon-Go projects: just include\n`uses: go-task/setup-task@v2` in the template; the `run: task \u003cname\u003e`\nlines work the same way.\n\n[go-task/setup-task]: https://github.com/go-task/setup-task\n\n## Working with AI tools\n\nIf you use AI coding assistants (Claude Code, Cursor, Copilot, etc.) in\nyour project, paste the snippet below into your `AGENTS.md` /\n`CLAUDE.md` / `.cursorrules` / whatever project-rules file your tool\nreads. It keeps them from trying to hand-edit the generated workflow\nwhen they should be editing the template instead.\n\n```text\nThis project uses task2ci to generate GitHub Actions workflows.\n\n- Source of truth:\n  - `.task2ci/workflows/\u003cname\u003e.yaml` — workflow templates.\n  - `Taskfile.yaml` — task definitions, opted into CI via\n    `# @ci: \u003ctag\u003e` annotations.\n- Generated, do not hand-edit:\n  - `.github/workflows/\u003cname\u003e.yaml` — regenerated from the templates\n    on every `task2ci` run.\n- After changing a template or annotation, run `task2ci` to regenerate\n  the workflow files and commit the result.\n- CI runs `task2ci -check` and fails on drift, orphan tag\n  annotations, or orphan placeholders.\n- Use `task2ci -fix` to delete orphan placeholders from templates.\n- Use `task2ci -init` to scaffold a starter template.\n\nFull docs: https://arnested.github.io/task2ci/\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnested%2Ftask2ci","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farnested%2Ftask2ci","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farnested%2Ftask2ci/lists"}