Releases: geomyidia/cascade
Release list
Release v0.1.3
v0.1.3 - Release Notes
Overview
Internal cleanup release. Closes the M2–M5 Go quality audit (16 findings plus 1 meta-finding) and ships a redesigned make release* workflow. No public API or CLI behaviour changes; safe to upgrade from v0.1.2.
The audit deliverable, CDC verification, follow-up plan, and per-finding closure walks live in docs/dev/0014-go-quality-audit.md and docs/dev/0015-audit-follow-up-plan-address-all-16-findings.md.
Details
Internals
internal/cli:*GitDiffErrorgrew aDirfield next toCmd, mirroring*golist.ExitError's shape exactly (cousin-shape parity, #17).internal/cli:gitDiffResultnow carries the actualargvconstructed by the seam, so*GitDiffError.Cmdreflects the exactgit diffinvocation rather than a parallel reconstruction that could drift (#17).pkg/golist: removed a stale_ = io.EOFimport-anchor placeholder; theioimport is already used byrunResult.stdout io.Readerin production code (#16).
Documentation
- Three citation-drift fixes in source comments:
AP-02 → AP-13atpkg/golist/errors.go:16, andEH-08 → CC-08at bothpkg/golist/golist.go:204andinternal/cli/seam.go:62(#16). - Strengthened the post-hoc-vs-propagation rationale at both
classifyRunError/classifyGitDiffErrorsites; the CC-08 deviation is now explicit and consistent across cousins (#16). - Rewrote the post-bug-#12 doc drift in
pkg/changeset.Resolve(the doc claimedmoduleRoot stays emptyonos.Getwderrors; the post-fixfilepath.Abs("")actually resolves it to the cwd) (#16). - Added an
AP-07 deviation (acknowledged)paragraph atinternal/project/version.go's build-info var block, naming the link-time-injection rationale and the kubectl/hugo/cobra precedent (#16). - Added a
PS-06carry-forward note inCLAUDE.md's Architecture section explaining thepkg/-layout deviation and flagging it for a v1.0 revisit (#16). - Documented
pkg/changeset.config.moduleRootSetas a deliberate test-driven design choice (#16). - Fixed the audit prompt's own
TD-09 / IM-04mis-pair toIM-12 / TD-09(PROMPT-1, #16).
Tests
- "Parallel-unsafe" headers on all four seam-test files (
internal/cli/seam_test.go,pkg/golist/seam_test.go,pkg/changeset/seam_test.go,internal/project/version_test.go), making the no-t.Parallel()discipline visible at every shared-state mutation site (#16). - Consolidated duplicate
internal/cli/helpers_test.gointocli_test.go(#16). - Refactored the inline-map subtest name in
pkg/golist/golist_test.go:TestRun_DefaultPatternsto a struct table (#16). - New assertion in
internal/cli/seam_test.go:TestClassifyGitDiffErrorverifies*GitDiffError.Diris populated (#17).
Build / release tooling (#19)
make release-dry-runnow works on stdlib-only modules. The previousgit diff --quiet go.mod go.sumerrored withfatal: go.sum: no such pathbecause cascade has no third-party deps, and was also fooled by stat-cache staleness aftergo mod tidybumpedgo.mod's mtime without changing content. Now usesgit status --porcelain, which handles missing paths cleanly and is status-aware.make releasereadsVERSIONfrominternal/project/VERSIONviaoverride, eliminating the typo-pronemake release VERSION=v0.1.0CLI form. The tag string is constructed asv$(VERSION).make releaserefuses to run on a dirty working tree — otherwise the build metadata embedded in the tagged binary would carry a-dirtysuffix and the tag would not reproduce the working tree at tag time.- New
make release-notestarget generates a placeholder at./workbench/release-notes-$(VERSION).mdwithOverview,Details, andChange Logsections plus a GitHub compare URL. Refuses to overwrite an existing file. make releasenow auto-creates a GitHub release viagh release create --notes-file …after tagging, when a release-notes file is present. Without a notes file, only the tag is pushed (with a clear "skipping" notice).make release-dry-runreports release-notes presence/absence with the appropriate guidance.make helpupdated.
Change Log
v0.1.2 — VERSION file bump (cosmetic patch)
cascade v0.1.2 — VERSION file bump (cosmetic patch)
Cosmetic patch release. No code changes; no behaviour changes; same exit-code contract; same flag set; same library API.
What this fixes
v0.1.1 shipped with internal/project/VERSION=0.1.0, so proxy-installed v0.1.1 binaries (go install ...@v0.1.1) reported cascade 0.1.0 incorrectly. The actual bug fix that motivated v0.1.1 (issue #12 — silent-empty output when --root is relative) is correct and works in v0.1.1. Only the --version self-report string was stale.
v0.1.2 is the one-line fix: bump the embedded VERSION file to 0.1.2 (skipping 0.1.1 since that tag already exists). Proxy-installed v0.1.2 binaries now correctly report cascade 0.1.2 (build N/A).
Should you upgrade from v0.1.1?
- Make-built / CI binaries: no — the Makefile's ldflags injection always reads the current
VERSIONfile at build time, so make-built v0.1.1 binaries already report0.1.1correctly. go install ...@v0.1.1-built binaries: optional — the version self-report is one minor behind, but the actual cascade behaviour is identical. Upgrade if you want the correct version string in your CI logs.go install ...@v0.1.0-built binaries (still affected by bug #12): yes — upgrade to v0.1.2 to get both the bug-#12 fix from v0.1.1 AND the correct version self-report.
Install
go install github.com/geomyidia/cascade/cmd/cascade@v0.1.2Known issue (deferred to v0.2)
The (build N/A) portion of --version output for proxy-installed binaries (everything except the Version field) is a separate, larger fix that needs to consume runtime/debug.ReadBuildInfo's BuildInfo.Settings (vcs.revision, vcs.time) — those carry commit + timestamp metadata for go install-built binaries. Tracked for v0.2.
Lessons learned (for v0.2 release-prep)
The "we forgot to bump internal/project/VERSION" miss is the kind of thing a release-prep checklist catches. v0.2 release-prep should include either:
- A
VERSION-vs-tag-version check in theMakefile'srelease-dry-runtarget (comparecat internal/project/VERSIONagainst$(VERSION)minus thevprefix); or - A pre-tag manual checklist embedded in the release flow.
Tracked informally for v0.2.
v0.1.1 — Release Notes
cascade v0.1.1
Patch release. Fixes a silent-empty-output regression in v0.1.0 where the documented invocation cascade --base=origin/main --head=HEAD (no --root, taking the default .) silently emitted empty output and exited 0 against any real Go monorepo. The exact silent-zero-packages failure mode that drove the gta → cascade pivot.
v0.1.0 users should upgrade. The bug only surfaces when --root is relative (the default) AND changedFiles contains relative paths AND the package list (from go list -deps -json) reports absolute Dir values — which is every real-world CI use case.
Install
go install github.com/geomyidia/cascade/cmd/cascade@v0.1.1What changed
Three-layer holistic fix (issue #12, PR #13):
- Library (
pkg/changeset):Resolvenow absolutizes the suppliedmoduleRootviafilepath.Absbefore the dirMap lookup. Protects any current or future library consumer from the bug class — the deferredpkg/cascade.Run, third-party imports ofpkg/changeset, downstream tooling. - CLI (
internal/cli):cli.Runabsolutizescfg.rootonce after flag parse, before any of the four downstream consumers (runGitDiff,classifyGitDiffError,golist.WithDir,changeset.WithModuleRoot) sees it. The default--root=.now resolves to the process cwd as documented. - Diagnostic (
internal/cli): whenchangedFilescontains.go-suffix entries but the affected-set is empty, cascade emits a stderr breadcrumb:Filtered oncascade: 4 changed Go file(s) did not resolve to any package; check --root.goso docs-only PRs (legitimate empty case) stay silent. Doesn't change exit code; just makes the suspicious-zero-result case loud instead of silent.
What didn't change
- The CLI flag set, the public library API surface, and the exit-code contract are all unchanged. v0.1.1 is a bug fix, not a feature release.
- The
WithModuleRoot's documented contract is refined (relative paths now resolve correctly) — no caller behaviour breaks. --versionoutput format is unchanged.
Regression coverage
Four new tests across two packages pin both the fix and the diagnostic:
pkg/changeset.TestResolve_RelativeModuleRoot_AbsolutizedInternally— library-layer regression for the.and""moduleRoot cases.internal/cli.TestRun_DefaultRootResolvesCWD— end-to-end CLI invocation with no--rootflag.internal/cli.TestRun_DiagnosticOnSuspiciousEmpty— diagnostic stderr line fires correctly.internal/cli.TestRun_NoDiagnosticOnDocsOnlyEmpty— diagnostic does NOT fire on legitimate docs-only changesets (pins the.go-suffix filter).
All five cascade packages remain at 100% statement coverage.
Methodology note
This bug surfaced a parameter-space gap that 100% line coverage didn't catch: the bug lived in the cell relative moduleRoot × absolute pkg.Dir, which neither the M4 changeset tests nor the M5 CLI tests happened to pair. Coverage measured statement reachability, not behavioural coverage across the parameter space. The full closing retrospective at docs/dev/0013-bug12-fix-relative-root.md records this as a carry-forward into future test design.
Acknowledgments
Bug discovered while wiring cascade into a real Go monorepo CI workflow. The author traced the exact code path before filing the issue, which made the holistic-fix design straightforward. Exactly the kind of high-quality bug report that makes patch releases land cleanly.
v0.1.0 — Initial Release
cascade v0.1.0
cascade computes the reverse-transitive closure of a Go change-set under the imports relation: given a base ref and a head ref, it prints the affected-package set so CI can run only the tests for packages the PR could plausibly have broken.
The intended use is dropping the full-suite test bill on a typical PR by 3-10× while keeping merge-queue runs honest.
Why cascade exists
DigitalOcean's gta silently fails on Go 1.25.x — the failure surfaces inside golang.org/x/tools/go/packages.Load, which gta uses; that loader's stricter module resolution emits "go: updates to go.mod needed" against modules the regular go list family considers tidy, and gta swallows the error and exits 0 with an empty package list. An empty list means CI runs zero tests; zero tests means a green build that proved nothing.
cascade takes a deliberately narrower path than gta: shell out to go list -deps -json directly (verified on Go 1.25 and 1.26), parse the stream into typed values, build the import DAG, reverse the edges, and compute the closure of the change-set. Every io error is returned and surfaced — silent-failure mode is structurally impossible.
What's in v0.1.0
The CLI (cmd/cascade):
- Flags:
--tags,--base,--head,--changed-files(file path or-for stdin),--root,--version,--help - Documented exit-code contract:
0success,1flag/input error,2git difffailed,3go listfailed,4internal logic error (should never occur),5cancelled or interrupted (SIGINT / SIGTERM / context cancellation) - Signal-driven cancellation via
signal.NotifyContext - Output: one affected import path per line, sorted lexicographically, ready to feed into
go test
The library API — three pure-data packages, all exported, all 100% test-covered:
pkg/golist— typedPackagevalues parsed fromgo list -deps -jsonoutput, plusRun()that owns theos/execboundarypkg/depgraph— directed import graph + reverse-transitive closure traversal (BFS over reverse edges, cycle-safe)pkg/changeset— maps changed file paths to import paths via parent-directory match
The three compose without adapter glue:
pkgs, err := golist.Run(ctx, tags, []string{"./..."}, golist.WithDir(root))
g := depgraph.Build(pkgs)
seeds := changeset.Resolve(changedFiles, pkgs, changeset.WithModuleRoot(root))
affected := g.RevDepClosure(seeds)Install
go install github.com/geomyidia/cascade/cmd/cascade@v0.1.0Requires Go ≥ 1.25.3.
Usage
# Standard CI usage: invoke with a PR's base/head range
cascade --tags=integration_test --base=origin/main --head=HEAD
# Feed the output directly into go test
go test $(cascade --base=origin/main --head=HEAD)
# Or, when you already have a list of changed files (e.g., from paths-filter)
git diff --name-only origin/main..HEAD | cascade --changed-files=-cascade --help prints the full flag reference and exit-code table.
CI integration tip
Branch on the specific exit code, not just non-zero:
| Code | Meaning | CI workflow advice |
|---|---|---|
| 0 | Success (output may be empty) | Continue with go test $(cascade ...) |
| 1 | Flag-parse / input error | Fail-fast; the workflow misconfigured cascade |
| 2 | git diff failed |
Investigate refs; *GitDiffError carries git's stderr |
| 3 | go list failed |
Investigate go.mod; *golist.ExitError carries go list's stderr |
| 4 | Internal logic error | Fail-fast; report as a cascade bug |
| 5 | Cancelled / interrupted | Retry the run; transient |
Pre-v1.0 stability note
This is v0.x — no API stability guarantees yet. The CLI flag set, the public library API surface (pkg/golist, pkg/depgraph, pkg/changeset), and the exit-code contract may change in v0.x releases as real-world usage surfaces friction. v1.0 will commit to the public surface in perpetuity (or until a v2-directory rev). If you're integrating cascade into a CI pipeline today, pin to a specific tag (@v0.1.0) rather than @latest to avoid surprise.
Acknowledgments
cascade owes its problem framing to DigitalOcean's gta, the prior art that worked beautifully on Go 1.24 and earlier. The algorithm shape — go list -deps -json plus reverse-graph traversal — is documented in various community gists and shell sketches; cascade refines it into a small, hand-rolled Go binary with first-class error handling and a public package API.
The project name nods both to the operation (a change cascading through its reverse-dependents) and to the Cascade Range of the Pacific Northwest.