gaunt-sloth-assistant 0.5.0 → 0.5.1

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 (135) hide show
  1. package/.claude/settings.local.json +15 -0
  2. package/.gsloth.backstory.md +0 -0
  3. package/.gsloth.guidelines.md +0 -0
  4. package/.gsloth.review.md +0 -0
  5. package/.gsloth.system.md +10 -0
  6. package/.prettierrc.json +0 -0
  7. package/CLAUDE.md +1 -0
  8. package/LICENSE +0 -0
  9. package/README.md +9 -0
  10. package/ROADMAP.md +0 -0
  11. package/assets/gaunt-sloth-logo.png +0 -0
  12. package/assets/release-notes/v0_4_0.md +0 -0
  13. package/assets/release-notes/v0_5_0.md +0 -0
  14. package/assets/release-notes/v0_5_1.md +47 -0
  15. package/dist/commands/askCommand.d.ts +0 -0
  16. package/dist/commands/askCommand.js +9 -5
  17. package/dist/commands/askCommand.js.map +1 -1
  18. package/dist/commands/commandUtils.d.ts +25 -0
  19. package/dist/commands/commandUtils.js +48 -0
  20. package/dist/commands/commandUtils.js.map +1 -0
  21. package/dist/commands/initCommand.d.ts +0 -0
  22. package/dist/commands/initCommand.js +0 -0
  23. package/dist/commands/initCommand.js.map +0 -0
  24. package/dist/commands/prCommand.d.ts +2 -0
  25. package/dist/commands/prCommand.js +52 -0
  26. package/dist/commands/prCommand.js.map +1 -0
  27. package/dist/commands/reviewCommand.d.ts +1 -2
  28. package/dist/commands/reviewCommand.js +17 -98
  29. package/dist/commands/reviewCommand.js.map +1 -1
  30. package/dist/config.d.ts +18 -36
  31. package/dist/config.js +104 -84
  32. package/dist/config.js.map +1 -1
  33. package/dist/configs/anthropic.d.ts +0 -0
  34. package/dist/configs/anthropic.js +0 -0
  35. package/dist/configs/anthropic.js.map +0 -0
  36. package/dist/configs/fake.d.ts +0 -0
  37. package/dist/configs/fake.js +0 -0
  38. package/dist/configs/fake.js.map +0 -0
  39. package/dist/configs/groq.d.ts +0 -0
  40. package/dist/configs/groq.js +0 -0
  41. package/dist/configs/groq.js.map +0 -0
  42. package/dist/configs/vertexai.d.ts +0 -0
  43. package/dist/configs/vertexai.js +0 -0
  44. package/dist/configs/vertexai.js.map +0 -0
  45. package/dist/consoleUtils.d.ts +0 -0
  46. package/dist/consoleUtils.js +0 -0
  47. package/dist/consoleUtils.js.map +0 -0
  48. package/dist/constants.d.ts +7 -0
  49. package/dist/constants.js +8 -0
  50. package/dist/constants.js.map +1 -0
  51. package/dist/filePathUtils.d.ts +0 -0
  52. package/dist/filePathUtils.js +0 -0
  53. package/dist/filePathUtils.js.map +0 -0
  54. package/dist/index.d.ts +0 -0
  55. package/dist/index.js +4 -2
  56. package/dist/index.js.map +1 -1
  57. package/dist/llmUtils.d.ts +1 -1
  58. package/dist/llmUtils.js +85 -22
  59. package/dist/llmUtils.js.map +1 -1
  60. package/dist/modules/questionAnsweringModule.d.ts +2 -1
  61. package/dist/modules/questionAnsweringModule.js +4 -7
  62. package/dist/modules/questionAnsweringModule.js.map +1 -1
  63. package/dist/modules/reviewModule.d.ts +2 -1
  64. package/dist/modules/reviewModule.js +4 -7
  65. package/dist/modules/reviewModule.js.map +1 -1
  66. package/dist/modules/types.d.ts +0 -0
  67. package/dist/modules/types.js +0 -0
  68. package/dist/modules/types.js.map +0 -0
  69. package/dist/prompt.d.ts +1 -0
  70. package/dist/prompt.js +4 -1
  71. package/dist/prompt.js.map +1 -1
  72. package/dist/providers/file.d.ts +0 -0
  73. package/dist/providers/file.js +0 -0
  74. package/dist/providers/file.js.map +0 -0
  75. package/dist/providers/ghIssueProvider.d.ts +0 -0
  76. package/dist/providers/ghIssueProvider.js +3 -1
  77. package/dist/providers/ghIssueProvider.js.map +1 -1
  78. package/dist/providers/ghPrDiffProvider.d.ts +0 -0
  79. package/dist/providers/ghPrDiffProvider.js +3 -1
  80. package/dist/providers/ghPrDiffProvider.js.map +1 -1
  81. package/dist/providers/jiraIssueLegacyProvider.d.ts +0 -0
  82. package/dist/providers/jiraIssueLegacyProvider.js +0 -0
  83. package/dist/providers/jiraIssueLegacyProvider.js.map +0 -0
  84. package/dist/providers/jiraIssueProvider.d.ts +0 -0
  85. package/dist/providers/jiraIssueProvider.js +0 -0
  86. package/dist/providers/jiraIssueProvider.js.map +0 -0
  87. package/dist/providers/text.d.ts +0 -0
  88. package/dist/providers/text.js +0 -0
  89. package/dist/providers/text.js.map +0 -0
  90. package/dist/providers/types.d.ts +0 -0
  91. package/dist/providers/types.js +0 -0
  92. package/dist/providers/types.js.map +0 -0
  93. package/dist/systemUtils.d.ts +0 -0
  94. package/dist/systemUtils.js +0 -0
  95. package/dist/systemUtils.js.map +0 -0
  96. package/dist/utils.d.ts +0 -0
  97. package/dist/utils.js +0 -0
  98. package/dist/utils.js.map +0 -0
  99. package/docs/CONFIGURATION.md +0 -0
  100. package/docs/DEVELOPMENT.md +0 -0
  101. package/docs/RELEASE-HOWTO.md +0 -0
  102. package/eslint.config.js +0 -0
  103. package/maintenance/doc-maintenance.md +0 -0
  104. package/package.json +10 -8
  105. package/src/commands/askCommand.ts +9 -5
  106. package/src/commands/commandUtils.ts +77 -0
  107. package/src/commands/initCommand.ts +0 -0
  108. package/src/commands/prCommand.ts +93 -0
  109. package/src/commands/reviewCommand.ts +33 -155
  110. package/src/config.ts +121 -119
  111. package/src/configs/anthropic.ts +0 -0
  112. package/src/configs/fake.ts +0 -0
  113. package/src/configs/groq.ts +0 -0
  114. package/src/configs/vertexai.ts +0 -0
  115. package/src/consoleUtils.ts +0 -0
  116. package/src/constants.ts +7 -0
  117. package/src/filePathUtils.ts +0 -0
  118. package/src/index.ts +4 -2
  119. package/src/llmUtils.ts +100 -23
  120. package/src/modules/questionAnsweringModule.ts +6 -12
  121. package/src/modules/reviewModule.ts +11 -7
  122. package/src/modules/types.ts +0 -0
  123. package/src/prompt.ts +5 -1
  124. package/src/providers/file.ts +0 -0
  125. package/src/providers/ghIssueProvider.ts +3 -1
  126. package/src/providers/ghPrDiffProvider.ts +3 -1
  127. package/src/providers/jiraIssueLegacyProvider.ts +0 -0
  128. package/src/providers/jiraIssueProvider.ts +0 -0
  129. package/src/providers/text.ts +0 -0
  130. package/src/providers/types.ts +0 -0
  131. package/src/systemUtils.ts +0 -0
  132. package/src/utils.ts +0 -0
  133. package/tsconfig.json +0 -0
  134. package/vitest-it.config.ts +0 -0
  135. package/vitest.config.ts +0 -0
@@ -1,36 +1,20 @@
1
1
  import { Command, Option } from 'commander';
2
- import type { SlothContext } from '#src/config.js';
3
- import { slothContext } from '#src/config.js';
4
- import { readBackstory, readGuidelines, readReviewInstructions } from '#src/prompt.js';
2
+ import {
3
+ readBackstory,
4
+ readGuidelines,
5
+ readReviewInstructions,
6
+ readSystemPrompt,
7
+ } from '#src/prompt.js';
5
8
  import { readMultipleFilesFromCurrentDir } from '#src/utils.js';
6
- import { displayError } from '#src/consoleUtils.js';
7
9
  import { getStringFromStdin } from '#src/systemUtils.js';
8
-
9
- /**
10
- * Requirements providers. Expected to be in `.providers/` dir.
11
- * Aliases are mapped to actual providers in this file
12
- */
13
- const REQUIREMENTS_PROVIDERS = {
14
- 'jira-legacy': 'jiraIssueLegacyProvider.js',
15
- jira: 'jiraIssueProvider.js',
16
- github: 'ghIssueProvider.js',
17
- text: 'text.js',
18
- file: 'file.js',
19
- } as const;
20
-
21
- type RequirementsProviderType = keyof typeof REQUIREMENTS_PROVIDERS;
22
-
23
- /**
24
- * Content providers. Expected to be in `.providers/` dir.
25
- * Aliases are mapped to actual providers in this file
26
- */
27
- const CONTENT_PROVIDERS = {
28
- github: 'ghPrDiffProvider.js',
29
- text: 'text.js',
30
- file: 'file.js',
31
- } as const;
32
-
33
- type ContentProviderType = keyof typeof CONTENT_PROVIDERS;
10
+ import {
11
+ REQUIREMENTS_PROVIDERS,
12
+ CONTENT_PROVIDERS,
13
+ type RequirementsProviderType,
14
+ type ContentProviderType,
15
+ getRequirementsFromProvider,
16
+ getContentFromProvider,
17
+ } from './commandUtils.js';
34
18
 
35
19
  interface ReviewCommandOptions {
36
20
  file?: string[];
@@ -40,12 +24,7 @@ interface ReviewCommandOptions {
40
24
  message?: string;
41
25
  }
42
26
 
43
- interface PrCommandOptions {
44
- file?: string[];
45
- requirementsProvider?: RequirementsProviderType;
46
- }
47
-
48
- export function reviewCommand(program: Command, context: SlothContext): void {
27
+ export function reviewCommand(program: Command): void {
49
28
  program
50
29
  .command('review')
51
30
  .description('Review provided diff or other content')
@@ -75,32 +54,38 @@ export function reviewCommand(program: Command, context: SlothContext): void {
75
54
  .option('-m, --message <message>', 'Extra message to provide just before the content')
76
55
  .action(async (contentId: string | undefined, options: ReviewCommandOptions) => {
77
56
  const { initConfig } = await import('#src/config.js');
78
- await initConfig();
57
+ const config = await initConfig(); // Initialize and get config
58
+ const systemPrompt = readSystemPrompt();
79
59
  const systemMessage = [
80
60
  readBackstory(),
81
- readGuidelines(slothContext.config.projectGuidelines),
82
- readReviewInstructions(slothContext.config.projectReviewInstructions),
61
+ readGuidelines(config.projectGuidelines),
62
+ readReviewInstructions(config.projectReviewInstructions),
83
63
  ];
64
+ if (systemPrompt) {
65
+ systemMessage.push(systemPrompt);
66
+ }
84
67
  const content: string[] = [];
85
68
  const requirementsId = options.requirements;
86
69
  const requirementsProvider =
87
70
  options.requirementsProvider ??
88
- (context.config?.commands?.review?.requirementsProvider as
89
- | RequirementsProviderType
90
- | undefined) ??
91
- (context.config?.requirementsProvider as RequirementsProviderType | undefined);
71
+ (config?.commands?.review?.requirementsProvider as RequirementsProviderType | undefined) ??
72
+ (config?.requirementsProvider as RequirementsProviderType | undefined);
92
73
  const contentProvider =
93
74
  options.contentProvider ??
94
- (context.config?.commands?.review?.contentProvider as ContentProviderType | undefined) ??
95
- (context.config?.contentProvider as ContentProviderType | undefined);
75
+ (config?.commands?.review?.contentProvider as ContentProviderType | undefined) ??
76
+ (config?.contentProvider as ContentProviderType | undefined);
96
77
 
97
78
  // TODO consider calling these in parallel
98
- const requirements = await getRequirementsFromProvider(requirementsProvider, requirementsId);
79
+ const requirements = await getRequirementsFromProvider(
80
+ requirementsProvider,
81
+ requirementsId,
82
+ config
83
+ );
99
84
  if (requirements) {
100
85
  content.push(requirements);
101
86
  }
102
87
 
103
- const providedContent = await getContentFromProvider(contentProvider, contentId);
88
+ const providedContent = await getContentFromProvider(contentProvider, contentId, config);
104
89
  if (providedContent) {
105
90
  content.push(providedContent);
106
91
  }
@@ -116,113 +101,6 @@ export function reviewCommand(program: Command, context: SlothContext): void {
116
101
  content.push(options.message);
117
102
  }
118
103
  const { review } = await import('#src/modules/reviewModule.js');
119
- await review('REVIEW', systemMessage.join('\n'), content.join('\n'));
120
- });
121
-
122
- program
123
- .command('pr')
124
- .description(
125
- 'Review provided Pull Request in current directory. ' +
126
- 'This command is similar to `review`, but default content provider is `github`. ' +
127
- '(assuming that GitHub CLI is installed and authenticated for current project'
128
- )
129
- .argument('<prId>', 'Pull request ID to review.')
130
- .argument(
131
- '[requirementsId]',
132
- 'Optional requirements ID argument to retrieve requirements with requirements provider'
133
- )
134
- .addOption(
135
- new Option(
136
- '-p, --requirements-provider <requirementsProvider>',
137
- 'Requirements provider for this review.'
138
- ).choices(Object.keys(REQUIREMENTS_PROVIDERS))
139
- )
140
- .option(
141
- '-f, --file [files...]',
142
- 'Input files. Content of these files will be added BEFORE the diff, but after requirements'
143
- )
144
- .action(async (prId: string, requirementsId: string | undefined, options: PrCommandOptions) => {
145
- const { initConfig } = await import('#src/config.js');
146
- await initConfig();
147
-
148
- const systemMessage = [
149
- readBackstory(),
150
- readGuidelines(slothContext.config.projectGuidelines),
151
- readReviewInstructions(slothContext.config.projectReviewInstructions),
152
- ];
153
- const content: string[] = [];
154
- const requirementsProvider =
155
- options.requirementsProvider ??
156
- (context.config?.commands?.pr?.requirementsProvider as
157
- | RequirementsProviderType
158
- | undefined) ??
159
- (context.config?.requirementsProvider as RequirementsProviderType | undefined);
160
-
161
- // Handle requirements
162
- const requirements = await getRequirementsFromProvider(requirementsProvider, requirementsId);
163
- if (requirements) {
164
- content.push(requirements);
165
- }
166
-
167
- if (options.file) {
168
- content.push(readMultipleFilesFromCurrentDir(options.file));
169
- }
170
-
171
- // Get PR diff using the 'github' provider
172
- const providerPath = `#src/providers/${CONTENT_PROVIDERS['github']}`;
173
- const { get } = await import(providerPath);
174
- content.push(await get(null, prId));
175
-
176
- const { review } = await import('#src/modules/reviewModule.js');
177
- // TODO consider including requirements id
178
- // TODO sanitize prId
179
- await review(`PR-${prId}`, systemMessage.join('\n'), content.join('\n'));
104
+ await review('REVIEW', systemMessage.join('\n'), content.join('\n'), config);
180
105
  });
181
-
182
- async function getRequirementsFromProvider(
183
- requirementsProvider: RequirementsProviderType | undefined,
184
- requirementsId: string | undefined
185
- ): Promise<string> {
186
- return getFromProvider(
187
- requirementsProvider,
188
- requirementsId,
189
- (context.config?.requirementsProviderConfig ?? {})[requirementsProvider as string],
190
- REQUIREMENTS_PROVIDERS
191
- );
192
- }
193
-
194
- async function getContentFromProvider(
195
- contentProvider: ContentProviderType | undefined,
196
- contentId: string | undefined
197
- ): Promise<string> {
198
- return getFromProvider(
199
- contentProvider,
200
- contentId,
201
- (context.config?.contentProviderConfig ?? {})[contentProvider as string],
202
- CONTENT_PROVIDERS
203
- );
204
- }
205
-
206
- async function getFromProvider(
207
- provider: RequirementsProviderType | ContentProviderType | undefined,
208
- id: string | undefined,
209
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
210
- config: any,
211
- legitPredefinedProviders: typeof REQUIREMENTS_PROVIDERS | typeof CONTENT_PROVIDERS
212
- ): Promise<string> {
213
- if (typeof provider === 'string') {
214
- // Use one of the predefined providers
215
- if (legitPredefinedProviders[provider as keyof typeof legitPredefinedProviders]) {
216
- const providerPath = `#src/providers/${legitPredefinedProviders[provider as keyof typeof legitPredefinedProviders]}`;
217
- const { get } = await import(providerPath);
218
- return await get(config, id);
219
- } else {
220
- displayError(`Unknown provider: ${provider}. Continuing without it.`);
221
- }
222
- } else if (typeof provider === 'function') {
223
- // Type assertion to handle function call
224
- return await (provider as (id: string | undefined) => Promise<string>)(id);
225
- }
226
- return '';
227
- }
228
106
  }
package/src/config.ts CHANGED
@@ -5,6 +5,13 @@ import { error, exit } from '#src/systemUtils.js';
5
5
  import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
6
6
  import { getGslothConfigReadPath, getGslothConfigWritePath } from '#src/filePathUtils.js';
7
7
  import type { Connection } from '@langchain/mcp-adapters';
8
+ import {
9
+ USER_PROJECT_CONFIG_JS,
10
+ USER_PROJECT_CONFIG_JSON,
11
+ USER_PROJECT_CONFIG_MJS,
12
+ PROJECT_GUIDELINES,
13
+ PROJECT_REVIEW_INSTRUCTIONS,
14
+ } from '#src/constants.js';
8
15
 
9
16
  export interface SlothConfig extends BaseSlothConfig {
10
17
  llm: BaseChatModel; // FIXME this is still bad keeping instance in config is probably not best choice
@@ -13,16 +20,7 @@ export interface SlothConfig extends BaseSlothConfig {
13
20
  projectGuidelines: string;
14
21
  projectReviewInstructions: string;
15
22
  streamOutput: boolean;
16
- commands: {
17
- pr: {
18
- contentProvider: string;
19
- requirementsProvider?: string;
20
- };
21
- review?: {
22
- requirementsProvider?: string;
23
- contentProvider?: string;
24
- };
25
- };
23
+ filesystem: string[] | 'all' | 'none';
26
24
  }
27
25
 
28
26
  /**
@@ -42,14 +40,20 @@ interface BaseSlothConfig {
42
40
  projectGuidelines?: string;
43
41
  projectReviewInstructions?: string;
44
42
  streamOutput?: boolean;
43
+ filesystem?: string[] | 'all' | 'none';
45
44
  commands?: {
46
- pr: {
47
- contentProvider: string;
45
+ pr?: {
46
+ contentProvider?: string;
48
47
  requirementsProvider?: string;
48
+ filesystem?: string[] | 'all' | 'none';
49
49
  };
50
50
  review?: {
51
51
  requirementsProvider?: string;
52
52
  contentProvider?: string;
53
+ filesystem?: string[] | 'all' | 'none';
54
+ };
55
+ ask?: {
56
+ filesystem?: string[] | 'all' | 'none';
53
57
  };
54
58
  };
55
59
  requirementsProviderConfig?: Record<string, unknown>;
@@ -57,26 +61,11 @@ interface BaseSlothConfig {
57
61
  mcpServers?: Record<string, Connection>;
58
62
  }
59
63
 
60
- /**
61
- * @deprecated
62
- * this object has blurred responsibility lines and bad name.
63
- */
64
- export interface SlothContext {
65
- config: SlothConfig;
66
- }
67
-
68
64
  export interface LLMConfig extends Record<string, unknown> {
69
65
  type: string;
70
66
  model: string;
71
67
  }
72
68
 
73
- export const USER_PROJECT_CONFIG_JS = '.gsloth.config.js';
74
- export const USER_PROJECT_CONFIG_JSON = '.gsloth.config.json';
75
- export const USER_PROJECT_CONFIG_MJS = '.gsloth.config.mjs';
76
- export const GSLOTH_BACKSTORY = '.gsloth.backstory.md';
77
- export const PROJECT_GUIDELINES = '.gsloth.guidelines.md';
78
- export const PROJECT_REVIEW_INSTRUCTIONS = '.gsloth.review.md';
79
-
80
69
  export const availableDefaultConfigs = ['vertexai', 'anthropic', 'groq'] as const;
81
70
  export type ConfigType = (typeof availableDefaultConfigs)[number];
82
71
 
@@ -87,6 +76,15 @@ export const DEFAULT_CONFIG: Partial<SlothConfig> = {
87
76
  projectGuidelines: PROJECT_GUIDELINES,
88
77
  projectReviewInstructions: PROJECT_REVIEW_INSTRUCTIONS,
89
78
  streamOutput: true,
79
+ filesystem: [
80
+ 'read_file',
81
+ 'read_multiple_files',
82
+ 'list_directory',
83
+ 'directory_tree',
84
+ 'search_files',
85
+ 'get_file_info',
86
+ 'list_allowed_directories',
87
+ ],
90
88
  commands: {
91
89
  pr: {
92
90
  contentProvider: 'github', // gh pr diff NN
@@ -96,30 +94,26 @@ export const DEFAULT_CONFIG: Partial<SlothConfig> = {
96
94
  };
97
95
 
98
96
  /**
99
- * @deprecated
100
- * this object has blurred responsibility lines and bad name.
101
- * TODO this should be reworked to something more robust
97
+ * Initialize configuration by loading from available config files
98
+ * @returns The loaded SlothConfig
102
99
  */
103
- export const slothContext = {
104
- config: DEFAULT_CONFIG,
105
- } as Partial<SlothContext> as SlothContext;
106
-
107
- export async function initConfig(): Promise<void> {
100
+ export async function initConfig(): Promise<SlothConfig> {
108
101
  const jsonConfigPath = getGslothConfigReadPath(USER_PROJECT_CONFIG_JSON);
109
- const jsConfigPath = getGslothConfigReadPath(USER_PROJECT_CONFIG_JS);
110
- const mjsConfigPath = getGslothConfigReadPath(USER_PROJECT_CONFIG_MJS);
111
102
 
112
- // Try loading JSON config file first
103
+ // Try loading the JSON config file first
113
104
  if (existsSync(jsonConfigPath)) {
114
105
  try {
115
106
  // TODO makes sense to employ ZOD to validate config
116
107
  const jsonConfig = JSON.parse(readFileSync(jsonConfigPath, 'utf8')) as RawSlothConfig;
117
108
  // If the config has an LLM with a type, create the appropriate LLM instance
118
109
  if (jsonConfig.llm && typeof jsonConfig.llm === 'object' && 'type' in jsonConfig.llm) {
119
- await tryJsonConfig(jsonConfig);
110
+ return await tryJsonConfig(jsonConfig);
120
111
  } else {
121
112
  error(`${jsonConfigPath} is not in valid format. Should at least define llm.type`);
122
113
  exit(1);
114
+ // noinspection ExceptionCaughtLocallyJS
115
+ // This throw is unreachable due to exit(1) above, but satisfies TS type analysis and prevents tests from exiting
116
+ throw new Error('Unexpected error occurred.');
123
117
  }
124
118
  } catch (e) {
125
119
  displayDebug(e instanceof Error ? e : String(e));
@@ -133,113 +127,113 @@ export async function initConfig(): Promise<void> {
133
127
  // JSON config not found, try JS
134
128
  return tryJsConfig();
135
129
  }
130
+ }
136
131
 
137
- // Helper function to try loading JS config
138
- async function tryJsConfig(): Promise<void> {
139
- if (existsSync(jsConfigPath)) {
140
- return importExternalFile(jsConfigPath)
141
- .then((i: { configure: (module: string) => Promise<Partial<SlothConfig>> }) =>
142
- i.configure(jsConfigPath)
143
- )
144
- .then((config) => {
145
- slothContext.config = { ...slothContext.config, ...config };
146
- })
147
- .catch((e) => {
148
- displayDebug(e instanceof Error ? e : String(e));
149
- displayError(
150
- `Failed to read config from ${USER_PROJECT_CONFIG_JS}, will try other formats.`
151
- );
152
- // Continue to try other formats
153
- return tryMjsConfig();
154
- });
155
- } else {
156
- // JS config not found, try MJS
132
+ // Helper function to try loading JS config
133
+ async function tryJsConfig(): Promise<SlothConfig> {
134
+ const jsConfigPath = getGslothConfigReadPath(USER_PROJECT_CONFIG_JS);
135
+ if (existsSync(jsConfigPath)) {
136
+ try {
137
+ const i = await importExternalFile(jsConfigPath);
138
+ const customConfig = await i.configure(jsConfigPath);
139
+ return mergeConfig(customConfig) as SlothConfig;
140
+ } catch (e) {
141
+ displayDebug(e instanceof Error ? e : String(e));
142
+ displayError(`Failed to read config from ${USER_PROJECT_CONFIG_JS}, will try other formats.`);
143
+ // Continue to try other formats
157
144
  return tryMjsConfig();
158
145
  }
159
- }
160
-
161
- // Helper function to try loading MJS config
162
- async function tryMjsConfig(): Promise<void> {
163
- if (existsSync(mjsConfigPath)) {
164
- return importExternalFile(mjsConfigPath)
165
- .then((i: { configure: (module: string) => Promise<Partial<SlothConfig>> }) =>
166
- i.configure(mjsConfigPath)
167
- )
168
- .then((config) => {
169
- slothContext.config = { ...slothContext.config, ...config };
170
- })
171
- .catch((e) => {
172
- displayDebug(e instanceof Error ? e : String(e));
173
- displayError(`Failed to read config from ${USER_PROJECT_CONFIG_MJS}.`);
174
- displayError(`No valid configuration found. Please create a valid configuration file.`);
175
- exit();
176
- });
177
- } else {
178
- // No config files found
179
- displayError(
180
- 'No configuration file found. Please create one of: ' +
181
- `${USER_PROJECT_CONFIG_JSON}, ${USER_PROJECT_CONFIG_JS}, or ${USER_PROJECT_CONFIG_MJS} ` +
182
- 'in your project directory.'
183
- );
184
- exit();
185
- }
146
+ } else {
147
+ // JS config not found, try MJS
148
+ return tryMjsConfig();
186
149
  }
187
150
  }
188
151
 
189
- // Process JSON LLM config by creating the appropriate LLM instance
190
- export async function tryJsonConfig(jsonConfig: RawSlothConfig): Promise<void> {
191
- const llmConfig = jsonConfig?.llm;
192
- const llmType = llmConfig?.type?.toLowerCase();
193
-
194
- // Check if the LLM type is in availableDefaultConfigs
195
- if (!llmType || !availableDefaultConfigs.includes(llmType as ConfigType)) {
152
+ // Helper function to try loading MJS config
153
+ async function tryMjsConfig(): Promise<SlothConfig> {
154
+ const mjsConfigPath = getGslothConfigReadPath(USER_PROJECT_CONFIG_MJS);
155
+ if (existsSync(mjsConfigPath)) {
156
+ try {
157
+ const i = await importExternalFile(mjsConfigPath);
158
+ const customConfig = await i.configure(mjsConfigPath);
159
+ return mergeConfig(customConfig) as SlothConfig;
160
+ } catch (e) {
161
+ displayDebug(e instanceof Error ? e : String(e));
162
+ displayError(`Failed to read config from ${USER_PROJECT_CONFIG_MJS}.`);
163
+ displayError(`No valid configuration found. Please create a valid configuration file.`);
164
+ exit(1);
165
+ }
166
+ } else {
167
+ // No config files found
196
168
  displayError(
197
- `Unsupported LLM type: ${llmType}. Available types are: ${availableDefaultConfigs.join(', ')}`
169
+ 'No configuration file found. Please create one of: ' +
170
+ `${USER_PROJECT_CONFIG_JSON}, ${USER_PROJECT_CONFIG_JS}, or ${USER_PROJECT_CONFIG_MJS} ` +
171
+ 'in your project directory.'
198
172
  );
199
173
  exit(1);
200
- return;
201
174
  }
175
+ // This throw is unreachable due to exit(1) above, but satisfies TS type analysis and prevents tests from exiting
176
+ throw new Error('Unexpected error occurred.');
177
+ }
202
178
 
179
+ /**
180
+ * Process JSON LLM config by creating the appropriate LLM instance
181
+ * @param jsonConfig - The parsed JSON config
182
+ * @returns Promise<SlothConfig>
183
+ */
184
+ export async function tryJsonConfig(jsonConfig: RawSlothConfig): Promise<SlothConfig> {
203
185
  try {
204
- // Import the appropriate config module based on the LLM type
205
- try {
186
+ if (jsonConfig.llm && typeof jsonConfig.llm === 'object') {
187
+ // Get the type of LLM (e.g., 'vertexai', 'anthropic') - this should exist
188
+ const llmType = (jsonConfig.llm as LLMConfig).type;
189
+ if (!llmType) {
190
+ displayError('LLM type not specified in config.');
191
+ exit(1);
192
+ }
193
+
194
+ // Get the configuration for the specific LLM type
195
+ const llmConfig = jsonConfig.llm;
196
+ // Import the appropriate config module
206
197
  const configModule = await import(`./configs/${llmType}.js`);
207
198
  if (configModule.processJsonConfig) {
208
199
  const llm = (await configModule.processJsonConfig(llmConfig)) as BaseChatModel;
209
- slothContext.config = { ...slothContext.config, ...jsonConfig, llm };
200
+ return mergeRawConfig(jsonConfig, llm);
210
201
  } else {
211
202
  displayWarning(`Config module for ${llmType} does not have processJsonConfig function.`);
212
203
  exit(1);
213
204
  }
214
- } catch (importError) {
215
- displayDebug(importError instanceof Error ? importError : String(importError));
216
- displayWarning(`Could not import config module for ${llmType}.`);
205
+ } else {
206
+ displayError('No LLM configuration found in config.');
217
207
  exit(1);
218
208
  }
219
- } catch (error) {
220
- displayDebug(error instanceof Error ? error : String(error));
221
- displayError(`Error creating LLM instance for type ${llmType}.`);
209
+ } catch (e) {
210
+ if (e instanceof Error && e.message.includes('Cannot find module')) {
211
+ displayError(`LLM type '${(jsonConfig.llm as LLMConfig).type}' not supported.`);
212
+ } else {
213
+ displayError(`Error processing LLM config: ${e instanceof Error ? e.message : String(e)}`);
214
+ }
222
215
  exit(1);
223
216
  }
217
+ // This throw is unreachable due to exit(1) above, but satisfies TS type analysis and prevents tests from exiting
218
+ throw new Error('Unexpected error occurred.');
224
219
  }
225
220
 
226
221
  export async function createProjectConfig(configType: string): Promise<void> {
227
- displayInfo(`Setting up your project\n`);
228
- writeProjectReviewPreamble();
229
- displayWarning(`Make sure you add as much detail as possible to your ${PROJECT_GUIDELINES}.\n`);
230
-
231
- // Check if the config type is in availableDefaultConfigs
222
+ // Check if the config type is valid
232
223
  if (!availableDefaultConfigs.includes(configType as ConfigType)) {
233
224
  displayError(
234
- `Unsupported config type: ${configType}. Available types are: ${availableDefaultConfigs.join(', ')}`
225
+ `Unknown config type: ${configType}. Available options: ${availableDefaultConfigs.join(', ')}`
235
226
  );
236
227
  exit(1);
237
- return;
238
228
  }
239
229
 
230
+ displayInfo(`Setting up your project\n`);
231
+ writeProjectReviewPreamble();
232
+ displayWarning(`Make sure you add as much detail as possible to your ${PROJECT_GUIDELINES}.\n`);
233
+
240
234
  displayInfo(`Creating project config for ${configType}`);
241
235
  const vendorConfig = await import(`./configs/${configType}.js`);
242
- vendorConfig.init(getGslothConfigWritePath(USER_PROJECT_CONFIG_JSON), slothContext);
236
+ vendorConfig.init(getGslothConfigWritePath(USER_PROJECT_CONFIG_JSON));
243
237
  }
244
238
 
245
239
  export function writeProjectReviewPreamble(): void {
@@ -287,12 +281,20 @@ Important! You are likely to be dealing with git diff below, please don't confus
287
281
  }
288
282
 
289
283
  /**
290
- * @deprecated test only
291
- * TODO should be gone together with slothContext itself
284
+ * Merge config with default config
285
+ */
286
+ function mergeConfig(partialConfig: Partial<SlothConfig>): SlothConfig {
287
+ const config = partialConfig as SlothConfig;
288
+ return {
289
+ ...DEFAULT_CONFIG,
290
+ ...config,
291
+ commands: { ...DEFAULT_CONFIG.commands, ...(config?.commands ?? {}) },
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Merge raw with default config
292
297
  */
293
- export function reset() {
294
- Object.keys(slothContext).forEach((key) => {
295
- delete (slothContext as unknown as Record<string, unknown>)[key];
296
- });
297
- slothContext.config = DEFAULT_CONFIG as SlothConfig;
298
+ function mergeRawConfig(config: RawSlothConfig, llm: BaseChatModel): SlothConfig {
299
+ return mergeConfig({ ...config, llm });
298
300
  }
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,7 @@
1
+ export const USER_PROJECT_CONFIG_JS = '.gsloth.config.js';
2
+ export const USER_PROJECT_CONFIG_JSON = '.gsloth.config.json';
3
+ export const USER_PROJECT_CONFIG_MJS = '.gsloth.config.mjs';
4
+ export const GSLOTH_BACKSTORY = '.gsloth.backstory.md';
5
+ export const PROJECT_GUIDELINES = '.gsloth.guidelines.md';
6
+ export const PROJECT_REVIEW_INSTRUCTIONS = '.gsloth.review.md';
7
+ export const GSLOTH_SYSTEM_PROMPT = '.gsloth.system.md';
File without changes
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@ import { Command } from 'commander';
2
2
  import { askCommand } from '#src/commands/askCommand.js';
3
3
  import { initCommand } from '#src/commands/initCommand.js';
4
4
  import { reviewCommand } from '#src/commands/reviewCommand.js';
5
- import { slothContext } from '#src/config.js';
5
+ import { prCommand } from '#src/commands/prCommand.js';
6
6
  import { getSlothVersion } from '#src/utils.js';
7
7
  import { argv, readStdin } from '#src/systemUtils.js';
8
8
  import { setVerbose } from '#src/llmUtils.js';
@@ -22,8 +22,10 @@ if (program.getOptionValue('verbose')) {
22
22
  setVerbose(true);
23
23
  }
24
24
 
25
+ // Initialize all commands - they will handle their own config loading
25
26
  initCommand(program);
26
- reviewCommand(program, slothContext);
27
+ reviewCommand(program);
28
+ prCommand(program);
27
29
  askCommand(program);
28
30
  // TODO add general interactive chat command
29
31