@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.
Files changed (40) hide show
  1. package/README.md +1112 -226
  2. package/dist/index.js +1325 -108
  3. package/dist/index.js.map +1 -1
  4. package/dist/src/agentic/commit.d.ts +6 -0
  5. package/dist/src/agentic/commit.d.ts.map +1 -1
  6. package/dist/src/agentic/executor.d.ts +7 -1
  7. package/dist/src/agentic/executor.d.ts.map +1 -1
  8. package/dist/src/agentic/publish.d.ts +31 -0
  9. package/dist/src/agentic/publish.d.ts.map +1 -0
  10. package/dist/src/agentic/release.d.ts +6 -0
  11. package/dist/src/agentic/release.d.ts.map +1 -1
  12. package/dist/src/ai.d.ts.map +1 -1
  13. package/dist/src/index.d.ts +3 -0
  14. package/dist/src/index.d.ts.map +1 -1
  15. package/dist/src/observability/conversation-logger.d.ts +53 -0
  16. package/dist/src/observability/conversation-logger.d.ts.map +1 -0
  17. package/dist/src/observability/index.d.ts +15 -0
  18. package/dist/src/observability/index.d.ts.map +1 -0
  19. package/dist/src/observability/metrics.d.ts +53 -0
  20. package/dist/src/observability/metrics.d.ts.map +1 -0
  21. package/dist/src/observability/reflection.d.ts +36 -0
  22. package/dist/src/observability/reflection.d.ts.map +1 -0
  23. package/dist/src/prompts/commit.d.ts.map +1 -1
  24. package/dist/src/prompts/index.d.ts +1 -0
  25. package/dist/src/prompts/index.d.ts.map +1 -1
  26. package/dist/src/prompts/release.d.ts.map +1 -1
  27. package/dist/src/prompts/review.d.ts.map +1 -1
  28. package/dist/src/prompts/templates.d.ts +22 -0
  29. package/dist/src/prompts/templates.d.ts.map +1 -0
  30. package/dist/src/tools/publish-tools.d.ts +6 -0
  31. package/dist/src/tools/publish-tools.d.ts.map +1 -0
  32. package/dist/src/tools/types.d.ts +17 -0
  33. package/dist/src/tools/types.d.ts.map +1 -1
  34. package/examples/01-simple-commit.ts +80 -0
  35. package/examples/02-release-notes.ts +124 -0
  36. package/examples/03-interactive-commit.ts +150 -0
  37. package/examples/04-custom-storage.ts +162 -0
  38. package/examples/05-custom-tools.ts +243 -0
  39. package/examples/README.md +320 -0
  40. 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 { recipe } from "@riotprompt/riotprompt";
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
- timeoutId = setTimeout(() => reject(new OpenAIError(`OpenAI API call timed out after ${timeoutMs2 / 1e3} seconds`)), timeoutMs2);
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 recipe(basePath).persona({ path: "personas/you.md" }).instructions({ path: "instructions/commit.md" }).overridePaths(_overridePaths ?? []).overrides(_overrides ?? true).content(...contentItems).context(...contextItems).cook();
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 recipe(basePath).persona({ path: "personas/releaser.md" }).instructions({ path: "instructions/release.md" }).overridePaths(_overridePaths ?? []).overrides(_overrides ?? true).content(...contentItems).context(...contextItems).cook();
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 recipe(basePath).persona({ path: "personas/you.md" }).instructions({ path: "instructions/review.md" }).overridePaths(_overridePaths ?? []).overrides(_overrides ?? true).content(...contentItems).context(...contextItems).cook();
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 messages = [...initialMessages];
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
- messages.push({
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: messages,
939
+ conversationHistory: conversation.toMessages(),
875
940
  toolMetrics: this.toolMetrics
876
941
  };
877
942
  }
878
- messages.push({
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
- const args = JSON.parse(toolCall.function.arguments);
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
- messages.push({
896
- role: "tool",
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
- messages.push({
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
- messages.push({
938
- role: "user",
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
- messages,
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
- messages.push({
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: messages,
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 systemPrompt = buildSystemPrompt$1();
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$1() {
1629
+ function buildSystemPrompt$2(toolGuidance) {
1495
1630
  return `You are an expert software engineer tasked with generating meaningful commit messages.
1496
1631
 
1497
- You have access to tools that let you investigate changes in detail:
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
- Your process should be:
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
- Guidelines:
1514
- - Use tools strategically - don't call every tool on every file
1515
- - Look at test changes to understand intent
1516
- - Check recent history to avoid redundant messages
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
- - Synthesize findings into clear, informative commit messages
1519
- - Follow conventional commit format when appropriate (feat:, fix:, refactor:, etc.)
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 systemPrompt = buildSystemPrompt();
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
- You have access to tools that let you investigate the release in detail:
2296
- - get_file_history: View commit history for specific files
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
- Your process should be:
2311
- 1. Analyze the commit log and diff to understand the overall scope of changes
2312
- 2. Use tools strategically to investigate significant changes that need more context
2313
- 3. Look at previous releases to understand how this release fits into the project's evolution
2314
- 4. Identify patterns, themes, and connections between changes
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
- Guidelines:
2320
- - Use tools strategically - focus on understanding significant changes
2321
- - Look at test changes to understand intent and functionality
2322
- - Check previous releases to provide context and compare scope
2323
- - Identify patterns and themes across multiple commits
2324
- - Consider the audience and what context they need
2325
- - Be thorough and analytical, especially for large releases
2326
- - Follow the release notes format and best practices provided
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
- const parsed = JSON.parse(jsonStr);
2393
- if (parsed.title && parsed.body) {
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