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.
| Step | Command |
|---|---|
| open the branch | sklp flow start <name> |
| work, commit | git commit |
| push and open the PR | sklp flow end |
| CI | runs on the PR, not on your machine |
| merge | gh pr merge <n> on the remote, then git pull on main |
| publish | sklp run publish from a clean main |
| rollout | Image 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| Flag | Effect |
|---|---|
--onto <ref> | base the diff and the PR target on this ref (default main) |
--no-test | print the changed files and the impacted scope, stop |
--local | run 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.branchessays where a push may start it; a pipeline that opts out of the current branch is skipped.require.branchessays 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 --localsays so.
Things to know
flow endpushes once. Commits made after it are not on the PR until you runsklp flow endagain (orgit push). Checkgit log origin/<branch>..HEADbefore merging.- It never merges. Opening the PR is the end of the command.
- Nothing changed means nothing pushed. An empty diff vs
--ontoexits 0 without a PR. --localnever pushes, even when every pipeline passes.- Open PRs with
sklp flow end, notgh 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 pullNever 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 diffTags 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.