contribute-now 0.4.0-dev.d24b735 → 0.4.0-dev.d48d9e6
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/dist/index.js +133 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -970,6 +970,68 @@ function withTimeout(promise, ms) {
|
|
|
970
970
|
}
|
|
971
971
|
var COPILOT_TIMEOUT_MS = 30000;
|
|
972
972
|
var COPILOT_LONG_TIMEOUT_MS = 90000;
|
|
973
|
+
var BATCH_CONFIG = {
|
|
974
|
+
LARGE_CHANGESET_THRESHOLD: 15,
|
|
975
|
+
COMPACT_PER_FILE_CHARS: 300,
|
|
976
|
+
MAX_COMPACT_PAYLOAD: 1e4,
|
|
977
|
+
FALLBACK_BATCH_SIZE: 15
|
|
978
|
+
};
|
|
979
|
+
function parseDiffByFile(rawDiff) {
|
|
980
|
+
const sections = new Map;
|
|
981
|
+
const headerPattern = /^diff --git a\/(.+?) b\//gm;
|
|
982
|
+
const positions = [];
|
|
983
|
+
for (let match = headerPattern.exec(rawDiff);match !== null; match = headerPattern.exec(rawDiff)) {
|
|
984
|
+
positions.push({ file: match[1], start: match.index });
|
|
985
|
+
}
|
|
986
|
+
for (let i = 0;i < positions.length; i++) {
|
|
987
|
+
const { file, start } = positions[i];
|
|
988
|
+
const end = i + 1 < positions.length ? positions[i + 1].start : rawDiff.length;
|
|
989
|
+
sections.set(file, rawDiff.slice(start, end));
|
|
990
|
+
}
|
|
991
|
+
return sections;
|
|
992
|
+
}
|
|
993
|
+
function extractDiffStats(diffSection) {
|
|
994
|
+
let added = 0;
|
|
995
|
+
let removed = 0;
|
|
996
|
+
for (const line of diffSection.split(`
|
|
997
|
+
`)) {
|
|
998
|
+
if (line.startsWith("+") && !line.startsWith("+++"))
|
|
999
|
+
added++;
|
|
1000
|
+
if (line.startsWith("-") && !line.startsWith("---"))
|
|
1001
|
+
removed++;
|
|
1002
|
+
}
|
|
1003
|
+
return { added, removed };
|
|
1004
|
+
}
|
|
1005
|
+
function createCompactDiff(files, rawDiff, maxTotalChars = BATCH_CONFIG.MAX_COMPACT_PAYLOAD) {
|
|
1006
|
+
if (files.length === 0)
|
|
1007
|
+
return "";
|
|
1008
|
+
const diffSections = parseDiffByFile(rawDiff);
|
|
1009
|
+
const perFileBudget = Math.min(BATCH_CONFIG.COMPACT_PER_FILE_CHARS, Math.floor(maxTotalChars / files.length));
|
|
1010
|
+
const parts = [];
|
|
1011
|
+
for (const file of files) {
|
|
1012
|
+
const section = diffSections.get(file);
|
|
1013
|
+
if (section) {
|
|
1014
|
+
const stats = extractDiffStats(section);
|
|
1015
|
+
const header = `[${file}] (+${stats.added}/-${stats.removed})`;
|
|
1016
|
+
if (section.length <= perFileBudget) {
|
|
1017
|
+
parts.push(`${header}
|
|
1018
|
+
${section}`);
|
|
1019
|
+
} else {
|
|
1020
|
+
const truncated = section.slice(0, perFileBudget - header.length - 20);
|
|
1021
|
+
parts.push(`${header}
|
|
1022
|
+
${truncated}
|
|
1023
|
+
...(truncated)`);
|
|
1024
|
+
}
|
|
1025
|
+
} else {
|
|
1026
|
+
parts.push(`[${file}] (new/binary file — no diff available)`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
const result = parts.join(`
|
|
1030
|
+
|
|
1031
|
+
`);
|
|
1032
|
+
return result.length > maxTotalChars ? `${result.slice(0, maxTotalChars - 15)}
|
|
1033
|
+
...(truncated)` : result;
|
|
1034
|
+
}
|
|
973
1035
|
async function checkCopilotAvailable() {
|
|
974
1036
|
try {
|
|
975
1037
|
const client = await getManagedClient();
|
|
@@ -1070,16 +1132,18 @@ function extractJson(raw) {
|
|
|
1070
1132
|
}
|
|
1071
1133
|
async function generateCommitMessage(diff, stagedFiles, model, convention = "clean-commit") {
|
|
1072
1134
|
try {
|
|
1135
|
+
const isLarge = stagedFiles.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD;
|
|
1073
1136
|
const multiFileHint = stagedFiles.length > 1 ? `
|
|
1074
1137
|
|
|
1075
1138
|
IMPORTANT: Multiple files are staged. Generate ONE commit message that captures the high-level purpose of ALL changes together. Focus on the overall intent, not individual file changes. Be specific but concise — do not list every file.` : "";
|
|
1139
|
+
const diffContent = isLarge ? createCompactDiff(stagedFiles, diff) : diff.slice(0, 4000);
|
|
1076
1140
|
const userMessage = `Generate a commit message for these staged changes:
|
|
1077
1141
|
|
|
1078
|
-
Files: ${stagedFiles.join(", ")}
|
|
1142
|
+
Files (${stagedFiles.length}): ${stagedFiles.join(", ")}
|
|
1079
1143
|
|
|
1080
1144
|
Diff:
|
|
1081
|
-
${
|
|
1082
|
-
const result = await callCopilot(getCommitSystemPrompt(convention), userMessage, model);
|
|
1145
|
+
${diffContent}${multiFileHint}`;
|
|
1146
|
+
const result = await callCopilot(getCommitSystemPrompt(convention), userMessage, model, isLarge ? COPILOT_LONG_TIMEOUT_MS : COPILOT_TIMEOUT_MS);
|
|
1083
1147
|
return result?.trim() ?? null;
|
|
1084
1148
|
} catch {
|
|
1085
1149
|
return null;
|
|
@@ -1128,16 +1192,23 @@ ${conflictDiff.slice(0, 4000)}`;
|
|
|
1128
1192
|
}
|
|
1129
1193
|
}
|
|
1130
1194
|
async function generateCommitGroups(files, diffs, model, convention = "clean-commit") {
|
|
1195
|
+
const isLarge = files.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD;
|
|
1196
|
+
const diffContent = isLarge ? createCompactDiff(files, diffs) : diffs.slice(0, 6000);
|
|
1197
|
+
const largeHint = isLarge ? `
|
|
1198
|
+
|
|
1199
|
+
NOTE: This is a large changeset (${files.length} files). Compact diffs are provided for every file. Focus on creating well-organized logical groups.` : "";
|
|
1131
1200
|
const userMessage = `Group these changed files into logical atomic commits:
|
|
1132
1201
|
|
|
1133
1202
|
Files:
|
|
1134
1203
|
${files.join(`
|
|
1135
1204
|
`)}
|
|
1136
1205
|
|
|
1137
|
-
Diffs
|
|
1138
|
-
${
|
|
1206
|
+
Diffs:
|
|
1207
|
+
${diffContent}${largeHint}`;
|
|
1139
1208
|
const result = await callCopilot(getGroupingSystemPrompt(convention), userMessage, model, COPILOT_LONG_TIMEOUT_MS);
|
|
1140
1209
|
if (!result) {
|
|
1210
|
+
if (isLarge)
|
|
1211
|
+
return generateCommitGroupsInBatches(files, diffs, model, convention);
|
|
1141
1212
|
throw new Error("AI returned an empty response");
|
|
1142
1213
|
}
|
|
1143
1214
|
const cleaned = extractJson(result);
|
|
@@ -1145,10 +1216,14 @@ ${diffs.slice(0, 6000)}`;
|
|
|
1145
1216
|
try {
|
|
1146
1217
|
parsed = JSON.parse(cleaned);
|
|
1147
1218
|
} catch {
|
|
1219
|
+
if (isLarge)
|
|
1220
|
+
return generateCommitGroupsInBatches(files, diffs, model, convention);
|
|
1148
1221
|
throw new Error(`AI response is not valid JSON. Raw start: "${result.slice(0, 120)}..."`);
|
|
1149
1222
|
}
|
|
1150
1223
|
const groups = parsed;
|
|
1151
1224
|
if (!Array.isArray(groups) || groups.length === 0) {
|
|
1225
|
+
if (isLarge)
|
|
1226
|
+
return generateCommitGroupsInBatches(files, diffs, model, convention);
|
|
1152
1227
|
throw new Error("AI response was not a valid JSON array of commit groups");
|
|
1153
1228
|
}
|
|
1154
1229
|
for (const group of groups) {
|
|
@@ -1158,7 +1233,51 @@ ${diffs.slice(0, 6000)}`;
|
|
|
1158
1233
|
}
|
|
1159
1234
|
return groups;
|
|
1160
1235
|
}
|
|
1236
|
+
async function generateCommitGroupsInBatches(files, diffs, model, convention = "clean-commit") {
|
|
1237
|
+
const batchSize = BATCH_CONFIG.FALLBACK_BATCH_SIZE;
|
|
1238
|
+
const allGroups = [];
|
|
1239
|
+
const diffSections = parseDiffByFile(diffs);
|
|
1240
|
+
for (let i = 0;i < files.length; i += batchSize) {
|
|
1241
|
+
const batchFiles = files.slice(i, i + batchSize);
|
|
1242
|
+
const batchDiff = batchFiles.map((f) => diffSections.get(f) ?? "").filter(Boolean).join(`
|
|
1243
|
+
`);
|
|
1244
|
+
const batchDiffContent = batchFiles.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD ? createCompactDiff(batchFiles, batchDiff) : batchDiff.slice(0, 6000);
|
|
1245
|
+
const batchNum = Math.floor(i / batchSize) + 1;
|
|
1246
|
+
const totalBatches = Math.ceil(files.length / batchSize);
|
|
1247
|
+
const userMessage = `Group these changed files into logical atomic commits:
|
|
1248
|
+
|
|
1249
|
+
Files:
|
|
1250
|
+
${batchFiles.join(`
|
|
1251
|
+
`)}
|
|
1252
|
+
|
|
1253
|
+
Diffs:
|
|
1254
|
+
${batchDiffContent}
|
|
1255
|
+
|
|
1256
|
+
NOTE: Processing batch ${batchNum}/${totalBatches} of a large changeset. Group only the files listed above.`;
|
|
1257
|
+
try {
|
|
1258
|
+
const result = await callCopilot(getGroupingSystemPrompt(convention), userMessage, model, COPILOT_LONG_TIMEOUT_MS);
|
|
1259
|
+
if (!result)
|
|
1260
|
+
continue;
|
|
1261
|
+
const cleaned = extractJson(result);
|
|
1262
|
+
const parsed = JSON.parse(cleaned);
|
|
1263
|
+
if (Array.isArray(parsed)) {
|
|
1264
|
+
for (const group of parsed) {
|
|
1265
|
+
if (Array.isArray(group.files) && typeof group.message === "string") {
|
|
1266
|
+
allGroups.push(group);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
} catch {}
|
|
1271
|
+
}
|
|
1272
|
+
if (allGroups.length === 0) {
|
|
1273
|
+
throw new Error("AI could not group any files even with batch processing");
|
|
1274
|
+
}
|
|
1275
|
+
return allGroups;
|
|
1276
|
+
}
|
|
1161
1277
|
async function regenerateAllGroupMessages(groups, diffs, model, convention = "clean-commit") {
|
|
1278
|
+
const totalFiles = groups.reduce((sum, g) => sum + g.files.length, 0);
|
|
1279
|
+
const isLarge = totalFiles >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD;
|
|
1280
|
+
const diffContent = isLarge ? createCompactDiff(groups.flatMap((g) => g.files), diffs) : diffs.slice(0, 6000);
|
|
1162
1281
|
const groupSummary = groups.map((g, i) => `Group ${i + 1}: [${g.files.join(", ")}]`).join(`
|
|
1163
1282
|
`);
|
|
1164
1283
|
const userMessage = `Regenerate ONLY the commit messages for these pre-defined file groups. Do NOT change the file groupings.
|
|
@@ -1166,8 +1285,8 @@ async function regenerateAllGroupMessages(groups, diffs, model, convention = "cl
|
|
|
1166
1285
|
Groups:
|
|
1167
1286
|
${groupSummary}
|
|
1168
1287
|
|
|
1169
|
-
Diffs
|
|
1170
|
-
${
|
|
1288
|
+
Diffs:
|
|
1289
|
+
${diffContent}`;
|
|
1171
1290
|
const result = await callCopilot(getGroupingSystemPrompt(convention), userMessage, model, COPILOT_LONG_TIMEOUT_MS);
|
|
1172
1291
|
if (!result)
|
|
1173
1292
|
return groups;
|
|
@@ -1186,12 +1305,14 @@ ${diffs.slice(0, 6000)}`;
|
|
|
1186
1305
|
}
|
|
1187
1306
|
async function regenerateGroupMessage(files, diffs, model, convention = "clean-commit") {
|
|
1188
1307
|
try {
|
|
1308
|
+
const isLarge = files.length >= BATCH_CONFIG.LARGE_CHANGESET_THRESHOLD;
|
|
1309
|
+
const diffContent = isLarge ? createCompactDiff(files, diffs) : diffs.slice(0, 4000);
|
|
1189
1310
|
const userMessage = `Generate a single commit message for these files:
|
|
1190
1311
|
|
|
1191
1312
|
Files: ${files.join(", ")}
|
|
1192
1313
|
|
|
1193
1314
|
Diff:
|
|
1194
|
-
${
|
|
1315
|
+
${diffContent}`;
|
|
1195
1316
|
const result = await callCopilot(getCommitSystemPrompt(convention), userMessage, model);
|
|
1196
1317
|
return result?.trim() ?? null;
|
|
1197
1318
|
} catch {
|
|
@@ -1749,7 +1870,8 @@ ${pc6.bold("Changed files:")}`);
|
|
|
1749
1870
|
warn(`AI unavailable: ${copilotError}`);
|
|
1750
1871
|
warn("Falling back to manual commit message entry.");
|
|
1751
1872
|
} else {
|
|
1752
|
-
const
|
|
1873
|
+
const spinnerMsg = stagedFiles.length >= 15 ? `Generating commit message with AI (${stagedFiles.length} files — using optimized batching)...` : "Generating commit message with AI...";
|
|
1874
|
+
const spinner = createSpinner(spinnerMsg);
|
|
1753
1875
|
commitMessage = await generateCommitMessage(diff, stagedFiles, args.model, config.commitConvention);
|
|
1754
1876
|
if (commitMessage) {
|
|
1755
1877
|
spinner.success("AI commit message generated.");
|
|
@@ -1840,7 +1962,7 @@ ${pc6.bold("Changed files:")}`);
|
|
|
1840
1962
|
for (const f of changedFiles) {
|
|
1841
1963
|
console.log(` ${pc6.dim("•")} ${f}`);
|
|
1842
1964
|
}
|
|
1843
|
-
const spinner = createSpinner(`Asking AI to group ${changedFiles.length} file(s) into logical commits...`);
|
|
1965
|
+
const spinner = createSpinner(changedFiles.length >= 15 ? `Asking AI to group ${changedFiles.length} file(s) into logical commits (using optimized batching)...` : `Asking AI to group ${changedFiles.length} file(s) into logical commits...`);
|
|
1844
1966
|
const diffs = await getFullDiffForFiles(changedFiles);
|
|
1845
1967
|
if (!diffs.trim()) {
|
|
1846
1968
|
spinner.stop();
|
|
@@ -2018,7 +2140,7 @@ import pc7 from "picocolors";
|
|
|
2018
2140
|
// package.json
|
|
2019
2141
|
var package_default = {
|
|
2020
2142
|
name: "contribute-now",
|
|
2021
|
-
version: "0.4.0-dev.
|
|
2143
|
+
version: "0.4.0-dev.d48d9e6",
|
|
2022
2144
|
description: "Git workflow CLI for squash-merge two-branch models. Keeps dev in sync with main after squash merges.",
|
|
2023
2145
|
type: "module",
|
|
2024
2146
|
bin: {
|
package/package.json
CHANGED