@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.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -5
  3. package/bin/icode.js +4 -4
  4. package/package.json +1 -1
  5. package/src/cli.js +60 -23
  6. package/src/commands/ai.js +4 -4
  7. package/src/commands/checkout.js +2 -2
  8. package/src/commands/clean.js +5 -2
  9. package/src/commands/codereview.js +5 -2
  10. package/src/commands/completion.js +93 -0
  11. package/src/commands/config.js +123 -10
  12. package/src/commands/doctor.js +80 -0
  13. package/src/commands/explain.js +6 -3
  14. package/src/commands/help.js +17 -0
  15. package/src/commands/info.js +5 -2
  16. package/src/commands/migrate.js +40 -3
  17. package/src/commands/push.js +72 -24
  18. package/src/commands/ready.js +110 -0
  19. package/src/commands/save.js +98 -0
  20. package/src/commands/sync.js +2 -2
  21. package/src/commands/tag.js +2 -2
  22. package/src/commands/undo.js +73 -10
  23. package/src/core/{ai-client.js → ai/client.js} +3 -3
  24. package/src/core/{ai-config.js → ai/config.js} +2 -2
  25. package/src/core/ai/diff-prompt.js +204 -0
  26. package/src/core/{ai-diff-range.js → ai/diff-range.js} +2 -2
  27. package/src/core/{args.js → cli/args.js} +23 -2
  28. package/src/core/cli/completion.js +720 -0
  29. package/src/core/cli/update-notifier.js +138 -0
  30. package/src/core/{shell.js → commands/shell.js} +13 -1
  31. package/src/core/{config-store.js → config/store.js} +53 -3
  32. package/src/core/{git-context.js → git/context.js} +24 -10
  33. package/src/core/git/preconditions.js +88 -0
  34. package/src/core/{git-service.js → git/service.js} +195 -5
  35. package/src/core/remote-merge/client.js +42 -0
  36. package/src/core/remote-merge/providers/gitlab.js +596 -0
  37. package/src/core/remote-merge/shared.js +49 -0
  38. package/src/core/tools/interactive.js +368 -0
  39. package/src/core/{loading.js → tools/loading.js} +5 -1
  40. package/src/core/{logger.js → tools/logger.js} +5 -0
  41. package/src/workflows/ai-codereview-workflow.js +18 -6
  42. package/src/workflows/ai-commit-workflow.js +20 -12
  43. package/src/workflows/ai-conflict-workflow.js +4 -4
  44. package/src/workflows/ai-explain-workflow.js +4 -4
  45. package/src/workflows/ai-risk-review-workflow.js +1 -1
  46. package/src/workflows/checkout-workflow.js +17 -5
  47. package/src/workflows/clean-workflow.js +92 -75
  48. package/src/workflows/doctor-workflow.js +131 -0
  49. package/src/workflows/info-workflow.js +4 -4
  50. package/src/workflows/migrate-workflow.js +269 -62
  51. package/src/workflows/push-workflow.js +475 -58
  52. package/src/workflows/ready-workflow.js +278 -0
  53. package/src/workflows/rollback-workflow.js +24 -9
  54. package/src/workflows/save-workflow.js +152 -0
  55. package/src/workflows/sync-workflow.js +22 -6
  56. package/src/workflows/tag-workflow.js +4 -4
  57. package/src/workflows/undo-workflow.js +37 -12
  58. package/src/core/prompts.js +0 -152
  59. /package/src/core/{ai-commit-summary.js → ai/commit-summary.js} +0 -0
  60. /package/src/core/{commit-conventions.js → git/commit-conventions.js} +0 -0
  61. /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
- - 检测到 revert/cherry-pick 冲突时,会提示继续或中止
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('../src/cli.js').catch((error) => {
3
- const message = error?.message || String(error)
4
- console.error(`\x1b[31m[icode] ${message}\x1b[0m`)
5
- process.exit(error?.exitCode || 1)
2
+ import { handleCliError, main } from '../src/cli.js'
3
+
4
+ main().catch((error) => {
5
+ handleCliError(error)
6
6
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icode-js/icode",
3
- "version": "3.0.4",
3
+ "version": "3.2.0",
4
4
  "description": "Modern git workflow CLI with robust parent-repo and hook handling",
5
5
  "type": "module",
6
6
  "author": "C",
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 { logger } from './core/logger.js'
16
- import { normalizeLegacyArgs } from './core/args.js'
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
- async function main() {
90
- const { globalArgs, commandName, commandArgs } = parseEntryArgs(process.argv.slice(2))
91
- const globalResult = applyGlobalFlags(globalArgs)
92
-
93
- if (globalResult.shouldExit) {
94
- process.exit(globalResult.exitCode)
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
- main().catch((error) => {
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
+ }
@@ -1,9 +1,9 @@
1
1
  import { parseArgs } from 'node:util'
2
- import { normalizeLegacyArgs } from '../core/args.js'
3
- import { getAiCommandOptions } from '../core/ai-config.js'
4
- import { formatAiCommitSummary } from '../core/ai-commit-summary.js'
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'
@@ -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() {
@@ -1,5 +1,6 @@
1
1
  import { parseArgs } from 'node:util'
2
- import { logger } from '../core/logger.js'
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: rawArgs,
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 { logger } from '../core/logger.js'
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: rawArgs,
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
+ }