Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions docs/plans/v0.21.0-architecture-hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# devbase v0.21.0 架构硬化计划

## 来源
error-budget-engineering 架构审计(2026-05-15)

## 目标
消除 P0 级架构误差,将系统从"功能完备但地基裂脑"推进到"可安全重构"。

## 阶段与验收标准

### Phase 1: Registry 数据模型统一(P0)
**问题**: `entities` 表(71 repo)与 CLI `query repos`(1 repo)数据不一致
**行动**:
1. 审计 `repo::list_repos` 查询路径,确认是否读取旧表
2. 统一 `list_repos`/`get_repo` 到 `entities` 表
3. 验证 `scan --register` → `query repos` → `sync --dry-run` 闭环
**验收**: `query repos` 返回数与 `sync --dry-run` 的 skipped_unmanaged 数一致

### Phase 2: 测试覆盖率回补 Wave 1(P0)
**问题**: 4.1% 覆盖率,重构无防护
**行动**:
1. 为核心 Registry 模块(repo/health/metrics)补充单元测试
2. 为 `release.yml` 新增 workflow 验证(act 或 dry-run 模式)
**验收**: 覆盖率提升至 15%+

### Phase 3: Sync 层透明化(P1)
**问题**: `managed` 标记机制不透明,71 个仓库全部 unmanaged
**行动**:
1. 明确 `managed` 存储位置(`repo_tags` vs `metadata`)
2. 增加 `devbase repo status` 命令,批量显示 ahead/behind/dirty
3. 优化 `sync` 输出,区分 "unmanaged" / "dirty" / "ahead" / "behind"
**验收**: 用户可一目了然判断哪些仓库可安全同步

### Phase 4: GreptimeDB 时序 Schema 设计(P2)
**问题**: health/metrics/stars 等时序数据存储在 SQLite,无趋势分析能力
**行动**:
1. 设计 GreptimeDB 兼容 Schema(health_metrics, code_metrics, stars_history)
2. 在 `devbase` 中增加可选的 `greptimedb` feature 与写入适配层
3. 保持 SQLite 作为 Registry OLTP,GreptimeDB 作为 OLAP
**验收**: 编译通过 `--features greptimedb`,health 数据可双写

### Phase 5: 性能基准与文档自动化(P2)
**问题**: 无大仓基准,README 手动维护
**行动**:
1. 为 `hybrid_search` 和 `scan` 增加 100K 文件基准测试
2. `release.yml` 自动更新 README 资产元数据
**验收**: CI 中运行基准测试并产出报告

## 当前执行
Phase 1 进行中。
28 changes: 28 additions & 0 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,34 @@ pub async fn run_json(
}));
}

// Special case: bare "repos" returns all registered repositories unfiltered.
if query_str.trim() == "repos" {
let repos = crate::registry::repo::list_repos(conn)?;
let results: Vec<serde_json::Value> = repos
.into_iter()
.map(|repo| {
let primary = repo.primary_remote();
serde_json::json!({
"id": repo.id,
"local_path": repo.local_path,
"upstream_url": primary.and_then(|r| r.upstream_url.clone()),
"tags": repo.tags.join(","),
"default_branch": primary.and_then(|r| r.default_branch.clone()),
"last_sync": primary.and_then(|r| r.last_sync.map(|dt| dt.to_rfc3339())),
"language": repo.language,
"match_reasons": ["all"]
})
})
.collect();
let count = results.len();
return Ok(serde_json::json!({
"success": true,
"count": count,
"expression": query_str,
"results": results
}));
}

let conditions = parse_query(query_str);

let repos = crate::registry::repo::list_repos(conn)?;
Expand Down
Loading