Skip to content
Draft
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
62 changes: 52 additions & 10 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ The Explain Error Plugin is a Jenkins plugin that provides AI-powered explanatio
- **GlobalConfigurationImpl**: Main plugin configuration class with `@Symbol("explainError")` for Configuration as Code support, handles migration from legacy enum-based configuration
- **BaseAIProvider**: Abstract base class for AI provider implementations with nested `Assistant` interface and `BaseProviderDescriptor` for extensibility
- **OpenAIProvider** / **GeminiProvider** / **BedrockProvider** / **OllamaProvider**: LangChain4j-based AI service implementations with provider-specific configurations
- **ExplainErrorStep**: Pipeline step implementation for `explainError()` function (supports `logPattern`, `maxLines`, `language`, `customContext` parameters)
- **ExplainErrorStep**: Pipeline step implementation for `explainError()` function (supports `logPattern`, `maxLines`, `language`, `customContext`, `collectDownstreamLogs`, `downstreamJobPattern`, and all `autoFix*` parameters)
- **ExplainErrorFolderProperty**: Folder-level AI provider override — allows teams to configure their own provider without touching global settings; walks up the folder hierarchy
- **ConsoleExplainErrorAction**: Adds "Explain Error" button to console output for manual triggering
- **ConsoleExplainErrorActionFactory**: TransientActionFactory that dynamically injects ConsoleExplainErrorAction into all runs (new and existing)
- **ErrorExplanationAction**: Build action for storing and displaying AI explanations
- **ConsolePageDecorator**: UI decorator to show explain button when conditions are met
- **ErrorExplainer**: Core error analysis logic that coordinates AI providers and log parsing; resolves provider priority (step > folder > global)
- **AutoFixOrchestrator**: Coordinates the full AI auto-fix flow — AI suggestion → diff validation → branch creation → file commits → pull request; handles rollback on failure
- **AutoFixAction**: Build action that persists and displays the auto-fix PR URL in the Jenkins sidebar
- **FixAssistant**: LangChain4j AI service interface that requests a structured fix suggestion (fixable flag, file diffs, confidence score)
- **UnifiedDiffApplier**: Parses and applies unified diffs to file content with ±3-line fuzzy matching; validates diffs before any branch is created
- **ScmApiClient / GitHubApiClient / GitLabApiClient / BitbucketApiClient**: SCM-provider-specific REST API clients using JDK `HttpClient` (zero extra dependencies); support GitHub Enterprise, GitLab self-managed, and Bitbucket Cloud
- **PipelineLogExtractor**: Extracts logs from the specific failing Pipeline step node (via `FlowGraphWalker`); integrates with optional `pipeline-graph-view` plugin for deep-linking
- **JenkinsLogAnalysis**: Structured record for AI response (errorSummary, resolutionSteps, bestPractices, errorSignature)
- **ExplanationException**: Custom exception for error explanation failures
Expand All @@ -28,7 +33,7 @@ The Explain Error Plugin is a Jenkins plugin that provides AI-powered explanatio
```
src/main/java/io/jenkins/plugins/explain_error/
├── GlobalConfigurationImpl.java # Plugin configuration & CasC + migration logic
├── ExplainErrorStep.java # Pipeline step (logPattern, maxLines, language, customContext)
├── ExplainErrorStep.java # Pipeline step (logPattern, maxLines, language, customContext, autoFix*)
├── ExplainErrorFolderProperty.java # Folder-level AI provider override
├── ErrorExplainer.java # Core error analysis logic (provider resolution)
├── PipelineLogExtractor.java # Failing step log extraction + pipeline-graph-view URL
Expand All @@ -39,12 +44,29 @@ src/main/java/io/jenkins/plugins/explain_error/
├── JenkinsLogAnalysis.java # Structured AI response record
├── ExplanationException.java # Custom exception for error handling
├── AIProvider.java # @Deprecated enum (backward compatibility)
└── provider/
├── BaseAIProvider.java # Abstract AI service with Assistant interface
├── OpenAIProvider.java # OpenAI/LangChain4j implementation
├── GeminiProvider.java # Google Gemini/LangChain4j implementation
├── BedrockProvider.java # AWS Bedrock/LangChain4j implementation
└── OllamaProvider.java # Ollama/LangChain4j implementation
├── provider/
│ ├── BaseAIProvider.java # Abstract AI service with Assistant interface
│ ├── OpenAIProvider.java # OpenAI/LangChain4j implementation
│ ├── GeminiProvider.java # Google Gemini/LangChain4j implementation
│ ├── BedrockProvider.java # AWS Bedrock/LangChain4j implementation
│ └── OllamaProvider.java # Ollama/LangChain4j implementation
└── autofix/
├── AutoFixOrchestrator.java # AI suggestion → branch → commits → PR (with rollback)
├── AutoFixAction.java # Build action: persists & displays PR URL in sidebar
├── AutoFixResult.java # Result value object (status + PR URL + message)
├── AutoFixStatus.java # Enum: CREATED, FAILED, SKIPPED_*, NOT_APPLICABLE
├── FixAssistant.java # LangChain4j interface for structured fix suggestions
├── FixSuggestion.java # AI response: fixable flag, file diffs, confidence
├── UnifiedDiffApplier.java # Applies unified diffs with ±3-line fuzzy matching
└── scm/
├── ScmApiClient.java # Interface: createBranch, commitFiles, createPullRequest
├── ScmClientFactory.java # Creates right client based on ScmType
├── ScmRepo.java # Value object: type + baseUrl + owner/repo + token
├── ScmType.java # Enum: GITHUB, GITLAB, BITBUCKET
├── GitHubApiClient.java # GitHub REST v3 — Git Trees API (atomic multi-file commit)
├── GitLabApiClient.java # GitLab REST v4 — Commits API with actions array
├── BitbucketApiClient.java # Bitbucket Cloud REST v2 — multipart /src commit
└── PullRequest.java # Value object: number + URL + branch names
```

## Coding Standards
Expand Down Expand Up @@ -119,6 +141,10 @@ Use `provider.setThrowError(true)` to simulate failures, `provider.getLastCustom
- Folder-level provider override (`ExplainErrorFolderPropertyTest`)
- Error explanation display (`ErrorExplanationActionTest`)
- Log extraction (`PipelineLogExtractorTest`)
- Auto-fix orchestration paths (`autofix/AutoFixOrchestratorTest`) — uses Mockito; covers fixable/not-fixable/empty-changes/path-guard flows
- SCM API clients (`autofix/GitHubApiClientTest`, `GitLabApiClientTest`, `BitbucketApiClientTest`) — WireMock integration tests; cover happy paths, auth failures, retry on 429/5xx
- `ScmRepo` parsing (`autofix/ScmRepoTest`) — SSH/HTTPS URL parsing, `parseWithOverride`, token redaction in `toString()`
- Unified diff application (`autofix/UnifiedDiffApplierTest`) — add/remove/modify hunks, multi-hunk, empty file, fuzzy matching

## Build & Dependencies

Expand All @@ -128,7 +154,7 @@ Use `provider.setThrowError(true)` to simulate failures, `provider.getLastCustom
- LangChain4j: v1.11.0 (langchain4j, langchain4j-open-ai, langchain4j-google-ai-gemini, langchain4j-bedrock, langchain4j-ollama)
- Key Jenkins dependencies: `jackson2-api`, `workflow-step-api`, `commons-lang3-api`
- SLF4J and Jackson exclusions to avoid conflicts with Jenkins core
- Test dependencies: `workflow-cps`, `workflow-job`, `workflow-durable-task-step`, `workflow-basic-steps`, `test-harness`
- Test dependencies: `workflow-cps`, `workflow-job`, `workflow-durable-task-step`, `workflow-basic-steps`, `test-harness`, `wiremock-standalone` (for SCM API integration tests)
- Key dependencies: `jackson2-api`, `workflow-step-api`, `commons-lang3-api`

### Commands
Expand Down Expand Up @@ -183,6 +209,22 @@ pipeline {
language: 'Chinese', // Response language (default: English)
customContext: 'This is a Maven project' // Step-level context; overrides global customContext
)

// Auto-fix: AI generates a code fix and opens a PR automatically
explainError(
autoFix: true,
autoFixCredentialsId: 'github-pat', // Jenkins credential with repo write access
autoFixAllowedPaths: 'pom.xml,*.yml', // Restrict files the AI may change
autoFixDraftPr: true // Open as draft PR (GitHub only)
)

// Auto-fix on a self-hosted GitLab instance
explainError(
autoFix: true,
autoFixCredentialsId: 'gitlab-pat',
autoFixScmType: 'gitlab',
autoFixGitlabUrl: 'https://gitlab.company.com'
)
}
}
}
Expand Down Expand Up @@ -253,7 +295,7 @@ Create `src/test/java/io/jenkins/plugins/explain_error/provider/AnthropicProvide

### Step 5 — Update Documentation

- Add provider to `README.md` feature list and CasC YAML example
- Add provider to `README.md` feature list, Supported AI Providers section, and CasC YAML example
- Update `copilot-instructions.md` provider list and Key Components

### Best Practices
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ work
.settings
.classpath
.project
.gstack/
32 changes: 27 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Run `make help` to see all available test-related targets.

### Writing Tests

We use JUnit 5 and Mockito for testing. Examples:
We use JUnit 5, Mockito, and WireMock for testing. Examples:

```java
@ExtendWith(MockitoExtension.class)
Expand Down Expand Up @@ -141,11 +141,33 @@ The plugin follows these patterns:
```
src/main/java/io/jenkins/plugins/explain_error/
├── GlobalConfigurationImpl.java # Main plugin class
├── ExplainErrorStep.java # Pipeline step implementation
├── AIService.java # AI communication service
├── ErrorExplainer.java # Error analysis logic
├── ExplainErrorStep.java # Pipeline step (explainError + autoFix parameters)
├── ErrorExplainer.java # Error analysis logic (provider resolution)
├── ConsoleExplainErrorAction.java # Console button action
└── ErrorExplanationAction.java # Build action for storing results
├── ErrorExplanationAction.java # Build action for storing results
├── provider/
│ ├── BaseAIProvider.java # Abstract AI service base class
│ ├── OpenAIProvider.java # OpenAI / LangChain4j
│ ├── GeminiProvider.java # Google Gemini / LangChain4j
│ ├── BedrockProvider.java # AWS Bedrock / LangChain4j
│ └── OllamaProvider.java # Ollama / LangChain4j
└── autofix/
├── AutoFixOrchestrator.java # Coordinates AI suggestion → branch → PR flow
├── AutoFixAction.java # Build action that stores and displays the PR URL
├── AutoFixResult.java # Result value object (status + PR URL + message)
├── AutoFixStatus.java # Enum: CREATED, FAILED, SKIPPED_*, NOT_APPLICABLE
├── FixAssistant.java # LangChain4j AI service interface for fix suggestions
├── FixSuggestion.java # Structured AI response (fixable, changes, confidence)
├── UnifiedDiffApplier.java # Applies unified diffs to file content (fuzzy match)
└── scm/
├── ScmApiClient.java # Interface: createBranch, commitFiles, createPullRequest…
├── ScmClientFactory.java # Factory: creates the right client from ScmRepo
├── ScmRepo.java # Value object: SCM type + base URL + owner/repo + token
├── ScmType.java # Enum: GITHUB, GITLAB, BITBUCKET
├── GitHubApiClient.java # GitHub REST API v3 (Git Trees API for atomic commits)
├── GitLabApiClient.java # GitLab REST API v4 (Commits API)
├── BitbucketApiClient.java # Bitbucket Cloud REST API v2
└── PullRequest.java # Value object returned by createPullRequest()
```

### Adding New Features
Expand Down
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Whether it’s a compilation error, test failure, or deployment hiccup, this plu
* **One-click error analysis** on any console output
* **Pipeline-ready** with a simple `explainError()` step
* **AI-powered explanations** via OpenAI GPT models, Google Gemini or local Ollama models
* **AI auto-fix** — automatically opens a pull request on GitHub, GitLab, or Bitbucket with AI-generated code changes when a build fails
* **Folder-level configuration** so teams can use project-specific settings
* **Smart provider management** — LangChain4j handles most providers automatically
* **Customizable**: set provider, model, API endpoint (enterprise-ready)[^1], log filters, and more
Expand Down Expand Up @@ -242,6 +243,16 @@ post {
| **customContext** | Additional instructions or context for the AI. Overrides global custom context if specified. | Uses global configuration |
| **collectDownstreamLogs** | Whether to include logs from failed downstream jobs discovered via the `build` step or `Cause.UpstreamCause` | `false` |
| **downstreamJobPattern** | Regular expression matched against downstream job full names. Used only when downstream collection is enabled. | `''` (collect none) |
| **autoFix** | Enable AI auto-fix: the plugin will attempt to generate and commit a code fix, then open a pull request | `false` |
| **autoFixCredentialsId** | Jenkins credentials ID for a personal access token with write access to the repository | `''` |
| **autoFixScmType** | SCM type override: `github`, `gitlab`, or `bitbucket`. Required for self-hosted instances whose hostname is not `github.com`, `gitlab.com`, or `bitbucket.org` | Auto-detected from remote URL |
| **autoFixGithubEnterpriseUrl** | Base URL of your GitHub Enterprise instance (e.g. `https://github.company.com`) | `''` (uses `api.github.com`) |
| **autoFixGitlabUrl** | Base URL of your self-hosted GitLab instance (e.g. `https://gitlab.company.com`) | `''` (uses `gitlab.com`) |
| **autoFixBitbucketUrl** | Base URL of your self-hosted Bitbucket instance | `''` (uses `api.bitbucket.org`) |
| **autoFixAllowedPaths** | Comma-separated list of file glob patterns the AI is permitted to modify | `pom.xml,build.gradle,*.yml,*.yaml,...` |
| **autoFixDraftPr** | Open the pull request as a draft (GitHub only) | `false` |
| **autoFixTimeoutSeconds** | Maximum seconds to wait for the auto-fix to complete | `60` |
| **autoFixPrTemplate** | Custom Markdown template for the PR body. Supports `{jobName}`, `{buildNumber}`, `{explanation}`, `{changesSummary}`, `{fixType}`, `{confidence}` placeholders | Built-in template |

```groovy
explainError(
Expand Down Expand Up @@ -273,6 +284,50 @@ Output appears in the sidebar of the failed job.

![Side Panel - AI Error Explanation](docs/images/side-panel.png)

### Auto-Fix: Automatic Pull Request Creation

When `autoFix: true` is set, the plugin goes one step further than explaining the error — it asks the AI to generate a code fix, commits the changes to a new branch, and opens a pull request for your review.

**Quick start:**

```groovy
post {
failure {
explainError(
autoFix: true,
autoFixCredentialsId: 'github-pat' // Jenkins credential with repo write access
)
}
}
```

The pull request is created on the same repository the build checks out from. The URL appears in the Jenkins build sidebar as soon as the PR is opened.

**Self-hosted SCM (GitHub Enterprise / GitLab self-managed / Bitbucket Server):**

```groovy
explainError(
autoFix: true,
autoFixCredentialsId: 'gitlab-pat',
autoFixScmType: 'gitlab',
autoFixGitlabUrl: 'https://gitlab.company.com'
)
```

**Restrict which files the AI may change** (recommended for production):

```groovy
explainError(
autoFix: true,
autoFixCredentialsId: 'github-pat',
autoFixAllowedPaths: 'pom.xml,build.gradle,*.properties'
)
```

The AI will only propose changes to files matching the glob patterns. Any attempt to modify files outside the list is rejected before a branch is created.

> **Note:** Auto-fix requires a personal access token (PAT) with write access to the repository. It does **not** use the SSH key used to check out the repository.

### Method 2: Manual Console Analysis

Works with Freestyle, Declarative, or any job type.
Expand Down
21 changes: 21 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,19 @@
<artifactId>cloudbees-folder</artifactId>
</dependency>

<!-- For credentials lookup (plain-credentials) -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plain-credentials</artifactId>
</dependency>

<!-- For git SCM URL extraction (optional compile dependency) -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git</artifactId>
<optional>true</optional>
</dependency>

<!-- Needed only for testing -->
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
Expand Down Expand Up @@ -160,6 +173,14 @@
<scope>test</scope>
</dependency>

<!-- WireMock for SCM API integration tests -->
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>3.0.1</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,34 @@

private String providerName;
private String urlString;
private String lastErrorLogs;

private static final Logger LOGGER = Logger.getLogger(ErrorExplainer.class.getName());

public String getProviderName() {
return providerName;
}

/**
* Returns the error logs extracted during the last call to {@link #explainError}.
* Returns {@code null} if {@code explainError} has not been called yet.
*/
public String getLastErrorLogs() {
return lastErrorLogs;
}

/**
* Returns the resolved AI provider for the given run (folder-level first, then global).
* Delegates to the private {@link #resolveProvider(Run)} method.
*
* @param run the build run to resolve the provider for
* @return the resolved AI provider, or {@code null} if none is configured
*/
@CheckForNull
public BaseAIProvider getResolvedProvider(@CheckForNull Run<?, ?> run) {
return resolveProvider(run);

Check warning on line 54 in src/main/java/io/jenkins/plugins/explain_error/ErrorExplainer.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 42-54 are not covered by tests
}

public String explainError(Run<?, ?> run, TaskListener listener, String logPattern, int maxLines) {
return explainError(run, listener, logPattern, maxLines, null, null, false, null, null);
}
Expand Down Expand Up @@ -72,6 +93,7 @@
// Extract error logs
String errorLogs = extractErrorLogs(run, logPattern, maxLines, collectDownstreamLogs,
downstreamJobPattern, authentication);
this.lastErrorLogs = errorLogs;

// Use step-level customContext if provided, otherwise fallback to global
String effectiveCustomContext = StringUtils.isNotBlank(customContext) ? customContext : GlobalConfigurationImpl.get().getCustomContext();
Expand Down
Loading
Loading