@eldrforge/ai-service 0.1.14 → 0.1.16

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