Skip to content

m4: changeset (changed-files-to-packages mapping)#9

Merged
oubiwann merged 6 commits into
mainfrom
m4/changeset-mapping
May 7, 2026
Merged

m4: changeset (changed-files-to-packages mapping)#9
oubiwann merged 6 commits into
mainfrom
m4/changeset-mapping

Conversation

@oubiwann

@oubiwann oubiwann commented May 7, 2026

Copy link
Copy Markdown
Contributor

What

M4 — the pkg/changeset package: pure-data mapping from git diff --name-only output to the import paths of the packages those files belong to. Bridges cascade's two algorithmic primitives — golist (M2) produces []golist.Package; depgraph (M3) walks reverse-edge closures from a seed set; changeset.Resolve turns "these files changed" into "these packages are the seeds for g.RevDepClosure."

Surface: one exported function (Resolve), one option type (Option), one option constructor (WithModuleRoot). Three exported names total. Algorithm: build dirMap[pkg.Dir]→pkg.ImportPath once; for each .go file in changedFiles, take filepath.Dir(filepath.Clean(absPath)) and look up; sorted-deduped output. O(P + F).

Where

  • pkg/changeset/changeset.goOption, WithModuleRoot, config, getCwd seam, Resolve. Single file; ~140 lines including comments.
  • pkg/changeset/changeset_test.go — public-API tests (package changeset_test); 16 StandardCases + HandTraceable's 7 sub-cases + PathNormalisation's 5 cases + IgnoredGoFiles + PackagesWithEmptyFieldsSkipped + EmptyFilePathSkipped.
  • pkg/changeset/seam_test.go — internal-package tests (package changeset) for the getCwd seam; covers the os.Getwd success and error branches without depending on the real test cwd.
  • pkg/changeset/helpers_test.go — shared stringSlicesEqual helper.
  • pkg/changeset/doc.go — M1 stub updated to reflect the io-edge fallback (the // Package changeset line is preserved per F-1).
  • docs/design/05-active/0005-cascade-m4-changeset-changed-files-to-packages-mapping.md — spec amended in-PR (1.0 → 1.1) so the spec stays the source-of-truth at merge. Version History section added at the bottom; spec amendments commit message (9e92a95) and Version History commit message (e17a730) name the diff.
  • docs/dev/0009-implementation-plan-changed-files-to-packages-mapping.md — implementation plan (committed by maintainer to local main as ed64e14 before branch cut).
  • docs/dev/0010-implementation-retrospective-m4-changeset.md — closing retrospective walking F-1..F-14 row-by-row.

How to verify

make check-all                                              # build + lint + test + coverage gate
go test -race -count=10 ./pkg/changeset/...                 # F-3..F-10 (10 runs)
bash scripts/coverage-check.sh                              # F-11: per-package gate (4/4 packages at 100%)
go doc github.com/geomyidia/cascade/pkg/changeset | head -20  # F-13: full godoc
go list -m all | wc -l                                       # F-12: minimal-deps (returns 1)

# Specific subtests called out by the spec's verify regexes:
go test -run 'TestResolve_StandardCases/_test\.go_in_pkga' ./pkg/changeset    # F-6
go test -run 'TestResolve_StandardCases/mixed' ./pkg/changeset                # F-7
go test -run 'TestResolve_StandardCases/Go_file_outside' ./pkg/changeset      # F-8
go test -run 'TestResolve_StandardCases/removed_Go_file' ./pkg/changeset      # F-9

Disclosed amendments to the spec (folded back into 0005-...md @ v1.1)

Three amendments resolved in plan-mode review with the maintainer; all three folded into the active spec in this PR (commits 9e92a95 + e17a730) so the spec stays the source-of-truth at merge.

  1. F-2 signature drift (Q1). Spec said func Resolve(changedFiles []string, pkgs []golist.Package, moduleRoot string) []string (positional). User overrode to functional option: func Resolve(changedFiles []string, pkgs []golist.Package, opts ...Option) []string with WithModuleRoot(string) Option. The Option type is the only knob in v0.x; future-proofs for WithIgnorePattern etc. and lets tests opt out of the io fallback (Q2) by passing WithModuleRoot explicitly.
  2. os.Getwd fallback (Q2). Spec leaned "skip relative paths when moduleRoot is empty." User chose: when WithModuleRoot is not supplied, fall back to os.Getwd at call time. The single io edge is hooked through an internal function-variable seam (getCwd = os.Getwd), structurally identical to M2's runGoList seam. Tests in seam_test.go (package changeset internal) replace getCwd to drive the success/error branches; production tests pass WithModuleRoot explicitly to bypass the io.
  3. doc.go content updated. M1 stub said "All operations are pure — no io." That is no longer accurate after Q2. Updated to describe the io edge plus the seam-based testability story. F-1's verify (the // Package changeset comment line) is preserved.

The implementation already matches the amended spec; the spec commit just brings the spec text in line with what shipped.

Abridged ledger (full version in docs/dev/0010-implementation-retrospective-m4-changeset.md)

ID Criterion Status Evidence
F-1 pkg/changeset/doc.go exists with package comment done M1 stub structural property preserved (// Package changeset line); content body amended (disclosed).
F-2 Resolve signature matches spec done func Resolve(changedFiles []string, pkgs []golist.Package, opts ...Option) []string — exact match to amended spec v1.1.
F-3 All TestResolve_StandardCases rows pass done 16 named subtests pass; each also asserts sort.StringsAreSorted(got) inline.
F-4 Hand-traceable 4-package case passes done Seven sub-cases assert exact mappings; pkga/a.go→ex/pkga, _test.go→ex/pkga, xtest→ex/pkgb, multi-pkg→sorted, subdir→nil, non-go→nil, empty→nil.
F-5 Path-normalisation cases pass done Five OS-portable cases pass: redundant separators, leading ./, interior ./, .. traversal, absolute-with-extra-separators.
F-6 _test.go files map to the package, not to _test done --- PASS: TestResolve_StandardCases/_test.go_in_pkga.
F-7 Non-Go files silently skipped done --- PASS: TestResolve_StandardCases/mixed_go_and_non_go.
F-8 Files outside any package skipped, no error done --- PASS: TestResolve_StandardCases/Go_file_outside_any_package.
F-9 Removed files mapped via parent-dir match done --- PASS: TestResolve_StandardCases/removed_Go_file_in_pkga — purely lexical lookup, no os.Stat.
F-10 Sorted, deduplicated output, deterministic done -count=10 clean; sort assertion inline in every subtest.
F-11 Per-package coverage gate at 100% done ok: github.com/geomyidia/cascade/pkg/changeset coverage 100% >= 100%. All four cascade packages now at 100% (golist, depgraph, changeset, internal/project).
F-12 No non-stdlib imports beyond pkg/golist done go list -m all | wc -l = 1. Imports: os, path/filepath, sort, strings from stdlib, plus pkg/golist from own module.
F-13 go doc renders cleanly done Package overview, Resolve, Option, WithModuleRoot — every exported name with DC-01 / DC-02-compliant doc comments.
F-14 Closing report names guides + IDs done Retro's substrate section enumerates: AP-29/30/36/40/44/52, API-42, IM-01, TD-09/17, TE-01..08/15/43, DC-01/02.

Total rows: 14. Done: 14. Deferred: 0. No-op: 0. Iteration count: 1.

Notable findings

  • Function-variable seam pattern generalised cleanly from M2 to M4. getCwd = os.Getwd + withCwdSeam helper is structurally identical to M2's runGoList + withSeam. Pattern is now mature; M5+ packages with io edges should reach for it by default.
  • 100% coverage on first try, no test contortions. The seam tests close the os.Getwd success/error branches; two small dedicated tests close the defensive-skip branches in the dirMap-build and changedFiles loops. Every branch in production code is reachable from natural test inputs.
  • OUT-1 paid for itself again. First make check-all after writing tests caught a gofmt issue (struct-literal trailing-comment alignment) before CI would have. Pattern is battle-tested across M3, refactor, and M4.
  • Trio of pure packages composes without adapter code. golist.Run → depgraph.Build → changeset.Resolve → g.RevDepClosure — each call's output type matches the next call's input shape. M5's CLI wires this directly.

Checklist

  • make check passes locally
  • New/changed exported symbols have godoc comments (Resolve, Option, WithModuleRoot all DC-01/DC-02 compliant)
  • Tests added/updated for behavior changes (table-driven; ~30 subtests across 6 test functions)
  • No public-API breakage (this is a new package; no removal/rename of existing surface)
  • If this PR closes a milestone ledger, each row's planned evidence text matches the criterion text (per M2 retro carry-forward) — pre-PR self-check in retrospective §"Pre-PR self-check (CC)" identified zero softpedals; F-1 and F-2's spec-vs-evidence shape differences are explicitly documented as disclosed amendments

Breaking change?

No for cascade's CLI users. No for library consumers — this PR adds a new exported package; no existing surface is removed or renamed. The package-layout refactor (PR #8) was the breaking-change milestone for the v0.x window.

🤖 Generated with Claude Code

oubiwann and others added 6 commits May 7, 2026 10:02
The bridge between cascade's two algorithmic primitives: golist (M2)
produces []golist.Package; depgraph (M3) walks reverse-edge closures
from a seed set. changeset.Resolve turns "these files changed" (git
diff --name-only output) into "these packages are the seeds" — the
input of g.RevDepClosure.

Surface: one exported function (Resolve), one option type (Option),
one option constructor (WithModuleRoot). Algorithm: build dirMap from
pkg.Dir → ImportPath, walk changedFiles, parent-dir lookup, sorted
dedupe via map[string]struct{}; O(P+F).

Disclosed amendments to the spec (carried in the closing retro):
- F-2 signature: spec said positional moduleRoot string; user override
  during plan-mode review (Q1) chose functional option pattern.
- Q2: empty/unset moduleRoot triggers os.Getwd fallback. Tests pass
  WithModuleRoot explicitly to bypass the io. The single io edge is
  hooked through an internal getCwd function-variable seam, mirroring
  M2's runGoList pattern (pkg/golist/seam_test.go's withSeam idiom is
  reused as withCwdSeam in pkg/changeset/seam_test.go).
- doc.go updated from "All operations are pure — no io" to a
  description that names the os.Getwd fallback and the seam-based
  testability story. F-1's verify (the // Package changeset line) is
  preserved.

Coverage at 100% statement coverage on pkg/changeset, joining
pkg/golist, pkg/depgraph, and internal/project at the same gate. Each
table-driven subtest also asserts sort.StringsAreSorted(got) inline so
non-determinism would surface in the failing subtest, not just as a
-count=10 flake (M3's What-Worked carry-forward).

Spec/plan: docs/dev/0009-implementation-plan-changed-files-to-packages-mapping.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Walks F-1..F-14 row-by-row with grep-verifiable evidence. 14 done,
0 deferred, 0 no-op. Documents three disclosed amendments to the
spec (Q1 functional-option signature, Q2 os.Getwd fallback with
seam, doc.go content update) so the spec's next ODM promotion
picks them up. Substrate section enumerates pattern IDs cited
during implementation (AP-29/30/36/40/44/52, API-42, IM-01,
TD-09/17, TE-01..08/15/43, DC-01/02).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the three disclosed amendments from the M4 retrospective
(0010) into the active spec at docs/design/05-active/0005-... so the
spec stays the source-of-truth for downstream consumers and CDC
review:

- Public API surface: Resolve signature reflects the functional-
  option pattern (Q1 user override). Adds Option type definition and
  WithModuleRoot constructor as a sibling subsection. Updates Resolve
  godoc to describe opts, the os.Getwd fallback (Q2), and the
  IgnoredGoFiles mapping rule (Q5 confirmed).
- Decided: bullets updated. moduleRoot via WithModuleRoot, not
  positional; the seam pattern (getCwd) for the io edge is named.
- F-2 ledger row's verify command updated to grep for the
  functional-option signature.
- Open questions section retitled "Open questions (resolved)" with
  each Q1-Q6 marked **Resolved:** plus the resolution chosen (or
  spec lean confirmed). Q1 and Q2 record the user override
  explicitly.
- Risks & mitigations: adds an entry for the single io edge from
  os.Getwd fallback, naming the seam pattern as the mitigation and
  M2's runGoList as the precedent.
- Frontmatter version 1.0 → 1.1.

The implementation already matches this amended spec; this commit
just brings the spec text in line with what shipped. Nothing in the
ledger's done-ness shifts: F-1..F-14 closed in 75617a2 and f78dcb9
remain done.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous spec-amendment commit (9e92a95) bumped the frontmatter
version from 1.0 to 1.1 but didn't document what changed in-doc.
Adding a Version History table at the bottom of the spec capturing:
- 1.0 baseline (positional Resolve, no exported types, no io)
- 1.1 amendments (Option + WithModuleRoot, os.Getwd fallback with
  getCwd seam, IgnoredGoFiles mapping rule, F-2 verify command,
  resolved open questions, risks-section addition)

Per spec-keeping discipline (assets/ai/AI-ENGINEERING-METHODOLOGY.md
§"Anti-degradation practices"): a version bump without a documented
change is silent drift; the table makes the diff visible to future
readers and to CDC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CDC pass run against m4/changeset-mapping head 75617a2 + retro
fix-up f78dcb9. Replaces the "Pending..." placeholder in the
§"CDC review notes" section with the full independent-verification
report:

- Direct re-reads on F-1 (doc.go), F-2 (Resolve signature),
  F-9 (removed-file lookup is purely lexical), F-12 (imports list
  + zero go.mod requires), F-14 (substrate enumeration).
- CC-attested-via-CI rows (F-3..F-8, F-10, F-11, F-13) accepted on
  the strength of CI green + local-run evidence in the row walk.
- Specifically verified the options pattern (config struct,
  WithModuleRoot setting both moduleRoot + moduleRootSet, the
  meaningful "not set vs explicitly empty" distinction the
  flag enables) and the seam-based pure-testing path (every test
  call site in changeset_test.go passes WithModuleRoot;
  TestResolve_DefaultUsesGetCwd + TestResolve_GetCwdErrorTolerated
  exercise both getCwd branches via withCwdSeam).

CDC findings: zero softpedals, zero silent drops, row count
14-declared / 14-closed. Closure recommendation: M4 is mergeable.

Three engineering observations carried forward into the retros'
What-Worked corpus: (a) the moduleRootSet flag as an API-design
pattern (option zero value semantically meaningful); (b) the
plan-mode AskUserQuestion round-trip surfacing design-affecting
decisions before code is written; (c) the external+internal
two-file test convention now established across pkg/golist
and pkg/changeset.

Forward-looking note: the active M4 spec's F-2 verify text
references the original positional moduleRoot signature; the
in-PR amendment commit (9e92a95) already updates this to the
functional-option form, so the spec is in-sync at v1.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@oubiwann oubiwann merged commit 9b010e7 into main May 7, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant