@icode-js/icode 3.2.0 → 3.4.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 (75) hide show
  1. package/README.md +287 -24
  2. package/bin/icode.js +10 -0
  3. package/package.json +3 -3
  4. package/src/cli.js +175 -8
  5. package/src/commands/checkout.js +73 -6
  6. package/src/commands/clean.js +47 -7
  7. package/src/commands/config-ux.js +292 -0
  8. package/src/commands/config.js +199 -48
  9. package/src/commands/continue.js +104 -0
  10. package/src/commands/doctor.js +15 -29
  11. package/src/commands/help.js +54 -34
  12. package/src/commands/history.js +147 -0
  13. package/src/commands/info.js +39 -4
  14. package/src/commands/log.js +142 -0
  15. package/src/commands/migrate.js +19 -12
  16. package/src/commands/open.js +138 -0
  17. package/src/commands/pr.js +219 -0
  18. package/src/commands/push.js +62 -22
  19. package/src/commands/ready.js +18 -48
  20. package/src/commands/release.js +112 -0
  21. package/src/commands/rescue.js +164 -0
  22. package/src/commands/save.js +52 -11
  23. package/src/commands/stash.js +210 -0
  24. package/src/commands/sw.js +89 -0
  25. package/src/commands/sync.js +19 -2
  26. package/src/commands/tag.js +40 -1
  27. package/src/commands/undo.js +9 -3
  28. package/src/core/ai/diff-range.js +1 -1
  29. package/src/core/cli/completion.js +237 -1
  30. package/src/core/cli/output.js +46 -0
  31. package/src/core/cli/render.js +121 -0
  32. package/src/core/commands/shell.js +9 -1
  33. package/src/core/config/repo-config.js +93 -0
  34. package/src/core/config/store.js +60 -10
  35. package/src/core/git/autostash.js +46 -0
  36. package/src/core/git/backup.js +170 -0
  37. package/src/core/git/conflicts.js +58 -0
  38. package/src/core/git/context.js +72 -5
  39. package/src/core/git/file-guard.js +193 -0
  40. package/src/core/git/operation-state.js +63 -0
  41. package/src/core/git/operations.js +32 -0
  42. package/src/core/git/semver.js +194 -0
  43. package/src/core/git/service.js +454 -24
  44. package/src/core/platform/http.js +53 -0
  45. package/src/core/platform/index.js +165 -0
  46. package/src/core/platform/providers.js +454 -0
  47. package/src/core/platform/remote-url.js +92 -0
  48. package/src/core/remote-merge/client.js +33 -3
  49. package/src/core/tools/browser.js +28 -0
  50. package/src/core/tools/commit-prompt.js +137 -0
  51. package/src/core/tools/interactive.js +272 -40
  52. package/src/core/tools/logger.js +33 -23
  53. package/src/core/tools/theme.js +228 -0
  54. package/src/workflows/ai-commit-workflow.js +52 -20
  55. package/src/workflows/checkout-workflow.js +24 -10
  56. package/src/workflows/clean-workflow.js +135 -83
  57. package/src/workflows/continue-workflow.js +233 -0
  58. package/src/workflows/doctor-workflow.js +10 -7
  59. package/src/workflows/history-workflow.js +211 -0
  60. package/src/workflows/migrate-workflow.js +39 -14
  61. package/src/workflows/pr-workflow.js +252 -0
  62. package/src/workflows/push/commit-step.js +123 -0
  63. package/src/workflows/push/local-merge-step.js +213 -0
  64. package/src/workflows/push/plan.js +63 -0
  65. package/src/workflows/push/remote-merge-step.js +224 -0
  66. package/src/workflows/push-workflow.js +185 -492
  67. package/src/workflows/ready-workflow.js +14 -10
  68. package/src/workflows/release-workflow.js +189 -0
  69. package/src/workflows/rescue-workflow.js +137 -0
  70. package/src/workflows/save-workflow.js +106 -41
  71. package/src/workflows/switch-workflow.js +171 -0
  72. package/src/workflows/sync-workflow.js +143 -44
  73. package/src/workflows/tag-workflow.js +103 -17
  74. package/src/workflows/undo-workflow.js +113 -34
  75. package/src/workflows/rollback-workflow.js +0 -99
@@ -0,0 +1,89 @@
1
+ import { parseCommandArgs, printJson } from '../core/cli/output.js'
2
+ import { c, print, table, ui } from '../core/cli/render.js'
3
+ import { resolveGitContext } from '../core/git/context.js'
4
+ import { GitService } from '../core/git/service.js'
5
+ import { logger } from '../core/tools/logger.js'
6
+ import { listSwitchCandidates, runSwitchWorkflow } from '../workflows/switch-workflow.js'
7
+
8
+ function printHelp() {
9
+ process.stdout.write(`
10
+ Usage:
11
+ icode sw [query] [options]
12
+
13
+ Arguments:
14
+ [query] 分支名或关键字(支持模糊匹配);"-" 切回上一个分支;不传则交互选择
15
+
16
+ Options:
17
+ -c, --create 新建分支并切换(基于当前分支,或 --from 指定)
18
+ --from <ref> 新建分支的基线
19
+ -a, --all 候选里包含仅存在于远程的分支(自动创建 tracking 分支)
20
+ --autostash 有冲突的未提交改动时自动 stash,切换后恢复
21
+ -l, --list 只列出分支(最近使用在前,含 ahead/behind)
22
+ --json 输出机器可读 JSON
23
+ -y, --yes 非交互:多个候选时直接报错
24
+ --repo-mode <mode> 仓库模式: auto | strict
25
+ -h, --help 查看帮助
26
+
27
+ Examples:
28
+ icode sw # 交互选择
29
+ icode sw login # 模糊匹配 feature/login
30
+ icode sw - # 切回上一个分支
31
+ icode sw -c fix/typo # 新建
32
+ icode sw -l
33
+ `)
34
+ }
35
+
36
+ function printList(list, currentBranch) {
37
+ print(table(list, [
38
+ { key: 'name', label: '分支', format: (name, item) => `${name === currentBranch ? c.green('●') : ' '} ${name === currentBranch ? c.bold(ui.branch(name)) : ui.branch(name)}` },
39
+ { key: 'ahead', label: '同步', format: (_, item) => [ui.sync(item.ahead, item.behind), item.gone ? c.red('远程已删') : '', item.kind === 'remote' ? c.blue('仅远程') : ''].filter(Boolean).join(' ') },
40
+ { key: 'relativeDate', label: '最近提交', format: (value) => ui.time(value || '') },
41
+ { key: 'subject', label: '', maxWidth: 60, format: (value) => ui.subject(value || '') }
42
+ ]))
43
+ }
44
+
45
+ export async function runSwCommand(rawArgs) {
46
+ const parsed = parseCommandArgs(rawArgs, {
47
+ create: { type: 'boolean', short: 'c', default: false },
48
+ from: { type: 'string' },
49
+ all: { type: 'boolean', short: 'a', default: false },
50
+ autostash: { type: 'boolean', default: false },
51
+ list: { type: 'boolean', short: 'l', default: false }
52
+ })
53
+ const { values, positionals } = parsed
54
+
55
+ if (values.help) {
56
+ printHelp()
57
+ return
58
+ }
59
+
60
+ if (values.list || (values.json && !positionals.length && !values.create)) {
61
+ const context = await resolveGitContext({ repoMode: values['repo-mode'] })
62
+ const git = new GitService(context)
63
+ const list = await listSwitchCandidates(git, { all: values.all })
64
+ if (values.json) {
65
+ printJson({ currentBranch: context.currentBranch, branches: list })
66
+ return list
67
+ }
68
+ printList(list, context.currentBranch)
69
+ return list
70
+ }
71
+
72
+ const result = await runSwitchWorkflow({
73
+ query: positionals[0],
74
+ create: values.create,
75
+ from: values.from,
76
+ all: values.all,
77
+ autostash: values.autostash,
78
+ yes: values.yes,
79
+ repoMode: values['repo-mode']
80
+ })
81
+
82
+ if (values.json) {
83
+ printJson(result)
84
+ return result
85
+ }
86
+
87
+ logger.success(`已切换到 ${c.bold(ui.branch(result.branch))}${result.previousBranch ? c.gray(`(来自 ${result.previousBranch})`) : ''}`)
88
+ return result
89
+ }
@@ -1,5 +1,6 @@
1
1
  import { parseArgs } from 'node:util'
2
2
  import { normalizeLegacyArgs } from '../core/cli/args.js'
3
+ import { print, renderBranchResults } from '../core/cli/render.js'
3
4
  import { logger } from '../core/tools/logger.js'
4
5
  import { runSyncWorkflow } from '../workflows/sync-workflow.js'
5
6
 
@@ -16,6 +17,8 @@ Options:
16
17
  --merge-main 同步后把主分支 merge 到目标分支
17
18
  --rebase pull 时使用 rebase
18
19
  --push 同步后自动 push
20
+ --autostash 有未提交改动时自动 stash,结束后恢复
21
+ --json 输出机器可读 JSON
19
22
  -y, --yes 自动确认(跳过确认提示)
20
23
  --repo-mode <mode> 仓库模式: auto(自动继承父仓库) | strict(禁止继承)
21
24
  --no-verify push 时跳过 hook/husky 校验
@@ -42,6 +45,8 @@ export async function runSyncCommand(rawArgs) {
42
45
  'merge-main': { type: 'boolean', default: false },
43
46
  rebase: { type: 'boolean', default: false },
44
47
  push: { type: 'boolean', default: false },
48
+ autostash: { type: 'boolean', default: false },
49
+ json: { type: 'boolean', default: false },
45
50
  yes: { type: 'boolean', short: 'y', default: false },
46
51
  'repo-mode': { type: 'string', default: 'auto' },
47
52
  'no-verify': { type: 'boolean', default: false },
@@ -60,15 +65,27 @@ export async function runSyncCommand(rawArgs) {
60
65
  mergeMain: parsed.values['merge-main'],
61
66
  rebase: parsed.values.rebase,
62
67
  push: parsed.values.push,
68
+ autostash: parsed.values.autostash,
63
69
  yes: parsed.values.yes,
64
70
  repoMode: parsed.values['repo-mode'],
65
71
  noVerify: parsed.values['no-verify']
66
72
  })
67
73
 
74
+ if (parsed.values.json) {
75
+ process.stdout.write(`${JSON.stringify({ ok: !result.canceled, ...result }, null, 2)}\n`)
76
+ return result
77
+ }
78
+
68
79
  if (result.canceled) {
69
80
  return
70
81
  }
71
82
 
72
- const summaryText = result.summary.map((item) => `${item.branch}:${item.status}`).join(', ')
73
- logger.success(`sync 完成: ${summaryText}`)
83
+ print('')
84
+ renderBranchResults(result.summary, {
85
+ synced: '已同步',
86
+ 'synced-and-pushed': '已同步并推送',
87
+ missing: '本地和远程都不存在'
88
+ })
89
+ print('')
90
+ logger.success(`sync 完成,共 ${result.summary.length} 个分支`)
74
91
  }
@@ -1,5 +1,7 @@
1
1
  import { parseArgs } from 'node:util'
2
2
  import { normalizeLegacyArgs } from '../core/cli/args.js'
3
+ import { printJson } from '../core/cli/output.js'
4
+ import { c, ui } from '../core/cli/render.js'
3
5
  import { logger } from '../core/tools/logger.js'
4
6
  import { runTagWorkflow } from '../workflows/tag-workflow.js'
5
7
 
@@ -10,14 +12,28 @@ Usage:
10
12
 
11
13
  Options:
12
14
  -n, --name <tag> 指定 tag 名(默认 vYYYYMMDD_NN)
15
+ -b, --bump <kind> 按语义化版本递增: auto | patch | minor | major | prerelease
16
+ --preid <id> 预发布标识,例如 beta、rc(配合 --bump)
13
17
  -m, --message <msg> tag 备注
14
18
  --from <ref> 从指定分支/commit 创建 tag
19
+ --no-push 只创建本地 tag,不推送
20
+ --dry-run 只计算 tag 名,不创建
21
+ --json 输出机器可读 JSON
22
+ -y, --yes 不再询问 tag 名
15
23
  --repo-mode <mode> 仓库模式: auto(自动继承父仓库) | strict(禁止继承)
16
24
  --no-verify 跳过 hook/husky 校验
17
25
  -h, --help 查看帮助
18
26
 
19
27
  Notes:
20
28
  默认 tag 规则: vYYYYMMDD_NN(例如 v20260309_01)。
29
+ --bump auto 会根据上个版本以来的提交推断:破坏性变更 -> major,feat -> minor,其它 -> patch。
30
+ 需要同时生成 changelog 和平台 Release,请用 icode release。
31
+
32
+ Examples:
33
+ icode tag
34
+ icode tag --bump auto --dry-run
35
+ icode tag --bump minor -y
36
+ icode tag --bump prerelease --preid rc
21
37
  `)
22
38
  }
23
39
 
@@ -28,8 +44,14 @@ export async function runTagCommand(rawArgs) {
28
44
  allowPositionals: true,
29
45
  options: {
30
46
  name: { type: 'string', short: 'n' },
47
+ bump: { type: 'string', short: 'b' },
48
+ preid: { type: 'string' },
31
49
  message: { type: 'string', short: 'm' },
32
50
  from: { type: 'string' },
51
+ 'no-push': { type: 'boolean', default: false },
52
+ 'dry-run': { type: 'boolean', default: false },
53
+ json: { type: 'boolean', default: false },
54
+ yes: { type: 'boolean', short: 'y', default: false },
33
55
  'repo-mode': { type: 'string', default: 'auto' },
34
56
  'no-verify': { type: 'boolean', default: false },
35
57
  help: { type: 'boolean', short: 'h', default: false }
@@ -43,11 +65,28 @@ export async function runTagCommand(rawArgs) {
43
65
 
44
66
  const result = await runTagWorkflow({
45
67
  tagName: parsed.values.name,
68
+ bump: parsed.values.bump,
69
+ preid: parsed.values.preid,
46
70
  message: parsed.values.message,
47
71
  fromRef: parsed.values.from,
72
+ noPush: parsed.values['no-push'],
73
+ dryRun: parsed.values['dry-run'],
74
+ yes: parsed.values.yes || parsed.values.json,
75
+ silentLog: parsed.values.json,
48
76
  repoMode: parsed.values['repo-mode'],
49
77
  noVerify: parsed.values['no-verify']
50
78
  })
51
79
 
52
- logger.success(`tag 完成: ${result.tagName}`)
80
+ if (parsed.values.json) {
81
+ printJson(result)
82
+ return result
83
+ }
84
+
85
+ if (result.dryRun) {
86
+ logger.info(`下一个 tag: ${c.bold(ui.tag(result.tagName))}${result.previousTag ? c.gray(`(上一个 ${result.previousTag} · ${result.bump})`) : ''} ${c.gray('dry-run')}`)
87
+ return result
88
+ }
89
+
90
+ logger.success(`已创建 tag ${c.bold(ui.tag(result.tagName))}${result.pushed ? c.gray(' 并已推送') : c.yellow('(未推送)')}`)
91
+ return result
53
92
  }
@@ -1,6 +1,7 @@
1
1
  import { parseArgs } from 'node:util'
2
2
  import { normalizeLegacyArgs } from '../core/cli/args.js'
3
3
  import { IcodeError } from '../core/errors.js'
4
+ import { c } from '../core/cli/render.js'
4
5
  import { logger } from '../core/tools/logger.js'
5
6
  import { runUndoWorkflow } from '../workflows/undo-workflow.js'
6
7
 
@@ -17,7 +18,7 @@ Options:
17
18
  --mode <mode> 回滚模式: revert | soft | mixed | hard
18
19
  --ref <ref> 回滚目标,默认按 mode 自动给出
19
20
  --hash <hash> 按 commit hash 指定回滚目标(等同 --ref)
20
- --recover <action> 冲突恢复策略: continue | abort | keep
21
+ --recover <action> 冲突恢复策略: continue | abort | keep(推荐直接用 icode continue / icode abort)
21
22
  --json 输出机器可读 JSON,适合 AI agent 或脚本消费
22
23
  -y, --yes 自动确认(跳过确认提示)
23
24
  --repo-mode <mode> 仓库模式: auto(自动继承父仓库) | strict(禁止继承)
@@ -121,9 +122,14 @@ export async function runUndoCommand(rawArgs) {
121
122
  }
122
123
 
123
124
  if (result.resolvedOperation) {
124
- logger.success(`undo 已处理未完成操作: ${result.resolvedOperation} -> ${result.recoverAction}`)
125
+ const action = result.recoverAction === 'continue' ? '已继续完成' : '已中止'
126
+ logger.success(`${action}未完成的 ${c.bold(result.resolvedOperation)}`)
125
127
  return
126
128
  }
127
129
 
128
- logger.success(`undo 完成: mode=${result.mode}, ref=${result.ref}`)
130
+ const modeLabel = { revert: '生成反向提交 (revert)', soft: '撤销提交,保留暂存区', mixed: '撤销提交,保留工作区改动', hard: '撤销提交并丢弃改动' }[result.mode] || result.mode
131
+ logger.success(`${modeLabel} ${c.gray(`ref=${result.ref}`)}`)
132
+ if (result.backupId) {
133
+ logger.hint(`已创建恢复点 ${result.backupId},后悔了可以 icode rescue 找回`)
134
+ }
129
135
  }
@@ -25,7 +25,7 @@ function summarizeGitError(error) {
25
25
 
26
26
  export async function resolveAiDiffRange({ git, context, baseRef = '', headRef = 'HEAD', label = 'AI', explicitHead = false }) {
27
27
  const explicitBase = Boolean((baseRef || '').trim())
28
- const remoteDefaultBase = `origin/${context.defaultBranch}`
28
+ const remoteDefaultBase = `${context.remote || 'origin'}/${context.defaultBranch}`
29
29
  const localDefaultBase = context.defaultBranch || ''
30
30
  const candidates = explicitBase
31
31
  ? [(baseRef || '').trim()]
@@ -8,9 +8,21 @@ const AI_COMMIT_LANG_VALUES = ['zh', 'en']
8
8
  const UNDO_MODE_VALUES = ['revert', 'soft', 'mixed', 'hard']
9
9
  const UNDO_RECOVER_VALUES = ['continue', 'abort', 'keep']
10
10
  const SHELL_VALUES = ['bash', 'zsh']
11
+ const BUMP_VALUES = ['auto', 'patch', 'minor', 'major', 'prerelease']
12
+ const PLATFORM_VALUES = ['github', 'gitlab', 'gitee', 'gitea']
13
+ const PR_STATE_VALUES = ['open', 'closed', 'merged', 'all']
14
+ const PR_MERGE_METHOD_VALUES = ['merge', 'squash', 'rebase']
15
+ const OPEN_TARGET_VALUES = ['repo', 'pr', 'prs', 'commit', 'compare', 'ci', 'releases']
11
16
  const CONFIG_PATH_VALUES = [
17
+ 'remote',
18
+ 'main',
19
+ 'repo-mode',
20
+ 'ai-profile',
12
21
  'defaults.repoMode',
13
22
  'defaults.defaultMainBranches',
23
+ 'defaults.remote',
24
+ 'platforms.tokens',
25
+ 'platforms.hosts',
14
26
  'ai.activeProfile',
15
27
  'ai.profiles',
16
28
  'ai.options',
@@ -40,8 +52,44 @@ const EXPLAIN_NODE = {
40
52
  ]
41
53
  }
42
54
 
55
+ const JSON_OPTION = { long: '--json' }
56
+ const YES_OPTION = { long: '--yes', short: '-y' }
57
+ const HELP_OPTION = { long: '--help', short: '-h' }
58
+ const REPO_MODE_OPTION = { long: '--repo-mode', valueSource: 'repo-mode' }
59
+ const BASIC_OPTIONS = [JSON_OPTION, YES_OPTION, REPO_MODE_OPTION, HELP_OPTION]
60
+
61
+ const PR_OPTIONS = [
62
+ { long: '--base', short: '-b', valueSource: 'branch' },
63
+ { long: '--title', short: '-t', valueSource: 'text' },
64
+ { long: '--body', valueSource: 'text' },
65
+ { long: '--draft' },
66
+ { long: '--ai' },
67
+ { long: '--profile', valueSource: 'ai-profile' },
68
+ { long: '--lang', valueSource: 'ai-commit-lang' },
69
+ { long: '--no-push' },
70
+ { long: '--web' },
71
+ { long: '--state', valueSource: 'pr-state' },
72
+ { long: '--method', valueSource: 'pr-merge-method' },
73
+ { long: '--branch', valueSource: 'text' },
74
+ { long: '--provider', valueSource: 'platform' },
75
+ ...BASIC_OPTIONS
76
+ ]
77
+
78
+ const SW_NODE = {
79
+ positionals: [{ valueSource: 'branch' }],
80
+ options: [
81
+ { long: '--create', short: '-c' },
82
+ { long: '--from', valueSource: 'ref' },
83
+ { long: '--all', short: '-a' },
84
+ { long: '--autostash' },
85
+ { long: '--list', short: '-l' },
86
+ ...BASIC_OPTIONS
87
+ ]
88
+ }
89
+
43
90
  const ROOT_COMPLETION_SPEC = {
44
91
  options: [
92
+ { long: '--remote', short: '-R', valueSource: 'remote' },
45
93
  { long: '--debug', short: '-d' },
46
94
  { long: '--help', short: '-h' }
47
95
  ],
@@ -82,6 +130,8 @@ const ROOT_COMPLETION_SPEC = {
82
130
  { long: '--yes', short: '-y' },
83
131
  { long: '--push-origin' },
84
132
  { long: '--pull-main' },
133
+ { long: '--autostash' },
134
+ { long: '--json' },
85
135
  { long: '--repo-mode', valueSource: 'repo-mode' },
86
136
  { long: '--no-verify' },
87
137
  { long: '--help', short: '-h' }
@@ -100,6 +150,8 @@ const ROOT_COMPLETION_SPEC = {
100
150
  { long: '--ai-commit' },
101
151
  { long: '--profile', valueSource: 'ai-profile' },
102
152
  { long: '--lang', valueSource: 'ai-commit-lang' },
153
+ { long: '--staged-only' },
154
+ { long: '--allow-risky' },
103
155
  { long: '--dry-run' },
104
156
  { long: '--json' },
105
157
  { long: '--repo-mode', valueSource: 'repo-mode' },
@@ -122,6 +174,10 @@ const ROOT_COMPLETION_SPEC = {
122
174
  { long: '--pull-main' },
123
175
  { long: '--not-push-current' },
124
176
  { long: '--force-protected' },
177
+ { long: '--force', short: '-f' },
178
+ { long: '--create-target' },
179
+ { long: '--allow-risky' },
180
+ { long: '--json' },
125
181
  { long: '--repo-mode', valueSource: 'repo-mode' },
126
182
  { long: '--no-verify' }
127
183
  ]
@@ -133,6 +189,8 @@ const ROOT_COMPLETION_SPEC = {
133
189
  { long: '--merge-main' },
134
190
  { long: '--rebase' },
135
191
  { long: '--push' },
192
+ { long: '--autostash' },
193
+ { long: '--json' },
136
194
  { long: '--yes', short: '-y' },
137
195
  { long: '--repo-mode', valueSource: 'repo-mode' },
138
196
  { long: '--no-verify' },
@@ -142,9 +200,14 @@ const ROOT_COMPLETION_SPEC = {
142
200
  clean: {
143
201
  options: [
144
202
  { long: '--merged-target', valueSource: 'branch' },
203
+ { long: '--merged' },
204
+ { long: '--gone' },
205
+ { long: '--stale', valueSource: 'text' },
145
206
  { long: '--keep', valueSource: 'branch' },
146
207
  { long: '--remote' },
147
208
  { long: '--force' },
209
+ { long: '--dry-run' },
210
+ { long: '--json' },
148
211
  { long: '--yes', short: '-y' },
149
212
  { long: '--repo-mode', valueSource: 'repo-mode' },
150
213
  { long: '--help', short: '-h' }
@@ -183,8 +246,14 @@ const ROOT_COMPLETION_SPEC = {
183
246
  tag: {
184
247
  options: [
185
248
  { long: '--name', short: '-n', valueSource: 'text' },
249
+ { long: '--bump', short: '-b', valueSource: 'bump' },
250
+ { long: '--preid', valueSource: 'text' },
186
251
  { long: '--message', short: '-m', valueSource: 'text' },
187
252
  { long: '--from', valueSource: 'ref' },
253
+ { long: '--no-push' },
254
+ { long: '--dry-run' },
255
+ { long: '--json' },
256
+ { long: '--yes', short: '-y' },
188
257
  { long: '--repo-mode', valueSource: 'repo-mode' },
189
258
  { long: '--no-verify' },
190
259
  { long: '--help', short: '-h' }
@@ -196,7 +265,34 @@ const ROOT_COMPLETION_SPEC = {
196
265
  { long: '--help', short: '-h' }
197
266
  ],
198
267
  subcommands: {
199
- list: {},
268
+ list: {
269
+ options: [{ long: '--show-secrets' }]
270
+ },
271
+ path: {},
272
+ edit: {},
273
+ init: {
274
+ options: [{ long: '--force' }, { long: '--yes', short: '-y' }]
275
+ },
276
+ platform: {
277
+ subcommands: {
278
+ token: {
279
+ subcommands: {
280
+ show: {},
281
+ set: { positionals: [{ valueSource: 'platform' }] },
282
+ remove: { positionals: [{ valueSource: 'platform' }] }
283
+ }
284
+ },
285
+ host: {
286
+ subcommands: {
287
+ set: { positionals: [{ valueSource: 'text' }, { valueSource: 'platform' }] },
288
+ remove: {}
289
+ }
290
+ },
291
+ 'remote-merge': {
292
+ subcommands: { show: {}, set: { options: [{ long: '--provider', valueSource: 'platform' }, { long: '--api-key', valueSource: 'text' }] } }
293
+ }
294
+ }
295
+ },
200
296
  get: {
201
297
  positionals: [{ valueSource: 'config-path' }]
202
298
  },
@@ -285,9 +381,116 @@ const ROOT_COMPLETION_SPEC = {
285
381
  info: {
286
382
  options: [
287
383
  { long: '--repo-mode', valueSource: 'repo-mode' },
384
+ { long: '--json' },
288
385
  { long: '--help', short: '-h' }
289
386
  ]
290
387
  },
388
+ continue: { options: [JSON_OPTION, REPO_MODE_OPTION, HELP_OPTION] },
389
+ abort: { options: BASIC_OPTIONS },
390
+ sw: SW_NODE,
391
+ switch: SW_NODE,
392
+ stash: {
393
+ options: [
394
+ { long: '--message', short: '-m', valueSource: 'text' },
395
+ { long: '--staged' },
396
+ { long: '--no-untracked' },
397
+ { long: '--index' },
398
+ ...BASIC_OPTIONS
399
+ ],
400
+ subcommands: {
401
+ list: {},
402
+ save: {},
403
+ push: {},
404
+ apply: {},
405
+ pop: {},
406
+ drop: {},
407
+ show: {}
408
+ }
409
+ },
410
+ log: {
411
+ positionals: [{ valueSource: 'ref' }],
412
+ options: [
413
+ { long: '--limit', short: '-n', valueSource: 'text' },
414
+ { long: '--mine' },
415
+ { long: '--author', valueSource: 'text' },
416
+ { long: '--since', valueSource: 'text' },
417
+ { long: '--grep', valueSource: 'text' },
418
+ { long: '--file', valueSource: 'text' },
419
+ { long: '--unpushed' },
420
+ { long: '--no-merges' },
421
+ { long: '--graph' },
422
+ ...BASIC_OPTIONS
423
+ ]
424
+ },
425
+ amend: {
426
+ options: [
427
+ { long: '--message', short: '-m', valueSource: 'text' },
428
+ { long: '--all', short: '-a' },
429
+ { long: '--allow-risky' },
430
+ { long: '--no-verify' },
431
+ ...BASIC_OPTIONS
432
+ ]
433
+ },
434
+ fixup: {
435
+ positionals: [{ valueSource: 'ref' }],
436
+ options: [
437
+ { long: '--all', short: '-a' },
438
+ { long: '--no-rebase' },
439
+ { long: '--allow-risky' },
440
+ { long: '--no-verify' },
441
+ ...BASIC_OPTIONS
442
+ ]
443
+ },
444
+ rescue: {
445
+ options: [
446
+ { long: '--branch', valueSource: 'text' },
447
+ { long: '--reset' },
448
+ { long: '--limit', valueSource: 'text' },
449
+ { long: '--keep', valueSource: 'text' },
450
+ ...BASIC_OPTIONS
451
+ ],
452
+ subcommands: {
453
+ list: {},
454
+ restore: {},
455
+ prune: {}
456
+ }
457
+ },
458
+ pr: {
459
+ options: PR_OPTIONS,
460
+ subcommands: {
461
+ create: { options: PR_OPTIONS },
462
+ list: { options: PR_OPTIONS },
463
+ view: { options: PR_OPTIONS },
464
+ checkout: { options: PR_OPTIONS },
465
+ merge: { options: PR_OPTIONS },
466
+ open: { options: PR_OPTIONS }
467
+ }
468
+ },
469
+ open: {
470
+ positionals: [{ valueSource: 'open-target' }],
471
+ options: [
472
+ { long: '--print', short: '-p' },
473
+ { long: '--branch', valueSource: 'branch' },
474
+ ...BASIC_OPTIONS
475
+ ]
476
+ },
477
+ release: {
478
+ options: [
479
+ { long: '--bump', short: '-b', valueSource: 'bump' },
480
+ { long: '--preid', valueSource: 'text' },
481
+ { long: '--name', short: '-n', valueSource: 'text' },
482
+ { long: '--write-changelog' },
483
+ { long: '--changelog-file', valueSource: 'text' },
484
+ { long: '--update-package' },
485
+ { long: '--no-platform' },
486
+ { long: '--draft' },
487
+ { long: '--prerelease' },
488
+ { long: '--no-push' },
489
+ { long: '--dry-run' },
490
+ { long: '--no-verify' },
491
+ ...BASIC_OPTIONS
492
+ ]
493
+ },
291
494
  help: {},
292
495
  completion: {
293
496
  positionals: [{ valueSource: 'shell' }],
@@ -562,6 +765,30 @@ function resolveValueCandidates(valueSource, providers) {
562
765
  return CONFIG_PATH_VALUES
563
766
  }
564
767
 
768
+ if (valueSource === 'bump') {
769
+ return BUMP_VALUES
770
+ }
771
+
772
+ if (valueSource === 'platform') {
773
+ return PLATFORM_VALUES
774
+ }
775
+
776
+ if (valueSource === 'pr-state') {
777
+ return PR_STATE_VALUES
778
+ }
779
+
780
+ if (valueSource === 'pr-merge-method') {
781
+ return PR_MERGE_METHOD_VALUES
782
+ }
783
+
784
+ if (valueSource === 'open-target') {
785
+ return OPEN_TARGET_VALUES
786
+ }
787
+
788
+ if (valueSource === 'remote') {
789
+ return listRemoteCandidates()
790
+ }
791
+
565
792
  if (valueSource === 'branch') {
566
793
  return providers.listBranchCandidates()
567
794
  }
@@ -603,6 +830,15 @@ function filterCandidates(candidates, currentWord) {
603
830
  * 读取当前仓库的分支候选项,失败时静默返回空数组。
604
831
  * 输入为空,输出为本地与远程分支名数组。
605
832
  */
833
+ function listRemoteCandidates() {
834
+ try {
835
+ const result = spawnSync('git', ['remote'], { encoding: 'utf8' })
836
+ return result.status === 0 ? result.stdout.split('\n').map((item) => item.trim()).filter(Boolean) : []
837
+ } catch {
838
+ return []
839
+ }
840
+ }
841
+
606
842
  function listBranchCandidates() {
607
843
  return listGitCandidates(['refs/heads', 'refs/remotes'])
608
844
  }
@@ -0,0 +1,46 @@
1
+ import { parseArgs } from 'node:util'
2
+ import { normalizeLegacyArgs } from './args.js'
3
+
4
+ /**
5
+ * 输出 JSON 结果;统一带上 ok 字段,方便脚本和 AI agent 判断。
6
+ * 输入为结果对象,输出为空。
7
+ */
8
+ export function printJson(result, ok = true) {
9
+ const payload = result && typeof result === 'object' && !Array.isArray(result)
10
+ ? { ok: result.canceled ? false : ok, ...result }
11
+ : { ok, data: result }
12
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`)
13
+ }
14
+
15
+ // 所有命令共用的参数。
16
+ export const COMMON_OPTIONS = {
17
+ 'repo-mode': { type: 'string', default: 'auto' },
18
+ json: { type: 'boolean', default: false },
19
+ yes: { type: 'boolean', short: 'y', default: false },
20
+ help: { type: 'boolean', short: 'h', default: false }
21
+ }
22
+
23
+ /**
24
+ * 解析子命令参数:历史别名归一化 + 合并公共参数。
25
+ * 输入为原始参数与命令自有参数定义,输出 parseArgs 结果。
26
+ */
27
+ export function parseCommandArgs(rawArgs, options = {}) {
28
+ return parseArgs({
29
+ args: normalizeLegacyArgs(rawArgs),
30
+ allowPositionals: true,
31
+ options: {
32
+ ...COMMON_OPTIONS,
33
+ ...options
34
+ }
35
+ })
36
+ }
37
+
38
+ // 左对齐补空格(按显示宽度粗略处理中文)。
39
+ export function padDisplay(text, width) {
40
+ const value = String(text ?? '')
41
+ let displayWidth = 0
42
+ for (const char of value) {
43
+ displayWidth += /[ᄀ-￿]/.test(char) ? 2 : 1
44
+ }
45
+ return displayWidth >= width ? value : `${value}${' '.repeat(width - displayWidth)}`
46
+ }