diff --git a/.github/actions/setup-cli/action.yaml b/.github/actions/setup-cli/action.yaml
index 6a78ccf8..bc071a3e 100644
--- a/.github/actions/setup-cli/action.yaml
+++ b/.github/actions/setup-cli/action.yaml
@@ -96,8 +96,8 @@ runs:
# Robustly capture a single-line version, sanitized for GHA's output
# file format. Older form `echo "version=$RAW" >> $GITHUB_OUTPUT`
# produced `##[error]Invalid format 'unknown'` against the v1.1.0
- # binary — likely a GHA file-command parser quirk where certain
- # values get mis-interpreted as a format directive. Strip CR/newlines
+ # binary (likely a GHA file-command parser quirk where certain
+ # values get mis-interpreted as a format directive). Strip CR/newlines
# and non-printable characters, fall back to "unknown" on empty/error,
# cap length, and use printf instead of echo to avoid backslash
# interpretation surprises.
diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml
index 1eb0c8af..41941488 100644
--- a/.github/workflows/e2e.yaml
+++ b/.github/workflows/e2e.yaml
@@ -1,9 +1,9 @@
# End-to-end test workflow
# Triggers:
-# push:tags — every release tag (existing)
-# workflow_dispatch — manual run against any ref (existing)
-# merge_group — runs as a merge-queue gate before merging to main
-# schedule — nightly at 07:00 UTC (low-traffic window) against main
+# push:tags every release tag (existing)
+# workflow_dispatch manual run against any ref (existing)
+# merge_group runs as a merge-queue gate before merging to main
+# schedule nightly at 07:00 UTC (low-traffic window) against main
#
# E2E uses act + gitea testcontainers and is too slow + flaky to run per PR.
# Run locally (`go test -v ./e2e/...`) before pushing instead.
@@ -73,7 +73,7 @@ jobs:
# enough headroom. Override per-dispatch as needed.
E2E_TIMEOUT: ${{ github.event.inputs.timeout || '60m' }}
# Cap subtest parallelism. The GitHub runner has 4 cores / ~7.9GB
- # RAM. Each scenario spins up gitea + act + N job containers — at
+ # RAM. Each scenario spins up gitea + act + N job containers; at
# the default GOMAXPROCS=4, four scenarios concurrently exhaust
# memory and the test process is OOM-killed (silent FAIL with no
# per-test output, see #104).
diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml
index 17f9d209..1efa94fc 100644
--- a/.github/workflows/pr.yaml
+++ b/.github/workflows/pr.yaml
@@ -1,6 +1,6 @@
# Pull Request validation workflow
# Runs unit tests + lint on PRs. E2E runs separately (e2e.yaml) on
-# release tags and manual dispatch — too slow + flaky to run per PR.
+# release tags and manual dispatch. Too slow and flaky to run per PR.
#
# This workflow triggers on every PR to main so the PR Gate job always
# reports a status. The expensive jobs (Unit Tests, Lint) are gated on a
diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml
index fae900dd..47b3f4ee 100644
--- a/.github/workflows/promote.yaml
+++ b/.github/workflows/promote.yaml
@@ -255,7 +255,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.release-data.outputs.sem_version }}
run: |
- # Only dispatch on real GitHub — in act/gitea e2e environments
+ # Only dispatch on real GitHub. In act/gitea e2e environments
# GITHUB_SERVER_URL is http://gitea:3000 and the Release workflow
# doesn't exist, so skip silently.
if [[ "$GITHUB_SERVER_URL" != "https://github.com" ]]; then
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index c6abfb57..42b8ecc1 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -18,7 +18,7 @@ on:
release:
types: [published, released]
# Explicitly triggered by the promote workflow's finalize job after
- # publishing. This is the reliable path — event-based triggers don't
+ # publishing. This is the reliable path; event-based triggers don't
# fire when releases are managed via API calls in a workflow run.
workflow_dispatch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90c5d2a0..a079f72c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -62,7 +62,7 @@ release that bumps `schema_version`.
`actions/upload-artifact` v7, `goreleaser/goreleaser-action` v7,
`github.com/spf13/cobra` v1.10.2. (#48, #49, #52, #53, #54)
-## [0.1.0] — 2026-06-09
+## [0.1.0] - 2026-06-09
Initial release of cascade: a trunk-based CI/CD orchestrator for GitHub Actions.
diff --git a/README.md b/README.md
index de679807..52c64cda 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
cascade
-
+
@@ -31,7 +31,7 @@
## How it works
-
+
The **manifest** (`.github/manifest.yaml`) is the single source of truth. It holds both the pipeline configuration and the live deployment state for every environment. You run `cascade generate-workflow` once; after that the generated workflows own their own execution.
@@ -64,7 +64,7 @@ Merge to trunk
└─────────────────────────────────────────────────────────────┘
```
-
+
---
@@ -146,20 +146,20 @@ cascade is a metadata courier. You construct the registry and deploy operations
cascade generates workflows that handle the orchestration layer. Your callback workflows handle the domain logic. The manifest gives you control over:
-- **Change detection** — builds and deploys only run when their declared `triggers` match changed paths
-- **Dependency ordering** — `depends_on` chains builds and deploys in the right order
-- **Matrix builds** — fan out a single build over a matrix of inputs
-- **Per-job runner selection** — `runs_on` at the config or per-build/deploy level
-- **Concurrency control** — configurable group and cancel-in-progress on orchestrate, promote, release, and external-update workflows
-- **Extra triggers** — attach `schedule`, `repository_dispatch`, `workflow_run`, and `merge_group` events to orchestration
-- **Dispatch inputs** — expose operator-facing manual-run inputs on the generated `workflow_dispatch`
-- **PR plan preview** — a comment on each PR shows which builds and deploys would run
-- **Merge queue lane** — a dedicated gate job runs before merge to protect trunk
-- **Action pinning** — `pin_mode: sha` emits pinned SHA references for all cascade-managed action calls; override individual actions via `action_pins`
-- **Breaking-change gate** — `feat!:` or `BREAKING CHANGE:` commits block the prerelease-to-release boundary unless explicitly overridden
-- **Artifact passing** — `artifact_id` output from build callbacks is stored in state and forwarded to deploys and the publish callback
-- **Publish callback** — after a release is published, a separate workflow call lets you retag RC artifacts in your registry
-- **Schema version enforcement** — `schema_version` on the manifest is checked on every CLI invocation; incompatible manifests are rejected with a clear error
+- **Change detection**: builds and deploys run only when their declared `triggers` match changed paths.
+- **Dependency ordering**: `depends_on` chains builds and deploys in the right order.
+- **Matrix builds**: fan out a single build over a matrix of inputs.
+- **Per-job runner selection**: set `runs_on` at the config or per-build/deploy level.
+- **Concurrency control**: configurable group and cancel-in-progress on orchestrate, promote, release, and external-update workflows.
+- **Extra triggers**: attach `schedule`, `repository_dispatch`, `workflow_run`, and `merge_group` events to orchestration.
+- **Dispatch inputs**: expose operator-facing manual-run inputs on the generated `workflow_dispatch`.
+- **PR plan preview**: a comment on each PR shows which builds and deploys would run.
+- **Merge queue lane**: a dedicated gate job runs before merge to protect trunk.
+- **Action pinning**: `pin_mode: sha` emits pinned SHA references for all cascade-managed action calls. Override individual actions via `action_pins`.
+- **Breaking-change gate**: `feat!:` or `BREAKING CHANGE:` commits block the prerelease-to-release boundary unless you override them.
+- **Artifact passing**: the `artifact_id` output from build callbacks is stored in state and forwarded to deploys and the publish callback.
+- **Publish callback**: once a release is published, a separate workflow call lets you retag RC artifacts in your registry.
+- **Schema version enforcement**: every CLI invocation checks `schema_version` on the manifest and rejects incompatible manifests with a clear error.
For a no-environment project (library or CLI), omit `environments` entirely. Commits produce RC pre-releases; a `promote` dispatch publishes the final release.
@@ -250,11 +250,11 @@ Full flag reference: [docs/cli-reference.md](docs/cli-reference.md).
## Roadmap to stable
-cascade is functional and self-hosted — the releases page shows the full pipeline running end to end. The remaining work before the v1.0.0 schema freeze falls into two areas:
+cascade is functional and self-hosted. Its own releases page shows the full pipeline running end to end. The remaining work before the v1.0.0 schema freeze falls into two areas.
-**Schema coverage** — a handful of GitHub Actions capabilities are modeled in the manifest shape but not yet emitted by the generator: environment gates, OIDC token configuration, and per-environment runner overrides. These are on the direct path to v1.0.0.
+**Schema coverage.** A few GitHub Actions capabilities are modeled in the manifest shape but not yet emitted by the generator: environment gates, OIDC token configuration, and per-environment runner overrides. These sit on the direct path to v1.0.0.
-**Hardening** — schema version enforcement (shipped), compatibility docs ([docs/versioning.md](docs/versioning.md)), and further e2e coverage to confirm that the generated workflows behave correctly under edge cases (empty builds, cross-repo coordination, rollback to N-1).
+**Hardening.** This covers schema version enforcement (shipped), compatibility docs ([docs/versioning.md](docs/versioning.md)), and more e2e coverage. The added tests confirm that the generated workflows behave correctly under edge cases such as empty builds, cross-repo coordination, and rollback to N-1.
The manifest schema field shapes were frozen in v0.1.0 as the v1 contract baseline. Minor versions between now and v1.0.0 may add new optional fields; no existing fields will be removed or renamed before v1.0.0.
@@ -266,10 +266,10 @@ Open work is tracked in [GitHub Issues](https://github.com/stablekernel/cascade/
cascade follows these conventions in its own codebase and in the generated workflows it produces:
-- **Additive manifest changes** — new fields are always optional with sensible defaults; existing manifest files continue to work across minor version bumps
-- **Conventional commits** — commit messages follow `type: subject` (e.g., `feat:`, `fix:`, `docs:`); the changelog generator reads this format
-- **Callback isolation** — generated workflows call your workflows via `workflow_call`; cascade never reaches into your callback logic
-- **Metadata courier** — cascade passes artifact identifiers and versions between stages; it never touches your container registry, package registry, or deployment target directly
+- **Additive manifest changes**: new fields are always optional with sensible defaults, so existing manifest files keep working across minor version bumps.
+- **Conventional commits**: commit messages follow `type: subject` (for example `feat:`, `fix:`, `docs:`), and the changelog generator reads this format.
+- **Callback isolation**: generated workflows call your workflows via `workflow_call`, and cascade never reaches into your callback logic.
+- **Metadata courier**: cascade passes artifact identifiers and versions between stages. It never touches your container registry, package registry, or deployment target directly.
---
diff --git a/SECURITY.md b/SECURITY.md
index 3e670b98..c5899b4c 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,7 +4,7 @@
| Version | Supported |
| --- | --- |
-| 0.x (latest) | Yes — security fixes backported promptly |
+| 0.x (latest) | Yes, security fixes backported promptly |
| Older 0.x tags | No |
The `0.x` line is the active release line. Only the most recent tag receives
diff --git a/docs/architecture.md b/docs/architecture.md
index 860e216d..e1c7b6ee 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -4,11 +4,11 @@ System design and internals of cascade.
## Design Principles
-1. **Build once, deploy everywhere** - Single artifact promoted through environments
-2. **Change-driven** - Only build/deploy what changed
-3. **Trunk-based** - Single main branch, short-lived feature branches
-4. **Callback contract** - Framework orchestrates, adopting repos own build/deploy
-5. **State tracking** - Manifest tracks what's deployed where
+1. Build once, deploy everywhere. One artifact is promoted through every environment.
+2. Change-driven. We build and deploy only what changed.
+3. Trunk-based. A single main branch backs short-lived feature branches.
+4. Callback contract. The framework orchestrates and adopting repos own build and deploy.
+5. State tracking. The manifest records what is deployed where.
## System Overview
@@ -561,21 +561,21 @@ For satellite repos with notify config:
The generator emits an `environment: ` key on each deploy job whenever the
manifest includes an `environments` list. That single key is enough for GitHub
Actions to attach deployment records, honour required-reviewer gates, apply
-wait timers, and scope environment secrets — all configured inside GitHub, not
-in the manifest. No cascade code calls the Deployments REST API or the
-Environments REST API directly.
+wait timers, and scope environment secrets. You configure all of that inside
+GitHub, not in the manifest. No cascade code calls the Deployments REST API or
+the Environments REST API directly.
### What is deferred
Two capabilities are intentionally out of scope for v1:
-- **Programmatic Deployments API status** — cascade does not currently call
+- Programmatic Deployments API status. cascade does not call
`POST /repos/{owner}/{repo}/deployments` or
`POST /repos/{owner}/{repo}/deployments/{id}/statuses`. GitHub Actions creates
these records automatically when a job carries `environment:`, so adopters get
- deployment records for free without cascade owning that call.
+ deployment records without cascade owning that call.
-- **Environments REST configuration sync** — cascade does not read or write
+- Environments REST configuration sync. cascade does not read or write
environment protection rules (required reviewers, wait timers, branch policies)
via the REST API. That configuration lives in GitHub today.
@@ -583,10 +583,10 @@ Two capabilities are intentionally out of scope for v1:
Keeping cascade out of these APIs in v1 bounds the surface area and avoids
coupling the tool to GitHub API semantics that are still evolving. The
-auto-created deployment records from `environment:` already satisfy the common
-case; adding programmatic control before there is a clear adopter need would
-add complexity without a demonstrable benefit. If those APIs change shape,
-cascade would need to track the change even though nothing in v1 depends on them.
+auto-created deployment records from `environment:` already cover the common
+case. Adding programmatic control before an adopter needs it would buy
+complexity and nothing else. If those APIs change shape, cascade would have to
+track the change even though nothing in v1 depends on them.
### How the design reserves the extension points
@@ -598,7 +598,7 @@ without a breaking change:
```yaml
config:
- environments: [dev, test, prod] # ordered list — source of truth, unchanged
+ environments: [dev, test, prod] # ordered list (source of truth), unchanged
environment_config: # reserved; omitting it is valid today
prod:
gha_environment: production # maps to the GHA environment name
diff --git a/docs/callback-contract.md b/docs/callback-contract.md
index 3c4f4287..c9a6840e 100644
--- a/docs/callback-contract.md
+++ b/docs/callback-contract.md
@@ -390,7 +390,7 @@ jobs:
docker push myrepo/${{ inputs.build_name }}:${{ inputs.new_version }}
```
-The framework only carries metadata. The publish callback is responsible for the registry operation. If `artifact_id` is provided, prefer it over `old_version` for unambiguous identification.
+The framework only carries metadata. The publish callback performs the registry operation. When `artifact_id` is present, use it instead of `old_version` so the target is unambiguous.
## Custom Inputs
diff --git a/docs/cli-reference.md b/docs/cli-reference.md
index b43d4a68..b79bf145 100644
--- a/docs/cli-reference.md
+++ b/docs/cli-reference.md
@@ -8,7 +8,7 @@ Complete reference for the `cascade` command-line tool.
# Latest stable
go install github.com/stablekernel/cascade/cmd/cascade@latest
-# Bleeding edge from master
+# Latest build from master
go install github.com/stablekernel/cascade/cmd/cascade@master
# Specific version
@@ -35,7 +35,7 @@ These flags are available on all commands:
| Flag | Type | Description |
|------|------|-------------|
-| `--dry-run` | bool | Preview mode — show what would happen without making changes |
+| `--dry-run` | bool | Preview mode: show what would happen without making changes |
| `--trace` | bool | Enable TRACE-level logging for detailed internals |
| `--json` | bool | Output structured JSON for workflow consumption |
@@ -189,7 +189,7 @@ cascade generate-workflow
- **Output chaining**: passes outputs from one callback to dependents
- **Per-callback policies**: respects `run_policy`, `on_failure`, `retries`
- **Environment overrides**: applies `env_inputs` per environment
-- **Publish step**: when `publish:` is configured, the promote workflow dispatches the callback once per build at the prerelease→release boundary
+- **Publish step**: when `publish:` is configured, the promote workflow dispatches the callback once per build at the boundary where a prerelease becomes a release
### manage-release
@@ -390,9 +390,9 @@ cascade next-version \
| `--json` | bool | No | Output as JSON |
Bump rules:
-- Breaking change (`feat!`, `BREAKING CHANGE:`) → major bump
-- Feature (`feat`) → minor bump
-- Fix (`fix`) → patch bump
+- Breaking change (`feat!`, `BREAKING CHANGE:`) triggers a major bump
+- Feature (`feat`) triggers a minor bump
+- Fix (`fix`) triggers a patch bump
- Pre-release environments append an RC suffix (e.g., `v1.3.0-rc.0`)
### external
diff --git a/docs/configuration.md b/docs/configuration.md
index d1354f31..f8549411 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -13,7 +13,7 @@ ci:
environments: [dev, test, prod]
# builds, deploys, etc.
- state: # Deployment tracking (managed by the framework — do not edit)
+ state: # Deployment tracking (managed by the framework, do not edit)
dev:
sha: "abc123"
version: "v1.2.0-rc.3"
@@ -57,7 +57,7 @@ Controls which CLI version the generated workflows install via setup-cli:
| Value | Behavior |
|-------|----------|
| `latest` | Most recent stable release (default) |
-| `beta` | Bleeding edge from `master` branch |
+| `beta` | Latest build from the `master` branch |
| `vX.Y.Z` | Specific version (e.g., `v2.0.4`) |
Pin to a specific version for reproducibility. Use `beta` for early access.
@@ -122,7 +122,7 @@ ci:
| `env_inputs` | map | {} | Per-environment input overrides |
| `run_policy` | string | default | Execution policy |
| `on_failure` | string | abort | Failure handling |
-| `retries` | int | 0 | Retry attempts (0–3) |
+| `retries` | int | 0 | Retry attempts (0-3) |
### builds Section
@@ -156,7 +156,7 @@ ci:
| `env_inputs` | map | No | Per-environment input overrides |
| `run_policy` | string | No | Execution policy |
| `on_failure` | string | No | Failure handling |
-| `retries` | int | No | Retry attempts (0–3) |
+| `retries` | int | No | Retry attempts (0-3) |
The build's `artifact_id` output (if declared) is captured automatically into state. Any other declared outputs are forwarded to dependent deploys as inputs.
@@ -194,7 +194,7 @@ ci:
| `env_inputs` | map | No | Per-environment overrides |
| `run_policy` | string | No | Execution policy |
| `on_failure` | string | No | Failure handling |
-| `retries` | int | No | Retry attempts (0–3) |
+| `retries` | int | No | Retry attempts (0-3) |
### Deploy Types
@@ -210,7 +210,7 @@ Build-linked deploys inherit the build's triggers for change detection during pr
### publish Section
-The publish callback runs once per build when a release is published (RC → final semver). Use it to retag artifacts that still carry their RC version.
+The publish callback runs once per build when a release is published, at the point where an RC version becomes a final semver. Use it to retag artifacts that still carry their RC version.
```yaml
ci:
@@ -265,8 +265,8 @@ ci:
| `deploys[].triggers` | list | No | File patterns for change detection |
**Workflow paths:**
-- Local (`.github/workflows/deploy.yaml`) — calls a workflow in the primary repo
-- External (`org/repo/.github/workflows/deploy.yaml@ref`) — calls a workflow in the external repo
+- Local (`.github/workflows/deploy.yaml`) calls a workflow in the primary repo
+- External (`org/repo/.github/workflows/deploy.yaml@ref`) calls a workflow in the external repo
When external deploys are configured, the generated promote workflow includes deploy jobs for each external deploy and the finalize job tracks their state.
@@ -331,7 +331,7 @@ Omit this section to use the built-in conventional commit parser.
## State Section
-The `state` section tracks deployment state per environment plus a synthetic `release` slot. It is automatically managed by the framework — do not hand-edit.
+The `state` section tracks deployment state per environment plus a synthetic `release` slot. The framework manages it automatically. Do not hand-edit.
### Structure
@@ -430,7 +430,7 @@ deploys:
stack_version: "v2.1.0"
```
-This enables diff-based change detection during promotions — only deployables with actual file changes are redeployed.
+This enables diff-based change detection during promotions. Only deployables with actual file changes are redeployed.
### External Deploy Tracking
@@ -477,7 +477,7 @@ Controls when a callback executes:
### retries
-Number of retry attempts if the callback fails (0–3).
+Number of retry attempts if the callback fails (0-3).
## Trigger Patterns
@@ -493,9 +493,9 @@ Triggers use glob patterns:
| `deploy/k8s/**` | All files under deploy/k8s/ |
Special characters:
-- `*` — matches any characters except `/`
-- `**` — matches any path segments
-- `?` — matches a single character
+- `*` matches any characters except `/`
+- `**` matches any path segments
+- `?` matches a single character
## Input Inheritance
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 10ef96a1..d12083b2 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -24,7 +24,7 @@ go install github.com/stablekernel/cascade/cmd/cascade@v2.0.4
cascade version
```
-In GitHub Actions, generated workflows install the CLI for you via the setup action — you don't need to add it explicitly. To pin a version, set `cli_version` in your manifest.
+In GitHub Actions, generated workflows install the CLI for you via the setup action, so you don't need to add it explicitly. To pin a version, set `cli_version` in your manifest.
If you need to invoke it manually:
@@ -83,7 +83,7 @@ ci:
prod: {}
```
-The framework owns `state:` and `latest_release:`. The `state: { dev: {}, ... }` skeleton is enough — the workflows fill in details on every run.
+The framework owns `state:` and `latest_release:`. The `state: { dev: {}, ... }` skeleton is enough. The workflows fill in the details on every run.
See [Configuration Reference](configuration.md) for every field.
@@ -260,8 +260,8 @@ cascade generate-workflow --force
```
This creates:
-- `.github/workflows/orchestrate.yaml` — runs on merge to trunk
-- `.github/workflows/promote.yaml` — manual promotion between environments
+- `.github/workflows/orchestrate.yaml` runs on merge to trunk
+- `.github/workflows/promote.yaml` handles manual promotion between environments
## Step 5: Validate
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 00000000..08750892
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,38 @@
+# Cascade
+
+**Declarative trunk-based CI/CD for GitHub Actions.**
+
+Define what to build and where to deploy in one manifest. Cascade generates the
+GitHub Actions wiring, tracks deployment state, manages releases, and cascades
+promotions through your environments.
+
+## How it works
+
+The manifest (`.github/manifest.yaml`) is the single source of truth. It holds the
+pipeline configuration and the live deployment state for every environment. You run
+`cascade generate-workflow` once. After that, the generated workflows own their
+execution.
+
+A merge to trunk runs the orchestrate workflow: it detects what changed, computes
+the next release candidate, builds and deploys to the first environment, and writes
+state back to the manifest. A `workflow_dispatch` then promotes the same artifacts
+forward, one environment at a time, until the release is published and the RC tags
+are cleaned up.
+
+## Where to go next
+
+- **[Getting Started](getting-started.md)** walks through a first manifest and the
+ generated workflows.
+- **[Manifest Reference](configuration.md)** documents every field.
+- **[Callback Contract](callback-contract.md)** covers the inputs and outputs your
+ build, deploy, and publish workflows exchange with cascade.
+- **[Workflows](workflows.md)** explains orchestrate, promote, and release.
+- **[CLI Reference](cli-reference.md)** lists the commands and flags.
+- **[Architecture](architecture.md)** describes the design and what cascade does and
+ does not own.
+- **[Versioning & Schema](versioning.md)** sets out the schema compatibility policy.
+
+## Project
+
+Cascade is open source under the Apache 2.0 license. The source, issue tracker, and
+releases live at [github.com/stablekernel/cascade](https://github.com/stablekernel/cascade).
diff --git a/docs/versioning.md b/docs/versioning.md
index 9c28c354..d8dad99f 100644
--- a/docs/versioning.md
+++ b/docs/versioning.md
@@ -1,8 +1,8 @@
# Versioning and schema compatibility
-The cascade manifest is the public contract between your repository and the
-cascade CLI. This document describes how the manifest schema is versioned and
-how the CLI decides whether it can read a given manifest.
+The cascade manifest is the contract between your repository and the cascade
+CLI. This document describes how the manifest schema is versioned and how the
+CLI decides whether it can read a given manifest.
## `schema_version`
@@ -16,8 +16,8 @@ ci:
# ...
```
-`schema_version` is a single monotonic integer — a "schema major" — not a semver
-string. It identifies which breaking-change generation of the schema the
+`schema_version` is a single monotonic integer, the "schema major". It is not a
+semver string. It identifies which breaking-change generation of the schema the
manifest is written for.
### Why an integer
@@ -25,8 +25,8 @@ manifest is written for.
The manifest evolves additively. New capabilities arrive as new optional fields,
new enum values, or new nested blocks, each with a sensible default. An older
CLI ignores fields it does not recognize, and a newer CLI fills in defaults for
-fields an older manifest omits. Because of this, **additive changes never change
-`schema_version`**. The integer only moves when a change is genuinely breaking:
+fields an older manifest omits. Because of this, additive changes never change
+`schema_version`. The integer only moves when a change is genuinely breaking:
- a field is removed,
- a field is re-typed,
@@ -39,16 +39,16 @@ additive-only design, never need to exist.
The CLI knows two bounds:
-- `CurrentSchemaVersion` — the highest schema version this CLI understands. A
+- `CurrentSchemaVersion` is the highest schema version this CLI understands. A
manifest that omits `schema_version` is assumed to target this version.
-- `MinSchemaVersion` — the oldest schema version this CLI still reads.
+- `MinSchemaVersion` is the oldest schema version this CLI still reads.
On load, the CLI applies the following rules:
| Manifest `schema_version` | CLI behavior |
| --- | --- |
| equal to `CurrentSchemaVersion` | Accepted silently. |
-| omitted or `0` | Accepted with a warning; assumed to be `CurrentSchemaVersion`. Pin it explicitly. Because `schema_version` is an `int` field with `omitempty`, an explicit `schema_version: 0` is encoded identically to an absent field and is treated the same way — as omitted. |
+| omitted or `0` | Accepted with a warning; assumed to be `CurrentSchemaVersion`. Pin it explicitly. Because `schema_version` is an `int` field with `omitempty`, an explicit `schema_version: 0` is encoded identically to an absent field and is treated the same way, as omitted. |
| between `MinSchemaVersion` and `CurrentSchemaVersion - 1` | Accepted with a warning; the CLI still reads it. See the migration table below. |
| below `MinSchemaVersion` (and not `0`) | Rejected. The schema generation is no longer supported; follow the migration table. |
| above `CurrentSchemaVersion` | Rejected. The manifest needs a newer CLI; upgrade the `cli_version` pin. A newer schema may rely on changed semantics this CLI would mis-handle, so it does not guess. |
@@ -83,14 +83,18 @@ current schema version is the first.
## Supported release line
-**0.x (current)** — the active development line. Bug fixes, security patches,
-and new capabilities all land here. No stability guarantee is made for the CLI
-command surface or the manifest schema between 0.x releases; additive changes
-arrive without a `schema_version` bump, but breaking changes (field removals,
-type changes, behaviour changes) increment `schema_version` and carry a
-`Migration` entry in [CHANGELOG.md](https://github.com/stablekernel/cascade/blob/main/CHANGELOG.md).
+### 0.x (current)
-**1.0** — when cascade reaches v1.0 the following guarantees apply:
+This is the active development line. Bug fixes, security patches, and new
+capabilities all land here. No stability guarantee is made for the CLI command
+surface or the manifest schema between 0.x releases. Additive changes arrive
+without a `schema_version` bump. Breaking changes (field removals, type changes,
+behaviour changes) increment `schema_version` and carry a `Migration` entry in
+[CHANGELOG.md](https://github.com/stablekernel/cascade/blob/main/CHANGELOG.md).
+
+### 1.0
+
+When cascade reaches v1.0 the following guarantees apply:
- The CLI command surface (flags, subcommands, exit codes, JSON output shapes)
follows semver: breaking changes require a major version bump.
diff --git a/docs/workflows.md b/docs/workflows.md
index d425ebed..5d0c7b7f 100644
--- a/docs/workflows.md
+++ b/docs/workflows.md
@@ -39,7 +39,7 @@ Merge to Trunk
### Triggering
-The orchestrate workflow is generated to fire on `push` to the trunk branch. There is no need to wrap it — the generator emits the trigger directly:
+The orchestrate workflow is generated to fire on `push` to the trunk branch. You don't need to wrap it. The generator emits the trigger directly:
```yaml
# .github/workflows/orchestrate.yaml (generated)
@@ -50,7 +50,7 @@ on:
### Standard Inputs
-The orchestrate workflow has no manual inputs by default — it runs automatically on push.
+The orchestrate workflow has no manual inputs by default. It runs automatically on push.
### Outputs
@@ -198,7 +198,7 @@ on:
### Atomic Promotions with Rollback
-The promote workflow supports atomic promotions where successful deploys are automatically rolled back if any deploy fails:
+The promote workflow can run atomic promotions. If any deploy fails, the deploys that already succeeded are rolled back:
```yaml
# Enabled by default
@@ -209,7 +209,8 @@ When enabled:
1. Preflight captures the target environment's current SHA as `rollback_sha`
2. If any deploy job fails, rollback jobs trigger for successful deploys
3. Rollback jobs redeploy using the `rollback_sha`
-4. This ensures all-or-nothing promotion semantics
+
+The result is all-or-nothing promotion: either every deploy lands or none does.
Disable for non-atomic promotions:
```yaml
@@ -278,7 +279,7 @@ The framework drops the RC suffix when crossing the prerelease→release boundar
## Hotfix
-Hotfix is currently handled via the standard promote workflow with `dry_run: false` and a deploy-list filter. A first-class hotfix workflow is on the roadmap (issue #94 — direct promotion to prod with branch ancestry checks).
+Hotfix is currently handled via the standard promote workflow with `dry_run: false` and a deploy-list filter. A first-class hotfix workflow is tracked in issue #94 (direct promotion to prod with branch ancestry checks).
## Workflow Permissions
diff --git a/e2e/harness/act.go b/e2e/harness/act.go
index 11e7775a..d0fe1c12 100644
--- a/e2e/harness/act.go
+++ b/e2e/harness/act.go
@@ -75,7 +75,7 @@ func NewActRunner(ctx context.Context, giteaURL, giteaToken, networkName string,
return nil, fmt.Errorf("failed to install act: %w", err)
}
- // Network override is passed on the CLI as `--network=` — act's
+ // Network override is passed on the CLI as `--network=`. Act's
// dedicated flag drives ContainerNetworkMode; --container-options is
// appended after docker create and cannot override the network mode.
actrc := `mkdir -p /root/.config/act && cat > /root/.config/act/actrc <<'EOF'
@@ -91,7 +91,7 @@ EOF`
// Pre-pull the act job-container image into the host docker daemon (via
// the bind-mounted docker socket). Without this, every parallel scenario
// triggers its own `docker pull` and races to fetch from registry-1.docker.io
- // — under proxied or rate-limited connections (e.g., Docker Desktop on
+ // Under proxied or rate-limited connections (e.g., Docker Desktop on
// macOS) those pulls intermittently time out and fail the workflow.
// The pull is best-effort: if it fails (offline, image already present,
// proxy hiccup), we proceed; act will retry on its own when needed.
@@ -282,7 +282,7 @@ func (a *ActRunner) RunWorkflowFromRepo(ctx context.Context, opts RunOpts) (*Ext
// that targeted a specific workflow file but produced zero parsed jobs is also
// a failure: act emitted no job events because it could not find or load the
// workflow (e.g. a missing orchestrate.yaml). Without this, such a run
-// masqueraded as Conclusion="success" with 0 jobs — a missing workflow showing
+// masqueraded as Conclusion="success" with 0 jobs. A missing workflow showing
// up as a green-but-empty scenario (#25).
func normalizeWorkflowResult(result *ExtendedWorkflowResult, workflowPath string, exitCode int) {
if exitCode != 0 {
diff --git a/e2e/harness/context.go b/e2e/harness/context.go
index 6398b21d..d2fa4fba 100644
--- a/e2e/harness/context.go
+++ b/e2e/harness/context.go
@@ -165,7 +165,7 @@ func (c *ExecutionContext) RecordState(env, sha, version string) {
// ClearState removes all env state. Used by sync routines that need to
// rebuild ctx from an authoritative source (the manifest), so deletions in
-// that source — e.g. finalize wiping state[prerelease] on publish — are
+// that source (e.g. finalize wiping state[prerelease] on publish) are
// reflected rather than leaving stale ctx entries.
func (c *ExecutionContext) ClearState() {
c.mu.Lock()
diff --git a/e2e/harness/gitea.go b/e2e/harness/gitea.go
index 3b2dba06..04c8a570 100644
--- a/e2e/harness/gitea.go
+++ b/e2e/harness/gitea.go
@@ -127,7 +127,7 @@ func (g *GiteaContainer) setupAdmin(ctx context.Context) error {
// The CLI writes the user to SQLite directly while the gitea API server
// runs in a separate process. /api/v1/version (used as the readiness probe)
- // returns OK before the API can authenticate the freshly-written admin —
+ // returns OK before the API can authenticate the freshly-written admin.
// observed in CI as "401 user does not exist [uid: 0, name: testadmin]"
// on the first request. Poll an authenticated endpoint until it succeeds.
return g.waitForAdminAuth(ctx, 30*time.Second)
diff --git a/e2e/harness/harness.go b/e2e/harness/harness.go
index 82ee232d..188479bc 100644
--- a/e2e/harness/harness.go
+++ b/e2e/harness/harness.go
@@ -117,7 +117,7 @@ func (h *Harness) StageRepoFromConfig(ctx context.Context, config Config) error
// Create stub workflow files for builds and deploys.
// These are reusable workflows that the CLI reads to discover inputs/outputs.
// We tag each stub's `name:` with a per-scenario suffix so act's
- // auto-generated job container hash diverges across parallel scenarios —
+ // auto-generated job container hash diverges across parallel scenarios.
// otherwise multiple scenarios with the same build/deploy names race on
// /act-Build-app-app-app- at the host docker level.
scenarioTag := scenarioTagFromTestName(h.t.Name())
@@ -287,7 +287,7 @@ func scenarioTagFromTestName(name string) string {
// generatePublishStubWorkflow creates a minimal workflow_dispatch stub for the
// publish callback. Unlike build/deploy stubs (which are workflow_call), the
// publish workflow is invoked via `gh workflow run` (dispatch) from the promote
-// finalize job. The stub accepts the standard publish inputs but does nothing —
+// finalize job. The stub accepts the standard publish inputs but does nothing;
// in the e2e harness the dispatch is a no-op because GITHUB_SERVER_URL points
// to gitea.
func generatePublishStubWorkflow(scenarioTag string) string {
@@ -409,7 +409,7 @@ func (h *Harness) GenerateWorkflows(ctx context.Context) error {
// a non-zero exit, so a run that exited 0 but wrote no workflow (e.g. the
// "No changes"/skip path) left no trace and the missing orchestrate.yaml
// only surfaced much later as a `cat: ... No such file` in the orchestrate
- // step — a different scenario per parallel dispatch (#25). Keep the output
+ // step. A different scenario per parallel dispatch (#25). Keep the output
// for diagnostics.
var genOutput bytes.Buffer
if reader != nil {
@@ -423,7 +423,7 @@ func (h *Harness) GenerateWorkflows(ctx context.Context) error {
// can exit 0 without emitting .github/workflows/orchestrate.yaml; if we
// commit and push that empty set, the downstream orchestrate step runs act
// against a non-existent `-W` path and act (with --detect-event) reports no
- // jobs while still exiting success — the run then masquerades as a passing
+ // jobs while still exiting success. The run then masquerades as a passing
// scenario with 0 jobs (#25). Fail here, at the source, with the generate
// output and a workflow-dir listing so the real cause is obvious.
if err := h.assertOrchestrateGenerated(ctx, genOutput.String()); err != nil {
@@ -436,12 +436,12 @@ func (h *Harness) GenerateWorkflows(ctx context.Context) error {
// - 'uses: build.yaml' → 'uses: ./build.yaml'
//
// Earlier this was a fire-and-forget shell sed loop with three blank
- // receivers — under heavy CI parallelism (28 scenarios × act + gitea
+ // receivers. Under heavy CI parallelism (28 scenarios × act + gitea
// containers) the exec would occasionally fail or no-op and leave
// un-localized refs in the workflow files. The act job container would
// then try to fetch `stablekernel/cascade@latest` from the test
// gitea (where that repo doesn't exist), failing with `authentication
- // required` — cf. #78. Now we check the exit code, verify no
+ // required` (cf. #78). Now we check the exit code, verify no
// `stablekernel/cascade` references remain, and retry on transient
// failure.
if err := h.localizeWorkflows(ctx); err != nil {
@@ -613,7 +613,7 @@ func (h *Harness) SyncRepoToActContainer(ctx context.Context) error {
// under heavy parallel load against the per-scenario gitea; bind the fetch
// to origin/main explicitly and chain the reset so a partial sync surfaces
// as a non-zero exit instead of silently resetting to a stale tree (which
- // would drop the just-pushed orchestrate.yaml — #25).
+ // would drop the just-pushed orchestrate.yaml (#25).
syncCmd := []string{
"bash", "-c",
"cd /tmp/repo && git fetch origin main && git reset --hard origin/main && (git branch -f master HEAD 2>/dev/null || true)",
diff --git a/e2e/harness/multistep.go b/e2e/harness/multistep.go
index 74b027a8..54c7d5ec 100644
--- a/e2e/harness/multistep.go
+++ b/e2e/harness/multistep.go
@@ -65,7 +65,7 @@ type StepExpect struct {
// WorkflowFileExpect asserts a generated workflow file contains/excludes
// specific substrings. Verifies manifest fields make it into the emitted
-// YAML — orthogonal to behavior checks (state/jobs/etc.) which observe the
+// YAML, orthogonal to behavior checks (state/jobs/etc.) which observe the
// run outcome. Used for features whose effect is purely the generated
// workflow shape (#92 concurrency, #97 timeout-minutes, #101/#102 push
// retry loops).
diff --git a/e2e/harness/runner.go b/e2e/harness/runner.go
index 8b46455f..c099d623 100644
--- a/e2e/harness/runner.go
+++ b/e2e/harness/runner.go
@@ -498,7 +498,7 @@ func (r *Runner) syncStateFromGitea(ctx context.Context, config Config) error {
// This ensures deleted tags are properly removed from tracking
r.ctx.ClearTags()
- // An RC is a draft only while it's exclusive to the first env (e.g., dev) —
+ // An RC is a draft only while it's exclusive to the first env (e.g., dev).
// once it's been promoted into any later env, it's "blessed" and the
// workflow's prerelease step would have flipped its draft flag. Collect
// the set of RC versions that are present in any non-firstEnv state.
@@ -562,7 +562,7 @@ func (r *Runner) syncStateFromGitea(ctx context.Context, config Config) error {
}
// Clear ctx state so deletions in the manifest (e.g. finalize wiping
- // state[prerelease] on publish) are reflected — otherwise stale entries
+ // state[prerelease] on publish) are reflected. Otherwise stale entries
// from prior steps make wiped: true assertions fail.
r.ctx.ClearState()
@@ -593,7 +593,7 @@ func (r *Runner) syncStateFromGitea(ctx context.Context, config Config) error {
// readPromotedRCVersions reads the manifest and returns the set of RC versions
// that appear in any state[env] beyond the first env. Once an RC has been
// promoted past dev (the first env), the workflow's prerelease step would have
-// flipped its draft flag — so the harness should treat it as non-draft.
+// flipped its draft flag, so the harness should treat it as non-draft.
func (r *Runner) readPromotedRCVersions(ctx context.Context, envs []string) map[string]bool {
promoted := make(map[string]bool)
if len(envs) < 2 {
@@ -698,7 +698,7 @@ func (r *Runner) assertStep(ctx context.Context, step *Step, preState *Execution
// Returns errors for missing-substring or unexpected-substring matches.
func (r *Runner) assertWorkflowFile(ctx context.Context, expect WorkflowFileExpect) []error {
if r.harness == nil || r.harness.act == nil {
- // In unit-test mode there's no act container — skip silently.
+ // In unit-test mode there's no act container; skip silently.
return nil
}
if expect.Path == "" {
diff --git a/e2e/scenarios/05-publish-callback.yaml b/e2e/scenarios/05-publish-callback.yaml
index c0eeeb6b..0a249f01 100644
--- a/e2e/scenarios/05-publish-callback.yaml
+++ b/e2e/scenarios/05-publish-callback.yaml
@@ -27,7 +27,7 @@ steps:
src/app.ts: |
export function main() { console.log("v0.1.0 feature"); }
- - name: "Orchestrate — build runs, state.prerelease created"
+ - name: "Orchestrate: build runs, state.prerelease created"
action: orchestrate
expect:
state:
@@ -37,7 +37,7 @@ steps:
jobs:
app: success
- - name: "Promote to release — publish step runs (dispatch no-op in harness)"
+ - name: "Promote to release: publish step runs (dispatch no-op in harness)"
action: promote
promote:
mode: default
diff --git a/e2e/scenarios/06-callback-timeout.yaml b/e2e/scenarios/06-callback-timeout.yaml
index 49b18b90..cdb1d63b 100644
--- a/e2e/scenarios/06-callback-timeout.yaml
+++ b/e2e/scenarios/06-callback-timeout.yaml
@@ -3,7 +3,7 @@ description: |
Verifies the timeout_minutes field on validate/builds/deploys propagates
into the generated orchestrate.yaml as a job-level timeout-minutes (#97).
- This is a generator-output verification scenario — assertion runs on the
+ This is a generator-output verification scenario. Assertion runs on the
staged repo after StageRepoFromConfig generates workflows but before any
orchestrate runs. We don't run orchestrate because act doesn't honor
timeout-minutes on `uses:` reusable workflow callers and would fail the
diff --git a/e2e/scenarios/08-state-push-retry.yaml b/e2e/scenarios/08-state-push-retry.yaml
index 6fece5f7..0bd2428d 100644
--- a/e2e/scenarios/08-state-push-retry.yaml
+++ b/e2e/scenarios/08-state-push-retry.yaml
@@ -4,7 +4,7 @@ description: |
fetch+reset+reapply+push retry loop (#101) instead of a single git push.
Generator-output verification. Reproducing the actual concurrent-push race
- needs real GHA — out of scope for the act+gitea harness.
+ needs real GHA and is out of scope for the act+gitea harness.
config:
trunk_branch: main
diff --git a/e2e/scenarios/09-inline-run-callback.yaml b/e2e/scenarios/09-inline-run-callback.yaml
index 6672a121..bf8393fc 100644
--- a/e2e/scenarios/09-inline-run-callback.yaml
+++ b/e2e/scenarios/09-inline-run-callback.yaml
@@ -4,7 +4,7 @@ description: |
inline run: step in the generated orchestrate.yaml, while a sibling callback
declaring workflow: still emits a jobs..uses reusable-workflow call (#36).
- This is a generator-output verification scenario — assertion runs on the
+ This is a generator-output verification scenario. Assertion runs on the
staged repo after StageRepoFromConfig generates workflows but before any
orchestrate runs.
diff --git a/e2e/scenarios/10-inline-job-attributes.yaml b/e2e/scenarios/10-inline-job-attributes.yaml
index 0651c089..068443e0 100644
--- a/e2e/scenarios/10-inline-job-attributes.yaml
+++ b/e2e/scenarios/10-inline-job-attributes.yaml
@@ -5,7 +5,7 @@ description: |
generated orchestrate.yaml (#12, #35, #15, #17).
A sibling reusable-workflow callback (workflow:) must not carry those
- attributes — GHA forbids runs-on/concurrency on jobs..uses calls.
+ attributes; GHA forbids runs-on/concurrency on jobs..uses calls.
Generator-output verification scenario; assertion runs on the staged repo
after StageRepoFromConfig generates workflows but before any orchestrate runs.
diff --git a/e2e/scenarios/11-job-timeouts-and-optional-deps.yaml b/e2e/scenarios/11-job-timeouts-and-optional-deps.yaml
index ab380199..b7e6b799 100644
--- a/e2e/scenarios/11-job-timeouts-and-optional-deps.yaml
+++ b/e2e/scenarios/11-job-timeouts-and-optional-deps.yaml
@@ -2,17 +2,17 @@ name: "Owned-job timeouts and optional dependencies"
description: |
Verifies two generator features in the emitted orchestrate.yaml:
- #37 — cascade-owned jobs (setup, finalize) carry a timeout-minutes from the
+ #37: cascade-owned jobs (setup, finalize) carry a timeout-minutes from the
config-level job_timeout_minutes (here 20), so cascade's own jobs do not
inherit GHA's 360-minute default. Reusable-workflow callbacks (jobs..uses)
- get no timeout — the called workflow owns its own.
+ get no timeout; the called workflow owns its own.
- #18 — optional_depends_on sequences a deploy AFTER a build (adds it to needs:)
+ #18: optional_depends_on sequences a deploy AFTER a build (adds it to needs:)
but does NOT skip-gate it: a skipped optional dep must not skip the deploy.
The emitted shape that yields this is the optional dep present in needs: and
absent from the if: skip-gate, while a hard depends_on still gates.
- This is a generator-output verification scenario — assertions run on the staged
+ This is a generator-output verification scenario. Assertions run on the staged
repo after StageRepoFromConfig generates workflows. We don't run orchestrate
because act does not honor timeout-minutes on `uses:` callers; real
timeout-firing belongs to real-GHA validation.
diff --git a/internal/changelog/parse.go b/internal/changelog/parse.go
index 295e9816..542dc937 100644
--- a/internal/changelog/parse.go
+++ b/internal/changelog/parse.go
@@ -20,7 +20,7 @@ var (
// Per Conventional Commits, `BREAKING CHANGE:` is a footer that appears
// at the start of a line. Anchor with `(?m)^` and use `[ \t]+` between
// BREAKING and CHANGE so word-wrapped narrative (`...BREAKING\nCHANGE:...`)
- // doesn't match — earlier `\s+` accepted newlines and flagged unrelated
+ // doesn't match. Earlier `\s+` accepted newlines and flagged unrelated
// commits as breaking.
breakingBodyPattern = regexp.MustCompile(`(?im)^BREAKING[ \t]+CHANGE\s*:`)
// Matches PR references like (#123) or (org/repo#123)
@@ -268,7 +268,7 @@ func formatCommitLine(c ConventionalCommit, repo string) string {
// Build attribution
var attribution string
if c.GitHubUsername != "" {
- attribution = fmt.Sprintf(" — @%s", c.GitHubUsername)
+ attribution = fmt.Sprintf(" (@%s)", c.GitHubUsername)
}
// Escape @branch patterns to prevent GitHub from treating them as user mentions
@@ -291,7 +291,7 @@ func formatOtherCommitLine(c git.Commit, repo string) string {
var attribution string
if c.GitHubUsername != "" {
- attribution = fmt.Sprintf(" — @%s", c.GitHubUsername)
+ attribution = fmt.Sprintf(" (@%s)", c.GitHubUsername)
}
// Escape @branch patterns to prevent GitHub from treating them as user mentions
diff --git a/internal/changelog/parse_test.go b/internal/changelog/parse_test.go
index 1c7bda44..937ca202 100644
--- a/internal/changelog/parse_test.go
+++ b/internal/changelog/parse_test.go
@@ -349,11 +349,11 @@ func TestFormatMarkdown_WithContributors(t *testing.T) {
result := FormatMarkdown(nil, features, fixes, nil, "owner/repo", "base123", "head456")
- // Check inline attribution exists (new format: — @username)
- if !strings.Contains(result, "— @alice") {
+ // Check inline attribution exists (the "(@username)" format).
+ if !strings.Contains(result, "(@alice)") {
t.Error("Missing inline contributor attribution for alice")
}
- if !strings.Contains(result, "— @bob") {
+ if !strings.Contains(result, "(@bob)") {
t.Error("Missing inline contributor attribution for bob")
}
diff --git a/internal/config/types.go b/internal/config/types.go
index 18d716d4..dc977427 100644
--- a/internal/config/types.go
+++ b/internal/config/types.go
@@ -167,8 +167,8 @@ func (c *TrunkConfig) GetSchemaVersion() int {
// validateSchemaVersion is the testable core of the compatibility check. It
// evaluates v against the provided min and current bounds and returns a warning
// string (non-empty means warn-and-accept) or an error (means reject). Callers
-// that need to exercise the full matrix — including branches that are currently
-// unreachable when min == current — should call this directly.
+// that need to exercise the full matrix (including branches that are currently
+// unreachable when min == current) should call this directly.
//
// Rules:
//
@@ -189,7 +189,7 @@ func validateSchemaVersion(v, min, current int) (warning string, err error) {
case v > current:
return "", fmt.Errorf(
"manifest requires schema version %d but this CLI supports schema versions up to %d; "+
- "upgrade the CLI (cli_version) — see docs/versioning.md", v, current)
+ "upgrade the CLI (cli_version); see docs/versioning.md", v, current)
case v < min:
return "", fmt.Errorf(
"manifest schema version %d is no longer supported (minimum %d); "+
@@ -340,7 +340,7 @@ type ValidateConfig struct {
// v1 reserved-shape per-callback fields (parse + structural validation only).
// The validate gate is a singleton, so the spec scopes optional_depends_on
- // (§2.11) and auto_commits (§5.5) to builds/deploys only — not here.
+ // (§2.11) and auto_commits (§5.5) to builds/deploys only; not here.
Secrets *SecretsConfig `yaml:"secrets,omitempty" json:"secrets,omitempty"`
Permissions map[string]string `yaml:"permissions,omitempty" json:"permissions,omitempty"`
RunsOn *RunsOn `yaml:"runs_on,omitempty" json:"runs_on,omitempty"`
diff --git a/internal/generate/action_pins.go b/internal/generate/action_pins.go
index 476e4c8c..1b509b4c 100644
--- a/internal/generate/action_pins.go
+++ b/internal/generate/action_pins.go
@@ -48,10 +48,10 @@ var defaultActionPins = map[string]actionPin{
// uniformly and no ref is missed.
//
// Resolution order:
-// 1. config.action_pins[action] — explicit per-action override (any ref/sha),
+// 1. config.action_pins[action]: explicit per-action override (any ref/sha),
// applied regardless of pin_mode. Use this for forks or org-mirrored actions.
-// 2. pin_mode: sha — emit @ # from the built-in table.
-// 3. pin_mode: tag (default) — emit @, today's behavior, never
+// 2. pin_mode: sha: emit @ # from the built-in table.
+// 3. pin_mode: tag (default): emit @, today's behavior, never
// @latest for a third-party action.
//
// An action not present in the built-in table and not overridden falls back to
diff --git a/internal/generate/dry_run_test.go b/internal/generate/dry_run_test.go
index 4fc47e1c..39fa2f7a 100644
--- a/internal/generate/dry_run_test.go
+++ b/internal/generate/dry_run_test.go
@@ -31,7 +31,7 @@ func TestPromote_SupportsDryRun_SingleDeploy(t *testing.T) {
content, err := gen.Generate()
require.NoError(t, err)
- // The job must NOT gate on dry_run != 'true' — the callback runs regardless.
+ // The job must NOT gate on dry_run != 'true'; the callback runs regardless.
assert.NotContains(t, content,
"github.event.inputs.dry_run != 'true' && contains(fromJSON(needs.preflight.outputs.deploys_to_run), 'app')",
"supports_dry_run deploy should not be skipped by dry_run guard")
diff --git a/internal/generate/env_gates_test.go b/internal/generate/env_gates_test.go
index 9e4373cf..4bed3da0 100644
--- a/internal/generate/env_gates_test.go
+++ b/internal/generate/env_gates_test.go
@@ -67,7 +67,7 @@ func TestEnvGates_Orchestrate_DeployJob_WithoutGHAEnvironment(t *testing.T) {
Deploys: []config.DeployConfig{
{Name: "svc", Workflow: ".github/workflows/deploy.yaml", Triggers: []string{"src/**"}},
},
- // No EnvironmentConfig — no gha_environment.
+ // No EnvironmentConfig; no gha_environment.
}
gen := NewGenerator(cfg, tmpDir)
diff --git a/internal/generate/generator.go b/internal/generate/generator.go
index 1791bdc5..b9435afb 100644
--- a/internal/generate/generator.go
+++ b/internal/generate/generator.go
@@ -584,7 +584,7 @@ func (g *Generator) writeExtraTriggers(sb *strings.Builder, et *config.ExtraTrig
// writeConcurrency emits a top-level concurrency: block. Two rapid pushes to
// trunk used to fire concurrent orchestrate runs, which raced on state writes
// and produced duplicate RC tags + non-fast-forward push failures (#92).
-// Default: cancel an older in-progress run when a newer push lands —
+// Default: cancel an older in-progress run when a newer push lands.
// the older run's work is obsolete. Override via config.concurrency.
func (g *Generator) writeConcurrency(sb *strings.Builder) {
sb.WriteString("concurrency:\n")
@@ -749,7 +749,7 @@ func (g *Generator) writeCallbackJob(sb *strings.Builder, info CallbackInfo, wor
fmt.Fprintf(sb, " needs: [%s]\n", strings.Join(needs, ", "))
// if: condition based on run_policy. Optional deps are intentionally not
- // passed here — they sequence the job without gating it.
+ // passed here; they sequence the job without gating it.
g.writeIfCondition(sb, info, needs)
switch {
@@ -768,7 +768,7 @@ func (g *Generator) writeCallbackJob(sb *strings.Builder, info CallbackInfo, wor
g.writeStrategyBlock(sb, info.Matrix)
}
- // environment: — emitted on deploy jobs when the config declares a
+ // environment: emitted on deploy jobs when the config declares a
// gha_environment for at least one environment. The job-level environment:
// key wires the job to a GitHub Environment so that the environment's
// protection rules (required reviewers, wait timers, deployment branch
@@ -792,7 +792,7 @@ func (g *Generator) writeCallbackJob(sb *strings.Builder, info CallbackInfo, wor
sb.WriteString(" continue-on-error: true\n")
}
- // Inline run: callback — emit a cascade-owned job with an inline run: step
+ // Inline run: callback. Emit a cascade-owned job with an inline run: step
// instead of a jobs..uses reusable-workflow call. Standard inputs reach
// the step as env: variables rather than reusable-workflow with: inputs.
if info.Run != "" {
@@ -1434,7 +1434,7 @@ func (g *Generator) writeManifestUpdateStep(sb *strings.Builder, sorted []string
}
// Add env vars for build artifact IDs. Only emitted when the build
- // workflow declares an `artifact_id` output — the generator discovers
+ // workflow declares an `artifact_id` output. The generator discovers
// this via discoverOutputsAndInputs. When present, finalize captures
// the immutable identifier (e.g., a Docker image digest) so it can be
// stored in state and later passed to the publish callback on release.
diff --git a/internal/generate/generator_test.go b/internal/generate/generator_test.go
index ecba9b03..51758581 100644
--- a/internal/generate/generator_test.go
+++ b/internal/generate/generator_test.go
@@ -216,7 +216,7 @@ func TestGenerator_CallbackTimeoutMinutes(t *testing.T) {
// TestGenerator_CallbackTimeoutOmittedWhenZero asserts no timeout-minutes is
// emitted on a reusable-workflow callback (jobs..uses) when its
-// timeout_minutes is unset — those callers own their own timeout. Cascade-owned
+// timeout_minutes is unset; those callers own their own timeout. Cascade-owned
// jobs (setup/finalize) still receive the owned-job default (#37), so the check
// is scoped to the callback job block, not the whole workflow.
func TestGenerator_CallbackTimeoutOmittedWhenZero(t *testing.T) {
@@ -1389,7 +1389,7 @@ on:
}
// =============================================================================
-// Publish callback (#39) — artifact_id tracking in orchestrate finalize
+// Publish callback (#39): artifact_id tracking in orchestrate finalize
// =============================================================================
func TestGenerator_BuildArtifactIDTracked(t *testing.T) {
@@ -1603,7 +1603,7 @@ func TestGenerator_ExtraTriggers_MergeGroup(t *testing.T) {
require.NoError(t, err)
assert.Contains(t, result, " merge_group:\n", "merge_group trigger must be emitted when MergeGroup is non-nil")
- // Lane behavior is a separate issue — no merge_queue: config involved here.
+ // Lane behavior is a separate issue; no merge_queue: config involved here.
assert.NotContains(t, result, "merge_queue:", "lane behavior config must not appear from trigger emission alone")
}
@@ -1712,7 +1712,7 @@ func TestGenerator_BuildMatrix_MaxParallelOmittedWhenZero(t *testing.T) {
Triggers: []string{"src/**"},
Matrix: &config.MatrixConfig{
Dimensions: map[string][]string{"os": {"linux"}},
- // MaxParallel zero and FailFast nil — neither should appear.
+ // MaxParallel zero and FailFast nil: neither should appear.
},
},
},
@@ -1783,7 +1783,7 @@ func TestGenerator_PassthroughArtifact_InlineUpload(t *testing.T) {
// The path must match the declared upload path.
assert.Contains(t, result, "path: dist/",
"upload step must set path to artifact.upload value")
- // No download step — this build has no downloads configured.
+ // No download step; this build has no downloads configured.
assert.NotContains(t, result, "uses: actions/download-artifact@v4",
"build without artifact.downloads must not emit download-artifact step")
}
@@ -1860,7 +1860,7 @@ func TestGenerator_PassthroughArtifact_NoArtifactNoSteps(t *testing.T) {
// (no release config is set, so the finalize job emits no artifact steps either).
assert.NotContains(t, result, "actions/upload-artifact@v4",
"build without artifact: must not emit upload-artifact")
- // download-artifact only appears in finalize when HasReleaseArtifacts — it is
+ // download-artifact only appears in finalize when HasReleaseArtifacts. It is
// absent here because no release artifacts are declared and no passthrough is set.
assert.NotContains(t, result, "actions/download-artifact@v4",
"build without artifact: must not emit download-artifact")
diff --git a/internal/generate/graph.go b/internal/generate/graph.go
index 5439772e..eb13d700 100644
--- a/internal/generate/graph.go
+++ b/internal/generate/graph.go
@@ -14,7 +14,7 @@ type DependencyGraph struct {
// OptionalEdges holds optional_depends_on edges (job ID -> dependencies as
// job IDs). Optional deps add to a job's needs: for ordering but do NOT
- // contribute a skip-gate to its if: condition — the job still runs when an
+ // contribute a skip-gate to its if: condition. The job still runs when an
// optional dep was skipped because its triggers didn't match (#18).
OptionalEdges map[string][]string
}
diff --git a/internal/generate/input_passthrough_test.go b/internal/generate/input_passthrough_test.go
index 6a739509..63886c7c 100644
--- a/internal/generate/input_passthrough_test.go
+++ b/internal/generate/input_passthrough_test.go
@@ -12,7 +12,7 @@ import (
)
// TestOrchestrateInputExpressionPassthrough verifies that operator-authored
-// inputs survive into the orchestrate callback with: block — vars.*
+// inputs survive into the orchestrate callback with: block. Vars.*
// passthrough expressions emit verbatim, literals emit as-is, and matrix.*
// placeholders are not treated as passthrough.
func TestOrchestrateInputExpressionPassthrough(t *testing.T) {
diff --git a/internal/generate/job_attributes_test.go b/internal/generate/job_attributes_test.go
index d7a58d0d..9b2523c9 100644
--- a/internal/generate/job_attributes_test.go
+++ b/internal/generate/job_attributes_test.go
@@ -264,7 +264,7 @@ func TestInlineRunOmittedAttributesUnchanged(t *testing.T) {
// TestReusableWorkflowCallbackHasNoJobAttributes asserts that a reusable
// workflow: callback (jobs..uses) never carries runs-on / permissions /
-// concurrency on the job — GHA forbids them there and schema validation rejects
+// concurrency on the job. GHA forbids them there and schema validation rejects
// runs_on/concurrency on reusable callbacks. Only permissions is structurally
// accepted on the config, but the generator must not emit any of the three on a
// uses: job.
diff --git a/internal/generate/job_control_test.go b/internal/generate/job_control_test.go
index 0323a744..3837c69e 100644
--- a/internal/generate/job_control_test.go
+++ b/internal/generate/job_control_test.go
@@ -89,7 +89,7 @@ func TestGenerator_OwnedJobTimeoutConfigurable(t *testing.T) {
}
// TestGenerator_TimeoutNotOnReusableCallback asserts a reusable-workflow
-// callback (jobs..uses) does NOT receive the owned-job timeout — the called
+// callback (jobs..uses) does NOT receive the owned-job timeout. The called
// workflow owns its own timeout. Inline run: callbacks, which are cascade-owned,
// DO get it.
func TestGenerator_TimeoutNotOnReusableCallback(t *testing.T) {
@@ -100,9 +100,9 @@ func TestGenerator_TimeoutNotOnReusableCallback(t *testing.T) {
TrunkBranch: "main",
Environments: []string{"dev"},
Builds: []config.BuildConfig{
- // Reusable-workflow callback (uses:) — no timeout-minutes.
+ // Reusable-workflow callback (uses:): no timeout-minutes.
{Name: "reusable", Workflow: ".github/workflows/build.yaml", Triggers: []string{"src/**"}},
- // Inline run: callback — cascade-owned, gets the default.
+ // Inline run: callback: cascade-owned, gets the default.
{Name: "inline", Run: "go test ./...", Triggers: []string{"src/**"}},
},
}
@@ -177,7 +177,7 @@ func TestGenerator_OptionalDependsOnAddsNeedsWithoutGating(t *testing.T) {
assert.Regexp(t, `needs:.*build-app`, deploy, "hard dep in needs:")
assert.Regexp(t, `needs:.*build-migrations`, deploy, "optional dep in needs: for ordering")
- // if: must skip-gate on the HARD dep but NOT on the optional dep — a skipped
+ // if: must skip-gate on the HARD dep but NOT on the optional dep. A skipped
// optional dep must not skip this job.
assert.Contains(t, deploy, "needs.build-app.result == 'success'",
"hard depends_on still contributes a skip-gate")
diff --git a/internal/generate/merge_queue.go b/internal/generate/merge_queue.go
index df168852..a5d4c9b6 100644
--- a/internal/generate/merge_queue.go
+++ b/internal/generate/merge_queue.go
@@ -13,8 +13,8 @@ import (
// workflow that validates the prospective trunk commit with cascade's own
// logic: it runs `cascade parse-config` as a validity gate and a dry-run
// `cascade orchestrate setup` to preview the build/deploy decisions against the
-// merge-group candidate ref. The lane is read-only — no state writes, no
-// releases, no deploys — and reports a status the merge queue can require.
+// merge-group candidate ref. The lane is read-only (no state writes, no
+// releases, no deploys) and reports a status the merge queue can require.
//
// This generator owns the LANE behavior. The raw merge_group trigger itself is
// expressible separately under extra_triggers.merge_group; the two are
diff --git a/internal/generate/promote.go b/internal/generate/promote.go
index 0f373fb5..3237de35 100644
--- a/internal/generate/promote.go
+++ b/internal/generate/promote.go
@@ -748,7 +748,7 @@ func (g *PromoteGenerator) writeDeployJobs(sb *strings.Builder) {
fmt.Fprintf(sb, " deploy-%s:\n", d.Name)
if d.Run != "" {
- // Inline run: deploy callback — cascade-owned job with an inline run:
+ // Inline run: deploy callback. This is a cascade-owned job with an inline run:
// step. Inline callbacks declare their inputs via the manifest inputs:
// keys (no reusable-workflow with: matrix); the standard environment/
// sha/image_tag inputs reach the step as env: vars.
@@ -761,7 +761,7 @@ func (g *PromoteGenerator) writeDeployJobs(sb *strings.Builder) {
} else {
fmt.Fprintf(sb, " if: ${{ github.event.inputs.dry_run != 'true' && contains(fromJSON(needs.preflight.outputs.deploys_to_run), '%s') }}\n", d.Name)
}
- // environment: — wires the job to a GitHub Environment so that the
+ // environment: wires the job to a GitHub Environment so that the
// environment's protection rules apply when gha_environment is configured
// for any env. The target env is resolved at runtime by preflight.
if anyEnvHasGHAConfig(g.config) {
@@ -831,7 +831,7 @@ func (g *PromoteGenerator) writeDeployJobs(sb *strings.Builder) {
} else {
fmt.Fprintf(sb, " if: ${{ github.event.inputs.dry_run != 'true' && contains(fromJSON(needs.preflight.outputs.deploys_to_run), '%s') }}\n", d.Name)
}
- // environment: — wires the job to a GitHub Environment so that the
+ // environment: wires the job to a GitHub Environment so that the
// environment's protection rules apply when gha_environment is configured
// for any env. The target env is resolved at runtime by preflight.
if anyEnvHasGHAConfig(g.config) {
@@ -866,7 +866,7 @@ func (g *PromoteGenerator) writeDeployJobs(sb *strings.Builder) {
} else {
sb.WriteString(" if: ${{ github.event.inputs.dry_run != 'true' && needs.preflight.outputs.has_prod_deployment == 'true' }}\n")
}
- // environment: — the prod deploy job always targets a single known env
+ // environment: The prod deploy job always targets a single known env
// (the final environment in the pipeline), so we can resolve the GitHub
// Environment name statically from gha_environment when configured.
if ec, ok := g.config.EnvironmentConfig[finalEnv]; ok && ec.GHAEnvironment != "" {
@@ -1247,7 +1247,7 @@ func (g *PromoteGenerator) writeFinalizeJob(sb *strings.Builder) {
fmt.Fprintf(sb, " GITHUB_TOKEN: %s\n", g.getReleaseTokenRef())
sb.WriteString(" TAG: ${{ steps.release-data.outputs.sem_version }}\n")
sb.WriteString(" run: |\n")
- sb.WriteString(" # Only dispatch on real GitHub — in act/gitea e2e environments\n")
+ sb.WriteString(" # Only dispatch on real GitHub. In act/gitea e2e environments\n")
sb.WriteString(" # GITHUB_SERVER_URL is http://gitea:3000 and the Release workflow\n")
sb.WriteString(" # doesn't exist, so skip silently.\n")
sb.WriteString(" if [[ \"$GITHUB_SERVER_URL\" != \"https://github.com\" ]]; then\n")
@@ -1297,7 +1297,7 @@ func (g *PromoteGenerator) writeFinalizeJob(sb *strings.Builder) {
//
// Each deploy job's conclusion is passed in as DEPLOY_RESULT_, derived
// from `needs.deploy-.result`. finalize reads these env vars to know
- // which deploys succeeded — more reliable than the legacy
+ // which deploys succeeded. More reliable than the legacy
// `gh api ... /jobs` query, which can't reach the GitHub API in act/Gitea
// test environments.
sb.WriteString(" - name: Finalize Promotion\n")
diff --git a/internal/generate/release.go b/internal/generate/release.go
index 71a0cf41..63d382f8 100644
--- a/internal/generate/release.go
+++ b/internal/generate/release.go
@@ -401,8 +401,8 @@ func (g *ReleaseGenerator) writeFinalizeJob(sb *strings.Builder) {
// Same retry-with-rebase pattern as the orchestrate finalize Update
// Manifest step (#101). release.yaml is workflow_dispatch-only so the
// race window is smaller than orchestrate's, but a concurrent
- // orchestrate state push can still reject this push as non-fast-forward
- // — see #102.
+ // orchestrate state push can still reject this push as non-fast-forward.
+ // See #102.
sb.WriteString(" echo \"Updating latest_release state\"\n")
sb.WriteString(" for attempt in 1 2 3 4 5; do\n")
sb.WriteString(" git fetch origin \"$BRANCH\"\n")
diff --git a/internal/generate/secrets_test.go b/internal/generate/secrets_test.go
index 4df85a7a..44950c6f 100644
--- a/internal/generate/secrets_test.go
+++ b/internal/generate/secrets_test.go
@@ -172,7 +172,7 @@ func TestOrchestrateDeployCallbackJob_ExplicitSecretsMap(t *testing.T) {
}
// TestOrchestrateCallbackJob_InlineRunUnaffected verifies that an inline-run
-// callback (run: ...) does not emit any secrets: key at all — inline jobs are
+// callback (run: ...) does not emit any secrets: key at all; inline jobs are
// cascade-owned, not reusable-workflow calls.
func TestOrchestrateCallbackJob_InlineRunUnaffected(t *testing.T) {
cfg := &config.TrunkConfig{
@@ -183,7 +183,7 @@ func TestOrchestrateCallbackJob_InlineRunUnaffected(t *testing.T) {
Name: "smoke",
Run: "go test ./...",
Triggers: []string{"**/*.go"},
- // Secrets field set on an inline-run callback — must be ignored.
+ // Secrets field set on an inline-run callback; must be ignored.
Secrets: &config.SecretsConfig{
Map: map[string]string{"SOME_TOKEN": "MY_TOKEN"},
},
@@ -195,7 +195,7 @@ func TestOrchestrateCallbackJob_InlineRunUnaffected(t *testing.T) {
result, err := gen.Generate()
require.NoError(t, err)
- // Inline run: jobs are cascade-owned steps — no secrets: key of any form.
+ // Inline run: jobs are cascade-owned steps; no secrets: key of any form.
assert.NotContains(t, result, "secrets:")
}
diff --git a/internal/orchestrate/orchestrator.go b/internal/orchestrate/orchestrator.go
index 0012c2f7..3b2291de 100644
--- a/internal/orchestrate/orchestrator.go
+++ b/internal/orchestrate/orchestrator.go
@@ -384,11 +384,11 @@ func (o *Orchestrator) calculateVersion() (string, error) {
//
// 1. Multi-env intermediate: next env's state SHA (e.g., dev's changelog
// is "what's new vs test"). Preserves the existing per-env progression
-// model — the changelog shows what's about to be promoted forward.
+// model. The changelog shows what's about to be promoted forward.
// 2. Last published release: state["release"].SHA, falling back to
// latest_release.SHA. This is the right base whenever there's no
-// "next env" to compare against — i.e., no-env library/CLI projects
-// after their first publish, OR the terminal env in a multi-env list.
+// "next env" to compare against (i.e., no-env library/CLI projects
+// after their first publish, OR the terminal env in a multi-env list).
// Without this, a freshly-published-then-orchestrated repo would
// compute a changelog covering its entire git history (cf. #80).
// 3. Initial commit: only when nothing has been released yet (truly the
diff --git a/internal/orchestrate/orchestrator_test.go b/internal/orchestrate/orchestrator_test.go
index 855952bb..e6dad8c2 100644
--- a/internal/orchestrate/orchestrator_test.go
+++ b/internal/orchestrate/orchestrator_test.go
@@ -291,7 +291,7 @@ func TestNewOrchestratorInvalidYAML(t *testing.T) {
// 2. No-env or last env → state["release"] (or latest_release)
// 3. Nothing released → initial commit (git fallback, not exercised here)
//
-// The git fallback isn't exercised — it requires a real repo and the rest of
+// The git fallback isn't exercised; it requires a real repo and the rest of
// the priority ladder is what we're really fixing.
func TestCalculateChangelogRefs(t *testing.T) {
tests := []struct {
diff --git a/internal/promote/auto_commit_sha_test.go b/internal/promote/auto_commit_sha_test.go
index fd3646b1..90a2d422 100644
--- a/internal/promote/auto_commit_sha_test.go
+++ b/internal/promote/auto_commit_sha_test.go
@@ -98,7 +98,7 @@ func TestFinalizer_NoAutoCommits_KeepsPromotionSHA(t *testing.T) {
Version: "v1.0.0-2",
}},
})
- // No SetHeadSHA call — standard path.
+ // No SetHeadSHA call; standard path.
require.NoError(t, fin.Run())
@@ -144,7 +144,7 @@ func TestFinalizer_AutoCommits_EmptySHA_FallsBackToPromotion(t *testing.T) {
Version: "v1.0.0-3",
}},
})
- fin.SetHeadSHA("") // explicit empty — must behave like unset
+ fin.SetHeadSHA("") // explicit empty: must behave like unset
require.NoError(t, fin.Run())
diff --git a/internal/promote/finalize.go b/internal/promote/finalize.go
index bf0a0682..e62e7370 100644
--- a/internal/promote/finalize.go
+++ b/internal/promote/finalize.go
@@ -155,7 +155,7 @@ func (f *Finalizer) updateState() {
for _, promo := range f.promotionResult.Promotions {
if promo.Environment == "" || promo.Environment == "release" {
- // Skip the release marker — it tracks publish state, not deploys.
+ // Skip the release marker; it tracks publish state, not deploys.
continue
}
if promo.SHA == "" {
diff --git a/internal/promote/preflight.go b/internal/promote/preflight.go
index 5f33c509..fb96b8a0 100644
--- a/internal/promote/preflight.go
+++ b/internal/promote/preflight.go
@@ -260,9 +260,9 @@ func (p *Preflighter) detectDeployChanges(sourceSHA, targetEnv string) ([]string
// Get target deploy state. Per-deploy state is the most precise
// comparison point ("when was this specific deploy last run for this
- // env"). When unavailable — typical for first-promotion scenarios
+ // env"). When unavailable (typical for first-promotion scenarios
// where the env has been promoted to some SHA but no deploy has run
- // yet — fall back to the env-level SHA. That captures the semantic
+ // yet), fall back to the env-level SHA. That captures the semantic
// "if the env is at SHA X, treat the deploy as having seen X" so
// trigger filters still apply rather than unconditionally including.
var targetSHA string
@@ -276,7 +276,7 @@ func (p *Preflighter) detectDeployChanges(sourceSHA, targetEnv string) ([]string
}
// If neither per-deploy nor env-level state exists, include
- // unconditionally — this is a never-deployed env.
+ // unconditionally: this is a never-deployed env.
if targetSHA == "" {
localDeploys = append(localDeploys, d.Name)
continue
@@ -493,7 +493,7 @@ func (p *Preflighter) getEnvCurrentSHA(env string) string {
// (baseSHA, headSHA] is a breaking change per the conventional-commit spec
// (`feat!:`, `fix!:`, or any commit body containing `BREAKING CHANGE:`).
//
-// On any failure to read git history, returns false (fail-open) — the gate
+// On any failure to read git history, returns false (fail-open). The gate
// is a guardrail, not a hard correctness check, and surfacing parse errors
// at preflight time would make the CLI brittle in environments with shallow
// or partially fetched histories.
diff --git a/internal/promote/promote.go b/internal/promote/promote.go
index 87e46e3d..a2b954ea 100644
--- a/internal/promote/promote.go
+++ b/internal/promote/promote.go
@@ -134,7 +134,7 @@ func (p *Promoter) defaultPromotion() (*PromotionResult, error) {
return p.noEnvironmentPromotion()
}
- // Snapshot current state — include "release" since it's a virtual env
+ // Snapshot current state; include "release" since it's a virtual env
// in the chain but isn't necessarily in the env list.
preState := make(map[string]*config.EnvState)
for _, env := range envs {
@@ -153,7 +153,7 @@ func (p *Promoter) defaultPromotion() (*PromotionResult, error) {
var skippedEnvs []string
// Determine prerelease and prod positions. The implicit chain is
- // [envs..., "release", prodEnv] — "release" is a virtual env between
+ // [envs..., "release", prodEnv]. "release" is a virtual env between
// the prerelease env and prod where the publish action lands. Each
// `mode: default` invocation advances state through the chain by one
// logical step, stopping at the publish boundary.
@@ -174,14 +174,14 @@ func (p *Promoter) defaultPromotion() (*PromotionResult, error) {
}
// Sequential walk through env pairs. Stops at the publish boundary
- // (sourceEnv == prereleaseEnv && targetEnv == prodEnv) — that crossing
+ // (sourceEnv == prereleaseEnv && targetEnv == prodEnv): that crossing
// produces a "release" promotion that advances state[release] only;
// state[prodEnv] is advanced by a subsequent default-mode invocation.
for i := 0; i < len(envs)-1; i++ {
sourceEnv := envs[i]
targetEnv := envs[i+1]
- // Skip "release" as a target if it's literally in the env list — the
+ // Skip "release" as a target if it's literally in the env list. The
// release marker is advanced via the publish path, not as a normal env.
if targetEnv == "release" {
continue
@@ -208,7 +208,7 @@ func (p *Promoter) defaultPromotion() (*PromotionResult, error) {
// (state marker + publish action). State[prodEnv] is left untouched;
// a subsequent default-mode invocation handles release→prod.
if sourceEnv == prereleaseEnv && targetEnv == prodEnv {
- // Skip if state[release] is already at sourceState.SHA — the
+ // Skip if state[release] is already at sourceState.SHA. The
// publish was completed by a prior invocation. Without this, a
// repeat default-mode call would re-emit a no-op publish promo
// instead of falling through to the release→prod fallback below.
@@ -315,7 +315,7 @@ func (p *Promoter) defaultPromotion() (*PromotionResult, error) {
// Fallback: if the env walk produced nothing AND state[release] is ahead
// of state[prodEnv], deploy release → prod. This is the second step of
- // the two-step "publish then deploy" flow — runs only when no upstream
+ // the two-step "publish then deploy" flow. Runs only when no upstream
// advance is pending. Order matters: if uat is ahead of release, we
// publish first; if release is ahead of prod and there's no upstream
// work, we deploy. (When "release" is in the env list literally, the
@@ -533,7 +533,7 @@ func (p *Promoter) cascadePromotion(target string) (*PromotionResult, error) {
// Build promotions for envs[sourceIdx+1..targetIdx]. Materialize "release"
// as its own promotion either when it's in the env list or when crossing
// into prodEnv from prereleaseEnv (publish boundary). The release
- // promotion advances state[release] only — no deploy.
+ // promotion advances state[release] only; no deploy.
var promotions []EnvPromotion
for i := sourceIdx + 1; i <= targetIdx; i++ {
env := envs[i]
diff --git a/mkdocs.yml b/mkdocs.yml
index fda052ad..f0ef02f0 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,6 +1,6 @@
site_name: Cascade
site_description: >-
- Manifest-driven release orchestration for GitHub Actions — coordinate
+ Manifest-driven release orchestration for GitHub Actions: coordinate
builds, promotions, and releases across any environment topology.
site_url: https://stablekernel.github.io/cascade/
repo_url: https://github.com/stablekernel/cascade
@@ -61,6 +61,7 @@ markdown_extensions:
permalink: true
nav:
+ - Home: index.md
- Getting Started: getting-started.md
- Manifest Reference: configuration.md
- Callback Contract: callback-contract.md