@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 +39 -21
- package/README.md +21 -0
- package/README.zh-CN.md +21 -0
- package/docs/distribution.md +1 -1
- package/docs/model-capabilities-license.md +32 -0
- package/docs/provider-compatibility.md +31 -31
- package/package.json +14 -4
- package/src/local-analysis.js +50 -6
- package/src/model-capabilities.json +2009 -0
- package/src/model-client.js +166 -109
- package/src/provider-response.js +21 -5
- package/src/providers.js +35 -29
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.
|
|
277
|
-
[2.6.
|
|
278
|
-
[2.6.
|
|
279
|
-
[2.6.
|
|
280
|
-
[2.6.
|
|
281
|
-
[2.6.
|
|
282
|
-
[2.6.
|
|
283
|
-
[2.6.
|
|
284
|
-
[2.
|
|
285
|
-
[2.
|
|
286
|
-
[2.
|
|
287
|
-
[2.
|
|
288
|
-
[2.
|
|
289
|
-
[2.
|
|
290
|
-
[2.2.
|
|
291
|
-
[2.2.
|
|
292
|
-
[2.1
|
|
293
|
-
[2.0
|
|
294
|
-
[2.
|
|
295
|
-
[
|
|
296
|
-
[
|
|
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、模型、路径和耗时均为截图时的实际环境。
|
package/docs/distribution.md
CHANGED
|
@@ -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.
|
|
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 **
|
|
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
|
|
5
|
+
AICommit 使用官方 **OpenAI JavaScript SDK**(`openai`,固定为 6.40.0)发送 Chat Completions 请求并解码 SSE。要求 Node.js **>=22.19.0**。
|
|
6
6
|
|
|
7
|
-
The
|
|
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`
|
|
9
|
+
现有 Provider/Model 配置格式保持不变。`providers.js` 负责模型能力校验与厂商参数映射;`model-client.js` 通过应用已有的受限请求层调用 SDK;`provider-response.js` 使用 `eventsource-parser` 解析 SSE 并统一厂商响应字段;`api.js` 保留提交提示词、规则校验与恢复。
|
|
10
10
|
|
|
11
|
-
| Provider / adapter |
|
|
12
|
-
| ------------------------- |
|
|
13
|
-
| OpenAI / `openai` | Chat Completions
|
|
14
|
-
| OpenRouter / `openrouter` | Chat Completions; `X-Title: aicommit`
|
|
15
|
-
| DeepSeek / `deepseek` | Chat Completions
|
|
16
|
-
| MiniMax / `minimax` | Chat Completions
|
|
17
|
-
| Kimi Code / `custom` |
|
|
18
|
-
| Ollama / `ollama` | Native JSON bridge for `/api/chat` and `/api/generate`;
|
|
19
|
-
| Custom / `custom` |
|
|
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
|
|
24
|
-
- `
|
|
25
|
-
- `reasoning.mode: auto` preserves server defaults and explicit `extraBody`. `on` / `off`
|
|
26
|
-
-
|
|
27
|
-
- Native Ollama remains non-streaming. `/api/generate` receives `system` and `prompt
|
|
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`
|
|
32
|
-
- Pi
|
|
33
|
-
- `
|
|
34
|
-
-
|
|
35
|
-
- Ollama
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
45
|
+
业务仍获得 `content`、可选 `reasoning`、归一 usage、结束原因、能力、尝试次数与耗时。缓存输入 token 只计入一次,推理 token 已包含在输出中。旧字段 `piMessage` 保留 assistant 内容块和 token 用量兼容结构,由本地组装,不再提供目录价格估算或 Pi 重放元数据。`raw` 保留完整 JSON 原响应,或重建 SSE 的 Chat Completions 结果。
|
|
46
46
|
|
|
47
|
-
重试由 AICommit 负责,
|
|
47
|
+
重试由 AICommit 负责,SDK 自动重试已关闭。仅 429、部分 5xx,以及**收到成功响应前**的网络失败可重试;已接受请求后的响应中断、格式错误、鉴权、参数及安全错误不会自动重放。超过上限的 `Retry-After` 会直接报错。
|
|
48
48
|
|
|
49
|
-
SSE 必须带 `finish_reason
|
|
49
|
+
SSE 必须带 `finish_reason`,仅有 EOF 或 `[DONE]` 时会拒绝结果。输出上限别名归一为 `length`,保留补全恢复流程。推理文本按到达顺序合并;同一事件中的重复表示只展示一次,后续事件中重复出现的文本保留。加密元数据不会作为推理文本输出。
|
|
50
50
|
|
|
51
|
-
Use `aicommit doctor -p provider-name -m model-name` to verify a configured connection.
|
|
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`
|
|
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.
|
|
4
|
-
"description": "
|
|
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",
|
package/src/local-analysis.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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('@@'))
|
|
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
|
-
|
|
195
|
-
|
|
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);
|