Documentation
¶
Overview ¶
Package release owns the end-of-cycle release mechanics for the ATDD pipeline driver: regex-remove `@Disabled`-style markers from in-scope test files, commit, and close the GitHub issue. It replaces the legacy `atdd-release` agent.
The package is deliberately mechanical and small. It exposes three primitives:
- RemoveDisabledMarkers walks a set of roots, applies one regex per language, and rewrites or deletes lines that match. Each language has a built-in default Pattern; callers can override the full set via RemoveOptions.Patterns.
- Commit shells out to `git add -A` + `git commit -m <msg>`. It REQUIRES a non-nil Confirmer — there is no way to silently commit. Callers that want non-interactive use must pass a Confirmer that auto-returns true; the explicit handshake makes "skip the gate" visible at the call site rather than buried in a flag.
- CloseIssue shells out to `gh issue close <N>`.
Scope explicitly excluded in this package: Cobra wiring, auto-detection of in-scope test files from issue commits, and any driver-loop integration. Callers pass Roots explicitly.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrCommitDeclined = errors.New("release: commit declined by user")
ErrCommitDeclined is returned by Commit when the Confirmer returns false.
var ErrConfirmerRequired = errors.New("release: Commit requires a Confirmer (no silent commits)")
ErrConfirmerRequired is returned by Commit when CommitOptions.Confirm is nil. This guards the "ask before commit" policy at the type-system level.
Functions ¶
func CloseIssue ¶
CloseIssue shells out to `gh issue close <N>`. The Confirmer policy is applied at the Commit boundary, not here — by convention closing an issue happens immediately after an already-approved final commit.
func Commit ¶
func Commit(ctx context.Context, opts CommitOptions) error
Commit stages all working-tree changes (`git add -A`) and creates a commit with the given message — but only after the user explicitly approves via the supplied Confirmer.
The "ask before every commit" gate is firm user policy across the academy; nil Confirm returns ErrConfirmerRequired so a forgetful caller can't accidentally bypass it.
Types ¶
type Changes ¶
type Changes struct {
Files []FileChange
}
Changes is the return type of RemoveDisabledMarkers.
func RemoveDisabledMarkers ¶
func RemoveDisabledMarkers(ctx context.Context, opts RemoveOptions) (Changes, error)
RemoveDisabledMarkers walks each root, matches files against each pattern's glob, and removes (or rewrites) marker lines per the pattern. Returns a Changes slice describing every file that would change; if opts.DryRun is true, no file is modified.
Ordering: roots are walked in the order given; within each root files are visited in lexical order (the default of filepath.WalkDir). Patterns are applied in the order given; within a single file every pattern that matches the glob runs, in order, so a file matching multiple patterns (rare) accumulates the union of edits.
type CommitOptions ¶
type CommitOptions struct {
Message string
Confirm Confirmer // mandatory; nil → error.
GitRunner GitRunner // optional injection point for tests; nil → real exec.
Stdout io.Writer // optional; nil → os.Stdout. Diff summary goes here.
}
CommitOptions controls a Commit call.
type Confirmer ¶
Confirmer asks the user to approve a destructive action. Implementations return (true, nil) to proceed, (false, nil) to abort cleanly, or (false, err) on I/O failure. A nil Confirmer is rejected by Commit; the "ask before every commit" gate is firm user policy and the only way to opt out is to pass a Confirmer that auto-returns true.
func InteractiveConfirmer ¶
InteractiveConfirmer returns a Confirmer that writes the prompt to stdout and reads one line from stdin. "y", "yes", "Y", "YES" → true; everything else → false. EOF on stdin is treated as a decline (false, nil), matching the "silence = no" rule in shared-commit-confirmation.md.
type FileChange ¶
type FileChange struct {
Path string
LinesRemoved []int // 1-indexed line numbers fully removed
LinesEdited []int // 1-indexed line numbers rewritten in place (e.g. .NET Skip drop)
PatternName string
}
FileChange records the per-file outcome of a RemoveDisabledMarkers pass.
type GitRunner ¶
GitRunner is the test seam for `git` invocations. Tests pass a fake; production callers pass nil and Commit uses the real `exec` runner.
type Pattern ¶
type Pattern struct {
Name string
Glob string
Line *regexp.Regexp
LineRewrite func(string) string
Imports *regexp.Regexp
}
Pattern describes how to find and remove a disabled-marker in one language. A file is matched when its path satisfies Glob; once matched, every line matching Line is removed from the file. If LineRewrite is non-nil, matched lines are passed through it instead of removed (used by the .NET `[Fact(Skip = "…")]` rewrite, which keeps the attribute and drops only the Skip parameter).
Imports is optional: if non-nil and the file has no remaining matches after removal, lines matching Imports are also removed (used to clean up `import org.junit.jupiter.api.Disabled;` once the last `@Disabled` is gone).
func DefaultPatterns ¶
func DefaultPatterns() []Pattern
DefaultPatterns returns the built-in marker patterns for Java, .NET, and TypeScript, anchored to the canonical forms in shop/docs/atdd/code/language-equivalents.md:
- Java: `@Disabled("reason")` standalone line, plus `import org.junit.jupiter.api.Disabled;` cleanup once the last `@Disabled` is gone.
- .NET (C#): `[Fact(Skip = "reason")]` / `[Theory(Skip = "reason")]` — keep the attribute, drop just the `Skip = "…"` parameter (and the resulting empty `()` or stray leading `, `). The standalone form `[Skip("…")]` is removed entirely.
- TypeScript: `test.skip(true, "reason")` standalone line — removed entirely.
Callers that need to test a non-default form should construct their own Pattern slice and pass it via RemoveOptions.Patterns.
type RemoveOptions ¶
type RemoveOptions struct {
// Roots are directories to walk recursively, e.g.
// ["system-test/java", "system-test/dotnet", "system-test/typescript"].
// Each root is walked independently; missing roots are skipped (not an
// error) so callers can pass all three language roots unconditionally.
Roots []string
// DryRun, if true, returns the FileChange list without modifying any
// file on disk.
DryRun bool
// Patterns optionally overrides the built-in default patterns. Nil
// (the common case) uses DefaultPatterns(). Callers passing a custom
// slice replace the defaults entirely — there is no merge.
Patterns []Pattern
}
RemoveOptions controls a RemoveDisabledMarkers run.