@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.
- package/README.md +346 -0
- package/bin/icode.js +6 -0
- package/package.json +34 -0
- package/src/cli.js +131 -0
- package/src/commands/ai.js +287 -0
- package/src/commands/checkout.js +59 -0
- package/src/commands/clean.js +65 -0
- package/src/commands/codereview.js +52 -0
- package/src/commands/config.js +513 -0
- package/src/commands/explain.js +80 -0
- package/src/commands/help.js +49 -0
- package/src/commands/info.js +57 -0
- package/src/commands/migrate.js +86 -0
- package/src/commands/push.js +125 -0
- package/src/commands/sync.js +74 -0
- package/src/commands/tag.js +53 -0
- package/src/commands/undo.js +66 -0
- package/src/core/ai-client.js +1125 -0
- package/src/core/ai-commit-summary.js +18 -0
- package/src/core/ai-config.js +342 -0
- package/src/core/ai-diff-range.js +117 -0
- package/src/core/args.js +47 -0
- package/src/core/commit-conventions.js +169 -0
- package/src/core/config-store.js +194 -0
- package/src/core/errors.js +25 -0
- package/src/core/git-context.js +105 -0
- package/src/core/git-service.js +428 -0
- package/src/core/hook-diagnostics.js +23 -0
- package/src/core/loading.js +36 -0
- package/src/core/logger.js +55 -0
- package/src/core/prompts.js +152 -0
- package/src/core/shell.js +77 -0
- package/src/workflows/ai-codereview-workflow.js +126 -0
- package/src/workflows/ai-commit-workflow.js +128 -0
- package/src/workflows/ai-conflict-workflow.js +102 -0
- package/src/workflows/ai-explain-workflow.js +116 -0
- package/src/workflows/ai-risk-review-workflow.js +49 -0
- package/src/workflows/checkout-workflow.js +85 -0
- package/src/workflows/clean-workflow.js +131 -0
- package/src/workflows/info-workflow.js +30 -0
- package/src/workflows/migrate-workflow.js +449 -0
- package/src/workflows/push-workflow.js +276 -0
- package/src/workflows/rollback-workflow.js +84 -0
- package/src/workflows/sync-workflow.js +141 -0
- package/src/workflows/tag-workflow.js +64 -0
- 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
|
+
}
|