> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ComposioHQ/agent-orchestrator/llms.txt
> Use this file to discover all available pages before exploring further.

# SCM

> Plugin interface for source control management platforms (GitHub, GitLab) with PR lifecycle, CI tracking, code review, and merge readiness

## Overview

The `SCM` (Source Control Management) interface is the richest plugin interface, covering the full PR pipeline: PR detection, state tracking, CI checks, code reviews, and merge operations.

**Plugin Slot:** `scm`\
**Default Plugin:** `github`

## Interface Definition

```typescript theme={null}
export interface SCM {
  readonly name: string;
  
  // PR Lifecycle
  detectPR(session: Session, project: ProjectConfig): Promise<PRInfo | null>;
  getPRState(pr: PRInfo): Promise<PRState>;
  getPRSummary?(pr: PRInfo): Promise<{
    state: PRState;
    title: string;
    additions: number;
    deletions: number;
  }>;
  mergePR(pr: PRInfo, method?: MergeMethod): Promise<void>;
  closePR(pr: PRInfo): Promise<void>;
  
  // CI Tracking
  getCIChecks(pr: PRInfo): Promise<CICheck[]>;
  getCISummary(pr: PRInfo): Promise<CIStatus>;
  
  // Review Tracking
  getReviews(pr: PRInfo): Promise<Review[]>;
  getReviewDecision(pr: PRInfo): Promise<ReviewDecision>;
  getPendingComments(pr: PRInfo): Promise<ReviewComment[]>;
  getAutomatedComments(pr: PRInfo): Promise<AutomatedComment[]>;
  
  // Merge Readiness
  getMergeability(pr: PRInfo): Promise<MergeReadiness>;
}
```

## PR Lifecycle Methods

<ResponseField name="detectPR" type="(session: Session, project: ProjectConfig) => Promise<PRInfo | null>" required>
  Detect if a session has an open PR (by branch name).

  **Parameters:**

  * `session` - Session to check
  * `project` - Project configuration

  **Returns:** PRInfo if PR exists, null otherwise
</ResponseField>

<ResponseField name="getPRState" type="(pr: PRInfo) => Promise<PRState>" required>
  Get current PR state.

  **Parameters:**

  * `pr` - PR info

  **Returns:** `"open"`, `"merged"`, or `"closed"`
</ResponseField>

<ResponseField name="getPRSummary" type="(pr: PRInfo) => Promise<object>">
  Optional: Get PR summary with stats (state, title, additions, deletions).

  **Parameters:**

  * `pr` - PR info

  **Returns:** Object with state, title, additions, deletions
</ResponseField>

<ResponseField name="mergePR" type="(pr: PRInfo, method?: MergeMethod) => Promise<void>" required>
  Merge a PR.

  **Parameters:**

  * `pr` - PR info
  * `method` - Merge method: `"merge"`, `"squash"`, or `"rebase"` (default: repo settings)
</ResponseField>

<ResponseField name="closePR" type="(pr: PRInfo) => Promise<void>" required>
  Close a PR without merging.

  **Parameters:**

  * `pr` - PR info
</ResponseField>

## CI Tracking Methods

<ResponseField name="getCIChecks" type="(pr: PRInfo) => Promise<CICheck[]>" required>
  Get individual CI check statuses.

  **Parameters:**

  * `pr` - PR info

  **Returns:** Array of CI checks with name, status, URL, timestamps
</ResponseField>

<ResponseField name="getCISummary" type="(pr: PRInfo) => Promise<CIStatus>" required>
  Get overall CI summary.

  **Parameters:**

  * `pr` - PR info

  **Returns:** `"pending"`, `"passing"`, `"failing"`, or `"none"`
</ResponseField>

## Review Tracking Methods

<ResponseField name="getReviews" type="(pr: PRInfo) => Promise<Review[]>" required>
  Get all reviews on a PR.

  **Parameters:**

  * `pr` - PR info

  **Returns:** Array of reviews with author, state, body, timestamp
</ResponseField>

<ResponseField name="getReviewDecision" type="(pr: PRInfo) => Promise<ReviewDecision>" required>
  Get the overall review decision.

  **Parameters:**

  * `pr` - PR info

  **Returns:** `"approved"`, `"changes_requested"`, `"pending"`, or `"none"`
</ResponseField>

<ResponseField name="getPendingComments" type="(pr: PRInfo) => Promise<ReviewComment[]>" required>
  Get pending (unresolved) review comments.

  **Parameters:**

  * `pr` - PR info

  **Returns:** Array of unresolved comments
</ResponseField>

<ResponseField name="getAutomatedComments" type="(pr: PRInfo) => Promise<AutomatedComment[]>" required>
  Get automated review comments (bots, linters, security scanners).

  **Parameters:**

  * `pr` - PR info

  **Returns:** Array of automated comments with severity
</ResponseField>

## Merge Readiness Method

<ResponseField name="getMergeability" type="(pr: PRInfo) => Promise<MergeReadiness>" required>
  Check if PR is ready to merge.

  **Parameters:**

  * `pr` - PR info

  **Returns:** MergeReadiness with mergeable flag, CI status, approval status, conflicts, and blockers
</ResponseField>

## Related Types

### PRInfo

```typescript theme={null}
export interface PRInfo {
  number: number;
  url: string;
  title: string;
  owner: string;
  repo: string;
  branch: string;
  baseBranch: string;
  isDraft: boolean;
}
```

<ParamField path="number" type="number" required>
  PR number
</ParamField>

<ParamField path="url" type="string" required>
  Full URL to PR
</ParamField>

<ParamField path="title" type="string" required>
  PR title
</ParamField>

<ParamField path="owner" type="string" required>
  Repository owner
</ParamField>

<ParamField path="repo" type="string" required>
  Repository name
</ParamField>

<ParamField path="branch" type="string" required>
  Source branch name
</ParamField>

<ParamField path="baseBranch" type="string" required>
  Target/base branch name
</ParamField>

<ParamField path="isDraft" type="boolean" required>
  Whether PR is in draft mode
</ParamField>

### PRState

```typescript theme={null}
export type PRState = "open" | "merged" | "closed";
```

### CICheck

```typescript theme={null}
export interface CICheck {
  name: string;
  status: "pending" | "running" | "passed" | "failed" | "skipped";
  url?: string;
  conclusion?: string;
  startedAt?: Date;
  completedAt?: Date;
}
```

### CIStatus

```typescript theme={null}
export type CIStatus = "pending" | "passing" | "failing" | "none";
```

### Review

```typescript theme={null}
export interface Review {
  author: string;
  state: "approved" | "changes_requested" | "commented" | "dismissed" | "pending";
  body?: string;
  submittedAt: Date;
}
```

### ReviewDecision

```typescript theme={null}
export type ReviewDecision = "approved" | "changes_requested" | "pending" | "none";
```

### ReviewComment

```typescript theme={null}
export interface ReviewComment {
  id: string;
  author: string;
  body: string;
  path?: string;
  line?: number;
  isResolved: boolean;
  createdAt: Date;
  url: string;
}
```

### AutomatedComment

```typescript theme={null}
export interface AutomatedComment {
  id: string;
  botName: string;
  body: string;
  path?: string;
  line?: number;
  severity: "error" | "warning" | "info";
  createdAt: Date;
  url: string;
}
```

### MergeReadiness

```typescript theme={null}
export interface MergeReadiness {
  mergeable: boolean;
  ciPassing: boolean;
  approved: boolean;
  noConflicts: boolean;
  blockers: string[];
}
```

<ParamField path="mergeable" type="boolean" required>
  Overall merge readiness (all checks must pass)
</ParamField>

<ParamField path="ciPassing" type="boolean" required>
  Whether CI is passing
</ParamField>

<ParamField path="approved" type="boolean" required>
  Whether PR is approved
</ParamField>

<ParamField path="noConflicts" type="boolean" required>
  Whether PR has no merge conflicts
</ParamField>

<ParamField path="blockers" type="string[]" required>
  Array of blocker descriptions (e.g. `"CI failing"`, `"Changes requested"`)
</ParamField>

### MergeMethod

```typescript theme={null}
export type MergeMethod = "merge" | "squash" | "rebase";
```

## Usage Examples

### Implementing an SCM Plugin

```typescript theme={null}
import type { SCM, PRInfo, PRState, CIStatus, ReviewDecision, MergeReadiness } from "@composio/ao-core";
import { execFile } from "node:child_process";
import { promisify } from "node:util";

const execFileAsync = promisify(execFile);

export function create(): SCM {
  return {
    name: "github",
    
    async detectPR(session: Session, project: ProjectConfig): Promise<PRInfo | null> {
      if (!session.branch) return null;
      
      const { stdout } = await execFileAsync("gh", [
        "pr",
        "view",
        session.branch,
        "--repo", project.repo,
        "--json", "number,url,title,headRefName,baseRefName,isDraft"
      ], { timeout: 30_000 }).catch(() => ({ stdout: "" }));
      
      if (!stdout) return null;
      
      const data = JSON.parse(stdout);
      const [owner, repo] = project.repo.split("/");
      
      return {
        number: data.number,
        url: data.url,
        title: data.title,
        owner,
        repo,
        branch: data.headRefName,
        baseBranch: data.baseRefName,
        isDraft: data.isDraft
      };
    },
    
    async getPRState(pr: PRInfo): Promise<PRState> {
      const { stdout } = await execFileAsync("gh", [
        "pr",
        "view",
        pr.number.toString(),
        "--repo", `${pr.owner}/${pr.repo}`,
        "--json", "state"
      ], { timeout: 10_000 });
      
      const data = JSON.parse(stdout);
      return data.state.toLowerCase() as PRState;
    },
    
    async getCISummary(pr: PRInfo): Promise<CIStatus> {
      const checks = await this.getCIChecks(pr);
      
      if (checks.length === 0) return "none";
      
      const anyFailed = checks.some(c => c.status === "failed");
      const anyPending = checks.some(c => c.status === "pending" || c.status === "running");
      
      if (anyFailed) return "failing";
      if (anyPending) return "pending";
      return "passing";
    },
    
    async getReviewDecision(pr: PRInfo): Promise<ReviewDecision> {
      const { stdout } = await execFileAsync("gh", [
        "pr",
        "view",
        pr.number.toString(),
        "--repo", `${pr.owner}/${pr.repo}`,
        "--json", "reviewDecision"
      ], { timeout: 10_000 });
      
      const data = JSON.parse(stdout);
      
      if (!data.reviewDecision) return "none";
      
      const decision = data.reviewDecision.toLowerCase();
      if (decision === "approved") return "approved";
      if (decision === "changes_requested") return "changes_requested";
      if (decision === "review_required") return "pending";
      return "none";
    },
    
    async getMergeability(pr: PRInfo): Promise<MergeReadiness> {
      const [ciStatus, reviewDecision, state] = await Promise.all([
        this.getCISummary(pr),
        this.getReviewDecision(pr),
        this.getPRState(pr)
      ]);
      
      const ciPassing = ciStatus === "passing" || ciStatus === "none";
      const approved = reviewDecision === "approved";
      const noConflicts = true; // Would need to check mergeable state
      
      const blockers: string[] = [];
      if (!ciPassing) blockers.push("CI failing");
      if (!approved) blockers.push("Not approved");
      if (!noConflicts) blockers.push("Merge conflicts");
      
      return {
        mergeable: blockers.length === 0 && state === "open",
        ciPassing,
        approved,
        noConflicts,
        blockers
      };
    },
    
    async mergePR(pr: PRInfo, method = "squash"): Promise<void> {
      await execFileAsync("gh", [
        "pr",
        "merge",
        pr.number.toString(),
        "--repo", `${pr.owner}/${pr.repo}`,
        method === "merge" ? "--merge" : method === "rebase" ? "--rebase" : "--squash",
        "--delete-branch"
      ], { timeout: 30_000 });
    }
  };
}
```

### Using SCM in Lifecycle Manager

```typescript theme={null}
import type { SCM } from "@composio/ao-core";

const scm: SCM = registry.get("scm", "github");

// Detect PR
const pr = await scm.detectPR(session, project);
if (pr) {
  // Check CI
  const ciStatus = await scm.getCISummary(pr);
  console.log(`CI status: ${ciStatus}`);
  
  // Check reviews
  const decision = await scm.getReviewDecision(pr);
  console.log(`Review decision: ${decision}`);
  
  // Check merge readiness
  const readiness = await scm.getMergeability(pr);
  if (readiness.mergeable) {
    await scm.mergePR(pr, "squash");
  } else {
    console.log(`Blockers: ${readiness.blockers.join(", ")}`);
  }
}
```

## Implementation Notes

### Authentication

SCM plugins typically use CLI tools or API tokens:

* GitHub: `gh` CLI (uses `~/.config/gh/hosts.yml`)
* GitLab: `glab` CLI or `GITLAB_TOKEN`

### Rate Limiting

Be mindful of API rate limits:

* Cache PR info in session metadata
* Batch requests when possible
* Use GraphQL for complex queries (GitHub)

### Review Comment Filtering

Distinguish between:

* Human reviews (priority)
* Bot comments (linters, security scanners)
* Resolved vs unresolved comments

### Built-in Plugins

* **github** - GitHub (default, uses `gh` CLI)

Future plugins could support GitLab, Bitbucket, Gitea, etc.

## See Also

* [Session](/api/session) - Session interface
* [Tracker](/api/tracker) - Issue tracking interface
* [Notifier](/api/notifier) - Notification interface


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.