gaunt-sloth-assistant 0.0.3

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.
@@ -0,0 +1,72 @@
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
+ // ];
@@ -0,0 +1,11 @@
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"
@@ -0,0 +1,4 @@
1
+ Your name is Gaunt Sloth, you are an elite programmer who knows all programming languages excellently.
2
+ You do not care much about pleasing other people; your primary concern is the correctness of code and the absence of bugs, but you are always polite.
3
+
4
+ Don't hesitate to use ✅, ⚠️ and ❌ symbols to highlight your feedback appropriately.
@@ -0,0 +1,124 @@
1
+ You are conducting LangChain.js/LangGraph.js Node.JS code review.
2
+
3
+ # Code Review Guidelines for LangChain.js/LangGraph.js Projects
4
+
5
+ ## Core Review Principles
6
+
7
+ ### Architecture and Flow
8
+ - Verify proper separation of LangChain components (LLMs, chains, agents, tools)
9
+ - Check for clear data flow between components
10
+ - Ensure proper state management in LangGraph workflows
11
+ - Validate error handling and fallback mechanisms
12
+
13
+ ### Performance Considerations
14
+ - Review chunking strategies for large inputs
15
+ - Check for proper caching implementation
16
+ - Verify memory management for conversation chains
17
+ - Assess streaming implementation where applicable
18
+
19
+ ### Security
20
+ - Validate API key handling and environment variables
21
+ - Check for proper input sanitization
22
+ - Review rate limiting implementation
23
+ - Verify output validation and sanitization
24
+
25
+ ## Technical Checklist
26
+
27
+ ### LangChain.js Specific
28
+ - [ ] Proper chain composition and sequencing
29
+ - [ ] Correct prompt template formatting
30
+ - [ ] Appropriate memory implementation
31
+ - [ ] Tool configuration and validation
32
+ - [ ] Output parser implementation
33
+ - [ ] Model configuration and defaults
34
+
35
+ ### LangGraph.js Specific
36
+ - [ ] State machine definition correctness
37
+ - [ ] Edge case handling in workflows
38
+ - [ ] Proper node transitions
39
+ - [ ] State persistence strategy
40
+ - [ ] Graph visualization implementation (if applicable)
41
+
42
+ ### General Code Quality
43
+ - [ ] TypeScript type definitions
44
+ - [ ] Async/await implementation
45
+ - [ ] Error boundary definition
46
+ - [ ] Logging implementation
47
+ - [ ] Test coverage
48
+ - [ ] Documentation quality
49
+
50
+ ## Best Practices
51
+
52
+ ### Configuration
53
+ Make sure that API keys are accidentally not included into diff.
54
+
55
+ ### Common Pitfalls to Check
56
+ 1. Improper chain composition
57
+ 2. Missing error handlers
58
+ 3. Memory leaks in long-running chains
59
+ 4. Incorrect prompt engineering
60
+ 5. Inadequate rate limiting
61
+ 6. Missing type definitions
62
+ 7. Improper streaming implementation
63
+
64
+ ### Performance Optimization Points
65
+ 1. Caching strategy
66
+ 2. Batch processing implementation
67
+ 3. Connection pooling
68
+ 4. Resource cleanup
69
+ 5. Memory management
70
+
71
+ ## Testing Requirements
72
+
73
+ ### Unit Tests
74
+ - Individual chain components
75
+ - Tool implementations
76
+ - Parser functions
77
+ - State transitions
78
+
79
+ ### Integration Tests
80
+ - End-to-end workflows
81
+ - External API interactions
82
+ - Error scenarios
83
+ - State persistence
84
+
85
+ ### Load Tests
86
+ - Concurrent request handling
87
+ - Memory usage under load
88
+ - Response time benchmarks
89
+
90
+ ## Documentation Requirements
91
+
92
+ 1. Architecture overview
93
+ 2. Component interaction diagrams
94
+ 3. Configuration guide
95
+ 4. API documentation
96
+ 5. Error handling guide
97
+ 6. Performance optimization guide
98
+ 7. Deployment checklist
99
+
100
+ ## Monitoring and Observability
101
+
102
+ ### Metrics to Track
103
+ - Chain execution times
104
+ - Token usage
105
+ - Error rates
106
+ - Memory consumption
107
+ - API latencies
108
+
109
+ ### Logging Requirements
110
+ - Request/response pairs
111
+ - Error stack traces
112
+ - Performance metrics
113
+ - State transitions
114
+ - Resource usage
115
+
116
+ ---
117
+
118
+ Provide specific feedback on any areas of concern or suggestions for improvement. Please categorize your feedback (e.g., "Bug," "Suggestion," "Nitpick").
119
+
120
+ Important! In the end conclude if you would recommend to approve this PR or not. Use ✅⚠️❌ symbols to highlight your feedback appropriately.
121
+
122
+ Thank you for your thorough review!
123
+
124
+ Important! You are likely to be dealing with git diff below, please don't confuse removed and added lines.
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2025-present Andrew Kondratev
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # Gaunt Sloth Assistant
2
+ Simplistic assistant helping to do code reviews from command line based on [Langchain.js](https://github.com/langchain-ai/langchainjs)
3
+
4
+ ## Review PR
5
+ `gsloth pr 42` - review PR by PR number.
6
+ Official [GitHub cli (gh)](https://cli.github.com/) should be installed
7
+ and authenticated to have access to your project.
8
+
9
+ `gsloth pr 42 -f PROJ-1234.md` - Review providing MD file with requirements and notes.
10
+ Jira integration is in [ROADMAP](ROADMAP.md).
11
+ Currently, the easiest ***meaningful*** way to add jira description is to
12
+ open Jira XML with "Export XML" in jira and to copy `<description></description>` block.
13
+ This block contains HTML and AI understands it easily
14
+ (most importantly it understand nested lists like ul>li).
15
+
16
+ ## Review Diff
17
+ `git --no-pager diff origin/master...ffd079c134eabf18d85975f155b76d62a895cdec | gsloth review`
18
+ (May be helpful to review subset of PR)
19
+
20
+ ## Question Answering
21
+ `gsloth ask "which types of primitives are available in JavaScript?"`
22
+
23
+ `gsloth ask "Please have a look at this file" -f index.js`
24
+
25
+ ## Installation
26
+ There's no npm module yet. Do `npm install -g ./` to install local build globally to your machine.
27
+ ```
28
+ git clone https://github.com/andruhon/gaunt-sloth.git
29
+ npm install
30
+ npm install -g ./
31
+ ```
32
+
33
+ ## Configuration
34
+ There is no global configuration yet. The project you want to get reviewed needs gsloth configuration.
35
+
36
+ Add `.gsloth.preamble.review.md` to your project.
37
+ Add general description of what your project is and what do you expect from this code review.
38
+ Check [.gsloth.preamble.review.md](.gsloth.preamble.review.md) for example.
39
+
40
+ Add `.gsloth.config.js,` to your project.
41
+
42
+ **Example of .gsloth.config.js for Anthropic**
43
+ ```javascript
44
+ export async function configure(importFunction, global) {
45
+ // this is going to be imported from sloth dependencies,
46
+ // but can potentially be pulled from global node modules or from this project
47
+ // At a moment only google-vertexai and anthropic packaged with Sloth, but you can install support for any other langchain llms
48
+ const anthropic = await importFunction('@langchain/anthropic');
49
+ return {
50
+ llm: new anthropic.ChatAnthropic({
51
+ apiKey: "sk-ant-api03--YOURAPIHASH", // You should put your API hash here
52
+ model: "claude-3-5-sonnet-20241022"
53
+ })
54
+ };
55
+ }
56
+
57
+ ```
58
+
59
+ **Example of .gsloth.config.js for VertexAI**
60
+ VertexAI usually needs `gcloud auth application-default login`
61
+ (or both `gcloud auth login` and `gcloud auth application-default login`) and does not need any separate API keys.
62
+ ```javascript
63
+ export async function configure(importFunction, global) {
64
+ // this is going to be imported from sloth dependencies,
65
+ // but can potentially be pulled from global node modules or from this project
66
+ // At a moment only google-vertexai and anthropic packaged with Sloth, but you can install support for any other langchain llms
67
+ // Note: for vertex AI you likely to need to do `gcloud auth login`
68
+ const vertexAi = await importFunction('@langchain/google-vertexai');
69
+ return {
70
+ llm: new vertexAi.ChatVertexAI({
71
+ model: "gemini-2.5-pro-exp-03-25", // Consider checking for latest recommended model versions
72
+ temperature: 0,
73
+ //// Other parameters might be relevant depending on Vertex AI API updates.
74
+ //// The project is not in the interface, but it is in documentation and it seems to work.
75
+ // project: 'your-cool-google-cloud-project',
76
+ })
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## License
82
+ License is [MIT](https://opensource.org/license/mit). See [LICENSE](LICENSE)
83
+
package/ROADMAP.md ADDED
@@ -0,0 +1,34 @@
1
+ # Gaunt Sloth Assistant roadmap
2
+
3
+
4
+ ## 1.0.0
5
+ Doing the following below and making it work stably should be sufficient to call it version 1.
6
+
7
+ ### Add tests and gain reasonable coverage
8
+ ### Add project init command
9
+ Add a command to init certain model in certain project, for example `gsloth init gemini`
10
+ or `gsloth init` and select one of the provided options.
11
+ -[x] VertexAI
12
+ -[x] Anthropic
13
+ -[ ] Groq
14
+ -[ ] Local LLm
15
+ ### Allow global configuration
16
+ ### Streamline and stabilize configuration
17
+ ### Add JIRA legacy token integration plugin
18
+ ### Teach assistant to identify important files and include their contents into prompt
19
+ The idea is to ask smaller model like flash to find important files from diff then pick them up and include into prompt.
20
+ ### Teach assistant to access provided public web links
21
+ ### Consider adding an option to always include certain source code files into prompt
22
+ ### Test with Groq
23
+ ### Add general chat command
24
+
25
+ ## Extra stuff for later
26
+
27
+ ### Modify local files within project (gsloth code)
28
+ Expected guardrails:
29
+ - Make sure it does not go outside project directory
30
+ - Make sure project has git (or later other vcs)
31
+ - Make sure that local changes are stashed if any present
32
+
33
+ ### Index project into Vector DB
34
+
package/index.js ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ import {Argument, Command} from 'commander';
3
+ import {dirname} from 'node:path';
4
+ import {displayError, displayInfo} from "./src/consoleUtils.js";
5
+ import {
6
+ availableDefaultConfigs,
7
+ createProjectConfig,
8
+ slothContext,
9
+ USER_PROJECT_REVIEW_PREAMBLE
10
+ } from "./src/config.js";
11
+ import {fileURLToPath} from "url";
12
+ import {getSlothVersion, readFileFromCurrentDir, readStdin} from "./src/utils.js";
13
+ import {getPrDiff, readInternalPreamble, readPreamble} from "./src/prompt.js";
14
+
15
+ const program = new Command();
16
+
17
+ slothContext.currentDir = process.cwd();
18
+ slothContext.installDir = dirname(fileURLToPath(import.meta.url))
19
+
20
+ program
21
+ .name('gsloth')
22
+ .description('Gaunt Sloth Assistant reviewing your PRs')
23
+ .version(getSlothVersion());
24
+
25
+ program.command('init')
26
+ .description('Initialize the Gaunt Sloth Assistant in your project. This will write necessary config files.')
27
+ .addArgument(new Argument('<type>', 'Config type').choices(availableDefaultConfigs))
28
+ .action(async (config) => {
29
+ await createProjectConfig(config);
30
+ });
31
+
32
+ program.command('pr')
33
+ .description('Review a PR in current git directory (assuming that GH cli is installed and authenticated for current project')
34
+ .argument('<prNumber>', 'PR number to review')
35
+ .option('-f, --file <file>', 'Input file. Context of this file will be added BEFORE the diff')
36
+ // TODO add option consuming extra message as argument
37
+ .action(async (pr, options) => {
38
+ if (slothContext.stdin) {
39
+ displayError('`gsloth pr` does not expect stdin, use `gsloth review` instead');
40
+ return;
41
+ }
42
+ displayInfo('Starting review of PR', pr);
43
+ const diff = await getPrDiff(pr);
44
+ const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
45
+ const content = [diff];
46
+ if (options.file) {
47
+ content.push(readFileFromCurrentDir(options.file));
48
+ }
49
+ const { review } = await import('./src/codeReview.js');
50
+ await review('sloth-PR-review-'+pr, preamble.join("\n"), content.join("\n"));
51
+ });
52
+
53
+ program.command('review')
54
+ .description('Review provided diff or other content')
55
+ .option('-f, --file <file>', 'Input file. Context of this file will be added BEFORE the diff')
56
+ // TODO add option consuming extra message as argument
57
+ .action(async (options) => {
58
+ if (!slothContext.stdin || options.file) {
59
+ displayError('gsloth review expects stdin with github diff stdin or a file');
60
+ return
61
+ }
62
+ const preamble = [readInternalPreamble(), readPreamble(USER_PROJECT_REVIEW_PREAMBLE)];
63
+ const content = [];
64
+ if (slothContext.stdin) {
65
+ content.push(slothContext.stdin);
66
+ }
67
+ if (options.file) {
68
+ content.push(readFileFromCurrentDir(options.file));
69
+ }
70
+ const { review } = await import('./src/codeReview.js');
71
+ await review('sloth-DIFF-review', preamble.join("\n"), content.join("\n"));
72
+ });
73
+
74
+ program.command('ask')
75
+ .description('Ask a question')
76
+ .argument('<message>', 'A message')
77
+ .option('-f, --file <file>', 'Input file. Context of this file will be added BEFORE the diff')
78
+ // TODO add option consuming extra message as argument
79
+ .action(async (message, options) => {
80
+ const preamble = [readInternalPreamble()];
81
+ const content = [message];
82
+ if (options.file) {
83
+ content.push(readFileFromCurrentDir(options.file));
84
+ }
85
+ const { askQuestion } = await import('./src/questionAnswering.js');
86
+ await askQuestion('sloth-ASK', preamble.join("\n"), content.join("\n"));
87
+ });
88
+
89
+ // TODO add general interactive chat command
90
+
91
+ readStdin(program);
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "gaunt-sloth-assistant",
3
+ "version": "0.0.3",
4
+ "description": "",
5
+ "license": "MIT",
6
+ "author": "Andrew Kondratev",
7
+ "type": "module",
8
+ "main": "index.js",
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1",
11
+ "test-run": "node --trace-deprecation index.js ask \"status check\""
12
+ },
13
+ "bin": {
14
+ "gsloth": "index.js"
15
+ },
16
+ "dependencies": {
17
+ "@eslint/js": "^9.24.0",
18
+ "@langchain/anthropic": "^0.3.17",
19
+ "@langchain/core": "^0.3.43",
20
+ "@langchain/google-vertexai": "^0.2.3",
21
+ "@langchain/langgraph": "^0.2.62",
22
+ "@types/node": "^22.14.1",
23
+ "chalk": "^5.4.1",
24
+ "commander": "^13.1.0",
25
+ "uuid": "^11.1.0"
26
+ }
27
+ }
@@ -0,0 +1,71 @@
1
+ import {
2
+ END,
3
+ MemorySaver,
4
+ MessagesAnnotation,
5
+ START,
6
+ StateGraph,
7
+ } from "@langchain/langgraph";
8
+ import { writeFileSync } from "node:fs";
9
+ import path from "node:path";
10
+ import {initConfig, slothContext} from "./config.js";
11
+ import { display, displayError, displaySuccess } from "./consoleUtils.js";
12
+ import { fileSafeLocalDate, toFileSafeString } from "./utils.js";
13
+
14
+ await initConfig();
15
+
16
+ export async function review(source, preamble, diff) {
17
+ // This node receives the current state (messages) and invokes the LLM
18
+ const callModel = async (state) => {
19
+ // state.messages will contain the list including the system preamble and user diff
20
+ const response = await slothContext.config.llm.invoke(state.messages);
21
+ // MessagesAnnotation expects the node to return the new message(s) to be added to the state.
22
+ // Wrap the response in an array if it's a single message object.
23
+ return { messages: response };
24
+ };
25
+
26
+ // Define the graph structure with MessagesAnnotation state
27
+ const workflow = new StateGraph(MessagesAnnotation)
28
+ // Define the node and edge
29
+ .addNode("model", callModel)
30
+ .addEdge(START, "model") // Start at the 'model' node
31
+ .addEdge("model", END); // End after the 'model' node completes
32
+
33
+ // Set up memory (optional but good practice for potential future multi-turn interactions)
34
+ const memory = new MemorySaver(); // TODO extract to config
35
+
36
+ // Compile the workflow into a runnable app
37
+ const app = workflow.compile({ checkpointer: memory });
38
+
39
+ // Construct the initial the messages including the preamble as a system message
40
+ const messages = [
41
+ {
42
+ role: "system",
43
+ content: preamble, // The preamble goes here
44
+ },
45
+ {
46
+ role: "user",
47
+ content: diff, // The code diff goes here
48
+ },
49
+ ];
50
+
51
+ process.stdout.write("Reviewing.");
52
+ const progress = setInterval(() => process.stdout.write('.'), 1000);
53
+ const output = await app.invoke({messages}, slothContext.session);
54
+ const filePath = path.resolve(process.cwd(), toFileSafeString(source)+'-'+fileSafeLocalDate()+".md");
55
+ display(`writing ${filePath}`);
56
+ // FIXME this looks ugly, there should be other way
57
+ const outputContent = output.messages[output.messages.length - 1].content;
58
+ clearInterval(progress);
59
+ console.log('');
60
+ // TODO highlight LLM output with something like Prism.JS (maybe system emoj are enough ✅⚠️❌)
61
+ display(outputContent);
62
+ try {
63
+ writeFileSync(filePath, outputContent);
64
+ displaySuccess(`This report can be found in ${filePath}`);
65
+ } catch (error) {
66
+ displayError(`Failed to write review to file: ${filePath}`);
67
+ displayError(error.message);
68
+ // Consider if you want to exit or just log the error
69
+ // process.exit(1);
70
+ }
71
+ }
package/src/config.js ADDED
@@ -0,0 +1,58 @@
1
+ import path, {dirname} from "node:path";
2
+ import url from "node:url";
3
+ import {v4 as uuidv4} from "uuid";
4
+ import {display, displayError, displayInfo, displaySuccess, displayWarning} from "./consoleUtils.js";
5
+ import {fileURLToPath} from "url";
6
+ import {write, writeFileSync, existsSync} from "node:fs";
7
+ import {writeFileIfNotExistsWithMessages} from "./utils.js";
8
+
9
+ export const USER_PROJECT_CONFIG_FILE = '.gsloth.config.js'
10
+ export const SLOTH_INTERNAL_PREAMBLE = '.gsloth.preamble.internal.md';
11
+ export const USER_PROJECT_REVIEW_PREAMBLE = '.gsloth.preamble.review.md';
12
+
13
+ export const availableDefaultConfigs = ['vertexai', 'anthropic'];
14
+
15
+ export const slothContext = {
16
+ /**
17
+ * Directory where the sloth is installed.
18
+ * index.js should set up this.
19
+ */
20
+ installDir: null,
21
+ /**
22
+ * Directory where the sloth has been invoked. Usually user's project root.
23
+ * index.js should set up this.
24
+ */
25
+ currentDir: null,
26
+ config: null,
27
+ stdin: '',
28
+ session: {configurable: {thread_id: uuidv4()}}
29
+ };
30
+
31
+ export async function initConfig() {
32
+ const configFileUrl = url.pathToFileURL(path.join(process.cwd(), USER_PROJECT_CONFIG_FILE));
33
+ const {configure} = await import(configFileUrl);
34
+ const config = await configure((module) => import(module));
35
+ slothContext.config = {...config};
36
+ }
37
+
38
+ export async function createProjectConfig(configType) {
39
+ displayInfo(`Setting up your project\n`);
40
+ writeProjectReviewPreamble();
41
+ displayWarning(`Make sure you add as much detail as possible to your ${USER_PROJECT_REVIEW_PREAMBLE}.\n`)
42
+
43
+ displayInfo(`Creating project config for ${configType}`);
44
+ const vendorConfig = await import(`./configs/${configType}.js`);
45
+ vendorConfig.init(USER_PROJECT_CONFIG_FILE, slothContext);
46
+ }
47
+
48
+ function writeProjectReviewPreamble() {
49
+ let reviewPreamblePath = path.join(slothContext.currentDir, USER_PROJECT_REVIEW_PREAMBLE);
50
+ writeFileIfNotExistsWithMessages(
51
+ reviewPreamblePath,
52
+ 'You are doing generic code review.\n'
53
+ + 'Important! Please remind user to prepare proper AI preamble in ' +
54
+ + USER_PROJECT_REVIEW_PREAMBLE
55
+ + 'for this project. Use decent amount of ⚠️ to highlight lack of config. '
56
+ + 'Explicitly mention `'+ USER_PROJECT_REVIEW_PREAMBLE + '`.'
57
+ );
58
+ }
@@ -0,0 +1,25 @@
1
+ import {writeFileIfNotExistsWithMessages} from "../utils.js";
2
+ import path from "node:path";
3
+ import {displayWarning} from "../consoleUtils.js";
4
+ import {USER_PROJECT_CONFIG_FILE} from "../config.js";
5
+
6
+ const content = `/* eslint-disable */
7
+ export async function configure(importFunction, global) {
8
+ // this is going to be imported from sloth dependencies,
9
+ // but can potentially be pulled from global node modules or from this project
10
+ // At a moment only google-vertexai and anthropic packaged with Sloth, but you can install support for any other langchain llms
11
+ const anthropic = await importFunction('@langchain/anthropic');
12
+ return {
13
+ llm: new anthropic.ChatAnthropic({
14
+ apiKey: "sk-ant-api--YOUR_API_HASH", // You should put your API hash here
15
+ model: "claude-3-5-sonnet-20241022" // Don't forget to check new models availability.
16
+ })
17
+ };
18
+ }
19
+ `;
20
+
21
+ export function init(configFileName, context) {
22
+ path.join(context.currentDir, configFileName);
23
+ writeFileIfNotExistsWithMessages(configFileName, content);
24
+ displayWarning(`You need to update your ${USER_PROJECT_CONFIG_FILE} to add your Anthropic API key.`);
25
+ }
@@ -0,0 +1,26 @@
1
+ import {writeFileIfNotExistsWithMessages} from "../utils.js";
2
+ import path from "node:path";
3
+ import {displayWarning} from "../consoleUtils.js";
4
+
5
+ const content = `/* eslint-disable */
6
+ export async function configure(importFunction, global) {
7
+ // this is going to be imported from sloth dependencies,
8
+ // but can potentially be pulled from global node modules or from this project
9
+ const vertexAi = await importFunction('@langchain/google-vertexai');
10
+ return {
11
+ llm: new vertexAi.ChatVertexAI({
12
+ model: "gemini-2.5-pro-exp-03-25", // Consider checking for latest recommended model versions
13
+ // temperature: 0,
14
+ // Other parameters might be relevant depending on Vertex AI API updates
15
+ // The project is not in the interface, but it is in documentation (seems to work unimarket-development as well)
16
+ // project: 'your-cool-gcloud-project'
17
+ })
18
+ }
19
+ }
20
+ `;
21
+
22
+ export function init(configFileName, context) {
23
+ path.join(context.currentDir, configFileName);
24
+ writeFileIfNotExistsWithMessages(configFileName, content);
25
+ displayWarning("For Google VertexAI you likely to need to do `gcloud auth login` and `gcloud auth application-default login`.");
26
+ }
@@ -0,0 +1,21 @@
1
+ import chalk from 'chalk';
2
+
3
+ export function displayError (message) {
4
+ console.error(chalk.red(message));
5
+ }
6
+
7
+ export function displayWarning (message) {
8
+ console.error(chalk.yellow(message));
9
+ }
10
+
11
+ export function displaySuccess (message) {
12
+ console.error(chalk.green(message));
13
+ }
14
+
15
+ export function displayInfo (message) {
16
+ console.error(chalk.blue(message));
17
+ }
18
+
19
+ export function display(message) {
20
+ console.log(message);
21
+ }
package/src/prompt.js ADDED
@@ -0,0 +1,25 @@
1
+ import {resolve} from "node:path";
2
+ import {SLOTH_INTERNAL_PREAMBLE, slothContext} from "./config.js";
3
+ import {readFileSyncWithMessages, spawnCommand} from "./utils.js";
4
+
5
+ export function readInternalPreamble() {
6
+ const filePath = resolve(slothContext.installDir, SLOTH_INTERNAL_PREAMBLE);
7
+ return readFileSyncWithMessages(filePath, "Error reading internal preamble file at:")
8
+ }
9
+
10
+ export function readPreamble(preambleFilename) {
11
+ const filePath = resolve(slothContext.currentDir, preambleFilename);
12
+ return readFileSyncWithMessages(
13
+ filePath,
14
+ "Error reading preamble file at:",
15
+ "Consider running `gsloth init` to set up your project. Check `gsloth init --help` to see options."
16
+ )
17
+ }
18
+
19
+ /**
20
+ * This function expects https://cli.github.com/ to be installed and authenticated.
21
+ */
22
+ export async function getPrDiff(pr) {
23
+ // TODO makes sense to check if gh is available and authenticated
24
+ return spawnCommand('gh', ['pr', 'diff', pr], 'Loading PR diff...', 'Loaded PR diff.');
25
+ }
@@ -0,0 +1,68 @@
1
+ import {
2
+ END,
3
+ MemorySaver,
4
+ MessagesAnnotation,
5
+ START,
6
+ StateGraph,
7
+ } from "@langchain/langgraph";
8
+ import { writeFileSync } from "node:fs";
9
+ import path from "node:path";
10
+ import {initConfig, slothContext} from "./config.js";
11
+ import { display, displayError, displaySuccess } from "./consoleUtils.js";
12
+ import { fileSafeLocalDate, toFileSafeString } from "./utils.js";
13
+
14
+ await initConfig();
15
+
16
+ export async function askQuestion(source, preamble, content) {
17
+ // This node receives the current state (messages) and invokes the LLM
18
+ const callModel = async (state) => {
19
+ // state.messages will contain the list including the system preamble and user diff
20
+ const response = await slothContext.config.llm.invoke(state.messages);
21
+ // MessagesAnnotation expects the node to return the new message(s) to be added to the state.
22
+ // Wrap the response in an array if it's a single message object.
23
+ return { messages: response };
24
+ };
25
+
26
+ // Define the graph structure with MessagesAnnotation state
27
+ const workflow = new StateGraph(MessagesAnnotation)
28
+ // Define the node and edge
29
+ .addNode("model", callModel)
30
+ .addEdge(START, "model") // Start at the 'model' node
31
+ .addEdge("model", END); // End after the 'model' node completes
32
+
33
+ // Set up memory (optional but good practice for potential future multi-turn interactions)
34
+ const memory = new MemorySaver();
35
+
36
+ // Compile the workflow into a runnable app
37
+ const app = workflow.compile({ checkpointer: memory });
38
+
39
+ // Construct the initial the messages including the preamble as a system message
40
+ const messages = [
41
+ {
42
+ role: "system",
43
+ content: preamble, // The preamble goes here
44
+ },
45
+ {
46
+ role: "user",
47
+ content, // The code diff goes here
48
+ },
49
+ ];
50
+
51
+ display("Thinking...");
52
+ const output = await app.invoke({messages}, slothContext.session);
53
+ // FIXME this looks ugly, there should be other way
54
+ const outputContent = output.messages[output.messages.length - 1].content;
55
+ const filePath = path.resolve(process.cwd(), toFileSafeString(source)+'-'+fileSafeLocalDate()+".md");
56
+ display(`writing ${filePath}`);
57
+ // TODO highlight LLM output with something like Prism.JS
58
+ display(outputContent);
59
+ try {
60
+ writeFileSync(filePath, outputContent);
61
+ displaySuccess(`This report can be found in ${filePath}`);
62
+ } catch (error) {
63
+ displayError(`Failed to write review to file: ${filePath}`);
64
+ displayError(error.message);
65
+ // Consider if you want to exit or just log the error
66
+ // process.exit(1);
67
+ }
68
+ }
package/src/utils.js ADDED
@@ -0,0 +1,94 @@
1
+ import {display, displayError, displaySuccess, displayWarning} from "./consoleUtils.js";
2
+ import {existsSync, readFileSync, writeFileSync} from "node:fs";
3
+ import {slothContext, USER_PROJECT_REVIEW_PREAMBLE} from "./config.js";
4
+ import {resolve} from "node:path";
5
+ import {spawn} from "node:child_process";
6
+
7
+ export function toFileSafeString(string) {
8
+ return string.replace(/[^A-Za-z0-9]/g, '-');
9
+ }
10
+
11
+ export function fileSafeLocalDate() {
12
+ const date = new Date();
13
+ const offsetMs = date.getTimezoneOffset() * 60 * 1000;
14
+ const msLocal = date.getTime() - offsetMs;
15
+ const dateLocal = new Date(msLocal);
16
+ const iso = dateLocal.toISOString();
17
+ const isoLocal = iso.slice(0, 19);
18
+ return toFileSafeString(isoLocal);
19
+ }
20
+
21
+ export function readFileFromCurrentDir(fileName) {
22
+ const filePath = resolve(slothContext.currentDir, fileName);
23
+ display(`Reading file ${fileName}...`);
24
+ return readFileSyncWithMessages(filePath);
25
+ }
26
+
27
+ export function writeFileIfNotExistsWithMessages(filePath, content) {
28
+ display(`checking ${filePath} existence`);
29
+ if (!existsSync(filePath)) {
30
+ writeFileSync(filePath, content);
31
+ displaySuccess(`Created ${filePath}`);
32
+ } else {
33
+ displayWarning(`${filePath} already exists`);
34
+ }
35
+ }
36
+
37
+ export function readFileSyncWithMessages(filePath, errorMessageIn, noFileMessage) {
38
+ const errorMessage = errorMessageIn ?? 'Error reading file at: ';
39
+ try {
40
+ return readFileSync(filePath, { encoding: 'utf8' });
41
+ } catch (error) {
42
+ displayError(errorMessage + filePath);
43
+ if (error.code === 'ENOENT') {
44
+ displayWarning(noFileMessage ?? 'Please ensure the file exists.');
45
+ } else {
46
+ displayError(error.message);
47
+ }
48
+ process.exit(1); // Exit gracefully after error
49
+ }
50
+ }
51
+
52
+ export function readStdin(program) {
53
+ if(process.stdin.isTTY) {
54
+ program.parse();
55
+ } else {
56
+ // Support piping diff into gsloth
57
+ process.stdout.write('reading STDIN.');
58
+ process.stdin.on('readable', function() {
59
+ const chunk = this.read();
60
+ process.stdout.write('.');
61
+ if (chunk !== null) {
62
+ slothContext.stdin += chunk;
63
+ }
64
+ });
65
+ process.stdin.on('end', function() {
66
+ console.log('');
67
+ program.parse(process.argv);
68
+ });
69
+ }
70
+ }
71
+
72
+ export async function spawnCommand(command, args, progressMessage, successMessage) {
73
+ return new Promise((resolve, reject) => {
74
+ const out = {stdout: ''};
75
+ const spawned = spawn(command, args);
76
+ spawned.stdout.on('data', async (stdoutChunk) => {
77
+ display(progressMessage);
78
+ out.stdout += stdoutChunk.toString();
79
+ });
80
+ spawned.on('error', (err) => {
81
+ reject(err);
82
+ })
83
+ spawned.on('close', (code) => {
84
+ display(successMessage);
85
+ resolve(out.stdout);
86
+ });
87
+ });
88
+ }
89
+
90
+ export function getSlothVersion() {
91
+ const jsonPath = resolve(slothContext.installDir, 'package.json');
92
+ const projectJson = readFileSync(jsonPath, { encoding: 'utf8' });
93
+ return JSON.parse(projectJson).version;
94
+ }
@@ -0,0 +1 @@
1
+ the `dfsdf` is a magic string which fixes our custom transpiler. We should unfortunately use it.