gaunt-sloth-assistant 0.0.8 → 0.1.0

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.
File without changes
File without changes
File without changes
File without changes
package/DEVELOPMENT.md ADDED
@@ -0,0 +1,9 @@
1
+ # Installation
2
+
3
+ ## GitHub (master)
4
+
5
+ ```shell
6
+ git clone https://github.com/andruhon/gaunt-sloth.git
7
+ npm install
8
+ npm install -g ./
9
+ ```
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -1,13 +1,19 @@
1
1
  # Gaunt Sloth Assistant
2
- Simplistic assistant helping to do code reviews from command line based on [Langchain.js](https://github.com/langchain-ai/langchainjs)
2
+ Simplistic AI assistant helping to do **code reviews from command line** based on [Langchain.js](https://github.com/langchain-ai/langchainjs)
3
+
4
+ ## Review PR (Pull Request)
5
+ To review PR by PR number:
6
+
7
+ First make sure the official [GitHub cli (gh)](https://cli.github.com/) is installed
8
+ and authenticated to have access to your project.
9
+
10
+ Open terminal (command line) in your project directory.
11
+
12
+ Type command: `gsloth pr [desired pull request number]`, for example:
3
13
 
4
- ## Review PR
5
- Review PR by PR number:
6
14
  ```shell
7
15
  gsloth pr 42
8
16
  ```
9
- Official [GitHub cli (gh)](https://cli.github.com/) should be installed
10
- and authenticated to have access to your project.
11
17
 
12
18
  Review providing markdown file with requirements and notes.
13
19
  ```shell
@@ -19,6 +25,44 @@ open Jira XML with "Export XML" in jira and to copy `<description></description>
19
25
  This block contains HTML and AI understands it easily
20
26
  (most importantly it understand nested lists like ul>li).
21
27
 
28
+ ## JIRA Integration
29
+
30
+ When JIRA integration is configured, the JIRA issue text can be included alongside the diff for review.
31
+ The project review preamble can be modified to reject a pull request immediately
32
+ if it appears to implement something different from what was requested in the requirements.
33
+
34
+ The command syntax is generally `gsloth pr <prId> [requirementsId]`,
35
+ for example, the snippet below does review of PR 42 and
36
+ supplies description of JIRA issue with number PP-4242:
37
+
38
+ ```shell
39
+ gsloth pr 42 PP-4242
40
+ ```
41
+
42
+ Example configuration setting up JIRA integration using a legacy API token.
43
+ Make sure you use your actual company domain in `baseUrl` and your personal legacy `token`.
44
+
45
+ A legacy token can be acquired from `Atlassian Account Settings -> Security -> Create and manage API tokens`.
46
+
47
+ ```javascript
48
+ export async function configure(importFunction, global) {
49
+ const vertexAi = await importFunction('@langchain/google-vertexai');
50
+ return {
51
+ llm: new vertexAi.ChatVertexAI({
52
+ model: "gemini-2.5-pro-exp-03-25"
53
+ }),
54
+ requirementsProvider: 'jira-legacy',
55
+ requirementsProviderConfig: {
56
+ 'jira-legacy': {
57
+ username: 'andrei.kondratev@unimarket.com', // Your Jira username/email
58
+ token: 'YOURSECRETTOKEN', // Replace with your real Jira API token
59
+ baseUrl: 'https://yourcompany.atlassian.net/rest/api/2/issue/' // Your Jira instance base URL
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
22
66
  ## Review any Diff
23
67
  ```shell
24
68
  git --no-pager diff origin/master...yourgitcommithash | gsloth review
@@ -48,14 +92,6 @@ Tested with Node 22 LTS.
48
92
  npm install gaunt-sloth-assistant -g
49
93
  ```
50
94
 
51
- ## GitHub (master)
52
-
53
- ```shell
54
- git clone https://github.com/andruhon/gaunt-sloth.git
55
- npm install
56
- npm install -g ./
57
- ```
58
-
59
95
  ## Configuration
60
96
  Go to your project directory and init sloth with vendor of your choice.
61
97
 
@@ -72,7 +108,14 @@ gcloud auth application-default login
72
108
  cd ./your-project
73
109
  gsloth init anthropic
74
110
  ```
75
- Make sure you edit `.gsloth.config.js` and set up your key.
111
+ Make sure you either define `ANTHROPIC_API_KEY` environment variable or edit `.gsloth.config.js` and set up your key.
112
+
113
+ ### Groq
114
+ ```shell
115
+ cd ./your-project
116
+ gsloth init groq
117
+ ```
118
+ Make sure you either define `GROQ_API_KEY` environment variable or edit `.gsloth.config.js` and set up your key.
76
119
 
77
120
  ### Further configuration
78
121
 
package/RELEASE-HOWTO.md CHANGED
@@ -1,5 +1,13 @@
1
1
  Make sure `npm config set git-tag-version true`
2
2
 
3
+ For patch, e.g., from 0.0.8 to 0.0.9
4
+ ```shell
5
+ npm version patch
6
+ git push
7
+ git push --tags
8
+ ```
9
+
10
+ For minor, e.g., from 0.0.8 to 0.1.0
3
11
  ```shell
4
12
  npm version patch
5
13
  git push
package/ROADMAP.md CHANGED
@@ -4,7 +4,7 @@
4
4
  ## 1.0.0
5
5
  Doing the following below and making it work stably should be sufficient to call it version 1.
6
6
 
7
- ### Add tests and gain reasonable coverage
7
+ ### Add tests and gain reasonable coverage
8
8
  ### Configure eslint for code quality checks
9
9
  ### Automate release process
10
10
  ### Add project init command
@@ -12,7 +12,7 @@ Add a command to init certain model in certain project, for example `gsloth init
12
12
  or `gsloth init` and select one of the provided options.
13
13
  -[x] VertexAI
14
14
  -[x] Anthropic
15
- -[ ] Groq
15
+ -[x] Groq
16
16
  -[ ] Local LLm
17
17
  ### Allow global configuration
18
18
  ### Streamline and stabilize configuration
package/UX-RESEARCH.md ADDED
@@ -0,0 +1,78 @@
1
+ # UX Research
2
+
3
+ ## Currently available commands:
4
+ ```
5
+ gsloth --help
6
+ Usage: gsloth [options] [command]
7
+
8
+ Gaunt Sloth Assistant reviewing your PRs
9
+
10
+ Options:
11
+ -V, --version output the version number
12
+ -h, --help display help for command
13
+
14
+ Commands:
15
+ init <type> Initialize the Gaunt Sloth Assistant in your project.
16
+ This will write necessary config files.
17
+ pr [options] <prNumber> Review a PR in current git directory (assuming that
18
+ GH cli is installed and authenticated for current
19
+ project
20
+ review [options] Review provided diff or other content
21
+ ask [options] <message> Ask a question
22
+ help [command] display help for command
23
+ ```
24
+
25
+ pr (we decided to rename it to r as shortcut for review with pull request provider)
26
+ ```
27
+ gsloth pr --help
28
+ Usage: gsloth pr [options] <prNumber>
29
+
30
+ Review a PR in current git directory (assuming that GH cli is installed and
31
+ authenticated for current project
32
+
33
+ Arguments:
34
+ prNumber PR number to review
35
+
36
+ Options:
37
+ -f, --file <file> Input file. Context of this file will be added BEFORE the
38
+ diff
39
+ -h, --help display help for command
40
+ ```
41
+
42
+ ask
43
+ ```
44
+ gsloth ask --help
45
+ Usage: gsloth ask [options] <message>
46
+
47
+ Ask a question
48
+
49
+ Arguments:
50
+ message A message
51
+
52
+ Options:
53
+ -f, --file <file> Input file. Context of this file will be added BEFORE the
54
+ diff
55
+ -h, --help display help for command
56
+ ```
57
+
58
+ review (lacks documentaion, it also accepts pipe with stdin)
59
+ ```
60
+ gsloth review --help
61
+ Usage: gsloth review [options]
62
+
63
+ Review provided diff or other content
64
+
65
+ Options:
66
+ -f, --file <file> Input file. Context of this file will be added BEFORE the
67
+ diff
68
+ -h, --help display help for command
69
+ ```
70
+
71
+ ## Future functions
72
+
73
+ - JIRA. We need to privide a jira number as a criteria, this should somehow go through separate provider and using config from .gsloth.config.
74
+ - External links (simple public links)
75
+ - Editing files
76
+ - Improve experience with specs or criteria (or requirements?)
77
+ - Should we allow to mention that the data is diff or plain code? Maybe we can somehow deduct it or ask smaller model to guess?
78
+ - Slot editing local files
package/index.js CHANGED
@@ -1,16 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import {Argument, Command} from 'commander';
3
- import {dirname} from 'node:path';
4
- import {displayError, displayInfo} from "./src/consoleUtils.js";
5
- import {
6
- availableDefaultConfigs,
7
- createProjectConfig,
8
- slothContext,
9
- USER_PROJECT_REVIEW_PREAMBLE
10
- } from "./src/config.js";
11
- import {fileURLToPath} from "url";
12
- import {getSlothVersion, readFileFromCurrentDir, readStdin} from "./src/utils.js";
13
- import {getPrDiff, readInternalPreamble, readPreamble} from "./src/prompt.js";
2
+ import { Command } from 'commander';
3
+ import { dirname } from 'node:path';
4
+ import { fileURLToPath } from "url";
5
+ import { reviewCommand } from "./src/commands/reviewCommand.js";
6
+ import { initCommand } from "./src/commands/initCommand.js";
7
+ import { askCommand } from "./src/commands/askCommand.js";
8
+ import { slothContext } from "./src/config.js";
9
+ import { getSlothVersion, readStdin } from "./src/utils.js";
14
10
 
15
11
  const program = new Command();
16
12
 
@@ -22,70 +18,12 @@ program
22
18
  .description('Gaunt Sloth Assistant reviewing your PRs')
23
19
  .version(getSlothVersion());
24
20
 
25
- program.command('init')
26
- .description('Initialize the Gaunt Sloth Assistant in your project. This will write necessary config files.')
27
- .addArgument(new Argument('<type>', 'Config type').choices(availableDefaultConfigs))
28
- .action(async (config) => {
29
- await createProjectConfig(config);
30
- });
21
+ initCommand(program, slothContext);
31
22
 
32
- program.command('pr')
33
- .description('Review a PR in current git directory (assuming that GH cli is installed and authenticated for current project')
34
- .argument('<prNumber>', 'PR number to review')
35
- .option('-f, --file <file>', 'Input file. Context of this file will be added BEFORE the diff')
36
- // TODO add option consuming extra message as argument
37
- .action(async (pr, options) => {
38
- if (slothContext.stdin) {
39
- displayError('`gsloth pr` does not expect stdin, use `gsloth review` instead');
40
- return;
41
- }
42
- displayInfo(`Starting review of PR ${pr}`);
43
- const diff = await getPrDiff(pr);
44
- const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
45
- const content = [diff];
46
- if (options.file) {
47
- content.push(readFileFromCurrentDir(options.file));
48
- }
49
- const { review } = await import('./src/codeReview.js');
50
- await review('sloth-PR-review-'+pr, preamble.join("\n"), content.join("\n"));
51
- });
23
+ reviewCommand(program, slothContext)
52
24
 
53
- program.command('review')
54
- .description('Review provided diff or other content')
55
- .option('-f, --file <file>', 'Input file. Context of this file will be added BEFORE the diff')
56
- // TODO add option consuming extra message as argument
57
- .action(async (options) => {
58
- if (!slothContext.stdin && !options.file) {
59
- displayError('gsloth review expects stdin with github diff stdin or a file');
60
- return
61
- }
62
- const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
63
- const content = [];
64
- if (slothContext.stdin) {
65
- content.push(slothContext.stdin);
66
- }
67
- if (options.file) {
68
- content.push(readFileFromCurrentDir(options.file));
69
- }
70
- const { review } = await import('./src/codeReview.js');
71
- await review('sloth-DIFF-review', preamble.join("\n"), content.join("\n"));
72
- });
73
-
74
- program.command('ask')
75
- .description('Ask a question')
76
- .argument('<message>', 'A message')
77
- .option('-f, --file <file>', 'Input file. Context of this file will be added BEFORE the diff')
78
- // TODO add option consuming extra message as argument
79
- .action(async (message, options) => {
80
- const preamble = [readInternalPreamble()];
81
- const content = [message];
82
- if (options.file) {
83
- content.push(readFileFromCurrentDir(options.file));
84
- }
85
- const { askQuestion } = await import('./src/questionAnswering.js');
86
- await askQuestion('sloth-ASK', preamble.join("\n"), content.join("\n"));
87
- });
25
+ askCommand(program, slothContext);
88
26
 
89
27
  // TODO add general interactive chat command
90
28
 
91
- readStdin(program);
29
+ await readStdin(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gaunt-sloth-assistant",
3
- "version": "0.0.8",
3
+ "version": "0.1.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "author": "Andrew Kondratev",
@@ -15,15 +15,16 @@
15
15
  "test": "jasmine"
16
16
  },
17
17
  "bin": {
18
- "gsloth": "index.js"
18
+ "gsloth": "index.js",
19
+ "gth": "index.js"
19
20
  },
20
21
  "dependencies": {
21
- "@eslint/js": "^9.24.0",
22
- "@langchain/anthropic": "^0.3.17",
23
- "@langchain/core": "^0.3.43",
24
- "@langchain/google-vertexai": "^0.2.3",
22
+ "@eslint/js": "^9.25.0",
23
+ "@langchain/anthropic": "^0.3.18",
24
+ "@langchain/core": "^0.3.45",
25
+ "@langchain/google-vertexai": "^0.2.4",
25
26
  "@langchain/groq": "^0.2.2",
26
- "@langchain/langgraph": "^0.2.64",
27
+ "@langchain/langgraph": "^0.2.65",
27
28
  "@types/node": "^22.14.1",
28
29
  "chalk": "^5.4.1",
29
30
  "commander": "^13.1.0",
@@ -31,6 +32,6 @@
31
32
  },
32
33
  "devDependencies": {
33
34
  "jasmine": "^5.6.0",
34
- "jest": "^29.7.0"
35
+ "testdouble": "^3.20.2"
35
36
  }
36
37
  }
@@ -0,0 +1,22 @@
1
+ export async function configure(importFunction, global) {
2
+ const test = await importFunction('@langchain/core/utils/testing');
3
+ return {
4
+ llm: new test.FakeListChatModel({
5
+ responses: ["First LLM message", "Second LLM message"],
6
+ }),
7
+ requirementsProviderConfig: {
8
+ 'jira-legacy': {
9
+ username: 'user.name@company.com', // Your Jira username/email
10
+ token: 'YoUrToKeN', // Replace with your real Jira API token
11
+ baseUrl: 'https://company.atlassian.net/rest/api/2/issue/' // Your Jira instance base URL
12
+ }
13
+ },
14
+ requirementsProvider: "jira-legacy",
15
+ contentProvider: "somethingSpecial",
16
+ contentProviderConfig: {
17
+ somethingSpecial: {
18
+ test: 'example'
19
+ }
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,58 @@
1
+ import {Command} from 'commander';
2
+ import * as td from 'testdouble';
3
+
4
+ describe('askCommand', function (){
5
+
6
+ beforeEach(async function() {
7
+ this.askQuestion = td.function();
8
+ this.prompt = await td.replaceEsm("../src/prompt.js");
9
+ td.when(this.prompt.readInternalPreamble()).thenReturn("INTERNAL PREAMBLE");
10
+ this.questionAnsweringMock = await td.replaceEsm("../src/modules/questionAnsweringModule.js");
11
+ await td.replaceEsm("../src/config.js");
12
+ this.utils = await td.replaceEsm("../src/utils.js");
13
+ td.when(this.utils.readFileFromCurrentDir("test.file")).thenReturn("FILE CONTENT");
14
+ td.when(this.questionAnsweringMock.askQuestion(
15
+ 'sloth-ASK',
16
+ td.matchers.anything(),
17
+ td.matchers.anything())
18
+ ).thenDo(this.askQuestion);
19
+ });
20
+
21
+ it('Should call askQuestion with message', async function() {
22
+ const { askCommand } = await import("../src/commands/askCommand.js");
23
+ const program = new Command();
24
+ await askCommand(program, {});
25
+ await program.parseAsync(['na', 'na', 'ask', 'test message']);
26
+ td.verify(this.askQuestion('sloth-ASK', "INTERNAL PREAMBLE", "test message"));
27
+ });
28
+
29
+ it('Should call askQuestion with message and file content', async function() {
30
+ const { askCommand } = await import("../src/commands/askCommand.js");
31
+ const program = new Command();
32
+ await askCommand(program, {});
33
+ await program.parseAsync(['na', 'na', 'ask', 'test message', '-f', 'test.file']);
34
+ td.verify(this.askQuestion('sloth-ASK', "INTERNAL PREAMBLE", "test message\nFILE CONTENT"));
35
+ });
36
+
37
+ it('Should display help correctly', async function() {
38
+ const { askCommand } = await import("../src/commands/askCommand.js");
39
+ const program = new Command();
40
+ const testOutput = { text: '' };
41
+
42
+ program.configureOutput({
43
+ writeOut: (str) => testOutput.text += str,
44
+ writeErr: (str) => testOutput.text += str
45
+ });
46
+
47
+ await askCommand(program, {});
48
+
49
+ const commandUnderTest = program.commands.find(c => c.name() == 'ask');
50
+ expect(commandUnderTest).toBeDefined();
51
+ commandUnderTest.outputHelp();
52
+
53
+ // Verify help content
54
+ expect(testOutput.text).toContain('Ask a question');
55
+ expect(testOutput.text).toContain('<message>');
56
+ expect(testOutput.text).toContain('-f, --file');
57
+ });
58
+ });
@@ -0,0 +1,54 @@
1
+ import {Command} from 'commander';
2
+ import * as td from 'testdouble';
3
+
4
+ describe('initCommand', function (){
5
+
6
+ beforeEach(async function() {
7
+ // Create a mock for createProjectConfig
8
+ this.createProjectConfig = td.function();
9
+
10
+ // Replace the config module
11
+ await td.replaceEsm("../src/config.js", {
12
+ createProjectConfig: this.createProjectConfig,
13
+ availableDefaultConfigs: ['vertexai', 'anthropic', 'groq'],
14
+ SLOTH_INTERNAL_PREAMBLE: '.gsloth.preamble.internal.md',
15
+ USER_PROJECT_REVIEW_PREAMBLE: '.gsloth.preamble.review.md',
16
+ USER_PROJECT_CONFIG_FILE: '.gsloth.config.js',
17
+ slothContext: {
18
+ installDir: '/mock/install/dir',
19
+ currentDir: '/mock/current/dir',
20
+ config: {},
21
+ session: {}
22
+ }
23
+ });
24
+ });
25
+
26
+ it('Should call createProjectConfig with the provided config type', async function() {
27
+ const { initCommand } = await import("../src/commands/initCommand.js");
28
+ const program = new Command();
29
+ await initCommand(program, {});
30
+ await program.parseAsync(['na', 'na', 'init', 'vertexai']);
31
+ td.verify(this.createProjectConfig('vertexai'));
32
+ });
33
+
34
+ it('Should display available config types in help', async function() {
35
+ const { initCommand } = await import("../src/commands/initCommand.js");
36
+ const program = new Command();
37
+ const testOutput = { text: '' };
38
+
39
+ program.configureOutput({
40
+ writeOut: (str) => testOutput.text += str,
41
+ writeErr: (str) => testOutput.text += str
42
+ });
43
+
44
+ await initCommand(program, {});
45
+
46
+ const commandUnderTest = program.commands.find(c => c.name() == 'init');
47
+ expect(commandUnderTest).toBeDefined();
48
+ commandUnderTest.outputHelp();
49
+
50
+ // Verify available config types are displayed
51
+ expect(testOutput.text).toContain('<type>');
52
+ expect(testOutput.text).toContain('(choices: "vertexai", "anthropic", "groq")');
53
+ });
54
+ });
@@ -0,0 +1,137 @@
1
+ import * as td from 'testdouble';
2
+
3
+ describe('questionAnsweringModule', function (){
4
+
5
+ beforeEach(async function() {
6
+ // Reset testdouble before each test
7
+ td.reset();
8
+
9
+ // Create a mock context
10
+ this.mockLlmInvoke = td.function();
11
+ this.context = {
12
+ config: {
13
+ llm: {
14
+ invoke: this.mockLlmInvoke
15
+ }
16
+ },
17
+ session: {configurable: {thread_id: 'test-thread-id'}}
18
+ };
19
+
20
+ // Create fs mock
21
+ this.fs = {
22
+ writeFileSync: td.function()
23
+ };
24
+
25
+ // Create path mock
26
+ this.path = {
27
+ resolve: td.function()
28
+ };
29
+
30
+ // Create consoleUtils mock
31
+ this.consoleUtils = {
32
+ display: td.function(),
33
+ displaySuccess: td.function(),
34
+ displayError: td.function()
35
+ };
36
+
37
+ // Create utils mock functions
38
+ const extractLastMessageContent = td.function();
39
+ const toFileSafeString = td.function();
40
+ const fileSafeLocalDate = td.function();
41
+ const ProgressIndicator = td.constructor();
42
+ const readFileSyncWithMessages = td.function();
43
+ const spawnCommand = td.function();
44
+
45
+ // Set up utils mock stubs
46
+ td.when(extractLastMessageContent(td.matchers.anything())).thenReturn('LLM Response');
47
+ td.when(toFileSafeString(td.matchers.anything())).thenReturn('sloth-ASK');
48
+ td.when(fileSafeLocalDate()).thenReturn('2025-01-01T00-00-00');
49
+
50
+ // Create the utils mock
51
+ this.utils = {
52
+ extractLastMessageContent,
53
+ toFileSafeString,
54
+ fileSafeLocalDate,
55
+ ProgressIndicator,
56
+ readFileSyncWithMessages,
57
+ spawnCommand
58
+ };
59
+
60
+ // Set up path.resolve mock
61
+ td.when(this.path.resolve(td.matchers.anything(), td.matchers.contains('sloth-ASK'))).thenReturn('test-file-path.md');
62
+
63
+ // Mock ProgressIndicator
64
+ this.progressIndicator = {
65
+ indicate: td.function()
66
+ };
67
+ td.when(new this.utils.ProgressIndicator(td.matchers.anything())).thenReturn(this.progressIndicator);
68
+
69
+ // Replace modules with mocks - do this after setting up all mocks
70
+ await td.replaceEsm("node:fs", this.fs);
71
+ await td.replaceEsm("node:path", this.path);
72
+ await td.replaceEsm("../src/consoleUtils.js", this.consoleUtils);
73
+ await td.replaceEsm("../src/utils.js", this.utils);
74
+
75
+ // Mock slothContext and other config exports
76
+ await td.replaceEsm("../src/config.js", {
77
+ slothContext: this.context,
78
+ SLOTH_INTERNAL_PREAMBLE: '.gsloth.preamble.internal.md',
79
+ USER_PROJECT_REVIEW_PREAMBLE: '.gsloth.preamble.review.md',
80
+ USER_PROJECT_CONFIG_FILE: '.gsloth.config.js',
81
+ initConfig: td.function()
82
+ });
83
+ });
84
+
85
+ it('Should call LLM with correct messages', async function() {
86
+ // Mock the LLM response
87
+ const llmResponse = [{ role: 'assistant', content: 'LLM Response' }];
88
+ td.when(this.mockLlmInvoke(td.matchers.anything())).thenResolve(llmResponse);
89
+
90
+ // Import the module after setting up mocks
91
+ const { askQuestionInner } = await import("../src/modules/questionAnsweringModule.js");
92
+
93
+ // Call the function
94
+ const result = await askQuestionInner(this.context, () => {}, 'Test Preamble', 'Test Content');
95
+
96
+ // Verify the result
97
+ expect(result).toBe('LLM Response');
98
+ });
99
+
100
+ it('Should write output to file', async function() {
101
+ // Mock the LLM response
102
+ const llmResponse = [{ role: 'assistant', content: 'LLM Response' }];
103
+ td.when(this.mockLlmInvoke(td.matchers.anything())).thenResolve(llmResponse);
104
+
105
+ // Import the module after setting up mocks
106
+ const { askQuestion } = await import("../src/modules/questionAnsweringModule.js");
107
+
108
+ // Call the function and wait for it to complete
109
+ await askQuestion('sloth-ASK', 'Test Preamble', 'Test Content');
110
+
111
+ // Verify the file was written with the correct content
112
+ td.verify(this.fs.writeFileSync('test-file-path.md', 'LLM Response'));
113
+
114
+ // Verify success message was displayed
115
+ td.verify(this.consoleUtils.displaySuccess(td.matchers.contains('test-file-path.md')));
116
+ });
117
+
118
+ it('Should handle file write errors', async function() {
119
+ // Mock the LLM response
120
+ const llmResponse = [{ role: 'assistant', content: 'LLM Response' }];
121
+ td.when(this.mockLlmInvoke(td.matchers.anything())).thenResolve(llmResponse);
122
+
123
+ // Mock file write to throw an error
124
+ const error = new Error('File write error');
125
+ td.when(this.fs.writeFileSync('test-file-path.md', 'LLM Response')).thenThrow(error);
126
+
127
+ // Import the module after setting up mocks
128
+ const { askQuestion } = await import("../src/modules/questionAnsweringModule.js");
129
+
130
+ // Call the function and wait for it to complete
131
+ await askQuestion('sloth-ASK', 'Test Preamble', 'Test Content');
132
+
133
+ // Verify error message was displayed
134
+ td.verify(this.consoleUtils.displayError(td.matchers.contains('test-file-path.md')));
135
+ td.verify(this.consoleUtils.displayError('File write error'));
136
+ });
137
+ });