jstar-reviewer 2.4.0 ā 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/jstar.js +21 -3
- package/dist/scripts/chat.js +5 -1
- package/dist/scripts/core/debate.js +4 -7
- package/dist/scripts/detective.js +24 -8
- package/dist/scripts/gemini-embedding.js +7 -6
- package/dist/scripts/indexer.js +36 -17
- package/dist/scripts/session.js +11 -11
- package/package.json +10 -9
- package/scripts/chat.ts +5 -1
- package/scripts/core/debate.ts +4 -3
- package/scripts/detective.ts +26 -8
- package/scripts/gemini-embedding.ts +7 -6
- package/scripts/indexer.ts +42 -17
- package/scripts/mock-llm.ts +2 -2
- package/scripts/session.ts +11 -11
package/bin/jstar.js
CHANGED
|
@@ -29,7 +29,7 @@ function log(msg) {
|
|
|
29
29
|
|
|
30
30
|
function printHelp() {
|
|
31
31
|
log(`
|
|
32
|
-
${COLORS.bold}š J-Star Reviewer v2.4.
|
|
32
|
+
${COLORS.bold}š J-Star Reviewer v2.4.2${COLORS.reset}
|
|
33
33
|
|
|
34
34
|
${COLORS.dim}AI-powered code review with local embeddings${COLORS.reset}
|
|
35
35
|
|
|
@@ -40,20 +40,34 @@ ${COLORS.bold}COMMANDS:${COLORS.reset}
|
|
|
40
40
|
${COLORS.green}init${COLORS.reset} Index the current codebase (build the brain)
|
|
41
41
|
${COLORS.green}review${COLORS.reset} Review staged git changes
|
|
42
42
|
${COLORS.green}chat${COLORS.reset} Resume an interactive session from the last review
|
|
43
|
+
${COLORS.green}detect${COLORS.reset} Run static analysis (Detective Engine)
|
|
43
44
|
${COLORS.green}setup${COLORS.reset} Create .env.example and .jstar/ in current directory
|
|
44
45
|
|
|
45
46
|
${COLORS.bold}OPTIONS:${COLORS.reset}
|
|
46
47
|
${COLORS.yellow}--json${COLORS.reset} Output machine-readable JSON (for CI/CD)
|
|
47
48
|
${COLORS.yellow}--headless${COLORS.reset} Enable stdin/stdout protocol (for AI agents)
|
|
48
49
|
|
|
50
|
+
${COLORS.bold}REVIEW OPTIONS:${COLORS.reset}
|
|
51
|
+
${COLORS.yellow}--pr${COLORS.reset} Review a Pull Request (compare against main/base)
|
|
52
|
+
${COLORS.yellow}--base <br>${COLORS.reset} Specify base branch for PR review (default: main)
|
|
53
|
+
${COLORS.yellow}--last${COLORS.reset} Review the very last commit (HEAD~1..HEAD)
|
|
54
|
+
${COLORS.yellow}--commit <h>${COLORS.reset} Review a specific commit hash
|
|
55
|
+
${COLORS.yellow}--range <a> <b>${COLORS.reset} Review diff between two refs
|
|
56
|
+
|
|
49
57
|
${COLORS.bold}EXAMPLES:${COLORS.reset}
|
|
50
58
|
${COLORS.dim}# First time setup${COLORS.reset}
|
|
51
59
|
jstar init
|
|
52
60
|
|
|
53
|
-
${COLORS.dim}# Review staged changes${COLORS.reset}
|
|
54
|
-
git add .
|
|
61
|
+
${COLORS.dim}# Review staged changes (default)${COLORS.reset}
|
|
55
62
|
jstar review
|
|
56
63
|
|
|
64
|
+
${COLORS.dim}# Review a Pull Request${COLORS.reset}
|
|
65
|
+
jstar review --pr
|
|
66
|
+
jstar review --pr --base develop
|
|
67
|
+
|
|
68
|
+
${COLORS.dim}# Review the last commit${COLORS.reset}
|
|
69
|
+
jstar review --last
|
|
70
|
+
|
|
57
71
|
${COLORS.dim}# JSON output for CI${COLORS.reset}
|
|
58
72
|
jstar review --json > report.json
|
|
59
73
|
|
|
@@ -225,6 +239,10 @@ switch (command) {
|
|
|
225
239
|
case 'chat':
|
|
226
240
|
runScript('chat.ts');
|
|
227
241
|
break;
|
|
242
|
+
case 'detect':
|
|
243
|
+
case 'det':
|
|
244
|
+
runScript('detective.ts');
|
|
245
|
+
break;
|
|
228
246
|
case 'setup':
|
|
229
247
|
createSetupFiles();
|
|
230
248
|
break;
|
package/dist/scripts/chat.js
CHANGED
|
@@ -58,7 +58,11 @@ async function loadSession() {
|
|
|
58
58
|
}
|
|
59
59
|
catch (e) {
|
|
60
60
|
if (e.code === 'ENOENT') {
|
|
61
|
-
return null; // File doesn't exist
|
|
61
|
+
return null; // File doesn't exist, start fresh
|
|
62
|
+
}
|
|
63
|
+
if (e instanceof SyntaxError) {
|
|
64
|
+
logger_1.Logger.warn(chalk_1.default.yellow("ā ļø Session file corrupted. Starting fresh."));
|
|
65
|
+
return null;
|
|
62
66
|
}
|
|
63
67
|
logger_1.Logger.error(`Failed to load session: ${e.message}`);
|
|
64
68
|
return null;
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.debateIssue = debateIssue;
|
|
7
4
|
const llamaindex_1 = require("llamaindex");
|
|
8
5
|
const ai_1 = require("ai");
|
|
9
6
|
const groq_1 = require("@ai-sdk/groq");
|
|
10
7
|
const config_1 = require("../config");
|
|
11
|
-
const
|
|
8
|
+
const logger_1 = require("../utils/logger");
|
|
12
9
|
const groq = (0, groq_1.createGroq)({ apiKey: process.env.GROQ_API_KEY });
|
|
13
10
|
async function debateIssue(issueTitle, issueDescription, fileName, userArgument, index) {
|
|
14
11
|
// Validate API key before making any calls
|
|
15
12
|
if (!process.env.GROQ_API_KEY) {
|
|
16
13
|
throw new Error("GROQ_API_KEY is required for debate mode. Please set it in your .env.local file.");
|
|
17
14
|
}
|
|
18
|
-
|
|
15
|
+
logger_1.Logger.dim(" š§ Thinking... (Consulting the Brain)");
|
|
19
16
|
// 1. Extract keywords/context
|
|
20
17
|
const query = `${userArgument} ${issueTitle}`;
|
|
21
18
|
// 2. Retrieve new context
|
|
@@ -23,11 +20,11 @@ async function debateIssue(issueTitle, issueDescription, fileName, userArgument,
|
|
|
23
20
|
const contextNodes = await retriever.retrieve(query);
|
|
24
21
|
const newContext = contextNodes.map(n => n.node.getContent(llamaindex_1.MetadataMode.NONE)).join("\n\n").slice(0, 2000);
|
|
25
22
|
if (newContext.length >= 2000) {
|
|
26
|
-
|
|
23
|
+
logger_1.Logger.warn(" ā ļø Context truncated to 2000 chars");
|
|
27
24
|
}
|
|
28
25
|
const sources = contextNodes.map(n => n.node.metadata?.['file_name']).filter(Boolean).join(', ');
|
|
29
26
|
if (sources) {
|
|
30
|
-
|
|
27
|
+
logger_1.Logger.dim(` š Found relevant context from: ${sources}`);
|
|
31
28
|
}
|
|
32
29
|
// 3. Ask the Judge
|
|
33
30
|
const systemPrompt = `You are a Senior Code Reviewer in a debate with a developer.
|
|
@@ -52,7 +52,8 @@ const RULES = [
|
|
|
52
52
|
id: 'ARCH-001',
|
|
53
53
|
severity: 'medium',
|
|
54
54
|
message: 'Avoid using console.log in production code',
|
|
55
|
-
pattern: /console\.log\(
|
|
55
|
+
pattern: /console\.log\(/,
|
|
56
|
+
excludePattern: /(bin[\\/]jstar\.js|scripts[\\/]utils[\\/]logger\.ts|setup\.js|test[\\/])/
|
|
56
57
|
},
|
|
57
58
|
];
|
|
58
59
|
// File-level rules that check the whole content
|
|
@@ -60,15 +61,17 @@ const FILE_RULES = [
|
|
|
60
61
|
{
|
|
61
62
|
id: 'ARCH-002',
|
|
62
63
|
severity: 'high',
|
|
63
|
-
message: 'Next.js "use client" must be at the very top of the file',
|
|
64
|
-
pattern: /^(?!['"]use client['"]).*['"]use client['"]/s,
|
|
65
|
-
filePattern: /\.tsx
|
|
64
|
+
message: 'Next.js "use client" must be at the very top of the file (before imports)',
|
|
65
|
+
pattern: /^(?!(?:\s*|(?:\/\/[^\n]*\n)|(?:\/\*[\s\S]*?\*\/))*['"]use client['"]).*['"]use client['"]/s,
|
|
66
|
+
filePattern: /\.tsx?$/,
|
|
67
|
+
excludePattern: /(scripts|test)[\\/]/
|
|
66
68
|
}
|
|
67
69
|
];
|
|
68
70
|
class Detective {
|
|
69
|
-
constructor(directory) {
|
|
71
|
+
constructor(directory, options = {}) {
|
|
70
72
|
this.directory = directory;
|
|
71
73
|
this.violations = [];
|
|
74
|
+
this.includeBuildFiles = options.includeBuildFiles ?? false;
|
|
72
75
|
}
|
|
73
76
|
async scan() {
|
|
74
77
|
this.walk(this.directory);
|
|
@@ -82,9 +85,12 @@ class Detective {
|
|
|
82
85
|
const filePath = path.join(dir, file);
|
|
83
86
|
const stat = fs.statSync(filePath);
|
|
84
87
|
if (stat.isDirectory()) {
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
// Ignore common build/config directories
|
|
89
|
+
const ignoredDirs = ['node_modules', '.git', '.jstar', 'dist', 'coverage', '.next'];
|
|
90
|
+
if (!this.includeBuildFiles && ignoredDirs.includes(file)) {
|
|
91
|
+
continue;
|
|
87
92
|
}
|
|
93
|
+
this.walk(filePath);
|
|
88
94
|
}
|
|
89
95
|
else {
|
|
90
96
|
this.checkFile(filePath);
|
|
@@ -94,12 +100,17 @@ class Detective {
|
|
|
94
100
|
checkFile(filePath) {
|
|
95
101
|
if (!filePath.match(/\.(ts|tsx|js|jsx)$/))
|
|
96
102
|
return;
|
|
103
|
+
// Skip .d.ts files
|
|
104
|
+
if (filePath.endsWith('.d.ts'))
|
|
105
|
+
return;
|
|
97
106
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
98
107
|
const lines = content.split('\n');
|
|
99
108
|
// Line-based rules
|
|
100
109
|
for (const rule of RULES) {
|
|
101
110
|
if (rule.filePattern && !filePath.match(rule.filePattern))
|
|
102
111
|
continue;
|
|
112
|
+
if (rule.excludePattern && filePath.match(rule.excludePattern))
|
|
113
|
+
continue;
|
|
103
114
|
lines.forEach((line, index) => {
|
|
104
115
|
if (rule.pattern.test(line)) {
|
|
105
116
|
this.addViolation(filePath, index + 1, rule);
|
|
@@ -110,6 +121,8 @@ class Detective {
|
|
|
110
121
|
for (const rule of FILE_RULES) {
|
|
111
122
|
if (rule.filePattern && !filePath.match(rule.filePattern))
|
|
112
123
|
continue;
|
|
124
|
+
if (rule.excludePattern && filePath.match(rule.excludePattern))
|
|
125
|
+
continue;
|
|
113
126
|
if (rule.pattern.test(content)) {
|
|
114
127
|
this.addViolation(filePath, 1, rule);
|
|
115
128
|
}
|
|
@@ -145,7 +158,10 @@ class Detective {
|
|
|
145
158
|
exports.Detective = Detective;
|
|
146
159
|
// CLI Integration
|
|
147
160
|
if (require.main === module) {
|
|
148
|
-
const
|
|
161
|
+
const args = process.argv.slice(2);
|
|
162
|
+
const includeBuildFiles = args.includes('--all');
|
|
163
|
+
// Scan current directory by default
|
|
164
|
+
const detective = new Detective(process.cwd(), { includeBuildFiles });
|
|
149
165
|
detective.scan();
|
|
150
166
|
detective.report();
|
|
151
167
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GeminiEmbedding = void 0;
|
|
4
4
|
const generative_ai_1 = require("@google/generative-ai");
|
|
5
|
+
const logger_1 = require("./utils/logger");
|
|
5
6
|
class GeminiEmbedding {
|
|
6
7
|
constructor() {
|
|
7
8
|
// Stubs for BaseEmbedding compliance
|
|
@@ -27,7 +28,7 @@ class GeminiEmbedding {
|
|
|
27
28
|
if (e.message.includes("fetch failed") || e.message.includes("network")) {
|
|
28
29
|
retries++;
|
|
29
30
|
const waitTime = Math.pow(2, retries) * 1000;
|
|
30
|
-
|
|
31
|
+
logger_1.Logger.warn(`ā ļø Network error. Retrying in ${waitTime / 1000}s... (${retries}/${maxRetries})`);
|
|
31
32
|
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
32
33
|
}
|
|
33
34
|
else {
|
|
@@ -42,7 +43,7 @@ class GeminiEmbedding {
|
|
|
42
43
|
}
|
|
43
44
|
async getTextEmbeddings(texts) {
|
|
44
45
|
const embeddings = [];
|
|
45
|
-
|
|
46
|
+
logger_1.Logger.info(`Creating embeddings for ${texts.length} chunks (Batching to avoid rate limits)...`);
|
|
46
47
|
// Process in smaller batches with delay
|
|
47
48
|
const BATCH_SIZE = 1; // Strict serial for safety on free tier
|
|
48
49
|
const DELAY_MS = 1000; // 1s delay between calls
|
|
@@ -58,17 +59,17 @@ class GeminiEmbedding {
|
|
|
58
59
|
success = true;
|
|
59
60
|
// Standard delay between calls
|
|
60
61
|
await new Promise(resolve => setTimeout(resolve, DELAY_MS));
|
|
61
|
-
|
|
62
|
+
logger_1.Logger.inline("."); // Progress indicator
|
|
62
63
|
}
|
|
63
64
|
catch (e) {
|
|
64
65
|
if (e.message.includes("429") || e.message.includes("quota")) {
|
|
65
66
|
retries++;
|
|
66
67
|
const waitTime = Math.pow(2, retries) * 2000; // 2s, 4s, 8s, 16s...
|
|
67
|
-
|
|
68
|
+
logger_1.Logger.warn(`\nā ļø Rate limit hit. Retrying in ${waitTime / 1000}s...`);
|
|
68
69
|
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
69
70
|
}
|
|
70
71
|
else {
|
|
71
|
-
|
|
72
|
+
logger_1.Logger.error("\nā Embedding failed irreversibly: " + e.message);
|
|
72
73
|
throw e;
|
|
73
74
|
}
|
|
74
75
|
}
|
|
@@ -78,7 +79,7 @@ class GeminiEmbedding {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
|
-
|
|
82
|
+
logger_1.Logger.success("\nā
Done embedding.");
|
|
82
83
|
return embeddings;
|
|
83
84
|
}
|
|
84
85
|
similarity(embedding1, embedding2) {
|
package/dist/scripts/indexer.js
CHANGED
|
@@ -42,6 +42,7 @@ const mock_llm_1 = require("./mock-llm");
|
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const fs = __importStar(require("fs"));
|
|
44
44
|
const chalk_1 = __importDefault(require("chalk"));
|
|
45
|
+
const logger_1 = require("./utils/logger");
|
|
45
46
|
// IMPORTANT: Import config for side effects (loads dotenv from cwd)
|
|
46
47
|
require("./config");
|
|
47
48
|
// Configuration
|
|
@@ -57,7 +58,7 @@ function getSourceDir() {
|
|
|
57
58
|
if (fs.existsSync(customPath)) {
|
|
58
59
|
return customPath;
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
+
logger_1.Logger.error(`ā Custom path not found: ${customPath}`);
|
|
61
62
|
process.exit(1);
|
|
62
63
|
}
|
|
63
64
|
// 2. Try common source directories
|
|
@@ -76,30 +77,49 @@ function getSourceDir() {
|
|
|
76
77
|
return cwd;
|
|
77
78
|
}
|
|
78
79
|
async function main() {
|
|
80
|
+
logger_1.Logger.init(); // Initialize Logger (auto-detects modes)
|
|
79
81
|
// 0. Environment Validation
|
|
80
82
|
const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY;
|
|
81
83
|
if (!geminiKey) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
logger_1.Logger.error("ā Missing GEMINI_API_KEY (or GOOGLE_API_KEY)!");
|
|
85
|
+
logger_1.Logger.warn("\nPlease ensure you have a .env.local file. Check .env.example for a template.\n");
|
|
84
86
|
process.exit(1);
|
|
85
87
|
}
|
|
86
88
|
const args = process.argv.slice(2);
|
|
87
89
|
const isWatch = args.includes("--watch");
|
|
88
90
|
const SOURCE_DIR = getSourceDir();
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
logger_1.Logger.info(chalk_1.default.blue("š§ J-Star Indexer: Scanning codebase..."));
|
|
92
|
+
logger_1.Logger.dim(` Source: ${SOURCE_DIR}`);
|
|
91
93
|
// 1. Load documents (Your Code)
|
|
92
94
|
if (!fs.existsSync(SOURCE_DIR)) {
|
|
93
|
-
|
|
95
|
+
logger_1.Logger.error(`ā Source directory not found: ${SOURCE_DIR}`);
|
|
94
96
|
process.exit(1);
|
|
95
97
|
}
|
|
96
98
|
const reader = new llamaindex_1.SimpleDirectoryReader();
|
|
97
99
|
const documents = await reader.loadData({ directoryPath: SOURCE_DIR });
|
|
98
|
-
|
|
100
|
+
// --- SECURITY FILTER ---
|
|
101
|
+
// Exclude sensitive files (like .env) and build artifacts
|
|
102
|
+
const EXCLUDED_PATTERNS = [
|
|
103
|
+
/pnpm-lock\.yaml/,
|
|
104
|
+
/package-lock\.json/,
|
|
105
|
+
/yarn\.lock/,
|
|
106
|
+
/\.env/,
|
|
107
|
+
/\.DS_Store/,
|
|
108
|
+
/node_modules/,
|
|
109
|
+
/\.git/,
|
|
110
|
+
/\.jstar/,
|
|
111
|
+
/\.json$/, // Prefer code over JSON data for context unless docs
|
|
112
|
+
];
|
|
113
|
+
const filteredDocuments = documents.filter(doc => {
|
|
114
|
+
const filePath = doc.metadata?.file_path || doc.id_ || '';
|
|
115
|
+
// Check strict exclusions
|
|
116
|
+
const isExcluded = EXCLUDED_PATTERNS.some(pattern => pattern.test(filePath));
|
|
117
|
+
return !isExcluded;
|
|
118
|
+
});
|
|
119
|
+
logger_1.Logger.info(chalk_1.default.yellow(`š Found ${documents.length} files. Indexing ${filteredDocuments.length} valid files (filtered ${documents.length - filteredDocuments.length} excluded).`));
|
|
99
120
|
const isInit = args.includes("--init");
|
|
100
121
|
try {
|
|
101
122
|
// 2. Setup Service Context with Google Gemini Embeddings
|
|
102
|
-
// using 'models/text-embedding-004' which is a strong, recent model
|
|
103
123
|
const embedModel = new gemini_embedding_1.GeminiEmbedding();
|
|
104
124
|
const llm = new mock_llm_1.MockLLM();
|
|
105
125
|
const serviceContext = (0, llamaindex_1.serviceContextFromDefaults)({
|
|
@@ -109,13 +129,13 @@ async function main() {
|
|
|
109
129
|
// 3. Create the Storage Context
|
|
110
130
|
let storageContext;
|
|
111
131
|
if (isInit) {
|
|
112
|
-
|
|
132
|
+
logger_1.Logger.info(chalk_1.default.blue("⨠Initializing fresh Local Brain..."));
|
|
113
133
|
storageContext = await (0, llamaindex_1.storageContextFromDefaults)({});
|
|
114
134
|
}
|
|
115
135
|
else {
|
|
116
136
|
// Try to load
|
|
117
137
|
if (!fs.existsSync(STORAGE_DIR)) {
|
|
118
|
-
|
|
138
|
+
logger_1.Logger.warn("ā ļø Storage not found. Running fresh init...");
|
|
119
139
|
storageContext = await (0, llamaindex_1.storageContextFromDefaults)({});
|
|
120
140
|
}
|
|
121
141
|
else {
|
|
@@ -125,12 +145,11 @@ async function main() {
|
|
|
125
145
|
}
|
|
126
146
|
}
|
|
127
147
|
// 4. Generate the Index
|
|
128
|
-
const index = await llamaindex_1.VectorStoreIndex.fromDocuments(
|
|
148
|
+
const index = await llamaindex_1.VectorStoreIndex.fromDocuments(filteredDocuments, {
|
|
129
149
|
storageContext,
|
|
130
150
|
serviceContext,
|
|
131
151
|
});
|
|
132
152
|
// 4. Persist (Save the Brain)
|
|
133
|
-
// Manual persistence for LlamaIndex TS compatibility
|
|
134
153
|
const ctxToPersist = index.storageContext;
|
|
135
154
|
if (ctxToPersist.docStore)
|
|
136
155
|
await ctxToPersist.docStore.persist(path.join(STORAGE_DIR, "doc_store.json"));
|
|
@@ -140,17 +159,17 @@ async function main() {
|
|
|
140
159
|
await ctxToPersist.indexStore.persist(path.join(STORAGE_DIR, "index_store.json"));
|
|
141
160
|
if (ctxToPersist.propStore)
|
|
142
161
|
await ctxToPersist.propStore.persist(path.join(STORAGE_DIR, "property_store.json"));
|
|
143
|
-
|
|
162
|
+
logger_1.Logger.success("ā
Indexing Complete. Brain is updated.");
|
|
144
163
|
if (isWatch) {
|
|
145
|
-
|
|
164
|
+
logger_1.Logger.info(chalk_1.default.blue("š Watch mode enabled."));
|
|
146
165
|
}
|
|
147
166
|
}
|
|
148
167
|
catch (e) {
|
|
149
|
-
|
|
168
|
+
logger_1.Logger.error("ā Indexing Failed: " + e.message);
|
|
150
169
|
if (e.message.includes("API") || e.message.includes("key")) {
|
|
151
|
-
|
|
170
|
+
logger_1.Logger.warn("š Tip: Make sure you have GEMINI_API_KEY in your .env.local file.");
|
|
152
171
|
}
|
|
153
172
|
process.exit(1);
|
|
154
173
|
}
|
|
155
174
|
}
|
|
156
|
-
main().catch(
|
|
175
|
+
main().catch(err => logger_1.Logger.error(err));
|
package/dist/scripts/session.js
CHANGED
|
@@ -52,8 +52,8 @@ async function startInteractiveSession(findings, index) {
|
|
|
52
52
|
if (interactiveFindings.length === 0 || interactiveFindings.every(f => f.issues.length === 0)) {
|
|
53
53
|
return { updatedFindings: interactiveFindings, hasUpdates: false };
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
logger_1.Logger.info(chalk_1.default.bold.magenta("\nš£ļø Interactive Review Session"));
|
|
56
|
+
logger_1.Logger.dim(" Use arrow keys to navigate. Select an issue to debate.");
|
|
57
57
|
while (active) {
|
|
58
58
|
// Re-calculate choices every loop to reflect status changes
|
|
59
59
|
// Flatten
|
|
@@ -92,33 +92,33 @@ async function startInteractiveSession(findings, index) {
|
|
|
92
92
|
const selected = flatIssues[selectedIdx];
|
|
93
93
|
const { issue, file } = selected;
|
|
94
94
|
// Show Details
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
logger_1.Logger.info(chalk_1.default.cyan(`\nTitle: ${issue.title}`));
|
|
96
|
+
logger_1.Logger.info(chalk_1.default.white(issue.description));
|
|
97
|
+
logger_1.Logger.dim(`File: ${file}`);
|
|
98
98
|
if (issue.confidenceScore)
|
|
99
|
-
|
|
99
|
+
logger_1.Logger.info(chalk_1.default.yellow(`Confidence: ${issue.confidenceScore}/5`));
|
|
100
100
|
if (issue.status)
|
|
101
|
-
|
|
101
|
+
logger_1.Logger.success(`Status: ${issue.status}`);
|
|
102
102
|
// Action Menu
|
|
103
103
|
const action = await (0, interaction_1.showActionMenu)(issue.title);
|
|
104
104
|
if (action === 'discuss') {
|
|
105
105
|
const argument = await (0, interaction_1.askForArgument)();
|
|
106
106
|
const result = await (0, debate_1.debateIssue)(issue.title, issue.description, file, argument, index);
|
|
107
|
-
|
|
107
|
+
logger_1.Logger.info(chalk_1.default.yellow(`\nš¤ Bot: ${result.text}`));
|
|
108
108
|
if (result.severity === 'LGTM') {
|
|
109
|
-
|
|
109
|
+
logger_1.Logger.success("ā
Issue withdrawn by AI!");
|
|
110
110
|
// Direct update to our state
|
|
111
111
|
interactiveFindings[selected.fileIndex].issues[selected.issueIndex].status = 'resolved';
|
|
112
112
|
hasUpdates = true;
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
else if (action === 'ignore') {
|
|
116
|
-
|
|
116
|
+
logger_1.Logger.dim('Issue ignored locally.');
|
|
117
117
|
interactiveFindings[selected.fileIndex].issues[selected.issueIndex].status = 'ignored';
|
|
118
118
|
hasUpdates = true;
|
|
119
119
|
}
|
|
120
120
|
else if (action === 'accept') {
|
|
121
|
-
|
|
121
|
+
logger_1.Logger.success('Issue accepted.');
|
|
122
122
|
}
|
|
123
123
|
else if (action === 'exit') {
|
|
124
124
|
active = false;
|
package/package.json
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jstar-reviewer",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"description": "Local-First, Context-Aware AI Code Reviewer - Works with any language",
|
|
5
5
|
"bin": {
|
|
6
6
|
"jstar": "bin/jstar.js"
|
|
7
7
|
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"index:init": "ts-node scripts/indexer.ts --init",
|
|
11
|
+
"index:watch": "ts-node scripts/indexer.ts --watch",
|
|
12
|
+
"review": "ts-node scripts/reviewer.ts",
|
|
13
|
+
"chat": "ts-node scripts/chat.ts",
|
|
14
|
+
"detect": "ts-node scripts/detective.ts",
|
|
15
|
+
"prepare": "husky install"
|
|
16
|
+
},
|
|
8
17
|
"keywords": [
|
|
9
18
|
"code-review",
|
|
10
19
|
"ai",
|
|
@@ -56,13 +65,5 @@
|
|
|
56
65
|
"type": "commonjs",
|
|
57
66
|
"bugs": {
|
|
58
67
|
"url": "https://github.com/JStaRFilms/jstar-code-review/issues"
|
|
59
|
-
},
|
|
60
|
-
"scripts": {
|
|
61
|
-
"build": "tsc",
|
|
62
|
-
"index:init": "ts-node scripts/indexer.ts --init",
|
|
63
|
-
"index:watch": "ts-node scripts/indexer.ts --watch",
|
|
64
|
-
"review": "ts-node scripts/reviewer.ts",
|
|
65
|
-
"chat": "ts-node scripts/chat.ts",
|
|
66
|
-
"detect": "ts-node scripts/detective.ts"
|
|
67
68
|
}
|
|
68
69
|
}
|
package/scripts/chat.ts
CHANGED
|
@@ -27,7 +27,11 @@ async function loadSession(): Promise<SessionState | null> {
|
|
|
27
27
|
return JSON.parse(content);
|
|
28
28
|
} catch (e: any) {
|
|
29
29
|
if (e.code === 'ENOENT') {
|
|
30
|
-
return null; // File doesn't exist
|
|
30
|
+
return null; // File doesn't exist, start fresh
|
|
31
|
+
}
|
|
32
|
+
if (e instanceof SyntaxError) {
|
|
33
|
+
Logger.warn(chalk.yellow("ā ļø Session file corrupted. Starting fresh."));
|
|
34
|
+
return null;
|
|
31
35
|
}
|
|
32
36
|
Logger.error(`Failed to load session: ${e.message}`);
|
|
33
37
|
return null;
|
package/scripts/core/debate.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { VectorStoreIndex, MetadataMode } from "llamaindex";
|
|
|
2
2
|
import { generateText } from "ai";
|
|
3
3
|
import { createGroq } from "@ai-sdk/groq";
|
|
4
4
|
import { Config } from "../config";
|
|
5
|
+
import { Logger } from "../utils/logger";
|
|
5
6
|
import chalk from "chalk";
|
|
6
7
|
|
|
7
8
|
const groq = createGroq({ apiKey: process.env.GROQ_API_KEY });
|
|
@@ -24,7 +25,7 @@ export async function debateIssue(
|
|
|
24
25
|
throw new Error("GROQ_API_KEY is required for debate mode. Please set it in your .env.local file.");
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
Logger.dim(" š§ Thinking... (Consulting the Brain)");
|
|
28
29
|
|
|
29
30
|
// 1. Extract keywords/context
|
|
30
31
|
const query = `${userArgument} ${issueTitle}`;
|
|
@@ -35,12 +36,12 @@ export async function debateIssue(
|
|
|
35
36
|
const newContext = contextNodes.map(n => n.node.getContent(MetadataMode.NONE)).join("\n\n").slice(0, 2000);
|
|
36
37
|
|
|
37
38
|
if (newContext.length >= 2000) {
|
|
38
|
-
|
|
39
|
+
Logger.warn(" ā ļø Context truncated to 2000 chars");
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
const sources = contextNodes.map(n => n.node.metadata?.['file_name']).filter(Boolean).join(', ');
|
|
42
43
|
if (sources) {
|
|
43
|
-
|
|
44
|
+
Logger.dim(` š Found relevant context from: ${sources}`);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
// 3. Ask the Judge
|
package/scripts/detective.ts
CHANGED
|
@@ -17,6 +17,7 @@ interface Rule {
|
|
|
17
17
|
message: string;
|
|
18
18
|
pattern: RegExp;
|
|
19
19
|
filePattern?: RegExp; // Only check files matching this pattern
|
|
20
|
+
excludePattern?: RegExp; // Exclude files matching this pattern
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const RULES: Rule[] = [
|
|
@@ -30,7 +31,8 @@ const RULES: Rule[] = [
|
|
|
30
31
|
id: 'ARCH-001',
|
|
31
32
|
severity: 'medium',
|
|
32
33
|
message: 'Avoid using console.log in production code',
|
|
33
|
-
pattern: /console\.log\(
|
|
34
|
+
pattern: /console\.log\(/,
|
|
35
|
+
excludePattern: /(bin[\\/]jstar\.js|scripts[\\/]utils[\\/]logger\.ts|setup\.js|test[\\/])/
|
|
34
36
|
},
|
|
35
37
|
];
|
|
36
38
|
|
|
@@ -39,16 +41,20 @@ const FILE_RULES: Rule[] = [
|
|
|
39
41
|
{
|
|
40
42
|
id: 'ARCH-002',
|
|
41
43
|
severity: 'high',
|
|
42
|
-
message: 'Next.js "use client" must be at the very top of the file',
|
|
43
|
-
pattern: /^(?!['"]use client['"]).*['"]use client['"]/s,
|
|
44
|
-
filePattern: /\.tsx
|
|
44
|
+
message: 'Next.js "use client" must be at the very top of the file (before imports)',
|
|
45
|
+
pattern: /^(?!(?:\s*|(?:\/\/[^\n]*\n)|(?:\/\*[\s\S]*?\*\/))*['"]use client['"]).*['"]use client['"]/s,
|
|
46
|
+
filePattern: /\.tsx?$/,
|
|
47
|
+
excludePattern: /(scripts|test)[\\/]/
|
|
45
48
|
}
|
|
46
49
|
];
|
|
47
50
|
|
|
48
51
|
export class Detective {
|
|
49
52
|
violations: Violation[] = [];
|
|
53
|
+
private includeBuildFiles: boolean;
|
|
50
54
|
|
|
51
|
-
constructor(private directory: string
|
|
55
|
+
constructor(private directory: string, options: { includeBuildFiles?: boolean } = {}) {
|
|
56
|
+
this.includeBuildFiles = options.includeBuildFiles ?? false;
|
|
57
|
+
}
|
|
52
58
|
|
|
53
59
|
async scan(): Promise<Violation[]> {
|
|
54
60
|
this.walk(this.directory);
|
|
@@ -63,9 +69,12 @@ export class Detective {
|
|
|
63
69
|
const stat = fs.statSync(filePath);
|
|
64
70
|
|
|
65
71
|
if (stat.isDirectory()) {
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
// Ignore common build/config directories
|
|
73
|
+
const ignoredDirs = ['node_modules', '.git', '.jstar', 'dist', 'coverage', '.next'];
|
|
74
|
+
if (!this.includeBuildFiles && ignoredDirs.includes(file)) {
|
|
75
|
+
continue;
|
|
68
76
|
}
|
|
77
|
+
this.walk(filePath);
|
|
69
78
|
} else {
|
|
70
79
|
this.checkFile(filePath);
|
|
71
80
|
}
|
|
@@ -74,6 +83,8 @@ export class Detective {
|
|
|
74
83
|
|
|
75
84
|
private checkFile(filePath: string) {
|
|
76
85
|
if (!filePath.match(/\.(ts|tsx|js|jsx)$/)) return;
|
|
86
|
+
// Skip .d.ts files
|
|
87
|
+
if (filePath.endsWith('.d.ts')) return;
|
|
77
88
|
|
|
78
89
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
79
90
|
const lines = content.split('\n');
|
|
@@ -81,6 +92,7 @@ export class Detective {
|
|
|
81
92
|
// Line-based rules
|
|
82
93
|
for (const rule of RULES) {
|
|
83
94
|
if (rule.filePattern && !filePath.match(rule.filePattern)) continue;
|
|
95
|
+
if (rule.excludePattern && filePath.match(rule.excludePattern)) continue;
|
|
84
96
|
|
|
85
97
|
lines.forEach((line, index) => {
|
|
86
98
|
if (rule.pattern.test(line)) {
|
|
@@ -92,6 +104,8 @@ export class Detective {
|
|
|
92
104
|
// File-based rules
|
|
93
105
|
for (const rule of FILE_RULES) {
|
|
94
106
|
if (rule.filePattern && !filePath.match(rule.filePattern)) continue;
|
|
107
|
+
if (rule.excludePattern && filePath.match(rule.excludePattern)) continue;
|
|
108
|
+
|
|
95
109
|
if (rule.pattern.test(content)) {
|
|
96
110
|
this.addViolation(filePath, 1, rule);
|
|
97
111
|
}
|
|
@@ -132,7 +146,11 @@ export class Detective {
|
|
|
132
146
|
|
|
133
147
|
// CLI Integration
|
|
134
148
|
if (require.main === module) {
|
|
135
|
-
const
|
|
149
|
+
const args = process.argv.slice(2);
|
|
150
|
+
const includeBuildFiles = args.includes('--all');
|
|
151
|
+
|
|
152
|
+
// Scan current directory by default
|
|
153
|
+
const detective = new Detective(process.cwd(), { includeBuildFiles });
|
|
136
154
|
detective.scan();
|
|
137
155
|
detective.report();
|
|
138
156
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
2
|
+
import { Logger } from "./utils/logger";
|
|
2
3
|
|
|
3
4
|
export class GeminiEmbedding {
|
|
4
5
|
private genAI: GoogleGenerativeAI;
|
|
@@ -26,7 +27,7 @@ export class GeminiEmbedding {
|
|
|
26
27
|
if (e.message.includes("fetch failed") || e.message.includes("network")) {
|
|
27
28
|
retries++;
|
|
28
29
|
const waitTime = Math.pow(2, retries) * 1000;
|
|
29
|
-
|
|
30
|
+
Logger.warn(`ā ļø Network error. Retrying in ${waitTime / 1000}s... (${retries}/${maxRetries})`);
|
|
30
31
|
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
31
32
|
} else {
|
|
32
33
|
throw e;
|
|
@@ -42,7 +43,7 @@ export class GeminiEmbedding {
|
|
|
42
43
|
|
|
43
44
|
async getTextEmbeddings(texts: string[]): Promise<number[][]> {
|
|
44
45
|
const embeddings: number[][] = [];
|
|
45
|
-
|
|
46
|
+
Logger.info(`Creating embeddings for ${texts.length} chunks (Batching to avoid rate limits)...`);
|
|
46
47
|
|
|
47
48
|
// Process in smaller batches with delay
|
|
48
49
|
const BATCH_SIZE = 1; // Strict serial for safety on free tier
|
|
@@ -60,15 +61,15 @@ export class GeminiEmbedding {
|
|
|
60
61
|
success = true;
|
|
61
62
|
// Standard delay between calls
|
|
62
63
|
await new Promise(resolve => setTimeout(resolve, DELAY_MS));
|
|
63
|
-
|
|
64
|
+
Logger.inline("."); // Progress indicator
|
|
64
65
|
} catch (e: any) {
|
|
65
66
|
if (e.message.includes("429") || e.message.includes("quota")) {
|
|
66
67
|
retries++;
|
|
67
68
|
const waitTime = Math.pow(2, retries) * 2000; // 2s, 4s, 8s, 16s...
|
|
68
|
-
|
|
69
|
+
Logger.warn(`\nā ļø Rate limit hit. Retrying in ${waitTime / 1000}s...`);
|
|
69
70
|
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
70
71
|
} else {
|
|
71
|
-
|
|
72
|
+
Logger.error("\nā Embedding failed irreversibly: " + e.message);
|
|
72
73
|
throw e;
|
|
73
74
|
}
|
|
74
75
|
}
|
|
@@ -78,7 +79,7 @@ export class GeminiEmbedding {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
|
-
|
|
82
|
+
Logger.success("\nā
Done embedding.");
|
|
82
83
|
return embeddings;
|
|
83
84
|
}
|
|
84
85
|
|
package/scripts/indexer.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { MockLLM } from "./mock-llm";
|
|
|
9
9
|
import * as path from "path";
|
|
10
10
|
import * as fs from "fs";
|
|
11
11
|
import chalk from "chalk";
|
|
12
|
+
import { Logger } from "./utils/logger";
|
|
12
13
|
// IMPORTANT: Import config for side effects (loads dotenv from cwd)
|
|
13
14
|
import "./config";
|
|
14
15
|
|
|
@@ -27,7 +28,7 @@ function getSourceDir(): string {
|
|
|
27
28
|
if (fs.existsSync(customPath)) {
|
|
28
29
|
return customPath;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
Logger.error(`ā Custom path not found: ${customPath}`);
|
|
31
32
|
process.exit(1);
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -49,11 +50,13 @@ function getSourceDir(): string {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
async function main() {
|
|
53
|
+
Logger.init(); // Initialize Logger (auto-detects modes)
|
|
54
|
+
|
|
52
55
|
// 0. Environment Validation
|
|
53
56
|
const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY;
|
|
54
57
|
if (!geminiKey) {
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
Logger.error("ā Missing GEMINI_API_KEY (or GOOGLE_API_KEY)!");
|
|
59
|
+
Logger.warn("\nPlease ensure you have a .env.local file. Check .env.example for a template.\n");
|
|
57
60
|
process.exit(1);
|
|
58
61
|
}
|
|
59
62
|
|
|
@@ -61,25 +64,48 @@ async function main() {
|
|
|
61
64
|
const isWatch = args.includes("--watch");
|
|
62
65
|
const SOURCE_DIR = getSourceDir();
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
Logger.info(chalk.blue("š§ J-Star Indexer: Scanning codebase..."));
|
|
68
|
+
Logger.dim(` Source: ${SOURCE_DIR}`);
|
|
66
69
|
|
|
67
70
|
// 1. Load documents (Your Code)
|
|
68
71
|
if (!fs.existsSync(SOURCE_DIR)) {
|
|
69
|
-
|
|
72
|
+
Logger.error(`ā Source directory not found: ${SOURCE_DIR}`);
|
|
70
73
|
process.exit(1);
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
const reader = new SimpleDirectoryReader();
|
|
74
77
|
const documents = await reader.loadData({ directoryPath: SOURCE_DIR });
|
|
75
78
|
|
|
76
|
-
|
|
79
|
+
// --- SECURITY FILTER ---
|
|
80
|
+
// Exclude sensitive files (like .env) and build artifacts
|
|
81
|
+
const EXCLUDED_PATTERNS = [
|
|
82
|
+
/pnpm-lock\.yaml/,
|
|
83
|
+
/package-lock\.json/,
|
|
84
|
+
/yarn\.lock/,
|
|
85
|
+
/\.env/,
|
|
86
|
+
/\.DS_Store/,
|
|
87
|
+
/node_modules/,
|
|
88
|
+
/\.git/,
|
|
89
|
+
/\.jstar/,
|
|
90
|
+
/\.json$/, // Prefer code over JSON data for context unless docs
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const filteredDocuments = documents.filter(doc => {
|
|
94
|
+
const filePath = (doc.metadata as any)?.file_path || (doc as any).id_ || '';
|
|
95
|
+
|
|
96
|
+
// Check strict exclusions
|
|
97
|
+
const isExcluded = EXCLUDED_PATTERNS.some(pattern => pattern.test(filePath));
|
|
98
|
+
|
|
99
|
+
return !isExcluded;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
Logger.info(chalk.yellow(`š Found ${documents.length} files. Indexing ${filteredDocuments.length} valid files (filtered ${documents.length - filteredDocuments.length} excluded).`));
|
|
103
|
+
|
|
77
104
|
|
|
78
105
|
const isInit = args.includes("--init");
|
|
79
106
|
|
|
80
107
|
try {
|
|
81
108
|
// 2. Setup Service Context with Google Gemini Embeddings
|
|
82
|
-
// using 'models/text-embedding-004' which is a strong, recent model
|
|
83
109
|
const embedModel = new GeminiEmbedding();
|
|
84
110
|
const llm = new MockLLM();
|
|
85
111
|
const serviceContext = serviceContextFromDefaults({
|
|
@@ -90,12 +116,12 @@ async function main() {
|
|
|
90
116
|
// 3. Create the Storage Context
|
|
91
117
|
let storageContext;
|
|
92
118
|
if (isInit) {
|
|
93
|
-
|
|
119
|
+
Logger.info(chalk.blue("⨠Initializing fresh Local Brain..."));
|
|
94
120
|
storageContext = await storageContextFromDefaults({});
|
|
95
121
|
} else {
|
|
96
122
|
// Try to load
|
|
97
123
|
if (!fs.existsSync(STORAGE_DIR)) {
|
|
98
|
-
|
|
124
|
+
Logger.warn("ā ļø Storage not found. Running fresh init...");
|
|
99
125
|
storageContext = await storageContextFromDefaults({});
|
|
100
126
|
} else {
|
|
101
127
|
storageContext = await storageContextFromDefaults({
|
|
@@ -105,32 +131,31 @@ async function main() {
|
|
|
105
131
|
}
|
|
106
132
|
|
|
107
133
|
// 4. Generate the Index
|
|
108
|
-
const index = await VectorStoreIndex.fromDocuments(
|
|
134
|
+
const index = await VectorStoreIndex.fromDocuments(filteredDocuments, {
|
|
109
135
|
storageContext,
|
|
110
136
|
serviceContext,
|
|
111
137
|
});
|
|
112
138
|
|
|
113
139
|
// 4. Persist (Save the Brain)
|
|
114
|
-
// Manual persistence for LlamaIndex TS compatibility
|
|
115
140
|
const ctxToPersist: any = index.storageContext;
|
|
116
141
|
if (ctxToPersist.docStore) await ctxToPersist.docStore.persist(path.join(STORAGE_DIR, "doc_store.json"));
|
|
117
142
|
if (ctxToPersist.vectorStore) await ctxToPersist.vectorStore.persist(path.join(STORAGE_DIR, "vector_store.json"));
|
|
118
143
|
if (ctxToPersist.indexStore) await ctxToPersist.indexStore.persist(path.join(STORAGE_DIR, "index_store.json"));
|
|
119
144
|
if (ctxToPersist.propStore) await ctxToPersist.propStore.persist(path.join(STORAGE_DIR, "property_store.json"));
|
|
120
145
|
|
|
121
|
-
|
|
146
|
+
Logger.success("ā
Indexing Complete. Brain is updated.");
|
|
122
147
|
|
|
123
148
|
if (isWatch) {
|
|
124
|
-
|
|
149
|
+
Logger.info(chalk.blue("š Watch mode enabled."));
|
|
125
150
|
}
|
|
126
151
|
|
|
127
152
|
} catch (e: any) {
|
|
128
|
-
|
|
153
|
+
Logger.error("ā Indexing Failed: " + e.message);
|
|
129
154
|
if (e.message.includes("API") || e.message.includes("key")) {
|
|
130
|
-
|
|
155
|
+
Logger.warn("š Tip: Make sure you have GEMINI_API_KEY in your .env.local file.");
|
|
131
156
|
}
|
|
132
157
|
process.exit(1);
|
|
133
158
|
}
|
|
134
159
|
}
|
|
135
160
|
|
|
136
|
-
main().catch(
|
|
161
|
+
main().catch(err => Logger.error(err));
|
package/scripts/mock-llm.ts
CHANGED
|
@@ -8,11 +8,11 @@ export class MockLLM {
|
|
|
8
8
|
tokenizer: undefined,
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
async chat(messages:
|
|
11
|
+
async chat(messages: { content: string, role: string }[], parentEvent?: any): Promise<{ message: { content: string } }> {
|
|
12
12
|
return { message: { content: "Mock response" } };
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
async complete(prompt: string, parentEvent?: any): Promise<
|
|
15
|
+
async complete(prompt: string, parentEvent?: any): Promise<{ text: string }> {
|
|
16
16
|
return { text: "Mock response" };
|
|
17
17
|
}
|
|
18
18
|
}
|
package/scripts/session.ts
CHANGED
|
@@ -21,8 +21,8 @@ export async function startInteractiveSession(
|
|
|
21
21
|
return { updatedFindings: interactiveFindings, hasUpdates: false };
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
Logger.info(chalk.bold.magenta("\nš£ļø Interactive Review Session"));
|
|
25
|
+
Logger.dim(" Use arrow keys to navigate. Select an issue to debate.");
|
|
26
26
|
|
|
27
27
|
while (active) {
|
|
28
28
|
// Re-calculate choices every loop to reflect status changes
|
|
@@ -74,11 +74,11 @@ export async function startInteractiveSession(
|
|
|
74
74
|
const { issue, file } = selected;
|
|
75
75
|
|
|
76
76
|
// Show Details
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (issue.confidenceScore)
|
|
81
|
-
if (issue.status)
|
|
77
|
+
Logger.info(chalk.cyan(`\nTitle: ${issue.title}`));
|
|
78
|
+
Logger.info(chalk.white(issue.description));
|
|
79
|
+
Logger.dim(`File: ${file}`);
|
|
80
|
+
if (issue.confidenceScore) Logger.info(chalk.yellow(`Confidence: ${issue.confidenceScore}/5`));
|
|
81
|
+
if (issue.status) Logger.success(`Status: ${issue.status}`);
|
|
82
82
|
|
|
83
83
|
// Action Menu
|
|
84
84
|
const action = await showActionMenu(issue.title);
|
|
@@ -93,21 +93,21 @@ export async function startInteractiveSession(
|
|
|
93
93
|
index
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
Logger.info(chalk.yellow(`\nš¤ Bot: ${result.text}`));
|
|
97
97
|
|
|
98
98
|
if (result.severity === 'LGTM') {
|
|
99
|
-
|
|
99
|
+
Logger.success("ā
Issue withdrawn by AI!");
|
|
100
100
|
// Direct update to our state
|
|
101
101
|
interactiveFindings[selected.fileIndex].issues[selected.issueIndex].status = 'resolved';
|
|
102
102
|
hasUpdates = true;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
} else if (action === 'ignore') {
|
|
106
|
-
|
|
106
|
+
Logger.dim('Issue ignored locally.');
|
|
107
107
|
interactiveFindings[selected.fileIndex].issues[selected.issueIndex].status = 'ignored';
|
|
108
108
|
hasUpdates = true;
|
|
109
109
|
} else if (action === 'accept') {
|
|
110
|
-
|
|
110
|
+
Logger.success('Issue accepted.');
|
|
111
111
|
} else if (action === 'exit') {
|
|
112
112
|
active = false;
|
|
113
113
|
}
|