@icode-js/icode 3.0.4 → 3.2.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/LICENSE +21 -0
- package/README.md +142 -5
- package/bin/icode.js +4 -4
- package/package.json +1 -1
- package/src/cli.js +60 -23
- package/src/commands/ai.js +4 -4
- package/src/commands/checkout.js +2 -2
- package/src/commands/clean.js +5 -2
- package/src/commands/codereview.js +5 -2
- package/src/commands/completion.js +93 -0
- package/src/commands/config.js +123 -10
- package/src/commands/doctor.js +80 -0
- package/src/commands/explain.js +6 -3
- package/src/commands/help.js +17 -0
- package/src/commands/info.js +5 -2
- package/src/commands/migrate.js +40 -3
- package/src/commands/push.js +72 -24
- package/src/commands/ready.js +110 -0
- package/src/commands/save.js +98 -0
- package/src/commands/sync.js +2 -2
- package/src/commands/tag.js +2 -2
- package/src/commands/undo.js +73 -10
- package/src/core/{ai-client.js → ai/client.js} +3 -3
- package/src/core/{ai-config.js → ai/config.js} +2 -2
- package/src/core/ai/diff-prompt.js +204 -0
- package/src/core/{ai-diff-range.js → ai/diff-range.js} +2 -2
- package/src/core/{args.js → cli/args.js} +23 -2
- package/src/core/cli/completion.js +720 -0
- package/src/core/cli/update-notifier.js +138 -0
- package/src/core/{shell.js → commands/shell.js} +13 -1
- package/src/core/{config-store.js → config/store.js} +53 -3
- package/src/core/{git-context.js → git/context.js} +24 -10
- package/src/core/git/preconditions.js +88 -0
- package/src/core/{git-service.js → git/service.js} +195 -5
- package/src/core/remote-merge/client.js +42 -0
- package/src/core/remote-merge/providers/gitlab.js +596 -0
- package/src/core/remote-merge/shared.js +49 -0
- package/src/core/tools/interactive.js +368 -0
- package/src/core/{loading.js → tools/loading.js} +5 -1
- package/src/core/{logger.js → tools/logger.js} +5 -0
- package/src/workflows/ai-codereview-workflow.js +18 -6
- package/src/workflows/ai-commit-workflow.js +20 -12
- package/src/workflows/ai-conflict-workflow.js +4 -4
- package/src/workflows/ai-explain-workflow.js +4 -4
- package/src/workflows/ai-risk-review-workflow.js +1 -1
- package/src/workflows/checkout-workflow.js +17 -5
- package/src/workflows/clean-workflow.js +92 -75
- package/src/workflows/doctor-workflow.js +131 -0
- package/src/workflows/info-workflow.js +4 -4
- package/src/workflows/migrate-workflow.js +269 -62
- package/src/workflows/push-workflow.js +475 -58
- package/src/workflows/ready-workflow.js +278 -0
- package/src/workflows/rollback-workflow.js +24 -9
- package/src/workflows/save-workflow.js +152 -0
- package/src/workflows/sync-workflow.js +22 -6
- package/src/workflows/tag-workflow.js +4 -4
- package/src/workflows/undo-workflow.js +37 -12
- package/src/core/prompts.js +0 -152
- /package/src/core/{ai-commit-summary.js → ai/commit-summary.js} +0 -0
- /package/src/core/{commit-conventions.js → git/commit-conventions.js} +0 -0
- /package/src/core/{hook-diagnostics.js → git/hook-diagnostics.js} +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 C
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
- 更稳定地处理复杂 Git 场景
|
|
8
8
|
- 更好支持 `husky/git hooks` 校验链路
|
|
9
9
|
- 默认识别“继承父级 Git 仓库”的目录结构
|
|
10
|
+
- 提供面向人类和 AI agent 的安全场景入口,而不是复刻 Git 子命令
|
|
10
11
|
- 采用更现代的 Node 20+ ESM 架构(无运行时第三方依赖)
|
|
11
12
|
|
|
12
13
|
## 安装
|
|
@@ -20,9 +21,32 @@ npm i -g @icode-js/icode
|
|
|
20
21
|
```bash
|
|
21
22
|
icode help # 查看命令总览
|
|
22
23
|
icode info
|
|
24
|
+
icode ready
|
|
25
|
+
icode save -m "feat: add login form"
|
|
26
|
+
icode doctor
|
|
23
27
|
icode config ai list
|
|
24
28
|
```
|
|
25
29
|
|
|
30
|
+
## Shell Tab 补全
|
|
31
|
+
|
|
32
|
+
`icode` 现在支持生成 `bash` / `zsh` 的 tab 补全脚本。
|
|
33
|
+
|
|
34
|
+
临时启用:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
source <(icode completion bash)
|
|
38
|
+
source <(icode completion zsh)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
持久启用可以把对应命令追加到 `~/.bashrc` 或 `~/.zshrc`。
|
|
42
|
+
|
|
43
|
+
补全范围包括:
|
|
44
|
+
|
|
45
|
+
- 顶层命令和嵌套子命令
|
|
46
|
+
- 已知参数开关与枚举值
|
|
47
|
+
- 常见 Git 分支 / ref
|
|
48
|
+
- `icode config ai ...` 下已配置的 profile 名称
|
|
49
|
+
|
|
26
50
|
## 全局参数
|
|
27
51
|
|
|
28
52
|
- `-h, --help`:查看帮助(也可 `icode <command> -h` 查看子命令参数)
|
|
@@ -30,7 +54,7 @@ icode config ai list
|
|
|
30
54
|
|
|
31
55
|
## AI 能力配置
|
|
32
56
|
|
|
33
|
-
先配置一个 AI profile(支持 OpenAI/Anthropic/Ollama
|
|
57
|
+
先配置一个 AI profile(支持 OpenAI/Anthropic/Ollama 三种接口格式):
|
|
34
58
|
|
|
35
59
|
```bash
|
|
36
60
|
icode config ai set openai \
|
|
@@ -79,8 +103,75 @@ icode config ai options set explain --json '{"profile":"ollama","base":"origin/m
|
|
|
79
103
|
icode config ai options set push --json '{"aiProfile":"ollama","aiCommitLang":"zh"}'
|
|
80
104
|
```
|
|
81
105
|
|
|
106
|
+
## 健康检查
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
icode doctor
|
|
110
|
+
icode doctor --json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`doctor` 会检查 Git 命令、`origin`、hooks、AI profile 与远程合并配置,并给出修复建议。建议新成员初始化环境后先执行一次。
|
|
114
|
+
|
|
115
|
+
参数说明:
|
|
116
|
+
|
|
117
|
+
- `--repo-mode auto|strict`:仓库模式(自动继承父仓库/禁止继承)
|
|
118
|
+
- `--json`:输出机器可读 JSON,适合脚本或 CI 读取
|
|
119
|
+
- `-h, --help`:查看帮助
|
|
120
|
+
|
|
82
121
|
## 核心命令
|
|
83
122
|
|
|
123
|
+
### ready
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
icode ready
|
|
127
|
+
icode ready --json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`ready` 是继续开发、保存或推送前的场景化检查入口,不会修改 Git 状态。它会检查:
|
|
131
|
+
|
|
132
|
+
- 当前是否在明确分支上
|
|
133
|
+
- 是否存在未完成的 `merge/rebase/cherry-pick/revert`
|
|
134
|
+
- 是否存在未提交改动或冲突文件
|
|
135
|
+
- 当前分支与 upstream 的 ahead/behind 关系
|
|
136
|
+
- AI profile 与远程合并配置是否可用
|
|
137
|
+
|
|
138
|
+
参数说明:
|
|
139
|
+
|
|
140
|
+
- `--repo-mode auto|strict`:仓库模式(自动继承父仓库/禁止继承)
|
|
141
|
+
- `--json`:输出机器可读 JSON,适合 AI agent 或脚本判断下一步
|
|
142
|
+
- `-h, --help`:查看帮助
|
|
143
|
+
|
|
144
|
+
### save
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
icode save -m "feat: add login form"
|
|
148
|
+
icode save --ai-commit -y
|
|
149
|
+
icode save -m "fix: adjust copy" --dry-run
|
|
150
|
+
icode save --ai-commit --dry-run --json
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`save` 是安全快速提交场景:检查状态 -> 暂存全部改动 -> 创建本地 commit。它不会 push,也不会隐式生成提交信息。
|
|
154
|
+
|
|
155
|
+
- 无改动会直接失败
|
|
156
|
+
- 存在冲突会直接失败
|
|
157
|
+
- 存在未完成 Git 操作会直接失败
|
|
158
|
+
- `--message` 和 `--ai-commit` 必须二选一
|
|
159
|
+
- `--dry-run` 只检查并展示计划,不执行 `git add` 或 `git commit`
|
|
160
|
+
- `--ai-commit` 必须使用已配置的 AI profile,不做静默降级
|
|
161
|
+
|
|
162
|
+
参数说明:
|
|
163
|
+
|
|
164
|
+
- `-m, --message <msg>`:指定提交信息
|
|
165
|
+
- `--ai-commit`:使用 AI 根据全部未提交改动生成提交信息
|
|
166
|
+
- `--profile <name>`:指定 AI profile(仅 `--ai-commit` 使用)
|
|
167
|
+
- `--lang <zh|en>`:AI 提交信息语言,默认 `zh`
|
|
168
|
+
- `--dry-run`:只检查并展示将提交的信息,不执行 add/commit
|
|
169
|
+
- `--json`:输出机器可读 JSON
|
|
170
|
+
- `--repo-mode auto|strict`:仓库模式(自动继承父仓库/禁止继承)
|
|
171
|
+
- `--no-verify`:commit 时跳过 hook/husky 校验
|
|
172
|
+
- `-y, --yes`:自动确认 AI 提交
|
|
173
|
+
- `-h, --help`:查看帮助
|
|
174
|
+
|
|
84
175
|
### checkout
|
|
85
176
|
|
|
86
177
|
```bash
|
|
@@ -90,6 +181,7 @@ icode checkout <branch> [base] [--push-origin] [--pull-main] [--repo-mode auto|s
|
|
|
90
181
|
- 本地存在分支: 直接切换
|
|
91
182
|
- 远程存在分支: 本地创建 tracking 分支并切换
|
|
92
183
|
- 都不存在: 从 `base` 或默认主分支创建
|
|
184
|
+
- 执行前要求工作区干净,且不存在未完成的 merge/rebase/cherry-pick/revert
|
|
93
185
|
|
|
94
186
|
参数说明:
|
|
95
187
|
|
|
@@ -105,7 +197,7 @@ icode checkout <branch> [base] [--push-origin] [--pull-main] [--repo-mode auto|s
|
|
|
105
197
|
### push
|
|
106
198
|
|
|
107
199
|
```bash
|
|
108
|
-
icode push [targetBranch...] [-m "commit message"] [--ai-commit] [--pull-main] [--not-push-current]
|
|
200
|
+
icode push [targetBranch...] [-m "commit message"] [--ai-commit] [--pull-main] [--not-push-current] [-r|--remote-merge] [--dry-run]
|
|
109
201
|
```
|
|
110
202
|
|
|
111
203
|
示例:
|
|
@@ -113,13 +205,18 @@ icode push [targetBranch...] [-m "commit message"] [--ai-commit] [--pull-main] [
|
|
|
113
205
|
```bash
|
|
114
206
|
icode push -m "feat: release" # 提交并推送当前分支
|
|
115
207
|
icode push release test -m "feat: batch publish" -y # 默认本地 merge 推送到多个分支
|
|
208
|
+
icode push release test --dry-run # 只查看计划,不执行任何 Git/远程合并副作用
|
|
209
|
+
icode push release test -r -y # 使用远程 MR 模式合并到多个目标分支
|
|
116
210
|
```
|
|
117
211
|
|
|
118
212
|
- 自动 `add + commit + push`
|
|
213
|
+
- 存在冲突或未完成 Git 操作时会直接停止
|
|
214
|
+
- 本地 merge 冲突会暂停在目标分支现场;解决后执行 `icode undo --recover continue`,或执行 `icode undo --recover abort` 中止
|
|
119
215
|
- 支持 `--ai-commit` 在 push 前自动生成并应用 AI 提交信息
|
|
120
216
|
- 若仓库存在 husky/hooks/commitlint 规则,AI commit 会先扫描本地规范再生成提交信息
|
|
121
217
|
- 支持把当前分支合并到多个目标分支
|
|
122
218
|
- 默认使用本地 merge 模式(会生成 merge commit)
|
|
219
|
+
- 支持远程 PR/MR API 模式(需配置平台密钥)
|
|
123
220
|
- 支持受保护分支策略(通过 `icode config protect ...` 管理)
|
|
124
221
|
|
|
125
222
|
参数说明:
|
|
@@ -128,6 +225,8 @@ icode push release test -m "feat: batch publish" -y # 默认本地 merge 推送
|
|
|
128
225
|
- `-m, --message <msg>`:提交信息(未填会提示输入)
|
|
129
226
|
- `-y, --yes`:自动确认(跳过确认提示)
|
|
130
227
|
- `--local-merge`:显式声明使用本地 merge 模式(当前为默认行为,会切换分支并生成 merge commit)
|
|
228
|
+
- `-r, --remote-merge`:改为远程 PR/MR 模式;会先推送源分支,再通过 API 创建合并请求;若发生冲突会暂停,并输出明确失败原因
|
|
229
|
+
- `--dry-run`:只展示计划,不执行 commit/fetch/pull/push/merge/MR
|
|
131
230
|
- `--ai-commit`:push 前自动执行 AI commit
|
|
132
231
|
- `--ai-profile <name>`:指定 AI profile(用于 `--ai-commit`)
|
|
133
232
|
- `--pull-main`:提交前同步主分支到当前分支
|
|
@@ -138,8 +237,25 @@ icode push release test -m "feat: batch publish" -y # 默认本地 merge 推送
|
|
|
138
237
|
- `-h, --help`:查看帮助
|
|
139
238
|
|
|
140
239
|
说明:
|
|
240
|
+
|
|
141
241
|
- `push` 的布尔开关(如 `--ai-commit`、`--pull-main`、`--no-verify`、`-y`)仅在命令行显式传入时生效。
|
|
142
242
|
|
|
243
|
+
远程合并配置示例:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
icode config platform remote-merge set --provider gitlab --api-key rm_xxx
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
建议:
|
|
250
|
+
|
|
251
|
+
- 密钥按平台维度管理,例如 GitLab / GitHub / 自建平台。
|
|
252
|
+
- GitLab API 地址固定为仓库 `origin` 对应站点的 `/api/v4`,无需再单独配置服务地址。
|
|
253
|
+
- 项目/仓库默认启用远程合并;如个别仓库不希望启用,可显式设置 `repositories.<repo>.remoteMerge.enabled=false`。
|
|
254
|
+
- 远程合并始终根据 `origin` 自动提取 GitLab 项目地址;若无法识别,会提示你修正仓库 remote 配置。
|
|
255
|
+
- GitLab 审批规则由 merge API 统一判断;若审批不足,会在 merge 阶段输出明确失败原因。
|
|
256
|
+
- GitLab 合并检查会展示等待进度,并在长时间 pending 后超时退出,避免命令无限卡住。
|
|
257
|
+
- 这样更符合密钥复用与安全边界,避免多个仓库重复维护同一平台 API 地址。
|
|
258
|
+
|
|
143
259
|
### ai
|
|
144
260
|
|
|
145
261
|
```bash
|
|
@@ -183,6 +299,7 @@ icode sync [branch...] [--all-local] [--merge-main] [--push]
|
|
|
183
299
|
```
|
|
184
300
|
|
|
185
301
|
- 批量同步多个分支(fetch + pull)
|
|
302
|
+
- 执行前要求当前处于普通分支、工作区干净,且不存在未完成 Git 操作
|
|
186
303
|
- 可选自动同步全部本地分支(`--all-local`)
|
|
187
304
|
- 可选把主分支自动 merge 到目标分支(`--merge-main`)
|
|
188
305
|
- 可选同步后自动推送(`--push`)
|
|
@@ -206,6 +323,7 @@ icode clean [--merged-target <branch>] [--remote] [--force]
|
|
|
206
323
|
```
|
|
207
324
|
|
|
208
325
|
- 安全清理“已合并”本地分支
|
|
326
|
+
- 执行前要求当前处于普通分支、工作区干净,且不存在未完成 Git 操作
|
|
209
327
|
- 默认保护当前分支/主分支/配置中的受保护分支
|
|
210
328
|
- 可选同步删除远程分支(`--remote`)
|
|
211
329
|
|
|
@@ -239,19 +357,24 @@ icode tag [--name <tag>] [--message <msg>] [--from <ref>]
|
|
|
239
357
|
### undo
|
|
240
358
|
|
|
241
359
|
```bash
|
|
242
|
-
icode undo [--mode revert|soft|mixed|hard] [--ref <ref>] [--recover continue|abort|keep]
|
|
360
|
+
icode undo [ref] [--mode revert|soft|mixed|hard] [--ref <ref>] [--hash <hash>] [--recover continue|abort|keep]
|
|
361
|
+
icode undo --mode revert --ref HEAD --json
|
|
243
362
|
```
|
|
244
363
|
|
|
245
364
|
- 向导式撤销命令(交互选择回滚策略)
|
|
246
365
|
- 适合新人或低频 Git 操作场景
|
|
247
|
-
- 也支持非交互参数:`--mode` + `--ref`
|
|
248
|
-
-
|
|
366
|
+
- 也支持非交互参数:`--mode` + `--ref` / `--hash`
|
|
367
|
+
- 支持用位置参数指定 commit hash,例如:`icode undo a1b2c3d --mode revert -y`
|
|
368
|
+
- 检测到 merge/revert/cherry-pick 冲突时,会提示继续或中止
|
|
369
|
+
- 支持 `--json` 输出结构化结果;适合 AI agent 判断是否已恢复现场
|
|
249
370
|
|
|
250
371
|
参数说明:
|
|
251
372
|
|
|
252
373
|
- `--mode <mode>`:回滚模式(`revert|soft|mixed|hard`)
|
|
253
374
|
- `--ref <ref>`:回滚目标,默认按 mode 自动给出
|
|
375
|
+
- `--hash <hash>`:按 commit hash 指定回滚目标(等同 `--ref`)
|
|
254
376
|
- `--recover <action>`:冲突恢复策略(`continue|abort|keep`)
|
|
377
|
+
- `--json`:输出机器可读 JSON
|
|
255
378
|
- `-y, --yes`:自动确认(跳过确认提示)
|
|
256
379
|
- `--repo-mode auto|strict`:仓库模式(自动继承父仓库/禁止继承)
|
|
257
380
|
- `-h, --help`:查看帮助
|
|
@@ -260,11 +383,16 @@ icode undo [--mode revert|soft|mixed|hard] [--ref <ref>] [--recover continue|abo
|
|
|
260
383
|
|
|
261
384
|
```bash
|
|
262
385
|
icode migrate <sourceBranch> <targetBranch> [--range <from..to>] [--push]
|
|
386
|
+
icode migrate <sourceBranch> <targetBranch> --dry-run --json
|
|
263
387
|
```
|
|
264
388
|
|
|
265
389
|
- 将 source 分支的提交迁移到 target(底层是 `cherry-pick`)
|
|
266
390
|
- 默认迁移 `target..source` 的增量提交
|
|
267
391
|
- 可通过 `--range` 精确指定迁移范围
|
|
392
|
+
- 执行前要求工作区干净,且不能存在未完成的 merge/rebase/cherry-pick/revert
|
|
393
|
+
- 支持 `--dry-run` 输出 checkout/pull/cherry-pick/push 计划,不产生 Git 副作用
|
|
394
|
+
- 支持 `--json` 输出结构化计划或执行结果,适合 AI agent 消费
|
|
395
|
+
- 如果迁移冲突,可使用 `icode undo --recover continue|abort|keep` 处理现场
|
|
268
396
|
|
|
269
397
|
参数说明:
|
|
270
398
|
|
|
@@ -272,6 +400,8 @@ icode migrate <sourceBranch> <targetBranch> [--range <from..to>] [--push]
|
|
|
272
400
|
- `<targetBranch>`:迁移目标分支
|
|
273
401
|
- `-i, --interactive`:交互模式(单选/多选 source/target 与迁移范围)
|
|
274
402
|
- `--range <from..to>`:指定提交范围,例如 `main..feature-x`
|
|
403
|
+
- `--dry-run`:只展示迁移计划,不 checkout/pull/cherry-pick/push
|
|
404
|
+
- `--json`:输出机器可读 JSON
|
|
275
405
|
- `--push`:迁移后自动推送 target 分支
|
|
276
406
|
- `-y, --yes`:自动确认(跳过确认提示)
|
|
277
407
|
- `--repo-mode auto|strict`:仓库模式(自动继承父仓库/禁止继承)
|
|
@@ -286,6 +416,7 @@ icode config get defaults.repoMode
|
|
|
286
416
|
icode config set defaults.repoMode strict
|
|
287
417
|
icode config protect add main release
|
|
288
418
|
icode config protect list
|
|
419
|
+
icode config platform remote-merge set --provider gitlab --api-key rm_xxx
|
|
289
420
|
```
|
|
290
421
|
|
|
291
422
|
参数说明:
|
|
@@ -298,6 +429,8 @@ icode config protect list
|
|
|
298
429
|
- `protect add <branch...>`:添加受保护分支
|
|
299
430
|
- `protect remove <branch...>`:移除受保护分支
|
|
300
431
|
- `ai <subcommand>`:AI profile 管理(见上文“AI 能力配置”)
|
|
432
|
+
- `platform remote-merge show`:查看远程合并平台配置(密钥脱敏)
|
|
433
|
+
- `platform remote-merge set --provider gitlab --api-key <key>`:写入 GitLab 远程合并密钥
|
|
301
434
|
- `--repo-mode auto|strict`:仓库模式(仅影响 protect)
|
|
302
435
|
- `-h, --help`:查看帮助
|
|
303
436
|
|
|
@@ -331,6 +464,10 @@ icode push -m "chore: hotfix" --no-verify
|
|
|
331
464
|
- 默认 `--repo-mode auto`:自动定位到父仓库根目录执行
|
|
332
465
|
- `--repo-mode strict`:检测到继承时直接阻断,避免误操作
|
|
333
466
|
|
|
467
|
+
### 3) 非 Git 目录
|
|
468
|
+
|
|
469
|
+
`icode` 不会在非 Git 目录中隐式执行 `git init`。如果需要初始化仓库,请先手动执行 `git init`,再运行 `icode` 命令。
|
|
470
|
+
|
|
334
471
|
## 开发
|
|
335
472
|
|
|
336
473
|
```bash
|
package/bin/icode.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { handleCliError, main } from '../src/cli.js'
|
|
3
|
+
|
|
4
|
+
main().catch((error) => {
|
|
5
|
+
handleCliError(error)
|
|
6
6
|
})
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { runAiCommand } from './commands/ai.js'
|
|
2
2
|
import { runCleanCommand } from './commands/clean.js'
|
|
3
3
|
import { runCodeReviewCommand } from './commands/codereview.js'
|
|
4
|
+
import { runCompletionCommand, runHiddenCompletionCommand } from './commands/completion.js'
|
|
4
5
|
import { runCheckoutCommand } from './commands/checkout.js'
|
|
5
6
|
import { runConfigCommand } from './commands/config.js'
|
|
7
|
+
import { runDoctorCommand } from './commands/doctor.js'
|
|
6
8
|
import { runExplainCommand } from './commands/explain.js'
|
|
7
9
|
import { printMainHelp } from './commands/help.js'
|
|
8
10
|
import { runInfoCommand } from './commands/info.js'
|
|
9
11
|
import { runMigrateCommand } from './commands/migrate.js'
|
|
10
12
|
import { runPushCommand } from './commands/push.js'
|
|
13
|
+
import { runReadyCommand } from './commands/ready.js'
|
|
14
|
+
import { runSaveCommand } from './commands/save.js'
|
|
11
15
|
import { runSyncCommand } from './commands/sync.js'
|
|
12
16
|
import { runTagCommand } from './commands/tag.js'
|
|
13
17
|
import { runUndoCommand } from './commands/undo.js'
|
|
14
18
|
import { asIcodeError } from './core/errors.js'
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
19
|
+
import { normalizeLegacyArgs } from './core/cli/args.js'
|
|
20
|
+
import { notifyIfCliUpdateAvailable } from './core/cli/update-notifier.js'
|
|
21
|
+
import { logger } from './core/tools/logger.js'
|
|
17
22
|
|
|
18
23
|
function isTruthy(value) {
|
|
19
24
|
const normalized = String(value || '').trim().toLowerCase()
|
|
@@ -31,7 +36,10 @@ function serializeErrorMeta(meta) {
|
|
|
31
36
|
const COMMANDS = {
|
|
32
37
|
ai: runAiCommand,
|
|
33
38
|
codereview: runCodeReviewCommand,
|
|
39
|
+
completion: runCompletionCommand,
|
|
34
40
|
checkout: runCheckoutCommand,
|
|
41
|
+
ready: runReadyCommand,
|
|
42
|
+
save: runSaveCommand,
|
|
35
43
|
push: runPushCommand,
|
|
36
44
|
sync: runSyncCommand,
|
|
37
45
|
clean: runCleanCommand,
|
|
@@ -39,11 +47,49 @@ const COMMANDS = {
|
|
|
39
47
|
migrate: runMigrateCommand,
|
|
40
48
|
tag: runTagCommand,
|
|
41
49
|
config: runConfigCommand,
|
|
50
|
+
doctor: runDoctorCommand,
|
|
42
51
|
explain: runExplainCommand,
|
|
43
52
|
info: runInfoCommand,
|
|
44
53
|
help: async () => {
|
|
45
54
|
printMainHelp()
|
|
55
|
+
},
|
|
56
|
+
__complete: runHiddenCompletionCommand
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const NO_UPDATE_NOTIFICATION_COMMANDS = new Set(['help', 'completion', '__complete'])
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 执行 CLI 主流程并在命令成功后触发升级提示检查。
|
|
63
|
+
* 输入为命令行参数与可注入依赖,输出为建议的退出码。
|
|
64
|
+
*/
|
|
65
|
+
export async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
66
|
+
const commands = dependencies.commands || COMMANDS
|
|
67
|
+
const printHelp = dependencies.printMainHelp || printMainHelp
|
|
68
|
+
const notifyUpdate = dependencies.notifyIfCliUpdateAvailable || notifyIfCliUpdateAvailable
|
|
69
|
+
const { globalArgs, commandName, commandArgs } = parseEntryArgs(argv)
|
|
70
|
+
const globalResult = applyGlobalFlags(globalArgs)
|
|
71
|
+
|
|
72
|
+
if (globalResult.shouldExit) {
|
|
73
|
+
return globalResult.exitCode
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!commandName) {
|
|
77
|
+
printHelp()
|
|
78
|
+
return 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const command = commands[commandName]
|
|
82
|
+
if (!command) {
|
|
83
|
+
throw new Error(`未知命令: ${commandName}`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await command(commandArgs)
|
|
87
|
+
|
|
88
|
+
if (!NO_UPDATE_NOTIFICATION_COMMANDS.has(commandName)) {
|
|
89
|
+
await notifyUpdate()
|
|
46
90
|
}
|
|
91
|
+
|
|
92
|
+
return 0
|
|
47
93
|
}
|
|
48
94
|
|
|
49
95
|
function parseEntryArgs(argv) {
|
|
@@ -86,28 +132,19 @@ function applyGlobalFlags(globalArgs) {
|
|
|
86
132
|
}
|
|
87
133
|
}
|
|
88
134
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (!commandName) {
|
|
98
|
-
printMainHelp()
|
|
99
|
-
process.exit(0)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const command = COMMANDS[commandName]
|
|
103
|
-
if (!command) {
|
|
104
|
-
throw new Error(`未知命令: ${commandName}`)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
await command(commandArgs)
|
|
135
|
+
/**
|
|
136
|
+
* 启动 CLI 入口,复用 runCli 处理参数与命令调度。
|
|
137
|
+
* 输入为可选 argv 与依赖注入,输出为退出码。
|
|
138
|
+
*/
|
|
139
|
+
export async function main(argv = process.argv.slice(2), dependencies = {}) {
|
|
140
|
+
return runCli(argv, dependencies)
|
|
108
141
|
}
|
|
109
142
|
|
|
110
|
-
|
|
143
|
+
/**
|
|
144
|
+
* 统一处理 CLI 异常并输出对用户可读的错误信息。
|
|
145
|
+
* 输入为任意异常对象,输出为进程退出。
|
|
146
|
+
*/
|
|
147
|
+
export function handleCliError(error) {
|
|
111
148
|
const normalized = asIcodeError(error)
|
|
112
149
|
logger.error(normalized.message)
|
|
113
150
|
|
|
@@ -128,4 +165,4 @@ main().catch((error) => {
|
|
|
128
165
|
}
|
|
129
166
|
|
|
130
167
|
process.exit(normalized.exitCode || 1)
|
|
131
|
-
}
|
|
168
|
+
}
|
package/src/commands/ai.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util'
|
|
2
|
-
import {
|
|
3
|
-
import { getAiCommandOptions } from '../core/ai
|
|
4
|
-
import {
|
|
2
|
+
import { formatAiCommitSummary } from '../core/ai/commit-summary.js'
|
|
3
|
+
import { getAiCommandOptions } from '../core/ai/config.js'
|
|
4
|
+
import { normalizeLegacyArgs } from '../core/cli/args.js'
|
|
5
5
|
import { IcodeError } from '../core/errors.js'
|
|
6
|
-
import { logger } from '../core/logger.js'
|
|
6
|
+
import { logger } from '../core/tools/logger.js'
|
|
7
7
|
import { runAiCodeReviewWorkflow } from '../workflows/ai-codereview-workflow.js'
|
|
8
8
|
import { runAiCommitWorkflow } from '../workflows/ai-commit-workflow.js'
|
|
9
9
|
import { runAiConflictWorkflow } from '../workflows/ai-conflict-workflow.js'
|
package/src/commands/checkout.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util'
|
|
2
|
-
import { normalizeLegacyArgs } from '../core/args.js'
|
|
3
|
-
import { logger } from '../core/logger.js'
|
|
2
|
+
import { normalizeLegacyArgs } from '../core/cli/args.js'
|
|
3
|
+
import { logger } from '../core/tools/logger.js'
|
|
4
4
|
import { runCheckoutWorkflow } from '../workflows/checkout-workflow.js'
|
|
5
5
|
|
|
6
6
|
function printHelp() {
|
package/src/commands/clean.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util'
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeLegacyArgs } from '../core/cli/args.js'
|
|
3
|
+
import { logger } from '../core/tools/logger.js'
|
|
3
4
|
import { runCleanWorkflow } from '../workflows/clean-workflow.js'
|
|
4
5
|
|
|
5
6
|
function printHelp() {
|
|
@@ -26,9 +27,11 @@ Examples:
|
|
|
26
27
|
`)
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
// 执行 clean 子命令,输入原始参数,输出清理结果;参数会先做历史别名归一化。
|
|
29
31
|
export async function runCleanCommand(rawArgs) {
|
|
32
|
+
const args = normalizeLegacyArgs(rawArgs)
|
|
30
33
|
const parsed = parseArgs({
|
|
31
|
-
args
|
|
34
|
+
args,
|
|
32
35
|
allowPositionals: true,
|
|
33
36
|
options: {
|
|
34
37
|
'merged-target': { type: 'string' },
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util'
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeLegacyArgs } from '../core/cli/args.js'
|
|
3
|
+
import { logger } from '../core/tools/logger.js'
|
|
3
4
|
import { runAiCodeReviewWorkflow } from '../workflows/ai-codereview-workflow.js'
|
|
4
5
|
|
|
5
6
|
function printHelp() {
|
|
@@ -18,9 +19,11 @@ Options:
|
|
|
18
19
|
`)
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
// 执行 codereview 子命令,输入原始参数,输出 AI 代码评审结果;参数会先归一化。
|
|
21
23
|
export async function runCodeReviewCommand(rawArgs) {
|
|
24
|
+
const args = normalizeLegacyArgs(rawArgs)
|
|
22
25
|
const parsed = parseArgs({
|
|
23
|
-
args
|
|
26
|
+
args,
|
|
24
27
|
allowPositionals: true,
|
|
25
28
|
options: {
|
|
26
29
|
base: { type: 'string' },
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util'
|
|
2
|
+
import { normalizeLegacyArgs } from '../core/cli/args.js'
|
|
3
|
+
import { IcodeError } from '../core/errors.js'
|
|
4
|
+
import { getCompletionCandidates, renderCompletionScript } from '../core/cli/completion.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 输出 completion 命令帮助,说明支持的 shell 与安装方式。
|
|
8
|
+
* 输入为空,输出为标准输出文本。
|
|
9
|
+
*/
|
|
10
|
+
function printHelp() {
|
|
11
|
+
process.stdout.write(`
|
|
12
|
+
Usage:
|
|
13
|
+
icode completion <bash|zsh>
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
-h, --help 查看帮助
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
source <(icode completion bash)
|
|
20
|
+
source <(icode completion zsh)
|
|
21
|
+
`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 解析隐藏补全命令参数,提取当前词与已确认参数列表。
|
|
26
|
+
* 输入为隐藏命令的原始参数数组,输出为 current 和 previousWords。
|
|
27
|
+
*/
|
|
28
|
+
function parseHiddenCompletionArgs(rawArgs) {
|
|
29
|
+
const args = normalizeLegacyArgs(rawArgs)
|
|
30
|
+
let current = ''
|
|
31
|
+
let separatorIndex = args.indexOf('--')
|
|
32
|
+
|
|
33
|
+
if (separatorIndex === -1) {
|
|
34
|
+
separatorIndex = args.length
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (let index = 0; index < separatorIndex; index += 1) {
|
|
38
|
+
const token = args[index]
|
|
39
|
+
if (token === '--current') {
|
|
40
|
+
current = args[index + 1] || ''
|
|
41
|
+
index += 1
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
current,
|
|
47
|
+
previousWords: args.slice(separatorIndex + 1)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 执行公开的 completion 命令,输出对应 shell 的补全脚本。
|
|
53
|
+
* 输入为命令行参数数组,输出为脚本文本到标准输出。
|
|
54
|
+
*/
|
|
55
|
+
export async function runCompletionCommand(rawArgs) {
|
|
56
|
+
const args = normalizeLegacyArgs(rawArgs)
|
|
57
|
+
const parsed = parseArgs({
|
|
58
|
+
args,
|
|
59
|
+
allowPositionals: true,
|
|
60
|
+
options: {
|
|
61
|
+
help: { type: 'boolean', short: 'h', default: false }
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
if (parsed.values.help || parsed.positionals.length === 0) {
|
|
66
|
+
printHelp()
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const [shellName] = parsed.positionals
|
|
71
|
+
const normalizedShell = shellName.trim().toLowerCase()
|
|
72
|
+
if (!['bash', 'zsh'].includes(normalizedShell)) {
|
|
73
|
+
throw new IcodeError(`completion 仅支持 bash|zsh,当前输入: ${shellName}`, {
|
|
74
|
+
code: 'COMPLETION_SHELL_INVALID',
|
|
75
|
+
exitCode: 2
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
process.stdout.write(renderCompletionScript(normalizedShell))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 执行隐藏补全命令,只输出候选项,避免污染 shell 的补全结果。
|
|
84
|
+
* 输入为隐藏命令参数数组,输出为逐行候选项到标准输出。
|
|
85
|
+
*/
|
|
86
|
+
export async function runHiddenCompletionCommand(rawArgs) {
|
|
87
|
+
const { current, previousWords } = parseHiddenCompletionArgs(rawArgs)
|
|
88
|
+
const candidates = getCompletionCandidates(previousWords, current)
|
|
89
|
+
|
|
90
|
+
if (candidates.length > 0) {
|
|
91
|
+
process.stdout.write(`${candidates.join('\n')}\n`)
|
|
92
|
+
}
|
|
93
|
+
}
|