Skip to content
Open
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Node.js 22+](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org/)

> **Cascade orchestrates AI agents (Claude Code, Codex, opencode, LLMist) across your workflows in GitHub, Trello, Jira, and Linear.**
> **Cascade orchestrates AI agents (Claude Code, Codex, opencode, LLMist) across your workflows in GitHub, Trello, Jira, Linear, and GitHub Projects.**

Cascade is an open-source platform that automates the full software development lifecycle. Connect your PM tool and GitHub repository, and Cascade drives work items from plan to merge:

Expand Down Expand Up @@ -38,7 +38,7 @@ For the full setup walkthrough — projects, credentials, webhooks, and triggers

## ⚡ Features

- **Multi-PM support** — Works with Trello, JIRA, and Linear out of the box
- **Multi-PM support** — Works with Trello, JIRA, Linear, and GitHub Projects out of the box
- **12 agent types** — Splitting, planning, implementation, review, debug, respond-to-review, respond-to-CI, alerting, and more
- **Dual-persona GitHub model** — Separate implementer and reviewer bot accounts to prevent feedback loops
- **Web dashboard + CLI** — Monitor runs, manage projects, configure triggers
Expand Down Expand Up @@ -151,7 +151,7 @@ All project-level credentials (GitHub tokens, PM keys, LLM API keys) are stored

**Dual-persona GitHub model** — Cascade uses two separate GitHub bot accounts per project (implementer and reviewer) to prevent feedback loops. The implementer writes code and creates PRs; the reviewer reviews and approves them.

**Trigger system** — Events from Trello, JIRA, Linear, GitHub, and Sentry webhooks are matched against registered `TriggerHandler` instances. Triggers are configured per-project in the database. Event names are category-prefixed, for example `pm:status-changed`, `scm:check-suite-success`, and `alerting:issue-alert`.
**Trigger system** — Events from Trello, JIRA, Linear, GitHub Projects, GitHub, and Sentry webhooks are matched against registered `TriggerHandler` instances. Triggers are configured per-project in the database. Event names are category-prefixed, for example `pm:status-changed`, `scm:check-suite-success`, and `alerting:issue-alert`.

**Agent engines** — Agents run through a shared execution lifecycle with a pluggable engine registry. Default engine is `claude-code` (Anthropic Claude Code SDK). Alternatives: `llmist` (supports OpenRouter, Anthropic, OpenAI), `codex` (OpenAI Codex CLI), `opencode` (OpenCode server).

Expand Down
12 changes: 8 additions & 4 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CASCADE Architecture

CASCADE is a PM-to-Code automation platform that connects project management tools (Trello, JIRA, Linear), source control (GitHub), and monitoring (Sentry) to AI-powered agents that autonomously implement features, review PRs, debug failures, and manage backlogs. Webhooks from external providers flow through a router, get queued in Redis, and are processed by ephemeral worker containers that run agents against cloned repositories.
CASCADE is a PM-to-Code automation platform that connects project management tools (Trello, JIRA, Linear, GitHub Projects), source control (GitHub), and monitoring (Sentry) to AI-powered agents that autonomously implement features, review PRs, debug failures, and manage backlogs. Webhooks from external providers flow through a router, get queued in Redis, and are processed by ephemeral worker containers that run agents against cloned repositories.

> **Relationship to CLAUDE.md**: `CLAUDE.md` is the operational reference (commands, env vars, how-to). This document and its deep-dives cover the *system design* — how components fit together and why.

Expand All @@ -12,6 +12,7 @@ graph TB
Trello
JIRA
Linear
GitHubProjects["GitHub Projects"]
GitHub
Sentry
end
Expand All @@ -32,6 +33,7 @@ graph TB
Trello -->|webhook| Router
JIRA -->|webhook| Router
Linear -->|webhook| Router
GitHubProjects -->|webhook| Router
GitHub -->|webhook| Router
Sentry -->|webhook| Router

Expand All @@ -42,6 +44,7 @@ graph TB
Worker -->|status updates| Trello
Worker -->|status updates| JIRA
Worker -->|status updates| Linear
Worker -->|status updates| GitHubProjects

Router <--> DB
Worker <--> DB
Expand All @@ -68,7 +71,7 @@ The canonical path from webhook to pull request:

```mermaid
sequenceDiagram
participant P as Provider<br/>(Trello/JIRA/Linear/GitHub/Sentry)
participant P as Provider<br/>(Trello/JIRA/Linear/GitHub Projects/GitHub/Sentry)
participant R as Router
participant Q as Redis/BullMQ
participant W as Worker
Expand Down Expand Up @@ -100,7 +103,7 @@ sequenceDiagram

**YAML-based agent definitions** — Agents are defined declaratively in YAML files specifying identity, capabilities, triggers, prompts, and lifecycle hooks. Definitions resolve via three tiers: in-memory cache, database, then YAML files on disk.

**AsyncLocalStorage credential scoping** — Provider clients (GitHub, Trello, JIRA, Linear, and PM dispatch scopes) use Node.js `AsyncLocalStorage` to scope credentials and active PM provider context per request, preventing cross-request credential leakage.
**AsyncLocalStorage credential scoping** — Provider clients (GitHub, Trello, JIRA, Linear, GitHub Projects, and PM dispatch scopes) use Node.js `AsyncLocalStorage` to scope credentials and active PM provider context per request, preventing cross-request credential leakage.

## Directory Map

Expand All @@ -113,8 +116,9 @@ sequenceDiagram
| `src/backends/` | LLM execution engines: Claude Code, LLMist, Codex, OpenCode |
| `src/gadgets/` | Tool implementations agents use (file ops, PM, SCM, alerting, shell) |
| `src/integrations/` | Unified integration interfaces, registry, bootstrap |
| `src/pm/` | PM abstraction layer: provider interface, Trello/JIRA/Linear adapters, lifecycle |
| `src/pm/` | PM abstraction layer: provider interface, Trello/JIRA/Linear/GitHub Projects adapters, lifecycle |
| `src/github/` | GitHub API client, dual-persona model, PR operations |
| `src/github-projects/` | GitHub Projects (Projects v2) GraphQL client |
| `src/trello/` | Trello API client |
| `src/jira/` | JIRA API client (jira.js wrapper) |
| `src/linear/` | Linear GraphQL API client |
Expand Down
6 changes: 4 additions & 2 deletions docs/architecture/02-webhook-pipeline.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Webhook Pipeline

Webhooks from external providers (Trello, JIRA, Linear, GitHub, Sentry) are processed through a two-layer system: a **webhook handler factory** that handles HTTP concerns, and a **router platform adapter** that implements the business logic pipeline.
Webhooks from external providers (Trello, JIRA, Linear, GitHub Projects, GitHub, Sentry) are processed through a two-layer system: a **webhook handler factory** that handles HTTP concerns, and a **router platform adapter** that implements the business logic pipeline.

## Webhook Handler Factory

Expand All @@ -16,7 +16,7 @@ Each webhook endpoint provides a `WebhookHandlerConfig`:

```typescript
interface WebhookHandlerConfig {
source: string; // 'trello' | 'github' | 'jira' | 'linear' | 'sentry'
source: string; // 'trello' | 'github' | 'jira' | 'linear' | 'github-projects' | 'sentry'
parsePayload: (c: Context) => ParseResult;
verifySignature?: (ctx, rawBody, projectId?) => VerificationResult | null;
processWebhook: (payload, eventType?, headers?) => Promise<WebhookLogOverrides>;
Expand All @@ -38,6 +38,7 @@ The factory handles:
| `parseTrelloPayload()` | JSON body | `action.type` field |
| `parseJiraPayload()` | JSON body | `webhookEvent` field |
| `parseLinearPayload()` | JSON body | `type` field |
| `parseGitHubProjectsPayload()` | JSON body | `projects_v2_item.action` field |
| `parseSentryPayload()` | JSON body | `Sentry-Hook-Resource` header |

## Platform Adapters
Expand Down Expand Up @@ -83,6 +84,7 @@ interface ParsedWebhookEvent {
| `GitHubRouterAdapter` | `src/router/adapters/github.ts` | `repoFullName` |
| `JiraRouterAdapter` | `src/router/adapters/jira.ts` | JIRA project key |
| `LinearRouterAdapter` | `src/router/adapters/linear.ts` | Linear team ID |
| `GitHubProjectsRouterAdapter` | `src/router/adapters/github-projects.ts` | Project node ID (`project_node_id`) |
| `SentryRouterAdapter` | `src/router/adapters/sentry.ts` | CASCADE `projectId` (from URL) |

Sentry keeps the route shape `/sentry/webhook/:projectId`. The project ID selects the Cascade project, whose Sentry config includes a paired `organizationSlug`/`projectSlug`. The adapter filters payloads by matching Sentry project identifiers against the configured `projectSlug`; organization-level deliveries whose payload project does not match are acknowledged but do not dispatch agents.
Expand Down
12 changes: 10 additions & 2 deletions docs/architecture/03-trigger-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ interface TriggerHandler {
```typescript
interface TriggerContext {
project: ProjectConfig;
source: TriggerSource; // 'trello' | 'github' | 'jira' | 'linear' | 'sentry'
source: TriggerSource; // 'trello' | 'github' | 'jira' | 'linear' | 'github-projects' | 'sentry'
payload: unknown; // Raw webhook payload
personaIdentities?: PersonaIdentities; // GitHub bot identities
}
Expand Down Expand Up @@ -75,7 +75,7 @@ interface TriggerResult {

## Built-in Triggers

Registration happens in `src/triggers/builtins.ts`. PM providers (Trello, JIRA, Linear) contribute triggers via the manifest registry; SCM and alerting providers use their own `register.ts` functions:
Registration happens in `src/triggers/builtins.ts`. PM providers (Trello, JIRA, Linear, GitHub Projects) contribute triggers via the manifest registry; SCM and alerting providers use their own `register.ts` functions:

```typescript
function registerBuiltInTriggers(registry: TriggerRegistry): void {
Expand Down Expand Up @@ -110,6 +110,14 @@ function registerBuiltInTriggers(registry: TriggerRegistry): void {
| `JiraStatusChangedTrigger` | Issue status transition | Per-status mapping |
| `JiraLabelAddedTrigger` | "cascade-ready" label added | `splitting` |

### GitHub Projects triggers (`src/triggers/github-projects/`)

| Handler | Event | Agent |
|---------|-------|-------|
| `GitHubProjectsStatusChangedTrigger` | Board Status field change (`projects_v2_item`) | Per-status mapping |

The handler reads the item's current Status option ID authoritatively via GraphQL (the `projects_v2_item` webhook does not reliably carry the new value), then resolves it against the configured status→agent mapping.

### GitHub triggers (`src/triggers/github/`)

| Handler | Event | Agent |
Expand Down
32 changes: 30 additions & 2 deletions docs/architecture/06-integration-layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The base contract for SCM and alerting integrations, and the compatibility surfa

```typescript
interface IntegrationModule {
readonly type: string; // 'trello', 'jira', 'linear', 'github', 'sentry'
readonly type: string; // 'trello', 'jira', 'linear', 'github-projects', 'github', 'sentry'
readonly category: IntegrationCategory; // 'pm' | 'scm' | 'alerting'

withCredentials<T>(projectId: string, fn: () => Promise<T>): Promise<T>;
Expand Down Expand Up @@ -52,7 +52,7 @@ const integrationRegistry: IntegrationRegistry; // singleton

### PMProviderManifest and PMIntegration

`src/integrations/pm/manifest.ts` — the manifest is the single PM-provider contract. Trello, JIRA, and Linear each declare identity, credential roles, webhook route/signature verification, router adapter, trigger handlers, platform ack client, config schema, discovery capabilities, wizard spec, and lifecycle fixture in one provider-owned object.
`src/integrations/pm/manifest.ts` — the manifest is the single PM-provider contract. Trello, JIRA, Linear, and GitHub Projects each declare identity, credential roles, webhook route/signature verification, router adapter, trigger handlers, platform ack client, config schema, discovery capabilities, wizard spec, and lifecycle fixture in one provider-owned object.

The PM barrel (`src/integrations/pm/index.ts`) imports each provider once, then mirrors each manifest's `pmIntegration` into `integrationRegistry`. New PM providers add one provider folder plus one import in that barrel; shared router, worker, dashboard, CLI, and config files are guarded against provider-specific edits by conformance tests.

Expand Down Expand Up @@ -99,6 +99,7 @@ Each provider declares its credential roles — the mapping from logical role na
| Trello | pm | `api_key` → `TRELLO_API_KEY`, `token` → `TRELLO_TOKEN` | `api_secret` |
| JIRA | pm | `email` → `JIRA_EMAIL`, `api_token` → `JIRA_API_TOKEN` | `webhook_secret` |
| Linear | pm | `api_key` → `LINEAR_API_KEY` | `webhook_secret` → `LINEAR_WEBHOOK_SECRET` |
| GitHub Projects | pm | `token` → `GITHUB_TOKEN` | `webhook_secret` → `GITHUB_WEBHOOK_SECRET` |
| GitHub | scm | `implementer_token` → `GITHUB_TOKEN_IMPLEMENTER`, `reviewer_token` → `GITHUB_TOKEN_REVIEWER` | `webhook_secret` |
| Sentry | alerting | `api_token` → `SENTRY_API_TOKEN` | `webhook_secret` |

Expand Down Expand Up @@ -135,6 +136,33 @@ Each provider declares its credential roles — the mapping from logical role na
- Issue identifier extraction via regex: `[A-Z][A-Z0-9]*-\d+` (e.g. `TEAM-123`)
- Work item URL format: `https://linear.app/<org>/issue/<identifier>`

### GitHub Projects (`src/integrations/pm/github-projects/`, `src/pm/github-projects/`, `src/github-projects/`)

- `githubProjectsManifest` declares the PM provider contract and registers with `pmProviderRegistry`
- `GitHubProjectsIntegration` implements the mirrored `PMIntegration`
- `GitHubProjectsPMProvider` implements `PMProvider` for **Projects v2** boards
- `src/github-projects/client.ts` — GraphQL API v4 client with AsyncLocalStorage credential scoping (`withGitHubProjectsCredentials`, distinct from the SCM GitHub token scope)
- Status = the board's **Status** single-select field; transitions map Cascade lifecycle keys to option IDs. The status-changed trigger reads the item's current Status option ID authoritatively via GraphQL rather than trusting the sparse `projects_v2_item` webhook payload
- Webhooks: route `/github-projects/webhook`, HMAC-SHA256 verification via the shared verifier. `projects_v2_item` is a valid **organization** webhook event, so for **org-owned** projects the wizard can create the webhook programmatically (`POST /orgs/{org}/hooks`, via the shared `webhooks.create/list/delete` tRPC endpoints with `githubProjectsOnly: true`; needs an `admin:org_hook`-scoped token). **User-owned** projects have no webhook-create API and are configured manually (the wizard copy is scoped by owner type).
- **Supported `PMProvider` surface**:
- `getWorkItem` — with inline-image extraction; resolves the content (Issue/PR) node directly and reads its Status option ID for the configured project
- `getWorkItemComments` — issue/PR comments, with inline-image extraction (matches the Trello/JIRA/Linear spec-016 contract)
- `updateWorkItem` — issue/PR title/body, routed to the correct `updateIssue` / `updatePullRequest` mutation
- `addComment` / `updateComment`
- `createWorkItem` — creates a real Issue in the project's **SCM repo** (`project.repo`, threaded from `ProjectConfig`) via `createIssue`, then adds it to the board via `addProjectV2ItemById`; the content (Issue) node ID is the returned identity so comments/labels/checklists/moves work afterward. This is what makes alert/friction **materialization** available for GitHub Projects (the config `statuses.alerts` / `statuses.friction` slots already resolve). Throws with an actionable message when the project has no SCM repo. Draft issues are intentionally not used (they cannot be commented on or labeled).
- `moveWorkItem` — Status single-select. Resolves the content (Issue/PR) node ID carried across the path to its `ProjectV2Item` node ID for the configured project before writing the field (a `PVTI_…` ID is used directly)
- `listWorkItems` — pages the project's items, filters by Status option ID client-side, keyed by the content node ID (so the pipeline-capacity gate's exclusion filter matches)
- `addLabel` / `removeLabel` — config value resolved to a repo-scoped label node ID (`Repository.label(name:)`, treated as a name; `LA_…` node IDs used directly), applied via `addLabelsToLabelable` / `removeLabelsFromLabelable`; skipped with a warn when the repo has no such label
- `getChecklists` / `createChecklist` / `createChecklistWithItems` / `addChecklistItem` / `updateChecklistItem` / `deleteChecklistItem` — inline markdown task lists (`### {name}` + `- [ ]` rows) in the issue/PR body via the shared `inline-checklist.ts` engine (spec 008), serialized under `withDescriptionMutationLock`
- **Not supported (minimal scope)** — the following `PMProvider` methods are intentional no-ops, so the corresponding features are unavailable for GitHub Projects projects:

| Method | Behavior | Consequence |
|--------|----------|-------------|
| `getAttachments` / `addAttachment*` | `[]` / no-op | formal attachments unavailable (inline-pasted images **are** delivered via the shared media pipeline) |
| `getCustomFieldNumber` / `updateCustomFieldNumber` | `0` / no-op | no cost/budget custom-field tracking (GitHub Projects number fields exist but are not wired — parity with Linear's stub) |
| `getWorkItem` / `listWorkItems` label **reads** | always `labels: []` | label **writes** (`addLabel` / `removeLabel`) work, but reads never surface current labels — label-conditioned logic can't observe GitHub Projects label state |
| `linkPR` | no-op | PRs link implicitly by being added to the project |

### GitHub (`src/github/`)

- `GitHubSCMIntegration` implements `SCMIntegration`
Expand Down
1 change: 1 addition & 0 deletions docs/architecture/08-config-credentials.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The config provider loads project configuration from the database with in-memory
| `loadProjectConfigByRepo(repo)` | GitHub `owner/repo` | `{ project, config }` |
| `loadProjectConfigByJiraProjectKey(key)` | JIRA project key | `{ project, config }` |
| `loadProjectConfigByLinearTeamId(teamId)` | Linear team ID | `{ project, config }` |
| `loadProjectConfigByGitHubProjectsProjectId(id)` | GitHub Project node ID (`PVT_…`) | `{ project, config }` |
| `loadProjectConfigById(id)` | CASCADE project ID | `{ project, config }` |

### Caching
Expand Down
Loading
Loading