@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,328 @@
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'
6
+ import { runRollbackWorkflow } from './rollback-workflow.js'
7
+
8
+ const UNDO_OPTIONS = [
9
+ {
10
+ value: 'revert:HEAD',
11
+ label: '安全回滚(revert HEAD,推荐)'
12
+ },
13
+ {
14
+ value: 'soft:HEAD~1',
15
+ label: '撤销最近一次提交,保留暂存区(reset --soft HEAD~1)'
16
+ },
17
+ {
18
+ value: 'mixed:HEAD~1',
19
+ label: '撤销最近一次提交,保留工作区(reset --mixed HEAD~1)'
20
+ },
21
+ {
22
+ value: 'hard:HEAD~1',
23
+ label: '强制回滚最近一次提交并丢弃改动(reset --hard HEAD~1)'
24
+ },
25
+ {
26
+ value: 'cancel',
27
+ label: '取消'
28
+ }
29
+ ]
30
+
31
+ const UNDO_REF_SUGGESTIONS = {
32
+ revert: ['HEAD', 'HEAD~1', 'HEAD~2', 'HEAD~3', 'HEAD~5'],
33
+ soft: ['HEAD~1', 'HEAD~2', 'HEAD~3', 'HEAD~5', 'HEAD'],
34
+ mixed: ['HEAD~1', 'HEAD~2', 'HEAD~3', 'HEAD~5', 'HEAD'],
35
+ hard: ['HEAD~1', 'HEAD~2', 'HEAD~3', 'HEAD~5', 'HEAD']
36
+ }
37
+
38
+ function parseSelection(selection) {
39
+ if (!selection || selection === 'cancel') {
40
+ return {
41
+ canceled: true
42
+ }
43
+ }
44
+
45
+ const [mode, ref] = selection.split(':')
46
+ return {
47
+ mode,
48
+ ref
49
+ }
50
+ }
51
+
52
+ function resolveDefaultRef(mode) {
53
+ return mode === 'revert' ? 'HEAD' : 'HEAD~1'
54
+ }
55
+
56
+ async function buildUndoRefChoices(options, mode) {
57
+ const fallbackRef = resolveDefaultRef(mode)
58
+ const suggestedRefs = UNDO_REF_SUGGESTIONS[mode] || UNDO_REF_SUGGESTIONS.soft
59
+ const context = await resolveGitContext({
60
+ cwd: options.cwd,
61
+ repoMode: options.repoMode
62
+ })
63
+ const git = new GitService(context)
64
+ const choices = []
65
+ const seen = new Set()
66
+
67
+ for (const ref of [fallbackRef, ...suggestedRefs]) {
68
+ if (seen.has(ref)) {
69
+ continue
70
+ }
71
+ seen.add(ref)
72
+ const summary = await git.showCommitSummary(ref)
73
+ if (!summary) {
74
+ continue
75
+ }
76
+ choices.push({
77
+ value: ref,
78
+ label: `${ref} (${summary})`
79
+ })
80
+ }
81
+
82
+ if (!choices.length) {
83
+ choices.push({
84
+ value: fallbackRef,
85
+ label: fallbackRef
86
+ })
87
+ }
88
+
89
+ choices.push({
90
+ value: 'cancel',
91
+ label: '取消'
92
+ })
93
+
94
+ const defaultIndex = Math.max(0, choices.findIndex((item) => item.value === fallbackRef))
95
+ return {
96
+ choices,
97
+ defaultIndex
98
+ }
99
+ }
100
+
101
+ function normalizeRecover(value) {
102
+ const normalized = (value || '').trim().toLowerCase()
103
+ if (!normalized) {
104
+ return ''
105
+ }
106
+
107
+ const allowed = new Set(['continue', 'abort', 'keep'])
108
+ if (!allowed.has(normalized)) {
109
+ throw new IcodeError('recover 仅支持: continue | abort | keep', {
110
+ code: 'UNDO_RECOVER_INVALID',
111
+ exitCode: 2
112
+ })
113
+ }
114
+
115
+ return normalized
116
+ }
117
+
118
+ async function resolvePendingOperation(options) {
119
+ const context = await resolveGitContext({
120
+ cwd: options.cwd,
121
+ repoMode: options.repoMode
122
+ })
123
+
124
+ const git = new GitService(context)
125
+ const pending = await git.getInProgressOperation()
126
+ if (!pending) {
127
+ return null
128
+ }
129
+
130
+ logger.warn(`检测到未完成的 ${pending} 操作。`)
131
+
132
+ let action = normalizeRecover(options.recover)
133
+ if (!action) {
134
+ if (options.yes) {
135
+ throw new IcodeError(
136
+ `自动模式下检测到未完成的 ${pending} 操作,请显式传 --recover continue|abort|keep。`,
137
+ {
138
+ code: 'UNDO_PENDING_OPERATION',
139
+ exitCode: 2
140
+ }
141
+ )
142
+ }
143
+
144
+ action = await chooseOne(
145
+ `未完成的 ${pending} 操作如何处理?`,
146
+ [
147
+ { value: 'continue', label: `继续 ${pending}(仅在冲突已解决后使用)` },
148
+ { value: 'abort', label: `中止 ${pending}` },
149
+ { value: 'keep', label: '保持现场,暂不处理' }
150
+ ],
151
+ 2
152
+ )
153
+ }
154
+
155
+ if (action === 'keep') {
156
+ logger.warn('保持现场,未执行继续/中止。')
157
+ return {
158
+ canceled: true,
159
+ pendingOperation: pending
160
+ }
161
+ }
162
+
163
+ if (action === 'continue') {
164
+ try {
165
+ if (pending === 'revert') {
166
+ await git.revertContinue()
167
+ } else {
168
+ await git.cherryPickContinue()
169
+ }
170
+
171
+ return {
172
+ resolvedOperation: pending,
173
+ recoverAction: 'continue'
174
+ }
175
+ } catch (error) {
176
+ throw new IcodeError(
177
+ `${pending} --continue 失败,请先解决冲突后重试,或使用 --recover abort。`,
178
+ {
179
+ code: 'UNDO_CONTINUE_FAILED',
180
+ cause: error,
181
+ meta: error.meta
182
+ }
183
+ )
184
+ }
185
+ }
186
+
187
+ if (pending === 'revert') {
188
+ await git.revertAbort()
189
+ } else {
190
+ await git.abortCherryPick()
191
+ }
192
+
193
+ return {
194
+ resolvedOperation: pending,
195
+ recoverAction: 'abort'
196
+ }
197
+ }
198
+
199
+ async function handleRevertConflict(options, mode, ref) {
200
+ let action = normalizeRecover(options.recover)
201
+
202
+ if (!action) {
203
+ if (options.yes) {
204
+ throw new IcodeError('检测到 revert 冲突,请显式传 --recover abort 或先手工处理冲突。', {
205
+ code: 'UNDO_REVERT_CONFLICT',
206
+ exitCode: 2
207
+ })
208
+ }
209
+
210
+ action = await chooseOne(
211
+ '检测到 revert 冲突,下一步?',
212
+ [
213
+ { value: 'abort', label: '中止本次 revert(推荐)' },
214
+ { value: 'continue', label: '继续 revert(需先手工解决冲突)' },
215
+ { value: 'keep', label: '保持现场,稍后手工处理' }
216
+ ],
217
+ 0
218
+ )
219
+ }
220
+
221
+ if (action === 'keep') {
222
+ return {
223
+ canceled: true,
224
+ mode,
225
+ ref,
226
+ conflict: 'revert'
227
+ }
228
+ }
229
+
230
+ const context = await resolveGitContext({
231
+ cwd: options.cwd,
232
+ repoMode: options.repoMode
233
+ })
234
+ const git = new GitService(context)
235
+
236
+ if (action === 'continue') {
237
+ await git.revertContinue()
238
+ } else {
239
+ await git.revertAbort()
240
+ }
241
+
242
+ return {
243
+ mode,
244
+ ref,
245
+ resolvedOperation: 'revert',
246
+ recoverAction: action
247
+ }
248
+ }
249
+
250
+ export async function runUndoWorkflow(options) {
251
+ const pendingResult = await resolvePendingOperation(options)
252
+ if (pendingResult) {
253
+ return pendingResult
254
+ }
255
+
256
+ let mode = options.mode?.trim()
257
+ let ref = options.ref?.trim()
258
+
259
+ if (!mode) {
260
+ const selected = await chooseOne('请选择回滚策略:', UNDO_OPTIONS, 0)
261
+ const parsed = parseSelection(selected)
262
+ if (parsed.canceled) {
263
+ logger.warn('已取消 undo。')
264
+ return {
265
+ canceled: true
266
+ }
267
+ }
268
+
269
+ mode = parsed.mode
270
+ ref = parsed.ref
271
+ }
272
+
273
+ const defaultRef = resolveDefaultRef(mode)
274
+ if (!ref) {
275
+ if (options.yes) {
276
+ ref = defaultRef
277
+ } else {
278
+ const refChoice = await buildUndoRefChoices(options, mode)
279
+ const selectedRef = await chooseOne('请选择要回滚的 ref:', refChoice.choices, refChoice.defaultIndex)
280
+ if (selectedRef === 'cancel') {
281
+ logger.warn('已取消 undo。')
282
+ return {
283
+ canceled: true,
284
+ mode,
285
+ ref: defaultRef
286
+ }
287
+ }
288
+ ref = selectedRef
289
+ }
290
+ }
291
+
292
+ // 给用户最后一次确认,减少误用 reset/hard 造成的数据丢失风险。
293
+ if (!options.yes) {
294
+ const accepted = (await chooseOne(
295
+ `确认执行 ${mode} 回滚,ref=${ref} ?`,
296
+ [
297
+ { value: 'yes', label: '确认执行' },
298
+ { value: 'no', label: '取消' }
299
+ ],
300
+ mode !== 'hard' ? 0 : 1
301
+ )) === 'yes'
302
+ if (!accepted) {
303
+ logger.warn('已取消 undo。')
304
+ return {
305
+ canceled: true,
306
+ mode,
307
+ ref
308
+ }
309
+ }
310
+ }
311
+
312
+ try {
313
+ return await runRollbackWorkflow({
314
+ ref,
315
+ mode,
316
+ yes: options.yes,
317
+ repoMode: options.repoMode,
318
+ cwd: options.cwd
319
+ })
320
+ } catch (error) {
321
+ if (error?.code !== 'REVERT_CONFLICT') {
322
+ throw error
323
+ }
324
+
325
+ logger.warn(error.message)
326
+ return handleRevertConflict(options, mode, ref)
327
+ }
328
+ }