feat(onboarding): 接入包模板分发 + apply_onboarding_pack 后端

dispatch_templates 向项目种入 AGENTS.md/lefthook.yml/git-workflow.md 标准模板(蓝本取自 enterprise-system,已通用化),不存在才写、绝不覆盖用户已有;apply_onboarding_pack 组合模板分发 + apply_blueprint_buff 返回 OnboardingReport。含 3 内嵌模板 + 不覆盖单测 + commands.ts 封装。前端 UI 待接。
This commit is contained in:
lanrtop 2026-06-19 13:00:22 +09:00
parent 3befc50d20
commit 5f8b2e8bec
8 changed files with 268 additions and 5 deletions

View File

@ -67,14 +67,14 @@ git 是「过去时 + 量化」(做了什么),蓝图是「将来时 + 语
> 已过 /architect + /splitter。A→B、C1→C2 两条链可并行D 依赖 C1+B**C1飞轮地基建议优先开工**。 > 已过 /architect + /splitter。A→B、C1→C2 两条链可并行D 依赖 C1+B**C1飞轮地基建议优先开工**。
### 📋 A 模板分发引擎 ### A 模板分发引擎
- status: planned - status: done
- complexity: M - complexity: M
- files: src-tauri/src/commands/onboarding.rs, src-tauri/resources/onboarding/{AGENTS.md,lefthook.yml,git-workflow.md}.tmpl - 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 - acceptance: include_str! 内嵌 3 个标准模板(蓝本取自 enterprise-system分发时文件不存在→写入、已存在→跳过并在报告标注绝不覆盖复用 mcp_inject 的路径解析(含 WSL返回每个模板的 written/skipped/failed
### 📋 B apply_onboarding_pack 编排 + 前端入口 ### 🔵 B apply_onboarding_pack 编排 + 前端入口
- status: planned - status: in_progress
- complexity: M - complexity: M
- depends: onboarding-pack卡A - 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 - 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 - files: src-tauri/src/commands/buff.rs, src-tauri/src/commands/blueprint.rs
- acceptance: check_project 用 `last_commit..HEAD` 取 commit messagegit log复用现有 WSL 路径处理)→ 调 C1 解析写 commit_metrics首次接入last_commit 空)限解析最近 N 条append_usage_snapshot 的 rework_count 改查 commit_metricsis_rework=1 AND is_ci_auto=0取代 parse_notes_metrics - acceptance: check_project 用 `last_commit..HEAD` 取 commit messagegit log复用现有 WSL 路径处理)→ 调 C1 解析写 commit_metrics首次接入last_commit 空)限解析最近 N 条append_usage_snapshot 的 rework_count 改查 commit_metricsis_rework=1 AND is_ci_auto=0取代 parse_notes_metrics
### 📋 D 数据可信度提示 ### 🔵 D 数据可信度提示
- status: planned - status: planned
- complexity: S - complexity: S
- depends: onboarding-pack卡 C1、卡 B - depends: onboarding-pack卡 C1、卡 B

View File

@ -0,0 +1,44 @@
<!-- 项目名 --> — AI Agent 上下文
> 本文件遵循 [AGENTS.md 开放规范](https://agents.md/),供 Claude Code、Qwen Code、Cursor、Copilot 等 AI 工具读取。
> 由炼境接入包分发。请按项目实际补全下方 TODO 段落。
## 项目概况
<!-- TODO: 一句话描述项目 + 技术栈。例pnpm monorepoReact 19 + TypeScript 前端 + Hono API。 -->
## 核心开发约束
### 包管理
- **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 强制),炼境据此采集飞轮数据。
## 架构与模块
<!-- TODO: 列出主要目录/模块及职责,帮助 AI 快速定位「改哪里」。 -->
## 多 AI CLI 协作
本项目可由多个 AI CLI 协同。交接时把结果总结成 3 条传给下一个:
```
1. 已完成:<改了哪些文件 + 核心决策>
2. 待验证:<需要确认的地方>
3. 约束继承:<影响后续的关键限制>
```
## 关键文档索引
<!-- TODO: 列出 README / ARCHITECTURE.md / 其他关键文档及用途。 -->

View File

@ -0,0 +1,63 @@
# Git 工作流 — AI Agent 操作指南
> 把此文件粘贴给任何 AI Agent让它了解本项目的 git 工作方式。由炼境接入包分发。
## 分支策略
```
dev ← 日常开发在这里 commit/push
main ← 稳定分支,通过 PR 合入(合入时触发 CI code review
```
- 小改动可直接 push `dev`
- 功能完整后开 PRdev → main
- 不要直接 push `main`,除非紧急热修复
## Commit 规范lefthook 强制)
格式:`<type>(<scope>): <描述>`
| 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保留改动
```

View File

@ -0,0 +1,22 @@
# 接入包标准 lefthook 配置(炼境分发)
# 作用:强制 Conventional Commits + 提交前类型检查
# 安装pnpm add -D lefthook && pnpm exec lefthook install
pre-commit:
parallel: true
commands:
# TODO: 按项目调整 typecheck 命令与 globmonorepo 用 --filter <pkg> 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<type>(<scope>): <描述>"
echo " type: feat|fix|docs|chore|refactor|ci|perf|style|test|build|revert"
echo " 例feat(auth/login): 新增手机号登录"
exit 1
}

View File

@ -19,6 +19,7 @@ pub mod cicd;
pub mod blueprint; pub mod blueprint;
pub mod buff; pub mod buff;
pub mod commit_metrics; pub mod commit_metrics;
pub mod onboarding;
pub mod canvas; pub mod canvas;
pub mod claude_config; pub mod claude_config;
pub mod mentor; pub mod mentor;

View File

@ -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<TemplateResult> {
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<TemplateResult>,
pub buff: crate::commands::buff::BuffStatus,
}
/// 应用接入包(基线档):分发标准模板 + 挂 buff炼境登记 + git 观测/飞轮采集)。
#[tauri::command]
pub fn apply_onboarding_pack(
project_path: String,
project_id: String,
) -> Result<OnboardingReport, String> {
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);
}
}

View File

@ -265,6 +265,8 @@ pub fn run() {
remove_blueprint_buff, remove_blueprint_buff,
get_buff_status, get_buff_status,
list_buffed_projects, list_buffed_projects,
// onboarding pack
commands::onboarding::apply_onboarding_pack,
// mentor // mentor
get_mentor_context, get_mentor_context,
get_project_notes, get_project_notes,

View File

@ -1599,6 +1599,22 @@ export const getBuffStatus = (projectPath: string) =>
export const listBuffedProjects = () => export const listBuffedProjects = () =>
invoke<BuffStatus[]>("list_buffed_projects"); invoke<BuffStatus[]>("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<OnboardingReport>("apply_onboarding_pack", { projectPath, projectId });
// ── Beast Global Status (read-only) ─────────────────────────────────────────── // ── Beast Global Status (read-only) ───────────────────────────────────────────
export interface BeastGlobalStatus { export interface BeastGlobalStatus {