gaunt-sloth-assistant 0.1.5 → 0.2.2
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/.prettierrc.json +9 -0
- package/README.md +177 -158
- package/ROADMAP.md +1 -1
- package/dist/commands/askCommand.d.ts +6 -0
- package/dist/commands/askCommand.js +26 -0
- package/dist/commands/askCommand.js.map +1 -0
- package/dist/commands/initCommand.d.ts +6 -0
- package/dist/commands/initCommand.js +16 -0
- package/dist/commands/initCommand.js.map +1 -0
- package/dist/commands/reviewCommand.d.ts +3 -0
- package/dist/commands/reviewCommand.js +128 -0
- package/dist/commands/reviewCommand.js.map +1 -0
- package/dist/config.d.ts +80 -0
- package/dist/config.js +178 -0
- package/dist/config.js.map +1 -0
- package/dist/configs/anthropic.d.ts +5 -0
- package/{src → dist}/configs/anthropic.js +45 -48
- package/dist/configs/anthropic.js.map +1 -0
- package/dist/configs/fake.d.ts +3 -0
- package/{src → dist}/configs/fake.js +11 -14
- package/dist/configs/fake.js.map +1 -0
- package/dist/configs/groq.d.ts +4 -0
- package/{src → dist}/configs/groq.js +10 -13
- package/dist/configs/groq.js.map +1 -0
- package/dist/configs/types.d.ts +14 -0
- package/dist/configs/types.js +2 -0
- package/dist/configs/types.js.map +1 -0
- package/dist/configs/vertexai.d.ts +4 -0
- package/{src → dist}/configs/vertexai.js +44 -47
- package/dist/configs/vertexai.js.map +1 -0
- package/dist/consoleUtils.d.ts +6 -0
- package/{src → dist}/consoleUtils.js +10 -15
- package/dist/consoleUtils.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/questionAnsweringModule.d.ts +18 -0
- package/{src → dist}/modules/questionAnsweringModule.js +72 -82
- package/dist/modules/questionAnsweringModule.js.map +1 -0
- package/dist/modules/reviewModule.d.ts +4 -0
- package/{src → dist}/modules/reviewModule.js +25 -35
- package/dist/modules/reviewModule.js.map +1 -0
- package/dist/modules/types.d.ts +18 -0
- package/dist/modules/types.js +2 -0
- package/dist/modules/types.js.map +1 -0
- package/dist/prompt.d.ts +7 -0
- package/dist/prompt.js +32 -0
- package/dist/prompt.js.map +1 -0
- package/dist/providers/file.d.ts +8 -0
- package/dist/providers/file.js +20 -0
- package/dist/providers/file.js.map +1 -0
- package/dist/providers/ghPrDiffProvider.d.ts +8 -0
- package/dist/providers/ghPrDiffProvider.js +16 -0
- package/dist/providers/ghPrDiffProvider.js.map +1 -0
- package/dist/providers/jiraIssueLegacyAccessTokenProvider.d.ts +8 -0
- package/dist/providers/jiraIssueLegacyAccessTokenProvider.js +62 -0
- package/dist/providers/jiraIssueLegacyAccessTokenProvider.js.map +1 -0
- package/dist/providers/jiraIssueLegacyProvider.d.ts +8 -0
- package/dist/providers/jiraIssueLegacyProvider.js +74 -0
- package/dist/providers/jiraIssueLegacyProvider.js.map +1 -0
- package/dist/providers/jiraIssueProvider.d.ts +11 -0
- package/dist/providers/jiraIssueProvider.js +96 -0
- package/dist/providers/jiraIssueProvider.js.map +1 -0
- package/dist/providers/text.d.ts +8 -0
- package/dist/providers/text.js +10 -0
- package/dist/providers/text.js.map +1 -0
- package/dist/providers/types.d.ts +21 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/systemUtils.d.ts +22 -0
- package/dist/systemUtils.js +36 -0
- package/dist/systemUtils.js.map +1 -0
- package/dist/utils.d.ts +49 -0
- package/{src → dist}/utils.js +73 -60
- package/dist/utils.js.map +1 -0
- package/docs/CONFIGURATION.md +95 -6
- package/docs/RELEASE-HOWTO.md +1 -1
- package/eslint.config.js +99 -21
- package/index.js +10 -27
- package/package.json +26 -15
- package/src/commands/askCommand.ts +34 -0
- package/src/commands/initCommand.ts +19 -0
- package/src/commands/reviewCommand.ts +209 -0
- package/src/config.ts +266 -0
- package/src/configs/anthropic.ts +55 -0
- package/src/configs/fake.ts +15 -0
- package/src/configs/groq.ts +54 -0
- package/src/configs/vertexai.ts +53 -0
- package/src/consoleUtils.ts +33 -0
- package/src/index.ts +21 -0
- package/src/modules/questionAnsweringModule.ts +97 -0
- package/src/modules/reviewModule.ts +81 -0
- package/src/modules/types.ts +23 -0
- package/src/prompt.ts +39 -0
- package/src/providers/file.ts +24 -0
- package/src/providers/ghPrDiffProvider.ts +20 -0
- package/src/providers/jiraIssueLegacyProvider.ts +103 -0
- package/src/providers/jiraIssueProvider.ts +133 -0
- package/src/providers/text.ts +14 -0
- package/src/providers/types.ts +24 -0
- package/src/systemUtils.ts +52 -0
- package/src/utils.ts +225 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +13 -0
- package/.eslint.config.mjs +0 -72
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/ci.yml +0 -33
- package/spec/.gsloth.config.js +0 -22
- package/spec/.gsloth.config.json +0 -25
- package/spec/askCommand.spec.js +0 -92
- package/spec/config.spec.js +0 -421
- package/spec/initCommand.spec.js +0 -55
- package/spec/predefinedConfigs.spec.js +0 -100
- package/spec/questionAnsweringModule.spec.js +0 -137
- package/spec/reviewCommand.spec.js +0 -222
- package/spec/reviewModule.spec.js +0 -28
- package/spec/support/jasmine.mjs +0 -14
- package/src/commands/askCommand.js +0 -27
- package/src/commands/initCommand.js +0 -17
- package/src/commands/reviewCommand.js +0 -154
- package/src/config.js +0 -177
- package/src/prompt.js +0 -34
- package/src/providers/file.js +0 -19
- package/src/providers/ghPrDiffProvider.js +0 -11
- package/src/providers/jiraIssueLegacyAccessTokenProvider.js +0 -84
- package/src/providers/text.js +0 -6
- package/src/systemUtils.js +0 -32
- /package/{.gsloth.preamble.internal.md → .gsloth.backstory.md} +0 -0
@@ -1,137 +0,0 @@
|
|
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.fsMock = {
|
22
|
-
writeFileSync: td.function()
|
23
|
-
};
|
24
|
-
|
25
|
-
// Create path mock
|
26
|
-
this.path = {
|
27
|
-
resolve: td.function(),
|
28
|
-
dirname: td.function()
|
29
|
-
};
|
30
|
-
|
31
|
-
// Create consoleUtils mock
|
32
|
-
this.consoleUtilsMock = {
|
33
|
-
display: td.function(),
|
34
|
-
displaySuccess: td.function(),
|
35
|
-
displayError: td.function()
|
36
|
-
};
|
37
|
-
|
38
|
-
// Create utils mock functions
|
39
|
-
const extractLastMessageContent = td.function();
|
40
|
-
const toFileSafeString = td.function();
|
41
|
-
const fileSafeLocalDate = td.function();
|
42
|
-
const ProgressIndicator = td.constructor();
|
43
|
-
const readFileSyncWithMessages = td.function();
|
44
|
-
const spawnCommand = td.function();
|
45
|
-
|
46
|
-
// Set up utils mock stubs
|
47
|
-
td.when(extractLastMessageContent(td.matchers.anything())).thenReturn('LLM Response');
|
48
|
-
td.when(toFileSafeString(td.matchers.anything())).thenReturn('sloth-ASK');
|
49
|
-
td.when(fileSafeLocalDate()).thenReturn('2025-01-01T00-00-00');
|
50
|
-
|
51
|
-
// Create the utils mock
|
52
|
-
this.utilsMock = {
|
53
|
-
extractLastMessageContent,
|
54
|
-
toFileSafeString,
|
55
|
-
fileSafeLocalDate,
|
56
|
-
ProgressIndicator,
|
57
|
-
readFileSyncWithMessages,
|
58
|
-
spawnCommand
|
59
|
-
};
|
60
|
-
|
61
|
-
// Set up path.resolve mock
|
62
|
-
td.when(this.path.resolve(td.matchers.anything(), td.matchers.contains('sloth-ASK'))).thenReturn('test-file-path.md');
|
63
|
-
|
64
|
-
// Mock ProgressIndicator
|
65
|
-
this.progressIndicator = {
|
66
|
-
indicate: td.function()
|
67
|
-
};
|
68
|
-
td.when(new this.utilsMock.ProgressIndicator(td.matchers.anything())).thenReturn(this.progressIndicator);
|
69
|
-
|
70
|
-
// Replace modules with mocks - do this after setting up all mocks
|
71
|
-
await td.replaceEsm("node:fs", this.fsMock);
|
72
|
-
await td.replaceEsm("node:path", this.path);
|
73
|
-
await td.replaceEsm("../src/consoleUtils.js", this.consoleUtilsMock);
|
74
|
-
await td.replaceEsm("../src/utils.js", this.utilsMock);
|
75
|
-
|
76
|
-
// Mock slothContext and other config exports
|
77
|
-
await td.replaceEsm("../src/config.js", {
|
78
|
-
slothContext: this.context,
|
79
|
-
SLOTH_INTERNAL_PREAMBLE: '.gsloth.preamble.internal.md',
|
80
|
-
USER_PROJECT_REVIEW_PREAMBLE: '.gsloth.preamble.review.md',
|
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.fsMock.writeFileSync('test-file-path.md', 'LLM Response'));
|
113
|
-
|
114
|
-
// Verify success message was displayed
|
115
|
-
td.verify(this.consoleUtilsMock.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.fsMock.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.consoleUtilsMock.displayError(td.matchers.contains('test-file-path.md')));
|
135
|
-
td.verify(this.consoleUtilsMock.displayError('File write error'));
|
136
|
-
});
|
137
|
-
});
|
@@ -1,222 +0,0 @@
|
|
1
|
-
import {Command} from 'commander';
|
2
|
-
import * as td from 'testdouble';
|
3
|
-
|
4
|
-
describe('reviewCommand', function (){
|
5
|
-
|
6
|
-
beforeEach(async function() {
|
7
|
-
td.reset();
|
8
|
-
this.review = td.function();
|
9
|
-
this.prompt = await td.replaceEsm("../src/prompt.js");
|
10
|
-
td.when(this.prompt.readInternalPreamble()).thenReturn("INTERNAL PREAMBLE");
|
11
|
-
td.when(this.prompt.readPreamble(".gsloth.preamble.review.md")).thenReturn("PROJECT PREAMBLE");
|
12
|
-
this.codeReviewMock = await td.replaceEsm("../src/modules/reviewModule.js");
|
13
|
-
await td.replaceEsm("../src/config.js", {
|
14
|
-
SLOTH_INTERNAL_PREAMBLE: '.gsloth.preamble.internal.md',
|
15
|
-
USER_PROJECT_REVIEW_PREAMBLE: '.gsloth.preamble.review.md',
|
16
|
-
slothContext: {
|
17
|
-
config: {},
|
18
|
-
currentDir: '/mock/current/dir'
|
19
|
-
},
|
20
|
-
initConfig: td.function()
|
21
|
-
});
|
22
|
-
const readFileFromCurrentDir = td.function();
|
23
|
-
const readMultipleFilesFromCurrentDir = td.function();
|
24
|
-
const extractLastMessageContent = td.function();
|
25
|
-
const toFileSafeString = td.function();
|
26
|
-
const fileSafeLocalDate = td.function();
|
27
|
-
this.utilsMock = {
|
28
|
-
readFileFromCurrentDir,
|
29
|
-
readMultipleFilesFromCurrentDir,
|
30
|
-
ProgressIndicator: td.constructor(),
|
31
|
-
extractLastMessageContent,
|
32
|
-
toFileSafeString,
|
33
|
-
fileSafeLocalDate
|
34
|
-
};
|
35
|
-
await td.replaceEsm("../src/utils.js", this.utilsMock);
|
36
|
-
td.when(this.utilsMock.readFileFromCurrentDir("test.file")).thenReturn("FILE TO REVIEW");
|
37
|
-
td.when(this.utilsMock.readMultipleFilesFromCurrentDir(["test.file"])).thenReturn("test.file:\n```\nFILE TO REVIEW\n```");
|
38
|
-
td.when(this.codeReviewMock.review(
|
39
|
-
'sloth-DIFF-review',
|
40
|
-
td.matchers.anything(),
|
41
|
-
td.matchers.anything())
|
42
|
-
).thenDo(this.review);
|
43
|
-
});
|
44
|
-
|
45
|
-
it('Should call review with file contents', async function() {
|
46
|
-
const { reviewCommand } = await import("../src/commands/reviewCommand.js");
|
47
|
-
const program = new Command();
|
48
|
-
await reviewCommand(program, {});
|
49
|
-
await program.parseAsync(['na', 'na', 'review', '-f', 'test.file']);
|
50
|
-
td.verify(this.review(
|
51
|
-
'sloth-DIFF-review',
|
52
|
-
"INTERNAL PREAMBLE\nPROJECT PREAMBLE",
|
53
|
-
"test.file:\n```\nFILE TO REVIEW\n```")
|
54
|
-
);
|
55
|
-
});
|
56
|
-
|
57
|
-
it('Should call review with multiple file contents', async function() {
|
58
|
-
const { reviewCommand } = await import("../src/commands/reviewCommand.js");
|
59
|
-
const program = new Command();
|
60
|
-
await reviewCommand(program, {});
|
61
|
-
td.when(this.utilsMock.readMultipleFilesFromCurrentDir(["test.file", "test2.file"]))
|
62
|
-
.thenReturn("test.file:\n```\nFILE TO REVIEW\n```\n\ntest2.file:\n```\nFILE2 TO REVIEW\n```");
|
63
|
-
await program.parseAsync(['na', 'na', 'review', '-f', 'test.file', 'test2.file']);
|
64
|
-
td.verify(this.review(
|
65
|
-
'sloth-DIFF-review',
|
66
|
-
"INTERNAL PREAMBLE\nPROJECT PREAMBLE",
|
67
|
-
"test.file:\n```\nFILE TO REVIEW\n```\n\ntest2.file:\n```\nFILE2 TO REVIEW\n```")
|
68
|
-
);
|
69
|
-
});
|
70
|
-
|
71
|
-
it('Should display predefined providers in help', async function() {
|
72
|
-
const { reviewCommand } = await import("../src/commands/reviewCommand.js");
|
73
|
-
const program = new Command();
|
74
|
-
const testOutput = { text: '' };
|
75
|
-
|
76
|
-
program.configureOutput({
|
77
|
-
writeOut: (str) => testOutput.text += str,
|
78
|
-
writeErr: (str) => testOutput.text += str
|
79
|
-
});
|
80
|
-
|
81
|
-
await reviewCommand(program, {});
|
82
|
-
|
83
|
-
const commandUnderTest = program.commands.find(c => c.name() === 'review');
|
84
|
-
|
85
|
-
expect(commandUnderTest).toBeDefined();
|
86
|
-
commandUnderTest.outputHelp();
|
87
|
-
|
88
|
-
// Verify content providers are displayed
|
89
|
-
expect(testOutput.text).toContain('--content-provider <contentProvider>');
|
90
|
-
expect(testOutput.text).toContain('(choices: "gh", "text", "file")');
|
91
|
-
|
92
|
-
// Verify requirements providers are displayed
|
93
|
-
expect(testOutput.text).toContain('--requirements-provider <requirementsProvider>');
|
94
|
-
expect(testOutput.text).toContain('(choices: "jira-legacy", "text", "file")');
|
95
|
-
});
|
96
|
-
|
97
|
-
it('Should call review with predefined requirements provider', async function() {
|
98
|
-
const { reviewCommand } = await import("../src/commands/reviewCommand.js");
|
99
|
-
const program = new Command();
|
100
|
-
const context = {
|
101
|
-
config: {
|
102
|
-
requirementsProvider: 'jira-legacy',
|
103
|
-
requirementsProviderConfig: {
|
104
|
-
'jira-legacy': {
|
105
|
-
username: 'test-user',
|
106
|
-
token: 'test-token',
|
107
|
-
baseUrl: 'https://test-jira.atlassian.net/rest/api/2/issue/'
|
108
|
-
}
|
109
|
-
}
|
110
|
-
}
|
111
|
-
};
|
112
|
-
|
113
|
-
// Mock the jira provider
|
114
|
-
const jiraProvider = td.func();
|
115
|
-
td.when(jiraProvider(td.matchers.anything(), 'JIRA-123')).thenResolve('JIRA Requirements');
|
116
|
-
|
117
|
-
// Replace the dynamic import with our mock
|
118
|
-
await td.replaceEsm('../src/providers/jiraIssueLegacyAccessTokenProvider.js', {
|
119
|
-
get: jiraProvider
|
120
|
-
});
|
121
|
-
|
122
|
-
await reviewCommand(program, context);
|
123
|
-
await program.parseAsync(['na', 'na', 'review', 'content-id', '-r', 'JIRA-123']);
|
124
|
-
|
125
|
-
td.verify(this.review('sloth-DIFF-review', "INTERNAL PREAMBLE\nPROJECT PREAMBLE", "JIRA Requirements"));
|
126
|
-
});
|
127
|
-
|
128
|
-
it('Should display meaningful error, when JIRA is enabled, but JIRA token is absent', async function() {
|
129
|
-
const testOutput = { text: '' };
|
130
|
-
|
131
|
-
const { reviewCommand } = await import("../src/commands/reviewCommand.js");
|
132
|
-
const program = new Command();
|
133
|
-
program.configureOutput({
|
134
|
-
writeOut: (str) => testOutput.text += str,
|
135
|
-
writeErr: (str) => testOutput.text += str
|
136
|
-
});
|
137
|
-
|
138
|
-
const context = {
|
139
|
-
config: {
|
140
|
-
requirementsProvider: 'jira-legacy',
|
141
|
-
requirementsProviderConfig: {
|
142
|
-
'jira-legacy': {
|
143
|
-
username: 'test-user',
|
144
|
-
baseUrl: 'https://test-jira.atlassian.net/rest/api/2/issue/'
|
145
|
-
}
|
146
|
-
}
|
147
|
-
}
|
148
|
-
};
|
149
|
-
|
150
|
-
// Mock the jira provider
|
151
|
-
const jiraProvider = td.func();
|
152
|
-
td.when(jiraProvider(td.matchers.anything(), 'JIRA-123')).thenResolve('JIRA Requirements');
|
153
|
-
|
154
|
-
|
155
|
-
await reviewCommand(program, context);
|
156
|
-
try {
|
157
|
-
await program.parseAsync(['na', 'na', 'pr', 'content-id', 'JIRA-123']);
|
158
|
-
} catch (e) {
|
159
|
-
expect(e.message)
|
160
|
-
.toContain(
|
161
|
-
'Missing JIRA Legacy API token. ' +
|
162
|
-
'The legacy token can be defined as JIRA_LEGACY_API_TOKEN environment variable ' +
|
163
|
-
'or as "token" in config.'
|
164
|
-
);
|
165
|
-
}
|
166
|
-
});
|
167
|
-
|
168
|
-
it('Should call review with predefined content provider', async function() {
|
169
|
-
const { reviewCommand } = await import("../src/commands/reviewCommand.js");
|
170
|
-
const program = new Command();
|
171
|
-
const context = {
|
172
|
-
config: {
|
173
|
-
contentProvider: 'gh'
|
174
|
-
}
|
175
|
-
};
|
176
|
-
|
177
|
-
// Mock the gh provider
|
178
|
-
const ghProvider = td.func();
|
179
|
-
td.when(ghProvider(td.matchers.anything(), '123')).thenResolve('PR Diff Content');
|
180
|
-
|
181
|
-
// Replace the dynamic import with our mock
|
182
|
-
await td.replaceEsm('../src/providers/ghPrDiffProvider.js', {
|
183
|
-
get: ghProvider
|
184
|
-
});
|
185
|
-
|
186
|
-
await reviewCommand(program, context);
|
187
|
-
await program.parseAsync(['na', 'na', 'review', '123']);
|
188
|
-
|
189
|
-
td.verify(this.review('sloth-DIFF-review', "INTERNAL PREAMBLE\nPROJECT PREAMBLE", "PR Diff Content"));
|
190
|
-
});
|
191
|
-
|
192
|
-
it('Should call pr command', async function() {
|
193
|
-
// Create a spy for the review function
|
194
|
-
const reviewSpy = td.func();
|
195
|
-
|
196
|
-
// Replace the review function in the codeReviewMock
|
197
|
-
this.codeReviewMock.review = reviewSpy;
|
198
|
-
|
199
|
-
// Mock the modules/reviewModule.js import in the reviewCommand.js file
|
200
|
-
await td.replaceEsm('../src/modules/reviewModule.js', {
|
201
|
-
review: reviewSpy
|
202
|
-
});
|
203
|
-
|
204
|
-
const { reviewCommand } = await import("../src/commands/reviewCommand.js");
|
205
|
-
const program = new Command();
|
206
|
-
const context = {};
|
207
|
-
|
208
|
-
// Mock the gh provider
|
209
|
-
const ghProvider = td.func();
|
210
|
-
td.when(ghProvider(td.matchers.anything(), '123')).thenResolve('PR Diff Content');
|
211
|
-
|
212
|
-
// Replace the dynamic import with our mock
|
213
|
-
await td.replaceEsm('../src/providers/ghPrDiffProvider.js', {
|
214
|
-
get: ghProvider
|
215
|
-
});
|
216
|
-
|
217
|
-
await reviewCommand(program, context);
|
218
|
-
await program.parseAsync(['na', 'na', 'pr', '123']);
|
219
|
-
|
220
|
-
td.verify(reviewSpy('sloth-PR-123-review', "INTERNAL PREAMBLE\nPROJECT PREAMBLE", "PR Diff Content"));
|
221
|
-
});
|
222
|
-
});
|
@@ -1,28 +0,0 @@
|
|
1
|
-
import { reviewInner } from '../src/modules/reviewModule.js';
|
2
|
-
import { slothContext } from '../src/config.js';
|
3
|
-
import { FakeListChatModel } from "@langchain/core/utils/testing";
|
4
|
-
import * as td from "testdouble";
|
5
|
-
|
6
|
-
describe('reviewModule', () => {
|
7
|
-
|
8
|
-
beforeEach(async function() {
|
9
|
-
td.reset();
|
10
|
-
});
|
11
|
-
|
12
|
-
it('should invoke LLM', async () => {
|
13
|
-
// Setup mock for slothContext
|
14
|
-
const testContext = {...slothContext,
|
15
|
-
config: {
|
16
|
-
llm: new FakeListChatModel({
|
17
|
-
responses: ["First LLM message", "Second LLM message"],
|
18
|
-
})
|
19
|
-
}
|
20
|
-
};
|
21
|
-
|
22
|
-
// Test the function
|
23
|
-
const output = await reviewInner(testContext, () => {}, 'test-preamble', 'test-diff');
|
24
|
-
|
25
|
-
expect(output).toBe("First LLM message");
|
26
|
-
});
|
27
|
-
|
28
|
-
});
|
package/spec/support/jasmine.mjs
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
import { readInternalPreamble } from "../prompt.js";
|
2
|
-
import { readMultipleFilesFromCurrentDir } from "../utils.js";
|
3
|
-
import { initConfig } from "../config.js";
|
4
|
-
|
5
|
-
/**
|
6
|
-
* Adds the ask command to the program
|
7
|
-
* @param {Object} program - The commander program
|
8
|
-
* @param {Object} context - The context object
|
9
|
-
*/
|
10
|
-
// eslint-disable-next-line no-unused-vars
|
11
|
-
export function askCommand(program, context) {
|
12
|
-
program.command('ask')
|
13
|
-
.description('Ask a question')
|
14
|
-
.argument('<message>', 'A message')
|
15
|
-
.option('-f, --file [files...]', 'Input files. Content of these files will be added BEFORE the message')
|
16
|
-
// TODO add option consuming extra message as argument
|
17
|
-
.action(async (message, options) => {
|
18
|
-
const preamble = [readInternalPreamble()];
|
19
|
-
const content = [message];
|
20
|
-
if (options.file) {
|
21
|
-
content.push(readMultipleFilesFromCurrentDir(options.file));
|
22
|
-
}
|
23
|
-
await initConfig();
|
24
|
-
const { askQuestion } = await import('../modules/questionAnsweringModule.js');
|
25
|
-
await askQuestion('sloth-ASK', preamble.join("\n"), content.join("\n"));
|
26
|
-
});
|
27
|
-
}
|
@@ -1,17 +0,0 @@
|
|
1
|
-
import { Argument } from 'commander';
|
2
|
-
import { availableDefaultConfigs, createProjectConfig } from "../config.js";
|
3
|
-
|
4
|
-
/**
|
5
|
-
* Adds the init command to the program
|
6
|
-
* @param {Object} program - The commander program
|
7
|
-
* @param {Object} context - The context object
|
8
|
-
*/
|
9
|
-
// eslint-disable-next-line no-unused-vars
|
10
|
-
export function initCommand(program, context) {
|
11
|
-
program.command('init')
|
12
|
-
.description('Initialize the Gaunt Sloth Assistant in your project. This will write necessary config files.')
|
13
|
-
.addArgument(new Argument('<type>', 'Config type').choices(availableDefaultConfigs))
|
14
|
-
.action(async (config) => {
|
15
|
-
await createProjectConfig(config);
|
16
|
-
});
|
17
|
-
}
|
@@ -1,154 +0,0 @@
|
|
1
|
-
import {Option} from 'commander';
|
2
|
-
import {USER_PROJECT_REVIEW_PREAMBLE} from "../config.js";
|
3
|
-
import {readInternalPreamble, readPreamble} from "../prompt.js";
|
4
|
-
import {readMultipleFilesFromCurrentDir} from "../utils.js";
|
5
|
-
import {displayError} from "../consoleUtils.js";
|
6
|
-
|
7
|
-
/**
|
8
|
-
* Requirements providers. Expected to be in `.providers/` dir
|
9
|
-
*/
|
10
|
-
const REQUIREMENTS_PROVIDERS = {
|
11
|
-
'jira-legacy': 'jiraIssueLegacyAccessTokenProvider.js',
|
12
|
-
'text': 'text.js',
|
13
|
-
'file': 'file.js'
|
14
|
-
};
|
15
|
-
|
16
|
-
/**
|
17
|
-
* Content providers. Expected to be in `.providers/` dir
|
18
|
-
*/
|
19
|
-
const CONTENT_PROVIDERS = {
|
20
|
-
'gh': 'ghPrDiffProvider.js',
|
21
|
-
'text': 'text.js',
|
22
|
-
'file': 'file.js'
|
23
|
-
};
|
24
|
-
|
25
|
-
export function reviewCommand(program, context) {
|
26
|
-
|
27
|
-
program.command('review')
|
28
|
-
.description('Review provided diff or other content')
|
29
|
-
.argument('[contentId]', 'Optional content ID argument to retrieve content with content provider')
|
30
|
-
.alias('r')
|
31
|
-
// TODO add provider to get results of git --no-pager diff
|
32
|
-
.option('-f, --file [files...]', 'Input files. Content of these files will be added BEFORE the diff, but after requirements')
|
33
|
-
// TODO figure out what to do with this (we probably want to merge it with requirementsId)?
|
34
|
-
.option('-r, --requirements <requirements>', 'Requirements for this review.')
|
35
|
-
.addOption(
|
36
|
-
new Option('-p, --requirements-provider <requirementsProvider>', 'Requirements provider for this review.')
|
37
|
-
.choices(Object.keys(REQUIREMENTS_PROVIDERS))
|
38
|
-
)
|
39
|
-
.addOption(
|
40
|
-
new Option('--content-provider <contentProvider>', 'Content provider')
|
41
|
-
.choices(Object.keys(CONTENT_PROVIDERS))
|
42
|
-
)
|
43
|
-
.option('-m, --message <message>', 'Extra message to provide just before the content')
|
44
|
-
.action(async (contentId, options) => {
|
45
|
-
const {initConfig} = await import("../config.js");
|
46
|
-
await initConfig();
|
47
|
-
const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
|
48
|
-
const content = [];
|
49
|
-
const requirementsId = options.requirements;
|
50
|
-
const requirementsProvider = options.requirementsProvider
|
51
|
-
?? context.config?.review?.requirementsProvider
|
52
|
-
?? context.config?.requirementsProvider;
|
53
|
-
const contentProvider = options.contentProvider
|
54
|
-
?? context.config?.review?.contentProvider
|
55
|
-
?? context.config?.contentProvider;
|
56
|
-
|
57
|
-
// TODO consider calling these in parallel
|
58
|
-
const requirements = await getRequirementsFromProvider(requirementsProvider, requirementsId);
|
59
|
-
if (requirements) {
|
60
|
-
content.push(requirements);
|
61
|
-
}
|
62
|
-
|
63
|
-
const providedContent = await getContentFromProvider(contentProvider, contentId);
|
64
|
-
if (providedContent) {
|
65
|
-
content.push(providedContent);
|
66
|
-
}
|
67
|
-
|
68
|
-
if (options.file) {
|
69
|
-
content.push(readMultipleFilesFromCurrentDir(options.file));
|
70
|
-
}
|
71
|
-
if (context.stdin) {
|
72
|
-
content.push(context.stdin);
|
73
|
-
}
|
74
|
-
if (options.message) {
|
75
|
-
content.push(options.message);
|
76
|
-
}
|
77
|
-
const {review} = await import('../modules/reviewModule.js');
|
78
|
-
await review('sloth-DIFF-review', preamble.join("\n"), content.join("\n"));
|
79
|
-
});
|
80
|
-
|
81
|
-
program.command('pr')
|
82
|
-
.description('Review provided Pull Request in current directory. ' +
|
83
|
-
'This command is similar to `review`, but default content provider is `gh`. ' +
|
84
|
-
'(assuming that GH cli is installed and authenticated for current project')
|
85
|
-
.argument('<prId>', 'Pull request ID to review.')
|
86
|
-
.argument('[requirementsId]', 'Optional requirements ID argument to retrieve requirements with requirements provider')
|
87
|
-
.addOption(
|
88
|
-
new Option('-p, --requirements-provider <requirementsProvider>', 'Requirements provider for this review.')
|
89
|
-
.choices(Object.keys(REQUIREMENTS_PROVIDERS))
|
90
|
-
)
|
91
|
-
.option('-f, --file [files...]', 'Input files. Content of these files will be added BEFORE the diff, but after requirements')
|
92
|
-
.action(async (prId, requirementsId, options) => {
|
93
|
-
const {initConfig} = await import("../config.js");
|
94
|
-
await initConfig();
|
95
|
-
|
96
|
-
const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
|
97
|
-
const content = [];
|
98
|
-
const requirementsProvider = options.requirementsProvider
|
99
|
-
?? context.config?.pr?.requirementsProvider
|
100
|
-
?? context.config?.requirementsProvider;
|
101
|
-
|
102
|
-
// Handle requirements
|
103
|
-
const requirements = await getRequirementsFromProvider(requirementsProvider, requirementsId);
|
104
|
-
if (requirements) {
|
105
|
-
content.push(requirements);
|
106
|
-
}
|
107
|
-
|
108
|
-
if (options.file) {
|
109
|
-
content.push(readMultipleFilesFromCurrentDir(options.file));
|
110
|
-
}
|
111
|
-
|
112
|
-
// Get PR diff using the 'gh' provider
|
113
|
-
const providerPath = `../providers/${CONTENT_PROVIDERS['gh']}`;
|
114
|
-
const {get} = await import(providerPath);
|
115
|
-
content.push(await get(null, prId));
|
116
|
-
|
117
|
-
const {review} = await import('../modules/reviewModule.js');
|
118
|
-
await review(`sloth-PR-${prId}-review`, preamble.join("\n"), content.join("\n"));
|
119
|
-
});
|
120
|
-
|
121
|
-
async function getRequirementsFromProvider(requirementsProvider, requirementsId) {
|
122
|
-
return getFromProvider(
|
123
|
-
requirementsProvider,
|
124
|
-
requirementsId,
|
125
|
-
(context.config?.requirementsProviderConfig ?? {})[requirementsProvider],
|
126
|
-
REQUIREMENTS_PROVIDERS
|
127
|
-
);
|
128
|
-
}
|
129
|
-
|
130
|
-
async function getContentFromProvider(contentProvider, contentId) {
|
131
|
-
return getFromProvider(
|
132
|
-
contentProvider,
|
133
|
-
contentId,
|
134
|
-
(context.config?.contentProviderConfig ?? {})[contentProvider],
|
135
|
-
CONTENT_PROVIDERS
|
136
|
-
);
|
137
|
-
}
|
138
|
-
|
139
|
-
async function getFromProvider(provider, id, config, legitPredefinedProviders) {
|
140
|
-
if (typeof provider === 'string') {
|
141
|
-
// Use one of the predefined providers
|
142
|
-
if (legitPredefinedProviders[provider]) {
|
143
|
-
const providerPath = `../providers/${legitPredefinedProviders[provider]}`;
|
144
|
-
const {get} = await import(providerPath);
|
145
|
-
return await get(config, id);
|
146
|
-
} else {
|
147
|
-
displayError(`Unknown provider: ${provider}. Continuing without it.`);
|
148
|
-
}
|
149
|
-
} else if (typeof provider === 'function') {
|
150
|
-
return await provider(id);
|
151
|
-
}
|
152
|
-
return '';
|
153
|
-
}
|
154
|
-
}
|