diff --git a/.github/workflows/commits.yml b/.github/workflows/commits.yml index 12862334..936ce8f9 100644 --- a/.github/workflows/commits.yml +++ b/.github/workflows/commits.yml @@ -13,6 +13,18 @@ concurrency: cancel-in-progress: true jobs: + title: + name: pull request title + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Validate Conventional Commit PR title + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: make check-pr-title + messages: name: commit messages runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index f862e05a..905641b5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help install install-deps lint docs-check check-commits check-assets check-deprecated-names check-cjk check-datetime openapi check-openapi format test integration package cov ci clean +.PHONY: help install install-deps lint docs-check check-commits check-pr-title check-assets check-deprecated-names check-cjk check-datetime openapi check-openapi format test integration package cov ci clean help: @echo "Targets:" @@ -7,6 +7,7 @@ help: @echo " lint ruff + import-linter + repo hygiene + datetime discipline + openapi drift" @echo " docs-check Validate Markdown links, use-case banners, and issue template YAML" @echo " check-commits Validate Conventional Commit subjects for a git range" + @echo " check-pr-title Validate PR title uses Conventional Commit format" @echo " check-assets Block committed images, videos, and asset/media directories" @echo " check-deprecated-names Block deprecated product names" @echo " check-cjk Scan for CJK outside the language-policy allowlist (advisory)" @@ -48,6 +49,9 @@ docs-check: check-commits: python3 scripts/check_commit_messages.py $(RANGE) +check-pr-title: + python3 scripts/check_pr_title.py + # Repository media hygiene gate. Images/videos belong in external hosting, # release artifacts, or other approved storage, then linked from docs. check-assets: diff --git a/README.md b/README.md index bbbd1f0d..0146fb48 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-![EverOS banner](https://github.com/EverMind-AI/EverOS/releases/download/v1.0.0/everos-readme-banner.jpg) +![EverOS banner](https://github.com/EverMind-AI/EverOS/releases/download/v1.0.0/everos-readme-banner-optimized.jpg)

X @@ -54,53 +54,36 @@ - - - - -
-Markdown-First Memory
-Memory is persisted as plain Markdown: visible, auditable, hand-editable, -Git-friendly, and owned by the user. +Markdown As Source Of Truth
+All memory is persisted as .md files: readable, editable, +grep-able, Git-versioned, and openable directly in Obsidian.
-Lightweight Local Stack
-Install with Python. SQLite tracks runtime state; LanceDB powers vector, -BM25, and scalar-filter retrieval locally. +Local Three-Part Stack
+Markdown + SQLite + LanceDB keep vectors, BM25, and scalar filters +local. No MongoDB, Elasticsearch, or Redis required.
-Layered Memory Model
-User memory and agent memory are first-class today. Wiki-style knowledge -is the next layer in the roadmap. +Dual-Track Memory
+Agent memory (cases / skills) and user memory +(episodes / profile) are extracted independently.
-Self-Evolving Agents
-Agent memory can extract reusable cases and skills from repeated -experience, so workflows become smarter over time. -
Multimodal Ingestion
-Text, image, audio, documents, PDF, HTML, and email can be parsed into -memory through the optional multimodal pipeline. -
-Online And Offline Strategy Control
-Online extraction and offline evolution stay separate, with configurable -prompts and models at each step. Dreaming is coming next. -
-Orthogonal Memory Scope
-Owner, memory type, and scope are independent: search by user, agent, -app, project, session, and structured filters. +Text, images, audio, documents, PDFs, HTML, and email are unified into +searchable memory.
-Progressive Disclosure
-Readable memory surfaces stay simple while deeper facts, cases, and -skills remain available. +Self-Evolution
+Common skills are extracted from real usage; repeated patterns become +reusable workflows, no retraining required.
-Modular By Design
-EverAlgo owns algorithms; EverOS owns runtime, persistence, online flows, -and offline evolution. +Orthogonal Retrieval
+Search independently by user_id, agent_id, +app_id, project_id, and session_id.
diff --git a/README.zh-CN.md b/README.zh-CN.md index 1c3d1756..e1540a63 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,6 +1,6 @@

-![EverOS banner](https://github.com/EverMind-AI/EverOS/releases/download/v1.0.0/everos-readme-banner.jpg) +![EverOS banner](https://github.com/EverMind-AI/EverOS/releases/download/v1.0.0/everos-readme-banner-optimized.jpg)

X @@ -54,44 +54,30 @@ - - - - -
-Markdown-First Memory
-记忆以普通 Markdown 持久化:可见、可审计、可手动编辑、Git 友好,并由用户自己拥有。 +Markdown As Source Of Truth
+所有记忆持久化为 .md 文件:可读、可改、可 grep、可 Git 版本化,也可直接用 Obsidian 打开。
-Lightweight Local Stack
-用 Python 即可安装。SQLite 负责运行时状态;LanceDB 在本地提供向量、BM25 和结构化过滤检索。 +Local Three-Part Stack
+Markdown + SQLite + LanceDB 在本地完成向量、BM25 和标量过滤检索,无需 MongoDB、Elasticsearch 或 Redis。
-Layered Memory Model
-用户记忆和 Agent 记忆现在是一等公民。Wiki 式知识层是路线图中的下一层。 +Dual-Track Memory
+Agent 记忆(cases / skills)与用户记忆(episodes / profile)独立提取,互不污染。
-Self-Evolving Agents
-Agent 记忆可以从重复经验中提取可复用的 cases 和 skills,让工作流随着时间变得更聪明。 -
Multimodal Ingestion
-文本、图片、音频、文档、PDF、HTML 和邮件都可以通过可选的多模态管线解析进记忆。 -
-Online And Offline Strategy Control
-在线提取和离线进化保持分离,并且每一步都可以配置 prompts 和 models。Dreaming 即将到来。 -
-Orthogonal Memory Scope
-Owner、memory type 和 scope 相互独立:可以按 user、agent、app、project、session 和结构化 filters 搜索。 +文本、图像、音频、文档、PDF、HTML 和邮件统一抽取为可检索的记忆形态。
-Progressive Disclosure
-可读记忆界面保持简单,同时更深层的 facts、cases 和 skills 仍然可以被系统使用。 +Self-Evolution
+从真实使用经验中自动抽取共性 skills,重复模式沉淀为可复用流程,无需重训。
-Modular By Design
-EverAlgo 负责算法;EverOS 负责运行时、持久化、在线流程和离线进化。 +Orthogonal Retrieval
+按 user_idagent_idapp_idproject_idsession_id 五维独立检索。
diff --git a/scripts/check_pr_title.py b/scripts/check_pr_title.py new file mode 100644 index 00000000..5935b75c --- /dev/null +++ b/scripts/check_pr_title.py @@ -0,0 +1,70 @@ +"""Validate pull request titles against the EverOS Conventional Commits policy.""" + +from __future__ import annotations + +import importlib.util +import os +import sys +from pathlib import Path +from types import ModuleType + +_SCRIPT_DIR = Path(__file__).resolve().parent + + +def _load_commit_policy() -> ModuleType: + policy_path = _SCRIPT_DIR / "check_commit_messages.py" + spec = importlib.util.spec_from_file_location("_commit_message_policy", policy_path) + if spec is None or spec.loader is None: + raise RuntimeError(f"Unable to load commit policy from {policy_path}") + + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +_POLICY = _load_commit_policy() +ALLOWED_TYPES = _POLICY.ALLOWED_TYPES +MAX_TITLE_LENGTH = _POLICY.MAX_TITLE_LENGTH +TITLE_RE = _POLICY.TITLE_RE + + +def validate_title(title: str) -> list[str]: + title = title.strip() + if not title: + return ["missing PR title"] + + if len(title) > MAX_TITLE_LENGTH: + return [f"PR title is {len(title)} chars; max is {MAX_TITLE_LENGTH}: {title}"] + + if not TITLE_RE.match(title): + allowed = ", ".join(ALLOWED_TYPES) + return [ + f"invalid PR title: {title}\n" + " expected: [(scope)][!]: \n" + f" allowed types: {allowed}" + ] + + return [] + + +def _title_from_args_or_env(argv: list[str]) -> str: + if argv: + return " ".join(argv) + return os.getenv("PR_TITLE", "") + + +def main(argv: list[str] | None = None) -> int: + title = _title_from_args_or_env(sys.argv[1:] if argv is None else argv) + failures = validate_title(title) + if failures: + print("Pull request title check failed:") + print("\n".join(failures)) + return 1 + + print("Pull request title follows Conventional Commits.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/unit/test_scripts/test_check_pr_title.py b/tests/unit/test_scripts/test_check_pr_title.py new file mode 100644 index 00000000..852f2a59 --- /dev/null +++ b/tests/unit/test_scripts/test_check_pr_title.py @@ -0,0 +1,56 @@ +"""Self-tests for ``scripts/check_pr_title.py``.""" + +from __future__ import annotations + +import importlib.util +import sys +from pathlib import Path + +_REPO_ROOT = Path(__file__).resolve().parents[3] +_CHECKER_PATH = _REPO_ROOT / "scripts" / "check_pr_title.py" + + +def _load_checker(): + assert _CHECKER_PATH.exists(), "PR title checker should exist" + spec = importlib.util.spec_from_file_location("_pr_title_checker", _CHECKER_PATH) + assert spec and spec.loader + mod = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = mod + spec.loader.exec_module(mod) + return mod + + +def test_conventional_pr_title_is_allowed() -> None: + checker = _load_checker() + + assert checker.validate_title("docs(readme): polish launch highlights") == [] + + +def test_bracketed_codex_pr_title_is_blocked() -> None: + checker = _load_checker() + + failures = checker.validate_title("[codex] simplify README launch highlights") + + assert len(failures) == 1 + assert "invalid PR title" in failures[0] + assert "expected: [(scope)][!]: " in failures[0] + + +def test_long_pr_title_is_blocked() -> None: + checker = _load_checker() + title = ( + "docs(readme): polish launch highlights and banner with a title that is " + "too long" + ) + + failures = checker.validate_title(title) + + assert len(failures) == 1 + assert "max is 72" in failures[0] + + +def test_main_reads_pr_title_environment(monkeypatch) -> None: + checker = _load_checker() + monkeypatch.setenv("PR_TITLE", "docs(readme): polish launch highlights") + + assert checker.main([]) == 0