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.
- package/.claude/settings.local.json +15 -0
- package/.gsloth.backstory.md +0 -0
- package/.gsloth.guidelines.md +0 -0
- package/.gsloth.review.md +0 -0
- package/.gsloth.system.md +10 -0
- package/.prettierrc.json +0 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +0 -0
- package/README.md +9 -0
- package/ROADMAP.md +0 -0
- package/assets/gaunt-sloth-logo.png +0 -0
- package/assets/release-notes/v0_4_0.md +0 -0
- package/assets/release-notes/v0_5_0.md +0 -0
- package/assets/release-notes/v0_5_1.md +47 -0
- package/dist/commands/askCommand.d.ts +0 -0
- package/dist/commands/askCommand.js +9 -5
- package/dist/commands/askCommand.js.map +1 -1
- package/dist/commands/commandUtils.d.ts +25 -0
- package/dist/commands/commandUtils.js +48 -0
- package/dist/commands/commandUtils.js.map +1 -0
- package/dist/commands/initCommand.d.ts +0 -0
- package/dist/commands/initCommand.js +0 -0
- package/dist/commands/initCommand.js.map +0 -0
- package/dist/commands/prCommand.d.ts +2 -0
- package/dist/commands/prCommand.js +52 -0
- package/dist/commands/prCommand.js.map +1 -0
- package/dist/commands/reviewCommand.d.ts +1 -2
- package/dist/commands/reviewCommand.js +17 -98
- package/dist/commands/reviewCommand.js.map +1 -1
- package/dist/config.d.ts +18 -36
- package/dist/config.js +104 -84
- package/dist/config.js.map +1 -1
- package/dist/configs/anthropic.d.ts +0 -0
- package/dist/configs/anthropic.js +0 -0
- package/dist/configs/anthropic.js.map +0 -0
- package/dist/configs/fake.d.ts +0 -0
- package/dist/configs/fake.js +0 -0
- package/dist/configs/fake.js.map +0 -0
- package/dist/configs/groq.d.ts +0 -0
- package/dist/configs/groq.js +0 -0
- package/dist/configs/groq.js.map +0 -0
- package/dist/configs/vertexai.d.ts +0 -0
- package/dist/configs/vertexai.js +0 -0
- package/dist/configs/vertexai.js.map +0 -0
- package/dist/consoleUtils.d.ts +0 -0
- package/dist/consoleUtils.js +0 -0
- package/dist/consoleUtils.js.map +0 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -0
- package/dist/filePathUtils.d.ts +0 -0
- package/dist/filePathUtils.js +0 -0
- package/dist/filePathUtils.js.map +0 -0
- package/dist/index.d.ts +0 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/llmUtils.d.ts +1 -1
- package/dist/llmUtils.js +85 -22
- package/dist/llmUtils.js.map +1 -1
- package/dist/modules/questionAnsweringModule.d.ts +2 -1
- package/dist/modules/questionAnsweringModule.js +4 -7
- package/dist/modules/questionAnsweringModule.js.map +1 -1
- package/dist/modules/reviewModule.d.ts +2 -1
- package/dist/modules/reviewModule.js +4 -7
- package/dist/modules/reviewModule.js.map +1 -1
- package/dist/modules/types.d.ts +0 -0
- package/dist/modules/types.js +0 -0
- package/dist/modules/types.js.map +0 -0
- package/dist/prompt.d.ts +1 -0
- package/dist/prompt.js +4 -1
- package/dist/prompt.js.map +1 -1
- package/dist/providers/file.d.ts +0 -0
- package/dist/providers/file.js +0 -0
- package/dist/providers/file.js.map +0 -0
- package/dist/providers/ghIssueProvider.d.ts +0 -0
- package/dist/providers/ghIssueProvider.js +3 -1
- package/dist/providers/ghIssueProvider.js.map +1 -1
- package/dist/providers/ghPrDiffProvider.d.ts +0 -0
- package/dist/providers/ghPrDiffProvider.js +3 -1
- package/dist/providers/ghPrDiffProvider.js.map +1 -1
- package/dist/providers/jiraIssueLegacyProvider.d.ts +0 -0
- package/dist/providers/jiraIssueLegacyProvider.js +0 -0
- package/dist/providers/jiraIssueLegacyProvider.js.map +0 -0
- package/dist/providers/jiraIssueProvider.d.ts +0 -0
- package/dist/providers/jiraIssueProvider.js +0 -0
- package/dist/providers/jiraIssueProvider.js.map +0 -0
- package/dist/providers/text.d.ts +0 -0
- package/dist/providers/text.js +0 -0
- package/dist/providers/text.js.map +0 -0
- package/dist/providers/types.d.ts +0 -0
- package/dist/providers/types.js +0 -0
- package/dist/providers/types.js.map +0 -0
- package/dist/systemUtils.d.ts +0 -0
- package/dist/systemUtils.js +0 -0
- package/dist/systemUtils.js.map +0 -0
- package/dist/utils.d.ts +0 -0
- package/dist/utils.js +0 -0
- package/dist/utils.js.map +0 -0
- package/docs/CONFIGURATION.md +0 -0
- package/docs/DEVELOPMENT.md +0 -0
- package/docs/RELEASE-HOWTO.md +0 -0
- package/eslint.config.js +0 -0
- package/maintenance/doc-maintenance.md +0 -0
- package/package.json +10 -8
- package/src/commands/askCommand.ts +9 -5
- package/src/commands/commandUtils.ts +77 -0
- package/src/commands/initCommand.ts +0 -0
- package/src/commands/prCommand.ts +93 -0
- package/src/commands/reviewCommand.ts +33 -155
- package/src/config.ts +121 -119
- package/src/configs/anthropic.ts +0 -0
- package/src/configs/fake.ts +0 -0
- package/src/configs/groq.ts +0 -0
- package/src/configs/vertexai.ts +0 -0
- package/src/consoleUtils.ts +0 -0
- package/src/constants.ts +7 -0
- package/src/filePathUtils.ts +0 -0
- package/src/index.ts +4 -2
- package/src/llmUtils.ts +100 -23
- package/src/modules/questionAnsweringModule.ts +6 -12
- package/src/modules/reviewModule.ts +11 -7
- package/src/modules/types.ts +0 -0
- package/src/prompt.ts +5 -1
- package/src/providers/file.ts +0 -0
- package/src/providers/ghIssueProvider.ts +3 -1
- package/src/providers/ghPrDiffProvider.ts +3 -1
- package/src/providers/jiraIssueLegacyProvider.ts +0 -0
- package/src/providers/jiraIssueProvider.ts +0 -0
- package/src/providers/text.ts +0 -0
- package/src/providers/types.ts +0 -0
- package/src/systemUtils.ts +0 -0
- package/src/utils.ts +0 -0
- package/tsconfig.json +0 -0
- package/vitest-it.config.ts +0 -0
- 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,
|
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
|
-
|
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
|
-
|
27
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
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
|
162
|
+
mcpServers,
|
86
163
|
});
|
87
164
|
} else {
|
88
165
|
return null;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
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 =
|
20
|
-
|
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 (!
|
25
|
+
if (!config.streamOutput) {
|
32
26
|
display('\n' + outputContent);
|
33
27
|
}
|
34
28
|
try {
|
@@ -1,19 +1,23 @@
|
|
1
|
-
import {
|
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(
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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 (!
|
20
|
+
if (!config.streamOutput) {
|
17
21
|
display('\n' + outputContent);
|
18
22
|
}
|
19
23
|
try {
|
package/src/modules/types.ts
CHANGED
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/
|
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);
|
package/src/providers/file.ts
CHANGED
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
|
package/src/providers/text.ts
CHANGED
File without changes
|
package/src/providers/types.ts
CHANGED
File without changes
|
package/src/systemUtils.ts
CHANGED
File without changes
|
package/src/utils.ts
CHANGED
File without changes
|
package/tsconfig.json
CHANGED
File without changes
|
package/vitest-it.config.ts
CHANGED
File without changes
|
package/vitest.config.ts
CHANGED
File without changes
|