diff --git a/examples/README.md b/examples/README.md index 5b6eda5..ab335ea 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,11 +6,13 @@ Project entries can override `issueAssistant` fields when a repo needs a different prompt, mention triggers, responder chain, notification list, or event source. -Each project also has a separate SCM config. `issueTracker.provider` selects -the issue tracker integration, while `scm.provider` selects how temporary repo -workspaces are prepared for responders. Legacy top-level project `provider` is -still accepted during load for backward compatibility. When `scm` is omitted -it defaults to a matching provider for existing GitHub, GitLab, and Gitea configs. +Each project also has separate tracker and SCM config. `issueTracker.provider` +selects the logical tracker family, `issueTracker.eventSource.type` selects the +concrete adapter used to receive updates, and `scm.provider` selects how +temporary repo workspaces are prepared for responders. Legacy top-level project +`provider` and legacy `issueAssistant.eventSource` are still accepted during +load for backward compatibility. When `scm` is omitted it defaults to a +matching provider for existing GitHub, GitLab, and Gitea configs. GitHub projects default to `gh/polling`, GitLab projects default to `glab/polling`, and Gitea projects default to `tea/polling`. OpenProject @@ -32,11 +34,12 @@ recovery, or use `reconciliation: startup-only` to skip background polling after startup. `--once` still uses a single polling reconciliation cycle so existing backlog handling keeps working. -Use a nested `issueAssistant.eventSource` object so source-specific fields stay -grouped with the selected source type. For example: +Use a nested `issueTracker.eventSource` object so adapter-specific fields stay +grouped with the selected adapter key. For example: ```yaml -issueAssistant: +issueTracker: + provider: github eventSource: type: gh/webhook-forward reconciliation: continuous @@ -44,7 +47,8 @@ issueAssistant: ``` ```yaml -issueAssistant: +issueTracker: + provider: github eventSource: type: gh/polling pollInterval: 30s diff --git a/examples/simple-gitea.yaml b/examples/simple-gitea.yaml index a9fe56c..8e8b1f5 100644 --- a/examples/simple-gitea.yaml +++ b/examples/simple-gitea.yaml @@ -7,14 +7,6 @@ worktreeDir: ~/.worktrees defaults: issueAssistant: enabled: true - # Use tea/gosmee for long-running runs. - eventSource: - type: tea/polling - pollInterval: 30s - # eventSource: - # type: tea/gosmee - # reconciliation: startup-only - # reconcileInterval: 30s maxConcurrentIssues: 3 mentionTriggers: ["@codex", "@helper"] responders: @@ -27,6 +19,14 @@ projects: sample-gitea: issueTracker: provider: gitea + # Use tea/gosmee for long-running runs. + eventSource: + type: tea/polling + pollInterval: 30s + # eventSource: + # type: tea/gosmee + # reconciliation: startup-only + # reconcileInterval: 30s scm: provider: gitea repo: owner/repo diff --git a/examples/simple-github.yaml b/examples/simple-github.yaml index 7b546bd..981fcaf 100644 --- a/examples/simple-github.yaml +++ b/examples/simple-github.yaml @@ -7,14 +7,6 @@ worktreeDir: ~/.worktrees defaults: issueAssistant: enabled: true - # Use gh/gosmee or gh/webhook-forward for long-running runs. - eventSource: - type: gh/polling - pollInterval: 30s - # eventSource: - # type: gh/gosmee - # reconciliation: startup-only - # reconcileInterval: 30s maxConcurrentIssues: 3 mentionTriggers: ["@codex", "@helper"] commentPrefixTemplate: | @@ -53,6 +45,14 @@ projects: code-work-spawner: issueTracker: provider: github + # Use gh/gosmee or gh/webhook-forward for long-running runs. + eventSource: + type: gh/polling + pollInterval: 30s + # eventSource: + # type: gh/gosmee + # reconciliation: startup-only + # reconcileInterval: 30s scm: provider: github repo: existedinnettw/code_work_spawner diff --git a/examples/simple-gitlab.yaml b/examples/simple-gitlab.yaml index 7d014ca..2d6fa73 100644 --- a/examples/simple-gitlab.yaml +++ b/examples/simple-gitlab.yaml @@ -7,10 +7,6 @@ worktreeDir: ~/.worktrees defaults: issueAssistant: enabled: true - # GitLab currently supports polling. - eventSource: - type: glab/polling - pollInterval: 30s maxConcurrentIssues: 3 mentionTriggers: ["@codex", "@helper"] responders: @@ -23,6 +19,10 @@ projects: sample-gitlab: issueTracker: provider: gitlab + # GitLab currently supports polling. + eventSource: + type: glab/polling + pollInterval: 30s scm: provider: gitlab repo: group/subgroup/repo diff --git a/examples/simple-openproject.yaml b/examples/simple-openproject.yaml index 81ce65e..d4a50b9 100644 --- a/examples/simple-openproject.yaml +++ b/examples/simple-openproject.yaml @@ -8,9 +8,6 @@ worktreeDir: ~/.worktrees defaults: issueAssistant: enabled: true - eventSource: - type: op/polling - pollInterval: 30s maxConcurrentIssues: 3 mentionTriggers: ["@codex", "@helper"] responders: @@ -23,6 +20,9 @@ projects: sample-openproject: issueTracker: provider: openproject + eventSource: + type: op/polling + pollInterval: 30s scm: provider: github # OpenProject project name or numeric project id. diff --git a/lib/src/config.dart b/lib/src/config.dart index 02b16e4..7ffb4bc 100644 --- a/lib/src/config.dart +++ b/lib/src/config.dart @@ -11,8 +11,8 @@ import 'config_schema.dart'; export 'config_schema.dart' show AssistantCapability, - IssueAssistantEventSourceKind, - IssueAssistantReconciliationKind, + IssueTrackerEventSourceKind, + IssueTrackerReconciliationKind, IssueTrackerProvider, ScmProvider, NotificationConfigDocument, @@ -139,7 +139,7 @@ class AppConfig { issueAssistant: IssueAssistantConfigDocument( enabled: true, eventSource: IssueAssistantEventSourceDocument( - type: IssueAssistantEventSourceKind.ghPolling, + type: IssueTrackerEventSourceKind.ghPolling, pollInterval: '30s', ), maxConcurrentIssues: 3, @@ -202,10 +202,33 @@ class ProjectConfig { ); } - final provider = - document.issueTracker?.provider ?? - document.provider ?? - IssueTrackerProvider.github; + final resolvedIssueAssistantDocument = + _resolveProjectIssueAssistantDocument( + key: key, + issueTracker: document.issueTracker, + issueAssistant: document.issueAssistant, + ); + final hasExplicitEventSourceType = + resolvedIssueAssistantDocument?.eventSource?.type != null; + var issueAssistant = defaultAssistant.merge(resolvedIssueAssistantDocument); + final configuredProvider = + document.issueTracker?.provider ?? document.provider; + final inferredProvider = _issueTrackerProviderForEventSource( + issueAssistant.eventSource.type, + ); + if (hasExplicitEventSourceType && + configuredProvider != null && + configuredProvider != inferredProvider) { + throw FormatException( + 'projects.$key.issueAssistant.eventSource ' + '${_eventSourceConfigValue(issueAssistant.eventSource.type)} ' + 'implies issue tracker provider ${inferredProvider.name}, ' + 'but the legacy configured provider is ${configuredProvider.name}.', + ); + } + final provider = hasExplicitEventSourceType + ? inferredProvider + : configuredProvider ?? inferredProvider; if (provider == IssueTrackerProvider.gitlab) { _validateGitLabProjectPath(repo, field: 'projects.$key.repo'); } @@ -213,15 +236,14 @@ class ProjectConfig { if (document.scm?.provider == null && fallbackScmProvider == null) { throw FormatException( 'projects.$key.scm.provider is required for ' - 'issueTracker.provider: ${provider.name}.', + 'issue tracking provider: ${provider.name}.', ); } final scm = ScmConfig.fromDocument( document.scm, fallbackProvider: fallbackScmProvider, ); - var issueAssistant = defaultAssistant.merge(document.issueAssistant); - if (document.issueAssistant?.eventSource?.type == null && + if (!hasExplicitEventSourceType && issueAssistant.eventSource is PollingIssueEventSourceConfig) { issueAssistant = issueAssistant.copyWith( eventSource: @@ -229,58 +251,17 @@ class ProjectConfig { .copyWith( type: switch (provider) { IssueTrackerProvider.github => - IssueAssistantEventSourceKind.ghPolling, + IssueTrackerEventSourceKind.ghPolling, IssueTrackerProvider.gitlab => - IssueAssistantEventSourceKind.glabPolling, + IssueTrackerEventSourceKind.glabPolling, IssueTrackerProvider.gitea => - IssueAssistantEventSourceKind.teaPolling, + IssueTrackerEventSourceKind.teaPolling, IssueTrackerProvider.openproject => - IssueAssistantEventSourceKind.opPolling, + IssueTrackerEventSourceKind.opPolling, }, ), ); } - switch (issueAssistant.eventSource.type) { - case IssueAssistantEventSourceKind.ghPolling: - case IssueAssistantEventSourceKind.ghGosmee: - case IssueAssistantEventSourceKind.ghWebhookForward: - if (provider != IssueTrackerProvider.github) { - throw FormatException( - 'projects.$key.issueAssistant.eventSource ' - '${_eventSourceConfigValue(issueAssistant.eventSource.type)} ' - 'requires issueTracker.provider: github.', - ); - } - case IssueAssistantEventSourceKind.glabPolling: - if (provider != IssueTrackerProvider.gitlab) { - throw FormatException( - 'projects.$key.issueAssistant.eventSource glab/polling ' - 'requires issueTracker.provider: gitlab.', - ); - } - case IssueAssistantEventSourceKind.teaPolling: - if (provider != IssueTrackerProvider.gitea) { - throw FormatException( - 'projects.$key.issueAssistant.eventSource tea/polling ' - 'requires issueTracker.provider: gitea.', - ); - } - case IssueAssistantEventSourceKind.teaGosmee: - if (provider != IssueTrackerProvider.gitea) { - throw FormatException( - 'projects.$key.issueAssistant.eventSource tea/gosmee ' - 'requires issueTracker.provider: gitea.', - ); - } - case IssueAssistantEventSourceKind.opPolling: - if (provider != IssueTrackerProvider.openproject) { - throw FormatException( - 'projects.$key.issueAssistant.eventSource op/polling ' - 'requires issueTracker.provider: openproject.', - ); - } - } - if (provider == IssueTrackerProvider.openproject && repo.trim().isEmpty) { throw FormatException( 'Invalid OpenProject project reference for projects.$key.repo: ' @@ -343,6 +324,49 @@ class ProjectConfig { } } +IssueAssistantConfigDocument? _resolveProjectIssueAssistantDocument({ + required String key, + required IssueTrackerConfigDocument? issueTracker, + required IssueAssistantConfigDocument? issueAssistant, +}) { + final trackerEventSource = issueTracker?.eventSource; + final assistantEventSource = issueAssistant?.eventSource; + if (trackerEventSource != null && + assistantEventSource != null && + !_eventSourceDocumentsMatch(trackerEventSource, assistantEventSource)) { + throw FormatException( + 'projects.$key.issueTracker.eventSource and ' + 'projects.$key.issueAssistant.eventSource must match when both are set.', + ); + } + if (trackerEventSource == null) { + return issueAssistant; + } + return IssueAssistantConfigDocument( + enabled: issueAssistant?.enabled, + eventSource: trackerEventSource, + maxConcurrentIssues: issueAssistant?.maxConcurrentIssues, + mentionTriggers: issueAssistant?.mentionTriggers, + prompt: issueAssistant?.prompt, + responders: issueAssistant?.responders, + capabilities: issueAssistant?.capabilities, + notifications: issueAssistant?.notifications, + commentTag: issueAssistant?.commentTag, + commentPrefixTemplate: issueAssistant?.commentPrefixTemplate, + commentFooterTemplate: issueAssistant?.commentFooterTemplate, + ); +} + +bool _eventSourceDocumentsMatch( + IssueAssistantEventSourceDocument left, + IssueAssistantEventSourceDocument right, +) { + return left.type == right.type && + left.pollInterval == right.pollInterval && + left.reconciliation == right.reconciliation && + left.reconcileInterval == right.reconcileInterval; +} + class ScmConfig { const ScmConfig({required this.provider}); @@ -422,7 +446,7 @@ class IssueAssistantConfig { sealed class IssueEventSourceConfig { const IssueEventSourceConfig({required this.type}); - final IssueAssistantEventSourceKind type; + final IssueTrackerEventSourceKind type; bool get usesChannelEventSource => !_isPollingEventSource(type); } @@ -436,7 +460,7 @@ class PollingIssueEventSourceConfig extends IssueEventSourceConfig { final Duration pollInterval; PollingIssueEventSourceConfig copyWith({ - IssueAssistantEventSourceKind? type, + IssueTrackerEventSourceKind? type, Duration? pollInterval, }) { return PollingIssueEventSourceConfig( @@ -453,12 +477,12 @@ class ChannelIssueEventSourceConfig extends IssueEventSourceConfig { required this.reconcileInterval, }); - final IssueAssistantReconciliationKind reconciliation; + final IssueTrackerReconciliationKind reconciliation; final Duration reconcileInterval; ChannelIssueEventSourceConfig copyWith({ - IssueAssistantEventSourceKind? type, - IssueAssistantReconciliationKind? reconciliation, + IssueTrackerEventSourceKind? type, + IssueTrackerReconciliationKind? reconciliation, Duration? reconcileInterval, }) { return ChannelIssueEventSourceConfig( @@ -608,7 +632,7 @@ class _IssueAssistantConfigResolver { required IssueAssistantEventSourceDocument? overlay, }) { final type = - overlay?.type ?? base?.type ?? IssueAssistantEventSourceKind.ghPolling; + overlay?.type ?? base?.type ?? IssueTrackerEventSourceKind.ghPolling; if (_isPollingEventSource(type)) { if (overlay?.reconciliation != null || overlay?.reconcileInterval != null) { @@ -643,7 +667,7 @@ class _IssueAssistantConfigResolver { switch (base) { ChannelIssueEventSourceConfig(:final reconciliation) => reconciliation, - _ => IssueAssistantReconciliationKind.continuous, + _ => IssueTrackerReconciliationKind.continuous, }, reconcileInterval: overlay?.reconcileInterval != null ? _parseDuration(overlay!.reconcileInterval!) @@ -654,27 +678,27 @@ class _IssueAssistantConfigResolver { } } -String _eventSourceConfigValue(IssueAssistantEventSourceKind value) { +String _eventSourceConfigValue(IssueTrackerEventSourceKind value) { return switch (value) { - IssueAssistantEventSourceKind.ghPolling => 'gh/polling', - IssueAssistantEventSourceKind.glabPolling => 'glab/polling', - IssueAssistantEventSourceKind.teaPolling => 'tea/polling', - IssueAssistantEventSourceKind.opPolling => 'op/polling', - IssueAssistantEventSourceKind.ghGosmee => 'gh/gosmee', - IssueAssistantEventSourceKind.ghWebhookForward => 'gh/webhook-forward', - IssueAssistantEventSourceKind.teaGosmee => 'tea/gosmee', + IssueTrackerEventSourceKind.ghPolling => 'gh/polling', + IssueTrackerEventSourceKind.glabPolling => 'glab/polling', + IssueTrackerEventSourceKind.teaPolling => 'tea/polling', + IssueTrackerEventSourceKind.opPolling => 'op/polling', + IssueTrackerEventSourceKind.ghGosmee => 'gh/gosmee', + IssueTrackerEventSourceKind.ghWebhookForward => 'gh/webhook-forward', + IssueTrackerEventSourceKind.teaGosmee => 'tea/gosmee', }; } -bool _isPollingEventSource(IssueAssistantEventSourceKind value) { +bool _isPollingEventSource(IssueTrackerEventSourceKind value) { return switch (value) { - IssueAssistantEventSourceKind.ghPolling => true, - IssueAssistantEventSourceKind.glabPolling => true, - IssueAssistantEventSourceKind.teaPolling => true, - IssueAssistantEventSourceKind.opPolling => true, - IssueAssistantEventSourceKind.ghGosmee => false, - IssueAssistantEventSourceKind.ghWebhookForward => false, - IssueAssistantEventSourceKind.teaGosmee => false, + IssueTrackerEventSourceKind.ghPolling => true, + IssueTrackerEventSourceKind.glabPolling => true, + IssueTrackerEventSourceKind.teaPolling => true, + IssueTrackerEventSourceKind.opPolling => true, + IssueTrackerEventSourceKind.ghGosmee => false, + IssueTrackerEventSourceKind.ghWebhookForward => false, + IssueTrackerEventSourceKind.teaGosmee => false, }; } @@ -864,6 +888,13 @@ void _assertCamelCaseConfigKeys(Map root) { issueTracker, scope: 'projects.${entry.key}.issueTracker', ); + final issueTrackerEventSource = issueTracker['eventSource']; + if (issueTrackerEventSource is Map) { + _assertCamelCaseMap( + issueTrackerEventSource, + scope: 'projects.${entry.key}.issueTracker.eventSource', + ); + } } _assertCamelCaseMap(project, scope: 'projects.${entry.key}'); final scm = project['scm']; @@ -919,6 +950,20 @@ ScmProvider? _defaultScmProviderForTracker(IssueTrackerProvider provider) { }; } +IssueTrackerProvider _issueTrackerProviderForEventSource( + IssueTrackerEventSourceKind type, +) { + return switch (type) { + IssueTrackerEventSourceKind.ghPolling => IssueTrackerProvider.github, + IssueTrackerEventSourceKind.glabPolling => IssueTrackerProvider.gitlab, + IssueTrackerEventSourceKind.teaPolling => IssueTrackerProvider.gitea, + IssueTrackerEventSourceKind.opPolling => IssueTrackerProvider.openproject, + IssueTrackerEventSourceKind.ghGosmee => IssueTrackerProvider.github, + IssueTrackerEventSourceKind.ghWebhookForward => IssueTrackerProvider.github, + IssueTrackerEventSourceKind.teaGosmee => IssueTrackerProvider.gitea, + }; +} + void _normalizeLegacyEventSourceConfig(Map root) { void normalizeProjectTracker(Map project) { final repo = project['repo']; @@ -942,9 +987,31 @@ void _normalizeLegacyEventSourceConfig(Map root) { } } + void normalizeIssueTrackerEventSource( + Map issueTracker, { + required IssueTrackerEventSourceKind? defaultType, + }) { + final rawEventSource = issueTracker['eventSource']; + Map? normalizedEventSource; + + if (rawEventSource is String) { + normalizedEventSource = {'type': rawEventSource}; + } else if (rawEventSource is Map) { + normalizedEventSource = Map.from(rawEventSource); + } else { + return; + } + + if (defaultType != null && !normalizedEventSource.containsKey('type')) { + normalizedEventSource['type'] = _eventSourceConfigValue(defaultType); + } + + issueTracker['eventSource'] = normalizedEventSource; + } + void normalizeIssueAssistant( Map issueAssistant, { - required IssueAssistantEventSourceKind? defaultType, + required IssueTrackerEventSourceKind? defaultType, }) { final rawEventSource = issueAssistant['eventSource']; Map? normalizedEventSource; @@ -989,7 +1056,7 @@ void _normalizeLegacyEventSourceConfig(Map root) { if (issueAssistant is Map) { normalizeIssueAssistant( issueAssistant, - defaultType: IssueAssistantEventSourceKind.ghPolling, + defaultType: IssueTrackerEventSourceKind.ghPolling, ); } } @@ -1002,8 +1069,28 @@ void _normalizeLegacyEventSourceConfig(Map root) { continue; } normalizeProjectTracker(project); + final issueTracker = project['issueTracker']; + if (issueTracker is Map) { + normalizeIssueTrackerEventSource( + issueTracker, + defaultType: switch (issueTracker['provider'] as String?) { + 'gitlab' => IssueTrackerEventSourceKind.glabPolling, + 'gitea' => IssueTrackerEventSourceKind.teaPolling, + 'openproject' => IssueTrackerEventSourceKind.opPolling, + _ => IssueTrackerEventSourceKind.ghPolling, + }, + ); + } final issueAssistant = project['issueAssistant']; if (issueAssistant is! Map) { + if (issueTracker is Map && + issueTracker['eventSource'] is Map) { + project['issueAssistant'] = { + 'eventSource': Map.from( + issueTracker['eventSource'] as Map, + ), + }; + } continue; } normalizeIssueAssistant( @@ -1011,11 +1098,19 @@ void _normalizeLegacyEventSourceConfig(Map root) { defaultType: switch ((project['issueTracker'] as Map?)?['provider'] as String?) { - 'gitlab' => IssueAssistantEventSourceKind.glabPolling, - 'gitea' => IssueAssistantEventSourceKind.teaPolling, - _ => IssueAssistantEventSourceKind.ghPolling, + 'gitlab' => IssueTrackerEventSourceKind.glabPolling, + 'gitea' => IssueTrackerEventSourceKind.teaPolling, + 'openproject' => IssueTrackerEventSourceKind.opPolling, + _ => IssueTrackerEventSourceKind.ghPolling, }, ); + if (issueTracker is Map && + issueTracker['eventSource'] == null && + issueAssistant['eventSource'] is Map) { + issueTracker['eventSource'] = Map.from( + issueAssistant['eventSource'] as Map, + ); + } } } } @@ -1098,10 +1193,6 @@ ${_YamlEmitter().write({'projects': projectsSection})} # issueAssistant: # enabled: ${assistant?.enabled ?? true} - # eventSource: - # type: gh/gosmee - # reconciliation: startup-only - # reconcileInterval: 30s # mentionTriggers: ["@helper"] # sessionPrefix: sample ''' diff --git a/lib/src/config_schema.dart b/lib/src/config_schema.dart index 3a4b207..7b69ff8 100644 --- a/lib/src/config_schema.dart +++ b/lib/src/config_schema.dart @@ -88,9 +88,10 @@ class ProjectConfigDocument { includeIfNull: false, ) class IssueTrackerConfigDocument { - const IssueTrackerConfigDocument({this.provider}); + const IssueTrackerConfigDocument({this.provider, this.eventSource}); final IssueTrackerProvider? provider; + final IssueAssistantEventSourceDocument? eventSource; factory IssueTrackerConfigDocument.fromJson(Map json) => _$IssueTrackerConfigDocumentFromJson(json); @@ -171,9 +172,9 @@ class IssueAssistantEventSourceDocument { this.reconcileInterval, }); - final IssueAssistantEventSourceKind? type; + final IssueTrackerEventSourceKind? type; final String? pollInterval; - final IssueAssistantReconciliationKind? reconciliation; + final IssueTrackerReconciliationKind? reconciliation; final String? reconcileInterval; factory IssueAssistantEventSourceDocument.fromJson( @@ -247,7 +248,7 @@ class NotificationConfigDocument { @JsonEnum(fieldRename: FieldRename.snake) enum AssistantCapability { planning, bugVerification } -enum IssueAssistantEventSourceKind { +enum IssueTrackerEventSourceKind { @JsonValue('gh/polling') ghPolling, @JsonValue('glab/polling') @@ -264,7 +265,7 @@ enum IssueAssistantEventSourceKind { teaGosmee, } -enum IssueAssistantReconciliationKind { +enum IssueTrackerReconciliationKind { @JsonValue('startup-only') startupOnly, @JsonValue('continuous') diff --git a/lib/src/issue_assistant_app.dart b/lib/src/issue_assistant_app.dart index 1e9002b..3de596d 100644 --- a/lib/src/issue_assistant_app.dart +++ b/lib/src/issue_assistant_app.dart @@ -63,21 +63,21 @@ class IssueAssistantApp { issueTrackerClient: issueTrackerClient, issueEventSource: RoutedIssueEventSource( sources: { - IssueAssistantEventSourceKind.ghPolling: pollingIssueEventSource, - IssueAssistantEventSourceKind.glabPolling: pollingIssueEventSource, - IssueAssistantEventSourceKind.teaPolling: pollingIssueEventSource, - IssueAssistantEventSourceKind.opPolling: pollingIssueEventSource, - IssueAssistantEventSourceKind.ghGosmee: GitHubGosmeeIssueEventSource( + IssueTrackerEventSourceKind.ghPolling: pollingIssueEventSource, + IssueTrackerEventSourceKind.glabPolling: pollingIssueEventSource, + IssueTrackerEventSourceKind.teaPolling: pollingIssueEventSource, + IssueTrackerEventSourceKind.opPolling: pollingIssueEventSource, + IssueTrackerEventSourceKind.ghGosmee: GitHubGosmeeIssueEventSource( issueTrackerClient: issueTrackerClient, gosmeeCommand: gosmeeCommand, logger: _logger, ), - IssueAssistantEventSourceKind.ghWebhookForward: + IssueTrackerEventSourceKind.ghWebhookForward: GitHubWebhookForwardIssueEventSource( issueTrackerClient: issueTrackerClient, logger: _logger, ), - IssueAssistantEventSourceKind.teaGosmee: GiteaGosmeeIssueEventSource( + IssueTrackerEventSourceKind.teaGosmee: GiteaGosmeeIssueEventSource( issueTrackerClient: issueTrackerClient, gosmeeCommand: gosmeeCommand, logger: _logger, diff --git a/lib/src/issue_event_source/base.dart b/lib/src/issue_event_source/base.dart index 8241d2a..4db612b 100644 --- a/lib/src/issue_event_source/base.dart +++ b/lib/src/issue_event_source/base.dart @@ -39,7 +39,7 @@ abstract class IssueEventSource { class RoutedIssueEventSource implements IssueEventSource { RoutedIssueEventSource({required this.sources}); - final Map sources; + final Map sources; @override Future run({ @@ -50,17 +50,15 @@ class RoutedIssueEventSource implements IssueEventSource { .where(_shouldRunStartupReconciliation) .toList(growable: false); if (startupReconcileProjects.isNotEmpty) { - await sources[IssueAssistantEventSourceKind.ghPolling]!.runOnce( + await sources[IssueTrackerEventSourceKind.ghPolling]!.runOnce( projects: startupReconcileProjects, onBatch: onBatch, ); } - final projectsBySource = - >{ - for (final eventSource in sources.keys) - eventSource: [], - }; + final projectsBySource = >{ + for (final eventSource in sources.keys) eventSource: [], + }; for (final project in projects) { for (final projectSource in _registeredSourcesFor(project)) { final sourceProjects = projectsBySource[projectSource]; @@ -90,7 +88,7 @@ class RoutedIssueEventSource implements IssueEventSource { required List projects, required IssueEventBatchHandler onBatch, }) { - return sources[IssueAssistantEventSourceKind.ghPolling]!.runOnce( + return sources[IssueTrackerEventSourceKind.ghPolling]!.runOnce( projects: projects, onBatch: onBatch, ); @@ -103,33 +101,33 @@ class RoutedIssueEventSource implements IssueEventSource { } } - Iterable _registeredSourcesFor( + Iterable _registeredSourcesFor( ProjectConfig project, ) sync* { switch (project.issueAssistant.eventSource.type) { - case IssueAssistantEventSourceKind.ghPolling: - yield IssueAssistantEventSourceKind.ghPolling; - case IssueAssistantEventSourceKind.glabPolling: - yield IssueAssistantEventSourceKind.glabPolling; - case IssueAssistantEventSourceKind.teaPolling: - yield IssueAssistantEventSourceKind.teaPolling; - case IssueAssistantEventSourceKind.opPolling: - yield IssueAssistantEventSourceKind.opPolling; - case IssueAssistantEventSourceKind.ghGosmee: + case IssueTrackerEventSourceKind.ghPolling: + yield IssueTrackerEventSourceKind.ghPolling; + case IssueTrackerEventSourceKind.glabPolling: + yield IssueTrackerEventSourceKind.glabPolling; + case IssueTrackerEventSourceKind.teaPolling: + yield IssueTrackerEventSourceKind.teaPolling; + case IssueTrackerEventSourceKind.opPolling: + yield IssueTrackerEventSourceKind.opPolling; + case IssueTrackerEventSourceKind.ghGosmee: if (_shouldRunContinuousReconciliation(project)) { - yield IssueAssistantEventSourceKind.ghPolling; + yield IssueTrackerEventSourceKind.ghPolling; } - yield IssueAssistantEventSourceKind.ghGosmee; - case IssueAssistantEventSourceKind.ghWebhookForward: + yield IssueTrackerEventSourceKind.ghGosmee; + case IssueTrackerEventSourceKind.ghWebhookForward: if (_shouldRunContinuousReconciliation(project)) { - yield IssueAssistantEventSourceKind.ghPolling; + yield IssueTrackerEventSourceKind.ghPolling; } - yield IssueAssistantEventSourceKind.ghWebhookForward; - case IssueAssistantEventSourceKind.teaGosmee: + yield IssueTrackerEventSourceKind.ghWebhookForward; + case IssueTrackerEventSourceKind.teaGosmee: if (_shouldRunContinuousReconciliation(project)) { - yield IssueAssistantEventSourceKind.teaPolling; + yield IssueTrackerEventSourceKind.teaPolling; } - yield IssueAssistantEventSourceKind.teaGosmee; + yield IssueTrackerEventSourceKind.teaGosmee; } } @@ -141,7 +139,7 @@ class RoutedIssueEventSource implements IssueEventSource { project.issueAssistant.eventSource is ChannelIssueEventSourceConfig && (project.issueAssistant.eventSource as ChannelIssueEventSourceConfig) .reconciliation == - IssueAssistantReconciliationKind.continuous; + IssueTrackerReconciliationKind.continuous; } class PollingIssueEventSource implements IssueEventSource { diff --git a/test/config/app_config_test.dart b/test/config/app_config_test.dart index d198f78..f09c9d6 100644 --- a/test/config/app_config_test.dart +++ b/test/config/app_config_test.dart @@ -407,21 +407,22 @@ projects: }); /// ```gherkin - /// Scenario: Load an explicit Gitea issue tracker provider - /// Given a config whose project sets issueTracker.provider to gitea + /// Scenario: Infer a Gitea issue tracker provider from the event source + /// Given a config whose project sets tea/polling as the event source type /// When loading the app config from disk - /// Then the project keeps the configured Gitea provider + /// Then the project infers the Gitea provider /// And the repository slug still parses normally /// ``` - test('Load an explicit Gitea issue tracker provider', () async { - // Given a config whose project sets issueTracker.provider to gitea. + test('Infer a Gitea issue tracker provider from the event source', () async { + // Given a config whose project sets tea/polling as the event source type. final tempDir = await Directory.systemTemp.createTemp('cws-gitea-'); final configFile = File(p.join(tempDir.path, 'agent-orchestrator.yaml')); await configFile.writeAsString(''' projects: sample: issueTracker: - provider: gitea + eventSource: + type: tea/polling repo: owner/sample path: ${tempDir.path} '''); @@ -429,7 +430,7 @@ projects: // When loading the app config from disk. final config = await AppConfig.load(configFile.path); - // Then the project keeps the configured Gitea provider. + // Then the project infers the Gitea provider. expect(config.projects['sample']!.provider, IssueTrackerProvider.gitea); expect(config.projects['sample']!.scm.provider, ScmProvider.gitea); @@ -437,26 +438,27 @@ projects: expect(config.projects['sample']!.repoSlug!.fullName, 'owner/sample'); expect( config.projects['sample']!.issueAssistant.eventSource.type, - IssueAssistantEventSourceKind.teaPolling, + IssueTrackerEventSourceKind.teaPolling, ); }); /// ```gherkin - /// Scenario: Load an explicit GitLab issue tracker provider - /// Given a config whose project sets issueTracker.provider to gitlab + /// Scenario: Infer a GitLab issue tracker provider from the event source + /// Given a config whose project sets glab/polling as the event source type /// When loading the app config from disk - /// Then the project keeps the configured GitLab provider + /// Then the project infers the GitLab provider /// And the project uses GitLab polling and GitLab SCM by default /// ``` - test('Load an explicit GitLab issue tracker provider', () async { - // Given a config whose project sets issueTracker.provider to gitlab. + test('Infer a GitLab issue tracker provider from the event source', () async { + // Given a config whose project sets glab/polling as the event source type. final tempDir = await Directory.systemTemp.createTemp('cws-gitlab-'); final configFile = File(p.join(tempDir.path, 'agent-orchestrator.yaml')); await configFile.writeAsString(''' projects: sample: issueTracker: - provider: gitlab + eventSource: + type: glab/polling repo: group/subgroup/sample path: ${tempDir.path} '''); @@ -464,67 +466,75 @@ projects: // When loading the app config from disk. final config = await AppConfig.load(configFile.path); - // Then the project keeps the configured GitLab provider. + // Then the project infers the GitLab provider. expect(config.projects['sample']!.provider, IssueTrackerProvider.gitlab); // And the project uses GitLab polling and GitLab SCM by default. expect(config.projects['sample']!.scm.provider, ScmProvider.gitlab); expect( config.projects['sample']!.issueAssistant.eventSource.type, - IssueAssistantEventSourceKind.glabPolling, + IssueTrackerEventSourceKind.glabPolling, ); expect(config.projects['sample']!.repo, 'group/subgroup/sample'); }); /// ```gherkin - /// Scenario: Load an explicit OpenProject issue tracker provider - /// Given a config whose project sets issueTracker.provider to openproject + /// Scenario: Infer an OpenProject issue tracker provider from the event source + /// Given a config whose project sets op/polling as the event source type /// And the project declares an explicit SCM provider /// When loading the app config from disk - /// Then the project keeps the configured OpenProject provider + /// Then the project infers the OpenProject provider /// And the project uses op/polling as its default issue event source /// ``` - test('Load an explicit OpenProject issue tracker provider', () async { - // Given a config whose project sets issueTracker.provider to openproject. - final tempDir = await Directory.systemTemp.createTemp('cws-openproject-'); - final configFile = File(p.join(tempDir.path, 'agent-orchestrator.yaml')); - await configFile.writeAsString(''' + test( + 'Infer an OpenProject issue tracker provider from the event source', + () async { + // Given a config whose project sets op/polling as the event source type. + final tempDir = await Directory.systemTemp.createTemp( + 'cws-openproject-', + ); + final configFile = File( + p.join(tempDir.path, 'agent-orchestrator.yaml'), + ); + await configFile.writeAsString(''' projects: sample: issueTracker: - provider: openproject + eventSource: + type: op/polling scm: provider: github repo: 42 path: ${tempDir.path} '''); - // When loading the app config from disk. - final config = await AppConfig.load(configFile.path); + // When loading the app config from disk. + final config = await AppConfig.load(configFile.path); - // Then the project keeps the configured OpenProject provider. - expect( - config.projects['sample']!.provider, - IssueTrackerProvider.openproject, - ); - expect(config.projects['sample']!.scm.provider, ScmProvider.github); + // Then the project infers the OpenProject provider. + expect( + config.projects['sample']!.provider, + IssueTrackerProvider.openproject, + ); + expect(config.projects['sample']!.scm.provider, ScmProvider.github); - // And the project uses op/polling as its default issue event source. - expect( - config.projects['sample']!.issueAssistant.eventSource.type, - IssueAssistantEventSourceKind.opPolling, - ); - }); + // And the project uses op/polling as its default issue event source. + expect( + config.projects['sample']!.issueAssistant.eventSource.type, + IssueTrackerEventSourceKind.opPolling, + ); + }, + ); /// ```gherkin /// Scenario: Require an explicit SCM provider for OpenProject projects - /// Given a config whose project uses the OpenProject issue tracker + /// Given a config whose project uses op/polling as the event source /// And the project omits scm.provider /// When loading the app config from disk /// Then loading fails with a clear SCM requirement error /// ``` test('Require an explicit SCM provider for OpenProject projects', () async { - // Given a config whose project uses the OpenProject issue tracker. + // Given a config whose project uses op/polling as the event source. final tempDir = await Directory.systemTemp.createTemp( 'cws-openproject-missing-scm-', ); @@ -533,7 +543,8 @@ projects: projects: sample: issueTracker: - provider: openproject + eventSource: + type: op/polling repo: 42 path: ${tempDir.path} '''); @@ -551,7 +562,7 @@ projects: (error) => error.message, 'message', contains( - 'projects.sample.scm.provider is required for issueTracker.provider: openproject', + 'projects.sample.scm.provider is required for issue tracking provider: openproject', ), ), ), @@ -560,13 +571,13 @@ projects: /// ```gherkin /// Scenario: Accept an OpenProject project name reference - /// Given a config whose project uses the OpenProject issue tracker + /// Given a config whose project uses op/polling as the event source /// And the project repo field is a human-readable project name /// When loading the app config from disk /// Then loading succeeds without requiring a numeric project id /// ``` test('Accept an OpenProject project name reference', () async { - // Given a config whose project uses the OpenProject issue tracker. + // Given a config whose project uses op/polling as the event source. final tempDir = await Directory.systemTemp.createTemp( 'cws-openproject-project-name-', ); @@ -575,7 +586,8 @@ projects: projects: sample: issueTracker: - provider: openproject + eventSource: + type: op/polling scm: provider: github repo: Demo project @@ -610,12 +622,10 @@ projects: projects: sample: issueTracker: - provider: github - repo: owner/sample - path: ${tempDir.path} - issueAssistant: eventSource: type: gh/gosmee + repo: owner/sample + path: ${tempDir.path} '''); // When loading the app config from disk. @@ -624,7 +634,7 @@ projects: // Then the project keeps gh/gosmee as its event source. expect( config.projects['sample']!.issueAssistant.eventSource.type, - IssueAssistantEventSourceKind.ghGosmee, + IssueTrackerEventSourceKind.ghGosmee, ); // And the GitHub provider remains valid for that project. @@ -652,7 +662,8 @@ projects: projects: sample: issueTracker: - provider: gitea + eventSource: + type: tea/polling scm: provider: github repo: owner/sample @@ -671,15 +682,17 @@ projects: ); /// ```gherkin - /// Scenario: Reject gh/gosmee for non-GitHub providers - /// Given a Gitea project config that opts into gh/gosmee + /// Scenario: Keep the legacy issue tracker provider as a fallback + /// Given a config whose project still sets issueTracker.provider to gitea + /// And the project omits issueAssistant.eventSource.type /// When loading the app config from disk - /// Then loading fails with a clear provider compatibility error + /// Then the project keeps the configured Gitea provider + /// And the polling event source is rewritten to tea/polling /// ``` - test('Reject gh/gosmee for non-GitHub providers', () async { - // Given a Gitea project config that opts into gh/gosmee. + test('Keep the legacy issue tracker provider as a fallback', () async { + // Given a config whose project still sets issueTracker.provider to gitea. final tempDir = await Directory.systemTemp.createTemp( - 'cws-gh-gosmee-gitea-', + 'cws-legacy-provider-fallback-', ); final configFile = File(p.join(tempDir.path, 'agent-orchestrator.yaml')); await configFile.writeAsString(''' @@ -689,22 +702,60 @@ projects: provider: gitea repo: owner/sample path: ${tempDir.path} - issueAssistant: +'''); + + // And the project omits issueAssistant.eventSource.type. + + // When loading the app config from disk. + final config = await AppConfig.load(configFile.path); + + // Then the project keeps the configured Gitea provider. + expect(config.projects['sample']!.provider, IssueTrackerProvider.gitea); + + // And the polling event source is rewritten to tea/polling. + expect( + config.projects['sample']!.issueAssistant.eventSource.type, + IssueTrackerEventSourceKind.teaPolling, + ); + }); + + /// ```gherkin + /// Scenario: Reject conflicting legacy tracker and event source settings + /// Given a project config whose legacy tracker provider is gitea + /// And the project opts into gh/gosmee + /// When loading the app config from disk + /// Then loading fails with a clear conflict error + /// ``` + test('Reject conflicting legacy tracker and event source settings', () async { + // Given a project config whose legacy tracker provider is gitea. + final tempDir = await Directory.systemTemp.createTemp( + 'cws-gh-gosmee-gitea-', + ); + final configFile = File(p.join(tempDir.path, 'agent-orchestrator.yaml')); + await configFile.writeAsString(''' +projects: + sample: + issueTracker: + provider: gitea eventSource: type: gh/gosmee + repo: owner/sample + path: ${tempDir.path} '''); // When loading the app config from disk. final load = AppConfig.load(configFile.path); - // Then loading fails with a clear provider compatibility error. + // Then loading fails with a clear conflict error. await expectLater( load, throwsA( isA().having( (error) => error.message, 'message', - contains('gh/gosmee requires issueTracker.provider: github'), + contains( + 'gh/gosmee implies issue tracker provider github, but the legacy configured provider is gitea', + ), ), ), ); @@ -727,12 +778,10 @@ projects: projects: sample: issueTracker: - provider: github - repo: owner/sample - path: ${tempDir.path} - issueAssistant: eventSource: type: gh/webhook-forward + repo: owner/sample + path: ${tempDir.path} '''); // When loading the app config from disk. @@ -741,7 +790,7 @@ projects: // Then the project keeps gh/webhook-forward as its event source. expect( config.projects['sample']!.issueAssistant.eventSource.type, - IssueAssistantEventSourceKind.ghWebhookForward, + IssueTrackerEventSourceKind.ghWebhookForward, ); // And the GitHub provider remains valid for that project. @@ -765,14 +814,12 @@ projects: projects: sample: issueTracker: - provider: github - repo: owner/sample - path: ${tempDir.path} - issueAssistant: eventSource: type: gh/webhook-forward reconciliation: startup-only reconcileInterval: 5m + repo: owner/sample + path: ${tempDir.path} '''); // When loading the app config from disk. @@ -783,7 +830,7 @@ projects: (config.projects['sample']!.issueAssistant.eventSource as ChannelIssueEventSourceConfig) .reconciliation, - IssueAssistantReconciliationKind.startupOnly, + IssueTrackerReconciliationKind.startupOnly, ); // And the configured reconcile interval is parsed for channel recovery. @@ -812,13 +859,11 @@ projects: projects: sample: issueTracker: - provider: github - repo: owner/sample - path: ${tempDir.path} - issueAssistant: eventSource: type: gh/webhook-forward pollInterval: 45s + repo: owner/sample + path: ${tempDir.path} '''); // When loading the app config from disk. @@ -840,46 +885,51 @@ projects: }); /// ```gherkin - /// Scenario: Reject gh/webhook-forward for non-GitHub providers - /// Given a Gitea project config that opts into gh/webhook-forward + /// Scenario: Reject a legacy Gitea provider that conflicts with gh/webhook-forward + /// Given a project config whose legacy tracker provider is gitea + /// And the project opts into gh/webhook-forward /// When loading the app config from disk - /// Then loading fails with a clear provider compatibility error + /// Then loading fails with a clear conflict error /// ``` - test('Reject gh/webhook-forward for non-GitHub providers', () async { - // Given a Gitea project config that opts into gh/webhook-forward. - final tempDir = await Directory.systemTemp.createTemp( - 'cws-webhook-forward-gitea-', - ); - final configFile = File(p.join(tempDir.path, 'agent-orchestrator.yaml')); - await configFile.writeAsString(''' + test( + 'Reject a legacy Gitea provider that conflicts with gh/webhook-forward', + () async { + // Given a project config whose legacy tracker provider is gitea. + final tempDir = await Directory.systemTemp.createTemp( + 'cws-webhook-forward-gitea-', + ); + final configFile = File( + p.join(tempDir.path, 'agent-orchestrator.yaml'), + ); + await configFile.writeAsString(''' projects: sample: issueTracker: provider: gitea - repo: owner/sample - path: ${tempDir.path} - issueAssistant: eventSource: type: gh/webhook-forward + repo: owner/sample + path: ${tempDir.path} '''); - // When loading the app config from disk. - final load = AppConfig.load(configFile.path); + // When loading the app config from disk. + final load = AppConfig.load(configFile.path); - // Then loading fails with a clear provider compatibility error. - await expectLater( - load, - throwsA( - isA().having( - (error) => error.message, - 'message', - contains( - 'gh/webhook-forward requires issueTracker.provider: github', + // Then loading fails with a clear conflict error. + await expectLater( + load, + throwsA( + isA().having( + (error) => error.message, + 'message', + contains( + 'gh/webhook-forward implies issue tracker provider github, but the legacy configured provider is gitea', + ), ), ), - ), - ); - }); + ); + }, + ); /// ```gherkin /// Scenario: Load tea/gosmee as the issue event source @@ -896,12 +946,10 @@ projects: projects: sample: issueTracker: - provider: gitea - repo: owner/sample - path: ${tempDir.path} - issueAssistant: eventSource: type: tea/gosmee + repo: owner/sample + path: ${tempDir.path} '''); // When loading the app config from disk. @@ -910,7 +958,7 @@ projects: // Then the project keeps tea/gosmee as its event source. expect( config.projects['sample']!.issueAssistant.eventSource.type, - IssueAssistantEventSourceKind.teaGosmee, + IssueTrackerEventSourceKind.teaGosmee, ); // And the Gitea provider remains valid for that project. @@ -918,44 +966,51 @@ projects: }); /// ```gherkin - /// Scenario: Reject tea/gosmee for non-Gitea providers - /// Given a GitHub project config that opts into tea/gosmee + /// Scenario: Reject a legacy GitHub provider that conflicts with tea/gosmee + /// Given a project config whose legacy tracker provider is github + /// And the project opts into tea/gosmee /// When loading the app config from disk - /// Then loading fails with a clear provider compatibility error + /// Then loading fails with a clear conflict error /// ``` - test('Reject tea/gosmee for non-Gitea providers', () async { - // Given a GitHub project config that opts into tea/gosmee. - final tempDir = await Directory.systemTemp.createTemp( - 'cws-gosmee-github-', - ); - final configFile = File(p.join(tempDir.path, 'agent-orchestrator.yaml')); - await configFile.writeAsString(''' + test( + 'Reject a legacy GitHub provider that conflicts with tea/gosmee', + () async { + // Given a project config whose legacy tracker provider is github. + final tempDir = await Directory.systemTemp.createTemp( + 'cws-gosmee-github-', + ); + final configFile = File( + p.join(tempDir.path, 'agent-orchestrator.yaml'), + ); + await configFile.writeAsString(''' projects: sample: issueTracker: provider: github - repo: owner/sample - path: ${tempDir.path} - issueAssistant: eventSource: type: tea/gosmee + repo: owner/sample + path: ${tempDir.path} '''); - // When loading the app config from disk. - final load = AppConfig.load(configFile.path); + // When loading the app config from disk. + final load = AppConfig.load(configFile.path); - // Then loading fails with a clear provider compatibility error. - await expectLater( - load, - throwsA( - isA().having( - (error) => error.message, - 'message', - contains('tea/gosmee requires issueTracker.provider: gitea'), + // Then loading fails with a clear conflict error. + await expectLater( + load, + throwsA( + isA().having( + (error) => error.message, + 'message', + contains( + 'tea/gosmee implies issue tracker provider gitea, but the legacy configured provider is github', + ), + ), ), - ), - ); - }); + ); + }, + ); /// ```gherkin /// Scenario: Reject snake_case config keys @@ -1097,16 +1152,6 @@ projects: .replaceFirst(' # timeout: 10m', ' timeout: 10m') .replaceFirst(' # issueAssistant:', ' issueAssistant:') .replaceFirst(' # enabled: true', ' enabled: true') - .replaceFirst(' # eventSource:', ' eventSource:') - .replaceFirst(' # type: gh/gosmee', ' type: gh/gosmee') - .replaceFirst( - ' # reconciliation: startup-only', - ' reconciliation: startup-only', - ) - .replaceFirst( - ' # reconcileInterval: 30s', - ' reconcileInterval: 30s', - ) .replaceFirst( ' # mentionTriggers: ["@helper"]', ' mentionTriggers: ["@helper"]', @@ -1138,10 +1183,8 @@ projects: '@helper', ]); expect( - (config.projects['sample']!.issueAssistant.eventSource - as ChannelIssueEventSourceConfig) - .reconciliation, - IssueAssistantReconciliationKind.startupOnly, + config.projects['sample']!.issueAssistant.eventSource, + isA(), ); }); }); diff --git a/test/issue_event_source/routed_issue_event_source_test.dart b/test/issue_event_source/routed_issue_event_source_test.dart index 74290eb..d573975 100644 --- a/test/issue_event_source/routed_issue_event_source_test.dart +++ b/test/issue_event_source/routed_issue_event_source_test.dart @@ -25,16 +25,16 @@ void main() { final webhookForward = _RecordingIssueEventSource(); final routed = RoutedIssueEventSource( sources: { - IssueAssistantEventSourceKind.ghPolling: polling, - IssueAssistantEventSourceKind.teaPolling: polling, - IssueAssistantEventSourceKind.ghWebhookForward: webhookForward, + IssueTrackerEventSourceKind.ghPolling: polling, + IssueTrackerEventSourceKind.teaPolling: polling, + IssueTrackerEventSourceKind.ghWebhookForward: webhookForward, }, ); // And a project that uses gh/webhook-forward with startup-only reconciliation. final project = _project( - eventSource: IssueAssistantEventSourceKind.ghWebhookForward, - reconciliation: IssueAssistantReconciliationKind.startupOnly, + eventSource: IssueTrackerEventSourceKind.ghWebhookForward, + reconciliation: IssueTrackerReconciliationKind.startupOnly, ); // When the routed event source starts in long-running mode. @@ -64,16 +64,16 @@ void main() { final webhookForward = _RecordingIssueEventSource(); final routed = RoutedIssueEventSource( sources: { - IssueAssistantEventSourceKind.ghPolling: polling, - IssueAssistantEventSourceKind.teaPolling: polling, - IssueAssistantEventSourceKind.ghWebhookForward: webhookForward, + IssueTrackerEventSourceKind.ghPolling: polling, + IssueTrackerEventSourceKind.teaPolling: polling, + IssueTrackerEventSourceKind.ghWebhookForward: webhookForward, }, ); // And a project that uses gh/webhook-forward with continuous reconciliation. final project = _project( - eventSource: IssueAssistantEventSourceKind.ghWebhookForward, - reconciliation: IssueAssistantReconciliationKind.continuous, + eventSource: IssueTrackerEventSourceKind.ghWebhookForward, + reconciliation: IssueTrackerReconciliationKind.continuous, ); // When the routed event source starts in long-running mode. @@ -115,8 +115,8 @@ class _RecordingIssueEventSource implements IssueEventSource { } ProjectConfig _project({ - required IssueAssistantEventSourceKind eventSource, - required IssueAssistantReconciliationKind reconciliation, + required IssueTrackerEventSourceKind eventSource, + required IssueTrackerReconciliationKind reconciliation, }) { return ProjectConfig( key: 'sample', diff --git a/test/workspace/workspace_manager_test.dart b/test/workspace/workspace_manager_test.dart index ac50e6b..78eabd6 100644 --- a/test/workspace/workspace_manager_test.dart +++ b/test/workspace/workspace_manager_test.dart @@ -78,7 +78,7 @@ void main() { issueAssistant: IssueAssistantConfig( enabled: true, eventSource: const PollingIssueEventSourceConfig( - type: IssueAssistantEventSourceKind.ghPolling, + type: IssueTrackerEventSourceKind.ghPolling, pollInterval: Duration(seconds: 30), ), maxConcurrentIssues: 3, @@ -131,7 +131,7 @@ void main() { issueAssistant: IssueAssistantConfig( enabled: true, eventSource: const PollingIssueEventSourceConfig( - type: IssueAssistantEventSourceKind.glabPolling, + type: IssueTrackerEventSourceKind.glabPolling, pollInterval: Duration(seconds: 30), ), maxConcurrentIssues: 3,