gaunt-sloth-assistant 0.5.0 → 0.5.1

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 (135) hide show
  1. package/.claude/settings.local.json +15 -0
  2. package/.gsloth.backstory.md +0 -0
  3. package/.gsloth.guidelines.md +0 -0
  4. package/.gsloth.review.md +0 -0
  5. package/.gsloth.system.md +10 -0
  6. package/.prettierrc.json +0 -0
  7. package/CLAUDE.md +1 -0
  8. package/LICENSE +0 -0
  9. package/README.md +9 -0
  10. package/ROADMAP.md +0 -0
  11. package/assets/gaunt-sloth-logo.png +0 -0
  12. package/assets/release-notes/v0_4_0.md +0 -0
  13. package/assets/release-notes/v0_5_0.md +0 -0
  14. package/assets/release-notes/v0_5_1.md +47 -0
  15. package/dist/commands/askCommand.d.ts +0 -0
  16. package/dist/commands/askCommand.js +9 -5
  17. package/dist/commands/askCommand.js.map +1 -1
  18. package/dist/commands/commandUtils.d.ts +25 -0
  19. package/dist/commands/commandUtils.js +48 -0
  20. package/dist/commands/commandUtils.js.map +1 -0
  21. package/dist/commands/initCommand.d.ts +0 -0
  22. package/dist/commands/initCommand.js +0 -0
  23. package/dist/commands/initCommand.js.map +0 -0
  24. package/dist/commands/prCommand.d.ts +2 -0
  25. package/dist/commands/prCommand.js +52 -0
  26. package/dist/commands/prCommand.js.map +1 -0
  27. package/dist/commands/reviewCommand.d.ts +1 -2
  28. package/dist/commands/reviewCommand.js +17 -98
  29. package/dist/commands/reviewCommand.js.map +1 -1
  30. package/dist/config.d.ts +18 -36
  31. package/dist/config.js +104 -84
  32. package/dist/config.js.map +1 -1
  33. package/dist/configs/anthropic.d.ts +0 -0
  34. package/dist/configs/anthropic.js +0 -0
  35. package/dist/configs/anthropic.js.map +0 -0
  36. package/dist/configs/fake.d.ts +0 -0
  37. package/dist/configs/fake.js +0 -0
  38. package/dist/configs/fake.js.map +0 -0
  39. package/dist/configs/groq.d.ts +0 -0
  40. package/dist/configs/groq.js +0 -0
  41. package/dist/configs/groq.js.map +0 -0
  42. package/dist/configs/vertexai.d.ts +0 -0
  43. package/dist/configs/vertexai.js +0 -0
  44. package/dist/configs/vertexai.js.map +0 -0
  45. package/dist/consoleUtils.d.ts +0 -0
  46. package/dist/consoleUtils.js +0 -0
  47. package/dist/consoleUtils.js.map +0 -0
  48. package/dist/constants.d.ts +7 -0
  49. package/dist/constants.js +8 -0
  50. package/dist/constants.js.map +1 -0
  51. package/dist/filePathUtils.d.ts +0 -0
  52. package/dist/filePathUtils.js +0 -0
  53. package/dist/filePathUtils.js.map +0 -0
  54. package/dist/index.d.ts +0 -0
  55. package/dist/index.js +4 -2
  56. package/dist/index.js.map +1 -1
  57. package/dist/llmUtils.d.ts +1 -1
  58. package/dist/llmUtils.js +85 -22
  59. package/dist/llmUtils.js.map +1 -1
  60. package/dist/modules/questionAnsweringModule.d.ts +2 -1
  61. package/dist/modules/questionAnsweringModule.js +4 -7
  62. package/dist/modules/questionAnsweringModule.js.map +1 -1
  63. package/dist/modules/reviewModule.d.ts +2 -1
  64. package/dist/modules/reviewModule.js +4 -7
  65. package/dist/modules/reviewModule.js.map +1 -1
  66. package/dist/modules/types.d.ts +0 -0
  67. package/dist/modules/types.js +0 -0
  68. package/dist/modules/types.js.map +0 -0
  69. package/dist/prompt.d.ts +1 -0
  70. package/dist/prompt.js +4 -1
  71. package/dist/prompt.js.map +1 -1
  72. package/dist/providers/file.d.ts +0 -0
  73. package/dist/providers/file.js +0 -0
  74. package/dist/providers/file.js.map +0 -0
  75. package/dist/providers/ghIssueProvider.d.ts +0 -0
  76. package/dist/providers/ghIssueProvider.js +3 -1
  77. package/dist/providers/ghIssueProvider.js.map +1 -1
  78. package/dist/providers/ghPrDiffProvider.d.ts +0 -0
  79. package/dist/providers/ghPrDiffProvider.js +3 -1
  80. package/dist/providers/ghPrDiffProvider.js.map +1 -1
  81. package/dist/providers/jiraIssueLegacyProvider.d.ts +0 -0
  82. package/dist/providers/jiraIssueLegacyProvider.js +0 -0
  83. package/dist/providers/jiraIssueLegacyProvider.js.map +0 -0
  84. package/dist/providers/jiraIssueProvider.d.ts +0 -0
  85. package/dist/providers/jiraIssueProvider.js +0 -0
  86. package/dist/providers/jiraIssueProvider.js.map +0 -0
  87. package/dist/providers/text.d.ts +0 -0
  88. package/dist/providers/text.js +0 -0
  89. package/dist/providers/text.js.map +0 -0
  90. package/dist/providers/types.d.ts +0 -0
  91. package/dist/providers/types.js +0 -0
  92. package/dist/providers/types.js.map +0 -0
  93. package/dist/systemUtils.d.ts +0 -0
  94. package/dist/systemUtils.js +0 -0
  95. package/dist/systemUtils.js.map +0 -0
  96. package/dist/utils.d.ts +0 -0
  97. package/dist/utils.js +0 -0
  98. package/dist/utils.js.map +0 -0
  99. package/docs/CONFIGURATION.md +0 -0
  100. package/docs/DEVELOPMENT.md +0 -0
  101. package/docs/RELEASE-HOWTO.md +0 -0
  102. package/eslint.config.js +0 -0
  103. package/maintenance/doc-maintenance.md +0 -0
  104. package/package.json +10 -8
  105. package/src/commands/askCommand.ts +9 -5
  106. package/src/commands/commandUtils.ts +77 -0
  107. package/src/commands/initCommand.ts +0 -0
  108. package/src/commands/prCommand.ts +93 -0
  109. package/src/commands/reviewCommand.ts +33 -155
  110. package/src/config.ts +121 -119
  111. package/src/configs/anthropic.ts +0 -0
  112. package/src/configs/fake.ts +0 -0
  113. package/src/configs/groq.ts +0 -0
  114. package/src/configs/vertexai.ts +0 -0
  115. package/src/consoleUtils.ts +0 -0
  116. package/src/constants.ts +7 -0
  117. package/src/filePathUtils.ts +0 -0
  118. package/src/index.ts +4 -2
  119. package/src/llmUtils.ts +100 -23
  120. package/src/modules/questionAnsweringModule.ts +6 -12
  121. package/src/modules/reviewModule.ts +11 -7
  122. package/src/modules/types.ts +0 -0
  123. package/src/prompt.ts +5 -1
  124. package/src/providers/file.ts +0 -0
  125. package/src/providers/ghIssueProvider.ts +3 -1
  126. package/src/providers/ghPrDiffProvider.ts +3 -1
  127. package/src/providers/jiraIssueLegacyProvider.ts +0 -0
  128. package/src/providers/jiraIssueProvider.ts +0 -0
  129. package/src/providers/text.ts +0 -0
  130. package/src/providers/types.ts +0 -0
  131. package/src/systemUtils.ts +0 -0
  132. package/src/utils.ts +0 -0
  133. package/tsconfig.json +0 -0
  134. package/vitest-it.config.ts +0 -0
  135. package/vitest.config.ts +0 -0
package/src/llmUtils.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  import type { Message } from '#src/modules/types.js';
2
- import { HumanMessage, isAIMessageChunk, SystemMessage } from '@langchain/core/messages';
2
+ import { HumanMessage, isAIMessage, SystemMessage } from '@langchain/core/messages';
3
3
  import { BaseChatModel } from '@langchain/core/language_models/chat_models';
4
4
  import { SlothConfig } from '#src/config.js';
5
+ import type { Connection } from '@langchain/mcp-adapters';
5
6
  import { MultiServerMCPClient } from '@langchain/mcp-adapters';
6
- import { display, displayError, displayInfo } from '#src/consoleUtils.js';
7
+ import { display, displayError, displayInfo, displayWarning } from '#src/consoleUtils.js';
7
8
  import { createReactAgent } from '@langchain/langgraph/prebuilt';
8
- import { stdout } from '#src/systemUtils.js';
9
+ import { getCurrentDir, stdout } from '#src/systemUtils.js';
10
+ import type { StructuredToolInterface } from '@langchain/core/tools';
11
+ import { ProgressIndicator } from '#src/utils.js';
9
12
 
10
13
  const llmGlobalSettings = {
11
14
  verbose: false,
@@ -15,17 +18,35 @@ export async function invoke(
15
18
  llm: BaseChatModel,
16
19
  systemMessage: string,
17
20
  prompt: string,
18
- config: SlothConfig
21
+ config: SlothConfig,
22
+ command?: 'ask' | 'pr' | 'review'
19
23
  ): Promise<string> {
24
+ try {
25
+ if (config.streamOutput && config.llm._llmType() === 'anthropic') {
26
+ displayWarning('To avoid known bug with Anthropic forcing streamOutput to false');
27
+ config.streamOutput = false;
28
+ }
29
+ } catch {}
20
30
  if (llmGlobalSettings.verbose) {
21
31
  llm.verbose = true;
22
32
  }
23
33
 
24
- const client = getClient(config);
34
+ // Merge command-specific filesystem config if provided
35
+ let effectiveConfig = config;
36
+ if (command && config.commands?.[command]?.filesystem !== undefined) {
37
+ effectiveConfig = {
38
+ ...config,
39
+ filesystem: config.commands[command].filesystem!,
40
+ };
41
+ }
42
+
43
+ const client = getClient(effectiveConfig);
44
+
45
+ const allTools = (await client?.getTools()) ?? [];
46
+ const tools = filterTools(allTools, effectiveConfig.filesystem || 'none');
25
47
 
26
- const tools = (await client?.getTools()) ?? [];
27
- if (tools && tools.length > 0) {
28
- displayInfo(`Loaded ${tools.length} MCP tools.`);
48
+ if (allTools.length > 0) {
49
+ displayInfo(`Loaded ${tools.length} tools.`);
29
50
  }
30
51
 
31
52
  // Create the React agent
@@ -38,20 +59,37 @@ export async function invoke(
38
59
  try {
39
60
  const messages: Message[] = [new SystemMessage(systemMessage), new HumanMessage(prompt)];
40
61
  display(`Connecting to LLM...`);
41
- const stream = await agent.stream({ messages }, { streamMode: 'messages' });
42
-
43
62
  const output = { aiMessage: '' };
44
- for await (const [chunk, _metadata] of stream) {
45
- if (isAIMessageChunk(chunk)) {
46
- if (config.streamOutput) {
47
- stdout.write(chunk.content as string, 'utf-8');
63
+ if (!config.streamOutput) {
64
+ const progress = new ProgressIndicator('Thinking.');
65
+ try {
66
+ const response = await agent.invoke({ messages });
67
+ output.aiMessage = response.messages[response.messages.length - 1].content as string;
68
+ const toolNames = response.messages
69
+ .filter((msg: any) => msg.tool_calls && msg.tool_calls.length > 0)
70
+ .flatMap((msg: any) => msg.tool_calls.map((tc: any) => tc.name));
71
+ if (toolNames.length > 0) {
72
+ displayInfo(`\nUsed tools: ${toolNames.join(', ')}`);
48
73
  }
49
- output.aiMessage += chunk.content;
50
- let toolCalls = chunk.tool_calls;
51
- if (toolCalls && toolCalls.length > 0) {
52
- const suffix = toolCalls.length > 1 ? 's' : '';
53
- const toolCallsString = toolCalls.map((t) => t?.name).join(', ');
54
- displayInfo(`Using tool${suffix} ${toolCallsString}`);
74
+ } catch (e) {
75
+ displayWarning(`Something went wrong ${(e as Error).message}`);
76
+ } finally {
77
+ progress.stop();
78
+ }
79
+ display(output.aiMessage);
80
+ } else {
81
+ const stream = await agent.stream({ messages }, { streamMode: 'messages' });
82
+
83
+ for await (const [chunk, _metadata] of stream) {
84
+ if (isAIMessage(chunk)) {
85
+ stdout.write(chunk.content as string, 'utf-8');
86
+ output.aiMessage += chunk.content;
87
+ let toolCalls = chunk.tool_calls;
88
+ if (toolCalls && toolCalls.length > 0) {
89
+ const suffix = toolCalls.length > 1 ? 's' : '';
90
+ const toolCallsString = toolCalls.map((t) => t?.name).join(', ');
91
+ displayInfo(`Using tool${suffix} ${toolCallsString}`);
92
+ }
55
93
  }
56
94
  }
57
95
  }
@@ -66,7 +104,6 @@ export async function invoke(
66
104
  throw error;
67
105
  } finally {
68
106
  if (client) {
69
- console.log('closing');
70
107
  await client.close();
71
108
  }
72
109
  }
@@ -76,13 +113,53 @@ export function setVerbose(debug: boolean) {
76
113
  llmGlobalSettings.verbose = debug;
77
114
  }
78
115
 
116
+ function filterTools(
117
+ tools: StructuredToolInterface[],
118
+ filesystemConfig: string[] | 'all' | 'none'
119
+ ) {
120
+ if (filesystemConfig === 'all' || !Array.isArray(filesystemConfig)) {
121
+ return tools;
122
+ }
123
+
124
+ // Create set of allowed tool names with mcp__filesystem__ prefix
125
+ const allowedToolNames = new Set(
126
+ filesystemConfig.map((shortName) => `mcp__filesystem__${shortName}`)
127
+ );
128
+
129
+ return tools.filter((tool) => {
130
+ // Allow non-filesystem tools and only allowed filesystem tools
131
+ return !tool.name.startsWith('mcp__filesystem__') || allowedToolNames.has(tool.name);
132
+ });
133
+ }
134
+
79
135
  function getClient(config: SlothConfig) {
80
- if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
136
+ const defaultServers: Record<string, Connection> = {};
137
+
138
+ // Add filesystem server if configured
139
+ if (config.filesystem && config.filesystem !== 'none') {
140
+ const filesystemConfig: Connection = {
141
+ transport: 'stdio' as const,
142
+ command: 'npx',
143
+ args: ['-y', '@modelcontextprotocol/server-filesystem', getCurrentDir()],
144
+ };
145
+
146
+ defaultServers.filesystem = filesystemConfig;
147
+ }
148
+
149
+ // Merge with user's mcpServers
150
+ const mcpServers = { ...defaultServers, ...(config.mcpServers || {}) };
151
+
152
+ // If user provided their own filesystem config, it overrides default
153
+ if (config.mcpServers?.filesystem) {
154
+ mcpServers.filesystem = config.mcpServers.filesystem;
155
+ }
156
+
157
+ if (Object.keys(mcpServers).length > 0) {
81
158
  return new MultiServerMCPClient({
82
159
  throwOnLoadError: true,
83
160
  prefixToolNameWithServerName: true,
84
161
  additionalToolNamePrefix: 'mcp',
85
- mcpServers: config.mcpServers,
162
+ mcpServers,
86
163
  });
87
164
  } else {
88
165
  return null;
@@ -1,4 +1,4 @@
1
- import { slothContext } from '#src/config.js';
1
+ import type { SlothConfig } from '#src/config.js';
2
2
  import { display, displayError, displaySuccess } from '#src/consoleUtils.js';
3
3
  import { getGslothFilePath } from '#src/filePathUtils.js';
4
4
  import { generateStandardFileName, ProgressIndicator } from '#src/utils.js';
@@ -14,21 +14,15 @@ import { invoke } from '#src/llmUtils.js';
14
14
  export async function askQuestion(
15
15
  source: string,
16
16
  preamble: string,
17
- content: string
17
+ content: string,
18
+ config: SlothConfig
18
19
  ): Promise<void> {
19
- const progressIndicator = slothContext.config.streamOutput
20
- ? undefined
21
- : new ProgressIndicator('Thinking.');
22
- const outputContent = await invoke(
23
- slothContext.config.llm,
24
- preamble,
25
- content,
26
- slothContext.config
27
- );
20
+ const progressIndicator = config.streamOutput ? undefined : new ProgressIndicator('Thinking.');
21
+ const outputContent = await invoke(config.llm, preamble, content, config, 'ask');
28
22
  progressIndicator?.stop();
29
23
  const filename = generateStandardFileName(source);
30
24
  const filePath = getGslothFilePath(filename);
31
- if (!slothContext.config.streamOutput) {
25
+ if (!config.streamOutput) {
32
26
  display('\n' + outputContent);
33
27
  }
34
28
  try {
@@ -1,19 +1,23 @@
1
- import { slothContext } from '#src/config.js';
1
+ import type { SlothConfig } from '#src/config.js';
2
2
  import { display, displayDebug, displayError, displaySuccess } from '#src/consoleUtils.js';
3
3
  import { generateStandardFileName, ProgressIndicator } from '#src/utils.js';
4
4
  import { writeFileSync } from 'node:fs';
5
5
  import { invoke } from '#src/llmUtils.js';
6
6
  import { getGslothFilePath } from '#src/filePathUtils.js';
7
7
 
8
- export async function review(source: string, preamble: string, diff: string): Promise<void> {
9
- const progressIndicator = slothContext.config.streamOutput
10
- ? undefined
11
- : new ProgressIndicator('Reviewing.');
12
- const outputContent = await invoke(slothContext.config.llm, preamble, diff, slothContext.config);
8
+ export async function review(
9
+ source: string,
10
+ preamble: string,
11
+ diff: string,
12
+ config: SlothConfig,
13
+ command: 'pr' | 'review' = 'review'
14
+ ): Promise<void> {
15
+ const progressIndicator = config.streamOutput ? undefined : new ProgressIndicator('Reviewing.');
16
+ const outputContent = await invoke(config.llm, preamble, diff, config, command);
13
17
  progressIndicator?.stop();
14
18
  const filename = generateStandardFileName(source);
15
19
  const filePath = getGslothFilePath(filename);
16
- if (!slothContext.config.streamOutput) {
20
+ if (!config.streamOutput) {
17
21
  display('\n' + outputContent);
18
22
  }
19
23
  try {
File without changes
package/src/prompt.ts CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  } from '#src/utils.js';
6
6
  import { displayError } from '#src/consoleUtils.js';
7
7
  import { exit } from '#src/systemUtils.js';
8
- import { GSLOTH_BACKSTORY } from '#src/config.js';
8
+ import { GSLOTH_BACKSTORY, GSLOTH_SYSTEM_PROMPT } from '#src/constants.js';
9
9
  import { getGslothConfigReadPath } from '#src/filePathUtils.js';
10
10
 
11
11
  export function readBackstory(): string {
@@ -28,6 +28,10 @@ export function readReviewInstructions(reviewInstructions: string): string {
28
28
  return readConfigPromptFile(reviewInstructions);
29
29
  }
30
30
 
31
+ export function readSystemPrompt(): string {
32
+ return readFileFromCurrentOrInstallDir(GSLOTH_SYSTEM_PROMPT, true);
33
+ }
34
+
31
35
  function readConfigPromptFile(guidelinesFilename: string): string {
32
36
  try {
33
37
  const filePath = getGslothConfigReadPath(guidelinesFilename);
File without changes
@@ -1,5 +1,5 @@
1
1
  import { displayWarning } from '#src/consoleUtils.js';
2
- import { execAsync } from '#src/utils.js';
2
+ import { execAsync, ProgressIndicator } from '#src/utils.js';
3
3
  import type { ProviderConfig } from './types.js';
4
4
 
5
5
  /**
@@ -19,7 +19,9 @@ export async function get(
19
19
 
20
20
  try {
21
21
  // Use the GitHub CLI to fetch issue details
22
+ const progress = new ProgressIndicator(`Fetching GitHub issue #${issueId}`);
22
23
  const issueContent = await execAsync(`gh issue view ${issueId}`);
24
+ progress.stop();
23
25
 
24
26
  if (!issueContent) {
25
27
  displayWarning(`No content found for GitHub issue #${issueId}`);
@@ -1,5 +1,5 @@
1
1
  import { displayWarning } from '#src/consoleUtils.js';
2
- import { execAsync } from '#src/utils.js';
2
+ import { execAsync, ProgressIndicator } from '#src/utils.js';
3
3
  import type { ProviderConfig } from './types.js';
4
4
 
5
5
  /**
@@ -19,7 +19,9 @@ export async function get(
19
19
 
20
20
  try {
21
21
  // Use the GitHub CLI to fetch PR diff
22
+ const progress = new ProgressIndicator(`Fetching GitHub PR #${prId} diff`);
22
23
  const prDiffContent = await execAsync(`gh pr diff ${prId}`);
24
+ progress.stop();
23
25
 
24
26
  if (!prDiffContent) {
25
27
  displayWarning(`No diff content found for GitHub PR #${prId}`);
File without changes
File without changes
File without changes
File without changes
File without changes
package/src/utils.ts CHANGED
File without changes
package/tsconfig.json CHANGED
File without changes
File without changes
package/vitest.config.ts CHANGED
File without changes