docs(readme): polish launch highlights and banner (#261)
* docs: simplify README launch highlights * docs(readme): use six launch highlights * docs(readme): use optimized banner asset * ci: lint pull request titles
This commit is contained in:
12
.github/workflows/commits.yml
vendored
12
.github/workflows/commits.yml
vendored
@ -13,6 +13,18 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
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:
|
messages:
|
||||||
name: commit messages
|
name: commit messages
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
6
Makefile
6
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:
|
help:
|
||||||
@echo "Targets:"
|
@echo "Targets:"
|
||||||
@ -7,6 +7,7 @@ help:
|
|||||||
@echo " lint ruff + import-linter + repo hygiene + datetime discipline + openapi drift"
|
@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 " 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-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-assets Block committed images, videos, and asset/media directories"
|
||||||
@echo " check-deprecated-names Block deprecated product names"
|
@echo " check-deprecated-names Block deprecated product names"
|
||||||
@echo " check-cjk Scan for CJK outside the language-policy allowlist (advisory)"
|
@echo " check-cjk Scan for CJK outside the language-policy allowlist (advisory)"
|
||||||
@ -48,6 +49,9 @@ docs-check:
|
|||||||
check-commits:
|
check-commits:
|
||||||
python3 scripts/check_commit_messages.py $(RANGE)
|
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,
|
# Repository media hygiene gate. Images/videos belong in external hosting,
|
||||||
# release artifacts, or other approved storage, then linked from docs.
|
# release artifacts, or other approved storage, then linked from docs.
|
||||||
check-assets:
|
check-assets:
|
||||||
|
|||||||
53
README.md
53
README.md
@ -1,6 +1,6 @@
|
|||||||
<div align="center" id="readme-top">
|
<div align="center" id="readme-top">
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://x.com/evermind"><img src="https://img.shields.io/badge/EverMind-000000?labelColor=gray&style=for-the-badge&logo=x&logoColor=white" alt="X"></a>
|
<a href="https://x.com/evermind"><img src="https://img.shields.io/badge/EverMind-000000?labelColor=gray&style=for-the-badge&logo=x&logoColor=white" alt="X"></a>
|
||||||
@ -54,53 +54,36 @@
|
|||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Markdown-First Memory</strong><br>
|
<strong>Markdown As Source Of Truth</strong><br>
|
||||||
Memory is persisted as plain Markdown: visible, auditable, hand-editable,
|
All memory is persisted as <code>.md</code> files: readable, editable,
|
||||||
Git-friendly, and owned by the user.
|
grep-able, Git-versioned, and openable directly in Obsidian.
|
||||||
</td>
|
</td>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Lightweight Local Stack</strong><br>
|
<strong>Local Three-Part Stack</strong><br>
|
||||||
Install with Python. SQLite tracks runtime state; LanceDB powers vector,
|
Markdown + SQLite + LanceDB keep vectors, BM25, and scalar filters
|
||||||
BM25, and scalar-filter retrieval locally.
|
local. No MongoDB, Elasticsearch, or Redis required.
|
||||||
</td>
|
</td>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Layered Memory Model</strong><br>
|
<strong>Dual-Track Memory</strong><br>
|
||||||
User memory and agent memory are first-class today. Wiki-style knowledge
|
Agent memory (<code>cases</code> / <code>skills</code>) and user memory
|
||||||
is the next layer in the roadmap.
|
(<code>episodes</code> / <code>profile</code>) are extracted independently.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Self-Evolving Agents</strong><br>
|
|
||||||
Agent memory can extract reusable cases and skills from repeated
|
|
||||||
experience, so workflows become smarter over time.
|
|
||||||
</td>
|
|
||||||
<td width="33%" valign="top">
|
|
||||||
<strong>Multimodal Ingestion</strong><br>
|
<strong>Multimodal Ingestion</strong><br>
|
||||||
Text, image, audio, documents, PDF, HTML, and email can be parsed into
|
Text, images, audio, documents, PDFs, HTML, and email are unified into
|
||||||
memory through the optional multimodal pipeline.
|
searchable memory.
|
||||||
</td>
|
</td>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Online And Offline Strategy Control</strong><br>
|
<strong>Self-Evolution</strong><br>
|
||||||
Online extraction and offline evolution stay separate, with configurable
|
Common skills are extracted from real usage; repeated patterns become
|
||||||
prompts and models at each step. Dreaming is coming next.
|
reusable workflows, no retraining required.
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td width="33%" valign="top">
|
|
||||||
<strong>Orthogonal Memory Scope</strong><br>
|
|
||||||
Owner, memory type, and scope are independent: search by user, agent,
|
|
||||||
app, project, session, and structured filters.
|
|
||||||
</td>
|
</td>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Progressive Disclosure</strong><br>
|
<strong>Orthogonal Retrieval</strong><br>
|
||||||
Readable memory surfaces stay simple while deeper facts, cases, and
|
Search independently by <code>user_id</code>, <code>agent_id</code>,
|
||||||
skills remain available.
|
<code>app_id</code>, <code>project_id</code>, and <code>session_id</code>.
|
||||||
</td>
|
|
||||||
<td width="33%" valign="top">
|
|
||||||
<strong>Modular By Design</strong><br>
|
|
||||||
EverAlgo owns algorithms; EverOS owns runtime, persistence, online flows,
|
|
||||||
and offline evolution.
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<div align="center" id="readme-top">
|
<div align="center" id="readme-top">
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://x.com/evermind"><img src="https://img.shields.io/badge/EverMind-000000?labelColor=gray&style=for-the-badge&logo=x&logoColor=white" alt="X"></a>
|
<a href="https://x.com/evermind"><img src="https://img.shields.io/badge/EverMind-000000?labelColor=gray&style=for-the-badge&logo=x&logoColor=white" alt="X"></a>
|
||||||
@ -54,44 +54,30 @@
|
|||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Markdown-First Memory</strong><br>
|
<strong>Markdown As Source Of Truth</strong><br>
|
||||||
记忆以普通 Markdown 持久化:可见、可审计、可手动编辑、Git 友好,并由用户自己拥有。
|
所有记忆持久化为 <code>.md</code> 文件:可读、可改、可 grep、可 Git 版本化,也可直接用 Obsidian 打开。
|
||||||
</td>
|
</td>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Lightweight Local Stack</strong><br>
|
<strong>Local Three-Part Stack</strong><br>
|
||||||
用 Python 即可安装。SQLite 负责运行时状态;LanceDB 在本地提供向量、BM25 和结构化过滤检索。
|
Markdown + SQLite + LanceDB 在本地完成向量、BM25 和标量过滤检索,无需 MongoDB、Elasticsearch 或 Redis。
|
||||||
</td>
|
</td>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Layered Memory Model</strong><br>
|
<strong>Dual-Track Memory</strong><br>
|
||||||
用户记忆和 Agent 记忆现在是一等公民。Wiki 式知识层是路线图中的下一层。
|
Agent 记忆(<code>cases</code> / <code>skills</code>)与用户记忆(<code>episodes</code> / <code>profile</code>)独立提取,互不污染。
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Self-Evolving Agents</strong><br>
|
|
||||||
Agent 记忆可以从重复经验中提取可复用的 cases 和 skills,让工作流随着时间变得更聪明。
|
|
||||||
</td>
|
|
||||||
<td width="33%" valign="top">
|
|
||||||
<strong>Multimodal Ingestion</strong><br>
|
<strong>Multimodal Ingestion</strong><br>
|
||||||
文本、图片、音频、文档、PDF、HTML 和邮件都可以通过可选的多模态管线解析进记忆。
|
文本、图像、音频、文档、PDF、HTML 和邮件统一抽取为可检索的记忆形态。
|
||||||
</td>
|
</td>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Online And Offline Strategy Control</strong><br>
|
<strong>Self-Evolution</strong><br>
|
||||||
在线提取和离线进化保持分离,并且每一步都可以配置 prompts 和 models。Dreaming 即将到来。
|
从真实使用经验中自动抽取共性 skills,重复模式沉淀为可复用流程,无需重训。
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td width="33%" valign="top">
|
|
||||||
<strong>Orthogonal Memory Scope</strong><br>
|
|
||||||
Owner、memory type 和 scope 相互独立:可以按 user、agent、app、project、session 和结构化 filters 搜索。
|
|
||||||
</td>
|
</td>
|
||||||
<td width="33%" valign="top">
|
<td width="33%" valign="top">
|
||||||
<strong>Progressive Disclosure</strong><br>
|
<strong>Orthogonal Retrieval</strong><br>
|
||||||
可读记忆界面保持简单,同时更深层的 facts、cases 和 skills 仍然可以被系统使用。
|
按 <code>user_id</code>、<code>agent_id</code>、<code>app_id</code>、<code>project_id</code> 和 <code>session_id</code> 五维独立检索。
|
||||||
</td>
|
|
||||||
<td width="33%" valign="top">
|
|
||||||
<strong>Modular By Design</strong><br>
|
|
||||||
EverAlgo 负责算法;EverOS 负责运行时、持久化、在线流程和离线进化。
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
70
scripts/check_pr_title.py
Normal file
70
scripts/check_pr_title.py
Normal file
@ -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: <type>[(scope)][!]: <description>\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())
|
||||||
56
tests/unit/test_scripts/test_check_pr_title.py
Normal file
56
tests/unit/test_scripts/test_check_pr_title.py
Normal file
@ -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: <type>[(scope)][!]: <description>" 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
|
||||||
Reference in New Issue
Block a user