From 33e5c2d54eefd49137b7780716d409c3a2a5a02d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:39:44 +0000 Subject: [PATCH 1/2] ci(branching): adopt develop as the integration branch Development merges into `develop`; `main` carries releases only and receives `release/x.y.z` and `hotfix/` pull requests. CircleCI's build-test workflow now runs on merges to both branches (the release workflow stays on tags), `make proto-breaking` compares against `origin/develop` by default, and CONTRIBUTING.md, AGENTS.md and docs/project/releasing.md describe the branch model and the release branch flow. Co-Authored-By: jason.han --- .circleci/config.yml | 26 +++++----- .github/workflows/pr.yml | 4 +- AGENTS.md | 4 +- CONTRIBUTING.md | 27 ++++++++--- Makefile | 6 +-- README.md | 2 +- .../unreleased/git-flow-branches.changed.md | 4 ++ docs/project/releasing.md | 47 ++++++++++++++++--- 8 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 changes/unreleased/git-flow-branches.changed.md diff --git a/.circleci/config.yml b/.circleci/config.yml index 3219084e3..f8c5dff14 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -191,8 +191,8 @@ jobs: name: Lint the protobuf schema command: make proto-lint - # Baseline is an ancestor of HEAD, never live `origin/main` (a merge landing meanwhile - # would read as deletions): the first parent on main, the previous release on a `v*` tag. + # Baseline is an ancestor of HEAD, never a live branch tip (a merge landing meanwhile would + # read as deletions): the first parent on main or develop, the previous release on a `v*` tag. - run: name: Check the protobuf schema for breaking changes command: | @@ -1510,26 +1510,26 @@ jobs: workflows: version: 2 - # Build and test every merge into main; pull requests run .github/workflows/pr.yml instead. + # Build and test every merge into main or develop; pull requests run .github/workflows/pr.yml instead. # The Go suite is the four `Go …` jobs, and everything downstream requires all of them. build-test: jobs: - go-static: name: Go static checks - filters: &main-only + filters: &integration-branches branches: - only: main + only: [main, develop] tags: ignore: /.*/ - go-race-test: name: Go race tests - filters: *main-only + filters: *integration-branches - go-coverage: name: Go coverage profile - filters: *main-only + filters: *integration-branches - go-gates: name: Go gates and binaries - filters: *main-only + filters: *integration-branches - python-test: name: Python client tests requires: &go-suite @@ -1537,19 +1537,19 @@ workflows: - Go race tests - Go coverage profile - Go gates and binaries - filters: *main-only + filters: *integration-branches - rust-test: name: Rust client tests requires: *go-suite - filters: *main-only + filters: *integration-branches - java-test: name: Java client tests requires: *go-suite - filters: *main-only + filters: *integration-branches - node-test: name: Node client tests requires: *go-suite - filters: *main-only + filters: *integration-branches - scan: name: SonarCloud scan # Matched exactly, like PyPI below; this context holds SONAR_TOKEN. @@ -1566,7 +1566,7 @@ workflows: - Python client tests - Java client tests - Node client tests - filters: *main-only + filters: *integration-branches # Build and release on tags. The suite runs here too: a tag can point at any # commit, so a release is only published from a revision proven green. diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a5185c4b1..c3682d53e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,7 +1,7 @@ name: PR -# The only CI a pull request runs. Pushes to main and tags are covered by the -# CircleCI config; this workflow mirrors its build-test workflow, not release. +# The only CI a pull request runs. Pushes to main and develop, and tags, are covered +# by the CircleCI config; this workflow mirrors its build-test workflow, not release. on: pull_request: diff --git a/AGENTS.md b/AGENTS.md index 851b132b5..862536c4d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,7 +13,7 @@ It provides a hand-written lexer/parser, semantic engine, execution runtime, LSP 1. **Correctness over expedience.** No shortcuts, no stubs left behind, no lossy conversions. If a proper fix is large, do it properly or stop and flag it. - **For features specifically: do not minimize code changes or dodge complexity.** Implement the feature fully and correctly even if it touches many files, adds new types, or requires refactoring. Completeness beats diff size. See §8. 2. **Root-cause first.** Before editing, confirm *why* something fails (read the code, add a temporary debug print, write a focused test). Then make the minimal correct change. -3. **Never regress.** `main` is green. Any test passing on `main` must still pass on your branch. Diff against `main` if unsure: `git stash && git checkout main && go test ./... ; git checkout - && git stash pop`. +3. **Never regress.** `develop` is green. Any test passing on `develop` must still pass on your branch. Diff against `develop` if unsure: `git stash && git checkout develop && go test ./... ; git checkout - && git stash pop`. 4. **Respect the architecture invariants** (see §4). The AST is immutable; semantics live in side tables; execution consumes lowered IR — do not bypass these. 5. **Tests are the contract.** Existing tests encode intended behavior (including *when* and *where* errors surface). Make code satisfy tests, not the reverse — unless the test is provably wrong, in which case explain before changing it. 6. **Leave no dead code.** Remove superseded helpers/structs. Run `go vet ./...` to catch it. @@ -139,7 +139,7 @@ Then update `docs/project/spec-compliance.md` mapping: semantic rule → impleme ## 6. Development Workflow -1. **Understand first.** Grep/read the relevant package and its tests. Diff the branch against `main` to see what changed and why. +1. **Understand first.** Grep/read the relevant package and its tests. Diff the branch against `develop` to see what changed and why. 2. **Reproduce.** Run the failing test(s) and read the exact error before changing anything. 3. **Locate the root cause** in the correct layer (lexer vs parser vs lower vs runtime). Bugs in specialized layers are often upstream of where they surface. 4. **Implement the correct fix.** For bug fixes, keep edits minimal and scoped. For features, implement completely (see §8) — "minimal" means *no unrelated changes*, never *under-built*. Match existing style; keep imports at the top. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5266506c0..4af4ef510 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,7 +55,7 @@ make proto # regenerate the Go, Java and Python stubs make proto-buf # Go and Java stubs only make python-proto # Python stubs only (needs grpcio-tools; PYTHON=... picks the interpreter) make proto-lint # lint the schema, as CI does -make proto-breaking # reject wire-breaking changes against main, as CI does +make proto-breaking # reject wire-breaking changes against develop, as CI does # Python gRPC bindings make python-install # install opensysml package @@ -144,14 +144,26 @@ docs: update Quick Start guide with new commands test(semantics): add conformance checking test cases ``` +## Branches + +The repository follows git-flow with two long-lived branches: + +- `develop` is the integration branch and the default branch. Every `feature/`, `fix/`, `docs/`, + `ci/` and `test/` branch is cut from `develop`, and its pull request targets `develop`. +- `main` is the release branch. It receives only `release/x.y.z` pull requests (cut from + `develop`, with the changelog fragments folded in) and `hotfix/` pull requests. Release tags — + `v*` and the client package tags — are created on `main`. After a release is tagged, `main` + is merged back into `develop` (a plain merge, no rebase) so hotfixes and the folded changelog + flow down. See [docs/project/releasing.md](docs/project/releasing.md). + ## Pull Request Process 1. **Fork** the repository -2. **Create a branch** from `main`: `git checkout -b feat/my-feature` +2. **Create a branch** from `develop`: `git checkout -b feat/my-feature` 3. **Make changes** with clear commit messages 4. **Run tests**: `go test ./...` 5. **Push** to your fork -6. **Open a Pull Request** targeting `main` +6. **Open a Pull Request** targeting `develop` ### PR Guidelines @@ -168,14 +180,17 @@ test(semantics): add conformance checking test cases ### Creating a Release 1. **Update version** (if needed in code) -2. **Tag the release:** +2. **Merge the `release/x.y.z` pull request** into `main` (see + [docs/project/releasing.md](docs/project/releasing.md)) +3. **Tag the release** on `main`: ```bash git tag -a v0.1.0 -m "Release v0.1.0: Initial public release" git push origin v0.1.0 ``` -3. **CI automatically:** +4. **CI automatically:** - Builds binaries for all platforms - Publishes to GitHub Releases +5. **Merge `main` back into `develop`** ### Release Checklist @@ -243,7 +258,7 @@ is over-tested rather than untested — teach the script about it, and add a cas `.circleci/config.yml` runs after a merge and on tags, never on a pull request branch: -**On every push to `main`:** +**On every push to `main` or `develop`:** - The same suite, gates, client tests and conformance runs as the pull-request workflow, over the merged tree, plus the host binaries - SonarCloud scan, fed by the Go and client coverage reports diff --git a/Makefile b/Makefile index f464d4204..930df65ca 100644 --- a/Makefile +++ b/Makefile @@ -20,8 +20,8 @@ GO_WINRES_VERSION := v0.3.3 # buf drives all protobuf codegen; override BUF to use an already-installed binary. BUF ?= go run github.com/bufbuild/buf/cmd/buf@$(BUF_VERSION) -# Wire-compatibility baseline: the schema as it stands on the main branch. -BUF_BREAKING_REF ?= origin/main +# Wire-compatibility baseline: the schema as it stands on the develop branch. +BUF_BREAKING_REF ?= origin/develop # go-winres embeds a VERSIONINFO resource into the Windows binaries (a build # tool only; nothing of it ships). The .syso it writes carries a _windows_amd64 @@ -253,7 +253,7 @@ proto-lint: ## Lint the protobuf schema $(BUF) lint @echo "✓ Proto lint passed" -proto-breaking: ## Check the protobuf schema for wire-breaking changes against main +proto-breaking: ## Check the protobuf schema for wire-breaking changes against develop @# An archive, not the .git directory: buf would clone that, which a blobless (CI) checkout cannot serve. baseline=$$(mktemp -t proto-baseline.XXXXXX) && trap 'rm -f "$$baseline"' EXIT && \ git archive --format=tar -o "$$baseline" '$(BUF_BREAKING_REF)' api/proto && \ diff --git a/README.md b/README.md index 6c88ccc9e..eccf6c633 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,7 @@ github.com/Open-MBEE/OpenSysML - **Grammar source:** OMG pilot Xtext grammars (`SysML.xtext` and `KerMLExpressions`) - **Spec compliance:** [OMG SysML v2.1 Beta 1 / KerML 1.1](https://www.omg.org/spec/SysML/2.0) (2026-07 release) - **Standard library:** 94 files from [SysML v2 Pilot Implementation 2026-07](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/releases/tag/2026-07), byte-identical, plus the non-normative `OpenSysML Libraries/OpenSysMLMathFunctions.kerml` extension -- **CI/CD:** GitHub Actions checks pull requests; CircleCI builds and tests `main` and publishes releases from tags +- **CI/CD:** GitHub Actions checks pull requests; CircleCI builds and tests `main` and `develop` and publishes releases from tags ## Releases diff --git a/changes/unreleased/git-flow-branches.changed.md b/changes/unreleased/git-flow-branches.changed.md new file mode 100644 index 000000000..df4a7adcf --- /dev/null +++ b/changes/unreleased/git-flow-branches.changed.md @@ -0,0 +1,4 @@ +- Development moved to a `develop` integration branch; `main` now carries releases only. + Feature and fix pull requests target `develop`, releases reach `main` through + `release/x.y.z` pull requests and are tagged there, and CircleCI builds and tests both + branches. `make proto-breaking` compares against `origin/develop` by default. diff --git a/docs/project/releasing.md b/docs/project/releasing.md index 71b366c61..a7c2f804d 100644 --- a/docs/project/releasing.md +++ b/docs/project/releasing.md @@ -72,6 +72,42 @@ Then check the release-facing text: and no compliance row claims more than the implementation does. Count first-level subtests: a case that registers sub-subtests, like `variant_connection_per_owner`, otherwise counts twice. +## The release branch + +Day-to-day work merges into `develop`; `main` carries releases only (see +[CONTRIBUTING.md § Branches](../../CONTRIBUTING.md#branches)). A release is a +branch that moves the integration state onto `main`: + +1. Cut `release/x.y.z` from `develop`: + + ```bash + git checkout develop && git pull + git checkout -b release/0.0.5 + ``` + +2. Fold the changelog fragments on that branch — `python3 scripts/changelog.py release 0.0.5`, + as [Before tagging](#before-tagging) describes — and commit `CHANGELOG.md` together with the + deleted fragments. Anything else the release needs (a version string in code, a doc that + names the version) lands here too; a feature does not. + +3. Open a pull request from `release/x.y.z` to `main` and merge it once the + pull-request workflow is green. Merging into `main` runs CircleCI's + `build-test` workflow over the merged tree. + +4. Tag `main` as [Tagging](#tagging) describes. + +5. Merge `main` back into `develop` — a plain merge, no rebase — so the folded + changelog, and any hotfix that landed on `main` in the meantime, flow down: + + ```bash + git checkout develop && git pull + git merge main + git push origin develop + ``` + +A `hotfix/` branch follows the same path from `main`: cut from `main`, pull +request to `main`, tag, merge back into `develop`. + ## Tagging The tag is the version: CircleCI passes `CIRCLE_TAG` to the build as @@ -83,12 +119,11 @@ git tag -a v0.0.5 -m "v0.0.5" git push origin v0.0.5 ``` -The tag belongs on the repository the releases live on. v0.0.1–v0.0.7 are -releases of `Open-MBEE/OpenSysML`, while development happens on -`JPL-Devin/OpenSysML`, which has no tags at all — so cutting a release means -promoting `main` upstream first (v0.0.4 came through Open-MBEE PR #47) and -tagging there. Tagging the development repository would build a release nobody -consumes. +The tag belongs on `Open-MBEE/OpenSysML`, the repository the releases live on +and where development happens: every release from v0.0.1 on is tagged on its +`main`. The clients resolve releases from that repository +(`DEFAULT_GITHUB_REPO` in `clients/python/opensysml/binary.py`), so a tag pushed +to a fork builds a release nobody consumes. Tags are matched by `/^v.*/` in `.circleci/config.yml`. A tag on a commit that fails the suite fails the release workflow before anything is published. From f879553f2aa6ce5c70c915082ff696715c2b144f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 18:45:15 +0000 Subject: [PATCH 2/2] docs(releasing): compare release and hotfix branches against origin/main Co-Authored-By: jason.han --- CONTRIBUTING.md | 3 ++- docs/project/releasing.md | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4af4ef510..8686086e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -215,7 +215,8 @@ A release is a **PATCH** when everything the previous release accepted still beh result into the one the Kernel Semantic Library derives; that is compatible and is listed under *Fixed*. - No CLI flag, REPL command, RPC or wire field is removed or renamed. The protobuf - wire-compatibility check, `make proto-breaking`, passes. + wire-compatibility check passes against the previous release's schema: + `make proto-breaking BUF_BREAKING_REF=origin/main` on the release branch. New features, new flags, new capabilities and new wire fields are all patch material. diff --git a/docs/project/releasing.md b/docs/project/releasing.md index a7c2f804d..f1d091442 100644 --- a/docs/project/releasing.md +++ b/docs/project/releasing.md @@ -88,7 +88,11 @@ branch that moves the integration state onto `main`: 2. Fold the changelog fragments on that branch — `python3 scripts/changelog.py release 0.0.5`, as [Before tagging](#before-tagging) describes — and commit `CHANGELOG.md` together with the deleted fragments. Anything else the release needs (a version string in code, a doc that - names the version) lands here too; a feature does not. + names the version) lands here too; a feature does not. Check the wire compatibility + against the released schema, not the branch's own source: + `make proto-breaking BUF_BREAKING_REF=origin/main` (the default baseline is + `origin/develop`; the pull-request workflow uses the base branch, so the PR to `main` + makes the same comparison). 3. Open a pull request from `release/x.y.z` to `main` and merge it once the pull-request workflow is green. Merging into `main` runs CircleCI's @@ -106,7 +110,8 @@ branch that moves the integration state onto `main`: ``` A `hotfix/` branch follows the same path from `main`: cut from `main`, pull -request to `main`, tag, merge back into `develop`. +request to `main` (`make proto-breaking BUF_BREAKING_REF=origin/main` locally, as above), +tag, merge back into `develop`. ## Tagging