@eldrforge/ai-service 0.1.14 → 0.1.16
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 +1383 -140
- package/dist/index.js.map +1 -1
- package/dist/src/agentic/commit.d.ts +6 -0
- package/dist/src/agentic/commit.d.ts.map +1 -1
- package/dist/src/agentic/executor.d.ts +7 -1
- package/dist/src/agentic/executor.d.ts.map +1 -1
- package/dist/src/agentic/publish.d.ts +31 -0
- package/dist/src/agentic/publish.d.ts.map +1 -0
- package/dist/src/agentic/release.d.ts +6 -0
- package/dist/src/agentic/release.d.ts.map +1 -1
- package/dist/src/ai.d.ts.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/observability/conversation-logger.d.ts +53 -0
- package/dist/src/observability/conversation-logger.d.ts.map +1 -0
- package/dist/src/observability/index.d.ts +15 -0
- package/dist/src/observability/index.d.ts.map +1 -0
- package/dist/src/observability/metrics.d.ts +53 -0
- package/dist/src/observability/metrics.d.ts.map +1 -0
- package/dist/src/observability/reflection.d.ts +36 -0
- package/dist/src/observability/reflection.d.ts.map +1 -0
- package/dist/src/prompts/commit.d.ts.map +1 -1
- package/dist/src/prompts/index.d.ts +1 -0
- package/dist/src/prompts/index.d.ts.map +1 -1
- package/dist/src/prompts/release.d.ts.map +1 -1
- package/dist/src/prompts/review.d.ts.map +1 -1
- package/dist/src/prompts/templates.d.ts +22 -0
- package/dist/src/prompts/templates.d.ts.map +1 -0
- package/dist/src/tools/publish-tools.d.ts +6 -0
- package/dist/src/tools/publish-tools.d.ts.map +1 -0
- package/dist/src/tools/types.d.ts +17 -0
- package/dist/src/tools/types.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { OpenAI } from "openai";
|
|
2
|
-
import { safeJsonParse, run } from "@eldrforge/git-tools";
|
|
2
|
+
import { safeJsonParse, run, localBranchExists, isBranchInSyncWithRemote, safeSyncBranchWithRemote } from "@eldrforge/git-tools";
|
|
3
3
|
import fs$1 from "fs";
|
|
4
4
|
import { spawnSync } from "child_process";
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import path__default from "path";
|
|
7
7
|
import * as os from "os";
|
|
8
8
|
import * as fs from "fs/promises";
|
|
9
|
-
import {
|
|
9
|
+
import { registerTemplates, cook, ConversationBuilder, generateToolGuidance, ConversationLogger, MetricsCollector } from "@riotprompt/riotprompt";
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
11
|
let logger;
|
|
12
12
|
function setLogger(customLogger) {
|
|
@@ -114,7 +114,10 @@ async function createCompletion(messages, options = { model: "gpt-4o-mini" }) {
|
|
|
114
114
|
if (!apiKey) {
|
|
115
115
|
throw new OpenAIError("OPENAI_API_KEY environment variable is not set");
|
|
116
116
|
}
|
|
117
|
-
const timeoutMs = parseInt(process.env.OPENAI_TIMEOUT_MS || "300000");
|
|
117
|
+
const timeoutMs = parseInt(process.env.OPENAI_TIMEOUT_MS || "300000", 10);
|
|
118
|
+
if (isNaN(timeoutMs) || timeoutMs <= 0) {
|
|
119
|
+
throw new OpenAIError("Invalid OPENAI_TIMEOUT_MS value - must be a positive number");
|
|
120
|
+
}
|
|
118
121
|
openai = new OpenAI({
|
|
119
122
|
apiKey,
|
|
120
123
|
timeout: timeoutMs
|
|
@@ -157,8 +160,9 @@ async function createCompletion(messages, options = { model: "gpt-4o-mini" }) {
|
|
|
157
160
|
const completionPromise = openai.chat.completions.create(apiOptions);
|
|
158
161
|
let timeoutId = null;
|
|
159
162
|
const timeoutPromise = new Promise((_, reject) => {
|
|
160
|
-
const timeoutMs2 = parseInt(process.env.OPENAI_TIMEOUT_MS || "300000");
|
|
161
|
-
|
|
163
|
+
const timeoutMs2 = parseInt(process.env.OPENAI_TIMEOUT_MS || "300000", 10);
|
|
164
|
+
const validTimeout = isNaN(timeoutMs2) || timeoutMs2 <= 0 ? 3e5 : timeoutMs2;
|
|
165
|
+
timeoutId = setTimeout(() => reject(new OpenAIError(`OpenAI API call timed out after ${validTimeout / 1e3} seconds`)), validTimeout);
|
|
162
166
|
});
|
|
163
167
|
let progressIntervalId = null;
|
|
164
168
|
progressIntervalId = setInterval(() => {
|
|
@@ -710,6 +714,31 @@ function requireTTY(errorMessage = "Interactive mode requires a terminal. Use --
|
|
|
710
714
|
throw new Error(errorMessage);
|
|
711
715
|
}
|
|
712
716
|
}
|
|
717
|
+
const initializeTemplates = () => {
|
|
718
|
+
const templates = {
|
|
719
|
+
// Commit message generation template
|
|
720
|
+
"commit": {
|
|
721
|
+
persona: { path: "personas/you.md" },
|
|
722
|
+
instructions: [{ path: "instructions/commit.md" }]
|
|
723
|
+
},
|
|
724
|
+
// Release notes generation template
|
|
725
|
+
"release": {
|
|
726
|
+
persona: { path: "personas/releaser.md" },
|
|
727
|
+
instructions: [{ path: "instructions/release.md" }]
|
|
728
|
+
},
|
|
729
|
+
// Code review template
|
|
730
|
+
"review": {
|
|
731
|
+
persona: { path: "personas/you.md" },
|
|
732
|
+
instructions: [{ path: "instructions/review.md" }]
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
registerTemplates(templates);
|
|
736
|
+
};
|
|
737
|
+
const TemplateNames = {
|
|
738
|
+
COMMIT: "commit",
|
|
739
|
+
RELEASE: "release",
|
|
740
|
+
REVIEW: "review"
|
|
741
|
+
};
|
|
713
742
|
const __filename$3 = fileURLToPath(import.meta.url);
|
|
714
743
|
const __dirname$3 = path__default.dirname(__filename$3);
|
|
715
744
|
const createCommitPrompt = async ({ overridePaths: _overridePaths, overrides: _overrides }, { diffContent, userDirection, isFileContent, githubIssuesContext }, { logContext, context, directories } = {}) => {
|
|
@@ -735,7 +764,14 @@ const createCommitPrompt = async ({ overridePaths: _overridePaths, overrides: _o
|
|
|
735
764
|
if (directories && directories.length > 0) {
|
|
736
765
|
contextItems.push({ directories, title: "Directories" });
|
|
737
766
|
}
|
|
738
|
-
return
|
|
767
|
+
return cook({
|
|
768
|
+
basePath,
|
|
769
|
+
template: TemplateNames.COMMIT,
|
|
770
|
+
overridePaths: _overridePaths ?? [],
|
|
771
|
+
overrides: _overrides ?? true,
|
|
772
|
+
content: contentItems,
|
|
773
|
+
context: contextItems
|
|
774
|
+
});
|
|
739
775
|
};
|
|
740
776
|
const __filename$2 = fileURLToPath(import.meta.url);
|
|
741
777
|
const __dirname$2 = path__default.dirname(__filename$2);
|
|
@@ -780,7 +816,14 @@ const createReleasePrompt = async ({ overrides: _overrides, overridePaths: _over
|
|
|
780
816
|
if (directories && directories.length > 0) {
|
|
781
817
|
contextItems.push({ directories, title: "Directories" });
|
|
782
818
|
}
|
|
783
|
-
const prompt = await
|
|
819
|
+
const prompt = await cook({
|
|
820
|
+
basePath,
|
|
821
|
+
template: TemplateNames.RELEASE,
|
|
822
|
+
overridePaths: _overridePaths ?? [],
|
|
823
|
+
overrides: _overrides ?? true,
|
|
824
|
+
content: contentItems,
|
|
825
|
+
context: contextItems
|
|
826
|
+
});
|
|
784
827
|
return {
|
|
785
828
|
prompt,
|
|
786
829
|
maxTokens,
|
|
@@ -814,7 +857,14 @@ const createReviewPrompt = async ({ overridePaths: _overridePaths, overrides: _o
|
|
|
814
857
|
if (directories && directories.length > 0) {
|
|
815
858
|
contextItems.push({ directories, title: "Directories" });
|
|
816
859
|
}
|
|
817
|
-
return
|
|
860
|
+
return cook({
|
|
861
|
+
basePath,
|
|
862
|
+
template: TemplateNames.REVIEW,
|
|
863
|
+
overridePaths: _overridePaths ?? [],
|
|
864
|
+
overrides: _overrides ?? true,
|
|
865
|
+
content: contentItems,
|
|
866
|
+
context: contextItems
|
|
867
|
+
});
|
|
818
868
|
};
|
|
819
869
|
class AgenticExecutor {
|
|
820
870
|
logger;
|
|
@@ -823,7 +873,7 @@ class AgenticExecutor {
|
|
|
823
873
|
this.logger = logger2;
|
|
824
874
|
}
|
|
825
875
|
/**
|
|
826
|
-
* Run the agentic loop
|
|
876
|
+
* Run the agentic loop with ConversationBuilder
|
|
827
877
|
*/
|
|
828
878
|
async run(config) {
|
|
829
879
|
const {
|
|
@@ -836,15 +886,33 @@ class AgenticExecutor {
|
|
|
836
886
|
debugResponseFile,
|
|
837
887
|
storage,
|
|
838
888
|
logger: logger2,
|
|
839
|
-
openaiReasoning = false
|
|
889
|
+
openaiReasoning = false,
|
|
890
|
+
tokenBudget
|
|
840
891
|
} = config;
|
|
841
|
-
const
|
|
892
|
+
const conversation = ConversationBuilder.create({ model }, logger2);
|
|
893
|
+
for (const msg of initialMessages) {
|
|
894
|
+
if (msg.role === "system") {
|
|
895
|
+
conversation.addSystemMessage(msg.content);
|
|
896
|
+
} else if (msg.role === "user") {
|
|
897
|
+
conversation.addUserMessage(msg.content);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (tokenBudget) {
|
|
901
|
+
this.log("Configuring token budget", tokenBudget);
|
|
902
|
+
conversation.withTokenBudget({
|
|
903
|
+
max: tokenBudget.max,
|
|
904
|
+
reserveForResponse: tokenBudget.reserveForResponse || 4e3,
|
|
905
|
+
strategy: tokenBudget.strategy || "fifo",
|
|
906
|
+
onBudgetExceeded: tokenBudget.onBudgetExceeded || "compress"
|
|
907
|
+
});
|
|
908
|
+
}
|
|
842
909
|
let iterations = 0;
|
|
843
910
|
let toolCallsExecuted = 0;
|
|
844
|
-
this.log("Starting agentic loop", { maxIterations, toolCount: tools.count() });
|
|
911
|
+
this.log("Starting agentic loop with ConversationBuilder", { maxIterations, toolCount: tools.count() });
|
|
845
912
|
while (iterations < maxIterations) {
|
|
846
913
|
iterations++;
|
|
847
914
|
this.log(`Iteration ${iterations}/${maxIterations}`);
|
|
915
|
+
const messages = conversation.toMessages();
|
|
848
916
|
const response = await createCompletionWithRetry(
|
|
849
917
|
messages,
|
|
850
918
|
{
|
|
@@ -863,23 +931,16 @@ class AgenticExecutor {
|
|
|
863
931
|
if (toolCalls.length === 0) {
|
|
864
932
|
const finalContent = message.content || "";
|
|
865
933
|
this.log("Agent completed without tool calls", { iterations, toolCallsExecuted });
|
|
866
|
-
|
|
867
|
-
role: "assistant",
|
|
868
|
-
content: finalContent
|
|
869
|
-
});
|
|
934
|
+
conversation.addAssistantMessage(finalContent);
|
|
870
935
|
return {
|
|
871
936
|
finalMessage: finalContent,
|
|
872
937
|
iterations,
|
|
873
938
|
toolCallsExecuted,
|
|
874
|
-
conversationHistory:
|
|
939
|
+
conversationHistory: conversation.toMessages(),
|
|
875
940
|
toolMetrics: this.toolMetrics
|
|
876
941
|
};
|
|
877
942
|
}
|
|
878
|
-
|
|
879
|
-
role: "assistant",
|
|
880
|
-
content: message.content || null,
|
|
881
|
-
tool_calls: toolCalls
|
|
882
|
-
});
|
|
943
|
+
conversation.addAssistantWithToolCalls(message.content || null, toolCalls);
|
|
883
944
|
this.log(`Executing ${toolCalls.length} tool call(s)`);
|
|
884
945
|
for (const toolCall of toolCalls) {
|
|
885
946
|
const startTime = Date.now();
|
|
@@ -889,14 +950,16 @@ class AgenticExecutor {
|
|
|
889
950
|
if (this.logger?.info) {
|
|
890
951
|
this.logger.info(`🔧 Running tool: ${toolName}`);
|
|
891
952
|
}
|
|
892
|
-
|
|
953
|
+
let args;
|
|
954
|
+
try {
|
|
955
|
+
args = JSON.parse(toolCall.function.arguments);
|
|
956
|
+
} catch (parseError) {
|
|
957
|
+
throw new Error(`Failed to parse tool arguments: ${parseError.message}`);
|
|
958
|
+
}
|
|
893
959
|
const result = await tools.execute(toolName, args);
|
|
894
960
|
const duration = Date.now() - startTime;
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
tool_call_id: toolCall.id,
|
|
898
|
-
content: this.formatToolResult({ id: toolCall.id, name: toolName, result })
|
|
899
|
-
});
|
|
961
|
+
const formattedResult = this.formatToolResult({ id: toolCall.id, name: toolName, result });
|
|
962
|
+
conversation.addToolResult(toolCall.id, formattedResult, toolName);
|
|
900
963
|
toolCallsExecuted++;
|
|
901
964
|
this.toolMetrics.push({
|
|
902
965
|
name: toolName,
|
|
@@ -924,22 +987,16 @@ class AgenticExecutor {
|
|
|
924
987
|
if (this.logger?.warn) {
|
|
925
988
|
this.logger.warn(`❌ Tool ${toolName} failed: ${errorMessage}`);
|
|
926
989
|
}
|
|
927
|
-
|
|
928
|
-
role: "tool",
|
|
929
|
-
tool_call_id: toolCall.id,
|
|
930
|
-
content: `Tool execution failed: ${errorMessage}`
|
|
931
|
-
});
|
|
990
|
+
conversation.addToolResult(toolCall.id, `Tool execution failed: ${errorMessage}`, toolName);
|
|
932
991
|
}
|
|
933
992
|
}
|
|
934
993
|
this.log(`Completed tool execution`, { toolCallsExecuted });
|
|
935
994
|
}
|
|
936
995
|
this.log("Max iterations reached, forcing completion", { iterations, toolCallsExecuted });
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
content: "Please provide your final analysis and commit message based on your investigation. Do not request any more tools."
|
|
940
|
-
});
|
|
996
|
+
conversation.addUserMessage("Please provide your final analysis based on your investigation. Do not request any more tools.");
|
|
997
|
+
const finalMessages = conversation.toMessages();
|
|
941
998
|
const finalResponse = await createCompletionWithRetry(
|
|
942
|
-
|
|
999
|
+
finalMessages,
|
|
943
1000
|
{
|
|
944
1001
|
model,
|
|
945
1002
|
openaiReasoning: openaiReasoning || void 0,
|
|
@@ -948,15 +1005,12 @@ class AgenticExecutor {
|
|
|
948
1005
|
logger: logger2
|
|
949
1006
|
}
|
|
950
1007
|
);
|
|
951
|
-
|
|
952
|
-
role: "assistant",
|
|
953
|
-
content: finalResponse
|
|
954
|
-
});
|
|
1008
|
+
conversation.addAssistantMessage(finalResponse);
|
|
955
1009
|
return {
|
|
956
1010
|
finalMessage: finalResponse,
|
|
957
1011
|
iterations,
|
|
958
1012
|
toolCallsExecuted,
|
|
959
|
-
conversationHistory:
|
|
1013
|
+
conversationHistory: conversation.toMessages(),
|
|
960
1014
|
toolMetrics: this.toolMetrics
|
|
961
1015
|
};
|
|
962
1016
|
}
|
|
@@ -1096,6 +1150,20 @@ function createGetFileHistoryTool$1() {
|
|
|
1096
1150
|
return {
|
|
1097
1151
|
name: "get_file_history",
|
|
1098
1152
|
description: "Get git commit history for one or more files to understand their evolution and past changes",
|
|
1153
|
+
category: "Understanding",
|
|
1154
|
+
cost: "cheap",
|
|
1155
|
+
examples: [
|
|
1156
|
+
{
|
|
1157
|
+
scenario: "Check if file has recent refactoring",
|
|
1158
|
+
params: { filePaths: ["src/auth.ts"], limit: 5 },
|
|
1159
|
+
expectedResult: "List of recent commits affecting auth.ts"
|
|
1160
|
+
},
|
|
1161
|
+
{
|
|
1162
|
+
scenario: "Understand evolution of multiple related files",
|
|
1163
|
+
params: { filePaths: ["src/user.ts", "src/auth.ts"], limit: 10, format: "detailed" },
|
|
1164
|
+
expectedResult: "Detailed commit history for both files"
|
|
1165
|
+
}
|
|
1166
|
+
],
|
|
1099
1167
|
parameters: {
|
|
1100
1168
|
type: "object",
|
|
1101
1169
|
properties: {
|
|
@@ -1137,6 +1205,20 @@ function createGetFileContentTool$1() {
|
|
|
1137
1205
|
return {
|
|
1138
1206
|
name: "get_file_content",
|
|
1139
1207
|
description: "Get the complete current content of a file to understand context around changes. Returns a message if the file does not exist (e.g., if it was deleted).",
|
|
1208
|
+
category: "Understanding",
|
|
1209
|
+
cost: "moderate",
|
|
1210
|
+
examples: [
|
|
1211
|
+
{
|
|
1212
|
+
scenario: "See full class definition for modified method",
|
|
1213
|
+
params: { filePath: "src/services/auth.ts", includeLineNumbers: true },
|
|
1214
|
+
expectedResult: "Complete file content with line numbers"
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
scenario: "Check imports and structure",
|
|
1218
|
+
params: { filePath: "src/utils/helpers.ts" },
|
|
1219
|
+
expectedResult: "Full file content showing imports and exports"
|
|
1220
|
+
}
|
|
1221
|
+
],
|
|
1140
1222
|
parameters: {
|
|
1141
1223
|
type: "object",
|
|
1142
1224
|
properties: {
|
|
@@ -1180,6 +1262,20 @@ function createSearchCodebaseTool$1() {
|
|
|
1180
1262
|
return {
|
|
1181
1263
|
name: "search_codebase",
|
|
1182
1264
|
description: "Search for code patterns, function names, or text across the codebase using git grep",
|
|
1265
|
+
category: "Analysis",
|
|
1266
|
+
cost: "moderate",
|
|
1267
|
+
examples: [
|
|
1268
|
+
{
|
|
1269
|
+
scenario: "Find all uses of a renamed function",
|
|
1270
|
+
params: { query: "oldFunctionName", fileTypes: ["ts", "tsx"] },
|
|
1271
|
+
expectedResult: "List of files and lines containing the function"
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
scenario: "Check if pattern exists elsewhere",
|
|
1275
|
+
params: { query: "pattern.*string", contextLines: 3 },
|
|
1276
|
+
expectedResult: "Matching lines with surrounding context"
|
|
1277
|
+
}
|
|
1278
|
+
],
|
|
1183
1279
|
parameters: {
|
|
1184
1280
|
type: "object",
|
|
1185
1281
|
properties: {
|
|
@@ -1224,6 +1320,9 @@ function createGetRelatedTestsTool$1() {
|
|
|
1224
1320
|
return {
|
|
1225
1321
|
name: "get_related_tests",
|
|
1226
1322
|
description: "Find test files related to production files to understand what the code is supposed to do",
|
|
1323
|
+
category: "Understanding",
|
|
1324
|
+
cost: "cheap",
|
|
1325
|
+
examples: [{ scenario: "Find tests for modified service", params: { filePaths: ["src/services/auth.ts"] }, expectedResult: "List of related test files" }],
|
|
1227
1326
|
parameters: {
|
|
1228
1327
|
type: "object",
|
|
1229
1328
|
properties: {
|
|
@@ -1270,6 +1369,9 @@ function createGetFileDependenciesTool$1() {
|
|
|
1270
1369
|
return {
|
|
1271
1370
|
name: "get_file_dependencies",
|
|
1272
1371
|
description: "Find which files import or depend on the changed files to assess change impact",
|
|
1372
|
+
category: "Analysis",
|
|
1373
|
+
cost: "moderate",
|
|
1374
|
+
examples: [{ scenario: "Check impact of util function change", params: { filePaths: ["src/utils/format.ts"] }, expectedResult: "Files that import format.ts" }],
|
|
1273
1375
|
parameters: {
|
|
1274
1376
|
type: "object",
|
|
1275
1377
|
properties: {
|
|
@@ -1313,6 +1415,9 @@ function createAnalyzeDiffSectionTool$1() {
|
|
|
1313
1415
|
return {
|
|
1314
1416
|
name: "analyze_diff_section",
|
|
1315
1417
|
description: "Get expanded context around specific lines in a file to better understand changes",
|
|
1418
|
+
category: "Understanding",
|
|
1419
|
+
cost: "cheap",
|
|
1420
|
+
examples: [{ scenario: "See context around confusing change", params: { filePath: "src/auth.ts", startLine: 45, endLine: 52, contextLines: 15 }, expectedResult: "Expanded code section with context" }],
|
|
1316
1421
|
parameters: {
|
|
1317
1422
|
type: "object",
|
|
1318
1423
|
properties: {
|
|
@@ -1361,6 +1466,9 @@ function createGetRecentCommitsTool$1() {
|
|
|
1361
1466
|
return {
|
|
1362
1467
|
name: "get_recent_commits",
|
|
1363
1468
|
description: "Get recent commits that modified the same files to understand recent work in this area",
|
|
1469
|
+
category: "Understanding",
|
|
1470
|
+
cost: "cheap",
|
|
1471
|
+
examples: [{ scenario: "Check for duplicate commit messages", params: { filePaths: ["src/auth.ts"], since: "1 week ago", limit: 5 }, expectedResult: "Recent commits to auth.ts" }],
|
|
1364
1472
|
parameters: {
|
|
1365
1473
|
type: "object",
|
|
1366
1474
|
properties: {
|
|
@@ -1400,6 +1508,15 @@ function createGroupFilesByConcernTool$1() {
|
|
|
1400
1508
|
return {
|
|
1401
1509
|
name: "group_files_by_concern",
|
|
1402
1510
|
description: "Analyze changed files and suggest logical groupings that might represent separate commits",
|
|
1511
|
+
category: "Organization",
|
|
1512
|
+
cost: "cheap",
|
|
1513
|
+
examples: [
|
|
1514
|
+
{
|
|
1515
|
+
scenario: "Check if multiple unrelated changes should be split",
|
|
1516
|
+
params: { filePaths: ["src/auth.ts", "README.md", "tests/auth.test.ts", "package.json"] },
|
|
1517
|
+
expectedResult: "Files grouped by concern with split suggestions"
|
|
1518
|
+
}
|
|
1519
|
+
],
|
|
1403
1520
|
parameters: {
|
|
1404
1521
|
type: "object",
|
|
1405
1522
|
properties: {
|
|
@@ -1458,7 +1575,8 @@ async function runAgenticCommit(config) {
|
|
|
1458
1575
|
debugResponseFile,
|
|
1459
1576
|
storage,
|
|
1460
1577
|
logger: logger2,
|
|
1461
|
-
openaiReasoning
|
|
1578
|
+
openaiReasoning,
|
|
1579
|
+
tokenBudget
|
|
1462
1580
|
} = config;
|
|
1463
1581
|
const toolRegistry = createToolRegistry({
|
|
1464
1582
|
workingDirectory: process.cwd(),
|
|
@@ -1467,7 +1585,13 @@ async function runAgenticCommit(config) {
|
|
|
1467
1585
|
});
|
|
1468
1586
|
const tools = createCommitTools();
|
|
1469
1587
|
toolRegistry.registerAll(tools);
|
|
1470
|
-
const
|
|
1588
|
+
const toolGuidance = generateToolGuidance(tools, {
|
|
1589
|
+
strategy: "adaptive",
|
|
1590
|
+
includeExamples: true,
|
|
1591
|
+
explainWhenToUse: true,
|
|
1592
|
+
includeCategories: true
|
|
1593
|
+
});
|
|
1594
|
+
const systemPrompt = buildSystemPrompt$2(toolGuidance);
|
|
1471
1595
|
const userMessage = buildUserMessage$1(changedFiles, diffContent, userDirection, logContext);
|
|
1472
1596
|
const messages = [
|
|
1473
1597
|
{ role: "system", content: systemPrompt },
|
|
@@ -1483,7 +1607,13 @@ async function runAgenticCommit(config) {
|
|
|
1483
1607
|
debugResponseFile,
|
|
1484
1608
|
storage,
|
|
1485
1609
|
logger: logger2,
|
|
1486
|
-
openaiReasoning
|
|
1610
|
+
openaiReasoning,
|
|
1611
|
+
tokenBudget: tokenBudget || {
|
|
1612
|
+
max: 15e4,
|
|
1613
|
+
reserveForResponse: 4e3,
|
|
1614
|
+
strategy: "fifo",
|
|
1615
|
+
onBudgetExceeded: "compress"
|
|
1616
|
+
}
|
|
1487
1617
|
};
|
|
1488
1618
|
const result = await runAgentic(agenticConfig);
|
|
1489
1619
|
const parsed = parseAgenticResult$1(result.finalMessage);
|
|
@@ -1496,53 +1626,60 @@ async function runAgenticCommit(config) {
|
|
|
1496
1626
|
toolMetrics: result.toolMetrics
|
|
1497
1627
|
};
|
|
1498
1628
|
}
|
|
1499
|
-
function buildSystemPrompt$
|
|
1500
|
-
return `You are
|
|
1629
|
+
function buildSystemPrompt$2(toolGuidance) {
|
|
1630
|
+
return `You are a professional software engineer writing commit messages for your team.
|
|
1631
|
+
|
|
1632
|
+
${toolGuidance}
|
|
1501
1633
|
|
|
1502
|
-
|
|
1634
|
+
## Your Task
|
|
1503
1635
|
|
|
1504
|
-
|
|
1636
|
+
Write a commit message that clearly explains what changed and why. Your message should help teammates understand the changes without needing to read the diff.
|
|
1505
1637
|
|
|
1506
|
-
|
|
1507
|
-
-
|
|
1508
|
-
-
|
|
1509
|
-
-
|
|
1638
|
+
Think about:
|
|
1639
|
+
- What problem does this solve?
|
|
1640
|
+
- How do the changes work together?
|
|
1641
|
+
- What should reviewers focus on?
|
|
1642
|
+
- Are there any important implications?
|
|
1510
1643
|
|
|
1511
|
-
|
|
1512
|
-
- get_file_history: Use to see evolution. Good for: understanding if this continues previous work, checking for patterns
|
|
1513
|
-
- get_recent_commits: Use to check recent context. Good for: avoiding duplicate messages, understanding if this is part of a series
|
|
1514
|
-
- search_codebase: Use to understand usage. Good for: seeing if changes affect multiple places, finding patterns
|
|
1644
|
+
Use the available tools to investigate the changes. The more you understand, the better your message will be.
|
|
1515
1645
|
|
|
1516
|
-
**
|
|
1517
|
-
-
|
|
1518
|
-
-
|
|
1646
|
+
**Important**: If additional context is provided (from context files or other sources), use your judgment:
|
|
1647
|
+
- If the context is relevant to these specific changes, incorporate it
|
|
1648
|
+
- If the context describes unrelated changes or other packages, ignore it
|
|
1649
|
+
- Don't force connections between unrelated information
|
|
1650
|
+
- Focus on accurately describing what actually changed in this commit
|
|
1519
1651
|
|
|
1520
|
-
## Investigation
|
|
1652
|
+
## Investigation Approach
|
|
1521
1653
|
|
|
1522
|
-
|
|
1523
|
-
-
|
|
1654
|
+
Use tools based on what you need to know:
|
|
1655
|
+
- group_files_by_concern - understand how files relate
|
|
1656
|
+
- get_file_content - see full context when diffs are unclear
|
|
1657
|
+
- get_file_history - understand how code evolved
|
|
1658
|
+
- get_file_dependencies - assess impact of changes
|
|
1659
|
+
- get_recent_commits - avoid duplicate messages
|
|
1660
|
+
- get_related_tests - understand behavior changes
|
|
1661
|
+
- search_codebase - find usage patterns
|
|
1524
1662
|
|
|
1525
|
-
|
|
1526
|
-
- Use 2-4 tools: group_files_by_concern, get_file_content for key files, get_recent_commits, get_related_tests
|
|
1663
|
+
## Writing Style
|
|
1527
1664
|
|
|
1528
|
-
|
|
1529
|
-
- Use
|
|
1665
|
+
Write naturally and directly:
|
|
1666
|
+
- Use plain language, not corporate speak
|
|
1667
|
+
- Be specific and concrete
|
|
1668
|
+
- Avoid buzzwords and jargon
|
|
1669
|
+
- No emojis or excessive punctuation
|
|
1670
|
+
- No phrases like "this commit" or "this PR"
|
|
1671
|
+
- No meta-commentary about the commit itself
|
|
1530
1672
|
|
|
1531
|
-
|
|
1673
|
+
Follow conventional commit format when appropriate (feat:, fix:, refactor:, docs:, test:, chore:), but prioritize clarity over formality.
|
|
1532
1674
|
|
|
1533
|
-
##
|
|
1534
|
-
- Follow conventional commit format when appropriate (feat:, fix:, refactor:, docs:, test:, chore:)
|
|
1535
|
-
- Consider suggesting split commits for unrelated changes
|
|
1536
|
-
- Output only the commit message and/or splits - no conversational remarks
|
|
1537
|
-
- NEVER include phrases like "If you want" or "Let me know" or offer follow-up actions
|
|
1675
|
+
## Output Format
|
|
1538
1676
|
|
|
1539
|
-
|
|
1540
|
-
When you're ready to provide the final commit message, format it as:
|
|
1677
|
+
When ready, format your response as:
|
|
1541
1678
|
|
|
1542
1679
|
COMMIT_MESSAGE:
|
|
1543
|
-
[Your
|
|
1680
|
+
[Your commit message here]
|
|
1544
1681
|
|
|
1545
|
-
If
|
|
1682
|
+
If changes should be split into multiple commits:
|
|
1546
1683
|
|
|
1547
1684
|
SUGGESTED_SPLITS:
|
|
1548
1685
|
Split 1:
|
|
@@ -1553,7 +1690,7 @@ Message: [commit message for this split]
|
|
|
1553
1690
|
Split 2:
|
|
1554
1691
|
...
|
|
1555
1692
|
|
|
1556
|
-
|
|
1693
|
+
Output only the commit message and splits. No conversational remarks or follow-up offers.`;
|
|
1557
1694
|
}
|
|
1558
1695
|
function buildUserMessage$1(changedFiles, diffContent, userDirection, logContext) {
|
|
1559
1696
|
let message = `I have staged changes that need a commit message.
|
|
@@ -1576,9 +1713,16 @@ ${logContext}`;
|
|
|
1576
1713
|
}
|
|
1577
1714
|
message += `
|
|
1578
1715
|
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1716
|
+
Analyze these changes and write a clear commit message. Consider:
|
|
1717
|
+
- What problem does this solve?
|
|
1718
|
+
- How do the changes work together?
|
|
1719
|
+
- Should this be one commit or multiple?
|
|
1720
|
+
|
|
1721
|
+
If context information is provided, use it only if relevant to these specific changes.
|
|
1722
|
+
Don't force connections that don't exist - if context doesn't apply to this package,
|
|
1723
|
+
simply ignore it and focus on the actual changes.
|
|
1724
|
+
|
|
1725
|
+
Investigate as needed to write an accurate, helpful message.`;
|
|
1582
1726
|
return message;
|
|
1583
1727
|
}
|
|
1584
1728
|
function parseAgenticResult$1(finalMessage) {
|
|
@@ -1976,6 +2120,9 @@ function createGetTagHistoryTool() {
|
|
|
1976
2120
|
return {
|
|
1977
2121
|
name: "get_tag_history",
|
|
1978
2122
|
description: "Get the history of previous release tags to understand release patterns and versioning",
|
|
2123
|
+
category: "Release Analysis",
|
|
2124
|
+
cost: "cheap",
|
|
2125
|
+
examples: [{ scenario: "Check recent version tags", params: { limit: 5, pattern: "v*" }, expectedResult: "List of recent version tags with dates" }],
|
|
1979
2126
|
parameters: {
|
|
1980
2127
|
type: "object",
|
|
1981
2128
|
properties: {
|
|
@@ -2025,6 +2172,9 @@ function createComparePreviousReleaseTool() {
|
|
|
2025
2172
|
return {
|
|
2026
2173
|
name: "compare_previous_release",
|
|
2027
2174
|
description: "Compare this release with a previous release to understand what changed between versions",
|
|
2175
|
+
category: "Release Analysis",
|
|
2176
|
+
cost: "moderate",
|
|
2177
|
+
examples: [{ scenario: "Compare with last release", params: { previousTag: "v1.0.0", currentRef: "HEAD" }, expectedResult: "Commit count and file change statistics" }],
|
|
2028
2178
|
parameters: {
|
|
2029
2179
|
type: "object",
|
|
2030
2180
|
properties: {
|
|
@@ -2080,6 +2230,9 @@ function createGetReleaseStatsTool() {
|
|
|
2080
2230
|
return {
|
|
2081
2231
|
name: "get_release_stats",
|
|
2082
2232
|
description: "Get comprehensive statistics about the release including contributors, file changes, and commit patterns",
|
|
2233
|
+
category: "Release Analysis",
|
|
2234
|
+
cost: "moderate",
|
|
2235
|
+
examples: [{ scenario: "Get release overview", params: { fromRef: "v1.0.0", toRef: "HEAD" }, expectedResult: "Contributors, file changes, and top modified files" }],
|
|
2083
2236
|
parameters: {
|
|
2084
2237
|
type: "object",
|
|
2085
2238
|
properties: {
|
|
@@ -2133,6 +2286,9 @@ function createGetBreakingChangesTool() {
|
|
|
2133
2286
|
return {
|
|
2134
2287
|
name: "get_breaking_changes",
|
|
2135
2288
|
description: "Search for potential breaking changes by looking for specific patterns in commits and diffs",
|
|
2289
|
+
category: "Release Analysis",
|
|
2290
|
+
cost: "expensive",
|
|
2291
|
+
examples: [{ scenario: "Check for breaking changes", params: { fromRef: "v1.0.0", toRef: "HEAD" }, expectedResult: "List of potential breaking changes found" }],
|
|
2136
2292
|
parameters: {
|
|
2137
2293
|
type: "object",
|
|
2138
2294
|
properties: {
|
|
@@ -2196,6 +2352,9 @@ function createAnalyzeCommitPatternsTool() {
|
|
|
2196
2352
|
return {
|
|
2197
2353
|
name: "analyze_commit_patterns",
|
|
2198
2354
|
description: "Analyze commit messages to identify patterns and themes in the release",
|
|
2355
|
+
category: "Release Analysis",
|
|
2356
|
+
cost: "cheap",
|
|
2357
|
+
examples: [{ scenario: "Find commit themes", params: { fromRef: "v1.0.0", toRef: "HEAD" }, expectedResult: "Commit types and top keywords" }],
|
|
2199
2358
|
parameters: {
|
|
2200
2359
|
type: "object",
|
|
2201
2360
|
properties: {
|
|
@@ -2267,7 +2426,8 @@ async function runAgenticRelease(config) {
|
|
|
2267
2426
|
debugResponseFile,
|
|
2268
2427
|
storage,
|
|
2269
2428
|
logger: logger2,
|
|
2270
|
-
openaiReasoning
|
|
2429
|
+
openaiReasoning,
|
|
2430
|
+
tokenBudget
|
|
2271
2431
|
} = config;
|
|
2272
2432
|
const toolRegistry = createToolRegistry({
|
|
2273
2433
|
workingDirectory: process.cwd(),
|
|
@@ -2276,7 +2436,13 @@ async function runAgenticRelease(config) {
|
|
|
2276
2436
|
});
|
|
2277
2437
|
const tools = createReleaseTools();
|
|
2278
2438
|
toolRegistry.registerAll(tools);
|
|
2279
|
-
const
|
|
2439
|
+
const toolGuidance = generateToolGuidance(tools, {
|
|
2440
|
+
strategy: "adaptive",
|
|
2441
|
+
includeExamples: true,
|
|
2442
|
+
explainWhenToUse: true,
|
|
2443
|
+
includeCategories: true
|
|
2444
|
+
});
|
|
2445
|
+
const systemPrompt = buildSystemPrompt$1(toolGuidance);
|
|
2280
2446
|
const userMessage = buildUserMessage({
|
|
2281
2447
|
fromRef,
|
|
2282
2448
|
toRef,
|
|
@@ -2300,7 +2466,13 @@ async function runAgenticRelease(config) {
|
|
|
2300
2466
|
debugResponseFile,
|
|
2301
2467
|
storage,
|
|
2302
2468
|
logger: logger2,
|
|
2303
|
-
openaiReasoning
|
|
2469
|
+
openaiReasoning,
|
|
2470
|
+
tokenBudget: tokenBudget || {
|
|
2471
|
+
max: 2e5,
|
|
2472
|
+
reserveForResponse: 8e3,
|
|
2473
|
+
strategy: "fifo",
|
|
2474
|
+
onBudgetExceeded: "compress"
|
|
2475
|
+
}
|
|
2304
2476
|
};
|
|
2305
2477
|
const result = await runAgentic(agenticConfig);
|
|
2306
2478
|
const parsed = parseAgenticResult(result.finalMessage);
|
|
@@ -2312,64 +2484,80 @@ async function runAgenticRelease(config) {
|
|
|
2312
2484
|
toolMetrics: result.toolMetrics
|
|
2313
2485
|
};
|
|
2314
2486
|
}
|
|
2315
|
-
function buildSystemPrompt() {
|
|
2316
|
-
return `You are
|
|
2487
|
+
function buildSystemPrompt$1(toolGuidance) {
|
|
2488
|
+
return `You are a professional software engineer writing release notes for your team and users.
|
|
2489
|
+
|
|
2490
|
+
${toolGuidance}
|
|
2491
|
+
|
|
2492
|
+
## Your Task
|
|
2493
|
+
|
|
2494
|
+
Write release notes that clearly explain what's in this release and why it matters. Your notes should help users and developers understand what changed without needing to read every commit.
|
|
2495
|
+
|
|
2496
|
+
Focus on:
|
|
2497
|
+
- What problems does this release solve?
|
|
2498
|
+
- What new capabilities does it add?
|
|
2499
|
+
- What's the impact on users and developers?
|
|
2500
|
+
- Are there breaking changes or important considerations?
|
|
2501
|
+
|
|
2502
|
+
Use the available tools to investigate the changes. The more you understand, the better your notes will be.
|
|
2503
|
+
|
|
2504
|
+
**Important**: If additional context is provided (from context files or other sources), use your judgment:
|
|
2505
|
+
- If the context is relevant to this specific package/release, incorporate it appropriately
|
|
2506
|
+
- If the context describes changes in other packages or unrelated work, ignore it
|
|
2507
|
+
- Don't fabricate connections between this package and unrelated context
|
|
2508
|
+
- Be honest about what changed - only mention what actually happened in this release
|
|
2509
|
+
- Context is supplemental information, not a requirement to include
|
|
2317
2510
|
|
|
2318
|
-
|
|
2511
|
+
## Investigation Approach
|
|
2319
2512
|
|
|
2320
|
-
|
|
2513
|
+
Use tools based on what you need to know:
|
|
2321
2514
|
|
|
2322
|
-
**
|
|
2323
|
-
-
|
|
2324
|
-
-
|
|
2325
|
-
- compare_previous_release
|
|
2326
|
-
- get_tag_history: Use early to understand release cadence. Good for: establishing context about project versioning patterns
|
|
2515
|
+
**Context:**
|
|
2516
|
+
- get_tag_history - understand release patterns
|
|
2517
|
+
- get_release_stats - quantify scope
|
|
2518
|
+
- compare_previous_release - see how this compares
|
|
2327
2519
|
|
|
2328
|
-
**
|
|
2329
|
-
-
|
|
2330
|
-
-
|
|
2331
|
-
-
|
|
2332
|
-
-
|
|
2520
|
+
**Understanding:**
|
|
2521
|
+
- group_files_by_concern - identify themes
|
|
2522
|
+
- analyze_commit_patterns - detect patterns
|
|
2523
|
+
- get_file_content - see full context
|
|
2524
|
+
- analyze_diff_section - expand unclear changes
|
|
2525
|
+
- get_file_history - understand evolution
|
|
2333
2526
|
|
|
2334
|
-
**
|
|
2335
|
-
-
|
|
2336
|
-
-
|
|
2337
|
-
-
|
|
2527
|
+
**Impact:**
|
|
2528
|
+
- get_file_dependencies - assess reach
|
|
2529
|
+
- search_codebase - find usage patterns
|
|
2530
|
+
- get_related_tests - understand behavior changes
|
|
2531
|
+
- get_breaking_changes - identify breaking changes (always use)
|
|
2338
2532
|
|
|
2339
|
-
|
|
2340
|
-
- get_breaking_changes: Always use. Good for: identifying API changes, finding removals/signature changes that could break users
|
|
2341
|
-
- get_related_tests: Use for significant logic changes. Good for: understanding what behavior changed, verifying test coverage exists
|
|
2533
|
+
## Writing Style
|
|
2342
2534
|
|
|
2343
|
-
|
|
2535
|
+
Write naturally and directly:
|
|
2536
|
+
- Use plain language that users can understand
|
|
2537
|
+
- Be specific about what changed and why
|
|
2538
|
+
- Avoid marketing speak and buzzwords
|
|
2539
|
+
- No emojis or excessive punctuation
|
|
2540
|
+
- No phrases like "we're excited to announce"
|
|
2541
|
+
- No meta-commentary about the release itself
|
|
2542
|
+
- Focus on facts and implications, not enthusiasm
|
|
2344
2543
|
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2544
|
+
Structure your notes logically:
|
|
2545
|
+
- Start with the most important changes
|
|
2546
|
+
- Group related changes together
|
|
2547
|
+
- Explain breaking changes clearly
|
|
2548
|
+
- Include practical examples when helpful
|
|
2350
2549
|
|
|
2351
|
-
|
|
2550
|
+
## Output Format
|
|
2352
2551
|
|
|
2353
|
-
|
|
2354
|
-
When you're ready to provide the final release notes, format them as JSON:
|
|
2552
|
+
When ready, format your response as JSON:
|
|
2355
2553
|
|
|
2356
2554
|
RELEASE_NOTES:
|
|
2357
2555
|
{
|
|
2358
|
-
"title": "
|
|
2359
|
-
"body": "
|
|
2360
|
-
}
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
- Demonstrate genuine understanding of the changes
|
|
2364
|
-
- Provide context and explain implications
|
|
2365
|
-
- Connect related changes to reveal patterns
|
|
2366
|
-
- Be substantial and analytical, not formulaic
|
|
2367
|
-
- Sound like they were written by a human who studied the changes
|
|
2368
|
-
- Be grounded in actual commits and issues (no hallucinations)
|
|
2369
|
-
- Be standalone documentation that can be published as-is
|
|
2370
|
-
- NEVER include conversational elements like "If you want, I can also..." or "Let me know if..."
|
|
2371
|
-
- NEVER offer follow-up actions, questions, or suggestions for additional work
|
|
2372
|
-
- End with substantive content, not conversational closing remarks`;
|
|
2556
|
+
"title": "Clear, factual title describing the main changes",
|
|
2557
|
+
"body": "Detailed release notes in Markdown format"
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
Output only the JSON. No conversational remarks or follow-up offers.`;
|
|
2373
2561
|
}
|
|
2374
2562
|
function buildUserMessage(params) {
|
|
2375
2563
|
const { fromRef, toRef, logContent, diffContent, milestoneIssues, releaseFocus, userContext } = params;
|
|
@@ -2402,15 +2590,17 @@ ${userContext}`;
|
|
|
2402
2590
|
}
|
|
2403
2591
|
message += `
|
|
2404
2592
|
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
5. Identify any breaking changes or significant architectural shifts
|
|
2411
|
-
6. Follow best practices for technical writing and release notes
|
|
2593
|
+
Analyze these changes and write clear release notes. Consider:
|
|
2594
|
+
- What's the main story of this release?
|
|
2595
|
+
- What problems does it solve?
|
|
2596
|
+
- What's the impact on users and developers?
|
|
2597
|
+
- Are there breaking changes?
|
|
2412
2598
|
|
|
2413
|
-
|
|
2599
|
+
If context information is provided, use it only if relevant to this specific package.
|
|
2600
|
+
Don't force connections that don't exist - if context describes changes in other packages
|
|
2601
|
+
or unrelated features, simply ignore it and focus on what actually changed in this release.
|
|
2602
|
+
|
|
2603
|
+
Investigate as needed to write accurate, helpful release notes.`;
|
|
2414
2604
|
return message;
|
|
2415
2605
|
}
|
|
2416
2606
|
function parseAgenticResult(finalMessage) {
|
|
@@ -2418,8 +2608,13 @@ function parseAgenticResult(finalMessage) {
|
|
|
2418
2608
|
if (jsonMatch) {
|
|
2419
2609
|
try {
|
|
2420
2610
|
const jsonStr = jsonMatch[1].trim();
|
|
2421
|
-
|
|
2422
|
-
|
|
2611
|
+
let parsed;
|
|
2612
|
+
try {
|
|
2613
|
+
parsed = JSON.parse(jsonStr);
|
|
2614
|
+
} catch {
|
|
2615
|
+
parsed = null;
|
|
2616
|
+
}
|
|
2617
|
+
if (parsed && parsed.title && parsed.body) {
|
|
2423
2618
|
return {
|
|
2424
2619
|
releaseNotes: {
|
|
2425
2620
|
title: parsed.title,
|
|
@@ -2455,36 +2650,1084 @@ function parseAgenticResult(finalMessage) {
|
|
|
2455
2650
|
}
|
|
2456
2651
|
};
|
|
2457
2652
|
}
|
|
2653
|
+
function createPublishTools() {
|
|
2654
|
+
return [
|
|
2655
|
+
createCheckGitStatusTool(),
|
|
2656
|
+
createCheckBranchSyncTool(),
|
|
2657
|
+
createAnalyzeDivergenceTool(),
|
|
2658
|
+
createGetCommitLogTool(),
|
|
2659
|
+
createGetBranchInfoTool(),
|
|
2660
|
+
createSyncBranchTool(),
|
|
2661
|
+
createGetDiffStatsTool(),
|
|
2662
|
+
createCheckConflictsTool(),
|
|
2663
|
+
createResetBranchTool()
|
|
2664
|
+
];
|
|
2665
|
+
}
|
|
2666
|
+
function createCheckGitStatusTool() {
|
|
2667
|
+
return {
|
|
2668
|
+
name: "check_git_status",
|
|
2669
|
+
description: "Check the current git repository status, including uncommitted changes, current branch, and repository state",
|
|
2670
|
+
parameters: {
|
|
2671
|
+
type: "object",
|
|
2672
|
+
properties: {
|
|
2673
|
+
showUntracked: {
|
|
2674
|
+
type: "boolean",
|
|
2675
|
+
description: "Include untracked files in output (default: false)",
|
|
2676
|
+
default: false
|
|
2677
|
+
}
|
|
2678
|
+
},
|
|
2679
|
+
required: []
|
|
2680
|
+
},
|
|
2681
|
+
execute: async (params, context) => {
|
|
2682
|
+
const { showUntracked = false } = params;
|
|
2683
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
2684
|
+
try {
|
|
2685
|
+
const branchResult = await run("git branch --show-current", { cwd: workingDir });
|
|
2686
|
+
const currentBranch = branchResult.stdout.trim();
|
|
2687
|
+
const statusCmd = showUntracked ? "git status --porcelain" : "git status --porcelain --untracked-files=no";
|
|
2688
|
+
const statusResult = await run(statusCmd, { cwd: workingDir });
|
|
2689
|
+
const hasChanges = statusResult.stdout.trim().length > 0;
|
|
2690
|
+
const headResult = await run("git rev-parse --short HEAD", { cwd: workingDir });
|
|
2691
|
+
const headSha = headResult.stdout.trim();
|
|
2692
|
+
return {
|
|
2693
|
+
currentBranch,
|
|
2694
|
+
headSha,
|
|
2695
|
+
hasUncommittedChanges: hasChanges,
|
|
2696
|
+
statusOutput: statusResult.stdout
|
|
2697
|
+
};
|
|
2698
|
+
} catch (error) {
|
|
2699
|
+
throw new Error(`Failed to check git status: ${error.message}`);
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
}
|
|
2704
|
+
function createCheckBranchSyncTool() {
|
|
2705
|
+
return {
|
|
2706
|
+
name: "check_branch_sync",
|
|
2707
|
+
description: "Check if a local branch is synchronized with its remote counterpart. Returns sync status, local/remote SHAs, and whether the branch exists locally.",
|
|
2708
|
+
parameters: {
|
|
2709
|
+
type: "object",
|
|
2710
|
+
properties: {
|
|
2711
|
+
branchName: {
|
|
2712
|
+
type: "string",
|
|
2713
|
+
description: "Name of the branch to check"
|
|
2714
|
+
}
|
|
2715
|
+
},
|
|
2716
|
+
required: ["branchName"]
|
|
2717
|
+
},
|
|
2718
|
+
execute: async (params, _context) => {
|
|
2719
|
+
const { branchName } = params;
|
|
2720
|
+
try {
|
|
2721
|
+
const exists = await localBranchExists(branchName);
|
|
2722
|
+
if (!exists) {
|
|
2723
|
+
return {
|
|
2724
|
+
exists: false,
|
|
2725
|
+
message: `Branch '${branchName}' does not exist locally`
|
|
2726
|
+
};
|
|
2727
|
+
}
|
|
2728
|
+
const syncStatus = await isBranchInSyncWithRemote(branchName);
|
|
2729
|
+
return {
|
|
2730
|
+
exists: true,
|
|
2731
|
+
branchName,
|
|
2732
|
+
inSync: syncStatus.inSync,
|
|
2733
|
+
localSha: syncStatus.localSha,
|
|
2734
|
+
remoteSha: syncStatus.remoteSha,
|
|
2735
|
+
error: syncStatus.error,
|
|
2736
|
+
isDiverged: syncStatus.localSha !== syncStatus.remoteSha
|
|
2737
|
+
};
|
|
2738
|
+
} catch (error) {
|
|
2739
|
+
throw new Error(`Failed to check branch sync: ${error.message}`);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
function createAnalyzeDivergenceTool() {
|
|
2745
|
+
return {
|
|
2746
|
+
name: "analyze_divergence",
|
|
2747
|
+
description: "Analyze how two branches have diverged by showing commits unique to each branch",
|
|
2748
|
+
parameters: {
|
|
2749
|
+
type: "object",
|
|
2750
|
+
properties: {
|
|
2751
|
+
sourceBranch: {
|
|
2752
|
+
type: "string",
|
|
2753
|
+
description: "The source/local branch name"
|
|
2754
|
+
},
|
|
2755
|
+
targetBranch: {
|
|
2756
|
+
type: "string",
|
|
2757
|
+
description: 'The target/remote branch to compare against (e.g., "origin/main")'
|
|
2758
|
+
},
|
|
2759
|
+
maxCommits: {
|
|
2760
|
+
type: "number",
|
|
2761
|
+
description: "Maximum number of commits to show for each branch (default: 10)",
|
|
2762
|
+
default: 10
|
|
2763
|
+
}
|
|
2764
|
+
},
|
|
2765
|
+
required: ["sourceBranch", "targetBranch"]
|
|
2766
|
+
},
|
|
2767
|
+
execute: async (params, context) => {
|
|
2768
|
+
const { sourceBranch, targetBranch, maxCommits = 10 } = params;
|
|
2769
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
2770
|
+
try {
|
|
2771
|
+
const sourceOnlyResult = await run(
|
|
2772
|
+
`git log ${targetBranch}..${sourceBranch} --oneline -n ${maxCommits}`,
|
|
2773
|
+
{ cwd: workingDir }
|
|
2774
|
+
);
|
|
2775
|
+
const targetOnlyResult = await run(
|
|
2776
|
+
`git log ${sourceBranch}..${targetBranch} --oneline -n ${maxCommits}`,
|
|
2777
|
+
{ cwd: workingDir }
|
|
2778
|
+
);
|
|
2779
|
+
const mergeBaseResult = await run(
|
|
2780
|
+
`git merge-base ${sourceBranch} ${targetBranch}`,
|
|
2781
|
+
{ cwd: workingDir }
|
|
2782
|
+
);
|
|
2783
|
+
return {
|
|
2784
|
+
sourceBranch,
|
|
2785
|
+
targetBranch,
|
|
2786
|
+
mergeBase: mergeBaseResult.stdout.trim(),
|
|
2787
|
+
commitsInSourceOnly: sourceOnlyResult.stdout.trim() || "None",
|
|
2788
|
+
commitsInTargetOnly: targetOnlyResult.stdout.trim() || "None",
|
|
2789
|
+
isDiverged: sourceOnlyResult.stdout.trim().length > 0 && targetOnlyResult.stdout.trim().length > 0,
|
|
2790
|
+
canFastForward: sourceOnlyResult.stdout.trim().length === 0
|
|
2791
|
+
// Target is ahead, can fast-forward
|
|
2792
|
+
};
|
|
2793
|
+
} catch (error) {
|
|
2794
|
+
throw new Error(`Failed to analyze divergence: ${error.message}`);
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
function createGetCommitLogTool() {
|
|
2800
|
+
return {
|
|
2801
|
+
name: "get_commit_log",
|
|
2802
|
+
description: "Get git commit log for a branch or commit range with detailed information",
|
|
2803
|
+
parameters: {
|
|
2804
|
+
type: "object",
|
|
2805
|
+
properties: {
|
|
2806
|
+
ref: {
|
|
2807
|
+
type: "string",
|
|
2808
|
+
description: 'Git ref (branch name, commit range like "main..working", or single commit)'
|
|
2809
|
+
},
|
|
2810
|
+
maxCommits: {
|
|
2811
|
+
type: "number",
|
|
2812
|
+
description: "Maximum number of commits to return (default: 20)",
|
|
2813
|
+
default: 20
|
|
2814
|
+
},
|
|
2815
|
+
format: {
|
|
2816
|
+
type: "string",
|
|
2817
|
+
description: 'Format: "oneline" for brief, "full" for detailed with message body',
|
|
2818
|
+
enum: ["oneline", "full"],
|
|
2819
|
+
default: "oneline"
|
|
2820
|
+
}
|
|
2821
|
+
},
|
|
2822
|
+
required: ["ref"]
|
|
2823
|
+
},
|
|
2824
|
+
execute: async (params, context) => {
|
|
2825
|
+
const { ref, maxCommits = 20, format = "oneline" } = params;
|
|
2826
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
2827
|
+
try {
|
|
2828
|
+
const formatStr = format === "full" ? "%H%n%an <%ae>%n%ad%n%s%n%n%b%n---" : "%h - %s (%an, %ar)";
|
|
2829
|
+
const result = await run(
|
|
2830
|
+
`git log "${ref}" --format="${formatStr}" -n ${maxCommits}`,
|
|
2831
|
+
{ cwd: workingDir }
|
|
2832
|
+
);
|
|
2833
|
+
return result.stdout || "No commits found";
|
|
2834
|
+
} catch (error) {
|
|
2835
|
+
throw new Error(`Failed to get commit log: ${error.message}`);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
function createGetBranchInfoTool() {
|
|
2841
|
+
return {
|
|
2842
|
+
name: "get_branch_info",
|
|
2843
|
+
description: "Get detailed information about a branch including its tracking info, last commit, and whether it exists remotely",
|
|
2844
|
+
parameters: {
|
|
2845
|
+
type: "object",
|
|
2846
|
+
properties: {
|
|
2847
|
+
branchName: {
|
|
2848
|
+
type: "string",
|
|
2849
|
+
description: "Name of the branch to inspect"
|
|
2850
|
+
}
|
|
2851
|
+
},
|
|
2852
|
+
required: ["branchName"]
|
|
2853
|
+
},
|
|
2854
|
+
execute: async (params, context) => {
|
|
2855
|
+
const { branchName } = params;
|
|
2856
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
2857
|
+
try {
|
|
2858
|
+
const localExists = await localBranchExists(branchName);
|
|
2859
|
+
if (!localExists) {
|
|
2860
|
+
try {
|
|
2861
|
+
const remoteCheckResult = await run(`git ls-remote --heads origin ${branchName}`, { cwd: workingDir });
|
|
2862
|
+
const remoteExists = remoteCheckResult.stdout.trim().length > 0;
|
|
2863
|
+
return {
|
|
2864
|
+
branchName,
|
|
2865
|
+
existsLocally: false,
|
|
2866
|
+
existsRemotely: remoteExists,
|
|
2867
|
+
message: remoteExists ? `Branch exists remotely but not locally` : `Branch does not exist locally or remotely`
|
|
2868
|
+
};
|
|
2869
|
+
} catch {
|
|
2870
|
+
return {
|
|
2871
|
+
branchName,
|
|
2872
|
+
existsLocally: false,
|
|
2873
|
+
existsRemotely: false
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
let trackingBranch = null;
|
|
2878
|
+
try {
|
|
2879
|
+
const trackingResult = await run(`git rev-parse --abbrev-ref ${branchName}@{upstream}`, { cwd: workingDir });
|
|
2880
|
+
trackingBranch = trackingResult.stdout.trim();
|
|
2881
|
+
} catch {
|
|
2882
|
+
}
|
|
2883
|
+
const lastCommitResult = await run(
|
|
2884
|
+
`git log ${branchName} -1 --format="%H|%h|%s|%an|%ar"`,
|
|
2885
|
+
{ cwd: workingDir }
|
|
2886
|
+
);
|
|
2887
|
+
const [fullSha, shortSha, subject, author, relativeDate] = lastCommitResult.stdout.trim().split("|");
|
|
2888
|
+
const countResult = await run(`git rev-list --count ${branchName}`, { cwd: workingDir });
|
|
2889
|
+
const commitCount = parseInt(countResult.stdout.trim(), 10);
|
|
2890
|
+
if (isNaN(commitCount)) {
|
|
2891
|
+
throw new Error(`Invalid commit count returned from git: ${countResult.stdout}`);
|
|
2892
|
+
}
|
|
2893
|
+
return {
|
|
2894
|
+
branchName,
|
|
2895
|
+
existsLocally: true,
|
|
2896
|
+
trackingBranch,
|
|
2897
|
+
lastCommit: {
|
|
2898
|
+
fullSha,
|
|
2899
|
+
shortSha,
|
|
2900
|
+
subject,
|
|
2901
|
+
author,
|
|
2902
|
+
relativeDate
|
|
2903
|
+
},
|
|
2904
|
+
commitCount
|
|
2905
|
+
};
|
|
2906
|
+
} catch (error) {
|
|
2907
|
+
throw new Error(`Failed to get branch info: ${error.message}`);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
function createSyncBranchTool() {
|
|
2913
|
+
return {
|
|
2914
|
+
name: "sync_branch",
|
|
2915
|
+
description: "Attempt to synchronize a local branch with its remote counterpart. Returns success status and details about what happened.",
|
|
2916
|
+
parameters: {
|
|
2917
|
+
type: "object",
|
|
2918
|
+
properties: {
|
|
2919
|
+
branchName: {
|
|
2920
|
+
type: "string",
|
|
2921
|
+
description: "Name of the branch to sync"
|
|
2922
|
+
}
|
|
2923
|
+
},
|
|
2924
|
+
required: ["branchName"]
|
|
2925
|
+
},
|
|
2926
|
+
execute: async (params, _context) => {
|
|
2927
|
+
const { branchName } = params;
|
|
2928
|
+
try {
|
|
2929
|
+
const syncResult = await safeSyncBranchWithRemote(branchName);
|
|
2930
|
+
return {
|
|
2931
|
+
branchName,
|
|
2932
|
+
success: syncResult.success,
|
|
2933
|
+
conflictResolutionRequired: syncResult.conflictResolutionRequired,
|
|
2934
|
+
error: syncResult.error,
|
|
2935
|
+
message: syncResult.success ? `Successfully synchronized ${branchName} with remote` : syncResult.conflictResolutionRequired ? `Sync failed due to merge conflicts in ${branchName}` : `Sync failed: ${syncResult.error}`
|
|
2936
|
+
};
|
|
2937
|
+
} catch (error) {
|
|
2938
|
+
throw new Error(`Failed to sync branch: ${error.message}`);
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
function createGetDiffStatsTool() {
|
|
2944
|
+
return {
|
|
2945
|
+
name: "get_diff_stats",
|
|
2946
|
+
description: "Get statistics about differences between two git refs (branches, commits, etc.)",
|
|
2947
|
+
parameters: {
|
|
2948
|
+
type: "object",
|
|
2949
|
+
properties: {
|
|
2950
|
+
fromRef: {
|
|
2951
|
+
type: "string",
|
|
2952
|
+
description: 'Starting ref (e.g., "main", "origin/main")'
|
|
2953
|
+
},
|
|
2954
|
+
toRef: {
|
|
2955
|
+
type: "string",
|
|
2956
|
+
description: 'Ending ref (e.g., "working", "HEAD")'
|
|
2957
|
+
},
|
|
2958
|
+
includeFileList: {
|
|
2959
|
+
type: "boolean",
|
|
2960
|
+
description: "Include list of changed files (default: true)",
|
|
2961
|
+
default: true
|
|
2962
|
+
}
|
|
2963
|
+
},
|
|
2964
|
+
required: ["fromRef", "toRef"]
|
|
2965
|
+
},
|
|
2966
|
+
execute: async (params, context) => {
|
|
2967
|
+
const { fromRef, toRef, includeFileList = true } = params;
|
|
2968
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
2969
|
+
try {
|
|
2970
|
+
const statsResult = await run(`git diff --shortstat ${fromRef}..${toRef}`, { cwd: workingDir });
|
|
2971
|
+
let fileList = null;
|
|
2972
|
+
if (includeFileList) {
|
|
2973
|
+
const fileListResult = await run(`git diff --name-status ${fromRef}..${toRef}`, { cwd: workingDir });
|
|
2974
|
+
fileList = fileListResult.stdout;
|
|
2975
|
+
}
|
|
2976
|
+
return {
|
|
2977
|
+
fromRef,
|
|
2978
|
+
toRef,
|
|
2979
|
+
stats: statsResult.stdout.trim() || "No differences",
|
|
2980
|
+
fileList
|
|
2981
|
+
};
|
|
2982
|
+
} catch (error) {
|
|
2983
|
+
throw new Error(`Failed to get diff stats: ${error.message}`);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
function createCheckConflictsTool() {
|
|
2989
|
+
return {
|
|
2990
|
+
name: "check_conflicts",
|
|
2991
|
+
description: "Check if merging one branch into another would result in conflicts (without actually performing the merge)",
|
|
2992
|
+
parameters: {
|
|
2993
|
+
type: "object",
|
|
2994
|
+
properties: {
|
|
2995
|
+
sourceBranch: {
|
|
2996
|
+
type: "string",
|
|
2997
|
+
description: "Branch to merge from"
|
|
2998
|
+
},
|
|
2999
|
+
targetBranch: {
|
|
3000
|
+
type: "string",
|
|
3001
|
+
description: "Branch to merge into"
|
|
3002
|
+
}
|
|
3003
|
+
},
|
|
3004
|
+
required: ["sourceBranch", "targetBranch"]
|
|
3005
|
+
},
|
|
3006
|
+
execute: async (params, context) => {
|
|
3007
|
+
const { sourceBranch, targetBranch } = params;
|
|
3008
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
3009
|
+
try {
|
|
3010
|
+
const mergeBaseResult = await run(
|
|
3011
|
+
`git merge-base ${targetBranch} ${sourceBranch}`,
|
|
3012
|
+
{ cwd: workingDir }
|
|
3013
|
+
);
|
|
3014
|
+
const mergeBase = mergeBaseResult.stdout.trim();
|
|
3015
|
+
const mergeTreeResult = await run(
|
|
3016
|
+
`git merge-tree ${mergeBase} ${targetBranch} ${sourceBranch}`,
|
|
3017
|
+
{ cwd: workingDir }
|
|
3018
|
+
);
|
|
3019
|
+
const hasConflicts = mergeTreeResult.stdout.includes("<<<<<<<") || mergeTreeResult.stdout.includes(">>>>>>>") || mergeTreeResult.stdout.includes("=======");
|
|
3020
|
+
const conflicts = hasConflicts ? mergeTreeResult.stdout.match(/\+\+\+ b\/([^\n]+)/g)?.map((m) => m.replace(/\+\+\+ b\//, "")) : [];
|
|
3021
|
+
return {
|
|
3022
|
+
sourceBranch,
|
|
3023
|
+
targetBranch,
|
|
3024
|
+
mergeBase,
|
|
3025
|
+
hasConflicts,
|
|
3026
|
+
conflictingFiles: conflicts || [],
|
|
3027
|
+
message: hasConflicts ? `Merging ${sourceBranch} into ${targetBranch} would create conflicts` : `Merging ${sourceBranch} into ${targetBranch} should succeed without conflicts`
|
|
3028
|
+
};
|
|
3029
|
+
} catch (error) {
|
|
3030
|
+
throw new Error(`Failed to check conflicts: ${error.message}`);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
3035
|
+
function createResetBranchTool() {
|
|
3036
|
+
return {
|
|
3037
|
+
name: "reset_branch",
|
|
3038
|
+
description: "Reset a branch to match another ref (e.g., reset local main to origin/main). USE WITH CAUTION - this is a destructive operation.",
|
|
3039
|
+
parameters: {
|
|
3040
|
+
type: "object",
|
|
3041
|
+
properties: {
|
|
3042
|
+
branchName: {
|
|
3043
|
+
type: "string",
|
|
3044
|
+
description: "Name of the branch to reset"
|
|
3045
|
+
},
|
|
3046
|
+
targetRef: {
|
|
3047
|
+
type: "string",
|
|
3048
|
+
description: 'Ref to reset to (e.g., "origin/main")'
|
|
3049
|
+
},
|
|
3050
|
+
resetType: {
|
|
3051
|
+
type: "string",
|
|
3052
|
+
description: 'Type of reset: "hard" (discard changes), "soft" (keep changes staged), "mixed" (keep changes unstaged)',
|
|
3053
|
+
enum: ["hard", "soft", "mixed"],
|
|
3054
|
+
default: "hard"
|
|
3055
|
+
}
|
|
3056
|
+
},
|
|
3057
|
+
required: ["branchName", "targetRef"]
|
|
3058
|
+
},
|
|
3059
|
+
execute: async (params, context) => {
|
|
3060
|
+
const { branchName, targetRef, resetType = "hard" } = params;
|
|
3061
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
3062
|
+
try {
|
|
3063
|
+
await run(`git checkout ${branchName}`, { cwd: workingDir });
|
|
3064
|
+
await run(`git reset --${resetType} ${targetRef}`, { cwd: workingDir });
|
|
3065
|
+
const headResult = await run("git rev-parse --short HEAD", { cwd: workingDir });
|
|
3066
|
+
const newHead = headResult.stdout.trim();
|
|
3067
|
+
return {
|
|
3068
|
+
branchName,
|
|
3069
|
+
targetRef,
|
|
3070
|
+
resetType,
|
|
3071
|
+
newHead,
|
|
3072
|
+
message: `Successfully reset ${branchName} to ${targetRef} (${resetType})`
|
|
3073
|
+
};
|
|
3074
|
+
} catch (error) {
|
|
3075
|
+
throw new Error(`Failed to reset branch: ${error.message}`);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
};
|
|
3079
|
+
}
|
|
3080
|
+
async function runAgenticPublish(config) {
|
|
3081
|
+
const {
|
|
3082
|
+
targetBranch,
|
|
3083
|
+
sourceBranch,
|
|
3084
|
+
issue,
|
|
3085
|
+
issueDetails,
|
|
3086
|
+
workingDirectory = process.cwd(),
|
|
3087
|
+
model = "gpt-4o",
|
|
3088
|
+
maxIterations = 10,
|
|
3089
|
+
debug = false,
|
|
3090
|
+
storage,
|
|
3091
|
+
logger: logger2,
|
|
3092
|
+
dryRun = false
|
|
3093
|
+
} = config;
|
|
3094
|
+
const toolContext = {
|
|
3095
|
+
workingDirectory,
|
|
3096
|
+
storage,
|
|
3097
|
+
logger: logger2
|
|
3098
|
+
};
|
|
3099
|
+
const tools = createToolRegistry(toolContext);
|
|
3100
|
+
tools.registerAll(createPublishTools());
|
|
3101
|
+
const systemPrompt = buildSystemPrompt(issue, dryRun);
|
|
3102
|
+
const userPrompt = buildUserPrompt({
|
|
3103
|
+
issue,
|
|
3104
|
+
targetBranch,
|
|
3105
|
+
sourceBranch,
|
|
3106
|
+
issueDetails
|
|
3107
|
+
});
|
|
3108
|
+
const messages = [
|
|
3109
|
+
{
|
|
3110
|
+
role: "system",
|
|
3111
|
+
content: systemPrompt
|
|
3112
|
+
},
|
|
3113
|
+
{
|
|
3114
|
+
role: "user",
|
|
3115
|
+
content: userPrompt
|
|
3116
|
+
}
|
|
3117
|
+
];
|
|
3118
|
+
const executor = new AgenticExecutor(logger2);
|
|
3119
|
+
const result = await executor.run({
|
|
3120
|
+
messages,
|
|
3121
|
+
tools,
|
|
3122
|
+
model,
|
|
3123
|
+
maxIterations,
|
|
3124
|
+
debug,
|
|
3125
|
+
storage,
|
|
3126
|
+
logger: logger2
|
|
3127
|
+
});
|
|
3128
|
+
const analysis = parseAgentResponse(result.finalMessage);
|
|
3129
|
+
return {
|
|
3130
|
+
success: analysis.success,
|
|
3131
|
+
message: result.finalMessage,
|
|
3132
|
+
actionsTaken: analysis.actionsTaken,
|
|
3133
|
+
iterations: result.iterations,
|
|
3134
|
+
toolCallsExecuted: result.toolCallsExecuted,
|
|
3135
|
+
requiresManualIntervention: analysis.requiresManualIntervention,
|
|
3136
|
+
manualSteps: analysis.manualSteps
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
function buildSystemPrompt(issue, dryRun) {
|
|
3140
|
+
const modeNote = dryRun ? "\n\nIMPORTANT: This is a DRY RUN. Do not use tools that make destructive changes (like reset_branch or sync_branch). Only use diagnostic tools to analyze the issue and provide recommendations." : "";
|
|
3141
|
+
return `You are an expert Git operations assistant helping to diagnose and fix issues that prevent publishing a software package.
|
|
3142
|
+
|
|
3143
|
+
Your role is to:
|
|
3144
|
+
1. Use the available tools to investigate the problem
|
|
3145
|
+
2. Analyze the root cause of the issue
|
|
3146
|
+
3. ${dryRun ? "Recommend" : "Attempt to fix"} the issue using best practices
|
|
3147
|
+
4. Provide clear explanations of what you found and ${dryRun ? "would do" : "did"}
|
|
3148
|
+
|
|
3149
|
+
Available tools allow you to:
|
|
3150
|
+
- Check git status and branch information
|
|
3151
|
+
- Analyze divergence between branches
|
|
3152
|
+
- View commit logs and diff statistics
|
|
3153
|
+
- Check for potential merge conflicts
|
|
3154
|
+
${dryRun ? "" : "- Sync branches with remote\n- Reset branches to match remote refs"}
|
|
3155
|
+
|
|
3156
|
+
Guidelines:
|
|
3157
|
+
- Always diagnose before taking action
|
|
3158
|
+
- Prefer safe, non-destructive operations when possible
|
|
3159
|
+
- If a situation requires manual intervention, clearly state why and provide steps
|
|
3160
|
+
- Be thorough in your analysis
|
|
3161
|
+
- Consider the impact of each action on the repository state
|
|
3162
|
+
|
|
3163
|
+
Current issue type: ${issue}${modeNote}`;
|
|
3164
|
+
}
|
|
3165
|
+
function buildUserPrompt(params) {
|
|
3166
|
+
const { issue, targetBranch, sourceBranch, issueDetails } = params;
|
|
3167
|
+
const issueDescriptions = {
|
|
3168
|
+
branch_sync: `The target branch '${targetBranch}' is not synchronized with its remote counterpart. This prevents the publish workflow from proceeding safely.`,
|
|
3169
|
+
uncommitted_changes: `There are uncommitted changes in the working directory on branch '${sourceBranch}'. The publish workflow requires a clean working directory.`,
|
|
3170
|
+
merge_conflicts: `There are merge conflicts between '${sourceBranch}' and '${targetBranch}' that need to be resolved before publishing.`,
|
|
3171
|
+
unknown: `An unknown issue is preventing the publish workflow from proceeding.`
|
|
3172
|
+
};
|
|
3173
|
+
const basePrompt = `I'm trying to run a publish workflow that will:
|
|
3174
|
+
1. Create a release from source branch '${sourceBranch}'
|
|
3175
|
+
2. Merge it into target branch '${targetBranch}'
|
|
3176
|
+
3. Publish the package
|
|
3177
|
+
|
|
3178
|
+
However, I'm encountering an issue:
|
|
3179
|
+
|
|
3180
|
+
${issueDescriptions[issue] || issueDescriptions.unknown}`;
|
|
3181
|
+
const detailsSection = issueDetails ? `
|
|
3182
|
+
|
|
3183
|
+
Additional details:
|
|
3184
|
+
${issueDetails}` : "";
|
|
3185
|
+
return `${basePrompt}${detailsSection}
|
|
3186
|
+
|
|
3187
|
+
Please investigate this issue and ${process.env.DRY_RUN ? "recommend how to fix it" : "attempt to fix it if possible"}. Use the available tools to:
|
|
3188
|
+
|
|
3189
|
+
1. Diagnose the exact problem
|
|
3190
|
+
2. Understand what caused it (e.g., what commits are causing divergence)
|
|
3191
|
+
3. ${process.env.DRY_RUN ? "Recommend a solution" : "Attempt to resolve it safely"}
|
|
3192
|
+
4. Explain what ${process.env.DRY_RUN ? "should be" : "was"} done and why
|
|
3193
|
+
|
|
3194
|
+
If the issue requires manual intervention, please explain why and provide clear steps.`;
|
|
3195
|
+
}
|
|
3196
|
+
function parseAgentResponse(message) {
|
|
3197
|
+
const successIndicators = [
|
|
3198
|
+
"successfully",
|
|
3199
|
+
"resolved",
|
|
3200
|
+
"fixed",
|
|
3201
|
+
"synced",
|
|
3202
|
+
"safe to proceed",
|
|
3203
|
+
"ready to publish"
|
|
3204
|
+
];
|
|
3205
|
+
const manualInterventionIndicators = [
|
|
3206
|
+
"requires manual",
|
|
3207
|
+
"manual intervention",
|
|
3208
|
+
"cannot automatically",
|
|
3209
|
+
"you will need to",
|
|
3210
|
+
"please manually"
|
|
3211
|
+
];
|
|
3212
|
+
const messageLower = message.toLowerCase();
|
|
3213
|
+
const hasSuccessIndicator = successIndicators.some((indicator) => messageLower.includes(indicator));
|
|
3214
|
+
const hasManualIndicator = manualInterventionIndicators.some((indicator) => messageLower.includes(indicator));
|
|
3215
|
+
const actionsTaken = [];
|
|
3216
|
+
const actionPatterns = [
|
|
3217
|
+
/(?:I |We )?(checked|analyzed|found|discovered|synced|reset|merged|fetched|pulled|compared|investigated)\s+([^.\n]+)/gi,
|
|
3218
|
+
/(?:Successfully|Successfully)\s+([^.\n]+)/gi
|
|
3219
|
+
];
|
|
3220
|
+
for (const pattern of actionPatterns) {
|
|
3221
|
+
const matches = message.matchAll(pattern);
|
|
3222
|
+
for (const match of matches) {
|
|
3223
|
+
actionsTaken.push(match[0].trim());
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
const manualSteps = [];
|
|
3227
|
+
const stepPatterns = [
|
|
3228
|
+
/(?:Step \d+:|^\d+\.)\s*(.+)$/gim,
|
|
3229
|
+
/^[-*]\s+(.+)$/gim
|
|
3230
|
+
];
|
|
3231
|
+
if (hasManualIndicator) {
|
|
3232
|
+
for (const pattern of stepPatterns) {
|
|
3233
|
+
const matches = message.matchAll(pattern);
|
|
3234
|
+
for (const match of matches) {
|
|
3235
|
+
const step = match[1]?.trim();
|
|
3236
|
+
if (step && step.length > 10) {
|
|
3237
|
+
manualSteps.push(step);
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
return {
|
|
3243
|
+
success: hasSuccessIndicator && !hasManualIndicator,
|
|
3244
|
+
actionsTaken: [...new Set(actionsTaken)],
|
|
3245
|
+
// Remove duplicates
|
|
3246
|
+
requiresManualIntervention: hasManualIndicator,
|
|
3247
|
+
manualSteps: [...new Set(manualSteps)]
|
|
3248
|
+
// Remove duplicates
|
|
3249
|
+
};
|
|
3250
|
+
}
|
|
3251
|
+
function formatAgenticPublishResult(result) {
|
|
3252
|
+
const lines = [];
|
|
3253
|
+
lines.push("");
|
|
3254
|
+
lines.push("═══════════════════════════════════════════════════════════");
|
|
3255
|
+
lines.push(" AGENTIC PUBLISH RECOVERY REPORT");
|
|
3256
|
+
lines.push("═══════════════════════════════════════════════════════════");
|
|
3257
|
+
lines.push("");
|
|
3258
|
+
if (result.success) {
|
|
3259
|
+
lines.push("✅ Status: RESOLVED");
|
|
3260
|
+
} else if (result.requiresManualIntervention) {
|
|
3261
|
+
lines.push("⚠️ Status: MANUAL INTERVENTION REQUIRED");
|
|
3262
|
+
} else {
|
|
3263
|
+
lines.push("❌ Status: UNRESOLVED");
|
|
3264
|
+
}
|
|
3265
|
+
lines.push("");
|
|
3266
|
+
lines.push(`Iterations: ${result.iterations}`);
|
|
3267
|
+
lines.push(`Tools executed: ${result.toolCallsExecuted}`);
|
|
3268
|
+
lines.push("");
|
|
3269
|
+
if (result.actionsTaken.length > 0) {
|
|
3270
|
+
lines.push("Actions taken:");
|
|
3271
|
+
for (const action of result.actionsTaken) {
|
|
3272
|
+
lines.push(` • ${action}`);
|
|
3273
|
+
}
|
|
3274
|
+
lines.push("");
|
|
3275
|
+
}
|
|
3276
|
+
if (result.requiresManualIntervention && result.manualSteps && result.manualSteps.length > 0) {
|
|
3277
|
+
lines.push("Manual steps required:");
|
|
3278
|
+
for (let i = 0; i < result.manualSteps.length; i++) {
|
|
3279
|
+
lines.push(` ${i + 1}. ${result.manualSteps[i]}`);
|
|
3280
|
+
}
|
|
3281
|
+
lines.push("");
|
|
3282
|
+
}
|
|
3283
|
+
lines.push("Detailed analysis:");
|
|
3284
|
+
lines.push("───────────────────────────────────────────────────────────");
|
|
3285
|
+
lines.push(result.message);
|
|
3286
|
+
lines.push("───────────────────────────────────────────────────────────");
|
|
3287
|
+
lines.push("");
|
|
3288
|
+
if (result.success) {
|
|
3289
|
+
lines.push("You can now retry the publish command.");
|
|
3290
|
+
} else if (result.requiresManualIntervention) {
|
|
3291
|
+
lines.push("Please complete the manual steps above, then retry the publish command.");
|
|
3292
|
+
} else {
|
|
3293
|
+
lines.push("The issue could not be resolved automatically. Please review the analysis above.");
|
|
3294
|
+
}
|
|
3295
|
+
lines.push("");
|
|
3296
|
+
return lines.join("\n");
|
|
3297
|
+
}
|
|
3298
|
+
function createConversationLogger(config) {
|
|
3299
|
+
const {
|
|
3300
|
+
outputDir,
|
|
3301
|
+
format = "json",
|
|
3302
|
+
storage,
|
|
3303
|
+
logger: logger2
|
|
3304
|
+
} = config;
|
|
3305
|
+
return {
|
|
3306
|
+
/**
|
|
3307
|
+
* Save a conversation to disk
|
|
3308
|
+
*/
|
|
3309
|
+
async save(conversationId, messages, metadata) {
|
|
3310
|
+
try {
|
|
3311
|
+
if (!storage) {
|
|
3312
|
+
throw new Error("Storage adapter required");
|
|
3313
|
+
}
|
|
3314
|
+
const conversation = {
|
|
3315
|
+
id: conversationId,
|
|
3316
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3317
|
+
messages,
|
|
3318
|
+
metadata: metadata || {}
|
|
3319
|
+
};
|
|
3320
|
+
const filename = `${conversationId}.${format}`;
|
|
3321
|
+
const filepath = path__default.join(outputDir, filename);
|
|
3322
|
+
const content = format === "json" ? JSON.stringify(conversation, null, 2) : formatConversationAsMarkdown(conversation);
|
|
3323
|
+
await storage.writeOutput(filename, content);
|
|
3324
|
+
if (logger2) {
|
|
3325
|
+
logger2.debug(`Logged conversation ${conversationId} to ${filepath}`);
|
|
3326
|
+
}
|
|
3327
|
+
return filepath;
|
|
3328
|
+
} catch (error) {
|
|
3329
|
+
if (logger2) {
|
|
3330
|
+
logger2.warn(`Failed to log conversation ${conversationId}: ${error.message}`);
|
|
3331
|
+
}
|
|
3332
|
+
throw error;
|
|
3333
|
+
}
|
|
3334
|
+
},
|
|
3335
|
+
/**
|
|
3336
|
+
* Load a conversation from disk
|
|
3337
|
+
*/
|
|
3338
|
+
async load(conversationId) {
|
|
3339
|
+
try {
|
|
3340
|
+
if (!storage) {
|
|
3341
|
+
throw new Error("Storage adapter required");
|
|
3342
|
+
}
|
|
3343
|
+
const filename = `${conversationId}.${format}`;
|
|
3344
|
+
const content = await storage.readFile(path__default.join(outputDir, filename), "utf-8");
|
|
3345
|
+
let conversation;
|
|
3346
|
+
if (format === "json") {
|
|
3347
|
+
try {
|
|
3348
|
+
conversation = JSON.parse(content);
|
|
3349
|
+
} catch (parseError) {
|
|
3350
|
+
throw new Error(`Failed to parse conversation JSON: ${parseError.message}`);
|
|
3351
|
+
}
|
|
3352
|
+
} else {
|
|
3353
|
+
conversation = content;
|
|
3354
|
+
}
|
|
3355
|
+
if (logger2) {
|
|
3356
|
+
logger2.info(`Loaded conversation ${conversationId}`);
|
|
3357
|
+
}
|
|
3358
|
+
return conversation;
|
|
3359
|
+
} catch (error) {
|
|
3360
|
+
if (logger2) {
|
|
3361
|
+
logger2.warn(`Failed to load conversation ${conversationId}: ${error.message}`);
|
|
3362
|
+
}
|
|
3363
|
+
throw error;
|
|
3364
|
+
}
|
|
3365
|
+
},
|
|
3366
|
+
/**
|
|
3367
|
+
* Get conversation summary
|
|
3368
|
+
*/
|
|
3369
|
+
async getSummary(conversationId) {
|
|
3370
|
+
try {
|
|
3371
|
+
const conversation = await this.load(conversationId);
|
|
3372
|
+
return {
|
|
3373
|
+
id: conversation.id,
|
|
3374
|
+
timestamp: conversation.timestamp,
|
|
3375
|
+
messageCount: conversation.messages?.length || 0,
|
|
3376
|
+
metadata: conversation.metadata
|
|
3377
|
+
};
|
|
3378
|
+
} catch (error) {
|
|
3379
|
+
if (logger2) {
|
|
3380
|
+
logger2.warn(`Failed to get summary for ${conversationId}: ${error.message}`);
|
|
3381
|
+
}
|
|
3382
|
+
throw error;
|
|
3383
|
+
}
|
|
3384
|
+
},
|
|
3385
|
+
/**
|
|
3386
|
+
* Create riotprompt ConversationLogger instance for advanced usage
|
|
3387
|
+
*/
|
|
3388
|
+
createRiotLogger() {
|
|
3389
|
+
const logConfig = {
|
|
3390
|
+
enabled: true,
|
|
3391
|
+
outputPath: outputDir,
|
|
3392
|
+
format,
|
|
3393
|
+
includeMetadata: true
|
|
3394
|
+
};
|
|
3395
|
+
return new ConversationLogger(logConfig, logger2);
|
|
3396
|
+
}
|
|
3397
|
+
};
|
|
3398
|
+
}
|
|
3399
|
+
function formatConversationAsMarkdown(conversation) {
|
|
3400
|
+
const lines = [];
|
|
3401
|
+
lines.push(`# Conversation: ${conversation.id}`);
|
|
3402
|
+
lines.push("");
|
|
3403
|
+
lines.push(`**Timestamp:** ${conversation.timestamp}`);
|
|
3404
|
+
if (conversation.metadata) {
|
|
3405
|
+
lines.push("");
|
|
3406
|
+
lines.push("## Metadata");
|
|
3407
|
+
lines.push("```json");
|
|
3408
|
+
lines.push(JSON.stringify(conversation.metadata, null, 2));
|
|
3409
|
+
lines.push("```");
|
|
3410
|
+
}
|
|
3411
|
+
lines.push("");
|
|
3412
|
+
lines.push("## Messages");
|
|
3413
|
+
lines.push("");
|
|
3414
|
+
for (let i = 0; i < conversation.messages.length; i++) {
|
|
3415
|
+
const msg = conversation.messages[i];
|
|
3416
|
+
lines.push(`### Message ${i + 1}: ${msg.role}`);
|
|
3417
|
+
lines.push("");
|
|
3418
|
+
lines.push(msg.content || "(no content)");
|
|
3419
|
+
lines.push("");
|
|
3420
|
+
}
|
|
3421
|
+
return lines.join("\n");
|
|
3422
|
+
}
|
|
3423
|
+
function generateConversationId(command) {
|
|
3424
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").split(".")[0];
|
|
3425
|
+
return `${command}-${timestamp}`;
|
|
3426
|
+
}
|
|
3427
|
+
async function generateReflectionReport(options) {
|
|
3428
|
+
const {
|
|
3429
|
+
iterations,
|
|
3430
|
+
toolCallsExecuted,
|
|
3431
|
+
maxIterations,
|
|
3432
|
+
toolMetrics,
|
|
3433
|
+
conversationHistory,
|
|
3434
|
+
commitMessage,
|
|
3435
|
+
suggestedSplits,
|
|
3436
|
+
releaseNotes,
|
|
3437
|
+
logger: logger2
|
|
3438
|
+
} = options;
|
|
3439
|
+
const sections = [];
|
|
3440
|
+
sections.push("# Agentic Workflow - Self-Reflection Report");
|
|
3441
|
+
sections.push("");
|
|
3442
|
+
sections.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
3443
|
+
sections.push("");
|
|
3444
|
+
sections.push("## Execution Summary");
|
|
3445
|
+
sections.push("");
|
|
3446
|
+
sections.push(`- **Iterations**: ${iterations}${iterations >= maxIterations ? " (max reached)" : ""}`);
|
|
3447
|
+
sections.push(`- **Tool Calls**: ${toolCallsExecuted}`);
|
|
3448
|
+
sections.push(`- **Unique Tools**: ${new Set(toolMetrics.map((m) => m.name)).size}`);
|
|
3449
|
+
sections.push("");
|
|
3450
|
+
const toolStats = calculateToolStats(toolMetrics);
|
|
3451
|
+
sections.push("## Tool Effectiveness Analysis");
|
|
3452
|
+
sections.push("");
|
|
3453
|
+
if (toolStats.size === 0) {
|
|
3454
|
+
sections.push("*No tools were executed during this run.*");
|
|
3455
|
+
} else {
|
|
3456
|
+
sections.push("| Tool | Calls | Success | Failures | Success Rate | Avg Duration |");
|
|
3457
|
+
sections.push("|------|-------|---------|----------|--------------|--------------|");
|
|
3458
|
+
for (const [toolName, stats] of Array.from(toolStats.entries()).sort((a, b) => b[1].total - a[1].total)) {
|
|
3459
|
+
const successRate = stats.total > 0 ? (stats.success / stats.total * 100).toFixed(1) : "0.0";
|
|
3460
|
+
const avgDuration = stats.total > 0 ? (stats.totalDuration / stats.total).toFixed(0) : "0";
|
|
3461
|
+
sections.push(`| ${toolName} | ${stats.total} | ${stats.success} | ${stats.failures} | ${successRate}% | ${avgDuration}ms |`);
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
sections.push("");
|
|
3465
|
+
if (toolStats.size > 0) {
|
|
3466
|
+
sections.push("### Tool Performance Insights");
|
|
3467
|
+
sections.push("");
|
|
3468
|
+
const failedTools = Array.from(toolStats.entries()).filter(([_, stats]) => stats.failures > 0);
|
|
3469
|
+
if (failedTools.length > 0) {
|
|
3470
|
+
sections.push("**Tools with Failures:**");
|
|
3471
|
+
for (const [toolName, stats] of failedTools) {
|
|
3472
|
+
const failureRate = stats.total > 0 ? (stats.failures / stats.total * 100).toFixed(1) : "0.0";
|
|
3473
|
+
sections.push(`- ${toolName}: ${stats.failures}/${stats.total} failures (${failureRate}%)`);
|
|
3474
|
+
}
|
|
3475
|
+
sections.push("");
|
|
3476
|
+
}
|
|
3477
|
+
const slowTools = Array.from(toolStats.entries()).filter(([_, stats]) => stats.total > 0 && stats.totalDuration / stats.total > 1e3).sort((a, b) => {
|
|
3478
|
+
const avgA = a[1].total > 0 ? a[1].totalDuration / a[1].total : 0;
|
|
3479
|
+
const avgB = b[1].total > 0 ? b[1].totalDuration / b[1].total : 0;
|
|
3480
|
+
return avgB - avgA;
|
|
3481
|
+
});
|
|
3482
|
+
if (slowTools.length > 0) {
|
|
3483
|
+
sections.push("**Slow Tools (>1s average):**");
|
|
3484
|
+
for (const [toolName, stats] of slowTools) {
|
|
3485
|
+
const avgDuration = stats.total > 0 ? (stats.totalDuration / stats.total / 1e3).toFixed(2) : "0.00";
|
|
3486
|
+
sections.push(`- ${toolName}: ${avgDuration}s average`);
|
|
3487
|
+
}
|
|
3488
|
+
sections.push("");
|
|
3489
|
+
}
|
|
3490
|
+
sections.push("**Most Frequently Used:**");
|
|
3491
|
+
const topTools = Array.from(toolStats.entries()).slice(0, 3);
|
|
3492
|
+
for (const [toolName, stats] of topTools) {
|
|
3493
|
+
sections.push(`- ${toolName}: ${stats.total} calls`);
|
|
3494
|
+
}
|
|
3495
|
+
sections.push("");
|
|
3496
|
+
}
|
|
3497
|
+
sections.push("## Recommendations for Improvement");
|
|
3498
|
+
sections.push("");
|
|
3499
|
+
const recommendations = generateRecommendations(options, toolStats);
|
|
3500
|
+
if (recommendations.length === 0) {
|
|
3501
|
+
sections.push("*No specific recommendations at this time. Execution appears optimal.*");
|
|
3502
|
+
} else {
|
|
3503
|
+
for (const rec of recommendations) {
|
|
3504
|
+
sections.push(rec);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
sections.push("");
|
|
3508
|
+
sections.push("## Detailed Execution Timeline");
|
|
3509
|
+
sections.push("");
|
|
3510
|
+
if (toolMetrics.length === 0) {
|
|
3511
|
+
sections.push("*No tool execution timeline available.*");
|
|
3512
|
+
} else {
|
|
3513
|
+
sections.push("| Time | Iteration | Tool | Result | Duration |");
|
|
3514
|
+
sections.push("|------|-----------|------|--------|----------|");
|
|
3515
|
+
for (const metric of toolMetrics) {
|
|
3516
|
+
const time = new Date(metric.timestamp).toLocaleTimeString();
|
|
3517
|
+
const result = metric.success ? "✅ Success" : `❌ ${metric.error || "Failed"}`;
|
|
3518
|
+
sections.push(`| ${time} | ${metric.iteration} | ${metric.name} | ${result} | ${metric.duration}ms |`);
|
|
3519
|
+
}
|
|
3520
|
+
sections.push("");
|
|
3521
|
+
}
|
|
3522
|
+
if (conversationHistory && conversationHistory.length > 0) {
|
|
3523
|
+
sections.push("## Conversation History");
|
|
3524
|
+
sections.push("");
|
|
3525
|
+
sections.push("<details>");
|
|
3526
|
+
sections.push("<summary>Click to expand full agentic interaction</summary>");
|
|
3527
|
+
sections.push("");
|
|
3528
|
+
sections.push("```json");
|
|
3529
|
+
sections.push(JSON.stringify(conversationHistory, null, 2));
|
|
3530
|
+
sections.push("```");
|
|
3531
|
+
sections.push("");
|
|
3532
|
+
sections.push("</details>");
|
|
3533
|
+
sections.push("");
|
|
3534
|
+
}
|
|
3535
|
+
if (commitMessage) {
|
|
3536
|
+
sections.push("## Generated Commit Message");
|
|
3537
|
+
sections.push("");
|
|
3538
|
+
sections.push("```");
|
|
3539
|
+
sections.push(commitMessage);
|
|
3540
|
+
sections.push("```");
|
|
3541
|
+
sections.push("");
|
|
3542
|
+
if (suggestedSplits && suggestedSplits.length > 1) {
|
|
3543
|
+
sections.push("## Suggested Commit Splits");
|
|
3544
|
+
sections.push("");
|
|
3545
|
+
for (let i = 0; i < suggestedSplits.length; i++) {
|
|
3546
|
+
const split = suggestedSplits[i];
|
|
3547
|
+
sections.push(`### Split ${i + 1}`);
|
|
3548
|
+
sections.push("");
|
|
3549
|
+
sections.push(`**Files**: ${split.files.join(", ")}`);
|
|
3550
|
+
sections.push("");
|
|
3551
|
+
sections.push(`**Rationale**: ${split.rationale}`);
|
|
3552
|
+
sections.push("");
|
|
3553
|
+
sections.push(`**Message**:`);
|
|
3554
|
+
sections.push("```");
|
|
3555
|
+
sections.push(split.message);
|
|
3556
|
+
sections.push("```");
|
|
3557
|
+
sections.push("");
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
if (releaseNotes) {
|
|
3562
|
+
sections.push("## Generated Release Notes");
|
|
3563
|
+
sections.push("");
|
|
3564
|
+
sections.push("### Title");
|
|
3565
|
+
sections.push("```");
|
|
3566
|
+
sections.push(releaseNotes.title);
|
|
3567
|
+
sections.push("```");
|
|
3568
|
+
sections.push("");
|
|
3569
|
+
sections.push("### Body");
|
|
3570
|
+
sections.push("```markdown");
|
|
3571
|
+
sections.push(releaseNotes.body);
|
|
3572
|
+
sections.push("```");
|
|
3573
|
+
sections.push("");
|
|
3574
|
+
}
|
|
3575
|
+
if (logger2) {
|
|
3576
|
+
logger2.debug(`Generated reflection report with ${toolStats.size} unique tools`);
|
|
3577
|
+
}
|
|
3578
|
+
return sections.join("\n");
|
|
3579
|
+
}
|
|
3580
|
+
function calculateToolStats(toolMetrics) {
|
|
3581
|
+
const stats = /* @__PURE__ */ new Map();
|
|
3582
|
+
for (const metric of toolMetrics) {
|
|
3583
|
+
if (!stats.has(metric.name)) {
|
|
3584
|
+
stats.set(metric.name, { total: 0, success: 0, failures: 0, totalDuration: 0 });
|
|
3585
|
+
}
|
|
3586
|
+
const toolStat = stats.get(metric.name);
|
|
3587
|
+
toolStat.total++;
|
|
3588
|
+
toolStat.totalDuration += metric.duration;
|
|
3589
|
+
if (metric.success) {
|
|
3590
|
+
toolStat.success++;
|
|
3591
|
+
} else {
|
|
3592
|
+
toolStat.failures++;
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
return stats;
|
|
3596
|
+
}
|
|
3597
|
+
function generateRecommendations(options, toolStats) {
|
|
3598
|
+
const recommendations = [];
|
|
3599
|
+
const toolsWithFailures = Array.from(toolStats.entries()).filter(([_, stats]) => stats.failures > 0);
|
|
3600
|
+
if (toolsWithFailures.length > 0) {
|
|
3601
|
+
recommendations.push("- **Tool Failures**: Investigate and fix tools that are failing. This may indicate issues with error handling or tool implementation.");
|
|
3602
|
+
}
|
|
3603
|
+
const slowTools = Array.from(toolStats.entries()).filter(([_, stats]) => stats.total > 0 && stats.totalDuration / stats.total > 1e3);
|
|
3604
|
+
if (slowTools.length > 0) {
|
|
3605
|
+
recommendations.push("- **Performance**: Consider optimizing slow tools or caching results to improve execution speed.");
|
|
3606
|
+
}
|
|
3607
|
+
if (options.iterations >= options.maxIterations) {
|
|
3608
|
+
recommendations.push("- **Max Iterations Reached**: The agent reached maximum iterations. Consider increasing the limit or improving tool efficiency to allow the agent to complete naturally.");
|
|
3609
|
+
}
|
|
3610
|
+
const underutilizedTools = Array.from(toolStats.entries()).filter(([_, stats]) => stats.total === 1);
|
|
3611
|
+
if (underutilizedTools.length > 3) {
|
|
3612
|
+
recommendations.push("- **Underutilized Tools**: Many tools were called only once. Consider whether all tools are necessary or if the agent needs better guidance on when to use them.");
|
|
3613
|
+
}
|
|
3614
|
+
const uniqueTools = toolStats.size;
|
|
3615
|
+
if (uniqueTools === 1 && options.toolCallsExecuted > 3) {
|
|
3616
|
+
recommendations.push("- **Low Tool Diversity**: Only one tool was used despite multiple calls. Consider if the agent needs better guidance to use a variety of tools.");
|
|
3617
|
+
}
|
|
3618
|
+
return recommendations;
|
|
3619
|
+
}
|
|
3620
|
+
async function saveReflectionReport(report, outputPath, storage) {
|
|
3621
|
+
if (!storage) {
|
|
3622
|
+
throw new Error("Storage adapter required to save report");
|
|
3623
|
+
}
|
|
3624
|
+
const filename = outputPath.split("/").pop() || "reflection.md";
|
|
3625
|
+
await storage.writeOutput(filename, report);
|
|
3626
|
+
}
|
|
3627
|
+
function createMetricsCollector(config = {}) {
|
|
3628
|
+
const { logger: logger2 } = config;
|
|
3629
|
+
const collector = new MetricsCollector(logger2);
|
|
3630
|
+
const toolMetrics = [];
|
|
3631
|
+
return {
|
|
3632
|
+
/**
|
|
3633
|
+
* Record a tool execution
|
|
3634
|
+
*/
|
|
3635
|
+
recordToolExecution(metric) {
|
|
3636
|
+
toolMetrics.push(metric);
|
|
3637
|
+
collector.recordToolCall(
|
|
3638
|
+
metric.name,
|
|
3639
|
+
metric.iteration,
|
|
3640
|
+
metric.duration,
|
|
3641
|
+
metric.success,
|
|
3642
|
+
metric.error
|
|
3643
|
+
);
|
|
3644
|
+
if (logger2) {
|
|
3645
|
+
const status = metric.success ? "success" : "failed";
|
|
3646
|
+
logger2.debug(`Tool ${metric.name} ${status} in ${metric.duration}ms`);
|
|
3647
|
+
}
|
|
3648
|
+
},
|
|
3649
|
+
/**
|
|
3650
|
+
* Record an iteration
|
|
3651
|
+
*/
|
|
3652
|
+
recordIteration() {
|
|
3653
|
+
collector.incrementIteration();
|
|
3654
|
+
if (logger2) {
|
|
3655
|
+
logger2.debug(`Iteration incremented`);
|
|
3656
|
+
}
|
|
3657
|
+
},
|
|
3658
|
+
/**
|
|
3659
|
+
* Get tool metrics in legacy format (for backward compatibility)
|
|
3660
|
+
*/
|
|
3661
|
+
getToolMetrics() {
|
|
3662
|
+
return [...toolMetrics];
|
|
3663
|
+
},
|
|
3664
|
+
/**
|
|
3665
|
+
* Generate execution metrics report
|
|
3666
|
+
*/
|
|
3667
|
+
generateReport() {
|
|
3668
|
+
return collector.getMetrics([]);
|
|
3669
|
+
},
|
|
3670
|
+
/**
|
|
3671
|
+
* Get summary statistics
|
|
3672
|
+
*/
|
|
3673
|
+
getSummary() {
|
|
3674
|
+
const report = collector.getMetrics([]);
|
|
3675
|
+
return {
|
|
3676
|
+
totalIterations: report.iterations,
|
|
3677
|
+
totalToolCalls: report.toolCallsExecuted,
|
|
3678
|
+
totalDuration: report.totalDuration,
|
|
3679
|
+
toolMetrics: report.toolMetrics,
|
|
3680
|
+
toolStats: report.toolStats
|
|
3681
|
+
};
|
|
3682
|
+
},
|
|
3683
|
+
/**
|
|
3684
|
+
* Reset all metrics
|
|
3685
|
+
*/
|
|
3686
|
+
reset() {
|
|
3687
|
+
toolMetrics.length = 0;
|
|
3688
|
+
}
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
2458
3691
|
export {
|
|
2459
3692
|
AgenticExecutor,
|
|
2460
3693
|
OpenAIError,
|
|
2461
3694
|
STANDARD_CHOICES,
|
|
2462
3695
|
SecureTempFile,
|
|
3696
|
+
TemplateNames,
|
|
2463
3697
|
ToolRegistry,
|
|
2464
3698
|
cleanupTempFile,
|
|
2465
3699
|
createCommitPrompt,
|
|
2466
3700
|
createCommitTools,
|
|
2467
3701
|
createCompletion,
|
|
2468
3702
|
createCompletionWithRetry,
|
|
3703
|
+
createConversationLogger,
|
|
3704
|
+
createMetricsCollector,
|
|
2469
3705
|
createNoOpLogger,
|
|
3706
|
+
createPublishTools,
|
|
2470
3707
|
createReleasePrompt,
|
|
2471
3708
|
createReleaseTools,
|
|
2472
3709
|
createReviewPrompt,
|
|
2473
3710
|
createSecureTempFile,
|
|
2474
3711
|
createToolRegistry,
|
|
2475
3712
|
editContentInEditor,
|
|
3713
|
+
formatAgenticPublishResult,
|
|
3714
|
+
generateConversationId,
|
|
3715
|
+
generateReflectionReport,
|
|
2476
3716
|
getLLMFeedbackInEditor,
|
|
2477
3717
|
getLogger,
|
|
2478
3718
|
getModelForCommand,
|
|
2479
3719
|
getOpenAIReasoningForCommand,
|
|
2480
3720
|
getUserChoice,
|
|
2481
3721
|
getUserTextInput,
|
|
3722
|
+
initializeTemplates,
|
|
2482
3723
|
isRateLimitError,
|
|
2483
3724
|
isTokenLimitError,
|
|
2484
3725
|
requireTTY,
|
|
2485
3726
|
runAgentic,
|
|
2486
3727
|
runAgenticCommit,
|
|
3728
|
+
runAgenticPublish,
|
|
2487
3729
|
runAgenticRelease,
|
|
3730
|
+
saveReflectionReport,
|
|
2488
3731
|
setLogger,
|
|
2489
3732
|
transcribeAudio,
|
|
2490
3733
|
tryLoadWinston
|