Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 4 additions & 16 deletions .github/workflows/branch-policy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ name: Enforce branch flow
on:
pull_request:
branches:
- stable
- main
- 'main/**'
- master

jobs:
check-branch-policy:
Expand All @@ -17,19 +15,9 @@ jobs:

echo "PR: $SOURCE → $TARGET"

# stable ← main, main/*, hotfix/*
# main ← develop, hotfix/*
# main/* ← develop, hotfix/*
if [[ "$TARGET" == "stable" ]]; then
PATTERN="^(main(/.*)?|hotfix/.+)$"
ALLOWED="main, main/*, hotfix/*"
elif [[ "$TARGET" == "main" || "$TARGET" == main/* ]]; then
PATTERN="^(develop|hotfix/.+)$"
ALLOWED="develop, hotfix/*"
else
echo "✅ No branch policy for target '$TARGET'"
exit 0
fi
# master ← develop, hotfix/*
PATTERN="^(develop|hotfix/.+)$"
ALLOWED="develop, hotfix/*"

if [[ "$SOURCE" =~ $PATTERN ]]; then
echo "✅ '$SOURCE' → '$TARGET' is allowed"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
name: Bump version on PR to stable
name: Bump version on PR to master

on:
pull_request:
types: [opened, synchronize]
branches: [stable]
branches: [master]

jobs:
bump-version:
# Only run for main/* branches
if: startsWith(github.head_ref, 'main/')
# Only run for the develop → master release train.
# Hotfix branches bump their version manually before opening the PR.
if: github.head_ref == 'develop'
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -23,20 +24,21 @@ jobs:
- name: Read versions and compare
id: compare
run: |
# Read version from source branch (main/*)
# Read version from source branch (develop)
SOURCE_VERSION=$(grep -m1 '^version' pyproject.toml | sed 's/.*"\(.*\)"/\1/')
echo "source=$SOURCE_VERSION" >> "$GITHUB_OUTPUT"

# Read version from stable
git fetch origin stable
STABLE_VERSION=$(git show origin/stable:pyproject.toml | grep -m1 '^version' | sed 's/.*"\(.*\)"/\1/')
echo "stable=$STABLE_VERSION" >> "$GITHUB_OUTPUT"
# Read version from master
git fetch origin master
MASTER_VERSION=$(git show origin/master:pyproject.toml | grep -m1 '^version' | sed 's/.*"\(.*\)"/\1/')
echo "master=$MASTER_VERSION" >> "$GITHUB_OUTPUT"

echo "Source: $SOURCE_VERSION | Stable: $STABLE_VERSION"
echo "Source: $SOURCE_VERSION | Master: $MASTER_VERSION"

# Compare using sort -V (version sort)
HIGHER=$(printf '%s\n%s' "$SOURCE_VERSION" "$STABLE_VERSION" | sort -V | tail -1)
if [ "$SOURCE_VERSION" != "$STABLE_VERSION" ] && [ "$SOURCE_VERSION" = "$HIGHER" ]; then
# Compare using sort -V (version sort). If develop is already ahead
# (e.g. a manual minor/major bump), no patch bump is needed.
HIGHER=$(printf '%s\n%s' "$SOURCE_VERSION" "$MASTER_VERSION" | sort -V | tail -1)
if [ "$SOURCE_VERSION" != "$MASTER_VERSION" ] && [ "$SOURCE_VERSION" = "$HIGHER" ]; then
echo "needs_bump=false" >> "$GITHUB_OUTPUT"
echo "Source is already ahead, no bump needed"
else
Expand All @@ -48,7 +50,7 @@ jobs:
if: steps.compare.outputs.needs_bump == 'true'
id: next
run: |
IFS='.' read -r MAJOR MINOR PATCH <<< "${{ steps.compare.outputs.stable }}"
IFS='.' read -r MAJOR MINOR PATCH <<< "${{ steps.compare.outputs.master }}"
PATCH=$((PATCH + 1))
echo "version=$MAJOR.$MINOR.$PATCH" >> "$GITHUB_OUTPUT"
echo "Next version: $MAJOR.$MINOR.$PATCH"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-lint-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name: PR Lint & Test

on:
pull_request:
branches: [develop, stable, main, 'main/**']
branches: [develop, master]
workflow_dispatch:

jobs:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: Release on merge to stable
name: Release on merge to master

on:
pull_request:
types: [closed]
branches: [stable]
branches: [master]

jobs:
release:
Expand All @@ -13,10 +13,10 @@ jobs:
contents: write

steps:
- name: Checkout stable
- name: Checkout master
uses: actions/checkout@v4
with:
ref: stable
ref: master
fetch-depth: 0

- name: Read version from pyproject.toml
Expand All @@ -37,4 +37,4 @@ jobs:
fi
git tag "$TAG"
git push origin "$TAG"
gh release create "$TAG" --title "$TAG" --generate-notes --target stable
gh release create "$TAG" --title "$TAG" --generate-notes --target master
70 changes: 33 additions & 37 deletions docs/branching-strategy.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,64 @@
# Branching Strategy

Dana Runtime uses a **Gitflow-based vertical branching** model with long-lived branches and short-lived branch types.
Dana Runtime uses a **simplified two-branch model**: one integration branch and one release branch.

![Branching Diagram](assets/gitflow-vertical-branching.svg)
```
feature/* ──PR──> develop ──PR──> master
|
hotfix/* ────┘ (branched from master; merged back to master + develop)
```

## Long-lived Branches

| Branch | Purpose | Accepts PRs from |
|-----------|--------------------------------------------------------------------------|----------------------|
| `stable` | Production-ready code. Every commit is tagged with a release version. | `main/*`, `hotfix/*` |
| `develop` | Integration branch. All feature work merges here first. | `feature/*` |

## Release Branches (`main/*`)

Each release gets its own branch under the `main/` prefix.

- Naming: `main/<version>` (e.g. `main/1.0`, `main/2.0`)
- Created from `develop` when a release is ready for stabilization
- **Only bugfixes** are allowed on release branches
- Bugfixes on `main/*` merge back into `develop` to stay in sync
- Once stable, merges into `stable` and a version tag is created
- Accepts PRs from: `develop`, `hotfix/*`
| Branch | Purpose | Accepts PRs from |
|-----------|----------------------------------------------------------------------|----------------------|
| `develop` | Integration branch. All feature work merges here first. **Default branch.** | `feature/*` |
| `master` | Release branch. Every merge produces a version tag + GitHub Release. | `develop`, `hotfix/*` |

## Short-lived Branches

### Feature branches (`feature/*`)

- Branch from `develop`
- Merge back into `develop` via PR
- Naming: `feature/<descriptive-slug>` (e.g. `feature/login`, `feature/timeline-compression`)
- Naming: `feature/<descriptive-slug>` (e.g. `feature/timeline-compression`)
- Delete after merge

### Hotfix branches (`hotfix/*`)

- Branch from `stable` for critical production bugs
- Merge into **both** `stable` and `develop` (to keep develop in sync)
- Naming: `hotfix/<version-or-slug>` (e.g. `hotfix/0.1.1`, `hotfix/fix-crash`)
- A new tag is created on `stable` after merge
- Branch from `master` for critical production bugs
- Merge into **both** `master` and `develop` (keep develop in sync)
- Bump the version on the hotfix branch manually before merging into `master`
- Naming: `hotfix/<version-or-slug>` (e.g. `hotfix/fix-crash`)
- Delete after merge

## Release Flow

1. `develop` accumulates features via merged feature branches
2. When ready for release, create `main/<version>` from `develop`
3. Only bugfixes are committed on `main/<version>` during stabilization
4. Bugfixes on `main/<version>` merge back into `develop` to stay in sync
5. Once stable, `main/<version>` merges into `stable` and a version tag is created
6. `main/<version>` also merges back into `develop` to include final bugfixes
2. When ready for release, set the target version in `pyproject.toml` on `develop`
3. Open a PR `develop → master`. CI auto-bumps the patch version if `develop` is not already ahead
4. Merge the PR → [`release-on-merge-to-master`](../.github/workflows/release-on-merge-to-master.yml) tags `v<version>` and creates a GitHub Release

## Branch Protection
## Versioning

Enforced by [`.github/workflows/branch-policy.yml`](../.github/workflows/branch-policy.yml):
- Source of truth: `version` in [`pyproject.toml`](../pyproject.toml)
- To ship a **minor/major** bump (e.g. `0.2.0`, `1.0.0`), set it on `develop` before opening the release PR — the auto-bumper detects `develop` is already ahead and skips
- Otherwise the auto-bumper raises the patch component (`0.1.3` → `0.1.4`)
- Tags follow `v<version>` (e.g. `v0.2.0`)

## Branch Protection

- **`stable`** only accepts PRs from `main/*` or `hotfix/*`
- **`main/*`** only accepts PRs from `develop` or `hotfix/*`
Enforced by [`.github/workflows/branch-policy.yml`](../.github/workflows/branch-policy.yml) plus repository rulesets:

Any PR violating these rules is automatically rejected by CI.
- **`master`** only accepts PRs from `develop` or `hotfix/*`
- Direct pushes to `master` are blocked (no non-fast-forward, no deletion); merges require review + signatures
- Any PR violating the source-branch rule is rejected by the `check-branch-policy` job

## Quick Reference

```text
feature/* ──PR──> develop ──PR──> main/<version> ──PR──> stable
^ |
| hotfix/* ────────PR──> stable
+----------- hotfix/* (also merged back)
+----------- main/<version> bugfixes (merged back)
feature/* ──PR──> develop ──PR──> master ──> tag vX.Y.Z + GitHub Release
^ |
| hotfix/* ──┘ (also merged back to develop)
+-------------------+
```
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "dana"
version = "0.1.3"
version = "0.2.0"
description = "Dana Agent - Domain-Aware Neurosymbolic Agents"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
Loading