@hifullmoon/aicommit 2.0.0 → 2.1.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.
@@ -15,22 +15,19 @@ JSON mode keeps one machine object on stdout and diagnostics on stderr. The `err
15
15
 
16
16
  JSON 模式保证 stdout 只有一个机器对象,诊断进入 stderr。`error.category` 与进程退出码可稳定用于自动化。
17
17
 
18
- | Symptom / 症状 | Category / exit | Likely cause / 常见原因 | Check and recovery / 检查与恢复 |
19
- | ------------------------------------------------------------------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
20
- | Config fails before Git/provider access / 在 Git/provider 前配置失败 | `config` / `2` | Malformed JSON, invalid URL, unsupported option, missing credential / JSON 损坏、URL 或选项无效、凭据缺失 | `aicommit config path`; `aicommit config validate`; fix only the user-owned file shown |
21
- | Project settings are ignored / 项目设置被忽略 | warning | Repository config tried to set endpoint, credential, extension, reasoning, metrics, or raise a ceiling / 仓库配置尝试设置连接、凭据、扩展或提高预算 | Move trusted connection settings to `~/.aicommit.config.json`; keep team rules in `.aicommit.policy.json` |
22
- | Not a Git repository, conflict, empty index / 非仓库、冲突或 index 为空 | `git_state` / `3` | Wrong directory or unsafe Git state / 目录错误或 Git 状态不安全 | `git status`; `aicommit /absolute/repo/path`; resolve conflicts before retrying |
23
- | DNS, connection, or timeout / DNS、连接或超时 | `network` / `4` | Endpoint unavailable, proxy/TLS issue, budget too short / endpoint 不可达、代理/TLS、超时过短 | `aicommit doctor -p NAME`; verify HTTPS URL; raise user-owned `timeoutMs` only if expected |
24
- | HTTP authentication/rate/parameter failure / 鉴权、限流或参数失败 | `provider` / `5` | Wrong key/model/body; non-retryable 4xx / key、model、body 错误 | Check `apiKeyEnv`, model and compatibility table; authentication is never retried automatically |
25
- | Empty or malformed model reply / 空或畸形回复 | `response_format` / `6` | Unsupported response dialect, token limit, policy/extension validator failure / 响应方言、token 限制、policy/扩展校验失败 | Raise `maxTokens`, choose the matching adapter, inspect validator issue code; one correction is already attempted |
26
- | `split run --scope=all --yes` stops before API call / split 非交互在 API 前停止 | `sensitive_data` / `7` | Complete untracked scan found sensitive-looking data / 完整未跟踪扫描发现疑似敏感数据 | Review/stage intended files explicitly; do not bypass without checking the actual content |
27
- | Commit aborts after generation / 生成后提交中止 | `concurrent_modification` / `8` | Index/worktree changed during the protected window / 受保护窗口中 index/worktree 被修改 | Review `git status`, restore the intended snapshot, and generate again |
28
- | Split stopped after one or more commits / split 部分提交后停止 | reported Git failure | Hook, crash, SIGINT, or concurrent pending edit / hook、崩溃、中断或待处理文件变化 | Run `aicommit split resume`; if another Git workflow already replaced the transaction, use `aicommit split abort`(只删除恢复元数据,不改提交或工作区) |
29
- | Preset install/rollback fails / preset 安装或回滚失败 | `config` / `2` | Schema/core range/adapter contract mismatch or no valid backup / schema、核心范围、adapter contract 不匹配或无备份 | `aicommit preset validate --file=...`; `aicommit preset show`; follow the compatibility guide |
30
- | Extension requires Node 20+ / 扩展要求 Node 20+ | `config` / `2` | Node 18 intentionally has no executable-extension fallback / Node 18 有意不降级运行扩展 | Upgrade Node for extensions or remove user manifest entries; core CLI still supports Node 18 |
31
- | Extension timeout or permission error / 扩展超时或权限错误 | warning or `response_format` | Extension exceeded its limit or tried denied filesystem/process access / 扩展超时或访问被拒资源 | Review the extension, keep it single-file, raise `extensions.timeoutMs` cautiously; validator failures are fail-closed |
32
- | npm provenance is absent or invalid / npm provenance 缺失或失败 | npm audit failure | Old npm CLI, non-trusted release, or wrong version / npm 过旧、非可信发布或版本错误 | Upgrade npm; run `npm audit signatures`; install only a version linked to the official workflow |
33
-
34
- If a failure remains, capture `aicommit doctor --output=json`, Node/Git versions, the error category, and redacted config sources. Never attach a diff, commit message, config file, API key, reasoning trace, extension source containing secrets, or credential-helper output to a public issue.
35
-
36
- 若问题仍未解决,请记录 `aicommit doctor --output=json`、Node/Git 版本、错误分类与脱敏后的配置来源。不要在公开 issue 中附加 diff、commit message、配置文件、API key、reasoning、含秘密的扩展源码或 credential-helper 输出。
18
+ | Symptom / 症状 | Category / exit | Likely cause / 常见原因 | Check and recovery / 检查与恢复 |
19
+ | ------------------------------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
20
+ | Config fails before Git/provider access / 在 Git/provider 前配置失败 | `config` / `2` | Malformed JSON, invalid URL, unsupported option, missing credential / JSON 损坏、URL 或选项无效、凭据缺失 | `aicommit config path`; `aicommit config validate`; fix only the user-owned file shown |
21
+ | Project settings are ignored / 项目设置被忽略 | warning | Repository config tried to set endpoint, credential, reasoning, or raise a ceiling / 仓库配置尝试设置连接、凭据、推理或提高预算 | Move trusted connection settings to `~/.aicommit/config.json`; keep team rules in `.aicommit.policy.json` |
22
+ | Not a Git repository, conflict, empty index / 非仓库、冲突或 index 为空 | `git_state` / `3` | Wrong directory or unsafe Git state / 目录错误或 Git 状态不安全 | `git status`; `aicommit /absolute/repo/path`; resolve conflicts before retrying |
23
+ | DNS, connection, or timeout / DNS、连接或超时 | `network` / `4` | Endpoint unavailable, proxy/TLS issue, budget too short / endpoint 不可达、代理/TLS、超时过短 | `aicommit doctor -p NAME`; verify HTTPS URL; raise user-owned `timeoutMs` only if expected |
24
+ | HTTP authentication/rate/parameter failure / 鉴权、限流或参数失败 | `provider` / `5` | Wrong key/model/body; non-retryable 4xx / key、model、body 错误 | Check `apiKeyEnv`, model and compatibility table; authentication is never retried automatically |
25
+ | Empty or malformed model reply / 空或畸形回复 | `response_format` / `6` | Unsupported response dialect, token limit, or policy validation failure / 响应方言、token 限制或 policy 校验失败 | Raise `maxTokens`, choose the matching adapter, inspect validator issue code; one correction is already attempted |
26
+ | `split run --scope=all --yes` stops before API call / split 非交互在 API 前停止 | `sensitive_data` / `7` | Complete untracked scan found sensitive-looking data / 完整未跟踪扫描发现疑似敏感数据 | Review/stage intended files explicitly; do not bypass without checking the actual content |
27
+ | Commit aborts after generation / 生成后提交中止 | `concurrent_modification` / `8` | Index/worktree changed during the protected window / 受保护窗口中 index/worktree 被修改 | Review `git status`, restore the intended snapshot, and generate again |
28
+ | Split stopped after one or more commits / split 部分提交后停止 | reported Git failure | Hook, crash, SIGINT, or concurrent pending edit / hook、崩溃、中断或待处理文件变化 | Run `aicommit split resume`; if another Git workflow already replaced the transaction, use `aicommit split abort`(只删除恢复元数据,不改提交或工作区) |
29
+ | npm provenance is absent or invalid / npm provenance 缺失或失败 | npm audit failure | Old npm CLI, non-trusted release, or wrong version / npm 过旧、非可信发布或版本错误 | Upgrade npm; run `npm audit signatures`; install only a version linked to the official workflow |
30
+
31
+ If a failure remains, capture `aicommit doctor --output=json`, Node/Git versions, the error category, and redacted config sources. Never attach a diff, commit message, config file, API key, reasoning trace, or credential-helper output to a public issue.
32
+
33
+ 若问题仍未解决,请记录 `aicommit doctor --output=json`、Node/Git 版本、错误分类与脱敏后的配置来源。不要在公开 issue 中附加 diff、commit message、配置文件、API key、reasoning 或 credential-helper 输出。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hifullmoon/aicommit",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Safe, local-first AI commit message generator for Git workflows",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -8,7 +8,6 @@ import {
8
8
  validateCommitCandidate,
9
9
  } from './policy.js';
10
10
  import { encodeUntrustedData } from './trust.js';
11
- import { extensionHostFor, resolveProviderAdapter } from './extensions.js';
12
11
 
13
12
  // Default per-request timeout; overridable via the "timeoutMs" config key.
14
13
  const DEFAULT_TIMEOUT_MS = 120_000;
@@ -167,7 +166,7 @@ async function fetchWithRetry(apiUrl, init, timeout, configuredPolicy, consume)
167
166
  export async function requestGeneration(config, request) {
168
167
  secureEndpoint(config.apiUrl);
169
168
  const timeout = config.timeoutMs || DEFAULT_TIMEOUT_MS;
170
- const adapter = await resolveProviderAdapter(config, getProviderAdapter);
169
+ const adapter = getProviderAdapter(config);
171
170
  const payload = await adapter.buildRequest({
172
171
  messages: request.messages,
173
172
  temperature: request.temperature,
@@ -370,7 +369,7 @@ function hitTokenLimit(data) {
370
369
  // happened. Disable it only for providers where we know the switch is valid;
371
370
  // unknown compatible endpoints keep their configured behavior.
372
371
  async function reasoningForFollowUp(config) {
373
- const adapter = await resolveProviderAdapter(config, getProviderAdapter);
372
+ const adapter = getProviderAdapter(config);
374
373
  return adapter.reasoningForFollowUp(config.reasoning);
375
374
  }
376
375
 
@@ -660,23 +659,7 @@ export async function generateCommitMessage(
660
659
  // Validate against the versioned policy and give the provider exactly one
661
660
  // cheap correction attempt. The diff is never re-sent: the prior reply plus
662
661
  // concrete violations are sufficient to repair formatting and constraints.
663
- const validate = async (candidate) => {
664
- const builtIn = validateCommitCandidate(candidate, { policy, diff });
665
- const host = extensionHostFor(config);
666
- if (!host) return builtIn;
667
- const extensionIssues = await host.validateMessage(candidate, policy);
668
- const issues = [...builtIn.issues, ...extensionIssues];
669
- const errors = issues.filter((item) => item.severity === 'error');
670
- const warnings = issues.filter((item) => item.severity === 'warning');
671
- return {
672
- ...builtIn,
673
- valid: errors.length === 0,
674
- needsCorrection: errors.length > 0,
675
- issues,
676
- errors,
677
- warnings,
678
- };
679
- };
662
+ const validate = (candidate) => validateCommitCandidate(candidate, { policy, diff });
680
663
 
681
664
  let validation = await validate(message);
682
665
  if (message.trim() && validation.needsCorrection) {
package/src/cli.js CHANGED
@@ -14,28 +14,24 @@ function showHelp() {
14
14
  ${chalk.bold('Usage:')}
15
15
  ${chalk.dim('$')} aicommit [path] [options]
16
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
17
  ${chalk.dim('$')} aicommit split [run|plan|apply|resume|abort] [options]
22
18
 
23
- ${chalk.bold('Commands:')}
19
+ ${chalk.bold('Everyday commands:')}
24
20
  setup Interactive configuration wizard
25
21
  doctor Diagnose runtime, config, credentials, and connectivity
22
+ split Plan and create file-level logical commits
23
+
24
+ ${chalk.bold('Advanced commands:')}
26
25
  config show Print the effective configuration with secrets redacted
27
26
  config validate Parse, merge, and validate configuration without reading credentials
28
27
  config path Print user, project, and team-policy paths
29
28
  policy template Print a safe repository team-policy template
30
29
  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
30
  completion Generate Bash, Zsh, or Fish completion on stdout
33
- split, split run Plan and create logical commits
34
31
  split plan Generate and export a fingerprinted JSON plan
35
32
  split apply Validate and apply an exported JSON plan
36
33
  split resume Resume the repository's unfinished transaction
37
34
  split abort Discard recovery metadata; keep commits and changes
38
- stats [action] Show quality/cost trends; show, clear, enable, disable
39
35
 
40
36
  ${chalk.bold('Arguments:')}
41
37
  path Target directory (default: current directory)
@@ -46,7 +42,6 @@ function showHelp() {
46
42
  -l, --lang=<zh|en> Commit message language (default: zh)
47
43
  -p, --provider=<name> Use the named provider from config "providers"
48
44
  -m, --model=<name> Use a named model from the selected provider
49
- --split-hunks Experimental same-file hunk planning (default: off)
50
45
  --scope=<scope> Scope for "split|split plan": staged, all
51
46
  --file=<path> Split-plan artifact or commit-message file
52
47
  --range=<revision> Git revision/range for "policy check" (default: HEAD)
@@ -58,35 +53,11 @@ function showHelp() {
58
53
  --debug Print debug info (parsed args, final config, etc.)
59
54
 
60
55
  ${chalk.bold('Examples:')}
61
- aicommit setup Run the interactive configuration wizard
62
- aicommit doctor Check runtime, config, credentials, and connectivity
63
- aicommit config show Show effective redacted configuration
64
- aicommit config validate --output=json Validate configuration for CI
65
- aicommit config path Show user and project config locations
66
- aicommit policy template > .aicommit.policy.json Create a committable policy
67
- aicommit policy check --file=.git/COMMIT_EDITMSG Validate a local commit message
68
- aicommit policy check --range=origin/main..HEAD --output=json Validate a CI range
69
- aicommit preset show Show the active versioned provider preset manifest
70
- aicommit preset install --file=provider-presets.json Install a compatible manifest
71
- aicommit preset rollback Restore the previously installed manifest
72
- aicommit completion zsh > _aicommit Generate Zsh completion
73
- aicommit stats Show local acceptance, quality, latency, and token trends
74
56
  aicommit Commit changes in current directory (Chinese)
75
57
  aicommit --lang=en Generate English commit message
76
- aicommit -p deepseek -m chat Select a configured provider and model
77
- aicommit split Choose staged/all scope, then plan logical commits
78
58
  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
81
- aicommit split plan --scope=staged --file=.aicommit-plan.json --yes
82
- aicommit split apply --file=.aicommit-plan.json --yes
83
- aicommit split resume --yes Resume pending groups from the checkpoint
84
- aicommit split abort --yes Discard a stale checkpoint without changing Git state
85
- aicommit --reasoning=low Stream reasoning; Ctrl+O expands/collapses it
86
59
  aicommit --dry-run Review a generated message without committing
87
60
  aicommit --yes Commit already staged changes without prompts
88
- aicommit --yes --output=json Emit one machine-readable result on stdout
89
- aicommit /path/to Commit changes in the specified directory
90
61
  `);
91
62
  }
92
63
 
@@ -115,7 +86,6 @@ function parsedDefaults(overrides = {}) {
115
86
  output: 'text',
116
87
  debug: false,
117
88
  split: null,
118
- splitHunks: false,
119
89
  splitCommand: null,
120
90
  splitPlanFile: null,
121
91
  dryRun: false,
@@ -126,10 +96,7 @@ function parsedDefaults(overrides = {}) {
126
96
  policyAction: null,
127
97
  policyMessageFile: null,
128
98
  policyRange: null,
129
- presetAction: null,
130
- presetFile: null,
131
99
  completionShell: null,
132
- statsAction: null,
133
100
  help: false,
134
101
  version: false,
135
102
  ...overrides,
@@ -137,6 +104,13 @@ function parsedDefaults(overrides = {}) {
137
104
  }
138
105
 
139
106
  export function parseArgs(args = process.argv.slice(2)) {
107
+ if (args[0] === 'stats' || args[0] === 'preset') {
108
+ throw fail(
109
+ ERROR_CATEGORIES.CONFIG,
110
+ `The "${args[0]}" command was removed to keep aicommit focused on commit generation.`,
111
+ );
112
+ }
113
+
140
114
  // "setup" is a standalone subcommand — it doesn't combine with the
141
115
  // commit-flow options, so short-circuit before parsing them.
142
116
  if (args[0] === 'setup') {
@@ -149,15 +123,6 @@ export function parseArgs(args = process.argv.slice(2)) {
149
123
  return parsedDefaults({ setup: true });
150
124
  }
151
125
 
152
- if (args[0] === 'stats') {
153
- const action = args[1] || 'show';
154
- const allowed = ['show', 'clear', 'enable', 'disable'];
155
- if (args.length > 2 || !allowed.includes(action)) {
156
- throw fail(ERROR_CATEGORIES.CONFIG, `stats accepts one action: ${allowed.join(', ')}.`);
157
- }
158
- return parsedDefaults({ statsAction: action });
159
- }
160
-
161
126
  if (args[0] === 'completion') {
162
127
  const shell = args[1];
163
128
  if (args.length !== 2 || !['bash', 'zsh', 'fish'].includes(shell)) {
@@ -182,18 +147,6 @@ export function parseArgs(args = process.argv.slice(2)) {
182
147
  args = args.slice(2);
183
148
  }
184
149
 
185
- let presetAction = null;
186
- if (args[0] === 'preset') {
187
- presetAction = args[1];
188
- if (!['show', 'validate', 'path', 'install', 'rollback'].includes(presetAction)) {
189
- throw fail(
190
- ERROR_CATEGORIES.CONFIG,
191
- 'preset requires one action: show, validate, path, install, or rollback.',
192
- );
193
- }
194
- args = args.slice(2);
195
- }
196
-
197
150
  let configAction = null;
198
151
  if (args[0] === 'config') {
199
152
  configAction = args[1];
@@ -235,11 +188,9 @@ export function parseArgs(args = process.argv.slice(2)) {
235
188
  let output = 'text';
236
189
  let debug = false;
237
190
  let split = null;
238
- let splitHunks = false;
239
191
  let splitPlanFile = null;
240
192
  let policyMessageFile = null;
241
193
  let policyRange = null;
242
- let presetFile = null;
243
194
  let splitScopeOption = false;
244
195
  let dryRun = false;
245
196
  let yes = false;
@@ -275,11 +226,6 @@ export function parseArgs(args = process.argv.slice(2)) {
275
226
  continue;
276
227
  }
277
228
 
278
- if (arg === '--split-hunks') {
279
- splitHunks = true;
280
- continue;
281
- }
282
-
283
229
  if (arg === '--scope') {
284
230
  splitScopeOption = true;
285
231
  split = takeValue(args, i, arg, 'staged|all');
@@ -302,7 +248,6 @@ export function parseArgs(args = process.argv.slice(2)) {
302
248
  if (arg === '--file') {
303
249
  const value = takeValue(args, i, arg, 'path');
304
250
  if (policyAction) policyMessageFile = value;
305
- else if (presetAction) presetFile = value;
306
251
  else splitPlanFile = value;
307
252
  i++;
308
253
  continue;
@@ -312,7 +257,6 @@ export function parseArgs(args = process.argv.slice(2)) {
312
257
  const value = arg.slice('--file='.length);
313
258
  if (!value) throw fail(ERROR_CATEGORIES.CONFIG, 'Missing value for --file.');
314
259
  if (policyAction) policyMessageFile = value;
315
- else if (presetAction) presetFile = value;
316
260
  else splitPlanFile = value;
317
261
  continue;
318
262
  }
@@ -430,12 +374,6 @@ export function parseArgs(args = process.argv.slice(2)) {
430
374
  if (!['text', 'json'].includes(output)) {
431
375
  throw fail(ERROR_CATEGORIES.CONFIG, `Invalid output mode: "${output}". Use text or json.`);
432
376
  }
433
- if (splitHunks && !['run', 'plan'].includes(splitCommand)) {
434
- throw fail(
435
- ERROR_CATEGORIES.CONFIG,
436
- '--split-hunks is only valid with "aicommit split run" or "aicommit split plan".',
437
- );
438
- }
439
377
  if (splitCommand) {
440
378
  if (['plan', 'apply'].includes(splitCommand) && !splitPlanFile) {
441
379
  throw fail(ERROR_CATEGORIES.CONFIG, `split ${splitCommand} requires --file=<path>.`);
@@ -453,10 +391,10 @@ export function parseArgs(args = process.argv.slice(2)) {
453
391
  }
454
392
  if (splitCommand === 'plan') dryRun = true;
455
393
  } else if (splitCommand === 'apply') {
456
- if (split || splitHunks) {
394
+ if (split) {
457
395
  throw fail(
458
396
  ERROR_CATEGORIES.CONFIG,
459
- 'split apply reads its scope from the plan; do not pass --scope or --split-hunks.',
397
+ 'split apply reads its scope from the plan; do not pass --scope.',
460
398
  );
461
399
  }
462
400
  if (cliProvider || cliModel || cliLang || cliReasoning || dryRun) {
@@ -466,16 +404,7 @@ export function parseArgs(args = process.argv.slice(2)) {
466
404
  );
467
405
  }
468
406
  } else {
469
- if (
470
- splitPlanFile ||
471
- split ||
472
- splitHunks ||
473
- cliProvider ||
474
- cliModel ||
475
- cliLang ||
476
- cliReasoning ||
477
- dryRun
478
- ) {
407
+ if (splitPlanFile || split || cliProvider || cliModel || cliLang || cliReasoning || dryRun) {
479
408
  throw fail(
480
409
  ERROR_CATEGORIES.CONFIG,
481
410
  `split ${splitCommand} accepts only an optional path, --yes, --output, and --debug.`,
@@ -494,14 +423,7 @@ export function parseArgs(args = process.argv.slice(2)) {
494
423
  }
495
424
  if (
496
425
  configAction &&
497
- (cliLang ||
498
- cliReasoning ||
499
- split ||
500
- splitHunks ||
501
- splitCommand ||
502
- splitPlanFile ||
503
- dryRun ||
504
- yes)
426
+ (cliLang || cliReasoning || split || splitCommand || splitPlanFile || dryRun || yes)
505
427
  ) {
506
428
  throw fail(
507
429
  ERROR_CATEGORIES.CONFIG,
@@ -513,15 +435,7 @@ export function parseArgs(args = process.argv.slice(2)) {
513
435
  }
514
436
  if (
515
437
  policyAction &&
516
- (cliProvider ||
517
- cliModel ||
518
- cliLang ||
519
- cliReasoning ||
520
- split ||
521
- splitHunks ||
522
- splitCommand ||
523
- dryRun ||
524
- yes)
438
+ (cliProvider || cliModel || cliLang || cliReasoning || split || splitCommand || dryRun || yes)
525
439
  ) {
526
440
  throw fail(
527
441
  ERROR_CATEGORIES.CONFIG,
@@ -534,30 +448,6 @@ export function parseArgs(args = process.argv.slice(2)) {
534
448
  if (policyMessageFile && policyRange) {
535
449
  throw fail(ERROR_CATEGORIES.CONFIG, 'policy check accepts either --file or --range, not both.');
536
450
  }
537
- if (
538
- presetAction &&
539
- (targetPath ||
540
- cliProvider ||
541
- cliModel ||
542
- cliLang ||
543
- cliReasoning ||
544
- split ||
545
- splitHunks ||
546
- splitCommand ||
547
- dryRun ||
548
- yes)
549
- ) {
550
- throw fail(
551
- ERROR_CATEGORIES.CONFIG,
552
- 'preset accepts only --file (validate/install), --output, and --debug.',
553
- );
554
- }
555
- if (presetAction === 'install' && !presetFile) {
556
- throw fail(ERROR_CATEGORIES.CONFIG, 'preset install requires --file=<manifest>.');
557
- }
558
- if (presetFile && !['validate', 'install'].includes(presetAction)) {
559
- throw fail(ERROR_CATEGORIES.CONFIG, '--file is only valid with preset validate or install.');
560
- }
561
451
  if (
562
452
  doctor &&
563
453
  (targetPath || cliLang || cliReasoning || split || splitPlanFile || dryRun || yes)
@@ -577,7 +467,6 @@ export function parseArgs(args = process.argv.slice(2)) {
577
467
  output,
578
468
  debug,
579
469
  split,
580
- splitHunks,
581
470
  splitCommand,
582
471
  splitPlanFile,
583
472
  dryRun,
@@ -588,10 +477,7 @@ export function parseArgs(args = process.argv.slice(2)) {
588
477
  policyAction,
589
478
  policyMessageFile,
590
479
  policyRange,
591
- presetAction,
592
- presetFile,
593
480
  completionShell: null,
594
- statsAction: null,
595
481
  help: false,
596
482
  version: false,
597
483
  };
package/src/completion.js CHANGED
@@ -3,16 +3,13 @@ const TOP_LEVEL = [
3
3
  'doctor',
4
4
  'config',
5
5
  'policy',
6
- 'preset',
7
6
  'completion',
8
7
  'split',
9
- 'stats',
10
8
  '--help',
11
9
  '--version',
12
10
  '--lang',
13
11
  '--provider',
14
12
  '--model',
15
- '--split-hunks',
16
13
  '--scope',
17
14
  '--reasoning',
18
15
  '--no-reasoning',
@@ -33,10 +30,8 @@ _aicommit() {
33
30
  case "$command" in
34
31
  config) COMPREPLY=( $(compgen -W "show validate path --provider --model --output --debug" -- "$cur") ); return ;;
35
32
  policy) COMPREPLY=( $(compgen -W "template check --file --range --output --debug" -- "$cur") ); return ;;
36
- preset) COMPREPLY=( $(compgen -W "show validate path install rollback --file --output --debug" -- "$cur") ); return ;;
37
33
  completion) COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") ); return ;;
38
- split) COMPREPLY=( $(compgen -W "run plan apply resume abort --scope --file --split-hunks --yes --output --debug" -- "$cur") ); return ;;
39
- stats) COMPREPLY=( $(compgen -W "show clear enable disable" -- "$cur") ); return ;;
34
+ split) COMPREPLY=( $(compgen -W "run plan apply resume abort --scope --file --yes --output --debug" -- "$cur") ); return ;;
40
35
  esac
41
36
  case "$prev" in
42
37
  --lang|-l) COMPREPLY=( $(compgen -W "zh en" -- "$cur") ); return ;;
@@ -57,10 +52,8 @@ _aicommit() {
57
52
  'doctor:diagnose configuration and connectivity'
58
53
  'config:inspect or validate configuration'
59
54
  'policy:print or enforce a repository team policy'
60
- 'preset:manage versioned provider preset manifests'
61
55
  'completion:generate shell completion'
62
56
  'split:run, plan, apply, resume, or abort split commits'
63
- 'stats:show local quality trends'
64
57
  )
65
58
  options=(
66
59
  '--help[show help]'
@@ -68,7 +61,6 @@ _aicommit() {
68
61
  '--lang=[commit language]:language:(zh en)'
69
62
  '--provider=[provider name]:provider:'
70
63
  '--model=[model name]:model:'
71
- '--split-hunks[experimental same-file hunk splitting]'
72
64
  '--scope=[split scope]:scope:(staged all)'
73
65
  '--file=[split plan or commit-message file]:path:_files'
74
66
  '--range=[Git range for policy check]:revision:'
@@ -85,10 +77,8 @@ _aicommit() {
85
77
  case $words[2] in
86
78
  config) _values 'config action' show validate path ;;
87
79
  policy) _values 'policy action' template check ;;
88
- preset) _values 'preset action' show validate path install rollback ;;
89
80
  completion) _values 'shell' bash zsh fish ;;
90
81
  split) _values 'split action' run plan apply resume abort ;;
91
- stats) _values 'action' show clear enable disable ;;
92
82
  *) _describe 'command' commands ;;
93
83
  esac
94
84
  ;;
@@ -103,22 +93,17 @@ complete -c aicommit -n '__fish_use_subcommand' -a setup -d 'Interactive configu
103
93
  complete -c aicommit -n '__fish_use_subcommand' -a doctor -d 'Diagnose configuration and connectivity'
104
94
  complete -c aicommit -n '__fish_use_subcommand' -a config -d 'Inspect or validate configuration'
105
95
  complete -c aicommit -n '__fish_use_subcommand' -a policy -d 'Print or enforce a repository team policy'
106
- complete -c aicommit -n '__fish_use_subcommand' -a preset -d 'Manage versioned provider preset manifests'
107
96
  complete -c aicommit -n '__fish_use_subcommand' -a completion -d 'Generate shell completion'
108
97
  complete -c aicommit -n '__fish_use_subcommand' -a split -d 'Run, plan, apply, resume, or abort split commits'
109
- complete -c aicommit -n '__fish_use_subcommand' -a stats -d 'Show local quality trends'
110
98
  complete -c aicommit -n '__fish_seen_subcommand_from config' -a 'show validate path'
111
99
  complete -c aicommit -n '__fish_seen_subcommand_from policy' -a 'template check'
112
- complete -c aicommit -n '__fish_seen_subcommand_from preset' -a 'show validate path install rollback'
113
100
  complete -c aicommit -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish'
114
101
  complete -c aicommit -n '__fish_seen_subcommand_from split' -a 'run plan apply resume abort'
115
- complete -c aicommit -n '__fish_seen_subcommand_from stats' -a 'show clear enable disable'
116
102
  complete -c aicommit -s h -l help -d 'Show help'
117
103
  complete -c aicommit -s v -l version -d 'Show version'
118
104
  complete -c aicommit -s l -l lang -x -a 'zh en' -d 'Commit language'
119
105
  complete -c aicommit -s p -l provider -x -d 'Provider name'
120
106
  complete -c aicommit -s m -l model -x -d 'Model name'
121
- complete -c aicommit -l split-hunks -d 'Experimental same-file hunk splitting'
122
107
  complete -c aicommit -l scope -x -a 'staged all' -d 'Split plan scope'
123
108
  complete -c aicommit -l file -r -d 'Split plan file'
124
109
  complete -c aicommit -l range -r -d 'Git range for policy check'
@@ -4,25 +4,43 @@ import { join } from 'node:path';
4
4
  import { getProjectRoot, loadConfig } from './config.js';
5
5
  import { fileExists, stringifyConfigRedacted } from './utils.js';
6
6
  import { TEAM_POLICY_FILENAME } from './team-policy.js';
7
+ import { projectConfigPath, resolveConfigLocations, userConfigLocations } from './config-paths.js';
7
8
 
8
9
  function redactedObject(value) {
9
10
  return JSON.parse(stringifyConfigRedacted(value));
10
11
  }
11
12
 
12
13
  export async function inspectConfigPaths(projectRoot = getProjectRoot()) {
13
- const user = join(homedir(), '.aicommit.config.json');
14
- const project = join(projectRoot, '.aicommit.config.json');
14
+ const userLocations = await resolveConfigLocations(userConfigLocations(homedir()));
15
+ const project = projectConfigPath(projectRoot);
16
+ const projectExists = project !== userLocations.activePath && (await fileExists(project));
15
17
  const teamPolicy = join(projectRoot, TEAM_POLICY_FILENAME);
16
18
  return {
17
- user: { path: user, exists: await fileExists(user) },
18
- project: { path: project, exists: project !== user && (await fileExists(project)) },
19
+ user: {
20
+ path: userLocations.canonical,
21
+ exists: Boolean(userLocations.activePath),
22
+ activePath: userLocations.activePath,
23
+ legacyPath: userLocations.legacy,
24
+ legacyExists: userLocations.legacyExists,
25
+ usingLegacy: userLocations.usingLegacy,
26
+ },
27
+ project: {
28
+ path: project,
29
+ exists: projectExists,
30
+ activePath: projectExists ? project : null,
31
+ },
19
32
  teamPolicy: { path: teamPolicy, exists: await fileExists(teamPolicy) },
20
33
  };
21
34
  }
22
35
 
23
36
  function printPaths(paths) {
24
- console.log(`User config: ${paths.user.path}${paths.user.exists ? '' : ' (not found)'}`);
25
- console.log(`Project config: ${paths.project.path}${paths.project.exists ? '' : ' (not found)'}`);
37
+ const displayConfigPath = (item) => {
38
+ if (!item.exists) return `${item.path} (not found)`;
39
+ if (item.usingLegacy) return `${item.activePath} (legacy; move to ${item.path})`;
40
+ return item.path;
41
+ };
42
+ console.log(`User config: ${displayConfigPath(paths.user)}`);
43
+ console.log(`Project config: ${displayConfigPath(paths.project)}`);
26
44
  console.log(
27
45
  `Team policy: ${paths.teamPolicy.path}${paths.teamPolicy.exists ? '' : ' (not found)'}`,
28
46
  );
@@ -0,0 +1,31 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+
4
+ import { fileExists } from './utils.js';
5
+
6
+ export const AICOMMIT_DIRECTORY = '.aicommit';
7
+ export const CONFIG_FILENAME = 'config.json';
8
+ export const PROJECT_CONFIG_FILENAME = '.aicommit.config.json';
9
+
10
+ export function userConfigLocations(home = homedir()) {
11
+ return {
12
+ canonical: join(home, AICOMMIT_DIRECTORY, CONFIG_FILENAME),
13
+ legacy: join(home, PROJECT_CONFIG_FILENAME),
14
+ };
15
+ }
16
+
17
+ export function projectConfigPath(projectRoot) {
18
+ return join(projectRoot, PROJECT_CONFIG_FILENAME);
19
+ }
20
+
21
+ export async function resolveConfigLocations(locations) {
22
+ const canonicalExists = await fileExists(locations.canonical);
23
+ const legacyExists = await fileExists(locations.legacy);
24
+ return {
25
+ ...locations,
26
+ canonicalExists,
27
+ legacyExists,
28
+ activePath: canonicalExists ? locations.canonical : legacyExists ? locations.legacy : null,
29
+ usingLegacy: !canonicalExists && legacyExists,
30
+ };
31
+ }