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, orgithub_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.
| Kind | Use case | Example |
|---|---|---|
oauth-redirect | Provider consent flow with callback and token storage | Atlassian |
project-resource | Adopt an existing project resource without OAuth token storage | GitHub 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
-
Scaffold and validate the adapter contract:
pnpm connector:new linearpnpm connector:check linearThe 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. -
Implement the upstream client, preview/iteration, and deterministic transform code.
-
Add an OAuth family under
packages/integrations/src/oauth/if the provider uses OAuth. -
Add the server provider in
packages/integrations/src/providers/X.ts. Set both its sourceidand theplatformIdof the credential row it uses. -
Register the server provider in
packages/integrations/src/providers/index.ts. -
Add browser-safe UI config in
packages/integrations/src/ui/X.ts. -
Register the UI config in
packages/integrations/src/ui/index.ts. -
Add an icon key in
apps/web/components/icons.tsxif needed. -
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.tsstays.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_synccan 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 (resolveRefinpackages/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_globsacts as an allowlist;exclude_globsalways 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
truncatedcheck, and the multiple-integrations check — are unaffected either way. - The organizer skips
raw/code/(packages/organizer/src/process.ts): mirrored repos carry their ownREADME.mdfiles, 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_MSto 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
| Limitation | Impact |
|---|---|
| Multi-workspace selection auto-picks the first workspace | Users with multiple Atlassian sites or GitHub installations cannot choose yet. |
| Scope form layout is generic | Complex 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 registry | Each UI config casts locally in summarizeSample. |
| Single GitHub installation per deployment | Switching 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:
-
Install the GitHub CLI using the platform-specific command or the linked official guide.
-
Authenticate the GitHub CLI with
gh auth loginand grant access to the project's knowledge repository. -
Install the skill with
npx skills add Neusis-AI-Org/project-brain-skill --skill project-brain. -
Add
.project-brain.jsonto the root of the code repository:{"repository": "owner/project-brain-repo"}The skill resolves the repository's default branch through
gh. Set an optionaldefaultBranchonly 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
| Concern | File |
|---|---|
| Provider types | packages/integrations/src/types.ts |
| Connector runtime | packages/connector-kit/src/index.ts |
| Persistence adapter | packages/integrations/src/sync/runtime.ts |
| Server registry | packages/integrations/src/registry.ts |
| UI registry | packages/integrations/src/ui/index.ts |
| Token lifecycle | packages/integrations/src/tokens.ts |
| Orphan helper | packages/integrations/src/sync/orphans.ts |
| Scope form | apps/web/components/scope-form.tsx |
| Provider grid | apps/web/app/(app)/projects/[slug]/integrations/page.tsx |
| Sources dashboard | apps/web/app/(app)/projects/[slug]/sources/page.tsx |
| Dynamic scope page | apps/web/app/(app)/projects/[slug]/integrations/[provider]/page.tsx |
| Source detail page | apps/web/app/(app)/projects/[slug]/sources/[provider]/page.tsx |
| Connect route | apps/web/app/api/projects/[slug]/integrations/[product]/connect/route.ts |
| OAuth callback | apps/web/app/api/integrations/oauth/callback/route.ts |
| Worker sync | apps/worker/src/workers/sync.ts |
| Agent skill | skills/project-brain/SKILL.md |