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:
parent
3befc50d20
commit
5f8b2e8bec
@ -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 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
|
- 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
|
- status: planned
|
||||||
- complexity: S
|
- complexity: S
|
||||||
- depends: onboarding-pack(卡 C1、卡 B)
|
- depends: onboarding-pack(卡 C1、卡 B)
|
||||||
|
|||||||
44
src-tauri/resources/onboarding/AGENTS.md.tmpl
Normal file
44
src-tauri/resources/onboarding/AGENTS.md.tmpl
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<!-- 项目名 --> — AI Agent 上下文
|
||||||
|
|
||||||
|
> 本文件遵循 [AGENTS.md 开放规范](https://agents.md/),供 Claude Code、Qwen Code、Cursor、Copilot 等 AI 工具读取。
|
||||||
|
> 由炼境接入包分发。请按项目实际补全下方 TODO 段落。
|
||||||
|
|
||||||
|
## 项目概况
|
||||||
|
|
||||||
|
<!-- TODO: 一句话描述项目 + 技术栈。例:pnpm monorepo,React 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 / 其他关键文档及用途。 -->
|
||||||
63
src-tauri/resources/onboarding/git-workflow.md.tmpl
Normal file
63
src-tauri/resources/onboarding/git-workflow.md.tmpl
Normal file
@ -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>(<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(保留改动)
|
||||||
|
```
|
||||||
22
src-tauri/resources/onboarding/lefthook.yml.tmpl
Normal file
22
src-tauri/resources/onboarding/lefthook.yml.tmpl
Normal file
@ -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 <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
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|||||||
115
src-tauri/src/commands/onboarding.rs
Normal file
115
src-tauri/src/commands/onboarding.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user