@hailer/mcp 0.1.13 → 0.1.15
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/.claude/agents/agent-lars-code-inspector.md +42 -0
- package/CLAUDE.md +1 -0
- package/dist/modules/bug-reports/giuseppe-bot.d.ts +1 -0
- package/dist/modules/bug-reports/giuseppe-bot.js +15 -0
- package/dist/modules/bug-reports/giuseppe-lsp.d.ts +42 -0
- package/dist/modules/bug-reports/giuseppe-lsp.js +143 -0
- package/package.json +3 -3
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-lars-code-inspector
|
|
3
|
+
description: LSP-powered code intelligence - finds bugs, dead code, unused imports, and navigates codebases.\n\n<example>\nuser: "Find bugs in src/App.tsx"\nassistant: {"status":"success","result":{"dead_code":[{"line":50,"symbol":"setColorMode"}],"unused_imports":[]},"summary":"Found 1 dead code issue"}\n</example>
|
|
4
|
+
model: haiku
|
|
5
|
+
tools: LSP, Read, Glob
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<identity>
|
|
9
|
+
I am Lars. LSP only. Minimal output. JSON only.
|
|
10
|
+
</identity>
|
|
11
|
+
|
|
12
|
+
<handles>
|
|
13
|
+
- Find unused variables/imports
|
|
14
|
+
- Find dead code
|
|
15
|
+
- Type errors
|
|
16
|
+
- Duplicate declarations
|
|
17
|
+
</handles>
|
|
18
|
+
|
|
19
|
+
<rules>
|
|
20
|
+
1. **LSP ONLY** - Use LSP operations, never tsc/eslint.
|
|
21
|
+
2. **MINIMAL OUTPUT** - No intermediate results. Final JSON only.
|
|
22
|
+
3. **NO PROSE** - Output JSON, then STOP immediately.
|
|
23
|
+
</rules>
|
|
24
|
+
|
|
25
|
+
<workflow>
|
|
26
|
+
1. Use `documentSymbol` to list symbols in file
|
|
27
|
+
2. Use `findReferences` on key symbols to check if used
|
|
28
|
+
3. Check LSP diagnostics in response for errors
|
|
29
|
+
4. Return compact JSON
|
|
30
|
+
</workflow>
|
|
31
|
+
|
|
32
|
+
<protocol>
|
|
33
|
+
Output JSON only. No explanations. No tool output dumps.
|
|
34
|
+
{
|
|
35
|
+
"status": "success|error",
|
|
36
|
+
"result": {
|
|
37
|
+
"dead_code": [{"line":0,"symbol":""}],
|
|
38
|
+
"type_errors": [{"line":0,"msg":""}]
|
|
39
|
+
},
|
|
40
|
+
"summary": "max 30 chars"
|
|
41
|
+
}
|
|
42
|
+
</protocol>
|
package/CLAUDE.md
CHANGED
|
@@ -47,6 +47,7 @@ Agents return JSON. You interpret it for the user.
|
|
|
47
47
|
| Activity CRUD | `agent-dmitri-activity-crud` | haiku |
|
|
48
48
|
| Discussions/chat | `agent-yevgeni-discussions` | haiku |
|
|
49
49
|
| Config audit | `agent-bjorn-config-audit` | haiku |
|
|
50
|
+
| LSP/code bugs | `agent-lars-code-inspector` | haiku |
|
|
50
51
|
| Workflows, fields, phases | `agent-helga-workflow-config` | sonnet |
|
|
51
52
|
| SQL insights | `agent-viktor-sql-insights` | sonnet |
|
|
52
53
|
| Function fields | `agent-alejandro-function-fields` | sonnet |
|
|
@@ -20,6 +20,7 @@ const app_scaffold_1 = require("../../mcp/tools/app-scaffold");
|
|
|
20
20
|
const giuseppe_ai_1 = require("./giuseppe-ai");
|
|
21
21
|
const giuseppe_git_1 = require("./giuseppe-git");
|
|
22
22
|
const giuseppe_files_1 = require("./giuseppe-files");
|
|
23
|
+
const giuseppe_lsp_1 = require("./giuseppe-lsp");
|
|
23
24
|
const logger = (0, logger_1.createLogger)({ component: 'giuseppe-bot' });
|
|
24
25
|
// Magic words for approval flow
|
|
25
26
|
const MAGIC_WORD_APPROVED = 'approved';
|
|
@@ -44,6 +45,7 @@ class GiuseppeBot {
|
|
|
44
45
|
ai;
|
|
45
46
|
git;
|
|
46
47
|
files;
|
|
48
|
+
lsp;
|
|
47
49
|
appsRegistry = new Map();
|
|
48
50
|
constructor(userContext, config, monitor) {
|
|
49
51
|
this.userContext = userContext;
|
|
@@ -54,6 +56,7 @@ class GiuseppeBot {
|
|
|
54
56
|
this.ai = new giuseppe_ai_1.GiuseppeAI(apiKey);
|
|
55
57
|
this.git = new giuseppe_git_1.GiuseppeGit();
|
|
56
58
|
this.files = new giuseppe_files_1.GiuseppeFiles(process.env.DEV_APPS_PATH);
|
|
59
|
+
this.lsp = new giuseppe_lsp_1.GiuseppeLsp();
|
|
57
60
|
// Load apps registry from config
|
|
58
61
|
if (config.appsRegistry) {
|
|
59
62
|
for (const [appId, entry] of Object.entries(config.appsRegistry)) {
|
|
@@ -236,6 +239,12 @@ class GiuseppeBot {
|
|
|
236
239
|
}
|
|
237
240
|
result.log?.push(`Found app project: ${app.projectPath}`);
|
|
238
241
|
await this.reportProgress(bug, `📁 Found app project: ${app.name}`);
|
|
242
|
+
// 1.5. Run LSP analysis to understand current codebase state
|
|
243
|
+
const preAnalysis = await this.lsp.analyzeProject(app.projectPath);
|
|
244
|
+
if (preAnalysis.issues.length > 0) {
|
|
245
|
+
result.log?.push(`Pre-fix LSP: ${preAnalysis.summary}`);
|
|
246
|
+
await this.reportProgress(bug, `🔎 Codebase analysis: ${preAnalysis.summary}`);
|
|
247
|
+
}
|
|
239
248
|
// 2. Analyze and generate fix
|
|
240
249
|
const allFiles = await this.files.scanSourceFiles(app.projectPath);
|
|
241
250
|
const fixPlan = await this.ai.analyzeAndPlanFix(bug, app, allFiles, (paths) => this.files.readSelectedFiles(app.projectPath, paths));
|
|
@@ -283,6 +292,12 @@ class GiuseppeBot {
|
|
|
283
292
|
await this.reportProgress(bug, `✏️ Applied fixes to ${applyResult.files.length} file(s)`);
|
|
284
293
|
const buildResult = await this.buildAndTest(app);
|
|
285
294
|
if (buildResult.success) {
|
|
295
|
+
// Run LSP analysis to check for remaining issues
|
|
296
|
+
const lspResult = await this.lsp.validateFix(app.projectPath);
|
|
297
|
+
if (!lspResult.valid) {
|
|
298
|
+
result.log?.push(`LSP found ${lspResult.errors.length} issue(s)`);
|
|
299
|
+
await this.reportProgress(bug, `⚠️ Build passed but LSP found issues:\n${lspResult.errors.slice(0, 3).join('\n')}`);
|
|
300
|
+
}
|
|
286
301
|
fixSuccess = true;
|
|
287
302
|
result.log?.push(`Build successful (attempt ${attempt})`);
|
|
288
303
|
await this.reportProgress(bug, '✅ Build successful');
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Giuseppe LSP Module - Code analysis via TypeScript compiler
|
|
3
|
+
*/
|
|
4
|
+
export interface CodeIssue {
|
|
5
|
+
file: string;
|
|
6
|
+
line: number;
|
|
7
|
+
column: number;
|
|
8
|
+
message: string;
|
|
9
|
+
code: string;
|
|
10
|
+
severity: 'error' | 'warning';
|
|
11
|
+
}
|
|
12
|
+
export interface AnalysisResult {
|
|
13
|
+
success: boolean;
|
|
14
|
+
issues: CodeIssue[];
|
|
15
|
+
summary: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class GiuseppeLsp {
|
|
18
|
+
/**
|
|
19
|
+
* Analyze a project for TypeScript errors
|
|
20
|
+
*/
|
|
21
|
+
analyzeProject(projectPath: string): Promise<AnalysisResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Analyze a single file
|
|
24
|
+
*/
|
|
25
|
+
analyzeFile(filePath: string): Promise<AnalysisResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Check if code compiles after a fix
|
|
28
|
+
*/
|
|
29
|
+
validateFix(projectPath: string): Promise<{
|
|
30
|
+
valid: boolean;
|
|
31
|
+
errors: string[];
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Find unused exports/variables in a file
|
|
35
|
+
*/
|
|
36
|
+
findDeadCode(projectPath: string): Promise<CodeIssue[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Parse tsc output into structured issues
|
|
39
|
+
*/
|
|
40
|
+
private parseTscOutput;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=giuseppe-lsp.d.ts.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Giuseppe LSP Module - Code analysis via TypeScript compiler
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.GiuseppeLsp = void 0;
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const logger_1 = require("../../lib/logger");
|
|
43
|
+
const logger = (0, logger_1.createLogger)({ component: 'giuseppe-lsp' });
|
|
44
|
+
class GiuseppeLsp {
|
|
45
|
+
/**
|
|
46
|
+
* Analyze a project for TypeScript errors
|
|
47
|
+
*/
|
|
48
|
+
async analyzeProject(projectPath) {
|
|
49
|
+
try {
|
|
50
|
+
logger.info(`Analyzing project: ${projectPath}`);
|
|
51
|
+
const result = (0, child_process_1.execSync)('npx tsc --noEmit --pretty false 2>&1 || true', {
|
|
52
|
+
cwd: projectPath,
|
|
53
|
+
encoding: 'utf-8',
|
|
54
|
+
maxBuffer: 10 * 1024 * 1024
|
|
55
|
+
});
|
|
56
|
+
const issues = this.parseTscOutput(result, projectPath);
|
|
57
|
+
return {
|
|
58
|
+
success: issues.filter(i => i.severity === 'error').length === 0,
|
|
59
|
+
issues,
|
|
60
|
+
summary: issues.length === 0
|
|
61
|
+
? 'No issues found'
|
|
62
|
+
: `Found ${issues.length} issue(s)`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
logger.error('Analysis failed', { error });
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
issues: [],
|
|
70
|
+
summary: `Analysis failed: ${error}`
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Analyze a single file
|
|
76
|
+
*/
|
|
77
|
+
async analyzeFile(filePath) {
|
|
78
|
+
const projectPath = path.dirname(filePath);
|
|
79
|
+
const result = await this.analyzeProject(projectPath);
|
|
80
|
+
const fileName = path.basename(filePath);
|
|
81
|
+
result.issues = result.issues.filter(i => i.file.endsWith(fileName));
|
|
82
|
+
result.summary = result.issues.length === 0
|
|
83
|
+
? `No issues in ${fileName}`
|
|
84
|
+
: `Found ${result.issues.length} issue(s) in ${fileName}`;
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if code compiles after a fix
|
|
89
|
+
*/
|
|
90
|
+
async validateFix(projectPath) {
|
|
91
|
+
const result = await this.analyzeProject(projectPath);
|
|
92
|
+
const errors = result.issues
|
|
93
|
+
.filter(i => i.severity === 'error')
|
|
94
|
+
.map(i => `${i.file}:${i.line} - ${i.message}`);
|
|
95
|
+
return {
|
|
96
|
+
valid: errors.length === 0,
|
|
97
|
+
errors
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Find unused exports/variables in a file
|
|
102
|
+
*/
|
|
103
|
+
async findDeadCode(projectPath) {
|
|
104
|
+
try {
|
|
105
|
+
const result = (0, child_process_1.execSync)('npx tsc --noEmit --noUnusedLocals --noUnusedParameters --pretty false 2>&1 || true', {
|
|
106
|
+
cwd: projectPath,
|
|
107
|
+
encoding: 'utf-8',
|
|
108
|
+
maxBuffer: 10 * 1024 * 1024
|
|
109
|
+
});
|
|
110
|
+
return this.parseTscOutput(result, projectPath)
|
|
111
|
+
.filter(i => i.message.includes('declared but') || i.message.includes('never used'));
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
logger.error('Dead code analysis failed', { error });
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Parse tsc output into structured issues
|
|
120
|
+
*/
|
|
121
|
+
parseTscOutput(output, basePath) {
|
|
122
|
+
const issues = [];
|
|
123
|
+
const lines = output.split('\n');
|
|
124
|
+
// Pattern: src/file.ts(10,5): error TS2304: Cannot find name 'foo'.
|
|
125
|
+
const pattern = /^(.+?)\((\d+),(\d+)\):\s*(error|warning)\s+(TS\d+):\s*(.+)$/;
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
const match = line.match(pattern);
|
|
128
|
+
if (match) {
|
|
129
|
+
issues.push({
|
|
130
|
+
file: path.resolve(basePath, match[1]),
|
|
131
|
+
line: parseInt(match[2], 10),
|
|
132
|
+
column: parseInt(match[3], 10),
|
|
133
|
+
severity: match[4],
|
|
134
|
+
code: match[5],
|
|
135
|
+
message: match[6]
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return issues;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
exports.GiuseppeLsp = GiuseppeLsp;
|
|
143
|
+
//# sourceMappingURL=giuseppe-lsp.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hailer/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"config": {
|
|
5
5
|
"docker": {
|
|
6
6
|
"registry": "registry.gitlab.com/hailer-repos/hailer-mcp"
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"form-data": "^4.0.4",
|
|
43
43
|
"openai": "^5.5.1",
|
|
44
44
|
"sharp": "^0.34.5",
|
|
45
|
+
"typescript-language-server": "^5.1.3",
|
|
45
46
|
"zod": "^3.24.1"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
@@ -54,8 +55,7 @@
|
|
|
54
55
|
"eslint": "^9.29.0",
|
|
55
56
|
"globals": "^16.2.0",
|
|
56
57
|
"tsx": "^4.19.2",
|
|
57
|
-
"typescript": "^5"
|
|
58
|
-
"typescript-language-server": "^5.1.3"
|
|
58
|
+
"typescript": "^5"
|
|
59
59
|
},
|
|
60
60
|
"bin": {
|
|
61
61
|
"hailer-mcp": "dist/cli.js"
|