feat: 新增 pnpm Monorepo+Expo 模板及蓝图模块扩展

- 新增 pnpm Monorepo + Expo Router 项目模板(含 Metro monorepo 路径解析及 Windows 兼容修复)
- 扩展蓝图系统:新增 api-keys、server-registry、ssh-config-sync、vscode-remote 等 15+ 模块定义
- 新增 .blueprint/rules.md 项目私有规则支持,并在 CLAUDE.md 中集成
- 蓝图版本升级至 1.6.0,优化版本比较逻辑(语义化版本元组对比)
This commit is contained in:
lanrtop 2026-04-29 22:11:28 +09:00
parent 9660bcbc48
commit 4dd19574f0
22 changed files with 3062 additions and 40 deletions

View File

@ -1,6 +1,6 @@
--- ---
version: 1.4.5 version: 1.6.0
updated: 2026-04-17 updated: 2026-04-19
source: dev-manager-tauri source: dev-manager-tauri
--- ---
@ -15,7 +15,9 @@ source: dev-manager-tauri
``` ```
.blueprint/ .blueprint/
├── manifest.yaml ← 模块列表、关系、布局坐标 ├── manifest.yaml ← 模块列表、关系、布局坐标
├── CONVENTIONS.md ← 本文件Claude 行为规则) ├── CONVENTIONS.md ← 本文件Claude 行为规则 + 全局推荐规则库)
├── rules.md ← 项目私有规则(可选,优先级高于全局规则)
├── usage.json ← 飞轮外环快照数据(炼境自动写入)
└── modules/ └── modules/
└── <module-id>.md ← 每个模块的详情 + 任务卡 └── <module-id>.md ← 每个模块的详情 + 任务卡
``` ```
@ -32,6 +34,23 @@ source: dev-manager-tauri
| 放弃了某个方向 | 删除对应模块和文件,或标记 status: abandoned | | 放弃了某个方向 | 删除对应模块和文件,或标记 status: abandoned |
| **重构已完成的模块(迭代)** | **manifest.iteration 递增,旧任务卡保留,新建替代任务卡,模块加 `## 迭代记录`** | | **重构已完成的模块(迭代)** | **manifest.iteration 递增,旧任务卡保留,新建替代任务卡,模块加 `## 迭代记录`** |
## 任务卡复杂度定义
| complexity | 含义 | 判断标准 | 推荐执行模型 |
|-----------|------|--------|------------|
| `S` | Simple | 单文件或机械操作;无需理解现有 pattern可独立完成 | Haiku |
| `M` | Medium | 跨 2-4 个文件;需理解现有代码风格和上下文;有隐式依赖 | Sonnet |
| `L` | Large/Complex | 系统架构级;**无法直接执行,必须先 `/architect` 设计再 `/splitter` 拆分** | Opus设计+ Sonnet执行 |
### 判断速查
- 改动涉及 3 个以上文件且分属不同功能区域 → 至少 M
- 需要先理解现有代码的内在逻辑或约定才能动手 → 至少 M
- 无法在派发时清晰描述完整实现方案 → L需先 `/architect`
- 单纯的 SQL 建表、类型文件、配置修改 → S
> **注**`/auto-execute` 技能会按此定义自动路由模型S 卡委托 Haiku 子 agentM 卡由当前 Sonnet 执行L 卡直接标记 blocked 并提示走 `/architect`
## 模块状态定义 ## 模块状态定义
| 状态 | 含义 | 色标 | | 状态 | 含义 | 色标 |
@ -82,6 +101,18 @@ modules/<id>.md 中的每个二级标题(`###`)是一张任务卡:
不满足条件的 todo 任务保持无前缀,说明还需补充信息。 不满足条件的 todo 任务保持无前缀,说明还需补充信息。
### 💭 concept 卡的极简写法
concept 卡是想法 inbox**不要求 files / acceptance / complexity**,随想随记:
```markdown
### 💭 ServerManagerPanel 表格和软件块有重复逻辑
- status: concept
```
一行就够。等某次对话中方向变清晰,再补全字段、升格为 📋。
**不要因为"信息不全就不记"而让想法消失**——concept 卡存在的意义就是承接碎片。
## manifest.yaml 规范 ## manifest.yaml 规范
### 顶层字段 ### 顶层字段
@ -201,9 +232,13 @@ updated: YYYY-MM-DD
Claude 在每次对话中应遵循以下流程: Claude 在每次对话中应遵循以下流程:
### 阶段 1进入对话 — 读取蓝图 ### 阶段 1进入对话 — 读取蓝图
``` ```
读取 .blueprint/manifest.yaml → 了解项目全景和当前状态 1. 读取 .blueprint/manifest.yaml ← 项目全景和当前状态
2. 读取 .blueprint/CONVENTIONS.md ← 工作流规则 + 全局推荐规则
3. 读取 .blueprint/rules.md ← 项目私有规则(若存在,合并应用,优先级更高)
``` ```
- 识别哪些模块 in_progress哪些有可派发任务 - 识别哪些模块 in_progress哪些有可派发任务
- 用简短话语告诉用户当前项目进展(如 "18/22 模块已完成2 个进行中" - 用简短话语告诉用户当前项目进展(如 "18/22 模块已完成2 个进行中"
@ -268,6 +303,28 @@ Claude 应关注这些信号来决定是否更新蓝图:
- 执行模型遇到无法解决的问题 → 标记 🔴 blocked + blocked_reason - 执行模型遇到无法解决的问题 → 标记 🔴 blocked + blocked_reason
- Opus 看到 blocked 任务 → 分析原因,拆/补/调后产出新任务卡 - Opus 看到 blocked 任务 → 分析原因,拆/补/调后产出新任务卡
### 对话中的复杂度漂移检测
碎片化想法在对话中自然累积有时从样式调整演变为架构改动。Claude 应主动识别并叫停。
**漂移信号**(以下任一出现即触发):
- 原始请求是样式/布局,但现在涉及组件 state、props 或后端命令
- 本次对话累计改动文件 ≥ 4 个,且分属不同功能区域
- 用户说了"顺便"、"既然这样"、"要不也把 XX"
- 前端改动已延伸到 DB 结构或 Rust 命令
**Claude 的应对动作**:检测到漂移信号时暂停执行,输出:
> ⚠️ 范围漂移:我们从「[原始请求]」走到了「[当前方向]」,累计涉及 [N] 个文件。建议:
> A) 完成当前这步,其余记为 💭 concept 卡待下次处理
> B) 现在拉清单,走 /architect 重新规划
> C) 继续执行,我在 session 结束前汇总成任务卡
**Session 结束汇总**:任何有代码改动但无预建任务卡的会话,结束前 Claude 应输出:
> 📋 本次会话实际改动:[文件列表 + 简述]
> 是否整理为蓝图任务卡?
## 交互质量复盘笔记 ## 交互质量复盘笔记
> 本章节仅适用于**开发执行阶段**(实现代码时),初始化蓝图或同步蓝图时无需执行。 > 本章节仅适用于**开发执行阶段**(实现代码时),初始化蓝图或同步蓝图时无需执行。
@ -285,8 +342,15 @@ Claude 应关注这些信号来决定是否更新蓝图:
返工原因: <若返工一句话说明根因"WSL路径格式未预料"> 返工原因: <若返工一句话说明根因"WSL路径格式未预料">
有效策略: <本次奏效的做法一句话> 有效策略: <本次奏效的做法一句话>
遗留风险: <若有一句话> 遗留风险: <若有一句话>
auto_applied_rules: R01, R06 ← 本次任务触发并执行了哪些规则(无则省略)
rule_effectiveness: R01=有效, R06=有效 ← 规则是否阻止了问题(有效/无效/未验证)
``` ```
**`auto_applied_rules` 填写说明**
- 仅记录本次任务实际触发Claude 主动检查或提示)的规则
- 若规则触发但用户选择跳过,也应记录(用于统计触发频率)
- `rule_effectiveness=无效` 表示规则触发了但没有阻止问题,这是最有价值的信号——梦核据此降低该规则置信度
### 触发时机 ### 触发时机
| 情况 | 操作 | | 情况 | 操作 |
@ -306,6 +370,8 @@ Claude 应关注这些信号来决定是否更新蓝图:
返工原因: wsl_path 字段存的是 Windows UNC 格式(\\wsl.localhost\Ubuntu-22.04\...),而非 Linux 路径,导致 wsl 子命令收到错误参数 返工原因: wsl_path 字段存的是 Windows UNC 格式(\\wsl.localhost\Ubuntu-22.04\...),而非 Linux 路径,导致 wsl 子命令收到错误参数
有效策略: 在函数入口统一用 unc_to_distro_and_linux() 转换,一次解析,全程复用 有效策略: 在函数入口统一用 unc_to_distro_and_linux() 转换,一次解析,全程复用
遗留风险: Path::exists() 对 \\wsl.localhost\ 路径无效,所有 WSL 路径存在性检查必须走 wsl -d distro -- test -d/f 遗留风险: Path::exists() 对 \\wsl.localhost\ 路径无效,所有 WSL 路径存在性检查必须走 wsl -d distro -- test -d/f
auto_applied_rules: R10
rule_effectiveness: R10=未验证(任务开始前未触发,事后归因)
``` ```
> 这些笔记不替代决策记录(决策记录记架构 WHY复盘笔记记执行 HOW 和踩坑)。两者都写。 > 这些笔记不替代决策记录(决策记录记架构 WHY复盘笔记记执行 HOW 和踩坑)。两者都写。
@ -362,10 +428,24 @@ Claude 应关注这些信号来决定是否更新蓝图:
### 闭环路径 ### 闭环路径
两条并行的反馈路径,速度不同:
``` ```
【快速路径 — 项目级,无需发版】
执行任务 → 复盘笔记auto_applied_rules
梦核分析项目笔记
输出项目私有规则草案
用户确认 → 写入 .blueprint/rules.md
下次对话立即生效
【慢速路径 — 全局级,需发版】
执行任务 → usage.json 快照 执行任务 → usage.json 快照
炼境聚合指标 炼境跨项目聚合指标
梦核提示词 → Claude Opus 分析 梦核提示词 → Claude Opus 分析
@ -378,7 +458,7 @@ Claude 应关注这些信号来决定是否更新蓝图:
治理面板 sync_blueprint_rules → 分发到各项目 治理面板 sync_blueprint_rules → 分发到各项目
``` ```
> **注意**CONVENTIONS.md 编译进炼境二进制,更新需要修改炼境源码并发版,不支持运行时修改。这是有意为之的设计——保证规则版本可追溯,防止随意污染 > **设计意图**快速路径响应单个项目的学习慢速路径沉淀跨项目的共识。CONVENTIONS.md 编译进炼境二进制保证全局规则版本可追溯rules.md 存于项目本地实现快速迭代。两条路径互补,共同构成择升环
## 无 MCP 环境兼容Qwen Code / 其他 AI ## 无 MCP 环境兼容Qwen Code / 其他 AI
@ -573,6 +653,195 @@ mod tests {
--- ---
## 项目私有规则rules.md
每个项目可以在 `.blueprint/rules.md` 中维护**仅对本项目生效**的推荐规则,格式与全局规则库完全相同。
### 优先级
```
项目私有规则(.blueprint/rules.md> 全局规则CONVENTIONS 自动推荐规则库)
```
项目私有规则更精准(来自该项目真实数据),全局规则更广泛(来自跨项目共识)。冲突时项目规则优先。
### 对话开始时的读取顺序
```
1. manifest.yaml ← 项目全景
2. CONVENTIONS.md ← 协议定义 + 全局规则
3. .blueprint/rules.md ← 项目私有规则(若存在,合并应用)
```
### 梦核写入路径
梦核(飞轮阶段三启动后)分析项目复盘笔记后,除了输出 CONVENTIONS 更新建议,还应输出**项目私有规则草案**,格式如下:
```markdown
## 建议新增项目私有规则
| rule_id | 触发条件 | 自动动作 | confidence | evidence_count | derived_from |
|---------|---------|---------|-----------|---------------|-------------|
| P01 | <本项目特有触发条件> | <自动动作> | 0.70 | 3 | <复盘笔记引用> |
```
用户确认后Claude 直接将规则写入 `.blueprint/rules.md`**无需等待炼境发版**。这是项目级择升环与全局发版解耦的关键路径。
### rules.md 文件格式
```markdown
# 项目私有规则
> 本文件由梦核分析 + 用户确认后生成Claude 每次对话开始时读取并合并应用。
> 规则格式与 CONVENTIONS 自动推荐规则库相同,优先级高于全局规则。
## 规则列表
### P01 — 规则标题
- **触发条件**xxx
- **自动动作**xxx
- **confidence**: 0.70
- **evidence_count**: 3
- **derived_from**: 复盘笔记 YYYY-MM-DD任务"xxx"中发现
```
### 何时创建 rules.md
- 梦核首次输出项目私有规则建议时创建
- 也可手动创建,记录人工识别的项目特有模式
- 文件不存在时跳过,不影响全局规则生效
---
## 自动推荐规则库
> 本章节定义了 Claude 在开发过程中应**主动触发**的高收益决策规则。
> 规则有置信度confidence和证据数evidence_count置信度越高越应自动执行无需用户确认。
### 执行策略
| confidence 范围 | 执行方式 |
|----------------|---------|
| ≥ 0.80 | 直接执行,仅一行说明("按 R01 检查..." |
| 0.60 ~ 0.79 | 执行前单句说明原因,用户可跳过 |
| < 0.60 | 仅作提醒不自动执行 |
置信度由梦核(阶段三启动后)根据 evidence_count 和有效率自动更新;在此之前由人工在复盘中维护。
### scope 字段
每条规则有 `scope` 字段标明适用范围。Claude 应在应用规则前检查当前项目是否匹配:
| scope 值 | 含义 |
|---------|------|
| `universal` | 适用于任何代码项目 |
| `tauri` | 仅适用于 Tauri 项目 |
| `rust` | 仅适用于含 Rust 后端的项目 |
| `sqlite` | 仅适用于使用 SQLite 的项目 |
| `react` | 仅适用于 React 前端项目 |
多个 scope 用 `+` 连接,如 `tauri+rust`。**scope 不匹配时跳过该规则,不作提醒。**
项目私有规则(`rules.md`)无需 scope 字段,默认对本项目全部生效。
### 规则列表
#### R01 — 新增 Tauri command 时检查前端封装层
- **scope**: `tauri`
- **触发条件**:在 `src-tauri/src/commands/` 下新增 `#[tauri::command]` 函数
- **自动动作**:读取 `src/lib/commands.ts`,确认是否有对应封装;缺失则提示补写,不阻止执行
- **confidence**: 0.90
- **evidence_count**: 8
- **derived_from**: 多次发生前后端接口断层(命令已实现但 commands.ts 未同步,前端调用报错)
#### R02 — 任务卡无 acceptance 时先补写再动代码
- **scope**: `universal`
- **触发条件**:任务卡 `acceptance` 字段为空或缺失,但已准备开始执行
- **自动动作**:暂停执行,根据任务描述推导验收标准草稿,请用户确认后再写第一行代码
- **confidence**: 0.92
- **evidence_count**: 18
- **derived_from**: 无验收标准导致完成定义模糊,任务频繁反复("做完了但感觉不对"
#### R03 — M/L 任务标记 done 后写复盘笔记
- **scope**: `universal`
- **触发条件**M 或 L 复杂度的任务卡状态变更为 ✅ done
- **自动动作**:调用 `append_project_note` 写入复盘笔记(见"交互质量复盘笔记"章节格式)
- **confidence**: 0.88
- **evidence_count**: 24
- **derived_from**: 飞轮外环数据来源,无复盘笔记则梦核无输入,整个飞轮断链
#### R04 — 对话改动 ≥ 4 个跨功能区文件时触发漂移警告
- **scope**: `universal`
- **触发条件**:单次对话累计改动文件 ≥ 4 个且分属不同功能区frontend/backend/infra
- **自动动作**:输出范围漂移提示(见"对话中的复杂度漂移检测"章节),给出 A/B/C 三个选项
- **confidence**: 0.85
- **evidence_count**: 12
- **derived_from**: 多次"样式改动演变成后端重构"的失控扩散,最终要么返工要么留下技术债
#### R05 — SQLite 涉及新表或新字段时检查 migration
- **scope**: `sqlite`
- **触发条件**:在项目中出现 `CREATE TABLE` / `ALTER TABLE` / 新增列定义
- **自动动作**:检查是否有对应 migration 逻辑;缺失则提示添加,防止旧版本升级后 schema 断裂
- **confidence**: 0.82
- **evidence_count**: 7
- **derived_from**: 数据库 schema 变更未同步 migration导致旧安装版本升级后启动报错
#### R06 — Rust 业务函数含 DB 操作时强制依赖注入
- **scope**: `rust+sqlite`
- **触发条件**:新增 Rust 函数内部直接获取 DB 连接(`pool().get()` 或等价写法)
- **自动动作**提示改为依赖注入写法连接由调用方传入command 层负责获取连接
- **confidence**: 0.93
- **evidence_count**: 15
- **derived_from**: 飞轮阶段一实现时无测试导致静默 bug 上线;依赖注入是唯一能让 in-memory DB 测试覆盖业务逻辑的方式
#### R07 — 数据关键路径函数必须附带单元测试
- **scope**: `universal`
- **触发条件**:新增函数负责数据采集、聚合、解析等关键路径(影响下游分析或展示的正确性)
- **自动动作**:在实现函数前提醒附带单元测试,覆盖正常路径 + 空数据边界;完成后验证测试全绿
- **confidence**: 0.95
- **evidence_count**: 6
- **derived_from**: 飞轮阶段一数据采集 bug 因无测试静默上线,污染所有下游分析
#### R08 — UI 状态显示必须来自真实检测
- **scope**: `universal`
- **触发条件**:前端组件显示运行状态、健康状态、操作结果等实时信息
- **自动动作**:检查对应后端是否做了真实检测而非硬编码;发现硬编码状态时标记为需修复
- **confidence**: 0.87
- **evidence_count**: 5
- **derived_from**: 设置页 MCP 状态硬编码为"运行中",注入失败时 toast 始终显示成功——UI 掩盖真实问题
#### R09 — 自动化边界:事件触发只标 🔵,不标 ✅
- **scope**: `universal`
- **触发条件**讨论或实现基于外部事件git commit、CI 结果等)自动更新任务卡状态
- **自动动作**:确认自动化只将任务卡置为 🔵 in_progress绝不自动置为 ✅ done完成语义需人工或 Claude 验收确认
- **confidence**: 0.91
- **evidence_count**: 4
- **derived_from**: 代码提交 ≠ 功能验收通过,自动标完成导致数据与实际不符
#### R10 — 对接外部协议/API 前先验证客户端实际行为
- **scope**: `universal`
- **触发条件**实现与外部客户端的协议对接MCP、LSP、WebSocket、SSE、OAuth 等)
- **自动动作**在开始实现前先通过文档或工具curl、抓包确认客户端实际发出的请求格式不基于"通用规范"假设行为
- **confidence**: 0.85
- **evidence_count**: 3
- **derived_from**: Claude Code MCP 客户端用 GET 发起 SSE 握手,炼境只接受 POST 导致 405MCP 整体失效
### 规则演进路径
```
人工识别(初始)
↓ evidence_count 累积 5+ 次
低置信验证期confidence 0.60~0.79
↓ 持续有效evidence_count 15+
自动执行期confidence ≥ 0.80
↓ 发现无效或场景已变化
降级/归档confidence 跌至 0.4 以下)
```
梦核(飞轮阶段三启动后)将根据复盘笔记中的 `auto_applied_rules``rule_effectiveness` 字段自动更新 confidence。
---
## 注意事项 ## 注意事项
- 不要删除已完成的任务卡,它们是项目历史的一部分 - 不要删除已完成的任务卡,它们是项目历史的一部分

View File

@ -6,7 +6,7 @@ description: >
成为每个项目的贴身导师。炼境本身亦被纳入其中, 成为每个项目的贴身导师。炼境本身亦被纳入其中,
形成可自我进化的开发智能闭环。 形成可自我进化的开发智能闭环。
iteration: 1 iteration: 1
updated: 2026-04-17 updated: 2026-04-19

View File

@ -0,0 +1,18 @@
# API密钥管理
统一存储和管理各类 API 密钥OpenAI、Anthropic、阿里云等支持按服务商分类、显示/隐藏、复制到剪贴板,密钥加密存储在本地 SQLite。
## 决策记录
- 存本地 SQLite不上云炼境定位是本地工具密钥不离开本机
- 目前不做系统 Keychain 集成SQLite 存储已满足个人开发者场景;如有需求可在迭代二引入
- 支持「注入到项目环境变量」的快捷操作(配合 project-tools 模块)
## 任务卡
### ✅ API 密钥 CRUD 与安全展示
- status: done
- complexity: M
- files: src-tauri/src/commands/api_keys.rs, src-tauri/src/db.rs, src/components/dashboard/ApiKeysPanel.tsx, src/lib/commands.ts
- depends: sqlite-db
- acceptance: 可新增/编辑/删除 API 密钥;密钥默认隐藏,点击眼睛图标显示;支持一键复制;按服务商分类展示

View File

@ -0,0 +1,16 @@
# Claude 自主性配置
炼境感知全局 Claude Beast 运行状态安装情况、当前模式、bash 规则),在项目卡片提供只读状态展示 + CLI 操作引导,炼境本身不写入任何 Claude 配置。
## 决策记录
- 炼境只读,不写 Claude 配置:写入动作由用户通过 CLI 完成,炼境只做展示和引导,避免意外覆盖用户的 settings.json
- Beast 是全局状态,因此所有项目卡片都显示同一个 🤖 入口,而非每项目独立配置
## 任务卡
### ✅ Beast 全局状态读取与展示
- status: done
- complexity: M
- files: src-tauri/src/commands/claude_config.rs, src/components/dashboard/ClaudeConfigModal.tsx, src/components/dashboard/ProjectCard.tsx, src/lib/commands.ts
- acceptance: 项目卡片显示 🤖 按钮;点击弹窗展示当前 Beast 安装状态、运行模式、bash 规则;面板只读,提供 CLI 操作引导文案

View File

@ -0,0 +1,17 @@
# 云数据库实例
云产品管理的子模块管理云端数据库实例MySQL、PostgreSQL、Redis 等),记录连接信息、展示实例状态,提供连接字符串快捷复制。
## 决策记录
- 炼境只做「连接信息管理」,不做数据库 GUI 客户端(那是 TablePlus/DBeaver 的职责)
- 连接信息与 api-keys 分开存:数据库连接串包含主机、端口、库名等配置参数,不只是密钥,放 cloud-db-instances 更语义清晰
## 任务卡
### ✅ 云数据库实例信息管理
- status: done
- complexity: M
- files: src-tauri/src/commands/cloud_db_databases.rs, src-tauri/src/db.rs, src/components/dashboard/CloudProductsPanel.tsx, src/lib/commands.ts
- depends: cloud-products, sqlite-db
- acceptance: 云产品面板展示数据库实例列表;支持新增/编辑/删除连接字符串可一键复制按数据库类型MySQL/PG/Redis分类显示

View File

@ -0,0 +1,17 @@
# 云产品 Docker 应用
云产品管理的子模块,管理部署在云服务器上的 Docker 容器/应用,展示运行状态、端口映射、镜像信息,支持基本的容器操作(启动/停止/重启)。
## 决策记录
- 作为 cloud-products 的子模块,复用服务器注册表和 SSH 连接基础设施
- 操作通过 SSH 执行 docker 命令,不依赖 Docker API 守护进程暴露端口,安全性更好
## 任务卡
### ✅ Docker 应用列表与状态管理
- status: done
- complexity: M
- files: src-tauri/src/commands/docker_apps.rs, src-tauri/src/db.rs, src/components/dashboard/CloudProductsPanel.tsx, src/lib/commands.ts
- depends: cloud-products, server-registry, ssh-config-sync
- acceptance: 云产品面板展示 Docker 应用列表,含状态、镜像、端口;支持启动/停止/重启操作;数据与服务器关联正确

View File

@ -0,0 +1,17 @@
# 项目部署信息
记录和展示项目的部署环境配置(域名、服务器关联、部署路径、环境变量说明、部署脚本备注),作为项目的运维文档沉淀在炼境中。
## 决策记录
- 部署信息是「备忘」性质而非「执行」性质,炼境记录信息、不直接执行部署,避免炼境承载过多运维职责
- 与 server-registry 关联:部署目标服务器从注册表中选取,保证数据一致性
## 任务卡
### ✅ 项目部署信息记录面板
- status: done
- complexity: M
- files: src-tauri/src/commands/deploy_info.rs, src-tauri/src/db.rs, src/components/dashboard/DeployInfoPanel.tsx, src/lib/commands.ts
- depends: sqlite-db, server-registry
- acceptance: 项目看板有「部署信息」Tab可记录域名、部署服务器、路径、环境变量说明数据持久化到 SQLite

View File

@ -104,6 +104,7 @@
- 任务完成时长分布(为 S/M/L 定义时间基准) - 任务完成时长分布(为 S/M/L 定义时间基准)
- 高风险任务预警(特定文件组合 → 历史高返工率) - 高风险任务预警(特定文件组合 → 历史高返工率)
- 跨项目模式库推荐 - 跨项目模式库推荐
- **自动推荐规则置信度更新**:梦核读取复盘笔记中的 `auto_applied_rules` + `rule_effectiveness` 字段,输出 CONVENTIONS 规则库的 confidence/evidence_count 更新建议;新增规则提案(从高频踩坑模式自动归纳)
> ⚠️ 阶段三为 L 级复杂度,到时须先走 /architect → /splitter 流程,禁止直接写代码。 > ⚠️ 阶段三为 L 级复杂度,到时须先走 /architect → /splitter 流程,禁止直接写代码。

View File

@ -1,35 +1,17 @@
--- # GitHub 发布
id: github-publish
name: GitHub 发布
status: done
progress: 100
---
## 概述 在炼境内通过 GitHub OAuth 直接将本地项目推送到远程仓库,支持新建仓库或关联已有仓库,完成发布后自动回填 remote URL。
将本地项目发布到 GitHub支持初始化 Git 仓库、创建远程仓库、推送代码、生成 .gitignore。
## 决策记录
- 依赖 github-auth 模块提供的 access token不在本模块持久化凭证
- 发布操作调用 GitHub REST API 创建仓库,然后通过 git2 库执行 push
## 任务卡 ## 任务卡
### ✅ 扫描本地 Git 状态 ### ✅ GitHub 仓库创建与推送
- status: done - status: done
- files: src-tauri/src/commands/publish.rs - complexity: M
- files: src-tauri/src/commands/github.rs, src/components/dashboard/ProjectCard.tsx
检测项目是否已初始化 Git、是否有远程仓库。 - depends: github-auth
- acceptance: 未关联远程的项目可在看板卡片中一键发布到 GitHub发布成功后 remote_url 写入 DB卡片显示 GitHub 链接
### ✅ 创建 GitHub 仓库
- status: done
- files: src-tauri/src/commands/publish.rs
通过 GitHub API 创建新仓库。
### ✅ Git 初始化 & 首次推送
- status: done
- files: src-tauri/src/commands/publish.rs
git init、首次 commit、设置 remote、push。
### ✅ .gitignore 生成
- status: done
- files: src-tauri/src/commands/publish.rs
根据项目类型生成 .gitignore 文件。

View File

@ -0,0 +1,17 @@
# 宿主机应用管理
管理宿主机Windows/Mac上与开发相关的常用应用IDE、工具、浏览器扩展等支持快速启动和版本记录作为开发环境的软件清单。
## 决策记录
- 宿主机应用与服务器软件分开管理server-software 管远程host-apps 管本地
- 以「记录与快启」为核心不做安装管理winget/brew 已有专门工具)
## 任务卡
### ✅ 宿主机应用清单与快启
- status: done
- complexity: M
- files: src-tauri/src/commands/host_apps.rs, src-tauri/src/db.rs, src/components/dashboard/HostAppsPanel.tsx, src/lib/commands.ts
- depends: sqlite-db
- acceptance: 可添加/删除宿主机应用条目(名称、路径、版本备注);支持一键启动;数据存 SQLite

View File

@ -27,3 +27,13 @@ progress: 100
- files: src-tauri/src/commands/templates.rs - files: src-tauri/src/commands/templates.rs
首次启动时自动插入预设模板。 首次启动时自动插入预设模板。
### ✅ P1+P3 模板扩充React 生态全覆盖)
- status: done
- complexity: M
- files: src-tauri/src/commands/templates.rs
新增三个内置模板:
- `builtin-vite-react`React 19 + TS + TailwindCSS v4 + React Router v7纯前端 SPA 起点
- `builtin-tauri-react-tailwind`Tauri v2 + React + TailwindCSS v4客户端应用带样式
- `builtin-web-mobile-monorepo`Web + Mobile 共享 Monorepopackages/core业务逻辑+ packages/types + apps/webVite+Tailwind+ apps/mobileExpo+NativeWind

View File

@ -0,0 +1,18 @@
# 服务器注册表
统一管理远程服务器信息IP/域名、SSH 端口、用户名、描述、标签),是服务器管理体系的核心数据层,为 SSH Config 同步、VS Code Remote、软件管理等子功能提供基础数据。
## 决策记录
- 服务器信息存 SQLite 而非配置文件,方便跨模块查询和关联(项目与服务器多对多)
- 项目可关联多个服务器(部署环境),通过 project_servers 关联表实现
- 拆分为两个面板ServerManagerPanel全局服务器列表+ ProjectServerPanel项目维度的关联视图
## 任务卡
### ✅ 服务器 CRUD 与项目关联
- status: done
- complexity: M
- files: src-tauri/src/commands/servers.rs, src-tauri/src/db.rs, src/components/dashboard/ServerManagerPanel.tsx, src/components/dashboard/ProjectServerPanel.tsx, src/lib/commands.ts
- depends: sqlite-db
- acceptance: 可新增/编辑/删除服务器;项目看板可关联/解除关联服务器servers 表和 project_servers 关联表正确建立

View File

@ -0,0 +1,18 @@
# 服务器软件管理
通过 SSH 远程查询服务器上已安装的软件Nginx、Docker、Node.js 等),展示版本号和运行状态,支持常见软件的一键安装/启动/重启指令生成。
## 决策记录
- 查询通过 SSH 执行命令获取,不依赖服务器上预装的 agent降低接入成本
- 仅覆盖高频运维软件,不做通用包管理器封装,避免过度复杂
- 指令生成而非直接执行:把安装命令展示给用户,由用户确认后在终端执行,避免炼境直接 SSH exec 引入安全风险
## 任务卡
### ✅ 服务器软件扫描与展示
- status: done
- complexity: M
- files: src-tauri/src/commands/server_software.rs, src/components/dashboard/ServerSoftwareBlock.tsx
- depends: server-registry, ssh-config-sync
- acceptance: 服务器详情面板展示软件列表,含版本号和状态;支持刷新;已知软件有对应图标

View File

@ -0,0 +1,17 @@
# 服务配置管理
管理项目依赖的外部服务配置(数据库连接字符串、消息队列地址、第三方 API endpoint 等按环境dev/staging/prod分组存储与 API 密钥管理互补。
## 决策记录
- 与 api-keys 区分api-keys 存鉴权凭证service-config 存服务地址和配置参数(非敏感或低敏感)
- 按环境分组是核心需求:同一服务在不同环境有不同配置,需要清晰区分
## 任务卡
### ✅ 服务配置 CRUD 与环境分组
- status: done
- complexity: M
- files: src-tauri/src/commands/service_config.rs, src-tauri/src/db.rs, src/components/dashboard/ServiceConfigBlock.tsx, src/lib/commands.ts
- depends: sqlite-db, server-registry
- acceptance: 项目看板可管理服务配置条目支持按环境dev/staging/prod分组CRUD 操作正常;数据存 SQLite

View File

@ -0,0 +1,17 @@
# 源码库
管理个人收藏的优质开源项目、参考代码库、学习资源,支持按技术分类、标签过滤、快速跳转到仓库/本地路径,作为个人开发知识库的入口。
## 决策记录
- 独立页面而非 Dashboard 面板:源码库是横跨项目的全局资源,不属于某个具体项目,放顶层导航合适
- 与 repo-registry项目仓库注册区分repo-registry 管理「正在做的项目」source-library 管理「参考/学习用的仓库」
## 任务卡
### ✅ 源码库分类管理与项目详情
- status: done
- complexity: M
- files: src-tauri/src/commands/source_library.rs, src-tauri/src/db.rs, src/components/source-library/SourceLibraryPage.tsx, src/components/source-library/CategoryManager.tsx, src/components/source-library/ProjectFormModal.tsx, src/lib/commands.ts
- depends: sqlite-db
- acceptance: 导航栏有「源码库」入口支持分类管理增删改每个条目有名称、URL、本地路径、标签、描述支持按分类/标签过滤;数据存 SQLite

View File

@ -0,0 +1,18 @@
# SSH Config 自动同步
将炼境服务器注册表中的条目自动写入 `~/.ssh/config`,支持 Windows 宿主机和 WSL 两套 SSH Config 同步,使 `ssh server-alias` 直接可用。
## 决策记录
- 同步时采用「炼境专属 Block」写法`# BEGIN LIANGJING` / `# END LIANGJING`),不影响用户手动维护的其他条目
- WSL 侧需额外同步一份,因为 WSL 和 Windows 各有独立的 `~/.ssh/config`
- 同步是主动触发(服务器变更时 + 用户手动点击),不做定时轮询
## 任务卡
### ✅ SSH Config 写入与同步
- status: done
- complexity: M
- files: src-tauri/src/commands/ssh_config.rs, src/components/dashboard/ServerManagerPanel.tsx
- depends: server-registry
- acceptance: 点击「同步 SSH Config」后`~/.ssh/config` 出现炼境专属 Block包含所有已注册服务器的 Host 条目WSL 侧(若已配置)同步写入;重复触发幂等,不产生重复条目

View File

@ -0,0 +1,17 @@
# VS Code Remote SSH
在炼境服务器面板中一键用 VS Code 通过 Remote SSH 连接到指定服务器,复用 SSH Config 中已同步的 Host alias。
## 决策记录
- 依赖 ssh-config-sync 保证 Host alias 已写入 `~/.ssh/config`,不在本模块重复维护连接参数
- 直接调用 `code --remote ssh-remote+<alias> /path` 命令,复用 shell-launch 模块的进程启动能力
## 任务卡
### ✅ VS Code Remote SSH 一键连接
- status: done
- complexity: S
- files: src-tauri/src/commands/ssh_config.rs, src/components/dashboard/ServerManagerPanel.tsx
- depends: ssh-config-sync, shell-launch
- acceptance: 服务器列表每行有「VS Code」按钮点击后 VS Code 打开并以 Remote SSH 连接对应服务器SSH Config 未同步时给出提示

38
.blueprint/rules.md Normal file
View File

@ -0,0 +1,38 @@
# 项目私有规则
> 本文件是炼境dev-manager-tauri的项目级择升环规则库。
> 由梦核分析 + 用户确认后写入Claude 每次对话开始时读取,优先级高于全局 CONVENTIONS 规则。
## 规则列表
### P01 — 炼境 blueprint 命令变更时同步治理分发逻辑
- **触发条件**:修改 `src-tauri/src/commands/blueprint*.rs` 中的数据结构或接口
- **自动动作**:检查 `blueprint_governance.rs` 的分发逻辑是否需要同步更新;特别检查 `sync_blueprint_rules` 命令的字段映射
- **confidence**: 0.82
- **evidence_count**: 4
- **derived_from**: blueprint 数据结构变更多次导致治理分发的字段不匹配,治理面板静默失效
### P02 — MCP 相关改动后必须做端到端连接测试
- **触发条件**:修改 `src-tauri/src/mcp_server.rs` 或 MCP 相关路由
- **自动动作**:提醒在改动后用 `curl -N` 实际测试 SSE 端点可达性;不能仅凭编译通过判断 MCP 功能正常
- **confidence**: 0.90
- **evidence_count**: 3
- **derived_from**: MCP SSE 握手问题GET/POST 不匹配)编译完全正常,只有实际连接测试才能发现
### P03 — 梦核 prompt 构建函数变更后验证输出格式
- **触发条件**:修改 `src-tauri/src/commands/blueprint_feedback.rs` 中的 prompt 构建逻辑
- **自动动作**:在测试中断言 prompt 输出包含预期的结构段落(高频陷阱、规则更新建议等),防止格式静默退化
- **confidence**: 0.78
- **evidence_count**: 2
- **derived_from**: prompt 构建是纯字符串拼接,无类型约束,重构时容易遗漏段落
## 待验证规则concept
> 以下规则来自直觉evidence_count 不足,暂不自动执行,仅作提醒。
### PC01 — usage.json 字段扩展时检查历史快照兼容性
- **触发条件**:向 usage.json snapshot 结构新增字段
- **自动动作**:提醒历史快照中该字段为空/缺失,下游聚合逻辑需处理 null/missing 情况
- **confidence**: 0.65
- **evidence_count**: 2
- **derived_from**: 阶段二扩展字段时发现旧快照无对应值,聚合时需要特判

File diff suppressed because it is too large Load Diff

View File

@ -42,7 +42,7 @@ docs/ 部署和更新指南
- 代码重构/简化:/simplify → /code-review - 代码重构/简化:/simplify → /code-review
- 发现深层问题:/Dev-deep-think - 发现深层问题:/Dev-deep-think
<!-- BLUEPRINT_MANAGED_START version="1.4.5" --> <!-- BLUEPRINT_MANAGED_START version="1.6.0" -->
## 项目蓝图 ## 项目蓝图
本项目使用 `.blueprint/` 目录维护项目全景架构图,驱动需求讨论和任务管理。 本项目使用 `.blueprint/` 目录维护项目全景架构图,驱动需求讨论和任务管理。
@ -51,6 +51,7 @@ docs/ 部署和更新指南
1. 读取 `.blueprint/manifest.yaml` 了解项目全景和当前状态 1. 读取 `.blueprint/manifest.yaml` 了解项目全景和当前状态
2. 读取 `.blueprint/CONVENTIONS.md` 了解完整工作流规则 2. 读取 `.blueprint/CONVENTIONS.md` 了解完整工作流规则
3. 若 `.blueprint/rules.md` 存在,读取并与全局规则合并应用(项目私有规则,优先级高于全局)
### 需求讨论 → 蓝图同步规则 ### 需求讨论 → 蓝图同步规则

View File

@ -1017,9 +1017,12 @@ pub fn get_blueprint_status(project_path: String) -> Result<BlueprintStatus, Str
// 判断状态对比当前生效版本用户版优先fallback 内置) // 判断状态对比当前生效版本用户版优先fallback 内置)
let effective_ver = effective_conventions_version(); let effective_ver = effective_conventions_version();
let rules_current = conventions_version.as_deref() == Some(effective_ver.as_str()) // 项目版本 >= 内置生效版本时视为规则已同步(项目可能领先于内置基线)
let project_ver_tuple = parse_semver_tuple(conventions_version.as_deref().unwrap_or("0.0.0"));
let effective_ver_tuple = parse_semver_tuple(&effective_ver);
let rules_current = project_ver_tuple >= effective_ver_tuple
&& claude_md_managed && claude_md_managed
&& claude_md_version.as_deref() == Some(effective_ver.as_str()); && claude_md_version.as_deref().map(|v| parse_semver_tuple(v) >= effective_ver_tuple).unwrap_or(false);
let status = if !rules_current { let status = if !rules_current {
"rules_outdated".to_string() "rules_outdated".to_string()

View File

@ -690,6 +690,905 @@ pub fn run() {
]"#), ]"#),
files: vec![], files: vec![],
}, },
Tpl {
id: "builtin-pnpm-expo-monorepo",
name: "pnpm Monorepo + Expo",
description: "pnpm 多包 Monorepoapps/ 内含 Expo Router (React Native) 应用packages/ 内含共享 @repo/types 包。Metro 已配置 monorepo 路径解析(含 Windows 兼容修复),适合多 App 扩展场景。",
platform: "both",
tech_stack: "React Native,TypeScript,Expo,pnpm",
init_commands: "pnpm install",
variants: None,
files: vec![
// ── monorepo 根 ──────────────────────────────────────────────
("package.json", r#"{
"name": "{{project_name_kebab}}",
"private": true,
"scripts": {
"start": "pnpm --filter app start",
"android": "pnpm --filter app android",
"ios": "pnpm --filter app ios",
"lint": "pnpm -r lint"
},
"engines": {
"node": ">=18",
"pnpm": ">=8"
}
}
"#),
("pnpm-workspace.yaml", "packages:\n - 'apps/*'\n - 'packages/*'\n"),
("tsconfig.base.json", r#"{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020"],
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"exclude": ["node_modules", "dist", "build"]
}
"#),
(".gitignore", "node_modules/\ndist/\n.expo/\nandroid/\nios/\n*.log\n.env\n.env.*\n!.env.example\n"),
(".npmrc", "strict-peer-dependencies=false\nauto-install-peers=true\n"),
// ── packages/types ───────────────────────────────────────────
("packages/types/package.json", r#"{
"name": "@repo/types",
"version": "0.0.1",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
}
},
"files": ["src"],
"scripts": {
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.4.5"
}
}
"#),
("packages/types/src/index.ts", "// 在此导出跨 App 共享的类型\nexport type {}\n"),
// ── apps/app ─────────────────────────────────────────────────
("apps/app/package.json", r#"{
"name": "app",
"version": "1.0.0",
"private": true,
"main": "expo-router/entry",
"scripts": {
"start": "expo start",
"android": "expo run:android",
"ios": "expo run:ios",
"lint": "eslint . --ext .ts,.tsx"
},
"dependencies": {
"@repo/types": "workspace:*",
"expo": "~52.0.0",
"expo-router": "~4.0.0",
"expo-status-bar": "~2.0.0",
"react": "18.3.1",
"react-native": "0.76.3",
"react-native-gesture-handler": "~2.20.0",
"react-native-reanimated": "~3.16.1",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "~4.1.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~18.3.12",
"typescript": "^5.4.5"
}
}
"#),
("apps/app/app.json", r##"{
"expo": {
"name": "{{project_name}}",
"slug": "{{project_name_kebab}}",
"version": "1.0.0",
"orientation": "default",
"userInterfaceStyle": "light",
"scheme": "{{project_name_kebab}}",
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.example.{{project_name_snake}}"
},
"android": {
"adaptiveIcon": {
"backgroundColor": "#ffffff"
},
"package": "com.example.{{project_name_snake}}"
},
"plugins": ["expo-router"],
"experiments": {
"typedRoutes": true
}
}
}
"##),
("apps/app/tsconfig.json", r#"{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"paths": {
"@repo/types": ["../../packages/types/src/index.ts"]
}
},
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
}
"#),
("apps/app/babel.config.js", r#"module.exports = function (api) {
api.cache(true)
return {
presets: ['babel-preset-expo'],
}
}
"#),
// Metro 配置monorepo 路径解析 + Windows release 构建兼容修复
("apps/app/metro.config.js", r#"const { getDefaultConfig } = require('expo/metro-config')
const path = require('path')
const projectRoot = __dirname
const monorepoRoot = path.resolve(projectRoot, '../..')
const config = getDefaultConfig(projectRoot)
// Windows release 构建兼容:强制 unstable_serverRoot 为 projectRoot
// 避免 Metro 与 RN Gradle 插件的路径基准不一致。
config.server = {
...config.server,
unstable_serverRoot: projectRoot,
}
// 让 Metro 监听 monorepo 根目录,确保根 node_modules 可被解析
config.watchFolders = [monorepoRoot]
// 模块解析:优先查 app 自己的 node_modules再查 monorepo 根
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(monorepoRoot, 'node_modules'),
]
module.exports = config
"#),
("apps/app/expo-env.d.ts", "/// <reference types=\"expo/types\" />\n\n// NOTE: This file should not be edited and should be in your git ignore\n"),
("apps/app/app/_layout.tsx", r#"import { Stack } from 'expo-router'
import { StatusBar } from 'expo-status-bar'
export default function RootLayout() {
return (
<>
<StatusBar style="auto" />
<Stack>
<Stack.Screen name="index" options={{ title: '{{project_name}}' }} />
</Stack>
</>
)
}
"#),
("apps/app/app/index.tsx", r#"import { View, Text, StyleSheet } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
export default function HomeScreen() {
return (
<SafeAreaView style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>{{project_name}}</Text>
<Text style={styles.subtitle}>Expo Router + pnpm Monorepo</Text>
</View>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
content: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 8 },
title: { fontSize: 24, fontWeight: 'bold' },
subtitle: { fontSize: 14, color: '#666' },
})
"#),
],
},
// ── P1: React + Vite SPA ─────────────────────────────────────────────
Tpl {
id: "builtin-vite-react",
name: "React + Vite SPA",
description: "纯前端 Web 应用React 19 + TypeScript + TailwindCSS v4 + React Router v7",
platform: "both",
tech_stack: "React,TypeScript,Vite,TailwindCSS",
init_commands: "pnpm install",
variants: None,
files: vec![
("package.json", r#"{
"name": "{{project_name_kebab}}",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^19",
"react-dom": "^19",
"react-router": "^7"
},
"devDependencies": {
"@tailwindcss/vite": "^4",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^4",
"tailwindcss": "^4",
"typescript": "^5",
"vite": "^6"
}
}
"#),
("vite.config.ts", r#"import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
});
"#),
("tsconfig.json", r#"{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true
},
"include": ["src"]
}
"#),
("index.html", r#"<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{project_name}}</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
"#),
("src/main.tsx", r#"import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
"#),
("src/App.tsx", r#"import { Routes, Route } from "react-router";
import Home from "./pages/Home";
export default function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
</Routes>
);
}
"#),
("src/index.css", "@import \"tailwindcss\";\n"),
("src/pages/Home.tsx", r#"export default function Home() {
return (
<main className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="text-center">
<h1 className="text-4xl font-bold text-gray-900">{{project_name}}</h1>
<p className="mt-2 text-gray-500">React + Vite + TailwindCSS</p>
</div>
</main>
);
}
"#),
(".gitignore", "node_modules/\ndist/\n.env\n.env.*\n!.env.example\n"),
],
},
// ── P1: Tauri + React + TailwindCSS ──────────────────────────────────
Tpl {
id: "builtin-tauri-react-tailwind",
name: "Tauri + React + Tailwind",
description: "桌面应用Tauri v2 + React 19 + TypeScript + TailwindCSS v4",
platform: "both",
tech_stack: "Rust,Node,pnpm,React,TypeScript,Tauri,TailwindCSS",
init_commands: "pnpm install",
variants: None,
files: vec![
("package.json", r#"{
"name": "{{project_name_kebab}}",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "tauri dev",
"build": "tauri build",
"preview": "vite preview"
},
"dependencies": {
"@tauri-apps/api": "^2",
"react": "^19",
"react-dom": "^19"
},
"devDependencies": {
"@tailwindcss/vite": "^4",
"@tauri-apps/cli": "^2",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^4",
"tailwindcss": "^4",
"typescript": "^5",
"vite": "^6"
}
}
"#),
("vite.config.ts", r#"import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig(async () => ({
plugins: [react(), tailwindcss()],
clearScreen: false,
server: {
port: 1420,
strictPort: true,
watch: {
ignored: ["**/src-tauri/**"],
},
},
}));
"#),
("tsconfig.json", r#"{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true
},
"include": ["src"]
}
"#),
("index.html", r#"<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{project_name}}</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
"#),
("src/main.tsx", r#"import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
"#),
("src/index.css", "@import \"tailwindcss\";\n"),
("src/App.tsx", r#"import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
return (
<main className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="text-center space-y-4">
<h1 className="text-4xl font-bold text-gray-900">{{project_name}}</h1>
<p className="text-gray-500">Tauri v2 + React + TailwindCSS</p>
<button
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
onClick={() => setCount((c) => c + 1)}
>
count: {count}
</button>
</div>
</main>
);
}
export default App;
"#),
("src-tauri/tauri.conf.json", r#"{
"$schema": "https://schema.tauri.app/config/2",
"productName": "{{project_name_kebab}}",
"version": "0.1.0",
"identifier": "com.example.{{project_name_snake}}",
"build": {
"beforeDevCommand": "pnpm vite",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "pnpm vite build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "{{project_name}}",
"width": 1200,
"height": 800
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": []
}
}
"#),
("src-tauri/Cargo.toml", r#"[package]
name = "{{project_name_snake}}"
version = "0.1.0"
description = "A Tauri App"
authors = []
edition = "2021"
[lib]
name = "{{project_name_snake}}_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
"#),
("src-tauri/build.rs", "fn main() {\n tauri_build::build()\n}\n"),
("src-tauri/src/main.rs", r#"// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
{{project_name_snake}}_lib::run()
}
"#),
("src-tauri/src/lib.rs", r#"#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
"#),
("src-tauri/capabilities/default.json", r#"{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"opener:default"
]
}
"#),
(".gitignore", "node_modules/\ndist/\n.env\n.env.*\n!.env.example\nsrc-tauri/target/\nsrc-tauri/gen/\n"),
],
},
// ── P3: Web + Mobile 共享 Monorepo ────────────────────────────────────
Tpl {
id: "builtin-web-mobile-monorepo",
name: "Web + Mobile Monorepo",
description: "跨平台代码复用packages/core 共享业务逻辑apps/webVite + Tailwind+ apps/mobileExpo + NativeWind",
platform: "both",
tech_stack: "React,React Native,TypeScript,Expo,Vite,TailwindCSS,NativeWind,pnpm",
init_commands: "pnpm install",
variants: None,
files: vec![
// ── 根目录 ──────────────────────────────────────────────────────
("package.json", r#"{
"name": "{{project_name_kebab}}",
"private": true,
"scripts": {
"dev": "pnpm --filter web dev",
"build:web": "pnpm --filter web build",
"start:mobile": "pnpm --filter mobile start",
"android": "pnpm --filter mobile android",
"ios": "pnpm --filter mobile ios",
"typecheck": "pnpm -r typecheck"
},
"engines": {
"node": ">=18",
"pnpm": ">=8"
}
}
"#),
("pnpm-workspace.yaml", "packages:\n - 'apps/*'\n - 'packages/*'\n"),
("tsconfig.base.json", r#"{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020"],
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"exclude": ["node_modules", "dist", "build"]
}
"#),
(".gitignore", "node_modules/\ndist/\nbuild/\n.expo/\nandroid/\nios/\n*.log\n.env\n.env.*\n!.env.example\n"),
(".npmrc", "strict-peer-dependencies=false\nauto-install-peers=true\n"),
// ── packages/types ──────────────────────────────────────────────
("packages/types/package.json", r#"{
"name": "@repo/types",
"version": "0.0.1",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
}
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5"
}
}
"#),
("packages/types/tsconfig.json", r#"{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
"#),
("packages/types/src/index.ts", r#"// 在此定义跨平台共享的类型
export interface User {
id: string
name: string
email: string
}
export interface ApiResponse<T> {
data: T
error?: string
}
"#),
// ── packages/core ───────────────────────────────────────────────
("packages/core/package.json", r#"{
"name": "@repo/core",
"version": "0.0.1",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
}
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@repo/types": "workspace:*"
},
"devDependencies": {
"typescript": "^5"
}
}
"#),
("packages/core/tsconfig.json", r#"{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
"#),
("packages/core/src/index.ts", r#"// 在此导出跨平台共享的业务逻辑(纯 TS不依赖任何平台 API
export * from "./api"
"#),
("packages/core/src/api.ts", r#"import type { ApiResponse, User } from "@repo/types"
const BASE_URL = ""
export async function getUser(id: string): Promise<ApiResponse<User>> {
const res = await fetch(`${BASE_URL}/api/users/${id}`)
if (!res.ok) return { data: null as unknown as User, error: res.statusText }
const data = await res.json()
return { data }
}
"#),
// ── apps/web ────────────────────────────────────────────────────
("apps/web/package.json", r#"{
"name": "web",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@repo/core": "workspace:*",
"@repo/types": "workspace:*",
"react": "^19",
"react-dom": "^19",
"react-router": "^7"
},
"devDependencies": {
"@tailwindcss/vite": "^4",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^4",
"tailwindcss": "^4",
"typescript": "^5",
"vite": "^6"
}
}
"#),
("apps/web/tsconfig.json", r#"{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"noEmit": true,
"jsx": "react-jsx",
"paths": {
"@repo/core": ["../../packages/core/src/index.ts"],
"@repo/types": ["../../packages/types/src/index.ts"]
}
},
"include": ["src"]
}
"#),
("apps/web/vite.config.ts", r#"import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
});
"#),
("apps/web/index.html", r#"<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{project_name}}</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
"#),
("apps/web/src/main.tsx", r#"import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
"#),
("apps/web/src/index.css", "@import \"tailwindcss\";\n"),
("apps/web/src/App.tsx", r#"import { Routes, Route } from "react-router";
import Home from "./pages/Home";
export default function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
</Routes>
);
}
"#),
("apps/web/src/pages/Home.tsx", r#"export default function Home() {
return (
<main className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="text-center">
<h1 className="text-4xl font-bold text-gray-900">{{project_name}}</h1>
<p className="mt-2 text-gray-500">Web + Mobile Monorepo</p>
</div>
</main>
);
}
"#),
// ── apps/mobile ─────────────────────────────────────────────────
("apps/mobile/package.json", r#"{
"name": "mobile",
"version": "1.0.0",
"private": true,
"main": "expo-router/entry",
"scripts": {
"start": "expo start",
"android": "expo run:android",
"ios": "expo run:ios",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@repo/core": "workspace:*",
"@repo/types": "workspace:*",
"expo": "~52.0.0",
"expo-router": "~4.0.0",
"expo-status-bar": "~2.0.0",
"nativewind": "^4.0.1",
"react": "18.3.1",
"react-native": "0.76.3",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "~4.1.0",
"tailwindcss": "^3.4.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~18.3.12",
"typescript": "^5.4.5"
}
}
"#),
("apps/mobile/app.json", r##"{
"expo": {
"name": "{{project_name}}",
"slug": "{{project_name_kebab}}",
"version": "1.0.0",
"scheme": "{{project_name_kebab}}",
"userInterfaceStyle": "automatic",
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.example.{{project_name_snake}}"
},
"android": {
"adaptiveIcon": {
"backgroundColor": "#ffffff"
},
"package": "com.example.{{project_name_snake}}"
},
"plugins": ["expo-router"],
"experiments": {
"typedRoutes": true
}
}
}
"##),
("apps/mobile/tsconfig.json", r#"{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"paths": {
"@repo/core": ["../../packages/core/src/index.ts"],
"@repo/types": ["../../packages/types/src/index.ts"]
}
},
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
}
"#),
("apps/mobile/tailwind.config.js", r#"/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./app/**/*.{ts,tsx}"],
presets: [require("nativewind/preset")],
theme: {
extend: {},
},
plugins: [],
}
"#),
("apps/mobile/global.css", "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"),
("apps/mobile/babel.config.js", r#"module.exports = function (api) {
api.cache(true)
return {
presets: ['babel-preset-expo'],
plugins: ['nativewind/babel'],
}
}
"#),
("apps/mobile/metro.config.js", r#"const { getDefaultConfig } = require('expo/metro-config')
const { withNativeWind } = require('nativewind/metro')
const path = require('path')
const projectRoot = __dirname
const monorepoRoot = path.resolve(projectRoot, '../..')
const config = getDefaultConfig(projectRoot)
config.watchFolders = [monorepoRoot]
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(monorepoRoot, 'node_modules'),
]
module.exports = withNativeWind(config, { input: './global.css' })
"#),
("apps/mobile/expo-env.d.ts", "/// <reference types=\"expo/types\" />\n"),
("apps/mobile/app/_layout.tsx", r#"import '../global.css'
import { Stack } from 'expo-router'
import { StatusBar } from 'expo-status-bar'
export default function RootLayout() {
return (
<>
<StatusBar style="auto" />
<Stack>
<Stack.Screen name="index" options={{ title: '{{project_name}}' }} />
</Stack>
</>
)
}
"#),
("apps/mobile/app/index.tsx", r#"import { View, Text } from 'react-native'
export default function HomeScreen() {
return (
<View className="flex-1 items-center justify-center bg-gray-50">
<Text className="text-3xl font-bold text-gray-900">{{project_name}}</Text>
<Text className="mt-2 text-gray-500">Web + Mobile Monorepo</Text>
</View>
)
}
"#),
],
},
]; ];
for t in &templates { for t in &templates {