@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.
- package/.aicommit.config.example.json +118 -0
- package/CHANGELOG.md +131 -0
- package/LICENSE +21 -0
- package/README.md +406 -0
- package/README.zh-CN.md +408 -0
- package/SECURITY.md +33 -0
- package/bin/aicommit.js +47 -0
- package/docs/distribution.md +104 -0
- package/docs/examples/aicommit-policy.yml +24 -0
- package/docs/examples/commit-msg +4 -0
- package/docs/examples/extension/aicommit-extension.json +9 -0
- package/docs/examples/extension/index.mjs +32 -0
- package/docs/extensions.md +93 -0
- package/docs/privacy.md +58 -0
- package/docs/provider-compatibility.md +37 -0
- package/docs/provider-presets.md +100 -0
- package/docs/team-policy.md +86 -0
- package/docs/troubleshooting.md +37 -0
- package/package.json +100 -0
- package/presets/provider-presets.json +60 -0
- package/schemas/aicommit-extension.schema.json +27 -0
- package/schemas/aicommit-output.schema.json +86 -0
- package/schemas/aicommit-provider-presets.schema.json +56 -0
- package/schemas/aicommit-split-checkpoint.schema.json +92 -0
- package/schemas/aicommit-split-plan.schema.json +108 -0
- package/schemas/aicommit-team-policy.schema.json +59 -0
- package/src/api.js +740 -0
- package/src/cli.js +558 -0
- package/src/completion.js +136 -0
- package/src/config-command.js +59 -0
- package/src/config.js +524 -0
- package/src/context.js +537 -0
- package/src/credentials.js +123 -0
- package/src/doctor.js +131 -0
- package/src/errors.js +96 -0
- package/src/extension-runner.mjs +36 -0
- package/src/extensions.js +426 -0
- package/src/generation-ui.js +53 -0
- package/src/git.js +458 -0
- package/src/main.js +768 -0
- package/src/metrics.js +375 -0
- package/src/output.js +87 -0
- package/src/policy-command.js +172 -0
- package/src/policy.js +421 -0
- package/src/preset-command.js +91 -0
- package/src/provider-presets.js +361 -0
- package/src/providers.js +306 -0
- package/src/setup.js +268 -0
- package/src/split-checkpoint.js +252 -0
- package/src/split-hunks.js +263 -0
- package/src/split-plan.js +339 -0
- package/src/split.js +2285 -0
- package/src/team-policy.js +95 -0
- package/src/trust.js +31 -0
- package/src/ui.js +488 -0
- package/src/utils.js +165 -0
- 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
|
+
}
|