feat(flywheel): Gitea Actions 模板 + Rules-Applied trailer 自吸——规则有效性闭环接通

Gitea Actions workflow 生成(gitea-integration 收官卡):
- generate_workflow 输出 .gitea/workflows/;tauri_oss 模板去掉 GitHub Release
  步骤(发布走 OSS),构建改 pnpm tauri build + 签名环境变量,runs-on 改
  自建 windows runner 标签;CicdModal Secrets 文案改 Gitea 路径
- 单元测试断言模板无 github.com / GITHUB_TOKEN / gh-release 残留

R1 Rules-Applied trailer 被动自吸(flywheel-intelligence):
- smart-commit 技能:commit message 末尾附 Rules-Applied: R01,R06 trailer
  (仅真实触发的规则,防数据污染)
- commit_metrics 新增 rules_applied 列(增量 migration)+ parse_rules_applied
  解析(token 校验/去重/规范化)+ rule_hit_stats 跨项目"命中×返工"聚合
- 梦核 prompt 新增「规则命中×返工交叉」数据段 + 【规则置信度】分析任务
  (升/降 confidence、按 R11 细化边界)——R01-R10 置信度首次有真实数据来源

验证:cargo test 65 passed(新增 7 测试,其中一个抓到 trailer 解析的
? 提前返回 bug)、typecheck 全绿

Rules-Applied: R01, R05, R06, R07
This commit is contained in:
lanrtop 2026-07-02 15:10:26 +09:00
parent bf697b5e28
commit de961d0e95
8 changed files with 270 additions and 45 deletions

View File

@ -304,8 +304,8 @@ modules:
name: 飞轮智能成长 name: 飞轮智能成长
area: backend area: backend
status: in_progress status: in_progress
progress: 85 progress: 92
note: "L0+F1+F2 完成;新增 R1 trailer 自吸 + R2 规则边界字段2026-07-02救活 rule_effectiveness 死链);阶段三待数据积累" note: "L0+F1+F2+R1+R2 完成2026-07-02rule_effectiveness 死链已接通);阶段三待数据覆盖 ≥10 项目"
position: [1750, 650] position: [1750, 650]
- id: cloud-products - id: cloud-products
@ -376,8 +376,8 @@ modules:
name: Gitea 集成 name: Gitea 集成
area: backend area: backend
status: in_progress status: in_progress
progress: 55 progress: 70
note: "G1-G5 全部完成(控制面+观测面+UIGitea Actions 生成📋、PR/CI 事件、迁移" note: "G1-G5 + Gitea Actions 生成完成2026-07-02PR/CI 事件联动、DeepSeek review、存量迁移concept"
position: [3500, 150] position: [3500, 150]
updated: 2026-07-02 updated: 2026-07-02

View File

@ -125,14 +125,15 @@
--- ---
### 📋 R1. Rules-Applied commit trailer 被动自吸(规则有效性数据源) ### R1. Rules-Applied commit trailer 被动自吸(规则有效性数据源)
- status: todo - status: done
- complexity: M - complexity: M
- depends: flywheel-intelligenceL0 已完成即可,不依赖阶段三) - depends: flywheel-intelligenceL0 已完成即可,不依赖阶段三)
- files: .claude/skills/commit或 smart-commit 模板), src-tauri/src/commands/commit_metrics.rs, src-tauri/src/db.rs, src-tauri/src/commands/blueprint.rs - files: .claude/skills/commit或 smart-commit 模板), src-tauri/src/commands/commit_metrics.rs, src-tauri/src/db.rs, src-tauri/src/commands/blueprint.rs
- acceptance: ① /commit 技能在本次任务触发过规则时commit message 末尾自动附 `Rules-Applied: R01,R06` trailer② commit_metrics 解析层提取该 trailer 存入新列(补 migrationR05③ get_flywheel_stats 聚合"规则命中 × 该 commit 是否 fix 返工"交叉统计注入梦核 prompt④ 新增解析函数有单元测试(依赖注入 + in-memory DBcargo test 全绿 - acceptance: ① /commit 技能在本次任务触发过规则时commit message 末尾自动附 `Rules-Applied: R01,R06` trailer② commit_metrics 解析层提取该 trailer 存入新列(补 migrationR05③ get_flywheel_stats 聚合"规则命中 × 该 commit 是否 fix 返工"交叉统计注入梦核 prompt④ 新增解析函数有单元测试(依赖注入 + in-memory DBcargo test 全绿
> 背景复盘笔记进料口枯竭70 条仅 4 条结构化rule_effectiveness 闭环是死链R01-R10 的 confidence 全是人工拍的。本卡把规则有效性数据改为 git 被动自吸——与 L0 同构,是 CONVENTIONS 置信度自动进化(阶段三)的真实数据前提。 > 背景复盘笔记进料口枯竭70 条仅 4 条结构化rule_effectiveness 闭环是死链R01-R10 的 confidence 全是人工拍的。本卡把规则有效性数据改为 git 被动自吸——与 L0 同构,是 CONVENTIONS 置信度自动进化(阶段三)的真实数据前提。
> 完成记录2026-07-02smart-commit 技能加 trailer 步骤commit_metrics 新增 rules_applied 列migration+ parse_rules_applied + rule_hit_stats 聚合;梦核 prompt 新增「规则命中×返工交叉」数据段与【规则置信度】分析任务5 个单元测试(测试抓到一个 ? 提前返回 bug。数据将随日常 commit 自然积累。
### ✅ R2. CONVENTIONS 规则边界字段 + R11 失败记录规则 ### ✅ R2. CONVENTIONS 规则边界字段 + R11 失败记录规则
- status: done - status: done

View File

@ -96,8 +96,8 @@ Gitea push → POST /webhook/gitea/:project_id
> ⚠️ push 事件部分已被 G3/G4 实现取代剩余范围收窄为「PR/CI/review 事件 + 轮询降级」见「PR 事件 ↔ 蓝图任务卡联动」concept 卡)。 > ⚠️ push 事件部分已被 G3/G4 实现取代剩余范围收窄为「PR/CI/review 事件 + 轮询降级」见「PR 事件 ↔ 蓝图任务卡联动」concept 卡)。
### 🔵 Gitea Actions workflow 生成 ### Gitea Actions workflow 生成
- status: in_progress - status: done
- complexity: M - complexity: M
- depends: G1 - depends: G1
- files: src-tauri/src/commands/cicd.rs, src/components/dashboard/CicdModal.tsx - files: src-tauri/src/commands/cicd.rs, src/components/dashboard/CicdModal.tsx
@ -160,6 +160,16 @@ Gitea push → POST /webhook/gitea/:project_id
## 复盘笔记 ## 复盘笔记
【复盘】Gitea Actions workflow 生成2026-07-02
任务类型: 后端命令 + 前端组件
复杂度: M
实际轮数: 1轮预估 1 轮)
是否返工: 否
有效策略: 语法兼容性使改造收敛为三处——输出目录 .gitea/workflows、去掉 GitHub Release 步骤(发布走 OSS 不需要 Release 页、runs-on 改自建 runner 标签;用单元测试断言"无 github.com/GITHUB_TOKEN 残留"把验收标准变成回归测试
遗留风险: ① tauri_oss 模板需要带 windows 标签的自建 act_runner尚未实际部署② actions/cachesetup-node 的 pnpm 缓存)依赖 act_runner 的 cache server旧版本 runner 可能不生效
auto_applied_rules: R01, R07
rule_effectiveness: R01=有效(命令未新增,仅改产出), R07=有效(模板残留断言测试)
【复盘】G2-G5 控制面 + 观测面全链路2026-07-02 【复盘】G2-G5 控制面 + 观测面全链路2026-07-02
任务类型: 后端命令 + MCP工具 + 前端组件 任务类型: 后端命令 + MCP工具 + 前端组件
复杂度: M×4 复杂度: M×4

View File

@ -1889,6 +1889,23 @@ pub fn generate_muhe_prompt(stats: FlywheelStats) -> String {
}; };
let notes_total = stats.project_notes.len(); let notes_total = stats.project_notes.len();
// ── 规则命中 × 返工交叉Rules-Applied trailer飞轮 R1─────────────────
let rule_hits_summary = crate::db::pool()
.get()
.ok()
.and_then(|conn| crate::commands::commit_metrics::rule_hit_stats(&conn).ok())
.filter(|v| !v.is_empty())
.map(|v| {
let header = "| 规则 | 命中 commit | 其中返工 | 覆盖项目 | 返工占比 |";
let sep = "|------|------------|----------|----------|----------|";
let rows: Vec<String> = v.iter().map(|r| {
let ratio = if r.hits > 0 { r.rework_hits as f64 / r.hits as f64 * 100.0 } else { 0.0 };
format!("| {} | {} | {} | {} | {:.0}% |", r.rule_id, r.hits, r.rework_hits, r.project_count, ratio)
}).collect();
format!("{header}\n{sep}\n{}", rows.join("\n"))
})
.unwrap_or_else(|| "暂无数据——commit message 附带 Rules-Applied trailer 后自动积累)".to_string());
format!(r#"你是炼境Dev Manager的蓝图架构师负责对飞轮运转数据进行记忆巩固分析。 format!(r#"你是炼境Dev Manager的蓝图架构师负责对飞轮运转数据进行记忆巩固分析。
## ##
@ -1929,6 +1946,13 @@ pub fn generate_muhe_prompt(stats: FlywheelStats) -> String {
{notes_summary} {notes_summary}
### × Rules-Applied trailer
> commit message =
> R11 failure_case/boundaries =
{rule_hits_summary}
--- ---
## CONVENTIONS.mdv{conventions_version} ## CONVENTIONS.mdv{conventions_version}
@ -1984,6 +2008,16 @@ pub fn generate_muhe_prompt(stats: FlywheelStats) -> String {
- + - +
- CONVENTIONS + - CONVENTIONS +
### / confidence
"规则命中 × 返工交叉"
1. confidence / evidence_count
2. confidence R11 failure_case boundaries
3.
- ID + / / +
--- ---

View File

@ -1,5 +1,5 @@
// CI/CD 配置管理与 Workflow 生成 // CI/CD 配置管理与 Workflow 生成Gitea Actions语法兼容 GitHub Actions
// 完整部署教程与踩坑指南见 docs/tauri-cicd-guide.md // 历史部署教程与踩坑指南见 docs/tauri-cicd-guide.md(原 GitHub Actions 方案)
use crate::db; use crate::db;
use crate::models::{CicdConfig, WorkflowResult}; use crate::models::{CicdConfig, WorkflowResult};
use rusqlite::params; use rusqlite::params;
@ -65,7 +65,7 @@ pub fn save_cicd_config(input: CicdConfig) -> Result<CicdConfig, String> {
get_cicd_config(input.project_id)?.ok_or_else(|| "save failed".to_string()) get_cicd_config(input.project_id)?.ok_or_else(|| "save failed".to_string())
} }
/// 根据项目的 cicd_configs 生成 workflow 文件,写入 {project_path}/.github/workflows/ /// 根据项目的 cicd_configs 生成 Gitea Actions workflow 文件,写入 {project_path}/.gitea/workflows/
/// project_path 必须是 Windows 可访问的路径WSL 项目请传 UNC 路径 \\wsl.localhost\... /// project_path 必须是 Windows 可访问的路径WSL 项目请传 UNC 路径 \\wsl.localhost\...
#[tauri::command] #[tauri::command]
pub fn generate_workflow(project_id: String, project_path: String) -> Result<WorkflowResult, String> { pub fn generate_workflow(project_id: String, project_path: String) -> Result<WorkflowResult, String> {
@ -85,7 +85,7 @@ pub fn generate_workflow(project_id: String, project_path: String) -> Result<Wor
_ => build_web_ssh_workflow(&config), _ => build_web_ssh_workflow(&config),
}; };
let workflows_dir = Path::new(&project_path).join(".github").join("workflows"); let workflows_dir = Path::new(&project_path).join(".gitea").join("workflows");
std::fs::create_dir_all(&workflows_dir).map_err(|e| e.to_string())?; std::fs::create_dir_all(&workflows_dir).map_err(|e| e.to_string())?;
let file_path = workflows_dir.join(&filename); let file_path = workflows_dir.join(&filename);
std::fs::write(&file_path, &content).map_err(|e| e.to_string())?; std::fs::write(&file_path, &content).map_err(|e| e.to_string())?;
@ -107,7 +107,8 @@ fn build_web_ssh_workflow(cfg: &CicdConfig) -> (String, String, Vec<String>) {
let start = cfg.start_command.as_deref().unwrap_or("pm2 restart app"); let start = cfg.start_command.as_deref().unwrap_or("pm2 restart app");
let content = format!( let content = format!(
r#"name: Deploy r#"# Gitea Actions语法兼容 GitHub Actionsaction 由 runner 从镜像源拉取)
name: Deploy
on: on:
push: push:
@ -163,8 +164,9 @@ fn build_tauri_oss_workflow(cfg: &CicdConfig) -> (String, String, Vec<String>) {
.unwrap_or("oss-cn-hangzhou.aliyuncs.com"); .unwrap_or("oss-cn-hangzhou.aliyuncs.com");
let content = format!( let content = format!(
r#"# Tauri v2 Windows 自动构建 + 签名 + 阿里云 OSS 发布 r#"# Tauri v2 Windows 自动构建 + 签名 + 阿里云 OSS 发布Gitea Actions
# docs/tauri-cicd-guide.md # 'windows' Windows runneract_runner
# OSS tauri-plugin-updater latest.json Release
name: Release name: Release
on: on:
@ -174,9 +176,7 @@ on:
jobs: jobs:
release: release:
runs-on: windows-latest runs-on: windows
permissions:
contents: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -212,32 +212,12 @@ jobs:
$lines = $env:TAURI_SIGNING_PRIVATE_KEY.Split("`n").Count $lines = $env:TAURI_SIGNING_PRIVATE_KEY.Split("`n").Count
Write-Host "TAURI_SIGNING_PRIVATE_KEY is set ($lines lines)" Write-Host "TAURI_SIGNING_PRIVATE_KEY is set ($lines lines)"
- name: Create Release - name: Build
uses: softprops/action-gh-release@v2 shell: pwsh
with:
tag_name: ${{{{ github.ref_name }}}}
name: ${{{{ github.ref_name }}}}
body: |
##
draft: false
prerelease: false
- name: Build & Release
uses: tauri-apps/tauri-action@v0
env: env:
GITHUB_TOKEN: ${{{{ secrets.GITHUB_TOKEN }}}}
TAURI_SIGNING_PRIVATE_KEY: ${{{{ secrets.TAURI_SIGNING_PRIVATE_KEY }}}} TAURI_SIGNING_PRIVATE_KEY: ${{{{ secrets.TAURI_SIGNING_PRIVATE_KEY }}}}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{{{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}}} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{{{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}}}
with: run: pnpm tauri build --verbose
tagName: ${{{{ github.ref_name }}}}
releaseName: ${{{{ github.ref_name }}}}
releaseBody: |
##
releaseDraft: false
prerelease: false
args: --verbose
- name: Upload to OSS & generate latest.json - name: Upload to OSS & generate latest.json
shell: pwsh shell: pwsh
@ -314,3 +294,47 @@ jobs:
]; ];
(content, "release.yml".to_string(), secrets) (content, "release.yml".to_string(), secrets)
} }
#[cfg(test)]
mod tests {
use super::*;
fn cfg(config_type: &str) -> CicdConfig {
CicdConfig {
id: "t".into(),
project_id: "p".into(),
config_type: config_type.into(),
trigger_branch: "main".into(),
build_command: Some("pnpm build".into()),
node_version: Some("20".into()),
server_host: Some("host".into()),
server_user: Some("ubuntu".into()),
deploy_path: Some("/app".into()),
start_command: Some("pm2 restart app".into()),
oss_endpoint: Some("oss-cn-hangzhou.aliyuncs.com".into()),
created_at: None,
}
}
#[test]
fn web_ssh_template_has_no_github_residue() {
let (content, filename, secrets) = build_web_ssh_workflow(&cfg("web_ssh"));
assert!(!content.contains("github.com"), "模板残留 github.com 引用");
assert!(!content.contains("GITHUB_TOKEN"), "模板残留 GITHUB_TOKEN");
assert_eq!(filename, "deploy.yml");
assert!(secrets.contains(&"SSH_KEY".to_string()));
}
#[test]
fn tauri_oss_template_has_no_github_residue() {
let (content, filename, secrets) = build_tauri_oss_workflow(&cfg("tauri_oss"));
assert!(!content.contains("github.com"), "模板残留 github.com 引用");
assert!(!content.contains("GITHUB_TOKEN"), "模板残留 GITHUB_TOKEN");
assert!(!content.contains("gh-release"), "模板残留 GitHub Release 步骤");
assert!(content.contains("pnpm tauri build"), "缺少构建步骤");
assert!(content.contains("latest.json"), "缺少 updater 产物上传");
assert_eq!(filename, "release.yml");
assert!(secrets.contains(&"TAURI_SIGNING_PRIVATE_KEY".to_string()));
assert!(!secrets.iter().any(|s| s.contains("GITHUB")), "secrets 残留 GitHub 项");
}
}

View File

@ -75,6 +75,35 @@ fn non_conventional(is_ci_auto: bool) -> ParsedCommit {
} }
} }
/// 解析 commit message 中的 `Rules-Applied: R01,R06` trailer飞轮 R1 规则有效性数据源)。
/// 只接受 R+数字 形式的 token规范化为大写去重逗号串无 trailer 或无有效 token 返回 None。
pub fn parse_rules_applied(message: &str) -> Option<String> {
for line in message.lines() {
let trimmed = line.trim();
let Some(rest) = trimmed
.strip_prefix("Rules-Applied:")
.or_else(|| trimmed.strip_prefix("rules-applied:"))
.or_else(|| trimmed.strip_prefix("Rules-applied:"))
else {
continue;
};
let rest = rest.trim();
let mut ids: Vec<String> = rest
.split([',', ' ', ''])
.map(|t| t.trim().to_uppercase())
.filter(|t| {
t.len() >= 2
&& t.starts_with('R')
&& t[1..].chars().all(|c| c.is_ascii_digit())
})
.collect();
ids.sort();
ids.dedup();
return if ids.is_empty() { None } else { Some(ids.join(",")) };
}
None
}
/// 解析并写入一条 commit 记录sha 为主键,重复写入忽略)。 /// 解析并写入一条 commit 记录sha 为主键,重复写入忽略)。
pub fn record_commit( pub fn record_commit(
conn: &Connection, conn: &Connection,
@ -84,10 +113,11 @@ pub fn record_commit(
committed_at: Option<&str>, committed_at: Option<&str>,
) -> Result<(), String> { ) -> Result<(), String> {
let p = parse_conventional(message); let p = parse_conventional(message);
let rules_applied = parse_rules_applied(message);
conn.execute( conn.execute(
"INSERT OR IGNORE INTO commit_metrics "INSERT OR IGNORE INTO commit_metrics
(sha, project_id, commit_type, scope, is_rework, is_ci_auto, committed_at) (sha, project_id, commit_type, scope, is_rework, is_ci_auto, committed_at, rules_applied)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
rusqlite::params![ rusqlite::params![
sha, sha,
project_id, project_id,
@ -96,12 +126,69 @@ pub fn record_commit(
p.is_rework as i32, p.is_rework as i32,
p.is_ci_auto as i32, p.is_ci_auto as i32,
committed_at, committed_at,
rules_applied,
], ],
) )
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
Ok(()) Ok(())
} }
/// 单条规则的命中统计(跨项目聚合)
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RuleHitStat {
pub rule_id: String,
/// 附带该规则的 commit 总数
pub hits: u32,
/// 其中 type=fix非 CI 自修)的返工 commit 数——规则触发了但仍出问题的信号
pub rework_hits: u32,
/// 覆盖的项目数
pub project_count: u32,
}
/// 聚合所有项目的 Rules-Applied 命中 × 返工交叉统计(供梦核置信度分析)。
pub fn rule_hit_stats(conn: &Connection) -> Result<Vec<RuleHitStat>, String> {
let mut stmt = conn
.prepare(
"SELECT rules_applied, is_rework, is_ci_auto, project_id
FROM commit_metrics WHERE rules_applied IS NOT NULL",
)
.map_err(|e| e.to_string())?;
let rows = stmt
.query_map([], |r| {
Ok((
r.get::<_, String>(0)?,
r.get::<_, i64>(1)? != 0,
r.get::<_, i64>(2)? != 0,
r.get::<_, String>(3)?,
))
})
.map_err(|e| e.to_string())?;
use std::collections::{BTreeMap, HashSet};
let mut hits: BTreeMap<String, (u32, u32, HashSet<String>)> = BTreeMap::new();
for row in rows {
let (rules, is_rework, is_ci_auto, project_id) = row.map_err(|e| e.to_string())?;
for rule in rules.split(',').map(str::trim).filter(|s| !s.is_empty()) {
let entry = hits.entry(rule.to_string()).or_default();
entry.0 += 1;
if is_rework && !is_ci_auto {
entry.1 += 1;
}
entry.2.insert(project_id.clone());
}
}
Ok(hits
.into_iter()
.map(|(rule_id, (h, rw, projects))| RuleHitStat {
rule_id,
hits: h,
rework_hits: rw,
project_count: projects.len() as u32,
})
.collect())
}
/// 项目的有效返工次数type=fix 且非 CI 自动修复。 /// 项目的有效返工次数type=fix 且非 CI 自动修复。
pub fn rework_count(conn: &Connection, project_id: &str) -> Result<u32, String> { pub fn rework_count(conn: &Connection, project_id: &str) -> Result<u32, String> {
conn.query_row( conn.query_row(
@ -614,4 +701,70 @@ mod tests {
let (total, _) = conventional_stats(&conn, "p3").unwrap(); let (total, _) = conventional_stats(&conn, "p3").unwrap();
assert_eq!(total, 1, "重复 sha 应被 INSERT OR IGNORE 忽略"); assert_eq!(total, 1, "重复 sha 应被 INSERT OR IGNORE 忽略");
} }
// ── R1Rules-Applied trailer ──────────────────────────────────────────
#[test]
fn parse_rules_applied_normalizes_tokens() {
let msg = "feat(core): thing
Rules-Applied: r06, R01,R01, xx, R10";
assert_eq!(parse_rules_applied(msg).as_deref(), Some("R01,R06,R10"));
} }
#[test]
fn parse_rules_applied_absent_or_empty() {
assert_eq!(parse_rules_applied("feat: no trailer"), None);
assert_eq!(parse_rules_applied("Rules-Applied: 无效, abc"), None);
}
#[test]
fn record_commit_stores_rules_applied() {
let conn = crate::db::conn_with_schema();
record_commit(&conn, "pr", "sha-r1", "feat(x): a
Rules-Applied: R01,R05", None).unwrap();
let stored: Option<String> = conn.query_row(
"SELECT rules_applied FROM commit_metrics WHERE sha = 'sha-r1'", [], |r| r.get(0),
).unwrap();
assert_eq!(stored.as_deref(), Some("R01,R05"));
}
#[test]
fn rule_hit_stats_cross_counts_rework() {
let conn = crate::db::conn_with_schema();
record_commit(&conn, "pa", "s1", "feat(x): ok
Rules-Applied: R01", None).unwrap();
record_commit(&conn, "pa", "s2", "fix(x): broke
Rules-Applied: R01,R06", None).unwrap();
record_commit(&conn, "pb", "s3", "feat(y): fine
Rules-Applied: R06", None).unwrap();
// CI 自修的 fix 不计返工
record_commit(&conn, "pb", "s4", "fix(y): auto [DeepSeek-V3]
Rules-Applied: R06", None).unwrap();
let stats = rule_hit_stats(&conn).unwrap();
let r01 = stats.iter().find(|s| s.rule_id == "R01").unwrap();
assert_eq!(r01.hits, 2);
assert_eq!(r01.rework_hits, 1);
assert_eq!(r01.project_count, 1);
let r06 = stats.iter().find(|s| s.rule_id == "R06").unwrap();
assert_eq!(r06.hits, 3);
assert_eq!(r06.rework_hits, 1, "CI 自修 fix 不应计入返工");
assert_eq!(r06.project_count, 2);
}
#[test]
fn rule_hit_stats_empty_when_no_trailers() {
let conn = crate::db::conn_with_schema();
record_commit(&conn, "pz", "s9", "feat: plain", None).unwrap();
assert!(rule_hit_stats(&conn).unwrap().is_empty());
}
}

View File

@ -535,6 +535,9 @@ fn migrate(conn: &rusqlite::Connection) {
CREATE INDEX IF NOT EXISTS idx_commit_metrics_project ON commit_metrics(project_id); CREATE INDEX IF NOT EXISTS idx_commit_metrics_project ON commit_metrics(project_id);
").expect("create commit_metrics table"); ").expect("create commit_metrics table");
// 增量迁移(飞轮 R1Rules-Applied trailer 规则命中列(已存在则忽略)
let _ = conn.execute("ALTER TABLE commit_metrics ADD COLUMN rules_applied TEXT", []);
// 仪表盘全局快启应用(无项目 FK 约束) // 仪表盘全局快启应用(无项目 FK 约束)
conn.execute_batch(" conn.execute_batch("
CREATE TABLE IF NOT EXISTS global_tools ( CREATE TABLE IF NOT EXISTS global_tools (

View File

@ -253,10 +253,10 @@ export function CicdModal({
{generatedSecrets && ( {generatedSecrets && (
<div className="bg-zinc-800 rounded-lg p-3 space-y-2"> <div className="bg-zinc-800 rounded-lg p-3 space-y-2">
<p className="text-xs font-medium text-zinc-300"> <p className="text-xs font-medium text-zinc-300">
Workflow GitHub Secrets Workflow .gitea/workflows/ Gitea Secrets
</p> </p>
<p className="text-xs text-zinc-500"> <p className="text-xs text-zinc-500">
Settings Secrets and variables Actions New repository secret Settings Actions Secrets Add Secret
</p> </p>
<div className="flex flex-wrap gap-1.5 mt-1"> <div className="flex flex-wrap gap-1.5 mt-1">
{generatedSecrets.map((s) => ( {generatedSecrets.map((s) => (