Skip to content

Releases: geomyidia/cascade

Release v0.1.3

Choose a tag to compare

@oubiwann oubiwann released this 08 May 16:27

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: *GitDiffError grew a Dir field next to Cmd, mirroring *golist.ExitError's shape exactly (cousin-shape parity, #17).
  • internal/cli: gitDiffResult now carries the actual argv constructed by the seam, so *GitDiffError.Cmd reflects the exact git diff invocation rather than a parallel reconstruction that could drift (#17).
  • pkg/golist: removed a stale _ = io.EOF import-anchor placeholder; the io import is already used by runResult.stdout io.Reader in production code (#16).

Documentation

  • Three citation-drift fixes in source comments: AP-02 → AP-13 at pkg/golist/errors.go:16, and EH-08 → CC-08 at both pkg/golist/golist.go:204 and internal/cli/seam.go:62 (#16).
  • Strengthened the post-hoc-vs-propagation rationale at both classifyRunError / classifyGitDiffError sites; 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 claimed moduleRoot stays empty on os.Getwd errors; the post-fix filepath.Abs("") actually resolves it to the cwd) (#16).
  • Added an AP-07 deviation (acknowledged) paragraph at internal/project/version.go's build-info var block, naming the link-time-injection rationale and the kubectl/hugo/cobra precedent (#16).
  • Added a PS-06 carry-forward note in CLAUDE.md's Architecture section explaining the pkg/-layout deviation and flagging it for a v1.0 revisit (#16).
  • Documented pkg/changeset.config.moduleRootSet as a deliberate test-driven design choice (#16).
  • Fixed the audit prompt's own TD-09 / IM-04 mis-pair to IM-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.go into cli_test.go (#16).
  • Refactored the inline-map subtest name in pkg/golist/golist_test.go:TestRun_DefaultPatterns to a struct table (#16).
  • New assertion in internal/cli/seam_test.go:TestClassifyGitDiffError verifies *GitDiffError.Dir is populated (#17).

Build / release tooling (#19)

  • make release-dry-run now works on stdlib-only modules. The previous git diff --quiet go.mod go.sum errored with fatal: go.sum: no such path because cascade has no third-party deps, and was also fooled by stat-cache staleness after go mod tidy bumped go.mod's mtime without changing content. Now uses git status --porcelain, which handles missing paths cleanly and is status-aware.
  • make release reads VERSION from internal/project/VERSION via override, eliminating the typo-prone make release VERSION=v0.1.0 CLI form. The tag string is constructed as v$(VERSION).
  • make release refuses to run on a dirty working tree — otherwise the build metadata embedded in the tagged binary would carry a -dirty suffix and the tag would not reproduce the working tree at tag time.
  • New make release-notes target generates a placeholder at ./workbench/release-notes-$(VERSION).md with Overview, Details, and Change Log sections plus a GitHub compare URL. Refuses to overwrite an existing file.
  • make release now auto-creates a GitHub release via gh 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-run reports release-notes presence/absence with the appropriate guidance.
  • make help updated.

Change Log

v0.1.2..v0.1.3

v0.1.2 — VERSION file bump (cosmetic patch)

Choose a tag to compare

@oubiwann oubiwann released this 07 May 21:55

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 VERSION file at build time, so make-built v0.1.1 binaries already report 0.1.1 correctly.
  • 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.2

Known 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 the Makefile's release-dry-run target (compare cat internal/project/VERSION against $(VERSION) minus the v prefix); or
  • A pre-tag manual checklist embedded in the release flow.

Tracked informally for v0.2.

v0.1.1 — Release Notes

Choose a tag to compare

@oubiwann oubiwann released this 07 May 21:47

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.1

What changed

Three-layer holistic fix (issue #12, PR #13):

  • Library (pkg/changeset): Resolve now absolutizes the supplied moduleRoot via filepath.Abs before the dirMap lookup. Protects any current or future library consumer from the bug class — the deferred pkg/cascade.Run, third-party imports of pkg/changeset, downstream tooling.
  • CLI (internal/cli): cli.Run absolutizes cfg.root once 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): when changedFiles contains .go-suffix entries but the affected-set is empty, cascade emits a stderr breadcrumb:
    cascade: 4 changed Go file(s) did not resolve to any package; check --root
    
    Filtered on .go so 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.
  • --version output 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 --root flag.
  • 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

Choose a tag to compare

@oubiwann oubiwann released this 07 May 18:40

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: 0 success, 1 flag/input error, 2 git diff failed, 3 go list failed, 4 internal logic error (should never occur), 5 cancelled 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 — typed Package values parsed from go list -deps -json output, plus Run() that owns the os/exec boundary
  • pkg/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.0

Requires 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.