@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.
- package/LICENSE +21 -0
- package/README.md +142 -5
- package/bin/icode.js +4 -4
- package/package.json +1 -1
- package/src/cli.js +60 -23
- package/src/commands/ai.js +4 -4
- package/src/commands/checkout.js +2 -2
- package/src/commands/clean.js +5 -2
- package/src/commands/codereview.js +5 -2
- package/src/commands/completion.js +93 -0
- package/src/commands/config.js +123 -10
- package/src/commands/doctor.js +80 -0
- package/src/commands/explain.js +6 -3
- package/src/commands/help.js +17 -0
- package/src/commands/info.js +5 -2
- package/src/commands/migrate.js +40 -3
- package/src/commands/push.js +72 -24
- package/src/commands/ready.js +110 -0
- package/src/commands/save.js +98 -0
- package/src/commands/sync.js +2 -2
- package/src/commands/tag.js +2 -2
- package/src/commands/undo.js +73 -10
- package/src/core/{ai-client.js → ai/client.js} +3 -3
- package/src/core/{ai-config.js → ai/config.js} +2 -2
- package/src/core/ai/diff-prompt.js +204 -0
- package/src/core/{ai-diff-range.js → ai/diff-range.js} +2 -2
- package/src/core/{args.js → cli/args.js} +23 -2
- package/src/core/cli/completion.js +720 -0
- package/src/core/cli/update-notifier.js +138 -0
- package/src/core/{shell.js → commands/shell.js} +13 -1
- package/src/core/{config-store.js → config/store.js} +53 -3
- package/src/core/{git-context.js → git/context.js} +24 -10
- package/src/core/git/preconditions.js +88 -0
- package/src/core/{git-service.js → git/service.js} +195 -5
- package/src/core/remote-merge/client.js +42 -0
- package/src/core/remote-merge/providers/gitlab.js +596 -0
- package/src/core/remote-merge/shared.js +49 -0
- package/src/core/tools/interactive.js +368 -0
- package/src/core/{loading.js → tools/loading.js} +5 -1
- package/src/core/{logger.js → tools/logger.js} +5 -0
- package/src/workflows/ai-codereview-workflow.js +18 -6
- package/src/workflows/ai-commit-workflow.js +20 -12
- package/src/workflows/ai-conflict-workflow.js +4 -4
- package/src/workflows/ai-explain-workflow.js +4 -4
- package/src/workflows/ai-risk-review-workflow.js +1 -1
- package/src/workflows/checkout-workflow.js +17 -5
- package/src/workflows/clean-workflow.js +92 -75
- package/src/workflows/doctor-workflow.js +131 -0
- package/src/workflows/info-workflow.js +4 -4
- package/src/workflows/migrate-workflow.js +269 -62
- package/src/workflows/push-workflow.js +475 -58
- package/src/workflows/ready-workflow.js +278 -0
- package/src/workflows/rollback-workflow.js +24 -9
- package/src/workflows/save-workflow.js +152 -0
- package/src/workflows/sync-workflow.js +22 -6
- package/src/workflows/tag-workflow.js +4 -4
- package/src/workflows/undo-workflow.js +37 -12
- package/src/core/prompts.js +0 -152
- /package/src/core/{ai-commit-summary.js → ai/commit-summary.js} +0 -0
- /package/src/core/{commit-conventions.js → git/commit-conventions.js} +0 -0
- /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
|
|
3
|
-
import { GitService } from '../core/git
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
285
|
+
logWarn(options, '已取消 undo。')
|
|
264
286
|
return {
|
|
265
287
|
canceled: true
|
|
266
288
|
}
|
|
267
289
|
}
|
|
268
290
|
|
|
269
291
|
mode = parsed.mode
|
|
270
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
+
logWarn(options, error.message)
|
|
326
351
|
return handleRevertConflict(options, mode, ref)
|
|
327
352
|
}
|
|
328
353
|
}
|
package/src/core/prompts.js
DELETED
|
@@ -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
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|