Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,22 @@ bash scripts/coverage-check.sh # CI-level: per-package 100% on pure pkgs

## Architecture

Two layers, with a deliberate boundary:
Three layers, with deliberate boundaries:

### Pure core (`golist/`, `depgraph/`, `changeset/`)
### Pure core — public API (`pkg/golist/`, `pkg/depgraph/`, `pkg/changeset/`)

Algorithmic packages. **No io, no syscalls, no `os/exec`** between them. Inputs are typed values; outputs are typed values; tests are table-driven over synthetic graphs. These are exported public APIs — downstreams can build their own affected-package tooling on top of cascade's primitives without re-implementing the graph algorithms.

- `golist` — typed `Package` values parsed from `go list -deps -json` output, plus a `Run()` function that owns the `os/exec` boundary. **The only place cascade shells out to `go`.** The parsed types are public so callers fighting `golang.org/x/tools/go/packages.Load` can use them directly.
- `depgraph` — directed import graph + reverse-transitive closure traversal.
- `changeset` — maps changed file paths to import paths.
- `pkg/golist` — typed `Package` values parsed from `go list -deps -json` output, plus a `Run()` function that owns the `os/exec` boundary. **The only place cascade shells out to `go`.** The parsed types are public so callers fighting `golang.org/x/tools/go/packages.Load` can use them directly.
- `pkg/depgraph` — directed import graph + reverse-transitive closure traversal.
- `pkg/changeset` — maps changed file paths to import paths.

These three packages are **gated at 100% statement coverage in CI** by `scripts/coverage-check.sh`. The gate skips packages with no coverage data ("N/A"), so empty pre-implementation stubs don't fire it; once a package gets code, the gate fires immediately.

### Private support — module-internal (`internal/project/`)

Build-metadata package; carries cascade-specific Version / GitCommit / GitBranch / BuildDate values populated via `-ldflags` at link time, with a `runtime/debug.ReadBuildInfo` fallback for `go install`-built binaries. The pattern is generic but the values are cascade-specific; Go's `internal/` rule structurally enforces that downstream consumers cannot import this package. Also gated at 100% coverage by `scripts/coverage-check.sh`.

### I/O shell (`cmd/cascade/`)

Wires the pure core to the outside world: argument parsing, `git diff` invocation, `go list` invocation, output formatting. Coverage is **not** gated per-package (would force untestable contortions); behavior is verified by an end-to-end test that builds the binary and runs it.
Expand All @@ -108,8 +112,8 @@ Standard load order at the start of any non-trivial milestone:
1. **Index, always:** `assets/ai/go/SKILL.md`. Read the "Document Selection Guide" table and the "Critical Rules" section. The chapters are loaded on demand.
2. **Anti-patterns, always:** `assets/ai/go/09-anti-patterns.md`. Walk the AP-NN list relevant to what you're touching. Cite IDs in commit messages and in the closing report (e.g., *"Replaces string-checked error with `errors.Is` (AP-04)"*).
3. **Topic-specific:** load on demand based on what the milestone touches. The full mapping is in SKILL.md; rough map for cascade:
- **I/O shells** (`golist/`, `cmd/cascade/`) — `03-error-handling.md`, `06-concurrency.md`, `02-api-design.md`, `05-interfaces-methods.md`, `07-testing.md`, `11-documentation.md`.
- **Pure-data packages** (`depgraph/`, `changeset/`) — `01-core-idioms.md`, `04-type-design.md`, `07-testing.md`, `11-documentation.md`.
- **I/O shells** (`pkg/golist/`, `cmd/cascade/`) — `03-error-handling.md`, `06-concurrency.md`, `02-api-design.md`, `05-interfaces-methods.md`, `07-testing.md`, `11-documentation.md`.
- **Pure-data packages** (`pkg/depgraph/`, `pkg/changeset/`) — `01-core-idioms.md`, `04-type-design.md`, `07-testing.md`, `11-documentation.md`.

Per-milestone design docs (in `docs/design/`) name the load-bearing chapter list and pattern IDs explicitly. Use those as the authoritative load list for the milestone you're working on.

Expand Down
10 changes: 5 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,25 @@ The general baseline is Go community best practices. The repo also keeps a curat
- **Testing:** `github.com/stretchr/testify/require` for assertions. Table-driven tests for anything with multiple input/output variants.
- **Test file ordering:** test functions first, then helpers and fixtures.
- **Variable declarations:** group related vars in a single `var ( ... )` block at file scope.
- **Coverage:** the pure packages (`golist/`, `depgraph/`, `changeset/`) must hit **100%** statement coverage, enforced per-package by `scripts/coverage-check.sh` in CI. The Makefile's `coverage-check` target enforces a softer 90% overall floor as a quick local sanity check. The CLI shell (`cmd/cascade/`) is not coverage-gated — its behavior is verified by an end-to-end test.
- **Coverage:** the pure packages (`pkg/golist/`, `pkg/depgraph/`, `pkg/changeset/`) plus the build-metadata package (`internal/project/`) must hit **100%** statement coverage, enforced per-package by `scripts/coverage-check.sh` in CI. The Makefile's `coverage-check` target enforces a softer 90% overall floor as a quick local sanity check. The CLI shell (`cmd/cascade/`) is not coverage-gated — its behavior is verified by an end-to-end test.

For coverage discipline specifically — what to test, how to read profiles, how to fix root causes rather than masking symptoms — see [`assets/ai/CLAUDE-CODE-COVERAGE.md`](./assets/ai/CLAUDE-CODE-COVERAGE.md).

## Adding a new public package

If a future change adds a new public package (sibling to `golist/`, `depgraph/`, `changeset/`) with implementation, also add a matching pair of entries to `PACKAGES` and `THRESHOLDS` in `scripts/coverage-check.sh` at the same array index. This is the structural way to commit to a coverage policy explicitly per package.
If a future change adds a new public package (sibling to `pkg/golist/`, `pkg/depgraph/`, `pkg/changeset/`) with implementation, also add a matching pair of entries to `PACKAGES` and `THRESHOLDS` in `scripts/coverage-check.sh` at the same array index. This is the structural way to commit to a coverage policy explicitly per package. New private (cascade-only) packages live under `internal/`.

## Go-version policy

CI's matrix tracks Go's currently-supported major versions (the two newest releases). Expect the floor in `go.mod` to advance with each new Go release.

## Versioning

The canonical version source is **`project/VERSION`** — a single file containing the module version (e.g. `0.1.0`). Bumping cascade is a one-file edit. The repo root has a `./VERSION` symlink pointing at `project/VERSION` for convenience (`cat VERSION` from the repo top still works).
The canonical version source is **`internal/project/VERSION`** — a single file containing the module version (e.g. `0.1.0`). Bumping cascade is a one-file edit. The repo root has a `./VERSION` symlink pointing at `internal/project/VERSION` for convenience (`cat VERSION` from the repo top still works).

Why inside `project/`? Go's `//go:embed` directive cannot reach files outside the package directory tree, so the canonical file lives next to the code that embeds it. The Makefile reads `project/VERSION` and injects the value into `project.Version` via `-ldflags` for full builds. Plain `go install` (no ldflags) gets the same value via the embedded file. Both paths converge on the same source of truth.
Why inside `internal/project/`? Go's `//go:embed` directive cannot reach files outside the package directory tree, so the canonical file lives next to the code that embeds it. The Makefile reads `internal/project/VERSION` and injects the value into `project.Version` via `-ldflags` for full builds. Plain `go install` (no ldflags) gets the same value via the embedded file. Both paths converge on the same source of truth.

**Note for Windows contributors:** git on Windows treats symlinks as text files unless `core.symlinks = true` is set. If `./VERSION` shows up as plain text on your checkout, either set that config or read `project/VERSION` directly — the Makefile already does.
**Note for Windows contributors:** git on Windows treats symlinks as text files unless `core.symlinks = true` is set. If `./VERSION` shows up as plain text on your checkout, either set that config or read `internal/project/VERSION` directly — the Makefile already does.

## Reporting issues

Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ CODE_NAME := "cascade"
MODULE_PATH := github.com/geomyidia/cascade
BIN_DIR := ./bin
MODE := debug
VERSION := $(shell cat project/VERSION 2>/dev/null || echo "unknown")
VERSION := $(shell cat internal/project/VERSION 2>/dev/null || echo "unknown")
GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
GIT_SUMMARY := $(shell git describe --tags --dirty --always 2>/dev/null || echo "untagged")
Expand All @@ -23,7 +23,7 @@ GO_VERSION := $(shell go version 2>/dev/null | awk '{print $$3}' || echo "unknow

# Fully-qualified import path for the project (build-metadata) package
# targeted by ldflags.
VERSION_PKG := $(MODULE_PATH)/project
VERSION_PKG := $(MODULE_PATH)/internal/project

# Coverage
COVERAGE_FILE := coverage.out
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ cascade's pure-core packages are exported public APIs. If you want to build your
import (
"context"

"github.com/geomyidia/cascade/changeset"
"github.com/geomyidia/cascade/depgraph"
"github.com/geomyidia/cascade/golist"
"github.com/geomyidia/cascade/pkg/changeset"
"github.com/geomyidia/cascade/pkg/depgraph"
"github.com/geomyidia/cascade/pkg/golist"
)

ctx := context.Background()
Expand Down
2 changes: 1 addition & 1 deletion VERSION
2 changes: 1 addition & 1 deletion cmd/cascade/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"io"
"os"

"github.com/geomyidia/cascade/project"
"github.com/geomyidia/cascade/internal/project"
)

func main() {
Expand Down
4 changes: 2 additions & 2 deletions cmd/cascade/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"strings"
"testing"

"github.com/geomyidia/cascade/project"
"github.com/geomyidia/cascade/internal/project"
)

// Layer 1: in-process unit tests of run(). These own the package's
Expand Down Expand Up @@ -120,7 +120,7 @@ func TestCascadeBinaryVersion(t *testing.T) {
binPath := filepath.Join(tmp, binName)

const (
versionPkg = "github.com/geomyidia/cascade/project"
versionPkg = "github.com/geomyidia/cascade/internal/project"
injectedVersion = "test-1.0.0"
injectedCommit = "abcdef0"
injectedBranch = "test-branch"
Expand Down
Loading
Loading