diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 0000000..bd1491c --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,268 @@ +name: Auto Release + +# Automatically creates a release when a PR is merged to main/master. +# Determines version bump from PR labels or commit messages: +# - Label "release:major" or commit with "BREAKING CHANGE" → major bump +# - Label "release:minor" or commit with "feat:" → minor bump +# - Label "release:patch" or commit with "fix:" → patch bump +# - Label "release:beta" → beta pre-release +# - Label "release:skip" → skip release entirely +# +# Can also be triggered manually via workflow_dispatch. + +on: + push: + branches: [main, master] + workflow_dispatch: + inputs: + bump_type: + description: "Version bump type" + required: true + type: choice + options: + - patch + - minor + - major + - beta + +permissions: + contents: write + pull-requests: read + +jobs: + determine-release: + name: Determine release type + runs-on: ubuntu-latest + # Skip release commits to avoid infinite loops + if: "!startsWith(github.event.head_commit.message, 'chore: release v')" + outputs: + should_release: ${{ steps.check.outputs.should_release }} + bump_type: ${{ steps.check.outputs.bump_type }} + is_prerelease: ${{ steps.check.outputs.is_prerelease }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check release type + id: check + env: + MANUAL_BUMP: ${{ github.event.inputs.bump_type }} + run: | + BUMP="" + IS_PRE="false" + + # Manual trigger takes priority + if [ -n "$MANUAL_BUMP" ]; then + if [ "$MANUAL_BUMP" = "beta" ]; then + BUMP="beta" + IS_PRE="true" + else + BUMP="$MANUAL_BUMP" + fi + echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "bump_type=$BUMP" >> "$GITHUB_OUTPUT" + echo "is_prerelease=$IS_PRE" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Check merged PR labels + PR_NUMBER=$(gh pr list --state merged --base main --json number,mergedAt --jq 'sort_by(.mergedAt) | last | .number' 2>/dev/null || echo "") + if [ -n "$PR_NUMBER" ]; then + LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name' 2>/dev/null || echo "") + + if echo "$LABELS" | grep -q "release:skip"; then + echo "should_release=false" >> "$GITHUB_OUTPUT" + echo "bump_type=" >> "$GITHUB_OUTPUT" + echo "is_prerelease=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if echo "$LABELS" | grep -q "release:major"; then BUMP="major"; fi + if echo "$LABELS" | grep -q "release:minor"; then BUMP="minor"; fi + if echo "$LABELS" | grep -q "release:patch"; then BUMP="patch"; fi + if echo "$LABELS" | grep -q "release:beta"; then BUMP="beta"; IS_PRE="true"; fi + fi + + # Fallback: detect from commit messages + if [ -z "$BUMP" ]; then + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ -n "$LAST_TAG" ]; then + COMMITS=$(git log "$LAST_TAG..HEAD" --pretty=format:"%s" --no-merges) + else + COMMITS=$(git log --pretty=format:"%s" --no-merges -20) + fi + + if echo "$COMMITS" | grep -qiE "BREAKING CHANGE|^feat!:"; then + BUMP="major" + elif echo "$COMMITS" | grep -qiE "^feat(\(.+\))?:"; then + BUMP="minor" + elif echo "$COMMITS" | grep -qiE "^fix(\(.+\))?:"; then + BUMP="patch" + fi + fi + + # Default: no auto-release unless there's a clear signal + if [ -z "$BUMP" ]; then + echo "should_release=false" >> "$GITHUB_OUTPUT" + echo "bump_type=" >> "$GITHUB_OUTPUT" + echo "is_prerelease=false" >> "$GITHUB_OUTPUT" + else + echo "should_release=true" >> "$GITHUB_OUTPUT" + echo "bump_type=$BUMP" >> "$GITHUB_OUTPUT" + echo "is_prerelease=$IS_PRE" >> "$GITHUB_OUTPUT" + fi + env: + GH_TOKEN: ${{ github.token }} + + auto-release: + name: Create release + runs-on: ubuntu-latest + needs: determine-release + if: needs.determine-release.outputs.should_release == 'true' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Bump version + id: bump + run: | + BUMP_TYPE="${{ needs.determine-release.outputs.bump_type }}" + IS_PRE="${{ needs.determine-release.outputs.is_prerelease }}" + CURRENT=$(cat VERSION | tr -d '[:space:]') + + if [ "$BUMP_TYPE" = "beta" ]; then + # Parse current version + BASE=$(echo "$CURRENT" | sed 's/-.*//') + IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE" + NEXT_MINOR="$MAJOR.$((MINOR + 1)).0" + + # Check if already in beta + if echo "$CURRENT" | grep -q "beta"; then + BETA_NUM=$(echo "$CURRENT" | grep -oP 'beta\.\K[0-9]+') + NEW_VERSION="${NEXT_MINOR}-beta.$((BETA_NUM + 1))" + else + NEW_VERSION="${NEXT_MINOR}-beta.1" + fi + else + # Standard semantic version bump + BASE=$(echo "$CURRENT" | sed 's/-.*//') + IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE" + case "$BUMP_TYPE" in + patch) NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))" ;; + minor) NEW_VERSION="$MAJOR.$((MINOR + 1)).0" ;; + major) NEW_VERSION="$((MAJOR + 1)).0.0" ;; + esac + fi + + echo "current=$CURRENT" >> "$GITHUB_OUTPUT" + echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT" + echo "tag=v$NEW_VERSION" >> "$GITHUB_OUTPUT" + echo "Bumping $CURRENT → $NEW_VERSION" + + # Update all version files + echo "$NEW_VERSION" > VERSION + + cat > backend/_version.py << PYEOF + """Single source of truth for MIZAN version — auto-generated""" + + __version__ = "$NEW_VERSION" + PYEOF + + # Clean indentation from heredoc + sed -i 's/^ //' backend/_version.py + + sed -i "s/^version = \".*\"/version = \"$NEW_VERSION\"/" pyproject.toml + sed -i "s/\"version\": \".*\"/\"version\": \"$NEW_VERSION\"/" frontend/package.json + + # CLI version + IFS='.' read -r NM NMI NP <<< "$(echo "$NEW_VERSION" | sed 's/-.*//')" + SHORT_VER="$NM.$NMI" + sed -i "s/MIZAN v[0-9][0-9]*\.[0-9][0-9]*/MIZAN v$SHORT_VER/" backend/cli.py + sed -i "s/MIZAN v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*/MIZAN v$NEW_VERSION/" backend/cli.py + + # API version + sed -i "s/\"version\": \"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[^\"]*\"/\"version\": \"$NEW_VERSION\"/g" backend/api/main.py + sed -i "s/version=\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[^\"]*\"/version=\"$NEW_VERSION\"/" backend/api/main.py + + - name: Generate changelog + id: changelog + run: | + NEW_VERSION="${{ steps.bump.outputs.new_version }}" + DATE=$(date +%Y-%m-%d) + + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ -n "$LAST_TAG" ]; then + COMMITS=$(git log "$LAST_TAG..HEAD" --pretty=format:"- %s" --no-merges) + else + COMMITS=$(git log --pretty=format:"- %s" --no-merges -20) + fi + + # Categorize + FEATURES=$(echo "$COMMITS" | grep -iE "^- (feat|add|new)" || true) + FIXES=$(echo "$COMMITS" | grep -iE "^- (fix|bug|patch|hotfix)" || true) + CHANGES=$(echo "$COMMITS" | grep -iE "^- (refactor|update|improve|enhance|chore|docs|style|perf)" || true) + + # Build entry + ENTRY="## [v$NEW_VERSION] — $DATE" + if [ -n "$FEATURES" ]; then + ENTRY="$ENTRY + + ### Added + $FEATURES" + fi + if [ -n "$FIXES" ]; then + ENTRY="$ENTRY + + ### Fixed + $FIXES" + fi + if [ -n "$CHANGES" ]; then + ENTRY="$ENTRY + + ### Changed + $CHANGES" + fi + + # Save for release notes + echo "$ENTRY" > /tmp/release_notes.md + + # Prepend to CHANGELOG.md + if [ -f CHANGELOG.md ]; then + HEADER=$(head -3 CHANGELOG.md) + BODY=$(tail -n +4 CHANGELOG.md) + printf "%s\n\n%s\n\n%s" "$HEADER" "$ENTRY" "$BODY" > CHANGELOG.md + fi + + - name: Commit and tag + run: | + git add -A + git commit -m "chore: release v${{ steps.bump.outputs.new_version }} + + Bump version ${{ steps.bump.outputs.current }} → ${{ steps.bump.outputs.new_version }} and update CHANGELOG. + + [skip ci]" + git tag -a "v${{ steps.bump.outputs.new_version }}" -m "Release v${{ steps.bump.outputs.new_version }}" + git push origin HEAD + git push origin "v${{ steps.bump.outputs.new_version }}" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: "v${{ steps.bump.outputs.new_version }}" + name: "MIZAN v${{ steps.bump.outputs.new_version }}" + body_path: /tmp/release_notes.md + draft: false + prerelease: ${{ needs.determine-release.outputs.is_prerelease == 'true' }} + generate_release_notes: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..681e469 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,49 @@ +name: Deploy Docs + +on: + push: + branches: [main, master] + paths: + - "docs/**" + - "CHANGELOG.md" + - "README.md" + - ".github/workflows/docs.yml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + build: + name: Build docs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs + + deploy: + name: Deploy to GitHub Pages + runs-on: ubuntu-latest + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy + id: deployment + uses: actions/deploy-pages@v4 diff --git a/Makefile b/Makefile index 3c62ee8..bc845cc 100644 --- a/Makefile +++ b/Makefile @@ -106,6 +106,12 @@ release-minor: ## Full release: bump minor, changelog, tag, push release-major: ## Full release: bump major, changelog, tag, push ./scripts/release.sh major +release-beta: ## Full release: create beta pre-release + ./scripts/release.sh beta + +release-rc: ## Full release: create release candidate + ./scripts/release.sh rc + release-dry: ## Dry run of a patch release (no changes) ./scripts/release.sh patch --dry-run diff --git a/docs/index.html b/docs/index.html index 7b2d744..0b2336c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -189,6 +189,117 @@ } .card h4 { margin-top: 0; } + /* Search */ + .search-box { + padding: 12px 16px; + margin-bottom: 8px; + } + .search-input { + width: 100%; + padding: 8px 12px 8px 32px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + color: var(--text); + font-size: 13px; + outline: none; + transition: border-color 0.2s; + } + .search-input:focus { + border-color: var(--accent); + } + .search-box { position: relative; } + .search-icon { + position: absolute; + left: 26px; + top: 50%; + transform: translateY(-50%); + color: var(--text-dim); + font-size: 14px; + pointer-events: none; + } + + /* Theme toggle */ + .theme-toggle { + position: absolute; + top: 24px; + right: 16px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + padding: 6px 8px; + color: var(--text-dim); + cursor: pointer; + font-size: 16px; + line-height: 1; + transition: all 0.2s; + } + .theme-toggle:hover { color: var(--accent); border-color: var(--accent); } + + /* Section transitions */ + .section { + display: none; + animation: fadeIn 0.3s ease; + } + .section.active { display: block; } + @keyframes fadeIn { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } + } + + /* Version badge */ + .version-badge { + display: inline-block; + padding: 2px 10px; + border-radius: 12px; + font-size: 11px; + font-weight: 600; + background: rgba(245, 166, 35, 0.15); + color: var(--accent); + margin-left: 8px; + vertical-align: middle; + } + + /* Back to top */ + .back-to-top { + position: fixed; + bottom: 24px; + right: 24px; + width: 40px; + height: 40px; + border-radius: 12px; + background: var(--accent); + color: #000; + border: none; + cursor: pointer; + font-size: 18px; + display: none; + align-items: center; + justify-content: center; + box-shadow: 0 4px 12px rgba(245, 166, 35, 0.3); + transition: transform 0.2s; + z-index: 50; + } + .back-to-top:hover { transform: translateY(-2px); } + .back-to-top.visible { display: flex; } + + /* Light theme */ + [data-theme="light"] { + --bg: #fafafa; + --bg-card: #ffffff; + --bg-code: #f4f4f8; + --border: #e0e0e8; + --text: #1a1a2e; + --text-dim: #5a5a70; + --link: #2563eb; + } + + /* Highlight search results */ + .nav-item.search-match { + background: rgba(245, 166, 35, 0.1); + color: var(--accent) !important; + } + /* Mobile */ .menu-toggle { display: none; position: fixed; top: 16px; left: 16px; z-index: 200; background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; @@ -198,19 +309,27 @@ .sidebar.open { transform: translateX(0); } .main { margin-left: 0; padding: 24px 16px; padding-top: 64px; } .menu-toggle { display: block; } + .theme-toggle { top: 16px; right: 56px; position: fixed; z-index: 200; } } + +
+ +
+

Multimodal Perception

+

MIZAN's perception system implements Quranic sensory processing: Sam' (hearing) processes before Basar (sight), following the order established in Quran 16:78 and 17:36.

+ +
+

Auditory-first processing: Audio input is always processed before visual input, mirroring the Quranic ordering of sam' before basar in every mention.

+
+ +

Basirah — Vision Engine

+

The BasirahEngine analyzes images using LLM vision capabilities. It returns structured JSON with:

+ + +

Qalb-Aware Focus

+

Vision analysis adapts based on emotional state:

+ + + + + + + + +
Qalb StateFocus Adjustment
frustratedPay attention to errors, warnings, and problematic elements
confusedFocus on clarity, labels, and explanatory elements
anxiousLook for reassuring elements and positive signals
determinedIdentify actionable items and next steps
+ +

Nutq — Voice Engine

+

The NutqEngine handles speech synthesis and analysis:

+ + +

QCA Integration

+

Perception results flow into the QCA engine via process_input_multimodal():

+
Input → Sam' (audio) → Basar (vision) → Fu'ad (integration)
+  ↓         ↓              ↓               ↓
+Nutq    Transcribe      Analyze         Merge results
+Engine   intent &       category &      + ISM root analysis
+         language       elements        + Lawh storage
+ +

Using Perception in the UI

+

Navigate to Tools → Perception in the sidebar, or attach an image/audio file directly in the chat interface. The system automatically routes media to the appropriate perception engine.

+
+ + +
+

Perception API

+ +

Analyze Multimodal Input

+
POST /api/perception/analyze
+
+{
+  "text": "What does this diagram show?",
+  "image_base64": "iVBORw0KGgo...",
+  "audio_base64": "SUQzBAA...",
+  "media_type": "image/png",
+  "agent_id": "agent_abc123",
+  "qalb_state": "neutral"
+}
+
+Response:
+{
+  "perception": {
+    "sam": { ... },
+    "basar": { ... },
+    "fuad": { ... },
+    "nutq": {
+      "text": "What does this diagram show?",
+      "confidence": 0.92,
+      "language": "english",
+      "intent": "question"
+    },
+    "basirah": {
+      "description": "A system architecture diagram showing ...",
+      "category": "diagram",
+      "confidence": 0.87,
+      "extracted_text": "QCA Engine → Memory → ...",
+      "key_elements": ["architecture", "pipeline", "layers"]
+    }
+  },
+  "roots_identified": { "ع-ل-م": { ... } },
+  "key_terms": ["architecture", "pipeline"],
+  "zahir": "User is asking about a technical diagram",
+  "batin": "User wants to understand system structure"
+}
+ +

WebSocket Multimodal Message

+
// Send via WebSocket
+{
+  "type": "multimodal",
+  "text": "Describe this image",
+  "image_base64": "...",
+  "media_type": "image/png",
+  "session_id": "session_123",
+  "agent_id": "agent_abc"
+}
+
+// Receive perception_result
+{
+  "type": "perception_result",
+  "result": { ... }  // Same as REST response
+}
+
+ + +
+

Releases & Versioning

+

MIZAN uses Semantic Versioning (MAJOR.MINOR.PATCH) with support for pre-release versions (beta, rc).

+ +

Version Format

+ + + + + + + +
FormatExampleWhen
MAJOR.MINOR.PATCH3.1.0Stable release
X.Y.Z-beta.N3.2.0-beta.1Beta pre-release
X.Y.Z-rc.N3.2.0-rc.1Release candidate
+ +

Manual Release

+
# Stable releases
+make release-patch    # 3.0.0 → 3.0.1  (bug fixes)
+make release-minor    # 3.0.0 → 3.1.0  (new features)
+make release-major    # 3.0.0 → 4.0.0  (breaking changes)
+
+# Pre-releases
+make release-beta     # 3.0.0 → 3.1.0-beta.1
+make release-rc       # 3.1.0-beta.2 → 3.1.0-rc.1
+
+# Preview without changes
+make release-dry      # Shows what would happen
+ +

What Happens During a Release

+
make release-patch
+  ↓
+scripts/release.sh:
+  1. Pre-flight: checks branch, clean tree, up-to-date
+  2. Bumps version in 6 files (VERSION, pyproject.toml, etc.)
+  3. Auto-generates CHANGELOG from git commits
+  4. Commits: "chore: release vX.Y.Z"
+  5. Creates annotated git tag: vX.Y.Z
+  6. Pushes branch + tag to origin
+  ↓
+GitHub Actions (release.yml) triggers on v* tag:
+  1. Validates: lint + test
+  2. Publishes to PyPI
+  3. Pushes Docker images to ghcr.io
+  4. Creates GitHub Release with changelog notes
+ +

Automatic Release

+

When a PR is merged to main, the auto-release workflow determines the version bump automatically:

+ + + + + + + + + +
SignalBump
PR label release:major or commit BREAKING CHANGEMajor
PR label release:minor or commit prefix feat:Minor
PR label release:patch or commit prefix fix:Patch
PR label release:betaBeta pre-release
PR label release:skipNo release
+ +
+

Tip: Add a release:minor label to your PR before merging to trigger an automatic minor release with auto-generated changelog.

+
+ +

Version Storage

+

The version is stored in 6 locations, all kept in sync by scripts/bump-version.sh:

+ + + + + + + + + + +
FilePurpose
VERSIONSingle source of truth
pyproject.tomlPython package metadata
backend/_version.pyRuntime import (from backend._version import __version__)
frontend/package.jsonFrontend version
backend/cli.pyCLI banner display
backend/api/main.pyAPI version metadata
+ +

Changelog Format

+

CHANGELOG.md follows Keep a Changelog. During release, commits are auto-categorized:

+ + + + + + + +
Commit PrefixCategory
feat: / add: / new:Added
fix: / bug: / hotfix:Fixed
refactor: / docs: / chore: / perf:Changed
+ +

Branch Protection

+

The main branch is protected with these rules:

+ +
# Apply protection rules (requires gh CLI)
+make protect
+
+

System Design

@@ -1269,14 +1603,20 @@

Project Structure

| | +-- plugins.py # Plugin manager (Wasilah) | | +-- middleware.py # Middleware pipeline (Silsilah) | +-- providers.py # LLM providers (Claude/GPT/Ollama/300+) +| +-- perception/ +| | +-- basirah.py # Vision engine (LLM-powered image analysis) +| | +-- nutq.py # Voice engine (speech + intent detection) +| | +-- vision.py # Low-level vision processing +| | +-- voice.py # Low-level voice processing | +-- memory/ | | +-- dhikr.py # 3-tier persistent memory | | +-- masalik.py # Neural pathways (spreading activation) -| | +-- lawh_mahfuz.py # Immutable memory (triple-checksum) +| | +-- lawh_mahfuz.py # Immutable memory (quad-checksum) | | +-- memory_pyramid.py # Unified 5-layer query engine | | +-- vector_store.py # Semantic embeddings (ChromaDB) | | +-- knowledge_graph.py # Entity-relationship graph -| | +-- living_memory.py # Adaptive memory lifecycle +| | +-- living_memory.py # Adaptive memory lifecycle (hybrid similarity) +| | +-- quaternary.py # DNA-inspired ACGT encoding & integrity | +-- reasoning/ | | +-- aql_engine.py # Arabic Query Language reasoning | | +-- causal_engine.py # Pearl's 3-rung causal ladder @@ -1290,11 +1630,13 @@

Project Structure

| +-- settings.py # Configuration | +-- cli.py # Terminal interface +-- frontend/src/ -| +-- App.tsx # Main UI + WebSocket handler +| +-- App.tsx # Main UI + WebSocket + multimodal chat | +-- components/ -| | +-- ChatMessage.tsx # Chat bubbles + CognitiveBar pills +| | +-- ChatMessage.tsx # Chat bubbles + CognitiveBar + PerceptionCard | | +-- AgentCard.tsx # Agent card (Nafs + Ruh bars) -| +-- pages/ # Feature pages +| | +-- Icons.tsx # SVG icon components +| +-- pages/ +| | +-- PerceptionPage.tsx # Vision & voice analysis UI | +-- hooks/ # React hooks | +-- types.ts # TypeScript types (CognitiveMetadata) +-- plugins/ # Your plugins go here! @@ -1310,18 +1652,114 @@

Project Structure

diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index 4f846ef..eb5dbed 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -35,7 +35,8 @@ CURRENT=$(cat "$VERSION_FILE" | tr -d '[:space:]') echo -e "\n ${BOLD}Current version:${NC} ${GOLD}$CURRENT${NC}" # ───── Parse current version ───── -IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" +BASE_CURRENT=$(echo "$CURRENT" | sed 's/-.*//') +IFS='.' read -r MAJOR MINOR PATCH <<< "$BASE_CURRENT" # ───── Calculate new version ───── case "${1:-}" in @@ -48,20 +49,46 @@ case "${1:-}" in major) NEW_VERSION="$((MAJOR + 1)).0.0" ;; + beta) + # Parse base version (strip any existing pre-release suffix) + BASE_VER=$(echo "$CURRENT" | sed 's/-.*//') + IFS='.' read -r BM BMI BP <<< "$BASE_VER" + NEXT_MINOR="$BM.$((BMI + 1)).0" + if echo "$CURRENT" | grep -q "beta"; then + BETA_NUM=$(echo "$CURRENT" | grep -oP 'beta\.\K[0-9]+' || echo "0") + NEW_VERSION="${NEXT_MINOR}-beta.$((BETA_NUM + 1))" + else + NEW_VERSION="${NEXT_MINOR}-beta.1" + fi + ;; + rc) + BASE_VER=$(echo "$CURRENT" | sed 's/-.*//') + if echo "$CURRENT" | grep -q "rc"; then + RC_NUM=$(echo "$CURRENT" | grep -oP 'rc\.\K[0-9]+' || echo "0") + NEW_VERSION="${BASE_VER}-rc.$((RC_NUM + 1))" + elif echo "$CURRENT" | grep -q "beta"; then + BASE_VER=$(echo "$CURRENT" | sed 's/-.*//') + NEW_VERSION="${BASE_VER}-rc.1" + else + NEW_VERSION="${CURRENT}-rc.1" + fi + ;; "") echo "" - echo "Usage: $0 " + echo "Usage: $0 " echo "" echo " patch $CURRENT → $MAJOR.$MINOR.$((PATCH + 1))" echo " minor $CURRENT → $MAJOR.$((MINOR + 1)).0" echo " major $CURRENT → $((MAJOR + 1)).0.0" + echo " beta $CURRENT → $MAJOR.$((MINOR + 1)).0-beta.1" + echo " rc $CURRENT → X.Y.Z-rc.1" echo " X.Y.Z Set exact version" exit 0 ;; *) - # Validate semver format - if [[ ! "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - error "Invalid version format: $1 (expected X.Y.Z)" + # Validate semver format (with optional pre-release) + if [[ ! "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc)\.[0-9]+)?$ ]]; then + error "Invalid version format: $1 (expected X.Y.Z or X.Y.Z-beta.N)" fi NEW_VERSION="$1" ;; @@ -92,18 +119,16 @@ sed -i "s/\"version\": \".*\"/\"version\": \"$NEW_VERSION\"/" "$ROOT_DIR/fronten success "frontend/package.json" # 5. backend/cli.py — banner and version command -SHORT_VER="$MAJOR.$MINOR" -if [ "$1" = "patch" ] || [[ "$NEW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - IFS='.' read -r NM NMI NP <<< "$NEW_VERSION" - SHORT_VER="$NM.$NMI" -fi +NEW_BASE=$(echo "$NEW_VERSION" | sed 's/-.*//') +IFS='.' read -r NM NMI NP <<< "$NEW_BASE" +SHORT_VER="$NM.$NMI" sed -i "s/MIZAN v[0-9][0-9]*\.[0-9][0-9]*/MIZAN v$SHORT_VER/" "$ROOT_DIR/backend/cli.py" sed -i "s/MIZAN v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*/MIZAN v$NEW_VERSION/" "$ROOT_DIR/backend/cli.py" success "backend/cli.py" # 6. backend/api/main.py — FastAPI version and API responses -sed -i "s/\"version\": \"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"/\"version\": \"$NEW_VERSION\"/g" "$ROOT_DIR/backend/api/main.py" -sed -i "s/version=\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\"/version=\"$NEW_VERSION\"/" "$ROOT_DIR/backend/api/main.py" +sed -i "s/\"version\": \"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[^\"]*\"/\"version\": \"$NEW_VERSION\"/g" "$ROOT_DIR/backend/api/main.py" +sed -i "s/version=\"[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[^\"]*\"/version=\"$NEW_VERSION\"/" "$ROOT_DIR/backend/api/main.py" success "backend/api/main.py" # ───── Summary ───── diff --git a/scripts/release.sh b/scripts/release.sh index 9940c74..e9aeb35 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -40,7 +40,7 @@ BUMP_TYPE="" for arg in "$@"; do case "$arg" in --dry-run) DRY_RUN=true ;; - patch|minor|major) BUMP_TYPE="$arg" ;; + patch|minor|major|beta|rc) BUMP_TYPE="$arg" ;; *) if [[ "$arg" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then BUMP_TYPE="$arg"