Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 1 addition & 49 deletions .github/workflows/release-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,50 +206,10 @@ jobs:
path: ./lintellect-api-${{ needs.extract-version.outputs.version }}-${{ matrix.platform }}.${{ matrix.extension }}
retention-days: 30

generate-release-notes:
name: Generate Release Notes
runs-on: ubuntu-latest
needs: [extract-version]
outputs:
release-notes: ${{ steps.notes.outputs.release-notes }}

steps:
- name: Checkout code
uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Generate release notes
id: notes
run: |
# Extract API section from CHANGELOG.md
VERSION="${{ needs.extract-version.outputs.version }}"

# Find the API section for this version
API_SECTION=$(awk "/## \[API v$VERSION\]/,/^## \[/ {if (!/^## \[/) print}" CHANGELOG.md)

if [ -z "$API_SECTION" ]; then
echo "No API section found for version $VERSION in CHANGELOG.md"
API_SECTION="## API v$VERSION\n\nNo release notes available."
fi

# Create release notes
RELEASE_NOTES="## Lintellect API v$VERSION\n\n$API_SECTION\n\n### Docker Images\n\n- \`ghcr.io/${{ github.repository_owner }}/lintellect-api:$VERSION\`\n- \`ghcr.io/${{ github.repository_owner }}/lintellect-api:latest\`\n\n### Installation\n\n\`\`\`bash\n# Pull the latest image\ndocker pull ghcr.io/${{ github.repository_owner }}/lintellect-api:$VERSION\n\n# Run the API\ndocker run -p 7000:7000 ghcr.io/${{ github.repository_owner }}/lintellect-api:$VERSION\n\`\`\`"

echo "release-notes<<EOF" >> $GITHUB_OUTPUT
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT

create-release:
name: Create Release
runs-on: ubuntu-latest
needs:
[
extract-version,
build-docker-images,
build-binaries,
generate-release-notes,
]
needs: [extract-version, build-docker-images, build-binaries]
permissions:
contents: write

Expand All @@ -264,17 +224,10 @@ jobs:
pattern: lintellect-api-*
merge-multiple: false

- name: Write release notes to file
run: |
cat > release-notes.md << 'RELEASE_NOTES_EOF'
${{ needs.generate-release-notes.outputs.release-notes }}
RELEASE_NOTES_EOF

- name: Create release with GitHub CLI
run: |
gh release create ${{ github.ref_name }} \
--title "Lintellect API v${{ needs.extract-version.outputs.version }}" \
--notes-file release-notes.md \
./binaries/lintellect-api-linux-x64/lintellect-api-${{ needs.extract-version.outputs.version }}-linux-x64.tar.gz \
./binaries/lintellect-api-win-x64/lintellect-api-${{ needs.extract-version.outputs.version }}-win-x64.zip
env:
Expand All @@ -293,7 +246,6 @@ jobs:
echo "✅ API v${{ needs.extract-version.outputs.version }} released successfully!"
echo "🐳 Docker images pushed to GitHub Container Registry"
echo "📦 Binary archives attached to GitHub Release"
echo "📝 Release notes published"

- name: Notify failure
if: needs.create-release.result == 'failure'
Expand Down
8 changes: 0 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [CLI v0.0.12] - 2025-11-17

### Changed

- Version bump for compatibility with API v0.0.12

## [API v0.0.12] - 2025-11-17

### Added

- Enhanced webhook comment processing for Azure DevOps PRs with question answering
Expand Down

This file was deleted.

4 changes: 3 additions & 1 deletion src/Lintellect.Api/Application/Interfaces/IGitClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ Task<PullRequestCommentThread> CreateCommentAsync(
string projectName,
string repositoryName,
int pullRequestId,
string comment);
string comment,
int? threadId = null);

/// <summary>
/// Creates a new code change suggestion comment on a pull request.
Expand Down Expand Up @@ -148,4 +149,5 @@ Task AddCodeOwnersToPr(
/// the <see cref="PullRequestCommentThread"/> with details of the specified comment thread.
/// </returns>
Task<PullRequestCommentThread> GetPullRequestThreadContextAsync(string projectName, string repositoryName, int pullRequestId, int prCommentId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public sealed record ProcessAnalysisJobCommand(
public sealed class ProcessAnalysisJobCommandHandler(
IApplicationDbContext context,
PullRequestService prService,
IAnalyzerServiceResolver analyzerResolver) : IRequestHandler<ProcessAnalysisJobCommand, PullRequestAnalysisReportModel>
IAnalyzerService analyzerService) : IRequestHandler<ProcessAnalysisJobCommand, PullRequestAnalysisReportModel>
{
public async ValueTask<PullRequestAnalysisReportModel> Handle(ProcessAnalysisJobCommand request, CancellationToken cancellationToken)
{
Expand All @@ -42,7 +42,6 @@ public async ValueTask<PullRequestAnalysisReportModel> Handle(ProcessAnalysisJob
LinesAdded = 0,
LinesRemoved = 0
},
AnalyzerUsed = analysisRequest.AIAnalyzer.ToString(),
AnalyzedAt = DateTimeOffset.UtcNow,
InlineSuggestions = null
};
Expand All @@ -65,12 +64,11 @@ public async ValueTask<PullRequestAnalysisReportModel> Handle(ProcessAnalysisJob


// Step 2: Prepare analyzer and custom instructions
var analyzer = analyzerResolver.GetAnalyzerService(analysisRequest.AIAnalyzer);
var customInstructions = await prService.GetCustomInstructionsAsync(analysisRequest);
var aiAnalyzerModel = new AnalyzerServiceModel(analysisRequest, customInstructions ?? string.Empty);

// Step 3: Execute analysis tasks in parallel
var analysisResults = await ExecuteAnalysisTasksAsync(analyzer, aiAnalyzerModel, diffs, analysisRequest, cancellationToken);
var analysisResults = await ExecuteAnalysisTasksAsync(analyzerService, aiAnalyzerModel, diffs, analysisRequest, cancellationToken);

// Step 4: Post results to PR
await PostResultsToPullRequestAsync(prService, analysisRequest, analysisResults, cancellationToken);
Expand Down Expand Up @@ -275,7 +273,6 @@ private static PullRequestAnalysisReportModel BuildAnalysisReport(
Summary = results.Summary,
DetailedAnalysis = results.DetailedAnalysis,
DiffStatistics = BuildDiffStatistics(diffs),
AnalyzerUsed = analysisRequest.AIAnalyzer.ToString(),
AnalyzedAt = DateTimeOffset.UtcNow,
InlineSuggestions = results.InlineSuggestions.Count != 0 ? "Inline suggestions posted" : null
};
Expand Down Expand Up @@ -342,6 +339,7 @@ private async Task<bool> CheckForDuplicateAnalysisAsync(AnalysisRequest analysis
var existingJob = await context.AnalysisJobs
.Where(job =>
job.AnalysisRequest != null &&
job.Status == Domain.Enums.AnalysisStatus.Completed &&
job.AnalysisRequest.GitInfo != null &&
job.AnalysisRequest.GitInfo.PullRequestId == pullRequestId &&
job.AnalysisRequest.GitProvider == analysisRequest.GitProvider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public sealed record ProcessCommentWebhookEventCommand(WebhookEvent WebhookEvent
public sealed class ProcessWebhookEventCommandHandler(
ILogger<ProcessWebhookEventCommandHandler> logger,
PullRequestService pullRequestService,
IAnalyzerServiceResolver analyzerResolver
IAnalyzerService analyzerService
) : IRequestHandler<ProcessCommentWebhookEventCommand>
{

Expand Down Expand Up @@ -92,7 +92,7 @@ private async Task HandleAzureDevOpsCommentAsync(WebhookEvent webhookEvent, Canc
var context = BuildQuestionContext(question, [.. threadContext.Comments], customInstructions);

// Answer the question
await AnswerQuestionAsync(analysisRequest, context, question, cancellationToken);
await AnswerQuestionAsync(analysisRequest, context, question, threadContext.Id, cancellationToken);
}

private Task HandleGitHubCommentAsync(WebhookEvent webhookEvent, CancellationToken cancellationToken)
Expand Down Expand Up @@ -138,16 +138,13 @@ private static bool IsQuestion(string comment)
var trimmed = comment.Trim();

// Check if bot is mentioned
if (trimmed.Contains("Lintellect", StringComparison.OrdinalIgnoreCase) ||
if (trimmed.Contains("@lintellect", StringComparison.OrdinalIgnoreCase) ||
trimmed.Contains("lintellect", StringComparison.OrdinalIgnoreCase))
{
return true;
}

// Check if it starts with question words
var questionWords = new[] { "explain", "what", "how", "why", "can you", "help", "tell me", "?" };
return questionWords.Any(word => trimmed.StartsWith(word, StringComparison.OrdinalIgnoreCase)) ||
trimmed.EndsWith("?", StringComparison.OrdinalIgnoreCase);
return false;
}

/// <summary>
Expand Down Expand Up @@ -175,6 +172,7 @@ public async Task AnswerQuestionAsync(
AnalysisRequest request,
string threadContext,
string question,
int threadId,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
Expand All @@ -184,14 +182,14 @@ public async Task AnswerQuestionAsync(

try
{
var analyzer = analyzerResolver.GetAnalyzerService(request.AIAnalyzer);
var instructions = await pullRequestService.GetCustomInstructionsAsync(request);
var model = new AnalyzerServiceModel(request, instructions ?? string.Empty);

var answer = await analyzer.AnswerQuestionAsync(model, threadContext, question, cancellationToken);

var answer = await analyzerService.AnswerQuestionAsync(model, threadContext, question, cancellationToken);

// Post answer back to PR
await pullRequestService.AddCommentAsync(request, answer);
await pullRequestService.AddCommentAsync(request, answer, threadId);
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public sealed class SemanticAnalyzerOptions
/// <summary>
/// The deployment name or model to use.
/// </summary>
public string DeploymentName { get; set; } = "gpt-4o";
public string? DeploymentName { get; set; }

/// <summary>
/// Maximum tokens for the response.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ public sealed class PullRequestAnalysisReportModel
/// </summary>
public required DiffStatistics DiffStatistics { get; init; }

/// <summary>
/// The AI analyzer that was used.
/// </summary>
public required string AnalyzerUsed { get; init; }

/// <summary>
/// When the analysis was performed.
/// </summary>
Expand Down
33 changes: 17 additions & 16 deletions src/Lintellect.Api/ConfigureServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,36 +60,40 @@ public static IServiceCollection AddAnalyzerServices(
options.ApiKey ??= claudeApiKey;
});

services.AddKeyedScoped<IAnalyzerService, ClaudeAnalyzerService>(
EAnalyzers.Claude,
(sp, key) =>
services.AddScoped<IAnalyzerService, ClaudeAnalyzerService>(
(sp) =>
{
var options = sp.GetRequiredService<IOptions<ClaudeAnalyzerOptions>>().Value;
var mcpServiceResolver = sp.GetRequiredService<IMcpServiceResolver>();

return new ClaudeAnalyzerService(options, mcpServiceResolver);
});
}
else
{
var semanticApiKey = configuration.GetValue<string>("SEMANTIC_API_KEY") ??
configuration.GetSection("SemanticAnalyzer:ApiKey").Value;

// Only register Semantic (AIFoundry) if configured
var semanticApiKey = configuration.GetValue<string>("SEMANTIC_API_KEY") ??
configuration.GetSection("SemanticAnalyzer:ApiKey").Value;
var semanticEndpoint = configuration.GetValue<string>("SEMANTIC_ENDPOINT") ??
configuration.GetSection("SemanticAnalyzer:Endpoint").Value;
var semanticEndpoint = configuration.GetValue<string>("SEMANTIC_ENDPOINT") ??
configuration.GetSection("SemanticAnalyzer:Endpoint").Value;

var semanticDeploymentName = configuration.GetValue<string>("SEMANTIC_DEPLOYMENT_NAME") ??
configuration.GetSection("SemanticAnalyzer:DeploymentName").Value;

if (!string.IsNullOrWhiteSpace(semanticApiKey) || !string.IsNullOrWhiteSpace(semanticEndpoint))
{
services.Configure<SemanticAnalyzerOptions>(options =>
{
configuration.GetSection("SemanticAnalyzer").Bind(options);
configureSemanticOptions?.Invoke(options);

options.ApiKey ??= semanticApiKey;
options.Endpoint ??= semanticEndpoint;

options.DeploymentName ??= semanticDeploymentName;
options.DeploymentName ??= "gpt-4o"; //fallback
});

services.AddKeyedScoped<IAnalyzerService, SemanticAnalyzerService>(
EAnalyzers.AIFoundry,
(sp, key) =>
services.AddScoped<IAnalyzerService, SemanticAnalyzerService>(
(sp) =>
{
var options = sp.GetRequiredService<IOptions<SemanticAnalyzerOptions>>().Value;
var mcpResolver = sp.GetRequiredService<IMcpServiceResolver>();
Expand All @@ -98,9 +102,6 @@ public static IServiceCollection AddAnalyzerServices(
});
}

// Register the resolver that picks the right analyzer based on configuration
services.AddScoped<IAnalyzerServiceResolver, AnalyzerServiceResolver>();

services.AddKeyedSingleton<IMcpService, Context7McpService>(EMcpServer.Context7);
services.AddKeyedSingleton<IMcpService, MicrosoftDocsMcpService>(EMcpServer.MicrosoftDocs);
services.AddScoped<IMcpServiceResolver, McpServiceResolver>();
Expand Down
3 changes: 1 addition & 2 deletions src/Lintellect.Api/Domain/Entities/AnalysisJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public AnalysisJob(AnalysisRequest cliAnalysisResult)
ArgumentNullException.ThrowIfNull(cliAnalysisResult);

Status = AnalysisStatus.Pending;
AnalysisRequest = CloneAnalysisRequest(cliAnalysisResult);
AnalysisRequest = cliAnalysisResult;

AddDomainEvent(new AnalysisJobCreatedEvent(Id,
cliAnalysisResult.GitInfo?.ProjectName ?? "Unknown",
Expand Down Expand Up @@ -123,7 +123,6 @@ private static AnalysisRequest CloneAnalysisRequest(AnalysisRequest request)
EnableDescriptionSummary = request.EnableDescriptionSummary,
EnableAzureDevopsCodeOwners = request.EnableAzureDevopsCodeOwners,
McpServer = request.McpServer is null ? [] : [.. request.McpServer],
AIAnalyzer = request.AIAnalyzer
};
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
You are an expert C# code reviewer providing inline code suggestions for {{gitProvider}} pull requests.
You are an expert C# code reviewer providing inline code suggestions for pull requests.

## Your Role:

You are NOT just a static analysis findings reporter. You are a COMPREHENSIVE C# code reviewer who:
You are a COMPREHENSIVE C# code reviewer who:

1. Reviews every line of changed C# code for issues beyond what static analyzers catch
2. Identifies security vulnerabilities, logic errors, performance issues, and bugs specific to C#
3. Suggests C# best practices and code quality improvements
4. Provides fixes for C# static analyzer findings (CA rules, compiler warnings)
5. ONLY make actionable suggestions with clear "what" and "how".
6. Avoid bikeshedding or subjective style preferences.
7. NEVER a comment if there are no issues to address.
8. You don't need to summarize changes; focus on inline suggestions only.

## C# Specific Guidelines:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ You are NOT just a static analysis findings reporter. You are a COMPREHENSIVE Ja
2. Identifies security vulnerabilities, logic errors, performance issues, and bugs specific to Java
3. Suggests Java best practices and code quality improvements
4. Provides fixes for Java static analyzer findings (SpotBugs, PMD, Checkstyle, SonarQube)
5. ONLY make actionable suggestions with clear "what" and "how".
6. Avoid bikeshedding or subjective style preferences.
7. NEVER a comment if there are no issues to address.
8. You don't need to summarize changes; focus on inline suggestions only.

## Your Task:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ You are NOT just a static analysis findings reporter. You are a COMPREHENSIVE Ja
2. Identifies security vulnerabilities, logic errors, performance issues, and bugs specific to JavaScript
3. Suggests JavaScript best practices and code quality improvements
4. Provides fixes for JavaScript static analyzer findings (ESLint, JSHint, etc.)
5. ONLY make actionable suggestions with clear "what" and "how".
6. Avoid bikeshedding or subjective style preferences.
7. NEVER a comment if there are no issues to address.
8. You don't need to summarize changes; focus on inline suggestions only.

## Your Task:
Generate inline code suggestions as structured JSON that can be posted as PR comments.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ You are NOT just a static analysis findings reporter. You are a COMPREHENSIVE Py
2. Identifies security vulnerabilities, logic errors, performance issues, and bugs specific to Python
3. Suggests Python best practices and code quality improvements
4. Provides fixes for Python static analyzer findings (pylint, flake8, mypy, bandit)
5. ONLY make actionable suggestions with clear "what" and "how".
6. Avoid bikeshedding or subjective style preferences.
7. NEVER a comment if there are no issues to address.
8. You don't need to summarize changes; focus on inline suggestions only.

## Your Task:

Expand Down
Loading
Loading