Compare commits
10 Commits
8afb16480a
...
0552180e45
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0552180e45 | ||
|
|
ecb5620abb | ||
|
|
58d2c79c74 | ||
|
|
150535ffcf | ||
|
|
ee2a37876e | ||
|
|
5f8b2e8bec | ||
|
|
3befc50d20 | ||
|
|
076bba7def | ||
|
|
2ed548556c | ||
|
|
5b28cb351c |
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
version: 1.7.0
|
version: 1.8.0
|
||||||
updated: 2026-05-06
|
updated: 2026-06-29
|
||||||
source: dev-manager-tauri
|
source: dev-manager-tauri
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -22,6 +22,26 @@ source: dev-manager-tauri
|
|||||||
└── <module-id>.md ← 每个模块的详情 + 任务卡
|
└── <module-id>.md ← 每个模块的详情 + 任务卡
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 蓝图定位(意图层)
|
||||||
|
|
||||||
|
蓝图只记录 git 永远无法自动产生的三类信息:
|
||||||
|
|
||||||
|
| 类型 | 内容 | 为什么 git 做不到 |
|
||||||
|
|------|------|----------------|
|
||||||
|
| **结构** | 模块列表 + 关系图(dependency/related) | git 只知道文件,不知道模块意图和边界 |
|
||||||
|
| **意图** | 为什么做 + 验收标准 + 复杂度 + 复盘笔记 | commit 记录"做了什么",不记录"为什么" |
|
||||||
|
| **规划** | concept / planned 方向 | 还没有代码,git 里什么都没有 |
|
||||||
|
|
||||||
|
**不属于蓝图的**(交给 git 信号,蓝图不手动维护):
|
||||||
|
- 任务状态(in_progress / done)→ 从 branch / commit 推断
|
||||||
|
- 涉及文件(files)→ 从 git diff 读取,蓝图里为可选提示
|
||||||
|
- 进度数字(progress)→ 从任务卡完成比例自动算
|
||||||
|
- 更新时间(updated)→ 从 git log 读取
|
||||||
|
|
||||||
|
> 蓝图是项目的"架构意图层",不是任务追踪系统。任务追踪的职责交还给 git。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 何时更新蓝图
|
## 何时更新蓝图
|
||||||
|
|
||||||
| 事件 | 操作 |
|
| 事件 | 操作 |
|
||||||
@ -69,7 +89,7 @@ modules/<id>.md 中的每个二级标题(`###`)是一张任务卡:
|
|||||||
### 📋 任务标题
|
### 📋 任务标题
|
||||||
- status: todo
|
- status: todo
|
||||||
- complexity: M
|
- complexity: M
|
||||||
- files: src/path/to/file.tsx, src-tauri/src/commands/xxx.rs
|
- files: src/path/to/file.tsx(可选,规划时填预期路径;实际文件由 git diff 追踪,无需回填)
|
||||||
- depends: other-module-id
|
- depends: other-module-id
|
||||||
- acceptance: 简要描述完成标准
|
- acceptance: 简要描述完成标准
|
||||||
- blocked_reason: (仅 status: blocked 时填写)执行受阻的具体原因
|
- blocked_reason: (仅 status: blocked 时填写)执行受阻的具体原因
|
||||||
@ -79,8 +99,6 @@ modules/<id>.md 中的每个二级标题(`###`)是一张任务卡:
|
|||||||
详细说明(可选)。
|
详细说明(可选)。
|
||||||
```
|
```
|
||||||
|
|
||||||
> **files 回填规则**:任务卡的 `files` 在规划时填入预期路径。实现完成后,若实际文件与规划有出入(合并、拆分、重命名),须在标记 ✅ done 之前将 `files` 更新为实际路径——这保证下次续接或派发时上下文准确。
|
|
||||||
|
|
||||||
### 状态前缀
|
### 状态前缀
|
||||||
|
|
||||||
| 前缀 | status | 含义 |
|
| 前缀 | status | 含义 |
|
||||||
@ -95,9 +113,10 @@ modules/<id>.md 中的每个二级标题(`###`)是一张任务卡:
|
|||||||
|
|
||||||
任务卡同时满足以下条件时标记为 📋:
|
任务卡同时满足以下条件时标记为 📋:
|
||||||
1. status 为 `todo`
|
1. status 为 `todo`
|
||||||
2. 有明确的 `files`(涉及哪些文件)
|
2. 有明确的 `acceptance`(验收标准)
|
||||||
3. 有明确的 `acceptance`(验收标准)
|
3. complexity 为 `S` 或 `M`
|
||||||
4. complexity 为 `S` 或 `M`
|
|
||||||
|
`files` 为可选提示字段,有则帮助 agent 更快定位,无则从 git diff 推断,不作派发门槛。
|
||||||
|
|
||||||
不满足条件的 todo 任务保持无前缀,说明还需补充信息。
|
不满足条件的 todo 任务保持无前缀,说明还需补充信息。
|
||||||
|
|
||||||
@ -130,8 +149,8 @@ updated: YYYY-MM-DD
|
|||||||
- id: kebab-case-id # 唯一标识,同时是 modules/ 下的文件名
|
- id: kebab-case-id # 唯一标识,同时是 modules/ 下的文件名
|
||||||
name: 显示名称
|
name: 显示名称
|
||||||
area: frontend # 所属领域 ID
|
area: frontend # 所属领域 ID
|
||||||
status: done # done / in_progress / planned / concept
|
status: done # planned/concept 手动设;in_progress/done 优先从 git 信号推断,人工确认
|
||||||
progress: 100 # 0~100,done 自动视为 100
|
progress: 100 # 由任务卡完成比例自动算,无需手填;App 根据 done 卡比例渲染
|
||||||
position: [x, y] # React Flow 画布坐标(可选)
|
position: [x, y] # React Flow 画布坐标(可选)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -247,7 +266,7 @@ Claude 在每次对话中应遵循以下流程:
|
|||||||
|----------|--------------|
|
|----------|--------------|
|
||||||
| "我想加个 XX 功能" | 确认需求后:manifest 加模块 (status: concept),创建 modules/<id>.md |
|
| "我想加个 XX 功能" | 确认需求后:manifest 加模块 (status: concept),创建 modules/<id>.md |
|
||||||
| "XX 功能需要 A、B、C 三步" | 在模块文件中拆分为 3 张任务卡 |
|
| "XX 功能需要 A、B、C 三步" | 在模块文件中拆分为 3 张任务卡 |
|
||||||
| "这个方案确定了" | 模块 status → planned,补充任务卡的 files/acceptance |
|
| "这个方案确定了" | 模块 status → planned,补充任务卡的 acceptance(files 可选,有则更准确) |
|
||||||
| "开始做 XX" | 模块 status → in_progress |
|
| "开始做 XX" | 模块 status → in_progress |
|
||||||
| "为什么选这个方案" | 记录到模块的 `## 决策记录` |
|
| "为什么选这个方案" | 记录到模块的 `## 决策记录` |
|
||||||
|
|
||||||
@ -402,7 +421,7 @@ rule_effectiveness: R10=未验证(任务开始前未触发,事后归因)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `dispatched` = 当前满足📋可派发条件的任务数(有 files + acceptance + complexity S/M)
|
- `dispatched` = 当前满足📋可派发条件的任务数(有 acceptance + complexity S/M;files 可选)
|
||||||
- 节流:同一 UTC 日期只写一条,避免多次打开导致数据膨胀
|
- 节流:同一 UTC 日期只写一条,避免多次打开导致数据膨胀
|
||||||
- 文件存于项目本地,不上传服务器
|
- 文件存于项目本地,不上传服务器
|
||||||
|
|
||||||
@ -529,17 +548,17 @@ rule_effectiveness: R10=未验证(任务开始前未触发,事后归因)
|
|||||||
2. 按以下原则拆解:
|
2. 按以下原则拆解:
|
||||||
- **粒度**:每张卡 complexity 必须是 S 或 M,不允许 L
|
- **粒度**:每张卡 complexity 必须是 S 或 M,不允许 L
|
||||||
- **依赖顺序**:数据层 → 后端命令 → 前端封装 → UI 组件
|
- **依赖顺序**:数据层 → 后端命令 → 前端封装 → UI 组件
|
||||||
- **可派发标准**:每张卡必须有 `files` + `acceptance` + `complexity`
|
- **可派发标准**:每张卡必须有 `acceptance` + `complexity`;`files` 建议填入预期路径,有则更准确
|
||||||
3. 将任务卡追加到 `modules/<id>.md`
|
3. 将任务卡追加到 `modules/<id>.md`
|
||||||
4. 更新 `manifest.yaml`:status → `planned`,更新 `updated` 日期
|
4. 更新 `manifest.yaml`:status → `planned`,更新 `updated` 日期
|
||||||
5. 输出拆解摘要(卡名、complexity、执行顺序)
|
5. 输出拆解摘要(卡名、complexity、执行顺序)
|
||||||
|
|
||||||
### 质量检查
|
### 质量检查
|
||||||
- [ ] 每张卡有 `files`
|
|
||||||
- [ ] 每张卡有 `acceptance`
|
- [ ] 每张卡有 `acceptance`
|
||||||
- [ ] complexity 全部是 S 或 M
|
- [ ] complexity 全部是 S 或 M
|
||||||
- [ ] 有依赖的卡填了 `depends`
|
- [ ] 有依赖的卡填了 `depends`
|
||||||
- [ ] 任务卡按执行顺序排列
|
- [ ] 任务卡按执行顺序排列
|
||||||
|
- [ ] `files` 尽量填入(建议项,有则帮助 agent 更快定位)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ description: >
|
|||||||
成为每个项目的贴身导师。炼境本身亦被纳入其中,
|
成为每个项目的贴身导师。炼境本身亦被纳入其中,
|
||||||
形成可自我进化的开发智能闭环。
|
形成可自我进化的开发智能闭环。
|
||||||
iteration: 1
|
iteration: 1
|
||||||
updated: 2026-04-19
|
updated: 2026-06-29
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -106,14 +106,14 @@ modules:
|
|||||||
- id: github-auth
|
- id: github-auth
|
||||||
name: GitHub OAuth
|
name: GitHub OAuth
|
||||||
area: backend
|
area: backend
|
||||||
status: done
|
status: deprecated
|
||||||
progress: 100
|
progress: 100
|
||||||
position: [250, 350]
|
position: [250, 350]
|
||||||
|
|
||||||
- id: github-publish
|
- id: github-publish
|
||||||
name: GitHub 发布
|
name: GitHub 发布
|
||||||
area: backend
|
area: backend
|
||||||
status: done
|
status: deprecated
|
||||||
progress: 100
|
progress: 100
|
||||||
position: [500, 350]
|
position: [500, 350]
|
||||||
|
|
||||||
@ -363,6 +363,28 @@ modules:
|
|||||||
progress: 100
|
progress: 100
|
||||||
position: [3250, -150]
|
position: [3250, -150]
|
||||||
|
|
||||||
|
# ── 统一 Gitea 接入包(2026-06-19 战略转向)──────
|
||||||
|
- id: onboarding-pack
|
||||||
|
name: 接入包
|
||||||
|
area: backend
|
||||||
|
status: done
|
||||||
|
progress: 100
|
||||||
|
position: [3500, 0]
|
||||||
|
|
||||||
|
- id: gitea-integration
|
||||||
|
name: Gitea 集成
|
||||||
|
area: backend
|
||||||
|
status: concept
|
||||||
|
progress: 0
|
||||||
|
position: [3500, 150]
|
||||||
|
|
||||||
|
- id: agent-infrastructure
|
||||||
|
name: Agent 友好基础设施生成
|
||||||
|
area: backend
|
||||||
|
status: concept
|
||||||
|
progress: 0
|
||||||
|
position: [3750, 0]
|
||||||
|
|
||||||
edges:
|
edges:
|
||||||
# 前端依赖
|
# 前端依赖
|
||||||
- from: workspace-mgmt
|
- from: workspace-mgmt
|
||||||
@ -572,3 +594,29 @@ edges:
|
|||||||
- from: cloud-products
|
- from: cloud-products
|
||||||
to: cloud-db-instances
|
to: cloud-db-instances
|
||||||
type: dependency
|
type: dependency
|
||||||
|
|
||||||
|
# 接入包(统一 Gitea 双层架构)
|
||||||
|
- from: blueprint-buff
|
||||||
|
to: onboarding-pack
|
||||||
|
type: dependency
|
||||||
|
- from: mcp-config-inject
|
||||||
|
to: onboarding-pack
|
||||||
|
type: dependency
|
||||||
|
- from: blueprint-onboarding
|
||||||
|
to: onboarding-pack
|
||||||
|
type: dependency
|
||||||
|
- from: onboarding-pack
|
||||||
|
to: gitea-integration
|
||||||
|
type: dependency
|
||||||
|
- from: onboarding-pack
|
||||||
|
to: agent-infrastructure
|
||||||
|
type: dependency
|
||||||
|
- from: devtools-scan
|
||||||
|
to: agent-infrastructure
|
||||||
|
type: dependency
|
||||||
|
- from: project-env-scan
|
||||||
|
to: agent-infrastructure
|
||||||
|
type: dependency
|
||||||
|
- from: gitea-integration
|
||||||
|
to: flywheel-intelligence
|
||||||
|
type: related
|
||||||
|
|||||||
104
.blueprint/modules/agent-infrastructure.md
Normal file
104
.blueprint/modules/agent-infrastructure.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# Agent 友好基础设施生成
|
||||||
|
|
||||||
|
接入包 v2 的核心扩展:从"分发固定模板"升级为"根据技术栈定制生成 Agent 友好基础设施"。
|
||||||
|
炼境扫描项目技术栈 → 用户确认 → 生成三件套(元规则 + 契约映射 + 工具派生格式)。
|
||||||
|
|
||||||
|
## 定位
|
||||||
|
|
||||||
|
现有接入包(v1)解决了"让项目被炼境感知"的问题。
|
||||||
|
本模块解决"让 agent 在项目里真正高效工作"的问题。
|
||||||
|
|
||||||
|
两者的关系:
|
||||||
|
- v1 是注册入炼境飞轮的门票
|
||||||
|
- v2 是项目内部 agent 工作环境的基础设施
|
||||||
|
|
||||||
|
## 生成的三件套
|
||||||
|
|
||||||
|
| 产物 | 用途 | 单一来源 |
|
||||||
|
|------|------|---------|
|
||||||
|
| `AGENTS.md`(元规则段落) | 跨工具通用的防腐约定 | 炼境统一版本,接入时写入 |
|
||||||
|
| `docs/ai-context/project-context.yaml` | 技术栈特定的契约路径映射 | 炼境扫描后生成,技术栈变更时重新生成 |
|
||||||
|
| `.claude/rules/doc-freshness.md` | Claude Code 的路径注入派生版 | 从 AGENTS.md 元规则自动派生 |
|
||||||
|
|
||||||
|
## 接入流程(扫描 → 确认 → 生成)
|
||||||
|
|
||||||
|
```
|
||||||
|
① 炼境扫描
|
||||||
|
package.json / Cargo.toml / go.mod / Dockerfile
|
||||||
|
→ 识别框架、ORM、API 层、monorepo 结构
|
||||||
|
|
||||||
|
② 展示给用户确认(不跳过此步)
|
||||||
|
"检测到:React 19 + Drizzle(drizzle/schema.ts)+ Hono(src/routes/)
|
||||||
|
是否正确?"
|
||||||
|
|
||||||
|
③ 用户确认或手动修正
|
||||||
|
|
||||||
|
④ 定制化生成三件套
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术栈 → 契约路径映射规则
|
||||||
|
|
||||||
|
炼境内置识别表(示例,可扩展):
|
||||||
|
|
||||||
|
| 识别条件 | 契约类型 | 默认路径 |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| `prisma` in dependencies | 数据库 schema | `prisma/schema.prisma` |
|
||||||
|
| `drizzle-orm` in dependencies | 数据库 schema | `drizzle/schema.ts` |
|
||||||
|
| `hono` / `express` | API 接口 | `src/routes/**` |
|
||||||
|
| `openapi` / `swagger` | API 接口 | `openapi.yaml` |
|
||||||
|
| Rust + `axum` | API 接口 | `src/routes/` |
|
||||||
|
| pnpm workspace | 共享类型 | `packages/types/**` |
|
||||||
|
|
||||||
|
## 腐化监控(接入后持续运行)
|
||||||
|
|
||||||
|
炼境定期读取 git log,对比文档文件 vs 代码文件的最后修改时间:
|
||||||
|
|
||||||
|
| 风险级别 | 判断条件 |
|
||||||
|
|---------|---------|
|
||||||
|
| 🟢 正常 | 文档与代码同步更新 |
|
||||||
|
| 🟡 注意 | 核心代码改了,文档 14 天未动 |
|
||||||
|
| 🔴 高风险 | 外部契约类文件改了,AGENTS.md 30 天未动 |
|
||||||
|
|
||||||
|
技术栈变更感知:`package.json` / `Cargo.toml` 变化时主动提示"建议重新运行接入包生成"。
|
||||||
|
|
||||||
|
## 设计原则
|
||||||
|
|
||||||
|
- 元规则不含具体路径(技术栈无关),`project-context.yaml` 含路径(技术栈特定)
|
||||||
|
- 炼境生成,不要求用户手动维护多份格式
|
||||||
|
- 修复腐化永远是本地 agent 的事,炼境只检测和提醒
|
||||||
|
|
||||||
|
## 决策记录
|
||||||
|
|
||||||
|
- 元规则选用四支柱 + T1-T4 触发类型的双层结构:四支柱给 agent 判断"这个项目 ok 吗",T1-T4 给 agent 判断"这次改动需要更新什么"
|
||||||
|
- project-context.yaml 选 YAML 而非写进 AGENTS.md:技术栈映射是易变信息,独立文件符合防腐铁律(不写死进常驻文档)
|
||||||
|
- 确认步骤不可跳过:项目可能正在迁移技术栈,扫描结果不一定是当前权威状态
|
||||||
|
|
||||||
|
## 参考验证
|
||||||
|
|
||||||
|
2026-06-29 用 enterprise-system 做模拟验证:
|
||||||
|
- 技术栈识别准确(React + Drizzle + Hono + PostgreSQL)
|
||||||
|
- 最近 git log 有 "chore(db): 彻底移除 Prisma,全面统一 Drizzle",完美复现 T3 工具链变更场景
|
||||||
|
- project-context.yaml 不存在,验证了它需要炼境主动生成
|
||||||
|
- .claude/rules/ 已有 6 个路径注入规则,验证了派生格式的可行性
|
||||||
|
|
||||||
|
## 任务卡
|
||||||
|
|
||||||
|
> 本模块为 L 级,需先走 /architect 设计再拆分。以下为 concept 阶段的方向记录。
|
||||||
|
|
||||||
|
### 💭 技术栈扫描与确认 UI
|
||||||
|
- status: concept
|
||||||
|
|
||||||
|
### 💭 project-context.yaml 生成器
|
||||||
|
- status: concept
|
||||||
|
|
||||||
|
### 💭 AGENTS.md 元规则段落注入
|
||||||
|
- status: concept
|
||||||
|
|
||||||
|
### 💭 .claude/rules/ 派生生成
|
||||||
|
- status: concept
|
||||||
|
|
||||||
|
### 💭 腐化风险看板(文档新鲜度评分)
|
||||||
|
- status: concept
|
||||||
|
|
||||||
|
### 💭 技术栈变更感知与重新生成提示
|
||||||
|
- status: concept
|
||||||
@ -10,10 +10,16 @@
|
|||||||
|
|
||||||
本模块分三个阶段逐步打通数据→改善的完整闭环。
|
本模块分三个阶段逐步打通数据→改善的完整闭环。
|
||||||
|
|
||||||
|
## 方向修订(2026-06-19)
|
||||||
|
|
||||||
|
阶段一/二依赖「AI 主动写结构化复盘笔记」作为执行质量数据源。实测发现该进料口几乎枯竭:70 条笔记仅炼境自身 4 条结构化,外部项目存量为旧格式,`rework_count`/`avg_rounds_M` 长期为 0/null。
|
||||||
|
|
||||||
|
**根本转向**:执行质量数据从「AI 主动上报」改为「炼境从 git 被动自吸」——解析 conventional commit 的 type(fix=返工)/scope(=模块)自动计算,客户端无关、工作流无关。新增 **L0 解析层**(见任务卡)作为阶段三预测层的数据地基。详见 memory [[project_gitea_unified_architecture]] 及 onboarding-pack / gitea-integration 模块。
|
||||||
|
|
||||||
## 阶段触发条件
|
## 阶段触发条件
|
||||||
|
|
||||||
- **阶段二启动条件**:阶段一上线后,梦核输出中稳定出现"高频陷阱摘要"板块,且跨项目复盘笔记数量 ≥ 20 条
|
- **阶段二启动条件**:阶段一上线后,梦核输出中稳定出现"高频陷阱摘要"板块,且跨项目复盘笔记数量 ≥ 20 条
|
||||||
- **阶段三启动条件**:阶段二运行 ≥ 3 个月,accumulated 数据覆盖 ≥ 15 个项目,usage.json 携带 rework_count 和 avg_rounds 字段
|
- **阶段三启动条件(已修订)**:L0 解析层上线后,conventional commit 执行质量数据稳定覆盖 ≥ 10 个项目(不再依赖复盘笔记积累时长,见上「方向修订」)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -93,6 +99,17 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### ✅ L0:Conventional Commit 执行质量解析层(数据地基,先于阶段三)
|
||||||
|
- status: done(2026-06-19,实现见 onboarding-pack 卡 C1+C2)
|
||||||
|
- complexity: L
|
||||||
|
- depends: onboarding-pack(项目须先种 lefthook + conventional 规范)
|
||||||
|
- files: src-tauri/src/commands/commit_metrics.rs, src-tauri/src/commands/buff.rs, src-tauri/src/commands/blueprint.rs, src-tauri/src/db.rs
|
||||||
|
- acceptance: 炼境后端从本地 git 解析 conventional commit —— type=fix 计返工、scope 映射模块、commit 数≈轮数;自动填充 usage.json 的 rework_count/avg_rounds_M/complexity_dist,取代「从复盘笔记解析」的枯竭路径;CI 自修 commit(如 `[DeepSeek-V3]`)须从返工率剔除;squash merge 项目须降级处理
|
||||||
|
|
||||||
|
> 背景:实测复盘笔记进料口枯竭(70 条仅 4 条结构化)。改由 git 自吸,客户端无关、工作流无关,是阶段三预测层的真实数据基础。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### 阶段三:执行质量预测层
|
### 阶段三:执行质量预测层
|
||||||
- status: todo
|
- status: todo
|
||||||
- complexity: L
|
- complexity: L
|
||||||
|
|||||||
55
.blueprint/modules/gitea-integration.md
Normal file
55
.blueprint/modules/gitea-integration.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Gitea 集成
|
||||||
|
|
||||||
|
对接已有的自建 Gitea 实例,作为所有被管理项目的统一 git 后端。接入包「增配档」的核心。
|
||||||
|
|
||||||
|
## 风筝线模型(炼境与 Gitea 的关系)
|
||||||
|
|
||||||
|
🪁 **风筝 = Gitea 本体**(git 存储 + CI 执行):独立部署、独立运行,炼境**不托管其进程、不背运维**。
|
||||||
|
🧵 **线 = 炼境的掌控**(控制面):炼境是**唯一持线人**——Gitea 连接/token 统一存炼境,建仓/关联/迁移/配置 lefthook&CI 的操控全经炼境,子项目对 Gitea 的动作都从它手里过。
|
||||||
|
👀 **关注风筝的动态 = 观测面**(飞轮燃料):炼境持续感知每个子项目 ↔ Gitea 的连接动态(push 的 commit type/scope、PR、CI 三阶段、review、停滞项目)。
|
||||||
|
|
||||||
|
> 「线」≠ 松散 API 对接:炼境是**主动掌控者**而非被动客户端。风筝越多 → 线越多 → 动态越丰富 → 飞轮越强(契合核心愿景)。
|
||||||
|
|
||||||
|
## 其他边界
|
||||||
|
|
||||||
|
- 替代炼境现有 GitHub 能力:`github-auth` / `github-publish` 已冻结,`cicd.rs` 的 GitHub Actions 生成改造为 Gitea Actions
|
||||||
|
- 炼境自身仓库也迁 Gitea(发布产物仍走 OSS updater 不变)
|
||||||
|
|
||||||
|
## 参考实现
|
||||||
|
|
||||||
|
`enterprise-system` 已趟通完整链路,属「照搬」而非「从零探索」:
|
||||||
|
- Gitea origin(`git.yljt.info`)+ Gitee 镜像(备份)
|
||||||
|
- lefthook 强制 conventional commit + typecheck(pre-commit)
|
||||||
|
- Gitea Actions 三阶段 CI:静态检查 → 测试(DeepSeek-V3 自修)→ PR code review(DeepSeek)
|
||||||
|
- Gitea Actions 语法兼容 GitHub Actions,迁移成本低
|
||||||
|
|
||||||
|
> 详见 `E:/code-project/enterprise-system/docs/ai-context/git-workflow.md` 与 `AGENTS.md`。
|
||||||
|
|
||||||
|
## 任务卡(concept,待 /architect)
|
||||||
|
|
||||||
|
### 💭 Gitea API 对接(握线基础)
|
||||||
|
- status: concept
|
||||||
|
- complexity: L
|
||||||
|
- acceptance: TBD —— 建仓/关联、推送、读 CI 状态、读 PR review 结果
|
||||||
|
|
||||||
|
### 💭 观测面:Gitea webhook 接入(持线人感知动态)
|
||||||
|
- status: concept
|
||||||
|
- complexity: L
|
||||||
|
- files: src-tauri/src/mcp_server.rs(内嵌 HTTP server 加 webhook 端点), src-tauri/src/commands/blueprint.rs
|
||||||
|
- acceptance: TBD —— 复用炼境内嵌 HTTP server 新增 Gitea webhook 接收端点;实时接住各子项目 push/PR/CI/review 事件 → 解析 conventional commit(对接 flywheel-intelligence 的 L0 解析层)→ 写飞轮快照 + 刷新连接动态;webhook 不可达时降级为定时轮询 Gitea API
|
||||||
|
|
||||||
|
### 💭 Gitea Actions workflow 生成
|
||||||
|
- status: concept
|
||||||
|
- complexity: M
|
||||||
|
- files: src-tauri/src/commands/cicd.rs(改造)
|
||||||
|
- acceptance: TBD —— 生成 Gitea Actions 三阶段 CI 模板(语法兼容 GitHub Actions)
|
||||||
|
|
||||||
|
### 💭 DeepSeek CI review 集成(可选插件)
|
||||||
|
- status: concept
|
||||||
|
- complexity: L
|
||||||
|
- acceptance: TBD —— 静态检查 + 测试自修 + PR review;作为复杂项目可选增配,不进基线档
|
||||||
|
|
||||||
|
### 💭 旧项目 GitHub → Gitea 迁移
|
||||||
|
- status: concept
|
||||||
|
- complexity: M
|
||||||
|
- acceptance: TBD —— remote 切换 + 推送,安全增量,不丢历史
|
||||||
@ -1,10 +1,12 @@
|
|||||||
---
|
---
|
||||||
id: github-auth
|
id: github-auth
|
||||||
name: GitHub OAuth
|
name: GitHub OAuth
|
||||||
status: done
|
status: deprecated
|
||||||
progress: 100
|
progress: 100
|
||||||
---
|
---
|
||||||
|
|
||||||
|
> ❄️ **已冻结(2026-06-19)**:全面统一自建 Gitea,弃用 GitHub。功能保留不删,但不再演进;新接入走 onboarding-pack / gitea-integration 模块。
|
||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
GitHub OAuth Device Flow 认证,获取 token 后支持 API 操作和 Git 推送。
|
GitHub OAuth Device Flow 认证,获取 token 后支持 API 操作和 Git 推送。
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
# GitHub 发布
|
# GitHub 发布
|
||||||
|
|
||||||
|
> ❄️ **已冻结(2026-06-19)**:全面统一自建 Gitea,弃用 GitHub。功能保留不删,不再演进;项目发布走 Gitea(见 gitea-integration 模块)。
|
||||||
|
|
||||||
在炼境内通过 GitHub OAuth 直接将本地项目推送到远程仓库,支持新建仓库或关联已有仓库,完成发布后自动回填 remote URL。
|
在炼境内通过 GitHub OAuth 直接将本地项目推送到远程仓库,支持新建仓库或关联已有仓库,完成发布后自动回填 remote URL。
|
||||||
|
|
||||||
## 决策记录
|
## 决策记录
|
||||||
|
|||||||
99
.blueprint/modules/onboarding-pack.md
Normal file
99
.blueprint/modules/onboarding-pack.md
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# 接入包(Onboarding Pack)
|
||||||
|
|
||||||
|
炼境向被管理项目一键分发的标准化接入套件,让任意新旧项目成为「飞轮合格公民」。
|
||||||
|
统一以自建 Gitea 为 git 数据底座,缝合 git 规范、AI 约定、蓝图语义、炼境登记四层。
|
||||||
|
|
||||||
|
## 双层架构
|
||||||
|
|
||||||
|
- **自建 Gitea**:统一 git 数据底座(稳定、自主、CI + review 自控)
|
||||||
|
- **炼境**:底座之上的「项目飞轮控制台」——读 git 数据做跨项目飞轮聚合、分发接入包、维护蓝图语义层
|
||||||
|
|
||||||
|
> 决策(2026-06-19):全面统一自建 Gitea,弃用 GitHub。数据主权 = 飞轮燃料自主可控,契合「管理越多→数据越多→飞轮越强」愿景。详见 memory [[project_gitea_unified_architecture]]。
|
||||||
|
|
||||||
|
## 蓝图重新定位(git 与蓝图分工)
|
||||||
|
|
||||||
|
> 完整定义见 `.blueprint/CONVENTIONS.md` §「蓝图定位(意图层)」。
|
||||||
|
|
||||||
|
核心分工:git 记录「做了什么」,蓝图只保留 git 给不了的三类信息(结构关系图 / 意图与验收标准 / 未来规划)。状态/进度/涉及文件从 git 信号推断,蓝图不手动双写。
|
||||||
|
|
||||||
|
## 接入包分档(底座统一,上层按需)
|
||||||
|
|
||||||
|
| 档 | 内容 | 适用 |
|
||||||
|
|----|------|------|
|
||||||
|
| **基线**(所有项目) | Gitea 建仓/迁移 + origin=Gitea + lefthook(强制 conventional) + AGENTS.md + git-workflow.md + 炼境登记 + buff 解析 commit | 全部新旧项目 |
|
||||||
|
| **增配**(复杂项目) | + Gitea Actions CI + DeepSeek review + 瘦蓝图 | 长周期/多业务线 |
|
||||||
|
|
||||||
|
## 决策记录
|
||||||
|
|
||||||
|
- 飞轮升格与 Gitea API 对接「分步实施」而非耦合:buff 读本地 `.git`(commit type/scope),与远程平台无关,可先做;Gitea API(CI 状态/PR review)随后
|
||||||
|
- 「完整分发」是陷阱,正确形态是分级菜单:新项目种全套,旧项目增量接入(复用 mcp_inject 的「命名空间隔离 + 合并不覆盖」语义,不动用户已有配置)
|
||||||
|
- 接入动作:现有 `apply_blueprint_buff` 升格为 `apply_onboarding_pack`,按档编排注入
|
||||||
|
- 语义层进料口隐患:决策/验收/踩坑仍需主动写(与饿死复盘笔记同源),须配「从 PR 标题/commit body/review 评论半自动提取」,不再单纯指望人自觉
|
||||||
|
|
||||||
|
## 复用的现有能力(~60% 地基已具备)
|
||||||
|
|
||||||
|
| 能力 | 现状 |
|
||||||
|
|------|------|
|
||||||
|
| `.claude/settings.json` 注入 | ✅ `mcp_inject`(命名空间隔离 + 合并不覆盖 + JSON 校验) |
|
||||||
|
| 生成接入 prompt / 扫描项目 | ✅ `generate_blueprint_prompt`(已读 AGENTS.md + monorepo 感知) |
|
||||||
|
| git 轮询 → 状态同步 | 🟡 `buff`(仅匹配文件路径,需补 conventional 解析,见 flywheel-intelligence L0) |
|
||||||
|
| CONVENTIONS 规则同步 | ✅ `sync_blueprint_rules` |
|
||||||
|
| 项目模板 | ✅ `templates` |
|
||||||
|
| CI workflow 生成 | 🟡 `cicd.rs`(仅 GitHub,需改造 Gitea Actions,见 gitea-integration) |
|
||||||
|
|
||||||
|
## 风险
|
||||||
|
|
||||||
|
- 旧项目存量 git 数据脏(无 scope、非 conventional)→ 数据质量是光谱,需「采用规范后切起算」或标不可信
|
||||||
|
- 跨项目聚合可比性:不同项目 scope 粒度/复杂度标准不同,fix 率直接相加会误导梦核
|
||||||
|
- 模板自身维护成本:AGENTS.md/lefthook/git-workflow.md 模板要随最佳实践演进
|
||||||
|
|
||||||
|
## 设计(architect 2026-06-19)
|
||||||
|
|
||||||
|
基线档范围:模板分发 + 炼境登记 + L0 commit 解析,**全部本地、不依赖 Gitea API**(Gitea 建仓/Actions/webhook 属 gitea-integration 档2)。
|
||||||
|
|
||||||
|
关键决策:
|
||||||
|
- **新建 `apply_onboarding_pack`**(不扩展 buff):buff=挂 git 观测,onboarding_pack=完整接入,分层清晰
|
||||||
|
- **模板"存在则跳过 + 报告"**:markdown/yaml 不强行整文件合并,绝不覆盖用户已有(复用 mcp_inject 的路径解析 + 不存在才写)
|
||||||
|
- **L0 commit 解析挂在现有 buff watcher**:`last_commit..HEAD` 取 message → parse_conventional → 写新表 `commit_metrics`;与文件路径匹配两条路径并行互不干扰
|
||||||
|
- **rework_count 来源**从枯竭的 `parse_notes_metrics` 换成查 `commit_metrics`(fix 且非 CI 自修);`avg_rounds_M` 基线暂为 null(需 PR 边界,留档2)
|
||||||
|
- **scope 原样聚合**,module 映射后置
|
||||||
|
|
||||||
|
新增:`commands/onboarding.rs`、`commands/commit_metrics.rs`、`resources/onboarding/*.tmpl`、`commit_metrics` 表
|
||||||
|
修改:`buff.rs`(check_project 加解析)、`blueprint.rs`(append_usage_snapshot)、`db.rs`、`lib.rs`、前端入口
|
||||||
|
|
||||||
|
## 任务卡
|
||||||
|
|
||||||
|
> 已过 /architect + /splitter。A→B、C1→C2 两条链可并行,D 依赖 C1+B;**C1(飞轮地基)建议优先开工**。
|
||||||
|
|
||||||
|
### ✅ 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: done
|
||||||
|
- 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
|
||||||
|
- acceptance: apply_onboarding_pack(project_path, project_id) 组合「模板分发 + apply_blueprint_buff」返回 OnboardingReport;看板卡片有"接入包"入口,展示分发报告(written/skipped/failed)
|
||||||
|
|
||||||
|
### ✅ C1 commit_metrics 表 + Conventional 解析器
|
||||||
|
- status: done
|
||||||
|
- complexity: M
|
||||||
|
- files: src-tauri/src/commands/commit_metrics.rs, src-tauri/src/db.rs
|
||||||
|
- acceptance: 新增 commit_metrics 表(sha PK / project_id / commit_type / scope / is_rework / is_ci_auto / committed_at / parsed_at);`parse_conventional(message)` 解析 type/scope,type=fix→is_rework,`[DeepSeek-V3]`/`[R1]` 等标记→is_ci_auto,非 conventional→commit_type=null 单列;提供聚合查询 `rework_count(project_id)`;解析逻辑纯函数可单测(覆盖 feat/fix/带scope/无scope/CI自修/非规范 六种输入)
|
||||||
|
|
||||||
|
### ✅ C2 挂接 buff watcher + 飞轮快照
|
||||||
|
- status: done
|
||||||
|
- complexity: M
|
||||||
|
- depends: onboarding-pack(卡 C1)
|
||||||
|
- 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 数据可信度提示
|
||||||
|
- status: done
|
||||||
|
- complexity: S
|
||||||
|
- depends: onboarding-pack(卡 C1、卡 B)
|
||||||
|
- files: src-tauri/src/commands/onboarding.rs, src-tauri/src/commands/commit_metrics.rs
|
||||||
|
- acceptance: OnboardingReport 与飞轮展示标注该项目 commit 规范化比例(conventional / 总数);比例低时提示"种 lefthook 后数据才准",避免脏数据被误读为可信(CI 过滤 / 非规范单列已在 C1 落实)
|
||||||
File diff suppressed because it is too large
Load Diff
4
.claude/rules/db-schema.md
Normal file
4
.claude/rules/db-schema.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
globs: src-tauri/src/db.rs
|
||||||
|
---
|
||||||
|
修改 `db.rs` 新增表或字段时,确认已在 `migrate()` 函数中补充对应 migration 语句;缺失会导致旧版本用户升级后启动报错(对应蓝图 R05 规则)。
|
||||||
4
.claude/rules/onboarding-sync.md
Normal file
4
.claude/rules/onboarding-sync.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
globs: src-tauri/resources/onboarding/**
|
||||||
|
---
|
||||||
|
修改接入包模板时,检查 `docs/templates/agents-meta-rules.md` 是否需要同步更新——两者描述同一套元规则,必须保持一致(单一事实来源:tmpl 是分发实体,docs/templates/ 是设计草案)。
|
||||||
4
.claude/rules/tauri-contract.md
Normal file
4
.claude/rules/tauri-contract.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
globs: src-tauri/src/commands/**
|
||||||
|
---
|
||||||
|
新增或修改 Tauri command 时,检查 `src/lib/commands.ts` 是否有对应前端封装;缺失则补写,否则前端无法调用(对应蓝图 R01 规则)。
|
||||||
111
AGENTS.md
Normal file
111
AGENTS.md
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# 炼境 — AI Agent 上下文
|
||||||
|
|
||||||
|
> 本文件遵循 [AGENTS.md 开放规范](https://agents.md/),供 Claude Code、Qwen Code、Cursor、Copilot 等 AI 工具读取。
|
||||||
|
|
||||||
|
## 项目定位
|
||||||
|
|
||||||
|
Git 多空间开发管理器,同时也是**项目 Agent 化改造引擎**。
|
||||||
|
以蓝图为神经系统,以 Claude 为智能层,感知所管理项目的功能运转、质量状态与成长方向。
|
||||||
|
炼境本身亦被纳入其中,形成可自我进化的开发智能闭环。
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- 前端:React 19 + TypeScript + TailwindCSS + React Query + Zustand + React Flow
|
||||||
|
- 后端:Rust + Tauri v2 + SQLite(rusqlite + r2d2)
|
||||||
|
- MCP Server:内嵌于 Tauri 进程(HTTP),供 Claude Code 调用炼境数据
|
||||||
|
- 包管理:pnpm(禁止 npm/yarn/npx/bun)
|
||||||
|
|
||||||
|
## 核心开发约束
|
||||||
|
|
||||||
|
**包管理**:pnpm 唯一,禁止 npm / yarn / npx / bun。
|
||||||
|
|
||||||
|
**TypeScript**:优先 TypeScript,避免 `any`;函数/变量 camelCase,组件 PascalCase;async/await,不用裸 Promise 链;try/catch 不吞异常。
|
||||||
|
|
||||||
|
**Rust 业务函数**:禁止在函数内部调用 `pool().get()`,连接由调用方传入(依赖注入,便于 in-memory DB 测试)。Tauri command 层负责获取连接并调用(薄包装,无需测试)。
|
||||||
|
|
||||||
|
## 架构速查
|
||||||
|
|
||||||
|
```
|
||||||
|
src/lib/commands.ts 所有 Tauri command 的前端封装(唯一调用入口)
|
||||||
|
src-tauri/src/commands/ Rust 后端实现(每功能一个 .rs 文件)
|
||||||
|
src-tauri/src/db.rs SQLite 建表 + migration 逻辑
|
||||||
|
src-tauri/src/mcp_server.rs 内嵌 MCP Server(agent 调用炼境的入口)
|
||||||
|
src-tauri/src/mcp_inject.rs MCP 配置注入(写入被管理项目的 .claude/settings.json)
|
||||||
|
src-tauri/resources/onboarding/ 接入包分发模板(AGENTS.md/lefthook/git-workflow)
|
||||||
|
.blueprint/ 项目蓝图(manifest + 模块详情 + 任务卡)
|
||||||
|
docs/ai-context/ AI 上下文文档(速查卡、契约映射)
|
||||||
|
docs/templates/ 炼境接入包模板草案(设计层)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键约定
|
||||||
|
|
||||||
|
1. **新增 Tauri command → 必须同步更新 `src/lib/commands.ts`**,否则前端无法调用
|
||||||
|
2. **SQLite 新增表或字段 → 必须在 `db.rs` 的 `migrate()` 里补 migration 语句**,否则旧版本升级后启动报错
|
||||||
|
3. **修改接入包模板(`resources/onboarding/`)→ 检查 `docs/templates/agents-meta-rules.md` 是否需要同步**
|
||||||
|
|
||||||
|
## 多 AI CLI 协作
|
||||||
|
|
||||||
|
本项目可由多个 AI CLI 协同。交接时附上:
|
||||||
|
|
||||||
|
```
|
||||||
|
已完成:<修改了哪些文件 + 核心决策>
|
||||||
|
待验证:<需要对方确认的地方>
|
||||||
|
约束继承:<影响后续工作的关键限制>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent 友好工程四支柱
|
||||||
|
|
||||||
|
修改代码前,确认这个项目满足四支柱:
|
||||||
|
|
||||||
|
**可导航**:凭文件名/目录名可在 30 秒内定位某功能的代码。通用能力有独立家,不寄生在业务模块下。
|
||||||
|
|
||||||
|
**可推理**:公共类型只在一处定义。改一个接口,类型检查能报出所有受影响处。
|
||||||
|
|
||||||
|
**可验证**:typecheck / lint / test 各有一条命令真能跑。关键路径有冒烟测试。
|
||||||
|
|
||||||
|
**可记忆**:规则按场景注入(不是一份巨型文档)。决策只写"为什么",代码自己回答"是什么"。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文档防腐元规则
|
||||||
|
|
||||||
|
做任何改动时,判断它属于哪类变更:
|
||||||
|
|
||||||
|
### T1 外部契约变更(最高优先级)
|
||||||
|
其他代码、服务、或 agent 依赖的定义——Tauri command 接口、MCP 接口、数据库 schema、共享类型。
|
||||||
|
→ 查 `docs/ai-context/project-context.yaml` 找到对应文档,检查并同步更新。
|
||||||
|
|
||||||
|
### T2 架构边界变更
|
||||||
|
模块划分、目录结构、依赖方向、命令分组变化。
|
||||||
|
→ 检查本文件架构速查和 `docs/ai-context/project-brief.md` 是否需要同步。
|
||||||
|
|
||||||
|
### T3 工具链变更
|
||||||
|
引入或替换库、Tauri 版本升级、Rust 依赖变更。
|
||||||
|
→ 更新本文件技术栈声明;更新 `docs/ai-context/project-context.yaml`。
|
||||||
|
|
||||||
|
### T4 常驻上下文过期
|
||||||
|
本文件(AGENTS.md)或 `docs/ai-context/project-brief.md` 描述的现实已经改变。
|
||||||
|
→ 立即更新。这两份文档常驻 agent 上下文,过期危害最大。
|
||||||
|
|
||||||
|
### 不触发检查
|
||||||
|
内部重构、性能优化、bug fix(外部行为不变)、测试文件、UI 样式调整。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文档防腐铁律
|
||||||
|
|
||||||
|
- 文档只写稳定的:为什么、铁律、意图、架构决策。
|
||||||
|
- 易变的(端口、表名、命令清单)交给工具自省,不写死进常驻文档。
|
||||||
|
- 单一事实来源:两处说同一事时,删掉派生的那处。
|
||||||
|
- 两份文档打架时,信代码,不信文档。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 定期防腐自查
|
||||||
|
|
||||||
|
- [ ] 技术栈声明还和 `package.json` / `Cargo.toml` 一致吗?
|
||||||
|
- [ ] `project-brief.md` 最近 30 天有没有随架构改动更新?
|
||||||
|
- [ ] `src/lib/commands.ts` 的封装覆盖了所有 `src-tauri/src/commands/` 下的 command 吗?
|
||||||
|
- [ ] 接入包模板(`resources/onboarding/`)和 `docs/templates/` 内容一致吗?
|
||||||
22
CLAUDE.md
22
CLAUDE.md
@ -13,13 +13,17 @@ Git 多空间开发管理器,Tauri v2 + React + TypeScript + Rust。
|
|||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
```
|
```
|
||||||
src/ 前端源码
|
src/ 前端源码
|
||||||
src/components/ React 组件(按功能分目录)
|
src/components/ React 组件(按功能分目录)
|
||||||
src/lib/commands.ts Tauri command 调用封装
|
src/lib/commands.ts Tauri command 调用封装
|
||||||
src-tauri/src/ Rust 后端
|
src-tauri/src/ Rust 后端
|
||||||
src-tauri/src/commands/ Tauri command 实现
|
src-tauri/src/commands/ Tauri command 实现
|
||||||
.blueprint/ 项目蓝图(manifest + 模块详情 + 任务卡)
|
src-tauri/resources/onboarding/ 接入包分发模板(AGENTS.md、git-workflow、lefthook)
|
||||||
docs/ 部署和更新指南
|
.blueprint/ 项目蓝图(manifest + 模块详情 + 任务卡)
|
||||||
|
.claude/rules/ 路径注入规则(tauri-contract / db-schema / onboarding-sync)
|
||||||
|
AGENTS.md 跨工具 AI 上下文(四支柱 + 元规则,单一来源)
|
||||||
|
docs/ai-context/ AI 速查文档(project-brief.md、project-context.yaml)
|
||||||
|
docs/templates/ 炼境接入包模板草案(agents-meta-rules 等)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 命令执行规则
|
## 命令执行规则
|
||||||
@ -59,7 +63,7 @@ docs/ 部署和更新指南
|
|||||||
|----------|--------------|
|
|----------|--------------|
|
||||||
| "我想加个 XX 功能" | 确认需求后:manifest 新增模块(status: concept),创建 `.blueprint/modules/<id>.md` |
|
| "我想加个 XX 功能" | 确认需求后:manifest 新增模块(status: concept),创建 `.blueprint/modules/<id>.md` |
|
||||||
| "XX 功能需要 A、B、C" | 在模块文件中拆分为任务卡 |
|
| "XX 功能需要 A、B、C" | 在模块文件中拆分为任务卡 |
|
||||||
| "这个方案确定了" | 模块 status → planned,补充任务卡 files/acceptance |
|
| "这个方案确定了" | 模块 status → planned,补充任务卡 acceptance(files 可选) |
|
||||||
| "开始做 XX" | 模块 status → in_progress |
|
| "开始做 XX" | 模块 status → in_progress |
|
||||||
| 没提蓝图但在讨论功能 | 主动提议更新蓝图 |
|
| 没提蓝图但在讨论功能 | 主动提议更新蓝图 |
|
||||||
|
|
||||||
@ -67,7 +71,7 @@ docs/ 部署和更新指南
|
|||||||
|
|
||||||
| complexity | 执行流程 |
|
| complexity | 执行流程 |
|
||||||
|-----------|---------|
|
|-----------|---------|
|
||||||
| S / M | 直接拆任务卡,确保每张卡有 `files` + `acceptance` + `complexity` → 标记 📋 |
|
| S / M | 直接拆任务卡,确保每张卡有 `acceptance` + `complexity` → 标记 📋(`files` 可选) |
|
||||||
| L(复杂系统设计) | 先 `/architect` 输出设计文档,用户确认后 `/splitter` 拆成 S/M 任务卡 |
|
| L(复杂系统设计) | 先 `/architect` 输出设计文档,用户确认后 `/splitter` 拆成 S/M 任务卡 |
|
||||||
|
|
||||||
> L 级任务禁止直接写代码,必须先经过 `/architect` → `/splitter` 流程。
|
> L 级任务禁止直接写代码,必须先经过 `/architect` → `/splitter` 流程。
|
||||||
|
|||||||
41
docs/ai-context/project-brief.md
Normal file
41
docs/ai-context/project-brief.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# 炼境 — 速查卡
|
||||||
|
|
||||||
|
**定位**:Git 多空间开发管理器 + 项目 Agent 化改造引擎。Tauri v2 桌面 App,管理多个开发项目,为每个项目注入 Agent 友好基础设施,形成智能成长飞轮。
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
| 层 | 技术 |
|
||||||
|
|----|------|
|
||||||
|
| 前端 | React 19 + TypeScript + TailwindCSS + React Query + Zustand + React Flow |
|
||||||
|
| 后端 | Rust + Tauri v2 |
|
||||||
|
| 数据 | SQLite(rusqlite + r2d2) |
|
||||||
|
| MCP | 内嵌 HTTP Server(供 Claude Code 调用) |
|
||||||
|
| 包管理 | pnpm |
|
||||||
|
|
||||||
|
## 关键约定(改代码前必看)
|
||||||
|
|
||||||
|
1. **Tauri command ↔ 前端封装**:新增 command 必须同步更新 `src/lib/commands.ts`
|
||||||
|
2. **DB 变更必须有 migration**:在 `src-tauri/src/db.rs` 的 `migrate()` 里补语句,否则旧版本升级报错
|
||||||
|
3. **Rust 业务函数依赖注入**:禁止内部 `pool().get()`,连接由调用方传入
|
||||||
|
4. **接入包模板 = 分发给被管理项目的规则**:改 `resources/onboarding/` 务必同步 `docs/templates/`
|
||||||
|
|
||||||
|
## 目录速查
|
||||||
|
|
||||||
|
```
|
||||||
|
src/lib/commands.ts 前后端契约(唯一前端入口)
|
||||||
|
src-tauri/src/commands/ Rust 后端实现(一功能一文件)
|
||||||
|
src-tauri/src/db.rs 数据库 schema + migration
|
||||||
|
src-tauri/src/mcp_server.rs MCP Server(agent 调用炼境的入口)
|
||||||
|
src-tauri/resources/onboarding/ 接入包分发模板
|
||||||
|
.blueprint/ 项目蓝图
|
||||||
|
docs/ai-context/ AI 上下文文档(本文件、契约映射)
|
||||||
|
docs/templates/ 接入包模板草案(设计层)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验收命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm typecheck # TypeScript 类型检查
|
||||||
|
pnpm tauri dev # 启动开发环境
|
||||||
|
cargo test # Rust 单元测试(src-tauri/ 目录下运行)
|
||||||
|
```
|
||||||
36
docs/ai-context/project-context.yaml
Normal file
36
docs/ai-context/project-context.yaml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# 炼境契约路径映射
|
||||||
|
# 炼境自身版本,手工维护(等 agent-infrastructure 模块完成后由炼境自动生成)
|
||||||
|
# 供 AGENTS.md 元规则 T1 查询:「哪个文件是这个项目的契约?」
|
||||||
|
|
||||||
|
contracts:
|
||||||
|
- label: Tauri command 接口(前后端契约)
|
||||||
|
paths:
|
||||||
|
- "src-tauri/src/commands/**/*.rs"
|
||||||
|
- "src/lib/commands.ts"
|
||||||
|
doc: "新增 command 时两端必须同步;src/lib/commands.ts 是唯一前端调用入口"
|
||||||
|
|
||||||
|
- label: 数据库 schema
|
||||||
|
paths:
|
||||||
|
- "src-tauri/src/db.rs"
|
||||||
|
doc: "SQLite 表结构变更必须在 migrate() 函数中补 migration 语句"
|
||||||
|
|
||||||
|
- label: MCP Server 接口
|
||||||
|
paths:
|
||||||
|
- "src-tauri/src/mcp_server.rs"
|
||||||
|
- "src-tauri/src/mcp_inject.rs"
|
||||||
|
doc: "MCP 接口变更影响 Claude Code 对炼境的调用,接口路由和参数需同步"
|
||||||
|
|
||||||
|
- label: 接入包分发模板
|
||||||
|
paths:
|
||||||
|
- "src-tauri/resources/onboarding/**"
|
||||||
|
doc: "模板变更需同步检查 docs/templates/agents-meta-rules.md(设计层来源)"
|
||||||
|
|
||||||
|
|
||||||
|
architecture_docs:
|
||||||
|
- "AGENTS.md"
|
||||||
|
- "CLAUDE.md"
|
||||||
|
- ".blueprint/manifest.yaml"
|
||||||
|
|
||||||
|
agent_docs:
|
||||||
|
- "AGENTS.md"
|
||||||
|
- "docs/ai-context/project-brief.md"
|
||||||
81
docs/templates/agents-meta-rules.md
vendored
Normal file
81
docs/templates/agents-meta-rules.md
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
version: 1.0.0
|
||||||
|
updated: 2026-06-29
|
||||||
|
usage: 炼境接入包分发时写入被管理项目 AGENTS.md 的元规则标准内容
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agent 友好工程元规则
|
||||||
|
|
||||||
|
> 本节由炼境接入包分发,规则技术栈无关,适用于所有项目和所有 AI 工具。
|
||||||
|
> 项目特定的路径映射见 `docs/ai-context/project-context.yaml`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent 友好工程四支柱
|
||||||
|
|
||||||
|
修改代码前,确认这个项目满足四支柱:
|
||||||
|
|
||||||
|
**可导航**:凭文件名/目录名可在 30 秒内定位某功能的代码。通用能力有独立家,不寄生在业务模块下。
|
||||||
|
|
||||||
|
**可推理**:公共类型只在一处定义。改一个接口,类型检查能报出所有受影响处。
|
||||||
|
|
||||||
|
**可验证**:typecheck / lint / test 各有一条命令真能跑。关键路径有冒烟测试。
|
||||||
|
|
||||||
|
**可记忆**:规则按场景注入(不是一份巨型文档)。决策只写"为什么",代码自己回答"是什么"。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文档防腐元规则
|
||||||
|
|
||||||
|
做任何改动时,判断它属于哪类变更:
|
||||||
|
|
||||||
|
### T1 外部契约变更(最高优先级)
|
||||||
|
其他代码、服务、或 agent 依赖的定义——接口、共享类型、数据结构、配置键名、环境变量名。
|
||||||
|
→ 查 `docs/ai-context/project-context.yaml` 找到记录该契约的文档,检查并同步更新。
|
||||||
|
|
||||||
|
### T2 架构边界变更
|
||||||
|
模块划分、目录结构、依赖方向、包边界调整。
|
||||||
|
→ 检查架构文档是否需要同步。
|
||||||
|
|
||||||
|
### T3 工具链变更
|
||||||
|
引入或替换库、框架、构建工具。
|
||||||
|
→ 更新 AGENTS.md 技术栈声明;通知炼境重新扫描并更新 `project-context.yaml`。
|
||||||
|
|
||||||
|
### T4 常驻上下文过期
|
||||||
|
AGENTS.md 或 `docs/ai-context/project-brief.md` 描述的现实已经改变。
|
||||||
|
→ 立即更新。这两份文档常驻 agent 上下文,过期危害最大。
|
||||||
|
|
||||||
|
### 不触发检查
|
||||||
|
内部重构、性能优化、bug fix(外部行为不变)、测试文件、临时脚本。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文档防腐铁律
|
||||||
|
|
||||||
|
- 文档只写稳定的:为什么、铁律、意图、架构决策。
|
||||||
|
- 易变的(端口、路由、表名、命令清单)交给工具自省,不写死进常驻文档。写死 = 定时炸弹。
|
||||||
|
- 单一事实来源:两处说同一事时,删掉派生的那处。
|
||||||
|
- 两份文档打架时,信代码,不信文档。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 跨 AI 工具交接模板
|
||||||
|
|
||||||
|
完成一段工作并交接给下一个 agent 时,附上:
|
||||||
|
|
||||||
|
```
|
||||||
|
已完成:<修改了哪些文件 + 核心决策>
|
||||||
|
待验证:<需要对方确认的地方>
|
||||||
|
约束继承:<影响对方工作的关键限制>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 定期防腐自查
|
||||||
|
|
||||||
|
每隔一阵或大改后,对照以下清单:
|
||||||
|
|
||||||
|
- [ ] AGENTS.md 里的技术栈声明还和代码一致吗?
|
||||||
|
- [ ] `project-brief.md` 最近 30 天有没有随架构改动更新?
|
||||||
|
- [ ] 有没有两份文档说同一件事且打架?
|
||||||
|
- [ ] `project-context.yaml` 的契约路径还准确吗?
|
||||||
44
docs/templates/project-brief.md.tmpl
vendored
Normal file
44
docs/templates/project-brief.md.tmpl
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# <!-- 项目名 --> — 速查卡
|
||||||
|
|
||||||
|
> 由炼境接入包生成。请补全 TODO 段落后提交到仓库。
|
||||||
|
> 目标:新 agent 30 秒内能定向,不超过 300 字。
|
||||||
|
|
||||||
|
**定位**:<!-- TODO: 一句话描述项目定位和核心功能 -->
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
| 层 | 技术 |
|
||||||
|
|----|------|
|
||||||
|
<!-- TODO: 填入技术栈,例:
|
||||||
|
| 前端 | React 19 + TypeScript |
|
||||||
|
| 后端 | Node.js + Hono |
|
||||||
|
| 数据 | PostgreSQL + Drizzle ORM |
|
||||||
|
| 包管理 | pnpm |
|
||||||
|
-->
|
||||||
|
|
||||||
|
## 关键约定(改代码前必看)
|
||||||
|
|
||||||
|
<!-- TODO: 列出 3-5 条最重要的开发约束,例:
|
||||||
|
1. **类型单一来源**:跨模块共享类型只在 `packages/types/` 定义
|
||||||
|
2. **ORM 唯一**:使用 Drizzle,禁止 Prisma
|
||||||
|
3. **API 层禁止写统计逻辑**:一律用 SQL view
|
||||||
|
-->
|
||||||
|
|
||||||
|
## 目录速查
|
||||||
|
|
||||||
|
```
|
||||||
|
<!-- TODO: 列出关键目录及职责,例:
|
||||||
|
src/routes/ API 路由
|
||||||
|
src/types/ 共享类型(单一来源)
|
||||||
|
drizzle/schema.ts 数据库 schema
|
||||||
|
-->
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验收命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# TODO: 填入项目的一键验证命令
|
||||||
|
# pnpm typecheck # 类型检查
|
||||||
|
# pnpm test # 测试
|
||||||
|
# pnpm lint # lint
|
||||||
|
```
|
||||||
35
docs/templates/project-context.yaml.tmpl
vendored
Normal file
35
docs/templates/project-context.yaml.tmpl
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# 项目契约路径映射
|
||||||
|
# 由炼境接入包根据技术栈扫描生成,用户确认后写入
|
||||||
|
# 供 AGENTS.md 元规则 T1 查询:「哪个文件是这个项目的外部契约?」
|
||||||
|
# 技术栈变更后重新运行接入包生成
|
||||||
|
|
||||||
|
# contracts 由炼境扫描技术栈后自动填入,也可手动补充。
|
||||||
|
# 常见示例(取消注释并按实际修改):
|
||||||
|
contracts:
|
||||||
|
# - label: 数据库 schema(Drizzle)
|
||||||
|
# paths: ["drizzle/schema.ts"]
|
||||||
|
# doc: "schema 变更需同步更新数据库文档"
|
||||||
|
#
|
||||||
|
# - label: 数据库 schema(Prisma)
|
||||||
|
# paths: ["prisma/schema.prisma"]
|
||||||
|
# doc: "schema 变更需同步更新数据库文档"
|
||||||
|
#
|
||||||
|
# - label: API 接口(Hono / Express)
|
||||||
|
# paths: ["src/routes/**"]
|
||||||
|
# doc: "接口变更需同步更新 API 文档或 openapi.yaml"
|
||||||
|
#
|
||||||
|
# - label: 共享类型
|
||||||
|
# paths: ["packages/types/**", "src/types/**"]
|
||||||
|
# doc: "类型只在此处定义,禁止手抄到其他模块"
|
||||||
|
#
|
||||||
|
# - label: 环境变量声明
|
||||||
|
# paths: [".env.example"]
|
||||||
|
# doc: "新增环境变量必须在 .env.example 中同步声明"
|
||||||
|
|
||||||
|
architecture_docs:
|
||||||
|
- "AGENTS.md"
|
||||||
|
# - "ARCHITECTURE.md"
|
||||||
|
|
||||||
|
agent_docs:
|
||||||
|
- "AGENTS.md"
|
||||||
|
- "docs/ai-context/project-brief.md"
|
||||||
103
src-tauri/resources/onboarding/AGENTS.md.tmpl
Normal file
103
src-tauri/resources/onboarding/AGENTS.md.tmpl
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<!-- 项目名 --> — 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 / 其他关键文档及用途。 -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent 友好工程四支柱
|
||||||
|
|
||||||
|
修改代码前,确认这个项目满足四支柱:
|
||||||
|
|
||||||
|
**可导航**:凭文件名/目录名可在 30 秒内定位某功能的代码。通用能力有独立家,不寄生在业务模块下。
|
||||||
|
|
||||||
|
**可推理**:公共类型只在一处定义。改一个接口,类型检查能报出所有受影响处。
|
||||||
|
|
||||||
|
**可验证**:typecheck / lint / test 各有一条命令真能跑。关键路径有冒烟测试。
|
||||||
|
|
||||||
|
**可记忆**:规则按场景注入(不是一份巨型文档)。决策只写"为什么",代码自己回答"是什么"。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文档防腐元规则
|
||||||
|
|
||||||
|
做任何改动时,判断它属于哪类变更:
|
||||||
|
|
||||||
|
### T1 外部契约变更(最高优先级)
|
||||||
|
其他代码、服务、或 agent 依赖的定义——接口、共享类型、数据结构、配置键名、环境变量名。
|
||||||
|
→ 查 `docs/ai-context/project-context.yaml`(若存在)找到记录该契约的文档,检查并同步更新。
|
||||||
|
|
||||||
|
### T2 架构边界变更
|
||||||
|
模块划分、目录结构、依赖方向、包边界调整。
|
||||||
|
→ 检查架构文档是否需要同步。
|
||||||
|
|
||||||
|
### T3 工具链变更
|
||||||
|
引入或替换库、框架、构建工具。
|
||||||
|
→ 更新本文件(AGENTS.md)技术栈声明;通知炼境重新扫描并更新 `project-context.yaml`。
|
||||||
|
|
||||||
|
### T4 常驻上下文过期
|
||||||
|
本文件(AGENTS.md)或 `docs/ai-context/project-brief.md` 描述的现实已经改变。
|
||||||
|
→ 立即更新。这两份文档常驻 agent 上下文,过期危害最大。
|
||||||
|
|
||||||
|
### 不触发检查
|
||||||
|
内部重构、性能优化、bug fix(外部行为不变)、测试文件、临时脚本。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文档防腐铁律
|
||||||
|
|
||||||
|
- 文档只写稳定的:为什么、铁律、意图、架构决策。
|
||||||
|
- 易变的(端口、路由、表名、命令清单)交给工具自省,不写死进常驻文档。写死 = 定时炸弹。
|
||||||
|
- 单一事实来源:两处说同一事时,删掉派生的那处。
|
||||||
|
- 两份文档打架时,信代码,不信文档。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 定期防腐自查
|
||||||
|
|
||||||
|
每隔一阵或大改后,对照以下清单:
|
||||||
|
|
||||||
|
- [ ] AGENTS.md 里的技术栈声明还和代码一致吗?
|
||||||
|
- [ ] `docs/ai-context/project-brief.md` 最近 30 天有没有随架构改动更新?
|
||||||
|
- [ ] 有没有两份文档说同一件事且打架?
|
||||||
|
- [ ] `docs/ai-context/project-context.yaml` 的契约路径还准确吗?
|
||||||
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
|
||||||
|
}
|
||||||
@ -2010,6 +2010,10 @@ pub fn generate_muhe_prompt(stats: FlywheelStats) -> String {
|
|||||||
|
|
||||||
/// 从项目的 project_notes 解析返工次数和 M 级任务平均实际轮数。
|
/// 从项目的 project_notes 解析返工次数和 M 级任务平均实际轮数。
|
||||||
/// 返回 (rework_count, avg_rounds_m),avg_rounds_m 为 None 表示无 M 级复盘数据。
|
/// 返回 (rework_count, avg_rounds_m),avg_rounds_m 为 None 表示无 M 级复盘数据。
|
||||||
|
///
|
||||||
|
/// 旧路径:飞轮 L0 起 rework_count 改由 commit_metrics(git 自吸)提供,
|
||||||
|
/// 此函数保留作参考与未来回退,暂不调用。
|
||||||
|
#[allow(dead_code)]
|
||||||
fn parse_notes_metrics(bp_dir: &Path) -> (u32, Option<f64>) {
|
fn parse_notes_metrics(bp_dir: &Path) -> (u32, Option<f64>) {
|
||||||
let project_path = match bp_dir.parent().and_then(|p| p.to_str()) {
|
let project_path = match bp_dir.parent().and_then(|p| p.to_str()) {
|
||||||
Some(p) => p.to_string(),
|
Some(p) => p.to_string(),
|
||||||
@ -2121,6 +2125,20 @@ fn parse_notes_metrics(bp_dir: &Path) -> (u32, Option<f64>) {
|
|||||||
(rework_count, avg)
|
(rework_count, avg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 从项目路径反查 project_id,再聚合 commit_metrics 的有效返工次数。
|
||||||
|
/// 项目未施加 buff(查不到 project_id)时返回 None。
|
||||||
|
fn commit_rework_for_path(project_path: &str) -> Option<u32> {
|
||||||
|
let conn = crate::db::pool().get().ok()?;
|
||||||
|
let pid: String = conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT project_id FROM blueprint_buffs WHERE project_path = ?1",
|
||||||
|
rusqlite::params![project_path.trim_end_matches(['/', '\\'])],
|
||||||
|
|r| r.get(0),
|
||||||
|
)
|
||||||
|
.ok()?;
|
||||||
|
crate::commands::commit_metrics::rework_count(&conn, &pid).ok()
|
||||||
|
}
|
||||||
|
|
||||||
/// 向项目 .blueprint/usage.json 追加一条每日飞轮快照。
|
/// 向项目 .blueprint/usage.json 追加一条每日飞轮快照。
|
||||||
/// 节流:同一 UTC 日期只追加一次;写入失败静默忽略(不影响主流程)。
|
/// 节流:同一 UTC 日期只追加一次;写入失败静默忽略(不影响主流程)。
|
||||||
fn append_usage_snapshot(bp_dir: &Path, manifest: &BlueprintManifest, stats: &BlueprintStats) {
|
fn append_usage_snapshot(bp_dir: &Path, manifest: &BlueprintManifest, stats: &BlueprintStats) {
|
||||||
@ -2201,8 +2219,14 @@ fn append_usage_snapshot(bp_dir: &Path, manifest: &BlueprintManifest, stats: &Bl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 project_notes 解析返工次数和 M 级任务平均轮数
|
// 飞轮 L0:rework_count 从 commit_metrics(git 自吸)取,取代枯竭的复盘笔记解析;
|
||||||
let (rework_count, avg_rounds_m) = parse_notes_metrics(bp_dir);
|
// avg_rounds_M 需 PR 边界,基线档暂不出(见 gitea-integration 档2)
|
||||||
|
let rework_count = bp_dir
|
||||||
|
.parent()
|
||||||
|
.and_then(|p| p.to_str())
|
||||||
|
.and_then(commit_rework_for_path)
|
||||||
|
.unwrap_or(0);
|
||||||
|
let avg_rounds_m: Option<f64> = None;
|
||||||
|
|
||||||
// 停滞模块:in_progress 且任务全为 in_progress/blocked(没有 todo 可推进)
|
// 停滞模块:in_progress 且任务全为 in_progress/blocked(没有 todo 可推进)
|
||||||
let stalled_modules: Vec<String> = manifest.modules.iter()
|
let stalled_modules: Vec<String> = manifest.modules.iter()
|
||||||
|
|||||||
@ -131,47 +131,72 @@ fn check_project(app: &AppHandle, project_path: &str) -> Result<(), String> {
|
|||||||
Err(_) => return Ok(()), // 不是 git 仓库或无提交,跳过
|
Err(_) => return Ok(()), // 不是 git 仓库或无提交,跳过
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查询上次已处理的 commit
|
// 查询 project_id 与上次已处理的 commit
|
||||||
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
||||||
let last_commit: Option<String> = conn
|
let (project_id, last_commit): (String, Option<String>) = conn
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT last_commit FROM blueprint_buffs WHERE project_path = ?1",
|
"SELECT project_id, last_commit FROM blueprint_buffs WHERE project_path = ?1",
|
||||||
rusqlite::params![project_path],
|
rusqlite::params![project_path],
|
||||||
|row| row.get(0),
|
|row| Ok((row.get(0)?, row.get(1)?)),
|
||||||
)
|
)
|
||||||
.unwrap_or(None);
|
.unwrap_or_else(|_| (String::new(), None));
|
||||||
|
|
||||||
// 没有变化,跳过
|
// 没有变化,跳过
|
||||||
if last_commit.as_deref() == Some(current_commit.as_str()) {
|
if last_commit.as_deref() == Some(current_commit.as_str()) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取变更文件列表
|
// 飞轮 L0:解析新增 commit 的 conventional 指标写 commit_metrics(与下方文件匹配互不干扰)
|
||||||
let changed = get_changed_files(root, last_commit.as_deref())?;
|
if !project_id.is_empty() {
|
||||||
|
let first_time = last_commit.is_none();
|
||||||
let mut synced = false;
|
let commits = get_new_commits(root, last_commit.as_deref());
|
||||||
if !changed.is_empty() {
|
if first_time && commits.len() >= FIRST_TIME_LIMIT {
|
||||||
let updated = update_task_statuses(root, &changed)?;
|
crate::db::log_event(
|
||||||
if updated > 0 {
|
"buff",
|
||||||
// 触发飞轮快照(调用 get_blueprint 会触发内部 append_usage_snapshot)
|
"info",
|
||||||
let _ = crate::commands::blueprint::get_blueprint(project_path.to_string());
|
&format!("首次接入仅解析最近 {FIRST_TIME_LIMIT} 条 commit,更早历史跳过"),
|
||||||
|
Some(project_path),
|
||||||
// 通知前端刷新
|
|
||||||
use tauri::Emitter;
|
|
||||||
let _ = app.emit(
|
|
||||||
"blueprint://changed",
|
|
||||||
serde_json::json!({ "project_path": project_path }),
|
|
||||||
);
|
);
|
||||||
synced = true;
|
|
||||||
}
|
}
|
||||||
|
// 批量写入用事务包裹(首次可达数十条,避免逐条 fsync)
|
||||||
|
let tx = conn.unchecked_transaction();
|
||||||
|
for (sha, subject, date) in &commits {
|
||||||
|
let _ = crate::commands::commit_metrics::record_commit(
|
||||||
|
&conn,
|
||||||
|
&project_id,
|
||||||
|
sha,
|
||||||
|
subject,
|
||||||
|
Some(date.as_str()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Ok(tx) = tx {
|
||||||
|
let _ = tx.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件路径匹配 → 标记命中的任务卡 🔵
|
||||||
|
let changed = get_changed_files(root, last_commit.as_deref())?;
|
||||||
|
let mut tasks_updated = false;
|
||||||
|
if !changed.is_empty() && update_task_statuses(root, &changed)? > 0 {
|
||||||
|
tasks_updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有新 commit 即刷新飞轮快照(rework_count 可能已变);任务卡有变动则通知前端重绘
|
||||||
|
let _ = crate::commands::blueprint::get_blueprint(project_path.to_string());
|
||||||
|
if tasks_updated {
|
||||||
|
use tauri::Emitter;
|
||||||
|
let _ = app.emit(
|
||||||
|
"blueprint://changed",
|
||||||
|
serde_json::json!({ "project_path": project_path }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 DB:记录已处理的 commit
|
// 更新 DB:记录已处理的 commit
|
||||||
let _ = conn.execute(
|
let _ = conn.execute(
|
||||||
"UPDATE blueprint_buffs
|
"UPDATE blueprint_buffs
|
||||||
SET last_commit = ?1, last_sync_at = CASE WHEN ?3 THEN datetime('now') ELSE last_sync_at END
|
SET last_commit = ?1, last_sync_at = datetime('now')
|
||||||
WHERE project_path = ?2",
|
WHERE project_path = ?2",
|
||||||
rusqlite::params![current_commit, project_path, synced],
|
rusqlite::params![current_commit, project_path],
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -262,6 +287,64 @@ fn get_changed_files(root: &Path, last_commit: Option<&str>) -> Result<Vec<Strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 辅助:新增 commit 解析(飞轮 L0)─────────────────────────────────────────
|
||||||
|
|
||||||
|
/// 首次接入时只解析最近这么多条 commit,避免一次性解析整个历史。
|
||||||
|
const FIRST_TIME_LIMIT: usize = 50;
|
||||||
|
|
||||||
|
/// 获取 last_commit..HEAD 之间的新 commit(sha, subject, committed_at ISO)。
|
||||||
|
/// 首次接入(last_commit 为 None)只取最近 FIRST_TIME_LIMIT 条。
|
||||||
|
fn get_new_commits(root: &Path, last_commit: Option<&str>) -> Vec<(String, String, String)> {
|
||||||
|
// sha \x1f subject \x1f committer-date(ISO),每条 commit 一行
|
||||||
|
const FMT: &str = "--format=%H%x1f%s%x1f%cI";
|
||||||
|
let path_str = root.to_string_lossy();
|
||||||
|
|
||||||
|
let out = if is_wsl_path(&path_str) {
|
||||||
|
let linux = match unc_to_linux(&path_str) {
|
||||||
|
Some(l) => l,
|
||||||
|
None => return vec![],
|
||||||
|
};
|
||||||
|
let range = match last_commit {
|
||||||
|
Some(prev) => format!("{prev}..HEAD"),
|
||||||
|
None => format!("-n {FIRST_TIME_LIMIT}"),
|
||||||
|
};
|
||||||
|
let cmd = format!("git -C {linux} log {range} {FMT}");
|
||||||
|
crate::silent_cmd("wsl").args(["bash", "-c", &cmd]).output()
|
||||||
|
} else {
|
||||||
|
let mut cmd = crate::silent_cmd("git");
|
||||||
|
cmd.arg("log");
|
||||||
|
match last_commit {
|
||||||
|
Some(prev) => {
|
||||||
|
cmd.arg(format!("{prev}..HEAD"));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
cmd.arg("-n").arg(FIRST_TIME_LIMIT.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.arg(FMT);
|
||||||
|
cmd.current_dir(root).output()
|
||||||
|
};
|
||||||
|
|
||||||
|
let out = match out {
|
||||||
|
Ok(o) if o.status.success() => o,
|
||||||
|
_ => return vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
String::from_utf8_lossy(&out.stdout)
|
||||||
|
.lines()
|
||||||
|
.filter_map(|line| {
|
||||||
|
let mut parts = line.split('\u{1f}');
|
||||||
|
let sha = parts.next()?.trim().to_string();
|
||||||
|
if sha.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let subject = parts.next().unwrap_or("").to_string();
|
||||||
|
let date = parts.next().unwrap_or("").to_string();
|
||||||
|
Some((sha, subject, date))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
// ── 辅助:任务状态更新 ────────────────────────────────────────────────────────
|
// ── 辅助:任务状态更新 ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn update_task_statuses(root: &Path, changed_files: &[String]) -> Result<usize, String> {
|
fn update_task_statuses(root: &Path, changed_files: &[String]) -> Result<usize, String> {
|
||||||
|
|||||||
252
src-tauri/src/commands/commit_metrics.rs
Normal file
252
src-tauri/src/commands/commit_metrics.rs
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
//! Conventional Commit 解析与执行质量指标(飞轮 L0 数据地基)。
|
||||||
|
//!
|
||||||
|
//! 从 git commit message 解析出 type/scope/返工/CI 自修信号,写入 commit_metrics 表,
|
||||||
|
//! 取代枯竭的「复盘笔记解析」路径。纯解析逻辑(`parse_conventional`)与 DB 无关,可独立单测。
|
||||||
|
|
||||||
|
use rusqlite::Connection;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// 一条 commit 的解析结果。
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ParsedCommit {
|
||||||
|
/// conventional type(feat/fix/chore...);None 表示非 conventional commit。
|
||||||
|
pub commit_type: Option<String>,
|
||||||
|
/// scope(如 estate1/cad);None 表示无 scope。
|
||||||
|
pub scope: Option<String>,
|
||||||
|
/// 是否返工:type == fix。
|
||||||
|
pub is_rework: bool,
|
||||||
|
/// 是否 CI 自动修复(含 [DeepSeek-V3]/[R1] 等标记),聚合返工率时剔除。
|
||||||
|
pub is_ci_auto: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CI 自动修复 commit 的标记(来自 enterprise-system CI:`fix(ci): auto-fix ... [DeepSeek-V3]`)。
|
||||||
|
const CI_AUTO_MARKERS: [&str; 3] = ["[DeepSeek", "[R1]", "[V3]"];
|
||||||
|
|
||||||
|
/// 解析一条 commit message 为 Conventional Commit 结构。
|
||||||
|
///
|
||||||
|
/// 纯函数,不触碰 DB。无法识别为 conventional 的(merge commit、随手写的)
|
||||||
|
/// `commit_type` 记为 None,聚合时单列、不污染 fix 率。
|
||||||
|
pub fn parse_conventional(message: &str) -> ParsedCommit {
|
||||||
|
let is_ci_auto = CI_AUTO_MARKERS.iter().any(|m| message.contains(m));
|
||||||
|
|
||||||
|
// 只看首行(subject)
|
||||||
|
let subject = message.lines().next().unwrap_or("").trim();
|
||||||
|
|
||||||
|
// 必须有 "<prefix>: <desc>" 结构
|
||||||
|
let prefix = match subject.split_once(':') {
|
||||||
|
Some((p, _desc)) => p.trim(),
|
||||||
|
None => return non_conventional(is_ci_auto),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 去掉 breaking change 的末尾 '!'
|
||||||
|
let prefix = prefix.trim_end_matches('!');
|
||||||
|
|
||||||
|
// 拆出 type 与可选 scope:type(scope)
|
||||||
|
let (typ, scope) = match prefix.split_once('(') {
|
||||||
|
Some((t, rest)) => {
|
||||||
|
let scope = rest
|
||||||
|
.strip_suffix(')')
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty());
|
||||||
|
(t.trim(), scope)
|
||||||
|
}
|
||||||
|
None => (prefix, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
// type 必须是纯小写字母(feat/fix/...),否则视为非 conventional(如 "Merge pull request")
|
||||||
|
if typ.is_empty() || !typ.chars().all(|c| c.is_ascii_lowercase()) {
|
||||||
|
return non_conventional(is_ci_auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsedCommit {
|
||||||
|
is_rework: typ == "fix",
|
||||||
|
commit_type: Some(typ.to_string()),
|
||||||
|
scope,
|
||||||
|
is_ci_auto,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn non_conventional(is_ci_auto: bool) -> ParsedCommit {
|
||||||
|
ParsedCommit {
|
||||||
|
commit_type: None,
|
||||||
|
scope: None,
|
||||||
|
is_rework: false,
|
||||||
|
is_ci_auto,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解析并写入一条 commit 记录(sha 为主键,重复写入忽略)。
|
||||||
|
pub fn record_commit(
|
||||||
|
conn: &Connection,
|
||||||
|
project_id: &str,
|
||||||
|
sha: &str,
|
||||||
|
message: &str,
|
||||||
|
committed_at: Option<&str>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let p = parse_conventional(message);
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR IGNORE INTO commit_metrics
|
||||||
|
(sha, project_id, commit_type, scope, is_rework, is_ci_auto, committed_at)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
||||||
|
rusqlite::params![
|
||||||
|
sha,
|
||||||
|
project_id,
|
||||||
|
p.commit_type,
|
||||||
|
p.scope,
|
||||||
|
p.is_rework as i32,
|
||||||
|
p.is_ci_auto as i32,
|
||||||
|
committed_at,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 项目的有效返工次数:type=fix 且非 CI 自动修复。
|
||||||
|
pub fn rework_count(conn: &Connection, project_id: &str) -> Result<u32, String> {
|
||||||
|
conn.query_row(
|
||||||
|
"SELECT COUNT(*) FROM commit_metrics
|
||||||
|
WHERE project_id = ?1 AND is_rework = 1 AND is_ci_auto = 0",
|
||||||
|
rusqlite::params![project_id],
|
||||||
|
|r| r.get::<_, i64>(0),
|
||||||
|
)
|
||||||
|
.map(|n| n as u32)
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// commit 规范化统计:(总数, conventional 数)。供数据可信度提示(卡 D)使用。
|
||||||
|
pub fn conventional_stats(conn: &Connection, project_id: &str) -> Result<(u32, u32), String> {
|
||||||
|
let total: i64 = conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT COUNT(*) FROM commit_metrics WHERE project_id = ?1",
|
||||||
|
rusqlite::params![project_id],
|
||||||
|
|r| r.get(0),
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let conv: i64 = conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT COUNT(*) FROM commit_metrics WHERE project_id = ?1 AND commit_type IS NOT NULL",
|
||||||
|
rusqlite::params![project_id],
|
||||||
|
|r| r.get(0),
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok((total as u32, conv as u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 项目 commit 执行质量统计(供前端数据可信度展示)。
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct CommitStats {
|
||||||
|
pub total: u32,
|
||||||
|
pub conventional: u32,
|
||||||
|
pub rework: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询某项目的 commit 执行质量统计(总数 / conventional 数 / 有效返工数)。
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_commit_stats(project_id: String) -> Result<CommitStats, String> {
|
||||||
|
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
||||||
|
let (total, conventional) = conventional_stats(&conn, &project_id)?;
|
||||||
|
let rework = rework_count(&conn, &project_id)?;
|
||||||
|
Ok(CommitStats {
|
||||||
|
total,
|
||||||
|
conventional,
|
||||||
|
rework,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_feat_with_scope() {
|
||||||
|
let p = parse_conventional("feat(hub/rpc): add shared type system");
|
||||||
|
assert_eq!(p.commit_type.as_deref(), Some("feat"));
|
||||||
|
assert_eq!(p.scope.as_deref(), Some("hub/rpc"));
|
||||||
|
assert!(!p.is_rework);
|
||||||
|
assert!(!p.is_ci_auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_fix_as_rework() {
|
||||||
|
let p = parse_conventional("fix(estate1/view): correct sort order");
|
||||||
|
assert_eq!(p.commit_type.as_deref(), Some("fix"));
|
||||||
|
assert_eq!(p.scope.as_deref(), Some("estate1/view"));
|
||||||
|
assert!(p.is_rework);
|
||||||
|
assert!(!p.is_ci_auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_type_without_scope() {
|
||||||
|
let p = parse_conventional("chore: update AGENTS.md");
|
||||||
|
assert_eq!(p.commit_type.as_deref(), Some("chore"));
|
||||||
|
assert_eq!(p.scope, None);
|
||||||
|
assert!(!p.is_rework);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn detects_ci_auto_fix() {
|
||||||
|
let p = parse_conventional("fix(ci): auto-fix type/lint errors [DeepSeek-V3]");
|
||||||
|
assert_eq!(p.commit_type.as_deref(), Some("fix"));
|
||||||
|
assert!(p.is_rework);
|
||||||
|
assert!(p.is_ci_auto, "应识别 [DeepSeek-V3] 为 CI 自修");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn breaking_change_marker_stripped() {
|
||||||
|
let p = parse_conventional("feat(api)!: drop legacy endpoint");
|
||||||
|
assert_eq!(p.commit_type.as_deref(), Some("feat"));
|
||||||
|
assert_eq!(p.scope.as_deref(), Some("api"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn merge_commit_is_non_conventional() {
|
||||||
|
let p = parse_conventional("Merge pull request 'xxx' (#1) from dev into main");
|
||||||
|
assert_eq!(p.commit_type, None);
|
||||||
|
assert!(!p.is_rework);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn freeform_message_is_non_conventional() {
|
||||||
|
let p = parse_conventional("update stuff");
|
||||||
|
assert_eq!(p.commit_type, None);
|
||||||
|
assert_eq!(p.scope, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uses_first_line_only() {
|
||||||
|
let p = parse_conventional("feat(x): title\n\nbody line with: colon");
|
||||||
|
assert_eq!(p.commit_type.as_deref(), Some("feat"));
|
||||||
|
assert_eq!(p.scope.as_deref(), Some("x"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rework_count_excludes_ci_auto_and_non_fix() {
|
||||||
|
let conn = crate::db::conn_with_schema();
|
||||||
|
record_commit(&conn, "proj", "sha1", "fix(a): real bug", None).unwrap();
|
||||||
|
record_commit(&conn, "proj", "sha2", "fix(ci): auto-fix [DeepSeek-V3]", None).unwrap();
|
||||||
|
record_commit(&conn, "proj", "sha3", "feat(a): feature", None).unwrap();
|
||||||
|
record_commit(&conn, "proj", "sha4", "update stuff", None).unwrap();
|
||||||
|
// 仅 sha1 算有效返工(sha2 是 CI 自修、sha3 是 feat、sha4 非规范)
|
||||||
|
assert_eq!(rework_count(&conn, "proj").unwrap(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn record_commit_is_idempotent() {
|
||||||
|
let conn = crate::db::conn_with_schema();
|
||||||
|
record_commit(&conn, "proj", "dup", "fix: x", None).unwrap();
|
||||||
|
record_commit(&conn, "proj", "dup", "fix: x", None).unwrap();
|
||||||
|
let (total, _) = conventional_stats(&conn, "proj").unwrap();
|
||||||
|
assert_eq!(total, 1, "同 sha 重复写入应被忽略");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn conventional_stats_counts_ratio() {
|
||||||
|
let conn = crate::db::conn_with_schema();
|
||||||
|
record_commit(&conn, "proj", "s1", "feat: a", None).unwrap();
|
||||||
|
record_commit(&conn, "proj", "s2", "random text", None).unwrap();
|
||||||
|
let (total, conv) = conventional_stats(&conn, "proj").unwrap();
|
||||||
|
assert_eq!(total, 2);
|
||||||
|
assert_eq!(conv, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,8 @@ pub mod tags;
|
|||||||
pub mod cicd;
|
pub mod cicd;
|
||||||
pub mod blueprint;
|
pub mod blueprint;
|
||||||
pub mod buff;
|
pub mod buff;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -520,6 +520,21 @@ fn migrate(conn: &rusqlite::Connection) {
|
|||||||
);
|
);
|
||||||
").expect("create blueprint_buffs table");
|
").expect("create blueprint_buffs table");
|
||||||
|
|
||||||
|
// 飞轮 L0:从 git 解析的 conventional commit 执行质量指标
|
||||||
|
conn.execute_batch("
|
||||||
|
CREATE TABLE IF NOT EXISTS commit_metrics (
|
||||||
|
sha TEXT PRIMARY KEY,
|
||||||
|
project_id TEXT NOT NULL,
|
||||||
|
commit_type TEXT,
|
||||||
|
scope TEXT,
|
||||||
|
is_rework INTEGER NOT NULL DEFAULT 0,
|
||||||
|
is_ci_auto INTEGER NOT NULL DEFAULT 0,
|
||||||
|
committed_at TEXT,
|
||||||
|
parsed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_commit_metrics_project ON commit_metrics(project_id);
|
||||||
|
").expect("create commit_metrics table");
|
||||||
|
|
||||||
// 仪表盘全局快启应用(无项目 FK 约束)
|
// 仪表盘全局快启应用(无项目 FK 约束)
|
||||||
conn.execute_batch("
|
conn.execute_batch("
|
||||||
CREATE TABLE IF NOT EXISTS global_tools (
|
CREATE TABLE IF NOT EXISTS global_tools (
|
||||||
|
|||||||
@ -265,6 +265,9 @@ 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,
|
||||||
|
commands::commit_metrics::get_commit_stats,
|
||||||
// mentor
|
// mentor
|
||||||
get_mentor_context,
|
get_mentor_context,
|
||||||
get_project_notes,
|
get_project_notes,
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
"identifier": "com.yangzhixiang.dev-manager-tauri",
|
"identifier": "com.yangzhixiang.dev-manager-tauri",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
"devUrl": "http://localhost:1420",
|
"devUrl": "http://localhost:1430",
|
||||||
"beforeBuildCommand": "pnpm build",
|
"beforeBuildCommand": "pnpm build",
|
||||||
"frontendDist": "../dist"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,7 +22,8 @@ import {
|
|||||||
getFlywheelStats,
|
getFlywheelStats,
|
||||||
generateMuhePrompt,
|
generateMuhePrompt,
|
||||||
archiveProjectDocs,
|
archiveProjectDocs,
|
||||||
applyBlueprintBuff,
|
applyOnboardingPack,
|
||||||
|
getCommitStats,
|
||||||
removeBlueprintBuff,
|
removeBlueprintBuff,
|
||||||
getBuffStatus,
|
getBuffStatus,
|
||||||
getUserConventionsStatus,
|
getUserConventionsStatus,
|
||||||
@ -38,6 +39,8 @@ import {
|
|||||||
type FlywheelStats,
|
type FlywheelStats,
|
||||||
type StalledProject,
|
type StalledProject,
|
||||||
type BuffStatus,
|
type BuffStatus,
|
||||||
|
type TemplateResult,
|
||||||
|
type CommitStats,
|
||||||
type UserConventionsStatus,
|
type UserConventionsStatus,
|
||||||
} from "../../lib/commands";
|
} from "../../lib/commands";
|
||||||
|
|
||||||
@ -1060,6 +1063,13 @@ function GovernancePanel({
|
|||||||
staleTime: 5000,
|
staleTime: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: commitStats } = useQuery<CommitStats | null>({
|
||||||
|
queryKey: ["commit-stats-governance", projectId],
|
||||||
|
queryFn: () => (projectId ? getCommitStats(projectId) : Promise.resolve(null)),
|
||||||
|
enabled: !!projectId,
|
||||||
|
staleTime: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
const [syncing, setSyncing] = useState(false);
|
const [syncing, setSyncing] = useState(false);
|
||||||
const [syncResult, setSyncResult] = useState<SyncResult | null>(null);
|
const [syncResult, setSyncResult] = useState<SyncResult | null>(null);
|
||||||
const [syncError, setSyncError] = useState<string | null>(null);
|
const [syncError, setSyncError] = useState<string | null>(null);
|
||||||
@ -1068,6 +1078,7 @@ function GovernancePanel({
|
|||||||
const [buffError, setBuffError] = useState<string | null>(null);
|
const [buffError, setBuffError] = useState<string | null>(null);
|
||||||
const [buffInitPrompt, setBuffInitPrompt] = useState<string | null>(null);
|
const [buffInitPrompt, setBuffInitPrompt] = useState<string | null>(null);
|
||||||
const [buffInitCopied, setBuffInitCopied] = useState(false);
|
const [buffInitCopied, setBuffInitCopied] = useState(false);
|
||||||
|
const [packTemplates, setPackTemplates] = useState<TemplateResult[] | null>(null);
|
||||||
|
|
||||||
const [promptMode, setPromptMode] = useState<"init" | "sync" | null>(null);
|
const [promptMode, setPromptMode] = useState<"init" | "sync" | null>(null);
|
||||||
const [prompt, setPrompt] = useState("");
|
const [prompt, setPrompt] = useState("");
|
||||||
@ -1091,8 +1102,10 @@ function GovernancePanel({
|
|||||||
setBuffLoading(true);
|
setBuffLoading(true);
|
||||||
setBuffError(null);
|
setBuffError(null);
|
||||||
setBuffInitPrompt(null);
|
setBuffInitPrompt(null);
|
||||||
|
setPackTemplates(null);
|
||||||
try {
|
try {
|
||||||
await applyBlueprintBuff(projectPath, projectId);
|
const report = await applyOnboardingPack(projectPath, projectId);
|
||||||
|
setPackTemplates(report.templates);
|
||||||
refetchBuff();
|
refetchBuff();
|
||||||
// 只有尚无蓝图时才展示 init prompt;已有蓝图则 Buff 直接激活无需初始化
|
// 只有尚无蓝图时才展示 init prompt;已有蓝图则 Buff 直接激活无需初始化
|
||||||
if (bpStatus?.status === "none") {
|
if (bpStatus?.status === "none") {
|
||||||
@ -1113,6 +1126,7 @@ function GovernancePanel({
|
|||||||
await removeBlueprintBuff(projectPath);
|
await removeBlueprintBuff(projectPath);
|
||||||
refetchBuff();
|
refetchBuff();
|
||||||
setBuffInitPrompt(null);
|
setBuffInitPrompt(null);
|
||||||
|
setPackTemplates(null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setBuffError(String(e));
|
setBuffError(String(e));
|
||||||
} finally {
|
} finally {
|
||||||
@ -1435,13 +1449,13 @@ function GovernancePanel({
|
|||||||
{/* ── 蓝图 Buff ─────────────────────────────────────── */}
|
{/* ── 蓝图 Buff ─────────────────────────────────────── */}
|
||||||
<div className="bg-white rounded-lg border border-gray-100 p-3 space-y-2">
|
<div className="bg-white rounded-lg border border-gray-100 p-3 space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-[10px] font-semibold text-gray-400 uppercase tracking-wider">蓝图 Buff</p>
|
<p className="text-[10px] font-semibold text-gray-400 uppercase tracking-wider">接入包</p>
|
||||||
{buffStatus?.status === "active" && (
|
{buffStatus?.status === "active" && (
|
||||||
<span className="text-[10px] text-purple-600 bg-purple-50 border border-purple-200 px-1.5 py-0.5 rounded-full">⚡ 已激活</span>
|
<span className="text-[10px] text-purple-600 bg-purple-50 border border-purple-200 px-1.5 py-0.5 rounded-full">⚡ 已激活</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[11px] text-gray-500 leading-relaxed">
|
<p className="text-[11px] text-gray-500 leading-relaxed">
|
||||||
施加 Buff 后,炼境将自动监听 git 提交,匹配任务文件并更新进度。
|
应用接入包:种入 AGENTS.md / lefthook / git-workflow 标准模板(不覆盖已有),并挂 buff 自动监听 git 提交、采集飞轮数据。
|
||||||
</p>
|
</p>
|
||||||
{buffStatus?.last_sync_at && (
|
{buffStatus?.last_sync_at && (
|
||||||
<p className="text-[10px] text-gray-400">上次同步:{buffStatus.last_sync_at}</p>
|
<p className="text-[10px] text-gray-400">上次同步:{buffStatus.last_sync_at}</p>
|
||||||
@ -1449,6 +1463,30 @@ function GovernancePanel({
|
|||||||
{buffError && (
|
{buffError && (
|
||||||
<p className="text-[11px] text-red-600 font-mono break-all">{buffError}</p>
|
<p className="text-[11px] text-red-600 font-mono break-all">{buffError}</p>
|
||||||
)}
|
)}
|
||||||
|
{packTemplates && (
|
||||||
|
<div className="space-y-0.5 pt-0.5">
|
||||||
|
<p className="text-[10px] text-gray-500 font-semibold">模板分发</p>
|
||||||
|
{packTemplates.map((t) => (
|
||||||
|
<p key={t.name} className="text-[10px] font-mono flex items-center gap-1">
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
t.action === "written"
|
||||||
|
? "text-green-600"
|
||||||
|
: t.action === "skipped"
|
||||||
|
? "text-gray-400"
|
||||||
|
: "text-red-600"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t.action === "written" ? "✓" : t.action === "skipped" ? "○" : "✗"}
|
||||||
|
</span>
|
||||||
|
<span className="text-gray-600 truncate flex-1">{t.name}</span>
|
||||||
|
<span className="text-gray-400">
|
||||||
|
{t.action === "written" ? "已写入" : t.action === "skipped" ? "已存在" : t.detail}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{buffStatus?.status === "active" ? (
|
{buffStatus?.status === "active" ? (
|
||||||
<button
|
<button
|
||||||
onClick={handleRemoveBuff}
|
onClick={handleRemoveBuff}
|
||||||
@ -1492,6 +1530,19 @@ function GovernancePanel({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{buffStatus?.status === "active" && commitStats && commitStats.total > 0 && (
|
||||||
|
<div className="pt-1.5 border-t border-gray-100 space-y-0.5">
|
||||||
|
<p className="text-[10px] text-gray-500">
|
||||||
|
commit 规范化 {commitStats.conventional}/{commitStats.total}(
|
||||||
|
{Math.round((commitStats.conventional / commitStats.total) * 100)}%) · 返工 {commitStats.rework}
|
||||||
|
</p>
|
||||||
|
{commitStats.conventional / commitStats.total < 0.6 && (
|
||||||
|
<p className="text-[10px] text-amber-600 leading-relaxed">
|
||||||
|
⚠ 规范化偏低,飞轮数据可信度低——种 lefthook(接入包已含)后逐步转准
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 项目信息 */}
|
{/* 项目信息 */}
|
||||||
|
|||||||
@ -60,7 +60,7 @@ interface Props {
|
|||||||
spacesJson?: SpacesJson | null;
|
spacesJson?: SpacesJson | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProjectCard({ project: p, role, spacesJson }: Props) {
|
export function ProjectCard({ project: p, spacesJson }: Props) {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const [showToolsMgr, setShowToolsMgr] = useState(false);
|
const [showToolsMgr, setShowToolsMgr] = useState(false);
|
||||||
const [toolsMgrDefaultAdd, setToolsMgrDefaultAdd] = useState(false);
|
const [toolsMgrDefaultAdd, setToolsMgrDefaultAdd] = useState(false);
|
||||||
|
|||||||
@ -1599,6 +1599,31 @@ 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 });
|
||||||
|
|
||||||
|
export interface CommitStats {
|
||||||
|
total: number;
|
||||||
|
conventional: number;
|
||||||
|
rework: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCommitStats = (projectId: string) =>
|
||||||
|
invoke<CommitStats>("get_commit_stats", { projectId });
|
||||||
|
|
||||||
// ── Beast Global Status (read-only) ───────────────────────────────────────────
|
// ── Beast Global Status (read-only) ───────────────────────────────────────────
|
||||||
|
|
||||||
export interface BeastGlobalStatus {
|
export interface BeastGlobalStatus {
|
||||||
|
|||||||
@ -15,14 +15,14 @@ export default defineConfig(async () => ({
|
|||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
// 2. tauri expects a fixed port, fail if that port is not available
|
// 2. tauri expects a fixed port, fail if that port is not available
|
||||||
server: {
|
server: {
|
||||||
port: 1420,
|
port: 1430,
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
host: host || "127.0.0.1",
|
host: host || "127.0.0.1",
|
||||||
hmr: host
|
hmr: host
|
||||||
? {
|
? {
|
||||||
protocol: "ws",
|
protocol: "ws",
|
||||||
host,
|
host,
|
||||||
port: 1421,
|
port: 1431,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user