@hifullmoon/aicommit 2.0.1 → 2.2.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 +0 -10
- package/CHANGELOG.md +24 -1
- package/README.md +37 -37
- package/README.zh-CN.md +37 -37
- package/SECURITY.md +0 -1
- package/bin/aicommit.js +0 -18
- package/docs/distribution.md +1 -1
- package/docs/privacy.md +18 -20
- package/docs/provider-compatibility.md +13 -25
- package/docs/team-policy.md +2 -2
- package/docs/troubleshooting.md +16 -19
- package/package.json +1 -1
- package/presets/provider-presets.json +61 -1
- package/src/api.js +3 -20
- package/src/cli.js +16 -130
- package/src/completion.js +1 -16
- package/src/config-command.js +24 -6
- package/src/config-paths.js +31 -0
- package/src/config.js +22 -42
- package/src/doctor.js +1 -12
- package/src/git.js +1 -1
- package/src/main.js +2 -26
- package/src/provider-presets.js +5 -95
- package/src/providers.js +5 -6
- package/src/setup.js +18 -4
- package/src/split.js +4 -119
- package/src/utils.js +1 -1
- package/docs/examples/extension/aicommit-extension.json +0 -9
- package/docs/examples/extension/index.mjs +0 -32
- package/docs/extensions.md +0 -93
- package/docs/provider-presets.md +0 -113
- package/schemas/aicommit-extension.schema.json +0 -27
- package/src/extension-runner.mjs +0 -36
- package/src/extensions.js +0 -426
- package/src/metrics.js +0 -375
- package/src/preset-command.js +0 -91
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
# Provider compatibility / Provider 兼容表
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Built-in setup defaults choose an adapter and endpoint; adapters own request/response dialects; the core owns Git state, user interaction, HTTPS enforcement, retry, timeout, authorization, and machine output.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
内置 setup 默认值负责选择 adapter 和端点;adapter 负责请求/响应方言;核心负责 Git 状态、用户交互、HTTPS、重试、超时、鉴权与机器输出。
|
|
6
6
|
|
|
7
|
-
| Provider / adapter | Endpoint and auth / 端点与鉴权
|
|
8
|
-
| ---------------------------- |
|
|
9
|
-
| OpenAI / `openai` | Official HTTPS Chat Completions; Bearer key / 官方 HTTPS;Bearer key
|
|
10
|
-
| OpenRouter / `openrouter` | OpenRouter HTTPS; Bearer key; `X-Title: aicommit`
|
|
11
|
-
| DeepSeek / `deepseek` | DeepSeek HTTPS; Bearer key
|
|
12
|
-
| MiniMax / `minimax` | MiniMax HTTPS; Bearer key
|
|
13
|
-
| Kimi Code / `custom` | Kimi Code OpenAI-compatible HTTPS; Bearer key / Kimi Code OpenAI 兼容 HTTPS
|
|
14
|
-
| Ollama native / `ollama` | Loopback `/api/chat` or `/api/generate`; normally keyless HTTP
|
|
15
|
-
| Custom compatible / `custom` | HTTPS remote or loopback HTTP; optional core Bearer key
|
|
16
|
-
| Extension / `extension:<id>` | Core HTTPS/loopback transport and optional Bearer only / 核心传输与可选 Bearer | Declared conservative in API v1 / v1 保守声明 | Adapter operation can transform non-secret reasoning config | Adapter maps body and normalized response | No custom credential scheme or headers; credential-like body fields rejected / 不支持自定义鉴权或 header |
|
|
7
|
+
| Provider / adapter | Endpoint and auth / 端点与鉴权 | Streaming / 流式 | Reasoning / 推理 | Token and usage mapping / token 与 usage | Notes / 说明 |
|
|
8
|
+
| ---------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
9
|
+
| OpenAI / `openai` | Official HTTPS Chat Completions; Bearer key / 官方 HTTPS;Bearer key | SSE | Native for recognized `o*`/`gpt-5*`; model-dependent otherwise / 已识别推理模型原生支持 | `max_completion_tokens` for reasoning models, otherwise `max_tokens`; OpenAI usage | Unsupported effort is rejected locally for known model generations / 已知模型不支持的 effort 会本地拒绝 |
|
|
10
|
+
| OpenRouter / `openrouter` | OpenRouter HTTPS; Bearer key; `X-Title: aicommit` | SSE | `reasoning.effort` | `max_tokens`; OpenAI-style usage | Model IDs commonly include vendor prefix / model 通常含厂商前缀 |
|
|
11
|
+
| DeepSeek / `deepseek` | DeepSeek HTTPS; Bearer key | SSE | `thinking.type` plus normalized effort / thinking 与归一 effort | `max_tokens`; compatible usage | `medium`/`xhigh` map to supported high behavior where required / 必要时映射为 high |
|
|
12
|
+
| MiniMax / `minimax` | MiniMax HTTPS; Bearer key | SSE | `reasoning_split` and thinking switch | `max_tokens`; compatible usage | Adapter removes conflicting switches before send / 发送前移除冲突开关 |
|
|
13
|
+
| Kimi Code / `custom` | Kimi Code OpenAI-compatible HTTPS; Bearer key / Kimi Code OpenAI 兼容 HTTPS | SSE | Server default; no vendor fields injected / 使用服务端默认值,不注入厂商字段 | `max_tokens`; OpenAI-style usage | Bundled preset uses `kimi-for-coding`; membership and Platform keys are distinct / 会员与开放平台 key 不通用 |
|
|
14
|
+
| Ollama native / `ollama` | Loopback `/api/chat` or `/api/generate`; normally keyless HTTP | Complete JSON in v1; native NDJSON streaming is not consumed / v1 使用完整 JSON | Native `think` boolean | `options.num_predict`; `prompt_eval_count` + `eval_count` | OpenAI-compatible `/v1/chat/completions` uses the compatible shape instead / `/v1` 使用兼容方言 |
|
|
15
|
+
| Custom compatible / `custom` | HTTPS remote or loopback HTTP; optional core Bearer key | SSE when endpoint supports Chat Completions events | No vendor fields by default; explicit `enabledBody`/`disabledBody` / 默认不注入厂商字段 | `max_tokens`; common OpenAI/Anthropic/Ollama usage fields normalized | Validate the endpoint before trusting it with code or credentials / 发送代码前验证端点 |
|
|
17
16
|
|
|
18
17
|
## Compatibility contract / 兼容契约
|
|
19
18
|
|
|
@@ -21,17 +20,6 @@ All built-in adapters return `content`, optional `reasoning`, normalized usage,
|
|
|
21
20
|
|
|
22
21
|
所有内置 adapter 返回 `content`、可选 `reasoning`、标准 usage、finish reason、raw response、capability、attempts 与 latency。仅 429、部分 5xx、网络/响应中断会重试;鉴权、参数与安全错误不会重试。
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
Use `aicommit doctor -p provider-name` to verify the selected adapter and live endpoint. Reuse a built-in adapter when only setup defaults change, and use `custom` for an OpenAI-compatible endpoint with optional body switches. A protocol requiring a different transport, streaming parser, or credential scheme needs core support.
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
aicommit preset show
|
|
30
|
-
aicommit preset validate --file=provider-presets.json
|
|
31
|
-
aicommit preset install --file=provider-presets.json
|
|
32
|
-
aicommit doctor -p provider-name
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Use an existing built-in adapter when only setup defaults change. Use `custom` for an OpenAI-compatible endpoint with optional body switches. Use a reviewed `providerAdapter` extension only when the JSON request/response dialect differs and core Bearer authorization is sufficient. A protocol requiring different transport, streaming parser, or credential scheme is not compatible with extension API v1 and must not be disguised as a preset.
|
|
36
|
-
|
|
37
|
-
若只改变 setup 默认值,应复用内置 adapter;OpenAI-compatible endpoint 及少量 body 开关使用 `custom`;仅当 JSON 请求/响应方言不同且核心 Bearer 鉴权足够时,才使用已审查的 `providerAdapter` 扩展。若协议需要不同传输、流解析或鉴权方案,则不兼容扩展 API v1,不能伪装成 preset。
|
|
25
|
+
使用 `aicommit doctor -p provider-name` 校验所选 adapter 与在线端点。若只改变 setup 默认值,应复用内置 adapter;OpenAI-compatible endpoint 及少量 body 开关使用 `custom`。需要不同传输、流解析或鉴权方案的协议必须由核心直接支持。
|
package/docs/team-policy.md
CHANGED
|
@@ -46,7 +46,7 @@ Both paths call the same validator. `--output=json` returns the effective policy
|
|
|
46
46
|
|
|
47
47
|
1. Move Conventional Commit types, scope rules, subject length, body rules, breaking-change handling, and language out of free-form `prompt` text and into `.aicommit.policy.json`.
|
|
48
48
|
2. Declare every field instead of relying on personal defaults. Start with optional scopes and narrow the values only after current history has been sampled.
|
|
49
|
-
3. Keep provider credentials in `~/.aicommit
|
|
49
|
+
3. Keep provider credentials in `~/.aicommit/config.json` or environment variables; never copy them into the repository policy.
|
|
50
50
|
4. If commitlint already defines `type-enum`, `scope-enum`, `subject-max-length`, or `header-max-length`, keep that file committed. AICommit reads recognized scalar values as data and never executes the config.
|
|
51
51
|
5. Run the local hook and CI example on the same known-good and known-bad messages. Their `policyFingerprint` and issue codes must match before making the gate required.
|
|
52
52
|
|
|
@@ -54,7 +54,7 @@ Both paths call the same validator. `--output=json` returns the effective policy
|
|
|
54
54
|
|
|
55
55
|
1. 将 Conventional Commit 类型、scope 规则、标题长度、正文规则、破坏性变更和语言要求从自由文本 `prompt` 迁移到 `.aicommit.policy.json`。
|
|
56
56
|
2. 明确声明所有字段,不依赖个人默认值。可先保留可选 scope,再根据现有提交历史逐步收紧取值。
|
|
57
|
-
3. provider 凭据继续放在 `~/.aicommit
|
|
57
|
+
3. provider 凭据继续放在 `~/.aicommit/config.json` 或环境变量中,绝不要复制到仓库策略。
|
|
58
58
|
4. 如果 commitlint 已定义 `type-enum`、`scope-enum`、`subject-max-length` 或 `header-max-length`,继续提交该配置。AICommit 只按数据读取识别出的标量规则,不执行配置文件。
|
|
59
59
|
5. 用同一组已知正确/错误消息分别运行本地 hook 与 CI 示例;在强制启用门禁前,确认两者的 `policyFingerprint` 和问题代码一致。
|
|
60
60
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -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 / 常见原因
|
|
19
|
-
| ------------------------------------------------------------------------------- | ------------------------------- |
|
|
20
|
-
| Config fails before Git/provider access / 在 Git/provider 前配置失败 | `config` / `2` | Malformed JSON, invalid URL, unsupported option, missing credential / JSON 损坏、URL 或选项无效、凭据缺失
|
|
21
|
-
| Project settings are ignored / 项目设置被忽略 | warning | Repository config tried to set endpoint, credential,
|
|
22
|
-
| Not a Git repository, conflict, empty index / 非仓库、冲突或 index 为空 | `git_state` / `3` | Wrong directory or unsafe Git state / 目录错误或 Git 状态不安全
|
|
23
|
-
| DNS, connection, or timeout / DNS、连接或超时 | `network` / `4` | Endpoint unavailable, proxy/TLS issue, budget too short / endpoint 不可达、代理/TLS、超时过短
|
|
24
|
-
| HTTP authentication/rate/parameter failure / 鉴权、限流或参数失败 | `provider` / `5` | Wrong key/model/body; non-retryable 4xx / key、model、body 错误
|
|
25
|
-
| Empty or malformed model reply / 空或畸形回复 | `response_format` / `6` | Unsupported response dialect, token limit, policy
|
|
26
|
-
| `split run --scope=all --yes` stops before API call / split 非交互在 API 前停止 | `sensitive_data` / `7` | Complete untracked scan found sensitive-looking data / 完整未跟踪扫描发现疑似敏感数据
|
|
27
|
-
| Commit aborts after generation / 生成后提交中止 | `concurrent_modification` / `8` | Index/worktree changed during the protected window / 受保护窗口中 index/worktree 被修改
|
|
28
|
-
| Split stopped after one or more commits / split 部分提交后停止 | reported Git failure | Hook, crash, SIGINT, or concurrent pending edit / hook、崩溃、中断或待处理文件变化
|
|
29
|
-
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"kind": "aicommit-provider-presets",
|
|
3
3
|
"schemaVersion": 2,
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"compatibility": {
|
|
6
6
|
"coreMinimum": "1.5.1",
|
|
7
7
|
"coreMaximumExclusive": "3.0.0",
|
|
@@ -18,6 +18,18 @@
|
|
|
18
18
|
"gpt-4o": {
|
|
19
19
|
"label": "GPT-4o",
|
|
20
20
|
"modelId": "gpt-4o"
|
|
21
|
+
},
|
|
22
|
+
"gpt-5.6-sol": {
|
|
23
|
+
"label": "GPT-5.6 Sol",
|
|
24
|
+
"modelId": "gpt-5.6-sol"
|
|
25
|
+
},
|
|
26
|
+
"gpt-5.6-terra": {
|
|
27
|
+
"label": "GPT-5.6 Terra",
|
|
28
|
+
"modelId": "gpt-5.6-terra"
|
|
29
|
+
},
|
|
30
|
+
"gpt-5.6-luna": {
|
|
31
|
+
"label": "GPT-5.6 Luna",
|
|
32
|
+
"modelId": "gpt-5.6-luna"
|
|
21
33
|
}
|
|
22
34
|
}
|
|
23
35
|
},
|
|
@@ -31,6 +43,10 @@
|
|
|
31
43
|
"chat": {
|
|
32
44
|
"label": "DeepSeek Chat",
|
|
33
45
|
"modelId": "deepseek-v4-flash"
|
|
46
|
+
},
|
|
47
|
+
"pro": {
|
|
48
|
+
"label": "DeepSeek V4 Pro",
|
|
49
|
+
"modelId": "deepseek-v4-pro"
|
|
34
50
|
}
|
|
35
51
|
}
|
|
36
52
|
},
|
|
@@ -43,6 +59,42 @@
|
|
|
43
59
|
"models": {
|
|
44
60
|
"default": {
|
|
45
61
|
"modelId": "openai/gpt-4o-mini"
|
|
62
|
+
},
|
|
63
|
+
"auto": {
|
|
64
|
+
"label": "OpenRouter Auto",
|
|
65
|
+
"modelId": "openrouter/auto"
|
|
66
|
+
},
|
|
67
|
+
"gpt-5.6-terra": {
|
|
68
|
+
"label": "GPT-5.6 Terra",
|
|
69
|
+
"modelId": "openai/gpt-5.6-terra"
|
|
70
|
+
},
|
|
71
|
+
"claude-sonnet-5": {
|
|
72
|
+
"label": "Claude Sonnet 5",
|
|
73
|
+
"modelId": "anthropic/claude-sonnet-5"
|
|
74
|
+
},
|
|
75
|
+
"gemini-3.7-flash": {
|
|
76
|
+
"label": "Gemini 3.7 Flash",
|
|
77
|
+
"modelId": "google/gemini-3.7-flash"
|
|
78
|
+
},
|
|
79
|
+
"deepseek-v4-pro": {
|
|
80
|
+
"label": "DeepSeek V4 Pro",
|
|
81
|
+
"modelId": "deepseek/deepseek-v4-pro"
|
|
82
|
+
},
|
|
83
|
+
"qwen3.8-max": {
|
|
84
|
+
"label": "Qwen3.8 Max",
|
|
85
|
+
"modelId": "qwen/qwen3.8-max"
|
|
86
|
+
},
|
|
87
|
+
"glm-5.3": {
|
|
88
|
+
"label": "GLM-5.3",
|
|
89
|
+
"modelId": "z-ai/glm-5.3"
|
|
90
|
+
},
|
|
91
|
+
"kimi-k3": {
|
|
92
|
+
"label": "Kimi K3",
|
|
93
|
+
"modelId": "moonshotai/kimi-k3"
|
|
94
|
+
},
|
|
95
|
+
"grok-4.6": {
|
|
96
|
+
"label": "Grok 4.6",
|
|
97
|
+
"modelId": "x-ai/grok-4.6"
|
|
46
98
|
}
|
|
47
99
|
}
|
|
48
100
|
},
|
|
@@ -86,6 +138,14 @@
|
|
|
86
138
|
"qwen": {
|
|
87
139
|
"label": "Qwen 3 8B",
|
|
88
140
|
"modelId": "qwen3:8b"
|
|
141
|
+
},
|
|
142
|
+
"deepseek": {
|
|
143
|
+
"label": "DeepSeek R1 8B",
|
|
144
|
+
"modelId": "deepseek-r1:8b"
|
|
145
|
+
},
|
|
146
|
+
"gpt-oss": {
|
|
147
|
+
"label": "GPT-OSS 20B",
|
|
148
|
+
"modelId": "gpt-oss:20b"
|
|
89
149
|
}
|
|
90
150
|
}
|
|
91
151
|
}
|
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 =
|
|
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 =
|
|
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 =
|
|
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('
|
|
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
|
|
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
|
|
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
|
};
|