@hifullmoon/aicommit 2.6.6 → 2.6.8

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/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ This file lists notable user-facing changes. Internal refactors, test-only chang
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.6.8] - 2026-10-07
8
+
9
+ ### Fixed
10
+
11
+ - Provider requests now ignore SDK environment headers and logging settings, preventing unrelated credentials from reaching configured endpoints and keeping JSON output free of SDK logs.
12
+
13
+ ## [2.6.7] - 2026-10-05
14
+
15
+ ### Changed
16
+
17
+ - Large-change local analysis now selects bounded excerpts across change hunks, prioritizing behavior changes over imports and comments.
18
+
19
+ ### Fixed
20
+
21
+ - Local change summaries now fit the complete commit request input budget, including system policy, repository context, and JSON envelopes.
22
+
7
23
  ## [2.6.6] - 2026-09-21
8
24
 
9
25
  ### Fixed
@@ -273,24 +289,26 @@ This file lists notable user-facing changes. Internal refactors, test-only chang
273
289
  - Added file-level split planning and execution with Git-state concurrency checks.
274
290
  - Added provider presets and user/project configuration boundaries.
275
291
 
276
- [Unreleased]: https://github.com/hi-fullmoon/AICommit/compare/v2.6.6...HEAD
277
- [2.6.6]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.6.6
278
- [2.6.5]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.6.5
279
- [2.6.4]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.6.4
280
- [2.6.3]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.6.3
281
- [2.6.2]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.6.2
282
- [2.6.1]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.6.1
283
- [2.6.0]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.6.0
284
- [2.5.0]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.5.0
285
- [2.4.1]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.4.1
286
- [2.4.0]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.4.0
287
- [2.3.0]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.3.0
288
- [2.2.3]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.2.3
289
- [2.2.2]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.2.2
290
- [2.2.1]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.2.1
291
- [2.2.0]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.2.0
292
- [2.1.0]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.1.0
293
- [2.0.1]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.0.1
294
- [2.0.0]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.0.0
295
- [1.5.1]: https://github.com/hi-fullmoon/AICommit/releases/tag/v1.5.1
296
- [1.5.0]: https://github.com/hi-fullmoon/AICommit/releases/tag/v1.5.0
292
+ [Unreleased]: https://github.com/hi-fullmoon/AICommit/compare/v2.6.8...HEAD
293
+ [2.6.8]: https://github.com/hi-fullmoon/AICommit/tree/v2.6.8
294
+ [2.6.7]: https://github.com/hi-fullmoon/AICommit/tree/v2.6.7
295
+ [2.6.6]: https://github.com/hi-fullmoon/AICommit/tree/v2.6.6
296
+ [2.6.5]: https://github.com/hi-fullmoon/AICommit/tree/v2.6.5
297
+ [2.6.4]: https://github.com/hi-fullmoon/AICommit/tree/v2.6.4
298
+ [2.6.3]: https://github.com/hi-fullmoon/AICommit/tree/v2.6.3
299
+ [2.6.2]: https://github.com/hi-fullmoon/AICommit/tree/v2.6.2
300
+ [2.6.1]: https://github.com/hi-fullmoon/AICommit/tree/v2.6.1
301
+ [2.6.0]: https://github.com/hi-fullmoon/AICommit/tree/v2.6.0
302
+ [2.5.0]: https://github.com/hi-fullmoon/AICommit/tree/v2.5.0
303
+ [2.4.1]: https://github.com/hi-fullmoon/AICommit/tree/v2.4.1
304
+ [2.4.0]: https://github.com/hi-fullmoon/AICommit/tree/v2.4.0
305
+ [2.3.0]: https://github.com/hi-fullmoon/AICommit/tree/v2.3.0
306
+ [2.2.3]: https://github.com/hi-fullmoon/AICommit/tree/v2.2.3
307
+ [2.2.2]: https://github.com/hi-fullmoon/AICommit/tree/v2.2.2
308
+ [2.2.1]: https://github.com/hi-fullmoon/AICommit/tree/v2.2.1
309
+ [2.2.0]: https://github.com/hi-fullmoon/AICommit/tree/v2.2.0
310
+ [2.1.0]: https://github.com/hi-fullmoon/AICommit/tree/v2.1.0
311
+ [2.0.1]: https://github.com/hi-fullmoon/AICommit/tree/v2.0.1
312
+ [2.0.0]: https://github.com/hi-fullmoon/AICommit/tree/v2.0.0
313
+ [1.5.1]: https://github.com/hi-fullmoon/AICommit/tree/v1.5.1
314
+ [1.5.0]: https://github.com/hi-fullmoon/AICommit/tree/v1.5.0
package/README.md CHANGED
@@ -4,6 +4,27 @@
4
4
 
5
5
  AI-powered git commit message generator: reads your diff, asks an AI model for a conventional commit message, and commits after your confirmation.
6
6
 
7
+ [npm package](https://www.npmjs.com/package/@hifullmoon/aicommit) · [Releases](https://github.com/hi-fullmoon/AICommit/releases) · [Privacy model](docs/privacy.md)
8
+
9
+ - Use OpenAI, DeepSeek, OpenRouter, MiniMax, Kimi Code, Ollama, or a custom OpenAI-compatible endpoint.
10
+ - Review, edit, or cancel before committing; preview with `--dry-run`.
11
+ - Generate English or Chinese messages and apply shared [team commit policies](docs/team-policy.md).
12
+
13
+ AICommit runs locally without usage telemetry. Cloud providers receive the selected diff and context; choose a local Ollama endpoint to use a local model. See the [privacy model](docs/privacy.md) for details.
14
+
15
+ ## Quick start
16
+
17
+ Requires Node.js >= 22.19.0 and Git. Run the last two commands inside your Git repository after configuring a provider:
18
+
19
+ ```bash
20
+ npm install --global @hifullmoon/aicommit
21
+ aicommit setup
22
+ aicommit --dry-run
23
+ aicommit
24
+ ```
25
+
26
+ Cloud providers require your own API credentials. For Ollama, start your local server and install a model before setup.
27
+
7
28
  ## Usage preview
8
29
 
9
30
  These screenshots were captured from real interactive terminal sessions in this repository. Provider, model, paths, and timings reflect the environment at capture time.
package/README.zh-CN.md CHANGED
@@ -4,6 +4,27 @@
4
4
 
5
5
  AI 驱动的 Git 提交信息生成器:读取 diff,请 AI 模型生成符合 Conventional Commits 规范的提交信息,并在你确认后执行提交。
6
6
 
7
+ [npm 包](https://www.npmjs.com/package/@hifullmoon/aicommit) · [版本发布](https://github.com/hi-fullmoon/AICommit/releases) · [隐私模型](docs/privacy.md)
8
+
9
+ - 支持 OpenAI、DeepSeek、OpenRouter、MiniMax、Kimi Code、Ollama 和自定义 OpenAI 兼容端点。
10
+ - 提交前可以检查、编辑或取消;通过 `--dry-run` 预览。
11
+ - 支持中文或英文提交信息,以及共享的[团队提交策略](docs/team-policy.md)。
12
+
13
+ AICommit 在本地运行,不记录使用指标。使用云端 Provider 时,选定的 diff 和上下文会发送给该 Provider;选择本地 Ollama 端点可以使用本地模型。详见[隐私模型](docs/privacy.md)。
14
+
15
+ ## 快速开始
16
+
17
+ 需要 Node.js >= 22.19.0 和 Git。配置 Provider 后,在你的 Git 仓库内执行最后两条命令:
18
+
19
+ ```bash
20
+ npm install --global @hifullmoon/aicommit
21
+ aicommit setup
22
+ aicommit --dry-run
23
+ aicommit
24
+ ```
25
+
26
+ 云端 Provider 需要你自己的 API 凭据。使用 Ollama 时,请先启动本地服务并安装模型,再运行配置向导。
27
+
7
28
  ## 使用预览
8
29
 
9
30
  以下截图来自本仓库中的真实交互式终端会话。Provider、模型、路径和耗时均为截图时的实际环境。
@@ -33,7 +33,7 @@ The release workflow uses npm Trusted Publishing without a long-lived `NPM_TOKEN
33
33
  ```bash
34
34
  workdir=$(mktemp -d)
35
35
  cd "$workdir"
36
- npm install --package-lock-only @hifullmoon/aicommit@2.6.6
36
+ npm install --package-lock-only @hifullmoon/aicommit@2.6.8
37
37
  npm audit signatures
38
38
  ```
39
39
 
@@ -0,0 +1,32 @@
1
+ # Model capability metadata
2
+
3
+ `src/model-capabilities.json` contains the reasoning capabilities and context/output limits of 403 OpenAI,
4
+ DeepSeek, and OpenRouter model IDs from `@earendil-works/pi-ai` version 0.85.0.
5
+ Models sharing the same capabilities share one profile. This is a static metadata
6
+ snapshot, not a runtime dependency or a model availability guarantee. When updating
7
+ it, verify supported efforts and disabled reasoning behavior against provider
8
+ documentation; unknown model IDs continue to use the existing fallback.
9
+
10
+ Source: https://github.com/earendil-works/pi/tree/main/packages/ai
11
+
12
+ MIT License
13
+
14
+ Copyright (c) 2025 Mario Zechner
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ of this software and associated documentation files (the "Software"), to deal
18
+ in the Software without restriction, including without limitation the rights
19
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ copies of the Software, and to permit persons to whom the Software is
21
+ furnished to do so, subject to the following conditions:
22
+
23
+ The above copyright notice and this permission notice shall be included in all
24
+ copies or substantial portions of the Software.
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
+ SOFTWARE.
@@ -1,53 +1,53 @@
1
1
  # Provider compatibility / Provider 兼容表
2
2
 
3
- AICommit uses **Pi AI** (`@earendil-works/pi-ai`, pinned to 0.85.0) for model request construction, message conversion, SSE decoding, thinking events, and normalized results. Node.js **>=22.19.0** is required. See the [Pi AI documentation](https://github.com/earendil-works/pi/tree/main/packages/ai).
3
+ AICommit uses the official **OpenAI JavaScript SDK** (`openai`, pinned to 6.40.0) for Chat Completions and SSE decoding. Node.js **>=22.19.0** is required. See the [OpenAI SDK documentation](https://developers.openai.com/api/reference/typescript).
4
4
 
5
- AICommit 使用 **Pi AI**(`@earendil-works/pi-ai`,固定为 0.85.0)构造模型请求、转换消息、解析 SSE,并读取统一的推理事件和结果。要求 Node.js **>=22.19.0**。
5
+ AICommit 使用官方 **OpenAI JavaScript SDK**(`openai`,固定为 6.40.0)发送 Chat Completions 请求并解码 SSE。要求 Node.js **>=22.19.0**。
6
6
 
7
- The runtime keeps the existing Provider/Model config format. `providers.js` selects Pi model metadata and applies configuration overrides; `model-client.js` calls Pi's `openai-completions` implementation. `provider-response.js` normalizes legacy response fields before Pi; its SSE framing uses the lightweight [eventsource-parser](https://github.com/rexxars/eventsource-parser) dependency. `api.js` retains commit prompts, policy validation, and recovery. Presets remain setup data, not executable adapters.
7
+ The existing Provider/Model configuration format is preserved. `providers.js` applies model capability checks and vendor parameter mappings; `model-client.js` calls the SDK through the application's restricted transport. `provider-response.js` normalizes vendor fields using [eventsource-parser](https://github.com/rexxars/eventsource-parser) for SSE framing. `api.js` retains commit prompts, policy validation, and recovery.
8
8
 
9
- 现有 Provider/Model 配置格式保持不变。`providers.js` 选择 Pi 模型元数据并应用配置覆盖;`model-client.js` 调用 Pi 的 `openai-completions` 实现;`provider-response.js` 在进入 Pi 前统一旧版响应字段,SSE 分帧使用轻量依赖 `eventsource-parser`;`api.js` 保留提交提示词、规则校验与恢复。预设仍是 setup 数据,不是可执行适配器。
9
+ 现有 Provider/Model 配置格式保持不变。`providers.js` 负责模型能力校验与厂商参数映射;`model-client.js` 通过应用已有的受限请求层调用 SDK;`provider-response.js` 使用 `eventsource-parser` 解析 SSE 并统一厂商响应字段;`api.js` 保留提交提示词、规则校验与恢复。
10
10
 
11
- | Provider / adapter | Pi integration / Pi 接入 | Reasoning / 推理 | Token budget / 输出预算 |
12
- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
13
- | OpenAI / `openai` | Chat Completions; bundled model metadata / Chat Completions 与内置模型元数据 | Pi thinking-level map; unsupported known efforts rejected locally / 使用 Pi 强度映射,已知不支持的强度本地拒绝 | Reasoning: `max_completion_tokens`; otherwise `max_tokens` |
14
- | OpenRouter / `openrouter` | Chat Completions; `X-Title: aicommit` | Pi `reasoning.effort` and catalog capability checks / Pi 参数映射与模型能力检查 | `max_tokens` |
15
- | DeepSeek / `deepseek` | Chat Completions; bundled model metadata / 内置模型元数据 | Pi `thinking.type` and effort mapping; enabled thinking omits temperature / Pi 映射推理参数,开启时省略 temperature | `max_tokens` |
16
- | MiniMax / `minimax` | Chat Completions / 兼容接口 | Small compatibility override for `reasoning_split` and thinking switches / 保留少量开关兼容逻辑 | `max_tokens` |
17
- | Kimi Code / `custom` | Existing OpenAI-compatible endpoint and `kimi-for-coding` preset / 保留现有兼容端点与预设 | Server defaults; configurable body switches / 服务端默认值或自定义请求体 | `max_tokens` |
18
- | Ollama / `ollama` | Native JSON bridge for `/api/chat` and `/api/generate`; compatible `/v1/chat/completions` uses Pi directly / 原生端点使用 JSON 桥接,兼容端点直接使用 Pi | Native `think` switch / 原生开关 | Native: `options.num_predict`; compatible: `max_tokens` |
19
- | Custom / `custom` | Arbitrary OpenAI-compatible model IDs and full endpoint URLs / 任意兼容模型 ID 与完整端点 URL | `enabledBody` / `disabledBody`; no inferred vendor fields / 不自动注入厂商开关 | `max_tokens` |
11
+ | Provider / adapter | Protocol / 协议 | Reasoning / 推理 | Token budget / 输出预算 |
12
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
13
+ | OpenAI / `openai` | Chat Completions | Local effort mapping and capability checks / 本地强度映射与能力校验 | Reasoning: `max_completion_tokens`; otherwise `max_tokens` |
14
+ | OpenRouter / `openrouter` | Chat Completions; `X-Title: aicommit` | `reasoning.effort` and model capability checks / 强度映射与模型能力校验 | `max_tokens` |
15
+ | DeepSeek / `deepseek` | Chat Completions | `thinking.type` and effort mapping; thinking omits temperature / 开关与强度映射,开启推理时省略 temperature | `max_tokens` |
16
+ | MiniMax / `minimax` | Chat Completions | `reasoning_split` and thinking switches / 推理分离与开关 | `max_tokens` |
17
+ | Kimi Code / `custom` | OpenAI-compatible endpoint and `kimi-for-coding` preset / 兼容端点与现有预设 | Server defaults or configured body switches / 服务端默认值或配置的请求体开关 | `max_tokens` |
18
+ | Ollama / `ollama` | Native JSON bridge for `/api/chat` and `/api/generate`; SDK for compatible endpoints / 原生端点使用 JSON 桥接,兼容端点使用 SDK | Native `think` switch / 原生开关 | Native: `options.num_predict`; compatible: `max_tokens` |
19
+ | Custom / `custom` | Full OpenAI-compatible endpoint URLs / 完整兼容端点 URL | `enabledBody` / `disabledBody` | `max_tokens` |
20
20
 
21
21
  ## Configuration and model metadata / 配置与模型元数据
22
22
 
23
- - `apiUrl` is still the **complete endpoint**, not a base URL. Proxy paths and query parameters are preserved. Requests use only the resolved AICommit credential. Pi environment-key discovery and OAuth are not invoked, and redirects are rejected.
24
- - `modelId` need not exist in Pi's catalog. Known OpenAI, DeepSeek, and OpenRouter models use the bundled metadata; unknown IDs retain a compatible fallback. No online model discovery runs during setup or generation.
25
- - `reasoning.mode: auto` preserves server defaults and explicit `extraBody`. `on` / `off` applies the selected mode after extras. Setup filters known supported effort levels. DeepSeek's legacy unsupported effort values are normalized by Pi: for the pinned V4 Flash catalog, `medium` becomes `high`, and `xhigh` becomes `max`.
26
- - Pi requests SSE by default, even when the CLI does not display reasoning. Complete JSON responses are bridged into Pi events. Set `extraBody: { "stream": false }` for endpoints that reject streaming requests; streaming-only options are removed automatically.
27
- - Native Ollama remains non-streaming. `/api/generate` receives `system` and `prompt`, while `/api/chat` receives `messages`; existing `options` are retained.
23
+ - `apiUrl` is the **complete endpoint**. Proxy paths and query parameters are preserved. Only the resolved AICommit credential is sent; SDK environment credential discovery is bypassed and redirects are rejected.
24
+ - `src/model-capabilities.json` preserves the reasoning capabilities, assistant message compatibility requirements, and context/output limits of 403 model IDs from the previous pinned Pi AI 0.85.0 catalog, sharing 176 capability profiles. It is a static metadata snapshot with [source and license information](model-capabilities-license.md), not a runtime Pi dependency. Unknown IDs retain the existing fallback. No online model discovery runs during setup or generation.
25
+ - `reasoning.mode: auto` preserves server defaults and explicit `extraBody`. Explicit `on` / `off` takes precedence over extras. Setup filters supported efforts. DeepSeek V4 Flash maps legacy `medium` to `high` and `xhigh` to `max`.
26
+ - SSE is requested by default, including when reasoning is not displayed. Complete JSON responses are normalized into SDK-readable events. `extraBody: { "stream": false }` disables streaming for incompatible endpoints and removes streaming-only options.
27
+ - Native Ollama remains non-streaming. `/api/generate` receives `system` and `prompt`; `/api/chat` receives `messages`. Existing `options` are retained.
28
28
 
29
29
  对应行为:
30
30
 
31
- - `apiUrl` 仍填写**完整接口地址**,代理路径与查询参数会保留。鉴权只使用 AICommit 已解析的凭据,不调用 Pi 的环境变量凭据发现或 OAuth,也不跟随重定向。
32
- - Pi 目录中没有的 `modelId` 也可以配置;已知 OpenAI、DeepSeek、OpenRouter 模型使用随依赖发布的元数据,未知模型走兼容路径。setup 与生成过程不在线拉取模型目录。
33
- - `reasoning.mode: auto` 保留服务端默认值及显式 `extraBody`;`on` / `off` 在 extras 之后应用。setup 根据已知能力过滤强度。DeepSeek 的旧配置由 Pi 归一:当前 V4 Flash 目录中 `medium` 映射为 `high`,`xhigh` 映射为 `max`。
34
- - Pi 默认请求 SSE,包括终端不展示推理的场景;完整 JSON 响应通过桥接交给 Pi。若服务拒绝流式请求,可配置 `extraBody: { "stream": false }`,流式专用参数会自动移除。
35
- - Ollama 原生端点继续使用非流式响应,保留 `options`;`/api/generate` 使用 `system` / `prompt`,`/api/chat` 使用 `messages`。
31
+ - `apiUrl` 仍填写**完整接口地址**,代理路径与查询参数会保留。仅发送 AICommit 已解析的凭据,不使用 SDK 自动读取的环境变量凭据,也不跟随重定向。
32
+ - `src/model-capabilities.json` 保存原 Pi AI 0.85.0 目录中 403 个模型 ID 的推理能力、assistant 消息兼容要求、上下文与输出上限,共享 176 组能力配置。这是带来源及许可说明的静态元数据,运行时不再依赖 Pi。未知模型继续走兼容路径,setup 与生成过程不在线拉取目录。
33
+ - `auto` 保留服务端默认值及显式 `extraBody`;`on` / `off` 在 extras 之后应用。setup 根据能力过滤强度。DeepSeek V4 Flash 的旧配置 `medium` 映射为 `high`,`xhigh` 映射为 `max`。
34
+ - 默认请求 SSE;完整 JSON 响应会统一为 SDK 可读取的事件。服务拒绝流式请求时,可配置 `extraBody: { "stream": false }`,流式专用参数会自动移除。
35
+ - Ollama 原生端点保留非流式响应和 `options`;`/api/generate` 使用 `system` / `prompt`,`/api/chat` 使用 `messages`。
36
36
 
37
37
  ## Result and retry contract / 结果与重试契约
38
38
 
39
- Callers receive `content`, optional `reasoning`, normalized usage, finish reason, capabilities, attempts, and latency. Cached input tokens are included once in `inputTokens`; reasoning tokens are already part of output usage. `piMessage` exposes Pi's normalized assistant result. `raw` retains the actual complete JSON response, or a reconstructed Chat Completions result for SSE, preserving `callAPI` compatibility.
39
+ Callers receive `content`, optional `reasoning`, normalized usage, finish reason, capabilities, attempts, and latency. Cached input tokens are included once in `inputTokens`; reasoning tokens are already part of output usage. The legacy `piMessage` field retains assistant content blocks and token usage for compatibility; it is assembled locally and no longer supplies catalog price estimates or Pi replay metadata. `raw` retains complete JSON responses or reconstructed Chat Completions results for SSE.
40
40
 
41
- Retries remain owned by AICommit; Pi and the underlying SDK's automatic retries are disabled. Only 429, selected 5xx, and network failures **before an accepted response** can retry. Accepted-body interruptions, malformed responses, authentication, invalid parameters, and safety failures are never automatically replayed. Oversized `Retry-After` fails instead of retrying early.
41
+ Retries are owned by AICommit; SDK automatic retries are disabled. Only 429, selected 5xx, and network failures **before an accepted response** may retry. Accepted-body interruptions, malformed responses, authentication, invalid parameters, and safety failures are never automatically replayed. Oversized `Retry-After` fails instead of retrying early.
42
42
 
43
- SSE must contain a `finish_reason`. A clean EOF or `[DONE]` alone is rejected, and partial content is not returned as a successful generation. Token-limit aliases (`max_tokens`, `max_output_tokens`, `token_limit`) are normalized to `length` before Pi so recovery can run. Textual `reasoning_details`, including legacy shapes, are combined with ordinary reasoning deltas in arrival order. Duplicate representations within one event are emitted once; repeated text in later events is retained. Encrypted metadata remains opaque.
43
+ SSE must contain a `finish_reason`. A clean EOF or `[DONE]` alone is rejected. Token-limit aliases (`max_tokens`, `max_output_tokens`, `token_limit`) normalize to `length` so recovery can run. Textual `reasoning_details`, including legacy shapes, are combined with ordinary reasoning deltas in arrival order. Duplicate representations within one event are emitted once; repeated text in later events is retained. Encrypted metadata is not displayed.
44
44
 
45
- 业务仍获得 `content`、可选 `reasoning`、归一 usage、结束原因、能力、尝试次数与耗时。缓存输入 token 只计入一次,推理 token 已包含在输出中。`piMessage` 提供 Pi 的统一 assistant 结果;完整 JSON 的 `raw` 保留原响应,SSE 的 `raw` 则重建兼容 Chat Completions 结构。
45
+ 业务仍获得 `content`、可选 `reasoning`、归一 usage、结束原因、能力、尝试次数与耗时。缓存输入 token 只计入一次,推理 token 已包含在输出中。旧字段 `piMessage` 保留 assistant 内容块和 token 用量兼容结构,由本地组装,不再提供目录价格估算或 Pi 重放元数据。`raw` 保留完整 JSON 原响应,或重建 SSE 的 Chat Completions 结果。
46
46
 
47
- 重试由 AICommit 负责,Pi 与底层 SDK 的自动重试均已关闭。只有 429、部分 5xx,以及**收到成功响应前**的网络失败可重试;已接受请求后的响应中断、格式错误、鉴权、参数及安全错误不会自动重放。超过上限的 `Retry-After` 会直接报错,不提前重试。
47
+ 重试由 AICommit 负责,SDK 自动重试已关闭。仅 429、部分 5xx,以及**收到成功响应前**的网络失败可重试;已接受请求后的响应中断、格式错误、鉴权、参数及安全错误不会自动重放。超过上限的 `Retry-After` 会直接报错。
48
48
 
49
- SSE 必须带 `finish_reason`。只有 EOF 或 `[DONE]` 时会拒绝结果,不将半截内容当作成功生成。输出上限别名(`max_tokens`、`max_output_tokens`、`token_limit`)在进入 Pi 前归一为 `length`,保留补全恢复流程。`reasoning_details`(含旧格式)中的文本与普通推理分片按到达顺序合并;同一事件中的重复表示只展示一次,后续事件中重复出现的文本则保留。加密元数据不会作为推理文本输出。
49
+ SSE 必须带 `finish_reason`,仅有 EOF 或 `[DONE]` 时会拒绝结果。输出上限别名归一为 `length`,保留补全恢复流程。推理文本按到达顺序合并;同一事件中的重复表示只展示一次,后续事件中重复出现的文本保留。加密元数据不会作为推理文本输出。
50
50
 
51
- Use `aicommit doctor -p provider-name -m model-name` to verify a configured connection. This integration covers the existing six adapter types; installing Pi does **not** automatically expose every Pi provider, OAuth flow, or native Anthropic/Gemini/Responses endpoint. Those protocols require explicit routing and configuration support.
51
+ Use `aicommit doctor -p provider-name -m model-name` to verify a configured connection. The six existing adapter types are supported; native Anthropic/Gemini/Responses endpoints and OAuth require explicit routing and configuration support.
52
52
 
53
- 使用 `aicommit doctor -p provider-name -m model-name` 检查配置的连接。本次接入覆盖现有六种适配类型;安装 Pi **不会自动开放**其全部供应商、OAuth 或 Anthropic/Gemini/Responses 原生端点,这些协议需要显式扩展路由与配置。
53
+ 使用 `aicommit doctor -p provider-name -m model-name` 检查配置的连接。现有六种适配类型保持支持;Anthropic、Gemini、Responses 原生端点及 OAuth 需要额外的路由与配置支持。
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hifullmoon/aicommit",
3
- "version": "2.6.6",
4
- "description": "Safe, local-first AI commit message generator for Git workflows",
3
+ "version": "2.6.8",
4
+ "description": "AI Git commit message CLI for Conventional Commits with OpenAI, DeepSeek, OpenRouter and local Ollama models",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "aicommit": "./bin/aicommit.js"
@@ -11,6 +11,7 @@
11
11
  "CHANGELOG.md",
12
12
  "LICENSE",
13
13
  "README.md",
14
+ "README.zh-CN.md",
14
15
  "SECURITY.md",
15
16
  "bin/",
16
17
  "docs/*.md",
@@ -48,7 +49,6 @@
48
49
  "lines": 70
49
50
  },
50
51
  "dependencies": {
51
- "@earendil-works/pi-ai": "0.85.0",
52
52
  "@inquirer/checkbox": "^4.3.2",
53
53
  "@inquirer/confirm": "^5.1.0",
54
54
  "@inquirer/core": "^10.3.2",
@@ -60,6 +60,7 @@
60
60
  "chalk": "^5.4.0",
61
61
  "eventsource-parser": "4.1.0",
62
62
  "ora": "^8.2.0",
63
+ "openai": "6.40.0",
63
64
  "wrap-ansi": "^9.0.2"
64
65
  },
65
66
  "keywords": [
@@ -67,11 +68,20 @@
67
68
  "commit",
68
69
  "ai",
69
70
  "cli",
71
+ "aicommit",
72
+ "ai-commit",
73
+ "git-commit",
74
+ "commit-message",
75
+ "commit-message-generator",
70
76
  "conventional-commits",
77
+ "developer-tools",
71
78
  "openai",
72
79
  "deepseek",
73
80
  "openrouter",
74
- "ollama"
81
+ "ollama",
82
+ "minimax",
83
+ "kimi",
84
+ "local-llm"
75
85
  ],
76
86
  "repository": {
77
87
  "type": "git",
@@ -1,6 +1,8 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { isLockFile, matchStripPattern } from './git.js';
3
3
  import { ERROR_CATEGORIES, fail } from './errors.js';
4
+ import { buildCommitMessages } from './api.js';
5
+ import { estimateTokens } from './analysis-budget.js';
4
6
 
5
7
  // These are evidence-selection rules, not exclusions from Git or secret scans.
6
8
  export function isGeneratedFile(path) {
@@ -117,6 +119,16 @@ export function localOverview(config, facts) {
117
119
  samples: [],
118
120
  };
119
121
  const cap = localInputBytes(config);
122
+ const fits = () => {
123
+ const text = JSON.stringify(data);
124
+ return (
125
+ Buffer.byteLength(text) <= cap &&
126
+ estimateTokens(JSON.stringify(buildCommitMessages(config, text).messages)) <=
127
+ (config.analysisBudget?.limits.chunkInputTokens ||
128
+ config.largeChange?.chunkInputTokens ||
129
+ 12000)
130
+ );
131
+ };
120
132
  let sampledFiles = 0;
121
133
  const seen = new Set();
122
134
  const ordered = ranked(facts);
@@ -145,17 +157,18 @@ export function localOverview(config, facts) {
145
157
  if (data.samples.length >= 16) break;
146
158
  const sample = summaryOf(fact);
147
159
  data.samples.push(sample);
148
- if (Buffer.byteLength(JSON.stringify(data)) > cap) {
160
+ data.omittedGroups--;
161
+ if (!fits()) {
149
162
  data.samples.pop();
163
+ data.omittedGroups++;
150
164
  continue;
151
165
  }
152
166
  if (fact.evidence) sampledFiles++;
153
- data.omittedGroups--;
154
167
  }
155
168
  // A tiny configured budget may not even fit aggregate metadata. Fail before
156
169
  // sending rather than silently slicing JSON or dropping the uncertainty notice.
157
170
  const text = JSON.stringify(data);
158
- if (Buffer.byteLength(text) > cap)
171
+ if (!fits())
159
172
  throw fail(
160
173
  ERROR_CATEGORIES.CONFIG,
161
174
  'Local change inventory does not fit the input budget. Increase largeChange.chunkInputTokens or maxDiffChars.',
@@ -172,30 +185,61 @@ export function analyzeLocally(config, capture, protect, previews) {
172
185
  hash: createHash('sha256'),
173
186
  evidence: '',
174
187
  textual: false,
188
+ excerpts: [],
189
+ excerpt: '',
190
+ score: 0,
175
191
  },
176
192
  ]),
177
193
  );
194
+ const finishExcerpt = (record) => {
195
+ if (!record.excerpt) return;
196
+ record.excerpts.push({ text: record.excerpt, score: record.score });
197
+ record.excerpts.sort((a, b) => b.score - a.score);
198
+ record.excerpts.length = Math.min(record.excerpts.length, 8);
199
+ record.excerpt = '';
200
+ record.score = 0;
201
+ };
178
202
  for (const unit of capture.units(protect, previews)) {
179
203
  if (unit.metadataOnly) continue;
180
204
  const record = records.get(unit.fileId);
181
205
  // Tracked fragments carry Git headers; untracked fragments are plain text.
182
206
  const section = unit.id.match(/S\d+(?=P\d+$)/)?.[0];
183
207
  if (section && section !== record.section) {
208
+ finishExcerpt(record);
184
209
  record.section = section;
185
210
  record.inHunk = false;
186
211
  }
187
212
  for (const line of unit.text.split(/(?<=\n)/)) {
188
213
  if (section) {
189
- if (line.startsWith('@@')) record.inHunk = true;
214
+ if (line.startsWith('@@')) {
215
+ finishExcerpt(record);
216
+ record.inHunk = true;
217
+ }
190
218
  if (!record.inHunk || !/^[+-]/.test(line)) continue;
191
219
  }
192
220
  record.hash.update(line);
193
221
  record.textual = true;
194
- const remaining = 900 - Buffer.byteLength(record.evidence);
195
- if (remaining > 3) record.evidence += clip(line, remaining);
222
+ // Keep bounded candidates across hunks instead of spending the entire
223
+ // evidence allowance on imports at the beginning of a file.
224
+ const remaining = 450 - Buffer.byteLength(record.excerpt);
225
+ if (remaining > 3) {
226
+ record.excerpt += clip(line, remaining);
227
+ record.score = Math.max(
228
+ record.score,
229
+ /^[+-]\s*(?:import\b|export\s+\{|\/\/|\*|$)/.test(line) ? 1 : 4,
230
+ );
231
+ }
232
+ if (Buffer.byteLength(record.excerpt) >= 447) finishExcerpt(record);
196
233
  }
197
234
  }
198
235
  const grouped = new Map();
236
+ for (const record of records.values()) {
237
+ finishExcerpt(record);
238
+ record.evidence = record.excerpts
239
+ .slice(0, 3)
240
+ .map((excerpt) => clip(excerpt.text, 300))
241
+ .join('');
242
+ }
199
243
  for (const { file, hash, evidence, textual } of records.values()) {
200
244
  const kind = category(file, config);
201
245
  const module = moduleOf(file.addPaths?.at(-1) || file.path);