@icode-js/icode 3.0.2

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 (46) hide show
  1. package/README.md +346 -0
  2. package/bin/icode.js +6 -0
  3. package/package.json +34 -0
  4. package/src/cli.js +131 -0
  5. package/src/commands/ai.js +287 -0
  6. package/src/commands/checkout.js +59 -0
  7. package/src/commands/clean.js +65 -0
  8. package/src/commands/codereview.js +52 -0
  9. package/src/commands/config.js +513 -0
  10. package/src/commands/explain.js +80 -0
  11. package/src/commands/help.js +49 -0
  12. package/src/commands/info.js +57 -0
  13. package/src/commands/migrate.js +86 -0
  14. package/src/commands/push.js +125 -0
  15. package/src/commands/sync.js +74 -0
  16. package/src/commands/tag.js +53 -0
  17. package/src/commands/undo.js +66 -0
  18. package/src/core/ai-client.js +1125 -0
  19. package/src/core/ai-commit-summary.js +18 -0
  20. package/src/core/ai-config.js +342 -0
  21. package/src/core/ai-diff-range.js +117 -0
  22. package/src/core/args.js +47 -0
  23. package/src/core/commit-conventions.js +169 -0
  24. package/src/core/config-store.js +194 -0
  25. package/src/core/errors.js +25 -0
  26. package/src/core/git-context.js +105 -0
  27. package/src/core/git-service.js +428 -0
  28. package/src/core/hook-diagnostics.js +23 -0
  29. package/src/core/loading.js +36 -0
  30. package/src/core/logger.js +55 -0
  31. package/src/core/prompts.js +152 -0
  32. package/src/core/shell.js +77 -0
  33. package/src/workflows/ai-codereview-workflow.js +126 -0
  34. package/src/workflows/ai-commit-workflow.js +128 -0
  35. package/src/workflows/ai-conflict-workflow.js +102 -0
  36. package/src/workflows/ai-explain-workflow.js +116 -0
  37. package/src/workflows/ai-risk-review-workflow.js +49 -0
  38. package/src/workflows/checkout-workflow.js +85 -0
  39. package/src/workflows/clean-workflow.js +131 -0
  40. package/src/workflows/info-workflow.js +30 -0
  41. package/src/workflows/migrate-workflow.js +449 -0
  42. package/src/workflows/push-workflow.js +276 -0
  43. package/src/workflows/rollback-workflow.js +84 -0
  44. package/src/workflows/sync-workflow.js +141 -0
  45. package/src/workflows/tag-workflow.js +64 -0
  46. package/src/workflows/undo-workflow.js +328 -0
@@ -0,0 +1,513 @@
1
+ import path from 'node:path'
2
+ import { parseArgs } from 'node:util'
3
+ import { askAi } from '../core/ai-client.js'
4
+ import {
5
+ getAiCommandOptions,
6
+ getAiProfileForDisplay,
7
+ listAiCommandOptions,
8
+ listAiProfiles,
9
+ removeAiCommandOptions,
10
+ removeAiProfile,
11
+ upsertAiCommandOptions,
12
+ upsertAiProfile,
13
+ useAiProfile
14
+ } from '../core/ai-config.js'
15
+ import {
16
+ deleteValue,
17
+ getConfigFilePath,
18
+ getRepoPolicy,
19
+ getValue,
20
+ readConfig,
21
+ setRepoPolicy,
22
+ setValue
23
+ } from '../core/config-store.js'
24
+ import { parseConfigValue } from '../core/args.js'
25
+ import { IcodeError } from '../core/errors.js'
26
+ import { resolveGitContext } from '../core/git-context.js'
27
+ import { logger } from '../core/logger.js'
28
+
29
+ function printHelp() {
30
+ process.stdout.write(`
31
+ Usage:
32
+ icode config <command> [...args]
33
+
34
+ Arguments:
35
+ <command> 子命令(list/get/set/delete/ai/protect)
36
+
37
+ Commands:
38
+ list 查看全部配置
39
+ get <path> 读取指定配置项
40
+ set <path> <value> 写入配置项
41
+ delete <path> 删除配置项
42
+ ai <subcommand> [...] AI profile 配置
43
+ protect list 查看受保护分支
44
+ protect add <branch...> 添加受保护分支
45
+ protect remove <branch...> 移除受保护分支
46
+
47
+ Options:
48
+ --repo-mode <mode> 仓库模式: auto(自动继承父仓库) | strict(禁止继承),仅影响 protect
49
+ -h, --help 查看帮助
50
+
51
+ Examples:
52
+ icode config set defaults.repoMode strict
53
+ icode config get defaults.repoMode
54
+ icode config protect add main release
55
+ icode config ai set openai --format openai --base-url https://api.openai.com/v1 --api-key sk-xxx --model gpt-4o-mini --activate
56
+ icode config ai set ollama --format ollama --base-url http://127.0.0.1:11434 --model qwen2.5:7b --activate
57
+ `)
58
+ }
59
+
60
+ function printAiHelp() {
61
+ process.stdout.write(`
62
+ Usage:
63
+ icode config ai <subcommand> [options]
64
+
65
+ Arguments:
66
+ <subcommand> 子命令(list/show/set/options/use/remove/test)
67
+
68
+ Subcommands:
69
+ list 查看所有 AI profiles
70
+ show [profile] 查看 profile 详情(默认当前激活)
71
+ set <profile> --format <openai|anthropic|ollama> --base-url <url> --api-key <key> --model <name> [--headers <json>] [--request-body <json>] [--activate]
72
+ 创建/更新 profile
73
+ options <list|show|set|remove> [...] 设置命令默认 options
74
+ use <profile> 切换默认 profile
75
+ remove <profile> 删除 profile
76
+ test [profile] 测试连通性(默认当前激活)
77
+
78
+ Options (set):
79
+ --format <openai|anthropic|ollama> 接口格式(必填)
80
+ --base-url <url> API 地址
81
+ --api-key <key> API Key
82
+ --model <name> 模型名称
83
+ --provider <name> 自定义提供方标识(可选)
84
+ --temperature <num> 采样温度(可选)
85
+ --max-tokens <num> 最大输出 tokens(可选)
86
+ --headers <json> 额外请求头 JSON
87
+ --request-body <json> 额外请求体 JSON
88
+ --activate 设置为默认 profile
89
+
90
+ Common options:
91
+ -h, --help 查看帮助
92
+
93
+ Examples:
94
+ icode config ai list
95
+ icode config ai show
96
+ icode config ai set claude --format anthropic --base-url https://api.anthropic.com/v1 --api-key xxx --model claude-3-5-sonnet-20241022 --activate
97
+ icode config ai set zhipu --format openai --base-url https://open.bigmodel.cn/api/paas/v4 --api-key xxx --model glm-5 --request-body '{"thinking":{"type":"disabled"},"stream":false}' --activate
98
+ icode config ai options set commit --json '{"profile":"local","lang":"zh","yes":true}'
99
+ icode config ai options set push --json '{"aiProfile":"local","aiCommitLang":"zh"}'
100
+ icode config ai set ollama --format ollama --base-url http://127.0.0.1:11434 --model qwen2.5:7b --activate
101
+ icode config ai use claude
102
+ icode config ai test claude
103
+ `)
104
+ }
105
+
106
+ function parseJsonObject(rawValue, fieldLabel = 'headers') {
107
+ if (!rawValue) {
108
+ return {}
109
+ }
110
+
111
+ try {
112
+ const parsed = JSON.parse(rawValue)
113
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
114
+ throw new Error(`${fieldLabel} 必须是 JSON 对象`)
115
+ }
116
+ return parsed
117
+ } catch (error) {
118
+ throw new IcodeError(`${fieldLabel} 解析失败: ${error.message}`, {
119
+ code: 'CONFIG_AI_HEADERS_INVALID',
120
+ exitCode: 2
121
+ })
122
+ }
123
+ }
124
+
125
+ function compactObject(value) {
126
+ const next = {}
127
+ Object.entries(value).forEach(([key, item]) => {
128
+ if (item !== undefined) {
129
+ next[key] = item
130
+ }
131
+ })
132
+ return next
133
+ }
134
+
135
+ function parseOptionsJson(rawValue) {
136
+ if (!rawValue) {
137
+ throw new IcodeError('缺少 --json 参数,示例: --json \'{"profile":"local"}\'', {
138
+ code: 'CONFIG_AI_OPTIONS_JSON_REQUIRED',
139
+ exitCode: 2
140
+ })
141
+ }
142
+
143
+ return parseJsonObject(rawValue)
144
+ }
145
+
146
+ function printAiOptionsHelp() {
147
+ process.stdout.write(`
148
+ Usage:
149
+ icode config ai options <command> [options]
150
+
151
+ Arguments:
152
+ <command> 子命令(list/show/set/remove)
153
+
154
+ Commands:
155
+ list 列出所有 scope 的默认 options
156
+ show <scope> 查看指定 scope 的默认 options
157
+ set <scope> --json <json> [--replace]
158
+ 写入 scope 默认 options
159
+ remove <scope> 删除 scope 默认 options
160
+
161
+ Options (set):
162
+ --json <json> JSON 对象字符串
163
+ --replace 覆盖写入(默认合并)
164
+
165
+ Common options:
166
+ -h, --help 查看帮助
167
+
168
+ Available scope:
169
+ commit | conflict | explain | push
170
+
171
+ Examples:
172
+ icode config ai options list
173
+ icode config ai options show commit
174
+ icode config ai options set commit --json '{"profile":"local","lang":"zh","yes":true}'
175
+ icode config ai options set explain --json '{"profile":"local","base":"origin/main"}'
176
+ icode config ai options set push --json '{"aiProfile":"local","aiCommitLang":"zh"}'
177
+ icode config ai options remove push
178
+ `)
179
+ }
180
+
181
+ async function runAiOptionsCommand(args) {
182
+ if (!args.length || args[0] === '-h' || args[0] === '--help') {
183
+ printAiOptionsHelp()
184
+ return
185
+ }
186
+
187
+ const [action, ...rest] = args
188
+
189
+ if (action === 'list') {
190
+ process.stdout.write(`${JSON.stringify(listAiCommandOptions(), null, 2)}\n`)
191
+ return
192
+ }
193
+
194
+ if (action === 'show') {
195
+ const [scopeName] = rest
196
+ const scopedOptions = getAiCommandOptions(scopeName)
197
+ process.stdout.write(`${JSON.stringify(scopedOptions, null, 2)}\n`)
198
+ return
199
+ }
200
+
201
+ if (action === 'set') {
202
+ const parsed = parseArgs({
203
+ args: rest,
204
+ allowPositionals: true,
205
+ options: {
206
+ json: { type: 'string' },
207
+ replace: { type: 'boolean', default: false }
208
+ }
209
+ })
210
+
211
+ const [scopeName] = parsed.positionals
212
+ if (!scopeName) {
213
+ throw new IcodeError('缺少 options 作用域: icode config ai options set <scope> --json ...', {
214
+ code: 'CONFIG_AI_OPTIONS_SCOPE_REQUIRED',
215
+ exitCode: 2
216
+ })
217
+ }
218
+
219
+ const next = upsertAiCommandOptions(scopeName, parseOptionsJson(parsed.values.json), {
220
+ replace: parsed.values.replace
221
+ })
222
+
223
+ logger.success(`AI options 已写入: ${scopeName}`)
224
+ process.stdout.write(`${JSON.stringify(next, null, 2)}\n`)
225
+ return
226
+ }
227
+
228
+ if (action === 'remove') {
229
+ const [scopeName] = rest
230
+ if (!scopeName) {
231
+ throw new IcodeError('缺少 options 作用域: icode config ai options remove <scope>', {
232
+ code: 'CONFIG_AI_OPTIONS_SCOPE_REQUIRED',
233
+ exitCode: 2
234
+ })
235
+ }
236
+
237
+ removeAiCommandOptions(scopeName)
238
+ logger.success(`AI options 已删除: ${scopeName}`)
239
+ return
240
+ }
241
+
242
+ throw new IcodeError(`未知 ai options 子命令: ${action}`, {
243
+ code: 'CONFIG_AI_OPTIONS_UNKNOWN_SUBCOMMAND',
244
+ exitCode: 2
245
+ })
246
+ }
247
+
248
+ async function runAiConfigCommand(args) {
249
+ if (!args.length || args[0] === '-h' || args[0] === '--help') {
250
+ printAiHelp()
251
+ return
252
+ }
253
+
254
+ const [action, ...rest] = args
255
+
256
+ if (action === 'options') {
257
+ await runAiOptionsCommand(rest)
258
+ return
259
+ }
260
+
261
+ if (action === 'list') {
262
+ const profiles = listAiProfiles()
263
+ if (!profiles.length) {
264
+ logger.warn('当前没有 AI profile。')
265
+ return
266
+ }
267
+
268
+ process.stdout.write(`${JSON.stringify(profiles, null, 2)}\n`)
269
+ return
270
+ }
271
+
272
+ if (action === 'show') {
273
+ const [profileName] = rest
274
+ const profile = getAiProfileForDisplay(profileName)
275
+ process.stdout.write(`${JSON.stringify(profile, null, 2)}\n`)
276
+ return
277
+ }
278
+
279
+ if (action === 'set') {
280
+ const parsed = parseArgs({
281
+ args: rest,
282
+ allowPositionals: true,
283
+ options: {
284
+ provider: { type: 'string' },
285
+ format: { type: 'string' },
286
+ 'base-url': { type: 'string' },
287
+ 'api-key': { type: 'string' },
288
+ model: { type: 'string' },
289
+ temperature: { type: 'string' },
290
+ 'max-tokens': { type: 'string' },
291
+ headers: { type: 'string' },
292
+ 'request-body': { type: 'string' },
293
+ activate: { type: 'boolean', default: false }
294
+ }
295
+ })
296
+
297
+ const [profileName] = parsed.positionals
298
+ if (!profileName) {
299
+ throw new IcodeError('缺少 profile 名称: icode config ai set <profile> ...', {
300
+ code: 'CONFIG_AI_PROFILE_REQUIRED',
301
+ exitCode: 2
302
+ })
303
+ }
304
+
305
+ const profile = upsertAiProfile(profileName, compactObject({
306
+ provider: parsed.values.provider,
307
+ format: parsed.values.format,
308
+ baseUrl: parsed.values['base-url'],
309
+ apiKey: parsed.values['api-key'],
310
+ model: parsed.values.model,
311
+ temperature: parsed.values.temperature,
312
+ maxTokens: parsed.values['max-tokens'],
313
+ headers: parsed.values.headers ? parseJsonObject(parsed.values.headers, 'headers') : undefined,
314
+ requestBody: parsed.values['request-body'] ? parseJsonObject(parsed.values['request-body'], 'request-body') : undefined
315
+ }))
316
+
317
+ if (parsed.values.activate) {
318
+ useAiProfile(profileName)
319
+ }
320
+
321
+ logger.success(`AI profile 已写入: ${profile.name}`)
322
+ return
323
+ }
324
+
325
+ if (action === 'use') {
326
+ const [profileName] = rest
327
+ if (!profileName) {
328
+ throw new IcodeError('缺少 profile 名称: icode config ai use <profile>', {
329
+ code: 'CONFIG_AI_PROFILE_REQUIRED',
330
+ exitCode: 2
331
+ })
332
+ }
333
+
334
+ useAiProfile(profileName)
335
+ logger.success(`已切换 AI profile: ${profileName}`)
336
+ return
337
+ }
338
+
339
+ if (action === 'remove') {
340
+ const [profileName] = rest
341
+ if (!profileName) {
342
+ throw new IcodeError('缺少 profile 名称: icode config ai remove <profile>', {
343
+ code: 'CONFIG_AI_PROFILE_REQUIRED',
344
+ exitCode: 2
345
+ })
346
+ }
347
+
348
+ removeAiProfile(profileName)
349
+ logger.success(`已删除 AI profile: ${profileName}`)
350
+ return
351
+ }
352
+
353
+ if (action === 'test') {
354
+ const [profileName] = rest
355
+ const response = await askAi(
356
+ {
357
+ systemPrompt: 'You are an API connectivity checker.',
358
+ userPrompt: 'Return exactly one word: pong'
359
+ },
360
+ {
361
+ profile: profileName
362
+ }
363
+ )
364
+ logger.success(`AI 连通性测试成功: ${response}`)
365
+ return
366
+ }
367
+
368
+ throw new IcodeError(`未知 ai 子命令: ${action}`, {
369
+ code: 'CONFIG_AI_UNKNOWN_SUBCOMMAND',
370
+ exitCode: 2
371
+ })
372
+ }
373
+
374
+ async function resolvePolicyRoot(repoMode) {
375
+ try {
376
+ const context = await resolveGitContext({ repoMode })
377
+ return context.topLevelPath
378
+ } catch {
379
+ return path.resolve(process.cwd())
380
+ }
381
+ }
382
+
383
+ export async function runConfigCommand(rawArgs) {
384
+ if (rawArgs[0] === 'ai') {
385
+ await runAiConfigCommand(rawArgs.slice(1))
386
+ return
387
+ }
388
+
389
+ const parsed = parseArgs({
390
+ args: rawArgs,
391
+ allowPositionals: true,
392
+ options: {
393
+ help: { type: 'boolean', short: 'h', default: false },
394
+ 'repo-mode': { type: 'string', default: 'auto' }
395
+ }
396
+ })
397
+
398
+ if (parsed.values.help || parsed.positionals.length === 0) {
399
+ printHelp()
400
+ return
401
+ }
402
+
403
+ const [command, ...rest] = parsed.positionals
404
+
405
+ if (command === 'list') {
406
+ process.stdout.write(`${JSON.stringify(readConfig(), null, 2)}\n`)
407
+ logger.info(`配置文件路径: ${getConfigFilePath()}`)
408
+ return
409
+ }
410
+
411
+ if (command === 'get') {
412
+ const pathExpression = rest[0]
413
+ if (!pathExpression) {
414
+ throw new IcodeError('缺少配置路径: icode config get <path>', {
415
+ code: 'CONFIG_GET_PATH_REQUIRED',
416
+ exitCode: 2
417
+ })
418
+ }
419
+
420
+ const value = getValue(pathExpression)
421
+ if (value === undefined) {
422
+ logger.warn('配置不存在。')
423
+ return
424
+ }
425
+
426
+ if (typeof value === 'object') {
427
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`)
428
+ } else {
429
+ process.stdout.write(`${value}\n`)
430
+ }
431
+
432
+ return
433
+ }
434
+
435
+ if (command === 'set') {
436
+ const [pathExpression, rawValue] = rest
437
+ if (!pathExpression || rawValue == null) {
438
+ throw new IcodeError('缺少参数: icode config set <path> <value>', {
439
+ code: 'CONFIG_SET_ARGUMENTS_REQUIRED',
440
+ exitCode: 2
441
+ })
442
+ }
443
+
444
+ const value = parseConfigValue(rawValue)
445
+ setValue(pathExpression, value)
446
+ logger.success(`已写入: ${pathExpression}`)
447
+ return
448
+ }
449
+
450
+ if (command === 'delete') {
451
+ const [pathExpression] = rest
452
+ if (!pathExpression) {
453
+ throw new IcodeError('缺少参数: icode config delete <path>', {
454
+ code: 'CONFIG_DELETE_PATH_REQUIRED',
455
+ exitCode: 2
456
+ })
457
+ }
458
+
459
+ deleteValue(pathExpression)
460
+ logger.success(`已删除: ${pathExpression}`)
461
+ return
462
+ }
463
+
464
+ if (command === 'protect') {
465
+ const [action, ...branches] = rest
466
+ const repoRoot = await resolvePolicyRoot(parsed.values['repo-mode'])
467
+ const currentPolicy = getRepoPolicy(repoRoot)
468
+ const currentProtected = new Set((currentPolicy.protectedBranches || []).map((item) => item.trim()).filter(Boolean))
469
+
470
+ if (action === 'list') {
471
+ process.stdout.write(`${[...currentProtected].join('\n')}\n`)
472
+ logger.info(`受保护分支作用仓库: ${repoRoot}`)
473
+ return
474
+ }
475
+
476
+ if (action === 'add') {
477
+ branches.forEach((branch) => {
478
+ const normalized = branch.trim()
479
+ if (normalized) {
480
+ currentProtected.add(normalized)
481
+ }
482
+ })
483
+
484
+ setRepoPolicy(repoRoot, {
485
+ protectedBranches: [...currentProtected]
486
+ })
487
+ logger.success(`受保护分支已更新: ${[...currentProtected].join(', ')}`)
488
+ return
489
+ }
490
+
491
+ if (action === 'remove') {
492
+ branches.forEach((branch) => {
493
+ currentProtected.delete(branch.trim())
494
+ })
495
+
496
+ setRepoPolicy(repoRoot, {
497
+ protectedBranches: [...currentProtected]
498
+ })
499
+ logger.success(`受保护分支已更新: ${[...currentProtected].join(', ')}`)
500
+ return
501
+ }
502
+
503
+ throw new IcodeError('protect 子命令仅支持: list | add | remove', {
504
+ code: 'CONFIG_PROTECT_INVALID_ACTION',
505
+ exitCode: 2
506
+ })
507
+ }
508
+
509
+ throw new IcodeError(`未知 config 子命令: ${command}`, {
510
+ code: 'CONFIG_UNKNOWN_SUBCOMMAND',
511
+ exitCode: 2
512
+ })
513
+ }
@@ -0,0 +1,80 @@
1
+ import { parseArgs } from 'node:util'
2
+ import { getAiCommandOptions } from '../core/ai-config.js'
3
+ import { logger } from '../core/logger.js'
4
+ import { runAiExplainWorkflow } from '../workflows/ai-explain-workflow.js'
5
+
6
+ function printHelp() {
7
+ process.stdout.write(`
8
+ Usage:
9
+ icode explain [options]
10
+
11
+ Options:
12
+ --base <ref> diff 基线,默认 origin/<defaultBranch>;不可用时回退到本地默认分支
13
+ --head <ref> diff 终点,默认 HEAD
14
+ --profile <name> 指定 AI profile
15
+ --repo-mode <mode> 仓库模式: auto(自动继承父仓库) | strict(禁止继承)
16
+ --dump-response 输出 AI 原始响应(调试数据格式)
17
+ -h, --help 查看帮助
18
+ `)
19
+ }
20
+
21
+ function resolveBooleanOption(cliValue, configValue, fallback = false) {
22
+ if (typeof cliValue === 'boolean') {
23
+ return cliValue
24
+ }
25
+ if (typeof configValue === 'boolean') {
26
+ return configValue
27
+ }
28
+ if (typeof configValue === 'string') {
29
+ const normalized = configValue.trim().toLowerCase()
30
+ if (['true', '1', 'yes', 'y', 'on'].includes(normalized)) {
31
+ return true
32
+ }
33
+ if (['false', '0', 'no', 'n', 'off'].includes(normalized)) {
34
+ return false
35
+ }
36
+ }
37
+ return fallback
38
+ }
39
+
40
+ function resolveStringOption(cliValue, configValue, fallback = '') {
41
+ if (typeof cliValue === 'string' && cliValue.trim()) {
42
+ return cliValue
43
+ }
44
+ if (typeof configValue === 'string' && configValue.trim()) {
45
+ return configValue
46
+ }
47
+ return fallback
48
+ }
49
+
50
+ export async function runExplainCommand(rawArgs) {
51
+ const scopedOptions = getAiCommandOptions('explain')
52
+ const parsed = parseArgs({
53
+ args: rawArgs,
54
+ allowPositionals: true,
55
+ options: {
56
+ base: { type: 'string' },
57
+ head: { type: 'string' },
58
+ profile: { type: 'string' },
59
+ 'repo-mode': { type: 'string' },
60
+ 'dump-response': { type: 'boolean' },
61
+ help: { type: 'boolean', short: 'h' }
62
+ }
63
+ })
64
+
65
+ if (parsed.values.help) {
66
+ printHelp()
67
+ return
68
+ }
69
+
70
+ const result = await runAiExplainWorkflow({
71
+ baseRef: resolveStringOption(parsed.values.base, scopedOptions.base, ''),
72
+ headRef: resolveStringOption(parsed.values.head, scopedOptions.head, ''),
73
+ profile: resolveStringOption(parsed.values.profile, scopedOptions.profile, ''),
74
+ repoMode: resolveStringOption(parsed.values['repo-mode'], scopedOptions.repoMode, 'auto'),
75
+ dumpResponse: resolveBooleanOption(parsed.values['dump-response'], scopedOptions.dumpResponse, false)
76
+ })
77
+
78
+ logger.info(`Explain 范围: ${result.rangeSpec}`)
79
+ process.stdout.write(`\n${result.explanation}\n`)
80
+ }
@@ -0,0 +1,49 @@
1
+ export function printMainHelp() {
2
+ process.stdout.write(`
3
+ icode v3 - Git workflow CLI
4
+
5
+ Usage:
6
+ icode <command> [options]
7
+
8
+ Commands:
9
+ ai AI 助手能力(提交信息/冲突建议/代码评审)
10
+ codereview AI 代码评审(不带 ai 前缀)
11
+ checkout 切换/创建分支(本地/远程自动识别)
12
+ push 提交并推送,可合并到多个目标分支
13
+ sync 批量同步分支(fetch + pull)
14
+ clean 清理已合并分支(可删远程)
15
+ undo 向导式撤销/回滚(revert/reset)
16
+ migrate 迁移分支提交(cherry-pick)
17
+ tag 创建并推送 tag(支持自动命名)
18
+ config 查看和修改本地配置(含 AI profile)
19
+ explain AI 解释 Git diff(自然语言)
20
+ info 查看当前 git 与配置环境
21
+ help 查看帮助(命令总览)
22
+
23
+ Global options:
24
+ -d, --debug 开启调试日志(输出更多细节)
25
+ -h, --help 查看帮助
26
+
27
+ Tips:
28
+ icode <command> -h 查看子命令完整参数说明
29
+
30
+ Examples:
31
+ icode checkout feature/login main --push-origin
32
+ icode ai commit --apply -y
33
+ icode ai codereview
34
+ icode codereview --base origin/main --head HEAD
35
+ icode explain --base origin/main --head HEAD
36
+ icode push release test -m "feat: batch publish" -y
37
+ icode push release test --ai-commit -y
38
+ icode push release test -m "feat: keep merge commit" --local-merge -y
39
+ icode sync --all-local --merge-main
40
+ icode clean --remote --force -y
41
+ icode undo --mode revert --ref HEAD~1 -y
42
+ icode undo --recover abort
43
+ icode migrate feature/login release --push -y
44
+ icode migrate --interactive
45
+ icode push --no-verify -m "chore: bypass hooks"
46
+ icode config protect add main release
47
+ icode info
48
+ `)
49
+ }
@@ -0,0 +1,57 @@
1
+ import { parseArgs } from 'node:util'
2
+ import { logger } from '../core/logger.js'
3
+ import { runInfoWorkflow } from '../workflows/info-workflow.js'
4
+
5
+ function printHelp() {
6
+ process.stdout.write(`
7
+ Usage:
8
+ icode info [options]
9
+
10
+ Options:
11
+ --repo-mode <mode> 仓库模式: auto(自动继承父仓库) | strict(禁止继承)
12
+ -h, --help 查看帮助
13
+
14
+ Notes:
15
+ 输出包含当前仓库、分支、hook 与 AI 配置概览。
16
+ `)
17
+ }
18
+
19
+ export async function runInfoCommand(rawArgs) {
20
+ const parsed = parseArgs({
21
+ args: rawArgs,
22
+ allowPositionals: true,
23
+ options: {
24
+ 'repo-mode': { type: 'string', default: 'auto' },
25
+ help: { type: 'boolean', short: 'h', default: false }
26
+ }
27
+ })
28
+
29
+ if (parsed.values.help) {
30
+ printHelp()
31
+ return
32
+ }
33
+
34
+ const result = await runInfoWorkflow({
35
+ repoMode: parsed.values['repo-mode']
36
+ })
37
+
38
+ const infoBlock = {
39
+ gitVersion: result.gitVersion,
40
+ configPath: result.configPath,
41
+ repoRoot: result.context.topLevelPath,
42
+ cwd: result.context.cwd,
43
+ inheritedFromParent: result.context.inheritedFromParent,
44
+ currentBranch: result.context.currentBranch,
45
+ defaultBranch: result.context.defaultBranch,
46
+ hookPath: result.context.hookPath,
47
+ hasHookPath: result.context.hasHookPath,
48
+ hasHuskyFolder: result.context.hasHuskyFolder,
49
+ isSubmodule: result.context.isSubmodule,
50
+ superprojectPath: result.context.superprojectPath,
51
+ protectedBranches: result.repoPolicy.protectedBranches || [],
52
+ ai: result.aiConfig
53
+ }
54
+
55
+ process.stdout.write(`${JSON.stringify(infoBlock, null, 2)}\n`)
56
+ logger.success('info 输出完成')
57
+ }