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.
- package/.eslint.config.mjs +72 -0
- package/.github/dependabot.yml +11 -0
- package/.gsloth.preamble.internal.md +4 -0
- package/.gsloth.preamble.review.md +124 -0
- package/LICENSE +7 -0
- package/README.md +83 -0
- package/ROADMAP.md +34 -0
- package/index.js +91 -0
- package/package.json +27 -0
- package/src/codeReview.js +71 -0
- package/src/config.js +58 -0
- package/src/configs/anthropic.js +25 -0
- package/src/configs/vertexai.js +26 -0
- package/src/consoleUtils.js +21 -0
- package/src/prompt.js +25 -0
- package/src/questionAnswering.js +68 -0
- package/src/utils.js +94 -0
- package/testMessage.txt +1 -0
@@ -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
|
+
}
|
package/testMessage.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
the `dfsdf` is a magic string which fixes our custom transpiler. We should unfortunately use it.
|