From 83fa053ee86ccaf1144f3257c950e3d5141da449 Mon Sep 17 00:00:00 2001 From: Duncan McGreggor Date: Thu, 7 May 2026 13:14:33 -0500 Subject: [PATCH 1/5] m5 retro follow-up: F-18 + F-19 closing evidence (ledger 22/22 done) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the two formally-deferred ledger rows from M5 PR #10: F-18 — `go install github.com/geomyidia/cascade/cmd/cascade@78cc7ae` via proxy.golang.org succeeded; resolves to pseudo-version v0.0.0-20260507180335-78cc7ae2dac9. Installed binary's --version + --help output captured (3.8MB darwin/arm64). Pseudo-version-extraction logic from M1.5 threads the commit SHA + build date through ReadBuildInfo into --version output. F-19 — Cascade-against-cascade real-codebase sanity check (M2 F-18 lineage; the gta-target codebase from M2 is private, this is the public-PR-safe substitute). Six cases: - golist root change → 5 packages affected (sorted) - changeset mid → 3 packages - cmd/cascade leaf → 1 package - internal/cli mid → 2 packages - empty stdin → empty output, exit 0 - real --base/--head git diff → 134ms wall-clock end-to-end Every observed affected-set matches the hand-derived prediction. Updated closure: M5 ledger reaches 22/22 done. Zero deferred at final close; zero no-ops; zero open. The two-deferral pattern (merge-prerequisite verification rows with explicit re-entry conditions) worked exactly as LEDGER_DISCIPLINE.md documents. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...tation-retrospective-m5-cli-main-wiring.md | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/docs/dev/0012-implementation-retrospective-m5-cli-main-wiring.md b/docs/dev/0012-implementation-retrospective-m5-cli-main-wiring.md index 1213714..1c90145 100644 --- a/docs/dev/0012-implementation-retrospective-m5-cli-main-wiring.md +++ b/docs/dev/0012-implementation-retrospective-m5-cli-main-wiring.md @@ -210,8 +210,82 @@ The five-seam pattern is now established convention but isn't yet mentioned in ` - **The trio composes without adapter code: structurally verified.** M3's design claim → M4's retro claim → M5's `runPipeline` four-liner. The chain holds. **For M6, if any v0.1.0 release-prep work surfaces an integration-layer adapter need, that's the signal to revisit one of the pure packages' API.** Otherwise, the trio's API stability claim is settled for v1.0. +## F-18 / F-19 post-merge follow-up evidence + +Appended after the M5 PR (#10) merged to `main` at commit `78cc7ae`. Both deferrals close out here; M5's ledger reaches **22/22 done**. + +### F-18: `go install …@` against `proxy.golang.org` + +Same M1 F-12 pattern. Fresh `GOBIN`, default `GOPROXY=https://proxy.golang.org,direct`: + +``` +$ GOBIN=$tmp GOPROXY=https://proxy.golang.org,direct \ + go install github.com/geomyidia/cascade/cmd/cascade@78cc7ae +go: downloading github.com/geomyidia/cascade v0.0.0-20260507180335-78cc7ae2dac9 +$ echo $? +0 + +$ $tmp/cascade --version +cascade 0.1.0 (build @78cc7ae, 2026-05-07T18:03:35Z) + +$ $tmp/cascade --help | head -8 +cascade — reverse-transitive closure of a Go change-set under the imports relation + +Usage: + cascade --base [--head ] [--tags ] [--root ] + cascade --changed-files=- [--tags ] [--root ] (read from stdin) + cascade --changed-files= [--tags ] [--root ] + cascade --version + cascade --help +``` + +Notes: +- `proxy.golang.org` indexed the merge SHA `78cc7ae` and resolved it to pseudo-version `v0.0.0-20260507180335-78cc7ae2dac9` (timestamp + 12-char SHA prefix). The pseudo-version-extraction logic from M1.5 (commit + build-date from `Main.Version` via `runtime/debug.ReadBuildInfo`) is what populates the `--version` output's `@78cc7ae` and `2026-05-07T18:03:35Z` fields. Branch is empty for proxy installs (proxy doesn't carry branch metadata) — same as M1's F-12 closing evidence. +- Binary builds clean for `darwin/arm64` (3.8 MB). +- The `--help` output's first 8 lines confirm the inline `helpText` constant from `internal/cli/cli.go` is the source-of-truth that ships in the released binary. + +**F-18 verified mergeable: install path is clean, version metadata threads correctly.** + +### F-19: real-codebase sanity check (cascade-against-cascade) + +Per the M2 F-18 lineage. Full pipeline (`git diff` → `go list` → `depgraph.Build` + `changeset.Resolve` → `RevDepClosure`) against the merged-to-`main` cascade repo at `cf38732` (head of `main` at follow-up time). + +The `gta`-target codebase from M2 F-18 is private; this evidence uses cascade-against-cascade as the public sanity-check substitute. Cascade's own dep graph has 5 packages (`pkg/golist`, `pkg/depgraph`, `pkg/changeset`, `internal/project`, `internal/cli`) plus `cmd/cascade`; the topology is small but each affected-set prediction is hand-derivable, which makes it the right shape for a structural smoke test. + +**Six cases run; six expected affected-sets observed:** + +| # | Change-set (stdin) | Expected affected-set (hand-derived) | Observed | Wall-clock | +|---|---|---|---|---| +| 1 | `pkg/golist/golist.go` (root of dep tree) | golist, depgraph, changeset, internal/cli, cmd/cascade | match (5 packages, sorted) | sub-second | +| 2 | `pkg/changeset/changeset.go` (mid; only cli + cmd import it) | changeset, internal/cli, cmd/cascade | match (3 packages) | sub-second | +| 3 | `cmd/cascade/main.go` (leaf; nothing imports `main`) | cmd/cascade only | match (1 package) | sub-second | +| 4 | `internal/cli/cli.go` (mid; only cmd/cascade imports) | internal/cli, cmd/cascade | match (2 packages) | sub-second | +| 5 | (empty stdin) | (none) | empty stdout, exit 0 | sub-second | +| 6 | real `--base=6ab23c5 --head=HEAD` (mostly docs commits) | empty (no Go files in diff) | empty stdout, exit 0 | **134ms wall-clock** | + +Case 6 measures the full pipeline against a real `git diff --name-only` invocation: `git diff` runs as a subprocess; `go list -deps -json ./...` runs as a subprocess (the heavier of the two for cascade's small repo); both `depgraph.Build` and `changeset.Resolve` run on the parsed result; `RevDepClosure` walks the (empty) seed set. **134ms wall-clock end-to-end** is well under the M5 spec's "milliseconds-to-tens-of-milliseconds + go list's seconds = under 5s for a typical PR" budget. + +The M2 retro's structural claim — *"the gta failure mode (silent zero-package output from `packages.Load`) is structurally absent"* — extends here: every case's observed output matches the hand-derived prediction, including the empty cases (case 5, case 6). Cascade does not silently emit zero packages when there's real input; cascade does not crash, hang, or exit non-zero when there's empty input. Each affected-set is what a human walking the dep graph would predict. + +**Generic-framing note:** the `gta`-target codebase from M2 F-18 was a private, large-corpus (~2400 packages) module the maintainer can rerun cascade against off-record. The cascade-against-cascade evidence here is the public-PR-safe substitute that closes F-19's letter and spirit: a real-Go-module sanity check producing predicted output. The maintainer's off-record run against the private corpus is the load-bearing scale evidence; both pieces together close M5's loop on the original gta-replacement promise. + +**F-19 verified mergeable: cascade produces correct affected-sets on a real Go module across the topology; real `git diff` integration takes 134ms wall-clock.** + +### Updated closure summary + +After the F-18 + F-19 follow-up commits land, the M5 ledger sits at: + +| Status | Count | +|--------|-------| +| Done | **22** | +| Deferred | 0 | +| No-op | 0 | +| Open at close | 0 | + +**22/22 done.** Both formerly-deferred rows close cleanly. The two-deferral pattern (F-18 + F-19 require merged-commit prerequisites) worked exactly as documented: deferral with explicit re-entry conditions, evidence appended once those conditions were met. Future milestones with merge-prerequisite verification rows should follow the same pattern. + ## Closure -Closing report submitted with the M5 PR; CDC verification pending. **20 of 22 ledger rows reach a final status of `done` in this PR; 2 (F-18, F-19) deferred to post-merge follow-ups with documented reasons and re-entry conditions.** Zero deferrals beyond merge-prerequisite items. Zero no-ops. Zero open at close. +Closing report submitted with the M5 PR (#10) and the F-18/F-19 follow-up PR. **All 22 ledger rows reach a final status of `done`.** Zero deferrals at final close. Zero no-ops. Zero open at close. -Total rows: 22. Done in PR: 20. Deferred to post-merge follow-up: 2. No-op: 0. +Total rows: 22. Done: 22. Deferred: 0. No-op: 0. From fa5e2f02efcb53505d5b4ec8d2396cd1717acc26 Mon Sep 17 00:00:00 2001 From: Duncan McGreggor Date: Thu, 7 May 2026 13:14:53 -0500 Subject: [PATCH 2/5] Removed milestones table from README (0.1.0 work is now done). --- README.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/README.md b/README.md index dab7e71..4d8edd9 100644 --- a/README.md +++ b/README.md @@ -125,19 +125,6 @@ The pure packages (`golist`, `depgraph`, `changeset`) have no io between them an Every step is small, typed, and tested. The only io is the two `os/exec` calls in steps 1 and 2 — both are isolated in the io shell, so the algorithmic core has no error-swallowing surface area. -## Milestones - -| | Milestone | Status | -|---|---|---| -| M1 | Repo scaffold + CI baseline | completed | -| M2 | `go list` adapter (shell-out + streaming JSON parser) | completed | -| M3 | Dep graph + reverse-dep index + closure | in progress | -| M4 | Changed-files-to-packages mapping | not started | -| M5 | CLI + main wiring | not started | -| M6 | `v0.1.0` release | not started | - -Detailed plans live in [`docs/design/01-draft/`](docs/design/01-draft/). The high-level plan is [`0001-cascade-high-level-project-plan.md`](docs/design/01-draft/0001-cascade-high-level-project-plan.md); each milestone has (or will have) its own design doc. - ## Development The project ships a `Makefile` that's the canonical menu of common commands: From 7d4f963a1f58523ca95c0fa8bc2a48e1fa114ca5 Mon Sep 17 00:00:00 2001 From: Duncan McGreggor Date: Thu, 7 May 2026 13:17:06 -0500 Subject: [PATCH 3/5] Removed 'Status' section. --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 4d8edd9..be22209 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,6 @@ cascade computes the affected-package set for a Go CI test-selection workflow: given a base ref and a head ref, it prints the set of packages that need re-testing — the changed packages plus everything that imports them, transitively. The intended use is dropping the full-suite test bill on a typical PR by 3-10× while keeping merge-queue runs honest. -## Status - -**Pre-v0.1.0; under active development.** The high-level project plan and per-milestone design docs live in [`docs/design/01-draft/`](docs/design/01-draft/). The CLI binary builds today but does not yet compute anything — see the [milestone status](#milestones) below for what's wired up. API and CLI surfaces will iterate freely until v1.0; once tagged, v1.x will commit to standard Go module compatibility, with breaking changes thereafter following Go's `vN/` directory convention. - -`cascade --version` reports the embedded module version from [`project/VERSION`](project/VERSION) and, for `go install`-built binaries (where the Makefile's `-ldflags` injection isn't applied), auto-detects commit/dirty/timestamp metadata via `runtime/debug.ReadBuildInfo`. Full Makefile builds inject branch + git-describe summary on top of that. - ## Why cascade exists because [DigitalOcean's `gta`](https://github.com/digitalocean/gta) — an established Go affected-package tool — silently fails on Go 1.25.x. The failure surfaces in `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. From 9a7981044835aea883acb3b46e9d5f76e8fb3c06 Mon Sep 17 00:00:00 2001 From: Duncan McGreggor Date: Thu, 7 May 2026 13:21:18 -0500 Subject: [PATCH 4/5] =?UTF-8?q?README:=20polish=20for=20v0.1.0=20=E2=80=94?= =?UTF-8?q?=20fix=20typo,=20drop=20dev-status=20hedge,=20sync=20M4=20signa?= =?UTF-8?q?ture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five small fixes for v0.1.0-quality README: 1. Fix the broken sentence on the second Why paragraph ("Do the the Go language's tendency to swap out libraries for changes in tooling..."). Rewrote to clearly state the design rationale: Go's helper libraries (especially golang.org/x/tools/go/packages) evolve faster than the `go` command, so cascade depends on the CLI tool as the more stable API. 2. Drop the "as of this writing" hedge from the Install section's Go-version policy line. CI policy is "floor + latest major, matrix advances with each Go release" — same factual claim, no timestamp to decay. 3. Update --version flag description to show the actual output format (`cascade (build @, )`) instead of just listing the field names. Mentions the empty-Branch case for proxy-installed binaries. 4. Update the Library section's changeset.Resolve example to use the M4 v1.1 functional-option signature (changeset.WithModuleRoot). The previous variadic-empty call still compiled and worked, but relied on the os.Getwd fallback at runtime — not the recommended library usage. 5. Clarify "have no io between them" claim to acknowledge the two real io edges: golist.Run's `go list` invocation and changeset's optional os.Getwd fallback. The pure-package compositional property still holds; the wording is now precise about where the io actually lives. F-20 verify (grep -c 'exit code' README.md) still returns 1. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index be22209..cc798b2 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ cascade computes the affected-package set for a Go CI test-selection workflow: g cascade exists because [DigitalOcean's `gta`](https://github.com/digitalocean/gta) — an established Go affected-package tool — silently fails on Go 1.25.x. The failure surfaces in `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. -Do the the Go language's tendency to swap out libraries for changes in tooling, treating the tool as "the API" is often the safer, more stable approach. Thus, cascade takes a deliberately narrower path than gta: shell out to `go list -deps -json` directly (verified to work 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. +Go's helper libraries — especially `golang.org/x/tools/go/packages` — evolve faster than the `go` command itself; depending on the CLI tool is the more stable bet. 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. ## Install @@ -24,7 +24,7 @@ Do the the Go language's tendency to swap out libraries for changes in tooling, go install github.com/geomyidia/cascade/cmd/cascade@latest ``` -Requires Go ≥ 1.25.3. CI tests against the floor and the latest currently-supported Go major (1.26.x as of this writing). +Requires Go ≥ 1.25.3. CI tests against the floor and the latest currently-supported Go major; the matrix advances with each Go release. ## CLI usage @@ -62,7 +62,7 @@ git diff --name-only origin/main..HEAD | cascade --changed-files=- | `--head` | `HEAD` | Head git ref. | | `--changed-files` | (none) | Path to a file with one change-set entry per line. `-` reads from stdin. When set, `--base` is not required and `git diff` is not invoked. | | `--root` | `.` | Working directory for `go list` and module-root for `changeset.Resolve`. | -| `--version` | false | Print version metadata (Version / GitCommit / GitBranch / BuildDate) and exit. | +| `--version` | false | Print `cascade (build @, )` and exit. (Branch is empty for `go install` builds — the module proxy doesn't carry branch metadata.) | | `--help` | false | Print usage and exit. Routes to stdout per GNU convention; flag-parse errors route help to stderr per stdlib `flag` default. | ### Exit codes @@ -101,13 +101,15 @@ if err != nil { /* handle, never swallow */ } g := depgraph.Build(pkgs) // Map changed file paths onto their containing packages' import paths. -seeds := changeset.Resolve(changedFiles, pkgs) +// repoRoot is typically `git rev-parse --show-toplevel` from the caller; +// when omitted, changeset.Resolve falls back to os.Getwd. +seeds := changeset.Resolve(changedFiles, pkgs, changeset.WithModuleRoot(repoRoot)) // Compute the reverse-transitive closure (the "cascade"). affected := g.RevDepClosure(seeds) ``` -The pure packages (`golist`, `depgraph`, `changeset`) have no io between them and are 100% test-covered. Errors from the io shell (`golist.Run`) wrap their causes with `%w`, so callers can use `errors.Is`/`errors.As` to triage. +The pure packages (`pkg/golist`, `pkg/depgraph`, `pkg/changeset`) compose without adapter glue and are 100% test-covered. Errors from the io edges (`golist.Run`'s `go list` invocation; `changeset.Resolve`'s optional `os.Getwd` fallback) wrap their causes with `%w`, so callers can use `errors.Is`/`errors.As` to triage. ## How it works From 1736c4d2bce7b85312011eed8a6845fc7237a10c Mon Sep 17 00:00:00 2001 From: Duncan McGreggor Date: Thu, 7 May 2026 13:22:45 -0500 Subject: [PATCH 5/5] Fixed I/O spelling. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cc798b2..2015843 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ cascade computes the affected-package set for a Go CI test-selection workflow: g cascade exists because [DigitalOcean's `gta`](https://github.com/digitalocean/gta) — an established Go affected-package tool — silently fails on Go 1.25.x. The failure surfaces in `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. -Go's helper libraries — especially `golang.org/x/tools/go/packages` — evolve faster than the `go` command itself; depending on the CLI tool is the more stable bet. 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. +Go's helper libraries — especially `golang.org/x/tools/go/packages` — evolve faster than the `go` command itself; depending on the CLI tool is the more stable bet. 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 I/O error is returned and surfaced — silent-failure mode is structurally impossible. ## Install @@ -109,7 +109,7 @@ seeds := changeset.Resolve(changedFiles, pkgs, changeset.WithModuleRoot(repoRoot affected := g.RevDepClosure(seeds) ``` -The pure packages (`pkg/golist`, `pkg/depgraph`, `pkg/changeset`) compose without adapter glue and are 100% test-covered. Errors from the io edges (`golist.Run`'s `go list` invocation; `changeset.Resolve`'s optional `os.Getwd` fallback) wrap their causes with `%w`, so callers can use `errors.Is`/`errors.As` to triage. +The pure packages (`pkg/golist`, `pkg/depgraph`, `pkg/changeset`) compose without adapter glue and are 100% test-covered. Errors from the I/O edges (`golist.Run`'s `go list` invocation; `changeset.Resolve`'s optional `os.Getwd` fallback) wrap their causes with `%w`, so callers can use `errors.Is`/`errors.As` to triage. ## How it works @@ -119,7 +119,7 @@ The pure packages (`pkg/golist`, `pkg/depgraph`, `pkg/changeset`) compose withou 4. **Map the changed file paths to the packages that contain them.** These become the seed set. 5. **BFS from the seeds over the reversed graph**; emit the union (seeds included), sorted lexicographically for determinism. -Every step is small, typed, and tested. The only io is the two `os/exec` calls in steps 1 and 2 — both are isolated in the io shell, so the algorithmic core has no error-swallowing surface area. +Every step is small, typed, and tested. The only I/O is the two `os/exec` calls in steps 1 and 2 — both are isolated in the I/O shell, so the algorithmic core has no error-swallowing surface area. ## Development