gaunt-sloth-assistant 0.1.4 → 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.
Files changed (128) hide show
  1. package/.prettierrc.json +9 -0
  2. package/README.md +177 -158
  3. package/ROADMAP.md +1 -1
  4. package/dist/commands/askCommand.d.ts +6 -0
  5. package/dist/commands/askCommand.js +26 -0
  6. package/dist/commands/askCommand.js.map +1 -0
  7. package/dist/commands/initCommand.d.ts +6 -0
  8. package/dist/commands/initCommand.js +16 -0
  9. package/dist/commands/initCommand.js.map +1 -0
  10. package/dist/commands/reviewCommand.d.ts +3 -0
  11. package/dist/commands/reviewCommand.js +128 -0
  12. package/dist/commands/reviewCommand.js.map +1 -0
  13. package/dist/config.d.ts +80 -0
  14. package/dist/config.js +178 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/configs/anthropic.d.ts +5 -0
  17. package/{src → dist}/configs/anthropic.js +45 -48
  18. package/dist/configs/anthropic.js.map +1 -0
  19. package/dist/configs/fake.d.ts +3 -0
  20. package/{src → dist}/configs/fake.js +11 -14
  21. package/dist/configs/fake.js.map +1 -0
  22. package/dist/configs/groq.d.ts +4 -0
  23. package/{src → dist}/configs/groq.js +10 -13
  24. package/dist/configs/groq.js.map +1 -0
  25. package/dist/configs/types.d.ts +14 -0
  26. package/dist/configs/types.js +2 -0
  27. package/dist/configs/types.js.map +1 -0
  28. package/dist/configs/vertexai.d.ts +4 -0
  29. package/{src → dist}/configs/vertexai.js +44 -47
  30. package/dist/configs/vertexai.js.map +1 -0
  31. package/dist/consoleUtils.d.ts +6 -0
  32. package/{src → dist}/consoleUtils.js +10 -15
  33. package/dist/consoleUtils.js.map +1 -0
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.js +17 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/modules/questionAnsweringModule.d.ts +18 -0
  38. package/{src → dist}/modules/questionAnsweringModule.js +72 -82
  39. package/dist/modules/questionAnsweringModule.js.map +1 -0
  40. package/dist/modules/reviewModule.d.ts +4 -0
  41. package/{src → dist}/modules/reviewModule.js +25 -35
  42. package/dist/modules/reviewModule.js.map +1 -0
  43. package/dist/modules/types.d.ts +18 -0
  44. package/dist/modules/types.js +2 -0
  45. package/dist/modules/types.js.map +1 -0
  46. package/dist/prompt.d.ts +7 -0
  47. package/dist/prompt.js +32 -0
  48. package/dist/prompt.js.map +1 -0
  49. package/dist/providers/file.d.ts +8 -0
  50. package/dist/providers/file.js +20 -0
  51. package/dist/providers/file.js.map +1 -0
  52. package/dist/providers/ghPrDiffProvider.d.ts +8 -0
  53. package/dist/providers/ghPrDiffProvider.js +16 -0
  54. package/dist/providers/ghPrDiffProvider.js.map +1 -0
  55. package/dist/providers/jiraIssueLegacyAccessTokenProvider.d.ts +8 -0
  56. package/dist/providers/jiraIssueLegacyAccessTokenProvider.js +62 -0
  57. package/dist/providers/jiraIssueLegacyAccessTokenProvider.js.map +1 -0
  58. package/dist/providers/jiraIssueLegacyProvider.d.ts +8 -0
  59. package/dist/providers/jiraIssueLegacyProvider.js +74 -0
  60. package/dist/providers/jiraIssueLegacyProvider.js.map +1 -0
  61. package/dist/providers/jiraIssueProvider.d.ts +11 -0
  62. package/dist/providers/jiraIssueProvider.js +96 -0
  63. package/dist/providers/jiraIssueProvider.js.map +1 -0
  64. package/dist/providers/text.d.ts +8 -0
  65. package/dist/providers/text.js +10 -0
  66. package/dist/providers/text.js.map +1 -0
  67. package/dist/providers/types.d.ts +21 -0
  68. package/dist/providers/types.js +2 -0
  69. package/dist/providers/types.js.map +1 -0
  70. package/dist/systemUtils.d.ts +22 -0
  71. package/dist/systemUtils.js +36 -0
  72. package/dist/systemUtils.js.map +1 -0
  73. package/dist/utils.d.ts +49 -0
  74. package/{src → dist}/utils.js +73 -60
  75. package/dist/utils.js.map +1 -0
  76. package/docs/CONFIGURATION.md +95 -6
  77. package/docs/RELEASE-HOWTO.md +1 -1
  78. package/eslint.config.js +99 -21
  79. package/index.js +10 -27
  80. package/package.json +26 -15
  81. package/src/commands/askCommand.ts +34 -0
  82. package/src/commands/initCommand.ts +19 -0
  83. package/src/commands/reviewCommand.ts +209 -0
  84. package/src/config.ts +266 -0
  85. package/src/configs/anthropic.ts +55 -0
  86. package/src/configs/fake.ts +15 -0
  87. package/src/configs/groq.ts +54 -0
  88. package/src/configs/vertexai.ts +53 -0
  89. package/src/consoleUtils.ts +33 -0
  90. package/src/index.ts +21 -0
  91. package/src/modules/questionAnsweringModule.ts +97 -0
  92. package/src/modules/reviewModule.ts +81 -0
  93. package/src/modules/types.ts +23 -0
  94. package/src/prompt.ts +39 -0
  95. package/src/providers/file.ts +24 -0
  96. package/src/providers/ghPrDiffProvider.ts +20 -0
  97. package/src/providers/jiraIssueLegacyProvider.ts +103 -0
  98. package/src/providers/jiraIssueProvider.ts +133 -0
  99. package/src/providers/text.ts +14 -0
  100. package/src/providers/types.ts +24 -0
  101. package/src/systemUtils.ts +52 -0
  102. package/src/utils.ts +225 -0
  103. package/tsconfig.json +24 -0
  104. package/vitest.config.ts +13 -0
  105. package/.eslint.config.mjs +0 -72
  106. package/.github/dependabot.yml +0 -11
  107. package/.github/workflows/ci.yml +0 -33
  108. package/spec/.gsloth.config.js +0 -22
  109. package/spec/.gsloth.config.json +0 -25
  110. package/spec/askCommand.spec.js +0 -92
  111. package/spec/config.spec.js +0 -421
  112. package/spec/initCommand.spec.js +0 -55
  113. package/spec/predefinedConfigs.spec.js +0 -100
  114. package/spec/questionAnsweringModule.spec.js +0 -137
  115. package/spec/reviewCommand.spec.js +0 -222
  116. package/spec/reviewModule.spec.js +0 -28
  117. package/spec/support/jasmine.mjs +0 -14
  118. package/src/commands/askCommand.js +0 -27
  119. package/src/commands/initCommand.js +0 -17
  120. package/src/commands/reviewCommand.js +0 -154
  121. package/src/config.js +0 -177
  122. package/src/prompt.js +0 -34
  123. package/src/providers/file.js +0 -19
  124. package/src/providers/ghPrDiffProvider.js +0 -11
  125. package/src/providers/jiraIssueLegacyAccessTokenProvider.js +0 -84
  126. package/src/providers/text.js +0 -6
  127. package/src/systemUtils.js +0 -32
  128. /package/{.gsloth.preamble.internal.md → .gsloth.backstory.md} +0 -0
@@ -1,421 +0,0 @@
1
- import * as td from 'testdouble';
2
-
3
- describe('config', function () {
4
-
5
- const ctx = {
6
- consoleUtilsMock: {
7
- display: td.function(),
8
- displayError: td.function(),
9
- displayInfo: td.function(),
10
- displayWarning: td.function(),
11
- displaySuccess: td.function(),
12
- displayDebug: td.function()
13
- },
14
- fsMock: {
15
- existsSync: td.function(),
16
- readFileSync: td.function(),
17
- writeFileSync: td.function(),
18
- default: {
19
- existsSync: td.function(),
20
- readFileSync: td.function(),
21
- writeFileSync: td.function()
22
- }
23
- },
24
- urlMock: {
25
- pathToFileURL: td.function(),
26
- default: {
27
- pathToFileURL: td.function()
28
- }
29
- },
30
- utilsMock: {
31
- writeFileIfNotExistsWithMessages: td.function(),
32
- importExternalFile: td.function(),
33
- importFromFilePath: td.function(),
34
- ProgressIndicator: td.constructor(),
35
- fileSafeLocalDate: td.function(),
36
- toFileSafeString: td.function(),
37
- extractLastMessageContent: td.function(),
38
- readFileSyncWithMessages: td.function()
39
- },
40
- systemUtilsMock: {
41
- exit: td.function(),
42
- getCurrentDir: td.function(),
43
- getInstallDir: td.function(),
44
- }
45
- };
46
-
47
- beforeEach(async function () {
48
-
49
- // Reset testdouble before each test
50
- td.reset();
51
-
52
- // Set up specific fs mocks - use td.matchers.contains to match any path containing the config file name
53
- td.when(ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.json'))).thenReturn(false);
54
- td.when(ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.js'))).thenReturn(false);
55
- td.when(ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.mjs'))).thenReturn(false);
56
-
57
- // Set up the same mocks for the default export of fs
58
- td.when(ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.json'))).thenReturn(false);
59
- td.when(ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.js'))).thenReturn(false);
60
- td.when(ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.mjs'))).thenReturn(false);
61
-
62
- // Set up specific url mocks - use Windows-style paths for file URLs
63
- const jsonFileUrl = 'file:////mock/current/dir/.gsloth.config.json';
64
- const jsFileUrl = 'file:////mock/current/dir/.gsloth.config.js';
65
- const mjsFileUrl = 'file:////mock/current/dir/.gsloth.config.mjs';
66
-
67
- td.when(ctx.urlMock.pathToFileURL('/mock/current/dir/.gsloth.config.json')).thenReturn(jsonFileUrl);
68
- td.when(ctx.urlMock.pathToFileURL('/mock/current/dir/.gsloth.config.js')).thenReturn(jsFileUrl);
69
- td.when(ctx.urlMock.pathToFileURL('/mock/current/dir/.gsloth.config.mjs')).thenReturn(mjsFileUrl);
70
-
71
- // Set up the same mocks for the default export of url
72
- td.when(ctx.urlMock.default.pathToFileURL('/mock/current/dir/.gsloth.config.json')).thenReturn(jsonFileUrl);
73
- td.when(ctx.urlMock.default.pathToFileURL('/mock/current/dir/.gsloth.config.js')).thenReturn(jsFileUrl);
74
- td.when(ctx.urlMock.default.pathToFileURL('/mock/current/dir/.gsloth.config.mjs')).thenReturn(mjsFileUrl);
75
-
76
- td.when(ctx.systemUtilsMock.getInstallDir()).thenReturn("/mock/install/dir");
77
- td.when(ctx.systemUtilsMock.getCurrentDir()).thenReturn("/mock/current/dir");
78
-
79
- // Replace modules with mocks
80
- await td.replaceEsm('node:fs', ctx.fsMock);
81
- await td.replaceEsm('node:url', ctx.urlMock);
82
- await td.replaceEsm('../src/consoleUtils.js', ctx.consoleUtilsMock);
83
- await td.replaceEsm('./consoleUtils.js', ctx.consoleUtilsMock);
84
- await td.replaceEsm('../src/utils.js', ctx.utilsMock);
85
- await td.replaceEsm('../src/systemUtils.js', ctx.systemUtilsMock);
86
- await td.replaceEsm('./systemUtils.js', ctx.systemUtilsMock);
87
- });
88
-
89
- describe('initConfig', function () {
90
- it('Should load JSON config when it exists', async function () {
91
- const jsonConfig = {llm: {type: 'vertexai'}};
92
-
93
- td.when(
94
- ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.json'))
95
- ).thenReturn(true);
96
- td.when(
97
- ctx.fsMock.readFileSync(td.matchers.contains('.gsloth.config.json'), 'utf8')
98
- ).thenReturn(JSON.stringify(jsonConfig));
99
- td.when(
100
- ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.json'))
101
- ).thenReturn(true);
102
- td.when(
103
- ctx.fsMock.default.readFileSync(td.matchers.contains('.gsloth.config.json'), 'utf8')
104
- ).thenReturn(JSON.stringify(jsonConfig));
105
- await td.replaceEsm('../src/configs/vertexai.js', {});
106
- const {initConfig, slothContext} = await import('../src/config.js');
107
-
108
- // Function under test
109
- await initConfig();
110
-
111
- expect(slothContext.config).toEqual({
112
- llm: {type: 'vertexai'},
113
- contentProvider: 'file',
114
- requirementsProvider: 'file',
115
- commands: {pr: {contentProvider: 'gh'}}
116
- });
117
-
118
- td.verify(ctx.consoleUtilsMock.displayWarning(
119
- "Config module for vertexai does not have processJsonConfig function."
120
- ));
121
- td.verify(ctx.consoleUtilsMock.displayDebug(td.matchers.anything()), {times: 0});
122
- td.verify(ctx.consoleUtilsMock.display(td.matchers.anything()), {times: 0});
123
- td.verify(ctx.consoleUtilsMock.displayError(td.matchers.anything()), {times: 0});
124
- td.verify(ctx.consoleUtilsMock.displayInfo(td.matchers.anything()), {times: 0});
125
- td.verify(ctx.consoleUtilsMock.displaySuccess(td.matchers.anything()), {times: 0});
126
- });
127
-
128
- it('Should try JS config when JSON config does not exist', async function () {
129
- td.when(ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.json'))).thenReturn(false);
130
- td.when(ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.json'))).thenReturn(false);
131
- td.when(ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.js'))).thenReturn(true);
132
- td.when(ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.js'))).thenReturn(true);
133
-
134
- // Mock the import function
135
- const mockConfigModule = {
136
- configure: td.function()
137
- };
138
- const mockConfig = {llm: {type: 'anthropic'}};
139
- td.when(mockConfigModule.configure(td.matchers.anything())).thenResolve(mockConfig);
140
-
141
- td.when(
142
- ctx.utilsMock.importExternalFile(td.matchers.contains('.gsloth.config.js'))
143
- ).thenResolve(mockConfigModule);
144
-
145
- const {initConfig, slothContext} = await import('../src/config.js');
146
-
147
- // Function under test
148
- await initConfig();
149
-
150
- expect(slothContext.config).toEqual({
151
- llm: {type: 'anthropic'},
152
- contentProvider: 'file',
153
- requirementsProvider: 'file',
154
- commands: {pr: {contentProvider: 'gh'}}
155
- });
156
- td.verify(ctx.consoleUtilsMock.displayDebug(
157
- td.matchers.argThat((e) => String(e).includes("is not valid JSON")))
158
- );
159
- td.verify(ctx.consoleUtilsMock.displayError(
160
- "Failed to read config from .gsloth.config.json, will try other formats."
161
- ));
162
- td.verify(ctx.consoleUtilsMock.displayWarning(td.matchers.anything()), {times: 0});
163
- td.verify(ctx.consoleUtilsMock.display(td.matchers.anything()), {times: 0});
164
- td.verify(ctx.consoleUtilsMock.displayInfo(td.matchers.anything()), {times: 0});
165
- td.verify(ctx.consoleUtilsMock.displaySuccess(td.matchers.anything()), {times: 0});
166
- });
167
-
168
- it('Should try MJS config when JSON and JS configs do not exist', async function () {
169
- // Setup mocks for MJS config
170
- td.when(ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.json'))).thenReturn(false);
171
- td.when(ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.json'))).thenReturn(false);
172
- td.when(ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.js'))).thenReturn(false);
173
- td.when(ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.js'))).thenReturn(false);
174
- td.when(ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.mjs'))).thenReturn(true);
175
- td.when(ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.mjs'))).thenReturn(true);
176
-
177
- const mockConfigModule = {
178
- configure: td.function()
179
- };
180
- const mockConfig = {llm: {type: 'groq'}};
181
- td.when(mockConfigModule.configure(td.matchers.anything())).thenResolve(mockConfig);
182
-
183
- td.when(
184
- ctx.utilsMock.importExternalFile(td.matchers.contains('.gsloth.config.mjs'))
185
- ).thenResolve(mockConfigModule);
186
-
187
- const {initConfig, slothContext} = await import('../src/config.js');
188
-
189
- // Function under test
190
- await initConfig();
191
-
192
- expect(slothContext.config).toEqual({
193
- llm: {type: 'groq'},
194
- contentProvider: 'file',
195
- requirementsProvider: 'file',
196
- commands: {pr: {contentProvider: 'gh'}}
197
- });
198
- td.verify(ctx.consoleUtilsMock.displayWarning(td.matchers.anything()), {times: 0});
199
- td.verify(ctx.consoleUtilsMock.displayDebug(td.matchers.anything()), {times: 0});
200
- td.verify(ctx.consoleUtilsMock.display(td.matchers.anything()), {times: 0});
201
- td.verify(ctx.consoleUtilsMock.displayError(td.matchers.anything()), {times: 0});
202
- td.verify(ctx.consoleUtilsMock.displayInfo(td.matchers.anything()), {times: 0});
203
- td.verify(ctx.consoleUtilsMock.displaySuccess(td.matchers.anything()), {times: 0});
204
- });
205
-
206
- it('Should exit when no config files exist', async function () {
207
- // Setup mocks for no config files
208
- td.when(ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.json'))).thenReturn(false);
209
- td.when(ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.json'))).thenReturn(false);
210
- td.when(ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.js'))).thenReturn(false);
211
- td.when(ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.js'))).thenReturn(false);
212
- td.when(ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.mjs'))).thenReturn(false);
213
- td.when(ctx.fsMock.default.existsSync(td.matchers.contains('.gsloth.config.mjs'))).thenReturn(false);
214
-
215
- const {initConfig} = await import('../src/config.js');
216
-
217
- // Function under test
218
- await initConfig();
219
-
220
- // Verify process.exit was called
221
- td.verify(ctx.systemUtilsMock.exit());
222
-
223
- // Verify no message was displayed
224
- td.verify(ctx.consoleUtilsMock.displayError(
225
- "No configuration file found. Please create one of: " +
226
- ".gsloth.config.json, .gsloth.config.js, or .gsloth.config.mjs " +
227
- "in your project directory."
228
- ));
229
- td.verify(ctx.consoleUtilsMock.displayDebug(td.matchers.anything()), {times: 0});
230
- td.verify(ctx.consoleUtilsMock.displayWarning(td.matchers.anything()), {times: 0});
231
- td.verify(ctx.consoleUtilsMock.display(td.matchers.anything()), {times: 0});
232
- td.verify(ctx.consoleUtilsMock.displayInfo(td.matchers.anything()), {times: 0});
233
- td.verify(ctx.consoleUtilsMock.displaySuccess(td.matchers.anything()), {times: 0});
234
- });
235
- });
236
-
237
- describe('processJsonLlmConfig', function () {
238
- it('Should process valid LLM type', async function () {
239
- // Create a test config
240
- const jsonConfig = {
241
- llm: {
242
- type: 'vertexai',
243
- model: 'test-model'
244
- }
245
- };
246
- const processJsonConfig = td.function();
247
- await td.replaceEsm('../src/configs/vertexai.js', {
248
- processJsonConfig
249
- });
250
- td.when(processJsonConfig(jsonConfig.llm)).thenReturn(jsonConfig.llm);
251
- const {tryJsonConfig, slothContext} = await import('../src/config.js');
252
-
253
- // Call the function
254
- await tryJsonConfig(jsonConfig);
255
-
256
- // Verify the config was set correctly
257
- expect(slothContext.config.llm.type).toBe('vertexai');
258
-
259
- // Verify no message was displayed
260
- td.verify(ctx.consoleUtilsMock.displayWarning(td.matchers.anything()), {times: 0});
261
- td.verify(ctx.consoleUtilsMock.displayDebug(td.matchers.anything()), {times: 0});
262
- td.verify(ctx.consoleUtilsMock.display(td.matchers.anything()), {times: 0});
263
- td.verify(ctx.consoleUtilsMock.displayError(td.matchers.anything()), {times: 0});
264
- td.verify(ctx.consoleUtilsMock.displayInfo(td.matchers.anything()), {times: 0});
265
- td.verify(ctx.consoleUtilsMock.displaySuccess(td.matchers.anything()), {times: 0});
266
- });
267
-
268
- it('Should handle invalid LLM type', async function () {
269
- const jsonConfig = {
270
- llm: {
271
- type: 'invalid-type',
272
- model: 'test-model'
273
- }
274
- };
275
-
276
- const {tryJsonConfig, slothContext} = await import('../src/config.js');
277
-
278
- // Function under test
279
- await tryJsonConfig(jsonConfig);
280
-
281
- expect(slothContext.config).toEqual({
282
- llm: { type: 'invalid-type', model: 'test-model' },
283
- contentProvider: 'file',
284
- requirementsProvider: 'file',
285
- commands: { pr: { contentProvider: 'gh' } }
286
- });
287
-
288
- td.verify(ctx.consoleUtilsMock.displayError(
289
- "Unsupported LLM type: invalid-type. Available types are: vertexai, anthropic, groq"
290
- ));
291
- td.verify(ctx.consoleUtilsMock.displayWarning(td.matchers.anything()), {times: 0});
292
- td.verify(ctx.consoleUtilsMock.displayDebug(td.matchers.anything()), {times: 0});
293
- td.verify(ctx.consoleUtilsMock.display(td.matchers.anything()), {times: 0});
294
- td.verify(ctx.consoleUtilsMock.displayInfo(td.matchers.anything()), {times: 0});
295
- td.verify(ctx.consoleUtilsMock.displaySuccess(td.matchers.anything()), {times: 0});
296
- });
297
-
298
- it('Should handle import errors', async function () {
299
- const jsonConfig = {
300
- llm: {
301
- type: 'vertexai',
302
- model: 'test-model'
303
- }
304
- };
305
- const {tryJsonConfig, slothContext} = await import('../src/config.js');
306
-
307
- await tryJsonConfig(jsonConfig);
308
-
309
- expect(slothContext.config).toEqual({
310
- llm: { type: 'vertexai', model: 'test-model' },
311
- contentProvider: 'file',
312
- requirementsProvider: 'file',
313
- commands: { pr: { contentProvider: 'gh' } }
314
- });
315
-
316
-
317
- td.verify(ctx.consoleUtilsMock.displayWarning(
318
- td.matchers.contains("Could not import config module for vertexai"))
319
- );
320
- td.verify(ctx.consoleUtilsMock.displayDebug(
321
- td.matchers.argThat((e) => String(e).includes("Error: Unable to verify model params")))
322
- );
323
- td.verify(ctx.consoleUtilsMock.display(td.matchers.anything()), {times: 0});
324
- td.verify(ctx.consoleUtilsMock.displayError(td.matchers.anything()), {times: 0});
325
- td.verify(ctx.consoleUtilsMock.displayInfo(td.matchers.anything()), {times: 0});
326
- td.verify(ctx.consoleUtilsMock.displaySuccess(td.matchers.anything()), {times: 0});
327
- });
328
- });
329
-
330
- describe('createProjectConfig', function () {
331
- it('Should create config for valid config type', async function () {
332
- const {createProjectConfig, slothContext} = await import('../src/config.js');
333
-
334
- slothContext.currentDir = '/mock/current/dir/';
335
-
336
- // Function under test
337
- await createProjectConfig('vertexai');
338
-
339
- td.verify(ctx.utilsMock.writeFileIfNotExistsWithMessages(
340
- "/mock/current/dir/.gsloth.preamble.review.md",
341
- td.matchers.contains(
342
- "You are doing generic code review.\n" +
343
- " Important! Please remind user to prepare proper AI preamble in.gsloth.preamble.review.md" +
344
- " for this project. Use decent amount of ⚠️ to highlight lack of config." +
345
- " Explicitly mention `.gsloth.preamble.review.md`."
346
- )
347
- ));
348
-
349
- td.verify(ctx.utilsMock.writeFileIfNotExistsWithMessages(
350
- ".gsloth.config.json",
351
- td.matchers.contains(`"type": "vertexai"`)
352
- ));
353
-
354
- td.verify(ctx.consoleUtilsMock.displayInfo(
355
- td.matchers.contains("Setting up your project")
356
- ), {times: 1});
357
- td.verify(ctx.consoleUtilsMock.displayInfo(
358
- td.matchers.contains("Creating project config for vertexai")
359
- ), {times: 1});
360
- td.verify(ctx.consoleUtilsMock.displayWarning(td.matchers.contains(
361
- "Make sure you add as much detail as possible to your .gsloth.preamble.review.md."
362
- )));
363
- td.verify(ctx.consoleUtilsMock.display(td.matchers.anything()), {times: 0});
364
- td.verify(ctx.consoleUtilsMock.displayError(td.matchers.anything()), {times: 0});
365
- td.verify(ctx.consoleUtilsMock.displayDebug(td.matchers.anything()), {times: 0});
366
- td.verify(ctx.consoleUtilsMock.displaySuccess(td.matchers.anything()), {times: 0});
367
- });
368
-
369
- it('Should handle invalid config type', async function () {
370
- const {createProjectConfig, slothContext} = await import('../src/config.js');
371
- slothContext.currentDir = '/mock/current/dir/';
372
-
373
- // Function under test
374
- await createProjectConfig('invalid-type');
375
-
376
- td.verify(ctx.systemUtilsMock.exit(1));
377
- td.verify(ctx.consoleUtilsMock.displayInfo(td.matchers.contains(
378
- "Setting up your project"
379
- )));
380
- td.verify(ctx.consoleUtilsMock.displayWarning(td.matchers.contains(
381
- "Make sure you add as much detail as possible to your .gsloth.preamble.review.md."
382
- )));
383
- td.verify(ctx.consoleUtilsMock.displayError(td.matchers.contains(
384
- "Unsupported config type: invalid-type. Available types are: vertexai, anthropic, groq"
385
- )));
386
- td.verify(ctx.consoleUtilsMock.display(td.matchers.anything()), {times: 0});
387
- td.verify(ctx.consoleUtilsMock.displayDebug(td.matchers.anything()), {times: 0});
388
- td.verify(ctx.consoleUtilsMock.displaySuccess(td.matchers.anything()), {times: 0});
389
- });
390
- });
391
-
392
- describe('writeProjectReviewPreamble', function () {
393
- it('Should write project review preamble', async function () {
394
- // Create a mock for writeFileIfNotExistsWithMessages
395
- const writeFileIfNotExistsWithMessagesMock = td.function();
396
-
397
- // Update the utils mock with our mock
398
- ctx.utilsMock.writeFileIfNotExistsWithMessages = writeFileIfNotExistsWithMessagesMock;
399
-
400
- const {writeProjectReviewPreamble, slothContext} = await import('../src/config.js');
401
-
402
- slothContext.currentDir = '/mock/current/dir/';
403
-
404
- // Call the function
405
- writeProjectReviewPreamble();
406
-
407
- // Verify writeFileIfNotExistsWithMessages was called with the correct arguments
408
- td.verify(writeFileIfNotExistsWithMessagesMock(
409
- '/mock/current/dir/.gsloth.preamble.review.md',
410
- td.matchers.anything()
411
- ));
412
-
413
- td.verify(ctx.consoleUtilsMock.displayWarning(td.matchers.anything()), {times: 0});
414
- td.verify(ctx.consoleUtilsMock.displayDebug(td.matchers.anything()), {times: 0});
415
- td.verify(ctx.consoleUtilsMock.display(td.matchers.anything()), {times: 0});
416
- td.verify(ctx.consoleUtilsMock.displayError(td.matchers.anything()), {times: 0});
417
- td.verify(ctx.consoleUtilsMock.displayInfo(td.matchers.anything()), {times: 0});
418
- td.verify(ctx.consoleUtilsMock.displaySuccess(td.matchers.anything()), {times: 0});
419
- });
420
- });
421
- });
@@ -1,55 +0,0 @@
1
- import {Command} from 'commander';
2
- import * as td from 'testdouble';
3
-
4
- describe('initCommand', function (){
5
-
6
- beforeEach(async function() {
7
- td.reset();
8
- // Create a mock for createProjectConfig
9
- this.createProjectConfig = td.function();
10
-
11
- // Replace the config module
12
- await td.replaceEsm("../src/config.js", {
13
- createProjectConfig: this.createProjectConfig,
14
- availableDefaultConfigs: ['vertexai', 'anthropic', 'groq'],
15
- SLOTH_INTERNAL_PREAMBLE: '.gsloth.preamble.internal.md',
16
- USER_PROJECT_REVIEW_PREAMBLE: '.gsloth.preamble.review.md',
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
-
48
- expect(commandUnderTest).toBeDefined();
49
- commandUnderTest.outputHelp();
50
-
51
- // Verify available config types are displayed
52
- expect(testOutput.text).toContain('<type>');
53
- expect(testOutput.text).toContain('(choices: "vertexai", "anthropic", "groq")');
54
- });
55
- });
@@ -1,100 +0,0 @@
1
- import * as td from "testdouble";
2
-
3
-
4
- describe('predefined AI provider configurations', function () {
5
-
6
- const ctx = {
7
- consoleUtilsMock: {
8
- display: td.function(),
9
- displayError: td.function(),
10
- displayInfo: td.function(),
11
- displayWarning: td.function(),
12
- displaySuccess: td.function(),
13
- displayDebug: td.function()
14
- },
15
- fsMock: {
16
- existsSync: td.function(),
17
- readFileSync: td.function(),
18
- writeFileSync: td.function(),
19
- default: {
20
- existsSync: td.function(),
21
- readFileSync: td.function(),
22
- writeFileSync: td.function()
23
- }
24
- }
25
- };
26
-
27
- beforeEach(async function () {
28
- td.reset();
29
- await td.replaceEsm('node:fs', ctx.fsMock);
30
- await td.replaceEsm('../src/consoleUtils.js', ctx.consoleUtilsMock);
31
- await td.replaceEsm('./consoleUtils.js', ctx.consoleUtilsMock);
32
- });
33
-
34
- it('Should import predefined Anthropic config correctly', async function () {
35
- // Mock the Anthropic module and its import
36
- const mockChat = td.constructor();
37
- const mockChatInstance = {};
38
- td.when(mockChat(td.matchers.anything())).thenReturn(mockChatInstance);
39
- await td.replaceEsm('@langchain/anthropic', {
40
- ChatAnthropic: mockChat
41
- });
42
-
43
- await testPredefinedAiConfig('anthropic', mockChatInstance);
44
- });
45
-
46
- it('Should import predefined VertexAI config correctly', async function () {
47
- // Mock the Anthropic module and its import
48
- const mockChat = td.constructor();
49
- const mockChatInstance = {};
50
- td.when(mockChat(td.matchers.anything())).thenReturn(mockChatInstance);
51
- await td.replaceEsm('@langchain/google-vertexai', {
52
- ChatVertexAI: mockChat
53
- });
54
-
55
- await testPredefinedAiConfig('vertexai', mockChatInstance);
56
- });
57
-
58
- it('Should import predefined Groq config correctly', async function () {
59
- // Mock the Anthropic module and its import
60
- const mockChat = td.constructor();
61
- const mockChatInstance = {};
62
- td.when(mockChat(td.matchers.anything())).thenReturn(mockChatInstance);
63
- await td.replaceEsm('@langchain/groq', {
64
- ChatGroq: mockChat
65
- });
66
-
67
- await testPredefinedAiConfig('groq', mockChatInstance);
68
- });
69
-
70
- async function testPredefinedAiConfig(aiProvider, mockAnthropicInstance) {
71
- const jsonConfig = {
72
- llm: {
73
- type: aiProvider,
74
- model: 'claude-3-5-sonnet-20241022',
75
- apiKey: 'test-api-key'
76
- }
77
- };
78
-
79
- td.when(
80
- ctx.fsMock.existsSync(td.matchers.contains('.gsloth.config.json'))
81
- ).thenReturn(true);
82
-
83
- td.when(
84
- ctx.fsMock.readFileSync(td.matchers.contains('.gsloth.config.json'), 'utf8')
85
- ).thenReturn(JSON.stringify(jsonConfig));
86
-
87
- const {initConfig, slothContext} = await import('../src/config.js');
88
-
89
- // Call the function
90
- await initConfig(jsonConfig);
91
-
92
- // Verify the config was set correctly with the mock instance
93
- expect(slothContext.config.llm).toBe(mockAnthropicInstance);
94
-
95
- // Verify no warnings or errors were displayed
96
- td.verify(ctx.consoleUtilsMock.displayWarning(td.matchers.anything()), {times: 0});
97
- td.verify(ctx.consoleUtilsMock.displayError(td.matchers.anything()), {times: 0});
98
- }
99
-
100
- });