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.
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
package/src/config.js DELETED
@@ -1,177 +0,0 @@
1
- import path from "node:path/posix";
2
- import {v4 as uuidv4} from "uuid";
3
- import {displayDebug, displayError, displayInfo, displayWarning} from "./consoleUtils.js";
4
- import {importExternalFile, writeFileIfNotExistsWithMessages} from "./utils.js";
5
- import {existsSync, readFileSync} from "node:fs";
6
- import {exit, getCurrentDir} from "./systemUtils.js";
7
-
8
- export const USER_PROJECT_CONFIG_JS = '.gsloth.config.js';
9
- export const USER_PROJECT_CONFIG_JSON = '.gsloth.config.json';
10
- export const USER_PROJECT_CONFIG_MJS = '.gsloth.config.mjs';
11
- export const SLOTH_INTERNAL_PREAMBLE = '.gsloth.preamble.internal.md';
12
- export const USER_PROJECT_REVIEW_PREAMBLE = '.gsloth.preamble.review.md';
13
-
14
- export const availableDefaultConfigs = ['vertexai', 'anthropic', 'groq'];
15
-
16
- export const DEFAULT_CONFIG = {
17
- llm: undefined,
18
- contentProvider: "file",
19
- requirementsProvider: "file",
20
- commands: {
21
- pr: {
22
- contentProvider: "gh"
23
- }
24
- }
25
- };
26
-
27
- export const slothContext = {
28
- /**
29
- * Directory where the sloth is installed.
30
- * index.js should set up this.
31
- */
32
- installDir: null,
33
- /**
34
- * Directory where the sloth has been invoked. Usually user's project root.
35
- * index.js should set up this.
36
- */
37
- currentDir: null,
38
- config: DEFAULT_CONFIG,
39
- stdin: '',
40
- session: {configurable: {thread_id: uuidv4()}}
41
- };
42
-
43
- export async function initConfig() {
44
- const currentDir = getCurrentDir();
45
- const jsonConfigPath = path.join(currentDir, USER_PROJECT_CONFIG_JSON);
46
- const jsConfigPath = path.join(currentDir, USER_PROJECT_CONFIG_JS);
47
- const mjsConfigPath = path.join(currentDir, USER_PROJECT_CONFIG_MJS);
48
-
49
- // Try loading JSON config file first
50
- if (existsSync(jsonConfigPath)) {
51
- try {
52
- const jsonConfig = JSON.parse(readFileSync(jsonConfigPath, 'utf8'));
53
-
54
- // If the config has an LLM with a type, create the appropriate LLM instance
55
- if (jsonConfig.llm && jsonConfig.llm.type) {
56
- await tryJsonConfig(jsonConfig);
57
- } else {
58
- slothContext.config = {...slothContext.config, ...jsonConfig};
59
- }
60
- } catch (e) {
61
- displayDebug(e);
62
- displayError(`Failed to read config from ${USER_PROJECT_CONFIG_JSON}, will try other formats.`);
63
- // Continue to try other formats
64
- return tryJsConfig();
65
- }
66
- } else {
67
- // JSON config not found, try JS
68
- return tryJsConfig();
69
- }
70
-
71
- // Helper function to try loading JS config
72
- async function tryJsConfig() {
73
- if (existsSync(jsConfigPath)) {
74
- return importExternalFile(jsConfigPath)
75
- .then((i) => i.configure((module) => import(module)))
76
- .then((config) => {
77
- slothContext.config = {...slothContext.config, ...config};
78
- })
79
- .catch((e) => {
80
- displayDebug(e);
81
- displayError(`Failed to read config from ${USER_PROJECT_CONFIG_JS}, will try other formats.`);
82
- // Continue to try other formats
83
- return tryMjsConfig();
84
- });
85
- } else {
86
- // JS config not found, try MJS
87
- return tryMjsConfig();
88
- }
89
- }
90
-
91
- // Helper function to try loading MJS config
92
- async function tryMjsConfig() {
93
- if (existsSync(mjsConfigPath)) {
94
- return importExternalFile(mjsConfigPath)
95
- .then((i) => i.configure((module) => import(module)))
96
- .then((config) => {
97
- slothContext.config = {...slothContext.config, ...config};
98
- })
99
- .catch((e) => {
100
- displayDebug(e);
101
- displayError(`Failed to read config from ${USER_PROJECT_CONFIG_MJS}.`);
102
- displayError(`No valid configuration found. Please create a valid configuration file.`);
103
- exit();
104
- });
105
- } else {
106
- // No config files found
107
- displayError(
108
- 'No configuration file found. Please create one of: '
109
- + `${USER_PROJECT_CONFIG_JSON}, ${USER_PROJECT_CONFIG_JS}, or ${USER_PROJECT_CONFIG_MJS} `
110
- + 'in your project directory.'
111
- );
112
- exit();
113
- }
114
- }
115
- }
116
-
117
- // Process JSON LLM config by creating the appropriate LLM instance
118
- export async function tryJsonConfig(jsonConfig) {
119
- const llmConfig = jsonConfig.llm;
120
- const llmType = llmConfig.type.toLowerCase();
121
-
122
- // Check if the LLM type is in availableDefaultConfigs
123
- if (!availableDefaultConfigs.includes(llmType)) {
124
- displayError(`Unsupported LLM type: ${llmType}. Available types are: ${availableDefaultConfigs.join(', ')}`);
125
- slothContext.config = {...slothContext.config, ...jsonConfig};
126
- return;
127
- }
128
-
129
- try {
130
- // Import the appropriate config module based on the LLM type
131
- try {
132
- const configModule = await import(`./configs/${llmType}.js`);
133
- if (configModule.processJsonConfig) {
134
- jsonConfig.llm = await configModule.processJsonConfig(llmConfig);
135
- } else {
136
- displayWarning(`Config module for ${llmType} does not have processJsonConfig function.`);
137
- }
138
- } catch (importError) {
139
- displayDebug(importError);
140
- displayWarning(`Could not import config module for ${llmType}.`);
141
- }
142
- } catch (error) {
143
- displayDebug(error);
144
- displayError(`Error creating LLM instance for type ${llmType}.`);
145
- }
146
-
147
- slothContext.config = {...slothContext.config, ...jsonConfig};
148
- }
149
-
150
- export async function createProjectConfig(configType) {
151
- displayInfo(`Setting up your project\n`);
152
- writeProjectReviewPreamble();
153
- displayWarning(`Make sure you add as much detail as possible to your ${USER_PROJECT_REVIEW_PREAMBLE}.\n`);
154
-
155
- // Check if the config type is in availableDefaultConfigs
156
- if (!availableDefaultConfigs.includes(configType)) {
157
- displayError(`Unsupported config type: ${configType}. Available types are: ${availableDefaultConfigs.join(', ')}`);
158
- exit(1);
159
- return;
160
- }
161
-
162
- displayInfo(`Creating project config for ${configType}`);
163
- const vendorConfig = await import(`./configs/${configType}.js`);
164
- vendorConfig.init(USER_PROJECT_CONFIG_JSON, slothContext);
165
- }
166
-
167
- export function writeProjectReviewPreamble() {
168
- let reviewPreamblePath = path.join(slothContext.currentDir, USER_PROJECT_REVIEW_PREAMBLE);
169
- writeFileIfNotExistsWithMessages(
170
- reviewPreamblePath,
171
- 'You are doing generic code review.\n'
172
- + ' Important! Please remind user to prepare proper AI preamble in'
173
- + USER_PROJECT_REVIEW_PREAMBLE
174
- + ' for this project. Use decent amount of ⚠️ to highlight lack of config.'
175
- + ' Explicitly mention `' + USER_PROJECT_REVIEW_PREAMBLE + '`.'
176
- );
177
- }
package/src/prompt.js DELETED
@@ -1,34 +0,0 @@
1
- import {resolve} from "node:path";
2
- import {SLOTH_INTERNAL_PREAMBLE, slothContext} from "./config.js";
3
- import {readFileSyncWithMessages, spawnCommand} from "./utils.js";
4
- import { displayError } from "./consoleUtils.js";
5
- import { exit } from "./systemUtils.js";
6
-
7
- export function readInternalPreamble() {
8
- const filePath = resolve(slothContext.installDir, SLOTH_INTERNAL_PREAMBLE);
9
- return readFileSyncWithMessages(filePath, "Error reading internal preamble file at:");
10
- }
11
-
12
- export function readPreamble(preambleFilename) {
13
- const filePath = resolve(slothContext.currentDir, preambleFilename);
14
- return readFileSyncWithMessages(
15
- filePath,
16
- "Error reading preamble file at:",
17
- "Consider running `gsloth init` to set up your project. Check `gsloth init --help` to see options."
18
- );
19
- }
20
-
21
- /**
22
- * This function expects https://cli.github.com/ to be installed and authenticated.
23
- * It does something like `gh pr diff 42`
24
- */
25
- export async function getPrDiff(pr) {
26
- // TODO makes sense to check if gh is available and authenticated
27
- try {
28
- return await spawnCommand('gh', ['pr', 'diff', pr], 'Loading PR diff...', 'Loaded PR diff.');
29
- } catch (e) {
30
- displayError(e.toString());
31
- displayError(`Failed to call "gh pr diff ${pr}", see message above for details.`);
32
- exit();
33
- }
34
- }
@@ -1,19 +0,0 @@
1
- import {resolve} from "node:path";
2
- import {slothContext} from "../config.js";
3
- import {display} from "../consoleUtils.js";
4
- import {readFileSyncWithMessages} from "../utils.js";
5
-
6
- /**
7
- * Reads the text file from current dir
8
- * @param _ config (unused in this provider)
9
- * @param fileName
10
- * @returns {string} file contents
11
- */
12
- export function get(_, fileName) {
13
- if (!fileName) {
14
- return null;
15
- }
16
- const filePath = resolve(slothContext.currentDir, fileName);
17
- display(`Reading file ${fileName}...`);
18
- return readFileSyncWithMessages(filePath);
19
- }
@@ -1,11 +0,0 @@
1
- import {spawnCommand} from "../utils.js";
2
- import {displayWarning} from "../consoleUtils.js";
3
-
4
- export async function get(_, pr) {
5
- // TODO makes sense to check if gh is available and authenticated
6
- if (!pr) {
7
- displayWarning("No PR provided, skipping PR diff fetching.");
8
- return "";
9
- }
10
- return spawnCommand('gh', ['pr', 'diff', pr], 'Loading PR diff...', 'Loaded PR diff.');
11
- }
@@ -1,84 +0,0 @@
1
- import {display, displayWarning} from "../consoleUtils.js";
2
- import { env } from "../systemUtils.js";
3
-
4
- export async function get(config, prId) {
5
- const issueData = await getJiraIssue(config, prId);
6
- return `## ${prId} Requirements - ${issueData.fields?.summary}\n\n${issueData.fields?.description}`;
7
- }
8
-
9
- /**
10
- * Fetches a Jira issue using the Atlassian REST API v2.
11
- *
12
- * @async
13
- * @param {object} config - Configuration object.
14
- * @param {string} config.username - Your Jira email address or username used for authentication.
15
- * @param {string} config.token - Your Jira API token (legacy access token).
16
- * @param {string} config.baseUrl - The base URL of your Jira instance API (e.g., 'https://your-domain.atlassian.net/rest/api/2/issue/').
17
- * @param {string} jiraKey - The Jira issue key (e.g., 'UI-1005').
18
- * @returns {Promise<object>} A promise that resolves with the Jira issue data as a JSON object.
19
- * @throws {Error} Throws an error if the fetch fails, authentication is wrong, the issue is not found, or the response status is not OK.
20
- */
21
- async function getJiraIssue(config, jiraKey) {
22
- const {username, baseUrl} = config;
23
- if (!jiraKey) {
24
- displayWarning("No jiraKey provided, skipping Jira issue fetching.");
25
- return "";
26
- }
27
- const token = env.JIRA_LEGACY_API_TOKEN ?? config?.token;
28
-
29
- if (!token) {
30
- throw new Error(
31
- 'Missing JIRA Legacy API token. ' +
32
- 'The legacy token can be defined as JIRA_LEGACY_API_TOKEN environment variable or as "token" in config.'
33
- );
34
- }
35
-
36
- // Validate essential inputs
37
- if (!username || !baseUrl) {
38
- throw new Error('Missing required parameters in config: username or baseUrl');
39
- }
40
-
41
- // Ensure baseUrl doesn't end with a slash to avoid double slashes in the URL
42
- const cleanBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
43
-
44
- // Construct the full API URL
45
- const apiUrl = `${cleanBaseUrl}/${jiraKey}`;
46
-
47
- // Encode credentials for Basic Authentication header
48
- const credentials = `${username}:${token}`;
49
- const encodedCredentials = Buffer.from(credentials).toString('base64');
50
- const authHeader = `Basic ${encodedCredentials}`;
51
-
52
- // Define request headers
53
- const headers = {
54
- 'Authorization': authHeader,
55
- 'Accept': 'application/json', // Tell the server we expect JSON back
56
- // 'Content-Type': 'application/json' // Usually not needed for GET requests
57
- };
58
-
59
- display(`Fetching Jira issue: ${apiUrl}`);
60
-
61
- const response = await fetch(apiUrl, {
62
- method: 'GET',
63
- headers: headers,
64
- });
65
-
66
- // Check if the response status code indicates success (e.g., 200 OK)
67
- if (!response.ok) {
68
- let errorBody = 'Could not read error body.';
69
- try {
70
- // Attempt to get more details from the response body for non-OK statuses
71
- errorBody = await response.text();
72
- // eslint-disable-next-line no-unused-vars
73
- } catch (e) {
74
- // Silent fail - we already have a generic error message
75
- }
76
- // Throw a detailed error including status, status text, URL, and body if available
77
- throw new Error(`HTTP error! Status: ${response.status} ${response.statusText}. URL: ${apiUrl}. Response Body: ${errorBody}`);
78
- }
79
-
80
- // Parse the JSON response body if the request was successful
81
- const issueData = await response.json();
82
- return issueData;
83
-
84
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Simple provider returning text as it is.
3
- */
4
- export async function get(_, text) {
5
- return text;
6
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * This file contains all system functions and objects that are globally available
3
- * but not imported directly, such as process.stdin, process.stdout, process.argv,
4
- * process.env, process.cwd(), process.exit(), etc.
5
- *
6
- * By centralizing these in one file, we improve testability and make it easier
7
- * to mock these dependencies in tests.
8
- */
9
-
10
- const innerState = {
11
- installDir: undefined
12
- };
13
-
14
- /* eslint-disable no-undef */
15
- // Process-related functions and objects
16
- export const getCurrentDir = () => process.cwd();
17
- export const getInstallDir = () => innerState.installDir;
18
- export const exit = (code) => process.exit(code);
19
- export const stdin = process.stdin;
20
- export const stdout = process.stdout;
21
- export const argv = process.argv;
22
- export const env = process.env;
23
-
24
- export const setInstallDir = (dir) => innerState.installDir = dir;
25
-
26
- // Console-related functions
27
- export const log = (message) => console.log(message);
28
- export const error = (message) => console.error(message);
29
- export const warn = (message) => console.warn(message);
30
- export const info = (message) => console.info(message);
31
- export const debug = (message) => console.debug(message);
32
- /* eslint-enable no-undef */