ai-engineering-init 1.5.0 → 1.6.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 +20 -236
- package/bin/index.js +437 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,267 +7,51 @@
|
|
|
7
7
|
|
|
8
8
|
## 快速开始
|
|
9
9
|
|
|
10
|
-
### 方式一:npx(推荐,无需克隆)
|
|
11
|
-
|
|
12
10
|
```bash
|
|
13
|
-
# 交互式选择
|
|
14
11
|
npx ai-engineering-init
|
|
15
|
-
|
|
16
|
-
# 直接指定工具
|
|
17
|
-
npx ai-engineering-init --tool claude # 初始化 Claude Code
|
|
18
|
-
npx ai-engineering-init --tool cursor # 初始化 Cursor
|
|
19
|
-
npx ai-engineering-init --tool codex # 初始化 OpenAI Codex
|
|
20
|
-
npx ai-engineering-init --tool all # 初始化全部
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### 方式二:Shell 脚本(远程执行)
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
# 交互式
|
|
27
|
-
bash <(curl -fsSL https://raw.githubusercontent.com/xu-cell/ai-engineering-init/main/init.sh)
|
|
28
|
-
|
|
29
|
-
# 直接指定工具
|
|
30
|
-
bash <(curl -fsSL https://raw.githubusercontent.com/xu-cell/ai-engineering-init/main/init.sh) --tool claude
|
|
31
|
-
bash <(curl -fsSL https://raw.githubusercontent.com/xu-cell/ai-engineering-init/main/init.sh) --tool cursor
|
|
32
12
|
```
|
|
33
13
|
|
|
34
|
-
|
|
14
|
+
交互式选择工具,或直接指定:
|
|
35
15
|
|
|
36
16
|
```bash
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
17
|
+
npx ai-engineering-init --tool claude # Claude Code
|
|
18
|
+
npx ai-engineering-init --tool cursor # Cursor
|
|
19
|
+
npx ai-engineering-init --tool codex # OpenAI Codex
|
|
20
|
+
npx ai-engineering-init --tool all # 全部
|
|
41
21
|
```
|
|
42
22
|
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-
| 工具 | 参数 | 初始化内容 |
|
|
46
|
-
|------|------|-----------|
|
|
47
|
-
| Claude Code | `--tool claude` | `.claude/` 目录 + `CLAUDE.md` |
|
|
48
|
-
| Cursor | `--tool cursor` | `.cursor/` 目录(Skills + Agents + MCP) |
|
|
49
|
-
| OpenAI Codex | `--tool codex` | `.codex/` 目录 + `AGENTS.md` |
|
|
50
|
-
| 全部 | `--tool all` | 以上全部 |
|
|
51
|
-
|
|
52
|
-
## 命令与选项
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
# 命令
|
|
56
|
-
npx ai-engineering-init # 交互式初始化(安装到当前项目)
|
|
57
|
-
npx ai-engineering-init update # 更新已安装的框架文件
|
|
58
|
-
npx ai-engineering-init global # 全局安装到 ~/.claude 等,对所有项目生效
|
|
59
|
-
|
|
60
|
-
# 选项
|
|
61
|
-
--tool, -t <工具> 指定工具: claude | cursor | codex | all
|
|
62
|
-
--dir, -d <目录> 目标目录(默认:当前目录,仅 init/update 有效)
|
|
63
|
-
--force,-f 强制覆盖(init 覆盖已有文件;update/global 同时更新保留文件)
|
|
64
|
-
--help, -h 显示帮助
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## 包含内容
|
|
68
|
-
|
|
69
|
-
### Claude Code(`.claude/`)
|
|
70
|
-
|
|
71
|
-
| 目录/文件 | 说明 |
|
|
72
|
-
|-----------|------|
|
|
73
|
-
| `skills/`(68个) | 业务技能:CRUD、API、数据库、安全、性能、leniu 云食堂专项等 |
|
|
74
|
-
| `commands/`(10个) | 快捷命令:`/dev`、`/crud`、`/check`、`/start`、`/progress` 等 |
|
|
75
|
-
| `agents/`(2个) | 子代理:`code-reviewer`(代码审查)、`project-manager`(项目管理) |
|
|
76
|
-
| `hooks/` | 自动化钩子:技能强制评估(UserPromptSubmit)、代码规范检查(PreToolUse) |
|
|
77
|
-
| `CLAUDE.md` | 项目规范说明(包名、架构、工具类、禁止事项等) |
|
|
78
|
-
|
|
79
|
-
### Cursor(`.cursor/`)
|
|
80
|
-
|
|
81
|
-
| 目录/文件 | 说明 |
|
|
82
|
-
|-----------|------|
|
|
83
|
-
| `skills/`(68个) | 与 `.claude/skills/` 完全同步,支持 `@技能名` 手动调用或 Agent 自动委托 |
|
|
84
|
-
| `agents/`(2个) | Subagents:`code-reviewer`(`readonly: true`)、`project-manager` |
|
|
85
|
-
| `mcp.json` | MCP 服务器配置:`sequential-thinking`、`context7`、`github` |
|
|
86
|
-
| `hooks.json` | Hooks 配置:技能文档引导(beforeSubmitPrompt)、危险命令拦截(preToolUse)、完成音效(stop) |
|
|
87
|
-
| `hooks/` | Hooks 脚本:`cursor-skill-eval.js`、`cursor-pre-tool-use.js` |
|
|
88
|
-
|
|
89
|
-
### OpenAI Codex(`.codex/`)
|
|
90
|
-
|
|
91
|
-
| 目录/文件 | 说明 |
|
|
92
|
-
|-----------|------|
|
|
93
|
-
| `skills/` | Codex 技能配置 |
|
|
94
|
-
| `AGENTS.md` | AI Agent 项目规范说明 |
|
|
95
|
-
|
|
96
|
-
> **MCP Server 支持**:Codex CLI 可通过 `codex mcp-server` 作为 MCP Server 暴露给 Claude Code,
|
|
97
|
-
> 配置写入 `.claude/settings.json` 的 `mcpServers` 后,Claude 可直接调用 `codex` / `codex-reply` 工具进行代码审查。
|
|
98
|
-
>
|
|
99
|
-
> **Windows 用户注意**:初始化后需将 `.claude/settings.json` 中 `mcpServers.codex.command` 的路径改为 Windows 上的实际路径,例如:
|
|
100
|
-
> ```json
|
|
101
|
-
> "command": "C:\\Users\\YourName\\AppData\\Roaming\\npm\\codex.cmd"
|
|
102
|
-
> ```
|
|
103
|
-
> 可通过 `where codex` 命令查询实际路径。
|
|
104
|
-
|
|
105
|
-
## Skills 列表(69个)
|
|
106
|
-
|
|
107
|
-
<details>
|
|
108
|
-
<summary>展开查看完整列表</summary>
|
|
109
|
-
|
|
110
|
-
**通用后端技能(34个)**
|
|
111
|
-
|
|
112
|
-
| 技能 | 触发场景 |
|
|
113
|
-
|------|---------|
|
|
114
|
-
| `crud-development` | CRUD 开发、业务模块、Entity/Service/Controller |
|
|
115
|
-
| `api-development` | RESTful API 设计、接口规范 |
|
|
116
|
-
| `database-ops` | 建表、SQL、字典、菜单配置 |
|
|
117
|
-
| `backend-annotations` | `@RateLimiter`、`@RepeatSubmit`、`@DataPermission` |
|
|
118
|
-
| `utils-toolkit` | StringUtils、MapstructUtils、StreamUtils 等工具类 |
|
|
119
|
-
| `architecture-design` | 系统架构、模块划分、代码重构 |
|
|
120
|
-
| `code-patterns` | 编码规范、命名规范、禁止事项 |
|
|
121
|
-
| `error-handler` | 异常处理、ServiceException、全局错误码 |
|
|
122
|
-
| `security-guard` | Sa-Token 认证授权、数据脱敏、加密 |
|
|
123
|
-
| `data-permission` | 行级数据权限、部门隔离 |
|
|
124
|
-
| `performance-doctor` | 慢查询优化、缓存策略、N+1 问题 |
|
|
125
|
-
| `redis-cache` | Redis 缓存、分布式锁、`@Cacheable` |
|
|
126
|
-
| `json-serialization` | JSON 序列化、BigDecimal 精度、日期格式 |
|
|
127
|
-
| `scheduled-jobs` | 定时任务、SnailJob、`@Scheduled` |
|
|
128
|
-
| `websocket-sse` | WebSocket、SSE 实时推送、消息通知 |
|
|
129
|
-
| `workflow-engine` | 工作流、审批流、WarmFlow |
|
|
130
|
-
| `file-oss-management` | 文件上传、OSS、MinIO |
|
|
131
|
-
| `sms-mail` | 短信、邮件、SMS4j |
|
|
132
|
-
| `social-login` | 第三方登录、OAuth2、JustAuth |
|
|
133
|
-
| `tenant-management` | 多租户隔离、TenantEntity |
|
|
134
|
-
| `test-development` | 单元测试、JUnit5、Mockito |
|
|
135
|
-
| `git-workflow` | Git 提交规范、分支策略 |
|
|
136
|
-
| `bug-detective` | Bug 排查、异常定位 |
|
|
137
|
-
| `brainstorm` | 方案设计、头脑风暴 |
|
|
138
|
-
| `tech-decision` | 技术选型、方案对比 |
|
|
139
|
-
| `task-tracker` | 任务进度跟踪 |
|
|
140
|
-
| `project-navigator` | 项目结构导航、文件定位 |
|
|
141
|
-
| `collaborating-with-codex` | 与 Codex 协同开发(支持 MCP Server 直调) |
|
|
142
|
-
| `collaborating-with-gemini` | 与 Gemini 协同开发 |
|
|
143
|
-
| `banana-image` | AI 图片生成、海报、缩略图 |
|
|
144
|
-
| `add-skill` | 创建新技能 |
|
|
145
|
-
| `ui-pc` | 前端 PC 端组件库 |
|
|
146
|
-
| `store-pc` | 前端 Vuex 状态管理 |
|
|
23
|
+
## 命令一览
|
|
147
24
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
| 技能 | 说明 |
|
|
25
|
+
| 命令 | 说明 |
|
|
151
26
|
|------|------|
|
|
152
|
-
| `
|
|
153
|
-
| `
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
156
|
-
| `leniu-error-handler` | LeException、I18n 国际化异常处理 |
|
|
157
|
-
| `leniu-backend-annotations` | `@RequiresAuthentication`、分组校验 |
|
|
158
|
-
| `leniu-utils-toolkit` | BeanUtil、CollUtil、StrUtil、RedisUtil |
|
|
159
|
-
| `leniu-code-patterns` | net.xnzn 包名规范、禁止事项 |
|
|
160
|
-
| `leniu-brainstorm` | 云食堂方案头脑风暴 |
|
|
161
|
-
| `leniu-data-permission` | 云食堂数据权限控制 |
|
|
162
|
-
| `leniu-java-entity` | Entity/VO/DTO/Param 数据类规范 |
|
|
163
|
-
| `leniu-java-mybatis` | MyBatis Plus、LambdaQueryWrapper、XML 映射 |
|
|
164
|
-
| `leniu-java-amount-handling` | 金额分/元转换(Long 类型存储) |
|
|
165
|
-
| `leniu-java-code-style` | 命名规范、注解风格 |
|
|
166
|
-
| `leniu-java-concurrent` | CompletableFuture、线程池、分布式锁 |
|
|
167
|
-
| `leniu-java-export` | Excel 异步导出 |
|
|
168
|
-
| `leniu-java-logging` | `@Slf4j`、日志级别规范 |
|
|
169
|
-
| `leniu-java-mq` | MqUtil、`@MqConsumer`、延迟消息 |
|
|
170
|
-
| `leniu-java-report-query-param` | 报表查询入参 Param 类规范 |
|
|
171
|
-
| `leniu-java-task` | XXL-Job 定时任务 |
|
|
172
|
-
| `leniu-java-total-line` | 报表分页合计行 |
|
|
173
|
-
| `leniu-mealtime` | 餐次(早/午/下午茶/晚/夜宵)处理 |
|
|
174
|
-
| `leniu-redis-cache` | 云食堂 Redis 缓存规范 |
|
|
175
|
-
| `leniu-security-guard` | SQL 注入防护、XSS 防护、限流 |
|
|
176
|
-
| `leniu-marketing-price-rule-customizer` | 营销计费规则定制 |
|
|
177
|
-
| `leniu-marketing-recharge-rule-customizer` | 营销充值规则定制 |
|
|
178
|
-
|
|
179
|
-
**OpenSpec 工作流技能(10个)**
|
|
180
|
-
|
|
181
|
-
`openspec-new-change`、`openspec-ff-change`、`openspec-apply-change`、`openspec-continue-change`、`openspec-archive-change`、`openspec-bulk-archive-change`、`openspec-explore`、`openspec-onboard`、`openspec-sync-specs`、`openspec-verify-change`
|
|
27
|
+
| `npx ai-engineering-init` | 交互式初始化到当前项目 |
|
|
28
|
+
| `npx ai-engineering-init@latest update` | 更新已安装的框架文件 |
|
|
29
|
+
| `npx ai-engineering-init@latest global` | 全局安装到 `~/.claude` 等,所有项目生效 |
|
|
30
|
+
| `npx ai-engineering-init sync-back` | 对比本地技能修改,反馈回源仓库 |
|
|
182
31
|
|
|
183
|
-
|
|
32
|
+
> 所有命令均支持 `--tool <claude|cursor|codex|all>` 指定工具。运行 `--help` 查看全部选项。
|
|
184
33
|
|
|
185
34
|
## 初始化后使用
|
|
186
35
|
|
|
187
36
|
### Claude Code
|
|
188
37
|
|
|
189
|
-
1.
|
|
190
|
-
2. 输入 `/start`
|
|
191
|
-
3. 输入 `/dev` 开始开发新功能
|
|
192
|
-
4. 输入 `/crud` 快速生成 CRUD 代码
|
|
193
|
-
5. 输入 `/check` 检查代码规范
|
|
38
|
+
1. 按需修改 `CLAUDE.md` 中的项目信息
|
|
39
|
+
2. 输入 `/start` 快速了解项目,`/dev` 开发新功能,`/crud` 生成 CRUD,`/check` 检查规范
|
|
194
40
|
|
|
195
41
|
### Cursor
|
|
196
42
|
|
|
197
|
-
1. 在 Chat 中输入 `/`
|
|
198
|
-
2.
|
|
199
|
-
3. Subagents 会根据任务自动委托:`/code-reviewer`、`/project-manager`
|
|
200
|
-
4. 在 Settings → MCP 中确认 MCP 服务器已连接
|
|
201
|
-
|
|
202
|
-
#### OpenSpec 规格驱动开发(opsx 命令)
|
|
203
|
-
|
|
204
|
-
基于 [OpenSpec](https://github.com/Fission-AI/OpenSpec) 的规格驱动开发工作流,在 Cursor Chat 中通过 `/opsx-*` 命令使用:
|
|
205
|
-
|
|
206
|
-
| 命令 | 用途 |
|
|
207
|
-
|------|------|
|
|
208
|
-
| `/opsx-new` | 新建变更,描述需求并创建规格文档 |
|
|
209
|
-
| `/opsx-ff` | 快速推进,一次性生成所有制品(规格+任务) |
|
|
210
|
-
| `/opsx-apply` | 开始实现,按任务清单编码 |
|
|
211
|
-
| `/opsx-continue` | 继续变更,创建下一个制品 |
|
|
212
|
-
| `/opsx-verify` | 验证实现是否与规格匹配 |
|
|
213
|
-
| `/opsx-sync` | 将 delta 规格同步到主规格 |
|
|
214
|
-
| `/opsx-archive` | 归档已完成的变更 |
|
|
215
|
-
| `/opsx-bulk-archive` | 批量归档多个变更 |
|
|
216
|
-
| `/opsx-explore` | 探索模式,思维伙伴式问题分析 |
|
|
217
|
-
| `/opsx-onboard` | 新手引导,完整工作流演示 |
|
|
218
|
-
|
|
219
|
-
**标准开发流程**:`/opsx-new` → `/opsx-ff` → `/opsx-apply` → `/opsx-verify` → `/opsx-archive`
|
|
43
|
+
1. 在 Chat 中输入 `/` 查看可用 Skills,或 `@技能名` 手动调用
|
|
44
|
+
2. 在 Settings → MCP 中确认 MCP 服务器已连接
|
|
220
45
|
|
|
221
46
|
### OpenAI Codex
|
|
222
47
|
|
|
223
|
-
1.
|
|
224
|
-
2. 使用 `.codex/skills/` 下的技能辅助开发
|
|
225
|
-
3. (可选)以 MCP Server 接入 Claude Code:`.claude/settings.json` → `mcpServers.codex`,重启后 Claude 可直接调用 `codex` / `codex-reply` 工具
|
|
226
|
-
- **Windows 用户**:将 `command` 路径改为 `where codex` 查询到的实际路径(如 `C:\Users\YourName\AppData\Roaming\npm\codex.cmd`)
|
|
227
|
-
|
|
228
|
-
## 更新已安装的项目
|
|
229
|
-
|
|
230
|
-
当有新版本发布时,在已安装的项目根目录执行:
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
# 必须加 @latest,确保使用最新源文件
|
|
234
|
-
npx ai-engineering-init@latest update
|
|
235
|
-
|
|
236
|
-
# 只更新指定工具
|
|
237
|
-
npx ai-engineering-init@latest update --tool cursor
|
|
238
|
-
npx ai-engineering-init@latest update --tool claude
|
|
239
|
-
|
|
240
|
-
# 强制更新,包括 settings.json / CLAUDE.md 等保留文件
|
|
241
|
-
npx ai-engineering-init@latest update --force
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
> **注意**:不加 `@latest` 会使用 npx 本地缓存的旧版本,源文件不是最新的。
|
|
245
|
-
|
|
246
|
-
## 全局安装(v1.3.0+)
|
|
247
|
-
|
|
248
|
-
一次安装,所有项目自动生效:
|
|
249
|
-
|
|
250
|
-
```bash
|
|
251
|
-
# 全局安装所有工具(~/.claude + ~/.cursor + ~/.codex)
|
|
252
|
-
npx ai-engineering-init@latest global
|
|
253
|
-
|
|
254
|
-
# 只全局安装指定工具
|
|
255
|
-
npx ai-engineering-init@latest global --tool claude
|
|
256
|
-
npx ai-engineering-init@latest global --tool cursor
|
|
257
|
-
|
|
258
|
-
# 强制覆盖已有全局文件
|
|
259
|
-
npx ai-engineering-init@latest global --force
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
> **说明**:全局安装将 Skills/Commands/Hooks 安装到 `~/.claude` / `~/.cursor` / `~/.codex`。`settings.json`、`mcp.json` 采用合并策略,不覆盖用户已有配置。项目级配置优先级高于全局配置,两者可共存。
|
|
48
|
+
1. 按需修改 `AGENTS.md`,使用 `.codex/skills/` 下的技能辅助开发
|
|
263
49
|
|
|
264
|
-
##
|
|
50
|
+
## 更多信息
|
|
265
51
|
|
|
266
|
-
|
|
52
|
+
[参考文档](./docs/reference.md) — Skills 列表(69个)、包含内容、命令详情、其他安装方式、全部选项
|
|
267
53
|
|
|
268
|
-
|
|
269
|
-
**v1.2.7 修复**:Cursor `beforeSubmitPrompt` hook 输出格式改为 JSON,修复技能注入失效。
|
|
270
|
-
**v1.2.6 修复**:Cursor stop hook 自包含,不再依赖 Claude Code 安装。
|
|
54
|
+
[更新日志](./CHANGELOG.md) — 完整版本变更记录
|
|
271
55
|
|
|
272
56
|
## License
|
|
273
57
|
|
package/bin/index.js
CHANGED
|
@@ -46,10 +46,12 @@ console.log('');
|
|
|
46
46
|
|
|
47
47
|
// ── 参数解析 ───────────────────────────────────────────────────────────────
|
|
48
48
|
const args = process.argv.slice(2);
|
|
49
|
-
let command = ''; // 'update' | 'global' | ''
|
|
49
|
+
let command = ''; // 'update' | 'global' | 'sync-back' | ''
|
|
50
50
|
let tool = '';
|
|
51
51
|
let targetDir = process.cwd();
|
|
52
52
|
let force = false;
|
|
53
|
+
let skillFilter = ''; // sync-back --skill <名称>
|
|
54
|
+
let submitIssue = false; // sync-back --submit
|
|
53
55
|
|
|
54
56
|
for (let i = 0; i < args.length; i++) {
|
|
55
57
|
const arg = args[i];
|
|
@@ -60,6 +62,9 @@ for (let i = 0; i < args.length; i++) {
|
|
|
60
62
|
case 'global':
|
|
61
63
|
command = 'global';
|
|
62
64
|
break;
|
|
65
|
+
case 'sync-back':
|
|
66
|
+
command = 'sync-back';
|
|
67
|
+
break;
|
|
63
68
|
case '--tool': case '-t':
|
|
64
69
|
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
65
70
|
console.error(fmt('red', `错误:${arg} 需要一个值(claude | cursor | codex | all)`));
|
|
@@ -77,6 +82,16 @@ for (let i = 0; i < args.length; i++) {
|
|
|
77
82
|
case '--force': case '-f':
|
|
78
83
|
force = true;
|
|
79
84
|
break;
|
|
85
|
+
case '--skill': case '-s':
|
|
86
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
87
|
+
console.error(fmt('red', `错误:${arg} 需要一个技能名称`));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
skillFilter = args[++i];
|
|
91
|
+
break;
|
|
92
|
+
case '--submit':
|
|
93
|
+
submitIssue = true;
|
|
94
|
+
break;
|
|
80
95
|
case '--help': case '-h':
|
|
81
96
|
printHelp();
|
|
82
97
|
process.exit(0);
|
|
@@ -96,12 +111,15 @@ function printHelp() {
|
|
|
96
111
|
console.log('命令:');
|
|
97
112
|
console.log(` ${fmt('bold', '(无)')} 交互式初始化(安装到当前项目目录)`);
|
|
98
113
|
console.log(` ${fmt('bold', 'update')} 更新已安装的框架文件(跳过用户自定义文件)`);
|
|
99
|
-
console.log(` ${fmt('bold', 'global')} 全局安装到 ~/.claude / ~/.cursor
|
|
114
|
+
console.log(` ${fmt('bold', 'global')} 全局安装到 ~/.claude / ~/.cursor 等,对所有项目生效`);
|
|
115
|
+
console.log(` ${fmt('bold', 'sync-back')} 对比本地技能修改,生成 diff 或提交 GitHub Issue\n`);
|
|
100
116
|
console.log('选项:');
|
|
101
|
-
console.log(' --tool,
|
|
102
|
-
console.log(' --dir,
|
|
103
|
-
console.log(' --force
|
|
104
|
-
console.log(' --
|
|
117
|
+
console.log(' --tool, -t <工具> 指定工具: claude | cursor | codex | all');
|
|
118
|
+
console.log(' --dir, -d <目录> 目标目录(默认:当前目录,仅 init/update 有效)');
|
|
119
|
+
console.log(' --force, -f 强制覆盖(init 时覆盖已有文件;update/global 时同时更新保留文件)');
|
|
120
|
+
console.log(' --skill, -s <技能> sync-back 时只对比指定技能');
|
|
121
|
+
console.log(' --submit sync-back 时自动创建 GitHub Issue(需要 gh CLI)');
|
|
122
|
+
console.log(' --help, -h 显示此帮助\n');
|
|
105
123
|
console.log('示例:');
|
|
106
124
|
console.log(' npx ai-engineering-init --tool claude');
|
|
107
125
|
console.log(' npx ai-engineering-init --tool all --dir /path/to/project');
|
|
@@ -109,7 +127,11 @@ function printHelp() {
|
|
|
109
127
|
console.log(' npx ai-engineering-init update --tool claude # 只更新 Claude');
|
|
110
128
|
console.log(' npx ai-engineering-init update --force # 强制更新,包括保留文件');
|
|
111
129
|
console.log(' npx ai-engineering-init global # 全局安装所有工具');
|
|
112
|
-
console.log(' npx ai-engineering-init global --tool claude # 只全局安装 Claude
|
|
130
|
+
console.log(' npx ai-engineering-init global --tool claude # 只全局安装 Claude');
|
|
131
|
+
console.log(' npx ai-engineering-init sync-back # 扫描所有已安装工具');
|
|
132
|
+
console.log(' npx ai-engineering-init sync-back --tool claude # 只扫描 Claude');
|
|
133
|
+
console.log(' npx ai-engineering-init sync-back --skill bug-detective # 只对比指定技能');
|
|
134
|
+
console.log(' npx ai-engineering-init sync-back --skill bug-detective --submit # 提交 Issue\n');
|
|
113
135
|
}
|
|
114
136
|
|
|
115
137
|
// ── 工具定义(init 用)────────────────────────────────────────────────────
|
|
@@ -675,11 +697,419 @@ function runUpdate(selectedTool) {
|
|
|
675
697
|
if (totalFailed > 0) process.exitCode = 1;
|
|
676
698
|
}
|
|
677
699
|
|
|
700
|
+
// ── SYNC-BACK 逻辑 ─────────────────────────────────────────────────────────
|
|
701
|
+
|
|
702
|
+
/** 技能目录名称到工具的映射 */
|
|
703
|
+
const SKILL_DIRS = {
|
|
704
|
+
claude: '.claude/skills',
|
|
705
|
+
cursor: '.cursor/skills',
|
|
706
|
+
codex: '.codex/skills',
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
/** 递归列出目录下所有文件(相对路径) */
|
|
710
|
+
function listFilesRecursive(dir, prefix) {
|
|
711
|
+
prefix = prefix || '';
|
|
712
|
+
let results = [];
|
|
713
|
+
let entries;
|
|
714
|
+
try { entries = fs.readdirSync(dir); } catch { return results; }
|
|
715
|
+
for (const entry of entries) {
|
|
716
|
+
const fullPath = path.join(dir, entry);
|
|
717
|
+
const relPath = prefix ? prefix + '/' + entry : entry;
|
|
718
|
+
try {
|
|
719
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
720
|
+
results = results.concat(listFilesRecursive(fullPath, relPath));
|
|
721
|
+
} else {
|
|
722
|
+
results.push(relPath);
|
|
723
|
+
}
|
|
724
|
+
} catch { /* 跳过不可读文件 */ }
|
|
725
|
+
}
|
|
726
|
+
return results;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* 简易 unified diff 生成器(纯 Node.js,零依赖)
|
|
731
|
+
* 使用贪心 LCS 简化算法,输出标准 unified diff 格式
|
|
732
|
+
*/
|
|
733
|
+
function generateDiff(oldContent, newContent, oldLabel, newLabel) {
|
|
734
|
+
const oldLines = oldContent.split(/\r?\n/);
|
|
735
|
+
const newLines = newContent.split(/\r?\n/);
|
|
736
|
+
|
|
737
|
+
// 计算 LCS 表(简化版,O(n*m) 但对技能文件足够)
|
|
738
|
+
const m = oldLines.length;
|
|
739
|
+
const n = newLines.length;
|
|
740
|
+
|
|
741
|
+
// 对于大文件,跳过 LCS 直接标记全部替换
|
|
742
|
+
if (m * n > 1000000) {
|
|
743
|
+
const lines = [];
|
|
744
|
+
lines.push(`--- ${oldLabel}`);
|
|
745
|
+
lines.push(`+++ ${newLabel}`);
|
|
746
|
+
lines.push(`@@ -1,${m} +1,${n} @@`);
|
|
747
|
+
for (const line of oldLines) lines.push('-' + line);
|
|
748
|
+
for (const line of newLines) lines.push('+' + line);
|
|
749
|
+
return lines.join('\n');
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// LCS 回溯表
|
|
753
|
+
const dp = Array.from({ length: m + 1 }, () => new Uint16Array(n + 1));
|
|
754
|
+
for (let i = 1; i <= m; i++) {
|
|
755
|
+
for (let j = 1; j <= n; j++) {
|
|
756
|
+
if (oldLines[i - 1] === newLines[j - 1]) {
|
|
757
|
+
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
758
|
+
} else {
|
|
759
|
+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// 回溯生成编辑操作序列
|
|
765
|
+
const ops = []; // { type: 'equal'|'delete'|'insert', line }
|
|
766
|
+
let i = m, j = n;
|
|
767
|
+
while (i > 0 || j > 0) {
|
|
768
|
+
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
|
769
|
+
ops.push({ type: 'equal', oldIdx: i, newIdx: j, line: oldLines[i - 1] });
|
|
770
|
+
i--; j--;
|
|
771
|
+
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
|
772
|
+
ops.push({ type: 'insert', newIdx: j, line: newLines[j - 1] });
|
|
773
|
+
j--;
|
|
774
|
+
} else {
|
|
775
|
+
ops.push({ type: 'delete', oldIdx: i, line: oldLines[i - 1] });
|
|
776
|
+
i--;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
ops.reverse();
|
|
780
|
+
|
|
781
|
+
// 将操作序列组织为 hunks(上下文 3 行)
|
|
782
|
+
const CTX = 3;
|
|
783
|
+
const hunks = [];
|
|
784
|
+
let hunkOps = [];
|
|
785
|
+
let lastChangeIdx = -999;
|
|
786
|
+
|
|
787
|
+
for (let k = 0; k < ops.length; k++) {
|
|
788
|
+
if (ops[k].type !== 'equal') {
|
|
789
|
+
// 如果距离上次变更超过 2*CTX+1,开始新 hunk
|
|
790
|
+
if (k - lastChangeIdx > 2 * CTX + 1 && hunkOps.length > 0) {
|
|
791
|
+
hunks.push(hunkOps);
|
|
792
|
+
hunkOps = [];
|
|
793
|
+
// 回退加上下文
|
|
794
|
+
const start = Math.max(k - CTX, lastChangeIdx + CTX + 1);
|
|
795
|
+
for (let c = start; c < k; c++) {
|
|
796
|
+
if (ops[c]) hunkOps.push(ops[c]);
|
|
797
|
+
}
|
|
798
|
+
} else if (hunkOps.length === 0) {
|
|
799
|
+
// 新 hunk 加前上下文
|
|
800
|
+
const start = Math.max(0, k - CTX);
|
|
801
|
+
for (let c = start; c < k; c++) {
|
|
802
|
+
hunkOps.push(ops[c]);
|
|
803
|
+
}
|
|
804
|
+
} else {
|
|
805
|
+
// 补充中间的上下文行
|
|
806
|
+
for (let c = lastChangeIdx + 1; c < k; c++) {
|
|
807
|
+
if (!hunkOps.includes(ops[c])) hunkOps.push(ops[c]);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
hunkOps.push(ops[k]);
|
|
811
|
+
lastChangeIdx = k;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
// 最后一个 hunk 加后上下文
|
|
815
|
+
if (hunkOps.length > 0) {
|
|
816
|
+
const end = Math.min(ops.length, lastChangeIdx + CTX + 1);
|
|
817
|
+
for (let c = lastChangeIdx + 1; c < end; c++) {
|
|
818
|
+
hunkOps.push(ops[c]);
|
|
819
|
+
}
|
|
820
|
+
hunks.push(hunkOps);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (hunks.length === 0) return ''; // 无差异
|
|
824
|
+
|
|
825
|
+
// 格式化输出
|
|
826
|
+
const lines = [];
|
|
827
|
+
lines.push(`--- ${oldLabel}`);
|
|
828
|
+
lines.push(`+++ ${newLabel}`);
|
|
829
|
+
|
|
830
|
+
for (const hunk of hunks) {
|
|
831
|
+
// 计算 hunk 头
|
|
832
|
+
let oldStart = Infinity, oldCount = 0, newStart = Infinity, newCount = 0;
|
|
833
|
+
for (const op of hunk) {
|
|
834
|
+
if (op.type === 'equal' || op.type === 'delete') {
|
|
835
|
+
if (op.oldIdx < oldStart) oldStart = op.oldIdx;
|
|
836
|
+
oldCount++;
|
|
837
|
+
}
|
|
838
|
+
if (op.type === 'equal' || op.type === 'insert') {
|
|
839
|
+
if (op.newIdx < newStart) newStart = op.newIdx;
|
|
840
|
+
newCount++;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
lines.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`);
|
|
844
|
+
for (const op of hunk) {
|
|
845
|
+
if (op.type === 'equal') lines.push(' ' + op.line);
|
|
846
|
+
if (op.type === 'delete') lines.push('-' + op.line);
|
|
847
|
+
if (op.type === 'insert') lines.push('+' + op.line);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return lines.join('\n');
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/** 统计 diff 中的增删行数 */
|
|
855
|
+
function countDiffLines(diffText) {
|
|
856
|
+
let added = 0, removed = 0;
|
|
857
|
+
for (const line of diffText.split('\n')) {
|
|
858
|
+
if (line.startsWith('+') && !line.startsWith('+++')) added++;
|
|
859
|
+
if (line.startsWith('-') && !line.startsWith('---')) removed++;
|
|
860
|
+
}
|
|
861
|
+
return { added, removed };
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/** 检测 gh CLI 是否可用 */
|
|
865
|
+
function isGhAvailable() {
|
|
866
|
+
try {
|
|
867
|
+
const { execSync } = require('child_process');
|
|
868
|
+
execSync('gh --version', { stdio: 'pipe' });
|
|
869
|
+
return true;
|
|
870
|
+
} catch { return false; }
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/** 通过 gh CLI 创建 GitHub Issue */
|
|
874
|
+
function submitGitHubIssue(changes, allDiffText) {
|
|
875
|
+
const { execSync } = require('child_process');
|
|
876
|
+
const skillNames = changes.map(c => c.skillName).join(', ');
|
|
877
|
+
const title = `[sync-back] 技能改进:${skillNames}`;
|
|
878
|
+
const body = [
|
|
879
|
+
'## 技能修改反馈',
|
|
880
|
+
'',
|
|
881
|
+
`> 由 \`npx ai-engineering-init sync-back --submit\` 自动生成`,
|
|
882
|
+
'',
|
|
883
|
+
'### 修改的技能',
|
|
884
|
+
'',
|
|
885
|
+
...changes.map(c => {
|
|
886
|
+
const files = c.files.map(f => ` - \`${f.relPath}\` (+${f.added}, -${f.removed})`).join('\n');
|
|
887
|
+
return `- **${c.skillName}**\n${files}`;
|
|
888
|
+
}),
|
|
889
|
+
'',
|
|
890
|
+
'### Diff',
|
|
891
|
+
'',
|
|
892
|
+
'```diff',
|
|
893
|
+
allDiffText,
|
|
894
|
+
'```',
|
|
895
|
+
'',
|
|
896
|
+
'---',
|
|
897
|
+
`CLI 版本: v${PKG_VERSION}`,
|
|
898
|
+
].join('\n');
|
|
899
|
+
|
|
900
|
+
try {
|
|
901
|
+
const result = execSync(
|
|
902
|
+
`gh issue create --repo xu-cell/ai-engineering-init --title "${title.replace(/"/g, '\\"')}" --body-file -`,
|
|
903
|
+
{ input: body, stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf8' }
|
|
904
|
+
);
|
|
905
|
+
return result.trim();
|
|
906
|
+
} catch (e) {
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/** sync-back 命令主流程 */
|
|
912
|
+
function runSyncBack(selectedTool, selectedSkill, doSubmit) {
|
|
913
|
+
console.log(` 目标目录: ${fmt('bold', targetDir)}`);
|
|
914
|
+
console.log(` 本机版本: ${fmt('bold', `v${PKG_VERSION}`)}`);
|
|
915
|
+
console.log('');
|
|
916
|
+
|
|
917
|
+
// 1. 确定扫描范围
|
|
918
|
+
let toolsToScan = [];
|
|
919
|
+
if (selectedTool && selectedTool !== 'all') {
|
|
920
|
+
if (!SKILL_DIRS[selectedTool]) {
|
|
921
|
+
console.error(fmt('red', `无效工具: "${selectedTool}"。有效选项: claude | cursor | codex | all`));
|
|
922
|
+
process.exit(1);
|
|
923
|
+
}
|
|
924
|
+
toolsToScan = [selectedTool];
|
|
925
|
+
} else {
|
|
926
|
+
toolsToScan = detectInstalledTools();
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (toolsToScan.length === 0) {
|
|
930
|
+
console.log(fmt('yellow', '⚠ 当前目录未检测到已安装的 AI 工具配置。'));
|
|
931
|
+
console.log(` 请先运行: ${fmt('bold', hintCmd('--tool claude'))}\n`);
|
|
932
|
+
process.exit(1);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
console.log(` 扫描工具: ${fmt('bold', toolsToScan.join(', '))}`);
|
|
936
|
+
console.log('');
|
|
937
|
+
console.log(fmt('bold', '🔍 正在对比技能文件...'));
|
|
938
|
+
console.log('');
|
|
939
|
+
|
|
940
|
+
// 2. 对比每个工具的 skills 目录
|
|
941
|
+
const allChanges = []; // { toolKey, skillName, files: [{ relPath, diff, added, removed }] }
|
|
942
|
+
|
|
943
|
+
for (const toolKey of toolsToScan) {
|
|
944
|
+
const skillDir = SKILL_DIRS[toolKey];
|
|
945
|
+
const userSkillsDir = path.join(targetDir, skillDir);
|
|
946
|
+
const srcSkillsDir = path.join(SOURCE_DIR, skillDir);
|
|
947
|
+
|
|
948
|
+
if (!isRealDir(userSkillsDir) || !isRealDir(srcSkillsDir)) continue;
|
|
949
|
+
|
|
950
|
+
// 列出用户目录中的技能
|
|
951
|
+
let skillNames;
|
|
952
|
+
try { skillNames = fs.readdirSync(userSkillsDir); } catch { continue; }
|
|
953
|
+
|
|
954
|
+
for (const name of skillNames) {
|
|
955
|
+
if (selectedSkill && name !== selectedSkill) continue;
|
|
956
|
+
|
|
957
|
+
const userSkillDir = path.join(userSkillsDir, name);
|
|
958
|
+
const srcSkillDir = path.join(srcSkillsDir, name);
|
|
959
|
+
|
|
960
|
+
if (!isRealDir(userSkillDir)) continue;
|
|
961
|
+
|
|
962
|
+
// 列出用户技能目录下所有文件
|
|
963
|
+
const userFiles = listFilesRecursive(userSkillDir);
|
|
964
|
+
const srcFiles = isRealDir(srcSkillDir) ? listFilesRecursive(srcSkillDir) : [];
|
|
965
|
+
const allFiles = [...new Set([...userFiles, ...srcFiles])].sort();
|
|
966
|
+
|
|
967
|
+
const changedFiles = [];
|
|
968
|
+
|
|
969
|
+
for (const relFile of allFiles) {
|
|
970
|
+
const userFile = path.join(userSkillDir, relFile);
|
|
971
|
+
const srcFile = path.join(srcSkillDir, relFile);
|
|
972
|
+
|
|
973
|
+
const userExists = fs.existsSync(userFile);
|
|
974
|
+
const srcExists = fs.existsSync(srcFile);
|
|
975
|
+
|
|
976
|
+
if (userExists && srcExists) {
|
|
977
|
+
// 两边都有,对比内容
|
|
978
|
+
const userContent = fs.readFileSync(userFile, 'utf8');
|
|
979
|
+
const srcContent = fs.readFileSync(srcFile, 'utf8');
|
|
980
|
+
if (userContent !== srcContent) {
|
|
981
|
+
const diff = generateDiff(srcContent, userContent,
|
|
982
|
+
`原版 (v${PKG_VERSION})`, '本地修改');
|
|
983
|
+
if (diff) {
|
|
984
|
+
const { added, removed } = countDiffLines(diff);
|
|
985
|
+
changedFiles.push({ relPath: relFile, diff, added, removed, status: 'modified' });
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
} else if (userExists && !srcExists) {
|
|
989
|
+
// 用户新增的文件
|
|
990
|
+
const content = fs.readFileSync(userFile, 'utf8');
|
|
991
|
+
const lineCount = content.split(/\r?\n/).length;
|
|
992
|
+
changedFiles.push({
|
|
993
|
+
relPath: relFile,
|
|
994
|
+
diff: `--- /dev/null\n+++ 本地新增\n@@ -0,0 +1,${lineCount} @@\n` +
|
|
995
|
+
content.split(/\r?\n/).map(l => '+' + l).join('\n'),
|
|
996
|
+
added: lineCount, removed: 0, status: 'added'
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
// srcExists && !userExists: 用户删除的文件(不报告,可能是有意删除)
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (changedFiles.length > 0) {
|
|
1003
|
+
// 去重:只保留第一个工具的结果(多工具 skills 内容相同)
|
|
1004
|
+
const existing = allChanges.find(c => c.skillName === name);
|
|
1005
|
+
if (!existing) {
|
|
1006
|
+
allChanges.push({ toolKey, skillName: name, files: changedFiles });
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// 3. 展示结果
|
|
1013
|
+
if (allChanges.length === 0) {
|
|
1014
|
+
console.log(fmt('green', ' ✓ 未检测到技能修改,所有技能与包版本一致。'));
|
|
1015
|
+
console.log('');
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
console.log(` 检测到 ${fmt('bold', String(allChanges.length))} 个技能有修改:`);
|
|
1020
|
+
console.log('');
|
|
1021
|
+
|
|
1022
|
+
for (let idx = 0; idx < allChanges.length; idx++) {
|
|
1023
|
+
const change = allChanges[idx];
|
|
1024
|
+
console.log(` ${fmt('bold', String(idx + 1) + '.')} ${fmt('cyan', change.skillName)}`);
|
|
1025
|
+
for (const file of change.files) {
|
|
1026
|
+
const statusLabel = file.status === 'added' ? fmt('green', '新增文件') : fmt('yellow', '修改文件');
|
|
1027
|
+
console.log(` ${statusLabel}: ${file.relPath} (${fmt('green', '+' + file.added)} 行, ${fmt('red', '-' + file.removed)} 行)`);
|
|
1028
|
+
}
|
|
1029
|
+
console.log('');
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// 4. 展示 diff 详情
|
|
1033
|
+
const allDiffParts = [];
|
|
1034
|
+
|
|
1035
|
+
for (const change of allChanges) {
|
|
1036
|
+
for (const file of change.files) {
|
|
1037
|
+
const header = `${change.skillName}/${file.relPath}`;
|
|
1038
|
+
console.log(fmt('bold', '─'.repeat(Math.min(50, header.length + 10))));
|
|
1039
|
+
console.log(`📋 ${fmt('bold', header)} 的变更:`);
|
|
1040
|
+
console.log(fmt('bold', '─'.repeat(Math.min(50, header.length + 10))));
|
|
1041
|
+
|
|
1042
|
+
// 着色 diff 输出
|
|
1043
|
+
for (const line of file.diff.split('\n')) {
|
|
1044
|
+
if (line.startsWith('+++') || line.startsWith('---')) {
|
|
1045
|
+
console.log(fmt('bold', line));
|
|
1046
|
+
} else if (line.startsWith('+')) {
|
|
1047
|
+
console.log(fmt('green', line));
|
|
1048
|
+
} else if (line.startsWith('-')) {
|
|
1049
|
+
console.log(fmt('red', line));
|
|
1050
|
+
} else if (line.startsWith('@@')) {
|
|
1051
|
+
console.log(fmt('cyan', line));
|
|
1052
|
+
} else {
|
|
1053
|
+
console.log(line);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
console.log('');
|
|
1057
|
+
|
|
1058
|
+
allDiffParts.push(`# ${header}\n${file.diff}`);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const allDiffText = allDiffParts.join('\n\n');
|
|
1063
|
+
|
|
1064
|
+
// 5. 提交 Issue 或提示
|
|
1065
|
+
if (doSubmit) {
|
|
1066
|
+
console.log(fmt('bold', '📤 正在提交 GitHub Issue...'));
|
|
1067
|
+
console.log('');
|
|
1068
|
+
|
|
1069
|
+
if (!isGhAvailable()) {
|
|
1070
|
+
console.log(fmt('yellow', '⚠ 未检测到 gh CLI,无法自动提交 Issue。'));
|
|
1071
|
+
console.log(` 安装方法: ${fmt('bold', 'https://cli.github.com/')}`);
|
|
1072
|
+
console.log('');
|
|
1073
|
+
console.log(fmt('bold', '📋 请手动复制以下内容到 GitHub Issue:'));
|
|
1074
|
+
console.log('');
|
|
1075
|
+
console.log(fmt('cyan', '─'.repeat(50)));
|
|
1076
|
+
console.log(`标题: [sync-back] 技能改进:${allChanges.map(c => c.skillName).join(', ')}`);
|
|
1077
|
+
console.log(fmt('cyan', '─'.repeat(50)));
|
|
1078
|
+
console.log(allDiffText);
|
|
1079
|
+
console.log(fmt('cyan', '─'.repeat(50)));
|
|
1080
|
+
console.log('');
|
|
1081
|
+
console.log(`提交到: ${fmt('bold', 'https://github.com/xu-cell/ai-engineering-init/issues/new')}`);
|
|
1082
|
+
} else {
|
|
1083
|
+
const issueUrl = submitGitHubIssue(allChanges, allDiffText);
|
|
1084
|
+
if (issueUrl) {
|
|
1085
|
+
console.log(fmt('green', fmt('bold', '✅ Issue 已创建!')));
|
|
1086
|
+
console.log(` ${fmt('bold', issueUrl)}`);
|
|
1087
|
+
} else {
|
|
1088
|
+
console.log(fmt('red', '✗ Issue 创建失败,请检查 gh 认证状态(gh auth status)'));
|
|
1089
|
+
console.log('');
|
|
1090
|
+
console.log(fmt('bold', '📋 请手动复制上方 diff 到 GitHub Issue:'));
|
|
1091
|
+
console.log(` ${fmt('bold', 'https://github.com/xu-cell/ai-engineering-init/issues/new')}`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
} else {
|
|
1095
|
+
console.log(fmt('cyan', '💡 提交方式:'));
|
|
1096
|
+
if (allChanges.length === 1) {
|
|
1097
|
+
console.log(` → 运行 ${fmt('bold', hintCmd(`sync-back --skill ${allChanges[0].skillName} --submit`))}`);
|
|
1098
|
+
} else {
|
|
1099
|
+
console.log(` → 运行 ${fmt('bold', hintCmd('sync-back --submit'))}`);
|
|
1100
|
+
}
|
|
1101
|
+
console.log(` → 或手动复制上方 diff 到 ${fmt('bold', 'https://github.com/xu-cell/ai-engineering-init/issues/new')}`);
|
|
1102
|
+
}
|
|
1103
|
+
console.log('');
|
|
1104
|
+
}
|
|
1105
|
+
|
|
678
1106
|
// ── 主入口 ────────────────────────────────────────────────────────────────
|
|
679
1107
|
if (command === 'update') {
|
|
680
1108
|
runUpdate(tool);
|
|
681
1109
|
} else if (command === 'global') {
|
|
682
1110
|
runGlobal(tool);
|
|
1111
|
+
} else if (command === 'sync-back') {
|
|
1112
|
+
runSyncBack(tool, skillFilter, submitIssue);
|
|
683
1113
|
} else if (tool) {
|
|
684
1114
|
run(tool);
|
|
685
1115
|
} else {
|