From 5f8b2e8becf51e3dd49b92082796bcc2c3117fb8 Mon Sep 17 00:00:00 2001 From: lanrtop Date: Fri, 19 Jun 2026 13:00:22 +0900 Subject: [PATCH] =?UTF-8?q?feat(onboarding):=20=E6=8E=A5=E5=85=A5=E5=8C=85?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E5=88=86=E5=8F=91=20+=20apply=5Fonboarding?= =?UTF-8?q?=5Fpack=20=E5=90=8E=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dispatch_templates 向项目种入 AGENTS.md/lefthook.yml/git-workflow.md 标准模板(蓝本取自 enterprise-system,已通用化),不存在才写、绝不覆盖用户已有;apply_onboarding_pack 组合模板分发 + apply_blueprint_buff 返回 OnboardingReport。含 3 内嵌模板 + 不覆盖单测 + commands.ts 封装。前端 UI 待接。 --- .blueprint/modules/onboarding-pack.md | 10 +- src-tauri/resources/onboarding/AGENTS.md.tmpl | 44 +++++++ .../resources/onboarding/git-workflow.md.tmpl | 63 ++++++++++ .../resources/onboarding/lefthook.yml.tmpl | 22 ++++ src-tauri/src/commands/mod.rs | 1 + src-tauri/src/commands/onboarding.rs | 115 ++++++++++++++++++ src-tauri/src/lib.rs | 2 + src/lib/commands.ts | 16 +++ 8 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 src-tauri/resources/onboarding/AGENTS.md.tmpl create mode 100644 src-tauri/resources/onboarding/git-workflow.md.tmpl create mode 100644 src-tauri/resources/onboarding/lefthook.yml.tmpl create mode 100644 src-tauri/src/commands/onboarding.rs diff --git a/.blueprint/modules/onboarding-pack.md b/.blueprint/modules/onboarding-pack.md index 6f61644..3f56cc6 100644 --- a/.blueprint/modules/onboarding-pack.md +++ b/.blueprint/modules/onboarding-pack.md @@ -67,14 +67,14 @@ git 是「过去时 + 量化」(做了什么),蓝图是「将来时 + 语 > 已过 /architect + /splitter。A→B、C1→C2 两条链可并行,D 依赖 C1+B;**C1(飞轮地基)建议优先开工**。 -### 📋 A 模板分发引擎 -- status: planned +### ✅ A 模板分发引擎 +- status: done - complexity: M - files: src-tauri/src/commands/onboarding.rs, src-tauri/resources/onboarding/{AGENTS.md,lefthook.yml,git-workflow.md}.tmpl - acceptance: include_str! 内嵌 3 个标准模板(蓝本取自 enterprise-system);分发时文件不存在→写入、已存在→跳过并在报告标注,绝不覆盖;复用 mcp_inject 的路径解析(含 WSL);返回每个模板的 written/skipped/failed -### 📋 B apply_onboarding_pack 编排 + 前端入口 -- status: planned +### 🔵 B apply_onboarding_pack 编排 + 前端入口 +- status: in_progress - complexity: M - depends: onboarding-pack(卡A) - files: src-tauri/src/commands/onboarding.rs, src-tauri/src/lib.rs, src/lib/commands.ts, src/components/dashboard/ProjectCard.tsx @@ -93,7 +93,7 @@ git 是「过去时 + 量化」(做了什么),蓝图是「将来时 + 语 - files: src-tauri/src/commands/buff.rs, src-tauri/src/commands/blueprint.rs - acceptance: check_project 用 `last_commit..HEAD` 取 commit message(git log,复用现有 WSL 路径处理)→ 调 C1 解析写 commit_metrics;首次接入(last_commit 空)限解析最近 N 条;append_usage_snapshot 的 rework_count 改查 commit_metrics(is_rework=1 AND is_ci_auto=0)取代 parse_notes_metrics -### 📋 D 数据可信度提示 +### 🔵 D 数据可信度提示 - status: planned - complexity: S - depends: onboarding-pack(卡 C1、卡 B) diff --git a/src-tauri/resources/onboarding/AGENTS.md.tmpl b/src-tauri/resources/onboarding/AGENTS.md.tmpl new file mode 100644 index 0000000..999e0cd --- /dev/null +++ b/src-tauri/resources/onboarding/AGENTS.md.tmpl @@ -0,0 +1,44 @@ + — AI Agent 上下文 + +> 本文件遵循 [AGENTS.md 开放规范](https://agents.md/),供 Claude Code、Qwen Code、Cursor、Copilot 等 AI 工具读取。 +> 由炼境接入包分发。请按项目实际补全下方 TODO 段落。 + +## 项目概况 + + + +## 核心开发约束 + +### 包管理 +- **pnpm 唯一**:禁止 npm / yarn / npx / bun +- lock 文件只有 `pnpm-lock.yaml`,不生成其他 + +### 代码规范 +- TypeScript 优先,避免 `any` +- 函数/变量 camelCase;组件 PascalCase +- 异步统一 `async/await`,不用裸 Promise 链 +- 错误处理明确 `try/catch`,不吞异常 +- 优先编辑现有文件,避免创建不必要的新文件 + +## Git 工作流 + +见 `docs/ai-context/git-workflow.md`(分支策略 + Conventional Commits + PR 规范)。 +**commit 必须符合 Conventional Commits**(lefthook 强制),炼境据此采集飞轮数据。 + +## 架构与模块 + + + +## 多 AI CLI 协作 + +本项目可由多个 AI CLI 协同。交接时把结果总结成 3 条传给下一个: + +``` +1. 已完成:<改了哪些文件 + 核心决策> +2. 待验证:<需要确认的地方> +3. 约束继承:<影响后续的关键限制> +``` + +## 关键文档索引 + + diff --git a/src-tauri/resources/onboarding/git-workflow.md.tmpl b/src-tauri/resources/onboarding/git-workflow.md.tmpl new file mode 100644 index 0000000..ce6dade --- /dev/null +++ b/src-tauri/resources/onboarding/git-workflow.md.tmpl @@ -0,0 +1,63 @@ +# Git 工作流 — AI Agent 操作指南 + +> 把此文件粘贴给任何 AI Agent,让它了解本项目的 git 工作方式。由炼境接入包分发。 + +## 分支策略 + +``` +dev ← 日常开发在这里 commit/push + ↓ +main ← 稳定分支,通过 PR 合入(合入时触发 CI code review) +``` + +- 小改动可直接 push `dev` +- 功能完整后开 PR(dev → main) +- 不要直接 push `main`,除非紧急热修复 + +## Commit 规范(lefthook 强制) + +格式:`(): <描述>` + +| type | 用途 | +|------|------| +| `feat` | 新功能 | +| `fix` | Bug 修复 | +| `chore` | 配置、依赖、脚本 | +| `docs` | 文档 | +| `refactor` | 重构(不改功能) | +| `ci` | CI 配置 | +| `perf` | 性能优化 | +| `style` | 代码格式 | +| `test` | 测试 | + +`scope` 用「模块/子模块」二级结构(如 `auth/login`、`api/user`、`db/order`),对应项目功能模块。 + +```bash +git commit -m "feat(auth/login): 新增手机号登录" +git commit -m "fix(api/user): 修正分页 off-by-one" +``` + +> 炼境会解析这些 commit 的 type/scope 作为飞轮数据:`fix` 计入返工率、`scope` 归到模块。 +> **commit 写得越规范,飞轮数据越准。** + +## 一个 commit = 一件独立的事 + +不要 `git add -A` 一次提交所有。按功能分批: + +```bash +git add <相关文件> +git commit -m "feat(模块): 一件事" +``` + +## PR 大小控制 + +PR 别攒太大(建议每 3~5 个 commit 或几百行内就开一次),否则 CI code review 易超时失效。 +「所有检测通过」≠ 代码被审查过——大 PR 即使 CI 全绿,review 可能已静默失效。 + +## 常用命令 + +```bash +git status --short +git log --oneline -10 +git reset --soft HEAD~1 # 撤销最后一次 commit(保留改动) +``` diff --git a/src-tauri/resources/onboarding/lefthook.yml.tmpl b/src-tauri/resources/onboarding/lefthook.yml.tmpl new file mode 100644 index 0000000..b30ed28 --- /dev/null +++ b/src-tauri/resources/onboarding/lefthook.yml.tmpl @@ -0,0 +1,22 @@ +# 接入包标准 lefthook 配置(炼境分发) +# 作用:强制 Conventional Commits + 提交前类型检查 +# 安装:pnpm add -D lefthook && pnpm exec lefthook install + +pre-commit: + parallel: true + commands: + # TODO: 按项目调整 typecheck 命令与 glob(monorepo 用 --filter typecheck) + typecheck: + glob: "*.{ts,tsx}" + run: pnpm typecheck + +commit-msg: + commands: + conventional: + run: | + head -1 "{1}" | grep -qE '^(feat|fix|docs|chore|refactor|ci|perf|style|test|build|revert)(\(.+\))?!?: .+' || { + echo "✗ commit message 必须符合 Conventional Commits:(): <描述>" + echo " type: feat|fix|docs|chore|refactor|ci|perf|style|test|build|revert" + echo " 例:feat(auth/login): 新增手机号登录" + exit 1 + } diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index ed2343a..2081817 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -19,6 +19,7 @@ pub mod cicd; pub mod blueprint; pub mod buff; pub mod commit_metrics; +pub mod onboarding; pub mod canvas; pub mod claude_config; pub mod mentor; diff --git a/src-tauri/src/commands/onboarding.rs b/src-tauri/src/commands/onboarding.rs new file mode 100644 index 0000000..5735a68 --- /dev/null +++ b/src-tauri/src/commands/onboarding.rs @@ -0,0 +1,115 @@ +//! 接入包模板分发(基线档)。 +//! +//! 向项目种入 AGENTS.md / lefthook.yml / git-workflow.md 标准模板: +//! 文件不存在才写、绝不覆盖用户已有;返回每个模板的处理结果。 +//! 模板蓝本取自 enterprise-system,已通用化。 + +use serde::{Deserialize, Serialize}; +use std::path::Path; + +// 标准模板(编译期内嵌) +const TMPL_AGENTS: &str = include_str!("../../resources/onboarding/AGENTS.md.tmpl"); +const TMPL_LEFTHOOK: &str = include_str!("../../resources/onboarding/lefthook.yml.tmpl"); +const TMPL_GIT_WORKFLOW: &str = include_str!("../../resources/onboarding/git-workflow.md.tmpl"); + +/// 单个模板的分发结果。 +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct TemplateResult { + /// 相对项目根的目标路径 + pub name: String, + /// "written" | "skipped" | "failed" + pub action: String, + pub detail: String, +} + +/// 分发单个模板:存在则跳过(绝不覆盖),不存在则写入。 +fn dispatch_one(root: &Path, relative: &str, content: &str) -> TemplateResult { + let target = root.join(relative); + if target.exists() { + return TemplateResult { + name: relative.to_string(), + action: "skipped".into(), + detail: "已存在,未覆盖".into(), + }; + } + if let Some(parent) = target.parent() { + if let Err(e) = std::fs::create_dir_all(parent) { + return TemplateResult { + name: relative.to_string(), + action: "failed".into(), + detail: format!("建目录失败: {e}"), + }; + } + } + match std::fs::write(&target, content) { + Ok(_) => TemplateResult { + name: relative.to_string(), + action: "written".into(), + detail: "已写入".into(), + }, + Err(e) => TemplateResult { + name: relative.to_string(), + action: "failed".into(), + detail: format!("写入失败: {e}"), + }, + } +} + +/// 向项目分发全部基线模板,返回每个的处理结果。 +/// project_path 为项目根(WSL 项目为 \\wsl.localhost\... UNC 路径,可直接写)。 +pub fn dispatch_templates(project_path: &str) -> Vec { + let root = Path::new(project_path.trim_end_matches(['/', '\\'])); + vec![ + dispatch_one(root, "AGENTS.md", TMPL_AGENTS), + dispatch_one(root, "lefthook.yml", TMPL_LEFTHOOK), + dispatch_one(root, "docs/ai-context/git-workflow.md", TMPL_GIT_WORKFLOW), + ] +} + +/// 接入包应用报告:模板分发结果 + buff 状态。 +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct OnboardingReport { + pub templates: Vec, + pub buff: crate::commands::buff::BuffStatus, +} + +/// 应用接入包(基线档):分发标准模板 + 挂 buff(炼境登记 + git 观测/飞轮采集)。 +#[tauri::command] +pub fn apply_onboarding_pack( + project_path: String, + project_id: String, +) -> Result { + let templates = dispatch_templates(&project_path); + let buff = crate::commands::buff::apply_blueprint_buff(project_path, project_id)?; + Ok(OnboardingReport { templates, buff }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn writes_then_skips_never_overwrites() { + let base = std::env::temp_dir() + .join(format!("lianjing_onboarding_test_{}", std::process::id())); + let _ = std::fs::remove_dir_all(&base); + std::fs::create_dir_all(&base).unwrap(); + let pp = base.to_str().unwrap(); + + // 首次:全部写入 + let r1 = dispatch_templates(pp); + assert!(r1.iter().all(|t| t.action == "written"), "首次应全部写入"); + assert!(base.join("AGENTS.md").exists()); + assert!(base.join("lefthook.yml").exists()); + assert!(base.join("docs/ai-context/git-workflow.md").exists()); + + // 改一个文件,验证再次分发不覆盖 + std::fs::write(base.join("AGENTS.md"), "用户自己的内容").unwrap(); + let r2 = dispatch_templates(pp); + assert!(r2.iter().all(|t| t.action == "skipped"), "已存在应全部跳过"); + let kept = std::fs::read_to_string(base.join("AGENTS.md")).unwrap(); + assert_eq!(kept, "用户自己的内容", "绝不覆盖用户已有内容"); + + let _ = std::fs::remove_dir_all(&base); + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 55923cc..09dea12 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -265,6 +265,8 @@ pub fn run() { remove_blueprint_buff, get_buff_status, list_buffed_projects, + // onboarding pack + commands::onboarding::apply_onboarding_pack, // mentor get_mentor_context, get_project_notes, diff --git a/src/lib/commands.ts b/src/lib/commands.ts index 2933de9..d4ebb78 100644 --- a/src/lib/commands.ts +++ b/src/lib/commands.ts @@ -1599,6 +1599,22 @@ export const getBuffStatus = (projectPath: string) => export const listBuffedProjects = () => invoke("list_buffed_projects"); +// ── Onboarding Pack(接入包)────────────────────────────────────────────────── + +export interface TemplateResult { + name: string; + action: "written" | "skipped" | "failed"; + detail: string; +} + +export interface OnboardingReport { + templates: TemplateResult[]; + buff: BuffStatus; +} + +export const applyOnboardingPack = (projectPath: string, projectId: string) => + invoke("apply_onboarding_pack", { projectPath, projectId }); + // ── Beast Global Status (read-only) ─────────────────────────────────────────── export interface BeastGlobalStatus {