Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c318dcb
chore(workspace): propagate unified make and release automation
Feb 20, 2026
2b3b507
feat(pr): add unified make pr workflow manager
Feb 20, 2026
cf58975
chore(workspace): update propagated project pointers
Feb 20, 2026
d616bbb
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
a8de292
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
1f638d9
chore(workspace): update flext-ldif pointer
Feb 20, 2026
0096967
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
9c63c47
chore(workspace): update flexcore pointer
Feb 20, 2026
be9ad89
test(scripts): centralize remaining script tests under unit layout
Feb 20, 2026
9ca7374
chore(workspace): update flext-core pointer
Feb 20, 2026
b66cb63
chore(workspace): update flext-core pointer
Feb 20, 2026
0f7ffe9
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
15670f8
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
e45413f
chore(workspace): checkpoint subproject automation updates
Feb 20, 2026
e851e81
chore(workspace): checkpoint remaining subproject updates
Feb 20, 2026
9ba23bb
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
665f6f0
chore(workspace): checkpoint latest subproject updates
Feb 20, 2026
6b15c18
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
7c5b4de
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
a3e211a
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
1248929
chore(workspace): checkpoint flext-ldif pending updates
Feb 20, 2026
668fcae
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
d2114f5
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
98c0904
chore(workspace): checkpoint flext-ldif updates
Feb 20, 2026
9a66b10
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
db4e431
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
3f83833
chore(workspace): checkpoint rebased subproject updates
Feb 20, 2026
32ff8d5
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
e226064
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
f5e1240
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
38e9035
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
892ac39
chore(workspace): checkpoint flext-ldif pending updates
Feb 20, 2026
784ac55
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
7657465
chore: checkpoint pending 0.11.0-dev changes
Feb 20, 2026
fc9eac1
chore: resolve main baseline conflicts for 0.11.0-dev
Feb 20, 2026
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
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ jobs:
- name: Setup workspace
run: make setup

- name: Run release-ci pipeline
- name: Run release pipeline
run: |
make release-ci \
make release \
RELEASE_PHASE=validate,version,build,publish \
VERSION="${{ steps.release.outputs.version }}" \
TAG="${{ steps.release.outputs.tag }}" \
Expand Down
78 changes: 27 additions & 51 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ PUSH ?=
VERSION ?=
TAG ?=
BUMP ?=
RELEASE_DEV_SUFFIX ?= 0
CREATE_BRANCHES ?= 1
PR_ACTION ?= status
PR_BASE ?= main
Expand All @@ -36,6 +37,8 @@ PR_DELETE_BRANCH ?= 0
PR_CHECKS_STRICT ?= 0
PR_RELEASE_ON_MERGE ?= 1
PR_INCLUDE_ROOT ?= 1
PR_BRANCH ?= 0.11.0-dev
PR_CHECKPOINT ?= 1
Comment on lines 27 to 41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

PR_BRANCH is hardcoded to 0.11.0-dev—this will need updating every release cycle.

All other PR_* defaults are either empty or generic. Line 39 pins PR_BRANCH to a specific development cycle, creating a maintenance burden. Consider defaulting to an empty string and letting the branch be derived at runtime (e.g., from the current branch), or documenting that this must be bumped each cycle.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` around lines 26 - 40, PR_BRANCH is hardcoded to "0.11.0-dev", which
forces manual bumps each release; change the Makefile so PR_BRANCH defaults to
an empty string (PR_BRANCH ?= "") and/or derive it at runtime (e.g., use git
rev-parse --abbrev-ref HEAD or a CI variable) when empty, and update any targets
that rely on PR_BRANCH to fall back to the current branch; ensure the variable
name PR_BRANCH is referenced in the Makefile logic so callers get the derived
value and add a short comment documenting the behavior.


Q := @
ifdef VERBOSE
Expand Down Expand Up @@ -134,7 +137,7 @@ if [ -n "$$residual_venvs" ]; then \
fi
endef

.PHONY: help setup upgrade build check security format docs test validate typings clean release release-ci pr
.PHONY: help setup upgrade build check security format docs test validate typings clean release pr

help: ## Show simple workspace verbs
$(Q)echo "FLEXT Workspace"
Expand All @@ -153,7 +156,6 @@ help: ## Show simple workspace verbs
$(Q)echo " test Run tests only in all projects"
$(Q)echo " validate Run validate gates (FIX=1 auto-fix, VALIDATE_SCOPE=workspace for repo-level)"
$(Q)echo " release Interactive workspace release orchestration"
$(Q)echo " release-ci Non-interactive release run for CI/tag workflows"
$(Q)echo " pr Manage PRs for selected projects"
$(Q)echo " typings Stub supply-chain + typing report (PROJECT/PROJECTS to scope)"
$(Q)echo " clean Clean all projects"
Expand All @@ -173,6 +175,7 @@ help: ## Show simple workspace verbs
$(Q)echo " DRY_RUN=1 Print plan, do not tag/push"
$(Q)echo " PUSH=1 Push release commit/tag"
$(Q)echo " VERSION=<semver> TAG=v<semver> BUMP=patch Release controls"
$(Q)echo " RELEASE_DEV_SUFFIX=0|1 Append -dev during release version phase"
$(Q)echo " CREATE_BRANCHES=1|0 Create release branches in workspace + projects"
$(Q)echo " PR_ACTION=status|create|view|checks|merge|close"
$(Q)echo " PR_BASE=main PR_HEAD=<branch> PR_NUMBER=<id> PR_DRAFT=0|1"
Expand All @@ -181,6 +184,7 @@ help: ## Show simple workspace verbs
$(Q)echo " PR_CHECKS_STRICT=0|1 checks action strict failure toggle"
$(Q)echo " PR_RELEASE_ON_MERGE=0|1 merge action: dispatch release workflow"
$(Q)echo " PR_INCLUDE_ROOT=0|1 include root repo in workspace PR automation"
$(Q)echo " PR_BRANCH=<name> PR_CHECKPOINT=0|1 normalize branch + checkpoint before action"
$(Q)echo " DEPS_REPORT=0 Skip dependency report after upgrade/typings"
$(Q)echo ""
$(Q)echo "Examples:"
Expand All @@ -192,7 +196,7 @@ help: ## Show simple workspace verbs
$(Q)echo " make test PROJECT=flext-api PYTEST_ARGS=\"-k unit\" FAIL_FAST=1"
$(Q)echo " make validate VALIDATE_SCOPE=workspace"
$(Q)echo " make release BUMP=minor"
$(Q)echo " make release-ci VERSION=0.11.0 TAG=v0.11.0 RELEASE_PHASE=all"
$(Q)echo " make release INTERACTIVE=0 CREATE_BRANCHES=0 VERSION=0.11.0 TAG=v0.11.0 RELEASE_PHASE=all"
$(Q)echo " make pr PROJECT=flext-core PR_ACTION=status"
$(Q)echo " make pr PROJECT=flext-core PR_ACTION=create PR_TITLE='release: 0.11.0-dev'"
$(Q)echo " NOTE: External projects (not in .gitmodules) require manual clone."
Expand Down Expand Up @@ -433,6 +437,7 @@ release: ## Interactive workspace release orchestration
--root "$(CURDIR)" \
--phase "$(RELEASE_PHASE)" \
--interactive "$(INTERACTIVE)" \
--dev-suffix "$(RELEASE_DEV_SUFFIX)" \
--create-branches "$(CREATE_BRANCHES)" \
--projects $(SELECTED_PROJECTS) \
$(if $(DRY_RUN),--dry-run "$(DRY_RUN)",) \
Expand All @@ -441,58 +446,29 @@ release: ## Interactive workspace release orchestration
$(if $(TAG),--tag "$(TAG)",) \
$(if $(BUMP),--bump "$(BUMP)",)

release-ci: ## Non-interactive release run for CI/tag workflows
$(Q)$(ENSURE_NO_PROJECT_CONFLICT)
$(Q)$(ENFORCE_WORKSPACE_VENV)
$(Q)$(ENSURE_SELECTED_PROJECTS)
$(Q)$(ENSURE_PROJECTS_EXIST)
$(Q)python scripts/release/run.py \
--root "$(CURDIR)" \
--phase "$(RELEASE_PHASE)" \
--interactive 0 \
--create-branches 0 \
--projects $(SELECTED_PROJECTS) \
$(if $(DRY_RUN),--dry-run "$(DRY_RUN)",) \
$(if $(PUSH),--push "$(PUSH)",) \
$(if $(VERSION),--version "$(VERSION)",) \
$(if $(TAG),--tag "$(TAG)",) \
$(if $(BUMP),--bump "$(BUMP)",)

pr: ## Manage pull requests for selected projects
$(Q)$(ENSURE_NO_PROJECT_CONFLICT)
$(Q)$(ENSURE_SELECTED_PROJECTS)
$(Q)$(ENSURE_PROJECTS_EXIST)
$(Q)$(ORCHESTRATOR) --verb pr \
$(if $(filter 1,$(FAIL_FAST)),--fail-fast) \
--make-arg "PR_ACTION=$(PR_ACTION)" \
--make-arg "PR_BASE=$(PR_BASE)" \
$(if $(PR_HEAD),--make-arg "PR_HEAD=$(PR_HEAD)",) \
$(if $(PR_NUMBER),--make-arg "PR_NUMBER=$(PR_NUMBER)",) \
$(if $(PR_TITLE),--make-arg "PR_TITLE=$(PR_TITLE)",) \
$(if $(PR_BODY),--make-arg "PR_BODY=$(PR_BODY)",) \
--make-arg "PR_DRAFT=$(PR_DRAFT)" \
--make-arg "PR_MERGE_METHOD=$(PR_MERGE_METHOD)" \
--make-arg "PR_AUTO=$(PR_AUTO)" \
--make-arg "PR_DELETE_BRANCH=$(PR_DELETE_BRANCH)" \
--make-arg "PR_CHECKS_STRICT=$(PR_CHECKS_STRICT)" \
--make-arg "PR_RELEASE_ON_MERGE=$(PR_RELEASE_ON_MERGE)" \
$(SELECTED_PROJECTS)
$(Q)if [ "$(PR_INCLUDE_ROOT)" = "1" ]; then \
python scripts/github/pr_manager.py \
--repo-root "$(CURDIR)" \
--action "$(PR_ACTION)" \
--base "$(PR_BASE)" \
$(if $(PR_HEAD),--head "$(PR_HEAD)",) \
$(if $(PR_NUMBER),--number "$(PR_NUMBER)",) \
$(if $(PR_TITLE),--title "$(PR_TITLE)",) \
$(if $(PR_BODY),--body "$(PR_BODY)",) \
--draft "$(PR_DRAFT)" \
--merge-method "$(PR_MERGE_METHOD)" \
--auto "$(PR_AUTO)" \
--delete-branch "$(PR_DELETE_BRANCH)" \
--checks-strict "$(PR_CHECKS_STRICT)" \
--release-on-merge "$(PR_RELEASE_ON_MERGE)"; \
fi
$(Q)python scripts/github/pr_workspace.py \
--workspace-root "$(CURDIR)" \
$(foreach proj,$(SELECTED_PROJECTS),--project "$(proj)") \
--include-root "$(PR_INCLUDE_ROOT)" \
--branch "$(PR_BRANCH)" \
--checkpoint "$(PR_CHECKPOINT)" \
--fail-fast "$(if $(filter 1,$(FAIL_FAST)),1,0)" \
--pr-action "$(PR_ACTION)" \
--pr-base "$(PR_BASE)" \
$(if $(PR_HEAD),--pr-head "$(PR_HEAD)",) \
$(if $(PR_NUMBER),--pr-number "$(PR_NUMBER)",) \
$(if $(PR_TITLE),--pr-title "$(PR_TITLE)",) \
$(if $(PR_BODY),--pr-body "$(PR_BODY)",) \
--pr-draft "$(PR_DRAFT)" \
--pr-merge-method "$(PR_MERGE_METHOD)" \
--pr-auto "$(PR_AUTO)" \
--pr-delete-branch "$(PR_DELETE_BRANCH)" \
--pr-checks-strict "$(PR_CHECKS_STRICT)" \
--pr-release-on-merge "$(PR_RELEASE_ON_MERGE)"

security: ## Run all security checks in all projects
$(Q)$(ENSURE_NO_PROJECT_CONFLICT)
Expand Down
73 changes: 73 additions & 0 deletions libs/versioning.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from __future__ import annotations

import re
import tomllib
from pathlib import Path

import tomlkit
from tomlkit.items import Table


SEMVER_RE = re.compile(
r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)$"
)


def parse_semver(version: str) -> tuple[int, int, int]:
match = SEMVER_RE.match(version)
if not match:
raise ValueError(f"invalid semver version: {version}")
return (
int(match.group("major")),
int(match.group("minor")),
int(match.group("patch")),
)


def bump_version(current_version: str, bump: str) -> str:
major, minor, patch = parse_semver(current_version)
if bump == "major":
return f"{major + 1}.0.0"
if bump == "minor":
return f"{major}.{minor + 1}.0"
if bump == "patch":
return f"{major}.{minor}.{patch + 1}"
raise ValueError(f"unsupported bump: {bump}")


def release_tag_from_branch(branch: str) -> str | None:
version = branch.removesuffix("-dev")
if SEMVER_RE.fullmatch(version):
return f"v{version}"
match = re.fullmatch(r"release/(?P<version>\d+\.\d+\.\d+)", branch)
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The release/... fallback regex uses \d+ which accepts leading zeros (e.g. release/01.2.3v01.2.3), unlike the stricter SEMVER_RE used in the first code path. Validate the extracted version with SEMVER_RE to enforce consistent semver rules across both paths.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libs/versioning.py, line 42:

<comment>The `release/...` fallback regex uses `\d+` which accepts leading zeros (e.g. `release/01.2.3` → `v01.2.3`), unlike the stricter `SEMVER_RE` used in the first code path. Validate the extracted version with `SEMVER_RE` to enforce consistent semver rules across both paths.</comment>

<file context>
@@ -0,0 +1,73 @@
+    version = branch.removesuffix("-dev")
+    if SEMVER_RE.fullmatch(version):
+        return f"v{version}"
+    match = re.fullmatch(r"release/(?P<version>\d+\.\d+\.\d+)", branch)
+    if not match:
+        return None
</file context>
Fix with Cubic

if not match:
return None
return f"v{match.group('version')}"


def current_workspace_version(root: Path) -> str:
pyproject = root / "pyproject.toml"
data = tomllib.loads(pyproject.read_text(encoding="utf-8"))
project = data.get("project")
if not isinstance(project, dict):
raise RuntimeError("unable to detect [project] section from pyproject.toml")
version = project.get("version")
if not isinstance(version, str) or not version:
raise RuntimeError("unable to detect version from pyproject.toml")
return version.removesuffix("-dev")


def replace_project_version(content: str, version: str) -> tuple[str, bool]:
document = tomlkit.parse(content)
project = document.get("project")
if not isinstance(project, Table):
return content, False
current = project.get("version")
if not isinstance(current, str) or not current:
return content, False
_ = parse_semver(current.removesuffix("-dev"))
if current == version:
return content, False
project["version"] = version
updated = tomlkit.dumps(document)
return updated, updated != content
Loading
Loading