{"id":50780694,"url":"https://github.com/ndisidore/cicada","last_synced_at":"2026-06-12T03:02:38.236Z","repository":{"id":336553567,"uuid":"1149980510","full_name":"ndisidore/cicada","owner":"ndisidore","description":"CI that ticks the boxes","archived":false,"fork":false,"pushed_at":"2026-06-10T14:57:36.000Z","size":708,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-10T16:25:42.318Z","etag":null,"topics":["continuous-delivery","continuous-integration"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ndisidore.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"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-02-04T18:39:01.000Z","updated_at":"2026-04-13T11:44:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ndisidore/cicada","commit_stats":null,"previous_names":["ndisidore/ciro","ndisidore/cicada"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ndisidore/cicada","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndisidore%2Fcicada","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndisidore%2Fcicada/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndisidore%2Fcicada/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndisidore%2Fcicada/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ndisidore","download_url":"https://codeload.github.com/ndisidore/cicada/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndisidore%2Fcicada/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34226630,"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-06-12T02:00:06.859Z","response_time":109,"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":["continuous-delivery","continuous-integration"],"created_at":"2026-06-12T03:02:37.315Z","updated_at":"2026-06-12T03:02:38.230Z","avatar_url":"https://github.com/ndisidore.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cicada. Hear your CI before it ships.\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/ndisidore/cicada/actions/workflows/ci.yaml\"\u003e\u003cimg src=\"https://github.com/ndisidore/cicada/actions/workflows/ci.yaml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://go.dev\"\u003e\u003cimg src=\"https://img.shields.io/badge/Go-1.25-00ADD8?logo=go\u0026logoColor=white\" alt=\"Go\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/moby/buildkit\"\u003e\u003cimg src=\"https://img.shields.io/badge/BuildKit-powered-blue?logo=docker\u0026logoColor=white\" alt=\"BuildKit\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://kdl.dev\"\u003e\u003cimg src=\"https://img.shields.io/badge/config-KDL-blueviolet\" alt=\"KDL\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nA container-native CI pipeline runner powered by [BuildKit](https://github.com/moby/buildkit).\nDefine pipelines in [KDL](https://kdl.dev), run them anywhere BuildKit runs -- your laptop included.\n\nNo more \"push and pray.\"\n\n## Motivation\n\nMost CI systems share the same dirty secret: the only way to find out if your pipeline works is to push it and wait. You tweak some YAML, open a PR, stare at a spinner for 8 minutes, watch it fail on line 3, and do it all over again. It's the worst feedback loop in software engineering, and we've somehow normalized it.\n\nGood CI should be better than that. Specifically, it should be:\n\n- **Locally repeatable**: The exact same pipeline that runs in CI should run on your machine. Not a \"close enough\" approximation. The *same* thing. If it passes on your laptop, it passes in CI. Full stop.\n\n- **Debuggable**: When something breaks, you should be able to drop into a shell, poke around, and iterate -- not squint at truncated logs from a VM you'll never touch.\n\n- **Tool-centric**: CI should be a thin wrapper around your existing scripts and task runners, not a vendor-specific reimagination of how builds work. Your `mise` tasks, your Makefile, your shell scripts -- those are the source of truth.\n\n- **Portable**: Switching CI providers shouldn't require rewriting your entire pipeline from scratch. Your build logic lives in *your* repo, not in some provider's proprietary DSL.\n\n- **Fast via caching**: Content-hash-based caching should prevent redundant work. You shouldn't reinstall your dependencies every single run because the CI system forgot what happened 5 minutes ago.\n\nCicada takes these ideas seriously. Pipelines are declared in KDL (not YAML -- you're welcome), executed inside containers via BuildKit, and run the same way everywhere. Your laptop is a first-class CI environment.\n\n## Comparison to Dagger\n\n[Dagger](https://dagger.io/) is a major inspiration for Cicada. It proved that BuildKit is a fantastic execution engine for CI and that local-first pipelines are not just possible but *preferable*. Cicada wouldn't exist without the trail Dagger blazed.\n\nThat said, Dagger's power comes with friction that Cicada tries to avoid:\n\n| | Dagger | Cicada |\n|---|---|---|\n| **Pipeline definition** | Go / TypeScript / Python SDK | KDL config file |\n| **Learning curve** | SDK APIs, generated clients, GraphQL engine internals | One config format, handful of options |\n| **Upgrade path** | Regenerate `./internal/dagger`, SDK version coupling, potential Go version mismatches | `go install` / update a binary |\n| **API surface** | Large, partially chainable, partially not | Deliberately small -- steps, mounts, caches, dependencies |\n| **Module ecosystem** | Daggerverse (powerful, but unclear trust/security model for secrets and env access) | None yet -- your pipeline is self-contained |\n| **Runtime overhead** | GraphQL engine + SDK runtime + BuildKit | BuildKit (that's it) |\n\nThe short version: Dagger gives you a full programming language and an ecosystem to go with it. Cicada gives you a config file and gets out of the way. If your pipeline needs loops, conditionals, and dynamic graph construction, Dagger is the better tool. If your pipeline is \"run these commands in these containers in this order,\" Cicada is the lighter path to get there.\n\nBoth agree on the thing that matters most: CI should run on your laptop.\n\n## Prerequisites\n\n- [mise](https://mise.jdx.dev/) -- task runner and tool manager (handles Go + linter versions for you)\n- A running [BuildKit](https://github.com/moby/buildkit) daemon (`buildkitd`)\n\n## Quick Start\n\n```bash\n# Install tools (Go, golangci-lint) via mise\nmise install\n\n# Build cicada\nmise build\n\n# Validate a pipeline\n./bin/cicada validate examples/hello.kdl\n\n# Run a pipeline\n./bin/cicada run examples/hello.kdl\n```\n\n## Pipeline Syntax\n\nPipelines are written in [KDL](https://kdl.dev), a document language that's cleaner than YAML and more readable than JSON.\n\n```kdl\npipeline \"hello\" {\n  step \"greet\" {\n    image \"alpine:latest\"\n    run \"echo 'Hello from Cicada!'\"\n  }\n\n  step \"build\" {\n    image \"rust:1.76\"\n    depends-on \"greet\"\n    mount \".\" \"/src\"\n    workdir \"/src\"\n    run \"cargo build\"\n  }\n}\n```\n\n### Step / Job Options\n\n| Option           | Scope     | Description                              | Example                                  |\n|------------------|-----------|------------------------------------------|------------------------------------------|\n| `image`          | job       | Container image to run in                | `image \"golang:1.25\"`                    |\n| `run`            | step      | Shell command (multiple allowed)         | `run \"go test ./...\"`                    |\n| `depends-on`     | job       | Job dependency (runs after)              | `depends-on \"build\"`                     |\n| `mount`          | job/step  | Bind mount from host                     | `mount \".\" \"/src\"`                       |\n| `mount` (ro)     | job/step  | Read-only bind mount                     | `mount \".\" \"/src\" readonly=true`         |\n| `workdir`        | job/step  | Working directory inside container       | `workdir \"/src\"`                         |\n| `cache`          | job/step  | Persistent cache volume                  | `cache \"gomod\" \"/go/pkg/mod\"`            |\n| `env`            | job/step  | Environment variable                     | `env \"GO111MODULE\" \"on\"`                 |\n| `secret`         | job/step  | Inject a declared secret                 | `secret \"TOKEN\" env=\"TOKEN\"`             |\n| `shell`          | job/step  | Override default shell                   | `shell \"/bin/bash\" \"-e\" \"-c\"`            |\n| `timeout`        | job/step  | Execution timeout                        | `timeout \"5m\"`                           |\n| `retry`          | job/step  | Retry on failure                         | `retry { attempts 3; delay \"5s\" }`       |\n| `allow-failure`  | step      | Step failure does not fail the job       | `allow-failure`                          |\n| `when`           | job/step  | Conditional execution (CEL expression)   | `when \"branch == 'main'\"`                |\n| `no-cache`       | job/step  | Disable BuildKit cache                   | `no-cache`                               |\n| `platform`       | job       | OCI target platform                      | `platform \"linux/arm64\"`                 |\n| `export`         | job/step  | Export a path to the host                | `export \"/out/app\" local=\"./bin/app\"`    |\n| `publish`        | job       | Publish filesystem as OCI image          | `publish \"ghcr.io/user/app:latest\"`      |\n\nSee [docs/schema.md](docs/schema.md) for the full reference and inheritance rules. Dependencies between jobs are resolved via topological sort; Cicada catches cycles and missing references before anything runs.\n\n## CLI Usage\n\n```bash\n# Validate without running\ncicada validate pipeline.kdl\n\n# Render a dependency graph\ncicada visualize pipeline.kdl\ncicada visualize pipeline.kdl --output pipeline.d2\n\n# Run a pipeline\ncicada run pipeline.kdl\n\n# Run against a remote BuildKit daemon\ncicada run pipeline.kdl --addr tcp://buildkit.example.com:1234\n\n# Dry run (generate LLB without executing)\ncicada run pipeline.kdl --dry-run\n\n# Skip cache\ncicada run pipeline.kdl --no-cache\n\n# Pre-pull images, then run offline\ncicada pull pipeline.kdl\ncicada run pipeline.kdl --offline\n\n# Manage the local BuildKit engine\ncicada engine start\ncicada engine status\ncicada engine stop\n```\n\nSee [docs/cli.md](docs/cli.md) for the complete flag reference.\n\n## Development\n\n[mise](https://mise.jdx.dev/) is the preferred way to interact with the project. All common tasks are a `mise` invocation away:\n\n```bash\nmise build        # Build the CLI binary to ./bin/cicada\nmise test         # Run tests with -race\nmise vet          # Run go vet\nmise lint         # Run golangci-lint\nmise fmt          # Format code with gofmt\nmise fmt:check    # Check formatting (CI-friendly)\nmise ci           # Run the full CI suite locally (build + fmt + vet + lint + test)\n```\n\nYes, `mise ci` runs the *actual* CI checks on your machine. Locally repeatable CI -- we meant it.\n\n## Project Layout\n\n```text\ncmd/cicada/            CLI entry point\npkg/pipeline/          Pipeline types and validation (importable)\npkg/parser/            KDL-to-Pipeline parser (importable)\npkg/conditional/       CEL-based condition evaluation (importable)\npkg/slogctx/           slog context utilities (importable)\npkg/gitinfo/           Git metadata helpers (importable)\ninternal/builder/      BuildKit LLB generation\ninternal/runner/       BuildKit execution engine\ninternal/cache/        Cache analytics and spec parsing\ninternal/secret/       Host-side secret resolution\ninternal/runtime/      Container runtime detection and abstraction\ninternal/tracing/      OpenTelemetry setup and vertex observer\nexamples/              Example KDL pipelines\n```\n\nPackages under `pkg/` are stable and safe for external consumers to import. Packages under `internal/` are implementation details.\n\n## Why Go?\n\nCicada was originally planned in Rust, and if you go back to the first few commits, you'll see that is how it started. But the container ecosystem speaks Go. BuildKit, containerd, the OCI spec libraries, Docker itself -- they're all Go projects with Go APIs. Writing Cicada in Rust would have meant maintaining FFI bindings or shelling out to CLI wrappers, or attempting to maintain complex gRPC-based session code for core functionality, trading real engineering time for a language preference.\n\nGo gave us native BuildKit integration (LLB construction, solve API, session management) with zero glue code. The tradeoff was worth it.\n\n## Roadmap\n\nSee [ROADMAP.md](ROADMAP.md) for what's coming next; matrix builds, modular configs, advanced caching, and more.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndisidore%2Fcicada","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fndisidore%2Fcicada","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndisidore%2Fcicada/lists"}