Compare commits

...

4 Commits

Author SHA1 Message Date
lanrtop
84ed61ac97 chore(release): bump version to 0.1.19
All checks were successful
Push & PR Check / check (push) Successful in 1m14s
Release / release (push) Successful in 5m20s
docs(git-workflow): 分支策略升级为复杂度感知模型 v1.3.0(单文件小改直推主分支,功能性改动开 feat/* + PR)
2026-07-04 21:05:13 +09:00
lanrtop
38d5cec53e feat(mcp): 新增 setup_gitea_repo agent 按钮——一键建仓库配 remote
调用默认 Gitea 实例为项目创建远程仓库,同时配置本地 git remote origin。
幂等设计:仓库已存在(409)直接绑定,remote 已有则 set-url 更新。
背景:此前 agent 无法自助完成"项目在 Gitea 建仓库"流程,需要手动
拼凑 curl + git 命令,本工具将整个流程收归炼境 MCP 调用层。
2026-07-04 18:27:01 +09:00
lanrtop
892e5977ed fix(onboarding): lefthook.yml.tmpl 用 COMMIT_EDITMSG 替换 {1}
lefthook v2 在 Windows 上解析 YAML 时会将 {1} 与模板变量混淆,
改为直接读取 .git/COMMIT_EDITMSG,与 react-phone-apps 修复保持一致。
2026-07-04 16:50:55 +09:00
lanrtop
cf86ab688a docs(conventions): 梦核蒸馏——规则置信度更新 + 梦核写入纪律
- R01 confidence 0.90→0.95(12 命中 0% 返工,最稳信号)
- R13 confidence 0.80→0.88(11 命中 9% 返工,高频高效)
- R08 confidence 0.87→0.72(3 命中 100% 返工,补 failure_case + boundaries)
- R10 confidence 0.85→0.60(6 命中 83% 返工,补 failure_case + boundaries)
- 追加梦核写入纪律:蒸馏替换而非追加,规则上限 ≤ 25 条
- 数据来源:2026-07-04 梦核分析,40 条复盘笔记 + Rules-Applied trailer
2026-07-04 16:42:02 +09:00
7 changed files with 188 additions and 22 deletions

View File

@ -479,6 +479,16 @@ rule_effectiveness: R10=未验证(任务开始前未触发,事后归因)
- **固化**:哪些跨项目重复出现的阻塞模式、哪些有效实践,值得写进 CONVENTIONS - **固化**:哪些跨项目重复出现的阻塞模式、哪些有效实践,值得写进 CONVENTIONS
- **遗忘**哪些休眠项目90天无快照的 AI 文档可以归档,哪些 CONVENTIONS 规则已过时 - **遗忘**哪些休眠项目90天无快照的 AI 文档可以归档,哪些 CONVENTIONS 规则已过时
### 梦核写入纪律(蒸馏,不是追加)
梦核的产出是**替换操作,不是追加**。每次梦核后遵守:
1. **项目专属规则不进全局** → 写到该项目的 `.blueprint/rules.md`,不写入 CONVENTIONS
2. **全局规则上限 ≤ 25 条** → 超出则先合并/删除再新增;规则总量是飞轮健康度的信号
3. **改原条目,不加新条目** → R10 置信度需降,直接修改 R10 的 confidence 字段,不在旁边另起一条注释
4. **置信度元数据就地维护** → confidence / evidence_count / failure_cases 内嵌在各规则条目中,不另建文件
5. **一次性洞察不固化** → 梦核分析在对话中消费完毕,不另存 .md 文件;原料(复盘笔记)已在飞轮中,随时可重新生成
### 闭环路径 ### 闭环路径
两条并行的反馈路径,速度不同: 两条并行的反馈路径,速度不同:
@ -882,9 +892,9 @@ scope 描述规则的正向适用范围,**boundaries 描述已知的反向边
- **scope**: `tauri` - **scope**: `tauri`
- **触发条件**:在 `src-tauri/src/commands/` 下新增 `#[tauri::command]` 函数 - **触发条件**:在 `src-tauri/src/commands/` 下新增 `#[tauri::command]` 函数
- **自动动作**:读取 `src/lib/commands.ts`,确认是否有对应封装;缺失则提示补写,不阻止执行 - **自动动作**:读取 `src/lib/commands.ts`,确认是否有对应封装;缺失则提示补写,不阻止执行
- **confidence**: 0.90 - **confidence**: 0.95
- **evidence_count**: 8 - **evidence_count**: 12
- **derived_from**: 多次发生前后端接口断层(命令已实现但 commands.ts 未同步,前端调用报错) - **derived_from**: 多次发生前后端接口断层(命令已实现但 commands.ts 未同步,前端调用报错);梦核 2026-07-0412 次命中0 次返工,最稳信号
#### R02 — 任务卡无 acceptance 时先补写再动代码 #### R02 — 任务卡无 acceptance 时先补写再动代码
- **scope**: `universal` - **scope**: `universal`
@ -938,9 +948,12 @@ scope 描述规则的正向适用范围,**boundaries 描述已知的反向边
- **scope**: `universal` - **scope**: `universal`
- **触发条件**:前端组件显示运行状态、健康状态、操作结果等实时信息 - **触发条件**:前端组件显示运行状态、健康状态、操作结果等实时信息
- **自动动作**:检查对应后端是否做了真实检测而非硬编码;发现硬编码状态时标记为需修复 - **自动动作**:检查对应后端是否做了真实检测而非硬编码;发现硬编码状态时标记为需修复
- **confidence**: 0.87 - **confidence**: 0.72
- **evidence_count**: 5 - **evidence_count**: 3
- **derived_from**: 设置页 MCP 状态硬编码为"运行中",注入失败时 toast 始终显示成功——UI 掩盖真实问题 - **derived_from**: 设置页 MCP 状态硬编码为"运行中",注入失败时 toast 始终显示成功——UI 掩盖真实问题
- **boundaries**: 展示类状态loading spinner、空状态提示属 UI 职责,不适用本规则;本规则仅限"系统真实运行状态"类展示
- **failure_cases**:
- 2026-07-04梦核归因3 次命中全部伴随返工100%),规则触发但未阻止问题——根因是"状态硬编码"发生在编写时,规则检查发生在展示层,时序错位;建议在写 Tauri command 时即检查返回值是否真实(与 R01 联动)
#### R09 — 自动化边界:事件触发只标 🔵,不标 ✅ #### R09 — 自动化边界:事件触发只标 🔵,不标 ✅
- **scope**: `universal` - **scope**: `universal`
@ -954,9 +967,12 @@ scope 描述规则的正向适用范围,**boundaries 描述已知的反向边
- **scope**: `universal` - **scope**: `universal`
- **触发条件**实现与外部客户端的协议对接MCP、LSP、WebSocket、SSE、OAuth 等) - **触发条件**实现与外部客户端的协议对接MCP、LSP、WebSocket、SSE、OAuth 等)
- **自动动作**在开始实现前先通过文档或工具curl、抓包确认客户端实际发出的请求格式不基于"通用规范"假设行为 - **自动动作**在开始实现前先通过文档或工具curl、抓包确认客户端实际发出的请求格式不基于"通用规范"假设行为
- **confidence**: 0.85 - **confidence**: 0.60
- **evidence_count**: 3 - **evidence_count**: 6
- **derived_from**: Claude Code MCP 客户端用 GET 发起 SSE 握手,炼境只接受 POST 导致 405MCP 整体失效 - **derived_from**: Claude Code MCP 客户端用 GET 发起 SSE 握手,炼境只接受 POST 导致 405MCP 整体失效
- **boundaries**: 实时协议WebSocket、SSE的完整握手序列难以通过 curl 完全预验证需配合协议库调试工具MCP Inspector 等);本规则防的是"基于规范假设而不看实际请求",不能防"验证了但实现仍有差"
- **failure_cases**:
- 2026-07-04梦核归因6 次命中中 5 次伴随返工83%),显示规则触发后问题仍发生——根因是协议对接本身复杂度高,"验证客户端行为"这一步经常被跳过或验证不够深入;建议将规则收窄为强制前置步骤:实现前必须有一条 curl/工具调用记录证明验证已执行
#### R11 — 自动执行规则失效时必须记录失败案例 #### R11 — 自动执行规则失效时必须记录失败案例
- **scope**: `universal` - **scope**: `universal`
@ -979,9 +995,9 @@ scope 描述规则的正向适用范围,**boundaries 描述已知的反向边
- **scope**: `universal` - **scope**: `universal`
- **触发条件**M 或 L 复杂度任务卡准备标记 ✅ done - **触发条件**M 或 L 复杂度任务卡准备标记 ✅ done
- **自动动作**acceptance 所述验证路径须由人或 agent **通过功能自身入口**真实走通一次agent 按钮载体就近选择Web 服务=HTTP API、CLI=命令、桌面应用=测试调用同一函数、UI 呈现层=Playwright 兜底。禁止绕过功能手改产物冒充验证agent 走通的关键路径顺手固化为一条测试(侦察→驻军),一次性验收不固化 - **自动动作**acceptance 所述验证路径须由人或 agent **通过功能自身入口**真实走通一次agent 按钮载体就近选择Web 服务=HTTP API、CLI=命令、桌面应用=测试调用同一函数、UI 呈现层=Playwright 兜底。禁止绕过功能手改产物冒充验证agent 走通的关键路径顺手固化为一条测试(侦察→驻军),一次性验收不固化
- **confidence**: 0.80 - **confidence**: 0.88
- **evidence_count**: 2 - **evidence_count**: 11
- **derived_from**: noteapp manifest 被手改导致 schema 约束修复未获真实验证、autoforphone 接线被命令行代办导致 G7 按钮未走通——手动替代掩盖功能真实状态2026-07-03 用户裁决 + MCP 治理三件套 agent 实测首证) - **derived_from**: noteapp manifest 被手改导致 schema 约束修复未获真实验证、autoforphone 接线被命令行代办导致 G7 按钮未走通——手动替代掩盖功能真实状态2026-07-03 用户裁决 + MCP 治理三件套 agent 实测首证);梦核 2026-07-0411 次命中9% 返工率,高频高效
### 规则演进路径 ### 规则演进路径

View File

@ -3,19 +3,22 @@
> 把此文件粘贴给任何 AI Agent让它了解本项目的 git 工作方式。由炼境接入包分发。 > 把此文件粘贴给任何 AI Agent让它了解本项目的 git 工作方式。由炼境接入包分发。
> `ONBOARDING_MANAGED` 标记块由炼境版本化管理(重跑接入包可原位升级),块外可自由补充项目特有约定。 > `ONBOARDING_MANAGED` 标记块由炼境版本化管理(重跑接入包可原位升级),块外可自由补充项目特有约定。
<!-- ONBOARDING_MANAGED_START version="1.2.0" --> <!-- ONBOARDING_MANAGED_START version="1.3.0" -->
## 分支策略 ## 分支策略(复杂度感知)
``` ```
dev ← 日常开发在这里 commit/push main/master ← 主分支,保持稳定
main ← 稳定分支,通过 PR 合入(合入时触发 CI code review ├── 简单改动 → 直接 push main/master
└── 功能性改动 → feat/<name> 分支 → PR → main/master
``` ```
- 小改动可直接 push `dev` **简单改动(直接推主分支)**bug fix、chore、docs、style、单文件小改行为对外无变化。
- 功能完整后开 PRdev → main
- 不要直接 push `main`,除非紧急热修复 **功能性改动(开 feat/* 分支 + PR**:新功能、重构、涉及多模块的 refactor。由 agent 根据改动文件数和性质自判,无需蓝图标签。
分支命名:`feat/<功能简述>`,如 `feat/login-sms`、`feat/order-pagination`。
## Commit 规范lefthook 强制) ## Commit 规范lefthook 强制)
@ -81,7 +84,7 @@ PR 别攒太大(建议每 3~5 个 commit 或几百行内就开一次),否
``` ```
1. Read .claude/liangjing.json → 取出 project_id 1. Read .claude/liangjing.json → 取出 project_id
2. 调用 MCP 工具 create_pull_request 2. 调用 MCP 工具 create_pull_request
{ "project_id": "<id>", "head": "dev", "base": "main", { "project_id": "<id>", "head": "feat/<name>", "base": "main",
"title": "<Conventional Commits 格式>", "body": "<变更摘要>" } "title": "<Conventional Commits 格式>", "body": "<变更摘要>" }
3. 炼境内部用存储的 Gitea 凭证调 API返回 pr_url 3. 炼境内部用存储的 Gitea 凭证调 API返回 pr_url
``` ```

View File

@ -1,6 +1,6 @@
[package] [package]
name = "dev-manager-tauri" name = "dev-manager-tauri"
version = "0.1.18" version = "0.1.19"
description = "A Tauri App" description = "A Tauri App"
authors = ["you"] authors = ["you"]
edition = "2021" edition = "2021"

View File

@ -13,4 +13,4 @@ pre-commit:
commit-msg: commit-msg:
commands: commands:
conventional: conventional:
run: "head -1 \"{1}\" | grep -qE '^(feat|fix|docs|chore|refactor|ci|perf|style|test|build|revert)(\\(.+\\))?!?: .+' || exit 1" run: "head -1 .git/COMMIT_EDITMSG | grep -qE '^(feat|fix|docs|chore|refactor|ci|perf|style|test|build|revert)(\\(.+\\))?!?: .+' || exit 1"

View File

@ -827,6 +827,133 @@ pub fn gitea_api_merge_pull_request(project_id: &str, pr_number: i64) -> Result<
Ok(format!("PR #{pr_number} 已成功合并({owner}/{repo_name}")) Ok(format!("PR #{pr_number} 已成功合并({owner}/{repo_name}"))
} }
// ── MCP 工具:一键建仓库 + 配置 remote ───────────────────────────────────────
/// 为项目在默认 Gitea 实例上创建远程仓库,并配置本地 git remote origin。
/// 幂等仓库已存在409则直接绑定remote 已配置则 set-url 更新。
pub fn gitea_api_setup_repo(
project_id: &str,
repo_name: Option<&str>,
private: bool,
) -> Result<String, String> {
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
// 项目基本信息
let (project_name, win_path): (String, Option<String>) = conn
.query_row(
"SELECT pp.name, pw.win_path
FROM project_workspaces pw
JOIN project_profiles pp ON pp.id = pw.profile_id
WHERE pw.id = ?1",
params![project_id],
|r| Ok((r.get(0)?, r.get(1)?)),
)
.map_err(|_| format!("项目 {} 不存在", project_id))?;
// 默认 Gitea 实例
let (instance_id, base_url, token): (String, String, String) = conn
.query_row(
"SELECT id, url, access_token FROM gitea_instances WHERE is_default = 1 LIMIT 1",
[],
|r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)),
)
.map_err(|_| "未配置默认 Gitea 实例,请先在炼境 UI 中添加并设为默认".to_string())?;
// 仓库名 slug 化(保留 ASCII 字母数字 + - _ .);空字符串等同未传
let final_repo_name = repo_name.filter(|s| !s.trim().is_empty()).map(|s| s.to_string()).unwrap_or_else(|| {
let slug: String = project_name
.to_ascii_lowercase()
.replace(' ', "-")
.chars()
.filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_' || *c == '.')
.collect();
if slug.is_empty() {
format!("project-{}", &project_id[..8.min(project_id.len())])
} else {
slug
}
});
// 仓库拥有者(从 token 获取登录名)
let owner = gitea_api_get_user(&base_url, &token)?.login;
// 创建仓库409=已存在时直接绑定
let (clone_url, already_existed) = match ureq::post(&format!("{base_url}/api/v1/user/repos"))
.set("Authorization", &format!("token {token}"))
.send_json(serde_json::json!({
"name": final_repo_name,
"description": format!("{} — 由炼境自动创建", project_name),
"private": private,
"auto_init": false,
})) {
Ok(resp) => {
let json: serde_json::Value =
resp.into_json().map_err(|e| format!("解析响应失败: {e}"))?;
let url = json["clone_url"].as_str().ok_or("响应缺少 clone_url")?.to_string();
(url, false)
}
Err(ureq::Error::Status(409, _)) => {
let json = gitea_api_get_repo(&base_url, &token, &owner, &final_repo_name)?;
let url = json["clone_url"].as_str().ok_or("响应缺少 clone_url")?.to_string();
(url, true)
}
Err(e) => return Err(api_err("创建仓库失败", e)),
};
// 写入炼境 DB 绑定关系
upsert_repo_link(&conn, &instance_id, project_id, &owner, &final_repo_name, Some(&clone_url))?;
// 配置本地 git remote origin
let remote_status = configure_git_remote(win_path.as_deref(), &clone_url);
let existed_tag = if already_existed { "已存在,直接绑定" } else { "新建" };
let remote_tag = match &remote_status {
Ok(action) => format!("{action}"),
Err(e) => format!("⚠️ {e}"),
};
let mut out = format!(
"Gitea 仓库已就绪:{base_url}/{owner}/{final_repo_name}\n\n\
- {owner}/{final_repo_name}{existed_tag}\n\
- project_id \n\
- git remote origin{remote_tag}\n"
);
if remote_status.is_ok() {
out.push_str("\n建议下一步git push -u origin main然后调用 setup_webhook 注册 webhook。");
}
Ok(out)
}
/// 配置本地仓库的 git remote originadd 或 set-url幂等
fn configure_git_remote(win_path: Option<&str>, clone_url: &str) -> Result<String, String> {
let path = win_path.ok_or_else(|| "项目无本地路径".to_string())?;
if !std::path::Path::new(path).join(".git").exists() {
return Err("本地目录无 .git跳过 remote 配置".to_string());
}
let has_origin = std::process::Command::new("git")
.args(["-C", path, "remote", "get-url", "origin"])
.output()
.map(|o| o.status.success())
.unwrap_or(false);
let out = if has_origin {
std::process::Command::new("git")
.args(["-C", path, "remote", "set-url", "origin", clone_url])
.output()
} else {
std::process::Command::new("git")
.args(["-C", path, "remote", "add", "origin", clone_url])
.output()
}
.map_err(|e| format!("执行 git remote 失败: {e}"))?;
if out.status.success() {
Ok(if has_origin { "origin set-url 更新成功".to_string() } else { "origin 已添加".to_string() })
} else {
Err(String::from_utf8_lossy(&out.stderr).trim().to_string())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -280,6 +280,19 @@ pub fn tools_list_result() -> Value {
}, },
"required": ["project_id"] "required": ["project_id"]
} }
},
{
"name": "setup_gitea_repo",
"description": "为项目在默认 Gitea 实例上创建远程仓库,并将本地 git remote origin 指向新仓库幂等仓库已存在则直接绑定remote 已有则 set-url 更新)。创建后炼境自动记录绑定关系,后续可调用 setup_webhook 注册 webhook。需先在炼境 UI 配置一个默认 Gitea 实例。",
"inputSchema": {
"type": "object",
"properties": {
"project_id": { "type": "string", "description": "项目 workspace ID来自 list_group_projects 的返回结果)" },
"repo_name": { "type": "string", "description": "Gitea 仓库名;省略则用项目名自动 slug 化(非 ASCII 字符过滤掉)" },
"private": { "type": "boolean", "description": "是否创建私有仓库,默认 false公开" }
},
"required": ["project_id"]
}
} }
] ]
}) })
@ -569,6 +582,12 @@ pub async fn tools_call(group_id: &str, params: Option<&Value>) -> Value {
crate::commands::risk::predict_task_risk(&conn, pid, scope, &files) crate::commands::risk::predict_task_risk(&conn, pid, scope, &files)
.and_then(|r| serde_json::to_string_pretty(&r).map_err(|e| e.to_string())) .and_then(|r| serde_json::to_string_pretty(&r).map_err(|e| e.to_string()))
} }
"setup_gitea_repo" => {
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
let repo_name = args.get("repo_name").and_then(|v| v.as_str());
let private = args.get("private").and_then(|v| v.as_bool()).unwrap_or(false);
crate::commands::gitea::gitea_api_setup_repo(pid, repo_name, private)
}
"apply_onboarding_pack" => { "apply_onboarding_pack" => {
let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or(""); let pid = args.get("project_id").and_then(|v| v.as_str()).unwrap_or("");
resolve_project_root(&gid, pid).and_then(|root| { resolve_project_root(&gid, pid).and_then(|root| {
@ -1149,6 +1168,7 @@ mod tests {
assert!(names.contains(&"predict_task_risk"), "缺少 predict_task_risk"); assert!(names.contains(&"predict_task_risk"), "缺少 predict_task_risk");
assert!(names.contains(&"ingest_git_history"), "缺少 ingest_git_history"); assert!(names.contains(&"ingest_git_history"), "缺少 ingest_git_history");
assert!(names.contains(&"unregister_project"), "缺少 unregister_project"); assert!(names.contains(&"unregister_project"), "缺少 unregister_project");
assert!(names.contains(&"setup_gitea_repo"), "缺少 setup_gitea_repo");
// 每个工具都有 description 和 inputSchema // 每个工具都有 description 和 inputSchema
for tool in tools { for tool in tools {
let name = tool["name"].as_str().unwrap_or("?"); let name = tool["name"].as_str().unwrap_or("?");

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "dev-manager-tauri", "productName": "dev-manager-tauri",
"version": "0.1.18", "version": "0.1.19",
"identifier": "com.yangzhixiang.dev-manager-tauri", "identifier": "com.yangzhixiang.dev-manager-tauri",
"build": { "build": {
"beforeDevCommand": "pnpm dev", "beforeDevCommand": "pnpm dev",