Files
git.stella-ops.org/docs/modules/advisory-ai/guides/scm-connector-plugins.md

13 KiB

SCM Connector Plugins

Sprint: SPRINT_20251226_016_AI_remedy_autopilot Tasks: REMEDY-08 through REMEDY-14

This guide documents the SCM (Source Control Management) connector plugin architecture for automated remediation PR generation.

Overview

StellaOps supports automated Pull Request generation for remediation plans across multiple SCM platforms. The plugin architecture enables customer-premise integrations with:

  • GitHub (github.com and GitHub Enterprise Server)
  • GitLab (gitlab.com and self-hosted)
  • Azure DevOps (Services and Server)
  • Gitea (including Forgejo and Codeberg)

Architecture

Plugin Interface

public interface IScmConnectorPlugin
{
    string ScmType { get; }           // "github", "gitlab", "azuredevops", "gitea"
    string DisplayName { get; }        // Human-readable name
    bool IsAvailable(ScmConnectorOptions options);  // Check if configured
    bool CanHandle(string repositoryUrl);           // Auto-detect from URL
    IScmConnector Create(ScmConnectorOptions options, HttpClient httpClient);
}

Connector Interface

public interface IScmConnector
{
    string ScmType { get; }

    // Branch operations
    Task<BranchResult> CreateBranchAsync(
        string owner, string repo, string branchName, string baseBranch, ...);

    // File operations
    Task<FileUpdateResult> UpdateFileAsync(
        string owner, string repo, string branch, string filePath,
        string content, string commitMessage, ...);

    // Pull request operations
    Task<PrCreateResult> CreatePullRequestAsync(
        string owner, string repo, string headBranch, string baseBranch,
        string title, string body, ...);
    Task<PrStatusResult> GetPullRequestStatusAsync(...);
    Task<bool> UpdatePullRequestAsync(...);
    Task<bool> AddCommentAsync(...);
    Task<bool> ClosePullRequestAsync(...);

    // CI status
    Task<CiStatusResult> GetCiStatusAsync(
        string owner, string repo, string commitSha, ...);
}

Catalog and Factory

public sealed class ScmConnectorCatalog
{
    // Get connector by explicit type
    IScmConnector? GetConnector(string scmType, ScmConnectorOptions options);

    // Auto-detect SCM type from repository URL
    IScmConnector? GetConnectorForRepository(string repositoryUrl, ScmConnectorOptions options);

    // List all available plugins
    IReadOnlyList<IScmConnectorPlugin> Plugins { get; }
}

Configuration

Sample Configuration

scmConnectors:
  timeoutSeconds: 30
  userAgent: "StellaOps.AdvisoryAI.Remediation/1.0"

  github:
    enabled: true
    baseUrl: ""  # Default: https://api.github.com
    apiToken: "${GITHUB_PAT}"

  gitlab:
    enabled: true
    baseUrl: ""  # Default: https://gitlab.com/api/v4
    apiToken: "${GITLAB_PAT}"

  azuredevops:
    enabled: true
    baseUrl: ""  # Default: https://dev.azure.com
    apiToken: "${AZURE_DEVOPS_PAT}"

  gitea:
    enabled: true
    baseUrl: "https://git.example.com"  # Required
    apiToken: "${GITEA_TOKEN}"

Environment Variables

Variable Description
STELLAOPS_SCM_GITHUB_TOKEN GitHub PAT or App token
STELLAOPS_SCM_GITLAB_TOKEN GitLab Personal/Project token
STELLAOPS_SCM_AZUREDEVOPS_TOKEN Azure DevOps PAT
STELLAOPS_SCM_GITEA_TOKEN Gitea application token

Required Token Scopes

Platform Required Scopes
GitHub repo, workflow (PAT) or contents:write, pull_requests:write, checks:read (App)
GitLab api, read_repository, write_repository
Azure DevOps Code (Read & Write), Pull Request Contribute, Build (Read)
Gitea repo (full repository access)

Connector Details

GitHub Connector

github:
  enabled: true
  baseUrl: ""  # Leave empty for github.com
  apiToken: "${GITHUB_PAT}"

Features:

  • Bearer token authentication
  • Check-runs API for CI status (GitHub Actions)
  • Combined commit status support
  • Enterprise Server support via baseUrl

API Endpoints Used:

  • GET /repos/{owner}/{repo}/git/refs/heads/{branch} - Get branch SHA
  • POST /repos/{owner}/{repo}/git/refs - Create branch
  • PUT /repos/{owner}/{repo}/contents/{path} - Update file
  • POST /repos/{owner}/{repo}/pulls - Create PR
  • GET /repos/{owner}/{repo}/commits/{sha}/check-runs - CI status

GitLab Connector

gitlab:
  enabled: true
  baseUrl: ""  # Leave empty for gitlab.com
  apiToken: "${GITLAB_PAT}"

Features:

  • PRIVATE-TOKEN header authentication
  • Merge Request creation (GitLab terminology)
  • Pipeline and Jobs API for CI status
  • Self-hosted instance support

API Endpoints Used:

  • POST /projects/{id}/repository/branches - Create branch
  • POST /projects/{id}/repository/commits - Commit file changes
  • POST /projects/{id}/merge_requests - Create MR
  • GET /projects/{id}/pipelines?sha={sha} - CI status
  • GET /projects/{id}/pipelines/{id}/jobs - Job details

Azure DevOps Connector

azuredevops:
  enabled: true
  baseUrl: ""  # Leave empty for Azure DevOps Services
  apiToken: "${AZURE_DEVOPS_PAT}"
  apiVersion: "7.1"

Features:

  • Basic authentication with PAT (empty username, token as password)
  • Push API for atomic commits
  • Azure Pipelines build status
  • Azure DevOps Server support

API Endpoints Used:

  • GET /{org}/{project}/_apis/git/refs - Get branch refs
  • POST /{org}/{project}/_apis/git/refs - Create branch
  • POST /{org}/{project}/_apis/git/pushes - Commit changes
  • POST /{org}/{project}/_apis/git/pullrequests - Create PR
  • GET /{org}/{project}/_apis/build/builds - Build status

Gitea Connector

gitea:
  enabled: true
  baseUrl: "https://git.example.com"  # Required
  apiToken: "${GITEA_TOKEN}"

Features:

  • Token header authentication
  • Gitea Actions support (workflow runs)
  • Compatible with Forgejo and Codeberg
  • Combined commit status API

API Endpoints Used:

  • GET /api/v1/repos/{owner}/{repo}/branches/{branch} - Get branch
  • POST /api/v1/repos/{owner}/{repo}/branches - Create branch
  • PUT /api/v1/repos/{owner}/{repo}/contents/{path} - Update file
  • POST /api/v1/repos/{owner}/{repo}/pulls - Create PR
  • GET /api/v1/repos/{owner}/{repo}/commits/{sha}/status - Status
  • GET /api/v1/repos/{owner}/{repo}/actions/runs - Workflow runs

Usage

Dependency Injection

// In Startup.cs or Program.cs
services.AddScmConnectors(config =>
{
    // Optionally add custom plugins
    config.AddPlugin(new CustomScmConnectorPlugin());

    // Or remove built-in plugins
    config.RemovePlugin("github");
});

Creating a Connector

public class RemediationService
{
    private readonly ScmConnectorCatalog _catalog;

    public async Task<PrCreateResult> CreateRemediationPrAsync(
        string repositoryUrl,
        RemediationPlan plan,
        CancellationToken cancellationToken)
    {
        var options = new ScmConnectorOptions
        {
            ApiToken = _configuration["ScmToken"],
            BaseUrl = _configuration["ScmBaseUrl"]
        };

        // Auto-detect connector from URL
        var connector = _catalog.GetConnectorForRepository(repositoryUrl, options);
        if (connector is null)
            throw new InvalidOperationException($"No connector available for {repositoryUrl}");

        // Create branch
        var branchResult = await connector.CreateBranchAsync(
            owner: "myorg",
            repo: "myrepo",
            branchName: $"stellaops/remediation/{plan.Id}",
            baseBranch: "main",
            cancellationToken);

        // Update files
        foreach (var change in plan.FileChanges)
        {
            await connector.UpdateFileAsync(
                owner: "myorg",
                repo: "myrepo",
                branch: branchResult.BranchName,
                filePath: change.Path,
                content: change.NewContent,
                commitMessage: $"chore: apply remediation for {plan.FindingId}",
                cancellationToken);
        }

        // Create PR
        return await connector.CreatePullRequestAsync(
            owner: "myorg",
            repo: "myrepo",
            headBranch: branchResult.BranchName,
            baseBranch: "main",
            title: $"[StellaOps] Remediation for {plan.FindingId}",
            body: GeneratePrBody(plan),
            cancellationToken);
    }
}

Polling CI Status

public async Task<CiState> WaitForCiAsync(
    IScmConnector connector,
    string owner,
    string repo,
    string commitSha,
    TimeSpan timeout,
    CancellationToken cancellationToken)
{
    var deadline = DateTime.UtcNow + timeout;

    while (DateTime.UtcNow < deadline)
    {
        var status = await connector.GetCiStatusAsync(
            owner, repo, commitSha, cancellationToken);

        switch (status.OverallState)
        {
            case CiState.Success:
            case CiState.Failure:
            case CiState.Error:
                return status.OverallState;

            case CiState.Pending:
            case CiState.Running:
                await Task.Delay(TimeSpan.FromSeconds(30), cancellationToken);
                break;
        }
    }

    return CiState.Unknown;
}

CI State Mapping

Different SCM platforms use different status values. The connector normalizes them:

Platform Pending Running Success Failure Error
GitHub pending, queued in_progress success failure error, cancelled
GitLab pending, waiting running success failed canceled, skipped
Azure DevOps notStarted, postponed inProgress succeeded failed canceled
Gitea pending, queued running success failure cancelled, timed_out

URL Auto-Detection

The CanHandle method on each plugin detects repository URLs:

Plugin URL Patterns
GitHub github.com, github.
GitLab gitlab.com, gitlab.
Azure DevOps dev.azure.com, visualstudio.com, azure.com
Gitea gitea., forgejo., codeberg.org

Example:

// Auto-detects GitHub
var connector = catalog.GetConnectorForRepository(
    "https://github.com/myorg/myrepo", options);

// Auto-detects GitLab
var connector = catalog.GetConnectorForRepository(
    "https://gitlab.com/mygroup/myproject", options);

Custom Plugins

To add support for a new SCM platform:

public sealed class BitbucketScmConnectorPlugin : IScmConnectorPlugin
{
    public string ScmType => "bitbucket";
    public string DisplayName => "Bitbucket";

    public bool IsAvailable(ScmConnectorOptions options) =>
        !string.IsNullOrEmpty(options.ApiToken);

    public bool CanHandle(string repositoryUrl) =>
        repositoryUrl.Contains("bitbucket.org", StringComparison.OrdinalIgnoreCase);

    public IScmConnector Create(ScmConnectorOptions options, HttpClient httpClient) =>
        new BitbucketScmConnector(httpClient, options);
}

public sealed class BitbucketScmConnector : ScmConnectorBase
{
    // Implement abstract methods...
}

Register the custom plugin:

services.AddScmConnectors(config =>
{
    config.AddPlugin(new BitbucketScmConnectorPlugin());
});

Error Handling

All connector methods return result objects with Success and ErrorMessage:

var result = await connector.CreateBranchAsync(...);

if (!result.Success)
{
    _logger.LogError("Failed to create branch: {Error}", result.ErrorMessage);
    return;
}

// Continue with successful result
var branchSha = result.CommitSha;

Security Considerations

  1. Token Storage: Never store tokens in configuration files. Use environment variables or secret management.

  2. Minimum Permissions: Request only required scopes for each platform.

  3. TLS Verification: Always verify TLS certificates in production (verifySsl: true).

  4. Audit Logging: All SCM operations are logged for compliance.

  5. Repository Access: Connectors only access repositories explicitly provided. No enumeration of accessible repos.

Telemetry

SCM operations emit structured logs:

{
  "timestamp": "2025-12-26T10:30:00Z",
  "operation": "scm_create_pr",
  "scmType": "github",
  "owner": "myorg",
  "repo": "myrepo",
  "branch": "stellaops/remediation/plan-123",
  "duration_ms": 1234,
  "success": true,
  "pr_number": 456,
  "pr_url": "https://github.com/myorg/myrepo/pull/456"
}