diff --git a/README.md b/README.md index 2ffbe1d..b4fc2b0 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ ReleaseKit is an opinionated Go library and executable for a shared release process. The Go module, root `releasekit` facade, `buildinfo` runtime package, -local candidate check, source-tag publication API, and `version` and `check` -commands are implemented. A publication command, builds, delivery, and release -automation remain to be implemented. Their selected -behavior and sequence are described in the -[product roadmap](docs/roadmap/releasekit.md) and -[implementation plan](docs/roadmap/implementation.md). +local candidate check, source-tag publication API, and `version`, `check`, and +`publish` commands are implemented. ReleaseKit's source-only maintainer +procedure is documented. Executable builds and delivery remain future work. +The [product roadmap](docs/roadmap/releasekit.md) and +[implementation plan](docs/roadmap/implementation.md) describe their selected +behavior and sequence. Run the offline Go test suite from the repository root with: @@ -28,25 +28,37 @@ configured checks (or the default offline Go test command): go run ./cmd/releasekit check v1.2.3 ``` +Publish the source tag from a committed candidate with: + +```sh +./scripts/release v1.2.3 +``` + +See the [ReleaseKit maintainer procedure](docs/release.md) for preparation and +failure recovery. + ## Documentation - [ReleaseKit proposal](docs/roadmap/releasekit.md): proposed publication, builds, version injection, and delivery, with the initial implementation plan. - [Contributor guide](docs/development.md): orientation, validation, and selective policy reading. -- [CLI contract](docs/cli.md): the `version` and local `check` commands. +- [CLI contract](docs/cli.md): the `version`, local `check`, and source + `publish` commands. - [Configuration](docs/config.md): profile fields and child-command settings. - [Runtime version guidance](docs/consumers/runtime-version.md): how applications read their embedded version through `buildinfo`. - [Candidate check guidance](docs/consumers/check.md): local candidate validation through the Go API and CLI. +- [Maintainer release procedure](docs/release.md): ReleaseKit's source-only + publication workflow and recovery. - [Internal package inventory](docs/internal/overview.md): implemented internal components and their responsibilities. - [Architecture policy](docs/policy/architecture.md): responsibility and dependency boundaries for development. - [ADR 0001](docs/adr/0001-public-facade.md): the accepted public facade decision. -- [Policy adoption roadmap](docs/roadmap/policy.md): completed adoption and pending - release bootstrap work. +- [Policy adoption roadmap](docs/roadmap/policy.md): completed adoption and the + current source-release path. ReleaseKit is licensed under the [LGPL v3.0 or later](COPYRIGHT). diff --git a/cmd/releasekit/internal/cli/config.go b/cmd/releasekit/internal/cli/config.go index 4321766..781e035 100644 --- a/cmd/releasekit/internal/cli/config.go +++ b/cmd/releasekit/internal/cli/config.go @@ -16,13 +16,14 @@ import ( "gitea.maximumdirect.net/eric/ReleaseKit/internal/process" ) -const Usage = "usage: releasekit version\n releasekit check [--repo DIR] [--config FILE] VERSION" +const Usage = "usage: releasekit version\n releasekit check [--repo DIR] [--config FILE] VERSION\n releasekit publish [--repo DIR] [--config FILE] VERSION" type Command string const ( VersionCommand Command = "version" CheckAction Command = "check" + PublishAction Command = "publish" ) // Invocation contains parsed command settings owned by the CLI adapter. @@ -44,8 +45,9 @@ func Parse(args []string) (Invocation, error) { return Invocation{}, errors.New("version takes no arguments") } return Invocation{Command: VersionCommand}, nil - case string(CheckAction): - flags := flag.NewFlagSet("check", flag.ContinueOnError) + case string(CheckAction), string(PublishAction): + action := Command(args[0]) + flags := flag.NewFlagSet(string(action), flag.ContinueOnError) flags.SetOutput(io.Discard) repository := flags.String("repo", ".", "repository directory") configPath := flags.String("config", "", "profile path") @@ -53,9 +55,9 @@ func Parse(args []string) (Invocation, error) { return Invocation{}, err } if flags.NArg() != 1 { - return Invocation{}, errors.New("check requires one version argument") + return Invocation{}, fmt.Errorf("%s requires one version argument", action) } - return Invocation{Command: CheckAction, Repository: *repository, ConfigPath: *configPath, Version: flags.Arg(0)}, nil + return Invocation{Command: action, Repository: *repository, ConfigPath: *configPath, Version: flags.Arg(0)}, nil default: return Invocation{}, fmt.Errorf("unknown command %q", args[0]) } diff --git a/cmd/releasekit/internal/cli/config_test.go b/cmd/releasekit/internal/cli/config_test.go index 0921f5b..2ef8b73 100644 --- a/cmd/releasekit/internal/cli/config_test.go +++ b/cmd/releasekit/internal/cli/config_test.go @@ -26,14 +26,21 @@ func TestParse(t *testing.T) { if check.Command != CheckAction || check.Repository != "repository with spaces" || check.ConfigPath != "profiles/release config.json" || check.Version != "v1.2.3" { t.Fatalf("Parse(check) = %#v", check) } + publish, err := Parse([]string{"publish", "--repo", "candidate", "--config", "releasekit.json", "v1.2.4"}) + if err != nil { + t.Fatal(err) + } + if publish.Command != PublishAction || publish.Repository != "candidate" || publish.ConfigPath != "releasekit.json" || publish.Version != "v1.2.4" { + t.Fatalf("Parse(publish) = %#v", publish) + } if _, err := Parse([]string{"check", "v1.2.3", "--repo", "repository"}); err == nil { t.Fatal("Parse accepted a flag after the version") } if _, err := Parse(nil); err == nil || !strings.Contains(err.Error(), "command is required") { t.Fatalf("Parse(nil) error = %v, want missing-command error", err) } - if _, err := Parse([]string{"publish"}); err == nil || !strings.Contains(err.Error(), "unknown command") { - t.Fatalf("Parse(publish) error = %v, want unknown-command error", err) + if _, err := Parse([]string{"unknown"}); err == nil || !strings.Contains(err.Error(), "unknown command") { + t.Fatalf("Parse(unknown) error = %v, want unknown-command error", err) } } diff --git a/cmd/releasekit/main.go b/cmd/releasekit/main.go index 2772421..3cd6d5b 100644 --- a/cmd/releasekit/main.go +++ b/cmd/releasekit/main.go @@ -32,7 +32,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int { case cli.VersionCommand: fmt.Fprintf(stdout, "releasekit %s\n", buildinfo.Version()) return 0 - case cli.CheckAction: + case cli.CheckAction, cli.PublishAction: settings, err := cli.LoadSettings(invocation.Repository, invocation.ConfigPath) if err != nil { fmt.Fprintf(stderr, "load profile: %v\n", err) @@ -54,6 +54,22 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int { }, stderr, stderr) } } + if invocation.Command == cli.PublishAction { + fmt.Fprintf(stderr, "validating and publishing %s\n", invocation.Version) + result, err := releasekit.Publish(ctx, request) + if err != nil { + fmt.Fprintf(stderr, "publication state=%s phase=%s version=%s commit=%s tag-object=%s: %v\n", result.State, result.Phase, result.Version, result.CandidateCommit, result.TagObject, err) + if errors.Is(err, releasekit.ErrInvalidVersion) { + return 2 + } + if result.State == releasekit.PublicationUnknown { + return 3 + } + return 1 + } + fmt.Fprintf(stdout, "published %s at %s (tag object %s)\n", result.Version, result.CandidateCommit, result.TagObject) + return 0 + } candidate, err := releasekit.Check(ctx, request) if err != nil { fmt.Fprintf(stderr, "check failed: %v\n", err) diff --git a/cmd/releasekit/main_test.go b/cmd/releasekit/main_test.go index 338baf1..6de6cbd 100644 --- a/cmd/releasekit/main_test.go +++ b/cmd/releasekit/main_test.go @@ -30,13 +30,13 @@ func TestVersionCommand(t *testing.T) { func TestUnknownCommand(t *testing.T) { var stdout, stderr bytes.Buffer - if status := run(context.Background(), []string{"publish"}, &stdout, &stderr); status != 2 { + if status := run(context.Background(), []string{"unknown"}, &stdout, &stderr); status != 2 { t.Fatalf("run() status = %d, want 2", status) } if stdout.Len() != 0 { t.Fatalf("unknown command wrote to stdout: %q", stdout.String()) } - if want := "unknown command \"publish\"\n" + cli.Usage + "\n"; stderr.String() != want { + if want := "unknown command \"unknown\"\n" + cli.Usage + "\n"; stderr.String() != want { t.Fatalf("usage output = %q, want %q", stderr.String(), want) } } @@ -82,6 +82,52 @@ func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) { } } +func TestPublishCommandPublishesConfiguredCandidate(t *testing.T) { + repository := newCLIRepository(t) + settings := cli.Settings{ + Product: "Example Product", + Remote: "origin", + Checks: []cli.CheckCommand{{ + Args: []string{"go", "test", "-mod=readonly", "-count=1", "./..."}, + Env: map[string]string{ + "GOTOOLCHAIN": "local", + "GOPROXY": "off", + "GOSUMDB": "off", + }, + }}, + } + contents, err := json.Marshal(settings) + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(repository, "releasekit.json"), contents, 0o600); err != nil { + t.Fatal(err) + } + cliGitRun(t, repository, "add", "releasekit.json") + cliGitRun(t, repository, "commit", "-m", "configure source release") + remote := filepath.Join(t.TempDir(), "origin.git") + cliGitRun(t, repository, "init", "--bare", remote) + cliGitRun(t, repository, "remote", "add", "origin", remote) + cliGitRun(t, repository, "push", "--set-upstream", "origin", "main") + + var stdout, stderr bytes.Buffer + args := []string{"publish", "--repo", repository, "v1.2.3"} + if status := run(context.Background(), args, &stdout, &stderr); status != 0 { + t.Fatalf("run() status = %d; stdout=%q stderr=%q", status, stdout.String(), stderr.String()) + } + commit := cliGitOutput(t, repository, "rev-parse", "HEAD") + tagObject := cliGitOutput(t, repository, "rev-parse", "refs/tags/v1.2.3") + if want := fmt.Sprintf("published v1.2.3 at %s (tag object %s)\n", commit, tagObject); stdout.String() != want { + t.Fatalf("stdout = %q, want %q", stdout.String(), want) + } + if !bytes.Contains(stderr.Bytes(), []byte("validating and publishing v1.2.3")) || !bytes.Contains(stderr.Bytes(), []byte("example.com/cli-candidate")) { + t.Fatalf("stderr does not show publication progress and validation: %q", stderr.String()) + } + if got := cliGitOutput(t, remote, "rev-parse", "refs/tags/v1.2.3"); got != tagObject { + t.Fatalf("remote tag object = %s, want %s", got, tagObject) + } +} + func TestCandidateCheckHelper(t *testing.T) { if os.Getenv("RELEASEKIT_CHECK_HELPER") != "1" { return diff --git a/docs/cli.md b/docs/cli.md index 016bcd1..487b5ae 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -1,6 +1,6 @@ # Command line -ReleaseKit currently provides two commands. +ReleaseKit currently provides three commands. Print the executable version: @@ -24,7 +24,28 @@ captured local commit and version. It checks the committed release note, local Git state, and configured validation, but does not inspect remote availability or publish a tag. -Both commands write errors and diagnostic output to standard error. `check` -returns status 0 after local validation, 1 after a candidate validation failure, -and 2 for usage, profile, or invalid-version errors. Unsupported argument -sequences print usage to standard error. +`check` writes errors and diagnostics to standard error. It returns status 0 +after local validation, 1 after a candidate validation failure, and 2 for usage, +profile, or invalid-version errors. Unsupported argument sequences print usage +to standard error. + +Publish a source tag: + +```sh +go run ./cmd/releasekit publish [--repo DIR] [--config FILE] VERSION +``` + +`publish` reads the same product, remote, and validation settings as `check`. +It validates the candidate once, checks the authoritative remote, creates an +annotated local tag, and pushes only its exact tag object. It prints the +confirmed version, candidate commit, and tag object on success. Validation +progress and failed publication state are written to standard error. A signal +cancels the active operation; if it arrives during a push, the library's +bounded reconciliation determines whether the exact tag was accepted. + +The command returns status 0 for confirmed publication, 1 for a known +publication failure or conflict, 2 for usage, profile, or invalid-version +errors, and 3 when the push outcome remains unknown. Unknown status must be +resolved against the authoritative remote before retrying. The +[maintainer procedure](release.md) selects the repository profile and gives +the source-release workflow. diff --git a/docs/config.md b/docs/config.md index 0b2df91..aaaf97a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -20,10 +20,12 @@ The overlay does not modify the ReleaseKit process environment. Unknown fields are rejected so a setting cannot be silently ignored. Build and delivery fields will be added when those operations are implemented. The `check` -command uses `product`, `remote`, and `checks`. If `checks` is omitted or empty, -it runs `go test -mod=readonly -count=1 ./...` with Go module and toolchain -network access disabled. If checks are configured, they replace that default and -run in order. +and `publish` commands use `product`, `remote`, and `checks`. If `checks` is +omitted or empty, it runs `go test -mod=readonly -count=1 ./...` with Go module +and toolchain network access disabled. If checks are configured, they replace +that default and run in order. +ReleaseKit's source-only publication profile is +[releasekit.json](../releasekit.json). See the [profile example](../examples/releasekit.json). The [CLI contract](cli.md) lists the currently available command. diff --git a/docs/development.md b/docs/development.md index 898424f..ae13597 100644 --- a/docs/development.md +++ b/docs/development.md @@ -65,7 +65,9 @@ git diff --cached --check -- '*.md' COPYRIGHT Verbatim license texts retain their supplied formatting. When changing those texts, compare their bytes with the selected upstream source. -There is no publication command yet. Add working development commands here as -implementation lands. Add `docs/release.md` with the first working publication -command; the [policy adoption roadmap](roadmap/policy.md#4-define-releasekits-own-release-path) -records the remaining bootstrap work. +Run the source publication command from the candidate repository root with +`./scripts/release VERSION`. It uses the local Go toolchain and committed +`releasekit.json` profile; prepare module dependencies before offline candidate +validation. The [maintainer release procedure](release.md) owns preparation, +publication, and recovery. Executable artifacts and delivery remain outside +ReleaseKit's initial source-only release. diff --git a/docs/policy/release.md b/docs/policy/release.md index 514b515..fe49816 100644 --- a/docs/policy/release.md +++ b/docs/policy/release.md @@ -18,10 +18,8 @@ policy and link to it rather than introduce additional release requirements. This policy governs releases of ReleaseKit itself. Consumer release capabilities are designed in the [product roadmap](../roadmap/releasekit.md) and acquire exact -public contracts when implemented. ReleaseKit currently has no publication -command. Create the maintainer runbook alongside the first working command; -the [policy adoption roadmap](../roadmap/policy.md#4-define-releasekits-own-release-path) -owns that pending work. +public contracts when implemented. ReleaseKit's source publication command and +maintainer runbook are implemented; no ReleaseKit release has been published. ## Release Model And Completion @@ -33,8 +31,8 @@ The procedure must name the authoritative publication remote. A successful push of the intended annotated tag, with its source commit already published on authoritative `main`, completes the release. The remote branch must identify the validated candidate before the command publishes only the exact -intended tag object. The runbook will identify the authoritative endpoint and -command when publication is implemented. +intended tag object. The [maintainer runbook](../release.md) identifies the +authoritative endpoint and command. Release commands must finish at successful publication. They must not wait for or poll CI, require successful CI status, inspect downloadable artifacts, or diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000..07ff6ce --- /dev/null +++ b/docs/release.md @@ -0,0 +1,93 @@ +# ReleaseKit release procedure + +ReleaseKit's initial deliverable is source only. The authoritative repository +is `origin` at `https://gitea.maximumdirect.net/eric/ReleaseKit.git`, and the +release branch is `main`. A release completes when the intended annotated tag +is successfully pushed, or when one exact-object recovery query confirms that +the push succeeded. CI, hosted artifacts, and consumer installation are not +completion checks. + +## Prepare a candidate + +Use a local Go 1.25 toolchain and a clean clone. Start on `main`, choose an +unused stable `vMAJOR.MINOR.PATCH` version, and add +`docs/releases/.md` with a first heading containing `ReleaseKit` and +the exact version. Summarize the change and any compatibility or upgrade impact. +End the note with the three license links at the immutable source tag: + +```markdown +[COPYRIGHT](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/vX.Y.Z/COPYRIGHT) | +[LICENSE](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/vX.Y.Z/LICENSE) | +[ATTRIBUTION](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/vX.Y.Z/docs/oss/attribution.md) +``` + +Replace every `vX.Y.Z` with the selected version. These are links to the same +release note's immutable source tree and also work when the note is used as a +hosted release body. + +Commit the note and all candidate changes on `main`, then publish that commit +to `origin/main`: + +```sh +git push origin main +``` + +ReleaseKit checks that the worktree is clean, the candidate is on `main`, the +authoritative branch identifies that commit, and the version is unused locally +and on `origin`. + +## Prepare dependencies, then publish + +Dependency preparation is separate from the offline candidate validation. Run +it from the captured candidate source before the release command: + +```sh +GOTOOLCHAIN=local go mod download +``` + +Then run the single publication command from the repository root: + +```sh +./scripts/release vX.Y.Z +``` + +The committed `releasekit.json` selects the offline default test suite: +`go test -mod=readonly -count=1 ./...`, with toolchain and module network +access disabled for validation. The command validates once, checks the +authoritative remote state, creates a local annotated tag from the captured +candidate and checked-in note, rechecks the tag and remote guards, and pushes +only that tag object. It prints the published version, commit, and tag object +on success. Exit status 0 means publication is confirmed; status 1 means a +known failure or conflict; status 2 means invalid input or profile; status 3 +means the push outcome remains unknown and requires resolution. + +The CLI runs from the source tree being published, so this command also works +for ReleaseKit's first release before any released ReleaseKit executable +exists. It uses the local Go toolchain. Preparation must finish before the +offline validation begins. + +## Resolve failures + +A failed push triggers one exact authoritative tag query. An exact tag-object +match confirms publication even when Git reported an error. A proven absent tag +is `not_published`; a different object is a conflict; an unreadable query is +`unknown`. The command retains its local tag and reports the state, phase, +candidate commit, and tag object when available. Do not retry an unknown result +until you resolve the authoritative ref: + +```sh +git ls-remote --refs origin "refs/tags/vX.Y.Z" +``` + +Compare the returned object ID with the tag object reported by the command. +An exact match confirms publication. If the ref is absent, the version was not +published; only then may you deliberately remove the unpublished local tag +with `git tag -d vX.Y.Z` before correcting the candidate or retrying. Do not +move, overwrite, or delete a tag that exists remotely. If a different object +uses the version, choose a new version after resolving the conflict. + +If validation or a guard fails before the push, the local tag has not been +published. Correct and commit the candidate, publish `main`, and run the +command again. ReleaseKit does not publish executable artifacts or wait for +asynchronous delivery; any future deliverables require a separate policy and +procedure update. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 136e107..af07427 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -649,7 +649,7 @@ local tag before push prevent publication. ## Stage 9: Expose publication and document ReleaseKit bootstrap -Status: Pending. Depends on Stage 8. +Status: Complete. Depends on Stage 8. 1. Wire the complete public publisher into `releasekit publish`, with selected progress output, final result, exit classification, and signal cancellation. @@ -672,6 +672,15 @@ committed profile and returns the expected exit/output behavior. Runbook command are exercised using a temporary repository configured like ReleaseKit. This stage prepares self-publication and does not publish a real ReleaseKit release. +Evidence: `publish` runs the public publisher with the CLI signal context, +selected progress output, final commit and tag-object output, and separate exit +status for unresolved outcomes. A temporary committed profile runs the offline +test command and publishes to a local bare remote. ReleaseKit's root profile, +entry script, CLI and configuration guides, development guide, and maintainer +runbook describe source-only publication and exact-object recovery. The +runbook's dependency preparation is separate from offline validation. No real +ReleaseKit release was published. + ## Stage 10: Build selected targets with shared version injection Status: Pending. Depends on Stage 9. diff --git a/docs/roadmap/policy.md b/docs/roadmap/policy.md index c1bf4d1..ecadecc 100644 --- a/docs/roadmap/policy.md +++ b/docs/roadmap/policy.md @@ -1,6 +1,8 @@ # Adopt the policy framework -Status: Policy adoption complete. Release publication implementation is pending. +Status: Policy adoption complete. Source publication and its maintainer runbook +are implemented; no ReleaseKit release has been published. Executable delivery +remains pending. Date: 2026-10-05 @@ -130,20 +132,21 @@ policy has separate defaults for applications and libraries. successful-tag-push completion rules. - [x] Select source only as ReleaseKit's initial deliverable in the release policy. Supporting downstream cross-builds does not declare ReleaseKit binaries. -- [ ] Add `docs/release.md` alongside the first working publication command, +- [x] Add `docs/release.md` alongside the first working publication command, documenting real prerequisites, authoritative remote, branch, validation, publication, and failure recovery. - [x] Account for first-release bootstrap: the initial publication command must run from the captured candidate source with dependencies prepared separately. The first release uses that source execution path before a released ReleaseKit - tool exists. Document and verify the actual command when it is implemented. + tool exists. The maintainer procedure documents the command and the temporary + repository integration test exercises the same CLI workflow. - [ ] Add executable delivery declarations and target checks when corresponding deliverables exist. Keep source completion and asynchronous delivery status separate. This task is deferred unless executable deliverables are selected. -The publication command, its automated guards and offline candidate validation, -and the executable maintainer runbook remain product implementation work. This -adoption does not claim an available release command or perform publication. +The publication command, its automated guards, offline candidate validation, +and executable maintainer runbook are implemented. This adoption does not claim +that a ReleaseKit version has been published. ## Validation and completion @@ -165,14 +168,14 @@ approval, and CI polling are not additional adoption or release gates. ## Adoption result The seven policies, root agent orientation, developer reading guide, documentation -ownership, project grant, attribution, and license texts are in place. The product -proposal lives at `docs/roadmap/releasekit.md`; `ideas.md` remains a navigation -pointer. Future capabilities retain their proposed status, and facade -implementation remains pending in the product roadmap. +ownership, project grant, attribution, license texts, source publication command, +and maintainer runbook are in place. The product proposal lives at +`docs/roadmap/releasekit.md`; `ideas.md` remains a navigation pointer. Executable +delivery remains pending, and no ReleaseKit release has been published. Verification covers local file and fragment links, per-policy source revision and base version, removed template adoption sections, attribution scope and pinned notice destinations, verbatim license bytes, and prose whitespace. The preserved ADR decision and relocated proposal are checked against their pre-adoption source. -Release automation and its runbook remain pending under stage 4 as permitted by -this roadmap's completion criteria. +ReleaseKit's source publication command and runbook are implemented; executable +delivery declarations remain pending under stage 4. diff --git a/docs/roadmap/releasekit.md b/docs/roadmap/releasekit.md index 8e0ae6f..74d7cd7 100644 --- a/docs/roadmap/releasekit.md +++ b/docs/roadmap/releasekit.md @@ -1,9 +1,11 @@ # ReleaseKit: shared release process This is a proposal based on a review of PromptKit, ConfigKit, Audita, Notarius, -Narratio, and Scriptorium on 2026-09-29. Product capabilities remain proposed. -The public facade architecture is accepted in -[ADR 0001](../adr/0001-public-facade.md), with implementation pending. +Narratio, and Scriptorium on 2026-09-29. The implementation plan records the +current implemented boundary; builds, delivery, and broader integrations remain +proposed. The public facade architecture is accepted in +[ADR 0001](../adr/0001-public-facade.md) and is implemented for runtime version, +candidate validation, and source publication. The [implementation plan](implementation.md) owns ordered stages, execution instructions, acceptance checks, and progress toward this target state. @@ -50,12 +52,11 @@ cleanup. ## Architecture [ADR 0001](../adr/0001-public-facade.md) owns the accepted facade decision. -The [architecture policy](../policy/architecture.md) owns the adopted responsibility -and dependency rules, including the runtime version boundary. The facades remain -unimplemented. -The root `releasekit` package will provide the public facade for publication, -builds, and delivery. A separate public `buildinfo` package will provide the -lightweight facade for runtime version information. +The [architecture policy](../policy/architecture.md) owns the adopted +responsibility and dependency rules, including the runtime version boundary. +The root `releasekit` package provides the public facade for candidate +validation and source publication; builds and delivery remain planned. The +separate public `buildinfo` package provides lightweight runtime version access. Each facade owns its public named types and documented contracts. Public operations, inputs, outcomes, errors, and callbacks express deliberately diff --git a/releasekit.json b/releasekit.json new file mode 100644 index 0000000..83defcd --- /dev/null +++ b/releasekit.json @@ -0,0 +1,14 @@ +{ + "product": "ReleaseKit", + "remote": "origin", + "checks": [ + { + "args": ["go", "test", "-mod=readonly", "-count=1", "./..."], + "env": { + "GOTOOLCHAIN": "local", + "GOPROXY": "off", + "GOSUMDB": "off" + } + } + ] +} diff --git a/scripts/release b/scripts/release new file mode 100755 index 0000000..6d469b2 --- /dev/null +++ b/scripts/release @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu + +if [ "$#" -ne 1 ]; then + printf '%s\n' 'usage: ./scripts/release VERSION' >&2 + exit 2 +fi + +GOTOOLCHAIN=local exec go run ./cmd/releasekit publish "$1"