ci(build): 增加第三阶段全量门禁与验收记录

This commit is contained in:
2026-09-07 16:57:36 +08:00
parent 724e9a600f
commit 6bd7471874
4 changed files with 173 additions and 1 deletions
+68
View File
@@ -0,0 +1,68 @@
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main, "feat/**", "fix/**", "chore/**"]
workflow_dispatch:
jobs:
docs-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: git diff --check
- run: python scripts/check-doc-links.py
backend-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install uv==0.9.24
- run: uv sync --frozen
working-directory: backend
- run: uv run python -m compileall -q app
working-directory: backend
- run: uv run pytest
working-directory: backend
service-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install uv==0.9.24
- run: uv sync --frozen
working-directory: backend
- run: uv sync --frozen && uv run pytest
working-directory: server sync
- run: uv sync --frozen && uv run pytest
working-directory: community-server
- run: backend/.venv/bin/python scripts/phase3-isolated-smoke.py
frontend-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "22", cache: pnpm, cache-dependency-path: frontend/pnpm-lock.yaml }
- run: corepack enable && corepack prepare pnpm@10.28.0 --activate
- run: pnpm install --frozen-lockfile
working-directory: frontend
- run: pnpm test && pnpm type-check && pnpm build
working-directory: frontend
rust-core:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with: { components: rustfmt, clippy }
- run: cargo fmt --check && cargo test --lib --locked && cargo clippy --lib --locked -- -D warnings
working-directory: frontend/src-tauri
+4 -1
View File
@@ -4,6 +4,8 @@
> 本文件用于团队开发期间快速配置环境、启动项目并了解当前实现状态,不是正式的项目 README。
> 第三阶段分支状态(2026-09-07):已加入 Tauri/Rust 原生 Vault 预览、Sync v1 服务原型、签名社区目录原型和七类社区入口。完整 Sidecar、Stronghold、生产插件隔离、同步客户端、升级回滚及三平台发布门禁仍未交付;详见[第三阶段实施与验收记录](docs/development/第三阶段实施与验收记录.md)。
NotesAgent 是本地优先的 AI 笔记与知识库项目。当前可运行形态为 Vue/Vite Web 前端与 FastAPI AI CoreMarkdown 和附件保存在本地 Vault,SQLite 管理元数据、全文索引、向量空间、搜索历史、AI 会话、任务、Agent Trace、多模态任务及运行诊断。AI 对话已接入知识库检索,会话与消息由后端持久化并供 Web 和桌面客户端共用。
截至 2026-09-06,第一阶段及第二阶段 A~F 的工程范围已经合并到 `main`。当前已完成真实 Workspace、混合检索与知识库问答、Agent/Tool/Permission、Skill/Plugin、MCP 配置与调用、模型提供商与路由、RAG Benchmark,以及本地 Embedding、音频转写和片段级声纹聚类。Tauri/Rust Host、Stronghold、原生多 Vault 文件系统、生产级 MCP 沙箱和 Sync Server 尚未接入。
@@ -15,7 +17,8 @@ NotesAgent/
├── frontend/ Vue 3 + TypeScript + Vite 前端
├── backend/ FastAPI AI Core、SQLite 与本地模型运行管理
├── docs/ 架构、契约、开发说明、协作规范与问题复盘
── server sync/ 云同步服务预留目录,当前未实现
── community-server/ 社区目录、签名发行与审核原型
└── server sync/ 独立 Sync v1 服务原型
```
## 当前能力
+40
View File
@@ -0,0 +1,40 @@
"""检查仓库 Markdown 相对链接;忽略远程 URL、页内锚点与示例占位。"""
from __future__ import annotations
import re
from pathlib import Path
from urllib.parse import unquote
ROOT = Path(__file__).resolve().parents[1]
LINK = re.compile(r"(?<!!)\[[^\]]*\]\(([^)]+)\)")
def main() -> int:
failures: list[str] = []
for document in ROOT.rglob("*.md"):
if any(part in {".build", ".venv", "node_modules", "target"} for part in document.parts):
continue
source = document.read_text(encoding="utf-8")
for raw in LINK.findall(source):
target = raw.strip().strip("<>").split()[0]
if not target or target.startswith(("#", "http://", "https://", "mailto:")):
continue
path = unquote(target.split("#", 1)[0])
resolved = (document.parent / path).resolve()
try:
resolved.relative_to(ROOT)
except ValueError:
failures.append(f"{document.relative_to(ROOT)}: 链接越出仓库: {target}")
continue
if not resolved.exists():
failures.append(f"{document.relative_to(ROOT)}: 不存在: {target}")
if failures:
print("\n".join(failures))
return 1
print("Markdown 相对链接检查通过")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+61
View File
@@ -0,0 +1,61 @@
"""在临时目录启动三个 ASGI 应用,不读取仓库 data 或调用外部服务。"""
from __future__ import annotations
import os
import subprocess
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def main() -> int:
with tempfile.TemporaryDirectory(prefix="notesagent-phase3-") as temporary:
root = Path(temporary)
os.environ.update({
"APP_DATA_DIR": str(root / "backend"),
"APP_DB_PATH": str(root / "backend" / "app.db"),
"APP_VAULT_PATH": str(root / "vault"),
"APP_ATTACHMENTS_PATH": str(root / "backend" / "attachments"),
})
sys.path.insert(0, str(ROOT / "backend"))
from fastapi.testclient import TestClient
from app.main import app as core
with TestClient(core) as client:
assert client.get("/health").json()["status"] == "ok"
assert client.get("/api/status").status_code == 200
def project_python(project: Path) -> Path:
candidates = [project / ".venv" / "Scripts" / "python.exe", project / ".venv" / "bin" / "python"]
return next(path for path in candidates if path.exists())
sync_code = """from pathlib import Path
from fastapi.testclient import TestClient
from sync_server.app import create_app
from sync_server.database import Database
from sync_server.storage import DiskObjects
root=Path(r'%s')
with TestClient(create_app(Database('sqlite:///'+str(root/'sync.db')),DiskObjects(root/'objects'),root/'staging')) as c:
assert c.get('/ready').json()=={'status':'ready','schema':1}
assert c.get('/sync/v1/handshake').json()['protocol']==1
""" % root
subprocess.run([project_python(ROOT / "server sync"), "-c", sync_code], cwd=ROOT / "server sync", check=True)
community_code = """from pathlib import Path
from fastapi.testclient import TestClient
from community.app import Registry,create_app
root=Path(r'%s')
with TestClient(create_app(Registry(root/'community.db'))) as c:
assert c.get('/health').json()['status']=='ok'
assert c.get('/catalog/v1/sources').json()['schema_version']==1
""" % root
subprocess.run([project_python(ROOT / "community-server"), "-c", community_code], cwd=ROOT / "community-server", check=True)
print("AI Core、Sync v1、Community v1 隔离冒烟通过")
return 0
if __name__ == "__main__":
raise SystemExit(main())