ci(build): 增加第三阶段全量门禁与验收记录
This commit is contained in:
@@ -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,6 +4,8 @@
|
||||
|
||||
> 本文件用于团队开发期间快速配置环境、启动项目并了解当前实现状态,不是正式的项目 README。
|
||||
|
||||
> 第三阶段分支状态(2026-09-07):已加入 Tauri/Rust 原生 Vault 预览、Sync v1 服务原型、签名社区目录原型和七类社区入口。完整 Sidecar、Stronghold、生产插件隔离、同步客户端、升级回滚及三平台发布门禁仍未交付;详见[第三阶段实施与验收记录](docs/development/第三阶段实施与验收记录.md)。
|
||||
|
||||
NotesAgent 是本地优先的 AI 笔记与知识库项目。当前可运行形态为 Vue/Vite Web 前端与 FastAPI AI Core:Markdown 和附件保存在本地 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 服务原型
|
||||
```
|
||||
|
||||
## 当前能力
|
||||
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user