accio-context 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +492 -0
- package/bin/accio-context.js +8 -0
- package/package.json +37 -0
- package/src/cli.js +331 -0
- package/src/install.js +192 -0
- package/src/inventory.js +94 -0
- package/src/repo.js +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# Accio 上下文知识库
|
|
2
|
+
|
|
3
|
+
一个全面的开发知识库和指南仓库,专为Accio项目设计,旨在确保整个开发团队在代码质量、架构模式和开发实践方面的一致性。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 项目概述
|
|
8
|
+
|
|
9
|
+
本知识库作为Accio应用程序开发标准、架构指南和最佳实践的中心仓库。它提供结构化文档,涵盖从基础继承模式到高级缓存策略和分析实现的各个方面。
|
|
10
|
+
|
|
11
|
+
**架构模式**: 文档驱动开发,采用模块化规范结构
|
|
12
|
+
**核心技术**: Android SDK、iOS SDK、Web前端技术栈、Java后端技术栈、MVP/PART架构、MTOP网络框架、FastJSON、DX框架
|
|
13
|
+
**主要特性**: 全栈开发规范、分层架构规范、代码风格强制执行、记忆驱动学习系统
|
|
14
|
+
|
|
15
|
+
## User Growth Agent System
|
|
16
|
+
|
|
17
|
+
The repository now includes a sophisticated **User Growth Agent System** located in `accio-agent-task/user_growth/`, which implements a multi-agent architecture for intelligent user growth services.
|
|
18
|
+
|
|
19
|
+
### Key Features
|
|
20
|
+
- 🤖 **Intelligent Agent Routing**: LLM-based intent recognition and agent selection
|
|
21
|
+
- 🔄 **Multi-modal Processing**: Unified handling of text and image inputs
|
|
22
|
+
- ⚡ **Async Streaming**: Real-time SSE event streaming for immediate response
|
|
23
|
+
- 📊 **Full-chain Monitoring**: Complete execution tracking and performance analysis
|
|
24
|
+
- 🧵 **Thread Pool Management**: Custom thread pool executor for high concurrency
|
|
25
|
+
|
|
26
|
+
### Architecture Documentation
|
|
27
|
+
- **Architecture Diagram**: [UserGrowth_Architecture.drawio](./UserGrowth_Architecture.drawio) - Complete system architecture visualization
|
|
28
|
+
- **Technical Documentation**: [UserGrowth_Architecture_Documentation.md](./UserGrowth_Architecture_Documentation.md) - Comprehensive technical specifications
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 目录结构
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
accio-context/
|
|
36
|
+
├── rules/ # 开发规范和指南
|
|
37
|
+
│ ├── android/ # Android开发规范
|
|
38
|
+
│ │ └── .cursor/
|
|
39
|
+
│ │ └── rules/
|
|
40
|
+
│ │ ├── 00-page-development-index.mdc
|
|
41
|
+
│ │ ├── 01-base-inheritance.mdc
|
|
42
|
+
│ │ ├── 02-mvp-architecture.mdc
|
|
43
|
+
│ │ ├── 03-part-architecture.mdc
|
|
44
|
+
│ │ ├── 04-network-requests.mdc
|
|
45
|
+
│ │ ├── 05-cache-strategy.mdc
|
|
46
|
+
│ │ ├── 06-analytics-tracking.mdc
|
|
47
|
+
│ │ ├── 07-dx-framework.mdc
|
|
48
|
+
│ │ ├── 08-image-loading.mdc
|
|
49
|
+
│ │ ├── 09-recyclerview-usage.mdc
|
|
50
|
+
│ │ ├── 10-router.mdc
|
|
51
|
+
│ │ ├── 11-dx-default-template-update.mdc
|
|
52
|
+
│ │ ├── accio-android.mdc
|
|
53
|
+
│ │ ├── async-framework-usage.mdc
|
|
54
|
+
│ │ ├── fastjson-usage.mdc
|
|
55
|
+
│ │ ├── memory-guidelines.mdc
|
|
56
|
+
│ │ ├── multilingual-adaptation.mdc
|
|
57
|
+
│ │ ├── nahida-unit-testing.mdc
|
|
58
|
+
│ │ └── ui-styling-rules.mdc
|
|
59
|
+
│ ├── iOS/ # iOS开发规范
|
|
60
|
+
│ │ └── .cursor/
|
|
61
|
+
│ │ └── rules/
|
|
62
|
+
│ │ ├── dynamicx-usage.mdc
|
|
63
|
+
│ │ ├── font.mdc
|
|
64
|
+
│ │ ├── image-assets.mdc
|
|
65
|
+
│ │ ├── learned-memories.mdc
|
|
66
|
+
│ │ ├── memory.mdc
|
|
67
|
+
│ │ ├── network.mdc
|
|
68
|
+
│ │ ├── objetive-c.mdc
|
|
69
|
+
│ │ └── user-track.mdc
|
|
70
|
+
│ ├── web/ # Web开发规范
|
|
71
|
+
│ │ └── .cursor/
|
|
72
|
+
│ │ └── rules/
|
|
73
|
+
│ │ ├── 00-page-development-index.mdc
|
|
74
|
+
│ │ ├── 01-project-structure.mdc
|
|
75
|
+
│ │ ├── 02-network-requests.mdc
|
|
76
|
+
│ │ ├── 03-analytics-log.mdc
|
|
77
|
+
│ │ ├── 04-icon.mdc
|
|
78
|
+
│ │ ├── 05-img.mdc
|
|
79
|
+
│ │ ├── 06-responsive.mdc
|
|
80
|
+
│ │ ├── 07-code-formatting-rules.mdc
|
|
81
|
+
│ │ ├── 08-dependency-architecture.mdc
|
|
82
|
+
│ │ ├── 09-stylelint-configuration.mdc
|
|
83
|
+
│ │ └── monorepo.mdc
|
|
84
|
+
│ ├── java/ # Java后端开发规范
|
|
85
|
+
│ │ └── .cursor/
|
|
86
|
+
│ │ └── rules/
|
|
87
|
+
│ │ ├── 01-overview.mdc
|
|
88
|
+
│ │ ├── 02-code-style.mdc
|
|
89
|
+
│ │ ├── 03-ddd.mdc
|
|
90
|
+
│ │ ├── 04-hsf.mdc
|
|
91
|
+
│ │ ├── 05-mybatis.mdc
|
|
92
|
+
│ │ ├── 06-api-standards.mdc
|
|
93
|
+
│ │ ├── 07-unit-testing.mdc
|
|
94
|
+
│ │ ├── 08-interaction.mdc
|
|
95
|
+
│ │ ├── async-reactive-patterns.mdc
|
|
96
|
+
│ │ ├── configuration-management.mdc
|
|
97
|
+
│ │ ├── database-tddl-patterns.mdc
|
|
98
|
+
│ │ ├── error-handling-patterns.mdc
|
|
99
|
+
│ │ ├── hsf-service-patterns.mdc
|
|
100
|
+
│ │ ├── logging-monitoring-patterns.mdc
|
|
101
|
+
│ │ ├── messaging-patterns.mdc
|
|
102
|
+
│ │ ├── pandora-boot-patterns.mdc
|
|
103
|
+
│ │ └── switch-config-patterns.mdc
|
|
104
|
+
│ └── common/ # 通用开发指南
|
|
105
|
+
│ └── .cursor/
|
|
106
|
+
│ └── rules/
|
|
107
|
+
│ ├── coding-principles.mdc
|
|
108
|
+
│ ├── linux-torvalds.mdc
|
|
109
|
+
│ ├── prd_review.md
|
|
110
|
+
│ ├── r2c_workflow.mdc
|
|
111
|
+
│ ├── readme-guidelines.mdc
|
|
112
|
+
│ └── ux_design.md
|
|
113
|
+
├── specs/ # 产品规格和设计文档
|
|
114
|
+
├── technical-design/ # 技术设计文档
|
|
115
|
+
├── templates/ # 文档模板
|
|
116
|
+
├── test-cases/ # 测试用例
|
|
117
|
+
├── collaboration/ # 协作文档
|
|
118
|
+
├── tools/ # 开发工具
|
|
119
|
+
├── todo/ # 待办事项
|
|
120
|
+
└── README.md
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 关键目录用途
|
|
124
|
+
|
|
125
|
+
- **`rules/`**: 所有开发规范和指南的根目录
|
|
126
|
+
- **`android/`**: 全面的Android开发规范和架构模式
|
|
127
|
+
- **`iOS/`**: iOS开发规范和最佳实践
|
|
128
|
+
- **`web/`**: Web前端开发规范和指南
|
|
129
|
+
- **`java/`**: Java后端开发规范和架构模式
|
|
130
|
+
- **`common/`**: 跨平台的通用开发指南和原则
|
|
131
|
+
- **`specs/`**: 产品规格和设计文档
|
|
132
|
+
- **`technical-design/`**: 技术架构和设计文档
|
|
133
|
+
- **`templates/`**: 文档模板和规范
|
|
134
|
+
- **`test-cases/`**: 测试用例和测试规范
|
|
135
|
+
- **`collaboration/`**: 团队协作文档和流程
|
|
136
|
+
- **`tools/`**: 开发工具和脚本
|
|
137
|
+
- **`todo/`**: 待办事项和任务跟踪
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 功能和特性
|
|
142
|
+
|
|
143
|
+
### 🏗️ 架构规范
|
|
144
|
+
- **基础继承模式**: 标准化的Activity/Fragment基类选择
|
|
145
|
+
- **MVP架构**: 完整的MVP模式实现,包含presenter生命周期管理
|
|
146
|
+
- **PART架构**: 用于复杂UI的高级基于组件的架构
|
|
147
|
+
- **模块化设计**: 分层架构,关注点清晰分离
|
|
148
|
+
|
|
149
|
+
### 🌐 网络和数据管理
|
|
150
|
+
- **MTOP框架**: 全面的网络请求处理和API集成
|
|
151
|
+
- **四层缓存策略**: 内存、磁盘、数据库和网络缓存实现
|
|
152
|
+
- **FastJSON集成**: 类型安全的JSON解析和错误处理模式
|
|
153
|
+
- **数据模型标准**: 强类型模型优于JSONObject使用
|
|
154
|
+
|
|
155
|
+
### 📊 用户体验和分析
|
|
156
|
+
- **分析跟踪**: 完整的用户行为和性能跟踪系统
|
|
157
|
+
- **图片加载**: 优化的图片加载,集成LoadableImageView
|
|
158
|
+
- **RecyclerView标准**: 使用AccioDpl框架的标准化列表实现
|
|
159
|
+
- **DX框架**: 用于灵活UI渲染的动态模板系统
|
|
160
|
+
|
|
161
|
+
### 🧠 记忆驱动学习系统
|
|
162
|
+
- **项目记忆**: 常见错误及其纠正的持久存储
|
|
163
|
+
- **代码风格强制执行**: 反模式的自动检测和预防
|
|
164
|
+
- **最佳实践文档**: 真实世界的示例和实现指南
|
|
165
|
+
- **持续改进**: 基于开发经验的迭代优化
|
|
166
|
+
|
|
167
|
+
### 🤖 User Growth Intelligence System
|
|
168
|
+
- **Multi-Agent Architecture**: Specialized agents for different types of user growth tasks
|
|
169
|
+
- **Intelligent Routing**: LLM-powered agent selection based on query intent and complexity
|
|
170
|
+
- **Streaming Processing**: Real-time SSE responses for enhanced user experience
|
|
171
|
+
- **Performance Monitoring**: Comprehensive tracking of agent performance and user interactions
|
|
172
|
+
- **Thread Pool Management**: Custom executor for high-concurrency multi-agent operations
|
|
173
|
+
|
|
174
|
+
### 🌍 Development Support
|
|
175
|
+
- **Multilingual Adaptation**: Guidelines for internationalization and localization
|
|
176
|
+
- **Complex Task Processing**: Structured approach to large feature development
|
|
177
|
+
- **README Standards**: Comprehensive documentation requirements and templates
|
|
178
|
+
- **Memory Management**: Efficient knowledge retention and retrieval system
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 开始使用
|
|
183
|
+
|
|
184
|
+
### 前置要求
|
|
185
|
+
- Android Studio Arctic Fox或更高版本
|
|
186
|
+
- JDK 8或更高版本
|
|
187
|
+
- 对Android开发概念的基本理解
|
|
188
|
+
- 熟悉MVP/MVVM架构模式
|
|
189
|
+
|
|
190
|
+
### 设置说明
|
|
191
|
+
|
|
192
|
+
1. **克隆仓库**
|
|
193
|
+
```bash
|
|
194
|
+
git clone http://gitlab.alibaba-inc.com/accio/accio-context.git
|
|
195
|
+
cd accio-context
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
2. **与Cursor IDE集成**
|
|
199
|
+
- `.cursor/rules/`目录包含所有开发指南
|
|
200
|
+
- Cursor IDE将自动加载这些规则以提供上下文感知的协助
|
|
201
|
+
- 确保您的Cursor设置引用此知识库
|
|
202
|
+
|
|
203
|
+
3. **审查核心指南**
|
|
204
|
+
- 从`android/00-page-development-index.mdc`开始,了解全面概述
|
|
205
|
+
- 阅读`genera/project-memory.mdc`了解常见陷阱和解决方案
|
|
206
|
+
- 查看`android/accio-android.mdc`了解开发工作流程
|
|
207
|
+
|
|
208
|
+
4. **环境配置**
|
|
209
|
+
- 无需额外的构建配置
|
|
210
|
+
- 知识库仅为文档
|
|
211
|
+
- 将指南集成到您现有的Android项目工作流程中
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## CLI 工具使用
|
|
216
|
+
|
|
217
|
+
为了方便团队在其他项目中安装和使用这些规范,我们提供了 CLI 工具。
|
|
218
|
+
|
|
219
|
+
### 安装 CLI 工具
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# 全局安装
|
|
223
|
+
npm install -g accio-context
|
|
224
|
+
|
|
225
|
+
# 或使用 npx(无需安装)
|
|
226
|
+
npx accio-context install
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 基本使用
|
|
230
|
+
|
|
231
|
+
#### 列出可用的资源
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
accio-context list
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### 交互式安装
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
accio-context install
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
这个命令会:
|
|
244
|
+
1. 自动从 git 仓库克隆或更新 accio-context 到 `~/.accio-context/repo`
|
|
245
|
+
2. 显示交互式菜单,让你选择要安装的 rules、skills 和 templates
|
|
246
|
+
3. 将选中的资源安装到当前项目(或指定的目标目录)
|
|
247
|
+
|
|
248
|
+
**交互方式:**
|
|
249
|
+
- 使用箭头键导航
|
|
250
|
+
- 回车进入目录或安装文件
|
|
251
|
+
- 选择"返回"选项返回上一级
|
|
252
|
+
- 在主菜单选择"完成安装"执行实际安装
|
|
253
|
+
|
|
254
|
+
#### 安装选项
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
accio-context install [options]
|
|
258
|
+
|
|
259
|
+
Options:
|
|
260
|
+
-t, --target <path> Target project path (default: current directory)
|
|
261
|
+
-r, --repo <url> Repository URL (default: auto-detect)
|
|
262
|
+
-c, --config <path> Config file (json/yaml)
|
|
263
|
+
--overwrite Overwrite existing files
|
|
264
|
+
--dry-run Show what would be copied
|
|
265
|
+
--json Output summary as JSON
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### 自定义仓库
|
|
269
|
+
|
|
270
|
+
默认情况下,工具会:
|
|
271
|
+
- 如果当前目录就是 accio-context 仓库(开发模式),使用当前目录
|
|
272
|
+
- 否则,克隆默认仓库到 `~/.accio-context/repo`
|
|
273
|
+
|
|
274
|
+
你可以指定自定义仓库 URL:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
accio-context install --repo http://gitlab.alibaba-inc.com/accio/accio-context.git
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
或通过环境变量设置:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
export ACCIO_CONTEXT_REPO=http://gitlab.alibaba-inc.com/accio/accio-context.git
|
|
284
|
+
accio-context install
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### 工作原理
|
|
288
|
+
|
|
289
|
+
1. 工具获取 accio-context 仓库(从 git 或使用本地副本)
|
|
290
|
+
2. 扫描可用的 rules、skills 和 templates
|
|
291
|
+
3. 提供交互式菜单选择要安装的内容
|
|
292
|
+
4. 将选中的文件复制到你的项目:
|
|
293
|
+
- Rules → `.cursor/rules/`
|
|
294
|
+
- Skills → `.cursor/skills/`
|
|
295
|
+
- Templates → `templates/`
|
|
296
|
+
|
|
297
|
+
### 开发模式
|
|
298
|
+
|
|
299
|
+
如果你在本地开发 CLI 工具:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
# 安装依赖
|
|
303
|
+
npm install
|
|
304
|
+
|
|
305
|
+
# 本地运行
|
|
306
|
+
node bin/accio-context.js install
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## 使用指南
|
|
312
|
+
|
|
313
|
+
### 对于新开发者
|
|
314
|
+
|
|
315
|
+
1. **从这里开始**: 阅读`android/00-page-development-index.mdc`了解完整的开发工作流程
|
|
316
|
+
2. **架构理解**: 查看`02-mvp-architecture.mdc`和`03-part-architecture.mdc`
|
|
317
|
+
3. **常见模式**: 学习`project-memory.mdc`了解经常遇到的问题和解决方案
|
|
318
|
+
|
|
319
|
+
### 对于有经验的开发者
|
|
320
|
+
|
|
321
|
+
1. **快速参考**: 使用规范文件作为实现细节的快速查找
|
|
322
|
+
2. **代码审查**: 在代码审查过程中参考指南
|
|
323
|
+
3. **架构决策**: 在做出架构选择之前咨询相关规范文件
|
|
324
|
+
|
|
325
|
+
### API文档
|
|
326
|
+
|
|
327
|
+
知识库提供以下规范:
|
|
328
|
+
- **MTOP API集成**: 完整的请求/响应处理模式
|
|
329
|
+
- **分析API**: 用户行为跟踪和性能监控
|
|
330
|
+
- **缓存API**: 多层缓存管理和失效
|
|
331
|
+
- **DX框架API**: 动态模板渲染和事件处理
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## 实现细节
|
|
336
|
+
|
|
337
|
+
### 架构概述
|
|
338
|
+
|
|
339
|
+
知识库遵循**分层规范架构**:
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
┌─────────────────────────────────────┐
|
|
343
|
+
│ 视图层 │
|
|
344
|
+
├─────────────────────────────────────┤
|
|
345
|
+
│ 逻辑层 │
|
|
346
|
+
├─────────────────────────────────────┤
|
|
347
|
+
│ 服务层 │
|
|
348
|
+
├─────────────────────────────────────┤
|
|
349
|
+
│ 框架层 │
|
|
350
|
+
└─────────────────────────────────────┘
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
- **视图层**: UI组件、Activities、Fragments
|
|
354
|
+
- **逻辑层**: Presenters、ViewModels、业务逻辑
|
|
355
|
+
- **服务层**: 数据管理器、缓存处理器、API客户端
|
|
356
|
+
- **框架层**: MTOP、DX、分析、图片加载
|
|
357
|
+
|
|
358
|
+
### 数据流模式
|
|
359
|
+
|
|
360
|
+
1. **请求流**: 视图 → Presenter → 服务 → 网络/缓存
|
|
361
|
+
2. **响应流**: 网络/缓存 → 服务 → Presenter → 视图
|
|
362
|
+
3. **错误处理**: 一致的错误传播,提供用户友好的消息
|
|
363
|
+
4. **分析流**: 用户操作 → 分析服务 → 后端跟踪
|
|
364
|
+
|
|
365
|
+
### 关键设计模式
|
|
366
|
+
|
|
367
|
+
- **MVP模式**: 视图和业务逻辑的清晰分离
|
|
368
|
+
- **仓库模式**: 具有多个源的统一数据访问层
|
|
369
|
+
- **观察者模式**: 响应式数据更新和UI同步
|
|
370
|
+
- **工厂模式**: 标准化对象创建和依赖注入
|
|
371
|
+
- **策略模式**: 用于缓存和图片加载的可插拔算法
|
|
372
|
+
|
|
373
|
+
### 性能考虑
|
|
374
|
+
|
|
375
|
+
- **内存管理**: 高效的对象生命周期和垃圾回收
|
|
376
|
+
- **网络优化**: 请求批处理、缓存和重试机制
|
|
377
|
+
- **图片加载**: 懒加载、内存池和大小优化
|
|
378
|
+
- **列表性能**: ViewHolder模式和数据绑定优化
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## 开发指南
|
|
383
|
+
|
|
384
|
+
### 代码风格标准
|
|
385
|
+
|
|
386
|
+
1. **导入管理**: 始终使用import语句,不要使用完整类路径
|
|
387
|
+
2. **类型安全**: 对于已知结构,优先使用数据类而非JSONObject
|
|
388
|
+
3. **错误处理**: 全面的try-catch块,提供有意义的消息
|
|
389
|
+
4. **文档**: 为复杂业务逻辑提供详细注释
|
|
390
|
+
5. **命名约定**: 清晰、描述性的名称,遵循Android约定
|
|
391
|
+
|
|
392
|
+
### 测试程序
|
|
393
|
+
|
|
394
|
+
1. **单元测试**: 测试presenters和services中的业务逻辑
|
|
395
|
+
2. **集成测试**: 测试API集成和缓存行为
|
|
396
|
+
3. **UI测试**: 验证用户交互和导航流程
|
|
397
|
+
4. **性能测试**: 监控内存使用和响应时间
|
|
398
|
+
|
|
399
|
+
### 最佳实践
|
|
400
|
+
|
|
401
|
+
- **记忆驱动开发**: 在实现之前始终检查`project-memory.mdc`
|
|
402
|
+
- **规范合规**: 在开发过程中参考相关的`.mdc`文件
|
|
403
|
+
- **代码审查**: 在同行审查过程中使用指南作为检查清单
|
|
404
|
+
- **持续学习**: 用新发现和解决方案更新记忆文件
|
|
405
|
+
|
|
406
|
+
### 要避免的常见陷阱
|
|
407
|
+
|
|
408
|
+
1. **FastJSON误用**: 不要将Android JSONObject的`opt*`方法与FastJSON一起使用
|
|
409
|
+
2. **数据覆盖**: 永远不要用分布式数据覆盖原始业务数据
|
|
410
|
+
3. **状态管理**: 使用明确的状态标志,不要依赖数据字段存在
|
|
411
|
+
4. **架构违规**: 维护清晰的层边界和职责
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## 开发工作流程
|
|
416
|
+
|
|
417
|
+
### 项目理解阶段
|
|
418
|
+
- 分析现有代码以理解架构和模式
|
|
419
|
+
- 识别关键设计模式和编码约定
|
|
420
|
+
- 确保与已建立的项目模式一致
|
|
421
|
+
|
|
422
|
+
### 需求分析阶段
|
|
423
|
+
- 从用户角度思考以识别实际需求
|
|
424
|
+
- 通过深思熟虑的讨论解决需求差距
|
|
425
|
+
- 选择简单有效的解决方案而非复杂方案
|
|
426
|
+
- 为复杂概念创建可视化图表
|
|
427
|
+
|
|
428
|
+
### 代码实现阶段
|
|
429
|
+
- 在实现之前充分规划
|
|
430
|
+
- 应用SOLID原则和适当的设计模式
|
|
431
|
+
- 使用具有明确里程碑的逐步方法
|
|
432
|
+
- 为重要代码块编写全面注释
|
|
433
|
+
- 实现适当的错误处理和监控
|
|
434
|
+
|
|
435
|
+
### 问题解决阶段
|
|
436
|
+
- 彻底分析相关代码以理解功能
|
|
437
|
+
- 识别根本原因而非解决症状
|
|
438
|
+
- 提出具有清晰推理的解决方案
|
|
439
|
+
- 基于反馈迭代直到问题解决
|
|
440
|
+
- 在记忆文件中记录解决方案以供将来参考
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## 参考资料和资源
|
|
445
|
+
|
|
446
|
+
### 内部文档
|
|
447
|
+
- [Android页面开发索引](/rules/android/.cursor/rules/00-page-development-index.mdc)
|
|
448
|
+
- [MVP架构指南](/rules/android/.cursor/rules/02-mvp-architecture.mdc)
|
|
449
|
+
- [网络请求规范](/rules/android/.cursor/rules/04-network-requests.mdc)
|
|
450
|
+
- [缓存策略实现](/rules/android/.cursor/rules/05-cache-strategy.mdc)
|
|
451
|
+
- [Web开发索引](/rules/web/.cursor/rules/00-page-development-index.mdc)
|
|
452
|
+
- [Java开发概览](/rules/java/.cursor/rules/01-overview.mdc)
|
|
453
|
+
- [iOS开发指南](/rules/iOS/.cursor/rules/dynamicx-usage.mdc)
|
|
454
|
+
- [通用编码原则](/rules/common/.cursor/rules/coding-principles.mdc)
|
|
455
|
+
|
|
456
|
+
### 外部资源
|
|
457
|
+
- [Android架构组件](https://developer.android.com/topic/libraries/architecture)
|
|
458
|
+
- [MVP模式最佳实践](https://github.com/MindorksOpenSource/android-mvp-architecture)
|
|
459
|
+
- [FastJSON文档](https://github.com/alibaba/fastjson)
|
|
460
|
+
- [MTOP框架指南](https://internal-docs.alibaba.com/mtop)
|
|
461
|
+
|
|
462
|
+
### 开发工具
|
|
463
|
+
- **Cursor IDE**: 具有上下文感知的AI驱动开发环境
|
|
464
|
+
- **Android Studio**: 具有插件支持的主要开发IDE
|
|
465
|
+
- **Git**: 版本控制,使用约定式提交消息
|
|
466
|
+
- **Gradle**: 构建自动化和依赖管理
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## 贡献
|
|
471
|
+
|
|
472
|
+
### 知识库更新
|
|
473
|
+
1. 为新指南遵循已建立的`.mdc`文件格式
|
|
474
|
+
2. 用新学习和解决方案更新`project-memory.mdc`
|
|
475
|
+
3. 保持与现有文档结构的一致性
|
|
476
|
+
4. 用真实实现示例测试指南
|
|
477
|
+
|
|
478
|
+
### 记忆管理
|
|
479
|
+
- 在记忆文件中添加新的错误和纠正
|
|
480
|
+
- 保持信息通用和可重用
|
|
481
|
+
- 使用清晰的标题和分类进行结构化
|
|
482
|
+
- 定期清理过时或被取代的信息
|
|
483
|
+
|
|
484
|
+
### 质量保证
|
|
485
|
+
- 验证所有代码示例都能正常工作并经过测试
|
|
486
|
+
- 确保文档与当前实现匹配
|
|
487
|
+
- 审查清晰度和可访问性
|
|
488
|
+
- 维护所有规范文件的一致性
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
*本知识库基于真实世界的开发经验和团队反馈持续发展。如有问题或建议,请参考项目记忆系统或咨询开发团队负责人。*
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "accio-context",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool to install Accio context assets (rules, skills, templates) into projects",
|
|
5
|
+
"main": "src/cli.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"accio-context": "bin/accio-context.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cursor",
|
|
15
|
+
"rules",
|
|
16
|
+
"skills",
|
|
17
|
+
"templates",
|
|
18
|
+
"cli"
|
|
19
|
+
],
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "http://gitlab.alibaba-inc.com/accio/accio-context.git"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin",
|
|
28
|
+
"src",
|
|
29
|
+
"package.json",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"commander": "^14.0.2",
|
|
34
|
+
"prompts": "^2.4.2",
|
|
35
|
+
"yaml": "^2.8.2"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
const fs = require("fs/promises");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { Command } = require("commander");
|
|
4
|
+
const prompts = require("prompts");
|
|
5
|
+
const YAML = require("yaml");
|
|
6
|
+
|
|
7
|
+
const { getInventory } = require("./inventory");
|
|
8
|
+
const { buildCopyPlan, executeCopyPlan } = require("./install");
|
|
9
|
+
const { getRepoRoot } = require("./repo");
|
|
10
|
+
|
|
11
|
+
async function loadConfig(configPath) {
|
|
12
|
+
if (!configPath) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const absolutePath = path.resolve(process.cwd(), configPath);
|
|
16
|
+
const contents = await fs.readFile(absolutePath, "utf-8");
|
|
17
|
+
const ext = path.extname(absolutePath).toLowerCase();
|
|
18
|
+
if (ext === ".json") {
|
|
19
|
+
return JSON.parse(contents);
|
|
20
|
+
}
|
|
21
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
22
|
+
return YAML.parse(contents);
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`Unsupported config format: ${ext}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function printList(inventory) {
|
|
28
|
+
console.log("Domains:");
|
|
29
|
+
for (const domain of inventory.domains) {
|
|
30
|
+
console.log(`- ${domain.name} (${domain.ruleFiles.length} rules)`);
|
|
31
|
+
}
|
|
32
|
+
console.log("");
|
|
33
|
+
console.log("Skills:");
|
|
34
|
+
for (const skill of inventory.skills) {
|
|
35
|
+
console.log(`- ${skill.name}`);
|
|
36
|
+
}
|
|
37
|
+
console.log("");
|
|
38
|
+
console.log("Templates:");
|
|
39
|
+
for (const template of inventory.templates) {
|
|
40
|
+
console.log(`- ${template.name}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function promptSelections(inventory) {
|
|
45
|
+
const selections = { domains: [], skills: [], templates: [], ruleFiles: [] };
|
|
46
|
+
|
|
47
|
+
// Navigation-style interaction: Enter to enter directory or install file
|
|
48
|
+
console.log("Tip: Use arrow keys to navigate, Enter to enter/install.\n");
|
|
49
|
+
|
|
50
|
+
while (true) {
|
|
51
|
+
const installedCount =
|
|
52
|
+
selections.ruleFiles.length + selections.skills.length + selections.templates.length;
|
|
53
|
+
const status = installedCount > 0 ? ` [${installedCount} items ready]` : "";
|
|
54
|
+
|
|
55
|
+
const mainChoices = [
|
|
56
|
+
{ title: "rules", value: "rules" },
|
|
57
|
+
{ title: "skills", value: "skills" },
|
|
58
|
+
{ title: "templates", value: "templates" },
|
|
59
|
+
{ title: `--- Complete and install${status} ---`, value: "done" },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const mainResponse = await prompts({
|
|
63
|
+
type: "select",
|
|
64
|
+
name: "action",
|
|
65
|
+
message: "Main menu",
|
|
66
|
+
choices: mainChoices,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!mainResponse.action || mainResponse.action === "done") {
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (mainResponse.action === "rules") {
|
|
74
|
+
await navigateRules(inventory, selections);
|
|
75
|
+
} else if (mainResponse.action === "skills") {
|
|
76
|
+
await navigateSkills(inventory, selections);
|
|
77
|
+
} else if (mainResponse.action === "templates") {
|
|
78
|
+
await navigateTemplates(inventory, selections);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return selections;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function navigateRules(inventory, selections) {
|
|
86
|
+
while (true) {
|
|
87
|
+
const domainChoices = inventory.domains.map((domain) => {
|
|
88
|
+
const selectedCount = selections.ruleFiles.filter(
|
|
89
|
+
(r) => r.domain === domain.name
|
|
90
|
+
).length;
|
|
91
|
+
const marker = selectedCount > 0 ? ` [${selectedCount} installed]` : "";
|
|
92
|
+
return {
|
|
93
|
+
title: `${domain.name} (${domain.ruleFiles.length} rules)${marker}`,
|
|
94
|
+
value: domain.name,
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
domainChoices.push({ title: "<- Back to main menu", value: "__back__" });
|
|
98
|
+
|
|
99
|
+
const domainResponse = await prompts({
|
|
100
|
+
type: "select",
|
|
101
|
+
name: "domain",
|
|
102
|
+
message: "Select a domain (Enter to enter)",
|
|
103
|
+
choices: domainChoices,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!domainResponse.domain || domainResponse.domain === "__back__") {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const domainName = domainResponse.domain;
|
|
111
|
+
const domain = inventory.domains.find((d) => d.name === domainName);
|
|
112
|
+
if (!domain) continue;
|
|
113
|
+
|
|
114
|
+
// Navigate into domain - show rules
|
|
115
|
+
await navigateDomainRules(domainName, domain, selections);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function navigateDomainRules(domainName, domain, selections) {
|
|
120
|
+
while (true) {
|
|
121
|
+
const alreadySelected = new Set(
|
|
122
|
+
selections.ruleFiles
|
|
123
|
+
.filter((r) => r.domain === domainName)
|
|
124
|
+
.map((r) => r.file.name)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const ruleChoices = domain.ruleFiles.map((rule) => {
|
|
128
|
+
const marker = alreadySelected.has(rule.name) ? " [installed]" : "";
|
|
129
|
+
return {
|
|
130
|
+
title: `${rule.name}${marker}`,
|
|
131
|
+
value: { domain: domainName, file: rule },
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
ruleChoices.push({ title: "<- Back to domains", value: "__back__" });
|
|
135
|
+
|
|
136
|
+
const ruleResponse = await prompts({
|
|
137
|
+
type: "select",
|
|
138
|
+
name: "rule",
|
|
139
|
+
message: `Rules in [${domainName}] (Enter to install)`,
|
|
140
|
+
choices: ruleChoices,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!ruleResponse.rule || ruleResponse.rule === "__back__") {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Install immediately
|
|
148
|
+
const ruleEntry = ruleResponse.rule;
|
|
149
|
+
const existingIndex = selections.ruleFiles.findIndex(
|
|
150
|
+
(r) => r.domain === domainName && r.file.name === ruleEntry.file.name
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (existingIndex < 0) {
|
|
154
|
+
// Add to selections
|
|
155
|
+
selections.ruleFiles.push(ruleEntry);
|
|
156
|
+
if (!selections.domains.includes(domainName)) {
|
|
157
|
+
selections.domains.push(domainName);
|
|
158
|
+
}
|
|
159
|
+
console.log(` ✓ Installed: ${ruleEntry.file.name}`);
|
|
160
|
+
} else {
|
|
161
|
+
console.log(` Already installed: ${ruleEntry.file.name}`);
|
|
162
|
+
}
|
|
163
|
+
// Continue browsing (don't break)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function navigateSkills(inventory, selections) {
|
|
168
|
+
while (true) {
|
|
169
|
+
const alreadySelected = new Set(selections.skills);
|
|
170
|
+
const skillChoices = inventory.skills.map((skill) => {
|
|
171
|
+
const marker = alreadySelected.has(skill.name) ? " [installed]" : "";
|
|
172
|
+
return {
|
|
173
|
+
title: `${skill.name}${marker}`,
|
|
174
|
+
value: skill.name,
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
skillChoices.push({ title: "<- Back to main menu", value: "__back__" });
|
|
178
|
+
|
|
179
|
+
const skillResponse = await prompts({
|
|
180
|
+
type: "select",
|
|
181
|
+
name: "skill",
|
|
182
|
+
message: "Select a skill (Enter to install)",
|
|
183
|
+
choices: skillChoices,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (!skillResponse.skill || skillResponse.skill === "__back__") {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Install immediately
|
|
191
|
+
const skillName = skillResponse.skill;
|
|
192
|
+
const index = selections.skills.indexOf(skillName);
|
|
193
|
+
if (index < 0) {
|
|
194
|
+
selections.skills.push(skillName);
|
|
195
|
+
console.log(` ✓ Installed: ${skillName}`);
|
|
196
|
+
} else {
|
|
197
|
+
console.log(` Already installed: ${skillName}`);
|
|
198
|
+
}
|
|
199
|
+
// Continue browsing (don't break)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function navigateTemplates(inventory, selections) {
|
|
204
|
+
while (true) {
|
|
205
|
+
const alreadySelected = new Set(selections.templates);
|
|
206
|
+
const templateChoices = inventory.templates.map((template) => {
|
|
207
|
+
const marker = alreadySelected.has(template.name) ? " [installed]" : "";
|
|
208
|
+
return {
|
|
209
|
+
title: `${template.name}${marker}`,
|
|
210
|
+
value: template.name,
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
templateChoices.push({ title: "<- Back to main menu", value: "__back__" });
|
|
214
|
+
|
|
215
|
+
const templateResponse = await prompts({
|
|
216
|
+
type: "select",
|
|
217
|
+
name: "template",
|
|
218
|
+
message: "Select a template (Enter to install)",
|
|
219
|
+
choices: templateChoices,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (!templateResponse.template || templateResponse.template === "__back__") {
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Install immediately
|
|
227
|
+
const templateName = templateResponse.template;
|
|
228
|
+
const index = selections.templates.indexOf(templateName);
|
|
229
|
+
if (index < 0) {
|
|
230
|
+
selections.templates.push(templateName);
|
|
231
|
+
console.log(` ✓ Installed: ${templateName}`);
|
|
232
|
+
} else {
|
|
233
|
+
console.log(` Already installed: ${templateName}`);
|
|
234
|
+
}
|
|
235
|
+
// Continue browsing (don't break)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function summarizeSelections(plan) {
|
|
240
|
+
return {
|
|
241
|
+
domains: plan.selectedDomains,
|
|
242
|
+
skills: plan.selectedSkills,
|
|
243
|
+
templates: plan.selectedTemplates,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function runInstall(options) {
|
|
248
|
+
const repoRoot = await getRepoRoot(options.repo);
|
|
249
|
+
const inventory = await getInventory(repoRoot);
|
|
250
|
+
const config = await loadConfig(options.config);
|
|
251
|
+
|
|
252
|
+
const targetRoot = path.resolve(
|
|
253
|
+
process.cwd(),
|
|
254
|
+
options.target || (config && config.targetPath) || "."
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const selections =
|
|
258
|
+
config || (await promptSelections(inventory)) || {
|
|
259
|
+
domains: [],
|
|
260
|
+
skills: [],
|
|
261
|
+
templates: [],
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const plan = await buildCopyPlan(inventory, selections, targetRoot);
|
|
265
|
+
const results = await executeCopyPlan(plan, {
|
|
266
|
+
overwrite: Boolean(options.overwrite),
|
|
267
|
+
dryRun: Boolean(options.dryRun),
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (options.json) {
|
|
271
|
+
console.log(
|
|
272
|
+
JSON.stringify(
|
|
273
|
+
{
|
|
274
|
+
targetRoot,
|
|
275
|
+
selections: summarizeSelections(plan),
|
|
276
|
+
copied: results.copied.length,
|
|
277
|
+
conflicts: results.conflicts.length,
|
|
278
|
+
dryRun: Boolean(options.dryRun),
|
|
279
|
+
},
|
|
280
|
+
null,
|
|
281
|
+
2
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
console.log(`Target: ${targetRoot}`);
|
|
288
|
+
console.log(`Copied: ${results.copied.length}`);
|
|
289
|
+
if (results.conflicts.length > 0) {
|
|
290
|
+
console.log(
|
|
291
|
+
`Conflicts (skipped): ${results.conflicts.length} (use --overwrite to replace)`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (options.dryRun) {
|
|
295
|
+
console.log("Dry run enabled: no files were copied.");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function main() {
|
|
300
|
+
const program = new Command();
|
|
301
|
+
program.name("accio-context").description("Install Accio context assets.");
|
|
302
|
+
|
|
303
|
+
program
|
|
304
|
+
.command("list")
|
|
305
|
+
.description("List available domains, skills, and templates")
|
|
306
|
+
.option("-r, --repo <url>", "Repository URL (default: auto-detect)")
|
|
307
|
+
.action(async (options) => {
|
|
308
|
+
const repoRoot = await getRepoRoot(options.repo);
|
|
309
|
+
const inventory = await getInventory(repoRoot);
|
|
310
|
+
printList(inventory);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
program
|
|
314
|
+
.command("install")
|
|
315
|
+
.description("Install selected assets into a target project")
|
|
316
|
+
.option("-c, --config <path>", "Config file (json/yaml)")
|
|
317
|
+
.option("-t, --target <path>", "Target project path")
|
|
318
|
+
.option("-r, --repo <url>", "Repository URL (default: auto-detect)")
|
|
319
|
+
.option("--overwrite", "Overwrite files on conflict")
|
|
320
|
+
.option("--dry-run", "Show what would be copied")
|
|
321
|
+
.option("--json", "Output summary as JSON")
|
|
322
|
+
.action(async (options) => {
|
|
323
|
+
await runInstall(options);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
await program.parseAsync(process.argv);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports = {
|
|
330
|
+
main,
|
|
331
|
+
};
|
package/src/install.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
const fs = require("fs/promises");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const { listFilesRecursive } = require("./inventory");
|
|
5
|
+
|
|
6
|
+
async function pathExists(targetPath) {
|
|
7
|
+
try {
|
|
8
|
+
await fs.access(targetPath);
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function ensureDir(dirPath) {
|
|
16
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeTemplatesSelection(selection, templates) {
|
|
20
|
+
if (selection === true || selection === "all" || selection == null) {
|
|
21
|
+
return templates.map((template) => template.name);
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(selection)) {
|
|
24
|
+
return selection;
|
|
25
|
+
}
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeSkillsSelection(selection, skills) {
|
|
30
|
+
if (selection === true || selection === "all" || selection == null) {
|
|
31
|
+
return skills.map((skill) => skill.name);
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(selection)) {
|
|
34
|
+
return selection;
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeDomainsSelection(selection, domains) {
|
|
40
|
+
if (selection === true || selection === "all" || selection == null) {
|
|
41
|
+
return domains.map((domain) => domain.name);
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(selection)) {
|
|
44
|
+
return selection;
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function collectSkillItems(skillsRoot, skillName, targetRoot) {
|
|
50
|
+
const sourceDir = path.join(skillsRoot, skillName);
|
|
51
|
+
const targetDir = path.join(targetRoot, ".cursor", "skills", skillName);
|
|
52
|
+
const sourceFiles = await listFilesRecursive(sourceDir);
|
|
53
|
+
return sourceFiles.map((sourcePath) => {
|
|
54
|
+
const relative = path.relative(sourceDir, sourcePath);
|
|
55
|
+
return {
|
|
56
|
+
sourcePath,
|
|
57
|
+
targetPath: path.join(targetDir, relative),
|
|
58
|
+
label: `skill:${skillName}`,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function collectRuleItems(domain, rulesDir, targetRoot) {
|
|
64
|
+
const entries = await fs.readdir(rulesDir, { withFileTypes: true });
|
|
65
|
+
const files = entries.filter((entry) => entry.isFile()).map((entry) => entry.name);
|
|
66
|
+
return files.map((name) => ({
|
|
67
|
+
sourcePath: path.join(rulesDir, name),
|
|
68
|
+
targetPath: path.join(targetRoot, ".cursor", "rules", name),
|
|
69
|
+
label: `rules:${domain}`,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function collectTemplateItems(templatesRoot, templateName, targetRoot) {
|
|
74
|
+
const sourcePath = path.join(templatesRoot, templateName);
|
|
75
|
+
const targetPath = path.join(targetRoot, "templates", templateName);
|
|
76
|
+
return [{ sourcePath, targetPath, label: "templates" }];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function validateSelection(selected, available, label) {
|
|
80
|
+
const availableSet = new Set(available);
|
|
81
|
+
const invalid = selected.filter((item) => !availableSet.has(item));
|
|
82
|
+
if (invalid.length > 0) {
|
|
83
|
+
const message = `Unknown ${label}: ${invalid.join(", ")}`;
|
|
84
|
+
throw new Error(message);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function buildCopyPlan(inventory, selections, targetRoot) {
|
|
89
|
+
const selectedSkills = normalizeSkillsSelection(
|
|
90
|
+
selections.skills,
|
|
91
|
+
inventory.skills
|
|
92
|
+
);
|
|
93
|
+
const selectedTemplates = normalizeTemplatesSelection(
|
|
94
|
+
selections.templates,
|
|
95
|
+
inventory.templates
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
validateSelection(
|
|
99
|
+
selectedSkills,
|
|
100
|
+
inventory.skills.map((skill) => skill.name),
|
|
101
|
+
"skills"
|
|
102
|
+
);
|
|
103
|
+
validateSelection(
|
|
104
|
+
selectedTemplates,
|
|
105
|
+
inventory.templates.map((template) => template.name),
|
|
106
|
+
"templates"
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const items = [];
|
|
110
|
+
|
|
111
|
+
// Skills
|
|
112
|
+
for (const skill of selectedSkills) {
|
|
113
|
+
items.push(...(await collectSkillItems(inventory.skillsRoot, skill, targetRoot)));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Rules: if ruleFiles provided (interactive mode), use them; otherwise use domains (config mode)
|
|
117
|
+
if (Array.isArray(selections.ruleFiles) && selections.ruleFiles.length > 0) {
|
|
118
|
+
for (const entry of selections.ruleFiles) {
|
|
119
|
+
const { domain, file } = entry;
|
|
120
|
+
items.push({
|
|
121
|
+
sourcePath: file.path,
|
|
122
|
+
targetPath: path.join(targetRoot, ".cursor", "rules", file.name),
|
|
123
|
+
label: `rules:${domain}`,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
} else if (selections.domains) {
|
|
127
|
+
const selectedDomains = normalizeDomainsSelection(
|
|
128
|
+
selections.domains,
|
|
129
|
+
inventory.domains
|
|
130
|
+
);
|
|
131
|
+
validateSelection(
|
|
132
|
+
selectedDomains,
|
|
133
|
+
inventory.domains.map((d) => d.name),
|
|
134
|
+
"domains"
|
|
135
|
+
);
|
|
136
|
+
for (const domain of selectedDomains) {
|
|
137
|
+
const domainInfo = inventory.domains.find((entry) => entry.name === domain);
|
|
138
|
+
if (domainInfo) {
|
|
139
|
+
items.push(...(await collectRuleItems(domain, domainInfo.rulesDir, targetRoot)));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Templates
|
|
145
|
+
for (const template of selectedTemplates) {
|
|
146
|
+
items.push(
|
|
147
|
+
...(await collectTemplateItems(inventory.templatesRoot, template, targetRoot))
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
items,
|
|
153
|
+
selectedDomains: selections.domains || [],
|
|
154
|
+
selectedSkills,
|
|
155
|
+
selectedTemplates,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function executeCopyPlan(plan, { overwrite, dryRun }) {
|
|
160
|
+
const results = {
|
|
161
|
+
copied: [],
|
|
162
|
+
skipped: [],
|
|
163
|
+
conflicts: [],
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
for (const item of plan.items) {
|
|
167
|
+
const exists = await pathExists(item.targetPath);
|
|
168
|
+
if (exists && !overwrite) {
|
|
169
|
+
results.conflicts.push(item);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (dryRun) {
|
|
174
|
+
results.skipped.push(item);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await ensureDir(path.dirname(item.targetPath));
|
|
179
|
+
await fs.copyFile(item.sourcePath, item.targetPath);
|
|
180
|
+
results.copied.push(item);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return results;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
buildCopyPlan,
|
|
188
|
+
executeCopyPlan,
|
|
189
|
+
normalizeDomainsSelection,
|
|
190
|
+
normalizeSkillsSelection,
|
|
191
|
+
normalizeTemplatesSelection,
|
|
192
|
+
};
|
package/src/inventory.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const fs = require("fs/promises");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
async function pathExists(targetPath) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(targetPath);
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function listDirEntries(dirPath) {
|
|
14
|
+
if (!(await pathExists(dirPath))) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
return fs.readdir(dirPath, { withFileTypes: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function listDirectories(dirPath) {
|
|
21
|
+
const entries = await listDirEntries(dirPath);
|
|
22
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function listFiles(dirPath) {
|
|
26
|
+
const entries = await listDirEntries(dirPath);
|
|
27
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => entry.name);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function listFilesRecursive(dirPath) {
|
|
31
|
+
const entries = await listDirEntries(dirPath);
|
|
32
|
+
const files = [];
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
35
|
+
if (entry.isDirectory()) {
|
|
36
|
+
const nested = await listFilesRecursive(entryPath);
|
|
37
|
+
files.push(...nested);
|
|
38
|
+
} else if (entry.isFile()) {
|
|
39
|
+
files.push(entryPath);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return files;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function getInventory(repoRoot) {
|
|
46
|
+
const skillsRoot = path.join(repoRoot, ".cursor", "skills");
|
|
47
|
+
const rulesRoot = path.join(repoRoot, "rules");
|
|
48
|
+
const templatesRoot = path.join(repoRoot, "templates");
|
|
49
|
+
|
|
50
|
+
const skillNames = await listDirectories(skillsRoot);
|
|
51
|
+
const skills = skillNames.map((name) => ({
|
|
52
|
+
name,
|
|
53
|
+
dir: path.join(skillsRoot, name),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
const domainNames = await listDirectories(rulesRoot);
|
|
57
|
+
const domains = [];
|
|
58
|
+
for (const domain of domainNames) {
|
|
59
|
+
const domainRulesDir = path.join(rulesRoot, domain, ".cursor", "rules");
|
|
60
|
+
if (await pathExists(domainRulesDir)) {
|
|
61
|
+
const ruleFileNames = await listFiles(domainRulesDir);
|
|
62
|
+
const ruleFiles = ruleFileNames.map((name) => ({
|
|
63
|
+
name,
|
|
64
|
+
path: path.join(domainRulesDir, name),
|
|
65
|
+
}));
|
|
66
|
+
domains.push({
|
|
67
|
+
name: domain,
|
|
68
|
+
rulesDir: domainRulesDir,
|
|
69
|
+
ruleFiles,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const templateFiles = await listFiles(templatesRoot);
|
|
75
|
+
const templates = templateFiles.map((name) => ({
|
|
76
|
+
name,
|
|
77
|
+
path: path.join(templatesRoot, name),
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
skillsRoot,
|
|
82
|
+
rulesRoot,
|
|
83
|
+
templatesRoot,
|
|
84
|
+
skills,
|
|
85
|
+
domains,
|
|
86
|
+
templates,
|
|
87
|
+
listFilesRecursive,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
getInventory,
|
|
93
|
+
listFilesRecursive,
|
|
94
|
+
};
|
package/src/repo.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const fs = require("fs/promises");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { execSync } = require("child_process");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
|
|
6
|
+
// Default repository URL - can be overridden via --repo option or ACCIO_CONTEXT_REPO env var
|
|
7
|
+
const DEFAULT_REPO_URL = process.env.ACCIO_CONTEXT_REPO || "http://gitlab.alibaba-inc.com/accio/accio-context.git";
|
|
8
|
+
const CACHE_DIR = path.join(os.homedir(), ".accio-context", "repo");
|
|
9
|
+
|
|
10
|
+
async function ensureRepo(repoUrl = DEFAULT_REPO_URL) {
|
|
11
|
+
// Check if repo exists and is up to date
|
|
12
|
+
const repoExists = await pathExists(CACHE_DIR);
|
|
13
|
+
|
|
14
|
+
if (repoExists) {
|
|
15
|
+
try {
|
|
16
|
+
// Try to update existing repo
|
|
17
|
+
console.log("Updating repository...");
|
|
18
|
+
execSync("git pull", { cwd: CACHE_DIR, stdio: "ignore" });
|
|
19
|
+
return CACHE_DIR;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
// If update fails, remove and re-clone
|
|
22
|
+
console.log("Repository update failed, re-cloning...");
|
|
23
|
+
await fs.rm(CACHE_DIR, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Clone repository
|
|
28
|
+
console.log(`Cloning repository from ${repoUrl}...`);
|
|
29
|
+
await fs.mkdir(path.dirname(CACHE_DIR), { recursive: true });
|
|
30
|
+
execSync(`git clone ${repoUrl} "${CACHE_DIR}"`, { stdio: "inherit" });
|
|
31
|
+
|
|
32
|
+
return CACHE_DIR;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function pathExists(targetPath) {
|
|
36
|
+
try {
|
|
37
|
+
await fs.access(targetPath);
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function getRepoRoot(repoUrl) {
|
|
45
|
+
if (repoUrl) {
|
|
46
|
+
// Custom repo URL provided
|
|
47
|
+
return await ensureRepo(repoUrl);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check if we're running from the repo itself (development mode)
|
|
51
|
+
const currentDir = __dirname;
|
|
52
|
+
const repoRoot = path.resolve(currentDir, "..");
|
|
53
|
+
const hasRepoStructure =
|
|
54
|
+
(await pathExists(path.join(repoRoot, ".cursor", "skills"))) ||
|
|
55
|
+
(await pathExists(path.join(repoRoot, "rules")));
|
|
56
|
+
|
|
57
|
+
if (hasRepoStructure) {
|
|
58
|
+
// Running from repo itself, use current directory
|
|
59
|
+
return repoRoot;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Use cached repo
|
|
63
|
+
return await ensureRepo();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
getRepoRoot,
|
|
68
|
+
ensureRepo,
|
|
69
|
+
};
|