@eldrforge/ai-service 0.1.14 → 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/dist/index.js +1266 -78
- 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,26 +1626,10 @@ async function runAgenticCommit(config) {
|
|
|
1496
1626
|
toolMetrics: result.toolMetrics
|
|
1497
1627
|
};
|
|
1498
1628
|
}
|
|
1499
|
-
function buildSystemPrompt$
|
|
1629
|
+
function buildSystemPrompt$2(toolGuidance) {
|
|
1500
1630
|
return `You are an expert software engineer tasked with generating meaningful commit messages.
|
|
1501
1631
|
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
## When to Use Each Tool
|
|
1505
|
-
|
|
1506
|
-
**Understanding What Changed:**
|
|
1507
|
-
- get_file_content: Use when you need full context. Good for: seeing entire class/function being modified, checking imports, understanding overall structure
|
|
1508
|
-
- analyze_diff_section: Use when diff is confusing. Good for: expanding context around small changes, seeing how code integrates
|
|
1509
|
-
- get_file_dependencies: Use for import/refactor changes. Good for: understanding what's being moved/reorganized, checking dependency impact
|
|
1510
|
-
|
|
1511
|
-
**Understanding Why:**
|
|
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
|
|
1515
|
-
|
|
1516
|
-
**Organizing Changes:**
|
|
1517
|
-
- group_files_by_concern: Use when multiple files changed. Good for: identifying logical groupings, determining if split is needed
|
|
1518
|
-
- get_related_tests: Use for logic changes. Good for: understanding intent from test changes, verifying behavior changes
|
|
1632
|
+
${toolGuidance}
|
|
1519
1633
|
|
|
1520
1634
|
## Investigation Strategy
|
|
1521
1635
|
|
|
@@ -1976,6 +2090,9 @@ function createGetTagHistoryTool() {
|
|
|
1976
2090
|
return {
|
|
1977
2091
|
name: "get_tag_history",
|
|
1978
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" }],
|
|
1979
2096
|
parameters: {
|
|
1980
2097
|
type: "object",
|
|
1981
2098
|
properties: {
|
|
@@ -2025,6 +2142,9 @@ function createComparePreviousReleaseTool() {
|
|
|
2025
2142
|
return {
|
|
2026
2143
|
name: "compare_previous_release",
|
|
2027
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" }],
|
|
2028
2148
|
parameters: {
|
|
2029
2149
|
type: "object",
|
|
2030
2150
|
properties: {
|
|
@@ -2080,6 +2200,9 @@ function createGetReleaseStatsTool() {
|
|
|
2080
2200
|
return {
|
|
2081
2201
|
name: "get_release_stats",
|
|
2082
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" }],
|
|
2083
2206
|
parameters: {
|
|
2084
2207
|
type: "object",
|
|
2085
2208
|
properties: {
|
|
@@ -2133,6 +2256,9 @@ function createGetBreakingChangesTool() {
|
|
|
2133
2256
|
return {
|
|
2134
2257
|
name: "get_breaking_changes",
|
|
2135
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" }],
|
|
2136
2262
|
parameters: {
|
|
2137
2263
|
type: "object",
|
|
2138
2264
|
properties: {
|
|
@@ -2196,6 +2322,9 @@ function createAnalyzeCommitPatternsTool() {
|
|
|
2196
2322
|
return {
|
|
2197
2323
|
name: "analyze_commit_patterns",
|
|
2198
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" }],
|
|
2199
2328
|
parameters: {
|
|
2200
2329
|
type: "object",
|
|
2201
2330
|
properties: {
|
|
@@ -2267,7 +2396,8 @@ async function runAgenticRelease(config) {
|
|
|
2267
2396
|
debugResponseFile,
|
|
2268
2397
|
storage,
|
|
2269
2398
|
logger: logger2,
|
|
2270
|
-
openaiReasoning
|
|
2399
|
+
openaiReasoning,
|
|
2400
|
+
tokenBudget
|
|
2271
2401
|
} = config;
|
|
2272
2402
|
const toolRegistry = createToolRegistry({
|
|
2273
2403
|
workingDirectory: process.cwd(),
|
|
@@ -2276,7 +2406,13 @@ async function runAgenticRelease(config) {
|
|
|
2276
2406
|
});
|
|
2277
2407
|
const tools = createReleaseTools();
|
|
2278
2408
|
toolRegistry.registerAll(tools);
|
|
2279
|
-
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);
|
|
2280
2416
|
const userMessage = buildUserMessage({
|
|
2281
2417
|
fromRef,
|
|
2282
2418
|
toRef,
|
|
@@ -2300,7 +2436,13 @@ async function runAgenticRelease(config) {
|
|
|
2300
2436
|
debugResponseFile,
|
|
2301
2437
|
storage,
|
|
2302
2438
|
logger: logger2,
|
|
2303
|
-
openaiReasoning
|
|
2439
|
+
openaiReasoning,
|
|
2440
|
+
tokenBudget: tokenBudget || {
|
|
2441
|
+
max: 2e5,
|
|
2442
|
+
reserveForResponse: 8e3,
|
|
2443
|
+
strategy: "fifo",
|
|
2444
|
+
onBudgetExceeded: "compress"
|
|
2445
|
+
}
|
|
2304
2446
|
};
|
|
2305
2447
|
const result = await runAgentic(agenticConfig);
|
|
2306
2448
|
const parsed = parseAgenticResult(result.finalMessage);
|
|
@@ -2312,17 +2454,10 @@ async function runAgenticRelease(config) {
|
|
|
2312
2454
|
toolMetrics: result.toolMetrics
|
|
2313
2455
|
};
|
|
2314
2456
|
}
|
|
2315
|
-
function buildSystemPrompt() {
|
|
2457
|
+
function buildSystemPrompt$1(toolGuidance) {
|
|
2316
2458
|
return `You are an expert software engineer and technical writer tasked with generating comprehensive, thoughtful release notes.
|
|
2317
2459
|
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
## Investigation Tools
|
|
2321
|
-
|
|
2322
|
-
**Understanding Context & History:**
|
|
2323
|
-
- get_file_history: Use when you need to understand how a file evolved. Good for: seeing if a refactor is part of a larger pattern, understanding why certain decisions were made
|
|
2324
|
-
- get_recent_commits: Use to see what happened to files recently. Good for: detecting related work, understanding if this is part of an ongoing effort
|
|
2325
|
-
- compare_previous_release: Use to contextualize scope. Good for: comparing size/impact of this release vs previous ones, identifying if this is major/minor
|
|
2460
|
+
${toolGuidance}
|
|
2326
2461
|
- get_tag_history: Use early to understand release cadence. Good for: establishing context about project versioning patterns
|
|
2327
2462
|
|
|
2328
2463
|
**Analyzing Current Changes:**
|
|
@@ -2418,8 +2553,13 @@ function parseAgenticResult(finalMessage) {
|
|
|
2418
2553
|
if (jsonMatch) {
|
|
2419
2554
|
try {
|
|
2420
2555
|
const jsonStr = jsonMatch[1].trim();
|
|
2421
|
-
|
|
2422
|
-
|
|
2556
|
+
let parsed;
|
|
2557
|
+
try {
|
|
2558
|
+
parsed = JSON.parse(jsonStr);
|
|
2559
|
+
} catch {
|
|
2560
|
+
parsed = null;
|
|
2561
|
+
}
|
|
2562
|
+
if (parsed && parsed.title && parsed.body) {
|
|
2423
2563
|
return {
|
|
2424
2564
|
releaseNotes: {
|
|
2425
2565
|
title: parsed.title,
|
|
@@ -2455,36 +2595,1084 @@ function parseAgenticResult(finalMessage) {
|
|
|
2455
2595
|
}
|
|
2456
2596
|
};
|
|
2457
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
|
+
}
|
|
2458
3636
|
export {
|
|
2459
3637
|
AgenticExecutor,
|
|
2460
3638
|
OpenAIError,
|
|
2461
3639
|
STANDARD_CHOICES,
|
|
2462
3640
|
SecureTempFile,
|
|
3641
|
+
TemplateNames,
|
|
2463
3642
|
ToolRegistry,
|
|
2464
3643
|
cleanupTempFile,
|
|
2465
3644
|
createCommitPrompt,
|
|
2466
3645
|
createCommitTools,
|
|
2467
3646
|
createCompletion,
|
|
2468
3647
|
createCompletionWithRetry,
|
|
3648
|
+
createConversationLogger,
|
|
3649
|
+
createMetricsCollector,
|
|
2469
3650
|
createNoOpLogger,
|
|
3651
|
+
createPublishTools,
|
|
2470
3652
|
createReleasePrompt,
|
|
2471
3653
|
createReleaseTools,
|
|
2472
3654
|
createReviewPrompt,
|
|
2473
3655
|
createSecureTempFile,
|
|
2474
3656
|
createToolRegistry,
|
|
2475
3657
|
editContentInEditor,
|
|
3658
|
+
formatAgenticPublishResult,
|
|
3659
|
+
generateConversationId,
|
|
3660
|
+
generateReflectionReport,
|
|
2476
3661
|
getLLMFeedbackInEditor,
|
|
2477
3662
|
getLogger,
|
|
2478
3663
|
getModelForCommand,
|
|
2479
3664
|
getOpenAIReasoningForCommand,
|
|
2480
3665
|
getUserChoice,
|
|
2481
3666
|
getUserTextInput,
|
|
3667
|
+
initializeTemplates,
|
|
2482
3668
|
isRateLimitError,
|
|
2483
3669
|
isTokenLimitError,
|
|
2484
3670
|
requireTTY,
|
|
2485
3671
|
runAgentic,
|
|
2486
3672
|
runAgenticCommit,
|
|
3673
|
+
runAgenticPublish,
|
|
2487
3674
|
runAgenticRelease,
|
|
3675
|
+
saveReflectionReport,
|
|
2488
3676
|
setLogger,
|
|
2489
3677
|
transcribeAudio,
|
|
2490
3678
|
tryLoadWinston
|