@icode-js/icode 3.0.3 → 3.1.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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -3
  3. package/bin/icode.js +4 -4
  4. package/package.json +1 -1
  5. package/src/cli.js +54 -23
  6. package/src/commands/ai.js +4 -4
  7. package/src/commands/checkout.js +2 -2
  8. package/src/commands/clean.js +1 -1
  9. package/src/commands/codereview.js +1 -1
  10. package/src/commands/completion.js +90 -0
  11. package/src/commands/config.js +117 -7
  12. package/src/commands/explain.js +2 -2
  13. package/src/commands/help.js +7 -0
  14. package/src/commands/info.js +1 -1
  15. package/src/commands/migrate.js +2 -2
  16. package/src/commands/push.js +29 -8
  17. package/src/commands/sync.js +2 -2
  18. package/src/commands/tag.js +2 -2
  19. package/src/commands/undo.js +60 -9
  20. package/src/core/{ai-client.js → ai/client.js} +3 -3
  21. package/src/core/{ai-config.js → ai/config.js} +2 -2
  22. package/src/core/{ai-diff-range.js → ai/diff-range.js} +2 -2
  23. package/src/core/cli/completion.js +687 -0
  24. package/src/core/cli/update-notifier.js +138 -0
  25. package/src/core/{shell.js → commands/shell.js} +13 -1
  26. package/src/core/{config-store.js → config/store.js} +55 -3
  27. package/src/core/{git-context.js → git/context.js} +25 -10
  28. package/src/core/{git-service.js → git/service.js} +78 -6
  29. package/src/core/remote-merge/client.js +42 -0
  30. package/src/core/remote-merge/providers/gitlab.js +537 -0
  31. package/src/core/remote-merge/shared.js +49 -0
  32. package/src/core/{prompts.js → tools/interactive.js} +46 -5
  33. package/src/workflows/ai-codereview-workflow.js +4 -4
  34. package/src/workflows/ai-commit-workflow.js +30 -15
  35. package/src/workflows/ai-conflict-workflow.js +4 -4
  36. package/src/workflows/ai-explain-workflow.js +4 -4
  37. package/src/workflows/ai-risk-review-workflow.js +1 -1
  38. package/src/workflows/checkout-workflow.js +3 -3
  39. package/src/workflows/clean-workflow.js +5 -5
  40. package/src/workflows/info-workflow.js +4 -4
  41. package/src/workflows/migrate-workflow.js +4 -4
  42. package/src/workflows/push-workflow.js +259 -24
  43. package/src/workflows/rollback-workflow.js +4 -4
  44. package/src/workflows/sync-workflow.js +4 -4
  45. package/src/workflows/tag-workflow.js +4 -4
  46. package/src/workflows/undo-workflow.js +12 -5
  47. /package/src/core/{ai-commit-summary.js → ai/commit-summary.js} +0 -0
  48. /package/src/core/{args.js → cli/args.js} +0 -0
  49. /package/src/core/{commit-conventions.js → git/commit-conventions.js} +0 -0
  50. /package/src/core/{hook-diagnostics.js → git/hook-diagnostics.js} +0 -0
  51. /package/src/core/{loading.js → tools/loading.js} +0 -0
  52. /package/src/core/{logger.js → tools/logger.js} +0 -0
@@ -1,15 +1,17 @@
1
1
  import { parseArgs } from 'node:util'
2
- import { normalizeLegacyArgs } from '../core/args.js'
3
- import { getAiCommandOptions } from '../core/ai-config.js'
4
- import { logger } from '../core/logger.js'
2
+ import { getAiCommandOptions } from '../core/ai/config.js'
3
+ import { normalizeLegacyArgs } from '../core/cli/args.js'
4
+ import { logger } from '../core/tools/logger.js'
5
5
  import { runPushWorkflow } from '../workflows/push-workflow.js'
6
6
 
7
7
  function formatBranchStatus(status) {
8
8
  const map = {
9
9
  pushed: '已推送',
10
10
  'merged-and-pushed': '已合并并推送',
11
+ 'remote-merged-and-pushed': '已远程合并并推送',
11
12
  'skipped-protected': '已跳过(受保护)',
12
- 'skipped-missing-remote': '已跳过(远程分支不存在)'
13
+ 'skipped-missing-remote': '已跳过(远程分支不存在)',
14
+ paused: '已暂停'
13
15
  }
14
16
 
15
17
  return map[status] || status
@@ -27,6 +29,7 @@ Options:
27
29
  -m, --message <msg> 提交信息(未填会提示输入)
28
30
  -y, --yes 自动确认(跳过确认提示)
29
31
  --local-merge 使用本地 merge 模式(默认,会切换分支并生成 merge commit)
32
+ --remote-merge 使用远程 PR/MR 合并模式(默认启用项目远程合并)
30
33
  --ai-commit push 前自动执行 AI commit(会参考本地 hook/commitlint 规范)
31
34
  --ai-profile <name> 指定 AI profile(用于 --ai-commit)
32
35
  --pull-main 提交前将主分支同步到当前分支
@@ -39,6 +42,8 @@ Options:
39
42
  Notes:
40
43
  默认使用本地 merge 模式。
41
44
  未指定 target 时默认处理当前分支。
45
+ 远程 merge 通过 PR/MR API 发起,不会本地切换到目标分支。
46
+ 远程 merge 若出现冲突会暂停流程,并保留明确失败原因。
42
47
  布尔开关仅在命令行显式传入时生效(如 --ai-commit / --pull-main / --no-verify / -y)。
43
48
  `)
44
49
  }
@@ -69,12 +74,21 @@ function resolveStringOption(cliValue, configValue, fallback = '') {
69
74
  return fallback
70
75
  }
71
76
 
77
+ // 统一输出 push 结果摘要,失败分支会追加具体原因,方便定位问题目标分支。
78
+ function printPushSummary(summary = []) {
79
+ summary.forEach((item) => {
80
+ const reason = typeof item.reason === 'string' && item.reason.trim() ? ` - ${item.reason.trim()}` : ''
81
+ logger.info(`[结果] ${item.branch}: ${formatBranchStatus(item.status)}${reason}`)
82
+ })
83
+ }
84
+
72
85
  export function resolvePushWorkflowOptions(parsedValues, parsedPositionals, scopedOptions = {}) {
73
86
  return {
74
87
  targetBranches: parsedPositionals,
75
88
  message: parsedValues.message,
76
89
  // 显式传入开关才生效,避免配置项隐式开启 push 行为。
77
90
  yes: resolveBooleanOption(parsedValues.yes, false),
91
+ remoteMerge: resolveBooleanOption(parsedValues['remote-merge'], false),
78
92
  aiCommit: resolveBooleanOption(parsedValues['ai-commit'], false),
79
93
  aiCommitLang: resolveStringOption(undefined, scopedOptions.aiCommitLang, 'zh'),
80
94
  aiProfile: resolveStringOption(parsedValues['ai-profile'], scopedOptions.aiProfile, ''),
@@ -96,6 +110,7 @@ export async function runPushCommand(rawArgs) {
96
110
  message: { type: 'string', short: 'm' },
97
111
  yes: { type: 'boolean', short: 'y' },
98
112
  'local-merge': { type: 'boolean' },
113
+ 'remote-merge': { type: 'boolean' },
99
114
  'ai-commit': { type: 'boolean' },
100
115
  'ai-profile': { type: 'string' },
101
116
  help: { type: 'boolean', short: 'h' },
@@ -112,14 +127,20 @@ export async function runPushCommand(rawArgs) {
112
127
  return
113
128
  }
114
129
 
115
- const result = await runPushWorkflow(resolvePushWorkflowOptions(parsed.values, parsed.positionals, scopedOptions))
130
+ let result
131
+ try {
132
+ result = await runPushWorkflow(resolvePushWorkflowOptions(parsed.values, parsed.positionals, scopedOptions))
133
+ } catch (error) {
134
+ if (Array.isArray(error?.meta?.summary) && error.meta.summary.length) {
135
+ printPushSummary(error.meta.summary)
136
+ }
137
+ throw error
138
+ }
116
139
 
117
140
  if (result.canceled) {
118
141
  return
119
142
  }
120
143
 
121
- result.summary.forEach((item) => {
122
- logger.info(`[结果] ${item.branch}: ${formatBranchStatus(item.status)}`)
123
- })
144
+ printPushSummary(result.summary)
124
145
  logger.success(`push 完成,共处理 ${result.summary.length} 个分支。`)
125
146
  }
@@ -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 { runSyncWorkflow } from '../workflows/sync-workflow.js'
5
5
 
6
6
  function printHelp() {
@@ -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 { runTagWorkflow } from '../workflows/tag-workflow.js'
5
5
 
6
6
  function printHelp() {
@@ -1,15 +1,21 @@
1
1
  import { parseArgs } from 'node:util'
2
- import { logger } from '../core/logger.js'
2
+ import { IcodeError } from '../core/errors.js'
3
+ import { logger } from '../core/tools/logger.js'
3
4
  import { runUndoWorkflow } from '../workflows/undo-workflow.js'
4
5
 
6
+ /**
7
+ * 输出 undo 命令帮助文本。
8
+ * 输入为空,输出为标准输出文本。
9
+ */
5
10
  function printHelp() {
6
11
  process.stdout.write(`
7
12
  Usage:
8
- icode undo [options]
13
+ icode undo [ref] [options]
9
14
 
10
15
  Options:
11
16
  --mode <mode> 回滚模式: revert | soft | mixed | hard
12
17
  --ref <ref> 回滚目标,默认按 mode 自动给出
18
+ --hash <hash> 按 commit hash 指定回滚目标(等同 --ref)
13
19
  --recover <action> 冲突恢复策略: continue | abort | keep
14
20
  -y, --yes 自动确认(跳过确认提示)
15
21
  --repo-mode <mode> 仓库模式: auto(自动继承父仓库) | strict(禁止继承)
@@ -20,12 +26,62 @@ Notes:
20
26
 
21
27
  Examples:
22
28
  icode undo
29
+ icode undo a1b2c3d
23
30
  icode undo --mode revert --ref HEAD~2
31
+ icode undo --mode hard --hash a1b2c3d -y
24
32
  icode undo --recover abort
25
33
  icode undo --mode hard --ref HEAD~1 -y
26
34
  `)
27
35
  }
28
36
 
37
+ /**
38
+ * 归一化 undo 命令参数。
39
+ * 输入为 parseArgs 的 values、positionals 与默认配置,输出为 workflow 可直接使用的显式选项。
40
+ */
41
+ export function resolveUndoWorkflowOptions(values, positionals, defaults = {}) {
42
+ if (positionals.length > 1) {
43
+ throw new IcodeError('undo 最多支持一个位置参数作为回滚目标 ref/hash', {
44
+ code: 'UNDO_POSITIONAL_INVALID',
45
+ exitCode: 2
46
+ })
47
+ }
48
+
49
+ const refCandidates = [
50
+ { source: '--ref', value: values.ref },
51
+ { source: '--hash', value: values.hash },
52
+ { source: 'position', value: positionals[0] }
53
+ ].filter((candidate) => typeof candidate.value === 'string' && candidate.value.trim())
54
+
55
+ const normalizedRefs = refCandidates.map((candidate) => ({
56
+ source: candidate.source,
57
+ value: candidate.value.trim()
58
+ }))
59
+ const firstRef = normalizedRefs[0]?.value
60
+ const conflict = normalizedRefs.find((candidate) => candidate.value !== firstRef)
61
+
62
+ if (conflict) {
63
+ throw new IcodeError(
64
+ `undo 回滚目标冲突: ${normalizedRefs.map((item) => `${item.source}=${item.value}`).join(', ')}`,
65
+ {
66
+ code: 'UNDO_REF_CONFLICT',
67
+ exitCode: 2
68
+ }
69
+ )
70
+ }
71
+
72
+ return {
73
+ mode: values.mode,
74
+ ref: firstRef,
75
+ recover: values.recover,
76
+ yes: values.yes === true,
77
+ repoMode: values['repo-mode'] || defaults.repoMode || 'auto'
78
+ }
79
+ }
80
+
81
+ /**
82
+ * 执行 undo 命令。
83
+ * 输入为子命令参数数组,输出为空;内部会按参数调用 undo workflow 并打印结果。
84
+ */
29
85
  export async function runUndoCommand(rawArgs) {
30
86
  const parsed = parseArgs({
31
87
  args: rawArgs,
@@ -33,6 +89,7 @@ export async function runUndoCommand(rawArgs) {
33
89
  options: {
34
90
  mode: { type: 'string' },
35
91
  ref: { type: 'string' },
92
+ hash: { type: 'string' },
36
93
  recover: { type: 'string' },
37
94
  yes: { type: 'boolean', short: 'y', default: false },
38
95
  'repo-mode': { type: 'string', default: 'auto' },
@@ -45,13 +102,7 @@ export async function runUndoCommand(rawArgs) {
45
102
  return
46
103
  }
47
104
 
48
- const result = await runUndoWorkflow({
49
- mode: parsed.values.mode,
50
- ref: parsed.values.ref,
51
- recover: parsed.values.recover,
52
- yes: parsed.values.yes,
53
- repoMode: parsed.values['repo-mode']
54
- })
105
+ const result = await runUndoWorkflow(resolveUndoWorkflowOptions(parsed.values, parsed.positionals))
55
106
 
56
107
  if (result.canceled) {
57
108
  return
@@ -1,6 +1,6 @@
1
- import { IcodeError } from './errors.js'
2
- import { getAiProfile } from './ai-config.js'
3
- import { withSpinner } from './loading.js'
1
+ import { IcodeError } from '../errors.js'
2
+ import { withSpinner } from '../tools/loading.js'
3
+ import { getAiProfile } from './config.js'
4
4
 
5
5
  function trimSlash(value) {
6
6
  return value.replace(/\/+$/, '')
@@ -1,5 +1,5 @@
1
- import { IcodeError } from './errors.js'
2
- import { readConfig, writeConfig } from './config-store.js'
1
+ import { IcodeError } from '../errors.js'
2
+ import { readConfig, writeConfig } from '../config/store.js'
3
3
 
4
4
  const DEFAULT_PROFILE = {
5
5
  provider: 'custom',
@@ -1,5 +1,5 @@
1
- import { IcodeError } from './errors.js'
2
- import { logger } from './logger.js'
1
+ import { IcodeError } from '../errors.js'
2
+ import { logger } from '../tools/logger.js'
3
3
 
4
4
  function unique(values) {
5
5
  return [...new Set(values.filter(Boolean))]