@hifullmoon/aicommit 1.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 (57) hide show
  1. package/.aicommit.config.example.json +118 -0
  2. package/CHANGELOG.md +131 -0
  3. package/LICENSE +21 -0
  4. package/README.md +406 -0
  5. package/README.zh-CN.md +408 -0
  6. package/SECURITY.md +33 -0
  7. package/bin/aicommit.js +47 -0
  8. package/docs/distribution.md +104 -0
  9. package/docs/examples/aicommit-policy.yml +24 -0
  10. package/docs/examples/commit-msg +4 -0
  11. package/docs/examples/extension/aicommit-extension.json +9 -0
  12. package/docs/examples/extension/index.mjs +32 -0
  13. package/docs/extensions.md +93 -0
  14. package/docs/privacy.md +58 -0
  15. package/docs/provider-compatibility.md +37 -0
  16. package/docs/provider-presets.md +100 -0
  17. package/docs/team-policy.md +86 -0
  18. package/docs/troubleshooting.md +37 -0
  19. package/package.json +100 -0
  20. package/presets/provider-presets.json +60 -0
  21. package/schemas/aicommit-extension.schema.json +27 -0
  22. package/schemas/aicommit-output.schema.json +86 -0
  23. package/schemas/aicommit-provider-presets.schema.json +56 -0
  24. package/schemas/aicommit-split-checkpoint.schema.json +92 -0
  25. package/schemas/aicommit-split-plan.schema.json +108 -0
  26. package/schemas/aicommit-team-policy.schema.json +59 -0
  27. package/src/api.js +740 -0
  28. package/src/cli.js +558 -0
  29. package/src/completion.js +136 -0
  30. package/src/config-command.js +59 -0
  31. package/src/config.js +524 -0
  32. package/src/context.js +537 -0
  33. package/src/credentials.js +123 -0
  34. package/src/doctor.js +131 -0
  35. package/src/errors.js +96 -0
  36. package/src/extension-runner.mjs +36 -0
  37. package/src/extensions.js +426 -0
  38. package/src/generation-ui.js +53 -0
  39. package/src/git.js +458 -0
  40. package/src/main.js +768 -0
  41. package/src/metrics.js +375 -0
  42. package/src/output.js +87 -0
  43. package/src/policy-command.js +172 -0
  44. package/src/policy.js +421 -0
  45. package/src/preset-command.js +91 -0
  46. package/src/provider-presets.js +361 -0
  47. package/src/providers.js +306 -0
  48. package/src/setup.js +268 -0
  49. package/src/split-checkpoint.js +252 -0
  50. package/src/split-hunks.js +263 -0
  51. package/src/split-plan.js +339 -0
  52. package/src/split.js +2285 -0
  53. package/src/team-policy.js +95 -0
  54. package/src/trust.js +31 -0
  55. package/src/ui.js +488 -0
  56. package/src/utils.js +165 -0
  57. package/templates/.aicommit.policy.json +22 -0
package/src/cli.js ADDED
@@ -0,0 +1,558 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ import chalk from 'chalk';
4
+
5
+ import { ERROR_CATEGORIES, fail } from './errors.js';
6
+
7
+ const _require = createRequire(import.meta.url);
8
+ const { version: VERSION } = _require('../package.json');
9
+
10
+ function showHelp() {
11
+ console.log(`
12
+ ${chalk.cyan.bold('aicommit')} — AI-powered git commit message generator
13
+
14
+ ${chalk.bold('Usage:')}
15
+ ${chalk.dim('$')} aicommit [path] [options]
16
+ ${chalk.dim('$')} aicommit setup
17
+ ${chalk.dim('$')} aicommit config <show|validate|path> [options]
18
+ ${chalk.dim('$')} aicommit policy <template|check> [options]
19
+ ${chalk.dim('$')} aicommit preset <show|validate|path|install|rollback> [options]
20
+ ${chalk.dim('$')} aicommit completion <bash|zsh|fish>
21
+ ${chalk.dim('$')} aicommit split <run|plan|apply|resume|abort> [options]
22
+
23
+ ${chalk.bold('Commands:')}
24
+ setup Interactive configuration wizard
25
+ doctor Diagnose runtime, config, credentials, and connectivity
26
+ config show Print the effective configuration with secrets redacted
27
+ config validate Parse, merge, and validate configuration without reading credentials
28
+ config path Print user, project, and team-policy paths
29
+ policy template Print a safe repository team-policy template
30
+ policy check Validate a message file or Git range with the effective team policy
31
+ preset Inspect, validate, install, or roll back provider preset manifests
32
+ completion Generate Bash, Zsh, or Fish completion on stdout
33
+ split run Plan and create logical commits
34
+ split plan Generate and export a fingerprinted JSON plan
35
+ split apply Validate and apply an exported JSON plan
36
+ split resume Resume the repository's unfinished transaction
37
+ split abort Discard recovery metadata; keep commits and changes
38
+ stats [action] Show quality/cost trends; show, clear, enable, disable
39
+
40
+ ${chalk.bold('Arguments:')}
41
+ path Target directory (default: current directory)
42
+
43
+ ${chalk.bold('Options:')}
44
+ -h, --help Show this help message
45
+ -v, --version Show version number
46
+ -l, --lang=<zh|en> Commit message language (default: zh)
47
+ -p, --provider=<name> Use the named provider from config "providers"
48
+ --split-hunks Experimental same-file hunk planning (default: off)
49
+ --scope=<scope> Scope for "split run|plan": staged, all
50
+ --file=<path> Split-plan artifact or commit-message file
51
+ --range=<revision> Git revision/range for "policy check" (default: HEAD)
52
+ --reasoning=<level> Set reasoning effort (enabled by default: medium)
53
+ --no-reasoning Explicitly disable reasoning when supported
54
+ --dry-run Generate and review without creating commits
55
+ -y, --yes Non-interactive: accept the generated message/plan
56
+ --output=<text|json> Output mode; JSON requires --yes for commit flows
57
+ --debug Print debug info (parsed args, final config, etc.)
58
+
59
+ ${chalk.bold('Examples:')}
60
+ aicommit setup Run the interactive configuration wizard
61
+ aicommit doctor Check runtime, config, credentials, and connectivity
62
+ aicommit config show Show effective redacted configuration
63
+ aicommit config validate --output=json Validate configuration for CI
64
+ aicommit config path Show user and project config locations
65
+ aicommit policy template > .aicommit.policy.json Create a committable policy
66
+ aicommit policy check --file=.git/COMMIT_EDITMSG Validate a local commit message
67
+ aicommit policy check --range=origin/main..HEAD --output=json Validate a CI range
68
+ aicommit preset show Show the active versioned provider preset manifest
69
+ aicommit preset install --file=provider-presets.json Install a compatible manifest
70
+ aicommit preset rollback Restore the previously installed manifest
71
+ aicommit completion zsh > _aicommit Generate Zsh completion
72
+ aicommit stats Show local acceptance, quality, latency, and token trends
73
+ aicommit Commit changes in current directory (Chinese)
74
+ 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
80
+ aicommit split plan --scope=staged --file=.aicommit-plan.json --yes
81
+ aicommit split apply --file=.aicommit-plan.json --yes
82
+ aicommit split resume --yes Resume pending groups from the checkpoint
83
+ aicommit split abort --yes Discard a stale checkpoint without changing Git state
84
+ aicommit --reasoning=low Stream reasoning; Ctrl+O expands/collapses it
85
+ aicommit --dry-run Review a generated message without committing
86
+ aicommit --yes Commit already staged changes without prompts
87
+ aicommit --yes --output=json Emit one machine-readable result on stdout
88
+ aicommit /path/to Commit changes in the specified directory
89
+ `);
90
+ }
91
+
92
+ function showVersion() {
93
+ console.log(`aicommit v${VERSION}`);
94
+ }
95
+
96
+ // Read the value for an option like -l/--lang or -p/--provider. The next
97
+ // argument must exist and must not be another flag — otherwise `-l -s`
98
+ // would silently swallow "-s" as the value.
99
+ function takeValue(args, i, arg, hint) {
100
+ const next = args[i + 1];
101
+ if (!next || next.startsWith('-')) {
102
+ throw fail(ERROR_CATEGORIES.CONFIG, `Missing value for ${arg}. Use ${arg}=<${hint}>`);
103
+ }
104
+ return next;
105
+ }
106
+
107
+ function parsedDefaults(overrides = {}) {
108
+ return {
109
+ targetPath: null,
110
+ cliLang: null,
111
+ cliProvider: null,
112
+ cliReasoning: null,
113
+ output: 'text',
114
+ debug: false,
115
+ split: null,
116
+ splitHunks: false,
117
+ splitCommand: null,
118
+ splitPlanFile: null,
119
+ dryRun: false,
120
+ yes: false,
121
+ setup: false,
122
+ doctor: false,
123
+ configAction: null,
124
+ policyAction: null,
125
+ policyMessageFile: null,
126
+ policyRange: null,
127
+ presetAction: null,
128
+ presetFile: null,
129
+ completionShell: null,
130
+ statsAction: null,
131
+ help: false,
132
+ version: false,
133
+ ...overrides,
134
+ };
135
+ }
136
+
137
+ export function parseArgs(args = process.argv.slice(2)) {
138
+ // "setup" is a standalone subcommand — it doesn't combine with the
139
+ // commit-flow options, so short-circuit before parsing them.
140
+ if (args[0] === 'setup') {
141
+ if (args.length > 1) {
142
+ throw fail(
143
+ ERROR_CATEGORIES.CONFIG,
144
+ `"setup" takes no arguments — got: ${args.slice(1).join(' ')}`,
145
+ );
146
+ }
147
+ return parsedDefaults({ setup: true });
148
+ }
149
+
150
+ if (args[0] === 'stats') {
151
+ const action = args[1] || 'show';
152
+ const allowed = ['show', 'clear', 'enable', 'disable'];
153
+ if (args.length > 2 || !allowed.includes(action)) {
154
+ throw fail(ERROR_CATEGORIES.CONFIG, `stats accepts one action: ${allowed.join(', ')}.`);
155
+ }
156
+ return parsedDefaults({ statsAction: action });
157
+ }
158
+
159
+ if (args[0] === 'completion') {
160
+ const shell = args[1];
161
+ if (args.length !== 2 || !['bash', 'zsh', 'fish'].includes(shell)) {
162
+ throw fail(ERROR_CATEGORIES.CONFIG, 'completion requires one shell: bash, zsh, or fish.');
163
+ }
164
+ return parsedDefaults({ completionShell: shell });
165
+ }
166
+
167
+ if (args[0] === 'policy' && args[1] === 'template') {
168
+ if (args.length !== 2) {
169
+ throw fail(ERROR_CATEGORIES.CONFIG, 'policy template takes no arguments.');
170
+ }
171
+ return parsedDefaults({ policyAction: 'template' });
172
+ }
173
+
174
+ let policyAction = null;
175
+ if (args[0] === 'policy') {
176
+ policyAction = args[1];
177
+ if (policyAction !== 'check') {
178
+ throw fail(ERROR_CATEGORIES.CONFIG, 'policy requires one action: template or check.');
179
+ }
180
+ args = args.slice(2);
181
+ }
182
+
183
+ let presetAction = null;
184
+ if (args[0] === 'preset') {
185
+ presetAction = args[1];
186
+ if (!['show', 'validate', 'path', 'install', 'rollback'].includes(presetAction)) {
187
+ throw fail(
188
+ ERROR_CATEGORIES.CONFIG,
189
+ 'preset requires one action: show, validate, path, install, or rollback.',
190
+ );
191
+ }
192
+ args = args.slice(2);
193
+ }
194
+
195
+ let configAction = null;
196
+ if (args[0] === 'config') {
197
+ configAction = args[1];
198
+ if (!['show', 'validate', 'path'].includes(configAction)) {
199
+ throw fail(ERROR_CATEGORIES.CONFIG, 'config requires one action: show, validate, or path.');
200
+ }
201
+ args = args.slice(2);
202
+ }
203
+
204
+ let splitCommand = null;
205
+ if (args[0] === 'split') {
206
+ splitCommand = args[1];
207
+ if (!['run', 'plan', 'apply', 'resume', 'abort'].includes(splitCommand)) {
208
+ throw fail(
209
+ ERROR_CATEGORIES.CONFIG,
210
+ 'split requires one action: run, plan, apply, resume, or abort.',
211
+ );
212
+ }
213
+ args = args.slice(2);
214
+ }
215
+
216
+ const doctor = args[0] === 'doctor';
217
+ if (doctor) args = args.slice(1);
218
+
219
+ let targetPath = null;
220
+ let cliLang = null;
221
+ let cliProvider = null;
222
+ let cliReasoning = null;
223
+ let output = 'text';
224
+ let debug = false;
225
+ let split = null;
226
+ let splitHunks = false;
227
+ let splitPlanFile = null;
228
+ let policyMessageFile = null;
229
+ let policyRange = null;
230
+ let presetFile = null;
231
+ let splitScopeOption = false;
232
+ let dryRun = false;
233
+ let yes = false;
234
+ const setup = false;
235
+
236
+ for (let i = 0; i < args.length; i++) {
237
+ const arg = args[i];
238
+
239
+ if (arg === '-h' || arg === '--help') {
240
+ showHelp();
241
+ return parsedDefaults({ output, help: true });
242
+ }
243
+
244
+ if (arg === '-v' || arg === '--version') {
245
+ showVersion();
246
+ return parsedDefaults({ output, version: true });
247
+ }
248
+
249
+ if (arg === '--debug') {
250
+ debug = true;
251
+ continue;
252
+ }
253
+
254
+ if (arg === '--output') {
255
+ output = takeValue(args, i, arg, 'text|json');
256
+ i++;
257
+ continue;
258
+ }
259
+
260
+ if (arg.startsWith('--output=')) {
261
+ output = arg.slice('--output='.length);
262
+ if (!output) throw fail(ERROR_CATEGORIES.CONFIG, 'Missing value for --output.');
263
+ continue;
264
+ }
265
+
266
+ if (arg === '--split-hunks') {
267
+ splitHunks = true;
268
+ continue;
269
+ }
270
+
271
+ if (arg === '--scope') {
272
+ splitScopeOption = true;
273
+ split = takeValue(args, i, arg, 'staged|all');
274
+ i++;
275
+ if (!['staged', 'all'].includes(split)) {
276
+ throw fail(ERROR_CATEGORIES.CONFIG, `Invalid split scope: "${split}". Use staged or all.`);
277
+ }
278
+ continue;
279
+ }
280
+
281
+ if (arg.startsWith('--scope=')) {
282
+ splitScopeOption = true;
283
+ split = arg.slice('--scope='.length);
284
+ if (!['staged', 'all'].includes(split)) {
285
+ throw fail(ERROR_CATEGORIES.CONFIG, `Invalid split scope: "${split}". Use staged or all.`);
286
+ }
287
+ continue;
288
+ }
289
+
290
+ if (arg === '--file') {
291
+ const value = takeValue(args, i, arg, 'path');
292
+ if (policyAction) policyMessageFile = value;
293
+ else if (presetAction) presetFile = value;
294
+ else splitPlanFile = value;
295
+ i++;
296
+ continue;
297
+ }
298
+
299
+ if (arg.startsWith('--file=')) {
300
+ const value = arg.slice('--file='.length);
301
+ if (!value) throw fail(ERROR_CATEGORIES.CONFIG, 'Missing value for --file.');
302
+ if (policyAction) policyMessageFile = value;
303
+ else if (presetAction) presetFile = value;
304
+ else splitPlanFile = value;
305
+ continue;
306
+ }
307
+
308
+ if (arg === '--range') {
309
+ policyRange = takeValue(args, i, arg, 'revision');
310
+ i++;
311
+ continue;
312
+ }
313
+
314
+ if (arg.startsWith('--range=')) {
315
+ policyRange = arg.slice('--range='.length);
316
+ if (!policyRange) throw fail(ERROR_CATEGORIES.CONFIG, 'Missing value for --range.');
317
+ continue;
318
+ }
319
+
320
+ if (arg === '--dry-run') {
321
+ dryRun = true;
322
+ continue;
323
+ }
324
+
325
+ if (arg === '-y' || arg === '--yes') {
326
+ yes = true;
327
+ continue;
328
+ }
329
+
330
+ if (arg === '--reasoning') {
331
+ cliReasoning = takeValue(args, i, arg, 'low|medium|high|xhigh|max');
332
+ i++;
333
+ continue;
334
+ }
335
+
336
+ if (arg.startsWith('--reasoning=')) {
337
+ cliReasoning = arg.slice('--reasoning='.length);
338
+ if (!cliReasoning) {
339
+ throw fail(ERROR_CATEGORIES.CONFIG, 'Missing value for --reasoning.');
340
+ }
341
+ continue;
342
+ }
343
+
344
+ if (arg === '--no-reasoning') {
345
+ cliReasoning = 'off';
346
+ continue;
347
+ }
348
+
349
+ if (arg === '-l' || arg === '--lang') {
350
+ cliLang = takeValue(args, i, arg, 'zh|en');
351
+ i++;
352
+ continue;
353
+ }
354
+
355
+ if (arg.startsWith('--lang=')) {
356
+ cliLang = arg.slice('--lang='.length);
357
+ continue;
358
+ }
359
+
360
+ if (arg.startsWith('-l') && arg.length > 2) {
361
+ cliLang = arg.slice(2);
362
+ continue;
363
+ }
364
+
365
+ if (arg === '-p' || arg === '--provider') {
366
+ cliProvider = takeValue(args, i, arg, 'name');
367
+ i++;
368
+ continue;
369
+ }
370
+
371
+ if (arg.startsWith('--provider=')) {
372
+ cliProvider = arg.slice('--provider='.length);
373
+ continue;
374
+ }
375
+
376
+ if (arg.startsWith('-p') && arg.length > 2) {
377
+ cliProvider = arg.slice(2);
378
+ continue;
379
+ }
380
+
381
+ if (!arg.startsWith('-')) {
382
+ if (targetPath) {
383
+ throw fail(
384
+ ERROR_CATEGORIES.CONFIG,
385
+ `Unexpected extra argument: ${arg}. Use aicommit --help for usage.`,
386
+ );
387
+ }
388
+ targetPath = arg;
389
+ } else {
390
+ throw fail(ERROR_CATEGORIES.CONFIG, `Unknown option: ${arg}. Use aicommit --help for usage.`);
391
+ }
392
+ }
393
+
394
+ const reasoningLevels = ['off', 'low', 'medium', 'high', 'xhigh', 'max'];
395
+ if (cliReasoning && !reasoningLevels.includes(cliReasoning)) {
396
+ throw fail(
397
+ ERROR_CATEGORIES.CONFIG,
398
+ `Invalid reasoning level: "${cliReasoning}". Use one of: ${reasoningLevels.join(', ')}`,
399
+ );
400
+ }
401
+ if (!['text', 'json'].includes(output)) {
402
+ throw fail(ERROR_CATEGORIES.CONFIG, `Invalid output mode: "${output}". Use text or json.`);
403
+ }
404
+ if (splitHunks && !['run', 'plan'].includes(splitCommand)) {
405
+ throw fail(
406
+ ERROR_CATEGORIES.CONFIG,
407
+ '--split-hunks is only valid with "aicommit split run" or "aicommit split plan".',
408
+ );
409
+ }
410
+ if (splitCommand) {
411
+ if (['plan', 'apply'].includes(splitCommand) && !splitPlanFile) {
412
+ throw fail(ERROR_CATEGORIES.CONFIG, `split ${splitCommand} requires --file=<path>.`);
413
+ }
414
+ if (splitCommand === 'run' || splitCommand === 'plan') {
415
+ split ||= 'prompt';
416
+ if (split === 'prompt' && yes) {
417
+ throw fail(
418
+ ERROR_CATEGORIES.CONFIG,
419
+ 'Non-interactive split requires an explicit scope: use --scope=staged or --scope=all.',
420
+ );
421
+ }
422
+ if (splitCommand === 'run' && splitPlanFile) {
423
+ throw fail(ERROR_CATEGORIES.CONFIG, 'split run does not accept --file.');
424
+ }
425
+ if (splitCommand === 'plan') dryRun = true;
426
+ } else if (splitCommand === 'apply') {
427
+ if (split || splitHunks) {
428
+ throw fail(
429
+ ERROR_CATEGORIES.CONFIG,
430
+ 'split apply reads its scope from the plan; do not pass --scope or --split-hunks.',
431
+ );
432
+ }
433
+ if (cliProvider || cliLang || cliReasoning || dryRun) {
434
+ throw fail(
435
+ ERROR_CATEGORIES.CONFIG,
436
+ 'split apply accepts only --file, --yes, --output, and --debug.',
437
+ );
438
+ }
439
+ } else {
440
+ if (
441
+ splitPlanFile ||
442
+ split ||
443
+ splitHunks ||
444
+ cliProvider ||
445
+ cliLang ||
446
+ cliReasoning ||
447
+ dryRun
448
+ ) {
449
+ throw fail(
450
+ ERROR_CATEGORIES.CONFIG,
451
+ `split ${splitCommand} accepts only an optional path, --yes, --output, and --debug.`,
452
+ );
453
+ }
454
+ }
455
+ }
456
+ if (splitScopeOption && !['run', 'plan'].includes(splitCommand)) {
457
+ throw fail(
458
+ ERROR_CATEGORIES.CONFIG,
459
+ '--scope is only valid with "aicommit split run" or "aicommit split plan".',
460
+ );
461
+ }
462
+ if (splitPlanFile && !['plan', 'apply'].includes(splitCommand)) {
463
+ throw fail(ERROR_CATEGORIES.CONFIG, '--file is only valid with split plan or split apply.');
464
+ }
465
+ if (
466
+ configAction &&
467
+ (cliLang ||
468
+ cliReasoning ||
469
+ split ||
470
+ splitHunks ||
471
+ splitCommand ||
472
+ splitPlanFile ||
473
+ dryRun ||
474
+ yes)
475
+ ) {
476
+ throw fail(
477
+ ERROR_CATEGORIES.CONFIG,
478
+ 'config accepts only an optional path, --provider, --output, and --debug.',
479
+ );
480
+ }
481
+ if (configAction === 'path' && cliProvider) {
482
+ throw fail(ERROR_CATEGORIES.CONFIG, 'config path does not accept --provider.');
483
+ }
484
+ if (
485
+ policyAction &&
486
+ (cliProvider || cliLang || cliReasoning || split || splitHunks || splitCommand || dryRun || yes)
487
+ ) {
488
+ throw fail(
489
+ ERROR_CATEGORIES.CONFIG,
490
+ 'policy check accepts only an optional path, --file or --range, --output, and --debug.',
491
+ );
492
+ }
493
+ if (!policyAction && policyRange) {
494
+ throw fail(ERROR_CATEGORIES.CONFIG, '--range is only valid with "aicommit policy check".');
495
+ }
496
+ if (policyMessageFile && policyRange) {
497
+ throw fail(ERROR_CATEGORIES.CONFIG, 'policy check accepts either --file or --range, not both.');
498
+ }
499
+ if (
500
+ presetAction &&
501
+ (targetPath ||
502
+ cliProvider ||
503
+ cliLang ||
504
+ cliReasoning ||
505
+ split ||
506
+ splitHunks ||
507
+ splitCommand ||
508
+ dryRun ||
509
+ yes)
510
+ ) {
511
+ throw fail(
512
+ ERROR_CATEGORIES.CONFIG,
513
+ 'preset accepts only --file (validate/install), --output, and --debug.',
514
+ );
515
+ }
516
+ if (presetAction === 'install' && !presetFile) {
517
+ throw fail(ERROR_CATEGORIES.CONFIG, 'preset install requires --file=<manifest>.');
518
+ }
519
+ if (presetFile && !['validate', 'install'].includes(presetAction)) {
520
+ throw fail(ERROR_CATEGORIES.CONFIG, '--file is only valid with preset validate or install.');
521
+ }
522
+ if (
523
+ doctor &&
524
+ (targetPath || cliLang || cliReasoning || split || splitPlanFile || dryRun || yes)
525
+ ) {
526
+ throw fail(
527
+ ERROR_CATEGORIES.CONFIG,
528
+ 'doctor accepts only --provider, --output, and --debug options.',
529
+ );
530
+ }
531
+
532
+ return {
533
+ targetPath,
534
+ cliLang,
535
+ cliProvider,
536
+ cliReasoning,
537
+ output,
538
+ debug,
539
+ split,
540
+ splitHunks,
541
+ splitCommand,
542
+ splitPlanFile,
543
+ dryRun,
544
+ yes,
545
+ setup,
546
+ doctor,
547
+ configAction,
548
+ policyAction,
549
+ policyMessageFile,
550
+ policyRange,
551
+ presetAction,
552
+ presetFile,
553
+ completionShell: null,
554
+ statsAction: null,
555
+ help: false,
556
+ version: false,
557
+ };
558
+ }
@@ -0,0 +1,136 @@
1
+ const TOP_LEVEL = [
2
+ 'setup',
3
+ 'doctor',
4
+ 'config',
5
+ 'policy',
6
+ 'preset',
7
+ 'completion',
8
+ 'split',
9
+ 'stats',
10
+ '--help',
11
+ '--version',
12
+ '--lang',
13
+ '--provider',
14
+ '--split-hunks',
15
+ '--scope',
16
+ '--reasoning',
17
+ '--no-reasoning',
18
+ '--dry-run',
19
+ '--yes',
20
+ '--output',
21
+ '--debug',
22
+ ];
23
+
24
+ const BASH = `# bash completion for aicommit
25
+ _aicommit() {
26
+ local cur prev command
27
+ COMPREPLY=()
28
+ cur="\${COMP_WORDS[COMP_CWORD]}"
29
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
30
+ command="\${COMP_WORDS[1]}"
31
+
32
+ case "$command" in
33
+ config) COMPREPLY=( $(compgen -W "show validate path --provider --output --debug" -- "$cur") ); return ;;
34
+ policy) COMPREPLY=( $(compgen -W "template check --file --range --output --debug" -- "$cur") ); return ;;
35
+ preset) COMPREPLY=( $(compgen -W "show validate path install rollback --file --output --debug" -- "$cur") ); return ;;
36
+ completion) COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") ); return ;;
37
+ split) COMPREPLY=( $(compgen -W "run plan apply resume abort --scope --file --split-hunks --yes --output --debug" -- "$cur") ); return ;;
38
+ stats) COMPREPLY=( $(compgen -W "show clear enable disable" -- "$cur") ); return ;;
39
+ esac
40
+ case "$prev" in
41
+ --lang|-l) COMPREPLY=( $(compgen -W "zh en" -- "$cur") ); return ;;
42
+ --reasoning) COMPREPLY=( $(compgen -W "low medium high xhigh max" -- "$cur") ); return ;;
43
+ --scope) COMPREPLY=( $(compgen -W "staged all" -- "$cur") ); return ;;
44
+ --output) COMPREPLY=( $(compgen -W "text json" -- "$cur") ); return ;;
45
+ esac
46
+ COMPREPLY=( $(compgen -W "${TOP_LEVEL.join(' ')}" -- "$cur") )
47
+ }
48
+ complete -F _aicommit aicommit
49
+ `;
50
+
51
+ const ZSH = `#compdef aicommit
52
+ _aicommit() {
53
+ local -a commands options
54
+ commands=(
55
+ 'setup:interactive configuration wizard'
56
+ 'doctor:diagnose configuration and connectivity'
57
+ 'config:inspect or validate configuration'
58
+ 'policy:print or enforce a repository team policy'
59
+ 'preset:manage versioned provider preset manifests'
60
+ 'completion:generate shell completion'
61
+ 'split:run, plan, apply, resume, or abort split commits'
62
+ 'stats:show local quality trends'
63
+ )
64
+ options=(
65
+ '--help[show help]'
66
+ '--version[show version]'
67
+ '--lang=[commit language]:language:(zh en)'
68
+ '--provider=[provider name]:provider:'
69
+ '--split-hunks[experimental same-file hunk splitting]'
70
+ '--scope=[split scope]:scope:(staged all)'
71
+ '--file=[split plan or commit-message file]:path:_files'
72
+ '--range=[Git range for policy check]:revision:'
73
+ '--reasoning=[reasoning effort]:effort:(low medium high xhigh max)'
74
+ '--no-reasoning[disable reasoning]'
75
+ '--dry-run[review without committing]'
76
+ '--yes[accept without prompts]'
77
+ '--output=[output mode]:output:(text json)'
78
+ '--debug[show debug details]'
79
+ )
80
+ _arguments -C $options '*::argument:->args'
81
+ case $state in
82
+ args)
83
+ case $words[2] in
84
+ config) _values 'config action' show validate path ;;
85
+ policy) _values 'policy action' template check ;;
86
+ preset) _values 'preset action' show validate path install rollback ;;
87
+ completion) _values 'shell' bash zsh fish ;;
88
+ split) _values 'split action' run plan apply resume abort ;;
89
+ stats) _values 'action' show clear enable disable ;;
90
+ *) _describe 'command' commands ;;
91
+ esac
92
+ ;;
93
+ esac
94
+ }
95
+ _aicommit "$@"
96
+ `;
97
+
98
+ const FISH = `# fish completion for aicommit
99
+ complete -c aicommit -f
100
+ complete -c aicommit -n '__fish_use_subcommand' -a setup -d 'Interactive configuration wizard'
101
+ complete -c aicommit -n '__fish_use_subcommand' -a doctor -d 'Diagnose configuration and connectivity'
102
+ complete -c aicommit -n '__fish_use_subcommand' -a config -d 'Inspect or validate configuration'
103
+ complete -c aicommit -n '__fish_use_subcommand' -a policy -d 'Print or enforce a repository team policy'
104
+ complete -c aicommit -n '__fish_use_subcommand' -a preset -d 'Manage versioned provider preset manifests'
105
+ complete -c aicommit -n '__fish_use_subcommand' -a completion -d 'Generate shell completion'
106
+ complete -c aicommit -n '__fish_use_subcommand' -a split -d 'Run, plan, apply, resume, or abort split commits'
107
+ complete -c aicommit -n '__fish_use_subcommand' -a stats -d 'Show local quality trends'
108
+ complete -c aicommit -n '__fish_seen_subcommand_from config' -a 'show validate path'
109
+ complete -c aicommit -n '__fish_seen_subcommand_from policy' -a 'template check'
110
+ complete -c aicommit -n '__fish_seen_subcommand_from preset' -a 'show validate path install rollback'
111
+ complete -c aicommit -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish'
112
+ complete -c aicommit -n '__fish_seen_subcommand_from split' -a 'run plan apply resume abort'
113
+ complete -c aicommit -n '__fish_seen_subcommand_from stats' -a 'show clear enable disable'
114
+ complete -c aicommit -s h -l help -d 'Show help'
115
+ complete -c aicommit -s v -l version -d 'Show version'
116
+ complete -c aicommit -s l -l lang -x -a 'zh en' -d 'Commit language'
117
+ complete -c aicommit -s p -l provider -x -d 'Provider name'
118
+ complete -c aicommit -l split-hunks -d 'Experimental same-file hunk splitting'
119
+ complete -c aicommit -l scope -x -a 'staged all' -d 'Split plan scope'
120
+ complete -c aicommit -l file -r -d 'Split plan file'
121
+ complete -c aicommit -l range -r -d 'Git range for policy check'
122
+ complete -c aicommit -l reasoning -x -a 'low medium high xhigh max' -d 'Reasoning effort'
123
+ complete -c aicommit -l no-reasoning -d 'Disable reasoning'
124
+ complete -c aicommit -l dry-run -d 'Review without committing'
125
+ complete -c aicommit -s y -l yes -d 'Accept without prompts'
126
+ complete -c aicommit -l output -x -a 'text json' -d 'Output mode'
127
+ complete -c aicommit -l debug -d 'Show debug details'
128
+ `;
129
+
130
+ const SCRIPTS = { bash: BASH, zsh: ZSH, fish: FISH };
131
+
132
+ export function generateCompletion(shell) {
133
+ const script = SCRIPTS[shell];
134
+ if (!script) throw new Error(`Unsupported shell: ${shell}. Use bash, zsh, or fish.`);
135
+ return script;
136
+ }