Code Quality Dashboards
Metrics Collection
Repository Health Overview using Claude: Ask Claude: βGive me a health overview of my repositories using Greptile dataβ Claude can now:- List all merge requests across your repositories
- Get code review completion rates
- Count unaddressed security comments
- Identify repositories with the most issues
- Calculate review coverage percentages
- Total PRs reviewed vs unreviewed
- Number of open security issues
- Most active repositories by review volume
- Custom context adoption rates
Copy
Ask AI
async function generateTeamReport(timeRange) {
const reviews = await mcpClient.call('list_code_reviews', {
limit: 200
});
const completedReviews = reviews.codeReviews
.filter(r => r.status === 'COMPLETED')
.filter(r => withinTimeRange(r.completedAt, timeRange));
const metrics = {
totalReviews: completedReviews.length,
averageReviewTime: calculateAverageTime(completedReviews),
reviewThroughput: completedReviews.length / timeRange.days,
topRepositories: getTopRepositories(completedReviews),
issueCategories: categorizeIssues(completedReviews)
};
return metrics;
}
Visualization Integration
Grafana Dashboard:Copy
Ask AI
// grafana-greptile-datasource.js
class GreptileDatasource {
async query(options) {
const queries = options.targets.map(async (target) => {
switch (target.metric) {
case 'review_count':
return this.getReviewCount(target.timeRange);
case 'comment_trends':
return this.getCommentTrends(target.timeRange);
case 'repository_health':
return this.getRepositoryHealth();
}
});
return Promise.all(queries);
}
async getReviewCount(timeRange) {
const reviews = await mcpClient.call('list_code_reviews', {
limit: 1000
});
return reviews.codeReviews
.filter(r => withinTimeRange(r.createdAt, timeRange))
.reduce((acc, review) => {
const date = review.createdAt.split('T')[0];
acc[date] = (acc[date] || 0) + 1;
return acc;
}, {});
}
}
CI/CD Integration
The MCP server enables automated workflows that check code quality before merges.Pre-merge Validation
Ask Claude to validate PRs: βCheck if this PR has any unaddressed Greptile security commentsβ Claude can:- Verify all critical comments are addressed
- Check if code review is complete
- Identify blocking issues before merge
- Trigger new reviews if needed
Copy
Ask AI
# .github/workflows/greptile-validation.yml
name: Greptile Pre-merge Validation
on:
pull_request:
types: [opened, synchronize]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Check Review Status
env:
GREPTILE_API_KEY: ${{ secrets.GREPTILE_API_KEY }}
run: |
node -e "
const prNumber = process.env.GITHUB_EVENT.number;
async function validate() {
// Check if review exists and is complete
const pr = await mcpClient.call('get_merge_request', {
name: '${{ github.repository }}',
remote: 'github',
defaultBranch: 'main',
prNumber: prNumber
});
const hasCompleteReview = pr.mergeRequest.codeReviews
.some(r => r.status === 'COMPLETED');
if (!hasCompleteReview) {
console.log('Triggering code review...');
await mcpClient.call('trigger_code_review', {
name: '${{ github.repository }}',
remote: 'github',
defaultBranch: 'main',
prNumber: prNumber
});
process.exit(1); // Block merge until review completes
}
// Check for unaddressed critical comments
const comments = await mcpClient.call('list_merge_request_comments', {
name: '${{ github.repository }}',
remote: 'github',
defaultBranch: 'main',
prNumber: prNumber,
addressed: false
});
const criticalComments = comments.comments.filter(c =>
c.body.includes('security') || c.body.includes('critical')
);
if (criticalComments.length > 0) {
console.log('Critical issues must be addressed before merge');
process.exit(1);
}
}
validate().catch(console.error);
"
Copy
Ask AI
# .gitlab-ci.yml
stages:
- review-validation
greptile-check:
stage: review-validation
script:
- |
# Get merge request details
MR_DATA=$(curl -s "https://api.greptile.com/v2/mcp" \
-H "Authorization: Bearer $GREPTILE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_merge_request",
"arguments": {
"name": "'$CI_PROJECT_PATH'",
"remote": "gitlab",
"defaultBranch": "'$CI_DEFAULT_BRANCH'",
"prNumber": '$CI_MERGE_REQUEST_IID'
}
}
}')
# Check review status and block if incomplete
echo $MR_DATA | jq -e '.result.content[0].text | fromjson | .mergeRequest.codeReviews | map(select(.status == "COMPLETED")) | length > 0'
only:
- merge_requests
Automated Review Triggering
Webhook Handler:Copy
Ask AI
// webhook-handler.js
const express = require('express');
const app = express();
app.post('/github/webhook', async (req, res) => {
const { action, pull_request, repository } = req.body;
if (action === 'opened' || action === 'synchronize') {
// Auto-trigger review for new or updated PRs
try {
await mcpClient.call('trigger_code_review', {
name: repository.full_name,
remote: 'github',
defaultBranch: repository.default_branch,
prNumber: pull_request.number
});
console.log(`Review triggered for PR #${pull_request.number}`);
} catch (error) {
console.error('Failed to trigger review:', error);
}
}
res.status(200).send('OK');
});
Development Workflows
IDE Integration
VS Code Extension:Copy
Ask AI
// extension.js
const vscode = require('vscode');
function activate(context) {
// Register command to get PR context
const prContextCommand = vscode.commands.registerCommand(
'greptile.getPRContext',
async () => {
const workspaceFolder = vscode.workspace.workspaceFolders[0];
const currentBranch = await git.getCurrentBranch();
// Get PR for current branch
const prs = await mcpClient.call('list_merge_requests', {
name: workspaceFolder.name,
remote: 'github',
defaultBranch: 'main',
state: 'open',
limit: 50
});
const currentPR = prs.mergeRequests.find(pr =>
pr.branches?.source === currentBranch
);
if (currentPR) {
const prDetails = await mcpClient.call('get_merge_request', {
name: workspaceFolder.name,
remote: 'github',
defaultBranch: 'main',
prNumber: currentPR.number
});
// Show PR info in panel
showPRPanel(prDetails.mergeRequest);
}
}
);
context.subscriptions.push(prContextCommand);
}
Onboarding Workflows
New Developer Setup:Copy
Ask AI
async function generateOnboardingGuide(repository) {
// Get organization patterns
const patterns = await mcpClient.call('list_custom_context', {
type: 'CUSTOM_INSTRUCTION',
limit: 50
});
// Get recent review patterns
const recentComments = await mcpClient.call('search_greptile_comments', {
query: repository.name,
limit: 100,
includeAddressed: true
});
// Analyze common issues
const commonIssues = categorizeComments(recentComments.comments);
return {
codingStandards: patterns.customContexts,
commonPitfalls: commonIssues.topCategories,
exampleFixes: commonIssues.resolutions,
setupChecklist: generateSetupTasks(patterns.customContexts)
};
}
Analytics and Monitoring
Code Quality Trends
Historical Analysis:Copy
Ask AI
async function analyzeQualityTrends(timeRange, repositories) {
const data = [];
for (let date of generateDateRange(timeRange)) {
const dayMetrics = await Promise.all(repositories.map(async (repo) => {
const [reviews, comments] = await Promise.all([
mcpClient.call('list_code_reviews', {
name: repo.name,
remote: repo.remote,
defaultBranch: repo.branch,
limit: 100
}),
mcpClient.call('search_greptile_comments', {
query: `${repo.name} ${date}`,
limit: 100
})
]);
const dayReviews = reviews.codeReviews.filter(r =>
r.createdAt.startsWith(date)
);
const dayComments = comments.comments.filter(c =>
c.createdAt.startsWith(date)
);
return {
date,
repository: repo.name,
reviewsCount: dayReviews.length,
issuesFound: dayComments.length,
criticalIssues: dayComments.filter(c =>
c.body.includes('security') || c.body.includes('critical')
).length
};
}));
data.push(...dayMetrics);
}
return data;
}
Pattern Effectiveness
Pattern Usage Analytics:Copy
Ask AI
async function analyzePatternEffectiveness() {
// Get all patterns
const patterns = await mcpClient.call('list_custom_context', {
limit: 200
});
const effectiveness = await Promise.all(
patterns.customContexts.map(async (pattern) => {
// Get comments linked to this pattern
const linkedComments = await mcpClient.call('search_greptile_comments', {
query: pattern.id,
limit: 100,
includeAddressed: true
});
const comments = linkedComments.comments.filter(c =>
c.linkedMemory?.id === pattern.id
);
const addressed = comments.filter(c => c.addressed);
return {
patternId: pattern.id,
patternBody: pattern.body,
timesTriggered: comments.length,
resolutionRate: addressed.length / comments.length,
averageResolutionTime: calculateAverageResolutionTime(comments),
effectiveness: calculateEffectivenessScore(comments)
};
})
);
return effectiveness.sort((a, b) => b.effectiveness - a.effectiveness);
}
Automation Workflows
Slack Integration
Daily Standups:Copy
Ask AI
async function sendDailyStandup() {
const yesterday = getYesterday();
// Get yesterday's reviews
const reviews = await mcpClient.call('list_code_reviews', {
limit: 100
});
const yesterdayReviews = reviews.codeReviews.filter(r =>
r.completedAt?.startsWith(yesterday)
);
// Get unaddressed comments
const openComments = await mcpClient.call('search_greptile_comments', {
query: 'created:' + yesterday,
limit: 50,
includeAddressed: false
});
const message = {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `*Daily Code Review Summary - ${yesterday}*`
}
},
{
type: "section",
fields: [
{
type: "mrkdwn",
text: `*Reviews Completed:* ${yesterdayReviews.length}`
},
{
type: "mrkdwn",
text: `*Open Issues:* ${openComments.comments.length}`
}
]
}
]
};
await slackClient.chat.postMessage({
channel: '#engineering',
...message
});
}
Release Management
Pre-release Validation:Copy
Ask AI
async function validateRelease(releaseTag, repositories) {
const issues = [];
for (const repo of repositories) {
// Get unaddressed critical comments
const criticalComments = await mcpClient.call('search_greptile_comments', {
query: `${repo.name} security OR critical`,
limit: 100,
includeAddressed: false
});
if (criticalComments.comments.length > 0) {
issues.push({
repository: repo.name,
type: 'critical_comments',
count: criticalComments.comments.length,
details: criticalComments.comments.slice(0, 5)
});
}
// Check for incomplete reviews on recent PRs
const recentPRs = await mcpClient.call('list_merge_requests', {
name: repo.name,
remote: repo.remote,
defaultBranch: repo.branch,
state: 'merged',
limit: 50
});
const unreviewedPRs = recentPRs.mergeRequests.filter(pr =>
!pr.codeReviews || pr.codeReviews.length === 0
);
if (unreviewedPRs.length > 0) {
issues.push({
repository: repo.name,
type: 'unreviewed_prs',
count: unreviewedPRs.length,
prs: unreviewedPRs.slice(0, 5)
});
}
}
return {
canRelease: issues.length === 0,
blockers: issues,
recommendation: issues.length === 0
? 'Release approved'
: 'Address issues before release'
};
}