@hifullmoon/aicommit 1.4.0 → 2.0.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.
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "kind": "aicommit-provider-presets",
3
- "schemaVersion": 1,
4
- "version": "1.1.0",
3
+ "schemaVersion": 2,
4
+ "version": "2.0.0",
5
5
  "compatibility": {
6
- "coreMinimum": "1.4.0",
7
- "coreMaximumExclusive": "2.0.0",
6
+ "coreMinimum": "1.5.1",
7
+ "coreMaximumExclusive": "3.0.0",
8
8
  "adapterContract": 1
9
9
  },
10
10
  "providers": [
@@ -13,33 +13,55 @@
13
13
  "label": "OpenAI",
14
14
  "adapter": "openai",
15
15
  "apiUrl": "https://api.openai.com/v1/chat/completions",
16
- "modelId": "gpt-4o"
16
+ "defaultModel": "gpt-4o",
17
+ "models": {
18
+ "gpt-4o": {
19
+ "label": "GPT-4o",
20
+ "modelId": "gpt-4o"
21
+ }
22
+ }
17
23
  },
18
24
  {
19
25
  "id": "deepseek",
20
26
  "label": "DeepSeek",
21
27
  "adapter": "deepseek",
22
28
  "apiUrl": "https://api.deepseek.com/v1/chat/completions",
23
- "modelId": "deepseek-v4-flash"
29
+ "defaultModel": "chat",
30
+ "models": {
31
+ "chat": {
32
+ "label": "DeepSeek Chat",
33
+ "modelId": "deepseek-v4-flash"
34
+ }
35
+ }
24
36
  },
25
37
  {
26
38
  "id": "openrouter",
27
39
  "label": "OpenRouter",
28
40
  "adapter": "openrouter",
29
41
  "apiUrl": "https://openrouter.ai/api/v1/chat/completions",
30
- "modelId": "openai/gpt-4o-mini"
42
+ "defaultModel": "default",
43
+ "models": {
44
+ "default": {
45
+ "modelId": "openai/gpt-4o-mini"
46
+ }
47
+ }
31
48
  },
32
49
  {
33
50
  "id": "minimax",
34
51
  "label": "MiniMax",
35
52
  "adapter": "minimax",
36
53
  "apiUrl": "https://api.minimaxi.com/v1/chat/completions",
37
- "modelId": "MiniMax-M3",
38
- "extraBody": {
39
- "thinking": {
40
- "type": "disabled"
41
- },
42
- "reasoning_split": true
54
+ "defaultModel": "default",
55
+ "models": {
56
+ "default": {
57
+ "modelId": "MiniMax-M3",
58
+ "extraBody": {
59
+ "thinking": {
60
+ "type": "disabled"
61
+ },
62
+ "reasoning_split": true
63
+ }
64
+ }
43
65
  }
44
66
  },
45
67
  {
@@ -47,14 +69,25 @@
47
69
  "label": "Kimi Code",
48
70
  "adapter": "custom",
49
71
  "apiUrl": "https://api.kimi.com/coding/v1/chat/completions",
50
- "modelId": "kimi-for-coding"
72
+ "defaultModel": "default",
73
+ "models": {
74
+ "default": {
75
+ "modelId": "kimi-for-coding"
76
+ }
77
+ }
51
78
  },
52
79
  {
53
80
  "id": "ollama",
54
81
  "label": "Ollama (local)",
55
82
  "adapter": "ollama",
56
83
  "apiUrl": "http://127.0.0.1:11434/api/chat",
57
- "modelId": "qwen3:8b"
84
+ "defaultModel": "qwen",
85
+ "models": {
86
+ "qwen": {
87
+ "label": "Qwen 3 8B",
88
+ "modelId": "qwen3:8b"
89
+ }
90
+ }
58
91
  }
59
92
  ]
60
93
  }
@@ -7,7 +7,7 @@
7
7
  "required": ["kind", "schemaVersion", "version", "compatibility", "providers"],
8
8
  "properties": {
9
9
  "kind": { "const": "aicommit-provider-presets" },
10
- "schemaVersion": { "const": 1 },
10
+ "schemaVersion": { "const": 2 },
11
11
  "version": {
12
12
  "type": "string",
13
13
  "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
@@ -35,7 +35,7 @@
35
35
  "items": {
36
36
  "type": "object",
37
37
  "additionalProperties": false,
38
- "required": ["id", "label", "adapter", "apiUrl", "modelId"],
38
+ "required": ["id", "label", "adapter", "apiUrl", "defaultModel", "models"],
39
39
  "properties": {
40
40
  "id": {
41
41
  "type": "string",
@@ -47,8 +47,28 @@
47
47
  "enum": ["openai", "openrouter", "deepseek", "minimax", "ollama", "custom"]
48
48
  },
49
49
  "apiUrl": { "type": "string", "minLength": 1, "maxLength": 2048 },
50
- "modelId": { "type": "string", "minLength": 1, "maxLength": 256 },
51
- "extraBody": { "type": "object" }
50
+ "defaultModel": {
51
+ "type": "string",
52
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
53
+ },
54
+ "models": {
55
+ "type": "object",
56
+ "minProperties": 1,
57
+ "maxProperties": 100,
58
+ "propertyNames": {
59
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$"
60
+ },
61
+ "additionalProperties": {
62
+ "type": "object",
63
+ "additionalProperties": false,
64
+ "required": ["modelId"],
65
+ "properties": {
66
+ "label": { "type": "string", "minLength": 1, "maxLength": 80 },
67
+ "modelId": { "type": "string", "minLength": 1, "maxLength": 256 },
68
+ "extraBody": { "type": "object" }
69
+ }
70
+ }
71
+ }
52
72
  }
53
73
  }
54
74
  }
package/src/cli.js CHANGED
@@ -18,7 +18,7 @@ function showHelp() {
18
18
  ${chalk.dim('$')} aicommit policy <template|check> [options]
19
19
  ${chalk.dim('$')} aicommit preset <show|validate|path|install|rollback> [options]
20
20
  ${chalk.dim('$')} aicommit completion <bash|zsh|fish>
21
- ${chalk.dim('$')} aicommit split <run|plan|apply|resume|abort> [options]
21
+ ${chalk.dim('$')} aicommit split [run|plan|apply|resume|abort] [options]
22
22
 
23
23
  ${chalk.bold('Commands:')}
24
24
  setup Interactive configuration wizard
@@ -30,7 +30,7 @@ function showHelp() {
30
30
  policy check Validate a message file or Git range with the effective team policy
31
31
  preset Inspect, validate, install, or roll back provider preset manifests
32
32
  completion Generate Bash, Zsh, or Fish completion on stdout
33
- split run Plan and create logical commits
33
+ split, split run Plan and create logical commits
34
34
  split plan Generate and export a fingerprinted JSON plan
35
35
  split apply Validate and apply an exported JSON plan
36
36
  split resume Resume the repository's unfinished transaction
@@ -45,8 +45,9 @@ function showHelp() {
45
45
  -v, --version Show version number
46
46
  -l, --lang=<zh|en> Commit message language (default: zh)
47
47
  -p, --provider=<name> Use the named provider from config "providers"
48
+ -m, --model=<name> Use a named model from the selected provider
48
49
  --split-hunks Experimental same-file hunk planning (default: off)
49
- --scope=<scope> Scope for "split run|plan": staged, all
50
+ --scope=<scope> Scope for "split|split plan": staged, all
50
51
  --file=<path> Split-plan artifact or commit-message file
51
52
  --range=<revision> Git revision/range for "policy check" (default: HEAD)
52
53
  --reasoning=<level> Set reasoning effort (enabled by default: medium)
@@ -72,11 +73,11 @@ function showHelp() {
72
73
  aicommit stats Show local acceptance, quality, latency, and token trends
73
74
  aicommit Commit changes in current directory (Chinese)
74
75
  aicommit --lang=en Generate English commit message
75
- aicommit -p deepseek Switch to the "deepseek" provider from config
76
- aicommit split run Choose staged/all scope, then plan logical commits
77
- aicommit split run --scope=staged Split only the reviewed index snapshot
78
- aicommit split run --scope=all --yes Split the complete working tree non-interactively
79
- aicommit split run --scope=staged --split-hunks Split eligible text hunks
76
+ aicommit -p deepseek -m chat Select a configured provider and model
77
+ aicommit split Choose staged/all scope, then plan logical commits
78
+ aicommit split --scope=staged Split only the reviewed index snapshot
79
+ aicommit split --scope=all --yes Split the complete working tree non-interactively
80
+ aicommit split --scope=staged --split-hunks Split eligible text hunks
80
81
  aicommit split plan --scope=staged --file=.aicommit-plan.json --yes
81
82
  aicommit split apply --file=.aicommit-plan.json --yes
82
83
  aicommit split resume --yes Resume pending groups from the checkpoint
@@ -109,6 +110,7 @@ function parsedDefaults(overrides = {}) {
109
110
  targetPath: null,
110
111
  cliLang: null,
111
112
  cliProvider: null,
113
+ cliModel: null,
112
114
  cliReasoning: null,
113
115
  output: 'text',
114
116
  debug: false,
@@ -203,14 +205,23 @@ export function parseArgs(args = process.argv.slice(2)) {
203
205
 
204
206
  let splitCommand = null;
205
207
  if (args[0] === 'split') {
206
- splitCommand = args[1];
207
- if (!['run', 'plan', 'apply', 'resume', 'abort'].includes(splitCommand)) {
208
+ const requestedAction = args[1];
209
+ const actions = ['run', 'plan', 'apply', 'resume', 'abort'];
210
+ if (!requestedAction || requestedAction.startsWith('-')) {
211
+ // Keep the common path short: `aicommit split` is the interactive
212
+ // split flow, while explicit actions remain available for automation
213
+ // and recovery.
214
+ splitCommand = 'run';
215
+ args = args.slice(1);
216
+ } else if (actions.includes(requestedAction)) {
217
+ splitCommand = requestedAction;
218
+ args = args.slice(2);
219
+ } else {
208
220
  throw fail(
209
221
  ERROR_CATEGORIES.CONFIG,
210
222
  'split requires one action: run, plan, apply, resume, or abort.',
211
223
  );
212
224
  }
213
- args = args.slice(2);
214
225
  }
215
226
 
216
227
  const doctor = args[0] === 'doctor';
@@ -219,6 +230,7 @@ export function parseArgs(args = process.argv.slice(2)) {
219
230
  let targetPath = null;
220
231
  let cliLang = null;
221
232
  let cliProvider = null;
233
+ let cliModel = null;
222
234
  let cliReasoning = null;
223
235
  let output = 'text';
224
236
  let debug = false;
@@ -378,6 +390,23 @@ export function parseArgs(args = process.argv.slice(2)) {
378
390
  continue;
379
391
  }
380
392
 
393
+ if (arg === '-m' || arg === '--model') {
394
+ cliModel = takeValue(args, i, arg, 'name');
395
+ i++;
396
+ continue;
397
+ }
398
+
399
+ if (arg.startsWith('--model=')) {
400
+ cliModel = arg.slice('--model='.length);
401
+ if (!cliModel) throw fail(ERROR_CATEGORIES.CONFIG, 'Missing value for --model.');
402
+ continue;
403
+ }
404
+
405
+ if (arg.startsWith('-m') && arg.length > 2) {
406
+ cliModel = arg.slice(2);
407
+ continue;
408
+ }
409
+
381
410
  if (!arg.startsWith('-')) {
382
411
  if (targetPath) {
383
412
  throw fail(
@@ -430,7 +459,7 @@ export function parseArgs(args = process.argv.slice(2)) {
430
459
  'split apply reads its scope from the plan; do not pass --scope or --split-hunks.',
431
460
  );
432
461
  }
433
- if (cliProvider || cliLang || cliReasoning || dryRun) {
462
+ if (cliProvider || cliModel || cliLang || cliReasoning || dryRun) {
434
463
  throw fail(
435
464
  ERROR_CATEGORIES.CONFIG,
436
465
  'split apply accepts only --file, --yes, --output, and --debug.',
@@ -442,6 +471,7 @@ export function parseArgs(args = process.argv.slice(2)) {
442
471
  split ||
443
472
  splitHunks ||
444
473
  cliProvider ||
474
+ cliModel ||
445
475
  cliLang ||
446
476
  cliReasoning ||
447
477
  dryRun
@@ -475,15 +505,23 @@ export function parseArgs(args = process.argv.slice(2)) {
475
505
  ) {
476
506
  throw fail(
477
507
  ERROR_CATEGORIES.CONFIG,
478
- 'config accepts only an optional path, --provider, --output, and --debug.',
508
+ 'config accepts only an optional path, --provider, --model, --output, and --debug.',
479
509
  );
480
510
  }
481
- if (configAction === 'path' && cliProvider) {
482
- throw fail(ERROR_CATEGORIES.CONFIG, 'config path does not accept --provider.');
511
+ if (configAction === 'path' && (cliProvider || cliModel)) {
512
+ throw fail(ERROR_CATEGORIES.CONFIG, 'config path does not accept --provider or --model.');
483
513
  }
484
514
  if (
485
515
  policyAction &&
486
- (cliProvider || cliLang || cliReasoning || split || splitHunks || splitCommand || dryRun || yes)
516
+ (cliProvider ||
517
+ cliModel ||
518
+ cliLang ||
519
+ cliReasoning ||
520
+ split ||
521
+ splitHunks ||
522
+ splitCommand ||
523
+ dryRun ||
524
+ yes)
487
525
  ) {
488
526
  throw fail(
489
527
  ERROR_CATEGORIES.CONFIG,
@@ -500,6 +538,7 @@ export function parseArgs(args = process.argv.slice(2)) {
500
538
  presetAction &&
501
539
  (targetPath ||
502
540
  cliProvider ||
541
+ cliModel ||
503
542
  cliLang ||
504
543
  cliReasoning ||
505
544
  split ||
@@ -525,7 +564,7 @@ export function parseArgs(args = process.argv.slice(2)) {
525
564
  ) {
526
565
  throw fail(
527
566
  ERROR_CATEGORIES.CONFIG,
528
- 'doctor accepts only --provider, --output, and --debug options.',
567
+ 'doctor accepts only --provider, --model, --output, and --debug options.',
529
568
  );
530
569
  }
531
570
 
@@ -533,6 +572,7 @@ export function parseArgs(args = process.argv.slice(2)) {
533
572
  targetPath,
534
573
  cliLang,
535
574
  cliProvider,
575
+ cliModel,
536
576
  cliReasoning,
537
577
  output,
538
578
  debug,
package/src/completion.js CHANGED
@@ -11,6 +11,7 @@ const TOP_LEVEL = [
11
11
  '--version',
12
12
  '--lang',
13
13
  '--provider',
14
+ '--model',
14
15
  '--split-hunks',
15
16
  '--scope',
16
17
  '--reasoning',
@@ -30,7 +31,7 @@ _aicommit() {
30
31
  command="\${COMP_WORDS[1]}"
31
32
 
32
33
  case "$command" in
33
- config) COMPREPLY=( $(compgen -W "show validate path --provider --output --debug" -- "$cur") ); return ;;
34
+ config) COMPREPLY=( $(compgen -W "show validate path --provider --model --output --debug" -- "$cur") ); return ;;
34
35
  policy) COMPREPLY=( $(compgen -W "template check --file --range --output --debug" -- "$cur") ); return ;;
35
36
  preset) COMPREPLY=( $(compgen -W "show validate path install rollback --file --output --debug" -- "$cur") ); return ;;
36
37
  completion) COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") ); return ;;
@@ -66,6 +67,7 @@ _aicommit() {
66
67
  '--version[show version]'
67
68
  '--lang=[commit language]:language:(zh en)'
68
69
  '--provider=[provider name]:provider:'
70
+ '--model=[model name]:model:'
69
71
  '--split-hunks[experimental same-file hunk splitting]'
70
72
  '--scope=[split scope]:scope:(staged all)'
71
73
  '--file=[split plan or commit-message file]:path:_files'
@@ -115,6 +117,7 @@ complete -c aicommit -s h -l help -d 'Show help'
115
117
  complete -c aicommit -s v -l version -d 'Show version'
116
118
  complete -c aicommit -s l -l lang -x -a 'zh en' -d 'Commit language'
117
119
  complete -c aicommit -s p -l provider -x -d 'Provider name'
120
+ complete -c aicommit -s m -l model -x -d 'Model name'
118
121
  complete -c aicommit -l split-hunks -d 'Experimental same-file hunk splitting'
119
122
  complete -c aicommit -l scope -x -a 'staged all' -d 'Split plan scope'
120
123
  complete -c aicommit -l file -r -d 'Split plan file'
@@ -28,7 +28,10 @@ function printPaths(paths) {
28
28
  );
29
29
  }
30
30
 
31
- export async function runConfigCommand(action, { provider = null, machineOutput = false } = {}) {
31
+ export async function runConfigCommand(
32
+ action,
33
+ { provider = null, model = null, machineOutput = false } = {},
34
+ ) {
32
35
  const projectRoot = getProjectRoot();
33
36
  const paths = await inspectConfigPaths(projectRoot);
34
37
  if (action === 'path') {
@@ -36,18 +39,17 @@ export async function runConfigCommand(action, { provider = null, machineOutput
36
39
  return { exitReason: 'config_path', data: { projectRoot, paths } };
37
40
  }
38
41
 
39
- const loaded = await loadConfig(provider, { resolveCredentials: false });
42
+ const loaded = await loadConfig(provider, { model, resolveCredentials: false });
40
43
  const data = {
41
44
  projectRoot: loaded.projectRoot,
42
45
  sources: loaded.loaded,
43
46
  provider: loaded.providerName,
47
+ model: loaded.modelName,
44
48
  paths,
45
49
  };
46
50
  if (action === 'validate') {
47
51
  if (!machineOutput) {
48
- console.log(
49
- `Configuration valid${loaded.providerName ? ` for provider "${loaded.providerName}"` : ''}.`,
50
- );
52
+ console.log(`Configuration valid for ${loaded.providerName}/${loaded.modelName}.`);
51
53
  printPaths(paths);
52
54
  }
53
55
  return { exitReason: 'config_valid', data };