Skip to content

feat: Implement Transfer Rules Engine for Cross-Repository Issue Routing#22

Merged
Kavirubc merged 5 commits intomainfrom
feature/repo-tranfer-010
Feb 4, 2026
Merged

feat: Implement Transfer Rules Engine for Cross-Repository Issue Routing#22
Kavirubc merged 5 commits intomainfrom
feature/repo-tranfer-010

Conversation

@Kavirubc
Copy link
Copy Markdown
Contributor

@Kavirubc Kavirubc commented Feb 4, 2026

Summary

Implements the Transfer Rules Engine for cross-repository issue routing as specified in issue #15. This enables automated issue transfers based on configurable rules matching labels, title/body content, and authors.

Changes

Core Transfer Logic

  • New Package: internal/transfer/ with rule matching engine
    • RuleMatcher evaluates rules by priority (descending order)
    • Case-insensitive matching for all conditions
    • Support for AND (labels) and OR (labels_any, title_contains, body_contains, author) logic
    • 12 comprehensive unit tests covering all scenarios

Configuration

  • Added TransferRule and TransferConfig types to internal/core/config/config.go
  • Supports rule conditions: labels (ALL), labels_any (ANY), title_contains, body_contains, author
  • Rules can be enabled/disabled and sorted by priority

Pipeline Integration

  • Moved transfer_check step before similarity_search in pipeline (prevents duplicate detection for transfers)
  • transfer_check sets skip_duplicate_detection metadata when transfer is detected
  • Updated similarity_search to respect skip flag

GitHub GraphQL Integration

  • New: internal/integrations/github/graphql.go - GraphQL client implementation
  • Implements transferIssue mutation with proper node ID resolution
  • Updated Client.TransferIssue() to use GraphQL API (returns new issue URL)
  • Requires authenticated GraphQL client (initialized in auth.go)

Action Executor

  • Updated to handle new TransferIssue signature returning (string, error)
  • Logs new issue URL after successful transfer
  • Sets Result.Transferred = true on success

Architecture

Config (YAML)         transfer/RuleMatcher       pipeline.Context
transfer_rules  -->   Match(issue)          -->  TransferTarget, Metadata
                           |
                           v
                   ActionExecutor --> GitHub GraphQL TransferIssue()

Configuration Example

transfer:
  enabled: true
  rules:
    - name: "critical-bugs-to-core"
      priority: 100
      target: "org/core-repo"
      labels: ["bug", "critical"]
      
    - name: "feature-requests-to-roadmap"
      priority: 50
      target: "org/roadmap"
      labels_any: ["feature", "enhancement"]
      title_contains: ["feature request", "add support"]

Testing

  • ✅ All transfer package tests pass (12/12)
  • ✅ All integration tests pass
  • ✅ Build successful with no errors
  • ✅ Updated GitHub client tests for new signature

Notes

  • Transfer requires admin/write access to both source and target repositories
  • Validation ensures target repo format is "owner/repo"
  • GraphQL client only initialized when authentication token is provided

Closes #15

@Kavirubc Kavirubc changed the title Implement Cross Repo Transfer feat: Implement Transfer Rules Engine for Cross-Repository Issue Routing Feb 4, 2026
@Kavirubc Kavirubc marked this pull request as ready for review February 4, 2026 06:19
@Kavirubc Kavirubc requested a review from Copilot February 4, 2026 06:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements a comprehensive Transfer Rules Engine for automated cross-repository issue routing, addressing issue #15. The implementation adds rule-based matching with configurable conditions (labels, content patterns, authors), GraphQL-based issue transfer functionality, and pipeline integration to prevent duplicate detection for transferred issues.

Changes:

  • New internal/transfer/ package with rule matching engine supporting priority-based evaluation and case-insensitive matching with comprehensive test coverage (12 tests)
  • GraphQL client implementation for GitHub's transferIssue mutation with proper node ID resolution
  • Pipeline reordering to execute transfer checks before similarity search, with metadata-based skip logic
  • Configuration types for transfer rules with support for AND/OR logic across multiple condition types

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
internal/transfer/types.go Defines data structures for issue input and match results
internal/transfer/matcher.go Implements rule matching engine with priority-based evaluation and case-insensitive matching logic
internal/transfer/matcher_test.go Comprehensive test coverage for all matching scenarios (12 test cases)
internal/steps/transfer_check.go Pipeline step that evaluates transfer rules and sets transfer target with skip metadata
internal/steps/similarity.go Updated to skip duplicate detection when transfer is detected
internal/steps/action_executor.go Updated to handle new TransferIssue signature returning URL and set Transferred flag
internal/integrations/github/graphql.go New GraphQL client with transferIssue mutation and node ID resolution methods
internal/integrations/github/client.go TransferIssue implementation using GraphQL API with validation and error handling
internal/integrations/github/auth.go GraphQL client initialization for authenticated operations
internal/integrations/github/client_test.go Updated tests for new TransferIssue signature
internal/core/pipeline/registry.go Reordered pipeline to execute transfer_check before similarity_search
internal/core/config/config.go Added TransferRule and TransferConfig types with merge logic
.claude/sessions/2026-02-04-0000.md Development session tracking metadata
.claude/sessions/.current-session Current session reference

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 66 to +69
// Validate input format
parts := strings.Split(targetRepo, "/")
if len(parts) != 2 {
return fmt.Errorf("invalid targetRepo format: expected 'owner/repo', got '%s'", targetRepo)
return "", fmt.Errorf("invalid targetRepo format: expected 'owner/repo', got '%s'", targetRepo)
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation doesn't trim whitespace from the target repository string before parsing. A target like "owner/repo " (with trailing space) would pass validation but might cause issues with the GraphQL API. Consider trimming whitespace from the targetRepo parameter before validation and parsing to handle user input more robustly.

Copilot uses AI. Check for mistakes.
Comment thread internal/integrations/github/graphql.go Outdated
Comment on lines +81 to +82
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("GraphQL request failed with status %d: %s", resp.StatusCode, string(respBody))
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message includes the full response body which could potentially leak sensitive information in logs. Consider truncating the response body or only including a sanitized portion of it in the error message to avoid exposing sensitive data that might be in error responses.

Copilot uses AI. Check for mistakes.
if err := json.Unmarshal(data, &result); err != nil {
return "", fmt.Errorf("failed to parse transfer result: %w", err)
}

Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function returns the URL without validating that the transfer was successful. Consider adding a check to ensure the returned URL is not empty, similar to the validation done in GetIssueNodeID and GetRepositoryNodeID. An empty URL could indicate a failed transfer that wasn't caught by error handling.

Suggested change
if result.TransferIssue.Issue.URL == "" {
return "", fmt.Errorf("issue transfer failed: empty URL returned")
}

Copilot uses AI. Check for mistakes.
Comment thread internal/core/config/config.go Outdated
Comment on lines +239 to +241
// Transfer: child overrides if enabled or has rules
if child.Transfer.Enabled || len(child.Transfer.Rules) > 0 {
result.Transfer = child.Transfer
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The merge logic for Transfer config only overrides if child.Transfer.Enabled is true or has rules. This means a child config cannot explicitly disable transfer if the parent has it enabled. Consider always merging the Transfer config if any field is set in the child, or add explicit handling for the case where a child wants to disable an enabled parent transfer configuration. For consistency with other boolean fields like CrossRepoSearch (line 232), consider whether Transfer.Enabled should be merged regardless of its value.

Suggested change
// Transfer: child overrides if enabled or has rules
if child.Transfer.Enabled || len(child.Transfer.Rules) > 0 {
result.Transfer = child.Transfer
// Transfer.Enabled: always take the child value so it can override parent true -> false and vice versa
result.Transfer.Enabled = child.Transfer.Enabled
// Transfer.Rules: child overrides rules if non-empty; otherwise inherit from parent
if len(child.Transfer.Rules) > 0 {
result.Transfer.Rules = child.Transfer.Rules

Copilot uses AI. Check for mistakes.
Comment thread internal/transfer/matcher.go Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Add whitespace trimming for targetRepo input validation
- Truncate error response bodies to prevent sensitive data leaks
- Add empty URL validation after transfer completion
- Fix Transfer config merge logic to allow child to disable parent
@Kavirubc Kavirubc merged commit 53abf8b into main Feb 4, 2026
3 checks passed
@Kavirubc Kavirubc deleted the feature/repo-tranfer-010 branch February 4, 2026 06:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v0.1.0] Implement Cross-Repo Issue Transfer (GraphQL)

2 participants