Skip to main content

Integrations architecture

Project Brain connects to external systems through a provider registry. Jira, Confluence, and GitHub Issues all use the same dispatch model, so most app routes and UI pages do not need provider-specific code.

Core idea​

  • Providers are registered by id, such as jira, confluence, or github_issues.
  • Connect, callback, preview, save, queue, worker sync, scope pages, and provider grids all dispatch through the registry.
  • Server-side registry and provider wiring live in @projectbrain/integrations.
  • Provider SDK clients and auth live in their own packages — @projectbrain/atlassian (OAuth + HTTP client shared by Jira and Confluence) and @projectbrain/github (the GitHub App: manifest registration, config, installation tokens) — which the server providers wrap.
  • Browser-safe scope form code lives in @projectbrain/integrations/ui.

This split keeps database clients, provider SDKs, and HTTP clients out of the browser bundle.

Package layout​

packages/
connector-kit/ # provider-neutral pagination and sync orchestration
adapters-jira/
adapters-confluence/
adapters-github-issues/
adapters-github-code/
atlassian/ # shared Atlassian OAuth + HTTP client + config
src/
oauth.ts
atlassian-client.ts
resources.ts
config.ts
github/ # GitHub App: manifest, config, installations, JWT signing
src/
manifest.ts
config.ts
installations.ts
app-jwt.ts
integrations/
src/
types.ts
registry.ts
tokens.ts
sync/runtime.ts # Project Brain persistence adapter for connector-kit
sync/orphans.ts
oauth/atlassian.ts
providers/
jira.ts
confluence.ts
github-issues.ts
github-code.ts
index.ts # module-load registration (registerProvider calls)
ui/
types.ts
jira.ts
confluence.ts
github-issues.ts
github-code.ts
index.ts

apps/
web/components/scope-form.tsx
web/app/(app)/projects/[slug]/integrations/
web/app/api/integrations/
worker/src/workers/sync.ts

Provider contract​

Each provider has two halves joined by the same id.

Server provider​

Runs in Next.js API routes and the worker.

interface IntegrationProvider<TScope, TSample> {
id: string;
platformId: string;
name: string;
blurb: string;
icon: string;
docs?: ProviderDocs;
isConfigured(): Promise<boolean>;
connect: ConnectFlow;
getAccessToken(integration): Promise<string>;
scope: {
schema: ZodType<TScope>;
empty: TScope;
};
preview(args): Promise<PreviewResult<TSample>>;
sync: {
kind;
pathPrefix;
pathRegex;
run;
};
}

UI provider​

Runs in the browser scope form.

interface ScopeUiConfig<TScope> {
fields: ScopeFieldConfig[];
queryLabel: string;
itemNoun: string;
formToScope(form): TScope;
scopeToForm(scope): Record<string, string>;
summarizeSample(item): SampleItemSummary;
}

Connect flows​

ConnectFlow is a discriminated union.

KindUse caseExample
oauth-redirectProvider consent flow with callback and token storageAtlassian
project-resourceAdopt an existing project resource without OAuth token storageGitHub App installation

The connect route switches on kind. The OAuth callback route only handles oauth-redirect.

GitHub registration uses a separate manifest flow (/api/admin/integrations/github/manifest → GitHub manifest endpoint → /api/github/manifest/callback) that creates the App on GitHub and persists the resulting App id, slug, client id/secret, webhook secret, and PEM to platform_integrations (encrypted). This sits in front of the project-resource connect, which then attaches an installation id to the same row. See packages/github/src/manifest.ts and packages/github/src/config.ts.

Add a provider​

  1. Scaffold and validate the adapter contract:

    pnpm connector:new linear
    pnpm connector:check linear

    The scaffolder creates packages/adapters-linear/ with client, fetch, scope, deterministic transform, and contract-test entry points. It refuses to overwrite an existing connector. The validator checks the required files, package identity, exports, and test/typecheck scripts.

  2. Implement the upstream client, preview/iteration, and deterministic transform code.

  3. Add an OAuth family under packages/integrations/src/oauth/ if the provider uses OAuth.

  4. Add the server provider in packages/integrations/src/providers/X.ts. Set both its source id and the platformId of the credential row it uses.

  5. Register the server provider in packages/integrations/src/providers/index.ts.

  6. Add browser-safe UI config in packages/integrations/src/ui/X.ts.

  7. Register the UI config in packages/integrations/src/ui/index.ts.

  8. Add an icon key in apps/web/components/icons.tsx if needed.

  9. Run the adapter's tests and typecheck, then pnpm connector:check X.

After registration, the provider grid, dynamic scope page, and worker sync dispatcher pick it up automatically.

Important design choices​

Server and client registries are separate​

The server registry imports database and provider code. The UI registry imports only browser-safe field definitions and pure mapping functions.

Client components import:

@projectbrain/integrations/ui

They should not import:

@projectbrain/integrations

Functions do not cross the RSC boundary​

React Server Components can only pass serializable props. The server page passes providerId, and the client scope form calls getProviderUi(providerId).

Hooks stay inside the inner form​

The outer scope form handles missing provider UI and returns an error state if needed. The inner form receives a valid ui object and runs hooks unconditionally.

Token lifecycle belongs to the provider​

OAuth providers call the shared getOAuthAccessToken(integration, family) helper. GitHub Apps mint installation tokens on demand through Octokit instead of using refresh tokens — the App's own credentials (id, client id/secret, webhook secret, PEM) live encrypted on platform_integrations, loaded via loadGitHubAppConfig() with a short-lived in-memory cache.

Sync orchestration and orphan detection are shared​

@projectbrain/connector-kit owns the provider-neutral sync loop. Its runConnectorSync function handles pagination, page-size and item caps, within-run deduplication, checksum comparison, batched staging, repeated-cursor protection, counters, and the rule that truncated runs never reconcile deletions. It depends only on storage and reconcile ports, so connector contracts can be tested without Project Brain's database.

Server providers use runProviderSync in:

packages/integrations/src/sync/runtime.ts

That adapter supplies Project Brain's Drizzle-backed checksum lookup, proposal staging, and orphan reconciliation. Jira, Confluence, and GitHub Issues are reference implementations; their provider files now only parse scope, create the upstream client, fetch pages, and normalize records.

detectAndStageDeletions is shared in:

packages/integrations/src/sync/orphans.ts

Each provider declares sync.pathPrefix and sync.pathRegex. The helper compares seen external ids against existing files and stages deletions.

GitHub Code mirrors source verbatim​

The github_code provider (packages/integrations/src/providers/github-code.ts, adapter in packages/adapters-github-code/) mirrors selected repos' text and source files into raw/code/{owner}/{repo}/… so the graph builder can parse them with tree-sitter. Points that differ from the document adapters:

  • Verbatim bodies, real extensions. Files are committed exactly as they exist upstream (src/foo.ts stays .ts, no frontmatter prepended). The proposal frontmatter is review-UI metadata only.
  • Tarball snapshot fetch. Each sync downloads one tarball per repo at the resolved head (~2–3 API requests per repo) instead of per-file Contents calls, so max_items_per_sync can default high (20 000) and runs stay non-truncated — which is what allows orphan reconciliation.
  • Repo, branch, commit — in that order. The scope form lists the installation's repos in a dropdown; picking one loads its branches (preferring main, else the repo's default) and fills the commit with that branch's head. Resolution order at sync time is commit → branch → default branch (resolveRef in packages/adapters-github-code/src/fetch.ts), so a pinned commit freezes the mirror and clearing it follows the branch instead. The commit field still refuses ref names: a pin that silently moves on every push is the bug that distinction exists to prevent.
  • Filter policy. Binaries (extension denylist + NUL sniff + strict UTF-8 gate), lockfiles, node_modules/dist/vendor-style trees, and files over the size cap (default 200 KB) are always skipped. include_globs acts as an allowlist; exclude_globs always wins. For large repos, scope the first sync with include globs (e.g. src/**, docs/**).
  • Deletions are reviewed. Files removed upstream become delete proposals held for human review, ≤ 25 per run — renames therefore commit the new path immediately and queue the old one for approval. Setting Deletions to accept automatically (Project → Settings → Sync approvals) approves and commits deletions in the same run, bounded only by the per-run cap, which is the right trade for a mirrored corpus whose source of truth is upstream: the prune is as mechanical as the create that preceded it. The guards that actually catch a bad prune — the empty-seen-set check, the truncated check, and the multiple-integrations check — are unaffected either way.
  • The organizer skips raw/code/ (packages/organizer/src/process.ts): mirrored repos carry their own README.md files, which must never be overwritten by generated directory indexes.
  • Auto graph rebuild. After ingestion or human commits, the writer enqueues a debounced graphify job (graphify-auto-{projectId}, default 5-minute delay, GRAPHIFY_AUTO_REBUILD_DELAY_MS to tune) so the graph follows the mirrored code without manual rebuild clicks. Agent commits (graphify's own output) never trigger it.
  • Graph scope. Project → Settings chooses whether builds index code only (tree-sitter AST, no model calls) or code plus documents (doc files go to the graph-builder model). The choice rides on each sidecar request, so it takes effect on the next build with no redeploy. Code-only needs no model at all — an unconfigured graph builder does not block those builds.

Current limitations​

LimitationImpact
Multi-workspace selection auto-picks the first workspaceUsers with multiple Atlassian sites or GitHub installations cannot choose yet.
Scope form layout is genericComplex provider UIs may need a future custom field escape hatch. GitHub Code now has repo/branch pickers via the repo-list field kind; other providers still get plain inputs.
Preview sample items are typed as unknown in the registryEach UI config casts locally in summarizeSample.
Single GitHub installation per deploymentSwitching org replaces the existing installation; you can't sync from multiple GitHub orgs concurrently.

Giving coding agents access​

The sections above cover how Project Brain ingests external systems. Coding agents consume that knowledge through the repository-native project-brain skill and the authenticated GitHub CLI. There is no background protocol server or separate Project Brain credential.

A project admin opens a project and chooses Install agent skill from the project action bar. The modal walks through four steps:

  1. Install the GitHub CLI using the platform-specific command or the linked official guide.

  2. Authenticate the GitHub CLI with gh auth login and grant access to the project's knowledge repository.

  3. Install the skill with npx skills add Neusis-AI-Org/project-brain-skill --skill project-brain.

  4. Add .project-brain.json to the root of the code repository:

    {
    "repository": "owner/project-brain-repo"
    }

    The skill resolves the repository's default branch through gh. Set an optional defaultBranch only when the brain should track a different branch.

The skill reads the generated wiki and its source documents through gh, cites brain-relative paths and the inspected commit, and stays read-only unless the user explicitly requests a reviewed knowledge update.

Reference files​

ConcernFile
Provider typespackages/integrations/src/types.ts
Connector runtimepackages/connector-kit/src/index.ts
Persistence adapterpackages/integrations/src/sync/runtime.ts
Server registrypackages/integrations/src/registry.ts
UI registrypackages/integrations/src/ui/index.ts
Token lifecyclepackages/integrations/src/tokens.ts
Orphan helperpackages/integrations/src/sync/orphans.ts
Scope formapps/web/components/scope-form.tsx
Provider gridapps/web/app/(app)/projects/[slug]/integrations/page.tsx
Sources dashboardapps/web/app/(app)/projects/[slug]/sources/page.tsx
Dynamic scope pageapps/web/app/(app)/projects/[slug]/integrations/[provider]/page.tsx
Source detail pageapps/web/app/(app)/projects/[slug]/sources/[provider]/page.tsx
Connect routeapps/web/app/api/projects/[slug]/integrations/[product]/connect/route.ts
OAuth callbackapps/web/app/api/integrations/oauth/callback/route.ts
Worker syncapps/worker/src/workers/sync.ts
Agent skillskills/project-brain/SKILL.md