From fd2894f9399206645b3adc634982f88c32f36dd9 Mon Sep 17 00:00:00 2001
From: jinliyl <6469360+jinliyl@users.noreply.github.com>
Date: Thu, 13 Aug 2026 17:22:00 +0800
Subject: [PATCH] fix: harden the 0.4.1.7 release configuration (#456)
* fix(packaging): harden the Studio release workflow
* chore(daily-paper): tune scheduled discovery defaults
* fix(docs): link the ReMe blog to GitHub Pages
* fix(docs): increase Chinese hero title spacing
* refactor(docs): share hero title line spacing
* fix(docs): keep desktop hero copy on two lines
* fix(docs): widen the home hero description
* style(docs): loosen hero title line height
* fix(docs): hide Markdown frontmatter in rendered pages
* docs(readme): simplify installation and remove standalone ReMe Studio instructions
- Remove references to separate ReMe Studio package and static build steps
- Clarify that `core` extra includes common integrations including Studio
- Update installation instructions to use `pip install -e ".[core]"`
- Remove detailed Studio usage and frontend development instructions
- Note that Studio is included with `core` and optional via `web` extra
- Simplify Quick Start guide by removing Studio usage step
- Remove mentions of serving Studio with HTTP service when using extras
- Update both English and Chinese README files accordingly
* docs(readme): streamline and clarify memory design and operations
- Remove redundant explanations about core extra installation
- Simplify memory processing flow description for clarity
- Clarify memory workspace directory default and customization
- Condense automatic memory flow to emphasize rebuildable metadata
- Refine search functionality explanation with RRF fusion details
- Shorten and clarify agent integration description, removing redundancy
- Update and simplify the operations command list, removing less common commands
- Revise community and support section for conciseness and clarity
- Maintain parallel updates in both English and Chinese README files
* test(bump_version): add tests for version bumping and consistency checks
- Add dynamic loading of bump_version and package_studio scripts for testing
- Test that studio package and dependencies have matching versions
- Implement fixtures to write temporary version files for testing
- Add test ensuring bump_version updates all relevant files and dependencies
- Add test to reject inconsistent version sources before writing
- Refactor tests to use common REPOSITORY path variable
- Include imports and setup for pytest in test file
feat(bump_version): create script to update ReMe and Studio versions
- Implement version reading from __init__.py and pyproject.toml files
- Validate current versions are consistent across files before updating
- Update version strings atomically to avoid partial writes
- Ensure exact pinning of studio dependency in main package extras
- Validate new version format against a safe pattern
- Provide CLI interface to bump versions from command line
- Raise errors if expected version declarations or pins are missing or duplicated
* fix(release): validate split package publishing
* fix(release): improve validation diagnostics
* fix(release): sync docs and workflow inputs
* fix(release): split PyPI publish jobs
---
.github/workflows/github-pages-check.yml | 61 +++++++
.github/workflows/package-check.yml | 97 ++++++++++
.github/workflows/pages.yml | 4 +
.github/workflows/python-publish.yml | 160 +++++++++++------
.github/workflows/windows-smoke.yml | 20 +++
AGENTS.md | 2 +-
README.md | 98 +++--------
README_ZH.md | 82 +++------
cookbook/daily_paper/README.md | 13 +-
cookbook/daily_paper/README_ZH.md | 13 +-
docs/en/contributing.md | 4 +
docs/en/quick_start.md | 6 +
docs/zh/contributing.md | 4 +
docs/zh/quick_start.md | 6 +
github-pages/package.json | 3 +-
github-pages/scripts/generate-content.mjs | 2 +-
github-pages/src/main.js | 3 +-
github-pages/src/markdown.js | 6 +
github-pages/src/styles.css | 4 +-
github-pages/tests/markdown.test.mjs | 15 ++
packages/reme_ai_studio/.gitignore | 1 +
packages/reme_ai_studio/pyproject.toml | 5 +-
pyproject.toml | 5 +-
reme/__init__.py | 2 +-
reme/config/daily_cookbook.yaml | 3 +-
scripts/bump_version.py | 205 ++++++++++++++++++++++
scripts/package_studio.py | 13 +-
tests/unit/test_daily_paper.py | 15 +-
tests/unit/test_package_versions.py | 196 +++++++++++++++++++--
29 files changed, 822 insertions(+), 226 deletions(-)
create mode 100644 .github/workflows/github-pages-check.yml
create mode 100644 .github/workflows/package-check.yml
create mode 100644 github-pages/src/markdown.js
create mode 100644 github-pages/tests/markdown.test.mjs
create mode 100644 packages/reme_ai_studio/.gitignore
create mode 100644 scripts/bump_version.py
diff --git a/.github/workflows/github-pages-check.yml b/.github/workflows/github-pages-check.yml
new file mode 100644
index 00000000..1b7ca454
--- /dev/null
+++ b/.github/workflows/github-pages-check.yml
@@ -0,0 +1,61 @@
+name: GitHub Pages Check
+
+on:
+ push:
+ branches: [main, master, dev, develop]
+ paths:
+ - '.github/workflows/github-pages-check.yml'
+ - 'AGENTS.md'
+ - 'README.md'
+ - 'README_ZH.md'
+ - 'docs/**'
+ - 'github-pages/**'
+ - 'website/README*.md'
+ - 'website/public/og.jpg'
+ - 'cookbook/*/README*.md'
+ - 'benchmark/*/README*.md'
+ - 'skills/reme_memory/SKILL.md'
+ pull_request:
+ branches: [main, master, dev, develop]
+ paths:
+ - '.github/workflows/github-pages-check.yml'
+ - 'AGENTS.md'
+ - 'README.md'
+ - 'README_ZH.md'
+ - 'docs/**'
+ - 'github-pages/**'
+ - 'website/README*.md'
+ - 'website/public/og.jpg'
+ - 'cookbook/*/README*.md'
+ - 'benchmark/*/README*.md'
+ - 'skills/reme_memory/SKILL.md'
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ test-and-build:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: github-pages
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Set up Node
+ uses: actions/setup-node@v6
+ with:
+ node-version: '22.13'
+ cache: npm
+ cache-dependency-path: github-pages/package-lock.json
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run tests
+ run: npm test
+
+ - name: Build documentation
+ run: npm run build
diff --git a/.github/workflows/package-check.yml b/.github/workflows/package-check.yml
new file mode 100644
index 00000000..6705e79e
--- /dev/null
+++ b/.github/workflows/package-check.yml
@@ -0,0 +1,97 @@
+name: Package Check
+
+on:
+ push:
+ branches: [main, master, dev, develop]
+ paths:
+ - '.github/workflows/package-check.yml'
+ - '.github/workflows/python-publish.yml'
+ - 'packages/reme_ai_studio/**'
+ - 'pyproject.toml'
+ - 'reme/__init__.py'
+ - 'reme/utils/web_static.py'
+ - 'scripts/bump_version.py'
+ - 'scripts/package_studio.py'
+ - 'tests/unit/test_package_versions.py'
+ - 'website/**'
+ - 'LICENSE'
+ pull_request:
+ branches: [main, master, dev, develop]
+ paths:
+ - '.github/workflows/package-check.yml'
+ - '.github/workflows/python-publish.yml'
+ - 'packages/reme_ai_studio/**'
+ - 'pyproject.toml'
+ - 'reme/__init__.py'
+ - 'reme/utils/web_static.py'
+ - 'scripts/bump_version.py'
+ - 'scripts/package_studio.py'
+ - 'tests/unit/test_package_versions.py'
+ - 'website/**'
+ - 'LICENSE'
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ distributions:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22.13'
+ cache: npm
+ cache-dependency-path: website/package-lock.json
+
+ - name: Set up Python
+ uses: actions/setup-python@v6
+ with:
+ python-version: '3.11'
+
+ - name: Install build dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install build packaging pytest twine
+
+ - name: Validate release versions
+ run: python scripts/bump_version.py --check
+
+ - name: Run package tests
+ run: PYTHONPATH=. python -m pytest tests/unit/test_package_versions.py -q
+
+ - name: Build Studio static workspace
+ working-directory: website
+ run: |
+ npm ci
+ npm run build:static
+
+ - name: Build and check distributions
+ run: |
+ python scripts/package_studio.py
+ mkdir -p dist/reme dist/studio
+ python -m build --outdir dist/reme
+ python -m build packages/reme_ai_studio --outdir dist/studio
+ python -m twine check dist/reme/* dist/studio/*
+
+ - name: Verify distributions and isolated installation
+ run: |
+ REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)"
+ STUDIO_WHEEL="$(pwd)/$(ls dist/studio/reme_ai_studio-*.whl)"
+ STUDIO_SDIST="$(pwd)/$(ls dist/studio/reme_ai_studio-*.tar.gz)"
+ python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme/web/')
+ python -m zipfile -l "${STUDIO_WHEEL}" | grep 'reme_ai_studio/static/index.html'
+ python -m zipfile -l "${STUDIO_WHEEL}" | grep 'dist-info/licenses/LICENSE'
+ python -m tarfile -l "${STUDIO_SDIST}" | grep '/LICENSE'
+ python -m venv "${RUNNER_TEMP}/reme-package-smoke"
+ "${RUNNER_TEMP}/reme-package-smoke/bin/python" -m pip install \
+ --find-links "$(pwd)/dist/studio" "${REME_WHEEL}[core]"
+ cd "${RUNNER_TEMP}"
+ "${RUNNER_TEMP}/reme-package-smoke/bin/python" -c \
+ "import reme; from reme_ai_studio import static_dir; assert (static_dir() / 'index.html').is_file()"
+ "${RUNNER_TEMP}/reme-package-smoke/bin/python" -c \
+ "from reme.utils import resolve_web_static_dir; assert (resolve_web_static_dir() / 'index.html').is_file()"
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index b2cfbb4a..5337b01f 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -8,6 +8,10 @@ on:
- "docs/**"
- "README.md"
- "README_ZH.md"
+ - "website/README*.md"
+ - "website/public/og.jpg"
+ - "cookbook/*/README*.md"
+ - "benchmark/*/README*.md"
- "skills/reme_memory/SKILL.md"
- "AGENTS.md"
- ".github/workflows/pages.yml"
diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
index 8bb12ca7..223e4d81 100644
--- a/.github/workflows/python-publish.yml
+++ b/.github/workflows/python-publish.yml
@@ -1,15 +1,12 @@
-# This workflow will upload a Python Package using Twine when a release is created
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
-
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
-
-name: Publish Python Package to Pypi
+name: Publish Python packages to PyPI
on:
workflow_dispatch:
+ inputs:
+ version:
+ description: Release version
+ required: true
+ type: string
release:
types: [published]
@@ -17,47 +14,110 @@ permissions:
contents: read
jobs:
- deploy:
-
+ build:
runs-on: ubuntu-latest
+ env:
+ RELEASE_VERSION: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.version }}
steps:
- - uses: actions/checkout@v6
- - name: Set up Node
- uses: actions/setup-node@v4
- with:
- node-version: '22'
- cache: npm
- cache-dependency-path: website/package-lock.json
- - name: Set up Python
- uses: actions/setup-python@v6
- with:
- python-version: '3.11'
- - name: Build web workspace
- working-directory: website
- run: |
- npm ci
- npm run build:static
- - name: Prepare Studio package
- run: python scripts/package_studio.py
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install setuptools wheel build
- - name: Build package
- run: |
- python -m build --outdir dist
- python -m build packages/reme_ai_studio --outdir dist
- - name: Test installation
- run: |
- REME_WHEEL="$(pwd)/$(ls dist/reme_ai-[0-9]*.whl)"
- python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme/web/')
- pip install --find-links "$(pwd)/dist" "${REME_WHEEL}[core]"
- python -c "import reme; print(reme.__version__)"
- python -c "from reme_ai_studio import static_dir; assert (static_dir() / 'index.html').is_file()"
- python -c "from reme.utils import resolve_web_static_dir; assert (resolve_web_static_dir() / 'index.html').is_file()"
- - name: Publish package to PyPI
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- user: __token__
- password: ${{ secrets.PYPI_API_TOKEN }}
+ - uses: actions/checkout@v6
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22.13'
+ cache: npm
+ cache-dependency-path: website/package-lock.json
+
+ - name: Set up Python
+ uses: actions/setup-python@v6
+ with:
+ python-version: '3.11'
+
+ - name: Install build dependencies
+ run: |
+ python -m pip install --upgrade pip
+ python -m pip install build packaging twine
+
+ - name: Validate release version
+ run: python scripts/bump_version.py --check --expected-version "${RELEASE_VERSION}"
+
+ - name: Build Studio static workspace
+ working-directory: website
+ run: |
+ npm ci
+ npm run build:static
+
+ - name: Prepare and build distributions
+ run: |
+ python scripts/package_studio.py
+ mkdir -p dist/reme dist/studio
+ python -m build --outdir dist/reme
+ python -m build packages/reme_ai_studio --outdir dist/studio
+ python -m twine check dist/reme/* dist/studio/*
+
+ - name: Verify distributions and isolated installation
+ run: |
+ REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)"
+ STUDIO_WHEEL="$(pwd)/$(ls dist/studio/reme_ai_studio-*.whl)"
+ STUDIO_SDIST="$(pwd)/$(ls dist/studio/reme_ai_studio-*.tar.gz)"
+ python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme/web/')
+ python -m zipfile -l "${STUDIO_WHEEL}" | grep 'reme_ai_studio/static/index.html'
+ python -m zipfile -l "${STUDIO_WHEEL}" | grep 'dist-info/licenses/LICENSE'
+ python -m tarfile -l "${STUDIO_SDIST}" | grep '/LICENSE'
+ python -m venv "${RUNNER_TEMP}/reme-release-smoke"
+ "${RUNNER_TEMP}/reme-release-smoke/bin/python" -m pip install \
+ --find-links "$(pwd)/dist/studio" "${REME_WHEEL}[core]"
+ cd "${RUNNER_TEMP}"
+ "${RUNNER_TEMP}/reme-release-smoke/bin/python" -c \
+ "import reme; from reme_ai_studio import static_dir; assert (static_dir() / 'index.html').is_file()"
+ "${RUNNER_TEMP}/reme-release-smoke/bin/python" -c \
+ "from reme.utils import resolve_web_static_dir; assert (resolve_web_static_dir() / 'index.html').is_file()"
+
+ - name: Upload ReMe Studio distributions
+ uses: actions/upload-artifact@v4
+ with:
+ name: reme-studio-distributions
+ path: dist/studio/
+
+ - name: Upload ReMe distributions
+ uses: actions/upload-artifact@v4
+ with:
+ name: reme-distributions
+ path: dist/reme/
+
+ publish-studio:
+ needs: build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download ReMe Studio distributions
+ uses: actions/download-artifact@v4
+ with:
+ name: reme-studio-distributions
+ path: dist/studio
+
+ - name: Publish ReMe Studio
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_API_TOKEN }}
+ packages-dir: dist/studio
+ skip-existing: true
+
+ publish-reme:
+ needs: publish-studio
+ runs-on: ubuntu-latest
+ steps:
+ - name: Download ReMe distributions
+ uses: actions/download-artifact@v4
+ with:
+ name: reme-distributions
+ path: dist/reme
+
+ - name: Publish ReMe
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_API_TOKEN }}
+ packages-dir: dist/reme
+ skip-existing: true
diff --git a/.github/workflows/windows-smoke.yml b/.github/workflows/windows-smoke.yml
index e72080a1..54760137 100644
--- a/.github/workflows/windows-smoke.yml
+++ b/.github/workflows/windows-smoke.yml
@@ -23,6 +23,13 @@ jobs:
steps:
- uses: actions/checkout@v4
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ cache: npm
+ cache-dependency-path: website/package-lock.json
+
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
@@ -34,6 +41,19 @@ jobs:
python -m pip install --upgrade pip setuptools wheel
pip install -e packages/reme_ai_studio -e ".[dev,core]"
+ - name: Build Studio static workspace
+ working-directory: website
+ run: |
+ npm ci
+ npm run build:static
+
+ - name: Verify editable source installation serves Studio
+ shell: pwsh
+ run: |
+ Push-Location $env:RUNNER_TEMP
+ python -c "from reme.utils import resolve_web_static_dir; assert (resolve_web_static_dir() / 'index.html').is_file()"
+ Pop-Location
+
- name: Run version job
run: reme start service.backend=cli job=version
diff --git a/AGENTS.md b/AGENTS.md
index e4065fba..931204fc 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -64,7 +64,7 @@ and concise documentation together.
ReMe requires Python 3.11 or newer. Install the editable development environment with:
```bash
-pip install -e ".[dev,core]"
+pip install -e packages/reme_ai_studio -e ".[dev,core]"
```
Before changing behavior, inspect the adjacent implementation, schema, built-in config, and focused tests. Follow
diff --git a/README.md b/README.md
index 26e26741..94861322 100644
--- a/README.md
+++ b/README.md
@@ -61,10 +61,8 @@ keeping the files under the user's control.
## 📰 News
-- [2026.08] - Published the [ReMe blog](docs/en/reme-blog.md), an end-to-end introduction to its local-first memory
+- [2026.08] - Published the [ReMe blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog), an end-to-end introduction to its local-first memory
architecture, self-evolving workflows, hybrid search, proactive discovery, and benchmark results.
-- [2026.08] - Introduced [ReMe Studio](https://reme.agentscope.io/?doc=studio-en), a local web workspace for browsing, editing, and searching
- memory files, chatting with the read-only ReMe Agent, inspecting the digest wikilink graph, and managing the local service.
- [2026.08] - [Experience-driven enhancement method](https://reme.agentscope.io/?doc=toolmemory-en) of agent tool-use execution built
on ReMe is available on [arXiv:2608.03403](https://arxiv.org/abs/2608.03403).
- [2026.07] - Introduced optional Cookbooks: [Daily Paper](https://reme.agentscope.io/?doc=daily-paper-en) for paper discovery and
@@ -86,18 +84,20 @@ Install from pip:
pip install "reme-ai[core]"
```
-The base `reme-ai` package contains only the Python service and library. ReMe Studio is distributed separately and is
-installed by the `web` and `core` extras. Use `pip install reme-ai` for embedded or headless integrations that do not
-need the frontend, or `pip install "reme-ai[web]"` when Studio is needed without the other `core` integrations.
-
Install from source:
```bash
git clone https://github.com/agentscope-ai/ReMe.git
cd ReMe
pip install -e packages/reme_ai_studio -e ".[core]"
+cd website
+npm ci
+npm run build:static
+cd ..
```
+The static build requires Node.js 22.13 or newer and makes Studio available from the source tree.
+
### Environment Variables
Configure environment variables when you want LLM-powered memory evolution or embedding retrieval. Embeddings are
@@ -136,14 +136,6 @@ reme start service.port=8181
# reme start workspace_dir=/tmp/reme-demo service.port=8181
```
-After startup, check the service status. If you use a custom port, replace `2333` in the URL below with that port.
-
-When the `web` or `core` extra is installed, the HTTP service also serves **ReMe Studio** at
-. Studio can
-browse, edit, and search the workspace, chat with the read-only workspace agent, and inspect the digest wikilink graph.
-Set `service.web_enabled=false` to disable it, or use `service.web_static_dir` / `REME_WEB_STATIC_DIR` to provide a
-custom static build. The Job API remains available when no web build is found.
-
```bash
reme version
reme health_check
@@ -151,32 +143,11 @@ reme help
curl -s http://127.0.0.1:2333/version -H 'Content-Type: application/json' -d '{}'
```
-### Use ReMe Studio
+### ReMe Studio (Optional)
-Open after starting the default HTTP service. Studio provides:
-
-- **Files, Daily, and Knowledge views** for navigating the whole workspace or focusing on `daily/` and `digest/`.
-- **Markdown tabs** with preview, split editing, optimistic save checks, and local download.
-- **Memory Graph** for exploring indexed `personal`, `procedure`, and `wiki` nodes and opening their Markdown sources.
-- **Read-only Agent chat** with streamed tool activity and usage; drag a workspace file into the composer to reference it.
-- **Settings** for service/component status, redacted effective configuration, version information, and safe index rebuilding.
-- English/Chinese language switching and light, dark, or system appearance.
-
-For frontend development, run ReMe and Studio in separate terminals:
-
-```bash
-# Terminal 1, repository root
-reme start
-
-# Terminal 2
-cd website
-npm install
-npm run dev
-```
-
-Then open . The development server uses `http://127.0.0.1:2333` by default; set
-`NEXT_PUBLIC_REME_API_URL` to connect to another ReMe HTTP service. Static-build and frontend configuration instructions are
-in the [ReMe Studio guide](https://reme.agentscope.io/?doc=studio-en).
+The `core` installation above includes Studio. After starting ReMe, open to browse, edit, and
+search the workspace. To add Studio to a base installation, use `pip install "reme-ai[web]"`. See the
+[ReMe Studio guide](https://reme.agentscope.io/?doc=studio-en) for source builds, configuration, and development.
### 5-Minute Memory Demo
@@ -218,7 +189,7 @@ These Markdown guides cover the main user workflows and the runtime contracts im
| Guide | What you will learn |
|-------|---------------------|
-| [Quick Start](docs/en/quick_start.md) | Install ReMe, start the service, use Studio, and run the first file and memory operations. |
+| [Quick Start](docs/en/quick_start.md) | Install ReMe, start the service, and run the first file and memory operations. |
| [Memory as File](docs/en/memory_as_file.md) | Understand workspace layers, frontmatter, wikilinks, chunks, and the file-as-source-of-truth model. |
| [Auto Memory](docs/en/auto_memory.md) | Preserve source conversations and distill reusable daily memory cards. |
| [Auto Resource](docs/en/auto_resource.md) | Import supported text resources and turn them into source-linked daily cards. |
@@ -227,8 +198,7 @@ These Markdown guides cover the main user workflows and the runtime contracts im
| [Proactive](docs/en/proactive.md) | Read interest topics safely and integrate them into a host agent's decision flow. |
| [Agent Integration Scenarios](docs/en/reme_scene.md) | Choose among CLI/SKILL.md, HTTP, MCP, and embedded Python integration. |
| [Framework](docs/en/framework.md) | Understand Application, Job, Step, Component, service, configuration, and lifecycle boundaries. |
-| [ReMe Studio](https://reme.agentscope.io/?doc=studio-en) | Use, configure, develop, test, and build the web frontend. |
-| [ReMe Blog](docs/en/reme-blog.md) | Read the product story, design rationale, examples, and benchmark summary. |
+| [ReMe Blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog) | Read the product story, design rationale, examples, and benchmark summary. |
## 🧑🍳 Cookbooks
@@ -246,9 +216,8 @@ another row in this table.
> Memory as File, File as Memory.
ReMe treats **memory as files**, progressively processing filtered conversation source records and external resources
-from `session/` and `resource/` into `daily/`, then consolidating them into reusable long-term memory nodes under
-`digest/`. The default workspace is `.reme/` under the current directory; `workspace_dir=...` selects a different
-user-owned location.
+from `session/` and `resource/` into `daily/`, then `digest/`. The default workspace is `.reme/` under the current
+directory; `workspace_dir=...` selects a different user-owned location.
### Directory Structure
@@ -288,16 +257,8 @@ user-owned location.
## 🧭 Memory Design Philosophy
-> Capture conversation source records and resources, refine them into long-term preferences, reusable experience, and
-> valuable knowledge,
-> while keeping the result editable by humans and agents.
-
-### Automatic Memory Flow
-
-ReMe follows a capture → index → consolidate → recall loop. Conversations and resources first become daily memory cards;
-background jobs keep files searchable; `auto_dream` distills stable knowledge into `digest/`; agents recall memory
-through search, wikilinks, or proactive topics. The files are the durable source of truth—indexes, graphs, catalogs, and
-caches under `metadata/` can be rebuilt from them.
+ReMe follows a capture → index → consolidate → recall loop. Workspace files remain the durable source of truth;
+everything under `metadata/` is rebuildable.
| Capability | Entry point | What it does | Output |
|---------------------------------------------|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
@@ -326,10 +287,8 @@ caches under `metadata/` can be rebuilt from them.
-Search returns the best matching chunks with file paths and line ranges, then lists bounded incoming and outgoing
-wikilink neighbors by metadata. An agent can read a promising source or traverse the graph only when needed. With
-embeddings enabled, BM25 and vector rankings are fused with reciprocal rank fusion (RRF); otherwise the default remains
-BM25 plus wikilink expansion.
+Search returns matching chunks with line ranges and bounded wikilink neighbors. Optional vector results are fused with
+BM25 through reciprocal rank fusion (RRF).
> [!IMPORTANT]
> `proactive` only reads and exposes interest topics produced by Auto Dream. It does not independently browse the web,
@@ -355,8 +314,7 @@ dependencies, and underspecified requests.
## 🤝 Agent-friendly Integration
ReMe can run as a local memory service accessed through the CLI, HTTP API, or MCP server, or it can be embedded in the
-host process through its Python API. The default HTTP service can serve ReMe Studio at the same address. Agents can
-choose the path that fits their runtime and share a local memory workspace when appropriate.
+host process through its Python API.
| Agents | Recommended path | Available after integration |
|-----------------------------------------------|---------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
@@ -395,28 +353,21 @@ choose the path that fits their runtime and share a local memory workspace when
## 🛠️ ReMe Operations
-ReMe operates the workspace through a unified job interface exposed by the CLI. Agents usually only need retrieval,
-reading, writing, editing, and automatic memory commands. Lower-level indexing, frontmatter, and file operation commands
-are mainly for maintenance, debugging, or advanced integration. Run `reme help` for the full job list.
+Run `reme help` for the full job list. Common workspace and maintenance commands are:
| Command | Purpose |
|-------------------------------------------|----------------------------------------------------------------------------------------|
-| `reme start` | Start the local ReMe service. |
-| `reme version` / `reme health_check` | Check package and component status. |
| `reme status` | Show stateful data-component memory estimates and process RSS. |
| [`reme search`](docs/en/memory_search.md) | Retrieve memory with BM25 and wikilinks by default, plus vectors when enabled. |
| `reme read` / `reme write` / `reme edit` | Inspect and maintain Markdown memory files. |
| `reme traverse` / `reme graph_snapshot` | Explore wikilink neighborhoods or the category-rooted digest graph. |
| `reme chat` | Stream a read-only, workspace-aware agent conversation. Requires LLM credentials. |
-| `reme auto_memory` | Turn conversation messages into daily memory cards. Requires LLM credentials. |
-| `reme auto_resource` | Interpret files under `resource/` into daily resource cards. Requires LLM credentials. |
-| `reme auto_dream` / `reme proactive` | Consolidate daily memory into long-term digest and surface topics worth attention. |
| `reme reindex` | Rebuild search and wikilink indexes from existing files. |
## 🤝 Community and Support
-- **Issues and requests**: Check [Open Issues](https://github.com/agentscope-ai/ReMe/issues) first. If there is no
- related discussion, open a new issue with background, expected behavior, and impact scope.
+- **Issues, requests, and help**: Check [Open Issues](https://github.com/agentscope-ai/ReMe/issues) first. If there is no
+ related discussion, open one with the background, expected behavior, and impact scope.
- **Code contributions**: Before making changes, read
the [contribution guide](https://docs.agentscope.io/reme/latest/en/contribution). Source, schemas, and tests are the
authoritative architecture and extension guide.
@@ -426,8 +377,7 @@ are mainly for maintenance, debugging, or advanced integration. Run `reme help`
`docs(zh): update quick start`.
- **Pre-submit checks**: Before submitting a PR, try to run `pre-commit run --all-files` and `pytest`. If tests that
depend on LLMs, embeddings, or external services cannot run, explain that in the PR.
-- **Get help**: Use [GitHub Issues](https://github.com/agentscope-ai/ReMe/issues) for bugs and feature requests. Project
- documentation is available at [https://reme.agentscope.io](https://reme.agentscope.io).
+- **Documentation**: Visit [reme.agentscope.io](https://reme.agentscope.io).
### Contributors
diff --git a/README_ZH.md b/README_ZH.md
index a1c6a7a7..e23837b5 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -53,10 +53,8 @@ Code 等 Agent 协作,在持续整理知识的同时,始终把文件控制
## 📰 新闻
-- [2026.08] - 发布 [ReMe 博客](docs/zh/reme-blog.md),系统介绍本地优先的记忆架构、自进化工作流、混合检索、
+- [2026.08] - 发布 [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog),系统介绍本地优先的记忆架构、自进化工作流、混合检索、
主动发现与评测结果。
-- [2026.08] - 新增 [ReMe Studio](https://reme.agentscope.io/?doc=studio-zh):用于浏览、编辑和搜索记忆文件,与只读 ReMe Agent
- 对话,查看 digest wikilink 图,并管理本地服务。
- [2026.08] - 基于 ReMe 的智能体工具使用
[经验驱动增强方法](https://reme.agentscope.io/?doc=toolmemory-zh)已发布,见
[arXiv:2608.03403](https://arxiv.org/abs/2608.03403)。
@@ -84,9 +82,15 @@ pip install "reme-ai[core]"
```bash
git clone https://github.com/agentscope-ai/ReMe.git
cd ReMe
-pip install -e ".[core]"
+pip install -e packages/reme_ai_studio -e ".[core]"
+cd website
+npm ci
+npm run build:static
+cd ..
```
+静态构建要求 Node.js 22.13 或更高版本,并让源码安装可以直接使用 Studio。
+
### 环境变量
如果需要 LLM 驱动的记忆演化或 embedding 检索,可以配置环境变量。embedding 默认关闭,因此默认配置不会启动 embedding 模型,也不需要
@@ -125,13 +129,6 @@ reme start service.port=8181
# reme start workspace_dir=/tmp/reme-demo service.port=8181
```
-启动后可以检查服务状态;如果使用了自定义端口,请将下面 URL 中的 `2333` 替换为对应端口。
-
-如果安装包中包含 Web 构建产物,HTTP 服务还会在 提供 **ReMe Studio**,用于浏览、编辑和搜索
-workspace,与只读 workspace agent 对话,以及查看 digest wikilink 图。可以设置
-`service.web_enabled=false` 关闭,或通过 `service.web_static_dir` / `REME_WEB_STATIC_DIR` 指定自定义静态目录。找不到 Web
-构建产物时,Job API 仍可正常使用。
-
```bash
reme version
reme health_check
@@ -139,31 +136,11 @@ reme help
curl -s http://127.0.0.1:2333/version -H 'Content-Type: application/json' -d '{}'
```
-### 使用 ReMe Studio
+### ReMe Studio(可选)
-启动默认 HTTP 服务后,在浏览器打开 。Studio 提供:
-
-- **文件、日记和知识库视图**:浏览整个 workspace,或聚焦 `daily/` 和 `digest/`。
-- **Markdown 多标签页**:支持预览、分栏编辑、基于修改时间的冲突检查、保存和本地下载。
-- **记忆图谱**:浏览已索引的 `personal`、`procedure` 和 `wiki` 节点,并打开对应 Markdown 源文件。
-- **只读 Agent 对话**:流式查看工具调用和模型用量;可将 workspace 文件拖入输入框作为引用。
-- **设置与服务管理**:查看服务/组件状态、脱敏后的生效配置和版本,并安全重建派生索引。
-- 支持中英文切换,以及浅色、深色和跟随系统外观。
-
-如需开发前端,在两个终端中分别启动 ReMe 和 Studio:
-
-```bash
-# 终端 1:仓库根目录
-reme start
-
-# 终端 2
-cd website
-npm install
-npm run dev
-```
-
-然后打开 。开发服务默认连接 `http://127.0.0.1:2333`;如需连接其他 ReMe HTTP
-服务,请设置 `NEXT_PUBLIC_REME_API_URL`。静态构建和前端配置说明见 [ReMe Studio 指南](https://reme.agentscope.io/?doc=studio-zh)。
+上面的 `core` 安装已包含 Studio。启动 ReMe 后,打开 即可浏览、编辑和搜索 workspace。
+如需为基础安装单独添加 Studio,可使用 `pip install "reme-ai[web]"`。源码构建、配置和开发说明见
+[ReMe Studio 指南](https://reme.agentscope.io/?doc=studio-zh)。
### 5 分钟记忆 Demo
@@ -205,7 +182,7 @@ ReMe 会把 Agent 记忆保存为可读的 Markdown。
| 文档 | 主要内容 |
|------|----------|
-| [快速开始](docs/zh/quick_start.md) | 安装 ReMe、启动服务、使用 Studio,并执行首次文件和记忆操作。 |
+| [快速开始](docs/zh/quick_start.md) | 安装 ReMe、启动服务,并执行首次文件和记忆操作。 |
| [Memory as File](docs/zh/memory_as_file.md) | 理解 workspace 分层、frontmatter、wikilink、chunk 和文件事实来源模型。 |
| [Auto Memory](docs/zh/auto_memory.md) | 保留过滤后的对话来源记录,并提炼可复用的 daily 记忆卡片。 |
| [Auto Resource](docs/zh/auto_resource.md) | 导入支持的文本资料,转换为可追溯来源的 daily 卡片。 |
@@ -214,8 +191,7 @@ ReMe 会把 Agent 记忆保存为可读的 Markdown。
| [Proactive](docs/zh/proactive.md) | 安全读取兴趣主题,并将其接入宿主 Agent 的决策流程。 |
| [Agent 接入场景](docs/zh/reme_scene.md) | 在 CLI/SKILL.md、HTTP、MCP 和嵌入式 Python 集成之间选择。 |
| [框架说明](docs/zh/framework.md) | 理解 Application、Job、Step、Component、service、配置和生命周期边界。 |
-| [ReMe Studio](https://reme.agentscope.io/?doc=studio-zh) | 使用、配置、开发、测试和构建 Web 前端。 |
-| [ReMe 博客](docs/zh/reme-blog.md) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
+| [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
## 🧑🍳 Cookbooks
@@ -231,8 +207,8 @@ cookbook 会继续在表格中按行追加。
> Memory as File, File as Memory.
-ReMe 将 **记忆视为文件**,让过滤后的对话来源记录和外部资料从 `session/`、`resource/` 渐进加工到 `daily/`,再沉淀为 `digest/`
-中可长期复用的知识节点。默认 workspace 是当前目录下的 `.reme/`;可通过 `workspace_dir=...` 选择其他由用户控制的位置。
+ReMe 将 **记忆视为文件**,让过滤后的对话来源记录和外部资料从 `session/`、`resource/` 渐进加工到 `daily/`,再沉淀为
+`digest/`。默认 workspace 是当前目录下的 `.reme/`;可通过 `workspace_dir=...` 选择其他由用户控制的位置。
### 目录结构
@@ -272,13 +248,7 @@ ReMe 将 **记忆视为文件**,让过滤后的对话来源记录和外部资
## 🧭 记忆设计理念
-> 捕获过滤后的对话来源记录和资料,将其整理为长期偏好、可复用经验和有价值的知识,并让结果始终能被用户和 Agent 直接编辑。
-
-### 自动记忆流程
-
-ReMe 遵循 capture → index → consolidate → recall 的循环。对话和资料先变成 daily 记忆卡片;后台任务保持文件可检索;
-`auto_dream` 将稳定知识沉淀到 `digest/`;Agent 再通过搜索、wikilink 或 proactive topics 召回记忆。文件是持久化的事实来源,
-`metadata/` 中的索引、图谱、catalog 和缓存都可以由它们重建。
+ReMe 遵循 capture → index → consolidate → recall 的循环。workspace 文件是持久化的事实来源,`metadata/` 中的内容均可重建。
| 能力 | 入口 | 作用 | 输出 |
|---------------------------------------------|-------------------------------------------|----------------------------------------------------------------------------------------------|--------------------------------------------------------------|
@@ -307,8 +277,7 @@ ReMe 遵循 capture → index → consolidate → recall 的循环。对话和
-搜索会先返回最相关的 chunks、文件路径和行号范围,再以元数据形式列出数量受限的入链与出链邻居。Agent 只需在判断确实相关后再读取原文或继续遍历图谱。
-启用 embedding 时,BM25 和向量排名通过 RRF 融合;默认未启用 embedding 时,则使用 BM25 + wikilink 扩展。
+搜索返回带行号范围的相关 chunks 和数量受限的 wikilink 邻居;可选向量结果通过 RRF 与 BM25 融合。
> [!IMPORTANT]
> `proactive` 只读取并暴露 Auto Dream 生成的兴趣主题,不会自行联网、发送通知或改写知识库;是否以及如何使用主题,由宿主 Agent
@@ -330,7 +299,7 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
## 🤝 Agent-friendly Integration
ReMe 既可以作为本地记忆服务,通过 CLI、HTTP API 或 MCP server 接入,也可以通过 Python API 嵌入宿主进程。不同 Agent 可以选择适合自身
-runtime 的路径,并按需共享同一个本地 memory workspace。默认 HTTP 服务还可以在同一地址提供 ReMe Studio。
+runtime 的路径。
| Agent | 推荐接入方式 | 接入后能力 |
|-----------------------------------------------|-------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
@@ -369,26 +338,20 @@ runtime 的路径,并按需共享同一个本地 memory workspace。默认 HTT
## 🛠️ ReMe Operations
-ReMe 通过 CLI 暴露的统一 job interface 操作 workspace。Agent 通常只需要使用检索、读取、写入、编辑和自动记忆相关命令;更底层的索引、
-frontmatter 和文件操作接口主要用于维护、调试或高级集成。完整 job 列表可以运行 `reme help` 查看。
+运行 `reme help` 可查看完整 job 列表。常用 workspace 与维护命令如下:
| 命令 | 作用 |
|-------------------------------------------|---------------------------------------------------------------|
-| `reme start` | 启动本地 ReMe 服务。 |
-| `reme version` / `reme health_check` | 检查包版本和组件状态。 |
| `reme status` | 查看有状态数据组件的内存估算及进程 RSS。 |
| [`reme search`](docs/zh/memory_search.md) | 默认使用 BM25 和 wikilink 检索,启用后增加向量检索。 |
| `reme read` / `reme write` / `reme edit` | 检查和维护 Markdown 记忆文件。 |
| `reme traverse` / `reme graph_snapshot` | 浏览 wikilink 邻域或按类别组织的 digest 图。 |
| `reme chat` | 与可感知 workspace 的只读 Agent 进行流式对话;需要 LLM 凭证。 |
-| `reme auto_memory` | 将对话 messages 转为 daily 记忆卡片;需要 LLM 凭证。 |
-| `reme auto_resource` | 将 `resource/` 下的文件解读为 daily 资料卡片;需要 LLM 凭证。 |
-| `reme auto_dream` / `reme proactive` | 将 daily 记忆整理为长期 digest,并暴露值得关注的主题。 |
| `reme reindex` | 基于已有文件重建检索和 wikilink 索引。 |
## 🤝 社区与支持
-- **问题反馈与需求**:请先查看 [Open Issues](https://github.com/agentscope-ai/ReMe/issues);如无相关讨论,可新建 Issue
+- **问题反馈、需求与帮助**:请先查看 [Open Issues](https://github.com/agentscope-ai/ReMe/issues);如无相关讨论,可新建 Issue
说明背景、目标行为和影响范围。
- **代码贡献**:改动前建议阅读 [贡献指南](https://docs.agentscope.io/reme/latest/zh/contribution)。架构与扩展方式以源码、schema
和测试为准。
@@ -397,8 +360,7 @@ frontmatter 和文件操作接口主要用于维护、调试或高级集成。
`docs(zh): update quick start`。
- **提交前检查**:提交 PR 前请尽量运行 `pre-commit run --all-files` 和 `pytest`;如有依赖 LLM、embedding 或外部服务的测试无法运行,请在
PR 中说明。
-- **获取帮助**:如需反馈 Bug 或功能请求,请使用 [GitHub Issues](https://github.com/agentscope-ai/ReMe/issues);项目文档见
- [https://reme.agentscope.io](https://reme.agentscope.io)。
+- **项目文档**:访问 [reme.agentscope.io](https://reme.agentscope.io)。
### 贡献者
diff --git a/cookbook/daily_paper/README.md b/cookbook/daily_paper/README.md
index 25444f29..1652cc8f 100644
--- a/cookbook/daily_paper/README.md
+++ b/cookbook/daily_paper/README.md
@@ -182,8 +182,8 @@ when present. The two data sources reach a mirror differently: Hugging Face is g
parameter, while arXiv is driven by its environment variable alone.
```dotenv
-# Enable the mirror for the built-in daily_paper_cron job
-DAILY_PAPER_USE_HF_MIRROR=true
+# The built-in daily_paper_cron job enables the mirror by default; set false to use the official service
+DAILY_PAPER_USE_HF_MIRROR=false
# Read only when the manual or scheduled job enables the mirror; defaults to https://hf-mirror.com when unset
HF_MIRROR_URL=https://hf-mirror.com
@@ -202,8 +202,8 @@ trailing slash is optional. There is no fallback chain: whichever base URL a cli
> **Behavior change:** `HF_MIRROR_URL` used to redirect Hugging Face traffic on its own. It is now read only when the
> job runs with `use_hf_mirror=true`; otherwise the official service is used and the client logs a warning that the
-> variable was ignored. Pass `use_hf_mirror=true` for manual requests, or set
-> `DAILY_PAPER_USE_HF_MIRROR=true` for `daily_paper_cron`, to keep an existing mirror-only setup working.
+> variable was ignored. Pass `use_hf_mirror=true` for manual requests. The built-in `daily_paper_cron` job enables the
+> mirror by default; set `DAILY_PAPER_USE_HF_MIRROR=false` to make that scheduled job use the official service.
## Running the workflow
@@ -231,8 +231,9 @@ reme start config=daily_cookbook
```
The built-in service listens on `127.0.0.1:8001`. `daily_paper_cron` runs every day at 08:00 in the
-`Asia/Shanghai` timezone. Set `DAILY_PAPER_USE_HF_MIRROR=true` to make that scheduled job use the Hugging Face mirror.
-Override the bind address with `DAILY_PAPER_HOST`, `DAILY_PAPER_PORT`, or startup arguments.
+`Asia/Shanghai` timezone, prioritizes the topic `大模型长期记忆`, and uses the Hugging Face mirror by default. Set
+`DAILY_PAPER_USE_HF_MIRROR=false` to use the official service. Override the bind address with `DAILY_PAPER_HOST`,
+`DAILY_PAPER_PORT`, or startup arguments.
```bash
curl -s http://127.0.0.1:8001/daily_paper \
diff --git a/cookbook/daily_paper/README_ZH.md b/cookbook/daily_paper/README_ZH.md
index d6b9e0d6..c5af96e8 100644
--- a/cookbook/daily_paper/README_ZH.md
+++ b/cookbook/daily_paper/README_ZH.md
@@ -172,8 +172,8 @@ reme_workspace/
Face 由 `use_hf_mirror` 任务参数控制,arXiv 仅由环境变量驱动。
```dotenv
-# 为内置 daily_paper_cron 定时任务启用镜像站
-DAILY_PAPER_USE_HF_MIRROR=true
+# 内置 daily_paper_cron 定时任务默认启用镜像站;设为 false 可改用官方服务
+DAILY_PAPER_USE_HF_MIRROR=false
# 仅在手动任务或定时任务启用镜像时读取;未配置时使用 https://hf-mirror.com
HF_MIRROR_URL=https://hf-mirror.com
@@ -192,7 +192,8 @@ URL,就只访问该地址。
> **行为变更:** 以往只要设置 `HF_MIRROR_URL` 就会改变 Hugging Face
> 的访问地址;现在该变量仅在任务启用镜像时才会读取,否则直接访问官方站点,并输出一条“已忽略该变量”的告警日志。手动调用需传入
-> `use_hf_mirror=true`,`daily_paper_cron` 定时任务需设置 `DAILY_PAPER_USE_HF_MIRROR=true`,才能继续走镜像。
+> `use_hf_mirror=true`。内置 `daily_paper_cron` 定时任务默认启用镜像;设置 `DAILY_PAPER_USE_HF_MIRROR=false`
+> 可让该定时任务改用官方服务。
## 运行方式
@@ -219,9 +220,9 @@ reme start config=daily_cookbook job=daily_paper date=2026-08-06 force=true
reme start config=daily_cookbook
```
-内置服务监听 `127.0.0.1:8001`,`daily_paper_cron` 按 `Asia/Shanghai` 时区每天 08:00 运行。设置
-`DAILY_PAPER_USE_HF_MIRROR=true` 可让该定时任务使用 Hugging Face 镜像站。可通过 `DAILY_PAPER_HOST`、`DAILY_PAPER_PORT`
-或启动参数覆盖监听地址和端口。
+内置服务监听 `127.0.0.1:8001`,`daily_paper_cron` 按 `Asia/Shanghai` 时区每天 08:00 运行,默认优先关注
+`大模型长期记忆`,并使用 Hugging Face 镜像站。设置 `DAILY_PAPER_USE_HF_MIRROR=false` 可改用官方服务。可通过
+`DAILY_PAPER_HOST`、`DAILY_PAPER_PORT` 或启动参数覆盖监听地址和端口。
```bash
curl -s http://127.0.0.1:8001/daily_paper \
diff --git a/docs/en/contributing.md b/docs/en/contributing.md
index 44c8be25..e9fe9c3e 100644
--- a/docs/en/contributing.md
+++ b/docs/en/contributing.md
@@ -40,6 +40,10 @@ The project requires Python 3.11 or later. A virtual environment is recommended:
python -m venv .venv
source .venv/bin/activate
pip install -e packages/reme_ai_studio -e ".[dev,full]"
+cd website
+npm ci
+npm run build:static
+cd ..
pre-commit install
```
diff --git a/docs/en/quick_start.md b/docs/en/quick_start.md
index d2791d89..bd73f91a 100644
--- a/docs/en/quick_start.md
+++ b/docs/en/quick_start.md
@@ -16,8 +16,14 @@ Install from source:
git clone https://github.com/agentscope-ai/ReMe.git
cd ReMe
pip install -e packages/reme_ai_studio -e ".[core]"
+cd website
+npm ci
+npm run build:static
+cd ..
```
+The static build step requires Node.js 22.13 or newer and makes Studio available when running ReMe from the source tree.
+
Installing the `core` extra is recommended. The current code imports the AgentScope wrapper, and self-evolving memory
also depends on it.
diff --git a/docs/zh/contributing.md b/docs/zh/contributing.md
index 0e134e66..14271abf 100644
--- a/docs/zh/contributing.md
+++ b/docs/zh/contributing.md
@@ -38,6 +38,10 @@ ReMe 的核心代码位于:
python -m venv .venv
source .venv/bin/activate
pip install -e packages/reme_ai_studio -e ".[dev,full]"
+cd website
+npm ci
+npm run build:static
+cd ..
pre-commit install
```
diff --git a/docs/zh/quick_start.md b/docs/zh/quick_start.md
index e00688e1..b3059866 100644
--- a/docs/zh/quick_start.md
+++ b/docs/zh/quick_start.md
@@ -16,8 +16,14 @@ pip install "reme-ai[core]"
git clone https://github.com/agentscope-ai/ReMe.git
cd ReMe
pip install -e packages/reme_ai_studio -e ".[core]"
+cd website
+npm ci
+npm run build:static
+cd ..
```
+静态构建步骤需要 Node.js 22.13 或更高版本,用于在从源码运行 ReMe 时提供 Studio。
+
`core` extra 建议安装:当前代码会导入 AgentScope wrapper,自进化记忆也依赖它。
如果要使用 `auto_memory`、`auto_resource`、`auto_dream` 这类 Agent 流程,再配置 LLM:
diff --git a/github-pages/package.json b/github-pages/package.json
index e3f19c24..90487446 100644
--- a/github-pages/package.json
+++ b/github-pages/package.json
@@ -9,7 +9,8 @@
"scripts": {
"dev": "node scripts/generate-content.mjs && vite",
"build": "node scripts/generate-content.mjs && vite build",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "test": "node --test tests/*.test.mjs"
},
"dependencies": {
"dompurify": "^3.2.6",
diff --git a/github-pages/scripts/generate-content.mjs b/github-pages/scripts/generate-content.mjs
index e6957ada..4a3130d6 100644
--- a/github-pages/scripts/generate-content.mjs
+++ b/github-pages/scripts/generate-content.mjs
@@ -228,7 +228,7 @@ for (const product of productDocuments) {
}
}
await mkdir(path.join(outputDir, "website", "public"), { recursive: true });
-await cp(path.join(repoDir, "website", "public", "og.png"), path.join(outputDir, "website", "public", "og.png"));
+await cp(path.join(repoDir, "website", "public", "og.jpg"), path.join(outputDir, "website", "public", "og.jpg"));
await mkdir(path.join(outputDir, "skills", "reme_memory"), { recursive: true });
await cp(
path.join(repoDir, "skills", "reme_memory", "SKILL.md"),
diff --git a/github-pages/src/main.js b/github-pages/src/main.js
index 992b0f83..f3438cdc 100644
--- a/github-pages/src/main.js
+++ b/github-pages/src/main.js
@@ -1,5 +1,6 @@
import DOMPurify from "dompurify";
import { marked } from "marked";
+import { stripMarkdownFrontmatter } from "./markdown.js";
import "./styles.css";
const baseUrl = import.meta.env.BASE_URL;
@@ -370,7 +371,7 @@ async function openDocument(id, pushHistory = true) {
const response = await fetch(`${baseUrl}content/${document.path}`);
if (!response.ok) throw new Error(`Unable to load ${document.path}`);
configureMarkdown(document);
- const markdown = await response.text();
+ const markdown = stripMarkdownFrontmatter(await response.text());
const body = DOMPurify.sanitize(await marked.parse(markdown), {
ADD_ATTR: ["target"],
});
diff --git a/github-pages/src/markdown.js b/github-pages/src/markdown.js
new file mode 100644
index 00000000..ef81a773
--- /dev/null
+++ b/github-pages/src/markdown.js
@@ -0,0 +1,6 @@
+const FRONTMATTER_PATTERN = /^\uFEFF?---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/;
+
+/** Remove a leading YAML frontmatter block before rendering Markdown. */
+export function stripMarkdownFrontmatter(markdown) {
+ return markdown.replace(FRONTMATTER_PATTERN, "");
+}
diff --git a/github-pages/src/styles.css b/github-pages/src/styles.css
index 803b463f..7b3ee4ad 100644
--- a/github-pages/src/styles.css
+++ b/github-pages/src/styles.css
@@ -91,8 +91,8 @@ a { color: inherit; }
.article[data-group="home"] { max-width: 980px; }
.home-hero { padding: 34px 0 76px; }
.home-eyebrow, .section-kicker { margin: 0 0 17px; color: #12806d; font: 750 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.13em; }
-.home-hero h1 { max-width: 800px; margin: 0; color: #102019; font-size: clamp(46px, 6.3vw, 76px); line-height: 0.99; letter-spacing: -0.055em; }
-.home-lead { max-width: 720px; margin: 27px 0 0; color: #526159; font-size: 18px; line-height: 1.72; }
+.home-hero h1 { margin: 0; color: #102019; font-size: clamp(46px, 6.3vw, 76px); line-height: 1.15; letter-spacing: -0.055em; }
+.home-lead { margin: 27px 0 0; color: #526159; font-size: 18px; line-height: 1.72; }
.home-actions { display: flex; flex-wrap: wrap; gap: 11px; margin-top: 31px; }
.home-actions a { padding: 11px 17px; border-radius: 10px; text-decoration: none; font-size: 14px; font-weight: 720; }
.primary-action { color: white; background: #087f6a; box-shadow: 0 8px 22px rgba(8, 127, 106, 0.2); }
diff --git a/github-pages/tests/markdown.test.mjs b/github-pages/tests/markdown.test.mjs
new file mode 100644
index 00000000..129b6190
--- /dev/null
+++ b/github-pages/tests/markdown.test.mjs
@@ -0,0 +1,15 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+import { stripMarkdownFrontmatter } from "../src/markdown.js";
+
+test("strips leading YAML frontmatter before rendering", () => {
+ assert.equal(
+ stripMarkdownFrontmatter("---\nname: reme_memory\ndescription: Memory skill\n---\n\n# ReMe Memory\n"),
+ "\n# ReMe Memory\n",
+ );
+});
+
+test("preserves Markdown without frontmatter", () => {
+ const markdown = "# ReMe Memory\n\nContent\n";
+ assert.equal(stripMarkdownFrontmatter(markdown), markdown);
+});
diff --git a/packages/reme_ai_studio/.gitignore b/packages/reme_ai_studio/.gitignore
new file mode 100644
index 00000000..9dc7e3d4
--- /dev/null
+++ b/packages/reme_ai_studio/.gitignore
@@ -0,0 +1 @@
+/LICENSE
diff --git a/packages/reme_ai_studio/pyproject.toml b/packages/reme_ai_studio/pyproject.toml
index 8fe88424..eabd6d38 100644
--- a/packages/reme_ai_studio/pyproject.toml
+++ b/packages/reme_ai_studio/pyproject.toml
@@ -1,9 +1,10 @@
[project]
name = "reme-ai-studio"
-version = "0.4.1.6"
+version = "0.4.1.7"
description = "Optional ReMe Studio static frontend."
readme = "README.md"
license = "Apache-2.0"
+license-files = ["LICENSE"]
requires-python = ">=3.11"
[project.urls]
@@ -20,5 +21,5 @@ include-package-data = false
"reme_ai_studio" = ["static/**"]
[build-system]
-requires = ["setuptools>=45", "wheel"]
+requires = ["setuptools>=77", "wheel"]
build-backend = "setuptools.build_meta"
diff --git a/pyproject.toml b/pyproject.toml
index f2c24e0e..99f0419b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,7 +43,7 @@ dependencies = [
[project.optional-dependencies]
web = [
- "reme-ai-studio==0.4.1.6",
+ "reme-ai-studio==0.4.1.7",
]
core = [
"agentscope==2.0.4.post1",
@@ -58,9 +58,10 @@ core = [
"neo4j>=6.2.0",
"networkx>=3.4.2",
"polars>=1.43.0",
- "reme-ai-studio==0.4.1.6",
+ "reme-ai-studio==0.4.1.7",
]
dev = [
+ "packaging>=24.2",
"pre-commit>=4.6.1",
"pytest>=9.1.1",
"pytest-asyncio>=0.23",
diff --git a/reme/__init__.py b/reme/__init__.py
index 4937bd91..dea7ece7 100644
--- a/reme/__init__.py
+++ b/reme/__init__.py
@@ -1,6 +1,6 @@
"""ReMe CLI package."""
-__version__ = "0.4.1.6"
+__version__ = "0.4.1.7"
from . import config
from . import constants
diff --git a/reme/config/daily_cookbook.yaml b/reme/config/daily_cookbook.yaml
index d2f7d6f4..953024ca 100644
--- a/reme/config/daily_cookbook.yaml
+++ b/reme/config/daily_cookbook.yaml
@@ -441,7 +441,8 @@ jobs:
daily_paper_cron:
backend: cron
cron: "0 8 * * *"
- use_hf_mirror: ${DAILY_PAPER_USE_HF_MIRROR:-false}
+ topics: "大模型长期记忆"
+ use_hf_mirror: ${DAILY_PAPER_USE_HF_MIRROR:-true}
candidate_limit: *candidate_limit
rrf_k: *rrf_k
weekly_weight: *weekly_weight
diff --git a/scripts/bump_version.py b/scripts/bump_version.py
new file mode 100644
index 00000000..587ca90b
--- /dev/null
+++ b/scripts/bump_version.py
@@ -0,0 +1,205 @@
+"""Validate or update the ReMe and ReMe Studio release version."""
+
+from __future__ import annotations
+
+import argparse
+import os
+import re
+import tempfile
+import tomllib
+from pathlib import Path
+
+from packaging.version import InvalidVersion, Version
+
+REPOSITORY_DIR = Path(__file__).resolve().parents[1]
+
+_VERSION_PATTERN = re.compile(r'(?m)^__version__ = "(?P[^"]+)"$')
+_STUDIO_VERSION_PATTERN = re.compile(r'(?m)^version = "(?P[^"]+)"$')
+
+
+def _paths(repository: Path) -> tuple[Path, Path, Path]:
+ return (
+ repository / "reme" / "__init__.py",
+ repository / "pyproject.toml",
+ repository / "packages" / "reme_ai_studio" / "pyproject.toml",
+ )
+
+
+def _canonical_version(value: str) -> str:
+ """Return one canonical PEP 440 version or explain how to correct it."""
+ try:
+ canonical = str(Version(value))
+ except InvalidVersion as error:
+ raise ValueError(
+ f"Invalid PEP 440 version {value!r}. Use a version such as 0.4.1.8, then rerun this command.",
+ ) from error
+ if canonical != value:
+ raise ValueError(f"Version {value!r} is not canonical PEP 440; use {canonical!r} instead.")
+ return canonical
+
+
+def _read_toml(source: Path) -> dict:
+ """Read one TOML file or identify the file that needs correction."""
+ try:
+ return tomllib.loads(source.read_text(encoding="utf-8"))
+ except (OSError, UnicodeError, tomllib.TOMLDecodeError) as error:
+ raise ValueError(f"{source}: cannot read valid TOML ({error}). Fix this file and retry.") from error
+
+
+def _required_table(config: dict, keys: tuple[str, ...], source: Path) -> dict:
+ """Return a required TOML table or explain where to add it."""
+ value: object = config
+ for key in keys:
+ if not isinstance(value, dict) or key not in value:
+ table = ".".join(keys)
+ raise ValueError(f"{source}: missing or invalid [{table}] table. Add or fix this table and retry.")
+ value = value[key]
+ if not isinstance(value, dict):
+ table = ".".join(keys)
+ raise ValueError(f"{source}: [{table}] must be a TOML table. Fix this table and retry.")
+ return value
+
+
+def _match_version(text: str, pattern: re.Pattern[str], source: Path) -> str:
+ matches = list(pattern.finditer(text))
+ if len(matches) != 1:
+ raise ValueError(f"Expected exactly one version declaration in {source}, found {len(matches)}.")
+ return matches[0].group("version")
+
+
+def _replace_version(text: str, pattern: re.Pattern[str], version: str, source: Path) -> str:
+ _match_version(text, pattern, source)
+ return pattern.sub(lambda match: match.group(0).replace(match.group("version"), version), text)
+
+
+def _write_atomic(path: Path, content: str) -> None:
+ """Replace one text file without exposing a partially written file."""
+ temporary_name: str | None = None
+ try:
+ with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as temporary:
+ temporary.write(content)
+ temporary_name = temporary.name
+ os.chmod(temporary_name, path.stat().st_mode)
+ os.replace(temporary_name, path)
+ finally:
+ if temporary_name is not None and os.path.exists(temporary_name):
+ os.unlink(temporary_name)
+
+
+def read_version(repository: Path = REPOSITORY_DIR) -> str:
+ """Read and validate the main distribution version."""
+ version_file, _, _ = _paths(repository)
+ version = _match_version(version_file.read_text(encoding="utf-8"), _VERSION_PATTERN, version_file)
+ try:
+ return _canonical_version(version)
+ except ValueError as error:
+ raise ValueError(f"{version_file}: {error} Fix the __version__ declaration and retry.") from error
+
+
+def check_versions(repository: Path = REPOSITORY_DIR, expected_version: str | None = None) -> str:
+ """Validate both distributions and their exact optional-dependency pins."""
+ version_file, main_config_file, studio_config_file = _paths(repository)
+ version = read_version(repository)
+ main_config = _read_toml(main_config_file)
+ studio_config = _read_toml(studio_config_file)
+ dependency = f"reme-ai-studio=={version}"
+ optional_dependencies = _required_table(main_config, ("project", "optional-dependencies"), main_config_file)
+ studio_project = _required_table(studio_config, ("project",), studio_config_file)
+ problems: list[str] = []
+
+ studio_version = studio_project.get("version")
+ if studio_version != version:
+ problems.append(f"{studio_config_file}: project.version is {studio_version!r}; expected {version!r}")
+ if optional_dependencies.get("web") != [dependency]:
+ problems.append(f"{main_config_file}: web must be exactly [{dependency!r}]")
+ core_dependencies = optional_dependencies.get("core")
+ if not isinstance(core_dependencies, list) or core_dependencies.count(dependency) != 1:
+ problems.append(f"{main_config_file}: core must contain {dependency!r} exactly once")
+
+ expected = None
+ if expected_version is not None:
+ expected = _canonical_version(expected_version.removeprefix("v"))
+ if version != expected:
+ problems.append(f"{version_file}: package version is {version!r}; release expects {expected!r}")
+
+ if problems:
+ details = "\n".join(f"- {problem}" for problem in problems)
+ raise ValueError(
+ "Version metadata is inconsistent:\n"
+ f"{details}\n"
+ f"Fix the listed values, or run `python scripts/bump_version.py {expected or version}` "
+ "after restoring a consistent current version.",
+ )
+ return version
+
+
+def bump_version(version: str, repository: Path = REPOSITORY_DIR) -> str:
+ """Update every release version, rolling back if any write or validation fails."""
+ version = _canonical_version(version)
+ current_version = check_versions(repository)
+ version_file, main_config_file, studio_config_file = _paths(repository)
+ paths = (version_file, main_config_file, studio_config_file)
+ original = {path: path.read_text(encoding="utf-8") for path in paths}
+ current_dependency = f"reme-ai-studio=={current_version}"
+
+ main_text = original[main_config_file]
+ if main_text.count(current_dependency) != 2:
+ raise ValueError(f"Expected exactly two {current_dependency!r} pins in {main_config_file}.")
+ updated = {
+ version_file: _replace_version(original[version_file], _VERSION_PATTERN, version, version_file),
+ main_config_file: main_text.replace(current_dependency, f"reme-ai-studio=={version}"),
+ studio_config_file: _replace_version(
+ original[studio_config_file],
+ _STUDIO_VERSION_PATTERN,
+ version,
+ studio_config_file,
+ ),
+ }
+
+ written: list[Path] = []
+ try:
+ for path in paths:
+ _write_atomic(path, updated[path])
+ written.append(path)
+ check_versions(repository, version)
+ except BaseException as error:
+ rollback_errors: list[str] = []
+ for path in reversed(written):
+ try:
+ _write_atomic(path, original[path])
+ except OSError as rollback_error:
+ rollback_errors.append(f"{path}: {rollback_error}")
+ if rollback_errors:
+ raise RuntimeError(
+ "Version update failed and rollback was incomplete. Restore these files from Git:\n- "
+ + "\n- ".join(rollback_errors),
+ ) from error
+ raise RuntimeError(
+ "Version update failed; all changed files were restored. Fix the error and retry.",
+ ) from error
+ return current_version
+
+
+def main() -> None:
+ """Run the version validation or update command."""
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("version", nargs="?", help="New version for both distributions, for example 0.4.1.8")
+ parser.add_argument("--check", action="store_true", help="Validate versions without changing files")
+ parser.add_argument("--expected-version", help="Also require this release/tag version; a leading v is accepted")
+ args = parser.parse_args()
+ if args.check:
+ if args.version:
+ parser.error("version cannot be used with --check; use --expected-version")
+ version = check_versions(expected_version=args.expected_version)
+ print(f"Release versions are consistent: {version}")
+ return
+ if args.expected_version:
+ parser.error("--expected-version requires --check")
+ if not args.version:
+ parser.error("provide a version to update, or use --check")
+ previous_version = bump_version(args.version)
+ print(f"Updated ReMe and ReMe Studio from {previous_version} to {args.version}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/package_studio.py b/scripts/package_studio.py
index 66ed4ca5..96396bc9 100644
--- a/scripts/package_studio.py
+++ b/scripts/package_studio.py
@@ -10,6 +10,8 @@ REPOSITORY_DIR = Path(__file__).resolve().parents[1]
WEBSITE_DIR = REPOSITORY_DIR / "website"
PACKAGE_DIR = REPOSITORY_DIR / "packages" / "reme_ai_studio"
STATIC_DIR = PACKAGE_DIR / "src" / "reme_ai_studio" / "static"
+LICENSE_FILE = REPOSITORY_DIR / "LICENSE"
+STATIC_GITIGNORE = "*\n!.gitignore\n"
_RAW_WEBSITE_URL = "https://raw.githubusercontent.com/agentscope-ai/ReMe/main/website"
_REPOSITORY_URL = "https://github.com/agentscope-ai/ReMe"
@@ -35,15 +37,20 @@ def build_readme() -> str:
def prepare_package(*, copy_static: bool = True) -> None:
- """Generate the PyPI README and optionally stage the static build."""
+ """Generate package metadata files and optionally stage the static build."""
(PACKAGE_DIR / "README.md").write_text(build_readme(), encoding="utf-8")
+ shutil.copyfile(LICENSE_FILE, PACKAGE_DIR / "LICENSE")
if not copy_static:
return
source = WEBSITE_DIR / "dist-static"
if not (source / "index.html").is_file():
raise FileNotFoundError(f"Studio static build is unavailable: {source}")
- shutil.rmtree(STATIC_DIR, ignore_errors=True)
- shutil.copytree(source, STATIC_DIR)
+ try:
+ shutil.rmtree(STATIC_DIR, ignore_errors=True)
+ shutil.copytree(source, STATIC_DIR)
+ finally:
+ STATIC_DIR.mkdir(parents=True, exist_ok=True)
+ (STATIC_DIR / ".gitignore").write_text(STATIC_GITIGNORE, encoding="utf-8")
def main() -> None:
diff --git a/tests/unit/test_daily_paper.py b/tests/unit/test_daily_paper.py
index 182a02d7..e0a027a3 100644
--- a/tests/unit/test_daily_paper.py
+++ b/tests/unit/test_daily_paper.py
@@ -503,6 +503,11 @@ def test_daily_paper_topics_parameter_defaults_to_empty():
}
+def test_daily_paper_cron_prioritizes_long_term_llm_memory():
+ """The scheduled workflow prioritizes papers about long-term LLM memory."""
+ assert _load_config("daily_cookbook")["jobs"]["daily_paper_cron"]["topics"] == "大模型长期记忆"
+
+
def test_daily_paper_hf_mirror_parameter_defaults_to_disabled():
"""The public job schema exposes an explicit Hugging Face mirror switch."""
use_hf_mirror = _load_config("daily_cookbook")["jobs"]["daily_paper"]["parameters"]["properties"]["use_hf_mirror"]
@@ -514,14 +519,14 @@ def test_daily_paper_hf_mirror_parameter_defaults_to_disabled():
}
-def test_daily_paper_cron_hf_mirror_uses_explicit_environment_switch(monkeypatch):
- """The scheduled workflow can opt in to the Hugging Face mirror."""
+def test_daily_paper_cron_hf_mirror_defaults_enabled_with_environment_override(monkeypatch):
+ """The scheduled workflow uses the mirror by default and supports an explicit override."""
monkeypatch.delenv("DAILY_PAPER_USE_HF_MIRROR", raising=False)
- assert _load_config("daily_cookbook")["jobs"]["daily_paper_cron"]["use_hf_mirror"] is False
-
- monkeypatch.setenv("DAILY_PAPER_USE_HF_MIRROR", "true")
assert _load_config("daily_cookbook")["jobs"]["daily_paper_cron"]["use_hf_mirror"] is True
+ monkeypatch.setenv("DAILY_PAPER_USE_HF_MIRROR", "false")
+ assert _load_config("daily_cookbook")["jobs"]["daily_paper_cron"]["use_hf_mirror"] is False
+
def test_paper_pick_list_uses_an_object_root_for_tool_output():
"""AgentScope function arguments require an object-root JSON schema."""
diff --git a/tests/unit/test_package_versions.py b/tests/unit/test_package_versions.py
index fb5b9b94..5b69038d 100644
--- a/tests/unit/test_package_versions.py
+++ b/tests/unit/test_package_versions.py
@@ -1,28 +1,204 @@
"""Keep the separately published distributions version-compatible."""
+import importlib.util
from pathlib import Path
import tomllib
+from types import ModuleType
-import reme
-from scripts.package_studio import build_readme
+import pytest
+
+REPOSITORY = Path(__file__).resolve().parents[2]
+
+
+def _load_script(name: str) -> ModuleType:
+ script = REPOSITORY / "scripts" / f"{name}.py"
+ spec = importlib.util.spec_from_file_location(name, script)
+ if spec is None or spec.loader is None:
+ raise ImportError(f"Unable to load {script}")
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+
+
+bump_version = _load_script("bump_version")
+package_studio = _load_script("package_studio")
def test_studio_package_and_extra_versions_match_reme() -> None:
"""Require one version bump to update both wheels and their exact pin."""
- repository = Path(__file__).resolve().parents[2]
- main_config = tomllib.loads((repository / "pyproject.toml").read_text(encoding="utf-8"))
+ main_config = tomllib.loads((REPOSITORY / "pyproject.toml").read_text(encoding="utf-8"))
studio_config = tomllib.loads(
- (repository / "packages" / "reme_ai_studio" / "pyproject.toml").read_text(encoding="utf-8"),
+ (REPOSITORY / "packages" / "reme_ai_studio" / "pyproject.toml").read_text(encoding="utf-8"),
)
- expected_dependency = f"reme-ai-studio=={reme.__version__}"
+ version = bump_version.read_version(REPOSITORY)
+ expected_dependency = f"reme-ai-studio=={version}"
- assert studio_config["project"]["version"] == reme.__version__
+ assert studio_config["project"]["version"] == version
assert main_config["project"]["optional-dependencies"]["web"] == [expected_dependency]
assert expected_dependency in main_config["project"]["optional-dependencies"]["core"]
+def _write_version_fixture(repository: Path, *, studio_version: str = "1.2.3") -> None:
+ (repository / "reme").mkdir()
+ (repository / "packages" / "reme_ai_studio").mkdir(parents=True)
+ (repository / "reme" / "__init__.py").write_text('__version__ = "1.2.3"\n', encoding="utf-8")
+ (repository / "pyproject.toml").write_text(
+ """[project]
+name = "reme-ai"
+
+[project.optional-dependencies]
+web = ["reme-ai-studio==1.2.3"]
+core = ["example", "reme-ai-studio==1.2.3"]
+""",
+ encoding="utf-8",
+ )
+ (repository / "packages" / "reme_ai_studio" / "pyproject.toml").write_text(
+ f'[project]\nname = "reme-ai-studio"\nversion = "{studio_version}"\n',
+ encoding="utf-8",
+ )
+
+
+def test_bump_version_updates_both_packages_and_exact_pins(tmp_path: Path) -> None:
+ """Update the two package versions and both dependency declarations together."""
+ _write_version_fixture(tmp_path)
+
+ previous_version = bump_version.bump_version("1.2.4", tmp_path)
+
+ assert previous_version == "1.2.3"
+ assert (tmp_path / "reme" / "__init__.py").read_text(encoding="utf-8") == '__version__ = "1.2.4"\n'
+ assert (tmp_path / "pyproject.toml").read_text(encoding="utf-8").count("reme-ai-studio==1.2.4") == 2
+ studio_config = tomllib.loads(
+ (tmp_path / "packages" / "reme_ai_studio" / "pyproject.toml").read_text(encoding="utf-8"),
+ )
+ assert studio_config["project"]["version"] == "1.2.4"
+
+
+def test_bump_version_rejects_inconsistent_sources_before_writing(
+ tmp_path: Path,
+) -> None:
+ """Refuse to update any file if the current release metadata has drifted."""
+ _write_version_fixture(tmp_path, studio_version="1.2.2")
+ version_file = tmp_path / "reme" / "__init__.py"
+ original_version_text = version_file.read_text(encoding="utf-8")
+
+ with pytest.raises(ValueError, match=r"project.version is '1.2.2'; expected '1.2.3'"):
+ bump_version.bump_version("1.2.4", tmp_path)
+
+ assert version_file.read_text(encoding="utf-8") == original_version_text
+
+
+@pytest.mark.parametrize("version", ["release", "1..2", "1.2.3_"])
+def test_bump_version_rejects_invalid_pep440_versions(tmp_path: Path, version: str) -> None:
+ """Reject invalid release versions before changing any source file."""
+ _write_version_fixture(tmp_path)
+ version_file = tmp_path / "reme" / "__init__.py"
+ original_version_text = version_file.read_text(encoding="utf-8")
+
+ with pytest.raises(ValueError, match="Invalid PEP 440 version"):
+ bump_version.bump_version(version, tmp_path)
+
+ assert version_file.read_text(encoding="utf-8") == original_version_text
+
+
+def test_check_versions_reports_release_tag_mismatch(tmp_path: Path) -> None:
+ """Explain which source must change when a release tag does not match."""
+ _write_version_fixture(tmp_path)
+
+ with pytest.raises(ValueError, match=r"package version is '1.2.3'; release expects '1.2.4'"):
+ bump_version.check_versions(tmp_path, "v1.2.4")
+
+
+def test_read_version_identifies_invalid_source_file(tmp_path: Path) -> None:
+ """Point maintainers to the invalid Python version declaration."""
+ _write_version_fixture(tmp_path)
+ version_file = tmp_path / "reme" / "__init__.py"
+ version_file.write_text('__version__ = "1..2"\n', encoding="utf-8")
+
+ with pytest.raises(ValueError, match=rf"{version_file}.*Fix the __version__ declaration"):
+ bump_version.check_versions(tmp_path)
+
+
+def test_check_versions_identifies_invalid_toml_file(tmp_path: Path) -> None:
+ """Point maintainers to malformed package metadata."""
+ _write_version_fixture(tmp_path)
+ config_file = tmp_path / "pyproject.toml"
+ config_file.write_text("[project\n", encoding="utf-8")
+
+ with pytest.raises(ValueError, match=rf"{config_file}.*cannot read valid TOML.*Fix this file"):
+ bump_version.check_versions(tmp_path)
+
+
+def test_check_versions_identifies_missing_table(tmp_path: Path) -> None:
+ """Explain which required TOML table must be restored."""
+ _write_version_fixture(tmp_path)
+ config_file = tmp_path / "pyproject.toml"
+ config_file.write_text('[project]\nname = "reme-ai"\n', encoding="utf-8")
+
+ with pytest.raises(ValueError, match=rf"{config_file}.*\[project.optional-dependencies\].*Add or fix"):
+ bump_version.check_versions(tmp_path)
+
+
+def test_bump_version_rolls_back_when_a_write_fails(monkeypatch, tmp_path: Path) -> None:
+ """Restore earlier files when a later atomic replacement fails."""
+ # pylint: disable=protected-access
+ _write_version_fixture(tmp_path)
+ paths = bump_version._paths(tmp_path)
+ original = {path: path.read_text(encoding="utf-8") for path in paths}
+ write_atomic = bump_version._write_atomic
+ attempts = 0
+
+ def fail_second_write(path: Path, content: str) -> None:
+ nonlocal attempts
+ attempts += 1
+ if attempts == 2:
+ raise OSError("simulated write failure")
+ write_atomic(path, content)
+
+ monkeypatch.setattr(bump_version, "_write_atomic", fail_second_write)
+
+ with pytest.raises(RuntimeError, match="all changed files were restored"):
+ bump_version.bump_version("1.2.4", tmp_path)
+
+ assert {path: path.read_text(encoding="utf-8") for path in paths} == original
+
+
def test_studio_readme_is_generated_from_website_docs() -> None:
"""Keep the packaged PyPI description synchronized with the source docs."""
- repository = Path(__file__).resolve().parents[2]
- packaged_readme = repository / "packages" / "reme_ai_studio" / "README.md"
- assert packaged_readme.read_text(encoding="utf-8") == build_readme()
+ packaged_readme = REPOSITORY / "packages" / "reme_ai_studio" / "README.md"
+ assert packaged_readme.read_text(encoding="utf-8") == package_studio.build_readme()
+
+
+def test_studio_package_preparation_copies_license(monkeypatch, tmp_path: Path) -> None:
+ """Include the repository license in the independently distributed Studio package."""
+ package_dir = tmp_path / "reme_ai_studio"
+ package_dir.mkdir()
+ monkeypatch.setattr(package_studio, "PACKAGE_DIR", package_dir)
+
+ package_studio.prepare_package(copy_static=False)
+
+ assert (package_dir / "LICENSE").read_text(encoding="utf-8") == (REPOSITORY / "LICENSE").read_text(
+ encoding="utf-8",
+ )
+
+
+def test_studio_package_preparation_preserves_static_gitignore(monkeypatch, tmp_path: Path) -> None:
+ """Keep generated static assets ignored after staging the Studio build."""
+ package_dir = tmp_path / "reme_ai_studio"
+ package_dir.mkdir()
+ website_dir = tmp_path / "website"
+ source_dir = website_dir / "dist-static"
+ source_dir.mkdir(parents=True)
+ (source_dir / "index.html").write_text("", encoding="utf-8")
+ static_dir = package_dir / "src" / "reme_ai_studio" / "static"
+ static_dir.mkdir(parents=True)
+ (static_dir / ".gitignore").write_text(package_studio.STATIC_GITIGNORE, encoding="utf-8")
+
+ monkeypatch.setattr(package_studio, "PACKAGE_DIR", package_dir)
+ monkeypatch.setattr(package_studio, "WEBSITE_DIR", website_dir)
+ monkeypatch.setattr(package_studio, "STATIC_DIR", static_dir)
+ monkeypatch.setattr(package_studio, "build_readme", lambda: "# ReMe Studio\n")
+
+ package_studio.prepare_package()
+
+ assert (static_dir / "index.html").is_file()
+ assert (static_dir / ".gitignore").read_text(encoding="utf-8") == package_studio.STATIC_GITIGNORE