gityo 1.0.17 → 1.1.1
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/README.md +0 -1
- package/dist/index.js +678 -109
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,6 +104,5 @@ Styles control how commit messages, PR titles, and PR bodies are written. Choose
|
|
|
104
104
|
|
|
105
105
|
- Run `gityo config styles` to view commit, PR title, and PR body styles.
|
|
106
106
|
- Add `.gityo.json` to a repository for project-specific settings.
|
|
107
|
-
- Add `.gityo.md` for project-specific commit instructions.
|
|
108
107
|
- Run `gityo config` to see config locations.
|
|
109
108
|
- Use the config schema for available settings and `--help` on each command for its options.
|
package/dist/index.js
CHANGED
|
@@ -1710,7 +1710,7 @@ function useColor() {
|
|
|
1710
1710
|
if (process$1.env.FORCE_COLOR || process$1.env.CLICOLOR_FORCE !== void 0) return true;
|
|
1711
1711
|
}
|
|
1712
1712
|
new Command();
|
|
1713
|
-
var version$4 = "1.
|
|
1713
|
+
var version$4 = "1.1.1";
|
|
1714
1714
|
const keybindingLookup = new Set(["emacs", "vim"]);
|
|
1715
1715
|
function isKeybinding(value) {
|
|
1716
1716
|
return keybindingLookup.has(value);
|
|
@@ -24031,6 +24031,15 @@ async function getGit(cwd = process.cwd()) {
|
|
|
24031
24031
|
liveGit: createLiveGit(root)
|
|
24032
24032
|
};
|
|
24033
24033
|
}
|
|
24034
|
+
async function getStagedFiles(git) {
|
|
24035
|
+
return splitNull(await git.raw([
|
|
24036
|
+
"diff",
|
|
24037
|
+
"--cached",
|
|
24038
|
+
"--name-only",
|
|
24039
|
+
"--diff-filter=ACDMRTUXB",
|
|
24040
|
+
"-z"
|
|
24041
|
+
])).sort((left, right) => left.localeCompare(right));
|
|
24042
|
+
}
|
|
24034
24043
|
async function getChangedFiles(git) {
|
|
24035
24044
|
const [unstaged, staged, untracked] = await Promise.all([
|
|
24036
24045
|
git.raw([
|
|
@@ -24039,13 +24048,7 @@ async function getChangedFiles(git) {
|
|
|
24039
24048
|
"--diff-filter=ACDMRTUXB",
|
|
24040
24049
|
"-z"
|
|
24041
24050
|
]),
|
|
24042
|
-
git
|
|
24043
|
-
"diff",
|
|
24044
|
-
"--cached",
|
|
24045
|
-
"--name-only",
|
|
24046
|
-
"--diff-filter=ACDMRTUXB",
|
|
24047
|
-
"-z"
|
|
24048
|
-
]),
|
|
24051
|
+
getStagedFiles(git),
|
|
24049
24052
|
git.raw([
|
|
24050
24053
|
"ls-files",
|
|
24051
24054
|
"--others",
|
|
@@ -24054,12 +24057,12 @@ async function getChangedFiles(git) {
|
|
|
24054
24057
|
])
|
|
24055
24058
|
]);
|
|
24056
24059
|
return Array.from(new Set([
|
|
24057
|
-
...
|
|
24060
|
+
...staged,
|
|
24058
24061
|
...splitNull(unstaged),
|
|
24059
24062
|
...splitNull(untracked)
|
|
24060
24063
|
])).sort((left, right) => left.localeCompare(right));
|
|
24061
24064
|
}
|
|
24062
|
-
async function getCommitDiff(git, contextLines = 3) {
|
|
24065
|
+
async function getCommitDiff(git, scope, contextLines = 3) {
|
|
24063
24066
|
const unified = [`-U${contextLines}`];
|
|
24064
24067
|
const staged = await git.raw([
|
|
24065
24068
|
"diff",
|
|
@@ -24067,12 +24070,12 @@ async function getCommitDiff(git, contextLines = 3) {
|
|
|
24067
24070
|
"--no-ext-diff",
|
|
24068
24071
|
...unified
|
|
24069
24072
|
]);
|
|
24070
|
-
if (
|
|
24071
|
-
|
|
24072
|
-
|
|
24073
|
-
};
|
|
24073
|
+
if (scope === "staged") return staged;
|
|
24074
|
+
if (scope !== "all") throw new Error(`Unknown commit scope '${scope}'.`);
|
|
24075
|
+
const hasStaged = staged.trim().length > 0;
|
|
24074
24076
|
const [unstaged, untracked] = await Promise.all([git.raw([
|
|
24075
24077
|
"diff",
|
|
24078
|
+
...hasStaged ? ["HEAD"] : [],
|
|
24076
24079
|
"--no-ext-diff",
|
|
24077
24080
|
...unified
|
|
24078
24081
|
]), git.raw([
|
|
@@ -24081,17 +24084,14 @@ async function getCommitDiff(git, contextLines = 3) {
|
|
|
24081
24084
|
"--exclude-standard",
|
|
24082
24085
|
"-z"
|
|
24083
24086
|
])]);
|
|
24084
|
-
return
|
|
24085
|
-
diff
|
|
24086
|
-
|
|
24087
|
-
|
|
24088
|
-
|
|
24089
|
-
|
|
24090
|
-
|
|
24091
|
-
|
|
24092
|
-
])))].filter((part) => part.length > 0).join("\n"),
|
|
24093
|
-
hasStaged: false
|
|
24094
|
-
};
|
|
24087
|
+
return [unstaged, ...await Promise.all(splitNull(untracked).map((file$4) => git.raw([
|
|
24088
|
+
"diff",
|
|
24089
|
+
"--no-index",
|
|
24090
|
+
...unified,
|
|
24091
|
+
"--",
|
|
24092
|
+
"/dev/null",
|
|
24093
|
+
file$4
|
|
24094
|
+
])))].filter((part) => part.length > 0).join("\n");
|
|
24095
24095
|
}
|
|
24096
24096
|
function splitNull(output$3) {
|
|
24097
24097
|
return output$3.split("\0").filter(Boolean);
|
|
@@ -24106,17 +24106,16 @@ function createLiveGit(baseDir) {
|
|
|
24106
24106
|
}
|
|
24107
24107
|
const DEFAULT_MAX_DIFF_TOKENS = 24e3;
|
|
24108
24108
|
const DEFAULT_PER_FILE_CAP = 400;
|
|
24109
|
-
const CHARS_PER_TOKEN = 4;
|
|
24109
|
+
const CHARS_PER_TOKEN$2 = 4;
|
|
24110
24110
|
const MINIMIZED_CONTEXT_LINES = 1;
|
|
24111
24111
|
const FILE_SECTION_PREFIX = "diff --git ";
|
|
24112
24112
|
const HUNK_HEADER_PREFIX = "@@ ";
|
|
24113
24113
|
const NOISE_FILE_PATTERNS = [/\.(lock|min\.(?:js|mjs|cjs|css)|map)$/i, /-lock\.[^/]+$/i];
|
|
24114
24114
|
function estimateTokens(text$1) {
|
|
24115
|
-
return Math.ceil(text$1.length / CHARS_PER_TOKEN);
|
|
24115
|
+
return Math.ceil(text$1.length / CHARS_PER_TOKEN$2);
|
|
24116
24116
|
}
|
|
24117
24117
|
async function minimizeDiff(git, options) {
|
|
24118
|
-
const
|
|
24119
|
-
const sections = splitFileSections(diff).map(parseSection);
|
|
24118
|
+
const sections = splitFileSections(await getCommitDiff(git, options.scope, MINIMIZED_CONTEXT_LINES)).map(parseSection);
|
|
24120
24119
|
const kept = [];
|
|
24121
24120
|
const excludedPaths = /* @__PURE__ */ new Set();
|
|
24122
24121
|
for (const section of sections) if (isNoiseFile(section.path)) excludedPaths.add(section.path);
|
|
@@ -24215,7 +24214,7 @@ function splitOversizedSection(section, maxTokens) {
|
|
|
24215
24214
|
}), maxTokens);
|
|
24216
24215
|
}
|
|
24217
24216
|
function hardSplit(text$1, maxTokens) {
|
|
24218
|
-
const maxChars = maxTokens * CHARS_PER_TOKEN;
|
|
24217
|
+
const maxChars = maxTokens * CHARS_PER_TOKEN$2;
|
|
24219
24218
|
const chunks = [];
|
|
24220
24219
|
let current = "";
|
|
24221
24220
|
for (const line of text$1.split("\n")) {
|
|
@@ -36987,11 +36986,11 @@ var postToApi$7 = async ({ url: url$4, headers = {}, body, successfulResponseHan
|
|
|
36987
36986
|
});
|
|
36988
36987
|
}
|
|
36989
36988
|
};
|
|
36990
|
-
function tool
|
|
36989
|
+
function tool(tool2) {
|
|
36991
36990
|
return tool2;
|
|
36992
36991
|
}
|
|
36993
36992
|
function createProviderDefinedToolFactory$4({ id, inputSchema: inputSchema$1 }) {
|
|
36994
|
-
return ({ execute, outputSchema: outputSchema$2, needsApproval, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool
|
|
36993
|
+
return ({ execute, outputSchema: outputSchema$2, needsApproval, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool({
|
|
36995
36994
|
type: "provider",
|
|
36996
36995
|
isProviderExecuted: false,
|
|
36997
36996
|
id,
|
|
@@ -37007,7 +37006,7 @@ function createProviderDefinedToolFactory$4({ id, inputSchema: inputSchema$1 })
|
|
|
37007
37006
|
});
|
|
37008
37007
|
}
|
|
37009
37008
|
function createProviderDefinedToolFactoryWithOutputSchema$3({ id, inputSchema: inputSchema$1, outputSchema: outputSchema$2 }) {
|
|
37010
|
-
return ({ execute, needsApproval, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool
|
|
37009
|
+
return ({ execute, needsApproval, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool({
|
|
37011
37010
|
type: "provider",
|
|
37012
37011
|
isProviderExecuted: false,
|
|
37013
37012
|
id,
|
|
@@ -37023,7 +37022,7 @@ function createProviderDefinedToolFactoryWithOutputSchema$3({ id, inputSchema: i
|
|
|
37023
37022
|
});
|
|
37024
37023
|
}
|
|
37025
37024
|
function createProviderExecutedToolFactory$2({ id, inputSchema: inputSchema$1, outputSchema: outputSchema$2, supportsDeferredResults }) {
|
|
37026
|
-
return ({ onInputStart, onInputDelta, onInputAvailable,...args }) => tool
|
|
37025
|
+
return ({ onInputStart, onInputDelta, onInputAvailable,...args }) => tool({
|
|
37027
37026
|
type: "provider",
|
|
37028
37027
|
isProviderExecuted: true,
|
|
37029
37028
|
id,
|
|
@@ -48321,7 +48320,7 @@ var init_transport = __esmMin((() => {
|
|
|
48321
48320
|
init_parseUrl();
|
|
48322
48321
|
init_toEndpointV1$1();
|
|
48323
48322
|
}));
|
|
48324
|
-
var parseBoolean, expectBoolean, expectNumber, MAX_FLOAT, expectFloat32, expectLong, expectInt, expectInt32, expectShort, expectByte, expectSizedInt, castInt, expectNonNull, expectObject, expectString, expectUnion, strictParseDouble, strictParseFloat, strictParseFloat32, NUMBER_REGEX, parseNumber, limitedParseDouble, handleFloat, limitedParseFloat, limitedParseFloat32, parseFloatString, strictParseLong, strictParseInt, strictParseInt32, strictParseShort, strictParseByte, stackTraceWarning, logger$1;
|
|
48323
|
+
var parseBoolean, expectBoolean, expectNumber, MAX_FLOAT, expectFloat32, expectLong, expectInt, expectInt32, expectShort, expectByte, expectSizedInt, castInt, expectNonNull, expectObject, expectString, expectUnion, strictParseDouble, strictParseFloat, strictParseFloat32, NUMBER_REGEX, parseNumber$1, limitedParseDouble, handleFloat, limitedParseFloat, limitedParseFloat32, parseFloatString, strictParseLong, strictParseInt, strictParseInt32, strictParseShort, strictParseByte, stackTraceWarning, logger$1;
|
|
48325
48324
|
var init_parse_utils = __esmMin((() => {
|
|
48326
48325
|
init_transport();
|
|
48327
48326
|
parseBoolean = (value) => {
|
|
@@ -48427,16 +48426,16 @@ var init_parse_utils = __esmMin((() => {
|
|
|
48427
48426
|
return asObject;
|
|
48428
48427
|
};
|
|
48429
48428
|
strictParseDouble = (value) => {
|
|
48430
|
-
if (typeof value == "string") return expectNumber(parseNumber(value));
|
|
48429
|
+
if (typeof value == "string") return expectNumber(parseNumber$1(value));
|
|
48431
48430
|
return expectNumber(value);
|
|
48432
48431
|
};
|
|
48433
48432
|
strictParseFloat = strictParseDouble;
|
|
48434
48433
|
strictParseFloat32 = (value) => {
|
|
48435
|
-
if (typeof value == "string") return expectFloat32(parseNumber(value));
|
|
48434
|
+
if (typeof value == "string") return expectFloat32(parseNumber$1(value));
|
|
48436
48435
|
return expectFloat32(value);
|
|
48437
48436
|
};
|
|
48438
48437
|
NUMBER_REGEX = /(-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)|(-?Infinity)|(NaN)/g;
|
|
48439
|
-
parseNumber = (value) => {
|
|
48438
|
+
parseNumber$1 = (value) => {
|
|
48440
48439
|
const matches = value.match(NUMBER_REGEX);
|
|
48441
48440
|
if (matches === null || matches[0].length !== value.length) throw new TypeError(`Expected real number, got implicit NaN`);
|
|
48442
48441
|
return parseFloat(value);
|
|
@@ -48460,20 +48459,20 @@ var init_parse_utils = __esmMin((() => {
|
|
|
48460
48459
|
}
|
|
48461
48460
|
};
|
|
48462
48461
|
strictParseLong = (value) => {
|
|
48463
|
-
if (typeof value === "string") return expectLong(parseNumber(value));
|
|
48462
|
+
if (typeof value === "string") return expectLong(parseNumber$1(value));
|
|
48464
48463
|
return expectLong(value);
|
|
48465
48464
|
};
|
|
48466
48465
|
strictParseInt = strictParseLong;
|
|
48467
48466
|
strictParseInt32 = (value) => {
|
|
48468
|
-
if (typeof value === "string") return expectInt32(parseNumber(value));
|
|
48467
|
+
if (typeof value === "string") return expectInt32(parseNumber$1(value));
|
|
48469
48468
|
return expectInt32(value);
|
|
48470
48469
|
};
|
|
48471
48470
|
strictParseShort = (value) => {
|
|
48472
|
-
if (typeof value === "string") return expectShort(parseNumber(value));
|
|
48471
|
+
if (typeof value === "string") return expectShort(parseNumber$1(value));
|
|
48473
48472
|
return expectShort(value);
|
|
48474
48473
|
};
|
|
48475
48474
|
strictParseByte = (value) => {
|
|
48476
|
-
if (typeof value === "string") return expectByte(parseNumber(value));
|
|
48475
|
+
if (typeof value === "string") return expectByte(parseNumber$1(value));
|
|
48477
48476
|
return expectByte(value);
|
|
48478
48477
|
};
|
|
48479
48478
|
stackTraceWarning = (message) => {
|
|
@@ -48495,7 +48494,7 @@ function dateToUtcString(date$10) {
|
|
|
48495
48494
|
const secondsString = secondsInt < 10 ? `0${secondsInt}` : `${secondsInt}`;
|
|
48496
48495
|
return `${DAYS[dayOfWeek]}, ${dayOfMonthString} ${MONTHS[month]} ${year$1} ${hoursString}:${minutesString}:${secondsString} GMT`;
|
|
48497
48496
|
}
|
|
48498
|
-
var DAYS, MONTHS, RFC3339, parseRfc3339DateTime, RFC3339_WITH_OFFSET$1, parseRfc3339DateTimeWithOffset, IMF_FIXDATE$1, RFC_850_DATE$1, ASC_TIME$1, parseRfc7231DateTime, parseEpochTimestamp, buildDate, parseTwoDigitYear, FIFTY_YEARS_IN_MILLIS, adjustRfc850Year, parseMonthByShortName, DAYS_IN_MONTH, validateDayOfMonth, isLeapYear, parseDateValue, parseMilliseconds, parseOffsetToMilliseconds, stripLeadingZeroes;
|
|
48497
|
+
var DAYS, MONTHS, RFC3339, parseRfc3339DateTime, RFC3339_WITH_OFFSET$1, parseRfc3339DateTimeWithOffset, IMF_FIXDATE$1, RFC_850_DATE$1, ASC_TIME$1, parseRfc7231DateTime, parseEpochTimestamp, buildDate, parseTwoDigitYear, FIFTY_YEARS_IN_MILLIS, adjustRfc850Year, parseMonthByShortName, DAYS_IN_MONTH, validateDayOfMonth, isLeapYear, parseDateValue, parseMilliseconds$1, parseOffsetToMilliseconds, stripLeadingZeroes;
|
|
48499
48498
|
var init_date_utils = __esmMin((() => {
|
|
48500
48499
|
init_parse_utils();
|
|
48501
48500
|
DAYS = [
|
|
@@ -48608,7 +48607,7 @@ var init_date_utils = __esmMin((() => {
|
|
|
48608
48607
|
buildDate = (year$1, month, day, time$7) => {
|
|
48609
48608
|
const adjustedMonth = month - 1;
|
|
48610
48609
|
validateDayOfMonth(year$1, adjustedMonth, day);
|
|
48611
|
-
return new Date(Date.UTC(year$1, adjustedMonth, day, parseDateValue(time$7.hours, "hour", 0, 23), parseDateValue(time$7.minutes, "minute", 0, 59), parseDateValue(time$7.seconds, "seconds", 0, 60), parseMilliseconds(time$7.fractionalMilliseconds)));
|
|
48610
|
+
return new Date(Date.UTC(year$1, adjustedMonth, day, parseDateValue(time$7.hours, "hour", 0, 23), parseDateValue(time$7.minutes, "minute", 0, 59), parseDateValue(time$7.seconds, "seconds", 0, 60), parseMilliseconds$1(time$7.fractionalMilliseconds)));
|
|
48612
48611
|
};
|
|
48613
48612
|
parseTwoDigitYear = (value) => {
|
|
48614
48613
|
const thisYear = (/* @__PURE__ */ new Date()).getUTCFullYear();
|
|
@@ -48653,7 +48652,7 @@ var init_date_utils = __esmMin((() => {
|
|
|
48653
48652
|
if (dateVal < lower || dateVal > upper) throw new TypeError(`${type$1} must be between ${lower} and ${upper}, inclusive`);
|
|
48654
48653
|
return dateVal;
|
|
48655
48654
|
};
|
|
48656
|
-
parseMilliseconds = (value) => {
|
|
48655
|
+
parseMilliseconds$1 = (value) => {
|
|
48657
48656
|
if (value === null || value === void 0) return 0;
|
|
48658
48657
|
return strictParseFloat32("0." + value) * 1e3;
|
|
48659
48658
|
};
|
|
@@ -150845,7 +150844,7 @@ function jsonrepair(text$1) {
|
|
|
150845
150844
|
throwUnexpectedCharacter();
|
|
150846
150845
|
function parseValue() {
|
|
150847
150846
|
parseWhitespaceAndSkipComments();
|
|
150848
|
-
const processed$1 = parseObject() || parseArray() || parseString() || parseNumber$
|
|
150847
|
+
const processed$1 = parseObject() || parseArray() || parseString() || parseNumber$2() || parseKeywords() || parseUnquotedString(false) || parseRegex();
|
|
150849
150848
|
parseWhitespaceAndSkipComments();
|
|
150850
150849
|
return processed$1;
|
|
150851
150850
|
}
|
|
@@ -151139,7 +151138,7 @@ function jsonrepair(text$1) {
|
|
|
151139
151138
|
}
|
|
151140
151139
|
return processed$1;
|
|
151141
151140
|
}
|
|
151142
|
-
function parseNumber$
|
|
151141
|
+
function parseNumber$2() {
|
|
151143
151142
|
const start = i$5;
|
|
151144
151143
|
let num = "";
|
|
151145
151144
|
let invalid = false;
|
|
@@ -171147,7 +171146,7 @@ function hasWebSearch(client) {
|
|
|
171147
171146
|
return typeof client.webSearch === "function";
|
|
171148
171147
|
}
|
|
171149
171148
|
function webSearch(options = {}) {
|
|
171150
|
-
return tool
|
|
171149
|
+
return tool({
|
|
171151
171150
|
description: "Search the web for current information about any topic. Use this when you need up-to-date information, recent news, current statistics, or real-time data that may not be in your training data.",
|
|
171152
171151
|
inputSchema: webSearchInputSchema,
|
|
171153
171152
|
execute: async (input2, { abortSignal }) => {
|
|
@@ -171203,7 +171202,7 @@ function webSearch(options = {}) {
|
|
|
171203
171202
|
}
|
|
171204
171203
|
var webFetchInputSchema = external_exports$2.object({ url: external_exports$2.string().url("Must be a valid URL").describe("The URL to fetch content from") });
|
|
171205
171204
|
function webFetch(options = {}) {
|
|
171206
|
-
return tool
|
|
171205
|
+
return tool({
|
|
171207
171206
|
description: "Fetch and read content from a specific web URL. Use this to retrieve the full text content of web pages, articles, documentation, or any publicly accessible web content for analysis or summarization.",
|
|
171208
171207
|
inputSchema: webFetchInputSchema,
|
|
171209
171208
|
execute: async (input2, context) => {
|
|
@@ -203416,7 +203415,7 @@ var require_dist$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
203416
203415
|
safeParseJSON: () => safeParseJSON$2,
|
|
203417
203416
|
safeValidateTypes: () => safeValidateTypes$2,
|
|
203418
203417
|
standardSchemaValidator: () => standardSchemaValidator,
|
|
203419
|
-
tool: () => tool$
|
|
203418
|
+
tool: () => tool$3,
|
|
203420
203419
|
validateTypes: () => validateTypes$2,
|
|
203421
203420
|
validator: () => validator,
|
|
203422
203421
|
validatorSymbol: () => validatorSymbol,
|
|
@@ -203918,7 +203917,7 @@ var require_dist$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
203918
203917
|
});
|
|
203919
203918
|
}
|
|
203920
203919
|
};
|
|
203921
|
-
function tool$
|
|
203920
|
+
function tool$3(tool2) {
|
|
203922
203921
|
return tool2;
|
|
203923
203922
|
}
|
|
203924
203923
|
function dynamicTool$2(tool2) {
|
|
@@ -203928,7 +203927,7 @@ var require_dist$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
203928
203927
|
};
|
|
203929
203928
|
}
|
|
203930
203929
|
function createProviderDefinedToolFactory({ id, name: name$18, inputSchema: inputSchema$1 }) {
|
|
203931
|
-
return ({ execute, outputSchema: outputSchema$2, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool$
|
|
203930
|
+
return ({ execute, outputSchema: outputSchema$2, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool$3({
|
|
203932
203931
|
type: "provider-defined",
|
|
203933
203932
|
id,
|
|
203934
203933
|
name: name$18,
|
|
@@ -203943,7 +203942,7 @@ var require_dist$2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
203943
203942
|
});
|
|
203944
203943
|
}
|
|
203945
203944
|
function createProviderDefinedToolFactoryWithOutputSchema({ id, name: name$18, inputSchema: inputSchema$1, outputSchema: outputSchema$2 }) {
|
|
203946
|
-
return ({ execute, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool$
|
|
203945
|
+
return ({ execute, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool$3({
|
|
203947
203946
|
type: "provider-defined",
|
|
203948
203947
|
id,
|
|
203949
203948
|
name: name$18,
|
|
@@ -206837,11 +206836,11 @@ var postToApi$1 = async ({ url: url$4, headers = {}, body, successfulResponseHan
|
|
|
206837
206836
|
});
|
|
206838
206837
|
}
|
|
206839
206838
|
};
|
|
206840
|
-
function tool$
|
|
206839
|
+
function tool$2(tool2) {
|
|
206841
206840
|
return tool2;
|
|
206842
206841
|
}
|
|
206843
206842
|
function createProviderToolFactory$1({ id, inputSchema: inputSchema$1 }) {
|
|
206844
|
-
return ({ execute, outputSchema: outputSchema$2, needsApproval, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool$
|
|
206843
|
+
return ({ execute, outputSchema: outputSchema$2, needsApproval, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool$2({
|
|
206845
206844
|
type: "provider",
|
|
206846
206845
|
id,
|
|
206847
206846
|
args,
|
|
@@ -206856,7 +206855,7 @@ function createProviderToolFactory$1({ id, inputSchema: inputSchema$1 }) {
|
|
|
206856
206855
|
});
|
|
206857
206856
|
}
|
|
206858
206857
|
function createProviderToolFactoryWithOutputSchema$1({ id, inputSchema: inputSchema$1, outputSchema: outputSchema$2, supportsDeferredResults }) {
|
|
206859
|
-
return ({ execute, needsApproval, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool$
|
|
206858
|
+
return ({ execute, needsApproval, toModelOutput, onInputStart, onInputDelta, onInputAvailable,...args }) => tool$2({
|
|
206860
206859
|
type: "provider",
|
|
206861
206860
|
id,
|
|
206862
206861
|
args,
|
|
@@ -228699,11 +228698,11 @@ const SUPPORTED_PROVIDERS = {
|
|
|
228699
228698
|
};
|
|
228700
228699
|
const PROVIDER_NPM_PACKAGES = Object.keys(SUPPORTED_PROVIDERS);
|
|
228701
228700
|
function resolveModelConfig(models, modelKey) {
|
|
228702
|
-
const
|
|
228701
|
+
const modelKeys = Object.keys(models ?? {});
|
|
228702
|
+
if (!models || modelKeys.length === 0) throw new Error("No models configured. Edit your config file to add a model — run `gityo config` to see where.");
|
|
228703
|
+
const modelConfig = models[modelKey ?? modelKeys[0]];
|
|
228703
228704
|
if (modelConfig) return modelConfig;
|
|
228704
|
-
|
|
228705
|
-
if (availableModels.length === 0) throw new Error("No models configured. Edit your config file to add a model — run `gityo config` to see where.");
|
|
228706
|
-
throw new Error(`Model '${modelKey}' is not configured. Available models: ${availableModels}.`);
|
|
228705
|
+
throw new Error(`Model '${modelKey}' is not configured. Available models: ${modelKeys.join(", ")}.`);
|
|
228707
228706
|
}
|
|
228708
228707
|
function resolveLanguageModel(modelConfig) {
|
|
228709
228708
|
const envVarNames = Array.isArray(modelConfig.apiKeyEnv) ? modelConfig.apiKeyEnv : [modelConfig.apiKeyEnv];
|
|
@@ -228808,6 +228807,10 @@ const instructionSchema = union$4([string$5().min(1), object$3({ path: string$5(
|
|
|
228808
228807
|
const configSchema = object$3({
|
|
228809
228808
|
$schema: url$3(),
|
|
228810
228809
|
models: record$3(string$5().min(1), modelSchema),
|
|
228810
|
+
model: string$5().min(1),
|
|
228811
|
+
prModel: string$5().min(1),
|
|
228812
|
+
commitModel: string$5().min(1),
|
|
228813
|
+
releaseModel: string$5().min(1),
|
|
228811
228814
|
commitInstructions: instructionSchema,
|
|
228812
228815
|
commitStyle: string$5().min(1),
|
|
228813
228816
|
commitStyles: record$3(string$5().min(1), instructionSchema),
|
|
@@ -228815,6 +228818,11 @@ const configSchema = object$3({
|
|
|
228815
228818
|
prBodyStyle: string$5().min(1),
|
|
228816
228819
|
prTitleStyles: record$3(string$5().min(1), instructionSchema),
|
|
228817
228820
|
prBodyStyles: record$3(string$5().min(1), instructionSchema),
|
|
228821
|
+
prMergeMethod: _enum$4([
|
|
228822
|
+
"merge",
|
|
228823
|
+
"rebase",
|
|
228824
|
+
"squash"
|
|
228825
|
+
]),
|
|
228818
228826
|
maxDiffTokens: number$4().int().min(1e3),
|
|
228819
228827
|
perFileCap: number$4().int().min(50),
|
|
228820
228828
|
autoAcceptCommitMessage: boolean$4(),
|
|
@@ -228825,6 +228833,10 @@ function resolveConfig(input$3) {
|
|
|
228825
228833
|
const parsed = configSchema.parse(input$3);
|
|
228826
228834
|
return {
|
|
228827
228835
|
models: parsed.models,
|
|
228836
|
+
model: parsed.model,
|
|
228837
|
+
prModel: parsed.prModel,
|
|
228838
|
+
commitModel: parsed.commitModel,
|
|
228839
|
+
releaseModel: parsed.releaseModel,
|
|
228828
228840
|
commitStyle: parsed.commitStyle,
|
|
228829
228841
|
commitStyles: parsed.commitStyles,
|
|
228830
228842
|
commitInstructions: parsed.commitInstructions,
|
|
@@ -228832,6 +228844,7 @@ function resolveConfig(input$3) {
|
|
|
228832
228844
|
prBodyStyle: parsed.prBodyStyle,
|
|
228833
228845
|
prTitleStyles: parsed.prTitleStyles,
|
|
228834
228846
|
prBodyStyles: parsed.prBodyStyles,
|
|
228847
|
+
prMergeMethod: parsed.prMergeMethod ?? "merge",
|
|
228835
228848
|
maxDiffTokens: parsed.maxDiffTokens,
|
|
228836
228849
|
perFileCap: parsed.perFileCap,
|
|
228837
228850
|
autoAcceptCommitMessage: parsed.autoAcceptCommitMessage ?? false,
|
|
@@ -228842,15 +228855,26 @@ function resolveConfig(input$3) {
|
|
|
228842
228855
|
const PROJECT_CONFIG_FILE_NAME = ".gityo.json";
|
|
228843
228856
|
const GLOBAL_CONFIG_FILE_PATH = path.join(os.homedir(), ".config", "gityo.json");
|
|
228844
228857
|
async function loadConfig(cwd = process.cwd()) {
|
|
228845
|
-
const [globalConfig$3, projectConfig
|
|
228846
|
-
readGlobalConfig(),
|
|
228847
|
-
readProjectConfig(cwd),
|
|
228848
|
-
readInstructionsFile(path.join(cwd, ".gityo.md"))
|
|
228849
|
-
]);
|
|
228858
|
+
const [globalConfig$3, projectConfig] = await Promise.all([readGlobalConfig(), readProjectConfig(cwd)]);
|
|
228850
228859
|
return resolveConfig({
|
|
228851
228860
|
...globalConfig$3 ?? {},
|
|
228852
228861
|
...projectConfig ?? {},
|
|
228853
|
-
|
|
228862
|
+
models: {
|
|
228863
|
+
...globalConfig$3?.models,
|
|
228864
|
+
...projectConfig?.models
|
|
228865
|
+
},
|
|
228866
|
+
commitStyles: {
|
|
228867
|
+
...globalConfig$3?.commitStyles,
|
|
228868
|
+
...projectConfig?.commitStyles
|
|
228869
|
+
},
|
|
228870
|
+
prTitleStyles: {
|
|
228871
|
+
...globalConfig$3?.prTitleStyles,
|
|
228872
|
+
...projectConfig?.prTitleStyles
|
|
228873
|
+
},
|
|
228874
|
+
prBodyStyles: {
|
|
228875
|
+
...globalConfig$3?.prBodyStyles,
|
|
228876
|
+
...projectConfig?.prBodyStyles
|
|
228877
|
+
}
|
|
228854
228878
|
});
|
|
228855
228879
|
}
|
|
228856
228880
|
async function readProjectConfig(cwd = process.cwd()) {
|
|
@@ -228877,17 +228901,6 @@ async function readConfigFile(filePath) {
|
|
|
228877
228901
|
if (!result.success) throw new Error(`Invalid config in ${filePath}: ${result.error.message}`);
|
|
228878
228902
|
return result.data;
|
|
228879
228903
|
}
|
|
228880
|
-
async function readInstructionsFile(filePath) {
|
|
228881
|
-
let contents;
|
|
228882
|
-
try {
|
|
228883
|
-
contents = await readFile(filePath, "utf8");
|
|
228884
|
-
} catch (error$125) {
|
|
228885
|
-
if (isMissingFileError(error$125)) return;
|
|
228886
|
-
throw error$125;
|
|
228887
|
-
}
|
|
228888
|
-
const value = contents.trim();
|
|
228889
|
-
return value.length > 0 ? value : void 0;
|
|
228890
|
-
}
|
|
228891
228904
|
function isMissingFileError(error$125) {
|
|
228892
228905
|
return error$125 instanceof Error && "code" in error$125 && typeof error$125.code === "string" && error$125.code === "ENOENT";
|
|
228893
228906
|
}
|
|
@@ -228950,14 +228963,14 @@ async function runWithLoading(label, task) {
|
|
|
228950
228963
|
readline.clearLine(process.stdout, 0);
|
|
228951
228964
|
}
|
|
228952
228965
|
}
|
|
228953
|
-
const MAP_CONCURRENCY = 3;
|
|
228954
|
-
const PROMPT_RESERVE_TOKENS = 800;
|
|
228955
|
-
const MIN_CHUNK_BUDGET_TOKENS = 1e3;
|
|
228956
|
-
const MAX_TOC_LINES = 500;
|
|
228966
|
+
const MAP_CONCURRENCY$1 = 3;
|
|
228967
|
+
const PROMPT_RESERVE_TOKENS$2 = 800;
|
|
228968
|
+
const MIN_CHUNK_BUDGET_TOKENS$1 = 1e3;
|
|
228969
|
+
const MAX_TOC_LINES$1 = 500;
|
|
228957
228970
|
async function mainController(options = {}) {
|
|
228958
228971
|
const { git, liveGit } = await getGit();
|
|
228959
228972
|
const config$5 = await loadConfig((await git.revparse(["--show-toplevel"])).trim());
|
|
228960
|
-
const modelKey = options.model ??
|
|
228973
|
+
const modelKey = options.model ?? config$5.commitModel ?? config$5.model;
|
|
228961
228974
|
const languageModel = resolveLanguageModel(resolveModelConfig(config$5.models, modelKey));
|
|
228962
228975
|
const styleKey = options.style ?? config$5.commitStyle ?? "default";
|
|
228963
228976
|
const style = await resolveStyle(styleKey, config$5.commitStyles);
|
|
@@ -228970,9 +228983,9 @@ async function mainController(options = {}) {
|
|
|
228970
228983
|
const forceExecPostCommand = options.post || options.yolo;
|
|
228971
228984
|
let finalCommitMessage = options.input?.trim() ?? "";
|
|
228972
228985
|
if (typeof options.input === "string" && finalCommitMessage.length === 0) throw new Error("Provided commit message input cannot be empty.");
|
|
228973
|
-
const files = await
|
|
228986
|
+
const { diffScope, files } = await getCommitFiles(git, options.scope ?? "staged-or-changes");
|
|
228974
228987
|
if (files.length === 0) return console.log("No changed files found.");
|
|
228975
|
-
const
|
|
228988
|
+
const diff = await getCommitDiff(git, diffScope);
|
|
228976
228989
|
if (finalCommitMessage.length > 0) {
|
|
228977
228990
|
console.log(source_default.yellow("✓ Using direct commit message"));
|
|
228978
228991
|
console.log(source_default.cyan.dim(finalCommitMessage));
|
|
@@ -228981,6 +228994,7 @@ async function mainController(options = {}) {
|
|
|
228981
228994
|
if (finalCommitMessage.length === 0) while (true) {
|
|
228982
228995
|
finalCommitMessage = (await runWithLoading("Generating commit message", () => generateMessage({
|
|
228983
228996
|
git,
|
|
228997
|
+
scope: diffScope,
|
|
228984
228998
|
languageModel,
|
|
228985
228999
|
style,
|
|
228986
229000
|
instructions,
|
|
@@ -228997,7 +229011,7 @@ async function mainController(options = {}) {
|
|
|
228997
229011
|
}
|
|
228998
229012
|
if (finalCommitMessage.length === 0) throw new Error("Commit message cannot be empty.");
|
|
228999
229013
|
if (forceLLMGenerate) console.log(source_default.green("✓ Committing changes"));
|
|
229000
|
-
if (
|
|
229014
|
+
if (diffScope === "all") await git.add(["-A"]);
|
|
229001
229015
|
console.log(files.join("\n"));
|
|
229002
229016
|
console.log("");
|
|
229003
229017
|
await liveGit.commit(finalCommitMessage);
|
|
@@ -229014,19 +229028,35 @@ async function mainController(options = {}) {
|
|
|
229014
229028
|
await liveGit.push();
|
|
229015
229029
|
if (config$5.postCommand === "push-and-pull") await liveGit.pull();
|
|
229016
229030
|
}
|
|
229031
|
+
async function getCommitFiles(git, commitScope) {
|
|
229032
|
+
const stagedFiles = await getStagedFiles(git);
|
|
229033
|
+
let diffScope;
|
|
229034
|
+
if (commitScope === "everything") diffScope = "all";
|
|
229035
|
+
else if (commitScope === "staged-only") {
|
|
229036
|
+
if (stagedFiles.length === 0) throw new Error("Nothing is staged.");
|
|
229037
|
+
diffScope = "staged";
|
|
229038
|
+
} else if (commitScope === "staged-or-changes") diffScope = stagedFiles.length > 0 ? "staged" : "all";
|
|
229039
|
+
else throw new Error(`Unknown commit scope '${commitScope}'.`);
|
|
229040
|
+
const files = diffScope === "staged" ? stagedFiles : await getChangedFiles(git);
|
|
229041
|
+
return {
|
|
229042
|
+
diffScope,
|
|
229043
|
+
files
|
|
229044
|
+
};
|
|
229045
|
+
}
|
|
229017
229046
|
async function generateMessage(options) {
|
|
229018
229047
|
const { git, languageModel, style, instructions, diff } = options;
|
|
229019
229048
|
if (estimateTokens(diff) <= options.maxDiffTokens) return generateCommitMessage(languageModel, style, instructions, diff);
|
|
229020
229049
|
const { toc, body } = await minimizeDiff(git, {
|
|
229050
|
+
scope: options.scope,
|
|
229021
229051
|
perFileCap: options.perFileCap,
|
|
229022
229052
|
allFiles: options.files
|
|
229023
229053
|
});
|
|
229024
229054
|
if (estimateTokens(toc) + estimateTokens(body) <= options.maxDiffTokens) return generateCommitMessage(languageModel, style, instructions, `${toc}\n\n${body}`);
|
|
229025
229055
|
let effectiveToc = toc;
|
|
229026
|
-
let chunkBudget = options.maxDiffTokens - estimateTokens(effectiveToc) - PROMPT_RESERVE_TOKENS;
|
|
229027
|
-
if (chunkBudget < MIN_CHUNK_BUDGET_TOKENS) {
|
|
229028
|
-
effectiveToc = toc.split("\n").slice(0, MAX_TOC_LINES).join("\n");
|
|
229029
|
-
chunkBudget = Math.max(options.maxDiffTokens - estimateTokens(effectiveToc) - PROMPT_RESERVE_TOKENS, MIN_CHUNK_BUDGET_TOKENS);
|
|
229056
|
+
let chunkBudget = options.maxDiffTokens - estimateTokens(effectiveToc) - PROMPT_RESERVE_TOKENS$2;
|
|
229057
|
+
if (chunkBudget < MIN_CHUNK_BUDGET_TOKENS$1) {
|
|
229058
|
+
effectiveToc = toc.split("\n").slice(0, MAX_TOC_LINES$1).join("\n");
|
|
229059
|
+
chunkBudget = Math.max(options.maxDiffTokens - estimateTokens(effectiveToc) - PROMPT_RESERVE_TOKENS$2, MIN_CHUNK_BUDGET_TOKENS$1);
|
|
229030
229060
|
}
|
|
229031
229061
|
const chunks = splitDiffIntoChunks(body, chunkBudget);
|
|
229032
229062
|
const summaries = new Array(chunks.length);
|
|
@@ -229037,7 +229067,7 @@ async function generateMessage(options) {
|
|
|
229037
229067
|
summaries[index$2] = await summarizeChanges(languageModel, effectiveToc, chunks[index$2]);
|
|
229038
229068
|
}
|
|
229039
229069
|
}
|
|
229040
|
-
await Promise.all(Array.from({ length: Math.min(MAP_CONCURRENCY, chunks.length) }, worker));
|
|
229070
|
+
await Promise.all(Array.from({ length: Math.min(MAP_CONCURRENCY$1, chunks.length) }, worker));
|
|
229041
229071
|
return generateCommitMessageFromSummaries(languageModel, style, instructions, effectiveToc, summaries);
|
|
229042
229072
|
}
|
|
229043
229073
|
const CONFIG_SCHEMA_URL = "https://github.com/NazmusSayad/gityo/raw/refs/heads/schema/schema.json";
|
|
@@ -229153,12 +229183,12 @@ async function createPullRequest$1(input$3) {
|
|
|
229153
229183
|
input$3.head
|
|
229154
229184
|
]);
|
|
229155
229185
|
}
|
|
229156
|
-
async function mergePullRequest(number$7) {
|
|
229186
|
+
async function mergePullRequest(number$7, method) {
|
|
229157
229187
|
await execInherit("gh", [
|
|
229158
229188
|
"pr",
|
|
229159
229189
|
"merge",
|
|
229160
229190
|
String(number$7),
|
|
229161
|
-
|
|
229191
|
+
`--${method}`
|
|
229162
229192
|
]);
|
|
229163
229193
|
}
|
|
229164
229194
|
async function openPullRequest(number$7) {
|
|
@@ -236143,7 +236173,7 @@ const renderPullRequest = createRenderer({
|
|
|
236143
236173
|
async function getCurrentBranch() {
|
|
236144
236174
|
return (await exec("git", ["branch", "--show-current"])).trim();
|
|
236145
236175
|
}
|
|
236146
|
-
async function getRepoRoot() {
|
|
236176
|
+
async function getRepoRoot$1() {
|
|
236147
236177
|
return (await exec("git", ["rev-parse", "--show-toplevel"])).trim();
|
|
236148
236178
|
}
|
|
236149
236179
|
async function resolvePrBranches(baseArg, headArg) {
|
|
@@ -236158,8 +236188,8 @@ async function resolvePrBranches(baseArg, headArg) {
|
|
|
236158
236188
|
};
|
|
236159
236189
|
}
|
|
236160
236190
|
async function loadPullRequestContext(options = {}) {
|
|
236161
|
-
const config$5 = await loadConfig(await getRepoRoot());
|
|
236162
|
-
const languageModel = resolveLanguageModel(resolveModelConfig(config$5.models, options.modelKey ??
|
|
236191
|
+
const config$5 = await loadConfig(await getRepoRoot$1());
|
|
236192
|
+
const languageModel = resolveLanguageModel(resolveModelConfig(config$5.models, options.modelKey ?? config$5.prModel ?? config$5.model));
|
|
236163
236193
|
const titleStyleKey = options.titleStyle ?? config$5.prTitleStyle ?? "default";
|
|
236164
236194
|
const titleStyle = config$5.prTitleStyles?.[titleStyleKey] ?? PR_TITLE_STYLES[titleStyleKey];
|
|
236165
236195
|
if (!titleStyle) {
|
|
@@ -236247,9 +236277,11 @@ function buildCompareContext(compare, maxDiffTokens) {
|
|
|
236247
236277
|
async function handleUncommittedChanges(headBranch, options = {}) {
|
|
236248
236278
|
if (await getCurrentBranch() !== headBranch) return;
|
|
236249
236279
|
const { git } = await getGit();
|
|
236250
|
-
const
|
|
236280
|
+
const scope = options.scope ?? "staged-or-changes";
|
|
236281
|
+
const { diffScope, files } = await getCommitFiles(git, scope);
|
|
236251
236282
|
if (files.length === 0) return;
|
|
236252
|
-
|
|
236283
|
+
const label = diffScope === "staged" ? "staged" : "uncommitted";
|
|
236284
|
+
console.log(source_default.yellow(`• You have ${files.length} ${label} local change(s):\n${files.join("\n")}`));
|
|
236253
236285
|
console.log("");
|
|
236254
236286
|
if (!options.yolo) {
|
|
236255
236287
|
if (!await dist_default({
|
|
@@ -236257,14 +236289,15 @@ async function handleUncommittedChanges(headBranch, options = {}) {
|
|
|
236257
236289
|
default: true,
|
|
236258
236290
|
theme: selectionTheme
|
|
236259
236291
|
})) {
|
|
236260
|
-
console.log(source_default.dim(
|
|
236292
|
+
console.log(source_default.dim(`Ignoring ${label} local changes.`));
|
|
236261
236293
|
console.log("");
|
|
236262
236294
|
return;
|
|
236263
236295
|
}
|
|
236264
236296
|
}
|
|
236265
236297
|
await mainController({
|
|
236266
236298
|
yolo: options.yolo,
|
|
236267
|
-
push: true
|
|
236299
|
+
push: true,
|
|
236300
|
+
scope
|
|
236268
236301
|
});
|
|
236269
236302
|
console.log("");
|
|
236270
236303
|
}
|
|
@@ -236276,7 +236309,10 @@ async function createPullRequestController(baseArg, headArg, options = {}) {
|
|
|
236276
236309
|
});
|
|
236277
236310
|
const systemPrompt = buildPullRequestSystemPrompt(context);
|
|
236278
236311
|
const branches = await resolvePrBranches(baseArg, headArg);
|
|
236279
|
-
await handleUncommittedChanges(branches.head, {
|
|
236312
|
+
await handleUncommittedChanges(branches.head, {
|
|
236313
|
+
yolo: options.yolo ?? false,
|
|
236314
|
+
scope: options.all ? "everything" : "staged-or-changes"
|
|
236315
|
+
});
|
|
236280
236316
|
const pullRequest = await createPullRequest({
|
|
236281
236317
|
base: branches.base,
|
|
236282
236318
|
head: branches.head,
|
|
@@ -236300,7 +236336,10 @@ async function mergePullRequestController(baseArg, headArg, options = {}) {
|
|
|
236300
236336
|
});
|
|
236301
236337
|
const systemPrompt = buildPullRequestSystemPrompt(context);
|
|
236302
236338
|
const branches = await resolvePrBranches(baseArg, headArg);
|
|
236303
|
-
await handleUncommittedChanges(branches.head, {
|
|
236339
|
+
await handleUncommittedChanges(branches.head, {
|
|
236340
|
+
yolo: options.yolo ?? false,
|
|
236341
|
+
scope: options.all ? "everything" : "staged-or-changes"
|
|
236342
|
+
});
|
|
236304
236343
|
let pullRequest = await findPullRequest(branches.base, branches.head);
|
|
236305
236344
|
if (pullRequest) console.log(pullRequest.url);
|
|
236306
236345
|
else pullRequest = await createPullRequest({
|
|
@@ -236321,7 +236360,498 @@ async function mergePullRequestController(baseArg, headArg, options = {}) {
|
|
|
236321
236360
|
console.log("Pull request merge cancelled.");
|
|
236322
236361
|
return;
|
|
236323
236362
|
}
|
|
236324
|
-
await mergePullRequest(pullRequest.number);
|
|
236363
|
+
await mergePullRequest(pullRequest.number, options.mergeMethod ?? context.config.prMergeMethod);
|
|
236364
|
+
}
|
|
236365
|
+
const toZeroIfInfinity = (value) => Number.isFinite(value) ? value : 0;
|
|
236366
|
+
function parseNumber(milliseconds) {
|
|
236367
|
+
return {
|
|
236368
|
+
days: Math.trunc(milliseconds / 864e5),
|
|
236369
|
+
hours: Math.trunc(milliseconds / 36e5 % 24),
|
|
236370
|
+
minutes: Math.trunc(milliseconds / 6e4 % 60),
|
|
236371
|
+
seconds: Math.trunc(milliseconds / 1e3 % 60),
|
|
236372
|
+
milliseconds: Math.trunc(milliseconds % 1e3),
|
|
236373
|
+
microseconds: Math.trunc(toZeroIfInfinity(milliseconds * 1e3) % 1e3),
|
|
236374
|
+
nanoseconds: Math.trunc(toZeroIfInfinity(milliseconds * 1e6) % 1e3)
|
|
236375
|
+
};
|
|
236376
|
+
}
|
|
236377
|
+
function parseBigint(milliseconds) {
|
|
236378
|
+
return {
|
|
236379
|
+
days: milliseconds / 86400000n,
|
|
236380
|
+
hours: milliseconds / 3600000n % 24n,
|
|
236381
|
+
minutes: milliseconds / 60000n % 60n,
|
|
236382
|
+
seconds: milliseconds / 1000n % 60n,
|
|
236383
|
+
milliseconds: milliseconds % 1000n,
|
|
236384
|
+
microseconds: 0n,
|
|
236385
|
+
nanoseconds: 0n
|
|
236386
|
+
};
|
|
236387
|
+
}
|
|
236388
|
+
function parseMilliseconds(milliseconds) {
|
|
236389
|
+
switch (typeof milliseconds) {
|
|
236390
|
+
case "number":
|
|
236391
|
+
if (Number.isFinite(milliseconds)) return parseNumber(milliseconds);
|
|
236392
|
+
break;
|
|
236393
|
+
case "bigint": return parseBigint(milliseconds);
|
|
236394
|
+
}
|
|
236395
|
+
throw new TypeError("Expected a finite number or bigint");
|
|
236396
|
+
}
|
|
236397
|
+
const isZero = (value) => value === 0 || value === 0n;
|
|
236398
|
+
const pluralize = (word, count) => count === 1 || count === 1n ? word : `${word}s`;
|
|
236399
|
+
const SECOND_ROUNDING_EPSILON = 1e-7;
|
|
236400
|
+
const ONE_DAY_IN_MILLISECONDS = 24n * 60n * 60n * 1000n;
|
|
236401
|
+
function prettyMilliseconds(milliseconds, options) {
|
|
236402
|
+
const isBigInt = typeof milliseconds === "bigint";
|
|
236403
|
+
if (!isBigInt && !Number.isFinite(milliseconds)) throw new TypeError("Expected a finite number or bigint");
|
|
236404
|
+
options = { ...options };
|
|
236405
|
+
const sign$2 = milliseconds < 0 ? "-" : "";
|
|
236406
|
+
milliseconds = milliseconds < 0 ? -milliseconds : milliseconds;
|
|
236407
|
+
if (options.colonNotation) {
|
|
236408
|
+
options.compact = false;
|
|
236409
|
+
options.formatSubMilliseconds = false;
|
|
236410
|
+
options.separateMilliseconds = false;
|
|
236411
|
+
options.verbose = false;
|
|
236412
|
+
}
|
|
236413
|
+
if (options.compact) {
|
|
236414
|
+
options.unitCount = 1;
|
|
236415
|
+
options.secondsDecimalDigits = 0;
|
|
236416
|
+
options.millisecondsDecimalDigits = 0;
|
|
236417
|
+
}
|
|
236418
|
+
let result = [];
|
|
236419
|
+
const floorDecimals = (value, decimalDigits) => {
|
|
236420
|
+
const flooredInterimValue = Math.floor(value * 10 ** decimalDigits + SECOND_ROUNDING_EPSILON);
|
|
236421
|
+
return (Math.round(flooredInterimValue) / 10 ** decimalDigits).toFixed(decimalDigits);
|
|
236422
|
+
};
|
|
236423
|
+
const add = (value, long, short, valueString) => {
|
|
236424
|
+
if ((result.length === 0 || !options.colonNotation) && isZero(value) && !(options.colonNotation && short === "m")) return;
|
|
236425
|
+
valueString ?? (valueString = String(value));
|
|
236426
|
+
if (options.colonNotation) {
|
|
236427
|
+
const wholeDigits = valueString.includes(".") ? valueString.split(".")[0].length : valueString.length;
|
|
236428
|
+
const minLength = result.length > 0 ? 2 : 1;
|
|
236429
|
+
valueString = "0".repeat(Math.max(0, minLength - wholeDigits)) + valueString;
|
|
236430
|
+
} else valueString += options.verbose ? " " + pluralize(long, value) : short;
|
|
236431
|
+
result.push(valueString);
|
|
236432
|
+
};
|
|
236433
|
+
const parsed = parseMilliseconds(milliseconds);
|
|
236434
|
+
const days = BigInt(parsed.days);
|
|
236435
|
+
if (options.hideYearAndDays) add(BigInt(days) * 24n + BigInt(parsed.hours), "hour", "h");
|
|
236436
|
+
else {
|
|
236437
|
+
if (options.hideYear) add(days, "day", "d");
|
|
236438
|
+
else {
|
|
236439
|
+
add(days / 365n, "year", "y");
|
|
236440
|
+
add(days % 365n, "day", "d");
|
|
236441
|
+
}
|
|
236442
|
+
add(Number(parsed.hours), "hour", "h");
|
|
236443
|
+
}
|
|
236444
|
+
add(Number(parsed.minutes), "minute", "m");
|
|
236445
|
+
if (!options.hideSeconds) if (options.separateMilliseconds || options.formatSubMilliseconds || !options.colonNotation && milliseconds < 1e3 && !options.subSecondsAsDecimals) {
|
|
236446
|
+
const seconds = Number(parsed.seconds);
|
|
236447
|
+
const milliseconds$1 = Number(parsed.milliseconds);
|
|
236448
|
+
const microseconds = Number(parsed.microseconds);
|
|
236449
|
+
const nanoseconds = Number(parsed.nanoseconds);
|
|
236450
|
+
add(seconds, "second", "s");
|
|
236451
|
+
if (options.formatSubMilliseconds) {
|
|
236452
|
+
add(milliseconds$1, "millisecond", "ms");
|
|
236453
|
+
add(microseconds, "microsecond", "µs");
|
|
236454
|
+
add(nanoseconds, "nanosecond", "ns");
|
|
236455
|
+
} else {
|
|
236456
|
+
const millisecondsAndBelow = milliseconds$1 + microseconds / 1e3 + nanoseconds / 1e6;
|
|
236457
|
+
const millisecondsDecimalDigits = typeof options.millisecondsDecimalDigits === "number" ? options.millisecondsDecimalDigits : 0;
|
|
236458
|
+
const roundedMilliseconds = millisecondsAndBelow >= 1 ? Math.round(millisecondsAndBelow) : Math.ceil(millisecondsAndBelow);
|
|
236459
|
+
const maximumMilliseconds = 1e3 - 10 ** -millisecondsDecimalDigits;
|
|
236460
|
+
const millisecondsString = millisecondsDecimalDigits ? Math.min(millisecondsAndBelow, maximumMilliseconds).toFixed(millisecondsDecimalDigits) : Math.min(roundedMilliseconds, maximumMilliseconds);
|
|
236461
|
+
add(Number.parseFloat(millisecondsString), "millisecond", "ms", millisecondsString);
|
|
236462
|
+
}
|
|
236463
|
+
} else {
|
|
236464
|
+
const secondsFixed = floorDecimals((isBigInt ? Number(milliseconds % ONE_DAY_IN_MILLISECONDS) : milliseconds) / 1e3 % 60, typeof options.secondsDecimalDigits === "number" ? options.secondsDecimalDigits : 1);
|
|
236465
|
+
const secondsString = options.keepDecimalsOnWholeSeconds ? secondsFixed : secondsFixed.replace(/\.0+$/, "");
|
|
236466
|
+
add(Number.parseFloat(secondsString), "second", "s", secondsString);
|
|
236467
|
+
}
|
|
236468
|
+
if (result.length === 0) return sign$2 + "0" + (options.verbose ? " milliseconds" : "ms");
|
|
236469
|
+
const separator = options.colonNotation ? ":" : " ";
|
|
236470
|
+
if (typeof options.unitCount === "number") result = result.slice(0, Math.max(options.unitCount, 1));
|
|
236471
|
+
return sign$2 + result.join(separator);
|
|
236472
|
+
}
|
|
236473
|
+
async function listReleases(limit) {
|
|
236474
|
+
const output$3 = await exec("gh", [
|
|
236475
|
+
"release",
|
|
236476
|
+
"list",
|
|
236477
|
+
"--exclude-drafts",
|
|
236478
|
+
"--limit",
|
|
236479
|
+
String(limit),
|
|
236480
|
+
"--json",
|
|
236481
|
+
"tagName,publishedAt"
|
|
236482
|
+
]);
|
|
236483
|
+
return JSON.parse(output$3).sort((a, b$2) => Date.parse(b$2.publishedAt) - Date.parse(a.publishedAt));
|
|
236484
|
+
}
|
|
236485
|
+
const COMMIT_JQ = "{hash: .sha, date: .commit.author.date, authorName: .commit.author.name, authorEmail: .commit.author.email, message: .commit.message}";
|
|
236486
|
+
async function getBranchHeadSha(branch) {
|
|
236487
|
+
return (await exec("gh", [
|
|
236488
|
+
"api",
|
|
236489
|
+
`repos/{owner}/{repo}/commits/${encodeURIComponent(branch)}`,
|
|
236490
|
+
"--jq",
|
|
236491
|
+
".sha"
|
|
236492
|
+
])).trim();
|
|
236493
|
+
}
|
|
236494
|
+
async function listReleaseCommits(fromTag, toSha) {
|
|
236495
|
+
const commits = (await exec("gh", [
|
|
236496
|
+
"api",
|
|
236497
|
+
"--paginate",
|
|
236498
|
+
...fromTag === null ? [
|
|
236499
|
+
`repos/{owner}/{repo}/commits?sha=${toSha}&per_page=100`,
|
|
236500
|
+
"--jq",
|
|
236501
|
+
`.[] | ${COMMIT_JQ}`
|
|
236502
|
+
] : [
|
|
236503
|
+
`repos/{owner}/{repo}/compare/${encodeURIComponent(fromTag)}...${toSha}?per_page=100`,
|
|
236504
|
+
"--jq",
|
|
236505
|
+
`.commits[] | ${COMMIT_JQ}`
|
|
236506
|
+
]
|
|
236507
|
+
])).split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
236508
|
+
return fromTag === null ? commits : commits.reverse();
|
|
236509
|
+
}
|
|
236510
|
+
async function releaseExists(tag) {
|
|
236511
|
+
try {
|
|
236512
|
+
await exec("gh", [
|
|
236513
|
+
"release",
|
|
236514
|
+
"view",
|
|
236515
|
+
tag,
|
|
236516
|
+
"--json",
|
|
236517
|
+
"tagName"
|
|
236518
|
+
]);
|
|
236519
|
+
return true;
|
|
236520
|
+
} catch {
|
|
236521
|
+
return false;
|
|
236522
|
+
}
|
|
236523
|
+
}
|
|
236524
|
+
async function deleteRelease(tag) {
|
|
236525
|
+
await execInherit("gh", [
|
|
236526
|
+
"release",
|
|
236527
|
+
"delete",
|
|
236528
|
+
tag,
|
|
236529
|
+
"--yes"
|
|
236530
|
+
]);
|
|
236531
|
+
await execInherit("gh", [
|
|
236532
|
+
"api",
|
|
236533
|
+
"--method",
|
|
236534
|
+
"DELETE",
|
|
236535
|
+
`repos/{owner}/{repo}/git/refs/tags/${encodeURIComponent(tag)}`
|
|
236536
|
+
]);
|
|
236537
|
+
}
|
|
236538
|
+
async function createRelease(input$3) {
|
|
236539
|
+
await execInherit("gh", [
|
|
236540
|
+
"release",
|
|
236541
|
+
"create",
|
|
236542
|
+
input$3.tag,
|
|
236543
|
+
"--target",
|
|
236544
|
+
input$3.target,
|
|
236545
|
+
"--notes",
|
|
236546
|
+
input$3.notes
|
|
236547
|
+
]);
|
|
236548
|
+
}
|
|
236549
|
+
async function getRepoRoot() {
|
|
236550
|
+
return (await exec("git", ["rev-parse", "--show-toplevel"])).trim();
|
|
236551
|
+
}
|
|
236552
|
+
async function fetchBranchAndTags(branch) {
|
|
236553
|
+
await exec("git", [
|
|
236554
|
+
"fetch",
|
|
236555
|
+
"origin",
|
|
236556
|
+
branch,
|
|
236557
|
+
"--tags",
|
|
236558
|
+
"--force"
|
|
236559
|
+
]);
|
|
236560
|
+
}
|
|
236561
|
+
async function assertLocalCommitsMatch(fromTag, toSha, githubHashes) {
|
|
236562
|
+
for (const ref of fromTag === null ? [toSha] : [fromTag, toSha]) try {
|
|
236563
|
+
await exec("git", [
|
|
236564
|
+
"cat-file",
|
|
236565
|
+
"-e",
|
|
236566
|
+
`${ref}^{commit}`
|
|
236567
|
+
]);
|
|
236568
|
+
} catch {
|
|
236569
|
+
throw new Error(`'${ref}' from GitHub is not in your local repository. Fetch it and try again.`);
|
|
236570
|
+
}
|
|
236571
|
+
const range$2 = fromTag === null ? toSha : `${fromTag}..${toSha}`;
|
|
236572
|
+
const localHashes = (await exec("git", ["rev-list", range$2])).split("\n").filter((line) => line.length > 0);
|
|
236573
|
+
const githubSet = new Set(githubHashes);
|
|
236574
|
+
if (!(localHashes.length === githubHashes.length && localHashes.every((hash$5) => githubSet.has(hash$5)))) throw new Error(`Local commits do not match GitHub for ${range$2}: ${localHashes.length} local, ${githubHashes.length} on GitHub.`);
|
|
236575
|
+
}
|
|
236576
|
+
var prompt_default = "You generate concise, well-structured, easy-to-read release notes from git commit history.\n\n**Audience**: end users, product managers, and non-technical stakeholders — not engineers.\n\n## What to include:\n\n- Include every change a user could notice, however small — new features, fixes, improvements, changed behavior. When in doubt, include it: your job is to translate a change into user terms, not to omit it.\n- Drop a change only when it is purely internal with zero user impact (refactors, renames, formatting, internal tooling, dependency/version bumps). Drop it silently — never mention it, not even as a generic \"internal improvements\" note.\n\n## How to write:\n\n- Describe WHAT changed and WHY it matters, not HOW it is implemented.\n- Write for someone who doesn't know the product is built with code. Use everyday language, framing each change as what the user can now do, see, or no longer worry about.\n- Avoid all technical jargon, library/framework names, and implementation-specific terminology.\n\n## Never expose:\n\n- Internal details: source code, file names or paths, class/function/variable names, API routes, database tables, config keys, dependency or package names, or any other internal identifier. Restate the change in user terms instead — omit it only if it has no user impact at all.\n- Sensitive information: credentials, secrets, tokens, internal URLs, private endpoints, or environment-specific values.\n\n## Output format:\n\n- Group related changes under `##` headings (e.g. \"New Features\", \"Bug Fixes\", \"Improvements\", \"Breaking Changes\"), with each change as a bullet point.\n- Put breaking changes in their own section and explain how users should adapt.\n- Output ONLY the release note content — no preamble, commentary, \"Here is...\", or top-level \"Release Note\" title.\n- Separate sections with `##` headings, never `---`.\n- If and ONLY if every change is purely internal with zero user impact, output exactly `_No notable changes in this release._` and nothing else — never explaining or listing what was dropped. If even one change is user-noticeable, write the note normally.\n";
|
|
236577
|
+
const MAP_CONCURRENCY = 3;
|
|
236578
|
+
async function summarizeChunks(languageModel, instructions, chunks) {
|
|
236579
|
+
const summaries = new Array(chunks.length);
|
|
236580
|
+
let nextChunk = 0;
|
|
236581
|
+
async function worker() {
|
|
236582
|
+
while (nextChunk < chunks.length) {
|
|
236583
|
+
const index$2 = nextChunk++;
|
|
236584
|
+
summaries[index$2] = (await generateText({
|
|
236585
|
+
model: languageModel,
|
|
236586
|
+
instructions,
|
|
236587
|
+
messages: [{
|
|
236588
|
+
role: "user",
|
|
236589
|
+
content: chunks[index$2]
|
|
236590
|
+
}]
|
|
236591
|
+
})).text.trim();
|
|
236592
|
+
}
|
|
236593
|
+
}
|
|
236594
|
+
await Promise.all(Array.from({ length: Math.min(MAP_CONCURRENCY, chunks.length) }, worker));
|
|
236595
|
+
return summaries.map((summary, index$2) => `Part ${index$2 + 1}:\n${summary}`).join("\n\n");
|
|
236596
|
+
}
|
|
236597
|
+
const CHARS_PER_TOKEN$1 = 4;
|
|
236598
|
+
const MIN_TOOL_OUTPUT_TOKENS = 200;
|
|
236599
|
+
const PROMPT_RESERVE_TOKENS$1 = 800;
|
|
236600
|
+
const MIN_CHUNK_BUDGET_TOKENS = 1e3;
|
|
236601
|
+
const MAX_TOC_LINES = 500;
|
|
236602
|
+
const BUDGET_EXHAUSTED = "Size limit reached. Write the release notes with the information you already have.";
|
|
236603
|
+
const NOISE_FILE_EXCLUDES = [
|
|
236604
|
+
":(top,exclude)*.lock",
|
|
236605
|
+
":(top,exclude)*-lock.*",
|
|
236606
|
+
":(top,exclude)*.min.js",
|
|
236607
|
+
":(top,exclude)*.min.mjs",
|
|
236608
|
+
":(top,exclude)*.min.cjs",
|
|
236609
|
+
":(top,exclude)*.min.css",
|
|
236610
|
+
":(top,exclude)*.map"
|
|
236611
|
+
];
|
|
236612
|
+
const diffSummaryPrompt = `You summarize parts of a large git diff for a release notes generator.
|
|
236613
|
+
Describe WHAT changed and any effect a user could notice.
|
|
236614
|
+
Be factual and terse. Do NOT write release notes. Maximum 120 words.`;
|
|
236615
|
+
function parseLsTree(output$3) {
|
|
236616
|
+
return output$3.split("\n").filter((line) => line.length > 0).map((line) => {
|
|
236617
|
+
const tabIdx = line.indexOf(" ");
|
|
236618
|
+
return tabIdx >= 0 ? line.slice(tabIdx + 1) : "";
|
|
236619
|
+
});
|
|
236620
|
+
}
|
|
236621
|
+
function createReleaseTools(languageModel, limits) {
|
|
236622
|
+
let remainingTokens = limits.maxTokens;
|
|
236623
|
+
function spend(text$1) {
|
|
236624
|
+
const maxChars = remainingTokens * CHARS_PER_TOKEN$1;
|
|
236625
|
+
const fitted = text$1.length <= maxChars ? text$1 : `${text$1.slice(0, maxChars)}\n[... truncated to fit the size limit]`;
|
|
236626
|
+
remainingTokens -= estimateTokens(fitted);
|
|
236627
|
+
return fitted;
|
|
236628
|
+
}
|
|
236629
|
+
async function readCommitDiff(ref) {
|
|
236630
|
+
const fullDiff = await exec("git", [
|
|
236631
|
+
"show",
|
|
236632
|
+
"--no-color",
|
|
236633
|
+
"--pretty=format:",
|
|
236634
|
+
ref
|
|
236635
|
+
]);
|
|
236636
|
+
if (estimateTokens(fullDiff) <= remainingTokens) return fullDiff;
|
|
236637
|
+
const tocLines = (await exec("git", [
|
|
236638
|
+
"show",
|
|
236639
|
+
"--no-color",
|
|
236640
|
+
"--pretty=format:",
|
|
236641
|
+
"--numstat",
|
|
236642
|
+
ref
|
|
236643
|
+
])).trim().split("\n");
|
|
236644
|
+
const toc = tocLines.length <= MAX_TOC_LINES ? tocLines.join("\n") : [...tocLines.slice(0, MAX_TOC_LINES), `[... ${tocLines.length - MAX_TOC_LINES} more files]`].join("\n");
|
|
236645
|
+
const header = `This diff is too large to show in full.\n\nChanged files (additions, deletions, path):\n${toc}`;
|
|
236646
|
+
const minimizedDiff = capFileSections(await exec("git", [
|
|
236647
|
+
"show",
|
|
236648
|
+
"--no-color",
|
|
236649
|
+
"--pretty=format:",
|
|
236650
|
+
"--unified=1",
|
|
236651
|
+
ref,
|
|
236652
|
+
"--",
|
|
236653
|
+
":/",
|
|
236654
|
+
...NOISE_FILE_EXCLUDES
|
|
236655
|
+
]), limits.perFileCap);
|
|
236656
|
+
const minimized = `${header}\n\nDiff with less context, generated files excluded, and long files truncated:\n${minimizedDiff}`;
|
|
236657
|
+
if (estimateTokens(minimized) <= remainingTokens) return minimized;
|
|
236658
|
+
return `${header}\n\nSummaries of the diff:\n${await summarizeChunks(languageModel, diffSummaryPrompt, splitDiffIntoChunks(minimizedDiff, Math.max(limits.maxTokens - estimateTokens(toc) - PROMPT_RESERVE_TOKENS$1, MIN_CHUNK_BUDGET_TOKENS)).map((chunk$1) => `All changed files:\n${toc}\n\nChanges (part of a larger diff):\n${chunk$1}`))}`;
|
|
236659
|
+
}
|
|
236660
|
+
return {
|
|
236661
|
+
check_diff: tool({
|
|
236662
|
+
description: "Get the full diff of a specific commit. Returns the patch showing all changes (additions, deletions, modifications) introduced by the commit.",
|
|
236663
|
+
inputSchema: object$3({ commithash: string$5().describe("The commit hash, tag, branch, or any other git ref resolvable by git rev-parse.") }),
|
|
236664
|
+
execute: async ({ commithash }) => {
|
|
236665
|
+
if (remainingTokens < MIN_TOOL_OUTPUT_TOKENS) return { error: BUDGET_EXHAUSTED };
|
|
236666
|
+
return spend(await readCommitDiff(commithash));
|
|
236667
|
+
}
|
|
236668
|
+
}),
|
|
236669
|
+
browse_code: tool({
|
|
236670
|
+
description: "Browse the code at a specific commit. If the path points to a file, returns its content. If the path points to a folder, returns the list of files and sub-folders it contains. Pass an empty string for the path to browse the repository root.",
|
|
236671
|
+
inputSchema: object$3({
|
|
236672
|
+
commithash: string$5().describe("The commit hash, tag, branch, or any other git ref resolvable by git rev-parse."),
|
|
236673
|
+
path: string$5().describe("Path to a file or folder within the repository, relative to the repo root. Use forward slashes. Pass an empty string for the root.")
|
|
236674
|
+
}),
|
|
236675
|
+
execute: async ({ commithash, path: path$4 }) => {
|
|
236676
|
+
if (remainingTokens < MIN_TOOL_OUTPUT_TOKENS) return { error: BUDGET_EXHAUSTED };
|
|
236677
|
+
const normalized = path$4.replace(/^\/+|\/+$/g, "");
|
|
236678
|
+
if (normalized === "") return {
|
|
236679
|
+
type: "folder",
|
|
236680
|
+
items: parseLsTree(spend(await exec("git", ["ls-tree", commithash])))
|
|
236681
|
+
};
|
|
236682
|
+
const ref = `${commithash}:${normalized}`;
|
|
236683
|
+
let objectType$2;
|
|
236684
|
+
try {
|
|
236685
|
+
objectType$2 = (await exec("git", [
|
|
236686
|
+
"cat-file",
|
|
236687
|
+
"-t",
|
|
236688
|
+
ref
|
|
236689
|
+
])).trim();
|
|
236690
|
+
} catch {
|
|
236691
|
+
return { error: "doesn't exists" };
|
|
236692
|
+
}
|
|
236693
|
+
if (objectType$2 === "tree") return {
|
|
236694
|
+
type: "folder",
|
|
236695
|
+
items: parseLsTree(spend(await exec("git", ["ls-tree", ref])))
|
|
236696
|
+
};
|
|
236697
|
+
if (objectType$2 === "blob") return {
|
|
236698
|
+
type: "file",
|
|
236699
|
+
content: spend(await exec("git", ["show", ref]))
|
|
236700
|
+
};
|
|
236701
|
+
return { error: "doesn't exists" };
|
|
236702
|
+
}
|
|
236703
|
+
})
|
|
236704
|
+
};
|
|
236705
|
+
}
|
|
236706
|
+
function capFileSections(diff, perFileCap) {
|
|
236707
|
+
return ("\n" + diff.trimStart()).split("\ndiff --git ").slice(1).map((section) => {
|
|
236708
|
+
const lines = `diff --git ${section}`.split("\n");
|
|
236709
|
+
const bodyLines = lines.length - 1;
|
|
236710
|
+
if (bodyLines <= perFileCap) return lines.join("\n");
|
|
236711
|
+
return [...lines.slice(0, perFileCap + 1), `[... ${bodyLines - perFileCap} more lines truncated]`].join("\n");
|
|
236712
|
+
}).join("\n");
|
|
236713
|
+
}
|
|
236714
|
+
const EMPTY_RELEASE_NOTES = "_No notable changes in this release._";
|
|
236715
|
+
const PROMPT_RESERVE_TOKENS = 800;
|
|
236716
|
+
const CHARS_PER_TOKEN = 4;
|
|
236717
|
+
const commitsSummaryPrompt = `You summarize part of a git commit history for a release notes generator.
|
|
236718
|
+
List every change a user could notice, each with the commit hash(es) it comes from.
|
|
236719
|
+
Skip purely internal changes. Be factual and terse. Do NOT write release notes. Maximum 200 words.`;
|
|
236720
|
+
async function generateReleaseNotes(languageModel, commits, limits) {
|
|
236721
|
+
const commitsContext = await buildCommitsContext(languageModel, commits, limits.maxTokens);
|
|
236722
|
+
return (await generateText({
|
|
236723
|
+
model: languageModel,
|
|
236724
|
+
instructions: prompt_default,
|
|
236725
|
+
tools: createReleaseTools(languageModel, limits),
|
|
236726
|
+
stopWhen: isStepCount(1e3),
|
|
236727
|
+
messages: [{
|
|
236728
|
+
role: "user",
|
|
236729
|
+
content: [{
|
|
236730
|
+
type: "text",
|
|
236731
|
+
text: "Here are the commits related to the release:"
|
|
236732
|
+
}, {
|
|
236733
|
+
type: "text",
|
|
236734
|
+
text: commitsContext
|
|
236735
|
+
}]
|
|
236736
|
+
}]
|
|
236737
|
+
})).text.trim();
|
|
236738
|
+
}
|
|
236739
|
+
async function buildCommitsContext(languageModel, commits, maxTokens) {
|
|
236740
|
+
const fullList = commits.map((commit) => formatCommit(commit, commit.message)).join("\n\n");
|
|
236741
|
+
if (estimateTokens(fullList) <= maxTokens) return fullList;
|
|
236742
|
+
const subjectList = commits.map((commit) => formatCommit(commit, commit.message.split("\n")[0])).join("\n\n");
|
|
236743
|
+
if (estimateTokens(subjectList) <= maxTokens) return `Only commit subjects are shown because the full messages are too large. Use check_diff for details.\n\n${subjectList}`;
|
|
236744
|
+
const chunkBudget = Math.max(maxTokens - PROMPT_RESERVE_TOKENS, 1e3);
|
|
236745
|
+
const chunks = [];
|
|
236746
|
+
let current = "";
|
|
236747
|
+
for (const commit of commits) {
|
|
236748
|
+
const entry = formatCommit(commit, commit.message).slice(0, chunkBudget * CHARS_PER_TOKEN);
|
|
236749
|
+
if (current.length > 0 && estimateTokens(`${current}\n\n${entry}`) > chunkBudget) {
|
|
236750
|
+
chunks.push(current);
|
|
236751
|
+
current = "";
|
|
236752
|
+
}
|
|
236753
|
+
current = current.length === 0 ? entry : `${current}\n\n${entry}`;
|
|
236754
|
+
}
|
|
236755
|
+
if (current.length > 0) chunks.push(current);
|
|
236756
|
+
const summaries = await summarizeChunks(languageModel, commitsSummaryPrompt, chunks);
|
|
236757
|
+
return `There are ${commits.length} commits, too many to show in full, so here are summaries of them. Use check_diff for details.\n\n${summaries}`;
|
|
236758
|
+
}
|
|
236759
|
+
function formatCommit(commit, message) {
|
|
236760
|
+
return [
|
|
236761
|
+
`- Commithash: ${commit.hash}`,
|
|
236762
|
+
` - > ${message.replaceAll("\n", "\n > ")}`,
|
|
236763
|
+
` - Timestamp: ${commit.date}`,
|
|
236764
|
+
` - Author: ${commit.authorName} <${commit.authorEmail}>`
|
|
236765
|
+
].join("\n");
|
|
236766
|
+
}
|
|
236767
|
+
const renderReleaseNotes = createRenderer({
|
|
236768
|
+
width: 80,
|
|
236769
|
+
listIndent: 2
|
|
236770
|
+
});
|
|
236771
|
+
async function releaseController(tagArg, options = {}) {
|
|
236772
|
+
if (options.bump && tagArg !== void 0) throw new Error(`Use either a release tag or --${options.bump}, not both.`);
|
|
236773
|
+
if (options.yolo && tagArg === void 0 && !options.bump) throw new Error("A release tag or --major, --minor, or --patch is required with --yolo.");
|
|
236774
|
+
const config$5 = await loadConfig(await getRepoRoot());
|
|
236775
|
+
const languageModel = resolveLanguageModel(resolveModelConfig(config$5.models, options.model ?? config$5.releaseModel ?? config$5.model));
|
|
236776
|
+
const releases = await listReleases(100);
|
|
236777
|
+
let tag = tagArg?.trim() ?? "";
|
|
236778
|
+
if (options.bump) {
|
|
236779
|
+
tag = bumpVersionTag(releases.slice(0, 3).map((release) => release.tagName), options.bump);
|
|
236780
|
+
console.log(source_default.dim(`Release tag: ${tag}`));
|
|
236781
|
+
} else if (tagArg === void 0) {
|
|
236782
|
+
printRecentReleases(releases.slice(0, 5).reverse());
|
|
236783
|
+
tag = (await dist_default$1({
|
|
236784
|
+
message: "Release tag",
|
|
236785
|
+
theme: selectionTheme,
|
|
236786
|
+
validate: (value) => value.trim().length > 0 || "Release tag cannot be empty."
|
|
236787
|
+
})).trim();
|
|
236788
|
+
}
|
|
236789
|
+
if (tag.length === 0) throw new Error("Release tag cannot be empty.");
|
|
236790
|
+
const exists$2 = await releaseExists(tag);
|
|
236791
|
+
if (exists$2 && !options.force) {
|
|
236792
|
+
if (!await dist_default({
|
|
236793
|
+
message: source_default.yellow(`Release ${tag} already exists. Recreate it?`),
|
|
236794
|
+
default: false,
|
|
236795
|
+
theme: selectionTheme
|
|
236796
|
+
})) {
|
|
236797
|
+
console.log("Release creation cancelled.");
|
|
236798
|
+
return;
|
|
236799
|
+
}
|
|
236800
|
+
}
|
|
236801
|
+
const branch = await getDefaultBranch();
|
|
236802
|
+
if (branch.length === 0) throw new Error("Could not determine the default branch.");
|
|
236803
|
+
const tagIndex = releases.findIndex((release) => release.tagName === tag);
|
|
236804
|
+
const previousTag = (tagIndex === -1 ? releases[0] : releases[tagIndex + 1])?.tagName ?? null;
|
|
236805
|
+
const headSha = await getBranchHeadSha(branch);
|
|
236806
|
+
const commits = await runWithLoading("Loading commits from GitHub", () => listReleaseCommits(previousTag, headSha));
|
|
236807
|
+
await runWithLoading("Fetching commits", () => fetchBranchAndTags(branch));
|
|
236808
|
+
await assertLocalCommitsMatch(previousTag, headSha, commits.map((commit) => commit.hash));
|
|
236809
|
+
console.log(source_default.dim(`${commits.length} commit(s) since ${previousTag ?? "the first commit"}`));
|
|
236810
|
+
let notes = "";
|
|
236811
|
+
while (true) {
|
|
236812
|
+
notes = commits.length === 0 ? EMPTY_RELEASE_NOTES : await runWithLoading("Generating release notes", () => generateReleaseNotes(languageModel, commits, {
|
|
236813
|
+
maxTokens: config$5.maxDiffTokens ?? DEFAULT_MAX_DIFF_TOKENS,
|
|
236814
|
+
perFileCap: config$5.perFileCap ?? DEFAULT_PER_FILE_CAP
|
|
236815
|
+
}));
|
|
236816
|
+
console.log(renderReleaseNotes(notes).trim());
|
|
236817
|
+
console.log("");
|
|
236818
|
+
if (options.yolo || await acceptGenerated(`Create release ${source_default.cyan.bold(tag)} on ${source_default.yellow.bold(branch)}`, "generate new release notes")) break;
|
|
236819
|
+
}
|
|
236820
|
+
if (notes.length === 0) throw new Error("The selected model returned empty release notes.");
|
|
236821
|
+
if (exists$2) await deleteRelease(tag);
|
|
236822
|
+
await createRelease({
|
|
236823
|
+
tag,
|
|
236824
|
+
target: headSha,
|
|
236825
|
+
notes
|
|
236826
|
+
});
|
|
236827
|
+
}
|
|
236828
|
+
function bumpVersionTag(recentTags, bump) {
|
|
236829
|
+
const versionPattern = /^(v?)(\d+)\.(\d+)\.(\d+)$/;
|
|
236830
|
+
const invalidTags = recentTags.filter((tag) => !versionPattern.test(tag));
|
|
236831
|
+
if (invalidTags.length > 0) throw new Error(`Recent release tags must be MAJOR.MINOR.PATCH versions to use --${bump}: ${invalidTags.join(", ")}`);
|
|
236832
|
+
const match = versionPattern.exec(recentTags[0] ?? "");
|
|
236833
|
+
if (!match) throw new Error(`No previous release found to apply --${bump} to.`);
|
|
236834
|
+
const prefix = match[1];
|
|
236835
|
+
const major = Number(match[2]);
|
|
236836
|
+
const minor = Number(match[3]);
|
|
236837
|
+
const patch = Number(match[4]);
|
|
236838
|
+
if (bump === "major") return `${prefix}${major + 1}.0.0`;
|
|
236839
|
+
if (bump === "minor") return `${prefix}${major}.${minor + 1}.0`;
|
|
236840
|
+
if (bump === "patch") return `${prefix}${major}.${minor}.${patch + 1}`;
|
|
236841
|
+
throw new Error(`Unknown version bump '${bump}'.`);
|
|
236842
|
+
}
|
|
236843
|
+
function printRecentReleases(releases) {
|
|
236844
|
+
if (releases.length === 0) return;
|
|
236845
|
+
const longestTag = Math.max(...releases.map((release) => release.tagName.length));
|
|
236846
|
+
console.log(source_default.blue.bold("Recent:"));
|
|
236847
|
+
for (const release of releases) {
|
|
236848
|
+
const age = prettyMilliseconds(Date.now() - Date.parse(release.publishedAt), {
|
|
236849
|
+
verbose: true,
|
|
236850
|
+
unitCount: 1
|
|
236851
|
+
});
|
|
236852
|
+
console.log(source_default.cyan(release.tagName.padEnd(longestTag)), source_default.dim(`${age} ago`));
|
|
236853
|
+
}
|
|
236854
|
+
console.log("");
|
|
236325
236855
|
}
|
|
236326
236856
|
async function showStylesController() {
|
|
236327
236857
|
const config$5 = await loadConfig();
|
|
@@ -236358,26 +236888,65 @@ function handleError(fn$1) {
|
|
|
236358
236888
|
}
|
|
236359
236889
|
});
|
|
236360
236890
|
}
|
|
236361
|
-
const app = new Command().enablePositionalOptions().name("gityo").description("Stage changes, generate a commit message, create a commit, and run a post-commit git command.").option("-i, --input <input>", "Use the provided input as the commit message.").option("-s, --style <style>", "Commit message style key to use (defaults to \"default\").").option("
|
|
236891
|
+
const app = new Command().enablePositionalOptions().name("gityo").description("Stage changes, generate a commit message, create a commit, and run a post-commit git command.").option("-i, --input <input>", "Use the provided input as the commit message.").option("-s, --style <style>", "Commit message style key to use (defaults to \"default\").").option("--model <model>", "Model key from config to use (overrides the configured model).").option("-g, --generate", "Generate a commit message and commit without asking.").option("-p, --post", "Run the post-commit git command without asking.").option("-S, --staged", "Commit only staged files. Fails if nothing is staged.").option("-A, --all", "Commit all changes, not just staged files.").option("-y, --yolo", "Skip all questions, and generate message, commit, run post command. [Will fail if no model available]").version(`v${version$4}`, "-v, --version", "Show the current version.").action((options) => {
|
|
236362
236892
|
if (options.generate && options.input) {
|
|
236363
236893
|
console.error("Cannot use --generate and --input together.");
|
|
236364
236894
|
process.exit(1);
|
|
236365
236895
|
}
|
|
236366
|
-
|
|
236896
|
+
if (options.staged && options.all) {
|
|
236897
|
+
console.error("Cannot use --staged and --all together.");
|
|
236898
|
+
process.exit(1);
|
|
236899
|
+
}
|
|
236900
|
+
handleError(() => mainController({
|
|
236901
|
+
...options,
|
|
236902
|
+
scope: options.all ? "everything" : options.staged ? "staged-only" : "staged-or-changes"
|
|
236903
|
+
}));
|
|
236367
236904
|
});
|
|
236368
|
-
app.command("commit").description("Stage changes, generate a commit message, create a commit, and run a post-commit git command.").option("-i, --input <input>", "Use the provided input as the commit message.").option("-s, --style <style>", "Commit message style key to use (defaults to \"default\").").option("
|
|
236905
|
+
app.command("commit").description("Stage changes, generate a commit message, create a commit, and run a post-commit git command.").option("-i, --input <input>", "Use the provided input as the commit message.").option("-s, --style <style>", "Commit message style key to use (defaults to \"default\").").option("--model <model>", "Model key from config to use (overrides the configured model).").option("-g, --generate", "Generate a commit message and commit without asking.").option("-p, --post", "Run the post-commit git command without asking.").option("-S, --staged", "Commit only staged files. Fails if nothing is staged.").option("-A, --all", "Commit all changes, not just staged files.").option("-y, --yolo", "Skip all questions, and generate message, commit, run post command. [Will fail if no model available]").action((options) => {
|
|
236369
236906
|
if (options.generate && options.input) {
|
|
236370
236907
|
console.error("Cannot use --generate and --input together.");
|
|
236371
236908
|
process.exit(1);
|
|
236372
236909
|
}
|
|
236373
|
-
|
|
236910
|
+
if (options.staged && options.all) {
|
|
236911
|
+
console.error("Cannot use --staged and --all together.");
|
|
236912
|
+
process.exit(1);
|
|
236913
|
+
}
|
|
236914
|
+
handleError(() => mainController({
|
|
236915
|
+
...options,
|
|
236916
|
+
scope: options.all ? "everything" : options.staged ? "staged-only" : "staged-or-changes"
|
|
236917
|
+
}));
|
|
236374
236918
|
});
|
|
236375
236919
|
const pr = app.command("pr").description("Create and merge GitHub pull requests.");
|
|
236376
|
-
pr.command("create").description("Create a pull request with an AI-generated title and body.").argument("[base-branch]", "Base branch to merge into (defaults to the repository default branch).").argument("[head-branch]", "Head branch with the changes (defaults to the current branch).").option("
|
|
236920
|
+
pr.command("create").description("Create a pull request with an AI-generated title and body.").argument("[base-branch]", "Base branch to merge into (defaults to the repository default branch).").argument("[head-branch]", "Head branch with the changes (defaults to the current branch).").option("--model <model>", "Model key from config to use (overrides the configured model).").option("-t, --title-style <style>", "Title style key to use (defaults to \"default\").").option("-b, --body-style <style>", "Body style key to use (defaults to \"default\").").option("-y, --yolo", "Create the pull request without asking for confirmation.").option("-A, --all", "Commit all local changes, not just staged files, before continuing.").option("-w, --web", "Open the pull request in a browser.").action((baseBranch, headBranch, options) => {
|
|
236377
236921
|
handleError(() => createPullRequestController(baseBranch, headBranch, options));
|
|
236378
236922
|
});
|
|
236379
|
-
pr.command("merge").description("Create the pull request if needed, then merge it.").argument("[base-branch]", "Base branch to merge into (defaults to the repository default branch).").argument("[head-branch]", "Head branch with the changes (defaults to the current branch).").option("
|
|
236380
|
-
|
|
236923
|
+
pr.command("merge").description("Create the pull request if needed, then merge it.").argument("[base-branch]", "Base branch to merge into (defaults to the repository default branch).").argument("[head-branch]", "Head branch with the changes (defaults to the current branch).").option("--model <model>", "Model key from config to use (overrides the configured model).").option("-t, --title-style <style>", "Title style key to use (defaults to \"default\").").option("-b, --body-style <style>", "Body style key to use (defaults to \"default\").").option("-y, --yolo", "Merge the pull request without asking for confirmation.").option("-A, --all", "Commit all local changes, not just staged files, before continuing.").option("-m, --merge", "Merge the commits with the base branch.").option("-r, --rebase", "Rebase the commits onto the base branch.").option("-s, --squash", "Squash the commits into one commit.").action((baseBranch, headBranch, options) => {
|
|
236924
|
+
if ([
|
|
236925
|
+
options.merge,
|
|
236926
|
+
options.rebase,
|
|
236927
|
+
options.squash
|
|
236928
|
+
].filter(Boolean).length > 1) {
|
|
236929
|
+
console.error("Use only one of --merge, --rebase, or --squash.");
|
|
236930
|
+
process.exit(1);
|
|
236931
|
+
}
|
|
236932
|
+
handleError(() => mergePullRequestController(baseBranch, headBranch, {
|
|
236933
|
+
...options,
|
|
236934
|
+
mergeMethod: options.merge ? "merge" : options.rebase ? "rebase" : options.squash ? "squash" : void 0
|
|
236935
|
+
}));
|
|
236936
|
+
});
|
|
236937
|
+
app.command("release").description("Create a GitHub release with AI-generated release notes.").argument("[tag]", "Release tag to create (asks when omitted).").option("--model <model>", "Model key from config to use (overrides the configured model).").option("-y, --yolo", "Create the release without asking for confirmation (requires a tag).").option("-f, --force", "Recreate the release without asking if it already exists.").option("--major", "Bump the major version of the latest release.").option("--minor", "Bump the minor version of the latest release.").option("--patch", "Bump the patch version of the latest release.").action((tag, options) => {
|
|
236938
|
+
if ([
|
|
236939
|
+
options.major,
|
|
236940
|
+
options.minor,
|
|
236941
|
+
options.patch
|
|
236942
|
+
].filter(Boolean).length > 1) {
|
|
236943
|
+
console.error("Use only one of --major, --minor, or --patch.");
|
|
236944
|
+
process.exit(1);
|
|
236945
|
+
}
|
|
236946
|
+
handleError(() => releaseController(tag, {
|
|
236947
|
+
...options,
|
|
236948
|
+
bump: options.major ? "major" : options.minor ? "minor" : options.patch ? "patch" : void 0
|
|
236949
|
+
}));
|
|
236381
236950
|
});
|
|
236382
236951
|
app.command("config").description("Show where to manage your gityo configuration.").action(() => {
|
|
236383
236952
|
handleError(showConfigController);
|