@hifullmoon/aicommit 2.6.4 → 2.6.5
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 +8 -1
- package/docs/distribution.md +1 -1
- package/package.json +1 -1
- package/src/api.js +3 -2
- package/src/change-analysis.js +32 -10
- package/src/split.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ This file lists notable user-facing changes. Internal refactors, test-only chang
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.6.5] - 2026-09-21
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Large split plans now validate commit messages during planning and automatically retry with specific policy errors, including missing or empty subjects. Planning failures are labeled accurately instead of being reported as API failures.
|
|
12
|
+
|
|
7
13
|
## [2.6.4] - 2026-09-19
|
|
8
14
|
|
|
9
15
|
### Fixed
|
|
@@ -261,7 +267,8 @@ This file lists notable user-facing changes. Internal refactors, test-only chang
|
|
|
261
267
|
- Added file-level split planning and execution with Git-state concurrency checks.
|
|
262
268
|
- Added provider presets and user/project configuration boundaries.
|
|
263
269
|
|
|
264
|
-
[Unreleased]: https://github.com/hi-fullmoon/AICommit/compare/v2.6.
|
|
270
|
+
[Unreleased]: https://github.com/hi-fullmoon/AICommit/compare/v2.6.5...HEAD
|
|
271
|
+
[2.6.5]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.6.5
|
|
265
272
|
[2.6.4]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.6.4
|
|
266
273
|
[2.6.3]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.6.3
|
|
267
274
|
[2.6.2]: https://github.com/hi-fullmoon/AICommit/releases/tag/v2.6.2
|
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.5
|
|
37
37
|
npm audit signatures
|
|
38
38
|
```
|
|
39
39
|
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -207,6 +207,7 @@ export async function getResponseText(
|
|
|
207
207
|
: reasoning || '';
|
|
208
208
|
|
|
209
209
|
const partial = text.trim();
|
|
210
|
+
const followUp = typeof followUpPrompt === 'function' ? followUpPrompt() : followUpPrompt;
|
|
210
211
|
const recoveryPrompt =
|
|
211
212
|
truncatedByLimit || invalidResponse
|
|
212
213
|
? `The previous response was ${
|
|
@@ -214,8 +215,8 @@ export async function getResponseText(
|
|
|
214
215
|
}. ` +
|
|
215
216
|
'Reproduce the COMPLETE answer from the beginning; do not continue from the cut-off point. ' +
|
|
216
217
|
'Keep the answer concise.\n\n' +
|
|
217
|
-
|
|
218
|
-
:
|
|
218
|
+
followUp
|
|
219
|
+
: followUp;
|
|
219
220
|
|
|
220
221
|
// With reasoning, its conclusion plus the partial answer is enough to
|
|
221
222
|
// reconstruct the output without paying to send the original diff again.
|
package/src/change-analysis.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createAnalysisBudget, DEFAULT_LARGE_CHANGE, estimateTokens } from './an
|
|
|
4
4
|
import { getResponseText } from './api.js';
|
|
5
5
|
import { encodeUntrustedData } from './trust.js';
|
|
6
6
|
import { ERROR_CATEGORIES, fail } from './errors.js';
|
|
7
|
-
import { normalizeCommitPolicy } from './policy.js';
|
|
7
|
+
import { normalizeCommitPolicy, validateCommitCandidate } from './policy.js';
|
|
8
8
|
import { getProviderAdapter } from './providers.js';
|
|
9
9
|
import { createHash } from 'node:crypto';
|
|
10
10
|
import {
|
|
@@ -315,6 +315,7 @@ function parseAnalysisJson(raw) {
|
|
|
315
315
|
}
|
|
316
316
|
|
|
317
317
|
async function jsonCall(config, instruction, items, validate = null, stream = null) {
|
|
318
|
+
let validationError = '';
|
|
318
319
|
const parseAndValidate = (raw) => {
|
|
319
320
|
const parsed = parseAnalysisJson(raw);
|
|
320
321
|
validate?.(parsed);
|
|
@@ -328,14 +329,17 @@ async function jsonCall(config, instruction, items, validate = null, stream = nu
|
|
|
328
329
|
],
|
|
329
330
|
0,
|
|
330
331
|
Math.min(2048, config.maxTokens || 1024),
|
|
331
|
-
|
|
332
|
-
|
|
332
|
+
() =>
|
|
333
|
+
'Return the complete requested JSON array, preserving every required input ID exactly once. ' +
|
|
334
|
+
`Required IDs: ${JSON.stringify(items.map((item) => item.id))}. ` +
|
|
335
|
+
(validationError ? `Correct these validation errors: ${validationError}` : ''),
|
|
333
336
|
stream,
|
|
334
337
|
(response) => {
|
|
335
338
|
try {
|
|
336
339
|
parseAndValidate(response);
|
|
337
340
|
return true;
|
|
338
|
-
} catch {
|
|
341
|
+
} catch (error) {
|
|
342
|
+
validationError = error.message;
|
|
339
343
|
return false;
|
|
340
344
|
}
|
|
341
345
|
},
|
|
@@ -351,6 +355,28 @@ async function jsonCall(config, instruction, items, validate = null, stream = nu
|
|
|
351
355
|
}
|
|
352
356
|
}
|
|
353
357
|
|
|
358
|
+
export function validatePlanMessages(groups, policy) {
|
|
359
|
+
const errors = [];
|
|
360
|
+
for (const [index, group] of groups.entries()) {
|
|
361
|
+
const subject = typeof group.subject === 'string' ? group.subject.trim() : '';
|
|
362
|
+
if (!subject) {
|
|
363
|
+
errors.push(`Group ${index + 1}: The subject field must be a non-empty commit header.`);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
const body = typeof group.body === 'string' ? group.body.trim() : '';
|
|
367
|
+
const validation = validateCommitCandidate(body ? `${subject}\n\n${body}` : subject, {
|
|
368
|
+
policy,
|
|
369
|
+
});
|
|
370
|
+
if (!validation.valid)
|
|
371
|
+
errors.push(`Group ${index + 1}: ${validation.errors.map((item) => item.message).join(' ')}`);
|
|
372
|
+
}
|
|
373
|
+
if (errors.length)
|
|
374
|
+
throw fail(
|
|
375
|
+
ERROR_CATEGORIES.RESPONSE_FORMAT,
|
|
376
|
+
`Split commit messages violate commitPolicy: ${errors.join(' ')}`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
354
380
|
export function validatePartition(groups, ids, requireSummary = true) {
|
|
355
381
|
const remaining = new Set(ids);
|
|
356
382
|
if (!Array.isArray(groups) || !groups.length)
|
|
@@ -777,15 +803,11 @@ export async function planAnalyzedChanges(config, facts, coverage = null, stream
|
|
|
777
803
|
};
|
|
778
804
|
const groups = await jsonCall(
|
|
779
805
|
config,
|
|
780
|
-
`Each commit message must follow this policy: ${JSON.stringify(policy)}.\nGroup related changes into logical commits, including related implementation and tests across directories. Return [{"ids":[input IDs],"summary":"factual combined change summary","subject":"commit
|
|
806
|
+
`Each commit message must follow this policy: ${JSON.stringify(policy)}.\nThe subject field must contain the complete Conventional Commit header: <type>[optional scope][optional !]: <description>. Respect the policy's language, scope, length, body, and breaking-change rules; include a body when required and omit it when forbidden.\nGroup related changes into logical commits, including related implementation and tests across directories. Return [{"ids":[input IDs],"summary":"factual combined change summary","subject":"complete commit header","body":"commit body if permitted or required"}]. Assign every input ID exactly once. Do not merge unrelated changes just to reduce group count.${finalPlan ? ' The summary field is optional in this final plan.' : ''}${!deep && level === 0 ? ' Inputs are a compact local inventory, not full semantic summaries. One ID may represent multiple files and must remain atomic; use kind, module, status, fileCount, examples, and any representativeExcerpt conservatively.' : ''}`,
|
|
781
807
|
batch,
|
|
782
808
|
(candidate) => {
|
|
783
809
|
validatePartition(candidate, ids, false);
|
|
784
|
-
|
|
785
|
-
throw fail(
|
|
786
|
-
ERROR_CATEGORIES.RESPONSE_FORMAT,
|
|
787
|
-
'Analysis plan is missing a commit subject.',
|
|
788
|
-
);
|
|
810
|
+
validatePlanMessages(candidate, policy);
|
|
789
811
|
},
|
|
790
812
|
batchStream,
|
|
791
813
|
);
|
package/src/split.js
CHANGED
|
@@ -1836,7 +1836,7 @@ export async function splitFlow(
|
|
|
1836
1836
|
reasoning: config.reasoning,
|
|
1837
1837
|
machineOutput,
|
|
1838
1838
|
cancelMessage: 'Split cancelled.',
|
|
1839
|
-
failureMessage: '
|
|
1839
|
+
failureMessage: 'Split planning failed',
|
|
1840
1840
|
task: async (stream) => {
|
|
1841
1841
|
if (large) {
|
|
1842
1842
|
let plan;
|