gaunt-sloth-assistant 0.1.5 → 0.3.0

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 (145) hide show
  1. package/{.gsloth.preamble.review.md → .gsloth.guidelines.md} +0 -8
  2. package/.gsloth.review.md +7 -0
  3. package/.prettierrc.json +9 -0
  4. package/README.md +177 -158
  5. package/ROADMAP.md +1 -1
  6. package/dist/commands/askCommand.d.ts +6 -0
  7. package/dist/commands/askCommand.js +27 -0
  8. package/dist/commands/askCommand.js.map +1 -0
  9. package/dist/commands/initCommand.d.ts +6 -0
  10. package/dist/commands/initCommand.js +16 -0
  11. package/dist/commands/initCommand.js.map +1 -0
  12. package/dist/commands/reviewCommand.d.ts +3 -0
  13. package/dist/commands/reviewCommand.js +142 -0
  14. package/dist/commands/reviewCommand.js.map +1 -0
  15. package/dist/config.d.ts +84 -0
  16. package/dist/config.js +180 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/configs/anthropic.d.ts +4 -0
  19. package/{src → dist}/configs/anthropic.js +45 -48
  20. package/dist/configs/anthropic.js.map +1 -0
  21. package/dist/configs/fake.d.ts +3 -0
  22. package/{src → dist}/configs/fake.js +11 -14
  23. package/dist/configs/fake.js.map +1 -0
  24. package/dist/configs/groq.d.ts +4 -0
  25. package/{src → dist}/configs/groq.js +10 -13
  26. package/dist/configs/groq.js.map +1 -0
  27. package/dist/configs/types.d.ts +14 -0
  28. package/dist/configs/types.js +2 -0
  29. package/dist/configs/types.js.map +1 -0
  30. package/dist/configs/vertexai.d.ts +4 -0
  31. package/{src → dist}/configs/vertexai.js +44 -47
  32. package/dist/configs/vertexai.js.map +1 -0
  33. package/dist/consoleUtils.d.ts +6 -0
  34. package/{src → dist}/consoleUtils.js +10 -15
  35. package/dist/consoleUtils.js.map +1 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +26 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/llmUtils.d.ts +4 -0
  40. package/dist/llmUtils.js +39 -0
  41. package/dist/llmUtils.js.map +1 -0
  42. package/dist/modules/questionAnsweringModule.d.ts +7 -0
  43. package/dist/modules/questionAnsweringModule.js +33 -0
  44. package/dist/modules/questionAnsweringModule.js.map +1 -0
  45. package/dist/modules/reviewModule.d.ts +1 -0
  46. package/dist/modules/reviewModule.js +29 -0
  47. package/dist/modules/reviewModule.js.map +1 -0
  48. package/dist/modules/types.d.ts +18 -0
  49. package/dist/modules/types.js +2 -0
  50. package/dist/modules/types.js.map +1 -0
  51. package/dist/prompt.d.ts +8 -0
  52. package/dist/prompt.js +45 -0
  53. package/dist/prompt.js.map +1 -0
  54. package/dist/providers/file.d.ts +8 -0
  55. package/dist/providers/file.js +20 -0
  56. package/dist/providers/file.js.map +1 -0
  57. package/dist/providers/ghPrDiffProvider.d.ts +8 -0
  58. package/dist/providers/ghPrDiffProvider.js +16 -0
  59. package/dist/providers/ghPrDiffProvider.js.map +1 -0
  60. package/dist/providers/jiraIssueLegacyAccessTokenProvider.d.ts +8 -0
  61. package/dist/providers/jiraIssueLegacyAccessTokenProvider.js +62 -0
  62. package/dist/providers/jiraIssueLegacyAccessTokenProvider.js.map +1 -0
  63. package/dist/providers/jiraIssueLegacyProvider.d.ts +8 -0
  64. package/dist/providers/jiraIssueLegacyProvider.js +74 -0
  65. package/dist/providers/jiraIssueLegacyProvider.js.map +1 -0
  66. package/dist/providers/jiraIssueProvider.d.ts +11 -0
  67. package/dist/providers/jiraIssueProvider.js +96 -0
  68. package/dist/providers/jiraIssueProvider.js.map +1 -0
  69. package/dist/providers/text.d.ts +8 -0
  70. package/dist/providers/text.js +10 -0
  71. package/dist/providers/text.js.map +1 -0
  72. package/dist/providers/types.d.ts +21 -0
  73. package/dist/providers/types.js +2 -0
  74. package/dist/providers/types.js.map +1 -0
  75. package/dist/systemUtils.d.ts +32 -0
  76. package/dist/systemUtils.js +70 -0
  77. package/dist/systemUtils.js.map +1 -0
  78. package/dist/utils.d.ts +49 -0
  79. package/dist/utils.js +192 -0
  80. package/dist/utils.js.map +1 -0
  81. package/docs/CONFIGURATION.md +99 -10
  82. package/docs/RELEASE-HOWTO.md +7 -1
  83. package/eslint.config.js +99 -21
  84. package/gth-ASK-2025-05-16T14-11-39.md +3 -0
  85. package/gth-ASK-2025-05-16T14-18-27.md +3 -0
  86. package/gth-ASK-2025-05-16T14-18-56.md +1 -0
  87. package/gth-ASK-2025-05-16T14-41-20.md +3 -0
  88. package/gth-ASK-2025-05-16T14-43-31.md +51 -0
  89. package/gth-ASK-2025-05-16T16-05-52.md +62 -0
  90. package/gth-DIFF-review-2025-05-16T16-07-53.md +56 -0
  91. package/gth-DIFF-review-2025-05-16T16-18-55.md +292 -0
  92. package/index.js +10 -27
  93. package/package.json +26 -15
  94. package/src/commands/askCommand.ts +35 -0
  95. package/src/commands/initCommand.ts +19 -0
  96. package/src/commands/reviewCommand.ts +223 -0
  97. package/src/config.ts +269 -0
  98. package/src/configs/anthropic.ts +57 -0
  99. package/src/configs/fake.ts +15 -0
  100. package/src/configs/groq.ts +54 -0
  101. package/src/configs/vertexai.ts +53 -0
  102. package/src/consoleUtils.ts +33 -0
  103. package/src/index.ts +30 -0
  104. package/src/llmUtils.ts +54 -0
  105. package/src/modules/questionAnsweringModule.ts +44 -0
  106. package/src/modules/reviewModule.ts +31 -0
  107. package/src/modules/types.ts +23 -0
  108. package/src/prompt.ts +54 -0
  109. package/src/providers/file.ts +24 -0
  110. package/src/providers/ghPrDiffProvider.ts +20 -0
  111. package/src/providers/jiraIssueLegacyProvider.ts +103 -0
  112. package/src/providers/jiraIssueProvider.ts +133 -0
  113. package/src/providers/text.ts +14 -0
  114. package/src/providers/types.ts +24 -0
  115. package/src/systemUtils.ts +90 -0
  116. package/src/utils.ts +232 -0
  117. package/tsconfig.json +24 -0
  118. package/vitest.config.ts +13 -0
  119. package/.eslint.config.mjs +0 -72
  120. package/.github/dependabot.yml +0 -11
  121. package/.github/workflows/ci.yml +0 -33
  122. package/spec/.gsloth.config.js +0 -22
  123. package/spec/.gsloth.config.json +0 -25
  124. package/spec/askCommand.spec.js +0 -92
  125. package/spec/config.spec.js +0 -421
  126. package/spec/initCommand.spec.js +0 -55
  127. package/spec/predefinedConfigs.spec.js +0 -100
  128. package/spec/questionAnsweringModule.spec.js +0 -137
  129. package/spec/reviewCommand.spec.js +0 -222
  130. package/spec/reviewModule.spec.js +0 -28
  131. package/spec/support/jasmine.mjs +0 -14
  132. package/src/commands/askCommand.js +0 -27
  133. package/src/commands/initCommand.js +0 -17
  134. package/src/commands/reviewCommand.js +0 -154
  135. package/src/config.js +0 -177
  136. package/src/modules/questionAnsweringModule.js +0 -82
  137. package/src/modules/reviewModule.js +0 -70
  138. package/src/prompt.js +0 -34
  139. package/src/providers/file.js +0 -19
  140. package/src/providers/ghPrDiffProvider.js +0 -11
  141. package/src/providers/jiraIssueLegacyAccessTokenProvider.js +0 -84
  142. package/src/providers/text.js +0 -6
  143. package/src/systemUtils.js +0 -32
  144. package/src/utils.js +0 -173
  145. /package/{.gsloth.preamble.internal.md → .gsloth.backstory.md} +0 -0
package/src/utils.ts ADDED
@@ -0,0 +1,232 @@
1
+ import { display, displayError, displaySuccess, displayWarning } from '#src/consoleUtils.js';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { SlothConfig } from '#src/config.js';
4
+ import { resolve } from 'node:path';
5
+ import { spawn } from 'node:child_process';
6
+ import { getCurrentDir, getInstallDir, stdout } from '#src/systemUtils.js';
7
+ import url from 'node:url';
8
+
9
+ export function toFileSafeString(string: string): string {
10
+ return string.replace(/[^A-Za-z0-9]/g, '-');
11
+ }
12
+
13
+ export function fileSafeLocalDate(): string {
14
+ const date = new Date();
15
+ const offsetMs = date.getTimezoneOffset() * 60 * 1000;
16
+ const msLocal = date.getTime() - offsetMs;
17
+ const dateLocal = new Date(msLocal);
18
+ const iso = dateLocal.toISOString();
19
+ const isoLocal = iso.slice(0, 19);
20
+ return toFileSafeString(isoLocal);
21
+ }
22
+
23
+ export function readFileFromCurrentDir(fileName: string): string {
24
+ const currentDir = getCurrentDir();
25
+ const filePath = resolve(currentDir, fileName);
26
+ display(`Reading file ${filePath}...`);
27
+ return readFileSyncWithMessages(filePath);
28
+ }
29
+
30
+ export function readFileFromCurrentOrInstallDir(filePath: string, silentCurrent?: boolean): string {
31
+ const currentDir = getCurrentDir();
32
+ const currentFilePath = resolve(currentDir, filePath);
33
+ if (!silentCurrent) {
34
+ display(`Reading file ${currentFilePath}...`);
35
+ }
36
+
37
+ try {
38
+ return readFileSync(currentFilePath, { encoding: 'utf8' });
39
+ } catch (_error) {
40
+ if (!silentCurrent) {
41
+ display(`The ${currentFilePath} not found or can\'t be read, trying install directory...`);
42
+ }
43
+ const installDir = getInstallDir();
44
+ const installFilePath = resolve(installDir, filePath);
45
+ try {
46
+ return readFileSync(installFilePath, { encoding: 'utf8' });
47
+ } catch (readFromInstallDirError) {
48
+ displayError(`The ${installFilePath} not found or can\'t be read.`);
49
+ throw readFromInstallDirError;
50
+ }
51
+ }
52
+ }
53
+
54
+ export function writeFileIfNotExistsWithMessages(filePath: string, content: string): void {
55
+ display(`checking ${filePath} existence`);
56
+ if (!existsSync(filePath)) {
57
+ writeFileSync(filePath, content);
58
+ displaySuccess(`Created ${filePath}`);
59
+ } else {
60
+ displayWarning(`${filePath} already exists`);
61
+ }
62
+ }
63
+
64
+ export function readFileSyncWithMessages(
65
+ filePath: string,
66
+ errorMessageIn?: string,
67
+ noFileMessage?: string
68
+ ): string {
69
+ const errorMessage = errorMessageIn ?? 'Error reading file at: ';
70
+ try {
71
+ return readFileSync(filePath, { encoding: 'utf8' });
72
+ } catch (error) {
73
+ displayError(errorMessage + filePath);
74
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
75
+ displayWarning(noFileMessage ?? 'Please ensure the file exists.');
76
+ } else {
77
+ displayError((error as Error).message);
78
+ }
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ interface SpawnOutput {
84
+ stdout: string;
85
+ stderr: string;
86
+ }
87
+
88
+ export async function spawnCommand(
89
+ command: string,
90
+ args: string[],
91
+ progressMessage: string,
92
+ successMessage: string
93
+ ): Promise<string> {
94
+ return new Promise((resolve, reject) => {
95
+ const progressIndicator = new ProgressIndicator(progressMessage, true);
96
+ const out: SpawnOutput = { stdout: '', stderr: '' };
97
+ const spawned = spawn(command, args);
98
+
99
+ spawned.stdout.on('data', async (stdoutChunk) => {
100
+ progressIndicator.indicate();
101
+ out.stdout += stdoutChunk.toString();
102
+ });
103
+
104
+ spawned.stderr.on('data', (err) => {
105
+ progressIndicator.indicate();
106
+ out.stderr += err.toString();
107
+ });
108
+
109
+ spawned.on('error', (err) => {
110
+ reject(err.toString());
111
+ });
112
+
113
+ spawned.on('close', (code) => {
114
+ if (code === 0) {
115
+ display(successMessage);
116
+ resolve(out.stdout);
117
+ } else {
118
+ displayError(`Failed to spawn command with code ${code}`);
119
+ reject(out.stdout + ' ' + out.stderr);
120
+ }
121
+ });
122
+ });
123
+ }
124
+
125
+ export function getSlothVersion(): string {
126
+ // TODO figure out if this can be injected with TS
127
+ const installDir = getInstallDir();
128
+ const jsonPath = resolve(installDir, 'package.json');
129
+ const projectJson = readFileSync(jsonPath, { encoding: 'utf8' });
130
+ return JSON.parse(projectJson).version;
131
+ }
132
+
133
+ export class ProgressIndicator {
134
+ private interval: number | undefined = undefined;
135
+
136
+ constructor(initialMessage: string, manual?: boolean) {
137
+ stdout.write(initialMessage);
138
+ if (!manual) {
139
+ this.interval = setInterval(this.indicateInner, 1000) as unknown as number;
140
+ }
141
+ }
142
+
143
+ private indicateInner(): void {
144
+ stdout.write('.');
145
+ }
146
+
147
+ indicate(): void {
148
+ if (this.interval) {
149
+ throw new Error('ProgressIndicator.indicate only to be called in manual mode');
150
+ }
151
+ this.indicateInner();
152
+ }
153
+
154
+ stop(): void {
155
+ if (this.interval) {
156
+ clearInterval(this.interval);
157
+ }
158
+ stdout.write('\n');
159
+ }
160
+ }
161
+
162
+ interface LLMOutput {
163
+ messages: Array<{
164
+ content: string;
165
+ }>;
166
+ }
167
+
168
+ /**
169
+ * Extracts the content of the last message from an LLM response
170
+ * @param output - The output from the LLM containing messages
171
+ * @returns The content of the last message
172
+ */
173
+ export function extractLastMessageContent(output: LLMOutput): string {
174
+ if (!output || !output.messages || !output.messages.length) {
175
+ return '';
176
+ }
177
+ return output.messages[output.messages.length - 1].content;
178
+ }
179
+
180
+ /**
181
+ * Dynamically imports a module from a file path from the outside of the installation dir
182
+ * @param filePath - The path to the file to import
183
+ * @returns A promise that resolves to the imported module
184
+ */
185
+ export function importExternalFile(
186
+ filePath: string
187
+ ): Promise<{ configure: (module: string) => Promise<Partial<SlothConfig>> }> {
188
+ const configFileUrl = url.pathToFileURL(filePath).toString();
189
+ return import(configFileUrl);
190
+ }
191
+
192
+ /**
193
+ * Alias for importExternalFile for backward compatibility with tests
194
+ * @param filePath - The path to the file to import
195
+ * @returns A promise that resolves to the imported module
196
+ */
197
+ export const importFromFilePath = importExternalFile;
198
+
199
+ /**
200
+ * Reads multiple files from the current directory and returns their contents
201
+ * @param fileNames - Array of file names to read
202
+ * @returns Combined content of all files with proper formatting
203
+ */
204
+ export function readMultipleFilesFromCurrentDir(fileNames: string | string[]): string {
205
+ if (!Array.isArray(fileNames)) {
206
+ return readFileFromCurrentDir(fileNames);
207
+ }
208
+
209
+ return fileNames
210
+ .map((fileName) => {
211
+ const content = readFileFromCurrentDir(fileName);
212
+ return `${fileName}:\n\`\`\`\n${content}\n\`\`\``;
213
+ })
214
+ .join('\n\n');
215
+ }
216
+
217
+ export async function execAsync(command: string): Promise<string> {
218
+ const { exec } = await import('node:child_process');
219
+ return new Promise((resolve, reject) => {
220
+ exec(command, (error, stdout, stderr) => {
221
+ if (error) {
222
+ reject(error);
223
+ return;
224
+ }
225
+ if (stderr) {
226
+ reject(new Error(stderr));
227
+ return;
228
+ }
229
+ resolve(stdout.trim());
230
+ });
231
+ });
232
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "outDir": "dist",
11
+ "rootDir": "src",
12
+ "declaration": true,
13
+ "sourceMap": true,
14
+ "typeRoots": ["./node_modules/@types", "./src/types"],
15
+ "allowJs": true,
16
+ "checkJs": false,
17
+ "baseUrl": ".",
18
+ "paths": {
19
+ "#src/*": ["./src/*"]
20
+ }
21
+ },
22
+ "include": ["src/**/*"],
23
+ "exclude": ["node_modules", "dist", "spec"]
24
+ }
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ['spec/**/*.spec.ts'],
6
+ environment: 'node',
7
+ coverage: {
8
+ provider: 'v8',
9
+ reporter: ['text', 'json', 'html'],
10
+ },
11
+ globals: true,
12
+ },
13
+ });
@@ -1,72 +0,0 @@
1
- // TODO
2
- // // eslint.config.mjs
3
- // // Remember to install dependencies:
4
- // // npm install --save-dev eslint @eslint/js eslint-plugin-n globals eslint-config-prettier
5
- // // yarn add --dev eslint @eslint/js eslint-plugin-n globals eslint-config-prettier
6
- // // pnpm add -D eslint @eslint/js eslint-plugin-n globals eslint-config-prettier
7
- //
8
- // import js from "@eslint/js"; // Provides eslint:recommended and eslint:all
9
- // import pluginN from "eslint-plugin-n"; // Successor to eslint-plugin-node
10
- // import globals from "globals"; // Provides standard global variables (node, browser, etc.)
11
- // import eslintConfigPrettier from "eslint-config-prettier"; // Disables rules that conflict with Prettier
12
- //
13
- // export default [
14
- // // 1. Global Ignores
15
- // // Files/directories to ignore globally. You can add more patterns.
16
- // {
17
- // ignores: [
18
- // "node_modules/",
19
- // "dist/", // Common build output directory
20
- // "build/", // Another common build output directory
21
- // ".env",
22
- // "*.log",
23
- // "coverage/", // Test coverage reports
24
- // ],
25
- // },
26
- //
27
- // // 2. ESLint Recommended Rules
28
- // // Provides a good baseline set of rules maintained by the ESLint team.
29
- // js.configs.recommended,
30
- //
31
- // // 3. Node.js Specific Rules (using eslint-plugin-n)
32
- // // Recommended configuration for Node.js projects.
33
- // pluginN.configs['flat/recommended'],
34
- //
35
- // // 4. Custom Configuration for your JS/MJS files
36
- // {
37
- // files: ["**/*.{js,mjs}"], // Apply these settings to .js and .mjs files
38
- // languageOptions: {
39
- // ecmaVersion: "latest", // Use the latest ECMAScript features
40
- // sourceType: "module", // Set to "module" for ES Modules (import/export)
41
- // globals: {
42
- // ...globals.nodeBuiltin, // Includes Node.js built-in globals like 'process', 'Buffer', etc.
43
- // // Add other global environments if needed:
44
- // // ...globals.browser, // If your code also runs in the browser
45
- // // Add any other custom global variables your project uses:
46
- // // myCustomGlobal: "readonly",
47
- // }
48
- // },
49
- // rules: {
50
- // // Customize or override rules here
51
- // "no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], // Warn about unused vars, except those starting with _
52
- // "semi": ["error", "always"], // Enforce semicolons
53
- // "quotes": ["warn", "single"], // Prefer single quotes
54
- // "indent": ["warn", 2], // Enforce 2-space indentation
55
- //
56
- // // Node specific rule examples (from eslint-plugin-n) - adjust as needed
57
- // "n/no-unpublished-import": ["error", {
58
- // "allowModules": [], // Add exceptions for modules used in dev but not in dependencies
59
- // }],
60
- // "n/no-missing-import": "error", // Ensure imports can be resolved
61
- // "n/no-extraneous-import": "error", // Prevent importing devDependencies in production code
62
- //
63
- // // Add other rules or modify existing ones based on your team's style guide
64
- // }
65
- // },
66
- //
67
- // // 5. Prettier Configuration (Optional but Recommended)
68
- // // IMPORTANT: This MUST be the LAST configuration object in the array.
69
- // // It disables ESLint rules that would conflict with Prettier's formatting.
70
- // // Assumes you are using Prettier for code formatting.
71
- // eslintConfigPrettier,
72
- // ];
@@ -1,11 +0,0 @@
1
- # To get started with Dependabot version updates, you'll need to specify which
2
- # package ecosystems to update and where the package manifests are located.
3
- # Please see the documentation for all configuration options:
4
- # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
-
6
- version: 2
7
- updates:
8
- - package-ecosystem: "npm" # See documentation for possible values
9
- directory: "/" # Location of package manifests
10
- schedule:
11
- interval: "weekly"
@@ -1,33 +0,0 @@
1
- name: Tests and Lint
2
-
3
- on:
4
- push:
5
- branches: [ "main" ]
6
- pull_request:
7
- branches: [ "main" ]
8
-
9
- jobs:
10
- test-and-lint:
11
- runs-on: ubuntu-latest
12
-
13
- strategy:
14
- matrix:
15
- node-version: [22.x]
16
-
17
- steps:
18
- - uses: actions/checkout@v4
19
-
20
- - name: Use Node.js ${{ matrix.node-version }}
21
- uses: actions/setup-node@v4
22
- with:
23
- node-version: ${{ matrix.node-version }}
24
- cache: 'npm'
25
-
26
- - name: Install dependencies
27
- run: npm ci
28
-
29
- - name: Run ESLint
30
- run: npm run lint
31
-
32
- - name: Run Tests
33
- run: npm test
@@ -1,22 +0,0 @@
1
- export async function configure(importFunction, global) {
2
- const test = await importFunction('@langchain/core/utils/testing');
3
- return {
4
- llm: new test.FakeListChatModel({
5
- responses: ["First LLM message", "Second LLM message"],
6
- }),
7
- requirementsProviderConfig: {
8
- 'jira-legacy': {
9
- username: 'user.name@company.com', // Your Jira username/email
10
- token: 'YoUrToKeN', // Replace with your real Jira API token
11
- baseUrl: 'https://company.atlassian.net/rest/api/2/issue/' // Your Jira instance base URL
12
- }
13
- },
14
- requirementsProvider: "jira-legacy",
15
- contentProvider: "somethingSpecial",
16
- contentProviderConfig: {
17
- somethingSpecial: {
18
- test: 'example'
19
- }
20
- }
21
- };
22
- }
@@ -1,25 +0,0 @@
1
- {
2
- "llm": {
3
- "type": "fake",
4
- "responses": ["First LLM message", "Second LLM message"]
5
- },
6
- "requirementsProviderConfig": {
7
- "jira-legacy": {
8
- "username": "user.name@company.com",
9
- "token": "YoUrToKeN",
10
- "baseUrl": "https://company.atlassian.net/rest/api/2/issue/"
11
- }
12
- },
13
- "requirementsProvider": "file",
14
- "contentProvider": "somethingSpecial",
15
- "contentProviderConfig": {
16
- "somethingSpecial": {
17
- "test": "example"
18
- }
19
- },
20
- "commands": {
21
- "pr": {
22
- "requirementsProvider": "jira-legacy"
23
- }
24
- }
25
- }
@@ -1,92 +0,0 @@
1
- import {Command} from 'commander';
2
- import * as td from 'testdouble';
3
-
4
- describe('askCommand', function (){
5
-
6
- beforeEach(async function() {
7
- td.reset();
8
- this.askQuestion = td.function();
9
- this.prompt = await td.replaceEsm("../src/prompt.js");
10
- td.when(this.prompt.readInternalPreamble()).thenReturn("INTERNAL PREAMBLE");
11
- this.questionAnsweringMock = await td.replaceEsm("../src/modules/questionAnsweringModule.js");
12
- await td.replaceEsm("../src/config.js", {
13
- SLOTH_INTERNAL_PREAMBLE: '.gsloth.preamble.internal.md',
14
- USER_PROJECT_REVIEW_PREAMBLE: '.gsloth.preamble.review.md',
15
- slothContext: {
16
- config: {},
17
- currentDir: '/mock/current/dir'
18
- },
19
- initConfig: td.function()
20
- });
21
- const readFileFromCurrentDir = td.function();
22
- const readMultipleFilesFromCurrentDir = td.function();
23
- const extractLastMessageContent = td.function();
24
- const toFileSafeString = td.function();
25
- const fileSafeLocalDate = td.function();
26
- this.utilsMock = {
27
- readFileFromCurrentDir,
28
- readMultipleFilesFromCurrentDir,
29
- ProgressIndicator: td.constructor(),
30
- extractLastMessageContent,
31
- toFileSafeString,
32
- fileSafeLocalDate
33
- };
34
- await td.replaceEsm("../src/utils.js", this.utilsMock);
35
- td.when(this.utilsMock.readFileFromCurrentDir("test.file")).thenReturn("FILE CONTENT");
36
- td.when(this.utilsMock.readMultipleFilesFromCurrentDir(["test.file"])).thenReturn("test.file:\n```\nFILE CONTENT\n```");
37
- td.when(this.questionAnsweringMock.askQuestion(
38
- 'sloth-ASK',
39
- td.matchers.anything(),
40
- td.matchers.anything())
41
- ).thenDo(this.askQuestion);
42
- });
43
-
44
- it('Should call askQuestion with message', async function() {
45
- const { askCommand } = await import("../src/commands/askCommand.js");
46
- const program = new Command();
47
- await askCommand(program, {});
48
- await program.parseAsync(['na', 'na', 'ask', 'test message']);
49
- td.verify(this.askQuestion('sloth-ASK', "INTERNAL PREAMBLE", "test message"));
50
- });
51
-
52
- it('Should call askQuestion with message and file content', async function() {
53
- const { askCommand } = await import("../src/commands/askCommand.js");
54
- const program = new Command();
55
- await askCommand(program, {});
56
- await program.parseAsync(['na', 'na', 'ask', 'test message', '-f', 'test.file']);
57
- td.verify(this.askQuestion('sloth-ASK', "INTERNAL PREAMBLE", "test message\ntest.file:\n```\nFILE CONTENT\n```"));
58
- });
59
-
60
- it('Should call askQuestion with message and multiple file contents', async function() {
61
- const { askCommand } = await import("../src/commands/askCommand.js");
62
- const program = new Command();
63
- await askCommand(program, {});
64
- td.when(this.utilsMock.readMultipleFilesFromCurrentDir(["test.file", "test2.file"]))
65
- .thenReturn("test.file:\n```\nFILE CONTENT\n```\n\ntest2.file:\n```\nFILE2 CONTENT\n```");
66
- await program.parseAsync(['na', 'na', 'ask', 'test message', '-f', 'test.file', 'test2.file']);
67
- td.verify(this.askQuestion('sloth-ASK', "INTERNAL PREAMBLE", "test message\ntest.file:\n```\nFILE CONTENT\n```\n\ntest2.file:\n```\nFILE2 CONTENT\n```"));
68
- });
69
-
70
- it('Should display help correctly', async function() {
71
- const { askCommand } = await import("../src/commands/askCommand.js");
72
- const program = new Command();
73
- const testOutput = { text: '' };
74
-
75
- program.configureOutput({
76
- writeOut: (str) => testOutput.text += str,
77
- writeErr: (str) => testOutput.text += str
78
- });
79
-
80
- await askCommand(program, {});
81
-
82
- const commandUnderTest = program.commands.find(c => c.name() == 'ask');
83
-
84
- expect(commandUnderTest).toBeDefined();
85
- commandUnderTest.outputHelp();
86
-
87
- // Verify help content
88
- expect(testOutput.text).toContain('Ask a question');
89
- expect(testOutput.text).toContain('<message>');
90
- expect(testOutput.text).toContain('-f, --file');
91
- });
92
- });