Change lifecycle

The lifecycle

Every feature, fix or refactor goes through the same path: a branch, a pull request, CI on that pull request, a merge on GitHub, then a publish from a clean main. sklp flow covers the first half; publish and rollout cover the rest.

StepCommand
open the branchsklp flow start <name>
work, commitgit commit
push and open the PRsklp flow end
CIruns on the PR, not on your machine
mergegh pr merge <n> on the remote, then git pull on main
publishsklp run publish from a clean main
rolloutImage Updater / the node picks up the new tag

Start a feature

sklp flow start <name> creates the branch feat/<name> from main. By default it gets its own worktree, a sibling of the repo named <repo>-worktrees/feat-<name>, so the main checkout stays untouched.

sklp flow start login               # worktree ../skalpai-worktrees/feat-login
sklp flow start login --branch      # checkout -b in place, no worktree
sklp flow start hotfix --onto release/1.4   # branch off another ref

--worktree and --branch are mutually exclusive. The command fails if the worktree path already exists. A fresh worktree of this repo needs go generate ./apps/sklp/space/embed/ once before any apps/sklp build. Reference: sklp flow start.

End a feature

sklp flow end diffs the current branch against main (three-dot: only what the branch changed since it diverged), pushes it with git push -u origin, and opens a pull request through the GitHub API. If a PR already exists for the branch, the push updated it and the command prints its URL.

CI is not run locally: the PR webhook dispatches it, and running it on the laptop too would do the same work twice on the same commit.

sklp flow end                    # diff, push, open or update the PR
sklp flow end --no-test          # show the diff and the selected pipelines, do nothing
sklp flow end --local            # run the selected pipelines here, push nothing
sklp flow end --local --pipeline ci-sklp   # force one pipeline
sklp flow end --onto release/1.4 # diff and PR against another base
FlagEffect
--onto <ref>base the diff and the PR target on this ref (default main)
--no-testprint the changed files and the impacted scope, stop
--localrun the pipelines on this machine; never pushes
--pipeline <name>with --local: run .sklp/pipelines/<name>.yaml instead of auto-selecting

With --local, every selected pipeline runs (no fail-fast) and the summary lists each as passed, failed or skipped. The PR title is the last commit's subject. Reference: sklp flow end.

Pipeline selection

Pipelines live in .sklp/pipelines/*.yaml. A pipeline is selected when one of its scopes: globs matches a changed file:

on:
  branches: ['**', '!main']
 
scopes:
  sklp:
    - "apps/sklp/**"
    - "go.work"
  • A pipeline without scopes: is never auto-selected.
  • on.branches says where a push may start it; a pipeline that opts out of the current branch is skipped.
  • require.branches says where it may run at all; a pipeline that would be refused (a main-only publish on a feature branch) is skipped, not failed.
  • No match means nothing to run, and flow end --local says so.

Things to know

  • flow end pushes once. Commits made after it are not on the PR until you run sklp flow end again (or git push). Check git log origin/<branch>..HEAD before merging.
  • It never merges. Opening the PR is the end of the command.
  • Nothing changed means nothing pushed. An empty diff vs --onto exits 0 without a PR.
  • --local never pushes, even when every pipeline passes.
  • Open PRs with sklp flow end, not gh pr create.

Merge and sync

Merging is a remote operation on GitHub, never a local git merge into main. Then bring local main up to date:

gh pr merge 1432 --merge
git checkout main && git pull

Never push origin/main directly: changes reach it through a PR.

Publish and rollout

Images are published only from a clean local main, and only for the services the diff touched. The publish pipeline is .sklp/pipelines/publish.yaml.

sklp run publish -resolve-only   # list what would ship
sklp run publish                 # build, scan, push impacted images
sklp run publish -only web,core  # restrict to named scopes
sklp run publish -force          # every service regardless of the diff

Tags follow YYYYMMDD-HHMM-<short-sha>, for example 20261026-0912-4b365812. Pushing an image is not a deployment: Image Updater (Kubernetes) or the node's image watch rolls the new tag out, and a change counts as deployed only once the new version serves traffic.