@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
@@ -1,8 +1,8 @@
1
1
  import { IcodeError } from '../core/errors.js'
2
- import { resolveGitContext } from '../core/git-context.js'
3
- import { GitService } from '../core/git-service.js'
4
- import { logger } from '../core/logger.js'
5
- import { chooseOne } from '../core/prompts.js'
2
+ import { resolveGitContext } from '../core/git/context.js'
3
+ import { GitService } from '../core/git/service.js'
4
+ import { chooseOne } from '../core/tools/interactive.js'
5
+ import { logger } from '../core/tools/logger.js'
6
6
  import { runRollbackWorkflow } from './rollback-workflow.js'
7
7
 
8
8
  const UNDO_OPTIONS = [
@@ -35,6 +35,14 @@ const UNDO_REF_SUGGESTIONS = {
35
35
  hard: ['HEAD~1', 'HEAD~2', 'HEAD~3', 'HEAD~5', 'HEAD']
36
36
  }
37
37
 
38
+ // 静默模式下输出 warn 日志,输入执行选项和日志文本,输出为空。
39
+ function logWarn(options, message) {
40
+ if (!options.silentLog) {
41
+ logger.warn(message)
42
+ }
43
+ }
44
+
45
+ // 解析交互菜单选择,输入菜单 value,输出 mode/ref 或 canceled。
38
46
  function parseSelection(selection) {
39
47
  if (!selection || selection === 'cancel') {
40
48
  return {
@@ -49,10 +57,12 @@ function parseSelection(selection) {
49
57
  }
50
58
  }
51
59
 
60
+ // 解析指定模式的默认 ref,输入 mode,输出默认回滚目标。
52
61
  function resolveDefaultRef(mode) {
53
62
  return mode === 'revert' ? 'HEAD' : 'HEAD~1'
54
63
  }
55
64
 
65
+ // 构建 undo 的 ref 选择项,输入 workflow options 和 mode,输出交互候选项。
56
66
  async function buildUndoRefChoices(options, mode) {
57
67
  const fallbackRef = resolveDefaultRef(mode)
58
68
  const suggestedRefs = UNDO_REF_SUGGESTIONS[mode] || UNDO_REF_SUGGESTIONS.soft
@@ -98,6 +108,7 @@ async function buildUndoRefChoices(options, mode) {
98
108
  }
99
109
  }
100
110
 
111
+ // 校验并归一化 recover 参数,输入用户参数,输出 continue/abort/keep 或空字符串。
101
112
  function normalizeRecover(value) {
102
113
  const normalized = (value || '').trim().toLowerCase()
103
114
  if (!normalized) {
@@ -115,6 +126,7 @@ function normalizeRecover(value) {
115
126
  return normalized
116
127
  }
117
128
 
129
+ // 处理未完成的 merge/revert/cherry-pick 操作,输入 workflow options,输出恢复结果或 null。
118
130
  async function resolvePendingOperation(options) {
119
131
  const context = await resolveGitContext({
120
132
  cwd: options.cwd,
@@ -127,7 +139,7 @@ async function resolvePendingOperation(options) {
127
139
  return null
128
140
  }
129
141
 
130
- logger.warn(`检测到未完成的 ${pending} 操作。`)
142
+ logWarn(options, `检测到未完成的 ${pending} 操作。`)
131
143
 
132
144
  let action = normalizeRecover(options.recover)
133
145
  if (!action) {
@@ -153,7 +165,7 @@ async function resolvePendingOperation(options) {
153
165
  }
154
166
 
155
167
  if (action === 'keep') {
156
- logger.warn('保持现场,未执行继续/中止。')
168
+ logWarn(options, '保持现场,未执行继续/中止。')
157
169
  return {
158
170
  canceled: true,
159
171
  pendingOperation: pending
@@ -164,6 +176,8 @@ async function resolvePendingOperation(options) {
164
176
  try {
165
177
  if (pending === 'revert') {
166
178
  await git.revertContinue()
179
+ } else if (pending === 'merge') {
180
+ await git.mergeContinue()
167
181
  } else {
168
182
  await git.cherryPickContinue()
169
183
  }
@@ -186,6 +200,8 @@ async function resolvePendingOperation(options) {
186
200
 
187
201
  if (pending === 'revert') {
188
202
  await git.revertAbort()
203
+ } else if (pending === 'merge') {
204
+ await git.mergeAbort()
189
205
  } else {
190
206
  await git.abortCherryPick()
191
207
  }
@@ -196,6 +212,7 @@ async function resolvePendingOperation(options) {
196
212
  }
197
213
  }
198
214
 
215
+ // 处理 revert 冲突后的恢复策略,输入 options/mode/ref,输出恢复结果。
199
216
  async function handleRevertConflict(options, mode, ref) {
200
217
  let action = normalizeRecover(options.recover)
201
218
 
@@ -247,6 +264,10 @@ async function handleRevertConflict(options, mode, ref) {
247
264
  }
248
265
  }
249
266
 
267
+ /**
268
+ * 执行 undo 工作流。
269
+ * 输入为已归一化的回滚模式、目标 ref/hash、恢复策略和仓库选项,输出为执行结果或取消状态。
270
+ */
250
271
  export async function runUndoWorkflow(options) {
251
272
  const pendingResult = await resolvePendingOperation(options)
252
273
  if (pendingResult) {
@@ -255,19 +276,22 @@ export async function runUndoWorkflow(options) {
255
276
 
256
277
  let mode = options.mode?.trim()
257
278
  let ref = options.ref?.trim()
279
+ const hasExplicitRef = Boolean(ref)
258
280
 
259
281
  if (!mode) {
260
282
  const selected = await chooseOne('请选择回滚策略:', UNDO_OPTIONS, 0)
261
283
  const parsed = parseSelection(selected)
262
284
  if (parsed.canceled) {
263
- logger.warn('已取消 undo。')
285
+ logWarn(options, '已取消 undo。')
264
286
  return {
265
287
  canceled: true
266
288
  }
267
289
  }
268
290
 
269
291
  mode = parsed.mode
270
- ref = parsed.ref
292
+ if (!hasExplicitRef) {
293
+ ref = parsed.ref
294
+ }
271
295
  }
272
296
 
273
297
  const defaultRef = resolveDefaultRef(mode)
@@ -278,7 +302,7 @@ export async function runUndoWorkflow(options) {
278
302
  const refChoice = await buildUndoRefChoices(options, mode)
279
303
  const selectedRef = await chooseOne('请选择要回滚的 ref:', refChoice.choices, refChoice.defaultIndex)
280
304
  if (selectedRef === 'cancel') {
281
- logger.warn('已取消 undo。')
305
+ logWarn(options, '已取消 undo。')
282
306
  return {
283
307
  canceled: true,
284
308
  mode,
@@ -300,7 +324,7 @@ export async function runUndoWorkflow(options) {
300
324
  mode !== 'hard' ? 0 : 1
301
325
  )) === 'yes'
302
326
  if (!accepted) {
303
- logger.warn('已取消 undo。')
327
+ logWarn(options, '已取消 undo。')
304
328
  return {
305
329
  canceled: true,
306
330
  mode,
@@ -315,14 +339,15 @@ export async function runUndoWorkflow(options) {
315
339
  mode,
316
340
  yes: options.yes,
317
341
  repoMode: options.repoMode,
318
- cwd: options.cwd
342
+ cwd: options.cwd,
343
+ silentLog: options.silentLog
319
344
  })
320
345
  } catch (error) {
321
346
  if (error?.code !== 'REVERT_CONFLICT') {
322
347
  throw error
323
348
  }
324
349
 
325
- logger.warn(error.message)
350
+ logWarn(options, error.message)
326
351
  return handleRevertConflict(options, mode, ref)
327
352
  }
328
353
  }
@@ -1,152 +0,0 @@
1
- import { createInterface } from 'node:readline/promises'
2
- import { stdin, stdout } from 'node:process'
3
-
4
- function isInteractive() {
5
- return Boolean(stdin.isTTY && stdout.isTTY)
6
- }
7
-
8
- export function isInteractiveTerminal() {
9
- return isInteractive()
10
- }
11
-
12
- export async function confirm(message, defaultValue = true) {
13
- if (!isInteractive()) {
14
- return defaultValue
15
- }
16
-
17
- const rl = createInterface({ input: stdin, output: stdout })
18
- const suffix = defaultValue ? '[Y/n]' : '[y/N]'
19
- const answer = (await rl.question(`${message} ${suffix} `)).trim().toLowerCase()
20
- rl.close()
21
-
22
- if (!answer) {
23
- return defaultValue
24
- }
25
-
26
- if (['y', 'yes'].includes(answer)) {
27
- return true
28
- }
29
-
30
- if (['n', 'no'].includes(answer)) {
31
- return false
32
- }
33
-
34
- return defaultValue
35
- }
36
-
37
- export async function input(message, defaultValue = '') {
38
- if (!isInteractive()) {
39
- return defaultValue
40
- }
41
-
42
- const rl = createInterface({ input: stdin, output: stdout })
43
- const answer = await rl.question(`${message}${defaultValue ? ` (${defaultValue})` : ''}: `)
44
- rl.close()
45
- const normalized = answer.trim()
46
- return normalized || defaultValue
47
- }
48
-
49
- export async function chooseOne(message, choices, defaultIndex = 0) {
50
- if (!Array.isArray(choices) || choices.length === 0) {
51
- throw new Error('chooseOne 需要至少一个可选项')
52
- }
53
-
54
- const safeDefaultIndex = Math.min(Math.max(defaultIndex, 0), choices.length - 1)
55
- if (!isInteractive()) {
56
- return choices[safeDefaultIndex].value
57
- }
58
-
59
- stdout.write(`${message}\n`)
60
- choices.forEach((choice, index) => {
61
- stdout.write(` ${index + 1}. ${choice.label}\n`)
62
- })
63
-
64
- const rl = createInterface({ input: stdin, output: stdout })
65
- const answer = (await rl.question(`请选择 [${safeDefaultIndex + 1}]: `)).trim()
66
- rl.close()
67
-
68
- if (!answer) {
69
- return choices[safeDefaultIndex].value
70
- }
71
-
72
- const numeric = Number(answer)
73
- if (!Number.isInteger(numeric) || numeric < 1 || numeric > choices.length) {
74
- return choices[safeDefaultIndex].value
75
- }
76
-
77
- return choices[numeric - 1].value
78
- }
79
-
80
- export async function chooseMany(message, choices, options = {}) {
81
- if (!Array.isArray(choices) || choices.length === 0) {
82
- throw new Error('chooseMany 需要至少一个可选项')
83
- }
84
-
85
- const parsedMin = Number(options.minSelections ?? 0)
86
- const parsedMax = Number(options.maxSelections ?? choices.length)
87
- const minSelections = Number.isFinite(parsedMin) ? Math.max(0, Math.floor(parsedMin)) : 0
88
- const maxCap = Number.isFinite(parsedMax) ? Math.max(0, Math.floor(parsedMax)) : choices.length
89
- const maxSelections = Math.max(minSelections, Math.min(choices.length, maxCap))
90
- const doneLabel = options.doneLabel || '完成选择'
91
- const cancelLabel = options.cancelLabel || '取消'
92
- const defaultValues = Array.isArray(options.defaultValues) ? options.defaultValues : []
93
- const defaultSet = new Set(defaultValues)
94
- const selectedValues = choices
95
- .map((choice) => choice.value)
96
- .filter((value) => defaultSet.has(value))
97
- .slice(0, maxSelections)
98
-
99
- if (!isInteractive()) {
100
- return selectedValues
101
- }
102
-
103
- const selected = new Set(selectedValues)
104
- const doneValue = '__prompt_done__'
105
- const cancelValue = '__prompt_cancel__'
106
-
107
- while (true) {
108
- const menuChoices = choices.map((choice) => ({
109
- value: choice.value,
110
- label: `${selected.has(choice.value) ? '[x]' : '[ ]'} ${choice.label}`
111
- }))
112
- menuChoices.push({
113
- value: doneValue,
114
- label: `${doneLabel}(已选 ${selected.size} 项)`
115
- })
116
- menuChoices.push({
117
- value: cancelValue,
118
- label: cancelLabel
119
- })
120
-
121
- const defaultChoiceValue = selected.size >= minSelections ? doneValue : choices[0].value
122
- const defaultIndex = Math.max(0, menuChoices.findIndex((item) => item.value === defaultChoiceValue))
123
- const selectedAction = await chooseOne(`${message}`, menuChoices, defaultIndex)
124
-
125
- if (selectedAction === cancelValue) {
126
- return null
127
- }
128
-
129
- if (selectedAction === doneValue) {
130
- if (selected.size < minSelections) {
131
- stdout.write(`至少需要选择 ${minSelections} 项。\n`)
132
- continue
133
- }
134
- return choices
135
- .map((choice) => choice.value)
136
- .filter((value) => selected.has(value))
137
- .slice(0, maxSelections)
138
- }
139
-
140
- if (selected.has(selectedAction)) {
141
- selected.delete(selectedAction)
142
- continue
143
- }
144
-
145
- if (selected.size >= maxSelections) {
146
- stdout.write(`最多只能选择 ${maxSelections} 项。\n`)
147
- continue
148
- }
149
-
150
- selected.add(selectedAction)
151
- }
152
- }