diff --git a/.gitignore b/.gitignore index 42edca5ce..32a4ed120 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,6 @@ PROMPT.md # Git-Worktree .git-worktree + +# Turborepo +.turbo diff --git a/AGENTS.md b/AGENTS.md index 265144a46..91a5fa839 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,28 +1,51 @@ -**短期核心目标:参考mpay代码,实现bioforestChain生态的基础功能:** -这是验收标准: - -1. 可以输入任意的密钥来创建bioforestChain生态的钱包:bioforestChain密钥的特殊之处在于它可以是任意字符串,而不是非得是助记词,因此需要一个textarea或者类似textarea的组件可以进行自由输入 -2. 可以进行bioforestChain生态的转账 -3. 可以进行bioforestChain生态的交易查询 -4. bioforestChain生态有多条链,可以通过设置自由配置 - - 需要内置一些现有的几条链的配置,参考mpay,默认配置存储在一个json文件中 - - 设置页面可以配置订阅url,默认是"默认",也就是本地这个json文件 - - json文件可以下载到多条链,所以我们还需要在这个设置页面提供一个"启用禁用"的功能 - - 还能绕过订阅,手动进行补充:最简单的方式就是手动输入json格式的配置,然后点击"添加"按钮,如果json配置是一个数组,那么就是多条数据、如果是一个obj,那么就是单条数据 - - 单条数据要考虑"版本号(x.y)"+"type",版本号是为了支持架构更新(通过不同的X-service),type是为了在一个版本中支持多种变体,如果代码更新补充了某种变体,那么小版本y就+1 -5. 在设置页面,切换语言的功能要能正常 -6. 钱包管理的一整个用户故事要完整闭环,所有涉及到bioforestChain的功能,都要正确移植到我们的service生态 -7. 我们的国际国家货币的service的封装也要完成 - - 默认的provider:Frankfurter(推荐,无需密钥) - 1. 实时/最新汇率:https://api.frankfurter.app/latest - 2. 查询特定货币(例如获取欧元兑美元、英镑的汇率):https://api.frankfurter.app/latest?from=EUR&to=USD,GBP - 3. 默认使用美元,不论用户界面使用什么语言 -8. 确保底部的tabs正常吗?会遮挡界面吗?视觉颜色正常吗?文字是否可见,暗色模式呢? -9. 确保与dweb相关的功能也完全迁移了,可以做到完全兼容mpay -10. 正确的可访问性: - 1. 背景和文字颜色没有导致低可读性。 - 2. 可以正确切换多国语言 - 3. 统一的色彩语言表达 +# KeyApp AI 开发入口 + +## AI 开发工作流 + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 开发闭环 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. 获取全部 pnpm agent │ +│ ↓ (规则 + 知识地图 + 当前任务列表) │ +│ 2. 领取任务 pnpm agent --claim 28 │ +│ ↓ (自动分配 + 显示 worktree 命令) │ +│ 3. 创建分支 git worktree add .git-worktree/issue-28 ... │ +│ ↓ │ +│ 4. 开发代码 (参考白皮书: pnpm agent --chapter <路径>) │ +│ ↓ │ +│ 5. 提交 PR gh pr create --body "Closes #28" │ +│ ↓ (PR 自动关联 Issue) │ +│ 6. 合并后 Issue 自动关闭,Project 状态更新 │ +│ │ +├─────────────────────────────────────────────────────────────────────┤ +│ 发现新问题? pnpm agent --create "问题描述" --category bug │ +│ (自动设置 roadmap 版本) │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +## 命令速查 + +```bash +pnpm agent # 规则 + 知识地图 + 当前任务 +pnpm agent --claim 28 # 领取任务 +pnpm agent --create "标题" --roadmap v1 # 创建任务(自动设置版本) +pnpm agent --chapter 03-架构篇/03-导航系统 # 查阅白皮书 +pnpm agent --stats # 查看进度统计 +``` + +## Roadmap + +GitHub Project:https://github.com/orgs/BioforestChain/projects/5 + +| 版本 | 说明 | +|-----|------| +| **V1** | 当前核心目标(bioforestChain 生态基础功能) | +| V2/V3 | 后续版本 | +| DRAFT | 草案/探索/待定 | + +别名:`CURRENT` -> V1, `NEXT` -> V2 @@ -61,21 +84,6 @@ BFM Pay 是一个现代化的多链钱包移动应用,是 mpay 的技术重构 | **白皮书** | `docs/white-book/` | 完整技术文档,涵盖产品、设计、架构、服务、组件、安全、测试、部署 | | 原始代码 | `/Users/kzf/Dev/bioforestChain/legacy-apps/apps/mpay/` | 参考实现细节 | -### 白皮书章节速查 - -| 需求类型 | 参考章节 | -|---------|---------| -| 产品需求、用户故事 | `docs/white-book/01-产品篇/` | -| 交互设计、视觉规范 | `docs/white-book/02-设计篇/` | -| 技术架构、状态管理 | `docs/white-book/03-架构篇/` | -| 服务接口规范 | `docs/white-book/04-服务篇/` | -| 组件规范 | `docs/white-book/05-组件篇/` | -| 安全规范 | `docs/white-book/06-安全篇/` | -| 国际化 | `docs/white-book/07-国际化篇/` | -| 测试策略 | `docs/white-book/08-测试篇/` | -| 部署发布 | `docs/white-book/09-部署篇/` | -| mpay迁移指南 | `docs/white-book/附录/C-mpay迁移指南/` | - ## 开发原则 ### 1. 文档优先级 @@ -108,46 +116,6 @@ BFM Pay 是一个现代化的多链钱包移动应用,是 mpay 的技术重构 - Zod 4.x + i18next - Storybook 10.x + Vitest 4.x -## 开发阶段 - -### Phase 1: 基础设施 ✅ 已完成 - -- [x] 项目初始化 (Vite + React 19) -- [x] Storybook 10.x 组件开发环境 -- [x] Vitest 4.x 测试环境 -- [x] shadcn/ui 组件库集成 -- [x] 基础 UI 组件开发与测试 (363 tests passing) - -### Phase 1.5: 核心交互组件(当前) - -**活跃 Change**: `add-core-interaction-components` - -- [ ] WalletSelector 钱包选择器 -- [ ] ChainAddressSelector 链地址选择器 -- [ ] TransferConfirmSheet 转账确认弹窗 -- [ ] PasswordConfirmSheet 密码确认弹窗 -- [ ] MnemonicConfirm 助记词确认 -- [ ] TransactionStatus, FeeDisplay, TokenIcon - -### Phase 2: 核心功能 - -- [ ] Stackflow 导航系统 -- [ ] TanStack Store 状态管理 -- [ ] 钱包创建/导入功能 -- [ ] 资产展示功能 - -### Phase 3: 交易功能 - -- [ ] 转账功能 -- [ ] 收款功能 -- [ ] 交易历史 - -### Phase 4: 高级功能 - -- [ ] 质押功能 -- [ ] DWEB/Plaoc 集成 -- [ ] 多语言支持 - ## 工作流程 ### 0. Git Worktree 工作环境(重要) @@ -221,17 +189,6 @@ git worktree remove .git-worktree/ - 回到主目录更新 main:`cd ../.. && git pull origin main` - 清理 worktree:`git worktree remove .git-worktree/` -## mpay 关键文件速查 - -详见 `docs/white-book/附录/C-mpay迁移指南/`,常用参考: - -| 功能 | mpay 路径 | -| --------- | ------------------------------------------------- | -| 首页 | `pages/home/home.component.ts` | -| 转账 | `pages/mnemonic/pages/home-transfer/` | -| DWEB 授权 | `pages/authorize/pages/signature/` | -| 钱包存储 | `services/expansion-tools/wallet-data-stroage.ts` | - ## 注意事项 - 所有组件必须有 Storybook story diff --git a/CLAUDE.md b/CLAUDE.md index f7a3361cd..5440eda0b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,45 +1,19 @@ -## ⚠️ 必读:开始开发前 - -**在进行任何 UI 开发前,请先阅读白皮书必读章节:** +## ⚠️ 开始开发前 ```bash -cat docs/white-book/00-必读/index.md -``` +# 1. 获取索引(最佳实践 + 知识地图) +bun scripts/agent-readme.ts -这个文件包含了避免常见错误的关键指引,特别是关于: -- Stackflow 导航系统的正确使用 -- 弹出层(Sheet/Modal)的正确实现方式 -- 为什么不能使用 Radix Dialog 或 `position: fixed` - ---- +# 2. 查看当前任务 +bun scripts/agent-readme.ts --roadmap current -**短期核心目标:参考mpay代码,实现bioforestChain生态的基础功能:** -这是验收标准: +# 3. 查阅白皮书必读章节 +bun scripts/agent-readme.ts --chapter 00-必读 +``` -1. 可以输入任意的密钥来创建bioforestChain生态的钱包:bioforestChain密钥的特殊之处在于它可以是任意字符串,而不是非得是助记词,因此需要一个textarea或者类似textarea的组件可以进行自由输入 -2. 可以进行bioforestChain生态的转账 -3. 可以进行bioforestChain生态的交易查询 -4. bioforestChain生态有多条链,可以通过设置自由配置 - - 需要内置一些现有的几条链的配置,参考mpay,默认配置存储在一个json文件中 - - 设置页面可以配置订阅url,默认是"默认",也就是本地这个json文件 - - json文件可以下载到多条链,所以我们还需要在这个设置页面提供一个"启用禁用"的功能 - - 还能绕过订阅,手动进行补充:最简单的方式就是手动输入json格式的配置,然后点击"添加"按钮,如果json配置是一个数组,那么就是多条数据、如果是一个obj,那么就是单条数据 - - 单条数据要考虑"版本号(x.y)"+"type",版本号是为了支持架构更新(通过不同的X-service),type是为了在一个版本中支持多种变体,如果代码更新补充了某种变体,那么小版本y就+1 -5. 在设置页面,切换语言的功能要能正常 -6. 钱包管理的一整个用户故事要完整闭环,所有涉及到bioforestChain的功能,都要正确移植到我们的service生态 -7. 我们的国际国家货币的service的封装也要完成 - - 默认的provider:Frankfurter(推荐,无需密钥) - 1. 实时/最新汇率:https://api.frankfurter.app/latest - 2. 查询特定货币(例如获取欧元兑美元、英镑的汇率):https://api.frankfurter.app/latest?from=EUR&to=USD,GBP - 3. 默认使用美元,不论用户界面使用什么语言 -8. 确保底部的tabs正常吗?会遮挡界面吗?视觉颜色正常吗?文字是否可见,暗色模式呢? -9. 确保与dweb相关的功能也完全迁移了,可以做到完全兼容mpay -10. 正确的可访问性: - 1. 背景和文字颜色没有导致低可读性。 - 2. 可以正确切换多国语言 - 3. 统一的色彩语言表达 +完整工作流和命令速查见 [AGENTS.md](./AGENTS.md) --- @@ -67,59 +41,16 @@ Keep this managed block so 'openspec update' can refresh the instructions. --- -# BFM Pay (KeyApp) - AI 开发指南 - -## 项目概述 - -BFM Pay 是一个现代化的多链钱包移动应用,是 mpay 的技术重构版本。保留原有功能的同时,在交互、视觉、代码质量和项目管理上进行专业提升。 +## 核心文档 -## 核心文档(按优先级) +| 文档 | 路径 | 用途 | +|-----|------|------| +| **白皮书** | `docs/white-book/` | 完整技术文档 | +| **mpay 原始代码** | `/Users/kzf/Dev/bioforestChain/legacy-apps/apps/mpay/` | 参考实现 | -| 文档 | 路径 | 用途 | -| -------- | ------------------------------------------------------ | --------------------------------------- | -| **白皮书** | `docs/white-book/` | 完整技术文档,涵盖产品、设计、架构、服务、组件、安全、测试、部署 | -| 原始代码 | `/Users/kzf/Dev/bioforestChain/legacy-apps/apps/mpay/` | 参考实现细节 | +**优先级**: 白皮书 > mpay 原始代码 -### 白皮书章节速查 - -| 需求类型 | 参考章节 | -|---------|---------| -| 产品需求、用户故事 | `docs/white-book/01-产品篇/` | -| 交互设计、视觉规范 | `docs/white-book/02-设计篇/` | -| 技术架构、状态管理 | `docs/white-book/03-架构篇/` | -| 服务接口规范 | `docs/white-book/04-服务篇/` | -| 组件规范 | `docs/white-book/05-组件篇/` | -| 安全规范 | `docs/white-book/06-安全篇/` | -| 国际化 | `docs/white-book/07-国际化篇/` | -| 测试策略 | `docs/white-book/08-测试篇/` | -| 部署发布 | `docs/white-book/09-部署篇/` | -| mpay迁移指南 | `docs/white-book/附录/C-mpay迁移指南/` | - -## 开发原则 - -### 1. 文档优先级 - -``` -白皮书 (docs/white-book/) > mpay 原始代码 -``` - -当文档与原始代码冲突时,以白皮书为准。 - -### 2. 开发工作流(重要) - -**任何开发任务开始前,必须按以下顺序执行:** - -1. **更新白皮书** - 如果需要新增功能或修改现有设计,先更新对应的白皮书章节 -2. **创建 openspec change** - 基于白皮书内容,创建具体的变更提案 -3. **开始编码** - 根据 openspec 的 tasks.md 执行开发 - -### 3. 原始代码参考原则 - -- **参考,而非复制**:理解业务逻辑,不要盲目复制 Angular 代码 -- **质疑原始实现**:mpay 可能有 Bug、过时模式或未完成功能 -- **现代化改进**:使用 TypeScript 类型安全、TanStack 生态、函数组件 - -### 4. 技术栈 +## 技术栈 - React 19 + Vite 7 - Stackflow (导航) + TanStack (Query + Store + Form) @@ -127,134 +58,28 @@ BFM Pay 是一个现代化的多链钱包移动应用,是 mpay 的技术重构 - Zod 4.x + i18next - Storybook 10.x + Vitest 4.x -## 开发阶段 - -### Phase 1: 基础设施 ✅ 已完成 - -- [x] 项目初始化 (Vite + React 19) -- [x] Storybook 10.x 组件开发环境 -- [x] Vitest 4.x 测试环境 -- [x] shadcn/ui 组件库集成 -- [x] 基础 UI 组件开发与测试 (363 tests passing) - -### Phase 1.5: 核心交互组件(当前) - -**活跃 Change**: `add-core-interaction-components` - -- [ ] WalletSelector 钱包选择器 -- [ ] ChainAddressSelector 链地址选择器 -- [ ] TransferConfirmSheet 转账确认弹窗 -- [ ] PasswordConfirmSheet 密码确认弹窗 -- [ ] MnemonicConfirm 助记词确认 -- [ ] TransactionStatus, FeeDisplay, TokenIcon - -### Phase 2: 核心功能 - -- [ ] Stackflow 导航系统 -- [ ] TanStack Store 状态管理 -- [ ] 钱包创建/导入功能 -- [ ] 资产展示功能 - -### Phase 3: 交易功能 - -- [ ] 转账功能 -- [ ] 收款功能 -- [ ] 交易历史 - -### Phase 4: 高级功能 - -- [ ] 质押功能 -- [ ] DWEB/Plaoc 集成 -- [ ] 多语言支持 - -## 工作流程 +## 开发原则 -### 0. Git Worktree 工作环境(重要) +- **参考而非复制** mpay 代码 +- **质疑原始实现**(可能有 Bug 或过时模式) +- **现代化改进**(TypeScript 类型安全、函数组件) -**所有编码工作必须在 `.git-worktree/` 目录下的独立 worktree 中进行。** 主目录始终保持在 main 分支上,保持干净。 +## Git Worktree 工作流 -**完整工作流程示例:** +所有编码工作必须在 `.git-worktree/` 目录下进行: ```bash -# 1. 在主目录创建 worktree(以功能名命名分支) -cd /path/to/KeyApp -git worktree add .git-worktree/ -b - -# 2. 进入 worktree 目录进行开发 -cd .git-worktree/ -pnpm install # 首次需要安装依赖 - -# 3. 开发完成后,在 worktree 中提交代码 -git add -A -git commit -m "feat/fix: 描述" - -# 4. 推送分支并创建 PR(使用 gh 命令) -git push -u origin -gh pr create --title "PR 标题" --body "PR 描述" --base main - -# 5. 合并 PR 到 main(CI 检查通过后) -gh pr merge --squash --delete-branch - -# 6. 回到主目录,更新 main 分支 -cd ../.. # 回到项目根目录 -git pull origin main - -# 7. 清理 worktree -git worktree remove .git-worktree/ +git worktree add .git-worktree/issue-28 -b feat/issue-28 +cd .git-worktree/issue-28 && pnpm install +# 开发... +gh pr create --body "Closes #28" ``` -**Worktree 优势**: -- 隔离开发环境,避免相互干扰 -- 可同时进行多个功能开发 -- 主目录保持干净,便于代码审查 -- main 分支受保护,必须通过 PR 合并 - -**注意事项**: -- 主目录(项目根目录)始终保持在 main 分支,不要在主目录直接修改代码 -- 所有开发工作都在 `.git-worktree/` 子目录中进行 -- 使用 `gh` CLI 工具来创建和合并 PR -- PR 合并后记得清理对应的 worktree - -### 1. 开始新功能前 - - 先更新 `docs/white-book/` 相关章节(如需要) - - 阅读 `openspec/` 下的相关 spec - - 检查 `openspec/changes/` 是否有进行中的变更 - - 创建 change proposal(如适用) - - **在 `.git-worktree/` 下创建新的 worktree** - -### 2. 开发时 - - 先写 Storybook story - - 再写 Vitest 测试 - - 最后实现组件 - - 确定所有测试都通过了,必要时需要更新e2e截图(必须查看截图内容确保符合预期) - -### 3. 完成后 - - 确保所有测试通过(`pnpm test`) - - 确保类型检查通过(`pnpm typecheck`) - - 更新 tasks.md 状态 - - 在 worktree 中提交代码 - - 推送分支:`git push -u origin ` - - 创建 PR:`gh pr create --title "标题" --body "描述" --base main` - - 等待 CI 检查通过 - - 合并 PR:`gh pr merge --squash --delete-branch` - - 回到主目录更新 main:`cd ../.. && git pull origin main` - - 清理 worktree:`git worktree remove .git-worktree/` - -## mpay 关键文件速查 - -详见 `docs/white-book/附录/C-mpay迁移指南/`,常用参考: - -| 功能 | mpay 路径 | -| --------- | ------------------------------------------------- | -| 首页 | `pages/home/home.component.ts` | -| 转账 | `pages/mnemonic/pages/home-transfer/` | -| DWEB 授权 | `pages/authorize/pages/signature/` | -| 钱包存储 | `services/expansion-tools/wallet-data-stroage.ts` | - ## 注意事项 - 所有组件必须有 Storybook story - 所有业务逻辑必须有单元测试 - 使用 TypeScript 严格模式 -- 遵循白皮书中的代码规范 +- PR 描述使用 `Closes #issue编号` 自动关联任务 + diff --git a/package.json b/package.json index b2af5c243..3d1dc3414 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "gen:stable": "bun scripts/gen-stable.ts", "i18n:extract": "bun scripts/i18n-extract.ts", "i18n:check": "bun scripts/i18n-check.ts", - "i18n:validate": "bun scripts/i18n-validate.ts" + "i18n:validate": "bun scripts/i18n-validate.ts", + "agent": "bun scripts/agent-readme.ts" }, "dependencies": { "@base-ui/react": "^1.0.0", diff --git a/scripts/agent-readme.ts b/scripts/agent-readme.ts new file mode 100644 index 000000000..582b166ea --- /dev/null +++ b/scripts/agent-readme.ts @@ -0,0 +1,570 @@ +#!/usr/bin/env bun +/** + * AI Agent 索引脚本 + * + * 为 AI 提供项目索引:最佳实践(纠正直觉)+ 知识地图(按需查阅)+ Roadmap(任务追踪) + * + * Usage: + * bun scripts/agent-readme.ts # 输出索引(最佳实践 + 知识地图) + * bun scripts/agent-readme.ts --roadmap # 查看 GitHub Project 任务列表 + * bun scripts/agent-readme.ts --chapter # 输出指定章节内容 + * + * Examples: + * bun scripts/agent-readme.ts --chapter 03-架构篇/03-导航系统 + * bun scripts/agent-readme.ts --roadmap + */ + +import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs' +import { join, resolve, relative } from 'node:path' +import { execSync } from 'node:child_process' + +const ROOT = resolve(import.meta.dirname, '..') +const WHITE_BOOK_DIR = join(ROOT, 'docs/white-book') + +const PROJECT_NUMBER = 5 +const PROJECT_OWNER = 'BioforestChain' + +// 版本别名,CURRENT 指向当前活跃版本 +const RELEASE_ALIASES: Record = { + CURRENT: 'V1', + NEXT: 'V2', +} + +const colors = { + reset: '\x1b[0m', + bold: '\x1b[1m', + dim: '\x1b[2m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + cyan: '\x1b[36m', +} + +const log = { + title: (msg: string) => console.log(`\n${colors.bold}${colors.cyan}${'='.repeat(60)}${colors.reset}`), + section: (msg: string) => console.log(`\n${colors.bold}${colors.green}## ${msg}${colors.reset}\n`), + subsection: (msg: string) => console.log(`\n${colors.yellow}### ${msg}${colors.reset}\n`), + info: (msg: string) => console.log(`${colors.dim}${msg}${colors.reset}`), +} + +interface ChapterInfo { + name: string + path: string + indexPath?: string + subChapters: ChapterInfo[] +} + +function getChapters(dir: string): ChapterInfo[] { + if (!existsSync(dir)) return [] + + const items = readdirSync(dir) + .filter(item => !item.startsWith('.')) + .sort() + + const chapters: ChapterInfo[] = [] + + for (const item of items) { + const fullPath = join(dir, item) + const stat = statSync(fullPath) + + if (stat.isDirectory()) { + const indexPath = join(fullPath, 'index.md') + const subChapters = getChapters(fullPath) + + chapters.push({ + name: item, + path: fullPath, + indexPath: existsSync(indexPath) ? indexPath : undefined, + subChapters, + }) + } else if (item.endsWith('.md') && item !== 'index.md') { + chapters.push({ + name: item.replace('.md', ''), + path: fullPath, + subChapters: [], + }) + } + } + + return chapters +} + +function printToc(chapters: ChapterInfo[], indent = 0): void { + for (const chapter of chapters) { + const prefix = ' '.repeat(indent) + const relPath = relative(WHITE_BOOK_DIR, chapter.path) + console.log(`${prefix}- ${chapter.name} (${relPath})`) + if (chapter.subChapters.length > 0) { + printToc(chapter.subChapters, indent + 1) + } + } +} + +function readMarkdownFile(filePath: string): string { + if (!existsSync(filePath)) return '' + return readFileSync(filePath, 'utf-8') +} + +function printChapterContent(chapterPath: string): void { + const fullPath = join(WHITE_BOOK_DIR, chapterPath) + + if (!existsSync(fullPath)) { + console.error(`${colors.red}错误: 章节不存在: ${chapterPath}${colors.reset}`) + process.exit(1) + } + + const stat = statSync(fullPath) + + if (stat.isDirectory()) { + const indexPath = join(fullPath, 'index.md') + if (existsSync(indexPath)) { + console.log(readMarkdownFile(indexPath)) + } + + // 输出子章节 + const subFiles = readdirSync(fullPath) + .filter(f => f.endsWith('.md') && f !== 'index.md') + .sort() + + for (const subFile of subFiles) { + log.subsection(subFile.replace('.md', '')) + console.log(readMarkdownFile(join(fullPath, subFile))) + } + } else if (fullPath.endsWith('.md')) { + console.log(readMarkdownFile(fullPath)) + } +} + +interface RoadmapItem { + title: string + status: string + release: string | null + issueNumber: number | null + assignees: string[] +} + +function fetchRoadmap(): RoadmapItem[] { + try { + const output = execSync( + `gh project item-list ${PROJECT_NUMBER} --owner ${PROJECT_OWNER} --format json`, + { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] } + ) + const data = JSON.parse(output) + return data.items.map((item: { + title: string + status: string + release?: string + content?: { number?: number } + assignees?: string[] + }) => ({ + title: item.title, + status: item.status, + release: item.release || null, + issueNumber: item.content?.number || null, + assignees: item.assignees || [], + })) + } catch { + return [] + } +} + +function resolveRelease(input: string): string { + const upper = input.toUpperCase() + return RELEASE_ALIASES[upper] || upper +} + +function printRoadmap(releaseFilter?: string): void { + const allItems = fetchRoadmap() + if (allItems.length === 0) { + console.log('无法获取 Roadmap,请确保已配置 gh auth (需要 project scope)') + return + } + + // 解析别名 + const resolvedFilter = releaseFilter ? resolveRelease(releaseFilter) : undefined + const displayName = releaseFilter?.toUpperCase() + const isAlias = releaseFilter && resolvedFilter !== releaseFilter.toUpperCase() + + const items = resolvedFilter + ? allItems.filter(i => i.release?.toUpperCase() === resolvedFilter) + : allItems + + const title = resolvedFilter + ? `KeyApp Roadmap - ${displayName}${isAlias ? ` (${resolvedFilter})` : ''}` + : 'KeyApp Roadmap' + console.log(`# ${title}\n`) + console.log(`> GitHub Project: https://github.com/orgs/${PROJECT_OWNER}/projects/${PROJECT_NUMBER}\n`) + + if (resolvedFilter && items.length === 0) { + console.log(`没有找到 ${displayName} 版本的任务。\n`) + console.log('可用版本: V1, V2, V3, DRAFT, CURRENT, NEXT') + return + } + + // 按版本分组(如果没有过滤) + if (!releaseFilter) { + const releases = ['V1', 'V2', 'V3', 'DRAFT', null] + for (const release of releases) { + const releaseItems = allItems.filter(i => + release === null ? !i.release : i.release === release + ) + if (releaseItems.length === 0) continue + + console.log(`## ${release || '未分配'}\n`) + printStatusGroups(releaseItems) + } + } else { + printStatusGroups(items) + } +} + +function printStatusGroups(items: RoadmapItem[], showDetails = false): void { + const grouped = { + 'In Progress': items.filter(i => i.status === 'In Progress'), + Todo: items.filter(i => i.status === 'Todo'), + Done: items.filter(i => i.status === 'Done'), + } + + for (const [status, tasks] of Object.entries(grouped)) { + if (tasks.length === 0) continue + for (const task of tasks) { + const icon = status === 'Done' ? '✅' : status === 'In Progress' ? '🔄' : '⬚' + const issueRef = task.issueNumber ? `#${task.issueNumber}` : '' + const assignee = task.assignees.length > 0 ? ` @${task.assignees.join(', @')}` : '' + + if (showDetails || task.issueNumber) { + console.log(`- ${icon} ${issueRef} ${task.title}${assignee}`) + } else { + console.log(`- ${icon} ${task.title}`) + } + } + } + console.log() +} + +function printBestPractices(): void { + console.log(` +# 最佳实践 + +❌ Radix Dialog / position:fixed → ✅ Stackflow BottomSheet/Modal +❌ React Router → ✅ Stackflow push/pop/replace +❌ 复制 mpay 代码 → ✅ 理解后用 React/TS 重写 +❌ 随意创建 store → ✅ 遵循 stores/ 现有模式 +❌ 明文选择器 → ✅ data-testid +❌ 安装新 UI 库 → ✅ shadcn/ui(已集成) +❌ 新建 CSS → ✅ Tailwind CSS + +详见: pnpm agent --chapter 00-必读 +`) +} + +function printKnowledgeMap(): void { + console.log(` +# 知识地图 + +代码: + src/stackflow/ → 导航配置、Activity (添加页面/Sheet/Modal) + src/services/ → 服务层、Adapter 模式 + src/stores/ → TanStack Store 状态管理 + src/components/ → UI 组件 + src/i18n/ → 国际化 + +白皮书 (pnpm agent --chapter <路径>): + 03-架构篇/03-导航系统/ → 页面跳转、Tab + 04-服务篇/01-服务架构/ → 服务分层、Adapter + 05-组件篇/01-基础组件/ → Sheet、Dialog + 04-服务篇/08-钱包数据存储/ → 钱包数据 + 07-国际化篇/ → 多语言 + 08-测试篇/03-Playwright配置/e2e-best-practices.md → E2E 测试 + 附录/C-mpay迁移指南/ → mpay 参考 + +外部文档: + Stackflow: https://stackflow.so/llms-full.txt + shadcn/ui: https://ui.shadcn.com/docs + TanStack: https://tanstack.com/query/latest + +mpay 原始代码: /Users/kzf/Dev/bioforestChain/legacy-apps/apps/mpay/ + pages/home/home.component.ts → 首页 + pages/mnemonic/pages/home-transfer/ → 转账 + pages/authorize/pages/signature/ → DWEB 授权 + services/expansion-tools/wallet-data-stroage.ts → 钱包存储 +`) +} + +function claimIssue(issueNumber: string): void { + try { + // 设置 assignee 为当前用户 + execSync( + `gh issue edit ${issueNumber} --add-assignee @me`, + { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], cwd: ROOT } + ) + console.log(`✅ 已领取 Issue #${issueNumber}`) + console.log(`\n下一步:`) + console.log(` 1. git worktree add .git-worktree/issue-${issueNumber} -b feat/issue-${issueNumber}`) + console.log(` 2. cd .git-worktree/issue-${issueNumber} && pnpm install`) + console.log(` 3. 开始开发...`) + console.log(` 4. PR 描述中使用 "Closes #${issueNumber}" 自动关联`) + } catch (e) { + console.error(`❌ 领取失败: ${e}`) + } +} + +function completeIssue(issueNumber: string): void { + try { + execSync( + `gh issue close ${issueNumber} --reason completed`, + { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], cwd: ROOT } + ) + console.log(`✅ 已完成 Issue #${issueNumber}`) + } catch (e) { + console.error(`❌ 完成失败: ${e}`) + } +} + +interface CreateIssueOptions { + title: string + body?: string + roadmap?: string // V1, V2, DRAFT + category?: string // feature, bug, refactor, docs +} + +function createIssue(options: CreateIssueOptions): void { + const { title, body = '', roadmap = 'DRAFT', category = 'feature' } = options + + // 确定 label + const labelMap: Record = { + feature: 'enhancement', + bug: 'bug', + refactor: 'refactor', + docs: 'documentation', + test: 'test', + } + const label = labelMap[category.toLowerCase()] || 'enhancement' + + // 构建 body + const fullBody = `${body} + +**Roadmap**: ${roadmap.toUpperCase()} +**Category**: ${category.charAt(0).toUpperCase() + category.slice(1)}` + + try { + const output = execSync( + `gh issue create --title "${title.replace(/"/g, '\\"')}" --body "${fullBody.replace(/"/g, '\\"')}" --label "${label}" --project "KeyApp Roadmap"`, + { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], cwd: ROOT } + ) + const issueUrl = output.trim() + const issueNumber = issueUrl.split('/').pop() + + console.log(`✅ 已创建 Issue ${issueUrl}`) + + // 自动设置 Release 字段 + try { + const releaseOptionMap: Record = { + V1: '9359aa58', + V2: '6a3e495d', + V3: '7e413227', + DRAFT: 'd75e4b65', + } + const optionId = releaseOptionMap[roadmap.toUpperCase()] + if (optionId) { + // 获取 project item ID + const itemsJson = execSync( + `gh project item-list ${PROJECT_NUMBER} --owner ${PROJECT_OWNER} --format json`, + { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] } + ) + const items = JSON.parse(itemsJson) + const item = items.items.find((i: { content?: { number?: number } }) => + i.content?.number === parseInt(issueNumber || '0') + ) + if (item) { + execSync( + `gh project item-edit --project-id PVT_kwDOBJVmF84BLGhY --id "${item.id}" --field-id PVTSSF_lADOBJVmF84BLGhYzg6xpp8 --single-select-option-id ${optionId}`, + { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] } + ) + console.log(`✅ 已设置 Release 为 ${roadmap.toUpperCase()}`) + } + } + } catch { + console.log(`⚠️ Release 字段设置失败,请手动设置`) + } + + console.log(`\n下一步:`) + console.log(` pnpm agent --claim ${issueNumber} # 领取任务`) + } catch (e) { + console.error(`❌ 创建失败: ${e}`) + } +} + +function printStats(): void { + const items = fetchRoadmap() + if (items.length === 0) { + console.log('无法获取统计数据') + return + } + + const releases = ['V1', 'V2', 'V3', 'DRAFT'] + + console.log('# 进度统计\n') + + for (const release of releases) { + const releaseItems = items.filter(i => i.release === release) + if (releaseItems.length === 0) continue + + const todo = releaseItems.filter(i => i.status === 'Todo').length + const inProgress = releaseItems.filter(i => i.status === 'In Progress').length + const done = releaseItems.filter(i => i.status === 'Done').length + const total = releaseItems.length + const progress = Math.round((done / total) * 100) + const bar = '█'.repeat(Math.floor(progress / 10)) + '░'.repeat(10 - Math.floor(progress / 10)) + + console.log(`${release}: ${bar} ${progress}% (${done}/${total} done, ${inProgress} wip, ${todo} todo)`) + } + console.log() +} + +function printWorkflow(): void { + console.log(` +# 工作流 + +pnpm agent --claim <#> 领取任务 (自动分配+worktree指引) +pnpm agent --create "x" 创建任务 (--category bug --roadmap v1) +pnpm agent --chapter 查阅白皮书 +pnpm agent --stats 进度统计 + +PR 规则: 描述中使用 Closes #issue编号 自动关联 +`) +} + +function printCurrentTasks(): void { + const items = fetchRoadmap() + const currentItems = items.filter(i => { + const release = i.release?.toUpperCase() + return release === resolveRelease('CURRENT') + }) + + if (currentItems.length === 0) return + + const inProgress = currentItems.filter(i => i.status === 'In Progress') + const todo = currentItems.filter(i => i.status === 'Todo') + const done = currentItems.filter(i => i.status === 'Done') + + console.log(` +# 当前任务 (${resolveRelease('CURRENT')}) ${done.length}/${currentItems.length} done +`) + + if (inProgress.length > 0) { + console.log('进行中:') + for (const t of inProgress) { + console.log(` #${t.issueNumber} ${t.title}${t.assignees.length ? ` @${t.assignees.join(',')}` : ''}`) + } + } + + if (todo.length > 0) { + console.log('待领取:') + for (const t of todo) { + console.log(` #${t.issueNumber} ${t.title}`) + } + } + console.log() +} + +function printIndex(): void { + console.log(`# KeyApp AI 开发索引 +`) + + printBestPractices() + printKnowledgeMap() + printCurrentTasks() + printWorkflow() +} + +function main(): void { + const args = process.argv.slice(2) + + if (args.includes('--help') || args.includes('-h')) { + console.log(` +AI Agent 索引脚本 + +Usage: + bun scripts/agent-readme.ts # 输出索引(最佳实践 + 知识地图) + bun scripts/agent-readme.ts --roadmap # 查看所有版本任务列表 + bun scripts/agent-readme.ts --roadmap current # 查看当前版本任务 + bun scripts/agent-readme.ts --claim # 领取任务(分配给自己) + bun scripts/agent-readme.ts --done # 完成任务(关闭 Issue) + bun scripts/agent-readme.ts --create # 创建新任务(自动设置版本) + [--body <描述>] [--roadmap v1|v2|draft] [--category feature|bug|refactor] + bun scripts/agent-readme.ts --stats # 查看进度统计 + bun scripts/agent-readme.ts --toc # 白皮书目录结构 + bun scripts/agent-readme.ts --chapter <name> # 查阅白皮书章节 + +Aliases: CURRENT -> V1, NEXT -> V2 + +Examples: + bun scripts/agent-readme.ts --roadmap current + bun scripts/agent-readme.ts --claim 28 + bun scripts/agent-readme.ts --create "修复某个问题" --category bug --roadmap v1 + bun scripts/agent-readme.ts --create "探索:新功能想法" --roadmap draft +`) + process.exit(0) + } + + const roadmapIndex = args.indexOf('--roadmap') + if (roadmapIndex !== -1) { + const releaseFilter = args[roadmapIndex + 1] + printRoadmap(releaseFilter) + process.exit(0) + } + + const claimIndex = args.indexOf('--claim') + if (claimIndex !== -1 && args[claimIndex + 1]) { + claimIssue(args[claimIndex + 1]) + process.exit(0) + } + + const doneIndex = args.indexOf('--done') + if (doneIndex !== -1 && args[doneIndex + 1]) { + completeIssue(args[doneIndex + 1]) + process.exit(0) + } + + if (args.includes('--stats')) { + printStats() + process.exit(0) + } + + const createIndex = args.indexOf('--create') + if (createIndex !== -1 && args[createIndex + 1]) { + const title = args[createIndex + 1] + const bodyIndex = args.indexOf('--body') + const roadmapIndex2 = args.indexOf('--roadmap', createIndex) + const categoryIndex = args.indexOf('--category') + + createIssue({ + title, + body: bodyIndex !== -1 ? args[bodyIndex + 1] : undefined, + roadmap: roadmapIndex2 !== -1 ? args[roadmapIndex2 + 1] : 'DRAFT', + category: categoryIndex !== -1 ? args[categoryIndex + 1] : 'feature', + }) + process.exit(0) + } + + if (args.includes('--toc')) { + console.log('# 白皮书目录结构\n') + const chapters = getChapters(WHITE_BOOK_DIR) + printToc(chapters) + process.exit(0) + } + + const chapterIndex = args.indexOf('--chapter') + if (chapterIndex !== -1 && args[chapterIndex + 1]) { + const chapterName = args[chapterIndex + 1] + log.section(`章节: ${chapterName}`) + printChapterContent(chapterName) + process.exit(0) + } + + printIndex() +} + +main()