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.
- 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
package/docs/CONFIGURATION.md
CHANGED
@@ -62,7 +62,7 @@ JSON configuration is simpler but less flexible than JavaScript configuration. I
|
|
62
62
|
{
|
63
63
|
"llm": {
|
64
64
|
"type": "vertexai",
|
65
|
-
"model": "gemini-2.5-pro-
|
65
|
+
"model": "gemini-2.5-pro-preview-05-06",
|
66
66
|
"temperature": 0
|
67
67
|
}
|
68
68
|
}
|
@@ -109,7 +109,7 @@ export async function configure(importFunction, global) {
|
|
109
109
|
const vertexAi = await importFunction('@langchain/google-vertexai');
|
110
110
|
return {
|
111
111
|
llm: new vertexAi.ChatVertexAI({
|
112
|
-
model: "gemini-2.5-pro-
|
112
|
+
model: "gemini-2.5-pro-preview-05-06", // Consider checking for latest recommended model versions
|
113
113
|
// API Key from AI Studio should also work
|
114
114
|
temperature: 0,
|
115
115
|
//// Other parameters might be relevant depending on Vertex AI API updates.
|
@@ -146,16 +146,106 @@ See [Langchain documentation](https://js.langchain.com/docs/tutorials/llm_chain/
|
|
146
146
|
|
147
147
|
### JIRA
|
148
148
|
|
149
|
+
Gaunt Sloth supports two methods to integrate with JIRA:
|
150
|
+
|
151
|
+
#### 1. Modern Jira REST API (Scoped Token)
|
152
|
+
|
153
|
+
This method uses the Atlassian REST API v3 with a Personal Access Token (PAT). It requires your Atlassian Cloud ID.
|
154
|
+
|
155
|
+
**Prerequisites:**
|
156
|
+
|
157
|
+
1. **Cloud ID**: You can find your Cloud ID by visiting `https://yourcompany.atlassian.net/_edge/tenant_info` while authenticated.
|
158
|
+
|
159
|
+
2. **Personal Access Token (PAT)**: Create a PAT with the appropriate permissions from `Atlassian Account Settings -> Security -> Create and manage API tokens -> [Create API token with scopes]`.
|
160
|
+
- For issue access, the recommended permission is `read:jira-work` (classic)
|
161
|
+
- Alternatively granular access would require: `read:issue-meta:jira`, `read:issue-security-level:jira`, `read:issue.vote:jira`, `read:issue.changelog:jira`, `read:avatar:jira`, `read:issue:jira`, `read:status:jira`, `read:user:jira`, `read:field-configuration:jira`
|
162
|
+
|
163
|
+
Refer to JIRA API documentation for more details [https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-get](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-get)
|
164
|
+
|
165
|
+
**Environment Variables Support:**
|
166
|
+
|
167
|
+
For better security, you can set the JIRA username, token, and cloud ID using environment variables instead of placing them in the configuration file:
|
168
|
+
|
169
|
+
- `JIRA_USERNAME`: Your JIRA username (e.g., `user@yourcompany.com`).
|
170
|
+
- `JIRA_API_PAT_TOKEN`: Your JIRA Personal Access Token with scopes.
|
171
|
+
- `JIRA_CLOUD_ID`: Your Atlassian Cloud ID.
|
172
|
+
|
173
|
+
If these environment variables are set, they will take precedence over the values in the configuration file.
|
174
|
+
|
175
|
+
JSON:
|
176
|
+
|
177
|
+
```json
|
178
|
+
{
|
179
|
+
"llm": {"type": "vertexai", "model": "gemini-2.5-pro-preview-05-06"},
|
180
|
+
"requirementsProvider": "jira",
|
181
|
+
"requirementsProviderConfig": {
|
182
|
+
"jira": {
|
183
|
+
"username": "username@yourcompany.com",
|
184
|
+
"token": "YOUR_JIRA_PAT_TOKEN",
|
185
|
+
"cloudId": "YOUR_ATLASSIAN_CLOUD_ID"
|
186
|
+
}
|
187
|
+
}
|
188
|
+
}
|
189
|
+
```
|
190
|
+
|
191
|
+
Optionally displayUrl can be defined to have a clickable link in the output:
|
192
|
+
|
193
|
+
```json
|
194
|
+
{
|
195
|
+
"llm": {"type": "vertexai", "model": "gemini-2.5-pro-preview-05-06"},
|
196
|
+
"requirementsProvider": "jira",
|
197
|
+
"requirementsProviderConfig": {
|
198
|
+
"jira": {
|
199
|
+
"displayUrl": "https://yourcompany.atlassian.net/browse/"
|
200
|
+
}
|
201
|
+
}
|
202
|
+
}
|
203
|
+
```
|
204
|
+
|
205
|
+
JavaScript:
|
206
|
+
|
207
|
+
```javascript
|
208
|
+
export async function configure(importFunction, global) {
|
209
|
+
const vertexAi = await importFunction('@langchain/google-vertexai');
|
210
|
+
return {
|
211
|
+
llm: new vertexAi.ChatVertexAI({
|
212
|
+
model: "gemini-2.5-pro-preview-05-06"
|
213
|
+
}),
|
214
|
+
requirementsProvider: 'jira',
|
215
|
+
requirementsProviderConfig: {
|
216
|
+
'jira': {
|
217
|
+
username: 'username@yourcompany.com', // Your Jira username/email
|
218
|
+
token: 'YOUR_JIRA_PAT_TOKEN', // Your Personal Access Token
|
219
|
+
cloudId: 'YOUR_ATLASSIAN_CLOUD_ID' // Your Atlassian Cloud ID
|
220
|
+
}
|
221
|
+
}
|
222
|
+
}
|
223
|
+
}
|
224
|
+
```
|
225
|
+
|
226
|
+
#### 2. Legacy Jira REST API (Unscoped Token)
|
227
|
+
|
228
|
+
This uses the Unscoped API token (Aka Legacy API token) method with REST API v2.
|
229
|
+
|
230
|
+
A legacy token can be acquired from `Atlassian Account Settings -> Security -> Create and manage API tokens -> [Create API token without scopes]`.
|
231
|
+
|
149
232
|
Example configuration setting up JIRA integration using a legacy API token for both `review` and `pr` commands.
|
150
233
|
Make sure you use your actual company domain in `baseUrl` and your personal legacy `token`.
|
151
234
|
|
152
|
-
|
235
|
+
**Environment Variables Support:**
|
236
|
+
|
237
|
+
For better security, you can set the JIRA username and token using environment variables instead of placing them in the configuration file:
|
238
|
+
|
239
|
+
- `JIRA_USERNAME`: Your JIRA username (e.g., `user@yourcompany.com`).
|
240
|
+
- `JIRA_LEGACY_API_TOKEN`: Your JIRA legacy API token.
|
241
|
+
|
242
|
+
If these environment variables are set, they will take precedence over the values in the configuration file.
|
153
243
|
|
154
244
|
JSON:
|
155
245
|
|
156
246
|
```json
|
157
247
|
{
|
158
|
-
"llm": {"type": "vertexai", "model": "gemini-2.5-pro-
|
248
|
+
"llm": {"type": "vertexai", "model": "gemini-2.5-pro-preview-05-06"},
|
159
249
|
"requirementsProvider": "jira-legacy",
|
160
250
|
"requirementsProviderConfig": {
|
161
251
|
"jira-legacy": {
|
@@ -174,7 +264,7 @@ export async function configure(importFunction, global) {
|
|
174
264
|
const vertexAi = await importFunction('@langchain/google-vertexai');
|
175
265
|
return {
|
176
266
|
llm: new vertexAi.ChatVertexAI({
|
177
|
-
model: "gemini-2.5-pro-
|
267
|
+
model: "gemini-2.5-pro-preview-05-06"
|
178
268
|
}),
|
179
269
|
requirementsProvider: 'jira-legacy',
|
180
270
|
requirementsProviderConfig: {
|
@@ -187,4 +277,3 @@ export async function configure(importFunction, global) {
|
|
187
277
|
}
|
188
278
|
}
|
189
279
|
```
|
190
|
-
|
package/docs/RELEASE-HOWTO.md
CHANGED
package/eslint.config.js
CHANGED
@@ -1,38 +1,116 @@
|
|
1
|
-
import
|
2
|
-
import { defineConfig } from
|
3
|
-
import
|
4
|
-
import
|
1
|
+
import jsConfig from '@eslint/js';
|
2
|
+
import { defineConfig } from 'eslint/config';
|
3
|
+
import globals from 'globals';
|
4
|
+
import tseslint from '@typescript-eslint/eslint-plugin';
|
5
|
+
import tsParser from '@typescript-eslint/parser';
|
6
|
+
import prettierConfig from 'eslint-config-prettier';
|
7
|
+
import prettierPlugin from 'eslint-plugin-prettier';
|
8
|
+
import path from 'path';
|
9
|
+
import { fileURLToPath } from 'url';
|
5
10
|
|
6
|
-
|
11
|
+
// Get the directory path of the current module
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
13
|
+
const __dirname = path.dirname(__filename);
|
14
|
+
|
15
|
+
// Global ignores applied to all configurations
|
16
|
+
const globalIgnores = ['**/node_modules/**', '**/dist/**', 'coverage/**', '.git/**'];
|
7
17
|
|
8
18
|
export default defineConfig([
|
19
|
+
// Ignore files config - applies first
|
20
|
+
{
|
21
|
+
ignores: globalIgnores,
|
22
|
+
},
|
23
|
+
// Base configuration for all JavaScript files
|
9
24
|
{
|
10
|
-
files: [
|
25
|
+
files: ['**/*.js'],
|
26
|
+
languageOptions: {
|
27
|
+
ecmaVersion: 2022,
|
28
|
+
sourceType: 'module',
|
29
|
+
globals: {
|
30
|
+
...globals.node,
|
31
|
+
},
|
32
|
+
},
|
11
33
|
plugins: {
|
12
|
-
|
34
|
+
prettier: prettierPlugin,
|
13
35
|
},
|
14
|
-
extends: ["js/recommended"],
|
15
36
|
rules: {
|
16
|
-
|
17
|
-
|
37
|
+
...jsConfig.configs.recommended.rules,
|
38
|
+
...prettierConfig.rules,
|
39
|
+
semi: 'error',
|
40
|
+
'eol-last': 'error',
|
41
|
+
'prettier/prettier': 'error',
|
42
|
+
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
18
43
|
},
|
44
|
+
},
|
45
|
+
// Source TypeScript files
|
46
|
+
{
|
47
|
+
files: ['src/**/*.ts'],
|
19
48
|
languageOptions: {
|
49
|
+
parser: tsParser,
|
50
|
+
parserOptions: {
|
51
|
+
ecmaVersion: 2022,
|
52
|
+
sourceType: 'module',
|
53
|
+
project: path.resolve(__dirname, 'tsconfig.json'),
|
54
|
+
},
|
20
55
|
globals: {
|
21
|
-
|
22
|
-
setInterval,
|
23
|
-
clearInterval,
|
24
|
-
Buffer,
|
25
|
-
fetch
|
56
|
+
...globals.node,
|
26
57
|
},
|
27
58
|
},
|
59
|
+
plugins: {
|
60
|
+
'@typescript-eslint': tseslint,
|
61
|
+
prettier: prettierPlugin,
|
62
|
+
},
|
63
|
+
rules: {
|
64
|
+
...tseslint.configs.recommended.rules,
|
65
|
+
...prettierConfig.rules,
|
66
|
+
semi: 'error',
|
67
|
+
'eol-last': 'error',
|
68
|
+
'prettier/prettier': 'error',
|
69
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
70
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
71
|
+
'@typescript-eslint/no-unused-vars': [
|
72
|
+
'error',
|
73
|
+
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
|
74
|
+
],
|
75
|
+
},
|
28
76
|
},
|
77
|
+
// Test TypeScript files with separate project reference
|
29
78
|
{
|
30
|
-
files: [
|
31
|
-
|
32
|
-
|
79
|
+
files: ['spec/**/*.ts', 'vitest.config.ts'],
|
80
|
+
languageOptions: {
|
81
|
+
parser: tsParser,
|
82
|
+
parserOptions: {
|
83
|
+
ecmaVersion: 2022,
|
84
|
+
sourceType: 'module',
|
85
|
+
// No project needed for tests to avoid parser errors
|
86
|
+
},
|
87
|
+
globals: {
|
88
|
+
...globals.node,
|
89
|
+
describe: 'readonly',
|
90
|
+
it: 'readonly',
|
91
|
+
expect: 'readonly',
|
92
|
+
beforeEach: 'readonly',
|
93
|
+
afterEach: 'readonly',
|
94
|
+
vi: 'readonly',
|
95
|
+
},
|
96
|
+
},
|
97
|
+
plugins: {
|
98
|
+
'@typescript-eslint': tseslint,
|
99
|
+
prettier: prettierPlugin,
|
100
|
+
},
|
33
101
|
rules: {
|
34
|
-
|
35
|
-
|
36
|
-
|
102
|
+
...tseslint.configs.recommended.rules,
|
103
|
+
...prettierConfig.rules,
|
104
|
+
semi: 'error',
|
105
|
+
'eol-last': 'error',
|
106
|
+
'prettier/prettier': 'error',
|
107
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
108
|
+
'@typescript-eslint/no-explicit-any': 'off', // Allow any in test files
|
109
|
+
'@typescript-eslint/no-unused-vars': [
|
110
|
+
'error',
|
111
|
+
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
112
|
+
], // Error for unused vars in tests
|
113
|
+
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], // For .js test files
|
114
|
+
},
|
37
115
|
},
|
38
116
|
]);
|
package/index.js
CHANGED
@@ -1,31 +1,14 @@
|
|
1
1
|
#!/usr/bin/env node
|
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";
|
10
|
-
import {getCurrentDir, getInstallDir, setInstallDir} from "./src/systemUtils.js";
|
11
2
|
|
12
|
-
|
3
|
+
// This is a minimalistic entry point that sets the installDir in systemUtils
|
4
|
+
// and delegates to the compiled TypeScript code in dist/index.js
|
5
|
+
import { setEntryPoint } from './dist/systemUtils.js';
|
13
6
|
|
14
|
-
|
15
|
-
|
16
|
-
slothContext.installDir = getInstallDir();
|
7
|
+
// Set the installation directory in systemUtils
|
8
|
+
setEntryPoint(import.meta.url);
|
17
9
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
initCommand(program, slothContext);
|
24
|
-
|
25
|
-
reviewCommand(program, slothContext)
|
26
|
-
|
27
|
-
askCommand(program, slothContext);
|
28
|
-
|
29
|
-
// TODO add general interactive chat command
|
30
|
-
|
31
|
-
await readStdin(program);
|
10
|
+
// Import and run the compiled TypeScript code
|
11
|
+
import('./dist/index.js').catch((err) => {
|
12
|
+
console.error('Failed to load application:', err);
|
13
|
+
process.exit(1);
|
14
|
+
});
|
package/package.json
CHANGED
@@ -1,41 +1,52 @@
|
|
1
1
|
{
|
2
2
|
"name": "gaunt-sloth-assistant",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.2.2",
|
4
4
|
"description": "",
|
5
5
|
"license": "MIT",
|
6
6
|
"author": "Andrew Kondratev",
|
7
7
|
"type": "module",
|
8
|
-
"main": "index.js",
|
8
|
+
"main": "dist/index.js",
|
9
9
|
"repository": "github:andruhon/gaunt-sloth-assistant",
|
10
10
|
"engines": {
|
11
11
|
"node": ">=22.0.0",
|
12
12
|
"npm": ">=10.9.0"
|
13
13
|
},
|
14
14
|
"scripts": {
|
15
|
-
"
|
16
|
-
"
|
15
|
+
"build": "tsc",
|
16
|
+
"test": "npm run build &&vitest run",
|
17
|
+
"lint": "eslint . --ext .js,.ts",
|
18
|
+
"format": "prettier --write 'src/**/*.{js,ts}'",
|
19
|
+
"prepare": "npm run build"
|
17
20
|
},
|
18
21
|
"bin": {
|
19
22
|
"gsloth": "index.js",
|
20
23
|
"gth": "index.js"
|
21
24
|
},
|
22
25
|
"dependencies": {
|
23
|
-
"@langchain/anthropic": "^0.3.
|
24
|
-
"@langchain/core": "^0.3.
|
25
|
-
"@langchain/google-vertexai": "^0.2.
|
26
|
+
"@langchain/anthropic": "^0.3.20",
|
27
|
+
"@langchain/core": "^0.3.55",
|
28
|
+
"@langchain/google-vertexai": "^0.2.8",
|
26
29
|
"@langchain/groq": "^0.2.2",
|
27
|
-
"@langchain/langgraph": "^0.2.
|
28
|
-
"@types/node": "^22.14.1",
|
30
|
+
"@langchain/langgraph": "^0.2.71",
|
29
31
|
"chalk": "^5.4.1",
|
30
32
|
"commander": "^13.1.0",
|
31
33
|
"uuid": "^11.1.0"
|
32
34
|
},
|
33
35
|
"devDependencies": {
|
34
|
-
"@eslint/js": "^9.
|
35
|
-
"
|
36
|
-
"
|
37
|
-
"
|
38
|
-
"
|
39
|
-
"
|
36
|
+
"@eslint/js": "^9.26.0",
|
37
|
+
"@types/node": "^22.15.17",
|
38
|
+
"@types/uuid": "^10.0.0",
|
39
|
+
"@typescript-eslint/eslint-plugin": "^8.32.0",
|
40
|
+
"@typescript-eslint/parser": "^8.32.0",
|
41
|
+
"eslint": "^9.26.0",
|
42
|
+
"eslint-config-prettier": "^9.1.0",
|
43
|
+
"eslint-plugin-prettier": "^5.1.3",
|
44
|
+
"globals": "^16.1.0",
|
45
|
+
"prettier": "3.5.3",
|
46
|
+
"typescript": "^5.4.2",
|
47
|
+
"vitest": "^3.1.3"
|
48
|
+
},
|
49
|
+
"imports": {
|
50
|
+
"#src/*.js": "./dist/*.js"
|
40
51
|
}
|
41
52
|
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import { Command } from 'commander';
|
2
|
+
import { readInternalPreamble } from '#src/prompt.js';
|
3
|
+
import { readMultipleFilesFromCurrentDir } from '#src/utils.js';
|
4
|
+
import { initConfig } from '#src/config.js';
|
5
|
+
|
6
|
+
interface AskCommandOptions {
|
7
|
+
file?: string[];
|
8
|
+
}
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Adds the ask command to the program
|
12
|
+
* @param program - The commander program
|
13
|
+
*/
|
14
|
+
export function askCommand(program: Command): void {
|
15
|
+
program
|
16
|
+
.command('ask')
|
17
|
+
.description('Ask a question')
|
18
|
+
.argument('<message>', 'A message')
|
19
|
+
.option(
|
20
|
+
'-f, --file [files...]',
|
21
|
+
'Input files. Content of these files will be added BEFORE the message'
|
22
|
+
)
|
23
|
+
// TODO add option consuming extra message as argument
|
24
|
+
.action(async (message: string, options: AskCommandOptions) => {
|
25
|
+
const preamble = [readInternalPreamble()];
|
26
|
+
const content = [message];
|
27
|
+
if (options.file) {
|
28
|
+
content.push(readMultipleFilesFromCurrentDir(options.file));
|
29
|
+
}
|
30
|
+
await initConfig();
|
31
|
+
const { askQuestion } = await import('#src/modules/questionAnsweringModule.js');
|
32
|
+
await askQuestion('sloth-ASK', preamble.join('\n'), content.join('\n'));
|
33
|
+
});
|
34
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import type { ConfigType } from '#src/config.js';
|
2
|
+
import { availableDefaultConfigs, createProjectConfig } from '#src/config.js';
|
3
|
+
import { Argument, Command } from 'commander';
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Adds the init command to the program
|
7
|
+
* @param program - The commander program
|
8
|
+
*/
|
9
|
+
export function initCommand(program: Command): void {
|
10
|
+
program
|
11
|
+
.command('init')
|
12
|
+
.description(
|
13
|
+
'Initialize the Gaunt Sloth Assistant in your project. This will write necessary config files.'
|
14
|
+
)
|
15
|
+
.addArgument(new Argument('<type>', 'Config type').choices(availableDefaultConfigs))
|
16
|
+
.action(async (config: ConfigType) => {
|
17
|
+
await createProjectConfig(config);
|
18
|
+
});
|
19
|
+
}
|
@@ -0,0 +1,209 @@
|
|
1
|
+
import { Command, Option } from 'commander';
|
2
|
+
import { USER_PROJECT_REVIEW_PREAMBLE } from '#src/config.js';
|
3
|
+
import { readInternalPreamble, readPreamble } from '#src/prompt.js';
|
4
|
+
import { readMultipleFilesFromCurrentDir } from '#src/utils.js';
|
5
|
+
import { displayError } from '#src/consoleUtils.js';
|
6
|
+
import type { SlothContext } from '#src/config.js';
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Requirements providers. Expected to be in `.providers/` dir
|
10
|
+
*/
|
11
|
+
const REQUIREMENTS_PROVIDERS = {
|
12
|
+
'jira-legacy': 'jiraIssueLegacyProvider.js',
|
13
|
+
jira: 'jiraIssueProvider.js',
|
14
|
+
text: 'text.js',
|
15
|
+
file: 'file.js',
|
16
|
+
} as const;
|
17
|
+
|
18
|
+
type RequirementsProviderType = keyof typeof REQUIREMENTS_PROVIDERS;
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Content providers. Expected to be in `.providers/` dir
|
22
|
+
*/
|
23
|
+
const CONTENT_PROVIDERS = {
|
24
|
+
gh: 'ghPrDiffProvider.js',
|
25
|
+
text: 'text.js',
|
26
|
+
file: 'file.js',
|
27
|
+
} as const;
|
28
|
+
|
29
|
+
type ContentProviderType = keyof typeof CONTENT_PROVIDERS;
|
30
|
+
|
31
|
+
interface ReviewCommandOptions {
|
32
|
+
file?: string[];
|
33
|
+
requirements?: string;
|
34
|
+
requirementsProvider?: RequirementsProviderType;
|
35
|
+
contentProvider?: ContentProviderType;
|
36
|
+
message?: string;
|
37
|
+
}
|
38
|
+
|
39
|
+
interface PrCommandOptions {
|
40
|
+
file?: string[];
|
41
|
+
requirementsProvider?: RequirementsProviderType;
|
42
|
+
}
|
43
|
+
|
44
|
+
export function reviewCommand(program: Command, context: SlothContext): void {
|
45
|
+
program
|
46
|
+
.command('review')
|
47
|
+
.description('Review provided diff or other content')
|
48
|
+
.argument(
|
49
|
+
'[contentId]',
|
50
|
+
'Optional content ID argument to retrieve content with content provider'
|
51
|
+
)
|
52
|
+
.alias('r')
|
53
|
+
// TODO add provider to get results of git --no-pager diff
|
54
|
+
.option(
|
55
|
+
'-f, --file [files...]',
|
56
|
+
'Input files. Content of these files will be added BEFORE the diff, but after requirements'
|
57
|
+
)
|
58
|
+
// TODO figure out what to do with this (we probably want to merge it with requirementsId)?
|
59
|
+
.option('-r, --requirements <requirements>', 'Requirements for this review.')
|
60
|
+
.addOption(
|
61
|
+
new Option(
|
62
|
+
'-p, --requirements-provider <requirementsProvider>',
|
63
|
+
'Requirements provider for this review.'
|
64
|
+
).choices(Object.keys(REQUIREMENTS_PROVIDERS))
|
65
|
+
)
|
66
|
+
.addOption(
|
67
|
+
new Option('--content-provider <contentProvider>', 'Content provider').choices(
|
68
|
+
Object.keys(CONTENT_PROVIDERS)
|
69
|
+
)
|
70
|
+
)
|
71
|
+
.option('-m, --message <message>', 'Extra message to provide just before the content')
|
72
|
+
.action(async (contentId: string | undefined, options: ReviewCommandOptions) => {
|
73
|
+
const { initConfig } = await import('#src/config.js');
|
74
|
+
await initConfig();
|
75
|
+
const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
|
76
|
+
const content: string[] = [];
|
77
|
+
const requirementsId = options.requirements;
|
78
|
+
const requirementsProvider =
|
79
|
+
options.requirementsProvider ??
|
80
|
+
(context.config?.review?.requirementsProvider as RequirementsProviderType | undefined) ??
|
81
|
+
(context.config?.requirementsProvider as RequirementsProviderType | undefined);
|
82
|
+
const contentProvider =
|
83
|
+
options.contentProvider ??
|
84
|
+
(context.config?.review?.contentProvider as ContentProviderType | undefined) ??
|
85
|
+
(context.config?.contentProvider as ContentProviderType | undefined);
|
86
|
+
|
87
|
+
// TODO consider calling these in parallel
|
88
|
+
const requirements = await getRequirementsFromProvider(requirementsProvider, requirementsId);
|
89
|
+
if (requirements) {
|
90
|
+
content.push(requirements);
|
91
|
+
}
|
92
|
+
|
93
|
+
const providedContent = await getContentFromProvider(contentProvider, contentId);
|
94
|
+
if (providedContent) {
|
95
|
+
content.push(providedContent);
|
96
|
+
}
|
97
|
+
|
98
|
+
if (options.file) {
|
99
|
+
content.push(readMultipleFilesFromCurrentDir(options.file));
|
100
|
+
}
|
101
|
+
if (context.stdin) {
|
102
|
+
content.push(context.stdin);
|
103
|
+
}
|
104
|
+
if (options.message) {
|
105
|
+
content.push(options.message);
|
106
|
+
}
|
107
|
+
const { review } = await import('#src/modules/reviewModule.js');
|
108
|
+
await review('sloth-DIFF-review', preamble.join('\n'), content.join('\n'));
|
109
|
+
});
|
110
|
+
|
111
|
+
program
|
112
|
+
.command('pr')
|
113
|
+
.description(
|
114
|
+
'Review provided Pull Request in current directory. ' +
|
115
|
+
'This command is similar to `review`, but default content provider is `gh`. ' +
|
116
|
+
'(assuming that GH cli is installed and authenticated for current project'
|
117
|
+
)
|
118
|
+
.argument('<prId>', 'Pull request ID to review.')
|
119
|
+
.argument(
|
120
|
+
'[requirementsId]',
|
121
|
+
'Optional requirements ID argument to retrieve requirements with requirements provider'
|
122
|
+
)
|
123
|
+
.addOption(
|
124
|
+
new Option(
|
125
|
+
'-p, --requirements-provider <requirementsProvider>',
|
126
|
+
'Requirements provider for this review.'
|
127
|
+
).choices(Object.keys(REQUIREMENTS_PROVIDERS))
|
128
|
+
)
|
129
|
+
.option(
|
130
|
+
'-f, --file [files...]',
|
131
|
+
'Input files. Content of these files will be added BEFORE the diff, but after requirements'
|
132
|
+
)
|
133
|
+
.action(async (prId: string, requirementsId: string | undefined, options: PrCommandOptions) => {
|
134
|
+
const { initConfig } = await import('#src/config.js');
|
135
|
+
await initConfig();
|
136
|
+
|
137
|
+
const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
|
138
|
+
const content: string[] = [];
|
139
|
+
const requirementsProvider =
|
140
|
+
options.requirementsProvider ??
|
141
|
+
(context.config?.pr?.requirementsProvider as RequirementsProviderType | undefined) ??
|
142
|
+
(context.config?.requirementsProvider as RequirementsProviderType | undefined);
|
143
|
+
|
144
|
+
// Handle requirements
|
145
|
+
const requirements = await getRequirementsFromProvider(requirementsProvider, requirementsId);
|
146
|
+
if (requirements) {
|
147
|
+
content.push(requirements);
|
148
|
+
}
|
149
|
+
|
150
|
+
if (options.file) {
|
151
|
+
content.push(readMultipleFilesFromCurrentDir(options.file));
|
152
|
+
}
|
153
|
+
|
154
|
+
// Get PR diff using the 'gh' provider
|
155
|
+
const providerPath = `#src/providers/${CONTENT_PROVIDERS['gh']}`;
|
156
|
+
const { get } = await import(providerPath);
|
157
|
+
content.push(await get(null, prId));
|
158
|
+
|
159
|
+
const { review } = await import('#src/modules/reviewModule.js');
|
160
|
+
await review(`sloth-PR-${prId}-review`, preamble.join('\n'), content.join('\n'));
|
161
|
+
});
|
162
|
+
|
163
|
+
async function getRequirementsFromProvider(
|
164
|
+
requirementsProvider: RequirementsProviderType | undefined,
|
165
|
+
requirementsId: string | undefined
|
166
|
+
): Promise<string> {
|
167
|
+
return getFromProvider(
|
168
|
+
requirementsProvider,
|
169
|
+
requirementsId,
|
170
|
+
(context.config?.requirementsProviderConfig ?? {})[requirementsProvider as string],
|
171
|
+
REQUIREMENTS_PROVIDERS
|
172
|
+
);
|
173
|
+
}
|
174
|
+
|
175
|
+
async function getContentFromProvider(
|
176
|
+
contentProvider: ContentProviderType | undefined,
|
177
|
+
contentId: string | undefined
|
178
|
+
): Promise<string> {
|
179
|
+
return getFromProvider(
|
180
|
+
contentProvider,
|
181
|
+
contentId,
|
182
|
+
(context.config?.contentProviderConfig ?? {})[contentProvider as string],
|
183
|
+
CONTENT_PROVIDERS
|
184
|
+
);
|
185
|
+
}
|
186
|
+
|
187
|
+
async function getFromProvider(
|
188
|
+
provider: RequirementsProviderType | ContentProviderType | undefined,
|
189
|
+
id: string | undefined,
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
191
|
+
config: any,
|
192
|
+
legitPredefinedProviders: typeof REQUIREMENTS_PROVIDERS | typeof CONTENT_PROVIDERS
|
193
|
+
): Promise<string> {
|
194
|
+
if (typeof provider === 'string') {
|
195
|
+
// Use one of the predefined providers
|
196
|
+
if (legitPredefinedProviders[provider as keyof typeof legitPredefinedProviders]) {
|
197
|
+
const providerPath = `#src/providers/${legitPredefinedProviders[provider as keyof typeof legitPredefinedProviders]}`;
|
198
|
+
const { get } = await import(providerPath);
|
199
|
+
return await get(config, id);
|
200
|
+
} else {
|
201
|
+
displayError(`Unknown provider: ${provider}. Continuing without it.`);
|
202
|
+
}
|
203
|
+
} else if (typeof provider === 'function') {
|
204
|
+
// Type assertion to handle function call
|
205
|
+
return await (provider as (id: string | undefined) => Promise<string>)(id);
|
206
|
+
}
|
207
|
+
return '';
|
208
|
+
}
|
209
|
+
}
|