-
Notifications
You must be signed in to change notification settings - Fork 11
Description
Multi-Integration Support Plan
Summary
Enable multiple external integrations (GitHub, GitLab, Linear, Jira, Bitbucket) to run in parallel, with all integrations being optional.
Current State
- GitHub is tightly coupled -
GitHubSyncServicedirectly used byTaskService - Task metadata has fixed
githubfield intask.metadata.github - Config has
sync.githubsection only - No abstraction layer exists for remote systems
Key Changes Needed
1. Create Sync Service Interface
New file: src/core/sync/interface.ts
export type IntegrationId = "github" | "gitlab" | "linear" | "jira" | "bitbucket";
export interface SyncService<T extends IntegrationMetadata = IntegrationMetadata> {
readonly id: IntegrationId;
readonly displayName: string;
syncTask(task: Task, store: TaskStore): Promise<SyncResult<T> | null>;
syncAll(store: TaskStore, options?: SyncAllOptions): Promise<SyncResult<T>[]>;
getRemoteId(task: Task): string | number | null;
getRemoteUrl(task: Task): string | null;
}2. Create Sync Registry
New file: src/core/sync/registry.ts
Manages multiple sync services, allowing parallel execution:
export class SyncRegistry {
register(service: SyncService): void;
get(id: IntegrationId): SyncService | undefined;
getAll(): SyncService[];
hasServices(): boolean;
}3. Update Task Metadata Schema
File: src/types.ts
Add integrations container to support multiple providers:
export const TaskMetadataSchema = z.object({
commit: CommitMetadataSchema.optional(),
github: GithubMetadataSchema.optional(), // Keep for backward compat
integrations: z.object({
github: GithubMetadataSchema.optional(),
gitlab: GitlabMetadataSchema.optional(),
linear: LinearMetadataSchema.optional(),
jira: JiraMetadataSchema.optional(),
bitbucket: BitbucketMetadataSchema.optional(),
}).optional(),
}).nullable();4. Update Configuration
File: src/core/config.ts
Add base interface and provider-specific configs:
export interface IntegrationSyncConfig {
enabled?: boolean;
auto?: { on_change?: boolean; max_age?: string };
}
export interface SyncConfig {
github?: GitHubSyncConfig;
gitlab?: GitLabSyncConfig;
linear?: LinearSyncConfig;
jira?: JiraSyncConfig;
bitbucket?: BitbucketSyncConfig;
}5. Update TaskService
File: src/core/task-service.ts
Replace single syncService with syncRegistry:
export interface TaskServiceOptions {
storage?: StorageEngine | string;
archiveStorage?: ArchiveStorage;
syncRegistry?: SyncRegistry | null; // Replaces syncService
syncConfig?: SyncConfig | null;
}
// New method to sync to all integrations in parallel
private async syncToIntegrations(task: Task): Promise<void> {
if (!this.syncRegistry?.hasServices()) return;
const services = this.syncRegistry.getAll();
await Promise.allSettled(services.map(s => s.syncTask(task, store)));
}6. Adapt GitHub Service
File: src/core/github/sync.ts
Implement the new SyncService interface:
export class GitHubSyncService implements SyncService<GithubMetadata> {
readonly id = "github" as const;
readonly displayName = "GitHub";
getRemoteId(task: Task): number | null {
// Check new format first, fall back to legacy
return task.metadata?.integrations?.github?.issueNumber
?? task.metadata?.github?.issueNumber ?? null;
}
// ... existing methods
}New Directory Structure
src/core/
├── sync/ # NEW
│ ├── index.ts # Re-exports
│ ├── interface.ts # Core interfaces
│ ├── registry.ts # SyncRegistry class
│ └── factory.ts # Creates registry from config
├── github/ # Existing, updated to implement interface
├── gitlab/ # Future
├── linear/ # Future
├── jira/ # Future
└── bitbucket/ # Future
Migration Strategy
- Read from both, write to both: Support
metadata.githubandmetadata.integrations.github - Schema preprocessor migrates old format automatically
- Eventually deprecate legacy
metadata.githubfield
Files to Modify
| File | Changes |
|---|---|
src/types.ts |
Add IntegrationsMetadataSchema, provider-specific schemas |
src/core/config.ts |
Add IntegrationSyncConfig base, provider configs |
src/core/task-service.ts |
Replace syncService with syncRegistry, parallel sync |
src/core/github/sync.ts |
Implement SyncService interface |
src/core/github/sync-factory.ts |
Return service that implements interface |
src/bootstrap.ts |
Use createSyncRegistry() instead of single factory |
src/cli/sync.ts |
Support multiple integrations |
New Files to Create
| File | Purpose |
|---|---|
src/core/sync/interface.ts |
SyncService, SyncResult, IntegrationId types |
src/core/sync/registry.ts |
SyncRegistry class |
src/core/sync/factory.ts |
createSyncRegistry() from config |
src/core/sync/index.ts |
Public exports |
Example Config (Future)
[sync.github]
enabled = true
label_prefix = "dex"
[sync.linear]
enabled = true
team_key = "ENG"
[sync.github.auto]
on_change = true
[sync.linear.auto]
on_change = false
max_age = "1h"Verification
- Run existing tests:
pnpm test - Test backward compatibility with existing GitHub sync
- Verify config loading with multiple integrations
- Test parallel sync behavior
- Verify metadata migration works correctly
Tasks
✅ Update TaskService for Multiple Sync Services
Description
Modify src/core/task-service.ts:
- Replace syncService?: GitHubSyncService with syncRegistry?: SyncRegistry
- Rename syncToGitHub() to syncToIntegrations()
- Implement parallel sync using Promise.allSettled across all registered services
- Update saveIntegrationMetadata() to write to metadata.integrations[provider]
- Update TaskServiceOptions interface
This is part of 'Multi-Integration Support Plan'.
Result
Updated TaskService to use SyncRegistry pattern for multi-integration support:
- Created SyncRegistry class (src/core/sync/registry.ts) with RegisterableSyncService interface
- Replaced syncService with syncRegistry in TaskServiceOptions
- Renamed syncToGitHub() to syncToIntegrations() with Promise.allSettled for parallel sync
- Updated saveIntegrationMetadata to write to both legacy metadata.github and new metadata.integrations[provider] for backward compatibility
- Added id/displayName properties to GitHubSyncService for registry compatibility
- Updated bootstrap.ts with createSyncRegistry() factory function
- Updated CLI, MCP server, and index.ts to use new pattern
- Added IntegrationsMetadataSchema to types.ts
Verification:
- Build passes: pnpm build ✓
- All 790 tests pass: pnpm test ✓
- Code reviewed by code-simplifier agent
✅ Update Task Metadata Schema
Description
Modify src/types.ts to add integrations container:
- Add IntegrationsMetadataSchema with optional github, gitlab, linear, jira, bitbucket fields
- Keep existing github field for backward compatibility
- Add schema preprocessor migration to copy metadata.github to metadata.integrations.github
- Add placeholder schemas for GitLab, Linear, Jira, Bitbucket metadata
This is part of 'Multi-Integration Support Plan'.
Result
Added IntegrationsMetadataSchema to src/types.ts:
- Created IntegrationsMetadataSchema with z.object containing github field (future fields can be added)
- Updated TaskMetadataSchema to include both:
- github (legacy location for backward compatibility)
- integrations (new multi-integration container)
The schema supports both read/write of metadata.github (legacy) and
metadata.integrations.github (new) for seamless migration.
Verification:
- Build passes: pnpm build ✓
- All 790 tests pass: pnpm test ✓
✅ Create Sync Registry
Description
Create new file src/core/sync/registry.ts with SyncRegistry class:
- register(service: SyncService): void
- get(id: IntegrationId): SyncService | undefined
- getAll(): SyncService[]
- hasServices(): boolean
- count(): number
Manages multiple sync services and allows parallel execution.
This is part of 'Multi-Integration Support Plan'.
Result
Created SyncRegistry class in src/core/sync/registry.ts:
- register(service: RegisterableSyncService): void - Register a sync service
- get(id: IntegrationId): RegisterableSyncService | undefined - Get service by ID
- getAll(): RegisterableSyncService[] - Get all registered services
- hasServices(): boolean - Check if any services are registered
Also created RegisterableSyncService interface and LegacySyncResult type to support
both new SyncService interface format and legacy GitHubSyncService format.
Verification:
- Build passes: pnpm build ✓
- All 790 tests pass: pnpm test ✓
✅ Create Sync Service Interface
Description
Create new file src/core/sync/interface.ts with core abstractions:
- IntegrationId type ('github' | 'gitlab' | 'linear' | 'jira' | 'bitbucket')
- IntegrationMetadata base interface
- SyncResult interface
- SyncProgress interface
- SyncAllOptions interface
- SyncService interface with: id, displayName, syncTask(), syncAll(), getRemoteId(), getRemoteUrl()
This is part of 'Multi-Integration Support Plan'.
Result
Created src/core/sync/interface.ts with core abstractions for multi-integration sync support:
- IntegrationId type ('github' | 'gitlab' | 'linear' | 'jira' | 'bitbucket')
- IntegrationMetadata base interface with remoteId and remoteUrl
- SyncResult generic interface for sync operation results
- SyncProgress interface for progress callbacks during syncAll
- SyncAllOptions interface for batch sync configuration
- SyncService interface with: id, displayName, syncTask(), syncAll(), getRemoteId(), getRemoteUrl()
Also created src/core/sync/index.ts barrel export file.
Verification:
- Build passes: pnpm build ✓
- Tests pass: pnpm test ✓
- Code reviewed by code-simplifier agent
✅ Adapt GitHub Service to SyncService Interface
Description
Modify src/core/github/sync.ts:
- Add 'implements SyncService' to GitHubSyncService
- Add readonly id = 'github' property
- Add readonly displayName = 'GitHub' property
- Implement getRemoteId() reading from both old and new metadata formats
- Implement getRemoteUrl() reading from both old and new metadata formats
- Update sync-factory.ts to work with new registry pattern
- Create src/core/sync/factory.ts with createSyncRegistry()
This is part of 'Multi-Integration Support Plan'.
Result
Added getRemoteId() and getRemoteUrl() methods to GitHubSyncService to support the SyncService interface. Both methods handle legacy and new metadata formats for backward compatibility.
✅ Update Configuration Types
Description
Modify src/core/config.ts:
- Add IntegrationSyncConfig base interface with enabled and auto fields
- Add GitLabSyncConfig, LinearSyncConfig, JiraSyncConfig, BitbucketSyncConfig interfaces
- Update SyncConfig to include all provider config options
- Update mergeSyncConfig() to handle multiple integrations
This is part of 'Multi-Integration Support Plan'.
Result
Added IntegrationSyncAuto and IntegrationSyncConfig base interfaces. GitHubSyncConfig now extends IntegrationSyncConfig. Added generic mergeIntegrationConfig() helper for config merging. Build and all 791 tests pass.