@eldrforge/ai-service 0.1.13 → 0.1.15
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 +1112 -226
- package/dist/index.js +1325 -108
- 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/examples/01-simple-commit.ts +80 -0
- package/examples/02-release-notes.ts +124 -0
- package/examples/03-interactive-commit.ts +150 -0
- package/examples/04-custom-storage.ts +162 -0
- package/examples/05-custom-tools.ts +243 -0
- package/examples/README.md +320 -0
- package/package.json +6 -5
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: {
|
|
@@ -1136,7 +1204,21 @@ function createGetFileHistoryTool$1() {
|
|
|
1136
1204
|
function createGetFileContentTool$1() {
|
|
1137
1205
|
return {
|
|
1138
1206
|
name: "get_file_content",
|
|
1139
|
-
description: "Get the complete current content of a file to understand context around changes",
|
|
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: {
|
|
@@ -1166,6 +1248,11 @@ function createGetFileContentTool$1() {
|
|
|
1166
1248
|
}
|
|
1167
1249
|
return content;
|
|
1168
1250
|
} catch (error) {
|
|
1251
|
+
if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
|
|
1252
|
+
return `File not found: ${filePath}
|
|
1253
|
+
|
|
1254
|
+
This file may have been deleted in this release or does not exist in the current working tree. Check the diff to see if this file was removed.`;
|
|
1255
|
+
}
|
|
1169
1256
|
throw new Error(`Failed to read file: ${error.message}`);
|
|
1170
1257
|
}
|
|
1171
1258
|
}
|
|
@@ -1175,6 +1262,20 @@ function createSearchCodebaseTool$1() {
|
|
|
1175
1262
|
return {
|
|
1176
1263
|
name: "search_codebase",
|
|
1177
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
|
+
],
|
|
1178
1279
|
parameters: {
|
|
1179
1280
|
type: "object",
|
|
1180
1281
|
properties: {
|
|
@@ -1207,7 +1308,7 @@ function createSearchCodebaseTool$1() {
|
|
|
1207
1308
|
const output = await run(command, { cwd: workingDir });
|
|
1208
1309
|
return output.stdout || "No matches found";
|
|
1209
1310
|
} catch (error) {
|
|
1210
|
-
if (error.message.includes("exit code 1")) {
|
|
1311
|
+
if (error.message.includes("exit code 1") || error.stderr?.includes("did not match any file")) {
|
|
1211
1312
|
return "No matches found";
|
|
1212
1313
|
}
|
|
1213
1314
|
throw new Error(`Search failed: ${error.message}`);
|
|
@@ -1219,6 +1320,9 @@ function createGetRelatedTestsTool$1() {
|
|
|
1219
1320
|
return {
|
|
1220
1321
|
name: "get_related_tests",
|
|
1221
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" }],
|
|
1222
1326
|
parameters: {
|
|
1223
1327
|
type: "object",
|
|
1224
1328
|
properties: {
|
|
@@ -1265,6 +1369,9 @@ function createGetFileDependenciesTool$1() {
|
|
|
1265
1369
|
return {
|
|
1266
1370
|
name: "get_file_dependencies",
|
|
1267
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" }],
|
|
1268
1375
|
parameters: {
|
|
1269
1376
|
type: "object",
|
|
1270
1377
|
properties: {
|
|
@@ -1308,6 +1415,9 @@ function createAnalyzeDiffSectionTool$1() {
|
|
|
1308
1415
|
return {
|
|
1309
1416
|
name: "analyze_diff_section",
|
|
1310
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" }],
|
|
1311
1421
|
parameters: {
|
|
1312
1422
|
type: "object",
|
|
1313
1423
|
properties: {
|
|
@@ -1356,6 +1466,9 @@ function createGetRecentCommitsTool$1() {
|
|
|
1356
1466
|
return {
|
|
1357
1467
|
name: "get_recent_commits",
|
|
1358
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" }],
|
|
1359
1472
|
parameters: {
|
|
1360
1473
|
type: "object",
|
|
1361
1474
|
properties: {
|
|
@@ -1395,6 +1508,15 @@ function createGroupFilesByConcernTool$1() {
|
|
|
1395
1508
|
return {
|
|
1396
1509
|
name: "group_files_by_concern",
|
|
1397
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
|
+
],
|
|
1398
1520
|
parameters: {
|
|
1399
1521
|
type: "object",
|
|
1400
1522
|
properties: {
|
|
@@ -1453,7 +1575,8 @@ async function runAgenticCommit(config) {
|
|
|
1453
1575
|
debugResponseFile,
|
|
1454
1576
|
storage,
|
|
1455
1577
|
logger: logger2,
|
|
1456
|
-
openaiReasoning
|
|
1578
|
+
openaiReasoning,
|
|
1579
|
+
tokenBudget
|
|
1457
1580
|
} = config;
|
|
1458
1581
|
const toolRegistry = createToolRegistry({
|
|
1459
1582
|
workingDirectory: process.cwd(),
|
|
@@ -1462,7 +1585,13 @@ async function runAgenticCommit(config) {
|
|
|
1462
1585
|
});
|
|
1463
1586
|
const tools = createCommitTools();
|
|
1464
1587
|
toolRegistry.registerAll(tools);
|
|
1465
|
-
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);
|
|
1466
1595
|
const userMessage = buildUserMessage$1(changedFiles, diffContent, userDirection, logContext);
|
|
1467
1596
|
const messages = [
|
|
1468
1597
|
{ role: "system", content: systemPrompt },
|
|
@@ -1478,7 +1607,13 @@ async function runAgenticCommit(config) {
|
|
|
1478
1607
|
debugResponseFile,
|
|
1479
1608
|
storage,
|
|
1480
1609
|
logger: logger2,
|
|
1481
|
-
openaiReasoning
|
|
1610
|
+
openaiReasoning,
|
|
1611
|
+
tokenBudget: tokenBudget || {
|
|
1612
|
+
max: 15e4,
|
|
1613
|
+
reserveForResponse: 4e3,
|
|
1614
|
+
strategy: "fifo",
|
|
1615
|
+
onBudgetExceeded: "compress"
|
|
1616
|
+
}
|
|
1482
1617
|
};
|
|
1483
1618
|
const result = await runAgentic(agenticConfig);
|
|
1484
1619
|
const parsed = parseAgenticResult$1(result.finalMessage);
|
|
@@ -1491,32 +1626,29 @@ async function runAgenticCommit(config) {
|
|
|
1491
1626
|
toolMetrics: result.toolMetrics
|
|
1492
1627
|
};
|
|
1493
1628
|
}
|
|
1494
|
-
function buildSystemPrompt$
|
|
1629
|
+
function buildSystemPrompt$2(toolGuidance) {
|
|
1495
1630
|
return `You are an expert software engineer tasked with generating meaningful commit messages.
|
|
1496
1631
|
|
|
1497
|
-
|
|
1498
|
-
- get_file_history: View commit history for files
|
|
1499
|
-
- get_file_content: Read full file contents
|
|
1500
|
-
- search_codebase: Search for patterns across the codebase
|
|
1501
|
-
- get_related_tests: Find test files related to changes
|
|
1502
|
-
- get_file_dependencies: Understand file dependencies and imports
|
|
1503
|
-
- analyze_diff_section: Get expanded context around specific changes
|
|
1504
|
-
- get_recent_commits: See recent commits to the same files
|
|
1505
|
-
- group_files_by_concern: Suggest logical groupings of changed files
|
|
1632
|
+
${toolGuidance}
|
|
1506
1633
|
|
|
1507
|
-
|
|
1508
|
-
1. Analyze the changed files and diff to understand the scope
|
|
1509
|
-
2. Use tools to investigate specific changes that need more context
|
|
1510
|
-
3. Identify if changes represent one cohesive commit or multiple logical commits
|
|
1511
|
-
4. Generate a commit message (or multiple if splits suggested) that accurately describes changes
|
|
1634
|
+
## Investigation Strategy
|
|
1512
1635
|
|
|
1513
|
-
|
|
1514
|
-
- Use tools
|
|
1515
|
-
|
|
1516
|
-
|
|
1636
|
+
For simple changes (1-3 files, obvious purpose):
|
|
1637
|
+
- Use 1-2 tools: get_recent_commits to avoid duplicates, get_related_tests if logic changed
|
|
1638
|
+
|
|
1639
|
+
For moderate changes (4-10 files, clear theme):
|
|
1640
|
+
- Use 2-4 tools: group_files_by_concern, get_file_content for key files, get_recent_commits, get_related_tests
|
|
1641
|
+
|
|
1642
|
+
For complex changes (10+ files, or unclear purpose):
|
|
1643
|
+
- Use 4-6 tools: group_files_by_concern, get_file_history, get_file_content for key files, get_file_dependencies, get_related_tests, search_codebase
|
|
1644
|
+
|
|
1645
|
+
Always use tools to understand context - don't rely only on the diff.
|
|
1646
|
+
|
|
1647
|
+
## Guidelines
|
|
1648
|
+
- Follow conventional commit format when appropriate (feat:, fix:, refactor:, docs:, test:, chore:)
|
|
1517
1649
|
- Consider suggesting split commits for unrelated changes
|
|
1518
|
-
-
|
|
1519
|
-
-
|
|
1650
|
+
- Output only the commit message and/or splits - no conversational remarks
|
|
1651
|
+
- NEVER include phrases like "If you want" or "Let me know" or offer follow-up actions
|
|
1520
1652
|
|
|
1521
1653
|
Output format:
|
|
1522
1654
|
When you're ready to provide the final commit message, format it as:
|
|
@@ -1645,7 +1777,7 @@ function createGetFileHistoryTool() {
|
|
|
1645
1777
|
function createGetFileContentTool() {
|
|
1646
1778
|
return {
|
|
1647
1779
|
name: "get_file_content",
|
|
1648
|
-
description: "Get the complete current content of a file to understand context around changes",
|
|
1780
|
+
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).",
|
|
1649
1781
|
parameters: {
|
|
1650
1782
|
type: "object",
|
|
1651
1783
|
properties: {
|
|
@@ -1675,6 +1807,11 @@ function createGetFileContentTool() {
|
|
|
1675
1807
|
}
|
|
1676
1808
|
return content;
|
|
1677
1809
|
} catch (error) {
|
|
1810
|
+
if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
|
|
1811
|
+
return `File not found: ${filePath}
|
|
1812
|
+
|
|
1813
|
+
This file may have been deleted in this release or does not exist in the current working tree. Check the diff to see if this file was removed.`;
|
|
1814
|
+
}
|
|
1678
1815
|
throw new Error(`Failed to read file: ${error.message}`);
|
|
1679
1816
|
}
|
|
1680
1817
|
}
|
|
@@ -1716,7 +1853,7 @@ function createSearchCodebaseTool() {
|
|
|
1716
1853
|
const output = await run(command, { cwd: workingDir });
|
|
1717
1854
|
return output.stdout || "No matches found";
|
|
1718
1855
|
} catch (error) {
|
|
1719
|
-
if (error.message.includes("exit code 1")) {
|
|
1856
|
+
if (error.message.includes("exit code 1") || error.stderr?.includes("did not match any file")) {
|
|
1720
1857
|
return "No matches found";
|
|
1721
1858
|
}
|
|
1722
1859
|
throw new Error(`Search failed: ${error.message}`);
|
|
@@ -1953,6 +2090,9 @@ function createGetTagHistoryTool() {
|
|
|
1953
2090
|
return {
|
|
1954
2091
|
name: "get_tag_history",
|
|
1955
2092
|
description: "Get the history of previous release tags to understand release patterns and versioning",
|
|
2093
|
+
category: "Release Analysis",
|
|
2094
|
+
cost: "cheap",
|
|
2095
|
+
examples: [{ scenario: "Check recent version tags", params: { limit: 5, pattern: "v*" }, expectedResult: "List of recent version tags with dates" }],
|
|
1956
2096
|
parameters: {
|
|
1957
2097
|
type: "object",
|
|
1958
2098
|
properties: {
|
|
@@ -2002,6 +2142,9 @@ function createComparePreviousReleaseTool() {
|
|
|
2002
2142
|
return {
|
|
2003
2143
|
name: "compare_previous_release",
|
|
2004
2144
|
description: "Compare this release with a previous release to understand what changed between versions",
|
|
2145
|
+
category: "Release Analysis",
|
|
2146
|
+
cost: "moderate",
|
|
2147
|
+
examples: [{ scenario: "Compare with last release", params: { previousTag: "v1.0.0", currentRef: "HEAD" }, expectedResult: "Commit count and file change statistics" }],
|
|
2005
2148
|
parameters: {
|
|
2006
2149
|
type: "object",
|
|
2007
2150
|
properties: {
|
|
@@ -2057,6 +2200,9 @@ function createGetReleaseStatsTool() {
|
|
|
2057
2200
|
return {
|
|
2058
2201
|
name: "get_release_stats",
|
|
2059
2202
|
description: "Get comprehensive statistics about the release including contributors, file changes, and commit patterns",
|
|
2203
|
+
category: "Release Analysis",
|
|
2204
|
+
cost: "moderate",
|
|
2205
|
+
examples: [{ scenario: "Get release overview", params: { fromRef: "v1.0.0", toRef: "HEAD" }, expectedResult: "Contributors, file changes, and top modified files" }],
|
|
2060
2206
|
parameters: {
|
|
2061
2207
|
type: "object",
|
|
2062
2208
|
properties: {
|
|
@@ -2110,6 +2256,9 @@ function createGetBreakingChangesTool() {
|
|
|
2110
2256
|
return {
|
|
2111
2257
|
name: "get_breaking_changes",
|
|
2112
2258
|
description: "Search for potential breaking changes by looking for specific patterns in commits and diffs",
|
|
2259
|
+
category: "Release Analysis",
|
|
2260
|
+
cost: "expensive",
|
|
2261
|
+
examples: [{ scenario: "Check for breaking changes", params: { fromRef: "v1.0.0", toRef: "HEAD" }, expectedResult: "List of potential breaking changes found" }],
|
|
2113
2262
|
parameters: {
|
|
2114
2263
|
type: "object",
|
|
2115
2264
|
properties: {
|
|
@@ -2173,6 +2322,9 @@ function createAnalyzeCommitPatternsTool() {
|
|
|
2173
2322
|
return {
|
|
2174
2323
|
name: "analyze_commit_patterns",
|
|
2175
2324
|
description: "Analyze commit messages to identify patterns and themes in the release",
|
|
2325
|
+
category: "Release Analysis",
|
|
2326
|
+
cost: "cheap",
|
|
2327
|
+
examples: [{ scenario: "Find commit themes", params: { fromRef: "v1.0.0", toRef: "HEAD" }, expectedResult: "Commit types and top keywords" }],
|
|
2176
2328
|
parameters: {
|
|
2177
2329
|
type: "object",
|
|
2178
2330
|
properties: {
|
|
@@ -2244,7 +2396,8 @@ async function runAgenticRelease(config) {
|
|
|
2244
2396
|
debugResponseFile,
|
|
2245
2397
|
storage,
|
|
2246
2398
|
logger: logger2,
|
|
2247
|
-
openaiReasoning
|
|
2399
|
+
openaiReasoning,
|
|
2400
|
+
tokenBudget
|
|
2248
2401
|
} = config;
|
|
2249
2402
|
const toolRegistry = createToolRegistry({
|
|
2250
2403
|
workingDirectory: process.cwd(),
|
|
@@ -2253,7 +2406,13 @@ async function runAgenticRelease(config) {
|
|
|
2253
2406
|
});
|
|
2254
2407
|
const tools = createReleaseTools();
|
|
2255
2408
|
toolRegistry.registerAll(tools);
|
|
2256
|
-
const
|
|
2409
|
+
const toolGuidance = generateToolGuidance(tools, {
|
|
2410
|
+
strategy: "adaptive",
|
|
2411
|
+
includeExamples: true,
|
|
2412
|
+
explainWhenToUse: true,
|
|
2413
|
+
includeCategories: true
|
|
2414
|
+
});
|
|
2415
|
+
const systemPrompt = buildSystemPrompt$1(toolGuidance);
|
|
2257
2416
|
const userMessage = buildUserMessage({
|
|
2258
2417
|
fromRef,
|
|
2259
2418
|
toRef,
|
|
@@ -2277,7 +2436,13 @@ async function runAgenticRelease(config) {
|
|
|
2277
2436
|
debugResponseFile,
|
|
2278
2437
|
storage,
|
|
2279
2438
|
logger: logger2,
|
|
2280
|
-
openaiReasoning
|
|
2439
|
+
openaiReasoning,
|
|
2440
|
+
tokenBudget: tokenBudget || {
|
|
2441
|
+
max: 2e5,
|
|
2442
|
+
reserveForResponse: 8e3,
|
|
2443
|
+
strategy: "fifo",
|
|
2444
|
+
onBudgetExceeded: "compress"
|
|
2445
|
+
}
|
|
2281
2446
|
};
|
|
2282
2447
|
const result = await runAgentic(agenticConfig);
|
|
2283
2448
|
const parsed = parseAgenticResult(result.finalMessage);
|
|
@@ -2289,41 +2454,36 @@ async function runAgenticRelease(config) {
|
|
|
2289
2454
|
toolMetrics: result.toolMetrics
|
|
2290
2455
|
};
|
|
2291
2456
|
}
|
|
2292
|
-
function buildSystemPrompt() {
|
|
2457
|
+
function buildSystemPrompt$1(toolGuidance) {
|
|
2293
2458
|
return `You are an expert software engineer and technical writer tasked with generating comprehensive, thoughtful release notes.
|
|
2294
2459
|
|
|
2295
|
-
|
|
2296
|
-
-
|
|
2297
|
-
- get_file_content: Read full file contents to understand context
|
|
2298
|
-
- search_codebase: Search for patterns across the codebase
|
|
2299
|
-
- get_related_tests: Find test files to understand functionality
|
|
2300
|
-
- get_file_dependencies: Understand file dependencies and impact
|
|
2301
|
-
- analyze_diff_section: Get expanded context around specific changes
|
|
2302
|
-
- get_recent_commits: See recent commits to the same files
|
|
2303
|
-
- group_files_by_concern: Identify logical groupings of changes
|
|
2304
|
-
- get_tag_history: View previous release tags and patterns
|
|
2305
|
-
- compare_previous_release: Compare with previous releases
|
|
2306
|
-
- get_release_stats: Get comprehensive statistics about the release
|
|
2307
|
-
- get_breaking_changes: Identify potential breaking changes
|
|
2308
|
-
- analyze_commit_patterns: Identify themes and patterns in commits
|
|
2460
|
+
${toolGuidance}
|
|
2461
|
+
- get_tag_history: Use early to understand release cadence. Good for: establishing context about project versioning patterns
|
|
2309
2462
|
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
5. Check for breaking changes and significant architectural shifts
|
|
2316
|
-
6. Understand the "why" behind changes by examining commit messages, issues, and code
|
|
2317
|
-
7. Synthesize findings into comprehensive, thoughtful release notes
|
|
2463
|
+
**Analyzing Current Changes:**
|
|
2464
|
+
- get_file_content: Use when diff alone isn't enough. Good for: understanding APIs, seeing full class/function context, checking imports
|
|
2465
|
+
- analyze_diff_section: Use to expand context around cryptic changes. Good for: seeing surrounding code, understanding integration points
|
|
2466
|
+
- get_file_dependencies: Use for refactors/moves. Good for: assessing impact scope, identifying what depends on changed code
|
|
2467
|
+
- search_codebase: Use to find usage patterns. Good for: checking if APIs are widely used, finding similar patterns elsewhere
|
|
2318
2468
|
|
|
2319
|
-
|
|
2320
|
-
- Use
|
|
2321
|
-
-
|
|
2322
|
-
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
-
|
|
2326
|
-
-
|
|
2469
|
+
**Pattern Recognition:**
|
|
2470
|
+
- group_files_by_concern: Use when many files changed. Good for: organizing changes into logical themes, identifying what actually happened
|
|
2471
|
+
- analyze_commit_patterns: Use for many commits. Good for: detecting themes across commits, identifying if work is focused or scattered
|
|
2472
|
+
- get_release_stats: Use to quantify scope. Good for: getting concrete metrics on scale of changes
|
|
2473
|
+
|
|
2474
|
+
**Risk Assessment:**
|
|
2475
|
+
- get_breaking_changes: Always use. Good for: identifying API changes, finding removals/signature changes that could break users
|
|
2476
|
+
- get_related_tests: Use for significant logic changes. Good for: understanding what behavior changed, verifying test coverage exists
|
|
2477
|
+
|
|
2478
|
+
## Your Investigation Strategy
|
|
2479
|
+
|
|
2480
|
+
1. **Start broad** (2-3 tools): get_tag_history, get_release_stats, analyze_commit_patterns
|
|
2481
|
+
2. **Identify themes** (1-2 tools): group_files_by_concern if many files, compare_previous_release for context
|
|
2482
|
+
3. **Deep dive** (3-5 tools): get_file_content for key changes, get_file_dependencies for refactors, analyze_diff_section for unclear changes
|
|
2483
|
+
4. **Verify understanding** (2-3 tools): get_related_tests for logic changes, search_codebase for impact
|
|
2484
|
+
5. **Check risks** (1 tool): get_breaking_changes always
|
|
2485
|
+
|
|
2486
|
+
Use at least 6-8 tools per release to ensure comprehensive analysis. Each tool provides a different lens on the changes.
|
|
2327
2487
|
|
|
2328
2488
|
Output format:
|
|
2329
2489
|
When you're ready to provide the final release notes, format them as JSON:
|
|
@@ -2340,7 +2500,11 @@ The release notes should:
|
|
|
2340
2500
|
- Connect related changes to reveal patterns
|
|
2341
2501
|
- Be substantial and analytical, not formulaic
|
|
2342
2502
|
- Sound like they were written by a human who studied the changes
|
|
2343
|
-
- Be grounded in actual commits and issues (no hallucinations)
|
|
2503
|
+
- Be grounded in actual commits and issues (no hallucinations)
|
|
2504
|
+
- Be standalone documentation that can be published as-is
|
|
2505
|
+
- NEVER include conversational elements like "If you want, I can also..." or "Let me know if..."
|
|
2506
|
+
- NEVER offer follow-up actions, questions, or suggestions for additional work
|
|
2507
|
+
- End with substantive content, not conversational closing remarks`;
|
|
2344
2508
|
}
|
|
2345
2509
|
function buildUserMessage(params) {
|
|
2346
2510
|
const { fromRef, toRef, logContent, diffContent, milestoneIssues, releaseFocus, userContext } = params;
|
|
@@ -2389,8 +2553,13 @@ function parseAgenticResult(finalMessage) {
|
|
|
2389
2553
|
if (jsonMatch) {
|
|
2390
2554
|
try {
|
|
2391
2555
|
const jsonStr = jsonMatch[1].trim();
|
|
2392
|
-
|
|
2393
|
-
|
|
2556
|
+
let parsed;
|
|
2557
|
+
try {
|
|
2558
|
+
parsed = JSON.parse(jsonStr);
|
|
2559
|
+
} catch {
|
|
2560
|
+
parsed = null;
|
|
2561
|
+
}
|
|
2562
|
+
if (parsed && parsed.title && parsed.body) {
|
|
2394
2563
|
return {
|
|
2395
2564
|
releaseNotes: {
|
|
2396
2565
|
title: parsed.title,
|
|
@@ -2426,36 +2595,1084 @@ function parseAgenticResult(finalMessage) {
|
|
|
2426
2595
|
}
|
|
2427
2596
|
};
|
|
2428
2597
|
}
|
|
2598
|
+
function createPublishTools() {
|
|
2599
|
+
return [
|
|
2600
|
+
createCheckGitStatusTool(),
|
|
2601
|
+
createCheckBranchSyncTool(),
|
|
2602
|
+
createAnalyzeDivergenceTool(),
|
|
2603
|
+
createGetCommitLogTool(),
|
|
2604
|
+
createGetBranchInfoTool(),
|
|
2605
|
+
createSyncBranchTool(),
|
|
2606
|
+
createGetDiffStatsTool(),
|
|
2607
|
+
createCheckConflictsTool(),
|
|
2608
|
+
createResetBranchTool()
|
|
2609
|
+
];
|
|
2610
|
+
}
|
|
2611
|
+
function createCheckGitStatusTool() {
|
|
2612
|
+
return {
|
|
2613
|
+
name: "check_git_status",
|
|
2614
|
+
description: "Check the current git repository status, including uncommitted changes, current branch, and repository state",
|
|
2615
|
+
parameters: {
|
|
2616
|
+
type: "object",
|
|
2617
|
+
properties: {
|
|
2618
|
+
showUntracked: {
|
|
2619
|
+
type: "boolean",
|
|
2620
|
+
description: "Include untracked files in output (default: false)",
|
|
2621
|
+
default: false
|
|
2622
|
+
}
|
|
2623
|
+
},
|
|
2624
|
+
required: []
|
|
2625
|
+
},
|
|
2626
|
+
execute: async (params, context) => {
|
|
2627
|
+
const { showUntracked = false } = params;
|
|
2628
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
2629
|
+
try {
|
|
2630
|
+
const branchResult = await run("git branch --show-current", { cwd: workingDir });
|
|
2631
|
+
const currentBranch = branchResult.stdout.trim();
|
|
2632
|
+
const statusCmd = showUntracked ? "git status --porcelain" : "git status --porcelain --untracked-files=no";
|
|
2633
|
+
const statusResult = await run(statusCmd, { cwd: workingDir });
|
|
2634
|
+
const hasChanges = statusResult.stdout.trim().length > 0;
|
|
2635
|
+
const headResult = await run("git rev-parse --short HEAD", { cwd: workingDir });
|
|
2636
|
+
const headSha = headResult.stdout.trim();
|
|
2637
|
+
return {
|
|
2638
|
+
currentBranch,
|
|
2639
|
+
headSha,
|
|
2640
|
+
hasUncommittedChanges: hasChanges,
|
|
2641
|
+
statusOutput: statusResult.stdout
|
|
2642
|
+
};
|
|
2643
|
+
} catch (error) {
|
|
2644
|
+
throw new Error(`Failed to check git status: ${error.message}`);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
function createCheckBranchSyncTool() {
|
|
2650
|
+
return {
|
|
2651
|
+
name: "check_branch_sync",
|
|
2652
|
+
description: "Check if a local branch is synchronized with its remote counterpart. Returns sync status, local/remote SHAs, and whether the branch exists locally.",
|
|
2653
|
+
parameters: {
|
|
2654
|
+
type: "object",
|
|
2655
|
+
properties: {
|
|
2656
|
+
branchName: {
|
|
2657
|
+
type: "string",
|
|
2658
|
+
description: "Name of the branch to check"
|
|
2659
|
+
}
|
|
2660
|
+
},
|
|
2661
|
+
required: ["branchName"]
|
|
2662
|
+
},
|
|
2663
|
+
execute: async (params, _context) => {
|
|
2664
|
+
const { branchName } = params;
|
|
2665
|
+
try {
|
|
2666
|
+
const exists = await localBranchExists(branchName);
|
|
2667
|
+
if (!exists) {
|
|
2668
|
+
return {
|
|
2669
|
+
exists: false,
|
|
2670
|
+
message: `Branch '${branchName}' does not exist locally`
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
const syncStatus = await isBranchInSyncWithRemote(branchName);
|
|
2674
|
+
return {
|
|
2675
|
+
exists: true,
|
|
2676
|
+
branchName,
|
|
2677
|
+
inSync: syncStatus.inSync,
|
|
2678
|
+
localSha: syncStatus.localSha,
|
|
2679
|
+
remoteSha: syncStatus.remoteSha,
|
|
2680
|
+
error: syncStatus.error,
|
|
2681
|
+
isDiverged: syncStatus.localSha !== syncStatus.remoteSha
|
|
2682
|
+
};
|
|
2683
|
+
} catch (error) {
|
|
2684
|
+
throw new Error(`Failed to check branch sync: ${error.message}`);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
function createAnalyzeDivergenceTool() {
|
|
2690
|
+
return {
|
|
2691
|
+
name: "analyze_divergence",
|
|
2692
|
+
description: "Analyze how two branches have diverged by showing commits unique to each branch",
|
|
2693
|
+
parameters: {
|
|
2694
|
+
type: "object",
|
|
2695
|
+
properties: {
|
|
2696
|
+
sourceBranch: {
|
|
2697
|
+
type: "string",
|
|
2698
|
+
description: "The source/local branch name"
|
|
2699
|
+
},
|
|
2700
|
+
targetBranch: {
|
|
2701
|
+
type: "string",
|
|
2702
|
+
description: 'The target/remote branch to compare against (e.g., "origin/main")'
|
|
2703
|
+
},
|
|
2704
|
+
maxCommits: {
|
|
2705
|
+
type: "number",
|
|
2706
|
+
description: "Maximum number of commits to show for each branch (default: 10)",
|
|
2707
|
+
default: 10
|
|
2708
|
+
}
|
|
2709
|
+
},
|
|
2710
|
+
required: ["sourceBranch", "targetBranch"]
|
|
2711
|
+
},
|
|
2712
|
+
execute: async (params, context) => {
|
|
2713
|
+
const { sourceBranch, targetBranch, maxCommits = 10 } = params;
|
|
2714
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
2715
|
+
try {
|
|
2716
|
+
const sourceOnlyResult = await run(
|
|
2717
|
+
`git log ${targetBranch}..${sourceBranch} --oneline -n ${maxCommits}`,
|
|
2718
|
+
{ cwd: workingDir }
|
|
2719
|
+
);
|
|
2720
|
+
const targetOnlyResult = await run(
|
|
2721
|
+
`git log ${sourceBranch}..${targetBranch} --oneline -n ${maxCommits}`,
|
|
2722
|
+
{ cwd: workingDir }
|
|
2723
|
+
);
|
|
2724
|
+
const mergeBaseResult = await run(
|
|
2725
|
+
`git merge-base ${sourceBranch} ${targetBranch}`,
|
|
2726
|
+
{ cwd: workingDir }
|
|
2727
|
+
);
|
|
2728
|
+
return {
|
|
2729
|
+
sourceBranch,
|
|
2730
|
+
targetBranch,
|
|
2731
|
+
mergeBase: mergeBaseResult.stdout.trim(),
|
|
2732
|
+
commitsInSourceOnly: sourceOnlyResult.stdout.trim() || "None",
|
|
2733
|
+
commitsInTargetOnly: targetOnlyResult.stdout.trim() || "None",
|
|
2734
|
+
isDiverged: sourceOnlyResult.stdout.trim().length > 0 && targetOnlyResult.stdout.trim().length > 0,
|
|
2735
|
+
canFastForward: sourceOnlyResult.stdout.trim().length === 0
|
|
2736
|
+
// Target is ahead, can fast-forward
|
|
2737
|
+
};
|
|
2738
|
+
} catch (error) {
|
|
2739
|
+
throw new Error(`Failed to analyze divergence: ${error.message}`);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
function createGetCommitLogTool() {
|
|
2745
|
+
return {
|
|
2746
|
+
name: "get_commit_log",
|
|
2747
|
+
description: "Get git commit log for a branch or commit range with detailed information",
|
|
2748
|
+
parameters: {
|
|
2749
|
+
type: "object",
|
|
2750
|
+
properties: {
|
|
2751
|
+
ref: {
|
|
2752
|
+
type: "string",
|
|
2753
|
+
description: 'Git ref (branch name, commit range like "main..working", or single commit)'
|
|
2754
|
+
},
|
|
2755
|
+
maxCommits: {
|
|
2756
|
+
type: "number",
|
|
2757
|
+
description: "Maximum number of commits to return (default: 20)",
|
|
2758
|
+
default: 20
|
|
2759
|
+
},
|
|
2760
|
+
format: {
|
|
2761
|
+
type: "string",
|
|
2762
|
+
description: 'Format: "oneline" for brief, "full" for detailed with message body',
|
|
2763
|
+
enum: ["oneline", "full"],
|
|
2764
|
+
default: "oneline"
|
|
2765
|
+
}
|
|
2766
|
+
},
|
|
2767
|
+
required: ["ref"]
|
|
2768
|
+
},
|
|
2769
|
+
execute: async (params, context) => {
|
|
2770
|
+
const { ref, maxCommits = 20, format = "oneline" } = params;
|
|
2771
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
2772
|
+
try {
|
|
2773
|
+
const formatStr = format === "full" ? "%H%n%an <%ae>%n%ad%n%s%n%n%b%n---" : "%h - %s (%an, %ar)";
|
|
2774
|
+
const result = await run(
|
|
2775
|
+
`git log "${ref}" --format="${formatStr}" -n ${maxCommits}`,
|
|
2776
|
+
{ cwd: workingDir }
|
|
2777
|
+
);
|
|
2778
|
+
return result.stdout || "No commits found";
|
|
2779
|
+
} catch (error) {
|
|
2780
|
+
throw new Error(`Failed to get commit log: ${error.message}`);
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
};
|
|
2784
|
+
}
|
|
2785
|
+
function createGetBranchInfoTool() {
|
|
2786
|
+
return {
|
|
2787
|
+
name: "get_branch_info",
|
|
2788
|
+
description: "Get detailed information about a branch including its tracking info, last commit, and whether it exists remotely",
|
|
2789
|
+
parameters: {
|
|
2790
|
+
type: "object",
|
|
2791
|
+
properties: {
|
|
2792
|
+
branchName: {
|
|
2793
|
+
type: "string",
|
|
2794
|
+
description: "Name of the branch to inspect"
|
|
2795
|
+
}
|
|
2796
|
+
},
|
|
2797
|
+
required: ["branchName"]
|
|
2798
|
+
},
|
|
2799
|
+
execute: async (params, context) => {
|
|
2800
|
+
const { branchName } = params;
|
|
2801
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
2802
|
+
try {
|
|
2803
|
+
const localExists = await localBranchExists(branchName);
|
|
2804
|
+
if (!localExists) {
|
|
2805
|
+
try {
|
|
2806
|
+
const remoteCheckResult = await run(`git ls-remote --heads origin ${branchName}`, { cwd: workingDir });
|
|
2807
|
+
const remoteExists = remoteCheckResult.stdout.trim().length > 0;
|
|
2808
|
+
return {
|
|
2809
|
+
branchName,
|
|
2810
|
+
existsLocally: false,
|
|
2811
|
+
existsRemotely: remoteExists,
|
|
2812
|
+
message: remoteExists ? `Branch exists remotely but not locally` : `Branch does not exist locally or remotely`
|
|
2813
|
+
};
|
|
2814
|
+
} catch {
|
|
2815
|
+
return {
|
|
2816
|
+
branchName,
|
|
2817
|
+
existsLocally: false,
|
|
2818
|
+
existsRemotely: false
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
let trackingBranch = null;
|
|
2823
|
+
try {
|
|
2824
|
+
const trackingResult = await run(`git rev-parse --abbrev-ref ${branchName}@{upstream}`, { cwd: workingDir });
|
|
2825
|
+
trackingBranch = trackingResult.stdout.trim();
|
|
2826
|
+
} catch {
|
|
2827
|
+
}
|
|
2828
|
+
const lastCommitResult = await run(
|
|
2829
|
+
`git log ${branchName} -1 --format="%H|%h|%s|%an|%ar"`,
|
|
2830
|
+
{ cwd: workingDir }
|
|
2831
|
+
);
|
|
2832
|
+
const [fullSha, shortSha, subject, author, relativeDate] = lastCommitResult.stdout.trim().split("|");
|
|
2833
|
+
const countResult = await run(`git rev-list --count ${branchName}`, { cwd: workingDir });
|
|
2834
|
+
const commitCount = parseInt(countResult.stdout.trim(), 10);
|
|
2835
|
+
if (isNaN(commitCount)) {
|
|
2836
|
+
throw new Error(`Invalid commit count returned from git: ${countResult.stdout}`);
|
|
2837
|
+
}
|
|
2838
|
+
return {
|
|
2839
|
+
branchName,
|
|
2840
|
+
existsLocally: true,
|
|
2841
|
+
trackingBranch,
|
|
2842
|
+
lastCommit: {
|
|
2843
|
+
fullSha,
|
|
2844
|
+
shortSha,
|
|
2845
|
+
subject,
|
|
2846
|
+
author,
|
|
2847
|
+
relativeDate
|
|
2848
|
+
},
|
|
2849
|
+
commitCount
|
|
2850
|
+
};
|
|
2851
|
+
} catch (error) {
|
|
2852
|
+
throw new Error(`Failed to get branch info: ${error.message}`);
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
function createSyncBranchTool() {
|
|
2858
|
+
return {
|
|
2859
|
+
name: "sync_branch",
|
|
2860
|
+
description: "Attempt to synchronize a local branch with its remote counterpart. Returns success status and details about what happened.",
|
|
2861
|
+
parameters: {
|
|
2862
|
+
type: "object",
|
|
2863
|
+
properties: {
|
|
2864
|
+
branchName: {
|
|
2865
|
+
type: "string",
|
|
2866
|
+
description: "Name of the branch to sync"
|
|
2867
|
+
}
|
|
2868
|
+
},
|
|
2869
|
+
required: ["branchName"]
|
|
2870
|
+
},
|
|
2871
|
+
execute: async (params, _context) => {
|
|
2872
|
+
const { branchName } = params;
|
|
2873
|
+
try {
|
|
2874
|
+
const syncResult = await safeSyncBranchWithRemote(branchName);
|
|
2875
|
+
return {
|
|
2876
|
+
branchName,
|
|
2877
|
+
success: syncResult.success,
|
|
2878
|
+
conflictResolutionRequired: syncResult.conflictResolutionRequired,
|
|
2879
|
+
error: syncResult.error,
|
|
2880
|
+
message: syncResult.success ? `Successfully synchronized ${branchName} with remote` : syncResult.conflictResolutionRequired ? `Sync failed due to merge conflicts in ${branchName}` : `Sync failed: ${syncResult.error}`
|
|
2881
|
+
};
|
|
2882
|
+
} catch (error) {
|
|
2883
|
+
throw new Error(`Failed to sync branch: ${error.message}`);
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
function createGetDiffStatsTool() {
|
|
2889
|
+
return {
|
|
2890
|
+
name: "get_diff_stats",
|
|
2891
|
+
description: "Get statistics about differences between two git refs (branches, commits, etc.)",
|
|
2892
|
+
parameters: {
|
|
2893
|
+
type: "object",
|
|
2894
|
+
properties: {
|
|
2895
|
+
fromRef: {
|
|
2896
|
+
type: "string",
|
|
2897
|
+
description: 'Starting ref (e.g., "main", "origin/main")'
|
|
2898
|
+
},
|
|
2899
|
+
toRef: {
|
|
2900
|
+
type: "string",
|
|
2901
|
+
description: 'Ending ref (e.g., "working", "HEAD")'
|
|
2902
|
+
},
|
|
2903
|
+
includeFileList: {
|
|
2904
|
+
type: "boolean",
|
|
2905
|
+
description: "Include list of changed files (default: true)",
|
|
2906
|
+
default: true
|
|
2907
|
+
}
|
|
2908
|
+
},
|
|
2909
|
+
required: ["fromRef", "toRef"]
|
|
2910
|
+
},
|
|
2911
|
+
execute: async (params, context) => {
|
|
2912
|
+
const { fromRef, toRef, includeFileList = true } = params;
|
|
2913
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
2914
|
+
try {
|
|
2915
|
+
const statsResult = await run(`git diff --shortstat ${fromRef}..${toRef}`, { cwd: workingDir });
|
|
2916
|
+
let fileList = null;
|
|
2917
|
+
if (includeFileList) {
|
|
2918
|
+
const fileListResult = await run(`git diff --name-status ${fromRef}..${toRef}`, { cwd: workingDir });
|
|
2919
|
+
fileList = fileListResult.stdout;
|
|
2920
|
+
}
|
|
2921
|
+
return {
|
|
2922
|
+
fromRef,
|
|
2923
|
+
toRef,
|
|
2924
|
+
stats: statsResult.stdout.trim() || "No differences",
|
|
2925
|
+
fileList
|
|
2926
|
+
};
|
|
2927
|
+
} catch (error) {
|
|
2928
|
+
throw new Error(`Failed to get diff stats: ${error.message}`);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
};
|
|
2932
|
+
}
|
|
2933
|
+
function createCheckConflictsTool() {
|
|
2934
|
+
return {
|
|
2935
|
+
name: "check_conflicts",
|
|
2936
|
+
description: "Check if merging one branch into another would result in conflicts (without actually performing the merge)",
|
|
2937
|
+
parameters: {
|
|
2938
|
+
type: "object",
|
|
2939
|
+
properties: {
|
|
2940
|
+
sourceBranch: {
|
|
2941
|
+
type: "string",
|
|
2942
|
+
description: "Branch to merge from"
|
|
2943
|
+
},
|
|
2944
|
+
targetBranch: {
|
|
2945
|
+
type: "string",
|
|
2946
|
+
description: "Branch to merge into"
|
|
2947
|
+
}
|
|
2948
|
+
},
|
|
2949
|
+
required: ["sourceBranch", "targetBranch"]
|
|
2950
|
+
},
|
|
2951
|
+
execute: async (params, context) => {
|
|
2952
|
+
const { sourceBranch, targetBranch } = params;
|
|
2953
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
2954
|
+
try {
|
|
2955
|
+
const mergeBaseResult = await run(
|
|
2956
|
+
`git merge-base ${targetBranch} ${sourceBranch}`,
|
|
2957
|
+
{ cwd: workingDir }
|
|
2958
|
+
);
|
|
2959
|
+
const mergeBase = mergeBaseResult.stdout.trim();
|
|
2960
|
+
const mergeTreeResult = await run(
|
|
2961
|
+
`git merge-tree ${mergeBase} ${targetBranch} ${sourceBranch}`,
|
|
2962
|
+
{ cwd: workingDir }
|
|
2963
|
+
);
|
|
2964
|
+
const hasConflicts = mergeTreeResult.stdout.includes("<<<<<<<") || mergeTreeResult.stdout.includes(">>>>>>>") || mergeTreeResult.stdout.includes("=======");
|
|
2965
|
+
const conflicts = hasConflicts ? mergeTreeResult.stdout.match(/\+\+\+ b\/([^\n]+)/g)?.map((m) => m.replace(/\+\+\+ b\//, "")) : [];
|
|
2966
|
+
return {
|
|
2967
|
+
sourceBranch,
|
|
2968
|
+
targetBranch,
|
|
2969
|
+
mergeBase,
|
|
2970
|
+
hasConflicts,
|
|
2971
|
+
conflictingFiles: conflicts || [],
|
|
2972
|
+
message: hasConflicts ? `Merging ${sourceBranch} into ${targetBranch} would create conflicts` : `Merging ${sourceBranch} into ${targetBranch} should succeed without conflicts`
|
|
2973
|
+
};
|
|
2974
|
+
} catch (error) {
|
|
2975
|
+
throw new Error(`Failed to check conflicts: ${error.message}`);
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
function createResetBranchTool() {
|
|
2981
|
+
return {
|
|
2982
|
+
name: "reset_branch",
|
|
2983
|
+
description: "Reset a branch to match another ref (e.g., reset local main to origin/main). USE WITH CAUTION - this is a destructive operation.",
|
|
2984
|
+
parameters: {
|
|
2985
|
+
type: "object",
|
|
2986
|
+
properties: {
|
|
2987
|
+
branchName: {
|
|
2988
|
+
type: "string",
|
|
2989
|
+
description: "Name of the branch to reset"
|
|
2990
|
+
},
|
|
2991
|
+
targetRef: {
|
|
2992
|
+
type: "string",
|
|
2993
|
+
description: 'Ref to reset to (e.g., "origin/main")'
|
|
2994
|
+
},
|
|
2995
|
+
resetType: {
|
|
2996
|
+
type: "string",
|
|
2997
|
+
description: 'Type of reset: "hard" (discard changes), "soft" (keep changes staged), "mixed" (keep changes unstaged)',
|
|
2998
|
+
enum: ["hard", "soft", "mixed"],
|
|
2999
|
+
default: "hard"
|
|
3000
|
+
}
|
|
3001
|
+
},
|
|
3002
|
+
required: ["branchName", "targetRef"]
|
|
3003
|
+
},
|
|
3004
|
+
execute: async (params, context) => {
|
|
3005
|
+
const { branchName, targetRef, resetType = "hard" } = params;
|
|
3006
|
+
const workingDir = context?.workingDirectory || process.cwd();
|
|
3007
|
+
try {
|
|
3008
|
+
await run(`git checkout ${branchName}`, { cwd: workingDir });
|
|
3009
|
+
await run(`git reset --${resetType} ${targetRef}`, { cwd: workingDir });
|
|
3010
|
+
const headResult = await run("git rev-parse --short HEAD", { cwd: workingDir });
|
|
3011
|
+
const newHead = headResult.stdout.trim();
|
|
3012
|
+
return {
|
|
3013
|
+
branchName,
|
|
3014
|
+
targetRef,
|
|
3015
|
+
resetType,
|
|
3016
|
+
newHead,
|
|
3017
|
+
message: `Successfully reset ${branchName} to ${targetRef} (${resetType})`
|
|
3018
|
+
};
|
|
3019
|
+
} catch (error) {
|
|
3020
|
+
throw new Error(`Failed to reset branch: ${error.message}`);
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
async function runAgenticPublish(config) {
|
|
3026
|
+
const {
|
|
3027
|
+
targetBranch,
|
|
3028
|
+
sourceBranch,
|
|
3029
|
+
issue,
|
|
3030
|
+
issueDetails,
|
|
3031
|
+
workingDirectory = process.cwd(),
|
|
3032
|
+
model = "gpt-4o",
|
|
3033
|
+
maxIterations = 10,
|
|
3034
|
+
debug = false,
|
|
3035
|
+
storage,
|
|
3036
|
+
logger: logger2,
|
|
3037
|
+
dryRun = false
|
|
3038
|
+
} = config;
|
|
3039
|
+
const toolContext = {
|
|
3040
|
+
workingDirectory,
|
|
3041
|
+
storage,
|
|
3042
|
+
logger: logger2
|
|
3043
|
+
};
|
|
3044
|
+
const tools = createToolRegistry(toolContext);
|
|
3045
|
+
tools.registerAll(createPublishTools());
|
|
3046
|
+
const systemPrompt = buildSystemPrompt(issue, dryRun);
|
|
3047
|
+
const userPrompt = buildUserPrompt({
|
|
3048
|
+
issue,
|
|
3049
|
+
targetBranch,
|
|
3050
|
+
sourceBranch,
|
|
3051
|
+
issueDetails
|
|
3052
|
+
});
|
|
3053
|
+
const messages = [
|
|
3054
|
+
{
|
|
3055
|
+
role: "system",
|
|
3056
|
+
content: systemPrompt
|
|
3057
|
+
},
|
|
3058
|
+
{
|
|
3059
|
+
role: "user",
|
|
3060
|
+
content: userPrompt
|
|
3061
|
+
}
|
|
3062
|
+
];
|
|
3063
|
+
const executor = new AgenticExecutor(logger2);
|
|
3064
|
+
const result = await executor.run({
|
|
3065
|
+
messages,
|
|
3066
|
+
tools,
|
|
3067
|
+
model,
|
|
3068
|
+
maxIterations,
|
|
3069
|
+
debug,
|
|
3070
|
+
storage,
|
|
3071
|
+
logger: logger2
|
|
3072
|
+
});
|
|
3073
|
+
const analysis = parseAgentResponse(result.finalMessage);
|
|
3074
|
+
return {
|
|
3075
|
+
success: analysis.success,
|
|
3076
|
+
message: result.finalMessage,
|
|
3077
|
+
actionsTaken: analysis.actionsTaken,
|
|
3078
|
+
iterations: result.iterations,
|
|
3079
|
+
toolCallsExecuted: result.toolCallsExecuted,
|
|
3080
|
+
requiresManualIntervention: analysis.requiresManualIntervention,
|
|
3081
|
+
manualSteps: analysis.manualSteps
|
|
3082
|
+
};
|
|
3083
|
+
}
|
|
3084
|
+
function buildSystemPrompt(issue, dryRun) {
|
|
3085
|
+
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." : "";
|
|
3086
|
+
return `You are an expert Git operations assistant helping to diagnose and fix issues that prevent publishing a software package.
|
|
3087
|
+
|
|
3088
|
+
Your role is to:
|
|
3089
|
+
1. Use the available tools to investigate the problem
|
|
3090
|
+
2. Analyze the root cause of the issue
|
|
3091
|
+
3. ${dryRun ? "Recommend" : "Attempt to fix"} the issue using best practices
|
|
3092
|
+
4. Provide clear explanations of what you found and ${dryRun ? "would do" : "did"}
|
|
3093
|
+
|
|
3094
|
+
Available tools allow you to:
|
|
3095
|
+
- Check git status and branch information
|
|
3096
|
+
- Analyze divergence between branches
|
|
3097
|
+
- View commit logs and diff statistics
|
|
3098
|
+
- Check for potential merge conflicts
|
|
3099
|
+
${dryRun ? "" : "- Sync branches with remote\n- Reset branches to match remote refs"}
|
|
3100
|
+
|
|
3101
|
+
Guidelines:
|
|
3102
|
+
- Always diagnose before taking action
|
|
3103
|
+
- Prefer safe, non-destructive operations when possible
|
|
3104
|
+
- If a situation requires manual intervention, clearly state why and provide steps
|
|
3105
|
+
- Be thorough in your analysis
|
|
3106
|
+
- Consider the impact of each action on the repository state
|
|
3107
|
+
|
|
3108
|
+
Current issue type: ${issue}${modeNote}`;
|
|
3109
|
+
}
|
|
3110
|
+
function buildUserPrompt(params) {
|
|
3111
|
+
const { issue, targetBranch, sourceBranch, issueDetails } = params;
|
|
3112
|
+
const issueDescriptions = {
|
|
3113
|
+
branch_sync: `The target branch '${targetBranch}' is not synchronized with its remote counterpart. This prevents the publish workflow from proceeding safely.`,
|
|
3114
|
+
uncommitted_changes: `There are uncommitted changes in the working directory on branch '${sourceBranch}'. The publish workflow requires a clean working directory.`,
|
|
3115
|
+
merge_conflicts: `There are merge conflicts between '${sourceBranch}' and '${targetBranch}' that need to be resolved before publishing.`,
|
|
3116
|
+
unknown: `An unknown issue is preventing the publish workflow from proceeding.`
|
|
3117
|
+
};
|
|
3118
|
+
const basePrompt = `I'm trying to run a publish workflow that will:
|
|
3119
|
+
1. Create a release from source branch '${sourceBranch}'
|
|
3120
|
+
2. Merge it into target branch '${targetBranch}'
|
|
3121
|
+
3. Publish the package
|
|
3122
|
+
|
|
3123
|
+
However, I'm encountering an issue:
|
|
3124
|
+
|
|
3125
|
+
${issueDescriptions[issue] || issueDescriptions.unknown}`;
|
|
3126
|
+
const detailsSection = issueDetails ? `
|
|
3127
|
+
|
|
3128
|
+
Additional details:
|
|
3129
|
+
${issueDetails}` : "";
|
|
3130
|
+
return `${basePrompt}${detailsSection}
|
|
3131
|
+
|
|
3132
|
+
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:
|
|
3133
|
+
|
|
3134
|
+
1. Diagnose the exact problem
|
|
3135
|
+
2. Understand what caused it (e.g., what commits are causing divergence)
|
|
3136
|
+
3. ${process.env.DRY_RUN ? "Recommend a solution" : "Attempt to resolve it safely"}
|
|
3137
|
+
4. Explain what ${process.env.DRY_RUN ? "should be" : "was"} done and why
|
|
3138
|
+
|
|
3139
|
+
If the issue requires manual intervention, please explain why and provide clear steps.`;
|
|
3140
|
+
}
|
|
3141
|
+
function parseAgentResponse(message) {
|
|
3142
|
+
const successIndicators = [
|
|
3143
|
+
"successfully",
|
|
3144
|
+
"resolved",
|
|
3145
|
+
"fixed",
|
|
3146
|
+
"synced",
|
|
3147
|
+
"safe to proceed",
|
|
3148
|
+
"ready to publish"
|
|
3149
|
+
];
|
|
3150
|
+
const manualInterventionIndicators = [
|
|
3151
|
+
"requires manual",
|
|
3152
|
+
"manual intervention",
|
|
3153
|
+
"cannot automatically",
|
|
3154
|
+
"you will need to",
|
|
3155
|
+
"please manually"
|
|
3156
|
+
];
|
|
3157
|
+
const messageLower = message.toLowerCase();
|
|
3158
|
+
const hasSuccessIndicator = successIndicators.some((indicator) => messageLower.includes(indicator));
|
|
3159
|
+
const hasManualIndicator = manualInterventionIndicators.some((indicator) => messageLower.includes(indicator));
|
|
3160
|
+
const actionsTaken = [];
|
|
3161
|
+
const actionPatterns = [
|
|
3162
|
+
/(?:I |We )?(checked|analyzed|found|discovered|synced|reset|merged|fetched|pulled|compared|investigated)\s+([^.\n]+)/gi,
|
|
3163
|
+
/(?:Successfully|Successfully)\s+([^.\n]+)/gi
|
|
3164
|
+
];
|
|
3165
|
+
for (const pattern of actionPatterns) {
|
|
3166
|
+
const matches = message.matchAll(pattern);
|
|
3167
|
+
for (const match of matches) {
|
|
3168
|
+
actionsTaken.push(match[0].trim());
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
const manualSteps = [];
|
|
3172
|
+
const stepPatterns = [
|
|
3173
|
+
/(?:Step \d+:|^\d+\.)\s*(.+)$/gim,
|
|
3174
|
+
/^[-*]\s+(.+)$/gim
|
|
3175
|
+
];
|
|
3176
|
+
if (hasManualIndicator) {
|
|
3177
|
+
for (const pattern of stepPatterns) {
|
|
3178
|
+
const matches = message.matchAll(pattern);
|
|
3179
|
+
for (const match of matches) {
|
|
3180
|
+
const step = match[1]?.trim();
|
|
3181
|
+
if (step && step.length > 10) {
|
|
3182
|
+
manualSteps.push(step);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
return {
|
|
3188
|
+
success: hasSuccessIndicator && !hasManualIndicator,
|
|
3189
|
+
actionsTaken: [...new Set(actionsTaken)],
|
|
3190
|
+
// Remove duplicates
|
|
3191
|
+
requiresManualIntervention: hasManualIndicator,
|
|
3192
|
+
manualSteps: [...new Set(manualSteps)]
|
|
3193
|
+
// Remove duplicates
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
function formatAgenticPublishResult(result) {
|
|
3197
|
+
const lines = [];
|
|
3198
|
+
lines.push("");
|
|
3199
|
+
lines.push("═══════════════════════════════════════════════════════════");
|
|
3200
|
+
lines.push(" AGENTIC PUBLISH RECOVERY REPORT");
|
|
3201
|
+
lines.push("═══════════════════════════════════════════════════════════");
|
|
3202
|
+
lines.push("");
|
|
3203
|
+
if (result.success) {
|
|
3204
|
+
lines.push("✅ Status: RESOLVED");
|
|
3205
|
+
} else if (result.requiresManualIntervention) {
|
|
3206
|
+
lines.push("⚠️ Status: MANUAL INTERVENTION REQUIRED");
|
|
3207
|
+
} else {
|
|
3208
|
+
lines.push("❌ Status: UNRESOLVED");
|
|
3209
|
+
}
|
|
3210
|
+
lines.push("");
|
|
3211
|
+
lines.push(`Iterations: ${result.iterations}`);
|
|
3212
|
+
lines.push(`Tools executed: ${result.toolCallsExecuted}`);
|
|
3213
|
+
lines.push("");
|
|
3214
|
+
if (result.actionsTaken.length > 0) {
|
|
3215
|
+
lines.push("Actions taken:");
|
|
3216
|
+
for (const action of result.actionsTaken) {
|
|
3217
|
+
lines.push(` • ${action}`);
|
|
3218
|
+
}
|
|
3219
|
+
lines.push("");
|
|
3220
|
+
}
|
|
3221
|
+
if (result.requiresManualIntervention && result.manualSteps && result.manualSteps.length > 0) {
|
|
3222
|
+
lines.push("Manual steps required:");
|
|
3223
|
+
for (let i = 0; i < result.manualSteps.length; i++) {
|
|
3224
|
+
lines.push(` ${i + 1}. ${result.manualSteps[i]}`);
|
|
3225
|
+
}
|
|
3226
|
+
lines.push("");
|
|
3227
|
+
}
|
|
3228
|
+
lines.push("Detailed analysis:");
|
|
3229
|
+
lines.push("───────────────────────────────────────────────────────────");
|
|
3230
|
+
lines.push(result.message);
|
|
3231
|
+
lines.push("───────────────────────────────────────────────────────────");
|
|
3232
|
+
lines.push("");
|
|
3233
|
+
if (result.success) {
|
|
3234
|
+
lines.push("You can now retry the publish command.");
|
|
3235
|
+
} else if (result.requiresManualIntervention) {
|
|
3236
|
+
lines.push("Please complete the manual steps above, then retry the publish command.");
|
|
3237
|
+
} else {
|
|
3238
|
+
lines.push("The issue could not be resolved automatically. Please review the analysis above.");
|
|
3239
|
+
}
|
|
3240
|
+
lines.push("");
|
|
3241
|
+
return lines.join("\n");
|
|
3242
|
+
}
|
|
3243
|
+
function createConversationLogger(config) {
|
|
3244
|
+
const {
|
|
3245
|
+
outputDir,
|
|
3246
|
+
format = "json",
|
|
3247
|
+
storage,
|
|
3248
|
+
logger: logger2
|
|
3249
|
+
} = config;
|
|
3250
|
+
return {
|
|
3251
|
+
/**
|
|
3252
|
+
* Save a conversation to disk
|
|
3253
|
+
*/
|
|
3254
|
+
async save(conversationId, messages, metadata) {
|
|
3255
|
+
try {
|
|
3256
|
+
if (!storage) {
|
|
3257
|
+
throw new Error("Storage adapter required");
|
|
3258
|
+
}
|
|
3259
|
+
const conversation = {
|
|
3260
|
+
id: conversationId,
|
|
3261
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3262
|
+
messages,
|
|
3263
|
+
metadata: metadata || {}
|
|
3264
|
+
};
|
|
3265
|
+
const filename = `${conversationId}.${format}`;
|
|
3266
|
+
const filepath = path__default.join(outputDir, filename);
|
|
3267
|
+
const content = format === "json" ? JSON.stringify(conversation, null, 2) : formatConversationAsMarkdown(conversation);
|
|
3268
|
+
await storage.writeOutput(filename, content);
|
|
3269
|
+
if (logger2) {
|
|
3270
|
+
logger2.debug(`Logged conversation ${conversationId} to ${filepath}`);
|
|
3271
|
+
}
|
|
3272
|
+
return filepath;
|
|
3273
|
+
} catch (error) {
|
|
3274
|
+
if (logger2) {
|
|
3275
|
+
logger2.warn(`Failed to log conversation ${conversationId}: ${error.message}`);
|
|
3276
|
+
}
|
|
3277
|
+
throw error;
|
|
3278
|
+
}
|
|
3279
|
+
},
|
|
3280
|
+
/**
|
|
3281
|
+
* Load a conversation from disk
|
|
3282
|
+
*/
|
|
3283
|
+
async load(conversationId) {
|
|
3284
|
+
try {
|
|
3285
|
+
if (!storage) {
|
|
3286
|
+
throw new Error("Storage adapter required");
|
|
3287
|
+
}
|
|
3288
|
+
const filename = `${conversationId}.${format}`;
|
|
3289
|
+
const content = await storage.readFile(path__default.join(outputDir, filename), "utf-8");
|
|
3290
|
+
let conversation;
|
|
3291
|
+
if (format === "json") {
|
|
3292
|
+
try {
|
|
3293
|
+
conversation = JSON.parse(content);
|
|
3294
|
+
} catch (parseError) {
|
|
3295
|
+
throw new Error(`Failed to parse conversation JSON: ${parseError.message}`);
|
|
3296
|
+
}
|
|
3297
|
+
} else {
|
|
3298
|
+
conversation = content;
|
|
3299
|
+
}
|
|
3300
|
+
if (logger2) {
|
|
3301
|
+
logger2.info(`Loaded conversation ${conversationId}`);
|
|
3302
|
+
}
|
|
3303
|
+
return conversation;
|
|
3304
|
+
} catch (error) {
|
|
3305
|
+
if (logger2) {
|
|
3306
|
+
logger2.warn(`Failed to load conversation ${conversationId}: ${error.message}`);
|
|
3307
|
+
}
|
|
3308
|
+
throw error;
|
|
3309
|
+
}
|
|
3310
|
+
},
|
|
3311
|
+
/**
|
|
3312
|
+
* Get conversation summary
|
|
3313
|
+
*/
|
|
3314
|
+
async getSummary(conversationId) {
|
|
3315
|
+
try {
|
|
3316
|
+
const conversation = await this.load(conversationId);
|
|
3317
|
+
return {
|
|
3318
|
+
id: conversation.id,
|
|
3319
|
+
timestamp: conversation.timestamp,
|
|
3320
|
+
messageCount: conversation.messages?.length || 0,
|
|
3321
|
+
metadata: conversation.metadata
|
|
3322
|
+
};
|
|
3323
|
+
} catch (error) {
|
|
3324
|
+
if (logger2) {
|
|
3325
|
+
logger2.warn(`Failed to get summary for ${conversationId}: ${error.message}`);
|
|
3326
|
+
}
|
|
3327
|
+
throw error;
|
|
3328
|
+
}
|
|
3329
|
+
},
|
|
3330
|
+
/**
|
|
3331
|
+
* Create riotprompt ConversationLogger instance for advanced usage
|
|
3332
|
+
*/
|
|
3333
|
+
createRiotLogger() {
|
|
3334
|
+
const logConfig = {
|
|
3335
|
+
enabled: true,
|
|
3336
|
+
outputPath: outputDir,
|
|
3337
|
+
format,
|
|
3338
|
+
includeMetadata: true
|
|
3339
|
+
};
|
|
3340
|
+
return new ConversationLogger(logConfig, logger2);
|
|
3341
|
+
}
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
function formatConversationAsMarkdown(conversation) {
|
|
3345
|
+
const lines = [];
|
|
3346
|
+
lines.push(`# Conversation: ${conversation.id}`);
|
|
3347
|
+
lines.push("");
|
|
3348
|
+
lines.push(`**Timestamp:** ${conversation.timestamp}`);
|
|
3349
|
+
if (conversation.metadata) {
|
|
3350
|
+
lines.push("");
|
|
3351
|
+
lines.push("## Metadata");
|
|
3352
|
+
lines.push("```json");
|
|
3353
|
+
lines.push(JSON.stringify(conversation.metadata, null, 2));
|
|
3354
|
+
lines.push("```");
|
|
3355
|
+
}
|
|
3356
|
+
lines.push("");
|
|
3357
|
+
lines.push("## Messages");
|
|
3358
|
+
lines.push("");
|
|
3359
|
+
for (let i = 0; i < conversation.messages.length; i++) {
|
|
3360
|
+
const msg = conversation.messages[i];
|
|
3361
|
+
lines.push(`### Message ${i + 1}: ${msg.role}`);
|
|
3362
|
+
lines.push("");
|
|
3363
|
+
lines.push(msg.content || "(no content)");
|
|
3364
|
+
lines.push("");
|
|
3365
|
+
}
|
|
3366
|
+
return lines.join("\n");
|
|
3367
|
+
}
|
|
3368
|
+
function generateConversationId(command) {
|
|
3369
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").split(".")[0];
|
|
3370
|
+
return `${command}-${timestamp}`;
|
|
3371
|
+
}
|
|
3372
|
+
async function generateReflectionReport(options) {
|
|
3373
|
+
const {
|
|
3374
|
+
iterations,
|
|
3375
|
+
toolCallsExecuted,
|
|
3376
|
+
maxIterations,
|
|
3377
|
+
toolMetrics,
|
|
3378
|
+
conversationHistory,
|
|
3379
|
+
commitMessage,
|
|
3380
|
+
suggestedSplits,
|
|
3381
|
+
releaseNotes,
|
|
3382
|
+
logger: logger2
|
|
3383
|
+
} = options;
|
|
3384
|
+
const sections = [];
|
|
3385
|
+
sections.push("# Agentic Workflow - Self-Reflection Report");
|
|
3386
|
+
sections.push("");
|
|
3387
|
+
sections.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
3388
|
+
sections.push("");
|
|
3389
|
+
sections.push("## Execution Summary");
|
|
3390
|
+
sections.push("");
|
|
3391
|
+
sections.push(`- **Iterations**: ${iterations}${iterations >= maxIterations ? " (max reached)" : ""}`);
|
|
3392
|
+
sections.push(`- **Tool Calls**: ${toolCallsExecuted}`);
|
|
3393
|
+
sections.push(`- **Unique Tools**: ${new Set(toolMetrics.map((m) => m.name)).size}`);
|
|
3394
|
+
sections.push("");
|
|
3395
|
+
const toolStats = calculateToolStats(toolMetrics);
|
|
3396
|
+
sections.push("## Tool Effectiveness Analysis");
|
|
3397
|
+
sections.push("");
|
|
3398
|
+
if (toolStats.size === 0) {
|
|
3399
|
+
sections.push("*No tools were executed during this run.*");
|
|
3400
|
+
} else {
|
|
3401
|
+
sections.push("| Tool | Calls | Success | Failures | Success Rate | Avg Duration |");
|
|
3402
|
+
sections.push("|------|-------|---------|----------|--------------|--------------|");
|
|
3403
|
+
for (const [toolName, stats] of Array.from(toolStats.entries()).sort((a, b) => b[1].total - a[1].total)) {
|
|
3404
|
+
const successRate = stats.total > 0 ? (stats.success / stats.total * 100).toFixed(1) : "0.0";
|
|
3405
|
+
const avgDuration = stats.total > 0 ? (stats.totalDuration / stats.total).toFixed(0) : "0";
|
|
3406
|
+
sections.push(`| ${toolName} | ${stats.total} | ${stats.success} | ${stats.failures} | ${successRate}% | ${avgDuration}ms |`);
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
sections.push("");
|
|
3410
|
+
if (toolStats.size > 0) {
|
|
3411
|
+
sections.push("### Tool Performance Insights");
|
|
3412
|
+
sections.push("");
|
|
3413
|
+
const failedTools = Array.from(toolStats.entries()).filter(([_, stats]) => stats.failures > 0);
|
|
3414
|
+
if (failedTools.length > 0) {
|
|
3415
|
+
sections.push("**Tools with Failures:**");
|
|
3416
|
+
for (const [toolName, stats] of failedTools) {
|
|
3417
|
+
const failureRate = stats.total > 0 ? (stats.failures / stats.total * 100).toFixed(1) : "0.0";
|
|
3418
|
+
sections.push(`- ${toolName}: ${stats.failures}/${stats.total} failures (${failureRate}%)`);
|
|
3419
|
+
}
|
|
3420
|
+
sections.push("");
|
|
3421
|
+
}
|
|
3422
|
+
const slowTools = Array.from(toolStats.entries()).filter(([_, stats]) => stats.total > 0 && stats.totalDuration / stats.total > 1e3).sort((a, b) => {
|
|
3423
|
+
const avgA = a[1].total > 0 ? a[1].totalDuration / a[1].total : 0;
|
|
3424
|
+
const avgB = b[1].total > 0 ? b[1].totalDuration / b[1].total : 0;
|
|
3425
|
+
return avgB - avgA;
|
|
3426
|
+
});
|
|
3427
|
+
if (slowTools.length > 0) {
|
|
3428
|
+
sections.push("**Slow Tools (>1s average):**");
|
|
3429
|
+
for (const [toolName, stats] of slowTools) {
|
|
3430
|
+
const avgDuration = stats.total > 0 ? (stats.totalDuration / stats.total / 1e3).toFixed(2) : "0.00";
|
|
3431
|
+
sections.push(`- ${toolName}: ${avgDuration}s average`);
|
|
3432
|
+
}
|
|
3433
|
+
sections.push("");
|
|
3434
|
+
}
|
|
3435
|
+
sections.push("**Most Frequently Used:**");
|
|
3436
|
+
const topTools = Array.from(toolStats.entries()).slice(0, 3);
|
|
3437
|
+
for (const [toolName, stats] of topTools) {
|
|
3438
|
+
sections.push(`- ${toolName}: ${stats.total} calls`);
|
|
3439
|
+
}
|
|
3440
|
+
sections.push("");
|
|
3441
|
+
}
|
|
3442
|
+
sections.push("## Recommendations for Improvement");
|
|
3443
|
+
sections.push("");
|
|
3444
|
+
const recommendations = generateRecommendations(options, toolStats);
|
|
3445
|
+
if (recommendations.length === 0) {
|
|
3446
|
+
sections.push("*No specific recommendations at this time. Execution appears optimal.*");
|
|
3447
|
+
} else {
|
|
3448
|
+
for (const rec of recommendations) {
|
|
3449
|
+
sections.push(rec);
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
sections.push("");
|
|
3453
|
+
sections.push("## Detailed Execution Timeline");
|
|
3454
|
+
sections.push("");
|
|
3455
|
+
if (toolMetrics.length === 0) {
|
|
3456
|
+
sections.push("*No tool execution timeline available.*");
|
|
3457
|
+
} else {
|
|
3458
|
+
sections.push("| Time | Iteration | Tool | Result | Duration |");
|
|
3459
|
+
sections.push("|------|-----------|------|--------|----------|");
|
|
3460
|
+
for (const metric of toolMetrics) {
|
|
3461
|
+
const time = new Date(metric.timestamp).toLocaleTimeString();
|
|
3462
|
+
const result = metric.success ? "✅ Success" : `❌ ${metric.error || "Failed"}`;
|
|
3463
|
+
sections.push(`| ${time} | ${metric.iteration} | ${metric.name} | ${result} | ${metric.duration}ms |`);
|
|
3464
|
+
}
|
|
3465
|
+
sections.push("");
|
|
3466
|
+
}
|
|
3467
|
+
if (conversationHistory && conversationHistory.length > 0) {
|
|
3468
|
+
sections.push("## Conversation History");
|
|
3469
|
+
sections.push("");
|
|
3470
|
+
sections.push("<details>");
|
|
3471
|
+
sections.push("<summary>Click to expand full agentic interaction</summary>");
|
|
3472
|
+
sections.push("");
|
|
3473
|
+
sections.push("```json");
|
|
3474
|
+
sections.push(JSON.stringify(conversationHistory, null, 2));
|
|
3475
|
+
sections.push("```");
|
|
3476
|
+
sections.push("");
|
|
3477
|
+
sections.push("</details>");
|
|
3478
|
+
sections.push("");
|
|
3479
|
+
}
|
|
3480
|
+
if (commitMessage) {
|
|
3481
|
+
sections.push("## Generated Commit Message");
|
|
3482
|
+
sections.push("");
|
|
3483
|
+
sections.push("```");
|
|
3484
|
+
sections.push(commitMessage);
|
|
3485
|
+
sections.push("```");
|
|
3486
|
+
sections.push("");
|
|
3487
|
+
if (suggestedSplits && suggestedSplits.length > 1) {
|
|
3488
|
+
sections.push("## Suggested Commit Splits");
|
|
3489
|
+
sections.push("");
|
|
3490
|
+
for (let i = 0; i < suggestedSplits.length; i++) {
|
|
3491
|
+
const split = suggestedSplits[i];
|
|
3492
|
+
sections.push(`### Split ${i + 1}`);
|
|
3493
|
+
sections.push("");
|
|
3494
|
+
sections.push(`**Files**: ${split.files.join(", ")}`);
|
|
3495
|
+
sections.push("");
|
|
3496
|
+
sections.push(`**Rationale**: ${split.rationale}`);
|
|
3497
|
+
sections.push("");
|
|
3498
|
+
sections.push(`**Message**:`);
|
|
3499
|
+
sections.push("```");
|
|
3500
|
+
sections.push(split.message);
|
|
3501
|
+
sections.push("```");
|
|
3502
|
+
sections.push("");
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
if (releaseNotes) {
|
|
3507
|
+
sections.push("## Generated Release Notes");
|
|
3508
|
+
sections.push("");
|
|
3509
|
+
sections.push("### Title");
|
|
3510
|
+
sections.push("```");
|
|
3511
|
+
sections.push(releaseNotes.title);
|
|
3512
|
+
sections.push("```");
|
|
3513
|
+
sections.push("");
|
|
3514
|
+
sections.push("### Body");
|
|
3515
|
+
sections.push("```markdown");
|
|
3516
|
+
sections.push(releaseNotes.body);
|
|
3517
|
+
sections.push("```");
|
|
3518
|
+
sections.push("");
|
|
3519
|
+
}
|
|
3520
|
+
if (logger2) {
|
|
3521
|
+
logger2.debug(`Generated reflection report with ${toolStats.size} unique tools`);
|
|
3522
|
+
}
|
|
3523
|
+
return sections.join("\n");
|
|
3524
|
+
}
|
|
3525
|
+
function calculateToolStats(toolMetrics) {
|
|
3526
|
+
const stats = /* @__PURE__ */ new Map();
|
|
3527
|
+
for (const metric of toolMetrics) {
|
|
3528
|
+
if (!stats.has(metric.name)) {
|
|
3529
|
+
stats.set(metric.name, { total: 0, success: 0, failures: 0, totalDuration: 0 });
|
|
3530
|
+
}
|
|
3531
|
+
const toolStat = stats.get(metric.name);
|
|
3532
|
+
toolStat.total++;
|
|
3533
|
+
toolStat.totalDuration += metric.duration;
|
|
3534
|
+
if (metric.success) {
|
|
3535
|
+
toolStat.success++;
|
|
3536
|
+
} else {
|
|
3537
|
+
toolStat.failures++;
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
return stats;
|
|
3541
|
+
}
|
|
3542
|
+
function generateRecommendations(options, toolStats) {
|
|
3543
|
+
const recommendations = [];
|
|
3544
|
+
const toolsWithFailures = Array.from(toolStats.entries()).filter(([_, stats]) => stats.failures > 0);
|
|
3545
|
+
if (toolsWithFailures.length > 0) {
|
|
3546
|
+
recommendations.push("- **Tool Failures**: Investigate and fix tools that are failing. This may indicate issues with error handling or tool implementation.");
|
|
3547
|
+
}
|
|
3548
|
+
const slowTools = Array.from(toolStats.entries()).filter(([_, stats]) => stats.total > 0 && stats.totalDuration / stats.total > 1e3);
|
|
3549
|
+
if (slowTools.length > 0) {
|
|
3550
|
+
recommendations.push("- **Performance**: Consider optimizing slow tools or caching results to improve execution speed.");
|
|
3551
|
+
}
|
|
3552
|
+
if (options.iterations >= options.maxIterations) {
|
|
3553
|
+
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.");
|
|
3554
|
+
}
|
|
3555
|
+
const underutilizedTools = Array.from(toolStats.entries()).filter(([_, stats]) => stats.total === 1);
|
|
3556
|
+
if (underutilizedTools.length > 3) {
|
|
3557
|
+
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.");
|
|
3558
|
+
}
|
|
3559
|
+
const uniqueTools = toolStats.size;
|
|
3560
|
+
if (uniqueTools === 1 && options.toolCallsExecuted > 3) {
|
|
3561
|
+
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.");
|
|
3562
|
+
}
|
|
3563
|
+
return recommendations;
|
|
3564
|
+
}
|
|
3565
|
+
async function saveReflectionReport(report, outputPath, storage) {
|
|
3566
|
+
if (!storage) {
|
|
3567
|
+
throw new Error("Storage adapter required to save report");
|
|
3568
|
+
}
|
|
3569
|
+
const filename = outputPath.split("/").pop() || "reflection.md";
|
|
3570
|
+
await storage.writeOutput(filename, report);
|
|
3571
|
+
}
|
|
3572
|
+
function createMetricsCollector(config = {}) {
|
|
3573
|
+
const { logger: logger2 } = config;
|
|
3574
|
+
const collector = new MetricsCollector(logger2);
|
|
3575
|
+
const toolMetrics = [];
|
|
3576
|
+
return {
|
|
3577
|
+
/**
|
|
3578
|
+
* Record a tool execution
|
|
3579
|
+
*/
|
|
3580
|
+
recordToolExecution(metric) {
|
|
3581
|
+
toolMetrics.push(metric);
|
|
3582
|
+
collector.recordToolCall(
|
|
3583
|
+
metric.name,
|
|
3584
|
+
metric.iteration,
|
|
3585
|
+
metric.duration,
|
|
3586
|
+
metric.success,
|
|
3587
|
+
metric.error
|
|
3588
|
+
);
|
|
3589
|
+
if (logger2) {
|
|
3590
|
+
const status = metric.success ? "success" : "failed";
|
|
3591
|
+
logger2.debug(`Tool ${metric.name} ${status} in ${metric.duration}ms`);
|
|
3592
|
+
}
|
|
3593
|
+
},
|
|
3594
|
+
/**
|
|
3595
|
+
* Record an iteration
|
|
3596
|
+
*/
|
|
3597
|
+
recordIteration() {
|
|
3598
|
+
collector.incrementIteration();
|
|
3599
|
+
if (logger2) {
|
|
3600
|
+
logger2.debug(`Iteration incremented`);
|
|
3601
|
+
}
|
|
3602
|
+
},
|
|
3603
|
+
/**
|
|
3604
|
+
* Get tool metrics in legacy format (for backward compatibility)
|
|
3605
|
+
*/
|
|
3606
|
+
getToolMetrics() {
|
|
3607
|
+
return [...toolMetrics];
|
|
3608
|
+
},
|
|
3609
|
+
/**
|
|
3610
|
+
* Generate execution metrics report
|
|
3611
|
+
*/
|
|
3612
|
+
generateReport() {
|
|
3613
|
+
return collector.getMetrics([]);
|
|
3614
|
+
},
|
|
3615
|
+
/**
|
|
3616
|
+
* Get summary statistics
|
|
3617
|
+
*/
|
|
3618
|
+
getSummary() {
|
|
3619
|
+
const report = collector.getMetrics([]);
|
|
3620
|
+
return {
|
|
3621
|
+
totalIterations: report.iterations,
|
|
3622
|
+
totalToolCalls: report.toolCallsExecuted,
|
|
3623
|
+
totalDuration: report.totalDuration,
|
|
3624
|
+
toolMetrics: report.toolMetrics,
|
|
3625
|
+
toolStats: report.toolStats
|
|
3626
|
+
};
|
|
3627
|
+
},
|
|
3628
|
+
/**
|
|
3629
|
+
* Reset all metrics
|
|
3630
|
+
*/
|
|
3631
|
+
reset() {
|
|
3632
|
+
toolMetrics.length = 0;
|
|
3633
|
+
}
|
|
3634
|
+
};
|
|
3635
|
+
}
|
|
2429
3636
|
export {
|
|
2430
3637
|
AgenticExecutor,
|
|
2431
3638
|
OpenAIError,
|
|
2432
3639
|
STANDARD_CHOICES,
|
|
2433
3640
|
SecureTempFile,
|
|
3641
|
+
TemplateNames,
|
|
2434
3642
|
ToolRegistry,
|
|
2435
3643
|
cleanupTempFile,
|
|
2436
3644
|
createCommitPrompt,
|
|
2437
3645
|
createCommitTools,
|
|
2438
3646
|
createCompletion,
|
|
2439
3647
|
createCompletionWithRetry,
|
|
3648
|
+
createConversationLogger,
|
|
3649
|
+
createMetricsCollector,
|
|
2440
3650
|
createNoOpLogger,
|
|
3651
|
+
createPublishTools,
|
|
2441
3652
|
createReleasePrompt,
|
|
2442
3653
|
createReleaseTools,
|
|
2443
3654
|
createReviewPrompt,
|
|
2444
3655
|
createSecureTempFile,
|
|
2445
3656
|
createToolRegistry,
|
|
2446
3657
|
editContentInEditor,
|
|
3658
|
+
formatAgenticPublishResult,
|
|
3659
|
+
generateConversationId,
|
|
3660
|
+
generateReflectionReport,
|
|
2447
3661
|
getLLMFeedbackInEditor,
|
|
2448
3662
|
getLogger,
|
|
2449
3663
|
getModelForCommand,
|
|
2450
3664
|
getOpenAIReasoningForCommand,
|
|
2451
3665
|
getUserChoice,
|
|
2452
3666
|
getUserTextInput,
|
|
3667
|
+
initializeTemplates,
|
|
2453
3668
|
isRateLimitError,
|
|
2454
3669
|
isTokenLimitError,
|
|
2455
3670
|
requireTTY,
|
|
2456
3671
|
runAgentic,
|
|
2457
3672
|
runAgenticCommit,
|
|
3673
|
+
runAgenticPublish,
|
|
2458
3674
|
runAgenticRelease,
|
|
3675
|
+
saveReflectionReport,
|
|
2459
3676
|
setLogger,
|
|
2460
3677
|
transcribeAudio,
|
|
2461
3678
|
tryLoadWinston
|